Branch for 0.9.13.x releases
diff --git a/trunk/LICENSE b/trunk/LICENSE
new file mode 100644
index 0000000..9d18d4c
--- /dev/null
+++ b/trunk/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ 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.
diff --git a/trunk/src/DEPS b/trunk/src/DEPS
new file mode 100644
index 0000000..5adbc13
--- /dev/null
+++ b/trunk/src/DEPS
@@ -0,0 +1,212 @@
+# Copyright 2009 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.
+
+vars = {
+ "chromium_trunk": "http://src.chromium.org/svn/trunk",
+ "chromium_revision": "@55218",
+ "chromium_deps_root": "src/third_party/chromium_deps",
+
+ # We should change this back to a tag once Pagespeed cuts a new branch.
+ #"libpagespeed_trunk": "http://page-speed.googlecode.com/svn/lib/tags/lib-1.9.3.0",
+ #"libpagespeed_revision": "@head",
+ "libpagespeed_trunk": "http://page-speed.googlecode.com/svn/lib/trunk",
+ "libpagespeed_revision": "@1300",
+ "libpagespeed_deps_root": "src/third_party/libpagespeed_deps",
+
+ "mod_spdy_trunk": "http://mod-spdy.googlecode.com/svn/trunk",
+ "mod_spdy_revision": "@143",
+ "mod_spdy_deps_root": "src/third_party/mod_spdy_deps",
+
+ "serf_src": "http://serf.googlecode.com/svn/tags/0.3.1",
+ "serf_revision": "@head",
+
+ "apr_src": "http://svn.apache.org/repos/asf/apr/apr/tags/1.4.2",
+ "apr_revision": "@head",
+
+ "aprutil_src": "http://svn.apache.org/repos/asf/apr/apr-util/tags/1.3.9",
+ "aprutil_revision": "@head",
+
+ "apache_httpd_src": "http://svn.apache.org/repos/asf/httpd/httpd/tags/2.2.15",
+ "apache_httpd_revision": "@head",
+
+ "opencv_src": "https://code.ros.org/svn/opencv/tags/2.1",
+ "opencv_revision": "@head",
+
+ "gflags_root": "http://google-gflags.googlecode.com/svn/tags/gflags-1.3/src",
+ "gflags_revision": "@head",
+
+ "google_sparsehash_root": "http://google-sparsehash.googlecode.com/svn/tags/sparsehash-1.8.1/src",
+ "google_sparsehash_revision": "@head",
+}
+
+deps = {
+ # Fetch dependent DEPS so we can sync our other dependencies relative
+ # to them.
+ Var("chromium_deps_root"):
+ File(Var("chromium_trunk") + "/src/DEPS" + Var("chromium_revision")),
+
+ Var("libpagespeed_deps_root"):
+ File(Var("libpagespeed_trunk") + "/src/DEPS" +
+ Var("libpagespeed_revision")),
+
+ # Other dependencies.
+ "src/base":
+ Var("libpagespeed_trunk") + "/src/base" + Var("libpagespeed_revision"),
+
+ "src/build/temp_gyp":
+ Var("libpagespeed_trunk") + "/src/build/temp_gyp" +
+ Var("libpagespeed_revision"),
+
+ "src/build/internal": From(Var("libpagespeed_deps_root")),
+ "src/build/linux": From(Var("libpagespeed_deps_root")),
+ "src/build/mac": From(Var("libpagespeed_deps_root")),
+ "src/build/util": From(Var("libpagespeed_deps_root")),
+ "src/build/win": From(Var("libpagespeed_deps_root")),
+
+ "src/googleurl": From(Var("chromium_deps_root")),
+
+ "src/googleurl_noicu":
+ (Var("libpagespeed_trunk") + "/src/googleurl_noicu" +
+ Var("libpagespeed_revision")),
+
+ "src/testing": From(Var("libpagespeed_deps_root")),
+ "src/testing/gtest": From(Var("chromium_deps_root")),
+
+ "src/third_party/apache":
+ Var("mod_spdy_trunk") + "/src/third_party/apache" +
+ Var("mod_spdy_revision"),
+
+ "src/third_party/apache/apr/src":
+ Var("apr_src") + Var("apr_revision"),
+
+ "src/third_party/apache/aprutil/src":
+ Var("aprutil_src") + Var("aprutil_revision"),
+
+ "src/third_party/apache/httpd/src/include":
+ Var("apache_httpd_src") + "/include" + Var("apache_httpd_revision"),
+
+ "src/third_party/apache/httpd/src/os":
+ Var("apache_httpd_src") + "/os" + Var("apache_httpd_revision"),
+
+ "src/third_party/chromium/src/base": From(Var("libpagespeed_deps_root")),
+ "src/third_party/chromium/src/build": From(Var("libpagespeed_deps_root")),
+ "src/third_party/chromium/src/chrome/tools/build":
+ File(Var("chromium_trunk") + "/src/chrome/tools/build/version.py" +
+ Var("chromium_revision")),
+ "src/third_party/chromium/src/net/base": From(Var("libpagespeed_deps_root")),
+
+ "src/third_party/chromium/src/net/tools/dump_cache":
+ Var("chromium_trunk") + "/src/net/tools/dump_cache" +
+ Var("chromium_revision"),
+
+ "src/third_party/gflags/src": Var("gflags_root") + Var("gflags_revision"),
+ "src/third_party/google-sparsehash/src":
+ Var("google_sparsehash_root") + Var("google_sparsehash_revision"),
+ "src/third_party/libjpeg": From(Var("libpagespeed_deps_root")),
+
+ "src/third_party/icu":
+ (Var("libpagespeed_trunk") + "/src/third_party/icu" +
+ Var("libpagespeed_revision")),
+
+ "src/third_party/libpagespeed/src/build":
+ (Var("libpagespeed_trunk") + "/src/build" +
+ Var("libpagespeed_revision")),
+
+ "src/third_party/libpagespeed/src/build/internal":
+ Var("chromium_trunk") + "/src/build/internal" + Var("chromium_revision"),
+
+ "src/third_party/libpagespeed/src/pagespeed/core":
+ (Var("libpagespeed_trunk") + "/src/pagespeed/core" +
+ Var("libpagespeed_revision")),
+
+ "src/third_party/libpagespeed/src/pagespeed/image_compression":
+ (Var("libpagespeed_trunk") + "/src/pagespeed/image_compression" +
+ Var("libpagespeed_revision")),
+
+ "src/third_party/libpagespeed/src/pagespeed/jsminify":
+ (Var("libpagespeed_trunk") + "/src/pagespeed/jsminify" +
+ Var("libpagespeed_revision")),
+
+ "src/third_party/libpagespeed/src/pagespeed/l10n":
+ (Var("libpagespeed_trunk") + "/src/pagespeed/l10n" +
+ Var("libpagespeed_revision")),
+
+ "src/third_party/libpagespeed/src/pagespeed/proto":
+ (Var("libpagespeed_trunk") + "/src/pagespeed/proto" +
+ Var("libpagespeed_revision")),
+
+ "src/third_party/libpagespeed/src/third_party":
+ (Var("libpagespeed_trunk") + "/src/third_party" +
+ Var("libpagespeed_revision")),
+
+ "src/third_party/protobuf2": From(Var("libpagespeed_deps_root")),
+ "src/third_party/protobuf2/src": From(Var("chromium_deps_root")),
+
+ "src/third_party/opencv/src/opencv/src":
+ Var("opencv_src") + "/opencv/src" + Var("opencv_revision"),
+
+ "src/third_party/opencv/src/opencv/include":
+ Var("opencv_src") + "/opencv/include" + Var("opencv_revision"),
+
+ "src/third_party/opencv/src/opencv/3rdparty/flann":
+ Var("opencv_src") + "/opencv/3rdparty/flann" + Var("opencv_revision"),
+
+ "src/third_party/opencv/src/opencv/3rdparty/lapack":
+ Var("opencv_src") + "/opencv/3rdparty/lapack" + Var("opencv_revision"),
+
+ "src/third_party/opencv/src/opencv/3rdparty/include":
+ Var("opencv_src") + "/opencv/3rdparty/include" + Var("opencv_revision"),
+
+ "src/third_party/serf/src": Var("serf_src") + Var("serf_revision"),
+
+ "src/third_party/zlib": From(Var("libpagespeed_deps_root")),
+
+ "src/tools/data_pack": From(Var("libpagespeed_deps_root")),
+ "src/tools/grit": From(Var("libpagespeed_deps_root")),
+ "src/tools/gyp": From(Var("chromium_deps_root")),
+}
+
+
+deps_os = {
+ "win": {
+ "src/third_party/cygwin": From(Var("chromium_deps_root")),
+ "src/third_party/python_24": From(Var("chromium_deps_root")),
+ },
+ "mac": {
+ },
+ "unix": {
+ },
+}
+
+
+include_rules = [
+ # Everybody can use some things.
+ "+base",
+ "+build",
+]
+
+
+# checkdeps.py shouldn't check include paths for files in these dirs:
+skip_child_includes = [
+ "testing",
+]
+
+
+hooks = [
+ {
+ # A change to a .gyp, .gypi, or to GYP itself should run the generator.
+ "pattern": ".",
+ "action": ["python", "src/build/gyp_chromium"],
+ },
+]
diff --git a/trunk/src/build/all.gyp b/trunk/src/build/all.gyp
new file mode 100644
index 0000000..618f901
--- /dev/null
+++ b/trunk/src/build/all.gyp
@@ -0,0 +1,28 @@
+# Copyright 2009 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.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'All',
+ 'type': 'none',
+ 'xcode_create_dependents_test_runner': 1,
+ 'dependencies': [
+ '../net/instaweb/instaweb.gyp:*',
+ '../net/instaweb/instaweb_core.gyp:*',
+ '../net/instaweb/instaweb_html_rewriter.gyp:*',
+ '../net/instaweb/mod_pagespeed.gyp:*',
+ 'install.gyp:*',
+ ],} ]
+}
diff --git a/trunk/src/build/build_util.gyp b/trunk/src/build/build_util.gyp
new file mode 100644
index 0000000..e18276c
--- /dev/null
+++ b/trunk/src/build/build_util.gyp
@@ -0,0 +1,85 @@
+# Copyright (c) 2009 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+ 'variables': {
+ 'version_py_path': 'version.py',
+ 'instaweb_path': '<(DEPTH)/net/instaweb',
+ 'version_path': '<(instaweb_path)/public/VERSION',
+ 'version_h_in_path': '<(instaweb_path)/public/version.h.in',
+ 'public_path' : 'net/instaweb/public',
+ 'version_h_path': '<(SHARED_INTERMEDIATE_DIR)/<(public_path)/version.h',
+ 'lastchange_out_path': '<(SHARED_INTERMEDIATE_DIR)/build/LASTCHANGE',
+ },
+ 'targets': [
+ {
+ 'target_name': 'lastchange',
+ 'type': 'none',
+ 'variables': {
+ 'default_lastchange_path': '../LASTCHANGE.in',
+ },
+ 'actions': [
+ {
+ 'action_name': 'lastchange',
+ 'inputs': [
+ # Note: <(default_lastchange_path) is optional,
+ # so it doesn't show up in inputs.
+ 'util/lastchange.py',
+ ],
+ 'outputs': [
+ '<(lastchange_out_path).always',
+ '<(lastchange_out_path)',
+ ],
+ 'action': [
+ 'python', '<@(_inputs)',
+ '-o', '<(lastchange_out_path)',
+ '-d', '<(default_lastchange_path)',
+ ],
+ 'message': 'Extracting last change to <(lastchange_out_path)',
+ 'process_outputs_as_sources': '1',
+ },
+ ],
+ },
+ {
+ 'target_name': 'mod_pagespeed_version_header',
+ 'type': 'none',
+ 'dependencies': [
+ 'lastchange',
+ ],
+ 'actions': [
+ {
+ 'action_name': 'version_header',
+ 'inputs': [
+ '<(version_path)',
+ '<(lastchange_out_path)',
+ '<(version_h_in_path)',
+ ],
+ 'outputs': [
+ '<(version_h_path)',
+ ],
+ 'action': [
+ 'python',
+ '<(version_py_path)',
+ '-f', '<(version_path)',
+ '-f', '<(lastchange_out_path)',
+ '<(version_h_in_path)',
+ '<@(_outputs)',
+ ],
+ 'message': 'Generating version header file: <@(_outputs)',
+ },
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(SHARED_INTERMEDIATE_DIR)',
+ ],
+ },
+ },
+ ]
+}
+
+# Local Variables:
+# tab-width:2
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=2 shiftwidth=2:
diff --git a/trunk/src/build/common.gypi b/trunk/src/build/common.gypi
new file mode 100644
index 0000000..188a9ce
--- /dev/null
+++ b/trunk/src/build/common.gypi
@@ -0,0 +1,84 @@
+# Copyright 2009 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.
+
+{
+ 'variables': {
+ # Make sure we link statically so everything gets linked into a
+ # single shared object.
+ 'library': 'static_library',
+
+ # We're building a shared library, so everything needs to be built
+ # with Position-Independent Code.
+ 'linux_fpic': 1,
+
+ # Define the overridable use_system_libs variable in its own
+ # nested block, so it's available for use in the conditions block
+ # below.
+ 'variables': {
+ 'use_system_libs%': 0,
+ },
+
+ 'conditions': [
+ ['use_system_libs==1', {
+ 'use_system_apache_dev': 1,
+ 'use_system_libjpeg': 1,
+ 'use_system_libpng': 1,
+ 'use_system_opencv': 1,
+ 'use_system_zlib': 1,
+ },{
+ 'use_system_apache_dev%': 0,
+ }],
+ ],
+ },
+ 'includes': [
+ '../third_party/libpagespeed/src/build/common.gypi',
+ ],
+ 'target_defaults': {
+ 'conditions': [
+ ['OS == "linux"', {
+ 'cflags': [
+ # Our dependency on OpenCV need us to turn on exceptions.
+ '-fexceptions',
+ # Now we are using exceptions. -fno-asynchronous-unwind-tables is
+ # set in libpagespeed's common.gypi. Now enable it.
+ '-fasynchronous-unwind-tables',
+ ],
+ 'cflags_cc': [
+ '-frtti', # Hardy's g++ 4.2 <trl/function> uses typeid
+ ],
+ }],
+ ['OS == "mac"', {
+ 'xcode_settings':{
+ 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', # -fexceptions
+ 'GCC_ENABLE_CPP_RTTI': 'YES', # -frtti
+ },
+ }],
+ ],
+
+ # Permit building us with coverage information
+ 'configurations': {
+ 'Debug_Coverage': {
+ 'inherit_from': ['Debug'],
+ 'cflags': [
+ '-ftest-coverage',
+ '-fprofile-arcs',
+ ],
+ 'ldflags': [
+ # takes care of -lgcov for us, but can be in a build configuration
+ '-ftest-coverage -fprofile-arcs',
+ ],
+ },
+ },
+ },
+}
diff --git a/trunk/src/build/compiler_version.py b/trunk/src/build/compiler_version.py
new file mode 100755
index 0000000..be71db2
--- /dev/null
+++ b/trunk/src/build/compiler_version.py
@@ -0,0 +1,24 @@
+#!/usr/bin/python
+
+# 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.
+
+# This script is wrapper for the Chromium version of compiler_version.py.
+
+import os
+
+script_dir = os.path.dirname(__file__)
+chrome_src = os.path.normpath(os.path.join(script_dir, os.pardir, 'third_party', 'chromium', 'src'))
+
+execfile(os.path.join(chrome_src, 'build', 'compiler_version.py'))
diff --git a/trunk/src/build/features_override.gypi b/trunk/src/build/features_override.gypi
new file mode 100644
index 0000000..a655733
--- /dev/null
+++ b/trunk/src/build/features_override.gypi
@@ -0,0 +1,18 @@
+# Copyright 2009 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.
+
+# Chromium expects this file to be here, but for our (Page Speed) purposes, it
+# doesn't need to actually do anything.
+
+{}
diff --git a/trunk/src/build/gyp_chromium b/trunk/src/build/gyp_chromium
new file mode 100755
index 0000000..de60768
--- /dev/null
+++ b/trunk/src/build/gyp_chromium
@@ -0,0 +1,24 @@
+#!/usr/bin/python
+
+# Copyright 2009 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.
+
+# This script is wrapper for the Chromium version of gyp_chromium.
+
+import os
+
+script_dir = os.path.dirname(__file__)
+chrome_src = os.path.normpath(os.path.join(script_dir, os.pardir, 'third_party', 'chromium', 'src'))
+
+execfile(os.path.join(chrome_src, 'build', 'gyp_chromium'))
diff --git a/trunk/src/build/install.gyp b/trunk/src/build/install.gyp
new file mode 100644
index 0000000..0221db5
--- /dev/null
+++ b/trunk/src/build/install.gyp
@@ -0,0 +1,191 @@
+# Copyright (c) 2010 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+ 'variables': {
+ 'install_path': '<(DEPTH)/install',
+ 'version_py_path': '<(DEPTH)/build/version.py',
+ 'version_path': '<(DEPTH)/net/instaweb/public/VERSION',
+ 'lastchange_path': '<(SHARED_INTERMEDIATE_DIR)/build/LASTCHANGE',
+ 'branding_dir': '<(install_path)/common',
+ },
+ 'conditions': [
+ ['OS=="linux"', {
+ 'variables': {
+ 'version' : '<!(python <(version_py_path) -f <(version_path) -t "@MAJOR@.@MINOR@.@BUILD@.@PATCH@")',
+ 'revision' : '<!(python <(DEPTH)/build/util/lastchange.py | cut -d "=" -f 2)',
+ 'packaging_files_common': [
+ '<(install_path)/common/apt.include',
+ '<(install_path)/common/mod-pagespeed/mod-pagespeed.info',
+ '<(install_path)/common/installer.include',
+ '<(install_path)/common/repo.cron',
+ '<(install_path)/common/rpm.include',
+ '<(install_path)/common/rpmrepo.cron',
+ '<(install_path)/common/updater',
+ '<(install_path)/common/variables.include',
+ '<(install_path)/common/BRANDING',
+ '<(install_path)/common/pagespeed.load.template',
+ '<(install_path)/common/pagespeed.conf.template',
+ ],
+ 'packaging_files_deb': [
+ '<(install_path)/debian/build.sh',
+ '<(install_path)/debian/changelog.template',
+ '<(install_path)/debian/conffiles',
+ '<(install_path)/debian/control.template',
+ '<(install_path)/debian/postinst',
+ '<(install_path)/debian/postrm',
+ '<(install_path)/debian/prerm',
+ ],
+ 'packaging_files_rpm': [
+ '<(install_path)/rpm/build.sh',
+ '<(install_path)/rpm/mod-pagespeed.spec.template',
+ ],
+ 'packaging_files_binaries': [
+ '<(PRODUCT_DIR)/libmod_pagespeed.so',
+ ],
+ 'flock_bash': ['flock', '--', '/tmp/linux_package_lock', 'bash'],
+ 'deb_build': '<(PRODUCT_DIR)/install/debian/build.sh',
+ 'rpm_build': '<(PRODUCT_DIR)/install/rpm/build.sh',
+ 'deb_cmd': ['<@(flock_bash)', '<(deb_build)', '-o' '<(PRODUCT_DIR)',
+ '-b', '<(PRODUCT_DIR)', '-a', '<(target_arch)'],
+ 'rpm_cmd': ['<@(flock_bash)', '<(rpm_build)', '-o' '<(PRODUCT_DIR)',
+ '-b', '<(PRODUCT_DIR)', '-a', '<(target_arch)'],
+ 'conditions': [
+ ['target_arch=="ia32"', {
+ 'deb_arch': 'i386',
+ 'rpm_arch': 'i386',
+ }],
+ ['target_arch=="x64"', {
+ 'deb_arch': 'amd64',
+ 'rpm_arch': 'x86_64',
+ }],
+ ],
+ },
+ 'targets': [
+ {
+ 'target_name': 'linux_installer_configs',
+ 'type': 'none',
+ # Add these files to the build output so the build archives will be
+ # "hermetic" for packaging.
+ 'copies': [
+ {
+ 'destination': '<(PRODUCT_DIR)/install/debian/',
+ 'files': [
+ '<@(packaging_files_deb)',
+ ]
+ },
+ {
+ 'destination': '<(PRODUCT_DIR)/install/rpm/',
+ 'files': [
+ '<@(packaging_files_rpm)',
+ ]
+ },
+ {
+ 'destination': '<(PRODUCT_DIR)/install/common/',
+ 'files': [
+ '<@(packaging_files_common)',
+ ]
+ },
+ ],
+ 'actions': [
+ {
+ 'action_name': 'save_build_info',
+ 'inputs': [
+ '<(branding_dir)/BRANDING',
+ '<(version_path)',
+ '<(lastchange_path)',
+ ],
+ 'outputs': [
+ '<(PRODUCT_DIR)/installer/version.txt',
+ ],
+ # Just output the default version info variables.
+ 'action': [
+ 'python', '<(version_py_path)',
+ '-f', '<(branding_dir)/BRANDING',
+ '-f', '<(version_path)',
+ '-f', '<(lastchange_path)',
+ '-o', '<@(_outputs)'
+ ],
+ },
+ ],
+ },
+ {
+ 'target_name': 'linux_packages',
+ 'suppress_wildcard': 1,
+ 'type': 'none',
+ 'dependencies': [
+ 'linux_package_deb',
+ 'linux_package_rpm',
+ ],
+ },
+ {
+ 'target_name': 'linux_package_deb',
+ 'suppress_wildcard': 1,
+ 'type': 'none',
+ 'dependencies': [
+ '<(DEPTH)/net/instaweb/mod_pagespeed.gyp:mod_pagespeed',
+ 'linux_installer_configs',
+ ],
+ 'actions': [
+ {
+ 'variables': {
+ 'channel': 'beta',
+ },
+ 'action_name': 'deb_package_<(channel)',
+ 'process_outputs_as_sources': 1,
+ 'inputs': [
+ '<(deb_build)',
+ '<@(packaging_files_binaries)',
+ '<@(packaging_files_common)',
+ '<@(packaging_files_deb)',
+ ],
+ 'outputs': [
+ '<(PRODUCT_DIR)/mod-pagespeed-<(channel)-<(version)-r<(revision)_<(deb_arch).deb',
+ ],
+ 'action': [ '<@(deb_cmd)', '-c', '<(channel)', ],
+ },
+ ],
+ },
+ {
+ 'target_name': 'linux_package_rpm',
+ 'suppress_wildcard': 1,
+ 'type': 'none',
+ 'dependencies': [
+ '<(DEPTH)/net/instaweb/mod_pagespeed.gyp:mod_pagespeed',
+ 'linux_installer_configs',
+ ],
+ 'actions': [
+ {
+ 'variables': {
+ 'channel': 'beta',
+ },
+ 'action_name': 'rpm_package_<(channel)',
+ 'process_outputs_as_sources': 1,
+ 'inputs': [
+ '<(rpm_build)',
+ '<(PRODUCT_DIR)/install/rpm/mod-pagespeed.spec.template',
+ '<@(packaging_files_binaries)',
+ '<@(packaging_files_common)',
+ '<@(packaging_files_rpm)',
+ ],
+ 'outputs': [
+ '<(PRODUCT_DIR)/mod-pagespeed-<(channel)-<(version)-r<(revision).<(rpm_arch).rpm',
+ ],
+ 'action': [ '<@(rpm_cmd)', '-c', '<(channel)', ],
+ },
+ ],
+ },
+ ],
+ },{
+ 'targets': [
+ ],
+ }],
+ ],
+}
+
+# Local Variables:
+# tab-width:2
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=2 shiftwidth=2:
diff --git a/trunk/src/build/output_dll_copy.rules b/trunk/src/build/output_dll_copy.rules
new file mode 100644
index 0000000..c6e9051
--- /dev/null
+++ b/trunk/src/build/output_dll_copy.rules
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<VisualStudioToolFile
+ Name="Output DLL copy"
+ Version="8.00"
+ >
+ <Rules>
+ <CustomBuildRule
+ Name="Output DLL copy"
+ CommandLine="xcopy /R /C /Y $(InputPath) $(OutDir)"
+ Outputs="$(OutDir)\$(InputFileName)"
+ FileExtensions="*.dll"
+ >
+ <Properties>
+ </Properties>
+ </CustomBuildRule>
+ </Rules>
+</VisualStudioToolFile>
diff --git a/trunk/src/build/release.gypi b/trunk/src/build/release.gypi
new file mode 100644
index 0000000..c12526b
--- /dev/null
+++ b/trunk/src/build/release.gypi
@@ -0,0 +1,19 @@
+{
+ 'conditions': [
+ # Handle build types.
+ ['buildtype=="Dev"', {
+ 'includes': ['internal/release_impl.gypi'],
+ }],
+ ['buildtype=="Official"', {
+ 'includes': ['internal/release_impl_official.gypi'],
+ }],
+ # TODO(bradnelson): may also need:
+ # checksenabled
+ # coverage
+ # dom_stats
+ # pgo_instrument
+ # pgo_optimize
+ # purify
+ ],
+}
+
diff --git a/trunk/src/build/version.py b/trunk/src/build/version.py
new file mode 100755
index 0000000..428f761
--- /dev/null
+++ b/trunk/src/build/version.py
@@ -0,0 +1,25 @@
+#!/usr/bin/python
+
+# 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.
+
+# This script is wrapper for the Chromium version of compiler_version.py.
+
+import os
+
+script_dir = os.path.dirname(__file__)
+chrome_src = os.path.normpath(os.path.join(script_dir, os.pardir, 'third_party',
+ 'chromium', 'src'))
+
+execfile(os.path.join(chrome_src, 'chrome', 'tools', 'build', 'version.py'))
diff --git a/trunk/src/install/Makefile b/trunk/src/install/Makefile
new file mode 100644
index 0000000..810a560
--- /dev/null
+++ b/trunk/src/install/Makefile
@@ -0,0 +1,236 @@
+# This Makefile is used to help drive the installation of mod_pagespeed into
+# an Apache installation.
+#
+# Note that the location of the Apache configuration files may vary by
+# Linux distribution. For example, we have seen the following installation
+# directories for the default Apache install.
+#
+# Ubuntu /etc/apache2/mods-enabled/*.conf
+# CentOS /etc/httpd/conf.d/*.conf
+# Custom Apache build from source /usr/local/apache2/conf/extra/
+#
+# In the case of the custom Apache build, you must also
+# edit /usr/local/apache2/conf # to add "Include conf/extra/pagespeed.conf"
+#
+# The goal of this Makefile is to help generate basic default
+# configuration files that can then be edited to tune the HTML
+# performance based on the Apache installation, internet-visible
+# hostnames, and the specific needs of the site.
+#
+# The usage model of this Makefile is that, as an unpriviledged user, you
+# create the desired configuration files in /tmp, where you can examine
+# them before installing them. You can then do either of these:
+#
+# (a) Run "make -n install" to see the recommended installation commands,
+# and execute them by hand
+# (b) Run "sudo make install" to install them automatically.
+#
+#
+# To install mod_pagespeed properly, we need to know the locations of
+# Apache configuration scripts and binaries. These can are specified
+# as Makefile variables which can be overridden on the command line.
+# They have defaults, which will often need to be changed.
+
+
+# The location of the Apache root installation directory. This helps form
+# defaults for other variables, but each of those can be overridden.
+APACHE_ROOT = /etc/httpd
+
+# The installation directory for modules (mod*.so)
+APACHE_MODULES = $(APACHE_ROOT)/modules
+
+# The root directory Apache uses for serving files.
+APACHE_DOC_ROOT = /var/www
+
+APACHE_CONTROL_PROGRAM = /etc/init.d/apache2
+APACHE_START = $(APACHE_CONTROL_PROGRAM) start
+APACHE_STOP = $(APACHE_CONTROL_PROGRAM) stop
+
+# A temp directory to stage generated configuration files. This must be
+# writable by the user, and readable by root.
+STAGING_DIR = /tmp/mod_pagespeed.install
+
+# The mod_pagespeed module is specified relative to the install directory,
+# which is src/install.
+MOD_PAGESPEED_ROOT = ..
+PAGESPEED_MODULE = $(MOD_PAGESPEED_ROOT)/out/Release/libmod_pagespeed.so
+
+# On systems dervied from the NCSA configuration files by Rob McCool,
+# you enable a module by writing its .conf file into
+# $(APACHE_ROOT)/mods-enabled/pagespeed.conf, and a single Load command into
+# $(APACHE_ROOT)/mods-enabled/pagespeed.conf. So if that exists, then we'll
+# try to automate that.
+MODS_ENABLED_DIR = $(shell if [ -d $(APACHE_ROOT)/mods-enabled ]; then \
+ echo $(APACHE_ROOT)/mods-enabled; fi)
+
+# Determines should mod_pagespeed should write files
+MOD_PAGESPEED_FILE_ROOT = /var/mod_pagespeed
+
+# The username used to run apache. This is needed to create the directory
+# used to store mod_pagespeed files and cache data.
+APACHE_USER = www-data
+
+# Set this to 1 to enable mod_proxy and mod_rewrite
+ENABLE_PROXY = 0
+
+.PHONY: config_file echo_vars
+
+echo_vars:
+ @echo Run "restart" to add default instaweb config to apache
+ @echo Or run "stop", "staging", "install", and "start".
+ @echo These configuration variables can be reset on the make command line,
+ @echo e.g. \"make config_file\"
+ @echo ""
+ @echo " APACHE_CONF=$(APACHE_CONF)"
+ @echo " APACHE_MODULES=$(APACHE_MODULES)"
+ @echo " APACHE_ROOT=$(APACHE_ROOT)"
+ @echo " APACHE_START=$(APACHE_START)"
+ @echo " APACHE_STOP=$(APACHE_STOP)"
+ @echo " MOD_PAGESPEED_FILE_ROOT=$(MOD_PAGESPEED_FILE_ROOT)"
+ @echo " MODS_ENABLED_DIR=$(MODS_ENABLED_DIR)"
+ @echo " STAGING_DIR=$(STAGING_DIR)"
+ @echo " ENABLE_PROXY=${ENABLE_PROXY}"
+ @echo " SLURP_DIR=${SLURP_DIR}"
+
+
+# In some Linux distributions, such as Ubuntu, there are two commands
+# in the default root config file:
+# Include /etc/apache2/modes-enabled/*.load
+# Include /etc/apache2/modes-enabled/*.conf
+# we need to write a one-line '.load' file and put that and our '.conf' file
+# into .../mods-enabled.
+#
+# In other distributions, such as CentOS, there is an 'Include DIR/*.conf',
+# but there is no implicit loading of modules, so we write our Load line
+# directly into our config file
+
+ifeq ($(MODS_ENABLED_DIR),)
+
+# This is a CentOS-like installation, where there is no explicit .load
+# file, and we instead pre-pend the LoadModule command to the .conf file.
+APACHE_CONF = $(APACHE_ROOT)/conf.d
+CONF_SOURCES = $(STAGING_DIR)/pagespeed.load $(STAGING_DIR)/pagespeed.conf
+
+else
+
+# This is an Ubuntu-like installation, where the .load files are placed
+# separately into a mods-enabled directory, and the .conf file is loaded
+# independently.
+MODS_ENABLED_INSTALL_COMMANDS = \
+ cp $(STAGING_DIR)/pagespeed.load $(MODS_ENABLED_DIR) ; \
+ rm -f $(MODS_ENABLED_DIR)/headers.load ; \
+ cd $(MODS_ENABLED_DIR) && ln -s ../mods-available/headers.load ; \
+ rm -f $(MODS_ENABLED_DIR)/deflate.load ; \
+ cd $(MODS_ENABLED_DIR) && ln -s ../mods-available/deflate.load
+
+APACHE_CONF = $(MODS_ENABLED_DIR)
+CONF_SOURCES = $(STAGING_DIR)/pagespeed.conf
+
+endif
+
+
+# We will generate 'proxy.conf' in the staging area
+# unconditiontionally, but we will load it into the
+# Apache server only if the user installs with ENABLE_PROXY=1
+ifeq ($(ENABLE_PROXY),1)
+CONF_SOURCES += $(STAGING_DIR)/proxy.conf
+endif
+
+APACHE_SLURP_READ_ONLY_COMMAND=\#ModPagespeedSlurpReadOnly on
+
+ifeq ($(SLURP_DIR),)
+ APACHE_SLURP_DIR_COMMAND = \#ModPagespeedSlurpDirectory ...
+else
+ APACHE_SLURP_DIR_COMMAND = ModPagespeedSlurpDirectory $(SLURP_DIR)
+ ifeq ($(SLURP_WRITE),1)
+ APACHE_SLURP_READ_ONLY_COMMAND=ModPagespeedSlurpReadOnly off
+ else
+ APACHE_SLURP_READ_ONLY_COMMAND=ModPagespeedSlurpReadOnly on
+ endif
+endif
+
+ifeq ($(STRESS_TEST),1)
+ # remove prefix
+ STRESS_TEST_SED_PATTERN=^\#STRESS
+else
+ # remove whole line
+ STRESS_TEST_SED_PATTERN=^\#STRESS.*
+endif
+
+# Note that the quoted sed replacement for APACHE_SLURP_DIR_COMMAND is because
+# that might have embedded spaces, and 'sed' is interpreted first by bash.
+
+$(STAGING_DIR)/pagespeed.conf : common/pagespeed.conf.template debug.conf.template
+ sed -e s@APACHE_DOC_ROOT@$(APACHE_DOC_ROOT)@g \
+ -e s!@@MODPAGESPEED_CACHE_ROOT@@!$(MOD_PAGESPEED_FILE_ROOT)!g \
+ -e "s@# ModPagespeedSlurpDirectory ...@$(APACHE_SLURP_DIR_COMMAND)@g" \
+ -e "s@# ModPagespeedSlurpReadOnly on@$(APACHE_SLURP_READ_ONLY_COMMAND)@g" \
+ -e "s@$(STRESS_TEST_SED_PATTERN)@@" \
+ $^ > $@
+
+$(STAGING_DIR)/proxy.conf : proxy.conf.template
+ sed -e s@APACHE_MODULES@$(APACHE_MODULES)@g \
+ $< > $@
+
+CONF_TEMPLATES = $(STAGING_DIR)/pagespeed.conf \
+ $(STAGING_DIR)/proxy.conf
+
+setup_staging_dir :
+ rm -rf $(STAGING_DIR)
+ mkdir -p $(STAGING_DIR)
+
+# Generate a configuration file and copy it to the staging area.
+# Also copy the example tree, and the built Apache module
+staging: setup_staging_dir $(CONF_TEMPLATES)
+ echo "LoadModule pagespeed_module $(APACHE_MODULES)/mod_pagespeed.so" \
+ > $(STAGING_DIR)/pagespeed.load
+ echo "LoadModule deflate_module $(APACHE_MODULES)/mod_deflate.so" \
+ >> $(STAGING_DIR)/pagespeed.load
+ $(MODS_ENABLED_STAGING_COMMANDS)
+ cp -rp mod_pagespeed_example $(STAGING_DIR)
+ cp $(PAGESPEED_MODULE) $(STAGING_DIR)/mod_pagespeed.so
+
+# To install the mod_pagespeed configuration into the system, you must
+# run this as root, or under sudo.
+install : mod_pagespeed_file_root
+ $(MODS_ENABLED_INSTALL_COMMANDS)
+ cat $(CONF_SOURCES) > $(APACHE_CONF)/pagespeed.conf
+ rm -rf $(APACHE_DOC_ROOT)/mod_pagespeed_example
+ cp -r $(STAGING_DIR)/mod_pagespeed_example $(APACHE_DOC_ROOT)
+ chown -R $(APACHE_USER) $(APACHE_DOC_ROOT)/mod_pagespeed_example
+ cp $(STAGING_DIR)/mod_pagespeed.so $(APACHE_MODULES)
+
+mod_pagespeed_file_root :
+ mkdir -p $(MOD_PAGESPEED_FILE_ROOT)/cache
+ mkdir -p $(MOD_PAGESPEED_FILE_ROOT)/files
+ chown -R $(APACHE_USER) $(MOD_PAGESPEED_FILE_ROOT)
+
+flush_disk_cache :
+ rm -rf $(MOD_PAGESPEED_FILE_ROOT)
+ $(MAKE) MOD_PAGESPEED_FILE_ROOT=$(MOD_PAGESPEED_FILE_ROOT) \
+ APACHE_USER=$(APACHE_USER) mod_pagespeed_file_root
+
+# Starts Apache server
+start :
+ sudo $(APACHE_START)
+stop :
+ sudo $(APACHE_STOP)
+
+# To run a complete iteration, stopping Apache, reconfiguring
+# it, and and restarting it, you can run 'make restart [args...]
+restart : staging
+ sudo $(APACHE_STOP)
+ $(MAKE) staging
+ sudo $(MAKE) install \
+ APACHE_DOC_ROOT=$(APACHE_DOC_ROOT) \
+ STAGING_DIR=$(STAGING_DIR) \
+ APACHE_CONF=$(APACHE_CONF) \
+ APACHE_MODULES=$(APACHE_MODULES) \
+ MODS_ENABLED_DIR=$(MODS_ENABLED_DIR) \
+ APACHE_USER=$(APACHE_USER) \
+ ENABLE_PROXY=$(ENABLE_PROXY)
+ sudo $(APACHE_START)
+
+# Tests that the installed mod_pagespeed server is working.
+test :
+ ./system_test.sh localhost
diff --git a/trunk/src/install/centos.sh b/trunk/src/install/centos.sh
new file mode 100755
index 0000000..77ee2d9
--- /dev/null
+++ b/trunk/src/install/centos.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+set -x
+exec make \
+ APACHE_CONTROL_PROGRAM=/etc/init.d/httpd \
+ APACHE_USER=apache \
+ APACHE_DOC_ROOT=/var/www/html \
+ $*
diff --git a/trunk/src/install/common/BRANDING b/trunk/src/install/common/BRANDING
new file mode 100644
index 0000000..e0a7de9
--- /dev/null
+++ b/trunk/src/install/common/BRANDING
@@ -0,0 +1,5 @@
+COMPANY_FULLNAME=Google Inc.
+COMPANY_SHORTNAME=Google Inc.
+PRODUCT_FULLNAME=mod_pagespeed
+PRODUCT_SHORTNAME=mod_pagespeed
+COPYRIGHT=Copyright (C) 2010.
diff --git a/trunk/src/install/common/apt.include b/trunk/src/install/common/apt.include
new file mode 100644
index 0000000..6c26417
--- /dev/null
+++ b/trunk/src/install/common/apt.include
@@ -0,0 +1,188 @@
+@@include@@variables.include
+
+APT_GET="`which apt-get 2> /dev/null`"
+APT_CONFIG="`which apt-config 2> /dev/null`"
+
+SOURCES_PREAMBLE="### THIS FILE IS AUTOMATICALLY CONFIGURED ###
+# You may comment out this entry, but any other modifications may be lost.\n"
+
+# Parse apt configuration and return requested variable value.
+apt_config_val() {
+ APTVAR="$1"
+ if [ -x "$APT_CONFIG" ]; then
+ "$APT_CONFIG" dump | sed -e "/^$APTVAR /"'!d' -e "s/^$APTVAR \"\(.*\)\".*/\1/"
+ fi
+}
+
+# Install the repository signing key (see also:
+# http://www.google.com/linuxrepositories/aboutkey.html)
+install_key() {
+ APT_KEY="`which apt-key 2> /dev/null`"
+ if [ -x "$APT_KEY" ]; then
+ "$APT_KEY" add - >/dev/null 2>&1 <<KEYDATA
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.2.2 (GNU/Linux)
+
+mQGiBEXwb0YRBADQva2NLpYXxgjNkbuP0LnPoEXruGmvi3XMIxjEUFuGNCP4Rj/a
+kv2E5VixBP1vcQFDRJ+p1puh8NU0XERlhpyZrVMzzS/RdWdyXf7E5S8oqNXsoD1z
+fvmI+i9b2EhHAA19Kgw7ifV8vMa4tkwslEmcTiwiw8lyUl28Wh4Et8SxzwCggDcA
+feGqtn3PP5YAdD0km4S4XeMEAJjlrqPoPv2Gf//tfznY2UyS9PUqFCPLHgFLe80u
+QhI2U5jt6jUKN4fHauvR6z3seSAsh1YyzyZCKxJFEKXCCqnrFSoh4WSJsbFNc4PN
+b0V0SqiTCkWADZyLT5wll8sWuQ5ylTf3z1ENoHf+G3um3/wk/+xmEHvj9HCTBEXP
+78X0A/0Tqlhc2RBnEf+AqxWvM8sk8LzJI/XGjwBvKfXe+l3rnSR2kEAvGzj5Sg0X
+4XmfTg4Jl8BNjWyvm2Wmjfet41LPmYJKsux3g0b8yzQxeOA4pQKKAU3Z4+rgzGmf
+HdwCG5MNT2A5XxD/eDd+L4fRx0HbFkIQoAi1J3YWQSiTk15fw7RMR29vZ2xlLCBJ
+bmMuIExpbnV4IFBhY2thZ2UgU2lnbmluZyBLZXkgPGxpbnV4LXBhY2thZ2VzLWtl
+eW1hc3RlckBnb29nbGUuY29tPohjBBMRAgAjAhsDBgsJCAcDAgQVAggDBBYCAwEC
+HgECF4AFAkYVdn8CGQEACgkQoECDD3+sWZHKSgCfdq3HtNYJLv+XZleb6HN4zOcF
+AJEAniSFbuv8V5FSHxeRimHx25671az+uQINBEXwb0sQCACuA8HT2nr+FM5y/kzI
+A51ZcC46KFtIDgjQJ31Q3OrkYP8LbxOpKMRIzvOZrsjOlFmDVqitiVc7qj3lYp6U
+rgNVaFv6Qu4bo2/ctjNHDDBdv6nufmusJUWq/9TwieepM/cwnXd+HMxu1XBKRVk9
+XyAZ9SvfcW4EtxVgysI+XlptKFa5JCqFM3qJllVohMmr7lMwO8+sxTWTXqxsptJo
+pZeKz+UBEEqPyw7CUIVYGC9ENEtIMFvAvPqnhj1GS96REMpry+5s9WKuLEaclWpd
+K3krttbDlY1NaeQUCRvBYZ8iAG9YSLHUHMTuI2oea07Rh4dtIAqPwAX8xn36JAYG
+2vgLAAMFB/wKqaycjWAZwIe98Yt0qHsdkpmIbarD9fGiA6kfkK/UxjL/k7tmS4Vm
+CljrrDZkPSQ/19mpdRcGXtb0NI9+nyM5trweTvtPw+HPkDiJlTaiCcx+izg79Fj9
+KcofuNb3lPdXZb9tzf5oDnmm/B+4vkeTuEZJ//IFty8cmvCpzvY+DAz1Vo9rA+Zn
+cpWY1n6z6oSS9AsyT/IFlWWBZZ17SpMHu+h4Bxy62+AbPHKGSujEGQhWq8ZRoJAT
+G0KSObnmZ7FwFWu1e9XFoUCt0bSjiJWTIyaObMrWu/LvJ3e9I87HseSJStfw6fki
+5og9qFEkMrIrBCp3QGuQWBq/rTdMuwNFiEkEGBECAAkFAkXwb0sCGwwACgkQoECD
+D3+sWZF/WACfeNAu1/1hwZtUo1bR+MWiCjpvHtwAnA1R3IHqFLQ2X3xJ40XPuAyY
+/FJG
+=Quqp
+-----END PGP PUBLIC KEY BLOCK-----
+KEYDATA
+ fi
+}
+
+# Set variables for the locations of the apt sources lists.
+find_apt_sources() {
+ APTDIR=$(apt_config_val Dir)
+ APTETC=$(apt_config_val 'Dir::Etc')
+ APT_SOURCES="$APTDIR$APTETC$(apt_config_val 'Dir::Etc::sourcelist')"
+ APT_SOURCESDIR="$APTDIR$APTETC$(apt_config_val 'Dir::Etc::sourceparts')"
+}
+
+# Update the Google repository if it's not set correctly.
+# Note: this doesn't necessarily enable the repository, it just makes sure the
+# correct settings are available in the sources list.
+# Returns:
+# 0 - no update necessary
+# 2 - error
+update_bad_sources() {
+ if [ ! "$REPOCONFIG" ]; then
+ return 0
+ fi
+
+ find_apt_sources
+
+ SOURCELIST="$APT_SOURCESDIR/@@PACKAGE@@.list"
+ # Don't do anything if the file isn't there, since that probably means the
+ # user disabled it.
+ if [ ! -r "$SOURCELIST" ]; then
+ return 0
+ fi
+
+ # Basic check for active configurations (non-blank, non-comment lines).
+ ACTIVECONFIGS=$(grep -v "^[[:space:]]*\(#.*\)\?$" "$SOURCELIST" 2>/dev/null)
+
+ # Check if the correct repository configuration is in there.
+ REPOMATCH=$(grep "^[[:space:]#]*\b$REPOCONFIG\b" "$SOURCELIST" \
+ 2>/dev/null)
+
+ # Check if the correct repository is disabled.
+ MATCH_DISABLED=$(echo "$REPOMATCH" | grep "^[[:space:]]*#" 2>/dev/null)
+
+ # Now figure out if we need to fix things.
+ BADCONFIG=1
+ if [ "$REPOMATCH" ]; then
+ # If it's there and active, that's ideal, so nothing to do.
+ if [ ! "$MATCH_DISABLED" ]; then
+ BADCONFIG=0
+ else
+ # If it's not active, but neither is anything else, that's fine too.
+ if [ ! "$ACTIVECONFIGS" ]; then
+ BADCONFIG=0
+ fi
+ fi
+ fi
+
+ if [ $BADCONFIG -eq 0 ]; then
+ return 0
+ fi
+
+ # At this point, either the correct configuration is completely missing, or
+ # the wrong configuration is active. In that case, just abandon the mess and
+ # recreate the file with the correct configuration. If there were no active
+ # configurations before, create the new configuration disabled.
+ DISABLE=""
+ if [ ! "$ACTIVECONFIGS" ]; then
+ DISABLE="#"
+ fi
+ printf "$SOURCES_PREAMBLE" > "$SOURCELIST"
+ printf "$DISABLE$REPOCONFIG\n" >> "$SOURCELIST"
+ if [ $? -eq 0 ]; then
+ return 0
+ fi
+ return 2
+}
+
+# Add the Google repository to the apt sources.
+# Returns:
+# 0 - sources list was created
+# 2 - error
+create_sources_lists() {
+ if [ ! "$REPOCONFIG" ]; then
+ return 0
+ fi
+
+ find_apt_sources
+
+ SOURCELIST="$APT_SOURCESDIR/@@PACKAGE@@.list"
+ if [ -d "$APT_SOURCESDIR" ]; then
+ printf "$SOURCES_PREAMBLE" > "$SOURCELIST"
+ printf "$REPOCONFIG\n" >> "$SOURCELIST"
+ if [ $? -eq 0 ]; then
+ return 0
+ fi
+ fi
+ return 2
+}
+
+# Remove our custom sources list file.
+# Returns:
+# 0 - successfully removed, or not configured
+# !0 - failed to remove
+clean_sources_lists() {
+ if [ ! "$REPOCONFIG" ]; then
+ return 0
+ fi
+
+ find_apt_sources
+
+ rm -f "$APT_SOURCESDIR/@@PACKAGE@@.list" \
+ "$APT_SOURCESDIR/@@PACKAGE@@-@@CHANNEL@@.list"
+}
+
+# Detect if the repo config was disabled by distro upgrade and enable if
+# necessary.
+handle_distro_upgrade() {
+ if [ ! "$REPOCONFIG" ]; then
+ return 0
+ fi
+
+ find_apt_sources
+ SOURCELIST="$APT_SOURCESDIR/@@PACKAGE@@.list"
+ if [ -r "$SOURCELIST" ]; then
+ REPOLINE=$(grep -E "^[[:space:]]*#[[:space:]]*$REPOCONFIG[[:space:]]*# disabled on upgrade to .*" "$SOURCELIST")
+ if [ $? -eq 0 ]; then
+ sed -i -e "s,^[[:space:]]*#[[:space:]]*\($REPOCONFIG\)[[:space:]]*# disabled on upgrade to .*,\1," \
+ "$SOURCELIST"
+ LOGGER=$(which logger 2> /dev/null)
+ if [ "$LOGGER" ]; then
+ "$LOGGER" -t "$0" "Reverted repository modification: $REPOLINE."
+ fi
+ fi
+ fi
+}
+
diff --git a/trunk/src/install/common/installer.include b/trunk/src/install/common/installer.include
new file mode 100644
index 0000000..33de58e
--- /dev/null
+++ b/trunk/src/install/common/installer.include
@@ -0,0 +1,118 @@
+# Recursively replace @@include@@ template variables with the referenced file,
+# and write the resulting text to stdout.
+process_template_includes() {
+ INCSTACK+="$1->"
+ # Includes are relative to the file that does the include.
+ INCDIR=$(dirname $1)
+ # Clear IFS so 'read' doesn't trim whitespace
+ local OLDIFS="$IFS"
+ IFS=''
+ while read -r LINE
+ do
+ INCLINE=$(sed -e '/^[[:space:]]*@@include@@/!d' <<<$LINE)
+ if [ -n "$INCLINE" ]; then
+ INCFILE=$(echo $INCLINE | sed -e "s#@@include@@\(.*\)#\1#")
+ # Simple filename match to detect cyclic includes.
+ CYCLE=$(sed -e "\#$INCFILE#"'!d' <<<$INCSTACK)
+ if [ "$CYCLE" ]; then
+ echo "ERROR: Possible cyclic include detected." 1>&2
+ echo "$INCSTACK$INCFILE" 1>&2
+ exit 1
+ fi
+ if [ ! -r "$INCDIR/$INCFILE" ]; then
+ echo "ERROR: Couldn't read include file: $INCDIR/$INCFILE" 1>&2
+ exit 1
+ fi
+ process_template_includes "$INCDIR/$INCFILE"
+ else
+ echo "$LINE"
+ fi
+ done < "$1"
+ IFS="$OLDIFS"
+ INCSTACK=${INCSTACK%"$1->"}
+}
+
+# Replace template variables (@@VARNAME@@) in the given template file. If a
+# second argument is given, save the processed text to that filename, otherwise
+# modify the template file in place.
+process_template() (
+ # Don't worry if some of these substitution variables aren't set.
+ # Note that this function is run in a sub-shell so we don't leak this
+ # setting, since we still want unbound variables to be an error elsewhere.
+ set +u
+
+ local TMPLIN="$1"
+ if [ -z "$2" ]; then
+ local TMPLOUT="$TMPLIN"
+ else
+ local TMPLOUT="$2"
+ fi
+ # Process includes first so included text also gets substitutions.
+ TMPLINCL="$(process_template_includes "$TMPLIN")"
+ sed \
+ -e "s#@@PACKAGE@@#${PACKAGE}#g" \
+ -e "s#@@CHANNEL@@#${CHANNEL}#g" \
+ -e "s#@@COMPANY_FULLNAME@@#${COMPANY_FULLNAME}#g" \
+ -e "s#@@VERSION@@#${VERSION}#g" \
+ -e "s#@@REVISION@@#${REVISION}#g" \
+ -e "s#@@VERSIONFULL@@#${VERSIONFULL}#g" \
+ -e "s#@@BUILDDIR@@#${BUILDDIR}#g" \
+ -e "s#@@STAGEDIR@@#${STAGEDIR}#g" \
+ -e "s#@@SCRIPTDIR@@#${SCRIPTDIR}#g" \
+ -e "s#@@PRODUCTURL@@#${PRODUCTURL}#g" \
+ -e "s#@@PREDEPENDS@@#${PREDEPENDS}#g" \
+ -e "s#@@DEPENDS@@#${DEPENDS}#g" \
+ -e "s#@@PROVIDES@@#${PROVIDES}#g" \
+ -e "s#@@REPLACES@@#${REPLACES}#g" \
+ -e "s#@@CONFLICTS@@#${CONFLICTS}#g" \
+ -e "s#@@ARCHITECTURE@@#${HOST_ARCH}#g" \
+ -e "s#@@MAINTNAME@@#${MAINTNAME}#g" \
+ -e "s#@@MAINTMAIL@@#${MAINTMAIL}#g" \
+ -e "s#@@REPOCONFIG@@#${REPOCONFIG}#g" \
+ -e "s#@@SHORTDESC@@#${SHORTDESC}#g" \
+ -e "s#@@FULLDESC@@#${FULLDESC}#g" \
+ -e "s#@@APACHE_CONFDIR@@#${APACHE_CONFDIR}#g" \
+ -e "s#@@APACHE_MODULEDIR@@#${APACHE_MODULEDIR}#g" \
+ -e "s#@@APACHE_USER@@#${APACHE_USER}#g" \
+ -e "s#@@MODPAGESPEED_CACHE_ROOT@@#${MODPAGESPEED_CACHE_ROOT}#g" \
+ -e "s#@@MODPAGESPEED_ENABLE_UPDATES@@#${MODPAGESPEED_ENABLE_UPDATES}#g" \
+ -e "s#@@COMMENT_OUT_DEFLATE@@#${COMMENT_OUT_DEFLATE}#g" \
+ > "$TMPLOUT" <<< "$TMPLINCL"
+)
+
+# Setup the installation directory hierachy in the package staging area.
+prep_staging_common() {
+ install -m 755 -d \
+ "${STAGEDIR}${APACHE_CONFDIR}" \
+ "${STAGEDIR}${APACHE_MODULEDIR}" \
+ "${STAGEDIR}${MODPAGESPEED_CACHE_ROOT}/files" \
+ "${STAGEDIR}${MODPAGESPEED_CACHE_ROOT}/cache"
+}
+
+get_version_info() {
+ # Default to a bogus low version, so if somebody creates and installs
+ # a package with no version info, it won't prevent upgrading when
+ # trying to install a properly versioned package (i.e. a proper
+ # package will always be "newer").
+ VERSION="0.0.0.0"
+ # Use epoch timestamp so packages with bogus versions still increment
+ # and will upgrade older bogus-versioned packages.
+ REVISION=$(date +"%s")
+ # Default to non-official build since official builds set this
+ # properly.
+ OFFICIAL_BUILD=0
+
+ VERSIONFILE="${BUILDDIR}/installer/version.txt"
+ if [ -f "${VERSIONFILE}" ]; then
+ source "${VERSIONFILE}"
+ VERSION="${MAJOR}.${MINOR}.${BUILD}.${PATCH}"
+ REVISION="${LASTCHANGE}"
+ fi
+}
+
+stage_install_common() {
+ echo "Staging common install files in '${STAGEDIR}'..."
+
+ # app and resources
+ install -m 644 -s "${BUILDDIR}/libmod_pagespeed.so" "${STAGEDIR}${APACHE_MODULEDIR}/mod_pagespeed.so"
+}
diff --git a/trunk/src/install/common/mod-pagespeed/mod-pagespeed.info b/trunk/src/install/common/mod-pagespeed/mod-pagespeed.info
new file mode 100644
index 0000000..9c1e2fd
--- /dev/null
+++ b/trunk/src/install/common/mod-pagespeed/mod-pagespeed.info
@@ -0,0 +1,20 @@
+# Copyright (c) 2009 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# This file provides common configuration information for building
+# mod-pagespeed packages for various platforms.
+
+# Base name of the package.
+PACKAGE="mod-pagespeed"
+
+# Brief package description.
+SHORTDESC="Apache 2 module to optimize web content."
+
+# Detailed package description.
+FULLDESC="mod_pagespeed is an Apache module that aims to speed up load time of pages by applying web performance best practices automatically."
+
+# Package maintainer information.
+MAINTNAME="mod_pagespeed developers"
+MAINTMAIL="mod-pagespeed-dev@googlegroups.com"
+PRODUCTURL="http://code.google.com/p/modpagespeed/"
diff --git a/trunk/src/install/common/pagespeed.conf.template b/trunk/src/install/common/pagespeed.conf.template
new file mode 100644
index 0000000..505a7f0
--- /dev/null
+++ b/trunk/src/install/common/pagespeed.conf.template
@@ -0,0 +1,119 @@
+<IfModule pagespeed_module>
+ # Turn on mod_pagespeed. To completely disable mod_pagespeed, you
+ # can set this to "off".
+ ModPagespeed on
+
+ # Direct Apache to send all HTML output to the mod_pagespeed
+ # output handler.
+ AddOutputFilterByType MOD_PAGESPEED_OUTPUT_FILTER text/html
+
+ # The ModPagespeedFileCachePath and
+ # ModPagespeedGeneratedFilePrefix directories must exist and be
+ # writable by the apache user (as specified by the User
+ # directive).
+ ModPagespeedFileCachePath "@@MODPAGESPEED_CACHE_ROOT@@/cache/"
+ ModPagespeedGeneratedFilePrefix "@@MODPAGESPEED_CACHE_ROOT@@/files/"
+
+ # Override the mod_pagespeed 'rewrite level'. The default level
+ # "CoreFilters" uses a set of rewrite filters that are generally
+ # safe for most web pages. Most sites should not need to change
+ # this value and can instead fine-tune the configuration using the
+ # ModPagespeedDisableFilters and ModPagespeedEnableFilters
+ # directives, below. Valid values for ModPagespeedRewriteLevel are
+ # PassThrough, CoreFilters, TestingCoreFilters and AllFilters.
+ #
+ # ModPagespeedRewriteLevel PassThrough
+
+ # Explicitly disables specific filters. This is useful in
+ # conjuction with ModPagespeedRewriteLevel. For instance, if one
+ # of the filters in the CoreFilters needs to be disabled for a
+ # site, that filter can be added to
+ # ModPagespeedDisableFilters. This directive contains a
+ # comma-separated list of filter names, and can be repeated.
+ #
+ # ModPagespeedDisableFilters rewrite_images
+
+ # Explicitly enables specific filters. This is useful in
+ # conjuction with ModPagespeedRewriteLevel. For instance, filters
+ # not included in the CoreFilters may be enabled using this
+ # directive. This directive contains a comma-separated list of
+ # filter names, and can be repeated.
+ #
+ # ModPagespeedEnableFilters rewrite_javascript,rewrite_css
+ # ModPagespeedEnableFilters collapse_whitespace,elide_attributes
+
+ # ModPagespeedDomain
+ # authorizes rewriting of JS, CSS, and Image files found in this
+ # domain. By default only resources with the same origin as the
+ # HTML file are rewritten. For example:
+ #
+ # ModPagespeedDomain cdn.myhost.com
+ #
+ # This will allow resources found on http://cdn.myhost.com to be
+ # rewritten in addition to those in the same domain as the HTML.
+ #
+ # Wildcards (* and ?) are allowed in the domain specification. Be
+ # careful when using them as if you rewrite domains that do not
+ # send you traffic, then the site receiving the traffic will not
+ # know how to serve the rewritten content.
+
+ # Other defaults (cache sizes and thresholds):
+ # ModPagespeedFileCacheSizeKb 102400
+ # ModPagespeedFileCacheCleanIntervalMs 3600000
+ # ModPagespeedLRUCacheKbPerProcess 1024
+ # ModPagespeedLRUCacheByteLimit 16384
+ # ModPagespeedCssInlineMaxBytes 2048
+ # ModPagespeedImgInlineMaxBytes 2048
+ # ModPagespeedJsInlineMaxBytes 2048
+ # ModPagespeedCssOutlineMinBytes 3000
+ # ModPagespeedJsOutlineMinBytes 3000
+
+ # Bound the number of images that can be rewritten at any one time; this
+ # avoids overloading the CPU. Set this to 0 to remove the bound.
+ # ModPagespeedImgMaxRewritesAtOnce 8
+
+ # When Apache is set up as a browser proxy, mod_pagespeed can record
+ # web-sites as they are requested, so that an image of the web is built up
+ # in the directory of the proxy administrator's choosing. When ReadOnly is
+ # on, only files already present in the SlurpDirectory are served by the
+ # proxy.
+ # ModPagespeedSlurpDirectory ...
+ # ModPagespeedSlurpReadOnly on
+
+
+ # Enables server-side instrumentation and statistics. If this rewriter is
+ # enabled, then each rewritten HTML page will have instrumentation javacript
+ # added that sends latency beacons to /mod_pagespeed_beacon. These
+ # statistics can be accessed at /mod_pagespeed_statistics. You must also
+ # enable the mod_pagespeed_statistics and mod_pagespeed_beacon handlers
+ # below.
+ #
+ # ModPagespeedEnableFilters add_instrumentation
+
+
+ # This handles the client-side instrumentation callbacks which are injected
+ # by the add_instrumentation filter.
+ # You can use a different location by adding the ModPagespeedBeaconUrl
+ # directive; see the documentation on add_instrumentation.
+ #
+ # <Location /mod_pagespeed_beacon>
+ # SetHandler mod_pagespeed_beacon
+ # </Location>
+
+ # Uncomment the following line if you want to disable statistics entirely.
+ # ModPagespeedStatistics off
+
+ # This page lets you view statistics about the mod_pagespeed module.
+ <Location /mod_pagespeed_statistics>
+ Order allow,deny
+ # You may insert other "Allow from" lines to add hosts you want to
+ # allow to look at generated statistics. Another possibility is
+ # to comment out the "Order" and "Allow" options from the config
+ # file, to allow any client that can reach your server to examine
+ # statistics. This might be appropriate in an experimental setup or
+ # if the Apache server is protected by a reverse proxy that will
+ # filter URLs in some fashion.
+ Allow from localhost
+ SetHandler mod_pagespeed_statistics
+ </Location>
+</IfModule>
diff --git a/trunk/src/install/common/pagespeed.load.template b/trunk/src/install/common/pagespeed.load.template
new file mode 100644
index 0000000..9644d1a
--- /dev/null
+++ b/trunk/src/install/common/pagespeed.load.template
@@ -0,0 +1,6 @@
+LoadModule pagespeed_module @@APACHE_MODULEDIR@@/mod_pagespeed.so
+
+# Only attempt to load mod_deflate if it hasn't been loaded already.
+<IfModule !mod_deflate.c>
+@@COMMENT_OUT_DEFLATE@@ LoadModule deflate_module @@APACHE_MODULEDIR@@/mod_deflate.so
+</IfModule>
diff --git a/trunk/src/install/common/repo.cron b/trunk/src/install/common/repo.cron
new file mode 100644
index 0000000..fbb3e74
--- /dev/null
+++ b/trunk/src/install/common/repo.cron
@@ -0,0 +1,42 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# This script is part of the @@PACKAGE@@ package.
+#
+# It creates the repository configuration file for package updates, and it
+# monitors that config to see if it has been disabled by the overly aggressive
+# distro upgrade process (e.g. intrepid -> jaunty). When this situation is
+# detected, the respository will be re-enabled. If the respository is disabled
+# for any other reason, this won't re-enable it.
+#
+# This functionality can be controlled by creating the $DEFAULTS_FILE and
+# setting "repo_add_once" and/or "repo_reenable_on_distupgrade" to "true" or
+# "false" as desired. An empty $DEFAULTS_FILE is the same as setting both values
+# to "false".
+
+@@include@@apt.include
+
+## MAIN ##
+DEFAULTS_FILE="/etc/default/@@PACKAGE@@"
+if [ -r "$DEFAULTS_FILE" ]; then
+ . "$DEFAULTS_FILE"
+fi
+
+if [ "$repo_add_once" = "true" ]; then
+ install_key
+ create_sources_lists
+ RES=$?
+ # Sources creation succeeded, so stop trying.
+ if [ $RES -ne 2 ]; then
+ sed -i -e 's/[[:space:]]*repo_add_once=.*/repo_add_once="false"/' "$DEFAULTS_FILE"
+ fi
+else
+ update_bad_sources
+fi
+
+if [ "$repo_reenable_on_distupgrade" = "true" ]; then
+ handle_distro_upgrade
+fi
diff --git a/trunk/src/install/common/rpm.include b/trunk/src/install/common/rpm.include
new file mode 100644
index 0000000..33d33a5
--- /dev/null
+++ b/trunk/src/install/common/rpm.include
@@ -0,0 +1,305 @@
+@@include@@variables.include
+
+# Install the repository signing key (see also:
+# http://www.google.com/linuxrepositories/aboutkey.html)
+install_rpm_key() {
+ # Check to see if key already exists.
+ rpm -q gpg-pubkey-7fac5991-4615767f > /dev/null 2>&1
+ if [ "$?" -eq "0" ]; then
+ # Key already exists
+ return 0
+ fi
+ # This is to work around a bug in RPM 4.7.0. (see http://crbug.com/22312)
+ rpm -q gpg-pubkey-7fac5991-45f06f46 > /dev/null 2>&1
+ if [ "$?" -eq "0" ]; then
+ # Key already exists
+ return 0
+ fi
+
+ # RPM on Mandriva 2009 is dumb and does not understand "rpm --import -"
+ TMPKEY=$(mktemp /tmp/google.sig.XXXXXX)
+ if [ -n "$TMPKEY" ]; then
+ cat > "$TMPKEY" <<KEYDATA
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.2.2 (GNU/Linux)
+
+mQGiBEXwb0YRBADQva2NLpYXxgjNkbuP0LnPoEXruGmvi3XMIxjEUFuGNCP4Rj/a
+kv2E5VixBP1vcQFDRJ+p1puh8NU0XERlhpyZrVMzzS/RdWdyXf7E5S8oqNXsoD1z
+fvmI+i9b2EhHAA19Kgw7ifV8vMa4tkwslEmcTiwiw8lyUl28Wh4Et8SxzwCggDcA
+feGqtn3PP5YAdD0km4S4XeMEAJjlrqPoPv2Gf//tfznY2UyS9PUqFCPLHgFLe80u
+QhI2U5jt6jUKN4fHauvR6z3seSAsh1YyzyZCKxJFEKXCCqnrFSoh4WSJsbFNc4PN
+b0V0SqiTCkWADZyLT5wll8sWuQ5ylTf3z1ENoHf+G3um3/wk/+xmEHvj9HCTBEXP
+78X0A/0Tqlhc2RBnEf+AqxWvM8sk8LzJI/XGjwBvKfXe+l3rnSR2kEAvGzj5Sg0X
+4XmfTg4Jl8BNjWyvm2Wmjfet41LPmYJKsux3g0b8yzQxeOA4pQKKAU3Z4+rgzGmf
+HdwCG5MNT2A5XxD/eDd+L4fRx0HbFkIQoAi1J3YWQSiTk15fw7RMR29vZ2xlLCBJ
+bmMuIExpbnV4IFBhY2thZ2UgU2lnbmluZyBLZXkgPGxpbnV4LXBhY2thZ2VzLWtl
+eW1hc3RlckBnb29nbGUuY29tPohjBBMRAgAjAhsDBgsJCAcDAgQVAggDBBYCAwEC
+HgECF4AFAkYVdn8CGQEACgkQoECDD3+sWZHKSgCfdq3HtNYJLv+XZleb6HN4zOcF
+AJEAniSFbuv8V5FSHxeRimHx25671az+uQINBEXwb0sQCACuA8HT2nr+FM5y/kzI
+A51ZcC46KFtIDgjQJ31Q3OrkYP8LbxOpKMRIzvOZrsjOlFmDVqitiVc7qj3lYp6U
+rgNVaFv6Qu4bo2/ctjNHDDBdv6nufmusJUWq/9TwieepM/cwnXd+HMxu1XBKRVk9
+XyAZ9SvfcW4EtxVgysI+XlptKFa5JCqFM3qJllVohMmr7lMwO8+sxTWTXqxsptJo
+pZeKz+UBEEqPyw7CUIVYGC9ENEtIMFvAvPqnhj1GS96REMpry+5s9WKuLEaclWpd
+K3krttbDlY1NaeQUCRvBYZ8iAG9YSLHUHMTuI2oea07Rh4dtIAqPwAX8xn36JAYG
+2vgLAAMFB/wKqaycjWAZwIe98Yt0qHsdkpmIbarD9fGiA6kfkK/UxjL/k7tmS4Vm
+CljrrDZkPSQ/19mpdRcGXtb0NI9+nyM5trweTvtPw+HPkDiJlTaiCcx+izg79Fj9
+KcofuNb3lPdXZb9tzf5oDnmm/B+4vkeTuEZJ//IFty8cmvCpzvY+DAz1Vo9rA+Zn
+cpWY1n6z6oSS9AsyT/IFlWWBZZ17SpMHu+h4Bxy62+AbPHKGSujEGQhWq8ZRoJAT
+G0KSObnmZ7FwFWu1e9XFoUCt0bSjiJWTIyaObMrWu/LvJ3e9I87HseSJStfw6fki
+5og9qFEkMrIrBCp3QGuQWBq/rTdMuwNFiEkEGBECAAkFAkXwb0sCGwwACgkQoECD
+D3+sWZF/WACfeNAu1/1hwZtUo1bR+MWiCjpvHtwAnA1R3IHqFLQ2X3xJ40XPuAyY
+/FJG
+=Quqp
+-----END PGP PUBLIC KEY BLOCK-----
+KEYDATA
+ rpm --import "$TMPKEY"
+ rc=$?
+ rm -f "$TMPKEY"
+ if [ "$rc" -eq "0" ]; then
+ return 0
+ fi
+ fi
+ return 1
+}
+
+determine_rpm_package_manager() {
+ local RELEASE
+ LSB_RELEASE="$(which lsb_release 2> /dev/null)"
+ if [ -x "$LSB_RELEASE" ]; then
+ RELEASE=$(lsb_release -i 2> /dev/null)
+ case $DISTRIB_ID in
+ "Fedora")
+ PACKAGEMANAGER=yum
+ ;;
+ "MandrivaLinux")
+ PACKAGEMANAGER=urpmi
+ ;;
+ "SUSE LINUX")
+ PACKAGEMANAGER=yast
+ ;;
+ esac
+ fi
+
+ if [ "$PACKAGEMANAGER" ]; then
+ return
+ fi
+
+ # Fallback methods that are probably unnecessary on modern systems.
+ if [ -f "/etc/lsb-release" ]; then
+ # file missing on Fedora, does not contain DISTRIB_ID on OpenSUSE.
+ eval $(sed -e '/DISTRIB_ID/!d' /etc/lsb-release)
+ case $DISTRIB_ID in
+ MandrivaLinux)
+ PACKAGEMANAGER=urpmi
+ ;;
+ esac
+ fi
+
+ if [ "$PACKAGEMANAGER" ]; then
+ return
+ fi
+
+ if [ -f "/etc/fedora-release" ] || [ -f "/etc/redhat-release" ]; then
+ PACKAGEMANAGER=yum
+ elif [ -f "/etc/SuSE-release" ]; then
+ PACKAGEMANAGER=yast
+ elif [ -f "/etc/mandriva-release" ]; then
+ PACKAGEMANAGER=urpmi
+ fi
+}
+
+DEFAULT_ARCH="@@ARCHITECTURE@@"
+YUM_REPO_FILE="/etc/yum.repos.d/@@PACKAGE@@.repo"
+ZYPPER_REPO_FILE="/etc/zypp/repos.d/@@PACKAGE@@.repo"
+URPMI_REPO_FILE="/etc/urpmi/urpmi.cfg"
+
+install_yum() {
+ install_rpm_key
+
+ if [ ! "$REPOCONFIG" ]; then
+ return 0
+ fi
+
+ if [ -d "/etc/yum.repos.d" ]; then
+cat > "$YUM_REPO_FILE" << REPOCONTENT
+[@@PACKAGE@@]
+name=@@PACKAGE@@
+baseurl=$REPOCONFIG/$DEFAULT_ARCH
+enabled=1
+gpgcheck=1
+REPOCONTENT
+ fi
+}
+
+# This is called by the cron job, rather than in the RPM postinstall.
+# We cannot do this during the install when urpmi is running due to
+# database locking. We also need to enable the repository, and we can
+# only do that while we are online.
+# see: https://qa.mandriva.com/show_bug.cgi?id=31893
+configure_urpmi() {
+ if [ ! "$REPOCONFIG" ]; then
+ return 0
+ fi
+
+ urpmq --list-media | grep -q -s "^@@PACKAGE@@$"
+ if [ "$?" -eq "0" ]; then
+ # Repository already configured
+ return 0
+ fi
+ urpmi.addmedia --update \
+ "@@PACKAGE@@" "$REPOCONFIG/$DEFAULT_ARCH"
+}
+
+install_urpmi() {
+ # urpmi not smart enough to pull media_info/pubkey from the repository?
+ install_rpm_key
+
+ # Defer urpmi.addmedia to configure_urpmi() in the cron job.
+ # See comment there.
+ #
+ # urpmi.addmedia --update \
+ # "@@PACKAGE@@" "$REPOCONFIG/$DEFAULT_ARCH"
+}
+
+install_yast() {
+ if [ ! "$REPOCONFIG" ]; then
+ return 0
+ fi
+
+ # We defer adding the key to later. See comment in the cron job.
+
+ # Ideally, we would run: zypper addrepo -t YUM -f \
+ # "$REPOCONFIG/$DEFAULT_ARCH" "@@PACKAGE@@"
+ # but that does not work when zypper is running.
+ if [ -d "/etc/zypp/repos.d" ]; then
+cat > "$ZYPPER_REPO_FILE" << REPOCONTENT
+[@@PACKAGE@@]
+name=@@PACKAGE@@
+enabled=1
+autorefresh=1
+baseurl=$REPOCONFIG/$DEFAULT_ARCH
+type=rpm-md
+keeppackages=0
+REPOCONTENT
+ fi
+}
+
+# Check if the automatic repository configuration is done, so we know when to
+# stop trying.
+verify_install() {
+ # It's probably enough to see that the repo configs have been created. If they
+ # aren't configured properly, update_bad_repo should catch that when it's run.
+ case $1 in
+ "yum")
+ [ -f "$YUM_REPO_FILE" ]
+ ;;
+ "yast")
+ [ -f "$ZYPPER_REPO_FILE" ]
+ ;;
+ "urpmi")
+ urpmq --list-url | grep -q -s "\b@@PACKAGE@@\b"
+ ;;
+ esac
+}
+
+# Update the Google repository if it's not set correctly.
+update_bad_repo() {
+ if [ ! "$REPOCONFIG" ]; then
+ return 0
+ fi
+
+ determine_rpm_package_manager
+
+ case $PACKAGEMANAGER in
+ "yum")
+ update_repo_file "$YUM_REPO_FILE"
+ ;;
+ "yast")
+ update_repo_file "$ZYPPER_REPO_FILE"
+ ;;
+ "urpmi")
+ update_urpmi_cfg
+ ;;
+ esac
+}
+
+update_repo_file() {
+ REPO_FILE="$1"
+
+ # Don't do anything if the file isn't there, since that probably means the
+ # user disabled it.
+ if [ ! -r "$REPO_FILE" ]; then
+ return 0
+ fi
+
+ # Check if the correct repository configuration is in there.
+ REPOMATCH=$(grep "^baseurl=$REPOCONFIG/$DEFAULT_ARCH" "$REPO_FILE" \
+ 2>/dev/null)
+ # If it's there, nothing to do
+ if [ "$REPOMATCH" ]; then
+ return 0
+ fi
+
+ # Check if it's there but disabled by commenting out (as opposed to using the
+ # 'enabled' setting).
+ MATCH_DISABLED=$(grep "^[[:space:]]*#.*baseurl=$REPOCONFIG/$DEFAULT_ARCH" \
+ "$REPO_FILE" 2>/dev/null)
+ if [ "$MATCH_DISABLED" ]; then
+ # It's OK for it to be disabled, as long as nothing bogus is enabled in its
+ # place.
+ ACTIVECONFIGS=$(grep "^baseurl=.*" "$REPO_FILE" 2>/dev/null)
+ if [ ! "$ACTIVECONFIGS" ]; then
+ return 0
+ fi
+ fi
+
+ # If we get here, the correct repository wasn't found, or something else is
+ # active, so fix it. This assumes there is a 'baseurl' setting, but if not,
+ # then that's just another way of disabling, so we won't try to add it.
+ sed -i -e "s,^baseurl=.*,baseurl=$REPOCONFIG/$DEFAULT_ARCH," "$REPO_FILE"
+}
+
+update_urpmi_cfg() {
+ REPOCFG=$(urpmq --list-url | grep "\b@@PACKAGE@@\b")
+ if [ ! "$REPOCFG" ]; then
+ # Don't do anything if the repo isn't there, since that probably means the
+ # user deleted it.
+ return 0
+ fi
+
+ # See if it's the right repo URL
+ REPOMATCH=$(echo "$REPOCFG" | grep "\b$REPOCONFIG/$DEFAULT_ARCH\b")
+ # If so, nothing to do
+ if [ "$REPOMATCH" ]; then
+ return 0
+ fi
+
+ # Looks like it's the wrong URL, so recreate it.
+ urpmi.removemedia "@@PACKAGE@@" && \
+ urpmi.addmedia --update "@@PACKAGE@@" "$REPOCONFIG/$DEFAULT_ARCH"
+}
+
+# We only remove the repository configuration during a purge. Since RPM has
+# no equivalent to dpkg --purge, the code below is actually never used. We
+# keep it only for reference purposes, should we ever need it.
+#
+#remove_yum() {
+# rm -f "$YUM_REPO_FILE"
+#}
+#
+#remove_urpmi() {
+# # Ideally, we would run: urpmi.removemedia "@@PACKAGE@@"
+# # but that does not work when urpmi is running.
+# # Sentinel comment text does not work either because urpmi.update removes
+# # all comments. So we just delete the entry that matches what we originally
+# # inserted. If such an entry was added manually, that's tough luck.
+# if [ -f "$URPMI_REPO_FILE" ]; then
+# sed -i '\_^@@PACKAGE@@ $REPOCONFIG/$DEFAULT_ARCH {$_,/^}$/d' "$URPMI_REPO_FILE"
+# fi
+#}
+#
+#remove_yast() {
+# # Ideally, we would run: zypper removerepo "@@PACKAGE@@"
+# # but that does not work when zypper is running.
+# rm -f /etc/zypp/repos.d/@@PACKAGE@@.repo
+#}
diff --git a/trunk/src/install/common/rpmrepo.cron b/trunk/src/install/common/rpmrepo.cron
new file mode 100644
index 0000000..8b0043a
--- /dev/null
+++ b/trunk/src/install/common/rpmrepo.cron
@@ -0,0 +1,56 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# This script is part of the @@PACKAGE@@ package.
+#
+# It creates the repository configuration file for package updates, since
+# we cannot do this during the @@PACKAGE@@ installation since the repository
+# is locked.
+#
+# This functionality can be controlled by creating the $DEFAULTS_FILE and
+# setting "repo_add_once" to "true" or "false" as desired. An empty
+# $DEFAULTS_FILE is the same as setting the value to "false".
+
+@@include@@rpm.include
+
+## MAIN ##
+DEFAULTS_FILE="/etc/default/@@PACKAGE@@"
+if [ -r "$DEFAULTS_FILE" ]; then
+ . "$DEFAULTS_FILE"
+fi
+
+if [ "$repo_add_once" = "true" ]; then
+ determine_rpm_package_manager
+
+ case $PACKAGEMANAGER in
+ "urpmi")
+ # We need to configure urpmi after the install has finished.
+ # See configure_urpmi() for details.
+ configure_urpmi
+ ;;
+ "yast")
+ # It looks as though yast/zypper has a lock on the RPM DB during
+ # postinstall, so we cannot add the signing key with install_rpm_key().
+ # Instead, we attempt to do this here. If the user attempt to update before
+ # the cron job imports the key, Yast will grab the key from our server and
+ # prompt the user to accept the key.
+ install_rpm_key
+ ;;
+ esac
+
+ if [ $? -eq 0 ]; then
+ # Before we quit auto-configuration, check that everything looks sane, since
+ # part of this happened during package install and we don't have the return
+ # value of that process.
+ verify_install $PACKAGEMANAGER
+ if [ $? -eq 0 ]; then
+ sed -i -e 's/[[:space:]]*repo_add_once=.*/repo_add_once="false"/' \
+ "$DEFAULTS_FILE"
+ fi
+ fi
+else
+ update_bad_repo
+fi
diff --git a/trunk/src/install/common/updater b/trunk/src/install/common/updater
new file mode 100755
index 0000000..53a8a80
--- /dev/null
+++ b/trunk/src/install/common/updater
@@ -0,0 +1,26 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# TODO
+# - handle other distros (e.g. non-apt).
+
+@@include@@apt.include
+
+if [ -x "$APT_GET" ]; then
+ update_sources_lists
+ # If the repo was just added, force a cache update.
+ if [ $? -eq 1 ]; then
+ install_key
+ "$APT_GET" -qq update
+ fi
+
+ # TODO(mmoss) detect if apt cache is stale (> 1 day) and force update?
+
+ # Just try to install the packge. If it's already installed, apt-get won't do
+ # anything.
+ "$APT_GET" install -y -q @@PACKAGE@@
+fi
+
diff --git a/trunk/src/install/common/variables.include b/trunk/src/install/common/variables.include
new file mode 100644
index 0000000..f3a17cd
--- /dev/null
+++ b/trunk/src/install/common/variables.include
@@ -0,0 +1,5 @@
+# System-wide package configuration.
+DEFAULTS_FILE="/etc/default/@@PACKAGE@@"
+
+# sources.list setting for @@PACKAGE@@ updates.
+REPOCONFIG="@@REPOCONFIG@@"
diff --git a/trunk/src/install/debian/build.sh b/trunk/src/install/debian/build.sh
new file mode 100755
index 0000000..e4f2cb3
--- /dev/null
+++ b/trunk/src/install/debian/build.sh
@@ -0,0 +1,247 @@
+#!/bin/bash
+#
+# Copyright (c) 2009 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+set -e
+if [ "$VERBOSE" ]; then
+ set -x
+fi
+set -u
+
+# Create the Debian changelog file needed by dpkg-gencontrol. This just adds a
+# placeholder change, indicating it is the result of an automatic build.
+gen_changelog() {
+ rm -f "${DEB_CHANGELOG}"
+ process_template "${SCRIPTDIR}/changelog.template" "${DEB_CHANGELOG}"
+ debchange -a --nomultimaint -m --changelog "${DEB_CHANGELOG}" \
+ --distribution UNRELEASED "automatic build"
+}
+
+# Create the Debian control file needed by dpkg-deb.
+gen_control() {
+ dpkg-gencontrol -v"${VERSIONFULL}" -c"${DEB_CONTROL}" -l"${DEB_CHANGELOG}" \
+ -f"${DEB_FILES}" -p"${PACKAGE}-${CHANNEL}" -P"${STAGEDIR}" -T"${DEB_SUBST}" \
+ -O > "${STAGEDIR}/DEBIAN/control"
+ rm -f "${DEB_CONTROL}"
+}
+
+# Create the Debian substvars file needed by dpkg-gencontrol.
+gen_substvars() {
+ # dpkg-shlibdeps requires a control file in debian/control, so we're
+ # forced to prepare a fake debian directory.
+ mkdir "${SUBSTFILEDIR}/debian"
+ cp "${DEB_CONTROL}" "${SUBSTFILEDIR}/debian"
+ pushd "${SUBSTFILEDIR}" >/dev/null
+ dpkg-shlibdeps "${STAGEDIR}${APACHE_MODULEDIR}/mod_pagespeed.so" \
+ -O >> "${DEB_SUBST}" 2>/dev/null
+ popd >/dev/null
+}
+
+# Setup the installation directory hierachy in the package staging area.
+prep_staging_debian() {
+ prep_staging_common
+ install -m 755 -d "${STAGEDIR}/DEBIAN" \
+ "${STAGEDIR}/etc/cron.daily"
+}
+
+# Put the package contents in the staging area.
+stage_install_debian() {
+ prep_staging_debian
+ stage_install_common
+ echo "Staging Debian install files in '${STAGEDIR}'..."
+ process_template "${BUILDDIR}/install/common/repo.cron" \
+ "${STAGEDIR}/etc/cron.daily/${PACKAGE}"
+ chmod 755 "${STAGEDIR}/etc/cron.daily/${PACKAGE}"
+ process_template "${BUILDDIR}/install/debian/postinst" \
+ "${STAGEDIR}/DEBIAN/postinst"
+ chmod 755 "${STAGEDIR}/DEBIAN/postinst"
+ process_template "${BUILDDIR}/install/debian/prerm" \
+ "${STAGEDIR}/DEBIAN/prerm"
+ chmod 755 "${STAGEDIR}/DEBIAN/prerm"
+ process_template "${BUILDDIR}/install/debian/postrm" \
+ "${STAGEDIR}/DEBIAN/postrm"
+ chmod 755 "${STAGEDIR}/DEBIAN/postrm"
+ install -m 644 "${BUILDDIR}/install/debian/conffiles" \
+ "${STAGEDIR}/DEBIAN/conffiles"
+ process_template "${BUILDDIR}/install/common/pagespeed.load.template" \
+ "${STAGEDIR}${APACHE_CONFDIR}/pagespeed.load"
+ chmod 644 "${STAGEDIR}${APACHE_CONFDIR}/pagespeed.load"
+ process_template "${BUILDDIR}/install/common/pagespeed.conf.template" \
+ "${STAGEDIR}${APACHE_CONFDIR}/pagespeed.conf"
+ chmod 644 "${STAGEDIR}${APACHE_CONFDIR}/pagespeed.conf"
+}
+
+# Build the deb file within a fakeroot.
+do_package_in_fakeroot() {
+ FAKEROOTFILE=$(mktemp -t fakeroot.tmp.XXXXXX) || exit 1
+ fakeroot -s "${FAKEROOTFILE}" -- \
+ chown -R ${APACHE_USER}:${APACHE_USER} ${STAGEDIR}${MODPAGESPEED_CACHE_ROOT}
+ fakeroot -i "${FAKEROOTFILE}" -- \
+ dpkg-deb -b "${STAGEDIR}" .
+ rm -f "${FAKEROOTFILE}"
+}
+
+# Actually generate the package file.
+do_package() {
+ export HOST_ARCH="$1"
+ echo "Packaging ${HOST_ARCH}..."
+ PREDEPENDS="$COMMON_PREDEPS"
+ DEPENDS="${COMMON_DEPS}"
+ gen_changelog
+ process_template "${SCRIPTDIR}/control.template" "${DEB_CONTROL}"
+ export DEB_HOST_ARCH="${HOST_ARCH}"
+ gen_substvars
+ if [ -f "${DEB_CONTROL}" ]; then
+ gen_control
+ fi
+
+ do_package_in_fakeroot
+}
+
+# Remove temporary files and unwanted packaging output.
+cleanup() {
+ echo "Cleaning..."
+ rm -rf "${STAGEDIR}"
+ rm -rf "${TMPFILEDIR}"
+ rm -rf "${SUBSTFILEDIR}"
+}
+
+usage() {
+ echo "usage: $(basename $0) [-c channel] [-a target_arch] [-o 'dir'] [-b 'dir']"
+ echo "-c channel the package channel (unstable, beta, stable)"
+ echo "-a arch package architecture (ia32 or x64)"
+ echo "-o dir package output directory [${OUTPUTDIR}]"
+ echo "-b dir build input directory [${BUILDDIR}]"
+ echo "-h this help message"
+}
+
+# Check that the channel name is one of the allowable ones.
+verify_channel() {
+ case $CHANNEL in
+ stable )
+ CHANNEL=stable
+ ;;
+ unstable|dev|alpha )
+ CHANNEL=unstable
+ ;;
+ testing|beta )
+ CHANNEL=beta
+ ;;
+ * )
+ echo
+ echo "ERROR: '$CHANNEL' is not a valid channel type."
+ echo
+ exit 1
+ ;;
+ esac
+}
+
+process_opts() {
+ while getopts ":o:b:c:a:h" OPTNAME
+ do
+ case $OPTNAME in
+ o )
+ OUTPUTDIR="$OPTARG"
+ mkdir -p "${OUTPUTDIR}"
+ ;;
+ b )
+ BUILDDIR=$(readlink -f "${OPTARG}")
+ ;;
+ c )
+ CHANNEL="$OPTARG"
+ ;;
+ a )
+ TARGETARCH="$OPTARG"
+ ;;
+ h )
+ usage
+ exit 0
+ ;;
+ \: )
+ echo "'-$OPTARG' needs an argument."
+ usage
+ exit 1
+ ;;
+ * )
+ echo "invalid command-line option: $OPTARG"
+ usage
+ exit 1
+ ;;
+ esac
+ done
+}
+
+#=========
+# MAIN
+#=========
+
+SCRIPTDIR=$(readlink -f "$(dirname "$0")")
+OUTPUTDIR="${PWD}"
+STAGEDIR=$(mktemp -d -t deb.build.XXXXXX) || exit 1
+TMPFILEDIR=$(mktemp -d -t deb.tmp.XXXXXX) || exit 1
+SUBSTFILEDIR=$(mktemp -d -t deb.subst.XXXXXX) || exit 1
+DEB_CHANGELOG="${TMPFILEDIR}/changelog"
+DEB_FILES="${TMPFILEDIR}/files"
+DEB_CONTROL="${TMPFILEDIR}/control"
+DEB_SUBST="${SUBSTFILEDIR}/debian/substvars"
+CHANNEL="beta"
+# Default target architecture to same as build host.
+if [ "$(uname -m)" = "x86_64" ]; then
+ TARGETARCH="x64"
+else
+ TARGETARCH="ia32"
+fi
+
+# call cleanup() on exit
+trap cleanup 0
+process_opts "$@"
+if [ ! "$BUILDDIR" ]; then
+ BUILDDIR=$(readlink -f "${SCRIPTDIR}/../../out/Release")
+fi
+
+source ${BUILDDIR}/install/common/installer.include
+
+get_version_info
+VERSIONFULL="${VERSION}-r${REVISION}"
+
+source "${BUILDDIR}/install/common/mod-pagespeed.info"
+eval $(sed -e "s/^\([^=]\+\)=\(.*\)$/export \1='\2'/" \
+ "${BUILDDIR}/install/common/BRANDING")
+
+REPOCONFIG="deb http://dl.google.com/linux/${PACKAGE#google-}/deb/ stable main"
+verify_channel
+
+# Some Debian packaging tools want these set.
+export DEBFULLNAME="${MAINTNAME}"
+export DEBEMAIL="${MAINTMAIL}"
+
+# Make everything happen in the OUTPUTDIR.
+cd "${OUTPUTDIR}"
+
+COMMON_DEPS="apache2.2-common"
+COMMON_PREDEPS="dpkg (>= 1.14.0)"
+
+APACHE_MODULEDIR="/usr/lib/apache2/modules"
+APACHE_CONFDIR="/etc/apache2/mods-available"
+MODPAGESPEED_CACHE_ROOT="/var/mod_pagespeed"
+APACHE_USER="www-data"
+COMMENT_OUT_DEFLATE=
+
+case "$TARGETARCH" in
+ ia32 )
+ stage_install_debian
+ do_package "i386"
+ ;;
+ x64 )
+ stage_install_debian
+ do_package "amd64"
+ ;;
+ * )
+ echo
+ echo "ERROR: Don't know how to build DEBs for '$TARGETARCH'."
+ echo
+ exit 1
+ ;;
+esac
diff --git a/trunk/src/install/debian/changelog.template b/trunk/src/install/debian/changelog.template
new file mode 100644
index 0000000..4ed22e5
--- /dev/null
+++ b/trunk/src/install/debian/changelog.template
@@ -0,0 +1,4 @@
+@@PACKAGE@@-@@CHANNEL@@ (@@VERSIONFULL@@) UNRELEASED; urgency=low
+ * No changes
+
+ -- @@MAINTNAME@@ <@@MAINTMAIL@@> Wed, 20 Oct 2010 14:54:35 -0800
diff --git a/trunk/src/install/debian/conffiles b/trunk/src/install/debian/conffiles
new file mode 100644
index 0000000..230698b
--- /dev/null
+++ b/trunk/src/install/debian/conffiles
@@ -0,0 +1,2 @@
+/etc/apache2/mods-available/pagespeed.load
+/etc/apache2/mods-available/pagespeed.conf
diff --git a/trunk/src/install/debian/control.template b/trunk/src/install/debian/control.template
new file mode 100644
index 0000000..cc9b1d0
--- /dev/null
+++ b/trunk/src/install/debian/control.template
@@ -0,0 +1,16 @@
+Source: @@PACKAGE@@-@@CHANNEL@@
+Section: httpd
+Priority: optional
+Maintainer: @@MAINTNAME@@ <@@MAINTMAIL@@>
+Build-Depends: dpkg-dev, devscripts, fakeroot
+Standards-Version: 3.8.0
+
+Package: @@PACKAGE@@-@@CHANNEL@@
+Provides: @@PROVIDES@@
+Replaces: @@REPLACES@@
+Conflicts: @@CONFLICTS@@
+Pre-Depends: @@PREDEPENDS@@
+Depends: ${shlibs:Depends}, @@DEPENDS@@
+Architecture: @@ARCHITECTURE@@
+Description: @@SHORTDESC@@
+ @@FULLDESC@@
diff --git a/trunk/src/install/debian/postinst b/trunk/src/install/debian/postinst
new file mode 100755
index 0000000..370207d
--- /dev/null
+++ b/trunk/src/install/debian/postinst
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+# Based on postinst from Chromium and Google Talk.
+
+@@include@@../common/apt.include
+
+MODPAGESPEED_ENABLE_UPDATES=@@MODPAGESPEED_ENABLE_UPDATES@@
+
+case "$1" in
+ configure)
+ if [ -n "${MODPAGESPEED_ENABLE_UPDATES}" -a ! -e "$DEFAULTS_FILE" ]; then
+ echo 'repo_add_once="true"' > "$DEFAULTS_FILE"
+ echo 'repo_reenable_on_distupgrade="true"' >> "$DEFAULTS_FILE"
+ fi
+
+ # Run the cron job immediately to perform repository
+ # configuration.
+ nohup sh /etc/cron.daily/@@PACKAGE@@ > /dev/null 2>&1 &
+
+ test ! -e /etc/apache2/mods-enabled/pagespeed.load && \
+ a2enmod pagespeed
+ ;;
+ abort-upgrade|abort-remove|abort-deconfigure)
+ ;;
+ *)
+ echo "postinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
diff --git a/trunk/src/install/debian/postrm b/trunk/src/install/debian/postrm
new file mode 100755
index 0000000..0dc3058
--- /dev/null
+++ b/trunk/src/install/debian/postrm
@@ -0,0 +1,27 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+action="$1"
+
+# Only do complete clean-up on purge.
+if [ "$action" != "purge" ] ; then
+ exit 0
+fi
+
+@@include@@../common/apt.include
+
+# Only remove the defaults file if it is not empty. An empty file was probably
+# put there by the sysadmin to disable automatic repository configuration, as
+# per the instructions on the package download page.
+if [ -s "$DEFAULTS_FILE" ]; then
+ # Make sure the package defaults are removed before the repository config,
+ # otherwise it could result in the repository config being removed, but the
+ # package defaults remain and are set to not recreate the repository config.
+ # In that case, future installs won't recreate it and won't get auto-updated.
+ rm "$DEFAULTS_FILE" || exit 1
+fi
+# Remove any Google repository added by the package.
+clean_sources_lists
diff --git a/trunk/src/install/debian/prerm b/trunk/src/install/debian/prerm
new file mode 100755
index 0000000..d9ab1f9
--- /dev/null
+++ b/trunk/src/install/debian/prerm
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+case "$1" in
+ remove)
+ test -e /etc/apache2/mods-enabled/pagespeed.load && a2dismod pagespeed
+ ;;
+ upgrade|deconfigure|failed-upgrade)
+ ;;
+ *)
+ echo "prerm called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
diff --git a/trunk/src/install/debug.conf.template b/trunk/src/install/debug.conf.template
new file mode 100644
index 0000000..d5722e7
--- /dev/null
+++ b/trunk/src/install/debug.conf.template
@@ -0,0 +1,36 @@
+# These caching headers are set up for the mod_pagespeed example, and
+# also serve as a demonstration of good values to set for the entire
+# site, if it is to be optimized by mod_pagespeed.
+<Directory APACHE_DOC_ROOT/mod_pagespeed_example>
+ <IfModule headers_module>
+ # To enable to show that mod_pagespeed to rewrites web pages, we must
+ # turn off Etags for HTML files and eliminate caching altogether.
+ # mod_pagespeed should rewrite HTML files each time they are served.
+ # The first time mod_pagespeed sees an HTML file, it may not optimize
+ # it fully. It will optimize better after the second view. Caching
+ # defeats this behavior.
+ <FilesMatch "\.(html|htm)$">
+ Header unset Etag
+ Header set Cache-control "max-age=0, no-cache, no-store"
+ </FilesMatch>
+
+ # Images, styles, and javascript are all cache-extended for
+ # a year by rewriting URLs to include a content hash. mod_pagespeed
+ # can only do this if the resources are cacheable in the first place.
+ # The origin caching policy, set here to 10 minutes, dictates how
+ # frequently mod_pagespeed must re-read the content files and recompute
+ # the content-hash. As long as the content doesn't actually change,
+ # the content-hash will remain the same, and the resources stored
+ # in browser caches will stay relevant.
+ <FilesMatch "\.(jpg|jpeg|gif|png|js|css)$">
+ Header unset Etag
+ Header set Cache-control "public, max-age=600"
+ </FilesMatch>
+ </IfModule>
+</Directory>
+
+#STRESS # These lines are only needed for the stress test.
+#STRESS <Directory /usr/local/apache2/htdocs/mod_pagespeed_example/cgi>
+#STRESS Options +ExecCGI
+#STRESS </Directory>
+#STRESS AddHandler cgi-script .cgi
diff --git a/trunk/src/install/install_apxs.sh b/trunk/src/install/install_apxs.sh
new file mode 100644
index 0000000..d60a5c6
--- /dev/null
+++ b/trunk/src/install/install_apxs.sh
@@ -0,0 +1,265 @@
+#!/bin/bash
+#
+# Install Page Speed, using the Apache apxs tool to determine the
+# installation locations.
+#
+# You can specify the path to apxs to install to a non-system-default
+# Apache.
+#
+# APXS_BIN=/path/to/apxs ./install_apxs.sh
+#
+# To install to a location that does not require superuser access:
+#
+# NO_SUDO=1 ./install_apxs.sh
+
+SRC_ROOT="$(dirname $0)/.."
+BUILD_ROOT="${SRC_ROOT}/out/Release"
+MODPAGESPEED_SO_PATH="${BUILD_ROOT}/libmod_pagespeed.so"
+
+MODPAGESPEED_CACHE_ROOT=${MODPAGESPEED_CACHE_ROOT:-"/var/mod_pagespeed"}
+APACHE_CONF_FILENAME=${APACHE_CONF_FILENAME:-"httpd.conf"}
+MODPAGESPEED_SO_NAME=${MODPAGESPEED_SO_NAME:-"mod_pagespeed.so"}
+MODPAGESPEED_CONF_NAME=${MODPAGESPEED_CONF_NAME:-"pagespeed.conf"}
+
+MODPAGESPEED_FILE_USER=${MODPAGESPEED_FILE_USER:-"root"}
+MODPAGESPEED_FILE_GROUP=${MODPAGESPEED_FILE_GROUP:-${MODPAGESPEED_FILE_USER}}
+SUDO_CMD=${SUDO_CMD:-"sudo"}
+
+# If NO_SUDO was specified, then we should use a user and group that
+# matches the current user and group when installing files.
+if [ ! -z "${NO_SUDO}" ]; then
+ MODPAGESPEED_FILE_USER=$USER
+ MODPAGESPEED_FILE_GROUP=$(groups | cut -d\ -f1)
+ SUDO_CMD=""
+fi
+
+# Load the script used to perform template substitutions on our config
+# files.
+source ${SRC_ROOT}/install/common/installer.include
+
+# Args: variable name
+#
+# Takes a variable name, and makes sure that it is set.
+function is_set() {
+ local DOLLAR='$';
+ local TO_EVAL="${DOLLAR}$1"
+ local VALUE=$(eval "echo $TO_EVAL")
+ local RET=1
+ if [ ! -z "${VALUE}" ]; then
+ RET=0
+ fi
+ return $RET
+}
+
+# Args: variable name, expression, debug string
+#
+# Exits if the specified variable name does not have an assigned value
+# or if the expression evaluates to false.
+function check() {
+ if ! is_set "$1" || ! eval "$2"; then
+ echo "Unable to determine $3."
+ echo "Please set the $1 environment variable when invoking $0."
+ exit 1
+ fi
+}
+
+# Args: user, group, misc, src, dst
+#
+# Some hackery to get around the fact that 'install' refuses to take
+# owner/group arguments unless run as root.
+function do_install() {
+ local INST_USER_GROUP=""
+ if [ -z "${NO_SUDO}" ]; then
+ INST_USER_GROUP="-o $1 -g $2"
+ fi
+ eval "${SUDO_CMD} install $INST_USER_GROUP $3 $4 $5"
+}
+
+# Args: setting name
+#
+# Extract an Apache compile-time setting with the given name.
+function extract_compile_setting() {
+ EXTRACT_COMPILE_SETTING=
+ APACHE_CONF_LINE=$(${APACHE_BIN} -V | grep $1)
+ if [ ! -z "${APACHE_CONF_LINE}" ]; then
+ local SED_REGEX="s/^.*${1}=?[\"\'\ ]*//"
+ EXTRACTED_COMPILE_SETTING=$(echo "${APACHE_CONF_LINE}" |
+ sed -r "${SED_REGEX}" |
+ sed "s/[\"\'\ ]*$//")
+ fi
+}
+
+if [ ! -f "${MODPAGESPEED_SO_PATH}" ]; then
+ echo "${MODPAGESPEED_SO_PATH} doesn't exist. Need to build first."
+ exit 1
+fi
+
+# Find the apxs binary, if not specified.
+if [ -z "${APXS_BIN}" ]; then
+ APXS_BIN=$(which apxs 2> /dev/null)
+ if [ -z "${APXS_BIN}" ]; then
+ APXS_BIN=$(which apxs2 2> /dev/null)
+ fi
+ if [ -z "${APXS_BIN}" ]; then
+ # Default location when Apache is installed from source.
+ APXS_BIN="/usr/local/apache2/bin/apxs"
+ fi
+fi
+
+# Find apxs which tells us about the system.
+check APXS_BIN "[ -f ${APXS_BIN} -a -x ${APXS_BIN} ]" "path to Apache apxs"
+
+echo "Using ${APXS_BIN} to determine installation location."
+echo ""
+
+# This is an optional configuration variable. If set, the conf file
+# path is relative to it.
+APACHE_ROOT=$(${APXS_BIN} -q PREFIX)
+
+# Find the Apache shared module dir.
+APACHE_MODULEDIR=$(${APXS_BIN} -q LIBEXECDIR)
+check APACHE_MODULEDIR "[ -d ${APACHE_MODULEDIR} ]" "Apache module dir"
+
+# Find the Apache conf dir.
+APACHE_CONFDIR=$(${APXS_BIN} -q SYSCONFDIR)
+check APACHE_CONFDIR "[ -d ${APACHE_CONFDIR} ]" "Apache conf dir"
+
+APACHE_SBINDIR=$(${APXS_BIN} -q SBINDIR)
+check APACHE_SBINDIR "[ -d ${APACHE_SBINDIR} ]" "Apache bin dir"
+
+APACHE_TARGET=$(${APXS_BIN} -q TARGET)
+APACHE_BIN="${APACHE_SBINDIR}/${APACHE_TARGET}"
+check APACHE_BIN "[ -f ${APACHE_BIN} -a -x ${APACHE_BIN} ]" "Apache binary"
+
+# Find the Apache conf file.
+if [ -z "${APACHE_CONF_FILE}" ]; then
+ extract_compile_setting SERVER_CONFIG_FILE
+ APACHE_CONF_FILE="${EXTRACTED_COMPILE_SETTING}"
+fi
+if [ ! -z "${APACHE_ROOT}" ]; then
+ APACHE_CONF_FILE="${APACHE_ROOT}/${APACHE_CONF_FILE}"
+fi
+if [ -z "${APACHE_CONF_FILE}" ]; then
+ APACHE_CONF_FILE="${APACHE_CONFDIR}/${APACHE_CONF_FILENAME}"
+fi
+check APACHE_CONF_FILE "[ -f ${APACHE_CONF_FILE} ]" "Apache configuration file"
+
+# Try to grep for the Apache user.
+if [ -z "${APACHE_USER}" ]; then
+ APACHE_USER_LINE=$(egrep -i "^[[:blank:]]*User[[:blank:]]+" "${APACHE_CONF_FILE}")
+ if [ ! -z "${APACHE_USER_LINE}" ]; then
+ APACHE_USER=$(echo "${APACHE_USER_LINE}" |
+ sed -r s/^.*User[[:blank:]]+[\"\']*// |
+ sed s/[\"\'[:blank:]]*$//)
+ fi
+fi
+
+# Try to grep for the Apache group.
+if [ -z "${APACHE_GROUP}" ]; then
+ APACHE_GROUP_LINE=$(egrep -i "^[[:blank:]]*Group[[:blank:]]+" "${APACHE_CONF_FILE}")
+ if [ ! -z "${APACHE_GROUP_LINE}" ]; then
+ APACHE_GROUP=$(echo "${APACHE_GROUP_LINE}" |
+ sed -r s/^.*Group[[:blank:]]+[\"\']*// |
+ sed s/[\"\'[:blank:]]*$//)
+ fi
+fi
+
+# Make sure we have an Apache user and group.
+check APACHE_USER "[ ! -z \'${APACHE_USER}\' ]" "Apache user"
+check APACHE_GROUP "[ ! -z \'${APACHE_GROUP}\' ]" "Apache group"
+
+# Make sure the user is valid.
+check APACHE_USER "id '${APACHE_USER}' &> /dev/null" "valid Apache user '${APACHE_USER}'"
+
+# Make sure the group is valid.
+# TODO: is there a way to ask the system if a group exists, similar to
+# the 'id' command?
+check APACHE_GROUP "egrep -q '^${APACHE_GROUP}:' /etc/group" "valid Apache group '${APACHE_GROUP}'"
+
+MODPAGESPEED_CONFDIR=${MODPAGESPEED_CONFDIR:-${APACHE_CONFDIR}}
+
+echo "mod_pagespeed needs to cache optimized resources on the file system."
+echo "The default location for this cache is '${MODPAGESPEED_CACHE_ROOT}'."
+read -p "Would you like to specify a different location? (y/N) " -n1 PROMPT
+if [ "${PROMPT}" = "y" -o "${PROMPT}" = "Y" ]; then
+ echo ""
+ read -p "Location for mod_pagespeed file cache: " MODPAGESPEED_CACHE_ROOT
+fi
+
+if [ -z "${MODPAGESPEED_CACHE_ROOT}" ]; then
+ echo ""
+ echo "Must specify a mod_pagespeed file cache."
+ exit 1
+fi
+
+echo ""
+echo "Preparing to install to the following locations:"
+echo "${APACHE_MODULEDIR}/${MODPAGESPEED_SO_NAME} (${MODPAGESPEED_FILE_USER}:${MODPAGESPEED_FILE_GROUP})"
+echo "${MODPAGESPEED_CONFDIR}/${MODPAGESPEED_CONF_NAME} (${MODPAGESPEED_FILE_USER}:${MODPAGESPEED_FILE_GROUP})"
+echo "${MODPAGESPEED_CACHE_ROOT}/cache (${APACHE_USER}:${APACHE_GROUP})"
+echo "${MODPAGESPEED_CACHE_ROOT}/files (${APACHE_USER}:${APACHE_GROUP})"
+echo ""
+if [ -z "${NO_PROMPT}" ]; then
+ echo -n "Continue? (y/N) "
+ read -n1 PROMPT
+ echo ""
+ if [ "${PROMPT}" != "y" -a "${PROMPT}" != "Y" ]; then
+ echo "Not continuing."
+ exit 1
+ fi
+fi
+
+if [ -d "${MODPAGESPEED_CACHE_ROOT}/cache" ]; then
+ echo "${MODPAGESPEED_CACHE_ROOT}/cache already exists. Not creating."
+fi
+if [ -d "${MODPAGESPEED_CACHE_ROOT}/files" ]; then
+ echo "${MODPAGESPEED_CACHE_ROOT}/files already exists. Not creating."
+fi
+
+# Only attempt to load mod_deflate in our conf file if it's actually
+# present on the system.
+COMMENT_OUT_DEFLATE='\#'
+if [ -f "${APACHE_MODULEDIR}/mod_deflate.so" ]; then
+ COMMENT_OUT_DEFLATE=
+else
+ echo "Unable to find mod_deflate.so. HTTP compression support not enabled!"
+fi
+
+TMP_CONF=$(mktemp -t conf.tmp.XXXXXX) || exit 1
+process_template "${SRC_ROOT}/install/common/pagespeed.conf.template" "${TMP_CONF}"
+TMP_LOAD=$(mktemp -t load.tmp.XXXXXX) || exit 1
+process_template "${SRC_ROOT}/install/common/pagespeed.load.template" "${TMP_LOAD}"
+cat "${TMP_CONF}" >> "${TMP_LOAD}"
+
+INSTALLATION_SUCCEEDED=0
+if (
+do_install "${MODPAGESPEED_FILE_USER}" "${MODPAGESPEED_FILE_GROUP}" "-m 644 -s" \
+ "${MODPAGESPEED_SO_PATH}" \
+ "${APACHE_MODULEDIR}/${MODPAGESPEED_SO_NAME}" &&
+do_install "${MODPAGESPEED_FILE_USER}" "${MODPAGESPEED_FILE_GROUP}" "-m 644" \
+ "${TMP_LOAD}" \
+ "${MODPAGESPEED_CONFDIR}/${MODPAGESPEED_CONF_NAME}" &&
+do_install "${APACHE_USER}" "${APACHE_GROUP}" "-m 755 -d" \
+ "${MODPAGESPEED_CACHE_ROOT}/cache" \
+ "${MODPAGESPEED_CACHE_ROOT}/files"
+); then
+ MODPAGESPEED_LOAD_LINE="Include ${MODPAGESPEED_CONFDIR}/${MODPAGESPEED_CONF_NAME}"
+ if ! grep -q "${MODPAGESPEED_LOAD_LINE}" "${APACHE_CONF_FILE}"; then
+ echo "Adding a load line for mod_pagespeed to ${APACHE_CONF_FILE}."
+ ${SUDO_CMD} sh -c "echo ${MODPAGESPEED_LOAD_LINE} >> ${APACHE_CONF_FILE}"
+ fi
+ if grep -q "${MODPAGESPEED_LOAD_LINE}" "${APACHE_CONF_FILE}"; then
+ INSTALLATION_SUCCEEDED=1
+ fi
+fi
+
+echo ""
+if [ $INSTALLATION_SUCCEEDED -eq 1 ]; then
+ echo "Installation succeeded."
+ echo "Restart apache to enable mod_pagespeed."
+else
+ echo "Installation failed."
+fi
+
+rm -f "${TMP_CONF}" "${TMP_LOAD}"
+
diff --git a/trunk/src/install/mod_pagespeed_example/.htaccess b/trunk/src/install/mod_pagespeed_example/.htaccess
new file mode 100644
index 0000000..9113fe2
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/.htaccess
@@ -0,0 +1,22 @@
+# Whether this file will be read by httpd depends on settings in httpd.conf. For
+# now, the presence of it verifies that these options are allowed.
+#
+# TODO(jmarantz): Add system test that proves these options are functional.
+ModPagespeed on
+ModPagespeedEnableFilters combine_css
+ModPagespeedJsOutlineMinBytes 3000
+ModPagespeedDisallow *
+ModPagespeedAllow *.htm*
+ModPagespeedAllow *.shtm*
+ModPagespeedAllow *.xhtm*
+ModPagespeedAllow */
+ModPagespeedAllow http://*/images/*.png
+ModPagespeedAllow http://*/images/*.jpg
+ModPagespeedAllow http://*/images/*.gif
+ModPagespeedAllow http://*/styles/*.css
+ModPagespeedAllow http://*/*.js
+ModPagespeedDisallow */images/captcha/*
+ModPagespeedMapRewriteDomain rewrite_to_domain rewrite_from_domain
+ModPagespeedMapOriginDomain origin_to_domain origin_from_domain
+ModPagespeedDomain my_domain
+ModPagespeedAllow *.cgi*
diff --git a/trunk/src/install/mod_pagespeed_example/add_instrumentation.html b/trunk/src/install/mod_pagespeed_example/add_instrumentation.html
new file mode 100644
index 0000000..a611823
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/add_instrumentation.html
@@ -0,0 +1 @@
+<html><body>This page will load very quickly.</body></html>
diff --git a/trunk/src/install/mod_pagespeed_example/cgi/slow.cgi b/trunk/src/install/mod_pagespeed_example/cgi/slow.cgi
new file mode 100755
index 0000000..5cd5d54
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/cgi/slow.cgi
@@ -0,0 +1,8 @@
+#!/bin/bash
+# This cgi script just sleeps for a while then returns an image. It's meant to
+# simulate a server environment where some resources are dynamically generated
+# by a process which is subject to delay (e.g. mysql, php).
+sleep 10;
+echo Content-type: image/jpeg
+echo
+cat ../images/Puzzle.jpg
diff --git a/trunk/src/install/mod_pagespeed_example/collapse_whitespace.html b/trunk/src/install/mod_pagespeed_example/collapse_whitespace.html
new file mode 100644
index 0000000..32ae8b7
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/collapse_whitespace.html
@@ -0,0 +1,16 @@
+<html>
+
+ <head>
+ <title>Hello, world! </title>
+ <script var x = 'Hello, world!';</script>
+ </head>
+
+ <body>
+ Hello, World!
+ <pre>
+ Hello,
+ World!
+ </pre>
+ </body>
+
+</html>
diff --git a/trunk/src/install/mod_pagespeed_example/combine_css.html b/trunk/src/install/mod_pagespeed_example/combine_css.html
new file mode 100644
index 0000000..19f7b28
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/combine_css.html
@@ -0,0 +1,11 @@
+<head>
+ <link rel="stylesheet" type="text/css" href="styles/yellow.css">
+ <link rel="stylesheet" type="text/css" href="styles/blue.css">
+ <link rel="stylesheet" type="text/css" href="styles/big.css">
+ <link rel="stylesheet" type="text/css" href="styles/bold.css">
+</head>
+<body>
+ <div class="blue yellow big bold">
+ Hello, mod_pagespeed!
+ </div>
+</body>
diff --git a/trunk/src/install/mod_pagespeed_example/combine_heads.html b/trunk/src/install/mod_pagespeed_example/combine_heads.html
new file mode 100644
index 0000000..d98beae
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/combine_heads.html
@@ -0,0 +1,11 @@
+ <head>
+ <title>Hello, world!</title>
+ </head>
+ <body>
+ <div class="big">
+ Hello, world!
+ </div>
+ </body>
+ <head>
+ <link rel="stylesheet" type="text/css" href="styles/big.css">
+ </head>
diff --git a/trunk/src/install/mod_pagespeed_example/elide_attributes.html b/trunk/src/install/mod_pagespeed_example/elide_attributes.html
new file mode 100644
index 0000000..36df69b
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/elide_attributes.html
@@ -0,0 +1,7 @@
+<html>
+ <body>
+ Here is a disabled button:
+ <button name="ok" disabled="disabled">button</button><br/>
+ Here is a text input: <input name="email" type="text"/>
+ </body>
+</html>
diff --git a/trunk/src/install/mod_pagespeed_example/extend_cache.html b/trunk/src/install/mod_pagespeed_example/extend_cache.html
new file mode 100644
index 0000000..748afe3
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/extend_cache.html
@@ -0,0 +1,10 @@
+<html>
+ <body>
+ <p>mod_pagespeed will rewrite this image's source to include a content-hash.
+ Note that this requires a background asynchronous fetch of the image;
+ therefore the page may not be rewritten the first time. If it isn't, wait
+ a few seconds and reload.</p>
+
+ <img src="images/Puzzle.jpg"/>
+ </body>
+</html>
diff --git a/trunk/src/install/mod_pagespeed_example/images/BikeCrashIcn.png b/trunk/src/install/mod_pagespeed_example/images/BikeCrashIcn.png
new file mode 100644
index 0000000..cdfcb41
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/images/BikeCrashIcn.png
Binary files differ
diff --git a/trunk/src/install/mod_pagespeed_example/images/Cuppa.png b/trunk/src/install/mod_pagespeed_example/images/Cuppa.png
new file mode 100644
index 0000000..6fb9cf3
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/images/Cuppa.png
Binary files differ
diff --git a/trunk/src/install/mod_pagespeed_example/images/Puzzle.jpg b/trunk/src/install/mod_pagespeed_example/images/Puzzle.jpg
new file mode 100644
index 0000000..a119109
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/images/Puzzle.jpg
Binary files differ
diff --git a/trunk/src/install/mod_pagespeed_example/index.html b/trunk/src/install/mod_pagespeed_example/index.html
new file mode 100644
index 0000000..dd019b6
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/index.html
@@ -0,0 +1,361 @@
+<html>
+ <head>
+ <title>mod_pagespeed Examples Directory</title>
+ <style type="text/css">
+ table { width: 100%; border-collapse: collapse; text-color: black;}
+ td { padding: 1em }
+ tr { background-color: #eee; border: 1px solid black; }
+ td.doc { width: 20%;}
+ .code {font-family:monospace;}
+ td.before, td.after { width: 10% }
+ </style>
+ </head>
+ <body>
+ <h2><span class="code">mod_pagespeed</span> Filter Examples</h2>
+ <p>
+ Here are some of the most useful filters provided by mod_pagespeed. Each
+ one has a simple HTML example attached; click "before" to see the original
+ file, and "after" to see what mod_pagespeed produces with that filter (and
+ only that filter) enabled. The two versions should look exactly the same,
+ but the "after" one will be (slightly) speedier. Use "view source" to see
+ the <span class="code">mod_pagespeed</span> difference!
+ </p>
+ <table>
+<tr>
+ <td class="code">
+ <a href="http://code.google.com/speed/page-speed/docs/filter-instrumentation-add.html">
+ add_instrumentation
+ </a>
+ </td>
+ <td class="desc">
+ Adds client-side latency instrumentation.
+ </td>
+ <td class="before">
+ <a href="add_instrumentation.html?ModPagespeed=off">
+ before
+ </a>
+ </td>
+ <td class="after">
+ <a href="add_instrumentation.html?ModPagespeed=on&ModPagespeedFilters=add_instrumentation">
+ after
+ </a>
+ </td>
+</tr>
+
+<tr>
+ <td class="code">
+ <a href="http://code.google.com/speed/page-speed/docs/filter-cache-extend.html">
+ extend_cache
+ </a>
+ </td>
+ <td class="desc">
+ Improves cacheability.
+ </td>
+ <td class="before">
+ <a href="extend_cache.html?ModPagespeed=off">
+ before
+ </a>
+ </td>
+ <td class="after">
+ <a href="extend_cache.html?ModPagespeed=on&ModPagespeedFilters=extend_cache">
+ after
+ </a>
+ </td>
+</tr>
+
+<tr>
+ <td class="code">
+ <a href="http://code.google.com/speed/page-speed/docs/filter-whitespace-collapse.html">
+collapse_whitespace
+ </a>
+ </td>
+ <td class="desc">
+ Removes unnecessary whitespace in HTML.
+ </td>
+ <td class="before">
+ <a href="collapse_whitespace.html?ModPagespeed=off">
+ before
+ </a>
+ </td>
+ <td class="after">
+ <a href="collapse_whitespace.html?ModPagespeed=on&ModPagespeedFilters=collapse_whitespace">
+ after
+ </a>
+ </td>
+</tr>
+
+<tr>
+ <td class="code">
+ <a href="http://code.google.com/speed/page-speed/docs/filter-css-combine.html">
+combine_css
+ </a>
+ </td>
+ <td class="desc">
+ Combines multiple CSS files into one.
+ </td>
+ <td class="before">
+ <a href="combine_css.html?ModPagespeed=off">
+ before
+ </a>
+ </td>
+ <td class="after">
+ <a href="combine_css.html?ModPagespeed=on&ModPagespeedFilters=combine_css">
+ after
+ </a>
+ </td>
+</tr>
+
+<tr>
+ <td class="code">
+ <a href="http://code.google.com/speed/page-speed/docs/filter-head-combine.html">
+combine_heads
+ </a>
+ </td>
+ <td class="desc">
+ Combines multiple <head> elements into one.
+ </td>
+ <td class="before">
+ <a href="combine_heads.html?ModPagespeed=off">
+ before
+ </a>
+ </td>
+ <td class="after">
+ <a href="combine_heads.html?ModPagespeed=on&ModPagespeedFilters=combine_heads">
+ after
+ </a>
+ </td>
+</tr>
+
+<tr>
+ <td class="code">
+ <a href="http://code.google.com/speed/page-speed/docs/filter-css-to-head.html">
+move_css_to_head
+ </a>
+ </td>
+ <td class="desc">
+ Moves CSS into the <head> element.
+ </td>
+ <td class="before">
+ <a href="move_css_to_head.html?ModPagespeed=off">
+ before
+ </a>
+ </td>
+ <td class="after">
+ <a href="move_css_to_head.html?ModPagespeed=on&ModPagespeedFilters=move_css_to_head">
+ after
+ </a>
+ </td>
+</tr>
+
+<tr>
+ <td class="code">
+ <a href="http://code.google.com/speed/page-speed/docs/filter-attribute-elide.html">
+elide_attributes
+ </a>
+ </td>
+ <td class="desc">
+ Removes unnecessary attributes in HTML tags.
+ </td>
+ <td class="before">
+ <a href="elide_attributes.html?ModPagespeed=off">
+ before
+ </a>
+ </td>
+ <td class="after">
+ <a href="elide_attributes.html?ModPagespeed=on&ModPagespeedFilters=elide_attributes">
+ after
+ </a>
+ </td>
+</tr>
+
+<tr>
+ <td class="code">
+ <a href="http://code.google.com/speed/page-speed/docs/filter-css-inline.html">
+inline_css
+ </a>
+ </td>
+ <td class="desc">
+ Inlines small external CSS files.
+ </td>
+ <td class="before">
+ <a href="inline_css.html?ModPagespeed=off">
+ before
+ </a>
+ </td>
+ <td class="after">
+ <a href="inline_css.html?ModPagespeed=on&ModPagespeedFilters=inline_css">
+ after
+ </a>
+ </td>
+</tr>
+
+<tr>
+ <td class="code">
+ <a href="http://code.google.com/speed/page-speed/docs/filter-js-inline.html">
+inline_javascript
+ </a>
+ </td>
+ <td class="desc">
+ Inlines small external Javascript files.
+ </td>
+ <td class="before">
+ <a href="inline_javascript.html?ModPagespeed=off">
+ before
+ </a>
+ </td>
+ <td class="after">
+ <a href="inline_javascript.html?ModPagespeed=on&ModPagespeedFilters=inline_javascript">
+ after
+ </a>
+ </td>
+</tr>
+
+<tr>
+ <td class="code">
+ <a href="http://code.google.com/speed/page-speed/docs/filter-css-outline.html">
+outline_css
+ </a>
+ </td>
+ <td class="desc">
+ Moves large inline <style> tags into external files for cacheability.
+ </td>
+ <td class="before">
+ <a href="outline_css.html?ModPagespeed=off">
+ before
+ </a>
+ </td>
+ <td class="after">
+ <a href="outline_css.html?ModPagespeed=on&ModPagespeedFilters=outline_css">
+ after
+ </a>
+ </td>
+</tr>
+
+<tr>
+ <td class="code">
+ <a href="http://code.google.com/speed/page-speed/docs/filter-js-outline.html">
+outline_javascript
+ </a>
+ </td>
+ <td class="desc">
+ Moves large inline <script> tags into external files for cacheability.
+ </td>
+ <td class="before">
+ <a href="outline_javascript.html?ModPagespeed=off">
+ before
+ </a>
+ </td>
+ <td class="after">
+ <a href="outline_javascript.html?ModPagespeed=on&ModPagespeedFilters=outline_javascript">
+ after
+ </a>
+ </td>
+</tr>
+
+<tr>
+ <td class="code">
+ <a href="http://code.google.com/speed/page-speed/docs/filter-quote-remove.html">
+remove_quotes
+ </a>
+ </td>
+ <td class="desc">
+ Removes unnecessary quotes in HTML tags.
+ </td>
+ <td class="before">
+ <a href="remove_quotes.html?ModPagespeed=off">
+ before
+ </a>
+ </td>
+ <td class="after">
+ <a href="remove_quotes.html?ModPagespeed=on&ModPagespeedFilters=remove_quotes">
+ after
+ </a>
+ </td>
+</tr>
+
+<tr>
+ <td class="code">
+ <a href="http://code.google.com/speed/page-speed/docs/filter-comment-remove.html">
+remove_comments
+ </a>
+ </td>
+ <td class="desc">
+ Removes HTML comments.
+ </td>
+ <td class="before">
+ <a href="remove_comments.html?ModPagespeed=off">
+ before
+ </a>
+ </td>
+ <td class="after">
+ <a href="remove_comments.html?ModPagespeed=on&ModPagespeedFilters=remove_comments">
+ after
+ </a>
+ </td>
+</tr>
+
+<tr>
+ <td class="code">
+ <a href="http://code.google.com/speed/page-speed/docs/filter-css-minify.html">
+rewrite_css
+ </a>
+ </td>
+ <td class="desc">
+ Minifies CSS.
+ </td>
+ <td class="before">
+ <a href="rewrite_css.html?ModPagespeed=off">
+ before
+ </a>
+ </td>
+ <td class="after">
+ <a href="rewrite_css.html?ModPagespeed=on&ModPagespeedFilters=rewrite_css">
+ after
+ </a>
+ </td>
+</tr>
+
+<tr>
+ <td class="code">
+ <a href="http://code.google.com/speed/page-speed/docs/filter-image-optimize.html">
+rewrite_images
+ </a>
+ </td>
+ <td class="desc">
+ Rescales, and compresses images; inlines small ones.
+ </td>
+ <td class="before">
+ <a href="rewrite_images.html?ModPagespeed=off">
+ before
+ </a>
+ </td>
+ <td class="after">
+ <a href="rewrite_images.html?ModPagespeed=on&ModPagespeedFilters=rewrite_images">
+ after
+ </a>
+ </td>
+</tr>
+
+<tr>
+ <td class="code">
+ <a href="http://code.google.com/speed/page-speed/docs/filter-js-minify.html">
+rewrite_javascript
+ </a>
+ </td>
+ <td class="desc">
+ Minifies Javascript.
+ </td>
+ <td class="before">
+ <a href="rewrite_javascript.html?ModPagespeed=off">
+ before
+ </a>
+ </td>
+ <td class="after">
+ <a href="rewrite_javascript.html?ModPagespeed=on&ModPagespeedFilters=rewrite_javascript">
+ after
+ </a>
+ </td>
+</tr>
+ </table>
+ </body>
+<html>
diff --git a/trunk/src/install/mod_pagespeed_example/inline_css.html b/trunk/src/install/mod_pagespeed_example/inline_css.html
new file mode 100644
index 0000000..e7bb01f
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/inline_css.html
@@ -0,0 +1,10 @@
+<html>
+ <head>
+ <link rel="stylesheet" href="styles/all_styles.css">
+ </head>
+ <body>
+ <div class="blue yellow big bold">
+ Hello, world!
+ </div>
+ </body>
+</html>
diff --git a/trunk/src/install/mod_pagespeed_example/inline_javascript.html b/trunk/src/install/mod_pagespeed_example/inline_javascript.html
new file mode 100644
index 0000000..316fc90
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/inline_javascript.html
@@ -0,0 +1,8 @@
+<html>
+ <head>
+ <script type="text/javascript" src="inline_javascript.js"></script>
+ </head>
+ <body>
+ world!
+ </body>
+</html>
diff --git a/trunk/src/install/mod_pagespeed_example/inline_javascript.js b/trunk/src/install/mod_pagespeed_example/inline_javascript.js
new file mode 100644
index 0000000..fc84b31
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/inline_javascript.js
@@ -0,0 +1 @@
+document.write("Hello, ");
diff --git a/trunk/src/install/mod_pagespeed_example/move_css_to_head.html b/trunk/src/install/mod_pagespeed_example/move_css_to_head.html
new file mode 100644
index 0000000..af201a8
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/move_css_to_head.html
@@ -0,0 +1,9 @@
+<html>
+ <head></head>
+ <body>
+ <div class="blue yellow big bold">
+ Hello, world!
+ </div>
+ <link rel="stylesheet" type="text/css" href="styles/all_styles.css">
+ </body>
+</html>
diff --git a/trunk/src/install/mod_pagespeed_example/outline_css.html b/trunk/src/install/mod_pagespeed_example/outline_css.html
new file mode 100644
index 0000000..8779527
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/outline_css.html
@@ -0,0 +1,64 @@
+<html>
+ <head>
+ <title>Outline Resources example</title>
+ <style type="text/css" id="small">
+ .yellow {background-color: yellow;}
+ .blue {color: blue;}
+ .big {font-size: 8em;}
+ .bold {font-weight: bold;}
+ </style>
+ <style type="text/css" id="large">
+<!-- Just padding this out so it exceeds ModPagespeedCssOutlineMinBytes. -->
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;}
+ </style>
+ </head>
+ <body>
+ <div class="blue yellow big bold">
+ Hello, mod_pagespeed!
+ </div>
+ </body>
+</html>
diff --git a/trunk/src/install/mod_pagespeed_example/outline_javascript.html b/trunk/src/install/mod_pagespeed_example/outline_javascript.html
new file mode 100644
index 0000000..5207bea
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/outline_javascript.html
@@ -0,0 +1,1223 @@
+<html>
+ <head>
+ <title>Outline Resources example</title>
+ <script type="text/javascript" id="small"> var hello = 1; </script>
+ <script type="text/javascript" id="large">
+// 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.
+
+// Copyright 2006 Google Inc. All Rights Reserved.
+
+/**
+ * @fileoverview Bootstrap for the Google JS Library (Closure).
+ *
+ * In uncompiled mode base.js will write out Closure's deps file, unless the
+ * global <code>CLOSURE_NO_DEPS</code> is set to true. This allows projects to
+ * include their own deps file(s) from different locations.
+ *
+ */
+
+/**
+ * @define {boolean} Overridden to true by the compiler when --closure_pass
+ * or --mark_as_compiled is specified.
+ */
+var COMPILED = false;
+
+
+/**
+ * Base namespace for the Closure library. Checks to see goog is
+ * already defined in the current scope before assigning to prevent
+ * clobbering if base.js is loaded more than once.
+ */
+var goog = goog || {}; // Check to see if already defined in current scope
+
+
+/**
+ * Reference to the global context. In most cases this will be 'window'.
+ */
+goog.global = this;
+
+
+/**
+ * @define {boolean} DEBUG is provided as a convenience so that debugging code
+ * that should not be included in a production js_binary can be easily stripped
+ * by specifying --define goog.DEBUG=false to the JSCompiler. For example, most
+ * toString() methods should be declared inside an "if (goog.DEBUG)" conditional
+ * because they are generally used for debugging purposes and it is difficult
+ * for the JSCompiler to statically determine whether they are used.
+ */
+goog.DEBUG = true;
+
+
+/**
+ * @define {string} LOCALE defines the locale being used for compilation. It is
+ * used to select locale specific data to be compiled in js binary. BUILD rule
+ * can specify this value by "--define goog.LOCALE=<locale_name>" as JSCompiler
+ * option.
+ *
+ * Take into account that the locale code format is important. You should use
+ * the canonical Unicode format with hyphen as a delimiter. Language must be
+ * lowercase, Language Script - Capitalized, Region - UPPERCASE.
+ * There are few examples: pt-BR, en, en-US, sr-Latin-BO, zh-Hans-CN.
+ *
+ * See more info about locale codes here:
+ * http://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers
+ *
+ * For language codes you should use values defined by ISO 693-1. See it here
+ * http://www.w3.org/WAI/ER/IG/ert/iso639.htm. There is only one exception from
+ * this rule: the Hebrew language. For legacy reasons the old code (iw) should
+ * be used instead of the new code (he), see http://wiki/Main/IIISynonyms.
+ */
+goog.LOCALE = 'en'; // default to en
+
+
+/**
+ * Indicates whether or not we can call 'eval' directly to eval code in the
+ * global scope. Set to a Boolean by the first call to goog.globalEval (which
+ * empirically tests whether eval works for globals). @see goog.globalEval
+ * @type {boolean?}
+ * @private
+ */
+goog.evalWorksForGlobals_ = null;
+
+
+/**
+ * Creates object stubs for a namespace. When present in a file, goog.provide
+ * also indicates that the file defines the indicated object. Calls to
+ * goog.provide are resolved by the compiler if --closure_pass is set.
+ * @param {string} name name of the object that this file defines.
+ */
+goog.provide = function(name) {
+ if (!COMPILED) {
+ // Ensure that the same namespace isn't provided twice. This is intended
+ // to teach new developers that 'goog.provide' is effectively a variable
+ // declaration. And when JSCompiler transforms goog.provide into a real
+ // variable declaration, the compiled JS should work the same as the raw
+ // JS--even when the raw JS uses goog.provide incorrectly.
+ if (goog.getObjectByName(name) && !goog.implicitNamespaces_[name]) {
+ throw Error('Namespace "' + name + '" already declared.');
+ }
+
+ var namespace = name;
+ while ((namespace = namespace.substring(0, namespace.lastIndexOf('.')))) {
+ goog.implicitNamespaces_[namespace] = true;
+ }
+ }
+
+ goog.exportPath_(name);
+};
+
+
+if (!COMPILED) {
+ /**
+ * Namespaces implicitly defined by goog.provide. For example,
+ * goog.provide('goog.events.Event') implicitly declares
+ * that 'goog' and 'goog.events' must be namespaces.
+ *
+ * @type {Object}
+ * @private
+ */
+ goog.implicitNamespaces_ = {};
+}
+
+
+/**
+ * Builds an object structure for the provided namespace path,
+ * ensuring that names that already exist are not overwritten. For
+ * example:
+ * "a.b.c" -> a = {};a.b={};a.b.c={};
+ * Used by goog.provide and goog.exportSymbol.
+ * @param {string} name name of the object that this file defines.
+ * @param {Object} opt_object the object to expose at the end of the path.
+ * @param {Object} opt_objectToExportTo The object to add the path to; default
+ * is |goog.global|.
+ * @private
+ */
+goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) {
+ var parts = name.split('.');
+ var cur = opt_objectToExportTo || goog.global;
+
+ // Internet Explorer exhibits strange behavior when throwing errors from
+ // methods externed in this manner. See the testExportSymbolExceptions in
+ // base_test.html for an example.
+ if (!(parts[0] in cur) && cur.execScript) {
+ cur.execScript('var ' + parts[0]);
+ }
+
+ // Certain browsers cannot parse code in the form for((a in b); c;);
+ // This pattern is produced by the JSCompiler when it collapses the
+ // statement above into the conditional loop below. To prevent this from
+ // happening, use a for-loop and reserve the init logic as below.
+
+ // Parentheses added to eliminate strict JS warning in Firefox.
+ for (var part; parts.length && (part = parts.shift());) {
+ if (!parts.length && goog.isDef(opt_object)) {
+ // last part and we have an object; use it
+ cur[part] = opt_object;
+ } else if (cur[part]) {
+ cur = cur[part];
+ } else {
+ cur = cur[part] = {};
+ }
+ }
+};
+
+
+/**
+ * Returns an object based on its fully qualified external name. If you are
+ * using a compilation pass that renames property names beware that using this
+ * function will not find renamed properties.
+ *
+ * @param {string} name The fully qualified name.
+ * @param {Object} opt_obj The object within which to look; default is
+ * |goog.global|.
+ * @return {Object?} The object or, if not found, null.
+ */
+goog.getObjectByName = function(name, opt_obj) {
+ var parts = name.split('.');
+ var cur = opt_obj || goog.global;
+ for (var part; part = parts.shift(); ) {
+ if (cur[part]) {
+ cur = cur[part];
+ } else {
+ return null;
+ }
+ }
+ return cur;
+};
+
+
+/**
+ * Globalizes a whole namespace, such as goog or goog.lang.
+ *
+ * @param {Object} obj The namespace to globalize.
+ * @param {Object} opt_global The object to add the properties to.
+ * @deprecated Properties may be explicitly exported to the global scope, but
+ * this should no longer be done in bulk.
+ */
+goog.globalize = function(obj, opt_global) {
+ var global = opt_global || goog.global;
+ for (var x in obj) {
+ global[x] = obj[x];
+ }
+};
+
+
+/**
+ * Adds a dependency from a file to the files it requires.
+ * @param {string} relPath The path to the js file.
+ * @param {Array} provides An array of strings with the names of the objects
+ * this file provides.
+ * @param {Array} requires An array of strings with the names of the objects
+ * this file requires.
+ */
+goog.addDependency = function(relPath, provides, requires) {
+ if (!COMPILED) {
+ var provide, require;
+ var path = relPath.replace(/\\/g, '/');
+ var deps = goog.dependencies_;
+ for (var i = 0; provide = provides[i]; i++) {
+ deps.nameToPath[provide] = path;
+ if (!(path in deps.pathToNames)) {
+ deps.pathToNames[path] = {};
+ }
+ deps.pathToNames[path][provide] = true;
+ }
+ for (var j = 0; require = requires[j]; j++) {
+ if (!(path in deps.requires)) {
+ deps.requires[path] = {};
+ }
+ deps.requires[path][require] = true;
+ }
+ }
+};
+
+
+/**
+ * Implements a system for the dynamic resolution of dependencies
+ * that works in parallel with the BUILD system. Note that all calls
+ * to goog.require will be stripped by the JSCompiler when the
+ * --closure_pass option is used.
+ * @param {string} rule Rule to include, in the form goog.package.part.
+ */
+goog.require = function(rule) {
+
+ // if the object already exists we do not need do do anything
+ // TODO: If we start to support require based on file name this has
+ // to change
+ // TODO: If we allow goog.foo.* this has to change
+ // TODO: If we implement dynamic load after page load we should probably
+ // not remove this code for the compiled output
+ if (!COMPILED) {
+ if (goog.getObjectByName(rule)) {
+ return;
+ }
+ var path = goog.getPathFromDeps_(rule);
+ if (path) {
+ goog.included_[path] = true;
+ goog.writeScripts_();
+ } else {
+ var errorMessage = 'goog.require could not find: ' + rule;
+ if (goog.global.console) {
+ goog.global.console['error'](errorMessage);
+ }
+
+ throw Error(errorMessage);
+ }
+ }
+};
+
+
+/**
+ * Whether goog.require should throw an exception if it fails.
+ * @type {boolean}
+ */
+goog.useStrictRequires = false;
+
+
+/**
+ * Path for included scripts
+ * @type {string}
+ */
+goog.basePath = '';
+
+
+/**
+ * A hook for overriding the base path.
+ * @type {string|undefined}
+ */
+goog.global.CLOSURE_BASE_PATH;
+
+
+/**
+ * Whether to write out Closure's deps file. By default,
+ * the deps are written.
+ * @type {boolean|undefined}
+ */
+goog.global.CLOSURE_NO_DEPS;
+
+
+/**
+ * Null function used for default values of callbacks, etc.
+ * @type {!Function}
+ */
+goog.nullFunction = function() {};
+
+
+/**
+ * The identity function. Returns its first argument.
+ *
+ * @param {*} var_args The arguments of the function.
+ * @return {*} The first argument.
+ * @deprecated Use goog.functions.identity instead.
+ */
+goog.identityFunction = function(var_args) {
+ return arguments[0];
+};
+
+
+/**
+ * When defining a class Foo with an abstract method bar(), you can do:
+ *
+ * Foo.prototype.bar = goog.abstractMethod
+ *
+ * Now if a subclass of Foo fails to override bar(), an error
+ * will be thrown when bar() is invoked.
+ *
+ * Note: This does not take the name of the function to override as
+ * an argument because that would make it more difficult to obfuscate
+ * our JavaScript code.
+ *
+ * @type {!Function}
+ * @throws {Error} when invoked to indicate the method should be
+ * overridden.
+ */
+goog.abstractMethod = function() {
+ throw Error('unimplemented abstract method');
+};
+
+
+/**
+ * Adds a {@code getInstance} static method that always return the same instance
+ * object.
+ * @param {!Function} ctor The constructor for the class to add the static
+ * method to.
+ */
+goog.addSingletonGetter = function(ctor) {
+ ctor.getInstance = function() {
+ return ctor.instance_ || (ctor.instance_ = new ctor());
+ };
+};
+
+
+if (!COMPILED) {
+ /**
+ * Object used to keep track of urls that have already been added. This
+ * record allows the prevention of circular dependencies.
+ * @type {Object}
+ * @private
+ */
+ goog.included_ = {};
+
+
+ /**
+ * This object is used to keep track of dependencies and other data that is
+ * used for loading scripts
+ * @private
+ * @type {Object}
+ */
+ goog.dependencies_ = {
+ pathToNames: {}, // 1 to many
+ nameToPath: {}, // 1 to 1
+ requires: {}, // 1 to many
+ visited: {}, // used when resolving dependencies to prevent us from
+ // visiting the file twice
+ written: {} // used to keep track of script files we have written
+ };
+
+
+ /**
+ * Tries to detect whether is in the context of an HTML document.
+ * @return {boolean} True if it looks like HTML document.
+ * @private
+ */
+ goog.inHtmlDocument_ = function() {
+ var doc = goog.global.document;
+ return typeof doc != 'undefined' &&
+ 'write' in doc; // XULDocument misses write.
+ };
+
+
+ /**
+ * Tries to detect the base path of the base.js script that bootstraps Closure
+ * @private
+ */
+ goog.findBasePath_ = function() {
+ if (!goog.inHtmlDocument_()) {
+ return;
+ }
+ var doc = goog.global.document;
+ if (goog.global.CLOSURE_BASE_PATH) {
+ goog.basePath = goog.global.CLOSURE_BASE_PATH;
+ return;
+ }
+ var scripts = doc.getElementsByTagName('script');
+ for (var script, i = 0; script = scripts[i]; i++) {
+ var src = script.src;
+ var l = src.length;
+ if (src.substr(l - 7) == 'base.js') {
+ goog.basePath = src.substr(0, l - 7);
+ return;
+ }
+ }
+ };
+
+
+ /**
+ * Writes a script tag if, and only if, that script hasn't already been added
+ * to the document. (Must be called at execution time)
+ * @param {string} src Script source.
+ * @private
+ */
+ goog.writeScriptTag_ = function(src) {
+ if (goog.inHtmlDocument_() &&
+ !goog.dependencies_.written[src]) {
+ goog.dependencies_.written[src] = true;
+ var doc = goog.global.document;
+ doc.write('<script type="text/javascript" src="' +
+ src + '"></' + 'script>');
+ }
+ };
+
+
+ /**
+ * Resolves dependencies based on the dependencies added using addDependency
+ * and calls writeScriptTag_ in the correct order.
+ * @private
+ */
+ goog.writeScripts_ = function() {
+ // the scripts we need to write this time
+ var scripts = [];
+ var seenScript = {};
+ var deps = goog.dependencies_;
+
+ function visitNode(path) {
+ if (path in deps.written) {
+ return;
+ }
+
+ // we have already visited this one. We can get here if we have cyclic
+ // dependencies
+ if (path in deps.visited) {
+ if (!(path in seenScript)) {
+ seenScript[path] = true;
+ scripts.push(path);
+ }
+ return;
+ }
+
+ deps.visited[path] = true;
+
+ if (path in deps.requires) {
+ for (var requireName in deps.requires[path]) {
+ if (requireName in deps.nameToPath) {
+ visitNode(deps.nameToPath[requireName]);
+ } else if (!goog.getObjectByName(requireName)) {
+ // If the required name is defined, we assume that this
+ // dependency was bootstapped by other means. Otherwise,
+ // throw an exception.
+ throw Error('Undefined nameToPath for ' + requireName);
+ }
+ }
+ }
+
+ if (!(path in seenScript)) {
+ seenScript[path] = true;
+ scripts.push(path);
+ }
+ }
+
+ for (var path in goog.included_) {
+ if (!deps.written[path]) {
+ visitNode(path);
+ }
+ }
+
+ for (var i = 0; i < scripts.length; i++) {
+ if (scripts[i]) {
+ goog.writeScriptTag_(goog.basePath + scripts[i]);
+ } else {
+ throw Error('Undefined script input');
+ }
+ }
+ };
+
+
+ /**
+ * Looks at the dependency rules and tries to determine the script file that
+ * fulfills a particular rule.
+ * @param {string} rule In the form goog.namespace.Class or project.script.
+ * @return {string?} Url corresponding to the rule, or null.
+ * @private
+ */
+ goog.getPathFromDeps_ = function(rule) {
+ if (rule in goog.dependencies_.nameToPath) {
+ return goog.dependencies_.nameToPath[rule];
+ } else {
+ return null;
+ }
+ };
+
+ goog.findBasePath_();
+
+ // Allow projects to manage the deps files themselves.
+ if (!goog.global.CLOSURE_NO_DEPS) {
+ // [MODIFIED FOR MOD_PAGESPEED_DEMO] goog.writeScriptTag_(goog.basePath + 'deps.js');
+ }
+}
+
+
+
+//==============================================================================
+// Language Enhancements
+//==============================================================================
+
+
+/**
+ * This is a "fixed" version of the typeof operator. It differs from the typeof
+ * operator in such a way that null returns 'null' and arrays return 'array'.
+ * @param {*} value The value to get the type of.
+ * @return {string} The name of the type.
+ */
+goog.typeOf = function(value) {
+ var s = typeof value;
+ if (s == 'object') {
+ if (value) {
+ // We cannot use constructor == Array or instanceof Array because
+ // different frames have different Array objects. In IE6, if the iframe
+ // where the array was created is destroyed, the array loses its
+ // prototype. Then dereferencing val.splice here throws an exception, so
+ // we can't use goog.isFunction. Calling typeof directly returns 'unknown'
+ // so that will work. In this case, this function will return false and
+ // most array functions will still work because the array is still
+ // array-like (supports length and []) even though it has lost its
+ // prototype.
+ // Mark Miller noticed that Object.prototype.toString
+ // allows access to the unforgeable [[Class]] property.
+ // 15.2.4.2 Object.prototype.toString ( )
+ // When the toString method is called, the following steps are taken:
+ // 1. Get the [[Class]] property of this object.
+ // 2. Compute a string value by concatenating the three strings
+ // "[object ", Result(1), and "]".
+ // 3. Return Result(2).
+ // and this behavior survives the destruction of the execution context.
+ if (value instanceof Array || // Works quickly in same execution context.
+ // If value is from a different execution context then
+ // !(value instanceof Object), which lets us early out in the common
+ // case when value is from the same context but not an array.
+ // The {if (value)} check above means we don't have to worry about
+ // undefined behavior of Object.prototype.toString on null/undefined.
+ //
+ // HACK: In order to use an Object prototype method on the arbitrary
+ // value, the compiler requires the value be cast to type Object,
+ // even though the ECMA spec explicitly allows it.
+ (!(value instanceof Object) &&
+ (Object.prototype.toString.call(
+ /** @type {Object} */ (value)) == '[object Array]') ||
+
+ // In IE all non value types are wrapped as objects across window
+ // boundaries (not iframe though) so we have to do object detection
+ // for this edge case
+ typeof value.length == 'number' &&
+ typeof value.splice != 'undefined' &&
+ typeof value.propertyIsEnumerable != 'undefined' &&
+ !value.propertyIsEnumerable('splice')
+
+ )) {
+ return 'array';
+ }
+ // HACK: There is still an array case that fails.
+ // function ArrayImpostor() {}
+ // ArrayImpostor.prototype = [];
+ // var impostor = new ArrayImpostor;
+ // this can be fixed by getting rid of the fast path
+ // (value instanceof Array) and solely relying on
+ // (value && Object.prototype.toString.vall(value) === '[object Array]')
+ // but that would require many more function calls and is not warranted
+ // unless closure code is receiving objects from untrusted sources.
+
+ // IE in cross-window calls does not correctly marshal the function type
+ // (it appears just as an object) so we cannot use just typeof val ==
+ // 'function'. However, if the object has a call property, it is a
+ // function.
+ if (!(value instanceof Object) &&
+ (Object.prototype.toString.call(
+ /** @type {Object} */ (value)) == '[object Function]' ||
+ typeof value.call != 'undefined' &&
+ typeof value.propertyIsEnumerable != 'undefined' &&
+ !value.propertyIsEnumerable('call'))) {
+ return 'function';
+ }
+
+
+ } else {
+ return 'null';
+ }
+
+ // In Safari typeof nodeList returns 'function', and on Firefox
+ // typeof behaves similarly for HTML{Applet,Embed,Object}Elements
+ // and RegExps. We would like to return object for those and we can
+ // detect an invalid function by making sure that the function
+ // object has a call method.
+ } else if (s == 'function' && typeof value.call == 'undefined') {
+ return 'object';
+ }
+ return s;
+};
+
+
+/**
+ * Safe way to test whether a property is enumarable. It allows testing
+ * for enumerable on objects where 'propertyIsEnumerable' is overridden or
+ * does not exist (like DOM nodes in IE). Does not use browser native
+ * Object.propertyIsEnumerable.
+ * @param {Object} object The object to test if the property is enumerable.
+ * @param {string} propName The property name to check for.
+ * @return {boolean} True if the property is enumarable.
+ * @private
+ */
+goog.propertyIsEnumerableCustom_ = function(object, propName) {
+ // KJS in Safari 2 is not ECMAScript compatible and lacks crucial methods
+ // such as propertyIsEnumerable. We therefore use a workaround.
+ // Does anyone know a more efficient work around?
+ if (propName in object) {
+ for (var key in object) {
+ if (key == propName &&
+ Object.prototype.hasOwnProperty.call(object, propName)) {
+ return true;
+ }
+ }
+ }
+ return false;
+};
+
+
+/**
+ * Safe way to test whether a property is enumarable. It allows testing
+ * for enumerable on objects where 'propertyIsEnumerable' is overridden or
+ * does not exist (like DOM nodes in IE).
+ * @param {Object} object The object to test if the property is enumerable.
+ * @param {string} propName The property name to check for.
+ * @return {boolean} True if the property is enumarable.
+ * @private
+ */
+goog.propertyIsEnumerable_ = function(object, propName) {
+ // In IE if object is from another window, cannot use propertyIsEnumerable
+ // from this window's Object. Will raise a 'JScript object expected' error.
+ if (object instanceof Object) {
+ return Object.prototype.propertyIsEnumerable.call(object, propName);
+ } else {
+ return goog.propertyIsEnumerableCustom_(object, propName);
+ }
+};
+
+
+/**
+ * Returns true if the specified value is not |undefined|.
+ * WARNING: Do not use this to test if an object has a property. Use the in
+ * operator instead. Additionally, this function assumes that the global
+ * undefined variable has not been redefined.
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is defined.
+ */
+goog.isDef = function(val) {
+ return val !== undefined;
+};
+
+
+/**
+ * Returns true if the specified value is |null|
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is null.
+ */
+goog.isNull = function(val) {
+ return val === null;
+};
+
+
+/**
+ * Returns true if the specified value is defined and not null
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is defined and not null.
+ */
+goog.isDefAndNotNull = function(val) {
+ // Note that undefined == null.
+ return val != null;
+};
+
+
+/**
+ * Returns true if the specified value is an array
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is an array.
+ */
+goog.isArray = function(val) {
+ return goog.typeOf(val) == 'array';
+};
+
+
+/**
+ * Returns true if the object looks like an array. To qualify as array like
+ * the value needs to be either a NodeList or an object with a Number length
+ * property.
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is an array.
+ */
+goog.isArrayLike = function(val) {
+ var type = goog.typeOf(val);
+ return type == 'array' || type == 'object' && typeof val.length == 'number';
+};
+
+
+/**
+ * Returns true if the object looks like a Date. To qualify as Date-like
+ * the value needs to be an object and have a getFullYear() function.
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is a like a Date.
+ */
+goog.isDateLike = function(val) {
+ return goog.isObject(val) && typeof val.getFullYear == 'function';
+};
+
+
+/**
+ * Returns true if the specified value is a string
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is a string.
+ */
+goog.isString = function(val) {
+ return typeof val == 'string';
+};
+
+
+/**
+ * Returns true if the specified value is a boolean
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is boolean.
+ */
+goog.isBoolean = function(val) {
+ return typeof val == 'boolean';
+};
+
+
+/**
+ * Returns true if the specified value is a number
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is a number.
+ */
+goog.isNumber = function(val) {
+ return typeof val == 'number';
+};
+
+
+/**
+ * Returns true if the specified value is a function
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is a function.
+ */
+goog.isFunction = function(val) {
+ return goog.typeOf(val) == 'function';
+};
+
+
+/**
+ * Returns true if the specified value is an object. This includes arrays
+ * and functions.
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is an object.
+ */
+goog.isObject = function(val) {
+ var type = goog.typeOf(val);
+ return type == 'object' || type == 'array' || type == 'function';
+};
+
+
+/**
+ * Adds a hash code field to an object. The hash code is unique for the
+ * given object.
+ * @param {Object} obj The object to get the hash code for.
+ * @return {number} The hash code for the object.
+ */
+goog.getHashCode = function(obj) {
+ // In IE, DOM nodes do not extend Object so they do not have this method.
+ // we need to check hasOwnProperty because the proto might have this set.
+
+ // TODO: There is a proposal to add hashcode as a global function to JS2
+ // we should keep track of this process so we can use that whenever
+ // it starts to show up in the real world.
+ if (obj.hasOwnProperty && obj.hasOwnProperty(goog.HASH_CODE_PROPERTY_)) {
+ return obj[goog.HASH_CODE_PROPERTY_];
+ }
+ if (!obj[goog.HASH_CODE_PROPERTY_]) {
+ obj[goog.HASH_CODE_PROPERTY_] = ++goog.hashCodeCounter_;
+ }
+ return obj[goog.HASH_CODE_PROPERTY_];
+};
+
+
+/**
+ * Removes the hash code field from an object.
+ * @param {Object} obj The object to remove the field from.
+ */
+goog.removeHashCode = function(obj) {
+ // DOM nodes in IE are not instance of Object and throws exception
+ // for delete. Instead we try to use removeAttribute
+ if ('removeAttribute' in obj) {
+ obj.removeAttribute(goog.HASH_CODE_PROPERTY_);
+ }
+ /** @preserveTry */
+ try {
+ delete obj[goog.HASH_CODE_PROPERTY_];
+ } catch (ex) {
+ }
+};
+
+
+/**
+ * Name for hash code property. Initialized in a way to help avoid collisions
+ * with other closure javascript on the same page.
+ * @type {string}
+ * @private
+ */
+goog.HASH_CODE_PROPERTY_ = 'closure_hashCode_' +
+ Math.floor(Math.random() * 2147483648).toString(36);
+
+
+/**
+ * Counter for hash codes.
+ * @type {number}
+ * @private
+ */
+goog.hashCodeCounter_ = 0;
+
+
+/**
+ * Clone an object/array (recursively)
+ * @param {Object} proto Object to clone.
+ * @return {Object} Clone of x;.
+ */
+goog.cloneObject = function(proto) {
+ var type = goog.typeOf(proto);
+ if (type == 'object' || type == 'array') {
+ if (proto.clone) {
+ // TODO Change to proto.clone() once # args warn is removed
+ return proto.clone.call(proto);
+ }
+ var clone = type == 'array' ? [] : {};
+ for (var key in proto) {
+ clone[key] = goog.cloneObject(proto[key]);
+ }
+ return clone;
+ }
+
+ return proto;
+};
+
+
+/**
+ * Forward declaration for the clone method. This is necessary until the
+ * compiler can better support duck-typing constructs as used in
+ * goog.cloneObject.
+ *
+ * TODO: Remove once the JSCompiler can infer that the check for
+ * proto.clone is safe in goog.cloneObject.
+ *
+ * @type {Function}
+ */
+Object.prototype.clone;
+
+
+/**
+ * Partially applies this function to a particular 'this object' and zero or
+ * more arguments. The result is a new function with some arguments of the first
+ * function pre-filled and the value of |this| 'pre-specified'.<br><br>
+ *
+ * Remaining arguments specified at call-time are appended to the pre-
+ * specified ones.<br><br>
+ *
+ * Also see: {@link #partial}.<br><br>
+ *
+ * Usage:
+ * <pre>var barMethBound = bind(myFunction, myObj, 'arg1', 'arg2');
+ * barMethBound('arg3', 'arg4');</pre>
+ *
+ * @param {Function} fn A function to partially apply.
+ * @param {Object|undefined} selfObj Specifies the object which |this| should
+ * point to when the function is run. If the value is null or undefined, it
+ * will default to the global object.
+ * @param {*} var_args Additional arguments that are partially
+ * applied to the function.
+ *
+ * @return {!Function} A partially-applied form of the function bind() was
+ * invoked as a method of.
+ */
+goog.bind = function(fn, selfObj, var_args) {
+ var context = selfObj || goog.global;
+
+ if (arguments.length > 2) {
+ var boundArgs = Array.prototype.slice.call(arguments, 2);
+ return function() {
+ // Prepend the bound arguments to the current arguments.
+ var newArgs = Array.prototype.slice.call(arguments);
+ Array.prototype.unshift.apply(newArgs, boundArgs);
+ return fn.apply(context, newArgs);
+ };
+
+ } else {
+ return function() {
+ return fn.apply(context, arguments);
+ };
+ }
+};
+
+
+/**
+ * Like bind(), except that a 'this object' is not required. Useful when the
+ * target function is already bound.
+ *
+ * Usage:
+ * var g = partial(f, arg1, arg2);
+ * g(arg3, arg4);
+ *
+ * @param {Function} fn A function to partially apply.
+ * @param {*} var_args Additional arguments that are partially
+ * applied to fn.
+ * @return {!Function} A partially-applied form of the function bind() was
+ * invoked as a method of.
+ */
+goog.partial = function(fn, var_args) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ return function() {
+ // Prepend the bound arguments to the current arguments.
+ var newArgs = Array.prototype.slice.call(arguments);
+ newArgs.unshift.apply(newArgs, args);
+ return fn.apply(this, newArgs);
+ };
+};
+
+
+/**
+ * Copies all the members of a source object to a target object.
+ * @param {Object} target Target.
+ * @param {Object} source Source.
+ * @deprecated Use goog.object.extend instead.
+ */
+goog.mixin = function(target, source) {
+ for (var x in source) {
+ target[x] = source[x];
+ }
+
+ // For IE the for-in-loop does not contain any properties that are not
+ // enumerable on the prototype object (for example, isPrototypeOf from
+ // Object.prototype) but also it will not include 'replace' on objects that
+ // extend String and change 'replace' (not that it is common for anyone to
+ // extend anything except Object).
+};
+
+
+/**
+ * A simple wrapper for new Date().getTime().
+ *
+ * @return {number} An integer value representing the number of milliseconds
+ * between midnight, January 1, 1970 and the current time.
+ */
+goog.now = Date.now || (function() {
+ return new Date().getTime();
+});
+
+
+/**
+ * Evals javascript in the global scope. In IE this uses execScript, other
+ * browsers use goog.global.eval. If goog.global.eval does not evaluate in the
+ * global scope (for example, in Safari), appends a script tag instead.
+ * Throws an exception if neither execScript or eval is defined.
+ * @param {string} script JavaScript string.
+ */
+goog.globalEval = function(script) {
+ if (goog.global.execScript) {
+ goog.global.execScript(script, 'JavaScript');
+ } else if (goog.global.eval) {
+ // Test to see if eval works
+ if (goog.evalWorksForGlobals_ == null) {
+ goog.global.eval('var _et_ = 1;');
+ if (typeof goog.global['_et_'] != 'undefined') {
+ delete goog.global['_et_'];
+ goog.evalWorksForGlobals_ = true;
+ } else {
+ goog.evalWorksForGlobals_ = false;
+ }
+ }
+
+ if (goog.evalWorksForGlobals_) {
+ goog.global.eval(script);
+ } else {
+ var doc = goog.global.document;
+ var scriptElt = doc.createElement('script');
+ scriptElt.type = 'text/javascript';
+ scriptElt.defer = false;
+ // NOTE: can't use .innerHTML since "t('<test>')" will fail and
+ // .text doesn't work in Safari 2. Therefore we append a text node.
+ scriptElt.appendChild(doc.createTextNode(script));
+ doc.body.appendChild(scriptElt);
+ doc.body.removeChild(scriptElt);
+ }
+ } else {
+ throw Error('goog.globalEval not available');
+ }
+};
+
+
+/**
+ * A macro for defining composite types.
+ *
+ * By assigning goog.typedef to a name, this tells JSCompiler that this is not
+ * the name of a class, but rather it's the name of a composite type.
+ *
+ * For example,
+ * /** @type {Array|NodeList} / goog.ArrayLike = goog.typedef;
+ * will tell JSCompiler to replace all appearances of goog.ArrayLike in type
+ * definitions with the union of Array and NodeList.
+ *
+ * Does nothing in uncompiled code.
+ */
+goog.typedef = true;
+
+
+/**
+ * Optional map of CSS class names to obfuscated names used with
+ * goog.getCssName().
+ * @type {Object|undefined}
+ * @private
+ * @see goog.setCssNameMapping
+ */
+goog.cssNameMapping_;
+
+
+/**
+ * Handles strings that are intended to be used as CSS class names.
+ *
+ * Without JS Compiler the arguments are simple joined with a hyphen and passed
+ * through unaltered.
+ *
+ * With the JS Compiler the arguments are inlined, e.g:
+ * var x = goog.getCssName('foo');
+ * var y = goog.getCssName(this.baseClass, 'active');
+ * becomes:
+ * var x= 'foo';
+ * var y = this.baseClass + '-active';
+ *
+ * If a CSS renaming map is passed to the compiler it will replace symbols in
+ * the classname. If one argument is passed it will be processed, if two are
+ * passed only the modifier will be processed, as it is assumed the first
+ * argument was generated as a result of calling goog.getCssName.
+ *
+ * Names are split on 'hyphen' and processed in parts such that the following
+ * are equivalent:
+ * var base = goog.getCssName('baseclass');
+ * goog.getCssName(base, 'modifier');
+ * goog.getCSsName('baseclass-modifier');
+ *
+ * If any part does not appear in the renaming map a warning is logged and the
+ * original, unobfuscated class name is inlined.
+ *
+ * @param {string} className The class name.
+ * @param {string} opt_modifier A modifier to be appended to the class name.
+ * @return {string} The class name or the concatenation of the class name and
+ * the modifier.
+ */
+goog.getCssName = function(className, opt_modifier) {
+ var cssName = className + (opt_modifier ? '-' + opt_modifier : '');
+ return (goog.cssNameMapping_ && (cssName in goog.cssNameMapping_)) ?
+ goog.cssNameMapping_[cssName] : cssName;
+};
+
+
+/**
+ * Sets the map to check when returning a value from goog.getCssName(). Example:
+ * <pre>
+ * goog.setCssNameMapping({
+ * "goog-menu": "a",
+ * "goog-menu-disabled": "a-b",
+ * "CSS_LOGO": "b",
+ * "hidden": "c"
+ * });
+ *
+ * // The following evaluates to: "a a-b".
+ * goog.getCssName('goog-menu') + ' ' + goog.getCssName('goog-menu', 'disabled')
+ * </pre>
+ * When declared as a map of string literals to string literals, the JSCompiler
+ * will replace all calls to goog.getCssName() using the supplied map if the
+ * --closure_pass flag is set.
+ *
+ * @param {!Object} mapping A map of strings to strings where keys are possible
+ * arguments to goog.getCssName() and values are the corresponding values
+ * that should be returned.
+ */
+goog.setCssNameMapping = function(mapping) {
+ goog.cssNameMapping_ = mapping;
+};
+
+
+/**
+ * Abstract implementation of goog.getMsg for use with localized messages.
+ * @param {string} str Translatable string, places holders in the form {$foo}.
+ * @param {Object} opt_values Map of place holder name to value.
+ * @return {string} message with placeholders filled.
+ */
+goog.getMsg = function(str, opt_values) {
+ var values = opt_values || {};
+ for (var key in values) {
+ str = str.replace(new RegExp('\\{\\$' + key + '\\}', 'gi'), values[key]);
+ }
+ return str;
+};
+
+
+/**
+ * Exposes an unobfuscated global namespace path for the given object.
+ * Note that fields of the exported object *will* be obfuscated,
+ * unless they are exported in turn via this function or
+ * goog.exportProperty
+ *
+ * <p>Also handy for making public items that are defined in anonymous
+ * closures.
+ *
+ * ex. goog.exportSymbol('Foo', Foo);
+ *
+ * ex. goog.exportSymbol('public.path.Foo.staticFunction',
+ * Foo.staticFunction);
+ * public.path.Foo.staticFunction();
+ *
+ * ex. goog.exportSymbol('public.path.Foo.prototype.myMethod',
+ * Foo.prototype.myMethod);
+ * new public.path.Foo().myMethod();
+ *
+ * @param {string} publicPath Unobfuscated name to export.
+ * @param {Object} object Object the name should point to.
+ * @param {Object} opt_objectToExportTo The object to add the path to; default
+ * is |goog.global|.
+ */
+goog.exportSymbol = function(publicPath, object, opt_objectToExportTo) {
+ goog.exportPath_(publicPath, object, opt_objectToExportTo);
+};
+
+
+/**
+ * Exports a property unobfuscated into the object's namespace.
+ * ex. goog.exportProperty(Foo, 'staticFunction', Foo.staticFunction);
+ * ex. goog.exportProperty(Foo.prototype, 'myMethod', Foo.prototype.myMethod);
+ * @param {Object} object Object whose static property is being exported.
+ * @param {string} publicName Unobfuscated name to export.
+ * @param {Object} symbol Object the name should point to.
+ */
+goog.exportProperty = function(object, publicName, symbol) {
+ object[publicName] = symbol;
+};
+
+
+/**
+ * Inherit the prototype methods from one constructor into another.
+ *
+ * Usage:
+ * <pre>
+ * function ParentClass(a, b) { }
+ * ParentClass.prototype.foo = function(a) { }
+ *
+ * function ChildClass(a, b, c) {
+ * ParentClass.call(this, a, b);
+ * }
+ *
+ * goog.inherits(ChildClass, ParentClass);
+ *
+ * var child = new ChildClass('a', 'b', 'see');
+ * child.foo(); // works
+ * </pre>
+ *
+ * In addition, a superclass' implementation of a method can be invoked
+ * as follows:
+ *
+ * <pre>
+ * ChildClass.prototype.foo = function(a) {
+ * ChildClass.superClass_.foo.call(this, a);
+ * // other code
+ * };
+ * </pre>
+ *
+ * @param {Function} childCtor Child class.
+ * @param {Function} parentCtor Parent class.
+ */
+goog.inherits = function(childCtor, parentCtor) {
+ /** @constructor */
+ function tempCtor() {};
+ tempCtor.prototype = parentCtor.prototype;
+ childCtor.superClass_ = parentCtor.prototype;
+ childCtor.prototype = new tempCtor();
+ childCtor.prototype.constructor = childCtor;
+};
+ </script>
+ </head>
+ <body>
+ <div>
+ Hello, mod_pagespeed!
+ </div>
+ </body>
+</html>
diff --git a/trunk/src/install/mod_pagespeed_example/remove_comments.html b/trunk/src/install/mod_pagespeed_example/remove_comments.html
new file mode 100644
index 0000000..3adc442
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/remove_comments.html
@@ -0,0 +1,10 @@
+<html>
+<body>
+<!-- This comment will be removed -->
+<div>Hello, world!</div>
+<!-- Apply IE-specific CSS -->
+<!-- [if IE ]>
+<link href="iecss.css" rel="stylesheet" type="text/css" title="This IE directive will be preserved">
+<![endif]-->
+</body>
+</html>
diff --git a/trunk/src/install/mod_pagespeed_example/remove_quotes.html b/trunk/src/install/mod_pagespeed_example/remove_quotes.html
new file mode 100644
index 0000000..ac781c7
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/remove_quotes.html
@@ -0,0 +1,7 @@
+<html>
+ <head>
+ </head>
+ <body>
+ <img src="images/BikeCrashIcn.png" align='right' alt="" border="2" width='70' height='70'>
+ </body>
+</html>
diff --git a/trunk/src/install/mod_pagespeed_example/rewrite_css.html b/trunk/src/install/mod_pagespeed_example/rewrite_css.html
new file mode 100644
index 0000000..815c8c6
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/rewrite_css.html
@@ -0,0 +1,14 @@
+<html><head><style type='text/css'>
+ #iddy, #anotherId {
+ /* This comment will be stripped.*/
+ border: solid 1px #cccccc;
+ padding: 1.2em;
+ float: left;
+ color:WindowText;
+ }
+ div.classy span, div.classy img {
+ display: block;
+ border: none !important;
+ background: none !important;
+ }
+</style></head><body>Yo, <div class="classy" id="iddy">this page is <span>stylin'</span>.</div></body></html>
diff --git a/trunk/src/install/mod_pagespeed_example/rewrite_images.html b/trunk/src/install/mod_pagespeed_example/rewrite_images.html
new file mode 100644
index 0000000..04aa4d6
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/rewrite_images.html
@@ -0,0 +1,10 @@
+<html>
+ <head>
+ <title>Rewrite Images example</title>
+ </head>
+ <body>
+ <img src="images/Puzzle.jpg" width="1023" height="766" title="A large image. mod_pagespeed will recompress it."/><br/>
+ <img src="images/Puzzle.jpg" width="256" height="192" title="The same image, but with smaller width and height attributes. mod_pagespeed will resize it."/><br/>
+ <img src="images/Cuppa.png" title="A small image. mod_pagespeed will inline it."/><br/>
+ </body>
+</html>
diff --git a/trunk/src/install/mod_pagespeed_example/rewrite_javascript.html b/trunk/src/install/mod_pagespeed_example/rewrite_javascript.html
new file mode 100644
index 0000000..4135181
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/rewrite_javascript.html
@@ -0,0 +1,44 @@
+<html>
+ <head>
+ </head>
+ <body>
+ <p>
+ Expected: Internal 0 External 1 External 2<br/>
+ Actual:
+ <script type="text/javascript" id="int1">
+ // This comment will be removed
+ var state = 0;
+ document.write("Internal " + state);
+ state = 1
+ </script>
+ <script src="rewrite_javascript.js" type="text/javascript" id="ext1">
+ </script>
+ <script src="rewrite_javascript.js" id="ext2">
+ /* Note that the contents and comments of a script block are preserved
+ if a src is specified. Since they are not executed, it is assumed the
+ code will be using the script block contents as data. */
+ document.write("Internal script; state = " + state); state = 42
+ </script>
+ </p><p>
+ External script url:
+ <script type="text/javascript">
+ /* This comment will also be removed */
+ document.write(document.getElementById("ext1").src);
+ </script>
+ </p><p>
+ Comment in internal script:
+ <script type="text/javascript">
+ // This comment will be removed too.
+ var source = document.getElementById("int1").innerHTML;
+ document.write(source.match("comment") ? "yes" : "no");
+ </script>
+ </p><p>
+ Comment in external script:
+ <script type="text/javascript">
+ /* Of course, this comment will be removed as well. */
+ var source = document.getElementById("ext2").innerHTML;
+ document.write(source.match("comment") ? "yes" : "no");
+ </script>
+ </p>
+ </body>
+</html>
diff --git a/trunk/src/install/mod_pagespeed_example/rewrite_javascript.js b/trunk/src/install/mod_pagespeed_example/rewrite_javascript.js
new file mode 100644
index 0000000..21e1f97
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/rewrite_javascript.js
@@ -0,0 +1,7 @@
+/*
+ * This external script is part of the rewrite_javascript example.
+ * This comment will be removed.
+ */
+// First inject the state into the document. This comment will also be removed.
+ document.write("External " + state);
+ state += 1; // Then update it.
diff --git a/trunk/src/install/mod_pagespeed_example/stress_test.html b/trunk/src/install/mod_pagespeed_example/stress_test.html
new file mode 100644
index 0000000..43ec720
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/stress_test.html
@@ -0,0 +1,20 @@
+<html>
+ <head>
+ <title>Stress Test</title>
+ </head>
+ <body>
+ This page includes a bunch of slow images for stress-testing mod_pagespeed.
+ <img src="cgi/slow.cgi?id=0"/><br/>
+ <img src="cgi/slow.cgi?id=1"/><br/>
+ <img src="cgi/slow.cgi?id=2"/><br/>
+ <img src="cgi/slow.cgi?id=3"/><br/>
+ <img src="cgi/slow.cgi?id=4"/><br/>
+ <img src="cgi/slow.cgi?id=5"/><br/>
+ <img src="cgi/slow.cgi?id=6"/><br/>
+ <img src="cgi/slow.cgi?id=7"/><br/>
+ <img src="cgi/slow.cgi?id=8"/><br/>
+ <img src="cgi/slow.cgi?id=9"/><br/>
+ <img src="cgi/slow.cgi?id=10"/><br/>
+ <img src="cgi/slow.cgi?id=11"/><br/>
+ </body>
+</html>
diff --git a/trunk/src/install/mod_pagespeed_example/styles/all_styles.css b/trunk/src/install/mod_pagespeed_example/styles/all_styles.css
new file mode 100644
index 0000000..a6214b8
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/styles/all_styles.css
@@ -0,0 +1,4 @@
+.yellow {background-color: yellow;}
+.blue {color: blue;}
+.big { font-size: 8em; }
+.bold { font-weight: bold; }
diff --git a/trunk/src/install/mod_pagespeed_example/styles/big.css b/trunk/src/install/mod_pagespeed_example/styles/big.css
new file mode 100644
index 0000000..d5f4055
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/styles/big.css
@@ -0,0 +1,48 @@
+.big {
+ font-size: 8em;
+}
+
+/*
+ * The class names below are just here to prevent the minifier and inlining from
+ * working on this file, to allow the css combiner to be demonstrated.
+ */
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_a0 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_a1 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_a0 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_a1 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_a0 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_a1 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_a6 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_a7 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_a8 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_a9 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_b0 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_b1 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_b2 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_b3 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_b4 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_b5 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_b6 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_b7 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_b8 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_b9 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_c0 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_c1 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_c2 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_c3 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_c4 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_c5 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_c6 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_c7 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_c8 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_c9 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_d0 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_d1 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_d2 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_d3 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_d4 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_d5 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_d6 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_d7 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_d8 { font-size: 8em; }
+.very_large_class_name_that_will_be_hard_to_minify_to_keep_file_above_threshold_d9 { font-size: 8em; }
diff --git a/trunk/src/install/mod_pagespeed_example/styles/blue.css b/trunk/src/install/mod_pagespeed_example/styles/blue.css
new file mode 100644
index 0000000..84d7d00
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/styles/blue.css
@@ -0,0 +1 @@
+.blue {color: blue;}
diff --git a/trunk/src/install/mod_pagespeed_example/styles/bold.css b/trunk/src/install/mod_pagespeed_example/styles/bold.css
new file mode 100644
index 0000000..81d40c0
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/styles/bold.css
@@ -0,0 +1,3 @@
+.bold {
+ font-weight: bold;
+}
diff --git a/trunk/src/install/mod_pagespeed_example/styles/yellow.css b/trunk/src/install/mod_pagespeed_example/styles/yellow.css
new file mode 100644
index 0000000..f8f5895
--- /dev/null
+++ b/trunk/src/install/mod_pagespeed_example/styles/yellow.css
@@ -0,0 +1 @@
+.yellow {background-color: yellow;}
diff --git a/trunk/src/install/proxy.conf.template b/trunk/src/install/proxy.conf.template
new file mode 100644
index 0000000..08342bd
--- /dev/null
+++ b/trunk/src/install/proxy.conf.template
@@ -0,0 +1,28 @@
+# Proxy configuration file to enable Instaweb to rewrite external
+# content. In this configuration we use assume a browser proxy,
+# pointing to localhost:80.
+
+LoadModule proxy_module APACHE_MODULES/mod_proxy.so
+# Depends: proxy
+LoadModule proxy_http_module APACHE_MODULES/mod_proxy_http.so
+
+<IfModule mod_proxy.c>
+ ProxyRequests On
+ ProxyVia On
+
+ # limit connections to LAN clients
+ <Proxy *>
+ AddDefaultCharset off
+ Order Deny,Allow
+ Allow from all
+ </Proxy>
+ ProxyPreserveHost On
+ ProxyStatus On
+ ProxyBadHeader Ignore
+
+ # Enable/disable the handling of HTTP/1.1 "Via:" headers.
+ # ("Full" adds the server version; "Block" removes all outgoing Via: headers)
+ # Set to one of: Off | On | Full | Block
+ ProxyVia On
+
+</IfModule>
diff --git a/trunk/src/install/rpm/build.sh b/trunk/src/install/rpm/build.sh
new file mode 100755
index 0000000..c98622d
--- /dev/null
+++ b/trunk/src/install/rpm/build.sh
@@ -0,0 +1,230 @@
+#!/bin/bash
+#
+# Copyright (c) 2009 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+set -e
+if [ "$VERBOSE" ]; then
+ set -x
+fi
+set -u
+
+gen_spec() {
+ rm -f "${SPEC}"
+ process_template "${SCRIPTDIR}/mod-pagespeed.spec.template" "${SPEC}"
+}
+
+# Setup the installation directory hierachy in the package staging area.
+prep_staging_rpm() {
+ prep_staging_common
+ install -m 755 -d "${STAGEDIR}/etc/cron.daily"
+}
+
+# Put the package contents in the staging area.
+stage_install_rpm() {
+ prep_staging_rpm
+ stage_install_common
+ echo "Staging RPM install files in '${STAGEDIR}'..."
+ process_template "${BUILDDIR}/install/common/rpmrepo.cron" \
+ "${STAGEDIR}/etc/cron.daily/${PACKAGE}"
+ chmod 755 "${STAGEDIR}/etc/cron.daily/${PACKAGE}"
+
+ # For CentOS, the load and conf files are combined into a single
+ # 'conf' file. So we install the load template as the conf file, and
+ # then concatenate the actual conf file.
+ process_template "${BUILDDIR}/install/common/pagespeed.load.template" \
+ "${STAGEDIR}${APACHE_CONFDIR}/pagespeed.conf"
+ process_template "${BUILDDIR}/install/common/pagespeed.conf.template" \
+ "${BUILDDIR}/install/common/pagespeed.conf"
+ cat "${BUILDDIR}/install/common/pagespeed.conf" >> \
+ "${STAGEDIR}${APACHE_CONFDIR}/pagespeed.conf"
+ chmod 644 "${STAGEDIR}${APACHE_CONFDIR}/pagespeed.conf"
+}
+
+# Actually generate the package file.
+do_package() {
+ echo "Packaging ${HOST_ARCH}..."
+ PROVIDES="${PACKAGE}"
+ local REPS="$REPLACES"
+ REPLACES=""
+ for rep in $REPS; do
+ if [ -z "$REPLACES" ]; then
+ REPLACES="$PACKAGE-$rep"
+ else
+ REPLACES="$REPLACES $PACKAGE-$rep"
+ fi
+ done
+
+ # If we specify a dependecy of foo.so below, we would depend on both the
+ # 32 and 64-bit versions on a 64-bit machine. The current version of RPM
+ # we use is too old and doesn't provide %{_isa}, so we do this manually.
+ if [ "$HOST_ARCH" = "x86_64" ] ; then
+ local EMPTY_VERSION="()"
+ local PKG_ARCH="(64bit)"
+ elif [ "$HOST_ARCH" = "i386" ] ; then
+ local EMPTY_VERSION=""
+ local PKG_ARCH=""
+ fi
+
+ DEPENDS="httpd >= 2.2, \
+ libstdc++ >= 4.1.2, \
+ at"
+ gen_spec
+
+ # Create temporary rpmbuild dirs.
+ RPMBUILD_DIR=$(mktemp -d -t rpmbuild.XXXXXX) || exit 1
+ mkdir -p "$RPMBUILD_DIR/BUILD"
+ mkdir -p "$RPMBUILD_DIR/RPMS"
+
+ rpmbuild --buildroot="$RPMBUILD_DIR/BUILD" -bb \
+ --target="$HOST_ARCH" --rmspec \
+ --define "_topdir $RPMBUILD_DIR" \
+ --define "_binary_payload w9.bzdio" \
+ "${SPEC}"
+ PKGNAME="${PACKAGE}-${CHANNEL}-${VERSION}-${REVISION}"
+ mv "$RPMBUILD_DIR/RPMS/$HOST_ARCH/${PKGNAME}.${HOST_ARCH}.rpm" "${OUTPUTDIR}"
+ # Make sure the package is world-readable, otherwise it causes problems when
+ # copied to share drive.
+ chmod a+r "${OUTPUTDIR}/${PKGNAME}.$HOST_ARCH.rpm"
+ rm -rf "$RPMBUILD_DIR"
+}
+
+# Remove temporary files and unwanted packaging output.
+cleanup() {
+ rm -rf "${STAGEDIR}"
+ rm -rf "${TMPFILEDIR}"
+}
+
+usage() {
+ echo "usage: $(basename $0) [-c channel] [-a target_arch] [-o 'dir'] [-b 'dir']"
+ echo "-c channel the package channel (unstable, beta, stable)"
+ echo "-a arch package architecture (ia32 or x64)"
+ echo "-o dir package output directory [${OUTPUTDIR}]"
+ echo "-b dir build input directory [${BUILDDIR}]"
+ echo "-h this help message"
+}
+
+# Check that the channel name is one of the allowable ones.
+verify_channel() {
+ case $CHANNEL in
+ stable )
+ CHANNEL=stable
+ REPLACES="unstable beta"
+ ;;
+ unstable|dev|alpha )
+ CHANNEL=unstable
+ REPLACES="stable beta"
+ ;;
+ testing|beta )
+ CHANNEL=beta
+ REPLACES="unstable stable"
+ ;;
+ * )
+ echo
+ echo "ERROR: '$CHANNEL' is not a valid channel type."
+ echo
+ exit 1
+ ;;
+ esac
+}
+
+process_opts() {
+ while getopts ":o:b:c:a:h" OPTNAME
+ do
+ case $OPTNAME in
+ o )
+ OUTPUTDIR="$OPTARG"
+ mkdir -p "${OUTPUTDIR}"
+ ;;
+ b )
+ BUILDDIR=$(readlink -f "${OPTARG}")
+ ;;
+ c )
+ CHANNEL="$OPTARG"
+ verify_channel
+ ;;
+ a )
+ TARGETARCH="$OPTARG"
+ ;;
+ h )
+ usage
+ exit 0
+ ;;
+ \: )
+ echo "'-$OPTARG' needs an argument."
+ usage
+ exit 1
+ ;;
+ * )
+ echo "invalid command-line option: $OPTARG"
+ usage
+ exit 1
+ ;;
+ esac
+ done
+}
+
+#=========
+# MAIN
+#=========
+
+SCRIPTDIR=$(readlink -f "$(dirname "$0")")
+OUTPUTDIR="${PWD}"
+STAGEDIR=$(mktemp -d -t rpm.build.XXXXXX) || exit 1
+TMPFILEDIR=$(mktemp -d -t rpm.tmp.XXXXXX) || exit 1
+CHANNEL="beta"
+# Default target architecture to same as build host.
+if [ "$(uname -m)" = "x86_64" ]; then
+ TARGETARCH="x64"
+else
+ TARGETARCH="ia32"
+fi
+SPEC="${TMPFILEDIR}/mod-pagespeed.spec"
+
+# call cleanup() on exit
+trap cleanup 0
+process_opts "$@"
+if [ ! "$BUILDDIR" ]; then
+ BUILDDIR=$(readlink -f "${SCRIPTDIR}/../../out/Release")
+fi
+
+source ${BUILDDIR}/install/common/installer.include
+
+get_version_info
+
+source "${BUILDDIR}/install/common/mod-pagespeed.info"
+eval $(sed -e "s/^\([^=]\+\)=\(.*\)$/export \1='\2'/" \
+ "${BUILDDIR}/install/common/BRANDING")
+
+REPOCONFIG="http://dl.google.com/linux/${PACKAGE#google-}/rpm/stable"
+verify_channel
+
+APACHE_CONFDIR="/etc/httpd/conf.d"
+MODPAGESPEED_CACHE_ROOT="/var/www/mod_pagespeed"
+APACHE_USER="apache"
+COMMENT_OUT_DEFLATE=
+
+# Make everything happen in the OUTPUTDIR.
+cd "${OUTPUTDIR}"
+
+case "$TARGETARCH" in
+ ia32 )
+ export APACHE_MODULEDIR="/usr/lib/httpd/modules"
+ export HOST_ARCH="i386"
+ stage_install_rpm
+ ;;
+ x64 )
+ export APACHE_MODULEDIR="/usr/lib64/httpd/modules"
+ export HOST_ARCH="x86_64"
+ stage_install_rpm
+ ;;
+ * )
+ echo
+ echo "ERROR: Don't know how to build RPMs for '$TARGETARCH'."
+ echo
+ exit 1
+ ;;
+esac
+
+do_package "$HOST_ARCH"
diff --git a/trunk/src/install/rpm/mod-pagespeed.spec.template b/trunk/src/install/rpm/mod-pagespeed.spec.template
new file mode 100644
index 0000000..f399f42
--- /dev/null
+++ b/trunk/src/install/rpm/mod-pagespeed.spec.template
@@ -0,0 +1,172 @@
+#------------------------------------------------------------------------------
+# mod-pagespeed.spec
+#------------------------------------------------------------------------------
+
+#------------------------------------------------------------------------------
+# Prologue information
+#------------------------------------------------------------------------------
+Summary : @@SHORTDESC@@
+License : Apache Software License
+Name : @@PACKAGE@@-@@CHANNEL@@
+Version : @@VERSION@@
+Release : @@REVISION@@
+Group : System Environment/Daemons
+Vendor : @@COMPANY_FULLNAME@@
+Url : @@PRODUCTURL@@
+Packager : @@MAINTNAME@@ <@@MAINTMAIL@@>
+
+#------------------------------------------------------------------------------
+# Tested on:
+# TODO
+#------------------------------------------------------------------------------
+
+Provides : @@PROVIDES@@ = %{version}
+Requires : @@DEPENDS@@
+Conflicts : @@REPLACES@@
+
+#------------------------------------------------------------------------------
+# Description
+#------------------------------------------------------------------------------
+%Description
+@@FULLDESC@@
+
+#------------------------------------------------------------------------------
+# Build rule - How to make the package
+#------------------------------------------------------------------------------
+%build
+
+#------------------------------------------------------------------------------
+# Installation rule - how to install it (note that it
+# gets installed into a temp directory given by $RPM_BUILD_ROOT)
+#------------------------------------------------------------------------------
+%install
+rm -rf "$RPM_BUILD_ROOT"
+
+if [ -z "@@STAGEDIR@@" -o ! -d "@@STAGEDIR@@" ] ; then
+ echo "@@STAGEDIR@@ appears to be incorrectly set - aborting"
+ exit 1
+fi
+
+install -m 755 -d \
+ "$RPM_BUILD_ROOT/etc" \
+ "$RPM_BUILD_ROOT/usr" \
+ "$RPM_BUILD_ROOT/var"
+# This is hard coded for now
+cp -a "@@STAGEDIR@@/etc/" "$RPM_BUILD_ROOT/"
+cp -a "@@STAGEDIR@@/usr/" "$RPM_BUILD_ROOT/"
+cp -a "@@STAGEDIR@@/var/" "$RPM_BUILD_ROOT/"
+
+#------------------------------------------------------------------------------
+# Rule to clean up a build
+#------------------------------------------------------------------------------
+%clean
+rm -rf "$RPM_BUILD_ROOT"
+
+#------------------------------------------------------------------------------
+# Files listing.
+#------------------------------------------------------------------------------
+%files
+%defattr(-,root,root)
+@@APACHE_MODULEDIR@@/mod_pagespeed.so
+%config(noreplace) @@APACHE_CONFDIR@@/pagespeed.conf
+/etc/cron.daily/mod-pagespeed
+%attr(-, @@APACHE_USER@@, @@APACHE_USER@@) @@MODPAGESPEED_CACHE_ROOT@@/cache
+%attr(-, @@APACHE_USER@@, @@APACHE_USER@@) @@MODPAGESPEED_CACHE_ROOT@@/files
+
+#------------------------------------------------------------------------------
+# Pre install script
+#------------------------------------------------------------------------------
+%pre
+
+exit 0
+
+
+
+
+#------------------------------------------------------------------------------
+# Post install script
+#------------------------------------------------------------------------------
+%post
+
+@@include@@../common/rpm.include
+
+MODPAGESPEED_ENABLE_UPDATES=@@MODPAGESPEED_ENABLE_UPDATES@@
+
+DEFAULTS_FILE="/etc/default/@@PACKAGE@@"
+if [ -n "${MODPAGESPEED_ENABLE_UPDATES}" -a ! -e "$DEFAULTS_FILE" ]; then
+ echo 'repo_add_once="true"' > "$DEFAULTS_FILE"
+fi
+
+if [ -e "$DEFAULTS_FILE" ]; then
+. "$DEFAULTS_FILE"
+
+if [ "$repo_add_once" = "true" ]; then
+ determine_rpm_package_manager
+
+ case $PACKAGEMANAGER in
+ "yum")
+ install_yum
+ ;;
+ "urpmi")
+ install_urpmi
+ ;;
+ "yast")
+ install_yast
+ ;;
+ esac
+fi
+
+# Some package managers have locks that prevent everything from being
+# configured at install time, so wait a bit then kick the cron job to do
+# whatever is left. Probably the db will be unlocked by then, but if not, the
+# cron job will keep retrying.
+# Do this with 'at' instead of a backgrounded shell because zypper waits on all
+# sub-shells to finish before it finishes, which is exactly the opposite of
+# what we want here. Also preemptively start atd because for some reason it's
+# not always running, which kind of defeats the purpose of having 'at' as a
+# required LSB command.
+service atd start
+echo "sh /etc/cron.daily/@@PACKAGE@@" | at now + 2 minute
+fi
+
+exit 0
+
+
+#------------------------------------------------------------------------------
+# Pre uninstallation script
+#------------------------------------------------------------------------------
+%preun
+
+if [ "$1" -eq "0" ]; then
+ mode="uninstall"
+elif [ "$1" -eq "1" ]; then
+ mode="upgrade"
+fi
+
+@@include@@../common/rpm.include
+
+# On Debian we only remove when we purge. However, RPM has no equivalent to
+# dpkg --purge, so this is all disabled.
+#
+#determine_rpm_package_manager
+#
+#case $PACKAGEMANAGER in
+#"yum")
+# remove_yum
+# ;;
+#"urpmi")
+# remove_urpmi
+# ;;
+#"yast")
+# remove_yast
+# ;;
+#esac
+
+exit 0
+
+#------------------------------------------------------------------------------
+# Post uninstallation script
+#------------------------------------------------------------------------------
+%postun
+
+exit 0
diff --git a/trunk/src/install/stress_test.sh b/trunk/src/install/stress_test.sh
new file mode 100755
index 0000000..4e11c2a
--- /dev/null
+++ b/trunk/src/install/stress_test.sh
@@ -0,0 +1,64 @@
+#!/bin/bash
+# Copyright 2010 Google Inc. All Rights Reserved.
+# Author: abliss@google.com (Adam Bliss)
+#
+# Usage: ./stress_test.sh HOSTPORT
+# Stress-tests a mod_pagespeed installation. This currently takes about 15sec.
+# Exits with status 0 if all tests pass. Exits 1 immediately if any test fails.
+# You should probably wipe out your cache and restart the server before starting
+# the test.
+
+if [ $# != 1 ]; then
+ echo Usage: ./stress_test.sh HOSTPORT;
+ exit 2;
+fi;
+HOSTPORT=$1
+
+TEST_DIR=/tmp/mod_pagespeed_stress_test.$USER;
+mkdir -p $TEST_DIR;
+cd $TEST_DIR;
+
+echo "Starting 10 simultaneous recursive wgets"
+X=0;
+PIDS="";
+while [ $X -lt 10 ]; do
+ wget -q -P $X -p http://$HOSTPORT/mod_pagespeed_example/stress_test.html &
+ PIDS="$PIDS $!";
+ X=$((X+1));
+done;
+
+# Monitor the number of processes for 10 seconds
+echo;
+echo;
+MAX=0;
+X=22;
+
+if [ $TERM == dumb ]; then
+ OVERWRITE=""
+else
+ OVERWRITE="\033[2A"
+fi
+
+while [ $X -ge 0 ]; do
+ NUM=` ps -efww|egrep 'bin/[h]ttpd|bin/[a]pache'|wc -l`
+ if [ $NUM -gt $MAX ]; then
+ MAX=$NUM;
+ fi;
+ /bin/echo -e "${OVERWRITE}Apache processes: $NUM ";
+ echo "Time remaining: $((X/2)) "
+ sleep 0.5;
+ X=$((X-1))
+done;
+
+echo "Test complete; killing wgets"
+kill $PIDS 2>/dev/null;
+
+if [ $MAX -gt 100 ]; then
+ echo "FAIL: $MAX processes were spawned.";
+ RETURN_VAL=1;
+else
+ echo "PASS."
+ RETURN_VAL=0;
+fi;
+
+exit $RETURN_VAL;
diff --git a/trunk/src/install/system_test.sh b/trunk/src/install/system_test.sh
new file mode 100755
index 0000000..71defea
--- /dev/null
+++ b/trunk/src/install/system_test.sh
@@ -0,0 +1,321 @@
+#!/bin/bash
+# Copyright 2010 Google Inc. All Rights Reserved.
+# Author: abliss@google.com (Adam Bliss)
+#
+# Usage: ./system_test.sh HOSTNAME
+# Tests a mod_pagespeed installation by fetching and verifying all the examples.
+# Exits with status 0 if all tests pass. Exits 1 immediately if any test fails.
+
+if [ $# != 1 ]; then
+ echo Usage: ./system_test.sh HOSTNAME
+ exit 2
+fi;
+
+# If the user has specified an alternate WGET as an environment variable, then
+# use that, otherwise use the one in the path.
+if [ "$WGET" == "" ]; then
+ WGET=wget
+else
+ echo WGET = $WGET
+fi
+
+$WGET --version | head -1 | grep 1.12 >/dev/null
+if [ $? != 0 ]; then
+ echo You have the wrong version of wget. 1.12 is required.
+ exit 1
+fi
+
+HOSTNAME=$1
+PORT=${HOSTNAME/*:/};
+if [ $PORT = $HOSTNAME ]; then
+ PORT=80
+fi;
+EXAMPLE_ROOT=http://$HOSTNAME/mod_pagespeed_example
+STATISTICS_URL=http://localhost:$PORT/mod_pagespeed_statistics
+BAD_RESOURCE_URL=http://$HOSTNAME/mod_pagespeed/bad.pagespeed.cf.hash.css
+
+OUTDIR=/tmp/mod_pagespeed_test.$USER/fetched_directory
+rm -rf $OUTDIR
+
+# Wget is used three different ways. The first way is nonrecursive and dumps a
+# single page (with headers) to standard out. This is useful for grepping for a
+# single expected string that's the result of a first-pass rewrite:
+# wget -q -O --save-headers - $URL | grep -q foo
+# "-q" quells wget's noisy output; "-O -" dumps to stdout; grep's -q quells
+# its output and uses the return value to indicate whether the string was
+# found. Note that exiting with a nonzero value will immediately kill
+# the make run.
+#
+# Sometimes we want to check for a condition that's not true on the first dump
+# of a page, but becomes true after a few seconds as the server's asynchronous
+# fetches complete. For this we use the the fetch_until() function:
+# fetch_until $URL 'grep -c delayed_foo' 1
+# In this case we will continuously fetch $URL and pipe the output to
+# grep -c (which prints the count of matches); we repeat until the number is 1.
+#
+# The final way we use wget is in a recursive mode to download all prerequisites
+# of a page. This fetches all resources associated with the page, and thereby
+# validates the resources generated by mod_pagespeed:
+# wget -H -p -S -o $WGET_OUTPUT -nd -P $OUTDIR $EXAMPLE_ROOT/$FILE
+# Here -H allows wget to cross hosts (e.g. in the case of a sharded domain); -p
+# means to fetch all prerequisites; "-S -o $WGET_OUTPUT" saves wget output
+# (including server headers) for later analysis; -nd puts all results in one
+# directory; -P specifies that directory. We can then run commands on
+# $OUTDIR/$FILE and nuke $OUTDIR when we're done.
+# TODO(abliss): some of these will fail on windows where wget escapes saved
+# filenames differently.
+
+WGET_OUTPUT=$OUTDIR/wget_output.txt
+WGET_DUMP="$WGET -q -O - --save-headers"
+WGET_PREREQ="$WGET -H -p -S -o $WGET_OUTPUT -nd -P $OUTDIR"
+
+# Call with a command and its args. Echos the command, then tries to eval it.
+# If it returns false, fail the tests.
+function check() {
+ echo " " $@
+ if eval "$@"; then
+ return;
+ else
+ echo FAIL.
+ exit 1;
+ fi;
+}
+
+# Continously fetches URL and pipes the output to COMMAND. Loops until
+# COMMAND outputs RESULT, in which case we return 0, or until 10 seconds have
+# passed, in which case we return 1.
+function fetch_until() {
+ URL=$1
+ COMMAND=$2
+ RESULT=$3
+
+ TIMEOUT=10
+ START=`date +%s`
+ STOP=$((START+$TIMEOUT))
+
+ echo " " Fetching $URL until '`'$COMMAND'`' = $RESULT
+ while test -t; do
+ if [ `$WGET -q -O - $URL 2>&1 | $COMMAND` = $RESULT ]; then
+ /bin/echo "."
+ return;
+ fi;
+ if [ `date +%s` -gt $STOP ]; then
+ /bin/echo "FAIL."
+ exit 1;
+ fi;
+ /bin/echo -n "."
+ sleep 0.1
+ done;
+}
+
+# Helper to set up most filter tests
+function test_filter() {
+ rm -rf $OUTDIR
+ mkdir -p $OUTDIR
+ FILTER_NAME=$1;
+ shift;
+ FILTER_DESCRIPTION=$@
+ echo TEST: $FILTER_NAME $FILTER_DESCRIPTION
+ FILE=$FILTER_NAME.html?ModPagespeedFilters=$FILTER_NAME
+ URL=$EXAMPLE_ROOT/$FILE
+ FETCHED=$OUTDIR/$FILE
+}
+
+
+# General system tests
+
+echo TEST: mod_pagespeed is running in Apache and writes the expected header.
+HTML_HEADERS=$($WGET_DUMP $EXAMPLE_ROOT/combine_css.html)
+
+echo Checking for X-Mod-Pagespeed header
+echo $HTML_HEADERS | grep -qi X-Mod-Pagespeed
+check [ $? = 0 ]
+
+echo Checking for lack of E-tag
+echo $HTML_HEADERS | grep -qi Etag
+check [ $? != 0 ]
+
+echo Checking for presence of Vary.
+echo $HTML_HEADERS | grep -qi 'Vary: Accept-Encoding'
+check [ $? = 0 ]
+
+echo Checking for absence of Last-Modified
+echo $HTML_HEADERS | grep -qi 'Last-Modified'
+check [ $? != 0 ]
+
+echo Checking for presence of Cache-control: no-cache
+echo $HTML_HEADERS | grep -qi 'Cache-Control: max-age=0, no-cache, no-store'
+check [ $? = 0 ]
+
+echo Checking for absense of Expires
+echo $HTML_HEADERS | grep -qi 'Expires'
+check [ $? != 0 ]
+
+# Determine whether statistics are enabled or not. If not, don't test them.
+grep "# ModPagespeedStatistics off" /usr/local/apache2/conf/pagespeed.conf \
+ >/dev/null
+if [ $? = 0 ]; then
+ echo TEST: 404s are served and properly recorded.
+ NUM_404=$($WGET_DUMP $STATISTICS_URL | grep resource_404_count | cut -d: -f2)
+ NUM_404=$(($NUM_404+1))
+ check "$WGET -O /dev/null $BAD_RESOURCE_URL 2>&1| grep -q '404 Not Found'"
+ check "$WGET_DUMP $STATISTICS_URL | grep -q 'resource_404_count: $NUM_404'"
+else
+ echo TEST: 404s are served. Statistics are disabled so not checking them.
+ check "$WGET -O /dev/null $BAD_RESOURCE_URL 2>&1| grep -q '404 Not Found'"
+fi
+
+echo TEST: directory is mapped to index.html.
+rm -rf $OUTDIR
+mkdir -p $OUTDIR
+check "$WGET_PREREQ $EXAMPLE_ROOT"
+check "$WGET_PREREQ $EXAMPLE_ROOT/index.html"
+check diff $OUTDIR/index.html $OUTDIR/mod_pagespeed_example
+
+echo TEST: compression is enabled for HTML.
+check "$WGET -O /dev/null -q -S --header='Accept-Encoding: gzip' \
+ $EXAMPLE_ROOT/ 2>&1 | grep -qi 'Content-Encoding: gzip'"
+
+# Individual filter tests, in alphabetical order
+
+test_filter add_instrumentation adds 2 script tags
+check $WGET_PREREQ $URL
+check [ `cat $FETCHED | sed 's/>/>\n/g' | grep -c '<script'` = 2 ]
+
+test_filter collapse_whitespace removes whitespace, but not from pre tags.
+check $WGET_PREREQ $URL
+check [ `egrep -c '^ +<' $FETCHED` = 1 ]
+
+test_filter combine_css combines 4 CSS files into 1.
+fetch_until $URL 'grep -c text/css' 1
+check $WGET_PREREQ $URL
+
+test_filter combine_heads combines 2 heads into 1
+check $WGET_PREREQ $URL
+check [ `grep -ce '<head>' $FETCHED` = 1 ]
+
+test_filter elide_attributes removes boolean and default attributes.
+check $WGET_PREREQ $URL
+grep "disabled=" $FETCHED # boolean, should not find
+check [ $? != 0 ]
+grep "type=" $FETCHED # default, should not find
+check [ $? != 0 ]
+
+test_filter extend_cache rewrites an image tag.
+fetch_until $URL 'grep -c src.*91_WewrLtP' 1
+check $WGET_PREREQ $URL
+
+echo TEST: Cache-extended image should respond 304 to an If-Modified-Since.
+URL=$EXAMPLE_ROOT/images/Puzzle.jpg.pagespeed.ce.91_WewrLtP.jpg
+DATE=`date -R`
+$WGET_PREREQ --header "If-Modified-Since: $DATE" $URL
+check grep '"304 Not Modified"' $WGET_OUTPUT
+
+echo TEST: Legacy format URLs should still work.
+URL=$EXAMPLE_ROOT/images/ce.0123456789abcdef0123456789abcdef.Puzzle,j.jpg
+check "$WGET_DUMP $URL | grep -q 'HTTP/1.1 200 OK'"
+
+test_filter move_css_to_head does what it says on the tin.
+check $WGET_PREREQ $URL
+check grep -q "'<head><link'" $FETCHED # link moved to head
+
+test_filter inline_css converts a link tag to a style tag
+fetch_until $URL 'grep -c style' 2
+
+test_filter inline_javascript inlines a small JS file
+fetch_until $URL 'grep -c document.write' 1
+
+test_filter outline_css outlines large styles, but not small ones.
+check $WGET_PREREQ $URL
+check egrep -q "'<link.*text/css.*large'" $FETCHED # outlined
+check egrep -q "'<style.*small'" $FETCHED # not outlined
+
+test_filter outline_javascript outlines large scripts, but not small ones.
+check $WGET_PREREQ $URL
+check egrep -q "'<script.*src=.*large'" $FETCHED # outlined
+check egrep -q "'<script.*small.*var hello'" $FETCHED # not outlined
+
+echo TEST: compression is enabled for rewritten JS.
+JS_URL=$(egrep -o http://.*.pagespeed.*.js $FETCHED)
+JS_HEADERS=$($WGET -O /dev/null -q -S --header='Accept-Encoding: gzip' \
+ $JS_URL 2>&1)
+echo $JS_HEADERS | grep -qi 'Content-Encoding: gzip'
+check [ $? = 0 ]
+echo $JS_HEADERS | grep -qi 'Vary: Accept-Encoding'
+check [ $? = 0 ]
+echo $JS_HEADERS | grep -qi 'Etag: W/0'
+check [ $? = 0 ]
+echo $JS_HEADERS | grep -qi 'Last-Modified:'
+check [ $? = 0 ]
+
+test_filter remove_comments removes comments but not IE directives.
+check $WGET_PREREQ $URL
+grep "removed" $FETCHED # comment, should not find
+check [ $? != 0 ]
+check grep -q preserved $FETCHED # preserves IE directives
+
+test_filter remove_quotes does what it says on the tin.
+check $WGET_PREREQ $URL
+check [ `sed 's/ /\n/g' $FETCHED | grep -c '"' ` = 2 ] # 2 quoted attrs
+check [ `grep -c "'" $FETCHED` = 0 ] # no apostrophes
+
+test_filter rewrite_css removes comments and saves a bunch of bytes.
+check $WGET_PREREQ $URL
+grep "comment" $FETCHED # comment, should not find
+check [ $? != 0 ]
+check [ `stat -c %s $FETCHED` -lt 315 ] # down from 472
+
+test_filter rewrite_images inlines, compresses, and resizes.
+fetch_until $URL 'grep -c image/png' 1 # inlined
+check $WGET_PREREQ $URL
+check [ `stat -c %s $OUTDIR/*1023x766*Puzzle*` -lt 241260 ] # compressed
+check [ `stat -c %s $OUTDIR/*256x192*Puzzle*` -lt 24126 ] # resized
+
+IMG_URL=$(egrep -o http://.*.pagespeed.*.jpg $FETCHED | head -n1)
+echo TEST: headers for rewrritten image "$IMG_URL"
+IMG_HEADERS=$($WGET -O /dev/null -q -S --header='Accept-Encoding: gzip' \
+ $IMG_URL 2>&1)
+# Make sure we have some valid headers.
+echo \"$IMG_HEADERS\" | grep -qi 'Content-Type: image/jpeg'
+check [ $? = 0 ]
+
+# Make sure the response was not gzipped.
+echo TEST: Images are not gzipped
+echo "$IMG_HEADERS" | grep -qi 'Content-Encoding: gzip'
+check [ $? != 0 ]
+
+# Make sure there is no vary-encoding
+echo TEST: Vary is not set for images
+echo "$IMG_HEADERS" | grep -qi 'Vary: Accept-Encoding'
+check [ $? != 0 ]
+
+# Make sure there is an etag
+echo TEST: Etags is present
+echo "$IMG_HEADERS" | grep -qi 'Etag: W/0'
+check [ $? = 0 ]
+
+# Make sure there is a last-modified tag
+echo TEST: Last-modified is present
+echo "$IMG_HEADERS" | grep -qi 'Last-Modified'
+check [ $? = 0 ]
+
+IMG_URL=${IMG_URL/Puzzle/BadName}
+echo TEST: rewrite_images redirects unknown image $IMG_URL
+$WGET_PREREQ $IMG_URL; # fails
+check grep '"307 Temporary Redirect"' $WGET_OUTPUT
+
+
+test_filter rewrite_javascript removes comments and saves a bunch of bytes.
+fetch_until $URL 'grep -c src.*1o978_K0_L' 2 # external scripts rewritten
+check $WGET_PREREQ $URL
+grep -R "removed" $OUTDIR # comments, should not find any
+check [ $? != 0 ]
+check [ `stat -c %s $FETCHED` -lt 1560 ] # net savings
+check grep -q preserved $FETCHED # preserves certain comments
+# rewritten JS is cache-extended
+check grep -qi "'Cache-control: max-age=31536000'" $WGET_OUTPUT
+check grep -qi "'Expires:'" $WGET_OUTPUT
+
+# Cleanup
+rm -rf $OUTDIR
+echo "PASS."
diff --git a/trunk/src/install/ubuntu.sh b/trunk/src/install/ubuntu.sh
new file mode 100755
index 0000000..747a0c1
--- /dev/null
+++ b/trunk/src/install/ubuntu.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+exec make \
+ APACHE_ROOT=/etc/apache2 \
+ APACHE_MODULES=/usr/lib/apache2/modules \
+ $*
diff --git a/trunk/src/net/instaweb/apache/apache_config.cc b/trunk/src/net/instaweb/apache/apache_config.cc
new file mode 100644
index 0000000..5a29e51
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/apache_config.cc
@@ -0,0 +1,25 @@
+// 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/apache/apache_config.h"
+
+namespace net_instaweb {
+
+ApacheConfig::ApacheConfig(const StringPiece& description)
+ : description_(description.data(), description.size()) {
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/apache/apache_config.h b/trunk/src/net/instaweb/apache/apache_config.h
new file mode 100644
index 0000000..8edb9a8
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/apache_config.h
@@ -0,0 +1,54 @@
+// 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_APACHE_APACHE_CONFIG_H_
+#define NET_INSTAWEB_APACHE_APACHE_CONFIG_H_
+
+#include "net/instaweb/rewriter/public/rewrite_options.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+// The httpd header must be after the
+// apache_rewrite_driver_factory.h. Otherwise, the compiler will
+// complain "strtoul_is_not_a_portable_function_use_strtol_instead".
+#include "httpd.h"
+
+namespace net_instaweb {
+
+// Establishes a context for directory-scoped options, either via .htaccess or
+// <Directory>...</Directory>.
+class ApacheConfig {
+ public:
+ explicit ApacheConfig(const StringPiece& dir);
+ ~ApacheConfig() {}
+
+ // Human-readable description of what this configuration is for. This
+ // may be a directory, or a string indicating a combination of directives
+ // for multiple directories.
+ StringPiece description() const { return description_; }
+
+ RewriteOptions* options() { return &options_; }
+
+ private:
+ std::string description_;
+ RewriteOptions options_;
+
+ DISALLOW_COPY_AND_ASSIGN(ApacheConfig);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_APACHE_APACHE_CONFIG_H_
diff --git a/trunk/src/net/instaweb/apache/apache_message_handler.cc b/trunk/src/net/instaweb/apache/apache_message_handler.cc
new file mode 100644
index 0000000..a7c81b7
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/apache_message_handler.cc
@@ -0,0 +1,98 @@
+// 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/apache/apache_message_handler.h"
+
+#include "net/instaweb/apache/log_message_handler.h"
+
+#include "httpd.h"
+// When HAVE_SYSLOG is defined, apache http_log.h will include syslog.h, which
+// #defined LOG_* as numbers. This conflicts with what we are using those here.
+#undef HAVE_SYSLOG
+#include "http_log.h"
+
+namespace {
+
+// This name will be prefixed to every logged message. This could be made
+// smaller if people think it's too long. In my opinion it's probably OK,
+// and it would be good to let people know where messages are coming from.
+const char kModuleName[] = "mod_pagespeed";
+
+}
+
+namespace net_instaweb {
+
+ApacheMessageHandler::ApacheMessageHandler(const server_rec* server,
+ const StringPiece& version)
+ : server_rec_(server),
+ version_(version.data(), version.size()) {
+ // Tell log_message_handler about this server_rec and version.
+ log_message_handler::AddServerConfig(server_rec_, version);
+
+ // TODO(jmarantz): consider making this a little terser by default.
+ // The string we expect in is something like "0.9.1.1-171" and we will
+ // may be able to pick off some of the 5 fields that prove to be boring.
+}
+
+int ApacheMessageHandler::GetApacheLogLevel(MessageType type) {
+ switch (type) {
+ case kInfo:
+ // TODO(sligocki): Do we want this to be INFO or NOTICE.
+ return APLOG_INFO;
+ case kWarning:
+ return APLOG_WARNING;
+ case kError:
+ return APLOG_ERR;
+ case kFatal:
+ return APLOG_ALERT;
+ }
+
+ // This should never fall through, but some compilers seem to complain if
+ // we don't include this.
+ return APLOG_ALERT;
+}
+
+void ApacheMessageHandler::MessageVImpl(MessageType type, const char* msg,
+ va_list args) {
+ int log_level = GetApacheLogLevel(type);
+ std::string formatted_message = Format(msg, args);
+ ap_log_error(APLOG_MARK, log_level, APR_SUCCESS, server_rec_,
+ "[%s %s] %s",
+ kModuleName, version_.c_str(), formatted_message.c_str());
+}
+
+void ApacheMessageHandler::FileMessageVImpl(MessageType type, const char* file,
+ int line, const char* msg,
+ va_list args) {
+ int log_level = GetApacheLogLevel(type);
+ std::string formatted_message = Format(msg, args);
+ ap_log_error(APLOG_MARK, log_level, APR_SUCCESS, server_rec_,
+ "[%s %s] %s:%d: %s",
+ kModuleName, version_.c_str(), file, line,
+ formatted_message.c_str());
+}
+
+// TODO(sligocki): It'd be nice not to do so much string copying.
+std::string ApacheMessageHandler::Format(const char* msg, va_list args) {
+ std::string buffer;
+
+ // Ignore the name of this routine: it formats with vsnprintf.
+ // See base/stringprintf.cc.
+ StringAppendV(&buffer, msg, args);
+ return buffer;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/apache/apache_message_handler.h b/trunk/src/net/instaweb/apache/apache_message_handler.h
new file mode 100644
index 0000000..669070d
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/apache_message_handler.h
@@ -0,0 +1,54 @@
+// 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)
+
+#ifndef NET_INSTAWEB_APACHE_APACHE_MESSAGE_HANDLER_H_
+#define NET_INSTAWEB_APACHE_APACHE_MESSAGE_HANDLER_H_
+
+#include <string>
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+struct server_rec;
+
+namespace net_instaweb {
+
+// Implementation of an HTML parser message handler that uses Apache
+// logging to emit messsages.
+class ApacheMessageHandler : public MessageHandler {
+ public:
+ ApacheMessageHandler(const server_rec* server, const StringPiece& version);
+
+ protected:
+ virtual void MessageVImpl(MessageType type, const char* msg, va_list args);
+
+ virtual void FileMessageVImpl(MessageType type, const char* filename,
+ int line, const char* msg, va_list args);
+
+ private:
+ int GetApacheLogLevel(MessageType type);
+ std::string Format(const char* msg, va_list args);
+
+ const server_rec* server_rec_;
+ const std::string version_;
+
+ DISALLOW_COPY_AND_ASSIGN(ApacheMessageHandler);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_APACHE_APACHE_MESSAGE_HANDLER_H_
diff --git a/trunk/src/net/instaweb/apache/apache_rewrite_driver_factory.cc b/trunk/src/net/instaweb/apache/apache_rewrite_driver_factory.cc
new file mode 100644
index 0000000..4f7e898
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/apache_rewrite_driver_factory.cc
@@ -0,0 +1,162 @@
+// 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.
+
+#include "net/instaweb/apache/apache_rewrite_driver_factory.h"
+
+#include "apr_pools.h"
+#include "net/instaweb/apache/apache_message_handler.h"
+#include "net/instaweb/apache/apr_file_system.h"
+#include "net/instaweb/apache/apr_mutex.h"
+#include "net/instaweb/apache/apr_statistics.h"
+#include "net/instaweb/apache/apr_timer.h"
+#include "net/instaweb/apache/serf_url_async_fetcher.h"
+#include "net/instaweb/apache/serf_url_fetcher.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/rewriter/public/rewrite_driver.h"
+#include "net/instaweb/rewriter/public/rewrite_options.h"
+#include "net/instaweb/util/public/file_cache.h"
+#include "net/instaweb/util/public/lru_cache.h"
+#include "net/instaweb/util/public/md5_hasher.h"
+#include "net/instaweb/util/public/threadsafe_cache.h"
+#include "net/instaweb/util/public/write_through_cache.h"
+
+namespace net_instaweb {
+
+ApacheRewriteDriverFactory::ApacheRewriteDriverFactory(
+ server_rec* server, const StringPiece& version)
+ : server_rec_(server),
+ serf_url_fetcher_(NULL),
+ serf_url_async_fetcher_(NULL),
+ statistics_(NULL),
+ lru_cache_kb_per_process_(0),
+ lru_cache_byte_limit_(0),
+ file_cache_clean_interval_ms_(Timer::kHourMs),
+ file_cache_clean_size_kb_(100 * 1024), // 100 megabytes
+ fetcher_time_out_ms_(5 * Timer::kSecondMs),
+ slurp_flush_limit_(0),
+ version_(version.data(), version.size()),
+ statistics_enabled_(true) {
+ apr_pool_create(&pool_, NULL);
+ cache_mutex_.reset(NewMutex());
+ rewrite_drivers_mutex_.reset(NewMutex());
+
+ // In Apache, we default to using the "core filters".
+ options()->SetDefaultRewriteLevel(RewriteOptions::kCoreFilters);
+}
+
+ApacheRewriteDriverFactory::~ApacheRewriteDriverFactory() {
+ // We free all the resources before destroying the pool, because some of the
+ // resource uses the sub-pool and will need that pool to be around to
+ // clean up properly.
+ ShutDown();
+
+ apr_pool_destroy(pool_);
+}
+
+FileSystem* ApacheRewriteDriverFactory::DefaultFileSystem() {
+ return new AprFileSystem(pool_);
+}
+
+Hasher* ApacheRewriteDriverFactory::NewHasher() {
+ return new MD5Hasher();
+}
+
+Timer* ApacheRewriteDriverFactory::DefaultTimer() {
+ return new AprTimer();
+}
+
+MessageHandler* ApacheRewriteDriverFactory::DefaultHtmlParseMessageHandler() {
+ return new ApacheMessageHandler(server_rec_, version_);
+}
+
+MessageHandler* ApacheRewriteDriverFactory::DefaultMessageHandler() {
+ return new ApacheMessageHandler(server_rec_, version_);
+}
+
+CacheInterface* ApacheRewriteDriverFactory::DefaultCacheInterface() {
+ FileCache::CachePolicy* policy = new FileCache::CachePolicy(
+ timer(), file_cache_clean_interval_ms_, file_cache_clean_size_kb_);
+ CacheInterface* cache = new FileCache(
+ file_cache_path_, file_system(), filename_encoder(), policy,
+ message_handler());
+ if (lru_cache_kb_per_process_ != 0) {
+ LRUCache* lru_cache = new LRUCache(lru_cache_kb_per_process_ * 1024);
+
+ // We only add the threadsafe-wrapper to the LRUCache. The FileCache
+ // is naturally thread-safe because it's got no writable member variables.
+ // And surrounding that slower-running class with a mutex would likely
+ // cause contention.
+ ThreadsafeCache* ts_cache = new ThreadsafeCache(lru_cache, cache_mutex());
+ WriteThroughCache* write_through_cache =
+ new WriteThroughCache(ts_cache, cache);
+ // By default, WriteThroughCache does not limit the size of entries going
+ // into its front cache.
+ if (lru_cache_byte_limit_ != 0) {
+ write_through_cache->set_cache1_limit(lru_cache_byte_limit_);
+ }
+ cache = write_through_cache;
+ }
+ return cache;
+}
+
+UrlFetcher* ApacheRewriteDriverFactory::DefaultUrlFetcher() {
+ if (serf_url_fetcher_ == NULL) {
+ DefaultAsyncUrlFetcher(); // Create async fetcher if necessary.
+ serf_url_fetcher_ = new SerfUrlFetcher(
+ fetcher_time_out_ms_, serf_url_async_fetcher_);
+ }
+ return serf_url_fetcher_;
+}
+
+UrlAsyncFetcher* ApacheRewriteDriverFactory::DefaultAsyncUrlFetcher() {
+ if (serf_url_async_fetcher_ == NULL) {
+ serf_url_async_fetcher_ = new SerfUrlAsyncFetcher(
+ fetcher_proxy_.c_str(), pool_, statistics_, timer(),
+ fetcher_time_out_ms_);
+ }
+ return serf_url_async_fetcher_;
+}
+
+
+HtmlParse* ApacheRewriteDriverFactory::DefaultHtmlParse() {
+ return new HtmlParse(html_parse_message_handler());
+}
+
+AbstractMutex* ApacheRewriteDriverFactory::NewMutex() {
+ return new AprMutex(pool_);
+}
+
+ResourceManager* ApacheRewriteDriverFactory::ComputeResourceManager() {
+ ResourceManager* resource_manager =
+ RewriteDriverFactory::ComputeResourceManager();
+ resource_manager->set_statistics(statistics_);
+ http_cache()->SetStatistics(statistics_);
+ return resource_manager;
+}
+
+void ApacheRewriteDriverFactory::ShutDown() {
+ if (serf_url_async_fetcher_ != NULL) {
+ serf_url_async_fetcher_->WaitForInProgressFetches(
+ fetcher_time_out_ms_, message_handler(),
+ SerfUrlAsyncFetcher::kThreadedAndMainline);
+ }
+ cache_mutex_.reset(NULL);
+ rewrite_drivers_mutex_.reset(NULL);
+ RewriteDriverFactory::ShutDown();
+}
+
+void ApacheRewriteDriverFactory::Terminate() {
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/apache/apache_rewrite_driver_factory.h b/trunk/src/net/instaweb/apache/apache_rewrite_driver_factory.h
new file mode 100644
index 0000000..fbf8512
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/apache_rewrite_driver_factory.h
@@ -0,0 +1,132 @@
+// 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.
+
+#ifndef NET_INSTAWEB_APACHE_APACHE_REWRITE_DRIVER_FACTORY_H_
+#define NET_INSTAWEB_APACHE_APACHE_REWRITE_DRIVER_FACTORY_H_
+
+#include <stdio.h>
+#include <set>
+#include <string>
+#include <vector>
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/rewriter/public/rewrite_driver_factory.h"
+
+struct apr_pool_t;
+struct server_rec;
+
+namespace net_instaweb {
+
+class AprStatistics;
+class SerfUrlAsyncFetcher;
+class SerfUrlFetcher;
+
+// Creates an Apache RewriteDriver.
+class ApacheRewriteDriverFactory : public RewriteDriverFactory {
+ public:
+ explicit ApacheRewriteDriverFactory(server_rec* server,
+ const StringPiece& version);
+ virtual ~ApacheRewriteDriverFactory();
+
+ virtual Hasher* NewHasher();
+ virtual AbstractMutex* NewMutex();
+
+ SerfUrlAsyncFetcher* serf_url_async_fetcher() {
+ return serf_url_async_fetcher_;
+ }
+
+ void set_lru_cache_kb_per_process(int64 x) { lru_cache_kb_per_process_ = x; }
+ void set_lru_cache_byte_limit(int64 x) { lru_cache_byte_limit_ = x; }
+ void set_slurp_flush_limit(int64 x) { slurp_flush_limit_ = x; }
+ int slurp_flush_limit() const { return slurp_flush_limit_; }
+ void set_file_cache_clean_interval_ms(int64 x) {
+ file_cache_clean_interval_ms_ = x;
+ }
+ void set_file_cache_clean_size_kb(int64 x) { file_cache_clean_size_kb_ = x; }
+ void set_fetcher_time_out_ms(int64 x) { fetcher_time_out_ms_ = x; }
+ void set_file_cache_path(const StringPiece& x) {
+ x.CopyToString(&file_cache_path_);
+ }
+ void set_fetcher_proxy(const StringPiece& x) {
+ x.CopyToString(&fetcher_proxy_);
+ }
+
+ StringPiece file_cache_path() { return file_cache_path_; }
+ int64 file_cache_clean_size_kb() { return file_cache_clean_size_kb_; }
+ int64 fetcher_time_out_ms() { return fetcher_time_out_ms_; }
+ AprStatistics* statistics() { return statistics_; }
+ void set_statistics(AprStatistics* x) { statistics_ = x; }
+ void set_statistics_enabled(bool x) { statistics_enabled_ = x; }
+ bool statistics_enabled() const { return statistics_enabled_; }
+
+ // Relinquish all static data
+ static void Terminate();
+
+ protected:
+ virtual UrlFetcher* DefaultUrlFetcher();
+ virtual UrlAsyncFetcher* DefaultAsyncUrlFetcher();
+
+ // Provide defaults.
+ virtual MessageHandler* DefaultHtmlParseMessageHandler();
+ virtual MessageHandler* DefaultMessageHandler();
+ virtual FileSystem* DefaultFileSystem();
+ virtual HtmlParse* DefaultHtmlParse();
+ virtual Timer* DefaultTimer();
+ virtual CacheInterface* DefaultCacheInterface();
+ virtual AbstractMutex* cache_mutex() { return cache_mutex_.get(); }
+ virtual AbstractMutex* rewrite_drivers_mutex() {
+ return rewrite_drivers_mutex_.get(); }
+
+ // Disable the Resource Manager's filesystem since we have a
+ // write-through http_cache.
+ virtual bool ShouldWriteResourcesToFileSystem() { return false; }
+
+ // When computing the resource manager for Apache, be sure to set up
+ // the statistics.
+ virtual ResourceManager* ComputeResourceManager();
+
+ // Release all the resources. It also calls the base class ShutDown to release
+ // the base class resources.
+ void ShutDown();
+
+ private:
+ apr_pool_t* pool_;
+ server_rec* server_rec_;
+ scoped_ptr<AbstractMutex> cache_mutex_;
+ scoped_ptr<AbstractMutex> rewrite_drivers_mutex_;
+ SerfUrlFetcher* serf_url_fetcher_;
+ SerfUrlAsyncFetcher* serf_url_async_fetcher_;
+ AprStatistics* statistics_;
+
+ // TODO(jmarantz): These options could be consolidated in a protobuf or
+ // some other struct, which would keep them distinct from the rest of the
+ // state. Note also that some of the options are in the base class,
+ // RewriteDriverFactory, so we'd have to sort out how that worked.
+ int64 lru_cache_kb_per_process_;
+ int64 lru_cache_byte_limit_;
+ int64 file_cache_clean_interval_ms_;
+ int64 file_cache_clean_size_kb_;
+ int64 fetcher_time_out_ms_;
+ int64 slurp_flush_limit_;
+ std::string file_cache_path_;
+ std::string fetcher_proxy_;
+ std::string version_;
+ bool statistics_enabled_;
+
+ DISALLOW_COPY_AND_ASSIGN(ApacheRewriteDriverFactory);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_APACHE_APACHE_REWRITE_DRIVER_FACTORY_H_
diff --git a/trunk/src/net/instaweb/apache/apache_slurp.cc b/trunk/src/net/instaweb/apache/apache_slurp.cc
new file mode 100644
index 0000000..92c3b62
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/apache_slurp.cc
@@ -0,0 +1,268 @@
+// 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: jmarantz@google.com (Joshua Marantz)
+
+// Must precede any Apache includes, for now, due a conflict in
+// the use of 'OK" as an Instaweb enum and as an Apache #define.
+#include "base/string_util.h"
+#include "net/instaweb/apache/header_util.h"
+
+// TODO(jmarantz): serf_url_async_fetcher evidently sets
+// 'gzip' unconditionally, and the response includes the 'gzip'
+// encoding header, but serf unzips the response itself.
+//
+// I think the correct behavior is that our async fetcher should
+// transmit the 'gzip' request header if it was specified in the call
+// to StreamingFetch. This would be easy to fix.
+//
+// Unfortunately, serf 0.31 appears to unzip the content for us, which
+// is not what we are asking for. And it leaves the 'gzip' response
+// header in despite having unzipped it itself. I have tried later
+// versions of serf, but the API is not compatible (easy to deal with)
+// but the resulting binary has unresolved symbols. I am wondering
+// whether we will have to go to libcurl.
+//
+// For now use wget when slurping additional files.
+
+#include "net/instaweb/apache/apache_rewrite_driver_factory.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/query_params.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include <string>
+#include "net/instaweb/util/public/string_writer.h"
+#include "net/instaweb/util/public/url_fetcher.h"
+
+// The Apache headers must be after instaweb headers. Otherwise, the
+// compiler will complain
+// "strtoul_is_not_a_portable_function_use_strtol_instead".
+#include "apr_strings.h" // for apr_pstrdup
+#include "http_protocol.h"
+
+namespace net_instaweb {
+
+namespace {
+
+// Default handler when the file is not found
+void SlurpDefaultHandler(request_rec* r) {
+ ap_set_content_type(r, "text/html; charset=utf-8");
+ std::string buf = StringPrintf(
+ "<html><head><title>Slurp Error</title></head>"
+ "<body><h1>Slurp failed</h1>\n"
+ "<p>host=%s\n"
+ "<p>uri=%s\n"
+ "</body></html>",
+ r->hostname, r->unparsed_uri);
+ ap_rputs(buf.c_str(), r);
+ r->status = HttpStatus::kNotFound;
+ r->status_line = "Not Found";
+}
+
+// TODO(jmarantz): The ApacheWriter defined below is much more
+// efficient than the mechanism we are currently using, which is to
+// buffer the entire response in a string and then send it later.
+// For some reason, this did not work when I tried it, but it's
+// worth another look.
+
+class ApacheWriter : public Writer {
+ public:
+ ApacheWriter(request_rec* r, SimpleMetaData* response_headers,
+ int64 flush_limit)
+ : request_(r),
+ response_headers_(response_headers),
+ size_(0),
+ flush_limit_(flush_limit),
+ headers_out_(false) {
+ }
+
+ virtual bool Write(const StringPiece& str, MessageHandler* handler) {
+ if (!headers_out_) {
+ OutputHeaders();
+ }
+ ap_rwrite(str.data(), str.size(), request_);
+ size_ += str.size();
+ if ((flush_limit_ != 0) && (size_ > flush_limit_)) {
+ Flush(handler);
+ }
+ return true;
+ }
+
+ virtual bool Flush(MessageHandler* handler) {
+ ap_rflush(request_);
+ size_ = 0;
+ return true;
+ }
+
+ int64 size() const { return size_; }
+
+ void OutputHeaders() {
+ if (headers_out_) {
+ return;
+ }
+ headers_out_ = true;
+
+ // Apache2 defaults to set the status line as HTTP/1.1. If the
+ // original content was HTTP/1.0, we need to force the server to use
+ // HTTP/1.0. I'm not sure why/whether we need to do this; it was in
+ // mod_static from the sdpy project, which is where I copied this
+ // code from.
+ if ((response_headers_->major_version() == 1) &&
+ (response_headers_->minor_version() == 0)) {
+ apr_table_set(request_->subprocess_env, "force-response-1.0", "1");
+ }
+
+ char* content_type = NULL;
+ CharStarVector v;
+ CHECK(response_headers_->headers_complete());
+ if (response_headers_->Lookup(HttpAttributes::kContentType, &v)) {
+ CHECK(!v.empty());
+ // ap_set_content_type does not make a copy of the string, we need
+ // to duplicate it. Note that we will update the content type below,
+ // after transforming the headers.
+ content_type = apr_pstrdup(request_->pool, v[v.size() - 1]);
+ }
+ response_headers_->RemoveAll(HttpAttributes::kTransferEncoding);
+ response_headers_->RemoveAll(HttpAttributes::kContentLength);
+ MetaDataToApacheHeader(*response_headers_, request_->headers_out,
+ &request_->status, &request_->proto_num);
+ if (content_type != NULL) {
+ ap_set_content_type(request_, content_type);
+ }
+
+ // Recompute the content-length, because the content is decoded.
+ // TODO(lsong): We don't know the content size, do we?
+ // ap_set_content_length(request_, contents.size());
+ }
+
+ private:
+ request_rec* request_;
+ SimpleMetaData* response_headers_;
+ int size_;
+ int flush_limit_;
+ bool headers_out_;
+
+ DISALLOW_COPY_AND_ASSIGN(ApacheWriter);
+};
+
+} // namespace
+
+// Remove any mod-pagespeed-specific modifiers before we go to our slurped
+// fetcher.
+//
+// TODO(jmarantz): share the string constants from mod_instaweb.cc and
+// formalize the prefix-matching assumed here.
+std::string RemoveModPageSpeedQueryParams(
+ const std::string& uri, const char* query_param_string) {
+ QueryParams query_params, stripped_query_params;
+ query_params.Parse(query_param_string);
+ bool rewrite_query_params = false;
+
+ for (int i = 0; i < query_params.size(); ++i) {
+ const char* name = query_params.name(i);
+ static const char kModPagespeed[] = "ModPagespeed";
+ if (strncmp(name, kModPagespeed, sizeof(kModPagespeed) - 1) == 0) {
+ rewrite_query_params = true;
+ } else {
+ stripped_query_params.Add(name, query_params.value(i));
+ }
+ }
+
+ std::string stripped_url;
+ if (rewrite_query_params) {
+ // TODO(jmarantz): It would be nice to use GoogleUrl to do this but
+ // it's not clear how it would help. Instead just hack the string.
+ std::string::size_type question_mark = uri.find('?');
+ CHECK(question_mark != std::string::npos);
+ stripped_url.append(uri.data(), question_mark); // does not include "?" yet
+ if (stripped_query_params.size() != 0) {
+ stripped_url += StrCat("?", stripped_query_params.ToString());
+ }
+ } else {
+ stripped_url = uri;
+ }
+ return stripped_url;
+}
+
+// Some of the sites we are trying to slurp have mod-pagespeed enabled already.
+// We actually want to start with the non-pagespeed-enabled site. But we'd
+// rather not send ModPagespeed=off to servers that are not expecting it.
+class ModPagespeedStrippingFetcher : public UrlFetcher {
+ public:
+ ModPagespeedStrippingFetcher(UrlFetcher* fetcher) : fetcher_(fetcher) {}
+ virtual bool StreamingFetchUrl(const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* fetched_content_writer,
+ MessageHandler* message_handler) {
+ std::string contents;
+ StringWriter writer(&contents);
+ bool fetched = fetcher_->StreamingFetchUrl(
+ url, request_headers, response_headers, &writer, message_handler);
+ if (fetched) {
+ CharStarVector v;
+ if (response_headers->Lookup("X-Mod-Pagespeed", &v)) {
+ response_headers->Clear();
+ std::string::size_type question = url.find('?');
+ std::string url_pagespeed_off(url);
+ if (question == std::string::npos) {
+ url_pagespeed_off += "?ModPagespeed=off";
+ } else {
+ url_pagespeed_off += "&ModPagespeed=off";
+ }
+ fetched = fetcher_->StreamingFetchUrl(
+ url_pagespeed_off, request_headers, response_headers,
+ fetched_content_writer, message_handler);
+ } else {
+ // It was not mod-pagespeed in the first place; just pass it through
+ // so it can be saved in the slurp directory.
+ fetched = fetched_content_writer->Write(contents, message_handler);
+ }
+ }
+ return fetched;
+ }
+ private:
+ UrlFetcher* fetcher_;
+};
+
+void SlurpUrl(const std::string& uri, ApacheRewriteDriverFactory* factory,
+ request_rec* r) {
+ SimpleMetaData request_headers, response_headers;
+ ApacheHeaderToMetaData(r->headers_in, 0, 0, &request_headers);
+ std::string contents;
+ ApacheWriter writer(r, &response_headers, factory->slurp_flush_limit());
+
+ std::string stripped_url = RemoveModPageSpeedQueryParams(
+ uri, r->parsed_uri.query);
+
+ UrlFetcher* fetcher = factory->ComputeUrlFetcher();
+ ModPagespeedStrippingFetcher stripping_fetcher(fetcher);
+
+ if (stripping_fetcher.StreamingFetchUrl(stripped_url, request_headers,
+ &response_headers, &writer,
+ factory->message_handler())) {
+ // In the event of empty content, the writer's Write method may not be
+ // called, but we should still emit headers.
+ writer.OutputHeaders();
+ } else {
+ MessageHandler* handler = factory->message_handler();
+ handler->Message(kInfo, "mod_pagespeed: slurp of url %s failed.\n"
+ "Request Headers: %s\n\nResponse Headers: %s",
+ stripped_url.c_str(),
+ request_headers.ToString().c_str(),
+ response_headers.ToString().c_str());
+ SlurpDefaultHandler(r);
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/apache/apache_slurp.h b/trunk/src/net/instaweb/apache/apache_slurp.h
new file mode 100644
index 0000000..c0ffab6
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/apache_slurp.h
@@ -0,0 +1,35 @@
+// 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_APACHE_APACHE_SLURP_H_
+#define NET_INSTAWEB_APACHE_APACHE_SLURP_H_
+
+#include <string>
+
+struct request_rec;
+
+namespace net_instaweb {
+
+class ApacheRewriteDriverFactory;
+
+// Loads the URL based on the fetchers and other infrastructure in the
+// factory.
+void SlurpUrl(const std::string& uri, ApacheRewriteDriverFactory* factory,
+ request_rec* r);
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_APACHE_APACHE_SLURP_H_
diff --git a/trunk/src/net/instaweb/apache/apr_file_system.cc b/trunk/src/net/instaweb/apache/apr_file_system.cc
new file mode 100644
index 0000000..35849e0
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/apr_file_system.cc
@@ -0,0 +1,423 @@
+// 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.
+
+#include "net/instaweb/apache/apr_file_system.h"
+
+#include <string>
+
+#include "apr_file_info.h"
+#include "apr_file_io.h"
+#include "apr_pools.h"
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/timer.h"
+#include "net/instaweb/util/stack_buffer.h"
+
+namespace net_instaweb {
+
+void AprReportError(MessageHandler* message_handler, const char* filename,
+ int line, const char* message, int error_code) {
+ char buf[kStackBufferSize];
+ apr_strerror(error_code, buf, sizeof(buf));
+ std::string error_format(message);
+ error_format.append(" (code=%d %s)");
+ message_handler->Error(filename, line, error_format.c_str(), error_code, buf);
+}
+
+// Helper class to factor out common implementation details between Input and
+// Output files, in lieu of multiple inheritance.
+class FileHelper {
+ public:
+ FileHelper(apr_file_t* file, const char* filename)
+ : file_(file),
+ filename_(filename) {
+ }
+
+ void ReportError(MessageHandler* message_handler, const char* format,
+ int error_code) {
+ AprReportError(message_handler, filename_.c_str(), 0, format, error_code);
+ }
+
+ bool Close(MessageHandler* message_handler);
+ apr_file_t* file() { return file_; }
+ const std::string& filename() const { return filename_; }
+
+ private:
+ apr_file_t* const file_;
+ const std::string filename_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileHelper);
+};
+
+bool FileHelper::Close(MessageHandler* message_handler) {
+ apr_status_t ret = apr_file_close(file_);
+ if (ret != APR_SUCCESS) {
+ ReportError(message_handler, "close file", ret);
+ return false;
+ } else {
+ return true;
+ }
+}
+
+class HtmlWriterInputFile : public FileSystem::InputFile {
+ public:
+ HtmlWriterInputFile(apr_file_t* file, const char* filename);
+ virtual int Read(char* buf, int size, MessageHandler* message_handler);
+ virtual bool Close(MessageHandler* message_handler) {
+ return helper_.Close(message_handler);
+ }
+ virtual const char* filename() { return helper_.filename().c_str(); }
+ private:
+ FileHelper helper_;
+
+ DISALLOW_COPY_AND_ASSIGN(HtmlWriterInputFile);
+};
+
+class HtmlWriterOutputFile : public FileSystem::OutputFile {
+ public:
+ HtmlWriterOutputFile(apr_file_t* file, const char* filename);
+ virtual bool Write(const StringPiece& buf,
+ MessageHandler* message_handler);
+ virtual bool Flush(MessageHandler* message_handler);
+ virtual bool Close(MessageHandler* message_handler) {
+ return helper_.Close(message_handler);
+ }
+ virtual bool SetWorldReadable(MessageHandler* message_handler);
+ virtual const char* filename() { return helper_.filename().c_str(); }
+ private:
+ FileHelper helper_;
+
+ DISALLOW_COPY_AND_ASSIGN(HtmlWriterOutputFile);
+};
+
+HtmlWriterInputFile::HtmlWriterInputFile(apr_file_t* file, const char* filename)
+ : helper_(file, filename) {
+}
+
+int HtmlWriterInputFile::Read(char* buf,
+ int size,
+ MessageHandler* message_handler) {
+ apr_size_t bytes = size;
+ apr_status_t ret = apr_file_read(helper_.file(), buf, &bytes);
+ if (ret == APR_EOF) {
+ return 0;
+ }
+ if (ret != APR_SUCCESS) {
+ bytes = 0;
+ helper_.ReportError(message_handler, "read file", ret);
+ }
+ return bytes;
+}
+
+HtmlWriterOutputFile::HtmlWriterOutputFile(apr_file_t* file,
+ const char* filename)
+ : helper_(file, filename) {
+}
+
+bool HtmlWriterOutputFile::Write(const StringPiece& buf,
+ MessageHandler* message_handler) {
+ bool success = false;
+ apr_size_t bytes = buf.size();
+ apr_status_t ret = apr_file_write(helper_.file(), buf.data(), &bytes);
+ if (ret != APR_SUCCESS) {
+ helper_.ReportError(message_handler, "write file", ret);
+ } else if (bytes != buf.size()) {
+ helper_.ReportError(message_handler, "write file partial", ret);
+ } else {
+ success = true;
+ }
+ return success;
+}
+
+bool HtmlWriterOutputFile::Flush(MessageHandler* message_handler) {
+ apr_status_t ret = apr_file_flush(helper_.file());
+ if (ret != APR_SUCCESS) {
+ helper_.ReportError(message_handler, "flush file", ret);
+ return false;
+ }
+ return true;
+}
+
+bool HtmlWriterOutputFile::SetWorldReadable(MessageHandler* message_handler) {
+ apr_status_t ret = apr_file_perms_set(helper_.filename().c_str(),
+ APR_FPROT_UREAD | APR_FPROT_UWRITE |
+ APR_FPROT_GREAD | APR_FPROT_WREAD);
+ if (ret != APR_SUCCESS) {
+ helper_.ReportError(message_handler, "set permission", ret);
+ return false;
+ }
+ return true;
+}
+
+AprFileSystem::AprFileSystem(apr_pool_t* pool)
+ : pool_(NULL) {
+ apr_pool_create(&pool_, pool);
+}
+
+AprFileSystem::~AprFileSystem() {
+ apr_pool_destroy(pool_);
+}
+
+FileSystem::InputFile* AprFileSystem::OpenInputFile(
+ const char* filename, MessageHandler* message_handler) {
+ apr_file_t* file;
+ apr_status_t ret = apr_file_open(&file, filename, APR_FOPEN_READ,
+ APR_OS_DEFAULT, pool_);
+ if (ret != APR_SUCCESS) {
+ AprReportError(message_handler, filename, 0, "open input file", ret);
+ return NULL;
+ }
+ return new HtmlWriterInputFile(file, filename);
+}
+
+FileSystem::OutputFile* AprFileSystem::OpenOutputFileHelper(
+ const char* filename, MessageHandler* message_handler) {
+ apr_file_t* file;
+ apr_status_t ret = apr_file_open(&file, filename,
+ APR_WRITE | APR_CREATE | APR_TRUNCATE,
+ APR_OS_DEFAULT, pool_);
+ if (ret != APR_SUCCESS) {
+ AprReportError(message_handler, filename, 0, "open output file", ret);
+ return NULL;
+ }
+ return new HtmlWriterOutputFile(file, filename);
+}
+
+FileSystem::OutputFile* AprFileSystem::OpenTempFileHelper(
+ const StringPiece& prefix_name,
+ MessageHandler* message_handler) {
+ static const char mkstemp_hook[] = "XXXXXX";
+ scoped_array<char> template_name(
+ new char[prefix_name.size() + sizeof(mkstemp_hook)]);
+ memcpy(template_name.get(), prefix_name.data(), prefix_name.size());
+ memcpy(template_name.get() + prefix_name.size(), mkstemp_hook,
+ sizeof(mkstemp_hook));
+
+ apr_file_t* file;
+ // A temp file will be generated with the XXXXXX part of template_name being
+ // replaced.
+ // Do not use flag APR_DELONCLOSE to delete the temp file on close, it will
+ // be renamed for later use.
+ apr_status_t ret = apr_file_mktemp(
+ &file, template_name.get(),
+ APR_CREATE | APR_READ | APR_WRITE | APR_EXCL, pool_);
+ if (ret != APR_SUCCESS) {
+ AprReportError(message_handler, template_name.get(), 0,
+ "open temp file", ret);
+ return NULL;
+ }
+ return new HtmlWriterOutputFile(file, template_name.get());
+}
+
+bool AprFileSystem::RenameFileHelper(
+ const char* old_filename, const char* new_filename,
+ MessageHandler* message_handler) {
+ apr_status_t ret = apr_file_rename(old_filename, new_filename, pool_);
+ if (ret != APR_SUCCESS) {
+ AprReportError(message_handler, new_filename, 0, "renaming temp file", ret);
+ return false;
+ }
+ return true;
+}
+bool AprFileSystem::RemoveFile(const char* filename,
+ MessageHandler* message_handler) {
+ apr_status_t ret = apr_file_remove(filename, pool_);
+ if (ret != APR_SUCCESS) {
+ AprReportError(message_handler, filename, 0, "removing file", ret);
+ return false;
+ }
+ return true;
+}
+
+bool AprFileSystem::MakeDir(const char* directory_path,
+ MessageHandler* handler) {
+ apr_status_t ret = apr_dir_make(directory_path, APR_FPROT_OS_DEFAULT, pool_);
+ if (ret != APR_SUCCESS) {
+ AprReportError(handler, directory_path, 0, "creating dir", ret);
+ return false;
+ }
+ return true;
+}
+
+BoolOrError AprFileSystem::Exists(const char* path, MessageHandler* handler) {
+ BoolOrError exists; // Error is the default state.
+ apr_int32_t wanted = APR_FINFO_TYPE;
+ apr_finfo_t finfo;
+ apr_status_t ret = apr_stat(&finfo, path, wanted, pool_);
+ if (ret != APR_SUCCESS && ret != APR_ENOENT) {
+ AprReportError(handler, path, 0, "failed to stat", ret);
+ exists.set_error();
+ } else {
+ exists.set(ret == APR_SUCCESS);
+ }
+ return exists;
+}
+
+BoolOrError AprFileSystem::IsDir(const char* path, MessageHandler* handler) {
+ BoolOrError is_dir; // Error is the default state.
+ apr_int32_t wanted = APR_FINFO_TYPE;
+ apr_finfo_t finfo;
+ apr_status_t ret = apr_stat(&finfo, path, wanted, pool_);
+ if (ret != APR_SUCCESS && ret != APR_ENOENT) {
+ AprReportError(handler, path, 0, "failed to stat", ret);
+ is_dir.set_error();
+ } else {
+ is_dir.set(ret == APR_SUCCESS && finfo.filetype == APR_DIR);
+ }
+ return is_dir;
+}
+
+bool AprFileSystem::ListContents(const StringPiece& dir,
+ StringVector* files,
+ MessageHandler* handler) {
+ std::string dirString = dir.as_string();
+ EnsureEndsInSlash(&dirString);
+ const char* dirname = dirString.c_str();
+ apr_dir_t* mydir;
+ apr_status_t ret = apr_dir_open(&mydir, dirname, pool_);
+ if (ret != APR_SUCCESS) {
+ AprReportError(handler, dirname, 0, "failed to opendir", ret);
+ return false;
+ } else {
+ apr_finfo_t finfo;
+ apr_int32_t wanted = APR_FINFO_NAME;
+ while (apr_dir_read(&finfo, wanted, mydir) != APR_ENOENT) {
+ if ((strcmp(finfo.name, ".") != 0) &&
+ (strcmp(finfo.name, "..") != 0)) {
+ files->push_back(dirString + finfo.name);
+ }
+ }
+ }
+ ret = apr_dir_close(mydir);
+ if (ret != APR_SUCCESS) {
+ AprReportError(handler, dirname, 0, "failed to closedir", ret);
+ return false;
+ }
+ return true;
+}
+
+bool AprFileSystem::Atime(const StringPiece& path,
+ int64* timestamp_sec, MessageHandler* handler) {
+ // TODO(abliss): there are some situations where this doesn't work
+ // -- e.g. if the filesystem is mounted noatime.
+ const std::string path_string = path.as_string();
+ const char* path_str = path_string.c_str();
+ apr_int32_t wanted = APR_FINFO_ATIME;
+ apr_finfo_t finfo;
+ apr_status_t ret = apr_stat(&finfo, path_str, wanted, pool_);
+ if (ret != APR_SUCCESS) {
+ AprReportError(handler, path_str, 0, "failed to stat", ret);
+ return false;
+ } else {
+ *timestamp_sec = finfo.atime;
+ return true;
+ }
+}
+
+bool AprFileSystem::Ctime(const StringPiece& path,
+ int64* timestamp_sec, MessageHandler* handler) {
+ const std::string path_string = path.as_string();
+ const char* path_str = path_string.c_str();
+ apr_int32_t wanted = APR_FINFO_CTIME;
+ apr_finfo_t finfo;
+ apr_status_t ret = apr_stat(&finfo, path_str, wanted, pool_);
+ if (ret != APR_SUCCESS) {
+ AprReportError(handler, path_str, 0, "failed to stat", ret);
+ return false;
+ } else {
+ *timestamp_sec = finfo.ctime;
+ return true;
+ }
+}
+
+bool AprFileSystem::Size(const StringPiece& path, int64* size,
+ MessageHandler* handler) {
+ const std::string path_string = path.as_string();
+ const char* path_str = path_string.c_str();
+ apr_int32_t wanted = APR_FINFO_SIZE;
+ apr_finfo_t finfo;
+ apr_status_t ret = apr_stat(&finfo, path_str, wanted, pool_);
+ if (ret != APR_SUCCESS) {
+ AprReportError(handler, path_str, 0, "failed to stat", ret);
+ return false;
+ } else {
+ *size = finfo.size;
+ return true;
+ }
+}
+
+BoolOrError AprFileSystem::TryLock(const StringPiece& lock_name,
+ MessageHandler* handler) {
+ const std::string lock_string = lock_name.as_string();
+ const char* lock_str = lock_string.c_str();
+ // TODO(abliss): mkdir is not atomic on all platforms. We should
+ // perhaps use an apr_global_mutex_t here.
+ apr_status_t ret = apr_dir_make(lock_str, APR_FPROT_OS_DEFAULT, pool_);
+ if (ret == APR_SUCCESS) {
+ return BoolOrError(true);
+ } else if (errno == EEXIST) {
+ return BoolOrError(false);
+ } else {
+ AprReportError(handler, lock_str, 0, "creating dir", ret);
+ return BoolOrError();
+ }
+}
+
+BoolOrError AprFileSystem::TryLockWithTimeout(const StringPiece& lock_name,
+ int64 timeout_ms,
+ MessageHandler* handler) {
+ const std::string lock_string = lock_name.as_string();
+ const char* lock_str = lock_string.c_str();
+ BoolOrError result = TryLock(lock_name, handler);
+ if (result.is_true() || result.is_error()) {
+ // We got the lock, or the lock is ungettable.
+ return result;
+ }
+ int64 c_time_us;
+ if (!Ctime(lock_name, &c_time_us, handler)) {
+ // We can't stat the lockfile.
+ return BoolOrError();
+ }
+
+ if (apr_time_now() - c_time_us < timeout_ms * 1000) {
+ // The lock is insufficiently stale.
+ return BoolOrError(false);
+ }
+ if (!Unlock(lock_name, handler)) {
+ // We couldn't break the lock. Maybe someone else beat us to it.
+ return BoolOrError();
+ }
+ handler->Info(lock_str, 0, "Breaking lock! now-ctime=%d-%d > %d (sec)",
+ static_cast<int>(apr_time_now() / Timer::kSecondUs),
+ static_cast<int>(c_time_us / Timer::kSecondUs),
+ static_cast<int>(timeout_ms / Timer::kSecondMs));
+
+ return TryLock(lock_name, handler);
+}
+
+bool AprFileSystem::Unlock(const StringPiece& lock_name,
+ MessageHandler* handler) {
+ const std::string lock_string = lock_name.as_string();
+ const char* lock_str = lock_string.c_str();
+ apr_status_t ret = apr_dir_remove(lock_str, pool_);
+ if (ret != APR_SUCCESS) {
+ AprReportError(handler, lock_str, 0, "removing dir", ret);
+ return false;
+ }
+ return true;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/apache/apr_file_system.h b/trunk/src/net/instaweb/apache/apr_file_system.h
new file mode 100644
index 0000000..de49745
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/apr_file_system.h
@@ -0,0 +1,79 @@
+// 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.
+
+#ifndef NET_INSTAWEB_APACHE_APR_FILE_SYSTEM_H_
+#define NET_INSTAWEB_APACHE_APR_FILE_SYSTEM_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/file_system.h"
+#include "net/instaweb/util/public/message_handler.h"
+
+struct apr_pool_t;
+
+namespace net_instaweb {
+
+void AprReportError(MessageHandler* message_handler, const char* filename,
+ int line, const char* message, int error_code);
+
+class AprFileSystem : public FileSystem {
+ public:
+ explicit AprFileSystem(apr_pool_t* pool);
+ ~AprFileSystem();
+ virtual bool Atime(const StringPiece& path,
+ int64* timestamp_sec, MessageHandler* handler);
+ bool Ctime(const StringPiece& path,
+ int64* timestamp_sec, MessageHandler* handler);
+ virtual InputFile* OpenInputFile(
+ const char* file, MessageHandler* message_handler);
+ virtual OutputFile* OpenOutputFileHelper(
+ const char* file, MessageHandler* message_handler);
+ // See FileSystem interface for specifics of OpenTempFile.
+ virtual OutputFile* OpenTempFileHelper(
+ const StringPiece& prefix_name,
+ MessageHandler* message_handler);
+ virtual bool RenameFileHelper(const char* old_filename,
+ const char* new_filename,
+ MessageHandler* message_handler);
+ virtual bool RemoveFile(const char* filename,
+ MessageHandler* message_handler);
+ virtual bool Size(const StringPiece& path, int64* size,
+ MessageHandler* handler);
+ // Like POSIX 'mkdir', makes a directory only if parent directory exists.
+ // Fails if directory_name already exists or parent directory doesn't exist.
+ virtual bool MakeDir(const char* directory_path, MessageHandler* handler);
+
+ // Like POSIX 'test -e', checks if path exists (is a file, directory, etc.).
+ virtual BoolOrError Exists(const char* path, MessageHandler* handler);
+
+ // Like POSIX 'test -d', checks if path exists and refers to a directory.
+ virtual BoolOrError IsDir(const char* path, MessageHandler* handler);
+
+ virtual bool ListContents(const StringPiece& dir, StringVector* files,
+ MessageHandler* handler);
+ virtual BoolOrError TryLock(const StringPiece& lock_name,
+ MessageHandler* handler);
+ virtual BoolOrError TryLockWithTimeout(const StringPiece& lock_name,
+ int64 timeout_ms,
+ MessageHandler* handler);
+ virtual bool Unlock(const StringPiece& lock_name, MessageHandler* handler);
+
+ private:
+ apr_pool_t* pool_;
+
+ DISALLOW_COPY_AND_ASSIGN(AprFileSystem);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_APACHE_APR_FILE_SYSTEM_H_
diff --git a/trunk/src/net/instaweb/apache/apr_file_system_test.cc b/trunk/src/net/instaweb/apache/apr_file_system_test.cc
new file mode 100644
index 0000000..0ec4870
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/apr_file_system_test.cc
@@ -0,0 +1,177 @@
+// 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.
+
+#include <string>
+
+#include "apr_file_io.h"
+#include "apr_pools.h"
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/apache/apr_file_system.h"
+#include "net/instaweb/util/public/file_system_test.h"
+#include "net/instaweb/util/public/google_message_handler.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net_instaweb {
+
+class AprFileSystemTest : public FileSystemTest {
+ protected:
+ AprFileSystemTest() { }
+
+ virtual void DeleteRecursively(const StringPiece& filename) {
+ MyDeleteFileRecursively(filename.as_string(), NULL, NULL);
+ }
+ virtual FileSystem* file_system() {
+ return file_system_.get();
+ }
+ virtual std::string test_tmpdir() {
+ return test_tmpdir_;
+ }
+ virtual void SetUp() {
+ apr_initialize();
+ atexit(apr_terminate);
+ apr_pool_create(&pool_, NULL);
+ file_system_.reset(new AprFileSystem(pool_));
+ GetAprFileSystemTestDir(&test_tmpdir_);
+ }
+
+ virtual void TearDown() {
+ file_system_.reset();
+ apr_pool_destroy(pool_);
+ }
+
+ void GetAprFileSystemTestDir(std::string* str) {
+ const char* tmpdir;
+ apr_status_t status = apr_temp_dir_get(&tmpdir, pool_);
+ ASSERT_EQ(APR_SUCCESS, status);
+ char* test_temp_dir;
+ status = apr_filepath_merge(&test_temp_dir, tmpdir, "apr_file_sytem_test",
+ APR_FILEPATH_NATIVE, pool_);
+ ASSERT_EQ(APR_SUCCESS, status);
+ if (file_system_->Exists(test_temp_dir, &handler_).is_false()) {
+ ASSERT_TRUE(file_system_->MakeDir(test_temp_dir, &handler_));
+ }
+ ASSERT_TRUE(file_system_->Exists(test_temp_dir, &handler_).is_true());
+ *str = test_temp_dir;
+ }
+
+ void MyDeleteFileRecursively(const std::string& filename,
+ const char* /*a*/,
+ const char* /*b*/) {
+ if (file_system_->IsDir(filename.c_str(), &handler_).is_true()) {
+ // TODO(lsong): Make it recursive.
+ apr_status_t status = apr_dir_remove(filename.c_str(), pool_);
+ if (status != APR_SUCCESS) {
+ AprReportError(&handler_, __FILE__, __LINE__, "dir remove", status);
+ // TODO(lsong): Rename the dir to try.
+ if (APR_STATUS_IS_ENOTEMPTY(status)) {
+ // Need a tempname to rename to.
+ char* template_name;
+ std::string tempname = filename + "-apr-XXXXXX";
+ status = apr_filepath_merge(&template_name, test_tmpdir_.c_str(),
+ tempname.c_str(), APR_FILEPATH_NATIVE,
+ pool_);
+ ASSERT_EQ(APR_SUCCESS, status);
+ apr_file_t* file;
+ status = apr_file_mktemp(&file, template_name, 0, pool_);
+ ASSERT_EQ(APR_SUCCESS, status);
+ const char* the_path_name;
+ status = apr_file_name_get(&the_path_name, file);
+ ASSERT_EQ(APR_SUCCESS, status);
+ status = apr_file_close(file);
+ ASSERT_EQ(APR_SUCCESS, status);
+ // Got the name to rename to.
+ status = apr_file_rename(filename.c_str(), the_path_name, pool_);
+ if (status != APR_SUCCESS) {
+ AprReportError(&handler_, __FILE__, __LINE__, "dir rename", status);
+ }
+ }
+ }
+ ASSERT_EQ(APR_SUCCESS, status);
+ } else {
+ file_system_->RemoveFile(filename.c_str(), &handler_);
+ }
+ }
+
+ protected:
+ GoogleMessageHandler handler_;
+ scoped_ptr<AprFileSystem> file_system_;
+ apr_pool_t* pool_;
+ std::string test_tmpdir_;
+
+ DISALLOW_COPY_AND_ASSIGN(AprFileSystemTest);
+};
+
+TEST_F(AprFileSystemTest, TestWriteRead) {
+ TestWriteRead();
+}
+
+TEST_F(AprFileSystemTest, TestTemp) {
+ TestTemp();
+}
+
+TEST_F(AprFileSystemTest, TestRename) {
+ TestRename();
+}
+
+TEST_F(AprFileSystemTest, TestRemove) {
+ TestRemove();
+}
+
+TEST_F(AprFileSystemTest, TestExists) {
+ TestExists();
+}
+
+TEST_F(AprFileSystemTest, TestCreateFileInDir) {
+ TestCreateFileInDir();
+}
+
+
+TEST_F(AprFileSystemTest, TestMakeDir) {
+ TestMakeDir();
+}
+
+TEST_F(AprFileSystemTest, TestIsDir) {
+ TestIsDir();
+}
+
+TEST_F(AprFileSystemTest, TestRecursivelyMakeDir) {
+ TestRecursivelyMakeDir();
+}
+
+TEST_F(AprFileSystemTest, TestRecursivelyMakeDir_NoPermission) {
+ TestRecursivelyMakeDir_NoPermission();
+}
+
+TEST_F(AprFileSystemTest, TestRecursivelyMakeDir_FileInPath) {
+ TestRecursivelyMakeDir_FileInPath();
+}
+
+TEST_F(AprFileSystemTest, TestListContents) {
+ TestListContents();
+}
+
+TEST_F(AprFileSystemTest, TestAtime) {
+ TestAtime();
+}
+
+TEST_F(AprFileSystemTest, TestSize) {
+ TestSize();
+}
+
+TEST_F(AprFileSystemTest, TestLock) {
+ TestLock();
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/apache/apr_mutex.cc b/trunk/src/net/instaweb/apache/apr_mutex.cc
new file mode 100644
index 0000000..162e5c5
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/apr_mutex.cc
@@ -0,0 +1,38 @@
+// 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.
+
+#include "net/instaweb/apache/apr_mutex.h"
+
+#include "apr_pools.h"
+#include "apr_thread_mutex.h"
+
+namespace net_instaweb {
+
+AprMutex::AprMutex(apr_pool_t* pool) {
+ apr_thread_mutex_create(&thread_mutex_, APR_THREAD_MUTEX_DEFAULT, pool);
+}
+
+AprMutex::~AprMutex() {
+ apr_thread_mutex_destroy(thread_mutex_);
+}
+
+void AprMutex::Lock() {
+ apr_thread_mutex_lock(thread_mutex_);
+}
+
+void AprMutex::Unlock() {
+ apr_thread_mutex_unlock(thread_mutex_);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/apache/apr_mutex.h b/trunk/src/net/instaweb/apache/apr_mutex.h
new file mode 100644
index 0000000..9a4da21
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/apr_mutex.h
@@ -0,0 +1,41 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+//
+// 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.
+
+#ifndef NET_INSTAWEB_APACHE_APR_MUTEX_H_
+#define NET_INSTAWEB_APACHE_APR_MUTEX_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/abstract_mutex.h"
+
+// Forward declaration.
+struct apr_thread_mutex_t;
+struct apr_pool_t;
+
+namespace net_instaweb {
+
+class AprMutex : public net_instaweb::AbstractMutex {
+ public:
+ explicit AprMutex(apr_pool_t* pool);
+ virtual ~AprMutex();
+ virtual void Lock();
+ virtual void Unlock();
+ private:
+ apr_thread_mutex_t* thread_mutex_;
+
+ DISALLOW_COPY_AND_ASSIGN(AprMutex);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_APACHE_APR_MUTEX_H_
diff --git a/trunk/src/net/instaweb/apache/apr_statistics.cc b/trunk/src/net/instaweb/apache/apr_statistics.cc
new file mode 100644
index 0000000..26eb8ca
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/apr_statistics.cc
@@ -0,0 +1,278 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+//
+// 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.
+
+#include "net/instaweb/apache/apr_statistics.h"
+
+#include "apr_global_mutex.h"
+#include "apr_shm.h"
+#include "apr_strings.h"
+#include "apr.h"
+#include "apr_errno.h"
+#include "apr_pools.h"
+#include "apr_time.h"
+#include "base/logging.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/writer.h"
+#include "net/instaweb/util/stack_buffer.h"
+
+extern "C" {
+#include "httpd.h"
+#include "http_config.h"
+#ifdef AP_NEED_SET_MUTEX_PERMS
+#include "unixd.h"
+#endif
+}
+
+
+namespace {
+
+const char kStatisticsDir[] = "statistics";
+const char kStatisticsMutexPrefix[] = "statistics/stats_mutex.";
+const char kStatisticsValuePrefix[] = "statistics/stats_value.";
+
+} // namespace
+
+namespace net_instaweb {
+
+#define COUNT_LOCK_WAIT_TIME 0
+#if COUNT_LOCK_WAIT_TIME
+// Unlocked counter, which is reasonably safe on x86 if it's 32-bit, which
+// is good for over hour of of locked time, which is good enough for
+// experiments, particularly on a prefork system where the only extra
+// thread is the one from serf.
+static uint32 accumulated_time_in_global_locks_us = 0;
+static uint32 prev_message_us = 0;
+#endif
+
+// Helper class for lexically scoped mutexing.
+// TODO(jmarantz): consier merging this with ScopedLock.
+class AprScopedGlobalLock {
+ public:
+ explicit AprScopedGlobalLock(const AprVariable* var) : variable_(var) {
+ if (variable_->mutex_ == NULL) {
+ acquired_ = false;
+ } else {
+#if COUNT_LOCK_WAIT_TIME
+ int64 time_us = apr_time_now();
+#endif
+ acquired_ = variable_->CheckResult(
+ apr_global_mutex_lock(variable_->mutex_), "lock mutex");
+#if COUNT_LOCK_WAIT_TIME
+ int64 delta_us = apr_time_now() - time_us;
+ if (delta_us > 0) {
+ accumulated_time_in_global_locks_us += delta_us;
+ if ((accumulated_time_in_global_locks_us - prev_message_us) > 1000000) {
+ // race condition is possible here
+ prev_message_us = accumulated_time_in_global_locks_us;
+ double time_wasted_seconds = accumulated_time_in_global_locks_us
+ / 1000000.0;
+ LOG(ERROR) << "Cumulative locked time spent: " << time_wasted_seconds
+ << "seconds";
+ }
+ }
+#endif
+ }
+ }
+
+ ~AprScopedGlobalLock() {
+ if (acquired_) {
+ variable_->CheckResult(
+ apr_global_mutex_unlock(variable_->mutex_), "unlock mutex");
+ }
+ }
+
+ bool acquired() const { return acquired_; }
+
+ private:
+ const AprVariable* variable_;
+ bool acquired_;
+
+ DISALLOW_COPY_AND_ASSIGN(AprScopedGlobalLock);
+};
+
+
+
+AprVariable::AprVariable(const StringPiece& name)
+ : mutex_(NULL), name_(name.as_string()), shm_(NULL), value_ptr_(NULL) {
+}
+
+int64 AprVariable::Get64() const {
+ AprScopedGlobalLock lock(this);
+ int64 value = lock.acquired() ? *value_ptr_ : -1;
+ return value;
+}
+
+int AprVariable::Get() const {
+ return Get64();
+}
+
+void AprVariable::Set(int newValue) {
+ AprScopedGlobalLock lock(this);
+ if (lock.acquired()) {
+ *value_ptr_ = newValue;
+ }
+}
+
+void AprVariable::Add(int delta) {
+ AprScopedGlobalLock lock(this);
+ if (lock.acquired()) {
+ *value_ptr_ += delta;
+ }
+}
+
+bool AprVariable::CheckResult(
+ const apr_status_t result, const StringPiece& verb,
+ const StringPiece& filename) const {
+ if (result != APR_SUCCESS) {
+ char buf[kStackBufferSize];
+ apr_strerror(result, buf, sizeof(buf));
+ LOG(ERROR) << "Variable " << name_ << " cannot " << verb << ": " << buf
+ << " " << filename;
+ return false;
+ }
+ return true;
+}
+
+bool AprVariable::InitMutex(const StringPiece& filename_prefix,
+ apr_pool_t* pool, bool parent) {
+ // Create or attach to the mutex if we don't have one already.
+ const char* filename = apr_pstrcat(
+ pool, filename_prefix.as_string().c_str(), kStatisticsMutexPrefix,
+ name_.c_str(), NULL);
+ if (parent) {
+ // We're being called from post_config. Must create mutex.
+ // Ensure the directory exists
+ apr_dir_make(StrCat(filename_prefix, kStatisticsDir).c_str(),
+ APR_FPROT_OS_DEFAULT, pool);
+ // TODO(abliss): do we need to destroy this mutex later?
+ if (CheckResult(apr_global_mutex_create(
+ &mutex_, filename, APR_LOCK_DEFAULT, pool),
+ "create mutex", filename)) {
+ // On apache installations which (a) are unix-based, (b) use a
+ // flock-based mutex, and (c) start the parent process as root but child
+ // processes as a less-privileged user, we need this extra code to set
+ // up the permissions of the lock.
+#ifdef AP_NEED_SET_MUTEX_PERMS
+ CheckResult(unixd_set_global_mutex_perms(mutex_), "chown mutex",
+ filename);
+#endif
+ return true;
+ }
+ } else {
+ // We're being called from child_init. Mutex must already exist.
+ if (mutex_) {
+ if (CheckResult(apr_global_mutex_child_init(&mutex_, filename, pool),
+ "attach mutex", filename)) {
+ return true;
+ } else {
+ // Something went wrong; disable this variable by nulling its mutex.
+ mutex_ = NULL;
+ }
+ } else {
+ CheckResult(APR_ENOLOCK, "attach mutex", filename);
+ }
+ }
+ return false;
+}
+
+bool AprVariable::InitShm(const StringPiece& filename_prefix,
+ apr_pool_t* pool, bool parent) {
+ // On some platforms we inherit the existing segment...
+ if (!shm_) {
+ // ... but on others we must reattach to it.
+ const char* filename = apr_pstrcat(
+ pool, filename_prefix.as_string().c_str(), kStatisticsValuePrefix,
+ name_.c_str(), NULL);
+ if (parent) {
+ // Sometimes the shm/file are leftover from a previous unclean exit.
+ apr_shm_remove(filename, pool);
+ apr_file_remove(filename, pool);
+ int64 foo;
+ // This shm is destroyed when apache is shutdown cleanly.
+ CheckResult(apr_shm_create(&shm_, sizeof(foo), filename, pool),
+ "create shared memory", filename);
+ } else {
+ CheckResult(apr_shm_attach(&shm_, filename, pool),
+ "attach to shared memory", filename);
+ }
+ }
+ if (shm_) {
+ // value_ptr always needs to be reset, even if shm was inherited,
+ // since its base address may have changed.
+ value_ptr_ = reinterpret_cast<int64*>(apr_shm_baseaddr_get(shm_));
+ return true;
+ } else {
+ // Something went wrong; disable this variable by nulling its mutex.
+ mutex_ = NULL;
+ return false;
+ }
+}
+
+AprStatistics::AprStatistics(const StringPiece& filename_prefix)
+ : frozen_(false), is_child_(false), filename_prefix_(filename_prefix) {
+ apr_pool_create(&pool_, NULL);
+}
+
+AprStatistics::~AprStatistics() {
+ if (!is_child_) {
+ apr_pool_destroy(pool_);
+ }
+}
+
+AprVariable* AprStatistics::NewVariable(const StringPiece& name, int index) {
+ if (frozen_) {
+ LOG(ERROR) << "Cannot add variable " << name
+ << " after AprStatistics is frozen!";
+ return NULL;
+ } else {
+ return new AprVariable(name);
+ }
+}
+
+void AprStatistics::InitVariables(bool parent) {
+ is_child_ |= !parent;
+ if (frozen_) {
+ return;
+ }
+ frozen_ = true;
+ // Set up a global mutex and a shared-memory segment for each variable.
+ for (int i = 0, n = variables_.size(); i < n; ++i) {
+ AprVariable* var = variables_[i];
+ if (!var->InitMutex(filename_prefix_, pool_, parent) ||
+ !var->InitShm(filename_prefix_, pool_, parent)) {
+ LOG(ERROR) << "Statistics initialization failed in pid " << getpid();
+ return;
+ }
+ }
+}
+
+void AprStatistics::Dump(Writer* writer, MessageHandler* message_handler) {
+ for (int i = 0, n = variables_.size(); i < n; ++i) {
+ AprVariable* var = variables_[i];
+ writer->Write(var->name(), message_handler);
+ writer->Write(": ", message_handler);
+ writer->Write(Integer64ToString(var->Get64()), message_handler);
+ writer->Write("\n", message_handler);
+ }
+}
+
+void AprStatistics::Clear() {
+ for (int i = 0, n = variables_.size(); i < n; ++i) {
+ AprVariable* var = variables_[i];
+ var->Set(0);
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/apache/apr_statistics.h b/trunk/src/net/instaweb/apache/apr_statistics.h
new file mode 100644
index 0000000..7b50969
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/apr_statistics.h
@@ -0,0 +1,114 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+//
+// 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.
+
+#ifndef NET_INSTAWEB_APACHE_APR_STATISTICS_H_
+#define NET_INSTAWEB_APACHE_APR_STATISTICS_H_
+
+#include <string>
+
+#include "net/instaweb/util/public/statistics_template.h"
+#include "net/instaweb/util/public/string_util.h"
+#include "apr_shm.h"
+#include "apr.h"
+#include "apr_errno.h"
+
+struct apr_pool_t;
+struct apr_global_mutex_t;
+namespace net_instaweb {
+
+class Writer;
+class MessageHandler;
+
+// An implementation of Statistics using the APR's Shared Memory module,
+// apr_shm. These statistics will be shared amongst all processes and threads
+// spawned by Apache. Note that we will be obtaining a global mutex for every
+// read and write to these variables. Since this may be expensive, it is
+// recommended that each thread keep a local cache and infrequently write
+// through to this Statistics object. TODO(abliss): actually do this.
+//
+// Because we must allocate shared memory segments before the module forks off
+// its children, all AddVariable calls must be in the post_config hook. Once
+// all variables are added, you must call InitVariables.
+//
+// If a variable fails to initialize (due to either its mutex or its shared
+// memory segment not working), it will not increment in that process (and a
+// warning message will be logged). Other variables will work normally. If the
+// variable fails to initialize in the process that happens to serve the
+// mod_pagespeed_statistics page, then the variable will show up with value -1.
+//
+// Implementation details heavily cribbed from mod_shm_counter by Aaron Bannert.
+
+class AprVariable : public Variable {
+ public:
+ explicit AprVariable(const StringPiece& name);
+ int64 Get64() const;
+ virtual int Get() const;
+ virtual void Set(int newValue);
+ virtual void Add(int delta);
+ private:
+ friend class AprStatistics;
+ friend class AprScopedGlobalLock;
+
+ // Logs an error message and returns false if the result is not SUCCESS.
+ bool CheckResult(
+ const apr_status_t result, const StringPiece& verb,
+ const StringPiece& filename = EmptyString::kEmptyString) const;
+ // Initialize this variable's mutex
+ bool InitMutex(const StringPiece& filename_prefix, apr_pool_t* pool,
+ bool parent);
+ // Initialize this variable's shared memory segment.
+ bool InitShm(const StringPiece& filename_prefix, apr_pool_t* pool,
+ bool parent);
+ const std::string& name() const { return name_; }
+ // The global (cross-thread, cross-process) mutex protecting value_.
+ // This is NULL if the variable has not yet been properly initialized.
+ apr_global_mutex_t* mutex_;
+ // The name of this variable.
+ const std::string name_;
+ // Pointer to the shared-memory segment containing our current value.
+ apr_shm_t* shm_;
+ // Offset within the shared-memory segment for our current value.
+ int64* value_ptr_;
+};
+
+class AprStatistics : public StatisticsTemplate<AprVariable> {
+ public:
+ AprStatistics(const StringPiece& filename_prefix);
+ virtual ~AprStatistics();
+
+ virtual AprVariable* NewVariable(const StringPiece& name, int index);
+
+ // Allocate shared memory segments and mutices for all variables. This must
+ // be called with parent=true from the post_config hook, and with parent=false
+ // from the child_init hook. After this is called, you must no longer call
+ // AddVariable.
+ void InitVariables(bool parent);
+
+ // Dump the statistics to the given writer.
+ void Dump(Writer* writer, MessageHandler* message_handler);
+
+ // Set all statistics to 0.
+ void Clear();
+
+ bool frozen() const { return frozen_; }
+ private:
+ bool frozen_;
+ bool is_child_;
+ const StringPiece& filename_prefix_;
+ apr_pool_t* pool_;
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_APACHE_APR_STATISTICS_H_
diff --git a/trunk/src/net/instaweb/apache/apr_timer.cc b/trunk/src/net/instaweb/apache/apr_timer.cc
new file mode 100644
index 0000000..c085873
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/apr_timer.cc
@@ -0,0 +1,32 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+//
+// 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.
+
+#include "apr_time.h"
+#include "net/instaweb/apache/apr_timer.h"
+
+namespace net_instaweb {
+
+AprTimer::~AprTimer() {
+}
+
+int64 AprTimer::NowUs() const {
+ // apr_time_now returns microseconds.
+ return apr_time_now();
+}
+
+void AprTimer::SleepUs(int64 us) {
+ apr_sleep(us);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/apache/apr_timer.h b/trunk/src/net/instaweb/apache/apr_timer.h
new file mode 100644
index 0000000..90ba26a
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/apr_timer.h
@@ -0,0 +1,33 @@
+// 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.
+
+#ifndef HTML_REWRITER_APR_TIMER_H_
+#define HTML_REWRITER_APR_TIMER_H_
+
+#include "net/instaweb/util/public/timer.h"
+
+using net_instaweb::Timer;
+
+namespace net_instaweb {
+
+class AprTimer : public Timer {
+ public:
+ virtual ~AprTimer();
+ virtual int64 NowUs() const;
+ virtual void SleepUs(int64 us);
+};
+
+} // namespace net_instaweb
+
+#endif // HTML_REWRITER_APR_TIMER_H_
diff --git a/trunk/src/net/instaweb/apache/header_util.cc b/trunk/src/net/instaweb/apache/header_util.cc
new file mode 100644
index 0000000..f87912d
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/header_util.cc
@@ -0,0 +1,143 @@
+// 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.
+
+#include "net/instaweb/apache/header_util.h"
+#include "net/instaweb/apache/apr_timer.h"
+#include "net/instaweb/apache/instaweb_context.h"
+#include "net/instaweb/rewriter/public/resource_manager.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include "net/instaweb/util/public/time_util.h"
+
+#include "http_core.h"
+
+namespace net_instaweb {
+
+namespace {
+
+const char kRepairCachingHeader[] = "X-Mod-Pagespeed-Repair";
+
+int AddAttributeCallback(void *rec, const char *key, const char *value) {
+ MetaData* meta_data = static_cast<MetaData*>(rec);
+ meta_data->Add(key, value);
+ return 1;
+}
+
+} // namespace
+
+// proto_num is the version number of protocol; 1.1 = 1001
+void ApacheHeaderToMetaData(const apr_table_t* apache_headers,
+ int status_code,
+ int proto_num,
+ MetaData* meta_data) {
+ meta_data->SetStatusAndReason(static_cast<HttpStatus::Code>(status_code));
+ if (proto_num >= 1000) {
+ meta_data->set_major_version(proto_num / 1000);
+ meta_data->set_minor_version(proto_num % 1000);
+ }
+ apr_table_do(AddAttributeCallback, meta_data, apache_headers, NULL);
+}
+
+void MetaDataToApacheHeader(const MetaData& meta_data,
+ apr_table_t* apache_headers,
+ int* status_code,
+ int* proto_num) {
+ *status_code = meta_data.status_code();
+ *proto_num = (meta_data.major_version() * 1000) + meta_data.minor_version();
+ for (int i = 0, n = meta_data.NumAttributes(); i < n; ++i) {
+ apr_table_add(apache_headers, meta_data.Name(i), meta_data.Value(i));
+ }
+}
+
+void UpdateCacheHeaders(const char* cache_control, request_rec* request) {
+ SimpleMetaData response_headers;
+ response_headers.set_status_code(request->status);
+ if (request->proto_num >= 1000) {
+ response_headers.set_major_version(request->proto_num / 1000);
+ response_headers.set_minor_version(request->proto_num % 1000);
+ }
+ AprTimer timer;
+ response_headers.Add(HttpAttributes::kCacheControl, cache_control);
+ response_headers.SetDate(timer.NowMs());
+ response_headers.ComputeCaching();
+ if (response_headers.IsCacheable()) {
+ apr_table_set(request->headers_out, HttpAttributes::kCacheControl,
+ cache_control);
+ apr_table_setn(request->headers_out, HttpAttributes::kEtag,
+ ResourceManager::kResourceEtagValue); // no copy neeeded
+
+ // Convert our own cache-control data into an Expires header.
+ int64 expire_time_ms = response_headers.CacheExpirationTimeMs();
+ bool unset_expires = true;
+ if (expire_time_ms > 0) {
+ std::string time_string;
+ if (ConvertTimeToString(expire_time_ms, &time_string)) {
+ apr_table_set(request->headers_out, HttpAttributes::kExpires,
+ time_string.c_str());
+ unset_expires = false;
+ }
+ }
+ if (unset_expires) {
+ apr_table_unset(request->headers_out, HttpAttributes::kExpires);
+ }
+ } else {
+ apr_table_unset(request->headers_out, HttpAttributes::kExpires);
+ apr_table_unset(request->headers_out, HttpAttributes::kEtag);
+ apr_table_unset(request->headers_out, HttpAttributes::kLastModified);
+ }
+}
+
+void SetupCacheRepair(const char* cache_control, request_rec* request) {
+ // In case Apache configuration directives set up caching headers,
+ // we will need override our cache-extended resources in a late-running
+ // output filter.
+ // TODO(jmarantz): Do not use headers_out as out message passing
+ // mechanism. Switch to configuration vectors or something like that
+ // instead.
+ apr_table_add(request->headers_out, kRepairCachingHeader, cache_control);
+ // Add the repair headers filter to fix the cacheing header.
+ ap_add_output_filter(InstawebContext::kRepairHeadersFilterName,
+ NULL, request, request->connection);
+}
+
+void RepairCachingHeaders(request_rec* request) {
+ const char* cache_control = apr_table_get(request->headers_out,
+ kRepairCachingHeader);
+ if (cache_control != NULL) {
+ SetCacheControl(cache_control, request);
+ apr_table_unset(request->headers_out, kRepairCachingHeader);
+ }
+}
+
+void SetCacheControl(const char* cache_control, request_rec* request) {
+ UpdateCacheHeaders(cache_control, request);
+ apr_table_set(request->headers_out, HttpAttributes::kCacheControl,
+ cache_control);
+}
+
+int PrintAttributeCallback(void *rec, const char *key, const char *value) {
+ fprintf(stdout, " %s: %s\n", key, value);
+ return 1;
+}
+
+// This routine is intended for debugging so fprintf to stdout is the way
+// to get instant feedback.
+void PrintHeaders(request_rec* request) {
+ fprintf(stdout, "Input headers:\n");
+ apr_table_do(PrintAttributeCallback, NULL, request->headers_in, NULL);
+ fprintf(stdout, "Output headers:\n");
+ apr_table_do(PrintAttributeCallback, NULL, request->headers_in, NULL);
+ fflush(stdout);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/apache/header_util.h b/trunk/src/net/instaweb/apache/header_util.h
new file mode 100644
index 0000000..4d86c8e
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/header_util.h
@@ -0,0 +1,69 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+//
+// 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.
+
+#ifndef NET_INSTAWEB_APACHE_HEADER_UTIL_H_
+#define NET_INSTAWEB_APACHE_HEADER_UTIL_H_
+
+#include "net/instaweb/util/public/meta_data.h"
+// The httpd header must be after the meta_data.h. Otherwise, the
+// compiler will complain
+// "strtoul_is_not_a_portable_function_use_strtol_instead".
+#include "httpd.h"
+
+namespace net_instaweb {
+
+// Converts Apache header structure into an Instaweb MetaData.
+//
+// proto_num is the version number of protocol; 1.1 = 1001
+void ApacheHeaderToMetaData(const apr_table_t* apache_headers,
+ int status_code,
+ int proto_num,
+ MetaData* meta_data);
+
+// Converts MetaData structure into an Apache header.
+//
+// proto_num is the version number of protocol; 1.1 = 1001
+void MetaDataToApacheHeader(const MetaData& meta_data,
+ apr_table_t* apache_headers,
+ int* status_code,
+ int* proto_num);
+
+// Examines a cache-control string and updates the output headers in
+// request to match it. If the response is cacheable, then
+// we assume it's cacheable forever (via cache extension, and
+// so we set an etag and a matching Expires header.
+//
+// If the content is not cacheable, then we instead clear the etag,
+// last-modified, and expires, in addition to setting the cache-control
+// as specified.
+void SetCacheControl(const char* cache_control, request_rec* request);
+
+// Like SetCacheControl but only updates the other headers, does not
+// set the cache-control header itself. Call this if cache-control is
+// already set and you wish to make the other headers consistent.
+void UpdateCacheHeaders(const char* cache_control, request_rec* request);
+
+// mod_headers typically runs after mod_pagespeed and borks its headers.
+// So we must insert a late filter to unbork the headers.
+void SetupCacheRepair(const char* cache_control, request_rec* request);
+
+// This is called from the late-running filter to unbork the headers.
+void RepairCachingHeaders(request_rec* request);
+
+// Debug utility for printing Apache headers to stdout
+void PrintHeaders(request_rec* request);
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_APACHE_HEADER_UTIL_H_
diff --git a/trunk/src/net/instaweb/apache/instaweb_context.cc b/trunk/src/net/instaweb/apache/instaweb_context.cc
new file mode 100644
index 0000000..2ea9e1d
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/instaweb_context.cc
@@ -0,0 +1,213 @@
+// 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: jmarantz@google.com (Joshua Marantz)
+// lsong@google.com (Libo Song)
+
+#include "net/instaweb/apache/instaweb_context.h"
+#include "net/instaweb/apache/header_util.h"
+#include "net/instaweb/util/public/content_type.h"
+#include "net/instaweb/util/public/gzip_inflater.h"
+#include "net/instaweb/util/stack_buffer.h"
+#include "http_config.h"
+
+extern "C" {
+extern module AP_MODULE_DECLARE_DATA pagespeed_module;
+}
+
+namespace net_instaweb {
+
+const char InstawebContext::kRepairHeadersFilterName[] =
+ "MOD_PAGESPEED_REPAIR_HEADERS";
+
+InstawebContext::InstawebContext(request_rec* request,
+ const ContentType& content_type,
+ ApacheRewriteDriverFactory* factory,
+ const std::string& absolute_url,
+ bool use_custom_options,
+ const RewriteOptions& custom_options)
+ : content_encoding_(kNone),
+ content_type_(content_type),
+ factory_(factory),
+ string_writer_(&output_),
+ inflater_(NULL),
+ content_detection_state_(kStart),
+ absolute_url_(absolute_url) {
+ if (use_custom_options) {
+ // TODO(jmarantz): this is a temporary hack until we sort out better
+ // memory management of RewriteOptions. This will drag on performance.
+ // We need to do this because we are changing RewriteDriver to keep
+ // a reference to its options throughout its lifetime to refer to the
+ // domain lawyer and other options.
+ rewrite_options_.CopyFrom(custom_options);
+ custom_rewriter_.reset(factory->NewCustomRewriteDriver(rewrite_options_));
+ rewrite_driver_ = custom_rewriter_.get();
+ } else {
+ rewrite_driver_ = factory->NewRewriteDriver();
+ }
+
+ ComputeContentEncoding(request);
+ apr_pool_cleanup_register(request->pool, this, Cleanup,
+ apr_pool_cleanup_null);
+
+ bucket_brigade_ = apr_brigade_create(request->pool,
+ request->connection->bucket_alloc);
+
+ if (content_encoding_ == kGzip || content_encoding_ == kDeflate) {
+ // TODO(jmarantz): consider keeping a pool of these if they are expensive
+ // to initialize.
+ if (content_encoding_ == kGzip) {
+ inflater_.reset(new GzipInflater(GzipInflater::kGzip));
+ } else {
+ inflater_.reset(new GzipInflater(GzipInflater::kDeflate));
+ }
+ inflater_->Init();
+ }
+
+
+ const char* user_agent = apr_table_get(request->headers_in,
+ HttpAttributes::kUserAgent);
+ rewrite_driver_->SetUserAgent(user_agent);
+ // TODO(lsong): Bypass the string buffer, writer data directly to the next
+ // apache bucket.
+ rewrite_driver_->SetWriter(&string_writer_);
+}
+
+InstawebContext::~InstawebContext() {
+ if (custom_rewriter_ == NULL) {
+ factory_->ReleaseRewriteDriver(rewrite_driver_);
+ }
+}
+
+void InstawebContext::Rewrite(const char* input, int size) {
+ if (inflater_.get() != NULL) {
+ char buf[kStackBufferSize];
+ inflater_->SetInput(input, size);
+ while (inflater_->HasUnconsumedInput()) {
+ int num_inflated_bytes = inflater_->InflateBytes(buf, kStackBufferSize);
+ ProcessBytes(buf, num_inflated_bytes);
+ }
+ } else {
+ ProcessBytes(input, size);
+ }
+}
+
+namespace {
+
+// http://en.wikipedia.org/wiki/Byte_order_mark
+//
+// The byte-order marker sequence will typically appear at the beginning of
+// an HTML or XML file. We probably should be order-sensitive but for now
+// we will just treat all such characters as allowable characters preceding
+// the HTML. Note the use of unsigned char here to avoid sign-extending when
+// comparing to the int constants.
+inline bool IsByteOrderMarkerCharacter(unsigned char c) {
+ return ((c == 0xef) || (c == 0xbb) || (c == 0xbf));
+}
+
+} // namespace
+
+void InstawebContext::ProcessBytes(const char* input, int size) {
+ // Try to figure out whether this looks like HTML or not, if we haven't
+ // figured it out already. We just scan past whitespace for '<'.
+ for (int i = 0; (content_detection_state_ == kStart) && (i < size); ++i) {
+ char c = input[i];
+ if (c == '<') {
+ content_detection_state_ = kHtml;
+ rewrite_driver_->html_parse()->StartParseWithType(absolute_url_,
+ content_type_);
+ } else if (!isspace(c) && !IsByteOrderMarkerCharacter(c)) {
+ // TODO(jmarantz): figure out whether it's possible to remove our
+ // filter from the chain entirely.
+ //
+ // TODO(jmarantz): look for 'gzip' data. We do not expect to see
+ // this if the Content-Encoding header is set upstream of mod_pagespeed,
+ // but we have heard evidence from the field that WordPress plugins and
+ // possibly other modules send compressed data through without that
+ // header.
+ content_detection_state_ = kNotHtml;
+ }
+ }
+
+ switch (content_detection_state_) {
+ case kStart:
+ // Handle the corner where the first buffer of text contains
+ // only whitespace, which we will retain for the next call.
+ buffer_.append(input, size);
+ break;
+
+ case kHtml:
+ // Looks like HTML: send it through the HTML rewriter.
+ if (!buffer_.empty()) {
+ rewrite_driver_->html_parse()->ParseText(
+ buffer_.data(), buffer_.size());
+ buffer_.clear();
+ }
+ rewrite_driver_->html_parse()->ParseText(input, size);
+ break;
+
+ case kNotHtml:
+ // Looks like something that's not HTML. Send it directly to the
+ // output buffer.
+ output_.append(buffer_.data(), buffer_.size());
+ buffer_.clear();
+ output_.append(input, size);
+ break;
+ }
+}
+
+apr_status_t InstawebContext::Cleanup(void* object) {
+ InstawebContext* ic = static_cast<InstawebContext*>(object);
+ delete ic;
+ return APR_SUCCESS;
+}
+
+void InstawebContext::ComputeContentEncoding(request_rec* request) {
+ // Check if the content is gzipped. Steal from mod_deflate.
+ const char* encoding = apr_table_get(
+ request->headers_out, HttpAttributes::kContentEncoding);
+ if (encoding) {
+ const char* err_enc = apr_table_get(request->err_headers_out,
+ HttpAttributes::kContentEncoding);
+ if (err_enc) {
+ // We don't properly handle stacked encodings now.
+ content_encoding_ = kOther;
+ }
+ } else {
+ encoding = apr_table_get(request->err_headers_out,
+ HttpAttributes::kContentEncoding);
+ }
+
+ if (encoding) {
+ if (strcasecmp(encoding, HttpAttributes::kGzip) == 0) {
+ content_encoding_ = kGzip;
+ } else if (strcasecmp(encoding, HttpAttributes::kDeflate) == 0) {
+ content_encoding_ = kDeflate;
+ } else {
+ content_encoding_ = kOther;
+ }
+ }
+
+ // Copy the output headers coming into our own filter into response_headers_.
+ // This is purely for debugging context.
+ ApacheHeaderToMetaData(request->headers_out, request->status,
+ request->proto_num, &response_headers_);
+}
+
+ApacheRewriteDriverFactory* InstawebContext::Factory(server_rec* server) {
+ return static_cast<ApacheRewriteDriverFactory*>
+ ap_get_module_config(server->module_config, &pagespeed_module);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/apache/instaweb_context.h b/trunk/src/net/instaweb/apache/instaweb_context.h
new file mode 100644
index 0000000..7727bd8
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/instaweb_context.h
@@ -0,0 +1,105 @@
+// 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: jmarantz@google.com (Joshua Marantz)
+// lsong@google.com (Libo Song)
+
+#ifndef NET_INSTAWEB_APACHE_INSTAWEB_CONTEXT_H_
+#define NET_INSTAWEB_APACHE_INSTAWEB_CONTEXT_H_
+
+#include "base/scoped_ptr.h"
+#include "net/instaweb/apache/apache_rewrite_driver_factory.h"
+#include "net/instaweb/rewriter/public/rewrite_driver.h"
+#include "net/instaweb/util/public/content_type.h"
+#include <string>
+#include "net/instaweb/util/public/string_writer.h"
+
+// The httpd header must be after the
+// apache_rewrite_driver_factory.h. Otherwise, the compiler will
+// complain "strtoul_is_not_a_portable_function_use_strtol_instead".
+#include "httpd.h"
+
+namespace net_instaweb {
+
+class GzipInflater;
+class RewriteOptions;
+
+// We use the following structure to keep the instaweb module context. The
+// rewriter will put the rewritten content into the output string when flushed
+// or finished. We call Flush when we see the FLUSH bucket, and call Finish when
+// we see the EOS bucket.
+class InstawebContext {
+ public:
+ enum ContentEncoding {kNone, kGzip, kDeflate, kOther};
+ enum ContentDetectionState {kStart, kHtml, kNotHtml};
+
+ static const char kRepairHeadersFilterName[];
+
+ InstawebContext(request_rec* request,
+ const ContentType& content_type,
+ net_instaweb::ApacheRewriteDriverFactory* factory,
+ const std::string& base_url,
+ bool use_custom_options,
+ const RewriteOptions& custom_options);
+ ~InstawebContext();
+
+ void Rewrite(const char* input, int size);
+ void Flush() {
+ if (content_detection_state_ == kHtml) {
+ rewrite_driver_->html_parse()->Flush();
+ }
+ }
+ void Finish() {
+ if (content_detection_state_ == kHtml) {
+ rewrite_driver_->html_parse()->FinishParse();
+ }
+ }
+ bool empty() const { return output_.empty(); }
+ apr_bucket_brigade* bucket_brigade() const { return bucket_brigade_; }
+ const std::string& output() { return output_; }
+ void clear() { output_.clear(); } // TODO(jmarantz): needed?
+ ContentEncoding content_encoding() const { return content_encoding_; }
+
+ // Looks up the factory from the server rec.
+ // TODO(jmarantz): Is there a better place to put this? It needs to
+ // be used by both mod_instaweb.cc and instaweb_handler.cc.
+ static ApacheRewriteDriverFactory* Factory(server_rec* server);
+
+ private:
+ void ComputeContentEncoding(request_rec* request);
+ void ProcessBytes(const char* input, int size);
+ static apr_status_t Cleanup(void* object);
+
+ std::string output_; // content after instaweb rewritten.
+ apr_bucket_brigade* bucket_brigade_;
+ ContentEncoding content_encoding_;
+ const ContentType content_type_;
+
+ net_instaweb::ApacheRewriteDriverFactory* factory_;
+ net_instaweb::RewriteDriver* rewrite_driver_;
+ net_instaweb::StringWriter string_writer_;
+ scoped_ptr<GzipInflater> inflater_;
+ scoped_ptr<RewriteDriver> custom_rewriter_;
+ std::string buffer_;
+ SimpleMetaData response_headers_;
+ ContentDetectionState content_detection_state_;
+ std::string absolute_url_;
+ RewriteOptions rewrite_options_;
+
+ DISALLOW_COPY_AND_ASSIGN(InstawebContext);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_APACHE_INSTAWEB_CONTEXT_H_
diff --git a/trunk/src/net/instaweb/apache/instaweb_handler.cc b/trunk/src/net/instaweb/apache/instaweb_handler.cc
new file mode 100644
index 0000000..9645e8b
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/instaweb_handler.cc
@@ -0,0 +1,263 @@
+// 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: lsong@google.com (Libo Song)
+// jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/apache/instaweb_handler.h"
+
+#include "apr_strings.h"
+#include "base/basictypes.h"
+#include "base/string_util.h"
+#include "net/instaweb/apache/apache_slurp.h"
+#include "net/instaweb/apache/apr_statistics.h"
+#include "net/instaweb/apache/apr_timer.h"
+#include "net/instaweb/apache/header_util.h"
+#include "net/instaweb/apache/instaweb_context.h"
+#include "net/instaweb/apache/serf_async_callback.h"
+#include "net/instaweb/apache/serf_url_async_fetcher.h"
+#include "net/instaweb/apache/mod_instaweb.h"
+#include "net/instaweb/rewriter/public/add_instrumentation_filter.h"
+#include "net/instaweb/rewriter/public/rewrite_driver.h"
+#include "net/instaweb/util/public/google_message_handler.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/string_writer.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_protocol.h"
+
+namespace net_instaweb {
+
+namespace {
+
+bool IsCompressibleContentType(const char* content_type) {
+ if (content_type == NULL) {
+ return false;
+ }
+ std::string type = content_type;
+ size_t separator_idx = type.find(";");
+ if (separator_idx != std::string::npos) {
+ type.erase(separator_idx);
+ }
+
+ bool res = false;
+ if (type.find("text/") == 0) {
+ res = true;
+ } else if (type.find("application/") == 0) {
+ if (type.find("javascript") != type.npos ||
+ type.find("json") != type.npos ||
+ type.find("ecmascript") != type.npos ||
+ type == "application/livescript" ||
+ type == "application/js" ||
+ type == "application/jscript" ||
+ type == "application/x-js" ||
+ type == "application/xhtml+xml" ||
+ type == "application/xml") {
+ res = true;
+ }
+ }
+
+ return res;
+}
+
+// Default handler when the file is not found
+void instaweb_default_handler(const std::string& url, request_rec* request) {
+ request->status = HTTP_NOT_FOUND;
+ ap_set_content_type(request, "text/html; charset=utf-8");
+ ap_rputs("<html><head><title>Not Found</title></head>", request);
+ ap_rputs("<body><h1>Apache server with mod_pagespeed</h1>OK", request);
+ ap_rputs("<hr>NOT FOUND:", request);
+ ap_rputs(url.c_str(), request);
+ ap_rputs("</body></html>", request);
+}
+
+// predeclare to minimize diffs for now. TODO(jmarantz): reorder
+void send_out_headers_and_body(
+ request_rec* request,
+ const SimpleMetaData& response_headers,
+ const std::string& output);
+
+// Determines whether the url can be handled as a mod_pagespeed resource,
+// and handles it, returning true. A 'true' routine means that this
+// method believed the URL was a mod_pagespeed resource -- it does not
+// imply that it was handled successfully. That information will be
+// in the status code in the response headers.
+bool handle_as_resource(ApacheRewriteDriverFactory* factory,
+ request_rec* request,
+ const std::string& url) {
+ RewriteDriver* rewrite_driver = factory->NewRewriteDriver();
+
+ SimpleMetaData request_headers, response_headers;
+ int n = arraysize(RewriteDriver::kPassThroughRequestAttributes);
+ for (int i = 0; i < n; ++i) {
+ const char* value = apr_table_get(
+ request->headers_in,
+ RewriteDriver::kPassThroughRequestAttributes[i]);
+ if (value != NULL) {
+ request_headers.Add(RewriteDriver::kPassThroughRequestAttributes[i],
+ value);
+ }
+ }
+ std::string output; // TODO(jmarantz): quit buffering resource output
+ StringWriter writer(&output);
+ MessageHandler* message_handler = factory->message_handler();
+ SerfAsyncCallback* callback = new SerfAsyncCallback(
+ &response_headers, &writer);
+ bool handled = rewrite_driver->FetchResource(
+ url, request_headers, callback->response_headers(), callback->writer(),
+ message_handler, callback);
+ if (handled) {
+ message_handler->Message(kInfo, "Fetching resource %s...", url.c_str());
+ if (!callback->done()) {
+ SerfUrlAsyncFetcher* serf_async_fetcher =
+ factory->serf_url_async_fetcher();
+ AprTimer timer;
+ int64 max_ms = factory->fetcher_time_out_ms();
+ for (int64 start_ms = timer.NowMs(), now_ms = start_ms;
+ !callback->done() && now_ms - start_ms < max_ms;
+ now_ms = timer.NowMs()) {
+ int64 remaining_us = max_ms - (now_ms - start_ms);
+ serf_async_fetcher->Poll(remaining_us);
+ }
+
+ if (!callback->done()) {
+ message_handler->Message(kError, "Timeout on url %s", url.c_str());
+ }
+ }
+ if (callback->success()) {
+ message_handler->Message(kInfo, "Fetch succeeded for %s, status=%d",
+ url.c_str(), response_headers.status_code());
+ send_out_headers_and_body(request, response_headers, output);
+ } else {
+ message_handler->Message(kError, "Fetch failed for %s, status=%d",
+ url.c_str(), response_headers.status_code());
+ factory->Increment404Count();
+ instaweb_default_handler(url, request);
+ }
+ } else {
+ callback->Done(false);
+ }
+ callback->Release();
+ factory->ReleaseRewriteDriver(rewrite_driver);
+ return handled;
+}
+
+void send_out_headers_and_body(
+ request_rec* request,
+ const SimpleMetaData& response_headers,
+ const std::string& output) {
+ if (response_headers.status_code() != 0) {
+ request->status = response_headers.status_code();
+ }
+ for (int idx = 0; idx < response_headers.NumAttributes(); ++idx) {
+ const char* name = response_headers.Name(idx);
+ const char* value = response_headers.Value(idx);
+ if (strcasecmp(name, HttpAttributes::kContentType) == 0) {
+ // ap_set_content_type does not make a copy of the string, we need
+ // to duplicate it.
+ char* ptr = apr_pstrdup(request->pool, value);
+ ap_set_content_type(request, ptr);
+ } else {
+ if (strcasecmp(name, HttpAttributes::kCacheControl) == 0) {
+ SetupCacheRepair(value, request);
+ }
+ // apr_table_add makes copies of both head key and value, so we do not
+ // have to duplicate them.
+ apr_table_add(request->headers_out, name, value);
+ }
+ }
+ if (response_headers.status_code() == HttpStatus::kOK &&
+ IsCompressibleContentType(request->content_type)) {
+ // Make sure compression is enabled for this response.
+ ap_add_output_filter("DEFLATE", NULL, request, request->connection);
+ }
+
+ // Recompute the content-length, because the content may have changed.
+ ap_set_content_length(request, output.size());
+ // Send the body
+ ap_rwrite(output.c_str(), output.size(), request);
+}
+
+} // namespace
+
+apr_status_t repair_caching_header(ap_filter_t *filter,
+ apr_bucket_brigade *bb) {
+ request_rec* request = filter->r;
+ RepairCachingHeaders(request);
+ ap_remove_output_filter(filter);
+ return ap_pass_brigade(filter->next, bb);
+}
+
+int instaweb_handler(request_rec* request) {
+ ApacheRewriteDriverFactory* factory =
+ InstawebContext::Factory(request->server);
+ int ret = OK;
+
+ // Only handle GET request
+ if (request->method_number != M_GET) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, request,
+ "Not GET request: %d.", request->method_number);
+ ret = DECLINED;
+ } else if (strcmp(request->handler, "mod_pagespeed_statistics") == 0) {
+ std::string output;
+ SimpleMetaData response_headers;
+ StringWriter writer(&output);
+ AprStatistics* statistics = factory->statistics();
+ if (statistics) {
+ statistics->Dump(&writer, factory->message_handler());
+ }
+ send_out_headers_and_body(request, response_headers, output);
+ } else if (strcmp(request->handler, "mod_pagespeed_beacon") == 0) {
+ RewriteDriver* driver = factory->NewRewriteDriver();
+ AddInstrumentationFilter* aif = driver->add_instrumentation_filter();
+ if (aif && aif->HandleBeacon(request->unparsed_uri)) {
+ ret = HTTP_NO_CONTENT;
+ } else {
+ ret = DECLINED;
+ }
+ factory->ReleaseRewriteDriver(driver);
+ } else {
+ /*
+ * In some contexts we are seeing relative URLs passed
+ * into request->unparsed_uri. But when using mod_slurp, the rewritten
+ * HTML contains complete URLs, so this construction yields the host:port
+ * prefix twice.
+ *
+ * TODO(jmarantz): Figure out how to do this correctly at all times.
+ */
+ std::string url;
+ if (strncmp(request->unparsed_uri, "http://", 7) == 0) {
+ url = request->unparsed_uri;
+ } else {
+ url = ap_construct_url(request->pool, request->unparsed_uri, request);
+ }
+
+ if (!handle_as_resource(factory, request, url)) {
+ if (factory->slurping_enabled()) {
+ SlurpUrl(url, factory, request);
+ if (request->status == HTTP_NOT_FOUND) {
+ factory->IncrementSlurpCount();
+ }
+ } else {
+ ret = DECLINED;
+ }
+ }
+ }
+ return ret;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/apache/instaweb_handler.h b/trunk/src/net/instaweb/apache/instaweb_handler.h
new file mode 100644
index 0000000..f364f82
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/instaweb_handler.h
@@ -0,0 +1,41 @@
+// 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.
+
+#ifndef MOD_INSTAWEB_INSTAWEB_HANDLER_H_
+#define MOD_INSTAWEB_INSTAWEB_HANDLER_H_
+
+// The httpd header must be after the instaweb_context.h. Otherwise,
+// the compiler will complain
+// "strtoul_is_not_a_portable_function_use_strtol_instead".
+#include "net/instaweb/apache/instaweb_context.h"
+#include "httpd.h" // NOLINT
+
+namespace net_instaweb {
+
+// The content generator for instaweb generated content, for example, the
+// combined CSS file. Requests for not-instab generated content will be
+// declined so that other Apache handlers may operate on them.
+int instaweb_handler(request_rec* request);
+
+// output-filter function to repair caching headers, which might have
+// been altered by a directive like:
+//
+// <FilesMatch "\.(jpg|jpeg|gif|png|js|css)$">
+// Header set Cache-control "public, max-age=600"
+// </FilesMatch>
+apr_status_t repair_caching_header(ap_filter_t *filter, apr_bucket_brigade *bb);
+
+} // namespace net_instaweb
+
+#endif // MOD_INSTAWEB_INSTAWEB_HANDLER_H_
diff --git a/trunk/src/net/instaweb/apache/log_message_handler.cc b/trunk/src/net/instaweb/apache/log_message_handler.cc
new file mode 100644
index 0000000..38a9d4a
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/log_message_handler.cc
@@ -0,0 +1,132 @@
+// 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.
+
+#include "net/instaweb/apache/log_message_handler.h"
+
+#include <limits>
+#include <string>
+#include "base/debug_util.h"
+#include "base/logging.h"
+#include "httpd.h"
+
+// When HAVE_SYSLOG is defined, apache http_log.h will include syslog.h, which
+// #defined LOG_* as numbers. This conflicts with what we are using those here.
+#undef HAVE_SYSLOG
+#include "http_log.h"
+
+// Make sure we don't attempt to use LOG macros here, since doing so
+// would cause us to go into an infinite log loop.
+#undef LOG
+#define LOG USING_LOG_HERE_WOULD_CAUSE_INFINITE_RECURSION
+
+namespace {
+
+apr_pool_t* log_pool = NULL;
+
+const int kMaxInt = std::numeric_limits<int>::max();
+int log_level_cutoff = kMaxInt;
+std::string* mod_pagespeed_version = NULL;
+
+int GetApacheLogLevel(int severity) {
+ switch (severity) {
+ case logging::LOG_INFO:
+ return APLOG_NOTICE;
+ case logging::LOG_WARNING:
+ return APLOG_WARNING;
+ case logging::LOG_ERROR:
+ return APLOG_ERR;
+ case logging::LOG_ERROR_REPORT:
+ return APLOG_CRIT;
+ case logging::LOG_FATAL:
+ return APLOG_ALERT;
+ default:
+ return APLOG_NOTICE;
+ }
+}
+
+bool LogMessageHandler(int severity, const std::string& str) {
+ const int this_log_level = GetApacheLogLevel(severity);
+
+ std::string message = str;
+ if (severity == logging::LOG_FATAL) {
+ if (DebugUtil::BeingDebugged()) {
+ DebugUtil::BreakDebugger();
+ } else {
+ StackTrace trace;
+ std::ostringstream stream;
+ trace.OutputToStream(&stream);
+ message.append(stream.str());
+ }
+ }
+
+ // Trim the newline off the end of the message string.
+ size_t last_msg_character_index = message.length() - 1;
+ if (message[last_msg_character_index] == '\n') {
+ message.resize(last_msg_character_index);
+ }
+
+ if (this_log_level <= log_level_cutoff || log_level_cutoff == kMaxInt) {
+ ap_log_perror(APLOG_MARK, this_log_level, APR_SUCCESS, log_pool,
+ "[mod_pagespeed %s] %s",
+ (mod_pagespeed_version == NULL)
+ ? ""
+ : mod_pagespeed_version->c_str(),
+ message.c_str());
+ }
+
+ if (severity == logging::LOG_FATAL) {
+ // Crash the process to generate a dump.
+ DebugUtil::BreakDebugger();
+ }
+
+ return true;
+}
+
+} // namespace
+
+
+namespace net_instaweb {
+
+namespace log_message_handler {
+
+void Install(apr_pool_t* pool) {
+ log_pool = pool;
+ logging::SetLogMessageHandler(&LogMessageHandler);
+}
+
+void ShutDown() {
+ if (mod_pagespeed_version != NULL) {
+ delete mod_pagespeed_version;
+ mod_pagespeed_version = NULL;
+ }
+}
+
+// TODO(sligocki): This is not thread-safe, do we care? Error case is when
+// you have multiple server_rec's with different LogLevels which start-up
+// simultaniously. In which case we might get a non-min global LogLevel
+void AddServerConfig(const server_rec* server, const StringPiece& version) {
+ // TODO(sligocki): Maybe use ap_log_error(server) if there is exactly one
+ // server added?
+ log_level_cutoff = std::min(server->loglevel, log_level_cutoff);
+ if (mod_pagespeed_version == NULL) {
+ mod_pagespeed_version = new std::string(version.as_string());
+ } else {
+ *mod_pagespeed_version = version.as_string();
+ }
+}
+
+
+} // namespace log_message_handler
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/apache/log_message_handler.h b/trunk/src/net/instaweb/apache/log_message_handler.h
new file mode 100644
index 0000000..92cb671
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/log_message_handler.h
@@ -0,0 +1,46 @@
+// 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.
+
+#ifndef NET_INSTAWEB_APACHE_LOG_MESSAGE_HANDLER_H_
+#define NET_INSTAWEB_APACHE_LOG_MESSAGE_HANDLER_H_
+
+#include <algorithm> // for std::min
+#include "apr_pools.h"
+
+#include "net/instaweb/util/public/string_util.h"
+
+struct server_rec;
+
+namespace net_instaweb {
+
+namespace log_message_handler {
+
+// Install a log message handler that routes LOG() messages to the
+// apache error log. Should be called once at startup.
+void Install(apr_pool_t* pool);
+
+// The log_message_handler is not attached to a specific server_rec, so the
+// LogLevel is not automatically set for it. Every server_rec instance
+// should call AddServerConfig and let us decide what level to log at.
+// Currently we set it to the min LogLevel.
+void AddServerConfig(const server_rec* server, const StringPiece& version);
+
+// Free the memory from the log message handler
+void ShutDown();
+
+} // namespace log_message_handler
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_APACHE_LOG_MESSAGE_HANDLER_H_
diff --git a/trunk/src/net/instaweb/apache/mem_clean_up.cc b/trunk/src/net/instaweb/apache/mem_clean_up.cc
new file mode 100644
index 0000000..a3dadcc
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/mem_clean_up.cc
@@ -0,0 +1,39 @@
+// 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: jmarantz@google.com (Joshua Marantz)
+
+#include "googleurl/src/url_util.h"
+#include "net/instaweb/htmlparse/public/html_escape.h"
+#include "net/instaweb/rewriter/public/css_filter.h"
+#include "third_party/protobuf2/src/src/google/protobuf/stubs/common.h"
+
+// Clean up valgrind-based memory-leak checks by deleting statically allocated
+// data from various libraries. This must be called both from unit-tests
+// and from the Apache module, so that valgrind can be run on both of them.
+
+namespace {
+
+class MemCleanUp {
+ public:
+ ~MemCleanUp() {
+ net_instaweb::CssFilter::Terminate();
+ net_instaweb::HtmlEscape::ShutDown();
+ google::protobuf::ShutdownProtobufLibrary();
+ url_util::Shutdown();
+ }
+};
+MemCleanUp mem_clean_up;
+
+} // namespace
diff --git a/trunk/src/net/instaweb/apache/mem_debug.cc b/trunk/src/net/instaweb/apache/mem_debug.cc
new file mode 100644
index 0000000..a14ccf9
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/mem_debug.cc
@@ -0,0 +1,144 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+// Very simple memory debugging overrides for operator new/delete, to
+// help us quickly find simple memory violations:
+// 1. Double destruct
+// 2. Read before write (via scribbling)
+// 3. Read after delete (via scribbling)
+//
+// Note that valgrind does all of this much better, but is too slow to
+// run all the time, and it's not obvious how well it works when
+// Apache is forking processes that do all the interesting work. However,
+// if this code helps find a problem then you can run valgrind with Apache
+// in -X mode and that will be a better debug tool.
+//
+// Principle of operation: add 8 bytes to every allocation. The first
+// 4 bytes are a marker (kLiveMarker or kDeadMarker1). The next 4
+// bytes are used to store size of the allocation, which helps us
+// know how many bytes to scribble when we free.
+//
+// TODO(jmarantz): consider integrating a richer memory debug library such
+// as dmalloc, efence, or the heap-checking features of tcmalloc.
+//
+// Note: this memory debugging will interfere with Valgrind's ability to
+// detect read-before-write errors, and hence should be disabled if you
+// want to run with valgrind. This can be detected automatically using
+// macros from valgrind.h, but then that would *require* valgrind be installed
+// before building mod_pagespeed. See
+//
+// http://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq
+
+// TODO(jmarantz): consider controlling inclusion of this module in the gyp
+// files rather than as an ifdef.
+//
+// Note: to be assured that these overrides are included in Debug builds but
+// are not included in Release builds, type:
+// nm out/Debug/libmod_pagespeed.so | /usr/bin/c++filt |grep 'operator new'
+// nm out/Release/libmod_pagespeed.so | /usr/bin/c++filt |grep 'operator new'
+#ifndef NDEBUG
+
+#include <stdlib.h>
+#include "base/basictypes.h"
+#include "base/logging.h"
+
+namespace {
+
+const int32 kLiveMarker = 0xfeedface; // first 4 bytes after alloc
+const int32 kDeadMarker1 = 0xabacabff; // first 4 bytes after free
+const int32 kDeadMarker2 = 0xdeadbeef; // overwrites the 'size' field on free
+const size_t kOverhead = 2 * sizeof(int32); // number of extra bytes to alloc
+
+size_t rounded_size(size_t size) {
+ if (size == 0) {
+ size = kOverhead;
+ } else if ((size % kOverhead) != 0) {
+ size = size + kOverhead - (size % kOverhead);
+ }
+ return size;
+}
+
+void scribble(void* ptr, size_t size, int32 scribble_word) {
+ CHECK_EQ(0U, size % sizeof(int32));
+ int num_ints = size / sizeof(int32);
+ int* p = static_cast<int*>(ptr);
+ for (int i = 0; i < num_ints; ++i, ++p) {
+ *p = scribble_word;
+ }
+}
+
+void* debug_malloc(size_t size) {
+ size_t rounded = rounded_size(size);
+ int32* marker = static_cast<int*>(malloc(rounded + kOverhead));
+ CHECK(marker != NULL);
+ marker[0] = kLiveMarker;
+ marker[1] = size;
+ int32* ret = marker + 2;
+ scribble(ret, rounded, kLiveMarker);
+ return reinterpret_cast<char*>(marker) + kOverhead;
+}
+
+void debug_free(void* ptr) {
+ if (ptr != NULL) {
+ char* alloced_ptr = static_cast<char*>(ptr) - kOverhead;
+ int32* marker = reinterpret_cast<int32*>(alloced_ptr);
+ scribble(ptr, rounded_size(marker[1]), kDeadMarker2);
+ CHECK_EQ(kLiveMarker, marker[0]);
+ marker[0] = kDeadMarker1;
+ marker[1] = kDeadMarker2;
+ free(marker);
+ }
+}
+
+} // namespace
+
+// C++ operator new/delete overrides.
+
+void* operator new(size_t size) throw (std::bad_alloc) {
+ return debug_malloc(size);
+}
+
+void* operator new(size_t size, const std::nothrow_t&) __THROW {
+ return debug_malloc(size);
+}
+
+void operator delete(void* ptr) __THROW {
+ debug_free(ptr);
+}
+
+void operator delete(void* ptr, const std::nothrow_t&) __THROW {
+ debug_free(ptr);
+}
+
+void* operator new[](size_t size) throw (std::bad_alloc) {
+ return debug_malloc(size);
+}
+
+void* operator new[](size_t size, const std::nothrow_t&) __THROW {
+ return debug_malloc(size);
+}
+
+void operator delete[](void* ptr) __THROW {
+ debug_free(ptr);
+}
+
+void operator delete[](void* ptr, const std::nothrow_t&) __THROW {
+ debug_free(ptr);
+}
+
+#endif // !NDEBUG
diff --git a/trunk/src/net/instaweb/apache/mod_instaweb.cc b/trunk/src/net/instaweb/apache/mod_instaweb.cc
new file mode 100644
index 0000000..53ca303
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/mod_instaweb.cc
@@ -0,0 +1,1101 @@
+// 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.
+
+#include <set>
+#include <string>
+
+#include "apr_strings.h"
+#include "apr_timer.h"
+#include "apr_version.h"
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/apache/apache_config.h"
+#include "net/instaweb/apache/header_util.h"
+#include "net/instaweb/apache/log_message_handler.h"
+#include "net/instaweb/apache/serf_url_async_fetcher.h"
+#include "net/instaweb/apache/instaweb_context.h"
+#include "net/instaweb/apache/instaweb_handler.h"
+#include "net/instaweb/apache/mod_instaweb.h"
+#include "net/instaweb/apache/apache_rewrite_driver_factory.h"
+#include "net/instaweb/apache/apr_statistics.h"
+#include "net/instaweb/public/version.h"
+#include "net/instaweb/public/global_constants.h"
+#include "net/instaweb/rewriter/public/rewrite_driver.h"
+#include "net/instaweb/rewriter/public/rewrite_options.h"
+#include "net/instaweb/util/public/content_type.h"
+#include "net/instaweb/util/public/google_message_handler.h"
+#include "net/instaweb/util/public/google_url.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include "net/instaweb/util/public/query_params.h"
+#include "net/instaweb/util/public/stl_util.h"
+#include "net/instaweb/util/public/string_util.h"
+
+// Note: a very useful reference is this file, which demos many Apache module
+// options:
+// http://svn.apache.org/repos/asf/httpd/httpd/trunk/modules/examples/mod_example_hooks.c
+
+// The httpd header must be after the pagepseed_server_context.h. Otherwise,
+// the compiler will complain
+// "strtoul_is_not_a_portable_function_use_strtol_instead".
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+// When HAVE_SYSLOG, syslog.h is included and #defined LOG_*, which conflicts
+// with log_message_handler.
+#undef HAVE_SYSLOG
+#include "http_log.h"
+#include "http_protocol.h"
+#if USE_FIXUP_HOOK
+#include "http_request.h" // NOLINT
+#endif
+
+extern "C" {
+ extern module AP_MODULE_DECLARE_DATA pagespeed_module;
+}
+
+namespace net_instaweb {
+
+namespace {
+
+// Instaweb directive names -- these must match
+// install/common/pagespeed.conf.template.
+const char* kModPagespeed = "ModPagespeed";
+const char* kModPagespeedUrlPrefix = "ModPagespeedUrlPrefix";
+const char* kModPagespeedFetchProxy = "ModPagespeedFetchProxy";
+const char* kModPagespeedGeneratedFilePrefix =
+ "ModPagespeedGeneratedFilePrefix";
+const char* kModPagespeedFileCachePath = "ModPagespeedFileCachePath";
+const char* kModPagespeedFileCacheSizeKb = "ModPagespeedFileCacheSizeKb";
+const char* kModPagespeedFileCacheCleanIntervalMs
+ = "ModPagespeedFileCacheCleanIntervalMs";
+const char* kModPagespeedLRUCacheKbPerProcess =
+ "ModPagespeedLRUCacheKbPerProcess";
+const char* kModPagespeedLRUCacheByteLimit = "ModPagespeedLRUCacheByteLimit";
+const char* kModPagespeedFetcherTimeoutMs = "ModPagespeedFetcherTimeOutMs";
+const char* kModPagespeedNumShards = "ModPagespeedNumShards";
+const char* kModPagespeedCssOutlineMinBytes = "ModPagespeedCssOutlineMinBytes";
+const char* kModPagespeedJsOutlineMinBytes = "ModPagespeedJsOutlineMinBytes";
+const char* kModPagespeedFilters = "ModPagespeedFilters";
+const char* kModPagespeedRewriteLevel = "ModPagespeedRewriteLevel";
+const char* kModPagespeedEnableFilters = "ModPagespeedEnableFilters";
+const char* kModPagespeedDisableFilters = "ModPagespeedDisableFilters";
+const char* kModPagespeedSlurpDirectory = "ModPagespeedSlurpDirectory";
+const char* kModPagespeedSlurpReadOnly = "ModPagespeedSlurpReadOnly";
+const char* kModPagespeedSlurpFlushLimit = "ModPagespeedSlurpFlushLimit";
+const char* kModPagespeedForceCaching = "ModPagespeedForceCaching";
+const char* kModPagespeedCssInlineMaxBytes = "ModPagespeedCssInlineMaxBytes";
+const char* kModPagespeedImgInlineMaxBytes = "ModPagespeedImgInlineMaxBytes";
+const char* kModPagespeedImgMaxRewritesAtOnce =
+ "ModPagespeedImgMaxRewritesAtOnce";
+const char* kModPagespeedJsInlineMaxBytes = "ModPagespeedJsInlineMaxBytes";
+const char* kModPagespeedDomain = "ModPagespeedDomain";
+const char* kModPagespeedMapRewriteDomain = "ModPagespeedMapRewriteDomain";
+const char* kModPagespeedMapOriginDomain = "ModPagespeedMapOriginDomain";
+const char* kModPagespeedFilterName = "MOD_PAGESPEED_OUTPUT_FILTER";
+const char* kModPagespeedBeaconUrl = "ModPagespeedBeaconUrl";
+const char* kModPagespeedAllow = "ModPagespeedAllow";
+const char* kModPagespeedDisallow = "ModPagespeedDisallow";
+const char* kModPagespeedStatistics = "ModPagespeedStatistics";
+
+// TODO(jmarantz): determine the version-number from SVN at build time.
+const char kModPagespeedVersion[] = MOD_PAGESPEED_VERSION_STRING "-"
+ LASTCHANGE_STRING;
+
+enum RewriteOperation {REWRITE, FLUSH, FINISH};
+enum ConfigSwitch {CONFIG_ON, CONFIG_OFF, CONFIG_ERROR};
+
+// Check if pagespeed optimization rules applicable.
+bool check_pagespeed_applicable(request_rec* request,
+ const ContentType& content_type) {
+ // We can't operate on Content-Ranges.
+ if (apr_table_get(request->headers_out, "Content-Range") != NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, request,
+ "Content-Range is not available");
+ return false;
+ }
+
+ // Only rewrite HTML-like content.
+ if (!content_type.IsHtmlLike()) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, request,
+ "Content-Type=%s Host=%s Uri=%s",
+ request->content_type, request->hostname,
+ request->unparsed_uri);
+ return false;
+ }
+
+ // mod_pagespeed often creates requests while rewriting an HTML. These
+ // requests are only intended to fetch resources (images, css, javascript) but
+ // in some circumstances they can end up fetching HTML. This HTML, if
+ // rewrittten, could in turn spawn more requests which could cascade into a
+ // bad situation. To mod_pagespeed, any fetched HTML is an error condition,
+ // so there's no reason to rewrite it anyway.
+ const char* user_agent = apr_table_get(request->headers_in,
+ HttpAttributes::kUserAgent);
+ // TODO(abliss): unify this string literal with the one in
+ // serf_url_async_fetcher.cc
+ if ((user_agent != NULL) && strstr(user_agent, "mod_pagespeed")) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, APR_SUCCESS, request,
+ "Not rewriting mod_pagespeed's own fetch");
+ return false;
+ }
+
+ return true;
+}
+
+// Create a new bucket from buf using HtmlRewriter.
+// TODO(lsong): the content is copied multiple times. The buf is
+// copied/processed to string output, then output is copied to new bucket.
+apr_bucket* rewrite_html(InstawebContext* context, request_rec* request,
+ RewriteOperation operation, const char* buf, int len) {
+ if (context == NULL) {
+ LOG(DFATAL) << "Context is null";
+ return NULL;
+ }
+ if (buf != NULL) {
+ context->Rewrite(buf, len);
+ }
+ if (operation == REWRITE) {
+ return NULL;
+ } else if (operation == FLUSH) {
+ context->Flush();
+ } else if (operation == FINISH) {
+ context->Finish();
+ }
+
+ const std::string& output = context->output();
+ if (output.empty()) {
+ return NULL;
+ }
+ // Use the rewritten content. Create in heap since output will
+ // be emptied for reuse.
+ apr_bucket* bucket = apr_bucket_heap_create(
+ output.data(), output.size(), NULL,
+ request->connection->bucket_alloc);
+ context->clear();
+ return bucket;
+}
+
+// To support query-specific rewriter sets, scan the query parameters to
+// see whether we have any options we want to set. We will only allow
+// a limited number of options to be set. In particular, some options are
+// risky to set per query, such as image inline threshold, which exposes
+// a DOS vulnerability and a risk of poisoning our internal cache. Domain
+// adjustments can potentially introduce a security vulnerability.
+//
+// So we will check for explicit parameters we want to support.
+bool ScanQueryParamsForRewriterOptions(RewriteDriverFactory* factory,
+ const QueryParams& query_params,
+ RewriteOptions* options) {
+ MessageHandler* handler = factory->message_handler();
+ bool ret = true;
+ int option_count = 0;
+ for (int i = 0; i < query_params.size(); ++i) {
+ const char* name = query_params.name(i);
+ const char* value = query_params.value(i);
+ if (value == NULL) {
+ // Empty; all our options require a value, so skip. It might be a
+ // perfectly legitimate query param for the underlying page.
+ continue;
+ }
+ int64 int_val;
+ if (strcmp(name, kModPagespeed) == 0) {
+ bool is_on = (strcmp(value, "on") == 0);
+ if (is_on || (strcmp(value, "off") == 0)) {
+ options->set_enabled(is_on);
+ ++option_count;
+ } else {
+ // TODO(sligocki): Return 404s instead of logging server errors here
+ // and below.
+ handler->Message(kWarning, "Invalid value for %s: %s "
+ "(should be on or off)", name, value);
+ ret = false;
+ }
+ } else if (strcmp(name, kModPagespeedFilters) == 0) {
+ // When using ModPagespeedFilters query param, only the
+ // specified filters should be enabled.
+ options->SetRewriteLevel(RewriteOptions::kPassThrough);
+ if (options->EnableFiltersByCommaSeparatedList(value, handler)) {
+ ++option_count;
+ } else {
+ handler->Message(kWarning,
+ "Invalid filter name in %s: %s", name, value);
+ ret = false;
+ }
+ // TODO(jmarantz): add js inlinine threshold, outline threshold.
+ } else if (strcmp(name, kModPagespeedCssInlineMaxBytes) == 0) {
+ if (StringToInt64(value, &int_val)) {
+ options->set_css_inline_max_bytes(int_val);
+ ++option_count;
+ } else {
+ handler->Message(kWarning, "Invalid integer value for %s: %s",
+ name, value);
+ ret = false;
+ }
+ }
+ }
+ return ret && (option_count > 0);
+}
+
+// Apache's pool-based cleanup is not effective on process shutdown. To allow
+// valgrind to report clean results, we must take matters into our own hands.
+// We employ a statically allocated class object and rely on its destructor to
+// get a reliable cleanup hook. I am, in general, strongly opposed to this
+// sort of technique, and it violates the C++ Style Guide:
+// http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml
+// However, it is not possible to use valgrind to track memory leaks
+// in our Apache module without this approach.
+//
+// We also need this context hold any data needed for statistics
+// collected in advance of the creation of the Statistics object, such
+// as directives-parsing time.
+class ApacheProcessContext {
+ public:
+ ApacheProcessContext()
+ : merge_time_us_(NULL),
+ parse_time_us_(NULL),
+ html_rewrite_time_us_(NULL),
+ stored_parse_time_us_(0) {
+ }
+
+ ~ApacheProcessContext() {
+ STLDeleteElements(&factories_);
+ STLDeleteElements(&configs_);
+ statistics_.reset(NULL);
+ ApacheRewriteDriverFactory::Terminate();
+ log_message_handler::ShutDown();
+ }
+
+ // Delete the specified factory on process exit.
+ void add_factory(ApacheRewriteDriverFactory* factory) {
+ factories_.insert(factory);
+ }
+
+ // Do not delete the specified factory on process exit -- it
+ // is being deleted on a pool hook.
+ void remove_factory(ApacheRewriteDriverFactory* factory) {
+ factories_.erase(factory);
+ }
+
+ // Delete the specified config on process exit.
+ void add_config(ApacheConfig* config) {
+ configs_.insert(config);
+ }
+
+ // Do not delete the specified config on process exit -- it
+ // is being deleted on a pool hook.
+ void remove_config(ApacheConfig* config) {
+ configs_.erase(config);
+ }
+
+ AprStatistics* InitStatistics(const StringPiece& filename_prefix) {
+ if (statistics_.get() == NULL) {
+ statistics_.reset(new AprStatistics(filename_prefix));
+ RewriteDriverFactory::Initialize(statistics_.get());
+ SerfUrlAsyncFetcher::Initialize(statistics_.get());
+ statistics_->AddVariable("merge_time_us");
+ statistics_->AddVariable("parse_time_us");
+ statistics_->AddVariable("html_rewrite_time_us");
+ statistics_->InitVariables(true);
+ merge_time_us_ = statistics_->GetVariable("merge_time_us");
+ parse_time_us_ = statistics_->GetVariable("parse_time_us");
+ html_rewrite_time_us_ = statistics_->GetVariable("html_rewrite_time_us");
+ parse_time_us_->Add(stored_parse_time_us_);
+ stored_parse_time_us_ = 0;
+ statistics_->InitVariables(true);
+ }
+ return statistics_.get();
+ }
+
+ void AddMergeTimeUs(int64 merge_time_us) {
+ if (statistics_.get() != NULL) {
+ merge_time_us_->Add(merge_time_us);
+ }
+ }
+
+ void AddHtmlRewriteTimeUs(int64 rewrite_time_us) {
+ if (statistics_.get() != NULL) {
+ html_rewrite_time_us_->Add(rewrite_time_us);
+ }
+ }
+
+ // Accumulating the time spent parsing directives requires special handling,
+ // because the parsing of directives precedes the initialization of the
+ // statistics object, which cannot be created until the file_prefix setting
+ // is parsed.
+ //
+ // Thus we need a place to store the accumulated parsing time, so we
+ // store it hera in the ApacheProcessContext, which gets statically
+ // initialized.
+ void AddParseTimeUs(int64 parse_time_us) {
+ if (parse_time_us_ != NULL) {
+ parse_time_us_->Add(parse_time_us);
+ } else {
+ stored_parse_time_us_ += parse_time_us;
+ }
+ }
+
+ std::set<ApacheRewriteDriverFactory*> factories_;
+ std::set<ApacheConfig*> configs_;
+ scoped_ptr<AprStatistics> statistics_;
+ Variable* merge_time_us_;
+ Variable* parse_time_us_;
+ Variable* html_rewrite_time_us_;
+ int64 stored_parse_time_us_;
+};
+ApacheProcessContext apache_process_context;
+
+typedef void (ApacheProcessContext::*AddTimeFn)(int64 delta);
+
+class ScopedTimer {
+ public:
+ explicit ScopedTimer(AddTimeFn add_time_fn)
+ : add_time_fn_(add_time_fn),
+ start_time_us_(timer_.NowUs()) {
+ }
+
+ ~ScopedTimer() {
+ int64 delta_us = timer_.NowUs() - start_time_us_;
+ (apache_process_context.*add_time_fn_)(delta_us);
+ }
+
+ private:
+ AddTimeFn add_time_fn_;
+ AprTimer timer_;
+ int64 start_time_us_;
+};
+
+void MergeOptions(const RewriteOptions& a, const RewriteOptions& b,
+ RewriteOptions* out) {
+ ScopedTimer timer(&ApacheProcessContext::AddMergeTimeUs);
+ out->Merge(a, b);
+}
+
+// Builds a new context for an HTTP request, returning NULL if we decide
+// that we should not handle the request.
+InstawebContext* build_context_for_request(request_rec* request) {
+ ApacheConfig* config = static_cast<ApacheConfig*>
+ ap_get_module_config(request->per_dir_config, &pagespeed_module);
+ ApacheRewriteDriverFactory* factory = InstawebContext::Factory(
+ request->server);
+ scoped_ptr<RewriteOptions> custom_options;
+ const RewriteOptions* options = factory->options();
+ const RewriteOptions* config_options = config->options();
+ bool use_custom_options = false;
+
+ if (config_options->modified()) {
+ custom_options.reset(new RewriteOptions);
+ MergeOptions(*options, *config_options, custom_options.get());
+ options = custom_options.get();
+ use_custom_options = true;
+ }
+
+ if (request->unparsed_uri == NULL) {
+ // TODO(jmarantz): consider adding Debug message if unparsed_uri is NULL,
+ // possibly of request->the_request which was non-null in the case where
+ // I found this in the debugger.
+ return NULL;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, request,
+ "ModPagespeed OutputFilter called for request %s",
+ request->unparsed_uri);
+
+ QueryParams query_params;
+ if (request->parsed_uri.query != NULL) {
+ query_params.Parse(request->parsed_uri.query);
+ }
+
+ const ContentType* content_type =
+ MimeTypeToContentType(request->content_type);
+ if (content_type == NULL) {
+ return NULL;
+ }
+
+ // Check if pagespeed optimization is applicable.
+ if (!check_pagespeed_applicable(request, *content_type)) {
+ return NULL;
+ }
+
+ // Check if mod_instaweb has already rewritten the HTML. If the server is
+ // setup as both the original and the proxy server, mod_pagespeed filter may
+ // be applied twice. To avoid this, skip the content if it is already
+ // optimized by mod_pagespeed.
+ if (apr_table_get(request->headers_out, kModPagespeedHeader) != NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, request,
+ "URL %s already has been processed by mod_pagespeed",
+ request->unparsed_uri);
+ return NULL;
+ }
+
+ // Determine the absolute URL for this request, which might take on different
+ // forms in the request structure depending on whether this request comes
+ // from a browser proxy, or whether mod_proxy is enabled.
+ std::string absolute_url;
+ if (strncmp(request->unparsed_uri, "http://", 7) == 0) {
+ absolute_url = request->unparsed_uri;
+ } else {
+ absolute_url = ap_construct_url(request->pool, request->unparsed_uri,
+ request);
+ }
+ if ((request->filename != NULL) &&
+ (strncmp(request->filename, "proxy:", 6) == 0)) {
+ absolute_url.assign(request->filename + 6, strlen(request->filename) - 6);
+ }
+
+ RewriteOptions query_options;
+ query_options.SetDefaultRewriteLevel(RewriteOptions::kCoreFilters);
+ if (ScanQueryParamsForRewriterOptions(
+ factory, query_params, &query_options)) {
+ use_custom_options = true;
+ RewriteOptions* merged_options = new RewriteOptions;
+ MergeOptions(*options, query_options, merged_options);
+ custom_options.reset(merged_options);
+ options = merged_options;
+ }
+
+ // Is ModPagespeed turned off? We check after parsing query params so that
+ // they can override .conf settings.
+ if (!options->enabled()) {
+ return NULL;
+ }
+
+ // Do ModPagespeedDisallow restrict us from rewriting this URL?
+ if (!options->IsAllowed(absolute_url)) {
+ return NULL;
+ }
+
+ InstawebContext* context = new InstawebContext(
+ request, *content_type, factory, absolute_url,
+ use_custom_options, *options);
+
+ InstawebContext::ContentEncoding encoding =
+ context->content_encoding();
+ if ((encoding == InstawebContext::kGzip) ||
+ (encoding == InstawebContext::kDeflate)) {
+ // Unset the content encoding because the InstawebContext will decode the
+ // content before parsing.
+ apr_table_unset(request->headers_out, HttpAttributes::kContentEncoding);
+ apr_table_unset(request->err_headers_out, HttpAttributes::kContentEncoding);
+ } else if (encoding == InstawebContext::kOther) {
+ // We don't know the encoding, so we cannot rewrite the HTML.
+ return NULL;
+ }
+
+ apr_table_setn(request->headers_out, kModPagespeedHeader,
+ kModPagespeedVersion);
+ SetCacheControl(HttpAttributes::kNoCache, request);
+ SetupCacheRepair(HttpAttributes::kNoCache, request);
+
+ apr_table_unset(request->headers_out, HttpAttributes::kContentLength);
+ apr_table_unset(request->headers_out, "Content-MD5");
+ apr_table_unset(request->headers_out, HttpAttributes::kContentEncoding);
+
+ // Make sure compression is enabled for this response.
+ ap_add_output_filter("DEFLATE", NULL, request, request->connection);
+ return context;
+}
+
+// This returns 'false' if the output filter should stop its loop over
+// the brigade and return an error.
+bool process_bucket(ap_filter_t *filter, request_rec* request,
+ InstawebContext* context, apr_bucket* bucket,
+ apr_status_t* return_code) {
+ // Remove the bucket from the old brigade. We will create new bucket or
+ // reuse the bucket to insert into the new brigade.
+ APR_BUCKET_REMOVE(bucket);
+ *return_code = APR_SUCCESS;
+ apr_bucket_brigade* context_bucket_brigade = context->bucket_brigade();
+ apr_bucket* new_bucket = NULL;
+ if (!APR_BUCKET_IS_METADATA(bucket)) {
+ const char* buf = NULL;
+ size_t bytes = 0;
+ *return_code = apr_bucket_read(bucket, &buf, &bytes, APR_BLOCK_READ);
+ if (*return_code == APR_SUCCESS) {
+ new_bucket = rewrite_html(context, request, REWRITE, buf, bytes);
+ } else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, *return_code, request,
+ "Reading bucket failed (rcode=%d)", *return_code);
+ apr_bucket_delete(bucket);
+ return false;
+ }
+ // Processed the bucket, now delete it.
+ apr_bucket_delete(bucket);
+ if (new_bucket != NULL) {
+ APR_BRIGADE_INSERT_TAIL(context_bucket_brigade, new_bucket);
+ }
+ } else if (APR_BUCKET_IS_EOS(bucket)) {
+ new_bucket = rewrite_html(context, request, FINISH, NULL, 0);
+ if (new_bucket != NULL) {
+ APR_BRIGADE_INSERT_TAIL(context_bucket_brigade, new_bucket);
+ }
+ // Insert the EOS bucket to the new brigade.
+ APR_BRIGADE_INSERT_TAIL(context_bucket_brigade, bucket);
+ // OK, we have seen the EOS. Time to pass it along down the chain.
+ *return_code = ap_pass_brigade(filter->next, context_bucket_brigade);
+ return false;
+ } else if (APR_BUCKET_IS_FLUSH(bucket)) {
+ new_bucket = rewrite_html(context, request, FLUSH, NULL, 0);
+ if (new_bucket != NULL) {
+ APR_BRIGADE_INSERT_TAIL(context_bucket_brigade, new_bucket);
+ }
+ APR_BRIGADE_INSERT_TAIL(context_bucket_brigade, bucket);
+ // OK, Time to flush, pass it along down the chain.
+ *return_code = ap_pass_brigade(filter->next, context_bucket_brigade);
+ if (*return_code != APR_SUCCESS) {
+ return false;
+ }
+ } else {
+ // TODO(lsong): remove this log.
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, APR_SUCCESS, request,
+ "Unknown meta data");
+ APR_BRIGADE_INSERT_TAIL(context_bucket_brigade, bucket);
+ }
+ return true;
+}
+
+apr_status_t instaweb_out_filter(ap_filter_t *filter, apr_bucket_brigade *bb) {
+ ScopedTimer timer(&ApacheProcessContext::AddHtmlRewriteTimeUs);
+
+ // Do nothing if there is nothing, and stop passing to other filters.
+ if (APR_BRIGADE_EMPTY(bb)) {
+ return APR_SUCCESS;
+ }
+
+ request_rec* request = filter->r;
+ InstawebContext* context = static_cast<InstawebContext*>(filter->ctx);
+
+ // Initialize per-request context structure. Note that instaweb_out_filter
+ // may get called multiple times per HTTP request, and this occurs only
+ // on the first call.
+ if (context == NULL) {
+ context = build_context_for_request(request);
+ if (context == NULL) {
+ ap_remove_output_filter(filter);
+ return ap_pass_brigade(filter->next, bb);
+ }
+ filter->ctx = context;
+ }
+
+ apr_status_t return_code = APR_SUCCESS;
+ while (!APR_BRIGADE_EMPTY(bb)) {
+ apr_bucket* bucket = APR_BRIGADE_FIRST(bb);
+ if (!process_bucket(filter, request, context, bucket, &return_code)) {
+ return return_code;
+ }
+ }
+
+ apr_brigade_cleanup(bb);
+ return return_code;
+}
+
+void pagespeed_child_init(apr_pool_t* pool, server_rec* server) {
+ // Create PageSpeed context used by instaweb rewrite-driver. This is
+ // per-process, so we initialize all the server's context by iterating the
+ // server lists in server->next.
+ server_rec* next_server = server;
+ while (next_server) {
+ ApacheRewriteDriverFactory* factory = InstawebContext::Factory(next_server);
+ if (factory->statistics()) {
+ factory->statistics()->InitVariables(false);
+ }
+ next_server = next_server->next;
+ }
+}
+
+int pagespeed_post_config(apr_pool_t* pool, apr_pool_t* plog, apr_pool_t* ptemp,
+ server_rec *server_list) {
+ AprStatistics* statistics = NULL;
+
+ // This routine is complicated by the fact that statistics use inter-process
+ // mutexes and have static data, which co-mingles poorly with this otherwise
+ // re-entrant module. The situation that gets interesting is when there are
+ // multiple VirtualHosts, some of which have statistics enabled and some of
+ // which don't. We don't want the behavior to be order-dependent so we
+ // do multiple passes.
+ //
+ // TODO(jmarantz): test VirtualHost
+
+ // In the first pass, we see whether any of the servers have
+ // statistics enabled, if found, do the static initialization of
+ // statistics to establish global memory segments.
+ for (server_rec* server = server_list; server != NULL;
+ server = server->next) {
+ ApacheRewriteDriverFactory* factory = InstawebContext::Factory(server);
+ if (factory->options()->enabled()) {
+ if (factory->filename_prefix().empty() ||
+ factory->file_cache_path().empty()) {
+ std::string buf = StrCat(
+ "mod_pagespeed is enabled. "
+ "The following directives must not be NULL\n",
+ kModPagespeedFileCachePath, "=",
+ StrCat(
+ factory->file_cache_path(), "\n",
+ kModPagespeedGeneratedFilePrefix, "=",
+ factory->filename_prefix(), "\n"));
+ factory->message_handler()->Message(kError, "%s", buf.c_str());
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ if ((factory->statistics_enabled() && (statistics == NULL))) {
+ statistics = apache_process_context.InitStatistics(
+ factory->filename_prefix());
+ }
+ }
+ }
+
+ // Next we do the instance-independent static initialization, once we have
+ // established whether *any* of the servers of stats enabled.
+ RewriteDriverFactory::Initialize(statistics);
+ SerfUrlAsyncFetcher::Initialize(statistics);
+
+ // Do a final pass over the servers and init the server-specific statistics.
+ for (server_rec* server = server_list; server != NULL;
+ server = server->next) {
+ ApacheRewriteDriverFactory* factory = InstawebContext::Factory(server);
+ factory->set_statistics(factory->statistics_enabled() ? statistics : NULL);
+ }
+ return OK;
+}
+
+// Here log transaction will wait for all the asynchronous resource fetchers to
+// finish.
+apr_status_t pagespeed_log_transaction(request_rec *request) {
+ server_rec* server = request->server;
+ ApacheRewriteDriverFactory* factory = InstawebContext::Factory(server);
+ if (factory == NULL) {
+ return DECLINED;
+ }
+ return DECLINED;
+}
+
+// This function is a callback and it declares what
+// other functions should be called for request
+// processing and configuration requests. This
+// callback function declares the Handlers for
+// other events.
+void mod_pagespeed_register_hooks(apr_pool_t *pool) {
+ // Enable logging using pagespeed style
+ log_message_handler::Install(pool);
+
+ // Use instaweb to handle generated resources.
+ ap_hook_handler(instaweb_handler, NULL, NULL, -1);
+ ap_register_output_filter(
+ kModPagespeedFilterName, instaweb_out_filter, NULL, AP_FTYPE_RESOURCE);
+ // We need our repair headers filter to run after mod_headers. The
+ // mod_headers, which is the filter that is used to add the cache settings, is
+ // AP_FTYPE_CONTENT_SET. Using (AP_FTYPE_CONTENT_SET + 2) to make sure that we
+ // run after mod_headers.
+ ap_register_output_filter(
+ InstawebContext::kRepairHeadersFilterName, repair_caching_header, NULL,
+ static_cast<ap_filter_type>(AP_FTYPE_CONTENT_SET + 2));
+ ap_hook_post_config(pagespeed_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_child_init(pagespeed_child_init, NULL, NULL, APR_HOOK_LAST);
+ ap_hook_log_transaction(pagespeed_log_transaction, NULL, NULL, APR_HOOK_LAST);
+}
+
+apr_status_t pagespeed_child_exit(void* data) {
+ ApacheRewriteDriverFactory* factory =
+ static_cast<ApacheRewriteDriverFactory*>(data);
+ // avoid double-destructing from the cleanup handler on process exit
+ apache_process_context.remove_factory(factory);
+ delete factory;
+ return APR_SUCCESS;
+}
+
+void* mod_pagespeed_create_server_config(apr_pool_t* pool, server_rec* server) {
+ ApacheRewriteDriverFactory* factory = InstawebContext::Factory(server);
+ if (factory == NULL) {
+ factory = new ApacheRewriteDriverFactory(server, kModPagespeedVersion);
+
+ apr_pool_cleanup_register(pool, factory, pagespeed_child_exit,
+ apr_pool_cleanup_null);
+
+ // The pool-based cleanup hooks do not appear to be effective when exiting
+ // the process. The pagespeed_child_exit will *not* be called when the
+ // apache process is shut down. However, the static
+ // apache_process_context's destructor will be.
+ //
+ // This approach is needed to clean up our memory so that valgrind can
+ // report real memory leaks.
+ apache_process_context.add_factory(factory);
+ }
+ return factory;
+}
+
+template<class Options>
+const char* ParseBoolOption(Options* options, cmd_parms* cmd,
+ void (Options::*fn)(bool val),
+ const char* arg) {
+ const char* ret = NULL;
+ if (strcasecmp(arg, "on") == 0) {
+ (options->*fn)(true);
+ } else if (strcasecmp(arg, "off") == 0) {
+ (options->*fn)(false);
+ } else {
+ ret = apr_pstrcat(cmd->pool, cmd->directive->directive, " on|off", NULL);
+ }
+ return ret;
+}
+
+template<class Options>
+const char* ParseInt64Option(Options* options, cmd_parms* cmd,
+ void (Options::*fn)(int64 val),
+ const char* arg) {
+ int64 val;
+ const char* ret = NULL;
+ if (StringToInt64(arg, &val)) {
+ (options->*fn)(val);
+ } else {
+ ret = apr_pstrcat(cmd->pool, cmd->directive->directive,
+ " must specify a 64-bit integer", NULL);
+ }
+ return ret;
+}
+
+template<class Options>
+const char* ParseIntOption(Options* options, cmd_parms* cmd,
+ void (Options::*fn)(int val),
+ const char* arg) {
+ int val;
+ const char* ret = NULL;
+ if (StringToInt(arg, &val)) {
+ (options->*fn)(val);
+ } else {
+ ret = apr_pstrcat(cmd->pool, cmd->directive->directive,
+ " must specify a 32-bit integer", NULL);
+ }
+ return ret;
+}
+
+void warn_deprecated(cmd_parms* cmd, const char* remedy) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, APR_SUCCESS, cmd->server,
+ "%s is deprecated. %s",
+ cmd->directive->directive, remedy);
+}
+
+// Callback function that parses a single-argument directive. This is called
+// by the Apache config parser.
+static const char* ParseDirective(cmd_parms* cmd, void* data, const char* arg) {
+ ScopedTimer timer(&ApacheProcessContext::AddParseTimeUs);
+ ApacheRewriteDriverFactory* factory = InstawebContext::Factory(cmd->server);
+ MessageHandler* handler = factory->message_handler();
+ const char* directive = cmd->directive->directive;
+ const char* ret = NULL;
+ ApacheConfig* config = static_cast<ApacheConfig*>(data);
+ RewriteOptions* options = factory->options();
+ if (!config->description().empty()) {
+ options = config->options();
+ }
+
+ if (strcasecmp(directive, kModPagespeed) == 0) {
+ ret = ParseBoolOption(options, cmd,
+ &RewriteOptions::set_enabled, arg);
+ } else if (strcasecmp(directive, kModPagespeedUrlPrefix) == 0) {
+ warn_deprecated(cmd, "Please remove it from your configuration.");
+ } else if (strcasecmp(directive, kModPagespeedFetchProxy) == 0) {
+ factory->set_fetcher_proxy(arg);
+ } else if (strcasecmp(directive, kModPagespeedGeneratedFilePrefix) == 0) {
+ if (!factory->set_filename_prefix(arg)) {
+ ret = apr_pstrcat(cmd->pool, "Directory ", arg,
+ " does not exist and can't be created.", NULL);
+ }
+ } else if (strcasecmp(directive, kModPagespeedFileCachePath) == 0) {
+ factory->set_file_cache_path(arg);
+ } else if (strcasecmp(directive, kModPagespeedFileCacheSizeKb) == 0) {
+ ret = ParseInt64Option(factory,
+ cmd, &ApacheRewriteDriverFactory::set_file_cache_clean_size_kb, arg);
+ } else if (strcasecmp(directive,
+ kModPagespeedFileCacheCleanIntervalMs) == 0) {
+ ret = ParseInt64Option(factory,
+ cmd, &ApacheRewriteDriverFactory::set_file_cache_clean_interval_ms,
+ arg);
+ } else if (strcasecmp(directive, kModPagespeedFetcherTimeoutMs) == 0) {
+ ret = ParseInt64Option(factory,
+ cmd, &ApacheRewriteDriverFactory::set_fetcher_time_out_ms, arg);
+ } else if (strcasecmp(directive, kModPagespeedNumShards) == 0) {
+ warn_deprecated(cmd, "Please remove it from your configuration.");
+ } else if (strcasecmp(directive, kModPagespeedCssOutlineMinBytes) == 0) {
+ ret = ParseInt64Option(options,
+ cmd, &RewriteOptions::set_css_outline_min_bytes, arg);
+ } else if (strcasecmp(directive, kModPagespeedJsOutlineMinBytes) == 0) {
+ ret = ParseInt64Option(options,
+ cmd, &RewriteOptions::set_js_outline_min_bytes, arg);
+ } else if (strcasecmp(directive, kModPagespeedImgInlineMaxBytes) == 0) {
+ ret = ParseInt64Option(options,
+ cmd, &RewriteOptions::set_img_inline_max_bytes, arg);
+ } else if (strcasecmp(directive, kModPagespeedJsInlineMaxBytes) == 0) {
+ ret = ParseInt64Option(options,
+ cmd, &RewriteOptions::set_js_inline_max_bytes, arg);
+ } else if (strcasecmp(directive, kModPagespeedCssInlineMaxBytes) == 0) {
+ ret = ParseInt64Option(options,
+ cmd, &RewriteOptions::set_css_inline_max_bytes, arg);
+ } else if (strcasecmp(directive, kModPagespeedLRUCacheKbPerProcess) == 0) {
+ ret = ParseInt64Option(factory,
+ cmd, &ApacheRewriteDriverFactory::set_lru_cache_kb_per_process, arg);
+ } else if (strcasecmp(directive, kModPagespeedLRUCacheByteLimit) == 0) {
+ ret = ParseInt64Option(factory,
+ cmd, &ApacheRewriteDriverFactory::set_lru_cache_byte_limit, arg);
+ } else if (strcasecmp(directive, kModPagespeedImgMaxRewritesAtOnce) == 0) {
+ ret = ParseIntOption(options,
+ cmd, &RewriteOptions::set_img_max_rewrites_at_once, arg);
+ } else if (strcasecmp(directive, kModPagespeedEnableFilters) == 0) {
+ if (!options->EnableFiltersByCommaSeparatedList(arg, handler)) {
+ ret = "Failed to enable some filters.";
+ }
+ } else if (strcasecmp(directive, kModPagespeedDisableFilters) == 0) {
+ if (!options->DisableFiltersByCommaSeparatedList(arg, handler)) {
+ ret = "Failed to disable some filters.";
+ }
+ } else if (strcasecmp(directive, kModPagespeedRewriteLevel) == 0) {
+ RewriteOptions::RewriteLevel level = RewriteOptions::kPassThrough;
+ if (RewriteOptions::ParseRewriteLevel(arg, &level)) {
+ options->SetRewriteLevel(level);
+ } else {
+ ret = "Failed to parse RewriteLevel.";
+ }
+ } else if (strcasecmp(directive, kModPagespeedSlurpDirectory) == 0) {
+ factory->set_slurp_directory(arg);
+ } else if (strcasecmp(directive, kModPagespeedSlurpReadOnly) == 0) {
+ ret = ParseBoolOption(static_cast<RewriteDriverFactory*>(factory),
+ cmd, &ApacheRewriteDriverFactory::set_slurp_read_only, arg);
+ } else if (strcasecmp(directive, kModPagespeedSlurpFlushLimit) == 0) {
+ ret = ParseInt64Option(factory,
+ cmd, &ApacheRewriteDriverFactory::set_slurp_flush_limit, arg);
+ } else if (strcasecmp(directive, kModPagespeedForceCaching) == 0) {
+ ret = ParseBoolOption(static_cast<RewriteDriverFactory*>(factory),
+ cmd, &ApacheRewriteDriverFactory::set_force_caching, arg);
+ } else if (strcasecmp(directive, kModPagespeedBeaconUrl) == 0) {
+ options->set_beacon_url(arg);
+ } else if (strcasecmp(directive, kModPagespeedDomain) == 0) {
+ options->domain_lawyer()->AddDomain(arg, factory->message_handler());
+ } else if (strcasecmp(directive, kModPagespeedAllow) == 0) {
+ options->Allow(arg);
+ } else if (strcasecmp(directive, kModPagespeedDisallow) == 0) {
+ options->Disallow(arg);
+ } else if (strcasecmp(directive, kModPagespeedStatistics) == 0) {
+ ret = ParseBoolOption(factory, cmd,
+ &ApacheRewriteDriverFactory::set_statistics_enabled, arg);
+ } else {
+ return "Unknown directive.";
+ }
+
+ return ret;
+}
+
+// Callback function that parses a two-argument directive. This is called
+// by the Apache config parser.
+static const char* ParseDirective2(cmd_parms* cmd, void* data,
+ const char* arg1, const char* arg2) {
+ ScopedTimer timer(&ApacheProcessContext::AddParseTimeUs);
+ ApacheRewriteDriverFactory* factory = InstawebContext::Factory(cmd->server);
+ RewriteOptions* options = factory->options();
+ const char* directive = cmd->directive->directive;
+ const char* ret = NULL;
+ if (strcasecmp(directive, kModPagespeedMapRewriteDomain) == 0) {
+ options->domain_lawyer()->AddRewriteDomainMapping(
+ arg1, arg2, factory->message_handler());
+ } else if (strcasecmp(directive, kModPagespeedMapOriginDomain) == 0) {
+ options->domain_lawyer()->AddOriginDomainMapping(
+ arg1, arg2, factory->message_handler());
+ } else {
+ return "Unknown directive.";
+ }
+ return ret;
+}
+
+// Setting up Apache options is cumbersome for several reasons:
+//
+// 1. Apache appears to require the option table be entirely constructed
+// using static data. So we cannot use helper functions to create the
+// helper table, so that we can populate it from another table.
+// 2. You have to fill in the table with a function pointer with a K&R
+// C declaration that does not specify its argument types. There appears
+// to be a type-correct union hidden behind an ifdef for
+// AP_HAVE_DESIGNATED_INITIALIZER, but that doesn't work. It gives a
+// syntax error; its comments indicate it is there for Doxygen.
+// 3. Although you have to pre-declare all the options, you need to again
+// dispatch based on the name of the options. You could, conceivably,
+// provide a different function pointer for each call. This might look
+// feasible with the 'mconfig' argument to AP_INIT_TAKE1, but mconfig
+// must be specified in a static initializer. So it wouldn't be that easy
+// to, say, create a C++ object for each config parameter.
+//
+// Googling for AP_MODULE_DECLARE_DATA didn't shed any light on how to do this
+// using a style suitable for programming after 1980. So all we can do is make
+// this a little less ugly with wrapper macros and helper functions.
+//
+// TODO(jmarantz): investigate usage of RSRC_CONF -- perhaps many of these
+// options should be allowable inside a Directory or Location by ORing in
+// ACCESS_CONF to RSRC_CONF.
+
+#define APACHE_CONFIG_OPTION(name, help) \
+ AP_INIT_TAKE1(name, reinterpret_cast<const char*(*)()>(ParseDirective), \
+ NULL, RSRC_CONF, help)
+#define APACHE_CONFIG_DIR_OPTION(name, help) \
+ AP_INIT_TAKE1(name, reinterpret_cast<const char*(*)()>(ParseDirective), \
+ NULL, OR_ALL, help)
+
+// Like APACHE_CONFIG_OPTION, but gets 2 arguments.
+#define APACHE_CONFIG_DIR_OPTION2(name, help) \
+ AP_INIT_TAKE2(name, reinterpret_cast<const char*(*)()>(ParseDirective2), \
+ NULL, OR_ALL, help)
+
+static const command_rec mod_pagespeed_filter_cmds[] = {
+ APACHE_CONFIG_DIR_OPTION(kModPagespeed, "Enable instaweb"),
+ APACHE_CONFIG_OPTION(kModPagespeedUrlPrefix, "Set the url prefix"),
+ APACHE_CONFIG_OPTION(kModPagespeedFetchProxy, "Set the fetch proxy"),
+ APACHE_CONFIG_OPTION(kModPagespeedGeneratedFilePrefix,
+ "Set generated file's prefix"),
+ APACHE_CONFIG_OPTION(kModPagespeedFileCachePath,
+ "Set the path for file cache"),
+ APACHE_CONFIG_OPTION(kModPagespeedFileCacheSizeKb,
+ "Set the target size (in kilobytes) for file cache"),
+ APACHE_CONFIG_OPTION(kModPagespeedFileCacheCleanIntervalMs,
+ "Set the interval (in ms) for cleaning the file cache"),
+ APACHE_CONFIG_OPTION(kModPagespeedFetcherTimeoutMs,
+ "Set internal fetcher timeout in milliseconds"),
+ APACHE_CONFIG_OPTION(kModPagespeedNumShards, "Set number of shards"),
+ APACHE_CONFIG_OPTION(kModPagespeedLRUCacheKbPerProcess,
+ "Set the total size, in KB, of the per-process "
+ "in-memory LRU cache"),
+ APACHE_CONFIG_OPTION(kModPagespeedLRUCacheByteLimit,
+ "Set the maximum byte size entry to store in the per-process "
+ "in-memory LRU cache"),
+ APACHE_CONFIG_DIR_OPTION(kModPagespeedRewriteLevel,
+ "Base level of rewriting (PassThrough, CoreFilters)"),
+ APACHE_CONFIG_DIR_OPTION(kModPagespeedEnableFilters,
+ "Comma-separated list of enabled filters"),
+ APACHE_CONFIG_DIR_OPTION(kModPagespeedDisableFilters,
+ "Comma-separated list of disabled filters"),
+ APACHE_CONFIG_OPTION(kModPagespeedSlurpDirectory,
+ "Directory from which to read slurped resources"),
+ APACHE_CONFIG_OPTION(kModPagespeedSlurpReadOnly,
+ "Only read from the slurped directory, fail to fetch "
+ "URLs not already in the slurped directory"),
+ APACHE_CONFIG_OPTION(kModPagespeedSlurpFlushLimit,
+ "Set the maximum byte size for the slurped content to hold before "
+ "a flush"),
+ APACHE_CONFIG_OPTION(kModPagespeedForceCaching,
+ "Ignore HTTP cache headers and TTLs"),
+ APACHE_CONFIG_DIR_OPTION(kModPagespeedCssOutlineMinBytes,
+ "Number of bytes above which inline "
+ "CSS resources will be outlined."),
+ APACHE_CONFIG_DIR_OPTION(kModPagespeedJsOutlineMinBytes,
+ "Number of bytes above which inline "
+ "Javascript resources will be outlined."),
+ APACHE_CONFIG_DIR_OPTION(kModPagespeedImgInlineMaxBytes,
+ "Number of bytes below which images will be inlined."),
+ APACHE_CONFIG_OPTION(kModPagespeedImgMaxRewritesAtOnce,
+ "Set bound on number of images being rewritten at one time "
+ "(0 = unbounded)."),
+ APACHE_CONFIG_DIR_OPTION(kModPagespeedJsInlineMaxBytes,
+ "Number of bytes below which javascript will be inlined."),
+ APACHE_CONFIG_DIR_OPTION(kModPagespeedCssInlineMaxBytes,
+ "Number of bytes below which stylesheets will be inlined."),
+ APACHE_CONFIG_DIR_OPTION(kModPagespeedBeaconUrl, "URL for beacon callback"
+ " injected by add_instrumentation."),
+ APACHE_CONFIG_DIR_OPTION(kModPagespeedDomain,
+ "Authorize mod_pagespeed to rewrite resources in a domain."),
+ APACHE_CONFIG_DIR_OPTION2(kModPagespeedMapRewriteDomain,
+ "to_domain from_domain[,from_domain]*"),
+ APACHE_CONFIG_DIR_OPTION2(kModPagespeedMapOriginDomain,
+ "to_domain from_domain[,from_domain]*"),
+ APACHE_CONFIG_DIR_OPTION(kModPagespeedAllow,
+ "wildcard_spec for urls"),
+ APACHE_CONFIG_DIR_OPTION(kModPagespeedDisallow,
+ "wildcard_spec for urls"),
+ APACHE_CONFIG_DIR_OPTION(kModPagespeedStatistics,
+ "Whether to collect cross-process statistics."),
+ {NULL}
+};
+
+// We use pool-based cleanup for ApacheConfigs. This is 99% effective.
+// There is at least one base config which is created with create_dir_config,
+// but whose pool is never freed. To allow clean valgrind reports, we
+// must delete that config too. So we keep a backup cleanup-set for
+// configs at end-of-process, and keep that set up-to-date when the
+// pool deletion does work.
+apr_status_t delete_config(void* data) {
+ ApacheConfig* config = static_cast<ApacheConfig*>(data);
+ // avoid double-destructing from the cleanup handler on process exit
+ apache_process_context.remove_config(config);
+ delete config;
+ return APR_SUCCESS;
+}
+
+// Function to allow all modules to create per directory configuration
+// structures.
+// dir is the directory currently being processed.
+// Returns the per-directory structure created.
+void* create_dir_config(apr_pool_t* pool, char* dir) {
+ ApacheConfig* config = new ApacheConfig(dir);
+ config->options()->SetDefaultRewriteLevel(RewriteOptions::kCoreFilters);
+ apache_process_context.add_config(config);
+ apr_pool_cleanup_register(pool, config, delete_config, apr_pool_cleanup_null);
+ apache_process_context.add_config(config);
+ return config;
+}
+
+// Function to allow all modules to merge the per directory configuration
+// structures for two directories.
+// base_conf is the directory structure created for the parent directory.
+// new_conf is the directory structure currently being processed.
+// This function returns the new per-directory structure created
+void* merge_dir_config(apr_pool_t* pool, void* base_conf, void* new_conf) {
+ ApacheConfig* dir1 = static_cast<ApacheConfig*>(base_conf);
+ ApacheConfig* dir2 = static_cast<ApacheConfig*>(new_conf);
+
+ // To make it easier to debug the merged configurations, we store
+ // the name of both input configurations as the description for
+ // the merged configuration.
+ ApacheConfig* dir3 = new ApacheConfig(StrCat(
+ "Combine(", dir1->description(), ", ", dir2->description(), ")"));
+ MergeOptions(*(dir1->options()), *(dir2->options()), dir3->options());
+ apr_pool_cleanup_register(pool, dir3, delete_config, apr_pool_cleanup_null);
+ apache_process_context.add_config(dir3);
+ return dir3;
+}
+
+} // namespace
+
+} // namespace net_instaweb
+
+extern "C" {
+// Export our module so Apache is able to load us.
+// See http://gcc.gnu.org/wiki/Visibility for more information.
+#if defined(__linux)
+#pragma GCC visibility push(default)
+#endif
+
+// Declare and populate the module's data structure. The
+// name of this structure ('pagespeed_module') is important - it
+// must match the name of the module. This structure is the
+// only "glue" between the httpd core and the module.
+module AP_MODULE_DECLARE_DATA pagespeed_module = {
+ // Only one callback function is provided. Real
+ // modules will need to declare callback functions for
+ // server/directory configuration, configuration merging
+ // and other tasks.
+ STANDARD20_MODULE_STUFF,
+ net_instaweb::create_dir_config,
+ net_instaweb::merge_dir_config,
+ net_instaweb::mod_pagespeed_create_server_config,
+ NULL, // merge per-server config structures
+ net_instaweb::mod_pagespeed_filter_cmds,
+ net_instaweb::mod_pagespeed_register_hooks,
+};
+
+#if defined(__linux)
+#pragma GCC visibility pop
+#endif
+} // extern "C"
diff --git a/trunk/src/net/instaweb/apache/mod_instaweb.h b/trunk/src/net/instaweb/apache/mod_instaweb.h
new file mode 100644
index 0000000..2345e2c
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/mod_instaweb.h
@@ -0,0 +1,34 @@
+// 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.
+
+
+#ifndef MOD_INSTAWEB_MOD_INSTAWEB_H_
+#define MOD_INSTAWEB_MOD_INSTAWEB_H_
+
+#include "base/basictypes.h"
+
+// Forward declaration.
+struct server_rec;
+
+namespace net_instaweb {
+
+struct PageSpeedConfig;
+class PageSpeedServerContext;
+
+PageSpeedConfig* mod_pagespeed_get_server_config(server_rec* server);
+PageSpeedServerContext* mod_pagespeed_get_config_server_context(
+ server_rec* server);
+} // namespace net_instaweb
+
+#endif // MOD_INSTAWEB_MOD_INSTAWEB_H_
diff --git a/trunk/src/net/instaweb/apache/serf_async_callback.cc b/trunk/src/net/instaweb/apache/serf_async_callback.cc
new file mode 100644
index 0000000..462cd1a
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/serf_async_callback.cc
@@ -0,0 +1,91 @@
+// 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: jmarantz@google.com (Joshua Marantz)
+// lsong@google.com (Libo Song)
+
+#include "net/instaweb/apache/serf_async_callback.h"
+#include "net/instaweb/util/public/writer.h"
+
+namespace net_instaweb {
+
+namespace {
+
+class ProtectedWriter : public Writer {
+ public:
+ ProtectedWriter(SerfAsyncCallback* callback, Writer* orig_writer)
+ : callback_(callback),
+ orig_writer_(orig_writer) {
+ }
+
+ virtual bool Write(const StringPiece& buf, MessageHandler* handler) {
+ bool ret = true;
+
+ // If the callback has not timed out and been released, then pass
+ // the data through.
+ if (!callback_->released()) {
+ ret = orig_writer_->Write(buf, handler);
+ }
+ return ret;
+ }
+
+ virtual bool Flush(MessageHandler* handler) {
+ bool ret = true;
+
+ // If the callback has not timed out and been released, then pass
+ // the flush through.
+ if (!callback_->released()) {
+ ret = orig_writer_->Flush(handler);
+ }
+ return ret;
+ }
+
+ private:
+ SerfAsyncCallback* callback_;
+ Writer* orig_writer_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProtectedWriter);
+};
+
+} // namespace
+
+SerfAsyncCallback::SerfAsyncCallback(MetaData* response_headers, Writer* writer)
+ : done_(false),
+ success_(false),
+ released_(false),
+ response_headers_(response_headers),
+ writer_(new ProtectedWriter(this, writer)) {
+}
+
+SerfAsyncCallback::~SerfAsyncCallback() {
+}
+
+void SerfAsyncCallback::Done(bool success) {
+ done_ = true;
+ success_ = success;
+ if (released_) {
+ delete this;
+ } else {
+ response_headers_->CopyFrom(response_headers_buffer_);
+ }
+}
+
+void SerfAsyncCallback::Release() {
+ released_ = true;
+ if (done_) {
+ delete this;
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/apache/serf_async_callback.h b/trunk/src/net/instaweb/apache/serf_async_callback.h
new file mode 100644
index 0000000..ea62e27
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/serf_async_callback.h
@@ -0,0 +1,72 @@
+// 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: jmarantz@google.com (Joshua Marantz)
+// lsong@google.com (Libo Song)
+
+#ifndef NET_INSTAWEB_APACHE_SERF_ASYNC_CALLBACK_H_
+#define NET_INSTAWEB_APACHE_SERF_ASYNC_CALLBACK_H_
+
+#include "base/scoped_ptr.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include "net/instaweb/util/public/url_async_fetcher.h"
+
+namespace net_instaweb {
+
+// Class to help run an asynchronous fetch synchronously with a timeout.
+class SerfAsyncCallback : public UrlAsyncFetcher::Callback {
+ public:
+ SerfAsyncCallback(MetaData* response_headers, Writer* writer);
+ virtual ~SerfAsyncCallback();
+ virtual void Done(bool success);
+
+ // When implementing a synchronous fetch with a timeout based on an
+ // underlying asynchronous mechanism, we need to ensure that we don't
+ // write to freed memory if the Done callback fires after the timeout.
+ //
+ // So we need to make sure the Writer and Response Buffers are owned
+ // by this Callback class, which will forward the output and headers
+ // to the caller *if* it has not been released by the time the callback
+ // is called.
+ MetaData* response_headers() { return &response_headers_buffer_; }
+ Writer* writer() { return writer_.get(); }
+
+ // When the 'owner' of this callback -- the code that calls 'new' --
+ // is done with it, it can call release. This will only delete the
+ // callback if Done() has been called. Otherwise it will stay around
+ // waiting for Done() to be called, and only then will it be deleted.
+ //
+ // When Release is called prior to Done(), the writer and response_headers
+ // will beo NULLed out in this structure so they will not be updated when
+ // Done() is finally called.
+ void Release();
+
+ bool done() const { return done_; }
+ bool success() const { return success_; }
+ bool released() const { return released_; }
+
+ private:
+ bool done_;
+ bool success_;
+ bool released_;
+ SimpleMetaData response_headers_buffer_;
+ MetaData* response_headers_;
+ scoped_ptr<Writer> writer_;
+
+ DISALLOW_COPY_AND_ASSIGN(SerfAsyncCallback);
+};
+
+} // namespace
+
+#endif // NET_INSTAWEB_APACHE_SERF_ASYNC_CALLBACK_H_
diff --git a/trunk/src/net/instaweb/apache/serf_url_async_fetcher.cc b/trunk/src/net/instaweb/apache/serf_url_async_fetcher.cc
new file mode 100644
index 0000000..940d520
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/serf_url_async_fetcher.cc
@@ -0,0 +1,878 @@
+// 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.
+
+// TODO(jmarantz): Avoid initiating fetches for resources already in flight.
+// The challenge is that we would want to call all the callbacks that indicated
+// interest in a particular URL once the callback completed. Alternatively,
+// this could be done in a level above the URL fetcher.
+
+#include "net/instaweb/apache/serf_url_async_fetcher.h"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "apr_atomic.h"
+#include "apr_strings.h"
+#include "apr_thread_proc.h"
+#include "apr_version.h"
+#include "base/basictypes.h"
+#include "base/stl_util-inl.h"
+#include "net/instaweb/apache/apr_mutex.h"
+#include "net/instaweb/public/version.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/meta_data.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include "net/instaweb/util/public/statistics.h"
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/timer.h"
+#include "net/instaweb/util/public/writer.h"
+#include "third_party/serf/src/serf.h"
+#include "third_party/serf/src/serf_bucket_util.h"
+
+// Until this fetcher has some mileage on it, it is useful to keep around
+// an easy way to turn on lots of debug messages. But they do get a bit chatty
+// when things are working well.
+#define SERF_DEBUG(x)
+
+namespace {
+const int kBufferSize = 2048;
+const char kFetchMethod[] = "GET";
+} // namespace
+
+extern "C" {
+ // Declares a new function added to
+ // src/third_party/serf/instaweb_context.c
+serf_bucket_t* serf_request_bucket_request_create_for_host(
+ serf_request_t *request,
+ const char *method,
+ const char *uri,
+ serf_bucket_t *body,
+ serf_bucket_alloc_t *allocator, const char* host);
+}
+
+namespace net_instaweb {
+
+const char SerfStats::kSerfFetchRequestCount[] = "serf_fetch_request_count";
+const char SerfStats::kSerfFetchByteCount[] = "serf_fetch_bytes_count";
+const char SerfStats::kSerfFetchTimeDurationMs[] =
+ "serf_fetch_time_duration_ms";
+const char SerfStats::kSerfFetchCancelCount[] = "serf_fetch_cancel_count";
+const char SerfStats::kSerfFetchOutstandingCount[] =
+ "serf_fetch_outstanding_count";
+const char SerfStats::kSerfFetchTimeoutCount[] = "serf_fetch_timeout_count";
+
+std::string GetAprErrorString(apr_status_t status) {
+ char error_str[1024];
+ apr_strerror(status, error_str, sizeof(error_str));
+ return error_str;
+}
+
+// TODO(lsong): Move this to a separate file. Necessary?
+class SerfFetch {
+ public:
+ // TODO(lsong): make use of request_headers.
+ SerfFetch(apr_pool_t* pool,
+ const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* fetched_content_writer,
+ MessageHandler* message_handler,
+ UrlAsyncFetcher::Callback* callback,
+ Timer* timer)
+ : fetcher_(NULL),
+ timer_(timer),
+ str_url_(url),
+ response_headers_(response_headers),
+ fetched_content_writer_(fetched_content_writer),
+ message_handler_(message_handler),
+ callback_(callback),
+ connection_(NULL),
+ byte_received_(0),
+ fetch_start_ms_(0),
+ fetch_end_ms_(0) {
+ request_headers_.CopyFrom(request_headers);
+ apr_pool_create(&pool_, pool);
+ bucket_alloc_ = serf_bucket_allocator_create(pool_, NULL, NULL);
+ }
+
+ ~SerfFetch() {
+ if (connection_ != NULL) {
+ serf_connection_close(connection_);
+ }
+ apr_pool_destroy(pool_);
+ }
+
+ // Start the fetch. It returns immediately. This can only be run when
+ // locked with fetcher->mutex_.
+ bool Start(SerfUrlAsyncFetcher* fetcher);
+
+ const char* str_url() { return str_url_.c_str(); }
+
+ // This must be called while holding SerfUrlAsyncFetcher's mutex_.
+ void Cancel() {
+ CallCallback(false);
+ }
+
+ // Calls the callback supplied by the user. This needs to happen
+ // exactly once. In some error cases it appears that Serf calls
+ // HandleResponse multiple times on the same object.
+ //
+ // This must be called while holding SerfUrlAsyncFetcher's mutex_.
+ void CallCallback(bool success) {
+ if (callback_ == NULL) {
+ LOG(INFO) << "Serf callback more than once on same fetch " << str_url()
+ << " (" << this << ")";
+ } else {
+ UrlAsyncFetcher::Callback* callback = callback_;
+ callback_ = NULL;
+ response_headers_ = NULL;
+ callback->Done(success);
+ fetch_end_ms_ = timer_->NowMs();
+ fetcher_->FetchComplete(this);
+ }
+ }
+
+ int64 TimeDuration() const {
+ if ((fetch_start_ms_ != 0) && (fetch_end_ms_ != 0)) {
+ return fetch_end_ms_ - fetch_start_ms_;
+ } else {
+ return 0;
+ }
+ }
+ int64 fetch_start_ms() const { return fetch_start_ms_; }
+
+ size_t byte_received() const { return byte_received_; }
+ MessageHandler* message_handler() { return message_handler_; }
+
+ private:
+
+ // Static functions used in callbacks.
+ static serf_bucket_t* ConnectionSetup(
+ apr_socket_t* socket, void* setup_baton, apr_pool_t* pool) {
+ SerfFetch* fetch = static_cast<SerfFetch*>(setup_baton);
+ return serf_bucket_socket_create(socket, fetch->bucket_alloc_);
+ }
+
+ static void ClosedConnection(serf_connection_t* conn,
+ void* closed_baton,
+ apr_status_t why,
+ apr_pool_t* pool) {
+ SerfFetch* fetch = static_cast<SerfFetch*>(closed_baton);
+ if (why != APR_SUCCESS) {
+ fetch->message_handler_->Warning(
+ fetch->str_url_.c_str(), 0, "Connection close (code=%d %s).",
+ why, GetAprErrorString(why).c_str());
+ }
+ // Connection is closed.
+ fetch->connection_ = NULL;
+ }
+
+ static serf_bucket_t* AcceptResponse(serf_request_t* request,
+ serf_bucket_t* stream,
+ void* acceptor_baton,
+ apr_pool_t* pool) {
+ // Get the per-request bucket allocator.
+ serf_bucket_alloc_t* bucket_alloc = serf_request_get_alloc(request);
+ // Create a barrier so the response doesn't eat us!
+ // From the comment in Serf:
+ // ### the stream does not have a barrier, this callback should generally
+ // ### add a barrier around the stream before incorporating it into a
+ // ### response bucket stack.
+ // ... i.e. the passed bucket becomes owned rather than
+ // ### borrowed.
+ serf_bucket_t* bucket = serf_bucket_barrier_create(stream, bucket_alloc);
+ return serf_bucket_response_create(bucket, bucket_alloc);
+ }
+
+ static apr_status_t HandleResponse(serf_request_t* request,
+ serf_bucket_t* response,
+ void* handler_baton,
+ apr_pool_t* pool) {
+ SerfFetch* fetch = static_cast<SerfFetch*>(handler_baton);
+ return fetch->HandleResponse(request, response);
+ }
+
+ // The handler MUST process data from the response bucket until the
+ // bucket's read function states it would block (APR_STATUS_IS_EAGAIN).
+ // The handler is invoked only when new data arrives. If no further data
+ // arrives, and the handler does not process all available data, then the
+ // system can result in a deadlock around the unprocessed, but read, data.
+ apr_status_t HandleResponse(serf_request_t* request,
+ serf_bucket_t* response) {
+ apr_status_t status = APR_EGENERAL;
+ if (response_headers_ == NULL) {
+ LOG(INFO) << "HandleResponse called on URL " << str_url()
+ << "(" << this << "), which is already erased";
+ return status;
+ }
+
+ serf_status_line status_line;
+ if ((response != NULL) &&
+ ((status = serf_bucket_response_status(response, &status_line))
+ == APR_SUCCESS)) {
+ response_headers_->SetStatusAndReason(
+ static_cast<HttpStatus::Code>(status_line.code));
+ response_headers_->set_major_version(status_line.version / 1000);
+ response_headers_->set_minor_version(status_line.version % 1000);
+ const char* data = NULL;
+ apr_size_t len = 0;
+ while ((status = serf_bucket_read(response, kBufferSize, &data, &len))
+ == APR_SUCCESS || APR_STATUS_IS_EOF(status) ||
+ APR_STATUS_IS_EAGAIN(status)) {
+ byte_received_ += len;
+ if (len > 0 &&
+ !fetched_content_writer_->Write(
+ StringPiece(data, len), message_handler_)) {
+ status = APR_EGENERAL;
+ break;
+ }
+ if (status != APR_SUCCESS) {
+ break;
+ }
+ }
+ // We could read the headers earlier, but then we have to check if we
+ // have received the headers. At EOF of response, we have the headers
+ // already. Read them.
+ if (APR_STATUS_IS_EOF(status)) {
+ status = ReadHeaders(response);
+ }
+ }
+ if (!APR_STATUS_IS_EAGAIN(status)) {
+ bool success = APR_STATUS_IS_EOF(status);
+ CallCallback(success);
+ }
+ return status;
+ }
+
+ apr_status_t ReadHeaders(serf_bucket_t* response) {
+ apr_status_t status = APR_SUCCESS;
+ serf_bucket_t* headers = serf_bucket_response_get_headers(response);
+ const char* data = NULL;
+ apr_size_t num_bytes = 0;
+ while ((status = serf_bucket_read(headers, kBufferSize, &data, &num_bytes))
+ == APR_SUCCESS || APR_STATUS_IS_EOF(status) ||
+ APR_STATUS_IS_EAGAIN(status)) {
+ if (response_headers_->headers_complete()) {
+ status = APR_EGENERAL;
+ message_handler_->Info(str_url_.c_str(), 0,
+ "headers complete but more data coming");
+ } else {
+ StringPiece str_piece(data, num_bytes);
+ apr_size_t parsed_len =
+ response_headers_->ParseChunk(str_piece, message_handler_);
+ if (parsed_len != num_bytes) {
+ status = APR_EGENERAL;
+ message_handler_->Error(str_url_.c_str(), 0,
+ "unexpected bytes at end of header");
+ }
+ }
+ if (status != APR_SUCCESS) {
+ break;
+ }
+ }
+ if (APR_STATUS_IS_EOF(status)
+ && !response_headers_->headers_complete()) {
+ message_handler_->Error(str_url_.c_str(), 0,
+ "eof on incomplete headers code=%d %s",
+ status, GetAprErrorString(status).c_str());
+ status = APR_EGENERAL;
+ }
+ return status;
+ }
+
+ // Ensures that a user-agent string is included, and that the mod_pagespeed
+ // version is appended.
+ void FixUserAgent() {
+ // Supply a default user-agent if none is present, and in any case
+ // append on a 'serf' suffix.
+ std::string user_agent;
+ CharStarVector v;
+ if (request_headers_.Lookup(HttpAttributes::kUserAgent, &v)) {
+ for (int i = 0, n = v.size(); i < n; ++i) {
+ if (i != 0) {
+ user_agent += " ";
+ }
+ user_agent += v[i];
+ }
+ request_headers_.RemoveAll(HttpAttributes::kUserAgent);
+ }
+ if (user_agent.empty()) {
+ user_agent += "Serf/" SERF_VERSION_STRING;
+ }
+ StringPiece version(" mod_pagespeed/" MOD_PAGESPEED_VERSION_STRING "-"
+ LASTCHANGE_STRING);
+ if (!StringPiece(user_agent).ends_with(version)) {
+ user_agent.append(version.data(), version.size());
+ }
+ request_headers_.Add(HttpAttributes::kUserAgent, user_agent);
+ }
+
+ static apr_status_t SetupRequest(serf_request_t* request,
+ void* setup_baton,
+ serf_bucket_t** req_bkt,
+ serf_response_acceptor_t* acceptor,
+ void** acceptor_baton,
+ serf_response_handler_t* handler,
+ void** handler_baton,
+ apr_pool_t* pool) {
+ SerfFetch* fetch = static_cast<SerfFetch*>(setup_baton);
+ const char* url_path = apr_uri_unparse(pool, &fetch->url_,
+ APR_URI_UNP_OMITSITEPART);
+
+ // If there is an explicit Host header, then override the
+ // host field in the Serf structure, as we will not be able
+ // to override it after it is created; only append to it.
+ //
+ // Serf automatically populates the Host field based on the
+ // URL, and provides no mechanism to override it, except
+ // by hacking source. We hacked source.
+ //
+ // See src/third_party/serf/src/instaweb_context.c
+ CharStarVector v;
+ const char* host = NULL;
+ if (fetch->request_headers_.Lookup(HttpAttributes::kHost, &v) &&
+ (v.size() == 1)) {
+ host = v[0];
+ }
+
+ fetch->FixUserAgent();
+
+ *req_bkt = serf_request_bucket_request_create_for_host(
+ request, kFetchMethod,
+ url_path, NULL,
+ serf_request_get_alloc(request), host);
+ serf_bucket_t* hdrs_bkt = serf_bucket_request_get_headers(*req_bkt);
+
+ for (int i = 0; i < fetch->request_headers_.NumAttributes(); ++i) {
+ const char* name = fetch->request_headers_.Name(i);
+ const char* value = fetch->request_headers_.Value(i);
+ if ((strcasecmp(name, HttpAttributes::kUserAgent) == 0) ||
+ (strcasecmp(name, HttpAttributes::kAcceptEncoding) == 0) ||
+ (strcasecmp(name, HttpAttributes::kReferer) == 0)) {
+ serf_bucket_headers_setn(hdrs_bkt, name, value);
+ }
+ }
+
+ // TODO(jmarantz): add accept-encoding:gzip even if not requested by
+ // the caller, but then decompress in the output handler.
+
+ *acceptor = SerfFetch::AcceptResponse;
+ *acceptor_baton = fetch;
+ *handler = SerfFetch::HandleResponse;
+ *handler_baton = fetch;
+ return APR_SUCCESS;
+ }
+
+ bool ParseUrl() {
+ apr_status_t status = 0;
+ status = apr_uri_parse(pool_, str_url_.c_str(), &url_);
+ if (status != APR_SUCCESS) {
+ return false; // Failed to parse URL.
+ }
+
+ // TODO(lsong): We do not handle HTTPS for now. HTTPS needs authentication
+ // verifying certificates, etc.
+ if (strcasecmp(url_.scheme, "https") == 0) {
+ return false;
+ }
+ if (!url_.port) {
+ url_.port = apr_uri_port_of_scheme(url_.scheme);
+ }
+ if (!url_.path) {
+ url_.path = apr_pstrdup(pool_, "/");
+ }
+ return true;
+ }
+
+ SerfUrlAsyncFetcher* fetcher_;
+ Timer* timer_;
+ const std::string str_url_;
+ SimpleMetaData request_headers_;
+ MetaData* response_headers_;
+ Writer* fetched_content_writer_;
+ MessageHandler* message_handler_;
+ UrlAsyncFetcher::Callback* callback_;
+
+ apr_pool_t* pool_;
+ serf_bucket_alloc_t* bucket_alloc_;
+ apr_uri_t url_;
+ serf_connection_t* connection_;
+ size_t byte_received_;
+ int64 fetch_start_ms_;
+ int64 fetch_end_ms_;
+
+
+ DISALLOW_COPY_AND_ASSIGN(SerfFetch);
+};
+
+class SerfThreadedFetcher : public SerfUrlAsyncFetcher {
+ public:
+ SerfThreadedFetcher(SerfUrlAsyncFetcher* parent, const char* proxy) :
+ SerfUrlAsyncFetcher(parent, proxy),
+ initiate_mutex_(pool_),
+ terminate_mutex_(pool_),
+ thread_done_(false) {
+ terminate_mutex_.Lock();
+ CHECK_EQ(APR_SUCCESS,
+ apr_thread_create(&thread_id_, NULL, SerfThreadFn, this, pool_));
+ }
+
+ ~SerfThreadedFetcher() {
+ // Although Cancel will be called in the base class destructor, we
+ // want to call it here as well, as it will make it easier for the
+ // thread to terminate.
+ CancelOutstandingFetches();
+ STLDeleteElements(&completed_fetches_);
+ STLDeleteElements(&initiate_fetches_);
+
+ // Let the thread terminate naturally by unlocking its mutexes.
+ thread_done_ = true;
+ mutex_->Unlock();
+ LOG(INFO) << "Waiting for threaded serf fetcher to terminate";
+ terminate_mutex_.Lock();
+ terminate_mutex_.Unlock();
+ }
+
+ // Called from mainline to queue up a fetch for the thread. If the
+ // thread is idle then we can unlock it.
+ void InitiateFetch(SerfFetch* fetch) {
+ ScopedMutex lock(&initiate_mutex_);
+ initiate_fetches_.push_back(fetch);
+ }
+
+ private:
+ static void* SerfThreadFn(apr_thread_t* thread_id, void* context) {
+ SerfThreadedFetcher* stc = static_cast<SerfThreadedFetcher*>(context);
+ CHECK_EQ(thread_id, stc->thread_id_);
+ stc->SerfThread();
+ return NULL;
+ }
+
+ // Thread-called function to transfer fetches from initiate_fetches_ vector to
+ // the active_fetches_ queue. Doesn't do any work if initiate_fetches_ is
+ // empty.
+ void TransferFetches() {
+ // Use a temp that to minimize the amount of time we hold the
+ // initiate_mutex_ lock, so that the parent thread doesn't get
+ // blocked trying to initiate fetches.
+ FetchVector xfer_fetches;
+ {
+ ScopedMutex lock(&initiate_mutex_);
+ xfer_fetches.swap(initiate_fetches_);
+ }
+
+ // Now that we've unblocked the parent thread, we can leisurely
+ // queue up the fetches, employing the proper lock for the active_fetches_
+ // set. Actually we expect we wll never have contention on this mutex
+ // from the thread.
+ if (!xfer_fetches.empty()) {
+ int num_started = 0;
+ ScopedMutex lock(mutex_);
+ for (int i = 0, n = xfer_fetches.size(); i < n; ++i) {
+ SerfFetch* fetch = xfer_fetches[i];
+ if (fetch->Start(this)) {
+ SERF_DEBUG(LOG(INFO) << "Adding threaded fetch to url "
+ << fetch->str_url()
+ << " (" << active_fetches_.size() << ")");
+ active_fetches_.push_back(fetch);
+ active_fetch_map_[fetch] = --active_fetches_.end();
+ ++num_started;
+ } else {
+ delete fetch;
+ }
+ }
+ if ((num_started != 0) && (outstanding_count_ != NULL)) {
+ outstanding_count_->Add(num_started);
+ }
+ }
+ }
+
+ void SerfThread() {
+ while (!thread_done_) {
+ // If initiate_fetches is empty, we will not do any work.
+ TransferFetches();
+
+ const int64 kPollIntervalUs = 500000;
+ SERF_DEBUG(LOG(INFO) << "Polling from serf thread (" << this << ")");
+ // If active_fetches_ is empty, we will not do any work.
+ int num_outstanding_fetches = Poll(kPollIntervalUs);
+ SERF_DEBUG(LOG(INFO) << "Finished polling from serf thread ("
+ << this << ")");
+ // We don't want to spin busily waiting for new fetches. We could use a
+ // semaphore, but we're not really concerned with latency here, so we can
+ // just check every once in a while.
+ if (num_outstanding_fetches == 0) {
+ sleep(1);
+ }
+ }
+ terminate_mutex_.Unlock();
+ }
+
+ apr_thread_t* thread_id_;
+
+ // protects initiate_fetches_
+ AprMutex initiate_mutex_;
+ // pushed in the main thread; popped in the serf thread.
+ std::vector<SerfFetch*> initiate_fetches_;
+
+ // Allows parent to block till thread exits
+ AprMutex terminate_mutex_;
+ bool thread_done_;
+
+ DISALLOW_COPY_AND_ASSIGN(SerfThreadedFetcher);
+};
+
+bool SerfFetch::Start(SerfUrlAsyncFetcher* fetcher) {
+ fetch_start_ms_ = timer_->NowMs();
+ fetcher_ = fetcher;
+ // Parse and validate the URL.
+ if (!ParseUrl()) {
+ return false;
+ }
+
+ apr_status_t status = serf_connection_create2(&connection_,
+ fetcher_->serf_context(),
+ url_,
+ ConnectionSetup, this,
+ ClosedConnection, this,
+ pool_);
+ if (status != APR_SUCCESS) {
+ message_handler_->Error(str_url_.c_str(), 0,
+ "Error status=%d (%s) serf_connection_create2",
+ status, GetAprErrorString(status).c_str());
+ return false;
+ }
+ serf_connection_request_create(connection_, SetupRequest, this);
+
+ // Start the fetch. It will connect to the remote host, send the request,
+ // and accept the response, without blocking.
+ status = serf_context_run(fetcher_->serf_context(), 0, fetcher_->pool());
+
+ if (status == APR_SUCCESS || APR_STATUS_IS_TIMEUP(status)) {
+ return true;
+ } else {
+ message_handler_->Error(str_url_.c_str(), 0,
+ "serf_context_run error status=%d (%s)",
+ status, GetAprErrorString(status).c_str());
+ return false;
+ }
+}
+
+
+// Set up the proxy for all the connections in the context. The proxy is in the
+// format of hostname:port.
+bool SerfUrlAsyncFetcher::SetupProxy(const char* proxy) {
+ apr_status_t status = 0;
+ if (proxy == NULL || *proxy == '\0') {
+ return true; // No proxy to be set.
+ }
+
+ apr_sockaddr_t* proxy_address = NULL;
+ apr_port_t proxy_port;
+ char* proxy_host;
+ char* proxy_scope;
+ status = apr_parse_addr_port(&proxy_host, &proxy_scope, &proxy_port, proxy,
+ pool_);
+ if (status != APR_SUCCESS || proxy_host == NULL || proxy_port == 0 ||
+ (status = apr_sockaddr_info_get(&proxy_address, proxy_host, APR_UNSPEC,
+ proxy_port, 0, pool_)) != APR_SUCCESS) {
+ return false;
+ }
+ serf_config_proxy(serf_context_, proxy_address);
+ return true;
+}
+
+SerfUrlAsyncFetcher::SerfUrlAsyncFetcher(const char* proxy, apr_pool_t* pool,
+ Statistics* statistics, Timer* timer,
+ int64 timeout_ms)
+ : pool_(pool),
+ timer_(timer),
+ mutex_(NULL),
+ serf_context_(NULL),
+ threaded_fetcher_(NULL),
+ outstanding_count_(NULL),
+ request_count_(NULL),
+ byte_count_(NULL),
+ time_duration_ms_(NULL),
+ cancel_count_(NULL),
+ timeout_count_(NULL),
+ timeout_ms_(timeout_ms) {
+ if (statistics != NULL) {
+ request_count_ =
+ statistics->GetVariable(SerfStats::kSerfFetchRequestCount);
+ byte_count_ = statistics->GetVariable(SerfStats::kSerfFetchByteCount);
+ time_duration_ms_ =
+ statistics->GetVariable(SerfStats::kSerfFetchTimeDurationMs);
+ cancel_count_ = statistics->GetVariable(SerfStats::kSerfFetchCancelCount);
+ outstanding_count_ = statistics->GetVariable(
+ SerfStats::kSerfFetchOutstandingCount);
+ timeout_count_ = statistics->GetVariable(
+ SerfStats::kSerfFetchTimeoutCount);
+ }
+ mutex_ = new AprMutex(pool_);
+ serf_context_ = serf_context_create(pool_);
+ threaded_fetcher_ = new SerfThreadedFetcher(this, proxy);
+ if (!SetupProxy(proxy)) {
+ LOG(WARNING) << "Proxy failed: " << proxy;
+ }
+}
+
+SerfUrlAsyncFetcher::SerfUrlAsyncFetcher(SerfUrlAsyncFetcher* parent,
+ const char* proxy)
+ : pool_(parent->pool_),
+ timer_(parent->timer_),
+ mutex_(NULL),
+ serf_context_(NULL),
+ threaded_fetcher_(NULL),
+ outstanding_count_(parent->outstanding_count_),
+ request_count_(parent->request_count_),
+ byte_count_(parent->byte_count_),
+ time_duration_ms_(parent->time_duration_ms_),
+ cancel_count_(parent->cancel_count_),
+ timeout_count_(parent->timeout_count_),
+ timeout_ms_(parent->timeout_ms()) {
+ mutex_ = new AprMutex(pool_);
+ serf_context_ = serf_context_create(pool_);
+ threaded_fetcher_ = NULL;
+ if (!SetupProxy(proxy)) {
+ LOG(WARNING) << "Proxy failed: " << proxy;
+ }
+}
+
+SerfUrlAsyncFetcher::~SerfUrlAsyncFetcher() {
+ CancelOutstandingFetches();
+ STLDeleteElements(&completed_fetches_);
+ int orphaned_fetches = active_fetches_.size();
+ if (orphaned_fetches != 0) {
+ LOG(ERROR) << "SerfFecher destructed with " << orphaned_fetches
+ << " orphaned fetches.";
+ if (outstanding_count_ != NULL) {
+ outstanding_count_->Add(-orphaned_fetches);
+ }
+ if (cancel_count_ != NULL) {
+ cancel_count_->Add(orphaned_fetches);
+ }
+ }
+
+ STLDeleteElements(&active_fetches_);
+ active_fetch_map_.clear();
+ if (threaded_fetcher_ != NULL) {
+ delete threaded_fetcher_;
+ }
+ delete mutex_;
+}
+
+void SerfUrlAsyncFetcher::CancelOutstandingFetches() {
+ // If there are still active requests, cancel them.
+ int num_canceled = 0;
+ {
+ ScopedMutex lock(mutex_);
+ while (!active_fetches_.empty()) {
+ FetchQueueEntry p = active_fetches_.begin();
+ SerfFetch* fetch = *p;
+ LOG(WARNING) << "Aborting fetch of " << fetch->str_url();
+ fetch->Cancel();
+ ++num_canceled;
+ }
+ }
+ if (num_canceled != 0) {
+ if (cancel_count_ != NULL) {
+ cancel_count_->Add(num_canceled);
+ }
+ if (outstanding_count_ != NULL) {
+ outstanding_count_->Add(-num_canceled);
+ }
+ }
+}
+
+bool SerfUrlAsyncFetcher::StreamingFetch(const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* fetched_content_writer,
+ MessageHandler* message_handler,
+ UrlAsyncFetcher::Callback* callback) {
+ SerfFetch* fetch = new SerfFetch(
+ pool_, url, request_headers, response_headers, fetched_content_writer,
+ message_handler, callback, timer_);
+ if (request_count_ != NULL) {
+ request_count_->Add(1);
+ }
+ if (callback->EnableThreaded()) {
+ message_handler->Message(kInfo, "Initiating async fetch for %s",
+ url.c_str());
+ threaded_fetcher_->InitiateFetch(fetch);
+ } else {
+ message_handler->Message(kInfo, "Initiating blocking fetch for %s",
+ url.c_str());
+ bool started = false;
+ {
+ ScopedMutex mutex(mutex_);
+ started = fetch->Start(this);
+ if (started) {
+ active_fetches_.push_back(fetch);
+ active_fetch_map_[fetch] = --active_fetches_.end();
+ if (outstanding_count_ != NULL) {
+ outstanding_count_->Add(1);
+ }
+ } else {
+ delete fetch;
+ }
+ }
+ }
+ return false;
+}
+
+void SerfUrlAsyncFetcher::PrintOutstandingFetches(
+ MessageHandler* handler) const {
+ ScopedMutex mutex(mutex_);
+ for (FetchQueue::const_iterator p = active_fetches_.begin(),
+ e = active_fetches_.end(); p != e; ++p) {
+ SerfFetch* fetch = *p;
+ handler->Message(kInfo, "Outstanding fetch: %s",
+ fetch->str_url());
+ }
+}
+
+// If active_fetches_ is empty, this does no work and returns 0.
+int SerfUrlAsyncFetcher::Poll(int64 microseconds) {
+ // Run serf polling up to microseconds.
+ ScopedMutex mutex(mutex_);
+ if (!active_fetches_.empty()) {
+ apr_status_t status = serf_context_run(serf_context_, microseconds, pool_);
+ STLDeleteElements(&completed_fetches_);
+ if (APR_STATUS_IS_TIMEUP(status)) {
+ // Remove expired fetches from the front of the queue.
+ int64 stale_cutoff = timer_->NowMs() - timeout_ms_;
+ FetchQueueEntry p = active_fetches_.begin(), e = active_fetches_.end();
+ int timeouts = 0;
+ while ((p != e) && ((*p)->fetch_start_ms() < stale_cutoff)) {
+ SerfFetch* fetch = *p;
+ ++p;
+ LOG(WARNING) << "Fetch timed out: " << fetch->str_url();
+ timeouts++;
+ fetch->Cancel();
+ }
+ if ((timeouts > 0) && (timeout_count_ != NULL)) {
+ timeout_count_->Add(timeouts);
+ }
+ }
+ bool success = ((status == APR_SUCCESS) || APR_STATUS_IS_TIMEUP(status));
+ // TODO(jmarantz): provide the success status to the caller if there is a
+ // need.
+ if (!success && !active_fetches_.empty()) {
+ // TODO(jmarantz): I have a new theory that we are getting
+ // behind when our self-directed URL fetches queue up multiple
+ // requests for the same URL, which might be sending the Serf
+ // library into an n^2 situation with its polling, even though
+ // we are using an rb_tree to hold the outstanding fetches. We
+ // should fix this by keeping a map from url->SerfFetch, where
+ // we'd have to store lists of Callback*, ResponseHeader*, Writer* so
+ // all interested parties were updated if and when the fetch finally
+ // completed.
+ //
+ // In the meantime by putting more detail into the log here, we'll
+ // know whether we are accumulating outstanding fetches to make the
+ // server fall over.
+ LOG(ERROR) << "Serf status " << status << " ("
+ << GetAprErrorString(status) << " ) polling for "
+ << active_fetches_.size()
+ << ((threaded_fetcher_ == NULL) ? ": (threaded)"
+ : ": (non-blocking)")
+ << " (" << this << ") for " << microseconds/1.0e6
+ << " seconds";
+ }
+ }
+ return active_fetches_.size();
+}
+
+void SerfUrlAsyncFetcher::FetchComplete(SerfFetch* fetch) {
+ // We do not have a ScopedMutex in FetchComplete, because it is only
+ // called from Poll and CancelOutstandingFetches, which have ScopedMutexes.
+ // Note that SerfFetch::Cancel is currently not exposed from outside this
+ // class.
+ LOG(WARNING) << "FetchComplete(" << fetch->str_url() << ", " << fetch << ")";
+ FetchMapEntry map_entry = active_fetch_map_.find(fetch);
+ CHECK(map_entry != active_fetch_map_.end());
+ active_fetches_.erase(map_entry->second);
+ active_fetch_map_.erase(map_entry);
+ completed_fetches_.push_back(fetch);
+ fetch->message_handler()->Message(kInfo, "Fetch complete: %s",
+ fetch->str_url());
+ if (time_duration_ms_) {
+ time_duration_ms_->Add(fetch->TimeDuration());
+ }
+ if (byte_count_) {
+ byte_count_->Add(fetch->byte_received());
+ }
+ if (outstanding_count_) {
+ outstanding_count_->Add(-1);
+ }
+}
+
+size_t SerfUrlAsyncFetcher::NumActiveFetches() {
+ ScopedMutex lock(mutex_);
+ return active_fetches_.size();
+}
+
+bool SerfUrlAsyncFetcher::WaitForInProgressFetches(
+ int64 max_ms, MessageHandler* message_handler, WaitChoice wait_choice) {
+ bool ret = true;
+ if ((threaded_fetcher_ != NULL) && (wait_choice != kMainlineOnly)) {
+ ret &= threaded_fetcher_->WaitForInProgressFetchesHelper(
+ max_ms, message_handler);
+ }
+ if (wait_choice != kThreadedOnly) {
+ ret &= WaitForInProgressFetchesHelper(max_ms, message_handler);
+ }
+ return ret;
+}
+
+bool SerfUrlAsyncFetcher::WaitForInProgressFetchesHelper(
+ int64 max_ms, MessageHandler* message_handler) {
+ int num_active_fetches = NumActiveFetches();
+ if (num_active_fetches != 0) {
+ int64 now_ms = timer_->NowMs();
+ int64 end_ms = now_ms + max_ms;
+ while ((now_ms < end_ms) && (num_active_fetches != 0)) {
+ int64 remaining_ms = end_ms - now_ms;
+ SERF_DEBUG(LOG(INFO) << "Blocking process waiting " << remaining_ms
+ << "ms for " << active_fetches_.size() << " to complete");
+ SERF_DEBUG(PrintOutstandingFetches(message_handler));
+ Poll(1000 * remaining_ms);
+ now_ms = timer_->NowMs();
+ num_active_fetches = NumActiveFetches();
+ }
+ if (!active_fetches_.empty()) {
+ message_handler->Message(
+ kError, "Serf timeout waiting for %d to complete",
+ num_active_fetches);
+ return false;
+ }
+ SERF_DEBUG(LOG(INFO) << "Serf successfully completed outstanding fetches");
+ }
+ return true;
+}
+void SerfUrlAsyncFetcher::Initialize(Statistics* statistics) {
+ if (statistics != NULL) {
+ statistics->AddVariable(SerfStats::kSerfFetchRequestCount);
+ statistics->AddVariable(SerfStats::kSerfFetchByteCount);
+ statistics->AddVariable(SerfStats::kSerfFetchTimeDurationMs);
+ statistics->AddVariable(SerfStats::kSerfFetchCancelCount);
+ statistics->AddVariable(SerfStats::kSerfFetchOutstandingCount);
+ statistics->AddVariable(SerfStats::kSerfFetchTimeoutCount);
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/apache/serf_url_async_fetcher.h b/trunk/src/net/instaweb/apache/serf_url_async_fetcher.h
new file mode 100644
index 0000000..99f9351
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/serf_url_async_fetcher.h
@@ -0,0 +1,132 @@
+// 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.
+
+#ifndef NET_INSTAWEB_APACHE_SERF_URL_ASYNC_FETCHER_H_
+#define NET_INSTAWEB_APACHE_SERF_URL_ASYNC_FETCHER_H_
+
+#include <set>
+#include <string>
+#include <vector>
+#include <list>
+#include <map>
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include "net/instaweb/util/public/url_async_fetcher.h"
+
+struct apr_pool_t;
+struct serf_context_t;
+struct apr_thread_mutex_t;
+
+namespace net_instaweb {
+
+class AprMutex;
+class Statistics;
+class SerfFetch;
+class SerfThreadedFetcher;
+class Timer;
+class Variable;
+
+struct SerfStats {
+ static const char kSerfFetchRequestCount[];
+ static const char kSerfFetchByteCount[];
+ static const char kSerfFetchTimeDurationMs[];
+ static const char kSerfFetchCancelCount[];
+ static const char kSerfFetchOutstandingCount[];
+ static const char kSerfFetchTimeoutCount[];
+};
+
+class SerfUrlAsyncFetcher : public UrlAsyncFetcher {
+ public:
+ SerfUrlAsyncFetcher(const char* proxy, apr_pool_t* pool,
+ Statistics* statistics, Timer* timer, int64 timeout_ms);
+ SerfUrlAsyncFetcher(SerfUrlAsyncFetcher* parent, const char* proxy);
+ virtual ~SerfUrlAsyncFetcher();
+ static void Initialize(Statistics* statistics);
+ virtual bool StreamingFetch(const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* fetched_content_writer,
+ MessageHandler* message_handler,
+ UrlAsyncFetcher::Callback* callback);
+
+ // Poll the active fetches, returning the number of fetches
+ // still outstanding.
+ int Poll(int64 microseconds);
+
+ enum WaitChoice {
+ kThreadedOnly,
+ kMainlineOnly,
+ kThreadedAndMainline
+ };
+
+ bool WaitForInProgressFetches(int64 max_milliseconds,
+ MessageHandler* message_handler,
+ WaitChoice wait_choice);
+
+ // Remove the completed fetch from the active fetch set, and put it into a
+ // completed fetch list to be cleaned up.
+ void FetchComplete(SerfFetch* fetch);
+ apr_pool_t* pool() const { return pool_; }
+ serf_context_t* serf_context() const { return serf_context_; }
+
+ void PrintOutstandingFetches(MessageHandler* handler) const;
+ virtual int64 timeout_ms() { return timeout_ms_; }
+
+ protected:
+ typedef std::list<SerfFetch*> FetchQueue;
+ typedef std::list<SerfFetch*>::iterator FetchQueueEntry;
+ typedef std::map<SerfFetch*, FetchQueueEntry> FetchMap;
+ typedef std::map<SerfFetch*, FetchQueueEntry>::iterator FetchMapEntry;
+ bool SetupProxy(const char* proxy);
+ size_t NumActiveFetches();
+ void CancelOutstandingFetches();
+ bool WaitForInProgressFetchesHelper(int64 max_ms,
+ MessageHandler* message_handler);
+
+ apr_pool_t* pool_;
+ Timer* timer_;
+
+ // mutex_ protects serf_context_, active_fetches_, and active_fetch_map_.
+ AprMutex* mutex_;
+ serf_context_t* serf_context_;
+ FetchQueue active_fetches_;
+ FetchMap active_fetch_map_;
+
+ typedef std::vector<SerfFetch*> FetchVector;
+ FetchVector completed_fetches_;
+ SerfThreadedFetcher* threaded_fetcher_;
+
+ // This is protected because it's updated along with active_fetches_,
+ // which happens in subclass SerfThreadedFetcher as well as this class.
+ Variable* outstanding_count_;
+
+ private:
+ // Removes the given fetch from both active_fetch_queue_ and
+ // active_fetch_map_. You are expected to already be holding the appropriate
+ // locks. Does not actually delete anything. Returns a queue iterator
+ // pointing to the next fetch after this one in the queue.
+ Variable* request_count_;
+ Variable* byte_count_;
+ Variable* time_duration_ms_;
+ Variable* cancel_count_;
+ Variable* timeout_count_;
+ const int64 timeout_ms_;
+
+ DISALLOW_COPY_AND_ASSIGN(SerfUrlAsyncFetcher);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_APACHE_SERF_URL_ASYNC_FETCHER_H_
diff --git a/trunk/src/net/instaweb/apache/serf_url_async_fetcher_test.cc b/trunk/src/net/instaweb/apache/serf_url_async_fetcher_test.cc
new file mode 100644
index 0000000..b7cedc3
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/serf_url_async_fetcher_test.cc
@@ -0,0 +1,419 @@
+// 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.
+
+#include "net/instaweb/apache/serf_url_async_fetcher.h"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+#include "apr_atomic.h"
+#include "apr_pools.h"
+#include "apr_strings.h"
+#include "apr_version.h"
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "base/stl_util-inl.h"
+#include "net/instaweb/apache/apr_file_system.h"
+#include "net/instaweb/apache/apr_mutex.h"
+#include "net/instaweb/apache/apr_timer.h"
+#include "net/instaweb/util/public/google_message_handler.h"
+#include "net/instaweb/util/public/gzip_inflater.h"
+#include "net/instaweb/util/public/mock_timer.h"
+#include "net/instaweb/util/public/string_writer.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include "net/instaweb/util/public/simple_stats.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/serf/src/serf.h"
+
+namespace net_instaweb {
+
+namespace {
+const char kProxy[] = "";
+const int kMaxMs = 20000;
+const int kThreadedPollMs = 200;
+const int kWaitTimeoutMs = 5 * 1000;
+const int kTimerAdvanceMs = 10;
+const int kFetcherTimeoutMs = 5 * 1000;
+
+class SerfTestCallback : public UrlAsyncFetcher::Callback {
+ public:
+ explicit SerfTestCallback(AprMutex* mutex, const std::string& url)
+ : done_(false),
+ mutex_(mutex),
+ url_(url),
+ enable_threaded_(false),
+ success_(false) {
+ }
+ virtual ~SerfTestCallback() {}
+ virtual void Done(bool success) {
+ ScopedMutex lock(mutex_);
+ CHECK(!done_);
+ done_ = true;
+ success_ = success;
+ }
+ bool IsDone() const {
+ ScopedMutex lock(mutex_);
+ return done_;
+ }
+ virtual bool EnableThreaded() const {
+ return enable_threaded_;
+ }
+ void set_enable_threaded(bool b) { enable_threaded_ = b; }
+ bool success() const { return success_; }
+ private:
+ bool done_;
+ AprMutex* mutex_;
+ std::string url_;
+ bool enable_threaded_;
+ bool success_;
+
+ DISALLOW_COPY_AND_ASSIGN(SerfTestCallback);
+};
+
+} // namespace
+
+class SerfUrlAsyncFetcherTest: public ::testing::Test {
+ public:
+ static void SetUpTestCase() {
+ apr_initialize();
+ atexit(apr_terminate);
+ }
+
+ protected:
+ SerfUrlAsyncFetcherTest() { }
+
+ virtual void SetUp() {
+ apr_pool_create(&pool_, NULL);
+ timer_.reset(new MockTimer(MockTimer::kApr_5_2010_ms));
+ SerfUrlAsyncFetcher::Initialize(&statistics_);
+ serf_url_async_fetcher_.reset(
+ new SerfUrlAsyncFetcher(kProxy, pool_, &statistics_,
+ timer_.get(), kFetcherTimeoutMs));
+ mutex_ = new AprMutex(pool_);
+ AddTestUrl("http://www.google.com/", "<!doctype html>");
+ AddTestUrl("http://www.google.com/favicon.ico",
+ std::string("\000\000\001\000", 4));
+ AddTestUrl("http://www.google.com/intl/en_ALL/images/logo.gif", "GIF");
+ AddTestUrl("http://stevesouders.com/bin/resource.cgi?type=js&sleep=10",
+ "var");
+ prev_done_count = 0;
+ }
+
+ virtual void TearDown() {
+ // Need to free the fetcher before destroy the pool.
+ serf_url_async_fetcher_.reset(NULL);
+ timer_.reset(NULL);
+ STLDeleteElements(&request_headers_);
+ STLDeleteElements(&response_headers_);
+ STLDeleteElements(&contents_);
+ STLDeleteElements(&writers_);
+ STLDeleteElements(&callbacks_);
+ apr_pool_destroy(pool_);
+ delete mutex_;
+ }
+
+ void AddTestUrl(const std::string& url,
+ const std::string& content_start) {
+ urls_.push_back(url);
+ content_starts_.push_back(content_start);
+ request_headers_.push_back(new SimpleMetaData);
+ response_headers_.push_back(new SimpleMetaData);
+ contents_.push_back(new std::string);
+ writers_.push_back(new StringWriter(contents_.back()));
+ callbacks_.push_back(new SerfTestCallback(mutex_, url));
+ }
+
+ void StartFetches(size_t begin, size_t end, bool enable_threaded) {
+ for (size_t idx = begin; idx < end; ++idx) {
+ SerfTestCallback* callback = callbacks_[idx];
+ callback->set_enable_threaded(enable_threaded);
+ serf_url_async_fetcher_->StreamingFetch(
+ urls_[idx], *request_headers_[idx], response_headers_[idx],
+ writers_[idx], &message_handler_, callback);
+ }
+ }
+
+ int OutstandingFetches() {
+ return statistics_.GetVariable(SerfStats::kSerfFetchOutstandingCount)
+ ->Get();
+ }
+
+ int CountCompletedFetches(size_t begin, size_t end) {
+ int completed = 0;
+ for (size_t idx = begin; idx < end; ++idx) {
+ if (callbacks_[idx]->IsDone()) {
+ ++completed;
+ }
+ }
+ return completed;
+ }
+
+ void ValidateFetches(size_t begin, size_t end) {
+ for (size_t idx = begin; idx < end; ++idx) {
+ ASSERT_TRUE(callbacks_[idx]->IsDone());
+ EXPECT_LT(static_cast<size_t>(0), contents_[idx]->size());
+ EXPECT_EQ(200, response_headers_[idx]->status_code());
+ EXPECT_EQ(content_starts_[idx],
+ contents_[idx]->substr(0, content_starts_[idx].size()));
+ }
+ }
+
+ int WaitTillDone(size_t begin, size_t end, int64 delay_ms) {
+ AprTimer timer;
+ bool done = false;
+ int64 now_ms = timer.NowMs();
+ int64 end_ms = now_ms + delay_ms;
+ size_t done_count = 0;
+ while (!done && (now_ms < end_ms)) {
+ int64 remaining_ms = end_ms - now_ms;
+ serf_url_async_fetcher_->Poll(1000 * remaining_ms);
+ done_count = 0;
+ for (size_t idx = begin; idx < end; ++idx) {
+ if (callbacks_[idx]->IsDone()) {
+ ++done_count;
+ }
+ }
+ if (done_count != prev_done_count) {
+ prev_done_count = done_count;
+ done = (done_count == (end - begin));
+ }
+ now_ms = timer.NowMs();
+ }
+ return done_count;
+ }
+
+ int TestFetch(size_t begin, size_t end) {
+ StartFetches(begin, end, false);
+ timer_->advance_ms(kTimerAdvanceMs);
+ int done = WaitTillDone(begin, end, kMaxMs);
+ ValidateFetches(begin, end);
+ return (done == (end - begin));
+ }
+
+ // Valgrind will not allow the async-fetcher thread to run without a sleep.
+ void YieldToThread() {
+ usleep(1);
+ }
+
+ apr_pool_t* pool_;
+ std::vector<std::string> urls_;
+ std::vector<std::string> content_starts_;
+ std::vector<SimpleMetaData*> request_headers_;
+ std::vector<SimpleMetaData*> response_headers_;
+ std::vector<std::string*> contents_;
+ std::vector<StringWriter*> writers_;
+ std::vector<SerfTestCallback*> callbacks_;
+ // The fetcher to be tested.
+ scoped_ptr<SerfUrlAsyncFetcher> serf_url_async_fetcher_;
+ scoped_ptr<MockTimer> timer_;
+ SimpleStats statistics_; // TODO(jmarantz): make this thread-safe
+ GoogleMessageHandler message_handler_;
+ size_t prev_done_count;
+ AprMutex* mutex_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SerfUrlAsyncFetcherTest);
+};
+
+TEST_F(SerfUrlAsyncFetcherTest, FetchOneURL) {
+ EXPECT_TRUE(TestFetch(0, 1));
+ EXPECT_FALSE(response_headers_[0]->IsGzipped());
+ int request_count =
+ statistics_.GetVariable(SerfStats::kSerfFetchRequestCount)->Get();
+ EXPECT_EQ(1, request_count);
+ int bytes_count =
+ statistics_.GetVariable(SerfStats::kSerfFetchByteCount)->Get();
+ // google.com is changing every time, check if we get a rough number.
+ EXPECT_LT(7500, bytes_count);
+ int time_duration =
+ statistics_.GetVariable(SerfStats::kSerfFetchTimeDurationMs)->Get();
+ EXPECT_EQ(kTimerAdvanceMs, time_duration);
+}
+
+TEST_F(SerfUrlAsyncFetcherTest, FetchOneURLGzipped) {
+ request_headers_[0]->Add(HttpAttributes::kAcceptEncoding,
+ HttpAttributes::kGzip);
+
+ // www.google.com doesn't respect our 'gzip' encoding request unless
+ // we have a reasonable user agent.
+ const char kDefaultUserAgent[] =
+ "Mozilla/5.0 (X11; U; Linux x86_64; en-US) "
+ "AppleWebKit/534.0 (KHTML, like Gecko) Chrome/6.0.408.1 Safari/534.0";
+
+ request_headers_[0]->Add(HttpAttributes::kUserAgent,
+ kDefaultUserAgent);
+ StartFetches(0, 1, false);
+ EXPECT_EQ(1, OutstandingFetches());
+ ASSERT_EQ(1, WaitTillDone(0, 1, kMaxMs));
+ ASSERT_TRUE(callbacks_[0]->IsDone());
+ EXPECT_LT(static_cast<size_t>(0), contents_[0]->size());
+ EXPECT_EQ(200, response_headers_[0]->status_code());
+ ASSERT_TRUE(response_headers_[0]->IsGzipped());
+
+ GzipInflater inflater(GzipInflater::kGzip);
+ ASSERT_TRUE(inflater.Init());
+ ASSERT_TRUE(inflater.SetInput(contents_[0]->data(), contents_[0]->size()));
+ ASSERT_TRUE(inflater.HasUnconsumedInput());
+ int size = content_starts_[0].size();
+ scoped_array<char> buf(new char[size]);
+ ASSERT_EQ(size, inflater.InflateBytes(buf.get(), size));
+ EXPECT_EQ(content_starts_[0], std::string(buf.get(), size));
+ EXPECT_EQ(0, OutstandingFetches());
+}
+
+TEST_F(SerfUrlAsyncFetcherTest, FetchTwoURLs) {
+ EXPECT_TRUE(TestFetch(1, 3));
+ int request_count =
+ statistics_.GetVariable(SerfStats::kSerfFetchRequestCount)->Get();
+ EXPECT_EQ(2, request_count);
+ int bytes_count =
+ statistics_.GetVariable(SerfStats::kSerfFetchByteCount)->Get();
+ // Maybe also need a rough number here. We will break if google's icon or logo
+ // changes.
+ EXPECT_EQ(9708, bytes_count);
+ int time_duration =
+ statistics_.GetVariable(SerfStats::kSerfFetchTimeDurationMs)->Get();
+ EXPECT_EQ(2 * kTimerAdvanceMs, time_duration);
+ EXPECT_EQ(0, OutstandingFetches());
+}
+
+TEST_F(SerfUrlAsyncFetcherTest, TestCancelThreeThreaded) {
+ StartFetches(0, 3, true);
+}
+
+TEST_F(SerfUrlAsyncFetcherTest, TestCancelOneThreadedTwoSync) {
+ StartFetches(0, 1, true);
+ StartFetches(1, 3, false);
+}
+
+TEST_F(SerfUrlAsyncFetcherTest, TestCancelTwoThreadedOneSync) {
+ StartFetches(0, 1, false),
+ StartFetches(1, 3, true);
+}
+
+TEST_F(SerfUrlAsyncFetcherTest, TestWaitThreeThreaded) {
+ StartFetches(0, 3, true);
+ serf_url_async_fetcher_->WaitForInProgressFetches(
+ kWaitTimeoutMs, &message_handler_,
+ SerfUrlAsyncFetcher::kThreadedOnly);
+ EXPECT_EQ(0, OutstandingFetches());
+}
+
+TEST_F(SerfUrlAsyncFetcherTest, TestThreeThreadedAsync) {
+ StartFetches(0, 1, true);
+ serf_url_async_fetcher_->WaitForInProgressFetches(
+ 10 /* milliseconds */, &message_handler_,
+ SerfUrlAsyncFetcher::kThreadedOnly);
+ StartFetches(1, 3, true);
+
+ // In this test case, we are not going to call the explicit threaded
+ // wait function, WaitForInProgressFetches. We have initiated async
+ // fetches and we are hoping they will complete within a certain amount
+ // of time. If the system is running well then we they will finish
+ // within a 100ms or so, so we'll loop in 50ms sleep intervals until
+ // we hit a max. We'll give it 5 seconds before declaring failure.
+ const int kMaxSeconds = 5;
+ const int kPollTimeUs = 50000;
+ const int kPollsPerSecond = 1000000 / kPollTimeUs;
+ const int kMaxIters = kMaxSeconds * kPollsPerSecond;
+ int completed = 0;
+ for (int i = 0; (completed < 3) && (i < kMaxIters); ++i) {
+ usleep(kPollTimeUs);
+ completed = CountCompletedFetches(0, 3);
+ }
+
+ // TODO(jmarantz): I have seen this test fail; then pass when it was
+ // run a second time. Find the flakiness and fix it.
+ // Value of: completed
+ // Actual: 0
+ // Expected: 3
+ //
+ // In the meantime, if this fails, re-running will help you determine whether
+ // this is due to your CL or not. It's possible this is associated with a
+ // recent change to the thread loop in serf_url_async_fetcher.cc to use
+ // sleep(1) rather than a mutex to keep from spinning when there is nothing
+ // to do. Maybe a little more than 5 seconds is now needed to complete 3
+ // async fetches.
+ ASSERT_EQ(3, completed) << "Async fetches times out before completing";
+ ValidateFetches(0, 3);
+ EXPECT_EQ(0, OutstandingFetches());
+}
+
+TEST_F(SerfUrlAsyncFetcherTest, TestWaitOneThreadedTwoSync) {
+ StartFetches(0, 1, true);
+ StartFetches(1, 3, false);
+ serf_url_async_fetcher_->WaitForInProgressFetches(
+ kWaitTimeoutMs, &message_handler_,
+ SerfUrlAsyncFetcher::kThreadedAndMainline);
+ EXPECT_EQ(0, OutstandingFetches());
+}
+
+TEST_F(SerfUrlAsyncFetcherTest, TestWaitTwoThreadedOneSync) {
+ StartFetches(0, 1, false),
+ StartFetches(1, 3, true);
+ serf_url_async_fetcher_->WaitForInProgressFetches(
+ kWaitTimeoutMs, &message_handler_,
+ SerfUrlAsyncFetcher::kThreadedAndMainline);
+ EXPECT_EQ(0, OutstandingFetches());
+}
+
+TEST_F(SerfUrlAsyncFetcherTest, TestThreeThreaded) {
+ StartFetches(0, 3, true);
+ int done = 0;
+ for (int i = 0; (done < 3) && (i < 100); ++i) {
+ YieldToThread();
+ done = WaitTillDone(0, 3, kThreadedPollMs);
+ }
+ EXPECT_EQ(3, done);
+ ValidateFetches(0, 3);
+}
+
+TEST_F(SerfUrlAsyncFetcherTest, TestOneThreadedTwoSync) {
+ StartFetches(0, 1, true);
+ StartFetches(1, 3, false);
+ int done = 0;
+ for (int i = 0; (done < 3) && (i < 100); ++i) {
+ YieldToThread();
+ done = WaitTillDone(0, 3, kThreadedPollMs);
+ }
+ EXPECT_EQ(3, done);
+ ValidateFetches(0, 3);
+}
+
+TEST_F(SerfUrlAsyncFetcherTest, TestTwoThreadedOneSync) {
+ StartFetches(0, 1, false);
+ StartFetches(1, 3, true);
+ int done = 0;
+ for (int i = 0; (done < 3) && (i < 100); ++i) {
+ YieldToThread();
+ done = WaitTillDone(0, 3, kThreadedPollMs);
+ }
+ EXPECT_EQ(3, done);
+ ValidateFetches(0, 3);
+}
+
+TEST_F(SerfUrlAsyncFetcherTest, TestTimeout) {
+ StartFetches(3, 4, false);
+ int timeouts =
+ statistics_.GetVariable(SerfStats::kSerfFetchTimeoutCount)->Get();
+ ASSERT_EQ(0, WaitTillDone(3, 4, kThreadedPollMs));
+ timer_->advance_ms(2 * kFetcherTimeoutMs);
+ ASSERT_EQ(1, WaitTillDone(3, 4, kThreadedPollMs));
+ ASSERT_TRUE(callbacks_[3]->IsDone());
+ EXPECT_FALSE(callbacks_[3]->success());
+ EXPECT_EQ(timeouts + 1,
+ statistics_.GetVariable(SerfStats::kSerfFetchTimeoutCount)->Get());
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/apache/serf_url_fetcher.cc b/trunk/src/net/instaweb/apache/serf_url_fetcher.cc
new file mode 100644
index 0000000..b489923
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/serf_url_fetcher.cc
@@ -0,0 +1,71 @@
+// 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.
+
+#include "net/instaweb/apache/serf_url_fetcher.h"
+
+#include <algorithm>
+#include "base/basictypes.h"
+#include "net/instaweb/apache/apr_timer.h"
+#include "net/instaweb/apache/serf_async_callback.h"
+
+namespace net_instaweb {
+
+SerfUrlFetcher::SerfUrlFetcher(int64 fetcher_timeout_ms,
+ SerfUrlAsyncFetcher* async_fetcher)
+ : fetcher_timeout_ms_(fetcher_timeout_ms),
+ async_fetcher_(async_fetcher) {
+}
+
+SerfUrlFetcher::~SerfUrlFetcher() {
+}
+
+bool SerfUrlFetcher::StreamingFetchUrl(const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* fetched_content_writer,
+ MessageHandler* message_handler) {
+ SerfAsyncCallback* callback = new SerfAsyncCallback(
+ response_headers, fetched_content_writer);
+ async_fetcher_->StreamingFetch(
+ url, request_headers, callback->response_headers(),
+ callback->writer(), message_handler, callback);
+
+ // We are counting on the serf async fetcher implementing its own timeouts,
+ // using the same timeout that we have in this class. To avoid a race
+ // we double the timeout in the limit set here and CHECK that the
+ // callback got called by the time our timeout loop exits.
+ AprTimer timer;
+ int64 start_ms = timer.NowMs();
+ int64 now_ms = start_ms;
+ for (int64 end_ms = now_ms + 2 * fetcher_timeout_ms_;
+ !callback->done() && (now_ms < end_ms);
+ now_ms = timer.NowMs()) {
+ int64 remaining_us = std::max(static_cast<int64>(0),
+ 1000 * (end_ms - now_ms));
+ async_fetcher_->Poll(remaining_us);
+ }
+ bool ret = false;
+ if (!callback->done()) {
+ message_handler->Message(
+ kWarning,
+ "Async fetcher allowed %dms to expire without calling its callback",
+ static_cast<int>(now_ms - start_ms));
+ } else {
+ ret = callback->success();
+ }
+ callback->Release();
+ return ret;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/apache/serf_url_fetcher.h b/trunk/src/net/instaweb/apache/serf_url_fetcher.h
new file mode 100644
index 0000000..0512e33
--- /dev/null
+++ b/trunk/src/net/instaweb/apache/serf_url_fetcher.h
@@ -0,0 +1,45 @@
+// 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.
+
+#ifndef NET_INSTAWEB_APACHE_SERF_URL_FETCHER_H_
+#define NET_INSTAWEB_APACHE_SERF_URL_FETCHER_H_
+
+#include <string>
+#include "base/basictypes.h"
+#include "net/instaweb/apache/serf_url_async_fetcher.h"
+#include "net/instaweb/util/public/url_fetcher.h"
+
+namespace net_instaweb {
+
+class SerfUrlFetcher : public UrlFetcher {
+ public:
+ SerfUrlFetcher(int64 fetcher_timeout_ms,
+ SerfUrlAsyncFetcher* async_fetcher);
+ virtual ~SerfUrlFetcher();
+ virtual bool StreamingFetchUrl(const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* fetched_content_writer,
+ MessageHandler* message_handler);
+
+ private:
+ int64 fetcher_timeout_ms_;
+ SerfUrlAsyncFetcher* async_fetcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(SerfUrlFetcher);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_APACHE_SERF_URL_FETCHER_H_
diff --git a/trunk/src/net/instaweb/htmlparse/doctype.cc b/trunk/src/net/instaweb/htmlparse/doctype.cc
new file mode 100644
index 0000000..634d4dd
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/doctype.cc
@@ -0,0 +1,99 @@
+// 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: mdsteele@google.com (Matthew D. Steele)
+
+#include "net/instaweb/htmlparse/public/doctype.h"
+
+#include <vector>
+
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+const DocType DocType::kUnknown(DocType::UNKNOWN);
+const DocType DocType::kHTML5(DocType::HTML_5);
+const DocType DocType::kXHTML5(DocType::XHTML_5);
+const DocType DocType::kHTML4Strict(DocType::HTML_4_STRICT);
+const DocType DocType::kHTML4Transitional(DocType::HTML_4_TRANSITIONAL);
+const DocType DocType::kXHTML11(DocType::XHTML_1_1);
+const DocType DocType::kXHTML10Strict(DocType::XHTML_1_0_STRICT);
+const DocType DocType::kXHTML10Transitional(DocType::XHTML_1_0_TRANSITIONAL);
+
+bool DocType::IsXhtml() const {
+ switch (doctype_) {
+ case XHTML_5:
+ case XHTML_1_1:
+ case XHTML_1_0_STRICT:
+ case XHTML_1_0_TRANSITIONAL:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool DocType::IsVersion5() const {
+ switch (doctype_) {
+ case HTML_5:
+ case XHTML_5:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool DocType::Parse(const StringPiece& directive,
+ const ContentType& content_type) {
+ // Check if this is a doctype directive; don't bother parsing if it isn't.
+ if (!StringCaseStartsWith(directive, "doctype ")) {
+ return false;
+ }
+
+ // Parse the directive.
+ std::vector<std::string> parts;
+ ParseShellLikeString(directive, &parts);
+
+ // Sanity check:
+ DCHECK(parts.size() >= 1);
+ DCHECK(StringCaseEqual(parts[0], "doctype"));
+
+ // Check for known doctypes.
+ // See http://en.wikipedia.org/wiki/DOCTYPE
+ doctype_ = UNKNOWN;
+ if (parts.size() >= 2 && StringCaseEqual(parts[1], "html")) {
+ if (parts.size() == 2) {
+ if (content_type.IsXmlLike()) {
+ doctype_ = XHTML_5;
+ } else {
+ doctype_ = HTML_5;
+ }
+ } else if (parts.size() == 5 && StringCaseEqual(parts[2], "public")) {
+ if (parts[3] == "-//W3C//DTD HTML 4.01//EN") {
+ doctype_ = HTML_4_STRICT;
+ } else if (parts[3] == "-//W3C//DTD HTML 4.01 Transitional//EN") {
+ doctype_ = HTML_4_TRANSITIONAL;
+ } else if (parts[3] == "-//W3C//DTD XHTML 1.1//EN") {
+ doctype_ = XHTML_1_1;
+ } else if (parts[3] == "-//W3C//DTD XHTML 1.0 Strict//EN") {
+ doctype_ = XHTML_1_0_STRICT;
+ } else if (parts[3] == "-//W3C//DTD XHTML 1.0 Transitional//EN") {
+ doctype_ = XHTML_1_0_TRANSITIONAL;
+ }
+ }
+ }
+ return true;
+}
+
+} // net_instaweb
diff --git a/trunk/src/net/instaweb/htmlparse/empty_html_filter.cc b/trunk/src/net/instaweb/htmlparse/empty_html_filter.cc
new file mode 100644
index 0000000..da87896
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/empty_html_filter.cc
@@ -0,0 +1,59 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "public/empty_html_filter.h"
+
+namespace net_instaweb {
+
+EmptyHtmlFilter::EmptyHtmlFilter() {
+}
+
+EmptyHtmlFilter::~EmptyHtmlFilter() {
+}
+
+void EmptyHtmlFilter::StartDocument() {
+}
+
+void EmptyHtmlFilter::EndDocument() {
+}
+
+void EmptyHtmlFilter::StartElement(HtmlElement* element) {
+}
+
+void EmptyHtmlFilter::EndElement(HtmlElement* element) {
+}
+
+void EmptyHtmlFilter::Cdata(HtmlCdataNode* cdata) {
+}
+
+void EmptyHtmlFilter::Comment(HtmlCommentNode* comment) {
+}
+
+void EmptyHtmlFilter::IEDirective(HtmlIEDirectiveNode* directive) {
+}
+
+void EmptyHtmlFilter::Characters(HtmlCharactersNode* characters) {
+}
+
+void EmptyHtmlFilter::Directive(HtmlDirectiveNode* directive) {
+}
+
+void EmptyHtmlFilter::Flush() {
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/htmlparse/file_driver.cc b/trunk/src/net/instaweb/htmlparse/file_driver.cc
new file mode 100644
index 0000000..05d3034
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/file_driver.cc
@@ -0,0 +1,120 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/htmlparse/public/file_driver.h"
+
+#include "net/instaweb/htmlparse/public/file_statistics_log.h"
+#include "net/instaweb/htmlparse/public/html_writer_filter.h"
+#include "net/instaweb/util/public/content_type.h"
+#include "net/instaweb/util/public/file_system.h"
+#include "net/instaweb/util/public/file_writer.h"
+#include "net/instaweb/util/public/message_handler.h"
+
+namespace {
+
+bool GenerateFilename(
+ const char* extension, const bool keep_old_extension,
+ const char* infilename, std::string* outfilename) {
+ bool ret = false;
+ const char* dot = strrchr(infilename, '.');
+ if (dot != NULL) {
+ outfilename->clear();
+ int base_size = dot - infilename;
+ outfilename->append(infilename, base_size);
+ *outfilename += extension;
+ if (keep_old_extension) {
+ *outfilename += dot;
+ }
+ ret = true;
+ }
+ return ret;
+}
+
+} // namespace
+
+namespace net_instaweb {
+
+FileDriver::FileDriver(HtmlParse* html_parse, FileSystem* file_system)
+ : html_parse_(html_parse),
+ logging_filter_(),
+ html_write_filter_(html_parse_),
+ filters_added_(false),
+ file_system_(file_system) {
+}
+
+bool FileDriver::GenerateOutputFilename(
+ const char* infilename, std::string* outfilename) {
+ return GenerateFilename(".out", true, infilename, outfilename);
+}
+
+bool FileDriver::GenerateStatsFilename(
+ const char* infilename, std::string* outfilename) {
+ return GenerateFilename(".stats", false, infilename, outfilename);
+}
+
+bool FileDriver::ParseFile(const char* infilename,
+ const char* outfilename,
+ const char* statsfilename,
+ MessageHandler* message_handler) {
+ FileSystem::OutputFile* outf =
+ file_system_->OpenOutputFile(outfilename, message_handler);
+ bool ret = false;
+
+ if (outf != NULL) {
+ if (!filters_added_) {
+ filters_added_ = true;
+ html_parse_->AddFilter(&logging_filter_);
+ html_parse_->AddFilter(&html_write_filter_);
+ }
+ logging_filter_.Reset();
+ FileWriter file_writer(outf);
+ html_write_filter_.set_writer(&file_writer);
+ FileSystem::InputFile* f =
+ file_system_->OpenInputFile(infilename, message_handler);
+ if (f != NULL) {
+ // HtmlParser needs a valid HTTP URL to evaluate relative paths,
+ // so we create a dummy URL.
+ std::string dummy_url = StrCat("http://file.name/", infilename);
+ html_parse_->StartParseId(dummy_url, infilename, kContentTypeHtml);
+ char buf[1000];
+ int nread;
+ while ((nread = f->Read(buf, sizeof(buf), message_handler)) > 0) {
+ html_parse_->ParseText(buf, nread);
+ }
+ file_system_->Close(f, message_handler);
+ html_parse_->FinishParse();
+ ret = true;
+ if (statsfilename != NULL) {
+ FileSystem::OutputFile* statsfile =
+ file_system_->OpenOutputFile(statsfilename, message_handler);
+ if (statsfile != NULL) {
+ FileStatisticsLog statslog(statsfile, message_handler);
+ logging_filter_.LogStatistics(&statslog);
+ file_system_->Close(statsfile, message_handler);
+ } else {
+ ret = false;
+ }
+ }
+ }
+ file_system_->Close(outf, message_handler);
+ }
+
+ return ret;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/htmlparse/file_statistics_log.cc b/trunk/src/net/instaweb/htmlparse/file_statistics_log.cc
new file mode 100644
index 0000000..1a3fbb7
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/file_statistics_log.cc
@@ -0,0 +1,59 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#include "net/instaweb/htmlparse/public/file_statistics_log.h"
+
+#include <stdio.h>
+#include "net/instaweb/util/public/file_system.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+// TODO(jmarantz): convert to statistics interface
+
+namespace net_instaweb {
+
+FileStatisticsLog::FileStatisticsLog(FileSystem::OutputFile* file,
+ MessageHandler* message_handler)
+ : file_(file),
+ message_handler_(message_handler) {
+}
+
+FileStatisticsLog::~FileStatisticsLog() {
+}
+
+void FileStatisticsLog::LogStat(const char *stat_name, int value) {
+ // Buffer whole log entry before writing, in case there's interleaving going
+ // on (ie avoid multiple writes for single log entry)
+ std::string buf(stat_name);
+ buf += StrCat(": ", IntegerToString(value), "\n");
+ file_->Write(buf, message_handler_);
+}
+
+void FileStatisticsLog::LogDifference(const char *stat_name,
+ int value1, int value2) {
+ // Buffer whole log entry before writing, in case there's interleaving going
+ // on (ie avoid multiple writes for single log entry)
+ std::string buf(stat_name);
+ buf += StrCat(":\t", IntegerToString(value1), " vs\t",
+ IntegerToString(value2));
+ buf += StrCat("\tdiffer by\t", IntegerToString(value1 - value2), "\n");
+ file_->Write(buf, message_handler_);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/htmlparse/html_element.cc b/trunk/src/net/instaweb/htmlparse/html_element.cc
new file mode 100644
index 0000000..ebf47a0
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/html_element.cc
@@ -0,0 +1,217 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "public/html_element.h"
+
+#include <stdio.h>
+
+#include "net/instaweb/htmlparse/public/html_escape.h"
+#include "net/instaweb/htmlparse/html_event.h"
+#include <string>
+
+namespace net_instaweb {
+
+HtmlElement::HtmlElement(HtmlElement* parent, Atom tag,
+ const HtmlEventListIterator& begin, const HtmlEventListIterator& end)
+ : HtmlNode(parent),
+ sequence_(-1),
+ tag_(tag),
+ begin_(begin),
+ end_(end),
+ close_style_(AUTO_CLOSE),
+ begin_line_number_(-1),
+ end_line_number_(-1) {
+}
+
+HtmlElement::~HtmlElement() {
+ for (int i = 0, n = attribute_size(); i < n; ++i) {
+ delete attributes_[i];
+ }
+}
+
+void HtmlElement::SynthesizeEvents(const HtmlEventListIterator& iter,
+ HtmlEventList* queue) {
+ // We use -1 as a bogus line number, since these events are synthetic.
+ HtmlEvent* start_tag = new HtmlStartElementEvent(this, -1);
+ set_begin(queue->insert(iter, start_tag));
+ HtmlEvent* end_tag = new HtmlEndElementEvent(this, -1);
+ set_end(queue->insert(iter, end_tag));
+}
+
+void HtmlElement::InvalidateIterators(const HtmlEventListIterator& end) {
+ set_begin(end);
+ set_end(end);
+}
+
+void HtmlElement::DeleteAttribute(int i) {
+ std::vector<Attribute*>::iterator iter = attributes_.begin() + i;
+ delete *iter;
+ attributes_.erase(iter);
+}
+
+bool HtmlElement::DeleteAttribute(Atom name) {
+ for (int i = 0; i < attribute_size(); ++i) {
+ const Attribute* attribute = attributes_[i];
+ if (attribute->name() == name) {
+ DeleteAttribute(i);
+ return true;
+ }
+ }
+ return false;
+}
+
+const HtmlElement::Attribute* HtmlElement::FindAttribute(
+ const Atom name) const {
+ const Attribute* ret = NULL;
+ for (int i = 0; i < attribute_size(); ++i) {
+ const Attribute* attribute = attributes_[i];
+ if (attribute->name() == name) {
+ ret = attribute;
+ break;
+ }
+ }
+ return ret;
+}
+
+void HtmlElement::ToString(std::string* buf) const {
+ *buf += "<";
+ *buf += tag_.c_str();
+ for (int i = 0; i < attribute_size(); ++i) {
+ const Attribute& attribute = *attributes_[i];
+ *buf += ' ';
+ *buf += attribute.name().c_str();
+ if (attribute.value() != NULL) {
+ *buf += "=";
+ const char* quote = (attribute.quote() != NULL) ? attribute.quote() : "?";
+ *buf += quote;
+ *buf += attribute.value();
+ *buf += quote;
+ }
+ }
+ switch (close_style_) {
+ case AUTO_CLOSE: *buf += "> (not yet closed)"; break;
+ case IMPLICIT_CLOSE: *buf += ">"; break;
+ case EXPLICIT_CLOSE: *buf += "></";
+ *buf += tag_.c_str();
+ *buf += ">";
+ break;
+ case BRIEF_CLOSE: *buf += "/>"; break;
+ case UNCLOSED: *buf += "> (unclosed)"; break;
+ }
+ if ((begin_line_number_ != -1) || (end_line_number_ != -1)) {
+ *buf += " ";
+ if (begin_line_number_ != -1) {
+ *buf += IntegerToString(begin_line_number_);
+ }
+ *buf += "...";
+ if (end_line_number_ != -1) {
+ *buf += IntegerToString(end_line_number_);
+ }
+ }
+}
+
+void HtmlElement::DebugPrint() const {
+ std::string buf;
+ ToString(&buf);
+ fprintf(stdout, "%s\n", buf.c_str());
+}
+
+void HtmlElement::AddAttribute(const Attribute& src_attr) {
+ Attribute* attr = new Attribute(src_attr.name(), src_attr.value(),
+ src_attr.escaped_value(), src_attr.quote());
+ attributes_.push_back(attr);
+}
+
+void HtmlElement::AddAttribute(Atom name, const StringPiece& value,
+ const char* quote) {
+ std::string buf;
+ Attribute* attr = new Attribute(name, value, HtmlEscape::Escape(value, &buf),
+ quote);
+ attributes_.push_back(attr);
+}
+
+void HtmlElement::AddAttribute(Atom name, int value) {
+ std::string buf = IntegerToString(value);
+ // We include quotes here because XHTML requires them. If it later turns out
+ // we're in HTML, the remove_quotes filter can take them back out.
+ Attribute* attr = new Attribute(name, buf, buf, "\"");
+ attributes_.push_back(attr);
+}
+
+void HtmlElement::AddEscapedAttribute(Atom name,
+ const StringPiece& escaped_value,
+ const char* quote) {
+ std::string buf;
+ Attribute* attr = new Attribute(name,
+ HtmlEscape::Unescape(escaped_value, &buf),
+ escaped_value, quote);
+ attributes_.push_back(attr);
+}
+
+void HtmlElement::Attribute::CopyValue(const StringPiece& src,
+ scoped_array<char>* dst) {
+ if (src.data() == NULL) {
+ // This case indicates attribute without value <tag attr>, as opposed
+ // to data()=="", which implies an empty value <tag attr=>.
+ dst->reset(NULL);
+ } else {
+ char* buf = new char[src.size() + 1];
+ memcpy(buf, src.data(), src.size());
+ buf[src.size()] = '\0';
+ dst->reset(buf);
+ }
+}
+
+HtmlElement::Attribute::Attribute(Atom name, const StringPiece& value,
+ const StringPiece& escaped_value,
+ const char* quote)
+ : name_(name), quote_(quote) {
+ CopyValue(value, &value_);
+ CopyValue(escaped_value, &escaped_value_);
+}
+
+// Modify value of attribute (eg to rewrite dest of src or href).
+// As with the constructor, copies the string in, so caller retains
+// ownership of value.
+void HtmlElement::Attribute::SetValue(const StringPiece& value) {
+ std::string buf;
+ // Note that we execute the lines in this order in case value
+ // is a substring of value_. This copies the value just prior
+ // to deallocation of the old value_.
+ const char* escaped_chars = escaped_value_.get();
+ DCHECK(value.data() + value.size() < escaped_chars ||
+ escaped_chars + strlen(escaped_chars) < value.data())
+ << "Setting unescaped value from substring of escaped value.";
+ CopyValue(HtmlEscape::Escape(value, &buf), &escaped_value_);
+ CopyValue(value, &value_);
+}
+
+void HtmlElement::Attribute::SetEscapedValue(const StringPiece& escaped_value) {
+ std::string buf;
+ // Note that we execute the lines in this order in case value
+ // is a substring of value_. This copies the value just prior
+ // to deallocation of the old value_.
+ const char* value_chars = value_.get();
+ DCHECK(value_chars + strlen(value_chars) < escaped_value.data() ||
+ escaped_value.data() + escaped_value.size() < value_chars)
+ << "Setting escaped value from substring of unescaped value.";
+ CopyValue(HtmlEscape::Unescape(escaped_value, &buf), &value_);
+ CopyValue(escaped_value, &escaped_value_);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/htmlparse/html_escape.cc b/trunk/src/net/instaweb/htmlparse/html_escape.cc
new file mode 100644
index 0000000..7688a57
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/html_escape.cc
@@ -0,0 +1,308 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/htmlparse/public/html_escape.h"
+#include <set>
+
+namespace {
+
+struct HtmlEscapeSequence {
+ const char* sequence;
+ const unsigned char value[3];
+};
+
+// TODO(jmarantz): the multi-byte sequences are not working yet.
+static HtmlEscapeSequence kHtmlEscapeSequences[] = {
+ { "AElig", {0xC6, 0x0} },
+ { "Aacute", {0xC1, 0x0} },
+ { "Acirc", {0xC2, 0x0} },
+ { "Agrave", {0xC0, 0x0} },
+ { "Aring", {0xC5, 0x0} },
+ { "Atilde", {0xC3, 0x0} },
+ { "Auml", {0xC4, 0x0} },
+ { "Ccedil", {0xC7, 0x0} },
+ { "ETH", {0xD0, 0x0} },
+ { "Eacute", {0xC9, 0x0} },
+ { "Ecirc", {0xCA, 0x0} },
+ { "Egrave", {0xC8, 0x0} },
+ { "Euml", {0xCB, 0x0} },
+ { "Iacute", {0xCD, 0x0} },
+ { "Icirc", {0xCE, 0x0} },
+ { "Igrave", {0xCC, 0x0} },
+ { "Iuml", {0xCF, 0x0} },
+ { "Ntilde", {0xD1, 0x0} },
+ { "Oacute", {0xD3, 0x0} },
+ { "Ocirc", {0xD4, 0x0} },
+ { "Ograve", {0xD2, 0x0} },
+ { "Oslash", {0xD8, 0x0} },
+ { "Otilde", {0xD5, 0x0} },
+ { "Ouml", {0xD6, 0x0} },
+ { "THORN", {0xDE, 0x0} },
+ { "Uacute", {0xDA, 0x0} },
+ { "Ucirc", {0xDB, 0x0} },
+ { "Ugrave", {0xD9, 0x0} },
+ { "Uuml", {0xDC, 0x0} },
+ { "Yacute", {0xDD, 0x0} },
+ { "aacute", {0xE1, 0x0} },
+ { "acirc", {0xE2, 0x0} },
+ { "acute", {0xB4, 0x0} },
+ { "aelig", {0xE6, 0x0} },
+ { "agrave", {0xE0, 0x0} },
+ { "amp", {0x26, 0x0} },
+ { "aring", {0xE5, 0x0} },
+ { "atilde", {0xE3, 0x0} },
+ { "auml", {0xE4, 0x0} },
+ { "brvbar", {0xA6, 0x0} },
+ { "ccedil", {0xE7, 0x0} },
+ { "cedil", {0xB8, 0x0} },
+ { "cent", {0xA2, 0x0} },
+ { "copy", {0xA9, 0x0} },
+ { "curren", {0xA4, 0x0} },
+ { "deg", {0xB0, 0x0} },
+ { "divide", {0xF7, 0x0} },
+ { "eacute", {0xE9, 0x0} },
+ { "ecirc", {0xEA, 0x0} },
+ { "egrave", {0xE8, 0x0} },
+ { "eth", {0xF0, 0x0} },
+ { "euml", {0xEB, 0x0} },
+ { "frac12", {0xBD, 0x0} },
+ { "frac14", {0xBC, 0x0} },
+ { "frac34", {0xBE, 0x0} },
+ { "gt", {0x3E, 0x0} },
+ { "iacute", {0xED, 0x0} },
+ { "icirc", {0xEE, 0x0} },
+ { "iexcl", {0xA1, 0x0} },
+ { "igrave", {0xEC, 0x0} },
+ { "iquest", {0xBF, 0x0} },
+ { "iuml", {0xEF, 0x0} },
+ { "laquo", {0xAB, 0x0} },
+ { "lt", {0x3C, 0x0} },
+ { "macr", {0xAF, 0x0} },
+ { "micro", {0xB5, 0x0} },
+ { "middot", {0xB7, 0x0} },
+ { "nbsp", {0xA0, 0x0} },
+ { "not", {0xAC, 0x0} },
+ { "ntilde", {0xF1, 0x0} },
+ { "oacute", {0xF3, 0x0} },
+ { "ocirc", {0xF4, 0x0} },
+ { "ograve", {0xF2, 0x0} },
+ { "ordf", {0xAA, 0x0} },
+ { "ordm", {0xBA, 0x0} },
+ { "oslash", {0xF8, 0x0} },
+ { "otilde", {0xF5, 0x0} },
+ { "ouml", {0xF6, 0x0} },
+ { "para", {0xB6, 0x0} },
+ { "plusmn", {0xB1, 0x0} },
+ { "pound", {0xA3, 0x0} },
+ { "quot", {0x22, 0x0} },
+ { "raquo", {0xBB, 0x0} },
+ { "reg", {0xAE, 0x0} },
+ { "sect", {0xA7, 0x0} },
+ { "shy", {0xAD, 0x0} },
+ { "sup1", {0xB9, 0x0} },
+ { "sup2", {0xB2, 0x0} },
+ { "sup3", {0xB3, 0x0} },
+ { "szlig", {0xDF, 0x0} },
+ { "thorn", {0xFE, 0x0} },
+ { "times", {0xD7, 0x0} },
+ { "uacute", {0xFA, 0x0} },
+ { "ucirc", {0xFB, 0x0} },
+ { "ugrave", {0xF9, 0x0} },
+ { "uml", {0xA8, 0x0} },
+ { "uuml", {0xFC, 0x0} },
+ { "yacute", {0xFD, 0x0} },
+ { "yen", {0xA5, 0x0} },
+ { "yuml", {0xFF, 0x0} }
+};
+
+} // namespace
+
+namespace net_instaweb {
+
+HtmlEscape* HtmlEscape::singleton_ = NULL;
+
+HtmlEscape::HtmlEscape() {
+ typedef std::set<std::string, StringCompareInsensitive> CaseInsensitiveSet;
+ CaseInsensitiveSet case_sensitive_symbols;
+ for (size_t i = 0; i < arraysize(kHtmlEscapeSequences); ++i) {
+ // Put all symbols in the case-sensitive map
+ const HtmlEscapeSequence& seq = kHtmlEscapeSequences[i];
+ unescape_sensitive_map_[seq.sequence] =
+ reinterpret_cast<const char*>(seq.value);
+
+ // Don't populate the case-insensitive map for symbols that we've
+ // already determined are case-sensitive.
+ if (case_sensitive_symbols.find(seq.sequence) ==
+ case_sensitive_symbols.end()) {
+ // If this symbol is already present in the insensitive map, then it
+ // must be case-sensitive. E.g. Æ and æ are distinct.
+ StringStringMapInsensitive::iterator p =
+ unescape_insensitive_map_.find(seq.sequence);
+ if (p != unescape_insensitive_map_.end()) {
+ // As this symbol is case-sensitive, we must remove it from the
+ // case-insensitive map. This way we will report an error for
+ // &Aelig;, rather than Æ or æ unpredictably. Also,
+ // note that this symbol is case-sensitive, and therefore must
+ // be not be ent
+ unescape_insensitive_map_.erase(p);
+ case_sensitive_symbols.insert(seq.sequence);
+ } else {
+ unescape_insensitive_map_[seq.sequence] =
+ reinterpret_cast<const char*>(seq.value);
+ }
+
+ // For now, we will only generate symbolic escaped-names for
+ // single-byte sequences
+ if (strlen(reinterpret_cast<const char*>(seq.value)) == 1) {
+ escape_map_[reinterpret_cast<const char*>(seq.value)] = seq.sequence;
+ }
+ }
+ }
+}
+
+void HtmlEscape::Init() {
+ if (singleton_ == NULL) {
+ singleton_ = new HtmlEscape();
+ }
+}
+
+void HtmlEscape::ShutDown() {
+ if (singleton_ != NULL) {
+ delete singleton_;
+ singleton_ = NULL;
+ }
+}
+
+StringPiece HtmlEscape::UnescapeHelper(const StringPiece& escaped,
+ std::string* buf) const {
+ if (escaped.data() == NULL) {
+ return escaped;
+ }
+ buf->clear();
+
+ // Attribute values may have HTML escapes in them, e.g.
+ // href="host.com/path?v1&v2"
+ // Un-escape the attribute value here before populating the
+ // attribute data structure.
+ std::string escape;
+ int numeric_value = 0;
+ bool accumulate_numeric_code = false;
+ bool hex_mode = false;
+ bool in_escape = false;
+ for (size_t i = 0; i < escaped.size(); ++i) {
+ char ch = escaped[i];
+ bool bogus_escape = false;
+ if (!in_escape) {
+ if (ch == '&') {
+ in_escape = true;
+ escape.clear();
+ numeric_value = 0;
+ accumulate_numeric_code = false;
+ hex_mode = false;
+ } else {
+ *buf += ch;
+ }
+ } else if (escape.empty() && (ch == '#')) {
+ escape += ch;
+ accumulate_numeric_code = true;
+ if (((i + 1) < escaped.size()) && (toupper(escaped[i + 1]) == 'X')) {
+ hex_mode = true;
+ ++i;
+ }
+ } else if (ch == ';') {
+ if (accumulate_numeric_code && (escape.size() > 1)) {
+ *buf += static_cast<char>(numeric_value);
+ } else {
+ // Some symbols are case-sensitive (AElig vs aelig are different
+ // code-points) where as some are case-insensitive (" and
+ // " both work. So do the case-sensitive lookup first, and
+ // if that fails, do an insensitive lookup.
+ StringStringMapSensitive::const_iterator p =
+ unescape_sensitive_map_.find(escape);
+ if (p != unescape_sensitive_map_.end()) {
+ *buf += p->second;
+ // TODO(jmarantz): fix this code for multi-byte sequences.
+ } else {
+ // The sensitive lookup failed, but allow, for example, " to work
+ // in place of "
+ StringStringMapInsensitive::const_iterator q =
+ unescape_insensitive_map_.find(escape);
+ if (q != unescape_insensitive_map_.end()) {
+ *buf += q->second;
+ } else {
+ bogus_escape = true;
+ }
+ }
+ }
+ in_escape = false;
+ } else if (accumulate_numeric_code &&
+ ((hex_mode && !AccumulateHexValue(ch, &numeric_value)) ||
+ (!hex_mode && !AccumulateDecimalValue(ch, &numeric_value)))) {
+ bogus_escape = true;
+ } else {
+ escape += ch;
+ }
+ if (bogus_escape) {
+ // Error("Invalid escape syntax: %s", escape.c_str());
+ *buf += "&";
+ *buf += escape;
+ in_escape = false;
+ *buf += ch;
+ }
+ }
+ if (in_escape) {
+ // Error("Unterminated escape: %s", escape.c_str());
+ *buf += "&";
+ *buf += escape;
+ }
+ return StringPiece(*buf);
+}
+
+StringPiece HtmlEscape::EscapeHelper(const StringPiece& unescaped,
+ std::string* buf) const {
+ if (unescaped.data() == NULL) {
+ return unescaped;
+ }
+ buf->clear();
+
+ std::string char_to_escape;
+ for (size_t i = 0; i < unescaped.size(); ++i) {
+ int ch = unescaped[i];
+ // See http://www.htmlescape.net/htmlescape_tool.html. Single-quote and
+ // semi-colon do not need to be escaped.
+ if ((ch > 127) || (ch < 32) || (ch == '"') || (ch == '&') || (ch == '<') ||
+ (ch == '>')) {
+ char_to_escape.clear();
+ char_to_escape += ch;
+ StringStringMapSensitive::const_iterator p =
+ escape_map_.find(char_to_escape);
+ if (p == escape_map_.end()) {
+ *buf += StringPrintf("&#%02d;", static_cast<int>(ch));
+ } else {
+ *buf += '&';
+ *buf += p->second;
+ *buf += ';';
+ }
+ } else {
+ *buf += unescaped[i];
+ }
+ }
+ return StringPiece(*buf);
+}
+
+} // namespace
diff --git a/trunk/src/net/instaweb/htmlparse/html_event.cc b/trunk/src/net/instaweb/htmlparse/html_event.cc
new file mode 100644
index 0000000..8403472
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/html_event.cc
@@ -0,0 +1,34 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/htmlparse/html_event.h"
+#include <stdio.h>
+#include <string>
+
+namespace net_instaweb {
+
+HtmlEvent::~HtmlEvent() {
+}
+
+void HtmlEvent::DebugPrint() {
+ std::string buf;
+ ToString(&buf);
+ fprintf(stdout, "%s\n", buf.c_str());
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/htmlparse/html_event.h b/trunk/src/net/instaweb/htmlparse/html_event.h
new file mode 100644
index 0000000..3ad21c8
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/html_event.h
@@ -0,0 +1,220 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_HTMLPARSE_HTML_EVENT_H_
+#define NET_INSTAWEB_HTMLPARSE_HTML_EVENT_H_
+
+#include <list>
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/htmlparse/public/html_filter.h"
+#include "net/instaweb/htmlparse/public/html_node.h"
+#include <string>
+
+namespace net_instaweb {
+
+class HtmlEvent {
+ public:
+ explicit HtmlEvent(int line_number) : line_number_(line_number) {
+ }
+ virtual ~HtmlEvent();
+ virtual void Run(HtmlFilter* filter) = 0;
+ virtual void ToString(std::string* buffer) = 0;
+
+ // If this is a StartElement event, returns the HtmlElement that is being
+ // started. Otherwise returns NULL.
+ virtual HtmlElement* GetElementIfStartEvent() { return NULL; }
+
+ // If this is an EndElement event, returns the HtmlElement that is being
+ // ended. Otherwise returns NULL.
+ virtual HtmlElement* GetElementIfEndEvent() { return NULL; }
+
+ virtual HtmlLeafNode* GetLeafNode() { return NULL; }
+ virtual HtmlNode* GetNode() { return NULL; }
+ virtual HtmlCharactersNode* GetCharactersNode() { return NULL; }
+ void DebugPrint();
+
+ int line_number() const { return line_number_; }
+ private:
+ int line_number_;
+
+ DISALLOW_COPY_AND_ASSIGN(HtmlEvent);
+};
+
+class HtmlStartDocumentEvent: public HtmlEvent {
+ public:
+ explicit HtmlStartDocumentEvent(int line_number) : HtmlEvent(line_number) {}
+ virtual void Run(HtmlFilter* filter) { filter->StartDocument(); }
+ virtual void ToString(std::string* str) { *str += "StartDocument"; }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HtmlStartDocumentEvent);
+};
+
+class HtmlEndDocumentEvent: public HtmlEvent {
+ public:
+ explicit HtmlEndDocumentEvent(int line_number) : HtmlEvent(line_number) {}
+ virtual void Run(HtmlFilter* filter) { filter->EndDocument(); }
+ virtual void ToString(std::string* str) { *str += "EndDocument"; }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HtmlEndDocumentEvent);
+};
+
+class HtmlStartElementEvent: public HtmlEvent {
+ public:
+ HtmlStartElementEvent(HtmlElement* element, int line_number)
+ : HtmlEvent(line_number),
+ element_(element) {
+ }
+ virtual void Run(HtmlFilter* filter) { filter->StartElement(element_); }
+ virtual void ToString(std::string* str) {
+ *str += "StartElement ";
+ *str += element_->tag().c_str();
+ }
+ virtual HtmlElement* GetElementIfStartEvent() { return element_; }
+ virtual HtmlElement* GetNode() { return element_; }
+ private:
+ HtmlElement* element_;
+
+ DISALLOW_COPY_AND_ASSIGN(HtmlStartElementEvent);
+};
+
+class HtmlEndElementEvent: public HtmlEvent {
+ public:
+ HtmlEndElementEvent(HtmlElement* element, int line_number)
+ : HtmlEvent(line_number),
+ element_(element) {
+ }
+ virtual void Run(HtmlFilter* filter) { filter->EndElement(element_); }
+ virtual void ToString(std::string* str) {
+ *str += "EndElement ";
+ *str += element_->tag().c_str();
+ }
+ virtual HtmlElement* GetElementIfEndEvent() { return element_; }
+ virtual HtmlElement* GetNode() { return element_; }
+ private:
+ HtmlElement* element_;
+
+ DISALLOW_COPY_AND_ASSIGN(HtmlEndElementEvent);
+};
+
+class HtmlLeafNodeEvent: public HtmlEvent {
+ public:
+ explicit HtmlLeafNodeEvent(int line_number) : HtmlEvent(line_number) { }
+ virtual HtmlNode* GetNode() { return GetLeafNode(); }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HtmlLeafNodeEvent);
+};
+
+class HtmlIEDirectiveEvent: public HtmlLeafNodeEvent {
+ public:
+ HtmlIEDirectiveEvent(HtmlIEDirectiveNode* directive, int line_number)
+ : HtmlLeafNodeEvent(line_number),
+ directive_(directive) {
+ }
+ virtual void Run(HtmlFilter* filter) { filter->IEDirective(directive_); }
+ virtual void ToString(std::string* str) {
+ *str += "IEDirective ";
+ *str += directive_->contents();
+ }
+ virtual HtmlLeafNode* GetLeafNode() { return directive_; }
+ private:
+ HtmlIEDirectiveNode* directive_;
+
+ DISALLOW_COPY_AND_ASSIGN(HtmlIEDirectiveEvent);
+};
+
+class HtmlCdataEvent: public HtmlLeafNodeEvent {
+ public:
+ HtmlCdataEvent(HtmlCdataNode* cdata, int line_number)
+ : HtmlLeafNodeEvent(line_number),
+ cdata_(cdata) {
+ }
+ virtual void Run(HtmlFilter* filter) { filter->Cdata(cdata_); }
+ virtual void ToString(std::string* str) {
+ *str += "Cdata ";
+ *str += cdata_->contents();
+ }
+ virtual HtmlLeafNode* GetLeafNode() { return cdata_; }
+ private:
+ HtmlCdataNode* cdata_;
+
+ DISALLOW_COPY_AND_ASSIGN(HtmlCdataEvent);
+};
+
+class HtmlCommentEvent: public HtmlLeafNodeEvent {
+ public:
+ HtmlCommentEvent(HtmlCommentNode* comment, int line_number)
+ : HtmlLeafNodeEvent(line_number),
+ comment_(comment) {
+ }
+ virtual void Run(HtmlFilter* filter) { filter->Comment(comment_); }
+ virtual void ToString(std::string* str) {
+ *str += "Comment ";
+ *str += comment_->contents();
+ }
+ virtual HtmlLeafNode* GetLeafNode() { return comment_; }
+
+ private:
+ HtmlCommentNode* comment_;
+
+ DISALLOW_COPY_AND_ASSIGN(HtmlCommentEvent);
+};
+
+class HtmlCharactersEvent: public HtmlLeafNodeEvent {
+ public:
+ HtmlCharactersEvent(HtmlCharactersNode* characters, int line_number)
+ : HtmlLeafNodeEvent(line_number),
+ characters_(characters) {
+ }
+ virtual void Run(HtmlFilter* filter) { filter->Characters(characters_); }
+ virtual void ToString(std::string* str) {
+ *str += "Characters ";
+ *str += characters_->contents();
+ }
+ virtual HtmlLeafNode* GetLeafNode() { return characters_; }
+ virtual HtmlCharactersNode* GetCharactersNode() { return characters_; }
+ private:
+ HtmlCharactersNode* characters_;
+
+ DISALLOW_COPY_AND_ASSIGN(HtmlCharactersEvent);
+};
+
+class HtmlDirectiveEvent: public HtmlLeafNodeEvent {
+ public:
+ HtmlDirectiveEvent(HtmlDirectiveNode* directive, int line_number)
+ : HtmlLeafNodeEvent(line_number),
+ directive_(directive) {
+ }
+ virtual void Run(HtmlFilter* filter) { filter->Directive(directive_); }
+ virtual void ToString(std::string* str) {
+ *str += "Directive: ";
+ *str += directive_->contents();
+ }
+ virtual HtmlLeafNode* GetLeafNode() { return directive_; }
+ private:
+ HtmlDirectiveNode* directive_;
+
+ DISALLOW_COPY_AND_ASSIGN(HtmlDirectiveEvent);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_HTMLPARSE_HTML_EVENT_H_
diff --git a/trunk/src/net/instaweb/htmlparse/html_filter.cc b/trunk/src/net/instaweb/htmlparse/html_filter.cc
new file mode 100644
index 0000000..90477f2
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/html_filter.cc
@@ -0,0 +1,29 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "public/html_filter.h"
+
+namespace net_instaweb {
+
+HtmlFilter::HtmlFilter() {
+}
+
+HtmlFilter::~HtmlFilter() {
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/htmlparse/html_lexer.cc b/trunk/src/net/instaweb/htmlparse/html_lexer.cc
new file mode 100644
index 0000000..56b014e
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/html_lexer.cc
@@ -0,0 +1,1045 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/htmlparse/html_lexer.h"
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include "net/instaweb/htmlparse/html_event.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/string_util.h"
+
+namespace {
+// These tags can be specified in documents without a brief "/>",
+// or an explicit </tag>, according to the Chrome Developer Tools console.
+//
+// TODO(jmarantz): Check out
+// http://www.whatwg.org/specs/web-apps/current-work/multipage/
+// syntax.html#optional-tags
+const char* kImplicitlyClosedHtmlTags[] = {
+ "meta", "input", "link", "br", "img", "area", "hr", "wbr", "param", "?xml",
+ NULL
+};
+
+// These tags cannot be closed using the brief syntax; they must
+// be closed by using an explicit </TAG>.
+const char* kNonBriefTerminatedTags[] = {
+ "script", "a", "div", "span", "iframe", "style", "textarea",
+ NULL
+};
+
+// These tags cause the text inside them to retained literally
+// and not interpreted.
+const char* kLiteralTags[] = {
+ "script", "iframe", "textarea", "style",
+ NULL
+};
+
+// These tags do not need to be explicitly closed, but can be. See
+// http://www.w3.org/TR/html5/syntax.html#optional-tags . Note that this
+// is *not* consistent with http://www.w3schools.com/tags/tag_p.asp which
+// claims this tag works the same in XHTML as HTML.
+//
+// TODO(jmarantz): http://www.w3.org/TR/html5/syntax.html#optional-tags
+// specifies complex rules, in some cases, dictating whether the closing
+// elements are optional or not. For now we just will eliminate the
+// messages for any of these tags in any case. These rules are echoed below
+// in case we want to add code to emit the 'unclosed tag' messages when they
+// are appropriate. For now we err on the side of silence.
+const char* kOptionallyClosedTags[] = {
+ // An html element's end tag may be omitted if the html element is not
+ // immediately followed by a comment.
+ "html",
+
+ // A body element's end tag may be omitted if the body element is not
+ // immediately followed by a comment.
+ "body",
+
+ // A li element's end tag may be omitted if the li element is immediately
+ // followed by another li element or if there is no more content in the
+ // parent element.
+ "li",
+
+ // A dt element's end tag may be omitted if the dt element is immediately
+ // followed by another dt element or a dd element.
+ "dt",
+
+ // A dd element's end tag may be omitted if the dd element is immediately
+ // followed by another dd element or a dt element, or if there is no more
+ // content in the parent element.
+ "dd",
+
+ // A p element's end tag may be omitted if the p element is immediately
+ // followed by an address, article, aside, blockquote, dir, div, dl, fieldset,
+ // footer, form, h1, h2, h3, h4, h5, h6, header, hgroup, hr, menu, nav, ol, p,
+ // pre, section, table, or ul, element, or if there is no more content in the
+ // parent element and the parent element is not an a element.
+ "p",
+
+ // An rt element's end tag may be omitted if the rt element is immediately
+ // followed by an rt or rp element, or if there is no more content in the
+ // parent element.
+ "rt",
+
+ // An rp element's end tag may be omitted if the rp element is immediately
+ // followed by an rt or rp element, or if there is no more content in the
+ // parent element.
+ "rp",
+
+ // An optgroup element's end tag may be omitted if the optgroup element is
+ // immediately followed by another optgroup element, or if there is no more
+ // content in the parent element.
+ "optgroup",
+
+ // An option element's end tag may be omitted if the option element is
+ // immediately followed by another option element, or if it is immediately
+ // followed by an optgroup element, or if there is no more content in the
+ // parent element.
+ "option",
+
+ // A colgroup element's end tag may be omitted if the colgroup element is not
+ // immediately followed by a space character or a comment.
+ "colgroup",
+
+ // A thead element's end tag may be omitted if the thead element is
+ // immediately followed by a tbody or tfoot element.
+ "thead",
+
+ // A tbody element's end tag may be omitted if the tbody element is
+ // immediately followed by a tbody or tfoot element, or if there is no more
+ // content in the parent element.
+ "tbody",
+
+ // A tfoot element's end tag may be omitted if the tfoot element is
+ // immediately followed by a tbody element, or if there is no more content in
+ // the parent element.
+ "tfoot",
+
+ // A tr element's end tag may be omitted if the tr element is immediately
+ // followed by another tr element, or if there is no more content in the
+ // parent element.
+ "tr",
+
+ // A td element's end tag may be omitted if the td element is immediately
+ // followed by a td or th element, or if there is no more content in the
+ // parent element.
+ "td",
+
+ // A th element's end tag may be omitted if the th element is immediately
+ // followed by a td or th element, or if there is no more content in the
+ // parent element.
+ "th",
+
+ NULL
+};
+
+// We start our stack-iterations from 1, because we put a NULL into
+// position 0 to reduce special-cases.
+const int kStartStack = 1;
+
+} // namespace
+
+// TODO(jmarantz): support multi-byte encodings
+// TODO(jmarantz): emit close-tags immediately for selected html tags,
+// rather than waiting for the next explicit close-tag to force a rebalance.
+// See http://www.whatwg.org/specs/web-apps/current-work/multipage/
+// syntax.html#optional-tags
+
+namespace net_instaweb {
+
+HtmlLexer::HtmlLexer(HtmlParse* html_parse)
+ : html_parse_(html_parse),
+ state_(START),
+ attr_quote_(""),
+ has_attr_value_(false),
+ element_(NULL),
+ line_(1),
+ tag_start_line_(-1) {
+ for (const char** p = kImplicitlyClosedHtmlTags; *p != NULL; ++p) {
+ implicitly_closed_.insert(html_parse->Intern(*p));
+ }
+ for (const char** p = kNonBriefTerminatedTags; *p != NULL; ++p) {
+ non_brief_terminated_tags_.insert(html_parse->Intern(*p));
+ }
+ for (const char** p = kLiteralTags; *p != NULL; ++p) {
+ literal_tags_.insert(html_parse->Intern(*p));
+ }
+ for (const char** p = kOptionallyClosedTags; *p != NULL; ++p) {
+ optionally_closed_tags_.insert(html_parse->Intern(*p));
+ }
+}
+
+HtmlLexer::~HtmlLexer() {
+}
+
+void HtmlLexer::EvalStart(char c) {
+ if (c == '<') {
+ literal_.resize(literal_.size() - 1);
+ EmitLiteral();
+ literal_ += c;
+ state_ = TAG;
+ tag_start_line_ = line_;
+ } else {
+ state_ = START;
+ }
+}
+
+// Browsers appear to only allow letters for first char in tag name,
+// plus ? for <?xml version="1.0" encoding="UTF-8"?>
+bool HtmlLexer::IsLegalTagFirstChar(char c) {
+ return isalpha(c) || (c == '?');
+}
+
+// ... and letters, digits, unicode and some symbols for subsequent chars.
+// Based on a test of Firefox and Chrome.
+//
+// TODO(jmarantz): revisit these predicates based on
+// http://www.w3.org/TR/REC-xml/#NT-NameChar . This
+// XML spec may or may not inform of us of what we need to do
+// to parse all HTML on the web.
+bool HtmlLexer::IsLegalTagChar(char c) {
+ return (IsI18nChar(c) ||
+ (isalnum(c) || (c == '-') || (c == '#') || (c == '_') || (c == ':')));
+}
+
+bool HtmlLexer::IsLegalAttrNameChar(char c) {
+ return (IsI18nChar(c) ||
+ ((c != '=') && (c != '>') && (c != '/') && (c != '<') &&
+ !isspace(c)));
+}
+
+bool HtmlLexer::IsLegalAttrValChar(char c) {
+ return (IsI18nChar(c) ||
+ ((c != '=') && (c != '>') && (c != '/') && (c != '<') &&
+ (c != '"') && (c != '\'') && !isspace(c)));
+}
+
+// Handle the case where "<" was recently parsed.
+void HtmlLexer::EvalTag(char c) {
+ if (c == '/') {
+ state_ = TAG_CLOSE;
+ } else if (IsLegalTagFirstChar(c)) { // "<x"
+ state_ = TAG_OPEN;
+ token_ += c;
+ } else if (c == '!') {
+ state_ = COMMENT_START1;
+ } else {
+ // Illegal tag syntax; just pass it through as raw characters
+ SyntaxError("Invalid tag syntax: unexpected sequence `<%c'", c);
+ EvalStart(c);
+ }
+}
+
+// Handle the case where "<x" was recently parsed. We will stay in this
+// state as long as we keep seeing legal tag characters, appending to
+// token_ for each character.
+void HtmlLexer::EvalTagOpen(char c) {
+ if (IsLegalTagChar(c)) {
+ token_ += c;
+ } else if (c == '>') {
+ EmitTagOpen(true);
+ } else if (c == '<') {
+ // Chrome transforms "<tag<tag>" into "<tag><tag>";
+ SyntaxError("Invalid tag syntax: expected close tag before opener");
+ EmitTagOpen(true);
+ literal_ = "<"; // will be removed by EvalStart.
+ EvalStart(c);
+ } else if (c == '/') {
+ state_ = TAG_BRIEF_CLOSE;
+ } else if (isspace(c)) {
+ state_ = TAG_ATTRIBUTE;
+ } else {
+ // Some other punctuation. Not sure what to do. Let's run this
+ // on the web and see what breaks & decide what to do. E.g. "<x&"
+ SyntaxError("Invalid character `%c` while parsing tag `%s'",
+ c, token_.c_str());
+ token_.clear();
+ state_ = START;
+ }
+}
+
+// Handle several cases of seeing "/" in the middle of a tag, but after
+// the identifier has been completed. Examples: "<x /" or "<x y/" or "x y=/z".
+void HtmlLexer::EvalTagBriefCloseAttr(char c) {
+ if (c == '>') {
+ FinishAttribute(c, has_attr_value_, true);
+ } else if (isspace(c)) {
+ // "<x y/ ". This can lead to "<x y/ z" where z would be
+ // a new attribute, or "<x y/ >" where the tag would be
+ // closed without adding a new attribute. In either case,
+ // we will be completing this attribute.
+ //
+ // TODO(jmarantz): what about "<x y/ =z>"? I am not sure
+ // sure if this matters, because testing that would require
+ // a browser that could react to a named attribute with a
+ // slash in the name (not the value). But should we wind
+ // up with 1 attributes or 2 for this case? There are probably
+ // more important questions, but if we ever need to answer that
+ // one, this is the place.
+ if (!attr_name_.empty()) {
+ if (has_attr_value_) {
+ // The "/" should be interpreted as the last character in
+ // the attribute, so we must tack it on before making it.
+ attr_value_ += '/';
+ }
+ MakeAttribute(has_attr_value_);
+ }
+ } else {
+ // Slurped www.google.com has
+ // <a href=/advanced_search?hl=en>Advanced Search</a>
+ // So when we first see the "/" it looks like it might
+ // be a brief-close, .e.g. <a href=/>. But when we see
+ // that what follows the '/' is not '>' then we know it's
+ // just part off the attribute name or value. So there's
+ // no need to even warn.
+ if (has_attr_value_) {
+ attr_value_ += '/';
+ state_ = TAG_ATTR_VAL;
+ EvalAttrVal(c);
+ // we know it's not the double-quoted or single-quoted versions
+ // because then we wouldn't have let the '/' get us into the
+ // brief-close state.
+ } else {
+ attr_name_ += '/';
+ state_ = TAG_ATTR_NAME;
+ EvalAttrName(c);
+ }
+ }
+}
+
+// Handle the case where "<x/" was recently parsed, where "x" can
+// be any length tag identifier. Note that we if we anything other
+// than a ">" after this, we will just consider the "/" to be part
+// of the tag identifier, and go back to the TAG_OPEN state.
+void HtmlLexer::EvalTagBriefClose(char c) {
+ if (c == '>') {
+ EmitTagOpen(false);
+ EmitTagBriefClose();
+ } else {
+ std::string expected(literal_.data(), literal_.size() - 1);
+ SyntaxError("Invalid close tag syntax: expected %s>, got %s",
+ expected.c_str(), literal_.c_str());
+ // Recover by returning to the mode from whence we came.
+ if (element_ != NULL) {
+ token_ += '/';
+ state_ = TAG_OPEN;
+ EvalTagOpen(c);
+ } else {
+ // E.g. "<R/A", see testdata/invalid_brief.html.
+ state_ = START;
+ token_.clear();
+ }
+ }
+}
+
+// Handle the case where "</" was recently parsed. This function
+// is also called for "</a ", in which case state will be TAG_CLOSE_TERMINATE.
+// We distinguish that case to report an error on "</a b>".
+void HtmlLexer::EvalTagClose(char c) {
+ if ((state_ != TAG_CLOSE_TERMINATE) && IsLegalTagChar(c)) { // "</x"
+ token_ += c;
+ } else if (isspace(c)) {
+ if (token_.empty()) { // e.g. "</ a>"
+ // just ignore the whitespace. Wait for
+ // the tag-name to begin.
+ } else {
+ // "</a ". Now we are in a state where we can only
+ // accept more whitesapce or a close.
+ state_ = TAG_CLOSE_TERMINATE;
+ }
+ } else if (c == '>') {
+ EmitTagClose(HtmlElement::EXPLICIT_CLOSE);
+ } else {
+ SyntaxError("Invalid tag syntax: expected `>' after `</%s' got `%c'",
+ token_.c_str(), c);
+ token_.clear();
+ EvalStart(c);
+ }
+}
+
+// Handle the case where "<!x" was recently parsed, where x
+// is any illegal tag identifier. We stay in this state until
+// we see the ">", accumulating the directive in token_.
+void HtmlLexer::EvalDirective(char c) {
+ if (c == '>') {
+ EmitDirective();
+ } else {
+ token_ += c;
+ }
+}
+
+// After a partial match of a multi-character lexical sequence, a mismatched
+// character needs to temporarily removed from the retained literal_ before
+// being emitted. Then re-inserted for so that EvalStart can attempt to
+// re-evaluate this character as potentialy starting a new lexical token.
+void HtmlLexer::Restart(char c) {
+ CHECK_LE(1U, literal_.size());
+ CHECK_EQ(c, literal_[literal_.size() - 1]);
+ literal_.resize(literal_.size() - 1);
+ EmitLiteral();
+ literal_ += c;
+ EvalStart(c);
+}
+
+// Handle the case where "<!" was recently parsed.
+void HtmlLexer::EvalCommentStart1(char c) {
+ if (c == '-') {
+ state_ = COMMENT_START2;
+ } else if (c == '[') {
+ state_ = CDATA_START1;
+ } else if (IsLegalTagChar(c)) { // "<!DOCTYPE ... >"
+ state_ = DIRECTIVE;
+ EvalDirective(c);
+ } else {
+ SyntaxError("Invalid comment syntax");
+ Restart(c);
+ }
+}
+
+// Handle the case where "<!-" was recently parsed.
+void HtmlLexer::EvalCommentStart2(char c) {
+ if (c == '-') {
+ state_ = COMMENT_BODY;
+ } else {
+ SyntaxError("Invalid comment syntax");
+ Restart(c);
+ }
+}
+
+// Handle the case where "<!--" was recently parsed. We will stay in
+// this state until we see "-". And even after that we may go back to
+// this state if the "-" is not followed by "->".
+void HtmlLexer::EvalCommentBody(char c) {
+ if (c == '-') {
+ state_ = COMMENT_END1;
+ } else {
+ token_ += c;
+ }
+}
+
+// Handle the case where "-" has been parsed from a comment. If we
+// see another "-" then we go to CommentEnd2, otherwise we go back
+// to the comment state.
+void HtmlLexer::EvalCommentEnd1(char c) {
+ if (c == '-') {
+ state_ = COMMENT_END2;
+ } else {
+ // thought we were ending a comment cause we saw '-', but
+ // now we changed our minds. No worries mate. That
+ // fake-out dash was just part of the comment.
+ token_ += '-';
+ token_ += c;
+ state_ = COMMENT_BODY;
+ }
+}
+
+// Handle the case where "--" has been parsed from a comment.
+void HtmlLexer::EvalCommentEnd2(char c) {
+ if (c == '>') {
+ EmitComment();
+ state_ = START;
+ } else if (c == '-') {
+ // There could be an arbitrarily long stream of dashes before
+ // we see the >. Keep looking.
+ token_ += "-";
+ } else {
+ // thought we were ending a comment cause we saw '--', but
+ // now we changed our minds. No worries mate. Those
+ // fake-out dashes were just part of the comment.
+ token_ += "--";
+ token_ += c;
+ state_ = COMMENT_BODY;
+ }
+}
+
+// Handle the case where "<![" was recently parsed.
+void HtmlLexer::EvalCdataStart1(char c) {
+ if (c == 'C') {
+ state_ = CDATA_START2;
+ } else {
+ SyntaxError("Invalid CDATA syntax");
+ Restart(c);
+ }
+}
+
+// Handle the case where "<![C" was recently parsed.
+void HtmlLexer::EvalCdataStart2(char c) {
+ if (c == 'D') {
+ state_ = CDATA_START3;
+ } else {
+ SyntaxError("Invalid CDATA syntax");
+ Restart(c);
+ }
+}
+
+// Handle the case where "<![CD" was recently parsed.
+void HtmlLexer::EvalCdataStart3(char c) {
+ if (c == 'A') {
+ state_ = CDATA_START4;
+ } else {
+ SyntaxError("Invalid CDATA syntax");
+ Restart(c);
+ }
+}
+
+// Handle the case where "<![CDA" was recently parsed.
+void HtmlLexer::EvalCdataStart4(char c) {
+ if (c == 'T') {
+ state_ = CDATA_START5;
+ } else {
+ SyntaxError("Invalid CDATA syntax");
+ Restart(c);
+ }
+}
+
+// Handle the case where "<![CDAT" was recently parsed.
+void HtmlLexer::EvalCdataStart5(char c) {
+ if (c == 'A') {
+ state_ = CDATA_START6;
+ } else {
+ SyntaxError("Invalid CDATA syntax");
+ Restart(c);
+ }
+}
+
+// Handle the case where "<![CDATA" was recently parsed.
+void HtmlLexer::EvalCdataStart6(char c) {
+ if (c == '[') {
+ state_ = CDATA_BODY;
+ } else {
+ SyntaxError("Invalid CDATA syntax");
+ Restart(c);
+ }
+}
+
+// Handle the case where "<![CDATA[" was recently parsed. We will stay in
+// this state until we see "]". And even after that we may go back to
+// this state if the "]" is not followed by "]>".
+void HtmlLexer::EvalCdataBody(char c) {
+ if (c == ']') {
+ state_ = CDATA_END1;
+ } else {
+ token_ += c;
+ }
+}
+
+// Handle the case where "]" has been parsed from a cdata. If we
+// see another "]" then we go to CdataEnd2, otherwise we go back
+// to the cdata state.
+void HtmlLexer::EvalCdataEnd1(char c) {
+ if (c == ']') {
+ state_ = CDATA_END2;
+ } else {
+ // thought we were ending a cdata cause we saw ']', but
+ // now we changed our minds. No worries mate. That
+ // fake-out bracket was just part of the cdata.
+ token_ += ']';
+ token_ += c;
+ state_ = CDATA_BODY;
+ }
+}
+
+// Handle the case where "]]" has been parsed from a cdata.
+void HtmlLexer::EvalCdataEnd2(char c) {
+ if (c == '>') {
+ EmitCdata();
+ state_ = START;
+ } else {
+ // thought we were ending a cdata cause we saw ']]', but
+ // now we changed our minds. No worries mate. Those
+ // fake-out brackets were just part of the cdata.
+ token_ += "]]";
+ token_ += c;
+ state_ = CDATA_BODY;
+ }
+}
+
+// Handle the case where a literal tag (script, iframe) was started.
+// This is of lexical significance because we ignore all the special
+// characters until we see "</script>" or "</iframe>".
+void HtmlLexer::EvalLiteralTag(char c) {
+ // Look explicitly for </script> in the literal buffer.
+ // TODO(jmarantz): check for whitespace in unexpected places.
+ if (c == '>') {
+ // expecting "</x>" for tag x.
+ html_parse_->message_handler()->Check(
+ literal_close_.size() > 3, "literal_close_.size() <= 3"); // NOLINT
+ int literal_minus_close_size = literal_.size() - literal_close_.size();
+ if ((literal_minus_close_size >= 0) &&
+ (strcasecmp(literal_.c_str() + literal_minus_close_size,
+ literal_close_.c_str()) == 0)) {
+ // The literal actually starts after the "<script>", and we will
+ // also let it finish before, so chop it off.
+ literal_.resize(literal_minus_close_size);
+ EmitLiteral();
+ token_.clear();
+ // Transform "</script>" into "script" to form close tag.
+ token_.append(literal_close_.c_str() + 2, literal_close_.size() - 3);
+ EmitTagClose(HtmlElement::EXPLICIT_CLOSE);
+ }
+ }
+}
+
+// Emits raw uninterpreted characters.
+void HtmlLexer::EmitLiteral() {
+ if (!literal_.empty()) {
+ html_parse_->AddEvent(new HtmlCharactersEvent(
+ html_parse_->NewCharactersNode(Parent(), literal_), tag_start_line_));
+ literal_.clear();
+ }
+ state_ = START;
+}
+
+void HtmlLexer::EmitComment() {
+ literal_.clear();
+ if ((token_.find("[if IE") != std::string::npos) &&
+ (token_.find("<![endif]") != std::string::npos)) {
+ HtmlIEDirectiveNode* node =
+ html_parse_->NewIEDirectiveNode(Parent(), token_);
+ html_parse_->AddEvent(new HtmlIEDirectiveEvent(node, tag_start_line_));
+ } else {
+ HtmlCommentNode* node = html_parse_->NewCommentNode(Parent(), token_);
+ html_parse_->AddEvent(new HtmlCommentEvent(node, tag_start_line_));
+ }
+ token_.clear();
+ state_ = START;
+}
+
+void HtmlLexer::EmitCdata() {
+ literal_.clear();
+ html_parse_->AddEvent(new HtmlCdataEvent(
+ html_parse_->NewCdataNode(Parent(), token_), tag_start_line_));
+ token_.clear();
+ state_ = START;
+}
+
+// If allow_implicit_close is true, and the element type is one which
+// does not require an explicit termination in HTML, then we will
+// automatically emit a matching 'element close' event.
+void HtmlLexer::EmitTagOpen(bool allow_implicit_close) {
+ literal_.clear();
+ MakeElement();
+ html_parse_->AddElement(element_, tag_start_line_);
+ element_stack_.push_back(element_);
+ if (literal_tags_.find(element_->tag()) != literal_tags_.end()) {
+ state_ = LITERAL_TAG;
+ literal_close_ = "</";
+ literal_close_ += element_->tag().c_str();
+ literal_close_ += ">";
+ } else {
+ state_ = START;
+ }
+
+ Atom tag = element_->tag();
+ if (allow_implicit_close && IsImplicitlyClosedTag(tag)) {
+ token_ = tag.c_str();
+ EmitTagClose(HtmlElement::IMPLICIT_CLOSE);
+ }
+
+ element_ = NULL;
+}
+
+void HtmlLexer::EmitTagBriefClose() {
+ HtmlElement* element = PopElement();
+ html_parse_->CloseElement(element, HtmlElement::BRIEF_CLOSE, line_);
+ state_ = START;
+}
+
+static void toLower(std::string* str) {
+ for (int i = 0, n = str->size(); i < n; ++i) {
+ char& c = (*str)[i];
+ c = tolower(c);
+ }
+}
+
+HtmlElement* HtmlLexer::Parent() const {
+ html_parse_->message_handler()->Check(!element_stack_.empty(),
+ "element_stack_.empty()");
+ return element_stack_.back();
+}
+
+void HtmlLexer::MakeElement() {
+ if (element_ == NULL) {
+ if (token_.empty()) {
+ SyntaxError("Making element with empty tag name");
+ }
+ toLower(&token_);
+ element_ = html_parse_->NewElement(Parent(), html_parse_->Intern(token_));
+ element_->set_begin_line_number(tag_start_line_);
+ token_.clear();
+ }
+}
+
+void HtmlLexer::StartParse(const StringPiece& id,
+ const ContentType& content_type) {
+ line_ = 1;
+ tag_start_line_ = -1;
+ id.CopyToString(&id_);
+ content_type_ = content_type;
+ has_attr_value_ = false;
+ attr_quote_ = "";
+ state_ = START;
+ element_stack_.clear();
+ element_stack_.push_back(NULL);
+ element_ = NULL;
+ token_.clear();
+ attr_name_.clear();
+ attr_value_.clear();
+ literal_.clear();
+ // clear buffers
+}
+
+void HtmlLexer::FinishParse() {
+ if (!token_.empty()) {
+ SyntaxError("End-of-file in mid-token: %s", token_.c_str());
+ token_.clear();
+ }
+ if (!attr_name_.empty()) {
+ SyntaxError("End-of-file in mid-attribute-name: %s", attr_name_.c_str());
+ attr_name_.clear();
+ }
+ if (!attr_value_.empty()) {
+ SyntaxError("End-of-file in mid-attribute-value: %s", attr_value_.c_str());
+ attr_value_.clear();
+ }
+
+ if (!literal_.empty()) {
+ EmitLiteral();
+ }
+
+ // Any unclosed tags? These should be noted.
+ html_parse_->message_handler()->Check(!element_stack_.empty(),
+ "element_stack_.empty()");
+ html_parse_->message_handler()->Check(element_stack_[0] == NULL,
+ "element_stack_[0] != NULL");
+ for (size_t i = kStartStack; i < element_stack_.size(); ++i) {
+ HtmlElement* element = element_stack_[i];
+ Atom tag = element->tag();
+ if (!IsOptionallyClosedTag(tag)) {
+ html_parse_->Info(id_.c_str(), element->begin_line_number(),
+ "End-of-file with open tag: %s",
+ tag.c_str());
+ }
+ }
+ element_stack_.clear();
+ element_stack_.push_back(NULL);
+ element_ = NULL;
+}
+
+void HtmlLexer::MakeAttribute(bool has_value) {
+ html_parse_->message_handler()->Check(element_ != NULL, "element_ == NULL");
+ toLower(&attr_name_);
+ Atom name = html_parse_->Intern(attr_name_);
+ attr_name_.clear();
+ const char* value = NULL;
+ html_parse_->message_handler()->Check(has_value == has_attr_value_,
+ "has_value != has_attr_value_");
+ if (has_value) {
+ value = attr_value_.c_str();
+ has_attr_value_ = false;
+ } else {
+ html_parse_->message_handler()->Check(attr_value_.empty(),
+ "!attr_value_.empty()");
+ }
+ element_->AddEscapedAttribute(name, value, attr_quote_);
+ attr_value_.clear();
+ attr_quote_ = "";
+ state_ = TAG_ATTRIBUTE;
+}
+
+void HtmlLexer::EvalAttribute(char c) {
+ MakeElement();
+ attr_name_.clear();
+ attr_value_.clear();
+ if (c == '>') {
+ EmitTagOpen(true);
+ } else if (c == '<') {
+ FinishAttribute(c, false, false);
+ } else if (c == '/') {
+ state_ = TAG_BRIEF_CLOSE_ATTR;
+ } else if (IsLegalAttrNameChar(c)) {
+ attr_name_ += c;
+ state_ = TAG_ATTR_NAME;
+ } else if (!isspace(c)) {
+ SyntaxError("Unexpected char `%c' in attribute list", c);
+ }
+}
+
+// "<x y" or "<x y ".
+void HtmlLexer::EvalAttrName(char c) {
+ if (c == '=') {
+ state_ = TAG_ATTR_EQ;
+ has_attr_value_ = true;
+ } else if (IsLegalAttrNameChar(c) && (state_ != TAG_ATTR_NAME_SPACE)) {
+ attr_name_ += c;
+ } else if (isspace(c)) {
+ state_ = TAG_ATTR_NAME_SPACE;
+ } else if (c == '>') {
+ MakeAttribute(false);
+ EmitTagOpen(true);
+ } else {
+ if (state_ == TAG_ATTR_NAME_SPACE) {
+ // "<x y z". Now that we see the 'z', we need
+ // to finish 'y' as an attribute, then queue up
+ // 'z' (c) as the start of a new attribute.
+ MakeAttribute(false);
+ state_ = TAG_ATTR_NAME;
+ attr_name_ += c;
+ } else {
+ FinishAttribute(c, false, false);
+ }
+ }
+}
+
+void HtmlLexer::FinishAttribute(char c, bool has_value, bool brief_close) {
+ if (isspace(c)) {
+ MakeAttribute(has_value);
+ state_ = TAG_ATTRIBUTE;
+ } else if (c == '/') {
+ // If / was seen terminating an attribute, without
+ // the closing quote or whitespace, it might just be
+ // part of a syntactically dubious attribute. We'll
+ // hold off completing the attribute till we see the
+ // next character.
+ state_ = TAG_BRIEF_CLOSE_ATTR;
+ } else if ((c == '<') || (c == '>')) {
+ if (!attr_name_.empty()) {
+ if (!brief_close &&
+ (strcmp(attr_name_.c_str(), "/") == 0) && !has_value) {
+ brief_close = true;
+ attr_name_.clear();
+ attr_value_.clear();
+ } else {
+ MakeAttribute(has_value);
+ }
+ }
+ EmitTagOpen(!brief_close);
+ if (brief_close) {
+ EmitTagBriefClose();
+ }
+
+ if (c == '<') {
+ // Chrome transforms "<tag a<tag>" into "<tag a><tag>"; we should too.
+ SyntaxError("Invalid tag syntax: expected close tag before opener");
+ literal_ += '<';
+ EvalStart(c);
+ }
+ has_attr_value_ = false;
+ } else {
+ // Some other funny character within a tag. Probably can't
+ // trust the tag at all. Check the web and see when this
+ // happens.
+ SyntaxError("Unexpected character in attribute: %c", c);
+ MakeAttribute(has_value);
+ has_attr_value_ = false;
+ }
+}
+
+void HtmlLexer::EvalAttrEq(char c) {
+ if (IsLegalAttrValChar(c)) {
+ state_ = TAG_ATTR_VAL;
+ attr_quote_ = "";
+ EvalAttrVal(c);
+ } else if (c == '"') {
+ attr_quote_ = "\"";
+ state_ = TAG_ATTR_VALDQ;
+ } else if (c == '\'') {
+ attr_quote_ = "'";
+ state_ = TAG_ATTR_VALSQ;
+ } else if (isspace(c)) {
+ // ignore -- spaces are allowed between "=" and the value
+ } else {
+ FinishAttribute(c, true, false);
+ }
+}
+
+void HtmlLexer::EvalAttrVal(char c) {
+ if (isspace(c) || (c == '>') || (c == '<')) {
+ FinishAttribute(c, true, false);
+ } else {
+ attr_value_ += c;
+ }
+}
+
+void HtmlLexer::EvalAttrValDq(char c) {
+ if (c == '"') {
+ MakeAttribute(true);
+ } else {
+ attr_value_ += c;
+ }
+}
+
+void HtmlLexer::EvalAttrValSq(char c) {
+ if (c == '\'') {
+ MakeAttribute(true);
+ } else {
+ attr_value_ += c;
+ }
+}
+
+void HtmlLexer::EmitTagClose(HtmlElement::CloseStyle close_style) {
+ toLower(&token_);
+ Atom tag = html_parse_->Intern(token_);
+ HtmlElement* element = PopElementMatchingTag(tag);
+ if (element != NULL) {
+ element->set_end_line_number(line_);
+ html_parse_->CloseElement(element, close_style, line_);
+ } else {
+ SyntaxError("Unexpected close-tag `%s', no tags are open", token_.c_str());
+ EmitLiteral();
+ }
+
+ literal_.clear();
+ token_.clear();
+ state_ = START;
+}
+
+void HtmlLexer::EmitDirective() {
+ literal_.clear();
+ html_parse_->AddEvent(new HtmlDirectiveEvent(
+ html_parse_->NewDirectiveNode(Parent(), token_), line_));
+ // Update the doctype; note that if this is not a doctype directive, Parse()
+ // will return false and not alter doctype_.
+ doctype_.Parse(token_, content_type_);
+ token_.clear();
+ state_ = START;
+}
+
+void HtmlLexer::Parse(const char* text, int size) {
+ for (int i = 0; i < size; ++i) {
+ char c = text[i];
+ if (c == '\n') {
+ ++line_;
+ }
+
+ // By default we keep track of every byte as it comes in.
+ // If we can't accurately parse it, we transmit it as
+ // raw characters to be re-serialized without interpretation,
+ // and good luck to the browser. When we do successfully
+ // parse something, we remove it from the literal.
+ literal_ += c;
+
+ switch (state_) {
+ case START: EvalStart(c); break;
+ case TAG: EvalTag(c); break;
+ case TAG_OPEN: EvalTagOpen(c); break;
+ case TAG_CLOSE: EvalTagClose(c); break;
+ case TAG_CLOSE_TERMINATE: EvalTagClose(c); break;
+ case TAG_BRIEF_CLOSE: EvalTagBriefClose(c); break;
+ case TAG_BRIEF_CLOSE_ATTR: EvalTagBriefCloseAttr(c); break;
+ case COMMENT_START1: EvalCommentStart1(c); break;
+ case COMMENT_START2: EvalCommentStart2(c); break;
+ case COMMENT_BODY: EvalCommentBody(c); break;
+ case COMMENT_END1: EvalCommentEnd1(c); break;
+ case COMMENT_END2: EvalCommentEnd2(c); break;
+ case CDATA_START1: EvalCdataStart1(c); break;
+ case CDATA_START2: EvalCdataStart2(c); break;
+ case CDATA_START3: EvalCdataStart3(c); break;
+ case CDATA_START4: EvalCdataStart4(c); break;
+ case CDATA_START5: EvalCdataStart5(c); break;
+ case CDATA_START6: EvalCdataStart6(c); break;
+ case CDATA_BODY: EvalCdataBody(c); break;
+ case CDATA_END1: EvalCdataEnd1(c); break;
+ case CDATA_END2: EvalCdataEnd2(c); break;
+ case TAG_ATTRIBUTE: EvalAttribute(c); break;
+ case TAG_ATTR_NAME: EvalAttrName(c); break;
+ case TAG_ATTR_NAME_SPACE: EvalAttrName(c); break;
+ case TAG_ATTR_EQ: EvalAttrEq(c); break;
+ case TAG_ATTR_VAL: EvalAttrVal(c); break;
+ case TAG_ATTR_VALDQ: EvalAttrValDq(c); break;
+ case TAG_ATTR_VALSQ: EvalAttrValSq(c); break;
+ case LITERAL_TAG: EvalLiteralTag(c); break;
+ case DIRECTIVE: EvalDirective(c); break;
+ }
+ }
+}
+
+bool HtmlLexer::IsImplicitlyClosedTag(Atom tag) const {
+ return (!doctype_.IsXhtml() &&
+ implicitly_closed_.find(tag) != implicitly_closed_.end());
+}
+
+bool HtmlLexer::TagAllowsBriefTermination(Atom tag) const {
+ return (non_brief_terminated_tags_.find(tag) ==
+ non_brief_terminated_tags_.end());
+}
+
+bool HtmlLexer::IsOptionallyClosedTag(Atom tag) const {
+ return (!doctype_.IsXhtml() &&
+ optionally_closed_tags_.find(tag) != optionally_closed_tags_.end());
+}
+
+void HtmlLexer::DebugPrintStack() {
+ for (size_t i = kStartStack; i < element_stack_.size(); ++i) {
+ std::string buf;
+ element_stack_[i]->ToString(&buf);
+ fprintf(stdout, "%s\n", buf.c_str());
+ }
+ fflush(stdout);
+}
+
+HtmlElement* HtmlLexer::PopElement() {
+ HtmlElement* element = NULL;
+ if (!element_stack_.empty()) {
+ element = element_stack_.back();
+ element_stack_.pop_back();
+ }
+ return element;
+}
+
+HtmlElement* HtmlLexer::PopElementMatchingTag(Atom tag) {
+ HtmlElement* element = NULL;
+
+ // Search the stack from top to bottom.
+ for (int i = element_stack_.size() - 1; i >= kStartStack; --i) {
+ element = element_stack_[i];
+ if (element->tag() == tag) {
+ // Emit warnings for the tags we are skipping. We have to do
+ // this in reverse order so that we maintain stack discipline.
+ for (int j = element_stack_.size() - 1; j > i; --j) {
+ HtmlElement* skipped = element_stack_[j];
+ // In fact, should we actually perform this optimization ourselves
+ // in a filter to omit closing tags that can be inferred?
+ Atom tag = skipped->tag();
+ if (!IsOptionallyClosedTag(tag)) {
+ html_parse_->Info(id_.c_str(), skipped->begin_line_number(),
+ "Unclosed element `%s'", skipped->tag().c_str());
+ }
+ // Before closing the skipped element, pop it off the stack. Otherwise,
+ // the parent redundancy check in HtmlParse::AddEvent will fail.
+ element_stack_.resize(j);
+ html_parse_->CloseElement(skipped, HtmlElement::UNCLOSED, line_);
+ }
+ element_stack_.resize(i);
+ break;
+ }
+ element = NULL;
+ }
+ return element;
+}
+
+void HtmlLexer::SyntaxError(const char* msg, ...) {
+ va_list args;
+ va_start(args, msg);
+ html_parse_->InfoV(id_.c_str(), line_, msg, args);
+ va_end(args);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/htmlparse/html_lexer.h b/trunk/src/net/instaweb/htmlparse/html_lexer.h
new file mode 100644
index 0000000..dcf428b
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/html_lexer.h
@@ -0,0 +1,223 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_HTMLPARSE_HTML_LEXER_H_
+#define NET_INSTAWEB_HTMLPARSE_HTML_LEXER_H_
+
+#include <stdarg.h>
+#include <set>
+#include <vector>
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/doctype.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/util/public/content_type.h"
+#include "net/instaweb/util/public/printf_format.h"
+#include <string>
+
+namespace net_instaweb {
+
+// Constructs a re-entrant HTML lexer. This lexer minimally parses tags,
+// attributes, and comments. It is intended to parse the Wild West of the
+// Web. It's designed to be tolerant of syntactic transgressions, merely
+// passing through unparseable chunks as Characters.
+//
+// TODO(jmarantz): refactor this with html_parse, so that this class owns
+// the symbol table and the event queue, and no longer needs to mutually
+// depend on HtmlParse. That will make it easier to unit-test.
+class HtmlLexer {
+ public:
+ explicit HtmlLexer(HtmlParse* html_parse);
+ ~HtmlLexer();
+
+ // Initialize a new parse session, id is only used for error messages.
+ void StartParse(const StringPiece& id, const ContentType& content_type);
+
+ // Parse a chunk of text, adding events to the parser by calling
+ // html_parse_->AddEvent(...).
+ void Parse(const char* text, int size);
+
+ // Completes parse, reporting any leftover text as a final HtmlCharacterEvent.
+ void FinishParse();
+
+ // Determines whether a tag should be terminated in HTML.
+ bool IsImplicitlyClosedTag(Atom tag) const;
+
+ // Determines whether a tag can be terminated briefly (e.g. <tag/>)
+ bool TagAllowsBriefTermination(Atom tag) const;
+
+ // Determines whether it's OK to leave a tag unclosed.
+ bool IsOptionallyClosedTag(Atom tag) const;
+
+ // Print element stack to stdout (for debugging).
+ void DebugPrintStack();
+
+ // Returns the current lowest-level parent element in the element stack
+ HtmlElement* Parent() const;
+
+ // Return the current assumed doctype of the document (based on the content
+ // type and any HTML directives encountered so far).
+ const DocType& doctype() const { return doctype_; }
+
+ private:
+ // Most of these routines expect c to be the last character of literal_
+ inline void EvalStart(char c);
+ inline void EvalTag(char c);
+ inline void EvalTagOpen(char c);
+ inline void EvalTagClose(char c);
+ inline void EvalTagCloseTerminate(char c);
+ inline void EvalTagBriefClose(char c);
+ inline void EvalTagBriefCloseAttr(char c);
+ inline void EvalCommentStart1(char c);
+ inline void EvalCommentStart2(char c);
+ inline void EvalCommentBody(char c);
+ inline void EvalCommentEnd1(char c);
+ inline void EvalCommentEnd2(char c);
+ inline void EvalCdataStart1(char c);
+ inline void EvalCdataStart2(char c);
+ inline void EvalCdataStart3(char c);
+ inline void EvalCdataStart4(char c);
+ inline void EvalCdataStart5(char c);
+ inline void EvalCdataStart6(char c);
+ inline void EvalCdataBody(char c);
+ inline void EvalCdataEnd1(char c);
+ inline void EvalCdataEnd2(char c);
+ inline void EvalAttribute(char c);
+ inline void EvalAttrName(char c);
+ inline void EvalAttrEq(char c);
+ inline void EvalAttrVal(char c);
+ inline void EvalAttrValSq(char c);
+ inline void EvalAttrValDq(char c);
+ inline void EvalLiteralTag(char c);
+ inline void EvalDirective(char c);
+
+ void MakeElement();
+ void MakeAttribute(bool has_value);
+ void FinishAttribute(char c, bool has_value, bool brief_close);
+
+ void EmitCdata();
+ void EmitComment();
+ void EmitLiteral();
+ void EmitTagOpen(bool allow_implicit_close);
+ void EmitTagClose(HtmlElement::CloseStyle close_style);
+ void EmitTagBriefClose();
+ void EmitDirective();
+ void Restart(char c);
+
+ // Emits a syntax error message.
+ void SyntaxError(const char* format, ...) INSTAWEB_PRINTF_FORMAT(2, 3);
+
+ // Takes an interned tag, and tries to find a matching HTML element on
+ // the stack. If it finds it, it pops all the intervening elements off
+ // the stack, issuing warnings for each discarded tag, the matching element
+ // is also popped off the stack, and returned.
+ //
+ // If the tag is not matched, then no mutations are done to the stack,
+ // and NULL is returned.
+ //
+ // The tag name should be interned.
+ // TODO(jmarantz): use type system
+ HtmlElement* PopElementMatchingTag(Atom tag);
+
+ HtmlElement* PopElement();
+ void CloseElement(HtmlElement* element, HtmlElement::CloseStyle close_style,
+ int line_nubmer);
+
+ // Minimal i18n analysis. With utf-8 and gb2312 we can do this
+ // context-free, and thus the method can be static. If we add
+ // more encodings we may need to turn this into a non-static method.
+ static inline bool IsI18nChar(char c) {return (((c) & 0x80) != 0); }
+
+ // Determines whether a character can be used in a tag name as first char ...
+ static inline bool IsLegalTagFirstChar(char c);
+ // ... or subsequent char.
+ static inline bool IsLegalTagChar(char c);
+
+ // Determines whether a character can be used in an attribute name.
+ static inline bool IsLegalAttrNameChar(char c);
+
+ // Determines whether a character can be used in an attribute value.
+ static inline bool IsLegalAttrValChar(char c);
+
+ // The lexer is implemented as a pure state machine. There is
+ // no lookahead. The state is understood primarily in this
+ // enum, although there are a few state flavors that are managed
+ // by the other member variables, notably: has_attr_value_ and
+ // attr_name_.empty(). Those could be eliminated by adding
+ // a few more explicit states.
+ enum State {
+ START,
+ TAG, // "<"
+ TAG_CLOSE, // "</"
+ TAG_CLOSE_TERMINATE, // "</x "
+ TAG_OPEN, // "<x"
+ TAG_BRIEF_CLOSE, // "<x/"
+ TAG_BRIEF_CLOSE_ATTR, // "<x /" or "<x y/" or "x y=/z" etc
+ COMMENT_START1, // "<!"
+ COMMENT_START2, // "<!-"
+ COMMENT_BODY, // "<!--"
+ COMMENT_END1, // "-"
+ COMMENT_END2, // "--"
+ CDATA_START1, // "<!["
+ CDATA_START2, // "<![C"
+ CDATA_START3, // "<![CD"
+ CDATA_START4, // "<![CDA"
+ CDATA_START5, // "<![CDAT"
+ CDATA_START6, // "<![CDATA"
+ CDATA_BODY, // "<![CDATA["
+ CDATA_END1, // "]"
+ CDATA_END2, // "]]"
+ TAG_ATTRIBUTE, // "<x "
+ TAG_ATTR_NAME, // "<x y"
+ TAG_ATTR_NAME_SPACE, // "<x y "
+ TAG_ATTR_EQ, // "<x y="
+ TAG_ATTR_VAL, // "<x y=x" value terminated by whitespace or >
+ TAG_ATTR_VALDQ, // '<x y="' value terminated by double-quote
+ TAG_ATTR_VALSQ, // "<x y='" value terminated by single-quote
+ LITERAL_TAG, // "<script " or "<iframe "
+ DIRECTIVE // "<!x"
+ };
+
+ HtmlParse* html_parse_;
+ State state_;
+ std::string token_; // accmulates tag names and comments
+ std::string literal_; // accumulates raw text to pass through
+ std::string attr_name_; // accumulates attribute name
+ std::string attr_value_; // accumulates attribute value
+ const char* attr_quote_; // accumulates quote used to delimit attribute
+ bool has_attr_value_; // distinguishes <a n=> from <a n>
+ HtmlElement* element_; // current element; used to collect attributes
+ int line_;
+ int tag_start_line_; // line at which we last transitioned to TAG state
+ std::string id_;
+ std::string literal_close_; // specific tag go close, e.g </script>
+
+ ContentType content_type_;
+ DocType doctype_;
+
+ AtomSet implicitly_closed_;
+ AtomSet non_brief_terminated_tags_;
+ AtomSet literal_tags_;
+ AtomSet optionally_closed_tags_;
+ std::vector<HtmlElement*> element_stack_;
+
+ DISALLOW_COPY_AND_ASSIGN(HtmlLexer);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_HTMLPARSE_HTML_LEXER_H_
diff --git a/trunk/src/net/instaweb/htmlparse/html_node.cc b/trunk/src/net/instaweb/htmlparse/html_node.cc
new file mode 100644
index 0000000..1f016a6
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/html_node.cc
@@ -0,0 +1,83 @@
+/**
+ * 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: mdsteele@google.com (Matthew D. Steele)
+
+#include "public/html_node.h"
+
+#include "net/instaweb/htmlparse/html_event.h"
+
+namespace net_instaweb {
+
+HtmlNode::~HtmlNode() {}
+
+void HtmlNode::MarkAsDead(const HtmlEventListIterator& end) {
+ live_ = false;
+ InvalidateIterators(end);
+}
+
+HtmlLeafNode::~HtmlLeafNode() {}
+
+void HtmlLeafNode::InvalidateIterators(const HtmlEventListIterator& end) {
+ set_iter(end);
+}
+
+HtmlCdataNode::~HtmlCdataNode() {}
+
+void HtmlCdataNode::SynthesizeEvents(const HtmlEventListIterator& iter,
+ HtmlEventList* queue) {
+ // We use -1 as a bogus line number, since the event is synthetic.
+ HtmlCdataEvent* event = new HtmlCdataEvent(this, -1);
+ set_iter(queue->insert(iter, event));
+}
+
+HtmlCharactersNode::~HtmlCharactersNode() {}
+
+void HtmlCharactersNode::SynthesizeEvents(const HtmlEventListIterator& iter,
+ HtmlEventList* queue) {
+ // We use -1 as a bogus line number, since the event is synthetic.
+ HtmlCharactersEvent* event = new HtmlCharactersEvent(this, -1);
+ set_iter(queue->insert(iter, event));
+}
+
+HtmlCommentNode::~HtmlCommentNode() {}
+
+void HtmlCommentNode::SynthesizeEvents(const HtmlEventListIterator& iter,
+ HtmlEventList* queue) {
+ // We use -1 as a bogus line number, since the event is synthetic.
+ HtmlCommentEvent* event = new HtmlCommentEvent(this, -1);
+ set_iter(queue->insert(iter, event));
+}
+
+HtmlIEDirectiveNode::~HtmlIEDirectiveNode() {}
+
+void HtmlIEDirectiveNode::SynthesizeEvents(const HtmlEventListIterator& iter,
+ HtmlEventList* queue) {
+ // We use -1 as a bogus line number, since the event is synthetic.
+ HtmlIEDirectiveEvent* event = new HtmlIEDirectiveEvent(this, -1);
+ set_iter(queue->insert(iter, event));
+}
+
+HtmlDirectiveNode::~HtmlDirectiveNode() {}
+
+void HtmlDirectiveNode::SynthesizeEvents(const HtmlEventListIterator& iter,
+ HtmlEventList* queue) {
+ // We use -1 as a bogus line number, since the event is synthetic.
+ HtmlDirectiveEvent* event = new HtmlDirectiveEvent(this, -1);
+ set_iter(queue->insert(iter, event));
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/htmlparse/html_parse.cc b/trunk/src/net/instaweb/htmlparse/html_parse.cc
new file mode 100644
index 0000000..bc3a4bc
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/html_parse.cc
@@ -0,0 +1,750 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/htmlparse/public/html_parse.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <utility> // for std::pair
+
+#include "base/logging.h"
+#include "net/instaweb/htmlparse/html_event.h"
+#include "net/instaweb/htmlparse/html_lexer.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/htmlparse/public/html_escape.h"
+#include "net/instaweb/htmlparse/public/html_filter.h"
+#include "net/instaweb/util/public/google_url.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include <string>
+#include "net/instaweb/util/public/timer.h"
+
+namespace net_instaweb {
+
+HtmlParse::HtmlParse(MessageHandler* message_handler)
+ : lexer_(NULL), // Can't initialize here, since "this" should not be used
+ // in the initializer list (it generates an error in
+ // Visual Studio builds).
+ sequence_(0),
+ current_(queue_.end()),
+ deleted_current_(false),
+ message_handler_(message_handler),
+ line_number_(1),
+ need_sanity_check_(false),
+ coalesce_characters_(true),
+ need_coalesce_characters_(false),
+ parse_start_time_us_(0),
+ timer_(NULL) {
+ lexer_ = new HtmlLexer(this);
+ HtmlEscape::Init();
+}
+
+HtmlParse::~HtmlParse() {
+ delete lexer_;
+ ClearElements();
+}
+
+void HtmlParse::AddFilter(HtmlFilter* html_filter) {
+ filters_.push_back(html_filter);
+}
+
+HtmlEventListIterator HtmlParse::Last() {
+ HtmlEventListIterator p = queue_.end();
+ --p;
+ return p;
+}
+
+// Checks that the parent provided when creating the event's Node is
+// consistent with the position in the list. An alternative approach
+// here is to use this code and remove the explicit specification of
+// parents when constructing nodes.
+//
+// A complexity we will run into with that approach is that the queue_ is
+// cleared on a Flush, so we cannot reliably derive the correct parent
+// from the queue. However, the Lexer keeps an element stack across
+// flushes, and therefore can keep correct parent pointers. So we have
+// to inject pessimism in this process.
+//
+// Note that we also have sanity checks that run after each filter.
+void HtmlParse::CheckParentFromAddEvent(HtmlEvent* event) {
+ HtmlNode* node = event->GetNode();
+ if (node != NULL) {
+ message_handler_->Check(lexer_->Parent() == node->parent(),
+ "lexer_->Parent() != node->parent()");
+ }
+}
+
+// Testing helper method
+void HtmlParse::AddEvent(HtmlEvent* event) {
+ CheckParentFromAddEvent(event);
+ queue_.push_back(event);
+ need_sanity_check_ = true;
+ need_coalesce_characters_ = true;
+
+ // If this is a leaf-node event, we need to set the iterator of the
+ // corresponding leaf node to point to this event's position in the queue.
+ // If this is an element event, then the iterators of the element will get
+ // set in HtmlParse::AddElement and HtmlParse::CloseElement, so there's no
+ // need to do it here. If this is some other kind of event, there are no
+ // iterators to set.
+ HtmlLeafNode* leaf = event->GetLeafNode();
+ if (leaf != NULL) {
+ leaf->set_iter(Last());
+ message_handler_->Check(IsRewritable(leaf), "!IsRewritable(leaf)");
+ }
+}
+
+// Testing helper method
+void HtmlParse::SetCurrent(HtmlNode* node) {
+ current_ = node->begin();
+}
+
+HtmlCdataNode* HtmlParse::NewCdataNode(HtmlElement* parent,
+ const StringPiece& contents) {
+ HtmlCdataNode* cdata = new HtmlCdataNode(parent, contents, queue_.end());
+ nodes_.insert(cdata);
+ return cdata;
+}
+
+HtmlCharactersNode* HtmlParse::NewCharactersNode(HtmlElement* parent,
+ const StringPiece& literal) {
+ HtmlCharactersNode* characters =
+ new HtmlCharactersNode(parent, literal, queue_.end());
+ nodes_.insert(characters);
+ return characters;
+}
+
+HtmlCommentNode* HtmlParse::NewCommentNode(HtmlElement* parent,
+ const StringPiece& contents) {
+ HtmlCommentNode* comment = new HtmlCommentNode(parent, contents,
+ queue_.end());
+ nodes_.insert(comment);
+ return comment;
+}
+
+HtmlIEDirectiveNode* HtmlParse::NewIEDirectiveNode(
+ HtmlElement* parent, const StringPiece& contents) {
+ HtmlIEDirectiveNode* directive =
+ new HtmlIEDirectiveNode(parent, contents, queue_.end());
+ nodes_.insert(directive);
+ return directive;
+}
+
+HtmlDirectiveNode* HtmlParse::NewDirectiveNode(HtmlElement* parent,
+ const StringPiece& contents) {
+ HtmlDirectiveNode* directive = new HtmlDirectiveNode(parent, contents,
+ queue_.end());
+ nodes_.insert(directive);
+ return directive;
+}
+
+HtmlElement* HtmlParse::NewElement(HtmlElement* parent, Atom tag) {
+ HtmlElement* element = new HtmlElement(parent, tag, queue_.end(),
+ queue_.end());
+ nodes_.insert(element);
+ element->set_sequence(sequence_++);
+ return element;
+}
+
+void HtmlParse::AddElement(HtmlElement* element, int line_number) {
+ HtmlStartElementEvent* event =
+ new HtmlStartElementEvent(element, line_number);
+ AddEvent(event);
+ element->set_begin(Last());
+ element->set_begin_line_number(line_number);
+}
+
+void HtmlParse::StartParseId(const StringPiece& url, const StringPiece& id,
+ const ContentType& content_type) {
+ url.CopyToString(&url_);
+ GURL gurl(url_);
+ // TODO(jmaessen): warn and propagate upwards. This will require
+ // major changes to the callers.
+ message_handler_->Check(gurl.is_valid(), "Invalid url %s", url_.c_str());
+ gurl_.Swap(&gurl);
+ line_number_ = 1;
+ id.CopyToString(&id_);
+ if (timer_ != NULL) {
+ parse_start_time_us_ = timer_->NowUs();
+ InfoHere("HtmlParse::StartParse");
+ }
+ AddEvent(new HtmlStartDocumentEvent(line_number_));
+ lexer_->StartParse(id, content_type);
+}
+
+void HtmlParse::ShowProgress(const char* message) {
+ if (timer_ != NULL) {
+ long delta = static_cast<long>(timer_->NowUs() - parse_start_time_us_);
+ InfoHere("%ldus: HtmlParse::%s", delta, message);
+ }
+}
+
+void HtmlParse::FinishParse() {
+ lexer_->FinishParse();
+ AddEvent(new HtmlEndDocumentEvent(line_number_));
+ Flush();
+ ClearElements();
+ ShowProgress("FinishParse");
+}
+
+void HtmlParse::ParseText(const char* text, int size) {
+ lexer_->Parse(text, size);
+}
+
+// This is factored out of Flush() for testing purposes.
+void HtmlParse::ApplyFilter(HtmlFilter* filter) {
+ if (coalesce_characters_ && need_coalesce_characters_) {
+ CoalesceAdjacentCharactersNodes();
+ need_coalesce_characters_ = false;
+ }
+
+ ShowProgress(StrCat("ApplyFilter:", filter->Name()).c_str());
+ for (current_ = queue_.begin(); current_ != queue_.end(); ) {
+ HtmlEvent* event = *current_;
+ line_number_ = event->line_number();
+ event->Run(filter);
+ deleted_current_ = false;
+ ++current_;
+ }
+ filter->Flush();
+
+ if (need_sanity_check_) {
+ SanityCheck();
+ need_sanity_check_ = false;
+ }
+}
+
+void HtmlParse::CoalesceAdjacentCharactersNodes() {
+ ShowProgress("CoalesceAdjacentCharactersNodes");
+ HtmlCharactersNode* prev = NULL;
+ for (current_ = queue_.begin(); current_ != queue_.end(); ) {
+ HtmlEvent* event = *current_;
+ HtmlCharactersNode* node = event->GetCharactersNode();
+ if ((node != NULL) && (prev != NULL)) {
+ prev->Append(node->contents());
+ current_ = queue_.erase(current_); // returns element after erased
+ delete event;
+ node->MarkAsDead(queue_.end());
+ need_sanity_check_ = true;
+ } else {
+ ++current_;
+ prev = node;
+ }
+ }
+}
+
+void HtmlParse::CheckEventParent(HtmlEvent* event, HtmlElement* expect,
+ HtmlElement* actual) {
+ if ((expect != NULL) && (actual != expect)) {
+ std::string actual_buf, expect_buf, event_buf;
+ if (actual != NULL) {
+ actual->ToString(&actual_buf);
+ } else {
+ actual_buf = "(null)";
+ }
+ expect->ToString(&expect_buf);
+ event->ToString(&event_buf);
+ FatalErrorHere("HtmlElement Parents of %s do not match:\n"
+ "Actual: %s\n"
+ "Expected: %s\n",
+ event_buf.c_str(), actual_buf.c_str(), expect_buf.c_str());
+ }
+}
+
+void HtmlParse::SanityCheck() {
+ ShowProgress("SanityCheck");
+
+ // Sanity check that the Node parent-pointers are consistent with the
+ // begin/end-element events. This is done in a second pass to avoid
+ // confusion when the filter mutates the event-stream. Also note that
+ // a mid-HTML call to HtmlParse::Flush means that we may pop out beyond
+ // the stack we can detect in this event stream. This is represented
+ // here by an empty stack.
+ std::vector<HtmlElement*> element_stack;
+ HtmlElement* expect_parent = NULL;
+ for (current_ = queue_.begin(); current_ != queue_.end(); ++current_) {
+ HtmlEvent* event = *current_;
+
+ // Determine whether the event we are looking at is a StartElement,
+ // EndElement, or a leaf. We manipulate our temporary stack when
+ // we see StartElement and EndElement, and we always test to make
+ // sure the elements have the expected parent based on context, when
+ // we can figure out what the expected parent is.
+ HtmlElement* start_element = event->GetElementIfStartEvent();
+ HtmlElement* actual_parent = NULL;
+ if (start_element != NULL) {
+ CheckEventParent(event, expect_parent, start_element->parent());
+ message_handler_->Check(start_element->begin() == current_,
+ "start_element->begin() != current_");
+ message_handler_->Check(start_element->live(), "!start_element->live()");
+ element_stack.push_back(start_element);
+ expect_parent = start_element;
+ } else {
+ HtmlElement* end_element = event->GetElementIfEndEvent();
+ if (end_element != NULL) {
+ message_handler_->Check(end_element->end() == current_,
+ "end_element->end() != current_");
+ message_handler_->Check(end_element->live(),
+ "!end_element->live()");
+ if (!element_stack.empty()) {
+ // The element stack can be empty on End can happen due
+ // to this sequence:
+ // <tag1>
+ // FLUSH
+ // </tag1> <!-- tag1 close seen with empty stack -->
+ message_handler_->Check(element_stack.back() == end_element,
+ "element_stack.back() != end_element");
+ element_stack.pop_back();
+ }
+ actual_parent = end_element->parent();
+ expect_parent = element_stack.empty() ? NULL : element_stack.back();
+ CheckEventParent(event, expect_parent, end_element->parent());
+ } else {
+ // We only know for sure what the parents are once we have seen
+ // a start_element.
+ HtmlLeafNode* leaf_node = event->GetLeafNode();
+ if (leaf_node != NULL) { // Start/EndDocument are not leaf nodes
+ message_handler_->Check(leaf_node->live(), "!leaf_node->live()");
+ message_handler_->Check(leaf_node->end() == current_,
+ "leaf_node->end() != current_");
+ CheckEventParent(event, expect_parent, leaf_node->parent());
+ }
+ }
+ }
+ }
+}
+
+void HtmlParse::Flush() {
+ ShowProgress("Flush");
+
+ for (size_t i = 0; i < filters_.size(); ++i) {
+ HtmlFilter* filter = filters_[i];
+ ApplyFilter(filter);
+ }
+
+ // Detach all the elements from their events, as we are now invalidating
+ // the events, but not the elements.
+ for (current_ = queue_.begin(); current_ != queue_.end(); ++current_) {
+ HtmlEvent* event = *current_;
+ line_number_ = event->line_number();
+ HtmlElement* element = event->GetElementIfStartEvent();
+ if (element != NULL) {
+ element->set_begin(queue_.end());
+ } else {
+ element = event->GetElementIfEndEvent();
+ if (element != NULL) {
+ element->set_end(queue_.end());
+ } else {
+ HtmlLeafNode* leaf_node = event->GetLeafNode();
+ if (leaf_node != NULL) {
+ leaf_node->set_iter(queue_.end());
+ }
+ }
+ }
+ delete event;
+ }
+ queue_.clear();
+ need_sanity_check_ = false;
+ need_coalesce_characters_ = false;
+}
+
+void HtmlParse::InsertElementBeforeElement(const HtmlNode* existing_node,
+ HtmlNode* new_node) {
+ // begin() == queue_.end() -> this is an invalid element.
+ // TODO(sligocki): Rather than checks, we should probably return this as
+ // the status.
+ message_handler_->Check(existing_node->begin() != queue_.end(),
+ "InsertElementBeforeElement: existing_node invalid");
+ new_node->set_parent(existing_node->parent());
+ InsertElementBeforeEvent(existing_node->begin(), new_node);
+}
+
+void HtmlParse::InsertElementAfterElement(const HtmlNode* existing_node,
+ HtmlNode* new_node) {
+ message_handler_->Check(existing_node->end() != queue_.end(),
+ "InsertElementAfterElement: existing_node invalid");
+ new_node->set_parent(existing_node->parent());
+ InsertElementAfterEvent(existing_node->end(), new_node);
+}
+
+void HtmlParse::PrependChild(const HtmlElement* existing_parent,
+ HtmlNode* new_child) {
+ message_handler_->Check(existing_parent->begin() != queue_.end(),
+ "PrependChild: existing_parent invalid");
+ new_child->set_parent(const_cast<HtmlElement*>(existing_parent));
+ InsertElementAfterEvent(existing_parent->begin(), new_child);
+}
+
+void HtmlParse::AppendChild(const HtmlElement* existing_parent,
+ HtmlNode* new_child) {
+ message_handler_->Check(existing_parent->end() != queue_.end(),
+ "AppendChild: existing_parent invalid");
+ new_child->set_parent(const_cast<HtmlElement*>(existing_parent));
+ InsertElementBeforeEvent(existing_parent->end(), new_child);
+}
+
+void HtmlParse::InsertElementBeforeCurrent(HtmlNode* new_node) {
+ if (deleted_current_) {
+ FatalErrorHere("InsertElementBeforeCurrent after current has been "
+ "deleted.");
+ }
+ if ((new_node->parent() == NULL) && (current_ != queue_.end())) {
+ // Add a parent if one was not provided in new_node. We figure out
+ // what the parent should be by looking at current_. If that's an
+ // EndElement event, then that means that we are adding a new child
+ // of that element. In all other cases, we are adding a sibling.
+ HtmlEvent* current_event = *current_;
+ HtmlElement* end_element = current_event->GetElementIfEndEvent();
+ if (end_element != NULL) {
+ // The node pointed to by Current will be our new parent.
+ new_node->set_parent(end_element);
+ } else {
+ // The node pointed to by Current will be our new sibling, so
+ // we should grab its parent.
+ HtmlNode* node = current_event->GetNode();
+ message_handler_->Check(node != NULL,
+ "Cannot compute parent for new node");
+ new_node->set_parent(node->parent());
+ }
+ }
+ InsertElementBeforeEvent(current_, new_node);
+}
+
+void HtmlParse::InsertElementBeforeEvent(const HtmlEventListIterator& event,
+ HtmlNode* new_node) {
+ need_sanity_check_ = true;
+ need_coalesce_characters_ = true;
+ new_node->SynthesizeEvents(event, &queue_);
+}
+
+void HtmlParse::InsertElementAfterEvent(const HtmlEventListIterator& event,
+ HtmlNode* new_node) {
+ message_handler_->Check(event != queue_.end(), "event == queue_.end()");
+ HtmlEventListIterator next_event = event;
+ ++next_event;
+ InsertElementBeforeEvent(next_event, new_node);
+}
+
+
+void HtmlParse::InsertElementAfterCurrent(HtmlNode* new_node) {
+ if (deleted_current_) {
+ FatalErrorHere("InsertElementAfterCurrent after current has been "
+ "deleted.");
+ }
+ if (current_ == queue_.end()) {
+ FatalErrorHere("InsertElementAfterCurrent called with queue at end.");
+ }
+ ++current_;
+ InsertElementBeforeEvent(current_, new_node);
+
+ // We want to leave current_ pointing to the newly created element.
+ --current_;
+ message_handler_->Check((*current_)->GetNode() == new_node,
+ "(*current_)->GetNode() != new_node");
+}
+
+bool HtmlParse::AddParentToSequence(HtmlNode* first, HtmlNode* last,
+ HtmlElement* new_parent) {
+ bool added = false;
+ HtmlElement* original_parent = first->parent();
+ if (IsRewritable(first) && IsRewritable(last) &&
+ (last->parent() == original_parent) &&
+ (new_parent->begin() == queue_.end()) &&
+ (new_parent->end() == queue_.end())) {
+ InsertElementBeforeEvent(first->begin(), new_parent);
+ // This sequence of checks culminated in inserting the parent's begin
+ // and end before 'first'. Now we must mutate new_parent's end pointer
+ // to insert it after the last->end(). list::insert(iter) inserts
+ // *before* the iter, so we'll increment last->end().
+ HtmlEvent* end_element_event = *new_parent->end();
+ queue_.erase(new_parent->end());
+ HtmlEventListIterator p = last->end();
+ ++p;
+ new_parent->set_end(queue_.insert(p, end_element_event));
+ FixParents(first->begin(), last->end(), new_parent);
+ added = true;
+ need_sanity_check_ = true;
+ need_coalesce_characters_ = true;
+ }
+ return added;
+}
+
+void HtmlParse::FixParents(const HtmlEventListIterator& begin,
+ const HtmlEventListIterator& end_inclusive,
+ HtmlElement* new_parent) {
+ HtmlEvent* event = *begin;
+ HtmlNode* first = event->GetNode();
+ HtmlElement* original_parent = first->parent();
+ // Loop over all the nodes from begin to end, inclusive,
+ // and set the parent pointer for the node, if there is one. A few
+ // event types don't have HtmlNodes, such as Comments and IEDirectives.
+ message_handler_->Check(end_inclusive != queue_.end(),
+ "end_inclusive == queue_.end()");
+ HtmlEventListIterator end = end_inclusive;
+ ++end;
+ for (HtmlEventListIterator p = begin; p != end; ++p) {
+ HtmlNode* node = (*p)->GetNode();
+ if ((node != NULL) && (node->parent() == original_parent)) {
+ node->set_parent(new_parent);
+ }
+ }
+}
+
+bool HtmlParse::MoveCurrentInto(HtmlElement* new_parent) {
+ bool moved = false;
+ HtmlNode* node = (*current_)->GetNode();
+ if ((node != NULL) && (node != new_parent) &&
+ IsRewritable(node) && IsRewritable(new_parent)) {
+ HtmlEventListIterator begin = node->begin();
+ HtmlEventListIterator end = node->end();
+ ++end; // splice is non-inclusive for the 'end' iterator.
+
+ // Manipulate current_ so that when Flush() iterates it lands
+ // you on object after current_'s original position, rather
+ // than re-iterating over the new_parent's EndElement event.
+ current_ = end;
+ queue_.splice(new_parent->end(), queue_, begin, end);
+ --current_;
+
+ // TODO(jmarantz): According to
+ // http://www.cplusplus.com/reference/stl/list/splice/
+ // the moved iterators are no longer valid, and we
+ // are retaining them in the HtmlNode, so we need to fix them.
+ //
+ // However, in practice they appear to remain valid. And
+ // I can't think of a reason they should be invalidated,
+ // as the iterator is a pointer to a node structure with
+ // next/prev pointers. splice can mutate the next/prev pointers
+ // in place.
+ //
+ // See http://stackoverflow.com/questions/143156
+
+ FixParents(node->begin(), node->end(), new_parent);
+ moved = true;
+ need_sanity_check_ = true;
+ need_coalesce_characters_ = true;
+ }
+ return moved;
+}
+
+bool HtmlParse::DeleteElement(HtmlNode* node) {
+ bool deleted = false;
+ if (IsRewritable(node)) {
+ bool done = false;
+ // If node is an HtmlLeafNode, then begin() and end() might be the same.
+ for (HtmlEventListIterator p = node->begin(); !done; ) {
+ // We want to include end, so once p == end we still have to do one more
+ // iteration.
+ done = (p == node->end());
+
+ // Clean up any nested elements/leaves as we get to their 'end' event.
+ HtmlEvent* event = *p;
+ HtmlNode* nested_node = event->GetElementIfEndEvent();
+ if (nested_node == NULL) {
+ nested_node = event->GetLeafNode();
+ }
+ if (nested_node != NULL) {
+ std::set<HtmlNode*>::iterator iter = nodes_.find(nested_node);
+ message_handler_->Check(iter != nodes_.end(), "iter == nodes_.end()");
+ message_handler_->Check(nested_node->live(), "!nested_node->live()");
+ nested_node->MarkAsDead(queue_.end());
+ }
+
+ // Check if we're about to delete the current event.
+ bool move_current = (p == current_);
+ p = queue_.erase(p);
+ if (move_current) {
+ current_ = p; // p is the event *after* the old current.
+ --current_; // Go to *previous* event so that we don't skip p.
+ deleted_current_ = true;
+ line_number_ = (*current_)->line_number();
+ }
+ delete event;
+ }
+
+ // Our iteration should have covered the passed-in element as well.
+ message_handler_->Check(!node->live(), "node->live()");
+ deleted = true;
+ need_sanity_check_ = true;
+ need_coalesce_characters_ = true;
+ }
+ return deleted;
+}
+
+bool HtmlParse::DeleteSavingChildren(HtmlElement* element) {
+ bool deleted = false;
+ if (IsRewritable(element)) {
+ HtmlElement* new_parent = element->parent();
+ HtmlEventListIterator first = element->begin();
+ ++first;
+ HtmlEventListIterator last = element->end();
+ if (first != last) {
+ --last;
+ FixParents(first, last, new_parent);
+ queue_.splice(element->begin(), queue_, first, element->end());
+ need_sanity_check_ = true;
+ need_coalesce_characters_ = true;
+ }
+ deleted = DeleteElement(element);
+ }
+ return deleted;
+}
+
+bool HtmlParse::ReplaceNode(HtmlNode* existing_node, HtmlNode* new_node) {
+ bool replaced = false;
+ if (IsRewritable(existing_node)) {
+ InsertElementBeforeElement(existing_node, new_node);
+ replaced = DeleteElement(existing_node);
+ message_handler_->Check(replaced, "!replaced");
+ }
+ return replaced;
+}
+
+bool HtmlParse::IsRewritable(const HtmlNode* node) const {
+ return IsInEventWindow(node->begin()) && IsInEventWindow(node->end());
+}
+
+bool HtmlParse::IsInEventWindow(const HtmlEventListIterator& iter) const {
+ return iter != queue_.end();
+}
+
+void HtmlParse::ClearElements() {
+ for (std::set<HtmlNode*>::iterator p = nodes_.begin(),
+ e = nodes_.end(); p != e; ++p) {
+ HtmlNode* node = *p;
+ delete node;
+ }
+ nodes_.clear();
+}
+
+void HtmlParse::DebugPrintQueue() {
+ for (HtmlEventList::iterator p = queue_.begin(), e = queue_.end();
+ p != e; ++p) {
+ std::string buf;
+ HtmlEvent* event = *p;
+ event->ToString(&buf);
+ long node_ptr = reinterpret_cast<long>(event->GetNode());
+ if (p == current_) {
+ fprintf(stdout, "* %s (0x%lx)\n", buf.c_str(), node_ptr);
+ } else {
+ fprintf(stdout, " %s (0x%lx)\n", buf.c_str(), node_ptr);
+ }
+ }
+ fflush(stdout);
+}
+
+bool HtmlParse::IsImplicitlyClosedTag(Atom tag) const {
+ return lexer_->IsImplicitlyClosedTag(tag);
+}
+
+bool HtmlParse::TagAllowsBriefTermination(Atom tag) const {
+ return lexer_->TagAllowsBriefTermination(tag);
+}
+
+const DocType& HtmlParse::doctype() const {
+ return lexer_->doctype();
+}
+
+void HtmlParse::InfoV(
+ const char* file, int line, const char *msg, va_list args) {
+ message_handler_->InfoV(file, line, msg, args);
+}
+
+void HtmlParse::WarningV(
+ const char* file, int line, const char *msg, va_list args) {
+ message_handler_->WarningV(file, line, msg, args);
+}
+
+void HtmlParse::ErrorV(
+ const char* file, int line, const char *msg, va_list args) {
+ message_handler_->ErrorV(file, line, msg, args);
+}
+
+void HtmlParse::FatalErrorV(
+ const char* file, int line, const char* msg, va_list args) {
+ message_handler_->FatalErrorV(file, line, msg, args);
+}
+
+void HtmlParse::Info(const char* file, int line, const char* msg, ...) {
+ va_list args;
+ va_start(args, msg);
+ InfoV(file, line, msg, args);
+ va_end(args);
+}
+
+void HtmlParse::Warning(const char* file, int line, const char* msg, ...) {
+ va_list args;
+ va_start(args, msg);
+ WarningV(file, line, msg, args);
+ va_end(args);
+}
+
+void HtmlParse::Error(const char* file, int line, const char* msg, ...) {
+ va_list args;
+ va_start(args, msg);
+ ErrorV(file, line, msg, args);
+ va_end(args);
+}
+
+void HtmlParse::FatalError(const char* file, int line, const char* msg, ...) {
+ va_list args;
+ va_start(args, msg);
+ FatalErrorV(file, line, msg, args);
+ va_end(args);
+}
+
+void HtmlParse::InfoHere(const char* msg, ...) {
+ va_list args;
+ va_start(args, msg);
+ InfoHereV(msg, args);
+ va_end(args);
+}
+
+void HtmlParse::WarningHere(const char* msg, ...) {
+ va_list args;
+ va_start(args, msg);
+ WarningHereV(msg, args);
+ va_end(args);
+}
+
+void HtmlParse::ErrorHere(const char* msg, ...) {
+ va_list args;
+ va_start(args, msg);
+ ErrorHereV(msg, args);
+ va_end(args);
+}
+
+void HtmlParse::FatalErrorHere(const char* msg, ...) {
+ va_list args;
+ va_start(args, msg);
+ FatalErrorHereV(msg, args);
+ va_end(args);
+}
+
+void HtmlParse::CloseElement(
+ HtmlElement* element, HtmlElement::CloseStyle close_style,
+ int line_number) {
+ HtmlEndElementEvent* end_event =
+ new HtmlEndElementEvent(element, line_number);
+ element->set_close_style(close_style);
+ AddEvent(end_event);
+ element->set_end(Last());
+ element->set_end_line_number(line_number);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/htmlparse/html_parse_test.cc b/trunk/src/net/instaweb/htmlparse/html_parse_test.cc
new file mode 100644
index 0000000..8e03c38
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/html_parse_test.cc
@@ -0,0 +1,724 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+// Unit-test the html reader/writer to ensure that a few tricky
+// constructs come through without corruption.
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/empty_html_filter.h"
+#include "net/instaweb/htmlparse/public/html_parse_test_base.h"
+#include "net/instaweb/htmlparse/html_event.h"
+#include "net/instaweb/htmlparse/html_testing_peer.h"
+
+namespace net_instaweb {
+
+class HtmlParseTest : public HtmlParseTestBase {
+ virtual bool AddBody() const { return true; }
+};
+
+class HtmlParseTestNoBody : public HtmlParseTestBase {
+ virtual bool AddBody() const { return false; }
+};
+
+TEST_F(HtmlParseTest, AvoidFalseXmlComment) {
+ ValidateNoChanges("avoid_false_xml_comment",
+ "<script type=\"text/javascript\">\n"
+ "// <!-- this looks like a comment but is not\n"
+ "</script>");
+}
+
+TEST_F(HtmlParseTest, RetainBogusEndTag) {
+ ValidateNoChanges("bogus_end_tag",
+ "<script language=\"JavaScript\" type=\"text/javascript\">\n"
+ "<!--\n"
+ "var s = \"</retain_bogus_end_tag>\";\n"
+ "// -->\n"
+ "</script>");
+}
+
+TEST_F(HtmlParseTest, AmpersandInHref) {
+ // Note that we will escape the "&" in the href.
+ ValidateNoChanges("ampersand_in_href",
+ "<a href=\"http://myhost.com/path?arg1=val1&arg2=val2\">Hello</a>");
+}
+
+TEST_F(HtmlParseTest, CorrectTaggify) {
+ // Don't turn <2 -> <2>
+ ValidateNoChanges("no_taggify_digit", "<p>1<2</p>");
+ ValidateNoChanges("no_taggify_unicode", "<p>☃<☕</p>");
+ ValidateExpected("taggify_letter", "<p>x<y</p>", "<p>x<y></p>");
+
+ ValidateExpected("taggify_letter+digit", "<p>x1<y2</p>", "<p>x1<y2></p>");
+ ValidateExpected("taggify_letter+unicode", "<p>x☃<y☕</p>", "<p>x☃<y☕></p>");
+
+ ValidateNoChanges("no_taggify_digit+letter", "<p>1x<2y</p>");
+ ValidateNoChanges("no_taggify_unicode+letter", "<p>☃x<☕y</p>");
+
+ // Found on http://www.taobao.com/
+ // Don't turn <1... -> <1...>
+ ValidateNoChanges("taobao", "<a>1+1<1母婴全场加1元超值购</a>");
+}
+
+TEST_F(HtmlParseTest, BooleanSpaceCloseInTag) {
+ ValidateExpected("bool_space_close", "<a b >foo</a>", "<a b>foo</a>");
+ ValidateNoChanges("bool_close", "<a b>foo</a>");
+ ValidateExpected("space_close_sq", "<a b='c' >foo</a>", "<a b='c'>foo</a>");
+ ValidateExpected("space_close_dq",
+ "<a b=\"c\" >foo</a>", "<a b=\"c\">foo</a>");
+ ValidateExpected("space_close_nq", "<a b=c >foo</a>", "<a b=c>foo</a>");
+ // Distilled from http://www.gougou.com/
+ // Unclear exactly what we should do here, maybe leave it as it was without
+ // the space?
+ ValidateExpected("allow_semicolon",
+ "<a onclick='return m(this)'; >foo</a>",
+ "<a onclick='return m(this)' ;>foo</a>");
+}
+
+class AttrValuesSaverFilter : public EmptyHtmlFilter {
+ public:
+ AttrValuesSaverFilter() { }
+
+ virtual void StartElement(HtmlElement* element) {
+ for (int i = 0; i < element->attribute_size(); ++i) {
+ value_ += element->attribute(i).value();
+ }
+ }
+
+ const std::string& value() { return value_; }
+ virtual const char* Name() const { return "attr_saver"; }
+
+ private:
+ std::string value_;
+
+ DISALLOW_COPY_AND_ASSIGN(AttrValuesSaverFilter);
+};
+
+TEST_F(HtmlParseTest, EscapedSingleQuote) {
+ AttrValuesSaverFilter attr_saver;
+ html_parse_.AddFilter(&attr_saver);
+ Parse("escaped_single_quote",
+ "<img src='my'single_quoted_image.jpg'/>");
+ EXPECT_EQ("my'single_quoted_image.jpg", attr_saver.value());
+}
+
+TEST_F(HtmlParseTest, UnclosedQuote) {
+ // In this test, the system automatically closes the 'a' tag, which
+ // didn't really get closed in the input text. The exact syntax
+ // of the expected results not critical, as long as the parser recovers
+ // and does not crash.
+ //
+ // TODO(jmarantz): test error reporting.
+ ValidateNoChanges("unclosed_quote",
+ "<div>\n"
+ " <a href=\"http://myhost.com/path?arg1=val1&arg2=val2>Hello</a>\n"
+ "</div>\n"
+ "<p>next token</p>"
+ "</body></html>\n"
+ "\"></a></div>");
+}
+
+TEST_F(HtmlParseTest, NestedDivInBr) {
+ ValidateNoChanges("nested_div_in_br",
+ "<br><div>hello</div></br>");
+}
+
+// bug 2465145 - Sequential defaulted attribute tags lost
+TEST_F(HtmlParseTest, SequentialDefaultedTagsLost) {
+ // This test cannot work with libxml, but since we use our own
+ // parser we can make it work. See
+ // https://bugzilla.gnome.org/show_bug.cgi?id=611655
+ ValidateNoChanges("sequential_defaulted_attribute_tags_lost",
+ "<select>\n"
+ " <option value=\"&cat=244\">Other option</option>\n"
+ " <option value selected style=\"color: #ccc;\">Default option"
+ "</option>\n"
+ "</select>");
+
+ // Illegal attribute "http://www.yahoo.com" mangled by parser into
+ // "http:", although if the parser changes how it mangles that somehow
+ // it's fine to regold.
+ ValidateNoChanges("yahoo",
+ "<a href=\"#\" http://www.yahoo.com "
+ "class=\"pa-btn-open hide-textindent\">yahoo</a>");
+
+ // Here's another interesting thing from the bug testcase.
+ // Specifying a literal "&" without a recognized sequence
+ // following it gets parsed correctly by libxml2, and then
+ // re-encoded by our writer as &. That's fine; let's
+ // make sure that doesn't change.
+ ValidateNoChanges("amp_cat",
+ "<option value=\"&cat=244\">other</option>");
+}
+
+// bug 2465201 : some html constructs do not need ';' termination.
+// Fixed by providing own lexer.
+TEST_F(HtmlParseTest, UnterminatedTokens) {
+ // the termination semicolons should be added in the output.
+ ValidateNoChanges("unterminated_tokens",
+ "<p>Look at the non breaking space: \" \"</p>");
+}
+
+// bug 2467040 : keep ampersands and quotes encoded
+TEST_F(HtmlParseTest, EncodeAmpersandsAndQuotes) {
+ ValidateNoChanges("ampersands_in_text",
+ "<p>This should be a string '&amp;' not a single ampersand.</p>");
+ ValidateNoChanges("ampersands_in_values",
+ "<img alt=\"This should be a string '&amp;' "
+ "not a single ampersand.\"/>");
+ ValidateNoChanges("quotes",
+ "<p>Clicking <a href=\"javascript: alert("Alert works!");\">"
+ "here</a> should pop up an alert box.</p>");
+}
+
+// bug 2508334 : encoding unicode in general
+TEST_F(HtmlParseTest, EncodeUnicode) {
+ ValidateNoChanges("unicode_in_text",
+ "<p>Non-breaking space: ' '</p>\n"
+ "<p>Alpha: 'α'</p>\n"
+ "<p>Unicode #54321: '퐱'</p>\n");
+}
+
+TEST_F(HtmlParseTest, ImplicitExplicitClose) {
+ // The lexer/printer preserves the input syntax, making it easier
+ // to diff inputs & outputs.
+ //
+ // TODO(jmarantz): But we can have a rewrite pass that eliminates
+ // the superfluous "/>".
+ ValidateNoChanges("one_brief_one_implicit_input",
+ "<input type=\"text\" name=\"username\">"
+ "<input type=\"password\" name=\"password\"/>");
+}
+
+TEST_F(HtmlParseTest, OpenBracketAfterQuote) {
+ // '<' after '"' in attr value
+ const char input[] =
+ "<input type=\"text\" name=\"username\""
+ "<input type=\"password\" name=\"password\"/>";
+ const char expected[] =
+ "<input type=\"text\" name=\"username\">" // note added '>'
+ "<input type=\"password\" name=\"password\"/>";
+ ValidateExpected("open_bracket_after_quote", input, expected);
+}
+
+TEST_F(HtmlParseTest, OpenBracketUnquoted) {
+ // '<' after after unquoted attr value
+ const char input[] =
+ "<input type=\"text\" name=username"
+ "<input type=\"password\" name=\"password\"/>";
+ const char expected[] =
+ "<input type=\"text\" name=username>" // note added '>'
+ "<input type=\"password\" name=\"password\"/>";
+ ValidateExpected("open_bracket_unquoted", input, expected);
+}
+
+TEST_F(HtmlParseTest, OpenBracketAfterEquals) {
+ // '<' after after unquoted attr value
+ const char input[] =
+ "<input type=\"text\" name="
+ "<input type=\"password\" name=\"password\"/>";
+ const char expected[] =
+ "<input type=\"text\" name=>" // note added '>'
+ "<input type=\"password\" name=\"password\"/>";
+ ValidateExpected("open_brack_after_equals", input, expected);
+}
+
+TEST_F(HtmlParseTest, OpenBracketAfterName) {
+ // '<' after after unquoted attr value
+ const char input[] =
+ "<input type=\"text\" name"
+ "<input type=\"password\" name=\"password\"/>";
+ const char expected[] =
+ "<input type=\"text\" name>" // note added '>'
+ "<input type=\"password\" name=\"password\"/>";
+ ValidateExpected("open_brack_after_name", input, expected);
+}
+
+TEST_F(HtmlParseTest, OpenBracketAfterSpace) {
+ // '<' after after unquoted attr value
+ const char input[] =
+ "<input type=\"text\" "
+ "<input type=\"password\" name=\"password\"/>";
+ const char expected[] =
+ "<input type=\"text\">" // note added '>'
+ "<input type=\"password\" name=\"password\"/>";
+ ValidateExpected("open_brack_after_name", input, expected);
+}
+
+// bug 2508140 : <noscript> in <head>
+TEST_F(HtmlParseTestNoBody, NoscriptInHead) {
+ // Some real websites (ex: google.com) have <noscript> in the <head> even
+ // though this is technically illegal acording to the HTML4 spec.
+ // We should support the case in use.
+ ValidateNoChanges("noscript_in_head",
+ "<head><noscript><title>You don't have JS enabled :(</title>"
+ "</noscript></head>");
+}
+
+
+// Bool that is auto-initialized to false
+class Bool {
+ public:
+ Bool() : value_(false) {}
+ Bool(bool value) : value_(value) {} // Copy constructor // NOLINT
+ const bool Test() const { return value_; }
+
+ private:
+ bool value_;
+};
+
+// Class simply keeps track of which handlers have been called.
+class HandlerCalledFilter : public HtmlFilter {
+ public:
+ HandlerCalledFilter() { }
+
+ virtual void StartDocument() { called_start_document_ = true; }
+ virtual void EndDocument() { called_end_document_ = true;}
+ virtual void StartElement(HtmlElement* element) {
+ called_start_element_ = true;
+ }
+ virtual void EndElement(HtmlElement* element) {
+ called_end_element_ = true;
+ }
+ virtual void Cdata(HtmlCdataNode* cdata) { called_cdata_ = true; }
+ virtual void Comment(HtmlCommentNode* comment) { called_comment_ = true; }
+ virtual void IEDirective(HtmlIEDirectiveNode* directive) {
+ called_ie_directive_ = true;
+ }
+ virtual void Characters(HtmlCharactersNode* characters) {
+ called_characters_ = true;
+ }
+ virtual void Directive(HtmlDirectiveNode* directive) {
+ called_directive_ = true;
+ }
+ virtual void Flush() { called_flush_ = true; }
+ virtual const char* Name() const { return "HandlerCalled"; }
+
+ Bool called_start_document_;
+ Bool called_end_document_;
+ Bool called_start_element_;
+ Bool called_end_element_;
+ Bool called_cdata_;
+ Bool called_comment_;
+ Bool called_ie_directive_;
+ Bool called_characters_;
+ Bool called_directive_;
+ Bool called_flush_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HandlerCalledFilter);
+};
+
+class HandlerCalledTest : public HtmlParseTest {
+ protected:
+ HandlerCalledTest() {
+ html_parse_.AddFilter(&handler_called_filter_);
+ }
+
+ HandlerCalledFilter handler_called_filter_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HandlerCalledTest);
+};
+
+// Check that StartDocument and EndDocument were called for filters.
+TEST_F(HandlerCalledTest, StartEndDocumentCalled) {
+ Parse("start_end_document_called", "");
+ EXPECT_TRUE(handler_called_filter_.called_start_document_.Test());
+ EXPECT_TRUE(handler_called_filter_.called_end_document_.Test());
+}
+
+TEST_F(HandlerCalledTest, StartEndElementCalled) {
+ Parse("start_end_element_called", "<p>...</p>");
+ EXPECT_TRUE(handler_called_filter_.called_start_element_.Test());
+ EXPECT_TRUE(handler_called_filter_.called_end_element_.Test());
+}
+
+TEST_F(HandlerCalledTest, CdataCalled) {
+ Parse("cdata_called", "<![CDATA[...]]>");
+ // Looks like a directive, but isn't.
+ EXPECT_FALSE(handler_called_filter_.called_directive_.Test());
+ EXPECT_TRUE(handler_called_filter_.called_cdata_.Test());
+}
+
+TEST_F(HandlerCalledTest, CommentCalled) {
+ Parse("comment_called", "<!--...-->");
+ EXPECT_TRUE(handler_called_filter_.called_comment_.Test());
+}
+
+TEST_F(HandlerCalledTest, IEDirectiveCalled) {
+ Parse("ie_directive_called", "<!--[if IE]>...<![endif]-->");
+ // Looks like a comment, but isn't.
+ EXPECT_FALSE(handler_called_filter_.called_comment_.Test());
+ EXPECT_TRUE(handler_called_filter_.called_ie_directive_.Test());
+}
+
+// Unit tests for event-list manipulation. In these tests, we do not parse
+// HTML input text, but instead create two 'Characters' nodes and use the
+// event-list manipulation methods and make sure they render as expected.
+class EventListManipulationTest : public HtmlParseTest {
+ protected:
+ EventListManipulationTest() { }
+
+ virtual void SetUp() {
+ HtmlParseTest::SetUp();
+ static const char kUrl[] = "http://html.parse.test/event_list_test.html";
+ html_parse_.StartParse(kUrl);
+ node1_ = html_parse_.NewCharactersNode(NULL, "1");
+ HtmlTestingPeer::AddEvent(&html_parse_,
+ new HtmlCharactersEvent(node1_, -1));
+ node2_ = html_parse_.NewCharactersNode(NULL, "2");
+ node3_ = html_parse_.NewCharactersNode(NULL, "3");
+ // Note: the last 2 are not added in SetUp.
+ }
+
+ virtual void TearDown() {
+ html_parse_.FinishParse();
+ HtmlParseTest::TearDown();
+ }
+
+ void CheckExpected(const std::string& expected) {
+ SetupWriter();
+ html_parse()->ApplyFilter(html_writer_filter_.get());
+ EXPECT_EQ(expected, output_buffer_);
+ }
+
+ HtmlCharactersNode* node1_;
+ HtmlCharactersNode* node2_;
+ HtmlCharactersNode* node3_;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(EventListManipulationTest);
+};
+
+TEST_F(EventListManipulationTest, TestReplace) {
+ EXPECT_TRUE(html_parse_.ReplaceNode(node1_, node2_));
+ CheckExpected("2");
+}
+
+TEST_F(EventListManipulationTest, TestInsertElementBeforeElement) {
+ HtmlTestingPeer::set_coalesce_characters(&html_parse_, false);
+ html_parse_.InsertElementBeforeElement(node1_, node2_);
+ CheckExpected("21");
+ html_parse_.InsertElementBeforeElement(node1_, node3_);
+ CheckExpected("231");
+}
+
+TEST_F(EventListManipulationTest, TestInsertElementAfterElement) {
+ HtmlTestingPeer::set_coalesce_characters(&html_parse_, false);
+ html_parse_.InsertElementAfterElement(node1_, node2_);
+ CheckExpected("12");
+ html_parse_.InsertElementAfterElement(node1_, node3_);
+ CheckExpected("132");
+}
+
+TEST_F(EventListManipulationTest, TestInsertElementBeforeCurrent) {
+ HtmlTestingPeer::set_coalesce_characters(&html_parse_, false);
+ html_parse_.InsertElementBeforeCurrent(node2_);
+ // Current is left at queue_.end() after the AddEvent.
+ CheckExpected("12");
+
+ HtmlTestingPeer::SetCurrent(&html_parse_, node1_);
+ html_parse_.InsertElementBeforeCurrent(node3_);
+ CheckExpected("312");
+}
+
+TEST_F(EventListManipulationTest, TestInsertElementAfterCurrent) {
+ HtmlTestingPeer::set_coalesce_characters(&html_parse_, false);
+ HtmlTestingPeer::SetCurrent(&html_parse_, node1_);
+ html_parse_.InsertElementAfterCurrent(node2_);
+ // Note that if we call CheckExpected here it will mutate current_.
+ html_parse_.InsertElementAfterCurrent(node3_);
+ CheckExpected("123");
+}
+
+TEST_F(EventListManipulationTest, TestDeleteOnly) {
+ html_parse_.DeleteElement(node1_);
+ CheckExpected("");
+}
+
+TEST_F(EventListManipulationTest, TestDeleteFirst) {
+ HtmlTestingPeer::set_coalesce_characters(&html_parse_, false);
+ HtmlTestingPeer::AddEvent(&html_parse_, new HtmlCharactersEvent(node2_, -1));
+ HtmlTestingPeer::AddEvent(&html_parse_, new HtmlCharactersEvent(node3_, -1));
+ html_parse_.DeleteElement(node1_);
+ CheckExpected("23");
+ html_parse_.DeleteElement(node2_);
+ CheckExpected("3");
+ html_parse_.DeleteElement(node3_);
+ CheckExpected("");
+}
+
+TEST_F(EventListManipulationTest, TestDeleteLast) {
+ HtmlTestingPeer::set_coalesce_characters(&html_parse_, false);
+ HtmlTestingPeer::AddEvent(&html_parse_, new HtmlCharactersEvent(node2_, -1));
+ HtmlTestingPeer::AddEvent(&html_parse_, new HtmlCharactersEvent(node3_, -1));
+ html_parse_.DeleteElement(node3_);
+ CheckExpected("12");
+ html_parse_.DeleteElement(node2_);
+ CheckExpected("1");
+ html_parse_.DeleteElement(node1_);
+ CheckExpected("");
+}
+
+TEST_F(EventListManipulationTest, TestDeleteMiddle) {
+ HtmlTestingPeer::set_coalesce_characters(&html_parse_, false);
+ HtmlTestingPeer::AddEvent(&html_parse_, new HtmlCharactersEvent(node2_, -1));
+ HtmlTestingPeer::AddEvent(&html_parse_, new HtmlCharactersEvent(node3_, -1));
+ html_parse_.DeleteElement(node2_);
+ CheckExpected("13");
+}
+
+// Note that an unconditionaly sanity check runs after every
+// filter, verifying that all the parent-pointers are correct.
+// CheckExpected applies the HtmlWriterFilter, so it runs the
+// parent-pointer check.
+TEST_F(EventListManipulationTest, TestAddParentToSequence) {
+ HtmlTestingPeer::set_coalesce_characters(&html_parse_, false);
+ HtmlTestingPeer::AddEvent(&html_parse_, new HtmlCharactersEvent(node2_, -1));
+ HtmlTestingPeer::AddEvent(&html_parse_, new HtmlCharactersEvent(node3_, -1));
+ HtmlElement* div = html_parse_.NewElement(NULL, html_parse_.Intern("div"));
+ EXPECT_TRUE(html_parse_.AddParentToSequence(node1_, node3_, div));
+ CheckExpected("<div>123</div>");
+
+ // Now interpose a span between the div and the Characeters nodes.
+ HtmlElement* span = html_parse_.NewElement(div, html_parse_.Intern("span"));
+ EXPECT_TRUE(html_parse_.AddParentToSequence(node1_, node2_, span));
+ CheckExpected("<div><span>12</span>3</div>");
+
+ // Next, add an HTML block above the div. Note that we pass 'div' in as
+ // both 'first' and 'last'.
+ HtmlElement* html = html_parse_.NewElement(NULL, html_parse_.Intern("html"));
+ EXPECT_TRUE(html_parse_.AddParentToSequence(div, div, html));
+ CheckExpected("<html><div><span>12</span>3</div></html>");
+}
+
+TEST_F(EventListManipulationTest, TestPrependChild) {
+ HtmlTestingPeer::set_coalesce_characters(&html_parse_, false);
+ HtmlElement* div = html_parse_.NewElement(NULL, html_parse_.Intern("div"));
+ html_parse_.InsertElementBeforeCurrent(div);
+ CheckExpected("1<div></div>");
+
+ html_parse_.PrependChild(div, node2_);
+ CheckExpected("1<div>2</div>");
+ html_parse_.PrependChild(div, node3_);
+ CheckExpected("1<div>32</div>");
+
+ // TODO(sligocki): Test with elements that don't explicitly end like image.
+}
+
+TEST_F(EventListManipulationTest, TestAppendChild) {
+ HtmlTestingPeer::set_coalesce_characters(&html_parse_, false);
+ HtmlElement* div = html_parse_.NewElement(NULL, html_parse_.Intern("div"));
+ html_parse_.InsertElementBeforeCurrent(div);
+ CheckExpected("1<div></div>");
+
+ html_parse_.AppendChild(div, node2_);
+ CheckExpected("1<div>2</div>");
+ html_parse_.AppendChild(div, node3_);
+ CheckExpected("1<div>23</div>");
+
+ // TODO(sligocki): Test with elements that don't explicitly end like image.
+}
+
+TEST_F(EventListManipulationTest, TestAddParentToSequenceDifferentParents) {
+ HtmlTestingPeer::set_coalesce_characters(&html_parse_, false);
+ HtmlTestingPeer::AddEvent(&html_parse_, new HtmlCharactersEvent(node2_, -1));
+ HtmlElement* div = html_parse_.NewElement(NULL, html_parse_.Intern("div"));
+ EXPECT_TRUE(html_parse_.AddParentToSequence(node1_, node2_, div));
+ CheckExpected("<div>12</div>");
+ HtmlTestingPeer::AddEvent(&html_parse_, new HtmlCharactersEvent(node3_, -1));
+ CheckExpected("<div>12</div>3");
+ EXPECT_FALSE(html_parse_.AddParentToSequence(node2_, node3_, div));
+}
+
+TEST_F(EventListManipulationTest, TestDeleteGroup) {
+ HtmlTestingPeer::AddEvent(&html_parse_, new HtmlCharactersEvent(node2_, -1));
+ HtmlElement* div = html_parse_.NewElement(NULL, html_parse_.Intern("div"));
+ EXPECT_TRUE(html_parse_.AddParentToSequence(node1_, node2_, div));
+ CheckExpected("<div>12</div>");
+ html_parse_.DeleteElement(div);
+ CheckExpected("");
+}
+
+TEST_F(EventListManipulationTest, TestMoveElementIntoParent1) {
+ HtmlElement* head = html_parse_.NewElement(NULL, html_parse_.Intern("head"));
+ EXPECT_TRUE(html_parse_.AddParentToSequence(node1_, node1_, head));
+ CheckExpected("<head>1</head>");
+ HtmlTestingPeer::AddEvent(&html_parse_, new HtmlCharactersEvent(node2_, -1));
+ HtmlElement* div = html_parse_.NewElement(NULL, html_parse_.Intern("div"));
+ EXPECT_TRUE(html_parse_.AddParentToSequence(node2_, node2_, div));
+ CheckExpected("<head>1</head><div>2</div>");
+ HtmlTestingPeer::AddEvent(&html_parse_, new HtmlCharactersEvent(node3_, -1));
+ CheckExpected("<head>1</head><div>2</div>3");
+ HtmlTestingPeer::SetCurrent(&html_parse_, div);
+ EXPECT_TRUE(html_parse_.MoveCurrentInto(head));
+ CheckExpected("<head>1<div>2</div></head>3");
+}
+
+TEST_F(EventListManipulationTest, TestMoveElementIntoParent2) {
+ HtmlTestingPeer::set_coalesce_characters(&html_parse_, false);
+ HtmlElement* head = html_parse_.NewElement(NULL, html_parse_.Intern("head"));
+ EXPECT_TRUE(html_parse_.AddParentToSequence(node1_, node1_, head));
+ CheckExpected("<head>1</head>");
+ HtmlTestingPeer::AddEvent(&html_parse_, new HtmlCharactersEvent(node2_, -1));
+ HtmlTestingPeer::AddEvent(&html_parse_, new HtmlCharactersEvent(node3_, -1));
+ CheckExpected("<head>1</head>23");
+ HtmlElement* div = html_parse_.NewElement(NULL, html_parse_.Intern("div"));
+ EXPECT_TRUE(html_parse_.AddParentToSequence(node3_, node3_, div));
+ CheckExpected("<head>1</head>2<div>3</div>");
+ HtmlTestingPeer::SetCurrent(&html_parse_, div);
+ EXPECT_TRUE(html_parse_.MoveCurrentInto(head));
+ CheckExpected("<head>1<div>3</div></head>2");
+ EXPECT_TRUE(html_parse_.DeleteSavingChildren(div));
+ CheckExpected("<head>13</head>2");
+ EXPECT_TRUE(html_parse_.DeleteSavingChildren(head));
+ CheckExpected("132");
+}
+
+TEST_F(EventListManipulationTest, TestCoalesceOnAdd) {
+ CheckExpected("1");
+ HtmlTestingPeer::AddEvent(&html_parse_, new HtmlCharactersEvent(node2_, -1));
+ CheckExpected("12");
+
+ // this will coalesce node1 and node2 togethers. So there is only
+ // one node1_="12", and node2_ is gone. Deleting node1_ will now
+ // leave us empty
+ html_parse_.DeleteElement(node1_);
+ CheckExpected("");
+}
+
+TEST_F(EventListManipulationTest, TestCoalesceOnDelete) {
+ CheckExpected("1");
+ HtmlElement* div = html_parse_.NewElement(NULL, html_parse_.Intern("div"));
+ html_parse_.AddElement(div, -1);
+ HtmlTestingPeer::AddEvent(&html_parse_, new HtmlCharactersEvent(node2_, -1));
+ HtmlTestingPeer testing_peer;
+ testing_peer.SetNodeParent(node2_, div);
+ html_parse_.CloseElement(div, HtmlElement::EXPLICIT_CLOSE, -1);
+ HtmlTestingPeer::AddEvent(&html_parse_, new HtmlCharactersEvent(node3_, -1));
+ CheckExpected("1<div>2</div>3");
+
+ // Removing the div, leaving the children intact...
+ EXPECT_TRUE(html_parse_.DeleteSavingChildren(div));
+ CheckExpected("123");
+
+ // At this point, node1, node2, and node3 are automatically coalesced.
+ // This means when we remove node1, all the content will disappear.
+ html_parse_.DeleteElement(node1_);
+ CheckExpected("");
+}
+
+// Unit tests for attribute manipulation.
+// Goal is to make sure we don't (eg) read deallocated storage
+// while manipulating attribute values.
+class AttributeManipulationTest : public HtmlParseTest {
+ protected:
+ AttributeManipulationTest() { }
+
+ virtual void SetUp() {
+ HtmlParseTest::SetUp();
+ static const char kUrl[] =
+ "http://html.parse.test/attribute_manipulation_test.html";
+ html_parse_.StartParse(kUrl);
+ node_ = html_parse_.NewElement(NULL, MakeAtom("a"));
+ html_parse_.AddElement(node_, 0);
+ node_->AddAttribute(MakeAtom("href"), "http://www.google.com/", "\"");
+ node_->AddAttribute(MakeAtom("id"), "37", "");
+ node_->AddAttribute(MakeAtom("class"), "search!", "'");
+ html_parse_.CloseElement(node_, HtmlElement::BRIEF_CLOSE, 0);
+ }
+
+ virtual void TearDown() {
+ html_parse_.FinishParse();
+ HtmlParseTest::TearDown();
+ }
+
+ Atom MakeAtom(const char *name) {
+ return html_parse_.Intern(name);
+ }
+
+ void CheckExpected(const std::string& expected) {
+ SetupWriter();
+ html_parse_.ApplyFilter(html_writer_filter_.get());
+ EXPECT_EQ(expected, output_buffer_);
+ }
+
+ HtmlElement* node_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AttributeManipulationTest);
+};
+
+TEST_F(AttributeManipulationTest, PropertiesAndDeserialize) {
+ StringPiece google("http://www.google.com/");
+ StringPiece number37("37");
+ StringPiece search("search!");
+ EXPECT_EQ(3, node_->attribute_size());
+ EXPECT_EQ(google, node_->AttributeValue(MakeAtom("href")));
+ EXPECT_EQ(number37, node_->AttributeValue(MakeAtom("id")));
+ EXPECT_EQ(search, node_->AttributeValue(MakeAtom("class")));
+ EXPECT_TRUE(NULL == node_->AttributeValue(MakeAtom("absent")));
+ int val = -35;
+ EXPECT_FALSE(node_->IntAttributeValue(MakeAtom("absent"), &val));
+ EXPECT_EQ(-35, val);
+ EXPECT_FALSE(node_->IntAttributeValue(MakeAtom("href"), &val));
+ EXPECT_EQ(0, val);
+ EXPECT_TRUE(node_->IntAttributeValue(MakeAtom("id"), &val));
+ EXPECT_EQ(37, val);
+ EXPECT_TRUE(NULL == node_->FindAttribute(MakeAtom("absent")));
+ EXPECT_EQ(google, node_->FindAttribute(MakeAtom("href"))->value());
+ EXPECT_EQ(number37, node_->FindAttribute(MakeAtom("id"))->value());
+ EXPECT_EQ(search, node_->FindAttribute(MakeAtom("class"))->value());
+ EXPECT_EQ(google, node_->FindAttribute(MakeAtom("href"))->escaped_value());
+ EXPECT_EQ(number37, node_->FindAttribute(MakeAtom("id"))->escaped_value());
+ EXPECT_EQ(search, node_->FindAttribute(MakeAtom("class"))->escaped_value());
+ CheckExpected("<a href=\"http://www.google.com/\" id=37 class='search!'/>");
+}
+
+TEST_F(AttributeManipulationTest, AddAttribute) {
+ node_->AddAttribute(MakeAtom("lang"), "ENG-US", "\"");
+ CheckExpected("<a href=\"http://www.google.com/\" id=37 class='search!'"
+ " lang=\"ENG-US\"/>");
+}
+
+TEST_F(AttributeManipulationTest, DeleteAttribute) {
+ node_->DeleteAttribute(1);
+ CheckExpected("<a href=\"http://www.google.com/\" class='search!'/>");
+}
+
+TEST_F(AttributeManipulationTest, ModifyAttribute) {
+ HtmlElement::Attribute* href =
+ node_->FindAttribute(MakeAtom("href"));
+ EXPECT_TRUE(href != NULL);
+ href->SetValue("google");
+ href->set_quote("'");
+ href->set_name(MakeAtom("src"));
+ CheckExpected("<a src='google' id=37 class='search!'/>");
+}
+
+TEST_F(AttributeManipulationTest, ModifyKeepAttribute) {
+ HtmlElement::Attribute* href =
+ node_->FindAttribute(MakeAtom("href"));
+ EXPECT_TRUE(href != NULL);
+ // This apparently do-nothing call to SetValue exposed an allocation bug.
+ href->SetValue(href->value());
+ href->set_quote(href->quote());
+ href->set_name(href->name());
+ CheckExpected("<a href=\"http://www.google.com/\" id=37 class='search!'/>");
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/htmlparse/html_testing_peer.h b/trunk/src/net/instaweb/htmlparse/html_testing_peer.h
new file mode 100644
index 0000000..ecbd435
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/html_testing_peer.h
@@ -0,0 +1,50 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_HTMLPARSE_HTML_TESTING_PEER_H_
+#define NET_INSTAWEB_HTMLPARSE_HTML_TESTING_PEER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/html_node.h"
+
+namespace net_instaweb {
+
+class HtmlTestingPeer {
+ public:
+ HtmlTestingPeer() { }
+
+ static void SetNodeParent(HtmlNode* node, HtmlElement* parent) {
+ node->set_parent(parent);
+ }
+ static void AddEvent(HtmlParse* parser, HtmlEvent* event) {
+ parser->AddEvent(event);
+ }
+ static void SetCurrent(HtmlParse* parser, HtmlNode* node) {
+ parser->SetCurrent(node);
+ }
+ static void set_coalesce_characters(HtmlParse* parser, bool x) {
+ parser->set_coalesce_characters(x);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HtmlTestingPeer);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_HTMLPARSE_HTML_TESTING_PEER_H_
diff --git a/trunk/src/net/instaweb/htmlparse/html_writer_filter.cc b/trunk/src/net/instaweb/htmlparse/html_writer_filter.cc
new file mode 100644
index 0000000..4d95070
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/html_writer_filter.cc
@@ -0,0 +1,230 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/htmlparse/public/html_writer_filter.h"
+
+#include "base/logging.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include <string>
+#include "net/instaweb/util/public/writer.h"
+
+namespace net_instaweb {
+
+static const int kDefaultMaxColumn = -1;
+
+HtmlWriterFilter::HtmlWriterFilter(HtmlParse* html_parse)
+ : writer_(NULL) {
+ html_parse_ = html_parse;
+
+ // Pre-intern a set of common symbols that can be used for
+ // fast comparisons when matching tags, and for pointer-based
+ // hash-tables.
+ symbol_a_ = html_parse->Intern("a");
+ symbol_link_ = html_parse->Intern("link");
+ symbol_href_ = html_parse->Intern("href");
+ symbol_img_ = html_parse->Intern("img");
+ symbol_script_ = html_parse->Intern("script");
+ symbol_src_ = html_parse->Intern("src");
+ symbol_alt_ = html_parse->Intern("alt");
+ max_column_ = kDefaultMaxColumn;
+ Clear();
+}
+
+HtmlWriterFilter::~HtmlWriterFilter() {
+}
+
+void HtmlWriterFilter::Clear() {
+ lazy_close_element_ = NULL;
+ column_ = 0;
+ write_errors_ = 0;
+}
+
+void HtmlWriterFilter::EmitBytes(const StringPiece& str) {
+ if (lazy_close_element_ != NULL) {
+ lazy_close_element_ = NULL;
+ if (!writer_->Write(">", html_parse_->message_handler())) {
+ ++write_errors_;
+ }
+ ++column_;
+ }
+
+ // Search backward from the end for the last occurrence of a newline.
+ column_ += str.size(); // if there are no newlines, bump up column counter.
+ for (int i = str.size() - 1; i >= 0; --i) {
+ if (str[i] == '\n') {
+ column_ = str.size() - i - 1; // found a newline; so reset the column.
+ break;
+ }
+ }
+ if (!writer_->Write(str, html_parse_->message_handler())) {
+ ++write_errors_;
+ }
+}
+
+void HtmlWriterFilter::StartElement(HtmlElement* element) {
+ EmitBytes("<");
+ EmitBytes(element->tag().c_str());
+
+ for (int i = 0; i < element->attribute_size(); ++i) {
+ const HtmlElement::Attribute& attribute = element->attribute(i);
+ // If the column has grown too large, insert a newline. It's always safe
+ // to insert whitespace in the middle of tag parameters.
+ int attr_length = 1 + attribute.name().size();
+ if (max_column_ > 0) {
+ if (attribute.escaped_value() != NULL) {
+ attr_length += 1 + strlen(attribute.escaped_value());
+ }
+ if ((column_ + attr_length) > max_column_) {
+ EmitBytes("\n");
+ }
+ }
+ EmitBytes(" ");
+ EmitBytes(attribute.name().c_str());
+ if (attribute.escaped_value() != NULL) {
+ EmitBytes("=");
+ EmitBytes(attribute.quote());
+ EmitBytes(attribute.escaped_value());
+ EmitBytes(attribute.quote());
+ }
+ }
+
+ // Attempt to briefly terminate any legal tag that was explicitly terminated
+ // in the input. Note that a rewrite pass might have injected events
+ // between the begin/end of an element that was closed briefly in the input
+ // html. In that case it cannot be closed briefly. It is up to this
+ // code to validate BRIEF_CLOSE on each element.
+ //
+ // TODO(jmarantz): Add a rewrite pass that morphs EXPLICIT_CLOSE into 'brief'
+ // when legal. Such a change will introduce textual diffs between
+ // input and output html that would cause htmlparse unit tests to require
+ // a regold. But the changes could be validated with the normalizer.
+ if (GetCloseStyle(element) == HtmlElement::BRIEF_CLOSE) {
+ lazy_close_element_ = element;
+ } else {
+ EmitBytes(">");
+ }
+}
+
+// Compute the tag-closing style for an element. If the style was specified
+// on construction, then we use that. If the element was synthesized by
+// a rewrite pass, then it's stored as AUTO_CLOSE, and we can determine
+// whether the element is briefly closable or implicitly closed.
+HtmlElement::CloseStyle HtmlWriterFilter::GetCloseStyle(HtmlElement* element) {
+ HtmlElement::CloseStyle style = element->close_style();
+ if (style == HtmlElement::AUTO_CLOSE) {
+ Atom tag = element->tag();
+ if (html_parse_->IsImplicitlyClosedTag(tag)) {
+ style = HtmlElement::IMPLICIT_CLOSE;
+ } else if (html_parse_->TagAllowsBriefTermination(tag)) {
+ style = HtmlElement::BRIEF_CLOSE;
+ } else {
+ style = HtmlElement::EXPLICIT_CLOSE;
+ }
+ }
+ return style;
+}
+
+void HtmlWriterFilter::EndElement(HtmlElement* element) {
+ HtmlElement::CloseStyle style = GetCloseStyle(element);
+ switch (style) {
+ case HtmlElement::AUTO_CLOSE:
+ // This cannot happen because GetCloseStyle prevents won't
+ // return AUTO_CLOSE.
+ html_parse_->message_handler()->FatalError(
+ __FILE__, __LINE__, "GetCloseStyle should never return AUTO_CLOSE.");
+ break;
+ case HtmlElement::IMPLICIT_CLOSE:
+ // Nothing new to write; the ">" was written in StartElement
+ break;
+ case HtmlElement::BRIEF_CLOSE:
+ // even if the element is briefly closeable, if more text
+ // got written after the element open, then we must
+ // explicitly close it, so we fall through.
+ if (lazy_close_element_ == element) {
+ lazy_close_element_ = NULL;
+
+ // If this attribute was unquoted, or lacked a value, then we'll need
+ // to add a space here to ensure that HTML parsers don't interpret the
+ // '/' in the '/>' as part of the attribute.
+ if (element->attribute_size() != 0) {
+ const HtmlElement::Attribute& attribute = element->attribute(
+ element->attribute_size() - 1);
+ if ((attribute.escaped_value() == NULL) ||
+ (attribute.quote()[0] == '\0')) {
+ EmitBytes(" ");
+ }
+ }
+ EmitBytes("/>");
+ break;
+ }
+ // fall through
+ case HtmlElement::EXPLICIT_CLOSE:
+ EmitBytes("</");
+ EmitBytes(element->tag().c_str());
+ EmitBytes(">");
+ break;
+ case HtmlElement::UNCLOSED:
+ // Nothing new to write; the ">" was written in StartElement
+ break;
+ }
+}
+
+void HtmlWriterFilter::Characters(HtmlCharactersNode* chars) {
+ EmitBytes(chars->contents());
+}
+
+void HtmlWriterFilter::Cdata(HtmlCdataNode* cdata) {
+ EmitBytes("<![CDATA[");
+ EmitBytes(cdata->contents());
+ EmitBytes("]]>");
+}
+
+void HtmlWriterFilter::Comment(HtmlCommentNode* comment) {
+ EmitBytes("<!--");
+ EmitBytes(comment->contents());
+ EmitBytes("-->");
+}
+
+void HtmlWriterFilter::IEDirective(HtmlIEDirectiveNode* directive) {
+ EmitBytes("<!--");
+ EmitBytes(directive->contents());
+ EmitBytes("-->");
+}
+
+void HtmlWriterFilter::Directive(HtmlDirectiveNode* directive) {
+ EmitBytes("<!");
+ EmitBytes(directive->contents());
+ EmitBytes(">");
+}
+
+void HtmlWriterFilter::StartDocument() {
+ Clear();
+}
+
+void HtmlWriterFilter::EndDocument() {
+}
+
+void HtmlWriterFilter::Flush() {
+ if (!writer_->Flush(html_parse_->message_handler())) {
+ ++write_errors_;
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/htmlparse/logging_html_filter.cc b/trunk/src/net/instaweb/htmlparse/logging_html_filter.cc
new file mode 100644
index 0000000..b3af089
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/logging_html_filter.cc
@@ -0,0 +1,147 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#include "public/logging_html_filter.h"
+#include "public/html_element.h"
+#include "public/statistics_log.h"
+
+// TODO(jmarantz): convert to Statistics interface
+
+namespace {
+// Printable names of the statistics.
+// Must match up with enum Statistic in logging_html_filter.h;
+// is this bad for maintenance?
+const char* kStatisticNames[] = {
+ "explicit_close", "implicit_close", "brief_close", "closed", "unclosed",
+ "spurious_close", "tags", "cdata", "comments", "directives", "documents",
+ "IE_directives",
+};
+}
+
+namespace net_instaweb {
+
+LoggingFilter::LoggingFilter() {
+ Reset();
+}
+
+void LoggingFilter::StartDocument() {
+ ++stats_[NUM_DOCUMENTS];
+}
+
+void LoggingFilter::StartElement(HtmlElement* element) {
+ // Does EndElement get called for singleton elements?
+ ++stats_[NUM_UNCLOSED];
+ ++stats_[NUM_TAGS];
+}
+
+void LoggingFilter::EndElement(HtmlElement* element) {
+ // Figure out what's up with the element (implicitly vs explicitly closed)
+ switch (element->close_style()) {
+ case HtmlElement::EXPLICIT_CLOSE: {
+ --stats_[NUM_UNCLOSED];
+ ++stats_[NUM_CLOSED];
+ ++stats_[NUM_EXPLICIT_CLOSED];
+ break;
+ }
+ case HtmlElement::IMPLICIT_CLOSE: {
+ --stats_[NUM_UNCLOSED];
+ ++stats_[NUM_CLOSED];
+ ++stats_[NUM_IMPLICIT_CLOSED];
+ break;
+ }
+ case HtmlElement::BRIEF_CLOSE: {
+ --stats_[NUM_UNCLOSED];
+ ++stats_[NUM_CLOSED];
+ ++stats_[NUM_BRIEF_CLOSED];
+ break;
+ }
+ case HtmlElement::UNCLOSED: {
+ // We assumed unmatchedness at StartElement, so do nothing.
+ break;
+ }
+ case HtmlElement::AUTO_CLOSE: {
+ // Another form of unmated tag, again do nothing.
+ break;
+ }
+ }
+}
+
+void LoggingFilter::Cdata(HtmlCdataNode* cdata) {
+ ++stats_[NUM_CDATA];
+}
+
+void LoggingFilter::Comment(HtmlCommentNode* comment) {
+ ++stats_[NUM_COMMENTS];
+}
+
+void LoggingFilter::IEDirective(HtmlIEDirectiveNode* directive) {
+ ++stats_[NUM_IE_DIRECTIVES];
+}
+
+void LoggingFilter::Directive(HtmlDirectiveNode* directive) {
+ ++stats_[NUM_DIRECTIVES];
+}
+
+// Logging, diffing, and aggregation
+void LoggingFilter::LogStatistics(StatisticsLog *statistics_log) const {
+ for (int statistic = MIN_STAT; statistic < MAX_STAT; ++statistic) {
+ statistics_log->LogStat(kStatisticNames[statistic], stats_[statistic]);
+ }
+}
+
+bool LoggingFilter::Equals(const LoggingFilter &that) const {
+ for (int statistic = MIN_STAT; statistic < MAX_STAT; ++statistic) {
+ if (this->stats_[statistic] != that.stats_[statistic])
+ return false;
+ }
+ return true;
+}
+
+void LoggingFilter::LogDifferences(
+ const LoggingFilter &that, StatisticsLog *statistics_log) const {
+ for (int statistic = MIN_STAT; statistic < MAX_STAT; ++statistic) {
+ if (this->stats_[statistic] != that.stats_[statistic]) {
+ statistics_log->LogDifference(
+ kStatisticNames[statistic],
+ this->stats_[statistic], that.stats_[statistic]);
+ }
+ }
+}
+
+void LoggingFilter::Aggregate(const LoggingFilter &that) {
+ for (int statistic = MIN_STAT; statistic < MAX_STAT; ++statistic) {
+ this->stats_[statistic] += that.stats_[statistic];
+ }
+}
+
+void LoggingFilter::AggregateDifferences(const LoggingFilter &first,
+ const LoggingFilter &second) {
+ for (int statistic = MIN_STAT; statistic < MAX_STAT; ++statistic) {
+ this->stats_[statistic] +=
+ first.stats_[statistic] - second.stats_[statistic];
+ }
+}
+
+void LoggingFilter::Reset() {
+ // Cleaner than memset?
+ for (int statistic = MIN_STAT; statistic < MAX_STAT; ++statistic) {
+ stats_[statistic] = 0;
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/htmlparse/null_filter.cc b/trunk/src/net/instaweb/htmlparse/null_filter.cc
new file mode 100644
index 0000000..7f6ae0f
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/null_filter.cc
@@ -0,0 +1,70 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include <stdio.h>
+#include "net/instaweb/htmlparse/public/file_driver.h"
+#include "net/instaweb/util/public/file_message_handler.h"
+#include "net/instaweb/htmlparse/public/file_statistics_log.h"
+#include "net/instaweb/util/public/stdio_file_system.h"
+
+int null_filter(int argc, char** argv) {
+ int ret = 1;
+
+ if ((argc < 2) || (argc > 4)) {
+ fprintf(stdout, "Usage: %s input_file [- | output_file] [log_file]\n",
+ argv[0]);
+ return ret;
+ }
+
+ const char* infile = argv[1];
+ net_instaweb::FileMessageHandler message_handler(stderr);
+ net_instaweb::StdioFileSystem file_system;
+ net_instaweb::HtmlParse html_parse(&message_handler);
+ net_instaweb::FileDriver file_driver(&html_parse, &file_system);
+ const char* outfile = NULL;
+ std::string outfile_buffer;
+ const char* statsfile = NULL;
+ std::string statsfile_buffer;
+
+ if (argc >= 3) {
+ outfile = argv[2];
+ } else if (net_instaweb::FileDriver::GenerateOutputFilename(
+ infile, &outfile_buffer)) {
+ outfile = outfile_buffer.c_str();
+ fprintf(stdout, "Null rewriting %s into %s\n", infile, outfile);
+ } else {
+ message_handler.FatalError(infile, 0, "Cannot generate output filename");
+ }
+
+ if (argc >= 4) {
+ statsfile = argv[3];
+ } else if (net_instaweb::FileDriver::GenerateStatsFilename(
+ infile, &statsfile_buffer)) {
+ statsfile = statsfile_buffer.c_str();
+ fprintf(stdout, "Logging statistics for %s into %s\n",
+ infile, statsfile);
+ } else {
+ message_handler.FatalError(infile, 0, "Cannot generate stats file name");
+ }
+
+ if (file_driver.ParseFile(infile, outfile, statsfile, &message_handler)) {
+ ret = 0;
+ }
+
+ return ret;
+}
diff --git a/trunk/src/net/instaweb/htmlparse/null_filter.h b/trunk/src/net/instaweb/htmlparse/null_filter.h
new file mode 100644
index 0000000..f94d497
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/null_filter.h
@@ -0,0 +1,24 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_HTMLPARSE_NULL_FILTER_H_
+#define NET_INSTAWEB_HTMLPARSE_NULL_FILTER_H_
+
+int null_filter(int argc, char** argv);
+
+#endif // NET_INSTAWEB_HTMLPARSE_NULL_FILTER_H_
diff --git a/trunk/src/net/instaweb/htmlparse/public/doctype.h b/trunk/src/net/instaweb/htmlparse/public/doctype.h
new file mode 100644
index 0000000..3c8df8c
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/public/doctype.h
@@ -0,0 +1,88 @@
+// 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: mdsteele@google.com (Matthew D. Steele)
+
+#ifndef NET_INSTAWEB_HTMLPARSE_PUBLIC_DOCTYPE_H_
+#define NET_INSTAWEB_HTMLPARSE_PUBLIC_DOCTYPE_H_
+
+#include "net/instaweb/util/public/string_util.h" // for StringPiece
+
+#include "net/instaweb/util/public/content_type.h"
+
+namespace net_instaweb {
+
+class DocType {
+ public:
+ DocType() : doctype_(UNKNOWN) {}
+ DocType(const DocType& src) : doctype_(src.doctype_) {}
+ ~DocType() {}
+
+ DocType& operator=(const DocType& src) {
+ if (&src != this) {
+ doctype_ = src.doctype_;
+ }
+ return *this;
+ }
+
+ bool operator==(const DocType& other) const {
+ return doctype_ == other.doctype_;
+ }
+
+ bool operator!=(const DocType& other) const {
+ return doctype_ != other.doctype_;
+ }
+
+ // Return true iff this is a known XHTML doctype (of some version).
+ bool IsXhtml() const;
+ // Return true iff this is an HTML 5 or XHTML 5 doctype.
+ bool IsVersion5() const;
+ // TODO(mdsteele): Add more such methods as necessary.
+
+ static const DocType kUnknown;
+ static const DocType kHTML5;
+ static const DocType kHTML4Strict;
+ static const DocType kHTML4Transitional;
+ static const DocType kXHTML5;
+ static const DocType kXHTML11;
+ static const DocType kXHTML10Strict;
+ static const DocType kXHTML10Transitional;
+
+ // Given the contents of an HTML directive and the content type of the file
+ // it appears in, update this DocType to match that specified by the
+ // directive and return true. If the directive is not a doctype directive,
+ // return false and don't alter the DocType.
+ bool Parse(const StringPiece& directive,
+ const ContentType& content_type);
+
+ private:
+ enum DocTypeEnum {
+ UNKNOWN = 0,
+ HTML_5,
+ HTML_4_STRICT,
+ HTML_4_TRANSITIONAL,
+ XHTML_5,
+ XHTML_1_1,
+ XHTML_1_0_STRICT,
+ XHTML_1_0_TRANSITIONAL,
+ };
+
+ DocType(DocTypeEnum doctype) : doctype_(doctype) {}
+
+ DocTypeEnum doctype_;
+};
+
+} // net_instaweb
+
+#endif // NET_INSTAWEB_HTMLPARSE_PUBLIC_DOCTYPE_H_
diff --git a/trunk/src/net/instaweb/htmlparse/public/empty_html_filter.h b/trunk/src/net/instaweb/htmlparse/public/empty_html_filter.h
new file mode 100644
index 0000000..668ffcb
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/public/empty_html_filter.h
@@ -0,0 +1,52 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_HTMLPARSE_PUBLIC_EMPTY_HTML_FILTER_H_
+#define NET_INSTAWEB_HTMLPARSE_PUBLIC_EMPTY_HTML_FILTER_H_
+
+#include <string>
+#include "net/instaweb/htmlparse/public/html_filter.h"
+
+namespace net_instaweb {
+
+// Base class for rewriting filters that don't need to be sure to
+// override every filter method. Other filters that need to be sure
+// they override every method would derive directly from HtmlFilter.
+class EmptyHtmlFilter : public HtmlFilter {
+ public:
+ EmptyHtmlFilter();
+ virtual ~EmptyHtmlFilter();
+
+ virtual void StartDocument();
+ virtual void EndDocument();
+ virtual void StartElement(HtmlElement* element);
+ virtual void EndElement(HtmlElement* element);
+ virtual void Cdata(HtmlCdataNode* cdata);
+ virtual void Comment(HtmlCommentNode* comment);
+ virtual void IEDirective(HtmlIEDirectiveNode* directive);
+ virtual void Characters(HtmlCharactersNode* characters);
+ virtual void Directive(HtmlDirectiveNode* directive);
+ virtual void Flush();
+
+ // Note -- this does not provide an implementation for Name(). This
+ // must be supplied by derived classes.
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_HTMLPARSE_PUBLIC_EMPTY_HTML_FILTER_H_
diff --git a/trunk/src/net/instaweb/htmlparse/public/file_driver.h b/trunk/src/net/instaweb/htmlparse/public/file_driver.h
new file mode 100644
index 0000000..cb9b091
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/public/file_driver.h
@@ -0,0 +1,73 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_HTMLPARSE_PUBLIC_FILE_DRIVER_H_
+#define NET_INSTAWEB_HTMLPARSE_PUBLIC_FILE_DRIVER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/html_parser_types.h"
+#include "net/instaweb/htmlparse/public/html_writer_filter.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/htmlparse/public/logging_html_filter.h"
+#include "net/instaweb/htmlparse/public/statistics_log.h"
+#include <string>
+
+namespace net_instaweb {
+
+// Framework for reading an input HTML file, running it through
+// a chain of HTML filters, and writing an output file.
+class FileDriver {
+ public:
+ FileDriver(HtmlParse* html_parse, FileSystem* file_system);
+
+ // Return the parser. This can be used to add filters.
+ HtmlParse* html_parse() { return html_parse_; }
+
+ // Helper function to generate an output .html filename from
+ // an input filename. Given "/a/b/c.html" returns "a/b/c.out.html".
+ // Returns false if the input file does not contain a "."
+ static bool GenerateOutputFilename(
+ const char* infilename, std::string* outfilename);
+
+ // Helper function to generate an output .stats filename from
+ // an input filename. Given "/a/b/c.html" returns "a/b/c.stats".
+ // Returns false if the input file does not contain a "."
+ static bool GenerateStatsFilename(
+ const char* infilename, std::string* statsfilename);
+
+ // Error messages are sent to the message file, true is returned
+ // if the file was parsed successfully.
+ bool ParseFile(const char* infilename,
+ const char* outfilename,
+ const char* statsfilename,
+ MessageHandler* handler);
+
+ private:
+ HtmlParse* html_parse_;
+ LoggingFilter logging_filter_;
+ StatisticsLog* stats_log_;
+ HtmlWriterFilter html_write_filter_;
+ bool filters_added_;
+ FileSystem* file_system_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileDriver);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_HTMLPARSE_PUBLIC_FILE_DRIVER_H_
diff --git a/trunk/src/net/instaweb/htmlparse/public/file_statistics_log.h b/trunk/src/net/instaweb/htmlparse/public/file_statistics_log.h
new file mode 100644
index 0000000..dfd7d55
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/public/file_statistics_log.h
@@ -0,0 +1,49 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#ifndef NET_INSTAWEB_HTMLPARSE_PUBLIC_FILE_STATISTICS_LOG_H_
+#define NET_INSTAWEB_HTMLPARSE_PUBLIC_FILE_STATISTICS_LOG_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/statistics_log.h"
+#include "net/instaweb/util/public/file_system.h"
+
+namespace net_instaweb {
+
+class MessageHandler;
+
+// Statistics logger that sends its output to a file.
+class FileStatisticsLog : public StatisticsLog {
+ public:
+ // Note: calling context responsible for closing & cleaning up file.
+ explicit FileStatisticsLog(FileSystem::OutputFile* file,
+ MessageHandler* message_handler);
+ virtual ~FileStatisticsLog();
+ virtual void LogStat(const char *statName, int value);
+ virtual void LogDifference(const char *statName,
+ int value1, int value2);
+ private:
+ FileSystem::OutputFile* file_;
+ MessageHandler* message_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileStatisticsLog);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_HTMLPARSE_PUBLIC_FILE_STATISTICS_LOG_H_
diff --git a/trunk/src/net/instaweb/htmlparse/public/html_element.h b/trunk/src/net/instaweb/htmlparse/public/html_element.h
new file mode 100644
index 0000000..11888d7
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/public/html_element.h
@@ -0,0 +1,264 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_HTMLPARSE_PUBLIC_HTML_ELEMENT_H_
+#define NET_INSTAWEB_HTMLPARSE_PUBLIC_HTML_ELEMENT_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/htmlparse/public/html_node.h"
+#include "net/instaweb/htmlparse/public/html_parser_types.h"
+#include "net/instaweb/util/public/atom.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/symbol_table.h"
+
+namespace net_instaweb {
+
+class HtmlElement : public HtmlNode {
+ public:
+ // Tags can be closed in three ways: implicitly (e.g. <img ..>),
+ // briefly (e.g. <br/>), or explicitly (<a...>...</a>). The
+ // Lexer will always record the way it parsed a tag, but synthesized
+ // elements will have AUTO_CLOSE, and rewritten elements may
+ // no longer qualify for the closing style with which they were
+ // parsed.
+ enum CloseStyle {
+ AUTO_CLOSE, // synthesized tag, or not yet closed in source
+ IMPLICIT_CLOSE, // E.g. <img...> <meta...> <link...> <br...> <input...>
+ EXPLICIT_CLOSE, // E.g. <a href=...>anchor</a>
+ BRIEF_CLOSE, // E.g. <head/>
+ UNCLOSED // Was never closed in source
+ };
+
+ class Attribute {
+ public:
+ // A large quantity of HTML in the wild has attributes that are
+ // improperly escaped. Browsers are generally tolerant of this.
+ // But we want to avoid corrupting pages we do not understand.
+
+ // The result of value() and escaped_value() is still owned by this, and
+ // will be invalidated by a subsequent call to SetValue() or
+ // SetUnescapedValue
+
+ Atom name() const { return name_; }
+ void set_name(Atom name) { name_ = name; }
+
+ // Returns the value in its original directly from the HTML source.
+ // This may have HTML escapes in it, such as "&".
+ const char* escaped_value() const { return escaped_value_.get(); }
+
+ // The result of value() is still owned by this, and will be invalidated by
+ // a subsequent call to set_value().
+ //
+ // The result will be a NUL-terminated string containing the value of the
+ // attribute, or NULL if the attribute has no value at all (this is
+ // distinct from having the empty string for a value).
+
+ // Returns the unescaped value, suitable for directly operating on
+ // in filters as URLs or other data.
+ const char* value() const { return value_.get(); }
+
+ // See comment about quote on constructor for Attribute.
+ // Returns the quotation mark associated with this URL, typically
+ // ", ', or an empty string.
+ const char* quote() const { return quote_; }
+
+ // Two related methods to modify the value of attribute (eg to rewrite
+ // dest of src or href). As with the constructor, copies the string in,
+ // so caller retains ownership of value.
+ //
+ // A StringPiece pointing to an empty string (that is, a char array {'\0'})
+ // indicates that the attribute value is the empty string (e.g. <foo
+ // bar="">); however, a StringPiece with a data() pointer of NULL indicates
+ // that the attribute has no value at all (e.g. <foo bar>). This is an
+ // important distinction.
+ //
+ // Note that passing a value containing NULs in the middle will cause
+ // breakage, but this isn't currently checked for.
+ // TODO(mdsteele): Perhaps we should check for this?
+
+ // Sets the value of the attribute. No HTML escaping is expected.
+ // This call causes the HTML-escaped value to be automatically computed
+ // by scanning the value and escaping any characters required in HTML
+ // attributes.
+ void SetValue(const StringPiece& value);
+
+ // Sets the escaped value. This is intended to be called from the HTML
+ // Lexer, and results in the Value being computed automatically by
+ // scanning the value for escape sequences.
+ void SetEscapedValue(const StringPiece& value);
+
+ // See comment about quote on constructor for Attribute.
+ void set_quote(const char *quote) {
+ quote_ = quote;
+ }
+
+ friend class HtmlElement;
+
+ private:
+ // TODO(jmarantz): arg 'quote' must be a static string, or NULL,
+ // if quoting is not yet known (e.g. this is a synthesized attribute.
+ // This is hard-to-describe and we should probably use an Atom for
+ // the quote, and decide how to handle NULL.
+ //
+ // This should only be called from AddAttribute
+ Attribute(Atom name, const StringPiece& value,
+ const StringPiece& escaped_value, const char* quote);
+
+ static inline void CopyValue(const StringPiece& src,
+ scoped_array<char>* dst);
+
+ Atom name_;
+ scoped_array<char> escaped_value_;
+ scoped_array<char> value_;
+ const char* quote_;
+
+ DISALLOW_COPY_AND_ASSIGN(Attribute);
+ };
+
+ virtual ~HtmlElement();
+
+ // Add a copy of an attribute to this element. The attribute may come
+ // from this element, or another one.
+ void AddAttribute(const Attribute& attr);
+
+ // Unconditionally add attribute, copying value.
+ // Quote is assumed to be a static const char *.
+ // Doesn't check for attribute duplication (which is illegal in html).
+ //
+ // The value, if non-null, is assumed to be unescaped. See also
+ // AddEscapedAttribute.
+ void AddAttribute(Atom name, const StringPiece& value, const char* quote);
+ // Unconditionally add attribute with int value.
+ void AddAttribute(Atom name, int value);
+ // As AddAttribute, but assumes value has been escaped for html output.
+ void AddEscapedAttribute(Atom name, const StringPiece& escaped_value,
+ const char* quote);
+
+ // Removes the attribute at the given index, shifting higher-indexed
+ // attributes down. Note that this operation is linear in the number of
+ // attributes.
+ void DeleteAttribute(int i);
+
+ // Remove the attribute with the given name. Return true if the attribute
+ // was deleted, false if it wasn't there to begin with.
+ bool DeleteAttribute(Atom name);
+
+ // Look up attribute by name. NULL if no attribute exists.
+ // Use this for attributes whose value you might want to change
+ // after lookup.
+ const Attribute* FindAttribute(Atom name) const;
+ Attribute* FindAttribute(Atom name) {
+ const HtmlElement* const_this = this;
+ const Attribute* result = const_this->FindAttribute(name);
+ return const_cast<Attribute*>(result);
+ }
+
+ // Look up attribute value by name. NULL if no attribute exists.
+ // Use this only if you don't intend to change the attribute value;
+ // if you might change the attribute value, use FindAttribute instead
+ // (this avoids a double lookup).
+ const char* AttributeValue(Atom name) const {
+ const Attribute* attribute = FindAttribute(name);
+ if (attribute != NULL) {
+ return attribute->value();
+ }
+ return NULL;
+ }
+
+ // Look up attribute value by name. false if no attribute exists,
+ // or attribute value cannot be converted to int. Otherwise
+ // sets *value.
+ bool IntAttributeValue(Atom name, int* value) const {
+ const Attribute* attribute = FindAttribute(name);
+ if (attribute != NULL) {
+ return StringToInt(attribute->value(), value);
+ }
+ return false;
+ }
+
+ // Small integer uniquely identifying the HTML element, primarily
+ // for debugging.
+ void set_sequence(int sequence) { sequence_ = sequence; }
+
+
+ Atom tag() const {return tag_;}
+
+ // Changing that tag of an element should only occur if the caller knows
+ // that the old attributes make sense for the new tag. E.g. a div could
+ // be changed to a span.
+ void set_tag(Atom new_tag) { tag_ = new_tag; }
+
+ int attribute_size() const {return attributes_.size(); }
+ const Attribute& attribute(int i) const { return *attributes_[i]; }
+ Attribute& attribute(int i) { return *attributes_[i]; }
+
+ friend class HtmlParse;
+ friend class HtmlLexer;
+
+ CloseStyle close_style() const { return close_style_; }
+ void set_close_style(CloseStyle style) { close_style_ = style; }
+
+ // Render an element as a string for debugging. This is not
+ // intended as a fully legal serialization.
+ void ToString(std::string* buf) const;
+ void DebugPrint() const;
+
+ int begin_line_number() const { return begin_line_number_; }
+ int end_line_number() const { return end_line_number_; }
+
+ protected:
+ virtual void SynthesizeEvents(const HtmlEventListIterator& iter,
+ HtmlEventList* queue);
+ virtual void InvalidateIterators(const HtmlEventListIterator& end);
+
+ virtual HtmlEventListIterator begin() const { return begin_; }
+ virtual HtmlEventListIterator end() const { return end_; }
+
+ private:
+ // Begin/end event iterators are used by HtmlParse to keep track
+ // of the span of events underneath an element. This is primarily to
+ // help delete the element. Events are not public.
+ void set_begin(const HtmlEventListIterator& begin) { begin_ = begin; }
+ void set_end(const HtmlEventListIterator& end) { end_ = end; }
+
+ void set_begin_line_number(int line) { begin_line_number_ = line; }
+ void set_end_line_number(int line) { end_line_number_ = line; }
+
+ // construct via HtmlParse::NewElement
+ HtmlElement(HtmlElement* parent, Atom tag, const HtmlEventListIterator& begin,
+ const HtmlEventListIterator& end);
+
+ int sequence_;
+ Atom tag_;
+ std::vector<Attribute*> attributes_;
+ HtmlEventListIterator begin_;
+ HtmlEventListIterator end_;
+ CloseStyle close_style_;
+ int begin_line_number_;
+ int end_line_number_;
+
+ DISALLOW_COPY_AND_ASSIGN(HtmlElement);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_HTMLPARSE_PUBLIC_HTML_ELEMENT_H_
diff --git a/trunk/src/net/instaweb/htmlparse/public/html_escape.h b/trunk/src/net/instaweb/htmlparse/public/html_escape.h
new file mode 100644
index 0000000..a6eb4b8
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/public/html_escape.h
@@ -0,0 +1,87 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_HTMLPARSE_PUBLIC_HTML_ESCAPE_H_
+#define NET_INSTAWEB_HTMLPARSE_PUBLIC_HTML_ESCAPE_H_
+
+#include <map>
+#include "base/basictypes.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+class HtmlEscape {
+ public:
+ // Initialize a singleton instance of this class. This call is
+ // inherently thread unsafe, but only the first time it is called.
+ // If multi-threaded programs call this function before spawning
+ // threads then there will be no races.
+ static void Init();
+
+ // Tear down the singleton instance of this class, freeing any
+ // allocated memory. This call is inherently thread unsafe.
+ static void ShutDown();
+
+ // Take a raw text and escape it so it's safe for an HTML attribute,
+ // e.g. a&b --> a&b
+ static StringPiece Escape(const StringPiece& unescaped, std::string* buf) {
+ return singleton_->EscapeHelper(unescaped, buf);
+ }
+
+ // Take escaped text and unescape it so its value can be interpreted,
+ // e.g. "http://myhost.com/p?v&w" --> "http://myhost.com/p?v&w"
+ static StringPiece Unescape(const StringPiece& escaped, std::string* buf) {
+ return singleton_->UnescapeHelper(escaped, buf);
+ }
+
+ // Note that Escape and Unescape are not guaranteed to be inverses of
+ // one another. For example, Unescape("")=="&", but Escape("&")="&".
+ // However, note that Unescape(Escape(s)) == s.
+ //
+ // Another case to be wary of is when the argument to Unescape is not
+ // properly escaped. The result will be that the string is returned
+ // unmodified. For example, Unescape("a&b")=="a&b", butthen re-escaping
+ // that will give "a&b". Hence, the careful maintainer of an HTML
+ // parsing and rewriting system will need to maintain the original escaped
+ // text parsed from HTML files, and pass that to browsers.
+
+ private:
+ HtmlEscape();
+ const char* UnescapeAttributeValue();
+
+ static HtmlEscape* singleton_;
+
+ StringPiece EscapeHelper(const StringPiece& unescaped,
+ std::string* buf) const;
+ StringPiece UnescapeHelper(const StringPiece& escaped,
+ std::string* buf) const;
+
+ typedef std::map<std::string, std::string,
+ StringCompareInsensitive> StringStringMapInsensitive;
+ typedef std::map<std::string, std::string> StringStringMapSensitive;
+ StringStringMapInsensitive unescape_insensitive_map_;
+ StringStringMapSensitive unescape_sensitive_map_;
+ StringStringMapSensitive escape_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(HtmlEscape);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_HTMLPARSE_PUBLIC_HTML_ESCAPE_H_
diff --git a/trunk/src/net/instaweb/htmlparse/public/html_filter.h b/trunk/src/net/instaweb/htmlparse/public/html_filter.h
new file mode 100644
index 0000000..bb6ac2d
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/public/html_filter.h
@@ -0,0 +1,82 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_HTMLPARSE_PUBLIC_HTML_FILTER_H_
+#define NET_INSTAWEB_HTMLPARSE_PUBLIC_HTML_FILTER_H_
+
+#include <string>
+#include "net/instaweb/htmlparse/public/html_parser_types.h"
+
+namespace net_instaweb {
+
+class HtmlFilter {
+ public:
+ HtmlFilter();
+ virtual ~HtmlFilter();
+
+ // Starts a new document. Filters should clear their state in this function,
+ // as the same Filter instance may be used for multiple HTML documents.
+ virtual void StartDocument() = 0;
+ // Note: EndDocument will be called imediately before the last Flush call.
+ virtual void EndDocument() = 0;
+
+ // When an HTML element is encountered during parsing, each filter's
+ // StartElement method is called. The HtmlElement lives for the entire
+ // duration of the document.
+ //
+ // TODO(jmarantz): consider passing handles rather than pointers and
+ // reference-counting them instead to save memory on long documents.
+ virtual void StartElement(HtmlElement* element) = 0;
+ virtual void EndElement(HtmlElement* element) = 0;
+
+ // Called for CDATA blocks (e.g. <![CDATA[foobar]]>)
+ virtual void Cdata(HtmlCdataNode* cdata) = 0;
+
+ // Called for HTML comments that aren't IE directives (e.g. <!--foobar-->).
+ virtual void Comment(HtmlCommentNode* comment) = 0;
+
+ // Called for an IE directive; typically used for CSS styling.
+ // See http://msdn.microsoft.com/en-us/library/ms537512(VS.85).aspx
+ //
+ // TODO(mdsteele): Should we try to maintain the nested structure of
+ // the conditionals, in the same way that we maintain nesting of elements?
+ virtual void IEDirective(HtmlIEDirectiveNode* directive) = 0;
+
+ // Called for raw characters between tags.
+ virtual void Characters(HtmlCharactersNode* characters) = 0;
+
+ // Called for HTML directives (e.g. <!doctype foobar>).
+ virtual void Directive(HtmlDirectiveNode* directive) = 0;
+
+ // Notifies the Filter that a flush is occurring. A filter that's
+ // generating streamed output should flush at this time. A filter
+ // that's mutating elements can mutate any element seen since the
+ // most recent flush; once an element is flushed it is already on
+ // the wire to its destination and it's too late to mutate. Flush
+ // is initiated by an application calling HttpParse::Flush().
+ //
+ // Flush() is called after all other handlers during a HttpParse::Flush().
+ virtual void Flush() = 0;
+
+ // The name of this filter -- used for logging and debugging.
+ virtual const char* Name() const = 0;
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_HTMLPARSE_PUBLIC_HTML_FILTER_H_
diff --git a/trunk/src/net/instaweb/htmlparse/public/html_node.h b/trunk/src/net/instaweb/htmlparse/public/html_node.h
new file mode 100644
index 0000000..5c78c3e
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/public/html_node.h
@@ -0,0 +1,207 @@
+/**
+ * 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: mdsteele@google.com (Matthew D. Steele)
+
+#ifndef NET_INSTAWEB_HTMLPARSE_PUBLIC_HTML_NODE_H_
+#define NET_INSTAWEB_HTMLPARSE_PUBLIC_HTML_NODE_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/html_parser_types.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+// Base class for HtmlElement and HtmlLeafNode
+class HtmlNode {
+ public:
+ virtual ~HtmlNode();
+ friend class HtmlParse;
+
+ HtmlElement* parent() const { return parent_; }
+ bool live() const { return live_; }
+
+ // Marks a node as dead. The queue's end iterator should be passed in,
+ // to remove references to stale iterators, and to force IsRewritable to
+ // return false.
+ void MarkAsDead(const HtmlEventListIterator& end);
+
+ protected:
+ // TODO(jmarantz): jmaessen suggests instantiating the html nodes
+ // without parents and computing them from context at the time they
+ // are instantiated from the lexer. This is a little more difficult
+ // when synthesizing new nodes, however. We assert sanity, however,
+ // when calling HtmlParse::ApplyFilter.
+ explicit HtmlNode(HtmlElement* parent) : parent_(parent), live_(true) {}
+
+ // Create new event object(s) representing this node, and insert them into
+ // the queue just before the given iterator; also, update this node object as
+ // necessary so that begin() and end() will return iterators pointing to
+ // the new event(s). The line number for each event should probably be -1.
+ virtual void SynthesizeEvents(const HtmlEventListIterator& iter,
+ HtmlEventList* queue) = 0;
+ virtual void InvalidateIterators(const HtmlEventListIterator& end) = 0;
+
+ // Return an iterator pointing to the first event associated with this node.
+ virtual HtmlEventListIterator begin() const = 0;
+ // Return an iterator pointing to the last event associated with this node.
+ virtual HtmlEventListIterator end() const = 0;
+
+ private:
+ friend class HtmlTestingPeer;
+
+ // Note: setting the parent doesn't change the DOM -- it just updates
+ // the pointer. This is intended to be called only from the DOM manipulation
+ // methods in HtmlParse.
+ void set_parent(HtmlElement* parent) { parent_ = parent; }
+
+ HtmlElement* parent_;
+ bool live_;
+ DISALLOW_COPY_AND_ASSIGN(HtmlNode);
+};
+
+// Base class for leaf nodes (like HtmlCharactersNode and HtmlCommentNode)
+class HtmlLeafNode : public HtmlNode {
+ public:
+ virtual ~HtmlLeafNode();
+ friend class HtmlParse;
+
+ protected:
+ HtmlLeafNode(HtmlElement* parent, const HtmlEventListIterator& iter)
+ : HtmlNode(parent),
+ iter_(iter) {}
+ virtual HtmlEventListIterator begin() const { return iter_; }
+ virtual HtmlEventListIterator end() const { return iter_; }
+ void set_iter(const HtmlEventListIterator& iter) { iter_ = iter; }
+ virtual void InvalidateIterators(const HtmlEventListIterator& end);
+
+ private:
+ HtmlEventListIterator iter_;
+ DISALLOW_COPY_AND_ASSIGN(HtmlLeafNode);
+};
+
+// Leaf node representing a CDATA section
+class HtmlCdataNode : public HtmlLeafNode {
+ public:
+ virtual ~HtmlCdataNode();
+ const std::string& contents() { return contents_; }
+ friend class HtmlParse;
+
+ protected:
+ virtual void SynthesizeEvents(const HtmlEventListIterator& iter,
+ HtmlEventList* queue);
+
+ private:
+ HtmlCdataNode(HtmlElement* parent,
+ const StringPiece& contents,
+ const HtmlEventListIterator& iter)
+ : HtmlLeafNode(parent, iter),
+ contents_(contents.data(), contents.size()) {}
+ const std::string contents_;
+ DISALLOW_COPY_AND_ASSIGN(HtmlCdataNode);
+};
+
+// Leaf node representing raw characters in HTML
+class HtmlCharactersNode : public HtmlLeafNode {
+ public:
+ virtual ~HtmlCharactersNode();
+ const std::string& contents() { return contents_; }
+ void Append(const StringPiece& str) {
+ contents_.append(str.data(), str.size());
+ }
+ friend class HtmlParse;
+
+ protected:
+ virtual void SynthesizeEvents(const HtmlEventListIterator& iter,
+ HtmlEventList* queue);
+
+ private:
+ HtmlCharactersNode(HtmlElement* parent,
+ const StringPiece& contents,
+ const HtmlEventListIterator& iter)
+ : HtmlLeafNode(parent, iter),
+ contents_(contents.data(), contents.size()) {}
+ std::string contents_;
+ DISALLOW_COPY_AND_ASSIGN(HtmlCharactersNode);
+};
+
+// Leaf node representing an HTML comment
+class HtmlCommentNode : public HtmlLeafNode {
+ public:
+ virtual ~HtmlCommentNode();
+ const std::string& contents() { return contents_; }
+ friend class HtmlParse;
+
+ protected:
+ virtual void SynthesizeEvents(const HtmlEventListIterator& iter,
+ HtmlEventList* queue);
+
+ private:
+ HtmlCommentNode(HtmlElement* parent,
+ const StringPiece& contents,
+ const HtmlEventListIterator& iter)
+ : HtmlLeafNode(parent, iter),
+ contents_(contents.data(), contents.size()) {}
+ const std::string contents_;
+ DISALLOW_COPY_AND_ASSIGN(HtmlCommentNode);
+};
+
+// Leaf node representing an HTML IE directive
+class HtmlIEDirectiveNode : public HtmlLeafNode {
+ public:
+ virtual ~HtmlIEDirectiveNode();
+ const std::string& contents() { return contents_; }
+ friend class HtmlParse;
+
+ protected:
+ virtual void SynthesizeEvents(const HtmlEventListIterator& iter,
+ HtmlEventList* queue);
+
+ private:
+ HtmlIEDirectiveNode(HtmlElement* parent,
+ const StringPiece& contents,
+ const HtmlEventListIterator& iter)
+ : HtmlLeafNode(parent, iter),
+ contents_(contents.data(), contents.size()) {}
+ const std::string contents_;
+ DISALLOW_COPY_AND_ASSIGN(HtmlIEDirectiveNode);
+};
+
+// Leaf node representing an HTML directive
+class HtmlDirectiveNode : public HtmlLeafNode {
+ public:
+ virtual ~HtmlDirectiveNode();
+ const std::string& contents() { return contents_; }
+ friend class HtmlParse;
+
+ protected:
+ virtual void SynthesizeEvents(const HtmlEventListIterator& iter,
+ HtmlEventList* queue);
+
+ private:
+ HtmlDirectiveNode(HtmlElement* parent,
+ const StringPiece& contents,
+ const HtmlEventListIterator& iter)
+ : HtmlLeafNode(parent, iter),
+ contents_(contents.data(), contents.size()) {}
+ const std::string contents_;
+ DISALLOW_COPY_AND_ASSIGN(HtmlDirectiveNode);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_HTMLPARSE_PUBLIC_HTML_NODE_H_
diff --git a/trunk/src/net/instaweb/htmlparse/public/html_parse.h b/trunk/src/net/instaweb/htmlparse/public/html_parse.h
new file mode 100644
index 0000000..c7cb804
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/public/html_parse.h
@@ -0,0 +1,294 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_HTMLPARSE_PUBLIC_HTML_PARSE_H_
+#define NET_INSTAWEB_HTMLPARSE_PUBLIC_HTML_PARSE_H_
+
+#include <stdarg.h>
+#include <set>
+#include <vector>
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/doctype.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/htmlparse/public/html_node.h"
+#include "net/instaweb/htmlparse/public/html_parser_types.h"
+#include "net/instaweb/util/public/content_type.h"
+#include "net/instaweb/util/public/google_url.h"
+#include "net/instaweb/util/public/printf_format.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/symbol_table.h"
+
+namespace net_instaweb {
+
+class Timer;
+
+class HtmlParse {
+ public:
+ explicit HtmlParse(MessageHandler* message_handler);
+ ~HtmlParse();
+
+ // Application methods for parsing functions and adding filters
+
+ // Add a new html filter to the filter-chain
+ void AddFilter(HtmlFilter* filter);
+
+ // Initiate a chunked parsing session. Finish with FinishParse. The
+ // url is only used to resolve relative URLs; the contents are not
+ // directly fetched. The caller must supply the text and call ParseText.
+ void StartParse(const StringPiece& url) {
+ StartParseWithType(url, kContentTypeHtml);
+ }
+ void StartParseWithType(const StringPiece& url,
+ const ContentType& content_type) {
+ StartParseId(url, url, content_type);
+ }
+ // Use an error message id that is distinct from the url.
+ // Mostly useful for testing.
+ void StartParseId(const StringPiece& url, const StringPiece& id,
+ const ContentType& content_type);
+
+ // Parses an arbitrary block of an html file, queuing up the events. Call
+ // Flush to send the events through the Filter.
+ //
+ // To parse an entire file, first call StartParse(), then call
+ // ParseText on the file contents (in whatever size chunks are convenient),
+ // then call FinishParse().
+ void ParseText(const char* content, int size);
+ void ParseText(const StringPiece& sp) { ParseText(sp.data(), sp.size()); }
+
+ // Flush the currently queued events through the filters. It is desirable
+ // for large web pages, particularly dynamically generated ones, to start
+ // getting delivered to the browser as soon as they are ready. On the
+ // other hand, rewriting is more powerful when more of the content can
+ // be considered for image/css/js spriting. This method should be called
+ // when the controlling network process wants to induce a new chunk of
+ // output. The less you call this function the better the rewriting will
+ // be.
+ void Flush();
+
+ // Finish a chunked parsing session. This also induces a Flush.
+ void FinishParse();
+
+
+ // Utility methods for implementing filters
+
+ HtmlCdataNode* NewCdataNode(HtmlElement* parent,
+ const StringPiece& contents);
+ HtmlCharactersNode* NewCharactersNode(HtmlElement* parent,
+ const StringPiece& literal);
+ HtmlCommentNode* NewCommentNode(HtmlElement* parent,
+ const StringPiece& contents);
+ HtmlDirectiveNode* NewDirectiveNode(HtmlElement* parent,
+ const StringPiece& contents);
+ HtmlIEDirectiveNode* NewIEDirectiveNode(HtmlElement* parent,
+ const StringPiece& contents);
+
+ // DOM-manipulation methods.
+ // TODO(sligocki): Find Javascript equivalents and list them or even change
+ // our names to be consistent.
+
+ // TODO(mdsteele): Rename these methods to e.g. InsertNodeBeforeNode.
+ // This and downstream filters will then see inserted elements but upstream
+ // filters will not.
+ // Note: In Javascript the first is called insertBefore and takes the arg
+ // in the oposite order.
+ void InsertElementBeforeElement(const HtmlNode* existing_node,
+ HtmlNode* new_node);
+ void InsertElementAfterElement(const HtmlNode* existing_node,
+ HtmlNode* new_node);
+
+ // Add child element at the begining or end of existing_parent's children.
+ // Named after Javascript's appendChild method.
+ void PrependChild(const HtmlElement* existing_parent, HtmlNode* new_child);
+ void AppendChild(const HtmlElement* existing_parent, HtmlNode* new_child);
+
+ // Insert element before the current one. current_ remains unchanged.
+ void InsertElementBeforeCurrent(HtmlNode* node);
+
+ // Insert element after the current one, moving current_ to the new
+ // element. In a Filter, the flush-loop will advance past this on
+ // the next iteration.
+ void InsertElementAfterCurrent(HtmlNode* node);
+
+ // Enclose element around two elements in a sequence. The first
+ // element must be the same as, or precede the last element in the
+ // event-stream, and this is not checked, but the two elements do
+ // not need to be adjacent. They must have the same parent to start
+ // with.
+ //
+ // This differs from MoveSequenceToParent in that the new parent is
+ // not yet in the DOM tree, and will be inserted around the
+ // elements.
+ bool AddParentToSequence(HtmlNode* first, HtmlNode* last,
+ HtmlElement* new_parent);
+
+ // Moves a node-sequence to an already-existing parent, where they
+ // will be placed as the last elements in that parent. Returns false
+ // if the operation could not be performed because either the node
+ // or its parent was partially or wholy flushed.
+ //
+ // This differs from AddParentToSequence in that the parent is already
+ // in the DOM-tree.
+ bool MoveCurrentInto(HtmlElement* new_parent);
+
+ // If the given node is rewritable, delete it and all of its children (if
+ // any) and return true; otherwise, do nothing and return false.
+ // Note: Javascript appears to use removeChild for this.
+ bool DeleteElement(HtmlNode* node);
+
+ // Delete a parent element, retaining any children and moving them to
+ // reside under the parent's parent.
+ bool DeleteSavingChildren(HtmlElement* element);
+
+ // If possible, replace the existing node with the new node and return true;
+ // otherwise, do nothing and return false.
+ bool ReplaceNode(HtmlNode* existing_node, HtmlNode* new_node);
+
+
+ HtmlElement* NewElement(HtmlElement* parent, Atom tag);
+
+ bool IsRewritable(const HtmlNode* node) const;
+
+ void ClearElements();
+
+ void DebugPrintQueue(); // Print queue (for debugging)
+
+ Atom Intern(const std::string& name) {
+ return string_table_.Intern(name);
+ }
+ Atom Intern(const char* name) {
+ return string_table_.Intern(name);
+ }
+
+ // Implementation helper with detailed knowledge of html parsing libraries
+ friend class HtmlLexer;
+
+ // Determines whether a tag should be terminated in HTML.
+ bool IsImplicitlyClosedTag(Atom tag) const;
+
+ // Determines whether a tag allows brief termination in HTML, e.g. <tag/>
+ bool TagAllowsBriefTermination(Atom tag) const;
+
+ MessageHandler* message_handler() const { return message_handler_; }
+ // Gets the current location information; typically to help with error
+ // messages.
+ const char* url() const { return url_.c_str(); }
+ // Gets a parsed GURL& corresponding to url().
+ const GURL& gurl() const { return gurl_; }
+ const char* id() const { return id_.c_str(); }
+ int line_number() const { return line_number_; }
+ // Return the current assumed doctype of the document (based on the content
+ // type and any HTML directives encountered so far).
+ const DocType& doctype() const;
+
+ // Interface for any caller to report an error message via the message handler
+ void Info(const char* filename, int line, const char* msg, ...)
+ INSTAWEB_PRINTF_FORMAT(4, 5);
+ void Warning(const char* filename, int line, const char* msg, ...)
+ INSTAWEB_PRINTF_FORMAT(4, 5);
+ void Error(const char* filename, int line, const char* msg, ...)
+ INSTAWEB_PRINTF_FORMAT(4, 5);
+ void FatalError(const char* filename, int line, const char* msg, ...)
+ INSTAWEB_PRINTF_FORMAT(4, 5);
+
+ void InfoV(const char* file, int line, const char *msg, va_list args);
+ void WarningV(const char* file, int line, const char *msg, va_list args);
+ void ErrorV(const char* file, int line, const char *msg, va_list args);
+ void FatalErrorV(const char* file, int line, const char* msg, va_list args);
+
+ // Report error message with current parsing filename and linenumber.
+ void InfoHere(const char* msg, ...) INSTAWEB_PRINTF_FORMAT(2, 3);
+ void WarningHere(const char* msg, ...) INSTAWEB_PRINTF_FORMAT(2, 3);
+ void ErrorHere(const char* msg, ...) INSTAWEB_PRINTF_FORMAT(2, 3);
+ void FatalErrorHere(const char* msg, ...) INSTAWEB_PRINTF_FORMAT(2, 3);
+
+ void InfoHereV(const char *msg, va_list args) {
+ InfoV(id_.c_str(), line_number_, msg, args);
+ }
+ void WarningHereV(const char *msg, va_list args) {
+ WarningV(id_.c_str(), line_number_, msg, args);
+ }
+ void ErrorHereV(const char *msg, va_list args) {
+ ErrorV(id_.c_str(), line_number_, msg, args);
+ }
+ void FatalErrorHereV(const char* msg, va_list args) {
+ FatalErrorV(id_.c_str(), line_number_, msg, args);
+ }
+
+ void AddElement(HtmlElement* element, int line_number);
+ void CloseElement(HtmlElement* element, HtmlElement::CloseStyle close_style,
+ int line_number);
+
+ // Run a filter on the current queue of parse nodes. This is visible
+ // for testing.
+ void ApplyFilter(HtmlFilter* filter);
+
+ // Provide timer to helping to report timing of each filter. In the absense
+ // of a timer, reporting will be suppressed.
+ void set_timer(Timer* timer) { timer_ = timer; }
+
+ private:
+ HtmlEventListIterator Last(); // Last element in queue
+ bool IsInEventWindow(const HtmlEventListIterator& iter) const;
+ void InsertElementBeforeEvent(const HtmlEventListIterator& event,
+ HtmlNode* new_node);
+ void InsertElementAfterEvent(const HtmlEventListIterator& event,
+ HtmlNode* new_node);
+ void SanityCheck();
+ void CheckEventParent(HtmlEvent* event, HtmlElement* expect,
+ HtmlElement* actual);
+ void CheckParentFromAddEvent(HtmlEvent* event);
+ void FixParents(const HtmlEventListIterator& begin,
+ const HtmlEventListIterator& end_inclusive,
+ HtmlElement* new_parent);
+ void CoalesceAdjacentCharactersNodes();
+ void ShowProgress(const char* message);
+
+ // Visible for testing only, via HtmlTestingPeer
+ friend class HtmlTestingPeer;
+ void AddEvent(HtmlEvent* event);
+ void SetCurrent(HtmlNode* node);
+ void set_coalesce_characters(bool x) { coalesce_characters_ = x; }
+
+ SymbolTableInsensitive string_table_;
+ std::vector<HtmlFilter*> filters_;
+ HtmlLexer* lexer_;
+ int sequence_;
+ std::set<HtmlNode*> nodes_;
+ HtmlEventList queue_;
+ HtmlEventListIterator current_;
+ // Have we deleted current? Then we shouldn't do certain manipulations to it.
+ bool deleted_current_;
+ MessageHandler* message_handler_;
+ std::string url_;
+ GURL gurl_;
+ std::string id_; // Per-request identifier string used in error messages.
+ int line_number_;
+ bool need_sanity_check_;
+ bool coalesce_characters_;
+ bool need_coalesce_characters_;
+ int64 parse_start_time_us_;
+ Timer* timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(HtmlParse);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_HTMLPARSE_PUBLIC_HTML_PARSE_H_
diff --git a/trunk/src/net/instaweb/htmlparse/public/html_parse_test_base.h b/trunk/src/net/instaweb/htmlparse/public/html_parse_test_base.h
new file mode 100644
index 0000000..a949307
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/public/html_parse_test_base.h
@@ -0,0 +1,174 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+// Infrastructure for testing html parsing and rewriting.
+
+#ifndef NET_INSTAWEB_HTMLPARSE_PUBLIC_HTML_PARSE_TEST_BASE_H_
+#define NET_INSTAWEB_HTMLPARSE_PUBLIC_HTML_PARSE_TEST_BASE_H_
+
+#include <string>
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/htmlparse/public/html_writer_filter.h"
+#include "net/instaweb/util/public/gtest.h"
+#include "net/instaweb/util/public/mock_message_handler.h"
+#include "net/instaweb/util/public/string_writer.h"
+#include "net/instaweb/util/public/writer.h"
+
+namespace net_instaweb {
+
+class HtmlParseTestBaseNoAlloc : public testing::Test {
+ protected:
+ HtmlParseTestBaseNoAlloc()
+ : write_to_string_(&output_buffer_),
+ added_filter_(false) {
+ }
+
+ virtual void TearDown() {
+ output_buffer_.clear();
+ doctype_string_.clear();
+ }
+
+ // To make the tests more concise, we generally omit the <html>...</html>
+ // tags bracketing the input. The libxml parser will add those in
+ // if we don't have them. To avoid having that make the test data more
+ // verbose, we automatically add them in the test infrastructure, both
+ // for stimulus and expected response.
+ //
+ // This flag controls whether we also add <body>...</body> tags. In
+ // the case html_parse_test, we go ahead and add them in. In the
+ // case of the rewriter tests, we want to explicitly control/observe
+ // the head and the body so we don't add the body tags in
+ // automatically. So classes that derive from HtmlParseTestBase must
+ // override this variable to indicate which they prefer.
+ virtual bool AddBody() const = 0;
+
+ // Set a doctype string (e.g. "<!doctype html>") to be inserted before the
+ // rest of the document (for the current test only). If none is set, it
+ // defaults to the empty string.
+ void SetDoctype(const StringPiece& directive) {
+ directive.CopyToString(&doctype_string_);
+ }
+
+ std::string AddHtmlBody(const std::string& html) {
+ std::string ret = AddBody() ? "<html><body>\n" : "<html>\n";
+ ret += html + (AddBody() ? "\n</body></html>\n" : "\n</html>");
+ return ret;
+ }
+
+ // Check that the output HTML is serialized to string-compare
+ // precisely with the input.
+ void ValidateNoChanges(const StringPiece& case_id,
+ const std::string& html_input) {
+ ValidateExpected(case_id, html_input, html_input);
+ }
+
+ // Fail to ValidateNoChanges.
+ void ValidateNoChangesFail(const StringPiece& case_id,
+ const std::string& html_input) {
+ ValidateExpectedFail(case_id, html_input, html_input);
+ }
+
+ void SetupWriter() {
+ output_buffer_.clear();
+ if (html_writer_filter_.get() == NULL) {
+ html_writer_filter_.reset(new HtmlWriterFilter(html_parse()));
+ html_writer_filter_->set_writer(&write_to_string_);
+ html_parse()->AddFilter(html_writer_filter_.get());
+ }
+ }
+
+ // Parse html_input, the result is stored in output_buffer_.
+ void Parse(const StringPiece& case_id, const std::string& html_input) {
+ // HtmlParser needs a valid HTTP URL to evaluate relative paths,
+ // so we create a dummy URL.
+ std::string dummy_url = StrCat("http://test.com/", case_id, ".html");
+ ParseUrl(dummy_url, html_input);
+ }
+
+ // Parse given an explicit URL rather than an id to build URL around.
+ void ParseUrl(const StringPiece& url, const std::string& html_input) {
+ // We don't add the filter in the constructor because it needs to be the
+ // last filter added.
+ SetupWriter();
+ html_parse()->StartParse(url);
+ html_parse()->ParseText(doctype_string_ + AddHtmlBody(html_input));
+ html_parse()->FinishParse();
+ }
+
+ // Validate that the output HTML serializes as specified in
+ // 'expected', which might not be identical to the input.
+ void ValidateExpected(const StringPiece& case_id,
+ const std::string& html_input,
+ const std::string& expected) {
+ Parse(case_id, html_input);
+ std::string xbody = doctype_string_ + AddHtmlBody(expected);
+ EXPECT_EQ(xbody, output_buffer_);
+ output_buffer_.clear();
+ }
+
+ // Same as ValidateExpected, but with an explicit URL rather than an id.
+ void ValidateExpectedUrl(const StringPiece& url,
+ const std::string& html_input,
+ const std::string& expected) {
+ ParseUrl(url, html_input);
+ std::string xbody = doctype_string_ + AddHtmlBody(expected);
+ EXPECT_EQ(xbody, output_buffer_);
+ output_buffer_.clear();
+ }
+
+ // Fail to ValidateExpected.
+ void ValidateExpectedFail(const StringPiece& case_id,
+ const std::string& html_input,
+ const std::string& expected) {
+ Parse(case_id, html_input);
+ std::string xbody = AddHtmlBody(expected);
+ EXPECT_NE(xbody, output_buffer_);
+ output_buffer_.clear();
+ }
+
+ virtual HtmlParse* html_parse() = 0;
+
+ MockMessageHandler message_handler_;
+ StringWriter write_to_string_;
+ std::string output_buffer_;
+ bool added_filter_;
+ scoped_ptr<HtmlWriterFilter> html_writer_filter_;
+ std::string doctype_string_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HtmlParseTestBaseNoAlloc);
+};
+
+class HtmlParseTestBase : public HtmlParseTestBaseNoAlloc {
+ public:
+ HtmlParseTestBase() : html_parse_(&message_handler_) {
+ };
+ protected:
+ virtual HtmlParse* html_parse() { return &html_parse_; }
+
+ HtmlParse html_parse_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HtmlParseTestBase);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_HTMLPARSE_PUBLIC_HTML_PARSE_TEST_BASE_H_
diff --git a/trunk/src/net/instaweb/htmlparse/public/html_parser_types.h b/trunk/src/net/instaweb/htmlparse/public/html_parser_types.h
new file mode 100644
index 0000000..b444ba6
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/public/html_parser_types.h
@@ -0,0 +1,50 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_HTMLPARSE_PUBLIC_HTML_PARSER_TYPES_H_
+#define NET_INSTAWEB_HTMLPARSE_PUBLIC_HTML_PARSER_TYPES_H_
+
+#include <list>
+
+namespace net_instaweb {
+
+class FileSystem;
+class HtmlCdataNode;
+class HtmlCharactersNode;
+class HtmlCommentNode;
+class HtmlDirectiveNode;
+class HtmlElement;
+class HtmlEvent;
+class HtmlFilter;
+class HtmlIEDirectiveNode;
+class HtmlLeafNode;
+class HtmlLexer;
+class HtmlNode;
+class HtmlParse;
+class HtmlStartElementEvent;
+class HtmlWriterFilter;
+class LibxmlAdapter;
+class MessageHandler;
+class Writer;
+
+typedef std::list<HtmlEvent*> HtmlEventList;
+typedef HtmlEventList::iterator HtmlEventListIterator;
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_HTMLPARSE_PUBLIC_HTML_PARSER_TYPES_H_
diff --git a/trunk/src/net/instaweb/htmlparse/public/html_writer_filter.h b/trunk/src/net/instaweb/htmlparse/public/html_writer_filter.h
new file mode 100644
index 0000000..69d4361
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/public/html_writer_filter.h
@@ -0,0 +1,88 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_HTMLPARSE_PUBLIC_HTML_WRITER_FILTER_H_
+#define NET_INSTAWEB_HTMLPARSE_PUBLIC_HTML_WRITER_FILTER_H_
+
+#include <string.h>
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/htmlparse/public/html_filter.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+class HtmlWriterFilter : public HtmlFilter {
+ public:
+ explicit HtmlWriterFilter(HtmlParse* html_parse);
+
+ void set_writer(Writer* writer) { writer_ = writer; }
+ virtual ~HtmlWriterFilter();
+
+ virtual void StartDocument();
+ virtual void EndDocument();
+ virtual void StartElement(HtmlElement* element);
+ virtual void EndElement(HtmlElement* element);
+ virtual void Cdata(HtmlCdataNode* cdata);
+ virtual void Comment(HtmlCommentNode* comment);
+ virtual void IEDirective(HtmlIEDirectiveNode* directive);
+ virtual void Characters(HtmlCharactersNode* characters);
+ virtual void Directive(HtmlDirectiveNode* directive);
+ virtual void Flush();
+
+ void set_max_column(int max_column) { max_column_ = max_column; }
+
+ virtual const char* Name() const { return "HtmlWriter"; }
+
+ private:
+ void EmitBytes(const StringPiece& str);
+ HtmlElement::CloseStyle GetCloseStyle(HtmlElement* element);
+ void Clear();
+
+ // Escapes arbitrary text as HTML, e.g. turning & into &. If quoteChar
+ // is non-zero, e.g. '"', then it would escape " as well.
+ void EncodeBytes(const std::string& val, int quoteChar);
+
+ HtmlParse* html_parse_;
+ Writer* writer_;
+
+ // Helps writer exploit shortcuts like <img .../> rather than writing
+ // <img ...></img>. At the end of StartElement, we defer writing the ">"
+ // until we see what's coming next. If it's the matching end_tag, then
+ // we can emit />. If something else comes first, then we have to
+ // first emit the delayed ">" before continuing.
+ HtmlElement* lazy_close_element_;
+
+ Atom symbol_a_;
+ Atom symbol_link_;
+ Atom symbol_href_;
+ Atom symbol_img_;
+ Atom symbol_script_;
+ Atom symbol_src_;
+ Atom symbol_alt_;
+ int column_;
+ int max_column_;
+ int write_errors_;
+
+ DISALLOW_COPY_AND_ASSIGN(HtmlWriterFilter);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_HTMLPARSE_PUBLIC_HTML_WRITER_FILTER_H_
diff --git a/trunk/src/net/instaweb/htmlparse/public/logging_html_filter.h b/trunk/src/net/instaweb/htmlparse/public/logging_html_filter.h
new file mode 100644
index 0000000..6ff0cd6
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/public/logging_html_filter.h
@@ -0,0 +1,121 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+// html_filter that passes data through unmodified, but
+// logs statistics about the data as it goes by.
+// It should be possible to create many instances of this
+// class and insert them at different points in the rewriting flow
+// Goal is to log:
+// NUM_EXPLICIT_CLOSED - <tag> </tag> pairs
+// NUM_IMPLICIT_CLOSED - <tag> for implicitly-closed tag
+// NUM_BRIEF_CLOSED - </tag>
+// NUM_CLOSED - Sum of above three
+// NUM_UNCLOSED - <tag> without matching </tag>
+// NUM_SPURIOUS_CLOSED - </tag> without preceding <tag>; UNCOUNTED RIGHT NOW!
+// NUM_TAGS - Total number of opening tags
+// NUM_CDATA - cdata sections
+// NUM_COMMENTS - comments
+// NUM_DIRECTIVES - directives
+// NUM_DOCUMENTS - started documents
+// NUM_IE_DIRECTIVES - ie directives
+// Reporting:
+// We report this information via a StatisticsLog: filter.ToString(log)
+// Two sets of statistics (eg before and after processing) can be
+// compared using before.Equals(after),
+
+#ifndef NET_INSTAWEB_HTMLPARSE_PUBLIC_LOGGING_HTML_FILTER_H_
+#define NET_INSTAWEB_HTMLPARSE_PUBLIC_LOGGING_HTML_FILTER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/empty_html_filter.h"
+#include <string>
+
+namespace net_instaweb {
+
+class StatisticsLog;
+
+class LoggingFilter : public EmptyHtmlFilter {
+ public:
+ // internal names of statistics.
+ // NOTE: must match string names in kStatisticNames at top of
+ // logging_html_filter.c
+ enum Statistic {
+ MIN_STAT = 0,
+ NUM_EXPLICIT_CLOSED = 0,
+ NUM_IMPLICIT_CLOSED,
+ NUM_BRIEF_CLOSED,
+ NUM_CLOSED,
+ NUM_UNCLOSED,
+ NUM_SPURIOUS_CLOSED,
+ NUM_TAGS,
+ NUM_CDATA,
+ NUM_COMMENTS,
+ NUM_DIRECTIVES,
+ NUM_DOCUMENTS,
+ NUM_IE_DIRECTIVES,
+ MAX_STAT
+ };
+
+ LoggingFilter();
+
+ // HtmlFilter methods.
+ virtual void StartDocument();
+ virtual void StartElement(HtmlElement* element);
+ virtual void EndElement(HtmlElement* element);
+ virtual void Cdata(HtmlCdataNode* cdata);
+ virtual void Comment(HtmlCommentNode* comment);
+ virtual void IEDirective(HtmlIEDirectiveNode* directive);
+ virtual void Directive(HtmlDirectiveNode* directive);
+ virtual const char* Name() const { return "Logging"; }
+
+ // Getter for individual statistics; NO BOUNDS CHECKS.
+ inline int get(const Statistic statistic) const {
+ return stats_[statistic];
+ }
+
+ // Logging, diffing, and aggregation
+
+ // Report all statistics
+ void LogStatistics(StatisticsLog *statistics_log) const;
+
+ // Return true if all statistics in this are equal to those in that.
+ bool Equals(const LoggingFilter &that) const;
+
+ // Report all statistics in this and that which differ
+ void LogDifferences(
+ const LoggingFilter &that, StatisticsLog *statistics_log) const;
+
+ // Add all statistics in that into this.
+ void Aggregate(const LoggingFilter &that);
+
+ // Aggregate differences between two sets of statistics into this.
+ // this.get(stat) += first.get(stat) - second.get(stat)
+ void AggregateDifferences(const LoggingFilter &first,
+ const LoggingFilter &second);
+
+ void Reset();
+
+ private:
+ int stats_[MAX_STAT];
+
+ DISALLOW_COPY_AND_ASSIGN(LoggingFilter);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_HTMLPARSE_PUBLIC_LOGGING_HTML_FILTER_H_
diff --git a/trunk/src/net/instaweb/htmlparse/public/statistics_log.h b/trunk/src/net/instaweb/htmlparse/public/statistics_log.h
new file mode 100644
index 0000000..9edcb4a
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/public/statistics_log.h
@@ -0,0 +1,40 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#ifndef NET_INSTAWEB_HTMLPARSE_PUBLIC_STATISTICS_LOG_H_
+#define NET_INSTAWEB_HTMLPARSE_PUBLIC_STATISTICS_LOG_H_
+
+#include "base/basictypes.h"
+
+namespace net_instaweb {
+
+class StatisticsLog {
+ public:
+ StatisticsLog() { }
+ virtual ~StatisticsLog();
+ virtual void LogStat(const char *statName, int value) = 0;
+ virtual void LogDifference(const char *statName,
+ int value1, int value2) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StatisticsLog);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_HTMLPARSE_PUBLIC_STATISTICS_LOG_H_
diff --git a/trunk/src/net/instaweb/htmlparse/statistics_log.cc b/trunk/src/net/instaweb/htmlparse/statistics_log.cc
new file mode 100644
index 0000000..eddd1c6
--- /dev/null
+++ b/trunk/src/net/instaweb/htmlparse/statistics_log.cc
@@ -0,0 +1,26 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#include "net/instaweb/htmlparse/public/statistics_log.h"
+
+namespace net_instaweb {
+
+StatisticsLog::~StatisticsLog() {
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/instaweb.gyp b/trunk/src/net/instaweb/instaweb.gyp
new file mode 100644
index 0000000..cb243fb
--- /dev/null
+++ b/trunk/src/net/instaweb/instaweb.gyp
@@ -0,0 +1,292 @@
+# Copyright 2009 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.
+
+{
+ 'variables': {
+ 'instaweb_root': '../..',
+ 'chromium_code': 1,
+ },
+ 'targets': [
+ {
+ # TODO: break this up into sub-libs (mocks, real, etc)
+ 'target_name': 'instaweb_util',
+ 'type': '<(library)',
+ 'dependencies': [
+ 'instaweb_core.gyp:instaweb_util_core',
+ '<(instaweb_root)/third_party/base64/base64.gyp:base64',
+ '<(DEPTH)/third_party/zlib/zlib.gyp:zlib',
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/third_party/chromium/src/net/tools/dump_cache.gyp:url_to_filename_encoder',
+ '<(DEPTH)/third_party/libpagespeed/src/pagespeed/core/core.gyp:pagespeed_core',
+ '<(DEPTH)/third_party/google-sparsehash/google-sparsehash.gyp:include',
+ ],
+ 'sources': [
+ 'util/abstract_mutex.cc',
+ 'util/cache_interface.cc',
+ 'util/cache_url_async_fetcher.cc',
+ 'util/cache_url_fetcher.cc',
+ 'util/data_url.cc',
+ 'util/dummy_url_fetcher.cc',
+ 'util/fake_url_async_fetcher.cc',
+ 'util/file_cache.cc',
+ 'util/file_system.cc',
+ 'util/file_system_lock_manager.cc',
+ 'util/file_writer.cc',
+ 'util/filename_encoder.cc',
+ 'util/gzip_inflater.cc',
+ 'util/hasher.cc',
+ 'util/http_cache.cc',
+ 'util/http_response_parser.cc',
+ 'util/http_dump_url_async_writer.cc',
+ 'util/http_dump_url_fetcher.cc',
+ 'util/http_dump_url_writer.cc',
+ 'util/http_value.cc',
+ 'util/lru_cache.cc',
+ 'util/meta_data.cc',
+ 'util/md5_hasher.cc',
+ 'util/mock_hasher.cc',
+ 'util/mock_message_handler.cc',
+ 'util/mock_timer.cc',
+ 'util/named_lock_manager.cc',
+ 'util/null_message_handler.cc',
+ 'util/null_writer.cc',
+ 'util/pthread_mutex.cc',
+ 'util/ref_counted.cc',
+ 'util/rolling_hash.cc',
+ 'util/query_params.cc',
+ 'util/simple_meta_data.cc',
+ 'util/simple_stats.cc',
+ 'util/statistics.cc',
+ 'util/statistics_work_bound.cc',
+ 'util/stdio_file_system.cc',
+ 'util/threadsafe_cache.cc',
+ 'util/time_util.cc',
+ 'util/timer_based_abstract_lock.cc',
+ 'util/url_async_fetcher.cc',
+ 'util/url_escaper.cc',
+ 'util/url_fetcher.cc',
+ 'util/url_multipart_encoder.cc',
+ 'util/url_segment_encoder.cc',
+ 'util/user_agent.cc',
+ 'util/wait_url_async_fetcher.cc',
+ 'util/wget_url_fetcher.cc',
+ 'util/wildcard.cc',
+ 'util/wildcard_group.cc',
+ 'util/work_bound.cc',
+ 'util/write_through_cache.cc',
+ ],
+ 'include_dirs': [
+ '<(instaweb_root)',
+ '<(DEPTH)',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(instaweb_root)',
+ '<(DEPTH)',
+ ],
+ },
+ },
+ {
+ 'target_name': 'instaweb_htmlparse',
+ 'type': '<(library)',
+ 'dependencies': [
+ 'instaweb_core.gyp:instaweb_htmlparse_core',
+ 'instaweb_util',
+ '<(DEPTH)/base/base.gyp:base',
+ ],
+ 'sources': [
+ 'htmlparse/file_driver.cc',
+ 'htmlparse/file_statistics_log.cc',
+ 'htmlparse/logging_html_filter.cc',
+ 'htmlparse/null_filter.cc',
+ 'htmlparse/statistics_log.cc',
+ ],
+ 'include_dirs': [
+ '<(instaweb_root)',
+ '<(DEPTH)',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(instaweb_root)',
+ '<(DEPTH)',
+ ],
+ },
+ },
+ {
+ 'target_name': 'instaweb_rewriter_base',
+ 'type': '<(library)',
+ 'dependencies': [
+ 'instaweb_util',
+ '<(DEPTH)/base/base.gyp:base',
+ ],
+ 'sources': [
+ 'rewriter/domain_lawyer.cc',
+ 'rewriter/resource.cc',
+ 'rewriter/output_resource.cc',
+ 'rewriter/resource_namer.cc',
+ 'rewriter/resource_manager.cc',
+ ],
+ 'include_dirs': [
+ '<(instaweb_root)',
+ '<(DEPTH)',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(instaweb_root)',
+ '<(DEPTH)',
+ ],
+ },
+ },
+ {
+ 'variables': {
+ # OpenCV has compile warnings in gcc 4.1 in a header file so turn off
+ # strict checking.
+ #
+ # TODO(jmarantz): disable the specific warning rather than
+ # turning off all warnings, and also scope this down to a
+ # minimal wrapper around the offending header file.
+ #
+ # TODO(jmarantz): figure out how to test for this failure in
+ # checkin tests, as it passes in gcc 4.2 and fails in gcc 4.1.
+ 'chromium_code': 0,
+ },
+ 'target_name': 'instaweb_rewriter_image',
+ 'type': '<(library)',
+ 'dependencies': [
+ 'instaweb_rewriter_base',
+ 'instaweb_util',
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/third_party/libpagespeed/src/pagespeed/image_compression/image_compression.gyp:pagespeed_jpeg_optimizer',
+ '<(DEPTH)/third_party/libpagespeed/src/pagespeed/image_compression/image_compression.gyp:pagespeed_png_optimizer',
+ '<(DEPTH)/third_party/opencv/opencv.gyp:highgui',
+ ],
+ 'sources': [
+ 'rewriter/image.cc',
+ 'rewriter/image_dim.cc',
+ 'rewriter/img_rewrite_filter.cc',
+ 'rewriter/img_tag_scanner.cc',
+ ],
+ 'include_dirs': [
+ '<(instaweb_root)',
+ '<(DEPTH)',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(instaweb_root)',
+ '<(DEPTH)',
+ ],
+ },
+ },
+ {
+ 'target_name': 'instaweb_rewriter_javascript',
+ 'type': '<(library)',
+ 'dependencies': [
+ 'instaweb_rewriter_base',
+ 'instaweb_util',
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/third_party/libpagespeed/src/pagespeed/jsminify/js_minify.gyp:pagespeed_jsminify',
+ ],
+ 'sources': [
+ 'rewriter/javascript_code_block.cc',
+ 'rewriter/javascript_filter.cc',
+ 'rewriter/javascript_library_identification.cc',
+ ],
+ 'include_dirs': [
+ '<(instaweb_root)',
+ '<(DEPTH)',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(instaweb_root)',
+ '<(DEPTH)',
+ ],
+ },
+ },
+ {
+ 'target_name': 'instaweb_rewriter_css',
+ 'type': '<(library)',
+ 'dependencies': [
+ 'instaweb_rewriter_base',
+ 'instaweb_util',
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/third_party/css_parser/css_parser.gyp:css_parser',
+ ],
+ 'sources': [
+ 'rewriter/css_filter.cc',
+ 'rewriter/css_minify.cc',
+ ],
+ 'include_dirs': [
+ '<(instaweb_root)',
+ '<(DEPTH)',
+ '<(DEPTH)/third_party/css_parser/src',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(instaweb_root)',
+ '<(DEPTH)',
+ ],
+ },
+ },
+ {
+ 'target_name': 'instaweb_rewriter',
+ 'type': '<(library)',
+ 'dependencies': [
+ 'instaweb_rewriter_base',
+ 'instaweb_core.gyp:instaweb_rewriter_html',
+ 'instaweb_rewriter_css',
+ 'instaweb_rewriter_image',
+ 'instaweb_rewriter_javascript',
+ 'instaweb_util',
+ '<(DEPTH)/base/base.gyp:base',
+ ],
+ 'sources': [
+ 'rewriter/add_head_filter.cc',
+ 'rewriter/add_instrumentation_filter.cc',
+ 'rewriter/base_tag_filter.cc',
+ 'rewriter/cache_extender.cc',
+ 'rewriter/common_filter.cc',
+ 'rewriter/css_combine_filter.cc',
+ 'rewriter/css_inline_filter.cc',
+ 'rewriter/css_move_to_head_filter.cc',
+ 'rewriter/css_outline_filter.cc',
+ 'rewriter/css_tag_scanner.cc',
+ 'rewriter/data_url_input_resource.cc',
+ 'rewriter/file_input_resource.cc',
+ 'rewriter/js_inline_filter.cc',
+ 'rewriter/js_outline_filter.cc',
+ 'rewriter/resource_tag_scanner.cc',
+ 'rewriter/rewrite_driver.cc',
+ 'rewriter/rewrite_driver_factory.cc',
+ 'rewriter/rewrite_filter.cc',
+ 'rewriter/rewrite_single_resource_filter.cc',
+ 'rewriter/rewrite_options.cc',
+ 'rewriter/script_tag_scanner.cc',
+ 'rewriter/strip_scripts_filter.cc',
+ 'rewriter/url_input_resource.cc',
+ 'rewriter/url_left_trim_filter.cc',
+ 'rewriter/url_partnership.cc',
+ ],
+ 'include_dirs': [
+ '<(instaweb_root)',
+ '<(DEPTH)',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(instaweb_root)',
+ '<(DEPTH)',
+ ],
+ },
+ },
+ ],
+}
diff --git a/trunk/src/net/instaweb/instaweb_core.gyp b/trunk/src/net/instaweb/instaweb_core.gyp
new file mode 100644
index 0000000..4624bf8
--- /dev/null
+++ b/trunk/src/net/instaweb/instaweb_core.gyp
@@ -0,0 +1,125 @@
+# Copyright 2009 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.
+
+{
+ 'variables': {
+ 'instaweb_root': '../..',
+ 'chromium_code': 1,
+ },
+ 'targets': [
+ {
+ 'target_name': 'instaweb_util_core',
+ 'type': '<(library)',
+ 'dependencies': [
+ '<(DEPTH)/base/base.gyp:base',
+ ],
+ 'sources': [
+ 'util/content_type.cc',
+ 'util/file_message_handler.cc',
+ 'util/google_message_handler.cc',
+ 'util/google_url.cc',
+ 'util/message_handler.cc',
+ 'util/string_convert.cc',
+ 'util/string_util.cc',
+ 'util/string_writer.cc',
+ 'util/timer.cc',
+ 'util/writer.cc',
+ ],
+ 'include_dirs': [
+ '<(instaweb_root)',
+ '<(DEPTH)',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(instaweb_root)',
+ '<(DEPTH)',
+ ],
+ },
+ },
+ {
+ 'target_name': 'instaweb_htmlparse_core',
+ 'type': '<(library)',
+ 'dependencies': [
+ 'instaweb_util_core',
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/build/temp_gyp/googleurl.gyp:googleurl',
+ ],
+ 'sources': [
+ 'htmlparse/doctype.cc',
+ 'htmlparse/empty_html_filter.cc',
+ 'htmlparse/html_element.cc',
+ 'htmlparse/html_escape.cc',
+ 'htmlparse/html_event.cc',
+ 'htmlparse/html_filter.cc',
+ 'htmlparse/html_lexer.cc',
+ 'htmlparse/html_node.cc',
+ 'htmlparse/html_parse.cc',
+ 'htmlparse/html_writer_filter.cc',
+ ],
+ 'include_dirs': [
+ '<(instaweb_root)',
+ '<(DEPTH)',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(instaweb_root)',
+ '<(DEPTH)',
+ ],
+ },
+ 'export_dependent_settings': [
+ 'instaweb_util_core',
+ ],
+ },
+ {
+ 'target_name': 'instaweb_rewriter_html',
+ 'type': '<(library)',
+ 'dependencies': [
+ 'instaweb_util_core',
+ '<(DEPTH)/base/base.gyp:base',
+ ],
+ 'sources': [
+ 'rewriter/collapse_whitespace_filter.cc',
+ 'rewriter/elide_attributes_filter.cc',
+ 'rewriter/html_attribute_quote_removal.cc',
+ 'rewriter/remove_comments_filter.cc',
+ ],
+ 'include_dirs': [
+ '<(instaweb_root)',
+ '<(DEPTH)',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(instaweb_root)',
+ '<(DEPTH)',
+ ],
+ },
+ },
+ # We build this target to make sure that we don't accidentially
+ # introduce dependencies from the core libraries to non-core
+ # libraries.
+ {
+ 'target_name': 'html_minifier_main',
+ 'type': 'executable',
+ 'dependencies': [
+ '<(DEPTH)/base/base.gyp:base',
+ 'instaweb_util_core',
+ 'instaweb_htmlparse_core',
+ 'instaweb_rewriter_html',
+ ],
+ 'sources': [
+ 'rewriter/html_minifier_main.cc',
+ ],
+ },
+ ],
+}
diff --git a/trunk/src/net/instaweb/instaweb_html_rewriter.gyp b/trunk/src/net/instaweb/instaweb_html_rewriter.gyp
new file mode 100644
index 0000000..4078c6d
--- /dev/null
+++ b/trunk/src/net/instaweb/instaweb_html_rewriter.gyp
@@ -0,0 +1,60 @@
+# 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.
+
+{
+ 'variables': {
+ # chromium_code indicates that the code is not
+ # third-party code and should be subjected to strict compiler
+ # warnings/errors in order to catch programming mistakes.
+ 'chromium_code': 1,
+ 'instaweb_root': '<(DEPTH)/net/instaweb',
+ },
+
+ 'targets': [
+ {
+ 'target_name': 'html_rewriter',
+ 'type': '<(library)',
+ 'dependencies': [
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/build/build_util.gyp:mod_pagespeed_version_header',
+ '<(DEPTH)/third_party/apache/httpd/httpd.gyp:include',
+ '<(DEPTH)/third_party/serf/serf.gyp:serf',
+ '<(instaweb_root)/instaweb.gyp:instaweb_htmlparse',
+ '<(instaweb_root)/instaweb.gyp:instaweb_rewriter',
+ '<(instaweb_root)/instaweb.gyp:instaweb_util',
+ ],
+ 'include_dirs': [
+ '<(DEPTH)',
+ ],
+ 'sources': [
+ 'apache/apache_message_handler.cc',
+ 'apache/apache_rewrite_driver_factory.cc',
+ 'apache/apache_slurp.cc',
+ 'apache/apr_file_system.cc',
+ 'apache/apr_mutex.cc',
+ 'apache/apr_statistics.cc',
+ 'apache/apr_timer.cc',
+ 'apache/header_util.cc',
+ 'apache/instaweb_context.cc',
+ 'apache/log_message_handler.cc',
+ 'apache/serf_url_async_fetcher.cc',
+ 'apache/serf_url_fetcher.cc',
+ 'apache/serf_async_callback.cc',
+ ],
+ 'export_dependent_settings': [
+ '<(instaweb_root)/instaweb.gyp:instaweb_util',
+ ],
+ },
+ ],
+}
diff --git a/trunk/src/net/instaweb/instaweb_mod_instaweb.gyp b/trunk/src/net/instaweb/instaweb_mod_instaweb.gyp
new file mode 100644
index 0000000..02a850c
--- /dev/null
+++ b/trunk/src/net/instaweb/instaweb_mod_instaweb.gyp
@@ -0,0 +1,128 @@
+# 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.
+
+{
+ 'variables': {
+ # chromium_code indicates that the code is not
+ # third-party code and should be subjected to strict compiler
+ # warnings/errors in order to catch programming mistakes.
+ 'chromium_code': 1,
+ },
+
+ 'targets': [
+ {
+ 'target_name': 'mod_instaweb',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ 'instaweb_html_rewriter.gyp:html_rewriter',
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/third_party/apache/httpd/httpd.gyp:include',
+ ],
+ 'include_dirs': [
+ '<(DEPTH)',
+ ],
+ 'sources': [
+ 'apache/instaweb_handler.cc',
+ 'apache/log_message_handler.cc',
+ 'apache/mod_instaweb.cc',
+ ],
+ },
+ {
+ 'variables': {
+ # OpenCV has compile warnings in gcc 4.1 in a header file so turn off
+ # strict checking.
+ #
+ # TODO(jmarantz): disable the specific warning rather than
+ # turning off all warnings, and also scope this down to a
+ # minimal wrapper around the offending header file.
+ #
+ # TODO(jmarantz): figure out how to test for this failure in
+ # checkin tests, as it passes in gcc 4.2 and fails in gcc 4.1.
+ 'chromium_code': 0,
+ },
+ 'target_name': 'mod_instaweb_test',
+ 'type': 'executable',
+ 'dependencies': [
+ 'instaweb_html_rewriter.gyp:html_rewriter',
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/testing/gtest.gyp:gtest',
+ '<(DEPTH)/testing/gtest.gyp:gtestmain',
+ '<(DEPTH)/third_party/apache/apr/apr.gyp:apr',
+ '<(DEPTH)/third_party/apache/aprutil/aprutil.gyp:aprutil',
+ '<(DEPTH)/third_party/apache/httpd/httpd.gyp:include',
+ '<(DEPTH)/third_party/google-sparsehash/google-sparsehash.gyp:include',
+ ],
+ 'include_dirs': [
+ '<(DEPTH)',
+ ],
+ 'sources': [
+ 'apache/serf_url_async_fetcher_test.cc',
+ 'apache/apr_file_system_test.cc',
+ 'util/base64_test.cc',
+ 'util/cache_fetcher_test.cc',
+ 'util/cache_url_async_fetcher_test.cc',
+ 'util/cache_url_fetcher_test.cc',
+ 'util/data_url_test.cc',
+ 'util/fetcher_test.cc',
+ 'util/file_cache_test.cc',
+ 'util/file_system_test.cc',
+ 'util/filename_encoder_test.cc',
+ 'util/gtest.cc',
+ 'util/gzip_inflater_test.cc',
+ 'util/http_cache_test.cc',
+ 'util/http_dump_url_async_writer_test.cc',
+ 'util/http_dump_url_fetcher_test.cc',
+ 'util/http_dump_url_writer_test.cc',
+ 'util/http_value_test.cc',
+ 'util/lru_cache_test.cc',
+ 'util/mem_file_system.cc',
+ 'util/mem_file_system_test.cc',
+ 'util/message_handler_test.cc',
+ 'util/mock_url_fetcher_test.cc',
+# 'util/simple_meta_data_test.cc',
+# 'util/simple_stats_test.cc',
+# 'util/split_writer_test.cc',
+# 'util/stdio_file_system_test.cc',
+ 'util/string_buffer_test.cc',
+ 'util/string_util_test.cc',
+ 'util/symbol_table_test.cc',
+# 'util/threadsafe_cache_test.cc',
+ 'util/time_util_test.cc',
+ 'util/url_escaper_test.cc',
+ 'util/write_through_cache_test.cc',
+ 'rewriter/collapse_whitespace_filter_test.cc',
+# TODO(sligocki): Fix. Fails in chromium/src/base/at_exit.cc(40).
+# 'rewriter/css_filter_test.cc',
+ 'rewriter/css_tag_scanner_test.cc',
+ 'rewriter/elide_attributes_filter_test.cc',
+ 'rewriter/html_attribute_quote_removal_test.cc',
+ 'rewriter/image_endian_test.cc',
+# 'rewriter/image_test.cc',
+ 'rewriter/javascript_code_block_test.cc',
+ 'rewriter/remove_comments_filter_test.cc',
+ 'rewriter/resource_encoder_test.cc',
+ 'rewriter/resource_manager_test.cc',
+ 'rewriter/resource_tag_scanner_test.cc',
+ 'rewriter/rewrite_driver_test.cc',
+ 'rewriter/rewriter_test.cc',
+ 'rewriter/script_tag_scanner_test.cc',
+ 'rewriter/strip_scripts_filter_test.cc',
+ 'rewriter/url_left_trim_filter_test.cc',
+# 'htmlparse/html_escape_test.cc',
+ 'htmlparse/html_parse_test.cc',
+ ],
+ },
+
+ ],
+}
diff --git a/trunk/src/net/instaweb/instaweb_mod_slurp.gyp b/trunk/src/net/instaweb/instaweb_mod_slurp.gyp
new file mode 100644
index 0000000..875dfa4
--- /dev/null
+++ b/trunk/src/net/instaweb/instaweb_mod_slurp.gyp
@@ -0,0 +1,38 @@
+# 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.
+
+{
+ 'variables': {
+ # chromium_code indicates that the code is not
+ # third-party code and should be subjected to strict compiler
+ # warnings/errors in order to catch programming mistakes.
+ 'chromium_code': 1,
+ },
+
+ 'targets': [
+ {
+ 'target_name': 'mod_slurp',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ 'instaweb_html_rewriter.gyp:html_rewriter',
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/third_party/apache/httpd/httpd.gyp:include',
+ ],
+ 'sources': [
+ 'apache/mod_slurp.cc',
+ 'apache/log_message_handler.cc',
+ ],
+ },
+ ],
+}
diff --git a/trunk/src/net/instaweb/instaweb_protobuf_gzip.gyp b/trunk/src/net/instaweb/instaweb_protobuf_gzip.gyp
new file mode 100644
index 0000000..05b4966
--- /dev/null
+++ b/trunk/src/net/instaweb/instaweb_protobuf_gzip.gyp
@@ -0,0 +1,37 @@
+# Copyright 2009 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.
+
+{
+ 'targets': [
+ {
+ # Unfortunately, the inherited protobuf target in protobuf.gyp
+ # does not build gzip_stream.cc, which we require. Thus we're
+ # required to define our own target that includes protobuf as
+ # well as gzip_stream.cc.
+ 'target_name': 'instaweb_protobuf_gzip',
+ 'type': '<(library)',
+ 'dependencies': [
+ '<(DEPTH)/third_party/protobuf2/protobuf.gyp:protobuf',
+ '<(DEPTH)/third_party/zlib/zlib.gyp:zlib',
+ ],
+ 'sources': [
+ '<(DEPTH)/third_party/protobuf2/src/src/google/protobuf/io/gzip_stream.cc',
+ ],
+ 'export_dependent_settings': [
+ '<(DEPTH)/third_party/protobuf2/protobuf.gyp:protobuf',
+ '<(DEPTH)/third_party/zlib/zlib.gyp:zlib',
+ ],
+ },
+ ],
+}
diff --git a/trunk/src/net/instaweb/mod_pagespeed.gyp b/trunk/src/net/instaweb/mod_pagespeed.gyp
new file mode 100644
index 0000000..e0557ac
--- /dev/null
+++ b/trunk/src/net/instaweb/mod_pagespeed.gyp
@@ -0,0 +1,160 @@
+# 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.
+
+# TODO(sligocki): This is a confusing name for the gyp file where tests live.
+
+{
+ 'variables': {
+ # chromium_code indicates that the code is not
+ # third-party code and should be subjected to strict compiler
+ # warnings/errors in order to catch programming mistakes.
+ 'chromium_code': 1,
+ },
+
+ 'targets': [
+ {
+ 'target_name': 'mod_pagespeed',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ 'instaweb_html_rewriter.gyp:html_rewriter',
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/third_party/apache/httpd/httpd.gyp:include',
+ '<(DEPTH)/build/build_util.gyp:mod_pagespeed_version_header',
+ ],
+ 'include_dirs': [
+ '<(DEPTH)',
+ ],
+ 'sources': [
+ 'apache/apache_config.cc',
+ 'apache/instaweb_handler.cc',
+ 'apache/log_message_handler.cc',
+ "apache/mem_clean_up.cc",
+ 'apache/mem_debug.cc',
+ 'apache/mod_instaweb.cc',
+ ],
+ },
+ {
+ 'variables': {
+ # OpenCV has compile warnings in gcc 4.1 in a header file so turn off
+ # strict checking.
+ #
+ # TODO(jmarantz): disable the specific warning rather than
+ # turning off all warnings, and also scope this down to a
+ # minimal wrapper around the offending header file.
+ #
+ # TODO(jmarantz): figure out how to test for this failure in
+ # checkin tests, as it passes in gcc 4.2 and fails in gcc 4.1.
+ 'chromium_code': 0,
+ },
+ 'target_name': 'mod_pagespeed_test',
+ 'type': 'executable',
+ 'dependencies': [
+ 'instaweb_html_rewriter.gyp:html_rewriter',
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/build/build_util.gyp:mod_pagespeed_version_header',
+ '<(DEPTH)/testing/gtest.gyp:gtest',
+ '<(DEPTH)/testing/gtest.gyp:gtestmain',
+ '<(DEPTH)/third_party/apache/apr/apr.gyp:apr',
+ '<(DEPTH)/third_party/apache/aprutil/aprutil.gyp:aprutil',
+ '<(DEPTH)/third_party/apache/httpd/httpd.gyp:include',
+ '<(DEPTH)/third_party/google-sparsehash/google-sparsehash.gyp:include',
+ ],
+ 'include_dirs': [
+ '<(DEPTH)',
+ ],
+ 'sources': [
+ 'apache/serf_url_async_fetcher_test.cc',
+ 'apache/apr_file_system_test.cc',
+ "apache/mem_clean_up.cc",
+ 'apache/mem_debug.cc',
+ 'util/base64_test.cc',
+ 'util/cache_fetcher_test.cc',
+ 'util/cache_url_async_fetcher_test.cc',
+ 'util/cache_url_fetcher_test.cc',
+ 'util/content_type_test.cc',
+ 'util/data_url_test.cc',
+ 'util/fetcher_test.cc',
+ 'util/file_cache_test.cc',
+ 'util/file_system_test.cc',
+ 'util/filename_encoder_test.cc',
+ 'util/google_url_test.cc',
+ 'util/gtest.cc',
+ 'util/gzip_inflater_test.cc',
+ 'util/http_cache_test.cc',
+ 'util/http_dump_url_async_writer_test.cc',
+ 'util/http_dump_url_fetcher_test.cc',
+ 'util/http_dump_url_writer_test.cc',
+ 'util/http_value_test.cc',
+ 'util/lru_cache_test.cc',
+ 'util/mem_file_system.cc',
+ 'util/mem_file_system_test.cc',
+ 'util/message_handler_test.cc',
+ 'util/md5_hasher_test.cc',
+ 'util/mock_message_handler_test.cc',
+ 'util/mock_url_fetcher.cc',
+ 'util/mock_url_fetcher_test.cc',
+# 'util/simple_meta_data_test.cc',
+# 'util/simple_stats_test.cc',
+ 'util/statistics_work_bound_test.cc',
+# 'util/split_writer_test.cc',
+# 'util/stdio_file_system_test.cc',
+ 'util/string_util_test.cc',
+ 'util/symbol_table_test.cc',
+# 'util/threadsafe_cache_test.cc',
+ 'util/time_util_test.cc',
+ 'util/url_escaper_test.cc',
+ 'util/url_multipart_encoder_test.cc',
+ 'util/user_agent_test.cc',
+ 'util/wait_url_async_fetcher_test.cc',
+ 'util/wildcard_test.cc',
+ 'util/wildcard_group_test.cc',
+ 'util/write_through_cache_test.cc',
+ 'rewriter/cache_extender_test.cc',
+ 'rewriter/collapse_whitespace_filter_test.cc',
+ 'rewriter/common_filter_test.cc',
+ 'rewriter/css_combine_filter_test.cc',
+ 'rewriter/css_inline_filter_test.cc',
+ 'rewriter/css_outline_filter_test.cc',
+ 'rewriter/css_filter_test.cc',
+ 'rewriter/css_move_to_head_filter_test.cc',
+ 'rewriter/css_tag_scanner_test.cc',
+ 'rewriter/domain_lawyer_test.cc',
+ 'rewriter/elide_attributes_filter_test.cc',
+ 'rewriter/html_attribute_quote_removal_test.cc',
+ 'rewriter/image_endian_test.cc',
+ 'rewriter/image_rewrite_filter_test.cc',
+# 'rewriter/image_test.cc',
+ 'rewriter/javascript_code_block_test.cc',
+ 'rewriter/javascript_filter_test.cc',
+ 'rewriter/js_inline_filter_test.cc',
+ 'rewriter/js_outline_filter_test.cc',
+ 'rewriter/remove_comments_filter_test.cc',
+ 'rewriter/resource_namer_test.cc',
+ 'rewriter/resource_manager_test.cc',
+ 'rewriter/resource_manager_test_base.cc',
+ 'rewriter/resource_tag_scanner_test.cc',
+ 'rewriter/rewrite_driver_test.cc',
+ 'rewriter/rewrite_options_test.cc',
+ 'rewriter/rewriter_test.cc',
+ 'rewriter/script_tag_scanner_test.cc',
+ 'rewriter/strip_scripts_filter_test.cc',
+ 'rewriter/url_left_trim_filter_test.cc',
+ 'rewriter/url_partnership_test.cc',
+# 'htmlparse/html_escape_test.cc',
+ 'htmlparse/html_parse_test.cc',
+ ],
+ },
+
+ ],
+}
diff --git a/trunk/src/net/instaweb/public/VERSION b/trunk/src/net/instaweb/public/VERSION
new file mode 100644
index 0000000..a23377b
--- /dev/null
+++ b/trunk/src/net/instaweb/public/VERSION
@@ -0,0 +1,4 @@
+MAJOR=0
+MINOR=9
+BUILD=0
+PATCH=0
diff --git a/trunk/src/net/instaweb/public/global_constants.h b/trunk/src/net/instaweb/public/global_constants.h
new file mode 100644
index 0000000..fd98deb
--- /dev/null
+++ b/trunk/src/net/instaweb/public/global_constants.h
@@ -0,0 +1,34 @@
+/**
+ * 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: pradnya@google.com (Pradnya Karbhari)
+
+// Header file that includes global constants to be used in net/instaweb.
+
+#ifndef NET_INSTAWEB_PUBLIC_GLOBAL_CONSTANTS_H_
+#define NET_INSTAWEB_PUBLIC_GLOBAL_CONSTANTS_H_
+
+namespace {
+
+// Time of day used in Chromium when running javascript in deterministic mode
+// (with flag --no-js-randomness). We use the same time of day for slurping,
+// validation and measurement in order to maintain consistency.
+static const double kChromiumTimeOfDay = 1204251968254LL;
+const char kModPagespeedHeader[] = "X-Mod-Pagespeed";
+
+} // namespace
+
+#endif // NET_INSTAWEB_PUBLIC_GLOBAL_CONSTANTS_H_
diff --git a/trunk/src/net/instaweb/public/version.h.in b/trunk/src/net/instaweb/public/version.h.in
new file mode 100644
index 0000000..f9db8bb
--- /dev/null
+++ b/trunk/src/net/instaweb/public/version.h.in
@@ -0,0 +1,25 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// version.h is generated from version.h.in. Edit the source!
+
+#pragma once
+
+// Version Information
+
+#define MOD_PAGESPEED_VERSION @MAJOR@,@MINOR@,@BUILD@,@PATCH@
+#define MOD_PAGESPEED_VERSION_STRING "@MAJOR@.@MINOR@.@BUILD@.@PATCH@"
+
+// Branding Information
+
+#define COMPANY_FULLNAME_STRING "@COMPANY_FULLNAME@"
+#define COMPANY_SHORTNAME_STRING "@COMPANY_SHORTNAME@"
+#define PRODUCT_FULLNAME_STRING "@PRODUCT_FULLNAME@"
+#define PRODUCT_SHORTNAME_STRING "@PRODUCT_SHORTNAME@"
+#define COPYRIGHT_STRING "@COPYRIGHT@"
+#define OFFICIAL_BUILD_STRING "@OFFICIAL_BUILD@"
+
+// Changelist Information
+
+#define LASTCHANGE_STRING "@LASTCHANGE@"
diff --git a/trunk/src/net/instaweb/rewriter/add_head_filter.cc b/trunk/src/net/instaweb/rewriter/add_head_filter.cc
new file mode 100644
index 0000000..7bcafe0
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/add_head_filter.cc
@@ -0,0 +1,84 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "public/add_head_filter.h"
+
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+
+namespace net_instaweb {
+
+AddHeadFilter::AddHeadFilter(HtmlParse* html_parse, bool combine_multiple_heads)
+ : html_parse_(html_parse),
+ combine_multiple_heads_(combine_multiple_heads),
+ found_head_(false),
+ s_head_(html_parse->Intern("head")),
+ s_body_(html_parse->Intern("body")),
+ head_element_(NULL) {
+}
+
+void AddHeadFilter::StartDocument() {
+ found_head_ = false;
+ head_element_ = NULL;
+}
+
+void AddHeadFilter::StartElement(HtmlElement* element) {
+ if (!found_head_) {
+ if (element->tag() == s_body_) {
+ head_element_ = html_parse_->NewElement(
+ element->parent(), s_head_);
+ html_parse_->InsertElementBeforeElement(element, head_element_);
+ found_head_ = true;
+ } else if (element->tag() == s_head_) {
+ found_head_ = true;
+ head_element_ = element;
+ }
+ }
+}
+
+void AddHeadFilter::EndElement(HtmlElement* element) {
+ // Detect elements that are children of a subsequent head. Move them
+ // into the first head if possible.
+ if (combine_multiple_heads_ && found_head_ && (head_element_ != NULL) &&
+ html_parse_->IsRewritable(head_element_)) {
+ if ((element->tag() == s_head_) && (element != head_element_)) {
+ // There should be no elements left in the subsequent head, so
+ // remove it.
+ bool moved = html_parse_->MoveCurrentInto(head_element_);
+ CHECK(moved) << "failed to move new head under old head";
+ bool deleted = html_parse_->DeleteSavingChildren(element);
+ CHECK(deleted) << "failed to delete extra head";
+ }
+ }
+}
+
+void AddHeadFilter::Flush() {
+ head_element_ = NULL;
+}
+
+void AddHeadFilter::EndDocument() {
+ if (!found_head_) {
+ // In order to insert a <head> in a docucument that lacks one, we
+ // must first find the body. If we get through the whole doc without
+ // finding a <head> or a <body> then this filter will have failed to
+ // add a head.
+ html_parse_->InfoHere("Reached end of document without finding <body>");
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/add_instrumentation_filter.cc b/trunk/src/net/instaweb/rewriter/add_instrumentation_filter.cc
new file mode 100644
index 0000000..021262a
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/add_instrumentation_filter.cc
@@ -0,0 +1,129 @@
+/**
+ * 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: abliss@google.com (Adam Bliss)
+
+#include "public/add_instrumentation_filter.h"
+
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/util/public/statistics.h"
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+namespace {
+
+// The javascript tag to insert in the top of the <head> element. We want this
+// as early as possible in the html. It must be short and fast.
+const char kHeadScript[] =
+ "<script type='text/javascript'>"
+ "window.mod_pagespeed_start = Number(new Date());"
+ "</script>";
+
+// The javascript tag to insert at the bottom of document. The %s will be
+// replaced with the custom beacon url, by default
+// "./mod_pagespeed_beacon?ets=". Then our timing info, e.g. "load:123", will
+// be appended.
+const char kTailScript[] =
+ "<script type='text/javascript'>"
+ "function g(){new Image().src='%sload:'+"
+ "(Number(new Date())-window.mod_pagespeed_start);};"
+ "var f=window.addEventListener;if(f){f('load',g,false);}else{"
+ "f=window.attachEvent;if(f){f('onload',g);}}"
+ "</script>";
+
+// Timing tag for total page load time. Also embedded in kTailScript above!
+const char kLoadTag[] = "load:";
+
+// Variables for the beacon to increment. These are currently handled in
+// mod_pagespeed_handler on apache. The average load time in milliseconds is
+// total_page_load_ms / page_load_count. Note that these are not updated
+// together atomically, so you might get a slightly bogus value.
+const char kTotalPageLoadMs[] = "total_page_load_ms";
+const char kPageLoadCount[] = "page_load_count";
+
+} // namespace
+
+AddInstrumentationFilter::AddInstrumentationFilter(
+ HtmlParse* html_parse, const StringPiece& beacon_url, Statistics* stats)
+ : html_parse_(html_parse),
+ found_head_(false),
+ s_head_(html_parse->Intern("head")),
+ s_body_(html_parse->Intern("body")),
+ total_page_load_ms_((stats == NULL) ? NULL :
+ stats->GetVariable(kTotalPageLoadMs)),
+ page_load_count_((stats == NULL) ? NULL :
+ stats->GetVariable(kPageLoadCount)) {
+ beacon_url.CopyToString(&beacon_url_);
+}
+
+void AddInstrumentationFilter::Initialize(Statistics* statistics) {
+ statistics->AddVariable(kTotalPageLoadMs);
+ statistics->AddVariable(kPageLoadCount);
+}
+
+void AddInstrumentationFilter::StartDocument() {
+ found_head_ = false;
+}
+
+void AddInstrumentationFilter::StartElement(HtmlElement* element) {
+ if (!found_head_) {
+ if (element->tag() == s_head_) {
+ found_head_ = true;
+ // TODO(abliss): add an actual element instead, so other filters can
+ // rewrite this JS
+ HtmlCharactersNode* script =
+ html_parse_->NewCharactersNode(element, kHeadScript);
+ html_parse_->InsertElementAfterCurrent(script);
+ }
+ }
+}
+
+void AddInstrumentationFilter::EndElement(HtmlElement* element) {
+ if (element->tag() == s_body_) {
+ // We relied on the existence of a <head> element. This should have been
+ // assured by add_head_filter.
+ CHECK(found_head_) << "Reached end of document without finding <head>."
+ " Please turn on the add_head filter.";
+ std::string tailScript = StringPrintf(kTailScript, beacon_url_.c_str());
+ HtmlCharactersNode* script =
+ html_parse_->NewCharactersNode(element, tailScript);
+ html_parse_->InsertElementBeforeCurrent(script);
+ }
+}
+
+bool AddInstrumentationFilter::HandleBeacon(const StringPiece& unparsed_url) {
+ if ((total_page_load_ms_ == NULL) || (page_load_count_ == NULL)) {
+ return false;
+ }
+ std::string url = unparsed_url.as_string();
+ // TODO(abliss): proper query parsing
+ size_t index = url.find(kLoadTag);
+ if (index == std::string::npos) {
+ return false;
+ }
+ url = url.substr(index + strlen(kLoadTag));
+ int value = 0;
+ if (!StringToInt(url, &value)) {
+ return false;
+ }
+ total_page_load_ms_->Add(value);
+ page_load_count_->Add(1);
+ return true;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/base_tag_filter.cc b/trunk/src/net/instaweb/rewriter/base_tag_filter.cc
new file mode 100644
index 0000000..6fb1cdb
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/base_tag_filter.cc
@@ -0,0 +1,65 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "public/base_tag_filter.h"
+
+#include "public/add_head_filter.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include <string>
+
+namespace net_instaweb {
+BaseTagFilter::BaseTagFilter(HtmlParse* html_parse) {
+ s_head_ = html_parse->Intern("head");
+ s_base_ = html_parse->Intern("base");
+ s_href_ = html_parse->Intern("href");
+ found_head_ = false;
+ html_parse_ = html_parse;
+}
+
+void BaseTagFilter::StartDocument() {
+ found_head_ = false;
+}
+
+// In a proxy server, we will want to set a base tag according to the current
+// URL being processed. But we need to add the BaseTagFilter upstream of
+// the HtmlWriterFilter, so we'll need to establish it at init time before
+// we know a URL. So in that mode, where we've installed the filter but
+// have no specific URL to set the base tag to, then we should avoid
+// adding an empty base tag.
+void BaseTagFilter::StartElement(HtmlElement* element) {
+ if ((element->tag() == s_head_) && !found_head_) {
+ found_head_ = true;
+ HtmlElement* new_element = html_parse_->NewElement(element, s_base_);
+ new_element->set_close_style(HtmlElement::IMPLICIT_CLOSE);
+ new_element->AddAttribute(s_href_, base_url_.c_str(), "\"");
+ html_parse_->InsertElementAfterCurrent(new_element);
+ } else if (element->tag() == s_base_) {
+ // There is was a pre-existing base tag. See if it specifies an href.
+ // If so, delete it, as it's now superseded by the one we added above.
+ for (int i = 0; i < element->attribute_size(); ++i) {
+ HtmlElement::Attribute& attribute = element->attribute(i);
+ if (attribute.name() == s_href_) {
+ html_parse_->DeleteElement(element);
+ break;
+ }
+ }
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/cache_extender.cc b/trunk/src/net/instaweb/rewriter/cache_extender.cc
new file mode 100644
index 0000000..9b90769
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/cache_extender.cc
@@ -0,0 +1,164 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/rewriter/public/cache_extender.h"
+
+#include "base/scoped_ptr.h"
+#include "net/instaweb/rewriter/public/css_tag_scanner.h"
+#include "net/instaweb/rewriter/public/output_resource.h"
+#include "net/instaweb/rewriter/public/resource_manager.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/util/public/hasher.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/meta_data.h"
+#include "net/instaweb/util/public/statistics.h"
+#include <string>
+#include "net/instaweb/util/public/string_writer.h"
+#include "net/instaweb/util/public/timer.h"
+#include "net/instaweb/util/public/url_escaper.h"
+
+namespace {
+
+// names for Statistics variables.
+const char kCacheExtensions[] = "cache_extensions";
+const char kNotCacheable[] = "not_cacheable";
+
+} // namespace
+
+namespace net_instaweb {
+
+// We do not want to bother to extend the cache lifetime for any resource
+// that is already cached for a month.
+const int64 kMinThresholdMs = Timer::kMonthMs;
+
+// TODO(jmarantz): consider factoring out the code that finds external resources
+
+CacheExtender::CacheExtender(RewriteDriver* driver, const char* filter_prefix)
+ : RewriteSingleResourceFilter(driver, filter_prefix),
+ html_parse_(driver->html_parse()),
+ resource_manager_(driver->resource_manager()),
+ tag_scanner_(html_parse_),
+ extension_count_(NULL),
+ not_cacheable_count_(NULL) {
+ Statistics* stats = resource_manager_->statistics();
+ if (stats != NULL) {
+ extension_count_ = stats->GetVariable(kCacheExtensions);
+ not_cacheable_count_ = stats->GetVariable(kNotCacheable);
+ }
+}
+
+void CacheExtender::Initialize(Statistics* statistics) {
+ statistics->AddVariable(kCacheExtensions);
+ statistics->AddVariable(kNotCacheable);
+}
+
+void CacheExtender::StartElementImpl(HtmlElement* element) {
+ MessageHandler* message_handler = html_parse_->message_handler();
+ HtmlElement::Attribute* href = tag_scanner_.ScanElement(element);
+ if ((href != NULL) && html_parse_->IsRewritable(element)) {
+ scoped_ptr<Resource> input_resource(CreateInputResource(href->value()));
+ if ((input_resource.get() != NULL) &&
+ !IsRewrittenResource(input_resource->url()) &&
+ resource_manager_->ReadIfCached(input_resource.get(),
+ message_handler)) {
+ const MetaData* headers = input_resource->metadata();
+ int64 now_ms = resource_manager_->timer()->NowMs();
+
+ // We cannot cache-extend a resource that's completely uncacheable,
+ // as our serving-side image would b
+ if (!resource_manager_->http_cache()->force_caching() &&
+ !headers->IsCacheable()) {
+ if (not_cacheable_count_ != NULL) {
+ not_cacheable_count_->Add(1);
+ }
+ } else if (((headers->CacheExpirationTimeMs() - now_ms) <
+ kMinThresholdMs) &&
+ (input_resource->type() != NULL)) {
+ scoped_ptr<OutputResource> output(
+ resource_manager_->CreateOutputResourceFromResource(
+ filter_prefix_, input_resource->type(),
+ resource_manager_->url_escaper(), input_resource.get(),
+ message_handler));
+ if (output.get() != NULL) {
+ CHECK(!output->IsWritten());
+
+ // TODO(sligocki): Shouldn't we be rewriting if !IsWritten?
+ if (!output->HasValidUrl()) {
+ // Transfer the input resource to output resource.
+ RewriteLoadedResource(input_resource.get(), output.get());
+ }
+
+ if (output->HasValidUrl()) {
+ href->SetValue(output->url());
+ if (extension_count_ != NULL) {
+ extension_count_->Add(1);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+// Just based on the pattern of the URL, see if we think this was
+// already the result of a rewrite. It should, in general, be
+// functionally correct to apply a new filter to an
+// already-rewritten resoure. However, in the case of cache
+// extension, there is no benefit because every rewriter generates
+// URLs that are served with long cache lifetimes. This filter
+// just wants to pick up the scraps. Note that we would discover
+// this anywahy in the cache expiration time below, but it's worth
+// going to the extra trouble to reduce the cache lookups since this
+// happens for basically every resource.
+bool CacheExtender::IsRewrittenResource(const StringPiece& url) const {
+ RewriteFilter* filter;
+ scoped_ptr<OutputResource> output_resource(driver_->DecodeOutputResource(
+ url, &filter));
+ return (output_resource.get() != NULL);
+}
+
+bool CacheExtender::RewriteLoadedResource(const Resource* input_resource,
+ OutputResource* output_resource) {
+ CHECK(input_resource->loaded());
+
+ MessageHandler* message_handler = html_parse_->message_handler();
+ StringPiece contents(input_resource->contents());
+ std::string absolutified;
+ std::string input_dir =
+ GoogleUrl::AllExceptLeaf(GoogleUrl::Create(input_resource->url()));
+ if ((input_resource->type() == &kContentTypeCss) &&
+ (input_dir != output_resource->resolved_base())) {
+ // TODO(jmarantz): find a mechanism to write this directly into
+ // the HTTPValue so we can reduce the number of times that we
+ // copy entire resources.
+ StringWriter writer(&absolutified);
+ CssTagScanner::AbsolutifyUrls(contents, input_resource->url(),
+ &writer, message_handler);
+ contents = absolutified;
+ }
+ // TODO(sligocki): Should we preserve the response headers from the
+ // original resource?
+ // TODO(sligocki): Maybe we shouldn't cache the rewritten resource,
+ // just the input_resource.
+ return resource_manager_->Write(
+ HttpStatus::kOK, contents, output_resource,
+ input_resource->metadata()->CacheExpirationTimeMs(), message_handler);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/cache_extender_test.cc b/trunk/src/net/instaweb/rewriter/cache_extender_test.cc
new file mode 100644
index 0000000..1589bd8
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/cache_extender_test.cc
@@ -0,0 +1,148 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+// Unit-test the cache extender.
+
+
+#include "base/scoped_ptr.h"
+#include "net/instaweb/rewriter/public/cache_extender.h"
+#include "net/instaweb/rewriter/public/resource_manager.h"
+#include "net/instaweb/rewriter/public/resource_manager_test_base.h"
+#include "net/instaweb/rewriter/public/resource_namer.h"
+#include "net/instaweb/util/public/google_message_handler.h"
+#include "net/instaweb/util/public/string_writer.h"
+
+namespace net_instaweb {
+
+namespace {
+
+static const char kDomain[] = "http://test.com/";
+
+const char kHtmlFormat[] =
+ "<link rel='stylesheet' href='%s' type='text/css'>\n"
+ "<img src='%s'/>\n"
+ "<script type='text/javascript' src='%s'></script>\n";
+
+const char kCssData[] = ".blue {color: blue;}";
+const char kImageData[] = "Invalid JPEG but it does not matter for this test";
+const char kJsData[] = "alert('hello, world!')";
+const char kFilterId[] = "ce";
+
+class CacheExtenderTest : public ResourceManagerTestBase {
+ protected:
+
+ virtual void SetUp() {
+ ResourceManagerTestBase::SetUp();
+ }
+
+ void InitTest(int64 ttl) {
+ AddFilter(RewriteOptions::kExtendCache);
+ InitMetaData("a.css", kContentTypeCss, kCssData, ttl);
+ InitMetaData("b.jpg", kContentTypeJpeg, kImageData, ttl);
+ InitMetaData("c.js", kContentTypeJavascript, kJsData, ttl);
+ }
+
+ // Generate HTML loading 3 resources with the specified URLs
+ std::string GenerateHtml(const std::string& a,
+ const std::string& b,
+ const std::string& c) {
+ return StringPrintf(kHtmlFormat, a.c_str(), b.c_str(), c.c_str());
+ }
+};
+
+TEST_F(CacheExtenderTest, DoExtend) {
+ InitTest(100);
+ for (int i = 0; i < 3; i++) {
+ ValidateExpected(
+ "do_extend",
+ GenerateHtml("a.css", "b.jpg", "c.js"),
+ GenerateHtml(
+ Encode(kDomain, "ce", "0", "a.css", "css"),
+ Encode(kDomain, "ce", "0", "b.jpg", "jpg"),
+ Encode(kDomain, "ce", "0", "c.js", "js")));
+ }
+}
+
+TEST_F(CacheExtenderTest, NoExtendAlreadyCachedProperly) {
+ InitTest(100000000); // cached for a long time to begin with
+ ValidateExpected("no_extend_cached_properly",
+ GenerateHtml("a.css", "b.jpg", "c.js"),
+ GenerateHtml("a.css", "b.jpg", "c.js"));
+}
+
+TEST_F(CacheExtenderTest, NoExtendOriginUncacheable) {
+ InitTest(0); // origin not cacheable
+ ValidateExpected("no_extend_origin_not_cacheable",
+ GenerateHtml("a.css", "b.jpg", "c.js"),
+ GenerateHtml("a.css", "b.jpg", "c.js"));
+}
+
+TEST_F(CacheExtenderTest, ServeFiles) {
+ std::string content;
+
+ InitTest(100);
+ ASSERT_TRUE(ServeResource(kDomain, kFilterId, "a.css", "css", &content));
+ EXPECT_EQ(std::string(kCssData), content);
+ ASSERT_TRUE(ServeResource(kDomain, kFilterId, "b.jpg", "jpg", &content));
+ EXPECT_EQ(std::string(kImageData), content);
+ ASSERT_TRUE(ServeResource(kDomain, kFilterId, "c.js", "js", &content));
+ EXPECT_EQ(std::string(kJsData), content);
+}
+
+TEST_F(CacheExtenderTest, ServeFilesFromDelayedFetch) {
+ InitTest(100);
+ ServeResourceFromManyContexts(Encode(kDomain, "ce", "0", "a.css", "css"),
+ RewriteOptions::kExtendCache,
+ &mock_hasher_, kCssData);
+ ServeResourceFromManyContexts(Encode(kDomain, "ce", "0", "b.jpg", "jpg"),
+ RewriteOptions::kExtendCache,
+ &mock_hasher_, kImageData);
+ ServeResourceFromManyContexts(Encode(kDomain, "ce", "0", "c.js", "js"),
+ RewriteOptions::kExtendCache,
+ &mock_hasher_, kJsData);
+
+ // TODO(jmarantz): make ServeResourceFromManyContexts check:
+ // 1. Gets the data from the cache, with no mock fetchers, null file system
+ // 2. Gets the data from the file system, with no cache, no mock fetchers.
+ // 3. Gets the data from the mock fetchers: no cache, no file system.
+}
+
+TEST_F(CacheExtenderTest, MinimizeCacheHits) {
+ options_.EnableFilter(RewriteOptions::kOutlineCss);
+ options_.EnableFilter(RewriteOptions::kExtendCache);
+ options_.set_css_outline_min_bytes(1);
+ rewrite_driver_.AddFilters();
+ std::string html_input = StrCat("<style>", kCssData, "</style>");
+ std::string html_output = StringPrintf(
+ "<link rel='stylesheet' href='%s'>",
+ Encode(kDomain, "co", "0", "_", "css").c_str());
+ ValidateExpected("no_extend_origin_not_cacheable", html_input, html_output);
+
+ // The key thing about this test is that the CacheExtendFilter should
+ // not pound the cache looking to see if it's already rewritten this
+ // resource. If we try, in the cache extend filter, to this already-optimized
+ // resource from the cache, then we'll get a cache-hit and decide that
+ // it's already got a long cache lifetime. But we should know, just from
+ // the name of the resource, that it should not be cache extended.
+ EXPECT_EQ(0, lru_cache_->num_hits());
+ EXPECT_EQ(1, lru_cache_->num_misses());
+}
+
+} // namespace
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/collapse_whitespace_filter.cc b/trunk/src/net/instaweb/rewriter/collapse_whitespace_filter.cc
new file mode 100644
index 0000000..d12520a
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/collapse_whitespace_filter.cc
@@ -0,0 +1,112 @@
+/**
+ * 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: mdsteele@google.com (Matthew D. Steele)
+
+#include "net/instaweb/rewriter/public/collapse_whitespace_filter.h"
+
+#include "base/logging.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/htmlparse/public/html_node.h"
+#include <string>
+
+namespace {
+
+// Tags within which we should never try to collapse whitespace (note that this
+// is not _quite_ the same thing as kLiteralTags in html_lexer.cc):
+const char* const kSensitiveTags[] = {"pre", "script", "style", "textarea"};
+
+bool IsHtmlWhiteSpace(char ch) {
+ // See http://www.w3.org/TR/html401/struct/text.html#h-9.1
+ return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\f';
+}
+
+// Sentinel value for use in the CollapseWhitespace function:
+const char kNotInWhitespace = '\0';
+
+// Append the input to the output with whitespace collapsed. Specifically,
+// each contiguous sequence of whitespace is replaced with the first
+// (whitespace) character in the sequence, except that any sequence containing
+// a newline is collapsed to a newline.
+void CollapseWhitespace(const std::string& input, std::string* output) {
+ // This variable stores the first whitespace character in each whitespace
+ // sequence, or kNotInWhitespace.
+ char whitespace = kNotInWhitespace;
+ for (std::string::const_iterator iter = input.begin(), end = input.end();
+ iter != end; ++iter) {
+ const char ch = *iter;
+ if (IsHtmlWhiteSpace(ch)) {
+ // We let newlines take precedence over other kinds of whitespace, for
+ // aesthetic reasons.
+ if (whitespace == kNotInWhitespace || ch == '\n') {
+ whitespace = ch;
+ }
+ } else {
+ if (whitespace != kNotInWhitespace) {
+ *output += whitespace;
+ whitespace = kNotInWhitespace;
+ }
+ *output += ch;
+ }
+ }
+ if (whitespace != kNotInWhitespace) {
+ *output += whitespace;
+ }
+}
+
+} // namespace
+
+namespace net_instaweb {
+
+CollapseWhitespaceFilter::CollapseWhitespaceFilter(HtmlParse* html_parse)
+ : html_parse_(html_parse) {
+ for (size_t i = 0; i < arraysize(kSensitiveTags); ++i) {
+ sensitive_tags_.insert(html_parse->Intern(kSensitiveTags[i]));
+ }
+}
+
+void CollapseWhitespaceFilter::StartDocument() {
+ atom_stack_.clear();
+}
+
+void CollapseWhitespaceFilter::StartElement(HtmlElement* element) {
+ const Atom tag = element->tag();
+ if (sensitive_tags_.count(tag) > 0) {
+ atom_stack_.push_back(tag);
+ }
+}
+
+void CollapseWhitespaceFilter::EndElement(HtmlElement* element) {
+ const Atom tag = element->tag();
+ if (!atom_stack_.empty() && tag == atom_stack_.back()) {
+ atom_stack_.pop_back();
+ } else {
+ DCHECK(sensitive_tags_.count(tag) == 0);
+ }
+}
+
+void CollapseWhitespaceFilter::Characters(HtmlCharactersNode* characters) {
+ if (atom_stack_.empty()) {
+ std::string minified;
+ CollapseWhitespace(characters->contents(), &minified);
+ html_parse_->ReplaceNode(
+ characters,
+ html_parse_->NewCharactersNode(characters->parent(), minified));
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/collapse_whitespace_filter_test.cc b/trunk/src/net/instaweb/rewriter/collapse_whitespace_filter_test.cc
new file mode 100644
index 0000000..76a6c43
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/collapse_whitespace_filter_test.cc
@@ -0,0 +1,96 @@
+/**
+ * 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: mdsteele@google.com (Matthew D. Steele)
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/html_parse_test_base.h"
+#include "net/instaweb/rewriter/public/collapse_whitespace_filter.h"
+
+namespace net_instaweb {
+
+class CollapseWhitespaceFilterTest : public HtmlParseTestBase {
+ protected:
+ CollapseWhitespaceFilterTest()
+ : collapse_whitespace_filter_(&html_parse_) {
+ html_parse_.AddFilter(&collapse_whitespace_filter_);
+ }
+
+ virtual bool AddBody() const { return false; }
+
+ private:
+ CollapseWhitespaceFilter collapse_whitespace_filter_;
+
+ DISALLOW_COPY_AND_ASSIGN(CollapseWhitespaceFilterTest);
+};
+
+TEST_F(CollapseWhitespaceFilterTest, NoChange) {
+ ValidateNoChanges("no_change",
+ "<head><title>Hello</title></head>"
+ "<body>Why, hello there!</body>");
+}
+
+TEST_F(CollapseWhitespaceFilterTest, CollapseWhitespace) {
+ ValidateExpected("collapse_whitespace",
+ "<body>hello world, it\n"
+ " is good to see you </body>",
+ "<body>hello world, it\n"
+ "is good to see you </body>");
+}
+
+TEST_F(CollapseWhitespaceFilterTest, NewlineTakesPrecedence) {
+ ValidateExpected("newline_takes_precedence",
+ "<body>hello world, it \n"
+ " is good to see you</body>",
+ "<body>hello world, it\n"
+ "is good to see you</body>");
+}
+
+TEST_F(CollapseWhitespaceFilterTest, DoNotCollapseWithinPre) {
+ ValidateNoChanges("do_not_collapse_within_pre",
+ "<body><pre>hello world, it\n"
+ " is good to see you </pre></body>");
+}
+
+TEST_F(CollapseWhitespaceFilterTest, CollapseAfterNestedPre) {
+ ValidateExpected("collapse_after_nested_pre",
+ "<body><pre>hello <pre>world, it</pre>\n"
+ " is good</pre> to see you </body>",
+ "<body><pre>hello <pre>world, it</pre>\n"
+ " is good</pre> to see you </body>");
+}
+
+TEST_F(CollapseWhitespaceFilterTest, DoNotCollapseWithinScript) {
+ ValidateExpected("do_not_collapse_within_script",
+ "<head><script>x = \"don't collapse\"</script></head>"
+ "<body>do collapse</body>",
+ "<head><script>x = \"don't collapse\"</script></head>"
+ "<body>do collapse</body>");
+}
+
+TEST_F(CollapseWhitespaceFilterTest, DoNotCollapseWithinStyle) {
+ ValidateNoChanges("do_not_collapse_within_style",
+ "<head><style>P{font-family:\"don't collapse\";}</style>"
+ "</head><body></body>");
+}
+
+TEST_F(CollapseWhitespaceFilterTest, DoNotCollapseWithinTextarea) {
+ ValidateNoChanges("do_not_collapse_within_textarea",
+ "<body><textarea>hello world, it\n"
+ " is good to see you </textarea></body>");
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/common_filter.cc b/trunk/src/net/instaweb/rewriter/common_filter.cc
new file mode 100644
index 0000000..d4e4cb2
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/common_filter.cc
@@ -0,0 +1,112 @@
+/**
+ * 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/common_filter.h"
+
+#include "net/instaweb/rewriter/public/resource_manager.h"
+#include "net/instaweb/rewriter/public/rewrite_driver.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+
+namespace net_instaweb {
+
+CommonFilter::CommonFilter(RewriteDriver* driver)
+ : driver_(driver),
+ html_parse_(driver->html_parse()),
+ s_base_(html_parse_->Intern("base")),
+ s_href_(html_parse_->Intern("href")),
+ s_noscript_(html_parse_->Intern("noscript")) {}
+
+CommonFilter::~CommonFilter() {}
+
+void CommonFilter::StartDocument() {
+ // Base URL starts as document URL.
+ base_gurl_ = html_parse_->gurl();
+ noscript_element_ = NULL;
+ // Run the actual filter's StartDocumentImpl.
+ StartDocumentImpl();
+}
+
+void CommonFilter::StartElement(HtmlElement* element) {
+ // <base>
+ if (element->tag() == s_base_) {
+ HtmlElement::Attribute* href = element->FindAttribute(s_href_);
+ if (href != NULL) {
+ GURL temp_url(href->value());
+ if (temp_url.is_valid()) {
+ base_gurl_.Swap(&temp_url);
+ }
+ }
+
+ // <noscript>
+ } else if (element->tag() == s_noscript_) {
+ if (noscript_element_ == NULL) {
+ noscript_element_ = element; // Record top-level <noscript>
+ }
+ }
+
+ // Run actual filter's StartElementImpl
+ StartElementImpl(element);
+}
+
+void CommonFilter::EndElement(HtmlElement* element) {
+ if (element == noscript_element_) {
+ noscript_element_ = NULL; // We are exitting the top-level <noscript>
+ }
+
+ // Run actual filter's EndElementImpl
+ EndElementImpl(element);
+}
+
+// TODO(jmarantz): While it is expedient to route the input-resource creation
+// through this base class, it's not clear this is the right place. I think
+// it would be easier to argue that it should be RewriteDriver. But this
+// fitler had access to the base_gurl which is very convenient, and was already
+// the base class of all filters that needed this. We should revisit and
+// decide where this should go.
+//
+// jmaessen also points out that we have not been symmetric: we have ignored
+// OutputResources here. In any case, we should strive for consistency.
+Resource* CommonFilter::CreateInputResource(const StringPiece& url) {
+ ResourceManager* resource_manager = driver_->resource_manager();
+ return resource_manager->CreateInputResource(
+ base_gurl(), url, driver_->options(), html_parse()->message_handler());
+}
+
+Resource* CommonFilter::CreateInputResourceAbsolute(const StringPiece& url) {
+ ResourceManager* resource_manager = driver_->resource_manager();
+ return resource_manager->CreateInputResourceAbsolute(
+ url, driver_->options(), html_parse()->message_handler());
+}
+
+Resource* CommonFilter::CreateInputResourceAndReadIfCached(
+ const StringPiece& url) {
+ ResourceManager* resource_manager = driver_->resource_manager();
+ return resource_manager->CreateInputResourceAndReadIfCached(
+ base_gurl(), url, driver_->options(), html_parse_->message_handler());
+}
+
+Resource* CommonFilter::CreateInputResourceFromOutputResource(
+ UrlSegmentEncoder* encoder, OutputResource* output_resource) {
+ ResourceManager* resource_manager = driver_->resource_manager();
+ return resource_manager->CreateInputResourceFromOutputResource(
+ encoder, output_resource, driver_->options(),
+ html_parse_->message_handler());
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/common_filter_test.cc b/trunk/src/net/instaweb/rewriter/common_filter_test.cc
new file mode 100644
index 0000000..39d4734
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/common_filter_test.cc
@@ -0,0 +1,198 @@
+/**
+ * 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/common_filter.h"
+
+#include "base/scoped_ptr.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/rewriter/public/resource_manager_test_base.h"
+#include "net/instaweb/util/public/google_message_handler.h"
+
+namespace net_instaweb {
+
+namespace {
+
+class CountingFilter : public CommonFilter {
+ public:
+ CountingFilter(RewriteDriver* driver) : CommonFilter(driver),
+ start_doc_calls_(0),
+ start_element_calls_(0),
+ end_element_calls_(0) {}
+
+ virtual void StartDocumentImpl() { ++start_doc_calls_; }
+ virtual void StartElementImpl(HtmlElement* element) {
+ ++start_element_calls_;
+ }
+ virtual void EndElementImpl(HtmlElement* element) { ++end_element_calls_; }
+
+ virtual const char* Name() const { return "CommonFilterTest.CountingFilter"; }
+
+ int start_doc_calls_;
+ int start_element_calls_;
+ int end_element_calls_;
+};
+
+class CommonFilterTest : public ResourceManagerTestBase {
+ protected:
+ CommonFilterTest() : html_parse_(rewrite_driver_.html_parse()),
+ filter_(&rewrite_driver_) {
+ html_parse_->AddFilter(&filter_);
+ }
+
+ void ExpectUrl(const std::string& expected_url, const GURL& actual_gurl) {
+ LOG(INFO) << actual_gurl;
+ EXPECT_EQ(expected_url, GoogleUrl::Spec(actual_gurl));
+ }
+
+ bool CanRewriteResource(CommonFilter* filter, const StringPiece& url) {
+ scoped_ptr<Resource> resource(filter->CreateInputResource(url));
+ return (resource.get() != NULL);
+ }
+
+ CommonFilter* MakeFilter(const StringPiece& base_url,
+ const StringPiece& domain,
+ RewriteOptions* options,
+ RewriteDriver* driver) {
+ options->domain_lawyer()->AddDomain(domain, &message_handler_);
+ CountingFilter* filter = new CountingFilter(driver);
+ driver->AddFilter(filter);
+ driver->html_parse()->StartParse(base_url);
+ driver->html_parse()->Flush();
+ return filter;
+ }
+
+ GoogleMessageHandler handler_;
+ HtmlParse* html_parse_;
+ CountingFilter filter_;
+};
+
+TEST_F(CommonFilterTest, DoesCallImpls) {
+ EXPECT_EQ(0, filter_.start_doc_calls_);
+ filter_.StartDocument();
+ EXPECT_EQ(1, filter_.start_doc_calls_);
+
+ HtmlElement* element =
+ html_parse_->NewElement(NULL, html_parse_->Intern("foo"));
+ EXPECT_EQ(0, filter_.start_element_calls_);
+ filter_.StartElement(element);
+ EXPECT_EQ(1, filter_.start_element_calls_);
+
+ EXPECT_EQ(0, filter_.end_element_calls_);
+ filter_.EndElement(element);
+ EXPECT_EQ(1, filter_.end_element_calls_);
+}
+
+TEST_F(CommonFilterTest, StoresCorrectBaseUrl) {
+ std::string doc_url = "http://www.example.com/";
+ html_parse_->StartParse(doc_url);
+ html_parse_->Flush();
+ // Base URL starts out as document URL.
+ ExpectUrl(doc_url, html_parse_->gurl());
+ ExpectUrl(doc_url, filter_.base_gurl());
+
+ html_parse_->ParseText("<html><head><link rel='stylesheet' href='foo.css'>");
+ html_parse_->Flush();
+ ExpectUrl(doc_url, filter_.base_gurl());
+
+ std::string base_url = "http://www.baseurl.com/foo/";
+ html_parse_->ParseText("<base href='");
+ html_parse_->ParseText(base_url);
+ html_parse_->ParseText("' />");
+ html_parse_->Flush();
+ // Update to base URL.
+ ExpectUrl(base_url, filter_.base_gurl());
+ // Make sure we didn't change the document URL.
+ ExpectUrl(doc_url, html_parse_->gurl());
+
+ html_parse_->ParseText("<link rel='stylesheet' href='foo.css'>");
+ html_parse_->Flush();
+ ExpectUrl(base_url, filter_.base_gurl());
+
+ std::string new_base_url = "http://www.somewhere-else.com/";
+ html_parse_->ParseText("<base href='");
+ html_parse_->ParseText(new_base_url);
+ html_parse_->ParseText("' />");
+ html_parse_->Flush();
+ // Uses new base URL.
+ ExpectUrl(new_base_url, filter_.base_gurl());
+
+ html_parse_->ParseText("</head></html>");
+ html_parse_->FinishParse();
+ ExpectUrl(new_base_url, filter_.base_gurl());
+ ExpectUrl(doc_url, html_parse_->gurl());
+}
+
+TEST_F(CommonFilterTest, DetectsNoScriptCorrectly) {
+ std::string doc_url = "http://www.example.com/";
+ html_parse_->StartParse(doc_url);
+ html_parse_->Flush();
+ EXPECT_TRUE(filter_.noscript_element() == NULL);
+
+ html_parse_->ParseText("<html><head><title>Example Site");
+ html_parse_->Flush();
+ EXPECT_TRUE(filter_.noscript_element() == NULL);
+
+ html_parse_->ParseText("</title><noscript>");
+ html_parse_->Flush();
+ EXPECT_TRUE(filter_.noscript_element() != NULL);
+
+ // Nested <noscript> elements
+ html_parse_->ParseText("Blah blah blah <noscript><noscript> do-de-do-do ");
+ html_parse_->Flush();
+ EXPECT_TRUE(filter_.noscript_element() != NULL);
+
+ html_parse_->ParseText("<link href='style.css'>");
+ html_parse_->Flush();
+ EXPECT_TRUE(filter_.noscript_element() != NULL);
+
+ // Close inner <noscript>s
+ html_parse_->ParseText("</noscript></noscript>");
+ html_parse_->Flush();
+ EXPECT_TRUE(filter_.noscript_element() != NULL);
+
+ // Close outter <noscript>
+ html_parse_->ParseText("</noscript>");
+ html_parse_->Flush();
+ EXPECT_TRUE(filter_.noscript_element() == NULL);
+
+ html_parse_->ParseText("</head></html>");
+ html_parse_->FinishParse();
+ EXPECT_TRUE(filter_.noscript_element() == NULL);
+
+}
+
+TEST_F(CommonFilterTest, TestTwoDomainLawyers) {
+ static const char kBaseUrl[] = "http://www.base.com/";
+ CommonFilter* a = MakeFilter(kBaseUrl, "a.com", &options_, &rewrite_driver_);
+ CommonFilter* b = MakeFilter(kBaseUrl, "b.com", &other_options_,
+ &other_rewrite_driver_);
+
+ // Either filter can rewrite resources from the base URL
+ EXPECT_TRUE(CanRewriteResource(a, StrCat(kBaseUrl, "base.css")));
+ EXPECT_TRUE(CanRewriteResource(b, StrCat(kBaseUrl, "base.css")));
+
+ // But the other domains are specific to the two different drivers/filters
+ EXPECT_TRUE(CanRewriteResource(a, "http://a.com/a.css"));
+ EXPECT_FALSE(CanRewriteResource(a, "http://b.com/b.css"));
+ EXPECT_FALSE(CanRewriteResource(b, "http://a.com/a.css"));
+ EXPECT_TRUE(CanRewriteResource(b, "http://b.com/b.css"));
+}
+
+} // namespace
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/css_combine_filter.cc b/trunk/src/net/instaweb/rewriter/css_combine_filter.cc
new file mode 100644
index 0000000..c40fa6b
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/css_combine_filter.cc
@@ -0,0 +1,560 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/rewriter/public/css_combine_filter.h"
+
+#include "base/scoped_ptr.h"
+#include "net/instaweb/rewriter/public/output_resource.h"
+#include "net/instaweb/rewriter/public/resource_manager.h"
+#include "net/instaweb/rewriter/public/resource_namer.h"
+#include "net/instaweb/rewriter/public/rewrite_options.h"
+#include "net/instaweb/rewriter/public/url_partnership.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/util/public/content_type.h"
+#include "net/instaweb/util/public/hasher.h"
+#include "net/instaweb/util/public/google_url.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include "net/instaweb/util/public/statistics.h"
+#include "net/instaweb/util/public/stl_util.h"
+#include <string>
+#include "net/instaweb/util/public/string_writer.h"
+#include "net/instaweb/util/public/url_escaper.h"
+#include "net/instaweb/util/public/url_multipart_encoder.h"
+
+namespace net_instaweb {
+
+namespace {
+
+// names for Statistics variables.
+const char kCssFileCountReduction[] = "css_file_count_reduction";
+
+// TODO(jmarantz): This is arguably fragile.
+//
+// Another approach is to put a CHECK that the final URL with the
+// resource naming does not exceed the limit.
+//
+// Another option too is to just instantiate a ResourceNamer and a
+// hasher put in the correct ID and EXT and leave the name blank and
+// take size of that.
+const int kIdOverhead = 2; // strlen("cc")
+const int kExtOverhead = 3; // strlen("css")
+const int kUrlOverhead = kIdOverhead + ResourceNamer::kOverhead + kExtOverhead;
+
+} // namespace
+
+class CssCombineFilter::Partnership : public UrlPartnership {
+ public:
+ Partnership(RewriteDriver* driver, const GURL& gurl)
+ : UrlPartnership(driver->options(), gurl),
+ rewrite_options_(driver->options()),
+ resource_manager_(driver->resource_manager()),
+ prev_num_components_(0),
+ accumulated_leaf_size_(0) {
+ }
+
+ virtual ~Partnership() {
+ STLDeleteElements(&resources_);
+ }
+
+ bool AddElement(HtmlElement* element, const StringPiece& href,
+ const StringPiece& media, scoped_ptr<Resource>* resource,
+ MessageHandler* handler) {
+ // Assert the sanity of three parallel vectors.
+ CHECK_EQ(num_urls(), static_cast<int>(resources_.size()));
+ CHECK_EQ(num_urls(), static_cast<int>(css_elements_.size()));
+ CHECK_EQ(num_urls(), static_cast<int>(multipart_encoder_.num_urls()));
+
+ bool added = true;
+ if (num_urls() == 0) {
+ // TODO(jmarantz): do media='' and media='display mean the same
+ // thing? sligocki thinks mdsteele looked into this and it
+ // depended on HTML version. In one display was default, in the
+ // other screen was IIRC.
+ media.CopyToString(&media_);
+ } else {
+ // After the first CSS file, subsequent CSS files must have matching
+ // media and no @import tags.
+ added = ((media_ == media) &&
+ !CssTagScanner::HasImport((*resource)->contents(), handler));
+ }
+ if (added) {
+ added = AddUrl(href, handler);
+ }
+ if (added) {
+ int index = num_urls() - 1;
+ CHECK_EQ(index, static_cast<int>(css_elements_.size()));
+
+ if (num_components() != prev_num_components_) {
+ UpdateResolvedBase();
+ }
+ const std::string relative_path = RelativePath(index);
+ multipart_encoder_.AddUrl(relative_path);
+
+ if (accumulated_leaf_size_ == 0) {
+ ComputeLeafSize();
+ } else {
+ AccumulateLeafSize(relative_path);
+ }
+
+ if (UrlTooBig()) {
+ added = false;
+ RemoveLast();
+ multipart_encoder_.pop_back();
+
+ // The base might have changed again
+ if (num_components() != prev_num_components_) {
+ UpdateResolvedBase();
+ }
+ } else {
+ css_elements_.push_back(element);
+ resources_.push_back(resource->release());
+ }
+ }
+ return added;
+ }
+
+ // Computes a name for the URL that meets all known character-set and
+ // size restrictions.
+ std::string UrlSafeId() const {
+ UrlEscaper* escaper = resource_manager_->url_escaper();
+ std::string segment;
+ escaper->EncodeToUrlSegment(multipart_encoder_.Encode(), &segment);
+ return segment;
+ }
+
+ // Computes the total size
+ void ComputeLeafSize() {
+ std::string segment = UrlSafeId();
+ // TODO(sligocki): Use hasher for custom overhead.
+ accumulated_leaf_size_ = segment.size() + kUrlOverhead
+ + resource_manager_->hasher()->HashSizeInChars();
+ }
+
+ // Incrementally updates the accumulated leaf size without re-examining
+ // every element in the combined css file.
+ void AccumulateLeafSize(const StringPiece& url) {
+ std::string segment;
+ UrlEscaper* escaper = resource_manager_->url_escaper();
+ escaper->EncodeToUrlSegment(url, &segment);
+ const int kMultipartOverhead = 1; // for the '+'
+ accumulated_leaf_size_ += segment.size() + kMultipartOverhead;
+ }
+
+ // Determines whether our accumulated leaf size is too big, taking into
+ // account both per-segment and total-url limitations.
+ bool UrlTooBig() {
+ if (accumulated_leaf_size_ > rewrite_options_->max_url_segment_size()) {
+ return true;
+ }
+ if ((accumulated_leaf_size_ + static_cast<int>(resolved_base_.size())) >
+ rewrite_options_->max_url_size()) {
+ return true;
+ }
+ return false;
+ }
+
+ void UpdateResolvedBase() {
+ // If the addition of this URL changes the base path,
+ // then we will have to recompute the multi-part encoding.
+ // This is n^2 in the pathalogical case and if this code
+ // is copied from CSS combining to image spriting then this
+ // algorithm should be revisited. For CSS we expect N to
+ // be relatively small.
+ prev_num_components_ = num_components();
+ resolved_base_ = ResolvedBase();
+ multipart_encoder_.clear();
+ for (size_t i = 0; i < css_elements_.size(); ++i) {
+ multipart_encoder_.AddUrl(RelativePath(i));
+ }
+
+ accumulated_leaf_size_ = 0;
+ }
+
+ HtmlElement* element(int i) { return css_elements_[i]; }
+ const ResourceVector& resources() const { return resources_; }
+ const std::string& media() const { return media_; }
+
+ private:
+ const RewriteOptions* rewrite_options_;
+ ResourceManager* resource_manager_;
+ std::vector<HtmlElement*> css_elements_;
+ std::vector<Resource*> resources_;
+ UrlMultipartEncoder multipart_encoder_;
+ int prev_num_components_;
+ int accumulated_leaf_size_;
+ std::string resolved_base_;
+ std::string media_;
+};
+
+// TODO(jmarantz) We exhibit zero intelligence about which css files to
+// combine; we combine whatever is possible. This can reduce performance
+// by combining highly cacheable shared resources with transient ones.
+//
+// TODO(jmarantz): We do not recognize IE directives as spriting boundaries.
+// We should supply a meaningful IEDirective method as a boundary.
+//
+// TODO(jmarantz): allow combining of CSS elements found in the body, whether
+// or not the head has already been flushed.
+
+CssCombineFilter::CssCombineFilter(RewriteDriver* driver,
+ const char* filter_prefix)
+ : RewriteFilter(driver, filter_prefix),
+ html_parse_(driver->html_parse()),
+ resource_manager_(driver->resource_manager()),
+ css_tag_scanner_(html_parse_),
+ css_file_count_reduction_(NULL) {
+ // This CHECK is here because RewriteDriver is constructed with it's
+ // resource_manager_ == NULL.
+ // TODO(sligocki): Construct RewriteDriver with a ResourceManager.
+ CHECK(resource_manager_ != NULL);
+ s_link_ = html_parse_->Intern("link");
+ s_href_ = html_parse_->Intern("href");
+ s_type_ = html_parse_->Intern("type");
+ s_rel_ = html_parse_->Intern("rel");
+ s_media_ = html_parse_->Intern("media");
+ s_style_ = html_parse_->Intern("style");
+ Statistics* stats = resource_manager_->statistics();
+ if (stats != NULL) {
+ css_file_count_reduction_ = stats->GetVariable(kCssFileCountReduction);
+ }
+}
+
+CssCombineFilter::~CssCombineFilter() {
+}
+
+void CssCombineFilter::Initialize(Statistics* statistics) {
+ statistics->AddVariable(kCssFileCountReduction);
+}
+
+void CssCombineFilter::StartDocumentImpl() {
+ // This should already be clear, but just in case.
+ partnership_.reset(new Partnership(driver_, base_gurl()));
+}
+
+void CssCombineFilter::EndElementImpl(HtmlElement* element) {
+ HtmlElement::Attribute* href;
+ const char* media;
+ if (css_tag_scanner_.ParseCssElement(element, &href, &media)) {
+ // We cannot combine with a link in <noscript> tag and we cannot combine
+ // over a link in a <noscript> tag, so this is a barrier.
+ if (noscript_element() != NULL) {
+ TryCombineAccumulated();
+ } else {
+ const char* url = href->value();
+ MessageHandler* handler = html_parse_->message_handler();
+ scoped_ptr<Resource> resource(CreateInputResource(url));
+ if (resource.get() == NULL) {
+ TryCombineAccumulated();
+ } else {
+ if (!resource_manager_->ReadIfCached(resource.get(), handler) ||
+ !resource->ContentsValid()) {
+ TryCombineAccumulated();
+ } else if (!partnership_->AddElement(element, url, media, &resource,
+ handler)) {
+ TryCombineAccumulated();
+
+ // Now we'll try to start a new partnership with this CSS file --
+ // perhaps we ran out out of space in the previous combination
+ // or this file is simply in a different authorized domain, or
+ // contained @Import.
+ partnership_->AddElement(element, url, media, &resource, handler);
+ }
+ }
+ }
+ } else if (element->tag() == s_style_) {
+ // We can't reorder styles on a page, so if we are only combining <link>
+ // tags, we can't combine them across a <style> tag.
+ // TODO(sligocki): Maybe we should just combine <style>s too?
+ // We can run outline_css first for now to make all <style>s into <link>s.
+ TryCombineAccumulated();
+ }
+}
+
+// An IE directive that includes any stylesheet info should be a barrier
+// for css combining. It's OK to emit the combination we've seen so far.
+void CssCombineFilter::IEDirective(HtmlIEDirectiveNode* directive) {
+ // TODO(sligocki): Figure out how to safely parse IEDirectives, for now we
+ // just consider them black boxes / solid barriers.
+ TryCombineAccumulated();
+}
+
+void CssCombineFilter::Flush() {
+ TryCombineAccumulated();
+}
+
+void CssCombineFilter::TryCombineAccumulated() {
+ CHECK(partnership_.get() != NULL);
+ MessageHandler* handler = html_parse_->message_handler();
+ if (partnership_->num_urls() > 1) {
+ // Ideally like to have a data-driven service tell us which elements should
+ // be combined together. Note that both the resources and the elements
+ // are managed, so we don't delete them even if the spriting fails.
+
+ // First, compute the name of the new resource based on the names of
+ // the CSS files.
+ std::string url_safe_id = partnership_->UrlSafeId();
+ HtmlElement* combine_element = html_parse_->NewElement(NULL, s_link_);
+ combine_element->AddAttribute(s_rel_, "stylesheet", "\"");
+ combine_element->AddAttribute(s_type_, "text/css", "\"");
+ StringPiece media = partnership_->media();
+ if (!media.empty()) {
+ combine_element->AddAttribute(s_media_, media, "\"");
+ }
+
+ // Start building up the combination. At this point we are still
+ // not committed to the combination, because the 'write' can fail.
+ // TODO(jmaessen, jmarantz): encode based on partnership
+ scoped_ptr<OutputResource> combination(
+ resource_manager_->CreateOutputResourceWithPath(
+ partnership_->ResolvedBase(),
+ filter_prefix_, url_safe_id, &kContentTypeCss, handler));
+ bool written = combination->IsWritten() ||
+ WriteCombination(partnership_->resources(), combination.get(), handler);
+
+ // We've collected at least two CSS files to combine, and whose
+ // HTML elements are in the current flush window. Last step
+ // is to write the combination.
+ if (written && combination->IsWritten()) {
+ // Commit by adding combine element ...
+ combine_element->AddAttribute(s_href_, combination->url(), "\"");
+ // TODO(sligocki): Put at top of head/flush-window.
+ // Right now we're putting it where the first original element used to be.
+ html_parse_->InsertElementBeforeElement(partnership_->element(0),
+ combine_element);
+ // ... and removing originals from the DOM.
+ for (int i = 0; i < partnership_->num_urls(); ++i) {
+ html_parse_->DeleteElement(partnership_->element(i));
+ }
+ html_parse_->InfoHere("Combined %d CSS files into one at %s",
+ partnership_->num_urls(),
+ combination->url().c_str());
+ if (css_file_count_reduction_ != NULL) {
+ css_file_count_reduction_->Add(partnership_->num_urls() - 1);
+ }
+ }
+ }
+ partnership_.reset(new Partnership(driver_, base_gurl()));
+}
+
+bool CssCombineFilter::WriteCombination(const ResourceVector& combine_resources,
+ OutputResource* combination,
+ MessageHandler* handler) {
+ bool written = true;
+ // TODO(sligocki): Write directly to a temp file rather than doing the extra
+ // string copy.
+ std::string combined_contents;
+ StringWriter writer(&combined_contents);
+ int64 min_origin_expiration_time_ms = 0;
+
+ for (int i = 0, n = combine_resources.size(); written && (i < n); ++i) {
+ Resource* input = combine_resources[i];
+ StringPiece contents = input->contents();
+ int64 input_expire_time_ms = input->CacheExpirationTimeMs();
+ if ((min_origin_expiration_time_ms == 0) ||
+ (input_expire_time_ms < min_origin_expiration_time_ms)) {
+ min_origin_expiration_time_ms = input_expire_time_ms;
+ }
+
+ std::string input_dir =
+ GoogleUrl::AllExceptLeaf(GoogleUrl::Create(input->url()));
+ if (input_dir == combination->resolved_base()) {
+ // We don't need to absolutify URLs if input directory is same as output.
+ written = writer.Write(contents, handler);
+ } else {
+ // If they are different directories, we need to absolutify.
+ // TODO(sligocki): Perhaps we should use the real CSS parser.
+ written = css_tag_scanner_.AbsolutifyUrls(contents, input->url(),
+ &writer, handler);
+ }
+ }
+ if (written) {
+ written =
+ resource_manager_->Write(
+ HttpStatus::kOK, combined_contents, combination,
+ min_origin_expiration_time_ms, handler);
+ }
+ return written;
+}
+
+// Callback to run whenever a CSS resource is collected. This keeps a
+// count of the resources collected so far. When the last one is collected,
+// it aggregates the results and calls the final callback with the result.
+class CssCombiner : public Resource::AsyncCallback {
+ public:
+ CssCombiner(CssCombineFilter* filter,
+ MessageHandler* handler,
+ UrlAsyncFetcher::Callback* callback,
+ OutputResource* combination,
+ Writer* writer,
+ MetaData* response_headers) :
+ enable_completion_(false),
+ emit_done_(true),
+ done_count_(0),
+ fail_count_(0),
+ filter_(filter),
+ message_handler_(handler),
+ callback_(callback),
+ combination_(combination),
+ writer_(writer),
+ response_headers_(response_headers) {
+ }
+
+ virtual ~CssCombiner() {
+ STLDeleteContainerPointers(combine_resources_.begin(),
+ combine_resources_.end());
+ }
+
+ // Note that the passed-in resource might be NULL; this gives us a chance
+ // to note failure.
+ bool AddResource(Resource* resource) {
+ bool ret = false;
+ if (resource == NULL) {
+ // Whoops, we've failed to even obtain a resource.
+ ++fail_count_;
+ } else if (fail_count_ > 0) {
+ // Another of the resource fetches failed. Drop this resource
+ // and don't fetch it.
+ delete resource;
+ } else {
+ combine_resources_.push_back(resource);
+ ret = true;
+ }
+ return ret;
+ }
+
+ virtual void Done(bool success, Resource* resource) {
+ if (!success) {
+ ++fail_count_;
+ }
+ ++done_count_;
+
+ if (Ready()) {
+ DoCombination();
+ }
+ }
+
+ bool Ready() {
+ return (enable_completion_ &&
+ (done_count_ == combine_resources_.size()));
+ }
+
+ void EnableCompletion() {
+ enable_completion_ = true;
+ if (Ready()) {
+ DoCombination();
+ }
+ }
+
+ void DoCombination() {
+ bool ok = fail_count_ == 0;
+ for (size_t i = 0; ok && (i < combine_resources_.size()); ++i) {
+ Resource* css_resource = combine_resources_[i];
+ ok = css_resource->ContentsValid();
+ }
+ if (ok) {
+ ok = (filter_->WriteCombination(combine_resources_, combination_,
+ message_handler_) &&
+ combination_->IsWritten() &&
+ ((writer_ == NULL) ||
+ writer_->Write(combination_->contents(), message_handler_)));
+ // Above code fills in combination_->metadata(); now propagate to
+ // repsonse_headers_.
+ }
+ if (ok) {
+ response_headers_->CopyFrom(*combination_->metadata());
+ } else {
+ response_headers_->SetStatusAndReason(HttpStatus::kNotFound);
+ }
+
+ if (emit_done_) {
+ callback_->Done(ok);
+ }
+ delete this;
+ }
+
+ void set_emit_done(bool new_emit_done) {
+ emit_done_ = new_emit_done;
+ }
+
+ private:
+ bool enable_completion_;
+ bool emit_done_;
+ size_t done_count_;
+ size_t fail_count_;
+ CssCombineFilter* filter_;
+ MessageHandler* message_handler_;
+ UrlAsyncFetcher::Callback* callback_;
+ OutputResource* combination_;
+ CssCombineFilter::ResourceVector combine_resources_;
+ Writer* writer_;
+ MetaData* response_headers_;
+
+ DISALLOW_COPY_AND_ASSIGN(CssCombiner);
+};
+
+bool CssCombineFilter::Fetch(OutputResource* combination,
+ Writer* writer,
+ const MetaData& request_header,
+ MetaData* response_headers,
+ MessageHandler* message_handler,
+ UrlAsyncFetcher::Callback* callback) {
+ CHECK(response_headers != NULL);
+ bool ret = false;
+ StringPiece url_safe_id = combination->name();
+ UrlMultipartEncoder multipart_encoder;
+ UrlEscaper* escaper = resource_manager_->url_escaper();
+ std::string multipart_encoding;
+ GURL gurl(combination->url());
+ if (gurl.is_valid() &&
+ escaper->DecodeFromUrlSegment(url_safe_id, &multipart_encoding) &&
+ multipart_encoder.Decode(multipart_encoding, message_handler)) {
+ std::string url, decoded_resource;
+ ret = true;
+ CssCombiner* combiner = new CssCombiner(
+ this, message_handler, callback, combination, writer, response_headers);
+
+ std::string root = GoogleUrl::AllExceptLeaf(gurl);
+ for (int i = 0; ret && (i < multipart_encoder.num_urls()); ++i) {
+ std::string url = StrCat(root, multipart_encoder.url(i));
+ Resource* css_resource = CreateInputResourceAbsolute(url);
+ ret = combiner->AddResource(css_resource);
+ if (ret) {
+ resource_manager_->ReadAsync(css_resource, combiner, message_handler);
+ }
+ }
+
+ // If we're about to return false, we do not want the combiner to emit
+ // Done as RewriteDriver::FetchResource will do it as well.
+ combiner->set_emit_done(ret);
+
+ // In the case where the first input CSS files is already cached,
+ // ReadAsync will directly call the CssCombineCallback, which, if
+ // already enabled, would think it was complete and run DoCombination
+ // prematurely. So we wait until the resources are all added before
+ // enabling the callback to complete.
+ combiner->EnableCompletion();
+ } else {
+ message_handler->Error(url_safe_id.as_string().c_str(), 0,
+ "Unable to decode resource string");
+ }
+ return ret;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/css_combine_filter_test.cc b/trunk/src/net/instaweb/rewriter/css_combine_filter_test.cc
new file mode 100644
index 0000000..bb8b29e
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/css_combine_filter_test.cc
@@ -0,0 +1,837 @@
+/*
+ * 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: jmarantz@google.com (Joshua Marantz)
+// and sligocki@google.com (Shawn Ligocki)
+
+#include "net/instaweb/rewriter/public/resource_manager_test_base.h"
+
+#include "net/instaweb/htmlparse/public/empty_html_filter.h"
+#include "net/instaweb/rewriter/public/css_tag_scanner.h"
+#include "net/instaweb/util/public/url_multipart_encoder.h"
+#include "net/instaweb/util/public/url_escaper.h"
+
+namespace net_instaweb {
+
+namespace {
+
+const char kDomain[] = "http://combine_css.test/";
+const char kYellow[] = ".yellow {background-color: yellow;}";
+const char kBlue[] = ".blue {color: blue;}\n";
+
+class CssCombineFilterTest : public ResourceManagerTestBase {
+ protected:
+ // Test spriting CSS with options to write headers and use a hasher.
+ void CombineCss(const StringPiece& id, Hasher* hasher,
+ const char* barrier_text, bool is_barrier) {
+ CombineCssWithNames(id, hasher, barrier_text, is_barrier, "a.css", "b.css");
+ }
+
+ void CombineCssWithNames(const StringPiece& id, Hasher* hasher,
+ const char* barrier_text, bool is_barrier,
+ const char* a_css_name,
+ const char* b_css_name) {
+ resource_manager_->set_hasher(hasher);
+ other_resource_manager_.set_hasher(hasher);
+
+ AddFilter(RewriteOptions::kCombineCss);
+ AddOtherFilter(RewriteOptions::kCombineCss);
+
+ // URLs and content for HTML document and resources.
+ CHECK_EQ(StringPiece::npos, id.find("/"));
+ std::string html_url = StrCat(kDomain, id, ".html");
+ std::string a_css_url = StrCat(kDomain, a_css_name);
+ std::string b_css_url = StrCat(kDomain, b_css_name);
+ std::string c_css_url = StrCat(kDomain, "c.css");
+
+ static const char html_input_format[] =
+ "<head>\n"
+ " <link rel='stylesheet' href='%s' type='text/css'>\n"
+ " <link rel='stylesheet' href='%s' type='text/css'>\n"
+ " <title>Hello, Instaweb</title>\n"
+ "%s"
+ "</head>\n"
+ "<body>\n"
+ " <div class=\"c1\">\n"
+ " <div class=\"c2\">\n"
+ " Yellow on Blue\n"
+ " </div>\n"
+ " </div>\n"
+ " <link rel='stylesheet' href='c.css' type='text/css'>\n"
+ "</body>\n";
+ std::string html_input =
+ StringPrintf(html_input_format, a_css_name, b_css_name, barrier_text);
+ const char a_css_body[] = ".c1 {\n background-color: blue;\n}\n";
+ const char b_css_body[] = ".c2 {\n color: yellow;\n}\n";
+ const char c_css_body[] = ".c3 {\n font-weight: bold;\n}\n";
+
+ // Put original CSS files into our fetcher.
+ SimpleMetaData default_css_header;
+ resource_manager_->SetDefaultHeaders(&kContentTypeCss, &default_css_header);
+ mock_url_fetcher_.SetResponse(a_css_url, default_css_header, a_css_body);
+ mock_url_fetcher_.SetResponse(b_css_url, default_css_header, b_css_body);
+ mock_url_fetcher_.SetResponse(c_css_url, default_css_header, c_css_body);
+
+ ParseUrl(html_url, html_input);
+
+ std::string headers;
+ AppendDefaultHeaders(kContentTypeCss, resource_manager_, &headers);
+
+ // Check for CSS files in the rewritten page.
+ StringVector css_urls;
+ CollectCssLinks(id, output_buffer_, &css_urls);
+ EXPECT_LE(1UL, css_urls.size());
+ const std::string& combine_url = css_urls[0];
+
+ GURL gurl(combine_url);
+ std::string path = GoogleUrl::PathAndLeaf(gurl);
+
+ std::string combine_filename;
+ filename_encoder_.Encode(file_prefix_, combine_url, &combine_filename);
+
+ // Expected CSS combination.
+ // This syntax must match that in css_combine_filter
+ std::string expected_combination = StrCat(a_css_body, b_css_body);
+ if (!is_barrier) {
+ expected_combination.append(c_css_body);
+ }
+
+ static const char expected_output_format[] =
+ "<head>\n"
+ " <link rel=\"stylesheet\" type=\"text/css\" href=\"%s\">\n"
+ " \n" // The whitespace from the original link is preserved here ...
+ " <title>Hello, Instaweb</title>\n"
+ "%s"
+ "</head>\n"
+ "<body>\n"
+ " <div class=\"c1\">\n"
+ " <div class=\"c2\">\n"
+ " Yellow on Blue\n"
+ " </div>\n"
+ " </div>\n"
+ " %s\n"
+ "</body>\n";
+ std::string expected_output = StringPrintf(
+ expected_output_format, combine_url.c_str(), barrier_text,
+ is_barrier ? "<link rel='stylesheet' href='c.css' type='text/css'>"
+ : "");
+ EXPECT_EQ(AddHtmlBody(expected_output), output_buffer_);
+
+ std::string actual_combination;
+ ASSERT_TRUE(file_system_.ReadFile(combine_filename.c_str(),
+ &actual_combination, &message_handler_));
+ EXPECT_EQ(headers + expected_combination, actual_combination);
+
+ // Fetch the combination to make sure we can serve the result from above.
+ SimpleMetaData request_headers, response_headers;
+ std::string fetched_resource_content;
+ StringWriter writer(&fetched_resource_content);
+ DummyCallback dummy_callback(true);
+ rewrite_driver_.FetchResource(combine_url, request_headers,
+ &response_headers, &writer,
+ &message_handler_, &dummy_callback);
+ EXPECT_EQ(HttpStatus::kOK, response_headers.status_code()) << combine_url;
+ EXPECT_EQ(expected_combination, fetched_resource_content);
+
+ // Now try to fetch from another server (other_rewrite_driver_) that
+ // does not already have the combination cached.
+ // TODO(sligocki): This has too much shared state with the first server.
+ // See RewriteImage for details.
+ SimpleMetaData other_response_headers;
+ fetched_resource_content.clear();
+ message_handler_.Message(kInfo, "Now with serving.");
+ file_system_.Enable();
+ dummy_callback.Reset();
+ other_rewrite_driver_.FetchResource(combine_url, request_headers,
+ &other_response_headers, &writer,
+ &message_handler_, &dummy_callback);
+ EXPECT_EQ(HttpStatus::kOK, other_response_headers.status_code());
+ EXPECT_EQ(expected_combination, fetched_resource_content);
+
+ // Try to fetch from an independent server.
+ ServeResourceFromManyContexts(combine_url, RewriteOptions::kCombineCss,
+ hasher, fetched_resource_content);
+ }
+
+ // Test what happens when CSS combine can't find a previously-rewritten
+ // resource during a subsequent resource fetch. This used to segfault.
+ void CssCombineMissingResource() {
+ std::string a_css_url = StrCat(kDomain, "a.css");
+ std::string c_css_url = StrCat(kDomain, "c.css");
+
+ const char a_css_body[] = ".c1 {\n background-color: blue;\n}\n";
+ const char c_css_body[] = ".c3 {\n font-weight: bold;\n}\n";
+ std::string expected_combination = StrCat(a_css_body, c_css_body);
+
+ AddFilter(RewriteOptions::kCombineCss);
+
+ // Put original CSS files into our fetcher.
+ SimpleMetaData default_css_header;
+ resource_manager_->SetDefaultHeaders(&kContentTypeCss, &default_css_header);
+ mock_url_fetcher_.SetResponse(a_css_url, default_css_header, a_css_body);
+ mock_url_fetcher_.SetResponse(c_css_url, default_css_header, c_css_body);
+
+ // First make sure we can serve the combination of a & c. This is to avoid
+ // spurious test successes.
+
+ std::string kACUrl = Encode(kDomain, "cc", "0", "a.css+c.css", "css");
+ std::string kABCUrl = Encode(kDomain, "cc", "0", "a.css+bbb.css+c.css",
+ "css");
+ SimpleMetaData request_headers, response_headers;
+ std::string fetched_resource_content;
+ StringWriter writer(&fetched_resource_content);
+ DummyCallback dummy_callback(true);
+
+ // NOTE: This first fetch used to return status 0 because response_headers
+ // weren't initialized by the first resource fetch (but were cached
+ // correctly). Content was correct.
+ EXPECT_TRUE(
+ rewrite_driver_.FetchResource(kACUrl, request_headers,
+ &response_headers, &writer,
+ &message_handler_, &dummy_callback));
+ EXPECT_EQ(HttpStatus::kOK, response_headers.status_code());
+ EXPECT_EQ(expected_combination, fetched_resource_content);
+
+ // We repeat the fetch to prove that it succeeds from cache:
+ fetched_resource_content.clear();
+ dummy_callback.Reset();
+ response_headers.Clear();
+ EXPECT_TRUE(
+ rewrite_driver_.FetchResource(kACUrl, request_headers,
+ &response_headers, &writer,
+ &message_handler_, &dummy_callback));
+ EXPECT_EQ(HttpStatus::kOK, response_headers.status_code());
+ EXPECT_EQ(expected_combination, fetched_resource_content);
+
+ // Now let's try fetching the url that references a missing resource
+ // (bbb.css) in addition to the two that do exist, a.css and c.css. Using
+ // an entirely non-existent resource appears to test a strict superset of
+ // filter code paths when compared with returning a 404 for the resource.
+ mock_url_fetcher_.set_fail_on_unexpected(false);
+ DummyCallback fail_callback(false);
+ fetched_resource_content.clear();
+ response_headers.Clear();
+ EXPECT_TRUE(
+ rewrite_driver_.FetchResource(kABCUrl, request_headers,
+ &response_headers, &writer,
+ &message_handler_, &fail_callback));
+ EXPECT_EQ(HttpStatus::kNotFound, response_headers.status_code());
+ EXPECT_EQ("", fetched_resource_content);
+ }
+
+ // Representation for a CSS tag.
+ class CssLink {
+ public:
+ CssLink(const StringPiece& url, const StringPiece& content,
+ const StringPiece& media, bool supply_mock)
+ : url_(url.data(), url.size()),
+ content_(content.data(), content.size()),
+ media_(media.data(), media.size()),
+ supply_mock_(supply_mock) {
+ }
+
+ // A vector of CssLink* should know how to accumulate and add.
+ class Vector : public std::vector<CssLink*> {
+ public:
+ ~Vector() {
+ STLDeleteElements(this);
+ }
+
+ void Add(const StringPiece& url, const StringPiece& content,
+ const StringPiece& media, bool supply_mock) {
+ push_back(new CssLink(url, content, media, supply_mock));
+ }
+ };
+
+ // Parses a combined CSS elementand provides the segments from which
+ // it came.
+ bool DecomposeCombinedUrl(std::string* base, StringVector* segments,
+ MessageHandler* handler) {
+ GURL gurl = GoogleUrl::Create(url_);
+ bool ret = false;
+ if (gurl.is_valid()) {
+ *base = GoogleUrl::AllExceptLeaf(gurl);
+ ResourceNamer namer;
+ if (namer.Decode(GoogleUrl::Leaf(gurl)) &&
+ (namer.id() == "cc")) { // TODO(jmarantz): Share this literal
+ UrlEscaper escaper;
+ UrlMultipartEncoder multipart_encoder;
+ std::string segment;
+ if (escaper.DecodeFromUrlSegment(namer.name(), &segment) &&
+ multipart_encoder.Decode(segment, handler)) {
+ ret = true;
+ for (int i = 0; i < multipart_encoder.num_urls(); ++i) {
+ segments->push_back(multipart_encoder.url(i));
+ }
+ }
+ }
+ }
+ return ret;
+ }
+
+ std::string url_;
+ std::string content_;
+ std::string media_;
+ bool supply_mock_;
+ };
+
+ // Helper class to collect CSS hrefs.
+ class CssCollector : public EmptyHtmlFilter {
+ public:
+ CssCollector(HtmlParse* html_parse, CssLink::Vector* css_links)
+ : css_links_(css_links),
+ css_tag_scanner_(html_parse) {
+ }
+
+ virtual void EndElement(HtmlElement* element) {
+ HtmlElement::Attribute* href;
+ const char* media;
+ if (css_tag_scanner_.ParseCssElement(element, &href, &media)) {
+ // TODO(jmarantz): collect content of the CSS files, before and
+ // after combination, so we can diff.
+ const char* content = "";
+ css_links_->Add(href->value(), content, media, false);
+ }
+ }
+
+ virtual const char* Name() const { return "CssCollector"; }
+
+ private:
+ CssLink::Vector* css_links_;
+ CssTagScanner css_tag_scanner_;
+
+ DISALLOW_COPY_AND_ASSIGN(CssCollector);
+ };
+
+ // Collects just the hrefs from CSS links into a string vector.
+ void CollectCssLinks(const StringPiece& id, const StringPiece& html,
+ StringVector* css_links) {
+ CssLink::Vector v;
+ CollectCssLinks(id, html, &v);
+ for (int i = 0, n = v.size(); i < n; ++i) {
+ css_links->push_back(v[i]->url_);
+ }
+ }
+
+ // Collects all information about CSS links into a CssLink::Vector.
+ void CollectCssLinks(const StringPiece& id, const StringPiece& html,
+ CssLink::Vector* css_links) {
+ HtmlParse html_parse(&message_handler_);
+ CssCollector collector(&html_parse, css_links);
+ html_parse.AddFilter(&collector);
+ std::string dummy_url = StrCat("http://collect.css.links/", id, ".html");
+ html_parse.StartParse(dummy_url);
+ html_parse.ParseText(html.data(), html.size());
+ html_parse.FinishParse();
+ }
+
+ // Common framework for testing barriers. A null-terminated set of css
+ // names is specified, with optional media tags. E.g.
+ // static const char* link[] {
+ // "a.css",
+ // "styles/b.css",
+ // "print.css media=print",
+ // }
+ //
+ // The output of this function is the collected CSS links after rewrite.
+ void BarrierTestHelper(
+ const StringPiece& id,
+ const CssLink::Vector& input_css_links,
+ CssLink::Vector* output_css_links) {
+ // TODO(sligocki): Allow other domains (this is constrained right now b/c
+ // of InitMetaData.
+ std::string html_url = StrCat("http://test.com/", id, ".html");
+ std::string html_input("<head>\n");
+ for (int i = 0, n = input_css_links.size(); i < n; ++i) {
+ const CssLink* link = input_css_links[i];
+ if (!link->url_.empty()) {
+ if (link->supply_mock_) {
+ // If the css-vector contains a 'true' for this, then we supply the
+ // mock fetcher with headers and content for the CSS file.
+ InitMetaData(link->url_, kContentTypeCss, link->content_, 600);
+ }
+ std::string style(" <");
+ style += StringPrintf("link rel='stylesheet' type='text/css' href='%s'",
+ link->url_.c_str());
+ if (!link->media_.empty()) {
+ style += StrCat(" media='", link->media_, "'");
+ }
+ style += ">\n";
+ html_input += style;
+ } else {
+ html_input += link->content_;
+ }
+ }
+ html_input += "</head>\n<body>\n <div class='yellow'>\n";
+ html_input += " Hello, mod_pagespeed!\n </div>\n</body>\n";
+
+ AddFilter(RewriteOptions::kCombineCss);
+ ParseUrl(html_url, html_input);
+ CollectCssLinks("combine_css_missing_files", output_buffer_,
+ output_css_links);
+
+ // TODO(jmarantz): fetch all content and provide output as text.
+ }
+};
+
+TEST_F(CssCombineFilterTest, CombineCss) {
+ CombineCss("combine_css_no_hash", &mock_hasher_, "", false);
+}
+
+TEST_F(CssCombineFilterTest, CombineCssMD5) {
+ CombineCss("combine_css_md5", &md5_hasher_, "", false);
+}
+
+
+// http://code.google.com/p/modpagespeed/issues/detail?q=css&id=39
+TEST_F(CssCombineFilterTest, DealWithParams) {
+ CombineCssWithNames("deal_with_params", &mock_hasher_, "", false,
+ "a.css?U", "b.css?rev=138");
+}
+
+TEST_F(CssCombineFilterTest, CombineCssWithIEDirective) {
+ const char ie_directive_barrier[] =
+ "<!--[if IE]>\n"
+ "<link rel=\"stylesheet\" type=\"text/css\" "
+ "href=\"http://graphics8.nytimes.com/css/"
+ "0.1/screen/build/homepage/ie.css\">\n"
+ "<![endif]-->";
+ CombineCss("combine_css_ie", &md5_hasher_, ie_directive_barrier, true);
+}
+
+TEST_F(CssCombineFilterTest, CombineCssWithStyle) {
+ const char style_barrier[] = "<style>a { color: red }</style>\n";
+ CombineCss("combine_css_style", &md5_hasher_, style_barrier, true);
+}
+
+TEST_F(CssCombineFilterTest, CombineCssWithBogusLink) {
+ const char bogus_barrier[] = "<link rel='stylesheet' type='text/css' "
+ "href='crazee://big/blue/fake'>\n";
+ CombineCss("combine_css_bogus_link", &md5_hasher_, bogus_barrier, true);
+}
+
+TEST_F(CssCombineFilterTest, CombineCssWithImportInFirst) {
+ CssLink::Vector css_in, css_out;
+ css_in.Add("1.css", "@Import \"1a.css\"", "", true);
+ css_in.Add("2.css", kYellow, "", true);
+ css_in.Add("3.css", kYellow, "", true);
+ BarrierTestHelper("combine_css_with_import1", css_in, &css_out);
+ EXPECT_EQ(1, css_out.size());
+}
+
+TEST_F(CssCombineFilterTest, CombineCssWithImportInSecond) {
+ CssLink::Vector css_in, css_out;
+ css_in.Add("1.css", kYellow, "", true);
+ css_in.Add("2.css", "@Import \"2a.css\"", "", true);
+ css_in.Add("3.css", kYellow, "", true);
+ BarrierTestHelper("combine_css_with_import1", css_in, &css_out);
+ EXPECT_EQ("1.css", css_out[0]->url_);
+ EXPECT_EQ(2, css_out.size());
+}
+
+TEST_F(CssCombineFilterTest, CombineCssWithNoscriptBarrier) {
+ const char noscript_barrier[] =
+ "<noscript>\n"
+ " <link rel='stylesheet' type='text/css' href='d.css'>\n"
+ "</noscript>\n";
+
+ // Put this in the Test class to remove repetition here and below.
+ std::string d_css_url = StrCat(kDomain, "d.css");
+ const char d_css_body[] = ".c4 {\n color: green;\n}\n";
+ SimpleMetaData default_css_header;
+ resource_manager_->SetDefaultHeaders(&kContentTypeCss, &default_css_header);
+ mock_url_fetcher_.SetResponse(d_css_url, default_css_header, d_css_body);
+
+ CombineCss("combine_css_noscript", &md5_hasher_, noscript_barrier, true);
+}
+
+TEST_F(CssCombineFilterTest, CombineCssWithFakeNoscriptBarrier) {
+ const char non_barrier[] =
+ "<noscript>\n"
+ " <p>You have no scripts installed</p>\n"
+ "</noscript>\n";
+ CombineCss("combine_css_fake_noscript", &md5_hasher_, non_barrier, false);
+}
+
+TEST_F(CssCombineFilterTest, CombineCssWithMediaBarrier) {
+ const char media_barrier[] =
+ "<link rel='stylesheet' type='text/css' href='d.css' media='print'>\n";
+
+ std::string d_css_url = StrCat(kDomain, "d.css");
+ const char d_css_body[] = ".c4 {\n color: green;\n}\n";
+ SimpleMetaData default_css_header;
+ resource_manager_->SetDefaultHeaders(&kContentTypeCss, &default_css_header);
+ mock_url_fetcher_.SetResponse(d_css_url, default_css_header, d_css_body);
+
+ CombineCss("combine_css_media", &md5_hasher_, media_barrier, true);
+}
+
+TEST_F(CssCombineFilterTest, CombineCssWithNonMediaBarrier) {
+ // Put original CSS files into our fetcher.
+ std::string html_url = StrCat(kDomain, "no_media_barrier.html");
+ std::string a_css_url = StrCat(kDomain, "a.css");
+ std::string b_css_url = StrCat(kDomain, "b.css");
+ std::string c_css_url = StrCat(kDomain, "c.css");
+ std::string d_css_url = StrCat(kDomain, "d.css");
+
+ const char a_css_body[] = ".c1 {\n background-color: blue;\n}\n";
+ const char b_css_body[] = ".c2 {\n color: yellow;\n}\n";
+ const char c_css_body[] = ".c3 {\n font-weight: bold;\n}\n";
+ const char d_css_body[] = ".c4 {\n color: green;\n}\n";
+
+ SimpleMetaData default_css_header;
+ resource_manager_->SetDefaultHeaders(&kContentTypeCss, &default_css_header);
+ mock_url_fetcher_.SetResponse(a_css_url, default_css_header, a_css_body);
+ mock_url_fetcher_.SetResponse(b_css_url, default_css_header, b_css_body);
+ mock_url_fetcher_.SetResponse(c_css_url, default_css_header, c_css_body);
+ mock_url_fetcher_.SetResponse(d_css_url, default_css_header, d_css_body);
+
+ // Only the first two CSS files should be combined.
+ const char html_input[] =
+ "<head>\n"
+ " <link rel='stylesheet' type='text/css' href='a.css' media='print'>\n"
+ " <link rel='stylesheet' type='text/css' href='b.css' media='print'>\n"
+ " <link rel='stylesheet' type='text/css' href='c.css'>\n"
+ " <link rel='stylesheet' type='text/css' href='d.css' media='print'>\n"
+ "</head>";
+
+ // Rewrite
+ AddFilter(RewriteOptions::kCombineCss);
+ ParseUrl(html_url, html_input);
+
+ // Check for CSS files in the rewritten page.
+ StringVector css_urls;
+ CollectCssLinks("combine_css_no_media-links", output_buffer_, &css_urls);
+ EXPECT_EQ(3UL, css_urls.size());
+ const std::string& combine_url = css_urls[0];
+
+ const char expected_output_format[] =
+ "<head>\n"
+ " <link rel=\"stylesheet\" type=\"text/css\" media=\"print\" "
+ "href=\"%s\">\n"
+ " \n"
+ " <link rel='stylesheet' type='text/css' href='c.css'>\n"
+ " <link rel='stylesheet' type='text/css' href='d.css' media='print'>\n"
+ "</head>";
+ std::string expected_output = StringPrintf(expected_output_format,
+ combine_url.c_str());
+
+ EXPECT_EQ(AddHtmlBody(expected_output), output_buffer_);
+}
+
+TEST_F(CssCombineFilterTest, CombineCssBaseUrl) {
+ // Put original CSS files into our fetcher.
+ std::string html_url = StrCat(kDomain, "base_url.html");
+ std::string a_css_url = StrCat(kDomain, "a.css");
+ const char b_css_url[] = "http://other_domain.test/foo/b.css";
+
+ const char a_css_body[] = ".c1 {\n background-color: blue;\n}\n";
+ const char b_css_body[] = ".c2 {\n color: yellow;\n}\n";
+
+ SimpleMetaData default_css_header;
+ resource_manager_->SetDefaultHeaders(&kContentTypeCss, &default_css_header);
+ mock_url_fetcher_.SetResponse(a_css_url, default_css_header, a_css_body);
+ mock_url_fetcher_.SetResponse(b_css_url, default_css_header, b_css_body);
+
+ // Second stylesheet is on other domain.
+ const char html_input[] =
+ "<head>\n"
+ " <link rel='stylesheet' type='text/css' href='a.css'>\n"
+ " <base href='http://other_domain.test/foo/'>\n"
+ " <link rel='stylesheet' type='text/css' href='b.css'>\n"
+ "</head>\n";
+
+ // Rewrite
+ AddFilter(RewriteOptions::kCombineCss);
+ ParseUrl(html_url, html_input);
+
+ // Check for CSS files in the rewritten page.
+ StringVector css_urls;
+ CollectCssLinks("combine_css_no_media-links", output_buffer_, &css_urls);
+ EXPECT_EQ(1UL, css_urls.size());
+ const std::string& combine_url = css_urls[0];
+
+ const char expected_output_format[] =
+ "<head>\n"
+ " <link rel=\"stylesheet\" type=\"text/css\" href=\"%s\">\n"
+ " <base href='http://other_domain.test/foo/'>\n"
+ " \n"
+ "</head>\n";
+ std::string expected_output = StringPrintf(expected_output_format,
+ combine_url.c_str());
+
+ EXPECT_EQ(AddHtmlBody(expected_output), output_buffer_);
+
+ EXPECT_TRUE(GURL(combine_url.c_str()).is_valid());
+}
+
+// TODO(jmaessen): Re-write for new sharding story when it exists.
+// TEST_F(CssCombineFilterTest, CombineCssShards) {
+// num_shards_ = 10;
+// url_prefix_ = "http://mysite%d/";
+// CombineCss("combine_css_sha1", &mock_hasher_, "", false);
+// }
+
+TEST_F(CssCombineFilterTest, CombineCssNoInput) {
+ // TODO(sligocki): This is probably not working correctly, we need to put
+ // a_broken.css and b.css in mock_fetcher_ ... not sure why this isn't
+ // crashing right now.
+ static const char html_input[] =
+ "<head>\n"
+ " <link rel='stylesheet' href='a_broken.css' type='text/css'>\n"
+ " <link rel='stylesheet' href='b.css' type='text/css'>\n"
+ "</head>\n"
+ "<body><div class=\"c1\"><div class=\"c2\"><p>\n"
+ " Yellow on Blue</p></div></div></body>";
+ ValidateNoChanges("combine_css_missing_input", html_input);
+}
+
+TEST_F(CssCombineFilterTest, CombineCssMissingResource) {
+ CssCombineMissingResource();
+}
+
+TEST_F(CssCombineFilterTest, CombineCssManyFiles) {
+ // Prepare an HTML fragment with too many CSS files to combine,
+ // exceeding the 250 char limit currently the default in rewrite_options.cc.
+ //
+ // It looks like we can fit a limited number of encodings of
+ // "yellow%d.css" in the buffer. It might be more general to base
+ // this on the constant declared in RewriteOptions but I think it's
+ // easier to understand leaving these exposed as constants; we can
+ // abstract them later.
+ const int kNumCssLinks = 31;
+ const int kNumCssInCombination = 18; // based on how we encode "yellow%d.css"
+ CssLink::Vector css_in, css_out;
+ for (int i = 0; i < kNumCssLinks; ++i) {
+ css_in.Add(StringPrintf("styles/yellow%d.css", i),
+ kYellow, "", true);
+ }
+ BarrierTestHelper("combine_css_many_files", css_in, &css_out);
+ ASSERT_EQ(2, css_out.size());
+
+ // Check that the first element is really a combination.
+ std::string base;
+ StringVector segments;
+ ASSERT_TRUE(css_out[0]->DecomposeCombinedUrl(&base, &segments,
+ &message_handler_));
+ EXPECT_EQ("http://test.com/styles/", base);
+ EXPECT_EQ(kNumCssInCombination, segments.size());
+
+ segments.clear();
+ ASSERT_TRUE(css_out[1]->DecomposeCombinedUrl(&base, &segments,
+ &message_handler_));
+ EXPECT_EQ("http://test.com/styles/", base);
+ EXPECT_EQ(kNumCssLinks - kNumCssInCombination, segments.size());
+}
+
+TEST_F(CssCombineFilterTest, CombineCssManyFilesOneOrphan) {
+ // This test differs from the previous test in we have exactly one CSS file
+ // that stays on its own.
+ const int kNumCssInCombination = 18; // based on how we encode "yellow%d.css"
+ const int kNumCssLinks = kNumCssInCombination + 1;
+ CssLink::Vector css_in, css_out;
+ for (int i = 0; i < kNumCssLinks - 1; ++i) {
+ css_in.Add(StringPrintf("styles/yellow%d.css", i),
+ kYellow, "", true);
+ }
+ css_in.Add("styles/last_one.css",
+ kYellow, "", true);
+ BarrierTestHelper("combine_css_many_files", css_in, &css_out);
+ ASSERT_EQ(2, css_out.size());
+
+ // Check that the first element is really a combination.
+ std::string base;
+ StringVector segments;
+ ASSERT_TRUE(css_out[0]->DecomposeCombinedUrl(&base, &segments,
+ &message_handler_));
+ EXPECT_EQ("http://test.com/styles/", base);
+ EXPECT_EQ(kNumCssInCombination, segments.size());
+ EXPECT_EQ("styles/last_one.css", css_out[1]->url_);
+}
+
+// Note -- this test is redundant with CombineCssMissingResource -- this
+// is a taste test. This new mechanism is more code per test but I think
+// the failures are more obvious and the expect/assert tests are in the
+// top level of the test which might make it easier to debug.
+TEST_F(CssCombineFilterTest, CombineCssNotCached) {
+ CssLink::Vector css_in, css_out;
+ css_in.Add("1.css", kYellow, "", true);
+ css_in.Add("2.css", kYellow, "", true);
+ css_in.Add("3.css", kYellow, "", false);
+ css_in.Add("4.css", kYellow, "", true);
+ mock_url_fetcher_.set_fail_on_unexpected(false);
+ BarrierTestHelper("combine_css_not_cached", css_in, &css_out);
+ EXPECT_EQ(3, css_out.size());
+ std::string base;
+ StringVector segments;
+ ASSERT_TRUE(css_out[0]->DecomposeCombinedUrl(&base, &segments,
+ &message_handler_));
+ EXPECT_EQ(2, segments.size());
+ EXPECT_EQ("1.css", segments[0]);
+ EXPECT_EQ("2.css", segments[1]);
+ EXPECT_EQ("3.css", css_out[1]->url_);
+ EXPECT_EQ("4.css", css_out[2]->url_);
+}
+
+// Note -- this test is redundant with CombineCssWithIEDirective -- this
+// is a taste test.
+TEST_F(CssCombineFilterTest, CombineStyleTag) {
+ CssLink::Vector css_in, css_out;
+ css_in.Add("1.css", kYellow, "", true);
+ css_in.Add("2.css", kYellow, "", true);
+ css_in.Add("", "<style>a { color: red }</style>\n", "", false);
+ css_in.Add("4.css", kYellow, "", true);
+ BarrierTestHelper("combine_css_with_style", css_in, &css_out);
+ EXPECT_EQ(2, css_out.size());
+ std::string base;
+ StringVector segments;
+ ASSERT_TRUE(css_out[0]->DecomposeCombinedUrl(&base, &segments,
+ &message_handler_));
+ EXPECT_EQ(2, segments.size());
+ EXPECT_EQ("1.css", segments[0]);
+ EXPECT_EQ("2.css", segments[1]);
+ EXPECT_EQ("4.css", css_out[1]->url_);
+}
+
+TEST_F(CssCombineFilterTest, NoAbsolutifySameDir) {
+ CssLink::Vector css_in, css_out;
+ css_in.Add("1.css", ".yellow {background-image: url('1.png');}\n", "", true);
+ css_in.Add("2.css", ".yellow {background-image: url('2.png');}\n", "", true);
+ BarrierTestHelper("combine_css_with_style", css_in, &css_out);
+ EXPECT_EQ(1, css_out.size());
+
+ // Note: the urls are not absolutified.
+ std::string expected_combination =
+ ".yellow {background-image: url('1.png');}\n"
+ ".yellow {background-image: url('2.png');}\n";
+
+ // Check fetched resource.
+ std::string actual_combination;
+ EXPECT_TRUE(ServeResourceUrl(css_out[0]->url_, &actual_combination));
+ // TODO(sligocki): Check headers?
+ EXPECT_EQ(expected_combination, actual_combination);
+}
+
+TEST_F(CssCombineFilterTest, DoAbsolutifyDifferentDir) {
+ CssLink::Vector css_in, css_out;
+ css_in.Add("1.css", ".yellow {background-image: url('1.png');}\n", "", true);
+ css_in.Add("foo/2.css", ".yellow {background-image: url('2.png');}\n",
+ "", true);
+ BarrierTestHelper("combine_css_with_style", css_in, &css_out);
+ EXPECT_EQ(1, css_out.size());
+
+ std::string expected_combination =
+ ".yellow {background-image: url('1.png');}\n"
+ ".yellow {background-image: url('http://test.com/foo/2.png');}\n";
+
+ // Check fetched resource.
+ std::string actual_combination;
+ EXPECT_TRUE(ServeResourceUrl(css_out[0]->url_, &actual_combination));
+ // TODO(sligocki): Check headers?
+ EXPECT_EQ(expected_combination, actual_combination);
+}
+
+// Verifies that when we combine across paths in a certain pattern we get
+// the correct results.
+TEST_F(CssCombineFilterTest, CrossAcrossPathsExceedingUrlSize) {
+ CssLink::Vector css_in, css_out;
+ css_in.Add("modules/acquia/fivestar/css/fivestar.css?3", "a", "", true);
+ css_in.Add("modules/node/node.css?3", "b", "", true);
+ css_in.Add("modules/poll/poll.css?3", "c", "", true);
+ css_in.Add("modules/system/defaults.css?3", "d", "", true);
+ css_in.Add("modules/system/system.css?3", "e", "", true);
+ css_in.Add("modules/system/system-menus.css?3", "f", "", true);
+ css_in.Add("modules/user/user.css?3", "g", "", true);
+
+ // This last 'Add' causes the resolved path to change from "/modules/" to "/".
+ css_in.Add("sites/all/modules/ckeditor/ckeditor.css?3", "h", "", true);
+ BarrierTestHelper("cross_paths", css_in, &css_out);
+ EXPECT_EQ(2, css_out.size());
+ std::string actual_combination;
+ EXPECT_TRUE(ServeResourceUrl(css_out[0]->url_, &actual_combination));
+ GURL gurl = GoogleUrl::Create(css_out[0]->url_);
+ EXPECT_EQ("/modules/", GoogleUrl::PathSansLeaf(gurl));
+ ResourceNamer namer;
+ ASSERT_TRUE(namer.Decode(GoogleUrl::Leaf(gurl)));
+ EXPECT_EQ("acquia,_fivestar,_css,_fivestar.css,q3+"
+ "node,_node.css,q3+"
+ "poll,_poll.css,q3+"
+ "system,_defaults.css,q3+"
+ "system,_system.css,q3+"
+ "system,_system-menus.css,q3+"
+ "user,_user.css,q3",
+ namer.name());
+ EXPECT_EQ("abcdefg", actual_combination);
+}
+
+TEST_F(CssCombineFilterTest, CrossMappedDomain) {
+ CssLink::Vector css_in, css_out;
+ DomainLawyer* laywer = options_.domain_lawyer();
+ laywer->AddRewriteDomainMapping("a.com", "b.com", &message_handler_);
+ bool supply_mock = false;
+ css_in.Add("http://a.com/1.css", kYellow, "", supply_mock);
+ css_in.Add("http://b.com/2.css", kBlue, "", supply_mock);
+ SimpleMetaData default_css_header;
+ resource_manager_->SetDefaultHeaders(&kContentTypeCss, &default_css_header);
+ mock_url_fetcher_.SetResponse("http://a.com/1.css", default_css_header,
+ kYellow);
+ mock_url_fetcher_.SetResponse("http://a.com/2.css", default_css_header,
+ kBlue);
+ BarrierTestHelper("combine_css_with_style", css_in, &css_out);
+ EXPECT_EQ(1, css_out.size());
+ std::string actual_combination;
+ EXPECT_TRUE(ServeResourceUrl(css_out[0]->url_, &actual_combination));
+ EXPECT_EQ(StrCat(kYellow, kBlue), actual_combination);
+}
+
+// Verifies that we cannot do the same cross-domain combo when we lack
+// the domain mapping.
+TEST_F(CssCombineFilterTest, CrossUnmappedDomain) {
+ CssLink::Vector css_in, css_out;
+ DomainLawyer* laywer = options_.domain_lawyer();
+ laywer->AddDomain("a.com", &message_handler_);
+ laywer->AddDomain("b.com", &message_handler_);
+ bool supply_mock = false;
+ const char kUrl1[] = "http://a.com/1.css";
+ const char kUrl2[] = "http://b.com/2.css";
+ css_in.Add(kUrl1, kYellow, "", supply_mock);
+ css_in.Add(kUrl2, kBlue, "", supply_mock);
+ SimpleMetaData default_css_header;
+ resource_manager_->SetDefaultHeaders(&kContentTypeCss, &default_css_header);
+ mock_url_fetcher_.SetResponse(kUrl1, default_css_header, kYellow);
+ mock_url_fetcher_.SetResponse(kUrl2, default_css_header, kBlue);
+ BarrierTestHelper("combine_css_with_style", css_in, &css_out);
+ EXPECT_EQ(2, css_out.size());
+ std::string actual_combination;
+ EXPECT_EQ(kUrl1, css_out[0]->url_);
+ EXPECT_EQ(kUrl2, css_out[1]->url_);
+}
+
+/*
+ TODO(jmarantz): cover intervening FLUSH
+ TODO(jmarantz): consider converting some of the existing tests to this
+ format, covering
+ IE Directive
+ @Import in any css element except the first
+ link in noscript tag
+ change in 'media'
+ incompatible domain
+ intervening inline style tag (TODO: outline first?)
+*/
+
+} // namespace
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/css_filter.cc b/trunk/src/net/instaweb/rewriter/css_filter.cc
new file mode 100644
index 0000000..17980c3
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/css_filter.cc
@@ -0,0 +1,352 @@
+/**
+ * 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/css_filter.h"
+
+#include "base/at_exit.h"
+
+#include "base/scoped_ptr.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/rewriter/public/css_minify.h"
+#include "net/instaweb/rewriter/public/output_resource.h"
+#include "net/instaweb/rewriter/public/resource.h"
+#include "net/instaweb/rewriter/public/resource_manager.h"
+#include "net/instaweb/util/public/content_type.h"
+#include "net/instaweb/util/public/google_url.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/statistics.h"
+#include "net/instaweb/util/public/string_writer.h"
+#include "net/instaweb/util/public/url_escaper.h"
+#include "net/instaweb/util/public/writer.h"
+#include "webutil/css/parser.h"
+
+namespace {
+
+base::AtExitManager* at_exit_manager = NULL;
+
+} // namespace
+
+namespace net_instaweb {
+
+namespace {
+
+const char kStylesheet[] = "stylesheet";
+
+} // namespace
+
+// Statistics variable names.
+const char CssFilter::kFilesMinified[] = "css_filter_files_minified";
+const char CssFilter::kMinifiedBytesSaved[] = "css_filter_minified_bytes_saved";
+const char CssFilter::kParseFailures[] = "css_filter_parse_failures";
+
+CssFilter::CssFilter(RewriteDriver* driver, const StringPiece& path_prefix)
+ : RewriteSingleResourceFilter(driver, path_prefix),
+ html_parse_(driver->html_parse()),
+ resource_manager_(driver->resource_manager()),
+ in_style_element_(false),
+ s_style_(html_parse_->Intern("style")),
+ s_link_(html_parse_->Intern("link")),
+ s_rel_(html_parse_->Intern("rel")),
+ s_href_(html_parse_->Intern("href")),
+ num_files_minified_(NULL),
+ minified_bytes_saved_(NULL),
+ num_parse_failures_(NULL) {
+ Statistics* stats = resource_manager_->statistics();
+ if (stats != NULL) {
+ num_files_minified_ = stats->GetVariable(CssFilter::kFilesMinified);
+ minified_bytes_saved_ = stats->GetVariable(CssFilter::kMinifiedBytesSaved);
+ num_parse_failures_ = stats->GetVariable(CssFilter::kParseFailures);
+ }
+}
+
+void CssFilter::Initialize(Statistics* statistics) {
+ if (statistics != NULL) {
+ statistics->AddVariable(CssFilter::kFilesMinified);
+ statistics->AddVariable(CssFilter::kMinifiedBytesSaved);
+ statistics->AddVariable(CssFilter::kParseFailures);
+ }
+
+ InitializeAtExitManager();
+}
+
+void CssFilter::Terminate() {
+ // Note: This is not thread-safe, but I don't believe we need it to be.
+ if (at_exit_manager != NULL) {
+ delete at_exit_manager;
+ at_exit_manager = NULL;
+ }
+}
+
+void CssFilter::InitializeAtExitManager() {
+ // Note: This is not thread-safe, but I don't believe we need it to be.
+ if (at_exit_manager == NULL) {
+ at_exit_manager = new base::AtExitManager;
+ }
+}
+
+void CssFilter::StartDocumentImpl() {
+ in_style_element_ = false;
+}
+
+void CssFilter::StartElementImpl(HtmlElement* element) {
+ // HtmlParse should not pass us elements inside a style element.
+ CHECK(!in_style_element_);
+ if (element->tag() == s_style_) {
+ in_style_element_ = true;
+ style_element_ = element;
+ style_char_node_ = NULL;
+ }
+ // We deal with <link> elements in EndElement.
+}
+
+void CssFilter::Characters(HtmlCharactersNode* characters_node) {
+ if (in_style_element_) {
+ if (style_char_node_ == NULL) {
+ style_char_node_ = characters_node;
+ } else {
+ html_parse_->ErrorHere("Multiple character nodes in style.");
+ in_style_element_ = false;
+ }
+ }
+}
+
+void CssFilter::EndElementImpl(HtmlElement* element) {
+ // Rewrite an inline style.
+ if (in_style_element_) {
+ CHECK(style_element_ == element); // HtmlParse should not pass unmatching.
+
+ if (html_parse_->IsRewritable(element) && style_char_node_ != NULL) {
+ CHECK(element == style_char_node_->parent()); // Sanity check.
+ std::string new_content;
+ if (RewriteCssText(style_char_node_->contents(), &new_content,
+ StrCat("inline CSS in ", html_parse_->url()),
+ html_parse_->message_handler())) {
+ // Note: Copy of new_content here.
+ HtmlCharactersNode* new_style_char_node =
+ html_parse_->NewCharactersNode(element, new_content);
+ html_parse_->ReplaceNode(style_char_node_, new_style_char_node);
+ }
+ }
+ in_style_element_ = false;
+
+ // Rewrite an external style.
+ } else if (element->tag() == s_link_ && html_parse_->IsRewritable(element)) {
+ StringPiece relation(element->AttributeValue(s_rel_));
+ if (relation == kStylesheet) {
+ HtmlElement::Attribute* element_href = element->FindAttribute(s_href_);
+ if (element_href != NULL) {
+ // If it has a href= attribute
+ std::string new_url;
+ if (RewriteExternalCss(element_href->value(), &new_url)) {
+ element_href->SetValue(new_url); // Update the href= attribute.
+ }
+ } else {
+ html_parse_->ErrorHere("Link element with no href.");
+ }
+ }
+ }
+}
+
+// Return value answers the question: May we rewrite?
+// If return false, out_text is undefined.
+// id should be the URL for external CSS and other identifying info
+// for inline CSS. It is used to log where the CSS parsing error was.
+bool CssFilter::RewriteCssText(const StringPiece& in_text,
+ std::string* out_text,
+ const std::string& id,
+ MessageHandler* handler) {
+ // Load stylesheet w/o expanding background attributes.
+ Css::Parser parser(in_text);
+ scoped_ptr<Css::Stylesheet> stylesheet(parser.ParseRawStylesheet());
+
+ bool ret = false;
+ if (parser.errors_seen_mask() != Css::Parser::kNoError) {
+ html_parse_->InfoHere("CSS parsing error in %s", id.c_str());
+ if (num_parse_failures_ != NULL) {
+ num_parse_failures_->Add(1);
+ }
+ } else {
+ // TODO(sligocki): Edit stylesheet.
+
+ // Re-serialize stylesheet.
+ StringWriter writer(out_text);
+ CssMinify::Stylesheet(*stylesheet, &writer, handler);
+
+ // Get signed versions so that we can subtract them.
+ int64 out_text_size = static_cast<int64>(out_text->size());
+ int64 in_text_size = static_cast<int64>(in_text.size());
+
+ // Don't rewrite if we don't make it smaller.
+ ret = (out_text_size < in_text_size);
+
+ // Don't rewrite if we blanked the CSS file! (This is a parse error)
+ // TODO(sligocki): Don't error if in_text is all whitespace.
+ if (out_text_size == 0 && in_text_size != 0) {
+ ret = false;
+ html_parse_->InfoHere("CSS parsing error in %s", id.c_str());
+ if (num_parse_failures_ != NULL) {
+ num_parse_failures_->Add(1);
+ }
+ }
+
+ // Statistics
+ if (ret && num_files_minified_ != NULL) {
+ num_files_minified_->Add(1);
+ minified_bytes_saved_->Add(in_text_size - out_text_size);
+ }
+ // TODO(sligocki): Do we want to save the AST 'stylesheet' somewhere?
+ // It currently, deletes itself at the end of the function.
+ }
+
+ return ret;
+}
+
+
+// Combine all 'original_stylesheets' (and all their sub stylescripts) into a
+// single returned stylesheet which has no @imports or returns NULL if we fail
+// to load some sub-resources.
+//
+// Note: we must cannibalize input stylesheets or we will have ownership
+// problems or a lot of deep-copying.
+Css::Stylesheet* CssFilter::CombineStylesheets(
+ std::vector<Css::Stylesheet*>* original_stylesheets) {
+ // Load all sub-stylesheets to assure that we can do the combination.
+ std::vector<Css::Stylesheet*> stylesheets;
+ std::vector<Css::Stylesheet*>::const_iterator iter;
+ for (iter = original_stylesheets->begin();
+ iter < original_stylesheets->end(); ++iter) {
+ Css::Stylesheet* stylesheet = *iter;
+ if (!LoadAllSubStylesheets(stylesheet, &stylesheets)) {
+ return NULL;
+ }
+ }
+
+ // Once all sub-stylesheets are loaded in memory, combine them.
+ Css::Stylesheet* combination = new Css::Stylesheet;
+ // TODO(sligocki): combination->rulesets().reserve(...);
+ for (std::vector<Css::Stylesheet*>::const_iterator iter = stylesheets.begin();
+ iter < stylesheets.end(); ++iter) {
+ Css::Stylesheet* stylesheet = *iter;
+ // Append all rulesets from 'stylesheet' to 'combination' ...
+ combination->mutable_rulesets().insert(
+ combination->mutable_rulesets().end(),
+ stylesheet->rulesets().begin(),
+ stylesheet->rulesets().end());
+ // ... and then clear rules from 'stylesheet' to avoid double ownership.
+ stylesheet->mutable_rulesets().clear();
+ }
+ return combination;
+}
+
+// Collect a list of all stylesheets @imported by base_stylesheet directly or
+// indirectly in the order that they will be dealt with by a CSS parser and
+// append them to vector 'all_stylesheets'.
+bool CssFilter::LoadAllSubStylesheets(
+ Css::Stylesheet* base_stylesheet,
+ std::vector<Css::Stylesheet*>* all_stylesheets) {
+ const Css::Imports& imports = base_stylesheet->imports();
+ for (Css::Imports::const_iterator iter = imports.begin();
+ iter < imports.end(); ++iter) {
+ Css::Import* import = *iter;
+ StringPiece url(import->link.utf8_data(), import->link.utf8_length());
+
+ // Fetch external stylesheet from url ...
+ Css::Stylesheet* sub_stylesheet = LoadStylesheet(url);
+ if (sub_stylesheet == NULL) {
+ html_parse_->ErrorHere("Failed to load sub-resource %s",
+ url.as_string().c_str());
+ return false;
+ }
+
+ // ... and recursively add all its sub-stylesheets (and it) to vector.
+ if (!LoadAllSubStylesheets(sub_stylesheet, all_stylesheets)) {
+ return false;
+ }
+ }
+ // Add base stylesheet after all imports have been added.
+ all_stylesheets->push_back(base_stylesheet);
+ return true;
+}
+
+
+// Read an external CSS file, rewrite it and write a new external CSS file.
+bool CssFilter::RewriteExternalCss(const StringPiece& in_url,
+ std::string* out_url) {
+ bool ret = false;
+ scoped_ptr<Resource> input_resource(CreateInputResource(in_url));
+ scoped_ptr<OutputResource> output_resource(
+ resource_manager_->CreateOutputResourceFromResource(
+ filter_prefix_, &kContentTypeCss, resource_manager_->url_escaper(),
+ input_resource.get(), html_parse_->message_handler()));
+ if (output_resource.get() != NULL &&
+ RewriteExternalCssToResource(input_resource.get(),
+ output_resource.get())) {
+ ret = true;
+ *out_url = output_resource->url();
+ }
+ return ret;
+}
+
+// Rewrite in input_resource once it has already been loaded.
+bool CssFilter::RewriteExternalCssToResource(Resource* input_resource,
+ OutputResource* output_resource) {
+ bool ret = false;
+ // If this OutputResource has not already been created, create it.
+ if (!output_resource->IsWritten()) {
+ // Load input stylesheet.
+ MessageHandler* handler = html_parse_->message_handler();
+ if (input_resource != NULL &&
+ resource_manager_->ReadIfCached(input_resource, handler)) {
+ if (input_resource->ContentsValid()) {
+ ret = RewriteLoadedResource(input_resource, output_resource);
+ } else {
+ // TODO(sligocki): Should these really be HtmlParse warnings?
+ html_parse_->WarningHere("CSS resource fetch failed: %s",
+ input_resource->url().c_str());
+ }
+ }
+ }
+
+ return ret;
+}
+
+bool CssFilter::RewriteLoadedResource(const Resource* input_resource,
+ OutputResource* output_resource) {
+ CHECK(input_resource->loaded());
+ if (input_resource->ContentsValid()) {
+ // Rewrite stylesheet.
+ StringPiece in_contents = input_resource->contents();
+ std::string out_contents;
+ if (!RewriteCssText(in_contents, &out_contents, input_resource->url(),
+ html_parse_->message_handler())) {
+ return false;
+ }
+
+ // Write new stylesheet.
+ // TODO(sligocki): Set expire time.
+ if (!resource_manager_->Write(HttpStatus::kOK, out_contents,
+ output_resource, -1,
+ html_parse_->message_handler())) {
+ return false;
+ }
+ }
+
+ return output_resource->IsWritten();
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/css_filter_test.cc b/trunk/src/net/instaweb/rewriter/css_filter_test.cc
new file mode 100644
index 0000000..fa24a1a
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/css_filter_test.cc
@@ -0,0 +1,405 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+// and sligocki@google.com (Shawn Ligocki)
+
+// Unit-test the html rewriter
+#include "base/scoped_ptr.h"
+#include "net/instaweb/rewriter/public/css_filter.h"
+#include "net/instaweb/rewriter/public/css_move_to_head_filter.h"
+#include "net/instaweb/rewriter/public/css_tag_scanner.h"
+#include "net/instaweb/rewriter/public/img_tag_scanner.h"
+#include "net/instaweb/rewriter/public/resource_manager.h"
+#include "net/instaweb/rewriter/public/resource_manager_test_base.h"
+#include "net/instaweb/rewriter/public/rewrite_options.h"
+#include "net/instaweb/util/public/simple_stats.h"
+#include <string>
+
+namespace net_instaweb {
+
+class CssFilterTest : public ResourceManagerTestBase {
+ protected:
+ CssFilterTest() {
+ RewriteDriver::Initialize(&statistics_);
+
+ num_files_minified_ = statistics_.GetVariable(CssFilter::kFilesMinified);
+ minified_bytes_saved_ =
+ statistics_.GetVariable(CssFilter::kMinifiedBytesSaved);
+ num_parse_failures_ = statistics_.GetVariable(CssFilter::kParseFailures);
+ }
+
+ virtual void SetUp() {
+ ResourceManagerTestBase::SetUp();
+ resource_manager_->set_statistics(&statistics_);
+ AddFilter(RewriteOptions::kRewriteCss);
+ }
+
+ // Check that inline CSS get's rewritten correctly.
+ void ValidateRewriteInlineCss(const StringPiece& id,
+ const StringPiece& css_input,
+ const StringPiece& expected_css_output,
+ bool expect_change,
+ bool expect_failure) {
+ static const char prefix[] =
+ "<head>\n"
+ " <title>Example style outline</title>\n"
+ " <!-- Style starts here -->\n"
+ " <style type='text/css'>";
+ static const char suffix[] = "</style>\n"
+ " <!-- Style ends here -->\n"
+ "</head>";
+
+ std::string html_input = StrCat(prefix, css_input, suffix);
+ std::string html_output = StrCat(prefix, expected_css_output, suffix);
+
+ // Reset stats
+ num_files_minified_->Set(0);
+ minified_bytes_saved_->Set(0);
+ num_parse_failures_->Set(0);
+
+ // Rewrite
+ ValidateExpected(id, html_input, html_output);
+
+ // Check stats
+ if (expect_change) {
+ EXPECT_EQ(1, num_files_minified_->Get());
+ EXPECT_EQ(css_input.size() - expected_css_output.size(),
+ minified_bytes_saved_->Get());
+ EXPECT_EQ(0, num_parse_failures_->Get());
+ } else {
+ EXPECT_EQ(0, num_files_minified_->Get());
+ EXPECT_EQ(0, minified_bytes_saved_->Get());
+ EXPECT_EQ((expect_failure ? 1 : 0), num_parse_failures_->Get()) << id;
+ }
+ }
+
+ // Check that external CSS gets rewritten correctly.
+ void ValidateRewriteExternalCss(const StringPiece& id,
+ const std::string& css_input,
+ const std::string& expected_css_output,
+ bool expect_change,
+ bool expect_failure) {
+ // TODO(sligocki): Allow arbitrary URLs.
+ std::string css_url = StrCat("http://test.com/", id, ".css");
+
+ Hasher* hasher = &mock_hasher_;
+ // TODO(sligocki): Allow testing with other hashers.
+ //resource_manager_->SetHasher(hasher);
+
+ // Set input file.
+ mock_url_fetcher_.Clear();
+ InitMetaData(StrCat(id, ".css"), kContentTypeCss, css_input, 300);
+
+ static const char html_template[] =
+ "<head>\n"
+ " <title>Example style outline</title>\n"
+ " <!-- Style starts here -->\n"
+ " <link rel='stylesheet' type='text/css' href='%s'>\n"
+ " <!-- Style ends here -->\n"
+ "</head>";
+
+ std::string html_input = StringPrintf(html_template, css_url.c_str());
+
+ std::string html_output;
+
+ ResourceNamer namer;
+ namer.set_id("cf");
+ namer.set_hash(hasher->Hash(expected_css_output));
+ namer.set_ext("css");
+ // TODO(sligocki): Derive these from css_url the "right" way.
+ std::string url_prefix = "http://test.com/";
+ namer.set_name(StrCat(id, ".css"));
+
+ std::string expected_new_url = StrCat(url_prefix, namer.Encode());
+
+ if (expect_change) {
+ html_output = StringPrintf(html_template, expected_new_url.c_str());
+ } else {
+ html_output = html_input;
+ }
+
+ // Reset stats
+ num_files_minified_->Set(0);
+ minified_bytes_saved_->Set(0);
+ num_parse_failures_->Set(0);
+
+ // Rewrite
+ ValidateExpected(id, html_input, html_output);
+
+ // Check stats
+ if (expect_change) {
+ EXPECT_EQ(1, num_files_minified_->Get());
+ EXPECT_EQ(css_input.size() - expected_css_output.size(),
+ minified_bytes_saved_->Get());
+ EXPECT_EQ(0, num_parse_failures_->Get());
+ } else {
+ EXPECT_EQ(0, num_files_minified_->Get());
+ EXPECT_EQ(0, minified_bytes_saved_->Get());
+ EXPECT_EQ((expect_failure ? 1 : 0), num_parse_failures_->Get()) << id;
+ }
+
+ // Check CSS output.
+ if (expect_change) {
+ std::string actual_output;
+ // TODO(sligocki): This will only work with mock_hasher.
+ EXPECT_TRUE(ServeResource(url_prefix,
+ namer.id(), namer.name(), namer.ext(),
+ &actual_output));
+ EXPECT_EQ(expected_css_output, actual_output);
+
+ // Serve from new context.
+ ServeResourceFromManyContexts(expected_new_url,
+ RewriteOptions::kRewriteCss,
+ hasher, expected_css_output);
+ }
+ }
+
+ void ValidateRewrite(const StringPiece& id,
+ const std::string& css_input,
+ const std::string& gold_output) {
+ ValidateRewriteInlineCss(StrCat(id, "-inline"),
+ css_input, gold_output, true, false);
+ ValidateRewriteExternalCss(StrCat(id, "-external"),
+ css_input, gold_output, true, false);
+ }
+
+ void ValidateNoChange(const StringPiece& id, const std::string& css_input) {
+ ValidateRewriteInlineCss(StrCat(id, "-inline"),
+ css_input, css_input, false, false);
+ ValidateRewriteExternalCss(StrCat(id, "-external"),
+ css_input, "", false, false);
+ }
+
+ void ValidateFailParse(const StringPiece& id, const std::string& css_input) {
+ ValidateRewriteInlineCss(StrCat(id, "-inline"),
+ css_input, css_input, false, true);
+ ValidateRewriteExternalCss(StrCat(id, "-external"),
+ css_input, "", false, true);
+ }
+
+
+ SimpleStats statistics_;
+ Variable* num_files_minified_;
+ Variable* minified_bytes_saved_;
+ Variable* num_parse_failures_;
+};
+
+
+TEST_F(CssFilterTest, SimpleRewriteCssTest) {
+ std::string input_style =
+ ".background_blue { background-color: #f00; }\n"
+ ".foreground_yellow { color: yellow; }\n";
+ std::string output_style =
+ ".background_blue{background-color:red}"
+ ".foreground_yellow{color:#ff0}";
+
+ ValidateRewrite("rewrite_css", input_style, output_style);
+}
+
+// Make sure we can deal with 0 character nodes between open and close of style.
+TEST_F(CssFilterTest, RewriteEmptyCssTest) {
+ ValidateNoChange("rewrite_empty_css", "");
+}
+
+// Make sure we don't change CSS with errors. Note: We can move these tests
+// to expected rewrites if we find safe ways to edit them.
+TEST_F(CssFilterTest, NoRewriteParseError) {
+ ValidateFailParse("non_unicode_charset",
+ "a { font-family: \"\xCB\xCE\xCC\xE5\"; }");
+ // From http://www.baidu.com/
+ ValidateFailParse("non_unicode_baidu",
+ "#lk span {font:14px \"\xCB\xCE\xCC\xE5\"}");
+ // From http://www.yahoo.com/
+ const char confusing_value[] =
+ "a { background-image:-webkit-gradient(linear, 50% 0%, 50% 100%,"
+ " from(rgb(232, 237, 240)), to(rgb(252, 252, 253)));}";
+ ValidateFailParse("non_standard_value", confusing_value);
+
+ ValidateFailParse("bad_char_in_selector", ".bold: { font-weight: bold }");
+}
+
+TEST_F(CssFilterTest, RewriteVariousCss) {
+ // Distilled examples.
+ const char* good_examples[] = {
+ "a.b #c.d e#d,f:g>h+i>j{color:red}", // .#,>+: in selectors
+ "a{border:solid 1px #ccc}", // Multiple values declaration
+ "a{border:none!important}", // !important
+ "a{background-image:url(foo.png)}", // url
+ "a{background-position:-19px 60%}", // negative position
+ "a{margin:0}", // 0 w/ no units
+ "a{padding:0.01em 0.25em}", // fractions and em
+ "a{-moz-border-radius-topleft:0}", // Browser-specific (-moz)
+ "a{background:none}", // CSS Parser used to expand this.
+ // http://code.google.com/p/modpagespeed/issues/detail?id=5
+ "a{font-family:trebuchet ms}", // Keep space between trebuchet and ms.
+ // http://code.google.com/p/modpagespeed/issues/detail?id=121
+ "a{color:inherit}",
+ };
+
+ for (int i = 0; i < arraysize(good_examples); ++i) {
+ std::string id = StringPrintf("distilled_css_good%d", i);
+ ValidateNoChange(id, good_examples[i]);
+ }
+
+ const char* fail_examples[] = {
+ // http://code.google.com/p/modpagespeed/issues/detail?id=50
+ "@media screen and (max-width:290px){a{color:red}}", // CSS3 "and (...)"
+ // http://code.google.com/p/modpagespeed/issues/detail?id=51
+ "a{box-shadow:-1px -2px 2px rgba(0, 0, 0, .15)}", // CSS3 rgba
+ // http://code.google.com/p/modpagespeed/issues/detail?id=66
+ "a{-moz-transform:rotate(7deg)}",
+ };
+
+ for (int i = 0; i < arraysize(fail_examples); ++i) {
+ std::string id = StringPrintf("distilled_css_fail%d", i);
+ ValidateFailParse(id, fail_examples[i]);
+ }
+}
+
+
+// Test more complicated CSS.
+TEST_F(CssFilterTest, ComplexCssTest) {
+ // Real-world examples. Picked out of Wikipedia's CSS.
+ const char* examples[][2] = {
+ { "#userlogin, #userloginForm {\n"
+ " border: solid 1px #cccccc;\n"
+ " padding: 1.2em;\n"
+ " float: left;\n"
+ "}\n",
+
+ "#userlogin,#userloginForm{border:solid 1px #ccc;padding:1.2em;"
+ "float:left}"},
+
+ { "h3 .editsection { font-size: 76%; font-weight: normal; }\n",
+ "h3 .editsection{font-size:76%;font-weight:normal}"},
+
+ { "div.magnify a, div.magnify img {\n"
+ " display: block;\n"
+ " border: none !important;\n"
+ " background: none !important;\n"
+ "}\n",
+
+ "div.magnify a,div.magnify img{display:block;border:none!important;"
+ "background:none!important}"},
+
+ { "#ca-watch.icon a:hover {\n"
+ " background-image: url('images/watch-icons.png?1');\n"
+ " background-position: -19px 60%;\n"
+ "}\n",
+
+ "#ca-watch.icon a:hover{background-image:url(images/watch-icons.png?1);"
+ "background-position:-19px 60%}"},
+
+ { "body {\n"
+ " background: White;\n"
+ " /*font-size: 11pt !important;*/\n"
+ " color: Black;\n"
+ " margin: 0;\n"
+ " padding: 0;\n"
+ "}\n",
+
+ "body{background:#fff;color:#000;margin:0;padding:0}"},
+
+ { ".suggestions-result{\n"
+ " color:black;\n"
+ " color:WindowText;\n"
+ " padding:0.01em 0.25em;\n"
+ "}\n",
+
+ // TODO(sligocki): Do we care about color:WindowText?
+ //".suggestions-result{color:#000;color:WindowText;padding:0.01em 0.25em}"
+
+ ".suggestions-result{color:#000;color:#000;padding:0.01em 0.25em}"},
+
+ { ".ui-corner-tl { -moz-border-radius-topleft: 0; -webkit-border-top-left"
+ "-radius: 0; }\n",
+
+ ".ui-corner-tl{-moz-border-radius-topleft:0;-webkit-border-top-left"
+ "-radius:0}"},
+
+ { ".ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li."
+ "ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { "
+ "cursor: pointer; }\n",
+
+ ".ui-tabs .ui-tabs-nav li.ui-tabs-selected a,.ui-tabs .ui-tabs-nav "
+ "li.ui-state-disabled a,.ui-tabs .ui-tabs-nav li.ui-state-processing a{"
+ "cursor:pointer}"},
+
+ // http://code.google.com/p/modpagespeed/issues/detail?id=121
+ { "body { font: 2em sans-serif; }", "body{font:2em sans-serif}" },
+ { "body { font: 0.75em sans-serif; }", "body{font:0.75em sans-serif}" },
+
+ // http://code.google.com/p/modpagespeed/issues/detail?id=128
+ { "#breadcrumbs ul { list-style-type: none; }",
+ "#breadcrumbs ul{list-style-type:none}" },
+
+ // http://code.google.com/p/modpagespeed/issues/detail?id=126
+ // Extra spaces assure that we actually rewrite the first arg even if
+ // font: is expanded by parser.
+ { ".menu { font: menu; } ", ".menu{font:menu}" },
+ };
+
+ for (int i = 0; i < arraysize(examples); ++i) {
+ std::string id = StringPrintf("complex_css%d", i);
+ ValidateRewrite(id, examples[i][0], examples[i][1]);
+ }
+
+ const char* parse_fail_examples[] = {
+ ".ui-datepicker-cover {\n"
+ " display: none; /*sorry for IE5*/\n"
+ " display/**/: block; /*sorry for IE5*/\n"
+ " position: absolute; /*must have*/\n"
+ " z-index: -1; /*must have*/\n"
+ " filter: mask(); /*must have*/\n"
+ " top: -4px; /*must have*/\n"
+ " left: -4px; /*must have*/\n"
+ " width: 200px; /*must have*/\n"
+ " height: 200px; /*must have*/\n"
+ "}\n",
+
+ // Right now we bail on parsing the above. Could probably be minified to:
+ //".ui-datepicker-cover{display:none;display/**/:block;position:absolute;"
+ //"z-index:-1;filter:mask();top:-4px;left:-4px;width:200px;height:200px}"
+ // TODO(sligocki): When this is parsed correctly, move it up to examples[][]
+
+ ".shift {\n"
+ " -moz-transform: rotate(7deg);\n"
+ " -webkit-transform: rotate(7deg);\n"
+ " -moz-transform: skew(-25deg);\n"
+ " -webkit-transform: skew(-25deg);\n"
+ " -moz-transform: scale(0.5);\n"
+ " -webkit-transform: scale(0.5);\n"
+ " -moz-transform: translate(3em, 0);\n"
+ " -webkit-transform: translate(3em, 0);\n"
+ "}\n",
+
+ // Right now we bail on parsing the above. Could probably be minified to:
+ //".shift{-moz-transform:rotate(7deg);-webkit-transform:rotate(7deg);"
+ //"-moz-transform:skew(-25deg);-webkit-transform:skew(-25deg);"
+ //"-moz-transform:scale(0.5);-webkit-transform:scale(0.5);"
+ //"-moz-transform:translate(3em,0);-webkit-transform:translate(3em,0);}"
+ // TODO(sligocki): When this is parsed correctly, move it up to examples[][]
+ };
+
+ for (int i = 0; i < arraysize(parse_fail_examples); ++i) {
+ std::string id = StringPrintf("complex_css_parse_fail%d", i);
+ ValidateFailParse(id, parse_fail_examples[i]);
+ }
+
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/css_inline_filter.cc b/trunk/src/net/instaweb/rewriter/css_inline_filter.cc
new file mode 100644
index 0000000..c6e0e40
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/css_inline_filter.cc
@@ -0,0 +1,122 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+//
+// 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: mdsteele@google.com (Matthew D. Steele)
+
+#include "net/instaweb/rewriter/public/css_inline_filter.h"
+
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "base/string_util.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/htmlparse/public/html_node.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/rewriter/public/css_tag_scanner.h"
+#include "net/instaweb/rewriter/public/resource_manager.h"
+#include "net/instaweb/rewriter/public/rewrite_driver.h"
+#include "net/instaweb/util/public/google_url.h"
+#include "net/instaweb/util/public/string_writer.h"
+
+namespace net_instaweb {
+
+CssInlineFilter::CssInlineFilter(RewriteDriver* driver)
+ : CommonFilter(driver),
+ html_parse_(driver->html_parse()),
+ href_atom_(html_parse_->Intern("href")),
+ link_atom_(html_parse_->Intern("link")),
+ media_atom_(html_parse_->Intern("media")),
+ rel_atom_(html_parse_->Intern("rel")),
+ style_atom_(html_parse_->Intern("style")),
+ size_threshold_bytes_(driver->options()->css_inline_max_bytes()) {}
+
+void CssInlineFilter::StartDocumentImpl() {
+ // TODO(sligocki): Domain lawyerify.
+ domain_ = html_parse_->gurl().host();
+}
+
+void CssInlineFilter::EndDocument() {
+ domain_.clear();
+}
+
+void CssInlineFilter::EndElementImpl(HtmlElement* element) {
+ if (element->tag() == link_atom_) {
+ const char* rel = element->AttributeValue(rel_atom_);
+ if (rel == NULL || strcmp(rel, "stylesheet")) {
+ return;
+ }
+
+ // If the link tag has a media attribute whose value isn't "all", don't
+ // inline. (Note that "all" is equivalent to having no media attribute;
+ // see http://www.w3.org/TR/html5/semantics.html#the-style-element)
+ const char* media = element->AttributeValue(media_atom_);
+ if (media != NULL && strcmp(media, "all") != 0) {
+ return;
+ }
+
+ // Get the URL where the external script is stored
+ const char* href = element->AttributeValue(href_atom_);
+ if (href == NULL) {
+ return; // We obviously can't inline if the URL isn't there.
+ }
+
+ // Make sure we're not moving across domains -- CSS can potentially contain
+ // Javascript expressions.
+ // TODO(jmaessen): Is the domain lawyer policy the appropriate one here?
+ // Or do we still have to check for strict domain equivalence?
+ // If so, add an inline-in-page policy to domainlawyer in some form,
+ // as we make a similar policy decision in js_inline_filter.
+ MessageHandler* message_handler = html_parse_->message_handler();
+ scoped_ptr<Resource> resource(CreateInputResourceAndReadIfCached(href));
+ if (resource == NULL || !resource->ContentsValid()) {
+ return;
+ }
+
+ // Check that the file is small enough to inline.
+ StringPiece contents = resource->contents();
+ if (contents.size() > size_threshold_bytes_) {
+ return;
+ }
+
+ // Absolutify the URLs in the CSS -- relative URLs will break otherwise.
+ std::string rewritten_contents;
+ StringWriter writer(&rewritten_contents);
+ std::string input_dir =
+ GoogleUrl::AllExceptLeaf(GoogleUrl::Create(resource->url()));
+ std::string base_dir = GoogleUrl::AllExceptLeaf(base_gurl());
+ bool written;
+ if (input_dir == base_dir) {
+ // We don't need to absolutify URLs if input directory is same as base.
+ written = writer.Write(contents, message_handler);
+ } else {
+ // If they are different directories, we need to absolutify.
+ // TODO(sligocki): Perhaps we should use the real CSS parser.
+ written = CssTagScanner::AbsolutifyUrls(contents, resource->url(),
+ &writer, message_handler);
+ }
+ if (!written) {
+ return;
+ }
+
+ // Inline the CSS.
+ HtmlElement* style_element =
+ html_parse_->NewElement(element->parent(), style_atom_);
+ if (html_parse_->ReplaceNode(element, style_element)) {
+ html_parse_->AppendChild(
+ style_element, html_parse_->NewCharactersNode(element,
+ rewritten_contents));
+ }
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/css_inline_filter_test.cc b/trunk/src/net/instaweb/rewriter/css_inline_filter_test.cc
new file mode 100644
index 0000000..798e4c2
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/css_inline_filter_test.cc
@@ -0,0 +1,133 @@
+/*
+ * 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: mdsteele@google.com (Matthew D. Steele)
+
+#include "net/instaweb/rewriter/public/resource_manager_test_base.h"
+
+namespace net_instaweb {
+
+namespace {
+
+class CssInlineFilterTest : public ResourceManagerTestBase {
+ protected:
+ void TestInlineCss(const std::string& html_url,
+ const std::string& css_url,
+ const std::string& other_attrs,
+ const std::string& css_original_body,
+ bool expect_inline,
+ const std::string& css_rewritten_body) {
+ AddFilter(RewriteOptions::kInlineCss);
+
+ const std::string html_input =
+ "<head>\n"
+ " <link rel=\"stylesheet\" href=\"" + css_url + "\"" +
+ (other_attrs.empty() ? "" : " " + other_attrs) + ">\n"
+ "</head>\n"
+ "<body>Hello, world!</body>\n";
+
+ // Put original CSS file into our fetcher.
+ SimpleMetaData default_css_header;
+ resource_manager_->SetDefaultHeaders(&kContentTypeCss,
+ &default_css_header);
+ mock_url_fetcher_.SetResponse(css_url, default_css_header,
+ css_original_body);
+
+ // Rewrite the HTML page.
+ ParseUrl(html_url, html_input);
+
+ const std::string expected_output =
+ (!expect_inline ? html_input :
+ "<head>\n"
+ " <style>" + css_rewritten_body + "</style>\n"
+ "</head>\n"
+ "<body>Hello, world!</body>\n");
+ EXPECT_EQ(AddHtmlBody(expected_output), output_buffer_);
+ }
+};
+
+TEST_F(CssInlineFilterTest, InlineCssSimple) {
+ const std::string css = "BODY { color: red; }\n";
+ TestInlineCss("http://www.example.com/index.html",
+ "http://www.example.com/styles.css",
+ "", css, true, css);
+}
+
+TEST_F(CssInlineFilterTest, InlineCssAbsolutifyUrls1) {
+ // CSS with a relative URL that needs to be changed:
+ const std::string css1 =
+ "BODY { background-image: url('bg.png'); }\n";
+ const std::string css2 =
+ "BODY { background-image: "
+ "url('http://www.example.com/foo/bar/bg.png'); }\n";
+ TestInlineCss("http://www.example.com/index.html",
+ "http://www.example.com/foo/bar/baz.css",
+ "", css1, true, css2);
+}
+
+TEST_F(CssInlineFilterTest, InlineCssAbsolutifyUrls2) {
+ // CSS with a relative URL, this time with ".." in it:
+ const std::string css1 =
+ "BODY { background-image: url('../quux/bg.png'); }\n";
+ const std::string css2 =
+ "BODY { background-image: "
+ "url('http://www.example.com/foo/quux/bg.png'); }\n";
+ TestInlineCss("http://www.example.com/index.html",
+ "http://www.example.com/foo/bar/baz.css",
+ "", css1, true, css2);
+}
+
+TEST_F(CssInlineFilterTest, NoAbsolutifyUrlsSameDir) {
+ const std::string css = "BODY { background-image: url('bg.png'); }\n";
+ TestInlineCss("http://www.example.com/index.html",
+ "http://www.example.com/baz.css",
+ "", css, true, css);
+}
+
+TEST_F(CssInlineFilterTest, DoNotInlineCssWithMediaAttr) {
+ const std::string css = "BODY { color: red; }\n";
+ TestInlineCss("http://www.example.com/index.html",
+ "http://www.example.com/styles.css",
+ "media=\"print\"", css, false, "");
+}
+
+TEST_F(CssInlineFilterTest, DoInlineCssWithMediaAll) {
+ const std::string css = "BODY { color: red; }\n";
+ TestInlineCss("http://www.example.com/index.html",
+ "http://www.example.com/styles.css",
+ "media=\"all\"", css, true, css);
+}
+
+TEST_F(CssInlineFilterTest, DoNotInlineCssTooBig) {
+ // CSS too large to inline:
+ const int64 length = 2 * RewriteOptions::kDefaultCssInlineMaxBytes;
+ TestInlineCss("http://www.example.com/index.html",
+ "http://www.example.com/styles.css", "",
+ ("BODY { background-image: url('" +
+ std::string(length, 'z') + ".png'); }\n"),
+ false, "");
+}
+
+TEST_F(CssInlineFilterTest, DoNotInlineCssDifferentDomain) {
+ // TODO(mdsteele): Is switching domains in fact an issue for CSS?
+ TestInlineCss("http://www.example.com/index.html",
+ "http://www.example.org/styles.css",
+ "", "BODY { color: red; }\n", false, "");
+}
+
+} // namespace
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/css_minify.cc b/trunk/src/net/instaweb/rewriter/css_minify.cc
new file mode 100644
index 0000000..af1e78f
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/css_minify.cc
@@ -0,0 +1,298 @@
+/**
+ * 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/css_minify.h"
+
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/writer.h"
+#include "webutil/css/parser.h"
+
+namespace net_instaweb {
+
+namespace {
+
+// Escape [(), \t\r\n\\'"]
+std::string CSSEscapeString(const StringPiece& src) {
+ const int dest_length = src.size() * 2 + 1; // Maximum possible expansion
+ scoped_array<char> dest(new char[dest_length]);
+
+ const char* src_end = src.data() + src.size();
+ int used = 0;
+
+ for (const char* p = src.data(); p < src_end; p++) {
+ switch (*p) {
+ case '\n': dest[used++] = '\\'; dest[used++] = 'n'; break;
+ case '\r': dest[used++] = '\\'; dest[used++] = 'r'; break;
+ case '\t': dest[used++] = '\\'; dest[used++] = 't'; break;
+ case '\"': case '\'': case '\\': case ',': case '(': case ')':
+ dest[used++] = '\\';
+ dest[used++] = *p;
+ break;
+ default: dest[used++] = *p; break;
+ }
+ }
+
+ return std::string(dest.get(), used);
+}
+
+std::string CSSEscapeString(const UnicodeText& src) {
+ return CSSEscapeString(StringPiece(src.utf8_data(), src.utf8_length()));
+}
+
+} // namespace
+
+bool CssMinify::Stylesheet(const Css::Stylesheet& stylesheet,
+ Writer* writer,
+ MessageHandler* handler) {
+ // Get an object to encapsulate writing.
+ CssMinify minifier(writer, handler);
+ minifier.Minify(stylesheet);
+ return minifier.ok_;
+}
+
+CssMinify::CssMinify(Writer* writer, MessageHandler* handler)
+ : writer_(writer), handler_(handler), ok_(true) {
+}
+
+CssMinify::~CssMinify() {
+}
+
+// Write if we have not encountered write error yet.
+void CssMinify::Write(const StringPiece& str) {
+ if (ok_) {
+ ok_ &= writer_->Write(str, handler_);
+ }
+}
+
+// Write out minified version of each element of vector using supplied function
+// seperated by sep.
+template<typename Container>
+void CssMinify::JoinMinify(const Container& container, const StringPiece& sep) {
+ JoinMinifyIter(container.begin(), container.end(), sep);
+}
+
+template<typename Iterator>
+void CssMinify::JoinMinifyIter(const Iterator& begin, const Iterator& end,
+ const StringPiece& sep) {
+ for (Iterator iter = begin; iter != end; ++iter) {
+ if (iter != begin) {
+ Write(sep);
+ }
+ Minify(**iter);
+ }
+}
+
+template<typename Container>
+void CssMinify::JoinMediaMinify(const Container& container,
+ const StringPiece& sep) {
+ for (typename Container::const_iterator iter = container.begin();
+ iter != container.end(); ++iter) {
+ if (iter != container.begin()) {
+ Write(sep);
+ }
+ Write(CSSEscapeString(*iter));
+ }
+}
+
+
+// Write the minified versions of each type. Most of these are called via
+// templated instantiations of JoinMinify (or JoinMinifyIter) so that we can
+// abstract the idea of minifying all sub-elements of a vector and joining them
+// together.
+// Adapted from webutil/css/tostring.cc
+
+void CssMinify::Minify(const Css::Stylesheet& stylesheet) {
+ // We might want to add in unnecessary newlines between rules and imports
+ // so that some readability is preserved.
+ JoinMinify(stylesheet.imports(), "");
+ JoinMinify(stylesheet.rulesets(), "");
+}
+
+void CssMinify::Minify(const Css::Import& import) {
+ Write("@import url(");
+ // TODO(sligocki): Make a URL printer method that absolutifies and prints.
+ Write(CSSEscapeString(import.link));
+ Write(") ");
+ JoinMediaMinify(import.media, ",");
+ Write(";");
+}
+
+void CssMinify::Minify(const Css::Ruleset& ruleset) {
+ if (!ruleset.media().empty()) {
+ Write("@media ");
+ JoinMediaMinify(ruleset.media(), ",");
+ Write("{");
+ }
+
+ JoinMinify(ruleset.selectors(), ",");
+ Write("{");
+ JoinMinify(ruleset.declarations(), ";");
+ Write("}");
+
+ if (!ruleset.media().empty()) {
+ Write("}");
+ }
+}
+
+void CssMinify::Minify(const Css::Selector& selector) {
+ // Note Css::Selector == std::vector<Css::SimpleSelectors*>
+ Css::Selector::const_iterator iter = selector.begin();
+ if (iter != selector.end()) {
+ bool isfirst = true;
+ Minify(**iter, isfirst);
+ ++iter;
+ JoinMinifyIter(iter, selector.end(), "");
+ }
+}
+
+void CssMinify::Minify(const Css::SimpleSelectors& sselectors, bool isfirst) {
+ if (sselectors.combinator() == Css::SimpleSelectors::CHILD) {
+ Write(">");
+ } else if (sselectors.combinator() == Css::SimpleSelectors::SIBLING) {
+ Write("+");
+ } else if (!isfirst) {
+ Write(" ");
+ }
+ // Note Css::SimpleSelectors == std::vector<Css::SimpleSelector*>
+ JoinMinify(sselectors, "");
+}
+
+void CssMinify::Minify(const Css::SimpleSelector& sselector) {
+ // SimpleSelector::ToString is already basically minified.
+ Write(sselector.ToString());
+}
+
+namespace {
+
+// TODO(sligocki): Either make this an accessible function in
+// webutil/css/tostring or specialize it for minifier.
+//
+// Note that currently the style is terrible and it will crash the program if
+// we have >= 5 args.
+std::string FontToString(const Css::Values& font_values) {
+ CHECK_LE(5U, font_values.size());
+ std::string tmp, result;
+
+ // font-style: defaults to normal
+ tmp = font_values.get(0)->ToString();
+ if (tmp != "normal") result += tmp + " ";
+ // font-variant: defaults to normal
+ tmp = font_values.get(1)->ToString();
+ if (tmp != "normal") result += tmp + " ";
+ // font-weight: defaults to normal
+ tmp = font_values.get(2)->ToString();
+ if (tmp != "normal") result += tmp + " ";
+ // font-size is required
+ result += font_values.get(3)->ToString();
+ // line-height: defaults to normal
+ tmp = font_values.get(4)->ToString();
+ if (tmp != "normal") result += "/" + tmp;
+ // font-family:
+ for (int i = 5, n = font_values.size(); i < n; ++i)
+ result += (i == 5 ? " " : ",") + font_values.get(i)->ToString();
+
+ return result;
+}
+
+} // namespace
+
+void CssMinify::Minify(const Css::Declaration& declaration) {
+ Write(declaration.prop_text());
+ Write(":");
+ switch (declaration.prop()) {
+ case Css::Property::FONT_FAMILY:
+ JoinMinify(*declaration.values(), ",");
+ break;
+ case Css::Property::FONT:
+ // font: menu special case.
+ if (declaration.values()->size() == 1) {
+ JoinMinify(*declaration.values(), " ");
+ // Normal font notation.
+ } else if (declaration.values()->size() >= 5) {
+ Write(FontToString(*declaration.values()));
+ } else {
+ handler_->Message(kError, "Unexpected number of values in "
+ "font declaration: %d",
+ static_cast<int>(declaration.values()->size()));
+ ok_ = false;
+ }
+ break;
+ default:
+ JoinMinify(*declaration.values(), " ");
+ break;
+ }
+ if (declaration.IsImportant()) {
+ Write("!important");
+ }
+}
+
+void CssMinify::Minify(const Css::Value& value) {
+ switch (value.GetLexicalUnitType()) {
+ case Css::Value::NUMBER:
+ // TODO(sligocki): Minify number
+ // TODO(sligocki): Check that exponential notation is appropriate.
+ Write(StringPrintf("%g%s",
+ value.GetFloatValue(),
+ value.GetDimensionUnitText().c_str()));
+ break;
+ case Css::Value::URI:
+ // TODO(sligocki): Make a URL printer method that absolutifies and prints.
+ Write("url(");
+ Write(CSSEscapeString(value.GetStringValue()));
+ Write(")");
+ break;
+ case Css::Value::COUNTER:
+ Write("counter(");
+ Write(value.GetParameters()->ToString());
+ Write(")");
+ break;
+ case Css::Value::FUNCTION:
+ Write(CSSEscapeString(value.GetFunctionName()));
+ Write("(");
+ Write(value.GetParameters()->ToString());
+ Write(")");
+ break;
+ case Css::Value::RECT:
+ Write("rect(");
+ Write(value.GetParameters()->ToString());
+ Write(")");
+ break;
+ case Css::Value::COLOR:
+ // TODO(sligocki): Can we assert, or might this happen in the wild?
+ CHECK(value.GetColorValue().IsDefined());
+ Write(HtmlColorUtils::MaybeConvertToCssShorthand(
+ value.GetColorValue()));
+ break;
+ case Css::Value::STRING:
+ Write("\"");
+ Write(CSSEscapeString(value.GetStringValue()));
+ Write("\"");
+ break;
+ case Css::Value::IDENT:
+ Write(CSSEscapeString(value.GetIdentifierText()));
+ break;
+ case Css::Value::UNKNOWN:
+ handler_->Message(kError, "Unknown attribute");
+ ok_ = false;
+ break;
+ case Css::Value::DEFAULT:
+ break;
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/css_move_to_head_filter.cc b/trunk/src/net/instaweb/rewriter/css_move_to_head_filter.cc
new file mode 100644
index 0000000..11994e9
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/css_move_to_head_filter.cc
@@ -0,0 +1,86 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/rewriter/public/css_move_to_head_filter.h"
+
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/util/public/statistics.h"
+
+namespace {
+
+// names for Statistics variables.
+const char kCssElements[] = "css_elements";
+
+} // namespace
+
+namespace net_instaweb {
+
+CssMoveToHeadFilter::CssMoveToHeadFilter(HtmlParse* html_parse,
+ Statistics* statistics)
+ : html_parse_(html_parse),
+ css_tag_scanner_(html_parse),
+ counter_(NULL) {
+ s_head_ = html_parse->Intern("head");
+ s_noscript_ = html_parse->Intern("noscript");
+ s_style_ = html_parse->Intern("style");
+ if (statistics != NULL) {
+ counter_ = statistics->GetVariable(kCssElements);
+ }
+}
+
+void CssMoveToHeadFilter::Initialize(Statistics* statistics) {
+ statistics->AddVariable(kCssElements);
+}
+
+void CssMoveToHeadFilter::StartDocument() {
+ head_element_ = NULL;
+ noscript_element_ = NULL;
+}
+
+void CssMoveToHeadFilter::StartElement(HtmlElement* element) {
+ if (noscript_element_ == NULL && element->tag() == s_noscript_) {
+ noscript_element_ = element; // Record top-level <noscript>.
+ }
+}
+
+void CssMoveToHeadFilter::EndElement(HtmlElement* element) {
+ if ((head_element_ == NULL) && (element->tag() == s_head_)) {
+ head_element_ = element;
+
+ } else if (element == noscript_element_) {
+ noscript_element_ = NULL; // We are exitting the top level </noscript>.
+
+ // Do not move anything out of a <noscript> element and we can only move
+ // this to <head> if <head> is still rewritable.
+ } else if (noscript_element_ == NULL && head_element_ != NULL &&
+ html_parse_->IsRewritable(head_element_)){
+ HtmlElement::Attribute* href;
+ const char* media;
+ if (element->tag() == s_style_ ||
+ css_tag_scanner_.ParseCssElement(element, &href, &media)) {
+ html_parse_->MoveCurrentInto(head_element_);
+ // TODO(sligocki): It'd be nice to have this pattern simplified.
+ if (counter_ != NULL) {
+ counter_->Add(1);
+ }
+ }
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/css_move_to_head_filter_test.cc b/trunk/src/net/instaweb/rewriter/css_move_to_head_filter_test.cc
new file mode 100644
index 0000000..015a1d9
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/css_move_to_head_filter_test.cc
@@ -0,0 +1,105 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+// Author: sligocki@google.com (Shawn Ligocki)
+
+#include "net/instaweb/rewriter/public/css_move_to_head_filter.h"
+
+#include "net/instaweb/rewriter/public/resource_manager_test_base.h"
+#include "net/instaweb/util/public/gtest.h"
+
+namespace net_instaweb {
+
+namespace {
+
+class CssMoveToHeadFilterTest : public ResourceManagerTestBase {
+ protected:
+ CssMoveToHeadFilterTest()
+ : move_to_head_filter_(rewrite_driver_.html_parse(), NULL) {
+ rewrite_driver_.html_parse()->AddFilter(&move_to_head_filter_);
+ }
+
+ CssMoveToHeadFilter move_to_head_filter_;
+};
+
+TEST_F(CssMoveToHeadFilterTest, MovesCssToHead) {
+ static const char html_input[] =
+ "<head>\n"
+ " <title>Example</title>\n"
+ "</head>\n"
+ "<body>\n"
+ " Hello,\n"
+ " <link rel='stylesheet' href='a.css' type='text/css'>" // no newline
+ "<link rel='stylesheet' href='b.css' type='text/css'>\n" // no indent
+ " <style type='text/css'>a {color: red }</style>\n"
+ " World!\n"
+ " <link rel='stylesheet' href='c.css' type='text/css'>\n"
+ "</body>\n";
+
+ static const char expected_output[] =
+ "<head>\n"
+ " <title>Example</title>\n"
+ "<link rel='stylesheet' href='a.css' type='text/css'>" // no newline
+ "<link rel='stylesheet' href='b.css' type='text/css'>" // no newline
+ "<style type='text/css'>a {color: red }</style>" // no newline
+ "<link rel='stylesheet' href='c.css' type='text/css'>" // no newline
+ "</head>\n"
+ "<body>\n"
+ " Hello,\n"
+ " \n"
+ " \n"
+ " World!\n"
+ " \n"
+ "</body>\n";
+
+ ValidateExpected("move_css_to_head", html_input, expected_output);
+}
+
+TEST_F(CssMoveToHeadFilterTest, DoesntMoveOutOfNoScript) {
+ static const char html[] =
+ "<head>\n"
+ " <title>Example</title>\n"
+ "</head>\n"
+ "<body>\n"
+ " <noscript>\n"
+ " <link rel='stylesheet' href='a.css' type='text/css'>\n"
+ " </noscript>\n"
+ "</body>\n";
+
+ ValidateNoChanges("noscript", html);
+}
+
+
+TEST_F(CssMoveToHeadFilterTest, DoesntReorderCss) {
+ static const char html[] =
+ "<head>\n"
+ " <title>Example</title>\n"
+ "</head>\n"
+ "<body>\n"
+ " <link rel='stylesheet' href='a.css' type='text/css'>\n"
+ " <link rel='stylesheet' href='b.css' type='text/css'>\n"
+ " <style type='text/css'>a { color: red }</style>\n"
+ " <link rel='stylesheet' href='d.css' type='text/css'>\n"
+ "</body>\n";
+
+ Parse("no_reorder_css", html);
+ LOG(INFO) << "output_buffer_ = " << output_buffer_;
+ size_t a_loc = output_buffer_.find("href='a.css'");
+ size_t b_loc = output_buffer_.find("href='b.css'");
+ size_t c_loc = output_buffer_.find("a { color: red }");
+ size_t d_loc = output_buffer_.find("href='d.css'");
+
+ // Make sure that all attributes are in output_buffer_ ...
+ EXPECT_NE(output_buffer_.npos, a_loc);
+ EXPECT_NE(output_buffer_.npos, b_loc);
+ EXPECT_NE(output_buffer_.npos, c_loc);
+ EXPECT_NE(output_buffer_.npos, d_loc);
+
+ // ... and that they are still in the right order (specifically, that
+ // the last link wasn't moved above the style).
+ EXPECT_LE(a_loc, b_loc);
+ EXPECT_LE(b_loc, c_loc);
+ EXPECT_LE(c_loc, d_loc);
+}
+
+} // namespace
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/css_outline_filter.cc b/trunk/src/net/instaweb/rewriter/css_outline_filter.cc
new file mode 100644
index 0000000..7c82a71
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/css_outline_filter.cc
@@ -0,0 +1,195 @@
+/**
+ * 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/css_outline_filter.h"
+
+#include "base/scoped_ptr.h"
+#include "net/instaweb/rewriter/public/css_tag_scanner.h"
+#include "net/instaweb/rewriter/public/output_resource.h"
+#include "net/instaweb/rewriter/public/resource_manager.h"
+#include "net/instaweb/rewriter/public/rewrite_driver.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/util/public/content_type.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include <string>
+#include "net/instaweb/util/public/string_writer.h"
+
+namespace net_instaweb {
+
+const char kStylesheet[] = "stylesheet";
+
+const char CssOutlineFilter::kFilterId[] = "co";
+
+CssOutlineFilter::CssOutlineFilter(RewriteDriver* driver)
+ : CommonFilter(driver),
+ inline_element_(NULL),
+ html_parse_(driver->html_parse()),
+ resource_manager_(driver->resource_manager()),
+ size_threshold_bytes_(driver->options()->css_outline_min_bytes()),
+ s_link_(html_parse_->Intern("link")),
+ s_style_(html_parse_->Intern("style")),
+ s_rel_(html_parse_->Intern("rel")),
+ s_href_(html_parse_->Intern("href")),
+ s_type_(html_parse_->Intern("type")) { }
+
+void CssOutlineFilter::StartDocumentImpl() {
+ inline_element_ = NULL;
+ buffer_.clear();
+}
+
+void CssOutlineFilter::StartElementImpl(HtmlElement* element) {
+ // No tags allowed inside style element.
+ if (inline_element_ != NULL) {
+ // TODO(sligocki): Add negative unit tests to hit these errors.
+ html_parse_->ErrorHere("Tag '%s' found inside style.",
+ element->tag().c_str());
+ inline_element_ = NULL; // Don't outline what we don't understand.
+ buffer_.clear();
+ }
+ if (element->tag() == s_style_) {
+ inline_element_ = element;
+ buffer_.clear();
+ }
+}
+
+void CssOutlineFilter::EndElementImpl(HtmlElement* element) {
+ if (inline_element_ != NULL) {
+ if (element != inline_element_) {
+ // No other tags allowed inside style element.
+ html_parse_->ErrorHere("Tag '%s' found inside style.",
+ element->tag().c_str());
+
+ } else if (buffer_.size() >= size_threshold_bytes_) {
+ OutlineStyle(inline_element_, buffer_);
+ } else {
+ html_parse_->InfoHere("Inline element not outlined because its size %d, "
+ "is below threshold %d",
+ static_cast<int>(buffer_.size()),
+ static_cast<int>(size_threshold_bytes_));
+ }
+ inline_element_ = NULL;
+ buffer_.clear();
+ }
+}
+
+void CssOutlineFilter::Flush() {
+ // If we were flushed in a style element, we cannot outline it.
+ inline_element_ = NULL;
+ buffer_.clear();
+}
+
+void CssOutlineFilter::Characters(HtmlCharactersNode* characters) {
+ if (inline_element_ != NULL) {
+ buffer_ += characters->contents();
+ }
+}
+
+void CssOutlineFilter::Comment(HtmlCommentNode* comment) {
+ if (inline_element_ != NULL) {
+ html_parse_->ErrorHere("Comment found inside style.");
+ inline_element_ = NULL; // Don't outline what we don't understand.
+ buffer_.clear();
+ }
+}
+
+void CssOutlineFilter::Cdata(HtmlCdataNode* cdata) {
+ if (inline_element_ != NULL) {
+ html_parse_->ErrorHere("CDATA found inside style.");
+ inline_element_ = NULL; // Don't outline what we don't understand.
+ buffer_.clear();
+ }
+}
+
+void CssOutlineFilter::IEDirective(HtmlIEDirectiveNode* directive) {
+ if (inline_element_ != NULL) {
+ html_parse_->ErrorHere("IE Directive found inside style.");
+ inline_element_ = NULL; // Don't outline what we don't understand.
+ buffer_.clear();
+ }
+}
+
+// Try to write content and possibly header to resource.
+bool CssOutlineFilter::WriteResource(const StringPiece& content,
+ OutputResource* resource,
+ MessageHandler* handler) {
+ // We set the TTL of the origin->hashed_name map to 0 because this is
+ // derived from the inlined HTML.
+ int64 origin_expire_time_ms = 0;
+ return resource_manager_->Write(HttpStatus::kOK, content, resource,
+ origin_expire_time_ms, handler);
+}
+
+// Create file with style content and remove that element from DOM.
+void CssOutlineFilter::OutlineStyle(HtmlElement* style_element,
+ const std::string& content_str) {
+ StringPiece content(content_str);
+ if (html_parse_->IsRewritable(style_element)) {
+ // Create style file from content.
+ const char* type = style_element->AttributeValue(s_type_);
+ // We only deal with CSS styles. If no type specified, CSS is assumed.
+ // See http://www.w3.org/TR/html5/semantics.html#the-style-element
+ if (type == NULL || strcmp(type, kContentTypeCss.mime_type()) == 0) {
+ MessageHandler* handler = html_parse_->message_handler();
+ // Create outline resource at the document location, not base URL location
+ scoped_ptr<OutputResource> output_resource(
+ resource_manager_->CreateOutputResourceWithPath(
+ GoogleUrl::AllExceptLeaf(html_parse_->gurl()),
+ kFilterId, "_",
+ &kContentTypeCss, handler));
+
+ // Absolutify URLs in content.
+ std::string absolute_content;
+ StringWriter absolute_writer(&absolute_content);
+ std::string base_dir = GoogleUrl::AllExceptLeaf(base_gurl());
+ bool content_valid = true;
+ if (base_dir != output_resource->resolved_base()) {
+ // TODO(sligocki): Use CssParser instead of CssTagScanner hack.
+ content_valid = CssTagScanner::AbsolutifyUrls(
+ content, GoogleUrl::Spec(base_gurl()), &absolute_writer, handler);
+ content = absolute_content; // StringPiece point to the new string.
+
+ }
+ if (content_valid &&
+ WriteResource(content, output_resource.get(), handler)) {
+ HtmlElement* link_element = html_parse_->NewElement(
+ style_element->parent(), s_link_);
+ link_element->AddAttribute(s_rel_, kStylesheet, "'");
+ link_element->AddAttribute(s_href_, output_resource->url(), "'");
+ // Add all style atrributes to link.
+ for (int i = 0; i < style_element->attribute_size(); ++i) {
+ const HtmlElement::Attribute& attr = style_element->attribute(i);
+ link_element->AddAttribute(attr);
+ }
+ // Add link to DOM.
+ html_parse_->InsertElementAfterElement(style_element, link_element);
+ // Remove style element from DOM.
+ if (!html_parse_->DeleteElement(style_element)) {
+ html_parse_->FatalErrorHere("Failed to delete inline sytle element");
+ }
+ }
+ } else {
+ std::string element_string;
+ style_element->ToString(&element_string);
+ html_parse_->InfoHere("Cannot outline non-css stylesheet %s",
+ element_string.c_str());
+ }
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/css_outline_filter_test.cc b/trunk/src/net/instaweb/rewriter/css_outline_filter_test.cc
new file mode 100644
index 0000000..77e8fff
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/css_outline_filter_test.cc
@@ -0,0 +1,128 @@
+/*
+ * 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/css_outline_filter.h"
+
+#include "net/instaweb/rewriter/public/resource_manager_test_base.h"
+
+namespace net_instaweb {
+
+namespace {
+
+class CssOutlineFilterTest : public ResourceManagerTestBase {
+ protected:
+ // Test general situations.
+ void TestOutlineCss(const std::string& html_url,
+ const std::string& other_content, // E.g. <base href>
+ const std::string& css_original_body,
+ bool expect_outline,
+ const std::string& css_rewritten_body) {
+ // TODO(sligocki): Test with outline threshold > 0.
+ options_.set_css_outline_min_bytes(0);
+ AddFilter(RewriteOptions::kOutlineCss);
+
+ // Figure out outline_url.
+ std::string hash = resource_manager_->hasher()->Hash(css_rewritten_body);
+ GURL html_gurl(html_url);
+ GURL outline_gurl = html_gurl.Resolve(
+ Encode("", CssOutlineFilter::kFilterId, hash, "_", "css"));
+ std::string outline_url = GoogleUrl::Spec(outline_gurl);
+ // Figure out outline_filename.
+ std::string outline_filename;
+ filename_encoder_.Encode(file_prefix_, outline_url, &outline_filename);
+ // Make sure the file we check later was written this time, rm any old one.
+ DeleteFileIfExists(outline_filename);
+
+ const std::string html_input =
+ "<head>\n" +
+ other_content +
+ " <style>" + css_original_body + "</style>\n"
+ "</head>\n"
+ "<body>Hello, world!</body>\n";
+
+ // Rewrite the HTML page.
+ ParseUrl(html_url, html_input);
+
+ // Check output HTML.
+ const std::string expected_output =
+ (!expect_outline ? html_input :
+ "<head>\n" +
+ other_content +
+ " <link rel='stylesheet' href='" + outline_url + "'>\n"
+ "</head>\n"
+ "<body>Hello, world!</body>\n");
+ EXPECT_EQ(AddHtmlBody(expected_output), output_buffer_);
+
+ // Expected headers.
+ std::string expected_headers;
+ AppendDefaultHeaders(kContentTypeCss, resource_manager_, &expected_headers);
+
+ // Check file was written.
+ // TODO(sligocki): Should we check this or only the fetch below?
+ std::string actual_outline;
+ ASSERT_TRUE(file_system_.ReadFile(outline_filename.c_str(),
+ &actual_outline,
+ &message_handler_));
+ EXPECT_EQ(expected_headers + css_rewritten_body, actual_outline);
+
+ // Check fetched resource.
+ actual_outline.clear();
+ EXPECT_TRUE(ServeResourceUrl(outline_url, &actual_outline));
+ // TODO(sligocki): Check headers?
+ EXPECT_EQ(css_rewritten_body, actual_outline);
+ }
+
+ // Test with different hashers for a specific situation.
+ void OutlineStyle(const StringPiece& id, Hasher* hasher) {
+ resource_manager_->set_hasher(hasher);
+
+ std::string html_url = StrCat("http://outline_style.test/", id, ".html");
+ std::string style_text = "background_blue { background-color: blue; }\n"
+ "foreground_yellow { color: yellow; }\n";
+ TestOutlineCss(html_url, "", style_text, true, style_text);
+ }
+};
+
+// Tests for Outlining styles.
+TEST_F(CssOutlineFilterTest, OutlineStyle) {
+ OutlineStyle("outline_styles_no_hash", &mock_hasher_);
+}
+
+TEST_F(CssOutlineFilterTest, OutlineStyleMD5) {
+ OutlineStyle("outline_styles_md5", &md5_hasher_);
+}
+
+
+TEST_F(CssOutlineFilterTest, NoAbsolutifySameDir) {
+ const std::string css = "body { background-image: url('bg.png'); }";
+ TestOutlineCss("http://outline_style.test/index.html", "",
+ css, true, css);
+}
+
+TEST_F(CssOutlineFilterTest, AbsolutifyDifferentDir) {
+ const std::string css1 = "body { background-image: url('bg.png'); }";
+ const std::string css2 =
+ "body { background-image: url('http://other_site.test/foo/bg.png'); }";
+ TestOutlineCss("http://outline_style.test/index.html",
+ " <base href='http://other_site.test/foo/'>\n",
+ css1, true, css2);
+}
+
+} // namespace
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/css_tag_scanner.cc b/trunk/src/net/instaweb/rewriter/css_tag_scanner.cc
new file mode 100644
index 0000000..e8d08b0
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/css_tag_scanner.cc
@@ -0,0 +1,201 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/rewriter/public/css_tag_scanner.h"
+
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/util/public/google_url.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include <string>
+#include "net/instaweb/util/public/writer.h"
+
+namespace {
+const char kStylesheet[] = "stylesheet";
+const char kTextCss[] = "text/css";
+}
+
+namespace net_instaweb {
+
+// Finds CSS files and calls another filter.
+CssTagScanner::CssTagScanner(HtmlParse* html_parse) {
+ s_link_ = html_parse->Intern("link");
+ s_href_ = html_parse->Intern("href");
+ s_type_ = html_parse->Intern("type");
+ s_rel_ = html_parse->Intern("rel");
+ s_media_ = html_parse->Intern("media");
+}
+
+// TODO(jmarantz): add test for this method to css_tag_scanner_test.cc
+bool CssTagScanner::ParseCssElement(
+ HtmlElement* element, HtmlElement::Attribute** href, const char** media) {
+ int num_required_attributes_found = 0;
+ *media = "";
+ *href = NULL;
+ if (element->tag() == s_link_) {
+ // We must have all attributes rel='stylesheet' href='name.css', and
+ // type='text/css', although they can be in any order. If there are,
+ // other attributes, we better learn about them so we don't lose them
+ // in css_combine_filter.cc.
+ int num_attrs = element->attribute_size();
+
+ // 'media=' is optional, but our filter requires href=*, and rel=stylesheet,
+ // and type=text/css.
+ //
+ // type should be "text/css", but if it's omitted, that's OK.
+ //
+ // TODO(jmarantz): Consider recognizing a wider variety of CSS references,
+ // including inline css so that the outline_filter can use it.
+ if ((num_attrs >= 2) || (num_attrs <= 4)) {
+ for (int i = 0; i < num_attrs; ++i) {
+ HtmlElement::Attribute& attr = element->attribute(i);
+ if (attr.name() == s_href_) {
+ *href = &attr;
+ ++num_required_attributes_found;
+ } else if (attr.name() == s_rel_) {
+ if (strcasecmp(attr.value(), kStylesheet) == 0) {
+ ++num_required_attributes_found;
+ } else {
+ // rel=something_else. abort.
+ num_required_attributes_found = 0;
+ break;
+ }
+ } else if (attr.name() == s_media_) {
+ *media = attr.value();
+ } else {
+ // The only other attribute we should see is type=text/css. This
+ // attribute is not required, but if the attribute we are
+ // finding here is anything else then abort.
+ if ((attr.name() != s_type_) ||
+ (strcasecmp(attr.value(), kTextCss) != 0)) {
+ num_required_attributes_found = 0;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // we require both 'href=...' and 'rel=stylesheet'.
+ // TODO(jmarantz): warn when CSS elements aren't quite what we expect?
+ return (num_required_attributes_found >= 2);
+}
+
+namespace {
+
+bool ExtractQuote(std::string* url, char* quote) {
+ bool ret = false;
+ int size = url->size();
+ if (size > 2) {
+ *quote = (*url)[0];
+ if (((*quote == '\'') || (*quote == '"')) && (*quote == (*url)[size - 1])) {
+ ret = true;
+ *url = url->substr(1, size - 2);
+ }
+ }
+ return ret;
+}
+
+} // namespace
+
+// TODO(jmarantz): replace this scan-and-replace-in-one-shot methdology with
+// a proper scanner/parser/filtering mechanism akin to HtmlParse/HtmlLexer.
+// See http://www.w3.org/Style/CSS/SAC/ for the C Parser.
+bool CssTagScanner::AbsolutifyUrls(
+ const StringPiece& contents, const std::string& base_url,
+ Writer* writer, MessageHandler* handler) {
+ size_t pos = 0;
+ size_t prev_pos = 0;
+ bool ok = true;
+
+ // If the CSS url was specified with an absolute path, use that to
+ // absolutify any URLs referenced in the CSS text.
+ //
+ // TODO(jmarantz): Consider pasting in any CSS resources found in an import
+ // statement, rather than merely absolutifying in the references. This would
+ // require a few changes in this class API.
+ //
+ // TODO(jmarantz): Consider calling image optimization, if enabled, on any
+ // images found.
+ GURL base_gurl(base_url);
+ if (base_gurl.is_valid()) {
+ while (ok && ((pos = contents.find("url(", pos)) != StringPiece::npos)) {
+ ok = writer->Write(contents.substr(prev_pos, pos - prev_pos), handler);
+ prev_pos = pos;
+ pos += 4;
+ size_t end_of_url = contents.find(')', pos);
+ if ((end_of_url != StringPiece::npos) && (end_of_url != pos)) {
+ std::string url;
+ TrimWhitespace(contents.substr(pos, end_of_url - pos), &url);
+ char quote;
+ bool is_quoted = ExtractQuote(&url, "e);
+ std::string url_string(url.data(), url.size());
+ GURL gurl(url_string);
+
+ // Relative paths are considered invalid by GURL, and those are the
+ // ones we need to resolve.
+ if (!gurl.is_valid()) {
+ GURL resolved = base_gurl.Resolve(url_string.c_str());
+ if (resolved.is_valid()) {
+ ok = writer->Write("url(", handler);
+ if (is_quoted) {
+ writer->Write(StringPiece("e, 1), handler);
+ }
+ ok = writer->Write(resolved.spec().c_str(), handler);
+ if (is_quoted) {
+ writer->Write(StringPiece("e, 1), handler);
+ }
+ ok = writer->Write(")", handler);
+ prev_pos = end_of_url + 1;
+ } else {
+ int line = 1;
+ for (size_t i = 0; i < pos; ++i) {
+ line += (contents[i] == '\n');
+ }
+ handler->Error(
+ base_url.c_str(), line,
+ "CSS URL resolution failed: %s", url_string.c_str());
+ }
+ }
+ }
+ }
+ }
+ if (ok) {
+ ok = writer->Write(contents.substr(prev_pos), handler);
+ }
+ return ok;
+}
+
+bool CssTagScanner::HasImport(const StringPiece& contents,
+ MessageHandler* handler) {
+ // Search for case insensitive @import.
+ size_t pos = -1; // So that pos + 1 == 0 below.
+ static const char kImport[] = "import";
+ static const size_t kImportSize = sizeof(kImport) - 1;
+ while ((pos = contents.find("@", pos + 1)) != StringPiece::npos) {
+ // Rest is everything past the @ (non-inclusive).
+ StringPiece rest = contents.substr(pos + 1);
+ if (rest.size() >= kImportSize &&
+ (strncasecmp(kImport, rest.data(), kImportSize) == 0)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/css_tag_scanner_test.cc b/trunk/src/net/instaweb/rewriter/css_tag_scanner_test.cc
new file mode 100644
index 0000000..93b4a4e
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/css_tag_scanner_test.cc
@@ -0,0 +1,212 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+// Unit-test the css filter
+
+#include "net/instaweb/rewriter/public/css_tag_scanner.h"
+
+#include <string>
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/util/public/google_message_handler.h"
+#include "net/instaweb/util/public/gtest.h"
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/string_writer.h"
+
+#include "net/instaweb/util/public/google_url.h"
+
+namespace net_instaweb {
+
+class CssTagScannerTest : public testing::Test {
+ protected:
+ CssTagScannerTest() : writer_(&output_buffer_) { }
+
+ void Check(const StringPiece& input, const StringPiece& expected) {
+ ASSERT_TRUE(CssTagScanner::AbsolutifyUrls(
+ input, "http://base/dir/styles.css", &writer_, &message_handler_));
+ EXPECT_EQ(expected, output_buffer_);
+ }
+
+ void CheckNoChange(const StringPiece& value) {
+ Check(value, value);
+ }
+
+ void CheckGurlResolve(const GURL& base, const char* relative_path,
+ const char* abs_path) {
+ GURL resolved = base.Resolve(relative_path);
+ EXPECT_TRUE(resolved.is_valid());
+ EXPECT_EQ(std::string(abs_path), resolved.spec());
+ }
+
+ std::string output_buffer_;
+ StringWriter writer_;
+ GoogleMessageHandler message_handler_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CssTagScannerTest);
+};
+
+TEST_F(CssTagScannerTest, TestAbsolutifyUrlsEmpty) {
+ CheckNoChange("");
+}
+
+TEST_F(CssTagScannerTest, TestAbsolutifyUrlsNoMatch) {
+ CheckNoChange("hello");
+}
+
+TEST_F(CssTagScannerTest, TestAbsolutifyUrlsAbsolute) {
+ const char css_with_abs_path[] = "a url(http://other_base/image.png) b";
+ CheckNoChange(css_with_abs_path);
+}
+
+TEST_F(CssTagScannerTest, TestAbsolutifyUrlsAbsoluteSQuote) {
+ const char css_with_abs_path[] = "a url('http://other_base/image.png') b";
+ CheckNoChange(css_with_abs_path);
+}
+
+TEST_F(CssTagScannerTest, TestAbsolutifyUrlsAbsoluteDQuote) {
+ CheckNoChange("a url(\"http://other_base/image.png\") b");
+}
+
+TEST_F(CssTagScannerTest, TestAbsolutifyUrlsRelative) {
+ Check("a url(subdir/image.png) b",
+ "a url(http://base/dir/subdir/image.png) b");
+}
+
+TEST_F(CssTagScannerTest, TestAbsolutifyUrlsRelativeSQuote) {
+ Check("a url('subdir/image.png') b",
+ "a url('http://base/dir/subdir/image.png') b");
+}
+
+// Testcase for Issue 60.
+TEST_F(CssTagScannerTest, TestAbsolutifyUrlsRelativeSQuoteSpaced) {
+ Check("a url( 'subdir/image.png' ) b",
+ "a url('http://base/dir/subdir/image.png') b");
+}
+
+TEST_F(CssTagScannerTest, TestAbsolutifyUrlsRelativeDQuote) {
+ Check("a url(\"subdir/image.png\") b",
+ "a url(\"http://base/dir/subdir/image.png\") b");
+}
+
+TEST_F(CssTagScannerTest, TestAbsolutifyUrls2Relative1Abs) {
+ Check("a url(s/1.png) b url(2.png) c url(http://a/3.png) d",
+ "a url(http://base/dir/s/1.png) b url(http://base/dir/2.png) c "
+ "url(http://a/3.png) d");
+}
+
+// This test verifies that we understand how GURL::Resolve works.
+TEST_F(CssTagScannerTest, TestGurl) {
+ GURL base_slash("http://base/");
+ EXPECT_TRUE(base_slash.is_valid());
+ CheckGurlResolve(base_slash, "r/path.ext", "http://base/r/path.ext");
+ CheckGurlResolve(base_slash, "/r/path.ext", "http://base/r/path.ext");
+ CheckGurlResolve(base_slash, "../r/path.ext", "http://base/r/path.ext");
+ CheckGurlResolve(base_slash, "./r/path.ext", "http://base/r/path.ext");
+
+ GURL base_no_slash("http://base");
+ EXPECT_TRUE(base_no_slash.is_valid());
+ CheckGurlResolve(base_no_slash, "r/path.ext", "http://base/r/path.ext");
+ CheckGurlResolve(base_no_slash, "/r/path.ext", "http://base/r/path.ext");
+ CheckGurlResolve(base_no_slash, "../r/path.ext", "http://base/r/path.ext");
+ CheckGurlResolve(base_no_slash, "./r/path.ext", "http://base/r/path.ext");
+}
+
+// This test makes sure we can identify a few different forms of CSS tags we've
+// seen.
+TEST_F(CssTagScannerTest, TestFull) {
+ HtmlParse html_parse(&message_handler_);
+ Atom s_link = html_parse.Intern("link");
+ Atom s_href = html_parse.Intern("href");
+ Atom s_type = html_parse.Intern("type");
+ Atom s_rel = html_parse.Intern("rel");
+ Atom s_media = html_parse.Intern("media");
+ Atom s_other = html_parse.Intern("other");
+ HtmlElement* link = html_parse.NewElement(NULL, s_link);
+ const char kUrl[] = "http://www.myhost.com/static/mycss.css";
+ const char kPrint[] = "print";
+ link->AddAttribute(s_rel, "stylesheet", "\"");
+ link->AddAttribute(s_href, kUrl, "\"");
+ HtmlElement::Attribute* href = NULL;
+ const char* media = NULL;
+ CssTagScanner scanner(&html_parse);
+
+ // We can parse css even lacking a 'type' attribute. Default to text/css.
+ EXPECT_TRUE(scanner.ParseCssElement(link, &href, &media));
+ EXPECT_EQ("", std::string(media));
+ EXPECT_EQ(kUrl, std::string(href->value()));
+
+ // Add an unexpected attribute. Now we don't know what to do with it.
+ link->AddAttribute(s_other, "value", "\"");
+ EXPECT_FALSE(scanner.ParseCssElement(link, &href, &media));
+
+ // Mutate it to the correct attribute.
+ HtmlElement::Attribute* attr = link->FindAttribute(s_other);
+ ASSERT_TRUE(attr != NULL);
+ attr->set_name(s_type);
+ attr->SetValue("text/css");
+ EXPECT_TRUE(scanner.ParseCssElement(link, &href, &media));
+ EXPECT_EQ("", std::string(media));
+ EXPECT_EQ(kUrl, std::string(href->value()));
+
+ // Add a media attribute. It should still pass, yielding media.
+ link->AddAttribute(s_media, kPrint, "\"");
+ EXPECT_TRUE(scanner.ParseCssElement(link, &href, &media));
+ EXPECT_EQ(kPrint, std::string(media));
+ EXPECT_EQ(kUrl, std::string(href->value()));
+
+ // TODO(jmarantz): test removal of 'rel' and 'href' attributes
+}
+
+TEST_F(CssTagScannerTest, TestHasImport) {
+ // Should work.
+ EXPECT_TRUE(CssTagScanner::HasImport("@import", &message_handler_));
+ EXPECT_TRUE(CssTagScanner::HasImport("@Import", &message_handler_));
+ EXPECT_TRUE(CssTagScanner::HasImport(
+ "@charset 'iso-8859-1';\n"
+ "@import url('http://foo.com');\n", &message_handler_));
+ EXPECT_TRUE(CssTagScanner::HasImport(
+ "@charset 'iso-8859-1';\n"
+ "@iMPorT url('http://foo.com');\n", &message_handler_));
+
+ // Should fail.
+ EXPECT_FALSE(CssTagScanner::HasImport("", &message_handler_));
+ EXPECT_FALSE(CssTagScanner::HasImport("@impor", &message_handler_));
+ EXPECT_FALSE(CssTagScanner::HasImport(
+ "@charset 'iso-8859-1';\n"
+ "@impor", &message_handler_));
+ // Make sure we aren't overflowing the buffer.
+ std::string import_string = "@import";
+ StringPiece truncated_import(import_string.data(), import_string.size() - 1);
+ EXPECT_FALSE(CssTagScanner::HasImport(truncated_import, &message_handler_));
+
+ // False positives.
+ EXPECT_TRUE(CssTagScanner::HasImport(
+ "@charset 'iso-8859-1';\n"
+ "@importinvalid url('http://foo.com');\n", &message_handler_));
+ EXPECT_TRUE(CssTagScanner::HasImport(
+ "@charset 'iso-8859-1';\n"
+ "/* @import url('http://foo.com'); */\n", &message_handler_));
+ EXPECT_TRUE(CssTagScanner::HasImport(
+ "@charset 'iso-8859-1';\n"
+ "a { color: pink; }\n"
+ "/* @import after rulesets is invalid */\n"
+ "@import url('http://foo.com');\n", &message_handler_));
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/data_url_input_resource.cc b/trunk/src/net/instaweb/rewriter/data_url_input_resource.cc
new file mode 100644
index 0000000..484c5d7
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/data_url_input_resource.cc
@@ -0,0 +1,42 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#include "net/instaweb/rewriter/public/data_url_input_resource.h"
+
+#include "net/instaweb/rewriter/public/resource_manager.h"
+#include "net/instaweb/util/public/data_url.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include "net/instaweb/util/public/url_async_fetcher.h"
+
+namespace net_instaweb {
+
+bool DataUrlInputResource::Load(MessageHandler* message_handler) {
+ if (DecodeDataUrlContent(encoding_, encoded_contents_,
+ &decoded_contents_) &&
+ value_.Write(decoded_contents_, message_handler)) {
+ resource_manager_->SetDefaultHeaders(type_, &meta_data_);
+ value_.SetHeaders(meta_data_);
+ }
+ return loaded();
+}
+
+bool DataUrlInputResource::IsCacheable() const {
+ return false;
+}
+
+}
diff --git a/trunk/src/net/instaweb/rewriter/domain_lawyer.cc b/trunk/src/net/instaweb/rewriter/domain_lawyer.cc
new file mode 100644
index 0000000..6874c19
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/domain_lawyer.cc
@@ -0,0 +1,286 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/rewriter/public/domain_lawyer.h"
+
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/stl_util.h"
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/wildcard.h"
+
+namespace net_instaweb {
+
+class DomainLawyer::Domain {
+ public:
+ explicit Domain(const StringPiece& name)
+ : wildcard_(name),
+ name_(name.data(), name.size()),
+ num_shards_(0),
+ rewrite_domain_(NULL),
+ origin_domain_(NULL) {
+ }
+
+ bool IsWildcarded() const { return !wildcard_.IsSimple(); }
+ bool Match(const StringPiece& domain) { return wildcard_.Match(domain); }
+ Domain* rewrite_domain() const { return rewrite_domain_; }
+ Domain* origin_domain() const { return origin_domain_; }
+ StringPiece name() const { return name_; }
+
+ // TODO(jmarantz): check for cycles
+ void set_rewrite_domain(Domain* x) { rewrite_domain_ = x; }
+ void set_origin_domain(Domain* x) { origin_domain_ = x; }
+
+ private:
+ Wildcard wildcard_;
+ std::string name_;
+ int num_shards_;
+
+ // The rewrite_domain, if non-null, gives the location of where this
+ // Domain should be rewritten. This can be used to move resources onto
+ // a CDN or onto a cookieless domain.
+ Domain* rewrite_domain_;
+
+ // The origin_domain, if non-null, gives the location of where
+ // resources should be fetched from by mod_pagespeed, in lieu of how
+ // it is specified in the HTML. This allows, for example, a CDN to
+ // fetch content from an origin domain, or an origin server behind a
+ // load-balancer to specify localhost or an IP address of a host to
+ // go to directly, skipping DNS resolution and reducing outbound
+ // traffic.
+ Domain* origin_domain_;
+};
+
+DomainLawyer::~DomainLawyer() {
+ STLDeleteValues(&domain_map_);
+}
+
+bool DomainLawyer::AddDomain(const StringPiece& domain_name,
+ MessageHandler* handler) {
+ return (AddDomainHelper(domain_name, true, handler) != NULL);
+}
+
+DomainLawyer::Domain* DomainLawyer::AddDomainHelper(
+ const StringPiece& domain_name, bool warn_on_duplicate,
+ MessageHandler* handler) {
+ if (domain_name.empty()) {
+ // handler will be NULL only when called from Merge, which should
+ // only have pre-validated (non-empty) domains. So it should not
+ // be possible to get here from Merge.
+ if (handler != NULL) {
+ handler->Message(kWarning, "Empty domain passed to AddDomain");
+ }
+ return NULL;
+ }
+
+ // Ensure that the following specifications are treated identically:
+ // www.google.com
+ // http://www.google.com
+ // www.google.com/
+ // http://www.google.com/
+ // all come out the same.
+ std::string domain_name_str;
+ if (domain_name.find("://") == std::string::npos) {
+ domain_name_str = StrCat("http://", domain_name);
+ } else {
+ domain_name.CopyToString(&domain_name_str);
+ }
+ EnsureEndsInSlash(&domain_name_str);
+ Domain* domain = NULL;
+ std::pair<DomainMap::iterator, bool> p = domain_map_.insert(
+ DomainMap::value_type(domain_name_str, domain));
+ DomainMap::iterator iter = p.first;
+ if (p.second) {
+ domain = new Domain(domain_name_str);
+ iter->second = domain;
+ if (domain->IsWildcarded()) {
+ wildcarded_domains_.push_back(domain);
+ }
+ iter->second = domain;
+ } else if (warn_on_duplicate) {
+ handler->Message(kWarning, "AddDomain of domain already in map: %s",
+ domain_name_str.c_str());
+ } else {
+ domain = iter->second;
+ }
+ return domain;
+}
+
+// Looks up the Domain* object by name. From the Domain object
+// we can tell if it's wildcarded, in which case it cannot be
+// the 'to' field for a map, and whether resources from it should
+// be mapped to a different domain, either for rewriting or for
+// fetching.
+DomainLawyer::Domain* DomainLawyer::FindDomain(
+ const std::string& domain_name) const {
+ DomainMap::const_iterator p = domain_map_.find(domain_name);
+ Domain* domain = NULL;
+ if (p != domain_map_.end()) {
+ domain = p->second;
+ } else {
+ // TODO(jmarantz): use a better lookup structure for this
+ for (int i = 0, n = wildcarded_domains_.size(); i < n; ++i) {
+ domain = wildcarded_domains_[i];
+ if (domain->Match(domain_name)) {
+ break;
+ } else {
+ domain = NULL;
+ }
+ }
+ }
+ return domain;
+}
+
+bool DomainLawyer::MapRequestToDomain(
+ const GURL& original_request,
+ const StringPiece& resource_url, // relative to original_request
+ std::string* mapped_domain_name,
+ GURL* resolved_request,
+ MessageHandler* handler) const {
+ CHECK(original_request.is_valid());
+ GURL original_origin = original_request.GetOrigin();
+ *resolved_request = GoogleUrl::Resolve(original_request, resource_url);
+ bool ret = false;
+ // At present we're not sure about appropriate resource
+ // policies for https: etc., so we only permit http resources
+ // to be rewritten.
+ // TODO(jmaessen): Figure out if this is appropriate.
+ if (resolved_request->is_valid() && resolved_request->SchemeIs("http")) {
+ GURL resolved_origin = resolved_request->GetOrigin();
+ std::string resolved_domain_name = GoogleUrl::Spec(resolved_origin);
+
+ // Looks at the resovled domain name from the original request and
+ // the resource_url (which might override the original request).
+ // Gets the Domain* object out of that.
+ Domain* resolved_domain = FindDomain(resolved_domain_name);
+
+ // The origin domain is authorized by default.
+ if ((resolved_origin == original_origin) || (resolved_domain != NULL)) {
+ *mapped_domain_name = resolved_domain_name;
+ ret = true;
+
+ // If we actually got a Domain* out of the lookups so far, then a
+ // mapping to a different rewrite_domain may be contained there. This
+ // helps move resources to CDNs or cookieless domains.
+ //
+ // Note that at this point, we are not really caring where we fetch
+ // from. We are only concerned here with what URLs we will write into
+ // HTML files. See MapOrigin below which is used to redirect fetch
+ // requests to a different domain (e.g. localhost).
+ if (resolved_domain != NULL) {
+ Domain* mapped_domain = resolved_domain->rewrite_domain();
+ if (mapped_domain != NULL) {
+ CHECK(!mapped_domain->IsWildcarded());
+ mapped_domain->name().CopyToString(mapped_domain_name);
+ *resolved_request = GoogleUrl::Create(*mapped_domain_name).Resolve(
+ GoogleUrl::PathAndLeaf(*resolved_request));
+ }
+ }
+ }
+ }
+ return ret;
+}
+
+bool DomainLawyer::MapOrigin(const StringPiece& in, std::string* out) const {
+ bool ret = false;
+ GURL gurl = GoogleUrl::Create(in);
+ // At present we're not sure about appropriate resource
+ // policies for https: etc., so we only permit http resources
+ // to be rewritten.
+ if (gurl.is_valid() && gurl.SchemeIs("http")) {
+ ret = true;
+ in.CopyToString(out);
+ GURL origin = gurl.GetOrigin();
+ std::string origin_name = GoogleUrl::Spec(origin);
+ Domain* domain = FindDomain(origin_name);
+ if (domain != NULL) {
+ Domain* origin_domain = domain->origin_domain();
+ if (origin_domain != NULL) {
+ CHECK(!origin_domain->IsWildcarded());
+ GURL mapped_gurl = GoogleUrl::Create(origin_domain->name()).Resolve(
+ GoogleUrl::PathAndLeaf(gurl));
+ if (mapped_gurl.is_valid()) {
+ *out = GoogleUrl::Spec(mapped_gurl);
+ }
+ }
+ }
+ }
+ return ret;
+}
+
+bool DomainLawyer::AddRewriteDomainMapping(
+ const StringPiece& to_domain_name,
+ const StringPiece& comma_separated_from_domains,
+ MessageHandler* handler) {
+ return MapDomainHelper(to_domain_name, comma_separated_from_domains,
+ &Domain::set_rewrite_domain, handler);
+}
+
+bool DomainLawyer::AddOriginDomainMapping(
+ const StringPiece& to_domain_name,
+ const StringPiece& comma_separated_from_domains,
+ MessageHandler* handler) {
+ return MapDomainHelper(to_domain_name, comma_separated_from_domains,
+ &Domain::set_origin_domain, handler);
+}
+
+bool DomainLawyer::MapDomainHelper(
+ const StringPiece& to_domain_name,
+ const StringPiece& comma_separated_from_domains,
+ SetDomainFn set_domain_fn,
+ MessageHandler* handler) {
+ Domain* to_domain = AddDomainHelper(to_domain_name, false, handler);
+ bool ret = false;
+ if (to_domain->IsWildcarded()) {
+ handler->Message(kError, "Cannot map to a wildcarded domain: %s",
+ to_domain_name.as_string().c_str());
+ } else if (to_domain != NULL) {
+ std::vector<StringPiece> domains;
+ SplitStringPieceToVector(comma_separated_from_domains, ",", &domains, true);
+ for (int i = 0, n = domains.size(); i < n; ++i) {
+ const StringPiece& domain_name = domains[i];
+ Domain* from_domain = AddDomainHelper(domain_name, false, handler);
+ if (from_domain != NULL) {
+ (from_domain->*set_domain_fn)(to_domain);
+ ret = true;
+ }
+ }
+ }
+ return ret;
+}
+
+void DomainLawyer::Merge(const DomainLawyer& src) {
+ for (DomainMap::const_iterator
+ p = src.domain_map_.begin(),
+ e = src.domain_map_.end();
+ p != e; ++p) {
+ Domain* src_domain = p->second;
+ Domain* dst_domain = AddDomainHelper(src_domain->name(), false, NULL);
+ Domain* src_rewrite_domain = src_domain->rewrite_domain();
+ if (src_rewrite_domain != NULL) {
+ dst_domain->set_rewrite_domain(
+ AddDomainHelper(src_rewrite_domain->name(), false, NULL));
+ }
+ Domain* src_origin_domain = src_domain->origin_domain();
+ if (src_origin_domain != NULL) {
+ dst_domain->set_origin_domain(
+ AddDomainHelper(src_origin_domain->name(), false, NULL));
+ }
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/domain_lawyer_test.cc b/trunk/src/net/instaweb/rewriter/domain_lawyer_test.cc
new file mode 100644
index 0000000..87ed189
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/domain_lawyer_test.cc
@@ -0,0 +1,337 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/rewriter/public/domain_lawyer.h"
+#include "net/instaweb/util/public/google_message_handler.h"
+#include "net/instaweb/util/public/gtest.h"
+
+namespace {
+
+const char kResourceUrl[] = "styles/style.css?appearance=reader";
+const char kCdnPrefix[] = "http://graphics8.nytimes.com/";
+const char kRequestDomain[] = "http://www.nytimes.com/";
+const char kRequestDomainPort[] = "http://www.nytimes.com:8080/";
+
+} // namespace
+
+namespace net_instaweb {
+
+class DomainLawyerTest : public testing::Test {
+ protected:
+ DomainLawyerTest()
+ : orig_request_("http://www.nytimes.com/index.html"),
+ port_request_("http://www.nytimes.com:8080/index.html"),
+ https_request_("https://www.nytimes.com/index.html") {
+ }
+
+ // Syntactic sugar to map a request.
+ bool MapRequest(const GURL& original_request,
+ const StringPiece& resource_url,
+ std::string* mapped_domain_name) {
+ GURL resolved_request;
+ return domain_lawyer_.MapRequestToDomain(
+ original_request, resource_url, mapped_domain_name, &resolved_request,
+ &message_handler_);
+ }
+
+ GURL orig_request_;
+ GURL port_request_;
+ GURL https_request_;
+ DomainLawyer domain_lawyer_;
+ GoogleMessageHandler message_handler_;
+};
+
+TEST_F(DomainLawyerTest, RelativeDomain) {
+ std::string mapped_domain_name;
+ ASSERT_TRUE(MapRequest(
+ orig_request_, kResourceUrl, &mapped_domain_name));
+ EXPECT_EQ(kRequestDomain, mapped_domain_name);
+}
+
+TEST_F(DomainLawyerTest, AbsoluteDomain) {
+ std::string mapped_domain_name;
+ ASSERT_TRUE(MapRequest(
+ orig_request_, StrCat(kRequestDomain, kResourceUrl),
+ &mapped_domain_name));
+ EXPECT_EQ(kRequestDomain, mapped_domain_name);
+}
+
+TEST_F(DomainLawyerTest, ExternalDomainNotDeclared) {
+ std::string mapped_domain_name;
+ EXPECT_FALSE(MapRequest(
+ orig_request_, StrCat(kCdnPrefix, kResourceUrl), &mapped_domain_name));
+}
+
+TEST_F(DomainLawyerTest, ExternalDomainDeclared) {
+ StringPiece cdn_domain(kCdnPrefix, sizeof(kCdnPrefix) - 1);
+ ASSERT_TRUE(domain_lawyer_.AddDomain(cdn_domain, &message_handler_));
+ std::string mapped_domain_name;
+ ASSERT_TRUE(MapRequest(
+ orig_request_, StrCat(kCdnPrefix, kResourceUrl), &mapped_domain_name));
+ EXPECT_EQ(cdn_domain, mapped_domain_name);
+
+ // Make sure that we do not allow requests when the port is present; we've
+ // only authorized origin "http://www.nytimes.com/",
+ // not "http://www.nytimes.com:8080/
+ std::string orig_cdn_domain(kCdnPrefix, sizeof(kCdnPrefix) - 2);
+ std::string port_cdn_domain(cdn_domain.data(), cdn_domain.size() - 1);
+ port_cdn_domain += ":8080/";
+ EXPECT_FALSE(MapRequest(
+ orig_request_, StrCat(port_cdn_domain, "/", kResourceUrl),
+ &mapped_domain_name));
+}
+
+TEST_F(DomainLawyerTest, ExternalDomainDeclaredWithoutScheme) {
+ StringPiece cdn_domain(kCdnPrefix, sizeof(kCdnPrefix) - 1);
+ ASSERT_TRUE(domain_lawyer_.AddDomain(kCdnPrefix + strlen("http://"),
+ &message_handler_));
+ std::string mapped_domain_name;
+ ASSERT_TRUE(MapRequest(
+ orig_request_, StrCat(kCdnPrefix, kResourceUrl), &mapped_domain_name));
+ EXPECT_EQ(cdn_domain, mapped_domain_name);
+}
+
+TEST_F(DomainLawyerTest, ExternalDomainDeclaredWithoutTrailingSlash) {
+ StringPiece cdn_domain(kCdnPrefix, sizeof(kCdnPrefix) - 1);
+ StringPiece cdn_domain_no_slash(kCdnPrefix, sizeof(kCdnPrefix) - 2);
+ ASSERT_TRUE(domain_lawyer_.AddDomain(cdn_domain_no_slash, &message_handler_));
+ std::string mapped_domain_name;
+ ASSERT_TRUE(MapRequest(
+ orig_request_, StrCat(kCdnPrefix, kResourceUrl), &mapped_domain_name));
+ EXPECT_EQ(cdn_domain, mapped_domain_name);
+}
+
+TEST_F(DomainLawyerTest, WildcardDomainDeclared) {
+ StringPiece cdn_domain(kCdnPrefix, sizeof(kCdnPrefix) - 1);
+ ASSERT_TRUE(domain_lawyer_.AddDomain("*.nytimes.com", &message_handler_));
+ std::string mapped_domain_name;
+ ASSERT_TRUE(MapRequest(
+ orig_request_, StrCat(kCdnPrefix, kResourceUrl), &mapped_domain_name));
+ EXPECT_EQ(cdn_domain, mapped_domain_name);
+}
+
+TEST_F(DomainLawyerTest, RelativeDomainPort) {
+ std::string mapped_domain_name;
+ ASSERT_TRUE(MapRequest(
+ port_request_, kResourceUrl, &mapped_domain_name));
+ EXPECT_EQ(kRequestDomainPort, mapped_domain_name);
+}
+
+TEST_F(DomainLawyerTest, AbsoluteDomainPort) {
+ std::string mapped_domain_name;
+ ASSERT_TRUE(MapRequest(
+ port_request_, StrCat(kRequestDomainPort, kResourceUrl),
+ &mapped_domain_name));
+ EXPECT_EQ(kRequestDomainPort, mapped_domain_name);
+}
+
+TEST_F(DomainLawyerTest, PortExternalDomainNotDeclared) {
+ std::string mapped_domain_name;
+ EXPECT_FALSE(MapRequest(
+ port_request_, StrCat(kCdnPrefix, kResourceUrl), &mapped_domain_name));
+}
+
+TEST_F(DomainLawyerTest, PortExternalDomainDeclared) {
+ std::string port_cdn_domain(kCdnPrefix, sizeof(kCdnPrefix) - 2);
+ port_cdn_domain += ":8080/";
+ ASSERT_TRUE(domain_lawyer_.AddDomain(port_cdn_domain, &message_handler_));
+ std::string mapped_domain_name;
+ ASSERT_TRUE(MapRequest(
+ port_request_, StrCat(port_cdn_domain, kResourceUrl),
+ &mapped_domain_name));
+ EXPECT_EQ(port_cdn_domain, mapped_domain_name);
+
+ // Make sure that we do not allow requests when the port is missing; we've
+ // only authorized origin "http://www.nytimes.com:8080/",
+ // not "http://www.nytimes.com:8080
+ std::string orig_cdn_domain(kCdnPrefix, sizeof(kCdnPrefix) - 2);
+ orig_cdn_domain += "/";
+ EXPECT_FALSE(MapRequest(
+ port_request_, StrCat(orig_cdn_domain, kResourceUrl),
+ &mapped_domain_name));
+}
+
+TEST_F(DomainLawyerTest, PortWildcardDomainDeclared) {
+ std::string port_cdn_domain(kCdnPrefix, sizeof(kCdnPrefix) - 2);
+ port_cdn_domain += ":8080/";
+ ASSERT_TRUE(domain_lawyer_.AddDomain("*.nytimes.com:*", &message_handler_));
+ std::string mapped_domain_name;
+ ASSERT_TRUE(MapRequest(
+ port_request_, StrCat(port_cdn_domain, kResourceUrl),
+ &mapped_domain_name));
+ EXPECT_EQ(port_cdn_domain, mapped_domain_name);
+}
+
+TEST_F(DomainLawyerTest, ResourceFromHttpsPage) {
+ ASSERT_TRUE(domain_lawyer_.AddDomain("www.nytimes.com", &message_handler_));
+ std::string mapped_domain_name;
+
+ // When a relative resource is requested from an https page we will fail.
+ ASSERT_FALSE(MapRequest(
+ https_request_, kResourceUrl,
+ &mapped_domain_name));
+ ASSERT_TRUE(MapRequest(
+ https_request_, StrCat(kRequestDomain, kResourceUrl),
+ &mapped_domain_name));
+}
+
+TEST_F(DomainLawyerTest, AddDomainRedundantly) {
+ ASSERT_TRUE(domain_lawyer_.AddDomain("www.nytimes.com", &message_handler_));
+ ASSERT_FALSE(domain_lawyer_.AddDomain("www.nytimes.com", &message_handler_));
+ ASSERT_TRUE(domain_lawyer_.AddDomain("*", &message_handler_));
+ ASSERT_FALSE(domain_lawyer_.AddDomain("*", &message_handler_));
+}
+
+TEST_F(DomainLawyerTest, VerifyPortIsDistinct1) {
+ ASSERT_TRUE(domain_lawyer_.AddDomain("www.example.com", &message_handler_));
+ std::string mapped_domain_name;
+ EXPECT_FALSE(MapRequest(
+ GURL("http://www.other.com/index.html"),
+ "http://www.example.com:81/styles.css",
+ &mapped_domain_name));
+}
+
+TEST_F(DomainLawyerTest, VerifyPortIsDistinct2) {
+ ASSERT_TRUE(domain_lawyer_.AddDomain("www.example.com:81", &message_handler_));
+ std::string mapped_domain_name;
+ EXPECT_FALSE(MapRequest(
+ GURL("http://www.other.com/index.html"),
+ "http://www.example.com/styles.css",
+ &mapped_domain_name));
+}
+
+TEST_F(DomainLawyerTest, VerifyWildcardedPortSpec) {
+ ASSERT_TRUE(domain_lawyer_.AddDomain("www.example.com*", &message_handler_));
+ std::string mapped_domain_name;
+ EXPECT_TRUE(MapRequest(
+ GURL("http://www.other.com/index.html"),
+ "http://www.example.com/styles.css",
+ &mapped_domain_name));
+ EXPECT_TRUE(MapRequest(
+ GURL("http://www.other.com/index.html"),
+ "http://www.example.com:81/styles.css",
+ &mapped_domain_name));
+}
+
+TEST_F(DomainLawyerTest, MapRewriteDomain) {
+ ASSERT_TRUE(domain_lawyer_.AddDomain("http://cdn.com/", &message_handler_));
+ ASSERT_TRUE(domain_lawyer_.AddDomain("http://origin.com/",
+ &message_handler_));
+ ASSERT_TRUE(domain_lawyer_.AddRewriteDomainMapping("http://cdn.com",
+ "http://origin.com",
+ &message_handler_));
+ // First try the mapping from origin.com to cdn.com
+ std::string mapped_domain_name;
+ ASSERT_TRUE(MapRequest(
+ GoogleUrl::Create(StringPiece("http://www.origin.com/index.html")),
+ "http://origin.com/styles/blue.css",
+ &mapped_domain_name));
+ EXPECT_EQ("http://cdn.com/", mapped_domain_name);
+
+ // But a relative reference will not map because we mapped origin.com,
+ // not www.origin.com
+ ASSERT_TRUE(MapRequest(
+ GoogleUrl::Create(StringPiece("http://www.origin.com/index.html")),
+ "styles/blue.css",
+ &mapped_domain_name));
+ EXPECT_EQ("http://www.origin.com/", mapped_domain_name);
+
+ // Now add the mapping from www.
+ ASSERT_TRUE(domain_lawyer_.AddRewriteDomainMapping("http://cdn.com",
+ "http://www.origin.com",
+ &message_handler_));
+
+ ASSERT_TRUE(MapRequest(
+ GoogleUrl::Create(StringPiece("http://www.origin.com/index.html")),
+ "styles/blue.css",
+ &mapped_domain_name));
+ EXPECT_EQ("http://cdn.com/", mapped_domain_name);
+}
+
+TEST_F(DomainLawyerTest, MapOriginDomain) {
+ ASSERT_TRUE(domain_lawyer_.AddOriginDomainMapping(
+ "http://localhost:8080", "http://origin.com:8080", &message_handler_));
+ std::string mapped;
+ ASSERT_TRUE(domain_lawyer_.MapOrigin("http://origin.com:8080/a/b/c?d=f",
+ &mapped));
+ EXPECT_EQ("http://localhost:8080/a/b/c?d=f", mapped);
+}
+
+TEST_F(DomainLawyerTest, Merge) {
+ // Add some mappings for domain_lawywer_.
+ ASSERT_TRUE(domain_lawyer_.AddDomain("http://d1.com/", &message_handler_));
+ ASSERT_TRUE(domain_lawyer_.AddRewriteDomainMapping(
+ "http://cdn1.com", "http://www.o1.com", &message_handler_));
+ ASSERT_TRUE(domain_lawyer_.AddOriginDomainMapping(
+ "http://localhost:8080", "http://o1.com:8080", &message_handler_));
+
+ // We'll also a mapping that will conflict, and one that won't
+ ASSERT_TRUE(domain_lawyer_.AddOriginDomainMapping(
+ "http://dest1/", "http://common_src1", &message_handler_));
+ ASSERT_TRUE(domain_lawyer_.AddOriginDomainMapping(
+ "http://dest2/", "http://common_src2", &message_handler_));
+
+ // Now add a similar set of mappings for another lawyer.
+ DomainLawyer merged;
+ ASSERT_TRUE(merged.AddDomain("http://d2.com/", &message_handler_));
+ ASSERT_TRUE(merged.AddRewriteDomainMapping(
+ "http://cdn2.com", "http://www.o2.com", &message_handler_));
+ ASSERT_TRUE(merged.AddOriginDomainMapping(
+ "http://localhost:8080", "http://o2.com:8080", &message_handler_));
+
+ // Here's a different mapping for the same source.
+ ASSERT_TRUE(merged.AddOriginDomainMapping(
+ "http://dest3/", "http://common_src1", &message_handler_));
+ ASSERT_TRUE(domain_lawyer_.AddOriginDomainMapping(
+ "http://dest4/", "http://common_src3", &message_handler_));
+
+ merged.Merge(domain_lawyer_);
+
+ // Now the tests for both domains should work post-merger.
+
+ std::string mapped;
+ GURL resolved_request;
+ ASSERT_TRUE(merged.MapRequestToDomain(
+ GoogleUrl::Create(StringPiece("http://www.o1.com/index.html")),
+ "styles/blue.css", &mapped, &resolved_request, &message_handler_));
+ EXPECT_EQ("http://cdn1.com/", mapped);
+ ASSERT_TRUE(merged.MapRequestToDomain(
+ GoogleUrl::Create(StringPiece("http://www.o2.com/index.html")),
+ "styles/blue.css", &mapped, &resolved_request, &message_handler_));
+ EXPECT_EQ("http://cdn2.com/", mapped);
+
+ ASSERT_TRUE(merged.MapOrigin("http://o1.com:8080/a/b/c?d=f", &mapped));
+ EXPECT_EQ("http://localhost:8080/a/b/c?d=f", mapped);
+ ASSERT_TRUE(merged.MapOrigin("http://o2.com:8080/a/b/c?d=f", &mapped));
+ EXPECT_EQ("http://localhost:8080/a/b/c?d=f", mapped);
+
+ // The conflict will be silently resolved to prefer the mapping from
+ // the domain that got merged, which is domain_laywer_1, overriding
+ // what was previously in the target.
+ ASSERT_TRUE(merged.MapOrigin("http://common_src1", &mapped));
+ EXPECT_EQ("http://dest1/", mapped);
+
+ // Now check the domains that were added.
+ ASSERT_TRUE(merged.MapOrigin("http://common_src2", &mapped));
+ EXPECT_EQ("http://dest2/", mapped);
+
+ ASSERT_TRUE(merged.MapOrigin("http://common_src3", &mapped));
+ EXPECT_EQ("http://dest4/", mapped);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/elide_attributes_filter.cc b/trunk/src/net/instaweb/rewriter/elide_attributes_filter.cc
new file mode 100644
index 0000000..c8b6372
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/elide_attributes_filter.cc
@@ -0,0 +1,265 @@
+/**
+ * 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: mdsteele@google.com (Matthew D. Steele)
+
+#include "net/instaweb/rewriter/public/elide_attributes_filter.h"
+
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/htmlparse/public/html_node.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace {
+
+// An attribute can be simplified if it is a "boolean attribute".
+// See http://www.w3.org/TR/html5/common-microsyntaxes.html#boolean-attribute
+// For example, <option selected="selected"> can become <option selected>.
+
+struct TagAttr {
+ const char* tag_name;
+ const char* attr_name;
+};
+
+const TagAttr kBooleanAttrs[] = {
+ // http://www.w3.org/TR/html4/struct/objects.html#h-13.6.1
+ {"area", "nohref"},
+ // http://www.w3.org/TR/html5/video.html#media-elements
+ {"audio", "autoplay"},
+ {"audio", "controls"},
+ {"audio", "loop"},
+ {"audio", "muted"},
+ // http://www.w3.org/TR/html5/the-button-element.html#the-button-element
+ {"button", "autofocus"},
+ {"button", "disabled"},
+ // http://www.w3.org/TR/html5/interactive-elements.html#the-command
+ {"command", "checked"},
+ {"command", "disabled"},
+ // http://www.w3.org/TR/html5/interactive-elements.html#the-details-element
+ {"details", "open"},
+ // http://www.w3.org/TR/html5/association-of-controls-and-forms.html#
+ // attributes-for-form-submission
+ {"form", "novalidate"},
+ // http://www.w3.org/TR/html4/present/frames.html#h-16.2.2
+ {"frame", "noresize"},
+ // http://www.w3.org/TR/html5/the-button-element.html#the-keygen-element
+ {"keygen", "autofocus"},
+ {"keygen", "disabled"},
+ // http://www.w3.org/TR/html5/the-iframe-element.html#the-iframe-element
+ {"iframe", "seamless"},
+ // http://www.w3.org/TR/html5/embedded-content-1.html#the-img-element
+ {"img", "ismap"},
+ // http://www.w3.org/TR/html5/the-input-element.html#the-input-element
+ {"input", "autofocus"},
+ {"input", "checked"},
+ {"input", "defaultchecked"},
+ {"input", "disabled"},
+ {"input", "formnovalidate"},
+ {"input", "indeterminate"},
+ {"input", "multiple"},
+ {"input", "readonly"},
+ {"input", "required"},
+ // http://www.w3.org/TR/html4/struct/objects.html#h-13.3
+ {"object", "declare"},
+ // http://www.w3.org/TR/html5/grouping-content.html#the-ol-element
+ {"ol", "reversed"},
+ // http://www.w3.org/TR/html5/the-button-element.html#the-optgroup-element
+ {"optgroup", "disabled"},
+ // http://www.w3.org/TR/html5/the-button-element.html#the-option-element
+ {"option", "defaultselected"},
+ {"option", "disabled"},
+ {"option", "selected"},
+ // http://www.w3.org/TR/html5/scripting-1.html#script
+ {"script", "async"},
+ {"script", "defer"},
+ // http://www.w3.org/TR/html5/the-button-element.html#the-select-element
+ {"select", "autofocus"},
+ {"select", "disabled"},
+ {"select", "multiple"},
+ {"select", "required"},
+ // http://www.w3.org/TR/html5/semantics.html#the-style-element
+ {"style", "scoped"},
+ // http://www.w3.org/TR/html5/the-button-element.html#the-textarea-element
+ {"textarea", "autofocus"},
+ {"textarea", "disabled"},
+ {"textarea", "readonly"},
+ {"textarea", "required"},
+ // http://www.w3.org/TR/html5/video.html#media-elements
+ {"video", "autoplay"},
+ {"video", "controls"},
+ {"video", "loop"},
+ {"video", "muted"},
+};
+
+// An attribute can be removed from a tag if its name and value is in
+// kDefaultList.
+//
+// Note: It is important that this list not include attributes that can be
+// inherited. Otherwise something like this could fail:
+// <div attr="non_default_value">
+// <div attr="default_value"> <!-- must not be elided -->
+// </div>
+// </div>
+
+struct TagAttrValue {
+ const char* tag_name;
+ const char* attr_name;
+ const char* attr_value;
+ bool requires_version_5; // Default value only exists in (X)HTML 5.
+};
+
+// References for HTML 4 and HTML 5 are included below, with extra notes for
+// entries that apply differently to HTML 4 and HTML 5 (i.e. those with 4th
+// argument true). If you are so inclined, you are encouraged to carefully
+// verify the references and make changes to any errors in this data.
+const TagAttrValue kDefaultList[] = {
+ // 4: http://www.w3.org/TR/html4/struct/links.html#h-12.2
+ // 5: Note that the <a> tag's shape attribute is deprecated in HTML5.
+ // http://www.w3.org/TR/html5/obsolete.html#non-conforming-features
+ {"a", "shape", "rect", false},
+ // 4: http://www.w3.org/TR/html4/struct/objects.html#h-13.6.1
+ // 5: http://www.w3.org/TR/html5/the-map-element.html#the-area-element
+ {"area", "shape", "rect", false},
+ // 4: http://www.w3.org/TR/html4/interact/forms.html#h-17.5
+ // 5: http://www.w3.org/TR/html5/the-button-element.html#the-button-element
+ {"button", "type", "submit", false},
+ // 4: The <command> tag does not exist in HTML 4.
+ // 5: http://www.w3.org/TR/html5/interactive-elements.html#the-command
+ {"command", "type", "command", true},
+ // 4: The <form> tag's autocomplete attribute does not exist in HTML 4.
+ // 5: http://www.w3.org/TR/html5/forms.html#the-form-element
+ {"form", "autocomplete", "on", true},
+ // 4: http://www.w3.org/TR/html4/interact/forms.html#h-17.3
+ // 5: http://www.w3.org/TR/html5/association-of-controls-and-forms.html#
+ // attributes-for-form-submission
+ {"form", "enctype", "application/x-www-form-urlencoded", false},
+ {"form", "method", "get", false},
+ // 4: http://www.w3.org/TR/html4/present/frames.html#h-16.2.2
+ // 5: Note that the <frame> tag is deprecated in HTML5.
+ // http://www.w3.org/TR/html5/obsolete.html#non-conforming-features
+ {"frame", "frameborder", "1", false},
+ {"frame", "scrolling", "auto", false},
+ // 4: http://www.w3.org/TR/html4/present/frames.html#h-16.5
+ // 5: Note that these attributes are deprecated in HTML5.
+ // http://www.w3.org/TR/html5/obsolete.html#non-conforming-features
+ {"iframe", "frameborder", "1", false},
+ {"iframe", "scrolling", "auto", false},
+ // 4: http://www.w3.org/TR/html4/interact/forms.html#h-17.4
+ // 5: http://www.w3.org/TR/html5/the-input-element.html#the-input-element
+ {"input", "type", "text", false},
+ // 4: The <keygen> tag does not exist in HTML 4.
+ // 5: http://www.w3.org/TR/html5/the-button-element.html#the-keygen-element
+ {"keygen", "keytype", "rsa", true},
+ // 4: The <menu> tag seems to mean something different in HTML 4.
+ // 5: http://www.w3.org/TR/html5/interactive-elements.html#menus
+ {"menu", "type", "list", true},
+ // 4: http://www.w3.org/TR/html4/struct/objects.html#h-13.3.2
+ // 5: Note that the <param> tag's valuetype attribute is deprecated in HTML5.
+ // http://www.w3.org/TR/html5/obsolete.html#non-conforming-features
+ {"param", "valuetype", "data", false},
+ // 4: These attributes have no default values in HTML 4.
+ // http://www.w3.org/TR/html4/interact/scripts.html#h-18.2.1
+ // 5: http://www.w3.org/TR/html5/scripting-1.html
+ {"script", "language", "javascript", true},
+ {"script", "type", "text/javascript", true},
+ // 4: The <source> tag does not exist in HTML 4.
+ // 5: http://www.w3.org/TR/html5/video.html#the-source-element
+ {"source", "media", "all", true},
+ // 4: This attribute has no default value in HTML 4.
+ // http://www.w3.org/TR/html4/present/styles.html#h-14.2.3
+ // 5: http://www.w3.org/TR/html5/semantics.html#the-style-element
+ {"style", "type", "text/css", true},
+ // 4: This attributes has a _different_ default value in HTML 4!
+ // http://www.w3.org/TR/html4/present/styles.html#h-14.2.3
+ // 5: http://www.w3.org/TR/html5/semantics.html#the-style-element
+ {"style", "media", "all", true},
+ // 4: The <textarea> tag's wrap attribute does not exist in HTML 4.
+ // 5: http://www.w3.org/TR/html5/the-button-element.html#the-textarea-element
+ {"textarea", "wrap", "soft", true},
+ // 4: http://www.w3.org/TR/html4/struct/tables.html
+ // 5: http://www.w3.org/TR/html5/tabular-data.html#table-model
+ {"col", "span", "1", false},
+ {"colgroup", "span", "1", false},
+ {"td", "colspan", "1", false},
+ {"td", "rowspan", "1", false},
+ {"th", "colspan", "1", false},
+ {"th", "rowspan", "1", false},
+};
+
+} // namespace
+
+namespace net_instaweb {
+
+ElideAttributesFilter::ElideAttributesFilter(HtmlParse* html_parse)
+ : html_parse_(html_parse) {
+ // Populate one_value_attrs_map_
+ for (size_t i = 0; i < arraysize(kBooleanAttrs); ++i) {
+ const TagAttr& entry = kBooleanAttrs[i];
+ one_value_attrs_map_[html_parse->Intern(entry.tag_name)].insert(
+ html_parse->Intern(entry.attr_name));
+ }
+ // Populate default_value_map_
+ for (size_t i = 0; i < arraysize(kDefaultList); ++i) {
+ const TagAttrValue& entry = kDefaultList[i];
+ AttrValue& value = default_value_map_[html_parse->Intern(entry.tag_name)]
+ [html_parse->Intern(entry.attr_name)];
+ value.attr_value = entry.attr_value;
+ value.requires_version_5 = entry.requires_version_5;
+ }
+}
+
+void ElideAttributesFilter::StartElement(HtmlElement* element) {
+ const DocType& doctype = html_parse_->doctype();
+
+ if (!doctype.IsXhtml()) {
+ // Check for boolean attributes.
+ AtomSetMap::const_iterator iter =
+ one_value_attrs_map_.find(element->tag());
+ if (iter != one_value_attrs_map_.end()) {
+ const AtomSet& oneValueAttrs = iter->second;
+ for (int i = 0, end = element->attribute_size(); i < end; ++i) {
+ HtmlElement::Attribute& attribute = element->attribute(i);
+ if (attribute.value() != NULL &&
+ oneValueAttrs.count(attribute.name()) > 0) {
+ attribute.SetValue(NULL);
+ }
+ }
+ }
+ }
+
+ // Check for attributes with default values.
+ ValueMapMap::const_iterator iter1 = default_value_map_.find(element->tag());
+ if (iter1 != default_value_map_.end()) {
+ const ValueMap& default_values = iter1->second;
+ for (int i = 0; i < element->attribute_size(); ++i) {
+ HtmlElement::Attribute& attribute = element->attribute(i);
+ if (attribute.value() != NULL) {
+ ValueMap::const_iterator iter2 = default_values.find(attribute.name());
+ if (iter2 != default_values.end()) {
+ const AttrValue& value = iter2->second;
+ if ((!value.requires_version_5 || doctype.IsVersion5()) &&
+ StringCaseEqual(attribute.value(), value.attr_value)) {
+ element->DeleteAttribute(i);
+ --i;
+ }
+ }
+ }
+ }
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/elide_attributes_filter_test.cc b/trunk/src/net/instaweb/rewriter/elide_attributes_filter_test.cc
new file mode 100644
index 0000000..57dff31
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/elide_attributes_filter_test.cc
@@ -0,0 +1,104 @@
+/**
+ * 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: mdsteele@google.com (Matthew D. Steele)
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/html_parse_test_base.h"
+#include "net/instaweb/rewriter/public/elide_attributes_filter.h"
+
+namespace net_instaweb {
+
+class ElideAttributesFilterTest : public HtmlParseTestBase {
+ protected:
+ ElideAttributesFilterTest()
+ : elide_attributes_filter_(&html_parse_) {
+ html_parse_.AddFilter(&elide_attributes_filter_);
+ }
+
+ virtual bool AddBody() const { return false; }
+
+ private:
+ ElideAttributesFilter elide_attributes_filter_;
+
+ DISALLOW_COPY_AND_ASSIGN(ElideAttributesFilterTest);
+};
+
+TEST_F(ElideAttributesFilterTest, NoChanges) {
+ ValidateNoChanges("no_changes",
+ "<head><script src=\"foo.js\"></script></head>"
+ "<body><form method=\"post\">"
+ "<input type=\"checkbox\" checked>"
+ "</form></body>");
+}
+
+TEST_F(ElideAttributesFilterTest, RemoveAttrWithDefaultValue) {
+ ValidateExpected("remove_attr_with_default_value",
+ "<head></head><body><form method=get></form></body>",
+ "<head></head><body><form></form></body>");
+}
+
+TEST_F(ElideAttributesFilterTest, RemoveValueFromAttr) {
+ SetDoctype("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" "
+ "\"http://www.w3.org/TR/html4/strict.dtd\">");
+ ValidateExpected("remove_value_from_attr",
+ "<head></head><body><form>"
+ "<input type=checkbox checked=checked></form></body>",
+ "<head></head><body><form>"
+ "<input type=checkbox checked></form></body>");
+}
+
+TEST_F(ElideAttributesFilterTest, DoNotRemoveValueFromAttrInXhtml) {
+ SetDoctype("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" "
+ "\"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">");
+ ValidateNoChanges("do_not_remove_value_from_attr_in_xhtml",
+ "<head></head><body><form>"
+ "<input type=checkbox checked=checked></form></body>");
+}
+
+TEST_F(ElideAttributesFilterTest, DoNotBreakVBScript) {
+ SetDoctype("<!doctype html>");
+ ValidateExpected("do_not_break_vbscript",
+ "<head><script language=\"JavaScript\">var x=1;</script>"
+ "<script language=\"VBScript\">"
+ "Sub foo(ByVal bar)\n call baz(bar)\nend sub"
+ "</script></head><body></body>",
+ // Remove language="JavaScript", but not the VBScript one:
+ "<head><script>var x=1;</script>"
+ "<script language=\"VBScript\">"
+ "Sub foo(ByVal bar)\n call baz(bar)\nend sub"
+ "</script></head><body></body>");
+}
+
+TEST_F(ElideAttributesFilterTest, RemoveScriptTypeInHtml5) {
+ SetDoctype("<!doctype html>");
+ ValidateExpected("remove_script_type_in_html_5",
+ "<head><script src=\"foo.js\" type=\"text/javascript\">"
+ "</script></head><body></body>",
+ "<head><script src=\"foo.js\">"
+ "</script></head><body></body>");
+}
+
+// See http://code.google.com/p/modpagespeed/issues/detail?id=59
+TEST_F(ElideAttributesFilterTest, DoNotRemoveScriptTypeInHtml4) {
+ SetDoctype("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" "
+ "\"http://www.w3.org/TR/html4/strict.dtd\">");
+ ValidateNoChanges("do_not_remove_script_type_in_html_4",
+ "<head><script src=\"foo.js\" type=\"text/javascript\">"
+ "</script></head><body></body>");
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/file_input_resource.cc b/trunk/src/net/instaweb/rewriter/file_input_resource.cc
new file mode 100644
index 0000000..d4ece8b
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/file_input_resource.cc
@@ -0,0 +1,39 @@
+/**
+ * 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/file_input_resource.h"
+
+#include "net/instaweb/rewriter/public/resource_manager.h"
+#include "net/instaweb/util/public/file_system.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+
+namespace net_instaweb {
+
+FileInputResource::~FileInputResource() {
+}
+
+bool FileInputResource::Load(MessageHandler* message_handler) {
+ FileSystem* file_system = resource_manager_->file_system();
+ if (file_system->ReadFile(filename_.c_str(), &value_, message_handler)) {
+ resource_manager_->SetDefaultHeaders(type_, &meta_data_);
+ value_.SetHeaders(meta_data_);
+ }
+ return loaded();
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/html_attribute_quote_removal.cc b/trunk/src/net/instaweb/rewriter/html_attribute_quote_removal.cc
new file mode 100644
index 0000000..677ea8d
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/html_attribute_quote_removal.cc
@@ -0,0 +1,95 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#include "net/instaweb/rewriter/public/html_attribute_quote_removal.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/util/public/string_util.h"
+
+namespace {
+
+// Explicit about signedness because we are
+// loading a 0-indexed lookup table.
+const unsigned char kNoQuoteChars[] =
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789-._:";
+
+} // namespace
+
+// TODO(jmaessen): Make adjustable.
+const bool kLogQuoteRemoval = false;
+
+namespace net_instaweb {
+// Remove quotes; see description in .h file.
+
+HtmlAttributeQuoteRemoval::HtmlAttributeQuoteRemoval(HtmlParse* html_parse)
+ : total_quotes_removed_(0),
+ html_parse_(html_parse) {
+ // In pidgin Python:
+ // needs_no_quotes[:] = false
+ // needs_no_quotes[kNoQuoteChars] = true
+ memset(&needs_no_quotes_, 0, sizeof(needs_no_quotes_));
+ for (int i = 0; kNoQuoteChars[i] != '\0'; ++i) {
+ needs_no_quotes_[kNoQuoteChars[i]] = true;
+ }
+}
+
+bool HtmlAttributeQuoteRemoval::NeedsQuotes(const char *val) {
+ bool needs_quotes = false;
+ int i = 0;
+ if (val != NULL) {
+ for (; val[i] != '\0'; ++i) {
+ // Explicit cast to unsigned char ensures that our offset
+ // into needs_no_quotes_ is positive.
+ needs_quotes = !needs_no_quotes_[static_cast<unsigned char>(val[i])];
+ if (needs_quotes) {
+ break;
+ }
+ }
+ }
+ // Note that due to inconsistencies in empty attribute parsing between Firefox
+ // and Chrome (Chrome seems to parse the next thing it sees after whitespace
+ // as the attribute value) we leave empty attributes intact.
+ return needs_quotes || i == 0;
+}
+
+void HtmlAttributeQuoteRemoval::StartElement(HtmlElement* element) {
+ if (html_parse_->doctype().IsXhtml()) {
+ return; // XHTML doctypes require quotes, so don't remove any.
+ }
+ int rewritten = 0;
+ for (int i = 0; i < element->attribute_size(); ++i) {
+ HtmlElement::Attribute& attr = element->attribute(i);
+ if (attr.quote() != NULL && attr.quote()[0] != '\0' &&
+ !NeedsQuotes(attr.value())) {
+ attr.set_quote("");
+ rewritten++;
+ }
+ }
+ if (rewritten > 0) {
+ total_quotes_removed_ += rewritten;
+ if (kLogQuoteRemoval) {
+ const char* plural = (rewritten == 1) ? "" : "s";
+ html_parse_->InfoHere("Scrubbed quotes from %d attribute%s",
+ rewritten, plural);
+ }
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/html_attribute_quote_removal_test.cc b/trunk/src/net/instaweb/rewriter/html_attribute_quote_removal_test.cc
new file mode 100644
index 0000000..bdb22dc
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/html_attribute_quote_removal_test.cc
@@ -0,0 +1,73 @@
+/**
+ * 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: mdsteele@google.com (Matthew D. Steele)
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/html_parse_test_base.h"
+#include "net/instaweb/rewriter/public/html_attribute_quote_removal.h"
+
+namespace net_instaweb {
+
+class HtmlAttributeQuoteRemovalTest : public HtmlParseTestBase {
+ protected:
+ HtmlAttributeQuoteRemovalTest()
+ : html_attribute_quote_removal_(&html_parse_) {
+ html_parse_.AddFilter(&html_attribute_quote_removal_);
+ }
+
+ virtual bool AddBody() const { return true; }
+
+ private:
+ HtmlAttributeQuoteRemoval html_attribute_quote_removal_;
+
+ DISALLOW_COPY_AND_ASSIGN(HtmlAttributeQuoteRemovalTest);
+};
+
+TEST_F(HtmlAttributeQuoteRemovalTest, NoQuotesNoChange) {
+ ValidateNoChanges("no_quotes_no_change",
+ "<div class=foo id=bar>foobar</div>");
+}
+
+TEST_F(HtmlAttributeQuoteRemovalTest, DoNotRemoveNeededQuotes) {
+ ValidateNoChanges("do_not_remove_needed_quotes",
+ "<a href=\"http://www.example.com/\">foobar</a>");
+}
+
+TEST_F(HtmlAttributeQuoteRemovalTest, DoNotDeleteEmptyAttrs) {
+ ValidateNoChanges("do_not_delete_empty_attrs",
+ "<div id=''></div>");
+}
+
+TEST_F(HtmlAttributeQuoteRemovalTest, RemoveUnneededQuotes) {
+ ValidateExpected("remove_unneeded_quotes",
+ "<div class=\"foo\" id='bar'>foobar</div>",
+ "<div class=foo id=bar>foobar</div>");
+}
+
+TEST_F(HtmlAttributeQuoteRemovalTest, NoValueNoChange) {
+ ValidateNoChanges("no_value_no_change",
+ "<input checked type=checkbox>");
+}
+
+TEST_F(HtmlAttributeQuoteRemovalTest, DoNotRemoveQuotesInXhtml) {
+ SetDoctype("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" "
+ "\"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">");
+ ValidateNoChanges("do_not_remove_quotes_in_xhtml",
+ "<div class=\"foo\" id='bar'>foobar</div>");
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/html_minifier_main.cc b/trunk/src/net/instaweb/rewriter/html_minifier_main.cc
new file mode 100644
index 0000000..2e70720
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/html_minifier_main.cc
@@ -0,0 +1,120 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+// Author: bmcquade@google.com (Bryan McQuade)
+//
+// Simple HTML minifier, based on pagespeed's minify_html.cc
+
+#include <stdio.h>
+
+#include <fstream>
+#include <string>
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/htmlparse/public/html_writer_filter.h"
+#include "net/instaweb/rewriter/public/collapse_whitespace_filter.h"
+#include "net/instaweb/rewriter/public/elide_attributes_filter.h"
+#include "net/instaweb/rewriter/public/html_attribute_quote_removal.h"
+#include "net/instaweb/rewriter/public/remove_comments_filter.h"
+#include "net/instaweb/util/public/file_message_handler.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/string_writer.h"
+
+namespace {
+
+using net_instaweb::StrCat;
+
+void ReadFileToStringOrDie(const char* filename,
+ std::string* dest) {
+ std::ifstream file_stream;
+ file_stream.open(filename, std::ifstream::in | std::ifstream::binary);
+ CHECK(!file_stream.fail());
+ dest->assign(std::istreambuf_iterator<char>(file_stream),
+ std::istreambuf_iterator<char>());
+ file_stream.close();
+ CHECK(!file_stream.fail());
+}
+
+void WriteStringToFileOrDie(const std::string& src,
+ const char* filename) {
+ std::ofstream file_stream;
+ file_stream.open(filename, std::ifstream::out | std::ifstream::binary);
+ CHECK(!file_stream.fail());
+ file_stream.write(src.c_str(), src.size());
+ file_stream.close();
+ CHECK(!file_stream.fail());
+}
+
+class HtmlMinifier {
+ public:
+ explicit HtmlMinifier();
+ ~HtmlMinifier();
+
+ // Return true if successful, false on error.
+ bool MinifyHtml(const std::string& input_name,
+ const std::string& input,
+ std::string* output);
+
+ private:
+ net_instaweb::FileMessageHandler message_handler_;
+ net_instaweb::HtmlParse html_parse_;
+ net_instaweb::RemoveCommentsFilter remove_comments_filter_;
+ net_instaweb::ElideAttributesFilter elide_attributes_filter_;
+ net_instaweb::HtmlAttributeQuoteRemoval quote_removal_filter_;
+ net_instaweb::CollapseWhitespaceFilter collapse_whitespace_filter_;
+ net_instaweb::HtmlWriterFilter html_writer_filter_;
+
+ DISALLOW_COPY_AND_ASSIGN(HtmlMinifier);
+};
+
+HtmlMinifier::HtmlMinifier()
+ : message_handler_(stderr),
+ html_parse_(&message_handler_),
+ remove_comments_filter_(&html_parse_),
+ elide_attributes_filter_(&html_parse_),
+ quote_removal_filter_(&html_parse_),
+ collapse_whitespace_filter_(&html_parse_),
+ html_writer_filter_(&html_parse_) {
+ html_parse_.AddFilter(&remove_comments_filter_);
+ html_parse_.AddFilter(&elide_attributes_filter_);
+ html_parse_.AddFilter("e_removal_filter_);
+ html_parse_.AddFilter(&collapse_whitespace_filter_);
+ html_parse_.AddFilter(&html_writer_filter_);
+}
+
+HtmlMinifier::~HtmlMinifier() {}
+
+bool HtmlMinifier::MinifyHtml(const std::string& input_name,
+ const std::string& input,
+ std::string* output) {
+ net_instaweb::StringWriter string_writer(output);
+ html_writer_filter_.set_writer(&string_writer);
+
+ std::string url = StrCat("http://html_minifier.com/", input_name, ".html");
+ html_parse_.StartParse(url);
+ html_parse_.ParseText(input.data(), input.size());
+ html_parse_.FinishParse();
+
+ html_writer_filter_.set_writer(NULL);
+
+ return true;
+}
+
+} // namespace
+
+int main(int argc, char** argv) {
+ if (argc != 3) {
+ fprintf(stderr, "Usage: minify_html <input> <output>\n");
+ return 1;
+ }
+
+ std::string original;
+ ReadFileToStringOrDie(argv[1], &original);
+
+ std::string minified;
+ HtmlMinifier html_minifier;
+ html_minifier.MinifyHtml(argv[1], original, &minified);
+
+ WriteStringToFileOrDie(minified, argv[2]);
+ return 0;
+}
diff --git a/trunk/src/net/instaweb/rewriter/image.cc b/trunk/src/net/instaweb/rewriter/image.cc
new file mode 100644
index 0000000..d1aada4
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/image.cc
@@ -0,0 +1,480 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#include "net/instaweb/rewriter/public/image.h"
+
+#include "base/basictypes.h"
+#include "net/instaweb/rewriter/public/resource.h"
+#include "net/instaweb/rewriter/public/resource_manager.h"
+#include "net/instaweb/util/public/content_type.h"
+#include "net/instaweb/util/public/file_system.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/writer.h"
+#undef PAGESPEED_PNG_OPTIMIZER_GIF_READER
+#define PAGESPEED_PNG_OPTIMIZER_GIF_READER 0
+#ifdef USE_SYSTEM_OPENCV
+#include "cv.h"
+#include "highgui.h"
+#else
+#include "third_party/opencv/src/opencv/include/opencv/cv.h"
+#include "third_party/opencv/src/opencv/include/opencv/highgui.h"
+#endif
+#include "pagespeed/image_compression/gif_reader.h"
+#include "pagespeed/image_compression/jpeg_optimizer.h"
+#include "pagespeed/image_compression/png_optimizer.h"
+
+namespace net_instaweb {
+
+namespace ImageHeaders {
+
+const char kPngHeader[] = "\x89PNG\r\n\x1a\n";
+const size_t kPngHeaderLength = sizeof(kPngHeader) - 1;
+const char kPngIHDR[] = "\0\0\0\x0dIHDR";
+const size_t kPngIntSize = 4;
+const size_t kPngSectionHeaderLength = 2 * kPngIntSize;
+const size_t kIHDRDataStart = kPngHeaderLength + kPngSectionHeaderLength;
+const size_t kPngSectionMinSize = kPngSectionHeaderLength + kPngIntSize;
+const size_t kPngColourTypeOffset = kIHDRDataStart + 2 * kPngIntSize + 1;
+const char kPngAlphaChannel = 0x4; // bit of ColourType set for alpha channel
+const char kPngIDAT[] = "IDAT";
+const char kPngtRNS[] = "tRNS";
+
+const char kGifHeader[] = "GIF8";
+const size_t kGifHeaderLength = sizeof(kGifHeader) - 1;
+const size_t kGifDimStart = kGifHeaderLength + 2;
+const size_t kGifIntSize = 2;
+
+const size_t kJpegIntSize = 2;
+
+} // namespace ImageHeaders
+
+namespace {
+
+bool WriteTempFileWithContentType(
+ const StringPiece& prefix_name, const ContentType& content_type,
+ const StringPiece& buffer, std::string* filename,
+ FileSystem* file_system, MessageHandler* handler) {
+ std::string tmp_filename;
+ bool ok = file_system->WriteTempFile(
+ prefix_name.as_string().c_str(), buffer, &tmp_filename, handler);
+ if (ok) {
+ *filename = StrCat(tmp_filename, content_type.file_extension());
+ ok = file_system->RenameFile(
+ tmp_filename.c_str(), filename->c_str(), handler);
+ }
+ return ok;
+}
+
+} // namespace
+
+Image::Image(const StringPiece& original_contents,
+ const std::string& url,
+ const StringPiece& file_prefix,
+ FileSystem* file_system,
+ MessageHandler* handler)
+ : file_prefix_(file_prefix.data(), file_prefix.size()),
+ file_system_(file_system),
+ handler_(handler),
+ image_type_(IMAGE_UNKNOWN),
+ original_contents_(original_contents),
+ output_contents_(),
+ output_valid_(false),
+ opencv_filename_(),
+ opencv_image_(NULL),
+ opencv_load_possible_(true),
+ resized_(false),
+ url_(url) { }
+
+Image::~Image() {
+ CleanOpenCV();
+}
+
+// Looks through blocks of jpeg stream to find SOFn block
+// indicating encoding and dimensions of image.
+// Loosely based on code and FAQs found here:
+// http://www.faqs.org/faqs/jpeg-faq/part1/
+void Image::FindJpegSize() {
+ const StringPiece& buf = original_contents_;
+ size_t pos = 2; // Position of first data block after header.
+ while (pos < buf.size()) {
+ // Read block identifier
+ int id = CharToInt(buf[pos++]);
+ if (id == 0xff) { // Padding byte
+ continue;
+ }
+ // At this point pos points to first data byte in block. In any block,
+ // first two data bytes are size (including these 2 bytes). But first,
+ // make sure block wasn't truncated on download.
+ if (pos + ImageHeaders::kJpegIntSize > buf.size()) {
+ break;
+ }
+ int length = JpegIntAtPosition(buf, pos);
+ // Now check for a SOFn header, which describes image dimensions.
+ if (0xc0 <= id && id <= 0xcf && // SOFn header
+ length >= 8 && // Valid SOFn block size
+ pos + 1 + 3 * ImageHeaders::kJpegIntSize <= buf.size() &&
+ // Above avoids case where dimension data was truncated
+ id != 0xc4 && id != 0xc8 && id != 0xcc) {
+ // 0xc4, 0xc8, 0xcc aren't actually valid SOFn headers.
+ // NOTE: we don't care if we have the whole SOFn block,
+ // just that we can fetch both dimensions without trouble.
+ // Our image download could be truncated at this point for
+ // all we care.
+ // We're a bit sloppy about SOFn block size, as it's
+ // actually 8 + 3 * buf[pos+2], but for our purposes this
+ // will suffice as we don't parse subsequent metadata (which
+ // describes the formatting of chunks of image data).
+ int height = JpegIntAtPosition(buf, pos + 1 + ImageHeaders::kJpegIntSize);
+ int width = JpegIntAtPosition(buf,
+ pos + 1 + 2 * ImageHeaders::kJpegIntSize);
+ dims_.set_dims(width, height);
+ break;
+ }
+ pos += length;
+ }
+ if (dims_.height() <= 0 || dims_.width() <= 0) {
+ dims_.invalidate();
+ handler_->Error(url_.c_str(), 0,
+ "Couldn't find jpeg dimensions (data truncated?).");
+ }
+}
+
+// Looks at first (IHDR) block of png stream to find image dimensions.
+// See also: http://www.w3.org/TR/PNG/
+void Image::FindPngSize() {
+ const StringPiece& buf = original_contents_;
+ // Here we make sure that buf contains at least enough data that we'll be able
+ // to decipher the image dimensions first, before we actually check for the
+ // headers and attempt to decode the dimensions (which are the first two ints
+ // after the IHDR section label).
+ if ((buf.size() >= // Not truncated
+ ImageHeaders::kIHDRDataStart + 2 * ImageHeaders::kPngIntSize) &&
+ (StringPiece(buf.data() + ImageHeaders::kPngHeaderLength,
+ ImageHeaders::kPngSectionHeaderLength) ==
+ StringPiece(ImageHeaders::kPngIHDR,
+ ImageHeaders::kPngSectionHeaderLength))) {
+ int width = PngIntAtPosition(buf, ImageHeaders::kIHDRDataStart);
+ int height = PngIntAtPosition(
+ buf, ImageHeaders::kIHDRDataStart + ImageHeaders::kPngIntSize);
+ dims_.set_dims(width, height);
+ } else {
+ handler_->Error(url_.c_str(), 0,
+ "Couldn't find png dimensions "
+ "(data truncated or IHDR missing).");
+ }
+}
+
+// Looks at header of GIF file to extract image dimensions.
+// See also: http://en.wikipedia.org/wiki/Graphics_Interchange_Format
+void Image::FindGifSize() {
+ const StringPiece& buf = original_contents_;
+ // Make sure that buf contains enough data that we'll be able to
+ // decipher the image dimensions before we attempt to do so.
+ if (buf.size() >=
+ ImageHeaders::kGifDimStart + 2 * ImageHeaders::kGifIntSize) {
+ // Not truncated
+ int width = GifIntAtPosition(buf, ImageHeaders::kGifDimStart);
+ int height = GifIntAtPosition(
+ buf, ImageHeaders::kGifDimStart + ImageHeaders::kGifIntSize);
+ dims_.set_dims(width, height);
+ } else {
+ handler_->Error(url_.c_str(), 0,
+ "Couldn't find gif dimensions (data truncated)");
+ }
+}
+
+// Looks at image data in order to determine image type, and also fills in any
+// dimension information it can (setting image_type_ and dims_).
+void Image::ComputeImageType() {
+ // Image classification based on buffer contents gakked from leptonica,
+ // but based on well-documented headers (see Wikipedia etc.).
+ // Note that we can be fooled if we're passed random binary data;
+ // we make the call based on as few as two bytes (JPEG).
+ const StringPiece& buf = original_contents_;
+ if (buf.size() >= 8) {
+ // Note that gcc rightly complains about constant ranges with the
+ // negative char constants unless we cast.
+ switch (CharToInt(buf[0])) {
+ case 0xff:
+ // Either jpeg or jpeg2
+ // (the latter we don't handle yet, and don't bother looking for).
+ if (CharToInt(buf[1]) == 0xd8) {
+ image_type_ = IMAGE_JPEG;
+ FindJpegSize();
+ }
+ break;
+ case 0x89:
+ // Possible png.
+ if (StringPiece(buf.data(), ImageHeaders::kPngHeaderLength) ==
+ StringPiece(ImageHeaders::kPngHeader,
+ ImageHeaders::kPngHeaderLength)) {
+ image_type_ = IMAGE_PNG;
+ FindPngSize();
+ }
+ break;
+ case 'G':
+ // Possible gif.
+ if ((StringPiece(buf.data(), ImageHeaders::kGifHeaderLength) ==
+ StringPiece(ImageHeaders::kGifHeader,
+ ImageHeaders::kGifHeaderLength)) &&
+ (buf[4] == '7' || buf[4] == '9') && buf[5] == 'a') {
+ image_type_ = IMAGE_GIF;
+ FindGifSize();
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+const ContentType* Image::content_type() {
+ const ContentType* res = NULL;
+ switch (image_type()) {
+ case IMAGE_UNKNOWN:
+ break;
+ case IMAGE_JPEG:
+ res = &kContentTypeJpeg;
+ break;
+ case IMAGE_PNG:
+ res = &kContentTypePng;
+ break;
+ case IMAGE_GIF:
+ res = &kContentTypeGif;
+ break;
+ }
+ return res;
+}
+
+// Compute whether a PNG can have transparent / semi-transparent pixels
+// by walking the image data in accordance with the spec:
+// http://www.w3.org/TR/PNG/
+// If the colour type (UK spelling from spec) includes an alpha channel, or
+// there is a tRNS section with at least one entry before IDAT, then we assume
+// the image contains non-opaque pixels and return true.
+bool Image::ComputePngTransparency() {
+ // We assume the image has transparency until we prove otherwise.
+ // This allows us to deal conservatively with truncation etc.
+ bool has_transparency = true;
+ const StringPiece& buf = original_contents_;
+ if (buf.size() > ImageHeaders::kPngColourTypeOffset &&
+ ((buf[ImageHeaders::kPngColourTypeOffset] &
+ ImageHeaders::kPngAlphaChannel) == 0)) {
+ // The colour type indicates that there is no dedicated alpha channel. Now
+ // we must look for a tRNS section indicating the existence of transparent
+ // colors or palette entries.
+ size_t section_start = ImageHeaders::kPngHeaderLength;
+ while (section_start + ImageHeaders::kPngSectionHeaderLength < buf.size()) {
+ size_t section_size = PngIntAtPosition(buf, section_start);
+ if (PngSectionIdIs(ImageHeaders::kPngIDAT, buf, section_start)) {
+ // tRNS section must occur before first IDAT. This image doesn't have a
+ // tRNS section, and thus doesn't have transparency.
+ has_transparency = false;
+ break;
+ } else if (PngSectionIdIs(ImageHeaders::kPngtRNS, buf, section_start) &&
+ section_size > 0) {
+ // Found a nonempty tRNS section. This image has_transparency.
+ break;
+ } else {
+ // Move on to next section.
+ section_start += section_size + ImageHeaders::kPngSectionMinSize;
+ }
+ }
+ }
+ return has_transparency;
+}
+
+bool Image::HasTransparency() {
+ bool result;
+ switch (image_type()) {
+ case IMAGE_PNG:
+ result = ComputePngTransparency();
+ break;
+ case IMAGE_GIF:
+ // Conservative.
+ // TODO(jmaessen): fix when gif transcoding is enabled.
+ result = true;
+ break;
+ default:
+ result = false;
+ break;
+ }
+ return result;
+}
+
+// Makes sure OpenCV version of image is loaded if that is possible.
+// Returns value of opencv_load_possible_ after load attempted.
+// Note that if the load fails, opencv_load_possible_ will be false
+// and future calls to LoadOpenCV will fail fast.
+bool Image::LoadOpenCV() {
+ if (opencv_image_ == NULL && opencv_load_possible_) {
+ Image::Type image_type = this->image_type();
+ const ContentType* content_type = this->content_type();
+ opencv_load_possible_ = (content_type != NULL &&
+ image_type != IMAGE_GIF &&
+ !HasTransparency());
+ if (opencv_load_possible_) {
+ opencv_load_possible_ =
+ WriteTempFileWithContentType(
+ file_prefix_, *content_type,
+ original_contents_, &opencv_filename_,
+ file_system_, handler_);
+ }
+ if (opencv_load_possible_) {
+ opencv_image_ = cvLoadImage(opencv_filename_.c_str());
+ file_system_->RemoveFile(opencv_filename_.c_str(), handler_);
+ opencv_load_possible_ = (opencv_image_ != NULL);
+ }
+ }
+ if (opencv_load_possible_) {
+ // A bit of belt and suspenders dimension checking. We used to do this for
+ // every image we loaded, but now we only do it when we're already paying
+ // the cost of OpenCV image conversion.
+ if (dims_.valid() && dims_.width() != opencv_image_->width) {
+ handler_->Error(url_.c_str(), 0,
+ "Computed width %d doesn't match OpenCV %d",
+ dims_.width(), opencv_image_->width);
+ }
+ if (dims_.valid() && dims_.height() != opencv_image_->height) {
+ handler_->Error(url_.c_str(), 0,
+ "Computed height %d doesn't match OpenCV %d",
+ dims_.height(), opencv_image_->height);
+ }
+ }
+ return opencv_load_possible_;
+}
+
+// Get rid of OpenCV image data gracefully (requires a call to OpenCV).
+void Image::CleanOpenCV() {
+ if (opencv_image_ != NULL) {
+ cvReleaseImage(&opencv_image_);
+ }
+}
+
+void Image::Dimensions(ImageDim* natural_dim) {
+ if (dims_.valid()) {
+ natural_dim->set_dims(dims_.width(), dims_.height());
+ } else {
+ natural_dim->invalidate();
+ }
+}
+
+bool Image::ResizeTo(const ImageDim& new_dim) {
+ CHECK(new_dim.valid());
+ if (resized_) {
+ // If we already resized, drop data and work with original image.
+ UndoResize();
+ }
+ bool ok = opencv_image_ != NULL || LoadOpenCV();
+ if (ok) {
+ IplImage* rescaled_image =
+ cvCreateImage(cvSize(new_dim.width(), new_dim.height()),
+ opencv_image_->depth,
+ opencv_image_->nChannels);
+ ok = rescaled_image != NULL;
+ if (ok) {
+ cvResize(opencv_image_, rescaled_image);
+ cvReleaseImage(&opencv_image_);
+ opencv_image_ = rescaled_image;
+ }
+ resized_ = ok;
+ }
+ return resized_;
+}
+
+void Image::UndoResize() {
+ if (resized_) {
+ CleanOpenCV();
+ output_valid_ = false;
+ image_type_ = IMAGE_UNKNOWN;
+ resized_ = false;
+ }
+}
+
+// Performs image optimization and output
+bool Image::ComputeOutputContents() {
+ if (!output_valid_) {
+ bool ok = true;
+ std::string opencv_contents;
+ StringPiece contents = original_contents_;
+ // Choose appropriate source for image contents.
+ // Favor original contents if image unchanged.
+ if (resized_ && opencv_image_ != NULL) {
+ cvSaveImage(opencv_filename_.c_str(), opencv_image_);
+ ok = file_system_->ReadFile(opencv_filename_.c_str(),
+ &opencv_contents, handler_);
+ file_system_->RemoveFile(opencv_filename_.c_str(), handler_);
+ if (ok) {
+ contents = opencv_contents;
+ }
+ }
+ // Take image contents and re-compress them.
+ if (ok) {
+ // If we can't optimize the image, we'll fail.
+ ok = false;
+ switch (image_type()) {
+ case IMAGE_UNKNOWN:
+ break;
+ case IMAGE_JPEG:
+ // TODO(jmarantz): The PageSpeed library should, ideally, take
+ // StringPiece args rather than const string&. We would save
+ // lots of string-copying if we made that change.
+ ok = pagespeed::image_compression::OptimizeJpeg(
+ std::string(contents.data(), contents.size()),
+ &output_contents_);
+ break;
+ case IMAGE_PNG: {
+ pagespeed::image_compression::PngReader png_reader;
+ ok = pagespeed::image_compression::PngOptimizer::OptimizePng(
+ png_reader,
+ std::string(contents.data(), contents.size()),
+ &output_contents_);
+ break;
+ }
+ case IMAGE_GIF: {
+#if PAGESPEED_PNG_OPTIMIZER_GIF_READER
+ pagespeed::image_compression::GifReader gif_reader;
+ ok = pagespeed::image_compression::PngOptimizer::OptimizePng(
+ gif_reader, contents, &output_contents_);
+ if (ok) {
+ image_type_ = IMAGE_PNG;
+ }
+#endif
+ break;
+ }
+ }
+ }
+ output_valid_ = ok;
+ }
+ return output_valid_;
+}
+
+StringPiece Image::Contents() {
+ StringPiece contents;
+ if (this->image_type() != IMAGE_UNKNOWN) {
+ contents = original_contents_;
+ if (output_valid_ || ComputeOutputContents()) {
+ contents = output_contents_;
+ }
+ }
+ return contents;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/image_dim.cc b/trunk/src/net/instaweb/rewriter/image_dim.cc
new file mode 100644
index 0000000..b50f83e
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/image_dim.cc
@@ -0,0 +1,80 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#include "net/instaweb/rewriter/public/image_dim.h"
+
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+namespace {
+
+// decodes decimal int followed by x at start of source, removing them
+// from source. Returns true on success. This is a utility for the
+// decoding of image dimensions.
+bool DecodeIntX(StringPiece* in, int *result) {
+ *result = 0;
+ bool ok = false;
+ for (; !in->empty(); in->remove_prefix(1)) {
+ // TODO(jmaessen): roll strtol-like functionality for StringPiece in util
+ if (!AccumulateDecimalValue((*in)[0], result)) {
+ break;
+ }
+ ok = true;
+ }
+ if (in->empty()) {
+ ok = false;
+ } else if ((*in)[0] != 'x') {
+ ok = false;
+ } else {
+ in->remove_prefix(1);
+ }
+ return ok;
+}
+
+} // namespace
+
+void ImageDim::EncodeTo(std::string* out) const {
+ if (valid_) {
+ out->append(StrCat(IntegerToString(width_), "x",
+ IntegerToString(height_)));
+ }
+ out->append("x");
+}
+
+bool ImageDim::DecodeFrom(StringPiece* in) {
+ valid_ = false;
+ bool ok;
+ if (in->empty()) {
+ ok = false;
+ } else if ((*in)[0] == 'x') {
+ // No dimensions.
+ in->remove_prefix(1);
+ ok = true;
+ } else if (DecodeIntX(in, &width_) &&
+ DecodeIntX(in, &height_)) {
+ // Dimensions.
+ valid_ = true;
+ ok = true;
+ } else {
+ ok = false;
+ }
+ return ok;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/image_endian_test.cc b/trunk/src/net/instaweb/rewriter/image_endian_test.cc
new file mode 100644
index 0000000..a93cb3c
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/image_endian_test.cc
@@ -0,0 +1,45 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+// Unit tests for endian-dependent operations used in image rewriting.
+
+#include "net/instaweb/rewriter/public/image.h"
+
+#include "net/instaweb/htmlparse/public/html_parse_test_base.h"
+#include "net/instaweb/util/public/gtest.h"
+
+namespace net_instaweb {
+namespace {
+
+TEST(ImageEndianTest, CharToIntTest) {
+ EXPECT_EQ(0xff, CharToInt(-1));
+ // Worried about C++ implicit conversions to/from int for arg passing:
+ EXPECT_EQ(0x05, CharToInt(static_cast<char>(0xffffff05)));
+ EXPECT_EQ(0x83, CharToInt(static_cast<char>(0xffffff83)));
+ EXPECT_EQ(0x33, CharToInt(0x33));
+ // Now test deserialization from buffer full of negative values
+ const char buf[] = { 0xf1, 0xf2, 0xf3, 0xf4 };
+ EXPECT_EQ(0xf1f2, JpegIntAtPosition(buf, 0));
+ EXPECT_EQ(0xf2f3, JpegIntAtPosition(buf, 1));
+ EXPECT_EQ(0xf2f1, GifIntAtPosition(buf, 0));
+ EXPECT_EQ(0xf4f3, GifIntAtPosition(buf, 2));
+ EXPECT_EQ(static_cast<int>(0xf1f2f3f4), PngIntAtPosition(buf, 0));
+}
+
+} // namespace
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/image_rewrite_filter_test.cc b/trunk/src/net/instaweb/rewriter/image_rewrite_filter_test.cc
new file mode 100644
index 0000000..e82e2e3
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/image_rewrite_filter_test.cc
@@ -0,0 +1,348 @@
+/*
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#include "net/instaweb/rewriter/public/resource_manager_test_base.h"
+
+#include "net/instaweb/htmlparse/public/empty_html_filter.h"
+#include "net/instaweb/rewriter/public/img_tag_scanner.h"
+#include "net/instaweb/rewriter/public/rewrite_driver.h"
+#include "net/instaweb/rewriter/public/rewrite_options.h"
+#include "net/instaweb/util/public/mock_timer.h"
+
+namespace net_instaweb {
+
+namespace {
+
+class ImageRewriteTest : public ResourceManagerTestBase {
+ protected:
+ // Simple image rewrite test to check resource fetching functionality.
+ void RewriteImage(const std::string& tag_string) {
+ options_.EnableFilter(RewriteOptions::kRewriteImages);
+ options_.EnableFilter(RewriteOptions::kInsertImgDimensions);
+ options_.set_img_inline_max_bytes(2000);
+ rewrite_driver_.AddFilters();
+
+ AddOtherFilter(RewriteOptions::kRewriteImages);
+
+ // URLs and content for HTML document and resources.
+ const char domain[] = "http://rewrite_image.test/";
+ const char html_url[] = "http://rewrite_image.test/RewriteImage.html";
+ const char image_url[] = "http://rewrite_image.test/Puzzle.jpg";
+
+ const std::string image_html =
+ StrCat("<head/><body><", tag_string, " src=\"Puzzle.jpg\"/></body>");
+
+ // Store image contents into fetcher.
+ const std::string image_filename =
+ StrCat(GTestSrcDir(), kTestData, "Puzzle.jpg");
+ AddFileToMockFetcher(image_url, image_filename, kContentTypeJpeg);
+
+ // Rewrite the HTML page.
+ ParseUrl(html_url, image_html);
+ StringVector img_srcs;
+ CollectImgSrcs("RewriteImage/collect_sources", output_buffer_, &img_srcs);
+ // output_buffer_ should have exactly one image file (Puzzle.jpg).
+ EXPECT_EQ(1UL, img_srcs.size());
+ const std::string& src_string = img_srcs[0];
+ EXPECT_EQ(domain, src_string.substr(0, strlen(domain)));
+ EXPECT_EQ(".jpg", src_string.substr(src_string.size() - 4, 4));
+
+ std::string rewritten_data;
+
+ const std::string expected_output =
+ StrCat("<head/><body><", tag_string, " src=\"", src_string,
+ "\" width=\"1023\" height=\"766\"/></body>");
+ EXPECT_EQ(AddHtmlBody(expected_output), output_buffer_);
+
+ std::string rewritten_filename;
+ filename_encoder_.Encode(file_prefix_, src_string, &rewritten_filename);
+
+ std::string rewritten_image_data;
+ ASSERT_TRUE(file_system_.ReadFile(rewritten_filename.c_str(),
+ &rewritten_image_data,
+ &message_handler_));
+
+ // Also fetch the resource to ensure it can be created dynamically
+ SimpleMetaData request_headers, response_headers;
+ std::string fetched_resource_content;
+ StringWriter writer(&fetched_resource_content);
+ DummyCallback dummy_callback(true);
+
+ std::string headers;
+ AppendDefaultHeaders(kContentTypeJpeg, resource_manager_, &headers);
+
+ writer.Write(headers, &message_handler_);
+ EXPECT_TRUE(
+ rewrite_driver_.FetchResource(src_string, request_headers,
+ &response_headers, &writer,
+ &message_handler_, &dummy_callback));
+ EXPECT_EQ(HttpStatus::kOK, response_headers.status_code()) <<
+ "Looking for " << src_string;
+ // For readability, only do EXPECT_EQ on initial portions of data
+ // as most of it isn't human-readable. This will show us the headers
+ // and the start of the image data. So far every failure fails this
+ // first, and we caught doubled headers this way.
+ EXPECT_EQ(rewritten_image_data.substr(0, 250),
+ fetched_resource_content.substr(0, 250)) <<
+ "In " << src_string;
+ EXPECT_TRUE(rewritten_image_data == fetched_resource_content) <<
+ "In " << src_string;
+
+ // Now we fetch from the "other" server. To simulate first fetch, we
+ // need to:
+ // 1) Clear the cache, so we don't just find the result there.
+ // TODO(sligocki): We should just use a separate cache.
+ lru_cache_->Clear();
+ // 2) Disable the file system, so we don't find the resource there.
+ // TODO(sligocki): We should just use a separate mem_file_system.
+ file_system_.Disable();
+ // 3) Disable the fetcher, so that the fetch doesn't finish imidiately.
+ // TODO(sligocki): We could use the CallCallbacks() trick to get a more
+ // correct response, rather than effectively shutting off our network.
+ mock_url_fetcher_.Disable();
+
+ fetched_resource_content.clear();
+ dummy_callback.Reset();
+ SimpleMetaData redirect_headers;
+ other_rewrite_driver_.FetchResource(src_string, request_headers,
+ &redirect_headers, &writer,
+ &message_handler_, &dummy_callback);
+ std::string expected_redirect =
+ StrCat("<img src=\"", image_url, "\" alt=\"Temporarily Moved\"/>");
+
+ EXPECT_EQ(HttpStatus::kTemporaryRedirect, redirect_headers.status_code());
+ EXPECT_EQ(expected_redirect, fetched_resource_content);
+
+ // Now we switch the file system and fetcher back on, corresponding to
+ // the case where a prior async fetch completed and we're ready to rewrite.
+ // This should yield the same results as the original html- driven rewrite.
+ file_system_.Enable();
+ mock_url_fetcher_.Enable();
+
+ // TODO(jmarantz): Refactor this code which tries the serving-fetch twice,
+ // the second after the cache stops 'remembering' that the origin fetch
+ // failed.
+ message_handler_.Message(
+ kInfo, "Now with serving, but with a recent fetch failure.");
+ SimpleMetaData other_headers;
+ // size_t header_size = fetched_resource_content.size();
+ dummy_callback.Reset();
+
+ // Trying this twice. The first time, we will again fail to rewrite
+ // the resource because the cache will 'remembered' that the image origin
+ // fetch failed and so the resource manager will not try again for 5
+ // minutes.
+ fetched_resource_content.clear();
+ other_rewrite_driver_.FetchResource(src_string, request_headers,
+ &other_headers, &writer,
+ &message_handler_, &dummy_callback);
+ EXPECT_EQ(HttpStatus::kTemporaryRedirect, redirect_headers.status_code());
+ EXPECT_EQ(expected_redirect, fetched_resource_content);
+
+ // Now let some time go by and try again.
+ message_handler_.Message(
+ kInfo, "Now with serving, but 5 minutes expired since fetch failure.");
+ other_file_system_.timer()->advance_ms(
+ 5 * Timer::kMinuteMs + 1 * Timer::kSecondMs);
+ fetched_resource_content.clear();
+ writer.Write(headers, &message_handler_);
+ EXPECT_EQ(headers, fetched_resource_content);
+ dummy_callback.Reset();
+ other_rewrite_driver_.FetchResource(src_string, request_headers,
+ &other_headers, &writer,
+ &message_handler_, &dummy_callback);
+ EXPECT_EQ(HttpStatus::kOK, other_headers.status_code());
+ EXPECT_EQ(rewritten_image_data.substr(0, 100),
+ fetched_resource_content.substr(0, 100));
+ EXPECT_TRUE(rewritten_image_data == fetched_resource_content);
+
+ std::string secondary_image_data;
+ ASSERT_TRUE(file_system_.ReadFile(rewritten_filename.c_str(),
+ &secondary_image_data,
+ &message_handler_));
+ EXPECT_EQ(rewritten_image_data.substr(0, 100),
+ secondary_image_data.substr(0, 100));
+ EXPECT_EQ(rewritten_image_data, secondary_image_data);
+
+ // Try to fetch from an independent server.
+ /* TODO(sligocki): Get this working. Right now it returns a redirect.
+ ServeResourceFromManyContexts(src_string, RewriteOptions::kRewriteImages,
+ &mock_hasher_,
+ fetched_resource_content.substr(header_size));
+ */
+ }
+
+ // Helper class to collect img srcs.
+ class ImgCollector : public EmptyHtmlFilter {
+ public:
+ ImgCollector(HtmlParse* html_parse, StringVector* img_srcs)
+ : img_srcs_(img_srcs),
+ img_filter_(html_parse) {
+ }
+
+ virtual void StartElement(HtmlElement* element) {
+ HtmlElement::Attribute* src = img_filter_.ParseImgElement(element);
+ if (src != NULL) {
+ img_srcs_->push_back(src->value());
+ }
+ }
+
+ virtual const char* Name() const { return "ImgCollector"; }
+
+ private:
+ StringVector* img_srcs_;
+ ImgTagScanner img_filter_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImgCollector);
+ };
+
+ // Fills `img_srcs` with the urls in img src attributes in `html`
+ void CollectImgSrcs(const StringPiece& id, const StringPiece& html,
+ StringVector* img_srcs) {
+ HtmlParse html_parse(&message_handler_);
+ ImgCollector collector(&html_parse, img_srcs);
+ html_parse.AddFilter(&collector);
+ std::string dummy_url = StrCat("http://collect.css.links/", id, ".html");
+ html_parse.StartParse(dummy_url);
+ html_parse.ParseText(html.data(), html.size());
+ html_parse.FinishParse();
+ }
+
+ void DataUrlResource() {
+ static const char* kCuppaData = "data:image/png;base64,"
+ "iVBORw0KGgoAAAANSUhEUgAAAEEAAABGCAAAAAC2maYhAAAC00lEQVQY0+3PTUhUYR"
+ "QG4HdmMhUaC6FaKSqEZS2MsEJEsaKSwMKgot2QkkKFUFBYWgSpGIhSZH+0yAgLDQ3p"
+ "ByoLRS2DjCjEfm0MzQhK08wZ5/Sde12kc8f5DrXLs3lfPs55uBf0t4MZ4X8QLjeY2X"
+ "C80cieUq9M6MB6I7tDcMgoRWgVCb5VyDLKFuCK8RCHMpFwEzjA+coGdHJ5COwRCSnA"
+ "Jc4cwOnlshs4KhFeA+jib48A1hovK4A6iXADiOB8oyQXF28Y0CIRKgDHsMoeJaTyw6"
+ "gDOC0RGtXlPS5RQOgAlwQgWSK4lZDDZacqxVyOqNIpECgSiBxTeVsdRo/z/9iBXImw"
+ "TV3eUemLU6WRXzYCziGB0KAOs7kUqLKZS40qVwVCr9qP4vJElblc3KocFAi+cMD2U5"
+ "VBdYhPqgyp3CcQKEYdDHCZDYT/mviYa5JvCANiubxTh2u4XAAcfQLhgzrM51KjSjmX"
+ "FGAvCYRTQGgvlwwggX/iGbDwm0RIAwo439tga+biAqpJIHy2I36Uyxkgl7MnBJkkEV"
+ "4AtUbJQvwP86/m94uE71juM8piPDayDOdJJNDKFjMzNpl5fcmYUPBMZIfbzBE3CQXB"
+ "TBIuHtaYwo5phHToTMk0QqaWUNxUUXrui7XggvZEFI9YCfu1AQeQbiWc0LrOe9D11Z"
+ "cNtFsIVVpCG696YrHVQqjVAezDxm4hEi2ElzpCvLl7EkkWwliIhrDD3K1EsoVASzWE"
+ "UnM1DbushO0aQpux2Qw8shJKggPzvLzYl4BYn5XQHVzI4r2Pi4CzZCVQUlChimi0cg"
+ "GQR9ZCRVDhbl1RtIoNngBC/yzozLJqLwUQqCjotTPR1fTnxVTBs3ra89T6/ikHfgK9"
+ "dQa+t1eS//gJVB8WUCgnLYHaYwIAeaQp0GC25S8cG9cWiOrm+AHrnhMJBLplmwLkE8"
+ "kEenp/8oyIBf2ZEWaEfyv8BsICdAZ/XeTCAAAAAElFTkSuQmCC";
+ std::string cuppa_string(kCuppaData);
+ scoped_ptr<Resource> cuppa_resource(
+ resource_manager_->CreateInputResourceAbsolute(cuppa_string,
+ &options_,
+ &message_handler_));
+ ASSERT_TRUE(cuppa_resource != NULL);
+ EXPECT_TRUE(resource_manager_->ReadIfCached(cuppa_resource.get(),
+ &message_handler_));
+ std::string cuppa_contents;
+ cuppa_resource->contents().CopyToString(&cuppa_contents);
+ // Now make sure axing the original cuppa_string doesn't affect the
+ // internals of the cuppa_resource.
+ scoped_ptr<Resource> other_resource(
+ resource_manager_->CreateInputResourceAbsolute(cuppa_string,
+ &options_,
+ &message_handler_));
+ ASSERT_TRUE(other_resource != NULL);
+ cuppa_string.clear();
+ EXPECT_TRUE(resource_manager_->ReadIfCached(other_resource.get(),
+ &message_handler_));
+ std::string other_contents;
+ cuppa_resource->contents().CopyToString(&other_contents);
+ ASSERT_EQ(cuppa_contents, other_contents);
+ }
+};
+
+TEST_F(ImageRewriteTest, ImgTag) {
+ RewriteImage("img");
+}
+
+TEST_F(ImageRewriteTest, InputTag) {
+ RewriteImage("input type=\"image\"");
+}
+
+TEST_F(ImageRewriteTest, DataUrlTest) {
+ DataUrlResource();
+}
+
+TEST_F(ImageRewriteTest, RespectsBaseUrl) {
+ // Put original files into our fetcher.
+ const char html_url[] = "http://image.test/base_url.html";
+ const char png_url[] = "http://other_domain.test/foo/bar/a.png";
+ const char jpeg_url[] = "http://other_domain.test/baz/b.jpeg";
+
+ AddFileToMockFetcher(png_url,
+ StrCat(GTestSrcDir(), kTestData, "BikeCrashIcn.png"),
+ kContentTypePng);
+ AddFileToMockFetcher(jpeg_url,
+ StrCat(GTestSrcDir(), kTestData, "Puzzle.jpg"),
+ kContentTypeJpeg);
+
+ // Second stylesheet is on other domain.
+ const char html_input[] =
+ "<head>\n"
+ " <base href='http://other_domain.test/foo/'>\n"
+ "</head>\n"
+ "<body>\n"
+ " <img src='bar/a.png'>\n"
+ " <img src='/baz/b.jpeg'>\n"
+ "</body>";
+
+ // Rewrite
+ AddFilter(RewriteOptions::kRewriteImages);
+ ParseUrl(html_url, html_input);
+
+ // Check for CSS files in the rewritten page.
+ StringVector image_urls;
+ CollectImgSrcs("base_url-links", output_buffer_, &image_urls);
+ EXPECT_EQ(2UL, image_urls.size());
+ const std::string& new_png_url = image_urls[0];
+ const std::string& new_jpeg_url = image_urls[1];
+
+ // Sanity check that we changed the URL.
+ EXPECT_NE("a.png", new_png_url);
+ EXPECT_NE("b.jpeg", new_jpeg_url);
+
+ LOG(INFO) << "new_png_url: " << new_png_url;
+ LOG(INFO) << "new_jpeg_url: " << new_jpeg_url;
+
+ const char expected_output_format[] =
+ "<head>\n"
+ " <base href='http://other_domain.test/foo/'>\n"
+ "</head>\n"
+ "<body>\n"
+ " <img src='%s'>\n"
+ " <img src='%s'>\n"
+ "</body>";
+ std::string expected_output = StringPrintf(expected_output_format,
+ new_png_url.c_str(),
+ new_jpeg_url.c_str());
+
+ EXPECT_EQ(AddHtmlBody(expected_output), output_buffer_);
+
+ GURL new_png_gurl = GoogleUrl::Create(new_png_url);
+ EXPECT_TRUE(new_png_gurl.is_valid());
+ EXPECT_EQ("other_domain.test", new_png_gurl.host());
+
+ GURL new_jpeg_gurl = GoogleUrl::Create(new_jpeg_url);
+ EXPECT_TRUE(new_jpeg_gurl.is_valid());
+ EXPECT_EQ("other_domain.test", new_jpeg_gurl.host());
+}
+
+} // namespace
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/img_rewrite_filter.cc b/trunk/src/net/instaweb/rewriter/img_rewrite_filter.cc
new file mode 100644
index 0000000..2b1ffce
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/img_rewrite_filter.cc
@@ -0,0 +1,474 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#include "net/instaweb/rewriter/public/img_rewrite_filter.h"
+
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/rewriter/public/image.h"
+#include "net/instaweb/rewriter/public/image_dim.h"
+#include "net/instaweb/rewriter/public/img_tag_scanner.h"
+#include "net/instaweb/rewriter/public/output_resource.h"
+#include "net/instaweb/rewriter/public/resource_manager.h"
+#include "net/instaweb/util/public/atom.h"
+#include "net/instaweb/util/public/content_type.h"
+#include "net/instaweb/util/public/data_url.h"
+#include "net/instaweb/util/public/file_system.h"
+#include <string>
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/meta_data.h"
+#include "net/instaweb/util/public/statistics.h"
+#include "net/instaweb/util/public/statistics_work_bound.h"
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/string_writer.h"
+#include "net/instaweb/util/public/url_escaper.h"
+
+namespace net_instaweb {
+
+namespace {
+
+// Rewritten image must be < kMaxRewrittenRatio * origSize to be worth
+// redirecting references to it.
+// TODO(jmaessen): Make this ratio adjustable.
+const double kMaxRewrittenRatio = 1.0;
+
+// Re-scale image if area / originalArea < kMaxAreaRatio
+// Should probably be much less than 1 due to jpeg quality loss.
+// Might need to differ depending upon img format.
+// TODO(jmaessen): Make adjustable.
+const double kMaxAreaRatio = 1.0;
+
+// We overload some http status codes for our own purposes
+
+// This is used to retain the knowledge that a particular image is not
+// profitable to optimize. According to pagespeed, 200, 203, 206, and
+// 304 are cacheable. So we must select from those.
+const HttpStatus::Code kNotOptimizable = HttpStatus::kNotModified; // 304
+
+// names for Statistics variables.
+const char kImageRewrites[] = "image_rewrites";
+const char kImageRewriteSavedBytes[] = "image_rewrite_saved_bytes";
+const char kImageInline[] = "image_inline";
+
+// name for statistic used to bound rewriting work.
+const char kImageOngoingRewrites[] = "image_ongoing_rewrites";
+
+} // namespace
+
+
+ImageUrlEncoder::ImageUrlEncoder(UrlEscaper* url_escaper, ImageDim* stored_dim)
+ : url_escaper_(url_escaper), stored_dim_(stored_dim) { }
+
+ImageUrlEncoder::~ImageUrlEncoder() { }
+
+void ImageUrlEncoder::EncodeToUrlSegment(
+ const StringPiece& origin_url, std::string* rewritten_url) {
+ stored_dim_->EncodeTo(rewritten_url);
+ url_escaper_->EncodeToUrlSegment(origin_url, rewritten_url);
+}
+
+bool ImageUrlEncoder::DecodeFromUrlSegment(
+ const StringPiece& rewritten_url, std::string* origin_url) {
+ // Note that "remaining" is shortened from the left as we parse.
+ StringPiece remaining(rewritten_url.data(), rewritten_url.size());
+ return (stored_dim_->DecodeFrom(&remaining) &&
+ url_escaper_->DecodeFromUrlSegment(remaining, origin_url));
+}
+
+ImgRewriteFilter::ImgRewriteFilter(RewriteDriver* driver,
+ bool log_image_elements,
+ bool insert_image_dimensions,
+ StringPiece path_prefix,
+ size_t img_inline_max_bytes,
+ size_t img_max_rewrites_at_once)
+ : RewriteFilter(driver, path_prefix),
+ file_system_(driver->file_system()),
+ html_parse_(driver->html_parse()),
+ img_filter_(new ImgTagScanner(html_parse_)),
+ resource_manager_(driver->resource_manager()),
+ img_inline_max_bytes_(img_inline_max_bytes),
+ log_image_elements_(log_image_elements),
+ insert_image_dimensions_(insert_image_dimensions),
+ s_width_(html_parse_->Intern("width")),
+ s_height_(html_parse_->Intern("height")),
+ rewrite_count_(NULL),
+ inline_count_(NULL),
+ rewrite_saved_bytes_(NULL) {
+ Statistics* stats = resource_manager_->statistics();
+ Variable* ongoing_rewrites = NULL;
+ if (stats != NULL) {
+ rewrite_count_ = stats->GetVariable(kImageRewrites);
+ rewrite_saved_bytes_ = stats->GetVariable(
+ kImageRewriteSavedBytes);
+ inline_count_ = stats->GetVariable(kImageInline);
+ ongoing_rewrites = stats->GetVariable(kImageOngoingRewrites);
+ }
+ work_bound_.reset(
+ new StatisticsWorkBound(ongoing_rewrites, img_max_rewrites_at_once));
+}
+
+void ImgRewriteFilter::Initialize(Statistics* statistics) {
+ statistics->AddVariable(kImageInline);
+ statistics->AddVariable(kImageRewriteSavedBytes);
+ statistics->AddVariable(kImageRewrites);
+
+ statistics->AddVariable(kImageOngoingRewrites);
+}
+
+void ImgRewriteFilter::OptimizeImage(
+ const Resource& input_resource, const ImageDim& page_dim,
+ Image* image, OutputResource* result) {
+ if (result != NULL && !result->IsWritten() && image != NULL &&
+ work_bound_->TryToWork()) {
+ ImageDim img_dim;
+ image->Dimensions(&img_dim);
+ const char* message; // Informational message for logging only.
+ if (page_dim.valid() && img_dim.valid()) {
+ int64 page_area =
+ static_cast<int64>(page_dim.width()) * page_dim.height();
+ int64 img_area = static_cast<int64>(img_dim.width()) * img_dim.height();
+ if (page_area < img_area * kMaxAreaRatio) {
+ if (image->ResizeTo(page_dim)) {
+ message = "Resized image";
+ } else {
+ message = "Couldn't resize image";
+ }
+ } else {
+ message = "Not worth resizing image";
+ }
+ html_parse_->InfoHere("%s `%s' from %dx%d to %dx%d", message,
+ input_resource.url().c_str(),
+ img_dim.width(), img_dim.height(),
+ page_dim.width(), page_dim.height());
+ }
+ // Unconditionally write resource back so we don't re-attempt optimization.
+ MessageHandler* message_handler = html_parse_->message_handler();
+
+ int64 origin_expire_time_ms = input_resource.CacheExpirationTimeMs();
+ if (image->output_size() <
+ image->input_size() * kMaxRewrittenRatio) {
+ if (resource_manager_->Write(
+ HttpStatus::kOK, image->Contents(), result,
+ origin_expire_time_ms, message_handler)) {
+ html_parse_->InfoHere(
+ "Shrinking image `%s' (%u bytes) to `%s' (%u bytes)",
+ input_resource.url().c_str(),
+ static_cast<unsigned>(image->input_size()),
+ result->url().c_str(),
+ static_cast<unsigned>(image->output_size()));
+
+ if (rewrite_saved_bytes_ != NULL) {
+ // Note: if we are serving a request from a different server
+ // than the server that rewrote the <img> tag, and they don't
+ // share a file system, then we will be bumping the byte-count
+ // here without bumping the rewrite count. This seems ok,
+ // though perhaps we may need to revisit.
+ //
+ // Currently this will be a problem even when serving on a
+ // different file that *does* share a filesystem,
+ // HashResourceManager does not yet load its internal map
+ // by scanning the filesystem on startup.
+ rewrite_saved_bytes_->Add(
+ image->input_size() - image->output_size());
+ }
+ }
+ } else {
+ // Write nothing and set status code to indicate not to rewrite
+ // in future.
+ resource_manager_->Write(kNotOptimizable, "", result,
+ origin_expire_time_ms, message_handler);
+ }
+ work_bound_->WorkComplete();
+ }
+}
+
+Image* ImgRewriteFilter::GetImage(const StringPiece& origin_url,
+ Resource* img_resource) {
+ Image* image = NULL;
+ MessageHandler* message_handler = html_parse_->message_handler();
+ if (img_resource == NULL) {
+ html_parse_->WarningHere("no input resource for %s",
+ origin_url.as_string().c_str());
+ } else if (!resource_manager_->ReadIfCached(img_resource, message_handler)) {
+ html_parse_->WarningHere("%s wasn't loaded",
+ img_resource->url().c_str());
+ } else if (!img_resource->ContentsValid()) {
+ html_parse_->WarningHere("Img contents from %s are invalid.",
+ img_resource->url().c_str());
+ } else {
+ image = new Image(img_resource->contents(), img_resource->url(),
+ resource_manager_->filename_prefix(), file_system_,
+ message_handler);
+ }
+ return image;
+}
+
+bool ImgRewriteFilter::OptimizedImageFor(
+ const StringPiece& origin_url, const ImageDim& page_dim,
+ Resource* img_resource, OutputResource* output) {
+ scoped_ptr<Image> image(GetImage(origin_url, img_resource));
+ bool ok = image.get() != NULL && image->image_type() != Image::IMAGE_UNKNOWN;
+ if (ok) {
+ OptimizeImage(*img_resource, page_dim, image.get(), output);
+ }
+ return ok;
+}
+
+// Convert (possibly NULL) Image* to corresponding (possibly NULL) ContentType*
+const ContentType* ImgRewriteFilter::ImageToContentType(
+ const std::string& origin_url, Image* image) {
+ const ContentType* content_type = NULL;
+ if (image != NULL) {
+ // Even if we know the content type from the extension coming
+ // in, the content-type can change as a result of compression,
+ // e.g. gif to png, or anything to vp8.
+ switch (image->image_type()) {
+ case Image::IMAGE_JPEG:
+ content_type = &kContentTypeJpeg;
+ break;
+ case Image::IMAGE_PNG:
+ content_type = &kContentTypePng;
+ break;
+ case Image::IMAGE_GIF:
+ content_type = &kContentTypeGif;
+ break;
+ default:
+ html_parse_->InfoHere(
+ "Cannot detect content type of image url `%s`",
+ origin_url.c_str());
+ break;
+ }
+ }
+ return content_type;
+}
+
+void ImgRewriteFilter::RewriteImageUrl(HtmlElement* element,
+ HtmlElement::Attribute* src) {
+ // TODO(jmaessen): content type can change after re-compression.
+ // How do we deal with that given only URL?
+ // Separate input and output content type?
+ MessageHandler* message_handler = html_parse_->message_handler();
+ scoped_ptr<Resource> input_resource(CreateInputResourceAndReadIfCached(
+ src->value()));
+ if (input_resource != NULL && input_resource->ContentsValid()) {
+ ImageDim page_dim;
+ // Always rewrite to absolute url used to obtain resource.
+ // This lets us do context-free fetches of content.
+ int width, height;
+ if (element->IntAttributeValue(s_width_, &width) &&
+ element->IntAttributeValue(s_height_, &height)) {
+ // Specific image size is called for. Rewrite to that size.
+ page_dim.set_dims(width, height);
+ }
+ scoped_ptr<Image> image(GetImage(src->value(), input_resource.get()));
+ const ContentType* content_type =
+ ImageToContentType(src->value(), image.get());
+
+ if (content_type != NULL) {
+ ImageDim actual_dim;
+ image->Dimensions(&actual_dim);
+ // Create an output resource and fetch it, as that will tell
+ // us if we have already optimized it, or determined that it was not
+ // worth optimizing.
+ std::string rewritten_name;
+ ImageUrlEncoder encoder(resource_manager_->url_escaper(), &page_dim);
+ scoped_ptr<OutputResource> output_resource(
+ resource_manager_->CreateOutputResourceFromResource(
+ filter_prefix_, content_type, &encoder, input_resource.get(),
+ message_handler));
+ if (output_resource.get() != NULL) {
+ if (!resource_manager_->FetchOutputResource(
+ output_resource.get(), NULL, NULL, message_handler,
+ ResourceManager::kNeverBlock)) {
+ OptimizeImage(*input_resource, page_dim, image.get(),
+ output_resource.get());
+ }
+ if (output_resource->IsWritten()) {
+ UpdateTargetElement(*input_resource, *output_resource,
+ page_dim, actual_dim, element, src);
+ }
+ }
+ }
+ }
+}
+
+bool ImgRewriteFilter::CanInline(
+ int img_inline_max_bytes, const StringPiece& contents,
+ const ContentType* content_type, std::string* data_url) {
+ bool ok = false;
+ if (content_type != NULL && contents.size() <= img_inline_max_bytes) {
+ DataUrl(*content_type, BASE64, contents, data_url);
+ ok = true;
+ }
+ return ok;
+}
+
+// Given image processing reflected in the already-written output_resource,
+// actually update the element (particularly the src attribute), and log
+// statistics on what happened.
+void ImgRewriteFilter::UpdateTargetElement(
+ const Resource& input_resource, const OutputResource& output_resource,
+ const ImageDim& page_dim, const ImageDim& actual_dim,
+ HtmlElement* element, HtmlElement::Attribute* src) {
+
+ if (actual_dim.valid() &&
+ (actual_dim.width() > 1 || actual_dim.height() > 1)) {
+ std::string inlined_url;
+ bool output_ok =
+ output_resource.metadata()->status_code() == HttpStatus::kOK;
+ bool ie6or7 = driver_->user_agent().IsIe6or7();
+ if (!ie6or7 &&
+ ((output_ok &&
+ CanInline(img_inline_max_bytes_, output_resource.contents(),
+ output_resource.type(), &inlined_url)) ||
+ CanInline(img_inline_max_bytes_, input_resource.contents(),
+ input_resource.type(), &inlined_url))) {
+ src->SetValue(inlined_url);
+ if (inline_count_ != NULL) {
+ inline_count_->Add(1);
+ }
+ } else {
+ if (output_ok) {
+ src->SetValue(output_resource.url());
+ if (rewrite_count_ != NULL) {
+ rewrite_count_->Add(1);
+ }
+ }
+ if (insert_image_dimensions_ && actual_dim.valid() && !page_dim.valid() &&
+ !element->FindAttribute(s_width_) &&
+ !element->FindAttribute(s_height_)) {
+ // Add image dimensions. We don't bother if even a single image
+ // dimension is already specified---even though we don't resize in that
+ // case, either, because we might be off by a pixel in the other
+ // dimension from the size chosen by the browser. We also don't bother
+ // to resize if either dimension is specified with units (px, em, %)
+ // rather than as absolute pixes. But note that we DO attempt to
+ // include image dimensions even if we otherwise choose not to optimize
+ // an image. This may require examining the image contents if we didn't
+ // just perform the image processing.
+ element->AddAttribute(s_width_, actual_dim.width());
+ element->AddAttribute(s_height_, actual_dim.height());
+ }
+ }
+ }
+}
+
+void ImgRewriteFilter::EndElementImpl(HtmlElement* element) {
+ HtmlElement::Attribute *src = img_filter_->ParseImgElement(element);
+ if (src != NULL) {
+ if (log_image_elements_) {
+ // We now know that element is an img tag.
+ // Log the element in its original form.
+ std::string tagstring;
+ element->ToString(&tagstring);
+ html_parse_->Info(
+ html_parse_->id(), element->begin_line_number(),
+ "Found image: %s", tagstring.c_str());
+ }
+ RewriteImageUrl(element, src);
+ }
+}
+
+void ImgRewriteFilter::Flush() {
+ // TODO(jmaessen): wait here for resources to have been rewritten??
+}
+
+bool ImgRewriteFilter::Fetch(OutputResource* resource,
+ Writer* writer,
+ const MetaData& request_header,
+ MetaData* response_headers,
+ MessageHandler* message_handler,
+ UrlAsyncFetcher::Callback* callback) {
+ bool ok = true;
+ const char* failure_reason = "";
+ const ContentType *content_type = resource->type();
+ if (content_type != NULL) {
+ ImageDim page_dim; // Dimensions given in source page (invalid if absent).
+ ImageUrlEncoder encoder(resource_manager_->url_escaper(), &page_dim);
+ scoped_ptr<Resource> input_image(
+ CreateInputResourceFromOutputResource(&encoder, resource));
+ if (input_image.get() != NULL) {
+ std::string origin_url = input_image->url();
+ // TODO(jmarantz): this needs to be refactored slightly to allow for
+ // asynchronous fetches of the input image, if it's not obtained via cache
+ // or local filesystem read.
+ if (!OptimizedImageFor(
+ origin_url, page_dim, input_image.get(), resource)) {
+ ok = false;
+ failure_reason = "Server could not find source image.";
+ } else if (!resource_manager_->FetchOutputResource(
+ resource, writer, response_headers, message_handler,
+ ResourceManager::kNeverBlock)) {
+ ok = false;
+ failure_reason = "Server could not read image resource.";
+ } else {
+ if (resource->metadata()->status_code() != HttpStatus::kOK) {
+ // Note that this should not happen, because the url should not have
+ // escaped into the wild. We're content serving an empty response if
+ // it does. We *could* serve / redirect to the origin_url as a fail
+ // safe, but it's probably not worth it. Instead we log and hope that
+ // this causes us to find and fix the problem.
+ message_handler->Error(resource->url().c_str(), 0,
+ "Rewriting %s rejected, "
+ "but URL requested (mistaken rewriting?).",
+ origin_url.c_str());
+ }
+ callback->Done(true);
+ }
+ // Image processing has failed, forward the original image data.
+ if (!ok && input_image != NULL) {
+ if (input_image->ContentsValid()) {
+ ok = writer->Write(input_image->contents(), message_handler);
+ }
+ if (ok) {
+ resource_manager_->SetDefaultHeaders(content_type, response_headers);
+ } else {
+ message_handler->Error(resource->name().as_string().c_str(), 0,
+ "%s", failure_reason);
+ ok = writer->Write("<img src=\"", message_handler);
+ ok &= writer->Write(origin_url, message_handler);
+ ok &= writer->Write("\" alt=\"Temporarily Moved\"/>",
+ message_handler);
+ response_headers->set_major_version(1);
+ response_headers->set_minor_version(1);
+ response_headers->SetStatusAndReason(HttpStatus::kTemporaryRedirect);
+ response_headers->Add(HttpAttributes::kLocation, origin_url.c_str());
+ response_headers->Add(HttpAttributes::kContentType, "text/html");
+ }
+ if (ok) {
+ callback->Done(true);
+ }
+ }
+ }
+ } else {
+ ok = false;
+ failure_reason = "Unrecognized image content type.";
+ }
+ if (!ok) {
+ writer->Write(failure_reason, message_handler);
+ response_headers->set_status_code(HttpStatus::kNotFound);
+ response_headers->set_reason_phrase(failure_reason);
+ message_handler->Error(resource->name().as_string().c_str(), 0,
+ "%s", failure_reason);
+ }
+ return ok;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/img_tag_scanner.cc b/trunk/src/net/instaweb/rewriter/img_tag_scanner.cc
new file mode 100644
index 0000000..c6db89f
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/img_tag_scanner.cc
@@ -0,0 +1,51 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#include "net/instaweb/rewriter/public/img_tag_scanner.h"
+
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/util/public/atom.h"
+
+namespace net_instaweb {
+
+ImgTagScanner::ImgTagScanner(HtmlParse* html_parse)
+ : s_img_(html_parse->Intern("img")),
+ s_input_(html_parse->Intern("input")),
+ s_src_(html_parse->Intern("src")),
+ s_type_(html_parse->Intern("type")) {
+}
+
+HtmlElement::Attribute*
+ImgTagScanner::ParseImgElement(HtmlElement* element) const {
+ // Return the src attribute of an <img> tag.
+ if (element->tag() == s_img_) {
+ return element->FindAttribute(s_src_);
+ }
+ // Return the src attribute of an <input type="image"> tag.
+ // See http://code.google.com/p/modpagespeed/issues/detail?id=86
+ if (element->tag() == s_input_) {
+ const char* type = element->AttributeValue(s_type_);
+ if (type != NULL && strcmp(type, "image") == 0) {
+ return element->FindAttribute(s_src_);
+ }
+ }
+ return NULL;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/javascript_code_block.cc b/trunk/src/net/instaweb/rewriter/javascript_code_block.cc
new file mode 100644
index 0000000..0cc2968
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/javascript_code_block.cc
@@ -0,0 +1,107 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#include "net/instaweb/rewriter/public/javascript_code_block.h"
+
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/statistics.h"
+#include "net/instaweb/util/public/string_util.h"
+#include "pagespeed/jsminify/js_minify.h"
+
+namespace net_instaweb {
+
+namespace {
+
+// Statistics names
+const char kJavascriptBlocksMinified[] = "javascript_blocks_minified";
+const char kJavascriptBytesSaved[] = "javascript_bytes_saved";
+const char kJavascriptMinificationFailures[] =
+ "javascript_minification_failures";
+const char kJavascriptTotalBlocks[] = "javascript_total_blocks";
+
+} // namespace
+
+JavascriptRewriteConfig::JavascriptRewriteConfig(Statistics* stats)
+ : minify_(true),
+ redirect_(true),
+ blocks_minified_(NULL),
+ bytes_saved_(NULL),
+ minification_failures_(NULL),
+ total_blocks_(NULL) {
+ if (stats != NULL) {
+ blocks_minified_ = stats->GetVariable(kJavascriptBlocksMinified);
+ bytes_saved_ = stats->GetVariable(kJavascriptBytesSaved);
+ minification_failures_ =
+ stats->GetVariable(kJavascriptMinificationFailures);
+ total_blocks_ = stats->GetVariable(kJavascriptTotalBlocks);
+ }
+}
+
+void JavascriptRewriteConfig::Initialize(Statistics* statistics) {
+ statistics->AddVariable(kJavascriptBlocksMinified);
+ statistics->AddVariable(kJavascriptBytesSaved);
+ statistics->AddVariable(kJavascriptMinificationFailures);
+ statistics->AddVariable(kJavascriptTotalBlocks);
+}
+
+JavascriptCodeBlock::JavascriptCodeBlock(
+ const StringPiece& original_code, JavascriptRewriteConfig* config,
+ MessageHandler* handler)
+ : config_(config),
+ handler_(handler),
+ original_code_(original_code.data(), original_code.size()),
+ output_code_(original_code_),
+ rewritten_(false) { }
+
+JavascriptCodeBlock::~JavascriptCodeBlock() { }
+
+const JavascriptLibraryId JavascriptCodeBlock::ComputeJavascriptLibrary() {
+ // We always RewriteIfNecessary just to provide a degree of
+ // predictability to the rewrite flow.
+ RewriteIfNecessary();
+ if (!config_->redirect()) {
+ return JavascriptLibraryId();
+ }
+ return JavascriptLibraryId::Find(rewritten_code_);
+}
+
+void JavascriptCodeBlock::Rewrite() {
+ // Before attempting library identification, we minify. However, we only
+ // point output_code_ at the minified code if we actually want to serve it to
+ // the rest of the universe.
+ config_->AddBlock();
+ if ((config_->minify() || config_->redirect())) {
+ if (!pagespeed::jsminify::MinifyJs(original_code_, &rewritten_code_)) {
+ handler_->Message(kError, "Minification failed. Preserving old code.");
+ config_->AddMinificationFailure();
+ TrimWhitespace(original_code_, &rewritten_code_);
+ }
+ } else {
+ TrimWhitespace(original_code_, &rewritten_code_);
+ }
+
+ if (config_->minify()) {
+ output_code_ = rewritten_code_;
+ if (rewritten_code_.size() < original_code_.size()) {
+ size_t savings = original_code_.size() - rewritten_code_.size();
+ config_->AddBytesSaved(savings);
+ }
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/javascript_code_block_test.cc b/trunk/src/net/instaweb/rewriter/javascript_code_block_test.cc
new file mode 100644
index 0000000..261f31b
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/javascript_code_block_test.cc
@@ -0,0 +1,213 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#include "net/instaweb/rewriter/public/javascript_code_block.h"
+
+#include "net/instaweb/util/public/google_message_handler.h"
+#include "net/instaweb/util/public/gtest.h"
+#include "net/instaweb/util/public/simple_stats.h"
+#include <string>
+
+namespace net_instaweb {
+
+namespace {
+
+// This sample code comes from Douglas Crockford's jsmin example.
+// The same code is used to test jsminify in pagespeed.
+const std::string kBeforeCompilation =
+ "// is.js\n"
+ "\n"
+ "// (c) 2001 Douglas Crockford\n"
+ "// 2001 June 3\n"
+ "\n"
+ "\n"
+ "// is\n"
+ "\n"
+ "// The -is- object is used to identify the browser. "
+ "Every browser edition\n"
+ "// identifies itself, but there is no standard way of doing it, "
+ "and some of\n"
+ "// the identification is deceptive. This is because the authors of web\n"
+ "// browsers are liars. For example, Microsoft's IE browsers claim to be\n"
+ "// Mozilla 4. Netscape 6 claims to be version 5.\n"
+ "\n"
+ "var is = {\n"
+ " ie: navigator.appName == 'Microsoft Internet Explorer',\n"
+ " java: navigator.javaEnabled(),\n"
+ " ns: navigator.appName == 'Netscape',\n"
+ " ua: navigator.userAgent.toLowerCase(),\n"
+ " version: parseFloat(navigator.appVersion.substr(21)) ||\n"
+ " parseFloat(navigator.appVersion),\n"
+ " win: navigator.platform == 'Win32'\n"
+ "}\n"
+ "is.mac = is.ua.indexOf('mac') >= 0;\n"
+ "if (is.ua.indexOf('opera') >= 0) {\n"
+ " is.ie = is.ns = false;\n"
+ " is.opera = true;\n"
+ "}\n"
+ "if (is.ua.indexOf('gecko') >= 0) {\n"
+ " is.ie = is.ns = false;\n"
+ " is.gecko = true;\n"
+ "}\n";
+
+const std::string kTruncatedComment =
+ "// is.js\n"
+ "\n"
+ "// (c) 2001 Douglas Crockford\n"
+ "// 2001 June 3\n"
+ "\n"
+ "\n"
+ "// is\n"
+ "\n"
+ "/* The -is- object is used to identify the browser. "
+ "Every browser edition\n"
+ " identifies itself, but there is no standard way of doing it, "
+ "and some of\n";
+
+const std::string kTruncatedRewritten =
+ "// is.js\n"
+ "\n"
+ "// (c) 2001 Douglas Crockford\n"
+ "// 2001 June 3\n"
+ "\n"
+ "\n"
+ "// is\n"
+ "\n"
+ "/* The -is- object is used to identify the browser. "
+ "Every browser edition\n"
+ " identifies itself, but there is no standard way of doing it, "
+ "and some of";
+
+const std::string kTruncatedString =
+ "var is = {\n"
+ " ie: navigator.appName == 'Microsoft Internet Explo";
+
+const std::string kAfterCompilation =
+ "var is={ie:navigator.appName=='Microsoft Internet Explorer',"
+ "java:navigator.javaEnabled(),ns:navigator.appName=='Netscape',"
+ "ua:navigator.userAgent.toLowerCase(),version:parseFloat("
+ "navigator.appVersion.substr(21))||parseFloat(navigator.appVersion)"
+ ",win:navigator.platform=='Win32'}\n"
+ "is.mac=is.ua.indexOf('mac')>=0;if(is.ua.indexOf('opera')>=0){"
+ "is.ie=is.ns=false;is.opera=true;}\n"
+ "if(is.ua.indexOf('gecko')>=0){is.ie=is.ns=false;is.gecko=true;}";
+
+const char kJavascriptBlocksMinified[] = "javascript_blocks_minified";
+const char kJavascriptBytesSaved[] = "javascript_bytes_saved";
+const char kJavascriptMinificationFailures[] =
+ "javascript_minification_failures";
+const char kJavascriptTotalBlocks[] = "javascript_total_blocks";
+
+void ExpectStats(const SimpleStats& stats,
+ int total_blocks, int minified_blocks, int failures,
+ int saved_bytes) {
+ EXPECT_EQ(minified_blocks,
+ stats.GetVariable(kJavascriptBlocksMinified)->Get());
+ EXPECT_EQ(total_blocks,
+ stats.GetVariable(kJavascriptTotalBlocks)->Get());
+ EXPECT_EQ(failures,
+ stats.GetVariable(kJavascriptMinificationFailures)->Get());
+ EXPECT_EQ(saved_bytes,
+ stats.GetVariable(kJavascriptBytesSaved)->Get());
+}
+
+TEST(JsCodeBlockTest, Config) {
+ SimpleStats stats;
+ JavascriptRewriteConfig::Initialize(&stats);
+ JavascriptRewriteConfig config(&stats);
+ EXPECT_TRUE(config.minify());
+ config.set_minify(false);
+ EXPECT_FALSE(config.minify());
+ config.set_minify(true);
+ EXPECT_TRUE(config.minify());
+ ExpectStats(stats, 0, 0, 0, 0);
+}
+
+TEST(JsCodeBlockTest, Rewrite) {
+ SimpleStats stats;
+ JavascriptRewriteConfig::Initialize(&stats);
+ JavascriptRewriteConfig config(&stats);
+ GoogleMessageHandler handler;
+ JavascriptCodeBlock block(kBeforeCompilation, &config, &handler);
+ EXPECT_TRUE(block.ProfitableToRewrite());
+ EXPECT_EQ(kAfterCompilation, block.Rewritten());
+ ExpectStats(stats, 1, 1, 0,
+ kBeforeCompilation.size() - kAfterCompilation.size());
+}
+
+TEST(JsCodeBlockTest, NoRewrite) {
+ SimpleStats stats;
+ JavascriptRewriteConfig::Initialize(&stats);
+ JavascriptRewriteConfig config(&stats);
+ GoogleMessageHandler handler;
+ JavascriptCodeBlock block(kAfterCompilation, &config, &handler);
+ EXPECT_FALSE(block.ProfitableToRewrite());
+ EXPECT_EQ(kAfterCompilation, block.Rewritten());
+ ExpectStats(stats, 1, 0, 0, 0);
+}
+
+TEST(JsCodeBlockTest, TruncatedComment) {
+ SimpleStats stats;
+ JavascriptRewriteConfig::Initialize(&stats);
+ JavascriptRewriteConfig config(&stats);
+ GoogleMessageHandler handler;
+ JavascriptCodeBlock block(kTruncatedComment, &config, &handler);
+ EXPECT_TRUE(block.ProfitableToRewrite());
+ EXPECT_EQ(kTruncatedRewritten, block.Rewritten());
+ ExpectStats(stats, 1, 1, 1,
+ kTruncatedComment.size() - kTruncatedRewritten.size());
+}
+
+TEST(JsCodeBlockTest, TruncatedString) {
+ SimpleStats stats;
+ JavascriptRewriteConfig::Initialize(&stats);
+ JavascriptRewriteConfig config(&stats);
+ GoogleMessageHandler handler;
+ JavascriptCodeBlock block(kTruncatedString, &config, &handler);
+ EXPECT_FALSE(block.ProfitableToRewrite());
+ EXPECT_EQ(kTruncatedString, block.Rewritten());
+ ExpectStats(stats, 1, 0, 1, 0);
+}
+
+TEST(JsCodeBlockTest, NoMinification) {
+ SimpleStats stats;
+ JavascriptRewriteConfig::Initialize(&stats);
+ JavascriptRewriteConfig config(&stats);
+ config.set_minify(false);
+ GoogleMessageHandler handler;
+ JavascriptCodeBlock block(kBeforeCompilation, &config, &handler);
+ EXPECT_FALSE(block.ProfitableToRewrite());
+ EXPECT_EQ(kBeforeCompilation, block.Rewritten());
+ ExpectStats(stats, 1, 0, 0, 0);
+}
+
+TEST(JsCodeBlockTest, DealWithSgmlComment) {
+ SimpleStats stats;
+ JavascriptRewriteConfig::Initialize(&stats);
+ JavascriptRewriteConfig config(&stats);
+ GoogleMessageHandler handler;
+ const std::string original = " <!-- \nvar x = 1;\n //--> ";
+ const std::string expected = "var x=1;";
+ JavascriptCodeBlock block(original, &config, &handler);
+ EXPECT_TRUE(block.ProfitableToRewrite());
+ EXPECT_EQ(expected, block.Rewritten());
+ ExpectStats(stats, 1, 1, 0, original.size() - expected.size());
+}
+
+} // namespace
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/javascript_filter.cc b/trunk/src/net/instaweb/rewriter/javascript_filter.cc
new file mode 100644
index 0000000..f763585
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/javascript_filter.cc
@@ -0,0 +1,308 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#include "net/instaweb/rewriter/public/javascript_filter.h"
+
+#include <ctype.h>
+#include "base/scoped_ptr.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/htmlparse/public/html_node.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/rewriter/public/javascript_code_block.h"
+#include "net/instaweb/rewriter/public/javascript_library_identification.h"
+#include "net/instaweb/rewriter/public/resource.h"
+#include "net/instaweb/rewriter/public/output_resource.h"
+#include "net/instaweb/rewriter/public/resource_manager.h"
+#include "net/instaweb/util/public/atom.h"
+#include "net/instaweb/util/public/content_type.h"
+#include "net/instaweb/util/public/google_url.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/meta_data.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/url_async_fetcher.h"
+#include "net/instaweb/util/public/url_escaper.h"
+#include "net/instaweb/util/public/writer.h"
+
+namespace net_instaweb {
+
+namespace {
+const HttpStatus::Code kNotOptimizable = HttpStatus::kNotModified;
+} // namespace
+
+
+JavascriptFilter::JavascriptFilter(RewriteDriver* driver,
+ const StringPiece& path_prefix)
+ : RewriteSingleResourceFilter(driver, path_prefix),
+ html_parse_(driver->html_parse()),
+ script_in_progress_(NULL),
+ script_src_(NULL),
+ resource_manager_(driver->resource_manager()),
+ some_missing_scripts_(false),
+ config_(driver->resource_manager()->statistics()),
+ s_script_(html_parse_->Intern("script")),
+ s_src_(html_parse_->Intern("src")),
+ s_type_(html_parse_->Intern("type")),
+ script_tag_scanner_(html_parse_) { }
+
+JavascriptFilter::~JavascriptFilter() { }
+
+void JavascriptFilter::Initialize(Statistics* statistics) {
+ JavascriptRewriteConfig::Initialize(statistics);
+}
+
+void JavascriptFilter::StartElementImpl(HtmlElement* element) {
+ CHECK(script_in_progress_ == NULL);
+
+ switch (script_tag_scanner_.ParseScriptElement(element, &script_src_)) {
+ case ScriptTagScanner::kJavaScript: {
+ script_in_progress_ = element;
+ if (script_src_ != NULL) {
+ html_parse_->InfoHere("Found script with src %s", script_src_->value());
+ }
+ break;
+ }
+ case ScriptTagScanner::kUnknownScript: {
+ std::string script_dump;
+ element->ToString(&script_dump);
+ html_parse_->InfoHere("Unrecognized script:'%s'", script_dump.c_str());
+ break;
+ }
+ case ScriptTagScanner::kNonScript:
+ break;
+ }
+}
+
+void JavascriptFilter::Characters(HtmlCharactersNode* characters) {
+ if (script_in_progress_ != NULL) {
+ // Note that we're keeping a vector of nodes here,
+ // and appending them lazily at the end. This is
+ // because there's usually only 1 HtmlCharactersNode involved,
+ // and we end up not actually needing to copy the string.
+ buffer_.push_back(characters);
+ }
+}
+
+// Flatten script fragments in buffer_, using script_buffer to hold
+// the data if necessary. Return a StringPiece referring to the data.
+const StringPiece JavascriptFilter::FlattenBuffer(std::string* script_buffer) {
+ const int buffer_size = buffer_.size();
+ if (buffer_.size() == 1) {
+ StringPiece result(buffer_[0]->contents());
+ return result;
+ } else {
+ for (int i = 0; i < buffer_size; i++) {
+ script_buffer->append(buffer_[i]->contents());
+ }
+ StringPiece result(*script_buffer);
+ return result;
+ }
+}
+
+void JavascriptFilter::RewriteInlineScript() {
+ const int buffer_size = buffer_.size();
+ if (buffer_size > 0) {
+ // First buffer up script data and minify it.
+ std::string script_buffer;
+ const StringPiece script = FlattenBuffer(&script_buffer);
+ MessageHandler* message_handler = html_parse_->message_handler();
+ JavascriptCodeBlock code_block(script, &config_, message_handler);
+ JavascriptLibraryId library = code_block.ComputeJavascriptLibrary();
+ if (library.recognized()) {
+ html_parse_->InfoHere("Script is %s %s",
+ library.name(), library.version());
+ }
+ if (code_block.ProfitableToRewrite()) {
+ // Now replace all CharactersNodes with a single CharactersNode containing
+ // the minified script.
+ HtmlCharactersNode* new_script =
+ html_parse_->NewCharactersNode(buffer_[0]->parent(),
+ code_block.Rewritten());
+ html_parse_->ReplaceNode(buffer_[0], new_script);
+ for (int i = 1; i < buffer_size; i++) {
+ html_parse_->DeleteElement(buffer_[i]);
+ }
+ }
+ }
+}
+
+// Load script resource located at the given URL,
+// on error report & return NULL (caller need not report)
+Resource* JavascriptFilter::ScriptAtUrl(const StringPiece& script_url) {
+ Resource* script_input = CreateInputResourceAndReadIfCached(script_url);
+ return script_input;
+}
+
+// Take script_out, which is derived from the script at script_url,
+// and write it to script_dest.
+// Returns true on success, reports failures itself.
+bool JavascriptFilter::WriteExternalScriptTo(
+ const Resource* script_resource,
+ const StringPiece& script_out, OutputResource* script_dest) {
+ bool ok = false;
+ MessageHandler* message_handler = html_parse_->message_handler();
+ int64 origin_expire_time_ms = script_resource->CacheExpirationTimeMs();
+ if (resource_manager_->Write(HttpStatus::kOK, script_out, script_dest,
+ origin_expire_time_ms, message_handler)) {
+ ok = true;
+ html_parse_->InfoHere("Rewrite script %s to %s",
+ script_resource->url().c_str(),
+ script_dest->url().c_str());
+ }
+ return ok;
+}
+
+// External script; minify and replace with rewritten version (also external).
+void JavascriptFilter::RewriteExternalScript() {
+ const StringPiece script_url(script_src_->value());
+ MessageHandler* message_handler = html_parse_->message_handler();
+ scoped_ptr<OutputResource> script_dest(
+ resource_manager_->CreateOutputResourceForRewrittenUrl(
+ base_gurl(), filter_prefix_, script_url,
+ &kContentTypeJavascript, resource_manager_->url_escaper(),
+ driver_->options(), message_handler));
+ if (script_dest != NULL) {
+ bool ok;
+ if (resource_manager_->FetchOutputResource(
+ script_dest.get(), NULL, NULL, message_handler,
+ ResourceManager::kNeverBlock)) {
+ // Only rewrite URL if we have usable rewritten data.
+ ok = script_dest->metadata()->status_code() == HttpStatus::kOK;
+ } else {
+ scoped_ptr<Resource> script_input(ScriptAtUrl(script_url));
+ ok = script_input != NULL;
+ if (ok) {
+ StringPiece script = script_input->contents();
+ MessageHandler* message_handler = html_parse_->message_handler();
+ JavascriptCodeBlock code_block(script, &config_, message_handler);
+ JavascriptLibraryId library = code_block.ComputeJavascriptLibrary();
+ if (library.recognized()) {
+ html_parse_->InfoHere("Script %s is %s %s",
+ script_input->url().c_str(),
+ library.name(), library.version());
+ }
+ ok = code_block.ProfitableToRewrite();
+ if (ok) {
+ ok = WriteExternalScriptTo(script_input.get(), code_block.Rewritten(),
+ script_dest.get());
+ } else {
+ // Rewriting happened but wasn't useful; remember this for later
+ // so we don't attempt to rewrite twice.
+ html_parse_->InfoHere("Script %s didn't shrink",
+ script_input->url().c_str());
+ int64 origin_expire_time_ms = script_input->CacheExpirationTimeMs();
+
+ // TODO(jmarantz): currently this will not work, because HTTPCache
+ // will not report a 'hit' on any status other than OK. This should
+ // be fixed by either:
+ // 1. adding a few other codes that HTTPCache will return hits for
+ // 2. using a special header to indicate failed-to-optimize.
+ resource_manager_->Write(
+ kNotOptimizable, "",
+ script_dest.get(), origin_expire_time_ms, message_handler);
+ }
+ } else {
+ some_missing_scripts_ = true;
+ }
+ }
+ if (ok) {
+ script_src_->SetValue(script_dest->url());
+ }
+ }
+ // Finally, note that the script might contain body data.
+ // We erase this if it is just whitespace; otherwise we leave it alone.
+ // The script body is ignored by all browsers we know of.
+ // However, various sources have encouraged using the body of an
+ // external script element to store a post-load callback.
+ // As this technique is preferable to storing callbacks in, say, html
+ // comments, we support it for now.
+ bool allSpaces = true;
+ for (size_t i = 0; allSpaces && i < buffer_.size(); ++i) {
+ const std::string& contents = buffer_[i]->contents();
+ for (size_t j = 0; allSpaces && j < contents.size(); ++j) {
+ char c = contents[j];
+ if (!isspace(c) && c != 0) {
+ html_parse_->WarningHere("Retaining contents of script tag"
+ " even though script is external.");
+ allSpaces = false;
+ }
+ }
+ }
+ for (size_t i = 0; allSpaces && i < buffer_.size(); ++i) {
+ html_parse_->DeleteElement(buffer_[i]);
+ }
+}
+
+// Reset state at end of script.
+void JavascriptFilter::CompleteScriptInProgress() {
+ buffer_.clear();
+ script_in_progress_ = NULL;
+ script_src_ = NULL;
+}
+
+void JavascriptFilter::EndElementImpl(HtmlElement* element) {
+ if (script_in_progress_ != NULL &&
+ html_parse_->IsRewritable(script_in_progress_) &&
+ html_parse_->IsRewritable(element)) {
+ if (element->tag() == s_script_) {
+ if (element->close_style() == HtmlElement::BRIEF_CLOSE) {
+ html_parse_->ErrorHere("Brief close of script tag (non-portable)");
+ }
+ if (script_src_ == NULL) {
+ RewriteInlineScript();
+ } else {
+ RewriteExternalScript();
+ }
+ CompleteScriptInProgress();
+ } else {
+ // Should not happen by construction (parser should not have tags here).
+ // Note that if we get here, this test *Will* fail; it is written
+ // out longhand to make diagnosis easier.
+ CHECK(script_in_progress_ == NULL);
+ }
+ }
+}
+
+void JavascriptFilter::Flush() {
+ // TODO(jmaessen): We can be smarter here if it turns out to be necessary (eg
+ // by buffering an in-progress script across the flush boundary).
+ if (script_in_progress_ != NULL) {
+ // Not actually an error!
+ html_parse_->InfoHere("Flush in mid-script; leaving script untouched.");
+ CompleteScriptInProgress();
+ some_missing_scripts_ = true;
+ }
+}
+
+void JavascriptFilter::IEDirective(HtmlIEDirectiveNode* directive) {
+ CHECK(script_in_progress_ == NULL);
+ // We presume an IE directive is concealing some js code.
+ some_missing_scripts_ = true;
+}
+
+bool JavascriptFilter::RewriteLoadedResource(const Resource* script_input,
+ OutputResource* output_resource) {
+ StringPiece script = script_input->contents();
+ std::string script_out;
+ JavascriptCodeBlock code_block(script, &config_,
+ html_parse_->message_handler());
+ return WriteExternalScriptTo(script_input,
+ code_block.Rewritten(), output_resource);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/javascript_filter_test.cc b/trunk/src/net/instaweb/rewriter/javascript_filter_test.cc
new file mode 100644
index 0000000..8514814
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/javascript_filter_test.cc
@@ -0,0 +1,159 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+// Unit-test the javascript filter
+
+
+#include "base/scoped_ptr.h"
+#include "net/instaweb/rewriter/public/cache_extender.h"
+#include "net/instaweb/rewriter/public/resource_manager.h"
+#include "net/instaweb/rewriter/public/resource_manager_test_base.h"
+#include "net/instaweb/rewriter/public/resource_namer.h"
+#include "net/instaweb/util/public/google_message_handler.h"
+#include "net/instaweb/util/public/string_writer.h"
+
+namespace {
+
+const char kHtmlFormat[] =
+ "<script type='text/javascript' src='%s'></script>\n";
+
+const char kJsData[] = "alert ( 'hello, world!' ) ";
+const char kJsMinData[] = "alert('hello, world!')";
+const char kFilterId[] = "jm";
+const char kOrigJsName[] = "hello.js";
+const char kRewrittenJsName[] = "hello.js";
+const char kSourcePrefix[] = "http://test.com/";
+
+} // namespace
+
+namespace net_instaweb {
+
+class JavascriptFilterTest : public ResourceManagerTestBase {
+ protected:
+ virtual void SetUp() {
+ ResourceManagerTestBase::SetUp();
+ AddFilter(RewriteOptions::kRewriteJavascript);
+ ResourceNamer namer;
+ namer.set_id(kFilterId);
+ namer.set_name(kRewrittenJsName);
+ namer.set_ext("js");
+ namer.set_hash("0");
+ expected_rewritten_path_ = StrCat(kSourcePrefix, namer.Encode());
+ }
+
+ void InitTest(int64 ttl) {
+ InitMetaData(kOrigJsName, kContentTypeJavascript, kJsData, ttl);
+ }
+
+ // Generate HTML loading 3 resources with the specified URLs
+ std::string GenerateHtml(const char* a) {
+ return StringPrintf(kHtmlFormat, a);
+ }
+
+ std::string expected_rewritten_path_;
+};
+
+TEST_F(JavascriptFilterTest, DoRewrite) {
+ InitTest(100);
+ ValidateExpected("do_rewrite",
+ GenerateHtml(kOrigJsName).c_str(),
+ GenerateHtml(expected_rewritten_path_.c_str()).c_str());
+}
+
+TEST_F(JavascriptFilterTest, RewriteAlreadyCachedProperly) {
+ InitTest(100000000); // cached for a long time to begin with
+ // But we will rewrite because we can make the data smaller.
+ ValidateExpected("rewrite_despite_being_cached_properly",
+ GenerateHtml(kOrigJsName).c_str(),
+ GenerateHtml(expected_rewritten_path_.c_str()).c_str());
+}
+
+TEST_F(JavascriptFilterTest, NoRewriteOriginUncacheable) {
+ InitTest(0); // origin not cacheable
+ ValidateExpected("no_extend_origin_not_cacheable",
+ GenerateHtml(kOrigJsName).c_str(),
+ GenerateHtml(kOrigJsName).c_str());
+}
+
+TEST_F(JavascriptFilterTest, ServeFiles) {
+ std::string content;
+
+ // TODO(jmarantz): Factor some of this logic-flow out so that
+ // cache_extender_test.cc can share it.
+
+ // When we start, there are no mock fetchers, so we'll need to get it
+ // from the cache or the disk. Start with the cache.
+ file_system_.Disable();
+ SimpleMetaData headers;
+ resource_manager_->SetDefaultHeaders(&kContentTypeJavascript, &headers);
+ http_cache_.Put(expected_rewritten_path_, headers, kJsMinData,
+ &message_handler_);
+ EXPECT_EQ(0, lru_cache_->num_hits());
+ ASSERT_TRUE(ServeResource(kSourcePrefix, kFilterId,
+ kRewrittenJsName, "js", &content));
+ EXPECT_EQ(1, lru_cache_->num_hits());
+ EXPECT_EQ(std::string(kJsMinData), content);
+
+ // Now remove it from the cache, but put it in the file system. Make sure
+ // that works. Still there is no mock fetcher.
+ file_system_.Enable();
+ lru_cache_->Clear();
+
+ // Getting the filename is kind of a drag, isn't it. But someone's
+ // gotta do it.
+ // TODO(jmarantz): refactor this and share it with other filter_tests.
+ std::string filename;
+ FilenameEncoder* encoder = resource_manager_->filename_encoder();
+ encoder->Encode(resource_manager_->filename_prefix(),
+ expected_rewritten_path_, &filename);
+ std::string data = StrCat(headers.ToString(), kJsMinData);
+ ASSERT_TRUE(file_system_.WriteFile(filename.c_str(), data,
+ &message_handler_));
+
+ ASSERT_TRUE(ServeResource(kSourcePrefix, kFilterId,
+ kRewrittenJsName, "js", &content));
+ EXPECT_EQ(std::string(kJsMinData), content);
+
+ // After serving from the disk, we should have seeded our cache. Check it.
+ EXPECT_EQ(CacheInterface::kAvailable, http_cache_.Query(
+ expected_rewritten_path_));
+
+ // Finally, nuke the file, nuke the cache, get it via a fetch.
+ file_system_.Disable();
+ ASSERT_TRUE(file_system_.RemoveFile(filename.c_str(), &message_handler_));
+ lru_cache_->Clear();
+ InitTest(100);
+ ASSERT_TRUE(ServeResource(kSourcePrefix, kFilterId,
+ kRewrittenJsName, "js", &content));
+ EXPECT_EQ(std::string(kJsMinData), content);
+
+ // Now we expect both the file and the cache entry to be there.
+ EXPECT_EQ(CacheInterface::kAvailable, http_cache_.Query(
+ expected_rewritten_path_));
+ file_system_.Enable();
+ EXPECT_TRUE(file_system_.Exists(filename.c_str(), &message_handler_)
+ .is_true());
+
+ // Finally, serve from a completely separate server.
+ ServeResourceFromManyContexts(expected_rewritten_path_,
+ RewriteOptions::kRewriteJavascript,
+ &mock_hasher_,
+ kJsMinData);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/javascript_library_identification.cc b/trunk/src/net/instaweb/rewriter/javascript_library_identification.cc
new file mode 100644
index 0000000..7404d2a
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/javascript_library_identification.cc
@@ -0,0 +1,56 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#include "net/instaweb/rewriter/public/javascript_library_identification.h"
+
+#include "net/instaweb/util/public/rolling_hash.h"
+#include "net/instaweb/util/public/string_util.h"
+
+// Include separately-generated library metadata (kLibraryMetadata)
+// whose 0th entry is unrecognized (NULL name).
+#include "net/instaweb/rewriter/javascript_metadata.cc"
+
+namespace net_instaweb {
+
+static const LibraryInfo& kUnrecognizedLibraryInfo = kLibraryMetadata[0];
+static const size_t kLibrarySize = arraysize(kLibraryMetadata);
+
+JavascriptLibraryId JavascriptLibraryId::Find(
+ const StringPiece& minified_code) {
+ const LibraryInfo* library = &kUnrecognizedLibraryInfo;
+ if (minified_code.size() >= kJavascriptHashIdBlockSize) {
+ uint64 block_hash =
+ RollingHash(minified_code.data(), 0, kJavascriptHashIdBlockSize);
+ // Right now we use a naive linear search.
+ // TODO(jmaessen): lazily-initialized search structure of some sort.
+ for (library = &kLibraryMetadata[kLibrarySize - 1];
+ library->name != NULL; --library) {
+ if (library->first_block_hash == block_hash &&
+ library->full_size == minified_code.size() &&
+ library->full_hash ==
+ RollingHash(minified_code.data(), 0, library->full_size)) {
+ break;
+ }
+ }
+ }
+ return JavascriptLibraryId(library);
+}
+
+JavascriptLibraryId::JavascriptLibraryId()
+ : info_(&kUnrecognizedLibraryInfo) { }
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/javascript_metadata.cc b/trunk/src/net/instaweb/rewriter/javascript_metadata.cc
new file mode 100644
index 0000000..d187157
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/javascript_metadata.cc
@@ -0,0 +1,32 @@
+/**
+ * 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.
+ */
+// Generated by:
+// ./rewriter/generate_javascript_metadata.sh /home/jmaessen/jslibs/
+// Intended for inclusion in javascript_library_identification.cc
+
+namespace net_instaweb {
+
+static const LibraryInfo kLibraryMetadata[] = {
+ {NULL, NULL, 0ULL, 0ULL, 0},
+ { "prototype", "1.6.0.2",
+ 0x84e682bac0395621ULL, 0x754d81bbcddf212aULL, 93706},
+ { "prototype", "1.6.0.3",
+ 0x6d056fb74c5f131cULL, 0xe63b907ad7e2163cULL, 95347},
+ { "prototype", "1.6.1.0",
+ 0x6ddb883a16ace985ULL, 0xea6f76a9731dc118ULL, 105569}
+};
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/js_inline_filter.cc b/trunk/src/net/instaweb/rewriter/js_inline_filter.cc
new file mode 100644
index 0000000..3e65054
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/js_inline_filter.cc
@@ -0,0 +1,138 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+//
+// 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: mdsteele@google.com (Matthew D. Steele)
+
+#include "net/instaweb/rewriter/public/js_inline_filter.h"
+
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "base/string_util.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/htmlparse/public/html_node.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/rewriter/public/rewrite_driver.h"
+#include "net/instaweb/util/public/google_url.h"
+
+namespace net_instaweb {
+
+JsInlineFilter::JsInlineFilter(RewriteDriver* driver)
+ : CommonFilter(driver),
+ html_parse_(driver->html_parse()),
+ script_atom_(html_parse_->Intern("script")),
+ src_atom_(html_parse_->Intern("src")),
+ size_threshold_bytes_(driver->options()->js_inline_max_bytes()),
+ script_tag_scanner_(html_parse_),
+ should_inline_(false) {}
+
+JsInlineFilter::~JsInlineFilter() {}
+
+void JsInlineFilter::StartDocumentImpl() {
+ // TODO(sligocki): This should go in the domain lawyer, right?
+ domain_ = html_parse_->gurl().host();
+ should_inline_ = false;
+}
+
+void JsInlineFilter::EndDocument() {
+ domain_.clear();
+}
+
+void JsInlineFilter::StartElementImpl(HtmlElement* element) {
+ DCHECK(!should_inline_);
+
+ HtmlElement::Attribute* src;
+ if (script_tag_scanner_.ParseScriptElement(element, &src) ==
+ ScriptTagScanner::kJavaScript) {
+ should_inline_ = (src != NULL) && (src->value() != NULL);
+ }
+}
+
+void JsInlineFilter::EndElementImpl(HtmlElement* element) {
+ if (should_inline_ && html_parse_->IsRewritable(element)) {
+ DCHECK(element->tag() == script_atom_);
+ const char* src = element->AttributeValue(src_atom_);
+ DCHECK(src != NULL);
+ should_inline_ = false;
+
+ // TODO(morlovich): Consider async/defer here; it may not be a good
+ // idea to inline async scripts in particular
+
+ scoped_ptr<Resource> resource(CreateInputResourceAndReadIfCached(src));
+ // TODO(jmaessen): Is the domain lawyer policy the appropriate one here?
+ // Or do we still have to check for strict domain equivalence?
+ // If so, add an inline-in-page policy to domainlawyer in some form,
+ // as we make a similar policy decision in css_inline_filter.
+ if (resource != NULL && resource->ContentsValid()) {
+ StringPiece contents = resource->contents();
+ // Only inline if it's small enough, and if it doesn't contain
+ // "</script>" anywhere. If we inline an external script containing
+ // "</script>", the <script> tag will be ended early.
+ // See http://code.google.com/p/modpagespeed/issues/detail?id=106
+ // TODO(mdsteele): We should consider rewriting "</script>" to
+ // "<\/script>" instead of just bailing. But we can't blindly search
+ // and replace because that would break legal (if contrived) code such
+ // as "if(x</script>/){...}", which is comparing x to a regex literal.
+ if (contents.size() <= size_threshold_bytes_ &&
+ contents.find("</script>") == StringPiece::npos) {
+ // If we're in XHTML, we should wrap the script in a <!CDATA[...]]>
+ // block to ensure that we don't break well-formedness. Since XHTML is
+ // sometimes interpreted as HTML (which will ignore CDATA delimiters),
+ // we have to hide the CDATA delimiters behind Javascript comments.
+ // See http://lachy.id.au/log/2006/11/xhtml-script
+ // and http://code.google.com/p/modpagespeed/issues/detail?id=125
+ if (html_parse_->doctype().IsXhtml()) {
+ // CDATA sections cannot be nested because they end with the first
+ // occurance of "]]>", so if the script contains that string
+ // anywhere (and we're in XHTML) we can't inline.
+ // TODO(mdsteele): Again, we should consider escaping somehow.
+ if (contents.find("]]>") == StringPiece::npos) {
+ HtmlCharactersNode* node =
+ html_parse_->NewCharactersNode(element, "//<![CDATA[\n");
+ node->Append(contents);
+ node->Append("\n//]]>");
+ html_parse_->InsertElementBeforeCurrent(node);
+ element->DeleteAttribute(src_atom_);
+ }
+ }
+ // If we're not in XHTML, we can simply paste in the external script
+ // verbatim.
+ else {
+ html_parse_->InsertElementBeforeCurrent(
+ html_parse_->NewCharactersNode(element, contents));
+ element->DeleteAttribute(src_atom_);
+ }
+ }
+ }
+ }
+}
+
+void JsInlineFilter::Characters(HtmlCharactersNode* characters) {
+ if (should_inline_) {
+ DCHECK(characters->parent() != NULL);
+ DCHECK(characters->parent()->tag() == script_atom_);
+ if (OnlyWhitespace(characters->contents())) {
+ // If it's just whitespace inside the script tag, it's (probably) safe to
+ // just remove it.
+ html_parse_->DeleteElement(characters);
+ } else {
+ // This script tag isn't empty, despite having a src field. The contents
+ // won't be executed by the browser, but will still be in the DOM; some
+ // external scripts like to use this as a place to store data. So, we'd
+ // better not try to inline in this case.
+ should_inline_ = false;
+ }
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/js_inline_filter_test.cc b/trunk/src/net/instaweb/rewriter/js_inline_filter_test.cc
new file mode 100644
index 0000000..10992b8
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/js_inline_filter_test.cc
@@ -0,0 +1,169 @@
+/*
+ * 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: mdsteele@google.com (Matthew D. Steele)
+
+#include "net/instaweb/rewriter/public/resource_manager_test_base.h"
+
+namespace net_instaweb {
+
+namespace {
+
+class JsInlineFilterTest : public ResourceManagerTestBase {
+ protected:
+ void TestInlineJavascript(const std::string& html_url,
+ const std::string& js_url,
+ const std::string& js_original_inline_body,
+ const std::string& js_outline_body,
+ bool expect_inline) {
+ TestInlineJavascriptGeneral(
+ html_url,
+ "", // don't use a doctype for these tests
+ js_url,
+ js_original_inline_body,
+ js_outline_body,
+ js_outline_body, // expect ouline body to be inlined verbatim
+ expect_inline);
+ }
+
+ void TestInlineJavascriptXhtml(const std::string& html_url,
+ const std::string& js_url,
+ const std::string& js_outline_body,
+ bool expect_inline) {
+ TestInlineJavascriptGeneral(
+ html_url,
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" "
+ "\"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">",
+ js_url,
+ "", // use an empty original inline body for these tests
+ js_outline_body,
+ // Expect outline body to get surrounded by a CDATA block:
+ "//<![CDATA[\n" + js_outline_body + "\n//]]>",
+ expect_inline);
+ }
+
+ void TestInlineJavascriptGeneral(const std::string& html_url,
+ const std::string& doctype,
+ const std::string& js_url,
+ const std::string& js_original_inline_body,
+ const std::string& js_outline_body,
+ const std::string& js_expected_inline_body,
+ bool expect_inline) {
+ AddFilter(RewriteOptions::kInlineJavascript);
+
+ // Specify the input and expected output.
+ if (!doctype.empty()) {
+ SetDoctype(doctype);
+ }
+ const std::string html_input =
+ "<head>\n"
+ " <script src=\"" + js_url + "\">" +
+ js_original_inline_body + "</script>\n"
+ "</head>\n"
+ "<body>Hello, world!</body>\n";
+ const std::string expected_output =
+ (!expect_inline ? html_input :
+ "<head>\n"
+ " <script>" + js_expected_inline_body + "</script>\n"
+ "</head>\n"
+ "<body>Hello, world!</body>\n");
+
+ // Put original Javascript file into our fetcher.
+ SimpleMetaData default_js_header;
+ resource_manager_->SetDefaultHeaders(&kContentTypeJavascript,
+ &default_js_header);
+ mock_url_fetcher_.SetResponse(js_url, default_js_header, js_outline_body);
+
+ // Rewrite the HTML page.
+ ValidateExpectedUrl(html_url, html_input, expected_output);
+ }
+};
+
+TEST_F(JsInlineFilterTest, DoInlineJavascriptSimple) {
+ // Simple case:
+ TestInlineJavascript("http://www.example.com/index.html",
+ "http://www.example.com/script.js",
+ "",
+ "function id(x) { return x; }\n",
+ true);
+}
+
+TEST_F(JsInlineFilterTest, DoInlineJavascriptWhitespace) {
+ // Whitespace between <script> and </script>:
+ TestInlineJavascript("http://www.example.com/index2.html",
+ "http://www.example.com/script2.js",
+ "\n \n ",
+ "function id(x) { return x; }\n",
+ true);
+}
+
+TEST_F(JsInlineFilterTest, DoNotInlineJavascriptDifferentDomain) {
+ // Different domains:
+ TestInlineJavascript("http://www.example.net/index.html",
+ "http://scripts.example.org/script.js",
+ "",
+ "function id(x) { return x; }\n",
+ false);
+}
+
+TEST_F(JsInlineFilterTest, DoNotInlineJavascriptInlineContents) {
+ // Inline contents:
+ TestInlineJavascript("http://www.example.com/index.html",
+ "http://www.example.com/script.js",
+ "{\"json\": true}",
+ "function id(x) { return x; }\n",
+ false);
+}
+
+TEST_F(JsInlineFilterTest, DoNotInlineJavascriptTooBig) {
+ // Javascript too long:
+ const int64 length = 2 * RewriteOptions::kDefaultJsInlineMaxBytes;
+ TestInlineJavascript("http://www.example.com/index.html",
+ "http://www.example.com/script.js",
+ "",
+ ("function longstr() { return '" +
+ std::string(length, 'z') + "'; }\n"),
+ false);
+}
+
+TEST_F(JsInlineFilterTest, DoNotInlineJavascriptWithCloseTag) {
+ // External script contains "</script>":
+ TestInlineJavascript("http://www.example.com/index.html",
+ "http://www.example.com/script.js",
+ "",
+ "function close() { return '</script>'; }\n",
+ false);
+}
+
+TEST_F(JsInlineFilterTest, DoInlineJavascriptXhtml) {
+ // Simple case:
+ TestInlineJavascriptXhtml("http://www.example.com/index.html",
+ "http://www.example.com/script.js",
+ "function id(x) { return x; }\n",
+ true);
+}
+
+TEST_F(JsInlineFilterTest, DoNotInlineJavascriptXhtmlWithCdataEnd) {
+ // External script contains "]]>":
+ TestInlineJavascriptXhtml("http://www.example.com/index.html",
+ "http://www.example.com/script.js",
+ "function end(x) { return ']]>'; }\n",
+ false);
+}
+
+} // namespace
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/js_outline_filter.cc b/trunk/src/net/instaweb/rewriter/js_outline_filter.cc
new file mode 100644
index 0000000..0ecf15a
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/js_outline_filter.cc
@@ -0,0 +1,174 @@
+/**
+ * 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 "base/scoped_ptr.h"
+#include "net/instaweb/rewriter/public/output_resource.h"
+#include "net/instaweb/rewriter/public/resource_manager.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/util/public/content_type.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include <string>
+#include "net/instaweb/util/public/string_writer.h"
+
+namespace net_instaweb {
+
+const char JsOutlineFilter::kFilterId[] = "jo";
+
+JsOutlineFilter::JsOutlineFilter(RewriteDriver* driver)
+ : inline_element_(NULL),
+ html_parse_(driver->html_parse()),
+ resource_manager_(driver->resource_manager()),
+ size_threshold_bytes_(driver->options()->js_outline_min_bytes()),
+ s_script_(html_parse_->Intern("script")),
+ s_src_(html_parse_->Intern("src")),
+ s_type_(html_parse_->Intern("type")),
+ script_tag_scanner_(html_parse_) { }
+
+void JsOutlineFilter::StartDocument() {
+ inline_element_ = NULL;
+ buffer_.clear();
+}
+
+void JsOutlineFilter::StartElement(HtmlElement* element) {
+ // No tags allowed inside script element.
+ if (inline_element_ != NULL) {
+ // TODO(sligocki): Add negative unit tests to hit these errors.
+ html_parse_->ErrorHere("Tag '%s' found inside script.",
+ element->tag().c_str());
+ inline_element_ = NULL; // Don't outline what we don't understand.
+ buffer_.clear();
+ }
+
+ HtmlElement::Attribute* src;
+ // We only deal with Javascript
+ if (script_tag_scanner_.ParseScriptElement(element, &src) ==
+ ScriptTagScanner::kJavaScript) {
+ inline_element_ = element;
+ buffer_.clear();
+ // script elements which already have a src should not be outlined.
+ if (src != NULL) {
+ inline_element_ = NULL;
+ }
+ }
+}
+
+void JsOutlineFilter::EndElement(HtmlElement* element) {
+ if (inline_element_ != NULL) {
+ if (element != inline_element_) {
+ // No other tags allowed inside script element.
+ html_parse_->ErrorHere("Tag '%s' found inside script.",
+ element->tag().c_str());
+
+ } else if (buffer_.size() >= size_threshold_bytes_) {
+ OutlineScript(inline_element_, buffer_);
+ } else {
+ html_parse_->InfoHere("Inline element not outlined because its size %d, "
+ "is below threshold %d",
+ static_cast<int>(buffer_.size()),
+ static_cast<int>(size_threshold_bytes_));
+ }
+ inline_element_ = NULL;
+ buffer_.clear();
+ }
+}
+
+void JsOutlineFilter::Flush() {
+ // If we were flushed in a script element, we cannot outline it.
+ inline_element_ = NULL;
+ buffer_.clear();
+}
+
+void JsOutlineFilter::Characters(HtmlCharactersNode* characters) {
+ if (inline_element_ != NULL) {
+ buffer_ += characters->contents();
+ }
+}
+
+void JsOutlineFilter::Comment(HtmlCommentNode* comment) {
+ if (inline_element_ != NULL) {
+ html_parse_->ErrorHere("Comment found inside script.");
+ inline_element_ = NULL; // Don't outline what we don't understand.
+ buffer_.clear();
+ }
+}
+
+void JsOutlineFilter::Cdata(HtmlCdataNode* cdata) {
+ if (inline_element_ != NULL) {
+ html_parse_->ErrorHere("CDATA found inside script.");
+ inline_element_ = NULL; // Don't outline what we don't understand.
+ buffer_.clear();
+ }
+}
+
+void JsOutlineFilter::IEDirective(HtmlIEDirectiveNode* directive) {
+ if (inline_element_ != NULL) {
+ html_parse_->ErrorHere("IE Directive found inside script.");
+ inline_element_ = NULL; // Don't outline what we don't understand.
+ buffer_.clear();
+ }
+}
+
+// Try to write content and possibly header to resource.
+bool JsOutlineFilter::WriteResource(const std::string& content,
+ OutputResource* resource,
+ MessageHandler* handler) {
+ // We set the TTL of the origin->hashed_name map to 0 because this is
+ // derived from the inlined HTML.
+ int64 origin_expire_time_ms = 0;
+ return resource_manager_->Write(HttpStatus::kOK, content, resource,
+ origin_expire_time_ms, handler);
+}
+
+// Create file with script content and remove that element from DOM.
+// TODO(sligocki): We probably will break any relative URL references here.
+void JsOutlineFilter::OutlineScript(HtmlElement* inline_element,
+ const std::string& content) {
+ if (html_parse_->IsRewritable(inline_element)) {
+ // Create script file from content.
+ MessageHandler* handler = html_parse_->message_handler();
+ // Create outline resource at the document location, not base URL location
+ scoped_ptr<OutputResource> resource(
+ resource_manager_->CreateOutputResourceWithPath(
+ GoogleUrl::AllExceptLeaf(html_parse_->gurl()), kFilterId, "_",
+ &kContentTypeJavascript, handler));
+ if (WriteResource(content, resource.get(), handler)) {
+ HtmlElement* outline_element = html_parse_->NewElement(
+ inline_element->parent(), s_script_);
+ outline_element->AddAttribute(s_src_, resource->url(), "'");
+ // Add all atrributes from old script element to new script src element.
+ for (int i = 0; i < inline_element->attribute_size(); ++i) {
+ const HtmlElement::Attribute& attr = inline_element->attribute(i);
+ outline_element->AddAttribute(attr);
+ }
+ // Add <script src=...> element to DOM.
+ html_parse_->InsertElementBeforeElement(inline_element,
+ outline_element);
+ // Remove original script element from DOM.
+ if (!html_parse_->DeleteElement(inline_element)) {
+ html_parse_->FatalErrorHere("Failed to delete inline script element");
+ }
+ } else {
+ html_parse_->ErrorHere("Failed to write outlined script resource.");
+ }
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/js_outline_filter_test.cc b/trunk/src/net/instaweb/rewriter/js_outline_filter_test.cc
new file mode 100644
index 0000000..de3f730
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/js_outline_filter_test.cc
@@ -0,0 +1,118 @@
+/*
+ * 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/resource_manager_test_base.h"
+
+namespace net_instaweb {
+
+namespace {
+
+class JsOutlineFilterTest : public ResourceManagerTestBase {
+ protected:
+ // TODO(sligocki): factor out common elements in OutlineStyle and Script.
+ // Test outlining scripts with options to write headers and use a hasher.
+ void OutlineScript(const StringPiece& id, Hasher* hasher) {
+ resource_manager_->set_hasher(hasher);
+
+ options_.set_js_outline_min_bytes(0);
+ AddFilter(RewriteOptions::kOutlineJavascript);
+
+ std::string script_text = "FOOBAR";
+ std::string outline_text;
+ AppendDefaultHeaders(kContentTypeJavascript, resource_manager_,
+ &outline_text);
+ outline_text += script_text;
+
+ std::string hash = hasher->Hash(script_text);
+ std::string outline_filename;
+ std::string outline_url = Encode(
+ "http://test.com/", JsOutlineFilter::kFilterId, hash, "_", "js");
+ filename_encoder_.Encode(file_prefix_, outline_url, &outline_filename);
+
+ // Make sure the file we check later was written this time, rm any old one.
+ DeleteFileIfExists(outline_filename);
+
+ std::string html_input =
+ "<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>";
+ std::string expected_output =
+ "<head>\n"
+ " <title>Example style outline</title>\n"
+ " <!-- Script starts here -->\n"
+ " <script src='" + outline_url + "' type='text/javascript'></script>\n"
+ " <!-- Script ends here -->\n"
+ "</head>";
+ ValidateExpected(id, html_input, expected_output);
+
+ std::string actual_outline;
+ ASSERT_TRUE(file_system_.ReadFile(outline_filename.c_str(),
+ &actual_outline,
+ &message_handler_));
+ EXPECT_EQ(outline_text, actual_outline);
+ }
+};
+
+// Tests for outlining scripts.
+TEST_F(JsOutlineFilterTest, OutlineScript) {
+ OutlineScript("outline_scripts_no_hash_with_headers", &mock_hasher_);
+}
+
+// Negative test.
+TEST_F(JsOutlineFilterTest, NoOutlineScript) {
+ std::string file_prefix = GTestTempDir() + "/no_outline";
+ std::string url_prefix = "http://mysite/no_outline";
+
+ // TODO(sligocki): Maybe test with other hashers.
+ //resource_manager_->set_hasher(hasher);
+
+ options_.EnableFilter(RewriteOptions::kOutlineCss);
+ options_.EnableFilter(RewriteOptions::kOutlineJavascript);
+ rewrite_driver_.AddFilters();
+
+ // We need to make sure we don't create this file, so rm any old one
+ std::string filename = Encode(file_prefix, JsOutlineFilter::kFilterId, "0",
+ "_", "js");
+ DeleteFileIfExists(filename.c_str());
+
+ 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);
+
+ // Check that it did *NOT* create the file.
+ // TODO(jmarantz): this is pretty brittle, and perhaps obsolete.
+ // We just change the test to ensure that we are not outlining when
+ // we don't want to.
+ EXPECT_FALSE(file_system_.Exists(filename.c_str(),
+ &message_handler_).is_true());
+}
+
+} // namespace
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/output_resource.cc b/trunk/src/net/instaweb/rewriter/output_resource.cc
new file mode 100644
index 0000000..821c227
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/output_resource.cc
@@ -0,0 +1,275 @@
+/**
+ * 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)
+// jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/rewriter/public/output_resource.h"
+
+#include "base/logging.h"
+#include "net/instaweb/rewriter/public/resource_namer.h"
+#include "net/instaweb/rewriter/public/resource_manager.h"
+#include "net/instaweb/rewriter/public/rewrite_filter.h"
+#include "net/instaweb/util/public/content_type.h"
+#include "net/instaweb/util/public/hasher.h"
+#include "net/instaweb/util/public/file_system.h"
+#include "net/instaweb/util/public/filename_encoder.h"
+#include "net/instaweb/util/public/named_lock_manager.h"
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/string_writer.h"
+#include "net/instaweb/util/public/timer.h"
+#include "net/instaweb/util/stack_buffer.h"
+
+namespace net_instaweb {
+
+namespace {
+
+const char kLockSuffix[] = ".outputlock";
+
+} // namespace
+
+OutputResource::OutputResource(ResourceManager* manager,
+ const StringPiece& resolved_base,
+ const ResourceNamer& full_name,
+ const ContentType* type)
+ : Resource(manager, type),
+ output_file_(NULL),
+ writing_complete_(false),
+ generated_(false),
+ resolved_base_(resolved_base.data(), resolved_base.size()),
+ full_name_() {
+ full_name_.CopyFrom(full_name);
+ if (type == NULL) {
+ std::string ext_with_dot = StrCat(".", full_name.ext());
+ type_ = NameExtensionToContentType(ext_with_dot);
+ } else {
+ // This if + check used to be a 1-liner, but it was failing and this
+ // yields debuggable output.
+ // TODO(jmaessen): The addition of 1 below avoids the leading ".";
+ // make this convention consistent and fix all code.
+ CHECK_EQ((type->file_extension() + 1), full_name.ext());
+ }
+ CHECK(EndsInSlash(resolved_base)) << "resolved_base must end in a slash.";
+}
+
+OutputResource::~OutputResource() {
+}
+
+bool OutputResource::OutputWriter::Write(const StringPiece& data,
+ MessageHandler* handler) {
+ bool ret = http_value_->Write(data, handler);
+ if (file_writer_.get() != NULL) {
+ ret &= file_writer_->Write(data, handler);
+ }
+ return ret;
+}
+
+OutputResource::OutputWriter* OutputResource::BeginWrite(
+ MessageHandler* handler) {
+ value_.Clear();
+ full_name_.ClearHash();
+ CHECK(!writing_complete_);
+ CHECK(output_file_ == NULL);
+ if (resource_manager_->store_outputs_in_file_system()) {
+ FileSystem* file_system = resource_manager_->file_system();
+ // Always write to a tempfile, so that if we get interrupted in the middle
+ // we won't leave a half-baked file in the serving path.
+ std::string temp_prefix = TempPrefix();
+ output_file_ = file_system->OpenTempFile(
+ temp_prefix.c_str(), handler);
+ bool success = (output_file_ != NULL);
+ if (success) {
+ std::string header;
+ StringWriter string_writer(&header);
+ meta_data_.Write(&string_writer, handler); // Serialize header.
+ // It does not make sense to have the headers in the hash.
+ // call output_file_->Write directly, rather than going through
+ // OutputWriter.
+ //
+ // TODO(jmarantz): consider refactoring to split out the header-file
+ // writing in a different way, e.g. to a separate file.
+ success &= output_file_->Write(header, handler);
+ }
+ OutputWriter* writer = NULL;
+ if (success) {
+ writer = new OutputWriter(output_file_, &value_);
+ }
+ return writer;
+ } else {
+ return new OutputWriter(NULL, &value_);
+ };
+}
+
+bool OutputResource::EndWrite(OutputWriter* writer, MessageHandler* handler) {
+ CHECK(!writing_complete_);
+ value_.SetHeaders(meta_data_);
+ Hasher* hasher = resource_manager_->hasher();
+ full_name_.set_hash(hasher->Hash(contents()));
+ writing_complete_ = true;
+ bool ret = true;
+ if (output_file_ != NULL) {
+ FileSystem* file_system = resource_manager_->file_system();
+ CHECK(file_system != NULL);
+ std::string temp_filename = output_file_->filename();
+ ret = file_system->Close(output_file_, handler);
+
+ // Now that we are done writing, we can rename to the filename we
+ // really want.
+ if (ret) {
+ ret = file_system->RenameFile(temp_filename.c_str(), filename().c_str(),
+ handler);
+ }
+
+ // TODO(jmarantz): Consider writing to the HTTP cache as we write the
+ // file, so same-process write-then-read never has to read from disk.
+ // This is moderately inconvenient because HTTPCache lacks a streaming
+ // Put interface.
+
+ output_file_ = NULL;
+ }
+ if (creation_lock_.get() != NULL) {
+ // We've created the data, never need to lock again.
+ creation_lock_->Unlock();
+ creation_lock_.reset(NULL);
+ }
+ return ret;
+}
+
+// Called by FilenameOutputResource::BeginWrite to determine how
+// to start writing the tmpfile.
+std::string OutputResource::TempPrefix() const {
+ return StrCat(resource_manager_->filename_prefix(), "temp_");
+}
+
+StringPiece OutputResource::suffix() const {
+ CHECK(type_ != NULL);
+ return type_->file_extension();
+}
+
+void OutputResource::set_suffix(const StringPiece& ext) {
+ type_ = NameExtensionToContentType(ext);
+ if (type_ != NULL) {
+ // TODO(jmaessen): The addition of 1 below avoids the leading ".";
+ // make this convention consistent and fix all code.
+ full_name_.set_ext(type_->file_extension() + 1);
+ } else {
+ full_name_.set_ext(ext.substr(1));
+ }
+}
+
+std::string OutputResource::filename() const {
+ std::string filename;
+ resource_manager_->filename_encoder()->Encode(
+ resource_manager_->filename_prefix(), url(), &filename);
+ return filename;
+}
+
+std::string OutputResource::name_key() const {
+ std::string id_name = full_name_.EncodeIdName();
+ std::string result;
+ CHECK(!resolved_base_.empty()); // Corresponding path in url() is dead code
+ result = StrCat(resolved_base_, id_name);
+ return result;
+}
+
+std::string OutputResource::hash_ext() const {
+ return full_name_.EncodeHashExt();
+}
+
+// TODO(jmarantz): change the name to reflect the fact that it is not
+// just an accessor now.
+std::string OutputResource::url() const {
+ std::string encoded = full_name_.Encode();
+ encoded = StrCat(resolved_base_, encoded);
+ return encoded;
+}
+
+void OutputResource::SetHash(const StringPiece& hash) {
+ CHECK(!writing_complete_);
+ CHECK(!has_hash());
+ full_name_.set_hash(hash);
+}
+
+bool OutputResource::Load(MessageHandler* handler) {
+ if (!writing_complete_ && resource_manager_->store_outputs_in_file_system()) {
+ FileSystem* file_system = resource_manager_->file_system();
+ FileSystem::InputFile* file = file_system->OpenInputFile(
+ filename().c_str(), handler);
+ if (file != NULL) {
+ char buf[kStackBufferSize];
+ int nread = 0, num_consumed = 0;
+ // TODO(jmarantz): this logic is duplicated in util/wget_url_fetcher.cc,
+ // consider a refactor to merge it.
+ meta_data_.Clear();
+ value_.Clear();
+ while (!meta_data_.headers_complete() &&
+ ((nread = file->Read(buf, sizeof(buf), handler)) != 0)) {
+ num_consumed = meta_data_.ParseChunk(
+ StringPiece(buf, nread), handler);
+ }
+ value_.SetHeaders(meta_data_);
+ writing_complete_ = value_.Write(
+ StringPiece(buf + num_consumed, nread - num_consumed),
+ handler);
+ while (writing_complete_ &&
+ ((nread = file->Read(buf, sizeof(buf), handler)) != 0)) {
+ writing_complete_ = value_.Write(StringPiece(buf, nread), handler);
+ }
+ file_system->Close(file, handler);
+ }
+ }
+ return writing_complete_;
+}
+
+bool OutputResource::IsWritten() const {
+ return writing_complete_;
+}
+
+void OutputResource::SetType(const ContentType* content_type) {
+ Resource::SetType(content_type);
+ // TODO(jmaessen): The addition of 1 below avoids the leading ".";
+ // make this convention consistent and fix all code.
+ full_name_.set_ext(content_type->file_extension() + 1);
+}
+
+bool OutputResource::LockForCreation(const ResourceManager* resource_manager,
+ ResourceManager::BlockingBehavior block) {
+ const int64 break_lock_ms = 30 * Timer::kSecondMs;
+ const int64 block_lock_ms = 5 * Timer::kSecondMs;
+ bool result = true;
+ if (creation_lock_.get() == NULL) {
+ std::string lock_name =
+ StrCat(resource_manager->filename_prefix(),
+ resource_manager->hasher()->Hash(name_key()),
+ kLockSuffix);
+ creation_lock_.reset(resource_manager->lock_manager()->
+ CreateNamedLock(lock_name));
+ }
+ switch (block) {
+ case ResourceManager::kNeverBlock:
+ // TODO(jmaessen): When caller retries properly in all cases, use
+ // LockTimedWaitStealOld with a sub-second timeout to try to catch
+ // rewritten data.
+ result = creation_lock_->TryLockStealOld(break_lock_ms);
+ break;
+ case ResourceManager::kMayBlock:
+ creation_lock_->LockStealOld(block_lock_ms);
+ break;
+ }
+ return result;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/public/add_head_filter.h b/trunk/src/net/instaweb/rewriter/public/add_head_filter.h
new file mode 100644
index 0000000..7f6d955
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/add_head_filter.h
@@ -0,0 +1,55 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_ADD_HEAD_FILTER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_ADD_HEAD_FILTER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/empty_html_filter.h"
+#include "net/instaweb/util/public/atom.h"
+
+namespace net_instaweb {
+
+// Adds a 'head' element before the 'body', if none was found
+// during parsing. This enables downstream filters to assume
+// that there will be a head.
+class AddHeadFilter : public EmptyHtmlFilter {
+ public:
+ explicit AddHeadFilter(HtmlParse* parser, bool combine_multiple_heads);
+
+ virtual void StartDocument();
+ virtual void StartElement(HtmlElement* element);
+ virtual void EndDocument();
+ virtual void EndElement(HtmlElement* element);
+ virtual void Flush();
+ virtual const char* Name() const { return "AddHead"; }
+
+ private:
+ HtmlParse* html_parse_;
+ bool combine_multiple_heads_;
+ bool found_head_;
+ Atom s_head_;
+ Atom s_body_;
+ HtmlElement* head_element_;
+
+ DISALLOW_COPY_AND_ASSIGN(AddHeadFilter);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_ADD_HEAD_FILTER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/add_instrumentation_filter.h b/trunk/src/net/instaweb/rewriter/public/add_instrumentation_filter.h
new file mode 100644
index 0000000..6748b4a
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/add_instrumentation_filter.h
@@ -0,0 +1,62 @@
+/**
+ * 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: abliss@google.com (Adam Bliss)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_ADD_INSTRUMENTATION_FILTER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_ADD_INSTRUMENTATION_FILTER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/empty_html_filter.h"
+#include "net/instaweb/util/public/atom.h"
+
+namespace net_instaweb {
+
+class Statistics;
+class Variable;
+
+// Injects javascript instrumentation for monitoring page-rendering time.
+class AddInstrumentationFilter : public EmptyHtmlFilter {
+ public:
+ explicit AddInstrumentationFilter(HtmlParse* parser,
+ const StringPiece& beacon_url,
+ Statistics* statistics);
+
+ static void Initialize(Statistics* statistics);
+ virtual void StartDocument();
+ virtual void StartElement(HtmlElement* element);
+ virtual void EndElement(HtmlElement* element);
+ virtual const char* Name() const { return "AddInstrumentation"; }
+ // Handles an incoming beacon request by incrementing the appropriate
+ // variables. Returns true if the url was parsed and handled correctly; in
+ // this case a 204 No Content response should be sent. Returns false if the
+ // url could not be parsed; in this case the request should be declined.
+ bool HandleBeacon(const StringPiece& unparsed_url);
+ private:
+ HtmlParse* html_parse_;
+ bool found_head_;
+ Atom s_head_;
+ Atom s_body_;
+ Variable* total_page_load_ms_;
+ Variable* page_load_count_;
+ std::string beacon_url_;
+
+ DISALLOW_COPY_AND_ASSIGN(AddInstrumentationFilter);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_ADD_INSTRUMENTATION_FILTER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/base_tag_filter.h b/trunk/src/net/instaweb/rewriter/public/base_tag_filter.h
new file mode 100644
index 0000000..82a87ad
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/base_tag_filter.h
@@ -0,0 +1,64 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_BASE_TAG_FILTER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_BASE_TAG_FILTER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/empty_html_filter.h"
+#include "net/instaweb/util/public/atom.h"
+#include <string>
+
+namespace net_instaweb {
+
+// Add this filter into the HtmlParse chain to add a base
+// tag into the head section of an HTML document.
+//
+// e.g.
+//
+// HtmlParser* parser = ...
+// ...
+// BaseTagFilter* base_tag_filter = new BaseTagFilter(parser);
+// parser->AddFilter(base_tag_filter);
+// base_tag_filter->set_base("http://my_new_base.com");
+// ...
+// parser->StartParse()...
+class BaseTagFilter : public EmptyHtmlFilter {
+ public:
+ explicit BaseTagFilter(HtmlParse* parser);
+
+ virtual void StartDocument();
+ virtual void StartElement(HtmlElement* element);
+ virtual const char* Name() const { return "BaseTag"; }
+
+ void set_base_url(const StringPiece& url) { url.CopyToString(&base_url_); }
+
+ private:
+ Atom s_head_;
+ Atom s_base_;
+ Atom s_href_;
+ bool found_head_;
+ std::string base_url_;
+ HtmlParse* html_parse_;
+
+ DISALLOW_COPY_AND_ASSIGN(BaseTagFilter);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_BASE_TAG_FILTER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/cache_extender.h b/trunk/src/net/instaweb/rewriter/public/cache_extender.h
new file mode 100644
index 0000000..389e3ee
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/cache_extender.h
@@ -0,0 +1,70 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_CACHE_EXTENDER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_CACHE_EXTENDER_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "net/instaweb/rewriter/public/resource_tag_scanner.h"
+#include "net/instaweb/rewriter/public/rewrite_single_resource_filter.h"
+#include "net/instaweb/util/public/atom.h"
+#include <string>
+
+namespace net_instaweb {
+
+class Hasher;
+class ResourceManager;
+class Timer;
+class Variable;
+
+// Rewrites resources to extend their cache lifetime, encoding the
+// content hash into the new URL to ensure we do not serve stale
+// data.
+class CacheExtender : public RewriteSingleResourceFilter {
+ public:
+ CacheExtender(RewriteDriver* driver, const char* path_prefix);
+
+ static void Initialize(Statistics* statistics);
+
+ virtual void StartDocumentImpl() {}
+ virtual void StartElementImpl(HtmlElement* element);
+ virtual void EndElementImpl(HtmlElement* element) {}
+
+ virtual const char* Name() const { return "CacheExtender"; }
+
+ protected:
+ virtual bool RewriteLoadedResource(const Resource* input_resource,
+ OutputResource* output_resource);
+
+ private:
+ bool IsRewrittenResource(const StringPiece& url) const;
+
+ HtmlParse* html_parse_;
+ ResourceManager* resource_manager_;
+ ResourceTagScanner tag_scanner_;
+ Variable* extension_count_;
+ Variable* not_cacheable_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(CacheExtender);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_CACHE_EXTENDER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/collapse_whitespace_filter.h b/trunk/src/net/instaweb/rewriter/public/collapse_whitespace_filter.h
new file mode 100644
index 0000000..9adbbf0
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/collapse_whitespace_filter.h
@@ -0,0 +1,59 @@
+/**
+ * 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: mdsteele@google.com (Matthew D. Steele)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_COLLAPSE_WHITESPACE_FILTER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_COLLAPSE_WHITESPACE_FILTER_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/empty_html_filter.h"
+#include "net/instaweb/util/public/atom.h"
+
+namespace net_instaweb {
+
+// Reduce the size of the HTML by collapsing whitespace (except within certain
+// tags, e.g. <pre> and <script>). Note that this is a dangerous filter, as
+// CSS can be used to make the HTML whitespace-sensitive in unpredictable
+// places; thus, it should only be used for content that you are sure will not
+// do this.
+//
+// TODO(mdsteele): Use the CSS parser (once it's finished) to try to
+// intelligently determine when the CSS "white-space: pre" property is in use;
+// that would make this filter much safer.
+class CollapseWhitespaceFilter : public EmptyHtmlFilter {
+ public:
+ explicit CollapseWhitespaceFilter(HtmlParse* html_parse);
+
+ virtual void StartDocument();
+ virtual void StartElement(HtmlElement* element);
+ virtual void EndElement(HtmlElement* element);
+ virtual void Characters(HtmlCharactersNode* characters);
+ virtual const char* Name() const { return "CollapseWhitespace"; }
+
+ private:
+ HtmlParse* html_parse_;
+ std::vector<Atom> atom_stack_;
+ AtomSet sensitive_tags_;
+
+ DISALLOW_COPY_AND_ASSIGN(CollapseWhitespaceFilter);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_COLLAPSE_WHITESPACE_FILTER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/common_filter.h b/trunk/src/net/instaweb/rewriter/public/common_filter.h
new file mode 100644
index 0000000..4eca80d
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/common_filter.h
@@ -0,0 +1,90 @@
+/**
+ * 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)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_BASE_FILTER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_BASE_FILTER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/empty_html_filter.h"
+#include "net/instaweb/util/public/atom.h"
+#include "net/instaweb/util/public/google_url.h"
+
+namespace net_instaweb {
+
+class HtmlElement;
+class HtmlParse;
+class Resource;
+class RewriteDriver;
+class OutputResource;
+class UrlSegmentEncoder;
+
+// CommonFilter encapsulates useful functionality that many filters will want.
+// All filters who want this functionality should inherit from CommonFilter and
+// define the Helper methods rather than the main methods.
+//
+// Currently, it stores the current base URL (which can depend on where you
+// are on a page since the <base> element does not have to be first)
+// and whether we are in a <noscript> element (in which case, we should be
+// careful about moving things out of this element).
+class CommonFilter : public EmptyHtmlFilter {
+ public:
+ CommonFilter(RewriteDriver* driver);
+ virtual ~CommonFilter();
+
+ // Getters
+ HtmlParse* html_parse() { return html_parse_; }
+ const GURL& base_gurl() { return base_gurl_; }
+ HtmlElement* noscript_element() { return noscript_element_; }
+
+ // Note: Don't overload these methods, overload the implementers instead!
+ virtual void StartDocument();
+ virtual void StartElement(HtmlElement* element);
+ virtual void EndElement(HtmlElement* element);
+
+ Resource* CreateInputResource(const StringPiece& url);
+ Resource* CreateInputResourceAbsolute(const StringPiece& url);
+ Resource* CreateInputResourceAndReadIfCached(const StringPiece& url);
+ Resource* CreateInputResourceFromOutputResource(
+ UrlSegmentEncoder* encoder, OutputResource* output_resource);
+
+ protected:
+ // Overload these implementer methods:
+ // Intentionally left abstract so that implementers don't forget to change
+ // the name from Blah to BlahImpl.
+ virtual void StartDocumentImpl() = 0;
+ virtual void StartElementImpl(HtmlElement* element) = 0;
+ virtual void EndElementImpl(HtmlElement* element) = 0;
+
+ private:
+ RewriteDriver* driver_;
+ HtmlParse* html_parse_;
+ // TODO(sligocki): Maybe: don't store a separate GURL in each filter.
+ GURL base_gurl_;
+ HtmlElement* noscript_element_;
+
+ const Atom s_base_;
+ const Atom s_href_;
+ const Atom s_noscript_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CommonFilter);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_BASE_FILTER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/css_combine_filter.h b/trunk/src/net/instaweb/rewriter/public/css_combine_filter.h
new file mode 100644
index 0000000..89d0136
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/css_combine_filter.h
@@ -0,0 +1,94 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_CSS_COMBINE_FILTER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_CSS_COMBINE_FILTER_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/rewriter/public/css_tag_scanner.h"
+#include "net/instaweb/rewriter/public/rewrite_filter.h"
+#include "net/instaweb/util/public/atom.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+class OutputResource;
+class Resource;
+class ResourceManager;
+class Variable;
+
+class CssCombineFilter : public RewriteFilter {
+ public:
+ CssCombineFilter(RewriteDriver* rewrite_driver, const char* path_prefix);
+ virtual ~CssCombineFilter();
+
+ static void Initialize(Statistics* statistics);
+ virtual void StartDocumentImpl();
+ virtual void StartElementImpl(HtmlElement* element) {}
+ virtual void EndElementImpl(HtmlElement* element);
+ virtual void Flush();
+ virtual void IEDirective(HtmlIEDirectiveNode* directive);
+ virtual bool Fetch(OutputResource* resource,
+ Writer* writer,
+ const MetaData& request_header,
+ MetaData* response_headers,
+ MessageHandler* message_handler,
+ UrlAsyncFetcher::Callback* callback);
+ virtual const char* Name() const { return "CssCombine"; }
+
+ private:
+ friend class CssCombiner;
+ typedef std::vector<Resource*> ResourceVector;
+
+ // Try to combine all the CSS files we have seen so far.
+ // Insert the combined resource where the first original CSS link was.
+ void TryCombineAccumulated();
+
+ bool WriteWithAbsoluteUrls(const StringPiece& contents,
+ OutputResource* combination,
+ const std::string& base_url,
+ MessageHandler* handler);
+ bool WriteCombination(const ResourceVector& combine_resources,
+ OutputResource* combination,
+ MessageHandler* handler);
+
+ Atom s_type_;
+ Atom s_link_;
+ Atom s_href_;
+ Atom s_rel_;
+ Atom s_media_;
+ Atom s_style_;
+
+ class Partnership;
+
+ scoped_ptr<Partnership> partnership_;
+ HtmlParse* html_parse_;
+ ResourceManager* resource_manager_;
+ CssTagScanner css_tag_scanner_;
+ Variable* css_file_count_reduction_;
+
+ DISALLOW_COPY_AND_ASSIGN(CssCombineFilter);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_CSS_COMBINE_FILTER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/css_filter.h b/trunk/src/net/instaweb/rewriter/public/css_filter.h
new file mode 100644
index 0000000..09e4799
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/css_filter.h
@@ -0,0 +1,117 @@
+/**
+ * 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)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_CSS_FILTER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_CSS_FILTER_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "net/instaweb/rewriter/public/rewrite_single_resource_filter.h"
+#include "net/instaweb/util/public/atom.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace Css {
+
+class Stylesheet;
+
+} // namespace Css
+
+namespace net_instaweb {
+
+class HtmlParse;
+class MessageHandler;
+class OutputResource;
+class Resource;
+class ResourceManager;
+
+// Find and parse all CSS in the page and apply transformations including:
+// minification, combining, refactoring, and optimizing sub-resources.
+//
+// Currently only does basic minification.
+//
+// Note that CssCombineFilter currently does combining (although there is a bug)
+// but CssFilter will eventually replace this.
+//
+// Currently only deals with inline <style> tags and external <link> resources.
+// It does not consider style= attributes on arbitrary elements.
+class CssFilter : public RewriteSingleResourceFilter {
+ public:
+ CssFilter(RewriteDriver* driver, const StringPiece& filter_prefix);
+
+ static void Initialize(Statistics* statistics);
+ static void Terminate();
+
+ // Note: AtExitManager needs to be initialized or you get a nasty error:
+ // Check failed: false. Tried to RegisterCallback without an AtExitManager.
+ // This is called by Initialize.
+ static void InitializeAtExitManager();
+
+ virtual void StartDocumentImpl();
+ virtual void StartElementImpl(HtmlElement* element);
+ virtual void Characters(HtmlCharactersNode* characters);
+ virtual void EndElementImpl(HtmlElement* element);
+
+ virtual const char* Name() const { return "CssFilter"; }
+
+ static const char kFilesMinified[];
+ static const char kMinifiedBytesSaved[];
+ static const char kParseFailures[];
+
+ private:
+ bool RewriteCssText(const StringPiece& in_text, std::string* out_text,
+ const std::string& id, MessageHandler* handler);
+ bool RewriteExternalCss(const StringPiece& in_url, std::string* out_url);
+ bool RewriteExternalCssToResource(Resource* input_resource,
+ OutputResource* output_resource);
+
+ virtual bool RewriteLoadedResource(const Resource* input_resource,
+ OutputResource* output_resource);
+
+ Css::Stylesheet* CombineStylesheets(
+ std::vector<Css::Stylesheet*>* stylesheets);
+ bool LoadAllSubStylesheets(Css::Stylesheet* stylesheet_with_imports,
+ std::vector<Css::Stylesheet*>* result_stylesheets);
+
+ Css::Stylesheet* LoadStylesheet(const StringPiece& url) { return NULL; }
+
+ HtmlParse* html_parse_;
+ ResourceManager* resource_manager_;
+
+ bool in_style_element_; // Are we in a style element?
+ // These are meaningless if in_style_element_ is false:
+ HtmlElement* style_element_; // The element we are in.
+ HtmlCharactersNode* style_char_node_; // The single character node in style.
+
+ Atom s_style_;
+ Atom s_link_;
+ Atom s_rel_;
+ Atom s_href_;
+
+ // Statistics
+ Variable* num_files_minified_;
+ Variable* minified_bytes_saved_;
+ Variable* num_parse_failures_;
+
+ DISALLOW_COPY_AND_ASSIGN(CssFilter);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_CSS_FILTER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/css_inline_filter.h b/trunk/src/net/instaweb/rewriter/public/css_inline_filter.h
new file mode 100644
index 0000000..5a74985
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/css_inline_filter.h
@@ -0,0 +1,59 @@
+/**
+ * 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: mdsteele@google.com (Matthew D. Steele)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_CSS_INLINE_FILTER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_CSS_INLINE_FILTER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/rewriter/public/common_filter.h"
+#include "net/instaweb/util/public/atom.h"
+#include <string>
+
+namespace net_instaweb {
+
+class ResourceManager;
+class RewriteDriver;
+
+// Inline small CSS files.
+class CssInlineFilter : public CommonFilter {
+ public:
+ explicit CssInlineFilter(RewriteDriver* driver);
+
+ virtual void StartDocumentImpl();
+ virtual void EndDocument();
+ virtual void StartElementImpl(HtmlElement* element) {}
+ virtual void EndElementImpl(HtmlElement* element);
+ virtual const char* Name() const { return "InlineCss"; }
+
+ private:
+ HtmlParse* const html_parse_;
+ const Atom href_atom_;
+ const Atom link_atom_;
+ const Atom media_atom_;
+ const Atom rel_atom_;
+ const Atom style_atom_;
+ const size_t size_threshold_bytes_;
+
+ std::string domain_;
+
+ DISALLOW_COPY_AND_ASSIGN(CssInlineFilter);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_CSS_INLINE_FILTER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/css_minify.h b/trunk/src/net/instaweb/rewriter/public/css_minify.h
new file mode 100644
index 0000000..d654265
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/css_minify.h
@@ -0,0 +1,82 @@
+/**
+ * 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)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_CSS_MINIFY_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_CSS_MINIFY_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/string_util.h"
+
+namespace Css {
+class Stylesheet;
+class Import;
+class Ruleset;
+class Selector;
+class SimpleSelector;
+class SimpleSelectors;
+class Declaration;
+class Value;
+}
+
+namespace net_instaweb {
+
+class MessageHandler;
+class Writer;
+
+class CssMinify {
+ public:
+ // Write minified Stylesheet.
+ static bool Stylesheet(const Css::Stylesheet& stylesheet,
+ Writer* writer,
+ MessageHandler* handler);
+
+ private:
+ CssMinify(Writer* writer, MessageHandler* handler);
+ ~CssMinify();
+
+ void Write(const StringPiece& str);
+
+ template<typename Container>
+ void JoinMinify(const Container& container, const StringPiece& sep);
+ template<typename Iterator>
+ void JoinMinifyIter(const Iterator& begin, const Iterator& end,
+ const StringPiece& sep);
+ template<typename Container>
+ void JoinMediaMinify(const Container& container, const StringPiece& sep);
+
+ // We name all of these methods identically to simplify the writing of the
+ // templated Join* methods.
+ void Minify(const Css::Stylesheet& stylesheet);
+ void Minify(const Css::Import& import);
+ void Minify(const Css::Ruleset& ruleset);
+ void Minify(const Css::Selector& selector);
+ void Minify(const Css::SimpleSelectors& sselectors, bool isfirst = false);
+ void Minify(const Css::SimpleSelector& sselector);
+ void Minify(const Css::Declaration& declaration);
+ void Minify(const Css::Value& value);
+
+ Writer* writer_;
+ MessageHandler* handler_;
+ bool ok_;
+
+ DISALLOW_COPY_AND_ASSIGN(CssMinify);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_CSS_MINIFY_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/css_move_to_head_filter.h b/trunk/src/net/instaweb/rewriter/public/css_move_to_head_filter.h
new file mode 100644
index 0000000..cc98714
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/css_move_to_head_filter.h
@@ -0,0 +1,58 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_CSS_MOVE_TO_HEAD_FILTER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_CSS_MOVE_TO_HEAD_FILTER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/empty_html_filter.h"
+#include "net/instaweb/rewriter/public/css_tag_scanner.h"
+#include "net/instaweb/util/public/atom.h"
+
+namespace net_instaweb {
+
+class Statistics;
+class Variable;
+
+class CssMoveToHeadFilter : public EmptyHtmlFilter {
+ public:
+ CssMoveToHeadFilter(HtmlParse* html_parse, Statistics* statistics);
+
+ static void Initialize(Statistics* statistics);
+ virtual void StartDocument();
+ virtual void StartElement(HtmlElement* element);
+ virtual void EndElement(HtmlElement* element);
+ virtual const char* Name() const { return "CssMoveToHead"; }
+
+ private:
+ Atom s_head_;
+ Atom s_noscript_;
+ Atom s_style_;
+
+ HtmlParse* html_parse_;
+ HtmlElement* head_element_;
+ HtmlElement* noscript_element_;
+ CssTagScanner css_tag_scanner_;
+ Variable* counter_;
+
+ DISALLOW_COPY_AND_ASSIGN(CssMoveToHeadFilter);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_CSS_MOVE_TO_HEAD_FILTER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/css_outline_filter.h b/trunk/src/net/instaweb/rewriter/public/css_outline_filter.h
new file mode 100644
index 0000000..9332fa6
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/css_outline_filter.h
@@ -0,0 +1,86 @@
+/**
+ * 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)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_CSS_OUTLINE_FILTER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_CSS_OUTLINE_FILTER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/rewriter/public/common_filter.h"
+#include "net/instaweb/util/public/atom.h"
+#include <string>
+
+namespace net_instaweb {
+
+class MessageHandler;
+class MetaData;
+class OutputResource;
+class ResourceManager;
+
+// Filter to take explicit <style> and <script> tags and outline them to files.
+class CssOutlineFilter : public CommonFilter {
+ public:
+ explicit CssOutlineFilter(RewriteDriver* driver);
+ static const char kFilterId[];
+
+ virtual void StartDocumentImpl();
+
+ virtual void StartElementImpl(HtmlElement* element);
+ virtual void EndElementImpl(HtmlElement* element);
+
+ virtual void Flush();
+
+ // HTML Events we expect to be in <style> elements.
+ virtual void Characters(HtmlCharactersNode* characters);
+
+ // HTML Events we do not expect to be in <style> and <script> elements.
+ virtual void Comment(HtmlCommentNode* comment);
+ virtual void Cdata(HtmlCdataNode* cdata);
+ virtual void IEDirective(HtmlIEDirectiveNode* directive);
+
+ // Ignored HTML Events.
+ virtual void EndDocument() {}
+ virtual void Directive(HtmlDirectiveNode* directive) {}
+
+ virtual const char* Name() const { return "OutlineCss"; }
+
+ private:
+ bool WriteResource(const StringPiece& content, OutputResource* resource,
+ MessageHandler* handler);
+ void OutlineStyle(HtmlElement* element, const std::string& content);
+
+ // The style or script element we are in (if it hasn't been flushed).
+ // If we are not in a script or style element, inline_element_ == NULL.
+ HtmlElement* inline_element_;
+ // Temporarily buffers the content between open and close of inline_element_.
+ std::string buffer_;
+ HtmlParse* html_parse_;
+ ResourceManager* resource_manager_;
+ size_t size_threshold_bytes_;
+ // HTML strings interned into a symbol table.
+ Atom s_link_;
+ Atom s_style_;
+ Atom s_rel_;
+ Atom s_href_;
+ Atom s_type_;
+
+ DISALLOW_COPY_AND_ASSIGN(CssOutlineFilter);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_CSS_OUTLINE_FILTER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/css_tag_scanner.h b/trunk/src/net/instaweb/rewriter/public/css_tag_scanner.h
new file mode 100644
index 0000000..602a3b6
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/css_tag_scanner.h
@@ -0,0 +1,65 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_CSS_TAG_SCANNER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_CSS_TAG_SCANNER_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/html_parser_types.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/util/public/atom.h"
+#include <string>
+
+namespace net_instaweb {
+
+class CssTagScanner {
+ public:
+ explicit CssTagScanner(HtmlParse* html_parse);
+
+ // Examines an HTML element to determine if it's a CSS link,
+ // extracting out the HREF and the media-type.
+ bool ParseCssElement(
+ HtmlElement* element, HtmlElement::Attribute** href, const char** media);
+
+ // Scans the contents of a CSS file, looking for the pattern url(xxx).
+ // If xxx is a relative URL, it absolutifies it based on the passed-in base
+ // path. If xxx is quoted with single-quotes or double-quotes, those are
+ // retained and the URL inside is absolutified.
+ static bool AbsolutifyUrls(const StringPiece& contents,
+ const std::string& base_url,
+ Writer* writer, MessageHandler* handler);
+
+ // Does this CSS file contain @import? If so, it cannot be combined with
+ // previous CSS files. This may give false-positives, but no false-negatives.
+ static bool HasImport(const StringPiece& contents, MessageHandler* handler);
+
+ private:
+ Atom s_link_;
+ Atom s_href_;
+ Atom s_type_;
+ Atom s_rel_;
+ Atom s_media_;
+
+ DISALLOW_COPY_AND_ASSIGN(CssTagScanner);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_CSS_TAG_SCANNER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/data_url_input_resource.h b/trunk/src/net/instaweb/rewriter/public/data_url_input_resource.h
new file mode 100644
index 0000000..7bb2a1e
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/data_url_input_resource.h
@@ -0,0 +1,87 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+//
+// An input resource representing a data: url. This is uncommon in web
+// pages, but we generate these urls as a result of image inlining and
+// this confuses subsequent filters in certain cases.
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/rewriter/public/resource.h"
+#include "net/instaweb/util/public/data_url.h"
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_DATA_URL_INPUT_RESOURCE_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_DATA_URL_INPUT_RESOURCE_H_
+
+namespace net_instaweb {
+
+class ResourceManager;
+class ContentType;
+enum Encoding;
+
+class DataUrlInputResource : public Resource {
+ public:
+ // We expose a factory; parse failure returns NULL.
+ static DataUrlInputResource* Make(const StringPiece& url,
+ ResourceManager* manager) {
+ const ContentType* type;
+ Encoding encoding;
+ StringPiece encoded_contents;
+ // We create the local copy of the url early, because
+ // encoded_contents will in general be a substring of this
+ // local copy and must have the same lifetime.
+ std::string* url_copy = new std::string();
+ url.CopyToString(url_copy);
+ if (!ParseDataUrl(*url_copy, &type, &encoding, &encoded_contents)) {
+ return NULL;
+ }
+ return new DataUrlInputResource(url_copy, encoding, type, encoded_contents,
+ manager);
+ }
+
+ virtual ~DataUrlInputResource() { }
+
+ virtual std::string url() const { return *url_.get(); }
+
+ protected:
+ virtual bool Load(MessageHandler* message_handler);
+ virtual bool IsCacheable() const;
+
+ private:
+ DataUrlInputResource(const std::string* url,
+ Encoding encoding,
+ const ContentType* type,
+ const StringPiece& encoded_contents,
+ ResourceManager* manager)
+ : Resource(manager, type),
+ url_(url),
+ encoding_(encoding),
+ encoded_contents_(encoded_contents) {
+ }
+
+ scoped_ptr<const std::string> url_;
+ const Encoding encoding_;
+ const StringPiece encoded_contents_; // substring of url.
+ std::string decoded_contents_;
+
+ DISALLOW_COPY_AND_ASSIGN(DataUrlInputResource);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_DATA_URL_INPUT_RESOURCE_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/domain_lawyer.h b/trunk/src/net/instaweb/rewriter/public/domain_lawyer.h
new file mode 100644
index 0000000..168cfea
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/domain_lawyer.h
@@ -0,0 +1,164 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+//
+// This class manages the relationships between domains and resources.
+//
+// The Lawyer keeps track of which domains we are allowed to rewrite, including
+// whether multiple resources can be bundled together.
+//
+// The Lawyer keeps track of domain mappings to move resources onto a CDN or
+// onto a cookieless domain.
+//
+// The Lawyer keeps track of domain sharding, for distributing resources across
+// equivalent domains to improve browser download parallelism.
+//
+// The class here holds state based on the configuration files
+// (e.g. Apache .conf).
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_DOMAIN_LAWYER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_DOMAIN_LAWYER_H_
+
+#include <map>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/util/public/google_url.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+class MessageHandler;
+
+class DomainLawyer {
+ public:
+ DomainLawyer() {}
+ ~DomainLawyer();
+
+ // Determines whether a resource can be rewritten, and returns the domain
+ // that it should be written to. Only the domain of the resolved request
+ // is considered. If the resource_url is relative (has no domain) then
+ // the resource can always be written, and will share the domain of the
+ // original request.
+ //
+ // The resource_url is considered relative to original_request. Generally
+ // it is always accessible to rewrite resources in the same domain as the
+ // original.
+ //
+ // TODO(jmarantz): the mapped domain name will not incorporate any sharding.
+ // This must be handled by another mapping function which has not yet
+ // been implemented.
+ //
+ // The returned mapped_domain_name will always end with a slash on success.
+ // The returned resolved_request incorporates rewrite-domain mapping and
+ // the original URL.
+ //
+ // Returns false on failure.
+ bool MapRequestToDomain(const GURL& original_request,
+ const StringPiece& resource_url,
+ std::string* mapped_domain_name,
+ GURL* resolved_request,
+ MessageHandler* handler) const;
+
+ // Maps an origin resource; just prior to fetching it. This fails
+ // if the input URL is not valid. It succeeds even if there is no
+ // mapping done. You must compare 'in' to 'out' to determine if
+ // mapping was done.
+ bool MapOrigin(const StringPiece& in, std::string* out) const;
+
+ // The methods below this comment are intended only to be run only
+ // at configuration time.
+
+ // Adds a simple domain to the set that can be rewritten. No
+ // mapping or sharding will be performed. Returns false if the
+ // domain syntax was not acceptable. Wildcards (*, ?) may be used in
+ // the domain_name. Careless use of wildcards can expose the user to
+ // XSS attacks.
+ bool AddDomain(const StringPiece& domain_name, MessageHandler* handler);
+
+ // Adds a domain mapping, to assist with serving resources from
+ // cookieless domains or CDNs. This implicitly calls AddDomain(to_domain)
+ // and AddDomain(from_domain) if necessary. If either 'to' or 'from' has
+ // invalid syntax then this function returns false and has no effect.
+ //
+ // Wildcards may not be used in the to_domain, but they can be used
+ // in the from_domains.
+ //
+ // This routine can be called multiple times for the same to_domain. If
+ // the 'from' domains overlap due to wildcards, this will not be detected.
+ bool AddRewriteDomainMapping(const StringPiece& to_domain,
+ const StringPiece& comma_separated_from_domains,
+ MessageHandler* handler);
+
+ // Adds a domain mapping, to assist with fetching resources from locally
+ // signficant names/ip-addresses.
+ //
+ // Wildcards may not be used in the to_domain, but they can be used
+ // in the from_domains.
+ //
+ // This routine can be called multiple times for the same to_domain. If
+ // the 'from' domains overlap due to wildcards, this will not be detected.
+ bool AddOriginDomainMapping(const StringPiece& to_domain,
+ const StringPiece& comma_separated_from_domains,
+ MessageHandler* handler);
+
+ // Specifies domain-sharding. This implicitly calls AddDomain(to_domain).
+ // The shard_pattern must include exactly one '%d'.
+ //
+ // Wildcards may not be used in the to_domain or the from_domain.
+ //
+ // TODO(jmarantz): implement this
+ bool ShardDomain(const StringPiece& to_domain,
+ const StringPiece& shard_pattern,
+ int num_shards, MessageHandler* handler);
+
+ // Merge the domains declared in src into this. There are no exclusions, so
+ // this is really just aggregating the mappings and authorizations declared in
+ // both domains. When the same domain is mapped in 'this' and 'src', 'src'
+ // wins.
+ void Merge(const DomainLawyer& src);
+
+ private:
+ class Domain;
+ typedef void (Domain::*SetDomainFn)(Domain* domain);
+
+ bool MapDomainHelper(
+ const StringPiece& to_domain_name,
+ const StringPiece& comma_separated_from_domains,
+ SetDomainFn set_domain_fn,
+ MessageHandler* handler);
+
+ Domain* AddDomainHelper(const StringPiece& domain_name,
+ bool warn_on_duplicate,
+ MessageHandler* handler);
+
+ Domain* FindDomain(const std::string& domain_name) const;
+
+ typedef std::map<std::string, Domain*> DomainMap;
+ DomainMap domain_map_;
+ typedef std::vector<Domain*> DomainVector;
+ DomainVector wildcarded_domains_;
+ // If you add more fields here, please be sure to update Merge().
+
+ DISALLOW_COPY_AND_ASSIGN(DomainLawyer);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_DOMAIN_LAWYER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/elide_attributes_filter.h b/trunk/src/net/instaweb/rewriter/public/elide_attributes_filter.h
new file mode 100644
index 0000000..cf227ae
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/elide_attributes_filter.h
@@ -0,0 +1,57 @@
+/**
+ * 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: mdsteele@google.com (Matthew D. Steele)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_ELIDE_ATTRIBUTES_FILTER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_ELIDE_ATTRIBUTES_FILTER_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/empty_html_filter.h"
+#include "net/instaweb/util/public/atom.h"
+
+namespace net_instaweb {
+
+// Remove attributes and attribute values that can be safely elided.
+class ElideAttributesFilter : public EmptyHtmlFilter {
+ public:
+ explicit ElideAttributesFilter(HtmlParse* html_parse);
+
+ virtual void StartElement(HtmlElement* element);
+ virtual const char* Name() const { return "ElideAttributes"; }
+
+ private:
+ struct AttrValue {
+ const char* attr_value;
+ bool requires_version_5; // Default value only exists in (X)HTML 5.
+ };
+
+ typedef std::map<Atom, AtomSet, AtomCompare> AtomSetMap;
+ typedef std::map<Atom, AttrValue, AtomCompare> ValueMap;
+ typedef std::map<Atom, ValueMap, AtomCompare> ValueMapMap;
+
+ HtmlParse* html_parse_;
+ AtomSetMap one_value_attrs_map_; // tag/attrs with only one possible value
+ ValueMapMap default_value_map_; // tag/attrs with default values
+
+ DISALLOW_COPY_AND_ASSIGN(ElideAttributesFilter);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_ELIDE_ATTRIBUTES_FILTER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/file_input_resource.h b/trunk/src/net/instaweb/rewriter/public/file_input_resource.h
new file mode 100644
index 0000000..1ec2fdd
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/file_input_resource.h
@@ -0,0 +1,56 @@
+/**
+ * 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)
+//
+// Input resource created based on a local file.
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_FILE_INPUT_RESOURCE_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_FILE_INPUT_RESOURCE_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/rewriter/public/resource.h"
+
+namespace net_instaweb {
+
+class FileInputResource : public Resource {
+ public:
+ FileInputResource(ResourceManager* manager,
+ const ContentType* type,
+ const StringPiece& url,
+ const StringPiece& filename)
+ : Resource(manager, type),
+ url_(url.data(), url.size()),
+ filename_(filename.data(), filename.size()) {
+ }
+
+ virtual ~FileInputResource();
+
+ virtual std::string url() const { return url_; }
+
+ protected:
+ virtual bool Load(MessageHandler* message_handler);
+
+ private:
+ std::string url_;
+ std::string filename_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileInputResource);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_FILE_INPUT_RESOURCE_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/html_attribute_quote_removal.h b/trunk/src/net/instaweb/rewriter/public/html_attribute_quote_removal.h
new file mode 100644
index 0000000..eb0f4fa
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/html_attribute_quote_removal.h
@@ -0,0 +1,67 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_HTML_ATTRIBUTE_QUOTE_REMOVAL_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_HTML_ATTRIBUTE_QUOTE_REMOVAL_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/empty_html_filter.h"
+
+namespace net_instaweb {
+// Very simple html filter that removes quotes from attributes that don't need
+// them.
+//
+// From http://www.w3.org/TR/REC-html40/intro/sgmltut.html#h-3.2.2:
+// In certain cases, authors may specify the value of an attribute without any
+// quotation marks. The attribute value may only contain letters (a-z and A-Z),
+// digits (0-9), hyphens (ASCII decimal 45), periods (ASCII decimal 46),
+// underscores (ASCII decimal 95), and colons (ASCII decimal 58).
+//
+// This is an experiment, to see if quote removal *actually* saves bandwidth.
+// After compression it may not (or may not save enough). In that case we
+// should not bother with quote removal.
+
+class HtmlElement;
+class HtmlParse;
+class ResourceManager;
+
+class HtmlAttributeQuoteRemoval : public EmptyHtmlFilter {
+ public:
+ explicit HtmlAttributeQuoteRemoval(HtmlParse* html_parse);
+ // Given context in object, does attribute value val require quotes?
+ bool NeedsQuotes(const char *val);
+ virtual void StartElement(HtmlElement* element);
+ // # of quote pairs removed from attributes in *all* documents processed.
+ int total_quotes_removed() const {
+ return total_quotes_removed_;
+ }
+
+ virtual const char* Name() const { return "HtmlAttributeQuoteRemoval"; }
+
+ private:
+ int total_quotes_removed_;
+ HtmlParse* html_parse_;
+ bool needs_no_quotes_[256]; // lookup chars for quotability
+ // should be const, but C++ initializer rules are broken.
+
+ DISALLOW_COPY_AND_ASSIGN(HtmlAttributeQuoteRemoval);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_HTML_ATTRIBUTE_QUOTE_REMOVAL_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/image.h b/trunk/src/net/instaweb/rewriter/public/image.h
new file mode 100644
index 0000000..6935b22
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/image.h
@@ -0,0 +1,210 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_IMAGE_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_IMAGE_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/rewriter/public/image_dim.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+#ifdef USE_SYSTEM_OPENCV
+#include "cv.h"
+#else
+#include "third_party/opencv/src/opencv/include/opencv/cv.h"
+#endif
+
+namespace net_instaweb {
+
+struct ContentType;
+struct ImageDim;
+class FileSystem;
+class MessageHandler;
+class Writer;
+
+// The following four helper functions were moved here for testability. We ran
+// into problems with sign extension under different compiler versions, and we'd
+// like to catch regressions on that front in the future.
+
+// char to int *without sign extension*.
+inline int CharToInt(char c) {
+ uint8 uc = static_cast<uint8>(c);
+ return static_cast<int>(uc);
+}
+
+inline int JpegIntAtPosition(const StringPiece& buf, size_t pos) {
+ return (CharToInt(buf[pos]) << 8) |
+ (CharToInt(buf[pos + 1]));
+}
+
+inline int GifIntAtPosition(const StringPiece& buf, size_t pos) {
+ return (CharToInt(buf[pos + 1]) << 8) |
+ (CharToInt(buf[pos]));
+}
+
+inline int PngIntAtPosition(const StringPiece& buf, size_t pos) {
+ return (CharToInt(buf[pos ]) << 24) |
+ (CharToInt(buf[pos + 1]) << 16) |
+ (CharToInt(buf[pos + 2]) << 8) |
+ (CharToInt(buf[pos + 3]));
+}
+
+inline bool PngSectionIdIs(const char* hdr,
+ const StringPiece& buf, size_t pos) {
+ return ((buf[pos + 4] == hdr[0]) &&
+ (buf[pos + 5] == hdr[1]) &&
+ (buf[pos + 6] == hdr[2]) &&
+ (buf[pos + 7] == hdr[3]));
+}
+
+namespace ImageHeaders {
+ // Constants that are shared by Image and its tests.
+ extern const char kPngHeader[];
+ extern const size_t kPngHeaderLength;
+ extern const char kPngIHDR[];
+ extern const size_t kPngIHDRLength;
+ extern const size_t kIHDRDataStart;
+ extern const size_t kPngIntSize;
+
+ extern const char kGifHeader[];
+ extern const size_t kGifHeaderLength;
+ extern const size_t kGifDimStart;
+ extern const size_t kGifIntSize;
+
+ extern const size_t kJpegIntSize;
+} // namespace ImageHeaders
+
+class Image {
+ public:
+ // Images that are in the process of being transformed are represented by an
+ // Image. This class encapsulates various operations that are sensitive to
+ // the format of the compressed image file and of the image libraries we are
+ // using. In particular, the timing of compression and decompression
+ // operations may be a bit unexpected, because we may do these operations
+ // early in order to retrieve image metadata, or we may choose to skip them
+ // entirely if we don't need them or don't understand how to do them.
+ //
+ // In future we may need to plumb this to other data sources or change how
+ // metadata is retrieved; the object is to do so locally in this class without
+ // disrupting any of its clients.
+
+ enum Type {
+ IMAGE_UNKNOWN = 0,
+ IMAGE_JPEG,
+ IMAGE_PNG,
+ IMAGE_GIF,
+ };
+
+ // Image owns none of its inputs. All of the arguments to Image(...) (the
+ // original_contents in particular) must outlive the Image object itself. The
+ // intent is that an Image is created in a scoped fashion from an existing
+ // known resource.
+ Image(const StringPiece& original_contents,
+ const std::string& url,
+ const StringPiece& file_prefix,
+ FileSystem* file_system,
+ MessageHandler* handler);
+
+ ~Image();
+
+ // Stores the image dimensions in natural_dim (on success, sets
+ // natural_dim->{width, height} and natural_dim->valid = true). This method
+ // can fail (natural_dim->valid == false) for various reasons: we don't
+ // understand the image format (eg a gif), we can't find the headers, the
+ // library doesn't support a particular encoding, etc. In that case the other
+ // fields are left alone.
+ void Dimensions(ImageDim* natural_dim);
+
+ // Returns the size of original input in bytes.
+ size_t input_size() const {
+ return original_contents_.size();
+ }
+
+ // Returns the size of output image in bytes.
+ size_t output_size() {
+ size_t ret;
+ if (output_valid_ || ComputeOutputContents()) {
+ ret = output_contents_.size();
+ } else {
+ ret = input_size();
+ }
+ return ret;
+ }
+
+ Type image_type() {
+ if (image_type_ == IMAGE_UNKNOWN) {
+ ComputeImageType();
+ }
+ return image_type_;
+ }
+
+ // Returns true if the image has transparency (an alpha channel, or a
+ // transparent color). Note that certain ambiguously-formatted images might
+ // yield false positive results here; we don't check whether alpha channels
+ // contain non-opaque data, nor do we check if a distinguished transparent
+ // color is actually used in an image. We assume that if the image file
+ // contains flags for transparency, it does so for a reason.
+ bool HasTransparency();
+
+ // Changes the size of the image to the given width and height. This will run
+ // image processing on the image, and return false if the image processing
+ // fails. Otherwise the image contents and type can change.
+ bool ResizeTo(const ImageDim& new_dim);
+
+ // UndoResize lets us bail out if a resize actually cost space!
+ void UndoResize();
+
+ // Returns image-appropriate content type, or NULL if no content type is
+ // known. Result is a top-level const pointer and should not be deleted etc.
+ const ContentType* content_type();
+
+ // Returns the best known image contents. If image type is not understood,
+ // then Contents() will have NULL data().
+ StringPiece Contents();
+
+ private:
+ // Internal methods used only in image.cc (see there for more).
+ void ComputeImageType();
+ void FindJpegSize();
+ inline void FindPngSize();
+ bool ComputePngTransparency();
+ inline void FindGifSize();
+ bool LoadOpenCV();
+ void CleanOpenCV();
+ bool ComputeOutputContents();
+
+ const std::string file_prefix_;
+ FileSystem* file_system_;
+ MessageHandler* handler_;
+ Type image_type_; // Lazily initialized, initially IMAGE_UNKNOWN.
+ const StringPiece original_contents_;
+ std::string output_contents_; // Lazily filled.
+ bool output_valid_; // Indicates output_contents_ now correct.
+ std::string opencv_filename_; // Lazily holds generated name of temp file.
+ IplImage* opencv_image_; // Lazily filled on OpenCV load.
+ bool opencv_load_possible_; // Attempt opencv_load in future?
+ bool resized_;
+ const std::string url_;
+ ImageDim dims_;
+
+ DISALLOW_COPY_AND_ASSIGN(Image);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_IMAGE_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/image_dim.h b/trunk/src/net/instaweb/rewriter/public/image_dim.h
new file mode 100644
index 0000000..0fc375e
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/image_dim.h
@@ -0,0 +1,60 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_IMAGE_DIM_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_IMAGE_DIM_H_
+
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+// Specification for image dimensions, either in an image file
+// or in the page source. This is 1 method away from a struct.
+class ImageDim {
+ public:
+ // Constructor ensures repeatable field values.
+ ImageDim() : valid_(false), width_(-1), height_(-1) { }
+
+ int width() const { return width_; }
+ int height() const { return height_; }
+ bool valid() const { return valid_; }
+
+ void invalidate() { valid_ = false; }
+ void set_dims(int width, int height) {
+ width_ = width;
+ height_ = height;
+ valid_ = true;
+ }
+
+ // *Append* encoding of this to *out.
+ void EncodeTo(std::string* out) const;
+
+ // Decode encoding from "in", truncating it to remove consumed data.
+ // Invalidate this and return false on parse failure; "in" is in an
+ // arbitrary state in this case.
+ bool DecodeFrom(StringPiece* in);
+
+ private:
+ bool valid_; // If false, other two fields have arbitrary values.
+ int width_;
+ int height_;
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_IMAGE_DIM_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/img_rewrite_filter.h b/trunk/src/net/instaweb/rewriter/public/img_rewrite_filter.h
new file mode 100644
index 0000000..dbc2e42
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/img_rewrite_filter.h
@@ -0,0 +1,152 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_IMG_REWRITE_FILTER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_IMG_REWRITE_FILTER_H_
+
+#include "net/instaweb/rewriter/public/rewrite_filter.h"
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/rewriter/public/img_tag_scanner.h"
+#include "net/instaweb/rewriter/public/resource.h"
+#include "net/instaweb/util/public/atom.h"
+#include <string>
+#include "net/instaweb/util/public/url_segment_encoder.h"
+#include "net/instaweb/util/public/work_bound.h"
+
+namespace net_instaweb {
+
+class ContentType;
+class FileSystem;
+class HtmlParse;
+class Image;
+class ImageDim;
+class OutputResource;
+class ResourceManager;
+class UrlEscaper;
+class Variable;
+
+// This class supports the encoding of image urls with optional
+// additional dimension metadata. The passed-in stored_dim is used as
+// the source and/or destination of this metadata during encode/decode
+// respectively.
+class ImageUrlEncoder : public UrlSegmentEncoder {
+ public:
+ ImageUrlEncoder(UrlEscaper* url_escaper, ImageDim* stored_dim);
+ virtual ~ImageUrlEncoder();
+
+ // Encode an origin_url and stored_dim from origin page to a rewritten_url.
+ virtual void EncodeToUrlSegment(
+ const StringPiece& origin_url, std::string* rewritten_url);
+
+ // Decode an origin_url and stored_dim from a rewritten_url, returning false
+ // on parse failure (invalidating output vars).
+ virtual bool DecodeFromUrlSegment(const StringPiece& rewritten_url,
+ std::string* origin_url);
+
+ private:
+ UrlEscaper* url_escaper_;
+ ImageDim* stored_dim_;
+};
+
+// Identify img tags in html and optimize them.
+// TODO(jmaessen): See which ones have immediately-obvious size info.
+// TODO(jmaessen): Provide alternate resources at rewritten urls
+// asynchronously somehow.
+// TODO(jmaessen): Big open question: how best to link pulled-in resources to
+// rewritten urls, when in general those urls will be in a different domain.
+class ImgRewriteFilter : public RewriteFilter {
+ public:
+ ImgRewriteFilter(RewriteDriver* driver,
+ bool log_image_elements,
+ bool insert_image_dimensions,
+ StringPiece path_prefix,
+ size_t img_inline_max_bytes,
+ size_t img_max_rewrites_at_once);
+ static void Initialize(Statistics* statistics);
+ virtual void StartDocumentImpl() {}
+ virtual void StartElementImpl(HtmlElement* element) {}
+ virtual void EndElementImpl(HtmlElement* element);
+ virtual void Flush();
+ virtual bool Fetch(OutputResource* resource,
+ Writer* writer,
+ const MetaData& request_header,
+ MetaData* response_headers,
+ MessageHandler* message_handler,
+ UrlAsyncFetcher::Callback* callback);
+ virtual const char* Name() const { return "ImgRewrite"; }
+
+ // Can we inline resource? If so, encode its contents into the data_url,
+ // otherwise leave data_url alone.
+ static bool CanInline(
+ int img_inline_max_bytes, const StringPiece& contents,
+ const ContentType* content_type, std::string* data_url);
+
+ private:
+ // Helper methods.
+ Image* GetImage(const StringPiece& origin_url, Resource* img_resource);
+ OutputResource* ImageOutputResource(const std::string& url_string,
+ Image* image);
+ const ContentType* ImageToContentType(const std::string& origin_url,
+ Image* image);
+ void OptimizeImage(const Resource& input_resource, const ImageDim& page_dim,
+ Image* image, OutputResource* result);
+ bool OptimizedImageFor(
+ const StringPiece& origin_url, const ImageDim& page_dim,
+ Resource* img_resource, OutputResource* output);
+ void RewriteImageUrl(HtmlElement* element, HtmlElement::Attribute* src);
+ void UpdateTargetElement(const Resource& input_resource,
+ const OutputResource& output_resource,
+ const ImageDim& page_dim, const ImageDim& actual_dim,
+ HtmlElement* element, HtmlElement::Attribute* src);
+
+ FileSystem* file_system_;
+ HtmlParse* html_parse_;
+ scoped_ptr<const ImgTagScanner> img_filter_;
+ ResourceManager* resource_manager_;
+ scoped_ptr<WorkBound> work_bound_;
+ // Threshold size (in bytes) below which we should just inline images
+ // encountered.
+ // TODO(jmaessen): Heuristic must be more sophisticated. Does this image
+ // touch a fresh domain? Require opening a new connection? If so we can
+ // afford to inline quite large images (basically anything we could transmit
+ // in the resulting RTTs)---but of course we don't know about RTT here. In
+ // the absence of such information, we ought to inline if header length + url
+ // size can be saved by inlining image, without increasing the size in packets
+ // of the html. Otherwise we end up loading the image in favor of the html,
+ // which might be a lose. More work is needed here to figure out the exact
+ // tradeoffs involved, especially as we also undermine image cacheability.
+ size_t img_inline_max_bytes_;
+ // Should we log each image element as we encounter it? Handy for debug.
+ bool log_image_elements_;
+ // Should we insert image dimensions into html if they are absent?
+ bool insert_image_dimensions_;
+ const Atom s_width_;
+ const Atom s_height_;
+ Variable* rewrite_count_;
+ Variable* inline_count_;
+ Variable* rewrite_saved_bytes_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImgRewriteFilter);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_IMG_REWRITE_FILTER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/img_tag_scanner.h b/trunk/src/net/instaweb/rewriter/public/img_tag_scanner.h
new file mode 100644
index 0000000..739356c
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/img_tag_scanner.h
@@ -0,0 +1,48 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_IMG_TAG_SCANNER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_IMG_TAG_SCANNER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/util/public/atom.h"
+
+namespace net_instaweb {
+class HtmlParse;
+
+class ImgTagScanner {
+ public:
+ explicit ImgTagScanner(HtmlParse* html_parse);
+
+ // Examine HTML element and determine if it is an img with a src. If so
+ // extract the src attribute and return it, otherwise return NULL.
+ HtmlElement::Attribute* ParseImgElement(HtmlElement* element) const;
+
+ private:
+ const Atom s_img_;
+ const Atom s_input_;
+ const Atom s_src_;
+ const Atom s_type_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImgTagScanner);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_IMG_TAG_SCANNER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/javascript_code_block.h b/trunk/src/net/instaweb/rewriter/public/javascript_code_block.h
new file mode 100644
index 0000000..03af5bc
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/javascript_code_block.h
@@ -0,0 +1,144 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_JAVASCRIPT_CODE_BLOCK_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_JAVASCRIPT_CODE_BLOCK_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/rewriter/public/javascript_library_identification.h"
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/statistics.h"
+
+namespace net_instaweb {
+
+class MessageHandler;
+class Statistics;
+class Variable;
+
+// Class wrapping up configuration information for javascript
+// rewriting, in order to minimize footprint of later changes
+// to javascript rewriting.
+class JavascriptRewriteConfig {
+ public:
+ explicit JavascriptRewriteConfig(Statistics* statistics);
+ static void Initialize(Statistics* statistics);
+ // Whether to minify javascript output (using jsminify).
+ // true by default.
+ bool minify() const {
+ return minify_;
+ }
+ void set_minify(bool minify) {
+ minify_ = minify;
+ }
+ // whether to redirect external javascript libraries to
+ // Google-as-a-CDN
+ bool redirect() const {
+ return redirect_;
+ }
+ void set_redirect(bool redirect) {
+ redirect_ = redirect;
+ }
+ void AddBytesSaved(size_t bytes) {
+ if (bytes_saved_ != NULL) {
+ bytes_saved_->Add(bytes);
+ blocks_minified_->Add(1);
+ }
+ }
+ void AddMinificationFailure() {
+ if (minification_failures_ != NULL) {
+ minification_failures_->Add(1);
+ }
+ }
+ void AddBlock() {
+ if (total_blocks_ != NULL) {
+ total_blocks_->Add(1);
+ }
+ }
+ private:
+ bool minify_;
+ bool redirect_;
+ Variable* blocks_minified_;
+ Variable* bytes_saved_;
+ Variable* minification_failures_;
+ Variable* total_blocks_;
+
+ DISALLOW_COPY_AND_ASSIGN(JavascriptRewriteConfig);
+};
+
+// Object representing a block of Javascript code that might be a
+// candidate for rewriting.
+// TODO(jmaessen): Does this architecture make sense when we have
+// multiple scripts on a page and the ability to move code around
+// a bunch? How do we maintain JS context in that setting?
+//
+// For now, we're content just being able to pull data in and parse it at all.
+class JavascriptCodeBlock {
+ public:
+ JavascriptCodeBlock(const StringPiece& original_code,
+ JavascriptRewriteConfig* config,
+ MessageHandler* handler);
+
+ virtual ~JavascriptCodeBlock();
+
+ // Is it profitable to replace js code with rewritten version?
+ bool ProfitableToRewrite() {
+ RewriteIfNecessary();
+ return (output_code_.size() < original_code_.size());
+ }
+ // TODO(jmaessen): Other questions we might reasonably ask:
+ // Can this code be floated downwards?
+
+ // Returns the current (maximally-rewritten) contents of the
+ // code block.
+ const StringPiece Rewritten() {
+ RewriteIfNecessary();
+ return output_code_;
+ }
+
+ // Is the current block a JS library that can be redirected to Google?
+ // If so, return the info necessary to do so. Otherwise returns a
+ // block for which .recognized() is false.
+ const JavascriptLibraryId ComputeJavascriptLibrary();
+
+ private:
+ void RewriteIfNecessary() {
+ if (!rewritten_) {
+ Rewrite();
+ rewritten_ = true;
+ }
+ }
+
+ protected:
+ void Rewrite();
+
+ JavascriptRewriteConfig* config_;
+ MessageHandler* handler_;
+ const std::string original_code_;
+ // Note that output_code_ points to either original_code_ or
+ // to rewritten_code_ depending upon the results of processing
+ // (ie it's an indirection to locally-owned data).
+ StringPiece output_code_;
+ bool rewritten_;
+ std::string rewritten_code_;
+
+ DISALLOW_COPY_AND_ASSIGN(JavascriptCodeBlock);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_JAVASCRIPT_CODE_BLOCK_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/javascript_filter.h b/trunk/src/net/instaweb/rewriter/public/javascript_filter.h
new file mode 100644
index 0000000..59367eb
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/javascript_filter.h
@@ -0,0 +1,121 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_JAVASCRIPT_FILTER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_JAVASCRIPT_FILTER_H_
+
+#include <vector>
+
+#include "net/instaweb/rewriter/public/rewrite_single_resource_filter.h"
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/rewriter/public/javascript_code_block.h"
+#include "net/instaweb/rewriter/public/script_tag_scanner.h"
+#include "net/instaweb/util/public/atom.h"
+#include "net/instaweb/util/public/google_url.h"
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/url_async_fetcher.h"
+
+namespace net_instaweb {
+class HtmlParse;
+class MessageHandler;
+class MetaData;
+class OutputResource;
+class Resource;
+class ResourceManager;
+class Statistics;
+class Writer;
+
+/**
+ * Find Javascript elements (either inline scripts or imported js files) and
+ * rewrite them. This can involve any combination of minifaction,
+ * concatenation, renaming, reordering, and incrementalization that accomplishes
+ * our goals.
+ *
+ * For the moment we keep it simple and just minify any scripts that we find.
+ *
+ * Challenges:
+ * * Identifying everywhere js is invoked, in particular event handlers on
+ * elements that might be found in css or in variously-randomly-named
+ * html properties.
+ * * Analysis of eval() contexts. Actually less hard than the last, assuming
+ * constant strings. Otherwise hard.
+ * * Figuring out where to re-inject code after analysis.
+ *
+ * We will probably need to do an end run around the need for js analysis by
+ * instrumenting and incrementally loading code, then probably using dynamic
+ * feedback to change the runtime instrumentation in future pages as we serve
+ * them.
+ */
+class JavascriptFilter : public RewriteSingleResourceFilter {
+ public:
+ JavascriptFilter(RewriteDriver* rewrite_driver,
+ const StringPiece& path_prefix);
+ virtual ~JavascriptFilter();
+ static void Initialize(Statistics* statistics);
+
+ virtual void StartDocumentImpl() {}
+ virtual void StartElementImpl(HtmlElement* element);
+ virtual void Characters(HtmlCharactersNode* characters);
+ virtual void EndElementImpl(HtmlElement* element);
+ virtual void Flush();
+ virtual void IEDirective(HtmlIEDirectiveNode* directive);
+
+ // Configuration settings for javascript filtering:
+ // Set whether to minify javascript code blocks encountered.
+ void set_minify(bool minify) {
+ config_.set_minify(minify);
+ }
+
+ virtual const char* Name() const { return "Javascript"; }
+
+ protected:
+ virtual bool RewriteLoadedResource(const Resource* input_resource,
+ OutputResource* output_resource);
+
+ private:
+ inline void CompleteScriptInProgress();
+ inline void RewriteInlineScript();
+ inline void RewriteExternalScript();
+ inline Resource* ScriptAtUrl(const StringPiece& script_url);
+ const StringPiece FlattenBuffer(std::string* script_buffer);
+ bool WriteExternalScriptTo(const Resource* script_resource,
+ const StringPiece& script_out,
+ OutputResource* script_dest);
+
+ std::vector<HtmlCharactersNode*> buffer_;
+ HtmlParse* html_parse_;
+ HtmlElement* script_in_progress_;
+ HtmlElement::Attribute* script_src_;
+ ResourceManager* resource_manager_;
+ // some_missing_scripts indicates that we stopped processing a script and
+ // therefore can't assume we know all of the Javascript on a page.
+ bool some_missing_scripts_;
+ JavascriptRewriteConfig config_;
+ const Atom s_script_;
+ const Atom s_src_;
+ const Atom s_type_;
+ ScriptTagScanner script_tag_scanner_;
+
+ DISALLOW_COPY_AND_ASSIGN(JavascriptFilter);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_JAVASCRIPT_FILTER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/javascript_library_identification.h b/trunk/src/net/instaweb/rewriter/public/javascript_library_identification.h
new file mode 100644
index 0000000..0e6e53c
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/javascript_library_identification.h
@@ -0,0 +1,76 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_JAVASCRIPT_LIBRARY_IDENTIFICATION_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_JAVASCRIPT_LIBRARY_IDENTIFICATION_H_
+
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+// Size of regions that is hashed to do initial detection
+const size_t kJavascriptHashIdBlockSize = 512;
+
+// Internal data used by JavascriptLibraryId.
+// We expose it here to allow the metadata to be
+// constructed sensibly by generate_javascript_metadata.
+struct LibraryInfo {
+ const char* name;
+ const char* version;
+ uint64 first_block_hash;
+ uint64 full_hash;
+ size_t full_size;
+};
+
+// A JavascriptLibraryId contains information about a single third-party
+// Javascript library. Given a block of minified Javascript with leading and
+// trailing whitespace removed, the Find(...) method returns a corresponding
+// JavascriptLibraryId. Note that JavascriptLibraryId is intended to be passed
+// by value.
+class JavascriptLibraryId {
+ public:
+ // Constructs an unrecognized library.
+ JavascriptLibraryId();
+
+ // Find the JavascriptLibraryId object associated with the given
+ // minified_code. This might be an unrecognized library.
+ static JavascriptLibraryId Find(const StringPiece& minified_code);
+
+ // Is this a recognized library? Otherwise we should ignore it.
+ bool recognized() const {
+ return info_->name != NULL;
+ }
+
+ // Canonical name of javascript library (NULL if unrecognized)
+ const char* name() const {
+ return info_->name;
+ }
+
+ // Version number of javascript library (NULL if unrecognized)
+ const char* version() const {
+ return info_->version;
+ }
+
+ private:
+ explicit JavascriptLibraryId(const LibraryInfo* info) : info_(info) { }
+
+ const LibraryInfo* info_;
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_JAVASCRIPT_LIBRARY_IDENTIFICATION_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/js_inline_filter.h b/trunk/src/net/instaweb/rewriter/public/js_inline_filter.h
new file mode 100644
index 0000000..a39c3df
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/js_inline_filter.h
@@ -0,0 +1,67 @@
+/**
+ * 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: mdsteele@google.com (Matthew D. Steele)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_JS_INLINE_FILTER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_JS_INLINE_FILTER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/rewriter/public/common_filter.h"
+#include "net/instaweb/rewriter/public/script_tag_scanner.h"
+#include "net/instaweb/util/public/atom.h"
+#include <string>
+
+namespace net_instaweb {
+
+class ResourceManager;
+class RewriteDriver;
+
+// Inline small Javascript files.
+class JsInlineFilter : public CommonFilter {
+ public:
+ explicit JsInlineFilter(RewriteDriver* driver);
+ virtual ~JsInlineFilter();
+
+ virtual void StartDocumentImpl();
+ virtual void EndDocument();
+ virtual void StartElementImpl(HtmlElement* element);
+ virtual void EndElementImpl(HtmlElement* element);
+ virtual void Characters(HtmlCharactersNode* characters);
+ virtual const char* Name() const { return "InlineJs"; }
+
+ private:
+ HtmlParse* const html_parse_;
+ const Atom script_atom_;
+ const Atom src_atom_;
+ const size_t size_threshold_bytes_;
+ ScriptTagScanner script_tag_scanner_;
+
+ std::string domain_; // The domain of the HTML file we're parsing
+
+ // This is set to true during StartElement() for a <script> tag that we
+ // should maybe inline, but may be set back to false by Characters(). If it
+ // is still true when we hit the corresponding EndElement(), then we'll
+ // inline the script (and set it back to false). It should never be true
+ // outside of <script> and </script>.
+ bool should_inline_;
+
+ DISALLOW_COPY_AND_ASSIGN(JsInlineFilter);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_JS_INLINE_FILTER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/js_outline_filter.h b/trunk/src/net/instaweb/rewriter/public/js_outline_filter.h
new file mode 100644
index 0000000..1cefc2d
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/js_outline_filter.h
@@ -0,0 +1,87 @@
+/**
+ * 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)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_JS_OUTLINE_FILTER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_JS_OUTLINE_FILTER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/empty_html_filter.h"
+#include "net/instaweb/rewriter/public/rewrite_driver.h"
+#include "net/instaweb/rewriter/public/script_tag_scanner.h"
+#include "net/instaweb/util/public/atom.h"
+#include <string>
+
+namespace net_instaweb {
+
+class MessageHandler;
+class MetaData;
+class OutputResource;
+class ResourceManager;
+
+// Filter to take explicit <style> and <script> tags and outline them to files.
+class JsOutlineFilter : public HtmlFilter {
+ public:
+ explicit JsOutlineFilter(RewriteDriver* driver);
+ static const char kFilterId[];
+
+ virtual void StartDocument();
+
+ virtual void StartElement(HtmlElement* element);
+ virtual void EndElement(HtmlElement* element);
+
+ virtual void Flush();
+
+ // HTML Events we expect to be in <script> elements.
+ virtual void Characters(HtmlCharactersNode* characters);
+
+ // HTML Events we do not expect to be in <style> and <script> elements.
+ virtual void Comment(HtmlCommentNode* comment);
+ virtual void Cdata(HtmlCdataNode* cdata);
+ virtual void IEDirective(HtmlIEDirectiveNode* directive);
+
+ // Ignored HTML Events.
+ virtual void EndDocument() {}
+ virtual void Directive(HtmlDirectiveNode* directive) {}
+
+ virtual const char* Name() const { return "OutlineJs"; }
+
+ private:
+ bool WriteResource(const std::string& content, OutputResource* resource,
+ MessageHandler* handler);
+ void OutlineScript(HtmlElement* element, const std::string& content);
+
+ // The style or script element we are in (if it hasn't been flushed).
+ // If we are not in a script or style element, inline_element_ == NULL.
+ HtmlElement* inline_element_;
+ // Temporarily buffers the content between open and close of inline_element_.
+ std::string buffer_;
+ HtmlParse* html_parse_;
+ ResourceManager* resource_manager_;
+ size_t size_threshold_bytes_;
+ // HTML strings interned into a symbol table.
+ Atom s_script_;
+ Atom s_src_;
+ Atom s_type_;
+ ScriptTagScanner script_tag_scanner_;
+
+ DISALLOW_COPY_AND_ASSIGN(JsOutlineFilter);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_JS_OUTLINE_FILTER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/output_resource.h b/trunk/src/net/instaweb/rewriter/public/output_resource.h
new file mode 100644
index 0000000..c381dee
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/output_resource.h
@@ -0,0 +1,179 @@
+/**
+ * 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)
+//
+// Output resources are created by a ResourceManager. They must be able to
+// write contents and return their url (so that it can be href'd on a page).
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_OUTPUT_RESOURCE_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_OUTPUT_RESOURCE_H_
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/util/public/file_system.h"
+#include "net/instaweb/util/public/file_writer.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/rewriter/public/resource.h"
+#include "net/instaweb/rewriter/public/resource_manager.h"
+#include "net/instaweb/rewriter/public/resource_namer.h"
+
+namespace net_instaweb {
+
+class AbstractLock;
+class MessageHandler;
+class NamedLockManager;
+
+class OutputResource : public Resource {
+ public:
+ // Construct an OutputResource. For the moment, we pass in type redundantly
+ // even though full_name embeds an extension. This reflects current code
+ // structure rather than a principled stand on anything.
+ // TODO(jmaessen): remove redundancy.
+ OutputResource(ResourceManager* manager,
+ const StringPiece& resolved_base,
+ const ResourceNamer& resource_id,
+ const ContentType* type);
+ ~OutputResource();
+
+ virtual bool Load(MessageHandler* message_handler);
+ virtual std::string url() const;
+
+ // The NameKey describes the source url and rewriter used, without hash and
+ // content type information. This is used to find previously-computed filter
+ // results whose output hash and content type is unknown. The full name of a
+ // resource is of the form
+ // path/prefix.encoded_resource_name.hash.extension
+ // we know prefix and name, but not the hash, and we don't always even have
+ // the extension, which might have changes as the result of, for example image
+ // optimization (e.g. gif->png). But We can "remember" the hash/extension for
+ // as long as the origin URL was cacheable. So we construct this as a key:
+ // path/prefix.encoded_resource_name
+ // and use that to map to the hash-code and extension. If we know the
+ // hash-code then we may also be able to look up the contents in the same
+ // cache.
+ virtual std::string name_key() const;
+ // The hash_ext describes the hash and content type of the resource;
+ // to index already-computed resources we lookup name_key() and obtain
+ // the corresponding hash_ext().
+ virtual std::string hash_ext() const;
+
+ // output-specific
+ const std::string& resolved_base() const { return resolved_base_; }
+ const ResourceNamer& full_name() const { return full_name_; }
+ StringPiece name() const { return full_name_.name(); }
+ std::string filename() const;
+ StringPiece suffix() const;
+ StringPiece filter_prefix() const { return full_name_.id(); }
+
+ // In a scalable installation where the sprites must be kept in a
+ // database, we cannot serve HTML that references new resources
+ // that have not been committed yet, and committing to a database
+ // may take too long to block on the HTML rewrite. So we will want
+ // to refactor this to check to see whether the desired resource is
+ // already known. For now we'll assume we can commit to serving the
+ // resource during the HTML rewriter.
+ bool IsWritten() const;
+
+ // Sets the suffix for an output resource. This must be called prior
+ // to Write if the content_type ctor arg was NULL. This can happen if
+ // we are managing a resource whose content-type is not known to us.
+ // CacheExtender is currently the only place where we need this.
+ void set_suffix(const StringPiece& ext);
+
+ // Sets the type of the output resource, and thus also its suffix.
+ virtual void SetType(const ContentType* type);
+
+ // Determines whether the output resource has a valid URL. If so,
+ // we don't need to actually load the output-resource content from
+ // cache during the Rewriting process -- we can immediately rewrite
+ // the href to it.
+ //
+ // Note that when serving content, we must actually load it, but
+ // when rewriting it we can, in some cases, exploit a URL swap.
+ bool HasValidUrl() const { return has_hash(); }
+
+ // Resources rewritten via a UrlPartnership will have a resolved
+ // base to use in lieu of the legacy UrlPrefix held by the resource
+ // manager.
+ void set_resolved_base(const StringPiece& base) {
+ base.CopyToString(&resolved_base_);
+ CHECK(EndsInSlash(base)) << "resolved_base must end in a slash.";
+ }
+
+ private:
+ friend class ResourceManager;
+ friend class ResourceManagerTestingPeer;
+ class OutputWriter {
+ public:
+ // file may be null if we shouldn't write to the filesystem.
+ OutputWriter(FileSystem::OutputFile* file, HTTPValue* http_value)
+ : http_value_(http_value) {
+ if (file != NULL) {
+ file_writer_.reset(new FileWriter(file));
+ } else {
+ file_writer_.reset(NULL);
+ }
+ }
+
+ // Adds the given data to our http_value, and, if
+ // non-null, our file.
+ bool Write(const StringPiece& data, MessageHandler* handler);
+ private:
+ scoped_ptr<FileWriter> file_writer_;
+ HTTPValue* http_value_;
+ };
+
+ void SetHash(const StringPiece& hash);
+ StringPiece hash() const { return full_name_.hash(); }
+ bool has_hash() const { return !hash().empty(); }
+ void set_written(bool written) { writing_complete_ = true; }
+ void set_generated(bool x) { generated_ = x; }
+ bool generated() const { return generated_; }
+ std::string TempPrefix() const;
+
+ OutputWriter* BeginWrite(MessageHandler* message_handler);
+ bool EndWrite(OutputWriter* writer, MessageHandler* message_handler);
+ // Attempt to obtain a named lock for the resource. Return true if we do so.
+ bool LockForCreation(const ResourceManager* resource_manager,
+ ResourceManager::BlockingBehavior block);
+
+ FileSystem::OutputFile* output_file_;
+ bool writing_complete_;
+
+ // Generated via ResourceManager::CreateGeneratedOutputResource,
+ // meaning that it does not have a name that is derived from an
+ // input URL. We must regenerate it every time, but the output name
+ // will be distinct because it's based on the hash of the content.
+ bool generated_;
+
+ // If this output url was created via a partnership then this field
+ // will be non-empty, and we will not need to use the resource manager's
+ // prefix.
+ std::string resolved_base_;
+ ResourceNamer full_name_;
+
+ // Lock guarding resource creation. Lazily initialized by LockForCreation,
+ // unlocked on destruction or EndWrite.
+ scoped_ptr<AbstractLock> creation_lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(OutputResource);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_OUTPUT_RESOURCE_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/remove_comments_filter.h b/trunk/src/net/instaweb/rewriter/public/remove_comments_filter.h
new file mode 100644
index 0000000..27857c2
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/remove_comments_filter.h
@@ -0,0 +1,46 @@
+/**
+ * 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: mdsteele@google.com (Matthew D. Steele)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_REMOVE_COMMENTS_FILTER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_REMOVE_COMMENTS_FILTER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/empty_html_filter.h"
+
+namespace net_instaweb {
+
+// Reduce the size of the HTML by removing all HTML comments (except those
+// which are IE directives). Note that this is a potentially dangerous
+// optimization; if a site is using comments for some squirrelly purpose, then
+// removing those comments might break something.
+class RemoveCommentsFilter : public EmptyHtmlFilter {
+ public:
+ explicit RemoveCommentsFilter(HtmlParse* html_parse);
+
+ virtual void Comment(HtmlCommentNode* comment);
+ virtual const char* Name() const { return "RemoveComments"; }
+
+ private:
+ HtmlParse* html_parse_;
+
+ DISALLOW_COPY_AND_ASSIGN(RemoveCommentsFilter);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_REMOVE_COMMENTS_FILTER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/resource.h b/trunk/src/net/instaweb/rewriter/public/resource.h
new file mode 100644
index 0000000..4a57e56
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/resource.h
@@ -0,0 +1,115 @@
+/**
+ * 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)
+// jmarantz@google.com (Joshua Marantz)
+//
+// Resources are created by a ResourceManager. Input resources are
+// read from URLs or the file system. Output resources are constructed
+// programatically, usually by transforming one or more existing
+// resources. Both input and output resources inherit from this class
+// so they can be used interchangably in successive rewrite passes.
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_RESOURCE_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_RESOURCE_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/content_type.h"
+#include "net/instaweb/util/public/http_value.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/url_async_fetcher.h"
+
+namespace net_instaweb {
+
+class ResourceManager;
+
+class Resource {
+ public:
+ static const int64 kDefaultExpireTimeMs;
+
+ Resource(ResourceManager* manager, const ContentType* type)
+ : resource_manager_(manager),
+ type_(type) {
+ }
+ virtual ~Resource();
+
+ // Common methods across all deriviations
+ ResourceManager* resource_manager() const { return resource_manager_; }
+ bool loaded() const { return meta_data_.status_code() != 0; }
+ // TODO(sligocki): Change name to HttpStatusOk?
+ bool ContentsValid() const {
+ return (meta_data_.status_code() == HttpStatus::kOK);
+ }
+ int64 CacheExpirationTimeMs() const;
+ StringPiece contents() const {
+ StringPiece val;
+ bool got_contents = value_.ExtractContents(&val);
+ CHECK(got_contents) << "Resource contents read before loading";
+ return val;
+ }
+ MetaData* metadata() { return &meta_data_; }
+ const MetaData* metadata() const { return &meta_data_; }
+ const ContentType* type() const { return type_; }
+ virtual void SetType(const ContentType* type);
+ virtual bool IsCacheable() const;
+
+ // Gets the absolute URL of the resource
+ virtual std::string url() const = 0;
+
+ virtual void DetermineContentType();
+
+ // We define a new Callback type here because we need to
+ // pass in the Resource to the Done callback so it can
+ // collect the fetched data.
+ class AsyncCallback {
+ public:
+ virtual ~AsyncCallback();
+ virtual void Done(bool success, Resource* resource) = 0;
+ };
+
+ // Links in the HTTP contents and header from a fetched value.
+ // The contents are linked by sharing. The HTTPValue also
+ // contains a serialization of the headers, and this routine
+ // parses them into meta_data_ and return whether that was
+ // successful.
+ bool Link(HTTPValue* source, MessageHandler* handler);
+
+ protected:
+ friend class ResourceManager;
+ friend class UrlReadAsyncFetchCallback;
+
+ // Load the resource asynchronously, storing MetaData and contents in cache.
+ // Returns true, if the resource is already loaded or loaded synchronously.
+ virtual bool Load(MessageHandler* message_handler) = 0;
+ // Same as Load, but calls a callback when finished.
+ virtual void LoadAndCallback(AsyncCallback* callback,
+ MessageHandler* message_handler);
+
+ ResourceManager* resource_manager_;
+
+ const ContentType* type_;
+ HTTPValue value_; // contains contents and meta-data
+ SimpleMetaData meta_data_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Resource);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_RESOURCE_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/resource_manager.h b/trunk/src/net/instaweb/rewriter/public/resource_manager.h
new file mode 100644
index 0000000..eb9432f
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/resource_manager.h
@@ -0,0 +1,287 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+// and sligocki@google.com (Shawn Ligocki)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_RESOURCE_MANAGER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_RESOURCE_MANAGER_H_
+
+#include <map>
+#include <vector>
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/util/public/http_cache.h"
+#include "net/instaweb/util/public/meta_data.h"
+#include "net/instaweb/rewriter/public/resource.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/url_async_fetcher.h"
+#include "net/instaweb/util/public/url_segment_encoder.h"
+
+class GURL;
+
+namespace net_instaweb {
+
+class ContentType;
+class DomainLawyer;
+class FileSystem;
+class FilenameEncoder;
+class HTTPCache;
+class HTTPValue;
+class Hasher;
+class MessageHandler;
+class MetaData;
+class NamedLockManager;
+class OutputResource;
+class ResourceNamer;
+class RewriteOptions;
+class Statistics;
+class UrlAsyncFetcher;
+class UrlEscaper;
+class Variable;
+class Writer;
+
+class ResourceManager {
+ public:
+ enum BlockingBehavior { kNeverBlock, kMayBlock };
+
+ static const int kNotSharded;
+
+ // This value is a shared constant so that it can also be used in
+ // the Apache-specific code that repairs our caching headers downstream
+ // of mod_headers.
+ static const char kResourceEtagValue[];
+
+ ResourceManager(const StringPiece& file_prefix,
+ FileSystem* file_system,
+ FilenameEncoder* filename_encoder,
+ UrlAsyncFetcher* url_async_fetcher,
+ Hasher* hasher,
+ HTTPCache* http_cache,
+ NamedLockManager* lock_manager);
+ ~ResourceManager();
+
+ // Initialize statistics gathering.
+ static void Initialize(Statistics* statistics);
+
+ // Created resources are managed by ResourceManager and eventually deleted by
+ // ResourceManager's destructor. Every time a Create...Resource... method is
+ // called, a fresh Resource object is generated (or the creation fails and
+ // NULL is returned). All content_type arguments can be NULL if the content
+ // type isn't known or isn't covered by the ContentType library. Where
+ // necessary, the extension is used to infer a content type if one is needed
+ // and none is provided. It is faster and more reliable to provide one
+ // explicitly when it is known.
+
+ // Constructs an output resource corresponding to the specified input resource
+ // and encoded using the provided encoder. Assumes permissions checking
+ // occurred when the input resource was constructed, and does not do it again.
+ // To avoid if-chains, tolerates a NULL input_resource (by returning NULL).
+ // TODO(jmaessen, jmarantz): Do we want to permit NULL input_resources here?
+ // jmarantz has evinced a distaste.
+ OutputResource* CreateOutputResourceFromResource(
+ const StringPiece& filter_prefix,
+ const ContentType* content_type,
+ UrlSegmentEncoder* encoder,
+ Resource* input_resource,
+ MessageHandler* handler);
+
+ // Constructs and permissions-checks an output resource for the specified url,
+ // which occurs in the context of document_gurl. Returns NULL on failure.
+ // The content_type argument cannot be NULL. The resource name will be
+ // encoded using the provided encoder.
+ OutputResource* CreateOutputResourceForRewrittenUrl(
+ const GURL& document_gurl,
+ const StringPiece& filter_prefix,
+ const StringPiece& resource_url,
+ const ContentType* content_type,
+ UrlSegmentEncoder* encoder,
+ const RewriteOptions* rewrite_options,
+ MessageHandler* handler);
+
+ // Creates an output resource where the name is provided by the rewriter.
+ // The intent is to be able to derive the content from the name, for example,
+ // by encoding URLs and metadata.
+ //
+ // This method is not dependent on shared persistent storage, and always
+ // succeeds.
+ //
+ // This name is prepended with path for writing hrefs, and the resulting url
+ // is encoded and stored at file_prefix when working with the file system. So
+ // hrefs are:
+ // $(PATH)/$(FILTER_PREFIX).$(HASH).$(NAME).$(CONTENT_TYPE_EXT)
+ //
+ // 'type' arg can be null if it's not known, or is not in our ContentType
+ // library.
+ OutputResource* CreateOutputResourceWithPath(
+ const StringPiece& path, const StringPiece& filter_prefix,
+ const StringPiece& name, const ContentType* type,
+ MessageHandler* handler);
+
+ // Creates a resource based on a URL. This is used for serving rewritten
+ // resources. No permission checks are performed on the url, though it
+ // is parsed to see if it looks like the url of a generated resource (which
+ // should mean checking the hash to ensure we generated it ourselves).
+ // TODO(jmaessen): add url hash & check thereof.
+ OutputResource* CreateOutputResourceForFetch(
+ const StringPiece& url);
+
+ // Creates an input resource with the url evaluated based on input_url
+ // which may need to be absolutified relative to base_url. Returns NULL if
+ // the input resource url isn't valid, or can't legally be rewritten in the
+ // context of this page.
+ Resource* CreateInputResource(const GURL& base_url,
+ const StringPiece& input_url,
+ const RewriteOptions* rewrite_options,
+ MessageHandler* handler);
+
+ // Create input resource from input_url, if it is legal in the context of
+ // base_gurl, and if the resource can be read from cache. If it's not in
+ // cache, initiate an asynchronous fetch so it will be on next access. This
+ // is a common case for filters.
+ Resource* CreateInputResourceAndReadIfCached(
+ const GURL& base_gurl, const StringPiece& input_url,
+ const RewriteOptions* rewrite_options, MessageHandler* handler);
+
+ // Create an input resource by decoding output_resource using the given
+ // encoder. Assures legality by checking hash signatures, rather than
+ // explicitly permission-checking the result.
+ Resource* CreateInputResourceFromOutputResource(
+ UrlSegmentEncoder* encoder,
+ OutputResource* output_resource,
+ const RewriteOptions* rewrite_options,
+ MessageHandler* handler);
+
+ // Creates an input resource from the given absolute url. Requires that the
+ // provided url has been checked, and can legally be rewritten in the current
+ // page context. If you have a GURL, prefer CreateInputResourceUnchecked,
+ // otherwise use this.
+ Resource* CreateInputResourceAbsolute(const StringPiece& absolute_url,
+ const RewriteOptions* rewrite_options,
+ MessageHandler* handler);
+
+ // Creates an input resource with the given gurl, already absolute and valid.
+ // Use only for resource fetches that lack a page context, or in places where
+ // permission checking has been done explicitly on the caller side (for
+ // example css_combine_filter, which constructs its own url_partnership).
+ Resource* CreateInputResourceUnchecked(const GURL& gurl,
+ const RewriteOptions* rewrite_options,
+ MessageHandler* handler);
+
+ // Set up a basic header for a given content_type.
+ // If content_type is null, the Content-Type is omitted.
+ // This method may only be called once on a header.
+ void SetDefaultHeaders(const ContentType* content_type,
+ MetaData* header) const;
+
+ // Changes the content type of a pre-initialized header.
+ void SetContentType(const ContentType* content_type, MetaData* header);
+
+ StringPiece filename_prefix() const { return file_prefix_; }
+ void set_filename_prefix(const StringPiece& file_prefix);
+ Statistics* statistics() const { return statistics_; }
+ void set_statistics(Statistics* s) {
+ statistics_ = s;
+ resource_url_domain_rejections_ = NULL; // Lazily initialized.
+ }
+ void set_relative_path(bool x) { relative_path_ = x; }
+ NamedLockManager* lock_manager() const { return lock_manager_; }
+
+ // Attempt to fetch extant version of an OutputResource. Returns false if the
+ // resource must be created by the caller. If true is returned, the resulting
+ // data could still be empty (eg because the resource is being rewritten in
+ // another thread, or rewriting results in errors), so
+ // output_resource->IsWritten() must be checked if this call succeeds. When
+ // blocking=kNeverBlock (the normal case for rewriting html), the call returns
+ // quickly if another thread is rewriting. When blocking=kMayBlock (the
+ // normal case for serving resources), the call blocks until the ongoing
+ // rewrite completes, or until the lock times out and can be seized by the
+ // serving thread.
+ bool FetchOutputResource(
+ OutputResource* output_resource,
+ Writer* writer, MetaData* response_headers,
+ MessageHandler* handler, BlockingBehavior blocking) const;
+
+ // Writes the specified contents into the output resource, retaining
+ // both a name->filename map and the filename->contents map.
+ //
+ // TODO(jmarantz): add last_modified arg.
+ bool Write(HttpStatus::Code status_code,
+ const StringPiece& contents, OutputResource* output,
+ int64 origin_expire_time_ms, MessageHandler* handler);
+
+ // Load the resource if it is cached (or if it can be fetched quickly).
+ // If not send off an asynchronous fetch and store the result in the cache.
+ //
+ // Returns true if the resource is loaded.
+ //
+ // The resource remains owned by the caller.
+ bool ReadIfCached(Resource* resource, MessageHandler* message_handler) const;
+
+ // Loads contents of resource asynchronously, calling callback when
+ // done. If the resource contents is cached, the callback will
+ // be called directly, rather than asynchronously. The resource
+ // will be passed to the callback, which will be responsible for
+ // ultimately freeing the resource. The resource will have its
+ // contents and headers filled in.
+ //
+ // The resource can be deleted only after the callback is called.
+ void ReadAsync(Resource* resource, Resource::AsyncCallback* callback,
+ MessageHandler* message_handler);
+
+ // TODO(jmarantz): check thread safety in Apache.
+ Hasher* hasher() const { return hasher_; }
+ // This setter should probably only be used in testing.
+ void set_hasher(Hasher* hasher) { hasher_ = hasher; }
+
+ FileSystem* file_system() { return file_system_; }
+ FilenameEncoder* filename_encoder() const { return filename_encoder_; }
+ UrlAsyncFetcher* url_async_fetcher() { return url_async_fetcher_; }
+ Timer* timer() { return http_cache_->timer(); }
+ HTTPCache* http_cache() { return http_cache_; }
+ UrlEscaper* url_escaper() { return url_escaper_.get(); }
+
+ // Whether or not resources should hit the filesystem.
+ bool store_outputs_in_file_system() { return store_outputs_in_file_system_; }
+ void set_store_outputs_in_file_system(bool store) {
+ store_outputs_in_file_system_ = store;
+ }
+
+ private:
+ inline void IncrementResourceUrlDomainRejections();
+
+ std::string file_prefix_;
+ int resource_id_; // Sequential ids for temporary Resource filenames.
+ FileSystem* file_system_;
+ FilenameEncoder* filename_encoder_;
+ UrlAsyncFetcher* url_async_fetcher_;
+ Hasher* hasher_;
+ Statistics* statistics_;
+ Variable* resource_url_domain_rejections_;
+ HTTPCache* http_cache_;
+ scoped_ptr<UrlEscaper> url_escaper_;
+ bool relative_path_;
+ bool store_outputs_in_file_system_;
+ NamedLockManager* lock_manager_;
+ std::string max_age_string_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResourceManager);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_RESOURCE_MANAGER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/resource_manager_test_base.h b/trunk/src/net/instaweb/rewriter/public/resource_manager_test_base.h
new file mode 100644
index 0000000..fab7006
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/resource_manager_test_base.h
@@ -0,0 +1,323 @@
+/**
+ * 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: abliss@google.com (Adam Bliss)
+
+// Base class for tests which want a ResourceManager.
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_RESOURCE_MANAGER_TEST_BASE_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_RESOURCE_MANAGER_TEST_BASE_H_
+
+#include "net/instaweb/htmlparse/public/html_parse_test_base.h"
+#include "net/instaweb/rewriter/public/domain_lawyer.h"
+#include "net/instaweb/rewriter/public/resource_manager.h"
+#include "net/instaweb/rewriter/public/resource_namer.h"
+#include "net/instaweb/rewriter/public/rewrite_driver.h"
+#include "net/instaweb/util/public/fake_url_async_fetcher.h"
+#include "net/instaweb/util/public/filename_encoder.h"
+#include "net/instaweb/util/public/file_system_lock_manager.h"
+#include "net/instaweb/util/public/hasher.h"
+#include "net/instaweb/util/public/http_cache.h"
+#include "net/instaweb/util/public/lru_cache.h"
+#include "net/instaweb/util/public/mem_file_system.h"
+#include "net/instaweb/util/public/md5_hasher.h"
+#include "net/instaweb/util/public/mock_hasher.h"
+#include "net/instaweb/util/public/mock_timer.h"
+#include "net/instaweb/util/public/mock_url_fetcher.h"
+#include "net/instaweb/util/public/null_writer.h"
+#include "net/instaweb/util/public/simple_stats.h"
+#include "net/instaweb/util/public/stdio_file_system.h"
+#include <string>
+#include "net/instaweb/util/public/wait_url_async_fetcher.h"
+
+#define URL_PREFIX "http://www.example.com/"
+
+namespace net_instaweb {
+
+const int kCacheSize = 100 * 1000 * 1000;
+
+class ResourceManagerTestBase : public HtmlParseTestBaseNoAlloc {
+ protected:
+ ResourceManagerTestBase()
+ : mock_url_async_fetcher_(&mock_url_fetcher_),
+ file_prefix_(StrCat(GTestTempDir(), "/")),
+ url_prefix_(URL_PREFIX),
+
+ lru_cache_(new LRUCache(kCacheSize)),
+ http_cache_(lru_cache_, file_system_.timer()),
+ // TODO(jmaessen): Pull timer out of file_system_ and make it
+ // standalone.
+ lock_manager_(&file_system_, file_system_.timer(), &message_handler_),
+ // TODO(sligocki): Why can't I init it here ...
+ // resource_manager_(new ResourceManager(
+ // file_prefix_, &file_system_,
+ // &filename_encoder_, &mock_url_async_fetcher_, &mock_hasher_,
+ // &http_cache_)),
+ rewrite_driver_(&message_handler_, &file_system_,
+ &mock_url_async_fetcher_, options_),
+
+ other_lru_cache_(new LRUCache(kCacheSize)),
+ other_http_cache_(other_lru_cache_, other_file_system_.timer()),
+ other_lock_manager_(
+ &other_file_system_, other_file_system_.timer(), &message_handler_),
+ other_resource_manager_(
+ file_prefix_, &other_file_system_,
+ &filename_encoder_, &mock_url_async_fetcher_, &mock_hasher_,
+ &other_http_cache_, &other_lock_manager_),
+ other_rewrite_driver_(&message_handler_, &other_file_system_,
+ &mock_url_async_fetcher_, other_options_) {
+ // rewrite_driver_.SetResourceManager(resource_manager_);
+ other_rewrite_driver_.SetResourceManager(&other_resource_manager_);
+ }
+
+ virtual void SetUp() {
+ HtmlParseTestBaseNoAlloc::SetUp();
+ // TODO(sligocki): Init this in constructor.
+ resource_manager_ = new ResourceManager(
+ file_prefix_, &file_system_,
+ &filename_encoder_, &mock_url_async_fetcher_, &mock_hasher_,
+ &http_cache_, &lock_manager_);
+ rewrite_driver_.SetResourceManager(resource_manager_);
+ }
+
+ virtual void TearDown() {
+ delete resource_manager_;
+ HtmlParseTestBaseNoAlloc::TearDown();
+ }
+
+ // In this set of tests, we will provide explicit body tags, so
+ // the test harness should not add them in for our convenience.
+ // It can go ahead and add the <html> and </html>, however.
+ virtual bool AddBody() const {
+ return false;
+ }
+
+ // Add a single rewrite filter to rewrite_driver_.
+ void AddFilter(RewriteOptions::Filter filter) {
+ options_.EnableFilter(filter);
+ rewrite_driver_.AddFilters();
+ }
+
+ // Add a single rewrite filter to rewrite_driver_.
+ void AddOtherFilter(RewriteOptions::Filter filter) {
+ other_options_.EnableFilter(filter);
+ other_rewrite_driver_.AddFilters();
+ }
+
+ // The async fetchers in these tests are really fake async fetchers, and
+ // will call their callbacks directly. Hence we don't really need
+ // any functionality in the async callback.
+ class DummyCallback : public UrlAsyncFetcher::Callback {
+ public:
+ explicit DummyCallback(bool expect_success)
+ : done_(false),
+ expect_success_(expect_success) {}
+ virtual ~DummyCallback() {
+ EXPECT_TRUE(done_);
+ }
+ virtual void Done(bool success) {
+ EXPECT_FALSE(done_) << "Already Done; perhaps you reused without Reset()";
+ done_ = true;
+ EXPECT_EQ(expect_success_, success);
+ }
+ void Reset() {
+ done_ = false;
+ }
+
+ bool done_;
+ bool expect_success_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DummyCallback);
+ };
+
+ MockTimer* mock_timer() { return file_system_.timer(); }
+
+ void DeleteFileIfExists(const std::string& filename) {
+ if (file_system_.Exists(filename.c_str(), &message_handler_).is_true()) {
+ ASSERT_TRUE(file_system_.RemoveFile(filename.c_str(), &message_handler_));
+ }
+ }
+
+ void AppendDefaultHeaders(const ContentType& content_type,
+ ResourceManager* resource_manager,
+ std::string* text) {
+ SimpleMetaData header;
+ int64 time = mock_timer()->NowUs();
+ // Reset mock timer so synthetic headers match original.
+ mock_timer()->set_time_us(0);
+ resource_manager->SetDefaultHeaders(&content_type, &header);
+ // Then set it back
+ mock_timer()->set_time_us(time);
+ StringWriter writer(text);
+ header.Write(&writer, &message_handler_);
+ }
+
+ void ServeResourceFromManyContexts(const std::string& resource_url,
+ RewriteOptions::Filter filter,
+ Hasher* hasher,
+ const StringPiece& expected_content) {
+ // TODO(sligocki): Serve the resource under several contexts. For example:
+ // 1) With output-resource cached,
+ // 2) With output-resource not cached, but in a file,
+ // 3) With output-resource unavailable, but input-resource cached,
+ // 4) With output-resource unavailable and input-resource not cached,
+ // but still fetchable,
+ ServeResourceFromNewContext(resource_url, filter, hasher, expected_content);
+ // 5) With nothing available (failure).
+ }
+
+ // Test that a resource can be served from an new server that has not already
+ // constructed it.
+ void ServeResourceFromNewContext(
+ const std::string& resource_url,
+ RewriteOptions::Filter filter,
+ Hasher* hasher,
+ const StringPiece& expected_content);
+
+ virtual HtmlParse* html_parse() { return rewrite_driver_.html_parse(); }
+
+ // Initializes a resource for mock fetching.
+ void InitMetaData(const StringPiece& resource_name,
+ const ContentType& content_type,
+ const StringPiece& content,
+ int64 ttl) {
+ std::string name = StrCat("http://test.com/", resource_name);
+ SimpleMetaData response_headers;
+ resource_manager_->SetDefaultHeaders(&content_type, &response_headers);
+ response_headers.RemoveAll(HttpAttributes::kCacheControl);
+ response_headers.Add(
+ HttpAttributes::kCacheControl,
+ StringPrintf("public, max-age=%ld", static_cast<long>(ttl)).c_str());
+ mock_url_fetcher_.SetResponse(name, response_headers, content);
+ }
+
+ // TODO(sligocki): Take a ttl and share code with InitMetaData.
+ void AddFileToMockFetcher(const StringPiece& url,
+ const std::string& filename,
+ const ContentType& content_type) {
+ // TODO(sligocki): There's probably a lot of wasteful copying here.
+
+ // We need to load a file from the testdata directory. Don't use this
+ // physical filesystem for anything else, use file_system_ which can be
+ // abstracted as a MemFileSystem instead.
+ std::string contents;
+ StdioFileSystem stdio_file_system;
+ ASSERT_TRUE(stdio_file_system.ReadFile(filename.c_str(), &contents,
+ &message_handler_));
+
+ // Put file into our fetcher.
+ SimpleMetaData default_header;
+ resource_manager_->SetDefaultHeaders(&content_type, &default_header);
+ mock_url_fetcher_.SetResponse(url, default_header, contents);
+ }
+
+ // Callback that can be used for testing resource fetches. As all the
+ // async fetchers in unit-tests call their callbacks immediately, it
+ // is safe to put this on the stack, rather than having it self-delete.
+ // TODO(sligocki): Do we need this and DummyCallback, could we give this
+ // a more descriptive name? MockCallback?
+ class FetchCallback : public UrlAsyncFetcher::Callback {
+ public:
+ FetchCallback() : success_(false), done_(false) {}
+ virtual void Done(bool success) {
+ success_ = success;
+ done_ = true;
+ }
+
+ bool success() const { return success_; }
+ bool done() const { return done_; }
+
+ private:
+ bool success_;
+ bool done_;
+ };
+
+ // Helper function to test resource fetching, returning true if the fetch
+ // succeeded, and modifying content. It is up to the caller to EXPECT_TRUE
+ // on the status and EXPECT_EQ on the content.
+ bool ServeResource(const StringPiece& path, const StringPiece& filter_id,
+ const StringPiece& name, const StringPiece& ext,
+ std::string* content) {
+ std::string url = Encode(path, filter_id, "0", name, ext);
+ return ServeResourceUrl(url, content);
+ }
+
+ bool ServeResourceUrl(const StringPiece& url, std::string* content) {
+ content->clear();
+ SimpleMetaData request_headers, response_headers;
+ StringWriter writer(content);
+ FetchCallback callback;
+ bool fetched = rewrite_driver_.FetchResource(
+ url, request_headers, &response_headers, &writer, &message_handler_,
+ &callback);
+ EXPECT_TRUE(callback.done());
+ return fetched && callback.success();
+ }
+
+ // Just check if we can fetch a resource successfully, ignore response.
+ bool TryFetchResource(const StringPiece& url) {
+ std::string contents;
+ return ServeResourceUrl(url, &contents);
+ }
+
+ // Helper function to encode a resource name from its pieces.
+ std::string Encode(const StringPiece& path,
+ const StringPiece& filter_id, const StringPiece& hash,
+ const StringPiece& name, const StringPiece& ext);
+
+ // Testdata directory.
+ static const char kTestData[];
+
+ MockUrlFetcher mock_url_fetcher_;
+ FakeUrlAsyncFetcher mock_url_async_fetcher_;
+ FilenameEncoder filename_encoder_;
+
+ MockHasher mock_hasher_;
+ MD5Hasher md5_hasher_;
+
+ std::string file_prefix_;
+ std::string url_prefix_;
+
+ // We have two independent RewriteDrivers representing two completely
+ // separate servers for the same domain (say behind a load-balancer).
+ //
+ // Server A runs rewrite_driver_ and will be used to rewrite pages and
+ // served the rewritten resources.
+ MemFileSystem file_system_;
+ LRUCache* lru_cache_; // Owned by http_cache_
+ HTTPCache http_cache_;
+ FileSystemLockManager lock_manager_;
+ ResourceManager* resource_manager_; // TODO(sligocki): Make not a pointer.
+ RewriteOptions options_;
+ RewriteDriver rewrite_driver_;
+
+ // Server B runs other_rewrite_driver_ and will get a request for
+ // resources that server A has rewritten, but server B has not heard
+ // of yet. Thus, server B will have to decode the instructions on how
+ // to rewrite the resource just from the request.
+ MemFileSystem other_file_system_;
+ LRUCache* other_lru_cache_; // Owned by other_http_cache_
+ HTTPCache other_http_cache_;
+ FileSystemLockManager other_lock_manager_;
+ ResourceManager other_resource_manager_;
+ RewriteOptions other_options_;
+ RewriteDriver other_rewrite_driver_;
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_RESOURCE_MANAGER_TEST_BASE_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/resource_namer.h b/trunk/src/net/instaweb/rewriter/public/resource_namer.h
new file mode 100644
index 0000000..1c3d2b7
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/resource_namer.h
@@ -0,0 +1,111 @@
+// 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_RESOURCE_NAMER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_RESOURCE_NAMER_H_
+
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+class ContentType;
+class ResourceManager;
+
+// Encapsulates the naming of resource URL leafs. The class holds the context
+// of a single resource, and is not intended for re-use. We could, of course,
+// add a Clear(), but it is a stateful class.
+class ResourceNamer {
+ public:
+ // This determines the overhead imposed on each URL by the ResourceNamer
+ // syntax, such as separators.
+ static const int kOverhead;
+
+ ResourceNamer() {}
+ ~ResourceNamer() {}
+
+ // Encoding and decoding in various formats.
+
+ // Decodes an entire resource name (ID.HASH.NAME.EXT), placing
+ // the result in the fields in this encoder.
+ bool Decode(const StringPiece& encoded_string);
+
+ // Encodes the fields in this encoder into an absolute url, with the
+ // trailing portion "ID.HASH.NAME.EXT".
+ std::string Encode() const;
+
+ // Encode a key that can used to do a lookup based on an id
+ // and the name. This key can be used to find the hash-code for a
+ // resource within the origin TTL.
+ //
+ // The 'id' is a short code indicating which Instaweb rewriter was
+ // used to generate the resource.
+ std::string EncodeIdName() const;
+
+ // Note: there is no need at this time to decode the name key.
+
+ // Encode/decode the hash and extension, which is used as the value
+ // in the origin-TTL-bounded cache.
+ std::string EncodeHashExt() const;
+ bool DecodeHashExt(const StringPiece& encoded_hash_ext);
+
+ // Simple getters
+ StringPiece id() const { return id_; }
+ StringPiece name() const { return name_; }
+ StringPiece hash() const { return hash_; }
+ StringPiece ext() const { return ext_; }
+
+ // Simple setters
+ void set_id(const StringPiece& p) { p.CopyToString(&id_); }
+ void set_name(const StringPiece& n) { n.CopyToString(&name_); }
+ void set_hash(const StringPiece& h) { h.CopyToString(&hash_); }
+ void set_ext(const StringPiece& e) {
+ // TODO(jmaessen): Remove check after transitioning to undotted extensions
+ // everywhere.
+ CHECK(e.empty() || e[0] != '.');
+ e.CopyToString(&ext_);
+ }
+
+ // Other setter-like operations
+ void ClearHash() { hash_.clear(); }
+ void CopyFrom(const ResourceNamer& other);
+
+ // Utility functions
+
+ // Name suitable for debugging and logging
+ std::string PrettyName() const {return InternalEncode(); }
+
+ // Compute a hash of non-path portions of the represented url.
+ size_t Hash() const;
+
+ // Compute a content-type based on ext(). NULL if unrecognized.
+ const ContentType* ContentTypeFromExt() const;
+
+ private:
+ std::string InternalEncode() const;
+ bool LegacyDecode(const StringPiece& encoded_string);
+
+ std::string id_;
+ std::string name_;
+ std::string hash_;
+ std::string ext_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResourceNamer);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_RESOURCE_NAMER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/resource_tag_scanner.h b/trunk/src/net/instaweb/rewriter/public/resource_tag_scanner.h
new file mode 100644
index 0000000..3355880
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/resource_tag_scanner.h
@@ -0,0 +1,47 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_RESOURCE_TAG_SCANNER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_RESOURCE_TAG_SCANNER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/rewriter/public/css_tag_scanner.h"
+#include "net/instaweb/rewriter/public/img_tag_scanner.h"
+#include "net/instaweb/rewriter/public/script_tag_scanner.h"
+
+namespace net_instaweb {
+
+class ResourceTagScanner {
+ public:
+ explicit ResourceTagScanner(HtmlParse* html_parse);
+
+ // Examines an HTML element to determine if it's a link to any sort
+ // of resource, extracting out the HREF or SRC.
+ HtmlElement::Attribute* ScanElement(HtmlElement* element);
+
+ private:
+ CssTagScanner css_tag_scanner_;
+ ImgTagScanner img_tag_scanner_;
+ ScriptTagScanner script_tag_scanner_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResourceTagScanner);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_RESOURCE_TAG_SCANNER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/rewrite_driver.h b/trunk/src/net/instaweb/rewriter/public/rewrite_driver.h
new file mode 100644
index 0000000..4b6f89e
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/rewrite_driver.h
@@ -0,0 +1,217 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_REWRITE_DRIVER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_REWRITE_DRIVER_H_
+
+#include <map>
+#include <vector>
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/rewriter/public/resource_manager.h"
+#include "net/instaweb/rewriter/public/rewrite_options.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/url_async_fetcher.h"
+#include "net/instaweb/util/public/user_agent.h"
+
+namespace net_instaweb {
+
+class AddInstrumentationFilter;
+class BaseTagFilter;
+class FileSystem;
+class Hasher;
+class HtmlFilter;
+class HtmlParse;
+class HtmlWriterFilter;
+class ResourceNamer;
+class RewriteFilter;
+class Statistics;
+class Timer;
+class UrlAsyncFetcher;
+class UrlFetcher;
+class UrlLeftTrimFilter;
+class Variable;
+class Writer;
+
+class RewriteDriver {
+ public:
+ static const char kCssCombinerId[];
+ static const char kCssFilterId[];
+ static const char kCacheExtenderId[];
+ static const char kImageCompressionId[];
+ static const char kJavascriptMinId[];
+
+ // A list of HTTP request headers. These are the headers which should be
+ // passed through from the client request into the MetaData request_headers
+ // sent to the rewrite driver. Headers not in this list will be ignored so
+ // there is no need to copy them over.
+ static const char* kPassThroughRequestAttributes[3];
+
+ RewriteDriver(MessageHandler* message_handler,
+ FileSystem* file_system,
+ UrlAsyncFetcher* url_async_fetcher,
+ const RewriteOptions& options);
+
+ // Need explicit destructors to allow destruction of scoped_ptr-controlled
+ // instances without propagating the include files.
+ ~RewriteDriver();
+
+ // Calls Initialize on all known rewrite_drivers.
+ static void Initialize(Statistics* statistics);
+
+ // Adds a resource manager and/or resource_server, enabling the rewriting of
+ // resources. This will replace any previous resource managers.
+ void SetResourceManager(ResourceManager* resource_manager);
+
+
+ void SetUserAgent(const char* user_agent_string) {
+ user_agent_.set_user_agent(user_agent_string);
+ }
+
+ const UserAgent& user_agent() const {
+ return user_agent_;
+ }
+
+ // Adds the filters from the options, specified by name in enabled_filters.
+ // This must be called explicitly after object construction to provide an
+ // opportunity to programatically add custom filters beyond those defined
+ // in RewriteOptions, via AddFilter(HtmlFilter* filter) (below).
+ void AddFilters();
+
+ // Add any HtmlFilter to the HtmlParse chain and take ownership of the filter.
+ void AddFilter(HtmlFilter* filter);
+
+ // Controls how HTML output is written. Be sure to call this last, after
+ // all other filters have been established.
+ //
+ // TODO(jmarantz): fix this in the implementation so that the caller can
+ // install filters in any order and the writer will always be last.
+ void SetWriter(Writer* writer);
+
+ // Sets the url that BaseTagFilter will set as the base for the document.
+ // This is an no-op if you haven't added BaseTagFilter.
+ // Call this for each new document you are processing or the old values will
+ // leak through.
+ // Note: Use this only to *change* the base URL, not to note the original
+ // base URL.
+ // TODO(sligocki): Do we need this? We should have some way of noting the
+ // base URL of a site if it is explicitly set.
+ void SetBaseUrl(const StringPiece& base);
+
+ // Initiates an async fetch for a rewritten resource with the specified name.
+ // If resource matches the pattern of what the driver is authorized to serve,
+ // then true is returned and the caller must listen on the callback for the
+ // completion of the request.
+ //
+ // If the pattern does not match, then false is returned, and the request
+ // should be passed to another handler, and the callback will *not* be
+ // called. In other words there are four outcomes for this routine:
+ //
+ // 1. the request was handled immediately and the callback called
+ // before the method returns. true is returned.
+ // 2. the request looks good but was queued because some other resource
+ // fetch is needed to satisfy it. true is returned.
+ // 3. the request looks like one it belongs to Instaweb, but the resource
+ // could not be decoded. The callback is called immediately with
+ // 'false', but true is returned.
+ // 4. the request does not look like it belongs to Instaweb. The callback
+ // will not be called, and false will be returned.
+ //
+ // In other words, if this routine returns 'false' then the callback
+ // will not be called. If the callback is called, then this should be the
+ // 'final word' on this request, whether it was called with success=true or
+ // success=false.
+ bool FetchResource(const StringPiece& resource,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* writer,
+ MessageHandler* message_handler,
+ UrlAsyncFetcher::Callback* callback);
+
+ // Attempts to decodes an output resource based on the URL pattern
+ // without actually rewriting it.
+ OutputResource* DecodeOutputResource(const StringPiece& url,
+ RewriteFilter** filter);
+
+ HtmlParse* html_parse() { return &html_parse_; }
+ FileSystem* file_system() { return file_system_; }
+ void set_async_fetcher(UrlAsyncFetcher* f) { url_async_fetcher_ = f; }
+
+ ResourceManager* resource_manager() const { return resource_manager_; }
+ Statistics* statistics() const;
+
+ AddInstrumentationFilter* add_instrumentation_filter() {
+ return add_instrumentation_filter_;
+ }
+
+ const RewriteOptions* options() { return &options_; }
+
+ private:
+ friend class ResourceManagerTestBase;
+ typedef std::map<std::string, RewriteFilter*> StringFilterMap;
+ typedef void (RewriteDriver::*SetStringMethod)(const StringPiece& value);
+ typedef void (RewriteDriver::*SetInt64Method)(int64 value);
+
+ bool ParseKeyString(const StringPiece& key, SetStringMethod m,
+ const std::string& flag);
+ bool ParseKeyInt64(const StringPiece& key, SetInt64Method m,
+ const std::string& flag);
+
+ // Registers RewriteFilter in the map, but does not put it in the
+ // html parse filter filter chain. This allows it to serve resource
+ // requests.
+ void RegisterRewriteFilter(RewriteFilter* filter);
+
+ // Adds a pre-added rewrite filter to the html parse chain.
+ void EnableRewriteFilter(const char* id);
+
+ StringFilterMap resource_filter_map_;
+
+ // These objects are provided on construction or later, and are
+ // owned by the caller.
+ HtmlParse html_parse_;
+ FileSystem* file_system_;
+ UrlAsyncFetcher* url_async_fetcher_;
+ ResourceManager* resource_manager_;
+
+ AddInstrumentationFilter* add_instrumentation_filter_;
+ scoped_ptr<HtmlWriterFilter> html_writer_filter_;
+ scoped_ptr<BaseTagFilter> base_tag_filter_;
+ scoped_ptr<UrlLeftTrimFilter> left_trim_filter_;
+ UserAgent user_agent_;
+ std::vector<HtmlFilter*> filters_;
+
+ // Statistics
+ static const char kResourceFetchesCached[];
+ static const char kResourceFetchConstructSuccesses[];
+ static const char kResourceFetchConstructFailures[];
+
+ Variable* cached_resource_fetches_;
+ Variable* succeeded_filter_resource_fetches_;
+ Variable* failed_filter_resource_fetches_;
+
+ const RewriteOptions& options_;
+
+ DISALLOW_COPY_AND_ASSIGN(RewriteDriver);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_REWRITE_DRIVER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/rewrite_driver_factory.h b/trunk/src/net/instaweb/rewriter/public/rewrite_driver_factory.h
new file mode 100644
index 0000000..c1f1a38
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/rewrite_driver_factory.h
@@ -0,0 +1,244 @@
+/**
+ * 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)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_REWRITE_DRIVER_FACTORY_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_REWRITE_DRIVER_FACTORY_H_
+
+#include <set>
+#include <vector>
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/rewriter/public/domain_lawyer.h"
+#include "net/instaweb/rewriter/public/rewrite_options.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+class AbstractMutex;
+class CacheInterface;
+class CacheUrlAsyncFetcher;
+class CacheUrlFetcher;
+class DelayController;
+class FileDriver;
+class FileSystem;
+class FilenameEncoder;
+class Hasher;
+class HtmlParse;
+class HTTPCache;
+class LRUCache;
+class MessageHandler;
+class NamedLockManager;
+class ResourceManager;
+class RewriteDriver;
+class Statistics;
+class Timer;
+class UrlAsyncFetcher;
+class UrlFetcher;
+class Variable;
+
+// A base RewriteDriverFactory.
+class RewriteDriverFactory {
+ public:
+ RewriteDriverFactory();
+ virtual ~RewriteDriverFactory();
+
+ // The RewriteDriveFactory will create objects of default type through the
+ // New* method from drived classs. Here are the objects that can be
+ // replaced before creating the RewriteDriver.
+ // Note: RewriteDriver takes ownership of these.
+ void set_html_parse_message_handler(MessageHandler* message_handler);
+ void set_message_handler(MessageHandler* message_handler);
+ void set_file_system(FileSystem* file_system);
+ void set_hasher(Hasher* hasher);
+ void set_filename_encoder(FilenameEncoder* filename_encoder);
+ void set_timer(Timer* timer);
+
+ // Set up a directory for slurped files for HTML and resources. If
+ // read_only is true, then it will only read from these files, and
+ // this will eliminate the usage of any other url_fetcher. If
+ // read_only is false, then the existing url fetcher will be used as
+ // a fallback if the slurped file is not found, and slurped files will
+ // be subsequently written so they don't have to be fetched from
+ // the Internet again.
+ //
+ // You must set the slurp directory prior to calling ComputeUrlFetcher
+ // or ComputeUrlAsyncFetcher.
+ void set_slurp_directory(const StringPiece& directory);
+ void set_slurp_read_only(bool read_only);
+ void set_slurp_print_urls(bool read_only);
+
+ // Determines whether Slurping is enabled.
+ bool slurping_enabled() const { return !slurp_directory_.empty(); }
+
+ // Setting HTTP caching on causes both the fetcher and the async
+ // fecher to return cached versions.
+ void set_force_caching(bool u) { force_caching_ = u; }
+
+ // You should either call set_base_url_fetcher,
+ // set_base_url_async_fetcher, or neither. Do not set both. If you
+ // want to enable real async fetching, because you are serving or
+ // want to model live traffic, then call set_base_url_async_fetcher
+ // before calling url_fetcher.
+ //
+ // These fetchers may be used directly when serving traffic, or they
+ // may be aggregated with other fetchers (e.g. for slurping).
+ //
+ // You cannot set either base URL fetcher once ComputeUrlFetcher has
+ // been called.
+ void set_base_url_fetcher(UrlFetcher* url_fetcher);
+ void set_base_url_async_fetcher(UrlAsyncFetcher* url_fetcher);
+
+ bool set_filename_prefix(StringPiece p);
+
+ RewriteOptions* options() { return &options_; }
+ MessageHandler* html_parse_message_handler();
+ MessageHandler* message_handler();
+ FileSystem* file_system();
+ // TODO(sligocki): Remove hasher() and force people to make a NewHasher when
+ // they need one.
+ Hasher* hasher();
+ FilenameEncoder* filename_encoder();
+ Timer* timer();
+ HTTPCache* http_cache();
+ NamedLockManager* lock_manager();
+
+ StringPiece filename_prefix();
+
+ // Computes URL fetchers using the based fetcher, and optionally,
+ // slurp_directory and slurp_read_only.
+ virtual UrlFetcher* ComputeUrlFetcher();
+ virtual UrlAsyncFetcher* ComputeUrlAsyncFetcher();
+ virtual ResourceManager* ComputeResourceManager();
+
+ // Generates a new mutex, hasher.
+ virtual AbstractMutex* NewMutex() = 0;
+ virtual Hasher* NewHasher() = 0;
+
+ // Generates a new managed RewriteDriver using the RewriteOptions
+ // managed by this class. Each RewriteDriver is not thread-safe,
+ // but you can generate a RewriteDriver* for each thread. The
+ // returned drivers are deleted by the factory; they do not need to
+ // be deleted by the allocator.
+ RewriteDriver* NewRewriteDriver();
+
+ // Releases a rewrite driver back into the pool. These are free-listed
+ // because they are not cheap to construct.
+ void ReleaseRewriteDriver(RewriteDriver* rewrite_driver);
+
+ // Generates a custom RewriteDriver using the passed-in options. This
+ // driver is *not* managed by the factory: you must delete it after
+ // you are done with it.
+ RewriteDriver* NewCustomRewriteDriver(const RewriteOptions& options);
+
+ // Initialize statistics variables for 404 responses.
+ static void Initialize(Statistics* statistics);
+ // Increment the count of resource returning 404.
+ void Increment404Count();
+ // Increment the cournt of slurp returning 404.
+ void IncrementSlurpCount();
+
+ protected:
+ virtual void AddPlatformSpecificRewritePasses(RewriteDriver* driver);
+ bool FetchersComputed() const;
+
+ // Implementors of RewriteDriverFactory must supply default definitions
+ // for each of these methods, although they may be overridden via set_
+ // methods above
+ virtual UrlFetcher* DefaultUrlFetcher() = 0;
+ virtual UrlAsyncFetcher* DefaultAsyncUrlFetcher() = 0;
+ virtual MessageHandler* DefaultHtmlParseMessageHandler() = 0;
+ virtual MessageHandler* DefaultMessageHandler() = 0;
+ virtual FileSystem* DefaultFileSystem() = 0;
+ virtual Timer* DefaultTimer() = 0;
+ virtual CacheInterface* DefaultCacheInterface() = 0;
+
+ // Implementors of RewriteDriverFactory must supply two mutexes.
+ virtual AbstractMutex* cache_mutex() = 0;
+ virtual AbstractMutex* rewrite_drivers_mutex() = 0;
+
+ // Clean up all the resources. When shutdown Apache, and destroy the process
+ // sub-pool. The RewriteDriverFactory owns some elements that were created
+ // from that sub-pool. The sub-pool is destroyed in ApacheRewriteFactory,
+ // which happens before the destruction of the base class. When the base class
+ // destroys, the sub-pool has been destroyed, but the elements in base class
+ // are still trying to destroy the sub-pool of the sub-pool. Call this
+ // function before destroying the process sub-pool.
+ void ShutDown();
+
+ // Called before creating the url fetchers.
+ virtual void FetcherSetupHooks();
+
+ // Override this to return false if you don't want the resource
+ // manager to write resources to the filesystem.
+ virtual bool ShouldWriteResourcesToFileSystem() { return true; }
+
+ private:
+ void SetupSlurpDirectories();
+
+ scoped_ptr<MessageHandler> html_parse_message_handler_;
+ scoped_ptr<MessageHandler> message_handler_;
+ scoped_ptr<FileSystem> file_system_;
+ UrlFetcher* url_fetcher_;
+ UrlAsyncFetcher* url_async_fetcher_;
+ scoped_ptr<UrlFetcher> base_url_fetcher_;
+ scoped_ptr<UrlAsyncFetcher> base_url_async_fetcher_;
+ scoped_ptr<Hasher> hasher_;
+ scoped_ptr<FilenameEncoder> filename_encoder_;
+ scoped_ptr<Timer> timer_;
+ HtmlParse* html_parse_;
+
+ std::string filename_prefix_;
+ std::string slurp_directory_;
+ RewriteOptions options_;
+ bool force_caching_;
+ bool slurp_read_only_;
+ bool slurp_print_urls_;
+
+ scoped_ptr<ResourceManager> resource_manager_;
+
+ // RewriteDrivers that were previously allocated, but have
+ // been released with ReleaseRewriteDriver, and are ready
+ // for re-use with NewRewriteDriver.
+ std::vector<RewriteDriver*> available_rewrite_drivers_;
+
+ // RewriteDrivers that are currently in use. This is retained
+ // as a sanity check to make sure our system is coherent,
+ // and to facilitate complete cleanup if a Shutdown occurs
+ // while a request is in flight.
+ std::set<RewriteDriver*> active_rewrite_drivers_;
+
+ // Caching support
+ scoped_ptr<HTTPCache> http_cache_;
+ scoped_ptr<CacheUrlFetcher> cache_fetcher_;
+ scoped_ptr<CacheUrlAsyncFetcher> cache_async_fetcher_;
+ Variable* resource_404_count_;
+ Variable* slurp_404_count_;
+
+ // Keep track of authorized domains, sharding, and mappings.
+ DomainLawyer domain_lawyer_;
+
+ // Manage locks for output resources.
+ scoped_ptr<NamedLockManager> lock_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(RewriteDriverFactory);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_REWRITE_DRIVER_FACTORY_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/rewrite_filter.h b/trunk/src/net/instaweb/rewriter/public/rewrite_filter.h
new file mode 100644
index 0000000..9a52f30
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/rewrite_filter.h
@@ -0,0 +1,83 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_REWRITE_FILTER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_REWRITE_FILTER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/rewriter/public/common_filter.h"
+#include "net/instaweb/rewriter/public/rewrite_driver.h"
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/url_async_fetcher.h"
+
+namespace net_instaweb {
+
+class HtmlParse;
+class OutputResource;
+class ResourceManager;
+class RewriteDriver;
+class UrlAsyncFetcher;
+class Writer;
+
+class RewriteFilter : public CommonFilter {
+ public:
+ explicit RewriteFilter(RewriteDriver* driver, StringPiece filter_prefix)
+ : CommonFilter(driver),
+ filter_prefix_(filter_prefix.data(), filter_prefix.size()),
+ driver_(driver) {
+ }
+ virtual ~RewriteFilter();
+
+ // Fetches a resource written using the filter. Filters that
+ // encode all the data (URLs, meta-data) needed to reconstruct
+ // a rewritten resource in a URL component, this method is the
+ // mechanism for the filter to serve the rewritten resource.
+ //
+ // The flow is that a RewriteFilter is instantiated with
+ // a path prefix, e.g. a two letter abbreviation of the
+ // filter, like "ce" for CacheExtender. When it rewrites a
+ // resource, it replaces the href with a url constructed as
+ // HOST://PATH/ENCODED_NAME.pagespeed.FILTER_ID.HASH.EXT
+ // Most ENCODED_NAMEs are just the original URL with a few
+ // characters, notably '?' and '&' esacped. For "ic" (ImgRewriterFilter)
+ // the encoding includes the original image URL, plus the pixel-dimensions
+ // to which the image was resized. For combine_css it includes
+ // all the original URLs separated by '+'.
+ virtual bool Fetch(OutputResource* output_resource,
+ Writer* response_writer,
+ const MetaData& request_header,
+ MetaData* response_headers,
+ MessageHandler* message_handler,
+ UrlAsyncFetcher::Callback* callback) = 0;
+
+ const std::string& id() const { return filter_prefix_; }
+ HtmlParse* html_parse() { return driver_->html_parse(); }
+ ResourceManager* resource_manager() { return driver_->resource_manager(); }
+
+ protected:
+ std::string filter_prefix_; // Prefix that should be used in front of all
+ // rewritten URLs
+ RewriteDriver* driver_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RewriteFilter);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_REWRITE_FILTER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/rewrite_options.h b/trunk/src/net/instaweb/rewriter/public/rewrite_options.h
new file mode 100644
index 0000000..4db0b72
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/rewrite_options.h
@@ -0,0 +1,337 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_REWRITE_OPTIONS_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_REWRITE_OPTIONS_H_
+
+#include <map>
+#include <set>
+#include "base/basictypes.h"
+#include "net/instaweb/rewriter/public/domain_lawyer.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/wildcard_group.h"
+
+namespace net_instaweb {
+
+class MessageHandler;
+
+class RewriteOptions {
+ public:
+ enum Filter {
+ kAddBaseTag, // Update kFirstEnumFilter if you add something before this.
+ kAddHead,
+ kAddInstrumentation,
+ kCollapseWhitespace,
+ kCombineCss,
+ kCombineHeads,
+ kDebugLogImgTags,
+ kElideAttributes,
+ kExtendCache,
+ kInlineCss,
+ kInlineJavascript,
+ kInsertImgDimensions,
+ kLeftTrimUrls,
+ kMoveCssToHead,
+ kOutlineCss,
+ kOutlineJavascript,
+ kRemoveComments,
+ kRemoveQuotes,
+ kRewriteCss,
+ kRewriteImages,
+ kRewriteJavascript,
+ kStripScripts, // Update kLastEnumFilter if you add something after this.
+ };
+
+ private:
+ // Needed by kAllFilters.
+ static const Filter kFirstEnumFilter = kAddBaseTag;
+ static const Filter kLastEnumFilter = kStripScripts;
+
+ public:
+ enum RewriteLevel {
+ // Enable no filters. Parse HTML but do not perform any
+ // transformations. This is the default value. Most users should
+ // explcitly enable the kCoreFilters level by calling
+ // SetRewriteLevel(kCoreFilters).
+ kPassThrough,
+
+ // Enable the core set of filters. These filters are considered
+ // generally safe for most sites, though even safe filters can
+ // break some sites. Most users should specify this option, and
+ // then optionally add or remove specific filters based on
+ // specific needs.
+ kCoreFilters,
+
+ // Enable all filters intended for core, but some of which might
+ // need more testing. Good for if users are willing to test out
+ // the results of the rewrite more closely.
+ kTestingCoreFilters,
+
+ // Enable all filters.
+ kAllFilters,
+ };
+
+ // Used for enumerating over all entries in the Filter enum.
+ static const Filter kFirstFilter = kAddBaseTag;
+ static const Filter kLastFilter = kStripScripts;
+
+ static const int64 kDefaultCssInlineMaxBytes;
+ static const int64 kDefaultImgInlineMaxBytes;
+ static const int64 kDefaultJsInlineMaxBytes;
+ static const int64 kDefaultCssOutlineMinBytes;
+ static const int64 kDefaultJsOutlineMinBytes;
+ static const std::string kDefaultBeaconUrl;
+
+ // IE limits URL size overall to about 2k characters. See
+ // http://support.microsoft.com/kb/208427/EN-US
+ static const int kMaxUrlSize;
+
+ static const int kDefaultImgMaxRewritesAtOnce;
+
+ // See http://code.google.com/p/modpagespeed/issues/detail?id=9
+ // Apache evidently limits each URL path segment (between /) to
+ // about 256 characters. This is not fundamental URL limitation
+ // but is Apache specific.
+ static const int64 kMaxUrlSegmentSize;
+
+ static bool ParseRewriteLevel(const StringPiece& in, RewriteLevel* out);
+
+ RewriteOptions();
+ ~RewriteOptions();
+
+ bool modified() const { return modified_; }
+
+ void SetDefaultRewriteLevel(RewriteLevel level) {
+ // Do not set the modified bit -- we are only changing the default.
+ level_.set_default(level);
+ }
+ void SetRewriteLevel(RewriteLevel level) {
+ modified_ = true;
+ level_.set(level);
+ }
+ RewriteLevel level() const { return level_.value();}
+
+ // Adds a set of filters to the enabled set. Returns false if any
+ // of the filter names are invalid, but all the valid ones will be
+ // added anyway.
+ bool EnableFiltersByCommaSeparatedList(const StringPiece& filters,
+ MessageHandler* handler);
+
+ // Adds a set of filters to the disabled set. Returns false if any
+ // of the filter names are invalid, but all the valid ones will be
+ // added anyway.
+ bool DisableFiltersByCommaSeparatedList(const StringPiece& filters,
+ MessageHandler* handler);
+ void EnableFilter(Filter filter);
+ void DisableFilter(Filter filter);
+
+ bool Enabled(Filter filter) const;
+
+ // TODO(jmarantz): consider setting flags in the set_ methods so that
+ // first's explicit settings can override default values from second.
+
+ int64 css_outline_min_bytes() const { return css_outline_min_bytes_.value(); }
+ void set_css_outline_min_bytes(int64 x) {
+ modified_ = true;
+ css_outline_min_bytes_.set(x);
+ }
+ int64 js_outline_min_bytes() const { return js_outline_min_bytes_.value(); }
+ void set_js_outline_min_bytes(int64 x) {
+ modified_ = true;
+ js_outline_min_bytes_.set(x);
+ }
+ int64 img_inline_max_bytes() const { return img_inline_max_bytes_.value(); }
+ void set_img_inline_max_bytes(int64 x) {
+ modified_ = true;
+ img_inline_max_bytes_.set(x);
+ }
+ int64 css_inline_max_bytes() const { return css_inline_max_bytes_.value(); }
+ void set_css_inline_max_bytes(int64 x) {
+ modified_ = true;
+ css_inline_max_bytes_.set(x);
+ }
+ int64 js_inline_max_bytes() const { return js_inline_max_bytes_.value(); }
+ void set_js_inline_max_bytes(int64 x) {
+ modified_ = true;
+ js_inline_max_bytes_.set(x);
+ }
+ int num_shards() const { return num_shards_.value(); }
+ void set_num_shards(int x) {
+ modified_ = true;
+ num_shards_.set(x);
+ }
+ const std::string& beacon_url() const { return beacon_url_.value(); }
+ void set_beacon_url(const StringPiece& p) {
+ modified_ = true;
+ beacon_url_.set(std::string(p.data(), p.size()));
+
+ }
+ // The maximum length of a URL segment.
+ // for http://a/b/c.d, this is == strlen("c.d")
+ int max_url_segment_size() const { return max_url_segment_size_.value(); }
+ void set_max_url_segment_size(int x) {
+ modified_ = true;
+ max_url_segment_size_.set(x);
+ }
+
+ int img_max_rewrites_at_once() const {
+ return img_max_rewrites_at_once_.value();
+ }
+ void set_img_max_rewrites_at_once(int x) {
+ modified_ = true;
+ img_max_rewrites_at_once_.set(x);
+ }
+
+ // The maximum size of the entire URL. If '0', this is left unlimited.
+ int max_url_size() const { return max_url_size_.value(); }
+ void set_max_url_size(int x) {
+ modified_ = true;
+ max_url_size_.set(x);
+ }
+
+ void set_enabled(bool x) {
+ modified_ = true;
+ enabled_.set(x);
+ }
+ bool enabled() const { return enabled_.value(); }
+
+ // Merge together two source RewriteOptions to populate this. The order
+ // is significant: the second will override the first. One semantic
+ // subject to interpretation is when a core-filter is disabled in the
+ // first set and not in the second. In this case, my judgement is that
+ // the 'disable' from the first should override the core-set membership
+ // in the second, but not an 'enable' in the second.
+ void Merge(const RewriteOptions& first, const RewriteOptions& second);
+
+ // Registers a wildcard pattern for to be allowed, potentially overriding
+ // previous Disallow wildcards.
+ void Allow(const StringPiece& wildcard_pattern) {
+ modified_ = true;
+ allow_resources_.Allow(wildcard_pattern);
+ }
+
+ // Registers a wildcard pattern for to be disallowed, potentially overriding
+ // previous Allow wildcards.
+ void Disallow(const StringPiece& wildcard_pattern) {
+ modified_ = true;
+ allow_resources_.Disallow(wildcard_pattern);
+ }
+
+ DomainLawyer* domain_lawyer() { return &domain_lawyer_; }
+ const DomainLawyer* domain_lawyer() const { return &domain_lawyer_; }
+
+ // Determines, based on the sequence of Allow/Disallow calls above, whether
+ // a url is allowed.
+ bool IsAllowed(const StringPiece& url) const {
+ return allow_resources_.Match(url);
+ }
+
+ void CopyFrom(const RewriteOptions& src) {
+ Merge(src, src); // We lack a better implementation of Copy.
+ }
+
+ private:
+ // Helper class to represent an Option, whose value is held in some class T.
+ // An option is explicitly initialized with its default value, although the
+ // default value can be altered later. It keeps track of whether a
+ // value has been explicitly set (independent of whether that happens to
+ // coincide with the default value).
+ //
+ // It can use this knowledge to intelligently merge a 'base' option value
+ // into a 'new' option value, allowing explicitly set values from 'base'
+ // to override default values from 'new'.
+ template<class T> class Option {
+ public:
+ explicit Option(const T& default_value)
+ : value_(default_value),
+ was_set_(false) {
+ }
+
+ void set(const T& val) {
+ was_set_ = true;
+ value_ = val;
+ }
+
+ void set_default(const T& val) {
+ if (!was_set_) {
+ value_ = val;
+ }
+ }
+
+ const T& value() const { return value_; }
+
+ void Merge(const Option& one, const Option& two) {
+ if (two.was_set_ || !one.was_set_) {
+ value_ = two.value_;
+ was_set_ = two.was_set_;
+ } else {
+ value_ = one.value_;
+ was_set_ = true; // this stmt is reached only if one.was_set_==true
+ }
+ }
+
+ private:
+ T value_;
+ bool was_set_;
+
+ DISALLOW_COPY_AND_ASSIGN(Option);
+ };
+
+ typedef std::set<Filter> FilterSet;
+ typedef std::map<std::string, Filter> NameToFilterMap;
+ typedef std::map<RewriteLevel, FilterSet> RewriteLevelToFilterSetMap;
+
+ void SetUp();
+ bool AddCommaSeparatedListToFilterSet(
+ const StringPiece& filters, MessageHandler* handler, FilterSet* set);
+
+ bool modified_;
+ NameToFilterMap name_filter_map_;
+ RewriteLevelToFilterSetMap level_filter_set_map_;
+ FilterSet enabled_filters_;
+ FilterSet disabled_filters_;
+
+ // Note: using the template class Option here saves a lot of repeated
+ // and error-prone merging code. However, it is not space efficient as
+ // we are alternating int64s and bools in the structure. If we cared
+ // about that, then we would keep the bools in a bitmask. But since
+ // we don't really care we'll try to keep the code structured better.
+ Option<RewriteLevel> level_;
+ Option<int64> css_inline_max_bytes_;
+ Option<int64> img_inline_max_bytes_;
+ Option<int64> img_max_rewrites_at_once_;
+ Option<int64> js_inline_max_bytes_;
+ Option<int64> css_outline_min_bytes_;
+ Option<int64> js_outline_min_bytes_;
+ Option<int> num_shards_;
+ Option<std::string> beacon_url_;
+ Option<int> max_url_segment_size_; // for http://a/b/c.d, use strlen("c.d")
+ Option<int> max_url_size_; // but this is strlen("http://a/b/c.d")
+ Option<bool> enabled_;
+ DomainLawyer domain_lawyer_;
+ // Be sure to update Merge() if a new field is added.
+
+ WildcardGroup allow_resources_;
+
+ DISALLOW_COPY_AND_ASSIGN(RewriteOptions);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_REWRITE_OPTIONS_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/rewrite_single_resource_filter.h b/trunk/src/net/instaweb/rewriter/public/rewrite_single_resource_filter.h
new file mode 100644
index 0000000..5baad2b
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/rewrite_single_resource_filter.h
@@ -0,0 +1,62 @@
+/*
+ * 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)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_REWRITE_SINGLE_RESOURCE_FILTER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_REWRITE_SINGLE_RESOURCE_FILTER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/rewriter/public/rewrite_filter.h"
+
+namespace net_instaweb {
+
+// Simpler interface for RewriteFilters which only convert one input resource
+// to one output resource.
+//
+// To derive from this class, implement RewriteLoadedResource.
+class RewriteSingleResourceFilter : public RewriteFilter {
+ public:
+ explicit RewriteSingleResourceFilter(
+ RewriteDriver* driver, StringPiece filter_prefix)
+ : RewriteFilter(driver, filter_prefix),
+ resource_manager_(driver->resource_manager()) {
+ }
+ virtual ~RewriteSingleResourceFilter();
+
+ virtual bool Fetch(OutputResource* output_resource,
+ Writer* response_writer,
+ const MetaData& request_header,
+ MetaData* response_headers,
+ MessageHandler* message_handler,
+ UrlAsyncFetcher::Callback* callback);
+
+ protected:
+ // Derived classes must implement this function instead of Fetch.
+ virtual bool RewriteLoadedResource(const Resource* input_resource,
+ OutputResource* output_resource) = 0;
+
+ private:
+ class FetchCallback;
+
+ ResourceManager* resource_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(RewriteSingleResourceFilter);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_REWRITE_SINGLE_RESOURCE_FILTER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/script_tag_scanner.h b/trunk/src/net/instaweb/rewriter/public/script_tag_scanner.h
new file mode 100644
index 0000000..d3c173f
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/script_tag_scanner.h
@@ -0,0 +1,83 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_SCRIPT_TAG_SCANNER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_SCRIPT_TAG_SCANNER_H_
+
+#include <set>
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/util/public/atom.h"
+
+namespace net_instaweb {
+class HtmlParse;
+
+class ScriptTagScanner {
+ public:
+ enum ScriptClassification {
+ kNonScript,
+ kUnknownScript,
+ kJavaScript
+ };
+
+ // Bit flags that specify when the script is to be run
+ enum ExecutionModeFlags {
+ kExecuteSync = 0,
+ kExecuteDefer = 1,
+ kExecuteAsync = 2,
+ kExecuteForEvent = 4 // IE extension. If this is set,
+ // script will not run in browsers following HTML5,
+ // and will run at hard-to-describe time in IE.
+ };
+
+ explicit ScriptTagScanner(HtmlParse* html_parse);
+
+ // Examines an HTML element and determine if it is a script.
+ // If it's not, it returns kNonScript and doesn't touch *src.
+ // If it is a script, it returns whether it is JavaScript or not,
+ // and sets *src to the src attribute (perhaps NULL)
+ ScriptClassification ParseScriptElement(HtmlElement* element,
+ HtmlElement::Attribute** src);
+
+ // Returns which execution model attributes are set.
+ // Keep in mind, however, that HTML5 browsers will ignore
+ // kExecuteDefer and kExecuteAsync on elements without src=''
+ int ExecutionMode(const HtmlElement* element) const;
+ private:
+ // Normalizes the input str by trimming whitespace and lowercasing.
+ static std::string Normalized(const StringPiece& str);
+
+ bool IsJsMime(const std::string& type_str);
+
+ const Atom s_async_;
+ const Atom s_defer_;
+ const Atom s_event_;
+ const Atom s_for_;
+ const Atom s_language_;
+ const Atom s_script_;
+ const Atom s_src_;
+ const Atom s_type_;
+ std::set<std::string> javascript_mimetypes_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScriptTagScanner);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_SCRIPT_TAG_SCANNER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/strip_scripts_filter.h b/trunk/src/net/instaweb/rewriter/public/strip_scripts_filter.h
new file mode 100644
index 0000000..f755b1f
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/strip_scripts_filter.h
@@ -0,0 +1,45 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_STRIP_SCRIPTS_FILTER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_STRIP_SCRIPTS_FILTER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/empty_html_filter.h"
+#include "net/instaweb/util/public/atom.h"
+
+namespace net_instaweb {
+
+// Remove all scripts from a web page.
+class StripScriptsFilter : public EmptyHtmlFilter {
+ public:
+ explicit StripScriptsFilter(HtmlParse* html_parse);
+
+ virtual void EndElement(HtmlElement* element);
+ virtual const char* Name() const { return "StripScripts"; }
+
+ private:
+ HtmlParse* html_parse_;
+ Atom s_script_;
+
+ DISALLOW_COPY_AND_ASSIGN(StripScriptsFilter);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_STRIP_SCRIPTS_FILTER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/url_input_resource.h b/trunk/src/net/instaweb/rewriter/public/url_input_resource.h
new file mode 100644
index 0000000..5d6114c
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/url_input_resource.h
@@ -0,0 +1,63 @@
+/**
+ * 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)
+//
+// Input resource created based on a network resource.
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_URL_INPUT_RESOURCE_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_URL_INPUT_RESOURCE_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/rewriter/public/resource.h"
+
+namespace net_instaweb {
+
+class MessageHandler;
+class MetaData;
+class RewriteOptions;
+class UrlFetcher;
+
+class UrlInputResource : public Resource {
+ public:
+ UrlInputResource(ResourceManager* manager,
+ const RewriteOptions* options,
+ const ContentType* type,
+ const StringPiece& url)
+ : Resource(manager, type),
+ url_(url.data(), url.size()),
+ rewrite_options_(options) {
+ }
+ virtual ~UrlInputResource();
+
+ virtual std::string url() const { return url_; }
+ const RewriteOptions* rewrite_options() const { return rewrite_options_; }
+
+ protected:
+ virtual bool Load(MessageHandler* message_handler);
+ virtual void LoadAndCallback(AsyncCallback* callback,
+ MessageHandler* message_handler);
+
+ private:
+ std::string url_;
+ const RewriteOptions* rewrite_options_;
+
+ DISALLOW_COPY_AND_ASSIGN(UrlInputResource);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_URL_INPUT_RESOURCE_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/url_left_trim_filter.h b/trunk/src/net/instaweb/rewriter/public/url_left_trim_filter.h
new file mode 100644
index 0000000..a92cd68
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/url_left_trim_filter.h
@@ -0,0 +1,82 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_URL_LEFT_TRIM_FILTER_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_URL_LEFT_TRIM_FILTER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/empty_html_filter.h"
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/rewriter/public/resource_tag_scanner.h"
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+// Relatively simple filter that trims redundant information from the left end
+// of each url. In particular, we can drop http: from any url that is on a page
+// served via http (etc. for other protocols). By the same token, a page often
+// contains fully-qualified urls that can be made base-relative (especially as
+// we may do this as a result of rewriting itself), or root-relative. We should
+// strip the leading portions of these urls.
+//
+// We actually register the base url of a page. This in turn registers
+// individual trimmings for the protocol, host, and path in that order. These
+// portions of the url are then trimmed off in order by the Trim(...) operation.
+//
+// TODO(jmaessen): url references in css / outside src= and href= properties
+// TODO(jmaessen): do we need a generic filter base class that just finds urls
+// and calls a class method? Or do we need context information for any
+// transform other than the sort of thing you see here?
+// TODO(jmaessen): Do we care to introduce ../ in order to relativize more urls?
+// Do we have a library solution to do so with minimal effort?
+
+class Statistics;
+class Variable;
+
+class UrlLeftTrimFilter : public EmptyHtmlFilter {
+ public:
+ UrlLeftTrimFilter(HtmlParse* html_parse, Statistics* resource_manager);
+ static void Initialize(Statistics* statistics);
+ virtual void StartElement(HtmlElement* element);
+ // TODO(sligocki): This is broken and only adds base_urls. We need to be able
+ // to ResetBaseUrl to a new value. There is only one base_url at a time.
+ virtual void AddBaseUrl(const StringPiece& base_url);
+ virtual const char* Name() const { return "UrlLeftTrim"; }
+
+ protected:
+ friend class UrlLeftTrimFilterTest;
+ bool Trim(StringPiece* url);
+ void AddTrimming(const StringPiece& trimming);
+
+ private:
+ HtmlParse* html_parse_;
+ StringVector left_trim_strings_;
+ const Atom s_base_;
+ const Atom s_href_;
+ const Atom s_src_;
+ Variable* trim_count_;
+ Variable* trim_saved_bytes_;
+
+ void TrimAttribute(HtmlElement::Attribute* attr);
+
+ DISALLOW_COPY_AND_ASSIGN(UrlLeftTrimFilter);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_URL_LEFT_TRIM_FILTER_H_
diff --git a/trunk/src/net/instaweb/rewriter/public/url_partnership.h b/trunk/src/net/instaweb/rewriter/public/url_partnership.h
new file mode 100644
index 0000000..c9d504c
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/public/url_partnership.h
@@ -0,0 +1,87 @@
+/**
+ * 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.
+ */
+
+#ifndef NET_INSTAWEB_REWRITER_PUBLIC_URL_PARTNERSHIP_H_
+#define NET_INSTAWEB_REWRITER_PUBLIC_URL_PARTNERSHIP_H_
+
+#include <vector>
+#include "net/instaweb/util/public/google_url.h"
+#include "net/instaweb/util/public/string_util.h"
+#include <string>
+
+class GURL;
+
+namespace net_instaweb {
+
+class MessageHandler;
+class RewriteOptions;
+
+// A URL partnership should be estalished in order to combine resources,
+// such as in CSS combination, JS combination, or image spriting. This
+// class centralizes the handling of such combinations, answering two
+// questions:
+// 1. Is it legal for a new URL to enter into the partnership
+// 2. What is the greatest common prefix
+// 3. What are the unique suffices for the elements.
+class UrlPartnership {
+ public:
+ UrlPartnership(const RewriteOptions* options, const GURL& original_request);
+ ~UrlPartnership();
+
+ // Adds a URL to a combination. If it can be legally added, consulting
+ // the DomainLaywer, then true is returned.
+ bool AddUrl(const StringPiece& resource_url, MessageHandler* handler);
+
+ // Computes the resolved base common to all URLs. This will always
+ // have a trailing slash.
+ std::string ResolvedBase() const;
+
+ // Returns the number of URLs that have been successfully added.
+ int num_urls() { return gurl_vector_.size(); }
+
+ // Returns the relative path of a particular URL that was added into
+ // the partnership. This requires that Resolve() be called first.
+ std::string RelativePath(int index) const;
+
+ // Returns the full resolved path
+ const GURL* FullPath(int index) const { return gurl_vector_[index]; }
+
+ // Removes the last URL that was added to the partnership.
+ void RemoveLast();
+
+ protected:
+ int num_components() const { return common_components_.size(); }
+
+ private:
+ void IncrementalResolve(int index);
+
+ typedef std::vector<GURL*> GurlVector;
+ GurlVector gurl_vector_;
+ std::string domain_;
+ GURL domain_gurl_;
+ const RewriteOptions* rewrite_options_;
+ GURL original_origin_and_path_;
+
+ // common_components_ is updated while adding Urls to support incremental
+ // resolution.
+ StringVector common_components_;
+
+ DISALLOW_COPY_AND_ASSIGN(UrlPartnership);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_PUBLIC_URL_PARTNERSHIP_H_
diff --git a/trunk/src/net/instaweb/rewriter/remove_comments_filter.cc b/trunk/src/net/instaweb/rewriter/remove_comments_filter.cc
new file mode 100644
index 0000000..0d1cb38
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/remove_comments_filter.cc
@@ -0,0 +1,32 @@
+/**
+ * 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: mdsteele@google.com (Matthew D. Steele)
+
+#include "net/instaweb/rewriter/public/remove_comments_filter.h"
+
+#include "net/instaweb/htmlparse/public/html_parse.h"
+
+namespace net_instaweb {
+
+RemoveCommentsFilter::RemoveCommentsFilter(HtmlParse* html_parse)
+ : html_parse_(html_parse) {}
+
+void RemoveCommentsFilter::Comment(HtmlCommentNode* comment) {
+ html_parse_->DeleteElement(comment);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/remove_comments_filter_test.cc b/trunk/src/net/instaweb/rewriter/remove_comments_filter_test.cc
new file mode 100644
index 0000000..e628d69
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/remove_comments_filter_test.cc
@@ -0,0 +1,65 @@
+/**
+ * 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: mdsteele@google.com (Matthew D. Steele)
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/html_parse_test_base.h"
+#include "net/instaweb/rewriter/public/remove_comments_filter.h"
+
+namespace net_instaweb {
+
+class RemoveCommentsFilterTest : public HtmlParseTestBase {
+ protected:
+ RemoveCommentsFilterTest()
+ : remove_comments_filter_(&html_parse_) {
+ html_parse_.AddFilter(&remove_comments_filter_);
+ }
+
+ virtual bool AddBody() const { return false; }
+
+ private:
+ RemoveCommentsFilter remove_comments_filter_;
+
+ DISALLOW_COPY_AND_ASSIGN(RemoveCommentsFilterTest);
+};
+
+TEST_F(RemoveCommentsFilterTest, NoComments) {
+ ValidateNoChanges("no_comments",
+ "<head><title>Hello</title></head>"
+ "<body>Why, hello there!</body>");
+}
+
+TEST_F(RemoveCommentsFilterTest, RemoveComment) {
+ ValidateExpected("remove_comment",
+ "<body>hello <!--world--></body>",
+ "<body>hello </body>");
+}
+
+TEST_F(RemoveCommentsFilterTest, RemoveMultipleComments) {
+ ValidateExpected("remove_multiple_comments",
+ "<head><!--1--><title>Hi<!--2--></title></head>"
+ "<body><!--3-->hello<!--4--><!--5--></body>",
+ "<head><title>Hi</title></head>"
+ "<body>hello</body>");
+}
+
+TEST_F(RemoveCommentsFilterTest, DoNotRemoveIEDirective) {
+ ValidateNoChanges("do_not_remove_ie_directive",
+ "<body>hello <!--[if IE 8]>world<![endif]--></body>");
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/resource.cc b/trunk/src/net/instaweb/rewriter/resource.cc
new file mode 100644
index 0000000..2957739
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/resource.cc
@@ -0,0 +1,87 @@
+/**
+ * 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)
+// jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/rewriter/public/resource.h"
+#include "net/instaweb/util/public/content_type.h"
+
+namespace net_instaweb {
+
+const int64 Resource::kDefaultExpireTimeMs = 5 * 60 * 1000;
+
+Resource::~Resource() {
+}
+
+int64 Resource::CacheExpirationTimeMs() const {
+ int64 input_expire_time_ms = kDefaultExpireTimeMs;
+ if (meta_data_.IsCacheable()) {
+ input_expire_time_ms = meta_data_.CacheExpirationTimeMs();
+ }
+ return input_expire_time_ms;
+}
+
+// Note: OutputResource overrides this to also set the file extension.
+void Resource::SetType(const ContentType* type) {
+ type_ = type;
+}
+
+void Resource::DetermineContentType() {
+ // Try to determine the content type from the URL extension, or
+ // the response headers.
+ CharStarVector content_types;
+ MetaData* headers = metadata();
+ const ContentType* content_type = NULL;
+ if (headers->Lookup("Content-type", &content_types)) {
+ for (int i = 0, n = content_types.size(); (i < n) && content_type == NULL;
+ ++i) {
+ content_type = MimeTypeToContentType(content_types[i]);
+ }
+ }
+
+ if (content_type == NULL) {
+ // If there is no content type in input headers, then try to
+ // determine it from the name.
+ std::string trimmed_url;
+ TrimWhitespace(url(), &trimmed_url);
+ content_type = NameExtensionToContentType(trimmed_url);
+ }
+ if (content_type != NULL) {
+ SetType(content_type);
+ }
+}
+
+// Default, blocking implementation which calls Load.
+// Resources which can fetch asynchronously should override this.
+void Resource::LoadAndCallback(AsyncCallback* callback,
+ MessageHandler* message_handler) {
+ callback->Done(Load(message_handler), this);
+}
+
+Resource::AsyncCallback::~AsyncCallback() {
+}
+
+bool Resource::Link(HTTPValue* value, MessageHandler* handler) {
+ SharedString* contents_and_headers = value->share();
+ return value_.Link(contents_and_headers, &meta_data_, handler);
+}
+
+bool Resource::IsCacheable() const {
+ return true;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/resource_manager.cc b/trunk/src/net/instaweb/rewriter/resource_manager.cc
new file mode 100644
index 0000000..94e7f8e
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/resource_manager.cc
@@ -0,0 +1,653 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/rewriter/public/resource_manager.h"
+
+#include "base/scoped_ptr.h"
+#include "net/instaweb/rewriter/public/data_url_input_resource.h"
+#include "net/instaweb/rewriter/public/file_input_resource.h"
+#include "net/instaweb/rewriter/public/output_resource.h"
+#include "net/instaweb/rewriter/public/resource_namer.h"
+#include "net/instaweb/rewriter/public/rewrite_driver_factory.h"
+#include "net/instaweb/rewriter/public/rewrite_filter.h"
+#include "net/instaweb/rewriter/public/rewrite_options.h"
+#include "net/instaweb/rewriter/public/url_input_resource.h"
+#include "net/instaweb/rewriter/public/url_partnership.h"
+#include "net/instaweb/util/public/content_type.h"
+#include "net/instaweb/util/public/google_url.h"
+#include "net/instaweb/util/public/hasher.h"
+#include "net/instaweb/util/public/http_cache.h"
+#include "net/instaweb/util/public/http_value.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/named_lock_manager.h"
+#include "net/instaweb/util/public/statistics.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/time_util.h"
+#include "net/instaweb/util/public/timer.h"
+#include "net/instaweb/util/public/url_escaper.h"
+
+namespace net_instaweb {
+
+namespace {
+
+// resource_url_domain_rejections counts the number of urls on a page that we
+// could have rewritten, except that they lay in a domain that did not
+// permit resource rewriting relative to the current page.
+const char kResourceUrlDomainRejections[] = "resource_url_domain_rejections";
+
+const int64 kGeneratedMaxAgeMs = Timer::kYearMs;
+const int64 kGeneratedMaxAgeSec = Timer::kYearMs / Timer::kSecondMs;
+
+// Our HTTP cache mostly stores full URLs, including the http: prefix,
+// mapping them into the URL contents and HTTP headers. However, we
+// also put name->hash mappings into the HTTP cache, and we prefix
+// these with "ResourceName:" to disambiguate them.
+//
+// Cache entries prefixed this way map the base name of a resource
+// into the hash-code of the contents. This mapping has a TTL based
+// on the minimum TTL of the input resources used to construct the
+// resource. After that TTL has expired, we will need to re-fetch the
+// resources from their origin, and recompute the hash.
+//
+// Whenever we change the hashing function we can bust caches by
+// changing this prefix.
+//
+// TODO(jmarantz): inject the SVN version number here to automatically bust
+// caches whenever pagespeed is upgraded.
+const char kCacheKeyPrefix[] = "rname/";
+
+} // namespace
+
+const int ResourceManager::kNotSharded = -1;
+
+// We set etags for our output resources to "W/0". The "W" means
+// that this etag indicates a functional consistency, but is not
+// guaranteeing byte-consistency. This distinction is important because
+// we serve different bytes for clients that do not accept gzip.
+//
+// This value is a shared constant so that it can also be used in
+// the Apache-specific code that repairs headers after mod_headers
+// alters them.
+const char ResourceManager::kResourceEtagValue[] = "W/0";
+
+ResourceManager::ResourceManager(const StringPiece& file_prefix,
+ FileSystem* file_system,
+ FilenameEncoder* filename_encoder,
+ UrlAsyncFetcher* url_async_fetcher,
+ Hasher* hasher,
+ HTTPCache* http_cache,
+ NamedLockManager* lock_manager)
+ : file_prefix_(file_prefix.data(), file_prefix.size()),
+ resource_id_(0),
+ file_system_(file_system),
+ filename_encoder_(filename_encoder),
+ url_async_fetcher_(url_async_fetcher),
+ hasher_(hasher),
+ statistics_(NULL),
+ resource_url_domain_rejections_(NULL),
+ http_cache_(http_cache),
+ url_escaper_(new UrlEscaper()),
+ relative_path_(false),
+ store_outputs_in_file_system_(true),
+ lock_manager_(lock_manager),
+ max_age_string_(StringPrintf("max-age=%d",
+ static_cast<int>(kGeneratedMaxAgeSec))) {
+}
+
+ResourceManager::~ResourceManager() {
+}
+
+void ResourceManager::Initialize(Statistics* statistics) {
+ statistics->AddVariable(kResourceUrlDomainRejections);
+}
+
+#if 0
+// Preserved for the sake of making it easier to revive sharding.
+
+std::string ResourceManager::UrlPrefixFor(const ResourceNamer& namer) const {
+ CHECK(!namer.hash().empty());
+ std::string url_prefix;
+ if (num_shards_ == 0) {
+ url_prefix = url_prefix_pattern_;
+ } else {
+ size_t hash = namer.Hash();
+ int shard = hash % num_shards_;
+ CHECK_NE(std::string::npos, url_prefix_pattern_.find("%d"));
+ // The following uses a user-provided printf format string; this would be
+ // really dangerous if we did not validate url_prefix_pattern_ by calling
+ // ValidateShardsAgainstUrlPrefixPattern() below.
+ url_prefix = StringPrintf(url_prefix_pattern_.c_str(), shard); // NOLINT
+ }
+ return url_prefix;
+}
+
+// Decode a base path into a shard number and canonical base url.
+// Right now the canonical base url is empty for the old resource
+// naming scheme, and non-empty otherwise.
+// TODO(jmaessen): Either axe or adapt to sharding post-url_prefix.
+std::string ResourceManager::CanonicalizeBase(
+ const StringPiece& base, int* shard) const {
+ std::string base_str = base.as_string();
+ base_str += "/";
+ std::string result;
+ if (num_shards_ == 0) {
+ CHECK_EQ(std::string::npos, url_prefix_pattern_.find("%d"));
+ if (url_prefix_pattern_.compare(base_str) != 0) {
+ base.CopyToString(&result);
+ }
+ } else {
+ CHECK_NE(std::string::npos, url_prefix_pattern_.find("%d"));
+ // TODO(jmaessen): Ugh. Lint hates this sscanf call and so do I. Can parse
+ // based on the results of the above find.
+ if (!sscanf(base_str.c_str(), url_prefix_pattern_.c_str(), shard) == 1) {
+ base.CopyToString(&result);
+ }
+ }
+ return result;
+}
+
+void ResourceManager::ValidateShardsAgainstUrlPrefixPattern() {
+ std::string::size_type pos = url_prefix_pattern_.find('%');
+ if (num_shards_ == 0) {
+ CHECK(pos == StringPiece::npos) << "URL prefix should not have a percent "
+ << "when num_shards==0";
+ } else {
+ // Ensure that the % is followed by a 'd'. But be careful because
+ // the percent may have appeared at the end of the string, which
+ // is not necessarily null-terminated.
+ if ((pos == std::string::npos) ||
+ ((pos + 1) == url_prefix_pattern_.size()) ||
+ (url_prefix_pattern_.substr(pos + 1, 1) != "d")) {
+ CHECK(false) << "url_prefix must contain exactly one %d";
+ } else {
+ // make sure there is not another percent
+ pos = url_prefix_pattern_.find('%', pos + 2);
+ CHECK(pos == std::string::npos) << "Extra % found in url_prefix_pattern";
+ }
+ }
+}
+#endif
+
+// TODO(jmarantz): consider moving this method to MetaData
+void ResourceManager::SetDefaultHeaders(const ContentType* content_type,
+ MetaData* header) const {
+ CHECK_EQ(0, header->major_version());
+ CHECK_EQ(0, header->NumAttributes());
+ header->set_major_version(1);
+ header->set_minor_version(1);
+ header->SetStatusAndReason(HttpStatus::kOK);
+ if (content_type != NULL) {
+ header->Add(HttpAttributes::kContentType, content_type->mime_type());
+ }
+ int64 now_ms = http_cache_->timer()->NowMs();
+ header->Add(HttpAttributes::kCacheControl, max_age_string_);
+ std::string expires_string;
+ if (ConvertTimeToString(now_ms + kGeneratedMaxAgeMs, &expires_string)) {
+ header->Add(HttpAttributes::kExpires, expires_string);
+ }
+
+ // While PageSpeed claims the "Vary" header is needed to avoid proxy cache
+ // issues for clients where some accept gzipped content and some don't, it
+ // should not be done here. It should instead be done by whatever code is
+ // conditionally gzipping the content based on user-agent, e.g. mod_deflate.
+ // header->Add(HttpAttributes::kVary, HttpAttributes::kAcceptEncoding);
+
+ // ETag is superfluous for mod_pagespeed as we sign the URL with the
+ // content hash. However, we have seen evidence that IE8 will not
+ // serve images from its cache when the image lacks an ETag. Since
+ // we sign URLs, there is no reason to have a unique signature in
+ // the ETag.
+ header->Add(HttpAttributes::kEtag, kResourceEtagValue);
+
+ // TODO(jmarantz): add date/last-modified headers by default.
+ CharStarVector v;
+ if (!header->Lookup(HttpAttributes::kDate, &v)) {
+ header->SetDate(now_ms);
+ }
+ if (!header->Lookup(HttpAttributes::kLastModified, &v)) {
+ header->SetLastModified(now_ms);
+ }
+
+ // TODO(jmarantz): Page-speed suggested adding a "Last-Modified" header
+ // for cache validation. To do this we must track the max of all
+ // Last-Modified values for all input resources that are used to
+ // create this output resource. For now we are using the current
+ // time.
+
+ header->ComputeCaching();
+}
+
+// TODO(jmarantz): consider moving this method to MetaData
+void ResourceManager::SetContentType(const ContentType* content_type,
+ MetaData* header) {
+ CHECK(content_type != NULL);
+ header->RemoveAll(HttpAttributes::kContentType);
+ header->Add(HttpAttributes::kContentType, content_type->mime_type());
+ header->ComputeCaching();
+}
+
+// Constructs an output resource corresponding to the specified input resource
+// and encoded using the provided encoder.
+OutputResource* ResourceManager::CreateOutputResourceFromResource(
+ const StringPiece& filter_prefix,
+ const ContentType* content_type,
+ UrlSegmentEncoder* encoder,
+ Resource* input_resource,
+ MessageHandler* handler) {
+ OutputResource* result = NULL;
+ if (input_resource != NULL) {
+ std::string url = input_resource->url();
+ GURL input_gurl(url);
+ CHECK(input_gurl.is_valid()); // or input_resource should have been NULL.
+ std::string name;
+ encoder->EncodeToUrlSegment(GoogleUrl::Leaf(input_gurl), &name);
+ result = CreateOutputResourceWithPath(
+ GoogleUrl::AllExceptLeaf(input_gurl),
+ filter_prefix, name, content_type, handler);
+ }
+ return result;
+}
+
+OutputResource* ResourceManager::CreateOutputResourceForRewrittenUrl(
+ const GURL& document_gurl,
+ const StringPiece& filter_prefix,
+ const StringPiece& resource_url,
+ const ContentType* content_type,
+ UrlSegmentEncoder* encoder,
+ const RewriteOptions* rewrite_options,
+ MessageHandler* handler) {
+ OutputResource* output_resource = NULL;
+ UrlPartnership partnership(rewrite_options, document_gurl);
+ if (partnership.AddUrl(resource_url, handler)) {
+ std::string base = partnership.ResolvedBase();
+ std::string relative_url = partnership.RelativePath(0);
+ std::string name;
+ encoder->EncodeToUrlSegment(relative_url, &name);
+ output_resource = CreateOutputResourceWithPath(
+ base, filter_prefix, name, content_type, handler);
+ }
+ return output_resource;
+}
+
+OutputResource* ResourceManager::CreateOutputResourceWithPath(
+ const StringPiece& path,
+ const StringPiece& filter_prefix,
+ const StringPiece& name,
+ const ContentType* content_type,
+ MessageHandler* handler) {
+ CHECK(content_type != NULL);
+ ResourceNamer full_name;
+ full_name.set_id(filter_prefix);
+ full_name.set_name(name);
+ // TODO(jmaessen): The addition of 1 below avoids the leading ".";
+ // make this convention consistent and fix all code.
+ full_name.set_ext(content_type->file_extension() + 1);
+ OutputResource* resource =
+ new OutputResource(this, path, full_name, content_type);
+
+ // Determine whether this output resource is still valid by looking
+ // up by hash in the http cache. Note that this cache entry will
+ // expire when any of the origin resources expire.
+ SimpleMetaData meta_data;
+ StringPiece hash_extension;
+ HTTPValue value;
+ std::string name_key = StrCat(kCacheKeyPrefix, resource->name_key());
+ if ((http_cache_->Find(name_key, &value, &meta_data, handler)
+ == HTTPCache::kFound) &&
+ value.ExtractContents(&hash_extension)) {
+ ResourceNamer hash_ext;
+ if (hash_ext.DecodeHashExt(hash_extension)) {
+ resource->SetHash(hash_ext.hash());
+ // Note that the '.' must be included in the suffix
+ // TODO(jmarantz): remove this from the suffix.
+ resource->set_suffix(StrCat(".", hash_ext.ext()));
+ }
+ }
+ return resource;
+}
+
+OutputResource* ResourceManager::CreateOutputResourceForFetch(
+ const StringPiece& url) {
+ OutputResource* resource = NULL;
+ std::string url_string(url.data(), url.size());
+ GURL gurl(url_string);
+ if (gurl.is_valid()) {
+ std::string name = GoogleUrl::Leaf(gurl);
+ ResourceNamer namer;
+ if (namer.Decode(name)) {
+ std::string base = GoogleUrl::AllExceptLeaf(gurl);
+ resource = new OutputResource(this, base, namer, NULL);
+ }
+ }
+ return resource;
+}
+
+void ResourceManager::set_filename_prefix(const StringPiece& file_prefix) {
+ file_prefix.CopyToString(&file_prefix_);
+}
+
+// Implements lazy initialization of resource_url_domain_rejections_,
+// necessitated by the fact that we can set_statistics before
+// Initialize(...) has been called and thus can't safely look
+// for the variable until first use.
+void ResourceManager::IncrementResourceUrlDomainRejections() {
+ if (resource_url_domain_rejections_ == NULL) {
+ if (statistics_ == NULL) {
+ return;
+ }
+ resource_url_domain_rejections_ =
+ statistics_->GetVariable(kResourceUrlDomainRejections);
+ }
+ resource_url_domain_rejections_->Add(1);
+}
+
+Resource* ResourceManager::CreateInputResource(
+ const GURL& base_gurl,
+ const StringPiece& input_url,
+ const RewriteOptions* rewrite_options,
+ MessageHandler* handler) {
+ UrlPartnership partnership(rewrite_options, base_gurl);
+ Resource* resource = NULL;
+ if (partnership.AddUrl(input_url, handler)) {
+ const GURL* input_gurl = partnership.FullPath(0);
+ resource = CreateInputResourceUnchecked(*input_gurl, rewrite_options,
+ handler);
+ } else {
+ handler->Message(kInfo, "%s: Invalid url relative to '%s'",
+ input_url.as_string().c_str(), base_gurl.spec().c_str());
+ IncrementResourceUrlDomainRejections();
+ resource = NULL;
+ }
+ return resource;
+}
+
+Resource* ResourceManager::CreateInputResourceAndReadIfCached(
+ const GURL& base_gurl, const StringPiece& input_url,
+ const RewriteOptions* rewrite_options,
+ MessageHandler* handler) {
+ Resource* input_resource = CreateInputResource(
+ base_gurl, input_url, rewrite_options, handler);
+ if (input_resource != NULL &&
+ (!input_resource->IsCacheable() ||
+ !ReadIfCached(input_resource, handler))) {
+ handler->Message(kInfo,
+ "%s: Couldn't fetch resource %s to rewrite.",
+ base_gurl.spec().c_str(), input_url.as_string().c_str());
+ delete input_resource;
+ input_resource = NULL;
+ }
+ return input_resource;
+}
+
+Resource* ResourceManager::CreateInputResourceFromOutputResource(
+ UrlSegmentEncoder* encoder,
+ OutputResource* output_resource,
+ const RewriteOptions* rewrite_options,
+ MessageHandler* handler) {
+ // Assumes output_resource has a url that's been checked by a lawyer. We
+ // should already have checked the signature on the encoded resource name and
+ // failed to create output_resource if it didn't match.
+ Resource* input_resource = NULL;
+ std::string input_name;
+ if (encoder->DecodeFromUrlSegment(output_resource->name(), &input_name)) {
+ GURL base_gurl(output_resource->resolved_base());
+ GURL input_gurl = base_gurl.Resolve(input_name);
+ input_resource = CreateInputResourceUnchecked(input_gurl,
+ rewrite_options, handler);
+ }
+ return input_resource;
+}
+
+Resource* ResourceManager::CreateInputResourceAbsolute(
+ const StringPiece& absolute_url, const RewriteOptions* rewrite_options,
+ MessageHandler* handler) {
+ std::string url_string(absolute_url.data(), absolute_url.size());
+ GURL url(url_string);
+ return CreateInputResourceUnchecked(url, rewrite_options, handler);
+}
+
+Resource* ResourceManager::CreateInputResourceUnchecked(
+ const GURL& url, const RewriteOptions* rewrite_options,
+ MessageHandler* handler) {
+ if (!url.is_valid()) {
+ // Note: Bad user-content can leave us here. But it's really hard
+ // to concatenate a valid protocol and domain onto an arbitrary string
+ // and end up with an invalid GURL.
+ handler->Message(kWarning, "%s: Invalid url",
+ url.possibly_invalid_spec().c_str());
+ return NULL;
+ }
+ std::string url_string = GoogleUrl::Spec(url);
+
+ Resource* resource = NULL;
+
+ if (url.SchemeIs("data")) {
+ resource = DataUrlInputResource::Make(url_string, this);
+ if (resource == NULL) {
+ // Note: Bad user-content can leave us here.
+ handler->Message(kWarning, "Badly formatted data url '%s'",
+ url_string.c_str());
+ }
+ } else if (url.SchemeIs("http")) {
+ // TODO(sligocki): Figure out if these are actually local, in
+ // which case we can do a local file read.
+
+ // Note: type may be NULL if url has an unexpected or malformed extension.
+ const ContentType* type = NameExtensionToContentType(url_string);
+ resource = new UrlInputResource(this, rewrite_options, type, url_string);
+ } else {
+ // Note: Bad user-content can leave us here.
+ handler->Message(kWarning, "Unsupported scheme '%s' for url '%s'",
+ url.scheme().c_str(), url_string.c_str());
+ }
+ return resource;
+}
+
+// TODO(jmarantz): remove writer/response_headers args from this function
+// and force caller to pull those directly from output_resource, as that will
+// save the effort of copying the headers.
+//
+// It will also simplify this routine quite a bit.
+bool ResourceManager::FetchOutputResource(
+ OutputResource* output_resource,
+ Writer* writer, MetaData* response_headers,
+ MessageHandler* handler, BlockingBehavior blocking) const {
+ if (output_resource == NULL) {
+ return false;
+ }
+
+ // TODO(jmarantz): we are making lots of copies of the data. We should
+ // retrieve the data from the cache without copying it.
+
+ // The http_cache is shared between multiple different classes in Instaweb.
+ // To avoid colliding hash keys, we use a class-specific prefix.
+ //
+ // TODO(jmarantz): consider formalizing this in the HTTPCache API and
+ // doing the StrCat inside.
+ bool ret = false;
+ StringPiece content;
+ MetaData* meta_data = output_resource->metadata();
+ if (output_resource->IsWritten()) {
+ ret = ((writer == NULL) ||
+ ((output_resource->value_.ExtractContents(&content)) &&
+ writer->Write(content, handler)));
+ } else if (output_resource->has_hash()) {
+ std::string url = output_resource->url();
+ // Check cache once without lock, then if that fails try again with lock.
+ // Note that it would be *correct* to lock up front and only check once.
+ // However, the common case here is that the resource is present (because
+ // this path mostly happens during resource fetch). We want to avoid
+ // unnecessarily serializing resource fetch on a lock.
+ for (int i = 0; !ret && i < 2; ++i) {
+ if ((http_cache_->Find(url, &output_resource->value_, meta_data, handler)
+ == HTTPCache::kFound) &&
+ ((writer == NULL) ||
+ output_resource->value_.ExtractContents(&content)) &&
+ ((writer == NULL) || writer->Write(content, handler))) {
+ output_resource->set_written(true);
+ ret = true;
+ } else if (ReadIfCached(output_resource, handler)) {
+ content = output_resource->contents();
+ http_cache_->Put(url, *meta_data, content, handler);
+ ret = ((writer == NULL) || writer->Write(content, handler));
+ }
+ // On the first iteration, obtain the lock if we don't have data.
+ if (!ret && i == 0 && !output_resource->LockForCreation(this, blocking)) {
+ // We didn't get the lock; we need to abandon ship. The caller should
+ // see this as a successful fetch for which IsWritten() remains false.
+ CHECK(!output_resource->IsWritten());
+ ret = true;
+ }
+ }
+ } else {
+ // TODO(jmaessen): This path should also re-try fetching the resource after
+ // obtaining the lock. However, in this case we need to look for the hash
+ // in the cache first, which duplicates logic from creation time and makes
+ // life generally complicated.
+ ret = !output_resource->LockForCreation(this, blocking);
+ }
+ if (ret && (response_headers != NULL) && (response_headers != meta_data)) {
+ response_headers->CopyFrom(*meta_data);
+ }
+ return ret;
+}
+
+bool ResourceManager::Write(HttpStatus::Code status_code,
+ const StringPiece& contents,
+ OutputResource* output,
+ int64 origin_expire_time_ms,
+ MessageHandler* handler) {
+ MetaData* meta_data = output->metadata();
+ SetDefaultHeaders(output->type(), meta_data);
+ meta_data->SetStatusAndReason(status_code);
+
+ scoped_ptr<OutputResource::OutputWriter> writer(output->BeginWrite(handler));
+ bool ret = (writer != NULL);
+ if (ret) {
+ ret = writer->Write(contents, handler);
+ ret &= output->EndWrite(writer.get(), handler);
+ http_cache_->Put(output->url(), &output->value_, handler);
+
+ if (!output->generated()) {
+ // Map the name of this resource to the fully expanded filename. The
+ // name of the output resource is usually a function of how it is
+ // constructed from input resources. For example, with combine_css,
+ // output->name() encodes all the component CSS filenames. The filename
+ // this maps to includes the hash of the content. Thus the two mappings
+ // have different lifetimes.
+ //
+ // The name->filename map expires when any of the origin files expire.
+ // When that occurs, fresh content must be read, and the output must
+ // be recomputed and re-hashed.
+ //
+ // However, the hashed output filename can live, essentially, forever.
+ // This is what we'll hash first as meta_data's default headers are
+ // to cache forever.
+
+ // Now we'll mutate meta_data to expire when the origin expires, and
+ // map the name to the hash.
+ int64 delta_ms = origin_expire_time_ms - http_cache_->timer()->NowMs();
+ int64 delta_sec = delta_ms / 1000;
+ if ((delta_sec > 0) || http_cache_->force_caching()) {
+ SimpleMetaData origin_meta_data;
+ SetDefaultHeaders(output->type(), &origin_meta_data);
+ std::string cache_control = StringPrintf(
+ "max-age=%ld",
+ static_cast<long>(delta_sec)); // NOLINT
+ origin_meta_data.RemoveAll(HttpAttributes::kCacheControl);
+ origin_meta_data.Add(HttpAttributes::kCacheControl, cache_control);
+ origin_meta_data.ComputeCaching();
+
+ std::string name_key = StrCat(kCacheKeyPrefix, output->name_key());
+ http_cache_->Put(name_key, origin_meta_data, output->hash_ext(),
+ handler);
+ }
+ }
+ } else {
+ // Note that we've already gotten a "could not open file" message;
+ // this just serves to explain why and suggest a remedy.
+ handler->Message(kInfo, "Could not create output resource"
+ " (bad filename prefix '%s'?)",
+ file_prefix_.c_str());
+ }
+ return ret;
+}
+
+void ResourceManager::ReadAsync(Resource* resource,
+ Resource::AsyncCallback* callback,
+ MessageHandler* handler) {
+ HTTPCache::FindResult result = HTTPCache::kNotFound;
+
+ // If the resource is not already loaded, and this type of resource (e.g.
+ // URL vs File vs Data) is cacheable, then try to load it.
+ if (resource->loaded()) {
+ result = HTTPCache::kFound;
+ } else if (resource->IsCacheable()) {
+ result = http_cache_->Find(resource->url(), &resource->value_,
+ resource->metadata(), handler);
+ }
+
+ switch (result) {
+ case HTTPCache::kFound:
+ callback->Done(true, resource);
+ break;
+ case HTTPCache::kRecentFetchFailedDoNotRefetch:
+ // TODO(jmarantz): in this path, should we try to fetch again
+ // sooner than 5 minutes? The issue is that in this path we are
+ // serving for the user, not for a rewrite. This could get
+ // frustrating, even if the software is functioning as intended,
+ // because a missing resource that is put in place by a site
+ // admin will not be checked again for 5 minutes.
+ //
+ // The "good" news is that if the admin is willing to crank up
+ // logging to 'info' then http_cache.cc will log the
+ // 'remembered' failure.
+ callback->Done(false, resource);
+ break;
+ case HTTPCache::kNotFound:
+ // If not, load it asynchronously.
+ resource->LoadAndCallback(callback, handler);
+ break;
+ }
+ // TODO(sligocki): Do we need to call DetermineContentType like below?
+}
+
+bool ResourceManager::ReadIfCached(Resource* resource,
+ MessageHandler* handler) const {
+ HTTPCache::FindResult result = HTTPCache::kNotFound;
+
+ // If the resource is not already loaded, and this type of resource (e.g.
+ // URL vs File vs Data) is cacheable, then try to load it.
+ if (resource->loaded()) {
+ result = HTTPCache::kFound;
+ } else if (resource->IsCacheable()) {
+ result = http_cache_->Find(resource->url(), &resource->value_,
+ resource->metadata(), handler);
+ }
+ if ((result == HTTPCache::kNotFound) && resource->Load(handler)) {
+ result = HTTPCache::kFound;
+ }
+ if (result == HTTPCache::kFound) {
+ resource->DetermineContentType();
+ return true;
+ }
+ return false;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/resource_manager_test.cc b/trunk/src/net/instaweb/rewriter/resource_manager_test.cc
new file mode 100644
index 0000000..074a8b0
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/resource_manager_test.cc
@@ -0,0 +1,261 @@
+/**
+ * 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: abliss@google.com (Adam Bliss)
+
+// Unit-test the resource manager
+
+#include "base/scoped_ptr.h"
+#include "net/instaweb/rewriter/public/output_resource.h"
+#include "net/instaweb/rewriter/public/resource_namer.h"
+#include "net/instaweb/rewriter/public/resource_manager.h"
+#include "net/instaweb/rewriter/public/resource_manager_test_base.h"
+#include "net/instaweb/rewriter/public/rewrite_filter.h"
+#include "net/instaweb/rewriter/resource_manager_testing_peer.h"
+#include "net/instaweb/util/public/content_type.h"
+#include "net/instaweb/util/public/gtest.h"
+#include "net/instaweb/util/public/meta_data.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/string_writer.h"
+#include "net/instaweb/util/public/url_escaper.h"
+
+namespace net_instaweb {
+
+class VerifyContentsCallback : public Resource::AsyncCallback {
+ public:
+ explicit VerifyContentsCallback(const std::string& contents) :
+ contents_(contents), called_(false) {
+ }
+ virtual void Done(bool success, Resource* resource) {
+ EXPECT_EQ(0, contents_.compare(resource->contents().as_string()));
+ called_ = true;
+ }
+ void AssertCalled() {
+ EXPECT_TRUE(called_);
+ }
+ std::string contents_;
+ bool called_;
+};
+
+class ResourceManagerTest : public ResourceManagerTestBase {
+ protected:
+ ResourceManagerTest() { }
+
+ // Calls FetchOutputResource with different values of writer and
+ // response_headers, to test all branches. Expects the fetch to succeed all
+ // times, and finally returns the contents.
+ std::string FetchOutputResource(OutputResource* resource) {
+ EXPECT_TRUE(resource_manager_->FetchOutputResource(
+ resource, NULL, NULL, &message_handler_, ResourceManager::kMayBlock));
+ SimpleMetaData empty;
+ EXPECT_TRUE(resource_manager_->FetchOutputResource(
+ resource, NULL, &empty, &message_handler_, ResourceManager::kMayBlock));
+ std::string contents;
+ StringWriter writer(&contents);
+ EXPECT_TRUE(resource_manager_->FetchOutputResource(
+ resource, &writer, resource->metadata(),
+ &message_handler_, ResourceManager::kMayBlock));
+ return contents;
+ }
+
+ // Asserts that the given url starts with an appropriate prefix;
+ // then cuts off that prefix.
+ virtual void RemoveUrlPrefix(std::string* url) {
+ EXPECT_EQ(0, url->compare(0, url_prefix_.length(), url_prefix_));
+ url->erase(0, url_prefix_.length());
+ }
+
+
+ // Tests for the lifecycle and various flows of a named output resource.
+ void TestNamed() {
+ const char* filter_prefix = "fp";
+ const char* name = "name";
+ const char* contents = "contents";
+ // origin_expire_time_ms should be considerably longer than the various
+ // timeouts for resource locking, since we hit those timeouts in various
+ // places.
+ const int64 origin_expire_time_ms = 100000;
+ const ContentType* content_type = &kContentTypeText;
+ scoped_ptr<OutputResource> nor(
+ resource_manager_->CreateOutputResourceWithPath(
+ url_prefix_, filter_prefix, name, content_type, &message_handler_));
+ ASSERT_TRUE(nor.get() != NULL);
+ // Check name_key against url_prefix/fp.name
+ std::string name_key = nor->name_key();
+ RemoveUrlPrefix(&name_key);
+ EXPECT_EQ(nor->full_name().EncodeIdName(), name_key);
+ // Make sure the resource hasn't already been created (and lock it for
+ // creation).
+ EXPECT_FALSE(resource_manager_->FetchOutputResource(
+ nor.get(), NULL, NULL, &message_handler_,
+ ResourceManager::kNeverBlock));
+ EXPECT_FALSE(nor->IsWritten());
+ {
+ // Now show that another attempt to create the resource will fail.
+ // Here we attempt to create without the hash.
+ scoped_ptr<OutputResource> nor1(
+ resource_manager_->CreateOutputResourceWithPath(
+ url_prefix_, filter_prefix, name, content_type,
+ &message_handler_));
+ ASSERT_TRUE(nor1.get() != NULL);
+ // We'll succeed in fetching (meaning don't create the resource), but the
+ // resource won't be written.
+ EXPECT_TRUE(resource_manager_->FetchOutputResource(
+ nor1.get(), NULL, NULL, &message_handler_,
+ ResourceManager::kNeverBlock));
+ EXPECT_FALSE(nor1->IsWritten());
+ }
+ {
+ // Here we attempt to create the object with the hash and fail.
+ ResourceNamer namer;
+ namer.CopyFrom(nor->full_name());
+ namer.set_hash("0");
+ namer.set_ext("txt");
+ std::string name = StrCat(url_prefix_, namer.Encode());
+ scoped_ptr<OutputResource> nor1(
+ resource_manager_->CreateOutputResourceForFetch(name));
+ ASSERT_TRUE(nor1.get() != NULL);
+ // Again we'll succeed in fetching (meaning don't create), but the
+ // resource won't be written. Note that we do a non-blocking fetch here.
+ // An actual resource fetch does a blocking fetch that would end by
+ // stealing the creation lock; we don't want to steal the lock here.
+ EXPECT_TRUE(resource_manager_->FetchOutputResource(
+ nor1.get(), NULL, NULL, &message_handler_,
+ ResourceManager::kNeverBlock));
+ EXPECT_FALSE(nor1->IsWritten());
+ }
+ // Write some data
+ EXPECT_FALSE(ResourceManagerTestingPeer::HasHash(nor.get()));
+ EXPECT_FALSE(ResourceManagerTestingPeer::Generated(nor.get()));
+ EXPECT_TRUE(resource_manager_->Write(HttpStatus::kOK, contents, nor.get(),
+ origin_expire_time_ms,
+ &message_handler_));
+ EXPECT_TRUE(nor->IsWritten());
+ // Check that hash_ext() is correct.
+ ResourceNamer full_name;
+ EXPECT_TRUE(full_name.DecodeHashExt(nor->hash_ext()));
+ EXPECT_EQ("0", full_name.hash());
+ EXPECT_EQ("txt", full_name.ext());
+ // Retrieve the same NOR from the cache.
+ scoped_ptr<OutputResource> nor2(
+ resource_manager_->CreateOutputResourceWithPath(
+ url_prefix_, filter_prefix, name, &kContentTypeText,
+ &message_handler_));
+ ASSERT_TRUE(nor2.get() != NULL);
+ EXPECT_TRUE(ResourceManagerTestingPeer::HasHash(nor2.get()));
+ EXPECT_FALSE(ResourceManagerTestingPeer::Generated(nor2.get()));
+ EXPECT_FALSE(nor2->IsWritten());
+
+ // Fetch its contents and make sure they match
+ EXPECT_EQ(contents, FetchOutputResource(nor2.get()));
+
+ // Try asynchronously too
+ VerifyContentsCallback callback(contents);
+ resource_manager_->ReadAsync(nor2.get(), &callback, &message_handler_);
+ callback.AssertCalled();
+
+ // Grab the URL for later
+ EXPECT_TRUE(nor2->HasValidUrl());
+ std::string url = nor2->url();
+ EXPECT_LT(0, url.length());
+
+ // Now expire it from the HTTP cache. Since we don't know its hash, we
+ // cannot fetch it (even though the contents are still in the filesystem).
+ mock_timer()->advance_ms(2 * origin_expire_time_ms);
+ {
+ scoped_ptr<OutputResource> nor3(
+ resource_manager_->CreateOutputResourceWithPath(
+ url_prefix_, filter_prefix, name, &kContentTypeText,
+ &message_handler_));
+ EXPECT_FALSE(resource_manager_->FetchOutputResource(
+ nor3.get(), NULL, NULL, &message_handler_,
+ ResourceManager::kNeverBlock));
+ // Now nor3 has locked the resource for creation.
+ // We must destruct nor3 in order to unlock it again, since we
+ // have no intention of creating it.
+ }
+
+ RemoveUrlPrefix(&url);
+ ASSERT_TRUE(full_name.Decode(url));
+ EXPECT_EQ(content_type, full_name.ContentTypeFromExt());
+ EXPECT_EQ(filter_prefix, full_name.id());
+ EXPECT_EQ(name, full_name.name());
+
+ // But with the URL (which contains the hash), we can retrieve it
+ // from the http_cache.
+ // first cut off the "http://mysite{,.0,.1}/" from the front.
+ scoped_ptr<OutputResource> nor4(
+ resource_manager_->CreateOutputResourceForFetch(nor->url()));
+ EXPECT_EQ(nor->url(), nor4->url());
+ EXPECT_EQ(contents, FetchOutputResource(nor4.get()));
+
+ // If it's evicted from the http_cache, we can also retrieve it from the
+ // filesystem.
+ lru_cache_->Clear();
+ nor4.reset(resource_manager_->CreateOutputResourceForFetch(nor->url()));
+ EXPECT_EQ(nor->url(), nor4->url());
+ EXPECT_EQ(contents, FetchOutputResource(nor4.get()));
+ // This also works asynchronously.
+ lru_cache_->Clear();
+ VerifyContentsCallback callback2(contents);
+ resource_manager_->ReadAsync(nor4.get(), &callback2, &message_handler_);
+ callback2.AssertCalled();
+ }
+};
+
+TEST_F(ResourceManagerTest, TestNamed) {
+ TestNamed();
+}
+
+TEST_F(ResourceManagerTest, TestOutputInputUrl) {
+ std::string url = Encode("http://example.com/dir/123/",
+ "jm", "0", "orig", "js");
+ scoped_ptr<OutputResource> output_resource(
+ resource_manager_->CreateOutputResourceForFetch(url));
+ ASSERT_TRUE(output_resource.get());
+ scoped_ptr<Resource> input_resource(
+ resource_manager_->CreateInputResourceFromOutputResource(
+ resource_manager_->url_escaper(),
+ output_resource.get(),
+ &options_,
+ &message_handler_));
+ EXPECT_EQ("http://example.com/dir/123/orig", input_resource->url());
+}
+
+// TODO(jmaessen): re-enable after sharding works again.
+// class ResourceManagerShardedTest : public ResourceManagerTest {
+// protected:
+// ResourceManagerShardedTest() {
+// url_prefix_ ="http://mysite.%d/";
+// num_shards_ = 2;
+// }
+// virtual void RemoveUrlPrefix(std::string* url) {
+// // One of these two should match.
+// StringPiece prefix(url->data(), 16);
+// EXPECT_TRUE((prefix == "http://mysite.0/") ||
+// (prefix == "http://mysite.1/")) << *url;
+// // "%d" -> "0" (or "1") loses 1 char.
+// url->erase(0, url_prefix_.length() - 1);
+// }
+// };
+
+// TODO(jmaessen): re-enable after sharding works again.
+// TEST_F(ResourceManagerShardedTest, TestNamed) {
+// TestNamed();
+// }
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/resource_manager_test_base.cc b/trunk/src/net/instaweb/rewriter/resource_manager_test_base.cc
new file mode 100644
index 0000000..68cbd8e
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/resource_manager_test_base.cc
@@ -0,0 +1,93 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+// Author: sligocki@google.com (Shawn Ligocki)
+
+#include "net/instaweb/rewriter/public/resource_manager_test_base.h"
+
+namespace net_instaweb {
+
+const char ResourceManagerTestBase::kTestData[] =
+ "/net/instaweb/rewriter/testdata/";
+
+// Test that a resource can be served from an new server that has not already
+// constructed it.
+void ResourceManagerTestBase::ServeResourceFromNewContext(
+ const std::string& resource_url,
+ RewriteOptions::Filter filter,
+ Hasher* hasher,
+ const StringPiece& expected_content) {
+
+ // New objects for the new server.
+ MemFileSystem other_file_system;
+ // other_lru_cache is owned by other_http_cache_.
+ LRUCache* other_lru_cache(new LRUCache(kCacheSize));
+ MockTimer* other_mock_timer = other_file_system.timer();
+ HTTPCache other_http_cache(other_lru_cache, other_mock_timer);
+ DomainLawyer other_domain_lawyer;
+ FileSystemLockManager other_lock_manager(
+ &other_file_system, other_mock_timer, &message_handler_);
+ WaitUrlAsyncFetcher wait_url_async_fetcher(&mock_url_fetcher_);
+ ResourceManager other_resource_manager(
+ file_prefix_, &other_file_system,
+ &filename_encoder_, &wait_url_async_fetcher, hasher,
+ &other_http_cache, &other_lock_manager);
+
+ SimpleStats stats;
+ RewriteDriver::Initialize(&stats);
+ other_resource_manager.set_statistics(&stats);
+
+ RewriteDriver other_rewrite_driver(&message_handler_, &other_file_system,
+ &wait_url_async_fetcher, other_options_);
+ other_rewrite_driver.SetResourceManager(&other_resource_manager);
+ other_options_.EnableFilter(filter);
+ other_rewrite_driver.AddFilters();
+
+ Variable* cached_resource_fetches =
+ stats.GetVariable(RewriteDriver::kResourceFetchesCached);
+ Variable* succeeded_filter_resource_fetches =
+ stats.GetVariable(RewriteDriver::kResourceFetchConstructSuccesses);
+ Variable* failed_filter_resource_fetches =
+ stats.GetVariable(RewriteDriver::kResourceFetchConstructFailures);
+
+ SimpleMetaData request_headers;
+ // TODO(sligocki): We should set default request headers.
+ SimpleMetaData response_headers;
+ std::string response_contents;
+ StringWriter response_writer(&response_contents);
+ DummyCallback callback(true);
+
+ // Check that we don't already have it in cache.
+ EXPECT_EQ(CacheInterface::kNotFound, other_http_cache.Query(resource_url));
+
+ // Initiate fetch.
+ EXPECT_EQ(true, other_rewrite_driver.FetchResource(
+ resource_url, request_headers, &response_headers, &response_writer,
+ &message_handler_, &callback));
+
+ // Content should not be set until we call the callback.
+ EXPECT_EQ(false, callback.done_);
+ EXPECT_EQ("", response_contents);
+
+ // After we call the callback, it should be correct.
+ wait_url_async_fetcher.CallCallbacks();
+ EXPECT_EQ(true, callback.done_);
+ EXPECT_EQ(expected_content, response_contents);
+ EXPECT_EQ(CacheInterface::kAvailable, other_http_cache.Query(resource_url));
+
+ // Check that stats say we took the construct resource path.
+ EXPECT_EQ(0, cached_resource_fetches->Get());
+ EXPECT_EQ(1, succeeded_filter_resource_fetches->Get());
+ EXPECT_EQ(0, failed_filter_resource_fetches->Get());
+}
+
+std::string ResourceManagerTestBase::Encode(
+ const StringPiece& path, const StringPiece& id, const StringPiece& hash,
+ const StringPiece& name, const StringPiece& ext) {
+ ResourceNamer namer;
+ namer.set_id(id);
+ namer.set_hash(hash);
+ namer.set_name(name);
+ namer.set_ext(ext);
+ return StrCat(path, namer.Encode());
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/resource_manager_testing_peer.h b/trunk/src/net/instaweb/rewriter/resource_manager_testing_peer.h
new file mode 100644
index 0000000..97d5011
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/resource_manager_testing_peer.h
@@ -0,0 +1,43 @@
+/**
+ * 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: abliss@google.com (Adam Bliss)
+
+#ifndef NET_INSTAWEB_REWRITER_RESOURCE_MANAGER_TESTING_PEER_H_
+#define NET_INSTAWEB_REWRITER_RESOURCE_MANAGER_TESTING_PEER_H_
+
+#include "net/instaweb/rewriter/public/output_resource.h"
+
+namespace net_instaweb {
+
+class ResourceManagerTestingPeer {
+ public:
+ ResourceManagerTestingPeer() { }
+
+ static bool HasHash(const OutputResource* resource) {
+ return resource->has_hash();
+ }
+ static bool Generated(const OutputResource* resource) {
+ return resource->generated();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResourceManagerTestingPeer);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_REWRITER_RESOURCE_MANAGER_TESTING_PEER_H_
diff --git a/trunk/src/net/instaweb/rewriter/resource_namer.cc b/trunk/src/net/instaweb/rewriter/resource_namer.cc
new file mode 100644
index 0000000..4903a23
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/resource_namer.cc
@@ -0,0 +1,186 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/rewriter/public/resource_namer.h"
+
+#include <vector>
+#include "net/instaweb/util/public/content_type.h"
+#include "net/instaweb/util/public/filename_encoder.h"
+#include "net/instaweb/util/public/string_hash.h"
+
+namespace net_instaweb {
+
+namespace {
+
+// The format of all resource names is:
+//
+// ORIGINAL_NAME.ID.pagespeed.HASH.EXT
+//
+// "pagespeed" is what we'll call the system ID. Rationale:
+// 1. Any abbreviation of this will not be well known, e.g.
+// ps, mps (mod page speed), psa (page speed automatic)
+// and early reports from users indicate confusion over
+// the gibberish names in our resources.
+// 2. "pagespeed" is the family of products now, not just the
+// firebug plug in. Page Speed Automatic is the proper name for
+// the rewriting technology but it's longer, and "pagespeed" solves the
+// "WTF is this garbage in my URL" problem.
+// 3. "mod_pagespeed" is slightly longer if/when this technology
+// is ported to other servers then the "mod_" is less relevant.
+//
+// If you change this, or the structure of the encoded string,
+// you will also need to change:
+//
+// apache/install/system_test.sh
+//
+// Plus a few constants in _test.cc files.
+
+static const char kSystemId[] = "pagespeed";
+static const int kNumSegments = 5;
+static const char kSeparatorString[] = ".";
+static const char kSeparatorChar = kSeparatorString[0];
+
+bool TokenizeSegmentFromRight(StringPiece* src, std::string* dest) {
+ StringPiece::size_type pos = src->rfind(kSeparatorChar);
+ if (pos == StringPiece::npos) {
+ return false;
+ }
+ src->substr(pos + 1).CopyToString(dest);
+ *src = src->substr(0, pos);
+ return true;
+}
+
+} // namespace
+
+const int ResourceNamer::kOverhead = 4 + sizeof(kSystemId) - 1;
+
+bool ResourceNamer::Decode(const StringPiece& encoded_string) {
+ StringPiece src(encoded_string);
+ std::string system_id;
+ if (TokenizeSegmentFromRight(&src, &ext_) &&
+ TokenizeSegmentFromRight(&src, &hash_) &&
+ TokenizeSegmentFromRight(&src, &id_) &&
+ TokenizeSegmentFromRight(&src, &system_id) &&
+ (system_id == kSystemId)) {
+ src.CopyToString(&name_);
+ return true;
+ }
+ return LegacyDecode(encoded_string);
+}
+
+// TODO(jmarantz): validate that the 'id' is one of the filters that
+// were implemented as of Nov 2010. Also validate that the hash
+// code is a 32-char hex number.
+bool ResourceNamer::LegacyDecode(const StringPiece& encoded_string) {
+ bool ret = false;
+ // First check that this URL has a known extension type
+ if (NameExtensionToContentType(encoded_string) != NULL) {
+ std::vector<StringPiece> names;
+ SplitStringPieceToVector(encoded_string, kSeparatorString, &names, true);
+ if (names.size() == 4) {
+ names[1].CopyToString(&hash_);
+
+ // The legacy hash codes were all either 1-character (for tests) or
+ // 32 characters, all in hex.
+ if ((hash_.size() != 1) && (hash_.size() != 32)) {
+ return false;
+ }
+ for (int i = 0, n = hash_.size(); i < n; ++i) {
+ char ch = hash_[i];
+ if (!isdigit(ch)) {
+ ch = toupper(ch);
+ if ((ch < 'A') || (ch > 'F')) {
+ return false;
+ }
+ }
+ }
+
+ names[0].CopyToString(&id_);
+ names[2].CopyToString(&name_);
+ names[3].CopyToString(&ext_);
+ ret = true;
+ }
+ }
+ return ret;
+}
+
+// This is used for legacy compatibility as we transition to the grand new
+// world.
+std::string ResourceNamer::InternalEncode() const {
+ return StrCat(name_, kSeparatorString,
+ kSystemId, kSeparatorString,
+ id_, kSeparatorString,
+ StrCat(hash_, kSeparatorString, ext_));
+}
+
+// The current encoding assumes there are no dots in any of the components.
+// This restriction may be relaxed in the future, but check it aggressively
+// for now.
+std::string ResourceNamer::Encode() const {
+ CHECK_EQ(StringPiece::npos, id_.find(kSeparatorChar));
+ CHECK(!hash_.empty());
+ CHECK_EQ(StringPiece::npos, hash_.find(kSeparatorChar));
+ CHECK_EQ(StringPiece::npos, ext_.find(kSeparatorChar));
+ return InternalEncode();
+}
+
+std::string ResourceNamer::EncodeIdName() const {
+ CHECK(id_.find(kSeparatorChar) == StringPiece::npos);
+ return StrCat(id_, kSeparatorString, name_);
+}
+
+// Note: there is no need at this time to decode the name key.
+
+std::string ResourceNamer::EncodeHashExt() const {
+ CHECK_EQ(StringPiece::npos, hash_.find(kSeparatorChar));
+ CHECK_EQ(StringPiece::npos, ext_.find(kSeparatorChar));
+ return StrCat(hash_, kSeparatorString, ext_);
+}
+
+bool ResourceNamer::DecodeHashExt(const StringPiece& encoded_hash_ext) {
+ std::vector<StringPiece> names;
+ SplitStringPieceToVector(encoded_hash_ext, kSeparatorString, &names, true);
+ bool ret = (names.size() == 2);
+ if (ret) {
+ names[0].CopyToString(&hash_);
+ names[1].CopyToString(&ext_);
+ }
+ return ret;
+}
+
+size_t ResourceNamer::Hash() const {
+ size_t id_hash = HashString( id_.data(), id_.size());
+ size_t name_hash = HashString(name_.data(), name_.size());
+ size_t hash_hash = HashString(hash_.data(), hash_.size());
+ size_t ext_hash = HashString( ext_.data(), ext_.size());
+ return
+ JoinHash(JoinHash(JoinHash(id_hash, name_hash), hash_hash), ext_hash);
+}
+
+const ContentType* ResourceNamer::ContentTypeFromExt() const {
+ return NameExtensionToContentType(StrCat(".", ext_));
+}
+
+void ResourceNamer::CopyFrom(const ResourceNamer& other) {
+ other.id().CopyToString(&id_);
+ other.name().CopyToString(&name_);
+ other.hash().CopyToString(&hash_);
+ other.ext().CopyToString(&ext_);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/resource_namer_test.cc b/trunk/src/net/instaweb/rewriter/resource_namer_test.cc
new file mode 100644
index 0000000..74205ad
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/resource_namer_test.cc
@@ -0,0 +1,85 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/rewriter/public/resource_manager.h"
+#include "net/instaweb/rewriter/public/resource_namer.h"
+#include "net/instaweb/util/public/gtest.h"
+
+namespace net_instaweb {
+
+namespace {
+
+class ResourceNamerTest : public testing::Test {
+ protected:
+ ResourceNamerTest() { }
+
+ ResourceNamer full_name_;
+};
+
+TEST_F(ResourceNamerTest, TestEncode) {
+ // Stand up a minimal resource manager that only has the
+ // resources we should need to encode.
+ full_name_.set_id("id");
+ full_name_.set_name("name.ext.as.many.as.I.like");
+ full_name_.set_hash("hash");
+ full_name_.set_ext("ext");
+ EXPECT_EQ(std::string("name.ext.as.many.as.I.like.pagespeed.id.hash.ext"),
+ full_name_.Encode());
+ EXPECT_EQ(std::string("id.name.ext.as.many.as.I.like"),
+ full_name_.EncodeIdName());
+ EXPECT_EQ(std::string("hash.ext"), full_name_.EncodeHashExt());
+}
+
+TEST_F(ResourceNamerTest, TestDecode) {
+ EXPECT_TRUE(full_name_.Decode(
+ "name.ext.as.many.as.I.like.pagespeed.id.hash.ext"));
+ EXPECT_EQ("id", full_name_.id());
+ EXPECT_EQ("name.ext.as.many.as.I.like", full_name_.name());
+ EXPECT_EQ("hash", full_name_.hash());
+ EXPECT_EQ("ext", full_name_.ext());
+}
+
+TEST_F(ResourceNamerTest, TestDecodeTooMany) {
+ EXPECT_TRUE(full_name_.Decode("name.extra_dot.pagespeed.id.hash.ext"));
+ EXPECT_FALSE(full_name_.DecodeHashExt("id.hash.ext"));
+}
+
+TEST_F(ResourceNamerTest, TestDecodeNotEnough) {
+ EXPECT_FALSE(full_name_.Decode("id.name.hash"));
+ EXPECT_FALSE(full_name_.DecodeHashExt("ext"));
+}
+
+TEST_F(ResourceNamerTest, TestDecodeHashExt) {
+ EXPECT_TRUE(full_name_.DecodeHashExt("hash.ext"));
+ EXPECT_EQ("", full_name_.id());
+ EXPECT_EQ("", full_name_.name());
+ EXPECT_EQ("hash", full_name_.hash());
+ EXPECT_EQ("ext", full_name_.ext());
+}
+
+TEST_F(ResourceNamerTest, TestLegacyDecode) {
+ EXPECT_TRUE(full_name_.Decode("id.0123456789abcdef0123456789ABCDEF.name.js"));
+ EXPECT_EQ("id", full_name_.id());
+ EXPECT_EQ("name", full_name_.name());
+ EXPECT_EQ("0123456789abcdef0123456789ABCDEF", full_name_.hash());
+ EXPECT_EQ("js", full_name_.ext());
+}
+
+} // namespace
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/resource_tag_scanner.cc b/trunk/src/net/instaweb/rewriter/resource_tag_scanner.cc
new file mode 100644
index 0000000..2f6cbe9
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/resource_tag_scanner.cc
@@ -0,0 +1,42 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/rewriter/public/resource_tag_scanner.h"
+
+namespace net_instaweb {
+
+// Finds resource references.
+ResourceTagScanner::ResourceTagScanner(HtmlParse* html_parse)
+ : css_tag_scanner_(html_parse),
+ img_tag_scanner_(html_parse),
+ script_tag_scanner_(html_parse) {
+}
+
+HtmlElement::Attribute* ResourceTagScanner::ScanElement(HtmlElement* element) {
+ HtmlElement::Attribute* attr = NULL;
+ const char* media;
+ if (!css_tag_scanner_.ParseCssElement(element, &attr, &media)) {
+ attr = img_tag_scanner_.ParseImgElement(element);
+ if (attr == NULL) {
+ script_tag_scanner_.ParseScriptElement(element, &attr);
+ }
+ }
+ return attr;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/resource_tag_scanner_test.cc b/trunk/src/net/instaweb/rewriter/resource_tag_scanner_test.cc
new file mode 100644
index 0000000..e99c464
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/resource_tag_scanner_test.cc
@@ -0,0 +1,80 @@
+/**
+ * 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: mdsteele@google.com (Matthew D. Steele)
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/empty_html_filter.h"
+#include "net/instaweb/htmlparse/public/html_parse_test_base.h"
+#include "net/instaweb/rewriter/public/resource_tag_scanner.h"
+#include "net/instaweb/util/public/gtest.h"
+
+namespace net_instaweb {
+
+class ResourceTagScannerTest : public HtmlParseTestBase {
+ protected:
+ ResourceTagScannerTest() {
+ }
+
+ virtual bool AddBody() const { return true; }
+
+ // Helper class to collect all external resources.
+ class ResourceCollector : public EmptyHtmlFilter {
+ public:
+ ResourceCollector(HtmlParse* html_parse, StringVector* resources)
+ : resources_(resources),
+ resource_tag_scanner_(html_parse) {
+ }
+
+ virtual void StartElement(HtmlElement* element) {
+ HtmlElement::Attribute* src =
+ resource_tag_scanner_.ScanElement(element);
+ if (src != NULL) {
+ resources_->push_back(src->value());
+ }
+ }
+
+ virtual const char* Name() const { return "ResourceCollector"; }
+
+ private:
+ StringVector* resources_;
+ ResourceTagScanner resource_tag_scanner_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResourceCollector);
+ };
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResourceTagScannerTest);
+};
+
+TEST_F(ResourceTagScannerTest, FindTags) {
+ StringVector resources;
+ ResourceCollector collector(&html_parse_, &resources);
+ html_parse_.AddFilter(&collector);
+ ValidateNoChanges(
+ "simple_script",
+ "<script src='myscript.js'></script>\n"
+ "<img src=\"image.jpg\"/>\n"
+ "<link rel=\"stylesheet\" type=\"text/css\" href=\"nomedia.css\">\n"
+ "<link rel=stylesheet type=text/css href=media.css media=print>");
+ ASSERT_EQ(static_cast<size_t>(4), resources.size());
+ EXPECT_EQ(std::string("myscript.js"), resources[0]);
+ EXPECT_EQ(std::string("image.jpg"), resources[1]);
+ EXPECT_EQ(std::string("nomedia.css"), resources[2]);
+ EXPECT_EQ(std::string("media.css"), resources[3]);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/rewrite_driver.cc b/trunk/src/net/instaweb/rewriter/rewrite_driver.cc
new file mode 100644
index 0000000..d385207
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/rewrite_driver.cc
@@ -0,0 +1,499 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/rewriter/public/rewrite_driver.h"
+
+#include <vector>
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/htmlparse/public/html_writer_filter.h"
+#include "net/instaweb/rewriter/public/add_head_filter.h"
+#include "net/instaweb/rewriter/public/add_instrumentation_filter.h"
+#include "net/instaweb/rewriter/public/base_tag_filter.h"
+#include "net/instaweb/rewriter/public/cache_extender.h"
+#include "net/instaweb/rewriter/public/collapse_whitespace_filter.h"
+#include "net/instaweb/rewriter/public/css_combine_filter.h"
+#include "net/instaweb/rewriter/public/css_filter.h"
+#include "net/instaweb/rewriter/public/css_inline_filter.h"
+#include "net/instaweb/rewriter/public/css_move_to_head_filter.h"
+#include "net/instaweb/rewriter/public/css_outline_filter.h"
+#include "net/instaweb/rewriter/public/elide_attributes_filter.h"
+#include "net/instaweb/rewriter/public/html_attribute_quote_removal.h"
+#include "net/instaweb/rewriter/public/img_rewrite_filter.h"
+#include "net/instaweb/rewriter/public/javascript_filter.h"
+#include "net/instaweb/rewriter/public/js_inline_filter.h"
+#include "net/instaweb/rewriter/public/js_outline_filter.h"
+#include "net/instaweb/rewriter/public/output_resource.h"
+#include "net/instaweb/rewriter/public/remove_comments_filter.h"
+#include "net/instaweb/rewriter/public/resource.h"
+#include "net/instaweb/rewriter/public/resource_namer.h"
+#include "net/instaweb/rewriter/public/resource_manager.h"
+#include "net/instaweb/rewriter/public/strip_scripts_filter.h"
+#include "net/instaweb/rewriter/public/url_left_trim_filter.h"
+#include "net/instaweb/util/public/stl_util.h"
+#include "net/instaweb/util/public/content_type.h"
+#include "net/instaweb/util/public/filename_encoder.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/statistics.h"
+#include "net/instaweb/util/public/url_async_fetcher.h"
+
+namespace net_instaweb {
+
+// RewriteFilter prefixes
+const char RewriteDriver::kCssCombinerId[] = "cc";
+const char RewriteDriver::kCssFilterId[] = "cf";
+const char RewriteDriver::kCacheExtenderId[] = "ce";
+const char RewriteDriver::kImageCompressionId[] = "ic";
+const char RewriteDriver::kJavascriptMinId[] = "jm";
+
+// TODO(jmarantz): Simplify the interface so we can just use
+// asynchronous fetchers, employing FakeUrlAsyncFetcher as needed
+// for running functional regression-tests where we don't mind blocking
+// behavior.
+RewriteDriver::RewriteDriver(MessageHandler* message_handler,
+ FileSystem* file_system,
+ UrlAsyncFetcher* url_async_fetcher,
+ const RewriteOptions& options)
+ : html_parse_(message_handler),
+ file_system_(file_system),
+ url_async_fetcher_(url_async_fetcher),
+ resource_manager_(NULL),
+ add_instrumentation_filter_(NULL),
+ cached_resource_fetches_(NULL),
+ succeeded_filter_resource_fetches_(NULL),
+ failed_filter_resource_fetches_(NULL),
+ options_(options) {
+}
+
+RewriteDriver::~RewriteDriver() {
+ STLDeleteElements(&filters_);
+}
+
+const char* RewriteDriver::kPassThroughRequestAttributes[3] = {
+ HttpAttributes::kIfModifiedSince,
+ HttpAttributes::kReferer,
+ HttpAttributes::kUserAgent
+};
+
+// names for Statistics variables.
+const char RewriteDriver::kResourceFetchesCached[] = "resource_fetches_cached";
+const char RewriteDriver::kResourceFetchConstructSuccesses[] =
+ "resource_fetch_construct_successes";
+const char RewriteDriver::kResourceFetchConstructFailures[] =
+ "resource_fetch_construct_failures";
+
+void RewriteDriver::Initialize(Statistics* statistics) {
+ if (statistics != NULL) {
+ statistics->AddVariable(kResourceFetchesCached);
+ statistics->AddVariable(kResourceFetchConstructSuccesses);
+ statistics->AddVariable(kResourceFetchConstructFailures);
+
+ // TODO(jmarantz): Make all of these work with null statistics so that
+ // they could mdo other required static initializations if desired
+ // without having to edit code to this method.
+ AddInstrumentationFilter::Initialize(statistics);
+ CacheExtender::Initialize(statistics);
+ CssCombineFilter::Initialize(statistics);
+ CssMoveToHeadFilter::Initialize(statistics);
+ ImgRewriteFilter::Initialize(statistics);
+ JavascriptFilter::Initialize(statistics);
+ ResourceManager::Initialize(statistics);
+ UrlLeftTrimFilter::Initialize(statistics);
+ }
+ CssFilter::Initialize(statistics);
+}
+
+void RewriteDriver::SetResourceManager(ResourceManager* resource_manager) {
+ resource_manager_ = resource_manager;
+ html_parse_.set_timer(resource_manager->timer());
+}
+
+// If flag starts with key (a string ending in "="), call m on the remainder of
+// flag (the piece after the "="). Always returns true if the key matched; m is
+// free to complain about invalid input using html_parse_->message_handler().
+bool RewriteDriver::ParseKeyString(const StringPiece& key, SetStringMethod m,
+ const std::string& flag) {
+ if (flag.rfind(key.data(), 0, key.size()) == 0) {
+ StringPiece sp(flag);
+ (this->*m)(flag.substr(key.size()));
+ return true;
+ } else {
+ return false;
+ }
+}
+
+// If flag starts with key (a string ending in "="), convert rest of flag after
+// the "=" to Int64, and call m on it. Always returns true if the key matched;
+// m is free to complain about invalid input using
+// html_parse_->message_handler() (failure to parse a number does so and never
+// calls m).
+bool RewriteDriver::ParseKeyInt64(const StringPiece& key, SetInt64Method m,
+ const std::string& flag) {
+ if (flag.rfind(key.data(), 0, key.size()) == 0) {
+ std::string str_value = flag.substr(key.size());
+ int64 value;
+ if (StringToInt64(str_value, &value)) {
+ (this->*m)(value);
+ } else {
+ html_parse_.message_handler()->Message(
+ kError, "'%s': ignoring value (should have been int64) after %s",
+ flag.c_str(), key.as_string().c_str());
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void RewriteDriver::AddFilters() {
+ CHECK(html_writer_filter_ == NULL);
+
+ // Add the rewriting filters to the map unconditionally -- we may
+ // need the to process resource requests due to a query-specific
+ // 'rewriters' specification. We still use the passed-in options
+ // to determine whether they get added to the html parse filter chain.
+ RegisterRewriteFilter(new CssCombineFilter(this, kCssCombinerId));
+ RegisterRewriteFilter(new CssFilter(this, kCssFilterId));
+ RegisterRewriteFilter(new JavascriptFilter(this, kJavascriptMinId));
+ RegisterRewriteFilter(
+ new ImgRewriteFilter(
+ this,
+ options_.Enabled(RewriteOptions::kDebugLogImgTags),
+ options_.Enabled(RewriteOptions::kInsertImgDimensions),
+ kImageCompressionId,
+ options_.img_inline_max_bytes(),
+ options_.img_max_rewrites_at_once()));
+ RegisterRewriteFilter(new CacheExtender(this, kCacheExtenderId));
+
+ // This function defines the order that filters are run. We document
+ // in pagespeed.conf.template that the order specified in the conf
+ // file does not matter, but we give the filters there in the order
+ // they are actually applied, for the benefit of the understanding
+ // of the site owner. So if you change that here, change it in
+ // install/common/pagespeed.conf.template as well.
+
+ // Now process boolean options, which may include propagating non-boolean
+ // and boolean parameter settings to filters.
+ if (options_.Enabled(RewriteOptions::kAddHead) ||
+ options_.Enabled(RewriteOptions::kCombineHeads) ||
+ options_.Enabled(RewriteOptions::kAddBaseTag) ||
+ options_.Enabled(RewriteOptions::kMoveCssToHead) ||
+ options_.Enabled(RewriteOptions::kAddInstrumentation)) {
+ // Adds a filter that adds a 'head' section to html documents if
+ // none found prior to the body.
+ AddFilter(new AddHeadFilter(
+ &html_parse_, options_.Enabled(RewriteOptions::kCombineHeads)));
+ }
+ if (options_.Enabled(RewriteOptions::kAddBaseTag)) {
+ // Adds a filter that establishes a base tag for the HTML document.
+ // This is required when implementing a proxy server. The base
+ // tag used can be changed for every request with SetBaseUrl.
+ // Adding the base-tag filter will establish the AddHeadFilter
+ // if needed.
+ base_tag_filter_.reset(new BaseTagFilter(&html_parse_));
+ html_parse_.AddFilter(base_tag_filter_.get());
+ }
+ if (options_.Enabled(RewriteOptions::kStripScripts)) {
+ // Experimental filter that blindly scripts all strips from a page.
+ AddFilter(new StripScriptsFilter(&html_parse_));
+ }
+ if (options_.Enabled(RewriteOptions::kOutlineCss)) {
+ // Cut out inlined styles and make them into external resources.
+ // This can only be called once and requires a resource_manager to be set.
+ CHECK(resource_manager_ != NULL);
+ CssOutlineFilter* css_outline_filter = new CssOutlineFilter(this);
+ AddFilter(css_outline_filter);
+ }
+ if (options_.Enabled(RewriteOptions::kOutlineJavascript)) {
+ // Cut out inlined scripts and make them into external resources.
+ // This can only be called once and requires a resource_manager to be set.
+ CHECK(resource_manager_ != NULL);
+ JsOutlineFilter* js_outline_filter = new JsOutlineFilter(this);
+ AddFilter(js_outline_filter);
+ }
+ if (options_.Enabled(RewriteOptions::kMoveCssToHead)) {
+ // It's good to move CSS links to the head prior to running CSS combine,
+ // which only combines CSS links that are already in the head.
+ AddFilter(new CssMoveToHeadFilter(&html_parse_, statistics()));
+ }
+ if (options_.Enabled(RewriteOptions::kCombineCss)) {
+ // Combine external CSS resources after we've outlined them.
+ // CSS files in html document. This can only be called
+ // once and requires a resource_manager to be set.
+ EnableRewriteFilter(kCssCombinerId);
+ }
+ if (options_.Enabled(RewriteOptions::kRewriteCss)) {
+ EnableRewriteFilter(kCssFilterId);
+ }
+ if (options_.Enabled(RewriteOptions::kRewriteJavascript)) {
+ // Rewrite (minify etc.) JavaScript code to reduce time to first
+ // interaction.
+ EnableRewriteFilter(kJavascriptMinId);
+ }
+ if (options_.Enabled(RewriteOptions::kInlineCss)) {
+ // Inline small CSS files. Give CssCombineFilter and CSS minification a
+ // chance to run before we decide what counts as "small".
+ CHECK(resource_manager_ != NULL);
+ AddFilter(new CssInlineFilter(this));
+ }
+ if (options_.Enabled(RewriteOptions::kInlineJavascript)) {
+ // Inline small Javascript files. Give JS minification a chance to run
+ // before we decide what counts as "small".
+ CHECK(resource_manager_ != NULL);
+ AddFilter(new JsInlineFilter(this));
+ }
+ if (options_.Enabled(RewriteOptions::kRewriteImages)) {
+ EnableRewriteFilter(kImageCompressionId);
+ }
+ if (options_.Enabled(RewriteOptions::kRemoveComments)) {
+ AddFilter(new RemoveCommentsFilter(&html_parse_));
+ }
+ if (options_.Enabled(RewriteOptions::kCollapseWhitespace)) {
+ // Remove excess whitespace in HTML
+ AddFilter(new CollapseWhitespaceFilter(&html_parse_));
+ }
+ if (options_.Enabled(RewriteOptions::kElideAttributes)) {
+ // Remove HTML element attribute values where
+ // http://www.w3.org/TR/html4/loose.dtd says that the name is all
+ // that's necessary
+ AddFilter(new ElideAttributesFilter(&html_parse_));
+ }
+ if (options_.Enabled(RewriteOptions::kExtendCache)) {
+ // Extend the cache lifetime of resources.
+ EnableRewriteFilter(kCacheExtenderId);
+ }
+ if (options_.Enabled(RewriteOptions::kLeftTrimUrls)) {
+ // Trim extraneous prefixes from urls in attribute values.
+ // Happens before RemoveQuotes but after everything else. Note:
+ // we Must left trim urls BEFORE quote removal.
+ left_trim_filter_.reset(
+ new UrlLeftTrimFilter(&html_parse_,
+ resource_manager_->statistics()));
+ html_parse_.AddFilter(left_trim_filter_.get());
+ }
+ if (options_.Enabled(RewriteOptions::kRemoveQuotes)) {
+ // Remove extraneous quotes from html attributes. Does this save
+ // enough bytes to be worth it after compression? If we do it
+ // everywhere it seems to give a small savings.
+ AddFilter(new HtmlAttributeQuoteRemoval(&html_parse_));
+ }
+ if (options_.Enabled(RewriteOptions::kAddInstrumentation)) {
+ // Inject javascript to instrument loading-time.
+ add_instrumentation_filter_ =
+ new AddInstrumentationFilter(&html_parse_,
+ options_.beacon_url(),
+ resource_manager_->statistics());
+ AddFilter(add_instrumentation_filter_);
+ }
+ // NOTE(abliss): Adding a new filter? Does it export any statistics? If it
+ // doesn't, it probably should. If it does, be sure to add it to the
+ // Initialize() function above or it will break under Apache!
+}
+
+void RewriteDriver::SetBaseUrl(const StringPiece& base) {
+ if (base_tag_filter_ != NULL) {
+ base_tag_filter_->set_base_url(base);
+ }
+}
+
+void RewriteDriver::AddFilter(HtmlFilter* filter) {
+ filters_.push_back(filter);
+ html_parse_.AddFilter(filter);
+}
+
+void RewriteDriver::EnableRewriteFilter(const char* id) {
+ RewriteFilter* filter = resource_filter_map_[id];
+ CHECK(filter);
+ html_parse_.AddFilter(filter);
+}
+
+void RewriteDriver::RegisterRewriteFilter(RewriteFilter* filter) {
+ // Track resource_fetches if we care about statistics. Note that
+ // the statistics are owned by the resource manager, which generally
+ // should be set up prior to the rewrite_driver.
+ //
+ // TODO(sligocki): It'd be nice to get this into the constructor.
+ Statistics* stats = statistics();
+ if ((stats != NULL) && (cached_resource_fetches_ == NULL)) {
+ cached_resource_fetches_ = stats->GetVariable(kResourceFetchesCached);
+ succeeded_filter_resource_fetches_ =
+ stats->GetVariable(kResourceFetchConstructSuccesses);
+ failed_filter_resource_fetches_ =
+ stats->GetVariable(kResourceFetchConstructFailures);
+ }
+ resource_filter_map_[filter->id()] = filter;
+ filters_.push_back(filter);
+}
+
+void RewriteDriver::SetWriter(Writer* writer) {
+ if (html_writer_filter_ == NULL) {
+ html_writer_filter_.reset(new HtmlWriterFilter(&html_parse_));
+ html_parse_.AddFilter(html_writer_filter_.get());
+ }
+ html_writer_filter_->set_writer(writer);
+}
+
+Statistics* RewriteDriver::statistics() const {
+ return (resource_manager_ == NULL) ? NULL : resource_manager_->statistics();
+}
+
+namespace {
+
+// Wraps an async fetcher callback, in order to delete the output
+// resource.
+class ResourceDeleterCallback : public UrlAsyncFetcher::Callback {
+ public:
+ ResourceDeleterCallback(OutputResource* output_resource,
+ UrlAsyncFetcher::Callback* callback,
+ HTTPCache* http_cache,
+ MessageHandler* message_handler)
+ : output_resource_(output_resource),
+ callback_(callback),
+ http_cache_(http_cache),
+ message_handler_(message_handler) {
+ }
+
+ virtual void Done(bool status) {
+ callback_->Done(status);
+ // Filters should generally write their output to the OutputResource,
+ // in which case when they are done we can insert it into the cache.
+ // However, not all filters do this yet notably img_rewrite_filter,
+ // so check for ContentsValid().
+ if (status && output_resource_->ContentsValid()) {
+ http_cache_->Put(output_resource_->url(),
+ *output_resource_->metadata(),
+ output_resource_->contents(),
+ message_handler_);
+ }
+ delete this;
+ }
+
+ private:
+ scoped_ptr<OutputResource> output_resource_;
+ UrlAsyncFetcher::Callback* callback_;
+ HTTPCache* http_cache_;
+ MessageHandler* message_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResourceDeleterCallback);
+};
+
+} // namespace
+
+OutputResource* RewriteDriver::DecodeOutputResource(
+ const StringPiece& url,
+ RewriteFilter** filter) {
+ // Note that this does permission checking and parsing of the url, but doesn't
+ // actually fetch any data until we specifically ask it to.
+ OutputResource* output_resource =
+ resource_manager_->CreateOutputResourceForFetch(url);
+
+ // If the resource name was ill-formed or unrecognized, we reject the request
+ // so it can be passed along.
+ if (output_resource != NULL) {
+ // For now let's reject as mal-formed if the id string is not
+ // in the rewrite drivers.
+ // TODO(jmarantz): it might be better to 'handle' requests with known
+ // IDs even if that filter is not enabled, rather rejecting the request.
+ // TODO(jmarantz): consider query-specific rewrites. We may need to
+ // enable filters for this driver based on the referrer.
+ StringPiece id = output_resource->filter_prefix();
+ StringFilterMap::iterator p = resource_filter_map_.find(
+ std::string(id.data(), id.size()));
+
+ // OutlineFilter is special because it's not a RewriteFilter -- it's
+ // just an HtmlFilter, but it does encode rewritten resources that
+ // must be served from the cache.
+ //
+ // TODO(jmarantz): figure out a better way to refactor this.
+ // TODO(jmarantz): add a unit-test to show serving outline-filter resources.
+ if (p != resource_filter_map_.end()) {
+ *filter = p->second;
+ } else if (((id != CssOutlineFilter::kFilterId) &&
+ (id != JsOutlineFilter::kFilterId)) ||
+ (output_resource->type() == NULL)) {
+ delete output_resource;
+ output_resource = NULL;
+ }
+ }
+ return output_resource;
+}
+
+bool RewriteDriver::FetchResource(
+ const StringPiece& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* writer,
+ MessageHandler* message_handler,
+ UrlAsyncFetcher::Callback* callback) {
+ bool queued = false;
+ bool handled = false;
+
+ // Note that this does permission checking and parsing of the url, but doesn't
+ // actually fetch any data until we specifically ask it to.
+ RewriteFilter* filter = NULL;
+ OutputResource* output_resource = DecodeOutputResource(url, &filter);
+
+ if (output_resource != NULL) {
+ handled = true;
+ callback = new ResourceDeleterCallback(output_resource, callback,
+ resource_manager_->http_cache(),
+ message_handler);
+ // None of our resources ever change -- the hash of the content is embedded
+ // in the filename. This is why we serve them with very long cache
+ // lifetimes. However, when the user presses Reload, the browser may
+ // attempt to validate that the cached copy is still fresh by sending a GET
+ // with an If-Modified-Since header. If this header is present, we should
+ // return a 304 Not Modified, since any representation of the resource
+ // that's in the browser's cache must be correct.
+ CharStarVector values;
+ if (request_headers.Lookup(HttpAttributes::kIfModifiedSince, &values)) {
+ response_headers->SetStatusAndReason(HttpStatus::kNotModified);
+ callback->Done(true);
+ queued = true;
+ } else if (resource_manager_->FetchOutputResource(
+ output_resource, writer, response_headers, message_handler,
+ ResourceManager::kMayBlock)) {
+ callback->Done(true);
+ queued = true;
+ if (cached_resource_fetches_ != NULL) {
+ cached_resource_fetches_->Add(1);
+ }
+ } else if (filter != NULL) {
+ queued = filter->Fetch(output_resource, writer,
+ request_headers, response_headers,
+ message_handler, callback);
+ if (queued) {
+ if (succeeded_filter_resource_fetches_ != NULL) {
+ succeeded_filter_resource_fetches_->Add(1);
+ }
+ } else {
+ if (failed_filter_resource_fetches_ != NULL) {
+ failed_filter_resource_fetches_->Add(1);
+ }
+ }
+ }
+ }
+ if (!queued && handled) {
+ // If we got here, we were asked to decode a resource for which we have
+ // no filter.
+ callback->Done(false);
+ }
+ return handled;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/rewrite_driver_factory.cc b/trunk/src/net/instaweb/rewriter/rewrite_driver_factory.cc
new file mode 100644
index 0000000..708c492
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/rewrite_driver_factory.cc
@@ -0,0 +1,390 @@
+/**
+ * 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/rewrite_driver_factory.h"
+
+#include "base/logging.h"
+#include "net/instaweb/rewriter/public/resource_manager.h"
+#include "net/instaweb/rewriter/public/rewrite_driver.h"
+#include "net/instaweb/util/public/abstract_mutex.h"
+#include "net/instaweb/util/public/cache_url_async_fetcher.h"
+#include "net/instaweb/util/public/cache_url_fetcher.h"
+#include "net/instaweb/util/public/fake_url_async_fetcher.h"
+#include "net/instaweb/util/public/filename_encoder.h"
+#include "net/instaweb/util/public/file_system.h"
+#include "net/instaweb/util/public/file_system_lock_manager.h"
+#include "net/instaweb/util/public/hasher.h"
+#include "net/instaweb/util/public/http_cache.h"
+#include "net/instaweb/util/public/http_dump_url_fetcher.h"
+#include "net/instaweb/util/public/http_dump_url_writer.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/named_lock_manager.h"
+#include "net/instaweb/util/public/statistics.h"
+#include "net/instaweb/util/public/stl_util.h"
+#include "net/instaweb/util/public/timer.h"
+
+namespace net_instaweb {
+
+const char kInstawebResource404Count[] = "resource_404_count";
+const char kInstawebSlurp404Count[] = "slurp_404_count";
+
+RewriteDriverFactory::RewriteDriverFactory()
+ : url_fetcher_(NULL),
+ url_async_fetcher_(NULL),
+ html_parse_(NULL),
+ filename_prefix_(""),
+ force_caching_(false),
+ slurp_read_only_(false),
+ slurp_print_urls_(false),
+ resource_404_count_(NULL),
+ slurp_404_count_(NULL) {
+}
+
+RewriteDriverFactory::~RewriteDriverFactory() {
+ ShutDown();
+}
+
+void RewriteDriverFactory::set_html_parse_message_handler(
+ MessageHandler* message_handler) {
+ html_parse_message_handler_.reset(message_handler);
+}
+
+void RewriteDriverFactory::set_message_handler(
+ MessageHandler* message_handler) {
+ message_handler_.reset(message_handler);
+}
+
+bool RewriteDriverFactory::FetchersComputed() const {
+ return (url_fetcher_ != NULL) || (url_async_fetcher_ != NULL);
+}
+
+void RewriteDriverFactory::set_slurp_directory(const StringPiece& dir) {
+ CHECK(!FetchersComputed())
+ << "Cannot call set_slurp_directory "
+ << " after ComputeUrl*Fetcher has been called";
+ dir.CopyToString(&slurp_directory_);
+}
+
+void RewriteDriverFactory::set_slurp_read_only(bool read_only) {
+ CHECK(!FetchersComputed())
+ << "Cannot call set_slurp_read_only "
+ << " after ComputeUrl*Fetcher has been called";
+ slurp_read_only_ = read_only;
+}
+
+void RewriteDriverFactory::set_slurp_print_urls(bool print_urls) {
+ CHECK(!FetchersComputed())
+ << "Cannot call set_slurp_print_urls "
+ << " after ComputeUrl*Fetcher has been called";
+ slurp_print_urls_ = print_urls;
+}
+
+void RewriteDriverFactory::set_file_system(FileSystem* file_system) {
+ file_system_.reset(file_system);
+}
+
+// TODO(jmarantz): Change this to set_base_url_fetcher
+void RewriteDriverFactory::set_base_url_fetcher(UrlFetcher* url_fetcher) {
+ CHECK(!FetchersComputed())
+ << "Cannot call set_base_url_fetcher "
+ << " after ComputeUrl*Fetcher has been called";
+ CHECK(base_url_async_fetcher_.get() == NULL)
+ << "Only call one of set_base_url_fetcher and set_base_url_async_fetcher";
+ base_url_fetcher_.reset(url_fetcher);
+}
+
+void RewriteDriverFactory::set_base_url_async_fetcher(
+ UrlAsyncFetcher* url_async_fetcher) {
+ CHECK(!FetchersComputed())
+ << "Cannot call set_base_url_fetcher "
+ << " after ComputeUrl*Fetcher has been called";
+ CHECK(base_url_fetcher_.get() == NULL)
+ << "Only call one of set_base_url_fetcher and set_base_url_async_fetcher";
+ base_url_async_fetcher_.reset(url_async_fetcher);
+}
+
+void RewriteDriverFactory::set_hasher(Hasher* hasher) {
+ hasher_.reset(hasher);
+}
+
+void RewriteDriverFactory::set_timer(Timer* timer) {
+ timer_.reset(timer);
+}
+
+void RewriteDriverFactory::set_filename_encoder(FilenameEncoder* e) {
+ filename_encoder_.reset(e);
+}
+
+MessageHandler* RewriteDriverFactory::html_parse_message_handler() {
+ if (html_parse_message_handler_ == NULL) {
+ html_parse_message_handler_.reset(DefaultHtmlParseMessageHandler());
+ }
+ return html_parse_message_handler_.get();
+}
+
+MessageHandler* RewriteDriverFactory::message_handler() {
+ if (message_handler_ == NULL) {
+ message_handler_.reset(DefaultMessageHandler());
+ }
+ return message_handler_.get();
+}
+
+FileSystem* RewriteDriverFactory::file_system() {
+ if (file_system_ == NULL) {
+ file_system_.reset(DefaultFileSystem());
+ }
+ return file_system_.get();
+}
+
+Timer* RewriteDriverFactory::timer() {
+ if (timer_ == NULL) {
+ timer_.reset(DefaultTimer());
+ }
+ return timer_.get();
+}
+
+Hasher* RewriteDriverFactory::hasher() {
+ if (hasher_ == NULL) {
+ hasher_.reset(NewHasher());
+ }
+ return hasher_.get();
+}
+
+FilenameEncoder* RewriteDriverFactory::filename_encoder() {
+ if (filename_encoder_ == NULL) {
+ filename_encoder_.reset(new FilenameEncoder);
+ }
+ return filename_encoder_.get();
+}
+
+NamedLockManager* RewriteDriverFactory::lock_manager() {
+ if (lock_manager_ == NULL) {
+ lock_manager_.reset(
+ new FileSystemLockManager(file_system(), timer(), message_handler()));
+ }
+ return lock_manager_.get();
+}
+
+bool RewriteDriverFactory::set_filename_prefix(StringPiece p) {
+ p.CopyToString(&filename_prefix_);
+ if (!file_system()->RecursivelyMakeDir(filename_prefix_, message_handler())) {
+ message_handler()->FatalError(
+ filename_prefix_.c_str(), 0,
+ "Directory does not exist and cannot be created");
+ return false;
+ }
+ return true;
+}
+
+StringPiece RewriteDriverFactory::filename_prefix() {
+ return filename_prefix_;
+}
+
+HTTPCache* RewriteDriverFactory::http_cache() {
+ if (http_cache_ == NULL) {
+ CacheInterface* cache = DefaultCacheInterface();
+ http_cache_.reset(new HTTPCache(cache, timer()));
+ http_cache_->set_force_caching(force_caching_);
+ }
+ return http_cache_.get();
+}
+
+ResourceManager* RewriteDriverFactory::ComputeResourceManager() {
+ if (resource_manager_ == NULL) {
+ CHECK(!filename_prefix_.empty())
+ << "Must specify --filename_prefix or call "
+ << "RewriteDriverFactory::set_filename_prefix.";
+ resource_manager_.reset(new ResourceManager(
+ filename_prefix_, file_system(), filename_encoder(),
+ ComputeUrlAsyncFetcher(), hasher(),
+ http_cache(), lock_manager()));
+ resource_manager_->set_store_outputs_in_file_system(
+ ShouldWriteResourcesToFileSystem());
+ }
+ return resource_manager_.get();
+}
+
+// TODO(jmaessen): Note that we *could* re-structure the
+// rewrite_driver freelist code as follows: Keep a
+// std::vector<RewriteDriver*> of all rewrite drivers. Have each
+// driver hold its index in the vector (as a number or iterator).
+// Keep index of first in use. To free, swap with first in use,
+// adjusting indexes, and increment first in use. To allocate,
+// decrement first in use and return that driver. If first in use was
+// 0, allocate a fresh driver and push it.
+
+RewriteDriver* RewriteDriverFactory::NewCustomRewriteDriver(
+ const RewriteOptions& options) {
+ RewriteDriver* rewrite_driver = new RewriteDriver(
+ message_handler(), file_system(), ComputeUrlAsyncFetcher(), options);
+ rewrite_driver->SetResourceManager(ComputeResourceManager());
+ AddPlatformSpecificRewritePasses(rewrite_driver);
+ rewrite_driver->AddFilters();
+ return rewrite_driver;
+}
+
+
+RewriteDriver* RewriteDriverFactory::NewRewriteDriver() {
+ ScopedMutex lock(rewrite_drivers_mutex());
+ RewriteDriver* rewrite_driver = NULL;
+ if (!available_rewrite_drivers_.empty()) {
+ rewrite_driver = available_rewrite_drivers_.back();
+ available_rewrite_drivers_.pop_back();
+ } else {
+ rewrite_driver = NewCustomRewriteDriver(options_);
+ }
+ active_rewrite_drivers_.insert(rewrite_driver);
+ return rewrite_driver;
+}
+
+void RewriteDriverFactory::ReleaseRewriteDriver(
+ RewriteDriver* rewrite_driver) {
+ ScopedMutex lock(rewrite_drivers_mutex());
+ int count = active_rewrite_drivers_.erase(rewrite_driver);
+ if (count != 1) {
+ LOG(ERROR) << "ReleaseRewriteDriver called with driver not in active set.";
+ } else {
+ available_rewrite_drivers_.push_back(rewrite_driver);
+ }
+}
+
+void RewriteDriverFactory::AddPlatformSpecificRewritePasses(
+ RewriteDriver* driver) {
+}
+
+UrlFetcher* RewriteDriverFactory::ComputeUrlFetcher() {
+ if (url_fetcher_ == NULL) {
+ // Run any hooks like setting up slurp directory.
+ FetcherSetupHooks();
+ if (slurp_directory_.empty()) {
+ if (base_url_fetcher_.get() == NULL) {
+ url_fetcher_ = DefaultUrlFetcher();
+ } else {
+ url_fetcher_ = base_url_fetcher_.get();
+ }
+ } else {
+ SetupSlurpDirectories();
+ }
+ }
+ return url_fetcher_;
+}
+
+UrlAsyncFetcher* RewriteDriverFactory::ComputeUrlAsyncFetcher() {
+ if (url_async_fetcher_ == NULL) {
+ // Run any hooks like setting up slurp directory.
+ FetcherSetupHooks();
+ if (slurp_directory_.empty()) {
+ if (base_url_async_fetcher_.get() == NULL) {
+ url_async_fetcher_ = DefaultAsyncUrlFetcher();
+ } else {
+ url_async_fetcher_ = base_url_async_fetcher_.get();
+ }
+ } else {
+ SetupSlurpDirectories();
+ }
+ }
+ return url_async_fetcher_;
+}
+
+void RewriteDriverFactory::SetupSlurpDirectories() {
+ CHECK(!FetchersComputed());
+ if (slurp_read_only_) {
+ CHECK(!FetchersComputed());
+ HttpDumpUrlFetcher* dump_fetcher = new HttpDumpUrlFetcher(
+ slurp_directory_, file_system(), timer());
+ dump_fetcher->set_print_urls(slurp_print_urls_);
+ url_fetcher_ = dump_fetcher;
+ } else {
+ // Check to see if the factory already had set_base_url_fetcher
+ // called on it. If so, then we'll want to use that fetcher
+ // as the mechanism for the dump-writer to retrieve missing
+ // content from the internet so it can be saved in the slurp
+ // directory.
+ url_fetcher_ = base_url_fetcher_.get();
+ if (url_fetcher_ == NULL) {
+ url_fetcher_ = DefaultUrlFetcher();
+ }
+ HttpDumpUrlWriter* dump_writer = new HttpDumpUrlWriter(
+ slurp_directory_, url_fetcher_, file_system(), timer());
+ dump_writer->set_print_urls(slurp_print_urls_);
+ url_fetcher_ = dump_writer;
+ }
+
+ // We do not use real async fetches when slurping.
+ url_async_fetcher_ = new FakeUrlAsyncFetcher(url_fetcher_);
+}
+
+void RewriteDriverFactory::FetcherSetupHooks() {
+}
+
+void RewriteDriverFactory::ShutDown() {
+ // Avoid double-destructing the url fetchers if they were not overridden
+ // programmatically
+ if ((url_async_fetcher_ != NULL) &&
+ (url_async_fetcher_ != base_url_async_fetcher_.get())) {
+ delete url_async_fetcher_;
+ }
+ url_async_fetcher_ = NULL;
+ if ((url_fetcher_ != NULL) && (url_fetcher_ != base_url_fetcher_.get())) {
+ delete url_fetcher_;
+ }
+ url_fetcher_ = NULL;
+ STLDeleteElements(&active_rewrite_drivers_);
+ STLDeleteElements(&available_rewrite_drivers_);
+
+ file_system_.reset(NULL);
+ hasher_.reset(NULL);
+ filename_encoder_.reset(NULL);
+ timer_.reset(NULL);
+ resource_manager_.reset(NULL);
+ html_parse_message_handler_.reset(NULL);
+ http_cache_.reset(NULL);
+ cache_fetcher_.reset(NULL);
+ cache_async_fetcher_.reset(NULL);
+}
+
+void RewriteDriverFactory::Initialize(Statistics* statistics) {
+ RewriteDriver::Initialize(statistics);
+ if (statistics != NULL) {
+ statistics->AddVariable(kInstawebResource404Count);
+ statistics->AddVariable(kInstawebSlurp404Count);
+ HTTPCache::Initialize(statistics);
+ }
+}
+
+void RewriteDriverFactory::Increment404Count() {
+ Statistics* statistics = resource_manager_->statistics();
+ if (statistics != NULL) {
+ if (resource_404_count_ == NULL) {
+ resource_404_count_ = statistics->GetVariable(kInstawebResource404Count);
+ }
+ resource_404_count_->Add(1);
+ }
+}
+
+void RewriteDriverFactory::IncrementSlurpCount() {
+ Statistics* statistics = resource_manager_->statistics();
+ if (statistics != NULL) {
+ if (slurp_404_count_ == NULL) {
+ slurp_404_count_ = statistics->GetVariable(kInstawebSlurp404Count);
+ }
+ slurp_404_count_->Add(1);
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/rewrite_driver_test.cc b/trunk/src/net/instaweb/rewriter/rewrite_driver_test.cc
new file mode 100644
index 0000000..7e2a405
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/rewrite_driver_test.cc
@@ -0,0 +1,67 @@
+/**
+ * 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: jmarantz@google.com (Joshua D. Marantz)
+
+#include "net/instaweb/rewriter/public/rewrite_driver.h"
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/rewriter/public/output_resource.h"
+#include "net/instaweb/rewriter/public/resource_manager_test_base.h"
+
+namespace net_instaweb {
+
+class RewriteDriverTest : public ResourceManagerTestBase {
+ protected:
+ RewriteDriverTest() {}
+
+ bool CanDecodeUrl(const StringPiece& url) {
+ RewriteFilter* filter;
+ scoped_ptr<OutputResource> resource(
+ rewrite_driver_.DecodeOutputResource(url, &filter));
+ return (resource.get() != NULL);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(RewriteDriverTest);
+};
+
+TEST_F(RewriteDriverTest, NoChanges) {
+ ValidateNoChanges("no_changes",
+ "<head><script src=\"foo.js\"></script></head>"
+ "<body><form method=\"post\">"
+ "<input type=\"checkbox\" checked>"
+ "</form></body>");
+}
+
+TEST_F(RewriteDriverTest, TestLegacyUrl) {
+ rewrite_driver_.AddFilters();
+ EXPECT_FALSE(CanDecodeUrl("http://example.com/dir/123/jm.0.orig"))
+ << "not enough dots";
+ EXPECT_TRUE(CanDecodeUrl("http://example.com/dir/123/jm.0.orig.js"));
+ EXPECT_TRUE(CanDecodeUrl(
+ "http://x.com/dir/123/jm.0123456789abcdef0123456789ABCDEF.orig.js"));
+ EXPECT_FALSE(CanDecodeUrl("http://example.com/dir/123/xx.0.orig.js"))
+ << "invalid filter xx";
+ ASSERT_FALSE(CanDecodeUrl("http://example.com/dir/123/jm.z.orig.js"))
+ << "invalid hash code -- not hex";
+ ASSERT_FALSE(CanDecodeUrl("http://example.com/dir/123/jm.ab.orig.js"))
+ << "invalid hash code -- not 1 or 32 chars";
+ ASSERT_FALSE(CanDecodeUrl("http://example.com/dir/123/jm.0.orig.x"))
+ << "invalid extension";
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/rewrite_filter.cc b/trunk/src/net/instaweb/rewriter/rewrite_filter.cc
new file mode 100644
index 0000000..aa53d25
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/rewrite_filter.cc
@@ -0,0 +1,26 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/rewriter/public/rewrite_filter.h"
+
+namespace net_instaweb {
+
+RewriteFilter::~RewriteFilter() {
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/rewrite_options.cc b/trunk/src/net/instaweb/rewriter/rewrite_options.cc
new file mode 100644
index 0000000..d28e1cb
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/rewrite_options.cc
@@ -0,0 +1,287 @@
+/**
+ * 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.
+ */
+
+#include "net/instaweb/rewriter/public/rewrite_options.h"
+
+#include <vector>
+#include "net/instaweb/util/public/message_handler.h"
+
+namespace net_instaweb {
+
+// TODO(jmarantz): consider merging this threshold with the image-inlining
+// threshold, which is currently defaulting at 2000, so we have a single
+// byte-count threshold, above which inlined resources get outlined, and
+// below which outlined resources get inlined.
+//
+// TODO(jmarantz): user-agent-specific selection of inline threshold so that
+// mobile phones are more prone to inlining.
+//
+// Further notes; jmaessen says:
+//
+// I suspect we do not want these bounds to match, and inlining for
+// images is a bit more complicated because base64 encoding inflates
+// the byte count of data: urls. This is a non-issue for other
+// resources (there may be some weirdness with iframes I haven't
+// thought about...).
+//
+// jmarantz says:
+//
+// One thing we could do, if we believe they should be conceptually
+// merged, is in img_rewrite_filter you could apply the
+// base64-bloat-factor before comparing against the threshold. Then
+// we could use one number if we like that idea.
+//
+
+// jmaessen: For the moment, there's a separate threshold for img inline.
+const int64 RewriteOptions::kDefaultCssInlineMaxBytes = 2048;
+const int64 RewriteOptions::kDefaultImgInlineMaxBytes = 2048;
+const int64 RewriteOptions::kDefaultJsInlineMaxBytes = 2048;
+const int64 RewriteOptions::kDefaultCssOutlineMinBytes = 3000;
+const int64 RewriteOptions::kDefaultJsOutlineMinBytes = 3000;
+
+// Limit on concurrent ongoing img rewrites.
+// TODO(jmaessen): Determine a sane default for this value.
+const int RewriteOptions::kDefaultImgMaxRewritesAtOnce = 8;
+
+// IE limits URL size overall to about 2k characters. See
+// http://support.microsoft.com/kb/208427/EN-US
+const int RewriteOptions::kMaxUrlSize = 2083;
+
+// See http://code.google.com/p/modpagespeed/issues/detail?id=9
+// Apache evidently limits each URL path segment (between /) to
+// about 256 characters. This is not a fundamental URL limitation
+// but is Apache specific. For the moment we will impose the
+// Apache limitation in general -- there is concern that even an
+// nginx server might fail if resources are then served through
+// an Apache proxy. Until then let's just set a reasonable limit.
+const int64 RewriteOptions::kMaxUrlSegmentSize = 250;
+
+const std::string RewriteOptions::kDefaultBeaconUrl =
+ "/mod_pagespeed_beacon?ets=";
+
+bool RewriteOptions::ParseRewriteLevel(
+ const StringPiece& in, RewriteLevel* out) {
+ bool ret = false;
+ if (in != NULL) {
+ if (strcasecmp(in.data(), "CoreFilters") == 0) {
+ *out = kCoreFilters;
+ ret = true;
+ } else if (strcasecmp(in.data(), "PassThrough") == 0) {
+ *out = kPassThrough;
+ ret = true;
+ } else if (strcasecmp(in.data(), "TestingCoreFilters") == 0) {
+ *out = kTestingCoreFilters;
+ ret = true;
+ } else if (strcasecmp(in.data(), "AllFilters") == 0) {
+ *out = kAllFilters;
+ ret = true;
+ }
+ }
+ return ret;
+}
+
+RewriteOptions::RewriteOptions()
+ : modified_(false),
+ level_(kPassThrough),
+ css_inline_max_bytes_(kDefaultCssInlineMaxBytes),
+ img_inline_max_bytes_(kDefaultImgInlineMaxBytes),
+ img_max_rewrites_at_once_(kDefaultImgMaxRewritesAtOnce),
+ js_inline_max_bytes_(kDefaultJsInlineMaxBytes),
+ css_outline_min_bytes_(kDefaultCssInlineMaxBytes),
+ js_outline_min_bytes_(kDefaultJsInlineMaxBytes),
+ num_shards_(0),
+ beacon_url_(kDefaultBeaconUrl),
+ max_url_segment_size_(kMaxUrlSegmentSize),
+ max_url_size_(kMaxUrlSize),
+ enabled_(true) {
+ // TODO: If we instantiate many RewriteOptions, this should become a
+ // public static method called once at startup.
+ SetUp();
+}
+
+RewriteOptions::~RewriteOptions() {
+}
+
+void RewriteOptions::SetUp() {
+ name_filter_map_["add_base_tag"] = kAddBaseTag;
+ name_filter_map_["add_head"] = kAddHead;
+ name_filter_map_["add_instrumentation"] = kAddInstrumentation;
+ name_filter_map_["collapse_whitespace"] = kCollapseWhitespace;
+ name_filter_map_["combine_css"] = kCombineCss;
+ name_filter_map_["combine_heads"] = kCombineHeads;
+ name_filter_map_["debug_log_img_tags"] = kDebugLogImgTags;
+ name_filter_map_["elide_attributes"] = kElideAttributes;
+ name_filter_map_["extend_cache"] = kExtendCache;
+ name_filter_map_["inline_css"] = kInlineCss;
+ name_filter_map_["inline_javascript"] = kInlineJavascript;
+ name_filter_map_["insert_img_dimensions"] = kInsertImgDimensions;
+ name_filter_map_["left_trim_urls"] = kLeftTrimUrls;
+ name_filter_map_["move_css_to_head"] = kMoveCssToHead;
+ name_filter_map_["outline_css"] = kOutlineCss;
+ name_filter_map_["outline_javascript"] = kOutlineJavascript;
+ name_filter_map_["remove_comments"] = kRemoveComments;
+ name_filter_map_["remove_quotes"] = kRemoveQuotes;
+ name_filter_map_["rewrite_css"] = kRewriteCss;
+ name_filter_map_["rewrite_images"] = kRewriteImages;
+ name_filter_map_["rewrite_javascript"] = kRewriteJavascript;
+ name_filter_map_["strip_scripts"] = kStripScripts;
+
+ // Create an empty set for the pass-through level.
+ level_filter_set_map_[kPassThrough];
+
+ // Core filter level includes the "core" filter set.
+ level_filter_set_map_[kCoreFilters].insert(kAddHead);
+ level_filter_set_map_[kCoreFilters].insert(kCombineCss);
+ // TODO(jmarantz): re-enable javascript and CSS minification in
+ // the core set after the reported bugs have been fixed. They
+ // can still be enabled individually.
+ // level_filter_set_map_[kCoreFilters].insert(kRewriteJavascript);
+ // level_filter_set_map_[kCoreFilters].insert(kRewriteCss);
+ level_filter_set_map_[kCoreFilters].insert(kInlineCss);
+ level_filter_set_map_[kCoreFilters].insert(kInlineJavascript);
+ level_filter_set_map_[kCoreFilters].insert(kRewriteImages);
+ level_filter_set_map_[kCoreFilters].insert(kInsertImgDimensions);
+ level_filter_set_map_[kCoreFilters].insert(kExtendCache);
+
+ // Copy CoreFilters set into TestingCoreFilters set ...
+ level_filter_set_map_[kTestingCoreFilters] =
+ level_filter_set_map_[kCoreFilters];
+ // ... and add possibly unsafe filters.
+ level_filter_set_map_[kTestingCoreFilters].insert(kRewriteJavascript);
+ level_filter_set_map_[kTestingCoreFilters].insert(kRewriteCss);
+
+ // Set complete set for all filters set.
+ for (int f = kFirstEnumFilter; f != kLastEnumFilter; ++f) {
+ level_filter_set_map_[kAllFilters].insert(static_cast<Filter>(f));
+ }
+}
+
+bool RewriteOptions::EnableFiltersByCommaSeparatedList(
+ const StringPiece& filters, MessageHandler* handler) {
+ return AddCommaSeparatedListToFilterSet(
+ filters, handler, &enabled_filters_);
+}
+
+bool RewriteOptions::DisableFiltersByCommaSeparatedList(
+ const StringPiece& filters, MessageHandler* handler) {
+ return AddCommaSeparatedListToFilterSet(
+ filters, handler, &disabled_filters_);
+}
+
+void RewriteOptions::EnableFilter(Filter filter) {
+ std::pair<FilterSet::iterator, bool> inserted =
+ enabled_filters_.insert(filter);
+ modified_ |= inserted.second;
+}
+
+void RewriteOptions::DisableFilter(Filter filter) {
+ std::pair<FilterSet::iterator, bool> inserted =
+ disabled_filters_.insert(filter);
+ modified_ |= inserted.second;
+}
+
+bool RewriteOptions::AddCommaSeparatedListToFilterSet(
+ const StringPiece& filters, MessageHandler* handler, FilterSet* set) {
+ std::vector<StringPiece> names;
+ SplitStringPieceToVector(filters, ",", &names, true);
+ bool ret = true;
+ for (int i = 0, n = names.size(); i < n; ++i) {
+ std::string option(names[i].data(), names[i].size());
+ NameToFilterMap::iterator p = name_filter_map_.find(option);
+ if (p == name_filter_map_.end()) {
+ handler->Message(kWarning, "Invalid filter name: %s", option.c_str());
+ ret = false;
+ } else {
+ std::pair<FilterSet::iterator, bool> inserted = set->insert(p->second);
+ modified_ |= inserted.second;
+ }
+ }
+ return ret;
+}
+
+bool RewriteOptions::Enabled(Filter filter) const {
+ if (disabled_filters_.find(filter) != disabled_filters_.end()) {
+ return false;
+ }
+
+ RewriteLevelToFilterSetMap::const_iterator it =
+ level_filter_set_map_.find(level_.value());
+ if (it != level_filter_set_map_.end()) {
+ const FilterSet& filters = it->second;
+ if (filters.find(filter) != filters.end()) {
+ return true;
+ }
+ }
+
+ return (enabled_filters_.find(filter) != enabled_filters_.end());
+}
+
+void RewriteOptions::Merge(const RewriteOptions& first,
+ const RewriteOptions& second) {
+ modified_ = first.modified_ || second.modified_;
+ enabled_filters_ = first.enabled_filters_;
+ disabled_filters_ = first.disabled_filters_;
+ for (FilterSet::const_iterator p = second.enabled_filters_.begin(),
+ e = second.enabled_filters_.end(); p != e; ++p) {
+ Filter filter = *p;
+ // Enabling in 'second' trumps Disabling in first.
+ disabled_filters_.erase(filter);
+ enabled_filters_.insert(filter);
+ }
+
+ for (FilterSet::const_iterator p = second.disabled_filters_.begin(),
+ e = second.disabled_filters_.end(); p != e; ++p) {
+ Filter filter = *p;
+ // Disabling in 'second' trumps enabling in anything.
+ disabled_filters_.insert(filter);
+ enabled_filters_.erase(filter);
+ }
+
+ // TODO(jmarantz): Use a virtual base class for Option so we can put
+ // this in a loop. Or something.
+ enabled_.Merge(first.enabled_, second.enabled_);
+ level_.Merge(first.level_, second.level_);
+ css_inline_max_bytes_.Merge(first.css_inline_max_bytes_,
+ second.css_inline_max_bytes_);
+ img_inline_max_bytes_.Merge(first.img_inline_max_bytes_,
+ second.img_inline_max_bytes_);
+ img_max_rewrites_at_once_.Merge(first.img_max_rewrites_at_once_,
+ second.img_max_rewrites_at_once_);
+ js_inline_max_bytes_.Merge(first.js_inline_max_bytes_,
+ second.js_inline_max_bytes_);
+ css_outline_min_bytes_.Merge(first.css_outline_min_bytes_,
+ second.css_outline_min_bytes_);
+ js_outline_min_bytes_.Merge(first.js_outline_min_bytes_,
+ second.js_outline_min_bytes_);
+ num_shards_.Merge(first.num_shards_,
+ second.num_shards_);
+ beacon_url_.Merge(first.beacon_url_,
+ second.beacon_url_);
+ max_url_segment_size_.Merge(first.max_url_segment_size_,
+ second.max_url_segment_size_);
+ max_url_size_.Merge(first.max_url_size_,
+ second.max_url_size_);
+
+ // Note that the domain-lawyer merge works one-at-a-time, which is easier
+ // to unit test. So we have to call it twice.
+ domain_lawyer_.Merge(first.domain_lawyer_);
+ domain_lawyer_.Merge(second.domain_lawyer_);
+
+ allow_resources_.CopyFrom(first.allow_resources_);
+ allow_resources_.AppendFrom(second.allow_resources_);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/rewrite_options_test.cc b/trunk/src/net/instaweb/rewriter/rewrite_options_test.cc
new file mode 100644
index 0000000..3d435fb
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/rewrite_options_test.cc
@@ -0,0 +1,314 @@
+/**
+ * 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: bmcquade@google.com (Bryan McQuade)
+
+#include "net/instaweb/util/public/gtest.h"
+#include "net/instaweb/util/public/null_message_handler.h"
+#include "net/instaweb/rewriter/public/rewrite_options.h"
+
+namespace {
+
+using net_instaweb::NullMessageHandler;
+using net_instaweb::RewriteOptions;
+
+class RewriteOptionsTest : public ::testing::Test {
+ protected:
+ typedef std::set<RewriteOptions::Filter> FilterSet;
+ void AssertNoneEnabled() {
+ FilterSet s;
+ AssertEnabled(s);
+ }
+
+ void AssertEnabled(const FilterSet& filters) {
+ for (RewriteOptions::Filter f = RewriteOptions::kFirstFilter;
+ f <= RewriteOptions::kLastFilter;
+ f = static_cast<RewriteOptions::Filter>(f + 1)) {
+ if (filters.find(f) != filters.end()) {
+ ASSERT_TRUE(options_.Enabled(f));
+ } else {
+ ASSERT_FALSE(options_.Enabled(f));
+ }
+ }
+ }
+
+ RewriteOptions options_;
+};
+
+TEST_F(RewriteOptionsTest, NoneEnabledByDefault) {
+ AssertNoneEnabled();
+}
+
+TEST_F(RewriteOptionsTest, InstrumentationDisabled) {
+ // Make sure the kCoreFilters enables some filters.
+ options_.SetRewriteLevel(RewriteOptions::kCoreFilters);
+ ASSERT_TRUE(options_.Enabled(RewriteOptions::kExtendCache));
+
+ // Now disable all filters and make sure none are enabled.
+ for (RewriteOptions::Filter f = RewriteOptions::kFirstFilter;
+ f <= RewriteOptions::kLastFilter;
+ f = static_cast<RewriteOptions::Filter>(f + 1)) {
+ options_.DisableFilter(f);
+ }
+ AssertNoneEnabled();
+}
+
+TEST_F(RewriteOptionsTest, DisableTrumpsEnable) {
+ for (RewriteOptions::Filter f = RewriteOptions::kFirstFilter;
+ f <= RewriteOptions::kLastFilter;
+ f = static_cast<RewriteOptions::Filter>(f + 1)) {
+ options_.DisableFilter(f);
+ options_.EnableFilter(f);
+ AssertNoneEnabled();
+ }
+}
+
+TEST_F(RewriteOptionsTest, CoreFilters) {
+ options_.SetRewriteLevel(RewriteOptions::kCoreFilters);
+ FilterSet s;
+ for (RewriteOptions::Filter f = RewriteOptions::kFirstFilter;
+ f <= RewriteOptions::kLastFilter;
+ f = static_cast<RewriteOptions::Filter>(f + 1)) {
+ if (options_.Enabled(f)) {
+ s.insert(f);
+ }
+ }
+
+ // Make sure that more than one filter is enabled in the core filter
+ // set.
+ ASSERT_GT(s.size(), 1);
+}
+
+TEST_F(RewriteOptionsTest, Enable) {
+ FilterSet s;
+ for (RewriteOptions::Filter f = RewriteOptions::kFirstFilter;
+ f <= RewriteOptions::kLastFilter;
+ f = static_cast<RewriteOptions::Filter>(f + 1)) {
+ s.insert(f);
+ options_.EnableFilter(f);
+ AssertEnabled(s);
+ }
+}
+
+TEST_F(RewriteOptionsTest, CommaSeparatedList) {
+ FilterSet s;
+ s.insert(RewriteOptions::kAddBaseTag);
+ s.insert(RewriteOptions::kLeftTrimUrls);
+ const char* kList = "add_base_tag,left_trim_urls";
+ NullMessageHandler handler;
+ ASSERT_TRUE(
+ options_.EnableFiltersByCommaSeparatedList(kList, &handler));
+ AssertEnabled(s);
+ ASSERT_TRUE(
+ options_.DisableFiltersByCommaSeparatedList(kList, &handler));
+ AssertNoneEnabled();
+}
+
+TEST_F(RewriteOptionsTest, ParseRewriteLevel) {
+ RewriteOptions::RewriteLevel level;
+ ASSERT_TRUE(RewriteOptions::ParseRewriteLevel("PassThrough", &level));
+ ASSERT_EQ(RewriteOptions::kPassThrough, level);
+
+ ASSERT_TRUE(RewriteOptions::ParseRewriteLevel("CoreFilters", &level));
+ ASSERT_EQ(RewriteOptions::kCoreFilters, level);
+
+ ASSERT_FALSE(RewriteOptions::ParseRewriteLevel(NULL, &level));
+ ASSERT_FALSE(RewriteOptions::ParseRewriteLevel("", &level));
+ ASSERT_FALSE(RewriteOptions::ParseRewriteLevel("Garbage", &level));
+}
+
+TEST_F(RewriteOptionsTest, MergeLevelsDefault) {
+ RewriteOptions one, two;
+ options_.Merge(one, two);
+ EXPECT_EQ(RewriteOptions::kPassThrough, options_.level());
+}
+
+TEST_F(RewriteOptionsTest, MergeLevelsOneCore) {
+ RewriteOptions one, two;
+ one.SetRewriteLevel(RewriteOptions::kCoreFilters);
+ options_.Merge(one, two);
+ EXPECT_EQ(RewriteOptions::kCoreFilters, options_.level());
+}
+
+TEST_F(RewriteOptionsTest, MergeLevelsOneCoreTwoPass) {
+ RewriteOptions one, two;
+ one.SetRewriteLevel(RewriteOptions::kCoreFilters);
+ two.SetRewriteLevel(RewriteOptions::kPassThrough); // overrides default
+ options_.Merge(one, two);
+ EXPECT_EQ(RewriteOptions::kPassThrough, options_.level());
+}
+
+TEST_F(RewriteOptionsTest, MergeLevelsOnePassTwoCore) {
+ RewriteOptions one, two;
+ one.SetRewriteLevel(RewriteOptions::kPassThrough); // overrides default
+ two.SetRewriteLevel(RewriteOptions::kCoreFilters); // overrides one
+ options_.Merge(one, two);
+ EXPECT_EQ(RewriteOptions::kCoreFilters, options_.level());
+}
+
+TEST_F(RewriteOptionsTest, MergeLevelsBothCore) {
+ RewriteOptions one, two;
+ one.SetRewriteLevel(RewriteOptions::kCoreFilters);
+ two.SetRewriteLevel(RewriteOptions::kCoreFilters);
+ options_.Merge(one, two);
+ EXPECT_EQ(RewriteOptions::kCoreFilters, options_.level());
+}
+
+TEST_F(RewriteOptionsTest, MergeFilterPassThrough) {
+ RewriteOptions one, two;
+ options_.Merge(one, two);
+ EXPECT_FALSE(options_.Enabled(RewriteOptions::kAddHead));
+}
+
+TEST_F(RewriteOptionsTest, MergeFilterEnaOne) {
+ RewriteOptions one, two;
+ one.EnableFilter(RewriteOptions::kAddHead);
+ options_.Merge(one, two);
+ EXPECT_TRUE(options_.Enabled(RewriteOptions::kAddHead));
+}
+
+TEST_F(RewriteOptionsTest, MergeFilterEnaTwo) {
+ RewriteOptions one, two;
+ two.EnableFilter(RewriteOptions::kAddHead);
+ options_.Merge(one, two);
+ EXPECT_TRUE(options_.Enabled(RewriteOptions::kAddHead));
+}
+
+TEST_F(RewriteOptionsTest, MergeFilterEnaOneDisTwo) {
+ RewriteOptions one, two;
+ one.EnableFilter(RewriteOptions::kAddHead);
+ two.DisableFilter(RewriteOptions::kAddHead);
+ options_.Merge(one, two);
+ EXPECT_FALSE(options_.Enabled(RewriteOptions::kAddHead));
+}
+
+TEST_F(RewriteOptionsTest, MergeFilterDisOneEnaTwo) {
+ RewriteOptions one, two;
+ one.DisableFilter(RewriteOptions::kAddHead);
+ two.EnableFilter(RewriteOptions::kAddHead);
+ options_.Merge(one, two);
+ EXPECT_TRUE(options_.Enabled(RewriteOptions::kAddHead));
+}
+
+TEST_F(RewriteOptionsTest, MergeCoreFilter) {
+ RewriteOptions one, two;
+ one.SetRewriteLevel(RewriteOptions::kCoreFilters);
+ options_.Merge(one, two);
+ EXPECT_TRUE(options_.Enabled(RewriteOptions::kExtendCache));
+}
+
+TEST_F(RewriteOptionsTest, MergeCoreFilterEnaOne) {
+ RewriteOptions one, two;
+ one.SetRewriteLevel(RewriteOptions::kCoreFilters);
+ one.EnableFilter(RewriteOptions::kExtendCache);
+ options_.Merge(one, two);
+ EXPECT_TRUE(options_.Enabled(RewriteOptions::kExtendCache));
+}
+
+TEST_F(RewriteOptionsTest, MergeCoreFilterEnaTwo) {
+ RewriteOptions one, two;
+ one.SetRewriteLevel(RewriteOptions::kCoreFilters);
+ two.EnableFilter(RewriteOptions::kExtendCache);
+ options_.Merge(one, two);
+ EXPECT_TRUE(options_.Enabled(RewriteOptions::kExtendCache));
+}
+
+TEST_F(RewriteOptionsTest, MergeCoreFilterEnaOneDisTwo) {
+ RewriteOptions one, two;
+ one.SetRewriteLevel(RewriteOptions::kCoreFilters);
+ one.EnableFilter(RewriteOptions::kExtendCache);
+ two.DisableFilter(RewriteOptions::kExtendCache);
+ options_.Merge(one, two);
+ EXPECT_FALSE(options_.Enabled(RewriteOptions::kExtendCache));
+}
+
+TEST_F(RewriteOptionsTest, MergeCoreFilterDisOne) {
+ RewriteOptions one, two;
+ one.SetRewriteLevel(RewriteOptions::kCoreFilters);
+ one.DisableFilter(RewriteOptions::kExtendCache);
+ options_.Merge(one, two);
+ EXPECT_FALSE(options_.Enabled(RewriteOptions::kExtendCache));
+}
+
+TEST_F(RewriteOptionsTest, MergeCoreFilterDisOneEnaTwo) {
+ RewriteOptions one, two;
+ one.SetRewriteLevel(RewriteOptions::kCoreFilters);
+ one.DisableFilter(RewriteOptions::kExtendCache);
+ two.EnableFilter(RewriteOptions::kExtendCache);
+ options_.Merge(one, two);
+ EXPECT_TRUE(options_.Enabled(RewriteOptions::kExtendCache));
+}
+
+TEST_F(RewriteOptionsTest, MergeThresholdDefault) {
+ RewriteOptions one, two;
+ options_.Merge(one, two);
+ EXPECT_EQ(RewriteOptions::kDefaultCssInlineMaxBytes,
+ options_.css_inline_max_bytes());
+}
+
+TEST_F(RewriteOptionsTest, MergeThresholdOne) {
+ RewriteOptions one, two;
+ one.set_css_inline_max_bytes(5);
+ options_.Merge(one, two);
+ EXPECT_EQ(5, options_.css_inline_max_bytes());
+}
+
+TEST_F(RewriteOptionsTest, MergeThresholdTwo) {
+ RewriteOptions one, two;
+ two.set_css_inline_max_bytes(6);
+ options_.Merge(one, two);
+ EXPECT_EQ(6, options_.css_inline_max_bytes());
+}
+
+TEST_F(RewriteOptionsTest, MergeThresholdOverride) {
+ RewriteOptions one, two;
+ one.set_css_inline_max_bytes(5);
+ two.set_css_inline_max_bytes(6);
+ options_.Merge(one, two);
+ EXPECT_EQ(6, options_.css_inline_max_bytes());
+}
+
+TEST_F(RewriteOptionsTest, Allow) {
+ options_.Allow("*.css");
+ EXPECT_TRUE(options_.IsAllowed("abcd.css"));
+ options_.Disallow("a*.css");
+ EXPECT_FALSE(options_.IsAllowed("abcd.css"));
+ options_.Allow("ab*.css");
+ EXPECT_TRUE(options_.IsAllowed("abcd.css"));
+ options_.Disallow("abc*.css");
+ EXPECT_FALSE(options_.IsAllowed("abcd.css"));
+}
+
+TEST_F(RewriteOptionsTest, MergeAllow) {
+ RewriteOptions one, two;
+ one.Allow("*.css");
+ EXPECT_TRUE(one.IsAllowed("abcd.css"));
+ one.Disallow("a*.css");
+ EXPECT_FALSE(one.IsAllowed("abcd.css"));
+
+ two.Allow("ab*.css");
+ EXPECT_TRUE(two.IsAllowed("abcd.css"));
+ two.Disallow("abc*.css");
+ EXPECT_FALSE(two.IsAllowed("abcd.css"));
+
+ options_.Merge(one, two);
+ EXPECT_FALSE(options_.IsAllowed("abcd.css"));
+ EXPECT_FALSE(options_.IsAllowed("abc.css"));
+ EXPECT_TRUE(options_.IsAllowed("ab.css"));
+ EXPECT_FALSE(options_.IsAllowed("a.css"));
+}
+
+} // namespace
diff --git a/trunk/src/net/instaweb/rewriter/rewrite_single_resource_filter.cc b/trunk/src/net/instaweb/rewriter/rewrite_single_resource_filter.cc
new file mode 100644
index 0000000..a6512da
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/rewrite_single_resource_filter.cc
@@ -0,0 +1,110 @@
+/*
+ * 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/rewrite_single_resource_filter.h"
+
+#include "base/scoped_ptr.h"
+#include "net/instaweb/rewriter/public/output_resource.h"
+#include "net/instaweb/rewriter/public/resource.h"
+#include "net/instaweb/rewriter/public/resource_manager.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/url_escaper.h"
+
+namespace net_instaweb {
+
+RewriteSingleResourceFilter::~RewriteSingleResourceFilter() {}
+
+// Manage rewriting an input resource after it has been fetched/loaded.
+class RewriteSingleResourceFilter::FetchCallback
+ : public Resource::AsyncCallback {
+ public:
+ FetchCallback(RewriteSingleResourceFilter* filter,
+ Resource* input_resource, OutputResource* output_resource,
+ MetaData* response_headers, Writer* response_writer,
+ MessageHandler* handler,
+ UrlAsyncFetcher::Callback* base_callback)
+ : filter_(filter),
+ input_resource_(input_resource),
+ output_resource_(output_resource),
+ response_headers_(response_headers),
+ response_writer_(response_writer),
+ handler_(handler),
+ base_callback_(base_callback) {}
+
+ virtual void Done(bool success, Resource* resource) {
+ CHECK_EQ(input_resource_.get(), resource);
+ if (success) {
+ // This checks HTTP status was 200 OK.
+ success = input_resource_->ContentsValid();
+ }
+ if (success) {
+ // Call the rewrite hook.
+ success = filter_->RewriteLoadedResource(input_resource_.get(),
+ output_resource_);
+ }
+ if (success) {
+ // Copy headers and content to HTTP response.
+ // TODO(sligocki): It might be worth streaming this.
+ response_headers_->CopyFrom(*output_resource_->metadata());
+ response_writer_->Write(output_resource_->contents(), handler_);
+ }
+ base_callback_->Done(success);
+ delete this;
+ }
+
+ private:
+ RewriteSingleResourceFilter* filter_;
+ scoped_ptr<Resource> input_resource_;
+ OutputResource* output_resource_;
+ MetaData* response_headers_;
+ Writer* response_writer_;
+ MessageHandler* handler_;
+ UrlAsyncFetcher::Callback* base_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(FetchCallback);
+};
+
+bool RewriteSingleResourceFilter::Fetch(
+ OutputResource* output_resource,
+ Writer* response_writer,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ MessageHandler* message_handler,
+ UrlAsyncFetcher::Callback* base_callback) {
+ bool ret = false;
+ Resource* input_resource =
+ resource_manager_->CreateInputResourceFromOutputResource(
+ resource_manager_->url_escaper(), output_resource,
+ driver_->options(), message_handler);
+ if (input_resource != NULL) {
+ // Callback takes ownership of input_resoruce.
+ FetchCallback* fetch_callback = new FetchCallback(
+ this, input_resource, output_resource,
+ response_headers, response_writer, message_handler, base_callback);
+ resource_manager_->ReadAsync(input_resource, fetch_callback,
+ message_handler);
+ ret = true;
+ } else {
+ std::string url;
+ output_resource->name().CopyToString(&url);
+ message_handler->Error(url.c_str(), 0, "Unable to decode resource string");
+ }
+ return ret;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/rewriter_test.cc b/trunk/src/net/instaweb/rewriter/rewriter_test.cc
new file mode 100644
index 0000000..425d0fb
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/rewriter_test.cc
@@ -0,0 +1,130 @@
+/*
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+// Unit-test some small filters.
+
+#include "net/instaweb/rewriter/public/resource_manager_test_base.h"
+
+#include "net/instaweb/rewriter/public/rewrite_driver.h"
+
+namespace net_instaweb {
+
+namespace {
+
+class RewriterTest : public ResourceManagerTestBase {};
+
+TEST_F(RewriterTest, AddHead) {
+ AddFilter(RewriteOptions::kAddHead);
+ ValidateExpected("add_head",
+ "<body><p>text</p></body>",
+ "<head/><body><p>text</p></body>");
+}
+
+TEST_F(RewriterTest, AddHeadNoBody) {
+ // Test for proper diagnostic (regression test for Issue 134)
+ AddFilter(RewriteOptions::kAddHead);
+ ValidateExpected("add_head_no_body",
+ "<p>text</p>",
+ "<p>text</p>");
+ EXPECT_EQ(0, message_handler_.SeriousMessages());
+}
+
+TEST_F(RewriterTest, MergeHead) {
+ AddFilter(RewriteOptions::kCombineHeads);
+ ValidateExpected("merge_2_heads",
+ "<head a><p>1</p></head>4<head b>2<link x>3</head><link y>end",
+ "<head a><p>1</p>2<link x>3</head>4<link y>end");
+ ValidateExpected("merge_3_heads",
+ "<head a><p>1</p></head>4<head b>2<link x>3</head><link y>"
+ "<body>b<head><link z></head>ye</body>",
+ "<head a><p>1</p>2<link x>3<link z></head>4<link y>"
+ "<body>bye</body>");
+}
+
+TEST_F(RewriterTest, BaseTagNoHead) {
+ AddFilter(RewriteOptions::kAddBaseTag);
+ rewrite_driver_.SetBaseUrl("http://base");
+ ValidateExpected("base_tag",
+ "<body><p>text</p></body>",
+ "<head><base href=\"http://base\"></head><body><p>text</p></body>");
+}
+
+TEST_F(RewriterTest, BaseTagExistingHead) {
+ AddFilter(RewriteOptions::kAddBaseTag);
+ rewrite_driver_.SetBaseUrl("http://base");
+ ValidateExpected("base_tag",
+ "<head><meta></head><body><p>text</p></body>",
+ "<head><base href=\"http://base\"><meta></head><body><p>text</p></body>");
+}
+
+TEST_F(RewriterTest, BaseTagExistingHeadAndNonHrefBase) {
+ AddFilter(RewriteOptions::kAddBaseTag);
+ rewrite_driver_.SetBaseUrl("http://base");
+ ValidateExpected("base_tag",
+ "<head><base x><meta></head><body></body>",
+ "<head><base href=\"http://base\"><base x><meta></head><body></body>");
+}
+
+TEST_F(RewriterTest, BaseTagExistingHeadAndHrefBase) {
+ AddFilter(RewriteOptions::kAddBaseTag);
+ rewrite_driver_.SetBaseUrl("http://base");
+ ValidateExpected("base_tag",
+ "<head><meta><base href=\"http://old\"></head><body></body>",
+ "<head><base href=\"http://base\"><meta></head><body></body>");
+}
+
+TEST_F(RewriterTest, FailGracefullyOnInvalidUrls) {
+ Hasher* hasher = &md5_hasher_;
+ resource_manager_->set_hasher(hasher);
+ AddFilter(RewriteOptions::kExtendCache);
+
+ const char kCssData[] = "a { color: red }";
+ InitMetaData("a.css", kContentTypeCss, kCssData, 100);
+
+ // Fetching the real rewritten resource name should work.
+ // TODO(sligocki): This will need to be regolded if naming format changes.
+ std::string hash = hasher->Hash(kCssData);
+ EXPECT_TRUE(
+ TryFetchResource(Encode("http://test.com/", "ce", hash, "a.css", "css")));
+
+ // Fetching variants should not cause system problems.
+ // Changing hash still works.
+ // Note: If any of these switch from true to false, that's probably fine.
+ // We'd just like to keep track of what causes errors and what doesn't.
+ EXPECT_TRUE(TryFetchResource(Encode("http://test.com/", "ce", "foobar",
+ "a.css", "css")));
+ EXPECT_TRUE(
+ TryFetchResource(Encode("http://test.com/", "ce", hash, "a.css",
+ "ext")));
+
+ // Changing other fields can lead to error.
+ std::string bad_url = Encode("http://test.com/", "xz", hash, "a.css", "css");
+
+ // Note that although we pass in a real value for 'request headers', we
+ // null out the response and writer as those should never be called.
+ SimpleMetaData request_headers;
+ FetchCallback callback;
+ bool fetched = rewrite_driver_.FetchResource(
+ bad_url, request_headers, NULL, NULL, &message_handler_, &callback);
+ EXPECT_FALSE(fetched);
+ EXPECT_FALSE(callback.done());
+}
+
+} // namespace
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/script_tag_scanner.cc b/trunk/src/net/instaweb/rewriter/script_tag_scanner.cc
new file mode 100644
index 0000000..1894504
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/script_tag_scanner.cc
@@ -0,0 +1,152 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/rewriter/public/script_tag_scanner.h"
+
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/util/public/atom.h"
+#include "net/instaweb/util/public/atom.h"
+
+namespace net_instaweb {
+
+// The list is from HTML5, "4.3.1.1 Scripting languages"
+static const char* const javascript_mimetypes[] = {
+ "application/ecmascript",
+ "application/javascript",
+ "application/x-ecmascript",
+ "application/x-javascript",
+ "text/ecmascript",
+ "text/javascript",
+ "text/javascript1.0",
+ "text/javascript1.1",
+ "text/javascript1.2",
+ "text/javascript1.3",
+ "text/javascript1.4",
+ "text/javascript1.5",
+ "text/jscript",
+ "text/livescript",
+ "text/x-ecmascript",
+ "text/x-javascript"
+};
+
+ScriptTagScanner::ScriptTagScanner(HtmlParse* html_parse)
+ : s_async_(html_parse->Intern("async")),
+ s_defer_(html_parse->Intern("defer")),
+ s_event_(html_parse->Intern("event")),
+ s_for_(html_parse->Intern("for")),
+ s_language_(html_parse->Intern("language")),
+ s_script_(html_parse->Intern("script")),
+ s_src_(html_parse->Intern("src")),
+ s_type_(html_parse->Intern("type")) {
+ for (int i = 0; i < int(arraysize(javascript_mimetypes)); ++i) {
+ javascript_mimetypes_.insert(std::string(javascript_mimetypes[i]));
+ }
+}
+
+ScriptTagScanner::ScriptClassification ScriptTagScanner::ParseScriptElement(
+ HtmlElement* element, HtmlElement::Attribute** src) {
+ if (element->tag() != s_script_) {
+ return kNonScript;
+ }
+
+ *src = element->FindAttribute(s_src_);
+
+ // Figure out if we have JS or not.
+ // This is based on the 'type' and 'language' attributes, with
+ // type having precedence. For this determination, however,
+ // <script type> acts as if the type attribute is not there,
+ // which is different from <script type="">
+ ScriptClassification lang;
+ HtmlElement::Attribute* type_attr = element->FindAttribute(s_type_);
+ HtmlElement::Attribute* lang_attr = element->FindAttribute(s_language_);
+ if (type_attr != NULL && type_attr->value() != NULL) {
+ StringPiece type_str = type_attr->value();
+ if (type_str.empty() || IsJsMime(Normalized(type_str))) {
+ // An empty type string (but not whitespace-only!) is JS,
+ // So is one that's a known mimetype once lowercased and
+ // having its leading and trailing whitespace removed
+ lang = kJavaScript;
+ } else {
+ lang = kUnknownScript;
+ }
+ } else if (lang_attr != NULL && lang_attr->value() != NULL) {
+ // Without type= the ultra-deprecated language attribute determines things.
+ // empty or null one is ignored. The test is done case-insensitively,
+ // but leading/trailing whitespace matters.
+ // (Note: null check on ->value() above as it's passed to std::string)
+ std::string lang_str = lang_attr->value();
+ LowerString(&lang_str);
+ if (lang_str.empty() || IsJsMime(StrCat("text/", lang_str))) {
+ lang = kJavaScript;
+ } else {
+ lang = kUnknownScript;
+ }
+ } else {
+ // JS is the default if nothing is specified at all.
+ lang = kJavaScript;
+ }
+
+ return lang;
+}
+
+
+int ScriptTagScanner::ExecutionMode(const HtmlElement* element) const {
+ int flags = 0;
+
+ if (element->FindAttribute(s_async_) != NULL) {
+ flags |= kExecuteAsync;
+ }
+
+ if (element->FindAttribute(s_defer_) != NULL) {
+ flags |= kExecuteDefer;
+ }
+
+ // HTML5 notes that certain values of IE-proprietary 'for' and 'event'
+ // attributes are magic and are to be handled as if they're not there,
+ // while others will cause the script to not be run at all.
+ // Note: there is a disagreement between Chrome and Firefox on how
+ // empty ones are handled. We set kExecuteForEvent as it is the conservative
+ // value, requiring careful treatment by filters
+ const HtmlElement::Attribute* for_attr = element->FindAttribute(s_for_);
+ const HtmlElement::Attribute* event_attr = element->FindAttribute(s_event_);
+ if (for_attr != NULL && event_attr != NULL) {
+ if (Normalized(for_attr->value()) != "window") {
+ flags |= kExecuteForEvent;
+ }
+ std::string event_str = Normalized(event_attr->value());
+ if (event_str != "onload" && event_str != "onload()") {
+ flags |= kExecuteForEvent;
+ }
+ }
+
+ return flags;
+}
+
+std::string ScriptTagScanner::Normalized(const StringPiece& str) {
+ std::string normal_form;
+ TrimWhitespace(str, &normal_form);
+ LowerString(&normal_form);
+ return normal_form;
+}
+
+bool ScriptTagScanner::IsJsMime(const std::string& type_str) {
+ return javascript_mimetypes_.find(type_str) != javascript_mimetypes_.end();
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/script_tag_scanner_test.cc b/trunk/src/net/instaweb/rewriter/script_tag_scanner_test.cc
new file mode 100644
index 0000000..bdc4be3
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/script_tag_scanner_test.cc
@@ -0,0 +1,389 @@
+/**
+ * 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: mdsteele@google.com (Matthew D. Steele)
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/empty_html_filter.h"
+#include "net/instaweb/htmlparse/public/html_parse_test_base.h"
+#include "net/instaweb/rewriter/public/script_tag_scanner.h"
+#include "net/instaweb/util/public/gtest.h"
+
+namespace net_instaweb {
+
+class ScriptTagScannerTest : public HtmlParseTestBase {
+ protected:
+ ScriptTagScannerTest() : collector_(&html_parse_) {
+ html_parse_.AddFilter(&collector_);
+ }
+
+ virtual bool AddBody() const { return true; }
+
+ // Helper class to collect script information (language,
+ // and attributes)
+ class ScriptCollector : public EmptyHtmlFilter {
+ public:
+ ScriptCollector(HtmlParse* html_parse)
+ : script_tag_scanner_(html_parse) {
+ }
+
+ virtual void StartElement(HtmlElement* element) {
+ HtmlElement::Attribute* src;
+ ScriptInfo info;
+ info.classification =
+ script_tag_scanner_.ParseScriptElement(element, &src);
+ if (info.classification != ScriptTagScanner::kNonScript) {
+ if (src) {
+ info.url = src->value();
+ }
+ info.flags = script_tag_scanner_.ExecutionMode(element);
+ scripts_.push_back(info);
+ }
+ }
+
+ int Size() const { return static_cast<int>(scripts_.size()); }
+
+ const std::string& UrlAt(int pos) const {
+ return scripts_[pos].url;
+ }
+
+ ScriptTagScanner::ScriptClassification ClassificationAt(int pos) {
+ return scripts_[pos].classification;
+ }
+
+ int FlagsAt(int pos) {
+ return scripts_[pos].flags;
+ }
+
+ virtual const char* Name() const { return "ScriptCollector"; }
+
+ private:
+ struct ScriptInfo {
+ std::string url;
+ ScriptTagScanner::ScriptClassification classification;
+ int flags;
+ };
+
+ std::vector<ScriptInfo> scripts_;
+ ScriptTagScanner script_tag_scanner_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScriptCollector);
+ };
+
+ struct TestSpec {
+ const char* attributes;
+ int expected_flags;
+ };
+
+ // Checks to make sure each of attributes inside <script> produces
+ // the appropriate flags. The array is expected to be 0-terminated
+ void TestFlags(const TestSpec* test_spec) {
+ std::string html;
+ int test;
+ for (test = 0; test_spec[test].attributes; ++test) {
+ html += "<script " + std::string(test_spec[test].attributes) +
+ "></script>";
+ }
+
+ ValidateNoChanges("from_test_spec", html);
+
+ ASSERT_EQ(test, collector_.Size());
+ for (test = 0; test_spec[test].attributes; ++test) {
+ LOG(INFO) << test_spec[test].attributes;
+ EXPECT_EQ(test_spec[test].expected_flags, collector_.FlagsAt(test));
+ }
+ }
+
+ std::string ScriptWithType(const std::string& type) {
+ return "<script type=\"" + type + "\"></script>";
+ }
+
+ std::string ScriptWithLang(const std::string& type) {
+ return "<script language=\"" + type + "\"></script>";
+ }
+
+ ScriptCollector collector_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScriptTagScannerTest);
+};
+
+// Note: kNonScript is covered by the length counts,
+// as it will not go into the collector
+
+TEST_F(ScriptTagScannerTest, NotFoundScriptTag) {
+ ValidateNoChanges("noscript", "<noscript>");
+ ASSERT_EQ(0, collector_.Size());
+}
+
+TEST_F(ScriptTagScannerTest, FindNoScriptTag) {
+ ValidateNoChanges("simple_script", "<script src=\"myscript.js\"></script>");
+ ASSERT_EQ(1, collector_.Size());
+ EXPECT_EQ(std::string("myscript.js"), collector_.UrlAt(0));
+ EXPECT_EQ(ScriptTagScanner::kJavaScript, collector_.ClassificationAt(0));
+}
+
+TEST_F(ScriptTagScannerTest, TypeNoVal) {
+ // type with no value - handle as JS
+ ValidateNoChanges("simple_script", "<script type></script>");
+ ASSERT_EQ(1, collector_.Size());
+ EXPECT_EQ(std::string(), collector_.UrlAt(0));
+ EXPECT_EQ(ScriptTagScanner::kJavaScript, collector_.ClassificationAt(0));
+}
+
+TEST_F(ScriptTagScannerTest, TypeEmpty) {
+ // type is empty - handle as JS
+ ValidateNoChanges("simple_script", "<script type=""></script>");
+ ASSERT_EQ(1, collector_.Size());
+ EXPECT_EQ(std::string(), collector_.UrlAt(0));
+ EXPECT_EQ(ScriptTagScanner::kJavaScript, collector_.ClassificationAt(0));
+}
+
+TEST_F(ScriptTagScannerTest, TypeNoValHaveLang) {
+ // type is no-value, but language is there --- it matters
+ ValidateNoChanges("simple_script", "<script type language=tcl></script>");
+ ASSERT_EQ(1, collector_.Size());
+ EXPECT_EQ(std::string(), collector_.UrlAt(0));
+ EXPECT_EQ(ScriptTagScanner::kUnknownScript, collector_.ClassificationAt(0));
+}
+
+TEST_F(ScriptTagScannerTest, TypeLangSubordinate) {
+ // make sure type beats language
+ ValidateNoChanges("simple_script",
+ "<script type=\"text/ecmascript\" language=tcl></script>");
+ ASSERT_EQ(1, collector_.Size());
+ EXPECT_EQ(std::string(), collector_.UrlAt(0));
+ EXPECT_EQ(ScriptTagScanner::kJavaScript, collector_.ClassificationAt(0));
+}
+
+TEST_F(ScriptTagScannerTest, LangNoVal) {
+ // lang no value - handle as JS
+ ValidateNoChanges("simple_script", "<script language></script>");
+ ASSERT_EQ(1, collector_.Size());
+ EXPECT_EQ(std::string(), collector_.UrlAt(0));
+ EXPECT_EQ(ScriptTagScanner::kJavaScript, collector_.ClassificationAt(0));
+}
+
+TEST_F(ScriptTagScannerTest, LangEmpty) {
+ // lang is empty - handle as JS
+ ValidateNoChanges("simple_script", "<script language=""></script>");
+ ASSERT_EQ(1, collector_.Size());
+ EXPECT_EQ(std::string(), collector_.UrlAt(0));
+ EXPECT_EQ(ScriptTagScanner::kJavaScript, collector_.ClassificationAt(0));
+}
+
+TEST_F(ScriptTagScannerTest, TypeScripts) {
+ // various type values. Nothing fancy done with them. List of types is from
+ // HTML5 + a few ones that are not
+ ValidateNoChanges("script types",
+ ScriptWithType("application/ecmascript") + // 0
+ ScriptWithType("application/javascript") +
+ ScriptWithType("application/x-ecmascript") +
+ ScriptWithType("application/x-javascript") +
+ ScriptWithType("text/ecmascript") + // 4
+ ScriptWithType("text/javascript") +
+ ScriptWithType("text/javascript1.0") +
+ ScriptWithType("text/javascript1.1") +
+ ScriptWithType("text/javascript1.2") +
+ ScriptWithType("text/javascript1.3") + // 9
+ ScriptWithType("text/javascript1.4") +
+ ScriptWithType("text/javascript1.5") +
+ ScriptWithType("text/jscript") +
+ ScriptWithType("text/livescript") +
+ ScriptWithType("text/x-ecmascript") + // 14
+ ScriptWithType("text/x-javascript") + // 15 -- last valid one
+ ScriptWithType("text/tcl") +
+ ScriptWithType("text/ecmascript4") +
+ ScriptWithType("text/javascript2.0") +
+ ScriptWithType(" ")); // 19 -- last invalid one
+
+ ASSERT_EQ(20, collector_.Size());
+ for (int i = 0; i <= 15; ++i) {
+ EXPECT_EQ(std::string(), collector_.UrlAt(i));
+ EXPECT_EQ(ScriptTagScanner::kJavaScript, collector_.ClassificationAt(i));
+ }
+
+ for (int i = 16; i <= 19; ++i) {
+ EXPECT_EQ(std::string(), collector_.UrlAt(i));
+ EXPECT_EQ(ScriptTagScanner::kUnknownScript, collector_.ClassificationAt(i));
+ }
+}
+
+TEST_F(ScriptTagScannerTest, TypeScriptsNormalize) {
+ // For type, we need to support removal of leading/trailing whitespace
+ // and case folding
+ ValidateNoChanges("script types",
+ ScriptWithType(" application/ecmascRipt") + // 0
+ ScriptWithType(" applicAtion/javascript ") +
+ ScriptWithType("application/x-ecmaScript ") +
+ ScriptWithType(" applicAtion/x-javascript") +
+ ScriptWithType("text/Ecmascript") + // 4
+ ScriptWithType(" text/jaVasCript ") +
+ ScriptWithType(" TEXt/javascript1.0\t") +
+ ScriptWithType(" text/javascript1.1") +
+ ScriptWithType(" teXt/javascripT1.2") +
+ ScriptWithType("\ttExt/javascRipt1.3 ") + // 9
+ ScriptWithType(" text/javascRipT1.4 ") +
+ ScriptWithType(" Text/javAscript1.5 ") +
+ ScriptWithType(" Text/jscrIpt") +
+ ScriptWithType(" text/lIvescript") +
+ ScriptWithType("teXt/x-ecmasCript ") + // 14
+ ScriptWithType("tExt/x-jaVascript ") + // 15 -- last valid one
+ ScriptWithType("Text/Tcl ") +
+ ScriptWithType(" text/Ecmascript4") +
+ ScriptWithType("tExt/javascript2.0")+
+ ScriptWithType("text/javasc ript")); // 19 -- last invalid one
+
+ ASSERT_EQ(20, collector_.Size());
+ for (int i = 0; i <= 15; ++i) {
+ EXPECT_EQ(std::string(), collector_.UrlAt(i));
+ EXPECT_EQ(ScriptTagScanner::kJavaScript, collector_.ClassificationAt(i));
+ }
+
+ for (int i = 16; i <= 19; ++i) {
+ EXPECT_EQ(std::string(), collector_.UrlAt(i));
+ EXPECT_EQ(ScriptTagScanner::kUnknownScript, collector_.ClassificationAt(i));
+ }
+}
+
+TEST_F(ScriptTagScannerTest, LangScripts) {
+ // for language attribute, we are supposed to test text/lang
+ // against the valid mimetypes list
+ ValidateNoChanges("script langs",
+ ScriptWithLang("ecmascript") +
+ ScriptWithLang("javascript") +
+ ScriptWithLang("javascript1.0") +
+ ScriptWithLang("javascript1.1") +
+ ScriptWithLang("javascript1.2") + // 4
+ ScriptWithLang("javascript1.3") +
+ ScriptWithLang("javascript1.4") +
+ ScriptWithLang("javascript1.5") +
+ ScriptWithLang("jscript") +
+ ScriptWithLang("livescript") + // 9
+ ScriptWithLang("x-ecmascript") +
+ ScriptWithLang("x-javascript") + // 11 -- last valid one
+ ScriptWithLang("tcl") +
+ ScriptWithLang("ecmascript4") +
+ ScriptWithLang("javascript2.0")); // 14 -- last invalid one
+
+ ASSERT_EQ(15, collector_.Size());
+ for (int i = 0; i <= 11; ++i) {
+ EXPECT_EQ(std::string(), collector_.UrlAt(i));
+ EXPECT_EQ(ScriptTagScanner::kJavaScript, collector_.ClassificationAt(i));
+ }
+
+ for (int i = 12; i <= 14; ++i) {
+ EXPECT_EQ(std::string(), collector_.UrlAt(i));
+ EXPECT_EQ(ScriptTagScanner::kUnknownScript, collector_.ClassificationAt(i));
+ }
+}
+
+TEST_F(ScriptTagScannerTest, LangScriptsNormalizeCase) {
+ // Case normalization is to be done for language="" as well.
+ ValidateNoChanges("script langs",
+ ScriptWithLang("ecmasCript") +
+ ScriptWithLang("javAscript") +
+ ScriptWithLang("javascript1.0") +
+ ScriptWithLang("javascRipt1.1") +
+ ScriptWithLang("javascripT1.2") + // 4
+ ScriptWithLang("javaScrIpt1.3") +
+ ScriptWithLang("jaVasCript1.4") +
+ ScriptWithLang("javaScriPt1.5") +
+ ScriptWithLang("jscRiPt") +
+ ScriptWithLang("livEscript") + // 9
+ ScriptWithLang("x-ecmaScript") +
+ ScriptWithLang("x-jaVascript") + // 11 -- last valid one
+ ScriptWithLang("tCl") +
+ ScriptWithLang("ecmasCript4") +
+ ScriptWithLang("jaVascript2.0")); // 14 -- last invalid one
+
+ ASSERT_EQ(15, collector_.Size());
+ for (int i = 0; i <= 11; ++i) {
+ EXPECT_EQ(std::string(), collector_.UrlAt(i));
+ EXPECT_EQ(ScriptTagScanner::kJavaScript, collector_.ClassificationAt(i));
+ }
+
+ for (int i = 12; i <= 14; ++i) {
+ EXPECT_EQ(std::string(), collector_.UrlAt(i));
+ EXPECT_EQ(ScriptTagScanner::kUnknownScript, collector_.ClassificationAt(i));
+ }
+}
+
+TEST_F(ScriptTagScannerTest, LangScriptsNormalizeWhitespace) {
+ // Whitespace, however, is not removed for language, unlike with type,
+ // so all of these are to fail
+ ValidateNoChanges("script langs",
+ ScriptWithLang(" ecmascript") +
+ ScriptWithLang("javascript\t") +
+ ScriptWithLang(" javascript1.0 ") +
+ ScriptWithLang(" javascript1.1") +
+ ScriptWithLang("javascript1.2 ") + // 4
+ ScriptWithLang(" javascript1.3") +
+ ScriptWithLang("javascript1.4 ") +
+ ScriptWithLang(" javascript1.5") +
+ ScriptWithLang("jscript ") +
+ ScriptWithLang("livescript ") + // 9
+ ScriptWithLang(" x-ecmascript") +
+ ScriptWithLang("x-javascript\t") +
+ ScriptWithLang(" tcl ") +
+ ScriptWithLang("ecmascript4 ") +
+ ScriptWithLang(" javascript2.0")); // 14 -- last invalid one
+
+ ASSERT_EQ(15, collector_.Size());
+ for (int i = 0; i <= 14; ++i) {
+ EXPECT_EQ(std::string(), collector_.UrlAt(i));
+ EXPECT_EQ(ScriptTagScanner::kUnknownScript, collector_.ClassificationAt(i));
+ }
+}
+
+TEST_F(ScriptTagScannerTest, ForEvent) {
+ TestSpec for_event_tests[] = {
+ { "for event", ScriptTagScanner::kExecuteForEvent },
+ { "for=\"\" event=\"\"", ScriptTagScanner::kExecuteForEvent },
+ { "for", ScriptTagScanner::kExecuteSync },
+ { "event", ScriptTagScanner::kExecuteSync },
+ { "for=\"a\" event=\"b\"", ScriptTagScanner::kExecuteForEvent },
+ { "for=\"window\" event=\"b\"", ScriptTagScanner::kExecuteForEvent },
+ { "for=\"window\" event=\"b\" async",
+ ScriptTagScanner::kExecuteForEvent | ScriptTagScanner::kExecuteAsync },
+ { "for=\"window\" event=\"onload\"", ScriptTagScanner::kExecuteSync },
+ { "for=\"window\" event=onload async", ScriptTagScanner::kExecuteAsync },
+ { "for=\"window\" event=\"onload()\"", ScriptTagScanner::kExecuteSync },
+ { "for=\"wiNdow \" event=\" onLoad \"", ScriptTagScanner::kExecuteSync },
+ { "for=\" windOw\" event=\"OnloAd() \"", ScriptTagScanner::kExecuteSync },
+ { 0, ScriptTagScanner::kExecuteSync }
+ };
+ TestFlags(for_event_tests);
+}
+
+TEST_F(ScriptTagScannerTest, AsyncDefer) {
+ TestSpec async_defer_tests[] = {
+ { "language=tcl async", ScriptTagScanner::kExecuteAsync },
+ { "async=\"irrelevant\"", ScriptTagScanner::kExecuteAsync },
+ { "defer", ScriptTagScanner::kExecuteDefer },
+ { "defer async",
+ ScriptTagScanner::kExecuteDefer | ScriptTagScanner::kExecuteAsync },
+ { "language=tcl async src=a", ScriptTagScanner::kExecuteAsync },
+ { "async=\"irrelevant\" src=a", ScriptTagScanner::kExecuteAsync },
+ { "defer src=a", ScriptTagScanner::kExecuteDefer },
+ { "defer async src=a",
+ ScriptTagScanner::kExecuteDefer | ScriptTagScanner::kExecuteAsync },
+ { 0, ScriptTagScanner::kExecuteSync }
+ };
+ TestFlags(async_defer_tests);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/strip_scripts_filter.cc b/trunk/src/net/instaweb/rewriter/strip_scripts_filter.cc
new file mode 100644
index 0000000..4e0a9ab
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/strip_scripts_filter.cc
@@ -0,0 +1,35 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/rewriter/public/strip_scripts_filter.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+
+namespace net_instaweb {
+
+StripScriptsFilter::StripScriptsFilter(HtmlParse* html_parse)
+ : html_parse_(html_parse),
+ s_script_(html_parse->Intern("script")) {
+}
+
+void StripScriptsFilter::EndElement(HtmlElement* element) {
+ if (element->tag() == s_script_) {
+ html_parse_->DeleteElement(element);
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/strip_scripts_filter_test.cc b/trunk/src/net/instaweb/rewriter/strip_scripts_filter_test.cc
new file mode 100644
index 0000000..cd4e313
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/strip_scripts_filter_test.cc
@@ -0,0 +1,54 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/html_parse_test_base.h"
+#include "net/instaweb/rewriter/public/strip_scripts_filter.h"
+
+namespace net_instaweb {
+
+class StripScriptsFilterTest : public HtmlParseTestBase {
+ protected:
+ StripScriptsFilterTest()
+ : strip_scripts_filter_(&html_parse_) {
+ html_parse_.AddFilter(&strip_scripts_filter_);
+ }
+
+ virtual bool AddBody() const { return false; }
+
+ private:
+ StripScriptsFilter strip_scripts_filter_;
+
+ DISALLOW_COPY_AND_ASSIGN(StripScriptsFilterTest);
+};
+
+TEST_F(StripScriptsFilterTest, RemoveScriptSrc) {
+ ValidateExpected("remove_script_src",
+ "<head><script src='http://www.google.com/javascript"
+ "/ajax_apis.js'></script></head><body>Hello, world!</body>",
+ "<head></head><body>Hello, world!</body>");
+}
+
+TEST_F(StripScriptsFilterTest, RemoveScriptInline) {
+ ValidateExpected("remove_script_src",
+ "<head><script>alert('Alert, alert!')"
+ "</script></head><body>Hello, world!</body>",
+ "<head></head><body>Hello, world!</body>");
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/url_input_resource.cc b/trunk/src/net/instaweb/rewriter/url_input_resource.cc
new file mode 100644
index 0000000..a6f9c23
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/url_input_resource.cc
@@ -0,0 +1,247 @@
+/**
+ * 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)
+// jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/rewriter/public/url_input_resource.h"
+
+#include "base/basictypes.h"
+#include "net/instaweb/rewriter/public/domain_lawyer.h"
+#include "net/instaweb/rewriter/public/resource_manager.h"
+#include "net/instaweb/rewriter/public/rewrite_options.h"
+#include "net/instaweb/util/public/abstract_mutex.h"
+#include "net/instaweb/util/public/file_system.h"
+#include "net/instaweb/util/public/google_url.h"
+#include "net/instaweb/util/public/hasher.h"
+#include "net/instaweb/util/public/http_value.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/timer.h"
+#include "net/instaweb/util/public/url_async_fetcher.h"
+
+namespace net_instaweb {
+
+UrlInputResource::~UrlInputResource() {
+}
+
+// Shared fetch callback, used by both Load and LoadAndCallback
+class UrlResourceFetchCallback : public UrlAsyncFetcher::Callback {
+ public:
+ UrlResourceFetchCallback(ResourceManager* resource_manager,
+ const RewriteOptions* rewrite_options) :
+ resource_manager_(resource_manager),
+ rewrite_options_(rewrite_options),
+ message_handler_(NULL) { }
+ virtual ~UrlResourceFetchCallback() {}
+
+ void AddToCache(bool success) {
+ if (success) {
+ HTTPValue* value = http_value();
+ value->SetHeaders(*response_headers());
+ http_cache()->Put(url(), value, message_handler_);
+ } else {
+ http_cache()->RememberNotCacheable(url(), message_handler_);
+ }
+ }
+
+ bool Fetch(UrlAsyncFetcher* fetcher, MessageHandler* handler) {
+ // TODO(jmarantz): consider request_headers. E.g. will we ever
+ // get different resources depending on user-agent?
+ SimpleMetaData request_headers;
+ message_handler_ = handler;
+ std::string lock_name = StrCat(
+ resource_manager_->filename_prefix(),
+ resource_manager_->hasher()->Hash(url()),
+ ".lock");
+ int64 lock_timeout = fetcher->timeout_ms();
+ if (lock_timeout == UrlAsyncFetcher::kUnspecifiedTimeout) {
+ // Even if the fetcher never explicitly times out requests, they probably
+ // won't succeed after more than 2 minutes.
+ lock_timeout = 2 * Timer::kMinuteMs;
+ } else {
+ // Give a little slack for polling, writing the file, freeing the lock.
+ lock_timeout *= 2;
+ }
+ if (resource_manager_->file_system()->TryLockWithTimeout(
+ lock_name, lock_timeout, message_handler_).is_false()) {
+ message_handler_->Info(lock_name.c_str(), 0,
+ "Someone is already fetching %s ",
+ url().c_str());
+ // TODO(abliss): a per-unit-time statistic would be useful here.
+ if (should_yield()) {
+ DoneInternal(false);
+ delete this;
+ return false;
+ }
+ } else {
+ message_handler_->Info(lock_name.c_str(), 0,
+ "Locking %s for PID %ld",
+ url().c_str(), static_cast<long>(getpid()));
+ lock_name_ = lock_name;
+ }
+
+ std::string url_string = url(), origin_url;
+ bool ret = false;
+ const DomainLawyer* lawyer = rewrite_options_->domain_lawyer();
+ if (lawyer->MapOrigin(url_string, &origin_url)) {
+ if (origin_url != url_string) {
+ // If mapping the URL changes its host, then add a 'Host' header
+ // pointing to the origin URL's hostname.
+ GURL gurl = GoogleUrl::Create(url_string);
+ if (gurl.is_valid()) {
+ request_headers.Add(HttpAttributes::kHost, gurl.host().c_str());
+ }
+ }
+ ret = fetcher->StreamingFetch(
+ origin_url, request_headers, response_headers(), http_value(),
+ handler, this);
+ } else {
+ delete this;
+ }
+ return ret;
+ }
+
+ virtual void Done(bool success) {
+ AddToCache(success);
+ if (!lock_name_.empty()) {
+ message_handler_->Info(lock_name_.c_str(), 0,
+ "Unlocking %s for PID %ld with success=%s",
+ url().c_str(), static_cast<long>(getpid()),
+ success ? "true" : "false");
+ resource_manager_->file_system()->Unlock(lock_name_, message_handler_);
+ }
+ DoneInternal(success);
+ delete this;
+ }
+
+ // The two derived classes differ in how they provide the
+ // fields below. The Async callback gets them from the resource,
+ // which must be live at the time it is called. The ReadIfCached
+ // cannot rely on the resource still being alive when the callback
+ // is called, so it must keep them locally in the class.
+ virtual MetaData* response_headers() = 0;
+ virtual HTTPValue* http_value() = 0;
+ virtual std::string url() const = 0;
+ virtual HTTPCache* http_cache() = 0;
+ // If someone is already fetching this resource, should we yield to them and
+ // try again later? If so, return true. Otherwise, if we must fetch the
+ // resource regardless, return false.
+ // TODO(abliss): unit test this
+ virtual bool should_yield() = 0;
+
+ protected:
+ virtual void DoneInternal(bool success) {
+ }
+
+ ResourceManager* resource_manager_;
+ const RewriteOptions* rewrite_options_;
+ MessageHandler* message_handler_;
+
+ private:
+ std::string lock_name_;
+ DISALLOW_COPY_AND_ASSIGN(UrlResourceFetchCallback);
+};
+
+class UrlReadIfCachedCallback : public UrlResourceFetchCallback {
+ public:
+ UrlReadIfCachedCallback(const std::string& url, HTTPCache* http_cache,
+ ResourceManager* resource_manager,
+ const RewriteOptions* rewrite_options)
+ : UrlResourceFetchCallback(resource_manager, rewrite_options),
+ url_(url),
+ http_cache_(http_cache) {
+ }
+
+ // Indicate that it's OK for the callback to be executed on a different
+ // thread, as it only populates the cache, which is thread-safe.
+ virtual bool EnableThreaded() const { return true; }
+
+ virtual MetaData* response_headers() { return &response_headers_; }
+ virtual HTTPValue* http_value() { return &http_value_; }
+ virtual std::string url() const { return url_; }
+ virtual HTTPCache* http_cache() { return http_cache_; }
+ virtual bool should_yield() { return true; }
+
+ private:
+ std::string url_;
+ HTTPCache* http_cache_;
+ HTTPValue http_value_;
+ SimpleMetaData response_headers_;
+
+ DISALLOW_COPY_AND_ASSIGN(UrlReadIfCachedCallback);
+};
+
+bool UrlInputResource::Load(MessageHandler* handler) {
+ meta_data_.Clear();
+ value_.Clear();
+
+ HTTPCache* http_cache = resource_manager()->http_cache();
+ UrlReadIfCachedCallback* cb = new UrlReadIfCachedCallback(
+ url_, http_cache, resource_manager(), rewrite_options_);
+
+ // If the fetcher can satisfy the request instantly, then we
+ // can try to populate the resource from the cache.
+ bool data_available =
+ (cb->Fetch(resource_manager_->url_async_fetcher(), handler) &&
+ (http_cache->Find(url_, &value_, &meta_data_, handler) ==
+ HTTPCache::kFound));
+ return data_available;
+}
+
+
+class UrlReadAsyncFetchCallback : public UrlResourceFetchCallback {
+ public:
+ explicit UrlReadAsyncFetchCallback(Resource::AsyncCallback* callback,
+ UrlInputResource* resource)
+ : UrlResourceFetchCallback(resource->resource_manager(),
+ resource->rewrite_options()),
+ resource_(resource),
+ callback_(callback) {
+ }
+
+ virtual void DoneInternal(bool success) {
+ callback_->Done(success, resource_);
+ }
+
+ virtual MetaData* response_headers() { return &resource_->meta_data_; }
+ virtual HTTPValue* http_value() { return &resource_->value_; }
+ virtual std::string url() const { return resource_->url(); }
+ virtual HTTPCache* http_cache() {
+ return resource_->resource_manager()->http_cache();
+ }
+ virtual bool should_yield() { return false; }
+
+ private:
+ UrlInputResource* resource_;
+ Resource::AsyncCallback* callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(UrlReadAsyncFetchCallback);
+};
+
+void UrlInputResource::LoadAndCallback(AsyncCallback* callback,
+ MessageHandler* message_handler) {
+ CHECK(callback != NULL) << "A callback must be supplied, or else it will "
+ "not be possible to determine when it's safe to delete the resource.";
+ if (loaded()) {
+ callback->Done(true, this);
+ } else {
+ UrlReadAsyncFetchCallback* cb =
+ new UrlReadAsyncFetchCallback(callback, this);
+ cb->Fetch(resource_manager_->url_async_fetcher(), message_handler);
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/url_left_trim_filter.cc b/trunk/src/net/instaweb/rewriter/url_left_trim_filter.cc
new file mode 100644
index 0000000..db4a454
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/url_left_trim_filter.cc
@@ -0,0 +1,137 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#include "net/instaweb/rewriter/public/url_left_trim_filter.h"
+
+#include <vector>
+#include "net/instaweb/htmlparse/public/html_element.h"
+#include "net/instaweb/htmlparse/public/html_parse.h"
+#include "net/instaweb/util/public/statistics.h"
+#include "net/instaweb/util/public/string_util.h"
+
+namespace {
+
+// names for Statistics variables.
+const char kUrlTrims[] = "url_trims";
+const char kUrlTrimSavedBytes[] = "url_trim_saved_bytes";
+
+} // namespace
+
+namespace net_instaweb {
+
+UrlLeftTrimFilter::UrlLeftTrimFilter(HtmlParse* html_parse,
+ Statistics* stats)
+ : html_parse_(html_parse),
+ s_base_(html_parse->Intern("base")),
+ s_href_(html_parse->Intern("href")),
+ s_src_(html_parse->Intern("src")),
+ trim_count_((stats == NULL) ? NULL : stats->GetVariable(kUrlTrims)),
+ trim_saved_bytes_(
+ (stats == NULL) ? NULL : stats->GetVariable(kUrlTrimSavedBytes)) {
+}
+
+void UrlLeftTrimFilter::Initialize(Statistics* statistics) {
+ statistics->AddVariable(kUrlTrims);
+ statistics->AddVariable(kUrlTrimSavedBytes);
+}
+
+void UrlLeftTrimFilter::StartElement(HtmlElement* element) {
+ // TODO(jmaessen): handle other places urls might lurk in html.
+ // But never rewrite the base tag; always include its full url.
+ if (element->tag() != s_base_) {
+ TrimAttribute(element->FindAttribute(s_href_));
+ TrimAttribute(element->FindAttribute(s_src_));
+ }
+}
+
+void UrlLeftTrimFilter::AddTrimming(const StringPiece& trimming) {
+ CHECK_LT(0u, trimming.size());
+ left_trim_strings_.push_back(trimming.as_string());
+}
+
+void UrlLeftTrimFilter::AddBaseUrl(const StringPiece& base) {
+ size_t colon_pos = base.find(':');
+ size_t host_start = 0;
+ if (colon_pos != base.npos) {
+ StringPiece protocol(base.data(), colon_pos+1);
+ AddTrimming(protocol);
+ host_start = colon_pos + 3;
+ } else {
+ colon_pos = -1;
+ }
+ size_t first_slash_pos = base.find('/', host_start);
+ if (first_slash_pos != base.npos) {
+ StringPiece host_name(base.data() + colon_pos + 1,
+ first_slash_pos - colon_pos - 1);
+ AddTrimming(host_name);
+ size_t last_slash_pos = base.rfind('/');
+ if (last_slash_pos != base.npos &&
+ last_slash_pos > first_slash_pos) {
+ // Note that we leave a case on the floor here: when base is the root of a
+ // domain (such as http://www.nytimes.com/ ) we can strip the leading /
+ // off rooted urls. We do not do so as the path / is a proper prefix of a
+ // protocol-stripped url such as //www.google.com/, and we don't want to
+ // transform the latter into the incorrect relative url /www.google.com/.
+ // If we simply require last_slash_pos >= first_slash_pos we include this
+ // case, and sites like nytimes break badly.
+ StringPiece base_dir(base.data() + first_slash_pos,
+ last_slash_pos-first_slash_pos+1);
+ AddTrimming(base_dir);
+ }
+ }
+}
+
+// Left trim all strings in left_trim_strings_ from url, in order.
+// StringPiece supports left and right trimming in place (the only
+// mutation it permits).
+bool UrlLeftTrimFilter::Trim(StringPiece* url) {
+ bool trimmed = false;
+ for (StringVector::iterator i = left_trim_strings_.begin();
+ i != left_trim_strings_.end(); ++i) {
+ // First condition below guarantees that we never completely
+ // remove a url, leaving it empty.
+ if (url->length() > i->length() && url->starts_with(*i)) {
+ url->remove_prefix(i->length());
+ trimmed = true;
+ }
+ }
+ return trimmed;
+}
+
+// Trim the value of the given attribute, if the attribute is non-NULL.
+void UrlLeftTrimFilter::TrimAttribute(HtmlElement::Attribute* attr) {
+ if (attr != NULL) {
+ StringPiece val(attr->value());
+ size_t orig_size = val.size();
+ if (Trim(&val)) {
+ size_t saved = orig_size - val.size();
+ const char* q = attr->quote();
+ html_parse_->InfoHere(
+ "trimmed %u %s=%s%s%s to %s%s%s.", static_cast<unsigned>(saved),
+ attr->name().c_str(), q, attr->value(), q,
+ q, val.as_string().c_str(), q);
+ attr->SetValue(val);
+ if (trim_count_ != NULL) {
+ trim_count_->Add(1);
+ trim_saved_bytes_->Add(orig_size - val.size());
+ }
+ }
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/url_left_trim_filter_test.cc b/trunk/src/net/instaweb/rewriter/url_left_trim_filter_test.cc
new file mode 100644
index 0000000..6d4d27d
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/url_left_trim_filter_test.cc
@@ -0,0 +1,122 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#include "net/instaweb/rewriter/public/url_left_trim_filter.h"
+
+#include "base/basictypes.h"
+#include "net/instaweb/htmlparse/public/html_parse_test_base.h"
+#include "net/instaweb/util/public/gtest.h"
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+class UrlLeftTrimFilterTest : public HtmlParseTestBase {
+ protected:
+ UrlLeftTrimFilterTest()
+ : left_trim_filter_(&html_parse_, NULL) {
+ html_parse_.AddFilter(&left_trim_filter_);
+ }
+
+ void AddTrimming(const StringPiece& trimming) {
+ left_trim_filter_.AddTrimming(trimming);
+ }
+
+ void OneTrim(bool changed,
+ const StringPiece init, const StringPiece expected) {
+ StringPiece url(init);
+ EXPECT_EQ(changed, left_trim_filter_.Trim(&url));
+ EXPECT_EQ(expected, url);
+ }
+
+ virtual bool AddBody() const { return false; }
+
+ UrlLeftTrimFilter left_trim_filter_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(UrlLeftTrimFilterTest);
+};
+
+static const char kBase[] = "http://foo.bar/baz/";
+static const char kHttp[] = "http:";
+static const char kDomain[] = "//foo.bar";
+static const char kPath[] = "/baz/";
+
+TEST_F(UrlLeftTrimFilterTest, SimpleTrims) {
+ StringPiece spHttp(kHttp);
+ StringPiece spDomain(kDomain);
+ StringPiece spPath(kPath);
+ AddTrimming(spHttp);
+ AddTrimming(spDomain);
+ AddTrimming(spPath);
+ OneTrim(true, "http://www.google.com/", "//www.google.com/");
+ OneTrim(true, kBase, kPath);
+ OneTrim(true, "http://foo.bar/baz/quux", "quux");
+ OneTrim(true, "/baz/quux", "quux");
+ OneTrim(true, "//foo.bar/img/img1.jpg", "/img/img1.jpg");
+ OneTrim(false, "/img/img1.jpg", "/img/img1.jpg");
+ OneTrim(false, kHttp, kHttp);
+ OneTrim(true, "//foo.bar/baz/quux", "quux");
+}
+
+static const char kRootedBase[] = "http://foo.bar/";
+
+// Catch screw cases when a base url lies at the root of a domain.
+TEST_F(UrlLeftTrimFilterTest, RootedTrims) {
+ left_trim_filter_.AddBaseUrl(kRootedBase);
+ OneTrim(true, "http://www.google.com/", "//www.google.com/");
+ OneTrim(true, kBase, kPath);
+ OneTrim(false, "//www.google.com/", "//www.google.com/");
+ OneTrim(false, kPath, kPath);
+ OneTrim(false, "quux", "quux");
+}
+
+static const char kNone[] =
+ "<head><base href='ftp://what.the/heck/'/>"
+ "<link src='ftp://what.the/heck/'></head>"
+ "<body><a href='spdy://www.google.com/'>google</a>"
+ "<img src='file:///where/the/heck.jpg'/></body>";
+
+TEST_F(UrlLeftTrimFilterTest, NoChanges) {
+ left_trim_filter_.AddBaseUrl(kBase);
+ ValidateNoChanges("none forward", kNone);
+}
+
+static const char kSome[] =
+ "<head><base href='http://foo.bar/baz/'/>"
+ "<link src='http://foo.bar/baz/'></head>"
+ "<body><a href='http://www.google.com/'>google</a>"
+ "<img src='http://foo.bar/baz/nav.jpg'/>"
+ "<img src='http://foo.bar/img/img1.jpg'/>"
+ "<img src='/baz/img2.jpg'/>"
+ "<img src='//foo.bar/baz/widget.png'/></body>";
+
+static const char kSomeRewritten[] =
+ "<head><base href='http://foo.bar/baz/'/>"
+ "<link src='/baz/'></head>"
+ "<body><a href='//www.google.com/'>google</a>"
+ "<img src='nav.jpg'/>"
+ "<img src='/img/img1.jpg'/>"
+ "<img src='img2.jpg'/>"
+ "<img src='widget.png'/></body>";
+
+TEST_F(UrlLeftTrimFilterTest, SomeChanges) {
+ left_trim_filter_.AddBaseUrl(kBase);
+ ValidateExpected("some forward", kSome, kSomeRewritten);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/url_partnership.cc b/trunk/src/net/instaweb/rewriter/url_partnership.cc
new file mode 100644
index 0000000..24597a4
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/url_partnership.cc
@@ -0,0 +1,172 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/rewriter/public/url_partnership.h"
+
+#include <algorithm> // for std::min
+#include <string>
+#include "net/instaweb/rewriter/public/domain_lawyer.h"
+#include "net/instaweb/rewriter/public/rewrite_options.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/stl_util.h"
+
+namespace net_instaweb {
+
+UrlPartnership::UrlPartnership(const RewriteOptions* rewrite_options,
+ const GURL& original_request)
+ : rewrite_options_(rewrite_options) {
+ if (original_request.is_valid()) {
+ original_origin_and_path_ = GoogleUrl::Create(
+ GoogleUrl::AllExceptLeaf(original_request));
+ }
+}
+
+UrlPartnership::~UrlPartnership() {
+ STLDeleteElements(&gurl_vector_);
+}
+
+// Adds a URL to a combination. If it can be legally added, consulting
+// the DomainLawyer, then true is returned. AddUrl cannot be called
+// after Resolve (CHECK failure).
+bool UrlPartnership::AddUrl(const StringPiece& untrimmed_resource_url,
+ MessageHandler* handler) {
+ std::string resource_url, mapped_domain_name;
+ bool ret = false;
+ TrimWhitespace(untrimmed_resource_url, &resource_url);
+
+ if (resource_url.empty()) {
+ handler->Message(kInfo, "Cannot rewrite empty URL relative to %s",
+ original_origin_and_path_.possibly_invalid_spec().c_str());
+ }
+ else if (!original_origin_and_path_.is_valid()) {
+ handler->Message(kInfo, "Cannot rewrite %s relative to invalid url %s",
+ resource_url.c_str(),
+ original_origin_and_path_.possibly_invalid_spec().c_str());
+ } else {
+ // First resolve the original request to ensure that it is allowed by the
+ // options.
+ GURL resolved_request = GoogleUrl::Resolve(original_origin_and_path_,
+ resource_url);
+ if (!resolved_request.is_valid()) {
+ handler->Message(
+ kInfo, "URL %s cannot be resolved relative to base URL %s",
+ resource_url.c_str(), original_origin_and_path_.spec().c_str());
+ } else if (!rewrite_options_->IsAllowed(
+ GoogleUrl::Spec(resolved_request))) {
+ handler->Message(kInfo,
+ "Rewriting URL %s is disallowed via configuration",
+ GoogleUrl::Spec(resolved_request).c_str());
+ } else if (rewrite_options_->domain_lawyer()->MapRequestToDomain(
+ original_origin_and_path_, resource_url, &mapped_domain_name,
+ &resolved_request, handler)) {
+ if (gurl_vector_.empty()) {
+ domain_.swap(mapped_domain_name);
+ domain_gurl_ = GoogleUrl::Create(domain_).Resolve(
+ GoogleUrl::Path(original_origin_and_path_));
+ ret = true;
+ } else {
+ ret = (domain_ == mapped_domain_name);
+ }
+
+ if (ret) {
+ gurl_vector_.push_back(new GURL(resolved_request));
+ int index = gurl_vector_.size() - 1;
+ IncrementalResolve(index);
+ }
+ }
+ }
+ return ret;
+}
+
+void UrlPartnership::RemoveLast() {
+ CHECK(!gurl_vector_.empty());
+ int last = gurl_vector_.size() - 1;
+ delete gurl_vector_[last];
+ gurl_vector_.resize(last);
+
+ // Re-resolve the entire partnership in the absense of the influence of the
+ // ex-partner, by re-adding the GURLs one at a time.
+ common_components_.clear();
+ for (int i = 0, n = gurl_vector_.size(); i < n; ++i) {
+ IncrementalResolve(i);
+ }
+}
+
+void UrlPartnership::IncrementalResolve(int index) {
+ CHECK_LE(0, index);
+ CHECK_LT(index, static_cast<int>(gurl_vector_.size()));
+
+ // When tokenizing a URL, we don't want to omit empty segments
+ // because we need to avoid aliasing "http://x" with "/http:/x".
+ bool omit_empty = false;
+ std::vector<StringPiece> components;
+
+ if (index == 0) {
+ std::string base = GoogleUrl::AllExceptLeaf(*gurl_vector_[0]);
+ SplitStringPieceToVector(base, "/", &components, omit_empty);
+ components.pop_back(); // base ends with "/"
+ CHECK_LE(3U, components.size()); // expect {"http:", "", "x"...}
+ for (size_t i = 0; i < components.size(); ++i) {
+ const StringPiece& sp = components[i];
+ common_components_.push_back(std::string(sp.data(), sp.size()));
+ }
+ } else {
+ // Split each string on / boundaries, then compare these path elements
+ // until one doesn't match, then shortening common_components.
+ std::string all_but_leaf = GoogleUrl::AllExceptLeaf(*gurl_vector_[index]);
+ SplitStringPieceToVector(all_but_leaf, "/", &components, omit_empty);
+ components.pop_back(); // base ends with "/"
+ CHECK_LE(3U, components.size()); // expect {"http:", "", "x"...}
+
+ if (components.size() < common_components_.size()) {
+ common_components_.resize(components.size());
+ }
+ for (size_t c = 0; c < common_components_.size(); ++c) {
+ if (common_components_[c] != components[c]) {
+ common_components_.resize(c);
+ break;
+ }
+ }
+ }
+}
+
+std::string UrlPartnership::ResolvedBase() const {
+ std::string ret;
+ if (!common_components_.empty()) {
+ for (size_t c = 0; c < common_components_.size(); ++c) {
+ const std::string& component = common_components_[c];
+ ret += component;
+ ret += "/"; // initial segment is "http" with no leading /
+ }
+ }
+ return ret;
+}
+
+// Returns the relative path of a particular URL that was added into
+// the partnership. This requires that Resolve() be called first.
+std::string UrlPartnership::RelativePath(int index) const {
+ std::string resolved_base = ResolvedBase();
+ std::string spec = gurl_vector_[index]->spec();
+ CHECK_GE(spec.size(), resolved_base.size());
+ CHECK_EQ(StringPiece(spec.data(), resolved_base.size()),
+ StringPiece(resolved_base));
+ return std::string(spec.data() + resolved_base.size(),
+ spec.size() - resolved_base.size());
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/rewriter/url_partnership_test.cc b/trunk/src/net/instaweb/rewriter/url_partnership_test.cc
new file mode 100644
index 0000000..a641431
--- /dev/null
+++ b/trunk/src/net/instaweb/rewriter/url_partnership_test.cc
@@ -0,0 +1,231 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/rewriter/public/url_partnership.h"
+
+#include <string>
+#include "net/instaweb/rewriter/public/domain_lawyer.h"
+#include "net/instaweb/rewriter/public/rewrite_options.h"
+#include "net/instaweb/util/public/google_message_handler.h"
+#include "net/instaweb/util/public/google_url.h"
+#include "net/instaweb/util/public/gtest.h"
+
+namespace {
+
+const char kOriginalRequest[] = "http://www.nytimes.com/index.html";
+const char kResourceUrl1[] = "r/styles/style.css?appearance=reader/writer?";
+const char kResourceUrl2[] = "r/styles/style2.css?appearance=reader";
+const char kResourceUrl3[] = "r/main.css";
+const char kCdnResourceUrl[] = "http://graphics8.nytimes.com/styles/style.css";
+
+// Resources 1-3 but specified absolutely
+const char kAbsoluteResourceUrl1[] =
+ "http://www.nytimes.com/r/styles/style.css?appearance=reader/writer?";
+const char kAbsoluteResourceUrl2[] =
+ "http://www.nytimes.com/r/styles/style2.css?appearance=reader";
+const char kAbsoluteResourceUrl3[] = "http://www.nytimes.com/r/main.css";
+
+} // namespace
+
+
+namespace net_instaweb {
+
+class UrlPartnershipTest : public testing::Test {
+ protected:
+ UrlPartnershipTest()
+ : domain_lawyer_(options_.domain_lawyer()),
+ partnership_(&options_, GURL(kOriginalRequest)),
+ styles_path_("http://www.nytimes.com/r/styles/"),
+ r_path_("http://www.nytimes.com/r/"),
+ style_url_("style.css?appearance=reader/writer?"),
+ style2_url_("style2.css?appearance=reader") {
+ }
+
+ // Add up to 3 URLs -- url2 and url3 are ignored if null.
+ bool AddUrls(const char* url1, const char* url2, const char* url3) {
+ bool ret = partnership_.AddUrl(url1, &message_handler_);
+ if (url2 != NULL) {
+ ret &= partnership_.AddUrl(url2, &message_handler_);
+ }
+ if (url3 != NULL) {
+ ret &= partnership_.AddUrl(url3, &message_handler_);
+ }
+ return ret;
+ }
+
+ // Gets the full path of an index as a std::string.
+ std::string FullPath(int index) {
+ const GURL* gurl = partnership_.FullPath(index);
+ std::string spec = gurl->spec();
+ return std::string(spec.data(), spec.size());
+ }
+
+ RewriteOptions options_;
+ DomainLawyer* domain_lawyer_;
+ UrlPartnership partnership_;
+ std::string styles_path_;
+ std::string r_path_;
+ std::string style_url_;
+ std::string style2_url_;
+ GoogleMessageHandler message_handler_;
+};
+
+TEST_F(UrlPartnershipTest, OneUrlFlow) {
+ ASSERT_TRUE(AddUrls(kResourceUrl1, NULL, NULL));
+ ASSERT_EQ(1, partnership_.num_urls());
+ EXPECT_EQ(styles_path_, partnership_.ResolvedBase());
+ EXPECT_EQ(style_url_, partnership_.RelativePath(0));
+ EXPECT_EQ(styles_path_ + style_url_, FullPath(0));
+}
+
+TEST_F(UrlPartnershipTest, OneUrlFlowAbsolute) {
+ ASSERT_TRUE(AddUrls(kAbsoluteResourceUrl1, NULL, NULL));
+ ASSERT_EQ(1, partnership_.num_urls());
+ EXPECT_EQ(styles_path_, partnership_.ResolvedBase());
+ EXPECT_EQ(style_url_, partnership_.RelativePath(0));
+ EXPECT_EQ(styles_path_ + style_url_, FullPath(0));
+}
+
+TEST_F(UrlPartnershipTest, TwoUrlFlowSamePath) {
+ AddUrls(kResourceUrl1, kResourceUrl2, NULL);
+ ASSERT_EQ(2, partnership_.num_urls());
+ EXPECT_EQ(styles_path_, partnership_.ResolvedBase());
+ EXPECT_EQ(style_url_, partnership_.RelativePath(0));
+ EXPECT_EQ(style2_url_, partnership_.RelativePath(1));
+}
+
+TEST_F(UrlPartnershipTest, TwoUrlFlowSamePathMixed) {
+ AddUrls(kAbsoluteResourceUrl1, kResourceUrl2, NULL);
+ ASSERT_EQ(2, partnership_.num_urls());
+ EXPECT_EQ(styles_path_, partnership_.ResolvedBase());
+ EXPECT_EQ(style_url_, partnership_.RelativePath(0));
+ EXPECT_EQ(style2_url_, partnership_.RelativePath(1));
+}
+
+TEST_F(UrlPartnershipTest, ThreeUrlFlowDifferentPaths) {
+ AddUrls(kResourceUrl1, kResourceUrl2, kResourceUrl3);
+ ASSERT_EQ(3, partnership_.num_urls());
+ EXPECT_EQ(r_path_, partnership_.ResolvedBase());
+ // We add 2 to the expected values of the 3 kResourceUrl* below to
+ // skip over the "r/".
+ EXPECT_EQ(std::string(kResourceUrl1 + 2), partnership_.RelativePath(0));
+ EXPECT_EQ(std::string(kResourceUrl2 + 2), partnership_.RelativePath(1));
+ EXPECT_EQ(std::string(kResourceUrl3 + 2), partnership_.RelativePath(2));
+}
+
+TEST_F(UrlPartnershipTest, ThreeUrlFlowDifferentPathsAbsolute) {
+ AddUrls(kAbsoluteResourceUrl1, kAbsoluteResourceUrl2, kAbsoluteResourceUrl3);
+ ASSERT_EQ(3, partnership_.num_urls());
+ EXPECT_EQ(r_path_, partnership_.ResolvedBase());
+ // We add 2 to the expected values of the 3 kResourceUrl* below to
+ // skip over the "r/".
+ EXPECT_EQ(std::string(kResourceUrl1 + 2), partnership_.RelativePath(0));
+ EXPECT_EQ(std::string(kResourceUrl2 + 2), partnership_.RelativePath(1));
+ EXPECT_EQ(std::string(kResourceUrl3 + 2), partnership_.RelativePath(2));
+}
+
+TEST_F(UrlPartnershipTest, ThreeUrlFlowDifferentPathsMixed) {
+ AddUrls(kAbsoluteResourceUrl1, kResourceUrl2, kAbsoluteResourceUrl3);
+ ASSERT_EQ(3, partnership_.num_urls());
+ EXPECT_EQ(r_path_, partnership_.ResolvedBase());
+ // We add 2 to the expected values of the 3 kResourceUrl* below to
+ // skip over the "r/".
+ EXPECT_EQ(std::string(kResourceUrl1 + 2), partnership_.RelativePath(0));
+ EXPECT_EQ(std::string(kResourceUrl2 + 2), partnership_.RelativePath(1));
+ EXPECT_EQ(std::string(kResourceUrl3 + 2), partnership_.RelativePath(2));
+}
+
+TEST_F(UrlPartnershipTest, ExternalDomainNotDeclared) {
+ EXPECT_FALSE(AddUrls(kCdnResourceUrl, NULL, NULL));
+}
+
+TEST_F(UrlPartnershipTest, ExternalDomainDeclared) {
+ domain_lawyer_->AddDomain("http://graphics8.nytimes.com", &message_handler_);
+ EXPECT_TRUE(partnership_.AddUrl(kCdnResourceUrl, &message_handler_));
+}
+
+TEST_F(UrlPartnershipTest, ExternalDomainDeclaredButNotMapped) {
+ // This test shows that while we can start partnerships from nytimes.com
+ // or graphics8.nytimes.com, we cannot combine those without a mapping.
+ domain_lawyer_->AddDomain("http://graphics8.nytimes.com", &message_handler_);
+ EXPECT_TRUE(partnership_.AddUrl(kCdnResourceUrl, &message_handler_));
+ EXPECT_FALSE(partnership_.AddUrl(kResourceUrl1, &message_handler_));
+}
+
+TEST_F(UrlPartnershipTest, AbsExternalDomainDeclaredButNotMapped) {
+ // This test shows that while we can start partnerships from nytimes.com
+ // or graphics8.nytimes.com, we cannot combine those without a mapping.
+ domain_lawyer_->AddDomain("http://graphics8.nytimes.com", &message_handler_);
+ EXPECT_TRUE(partnership_.AddUrl(kCdnResourceUrl, &message_handler_));
+ EXPECT_FALSE(partnership_.AddUrl(kAbsoluteResourceUrl1, &message_handler_));
+}
+
+TEST_F(UrlPartnershipTest, EmptyTail) {
+ EXPECT_FALSE(partnership_.AddUrl("", &message_handler_));
+ EXPECT_TRUE(partnership_.AddUrl("http://www.nytimes.com",
+ &message_handler_));
+ EXPECT_TRUE(partnership_.AddUrl("http://www.nytimes.com/",
+ &message_handler_));
+}
+
+TEST_F(UrlPartnershipTest, EmptyWithPartner) {
+ UrlPartnership p(&options_, GURL("http://www.google.com/styles/x.html"));
+ EXPECT_TRUE(p.AddUrl("/styles", &message_handler_));
+ EXPECT_FALSE(p.AddUrl("", &message_handler_));
+ EXPECT_TRUE(p.AddUrl("/", &message_handler_));
+ EXPECT_TRUE(p.AddUrl("..", &message_handler_));
+}
+
+TEST_F(UrlPartnershipTest, NeedsATrim) {
+ AddUrls(" http://www.nytimes.com/needs_a_trim.jpg ", NULL, NULL);
+ EXPECT_EQ(std::string("needs_a_trim.jpg"), partnership_.RelativePath(0));
+}
+
+TEST_F(UrlPartnershipTest, RemoveLast) {
+ AddUrls(kAbsoluteResourceUrl1, kAbsoluteResourceUrl2, kAbsoluteResourceUrl3);
+ EXPECT_EQ(r_path_, partnership_.ResolvedBase());
+ partnership_.RemoveLast();
+ EXPECT_EQ(styles_path_, partnership_.ResolvedBase());
+}
+
+TEST_F(UrlPartnershipTest, ResourcesFromMappedDomains) {
+ domain_lawyer_->AddRewriteDomainMapping(
+ "http://graphics8.nytimes.com", "http://www.nytimes.com",
+ &message_handler_);
+ domain_lawyer_->AddRewriteDomainMapping(
+ "http://graphics8.nytimes.com", "http://styles.com", &message_handler_);
+
+ // We can legally combine resources across multiple domains if they are
+ // all mapped together
+ ASSERT_TRUE(AddUrls(kCdnResourceUrl, kResourceUrl1,
+ "http://styles.com/external.css"));
+ EXPECT_EQ("http://graphics8.nytimes.com/", partnership_.ResolvedBase());
+}
+
+TEST_F(UrlPartnershipTest, AllowDisallow) {
+ // This test shows that while we can start partnerships from nytimes.com
+ // or graphics8.nytimes.com, we cannot combine those without a mapping.
+ domain_lawyer_->AddDomain("http://graphics8.nytimes.com", &message_handler_);
+ options_.Disallow("*/*.css");
+ options_.Allow("*/a*.css");
+ EXPECT_FALSE(partnership_.AddUrl("foo.css", &message_handler_));
+ EXPECT_TRUE(partnership_.AddUrl("afoo.css", &message_handler_));
+ EXPECT_TRUE(partnership_.AddUrl("foo.jpg", &message_handler_));
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/abstract_mutex.cc b/trunk/src/net/instaweb/util/abstract_mutex.cc
new file mode 100644
index 0000000..1f5d81b
--- /dev/null
+++ b/trunk/src/net/instaweb/util/abstract_mutex.cc
@@ -0,0 +1,26 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/abstract_mutex.h"
+
+namespace net_instaweb {
+
+AbstractMutex::~AbstractMutex() {
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/base64_test.cc b/trunk/src/net/instaweb/util/base64_test.cc
new file mode 100644
index 0000000..50c5033
--- /dev/null
+++ b/trunk/src/net/instaweb/util/base64_test.cc
@@ -0,0 +1,143 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+// Unit-test the base64 encoder.
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "net/instaweb/util/public/base64_util.h"
+#include "net/instaweb/util/public/google_message_handler.h"
+#include "net/instaweb/util/public/gtest.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/string_writer.h"
+
+namespace {
+
+const char chinese_data[] = "中华网,中华,中国,中文网,中国新闻,香港新闻,"
+ "国际新闻,中文新闻,新闻,港台新闻,两会,嫦娥一号";
+const int chinese_size = sizeof(chinese_data) - 1;
+
+// Also test some binary data, including embedded nulls, 2^7-1, 2^8-1
+const char binary_data[] = "\0\1\2\3\4\5\6\7\10\0\177\176\175\377\376";
+const int binary_size = sizeof(binary_data) - 1;
+
+class Codec {
+ public:
+ virtual ~Codec() {}
+ virtual void encode(const std::string& in, std::string* out) const = 0;
+ virtual bool decode(const std::string& in, std::string* out) const = 0;
+};
+
+class WebSafeBase64Codec : public Codec {
+ public:
+ virtual void encode(const std::string& in, std::string* out) const {
+ net_instaweb::Web64Encode(in, out);
+ }
+ virtual bool decode(const std::string& in, std::string* out) const {
+ return net_instaweb::Web64Decode(in, out);
+ }
+};
+
+class MimeBase64Codec : public Codec {
+ public:
+ virtual void encode(const std::string& in, std::string* out) const {
+ net_instaweb::Mime64Encode(in, out);
+ }
+ virtual bool decode(const std::string& in, std::string* out) const {
+ return net_instaweb::Mime64Decode(in, out);
+ }
+};
+
+} // namespace
+
+namespace net_instaweb {
+
+class Base64Test : public testing::Test {
+ protected:
+ Base64Test()
+ : chinese_(chinese_data, chinese_size),
+ binary_(binary_data, binary_size),
+ web64_codec_(),
+ mime64_codec_() {
+ }
+
+ void TestWeb64(const Codec &codec, const std::string& input) {
+ std::string encoded, decoded;
+ codec.encode(input, &encoded);
+ ASSERT_TRUE(codec.decode(encoded, &decoded));
+ EXPECT_EQ(input, decoded);
+ }
+
+ // Tests that attempts to decode a string that is not properly base64
+ // encoded will gracefully fail (Web64Decode returns false) rather than
+ // crash or produce invalid output. corrupt_char must be a character
+ // that is not in the base64 char-set.
+ //
+ // If the 'index' is specified as a negative number, it will be taken
+ // as an offset from the end of the string.
+ void TestCorrupt(const Codec &codec,
+ const std::string& input, char corrupt_char, int index) {
+ std::string encoded, decoded;
+ codec.encode(input, &encoded);
+ if (index < 0) {
+ index = encoded.size() + index;
+ }
+ encoded[index] = corrupt_char;
+ ASSERT_FALSE(codec.decode(encoded, &decoded));
+ }
+
+ std::string chinese_;
+ std::string binary_;
+ WebSafeBase64Codec web64_codec_;
+ MimeBase64Codec mime64_codec_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Base64Test);
+};
+
+TEST_F(Base64Test, Chinese) {
+ TestWeb64(web64_codec_, chinese_);
+ TestWeb64(mime64_codec_, chinese_);
+}
+
+TEST_F(Base64Test, Binary) {
+ TestWeb64(web64_codec_, binary_);
+ TestWeb64(mime64_codec_, binary_);
+}
+
+TEST_F(Base64Test, CorruptFirst) {
+ TestCorrupt(web64_codec_, chinese_, '@', 0);
+ TestCorrupt(mime64_codec_, chinese_, '@', 0);
+}
+
+TEST_F(Base64Test, CorruptMiddle) {
+ TestCorrupt(web64_codec_, chinese_, ':', chinese_.size() / 2);
+ TestCorrupt(mime64_codec_, chinese_, ':', chinese_.size() / 2);
+}
+
+TEST_F(Base64Test, CorruptEnd) {
+ // I wanted to put the '/' as the last character, but it turns out
+ // that encoders may put '=' characters in to pad to a multiple of
+ // 4 bytes, and the decoder stops decoding when it gets to the first
+ // pad character, so changing "==" to "=/" has no effect.
+ TestCorrupt(web64_codec_, chinese_, '/', -4);
+ TestCorrupt(mime64_codec_, chinese_, '_', -4);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/cache_fetcher_test.cc b/trunk/src/net/instaweb/util/cache_fetcher_test.cc
new file mode 100644
index 0000000..a694cd4
--- /dev/null
+++ b/trunk/src/net/instaweb/util/cache_fetcher_test.cc
@@ -0,0 +1,25 @@
+/**
+ * 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/util/cache_fetcher_test.h"
+
+namespace net_instaweb {
+
+const int CacheFetcherTest::kMaxSize = 10000;
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/cache_fetcher_test.h b/trunk/src/net/instaweb/util/cache_fetcher_test.h
new file mode 100644
index 0000000..97a1e73
--- /dev/null
+++ b/trunk/src/net/instaweb/util/cache_fetcher_test.h
@@ -0,0 +1,58 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+// Unit-test framework for caching fetchers. This is used by
+// both cache_url_fetcher_test.cc and cache_url_async_fetcher_test.cc.
+
+#ifndef NET_INSTAWEB_UTIL_CACHE_FETCHER_TEST_H_
+#define NET_INSTAWEB_UTIL_CACHE_FETCHER_TEST_H_
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "net/instaweb/util/fetcher_test.h"
+#include "net/instaweb/util/public/google_message_handler.h"
+#include "net/instaweb/util/public/http_cache.h"
+#include "net/instaweb/util/public/lru_cache.h"
+#include "net/instaweb/util/public/mock_timer.h"
+
+namespace net_instaweb {
+
+class CacheFetcherTest : public FetcherTest {
+ protected:
+ static const int kMaxSize;
+
+ CacheFetcherTest()
+ : mock_timer_(0),
+ http_cache_(new LRUCache(kMaxSize), &mock_timer_) {
+ int64 start_time_ms;
+ bool parsed = MetaData::ParseTime(kStartDate, &start_time_ms);
+ CHECK(parsed);
+ mock_timer_.set_time_ms(start_time_ms);
+ }
+
+ MockTimer mock_timer_;
+ HTTPCache http_cache_;
+ GoogleMessageHandler message_handler_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CacheFetcherTest);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_CACHE_FETCHER_TEST_H_
diff --git a/trunk/src/net/instaweb/util/cache_interface.cc b/trunk/src/net/instaweb/util/cache_interface.cc
new file mode 100644
index 0000000..16ca26d
--- /dev/null
+++ b/trunk/src/net/instaweb/util/cache_interface.cc
@@ -0,0 +1,26 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/cache_interface.h"
+
+namespace net_instaweb {
+
+CacheInterface::~CacheInterface() {
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/cache_url_async_fetcher.cc b/trunk/src/net/instaweb/util/cache_url_async_fetcher.cc
new file mode 100644
index 0000000..d04b345
--- /dev/null
+++ b/trunk/src/net/instaweb/util/cache_url_async_fetcher.cc
@@ -0,0 +1,112 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/cache_url_async_fetcher.h"
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/cache_url_fetcher.h"
+#include "net/instaweb/util/public/http_cache.h"
+#include "net/instaweb/util/public/http_value.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include "net/instaweb/util/public/string_writer.h"
+
+namespace net_instaweb {
+
+namespace {
+
+// This version of the caching async fetcher callback uses the
+// caller-supplied response-header buffer, and also forwards the
+// content to the client before caching it.
+class ForwardingAsyncFetch : public CacheUrlFetcher::AsyncFetch {
+ public:
+ ForwardingAsyncFetch(const StringPiece& url, HTTPCache* cache,
+ MessageHandler* handler, Callback* callback,
+ Writer* writer, MetaData* response_headers,
+ bool force_caching)
+ : CacheUrlFetcher::AsyncFetch(url, cache, handler, force_caching),
+ callback_(callback),
+ client_writer_(writer),
+ response_headers_(response_headers) {
+ }
+
+ virtual void Done(bool success) {
+ // Copy the data to the client even with a failure; there may be useful
+ // error messages in the content.
+ StringPiece contents;
+ if (value_.ExtractContents(&contents)) {
+ client_writer_->Write(contents, message_handler_);
+ } else {
+ success = false;
+ }
+
+ // Update the cache before calling the client Done callback, which might
+ // delete the headers. Note that if the value is not cacheable, we will
+ // record in the cache that this entry is not cacheable, but we will still
+ // call the async fetcher callback with the value. This allows us to
+ // do resource-serving via CacheUrlAsyncFetcher, where we need to serve
+ // the resources even if they are not cacheable.
+ //
+ // We do not update the cache at all if the fetch failed.
+ if (success) {
+ UpdateCache();
+ }
+
+ callback_->Done(success);
+ delete this;
+ }
+
+ virtual MetaData* ResponseHeaders() { return response_headers_; }
+
+ private:
+ Callback* callback_;
+ Writer* client_writer_;
+ MetaData* response_headers_;
+
+ DISALLOW_COPY_AND_ASSIGN(ForwardingAsyncFetch);
+};
+
+} // namespace
+
+CacheUrlAsyncFetcher::~CacheUrlAsyncFetcher() {
+}
+
+bool CacheUrlAsyncFetcher::StreamingFetch(
+ const std::string& url, const MetaData& request_headers,
+ MetaData* response_headers, Writer* writer, MessageHandler* handler,
+ Callback* callback) {
+ HTTPValue value;
+ StringPiece contents;
+ bool ret = false;
+ if ((http_cache_->Find(url.c_str(), &value, response_headers, handler) ==
+ HTTPCache::kFound) &&
+ !CacheUrlFetcher::RememberNotCached(*response_headers) &&
+ value.ExtractContents(&contents)) {
+ bool success = writer->Write(contents, handler);
+ callback->Done(success);
+ ret = true;
+ } else {
+ response_headers->Clear();
+ ForwardingAsyncFetch* fetch = new ForwardingAsyncFetch(
+ url, http_cache_, handler, callback, writer, response_headers,
+ force_caching_);
+ fetch->Start(fetcher_, request_headers);
+ }
+ return ret;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/cache_url_async_fetcher_test.cc b/trunk/src/net/instaweb/util/cache_url_async_fetcher_test.cc
new file mode 100644
index 0000000..9dd66ca
--- /dev/null
+++ b/trunk/src/net/instaweb/util/cache_url_async_fetcher_test.cc
@@ -0,0 +1,105 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+// Unit-test the async caching fetcher, using a mock fetcher, and an async
+// wrapper around that.
+
+#include "net/instaweb/util/public/cache_url_async_fetcher.h"
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/cache_fetcher_test.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+
+namespace net_instaweb {
+
+class CacheUrlAsyncFetcherTest : public CacheFetcherTest {
+ protected:
+ CacheUrlAsyncFetcherTest()
+ : cache_fetcher_(&http_cache_, &mock_async_fetcher_) {
+ }
+
+ virtual UrlAsyncFetcher* async_fetcher() { return &cache_fetcher_; }
+
+ CacheUrlAsyncFetcher cache_fetcher_;
+};
+
+TEST_F(CacheUrlAsyncFetcherTest, TestAsyncCache) {
+ // With the async cached fetching interface, we will expect even the
+ // initial request to succeed, once the callbacks are run.
+ bool callback_called1, callback_called2, callback_called3;
+ EXPECT_EQ(1, CountFetchesAsync(kGoodUrl, true, &callback_called1));
+ EXPECT_FALSE(callback_called1);
+
+ EXPECT_EQ(1, CountFetchesAsync(kGoodUrl, true, &callback_called2));
+ EXPECT_FALSE(callback_called1);
+ EXPECT_FALSE(callback_called2);
+
+ mock_async_fetcher_.CallCallbacks();
+ EXPECT_TRUE(callback_called1);
+ EXPECT_TRUE(callback_called2);
+
+ EXPECT_EQ(0, CountFetchesAsync(kGoodUrl, true, &callback_called3));
+ // No async fetcher callbacks were queued because the content
+ // was cached, so no need to call CallCallbacks() again here.
+ EXPECT_TRUE(callback_called3);
+}
+
+TEST_F(CacheUrlAsyncFetcherTest, TestAsyncNotCached) {
+ // With the async cached fetching interface, we will expect even the
+ // initial request to succeed, once the callbacks are run.
+ bool callback_called1, callback_called2, callback_called3;
+ EXPECT_EQ(1, CountFetchesAsync(kNotCachedUrl, true, &callback_called1));
+ EXPECT_FALSE(callback_called1);
+
+ EXPECT_EQ(1, CountFetchesAsync(kNotCachedUrl, true, &callback_called2));
+ EXPECT_FALSE(callback_called1);
+ EXPECT_FALSE(callback_called2);
+
+ mock_async_fetcher_.CallCallbacks();
+ EXPECT_TRUE(callback_called1);
+ EXPECT_TRUE(callback_called2);
+
+ // This is not a proper cache and does not distinguish between cacheable
+ // or non-cacheable URLs.
+ EXPECT_EQ(1, CountFetchesAsync(kNotCachedUrl, true, &callback_called3));
+ EXPECT_FALSE(callback_called3);
+ mock_async_fetcher_.CallCallbacks(); // Otherwise memory will be leaked
+ EXPECT_TRUE(callback_called3);
+}
+
+TEST_F(CacheUrlAsyncFetcherTest, TestCacheWithASyncFetcherFail) {
+ bool callback_called1, callback_called2, callback_called3;
+
+ EXPECT_EQ(1, CountFetchesAsync(kBadUrl, false, &callback_called1));
+ EXPECT_FALSE(callback_called1);
+
+ EXPECT_EQ(1, CountFetchesAsync(kBadUrl, false, &callback_called2));
+ EXPECT_FALSE(callback_called1);
+ EXPECT_FALSE(callback_called2);
+
+ mock_async_fetcher_.CallCallbacks();
+ EXPECT_TRUE(callback_called1);
+ EXPECT_TRUE(callback_called2);
+
+ EXPECT_EQ(1, CountFetchesAsync(kBadUrl, false, &callback_called3));
+ EXPECT_FALSE(callback_called3);
+ mock_async_fetcher_.CallCallbacks(); // Otherwise memory will be leaked
+ EXPECT_TRUE(callback_called3);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/cache_url_fetcher.cc b/trunk/src/net/instaweb/util/cache_url_fetcher.cc
new file mode 100644
index 0000000..45aa8bc
--- /dev/null
+++ b/trunk/src/net/instaweb/util/cache_url_fetcher.cc
@@ -0,0 +1,184 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/cache_url_fetcher.h"
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/http_cache.h"
+#include "net/instaweb/util/public/http_value.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include "net/instaweb/util/public/string_writer.h"
+#include "net/instaweb/util/public/timer.h"
+#include "net/instaweb/util/public/url_async_fetcher.h"
+
+namespace net_instaweb {
+
+namespace {
+
+const char kRememberNotCached[] = "X-Instaweb-Disable-cache";
+
+// The synchronous version of the caching fetch must supply a
+// response_headers buffer that will still be valid at when the fetch
+// completes and the callback executes.
+class AsyncFetchWithHeaders : public CacheUrlFetcher::AsyncFetch {
+ public:
+ AsyncFetchWithHeaders(const StringPiece& url, HTTPCache* cache,
+ MessageHandler* handler, bool force_caching)
+ : CacheUrlFetcher::AsyncFetch(url, cache, handler, force_caching) {
+ }
+
+ virtual MetaData* ResponseHeaders() {
+ return &response_headers_;
+ }
+ private:
+ SimpleMetaData response_headers_;
+
+ DISALLOW_COPY_AND_ASSIGN(AsyncFetchWithHeaders);
+};
+
+} // namespace
+
+CacheUrlFetcher::AsyncFetch::AsyncFetch(const StringPiece& url,
+ HTTPCache* cache,
+ MessageHandler* handler,
+ bool force_caching)
+ : message_handler_(handler),
+ url_(url.data(), url.size()),
+ http_cache_(cache),
+ force_caching_(force_caching) {
+}
+
+CacheUrlFetcher::AsyncFetch::~AsyncFetch() {
+}
+
+/*
+ * Note: this can be called from a different thread than the one where
+ * the request was made. We are depending on the caches being thread-safe
+ * if necessary.
+ */
+void CacheUrlFetcher::AsyncFetch::UpdateCache() {
+ // TODO(jmarantz): allow configuration of whether we ignore
+ // IsProxyCacheable, e.g. for content served from the same host
+ MetaData* response_headers = ResponseHeaders();
+ if ((http_cache_->Query(url_.c_str()) == CacheInterface::kNotFound)) {
+ if (force_caching_ || response_headers->IsProxyCacheable()) {
+ value_.SetHeaders(*response_headers);
+ http_cache_->Put(url_.c_str(), &value_, message_handler_);
+ } else {
+ // Leave value_ alone as we prep a cache entry to indicate that
+ // this url is not cacheable. This is because this code is
+ // shared with cache_url_async_fetcher.cc, which needs to
+ // actually pass through the real value and headers, even while
+ // remembering the non-cachability of the URL.
+ HTTPValue dummy_value;
+ SimpleMetaData remember_not_cached;
+
+ // We need to set the header status code to 'OK' to satisfy
+ // HTTPCache::IsCurrentlyValid. We rely on the detection of the
+ // header X-Instaweb-Disable-cache to avoid letting this escape into
+ // the wild. We may want to revisit if this proves problematic.
+ remember_not_cached.SetStatusAndReason(HttpStatus::kOK);
+ remember_not_cached.SetDate(http_cache_->timer()->NowMs());
+ remember_not_cached.Add("Cache-control", "max-age=300");
+ remember_not_cached.Add(kRememberNotCached, "1"); // value doesn't matter
+ dummy_value.Write("", message_handler_);
+ dummy_value.SetHeaders(remember_not_cached);
+ http_cache_->Put(url_.c_str(), &dummy_value, message_handler_);
+ }
+ }
+}
+
+void CacheUrlFetcher::AsyncFetch::Done(bool success) {
+ if (success) {
+ UpdateCache();
+ } else {
+ message_handler_->Info(url_.c_str(), 0, "Fetch failed, not caching.");
+ // TODO(jmarantz): cache that this request is not fetchable
+ }
+ delete this;
+}
+
+bool CacheUrlFetcher::AsyncFetch::EnableThreaded() const {
+ // Our cache implementations are thread-safe, so it's OK to update
+ // them asynchronously.
+ return true;
+}
+
+void CacheUrlFetcher::AsyncFetch::Start(
+ UrlAsyncFetcher* fetcher, const MetaData& request_headers) {
+ fetcher->StreamingFetch(url_, request_headers, ResponseHeaders(),
+ &value_, message_handler_, this);
+}
+
+CacheUrlFetcher::~CacheUrlFetcher() {
+}
+
+bool CacheUrlFetcher::RememberNotCached(const MetaData& headers) {
+ CharStarVector not_cached_values;
+ return headers.Lookup(kRememberNotCached, ¬_cached_values);
+}
+
+bool CacheUrlFetcher::StreamingFetchUrl(
+ const std::string& url, const MetaData& request_headers,
+ MetaData* response_headers, Writer* writer, MessageHandler* handler) {
+ bool ret = false;
+ HTTPValue value;
+ StringPiece contents;
+ ret = ((http_cache_->Find(url.c_str(), &value, response_headers, handler)
+ == HTTPCache::kFound) &&
+ value.ExtractContents(&contents));
+ if (ret) {
+ // If we have remembered that this value is not cachable, then mutate
+ // the reponse code and return false. Note that we must use an X-code
+ // in the headers, rather than the status code, so that HTTPCache will
+ // not reject the item on retrieval, spoiling our ability to remember
+ // fact that the item is uncacheable.
+ if (RememberNotCached(*response_headers)) {
+ response_headers->SetStatusAndReason(HttpStatus::kUnavailable);
+ ret = false;
+ } else {
+ ret = writer->Write(contents, handler);
+ }
+ } else if (sync_fetcher_ != NULL) {
+ // We need to hang onto a copy of the data so we can shove it
+ // into the cache, which lacks a streaming Put.
+ std::string content;
+ StringWriter string_writer(&content);
+ ret = sync_fetcher_->StreamingFetchUrl(
+ url, request_headers, response_headers, &string_writer, handler);
+ ret &= writer->Write(content, handler);
+ if (ret) {
+ if (force_caching_ || response_headers->IsProxyCacheable()) {
+ value.Clear();
+ value.SetHeaders(*response_headers);
+ value.Write(content, handler);
+ http_cache_->Put(url.c_str(), &value, handler);
+ }
+ } else {
+ // TODO(jmarantz): Consider caching that this request is not fetchable
+ }
+ } else {
+ AsyncFetch* fetch = new AsyncFetchWithHeaders(url, http_cache_, handler,
+ force_caching_);
+ fetch->Start(async_fetcher_, request_headers);
+ }
+ return ret;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/cache_url_fetcher_test.cc b/trunk/src/net/instaweb/util/cache_url_fetcher_test.cc
new file mode 100644
index 0000000..3274e76
--- /dev/null
+++ b/trunk/src/net/instaweb/util/cache_url_fetcher_test.cc
@@ -0,0 +1,99 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+// Unit-test the caching fetcher, using a mock fetcher, and an async
+// wrapper around that.
+
+#include "net/instaweb/util/public/cache_url_fetcher.h"
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/cache_fetcher_test.h"
+
+namespace net_instaweb {
+
+class CacheUrlFetcherTest : public CacheFetcherTest {};
+
+TEST_F(CacheUrlFetcherTest, TestCachableWithSyncFetcher) {
+ CacheUrlFetcher cache_fetcher(&http_cache_, &mock_fetcher_);
+ EXPECT_EQ(1, CountFetchesSync(kGoodUrl, &cache_fetcher, true, true));
+ EXPECT_EQ(0, CountFetchesSync(kGoodUrl, &cache_fetcher, true, true));
+}
+
+TEST_F(CacheUrlFetcherTest, TestNonCachableWithSyncFetcher) {
+ // When a CacheUrlFetcher is implemented using a sync fetcher,
+ // then non-cacheable URLs will result in sync-fetch successes
+ // that are not cached.
+ // so we will not do the fetch the second time.
+ CacheUrlFetcher cache_fetcher(&http_cache_, &mock_fetcher_);
+ EXPECT_EQ(1, CountFetchesSync(kNotCachedUrl, &cache_fetcher, true, true));
+ EXPECT_EQ(1, CountFetchesSync(kNotCachedUrl, &cache_fetcher, true, true));
+}
+
+TEST_F(CacheUrlFetcherTest, TestCacheWithSyncFetcherFail) {
+ CacheUrlFetcher cache_fetcher(&http_cache_, &mock_fetcher_);
+ EXPECT_EQ(1, CountFetchesSync(kBadUrl, &cache_fetcher, false, true));
+ // For now, we don't cache failure, so we expect a new fetch
+ // on each request
+ EXPECT_EQ(1, CountFetchesSync(kBadUrl, &cache_fetcher, false, true));
+}
+
+TEST_F(CacheUrlFetcherTest, TestCachableWithASyncFetcher) {
+ CacheUrlFetcher cache_fetcher(&http_cache_, &mock_async_fetcher_);
+
+ // The first fetch from the cache fetcher with the async interface
+ // will not succeed (arg==false), and it will queue up an async fetch
+ EXPECT_EQ(1, CountFetchesSync(kGoodUrl, &cache_fetcher, false, false));
+
+ // The second one will behave exactly the same, because we have
+ // not let the async callback run yet.
+ EXPECT_EQ(1, CountFetchesSync(kGoodUrl, &cache_fetcher, false, false));
+
+ // Now we call the callbacks, and so subsequent requests to the
+ // the cache will succeed (arg==true) and will yield 0 new fetch
+ // requests.
+ mock_async_fetcher_.CallCallbacks();
+ EXPECT_EQ(0, CountFetchesSync(kGoodUrl, &cache_fetcher, true, false));
+}
+
+TEST_F(CacheUrlFetcherTest, TestNonCachableWithASyncFetcher) {
+ CacheUrlFetcher cache_fetcher(&http_cache_, &mock_async_fetcher_);
+
+ // The first fetch from the cache fetcher with the async interface
+ // will not succeed (arg==false), and it will queue up an async fetch
+ EXPECT_EQ(1, CountFetchesSync(kNotCachedUrl, &cache_fetcher, false, false));
+ EXPECT_EQ(1, CountFetchesSync(kNotCachedUrl, &cache_fetcher, false, false));
+ mock_async_fetcher_.CallCallbacks();
+
+ // When a CacheUrlFetcher is implemented using an async fetcher,
+ // then non-cacheable URLs will result in sync-fetch failures,
+ // but we will remember the failure so we don't have to do the
+ // second fetch.
+ EXPECT_EQ(0, CountFetchesSync(kNotCachedUrl, &cache_fetcher, false, false));
+}
+
+TEST_F(CacheUrlFetcherTest, TestCacheWithASyncFetcherFail) {
+ CacheUrlFetcher cache_fetcher(&http_cache_, &mock_async_fetcher_);
+
+ EXPECT_EQ(1, CountFetchesSync(kBadUrl, &cache_fetcher, false, false));
+ EXPECT_EQ(1, CountFetchesSync(kBadUrl, &cache_fetcher, false, false));
+ mock_async_fetcher_.CallCallbacks();
+ EXPECT_EQ(1, CountFetchesSync(kBadUrl, &cache_fetcher, false, false));
+ mock_async_fetcher_.CallCallbacks(); // Otherwise memory will be leaked
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/content_type.cc b/trunk/src/net/instaweb/util/content_type.cc
new file mode 100644
index 0000000..5716e69
--- /dev/null
+++ b/trunk/src/net/instaweb/util/content_type.cc
@@ -0,0 +1,134 @@
+/**
+ * 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/util/public/content_type.h"
+
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+namespace {
+
+const ContentType kTypes[] = {
+ // Canonical types:
+ {"text/html", ".html", ContentType::kHtml}, // RFC 2854
+ {"application/xhtml+xml", ".xhtml", ContentType::kXhtml}, // RFC 3236
+ {"application/ce-html+xml", ".xhtml", ContentType::kCeHtml},
+
+ {"text/javascript", ".js", ContentType::kJavascript},
+ {"text/css", ".css", ContentType::kCss},
+ {"text/plain", ".txt", ContentType::kText},
+ {"text/xml", ".xml", ContentType::kXml}, // RFC 3023
+
+ {"image/png", ".png", ContentType::kPng},
+ {"image/gif", ".gif", ContentType::kGif},
+ {"image/jpeg", ".jpg", ContentType::kJpeg},
+
+ // Synonyms; Note that the canonical types are referenced by index
+ // in the named references declared below.
+ {"application/x-javascript", ".js", ContentType::kJavascript},
+ {"application/javascript", ".js", ContentType::kJavascript},
+ {"text/ecmascript", ".js", ContentType::kJavascript},
+ {"application/ecmascript", ".js", ContentType::kJavascript},
+ {"image/jpeg", ".jpeg", ContentType::kJpeg},
+ {"text/html", ".htm", ContentType::kHtml},
+ {"application/xml", ".xml", ContentType::kXml}, // RFC 3023
+};
+const int kNumTypes = arraysize(kTypes);
+
+} // namespace
+
+const ContentType& kContentTypeHtml = kTypes[0];
+const ContentType& kContentTypeXhtml = kTypes[1];
+const ContentType& kContentTypeCeHtml = kTypes[2];
+
+const ContentType& kContentTypeJavascript = kTypes[3];
+const ContentType& kContentTypeCss = kTypes[4];
+const ContentType& kContentTypeText = kTypes[5];
+const ContentType& kContentTypeXml = kTypes[6];
+
+const ContentType& kContentTypePng = kTypes[7];
+const ContentType& kContentTypeGif = kTypes[8];
+const ContentType& kContentTypeJpeg = kTypes[9];
+
+bool ContentType::IsHtmlLike() const {
+ switch (type_) {
+ case kHtml:
+ case kXhtml:
+ case kCeHtml:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool ContentType::IsXmlLike() const {
+ switch (type_) {
+ case kXhtml:
+ case kXml:
+ return true;
+ default:
+ return false;
+ }
+}
+
+const ContentType* NameExtensionToContentType(const StringPiece& name) {
+ // Get the name from the extension.
+ StringPiece::size_type ext_pos = name.rfind('.');
+ const ContentType* res = NULL;
+ if (ext_pos != StringPiece::npos) {
+ StringPiece ext = name.substr(ext_pos);
+ // TODO(jmarantz): convert to a map if the list gets large.
+ for (int i = 0; i < kNumTypes; ++i) {
+ if (StringCaseEqual(ext, kTypes[i].file_extension())) {
+ res = &kTypes[i];
+ break;
+ }
+ }
+ }
+ return res;
+}
+
+const ContentType* MimeTypeToContentType(const StringPiece& mime_type) {
+ // TODO(jmarantz): convert to a map if the list gets large.
+ const ContentType* res = NULL;
+
+ // The content-type can have a "; charset=...". We are not interested
+ // in that, for the purpose of our ContentType object.
+ //
+ // TODO(jmarantz): we should be grabbing the encoding, however, and
+ // saving it so that when we emit content-type headers for resources,
+ // they include the proper encoding.
+ StringPiece stripped_mime_type;
+ StringPiece::size_type semi_colon = mime_type.find(';');
+ if (semi_colon == StringPiece::npos) {
+ stripped_mime_type = mime_type;
+ } else {
+ stripped_mime_type = mime_type.substr(0, semi_colon);
+ }
+
+ for (int i = 0; i < kNumTypes; ++i) {
+ if (StringCaseEqual(stripped_mime_type, kTypes[i].mime_type())) {
+ res = &kTypes[i];
+ break;
+ }
+ }
+ return res;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/content_type_test.cc b/trunk/src/net/instaweb/util/content_type_test.cc
new file mode 100644
index 0000000..84cf563
--- /dev/null
+++ b/trunk/src/net/instaweb/util/content_type_test.cc
@@ -0,0 +1,72 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+// Unit-test the string-splitter.
+
+#include "net/instaweb/util/public/content_type.h"
+#include "net/instaweb/util/public/gtest.h"
+
+namespace net_instaweb {
+
+class ContentTypeTest : public testing::Test {
+ protected:
+ ContentType::Type ExtToType(const char* ext) {
+ return NameExtensionToContentType(ext)->type();
+ }
+ ContentType::Type MimeToType(const char* mime_type) {
+ return MimeTypeToContentType(mime_type)->type();
+ }
+};
+
+TEST_F(ContentTypeTest, TestUnknown) {
+ EXPECT_EQ(NULL, NameExtensionToContentType(".unknown"));
+ EXPECT_EQ(NULL, MimeTypeToContentType("unknown/unknown"));
+}
+
+TEST_F(ContentTypeTest, TestExtensions) {
+ EXPECT_EQ(ContentType::kHtml, ExtToType(".html"));
+ EXPECT_EQ(ContentType::kJavascript, ExtToType(".js"));
+ EXPECT_EQ(ContentType::kJpeg, ExtToType(".jpg"));
+ EXPECT_EQ(ContentType::kJpeg, ExtToType(".jpeg"));
+ EXPECT_EQ(ContentType::kCss, ExtToType(".css"));
+ EXPECT_EQ(ContentType::kText, ExtToType(".txt"));
+ EXPECT_EQ(ContentType::kXml, ExtToType(".xml"));
+ EXPECT_EQ(ContentType::kPng, ExtToType(".png"));
+ EXPECT_EQ(ContentType::kGif, ExtToType(".gif"));
+}
+
+TEST_F(ContentTypeTest, TestMimeType) {
+ EXPECT_EQ(ContentType::kHtml, MimeToType("text/html"));
+ EXPECT_EQ(ContentType::kHtml, MimeToType("text/html; charset=UTF-8"));
+ EXPECT_EQ(ContentType::kXhtml, MimeToType("application/xhtml+xml"));
+ EXPECT_EQ(ContentType::kCeHtml, MimeToType("application/ce-html+xml"));
+ EXPECT_EQ(ContentType::kJavascript, MimeToType("text/javascript"));
+ EXPECT_EQ(ContentType::kJavascript, MimeToType("application/x-javascript"));
+ EXPECT_EQ(ContentType::kJavascript, MimeToType("application/javascript"));
+ EXPECT_EQ(ContentType::kJavascript, MimeToType("text/ecmascript"));
+ EXPECT_EQ(ContentType::kJavascript, MimeToType("application/ecmascript"));
+ EXPECT_EQ(ContentType::kJpeg, MimeToType("image/jpeg"));
+ EXPECT_EQ(ContentType::kCss, MimeToType("text/css"));
+ EXPECT_EQ(ContentType::kText, MimeToType("text/plain"));
+ EXPECT_EQ(ContentType::kXml, MimeToType("application/xml"));
+ EXPECT_EQ(ContentType::kXml, MimeToType("text/xml"));
+ EXPECT_EQ(ContentType::kPng, MimeToType("image/png"));
+ EXPECT_EQ(ContentType::kGif, MimeToType("image/gif"));
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/data_url.cc b/trunk/src/net/instaweb/util/data_url.cc
new file mode 100644
index 0000000..fb977e1
--- /dev/null
+++ b/trunk/src/net/instaweb/util/data_url.cc
@@ -0,0 +1,116 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#include "net/instaweb/util/public/data_url.h"
+
+#include "net/instaweb/util/public/base64_util.h"
+#include "net/instaweb/util/public/content_type.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+void DataUrl(const ContentType& content_type,
+ const Encoding encoding,
+ const StringPiece& content,
+ std::string* result) {
+ result->assign("data:");
+ result->append(content_type.mime_type());
+ switch (encoding) {
+ case BASE64: {
+ result->append(";base64,");
+ std::string encoded;
+ Mime64Encode(content, &encoded);
+ result->append(encoded);
+ break;
+ }
+// TODO(jmaessen): Figure out if we ever need non-BASE64 encodings,
+// and if so actually write them. Here are the stubs.
+// case UTF8:
+// result->append(";charset=\"utf-8\",");
+// // TODO(jmaessen): find %-encoding code to use here.
+// // jmarantz has one pending.
+// result.append(content);
+// case LATIN1:
+// result->append(";charset=\"\",");
+// // TODO(jmaessen): find %-encoding code to use here.
+// // Not the UTF-8 one!
+ default: {
+ // either UNKNOWN or PLAIN. No special encoding or alphabet. We're in a
+ // context where we don't want to fail, so we try to give sensible output
+ // if encoding is actually out of range; this gives some hope of graceful
+ // degradation of experience.
+ result->append(",");
+ content.AppendToString(result);
+ break;
+ }
+ }
+}
+
+bool ParseDataUrl(const StringPiece& url,
+ const ContentType** content_type,
+ Encoding* encoding,
+ StringPiece* encoded_content) {
+ const char kData[] = "data:";
+ const size_t kDataSize = sizeof(kData) - 1;
+ const char kBase64[] = ";base64";
+ const size_t kBase64Size = sizeof(kBase64) - 1;
+ // First invalidate all outputs.
+ *content_type = NULL;
+ *encoding = UNKNOWN;
+ encoded_content->clear();
+ size_t header_boundary = url.find(',');
+ if (header_boundary == url.npos || !url.starts_with(kData)) {
+ return false;
+ }
+ StringPiece header(url.data(), header_boundary);
+ size_t mime_boundary = header.find(';');
+ if (mime_boundary == url.npos) {
+ // no charset or base64 encoding.
+ mime_boundary = header_boundary;
+ *encoding = PLAIN;
+ } else if (header_boundary >= mime_boundary + kBase64Size) {
+ if (header.ends_with(kBase64)) {
+ *encoding = BASE64;
+ } else {
+ *encoding = PLAIN;
+ }
+ }
+ StringPiece mime_type(url.data() + kDataSize, mime_boundary - kDataSize);
+ *content_type = MimeTypeToContentType(mime_type);
+ encoded_content->set(url.data() + header_boundary + 1,
+ url.size() - header_boundary - 1);
+ return true;
+}
+
+bool DecodeDataUrlContent(Encoding encoding,
+ const StringPiece& encoded_content,
+ std::string* decoded_content) {
+ switch (encoding) {
+ case PLAIN:
+ // No change, just copy data.
+ encoded_content.CopyToString(decoded_content);
+ return true;
+ case BASE64:
+ return Mime64Decode(encoded_content, decoded_content);
+ default:
+ return false;
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/data_url_test.cc b/trunk/src/net/instaweb/util/data_url_test.cc
new file mode 100644
index 0000000..909514d
--- /dev/null
+++ b/trunk/src/net/instaweb/util/data_url_test.cc
@@ -0,0 +1,171 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#include "net/instaweb/util/public/data_url.h"
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/content_type.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/gtest.h"
+
+namespace net_instaweb {
+namespace {
+
+const std::string kAsciiData =
+ "A_Rather=Long,But-conventional?looking_string#with;some:odd,characters.";
+const std::string kAsciiDataBase64 =
+ "QV9SYXRoZXI9TG9uZyxCdXQtY29udmVudGlvbmFsP2xvb2tpbmdfc3RyaW5nI3dpdGg7c29"
+ "tZTpvZGQsY2hhcmFjdGVycy4=";
+
+// A string with embedded NULs; we must construct it carefully to avoid
+// truncation.
+const char kMixedDataChars[] =
+ "This string\ncontains\0lots of\tunusual\xe3~characters\xd7\xa5";
+const char kMixedDataBase64[] =
+ "VGhpcyBzdHJpbmcKY29udGFpbnMAbG90cyBvZgl1bnVzdWFs435jaGFyYWN0ZXJz16U=";
+
+const char kPlainPrefix[] = "data:text/plain,";
+const char kBase64Prefix[] = "data:text/plain;base64,";
+
+const char kGifPlainPrefix[] = "data:image/gif,";
+const char kGifBase64Prefix[] = "data:image/gif;base64,";
+
+class DataUrlTest : public testing::Test {
+ public:
+ DataUrlTest()
+ : mixed_data_(kMixedDataChars, sizeof(kMixedDataChars) - 1) { }
+
+ protected:
+ // Make a ContentType yield readable failure output. Needed this to fix bugs
+ // in the tests! (No actual program bugs found here...)
+ const char* Mime(const ContentType* type) {
+ if (type == NULL || type->mime_type() == NULL) {
+ return "NULL";
+ } else {
+ return type->mime_type();
+ }
+ }
+
+ void TestDecoding(const bool can_parse,
+ const bool can_decode,
+ const std::string& prefix,
+ const std::string& encoded,
+ const ContentType* type,
+ Encoding encoding,
+ const std::string& decoded) {
+ std::string url = prefix + encoded;
+ const ContentType* parsed_type;
+ Encoding parsed_encoding = UNKNOWN;
+ StringPiece parsed_encoded;
+ EXPECT_EQ(can_parse,
+ ParseDataUrl(url, &parsed_type,
+ &parsed_encoding, &parsed_encoded));
+ EXPECT_EQ(encoding, parsed_encoding);
+ EXPECT_EQ(type, parsed_type) << "type '" << Mime(type) <<
+ "' didn't match '" << Mime(parsed_type) << "'\n";
+ EXPECT_EQ(encoded, parsed_encoded);
+ std::string parsed_decoded;
+ EXPECT_EQ(can_decode,
+ DecodeDataUrlContent(encoding, parsed_encoded, &parsed_decoded));
+ EXPECT_EQ(decoded, parsed_decoded);
+ }
+
+ std::string mixed_data_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DataUrlTest);
+};
+
+TEST_F(DataUrlTest, TestDataPlain) {
+ std::string url;
+ DataUrl(kContentTypeText, PLAIN, kAsciiData, &url);
+ EXPECT_EQ(kPlainPrefix + kAsciiData, url);
+}
+
+TEST_F(DataUrlTest, TestDataBase64) {
+ std::string url;
+ DataUrl(kContentTypeText, BASE64, kAsciiData, &url);
+ EXPECT_EQ(kBase64Prefix + kAsciiDataBase64, url);
+}
+
+TEST_F(DataUrlTest, TestData1Plain) {
+ std::string url;
+ DataUrl(kContentTypeGif, PLAIN, mixed_data_, &url);
+ EXPECT_EQ(kGifPlainPrefix + mixed_data_, url);
+}
+
+TEST_F(DataUrlTest, TestData1Base64) {
+ std::string url;
+ DataUrl(kContentTypeGif, BASE64, mixed_data_, &url);
+ EXPECT_EQ(StrCat(kGifBase64Prefix, kMixedDataBase64), url);
+}
+
+TEST_F(DataUrlTest, ParseDataPlain) {
+ TestDecoding(true, true, kPlainPrefix, kAsciiData,
+ &kContentTypeText, PLAIN, kAsciiData);
+}
+
+TEST_F(DataUrlTest, ParseDataBase64) {
+ TestDecoding(true, true, kBase64Prefix, kAsciiDataBase64,
+ &kContentTypeText, BASE64, kAsciiData);
+}
+
+TEST_F(DataUrlTest, ParseData1Plain) {
+ TestDecoding(true, true, kPlainPrefix, mixed_data_,
+ &kContentTypeText, PLAIN, mixed_data_);
+}
+
+TEST_F(DataUrlTest, ParseData1Base64) {
+ TestDecoding(true, true, kBase64Prefix, kMixedDataBase64,
+ &kContentTypeText, BASE64, mixed_data_);
+}
+
+TEST_F(DataUrlTest, ParseBadProtocol) {
+ TestDecoding(false, false, "http://www.google.com/", "",
+ NULL, UNKNOWN, "");
+}
+
+TEST_F(DataUrlTest, ParseNoComma) {
+ TestDecoding(false, false,
+ StrCat("data:text/plain;base64;", kMixedDataBase64), "",
+ NULL, UNKNOWN, "");
+}
+
+TEST_F(DataUrlTest, ParseNoMime) {
+ TestDecoding(true, true, "data:;base64,", kMixedDataBase64,
+ NULL, BASE64, mixed_data_);
+}
+
+TEST_F(DataUrlTest, ParseCorruptMime) {
+ TestDecoding(true, true, "data:#$!;base64,", kMixedDataBase64,
+ NULL, BASE64, mixed_data_);
+}
+
+TEST_F(DataUrlTest, ParseBadEncodingIsPlain) {
+ TestDecoding(true, true, "data:text/plain;mumbledypeg,", mixed_data_,
+ &kContentTypeText, PLAIN, mixed_data_);
+}
+
+TEST_F(DataUrlTest, ParseBadBase64) {
+ TestDecoding(true, false, kBase64Prefix, "@%#$%@#$%^@%%^%*%^&*",
+ &kContentTypeText, BASE64, "");
+}
+
+} // namespace
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/dummy_url_fetcher.cc b/trunk/src/net/instaweb/util/dummy_url_fetcher.cc
new file mode 100644
index 0000000..267c98d
--- /dev/null
+++ b/trunk/src/net/instaweb/util/dummy_url_fetcher.cc
@@ -0,0 +1,35 @@
+/**
+ * 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/util/public/dummy_url_fetcher.h"
+
+#include "net/instaweb/util/public/message_handler.h"
+#include <string>
+
+namespace net_instaweb {
+
+bool DummyUrlFetcher::StreamingFetchUrl(const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* fetched_content_writer,
+ MessageHandler* message_handler) {
+ message_handler->Message(kFatal, "DummyUrlFetcher used");
+ return false;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/fake_url_async_fetcher.cc b/trunk/src/net/instaweb/util/fake_url_async_fetcher.cc
new file mode 100644
index 0000000..b3558f2
--- /dev/null
+++ b/trunk/src/net/instaweb/util/fake_url_async_fetcher.cc
@@ -0,0 +1,26 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/fake_url_async_fetcher.h"
+
+namespace net_instaweb {
+
+FakeUrlAsyncFetcher::~FakeUrlAsyncFetcher() {
+}
+
+} // namespace instaweb
diff --git a/trunk/src/net/instaweb/util/fetcher_test.cc b/trunk/src/net/instaweb/util/fetcher_test.cc
new file mode 100644
index 0000000..6f7790f
--- /dev/null
+++ b/trunk/src/net/instaweb/util/fetcher_test.cc
@@ -0,0 +1,163 @@
+/**
+ * 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/util/fetcher_test.h"
+
+namespace net_instaweb {
+
+const char FetcherTest::kStartDate[] = "Sun, 16 Dec 1979 02:27:45 GMT";
+const char FetcherTest::kHtmlContent[] = "<html><body>Nuts!</body></html>";
+const char FetcherTest::kErrorMessage[] = "Invalid URL";
+const char FetcherTest::kGoodUrl[] = "http://pi.com";
+const char FetcherTest::kNotCachedUrl[] = "http://not_cacheable.com";
+const char FetcherTest::kBadUrl[] = "http://this_url_will_fail.com";
+const char FetcherTest::kHeaderName[] = "header-name";
+const char FetcherTest::kHeaderValue[] = "header value";
+
+void FetcherTest::ValidateMockFetcherResponse(
+ bool success, bool check_error_message,
+ const std::string& content,
+ const MetaData& response_headers) {
+ if (success) {
+ EXPECT_EQ(std::string(kHtmlContent), content);
+ CharStarVector values;
+ EXPECT_TRUE(response_headers.Lookup(kHeaderName, &values));
+ EXPECT_EQ(1, values.size());
+ EXPECT_EQ(std::string(kHeaderValue), values[0]);
+ } else if (check_error_message) {
+ EXPECT_EQ(std::string(kErrorMessage), content);
+ }
+}
+
+int FetcherTest::CountFetchesSync(const StringPiece& url, bool expect_success,
+ bool check_error_message) {
+ CHECK(sync_fetcher() != NULL);
+ return CountFetchesSync(url, sync_fetcher(),
+ expect_success, check_error_message);
+}
+
+int FetcherTest::CountFetchesSync(
+ const StringPiece& url, UrlFetcher* fetcher,
+ bool expect_success, bool check_error_message) {
+ int starting_fetches = mock_fetcher_.num_fetches();
+ std::string content;
+ StringWriter content_writer(&content);
+ SimpleMetaData request_headers, response_headers;
+ bool success = fetcher->StreamingFetchUrl(
+ url.as_string(), request_headers, &response_headers, &content_writer,
+ &message_handler_);
+ EXPECT_EQ(expect_success, success);
+ ValidateMockFetcherResponse(success, check_error_message, content,
+ response_headers);
+ return mock_fetcher_.num_fetches() - starting_fetches;
+}
+
+int FetcherTest::CountFetchesAsync(const StringPiece& url, bool expect_success,
+ bool* callback_called) {
+ CHECK(async_fetcher() != NULL);
+ *callback_called = false;
+ int starting_fetches = mock_fetcher_.num_fetches();
+ SimpleMetaData request_headers;
+ CheckCallback* fetch = new CheckCallback(expect_success, callback_called);
+ async_fetcher()->StreamingFetch(
+ url.as_string(), request_headers, &fetch->response_headers_,
+ &fetch->content_writer_, &message_handler_, fetch);
+ return mock_fetcher_.num_fetches() - starting_fetches;
+}
+
+
+void FetcherTest::ValidateOutput(const std::string& content,
+ const MetaData& response_headers) {
+ // The detailed header parsing code is tested in
+ // simple_meta_data_test.cc. But let's check the rseponse code
+ // and the last header here, and make sure we got the content.
+ EXPECT_EQ(200, response_headers.status_code());
+ EXPECT_EQ(15, response_headers.NumAttributes());
+ EXPECT_EQ(std::string("X-Google-GFE-Response-Body-Transformations"),
+ std::string(response_headers.Name(14)));
+ EXPECT_EQ(std::string("gunzipped"),
+ std::string(response_headers.Value(14)));
+
+ // Verifies that after the headers, we see the content. Note that this
+ // currently assumes 'wget' style output. Wget takes care of any unzipping.
+ static const char start_of_doc[] = "<!doctype html>";
+ EXPECT_EQ(0, strncmp(start_of_doc, content.c_str(),
+ sizeof(start_of_doc) - 1));
+}
+
+
+// MockFetcher
+bool FetcherTest::MockFetcher::StreamingFetchUrl(
+ const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* writer,
+ MessageHandler* message_handler) {
+ bool ret = false;
+ if (url == kGoodUrl) {
+ ret = Populate("max-age=300", response_headers, writer,
+ message_handler);
+ } else if (url == kNotCachedUrl) {
+ ret = Populate("no-cache", response_headers, writer,
+ message_handler);
+ } else {
+ writer->Write(kErrorMessage, message_handler);
+ }
+ ++num_fetches_;
+ return ret;
+}
+
+bool FetcherTest::MockFetcher::Populate(const char* cache_control,
+ MetaData* response_headers,
+ Writer* writer,
+ MessageHandler* message_handler) {
+ response_headers->SetStatusAndReason(HttpStatus::kOK);
+ response_headers->Add(HttpAttributes::kCacheControl, cache_control);
+ response_headers->Add("Date", kStartDate);
+ response_headers->Add(kHeaderName, kHeaderValue);
+ response_headers->ComputeCaching();
+ response_headers->set_headers_complete(true);
+ writer->Write(kHtmlContent, message_handler);
+ return true;
+}
+
+
+// MockAsyncFetcher
+bool FetcherTest::MockAsyncFetcher::StreamingFetch(
+ const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* writer,
+ MessageHandler* handler,
+ Callback* callback) {
+ bool status = url_fetcher_->StreamingFetchUrl(
+ url, request_headers, response_headers, writer, handler);
+ deferred_callbacks_.push_back(std::make_pair(status, callback));
+ return false;
+}
+
+void FetcherTest::MockAsyncFetcher::CallCallbacks() {
+ for (int i = 0, n = deferred_callbacks_.size(); i < n; ++i) {
+ bool status = deferred_callbacks_[i].first;
+ Callback* callback = deferred_callbacks_[i].second;
+ callback->Done(status);
+ }
+ deferred_callbacks_.clear();
+}
+
+} // namespace net_isntaweb
diff --git a/trunk/src/net/instaweb/util/fetcher_test.h b/trunk/src/net/instaweb/util/fetcher_test.h
new file mode 100644
index 0000000..827410f
--- /dev/null
+++ b/trunk/src/net/instaweb/util/fetcher_test.h
@@ -0,0 +1,183 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+// Unit-test framework for wget fetcher
+
+#ifndef NET_INSTAWEB_UTIL_FETCHER_TEST_H_
+#define NET_INSTAWEB_UTIL_FETCHER_TEST_H_
+
+#include <algorithm>
+#include <utility> // for pair
+#include <vector>
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "net/instaweb/util/public/google_message_handler.h"
+#include "net/instaweb/util/public/gtest.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include <string>
+#include "net/instaweb/util/public/string_writer.h"
+#include "net/instaweb/util/public/url_async_fetcher.h"
+#include "net/instaweb/util/public/url_fetcher.h"
+
+namespace net_instaweb {
+
+class FetcherTest : public testing::Test {
+ protected:
+ static const char kStartDate[];
+ static const char kHtmlContent[];
+ static const char kGoodUrl[];
+ static const char kNotCachedUrl[];
+ static const char kBadUrl[];
+ static const char kHeaderName[];
+ static const char kHeaderValue[];
+ static const char kErrorMessage[];
+
+ FetcherTest() : mock_async_fetcher_(&mock_fetcher_) {}
+
+ // Helpful classes for testing.
+
+ // This mock fetcher will only fetch kGoodUrl, returning kHtmlContent.
+ // If you ask for any other URL it will fail.
+ class MockFetcher : public UrlFetcher {
+ public:
+ MockFetcher() : num_fetches_(0) {}
+
+
+ virtual bool StreamingFetchUrl(const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* response_writer,
+ MessageHandler* message_handler);
+
+ int num_fetches() const { return num_fetches_; }
+
+ private:
+ bool Populate(const char* cache_control, MetaData* response_headers,
+ Writer* writer, MessageHandler* message_handler);
+
+ int num_fetches_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockFetcher);
+ };
+
+ // This is a pseudo-asynchronous interface to MockFetcher. It performs
+ // fetches instantly, but defers calling the callback until the user
+ // calls CallCallbacks(). Then it will execute the deferred callbacks.
+ class MockAsyncFetcher : public UrlAsyncFetcher {
+ public:
+ explicit MockAsyncFetcher(UrlFetcher* url_fetcher)
+ : url_fetcher_(url_fetcher) {}
+
+ virtual bool StreamingFetch(const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* response_writer,
+ MessageHandler* handler,
+ Callback* callback);
+
+ void CallCallbacks();
+
+ private:
+ UrlFetcher* url_fetcher_;
+ std::vector<std::pair<bool, Callback*> > deferred_callbacks_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockAsyncFetcher);
+ };
+
+ // Callback that just checks correct Done status and keeps track of whether
+ // it has been called yet or not.
+ class CheckCallback : public UrlAsyncFetcher::Callback {
+ public:
+ explicit CheckCallback(bool expect_success, bool* callback_called)
+ : expect_success_(expect_success),
+ content_writer_(&content_),
+ callback_called_(callback_called) {
+ }
+
+ virtual void Done(bool success) {
+ *callback_called_ = true;
+ CHECK_EQ(expect_success_, success);
+ ValidateMockFetcherResponse(success, true, content_, response_headers_);
+ delete this;
+ }
+
+ bool expect_success_;
+ SimpleMetaData response_headers_;
+ std::string content_;
+ StringWriter content_writer_;
+ bool* callback_called_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CheckCallback);
+ };
+
+ static void ValidateMockFetcherResponse(bool success,
+ bool check_error_message,
+ const std::string& content,
+ const MetaData& response_headers);
+
+ // Do a URL fetch, and return the number of times the mock fetcher
+ // had to be run to perform the fetch.
+ // Note: You must override sync_fetcher() to return the correct fetcher.
+ int CountFetchesSync(const StringPiece& url, bool expect_success,
+ bool check_error_message);
+ // Use an explicit fetcher (you don't need to override sync_fetcher()).
+ int CountFetchesSync(const StringPiece& url, UrlFetcher* fetcher,
+ bool expect_success, bool check_error_message);
+
+ // Initiate an async URL fetch, and return the number of times the mock
+ // fetcher had to be run to perform the fetch.
+ // Note: You must override async_fetcher() to return the correct fetcher.
+ int CountFetchesAsync(const StringPiece& url, bool expect_success,
+ bool* callback_called);
+
+ // Override these to allow CountFetchesSync or Async respectively.
+ // These are not abstract (= 0) because they only need to be overridden by
+ // classes which want to use CountFetchersSync/Async without specifying the
+ // fetcher in each call.
+ virtual UrlFetcher* sync_fetcher() {
+ CHECK(false) << "sync_fetcher() must be overridden before use.";
+ return NULL;
+ };
+ virtual UrlAsyncFetcher* async_fetcher() {
+ CHECK(false) << "async_fetcher() must be overridden before use.";
+ return NULL;
+ };
+
+
+ std::string TestFilename() {
+ return (GTestSrcDir() +
+ "/net/instaweb/util/testdata/google.http");
+ }
+
+ // This validation code is hard-coded to the http request capture in
+ // testdata/google.http.
+ void ValidateOutput(const std::string& content,
+ const MetaData& response_headers);
+
+ GoogleMessageHandler message_handler_;
+ MockFetcher mock_fetcher_;
+ MockAsyncFetcher mock_async_fetcher_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FetcherTest);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_FETCHER_TEST_H_
diff --git a/trunk/src/net/instaweb/util/file_cache.cc b/trunk/src/net/instaweb/util/file_cache.cc
new file mode 100644
index 0000000..6f3db80
--- /dev/null
+++ b/trunk/src/net/instaweb/util/file_cache.cc
@@ -0,0 +1,279 @@
+/**
+ * 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: lsong@google.com (Libo Song)
+
+#include "net/instaweb/util/public/file_cache.h"
+
+#include <stdlib.h>
+#include <vector>
+#include <queue>
+#include "net/instaweb/util/public/base64_util.h"
+#include "net/instaweb/util/public/null_message_handler.h"
+#include "net/instaweb/util/public/shared_string.h"
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+namespace { // For structs used only in Clean().
+
+class CacheFileInfo {
+ public:
+ CacheFileInfo(int64 size, int64 atime, const std::string& name)
+ : size_(size), atime_(atime), name_(name) {}
+ int64 size_;
+ int64 atime_;
+ std::string name_;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CacheFileInfo);
+};
+
+struct CompareByAtime {
+ public:
+ bool operator()(const CacheFileInfo* one,
+ const CacheFileInfo* two) const {
+ return one->atime_ < two->atime_;
+ }
+};
+
+} // namespace for structs used only in Clean().
+
+// Filenames for the next scheduled clean time and the lockfile. In
+// order to prevent these from colliding with actual cachefiles, they
+// contain characters that our filename encoder would escape.
+const char FileCache::kCleanTimeName[] = "!clean!time!";
+const char FileCache::kCleanLockName[] = "!clean!lock!";
+
+// TODO(abliss): remove policy from constructor; provide defaults here
+// and setters below.
+FileCache::FileCache(const std::string& path, FileSystem* file_system,
+ FilenameEncoder* filename_encoder,
+ CachePolicy* policy,
+ MessageHandler* handler)
+ : path_(path),
+ file_system_(file_system),
+ filename_encoder_(filename_encoder),
+ message_handler_(handler),
+ cache_policy_(policy),
+ // NOTE(abliss): We don't want all the caches racing for the
+ // lock at startup, so each one gets a random offset.
+ next_clean_ms_(policy->timer->NowMs()
+ + (random() % policy->clean_interval_ms)) {
+}
+
+FileCache::~FileCache() {
+}
+
+bool FileCache::Get(const std::string& key, SharedString* value) {
+ std::string filename;
+ bool ret = EncodeFilename(key, &filename);
+ if (ret) {
+ std::string* buffer = value->get();
+
+ // Suppress read errors. Note that we want to show Write errors,
+ // as they likely indicate a permissions or disk-space problem
+ // which is best not eaten. It's cheap enough to construct
+ // a NullMessageHandler on the stack when we want one.
+ NullMessageHandler null_handler;
+ ret = file_system_->ReadFile(filename.c_str(), buffer, &null_handler);
+ }
+ return ret;
+}
+
+void FileCache::Put(const std::string& key, SharedString* value) {
+ std::string filename;
+ if (EncodeFilename(key, &filename)) {
+ const std::string& buffer = **value;
+ std::string temp_filename;
+ if (file_system_->WriteTempFile(filename.c_str(), buffer,
+ &temp_filename, message_handler_)) {
+ file_system_->RenameFile(temp_filename.c_str(), filename.c_str(),
+ message_handler_);
+ }
+ }
+ CheckClean();
+}
+
+void FileCache::Delete(const std::string& key) {
+ std::string filename;
+ if (!EncodeFilename(key, &filename)) {
+ return;
+ }
+ file_system_->RemoveFile(filename.c_str(), message_handler_);
+ return;
+}
+
+bool FileCache::EncodeFilename(const std::string& key,
+ std::string* filename) {
+ std::string prefix = path_;
+ // TODO(abliss): unify and make explicit everyone's assumptions
+ // about trailing slashes.
+ EnsureEndsInSlash(&prefix);
+ filename_encoder_->Encode(prefix, key, filename);
+ return true;
+}
+
+CacheInterface::KeyState FileCache::Query(const std::string& key) {
+ std::string filename;
+ if (!EncodeFilename(key, &filename)) {
+ return CacheInterface::kNotFound;
+ }
+ NullMessageHandler null_handler;
+ if (file_system_->Exists(filename.c_str(), &null_handler).is_true()) {
+ return CacheInterface::kAvailable;
+ }
+ return CacheInterface::kNotFound;
+}
+
+bool FileCache::Clean(int64 target_size) {
+ StringVector files;
+ int64 file_size;
+ int64 file_atime;
+ int64 total_size = 0;
+ if (!file_system_->RecursiveDirSize(path_, &total_size, message_handler_)) {
+ return false;
+ }
+
+ // TODO(jmarantz): gcc 4.1 warns about double/int64 comparisons here,
+ // but this really should be factored into a settable member var.
+ if (total_size < ((target_size * 5) / 4)) {
+ message_handler_->Message(kInfo,
+ "File cache size is %ld; no cleanup needed.",
+ static_cast<long>(total_size));
+ return true;
+ }
+ message_handler_->Message(kInfo,
+ "File cache size is %ld; beginning cleanup.",
+ static_cast<long>(total_size));
+ bool everything_ok = true;
+ everything_ok &= file_system_->ListContents(path_, &files, message_handler_);
+
+ // We will now iterate over the entire directory and its children,
+ // keeping a heap of files to be deleted. Our goal is to delete the
+ // oldest set of files that sum to enough space to bring us below
+ // our target.
+ std::priority_queue<CacheFileInfo*, std::vector<CacheFileInfo*>,
+ CompareByAtime> heap;
+ int64 total_heap_size = 0;
+ // TODO(jmarantz): gcc 4.1 warns about double/int64 comparisons here,
+ // but this really should be factored into a settable member var.
+ int64 target_heap_size = total_size - ((target_size * 3 / 4));
+
+ std::string prefix = path_;
+ EnsureEndsInSlash(&prefix);
+ for (size_t i = 0; i < files.size(); i++) {
+ std::string file_name = files[i];
+ BoolOrError isDir = file_system_->IsDir(file_name.c_str(),
+ message_handler_);
+ if (isDir.is_error()) {
+ return false;
+ } else if (isDir.is_true()) {
+ // add files in this directory to the end of the vector, to be
+ // examined later.
+ everything_ok &= file_system_->ListContents(file_name, &files,
+ message_handler_);
+ } else {
+ everything_ok &= file_system_->Size(file_name, &file_size,
+ message_handler_);
+ everything_ok &= file_system_->Atime(file_name, &file_atime,
+ message_handler_);
+ // If our heap is still too small; add everything in.
+ // Otherwise, add the file in only if it's older than the newest
+ // thing in the heap.
+ if ((total_heap_size < target_heap_size) ||
+ (file_atime < heap.top()->atime_)) {
+ CacheFileInfo* info =
+ new CacheFileInfo(file_size, file_atime, file_name);
+ heap.push(info);
+ total_heap_size += file_size;
+ // Now remove new things from the heap which are not needed
+ // to keep the heap size over its target size.
+ while (total_heap_size - heap.top()->size_ > target_heap_size) {
+ total_heap_size -= heap.top()->size_;
+ delete heap.top();
+ heap.pop();
+ }
+ }
+ }
+ }
+ for (size_t i = heap.size(); i > 0; i--) {
+ everything_ok &= file_system_->RemoveFile(heap.top()->name_.c_str(),
+ message_handler_);
+ delete heap.top();
+ heap.pop();
+ }
+ message_handler_->Message(kInfo,
+ "File cache cleanup complete; freed %ld bytes\n",
+ static_cast<long>(total_heap_size));
+ return everything_ok;
+}
+
+bool FileCache::CheckClean() {
+ const int64 now_ms = cache_policy_->timer->NowMs();
+ if (now_ms < next_clean_ms_) {
+ return false;
+ }
+ std::string lock_name(path_);
+ EnsureEndsInSlash(&lock_name);
+ lock_name += kCleanLockName;
+ bool to_return = false;
+ if (file_system_->TryLock(lock_name, message_handler_).is_true()) {
+ std::string clean_time_name(path_);
+ EnsureEndsInSlash(&clean_time_name);
+ clean_time_name += kCleanTimeName;
+ std::string clean_time_str;
+ int64 clean_time_ms = 0;
+ int64 new_clean_time_ms = now_ms + cache_policy_->clean_interval_ms;
+ if (file_system_->ReadFile(clean_time_name.c_str(), &clean_time_str,
+ message_handler_)) {
+ StringToInt64(clean_time_str, &clean_time_ms);
+ }
+ // If the "clean time" written in the file is older than now, we
+ // clean.
+ if (clean_time_ms < now_ms) {
+ message_handler_->Message(kInfo,
+ "Checking file cache size against target %ld",
+ static_cast<long>(cache_policy_->target_size));
+ to_return = true;
+ }
+ // If the "clean time" is later than now plus one interval, something
+ // went wrong (like the system clock moving backwards or the file
+ // getting corrupt) so we clean and reset it.
+ if (clean_time_ms > new_clean_time_ms) {
+ message_handler_->Message(kError,
+ "Next scheduled file cache clean time %ld"
+ " is implausibly remote. Cleaning now.",
+ static_cast<long>(clean_time_ms));
+ to_return = true;
+ }
+ if (to_return) {
+ clean_time_str = Integer64ToString(new_clean_time_ms);
+ file_system_->WriteFile(clean_time_name.c_str(), clean_time_str,
+ message_handler_);
+ }
+ file_system_->Unlock(lock_name, message_handler_);
+ } else {
+ // TODO(abliss): add a way to break a stale lock
+ }
+ next_clean_ms_ = now_ms + cache_policy_->clean_interval_ms;
+ if (to_return) {
+ // TODO(abliss): add a thread here so we don't block the unlucky request.
+ to_return &= Clean(cache_policy_->target_size);
+ }
+ return to_return;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/file_cache_test.cc b/trunk/src/net/instaweb/util/file_cache_test.cc
new file mode 100644
index 0000000..cea4e5b
--- /dev/null
+++ b/trunk/src/net/instaweb/util/file_cache_test.cc
@@ -0,0 +1,144 @@
+/**
+ * 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: lsong@google.com (Libo Song)
+
+// Unit-test the file cache
+
+#include "net/instaweb/util/public/file_cache.h"
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "net/instaweb/util/public/gtest.h"
+#include "net/instaweb/util/public/filename_encoder.h"
+#include "net/instaweb/util/public/google_message_handler.h"
+#include "net/instaweb/util/public/mem_file_system.h"
+#include "net/instaweb/util/public/mock_timer.h"
+#include "net/instaweb/util/public/shared_string.h"
+#include <string>
+
+namespace net_instaweb {
+
+class FileCacheTest : public testing::Test {
+ protected:
+ FileCacheTest()
+ : mock_timer_(0),
+ kCleanIntervalMs(Timer::kMinuteMs),
+ kTargetSize(409600),
+ cache_(GTestTempDir(), &file_system_, &filename_encoder_,
+ new FileCache::CachePolicy(
+ &mock_timer_, kCleanIntervalMs, kTargetSize),
+ &message_handler_) {
+ }
+
+ void CheckGet(const char* key, const std::string& expected_value) {
+ SharedString value_buffer;
+ EXPECT_TRUE(cache_.Get(key, &value_buffer));
+ EXPECT_EQ(expected_value, *value_buffer);
+ EXPECT_EQ(CacheInterface::kAvailable, cache_.Query(key));
+ }
+
+ void Put(const char* key, const char* value) {
+ SharedString put_buffer(value);
+ cache_.Put(key, &put_buffer);
+ }
+
+ void CheckNotFound(const char* key) {
+ SharedString value_buffer;
+ EXPECT_FALSE(cache_.Get(key, &value_buffer));
+ EXPECT_EQ(CacheInterface::kNotFound, cache_.Query(key));
+ }
+
+ protected:
+ MemFileSystem file_system_;
+ FilenameEncoder filename_encoder_;
+ MockTimer mock_timer_;
+ const int64 kCleanIntervalMs;
+ const int64 kTargetSize;
+ FileCache cache_;
+ GoogleMessageHandler message_handler_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileCacheTest);
+};
+
+// Simple flow of putting in an item, getting it, deleting it.
+TEST_F(FileCacheTest, PutGetDelete) {
+ Put("Name", "Value");
+ CheckGet("Name", "Value");
+ CheckNotFound("Another Name");
+
+ Put("Name", "NewValue");
+ CheckGet("Name", "NewValue");
+
+ cache_.Delete("Name");
+ SharedString value_buffer;
+ EXPECT_FALSE(cache_.Get("Name", &value_buffer));
+}
+
+// Throw a bunch of files into the cache and verify that they are
+// evicted sensibly.
+TEST_F(FileCacheTest, Clean) {
+ file_system_.Clear();
+ // Make some "directory" entries so that the mem_file_system recurses
+ // correctly.
+ std::string dir1 = GTestTempDir() + "/a/";
+ std::string dir2 = GTestTempDir() + "/b/";
+ EXPECT_TRUE(file_system_.MakeDir(dir1.c_str(), &message_handler_));
+ EXPECT_TRUE(file_system_.MakeDir(dir2.c_str(), &message_handler_));
+ // Commonly-used keys
+ const char* names1[] = {"a1", "a2", "a/3"};
+ const char* values1[] = {"a2", "a234", "a2345678"};
+ // Less common keys
+ const char* names2[] =
+ {"b/1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9"};
+ const char* values2[] = {"b2", "b234", "b2345678",
+ "b2", "b234", "b2345678",
+ "b2", "b234", "b2345678"};
+ for (int i = 0; i < 3; i++) {
+ Put(names1[i], values1[i]);
+ }
+ for (int i = 0; i < 9; i++) {
+ Put(names2[i], values2[i]);
+ }
+ int64 total_size = 0;
+ EXPECT_TRUE(
+ file_system_.RecursiveDirSize(GTestTempDir(), &total_size,
+ &message_handler_));
+ EXPECT_EQ((2 + 4 + 8) * 4, total_size);
+
+ // Clean should not remove anything if target is bigger than total size.
+ EXPECT_TRUE(cache_.Clean(total_size + 1));
+ for (int i = 0; i < 27; i++) {
+ // This pattern represents more common usage of the names1 files.
+ CheckGet(names1[i % 3], values1[i % 3]);
+ CheckGet(names2[i % 9], values2[i % 9]);
+ }
+
+ // TODO(jmarantz): gcc 4.1 warns about double/int64 comparisons here,
+ // but this really should be factored into a settable member var.
+ int64 target_size = (4 * total_size) / 5 - 1;
+ EXPECT_TRUE(cache_.Clean(target_size));
+ // Common files should stay
+ for (int i = 0; i < 3; i++) {
+ CheckGet(names1[i], values1[i]);
+ }
+ // Some of the less common files should be gone
+ for (int i = 0; i < 3; i++) {
+ CheckNotFound(names2[i]);
+ }
+}
+// TODO(abliss): add test for CheckClean
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/file_message_handler.cc b/trunk/src/net/instaweb/util/file_message_handler.cc
new file mode 100644
index 0000000..ba03b55
--- /dev/null
+++ b/trunk/src/net/instaweb/util/file_message_handler.cc
@@ -0,0 +1,52 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/file_message_handler.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+namespace net_instaweb {
+
+FileMessageHandler::FileMessageHandler(FILE* file) : file_(file) {
+}
+
+void FileMessageHandler::MessageVImpl(MessageType type, const char* msg,
+ va_list args) {
+ fprintf(file_, "%s: ", MessageTypeToString(type));
+ vfprintf(file_, msg, args);
+ fputc('\n', file_);
+
+ if (type == kFatal) {
+ abort();
+ }
+}
+
+void FileMessageHandler::FileMessageVImpl(MessageType type,
+ const char* filename, int line,
+ const char *msg, va_list args) {
+ fprintf(file_, "%s: %s:%d: ", MessageTypeToString(type), filename, line);
+ vfprintf(file_, msg, args);
+ fputc('\n', file_);
+
+ if (type == kFatal) {
+ abort();
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/file_system.cc b/trunk/src/net/instaweb/util/file_system.cc
new file mode 100644
index 0000000..4d5d5c3
--- /dev/null
+++ b/trunk/src/net/instaweb/util/file_system.cc
@@ -0,0 +1,181 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/file_system.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/string_writer.h"
+#include "net/instaweb/util/stack_buffer.h"
+
+namespace net_instaweb {
+
+FileSystem::~FileSystem() {
+}
+
+FileSystem::File::~File() {
+}
+
+FileSystem::InputFile::~InputFile() {
+}
+
+FileSystem::OutputFile::~OutputFile() {
+}
+
+bool FileSystem::ReadFile(const char* filename, std::string* buffer,
+ MessageHandler* message_handler) {
+ StringWriter writer(buffer);
+ return ReadFile(filename, &writer, message_handler);
+}
+
+bool FileSystem::ReadFile(const char* filename, Writer* writer,
+ MessageHandler* message_handler) {
+ InputFile* input_file = OpenInputFile(filename, message_handler);
+ bool ret = false;
+ if (input_file != NULL) {
+ char buf[kStackBufferSize];
+ int nread;
+ ret = true;
+ while (ret && ((nread = input_file->Read(
+ buf, sizeof(buf), message_handler)) > 0)) {
+ ret = writer->Write(StringPiece(buf, nread), message_handler);
+ }
+ ret &= (nread == 0);
+ ret &= Close(input_file, message_handler);
+ }
+ return ret;
+}
+
+bool FileSystem::WriteFile(const char* filename, const StringPiece& buffer,
+ MessageHandler* message_handler) {
+ OutputFile* output_file = OpenOutputFile(filename, message_handler);
+ bool ret = false;
+ if (output_file != NULL) {
+ ret = output_file->Write(buffer, message_handler);
+ ret &= output_file->SetWorldReadable(message_handler);
+ ret &= Close(output_file, message_handler);
+ }
+ return ret;
+}
+
+bool FileSystem::WriteTempFile(const StringPiece& prefix_name,
+ const StringPiece& buffer,
+ std::string* filename,
+ MessageHandler* message_handler) {
+ OutputFile* output_file = OpenTempFile(prefix_name, message_handler);
+ bool ok = (output_file != NULL);
+ if (ok) {
+ // Store filename early, since it's invalidated by Close.
+ *filename = output_file->filename();
+ ok = output_file->Write(buffer, message_handler);
+ // attempt Close even if write fails.
+ ok &= Close(output_file, message_handler);
+ }
+ if (!ok) {
+ // Clear filename so we end in a consistent state.
+ filename->clear();
+ }
+ return ok;
+}
+
+bool FileSystem::Close(File* file, MessageHandler* message_handler) {
+ bool ret = file->Close(message_handler);
+ delete file;
+ return ret;
+}
+
+
+bool FileSystem::RecursivelyMakeDir(const StringPiece& full_path_const,
+ MessageHandler* handler) {
+ bool ret = true;
+ std::string full_path = full_path_const.as_string();
+ EnsureEndsInSlash(&full_path);
+ std::string subpath;
+ subpath.reserve(full_path.size());
+ size_t old_pos = 0, new_pos;
+ // Note that we intentionally start searching at pos = 1 to avoid having
+ // subpath be "" on absolute paths.
+ while ((new_pos = full_path.find('/', old_pos + 1)) != std::string::npos) {
+ // Build up path, one segment at a time.
+ subpath.append(full_path.data() + old_pos, new_pos - old_pos);
+ if (Exists(subpath.c_str(), handler).is_false()) {
+ if (!MakeDir(subpath.c_str(), handler)) {
+ ret = false;
+ break;
+ }
+ } else if (IsDir(subpath.c_str(), handler).is_false()) {
+ handler->Message(kError, "Subpath '%s' of '%s' is a non-directory file.",
+ subpath.c_str(), full_path.c_str());
+ ret = false;
+ break;
+ }
+ old_pos = new_pos;
+ }
+ return ret;
+}
+
+bool FileSystem::RecursiveDirSize(const StringPiece& path, int64* size,
+ MessageHandler* handler) {
+ // TODO(abliss): replace this recursive algorithm with an iterator
+ // that keeps its own state. It can keep a tree of directory names
+ // to save memory, and simplify the implementation of file_cache.Clean.
+ const std::string path_string = path.as_string();
+ const char* path_str = path_string.c_str();
+ int64 file_size = 0;
+ StringVector files;
+ if (!ListContents(path_str, &files, handler)) {
+ return false;
+ }
+ const std::string prefix = path_string + "/";
+ for (int i = files.size() - 1; i >= 0; i--) {
+ const std::string file_name = files[i];
+ BoolOrError isDir = IsDir(file_name.c_str(), handler);
+ if (isDir.is_error()) {
+ return false;
+ } else if (isDir.is_false()) {
+ if (!Size(file_name, &file_size, handler)) {
+ return false;
+ }
+ *size += file_size;
+ } else {
+ // Recurse on directory
+ // TODO(abliss): Should guard against infinite loops here, in
+ // the case of a filesystem with cyclic symlinks.
+ if (!RecursiveDirSize(file_name, size, handler)) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+// Try to make directories to store file.
+void FileSystem::SetupFileDir(const StringPiece& filename,
+ MessageHandler* handler) {
+ size_t last_slash = filename.rfind('/');
+ if (last_slash != StringPiece::npos) {
+ StringPiece directory_name = filename.substr(0, last_slash);
+ if (!RecursivelyMakeDir(directory_name, handler)) {
+ // TODO(sligocki): Specify where dir creation failed?
+ handler->Message(kError, "Could not create directories for file %s",
+ filename.as_string().c_str());
+ }
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/file_system_lock_manager.cc b/trunk/src/net/instaweb/util/file_system_lock_manager.cc
new file mode 100644
index 0000000..737a597
--- /dev/null
+++ b/trunk/src/net/instaweb/util/file_system_lock_manager.cc
@@ -0,0 +1,84 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+#include "net/instaweb/util/public/file_system_lock_manager.h"
+
+#include "net/instaweb/util/public/file_system.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/named_lock_manager.h"
+#include "net/instaweb/util/public/timer.h"
+#include "net/instaweb/util/public/timer_based_abstract_lock.h"
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+class FileSystemLock : public TimerBasedAbstractLock {
+ public:
+ virtual ~FileSystemLock() {
+ if (held_) {
+ Unlock();
+ }
+ }
+ virtual bool TryLock() {
+ bool result = false;
+ if (manager_->file_system()->
+ TryLock(name_, manager_->handler()).is_true()) {
+ held_ = result = true;
+ }
+ return result;
+ }
+ virtual bool TryLockStealOld(int64 timeout_ms) {
+ bool result = false;
+ if (manager_->file_system()->
+ TryLockWithTimeout(name_, timeout_ms, manager_->handler()).is_true()) {
+ held_ = result = true;
+ }
+ return result;
+ }
+ virtual void Unlock() {
+ held_ = !manager_->file_system()->Unlock(name_, manager_->handler());
+ }
+ protected:
+ virtual Timer* timer() const {
+ return manager_->timer();
+ }
+ private:
+ friend class FileSystemLockManager;
+
+ // ctor should only be called by CreateNamedLock below.
+ FileSystemLock(const StringPiece& name, FileSystemLockManager* manager)
+ : name_(name.data(), name.size()),
+ manager_(manager),
+ held_(false) { }
+
+ std::string name_;
+ FileSystemLockManager* manager_;
+ // The held_ field contains an approximation to whether the lock is locked or
+ // not. If we believe the lock to be locked, we will unlock on destruction.
+ // We therefore try to conservatively leave it "true" when we aren't sure.
+ bool held_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemLock);
+};
+
+FileSystemLockManager::~FileSystemLockManager() { }
+
+AbstractLock* FileSystemLockManager::CreateNamedLock(const StringPiece& name) {
+ return new FileSystemLock(name, this);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/file_system_test.cc b/trunk/src/net/instaweb/util/file_system_test.cc
new file mode 100644
index 0000000..9ade2af
--- /dev/null
+++ b/trunk/src/net/instaweb/util/file_system_test.cc
@@ -0,0 +1,305 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include <unistd.h>
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/util/public/file_system_test.h"
+#include "net/instaweb/util/public/google_message_handler.h"
+#include <string>
+#include "net/instaweb/util/public/gtest.h"
+
+namespace net_instaweb {
+
+FileSystemTest::FileSystemTest() { }
+FileSystemTest::~FileSystemTest() { }
+
+std::string FileSystemTest::WriteNewFile(const StringPiece& suffix,
+ const std::string& content) {
+ std::string filename = StrCat(test_tmpdir(), suffix);
+
+ // Make sure we don't read an old file.
+ DeleteRecursively(filename);
+ EXPECT_TRUE(file_system()->WriteFile(filename.c_str(), content, &handler_));
+
+ return filename;
+}
+
+// Check that a file has been read.
+void FileSystemTest::CheckRead(const std::string& filename,
+ const std::string& expected_contents) {
+ std::string buffer;
+ ASSERT_TRUE(file_system()->ReadFile(filename.c_str(), &buffer, &handler_));
+ EXPECT_EQ(buffer, expected_contents);
+}
+
+// Make sure we can no longer read the file by the old name. Note
+// that this will spew some error messages into the log file, and
+// we can add a null_message_handler implementation to
+// swallow them, if they become annoying.
+void FileSystemTest::CheckDoesNotExist(const std::string& filename) {
+ std::string read_buffer;
+ EXPECT_FALSE(file_system()->ReadFile(filename.c_str(), &read_buffer,
+ &handler_));
+ EXPECT_TRUE(file_system()->Exists(filename.c_str(), &handler_).is_false());
+}
+
+// Write a named file, then read it.
+void FileSystemTest::TestWriteRead() {
+ std::string filename = test_tmpdir() + "/write.txt";
+ std::string msg("Hello, world!");
+
+ DeleteRecursively(filename);
+ FileSystem::OutputFile* ofile = file_system()->OpenOutputFile(
+ filename.c_str(), &handler_);
+ ASSERT_TRUE(ofile != NULL);
+ EXPECT_TRUE(ofile->Write(msg, &handler_));
+ EXPECT_TRUE(file_system()->Close(ofile, &handler_));
+ CheckRead(filename, msg);
+}
+
+// Write a temp file, then read it.
+void FileSystemTest::TestTemp() {
+ std::string prefix = test_tmpdir() + "/temp_prefix";
+ FileSystem::OutputFile* ofile = file_system()->OpenTempFile(
+ prefix.c_str(), &handler_);
+ ASSERT_TRUE(ofile != NULL);
+ std::string filename(ofile->filename());
+ std::string msg("Hello, world!");
+ EXPECT_TRUE(ofile->Write(msg, &handler_));
+ EXPECT_TRUE(file_system()->Close(ofile, &handler_));
+
+ CheckRead(filename, msg);
+}
+
+// Write a temp file, rename it, then read it.
+void FileSystemTest::TestRename() {
+ std::string from_text = "Now is time time";
+ std::string to_file = test_tmpdir() + "/to.txt";
+ DeleteRecursively(to_file);
+
+ std::string from_file = WriteNewFile("/from.txt", from_text);
+ ASSERT_TRUE(file_system()->RenameFile(from_file.c_str(), to_file.c_str(),
+ &handler_));
+
+ CheckDoesNotExist(from_file);
+ CheckRead(to_file, from_text);
+}
+
+// Write a file and successfully delete it.
+void FileSystemTest::TestRemove() {
+ std::string filename = WriteNewFile("/remove.txt", "Goodbye, world!");
+ ASSERT_TRUE(file_system()->RemoveFile(filename.c_str(), &handler_));
+ CheckDoesNotExist(filename);
+}
+
+// Write a file and check that it exists.
+void FileSystemTest::TestExists() {
+ std::string filename = WriteNewFile("/exists.txt", "I'm here.");
+ ASSERT_TRUE(file_system()->Exists(filename.c_str(), &handler_).is_true());
+}
+
+// Create a file along with its directory which does not exist.
+void FileSystemTest::TestCreateFileInDir() {
+ std::string dir_name = test_tmpdir() + "/make_dir";
+ DeleteRecursively(dir_name);
+ std::string filename = dir_name + "/file-in-dir.txt";
+
+ FileSystem::OutputFile* file =
+ file_system()->OpenOutputFile(filename.c_str(), &handler_);
+ ASSERT_TRUE(file);
+ file_system()->Close(file, &handler_);
+}
+
+
+// Make a directory and check that files may be placed in it.
+void FileSystemTest::TestMakeDir() {
+ std::string dir_name = test_tmpdir() + "/make_dir";
+ DeleteRecursively(dir_name);
+ std::string filename = dir_name + "/file-in-dir.txt";
+
+ ASSERT_TRUE(file_system()->MakeDir(dir_name.c_str(), &handler_));
+ // ... but we can open a file after we've created the directory.
+ FileSystem::OutputFile* file =
+ file_system()->OpenOutputFile(filename.c_str(), &handler_);
+ ASSERT_TRUE(file);
+ file_system()->Close(file, &handler_);
+}
+
+// Make a directory and check that it is a directory.
+void FileSystemTest::TestIsDir() {
+ std::string dir_name = test_tmpdir() + "/this_is_a_dir";
+ DeleteRecursively(dir_name);
+
+ // Make sure we don't think the directory is there when it isn't ...
+ ASSERT_TRUE(file_system()->IsDir(dir_name.c_str(), &handler_).is_false());
+ ASSERT_TRUE(file_system()->MakeDir(dir_name.c_str(), &handler_));
+ // ... and that we do think it's there when it is.
+ ASSERT_TRUE(file_system()->IsDir(dir_name.c_str(), &handler_).is_true());
+
+ // Make sure that we don't think a regular file is a directory.
+ std::string filename = dir_name + "/this_is_a_file.txt";
+ std::string content = "I'm not a directory.";
+ ASSERT_TRUE(file_system()->WriteFile(filename.c_str(), content, &handler_));
+ ASSERT_TRUE(file_system()->IsDir(filename.c_str(), &handler_).is_false());
+}
+
+// Recursively make directories and check that it worked.
+void FileSystemTest::TestRecursivelyMakeDir() {
+ std::string base = test_tmpdir() + "/base";
+ std::string long_path = base + "/dir/of/a/really/deep/hierarchy";
+ DeleteRecursively(base);
+
+ // Make sure we don't think the directory is there when it isn't ...
+ ASSERT_TRUE(file_system()->IsDir(long_path.c_str(), &handler_).is_false());
+ ASSERT_TRUE(file_system()->RecursivelyMakeDir(long_path, &handler_));
+ // ... and that we do think it's there when it is.
+ ASSERT_TRUE(file_system()->IsDir(long_path.c_str(), &handler_).is_true());
+}
+
+// Check that we cannot create a directory we do not have permissions for.
+// Note: depends upon root dir not being writable.
+void FileSystemTest::TestRecursivelyMakeDir_NoPermission() {
+ std::string base = "/bogus-dir";
+ std::string path = base + "/no/permission/to/make/this/dir";
+
+ // Make sure the bogus bottom level directory is not there.
+ ASSERT_TRUE(file_system()->Exists(base.c_str(), &handler_).is_false());
+ // We do not have permission to create it.
+ ASSERT_FALSE(file_system()->RecursivelyMakeDir(path, &handler_));
+}
+
+// Check that we cannot create a directory below a file.
+void FileSystemTest::TestRecursivelyMakeDir_FileInPath() {
+ std::string base = test_tmpdir() + "/file-in-path";
+ std::string filename = base + "/this-is-a-file";
+ std::string bad_path = filename + "/some/more/path";
+ DeleteRecursively(base);
+ std::string content = "Your path must end here. You shall not pass!";
+
+ ASSERT_TRUE(file_system()->MakeDir(base.c_str(), &handler_));
+ ASSERT_TRUE(file_system()->WriteFile(filename.c_str(), content, &handler_));
+ ASSERT_FALSE(file_system()->RecursivelyMakeDir(bad_path, &handler_));
+}
+
+void FileSystemTest::TestListContents() {
+ std::string dir_name = test_tmpdir() + "/make_dir";
+ DeleteRecursively(dir_name);
+ std::string filename1 = dir_name + "/file-in-dir.txt";
+ std::string filename2 = dir_name + "/another-file-in-dir.txt";
+ std::string content = "Lorem ipsum dolor sit amet";
+
+ StringVector mylist;
+
+ ASSERT_TRUE(file_system()->MakeDir(dir_name.c_str(), &handler_));
+ ASSERT_TRUE(file_system()->WriteFile(filename1.c_str(),
+ content, &handler_));
+ ASSERT_TRUE(file_system()->WriteFile(filename2.c_str(),
+ content, &handler_));
+ EXPECT_TRUE(file_system()->ListContents(dir_name, &mylist, &handler_));
+ EXPECT_EQ(size_t(2), mylist.size());
+ // Make sure our filenames are in there
+ EXPECT_FALSE(filename1.compare(mylist.at(0))
+ && filename1.compare(mylist.at(1)));
+ EXPECT_FALSE(filename2.compare(mylist.at(0))
+ && filename2.compare(mylist.at(1)));
+}
+
+void FileSystemTest::TestAtime() {
+ std::string dir_name = test_tmpdir() + "/make_dir";
+ DeleteRecursively(dir_name);
+ std::string filename1 = "file-in-dir.txt";
+ std::string filename2 = "another-file-in-dir.txt";
+ std::string full_path1 = dir_name + "/" + filename1;
+ std::string full_path2 = dir_name + "/" + filename2;
+ std::string content = "Lorem ipsum dolor sit amet";
+ // We need to sleep a bit between accessing files so that the
+ // difference shows up in in atimes which are measured in seconds.
+ unsigned int sleep_micros = 1500000;
+
+ ASSERT_TRUE(file_system()->MakeDir(dir_name.c_str(), &handler_));
+ ASSERT_TRUE(file_system()->WriteFile(full_path1.c_str(),
+ content, &handler_));
+ ASSERT_TRUE(file_system()->WriteFile(full_path2.c_str(),
+ content, &handler_));
+
+ int64 atime1, atime2;
+ CheckRead(full_path1, content);
+ usleep(sleep_micros);
+ CheckRead(full_path2, content);
+ ASSERT_TRUE(file_system()->Atime(full_path1, &atime1, &handler_));
+ ASSERT_TRUE(file_system()->Atime(full_path2, &atime2, &handler_));
+ EXPECT_LT(atime1, atime2);
+
+ CheckRead(full_path2, content);
+ usleep(sleep_micros);
+ CheckRead(full_path1, content);
+ ASSERT_TRUE(file_system()->Atime(full_path1, &atime1, &handler_));
+ ASSERT_TRUE(file_system()->Atime(full_path2, &atime2, &handler_));
+ EXPECT_LT(atime2, atime1);
+
+}
+
+void FileSystemTest::TestSize() {
+ std::string dir_name = test_tmpdir() + "/make_dir";
+ DeleteRecursively(dir_name);
+ std::string dir_name2 = dir_name + "/make_dir2";
+ std::string filename1 = "file-in-dir.txt";
+ std::string filename2 = "another-file-in-dir.txt";
+ std::string full_path1 = dir_name2 + "/" + filename1;
+ std::string full_path2 = dir_name2 + "/" + filename2;
+ std::string content1 = "12345";
+ std::string content2 = "1234567890";
+ ASSERT_TRUE(file_system()->MakeDir(dir_name.c_str(), &handler_));
+ ASSERT_TRUE(file_system()->MakeDir(dir_name2.c_str(), &handler_));
+ ASSERT_TRUE(file_system()->WriteFile(full_path1.c_str(),
+ content1, &handler_));
+ ASSERT_TRUE(file_system()->WriteFile(full_path2.c_str(),
+ content2, &handler_));
+ int64 size;
+
+ EXPECT_TRUE(file_system()->Size(full_path1, &size, &handler_));
+ EXPECT_EQ(content1.size(), size_t(size));
+ EXPECT_TRUE(file_system()->Size(full_path2, &size, &handler_));
+ EXPECT_EQ(content2.size(), size_t(size));
+ size = 0;
+ EXPECT_TRUE(file_system()->RecursiveDirSize(dir_name2, &size, &handler_));
+ EXPECT_EQ(content1.size() + content2.size(), size_t(size));
+ size = 0;
+ EXPECT_TRUE(file_system()->RecursiveDirSize(dir_name, &size, &handler_));
+ EXPECT_EQ(content1.size() + content2.size(), size_t(size));
+}
+
+void FileSystemTest::TestLock() {
+ std::string dir_name = test_tmpdir() + "/make_dir";
+ DeleteRecursively(dir_name);
+ ASSERT_TRUE(file_system()->MakeDir(dir_name.c_str(), &handler_));
+ std::string lock_name = dir_name + "/lock";
+ // Acquire the lock
+ EXPECT_TRUE(file_system()->TryLock(lock_name, &handler_).is_true());
+ // Can't re-acquire the lock
+ EXPECT_TRUE(file_system()->TryLock(lock_name, &handler_).is_false());
+ // Release the lock
+ EXPECT_TRUE(file_system()->Unlock(lock_name, &handler_));
+ // Do it all again to make sure the release worked.
+ EXPECT_TRUE(file_system()->TryLock(lock_name, &handler_).is_true());
+ EXPECT_TRUE(file_system()->TryLock(lock_name, &handler_).is_false());
+ EXPECT_TRUE(file_system()->Unlock(lock_name, &handler_));
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/file_writer.cc b/trunk/src/net/instaweb/util/file_writer.cc
new file mode 100644
index 0000000..bd85aff
--- /dev/null
+++ b/trunk/src/net/instaweb/util/file_writer.cc
@@ -0,0 +1,34 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/file_writer.h"
+
+namespace net_instaweb {
+
+FileWriter::~FileWriter() {
+}
+
+bool FileWriter::Write(const StringPiece& str, MessageHandler* handler) {
+ return file_->Write(str, handler);
+}
+
+bool FileWriter::Flush(MessageHandler* message_handler) {
+ return file_->Flush(message_handler);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/filename_encoder.cc b/trunk/src/net/instaweb/util/filename_encoder.cc
new file mode 100644
index 0000000..0e77d0e
--- /dev/null
+++ b/trunk/src/net/instaweb/util/filename_encoder.cc
@@ -0,0 +1,42 @@
+/**
+ * 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/util/public/filename_encoder.h"
+#include <vector>
+using net::UrlToFilenameEncoder;
+
+namespace net_instaweb {
+
+FilenameEncoder::~FilenameEncoder() {
+}
+
+void FilenameEncoder::Encode(const StringPiece& filename_prefix,
+ const StringPiece& filename_ending,
+ std::string* encoded_filename) {
+ UrlToFilenameEncoder::EncodeSegment(filename_prefix.as_string(),
+ filename_ending.as_string(),
+ '/', encoded_filename);
+}
+
+bool FilenameEncoder::Decode(const StringPiece& encoded_filename,
+ std::string* decoded_url) {
+ return UrlToFilenameEncoder::Decode(encoded_filename.as_string(), '/',
+ decoded_url);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/filename_encoder_test.cc b/trunk/src/net/instaweb/util/filename_encoder_test.cc
new file mode 100644
index 0000000..6647ea3
--- /dev/null
+++ b/trunk/src/net/instaweb/util/filename_encoder_test.cc
@@ -0,0 +1,148 @@
+/**
+ * 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/util/public/filename_encoder.h"
+
+#include <string>
+#include <vector>
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/gtest.h"
+
+namespace {
+// net::kMaximumSubdirectoryLength is defined in url_to_filename_encoder.cc, but
+// we cannot link it.
+const size_t kMaxLen = 128;
+} // namespace
+
+namespace net_instaweb {
+
+// Note -- the exact behavior of the encoder is tested in
+// gfe/tools/loadtesting/spdy_testing/url_to_filename_encoder_test.cc
+//
+// Here we just test that the names meet certain properties:
+// 1. The segments are small
+// 2. The URL can be recovered from the filename
+// 3. No invalid filename characters are present.
+class FilenameEncoderTest : public ::testing::Test {
+ public:
+ FilenameEncoderTest() { }
+
+ protected:
+ void CheckSegmentLength(const StringPiece& escaped_word) {
+ std::vector<StringPiece> components;
+ SplitStringPieceToVector(escaped_word, "/", &components, false);
+ for (size_t i = 0; i < components.size(); ++i) {
+ EXPECT_GE(kMaxLen, components[i].size());
+ }
+ }
+
+ void CheckValidChars(const StringPiece& escaped_word) {
+ // These characters are invalid in Windows. We will
+ // ignore / for this test, but add in '.
+ // See http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx
+ static const char kInvalidChars[] = "<>:\"\\|?*'";
+ for (size_t i = 0; i < escaped_word.size(); ++i) {
+ char c = escaped_word[i];
+ EXPECT_TRUE(NULL == strchr(kInvalidChars, c));
+ }
+ }
+
+ void Validate(const std::string& in_word) {
+ std::string escaped_word, decoded_url;
+ encoder.Encode("", in_word, &escaped_word);
+ CheckSegmentLength(escaped_word);
+ CheckValidChars(escaped_word);
+ EXPECT_TRUE(encoder.Decode(escaped_word, &decoded_url));
+ EXPECT_EQ(in_word, decoded_url);
+ }
+
+ FilenameEncoder encoder;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FilenameEncoderTest);
+};
+
+TEST_F(FilenameEncoderTest, DoesNotEscapeAlphanum) {
+ Validate("");
+ Validate("abcdefg");
+ Validate("abcdefghijklmnopqrstuvwxyz");
+ Validate("ZYXWVUT");
+ Validate("ZYXWVUTSRQPONMLKJIHGFEDCBA");
+ Validate("01234567689");
+ Validate("/-_");
+ Validate("abcdefghijklmnopqrstuvwxyzZYXWVUTSRQPONMLKJIHGFEDCBA"
+ "01234567689/-_");
+}
+
+TEST_F(FilenameEncoderTest, DoesEscapeNonAlphanum) {
+ Validate(".");
+ Validate("`~!@#$%^&*()_=+[{]}\\|;:'\",<.>?");
+}
+
+TEST_F(FilenameEncoderTest, DoesEscapeCorrectly) {
+ Validate("index.html");
+ Validate("search?q=dogs&go=&form=QBLH&qs=n");
+ Validate("~joebob/my_neeto-website+with_stuff.asp?id=138&content=true");
+}
+
+TEST_F(FilenameEncoderTest, LongTail) {
+ static char long_word[] =
+ "~joebob/briggs/12345678901234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890123456789012345678901234567890";
+ Validate(long_word);
+}
+
+TEST_F(FilenameEncoderTest, LongTailDots) {
+ // Here the '.' in the last path segment expands to x2E, making
+ // it hits 128 chars before the input segment gets that big.
+ static char long_word[] =
+ "~joebob/briggs/1234567.1234567.1234567.1234567.1234567."
+ "1234567.1234567.1234567.1234567.1234567.1234567.1234567."
+ "1234567.1234567.1234567.1234567.1234567.1234567.1234567."
+ "1234567.1234567.1234567.1234567.1234567.1234567.1234567."
+ "1234567.1234567.1234567.1234567.1234567.1234567.1234567."
+ "1234567.1234567.1234567.1234567.1234567.1234567.1234567.";
+ Validate(long_word);
+}
+
+TEST_F(FilenameEncoderTest, CornerCasesNearMaxLenNoEscape) {
+ // hit corner cases, +/- 4 characters from kMaxLen
+ for (int i = -4; i <= 4; ++i) {
+ std::string input;
+ input.append(i + kMaxLen, 'x');
+ Validate(input);
+ }
+}
+
+TEST_F(FilenameEncoderTest, CornerCasesNearMaxLenWithEscape) {
+ // hit corner cases, +/- 4 characters from kMaxLen. This time we
+ // leave off the last 'x' and put in a '.', which ensures that we
+ // are truncating with '/' *after* the expansion.
+ for (int i = -4; i <= 4; ++i) {
+ std::string input;
+ input.append(i + kMaxLen - 1, 'x');
+ input.append(1, '.'); // this will expand to 3 characters.
+ Validate(input);
+ }
+}
+
+} // namespace
diff --git a/trunk/src/net/instaweb/util/google_message_handler.cc b/trunk/src/net/instaweb/util/google_message_handler.cc
new file mode 100644
index 0000000..9940e84
--- /dev/null
+++ b/trunk/src/net/instaweb/util/google_message_handler.cc
@@ -0,0 +1,72 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/google_message_handler.h"
+#include "net/instaweb/util/public/string_util.h"
+#include "base/logging.h"
+
+namespace net_instaweb {
+
+void GoogleMessageHandler::MessageVImpl(MessageType type, const char* msg,
+ va_list args) {
+ switch (type) {
+ case kInfo:
+ LOG(INFO) << Format(msg, args);
+ break;
+ case kWarning:
+ LOG(WARNING) << Format(msg, args);
+ break;
+ case kError:
+ LOG(ERROR) << Format(msg, args);
+ break;
+ case kFatal:
+ LOG(FATAL) << Format(msg, args);
+ break;
+ }
+}
+
+void GoogleMessageHandler::FileMessageVImpl(MessageType type, const char* file,
+ int line, const char* msg,
+ va_list args) {
+ switch (type) {
+ case kInfo:
+ LOG(INFO) << file << ":" << line << ": " << Format(msg, args);
+ break;
+ case kWarning:
+ LOG(WARNING) << file << ":" << line << ": " << Format(msg, args);
+ break;
+ case kError:
+ LOG(ERROR) << file << ":" << line << ": " << Format(msg, args);
+ break;
+ case kFatal:
+ LOG(FATAL) << file << ":" << line << ": " << Format(msg, args);
+ break;
+ }
+}
+
+// TODO(sligocki): It'd be nice not to do so much string copying.
+std::string GoogleMessageHandler::Format(const char* msg, va_list args) {
+ std::string buffer;
+
+ // Ignore the name of this routine: it formats with vsnprintf.
+ // See base/stringprintf.cc.
+ StringAppendV(&buffer, msg, args);
+ return buffer;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/google_url.cc b/trunk/src/net/instaweb/util/google_url.cc
new file mode 100644
index 0000000..2070d2b
--- /dev/null
+++ b/trunk/src/net/instaweb/util/google_url.cc
@@ -0,0 +1,124 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/google_url.h"
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+
+std::string GoogleUrl::AllExceptLeaf(const GURL& gurl) {
+ CHECK(gurl.is_valid());
+ std::string spec_str = gurl.spec();
+
+ // Find the last slash before the question-mark, if any. See
+ // http://en.wikipedia.org/wiki/URI_scheme -- the query-string
+ // syntax is not well-defined. But the query-separator is well-defined:
+ // it's a ? so I believe this implies that the first ? has to delimit
+ // the query string.
+ std::string::size_type question = spec_str.find('?');
+ std::string::size_type last_slash = spec_str.find_last_of('/', question);
+ CHECK(last_slash != std::string::npos);
+ return std::string(spec_str.data(), last_slash + 1);
+
+ // TODO(jmarantz): jmaessen suggests using GURL to do the above this
+ // way:
+ // void ResourceNamer::SetNonResourceFields(const GURL& origin) {
+ // // This code looks absolutely horrible, but appears to be the most
+ // // graceful means available to pull the information we need, intact, out
+ // // of origin.
+ // const std::string& spec = origin.possibly_invalid_spec();
+ // const url_parse::Parsed& parsed =
+ // origin.parsed_for_possibly_invalid_spec();
+ // url_parse::Component filename;
+ // url_parse::ExtractFileName(spec.data(), parsed.path, &filename);
+ // // By default, slice off trailing / character from path.
+ // size_t path_len = filename.begin - 1;
+ // if (spec[path_len] != kPathSeparatorChar) {
+ // // We have an incomplete path of some sort without a trailing /
+ // // so include all the characters in the path.
+ // path_len++;
+ // }
+ // host_and_path_.assign(spec.data(), 0, path_len);
+ // size_t filename_end = filename.begin + filename.len;
+ // size_t ext_begin = filename_end;
+ // size_t ext_len = 0;
+ // size_t filename_len = filename.len;
+ // if (filename_len > 0) {
+ // ext_begin = spec.rfind(kSeparatorChar, filename_end);
+ // if (ext_begin < static_cast<size_t>(filename.begin) ||
+ // ext_begin == spec.npos) {
+ // ext_begin = filename_end;
+ // } else {
+ // filename_len = ext_begin - filename.begin;
+ // ext_begin++;
+ // ext_len = filename_end - ext_begin;
+ // }
+ // }
+ // name_.assign(spec.data(), filename.begin, filename_len);
+ // ext_.assign(spec.data(), ext_begin, ext_len);
+ // query_params_.assign(spec.data() + filename_end,
+ // spec.size() - filename_end);
+ // }
+ //
+ // You only need about half these lines, since the above code also fishes
+ // around for an extension and splits off the query params.
+}
+
+std::string GoogleUrl::Leaf(const GURL& gurl) {
+ std::string spec_str = gurl.spec();
+ std::string::size_type question = spec_str.find('?');
+ std::string::size_type last_slash = spec_str.find_last_of('/', question);
+ CHECK(last_slash != std::string::npos);
+ return std::string(spec_str.data() + last_slash + 1,
+ spec_str.size() - (last_slash + 1));
+}
+
+// For "http://a.com/b/c/d?e=f/g returns "http://a.com" without trailing slash
+std::string GoogleUrl::Origin(const GURL& gurl) {
+ std::string spec = gurl.spec();
+ size_t origin_size = gurl.GetWithEmptyPath().spec().size();
+ CHECK_LT(0, static_cast<int>(origin_size));
+ --origin_size; // skip trailing slash
+ CHECK_LE(origin_size, spec.size());
+ CHECK_EQ('/', spec[origin_size]);
+ return std::string(spec.data(), origin_size);
+}
+
+// For "http://a.com/b/c/d?e=f/g returns "/b/c/d?e=f/g" including leading slash
+std::string GoogleUrl::PathAndLeaf(const GURL& gurl) {
+ std::string spec = gurl.spec();
+ size_t origin_size = gurl.GetWithEmptyPath().spec().size();
+ CHECK_LT(0, static_cast<int>(origin_size));
+ --origin_size; // include trailing slash
+ CHECK_LE(origin_size, spec.size());
+ CHECK_EQ('/', spec[origin_size]);
+ CHECK_LE(origin_size, spec.size());
+ return std::string(spec.data() + origin_size, spec.size() - origin_size);
+}
+
+// For "http://a.com/b/c/d?e=f/g returns "/b/c/d/" including leading and
+// trailing slashes.
+std::string GoogleUrl::PathSansLeaf(const GURL& gurl) {
+ std::string path_and_leaf = GoogleUrl::PathAndLeaf(gurl);
+ std::string leaf = GoogleUrl::Leaf(gurl);
+ CHECK_GE(path_and_leaf.size(), leaf.size());
+ return std::string(path_and_leaf.data(), path_and_leaf.size() - leaf.size());
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/google_url_test.cc b/trunk/src/net/instaweb/util/google_url_test.cc
new file mode 100644
index 0000000..b5341c2
--- /dev/null
+++ b/trunk/src/net/instaweb/util/google_url_test.cc
@@ -0,0 +1,82 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+// Unit-test the string-splitter.
+
+#include "net/instaweb/util/public/google_url.h"
+#include "net/instaweb/util/public/gtest.h"
+
+namespace {
+
+const char kUrl[] = "http://a.com/b/c/d.ext?f=g/h";
+const char kUrlWithPort[] = "http://a.com:8080/b/c/d.ext?f=g/h";
+
+} // namespace
+
+namespace net_instaweb {
+
+class GoogleUrlTest : public testing::Test {
+ protected:
+ GoogleUrlTest() : gurl_(kUrl), gurl_with_port_(kUrlWithPort) {}
+
+ GURL gurl_;
+ GURL gurl_with_port_;
+};
+
+TEST_F(GoogleUrlTest, TestSpec) {
+ EXPECT_EQ(std::string(kUrl), GoogleUrl::Spec(gurl_));
+ EXPECT_EQ(std::string("http://a.com/b/c/"), GoogleUrl::AllExceptLeaf(gurl_));
+ EXPECT_EQ(std::string("d.ext?f=g/h"), GoogleUrl::Leaf(gurl_));
+ EXPECT_EQ(std::string("http://a.com"), GoogleUrl::Origin(gurl_));
+ EXPECT_EQ(std::string("/b/c/d.ext?f=g/h"), GoogleUrl::PathAndLeaf(gurl_));
+ EXPECT_EQ(std::string("/b/c/d.ext"), GoogleUrl::Path(gurl_));
+}
+
+TEST_F(GoogleUrlTest, TestSpecWithPort) {
+ EXPECT_EQ(std::string(kUrlWithPort), GoogleUrl::Spec(gurl_with_port_));
+ EXPECT_EQ(std::string("http://a.com:8080/b/c/"),
+ GoogleUrl::AllExceptLeaf(gurl_with_port_));
+ EXPECT_EQ(std::string("d.ext?f=g/h"), GoogleUrl::Leaf(gurl_with_port_));
+ EXPECT_EQ(std::string("http://a.com:8080"), GoogleUrl::Origin(gurl_with_port_));
+ EXPECT_EQ(std::string("/b/c/d.ext?f=g/h"),
+ GoogleUrl::PathAndLeaf(gurl_with_port_));
+ EXPECT_EQ(std::string("/b/c/d.ext"), GoogleUrl::Path(gurl_));
+ EXPECT_EQ(std::string("/b/c/"), GoogleUrl::PathSansLeaf(gurl_));
+}
+
+TEST_F(GoogleUrlTest, ResolveRelative) {
+ GURL base = GoogleUrl::Create(StringPiece("http://www.google.com"));
+ ASSERT_TRUE(base.is_valid());
+ GURL resolved = GoogleUrl::Resolve(base, "test.html");
+ ASSERT_TRUE(resolved.is_valid());
+ EXPECT_EQ(std::string("http://www.google.com/test.html"),
+ GoogleUrl::Spec(resolved));
+ EXPECT_EQ(std::string("/test.html"), GoogleUrl::Path(resolved));
+}
+
+TEST_F(GoogleUrlTest, ResolveAbsolute) {
+ GURL base = GoogleUrl::Create(StringPiece("http://www.google.com"));
+ ASSERT_TRUE(base.is_valid());
+ GURL resolved = GoogleUrl::Resolve(base, "http://www.google.com");
+ ASSERT_TRUE(resolved.is_valid());
+ EXPECT_EQ(std::string("http://www.google.com/"),
+ GoogleUrl::Spec(resolved));
+ EXPECT_EQ(std::string("/"), GoogleUrl::Path(resolved));
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/gtest.cc b/trunk/src/net/instaweb/util/gtest.cc
new file mode 100644
index 0000000..c7b90f9
--- /dev/null
+++ b/trunk/src/net/instaweb/util/gtest.cc
@@ -0,0 +1,62 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+//
+
+#include <unistd.h> // For getpid()
+
+#include "net/instaweb/util/public/gtest.h"
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/stack_buffer.h"
+
+namespace net_instaweb {
+
+
+std::string GTestSrcDir() {
+ // Climb up the directory hierarchy till we find "src".
+ // TODO(jmarantz): check to make sure we are not in a subdirectory of
+ // our top-level 'src' named src.
+
+ char cwd[kStackBufferSize];
+ CHECK(getcwd(cwd, sizeof(cwd)) != NULL);
+ std::vector<StringPiece> components;
+ SplitStringPieceToVector(cwd, "/", &components, true);
+ int level = components.size();
+ bool found = false;
+ for (int i = level - 1; i >= 0; --i) {
+ if (components[i] == "src") {
+ level = i + 1;
+ found = true;
+ break;
+ }
+ }
+
+ CHECK(found) << "Cannot find 'src' directory from cwd=" << cwd;
+ std::string src_dir;
+ for (int i = 0; i < level; ++i) {
+ src_dir += "/";
+ components[i].AppendToString(&src_dir);
+ }
+ return src_dir;
+}
+
+std::string GTestTempDir() {
+ return StringPrintf("/tmp/gtest.%d", getpid());
+}
+
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/gzip_inflater.cc b/trunk/src/net/instaweb/util/gzip_inflater.cc
new file mode 100644
index 0000000..5e2f0bf
--- /dev/null
+++ b/trunk/src/net/instaweb/util/gzip_inflater.cc
@@ -0,0 +1,180 @@
+/**
+ * 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.
+ */
+
+#include "net/instaweb/util/public/gzip_inflater.h"
+
+#include <stdlib.h>
+#include "base/logging.h"
+#ifdef USE_SYSTEM_ZLIB
+#include "zlib.h"
+#else
+#include "third_party/zlib/zlib.h"
+#endif
+
+namespace net_instaweb {
+
+GzipInflater::GzipInflater(InflateType type)
+ : zlib_(NULL),
+ finished_(false),
+ error_(false),
+ type_(type) {
+}
+
+GzipInflater::~GzipInflater() {
+ Free();
+}
+
+void GzipInflater::Free() {
+ if (zlib_ == NULL) {
+ // Already freed.
+ return;
+ }
+
+ int err = inflateEnd(zlib_);
+ if (err != Z_OK) {
+ error_ = true;
+ }
+
+ free(zlib_);
+
+ zlib_ = NULL;
+}
+
+bool GzipInflater::Init() {
+ if (zlib_ != NULL) {
+ return false;
+ }
+
+ zlib_ = static_cast<z_stream *>(malloc(sizeof(z_stream)));
+ if (zlib_ == NULL) {
+ return false;
+ }
+ memset(zlib_, 0, sizeof(z_stream));
+
+ // Tell zlib that the data is gzip-encoded or deflate-encode.
+ int window_size = 31; // window size of 15, plus 16 for gzip
+ if (type_ == kDeflate) {
+ window_size = -15; // window size of -15 for row deflate.
+ }
+ int err = inflateInit2(zlib_, window_size);
+
+ if (err != Z_OK) {
+ Free();
+ error_ = true;
+ return false;
+ }
+
+ return true;
+}
+
+bool GzipInflater::HasUnconsumedInput() const {
+ if (zlib_ == NULL) {
+ return false;
+ }
+
+ if (finished_ || error_) {
+ return false;
+ }
+
+ return zlib_->avail_in > 0;
+}
+
+bool GzipInflater::SetInput(const void *in, size_t in_size) {
+ if (zlib_ == NULL) {
+ return false;
+ }
+
+ if (HasUnconsumedInput()) {
+ return false;
+ }
+
+ if (finished_) {
+ return false;
+ }
+
+ if (error_) {
+ return false;
+ }
+
+ if (in == NULL || in_size == 0) {
+ return false;
+ }
+
+ // The zlib library won't modify the buffer, but it does not use
+ // const here, so we must const cast.
+ zlib_->next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(in));
+ zlib_->avail_in = static_cast<uInt>(in_size);
+
+ return true;
+}
+
+int GzipInflater::InflateBytes(char *buf, size_t buf_size) {
+ if (zlib_ == NULL) {
+ return -1;
+ }
+
+ if (!HasUnconsumedInput()) {
+ return -1;
+ }
+
+ if (finished_) {
+ return -1;
+ }
+
+ if (error_) {
+ return -1;
+ }
+
+ if (buf == NULL || buf_size == 0) {
+ return -1;
+ }
+
+ const size_t inflated_bytes_before = zlib_->total_out;
+
+ zlib_->next_out = reinterpret_cast<Bytef *>(buf);
+ zlib_->avail_out = static_cast<uInt>(buf_size);
+
+ int err = inflate(zlib_, Z_SYNC_FLUSH);
+
+ const size_t inflated_bytes = zlib_->total_out - inflated_bytes_before;
+
+ if (err == Z_STREAM_END) {
+ finished_ = true;
+ } else if (err == Z_OK) {
+ if (inflated_bytes < buf_size) {
+ // Sanity check that if we didn't fill the output buffer, it's
+ // because we consumed all of the input.
+ DCHECK(!HasUnconsumedInput());
+ }
+ } else if (err == Z_BUF_ERROR) {
+ // Sanity check that if we encountered this error, it's because we
+ // were unable to write any inflated bytes to the output
+ // buffer. zlib documentation says that this is a non-terminal
+ // error, so we do not set error_ to true here.
+ DCHECK_EQ(inflated_bytes, static_cast<size_t>(0));
+ } else {
+ error_ = true;
+ return -1;
+ }
+
+ return static_cast<int>(inflated_bytes);
+}
+
+void GzipInflater::ShutDown() {
+ Free();
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/gzip_inflater_test.cc b/trunk/src/net/instaweb/util/gzip_inflater_test.cc
new file mode 100644
index 0000000..7603a43
--- /dev/null
+++ b/trunk/src/net/instaweb/util/gzip_inflater_test.cc
@@ -0,0 +1,135 @@
+/**
+ * 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.
+ */
+
+#include <string>
+#include "net/instaweb/util/public/gzip_inflater.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net_instaweb {
+
+namespace {
+
+const char kBasic[] = "Hello\n";
+
+// The above string "Hello\n", gzip compressed.
+const unsigned char kCompressed[] = {
+ 0x1f, 0x8b, 0x08, 0x08, 0x38, 0x18, 0x2e, 0x4c, 0x00, 0x03, 0x63,
+ 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x2e, 0x68,
+ 0x74, 0x6d, 0x6c, 0x00, 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0xe7, 0x02,
+ 0x00, 0x16, 0x35, 0x96, 0x31, 0x06, 0x00, 0x00, 0x00
+};
+
+// This is the word "test" deflated
+const char kTestDeflate[] ="\x2b\x49\x2d\x2e\x01";
+const char kTest[] = "test";
+
+const size_t kBufSize = 256;
+
+TEST(GzipInflaterTest, Simple) {
+ std::string buf;
+ buf.reserve(kBufSize);
+ GzipInflater inflater(GzipInflater::kGzip);
+ inflater.Init();
+ ASSERT_FALSE(inflater.HasUnconsumedInput());
+ ASSERT_TRUE(inflater.SetInput(kCompressed, sizeof(kCompressed)));
+ ASSERT_TRUE(inflater.HasUnconsumedInput());
+ int num_inflated_bytes = inflater.InflateBytes(&buf[0], kBufSize);
+ ASSERT_EQ(strlen(kBasic), static_cast<size_t>(num_inflated_bytes));
+ // null-terminate the buffer
+ buf[num_inflated_bytes] = '\0';
+ ASSERT_FALSE(inflater.HasUnconsumedInput());
+ ASSERT_TRUE(inflater.finished());
+ ASSERT_FALSE(inflater.error());
+ inflater.ShutDown();
+ ASSERT_STREQ(kBasic, buf.c_str());
+}
+
+TEST(GzipInflaterTest, OneByteAtATime) {
+ std::string buf;
+ buf.reserve(kBufSize);
+ GzipInflater inflater(GzipInflater::kGzip);
+ inflater.Init();
+ int num_inflated_bytes = 0;
+ ASSERT_FALSE(inflater.HasUnconsumedInput());
+ for (size_t input_offset = 0;
+ input_offset < sizeof(kCompressed);
+ ++input_offset) {
+ ASSERT_TRUE(inflater.SetInput(&kCompressed[input_offset], 1));
+ ASSERT_TRUE(inflater.HasUnconsumedInput());
+ num_inflated_bytes +=
+ inflater.InflateBytes(&buf[num_inflated_bytes],
+ kBufSize - num_inflated_bytes);
+ }
+ ASSERT_EQ(strlen(kBasic), static_cast<size_t>(num_inflated_bytes));
+ // null-terminate the buffer
+ buf[num_inflated_bytes] = '\0';
+ ASSERT_FALSE(inflater.HasUnconsumedInput());
+ ASSERT_TRUE(inflater.finished());
+ ASSERT_FALSE(inflater.error());
+ inflater.ShutDown();
+ ASSERT_STREQ(kBasic, buf.c_str());
+}
+
+TEST(GzipInflaterTest, SimpleDeflate) {
+ std::string buf;
+ buf.reserve(kBufSize);
+ GzipInflater inflater(GzipInflater::kDeflate);
+ inflater.Init();
+ ASSERT_FALSE(inflater.HasUnconsumedInput());
+ ASSERT_TRUE(inflater.SetInput(kTestDeflate, sizeof(kTestDeflate)));
+ ASSERT_TRUE(inflater.HasUnconsumedInput());
+ int num_inflated_bytes = inflater.InflateBytes(&buf[0], kBufSize);
+ ASSERT_EQ(strlen(kTest), static_cast<size_t>(num_inflated_bytes));
+ // null-terminate the buffer
+ buf[num_inflated_bytes] = '\0';
+ ASSERT_FALSE(inflater.HasUnconsumedInput());
+ ASSERT_TRUE(inflater.finished());
+ ASSERT_FALSE(inflater.error());
+ inflater.ShutDown();
+ ASSERT_STREQ(kTest, buf.c_str());
+}
+
+TEST(GzipInflaterTest, OneByteAtATimeDeflate) {
+ std::string buf;
+ buf.reserve(kBufSize);
+ GzipInflater inflater(GzipInflater::kDeflate);
+ inflater.Init();
+ int num_inflated_bytes = 0;
+ ASSERT_FALSE(inflater.HasUnconsumedInput());
+ for (size_t input_offset = 0;
+ input_offset < sizeof(kTestDeflate);
+ ++input_offset) {
+ ASSERT_TRUE(inflater.SetInput(&kTestDeflate[input_offset], 1));
+ ASSERT_TRUE(inflater.HasUnconsumedInput());
+ num_inflated_bytes +=
+ inflater.InflateBytes(&buf[num_inflated_bytes],
+ kBufSize - num_inflated_bytes);
+ }
+ ASSERT_EQ(strlen(kTest), static_cast<size_t>(num_inflated_bytes));
+ // null-terminate the buffer
+ buf[num_inflated_bytes] = '\0';
+ ASSERT_FALSE(inflater.HasUnconsumedInput());
+ ASSERT_TRUE(inflater.finished());
+ ASSERT_FALSE(inflater.error());
+ inflater.ShutDown();
+ ASSERT_STREQ(kTest, buf.c_str());
+}
+
+
+} // namespace
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/hasher.cc b/trunk/src/net/instaweb/util/hasher.cc
new file mode 100644
index 0000000..5b5ef4c
--- /dev/null
+++ b/trunk/src/net/instaweb/util/hasher.cc
@@ -0,0 +1,26 @@
+/**
+ * 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/util/public/hasher.h"
+
+namespace net_instaweb {
+
+Hasher::~Hasher() {
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/http_cache.cc b/trunk/src/net/instaweb/util/http_cache.cc
new file mode 100644
index 0000000..0cd2232
--- /dev/null
+++ b/trunk/src/net/instaweb/util/http_cache.cc
@@ -0,0 +1,174 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/http_cache.h"
+#include "net/instaweb/util/public/cache_interface.h"
+#include "net/instaweb/util/public/http_value.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/meta_data.h"
+#include "net/instaweb/util/public/shared_string.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include "net/instaweb/util/public/statistics.h"
+#include "net/instaweb/util/public/timer.h"
+
+namespace net_instaweb {
+
+namespace {
+
+// Remember that a Fetch failed for 5 minutes.
+// TODO(jmarantz): consider allowing this to be configurable.
+//
+// TODO(jmarantz): We could handle cc-private a little differently:
+// in this case we could arguably remember it using the original cc-private ttl.
+const char kRememberNotFoundCacheControl[] = "max-age=300";
+const char kCacheTimeUs[] = "cache_time_us";
+const char kCacheHits[] = "cache_hits";
+const char kCacheMisses[] = "cache_misses";
+const char kCacheExpirations[] = "cache_expirations";
+const char kCacheInserts[] = "cache_inserts";
+
+} // namespace
+
+
+HTTPCache::~HTTPCache() {}
+
+bool HTTPCache::IsCurrentlyValid(const MetaData& headers, int64 now_ms) {
+ if (force_caching_) {
+ return true;
+ }
+ if (!headers.IsCacheable() || !headers.IsProxyCacheable()) {
+ // TODO(jmarantz): Should we have a separate 'force' bit that doesn't
+ // expired resources to be valid, but does ignore cache-control:private?
+ return false;
+ }
+ if (headers.CacheExpirationTimeMs() > now_ms) {
+ return true;
+ }
+ if (cache_expirations_ != NULL) {
+ cache_expirations_->Add(1);
+ }
+ return false;
+}
+
+HTTPCache::FindResult HTTPCache::Find(
+ const std::string& key, HTTPValue* value, MetaData* headers,
+ MessageHandler* handler) {
+ SharedString cache_buffer;
+
+ int64 start_us = timer_->NowUs();
+ int64 now_ms = start_us / 1000;
+ FindResult ret = kNotFound;
+
+ if ((cache_->Get(key, &cache_buffer) &&
+ value->Link(&cache_buffer, headers, handler))) {
+ if (IsCurrentlyValid(*headers, now_ms)) {
+ if (headers->status_code() == HttpStatus::kRememberNotFoundStatusCode) {
+ long remember_not_found_time_ms = headers->CacheExpirationTimeMs()
+ - now_ms;
+ handler->Info(key.c_str(), 0,
+ "HTTPCache: remembering not-found status for %ld seconds",
+ remember_not_found_time_ms / 1000);
+ ret = kRecentFetchFailedDoNotRefetch;
+ } else {
+ ret = kFound;
+ }
+ }
+ }
+
+ if (cache_time_us_ != NULL) {
+ long delta_us = timer_->NowUs() - start_us;
+ cache_time_us_->Add(delta_us);
+ if (ret == kFound) {
+ cache_hits_->Add(1);
+ } else {
+ cache_misses_->Add(1);
+ }
+ }
+
+ if (ret != kFound) {
+ headers->Clear();
+ value->Clear();
+ }
+ return ret;
+}
+
+void HTTPCache::RememberNotCacheable(const std::string& key,
+ MessageHandler* handler) {
+ SimpleMetaData headers;
+ headers.set_status_code(HttpStatus::kRememberNotFoundStatusCode);
+ headers.Add(HttpAttributes::kCacheControl, kRememberNotFoundCacheControl);
+ int64 now_ms = timer_->NowMs();
+ headers.UpdateDateHeader(HttpAttributes::kDate, now_ms);
+ headers.ComputeCaching();
+ headers.set_headers_complete(true);
+ Put(key, headers, "", handler);
+}
+
+void HTTPCache::Put(const std::string& key, HTTPValue* value,
+ MessageHandler* handler) {
+ SharedString* shared_string = value->share();
+ cache_->Put(key, shared_string);
+}
+
+void HTTPCache::Put(const std::string& key, const MetaData& headers,
+ const StringPiece& content,
+ MessageHandler* handler) {
+ int64 start_us = timer_->NowUs();
+ int64 now_ms = start_us / 1000;
+ if (!IsCurrentlyValid(headers, now_ms)) {
+ return;
+ }
+
+ HTTPValue value;
+ value.SetHeaders(headers);
+ value.Write(content, handler);
+ Put(key, &value, handler);
+ if (cache_time_us_ != NULL) {
+ int64 delta_us = timer_->NowUs() - start_us;
+ cache_time_us_->Add(delta_us);
+ cache_inserts_->Add(1);
+ }
+}
+
+CacheInterface::KeyState HTTPCache::Query(const std::string& key) {
+ return cache_->Query(key);
+}
+
+void HTTPCache::Delete(const std::string& key) {
+ return cache_->Delete(key);
+}
+
+void HTTPCache::Initialize(Statistics* statistics) {
+ statistics->AddVariable(kCacheTimeUs);
+ statistics->AddVariable(kCacheHits);
+ statistics->AddVariable(kCacheMisses);
+ statistics->AddVariable(kCacheExpirations);
+ statistics->AddVariable(kCacheInserts);
+}
+
+void HTTPCache::SetStatistics(Statistics* statistics) {
+ if (statistics != NULL) {
+ cache_time_us_ = statistics->GetVariable(kCacheTimeUs);
+ cache_hits_ = statistics->GetVariable(kCacheHits);
+ cache_misses_ = statistics->GetVariable(kCacheMisses);
+ cache_expirations_ = statistics->GetVariable(kCacheExpirations);
+ cache_inserts_ = statistics->GetVariable(kCacheInserts);
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/http_cache_test.cc b/trunk/src/net/instaweb/util/http_cache_test.cc
new file mode 100644
index 0000000..34719aa
--- /dev/null
+++ b/trunk/src/net/instaweb/util/http_cache_test.cc
@@ -0,0 +1,141 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+// Unit-test the lru cache
+
+#include "net/instaweb/util/public/http_cache.h"
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "net/instaweb/util/public/cache_interface.h"
+#include "net/instaweb/util/public/google_message_handler.h"
+#include "net/instaweb/util/public/gtest.h"
+#include "net/instaweb/util/public/http_value.h"
+#include "net/instaweb/util/public/lru_cache.h"
+#include "net/instaweb/util/public/mock_timer.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace {
+// Set the cache size large enough so nothing gets evicted during this test.
+const int kMaxSize = 10000;
+const char kStartDate[] = "Sun, 16 Dec 1979 02:27:45 GMT";
+}
+
+namespace net_instaweb {
+
+class HTTPCacheTest : public testing::Test {
+ protected:
+ static int64 ParseDate(const char* start_date) {
+ int64 time_ms;
+ MetaData::ParseTime(start_date, &time_ms);
+ return time_ms;
+ }
+
+ HTTPCacheTest() : mock_timer_(ParseDate(kStartDate)),
+ http_cache_(new LRUCache(kMaxSize), &mock_timer_) {
+ }
+
+ void InitHeaders(MetaData* headers, const char* cache_control) {
+ headers->Add("name", "value");
+ headers->Add("Date", kStartDate);
+ if (cache_control != NULL) {
+ headers->Add("Cache-control", cache_control);
+ }
+ headers->SetStatusAndReason(HttpStatus::kOK);
+ headers->ComputeCaching();
+ }
+
+ MockTimer mock_timer_;
+ HTTPCache http_cache_;
+ GoogleMessageHandler message_handler_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HTTPCacheTest);
+};
+
+// Simple flow of putting in an item, getting it.
+TEST_F(HTTPCacheTest, PutGet) {
+ SimpleMetaData meta_data_in, meta_data_out;
+ InitHeaders(&meta_data_in, "max-age=300");
+ http_cache_.Put("mykey", meta_data_in, "content", &message_handler_);
+ EXPECT_EQ(CacheInterface::kAvailable, http_cache_.Query("mykey"));
+ HTTPValue value;
+ HTTPCache::FindResult found = http_cache_.Find(
+ "mykey", &value, &meta_data_out, &message_handler_);
+ ASSERT_EQ(HTTPCache::kFound, found);
+ ASSERT_TRUE(meta_data_out.headers_complete());
+ StringPiece contents;
+ ASSERT_TRUE(value.ExtractContents(&contents));
+ CharStarVector values;
+ ASSERT_TRUE(meta_data_out.Lookup("name", &values));
+ ASSERT_EQ(static_cast<size_t>(1), values.size());
+ EXPECT_EQ(std::string("value"), std::string(values[0]));
+ EXPECT_EQ("content", contents);
+
+ // Now advance time 301 seconds and the we should no longer
+ // be able to fetch this resource out of the cache.
+ mock_timer_.advance_ms(301 * 1000);
+ found = http_cache_.Find("mykey", &value, &meta_data_out, &message_handler_);
+ ASSERT_EQ(HTTPCache::kNotFound, found);
+ ASSERT_FALSE(meta_data_out.headers_complete());
+}
+
+// Verifies that the cache will 'remember' that a fetch should not be
+// cached for 5 minutes.
+TEST_F(HTTPCacheTest, RememberNotCacheable) {
+ SimpleMetaData meta_data_out;
+ http_cache_.RememberNotCacheable("mykey", &message_handler_);
+ HTTPValue value;
+ EXPECT_EQ(HTTPCache::kRecentFetchFailedDoNotRefetch,
+ http_cache_.Find("mykey", &value, &meta_data_out,
+ &message_handler_));
+
+ // Now advance time 301 seconds; the cache should allow us to try fetching
+ // again.
+ mock_timer_.advance_ms(301 * 1000);
+ EXPECT_EQ(HTTPCache::kNotFound,
+ http_cache_.Find("mykey", &value, &meta_data_out,
+ &message_handler_));
+}
+
+TEST_F(HTTPCacheTest, Uncacheable) {
+ SimpleMetaData meta_data_in, meta_data_out;
+ InitHeaders(&meta_data_in, NULL);
+ http_cache_.Put("mykey", meta_data_in, "content", &message_handler_);
+ EXPECT_EQ(CacheInterface::kNotFound, http_cache_.Query("mykey"));
+ HTTPValue value;
+ HTTPCache::FindResult found = http_cache_.Find(
+ "mykey", &value, &meta_data_out, &message_handler_);
+ ASSERT_EQ(HTTPCache::kNotFound, found);
+ ASSERT_FALSE(meta_data_out.headers_complete());
+}
+
+TEST_F(HTTPCacheTest, UncacheablePrivate) {
+ SimpleMetaData meta_data_in, meta_data_out;
+ InitHeaders(&meta_data_in, "private, max-age=300");
+ http_cache_.Put("mykey", meta_data_in, "content", &message_handler_);
+ EXPECT_EQ(CacheInterface::kNotFound, http_cache_.Query("mykey"));
+ HTTPValue value;
+ HTTPCache::FindResult found = http_cache_.Find(
+ "mykey", &value, &meta_data_out, &message_handler_);
+ ASSERT_EQ(HTTPCache::kNotFound, found);
+ ASSERT_FALSE(meta_data_out.headers_complete());
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/http_dump_url_async_writer.cc b/trunk/src/net/instaweb/util/http_dump_url_async_writer.cc
new file mode 100644
index 0000000..e873f27
--- /dev/null
+++ b/trunk/src/net/instaweb/util/http_dump_url_async_writer.cc
@@ -0,0 +1,150 @@
+/**
+ * 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/util/public/http_dump_url_async_writer.h"
+
+#include "net/instaweb/util/public/file_writer.h"
+#include "net/instaweb/util/public/google_url.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/meta_data.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include "net/instaweb/util/public/string_writer.h"
+
+namespace net_instaweb {
+
+class HttpDumpUrlAsyncWriter::Fetch : UrlAsyncFetcher::Callback {
+ public:
+ Fetch(const std::string& url, const MetaData& request_headers,
+ MetaData* response_headers, Writer* response_writer,
+ MessageHandler* handler, Callback* callback,
+ const std::string& filename, UrlFetcher* dump_fetcher,
+ FileSystem* file_system)
+ : url_(url), response_headers_(response_headers),
+ response_writer_(response_writer), handler_(handler),
+ callback_(callback), filename_(filename), dump_fetcher_(dump_fetcher),
+ file_system_(file_system), string_writer_(&contents_) {
+ request_headers_.CopyFrom(request_headers);
+ }
+
+ // Like UrlAsyncFetcher::StreamingFetch, returns true if callback has been
+ // called already.
+ bool StartFetch(const bool accept_gzip, UrlAsyncFetcher* base_fetcher) {
+ // In general we will want to always ask the origin for gzipped output,
+ // but we are leaving in variable so this could be overridden by the
+ // instantiator of the DumpUrlWriter.
+ compress_headers_.CopyFrom(request_headers_);
+ if (accept_gzip) {
+ compress_headers_.RemoveAll(HttpAttributes::kAcceptEncoding);
+ compress_headers_.Add(HttpAttributes::kAcceptEncoding,
+ HttpAttributes::kGzip);
+ }
+
+ return base_fetcher->StreamingFetch(url_, compress_headers_,
+ &compressed_response_, &string_writer_,
+ handler_, this);
+ }
+
+ // Finishes the Fetch when called back.
+ void Done(bool success) {
+ compressed_response_.RemoveAll(HttpAttributes::kContentLength);
+ compressed_response_.Add(HttpAttributes::kContentLength,
+ IntegerToString(contents_.size()).c_str());
+ compressed_response_.ComputeCaching();
+
+ // Do not write an empty file if the fetch failed.
+ if (success) {
+ FileSystem::OutputFile* file = file_system_->OpenTempFile(
+ filename_ + ".temp", handler_);
+ if (file != NULL) {
+ handler_->Message(kInfo, "Storing %s as %s", url_.c_str(),
+ filename_.c_str());
+ std::string temp_filename = file->filename();
+ FileWriter file_writer(file);
+ success = compressed_response_.Write(&file_writer, handler_) &&
+ file->Write(contents_, handler_);
+ success &= file_system_->Close(file, handler_);
+ success &= file_system_->RenameFile(temp_filename.c_str(),
+ filename_.c_str(),
+ handler_);
+ } else {
+ success = false;
+ }
+ }
+
+ // We are not going to be able to read the response from the file
+ // system so we better pass the error message through.
+ if (!success) {
+ response_headers_->CopyFrom(compressed_response_);
+ response_writer_->Write(contents_, handler_);
+ } else {
+ // Let dump fetcher fetch the actual response so that it can decompress.
+ success = dump_fetcher_->StreamingFetchUrl(
+ url_, request_headers_, response_headers_, response_writer_,
+ handler_);
+ }
+
+ callback_->Done(success);
+ delete this;
+ }
+
+ private:
+ const std::string url_;
+ SimpleMetaData request_headers_;
+ MetaData* response_headers_;
+ Writer* response_writer_;
+ MessageHandler* handler_;
+ Callback* callback_;
+
+ const std::string filename_;
+ UrlFetcher* dump_fetcher_;
+ FileSystem* file_system_;
+
+ std::string contents_;
+ StringWriter string_writer_;
+ SimpleMetaData compress_headers_;
+ SimpleMetaData compressed_response_;
+
+ DISALLOW_COPY_AND_ASSIGN(Fetch);
+};
+
+HttpDumpUrlAsyncWriter::~HttpDumpUrlAsyncWriter() {
+}
+
+bool HttpDumpUrlAsyncWriter::StreamingFetch(const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* response_writer,
+ MessageHandler* handler,
+ Callback* callback) {
+ std::string filename;
+ dump_fetcher_.GetFilename(GURL(url), &filename, handler);
+
+ if (file_system_->Exists(filename.c_str(), handler).is_true()) {
+ bool success = dump_fetcher_.StreamingFetchUrl(
+ url, request_headers, response_headers, response_writer, handler);
+ callback->Done(success);
+ return true;
+ } else {
+ Fetch* fetch = new Fetch(url, request_headers, response_headers,
+ response_writer, handler, callback, filename,
+ &dump_fetcher_, file_system_);
+ return fetch->StartFetch(accept_gzip_, base_fetcher_);
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/http_dump_url_async_writer_test.cc b/trunk/src/net/instaweb/util/http_dump_url_async_writer_test.cc
new file mode 100644
index 0000000..7027dca
--- /dev/null
+++ b/trunk/src/net/instaweb/util/http_dump_url_async_writer_test.cc
@@ -0,0 +1,111 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+// Author: sligocki@google.com (Shawn Ligocki)
+
+#include "net/instaweb/util/public/http_dump_url_async_writer.h"
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/fetcher_test.h"
+#include "net/instaweb/util/public/mock_timer.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include "net/instaweb/util/public/stdio_file_system.h"
+
+namespace net_instaweb {
+
+// TODO(sligocki): Merge with CacheUrlAsyncFetcherTest and refactor into
+// FetcherTestBase.
+class HttpDumpUrlAsyncWriterTest : public FetcherTest {
+ protected:
+ HttpDumpUrlAsyncWriterTest()
+ : root_dir_(GTestTempDir() + "/http_dump_url_async_writer_test/"),
+ mock_timer_(0),
+ dump_fetcher_(root_dir_, &mock_async_fetcher_, &file_system_,
+ &mock_timer_) {
+ }
+
+ UrlAsyncFetcher* async_fetcher() { return &dump_fetcher_; }
+
+ std::string root_dir_;
+ StdioFileSystem file_system_;
+ MockTimer mock_timer_;
+ HttpDumpUrlAsyncWriter dump_fetcher_;
+};
+
+TEST_F(HttpDumpUrlAsyncWriterTest, TestCacheable) {
+ // With the async cached fetching interface, we will expect even the
+ // initial request to succeed, once the callbacks are run.
+ bool callback_called1, callback_called2, callback_called3;
+ EXPECT_EQ(1, CountFetchesAsync(kGoodUrl, true, &callback_called1));
+ EXPECT_FALSE(callback_called1);
+
+ EXPECT_EQ(1, CountFetchesAsync(kGoodUrl, true, &callback_called2));
+ EXPECT_FALSE(callback_called1);
+ EXPECT_FALSE(callback_called2);
+
+ mock_async_fetcher_.CallCallbacks();
+ EXPECT_TRUE(callback_called1);
+ EXPECT_TRUE(callback_called2);
+
+ EXPECT_EQ(0, CountFetchesAsync(kGoodUrl, true, &callback_called3));
+ // No async fetcher callbacks were queued because the content
+ // was cached, so no need to call CallCallbacks() again here.
+ EXPECT_TRUE(callback_called3);
+}
+
+TEST_F(HttpDumpUrlAsyncWriterTest, TestNotCacheable) {
+ // With the async cached fetching interface, we will expect even the
+ // initial request to succeed, once the callbacks are run.
+ bool callback_called1, callback_called2, callback_called3;
+ EXPECT_EQ(1, CountFetchesAsync(kNotCachedUrl, true, &callback_called1));
+ EXPECT_FALSE(callback_called1);
+
+ EXPECT_EQ(1, CountFetchesAsync(kNotCachedUrl, true, &callback_called2));
+ EXPECT_FALSE(callback_called1);
+ EXPECT_FALSE(callback_called2);
+
+ mock_async_fetcher_.CallCallbacks();
+ EXPECT_TRUE(callback_called1);
+ EXPECT_TRUE(callback_called2);
+
+ // This is not a proper cache and does not distinguish between cacheable
+ // or non-cacheable URLs.
+ EXPECT_EQ(0, CountFetchesAsync(kNotCachedUrl, true, &callback_called3));
+ EXPECT_TRUE(callback_called3);
+}
+
+TEST_F(HttpDumpUrlAsyncWriterTest, TestCacheWithASyncFetcherFail) {
+ bool callback_called1, callback_called2, callback_called3;
+
+ EXPECT_EQ(1, CountFetchesAsync(kBadUrl, false, &callback_called1));
+ EXPECT_FALSE(callback_called1);
+
+ EXPECT_EQ(1, CountFetchesAsync(kBadUrl, false, &callback_called2));
+ EXPECT_FALSE(callback_called1);
+ EXPECT_FALSE(callback_called2);
+
+ mock_async_fetcher_.CallCallbacks();
+ EXPECT_TRUE(callback_called1);
+ EXPECT_TRUE(callback_called2);
+
+ EXPECT_EQ(1, CountFetchesAsync(kBadUrl, false, &callback_called3));
+ EXPECT_FALSE(callback_called3);
+ mock_async_fetcher_.CallCallbacks(); // Otherwise memory will be leaked
+ EXPECT_TRUE(callback_called3);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/http_dump_url_fetcher.cc b/trunk/src/net/instaweb/util/http_dump_url_fetcher.cc
new file mode 100644
index 0000000..6700954
--- /dev/null
+++ b/trunk/src/net/instaweb/util/http_dump_url_fetcher.cc
@@ -0,0 +1,300 @@
+/**
+ * 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/util/public/http_dump_url_fetcher.h"
+
+#include <stdio.h>
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/util/public/file_system.h"
+#include "net/instaweb/util/public/filename_encoder.h"
+#include "net/instaweb/util/public/google_url.h"
+#include "net/instaweb/util/public/gzip_inflater.h"
+#include "net/instaweb/util/public/http_response_parser.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/meta_data.h"
+#include "net/instaweb/util/public/null_message_handler.h"
+#include <string>
+#include "net/instaweb/util/public/timer.h"
+#include "net/instaweb/util/public/writer.h"
+#include "net/instaweb/util/stack_buffer.h"
+
+namespace net_instaweb {
+
+namespace {
+
+static const char kErrorHtml[] =
+ "<html><head><title>HttpDumpUrlFetcher Error</title></head>"
+ "<body><h1>HttpDumpUrlFetcher Error</h1></body></html>";
+
+void ApplyTimeDelta(const char* attr, int64 delta_ms, MetaData* headers) {
+ int64 time_ms;
+ if (headers->ParseDateHeader(attr, &time_ms) && (time_ms > delta_ms)) {
+ headers->UpdateDateHeader(attr, time_ms + delta_ms);
+ }
+}
+
+// The slurped files we read off the disk will contain a Date header from the
+// time we did the slurp. They may have an Expires header shortly after that.
+// As part of the dump-fetching process, we will want to correct the Date
+// header based on the current time, and also update the Expires header by
+// the same delta.
+void CorrectDateHeaders(int64 now_ms, MetaData* headers) {
+ int64 date_ms;
+ if (headers->ParseDateHeader(HttpAttributes::kDate, &date_ms) &&
+ (date_ms < now_ms)) {
+ int64 delta_ms = now_ms - date_ms;
+ headers->UpdateDateHeader(HttpAttributes::kDate, now_ms);
+ ApplyTimeDelta(HttpAttributes::kExpires, delta_ms, headers);
+ ApplyTimeDelta(HttpAttributes::kLastModified, delta_ms, headers);
+ }
+}
+
+} // namespace
+
+const char HttpDumpUrlFetcher::kGzipContentLengthAttribute[] =
+ "X-Instaweb-Gzip-Content-Length";
+
+HttpDumpUrlFetcher::HttpDumpUrlFetcher(const StringPiece& root_dir,
+ FileSystem* file_system,
+ Timer* timer)
+ : root_dir_(root_dir.data(), root_dir.size()),
+ file_system_(file_system),
+ timer_(timer),
+ error_body_(kErrorHtml) {
+ EnsureEndsInSlash(&root_dir_);
+}
+
+HttpDumpUrlFetcher::~HttpDumpUrlFetcher() {
+}
+
+bool HttpDumpUrlFetcher::GetFilenameFromUrl(const StringPiece& root_dir,
+ const GURL& gurl,
+ std::string* filename,
+ MessageHandler* handler) {
+ bool ret = false;
+ if (!EndsInSlash(root_dir)) {
+ handler->Message(kError,
+ "GetFilenameFromUrl: root_dir must end in slash, was %s",
+ root_dir.as_string().c_str());
+ } else if (!gurl.is_valid()) {
+ handler->Message(kError, "GetFilenameFromUrl: gurl is invalid");
+ } else {
+ ret = true;
+
+ // Seperate the url into domain and path. Note: we ignore scheme, username,
+ // password, port and ref (stuff after '#').
+ // TODO(sligocki): Perhaps we should include these (except ref).
+ std::string domain = gurl.host();
+ std::string path = gurl.path();
+
+ // Add other bits of url used by latency lab.
+ if (!gurl.query().empty()) { // Part after '?' in url.
+ path.append(1, '?');
+ path.append(gurl.query());
+ }
+
+ FilenameEncoder encoder;
+ const std::string prefix = StrCat(root_dir, domain);
+ encoder.Encode(prefix, path, filename); // Writes encoded filename.
+ }
+ return ret;
+}
+
+bool HttpDumpUrlFetcher::GetFilenamePrefixFromUrl(const StringPiece& root_dir,
+ const GURL& url,
+ std::string* filename,
+ MessageHandler* handler) {
+ handler->Check(EndsInSlash(StringPiece(url.spec())),
+ "Prefix url must end in '/', was %s", url.spec().c_str());
+ bool ret = GetFilenameFromUrl(root_dir, url, filename, handler);
+ if (ret) {
+ size_t last_slash = filename->find_last_of('/');
+ CHECK(last_slash != std::string::npos);
+ filename->resize(last_slash + 1);
+ }
+ return ret;
+}
+
+void HttpDumpUrlFetcher::RespondError(MetaData* response_headers,
+ Writer* response_writer,
+ MessageHandler* handler) {
+ response_headers->SetStatusAndReason(HttpStatus::kNotFound);
+ response_headers->Add(HttpAttributes::kContentType, "text/html");
+ response_headers->ComputeCaching();
+ response_headers->set_headers_complete(true);
+ response_writer->Write(error_body_, handler);
+}
+
+// Passes Http contents through to another writer, optionally
+// gunzipping if want_gzip is set (and content is gzipped).
+class HttpResponseWriter : public Writer {
+ public:
+ HttpResponseWriter(const StringPiece& url, bool want_gzip, Writer* writer,
+ MetaData* response)
+ : url_(url.data(), url.size()),
+ content_length_(0),
+ gzip_content_length_(0),
+ want_gzip_(want_gzip),
+ first_write_(true),
+ writer_(writer),
+ response_(response) {
+ }
+
+ virtual bool Write(const StringPiece& str, MessageHandler* handler) {
+ bool ret = true;
+
+ // We don't store the request headers with the slurped file. So if
+ // we slurp with a gzipped encoding, but the requester wants to see
+ // cleartext, then we will convert inline in the Writer. Determine
+ // that the first time Write() is called.
+ if (first_write_) {
+ first_write_ = false;
+ CHECK(response_->headers_complete());
+ CharStarVector v;
+ if (!want_gzip_ && response_->IsGzipped()) {
+ inflater_.reset(new GzipInflater(GzipInflater::kGzip));
+ CHECK(inflater_->Init());
+ response_->RemoveAll(HttpAttributes::kContentEncoding);
+ }
+ }
+ if (inflater_.get() != NULL) {
+ CHECK(!inflater_->HasUnconsumedInput());
+ CHECK(inflater_->SetInput(str.data(), str.size()));
+ gzip_content_length_ += str.size();
+ while (inflater_->HasUnconsumedInput() && ret) {
+ char buf[kStackBufferSize];
+ int bytes = inflater_->InflateBytes(buf, sizeof(buf));
+ if (bytes == 0) {
+ handler->Error(url_.c_str(), 0,
+ "zlib reported unconsumed data but yielded 0 bytes");
+ ret = false;
+ } else {
+ if (inflater_->error()) {
+ handler->Error(url_.c_str(), 0, "zlib inflate error");
+ ret = false;
+ } else {
+ ret = writer_->Write(StringPiece(buf, bytes), handler);
+ content_length_ += bytes;
+ }
+ }
+ }
+ } else {
+ ret = writer_->Write(str, handler);
+ content_length_ += str.size();
+ }
+ return ret;
+ }
+
+ bool Flush(MessageHandler* handler) {
+ return writer_->Flush(handler);
+ }
+
+ int content_length() const { return content_length_; }
+ int gzip_content_length() const { return gzip_content_length_; }
+
+ private:
+ std::string url_;
+ int content_length_;
+ int gzip_content_length_;
+ bool want_gzip_;
+ bool first_write_;
+ Writer* writer_;
+ MetaData* response_;
+ scoped_ptr<GzipInflater> inflater_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpResponseWriter);
+};
+
+bool HttpDumpUrlFetcher::StreamingFetchUrl(const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* response_writer,
+ MessageHandler* handler) {
+ bool ret = false;
+ std::string filename;
+ GURL gurl(url);
+ if (gurl.is_valid() && gurl.IsStandard() &&
+ GetFilenameFromUrl(root_dir_, gurl, &filename, handler)) {
+ NullMessageHandler null_handler;
+ // Pass in NullMessageHandler so that we don't get errors for file not found
+ FileSystem::InputFile* file =
+ file_system_->OpenInputFile(filename.c_str(), &null_handler);
+ if (file != NULL) {
+ CharStarVector v;
+ // TODO(jmarantz): handle 'deflate'.
+ bool want_gzip = request_headers.AcceptsGzip();
+ HttpResponseWriter writer(url, want_gzip, response_writer,
+ response_headers);
+ HttpResponseParser response(response_headers, &writer, handler);
+ if (response.ParseFile(file)) {
+ handler->Message(kInfo, "HttpDumpUrlFetcher: Fetched %s as %s",
+ url.c_str(), filename.c_str());
+ if (!response_headers->headers_complete()) {
+ // Fill in some default headers and body. Note that if we have
+ // a file, then we will return true, even if the file is corrupt.
+ RespondError(response_headers, response_writer, handler);
+ } else {
+ // Update 'date' and 'Expires' headers, if found.
+ //
+ // TODO(jmarantz): make this conditional based on a flag.
+ int64 now_ms = timer_->NowMs();
+ CorrectDateHeaders(now_ms, response_headers);
+ response_headers->RemoveAll(HttpAttributes::kContentLength);
+ response_headers->Add(HttpAttributes::kContentLength, IntegerToString(
+ writer.content_length()).c_str());
+ }
+ if (writer.gzip_content_length() != 0) {
+ response_headers->Add(kGzipContentLengthAttribute, IntegerToString(
+ writer.gzip_content_length()).c_str());
+ }
+ response_headers->ComputeCaching();
+ ret = true;
+ } else {
+ handler->Message(kWarning,
+ "HttpDumpUrlFetcher: Failed to parse %s for %s",
+ filename.c_str(), url.c_str());
+ }
+ file_system_->Close(file, handler);
+ } else {
+ handler->Message(kInfo,
+ "HttpDumpUrlFetcher: Failed to find file %s for %s",
+ filename.c_str(), url.c_str());
+ }
+ } else {
+ handler->Message(kError,
+ "HttpDumpUrlFetcher: Requested invalid URL %s",
+ url.c_str());
+ }
+
+ if ((urls_.get() != NULL) && urls_->insert(url).second) {
+ fprintf(stdout, "url: %s\n", url.c_str());
+ }
+
+ return ret;
+}
+
+void HttpDumpUrlFetcher::set_print_urls(bool on) {
+ if (on) {
+ urls_.reset(new StringSet);
+ } else {
+ urls_.reset(NULL);
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/http_dump_url_fetcher_test.cc b/trunk/src/net/instaweb/util/http_dump_url_fetcher_test.cc
new file mode 100644
index 0000000..1b44f02
--- /dev/null
+++ b/trunk/src/net/instaweb/util/http_dump_url_fetcher_test.cc
@@ -0,0 +1,98 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+// Unit-test the http dump fetcher, using a mock fetcher. Note that
+// the HTTP Dump Fetcher is, in essence, a caching fetcher except that:
+// 1. It ignores caching headers completely
+// 2. It uses file-based storage with no expectation of ever evicting
+// anything.
+//
+// TODO(jmarantz): consider making this class a special case of the
+// combination of HTTPCache, FileCache, and HttpDumpUrlFetcher.
+
+#include "net/instaweb/util/public/http_dump_url_fetcher.h"
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/google_message_handler.h"
+#include "net/instaweb/util/public/gtest.h"
+#include "net/instaweb/util/public/mock_timer.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include "net/instaweb/util/public/stdio_file_system.h"
+#include <string>
+#include "net/instaweb/util/public/string_writer.h"
+
+namespace net_instaweb {
+
+class HttpDumpUrlFetcherTest : public testing::Test {
+ public:
+ HttpDumpUrlFetcherTest()
+ : content_writer_(&content_),
+ mock_timer_(0),
+ http_dump_fetcher_(
+ GTestSrcDir() + "/net/instaweb/util/testdata",
+ &file_system_,
+ &mock_timer_) {
+ }
+
+ protected:
+ StdioFileSystem file_system_;
+ std::string content_;
+ StringWriter content_writer_;
+ MockTimer mock_timer_;
+ HttpDumpUrlFetcher http_dump_fetcher_;
+ GoogleMessageHandler message_handler_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HttpDumpUrlFetcherTest);
+};
+
+TEST_F(HttpDumpUrlFetcherTest, TestReadWithGzip) {
+ SimpleMetaData request, response;
+ request.Add(HttpAttributes::kAcceptEncoding, HttpAttributes::kGzip);
+ ASSERT_TRUE(http_dump_fetcher_.StreamingFetchUrl(
+ "http://www.google.com", request, &response, &content_writer_,
+ &message_handler_));
+ CharStarVector v;
+ ASSERT_TRUE(response.Lookup(HttpAttributes::kContentEncoding, &v));
+ ASSERT_EQ(1, v.size());
+ CHECK_EQ(std::string(HttpAttributes::kGzip), v[0]);
+ CHECK_EQ(5513, content_.size());
+ v.clear();
+ ASSERT_TRUE(response.Lookup(HttpAttributes::kContentLength, &v));
+ ASSERT_EQ(1, v.size());
+ CHECK_EQ(std::string("5513"), v[0]);
+}
+
+TEST_F(HttpDumpUrlFetcherTest, TestReadUncompressedFromGzippedDump) {
+ SimpleMetaData request, response;
+ ASSERT_TRUE(http_dump_fetcher_.StreamingFetchUrl(
+ "http://www.google.com", request, &response, &content_writer_,
+ &message_handler_));
+ CharStarVector v;
+ if (response.Lookup(HttpAttributes::kContentEncoding, &v)) {
+ ASSERT_EQ(1, v.size());
+ CHECK_NE(std::string(HttpAttributes::kGzip), v[0]);
+ }
+ CHECK_EQ(14450, content_.size());
+ v.clear();
+ ASSERT_TRUE(response.Lookup(HttpAttributes::kContentLength, &v));
+ ASSERT_EQ(1, v.size());
+ CHECK_EQ(std::string("14450"), v[0]);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/http_dump_url_writer.cc b/trunk/src/net/instaweb/util/http_dump_url_writer.cc
new file mode 100644
index 0000000..5fffaee
--- /dev/null
+++ b/trunk/src/net/instaweb/util/http_dump_url_writer.cc
@@ -0,0 +1,134 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/http_dump_url_writer.h"
+#include "net/instaweb/util/public/file_writer.h"
+#include "net/instaweb/util/public/google_url.h"
+#include "net/instaweb/util/public/gzip_inflater.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/meta_data.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include "net/instaweb/util/public/string_writer.h"
+#include "net/instaweb/util/stack_buffer.h"
+
+namespace net_instaweb {
+
+HttpDumpUrlWriter::~HttpDumpUrlWriter() {
+}
+
+bool HttpDumpUrlWriter::StreamingFetchUrl(const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* response_writer,
+ MessageHandler* handler) {
+ bool ret = true;
+ std::string filename;
+
+ if (!dump_fetcher_.GetFilename(GURL(url), &filename, handler)) {
+ handler->Message(kError, "Invalid url: %s", url.c_str());
+ ret = false;
+ } else if (!file_system_->Exists(filename.c_str(), handler).is_true()) {
+ // Do the Fetch first, before opening the output file, so that if the
+ // fetch fails, do not make an empty file.
+ //
+ // TODO(jmarantz): Re-integrate the use of SplitWriter. We'll have
+ // to do a lazy-open of the OutputFile* in a custom writer, though, to
+ // avoid opening up a zero-size file when the URL fetch fails.
+ std::string contents;
+ StringWriter string_writer(&contents);
+ // TODO(sligocki): Have this actually stream to response_writer.
+
+ // In general we will want to always ask the origin for gzipped output,
+ // but we are leaving in variable so this could be overridden by the
+ // instantiator of the DumpUrlWriter.
+ SimpleMetaData compress_headers, compressed_response;
+ compress_headers.CopyFrom(request_headers);
+ if (accept_gzip_) {
+ compress_headers.RemoveAll(HttpAttributes::kAcceptEncoding);
+ compress_headers.Add(HttpAttributes::kAcceptEncoding,
+ HttpAttributes::kGzip);
+ }
+
+ ret = base_fetcher_->StreamingFetchUrl(url, compress_headers,
+ &compressed_response, &string_writer,
+ handler);
+ compressed_response.RemoveAll(HttpAttributes::kContentLength);
+ compressed_response.Add(HttpAttributes::kContentLength,
+ IntegerToString(contents.size()).c_str());
+ compressed_response.ComputeCaching();
+
+ // Do not write an empty file if the fetch failed.
+ if (ret) {
+ // Check to see if a response marked as gzipped are really unzippable.
+ if (compressed_response.IsGzipped()) {
+ GzipInflater inflater(GzipInflater::kGzip);
+ inflater.Init();
+ if (contents.data() == NULL || contents.size() == 0) {
+ // CHECK below would fail on these.
+ compressed_response.RemoveAll(HttpAttributes::kContentEncoding);
+ } else {
+ CHECK(inflater.SetInput(contents.data(), contents.size()));
+ while (inflater.HasUnconsumedInput()) {
+ char buf[kStackBufferSize];
+ if ((inflater.InflateBytes(buf, sizeof(buf)) == 0) ||
+ inflater.error()) {
+ compressed_response.RemoveAll(HttpAttributes::kContentEncoding);
+ break;
+ }
+ }
+ }
+ }
+
+ FileSystem::OutputFile* file = file_system_->OpenTempFile(
+ filename + ".temp", handler);
+ if (file != NULL) {
+ handler->Message(kInfo, "Storing %s as %s", url.c_str(),
+ filename.c_str());
+ std::string temp_filename = file->filename();
+ FileWriter file_writer(file);
+ ret = compressed_response.Write(&file_writer, handler) &&
+ file->Write(contents, handler);
+ ret &= file_system_->Close(file, handler);
+ ret &= file_system_->RenameFile(temp_filename.c_str(), filename.c_str(),
+ handler);
+ } else {
+ ret = false;
+ }
+ }
+
+ // We are not going to be able to read the response from the file
+ // system so we better pass the error message through.
+ if (!ret) {
+ response_headers->CopyFrom(compressed_response);
+ if (!response_headers->headers_complete()) {
+ response_headers->SetStatusAndReason(HttpStatus::kNotFound);
+ response_headers->ComputeCaching();
+ response_headers->set_headers_complete(true);
+ }
+ response_writer->Write(contents, handler);
+ }
+ }
+
+ // Always use the HttpDumpUrlFetcher, even if we are reading the file
+ // ourselves. Thus the problem of inflating gzipped requests for requesters
+ // that want cleartext only is solved only in that file.
+ return ret && dump_fetcher_.StreamingFetchUrl(
+ url, request_headers, response_headers, response_writer, handler);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/http_dump_url_writer_test.cc b/trunk/src/net/instaweb/util/http_dump_url_writer_test.cc
new file mode 100644
index 0000000..954aaf3
--- /dev/null
+++ b/trunk/src/net/instaweb/util/http_dump_url_writer_test.cc
@@ -0,0 +1,88 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+// Unit-test the http dump fetcher, using a mock fetcher. Note that
+// the HTTP Dump Fetcher is, in essence, a caching fetcher except that:
+// 1. It ignores caching headers completely
+// 2. It uses file-based storage with no expectation of ever evicting
+// anything.
+
+#include "net/instaweb/util/public/http_dump_url_writer.h"
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/fetcher_test.h"
+#include "net/instaweb/util/public/google_url.h"
+#include "net/instaweb/util/public/mock_timer.h"
+#include "net/instaweb/util/public/stdio_file_system.h"
+
+namespace net_instaweb {
+
+class HttpDumpUrlWriterTest : public FetcherTest {
+ protected:
+ HttpDumpUrlWriterTest()
+ : mock_timer_(0),
+ http_dump_writer_(GTestTempDir() + "/http_dump/", &mock_fetcher_,
+ &file_system_, &mock_timer_) {
+ }
+
+ virtual UrlFetcher* sync_fetcher() { return &http_dump_writer_; }
+
+ virtual void SetUp() {
+ RemoveFileIfPresent(kGoodUrl);
+ RemoveFileIfPresent(kNotCachedUrl);
+ RemoveFileIfPresent(kBadUrl);
+ }
+
+ void RemoveFileIfPresent(const char* url) {
+ GURL gurl(url);
+ std::string path;
+ HttpDumpUrlFetcher::GetFilenameFromUrl(GTestTempDir() + "/http_dump/",
+ gurl, &path, &message_handler_);
+ file_system_.RemoveFile(path.c_str(), &message_handler_);
+ }
+
+ StdioFileSystem file_system_;
+ MockTimer mock_timer_;
+ HttpDumpUrlWriter http_dump_writer_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HttpDumpUrlWriterTest);
+};
+
+TEST_F(HttpDumpUrlWriterTest, TestCachableWithSyncFetcher) {
+ EXPECT_EQ(1, CountFetchesSync(kGoodUrl, true, true));
+ EXPECT_EQ(0, CountFetchesSync(kGoodUrl, true, true));
+}
+
+TEST_F(HttpDumpUrlWriterTest, TestNonCachableWithSyncFetcher) {
+ // When a HttpDumpUrlFetcher is implemented using a sync fetcher,
+ // then non-cacheable URLs will result in sync-fetch successes.
+ // As we are ignoring caching headers, we don't expect the
+ // underlying fetcher to be called here either.
+ EXPECT_EQ(1, CountFetchesSync(kNotCachedUrl, true, true));
+ EXPECT_EQ(0, CountFetchesSync(kNotCachedUrl, true, true));
+}
+
+TEST_F(HttpDumpUrlWriterTest, TestCacheWithSyncFetcherFail) {
+ EXPECT_EQ(1, CountFetchesSync(kBadUrl, false, true));
+ // For now, we don't cache failure, so we expect a new fetch
+ // on each request
+ EXPECT_EQ(1, CountFetchesSync(kBadUrl, false, true));
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/http_response_parser.cc b/trunk/src/net/instaweb/util/http_response_parser.cc
new file mode 100644
index 0000000..9c61230
--- /dev/null
+++ b/trunk/src/net/instaweb/util/http_response_parser.cc
@@ -0,0 +1,63 @@
+/**
+ * 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/util/public/http_response_parser.h"
+
+#include <stdio.h>
+#include "net/instaweb/util/public/file_system.h"
+#include "net/instaweb/util/public/meta_data.h"
+#include "net/instaweb/util/public/writer.h"
+#include "net/instaweb/util/stack_buffer.h"
+
+namespace net_instaweb {
+
+bool HttpResponseParser::ParseFile(FileSystem::InputFile* file) {
+ char buf[kStackBufferSize];
+ int nread;
+ while (ok_ && ((nread = file->Read(buf, sizeof(buf), handler_)) > 0)) {
+ ParseChunk(StringPiece(buf, nread));
+ }
+ return ok_;
+}
+
+bool HttpResponseParser::Parse(FILE* stream) {
+ char buf[kStackBufferSize];
+ int nread;
+ while (ok_ && ((nread = fread(buf, 1, sizeof(buf), stream)) > 0)) {
+ ParseChunk(StringPiece(buf, nread));
+ }
+ return ok_;
+}
+
+bool HttpResponseParser::ParseChunk(const StringPiece& data) {
+ if (reading_headers_) {
+ int consumed = response_headers_->ParseChunk(data, handler_);
+ if (response_headers_->headers_complete()) {
+ // In this chunk we may have picked up some of the body.
+ // Before we move to the next buffer, send it to the output
+ // stream.
+ ok_ = writer_->Write(data.substr(consumed), handler_);
+ reading_headers_ = false;
+ }
+ } else {
+ ok_ = writer_->Write(data, handler_);
+ }
+ return ok_;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/http_value.cc b/trunk/src/net/instaweb/util/http_value.cc
new file mode 100644
index 0000000..99bcb4b
--- /dev/null
+++ b/trunk/src/net/instaweb/util/http_value.cc
@@ -0,0 +1,202 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/http_value.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+
+namespace {
+
+// The headers and body are both encoded into one Shared String, which can then
+// be efficiently held in an in-memory cache, or passed around as an HTTPValue
+// object. The class supports both setting the headers first and then the body,
+// and vice versa. Both the headers and body are variable length, and to avoid
+// having to re-shuffle memory, we encode which is first in the buffer as the
+// first byte. The next four bytes encode the size.
+const char kHeadersFirst = 'h';
+const char kBodyFirst = 'b';
+
+const int kStorageTypeOverhead = 1;
+const int kStorageSizeOverhead = 4;
+const unsigned int kStorageOverhead =
+ kStorageTypeOverhead + kStorageSizeOverhead;
+
+} // namespace
+
+namespace net_instaweb {
+
+void HTTPValue::CopyOnWrite() {
+ if (!storage_.unique()) {
+ SharedString new_storage(*storage_);
+ storage_ = new_storage;
+ }
+}
+
+void HTTPValue::Clear() {
+ CopyOnWrite();
+ storage_->clear();
+}
+
+void HTTPValue::SetHeaders(const MetaData& headers) {
+ CopyOnWrite();
+ std::string headers_string = headers.ToString();
+ if (storage_->empty()) {
+ storage_->append(&kHeadersFirst, 1);
+ SetSizeOfFirstChunk(headers_string.size());
+ } else {
+ CHECK(type_identifier() == kBodyFirst);
+ // Using 'unsigned int' to facilitate bit-shifting in
+ // SizeOfFirstChunk and SetSizeOfFirstChunk, and I don't
+ // want to worry about sign extension.
+ unsigned int size = SizeOfFirstChunk();
+ CHECK(storage_->size() == (kStorageOverhead + size));
+ }
+ storage_->append(headers_string);
+}
+
+bool HTTPValue::Write(const StringPiece& str, MessageHandler* handler) {
+ CopyOnWrite();
+ if (storage_->empty()) {
+ storage_->append(&kBodyFirst, 1);
+ storage_->append(" ", 4);
+ SetSizeOfFirstChunk(str.size());
+ } else if (type_identifier() == kBodyFirst) {
+ CHECK(storage_->size() >= kStorageOverhead);
+ unsigned int string_size = SizeOfFirstChunk();
+ CHECK(string_size == storage_->size() - kStorageOverhead);
+ SetSizeOfFirstChunk(str.size() + string_size);
+ } else {
+ CHECK(type_identifier() == kHeadersFirst);
+ }
+ storage_->append(str.data(), str.size());
+ return true;
+}
+
+bool HTTPValue::Flush(MessageHandler* handler) {
+ return true;
+}
+
+// Encode the size of the first chunk, which is either the headers or body,
+// depending on the order they are called. Rather than trying to assume any
+// particular alignment for casting between char* and int*, we just manually
+// encode one byte at a time.
+void HTTPValue::SetSizeOfFirstChunk(unsigned int size) {
+ CHECK(!storage_->empty()) << "type encoding should already be in first byte";
+ unsigned char size_buffer[4];
+ size_buffer[0] = size & 0xff;
+ size_buffer[1] = (size >> 8) & 0xff;
+ size_buffer[2] = (size >> 16) & 0xff;
+ size_buffer[3] = (size >> 24) & 0xff;
+ if (storage_->size() < kStorageOverhead) {
+ // Ensure the buffer is exactly 5 bytes so we can overwrite
+ // bytes 1-4 (the type code in byte 0).
+ storage_->append(" ", kStorageOverhead - storage_->size());
+ }
+ memcpy(&((*storage_)[1]), &size_buffer, sizeof(size_buffer));
+}
+
+// Decodes the size of the first chunk, which is either the headers or body,
+// depending on the order they are called. Rather than trying to assume any
+// particular alignment for casting between char* and int*, we just manually
+// decode one byte at a time.
+unsigned int HTTPValue::SizeOfFirstChunk() const {
+ CHECK(storage_->size() >= kStorageOverhead);
+ const unsigned char *size_buffer =
+ reinterpret_cast<const unsigned char*>(storage_->data() + 1);
+ unsigned int size = size_buffer[0];
+ size |= size_buffer[1] << 8;
+ size |= size_buffer[2] << 16;
+ size |= size_buffer[3] << 24;
+ return size;
+}
+
+// Note that we avoid CHECK, and instead return false on error. So if
+// our cache gets corrupted (say) on disk, we just consider it an
+// invalid entry rather than aborting the server.
+bool HTTPValue::ExtractHeaders(MetaData* headers, MessageHandler* handler)
+ const {
+ bool ret = false;
+ headers->Clear();
+ if (storage_->size() >= kStorageOverhead) {
+ char type_id = type_identifier();
+ const char* start = storage_->data() + kStorageOverhead;
+ unsigned int size = SizeOfFirstChunk();
+ if (size <= storage_->size() - kStorageOverhead) {
+ if (type_id == kBodyFirst) {
+ start += size;
+ size = storage_->size() - size - kStorageOverhead;
+ } else {
+ ret = (type_id == kHeadersFirst);
+ }
+ unsigned int num_consumed =
+ headers->ParseChunk(StringPiece(start, size), handler);
+ ret = (num_consumed == size);
+ ret &= (headers->headers_complete());
+ }
+ }
+ return ret;
+}
+
+// Note that we avoid CHECK, and instead return false on error. So if
+// our cache gets corrupted (say) on disk, we just consider it an
+// invalid entry rather than aborting the server.
+bool HTTPValue::ExtractContents(StringPiece* val) const {
+ bool ret = false;
+ if (storage_->size() >= kStorageOverhead) {
+ char type_id = type_identifier();
+ const char* start = storage_->data() + kStorageOverhead;
+ unsigned int size = SizeOfFirstChunk();
+ if (size <= storage_->size() - kStorageOverhead) {
+ if (type_id == kHeadersFirst) {
+ start += size;
+ size = storage_->size() - size - kStorageOverhead;
+ ret = true;
+ } else {
+ ret = (type_id == kBodyFirst);
+ }
+ *val = StringPiece(start, size);
+ }
+ }
+ return ret;
+}
+
+bool HTTPValue::Link(SharedString* src, MetaData* headers,
+ MessageHandler* handler) {
+ bool ok = false;
+ if ((*src)->size() >= kStorageOverhead) {
+ // The simplest way to ensure that src is well formed is to save the
+ // existing storage_ in a temp, assign the storage, and make sure
+ // Headers and Contents return true. The drawback is that the headers
+ // parsing is arguably a little heavyweight. We could consider encoding
+ // the headers in an easier-to-extract form, so we don't have to give up
+ // the integrity checks.
+ SharedString temp(storage_);
+ storage_ = *src;
+
+ // TODO(jmarantz): this could be a lot lighter weight, but we are going
+ // to be sure at this point that both the headers and the contents are
+ // valid. It would be nice to have an HTML headers parser that didn't
+ // actually create new temp copies of all the names/values.
+ ok = ExtractHeaders(headers, handler);
+ if (!ok) {
+ storage_ = temp;
+ }
+ }
+ return ok;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/http_value_test.cc b/trunk/src/net/instaweb/util/http_value_test.cc
new file mode 100644
index 0000000..e7620f5
--- /dev/null
+++ b/trunk/src/net/instaweb/util/http_value_test.cc
@@ -0,0 +1,191 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+// Unit-test the lru cache
+
+#include "net/instaweb/util/public/http_value.h"
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "net/instaweb/util/public/google_message_handler.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/gtest.h"
+#include "net/instaweb/util/public/shared_string.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+
+namespace {
+const int kMaxSize = 100;
+}
+
+namespace net_instaweb {
+
+class HTTPValueTest : public testing::Test {
+ protected:
+ HTTPValueTest() { }
+
+ void FillMetaData(MetaData* meta_data) {
+ meta_data->SetStatusAndReason(HttpStatus::kOK);
+ meta_data->set_major_version(1);
+ meta_data->set_minor_version(0);
+ meta_data->set_reason_phrase("OK");
+ meta_data->Add("Cache-control", "max-age=300");
+ }
+
+ void CheckMetaData(const MetaData& meta_data) {
+ SimpleMetaData expected;
+ FillMetaData(&expected);
+ EXPECT_EQ(expected.ToString(), meta_data.ToString());
+ }
+
+ GoogleMessageHandler message_handler_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HTTPValueTest);
+};
+
+TEST_F(HTTPValueTest, Empty) {
+ HTTPValue value;
+ EXPECT_TRUE(value.Empty());
+}
+
+TEST_F(HTTPValueTest, HeadersFirst) {
+ HTTPValue value;
+ SimpleMetaData headers, check_headers;
+ FillMetaData(&headers);
+ value.SetHeaders(headers);
+ value.Write("body", &message_handler_);
+ StringPiece body;
+ ASSERT_TRUE(value.ExtractContents(&body));
+ EXPECT_EQ("body", body.as_string());
+ ASSERT_TRUE(value.ExtractHeaders(&check_headers, &message_handler_));
+ CheckMetaData(check_headers);
+}
+
+TEST_F(HTTPValueTest, ContentsFirst) {
+ HTTPValue value;
+ SimpleMetaData headers, check_headers;
+ FillMetaData(&headers);
+ value.Write("body", &message_handler_);
+ value.SetHeaders(headers);
+ StringPiece body;
+ ASSERT_TRUE(value.ExtractContents(&body));
+ EXPECT_EQ("body", body.as_string());
+ ASSERT_TRUE(value.ExtractHeaders(&check_headers, &message_handler_));
+ CheckMetaData(check_headers);
+}
+
+TEST_F(HTTPValueTest, EmptyContentsFirst) {
+ HTTPValue value;
+ SimpleMetaData headers, check_headers;
+ FillMetaData(&headers);
+ value.Write("", &message_handler_);
+ value.SetHeaders(headers);
+ StringPiece body;
+ ASSERT_TRUE(value.ExtractContents(&body));
+ EXPECT_EQ("", body.as_string());
+ ASSERT_TRUE(value.ExtractHeaders(&check_headers, &message_handler_));
+ CheckMetaData(check_headers);
+}
+
+TEST_F(HTTPValueTest, TestCopyOnWrite) {
+ HTTPValue v1;
+ v1.Write("Hello", &message_handler_);
+ StringPiece v1_contents, v2_contents, v3_contents;
+ ASSERT_TRUE(v1.ExtractContents(&v1_contents));
+ EXPECT_TRUE(v1.unique());
+
+ // Now check that copy-construction shares the buffer.
+ HTTPValue v2(v1);
+ EXPECT_FALSE(v1.unique());
+ EXPECT_FALSE(v2.unique());
+ ASSERT_TRUE(v2.ExtractContents(&v2_contents));
+ EXPECT_EQ(v1_contents, v2_contents);
+ EXPECT_EQ(v1_contents.data(), v2_contents.data()); // buffer sharing
+
+ // Also the assignment operator should induce sharing.
+ HTTPValue v3 = v1;
+ EXPECT_FALSE(v3.unique());
+ ASSERT_TRUE(v3.ExtractContents(&v3_contents));
+ EXPECT_EQ(v1_contents, v3_contents);
+ EXPECT_EQ(v1_contents.data(), v3_contents.data()); // buffer sharing
+
+ // Now write something into v1. Due to copy-on-write semantics, v2 and
+ // will v3 not see it.
+ v1.Write(", World!", &message_handler_);
+ ASSERT_TRUE(v1.ExtractContents(&v1_contents));
+ ASSERT_TRUE(v2.ExtractContents(&v2_contents));
+ ASSERT_TRUE(v3.ExtractContents(&v3_contents));
+ EXPECT_EQ("Hello, World!", v1_contents);
+ EXPECT_NE(v1_contents, v2_contents);
+ EXPECT_NE(v1_contents.data(), v2_contents.data()); // no buffer sharing
+ EXPECT_NE(v1_contents, v3_contents);
+ EXPECT_NE(v1_contents.data(), v3_contents.data()); // no buffer sharing
+
+ // But v2 and v3 will remain connected to one another
+ EXPECT_EQ(v2_contents, v3_contents);
+ EXPECT_EQ(v2_contents.data(), v3_contents.data()); // buffer sharing
+}
+
+TEST_F(HTTPValueTest, TestShare) {
+ SharedString storage;
+
+ {
+ HTTPValue value;
+ SimpleMetaData headers, check_headers;
+ FillMetaData(&headers);
+ value.SetHeaders(headers);
+ value.Write("body", &message_handler_);
+ storage = *value.share();
+ }
+
+ {
+ HTTPValue value;
+ SimpleMetaData check_headers;
+ ASSERT_TRUE(value.Link(&storage, &check_headers, &message_handler_));
+ StringPiece body;
+ ASSERT_TRUE(value.ExtractContents(&body));
+ EXPECT_EQ("body", body.as_string());
+ CheckMetaData(check_headers);
+ }
+}
+
+TEST_F(HTTPValueTest, LinkEmpty) {
+ SharedString storage;
+ HTTPValue value;
+ SimpleMetaData headers;
+ ASSERT_FALSE(value.Link(&storage, &headers, &message_handler_));
+}
+
+TEST_F(HTTPValueTest, LinkCorrupt) {
+ SharedString storage("h");
+ HTTPValue value;
+ SimpleMetaData headers;
+ ASSERT_FALSE(value.Link(&storage, &headers, &message_handler_));
+ storage->append("9999");
+ ASSERT_FALSE(value.Link(&storage, &headers, &message_handler_));
+ storage->append("xyz");
+ ASSERT_FALSE(value.Link(&storage, &headers, &message_handler_));
+ *storage = "b";
+ ASSERT_FALSE(value.Link(&storage, &headers, &message_handler_));
+ storage->append("9999");
+ ASSERT_FALSE(value.Link(&storage, &headers, &message_handler_));
+ storage->append("xyz");
+ ASSERT_FALSE(value.Link(&storage, &headers, &message_handler_));
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/lru_cache.cc b/trunk/src/net/instaweb/util/lru_cache.cc
new file mode 100644
index 0000000..5b6d156
--- /dev/null
+++ b/trunk/src/net/instaweb/util/lru_cache.cc
@@ -0,0 +1,208 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/lru_cache.h"
+
+#include "base/logging.h"
+#include "net/instaweb/util/public/shared_string.h"
+
+namespace net_instaweb {
+
+LRUCache::~LRUCache() {
+ Clear();
+}
+
+// Freshen a key-value pair by putting it in the front of the
+// LRU list. Returns a ListNode to insert into the map so we
+// can do fast lookup.
+LRUCache::ListNode LRUCache::Freshen(KeyValuePair* key_value) {
+ lru_ordered_list_.push_front(key_value);
+ return lru_ordered_list_.begin();
+}
+
+bool LRUCache::Get(const std::string& key, SharedString* value) {
+ Map::iterator p = map_.find(key);
+ bool ret = false;
+ if (p != map_.end()) {
+ ret = true;
+ ListNode cell = p->second;
+ KeyValuePair* key_value = *cell;
+ lru_ordered_list_.erase(cell);
+ p->second = Freshen(key_value);
+ *value = key_value->second;
+ ++num_hits_;
+ } else {
+ ++num_misses_;
+ }
+ return ret;
+}
+
+void LRUCache::Put(const std::string& key, SharedString* new_value) {
+ // Just do one map operation, calling the awkward 'insert' which returns
+ // a pair. The bool indicates whether a new value was inserted, and the
+ // iterator provides access to the element, whether it's new or old.
+ //
+ // If the key is already in the map, this will give us access to the value
+ // cell, and the uninitialized cell will not be used.
+ ListNode cell;
+ std::pair<Map::iterator, bool> iter_found =
+ map_.insert(Map::value_type(key, cell));
+ bool found = !iter_found.second;
+ Map::iterator map_iter = iter_found.first;
+ bool need_to_insert = true;
+ if (found) {
+ cell = map_iter->second;
+ KeyValuePair* key_value = *cell;
+
+ // Protect the element that we are rewriting by erasing
+ // it from the entry_list prior to calling EvictIfNecessary,
+ // which can't find it if it isn't in the list.
+ lru_ordered_list_.erase(cell);
+ if (**new_value == *(key_value->second)) {
+ map_iter->second = Freshen(key_value);
+ need_to_insert = false;
+ // TODO(jmarantz): count number of re-inserts of existing value?
+ } else {
+ ++num_deletes_;
+ current_bytes_in_cache_ -= entry_size(key_value);
+ delete key_value;
+ }
+ }
+
+ if (need_to_insert) {
+ // At this point, if we were doing a replacement, then the value
+ // is removed from the list, so we can treat replacements and new
+ // insertions the same way. In both cases, the new key is in the map
+ // as a result of the call to map_.insert above.
+
+ if (EvictIfNecessary(key.size() + (*new_value)->size())) {
+ // The new value fits. Put it in the LRU-list.
+ KeyValuePair* kvp = new KeyValuePair(&map_iter->first, *new_value);
+ map_iter->second = Freshen(kvp);
+ ++num_inserts_;
+ } else {
+ // The new value was too big to fit. Remove it from the map.
+ // it's already removed from the list. We have failed. We
+ // could potentially log this somewhere or keep a stat.
+ map_.erase(map_iter);
+ }
+ }
+}
+
+// Evicts enough items from the cache to allow an object of the
+// specified bytes-size to be inserted. If successful, we assumes that
+// the item will be inserted and current_bytes_in_cache_ is adjusted
+// accordingly.
+bool LRUCache::EvictIfNecessary(size_t bytes_needed) {
+ bool ret = false;
+ if (bytes_needed < max_bytes_in_cache_) {
+ while (bytes_needed + current_bytes_in_cache_ > max_bytes_in_cache_) {
+ KeyValuePair* key_value = lru_ordered_list_.back();
+ lru_ordered_list_.pop_back();
+ current_bytes_in_cache_ -= entry_size(key_value);
+ map_.erase(*key_value->first);
+ CHECK(current_bytes_in_cache_ >= 0);
+ delete key_value;
+ ++num_evictions_;
+ }
+ current_bytes_in_cache_ += bytes_needed;
+ ret = true;
+ }
+ return ret;
+}
+
+void LRUCache::Delete(const std::string& key) {
+ Map::iterator p = map_.find(key);
+ if (p != map_.end()) {
+ ListNode cell = p->second;
+ KeyValuePair* key_value = *cell;
+ lru_ordered_list_.erase(cell);
+ current_bytes_in_cache_ -= entry_size(key_value);
+ map_.erase(p);
+ delete key_value;
+ ++num_deletes_;
+ } else {
+ // TODO(jmarantz): count number of misses on a 'delete' request?
+ }
+}
+
+void LRUCache::SanityCheck() {
+ CHECK(map_.size() == lru_ordered_list_.size());
+ size_t count = 0;
+ size_t bytes_used = 0;
+
+ // Walk forward through the list, making sure the map and list elements
+ // point to each other correctly.
+ for (ListNode cell = lru_ordered_list_.begin(), e = lru_ordered_list_.end();
+ cell != e; ++cell, ++count) {
+ KeyValuePair* key_value = *cell;
+ Map::iterator map_iter = map_.find(*key_value->first);
+ CHECK(map_iter != map_.end());
+ CHECK(&map_iter->first == key_value->first);
+ CHECK(map_iter->second == cell);
+ bytes_used += entry_size(key_value);
+ }
+ CHECK(count == map_.size());
+ CHECK(current_bytes_in_cache_ == bytes_used);
+ CHECK(current_bytes_in_cache_ <= max_bytes_in_cache_);
+
+ // Walk backward through the list, making sure it's coherent as well.
+ count = 0;
+ for (EntryList::reverse_iterator cell = lru_ordered_list_.rbegin(),
+ e = lru_ordered_list_.rend(); cell != e; ++cell, ++count) {
+ }
+ CHECK(count == map_.size());
+}
+
+CacheInterface::KeyState LRUCache::Query(const std::string& key) {
+ Map::iterator p = map_.find(key);
+ KeyState state = kNotFound;
+ if (p != map_.end()) {
+ state = kAvailable;
+ }
+ return state;
+}
+
+// TODO(jmarantz): consider accounting for overhead for list cells, map
+// cells, string objects, etc. Currently we are only accounting for the
+// actual characters in the key and value.
+int LRUCache::entry_size(KeyValuePair* kvp) const {
+ return kvp->first->size() + kvp->second->size();
+}
+
+void LRUCache::Clear() {
+ current_bytes_in_cache_ = 0;
+
+ for (ListNode p = lru_ordered_list_.begin(), e = lru_ordered_list_.end();
+ p != e; ++p) {
+ KeyValuePair* key_value = *p;
+ delete key_value;
+ }
+ lru_ordered_list_.clear();
+ map_.clear();
+}
+
+void LRUCache::ClearStats() {
+ num_evictions_ = 0;
+ num_hits_ = 0;
+ num_misses_ = 0;
+ num_inserts_ = 0;
+ num_deletes_ = 0;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/lru_cache_test.cc b/trunk/src/net/instaweb/util/lru_cache_test.cc
new file mode 100644
index 0000000..2076bbf
--- /dev/null
+++ b/trunk/src/net/instaweb/util/lru_cache_test.cc
@@ -0,0 +1,159 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+// Unit-test the lru cache
+
+#include "net/instaweb/util/public/lru_cache.h"
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/gtest.h"
+#include "net/instaweb/util/public/shared_string.h"
+
+namespace {
+const size_t kMaxSize = 100;
+}
+
+namespace net_instaweb {
+
+class LRUCacheTest : public testing::Test {
+ protected:
+ LRUCacheTest()
+ : cache_(kMaxSize) {
+ }
+
+ void CheckGet(const char* key, const std::string& expected_value) {
+ SharedString value_buffer;
+ ASSERT_TRUE(cache_.Get(key, &value_buffer));
+ EXPECT_EQ(expected_value, *value_buffer);
+ EXPECT_EQ(CacheInterface::kAvailable, cache_.Query(key));
+ cache_.SanityCheck();
+ }
+
+ void CheckPut(const char* key, const char* value) {
+ SharedString put_buffer(value);
+ cache_.Put(key, &put_buffer);
+ cache_.SanityCheck();
+ }
+
+ void CheckNotFound(const char* key) {
+ SharedString value_buffer;
+ ASSERT_FALSE(cache_.Get(key, &value_buffer));
+ EXPECT_EQ(CacheInterface::kNotFound, cache_.Query(key));
+ cache_.SanityCheck();
+ }
+
+ LRUCache cache_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LRUCacheTest);
+};
+
+// Simple flow of putting in an item, getting it, deleting it.
+TEST_F(LRUCacheTest, PutGetDelete) {
+ EXPECT_EQ(static_cast<size_t>(0), cache_.size_bytes());
+ EXPECT_EQ(static_cast<size_t>(0), cache_.num_elements());
+ CheckPut("Name", "Value");
+ CheckGet("Name", "Value");
+ EXPECT_EQ(static_cast<size_t>(9), cache_.size_bytes()); // "Name" + "Value"
+ EXPECT_EQ(static_cast<size_t>(1), cache_.num_elements());
+ CheckNotFound("Another Name");
+
+ CheckPut("Name", "NewValue");
+ CheckGet("Name", "NewValue");
+ EXPECT_EQ(static_cast<size_t>(12),
+ cache_.size_bytes()); // "Name" + "NewValue"
+ EXPECT_EQ(static_cast<size_t>(1), cache_.num_elements());
+
+ cache_.Delete("Name");
+ cache_.SanityCheck();
+ SharedString value_buffer;
+ EXPECT_FALSE(cache_.Get("Name", &value_buffer));
+ EXPECT_EQ(static_cast<size_t>(0), cache_.size_bytes());
+ EXPECT_EQ(static_cast<size_t>(0), cache_.num_elements());
+}
+
+// Test eviction. We happen to know that the cache does not account for
+// STL overhead -- it's just counting key/value size. Exploit that to
+// understand when objects fall off the end.
+TEST_F(LRUCacheTest, LeastRecentlyUsed) {
+ // Fill the cache.
+ std::string keys[10], values[10];
+ const char key_pattern[] = "name%d";
+ const char value_pattern[] = "valu%d";
+ const int key_plus_value_size = 10; // strlen("name7") + strlen("valu7")
+ const size_t num_elements = kMaxSize / key_plus_value_size;
+ for (int i = 0; i < 10; ++i) {
+ SStringPrintf(&keys[i], key_pattern, i);
+ SStringPrintf(&values[i], value_pattern, i);
+ CheckPut(keys[i].c_str(), values[i].c_str());
+ }
+ EXPECT_EQ(kMaxSize, cache_.size_bytes());
+ EXPECT_EQ(num_elements, cache_.num_elements());
+
+ // Ensure we can see those.
+ for (int i = 0; i < 10; ++i) {
+ CheckGet(keys[i].c_str(), values[i].c_str());
+ }
+
+ // Now if we insert a new entry totaling 10 bytes, that should work,
+ // but we will lose name0 due to LRU semantics. We should still have name1,
+ // and by Get-ing name1 it we will make it the MRU.
+ CheckPut("nameA", "valuA");
+ CheckGet("nameA", "valuA");
+ CheckNotFound("name0");
+ CheckGet("name1", "valu1");
+
+ // So now when we put in nameB,valuB we will lose name2 but keep name1,
+ // which got bumped up to the MRU when we checked it above.
+ CheckPut("nameB", "valuB");
+ CheckGet("nameB", "valuB");
+ CheckGet("name1", "valu1");
+ CheckNotFound("name2");
+
+ // Now insert something 1 byte too big, spelling out "value" this time.
+ // We will now lose name3 and name4. We should still have name5-name9,
+ // plus name1, nameA, and nameB.
+ CheckPut("nameC", "valueC");
+ CheckNotFound("name3");
+ CheckNotFound("name4");
+ CheckGet("nameA", "valuA");
+ CheckGet("nameB", "valuB");
+ CheckGet("nameC", "valueC");
+ CheckGet("name1", "valu1");
+ for (int i = 5; i < 10; ++i) {
+ CheckGet(keys[i].c_str(), values[i].c_str());
+ }
+
+ // Now the oldest item is "nameA". Freshen it by re-inserting it, tickling
+ // the code-path in lru_cache.cc that special-cases handling of re-inserting
+ // the same value.
+ CheckPut("nameA", "valuA");
+ CheckPut("nameD", "valuD");
+ // nameB should be evicted, the others should be retained.
+ CheckNotFound("nameB");
+ CheckGet("nameA", "valuA");
+ CheckGet("nameC", "valueC");
+ CheckGet("name1", "valu1");
+ for (int i = 5; i < 10; ++i) {
+ CheckGet(keys[i].c_str(), values[i].c_str());
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/md5_hasher.cc b/trunk/src/net/instaweb/util/md5_hasher.cc
new file mode 100644
index 0000000..1309cf7
--- /dev/null
+++ b/trunk/src/net/instaweb/util/md5_hasher.cc
@@ -0,0 +1,55 @@
+// 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.
+//
+// Authors: sligocki@google.com (Shawn Ligocki),
+// lsong@google.com (Libo Song)
+
+#include "net/instaweb/util/public/md5_hasher.h"
+
+#include "net/instaweb/util/public/base64_util.h"
+
+#include "base/md5.h"
+
+namespace net_instaweb {
+
+namespace {
+
+const int kMD5NumBytes = sizeof(MD5Digest);
+
+} // namespace
+
+// Hash size is size after Base64 encoding, which expands by 4/3. We round down,
+// this should not matter unless someone really wants that extra few bits.
+const int MD5Hasher::kMaxHashSize = kMD5NumBytes * 4 / 3;
+
+MD5Hasher::~MD5Hasher() {
+}
+
+std::string MD5Hasher::Hash(const StringPiece& content) const {
+ // Note: It may seem more efficient to initialize the MD5Context
+ // in a constructor so it can be re-used. But a quick inspection
+ // of src/third_party/chromium/src/base/md5.cc indicates that
+ // the cost of MD5Init is very tiny compared to the cost of
+ // MD5Update, so it's better to stay thread-safe.
+ MD5Digest digest;
+ MD5Sum(content.data(), content.size(), &digest);
+ // Note: digest.a is an unsigned char[16] so it's not null-terminated.
+ StringPiece raw_hash(reinterpret_cast<char*>(digest.a), sizeof(digest.a));
+ std::string out;
+ Web64Encode(raw_hash, &out);
+ out.resize(hash_size_);
+ return out;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/md5_hasher_test.cc b/trunk/src/net/instaweb/util/md5_hasher_test.cc
new file mode 100644
index 0000000..7144ab3
--- /dev/null
+++ b/trunk/src/net/instaweb/util/md5_hasher_test.cc
@@ -0,0 +1,50 @@
+// 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.
+//
+// Authors: sligocki@google.com (Shawn Ligocki),
+
+#include "net/instaweb/util/public/md5_hasher.h"
+
+#include "net/instaweb/util/public/gtest.h"
+
+namespace net_instaweb {
+
+namespace {
+
+// See http://goto/gunitprimer for an introduction to gUnit.
+
+class MD5HasherTest : public ::testing::Test {};
+
+TEST_F(MD5HasherTest, CorrectHashSize) {
+ for (int i = MD5Hasher::kMaxHashSize; i >= 0; --i) {
+ MD5Hasher hasher(i);
+ EXPECT_EQ(i, hasher.HashSizeInChars());
+ EXPECT_EQ(i, hasher.Hash("foobar").size());
+ // Large string.
+ EXPECT_EQ(i, hasher.Hash(std::string(5000, 'z')).size());
+ }
+}
+
+TEST_F(MD5HasherTest, HashesDiffer) {
+ MD5Hasher hasher;
+
+ // Basic sanity tests. More thorough tests belong in the base implementation.
+ EXPECT_NE(hasher.Hash("foo"), hasher.Hash("bar"));
+ EXPECT_NE(hasher.Hash(std::string(5000, 'z')),
+ hasher.Hash(std::string(5001, 'z')));
+}
+
+} // namespace
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/mem_file_system.cc b/trunk/src/net/instaweb/util/mem_file_system.cc
new file mode 100644
index 0000000..a000849
--- /dev/null
+++ b/trunk/src/net/instaweb/util/mem_file_system.cc
@@ -0,0 +1,278 @@
+/**
+ * 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: abliss@google.com (Adam Bliss)
+
+#include "net/instaweb/util/public/mem_file_system.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/errno.h>
+#include <sys/stat.h>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/mock_timer.h"
+#include <string>
+#include "net/instaweb/util/public/timer.h"
+
+namespace net_instaweb {
+
+class MemInputFile : public FileSystem::InputFile {
+ public:
+ MemInputFile(const StringPiece& filename, const std::string& contents)
+ : contents_(contents),
+ filename_(filename.data(), filename.size()),
+ offset_(0) {
+ }
+
+ virtual bool Close(MessageHandler* message_handler) {
+ offset_ = contents_.length();
+ return true;
+ }
+
+ virtual const char* filename() { return filename_.c_str(); }
+
+ virtual int Read(char* buf, int size, MessageHandler* message_handler) {
+ if (size + offset_ > static_cast<int>(contents_.length())) {
+ size = contents_.length() - offset_;
+ }
+ memcpy(buf, contents_.c_str() + offset_, size);
+ offset_ += size;
+ return size;
+ }
+
+ private:
+ const std::string contents_;
+ const std::string filename_;
+ int offset_;
+
+ DISALLOW_COPY_AND_ASSIGN(MemInputFile);
+};
+
+
+class MemOutputFile : public FileSystem::OutputFile {
+ public:
+ MemOutputFile(const StringPiece& filename, std::string* contents)
+ : contents_(contents), filename_(filename.data(), filename.size()) {
+ contents_->clear();
+ }
+
+ virtual bool Close(MessageHandler* message_handler) {
+ Flush(message_handler);
+ return true;
+ }
+
+ virtual const char* filename() { return filename_.c_str(); }
+
+ virtual bool Flush(MessageHandler* message_handler) {
+ contents_->append(written_);
+ written_.clear();
+ return true;
+ }
+
+ virtual bool SetWorldReadable(MessageHandler* message_handler) {
+ return true;
+ }
+
+ virtual bool Write(const StringPiece& buf, MessageHandler* handler) {
+ buf.AppendToString(&written_);
+ return true;
+ }
+
+ private:
+ std::string* contents_;
+ const std::string filename_;
+ std::string written_;
+
+ DISALLOW_COPY_AND_ASSIGN(MemOutputFile);
+};
+
+MemFileSystem::~MemFileSystem() {
+}
+
+int64 MemFileSystem::CurrentTimeAndAdvance() {
+ int64 now_us = timer_.NowUs();
+ int64 now_s = now_us / Timer::kSecondUs;
+ timer_.advance_us(Timer::kSecondUs);
+ return now_s;
+}
+
+void MemFileSystem::Clear() {
+ string_map_.clear();
+}
+
+BoolOrError MemFileSystem::Exists(const char* path, MessageHandler* handler) {
+ StringMap::const_iterator iter = string_map_.find(path);
+ return BoolOrError(iter != string_map_.end());
+}
+
+BoolOrError MemFileSystem::IsDir(const char* path, MessageHandler* handler) {
+ return Exists(path, handler).is_true()
+ ? BoolOrError(EndsInSlash(path)) : BoolOrError();
+}
+
+bool MemFileSystem::MakeDir(const char* path, MessageHandler* handler) {
+ // We store directories as empty files with trailing slashes.
+ std::string path_string = path;
+ EnsureEndsInSlash(&path_string);
+ string_map_[path_string] = "";
+ atime_map_[path_string] = CurrentTimeAndAdvance();
+ return true;
+}
+
+FileSystem::InputFile* MemFileSystem::OpenInputFile(
+ const char* filename, MessageHandler* message_handler) {
+ if (!enabled_) {
+ return NULL;
+ }
+
+ StringMap::const_iterator iter = string_map_.find(filename);
+ if (iter == string_map_.end()) {
+ message_handler->Error(filename, 0, "opening input file: %s",
+ "file not found");
+ return NULL;
+ } else {
+ atime_map_[filename] = CurrentTimeAndAdvance();
+ return new MemInputFile(filename, iter->second);
+ }
+}
+
+FileSystem::OutputFile* MemFileSystem::OpenOutputFileHelper(
+ const char* filename, MessageHandler* message_handler) {
+ atime_map_[filename] = CurrentTimeAndAdvance();
+ return new MemOutputFile(filename, &(string_map_[filename]));
+}
+
+FileSystem::OutputFile* MemFileSystem::OpenTempFileHelper(
+ const StringPiece& prefix, MessageHandler* message_handler) {
+ std::string filename = StringPrintf("tmpfile%d", temp_file_index_++);
+ atime_map_[filename] = CurrentTimeAndAdvance();
+ return new MemOutputFile(filename, &string_map_[filename]);
+}
+
+bool MemFileSystem::RecursivelyMakeDir(const StringPiece& full_path_const,
+ MessageHandler* handler) {
+ // This is called to make sure that files can be written under the
+ // named directory. We don't have directories and files can be
+ // written anywhere, so just return true.
+ return true;
+}
+
+bool MemFileSystem::RemoveFile(const char* filename,
+ MessageHandler* handler) {
+ atime_map_.erase(filename);
+ return (string_map_.erase(filename) == 1);
+}
+
+bool MemFileSystem::RenameFileHelper(const char* old_file,
+ const char* new_file,
+ MessageHandler* handler) {
+ atime_map_[new_file] = CurrentTimeAndAdvance();
+ if (strcmp(old_file, new_file) == 0) {
+ handler->Error(old_file, 0, "Cannot move a file to itself");
+ return false;
+ }
+
+ StringMap::iterator iter = string_map_.find(old_file);
+ if (iter == string_map_.end()) {
+ handler->Error(old_file, 0, "File not found");
+ return false;
+ }
+
+ string_map_[new_file] = iter->second;
+ string_map_.erase(iter);
+ return true;
+}
+
+bool MemFileSystem::ListContents(const StringPiece& dir, StringVector* files,
+ MessageHandler* handler) {
+ std::string prefix = dir.as_string();
+ EnsureEndsInSlash(&prefix);
+ const size_t prefix_length = prefix.size();
+ // We don't have directories, so we just list everything in the
+ // filesystem that matches the prefix and doesn't have another
+ // internal slash.
+ for (StringMap::iterator it = string_map_.begin(), end = string_map_.end();
+ it != end;
+ it++) {
+ const std::string& path = (*it).first;
+ if ((0 == path.compare(0, prefix_length, prefix)) &&
+ path.length() > prefix_length) {
+ const size_t next_slash = path.find("/", prefix_length + 1);
+ // Only want to list files without another slash, unless that
+ // slash is the last char in the filename.
+ if ((next_slash == std::string::npos)
+ || (next_slash == path.length() - 1)) {
+ files->push_back(path);
+ }
+ }
+ }
+ return true;
+}
+
+bool MemFileSystem::Atime(const StringPiece& path, int64* timestamp_sec,
+ MessageHandler* handler) {
+ *timestamp_sec = atime_map_[path.as_string()];
+ return true;
+}
+
+bool MemFileSystem::Size(const StringPiece& path, int64* size,
+ MessageHandler* handler) {
+ const std::string path_string = path.as_string();
+ const char* path_str = path_string.c_str();
+ if (Exists(path_str, handler).is_true()) {
+ *size = string_map_[path_string].size();
+ return true;
+ } else {
+ return false;
+ }
+}
+
+BoolOrError MemFileSystem::TryLock(const StringPiece& lock_name,
+ MessageHandler* handler) {
+ // Not actually threadsafe! This is just for tests.
+ if (lock_map_.count(lock_name.as_string()) != 0) {
+ return BoolOrError(false);
+ } else {
+ lock_map_[lock_name.as_string()] = timer_.NowMs();
+ return BoolOrError(true);
+ }
+}
+
+BoolOrError MemFileSystem::TryLockWithTimeout(const StringPiece& lock_name,
+ int64 timeout_ms,
+ MessageHandler* handler) {
+ // As above, not actually threadsafe (and quick-and-dirty rather than
+ // efficient; efficiency requires map::find).
+ std::string name = lock_name.as_string();
+ int64 now = timer_.NowMs();
+ if (lock_map_.count(name) != 0 &&
+ now <= lock_map_[name] + timeout_ms) {
+ return BoolOrError(false);
+ } else {
+ lock_map_[name] = timer_.NowMs();
+ return BoolOrError(true);
+ }
+}
+
+bool MemFileSystem::Unlock(const StringPiece& lock_name,
+ MessageHandler* handler) {
+ return (lock_map_.erase(lock_name.as_string()) == 1);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/mem_file_system_test.cc b/trunk/src/net/instaweb/util/mem_file_system_test.cc
new file mode 100644
index 0000000..256d46f
--- /dev/null
+++ b/trunk/src/net/instaweb/util/mem_file_system_test.cc
@@ -0,0 +1,149 @@
+/**
+ * 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: abliss@google.com (Adam Bliss)
+
+// Unit-test the in-memory filesystem
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/util/public/file_system_test.h"
+#include "net/instaweb/util/public/gtest.h"
+#include "net/instaweb/util/public/mem_file_system.h"
+#include "net/instaweb/util/public/null_message_handler.h"
+#include <string>
+
+namespace net_instaweb {
+
+class MemFileSystemTest : public FileSystemTest {
+ protected:
+ MemFileSystemTest() {}
+ virtual void DeleteRecursively(const StringPiece& filename) {
+ mem_file_system_.Clear();
+ }
+ virtual FileSystem* file_system() {
+ return &mem_file_system_;
+ }
+ virtual std::string test_tmpdir() {
+ return GTestTempDir();
+ }
+ private:
+ MemFileSystem mem_file_system_;
+
+ DISALLOW_COPY_AND_ASSIGN(MemFileSystemTest);
+};
+
+// Write a named file, then read it.
+TEST_F(MemFileSystemTest, TestWriteRead) {
+ TestWriteRead();
+}
+
+// Write a temp file, then read it.
+TEST_F(MemFileSystemTest, TestTemp) {
+ TestTemp();
+}
+
+// Write a temp file, rename it, then read it.
+TEST_F(MemFileSystemTest, TestRename) {
+ TestRename();
+}
+
+// Write a file and successfully delete it.
+TEST_F(MemFileSystemTest, TestRemove) {
+ TestRemove();
+}
+
+// Write a file and check that it exists.
+TEST_F(MemFileSystemTest, TestExists) {
+ TestExists();
+}
+
+// Create a file along with its directory which does not exist.
+TEST_F(MemFileSystemTest, TestCreateFileInDir) {
+ TestCreateFileInDir();
+}
+
+
+// Make a directory and check that files may be placed in it.
+TEST_F(MemFileSystemTest, TestMakeDir) {
+ TestMakeDir();
+}
+
+TEST_F(MemFileSystemTest, TestSize) {
+ // Since we don't have directories, we need to do a slightly
+ // different size test.
+ std::string filename1 = "file-in-dir.txt";
+ std::string filename2 = "another-file-in-dir.txt";
+ std::string content1 = "12345";
+ std::string content2 = "1234567890";
+ ASSERT_TRUE(file_system()->WriteFile(filename1.c_str(),
+ content1, &handler_));
+ ASSERT_TRUE(file_system()->WriteFile(filename2.c_str(),
+ content2, &handler_));
+ int64 size;
+
+ EXPECT_TRUE(file_system()->Size(filename1, &size, &handler_));
+ EXPECT_EQ(5, size);
+ EXPECT_TRUE(file_system()->Size(filename2, &size, &handler_));
+ EXPECT_EQ(10, size);
+}
+
+TEST_F(MemFileSystemTest, TestListContents) {
+ TestListContents();
+}
+
+TEST_F(MemFileSystemTest, TestAtime) {
+ // Slightly modified version of TestAtime, without the sleeps
+ std::string dir_name = test_tmpdir() + "/make_dir";
+ DeleteRecursively(dir_name);
+ std::string filename1 = "file-in-dir.txt";
+ std::string filename2 = "another-file-in-dir.txt";
+ std::string full_path1 = dir_name + "/" + filename1;
+ std::string full_path2 = dir_name + "/" + filename2;
+ std::string content = "Lorem ipsum dolor sit amet";
+
+ ASSERT_TRUE(file_system()->MakeDir(dir_name.c_str(), &handler_));
+ ASSERT_TRUE(file_system()->WriteFile(full_path1.c_str(),
+ content, &handler_));
+ ASSERT_TRUE(file_system()->WriteFile(full_path2.c_str(),
+ content, &handler_));
+
+ int64 atime1, atime2;
+ CheckRead(full_path1, content);
+ CheckRead(full_path2, content);
+ ASSERT_TRUE(file_system()->Atime(full_path1, &atime1, &handler_));
+ ASSERT_TRUE(file_system()->Atime(full_path2, &atime2, &handler_));
+ EXPECT_LT(atime1, atime2);
+
+ CheckRead(full_path2, content);
+ CheckRead(full_path1, content);
+ ASSERT_TRUE(file_system()->Atime(full_path1, &atime1, &handler_));
+ ASSERT_TRUE(file_system()->Atime(full_path2, &atime2, &handler_));
+ EXPECT_LT(atime2, atime1);
+}
+
+TEST_F(MemFileSystemTest, TestLock) {
+ TestLock();
+}
+
+// Since this filesystem doesn't support directories, we skip these tests:
+// TestIsDir
+// TestRecursivelyMakeDir
+// TestRecursivelyMakeDir_NoPermission
+// TestRecursivelyMakeDir_FileInPath
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/message_handler.cc b/trunk/src/net/instaweb/util/message_handler.cc
new file mode 100644
index 0000000..1882f04
--- /dev/null
+++ b/trunk/src/net/instaweb/util/message_handler.cc
@@ -0,0 +1,125 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/message_handler.h"
+
+#include "base/logging.h"
+
+namespace net_instaweb {
+
+MessageHandler::MessageHandler() : min_message_type_(kInfo) {
+}
+
+MessageHandler::~MessageHandler() {
+}
+
+const char* MessageHandler::MessageTypeToString(const MessageType type) const {
+ const char* type_string = NULL;
+
+ // Don't include a 'default:' clause so that the compiler can tell us when
+ // we are missing an enum value. Intead use a null check for 'type_string' to
+ // indicate a data corruption that avoids hitting any of the cases.
+ switch (type) {
+ case kInfo:
+ type_string = "Info";
+ break;
+ case kWarning:
+ type_string = "Warning";
+ break;
+ case kError:
+ type_string = "Error";
+ break;
+ case kFatal:
+ type_string = "Fatal";
+ break;
+ }
+ CHECK(type_string != NULL) << "INVALID MessageType!";
+ return type_string;
+}
+
+void MessageHandler::Message(MessageType type, const char* msg, ...) {
+ va_list args;
+ va_start(args, msg);
+ MessageV(type, msg, args);
+ va_end(args);
+}
+
+void MessageHandler::MessageV(MessageType type, const char* msg, va_list args) {
+ if (type >= min_message_type_) {
+ MessageVImpl(type, msg, args);
+ }
+}
+
+void MessageHandler::FileMessage(MessageType type, const char* file, int line,
+ const char* msg, ...) {
+ va_list args;
+ va_start(args, msg);
+ FileMessageV(type, file, line, msg, args);
+ va_end(args);
+}
+
+void MessageHandler::FileMessageV(MessageType type, const char* filename,
+ int line, const char* msg, va_list args) {
+ if (type >= min_message_type_) {
+ FileMessageVImpl(type, filename, line, msg, args);
+ }
+}
+
+void MessageHandler::Check(bool condition, const char* msg, ...) {
+ va_list args;
+ va_start(args, msg);
+ CheckV(condition, msg, args);
+ va_end(args);
+}
+
+void MessageHandler::CheckV(bool condition, const char* msg, va_list args) {
+ if (!condition) {
+ MessageV(kFatal, msg, args);
+ }
+}
+
+void MessageHandler::Info(const char* file, int line, const char* msg, ...) {
+ va_list args;
+ va_start(args, msg);
+ InfoV(file, line, msg, args);
+ va_end(args);
+}
+
+void MessageHandler::Warning(const char* file, int line, const char* msg, ...) {
+ va_list args;
+ va_start(args, msg);
+ WarningV(file, line, msg, args);
+ va_end(args);
+}
+
+void MessageHandler::Error(const char* file, int line, const char* msg, ...) {
+ va_list args;
+ va_start(args, msg);
+ ErrorV(file, line, msg, args);
+ va_end(args);
+}
+
+void MessageHandler::FatalError(
+ const char* file, int line, const char* msg, ...) {
+ va_list args;
+ va_start(args, msg);
+ FatalErrorV(file, line, msg, args);
+ va_end(args);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/message_handler_test.cc b/trunk/src/net/instaweb/util/message_handler_test.cc
new file mode 100644
index 0000000..160114f
--- /dev/null
+++ b/trunk/src/net/instaweb/util/message_handler_test.cc
@@ -0,0 +1,85 @@
+/**
+ * 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: bmcquade@google.com (Bryan McQuade)
+
+#include "net/instaweb/util/public/gtest.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+namespace {
+
+class TestMessageHandler : public net_instaweb::MessageHandler {
+ public:
+ typedef std::vector<std::string> MessageVector;
+
+ const MessageVector& messages() { return messages_; }
+
+ protected:
+ virtual void MessageVImpl(MessageType type, const char* msg,
+ va_list args) {
+ std::string message;
+ StringAppendF(&message, "%s: ", MessageTypeToString(type));
+ StringAppendV(&message, msg, args);
+ messages_.push_back(message);
+ }
+
+ virtual void FileMessageVImpl(MessageType type, const char* filename,
+ int line, const char* msg, va_list args) {
+ std::string message;
+ StringAppendF(&message, "%s: %s: %d: ", MessageTypeToString(type),
+ filename, line);
+ StringAppendV(&message, msg, args);
+ messages_.push_back(message);
+ }
+
+ private:
+ MessageVector messages_;
+};
+
+class MessageHandlerTest : public testing::Test {
+ protected:
+ const TestMessageHandler::MessageVector& messages() {
+ return handler_.messages();
+ }
+
+ TestMessageHandler handler_;
+};
+
+
+TEST_F(MessageHandlerTest, Simple) {
+ handler_.Message(kWarning, "here is a message");
+ handler_.Info("filename.cc", 1, "here is another message");
+ ASSERT_EQ(2U, messages().size());
+ ASSERT_EQ(messages()[0], "Warning: here is a message");
+ ASSERT_EQ(messages()[1], "Info: filename.cc: 1: here is another message");
+}
+
+TEST_F(MessageHandlerTest, MinMessageType) {
+ handler_.set_min_message_type(kError);
+ handler_.Info("filename.cc", 1, "here is a message");
+ handler_.Warning("filename.cc", 1, "here is a message");
+ ASSERT_EQ(0U, messages().size());
+ handler_.Error("filename.cc", 1, "here is another message");
+ ASSERT_EQ(1U, messages().size());
+ ASSERT_EQ(messages()[0], "Error: filename.cc: 1: here is another message");
+}
+
+} // namespace
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/meta_data.cc b/trunk/src/net/instaweb/util/meta_data.cc
new file mode 100644
index 0000000..92ae961
--- /dev/null
+++ b/trunk/src/net/instaweb/util/meta_data.cc
@@ -0,0 +1,177 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/meta_data.h"
+
+#include <stdio.h>
+#include "net/instaweb/util/public/time_util.h"
+#include "pagespeed/core/resource_util.h"
+
+namespace net_instaweb {
+
+const char HttpAttributes::kAcceptEncoding[] = "Accept-Encoding";
+const char HttpAttributes::kCacheControl[] = "Cache-Control";
+const char HttpAttributes::kContentEncoding[] = "Content-Encoding";
+const char HttpAttributes::kContentLength[] = "Content-Length";
+const char HttpAttributes::kContentType[] = "Content-Type";
+const char HttpAttributes::kDate[] = "Date";
+const char HttpAttributes::kDeflate[] = "deflate";
+const char HttpAttributes::kEtag[] = "Etag";
+const char HttpAttributes::kExpires[] = "Expires";
+const char HttpAttributes::kGzip[] = "gzip";
+const char HttpAttributes::kHost[] = "Host";
+const char HttpAttributes::kIfModifiedSince[] = "If-Modified-Since";
+const char HttpAttributes::kLastModified[] = "Last-Modified";
+const char HttpAttributes::kLocation[] = "Location";
+const char HttpAttributes::kNoCache[] = "max-age=0, no-cache, no-store";
+const char HttpAttributes::kReferer[] = "Referer"; // sic
+const char HttpAttributes::kServer[] = "Server";
+const char HttpAttributes::kSetCookie[] = "Set-Cookie";
+const char HttpAttributes::kTransferEncoding[] = "Transfer-Encoding";
+const char HttpAttributes::kUserAgent[] = "User-Agent";
+const char HttpAttributes::kVary[] = "Vary";
+
+MetaData::~MetaData() {
+}
+
+void MetaData::CopyFrom(const MetaData& other) {
+ set_major_version(other.major_version());
+ set_minor_version(other.minor_version());
+ set_status_code(other.status_code());
+ set_reason_phrase(other.reason_phrase());
+ set_headers_complete(other.headers_complete());
+ for (int i = 0; i < other.NumAttributes(); ++i) {
+ Add(other.Name(i), other.Value(i));
+ }
+ ComputeCaching();
+}
+
+const char* HttpStatus::GetReasonPhrase(HttpStatus::Code rc) {
+ switch (rc) {
+ case HttpStatus::kContinue : return "Continue";
+ case HttpStatus::kSwitchingProtocols : return "Switching Protocols";
+
+ case HttpStatus::kOK : return "OK";
+ case HttpStatus::kCreated : return "Created";
+ case HttpStatus::kAccepted : return "Accepted";
+ case HttpStatus::kNonAuthoritative :
+ return "Non-Authoritative Information";
+ case HttpStatus::kNoContent : return "No Content";
+ case HttpStatus::kResetContent : return "Reset Content";
+ case HttpStatus::kPartialContent : return "Partial Content";
+
+ // 300 range: redirects
+ case HttpStatus::kMultipleChoices : return "Multiple Choices";
+ case HttpStatus::kMovedPermanently : return "Moved Permanently";
+ case HttpStatus::kFound : return "Found";
+ case HttpStatus::kSeeOther : return "See Other";
+ case HttpStatus::kNotModified : return "Not Modified";
+ case HttpStatus::kUseProxy : return "Use Proxy";
+ case HttpStatus::kTemporaryRedirect : return "OK";
+
+ // 400 range: client errors
+ case HttpStatus::kBadRequest : return "Bad Request";
+ case HttpStatus::kUnauthorized : return "Unauthorized";
+ case HttpStatus::kPaymentRequired : return "Payment Required";
+ case HttpStatus::kForbidden : return "Forbidden";
+ case HttpStatus::kNotFound : return "Not Found";
+ case HttpStatus::kMethodNotAllowed : return "Method Not Allowed";
+ case HttpStatus::kNotAcceptable : return "Not Acceptable";
+ case HttpStatus::kProxyAuthRequired :
+ return "Proxy Authentication Required";
+ case HttpStatus::kRequestTimeout : return "Request Time-out";
+ case HttpStatus::kConflict : return "Conflict";
+ case HttpStatus::kGone : return "Gone";
+ case HttpStatus::kLengthRequired : return "Length Required";
+ case HttpStatus::kPreconditionFailed : return "Precondition Failed";
+ case HttpStatus::kEntityTooLarge :
+ return "Request Entity Too Large";
+ case HttpStatus::kUriTooLong : return "Request-URI Too Large";
+ case HttpStatus::kUnsupportedMediaType : return "Unsupported Media Type";
+ case HttpStatus::kRangeNotSatisfiable :
+ return "Requested range not satisfiable";
+ case HttpStatus::kExpectationFailed : return "Expectation Failed";
+
+ // 500 range: server errors
+ case HttpStatus::kInternalServerError : return "Internal Server Error";
+ case HttpStatus::kNotImplemented : return "Not Implemented";
+ case HttpStatus::kBadGateway : return "Bad Gateway";
+ case HttpStatus::kUnavailable : return "Service Unavailable";
+ case HttpStatus::kGatewayTimeout : return "Gateway Time-out";
+
+ default:
+ // We don't have a name for this response code, so we'll just
+ // take the blame
+ return "Internal Server Error";
+ }
+ return "";
+}
+
+void MetaData::SetStatusAndReason(HttpStatus::Code code) {
+ set_status_code(code);
+ set_reason_phrase(HttpStatus::GetReasonPhrase(code));
+}
+
+bool MetaData::ParseTime(const char* time_str, int64* time_ms) {
+ return pagespeed::resource_util::ParseTimeValuedHeader(time_str, time_ms);
+}
+
+bool MetaData::IsGzipped() const {
+ CHECK(headers_complete());
+ CharStarVector v;
+ return (Lookup(HttpAttributes::kContentEncoding, &v) && (v.size() == 1) &&
+ (strcmp(v[0], HttpAttributes::kGzip) == 0));
+}
+
+bool MetaData::AcceptsGzip() const {
+ CharStarVector v;
+ if (Lookup(HttpAttributes::kAcceptEncoding, &v)) {
+ for (int i = 0, nv = v.size(); i < nv; ++i) {
+ std::vector<StringPiece> encodings;
+ SplitStringPieceToVector(v[i], ",", &encodings, true);
+ for (int j = 0, nencodings = encodings.size(); j < nencodings; ++j) {
+ if (strcasecmp(encodings[j].as_string().c_str(),
+ HttpAttributes::kGzip) == 0) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+bool MetaData::ParseDateHeader(const char* attr, int64* date_ms) const {
+ CharStarVector values;
+ return (Lookup(attr, &values) &&
+ (values.size() == 1) &&
+ ConvertStringToTime(values[0], date_ms));
+}
+
+void MetaData::UpdateDateHeader(const char* attr, int64 date_ms) {
+ RemoveAll(attr);
+ std::string buf;
+ if (ConvertTimeToString(date_ms, &buf)) {
+ Add(attr, buf.c_str());
+ }
+}
+
+void MetaData::DebugPrint() const {
+ fprintf(stderr, "%s\n", ToString().c_str());
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/mock_hasher.cc b/trunk/src/net/instaweb/util/mock_hasher.cc
new file mode 100644
index 0000000..bbd9b97
--- /dev/null
+++ b/trunk/src/net/instaweb/util/mock_hasher.cc
@@ -0,0 +1,26 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/mock_hasher.h"
+
+namespace net_instaweb {
+
+MockHasher::~MockHasher() {
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/mock_message_handler.cc b/trunk/src/net/instaweb/util/mock_message_handler.cc
new file mode 100644
index 0000000..fdca5ef
--- /dev/null
+++ b/trunk/src/net/instaweb/util/mock_message_handler.cc
@@ -0,0 +1,62 @@
+/**
+ * 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: morlovich@google.com (Maksim Orlovich)
+
+#include "net/instaweb/util/public/mock_message_handler.h"
+
+namespace net_instaweb {
+
+MockMessageHandler::~MockMessageHandler() {
+}
+
+void MockMessageHandler::MessageVImpl(MessageType type,
+ const char* msg,
+ va_list args) {
+ GoogleMessageHandler::MessageVImpl(type, msg, args);
+ ++message_counts_[type];
+}
+
+void MockMessageHandler::FileMessageVImpl(MessageType type,
+ const char* filename, int line,
+ const char* msg, va_list args) {
+ GoogleMessageHandler::FileMessageVImpl(type, filename, line, msg, args);
+ ++message_counts_[type];
+}
+
+int MockMessageHandler::MessagesOfType(MessageType type) const {
+ MessageCountMap::const_iterator i = message_counts_.find(type);
+ if (i != message_counts_.end()) {
+ return i->second;
+ } else {
+ return 0;
+ }
+}
+
+int MockMessageHandler::TotalMessages() const {
+ int total = 0;
+ for (MessageCountMap::const_iterator i = message_counts_.begin();
+ i != message_counts_.end(); ++i) {
+ total += i->second;
+ }
+ return total;
+}
+
+int MockMessageHandler::SeriousMessages() const {
+ return TotalMessages() - MessagesOfType(kInfo);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/mock_message_handler_test.cc b/trunk/src/net/instaweb/util/mock_message_handler_test.cc
new file mode 100644
index 0000000..9c36daf
--- /dev/null
+++ b/trunk/src/net/instaweb/util/mock_message_handler_test.cc
@@ -0,0 +1,70 @@
+/**
+ * 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: morlovich@google.com (Maksim Orlovich)
+
+#include "net/instaweb/util/public/gtest.h"
+#include "net/instaweb/util/public/mock_message_handler.h"
+
+namespace net_instaweb {
+
+namespace {
+
+class MockMessageHandlerTest : public testing::Test {
+ protected:
+ void CheckCounts(int expectInfo, int expectWarn, int expectError,
+ int expectFatal) {
+ EXPECT_EQ(expectInfo, handler_.MessagesOfType(kInfo));
+ EXPECT_EQ(expectWarn, handler_.MessagesOfType(kWarning));
+ EXPECT_EQ(expectError, handler_.MessagesOfType(kError));
+ EXPECT_EQ(expectFatal, handler_.MessagesOfType(kFatal));
+ }
+
+ MockMessageHandler handler_;
+};
+
+
+TEST_F(MockMessageHandlerTest, Simple) {
+ EXPECT_EQ(0, handler_.TotalMessages());
+ EXPECT_EQ(0, handler_.SeriousMessages());
+
+ handler_.Message(kInfo, "test info message");
+ EXPECT_EQ(1, handler_.TotalMessages());
+ EXPECT_EQ(0, handler_.SeriousMessages());
+ CheckCounts(1, 0, 0, 0);
+
+ handler_.Message(kWarning, "text warning message");
+ EXPECT_EQ(2, handler_.TotalMessages());
+ EXPECT_EQ(1, handler_.SeriousMessages());
+ CheckCounts(1, 1, 0, 0);
+
+ handler_.Message(kError, "text Error message");
+ EXPECT_EQ(3, handler_.TotalMessages());
+ EXPECT_EQ(2, handler_.SeriousMessages());
+ CheckCounts(1, 1, 1, 0);
+
+ // We can't actually test fatal, as it aborts
+ // TODO(morlovich) mock the fatal behavior so the test does not crash
+
+ handler_.Message(kInfo, "another test info message");
+ EXPECT_EQ(4, handler_.TotalMessages());
+ EXPECT_EQ(2, handler_.SeriousMessages());
+ CheckCounts(2, 1, 1, 0);
+}
+
+} // namespace
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/mock_timer.cc b/trunk/src/net/instaweb/util/mock_timer.cc
new file mode 100644
index 0000000..47a85a8
--- /dev/null
+++ b/trunk/src/net/instaweb/util/mock_timer.cc
@@ -0,0 +1,28 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/mock_timer.h"
+
+namespace net_instaweb {
+
+const int64 MockTimer::kApr_5_2010_ms = 1270493486000LL;
+
+MockTimer::~MockTimer() {
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/mock_url_fetcher.cc b/trunk/src/net/instaweb/util/mock_url_fetcher.cc
new file mode 100644
index 0000000..b3aeed5
--- /dev/null
+++ b/trunk/src/net/instaweb/util/mock_url_fetcher.cc
@@ -0,0 +1,79 @@
+/**
+ * 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/util/public/gtest.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/mock_url_fetcher.h"
+#include "net/instaweb/util/public/stl_util.h"
+#include "net/instaweb/util/public/writer.h"
+
+namespace net_instaweb {
+
+MockUrlFetcher::~MockUrlFetcher() {
+ Clear();
+}
+
+void MockUrlFetcher::SetResponse(const StringPiece& url,
+ const MetaData& response_header,
+ const StringPiece& response_body) {
+ std::string url_string = url.as_string();
+ // Delete any old response.
+ ResponseMap::iterator iter = response_map_.find(url_string);
+ if (iter != response_map_.end()) {
+ delete iter->second;
+ response_map_.erase(iter);
+ }
+
+ // Add new response.
+ HttpResponse* response = new HttpResponse(response_header, response_body);
+ response_map_.insert(ResponseMap::value_type(url_string, response));
+}
+
+void MockUrlFetcher::Clear() {
+ STLDeleteContainerPairSecondPointers(response_map_.begin(),
+ response_map_.end());
+ response_map_.clear();
+}
+
+bool MockUrlFetcher::StreamingFetchUrl(const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* response_writer,
+ MessageHandler* message_handler) {
+ bool ret = false;
+ if (enabled_) {
+ ResponseMap::iterator iter = response_map_.find(url);
+ if (iter != response_map_.end()) {
+ const HttpResponse* response = iter->second;
+ response_headers->CopyFrom(response->header());
+ response_writer->Write(response->body(), message_handler);
+ ret = true;
+ } else {
+ // This is used in tests and we do not expect the test to request a
+ // resource that we don't have. So fail if we do.
+ //
+ // If you want a 404 response, you must explicitly use SetResponse.
+ if (fail_on_unexpected_) {
+ EXPECT_TRUE(false) << "Requested unset url " << url;
+ }
+ }
+ }
+ return ret;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/mock_url_fetcher_test.cc b/trunk/src/net/instaweb/util/mock_url_fetcher_test.cc
new file mode 100644
index 0000000..e8d52db
--- /dev/null
+++ b/trunk/src/net/instaweb/util/mock_url_fetcher_test.cc
@@ -0,0 +1,116 @@
+/**
+ * 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/util/public/mock_url_fetcher.h"
+
+#include "net/instaweb/util/public/google_message_handler.h"
+#include "net/instaweb/util/public/gtest.h"
+#include <string>
+#include "net/instaweb/util/public/string_writer.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+
+namespace net_instaweb {
+
+namespace {
+
+// See http://goto/gunitprimer for an introduction to gUnit.
+
+class MockUrlFetcherTest : public ::testing::Test {
+ protected:
+ MockUrlFetcherTest() {
+ fetcher_.set_fail_on_unexpected(false);
+ }
+
+ void TestResponse(const std::string& url, const MetaData& expected_header,
+ const std::string& expected_body) {
+ const SimpleMetaData dummy_header;
+ SimpleMetaData response_header;
+ std::string response_body;
+ StringWriter response_writer(&response_body);
+ GoogleMessageHandler handler;
+
+ EXPECT_TRUE(fetcher_.StreamingFetchUrl(url, dummy_header, &response_header,
+ &response_writer, &handler));
+ EXPECT_EQ(expected_header.ToString(), response_header.ToString());
+ EXPECT_EQ(expected_body, response_body);
+ }
+
+ void TestFetchFail(const std::string& url) {
+ const SimpleMetaData dummy_header;
+ SimpleMetaData response_header;
+ std::string response_body;
+ StringWriter response_writer(&response_body);
+ GoogleMessageHandler handler;
+
+ EXPECT_FALSE(fetcher_.StreamingFetchUrl(url, dummy_header, &response_header,
+ &response_writer, &handler));
+ }
+
+ MockUrlFetcher fetcher_;
+};
+
+TEST_F(MockUrlFetcherTest, GetsCorrectMappedResponse) {
+ const char url1[] = "http://www.example.com/successs.html";
+ SimpleMetaData header1;
+ header1.set_first_line(1, 1, 200, "OK");
+ const char body1[] = "This website loaded :)";
+
+ const char url2[] = "http://www.example.com/failure.html";
+ SimpleMetaData header2;
+ header2.set_first_line(1, 1, 404, "Not Found");
+ const char body2[] = "File Not Found :(";
+
+ // We can't fetch the URLs before they're set.
+ // Note: this does not crash because we are using NullMessageHandler.
+ TestFetchFail(url1);
+ TestFetchFail(url2);
+
+ // Set the responses.
+ fetcher_.SetResponse(url1, header1, body1);
+ fetcher_.SetResponse(url2, header2, body2);
+
+ // Now we can fetch the correct URLs
+ TestResponse(url1, header1, body1);
+ TestResponse(url2, header2, body2);
+
+ // Check that we can fetch the same URL multiple times.
+ TestResponse(url1, header1, body1);
+
+
+ // Check that fetches fail after disabling the fetcher_.
+ fetcher_.Disable();
+ TestFetchFail(url1);
+ // And then work again when re-enabled.
+ fetcher_.Enable();
+ TestResponse(url1, header1, body1);
+
+
+ // Change the response. Test both editability and memory management.
+ fetcher_.SetResponse(url1, header2, body2);
+
+ // Check that we get the new response.
+ TestResponse(url1, header2, body2);
+
+ // Change it back.
+ fetcher_.SetResponse(url1, header1, body1);
+ TestResponse(url1, header1, body1);
+}
+
+} // namespace
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/named_lock_manager.cc b/trunk/src/net/instaweb/util/named_lock_manager.cc
new file mode 100644
index 0000000..f39dc95
--- /dev/null
+++ b/trunk/src/net/instaweb/util/named_lock_manager.cc
@@ -0,0 +1,27 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#include "net/instaweb/util/public/named_lock_manager.h"
+
+namespace net_instaweb {
+
+AbstractLock::~AbstractLock() { }
+
+NamedLockManager::~NamedLockManager() { }
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/null_message_handler.cc b/trunk/src/net/instaweb/util/null_message_handler.cc
new file mode 100644
index 0000000..9db5a2e
--- /dev/null
+++ b/trunk/src/net/instaweb/util/null_message_handler.cc
@@ -0,0 +1,35 @@
+/**
+ * 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: lsong@google.com (Libo Song)
+
+#include "net/instaweb/util/public/null_message_handler.h"
+
+namespace net_instaweb {
+
+NullMessageHandler::~NullMessageHandler() {
+}
+
+void NullMessageHandler::MessageVImpl(MessageType type, const char* msg,
+ va_list args) {
+}
+
+void NullMessageHandler::FileMessageVImpl(MessageType type, const char* file,
+ int line, const char* msg,
+ va_list args) {
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/null_writer.cc b/trunk/src/net/instaweb/util/null_writer.cc
new file mode 100644
index 0000000..e407ab9
--- /dev/null
+++ b/trunk/src/net/instaweb/util/null_writer.cc
@@ -0,0 +1,34 @@
+/*
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/null_writer.h"
+
+namespace net_instaweb {
+
+NullWriter::~NullWriter() {
+}
+
+bool NullWriter::Write(const StringPiece& str, MessageHandler* handler) {
+ return true;
+}
+
+bool NullWriter::Flush(MessageHandler* handler) {
+ return true;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/pthread_mutex.cc b/trunk/src/net/instaweb/util/pthread_mutex.cc
new file mode 100644
index 0000000..e97d204
--- /dev/null
+++ b/trunk/src/net/instaweb/util/pthread_mutex.cc
@@ -0,0 +1,39 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/pthread_mutex.h"
+
+namespace net_instaweb {
+
+PthreadMutex::PthreadMutex() {
+ pthread_mutex_init(&mutex_, NULL);
+}
+
+PthreadMutex::~PthreadMutex() {
+ pthread_mutex_destroy(&mutex_);
+}
+
+void PthreadMutex::Lock() {
+ pthread_mutex_lock(&mutex_);
+}
+
+void PthreadMutex::Unlock() {
+ pthread_mutex_unlock(&mutex_);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/public/abstract_mutex.h b/trunk/src/net/instaweb/util/public/abstract_mutex.h
new file mode 100644
index 0000000..7086069
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/abstract_mutex.h
@@ -0,0 +1,52 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_ABSTRACT_MUTEX_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_ABSTRACT_MUTEX_H_
+
+#include "base/basictypes.h"
+
+namespace net_instaweb {
+
+// Abstract interface for implementing a mutex.
+class AbstractMutex {
+ public:
+ virtual ~AbstractMutex();
+ virtual void Lock() = 0;
+ virtual void Unlock() = 0;
+};
+
+// Helper class for lexically scoped mutexing.
+class ScopedMutex {
+ public:
+ explicit ScopedMutex(AbstractMutex* mutex) : mutex_(mutex) {
+ mutex_->Lock();
+ }
+
+ ~ScopedMutex() {
+ mutex_->Unlock();
+ }
+ private:
+ AbstractMutex* mutex_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedMutex);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_ABSTRACT_MUTEX_H_
diff --git a/trunk/src/net/instaweb/util/public/atom.h b/trunk/src/net/instaweb/util/public/atom.h
new file mode 100644
index 0000000..8887ee4
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/atom.h
@@ -0,0 +1,89 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_ATOM_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_ATOM_H_
+
+#include <stdlib.h>
+#include <set>
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+struct StringCompareInsensitive;
+struct StringCompareSensitive;
+template<class SymbolCompare> class SymbolTable;
+
+// Atoms are idempotent representations of strings, created
+// via a symbol table.
+class Atom {
+ public:
+ Atom(const Atom& src) : str_(src.str_) {}
+ Atom() : str_("") {}
+ ~Atom() {} // atoms are memory-managed by SymbolTables.
+
+ Atom& operator=(const Atom& src) {
+ if (&src != this) {
+ str_ = src.str_;
+ }
+ return *this;
+ }
+
+ // string-like accessors.
+ const char* c_str() const { return str_; }
+ int size() const { return strlen(str_); }
+
+ // This is comparing the underlying char* pointers. It is invalid
+ // to compare Atoms from different symbol tables.
+ bool operator==(const Atom& sym) const {
+ return str_ == sym.str_;
+ }
+
+ // This is comparing the underlying char* pointers. It is invalid
+ // to compare Atoms from different symbol tables.
+ bool operator!=(const Atom& sym) const {
+ return str_ != sym.str_;
+ }
+
+ // SymbolTable is a friend of Symbol because SymbolTable is the
+ // only class that has the right to construct a new Atom from
+ // a char*.
+ friend class SymbolTable<CharStarCompareInsensitive>;
+ friend class SymbolTable<CharStarCompareSensitive>;
+
+ private:
+ explicit Atom(const char* str) : str_(str) {}
+ const char* str_;
+};
+
+// Once interned, Atoms are very cheap to put in a set, using
+// pointer-comparison.
+struct AtomCompare {
+ bool operator()(const Atom& a1, const Atom& a2) const {
+ return a1.c_str() < a2.c_str(); // compares pointers
+ }
+};
+
+// A set of atoms can be constructed very efficiently. Note that
+// iteration over this set will *not* be in alphabetical order.
+typedef std::set<Atom, AtomCompare> AtomSet;
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_ATOM_H_
diff --git a/trunk/src/net/instaweb/util/public/base64_util.h b/trunk/src/net/instaweb/util/public/base64_util.h
new file mode 100644
index 0000000..f7eb0e9
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/base64_util.h
@@ -0,0 +1,54 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_BASE64_UTIL_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_BASE64_UTIL_H_
+
+
+#include <string>
+
+#include "base/string_piece.h"
+#include "third_party/base64/base64.h"
+
+namespace net_instaweb {
+
+typedef base::StringPiece StringPiece;
+
+inline void Web64Encode(const StringPiece& in, std::string* out) {
+ *out = web64_encode(reinterpret_cast<const unsigned char*>(in.data()),
+ in.size());
+}
+
+inline bool Web64Decode(const StringPiece& in, std::string* out) {
+ bool ret = web64_decode(in.as_string(), out);
+ return ret;
+}
+
+inline void Mime64Encode(const StringPiece& in, std::string* out) {
+ *out = base64_encode(reinterpret_cast<const unsigned char*>(in.data()),
+ in.size());
+}
+
+inline bool Mime64Decode(const StringPiece& in, std::string* out) {
+ bool ret = base64_decode(in.as_string(), out);
+ return ret;
+}
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_BASE64_UTIL_H_
diff --git a/trunk/src/net/instaweb/util/public/cache_interface.h b/trunk/src/net/instaweb/util/public/cache_interface.h
new file mode 100644
index 0000000..62604a6
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/cache_interface.h
@@ -0,0 +1,57 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_CACHE_INTERFACE_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_CACHE_INTERFACE_H_
+
+// TODO(sligocki): We shouldn't need to include this in the .h, but it was
+// breaking someone somewhere, look into later.
+#include "net/instaweb/util/public/shared_string.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+class MessageHandler;
+class SharedString;
+
+// Abstract interface for a cache.
+class CacheInterface {
+ public:
+ enum KeyState {
+ kAvailable, // Requested key is available for serving
+ kInTransit, // Requested key is being written, but is not readable
+ kNotFound // Requested key needs to be written
+ };
+
+ virtual ~CacheInterface();
+
+ // Gets an object from the cache, returning false on a cache miss
+ virtual bool Get(const std::string& key, SharedString* value) = 0;
+
+ // Puts a value into the cache. The value that is passed in is not modified,
+ // but the SharedString is passed by non-const pointer because its reference
+ // count is bumped.
+ virtual void Put(const std::string& key, SharedString* value) = 0;
+ virtual void Delete(const std::string& key) = 0;
+ virtual KeyState Query(const std::string& key) = 0;
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_CACHE_INTERFACE_H_
diff --git a/trunk/src/net/instaweb/util/public/cache_url_async_fetcher.h b/trunk/src/net/instaweb/util/public/cache_url_async_fetcher.h
new file mode 100644
index 0000000..59fa32c
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/cache_url_async_fetcher.h
@@ -0,0 +1,75 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_CACHE_URL_ASYNC_FETCHER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_CACHE_URL_ASYNC_FETCHER_H_
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/util/public/http_cache.h"
+#include <string>
+#include "net/instaweb/util/public/url_async_fetcher.h"
+
+namespace net_instaweb {
+
+class MessageHandler;
+class UrlAsyncFetcher;
+
+// Composes an asynchronous URL fetcher with an http cache, to
+// generate an asynchronous caching URL fetcher.
+//
+// This fetcher will call the callback immediately for entries in the
+// cache. When entries are not in the cache, it will initiate an
+// asynchronous 'get' and store the result in the cache, as well as
+// calling the passed-in callback.
+//
+// See also CacheUrlFetcher, which will returns results only for
+// URLs still in the cache.
+class CacheUrlAsyncFetcher : public UrlAsyncFetcher {
+ public:
+ CacheUrlAsyncFetcher(HTTPCache* cache, UrlAsyncFetcher* fetcher)
+ : http_cache_(cache),
+ fetcher_(fetcher),
+ force_caching_(false) {
+ }
+ virtual ~CacheUrlAsyncFetcher();
+
+ virtual bool StreamingFetch(
+ const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* fetched_content_writer,
+ MessageHandler* message_handler,
+ Callback* callback);
+
+ void set_force_caching(bool force) {
+ force_caching_ = force;
+ http_cache_->set_force_caching(force);
+ }
+
+ private:
+ HTTPCache* http_cache_;
+ UrlAsyncFetcher* fetcher_;
+ bool force_caching_;
+
+ DISALLOW_COPY_AND_ASSIGN(CacheUrlAsyncFetcher);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_CACHE_URL_ASYNC_FETCHER_H_
diff --git a/trunk/src/net/instaweb/util/public/cache_url_fetcher.h b/trunk/src/net/instaweb/util/public/cache_url_fetcher.h
new file mode 100644
index 0000000..edd9059
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/cache_url_fetcher.h
@@ -0,0 +1,133 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_CACHE_URL_FETCHER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_CACHE_URL_FETCHER_H_
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/util/public/http_cache.h"
+#include "net/instaweb/util/public/http_value.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/string_writer.h"
+#include "net/instaweb/util/public/url_async_fetcher.h"
+#include "net/instaweb/util/public/url_fetcher.h"
+
+namespace net_instaweb {
+
+class MessageHandler;
+class UrlAsyncFetcher;
+
+// Composes a URL fetcher with an http cache, to generate a caching
+// URL fetcher.
+//
+// This fetcher will return true and provide an immediate result for
+// entries in the cache. When entries are not in the cache, it will
+// initiate an asynchronous 'get' and store the result in the cache.
+//
+// When the supplied url fetcher indicates that the entry is not
+// cacheable, then we cache the fact that the resource is not cacheable
+// for 5 minutes, and during that time we will not request the element
+// again, but instead will return false from StreamingUrlFetch. This
+// will allow us to quickly punt on rewrites for non-cacheable items.
+//
+// TODO(jmarantz): allow experimental adjustment of the timeout
+// period, and whether or not we will retain resources marked as
+// cache-control:private.
+//
+// See also CacheUrlAsyncFetcher, which will yield its results asynchronously
+// for elements not in the cache, and immediately for results that are.
+class CacheUrlFetcher : public UrlFetcher {
+ public:
+ CacheUrlFetcher(HTTPCache* cache, UrlFetcher* fetcher)
+ : http_cache_(cache),
+ sync_fetcher_(fetcher),
+ async_fetcher_(NULL),
+ force_caching_(false) {
+ }
+ CacheUrlFetcher(HTTPCache* cache, UrlAsyncFetcher* fetcher)
+ : http_cache_(cache),
+ sync_fetcher_(NULL),
+ async_fetcher_(fetcher),
+ force_caching_(false) {
+ }
+ virtual ~CacheUrlFetcher();
+
+ virtual bool StreamingFetchUrl(
+ const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* fetched_content_writer,
+ MessageHandler* message_handler);
+
+ // Helper class to hold state for a single asynchronous fetch. When
+ // the fetch is complete, we'll put the resource in the cache.
+ //
+ // This class is declared here to facilitate code-sharing with
+ // CacheAsyncUrlFetcher.
+ class AsyncFetch : public UrlAsyncFetcher::Callback {
+ public:
+ AsyncFetch(const StringPiece& url, HTTPCache* cache,
+ MessageHandler* handler, bool force_caching);
+ virtual ~AsyncFetch();
+
+ virtual void Done(bool success);
+ void Start(UrlAsyncFetcher* fetcher, const MetaData& request_headers);
+
+ // This hook allows the CacheUrlAsyncFetcher to capture the headers for
+ // its client, while still enabling this class to cache them.
+ virtual MetaData* ResponseHeaders() = 0;
+
+ void UpdateCache();
+
+ virtual bool EnableThreaded() const;
+
+ protected:
+ HTTPValue value_;
+ MessageHandler* message_handler_;
+
+ private:
+ std::string url_;
+ HTTPCache* http_cache_;
+ bool force_caching_;
+ };
+
+ void set_force_caching(bool force) {
+ force_caching_ = force;
+ http_cache_->set_force_caching(force);
+ }
+
+ // Determines whether the HTTP response headers indicate a response that
+ // we have cached to indicate that the response is not cacheable. This
+ // is in this header file so the functionality can be shared between
+ // CacheUrlAsyncFetcher and CacheUrlFetcher.
+ static bool RememberNotCached(const MetaData& headers);
+
+ private:
+ HTTPCache* http_cache_;
+ UrlFetcher* sync_fetcher_;
+ UrlAsyncFetcher* async_fetcher_;
+ bool force_caching_;
+
+ DISALLOW_COPY_AND_ASSIGN(CacheUrlFetcher);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_CACHE_URL_FETCHER_H_
diff --git a/trunk/src/net/instaweb/util/public/content_type.h b/trunk/src/net/instaweb/util/public/content_type.h
new file mode 100644
index 0000000..0b9c9b2
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/content_type.h
@@ -0,0 +1,84 @@
+/**
+ * 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)
+//
+// A collection of content-types and their attributes.
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_CONTENT_TYPE_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_CONTENT_TYPE_H_
+
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+struct ContentType {
+ public:
+ enum Type {
+ kHtml,
+ kXhtml,
+ kCeHtml, // See http://en.wikipedia.org/wiki/CE-HTML
+ kJavascript,
+ kCss,
+ kText,
+ kXml,
+ kPng,
+ kGif,
+ kJpeg,
+ };
+
+ const char* mime_type() const { return mime_type_; }
+ const char* file_extension() const { return file_extension_; }
+ Type type() const { return type_; }
+
+ // Return true iff this content type is HTML, or XHTML, or some other such
+ // thing (e.g. CE-HTML) that we can rewrite.
+ bool IsHtmlLike() const;
+
+ // Return true iff this content type is XML of some kind (either XHTML or
+ // some other XML).
+ bool IsXmlLike() const;
+
+ // These fields should be private; we leave them public only so we can use
+ // struct literals in content_type.cc. Other code should use the above
+ // accessor methods instead of accessing these fields directly.
+ const char* mime_type_;
+ const char* file_extension_; // includes ".", e.g. ".ext"
+ Type type_;
+};
+
+// HTML-like (i.e. rewritable) text:
+extern const ContentType& kContentTypeHtml;
+extern const ContentType& kContentTypeXhtml;
+extern const ContentType& kContentTypeCeHtml;
+// Other text:
+extern const ContentType& kContentTypeJavascript;
+extern const ContentType& kContentTypeCss;
+extern const ContentType& kContentTypeText;
+extern const ContentType& kContentTypeXml;
+// Images:
+extern const ContentType& kContentTypePng;
+extern const ContentType& kContentTypeGif;
+extern const ContentType& kContentTypeJpeg;
+
+// Given a name (file or url), see if it has the canonical extension
+// corresponding to a particular content type.
+const ContentType* NameExtensionToContentType(const StringPiece& name);
+const ContentType* MimeTypeToContentType(const StringPiece& mime_type);
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_CONTENT_TYPE_H_
diff --git a/trunk/src/net/instaweb/util/public/data_url.h b/trunk/src/net/instaweb/util/public/data_url.h
new file mode 100644
index 0000000..1f29f3c
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/data_url.h
@@ -0,0 +1,64 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_DATA_URL_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_DATA_URL_H_
+
+#include "net/instaweb/util/public/content_type.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+enum Encoding {
+ UNKNOWN, // Used only for output of ParseDataUrl.
+ BASE64,
+// LATIN1, // TODO(jmaessen): implement non-BASE64 encodings.
+// UTF8,
+ PLAIN
+};
+
+// Create a data: url from the given content-type and content. See:
+// http://en.wikipedia.org/wiki/Data_URI_scheme
+//
+// The ENCODING indicates how to encode the content; for binary data
+// this is UTF8, for ascii / Latin1 it's LATIN1. If you have ascii
+// without high bits or NULs, use LATIN1. If you have alphanumeric data,
+// use PLAIN (which doesn't encode at all).
+//
+// Note in particular that IE<=7 does not support this, so it makes us
+// UserAgent-dependent. It also pretty much requires outgoing content to be
+// compressed as we tend to base64-encode the content.
+void DataUrl(const ContentType& content_type, const Encoding encoding,
+ const StringPiece& content, std::string* result);
+
+// Dismantle a data: url into its component pieces, but do not decode the
+// content. Note that encoded_content will be a substring of the input url and
+// shares its lifetime. Invalidates all outputs if url does not parse.
+bool ParseDataUrl(const StringPiece& url,
+ const ContentType** content_type,
+ Encoding* encoding,
+ StringPiece* encoded_content);
+
+bool DecodeDataUrlContent(Encoding encoding,
+ const StringPiece& encoded_content,
+ std::string* decoded_content);
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_DATA_URL_H_
diff --git a/trunk/src/net/instaweb/util/public/dense_hash_set.h b/trunk/src/net/instaweb/util/public/dense_hash_set.h
new file mode 100644
index 0000000..a7cd887
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/dense_hash_set.h
@@ -0,0 +1,24 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_DENSE_HASH_SET_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_DENSE_HASH_SET_H_
+
+#include "google/dense_hash_set"
+using namespace google;
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_DENSE_HASH_SET_H_
diff --git a/trunk/src/net/instaweb/util/public/dummy_url_fetcher.h b/trunk/src/net/instaweb/util/public/dummy_url_fetcher.h
new file mode 100644
index 0000000..f7fc61b
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/dummy_url_fetcher.h
@@ -0,0 +1,41 @@
+/**
+ * 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)
+//
+// Dummy implementation that aborts if used (useful for tests).
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_DUMMY_URL_FETCHER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_DUMMY_URL_FETCHER_H_
+
+#include "net/instaweb/util/public/url_fetcher.h"
+
+namespace net_instaweb {
+
+class MessageHandler;
+
+class DummyUrlFetcher : public UrlFetcher {
+ public:
+ virtual bool StreamingFetchUrl(const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* fetched_content_writer,
+ MessageHandler* message_handler);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_DUMMY_URL_FETCHER_H_
diff --git a/trunk/src/net/instaweb/util/public/fake_url_async_fetcher.h b/trunk/src/net/instaweb/util/public/fake_url_async_fetcher.h
new file mode 100644
index 0000000..cad09a0
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/fake_url_async_fetcher.h
@@ -0,0 +1,64 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+//
+// UrlFetcher is an interface for asynchronously fetching urls. The
+// caller must supply a callback to be called when the fetch is complete.
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_FAKE_URL_ASYNC_FETCHER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_FAKE_URL_ASYNC_FETCHER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/url_async_fetcher.h"
+#include "net/instaweb/util/public/url_fetcher.h"
+
+namespace net_instaweb {
+
+// Constructs an async fetcher using a synchronous fetcher, blocking
+// on a fetch and then the 'done' callback directly. It's also
+// possible to construct a real async interface using a synchronous
+// fetcher in a thread, but this does not do that: it blocks.
+//
+// This is intended for functional regression tests only.
+class FakeUrlAsyncFetcher : public UrlAsyncFetcher {
+ public:
+ explicit FakeUrlAsyncFetcher(UrlFetcher* url_fetcher)
+ : url_fetcher_(url_fetcher) {
+ }
+ virtual ~FakeUrlAsyncFetcher();
+
+ virtual bool StreamingFetch(const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* writer,
+ MessageHandler* handler,
+ Callback* callback) {
+ bool ret = url_fetcher_->StreamingFetchUrl(
+ url, request_headers, response_headers, writer, handler);
+ callback->Done(ret);
+ return true;
+ }
+
+ private:
+ UrlFetcher* url_fetcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeUrlAsyncFetcher);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_FAKE_URL_ASYNC_FETCHER_H_
diff --git a/trunk/src/net/instaweb/util/public/file_cache.h b/trunk/src/net/instaweb/util/public/file_cache.h
new file mode 100644
index 0000000..4f472b3
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/file_cache.h
@@ -0,0 +1,86 @@
+/**
+ * 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: lsong@google.com (Libo Song)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_FILE_CACHE_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_FILE_CACHE_H_
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/util/public/cache_interface.h"
+#include "net/instaweb/util/public/file_system.h"
+#include "net/instaweb/util/public/filename_encoder.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include <string>
+#include "net/instaweb/util/public/timer.h"
+
+namespace net_instaweb {
+
+class Timer;
+
+// Simple C++ implementation of file cache.
+class FileCache : public CacheInterface {
+ public:
+
+ struct CachePolicy {
+ CachePolicy(Timer* timer, int64 clean_interval_ms, int64 target_size)
+ : timer(timer), clean_interval_ms(clean_interval_ms),
+ target_size(target_size) {}
+ const Timer* timer;
+ const int64 clean_interval_ms;
+ const int64 target_size;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CachePolicy);
+ };
+
+ FileCache(const std::string& path, FileSystem* file_system,
+ FilenameEncoder* filename_encoder, CachePolicy* policy,
+ MessageHandler* handler);
+ virtual ~FileCache();
+
+ virtual bool Get(const std::string& key, SharedString* value);
+ virtual void Put(const std::string& key, SharedString* value);
+ virtual void Delete(const std::string& key);
+ virtual KeyState Query(const std::string& key);
+ // Attempts to clean the cache. Returns false if we failed and the
+ // cache still needs to be cleaned. Returns true if everything's
+ // fine. This may take a while. It's OK for others to write and
+ // read from the cache while this is going on, but try to avoid
+ // Cleaning from two threads at the same time.
+ bool Clean(int64 target_size);
+ // Check to see if it's time to clean the cache, and if so start
+ // cleaning. Return true if we cleaned, false if we didn't.
+ bool CheckClean();
+ private:
+ bool EncodeFilename(const std::string& key, std::string* filename);
+
+ std::string path_;
+ FileSystem* file_system_;
+ FilenameEncoder* filename_encoder_;
+ MessageHandler* message_handler_;
+ const scoped_ptr<CachePolicy> cache_policy_;
+ int64 next_clean_ms_;
+ // The file where we keep the next scheduled cleanup time in seconds.
+ static const char kCleanTimeName[];
+ // The name of the global mutex protecting reads and writes to that file.
+ static const char kCleanLockName[];
+ DISALLOW_COPY_AND_ASSIGN(FileCache);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_FILE_CACHE_H_
diff --git a/trunk/src/net/instaweb/util/public/file_message_handler.h b/trunk/src/net/instaweb/util/public/file_message_handler.h
new file mode 100644
index 0000000..56534ce
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/file_message_handler.h
@@ -0,0 +1,48 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_FILE_MESSAGE_HANDLER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_FILE_MESSAGE_HANDLER_H_
+
+#include <stdio.h>
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/message_handler.h"
+
+namespace net_instaweb {
+
+// Message handler implementation for directing all error and
+// warning messages to a file.
+class FileMessageHandler : public MessageHandler {
+ public:
+ explicit FileMessageHandler(FILE* file);
+
+ protected:
+ virtual void MessageVImpl(MessageType type, const char* msg, va_list args);
+
+ virtual void FileMessageVImpl(MessageType type, const char* filename,
+ int line, const char* msg, va_list args);
+
+ private:
+ FILE* file_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileMessageHandler);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_FILE_MESSAGE_HANDLER_H_
diff --git a/trunk/src/net/instaweb/util/public/file_system.h b/trunk/src/net/instaweb/util/public/file_system.h
new file mode 100644
index 0000000..ea91235
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/file_system.h
@@ -0,0 +1,263 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_FILE_SYSTEM_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_FILE_SYSTEM_H_
+
+#include "base/basictypes.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+// Three-way return type for distinguishing Errors from boolean answer.
+//
+// This is physically just an enum, but is wrapped in a class to prevent
+// accidental usage in an if- or ternary-condition without explicitly indicating
+// whether you are looking for true, false, or error.
+class BoolOrError {
+ enum Choice {
+ kIsFalse,
+ kIsTrue,
+ kIsError
+ };
+
+ public:
+ BoolOrError() : choice_(kIsError) { }
+ explicit BoolOrError(bool t_or_f) : choice_(t_or_f ? kIsTrue : kIsFalse) { }
+
+ // Intended to be passed by value; explicitly support copy & assign
+ BoolOrError(const BoolOrError& src) : choice_(src.choice_) { }
+ BoolOrError& operator=(const BoolOrError& src) {
+ if (&src != this) {
+ choice_ = src.choice_;
+ }
+ return *this;
+ }
+
+ bool is_false() const { return choice_ == kIsFalse; }
+ bool is_true() const { return choice_ == kIsTrue; }
+ bool is_error() const { return choice_ == kIsError; }
+ void set_error() { choice_ = kIsError; }
+ void set(bool t_or_f) { choice_ = t_or_f ? kIsTrue : kIsFalse; }
+
+ private:
+ Choice choice_;
+};
+
+class MessageHandler;
+class Writer;
+
+// Provides abstract file system interface. This isolation layer helps us:
+// - write unit tests that don't test the physical filesystem via a
+// MemFileSystem.
+// - Eases integration with Apache, which has its own file system interface,
+// and this class can help serve as the glue.
+// - provides a speculative conduit to a database so we can store resources
+// in a place where multiple Apache servers can see them.
+class FileSystem {
+ public:
+ virtual ~FileSystem();
+
+ class File {
+ public:
+ virtual ~File();
+
+ // Gets the name of the file.
+ virtual const char* filename() = 0;
+
+ protected:
+ // Use public interface provided by FileSystem::Close.
+ friend class FileSystem;
+ virtual bool Close(MessageHandler* handler) = 0;
+ };
+
+ class InputFile : public File {
+ public:
+ // TODO(sligocki): Perhaps this should be renamed to avoid confusing
+ // that it returns a bool to indicate success like all other Read methods
+ // in our codebase.
+ virtual int Read(char* buf, int size, MessageHandler* handler) = 0;
+
+ protected:
+ friend class FileSystem;
+ virtual ~InputFile();
+ };
+
+ class OutputFile : public File {
+ public:
+ // Note: Write is not atomic. If Write fails, there is no indication of how
+ // much data has already been written to the file.
+ //
+ // TODO(sligocki): Would we like a version that returns the amound written?
+ // If so, it should be named so that it is clear it is returning int.
+ virtual bool Write(const StringPiece& buf, MessageHandler* handler) = 0;
+ virtual bool Flush(MessageHandler* handler) = 0;
+ virtual bool SetWorldReadable(MessageHandler* handler) = 0;
+
+ protected:
+ friend class FileSystem;
+ virtual ~OutputFile();
+ };
+
+ // High level support to read/write entire files in one shot.
+ virtual bool ReadFile(const char* filename,
+ std::string* buffer,
+ MessageHandler* handler);
+ virtual bool ReadFile(const char* filename,
+ Writer* writer,
+ MessageHandler* handler);
+ virtual bool WriteFile(const char* filename,
+ const StringPiece& buffer,
+ MessageHandler* handler);
+ // Writes given data to a temp file in one shot, storing the filename
+ // in filename on success. Returns false and clears filename on failure.
+ virtual bool WriteTempFile(const StringPiece& prefix_name,
+ const StringPiece& buffer,
+ std::string* filename,
+ MessageHandler* handler);
+
+ virtual InputFile* OpenInputFile(const char* filename,
+ MessageHandler* handler) = 0;
+ // Automatically creates sub-directories to filename.
+ OutputFile* OpenOutputFile(const char* filename,
+ MessageHandler* handler) {
+ SetupFileDir(filename, handler);
+ return OpenOutputFileHelper(filename, handler);
+ }
+ // Opens a temporary file to write, with the specified prefix.
+ // If successful, the filename can be obtained from File::filename().
+ // Automatically creates sub-directories to filename.
+ //
+ // NULL is returned on failure.
+ OutputFile* OpenTempFile(const StringPiece& prefix_name,
+ MessageHandler* handler) {
+ SetupFileDir(prefix_name, handler);
+ return OpenTempFileHelper(prefix_name, handler);
+ }
+
+ // Closes the File and cleans up memory.
+ virtual bool Close(File* file, MessageHandler* handler);
+
+
+ // Like POSIX 'rm'.
+ virtual bool RemoveFile(const char* filename, MessageHandler* handler) = 0;
+
+ // Like POSIX 'mv', except it automatically creates sub-directories for
+ // new_filename.
+ bool RenameFile(const char* old_filename, const char* new_filename,
+ MessageHandler* handler) {
+ SetupFileDir(new_filename, handler);
+ return RenameFileHelper(old_filename, new_filename, handler);
+ }
+
+ // Like POSIX 'mkdir', makes a directory only if parent directory exists.
+ // Fails if directory_name already exists or parent directory doesn't exist.
+ virtual bool MakeDir(const char* directory_path, MessageHandler* handler) = 0;
+
+ // Like POSIX 'test -e', checks if path exists (is a file, directory, etc.).
+ virtual BoolOrError Exists(const char* path, MessageHandler* handler) = 0;
+
+ // Like POSIX 'test -d', checks if path exists and refers to a directory.
+ virtual BoolOrError IsDir(const char* path, MessageHandler* handler) = 0;
+
+ // Like POSIX 'mkdir -p', makes all directories up to this one recursively.
+ // Fails if we do not have permission to make any directory in chain.
+ virtual bool RecursivelyMakeDir(const StringPiece& directory_path,
+ MessageHandler* handler);
+
+ // Like POSIX 'ls -a', lists all files and directories under the given
+ // directory (but omits "." and ".."). Full paths (not just filenames) will
+ // be pushed onto the back of the supplied vector (without clearing it).
+ // Returns true on success (even if the dir was empty), false on error (even
+ // if some files were pushed onto the vector). This is generally not
+ // threadsafe! Use a mutex.
+ virtual bool ListContents(const StringPiece& dir, StringVector* files,
+ MessageHandler* handler) = 0;
+
+ // Stores in *timestamp_sec the timestamp (in seconds since the
+ // epoch) of the last time the file was accessed (through one of our
+ // Read methods, or by someone else accessing the filesystem
+ // directly). Returns true on success, false on failure.
+ // TODO(abliss): replace this with a single Stat() function.
+ virtual bool Atime(const StringPiece& path, int64* timestamp_sec,
+ MessageHandler* handler) = 0;
+
+ // Given a directory, recursively computes the total size of all its
+ // files and directories, and increments *size by the sum total. We
+ // assume no circular links. Returns true on success, false on
+ // failure. If the files are modified while we traverse, we are not
+ // guaranteed to represent their final state.
+ // The path name should NOT end in a "/".
+ // TODO(abliss): unify all slash-ending assumptions
+ virtual bool RecursiveDirSize(const StringPiece& path, int64* size,
+ MessageHandler* handler);
+
+ // Given a file, computes its size in bytes and store it in *size. Returns
+ // true on success, false on failure. Behavior is undefined if path refers to
+ // a directory.
+ // TODO(abliss): replace this with a single Stat() function.
+ virtual bool Size(const StringPiece& path, int64* size,
+ MessageHandler* handler) = 0;
+
+ // Attempts to obtain a global (cross-process, cross-thread) lock of the given
+ // name (which should be a valid filename, not otherwise used, in an extant
+ // directory). If someone else has this lock, returns False immediately. If
+ // anything goes wrong, returns Error. On success, returns True: then you
+ // must call Unlock when you are done.
+ virtual BoolOrError TryLock(const StringPiece& lock_name,
+ MessageHandler* handler) = 0;
+
+ // Like TryLock, but may attempt to break the lock if it appears to be staler
+ // than the given number of milliseconds. (The default implementation never
+ // actually breaks locks.) If you obtain a lock through this method, there
+ // are no hard guarantees that nobody else has it too.
+ // <blink> If you use this function, your lock becomes "best-effort". </blink>
+ virtual BoolOrError TryLockWithTimeout(const StringPiece& lock_name,
+ int64 timeout_millis,
+ MessageHandler* handler) {
+ return TryLock(lock_name, handler);
+ }
+
+ // Attempts to release a lock previously obtained through TryLock. If your
+ // thread did not prevously obtain the lock, the behavior is undefined.
+ // Returns true if we successfully release the lock. Returns false if we were
+ // unable to release the lock (e.g. somebody came along and write-protected
+ // the lockfile). You might try again, or start using a different lock name.
+ virtual bool Unlock(const StringPiece& lock_name,
+ MessageHandler* handler) = 0;
+
+ protected:
+ // These interfaces must be defined by implementers of FileSystem.
+ // They may assume the directory already exists.
+ virtual OutputFile* OpenOutputFileHelper(const char* filename,
+ MessageHandler* handler) = 0;
+ virtual OutputFile* OpenTempFileHelper(const StringPiece& filename,
+ MessageHandler* handler) = 0;
+ virtual bool RenameFileHelper(const char* old_filename,
+ const char* new_filename,
+ MessageHandler* handler) = 0;
+
+ private:
+ // RecursiveMakeDir the directory needed for filename.
+ void SetupFileDir(const StringPiece& filename, MessageHandler* handler);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_FILE_SYSTEM_H_
diff --git a/trunk/src/net/instaweb/util/public/file_system_lock_manager.h b/trunk/src/net/instaweb/util/public/file_system_lock_manager.h
new file mode 100644
index 0000000..a9c2746
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/file_system_lock_manager.h
@@ -0,0 +1,75 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_FILE_SYSTEM_LOCK_MANAGER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_FILE_SYSTEM_LOCK_MANAGER_H_
+
+#include "net/instaweb/util/public/named_lock_manager.h"
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+class FileSystem;
+class Timer;
+class MessageHandler;
+
+// Use the locking routines in FileSystem to implement named locks. Requires a
+// Timer as well because the FileSystem locks are non-blocking and we must use
+// spin+sleep. A MessageHandler is used to report file system errors during
+// lock creation and cleanup.
+class FileSystemLockManager : public NamedLockManager {
+ public:
+ // Note: a FileSystemLockManager must outlive
+ // any and all locks that it creates.
+ // It does not assume ownership of the passed-in
+ // constructor arguments.
+ FileSystemLockManager(FileSystem* file_system,
+ Timer* timer,
+ MessageHandler* handler)
+ : file_system_(file_system),
+ timer_(timer),
+ handler_(handler) { }
+ virtual ~FileSystemLockManager();
+
+ // Multiple lock objects with the same name will manage the same underlying
+ // lock. Lock names must be legal file names according to file_system.
+ //
+ // A lock created by CreateNamedLock will be Unlocked when it is destructed if
+ // the AbstractLock object appears to still be locked at destruction time.
+ // This attempts to ensure that the file system is not littered with the
+ // remnants of dead locks. A given AbstractLock object should Lock and Unlock
+ // in matched pairs; DO NOT use separate AbstractLock objects created with the
+ // same name to perform a Lock and the corresponding Unlock.
+ virtual AbstractLock* CreateNamedLock(const StringPiece& name);
+
+ // Simple accessors for constructor arguments
+ FileSystem* file_system() const { return file_system_; }
+ Timer* timer() const { return timer_; }
+ MessageHandler* handler() const { return handler_; }
+
+ private:
+ FileSystem* file_system_;
+ Timer* timer_;
+ MessageHandler* handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemLockManager);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_FILE_SYSTEM_LOCK_MANAGER_H_
diff --git a/trunk/src/net/instaweb/util/public/file_system_test.h b/trunk/src/net/instaweb/util/public/file_system_test.h
new file mode 100644
index 0000000..3d8ea82
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/file_system_test.h
@@ -0,0 +1,82 @@
+/**
+ * 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: abliss@google.com (Adam Bliss)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_FILE_SYSTEM_TEST_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_FILE_SYSTEM_TEST_H_
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/util/public/file_system.h"
+#include "net/instaweb/util/public/google_message_handler.h"
+#include "net/instaweb/util/public/gtest.h"
+#include <string>
+
+namespace net_instaweb {
+
+// Base class for testing a FileSystem implementation. Subclasses
+// must implement DeleteRecursively and GetFileSystem, then should
+// create their own tests calling each of our Test* methods.
+class FileSystemTest : public testing::Test {
+ protected:
+ FileSystemTest();
+ virtual ~FileSystemTest();
+
+ void CheckDoesNotExist(const std::string& filename);
+
+ void CheckRead(const std::string& filename,
+ const std::string& expected_contents);
+
+ // Delete (at least) the named file or directory and everything
+ // underneath it. The test is permitted to delete more things (up
+ // to and including the entire file system).
+ virtual void DeleteRecursively(const StringPiece& filename) = 0;
+
+ // Provide a pointer to your favorite filesystem implementation.
+ virtual FileSystem* file_system() = 0;
+
+ // Provide a temporary directory for tests to put files in.
+ virtual std::string test_tmpdir() = 0;
+
+ std::string WriteNewFile(const StringPiece& suffix,
+ const std::string& content);
+
+ void TestWriteRead();
+ void TestTemp();
+ void TestRename();
+ void TestRemove();
+ void TestExists();
+ void TestCreateFileInDir();
+ void TestMakeDir();
+ void TestIsDir();
+ void TestRecursivelyMakeDir();
+ void TestRecursivelyMakeDir_NoPermission();
+ void TestRecursivelyMakeDir_FileInPath();
+ void TestListContents();
+ void TestAtime();
+ void TestSize();
+ void TestLock();
+
+ GoogleMessageHandler handler_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileSystemTest);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_FILE_SYSTEM_TEST_H_
diff --git a/trunk/src/net/instaweb/util/public/file_writer.h b/trunk/src/net/instaweb/util/public/file_writer.h
new file mode 100644
index 0000000..adac186
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/file_writer.h
@@ -0,0 +1,43 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_FILE_WRITER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_FILE_WRITER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/file_system.h"
+#include "net/instaweb/util/public/writer.h"
+
+namespace net_instaweb {
+
+// Writer implementation for directing HTML output to a file.
+class FileWriter : public Writer {
+ public:
+ explicit FileWriter(FileSystem::OutputFile* f) : file_(f) { }
+ virtual ~FileWriter();
+ virtual bool Write(const StringPiece& str, MessageHandler* message_handler);
+ virtual bool Flush(MessageHandler* message_handler);
+ private:
+ FileSystem::OutputFile* file_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileWriter);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_FILE_WRITER_H_
diff --git a/trunk/src/net/instaweb/util/public/filename_encoder.h b/trunk/src/net/instaweb/util/public/filename_encoder.h
new file mode 100644
index 0000000..742efb7
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/filename_encoder.h
@@ -0,0 +1,41 @@
+/**
+ * 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)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_FILENAME_ENCODER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_FILENAME_ENCODER_H_
+
+#include "net/instaweb/util/public/string_util.h"
+#include "net/tools/dump_cache/url_to_filename_encoder.h"
+namespace net_instaweb {
+
+class FilenameEncoder {
+ public:
+ FilenameEncoder() {}
+ ~FilenameEncoder();
+
+ void Encode(const StringPiece& filename_prefix,
+ const StringPiece& filename_ending,
+ std::string* encoded_filename);
+
+ bool Decode(const StringPiece& encoded_filename,
+ std::string* decoded_url);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_FILENAME_ENCODER_H_
diff --git a/trunk/src/net/instaweb/util/public/google_message_handler.h b/trunk/src/net/instaweb/util/public/google_message_handler.h
new file mode 100644
index 0000000..7a15333
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/google_message_handler.h
@@ -0,0 +1,48 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_GOOGLE_MESSAGE_HANDLER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_GOOGLE_MESSAGE_HANDLER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include <string>
+
+namespace net_instaweb {
+
+// Implementation of an HTML parser message handler that uses Google
+// logging to emit messsages.
+class GoogleMessageHandler : public MessageHandler {
+ public:
+ GoogleMessageHandler() { }
+
+ protected:
+ virtual void MessageVImpl(MessageType type, const char* msg, va_list args);
+
+ virtual void FileMessageVImpl(MessageType type, const char* filename,
+ int line, const char* msg, va_list args);
+
+ private:
+ std::string Format(const char* msg, va_list args);
+
+ DISALLOW_COPY_AND_ASSIGN(GoogleMessageHandler);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_GOOGLE_MESSAGE_HANDLER_H_
diff --git a/trunk/src/net/instaweb/util/public/google_url.h b/trunk/src/net/instaweb/util/public/google_url.h
new file mode 100644
index 0000000..0c8c5e7
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/google_url.h
@@ -0,0 +1,83 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_GOOGLE_URL_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_GOOGLE_URL_H_
+
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+
+#include "googleurl/src/gurl.h"
+
+namespace net_instaweb {
+
+namespace GoogleUrl {
+
+// Helper functions around GURL to make it a little easier to use
+inline std::string Spec(const GURL& gurl) { return gurl.spec(); }
+
+// Makes a GURL object from a StringPiece.
+inline GURL Create(const StringPiece& sp) { return GURL(sp.as_string()); }
+
+// Makes a GURL object from a string.
+inline GURL Create(const std::string& str) {
+ return GURL(str);
+}
+
+// Resolves a GURL object and a new path into a new GURL.
+inline GURL Resolve(const GURL& gurl, const StringPiece& sp) {
+ return gurl.Resolve(sp.as_string()); }
+
+// Resolves a GURL object and a new path into a new GURL.
+inline GURL Resolve(const GURL& gurl, const std::string& str) {
+ return gurl.Resolve(str);
+}
+
+// Resolves a GURL object and a new path into a new GURL.
+inline GURL Resolve(const GURL& gurl, const char* str) {
+ return gurl.Resolve(str);
+}
+
+// For "http://a.com/b/c/d?e=f/g returns "http://a/b/c/",
+// including trailing slash.
+std::string AllExceptLeaf(const GURL& gurl);
+
+// For "http://a.com/b/c/d?e=f/g returns "d?e=f/g", omitting leading slash.
+std::string Leaf(const GURL& gurl);
+
+// For "http://a.com/b/c/d?e=f/g returns "http://a.com" without trailing slash
+std::string Origin(const GURL& gurl);
+
+// For "http://a.com/b/c/d?E=f/g returns "/b/c/d?e=f/g" including leading slash
+std::string PathAndLeaf(const GURL& gurl);
+
+// For "http://a.com/b/c/d?E=f/g returns "/b/c/d" including leading slash
+inline std::string Path(const GURL& gurl) {
+ return gurl.path();
+}
+
+// For "http://a.com/b/c/d?e=f/g returns "/b/c/d/" including leading and
+// trailing slashes.
+std::string PathSansLeaf(const GURL& gurl);
+
+} // namespace GoogleUrl
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_GOOGLE_URL_H_
diff --git a/trunk/src/net/instaweb/util/public/gtest.h b/trunk/src/net/instaweb/util/public/gtest.h
new file mode 100644
index 0000000..c7c2c20
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/gtest.h
@@ -0,0 +1,35 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+//
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_GTEST_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_GTEST_H_
+
+#include <vector>
+
+#include <string>
+#include "gtest/gtest.h"
+
+namespace net_instaweb {
+
+std::string GTestSrcDir();
+std::string GTestTempDir();
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_GTEST_H_
diff --git a/trunk/src/net/instaweb/util/public/gzip_inflater.h b/trunk/src/net/instaweb/util/public/gzip_inflater.h
new file mode 100644
index 0000000..00a302c
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/gzip_inflater.h
@@ -0,0 +1,77 @@
+/**
+ * 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.
+ */
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_GZIP_INFLATER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_GZIP_INFLATER_H_
+
+#include <string.h>
+#include "base/basictypes.h"
+
+typedef struct z_stream_s z_stream;
+
+namespace net_instaweb {
+
+class GzipInflater {
+ public:
+ enum InflateType {kGzip, kDeflate};
+
+ explicit GzipInflater(InflateType type);
+ ~GzipInflater();
+
+ // Should be called once, before inflating any data.
+ bool Init();
+
+ // Should be called once, after inflating is finished.
+ void ShutDown();
+
+ // Does the inflater still have input that has not yet been
+ // consumed? If true, the caller should call InflateBytes(). If
+ // false, the gzip inflater is ready for additional input.
+ bool HasUnconsumedInput() const;
+
+ // Pass a gzip-compressed buffer to the gzip inflater. The gzip
+ // inflater will inflate the buffer via InflateBytes(). SetInput
+ // should not be called if HasUnconsumedInput() is true, and the
+ // buffer passed into SetInput should not be modified by the caller
+ // until HasUnconsumedInput() returns false.
+ bool SetInput(const void *in, size_t in_size);
+
+ // Decompress the input passed in via SetInput. Should be called
+ // until HasUnconsumedInput returns false. Returns the number of
+ // bytes inflated, or -1 if an error was encountered while
+ // inflating.
+ int InflateBytes(char *buf, size_t buf_size);
+
+ // Has the entire input been inflated?
+ bool finished() const { return finished_; }
+
+ // Was an error encountered during inflating?
+ bool error() const { return error_; }
+
+ private:
+ void Free();
+
+ z_stream *zlib_;
+ bool finished_;
+ bool error_;
+ InflateType type_;
+
+ DISALLOW_COPY_AND_ASSIGN(GzipInflater);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_GZIP_INFLATER_H_
diff --git a/trunk/src/net/instaweb/util/public/hasher.h b/trunk/src/net/instaweb/util/public/hasher.h
new file mode 100644
index 0000000..ef54129
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/hasher.h
@@ -0,0 +1,47 @@
+/**
+ * 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)
+//
+// Interface for a hash function.
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_HASHER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_HASHER_H_
+
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+class Hasher {
+ public:
+ Hasher() { }
+ virtual ~Hasher();
+
+ // Interface to compute a hash of a single string. This
+ // operation is thread-safe.
+ virtual std::string Hash(const StringPiece& content) const = 0;
+
+ // Return string length of hashes produced by this hasher.
+ virtual int HashSizeInChars() const = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Hasher);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_HASHER_H_
diff --git a/trunk/src/net/instaweb/util/public/http_cache.h b/trunk/src/net/instaweb/util/public/http_cache.h
new file mode 100644
index 0000000..d9a2964
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/http_cache.h
@@ -0,0 +1,124 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_HTTP_CACHE_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_HTTP_CACHE_H_
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/util/public/cache_interface.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+class CacheInterface;
+class HTTPValue;
+class MessageHandler;
+class MetaData;
+class Statistics;
+class Timer;
+class Variable;
+
+// Implements HTTP caching semantics, including cache expiration and
+// retention of the originally served cache headers.
+class HTTPCache {
+ public:
+ // Takes over ownership of the cache.
+ HTTPCache(CacheInterface* cache, Timer* timer)
+ : cache_(cache),
+ timer_(timer),
+ force_caching_(false),
+ cache_time_us_(NULL),
+ cache_hits_(NULL),
+ cache_misses_(NULL),
+ cache_expirations_(NULL),
+ cache_inserts_(NULL) {
+ }
+
+ ~HTTPCache();
+
+ // When a lookup is done in the HTTP Cache, it returns one of these values.
+ // 2 of these are obvious, one is used to help avoid frequently re-fetching
+ // the same content that failed to fetch, or was fetched but was not cacheable.
+ //
+ // TODO(jmarantz): consider merging these 3 into the 3 status codes defined
+ // CacheInterface, making 4 distinct codes. That would be a little clearer,
+ // but would require that all callers of Find handle kInTransit which no
+ // cache implementations currently generate.
+ enum FindResult {
+ kFound,
+ kRecentFetchFailedDoNotRefetch,
+ kNotFound
+ };
+
+ FindResult Find(const std::string& key, HTTPValue* value, MetaData* headers,
+ MessageHandler* handler);
+
+ // Note that Put takes a non-const pointer for HTTPValue so it can
+ // bump the reference count.
+ void Put(const std::string& key, HTTPValue* value, MessageHandler* handler);
+ void Put(const std::string& key, const MetaData& headers,
+ const StringPiece& content, MessageHandler* handler);
+
+ CacheInterface::KeyState Query(const std::string& key);
+ void Delete(const std::string& key);
+
+ void set_force_caching(bool force) { force_caching_ = force; }
+ bool force_caching() const { return force_caching_; }
+ Timer* timer() const { return timer_; }
+
+ // Initializes statistics for the cache (time, hits, misses, expirations)
+ void SetStatistics(Statistics* stats);
+
+ // Tell the HTTP Cache to remember that a particular key is not cacheable.
+ // This may be due to the associated URL failing Fetch, or it may be because
+ // the URL was fetched but was marked with Cache-Control 'nocache' or
+ // Cache-Control 'private'. In any case we would like to avoid DOSing
+ // the origin server or spinning our own wheels trying to re-fetch this
+ // resource.
+ //
+ // The not-cacheable setting will be 'remembered' for 5 minutes -- currently
+ // hard-coded in http_cache.cc.
+ //
+ // TODO(jmarantz): if fetch failed, maybe we should try back soon,
+ // but if it is Cache-Control: private, we can probably assume that
+ // it still will be in 5 minutes.
+ void RememberNotCacheable(const std::string& key, MessageHandler * handler);
+
+ // Initialize statistics variables for the cache
+ static void Initialize(Statistics* statistics);
+
+ private:
+ bool IsCurrentlyValid(const MetaData& headers, int64 now_ms);
+
+ scoped_ptr<CacheInterface> cache_;
+ Timer* timer_;
+ bool force_caching_;
+ Variable* cache_time_us_;
+ Variable* cache_hits_;
+ Variable* cache_misses_;
+ Variable* cache_expirations_;
+ Variable* cache_inserts_;
+
+ DISALLOW_COPY_AND_ASSIGN(HTTPCache);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_HTTP_CACHE_H_
diff --git a/trunk/src/net/instaweb/util/public/http_dump_url_async_writer.h b/trunk/src/net/instaweb/util/public/http_dump_url_async_writer.h
new file mode 100644
index 0000000..575c35e
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/http_dump_url_async_writer.h
@@ -0,0 +1,78 @@
+/**
+ * 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)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_HTTP_DUMP_URL_ASYNC_WRITER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_HTTP_DUMP_URL_ASYNC_WRITER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/http_dump_url_fetcher.h"
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/url_async_fetcher.h"
+
+namespace net_instaweb {
+
+class FileSystem;
+
+// HttpDumpWriter checks to see whether the HTTP dump is available on the
+// filesystem. If not, it fetches it from another fetcher (e.g. one that
+// uses the network) and writes it to the filesystem so that HttpDumpFetcher
+// can find it.
+class HttpDumpUrlAsyncWriter : public UrlAsyncFetcher {
+ public:
+ HttpDumpUrlAsyncWriter(const StringPiece& root_dir,
+ UrlAsyncFetcher* base_fetcher,
+ FileSystem* file_system,
+ Timer* timer)
+ : dump_fetcher_(root_dir, file_system, timer),
+ base_fetcher_(base_fetcher),
+ file_system_(file_system),
+ accept_gzip_(true) {
+ root_dir.CopyToString(&root_dir_);
+ }
+ virtual ~HttpDumpUrlAsyncWriter();
+
+ // This is a synchronous/blocking implementation.
+ virtual bool StreamingFetch(const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* response_writer,
+ MessageHandler* message_handler,
+ Callback* callback);
+
+ // Controls whether we will request and save gzipped content to the
+ // file system. Note that http_dump_url_fetcher will inflate on
+ // read if its caller does not want gzipped output.
+ void set_accept_gzip(bool x) { accept_gzip_ = x; }
+
+ private:
+ // Helper class to manage individual fetchs.
+ class Fetch;
+
+ HttpDumpUrlFetcher dump_fetcher_;
+ // Used to fetch urls that aren't in the dump yet.
+ UrlAsyncFetcher* base_fetcher_;
+ std::string root_dir_; // Root directory of the HTTP dumps.
+ FileSystem* file_system_;
+ bool accept_gzip_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpDumpUrlAsyncWriter);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_HTTP_DUMP_URL_ASYNC_WRITER_H_
diff --git a/trunk/src/net/instaweb/util/public/http_dump_url_fetcher.h b/trunk/src/net/instaweb/util/public/http_dump_url_fetcher.h
new file mode 100644
index 0000000..24c8c4b
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/http_dump_url_fetcher.h
@@ -0,0 +1,113 @@
+/**
+ * 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)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_HTTP_DUMP_URL_FETCHER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_HTTP_DUMP_URL_FETCHER_H_
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/util/public/file_system.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/url_fetcher.h"
+
+class GURL;
+
+namespace net_instaweb {
+
+class Timer;
+
+// TODO(sligocki): Can we forward declare these somehow?
+// class FileSystem;
+// class FileSystem::InputFile;
+
+// HttpDumpFetcher fetches raw HTTP dumps from the filesystem.
+// These dumps could be compressed or chunked, the fetcher does not
+// decompress or de-chunk them.
+class HttpDumpUrlFetcher : public UrlFetcher {
+ public:
+
+ // When the slurped data is gzipped, but request headers are made
+ // that don't include 'gzip' in an Accept-Encodings header, then
+ // this fetcher inflates the gzipped output as it streams. It
+ // also captures the original gzipped size in this attribute in
+ // the response headers.
+ static const char kGzipContentLengthAttribute[];
+
+ HttpDumpUrlFetcher(const StringPiece& root_dir, FileSystem* file_system,
+ Timer* timer);
+ virtual ~HttpDumpUrlFetcher();
+
+ // Converts URL into filename the way that Latency Lab does.
+ // Note: root_dir_ must be standardized to have a / at end already.
+ static bool GetFilenameFromUrl(const StringPiece& root_dir,
+ const GURL& url,
+ std::string* filename,
+ MessageHandler* message_handler);
+
+ // Non-static version that uses the fetcher's root dir.
+ bool GetFilename(const GURL& url,
+ std::string* filename,
+ MessageHandler* message_handler) {
+ return GetFilenameFromUrl(root_dir_, url, filename, message_handler);
+ }
+
+ // Converts URL into filename prefix the way that Latency Lab does.
+ // Note: root_dir_ must be standardized to have a / at end already.
+ static bool GetFilenamePrefixFromUrl(const StringPiece& root_dir,
+ const GURL& url,
+ std::string* filename,
+ MessageHandler* message_handler);
+
+ // This is a synchronous/blocking implementation.
+ virtual bool StreamingFetchUrl(const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* fetched_content_writer,
+ MessageHandler* message_handler);
+
+ // Parse file into response_headers and response_writer as if it were bytes
+ // off the wire.
+ bool ParseFile(FileSystem::InputFile* file,
+ MetaData* response_headers,
+ Writer* response_writer,
+ MessageHandler* handler);
+
+ // Helper function to return a generic error response.
+ void RespondError(MetaData* response_headers, Writer* response_writer,
+ MessageHandler* handler);
+
+ // Print URLs each time they are fetched.
+ void set_print_urls(bool on);
+
+ private:
+ std::string root_dir_; // Root directory of the HTTP dumps.
+ FileSystem* file_system_;
+ Timer* timer_;
+
+ // Response to use if something goes wrong.
+ std::string error_body_;
+
+ scoped_ptr<StringSet> urls_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpDumpUrlFetcher);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_HTTP_DUMP_URL_FETCHER_H_
diff --git a/trunk/src/net/instaweb/util/public/http_dump_url_writer.h b/trunk/src/net/instaweb/util/public/http_dump_url_writer.h
new file mode 100644
index 0000000..819e320
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/http_dump_url_writer.h
@@ -0,0 +1,74 @@
+/**
+ * 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)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_HTTP_DUMP_URL_WRITER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_HTTP_DUMP_URL_WRITER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/http_dump_url_fetcher.h"
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/url_fetcher.h"
+
+namespace net_instaweb {
+
+class FileSystem;
+
+// HttpDumpWriter checks to see whether the HTTP dump is available on the
+// filesystem. If not, it fetches it from another fetcher (e.g. one that
+// uses the network) and writes it to the filesystem so that HttpDumpFetcher
+// can find it.
+class HttpDumpUrlWriter : public UrlFetcher {
+ public:
+ HttpDumpUrlWriter(const StringPiece& root_dir, UrlFetcher* base_fetcher,
+ FileSystem* file_system, Timer* timer)
+ : dump_fetcher_(root_dir, file_system, timer),
+ base_fetcher_(base_fetcher),
+ file_system_(file_system),
+ accept_gzip_(true) {
+ root_dir.CopyToString(&root_dir_);
+ }
+ virtual ~HttpDumpUrlWriter();
+
+ // This is a synchronous/blocking implementation.
+ virtual bool StreamingFetchUrl(const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* response_writer,
+ MessageHandler* message_handler);
+
+ // Controls whether we will request and save gzipped content to the
+ // file system. Note that http_dump_url_fetcher will inflate on
+ // read if its caller does not want gzipped output.
+ void set_accept_gzip(bool x) { accept_gzip_ = x; }
+
+ // Print URLs each time they are fetched.
+ void set_print_urls(bool on) { dump_fetcher_.set_print_urls(on); }
+
+ private:
+ HttpDumpUrlFetcher dump_fetcher_;
+ UrlFetcher* base_fetcher_; // Used to fetch urls that aren't in the dump yet.
+ std::string root_dir_; // Root directory of the HTTP dumps.
+ FileSystem* file_system_;
+ bool accept_gzip_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpDumpUrlWriter);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_HTTP_DUMP_URL_WRITER_H_
diff --git a/trunk/src/net/instaweb/util/public/http_response_parser.h b/trunk/src/net/instaweb/util/public/http_response_parser.h
new file mode 100644
index 0000000..c8313ac
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/http_response_parser.h
@@ -0,0 +1,71 @@
+/**
+ * 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)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_HTTP_RESPONSE_PARSER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_HTTP_RESPONSE_PARSER_H_
+
+#include "base/basictypes.h"
+// TODO(sligocki): Find a way to forward declare FileSystem::InputFile.
+#include "net/instaweb/util/public/file_system.h"
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+class MessageHandler;
+class MetaData;
+class Writer;
+
+// Helper class to fascilitate parsing a raw streaming HTTP response including
+// headers and body.
+class HttpResponseParser {
+ public:
+ HttpResponseParser(MetaData* response_headers, Writer* writer,
+ MessageHandler* handler)
+ : reading_headers_(true),
+ ok_(true),
+ response_headers_(response_headers),
+ writer_(writer),
+ handler_(handler) {
+ }
+
+ // Parse complete HTTP response from a file.
+ bool ParseFile(FileSystem::InputFile* file);
+
+ // Parse complete HTTP response from a FILE stream.
+ // TODO(sligocki): We need a Readable abstraction (like Writer)
+ bool Parse(FILE* stream);
+
+ // Read a chunk of HTTP response, populating response_headers and call
+ // writer on output body, returning true if the status is ok.
+ bool ParseChunk(const StringPiece& data);
+
+ bool ok() const { return ok_; }
+
+ private:
+ bool reading_headers_;
+ bool ok_;
+ MetaData* response_headers_;
+ Writer* writer_;
+ MessageHandler* handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpResponseParser);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_HTTP_RESPONSE_PARSER_H_
diff --git a/trunk/src/net/instaweb/util/public/http_value.h b/trunk/src/net/instaweb/util/public/http_value.h
new file mode 100644
index 0000000..22679bb
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/http_value.h
@@ -0,0 +1,103 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_HTTP_VALUE_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_HTTP_VALUE_H_
+
+#include "net/instaweb/util/public/shared_string.h"
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/writer.h"
+
+namespace net_instaweb {
+
+class MetaData;
+
+// Provides shared, ref-counted, copy-on-write storage for HTTP
+// contents, to aid sharing between active fetches and filters, and
+// the cache, which from which data may be evicted at any time.
+class HTTPValue : public Writer {
+ public:
+ HTTPValue() { }
+
+ // Clears the value (both headers and content)
+ void Clear();
+
+ // Is this HTTPValue empty
+ bool Empty() const { return storage_->empty(); }
+
+ // Sets the HTTP headers for this value. This method may only
+ // be called once and must be called before or after all of the
+ // contents are set (using the streaming interface Write).
+ //
+ // If Clear() is called, then SetHeaders() can be called once again.
+ void SetHeaders(const MetaData& headers);
+
+ // Writes contents into the HTTPValue object. Write can be called
+ // multiple times to append more data, and can be called before
+ // or after SetHeaders. However, SetHeaders cannot be interleaved
+ // in between calls to Write.
+ virtual bool Write(const StringPiece& str, MessageHandler* handler);
+ virtual bool Flush(MessageHandler* handler);
+
+ // Retrieves the headers, returning false if empty.
+ bool ExtractHeaders(MetaData* headers, MessageHandler* handler) const;
+
+ // Retrieves the contents, returning false if empty. Note that the
+ // contents are only guaranteed valid as long as the HTTPValue
+ // object is in scope.
+ bool ExtractContents(StringPiece* str) const;
+
+ // Explicit support for copy-construction and assignment operators
+ HTTPValue(const HTTPValue& src) : storage_(src.storage_) { }
+ HTTPValue& operator=(const HTTPValue& src) {
+ if (&src != this) {
+ storage_ = src.storage_;
+ }
+ return *this;
+ }
+
+ // Tests whether this reference is the only active one to the string object.
+ bool unique() const { return storage_.unique(); }
+
+ // Assigns the storage of an HTTPValue based on the provided storage. This
+ // can be used for a cache Get. Returns false if the string is not
+ // well-formed.
+ //
+ // Extracts the headers into the provided MetaData buffer.
+ bool Link(SharedString* src, MetaData* headers, MessageHandler* handler);
+
+ // Access the shared string, for insertion into a cache via Put.
+ SharedString* share() { return &storage_; }
+
+ size_t size() const { return storage_->size(); }
+
+ private:
+ char type_identifier() const { return (*storage_.get())[0]; }
+ unsigned int SizeOfFirstChunk() const;
+ void SetSizeOfFirstChunk(unsigned int size);
+
+ // Disconnects this HTTPValue from other HTTPValues that may share the
+ // underlying storage, allowing a new buffer.
+ void CopyOnWrite();
+
+ SharedString storage_;
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_HTTP_VALUE_H_
diff --git a/trunk/src/net/instaweb/util/public/lru_cache.h b/trunk/src/net/instaweb/util/public/lru_cache.h
new file mode 100644
index 0000000..94aa359
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/lru_cache.h
@@ -0,0 +1,110 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_LRU_CACHE_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_LRU_CACHE_H_
+
+#include <list>
+#include <map>
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/cache_interface.h"
+#include <string>
+
+namespace net_instaweb {
+
+// Simple C++ implementation of an in-memory least-recently used (LRU)
+// cache. This implementation is not thread-safe, and must be
+// combined with a mutex to make it so.
+//
+// The purpose of this implementation is as a default implementation,
+// or an local shadow for memcached.
+//
+// Also of note: the Get interface allows for streaming. To get into
+// a std::string, use a StringWriter.
+//
+// TODO(jmarantz): The Put interface does not currently stream, but this
+// should be added.
+class LRUCache : public CacheInterface {
+ public:
+ explicit LRUCache(size_t max_size)
+ : max_bytes_in_cache_(max_size),
+ current_bytes_in_cache_(0) {
+ ClearStats();
+ }
+ virtual ~LRUCache();
+
+ virtual bool Get(const std::string& key, SharedString* value);
+
+ // Puts an object into the cache, sharing the bytes.
+ //
+ // TODO(jmarantz): currently if the caller mutates the
+ // SharedString after having called Put, it will actually
+ // modify the value in the cache. We should change
+ // SharedString to Copy-On-Write semantics.
+ virtual void Put(const std::string& key, SharedString* new_value);
+ virtual void Delete(const std::string& key);
+ virtual KeyState Query(const std::string& key);
+
+ // Total size in bytes of keys and values stored.
+ size_t size_bytes() const { return current_bytes_in_cache_; }
+
+ // Number of elements stored
+ size_t num_elements() const { return map_.size(); }
+
+ size_t num_evictions() const { return num_evictions_; }
+ size_t num_hits() const { return num_hits_; }
+ size_t num_misses() const { return num_misses_; }
+ size_t num_inserts() const { return num_inserts_; }
+ size_t num_deletes() const { return num_deletes_; }
+
+ // Sanity check the cache data structures.
+ void SanityCheck();
+
+ // Clear the entire cache. Used primarily for testing. Note that this
+ // will not clear the stats, however it will update current_bytes_in_cache_.
+ void Clear();
+
+ // Clear the stats -- note that this will not clear the content.
+ void ClearStats();
+
+ private:
+ typedef std::pair<const std::string*, SharedString> KeyValuePair;
+ typedef std::list<KeyValuePair*> EntryList;
+ // STL guarantees lifetime of list itererators as long as the node is in list.
+ typedef EntryList::iterator ListNode;
+ typedef std::map<std::string, ListNode> Map;
+ inline int entry_size(KeyValuePair* kvp) const;
+ inline ListNode Freshen(KeyValuePair* key_value);
+ bool EvictIfNecessary(size_t bytes_needed);
+
+ size_t max_bytes_in_cache_;
+ size_t current_bytes_in_cache_;
+ size_t num_evictions_;
+ size_t num_hits_;
+ size_t num_misses_;
+ size_t num_inserts_;
+ size_t num_deletes_;
+ EntryList lru_ordered_list_;
+ Map map_;
+
+ DISALLOW_COPY_AND_ASSIGN(LRUCache);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_LRU_CACHE_H_
diff --git a/trunk/src/net/instaweb/util/public/md5_hasher.h b/trunk/src/net/instaweb/util/public/md5_hasher.h
new file mode 100644
index 0000000..48c660c
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/md5_hasher.h
@@ -0,0 +1,51 @@
+// 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.
+//
+// Authors: sligocki@google.com (Shawn Ligocki),
+// lsong@google.com (Libo Song)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_MD5_HASHER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_MD5_HASHER_H_
+
+#include <algorithm> // for std::min
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/hasher.h"
+
+namespace net_instaweb {
+
+class MD5Hasher : public Hasher {
+ public:
+ static const int kMaxHashSize;
+ static const int kDefaultHashSize = 10;
+
+ MD5Hasher() : hash_size_(kDefaultHashSize) {}
+ MD5Hasher(int hash_size) {
+ CHECK(hash_size >= 0);
+ hash_size_ = std::min(hash_size, kMaxHashSize);
+ }
+ virtual ~MD5Hasher();
+
+ virtual std::string Hash(const StringPiece& content) const;
+
+ virtual int HashSizeInChars() const { return hash_size_; }
+
+ private:
+ int hash_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(MD5Hasher);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_MD5_HASHER_H_
diff --git a/trunk/src/net/instaweb/util/public/mem_file_system.h b/trunk/src/net/instaweb/util/public/mem_file_system.h
new file mode 100644
index 0000000..fbc2cf2
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/mem_file_system.h
@@ -0,0 +1,101 @@
+/**
+ * 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: abliss@google.com (Adam Bliss)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_MEM_FILE_SYSTEM_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_MEM_FILE_SYSTEM_H_
+
+#include <stdlib.h>
+#include <map>
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/file_system.h"
+#include "net/instaweb/util/public/mock_timer.h"
+
+namespace net_instaweb {
+
+// An in-memory implementation of the FileSystem interface, for use in
+// unit tests. Does not fully support directories. Not particularly efficient.
+// Not threadsafe.
+// TODO(abliss): add an ability to block writes for arbitrarily long, to
+// enable testing resilience to concurrency problems with real filesystems.
+class MemFileSystem : public FileSystem {
+ public:
+ MemFileSystem() : enabled_(true), timer_(0), temp_file_index_(0) {}
+ virtual ~MemFileSystem();
+
+ // We offer a "simulated atime" in which the clock ticks forward one
+ // second every time you read or write a file.
+ virtual bool Atime(const StringPiece& path, int64* timestamp_sec,
+ MessageHandler* handler);
+ virtual BoolOrError Exists(const char* path, MessageHandler* handler);
+ virtual BoolOrError IsDir(const char* path, MessageHandler* handler);
+ virtual bool ListContents(const StringPiece& dir, StringVector* files,
+ MessageHandler* handler);
+ virtual bool MakeDir(const char* directory_path, MessageHandler* handler);
+ virtual InputFile* OpenInputFile(const char* filename,
+ MessageHandler* message_handler);
+ virtual OutputFile* OpenOutputFileHelper(const char* filename,
+ MessageHandler* message_handler);
+ virtual OutputFile* OpenTempFileHelper(const StringPiece& prefix_name,
+ MessageHandler* message_handle);
+ virtual bool RecursivelyMakeDir(const StringPiece& directory_path,
+ MessageHandler* handler);
+ virtual bool RemoveFile(const char* filename, MessageHandler* handler);
+ virtual bool RenameFileHelper(const char* old_file, const char* new_file,
+ MessageHandler* handler);
+ virtual bool Size(const StringPiece& path, int64* size,
+ MessageHandler* handler);
+ virtual BoolOrError TryLock(const StringPiece& lock_name,
+ MessageHandler* handler);
+ virtual BoolOrError TryLockWithTimeout(const StringPiece& lock_name,
+ int64 timeout_ms,
+ MessageHandler* handler);
+ virtual bool Unlock(const StringPiece& lock_name,
+ MessageHandler* handler);
+
+ // Empties out the entire filesystem. Should not be called while files
+ // are open.
+ void Clear();
+
+ // Test-specific functionality to disable and re-enable the filesystem.
+ void Disable() { enabled_ = false; }
+ void Enable() { enabled_ = true; }
+
+ // Accessor for timer. Timer is owned by mem_file_system.
+ MockTimer* timer() { return &timer_; }
+
+ private:
+ inline int64 CurrentTimeAndAdvance();
+ bool enabled_; // When disabled, OpenInputFile returns NULL.
+ typedef std::map<std::string, std::string> StringMap;
+ StringMap string_map_;
+ MockTimer timer_;
+ // atime_map_ holds times (in s) that files were last opened/modified. Each
+ // time we do such an operation, timer() advances by 1s (so all ATimes are
+ // distinct).
+ std::map<std::string, int64> atime_map_;
+ int temp_file_index_;
+ // lock_map_ holds times that locks were established (in ms).
+ // locking and unlocking don't advance time.
+ std::map<std::string, int64> lock_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(MemFileSystem);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_MEM_FILE_SYSTEM_H_
diff --git a/trunk/src/net/instaweb/util/public/message_handler.h b/trunk/src/net/instaweb/util/public/message_handler.h
new file mode 100644
index 0000000..580e57d
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/message_handler.h
@@ -0,0 +1,103 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_MESSAGE_HANDLER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_MESSAGE_HANDLER_H_
+
+#include <stdarg.h>
+
+#include "net/instaweb/util/public/printf_format.h"
+
+namespace net_instaweb {
+
+enum MessageType {
+ kInfo,
+ kWarning,
+ kError,
+ kFatal
+};
+
+class MessageHandler {
+ public:
+ MessageHandler();
+ virtual ~MessageHandler();
+
+ // String representation for MessageType.
+ const char* MessageTypeToString(const MessageType type) const;
+
+ // Specify the minimum message type. Lower message types will not be
+ // logged.
+ void set_min_message_type(MessageType min) { min_message_type_ = min; }
+
+ // Log an info, warning, error or fatal error message.
+ void Message(MessageType type, const char* msg, ...)
+ INSTAWEB_PRINTF_FORMAT(3, 4);
+ void MessageV(MessageType type, const char* msg, va_list args);
+
+ // Log a message with a filename and line number attached.
+ void FileMessage(MessageType type, const char* filename, int line,
+ const char* msg, ...) INSTAWEB_PRINTF_FORMAT(5, 6);
+ void FileMessageV(MessageType type, const char* filename, int line,
+ const char* msg, va_list args);
+
+
+ // Conditional errors.
+ void Check(bool condition, const char* msg, ...) INSTAWEB_PRINTF_FORMAT(3, 4);
+ void CheckV(bool condition, const char* msg, va_list args);
+
+
+ // Convenience functions for FileMessage for backwards compatibility.
+ // TODO(sligocki): Rename these to InfoAt, ... so that Info, ... can be used
+ // for general Messages.
+ void Info(const char* filename, int line, const char* msg, ...)
+ INSTAWEB_PRINTF_FORMAT(4, 5);
+ void Warning(const char* filename, int line, const char* msg, ...)
+ INSTAWEB_PRINTF_FORMAT(4, 5);
+ void Error(const char* filename, int line, const char* msg, ...)
+ INSTAWEB_PRINTF_FORMAT(4, 5);
+ void FatalError(const char* filename, int line, const char* msg, ...)
+ INSTAWEB_PRINTF_FORMAT(4, 5);
+
+ void InfoV(const char* filename, int line, const char* msg, va_list args) {
+ FileMessageV(kInfo, filename, line, msg, args);
+ }
+ void WarningV(const char* filename, int line, const char* msg, va_list a) {
+ FileMessageV(kWarning, filename, line, msg, a);
+ }
+ void ErrorV(const char* filename, int line, const char* msg, va_list args) {
+ FileMessageV(kError, filename, line, msg, args);
+ }
+ void FatalErrorV(const char* fname, int line, const char* msg, va_list a) {
+ FileMessageV(kFatal, fname, line, msg, a);
+ }
+
+ protected:
+ virtual void MessageVImpl(MessageType type, const char* msg,
+ va_list args) = 0;
+ virtual void FileMessageVImpl(MessageType type, const char* filename,
+ int line, const char* msg, va_list args) = 0;
+
+ private:
+ // The minimum message type to log at. Any messages below this level
+ // will not be logged.
+ MessageType min_message_type_;
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_MESSAGE_HANDLER_H_
diff --git a/trunk/src/net/instaweb/util/public/meta_data.h b/trunk/src/net/instaweb/util/public/meta_data.h
new file mode 100644
index 0000000..edf4e97
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/meta_data.h
@@ -0,0 +1,232 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+//
+// Meta-data associated with a rewriting resource. This is
+// primarily a key-value store, but additionally we want to
+// get easy access to the cache expiration time.
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_META_DATA_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_META_DATA_H_
+
+#include <vector>
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+class MessageHandler;
+class Writer;
+
+// Global constants for common HTML attribues names and values.
+//
+// TODO(jmarantz): proactively change all the occurences of the static strings
+// to use these shared constants.
+struct HttpAttributes {
+ static const char kAcceptEncoding[];
+ static const char kCacheControl[];
+ static const char kContentEncoding[];
+ static const char kContentLength[];
+ static const char kContentType[];
+ static const char kDate[];
+ static const char kDeflate[];
+ static const char kEtag[];
+ static const char kExpires[];
+ static const char kGzip[];
+ static const char kHost[];
+ static const char kIfModifiedSince[];
+ static const char kLastModified[];
+ static const char kLocation[];
+ static const char kNoCache[];
+ static const char kReferer[]; // sic
+ static const char kServer[];
+ static const char kSetCookie[];
+ static const char kTransferEncoding[];
+ static const char kUserAgent[];
+ static const char kVary[];
+};
+
+namespace HttpStatus {
+// Http status codes.
+// Grokked from http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
+enum Code {
+ kContinue = 100,
+ kSwitchingProtocols = 101,
+
+ kOK = 200,
+ kCreated = 201,
+ kAccepted = 202,
+ kNonAuthoritative = 203,
+ kNoContent = 204,
+ kResetContent = 205,
+ kPartialContent = 206,
+
+ kMultipleChoices = 300,
+ kMovedPermanently = 301,
+ kFound = 302,
+ kSeeOther = 303,
+ kNotModified = 304,
+ kUseProxy = 305,
+ kSwitchProxy = 306, // In old spec; no longer used.
+ kTemporaryRedirect = 307,
+
+ kBadRequest = 400,
+ kUnauthorized = 401,
+ kPaymentRequired = 402,
+ kForbidden = 403,
+ kNotFound = 404,
+ kMethodNotAllowed = 405,
+ kNotAcceptable = 406,
+ kProxyAuthRequired = 407,
+ kRequestTimeout = 408,
+ kConflict = 409,
+ kGone = 410,
+ kLengthRequired = 411,
+ kPreconditionFailed = 412,
+ kEntityTooLarge = 413,
+ kUriTooLong = 414,
+ kUnsupportedMediaType = 415,
+ kRangeNotSatisfiable = 416,
+ kExpectationFailed = 417,
+
+ kInternalServerError = 500,
+ kNotImplemented = 501,
+ kBadGateway = 502,
+ kUnavailable = 503,
+ kGatewayTimeout = 504,
+ kHttpVersionNotSupported = 505,
+
+ // Instaweb-specific response codes: these are intentionally chosen to be
+ // outside the normal HTTP range, but we consider these response codes
+ // to be 'cacheable' in our own cache.
+ kRememberNotFoundStatusCode = 10001,
+};
+
+// Transform a status code into the equivalent reason phrase.
+const char* GetReasonPhrase(Code rc);
+
+} // namespace HttpStatus
+
+// Container for required meta-data. General HTTP headers can be added
+// here as name/value pairs, and caching information can then be derived.
+//
+// TODO(jmarantz): consider rename to HTTPHeader.
+// TODO(sligocki): This represents an HTTP response header. We need a request
+// header class as well.
+// TODO(sligocki): Stop using char* in interface.
+class MetaData {
+ public:
+ MetaData() {}
+ virtual ~MetaData();
+
+ void CopyFrom(const MetaData& other);
+
+ // Reset headers to initial state.
+ virtual void Clear() = 0;
+
+ // Raw access for random access to attribute name/value pairs.
+ virtual int NumAttributes() const = 0;
+ virtual const char* Name(int index) const = 0;
+ virtual const char* Value(int index) const = 0;
+
+ // Get the attribute values associated with this name. Returns
+ // false if the attribute is not found. If it was found, then
+ // the values vector is filled in.
+ virtual bool Lookup(const char* name, CharStarVector* values) const = 0;
+
+ // Add a new header.
+ virtual void Add(const StringPiece& name, const StringPiece& value) = 0;
+
+ // Remove all headers by name.
+ virtual void RemoveAll(const char* name) = 0;
+
+ // Serialize HTTP response header to a stream.
+ virtual bool Write(Writer* writer, MessageHandler* handler) const = 0;
+ // Serialize just the headers (not the version and response code line).
+ virtual bool WriteHeaders(Writer* writer, MessageHandler* handler) const = 0;
+
+ // Parse a chunk of HTTP response header. Returns number of bytes consumed.
+ virtual int ParseChunk(const StringPiece& text, MessageHandler* handler) = 0;
+
+ // Compute caching information. The current time is used to compute
+ // the absolute time when a cache resource will expire. The timestamp
+ // is in milliseconds since 1970. It is an error to call any of the
+ // accessors before ComputeCaching is called.
+ virtual void ComputeCaching() = 0;
+ virtual bool IsCacheable() const = 0;
+ virtual bool IsProxyCacheable() const = 0;
+ virtual int64 CacheExpirationTimeMs() const = 0;
+ virtual void SetDate(int64 date_ms) = 0;
+ virtual void SetLastModified(int64 last_modified_ms) = 0;
+
+ virtual bool headers_complete() const = 0;
+ virtual void set_headers_complete(bool x) = 0;
+
+ virtual int major_version() const = 0;
+ virtual int minor_version() const = 0;
+ virtual int status_code() const = 0;
+ virtual const char* reason_phrase() const = 0;
+ virtual int64 timestamp_ms() const = 0;
+ virtual bool has_timestamp_ms() const = 0;
+
+ virtual void set_major_version(int major_version) = 0;
+ virtual void set_minor_version(int minor_version) = 0;
+
+ // Sets the status code and reason_phrase based on an internal table.
+ void SetStatusAndReason(HttpStatus::Code code);
+
+ virtual void set_status_code(int status_code) = 0;
+ virtual void set_reason_phrase(const StringPiece& reason_phrase) = 0;
+ // Set whole first line.
+ virtual void set_first_line(int major_version,
+ int minor_version,
+ int status_code,
+ const StringPiece& reason_phrase) {
+ set_major_version(major_version);
+ set_minor_version(minor_version);
+ set_status_code(status_code);
+ set_reason_phrase(reason_phrase);
+ }
+
+ virtual std::string ToString() const = 0;
+ void DebugPrint() const;
+
+ // Parses an arbitrary string into milliseconds since 1970
+ static bool ParseTime(const char* time_str, int64* time_ms);
+
+ // Determines whether a response header is marked as gzipped.
+ bool IsGzipped() const;
+
+ // Determines whether a request header accepts gzipped content.
+ bool AcceptsGzip() const;
+
+ // Parses a date header such as HttpAttributes::kDate or
+ // HttpAttributes::kExpires, returning the timestamp as
+ // number of milliseconds since 1970.
+ bool ParseDateHeader(const char* attr, int64* date_ms) const;
+
+ // Updates a date header using time specified as a number of milliseconds
+ // since 1970.
+ void UpdateDateHeader(const char* attr, int64 date_ms);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MetaData);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_META_DATA_H_
diff --git a/trunk/src/net/instaweb/util/public/mock_hasher.h b/trunk/src/net/instaweb/util/public/mock_hasher.h
new file mode 100644
index 0000000..440053f
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/mock_hasher.h
@@ -0,0 +1,41 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_MOCK_HASHER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_MOCK_HASHER_H_
+
+#include "net/instaweb/util/public/hasher.h"
+#include <string>
+
+namespace net_instaweb {
+
+class MockHasher : public Hasher {
+ public:
+ MockHasher() {}
+ virtual ~MockHasher();
+
+ virtual std::string Hash(const StringPiece& content) const { return "0"; }
+ virtual int HashSizeInChars() const { return 1; }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockHasher);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_MOCK_HASHER_H_
diff --git a/trunk/src/net/instaweb/util/public/mock_message_handler.h b/trunk/src/net/instaweb/util/public/mock_message_handler.h
new file mode 100644
index 0000000..60c0160
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/mock_message_handler.h
@@ -0,0 +1,58 @@
+/**
+ * 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: morlovich@google.com (Maksim Orlovich)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_MOCK_MESSAGE_HANDLER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_MOCK_MESSAGE_HANDLER_H_
+
+#include <map>
+
+#include "net/instaweb/util/public/google_message_handler.h"
+
+namespace net_instaweb {
+
+// A version of GoogleMessageHandler to use in testcases that keeps
+// track of number of messages output, to validate diagnostics
+class MockMessageHandler : public GoogleMessageHandler {
+ public:
+ MockMessageHandler() {}
+ virtual ~MockMessageHandler();
+
+ // Returns number of messages of given type issued
+ int MessagesOfType(MessageType type) const;
+
+ // Returns total number of messages issued
+ int TotalMessages() const;
+
+ // Returns number of messages of severity higher than info
+ int SeriousMessages() const;
+
+ protected:
+ virtual void MessageVImpl(MessageType type, const char* msg, va_list args);
+
+ virtual void FileMessageVImpl(MessageType type, const char* filename,
+ int line, const char* msg, va_list args);
+
+ private:
+ typedef std::map<MessageType, int> MessageCountMap;
+ MessageCountMap message_counts_;
+ DISALLOW_COPY_AND_ASSIGN(MockMessageHandler);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_MOCK_HASHER_H_
diff --git a/trunk/src/net/instaweb/util/public/mock_timer.h b/trunk/src/net/instaweb/util/public/mock_timer.h
new file mode 100644
index 0000000..4bf093f
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/mock_timer.h
@@ -0,0 +1,53 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_MOCK_TIMER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_MOCK_TIMER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/timer.h"
+
+namespace net_instaweb {
+
+class MockTimer : public Timer {
+ public:
+ // A useful recent time-constant for testing.
+ static const int64 kApr_5_2010_ms;
+
+ explicit MockTimer(int64 time_ms) : time_us_(1000 * time_ms) {}
+ virtual ~MockTimer();
+
+ void set_time_us(int64 time_us) { time_us_ = time_us; }
+ void set_time_ms(int64 time_ms) { set_time_us(1000 * time_ms); }
+ void advance_us(int64 delta_us) { time_us_ += delta_us; }
+ void advance_ms(int64 delta_ms) { advance_us(1000 * delta_ms); }
+
+ // Returns number of microseconds since 1970.
+ virtual int64 NowUs() const { return time_us_; }
+ virtual void SleepUs(int64 us) { advance_us(us); }
+ virtual void SleepMs(int64 ms) { advance_us(1000 * ms); }
+
+ private:
+ int64 time_us_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockTimer);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_MOCK_TIMER_H_
diff --git a/trunk/src/net/instaweb/util/public/mock_url_fetcher.h b/trunk/src/net/instaweb/util/public/mock_url_fetcher.h
new file mode 100644
index 0000000..7335c25
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/mock_url_fetcher.h
@@ -0,0 +1,86 @@
+/**
+ * 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)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_MOCK_URL_FETCHER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_MOCK_URL_FETCHER_H_
+
+#include <map>
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/url_fetcher.h"
+
+namespace net_instaweb {
+
+// Simple UrlFetcher meant for tests, you can set responses for individual URLs.
+class MockUrlFetcher : public UrlFetcher {
+ public:
+ MockUrlFetcher() : enabled_(true), fail_on_unexpected_(true) {}
+ virtual ~MockUrlFetcher();
+
+ void SetResponse(const StringPiece& url, const MetaData& response_header,
+ const StringPiece& response_body);
+
+ // Fetching unset URLs will cause EXPECT failures as well as return false.
+ virtual bool StreamingFetchUrl(const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* response_writer,
+ MessageHandler* message_handler);
+
+ // Clear all set responses.
+ void Clear();
+
+ // When disabled, fetcher will fail (but not crash) for all requests.
+ // Use to simulate temporarily not having access to resources, for example.
+ void Disable() { enabled_ = false; }
+ void Enable() { enabled_ = true; }
+
+ // Set to false if you don't want the fetcher to EXPECT fail on unfound URL.
+ // Useful in MockUrlFetcher unittest :)
+ void set_fail_on_unexpected(bool x) { fail_on_unexpected_ = x; }
+
+ private:
+ class HttpResponse {
+ public:
+ HttpResponse(const MetaData& in_header, const StringPiece& in_body)
+ : body_(in_body.data(), in_body.size()) {
+ header_.CopyFrom(in_header);
+ }
+
+ const SimpleMetaData& header() const { return header_; }
+ const std::string& body() const { return body_; }
+
+ private:
+ SimpleMetaData header_;
+ std::string body_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpResponse);
+ };
+ typedef std::map<const std::string, const HttpResponse*> ResponseMap;
+
+ ResponseMap response_map_;
+ bool enabled_;
+ bool fail_on_unexpected_; // Should we EXPECT if unexpected url called?
+
+ DISALLOW_COPY_AND_ASSIGN(MockUrlFetcher);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_MOCK_URL_FETCHER_H_
diff --git a/trunk/src/net/instaweb/util/public/named_lock_manager.h b/trunk/src/net/instaweb/util/public/named_lock_manager.h
new file mode 100644
index 0000000..c33f529
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/named_lock_manager.h
@@ -0,0 +1,62 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_NAMED_LOCK_MANAGER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_NAMED_LOCK_MANAGER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/abstract_mutex.h"
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+class AbstractLock : public AbstractMutex {
+ public:
+ virtual ~AbstractLock();
+ // If lock is held, return false, otherwise lock and return true
+ virtual bool TryLock() = 0;
+ // Wait bounded amount of time to take lock, otherwise return false.
+ virtual bool LockTimedWait(int64 wait_ms) = 0;
+
+ // ...StealOld versions of locking routines steal the lock if its current
+ // holder has locked it for more than timeout_ms. *WARNING* If you use
+ // any ...StealOld methods, your lock becomes "best-effort" and there may
+ // be multiple workers in a section! *WARNING*
+
+ // LockStealOld will block until the lock is unlocked or times out.
+ virtual void LockStealOld(int64 timeout_ms) = 0;
+ // Locks if lock is unlocked or held longer than timeout_ms.
+ virtual bool TryLockStealOld(int64 timeout_ms) = 0;
+ // LockTimedWaitStealOld will block until unlocked, the lock has been held for
+ // timeout_ms, or the caller has waited for wait_ms. Thus wait_ms is
+ // effectively bounded by timeout_ms.
+ virtual bool LockTimedWaitStealOld(int64 wait_ms, int64 timeout_ms) = 0;
+};
+
+// A named_lock_manager provides global locks named by strings (with the same
+// naming limitations in general as file names). They provide a fairly rich
+// api, with blocking and try versions and various timeout / steal behaviors.
+class NamedLockManager {
+ public:
+ virtual ~NamedLockManager();
+ virtual AbstractLock* CreateNamedLock(const StringPiece& name) = 0;
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_NAMED_LOCK_MANAGER_H_
diff --git a/trunk/src/net/instaweb/util/public/null_message_handler.h b/trunk/src/net/instaweb/util/public/null_message_handler.h
new file mode 100644
index 0000000..68c1e6e
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/null_message_handler.h
@@ -0,0 +1,45 @@
+/**
+ * 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: lsong@google.com (Libo Song)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_NULL_MESSAGE_HANDLER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_NULL_MESSAGE_HANDLER_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/message_handler.h"
+
+namespace net_instaweb {
+
+// Implementation of a message handler that does nothing.
+class NullMessageHandler : public MessageHandler {
+ public:
+ NullMessageHandler() {}
+ virtual ~NullMessageHandler();
+
+ protected:
+ virtual void MessageVImpl(MessageType type, const char* msg, va_list args);
+
+ virtual void FileMessageVImpl(MessageType type, const char* filename,
+ int line, const char* msg, va_list args);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NullMessageHandler);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_NULL_MESSAGE_HANDLER_H_
diff --git a/trunk/src/net/instaweb/util/public/null_writer.h b/trunk/src/net/instaweb/util/public/null_writer.h
new file mode 100644
index 0000000..7c11c82
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/null_writer.h
@@ -0,0 +1,41 @@
+/*
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_NULL_WRITER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_NULL_WRITER_H_
+
+#include "net/instaweb/util/public/writer.h"
+
+namespace net_instaweb {
+
+// A writer that silently eats the bytes. This can be used, for
+// example, with writers is designed to cascade to another one, such
+// as CountingWriter. If you just want to count the bytes and don't
+// want to store them, you can pass a NullWriter to a CountingWriter's
+// constructor.
+class NullWriter : public Writer {
+ public:
+ explicit NullWriter() { }
+ virtual ~NullWriter();
+ virtual bool Write(const StringPiece& str, MessageHandler* handler);
+ virtual bool Flush(MessageHandler* handler);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_NULL_WRITER_H_
diff --git a/trunk/src/net/instaweb/util/public/printf_format.h b/trunk/src/net/instaweb/util/public/printf_format.h
new file mode 100644
index 0000000..d1c350b
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/printf_format.h
@@ -0,0 +1,38 @@
+/**
+ * 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: bmcquade@google.com (Bryan McQuade)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_PRINTF_FORMAT_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_PRINTF_FORMAT_H_
+
+#ifdef __GNUC__
+
+// Tell the compiler a function is using a printf-style format string.
+// |format_param| is the one-based index of the format string parameter;
+// |dots_param| is the one-based index of the "..." parameter.
+// For v*printf functions (which take a va_list), pass 0 for dots_param.
+// (This is undocumented but matches what the system C headers do.)
+#define INSTAWEB_PRINTF_FORMAT(format_param, dots_param) \
+ __attribute__((format(printf, format_param, dots_param)))
+
+#else // Not GCC
+
+#define INSTAWEB_PRINTF_FORMAT(x, y)
+
+#endif
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_PRINTF_FORMAT_H_
diff --git a/trunk/src/net/instaweb/util/public/pthread_mutex.h b/trunk/src/net/instaweb/util/public/pthread_mutex.h
new file mode 100644
index 0000000..3825474
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/pthread_mutex.h
@@ -0,0 +1,43 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_PTHREAD_MUTEX_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_PTHREAD_MUTEX_H_
+
+#include <pthread.h>
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/abstract_mutex.h"
+
+namespace net_instaweb {
+
+// Implementation of AbstractMutex for Pthread mutexes.
+class PthreadMutex : public AbstractMutex {
+ public:
+ PthreadMutex();
+ virtual ~PthreadMutex();
+ virtual void Lock();
+ virtual void Unlock();
+ private:
+ pthread_mutex_t mutex_;
+
+ DISALLOW_COPY_AND_ASSIGN(PthreadMutex);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_PTHREAD_MUTEX_H_
diff --git a/trunk/src/net/instaweb/util/public/query_params.h b/trunk/src/net/instaweb/util/public/query_params.h
new file mode 100644
index 0000000..87a0c33
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/query_params.h
@@ -0,0 +1,85 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_QUERY_PARAMS_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_QUERY_PARAMS_H_
+
+#include <map>
+#include <vector>
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+// Parses and rewrites URL query parameters.
+class QueryParams {
+ public:
+ QueryParams() {}
+ ~QueryParams() { Clear(); }
+
+ // Parse a query param string, e.g. x=0&y=1&z=2. We expect the "?"
+ // to be extracted (e.g. this string is the output of GURL::query().
+ void Parse(const StringPiece& query_string);
+
+ // Find the values associated with a variable. Note that you may
+ // specify a variable multiple times in a query-string, e.g.
+ // a=0&a=1&a=2
+ bool Lookup(const char* name, CharStarVector* values) const;
+
+ // Remove all variables by name.
+ void RemoveAll(const char* name);
+
+ // Remove all variables.
+ void Clear();
+
+ // Raw access for random access to variable name/value pairs.
+ int size() const { return variable_vector_.size(); }
+ const char* name(int index) const { return variable_vector_[index].first; }
+
+ // Note that the value can be NULL, indicating that the variables
+ // was not followed by a '='. So given "a=0&b&c=", the values will
+ // be {"0", NULL, ""}.
+ const char* value(int index) const { return variable_vector_[index].second; }
+
+ std::string ToString() const;
+
+ // Add a new variable.
+ void Add(const StringPiece& name, const StringPiece& value);
+
+ private:
+ // We are keeping two structures, conceptually map<String,vector<String>> and
+ // vector<pair<String,String>>, so we can do associative lookups and
+ // also order-preserving iteration and random access.
+ //
+ // To avoid duplicating the strings, we will have the map own the
+ // Names (keys) in a std::string, and the string-pair-vector own the
+ // value as an explicitly newed char*. The risk of using a std::string
+ // to hold the value is that the pointers will not survive a resize.
+ typedef std::pair<const char*, char*> StringPair; // owns the value
+ typedef std::map<std::string, CharStarVector> VariableMap;
+ typedef std::vector<StringPair> VariableVector;
+
+ VariableMap variable_map_;
+ VariableVector variable_vector_;
+
+ DISALLOW_COPY_AND_ASSIGN(QueryParams);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_QUERY_PARAMS_H_
diff --git a/trunk/src/net/instaweb/util/public/rolling_hash.h b/trunk/src/net/instaweb/util/public/rolling_hash.h
new file mode 100644
index 0000000..c245ba3
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/rolling_hash.h
@@ -0,0 +1,69 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_CYCLIC_HASH_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_CYCLIC_HASH_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+// Rolling hash for char buffers based on a polynomial lookup table.
+// See http://en.wikipedia.org/wiki/Rolling_hash
+
+// Per character hash values. Exported for use in NextRollingHash.
+extern const uint64 kRollingHashCharTable[256];
+
+// Compute the rolling hash of buf[start : start + n - 1]
+uint64 RollingHash(const char* buf, size_t start, size_t n);
+
+// Given the rolling hash prev of buf[start - 1 : start + n - 2], efficiently
+// compute the hash of buf[start : start + n - 1]. Note that this indexes
+// buf[start - 1], so we can't just use a StringPiece here. We eschew
+// StringPiece in any case, because of efficiency.
+//
+// Note that to get efficient operation here for fixed n (eg when we're doing
+// something like Rabin-Karp string matching), we must inline the computation of
+// shift amounts and then hoist them as loop invariants. That is why this
+// function (intended for use in an inner loop) is inlined.
+inline uint64 NextRollingHash(
+ const char* buf, size_t start, size_t n, uint64 prev) {
+ // In a reasonable loop, the following two tests should be eliminated based on
+ // contextual information, if our compiler is optimizing enough.
+ CHECK_LT(static_cast<size_t>(0), start);
+ uint64 start_hash =
+ kRollingHashCharTable[static_cast<uint8>(buf[start - 1])];
+ uint64 end_hash =
+ kRollingHashCharTable[static_cast<uint8>(buf[start - 1 + n])];
+ uint64 prev_rot1 = (prev << 1) | (prev >> 63); // rotate left 1
+ uint64 start_hash_rotn;
+ // Corner case: shift by >= 64 bits is not defined in C. gcc had better
+ // constant-fold this to a rotate! (It appears to.) We inline in large part
+ // to ensure the truthiness of this fact.
+ size_t shift = n % 64;
+ if (shift == 0) {
+ start_hash_rotn = start_hash;
+ } else {
+ // rotate left by shift (equiv to rotating left n times).
+ start_hash_rotn = (start_hash << shift) | (start_hash >> (64 - shift));
+ }
+ return (start_hash_rotn ^ prev_rot1 ^ end_hash);
+}
+} // net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_ROLLING_HASH_H_
diff --git a/trunk/src/net/instaweb/util/public/shared_string.h b/trunk/src/net/instaweb/util/public/shared_string.h
new file mode 100644
index 0000000..9eb41fb
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/shared_string.h
@@ -0,0 +1,77 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+//
+// Implements a ref-counted string class, with full sharing. This
+// class does *not* implement copy-on-write semantics, however, it
+// does support a unique() method for determining, prior to writing,
+// whether other references exist. Thus it is feasible to implement
+// copy-on-write as a layer over this class.
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_SHARED_STRING_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_SHARED_STRING_H_
+
+#include "base/basictypes.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+#include "base/ref_counted.h"
+
+namespace net_instaweb {
+
+
+class RefCountedString : public base::RefCountedThreadSafe<RefCountedString> {
+ public:
+ RefCountedString() { }
+ explicit RefCountedString(const StringPiece& str)
+ : string_(str.data(), str.size()) {
+ }
+ const std::string& value() const { return string_; }
+ std::string& value() { return string_; }
+ size_t size() const { return string_.size(); }
+ const char* data() const { return string_.data(); }
+
+ private:
+ friend class base::RefCountedThreadSafe<RefCountedString>;
+ ~RefCountedString() { }
+
+ std::string string_;
+
+ DISALLOW_COPY_AND_ASSIGN(RefCountedString);
+};
+
+class SharedString : public scoped_refptr<RefCountedString> {
+ public:
+ SharedString() : scoped_refptr<RefCountedString>(new RefCountedString) {
+ }
+
+ explicit SharedString(const StringPiece& str)
+ : scoped_refptr<RefCountedString>(new RefCountedString(str)) {
+ }
+ std::string& operator*() { return ptr_->value(); }
+ std::string* get() { return &(ptr_->value()); }
+ const std::string* get() const { return &(ptr_->value()); }
+ std::string* operator->() { return &(ptr_->value()); }
+ const std::string* operator->() const { return &(ptr_->value()); }
+ bool unique() const { return ptr_->HasOneRef(); }
+ std::string::size_type size() const { return ptr_->value().size(); }
+};
+
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_SHARED_STRING_H_
diff --git a/trunk/src/net/instaweb/util/public/simple_meta_data.h b/trunk/src/net/instaweb/util/public/simple_meta_data.h
new file mode 100644
index 0000000..0909fa8
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/simple_meta_data.h
@@ -0,0 +1,140 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_SIMPLE_META_DATA_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_SIMPLE_META_DATA_H_
+
+#include <stdlib.h>
+#include <map>
+#include <vector>
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/meta_data.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+// Very basic implementation of HTTP headers.
+//
+// TODO(jmarantz): implement caching rules properly.
+class SimpleMetaData : public MetaData {
+ public:
+ SimpleMetaData();
+ virtual ~SimpleMetaData();
+
+ virtual void Clear();
+
+ // Raw access for random access to attribute name/value pairs.
+ virtual int NumAttributes() const;
+ virtual const char* Name(int index) const;
+ virtual const char* Value(int index) const;
+ virtual bool Lookup(const char* name, CharStarVector* values) const;
+
+ // Add a new header.
+ virtual void Add(const StringPiece& name, const StringPiece& value);
+
+ // Remove all headers by name.
+ virtual void RemoveAll(const char* name);
+
+ // Serialize HTTP response header to a stream.
+ virtual bool Write(Writer* writer, MessageHandler* message_handler) const;
+ // Serialize just the headers (not the version and response code line).
+ virtual bool WriteHeaders(Writer* writer, MessageHandler* handler) const;
+
+ // Parse a chunk of HTTP response header. Returns number of bytes consumed.
+ virtual int ParseChunk(const StringPiece& text, MessageHandler* handler);
+
+ // Compute caching information. The current time is used to compute
+ // the absolute time when a cache resource will expire. The timestamp
+ // is in milliseconds since 1970. It is an error to call any of the
+ // accessors before ComputeCaching is called.
+ virtual void ComputeCaching();
+ virtual bool IsCacheable() const;
+ virtual bool IsProxyCacheable() const;
+ virtual int64 CacheExpirationTimeMs() const;
+ virtual void SetDate(int64 date_ms);
+ virtual void SetLastModified(int64 last_modified_ms);
+
+ virtual bool headers_complete() const { return headers_complete_; }
+ virtual void set_headers_complete(bool x) { headers_complete_ = x; }
+
+ virtual int major_version() const { return major_version_; }
+ virtual int minor_version() const { return minor_version_; }
+ virtual int status_code() const { return status_code_; }
+ virtual const char* reason_phrase() const {
+ return reason_phrase_.c_str();
+ }
+ virtual int64 timestamp_ms() const { return timestamp_ms_; }
+ virtual bool has_timestamp_ms() const;
+
+ virtual void set_major_version(const int major_version) {
+ major_version_ = major_version;
+ }
+ virtual void set_minor_version(const int minor_version) {
+ minor_version_ = minor_version;
+ }
+ virtual void set_status_code(const int code) { status_code_ = code; }
+ virtual void set_reason_phrase(const StringPiece& reason_phrase) {
+ reason_phrase.CopyToString(&reason_phrase_);
+ }
+
+ virtual std::string ToString() const;
+
+ private:
+ bool GrabLastToken(const std::string& input, std::string* output);
+
+ friend class SimpleMetaDataTest;
+
+ // We are keeping two structures, conseptually map<String,vector<String>> and
+ // vector<pair<String,String>>, so we can do associative lookups and
+ // also order-preserving iteration and random access.
+ //
+ // To avoid duplicating the strings, we will have the map own the
+ // Names (keys) in a std::string, and the string-pair-vector own the
+ // value as an explicitly newed char*. The risk of using a std::string
+ // to hold the value is that the pointers will not survive a resize.
+ typedef std::pair<const char*, char*> StringPair; // owns the value
+ typedef std::map<std::string, CharStarVector,
+ StringCompareInsensitive> AttributeMap;
+ typedef std::vector<StringPair> AttributeVector;
+
+ AttributeMap attribute_map_;
+ AttributeVector attribute_vector_;
+
+ bool parsing_http_;
+ bool parsing_value_;
+ bool headers_complete_;
+ bool cache_fields_dirty_;
+ bool is_cacheable_; // accurate only if !cache_fields_dirty_
+ bool is_proxy_cacheable_; // accurate only if !cache_fields_dirty_
+ int64 expiration_time_ms_; // accurate only if !cache_fields_dirty_
+ int64 timestamp_ms_; // accurate only if !cache_fields_dirty_
+ std::string parse_name_;
+ std::string parse_value_;
+
+ int major_version_;
+ int minor_version_;
+ int status_code_;
+ std::string reason_phrase_;
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleMetaData);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_SIMPLE_META_DATA_H_
diff --git a/trunk/src/net/instaweb/util/public/simple_stats.h b/trunk/src/net/instaweb/util/public/simple_stats.h
new file mode 100644
index 0000000..48e6dfe
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/simple_stats.h
@@ -0,0 +1,57 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_SIMPLE_STATS_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_SIMPLE_STATS_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/statistics_template.h"
+
+namespace net_instaweb {
+
+class SimpleStats;
+class SimpleStatsVariable : public Variable {
+ public:
+ SimpleStatsVariable() : value_(0) {}
+ virtual ~SimpleStatsVariable();
+ virtual int Get() const { return value_; }
+ virtual void Set(int value) { value_ = value; }
+
+ private:
+ int value_;
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleStatsVariable);
+};
+
+// Simple name/value pair statistics implementation.
+class SimpleStats : public StatisticsTemplate<SimpleStatsVariable> {
+ public:
+ static const int kNotFound;
+
+ SimpleStats() { }
+ virtual ~SimpleStats();
+
+ protected:
+ virtual SimpleStatsVariable* NewVariable(const StringPiece& name, int index);
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleStats);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_SIMPLE_STATS_H_
diff --git a/trunk/src/net/instaweb/util/public/statistics.h b/trunk/src/net/instaweb/util/public/statistics.h
new file mode 100644
index 0000000..bd2a643
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/statistics.h
@@ -0,0 +1,60 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_STATISTICS_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_STATISTICS_H_
+
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+class Variable {
+ public:
+ virtual ~Variable();
+ // TODO(sligocki): int -> int64
+ virtual int Get() const = 0;
+ virtual void Set(int delta) = 0;
+
+ virtual void Add(int delta) { Set(delta + Get()); }
+ void Clear() { Set(0); }
+};
+
+// Helps build a statistics that can be exported as a CSV file.
+class Statistics {
+ public:
+ virtual ~Statistics();
+
+ // Add a new variable, or returns an existing one of that name.
+ // The Variable* is owned by the Statistics class -- it should
+ // not be deleted by the caller.
+ virtual Variable* AddVariable(const StringPiece& name) = 0;
+
+ // Find a variable from a name, returning NULL if not found.
+ virtual Variable* FindVariable(const StringPiece& name) const = 0;
+
+ // Find a variable from a name, aborting if not found.
+ virtual Variable* GetVariable(const StringPiece& name) const {
+ Variable* var = FindVariable(name);
+ CHECK(var != NULL) << "Variable not found: " << name;
+ return var;
+ }
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_STATISTICS_H_
diff --git a/trunk/src/net/instaweb/util/public/statistics_template.h b/trunk/src/net/instaweb/util/public/statistics_template.h
new file mode 100644
index 0000000..bcd848d
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/statistics_template.h
@@ -0,0 +1,90 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_STATISTICS_TEMPLATE_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_STATISTICS_TEMPLATE_H_
+
+#include <map>
+#include <vector>
+#include "net/instaweb/util/public/statistics.h"
+#include "net/instaweb/util/public/stl_util.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+// This class makes it easier to define new Statistics implementations
+// by providing a templatized implementation of variable registration and
+// management.
+template<class Var> class StatisticsTemplate : public Statistics {
+ public:
+ StatisticsTemplate() {}
+ virtual ~StatisticsTemplate() {
+ STLDeleteContainerPointers(variables_.begin(), variables_.end());
+ }
+
+ // Add a new variable, or returns an existing one of that name.
+ // The Variable* is owned by the Statistics class -- it should
+ // not be deleted by the caller.
+ virtual Variable* AddVariable(const StringPiece& name) {
+ return AddVariableInternal(name);
+ }
+
+ // Find a variable from a name, returning NULL if not found.
+ virtual Variable* FindVariable(const StringPiece& name) const {
+ return FindVariableInternal(name);
+ }
+
+ protected:
+ // Adds a variable, returning as the template class Var type, rather
+ // than its base class, as AddVariable does.
+ virtual Var* AddVariableInternal(const StringPiece& name) {
+ Var* var = FindVariableInternal(name);
+ if (var == NULL) {
+ var = NewVariable(name, variables_.size());
+ variables_.push_back(var);
+ variable_map_[std::string(name.data(), name.size())] = var;
+ }
+ return var;
+ }
+
+ // Finds a variable, returning as the template class Var type, rather
+ // than its base class, as FindVariable does.
+ virtual Var* FindVariableInternal(const StringPiece& name) const {
+ typename VarMap::const_iterator p = variable_map_.find(name.as_string());
+ Var* var = NULL;
+ if (p != variable_map_.end()) {
+ var = p->second;
+ }
+ return var;
+ }
+
+ virtual Var* NewVariable(const StringPiece& name, int index) = 0;
+
+ protected:
+ typedef std::vector<Var*> VarVector;
+ typedef std::map<std::string, Var*> VarMap;
+ VarVector variables_;
+ VarMap variable_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(StatisticsTemplate);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_STATISTICS_TEMPLATE_H_
diff --git a/trunk/src/net/instaweb/util/public/statistics_work_bound.h b/trunk/src/net/instaweb/util/public/statistics_work_bound.h
new file mode 100644
index 0000000..7e50c97
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/statistics_work_bound.h
@@ -0,0 +1,49 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_STATISTICS_WORK_BOUND_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_STATISTICS_WORK_BOUND_H_
+
+#include "net/instaweb/util/public/work_bound.h"
+
+namespace net_instaweb {
+
+class Variable;
+
+// A WorkBound implementation in terms of statistics. This is a bit of a hack
+// that gets things implemented quickly (especially given the complexity of
+// multiprocess shared-memory infrastructure, which we only want to roll once).
+// Note in particular that we handle a NULL variable gracefully by imposing no
+// bound at all.
+class StatisticsWorkBound : public WorkBound {
+ public:
+ // Note that ownership of variable remains with the creating Statistics
+ // object. If the bound is 0, the bound is actually infinite.
+ StatisticsWorkBound(Variable* variable, int bound);
+ virtual ~StatisticsWorkBound();
+
+ virtual bool TryToWork();
+ virtual void WorkComplete();
+ private:
+ Variable* variable_;
+ int bound_;
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_STATISTICS_WORK_BOUND_H_
diff --git a/trunk/src/net/instaweb/util/public/stdio_file_system.h b/trunk/src/net/instaweb/util/public/stdio_file_system.h
new file mode 100644
index 0000000..7ff8aec
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/stdio_file_system.h
@@ -0,0 +1,65 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_STDIO_FILE_SYSTEM_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_STDIO_FILE_SYSTEM_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/file_system.h"
+
+namespace net_instaweb {
+
+class StdioFileSystem : public FileSystem {
+ public:
+ StdioFileSystem() {}
+ virtual ~StdioFileSystem();
+
+ virtual bool Atime(const StringPiece& path, int64* timestamp_sec,
+ MessageHandler* handler);
+ virtual InputFile* OpenInputFile(const char* filename,
+ MessageHandler* message_handler);
+ virtual OutputFile* OpenOutputFileHelper(const char* filename,
+ MessageHandler* message_handler);
+ virtual OutputFile* OpenTempFileHelper(const StringPiece& prefix_name,
+ MessageHandler* message_handle);
+
+ virtual bool RemoveFile(const char* filename, MessageHandler* handler);
+ virtual bool RenameFileHelper(const char* old_file, const char* new_file,
+ MessageHandler* handler);
+ virtual bool MakeDir(const char* directory_path, MessageHandler* handler);
+ virtual BoolOrError Exists(const char* path, MessageHandler* handler);
+ virtual BoolOrError IsDir(const char* path, MessageHandler* handler);
+ virtual bool ListContents(const StringPiece& dir, StringVector* files,
+ MessageHandler* handler);
+ virtual bool Size(const StringPiece& path, int64* size,
+ MessageHandler* handler);
+ virtual BoolOrError TryLock(const StringPiece& lock_name,
+ MessageHandler* handler);
+ virtual bool Unlock(const StringPiece& lock_name,
+ MessageHandler* handler);
+ InputFile* Stdin();
+ OutputFile* Stdout();
+ OutputFile* Stderr();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StdioFileSystem);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_STDIO_FILE_SYSTEM_H_
diff --git a/trunk/src/net/instaweb/util/public/stl_util.h b/trunk/src/net/instaweb/util/public/stl_util.h
new file mode 100644
index 0000000..b09c180
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/stl_util.h
@@ -0,0 +1,24 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_STL_UTIL_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_STL_UTIL_H_
+
+#include "base/stl_util-inl.h"
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_STL_UTIL_H_
diff --git a/trunk/src/net/instaweb/util/public/string_hash.h b/trunk/src/net/instaweb/util/public/string_hash.h
new file mode 100644
index 0000000..da86af8
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/string_hash.h
@@ -0,0 +1,52 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_STRING_HASH_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_STRING_HASH_H_
+
+#include <stddef.h>
+
+namespace net_instaweb {
+
+inline size_t HashString(const char* s, size_t len) {
+ // TODO(jmarantz): The portability layers defined in
+ // third_party/chromium/src/base/hash_tables.h
+ // third_party/protobuf2/src/src/google/protobuf/stubs/hash.h
+ // do not make it obvious how to hash a string directly -- they only
+ // make it possible to instantiate hash_set<string> and hash_map<string,...>.
+ // A reasonable hashing function looks available in apr_hashfunc_default,
+ // but for build isolation reasons we don't want to reference that here.
+ //
+ // For now, define our own implemention, copied from code in
+ // third_party/chromium/src/base/hash_tables.h.
+ size_t result = 0;
+ for (size_t i = 0; i < len; ++i, ++s) {
+ result = (result * 131) + *s;
+ }
+ return result;
+}
+
+// Combine two hash values in a reasonable way. Here to avoid
+// excessive mysticism in the remainder of the code.
+inline size_t JoinHash(size_t a, size_t b) {
+ return (a + 56) * 137 + b * 151; // Uses different prime multipliers.
+}
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_STRING_HASH_H_
diff --git a/trunk/src/net/instaweb/util/public/string_util.h b/trunk/src/net/instaweb/util/public/string_util.h
new file mode 100644
index 0000000..bf8874f
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/string_util.h
@@ -0,0 +1,170 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_STRING_UTIL_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_STRING_UTIL_H_
+
+
+#include <set>
+#include <vector>
+#include <string>
+
+#include <stdlib.h>
+#include "base/string_number_conversions.h"
+#include "base/string_piece.h"
+#include "base/string_util.h"
+
+namespace net_instaweb {
+
+typedef base::StringPiece StringPiece;
+
+inline std::string IntegerToString(int i) {
+ return base::IntToString(i);
+}
+
+inline std::string Integer64ToString(int64 i) {
+ return base::Int64ToString(i);
+}
+
+inline bool StringToInt(const char* in, int* out) {
+ // TODO(bmcquade): Use char*-based StringToInt once we sync the
+ // Chromium repository.
+ std::string str(in);
+ return base::StringToInt(str, out);
+}
+
+inline bool StringToInt64(const char* in, int64* out) {
+ // TODO(bmcquade): Use char*-based StringToInt64 once we sync the
+ // Chromium repository.
+ std::string str(in);
+ return base::StringToInt64(str, out);
+}
+
+inline bool StringToInt(const std::string& in, int* out) {
+ return base::StringToInt(in, out);
+}
+
+inline bool StringToInt64(const std::string& in, int64* out) {
+ return base::StringToInt64(in, out);
+}
+
+class EmptyString {
+ public:
+ static const StringPiece kEmptyString;
+};
+
+// TODO(jmarantz): use overloading instead of default args and get
+// rid of this statically constructed global object.
+std::string StrCat(const StringPiece& a, const StringPiece& b,
+ const StringPiece& c = EmptyString::kEmptyString,
+ const StringPiece& d = EmptyString::kEmptyString,
+ const StringPiece& e = EmptyString::kEmptyString,
+ const StringPiece& f = EmptyString::kEmptyString,
+ const StringPiece& g = EmptyString::kEmptyString,
+ const StringPiece& h = EmptyString::kEmptyString);
+
+void SplitStringPieceToVector(const StringPiece& sp, const char* separator,
+ std::vector<StringPiece>* components,
+ bool omit_empty_strings);
+
+void BackslashEscape(const StringPiece& src,
+ const StringPiece& to_escape,
+ std::string* dest);
+
+bool HasPrefixString(const StringPiece& str, const StringPiece& prefix);
+
+void LowerString(std::string* str);
+
+inline bool OnlyWhitespace(const std::string& str) {
+ return ContainsOnlyWhitespaceASCII(str);
+}
+
+inline char* strdup(const char* str) {
+ return base::strdup(str);
+}
+
+inline int strcasecmp(const char* s1, const char* s2) {
+ return base::strcasecmp(s1, s2);
+}
+
+inline int strncasecmp(const char* s1, const char* s2, size_t count) {
+ return base::strncasecmp(s1, s2, count);
+}
+
+inline void TrimWhitespace(const StringPiece& in, std::string* output) {
+ static const char whitespace[] = " \r\n\t";
+ TrimString(std::string(in.data(), in.size()), whitespace, output);
+}
+
+// Accumulates a decimal value from 'c' into *value.
+// Returns false and leaves *value unchanged if c is not a decimal digit.
+bool AccumulateDecimalValue(char c, int* value);
+
+// Accumulates a hex value from 'c' into *value
+// Returns false and leaves *value unchanged if c is not a hex digit.
+bool AccumulateHexValue(char c, int* value);
+
+// Return true iff the two strings are equal, ignoring case.
+bool StringCaseEqual(const StringPiece& s1, const StringPiece& s2);
+// Return true iff str starts with prefix, ignoring case.
+bool StringCaseStartsWith(const StringPiece& str, const StringPiece& prefix);
+// Return true iff str ends with suffix, ignoring case.
+bool StringCaseEndsWith(const StringPiece& str, const StringPiece& suffix);
+
+struct CharStarCompareInsensitive {
+ bool operator()(const char* s1, const char* s2) const {
+ return strcasecmp(s1, s2) < 0;
+ };
+};
+
+struct CharStarCompareSensitive {
+ bool operator()(const char* s1, const char* s2) const {
+ return strcmp(s1, s2) < 0;
+ }
+};
+
+struct StringCompareInsensitive {
+ bool operator()(const std::string& s1, const std::string& s2) const {
+ return strcasecmp(s1.c_str(), s2.c_str()) < 0;
+ };
+};
+
+typedef std::vector<const char*> CharStarVector;
+typedef std::vector<std::string> StringVector;
+typedef std::set<std::string> StringSet;
+
+// Does a path end in slash?
+inline bool EndsInSlash(const StringPiece& path) {
+ return path.ends_with("/");
+}
+
+// Make sure directory's path ends in '/'.
+inline void EnsureEndsInSlash(std::string* dir) {
+ if (!EndsInSlash(*dir)) {
+ dir->append("/");
+ }
+}
+
+// Given a string such as: a b "c d" e 'f g'
+// Parse it into a vector: ["a", "b", "c d", "e", "f g"]
+void ParseShellLikeString(const StringPiece& input,
+ std::vector<std::string>* output);
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_STRING_UTIL_H_
diff --git a/trunk/src/net/instaweb/util/public/string_writer.h b/trunk/src/net/instaweb/util/public/string_writer.h
new file mode 100644
index 0000000..43d93c7
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/string_writer.h
@@ -0,0 +1,43 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_STRING_WRITER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_STRING_WRITER_H_
+
+#include "base/basictypes.h"
+#include <string>
+#include "net/instaweb/util/public/writer.h"
+
+namespace net_instaweb {
+
+// Writer implementation for directing HTML output to a string.
+class StringWriter : public Writer {
+ public:
+ explicit StringWriter(std::string* str) : string_(str) { }
+ virtual ~StringWriter();
+ virtual bool Write(const StringPiece& str, MessageHandler* message_handler);
+ virtual bool Flush(MessageHandler* message_handler);
+ private:
+ std::string* string_;
+
+ DISALLOW_COPY_AND_ASSIGN(StringWriter);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_STRING_WRITER_H_
diff --git a/trunk/src/net/instaweb/util/public/symbol_table.h b/trunk/src/net/instaweb/util/public/symbol_table.h
new file mode 100644
index 0000000..bfec527
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/symbol_table.h
@@ -0,0 +1,100 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_SYMBOL_TABLE_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_SYMBOL_TABLE_H_
+
+#include <stdlib.h>
+#include <set>
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/atom.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+// Implements a generic symbol table, allowing for case-sensitive
+// and case insensitive versions. The elements of SymbolTables are
+// Atoms. Atoms are created by Interning strings.
+//
+// Atoms are cheap and are passed around by value, not by reference or
+// pointer. Atoms can be compared to one another via ==. A const char*
+// can be extracted from an Atom via ==.
+//
+// Atoms are memory-managed by the symbol table from which they came.
+// When the symbol table is destroyed, so are all the Atoms that
+// were interned in it.
+//
+// Care should be taken not to attempt to compare Atoms created from
+// multiple symbol tables.
+//
+// TODO(jmarantz): Symbol tables are not currently thread-safe. We
+// should consider whether it's worth making them thread-safe, or
+// whether it's better to use separate symbol tables in each thread.
+template<class SymbolCompare> class SymbolTable {
+ public:
+ SymbolTable() { }
+ ~SymbolTable() {
+ while (!string_set_.empty()) {
+ // Note: This should perform OK for rb-trees, but will perform
+ // poorly if a hash-table is used.
+ typename SymbolSet::const_iterator p = string_set_.begin();
+ const char* str = *p;
+ string_set_.erase(p);
+ free(const_cast<char*>(str));
+ }
+ }
+
+ Atom Intern(const char* src) {
+ Atom atom(src);
+ typename SymbolSet::const_iterator iter = string_set_.find(src);
+ if (iter == string_set_.end()) {
+ char* str = strdup(src);
+ string_set_.insert(str);
+ return Atom(str);
+ }
+ return Atom(*iter);
+ }
+
+ inline Atom Intern(const std::string& src) {
+ return Intern(src.c_str());
+ }
+
+ private:
+ typedef std::set<const char*, SymbolCompare> SymbolSet;
+ SymbolSet string_set_;
+
+ DISALLOW_COPY_AND_ASSIGN(SymbolTable);
+};
+
+class SymbolTableInsensitive : public SymbolTable<CharStarCompareInsensitive> {
+ public:
+ SymbolTableInsensitive() { }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SymbolTableInsensitive);
+};
+class SymbolTableSensitive : public SymbolTable<CharStarCompareSensitive> {
+ public:
+ SymbolTableSensitive() { }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SymbolTableSensitive);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_SYMBOL_TABLE_H_
diff --git a/trunk/src/net/instaweb/util/public/threadsafe_cache.h b/trunk/src/net/instaweb/util/public/threadsafe_cache.h
new file mode 100644
index 0000000..52f2bccd
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/threadsafe_cache.h
@@ -0,0 +1,57 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_THREADSAFE_CACHE_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_THREADSAFE_CACHE_H_
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/util/public/cache_interface.h"
+#include <string>
+
+namespace net_instaweb {
+
+class MessageHandler;
+class Writer;
+class AbstractMutex;
+
+// Composes a cache with a Mutex to form a threadsafe cache.
+class ThreadsafeCache : public CacheInterface {
+ public:
+ // Takes ownership of the cache that's passed in.
+ ThreadsafeCache(CacheInterface* cache, AbstractMutex* mutex)
+ : cache_(cache),
+ mutex_(mutex) {
+ }
+ virtual ~ThreadsafeCache();
+
+ virtual bool Get(const std::string& key, SharedString* value);
+ virtual void Put(const std::string& key, SharedString* value);
+ virtual void Delete(const std::string& key);
+ virtual KeyState Query(const std::string& key);
+
+ private:
+ scoped_ptr<CacheInterface> cache_;
+ AbstractMutex* mutex_;
+
+ DISALLOW_COPY_AND_ASSIGN(ThreadsafeCache);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_THREADSAFE_CACHE_H_
diff --git a/trunk/src/net/instaweb/util/public/time_util.h b/trunk/src/net/instaweb/util/public/time_util.h
new file mode 100644
index 0000000..088ba05
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/time_util.h
@@ -0,0 +1,37 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_TIME_UTIL_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_TIME_UTIL_H_
+
+#include "base/basictypes.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+// Converts time, in milliseconds, to a string. Returns false on failure.
+bool ConvertTimeToString(int64 time_ms, std::string* time_string);
+
+// Converts time in string format, to the number of milliseconds since 1970.
+// Returns false on failure.
+bool ConvertStringToTime(const StringPiece& time_string, int64 *time_ms);
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_TIME_UTIL_H_
diff --git a/trunk/src/net/instaweb/util/public/timer.h b/trunk/src/net/instaweb/util/public/timer.h
new file mode 100644
index 0000000..4a6b48b
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/timer.h
@@ -0,0 +1,55 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_TIMER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_TIMER_H_
+
+#include "base/basictypes.h"
+
+namespace net_instaweb {
+
+// Timer interface, made virtual so it can be mocked for tests.
+class Timer {
+ public:
+ static const int64 kSecondUs;
+ static const int64 kSecondMs;
+ static const int64 kMinuteMs;
+ static const int64 kHourMs;
+ static const int64 kDayMs;
+ static const int64 kWeekMs;
+ static const int64 kMonthMs;
+ static const int64 kYearMs;
+
+ virtual ~Timer();
+
+ // Returns number of milliseconds since 1970.
+ virtual int64 NowMs() const;
+
+ // Returns number of microseconds since 1970.
+ virtual int64 NowUs() const = 0;
+
+ // Sleep for given number of milliseconds.
+ virtual void SleepMs(int64 ms);
+
+ // Sleep for given number of microseconds.
+ virtual void SleepUs(int64 us) = 0;
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_TIMER_H_
diff --git a/trunk/src/net/instaweb/util/public/timer_based_abstract_lock.h b/trunk/src/net/instaweb/util/public/timer_based_abstract_lock.h
new file mode 100644
index 0000000..83e2d9f
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/timer_based_abstract_lock.h
@@ -0,0 +1,54 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_TIMER_BASED_ABSTRACT_LOCK_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_TIMER_BASED_ABSTRACT_LOCK_H_
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/named_lock_manager.h"
+
+namespace net_instaweb {
+
+class Timer;
+
+// A TimerBasedAbstractLock implements a Lock by spinning on a TryLock, using a
+// Timer to perform exponential sleep backoff. Note that this means that it may
+// not obtain a long-held lock in a timely fashion after it has been unlocked.
+class TimerBasedAbstractLock : public AbstractLock {
+ public:
+ virtual ~TimerBasedAbstractLock();
+ virtual void Lock();
+ virtual bool LockTimedWait(int64 wait_ms);
+
+ virtual void LockStealOld(int64 timeout_ms);
+ virtual bool LockTimedWaitStealOld(int64 wait_ms, int64 timeout_ms);
+
+ protected:
+ virtual Timer* timer() const = 0;
+
+ private:
+ typedef bool (TimerBasedAbstractLock::*TryLockMethod)(int64 timeout_ms);
+ bool TryLockIgnoreTimeout(int64 timeout_ignored);
+ bool BusySpin(TryLockMethod try_lock, int64 timeout_ms);
+ void Spin(TryLockMethod try_lock, int64 timeout_ms);
+ bool SpinFor(TryLockMethod try_lock, int64 timeout_ms, int64 wait_ms);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_TIMER_BASED_ABSTRACT_LOCK_H_
diff --git a/trunk/src/net/instaweb/util/public/url_async_fetcher.h b/trunk/src/net/instaweb/util/public/url_async_fetcher.h
new file mode 100644
index 0000000..0a41f41
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/url_async_fetcher.h
@@ -0,0 +1,69 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+//
+// UrlFetcher is an interface for asynchronously fetching urls. The
+// caller must supply a callback to be called when the fetch is complete.
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_URL_ASYNC_FETCHER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_URL_ASYNC_FETCHER_H_
+
+#include "base/basictypes.h"
+#include <string>
+
+namespace net_instaweb {
+
+class MessageHandler;
+class MetaData;
+class Writer;
+
+class UrlAsyncFetcher {
+ public:
+ static const int64 kUnspecifiedTimeout;
+ struct Callback {
+ virtual ~Callback();
+ virtual void Done(bool success) = 0;
+
+ // Set to true if it's OK to call the callback from a different
+ // thread. The base class implementation returns false.
+ virtual bool EnableThreaded() const;
+ };
+
+ virtual ~UrlAsyncFetcher();
+
+ // Fetch a URL, streaming the output to fetched_content_writer, and
+ // returning the headers. request_headers is optional -- it can be NULL.
+ // response_headers and fetched_content_writer must be valid until
+ // the call to Done().
+ //
+ // This function returns true if the request was immediately satisfied.
+ // In either case, the callback will be called with the completion status,
+ // so it's safe to ignore the return value.
+ virtual bool StreamingFetch(const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* response_writer,
+ MessageHandler* message_handler,
+ Callback* callback) = 0;
+ // Returns a maximum time that we will allow fetches to take, or
+ // kUnspecifiedTimeout (the default) if we don't promise to timeout fetches.
+ virtual int64 timeout_ms() { return kUnspecifiedTimeout; }
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_URL_ASYNC_FETCHER_H_
diff --git a/trunk/src/net/instaweb/util/public/url_escaper.h b/trunk/src/net/instaweb/util/public/url_escaper.h
new file mode 100644
index 0000000..44525c6
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/url_escaper.h
@@ -0,0 +1,86 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_URL_ESCAPER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_URL_ESCAPER_H_
+
+#include "net/instaweb/util/public/url_segment_encoder.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+/*
+common url format
+
+ http://www.foo.bar/z1234/b_c.d?e=f&g=h
+
+this suggests we should have short encodings for a-zA-Z0-9.:/?&
+
+We would like the above URL to be reasonably legible if possible.
+However it's also nice if it's short.
+
+One annoyance is that we are using '.' to delimit the 4 fields of an
+instaweb-generated URL. That can probably be changed to use ^, which
+is a legal URL but is rarely used in URLs. This would enable us to
+leave . alone. But that's probably a moderately painful change
+involving a fair amount of regolding.
+
+In the meantime we can replace . with ^ in this encoder so the they
+don't change size. So the transform table is:
+
+a-zA-Z0-9_=+-&? unchanged
+% %%
+/ %_
+\ %-
+http:// %h
+.com %c
+.css %s
+.edu %e
+.gif %g
+.html %t
+.jpeg %k
+.jpg %j
+.js %l
+.net %n
+.png %p
+www. %w
+. ^
+^ %^
+
+everything else %XX where xx are hex digits using capital latters
+
+
+The intent of this class to to help encode arbitrary URLs (really, any
+stream of 8-byte characters, but optimized for URLs) so that it can be
+used in one 'segment' of a new URL. This means we will not output
+. or / but will instead escape those.
+*/
+
+class UrlEscaper : public UrlSegmentEncoder {
+ public:
+ virtual ~UrlEscaper();
+ virtual void EncodeToUrlSegment(
+ const StringPiece& in, std::string* url_segment);
+ virtual bool DecodeFromUrlSegment(
+ const StringPiece& url_segment, std::string* out);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_URL_ESCAPER_H_
diff --git a/trunk/src/net/instaweb/util/public/url_fetcher.h b/trunk/src/net/instaweb/util/public/url_fetcher.h
new file mode 100644
index 0000000..3bd720c
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/url_fetcher.h
@@ -0,0 +1,61 @@
+/**
+ * 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)
+//
+// UrlFetcher is an interface for fetching urls.
+//
+// TODO(jmarantz): Consider asynchronous fetches. This may not require
+// a change in interface; we would simply always return 'false' if the
+// url contents is not already cached. We may want to consider a richer
+// return-value enum to distinguish illegal ULRs from invalid ones, from
+// ones where the fetch is in-progress. Or maybe the caller doesn't care.
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_URL_FETCHER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_URL_FETCHER_H_
+
+#include <string>
+
+namespace net_instaweb {
+
+class MessageHandler;
+class MetaData;
+class Writer;
+
+class UrlFetcher {
+ public:
+ virtual ~UrlFetcher();
+
+ // Fetch a URL, streaming the output to fetched_content_writer, and
+ // returning the headers. Returns true if the fetch was successful.
+ virtual bool StreamingFetchUrl(const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* response_writer,
+ MessageHandler* message_handler) = 0;
+
+ // Convenience method for fetching URL into a string, with no headers in
+ // our out. This is primarily for upward compatibility.
+ //
+ // TODO(jmarantz): change callers to use StreamingFetchUrl and remove this.
+ bool FetchUrl(const std::string& url,
+ std::string* content,
+ MessageHandler* message_handler);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_URL_FETCHER_H_
diff --git a/trunk/src/net/instaweb/util/public/url_multipart_encoder.h b/trunk/src/net/instaweb/util/public/url_multipart_encoder.h
new file mode 100644
index 0000000..e33a301
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/url_multipart_encoder.h
@@ -0,0 +1,80 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_URL_MULTIPART_ENCODER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_URL_MULTIPART_ENCODER_H_
+
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+class MessageHandler;
+
+// Encodes a multiple strings into a single string so that it
+// can be decoded. This is not restricted to URLs but is optimized
+// for them in its choice of escape characters. '+' is used to
+// separate the parts, and any parts that include '+' are prefixed
+// by a '='. '=' is converted to '==' -- it's a pretty lightweight
+// encoding, and any other character restrictions will have to be
+// applied to the output of this class.
+//
+// TODO(jmarantz): One possibly improvement is to bake this
+// functionality into UrlEscaper, changing its interface to accept
+// arbitrary numbers of pieces in & out. However, that would change
+// an interface that's used in multiple places, so this is left as
+// a TODO.
+class UrlMultipartEncoder {
+ public:
+ UrlMultipartEncoder() {}
+
+ // Removes all the URLs from the encoding.
+ void clear() { urls_.clear(); }
+
+ // Adds a new URL to the encoding. Actually there are no
+ // character-set restrictions imposed by this method.
+ void AddUrl(const StringPiece& url) {
+ urls_.push_back(std::string(url.data(), url.size()));
+ }
+
+ // Encode the URLs added to this class into a single string.
+ std::string Encode() const;
+
+ // Decodde an encoding produced by Encode() above to populate
+ // this class.
+ bool Decode(const StringPiece& encoding, MessageHandler* handler);
+
+ // Returns the number of URLs stored (either by Decode or by
+ // AddUrl.
+ int num_urls() const { return urls_.size(); }
+
+ // Returns the url at the index.
+ const std::string& url(int index) const { return urls_[index]; }
+
+ // Removes the last URL.
+ void pop_back() { urls_.pop_back(); }
+
+ private:
+ StringVector urls_;
+
+ DISALLOW_COPY_AND_ASSIGN(UrlMultipartEncoder);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_URL_MULTIPART_ENCODER_H_
diff --git a/trunk/src/net/instaweb/util/public/url_segment_encoder.h b/trunk/src/net/instaweb/util/public/url_segment_encoder.h
new file mode 100644
index 0000000..ab38f73
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/url_segment_encoder.h
@@ -0,0 +1,48 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_URL_SEGMENT_ENCODER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_URL_SEGMENT_ENCODER_H_
+
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+// Abstract class that describes encoding of url segments by rewriters.
+// Most instances of this will want to delegate to a UrlEscaper, which
+// is itself an instance.
+class UrlSegmentEncoder {
+ public:
+ virtual ~UrlSegmentEncoder();
+ // *Append* encoding of url segment "in" to "url_segment".
+ virtual void EncodeToUrlSegment(const StringPiece& in,
+ std::string* url_segment) = 0;
+ // Decode url segment from "url_segment", *appending* to "out"; should consume
+ // entire StringPiece. Return false on decode failure.
+ virtual bool DecodeFromUrlSegment(const StringPiece& url_segment,
+ std::string* out) = 0;
+ protected:
+ UrlSegmentEncoder() { }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(UrlSegmentEncoder);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_URL_SEGMENT_ENCODER_H_
diff --git a/trunk/src/net/instaweb/util/public/user_agent.h b/trunk/src/net/instaweb/util/public/user_agent.h
new file mode 100644
index 0000000..16bb53f
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/user_agent.h
@@ -0,0 +1,41 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+//
+// 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.
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_USER_AGENT_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_USER_AGENT_H_
+
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+class UserAgent {
+ public:
+ UserAgent();
+ void set_user_agent(const char* user_agent);
+
+ bool IsIe() const;
+ bool IsIe6() const;
+ bool IsIe7() const;
+ bool IsIe6or7() const {
+ return IsIe6() || IsIe7();
+ };
+
+ private:
+ std::string user_agent_;
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_USER_AGENT_H_
diff --git a/trunk/src/net/instaweb/util/public/wait_url_async_fetcher.h b/trunk/src/net/instaweb/util/public/wait_url_async_fetcher.h
new file mode 100644
index 0000000..e162701
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/wait_url_async_fetcher.h
@@ -0,0 +1,60 @@
+/*
+ * 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)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_WAIT_URL_ASYNC_FETCHER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_WAIT_URL_ASYNC_FETCHER_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/url_async_fetcher.h"
+
+namespace net_instaweb {
+
+class UrlFetcher;
+
+// Fake UrlAsyncFetcher which waits to call underlying blocking fetcher until
+// you explicitly call CallCallbacks().
+class WaitUrlAsyncFetcher : public UrlAsyncFetcher {
+ public:
+ WaitUrlAsyncFetcher(UrlFetcher* url_fetcher) : url_fetcher_(url_fetcher) {}
+ virtual ~WaitUrlAsyncFetcher();
+
+ // Initiate fetches that will finish when CallCallbacks is called.
+ virtual bool StreamingFetch(const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* response_writer,
+ MessageHandler* message_handler,
+ Callback* callback);
+
+ // Call all callbacks from previously initiated fetches.
+ void CallCallbacks();
+
+ private:
+ class DelayedFetch;
+
+ UrlFetcher* url_fetcher_;
+ std::vector<DelayedFetch*> delayed_fetches_;
+
+ DISALLOW_COPY_AND_ASSIGN(WaitUrlAsyncFetcher);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_WAIT_URL_ASYNC_FETCHER_H_
diff --git a/trunk/src/net/instaweb/util/public/wget_url_fetcher.h b/trunk/src/net/instaweb/util/public/wget_url_fetcher.h
new file mode 100644
index 0000000..53ef679
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/wget_url_fetcher.h
@@ -0,0 +1,52 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_WGET_URL_FETCHER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_WGET_URL_FETCHER_H_
+
+#include <stdio.h>
+#include "base/basictypes.h"
+#include <string>
+#include "net/instaweb/util/public/url_fetcher.h"
+
+namespace net_instaweb {
+
+// Runs 'wget' via popen for blocking URL fetches.
+class WgetUrlFetcher : public UrlFetcher {
+ public:
+ WgetUrlFetcher() { }
+ virtual ~WgetUrlFetcher();
+
+ // TODO(sligocki): Allow protocol version number (e.g. HTTP/1.1)
+ // and request type (e.g. GET, POST, etc.) to be specified.
+ virtual bool StreamingFetchUrl(const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* writer,
+ MessageHandler* message_handler);
+
+ // Default user agent to use.
+ static const char kDefaultUserAgent[];
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WgetUrlFetcher);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_WGET_URL_FETCHER_H_
diff --git a/trunk/src/net/instaweb/util/public/wildcard.h b/trunk/src/net/instaweb/util/public/wildcard.h
new file mode 100644
index 0000000..e1f2652
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/wildcard.h
@@ -0,0 +1,60 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_WILDCARD_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_WILDCARD_H_
+
+#include <vector>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+class Wildcard {
+ public:
+ static const char kMatchAny; // *
+ static const char kMatchOne; // ?
+
+ // Create a wildcard object with the specification using * and ?
+ // as wildcards. There is currently no way to quote * or ?.
+ explicit Wildcard(const StringPiece& wildcard_spec);
+
+ // Determines whether a string matches the wildcard.
+ bool Match(const StringPiece& str) { return MatchHelper(0, str); }
+
+ // Determines whether this wildcard is just a simple name, lacking
+ // any wildcard characters.
+ bool IsSimple() const;
+
+ // Returns the original wildcard specification.
+ const StringPiece spec() const { return storage_; }
+
+ // Makes a duplicate copy of the wildcard object.
+ Wildcard* Duplicate() const;
+
+ private:
+ bool MatchHelper(int piece_index, const StringPiece& str);
+ void InitFromStorage();
+
+ std::string storage_;
+ std::vector<StringPiece> pieces_;
+ DISALLOW_COPY_AND_ASSIGN(Wildcard);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_WILDCARD_H_
diff --git a/trunk/src/net/instaweb/util/public/wildcard_group.h b/trunk/src/net/instaweb/util/public/wildcard_group.h
new file mode 100644
index 0000000..9ac948f
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/wildcard_group.h
@@ -0,0 +1,77 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_SELECTOR_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_SELECTOR_H_
+
+#include <vector>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+class Wildcard;
+
+// This forms the basis of a wildcard selection mechanism, allowing
+// a user to issue sequence of commands like:
+//
+// 1. allow *.cc
+// 2. allow *.h
+// 3. disallow a*.h
+// 4. allow ab*.h
+// 5. disallow c*.cc
+//
+// This sequence would yield the following results whnen
+// Match("x.cc") --> true due to rule #1
+// Match("c.cc") --> false due to rule #5 which overrides rule #1
+// Match("y.h") --> true due to rule #2
+// Match("a.h") --> false due to rule #3 which overrides rule #2
+// Match("ab.h") --> true due to rule #4 which overrides rule #3
+// So order matters.
+class WildcardGroup {
+ public:
+ WildcardGroup() {}
+ ~WildcardGroup();
+
+ // Determines whether a string matches the wildcard group.
+ bool Match(const StringPiece& str) const;
+
+ // Add an expression to Allow, potentially overriding previous calls to
+ // Disallow.
+ void Allow(const StringPiece& wildcard);
+
+ // Add an expression to Disallow, potentially overriding previous calls to
+ // Allow.
+ void Disallow(const StringPiece& wildcard);
+
+ void CopyFrom(const WildcardGroup& src);
+ void AppendFrom(const WildcardGroup& src);
+
+ private:
+ bool MatchHelper(int piece_index, const StringPiece& str);
+
+ // To avoid having to new another structure we use two parallel
+ // vectors. Note that vector<bool> is special-case implemented
+ // in STL to be bit-packed.
+ std::vector<Wildcard*> wildcards_;
+ std::vector<bool> allow_; // parallel array (actually a bitvector)
+ DISALLOW_COPY_AND_ASSIGN(WildcardGroup);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_SELECTOR_H_
diff --git a/trunk/src/net/instaweb/util/public/work_bound.h b/trunk/src/net/instaweb/util/public/work_bound.h
new file mode 100644
index 0000000..29f875e
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/work_bound.h
@@ -0,0 +1,36 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_WORK_BOUND_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_WORK_BOUND_H_
+
+namespace net_instaweb {
+
+// A WorkBound represents permission to do work bounded by some upper bound.
+// Roughly speaking we can represent this as a bounded shared counter, but
+// how we realize the counter implementation must vary from system to system.
+class WorkBound {
+ public:
+ virtual ~WorkBound();
+ virtual bool TryToWork() = 0;
+ virtual void WorkComplete() = 0;
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_WORK_BOUND_H_
diff --git a/trunk/src/net/instaweb/util/public/write_through_cache.h b/trunk/src/net/instaweb/util/public/write_through_cache.h
new file mode 100644
index 0000000..0122c36
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/write_through_cache.h
@@ -0,0 +1,69 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_WRITE_THROUGH_CACHE_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_WRITE_THROUGH_CACHE_H_
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "net/instaweb/util/public/cache_interface.h"
+#include <string>
+
+namespace net_instaweb {
+
+class MessageHandler;
+class Writer;
+class AbstractMutex;
+
+// Composes two caches to form a write-through cache.
+class WriteThroughCache : public CacheInterface {
+ public:
+ static const size_t kUnlimited;
+
+ // Takes ownership of both caches passed in.
+ WriteThroughCache(CacheInterface* cache1, CacheInterface* cache2)
+ : cache1_(cache1),
+ cache2_(cache2),
+ cache1_size_limit_(kUnlimited) {
+ }
+ virtual ~WriteThroughCache();
+
+ virtual bool Get(const std::string& key, SharedString* value);
+ virtual void Put(const std::string& key, SharedString* value);
+ virtual void Delete(const std::string& key);
+ virtual KeyState Query(const std::string& key);
+
+ // By default, all data goes into both cache1 and cache2. But
+ // if you only want to put small items in cache1, you can set the
+ // size limit. Note that both the key and value will count
+ // torward the size.
+ void set_cache1_limit(size_t limit) { cache1_size_limit_ = limit; }
+
+ private:
+ void PutInCache1(const std::string& key, SharedString* value);
+
+ scoped_ptr<CacheInterface> cache1_;
+ scoped_ptr<CacheInterface> cache2_;
+ size_t cache1_size_limit_;
+
+ DISALLOW_COPY_AND_ASSIGN(WriteThroughCache);
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_WRITE_THROUGH_CACHE_H_
diff --git a/trunk/src/net/instaweb/util/public/writer.h b/trunk/src/net/instaweb/util/public/writer.h
new file mode 100644
index 0000000..01ba5ad
--- /dev/null
+++ b/trunk/src/net/instaweb/util/public/writer.h
@@ -0,0 +1,41 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#ifndef NET_INSTAWEB_UTIL_PUBLIC_WRITER_H_
+#define NET_INSTAWEB_UTIL_PUBLIC_WRITER_H_
+
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+class MessageHandler;
+
+// Interface for writing bytes to an output stream.
+class Writer {
+ public:
+ virtual ~Writer();
+
+ virtual bool Write(const StringPiece& str, MessageHandler* handler) = 0;
+
+ virtual bool Flush(MessageHandler* message_handler) = 0;
+};
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_PUBLIC_WRITER_H_
diff --git a/trunk/src/net/instaweb/util/query_params.cc b/trunk/src/net/instaweb/util/query_params.cc
new file mode 100644
index 0000000..09aed4b
--- /dev/null
+++ b/trunk/src/net/instaweb/util/query_params.cc
@@ -0,0 +1,111 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/query_params.h"
+
+#include <stdio.h>
+#include "base/logging.h"
+#include "net/instaweb/util/public/message_handler.h"
+
+namespace net_instaweb {
+
+// TODO(jmarantz): Refactor much of this code with simple_meta_data.cc. We'd
+// have to templatize to indicate whether we wanted case sensitivity in our
+// variables.
+
+void QueryParams::Clear() {
+ for (int i = 0, n = variable_vector_.size(); i < n; ++i) {
+ delete [] variable_vector_[i].second;
+ }
+ variable_map_.clear();
+ variable_vector_.clear();
+}
+
+bool QueryParams::Lookup(const char* name, CharStarVector* values) const {
+ VariableMap::const_iterator p = variable_map_.find(name);
+ bool ret = false;
+ if (p != variable_map_.end()) {
+ ret = true;
+ *values = p->second;
+ }
+ return ret;
+}
+
+void QueryParams::Add(const StringPiece& name, const StringPiece& value) {
+ CharStarVector dummy_values;
+ std::string name_buf(name.data(), name.size());
+ std::pair<VariableMap::iterator, bool> iter_inserted = variable_map_.insert(
+ VariableMap::value_type(name_buf.c_str(), dummy_values));
+ VariableMap::iterator iter = iter_inserted.first;
+ CharStarVector& values = iter->second;
+ char* value_copy = NULL;
+ if (value.data() != NULL) {
+ int value_size = value.size();
+ value_copy = new char[value_size + 1];
+ memcpy(value_copy, value.data(), value_size);
+ value_copy[value_size] = '\0';
+ }
+ values.push_back(value_copy);
+ variable_vector_.push_back(StringPair(iter->first.c_str(), value_copy));
+}
+
+void QueryParams::RemoveAll(const char* var_name) {
+ VariableVector temp_vector; // Temp variable for new vector.
+ temp_vector.reserve(variable_vector_.size());
+ for (int i = 0; i < size(); ++i) {
+ if (strcasecmp(name(i), var_name) != 0) {
+ temp_vector.push_back(variable_vector_[i]);
+ } else {
+ delete [] variable_vector_[i].second;
+ }
+ }
+ variable_vector_.swap(temp_vector);
+
+ // Note: we have to erase from the map second, because map owns the name.
+ variable_map_.erase(var_name);
+}
+
+void QueryParams::Parse(const StringPiece& text) {
+ CHECK_EQ(0, size());
+ std::vector<StringPiece> components;
+ SplitStringPieceToVector(text, "&", &components, true);
+ for (int i = 0, n = components.size(); i < n; ++i) {
+ StringPiece::size_type pos = components[i].find('=');
+ if (pos != StringPiece::npos) {
+ Add(components[i].substr(0, pos), components[i].substr(pos + 1));
+ } else {
+ Add(components[i], StringPiece(NULL, 0));
+ }
+ }
+}
+
+std::string QueryParams::ToString() const {
+ std::string str;
+ const char* prefix="";
+ for (int i = 0; i < size(); ++i) {
+ if (value(i) == NULL) {
+ str += StrCat(prefix, name(i));
+ } else {
+ str += StrCat(prefix, name(i), "=", value(i));
+ }
+ prefix = "&";
+ }
+ return str;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/query_params_test.cc b/trunk/src/net/instaweb/util/query_params_test.cc
new file mode 100644
index 0000000..e589936
--- /dev/null
+++ b/trunk/src/net/instaweb/util/query_params_test.cc
@@ -0,0 +1,101 @@
+// Copyright 2010 and onwards Google Inc.
+// Author: jmarantz@google.com (Joshua Marantz)
+
+// Unit-test SimpleUrlData, in particular it's HTTP header parser.
+
+#include "net/instaweb/util/public/query_params.h"
+#include <algorithm>
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "net/instaweb/util/public/google_message_handler.h"
+#include "net/instaweb/util/public/gtest.h"
+#include "net/instaweb/util/public/stdio_file_system.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/string_writer.h"
+
+namespace {
+
+const char kQueryString[] = "a=1&b&c=2&d=&a=3";
+
+} // namespace
+
+namespace net_instaweb {
+
+class QueryParamsTest : public testing::Test {
+ protected:
+ QueryParamsTest() { }
+
+ virtual void SetUp() {
+ query_params_.Parse(kQueryString);
+ }
+
+ QueryParams query_params_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(QueryParamsTest);
+};
+
+TEST_F(QueryParamsTest, TestParse) {
+ ASSERT_EQ(5, query_params_.size());
+ EXPECT_EQ(std::string("a"), query_params_.name(0));
+ EXPECT_EQ(std::string("1"), query_params_.value(0));
+ EXPECT_EQ(std::string("b"), query_params_.name(1));
+ EXPECT_EQ(NULL, query_params_.value(1));
+ EXPECT_EQ(std::string("c"), query_params_.name(2));
+ EXPECT_EQ(std::string("2"), query_params_.value(2));
+ EXPECT_EQ(std::string("d"), query_params_.name(3));
+ EXPECT_EQ(std::string(""), query_params_.value(3));
+ EXPECT_EQ(std::string("a"), query_params_.name(4));
+ EXPECT_EQ(std::string("3"), query_params_.value(4));
+ EXPECT_EQ(kQueryString, query_params_.ToString());
+}
+
+TEST_F(QueryParamsTest, TestLookup) {
+ CharStarVector v;
+ ASSERT_TRUE(query_params_.Lookup("a", &v));
+ ASSERT_EQ(2, v.size());
+ EXPECT_EQ(std::string("1"), v[0]);
+ EXPECT_EQ(std::string("3"), v[1]);
+ ASSERT_TRUE(query_params_.Lookup("b", &v));
+ ASSERT_EQ(1, v.size());
+ EXPECT_EQ(NULL, v[0]);
+ ASSERT_TRUE(query_params_.Lookup("c", &v));
+ ASSERT_EQ(1, v.size());
+ EXPECT_EQ(std::string("2"), v[0]);
+ ASSERT_TRUE(query_params_.Lookup("d", &v));
+ ASSERT_EQ(1, v.size());
+ EXPECT_EQ(std::string(""), v[0]);
+}
+
+TEST_F(QueryParamsTest, TestRemove) {
+ CharStarVector v;
+ query_params_.RemoveAll("a");
+ EXPECT_EQ("b&c=2&d=", query_params_.ToString());
+ EXPECT_EQ(3, query_params_.size());
+ query_params_.RemoveAll("b");
+ EXPECT_EQ("c=2&d=", query_params_.ToString());
+ EXPECT_EQ(2, query_params_.size());
+ query_params_.RemoveAll("c");
+ EXPECT_EQ("d=", query_params_.ToString());
+ EXPECT_EQ(1, query_params_.size());
+ query_params_.RemoveAll("d");
+ EXPECT_EQ("", query_params_.ToString());
+ EXPECT_EQ(0, query_params_.size());
+}
+
+TEST_F(QueryParamsTest, TestClear) {
+ query_params_.Clear();
+ EXPECT_EQ("", query_params_.ToString());
+ EXPECT_EQ(0, query_params_.size());
+}
+
+TEST_F(QueryParamsTest, TestAEqualsBEquals1) {
+ query_params_.Clear();
+ query_params_.Parse("a=b=1");
+ ASSERT_EQ(1, query_params_.size());
+ ASSERT_EQ(std::string("a"), query_params_.name(0));
+ ASSERT_EQ(std::string("b=1"), query_params_.value(0));
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/ref_counted.cc b/trunk/src/net/instaweb/util/ref_counted.cc
new file mode 100644
index 0000000..d82e900
--- /dev/null
+++ b/trunk/src/net/instaweb/util/ref_counted.cc
@@ -0,0 +1,26 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+// This file works around an apparent omission in the Chromium
+// libraries. The Chromium source tree includes classes for reference
+// counting, but the implementation files are not included in the
+// Chromium library. The easiest way to ensure they are compiled into
+// Instaweb is to include them in a new .cc file:
+
+#include "base/ref_counted.cc"
+#include "base/thread_collision_warner.cc"
diff --git a/trunk/src/net/instaweb/util/rolling_hash.cc b/trunk/src/net/instaweb/util/rolling_hash.cc
new file mode 100644
index 0000000..01ec29c
--- /dev/null
+++ b/trunk/src/net/instaweb/util/rolling_hash.cc
@@ -0,0 +1,293 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#include "net/instaweb/util/public/rolling_hash.h"
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+uint64 RollingHash(const char* buf, size_t start, size_t n) {
+ CHECK_LE(static_cast<size_t>(0), start);
+ buf += start;
+ uint64 hash = 0;
+ for (size_t i = 0; i < n; ++i) {
+ hash = (hash << 1) | (hash >> 63); // Rotate left 1
+ hash ^= kRollingHashCharTable[static_cast<uint8>(buf[i])];
+ }
+ return hash;
+}
+
+const uint64 kRollingHashCharTable[256] = {
+ 0xf0dcd27e0762b251ULL,
+ 0xb621e668da0b3d53ULL,
+ 0xce8c72d316e9293cULL,
+ 0xe958017c3359bad5ULL,
+ 0x6b11ca3935e9bb14ULL,
+ 0x64f67431b8b0bcb4ULL,
+ 0x8c4dd71ad988d23eULL,
+ 0x24936d1c08aff78dULL,
+ 0x69ecc7f3052e0396ULL,
+ 0xcdc8f08d999754c5ULL,
+ 0x39ab6e32568685cdULL,
+ 0x4e4f289b8b96e626ULL,
+ 0xa3c3cb4a32546b3dULL,
+ 0xe929810fec372e4dULL,
+ 0xfd94f0e82a05ac37ULL,
+ 0x3b71322f12f04f66ULL,
+ 0x8ad767cc6b92f018ULL,
+ 0xb4fff19004481ff1ULL,
+ 0xc9a1422e55e9d8bbULL,
+ 0x016fccc64de16d17ULL,
+ 0xd27154733cd80e3eULL,
+ 0x3f837bd6c3588413ULL,
+ 0x2c493e9f8c70ee0eULL,
+ 0xb45526d0cdcae876ULL,
+ 0xfa8778e134e91383ULL,
+ 0x0e8f821f8c783f69ULL,
+ 0xc28fd9493d9268e9ULL,
+ 0xf5b90b302376dc19ULL,
+ 0x12d5edf71944c0b9ULL,
+ 0x3de65234604d9f95ULL,
+ 0x6e0b7823a5ab650fULL,
+ 0xf30d1b84d74f542cULL,
+ 0xd59ea7a1a04daa1eULL,
+ 0xc322bb145da0eaeeULL,
+ 0x9f55e20e76bc821cULL,
+ 0x84eb8f72c858db2cULL,
+ 0x0727b30ff9b3080fULL,
+ 0x40cb8bf1f3e4114fULL,
+ 0x379d10f1f0166fc2ULL,
+ 0x8766af09de794470ULL,
+ 0x4cebe022f58b47f0ULL,
+ 0x75d232d0c4a94ebbULL,
+ 0xc63ba1fbd098199cULL,
+ 0xa0d947c29f383a9eULL,
+ 0x3bfd354a0404e6deULL,
+ 0x9d3164f90bd471a3ULL,
+ 0x357f031c2b1cee0eULL,
+ 0x47bb2d71880becd1ULL,
+ 0xc048dc946fd9dd85ULL,
+ 0xa21e8226ed9b2db9ULL,
+ 0xad06169fbe3a05a9ULL,
+ 0x0a7b91d58f44e29bULL,
+ 0x36bdd30c057a61baULL,
+ 0xcb78135eb2c3b107ULL,
+ 0xa65907ee1f4209ddULL,
+ 0x0962bb3ccf9816f2ULL,
+ 0xceb546dda951601eULL,
+ 0x5e5769344a1f8f21ULL,
+ 0x1fe188e97a0685eeULL,
+ 0x59d0d4289c5edacbULL,
+ 0xaa212857bb62f723ULL,
+ 0xa527e223de884eceULL,
+ 0x9c9a15c9aa56eba4ULL,
+ 0x9b16d95901aee267ULL,
+ 0xf4c5b630931f11e9ULL,
+ 0x3df802e7e85a6750ULL,
+ 0x4968f654cce17653ULL,
+ 0xfc3bc0ad02761567ULL,
+ 0xff18ec7e401c445dULL,
+ 0x2f844385f9c31b7aULL,
+ 0x88d750f43c6989edULL,
+ 0x4bb020be66f231f9ULL,
+ 0x12502d671fa7f487ULL,
+ 0x1926420bae67fb5aULL,
+ 0x2acb699d19d6c4d4ULL,
+ 0x5cdb79d0349158eaULL,
+ 0x67e93d1bf4815342ULL,
+ 0x43dc874f7024e36dULL,
+ 0xb92097281fed49b6ULL,
+ 0x62ed4f7a0a6958a9ULL,
+ 0x574c6b02ae6f3626ULL,
+ 0xd49b443e8932f23dULL,
+ 0x7dc50d1a701aab7aULL,
+ 0x48621174dff0fd92ULL,
+ 0x5746557ae580c35eULL,
+ 0x4bc7f97a8ebc2480ULL,
+ 0x07e620aad2f7b646ULL,
+ 0x1285636d57e3f612ULL,
+ 0x9f6cd306e5514c5cULL,
+ 0x0de00632f6f709d7ULL,
+ 0x7aa301698adee68bULL,
+ 0x2df10184f4cfd4ecULL,
+ 0x5f5b744cf78e8048ULL,
+ 0x88ebe0a2615fe4b3ULL,
+ 0xcb6f29a29401f98fULL,
+ 0x9c306f274e66d287ULL,
+ 0x88f632944f2ccdd9ULL,
+ 0x63bcf4a925db4311ULL,
+ 0xc6d2f5e9c0a50b72ULL,
+ 0xd6a731846a17be49ULL,
+ 0x2fd613caa70c235eULL,
+ 0x534de67031253facULL,
+ 0x4c685e47e9592796ULL,
+ 0x9b153e27865bd258ULL,
+ 0xd614462b61589f7dULL,
+ 0x1532cb94e28d8cbfULL,
+ 0x91497f579632a631ULL,
+ 0x26e3a246cc96fb61ULL,
+ 0xb28d962661bf129bULL,
+ 0x85da5d60a6ae5aa3ULL,
+ 0x0a9a1f6cb7c3e6a0ULL,
+ 0xae039586bd8c7f11ULL,
+ 0x0bfd1a8454e77964ULL,
+ 0xd6fa6b85a423d107ULL,
+ 0x015c3ab5134f69fcULL,
+ 0xdab1025b8bed7c18ULL,
+ 0xdf404b3edca12395ULL,
+ 0xad3d2d77444e1887ULL,
+ 0x92654b67bda1e990ULL,
+ 0xc412fe06d4e29f1eULL,
+ 0x2d382f434b99af46ULL,
+ 0x8cb666ada86575a1ULL,
+ 0x833d4b153f5f5405ULL,
+ 0x6007f6c51629e797ULL,
+ 0xd8a1f922cba896e3ULL,
+ 0x2c9b5a1a249f9e47ULL,
+ 0xd899781c79cb063bULL,
+ 0x911f5c7513ea3c91ULL,
+ 0x0c77ae198ca79978ULL,
+ 0x20ba7b1e0c97d74cULL,
+ 0xb3711eccdd549521ULL,
+ 0x0a94dc19e59543dfULL,
+ 0x3b348e9c6b0e36a6ULL,
+ 0x9627b15951a7a6b8ULL,
+ 0x9193dc839dbe7049ULL,
+ 0xba707aa6a2add40eULL,
+ 0x1777251ac57b2a2aULL,
+ 0xca6c2edba50c4c4fULL,
+ 0xa6c43cd31526ff60ULL,
+ 0x310d05dff737a640ULL,
+ 0xa8663ab4709bdd52ULL,
+ 0xd05f9d24885f3b94ULL,
+ 0x8c863ea5aafb221dULL,
+ 0x21ef263381f8cd63ULL,
+ 0x4b6fb4a8bc5458aaULL,
+ 0xc1d84b559be971b0ULL,
+ 0x210fb1bab7f15072ULL,
+ 0xe66ee09a51a55963ULL,
+ 0x39c21db635c08debULL,
+ 0xf05b979841675d0dULL,
+ 0x2978a9f732d96470ULL,
+ 0x4dce978a19c3a1f1ULL,
+ 0x5c0e92ab7319cd99ULL,
+ 0x743b286678d9e686ULL,
+ 0xdaf46993e90a5e81ULL,
+ 0x96dc2e0adcbb0d16ULL,
+ 0xd95690fe868663d8ULL,
+ 0x66ef0231433f2b39ULL,
+ 0x30774974792d96ccULL,
+ 0x44ffb0e25c47c44eULL,
+ 0x4cd9b89be5889713ULL,
+ 0xe62e5a6014f07cf3ULL,
+ 0xb56ac85a8af4dea0ULL,
+ 0xde92b05ba2e1b34aULL,
+ 0x23fd1311f823b78cULL,
+ 0xff80ac0f287ec43cULL,
+ 0x5d90a3866dd95fc0ULL,
+ 0xdec51083823f593fULL,
+ 0x86ad4349ac174faeULL,
+ 0xaedff28454b16a41ULL,
+ 0x3d8bc08e3f2e0c9dULL,
+ 0xf86319f232a3c729ULL,
+ 0xcd5e5e0f94263499ULL,
+ 0x8d7ba52980b19b79ULL,
+ 0xa67e8a3d335c81d2ULL,
+ 0x65d09ac6aa0fdab2ULL,
+ 0xc1795b7f5b644a20ULL,
+ 0xec83e7447c32d0dcULL,
+ 0x6f90245af445f4aeULL,
+ 0x56d70cadcc4a28fcULL,
+ 0xaf264eb82bcc4f90ULL,
+ 0xa16010fce3634affULL,
+ 0x8ea336b9c2c1e45bULL,
+ 0x7680ddb84af244bbULL,
+ 0xf97371315d450666ULL,
+ 0x8166dfa721896adcULL,
+ 0x3648e8d5b8d995d8ULL,
+ 0xa63975a605b31cf2ULL,
+ 0x35e60de4a3359ad4ULL,
+ 0x9b81705b2e4be07bULL,
+ 0xb9ae701a8eb85593ULL,
+ 0xaedecc0d138f3115ULL,
+ 0x8a1fdbb92e3423c1ULL,
+ 0xb0c96dee77615860ULL,
+ 0x629b7c06bf44e634ULL,
+ 0x696eb82aa746b1e1ULL,
+ 0x02efce1165256d4fULL,
+ 0x69d7a6b7150117a6ULL,
+ 0x9dcc2d6e896e5681ULL,
+ 0xcd815845d154cfbeULL,
+ 0x28ea53acb6da26f0ULL,
+ 0x7100bc4d21fe52dfULL,
+ 0x653e558ec969142fULL,
+ 0x83d730de45a0f5d4ULL,
+ 0x3831dd5c5647e4b1ULL,
+ 0x0f4072bf5123e9d5ULL,
+ 0xa642dce46ec285eeULL,
+ 0xae995373b5193c28ULL,
+ 0xf0f3dafb8a611288ULL,
+ 0xbc482a9e9b15c5abULL,
+ 0x41668ebcdc7cc0f4ULL,
+ 0xcdaaef0cd5519603ULL,
+ 0xebc0b08ad6d7c0e5ULL,
+ 0xb35073399de97c08ULL,
+ 0x97e4241d5f265b52ULL,
+ 0x2e52578c54e99676ULL,
+ 0x288a75c28fad917bULL,
+ 0x466e1dd37d710926ULL,
+ 0x25fd42c4845ce5fcULL,
+ 0xe0a1137fa3234b76ULL,
+ 0xfbfc21548eb90370ULL,
+ 0x8c4cf202e1e875feULL,
+ 0x9b7cd1e8a5aaa1a1ULL,
+ 0x2dec3dc9615ca561ULL,
+ 0xb4458b07977eea11ULL,
+ 0x75cea1e7a8560366ULL,
+ 0x109bf4b3b2e39a46ULL,
+ 0xdf8ed90441ba266bULL,
+ 0xd1856901e5f8bac7ULL,
+ 0x7a8b4ab2edd81a62ULL,
+ 0xb1ae8556c90ae5cbULL,
+ 0x42d15186c967771fULL,
+ 0xa07cdf5a38a872a9ULL,
+ 0x986954db9832acebULL,
+ 0xf130e68aa4fce616ULL,
+ 0xcc0caef3d0c76c52ULL,
+ 0x2c6a3fe49a7d500eULL,
+ 0x076cb3ad48ed1b85ULL,
+ 0xe6f8d0aecbb14027ULL,
+ 0x2cbe5b65d4e5c490ULL,
+ 0x9dac9ec2da0d12a7ULL,
+ 0x14ab4abbd07e5ab0ULL,
+ 0xae99ca6cb4cd1e03ULL,
+ 0x3bcd346450954fbaULL,
+ 0x2430fee89dd27a62ULL,
+ 0x7dc551166f0c94f8ULL,
+ 0x87947cbebe2a5031ULL,
+ 0x67003a8dfe2fd1b0ULL,
+ 0xfed080735857b507ULL,
+ 0x5097a42c664d9bf9ULL,
+ 0x9389590f671cf117ULL,
+ 0x9c5781b4f071956dULL,
+ 0x9c16eac9bd72017aULL,
+ 0x45186d635717d743ULL,
+ 0x3f375208bc7ce161ULL,
+ 0x768cd5d30f885c47ULL };
+} // net_instaweb
diff --git a/trunk/src/net/instaweb/util/rolling_hash_test.cc b/trunk/src/net/instaweb/util/rolling_hash_test.cc
new file mode 100644
index 0000000..7f63669
--- /dev/null
+++ b/trunk/src/net/instaweb/util/rolling_hash_test.cc
@@ -0,0 +1,111 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#include "net/instaweb/util/public/rolling_hash.h"
+
+#include "net/instaweb/util/public/dense_hash_set.h"
+#include "net/instaweb/util/public/gtest.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+namespace {
+
+const char kTestString[] =
+ "The quick brown fox jumps over the lazy dog.\n"
+ "Now is the time for ALL good men to come to the aid of their party.\r\n"
+ "@$%^@#$%#^%^987293 458798\x8f\xfa\xce\t";
+const size_t kTestStringSize = sizeof(kTestString) - 1;
+
+TEST(RollingHashTest, EmptyString) {
+ EXPECT_EQ(0, RollingHash("", 0, 0));
+ EXPECT_EQ(0, RollingHash(NULL, 0, 0));
+}
+
+TEST(RollingHashTest, SingleChar) {
+ EXPECT_EQ(kRollingHashCharTable[' '], RollingHash(" ", 0, 1));
+}
+
+TEST(RollingHashTest, SingleRoll) {
+ static const char kCSpace[] = "C ";
+ uint64 h0 = RollingHash(kCSpace, 0, 1);
+ EXPECT_EQ(kRollingHashCharTable['C'], h0);
+ EXPECT_EQ(kRollingHashCharTable[' '], NextRollingHash(kCSpace, 1, 1, h0));
+}
+
+TEST(RollingHashTest, RollShakedown) {
+ for (size_t i = 1; i < kTestStringSize; ++i) {
+ uint64 hash = RollingHash(kTestString, 0, i);
+ for (size_t j = 1; j < kTestStringSize - i; ++j) {
+ hash = NextRollingHash(kTestString, j, i, hash);
+ EXPECT_EQ(RollingHash(kTestString, j, i), hash);
+ }
+ }
+}
+
+// Prove that there are no trivial 1-, 2-, or 3-gram overlaps.
+// Note that the open-vcdiff rolling hash cannot pass this test
+// as it only has 23 bits!
+TEST(RollingHashTest, NGrams) {
+ // Using dense_hash_set is MUCH faster (6x) than std::set.
+ // That keeps this test "small".
+ dense_hash_set<uint64> grams;
+ grams.set_empty_key(0ULL);
+ char buf[3];
+ uint64 hash;
+ bool gramOverlap = false;
+ for (int i = 0; i < 256; i++) {
+ buf[0] = i;
+ hash = RollingHash(buf, 0, 1);
+ ASSERT_NE(0, hash);
+ if (!grams.insert(hash).second) {
+ gramOverlap = true;
+ printf("Gram overlap including %2x", i);
+ }
+ }
+ for (int i = 0; i < 256; i++) {
+ buf[0] = i;
+ for (int j = 0; j < 256; j++) {
+ buf[1] = j;
+ hash = RollingHash(buf, 0, 2);
+ ASSERT_NE(0, hash);
+ if (!grams.insert(hash).second) {
+ gramOverlap = true;
+ printf("Gram overlap including %2x %2x", i, j);
+ }
+ }
+ }
+ for (int i = 0; i < 256; i++) {
+ buf[0] = i;
+ for (int j = 0; j < 256; j++) {
+ buf[1] = j;
+ for (int k = 0; k < 256; k++) {
+ buf[2] = k;
+ hash = RollingHash(buf, 0, 3);
+ ASSERT_NE(0, hash);
+ if (!grams.insert(hash).second) {
+ gramOverlap = true;
+ printf("Gram overlap including %2x %2x %2x\n", i, j, k);
+ }
+ }
+ }
+ }
+ EXPECT_FALSE(gramOverlap);
+}
+
+} // namespace
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/simple_meta_data.cc b/trunk/src/net/instaweb/util/simple_meta_data.cc
new file mode 100644
index 0000000..d1e25b5
--- /dev/null
+++ b/trunk/src/net/instaweb/util/simple_meta_data.cc
@@ -0,0 +1,380 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/simple_meta_data.h"
+
+#include <stdio.h>
+#include "base/logging.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/string_writer.h"
+#include "net/instaweb/util/public/timer.h"
+#include "net/instaweb/util/public/time_util.h"
+#include "net/instaweb/util/public/writer.h"
+#include "pagespeed/core/resource_util.h"
+
+namespace {
+
+const int64 TIME_UNINITIALIZED = -1;
+const int64 kImplicitCacheTtlMs = 5 * net_instaweb::Timer::kMinuteMs;
+
+}
+
+namespace net_instaweb {
+
+SimpleMetaData::SimpleMetaData() {
+ Clear();
+}
+
+SimpleMetaData::~SimpleMetaData() {
+ Clear();
+}
+
+void SimpleMetaData::Clear() {
+ for (int i = 0, n = attribute_vector_.size(); i < n; ++i) {
+ delete [] attribute_vector_[i].second;
+ }
+ attribute_map_.clear();
+ attribute_vector_.clear();
+
+ parsing_http_ = false;
+ parsing_value_ = false;
+ headers_complete_ = false;
+ cache_fields_dirty_ = false;
+ is_cacheable_ = false;
+ is_proxy_cacheable_ = false; // accurate only if !cache_fields_dirty_
+ expiration_time_ms_= TIME_UNINITIALIZED;
+ timestamp_ms_= TIME_UNINITIALIZED;
+ parse_name_.clear();
+ parse_value_.clear();
+
+ major_version_ = 0;
+ minor_version_ = 0;
+ status_code_ = 0;
+ reason_phrase_.clear();
+}
+
+int SimpleMetaData::NumAttributes() const {
+ return attribute_vector_.size();
+}
+
+const char* SimpleMetaData::Name(int index) const {
+ return attribute_vector_[index].first;
+}
+
+const char* SimpleMetaData::Value(int index) const {
+ return attribute_vector_[index].second;
+}
+
+bool SimpleMetaData::Lookup(const char* name, CharStarVector* values) const {
+ AttributeMap::const_iterator p = attribute_map_.find(name);
+ bool ret = false;
+ if (p != attribute_map_.end()) {
+ ret = true;
+ *values = p->second;
+ }
+ return ret;
+}
+
+void SimpleMetaData::Add(const StringPiece& name, const StringPiece& value) {
+ // TODO(jmarantz): Parse comma-separated values. bmcquade sez:
+ // you probably want to normalize these by splitting on commas and
+ // adding a separate k,v pair for each comma-separated value. then
+ // it becomes very easy to do things like search for individual
+ // Content-Type tokens. Otherwise the client has to assume that
+ // every single value could be comma-separated and they have to
+ // parse it as such. the list of header names that are not safe to
+ // comma-split is at
+ // http://src.chromium.org/viewvc/chrome/trunk/src/net/http/http_util.cc
+ // (search for IsNonCoalescingHeader)
+
+ CharStarVector dummy_values;
+ std::pair<AttributeMap::iterator, bool> iter_inserted =
+ attribute_map_.insert(AttributeMap::value_type(name.as_string(),
+ dummy_values));
+ AttributeMap::iterator iter = iter_inserted.first;
+ CharStarVector& values = iter->second;
+ int value_buf_size = value.size() + 1;
+ char* value_copy = new char[value_buf_size];
+ memcpy(value_copy, value.data(), value_buf_size);
+ values.push_back(value_copy);
+ attribute_vector_.push_back(StringPair(iter->first.c_str(), value_copy));
+ cache_fields_dirty_ = true;
+}
+
+void SimpleMetaData::RemoveAll(const char* name) {
+ AttributeVector temp_vector; // Temp variable for new vector.
+ temp_vector.reserve(attribute_vector_.size());
+ for (int i = 0; i < NumAttributes(); ++i) {
+ if (strcasecmp(Name(i), name) != 0) {
+ temp_vector.push_back(attribute_vector_[i]);
+ } else {
+ delete [] attribute_vector_[i].second;
+ }
+ }
+ attribute_vector_.swap(temp_vector);
+
+ // Note: we have to erase from the map second, because map owns the name.
+ attribute_map_.erase(name);
+}
+
+// Serialize meta-data to a stream.
+bool SimpleMetaData::Write(Writer* writer, MessageHandler* handler) const {
+ bool ret = true;
+ char buf[100];
+ snprintf(buf, sizeof(buf), "HTTP/%d.%d %d ",
+ major_version_, minor_version_, status_code_);
+ ret &= writer->Write(buf, handler);
+ ret &= writer->Write(reason_phrase_, handler);
+ ret &= writer->Write("\r\n", handler);
+ ret &= WriteHeaders(writer, handler);
+ return ret;
+}
+
+bool SimpleMetaData::WriteHeaders(Writer* writer,
+ MessageHandler* handler) const {
+ bool ret = true;
+ for (int i = 0, n = attribute_vector_.size(); ret && (i < n); ++i) {
+ const StringPair& attribute = attribute_vector_[i];
+ ret &= writer->Write(attribute.first, handler);
+ ret &= writer->Write(": ", handler);
+ ret &= writer->Write(attribute.second, handler);
+ ret &= writer->Write("\r\n", handler);
+ }
+ ret &= writer->Write("\r\n", handler);
+ return ret;
+}
+
+// TODO(jmaessen): http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
+// I bet we're doing this wrong:
+// Header fields can be extended over multiple lines by preceding each extra
+// line with at least one SP or HT.
+int SimpleMetaData::ParseChunk(const StringPiece& text,
+ MessageHandler* handler) {
+ CHECK(!headers_complete_);
+ int num_consumed = 0;
+ int num_bytes = text.size();
+
+ for (; num_consumed < num_bytes; ++num_consumed) {
+ char c = text[num_consumed];
+ if ((c == '/') && (parse_name_ == "HTTP")) {
+ if (major_version_ != 0) {
+ handler->Message(kError, "Multiple HTTP Lines");
+ } else {
+ parsing_http_ = true;
+ parsing_value_ = true;
+ }
+ } else if (!parsing_value_ && (c == ':')) {
+ parsing_value_ = true;
+ } else if (c == '\r') {
+ // Just ignore CRs for now, and break up headers on newlines for
+ // simplicity. It's not clear to me if it's important that we
+ // reject headers that lack the CR in front of the LF.
+ } else if (c == '\n') {
+ if (parse_name_.empty()) {
+ // blank line. This marks the end of the headers.
+ ++num_consumed;
+ headers_complete_ = true;
+ ComputeCaching();
+ break;
+ }
+ if (parsing_http_) {
+ // Parsing "1.0 200 OK\r", using sscanf for the integers, and
+ // private method GrabLastToken for the "OK".
+ if ((sscanf(parse_value_.c_str(), "%d.%d %d ", // NOLINT
+ &major_version_, &minor_version_, &status_code_) != 3) ||
+ !GrabLastToken(parse_value_, &reason_phrase_)) {
+ // TODO(jmarantz): capture the filename/url, track the line numbers.
+ handler->Message(kError, "Invalid HTML headers: %s",
+ parse_value_.c_str());
+ }
+ parsing_http_ = false;
+ } else {
+ Add(parse_name_.c_str(), parse_value_.c_str());
+ }
+ parsing_value_ = false;
+ parse_name_.clear();
+ parse_value_.clear();
+ } else if (parsing_value_) {
+ // Skip leading whitespace
+ if (!parse_value_.empty() || !isspace(c)) {
+ parse_value_ += c;
+ }
+ } else {
+ parse_name_ += c;
+ }
+ }
+ return num_consumed;
+}
+
+// Specific information about cache. This is all embodied in the
+// headers but is centrally parsed so we can try to get it right.
+bool SimpleMetaData::IsCacheable() const {
+ // We do not compute caching from accessors so that the
+ // accessors can be easier to call from multiple threads
+ // without mutexing.
+ CHECK(!cache_fields_dirty_);
+ return is_cacheable_;
+}
+
+bool SimpleMetaData::IsProxyCacheable() const {
+ CHECK(!cache_fields_dirty_);
+ return is_proxy_cacheable_;
+}
+
+// Returns the ms-since-1970 absolute time when this resource
+// should be expired out of caches.
+int64 SimpleMetaData::CacheExpirationTimeMs() const {
+ CHECK(!cache_fields_dirty_);
+ return expiration_time_ms_;
+}
+
+void SimpleMetaData::SetDate(int64 date_ms) {
+ std::string time_string;
+ if (ConvertTimeToString(date_ms, &time_string)) {
+ Add("Date", time_string.c_str());
+ }
+}
+
+void SimpleMetaData::SetLastModified(int64 last_modified_ms) {
+ std::string time_string;
+ if (ConvertTimeToString(last_modified_ms, &time_string)) {
+ Add(HttpAttributes::kLastModified, time_string.c_str());
+ }
+}
+
+void SimpleMetaData::ComputeCaching() {
+ pagespeed::Resource resource;
+ for (int i = 0, n = NumAttributes(); i < n; ++i) {
+ resource.AddResponseHeader(Name(i), Value(i));
+ }
+ resource.SetResponseStatusCode(status_code_);
+
+ CharStarVector values;
+ int64 date;
+ // Compute the timestamp if we can find it
+ if (Lookup("Date", &values) && (values.size() == 1) &&
+ ConvertStringToTime(values[0], &date)) {
+ timestamp_ms_ = date;
+ }
+
+ // TODO(jmarantz): Should we consider as cacheable a resource
+ // that simply has no cacheable hints at all? For now, let's
+ // make that assumption. We should review this policy with bmcquade,
+ // souders, etc, but first let's try to measure some value with this
+ // optimistic intrepretation.
+ //
+ // TODO(jmarantz): get from bmcquade a comprehensive ways in which these
+ // policies will differ for Instaweb vs Pagespeed.
+ bool explicit_no_cache =
+ pagespeed::resource_util::HasExplicitNoCacheDirective(resource);
+ bool likely_static =
+ pagespeed::resource_util::IsLikelyStaticResource(resource);
+
+ // status_cacheable implies that either the resource content was
+ // cacheable, or the status code indicated some other aspect of
+ // our system that we want to remember in the cache, such as
+ // that fact that a fetch failed for a resource, and we don't want
+ // to try again until some time has passed.
+ bool status_cacheable =
+ ((status_code_ == HttpStatus::kRememberNotFoundStatusCode) ||
+ pagespeed::resource_util::IsCacheableResourceStatusCode(status_code_));
+ int64 freshness_lifetime_ms;
+ bool explicit_cacheable =
+ pagespeed::resource_util::GetFreshnessLifetimeMillis(
+ resource, &freshness_lifetime_ms) && has_timestamp_ms();
+ is_cacheable_ = (!explicit_no_cache &&
+ (explicit_cacheable || likely_static) &&
+ status_cacheable);
+
+ if (is_cacheable_) {
+ if (explicit_cacheable) {
+ // TODO(jmarantz): check "Age" resource and use that to reduce
+ // the expiration_time_ms_. This is, says, bmcquade@google.com,
+ // typically use to indicate how long a resource has been sitting
+ // in a proxy-cache.
+ expiration_time_ms_ = timestamp_ms_ + freshness_lifetime_ms;
+ } else {
+ // implicitly cached items stay alive in our system for 5 minutes
+ // TODO(jmarantz): consider making this a flag, or getting some
+ // other heuristic value from the PageSpeed libraries.
+ expiration_time_ms_ = timestamp_ms_ + kImplicitCacheTtlMs;
+ }
+
+ // Assume it's proxy cacheable. Then iterate over all the headers
+ // with key HttpAttributes::kCacheControl, and all the comma-separated
+ // values within those values, and look for 'private'.
+ is_proxy_cacheable_ = true;
+ values.clear();
+ if (Lookup(HttpAttributes::kCacheControl, &values)) {
+ for (int i = 0, n = values.size(); i < n; ++i) {
+ const char* cache_control = values[i];
+ pagespeed::resource_util::DirectiveMap directive_map;
+ if (pagespeed::resource_util::GetHeaderDirectives(
+ cache_control, &directive_map)) {
+ pagespeed::resource_util::DirectiveMap::iterator p =
+ directive_map.find("private");
+ if (p != directive_map.end()) {
+ is_proxy_cacheable_ = false;
+ break;
+ }
+ }
+ }
+ }
+ } else {
+ expiration_time_ms_ = 0;
+ is_proxy_cacheable_ = false;
+ }
+ cache_fields_dirty_ = false;
+}
+
+bool SimpleMetaData::has_timestamp_ms() const {
+ return timestamp_ms_ != TIME_UNINITIALIZED;
+}
+
+std::string SimpleMetaData::ToString() const {
+ std::string str;
+ StringWriter writer(&str);
+ Write(&writer, NULL);
+ return str;
+}
+
+// Grabs the last non-whitespace token from 'input' and puts it in 'output'.
+bool SimpleMetaData::GrabLastToken(const std::string& input,
+ std::string* output) {
+ bool ret = false;
+ // Safely grab the response code string from the end of parse_value_.
+ int last_token_char = -1;
+ for (int i = input.size() - 1; i >= 0; --i) {
+ char c = input[i];
+ if (isspace(c)) {
+ if (last_token_char >= 0) {
+ // We found the whole token.
+ const char* token_start = input.c_str() + i + 1;
+ int token_len = last_token_char - i;
+ output->append(token_start, token_len);
+ ret = true;
+ break;
+ }
+ } else if (last_token_char == -1) {
+ last_token_char = i;
+ }
+ }
+ return ret;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/simple_stats.cc b/trunk/src/net/instaweb/util/simple_stats.cc
new file mode 100644
index 0000000..19e0b66
--- /dev/null
+++ b/trunk/src/net/instaweb/util/simple_stats.cc
@@ -0,0 +1,34 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/simple_stats.h"
+
+namespace net_instaweb {
+
+SimpleStatsVariable::~SimpleStatsVariable() {
+}
+
+SimpleStats::~SimpleStats() {
+}
+
+SimpleStatsVariable* SimpleStats::NewVariable(
+ const StringPiece& name, int index) {
+ return new SimpleStatsVariable;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/stack_buffer.h b/trunk/src/net/instaweb/util/stack_buffer.h
new file mode 100644
index 0000000..8013b93
--- /dev/null
+++ b/trunk/src/net/instaweb/util/stack_buffer.h
@@ -0,0 +1,32 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+// Constant for allocating stack buffers.
+
+#ifndef NET_INSTAWEB_UTIL_STACK_BUFFER_H_
+#define NET_INSTAWEB_UTIL_STACK_BUFFER_H_
+
+namespace net_instaweb {
+
+// Size of stack buffer for read-blocks. This can't be too big or it will blow
+// the stack, which may be set small in multi-threaded environments.
+const int kStackBufferSize = 10000;
+
+} // namespace net_instaweb
+
+#endif // NET_INSTAWEB_UTIL_STACK_BUFFER_H_
diff --git a/trunk/src/net/instaweb/util/statistics.cc b/trunk/src/net/instaweb/util/statistics.cc
new file mode 100644
index 0000000..73ee39c
--- /dev/null
+++ b/trunk/src/net/instaweb/util/statistics.cc
@@ -0,0 +1,29 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/statistics.h"
+
+namespace net_instaweb {
+
+Variable::~Variable() {
+}
+
+Statistics::~Statistics() {
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/statistics_work_bound.cc b/trunk/src/net/instaweb/util/statistics_work_bound.cc
new file mode 100644
index 0000000..7acc770
--- /dev/null
+++ b/trunk/src/net/instaweb/util/statistics_work_bound.cc
@@ -0,0 +1,51 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#include "net/instaweb/util/public/statistics_work_bound.h"
+
+#include "net/instaweb/util/public/statistics.h"
+
+namespace net_instaweb {
+
+StatisticsWorkBound::StatisticsWorkBound(Variable* variable, int bound)
+ : variable_((bound == 0) ? NULL : variable), bound_(bound) { }
+StatisticsWorkBound::~StatisticsWorkBound() { }
+
+bool StatisticsWorkBound::TryToWork() {
+ bool ok = true;
+ if (variable_ != NULL) {
+ // We conservatively increment, then test, and decrement on failure. This
+ // guarantees that two incrementors don't both get through when we're within
+ // 1 of the bound, at the cost of occasionally rejecting them both.
+ variable_->Add(1);
+ ok = (variable_->Get() <= bound_);
+ if (!ok) {
+ variable_->Add(-1);
+ }
+ }
+ return ok;
+}
+
+void StatisticsWorkBound::WorkComplete() {
+ if (variable_ != NULL) {
+ variable_->Add(-1);
+ }
+}
+
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/statistics_work_bound_test.cc b/trunk/src/net/instaweb/util/statistics_work_bound_test.cc
new file mode 100644
index 0000000..56bab8a
--- /dev/null
+++ b/trunk/src/net/instaweb/util/statistics_work_bound_test.cc
@@ -0,0 +1,122 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+// Unit-test the statistics work bound.
+
+#include "net/instaweb/util/public/statistics_work_bound.h"
+
+#include "base/scoped_ptr.h"
+#include "net/instaweb/util/public/gtest.h"
+#include "net/instaweb/util/public/simple_stats.h"
+
+namespace net_instaweb {
+
+namespace {
+
+class StatisticsWorkBoundTest : public testing::Test {
+ public:
+ StatisticsWorkBoundTest()
+ : stats_(),
+ var1_(stats_.AddVariable("var1")),
+ var2_(stats_.AddVariable("var2")) { }
+
+ protected:
+ SimpleStats stats_;
+ Variable* var1_;
+ Variable* var2_;
+
+ StatisticsWorkBound* MakeBound(Variable* var, int bound) {
+ StatisticsWorkBound* result = new StatisticsWorkBound(var, bound);
+ CHECK(NULL != result);
+ return result;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StatisticsWorkBoundTest);
+};
+
+// Test with a bound of two.
+TEST_F(StatisticsWorkBoundTest, TestTwoBound) {
+ // We allocate two objects backed by the same statistic,
+ // to ensure that they share a common count. This is what
+ // happens in a multi-process setting.
+ scoped_ptr<StatisticsWorkBound> bound1(MakeBound(var1_, 2));
+ scoped_ptr<StatisticsWorkBound> bound2(MakeBound(var1_, 2));
+ // Repeat twice to ensure that we actually made it back to 0.
+ for (int i = 0; i < 2; i++) {
+ // Start with no workers.
+ EXPECT_TRUE(bound1->TryToWork());
+ // One worker here.
+ EXPECT_TRUE(bound1->TryToWork());
+ EXPECT_FALSE(bound1->TryToWork());
+ EXPECT_FALSE(bound2->TryToWork());
+ bound1->WorkComplete();
+ // One worker here.
+ EXPECT_TRUE(bound2->TryToWork());
+ EXPECT_FALSE(bound1->TryToWork());
+ EXPECT_FALSE(bound2->TryToWork());
+ bound1->WorkComplete();
+ // Back to one worker.
+ EXPECT_TRUE(bound2->TryToWork());
+ EXPECT_FALSE(bound1->TryToWork());
+ EXPECT_FALSE(bound2->TryToWork());
+ bound2->WorkComplete();
+ // Back to one worker.
+ bound2->WorkComplete();
+ // Back to none.
+ }
+}
+
+// Test that a bound of 0 allows large # of tries.
+TEST_F(StatisticsWorkBoundTest, TestZeroBound) {
+ scoped_ptr<StatisticsWorkBound> bound1(MakeBound(var1_, 0));
+ scoped_ptr<StatisticsWorkBound> bound2(MakeBound(var1_, 0));
+ for (int i = 0; i < 1000; ++i) {
+ EXPECT_TRUE(bound1->TryToWork());
+ EXPECT_TRUE(bound2->TryToWork());
+ }
+}
+
+// Test that absent variable allows large # of tries.
+TEST_F(StatisticsWorkBoundTest, TestNullVar) {
+ scoped_ptr<StatisticsWorkBound> bound1(MakeBound(NULL, 2));
+ scoped_ptr<StatisticsWorkBound> bound2(MakeBound(NULL, 2));
+ for (int i = 0; i < 1000; ++i) {
+ EXPECT_TRUE(bound1->TryToWork());
+ EXPECT_TRUE(bound2->TryToWork());
+ }
+}
+
+// Test that differently-named bounds are distinct
+TEST_F(StatisticsWorkBoundTest, TestDistinctVar) {
+ scoped_ptr<StatisticsWorkBound> bound1(MakeBound(var1_, 2));
+ scoped_ptr<StatisticsWorkBound> bound2(MakeBound(var2_, 2));
+ EXPECT_TRUE(bound1->TryToWork());
+ EXPECT_TRUE(bound1->TryToWork());
+ EXPECT_FALSE(bound1->TryToWork());
+ EXPECT_TRUE(bound2->TryToWork());
+ EXPECT_TRUE(bound2->TryToWork());
+ EXPECT_FALSE(bound2->TryToWork());
+ bound1->WorkComplete();
+ EXPECT_FALSE(bound2->TryToWork());
+ EXPECT_TRUE(bound1->TryToWork());
+}
+
+} // namespace
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/stdio_file_system.cc b/trunk/src/net/instaweb/util/stdio_file_system.cc
new file mode 100644
index 0000000..f9abd9c
--- /dev/null
+++ b/trunk/src/net/instaweb/util/stdio_file_system.cc
@@ -0,0 +1,379 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/stdio_file_system.h"
+
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/errno.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include <string>
+
+namespace net_instaweb {
+
+// Helper class to factor out common implementation details between Input and
+// Output files, in lieu of multiple inheritance.
+class StdioFileHelper {
+ public:
+ StdioFileHelper(FILE* f, const StringPiece& filename)
+ : file_(f),
+ line_(1) {
+ filename.CopyToString(&filename_);
+ }
+
+ ~StdioFileHelper() {
+ CHECK(file_ == NULL);
+ }
+
+ void CountNewlines(const char* buf, int size) {
+ for (int i = 0; i < size; ++i, ++buf) {
+ line_ += (*buf == '\n');
+ }
+ }
+
+ void ReportError(MessageHandler* message_handler, const char* format) {
+ message_handler->Error(filename_.c_str(), line_, format, strerror(errno));
+ }
+
+ bool Close(MessageHandler* message_handler) {
+ bool ret = true;
+ if (file_ != stdout && file_ != stderr && file_ != stdin) {
+ if (fclose(file_) != 0) {
+ ReportError(message_handler, "closing file: %s");
+ ret = false;
+ }
+ }
+ file_ = NULL;
+ return ret;
+ }
+
+ FILE* file_;
+ std::string filename_;
+ int line_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StdioFileHelper);
+};
+
+class StdioInputFile : public FileSystem::InputFile {
+ public:
+ StdioInputFile(FILE* f, const StringPiece& filename)
+ : file_helper_(f, filename) {
+ }
+
+ virtual int Read(char* buf, int size, MessageHandler* message_handler) {
+ int ret = fread(buf, 1, size, file_helper_.file_);
+ file_helper_.CountNewlines(buf, ret);
+ if ((ret == 0) && (ferror(file_helper_.file_) != 0)) {
+ file_helper_.ReportError(message_handler, "reading file: %s");
+ }
+ return ret;
+ }
+
+ virtual bool Close(MessageHandler* message_handler) {
+ return file_helper_.Close(message_handler);
+ }
+
+ virtual const char* filename() { return file_helper_.filename_.c_str(); }
+
+ private:
+ StdioFileHelper file_helper_;
+
+ DISALLOW_COPY_AND_ASSIGN(StdioInputFile);
+};
+
+class StdioOutputFile : public FileSystem::OutputFile {
+ public:
+ StdioOutputFile(FILE* f, const StringPiece& filename)
+ : file_helper_(f, filename) {
+ }
+
+ virtual bool Write(const StringPiece& buf, MessageHandler* handler) {
+ size_t bytes_written =
+ fwrite(buf.data(), 1, buf.size(), file_helper_.file_);
+ file_helper_.CountNewlines(buf.data(), bytes_written);
+ bool ret = (bytes_written == buf.size());
+ if (!ret) {
+ file_helper_.ReportError(handler, "writing file: %s");
+ }
+ return ret;
+ }
+
+ virtual bool Flush(MessageHandler* message_handler) {
+ bool ret = true;
+ if (fflush(file_helper_.file_) != 0) {
+ file_helper_.ReportError(message_handler, "flushing file: %s");
+ ret = false;
+ }
+ return ret;
+ }
+
+ virtual bool Close(MessageHandler* message_handler) {
+ return file_helper_.Close(message_handler);
+ }
+
+ virtual const char* filename() { return file_helper_.filename_.c_str(); }
+
+ virtual bool SetWorldReadable(MessageHandler* message_handler) {
+ bool ret = true;
+ int fd = fileno(file_helper_.file_);
+ int status = fchmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ if (status != 0) {
+ ret = false;
+ file_helper_.ReportError(message_handler, "setting world-readble: %s");
+ }
+ return ret;
+ }
+
+ private:
+ StdioFileHelper file_helper_;
+
+ DISALLOW_COPY_AND_ASSIGN(StdioOutputFile);
+};
+
+StdioFileSystem::~StdioFileSystem() {
+}
+
+FileSystem::InputFile* StdioFileSystem::OpenInputFile(
+ const char* filename, MessageHandler* message_handler) {
+ FileSystem::InputFile* input_file = NULL;
+ FILE* f = fopen(filename, "r");
+ if (f == NULL) {
+ message_handler->Error(filename, 0, "opening input file: %s",
+ strerror(errno));
+ } else {
+ input_file = new StdioInputFile(f, filename);
+ }
+ return input_file;
+}
+
+
+FileSystem::OutputFile* StdioFileSystem::OpenOutputFileHelper(
+ const char* filename, MessageHandler* message_handler) {
+ FileSystem::OutputFile* output_file = NULL;
+ if (strcmp(filename, "-") == 0) {
+ output_file = new StdioOutputFile(stdout, "<stdout>");
+ } else {
+ FILE* f = fopen(filename, "w");
+ if (f == NULL) {
+ message_handler->Error(filename, 0,
+ "opening output file: %s", strerror(errno));
+ } else {
+ output_file = new StdioOutputFile(f, filename);
+ }
+ }
+ return output_file;
+}
+
+FileSystem::OutputFile* StdioFileSystem::OpenTempFileHelper(
+ const StringPiece& prefix, MessageHandler* message_handler) {
+ // TODO(jmarantz): As jmaessen points out, mkstemp warns "Don't use
+ // this function, use tmpfile(3) instead. It is better defined and
+ // more portable." However, tmpfile does not allow a location to be
+ // specified. I'm not 100% sure if that's going to be work well for
+ // us. More importantly, our usage scenario is that we will be
+ // closing the file and renaming it to a permanent name. tmpfiles
+ // automatically are deleted when they are closed.
+ int prefix_len = prefix.length();
+ static char mkstemp_hook[] = "XXXXXX";
+ char* template_name = new char[prefix_len + sizeof(mkstemp_hook)];
+ memcpy(template_name, prefix.data(), prefix_len);
+ memcpy(template_name + prefix_len, mkstemp_hook, sizeof(mkstemp_hook));
+ int fd = mkstemp(template_name);
+ OutputFile* output_file = NULL;
+ if (fd < 0) {
+ message_handler->Error(template_name, 0,
+ "opening temp file: %s", strerror(errno));
+ } else {
+ FILE* f = fdopen(fd, "w");
+ if (f == NULL) {
+ close(fd);
+ message_handler->Error(template_name, 0,
+ "re-opening temp file: %s", strerror(errno));
+ } else {
+ output_file = new StdioOutputFile(f, template_name);
+ }
+ }
+ delete [] template_name;
+ return output_file;
+}
+
+
+bool StdioFileSystem::RemoveFile(const char* filename,
+ MessageHandler* handler) {
+ bool ret = (remove(filename) == 0);
+ if (!ret) {
+ handler->Message(kError, "Failed to delete file %s: %s",
+ filename, strerror(errno));
+ }
+ return ret;
+}
+
+bool StdioFileSystem::RenameFileHelper(const char* old_file,
+ const char* new_file,
+ MessageHandler* handler) {
+ bool ret = (rename(old_file, new_file) == 0);
+ if (!ret) {
+ handler->Message(kError, "Failed to rename file %s to %s: %s",
+ old_file, new_file, strerror(errno));
+ }
+ return ret;
+}
+
+bool StdioFileSystem::MakeDir(const char* path, MessageHandler* handler) {
+ // Mode 0777 makes the file use standard umask permissions.
+ bool ret = (mkdir(path, 0777) == 0);
+ if (!ret) {
+ handler->Message(kError, "Failed to make directory %s: %s",
+ path, strerror(errno));
+ }
+ return ret;
+}
+
+BoolOrError StdioFileSystem::Exists(const char* path, MessageHandler* handler) {
+ struct stat statbuf;
+ BoolOrError ret(stat(path, &statbuf) == 0);
+ if (ret.is_false() && errno != ENOENT) { // Not error if file doesn't exist.
+ handler->Message(kError, "Failed to stat %s: %s",
+ path, strerror(errno));
+ ret.set_error();
+ }
+ return ret;
+}
+
+BoolOrError StdioFileSystem::IsDir(const char* path, MessageHandler* handler) {
+ struct stat statbuf;
+ BoolOrError ret(false);
+ if (stat(path, &statbuf) == 0) {
+ ret.set(S_ISDIR(statbuf.st_mode));
+ } else if (errno != ENOENT) { // Not an error if file doesn't exist.
+ handler->Message(kError, "Failed to stat %s: %s",
+ path, strerror(errno));
+ ret.set_error();
+ }
+ return ret;
+}
+
+bool StdioFileSystem::ListContents(const StringPiece& dir, StringVector* files,
+ MessageHandler* handler) {
+ std::string dir_string = dir.as_string();
+ EnsureEndsInSlash(&dir_string);
+ const char* dirname = dir_string.c_str();
+ DIR* mydir = opendir(dirname);
+ if (mydir == NULL) {
+ handler->Error(dirname, 0, "Failed to opendir: %s", strerror(errno));
+ return false;
+ } else {
+ dirent* entry = NULL;
+ dirent buffer;
+ while (readdir_r(mydir, &buffer, &entry) == 0 && entry != NULL) {
+ if ((strcmp(entry->d_name, ".") != 0) &&
+ (strcmp(entry->d_name, "..") != 0)) {
+ files->push_back(dir_string + entry->d_name);
+ }
+ }
+ if (closedir(mydir) != 0) {
+ handler->Error(dirname, 0, "Failed to closedir: %s", strerror(errno));
+ return false;
+ }
+ return true;
+ }
+}
+
+bool StdioFileSystem::Atime(const StringPiece& path, int64* timestamp_sec,
+ MessageHandler* handler) {
+ // TODO(abliss): there are some situations where this doesn't work
+ // -- e.g. if the filesystem is mounted noatime. We should try to
+ // detect that and provide a workaround.
+ const std::string path_string = path.as_string();
+ const char* path_str = path_string.c_str();
+ struct stat statbuf;
+ if (stat(path_str, &statbuf) == 0) {
+ *timestamp_sec = statbuf.st_atime;
+ return true;
+ } else {
+ handler->Message(kError, "Failed to stat %s: %s",
+ path_str, strerror(errno));
+ return false;
+ }
+}
+
+bool StdioFileSystem::Size(const StringPiece& path, int64* size,
+ MessageHandler* handler) {
+ const std::string path_string = path.as_string();
+ const char* path_str = path_string.c_str();
+ struct stat statbuf;
+ if (stat(path_str, &statbuf) == 0) {
+ *size = statbuf.st_size;
+ return true;
+ } else {
+ handler->Message(kError, "Failed to stat %s: %s",
+ path_str, strerror(errno));
+ return false;
+ }
+}
+
+BoolOrError StdioFileSystem::TryLock(const StringPiece& lock_name,
+ MessageHandler* handler) {
+ const std::string lock_string = lock_name.as_string();
+ const char* lock_str = lock_string.c_str();
+ // POSIX mkdir is widely believed to be atomic, although I have
+ // found no reliable documentation of this fact.
+ if (mkdir(lock_str, 0777) == 0) {
+ return BoolOrError(true);
+ } else if (errno == EEXIST) {
+ return BoolOrError(false);
+ } else {
+ handler->Message(kError, "Failed to mkdir %s: %s",
+ lock_str, strerror(errno));
+ return BoolOrError();
+ }
+}
+
+bool StdioFileSystem::Unlock(const StringPiece& lock_name,
+ MessageHandler* handler) {
+ const std::string lock_string = lock_name.as_string();
+ const char* lock_str = lock_string.c_str();
+ if (rmdir(lock_str) == 0) {
+ return true;
+ } else {
+ handler->Message(kError, "Failed to rmdir %s: %s",
+ lock_str, strerror(errno));
+ return false;
+ }
+}
+
+FileSystem::InputFile* StdioFileSystem::Stdin() {
+ return new StdioInputFile(stdin, "stdin");
+}
+
+FileSystem::OutputFile* StdioFileSystem::Stdout() {
+ return new StdioOutputFile(stdout, "stdout");
+}
+
+FileSystem::OutputFile* StdioFileSystem::Stderr() {
+ return new StdioOutputFile(stderr, "stderr");
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/string_convert.cc b/trunk/src/net/instaweb/util/string_convert.cc
new file mode 100644
index 0000000..2316518
--- /dev/null
+++ b/trunk/src/net/instaweb/util/string_convert.cc
@@ -0,0 +1,49 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+bool AccumulateDecimalValue(char c, int* value) {
+ bool ret = true;
+ if ((c >= '0') && (c <= '9')) {
+ *value *= 10;
+ *value += c - '0';
+ } else {
+ ret = false;
+ }
+ return ret;
+}
+
+bool AccumulateHexValue(char c, int* value) {
+ int digit = 0;
+ if ((c >= '0') && (c <= '9')) {
+ digit = c - '0';
+ } else if ((c >= 'a') && (c <= 'f')) {
+ digit = 10 + c - 'a';
+ } else if ((c >= 'A') && (c <= 'F')) {
+ digit = 10 + c - 'A';
+ } else {
+ return false;
+ }
+ *value = *value * 16 + digit;
+ return true;
+}
+
+} // namespace
diff --git a/trunk/src/net/instaweb/util/string_util.cc b/trunk/src/net/instaweb/util/string_util.cc
new file mode 100644
index 0000000..990321b
--- /dev/null
+++ b/trunk/src/net/instaweb/util/string_util.cc
@@ -0,0 +1,152 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/string_util.h"
+#include <vector>
+
+namespace net_instaweb {
+
+void SplitStringPieceToVector(const StringPiece& sp, const char* separator,
+ std::vector<StringPiece>* components,
+ bool omit_empty_strings) {
+ size_t prev_pos = 0;
+ size_t pos = 0;
+ StringPiece sep(separator);
+ while ((pos = sp.find(sep, pos)) != StringPiece::npos) {
+ if (!omit_empty_strings || (pos > prev_pos)) {
+ components->push_back(sp.substr(prev_pos, pos - prev_pos));
+ }
+ ++pos;
+ prev_pos = pos;
+ }
+ if (!omit_empty_strings || (prev_pos < sp.size())) {
+ components->push_back(sp.substr(prev_pos, prev_pos - sp.size()));
+ }
+}
+
+std::string StrCat(const StringPiece& a,
+ const StringPiece& b,
+ const StringPiece& c,
+ const StringPiece& d,
+ const StringPiece& e,
+ const StringPiece& f,
+ const StringPiece& g,
+ const StringPiece& h) {
+ std::string res;
+ res.reserve(a.size() + b.size() + c.size() + d.size() + e.size() + f.size() +
+ g.size() + h.size());
+ a.AppendToString(&res);
+ b.AppendToString(&res);
+ c.AppendToString(&res);
+ d.AppendToString(&res);
+ e.AppendToString(&res);
+ f.AppendToString(&res);
+ g.AppendToString(&res);
+ h.AppendToString(&res);
+ return res;
+}
+
+void BackslashEscape(const StringPiece& src,
+ const StringPiece& to_escape,
+ std::string* dest) {
+ dest->reserve(dest->size() + src.size());
+ for (const char *p = src.data(), *end = src.data() + src.size();
+ p != end; ++p) {
+ if (to_escape.find(*p) != StringPiece::npos) {
+ dest->push_back('\\');
+ }
+ dest->push_back(*p);
+ }
+}
+
+bool StringCaseEqual(const StringPiece& s1, const StringPiece& s2) {
+ return (s1.size() == s2.size() &&
+ 0 == strncasecmp(s1.data(), s2.data(), s1.size()));
+}
+
+bool StringCaseStartsWith(const StringPiece& str, const StringPiece& prefix) {
+ return (str.size() >= prefix.size() &&
+ 0 == strncasecmp(str.data(), prefix.data(), prefix.size()));
+}
+
+bool StringCaseEndsWith(const StringPiece& str, const StringPiece& suffix) {
+ return (str.size() >= suffix.size() &&
+ 0 == strncasecmp(str.data() + str.size() - suffix.size(),
+ suffix.data(), suffix.size()));
+}
+
+void ParseShellLikeString(const StringPiece& input,
+ std::vector<std::string>* output) {
+ output->clear();
+ for (size_t index = 0; index < input.size();) {
+ const char ch = input[index];
+ // If we see a quoted section, treat it as a single item even if there are
+ // spaces in it.
+ if (ch == '"' || ch == '\'') {
+ const char quote = ch;
+ ++index; // skip open quote
+ output->push_back("");
+ std::string& part = output->back();
+ for (; index < input.size() && input[index] != quote; ++index) {
+ if (input[index] == '\\') {
+ ++index; // skip backslash
+ if (index >= input.size()) {
+ break;
+ }
+ }
+ part.push_back(input[index]);
+ }
+ ++index; // skip close quote
+ }
+ // Without quotes, items are whitespace-separated.
+ else if (!isspace(ch)) {
+ output->push_back("");
+ std::string& part = output->back();
+ for (;index < input.size() && !isspace(input[index]); ++index) {
+ part.push_back(input[index]);
+ }
+ }
+ // Ignore whitespace (outside of quotes).
+ else {
+ ++index;
+ }
+ }
+}
+
+// From src/third_party/protobuf2/src/src/google/protobuf/stubs/strutil.h
+// but we don't need any other aspect of protobufs so we don't want to
+// incur the link cost.
+void LowerString(std::string* s) {
+ std::string::iterator end = s->end();
+ for (std::string::iterator i = s->begin(); i != end; ++i) {
+ // tolower() changes based on locale. We don't want this!
+ if ('A' <= *i && *i <= 'Z') *i += 'a' - 'A';
+ }
+}
+
+// From src/third_party/protobuf2/src/src/google/protobuf/stubs/strutil.h
+// but we don't need any other aspect of protobufs so we don't want to
+// incur the link cost.
+bool HasPrefixString(const StringPiece& str, const StringPiece& prefix) {
+ return ((str.size() >= prefix.size()) &&
+ (str.substr(0, prefix.size()) == prefix));
+}
+
+const StringPiece EmptyString::kEmptyString;
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/string_util_test.cc b/trunk/src/net/instaweb/util/string_util_test.cc
new file mode 100644
index 0000000..9bafc83
--- /dev/null
+++ b/trunk/src/net/instaweb/util/string_util_test.cc
@@ -0,0 +1,260 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+// Unit-test the string-splitter.
+
+#include <vector>
+
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/gtest.h"
+
+namespace net_instaweb {
+
+class IntegerToStringToIntTest : public testing::Test {
+ protected:
+ void ValidateIntegerToString(int i, std::string s) {
+ EXPECT_EQ(s, IntegerToString(i));
+ ValidateInteger64ToString(static_cast<int64>(i), s);
+ }
+
+ void ValidateStringToInt(std::string s, int i) {
+ int i2;
+ EXPECT_TRUE(StringToInt(s, &i2));
+ EXPECT_EQ(i, i2);
+ ValidateStringToInt64(s, static_cast<int64>(i));
+ }
+
+ void InvalidStringToInt(std::string s) {
+ int i;
+ EXPECT_FALSE(StringToInt(s, &i));
+ InvalidStringToInt64(s);
+ }
+
+ void ValidateIntegerToStringToInt(int i) {
+ ValidateStringToInt(IntegerToString(i), i);
+ }
+
+ // Second verse, same as the first, a little more bits...
+ void ValidateInteger64ToString(int64 i, std::string s) {
+ EXPECT_EQ(s, Integer64ToString(i));
+ }
+
+ void ValidateStringToInt64(std::string s, int64 i) {
+ int64 i2;
+ EXPECT_TRUE(StringToInt64(s, &i2));
+ EXPECT_EQ(i, i2);
+ }
+
+ void InvalidStringToInt64(std::string s) {
+ int64 i;
+ EXPECT_FALSE(StringToInt64(s, &i));
+ }
+
+ void ValidateInteger64ToStringToInt64(int64 i) {
+ ValidateStringToInt64(Integer64ToString(i), i);
+ }
+};
+
+TEST_F(IntegerToStringToIntTest, TestIntegerToString) {
+ ValidateIntegerToString(0, "0");
+ ValidateIntegerToString(1, "1");
+ ValidateIntegerToString(10, "10");
+ ValidateIntegerToString(-5, "-5");
+ ValidateIntegerToString(123456789, "123456789");
+ ValidateIntegerToString(-123456789, "-123456789");
+ ValidateInteger64ToString(99123456789LL, "99123456789");
+ ValidateInteger64ToString(-99123456789LL, "-99123456789");
+}
+
+TEST_F(IntegerToStringToIntTest, TestStringToInt) {
+ ValidateStringToInt("0", 0);
+ ValidateStringToInt("1", 1);
+ ValidateStringToInt("10", 10);
+ ValidateStringToInt("-5", -5);
+ ValidateStringToInt("+5", 5);
+ ValidateStringToInt("123456789", 123456789);
+ ValidateStringToInt("-123456789", -123456789);
+ ValidateStringToInt("00000", 0);
+ ValidateStringToInt("010", 10);
+ ValidateStringToInt("-0000005", -5);
+ ValidateStringToInt("-00089", -89);
+ ValidateStringToInt64("-99123456789", -99123456789LL);
+}
+
+TEST_F(IntegerToStringToIntTest, TestInvalidString) {
+ InvalidStringToInt("");
+ InvalidStringToInt("-");
+ InvalidStringToInt("+");
+ InvalidStringToInt("--1");
+ InvalidStringToInt("++1");
+ InvalidStringToInt("1-");
+ InvalidStringToInt("1+");
+ InvalidStringToInt("1 000");
+ InvalidStringToInt("a");
+ InvalidStringToInt("1e2");
+ InvalidStringToInt("10^3");
+ InvalidStringToInt("1+3");
+ InvalidStringToInt("0x6A7");
+}
+
+TEST_F(IntegerToStringToIntTest, TestIntegerToStringToInt) {
+ int n = 1;
+ for (int i = 0; i < 1000; ++i) {
+ ValidateIntegerToStringToInt(n);
+ n *= -3; // This will overflow, that's fine, we just want a range of ints.
+ }
+ int64 n64 = 1LL;
+ for (int i = 0; i < 1000; ++i) {
+ ValidateInteger64ToStringToInt64(n64);
+ n64 *= -3; // This will overflow, that's fine, we just want a range of ints
+ }
+}
+
+class SplitStringTest : public testing::Test {
+};
+
+TEST_F(SplitStringTest, TestSplitNoOmitTrailing) {
+ std::vector<StringPiece> components;
+ SplitStringPieceToVector(".a.b..c.", ".", &components, false);
+ ASSERT_EQ(static_cast<size_t>(6), components.size());
+ ASSERT_EQ("", components[0]);
+ ASSERT_EQ("a", components[1]);
+ ASSERT_EQ("b", components[2]);
+ ASSERT_EQ("", components[3]);
+ ASSERT_EQ("c", components[4]);
+ ASSERT_EQ("", components[5]);
+}
+
+TEST_F(SplitStringTest, TestSplitNoOmitNoTrailing) {
+ std::vector<StringPiece> components;
+ SplitStringPieceToVector(".a.b..c", ".", &components, false);
+ ASSERT_EQ(static_cast<size_t>(5), components.size());
+ ASSERT_EQ("", components[0]);
+ ASSERT_EQ("a", components[1]);
+ ASSERT_EQ("b", components[2]);
+ ASSERT_EQ("", components[3]);
+ ASSERT_EQ("c", components[4]);
+}
+
+TEST_F(SplitStringTest, TestSplitNoOmitEmpty) {
+ std::vector<StringPiece> components;
+ SplitStringPieceToVector("", ".", &components, false);
+ ASSERT_EQ(static_cast<size_t>(1), components.size());
+ ASSERT_EQ("", components[0]);
+}
+
+TEST_F(SplitStringTest, TestSplitNoOmitOneDot) {
+ std::vector<StringPiece> components;
+ SplitStringPieceToVector(".", ".", &components, false);
+ ASSERT_EQ(static_cast<size_t>(2), components.size());
+ ASSERT_EQ("", components[0]);
+ ASSERT_EQ("", components[1]);
+}
+
+TEST_F(SplitStringTest, TestSplitOmitTrailing) {
+ std::vector<StringPiece> components;
+ SplitStringPieceToVector(".a.b..c.", ".", &components, true);
+ ASSERT_EQ(static_cast<size_t>(3), components.size());
+ ASSERT_EQ("a", components[0]);
+ ASSERT_EQ("b", components[1]);
+ ASSERT_EQ("c", components[2]);
+}
+
+TEST_F(SplitStringTest, TestSplitOmitNoTrailing) {
+ std::vector<StringPiece> components;
+ SplitStringPieceToVector(".a.b..c", ".", &components, true);
+ ASSERT_EQ(static_cast<size_t>(3), components.size());
+ ASSERT_EQ("a", components[0]);
+ ASSERT_EQ("b", components[1]);
+ ASSERT_EQ("c", components[2]);
+}
+
+TEST_F(SplitStringTest, TestSplitOmitEmpty) {
+ std::vector<StringPiece> components;
+ SplitStringPieceToVector("", ".", &components, true);
+ ASSERT_EQ(static_cast<size_t>(0), components.size());
+}
+
+TEST_F(SplitStringTest, TestSplitOmitOneDot) {
+ std::vector<StringPiece> components;
+ SplitStringPieceToVector(".", ".", &components, true);
+ ASSERT_EQ(static_cast<size_t>(0), components.size());
+}
+
+TEST(StringCaseTest, TestStringCaseEqual) {
+ EXPECT_FALSE(StringCaseEqual("foobar", "fobar"));
+ EXPECT_TRUE(StringCaseEqual("foobar", "foobar"));
+ EXPECT_TRUE(StringCaseEqual("foobar", "FOOBAR"));
+ EXPECT_TRUE(StringCaseEqual("FOOBAR", "foobar"));
+ EXPECT_TRUE(StringCaseEqual("fOoBaR", "FoObAr"));
+}
+
+TEST(StringCaseTest, TestStringCaseStartsWith) {
+ EXPECT_FALSE(StringCaseStartsWith("foobar", "fob"));
+ EXPECT_TRUE(StringCaseStartsWith("foobar", "foobar"));
+ EXPECT_TRUE(StringCaseStartsWith("foobar", "foo"));
+ EXPECT_TRUE(StringCaseStartsWith("foobar", "FOO"));
+ EXPECT_TRUE(StringCaseStartsWith("FOOBAR", "foo"));
+ EXPECT_TRUE(StringCaseStartsWith("fOoBaR", "FoO"));
+ EXPECT_FALSE(StringCaseStartsWith("zzz", "zzzz"));
+}
+
+TEST(StringCaseTest, TestStringCaseEndsWith) {
+ EXPECT_FALSE(StringCaseEndsWith("foobar", "baar"));
+ EXPECT_TRUE(StringCaseEndsWith("foobar", "foobar"));
+ EXPECT_TRUE(StringCaseEndsWith("foobar", "bar"));
+ EXPECT_TRUE(StringCaseEndsWith("foobar", "BAR"));
+ EXPECT_TRUE(StringCaseEndsWith("FOOBAR", "bar"));
+ EXPECT_TRUE(StringCaseEndsWith("fOoBaR", "bAr"));
+ EXPECT_FALSE(StringCaseEndsWith("zzz", "zzzz"));
+}
+
+TEST(ParseShellLikeStringTest, TestParse) {
+ std::vector<std::string> parts;
+ ParseShellLikeString("a b \"c d\" e 'f g'", &parts);
+ ASSERT_EQ(5, parts.size());
+ EXPECT_EQ("a", parts[0]);
+ EXPECT_EQ("b", parts[1]);
+ EXPECT_EQ("c d", parts[2]);
+ EXPECT_EQ("e", parts[3]);
+ EXPECT_EQ("f g", parts[4]);
+}
+
+TEST(ParseShellLikeStringTest, Backslash) {
+ std::vector<std::string> parts;
+ ParseShellLikeString(" \"a\\\"b\" 'c\\'d' ", &parts);
+ ASSERT_EQ(2, parts.size());
+ EXPECT_EQ("a\"b", parts[0]);
+ EXPECT_EQ("c'd", parts[1]);
+}
+
+TEST(ParseShellLikeStringTest, UnclosedQuote) {
+ std::vector<std::string> parts;
+ ParseShellLikeString("'a b", &parts);
+ ASSERT_EQ(1, parts.size());
+ EXPECT_EQ("a b", parts[0]);
+}
+
+TEST(ParseShellLikeStringTest, UnclosedQuoteAndBackslash) {
+ std::vector<std::string> parts;
+ ParseShellLikeString("'a b\\", &parts);
+ ASSERT_EQ(1, parts.size());
+ EXPECT_EQ("a b", parts[0]);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/string_writer.cc b/trunk/src/net/instaweb/util/string_writer.cc
new file mode 100644
index 0000000..b6b5d66
--- /dev/null
+++ b/trunk/src/net/instaweb/util/string_writer.cc
@@ -0,0 +1,35 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/string_writer.h"
+
+namespace net_instaweb {
+
+StringWriter::~StringWriter() {
+}
+
+bool StringWriter::Write(const StringPiece& str, MessageHandler* handler) {
+ string_->append(str.data(), str.size());
+ return true;
+}
+
+bool StringWriter::Flush(MessageHandler* message_handler) {
+ return true;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/symbol_table_test.cc b/trunk/src/net/instaweb/util/symbol_table_test.cc
new file mode 100644
index 0000000..d604ad9
--- /dev/null
+++ b/trunk/src/net/instaweb/util/symbol_table_test.cc
@@ -0,0 +1,73 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+// Unit-test the symbol table
+
+#include "net/instaweb/util/public/symbol_table.h"
+#include "base/logging.h"
+#include "net/instaweb/util/public/gtest.h"
+#include <string>
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+class SymbolTableTest : public testing::Test {
+};
+
+TEST_F(SymbolTableTest, TestInternSensitive) {
+ SymbolTableSensitive symbol_table;
+ std::string s1("hello");
+ std::string s2("hello");
+ std::string s3("goodbye");
+ std::string s4("Goodbye");
+ EXPECT_NE(s1.c_str(), s2.c_str());
+ Atom a1 = symbol_table.Intern(s1);
+ Atom a2 = symbol_table.Intern(s2);
+ Atom a3 = symbol_table.Intern(s3);
+ Atom a4 = symbol_table.Intern(s4);
+ EXPECT_TRUE(a1 == a2);
+ EXPECT_EQ(a1.c_str(), a2.c_str());
+ EXPECT_FALSE(a1 == a3);
+ EXPECT_NE(a1.c_str(), a3.c_str());
+ EXPECT_FALSE(a3 == a4);
+
+ EXPECT_EQ(s1, a1.c_str());
+ EXPECT_EQ(s2, a2.c_str());
+ EXPECT_EQ(s3, a3.c_str());
+ EXPECT_EQ(s4, a4.c_str());
+}
+
+TEST_F(SymbolTableTest, TestInternInsensitive) {
+ SymbolTableInsensitive symbol_table;
+ std::string s1("hello");
+ std::string s2("Hello");
+ std::string s3("goodbye");
+ Atom a1 = symbol_table.Intern(s1);
+ Atom a2 = symbol_table.Intern(s2);
+ Atom a3 = symbol_table.Intern(s3);
+ EXPECT_TRUE(a1 == a2);
+ EXPECT_EQ(a1.c_str(), a2.c_str());
+ EXPECT_FALSE(a1 == a3);
+ EXPECT_NE(a1.c_str(), a3.c_str());
+
+ EXPECT_EQ(0, strcasecmp(s1.c_str(), a1.c_str()));
+ EXPECT_EQ(0, strcasecmp(s2.c_str(), a2.c_str()));
+ EXPECT_EQ(0, strcasecmp(s3.c_str(), a3.c_str()));
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/threadsafe_cache.cc b/trunk/src/net/instaweb/util/threadsafe_cache.cc
new file mode 100644
index 0000000..b131fe9
--- /dev/null
+++ b/trunk/src/net/instaweb/util/threadsafe_cache.cc
@@ -0,0 +1,47 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/threadsafe_cache.h"
+#include "net/instaweb/util/public/abstract_mutex.h"
+
+namespace net_instaweb {
+
+ThreadsafeCache::~ThreadsafeCache() {
+}
+
+bool ThreadsafeCache::Get(const std::string& key, SharedString* value) {
+ ScopedMutex mutex(mutex_);
+ return cache_->Get(key, value);
+}
+
+void ThreadsafeCache::Put(const std::string& key, SharedString* value) {
+ ScopedMutex mutex(mutex_);
+ cache_->Put(key, value);
+}
+
+void ThreadsafeCache::Delete(const std::string& key) {
+ ScopedMutex mutex(mutex_);
+ cache_->Delete(key);
+}
+
+CacheInterface::KeyState ThreadsafeCache::Query(const std::string& key) {
+ ScopedMutex mutex(mutex_);
+ return cache_->Query(key);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/time_util.cc b/trunk/src/net/instaweb/util/time_util.cc
new file mode 100644
index 0000000..c16f3ca
--- /dev/null
+++ b/trunk/src/net/instaweb/util/time_util.cc
@@ -0,0 +1,66 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/time_util.h"
+#include <time.h>
+
+#include "pagespeed/core/resource_util.h"
+
+namespace net_instaweb {
+
+bool ConvertTimeToString(int64 time_ms, std::string* time_string) {
+ time_t time_sec = time_ms / 1000;
+ struct tm time_buf;
+ struct tm* time_info = gmtime_r(&time_sec, &time_buf);
+ if ((time_info == NULL) ||
+ (time_buf.tm_wday < 0) ||
+ (time_buf.tm_wday > 6) ||
+ (time_buf.tm_mon < 0) ||
+ (time_buf.tm_mon > 11)) {
+ return false;
+ }
+
+ static const char* kWeekDay[] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+ static const char* kMonth[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov",
+ "Dec"};
+
+ // RFC 822 says to format like this:
+ // Thu Nov 18 02:15:22 2010 GMT
+ // See http://www.faqs.org/rfcs/rfc822.html
+ //
+ // But redbot.org likes this:
+ // Wed, 24 Nov 2010 21:14:12 GMT
+ *time_string = StringPrintf("%s, %02d %s %4d %02d:%02d:%02d GMT",
+ kWeekDay[time_buf.tm_wday],
+ time_buf.tm_mday,
+ kMonth[time_buf.tm_mon],
+ 1900 + time_buf.tm_year,
+ time_buf.tm_hour,
+ time_buf.tm_min,
+ time_buf.tm_sec);
+ return true;
+}
+
+bool ConvertStringToTime(const StringPiece& time_string, int64 *time_ms) {
+ std::string buf(time_string.data(), time_string.size());
+ return pagespeed::resource_util::ParseTimeValuedHeader(buf.c_str(), time_ms);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/time_util_test.cc b/trunk/src/net/instaweb/util/time_util_test.cc
new file mode 100644
index 0000000..8b6244c
--- /dev/null
+++ b/trunk/src/net/instaweb/util/time_util_test.cc
@@ -0,0 +1,62 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/time_util.h"
+#include "net/instaweb/util/public/gtest.h"
+
+namespace {
+
+const char kApr5[] = "Mon, 05 Apr 2010 18:49:46 GMT";
+
+// The time-conversion functions are only accurate to the second,
+// and we will not be able to test for identity transforms if we
+// are not using a mulitple of 1000.
+const int64 kTimestampMs = 718981 * 1000;
+
+} // namespace
+
+namespace net_instaweb {
+
+class TimeUtilTest : public testing::Test {
+ protected:
+ std::string GetTimeString(int64 time_ms) {
+ std::string out;
+ EXPECT_TRUE(ConvertTimeToString(time_ms, &out));
+ return out;
+ }
+
+ int64 GetTimeValue(const std::string& time_str) {
+ int64 val;
+ CHECK(ConvertStringToTime(time_str, &val)) << "Time conversion failed";
+ return val;
+ }
+
+ std::string out_;
+};
+
+TEST_F(TimeUtilTest, Test1970) {
+ EXPECT_EQ("Thu, 01 Jan 1970 00:00:00 GMT", GetTimeString(0));
+ EXPECT_EQ(1270493386000LL, GetTimeValue(kApr5));
+}
+
+TEST_F(TimeUtilTest, TestIdentity) {
+ EXPECT_EQ(kTimestampMs, GetTimeValue(GetTimeString(kTimestampMs)));
+ EXPECT_EQ(kApr5, GetTimeString(GetTimeValue(kApr5)));
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/timer.cc b/trunk/src/net/instaweb/util/timer.cc
new file mode 100644
index 0000000..a0e48a8
--- /dev/null
+++ b/trunk/src/net/instaweb/util/timer.cc
@@ -0,0 +1,43 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/timer.h"
+
+namespace net_instaweb {
+
+const int64 Timer::kSecondUs = 1000 * 1000;
+const int64 Timer::kSecondMs = 1000;
+const int64 Timer::kMinuteMs = 60 * Timer::kSecondMs;
+const int64 Timer::kHourMs = 60 * Timer::kMinuteMs;
+const int64 Timer::kDayMs = 24 * Timer::kHourMs;
+const int64 Timer::kWeekMs = 7 * Timer::kDayMs;
+const int64 Timer::kMonthMs = 31 * Timer::kDayMs;
+const int64 Timer::kYearMs = 365 * Timer::kDayMs;
+
+Timer::~Timer() {
+}
+
+int64 Timer::NowMs() const {
+ return NowUs() / 1000;
+}
+
+void Timer::SleepMs(int64 ms) {
+ SleepUs(ms * 1000);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/timer_based_abstract_lock.cc b/trunk/src/net/instaweb/util/timer_based_abstract_lock.cc
new file mode 100644
index 0000000..e360fe1
--- /dev/null
+++ b/trunk/src/net/instaweb/util/timer_based_abstract_lock.cc
@@ -0,0 +1,126 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#include "net/instaweb/util/public/timer_based_abstract_lock.h"
+
+#include "net/instaweb/util/public/timer.h"
+
+namespace net_instaweb {
+
+namespace {
+
+// Number of times we busy spin before we start to sleep.
+int kBusySpinIterations = 100;
+
+// We back off exponentially, with a constant of 1.5.
+int64 Backoff(int64 interval_ms) {
+ return (1 + interval_ms + (interval_ms >> 1));
+}
+
+int64 IntervalWithEnd(Timer* timer, int64 interval_ms, int64 end_time_ms) {
+ int64 now_ms = timer->NowMs();
+ int64 remaining = end_time_ms - now_ms;
+ interval_ms = Backoff(interval_ms);
+ if (remaining > interval_ms) {
+ return interval_ms;
+ } else {
+ return remaining;
+ }
+}
+
+} // namespace
+
+TimerBasedAbstractLock::~TimerBasedAbstractLock() { }
+
+void TimerBasedAbstractLock::Lock() {
+ if (TryLock() ||
+ BusySpin(&TimerBasedAbstractLock::TryLockIgnoreTimeout, 0)) {
+ return;
+ }
+ Spin(&TimerBasedAbstractLock::TryLockIgnoreTimeout, 0);
+}
+
+bool TimerBasedAbstractLock::LockTimedWait(int64 wait_ms) {
+ return (TryLock() ||
+ SpinFor(&TimerBasedAbstractLock::TryLockIgnoreTimeout, 0, wait_ms));
+}
+
+void TimerBasedAbstractLock::LockStealOld(int64 timeout_ms) {
+ if (LockTimedWaitStealOld(timeout_ms, timeout_ms)) {
+ return;
+ }
+ // Contention! Restart spin, but don't decay again in future.
+ Spin(&TimerBasedAbstractLock::TryLockStealOld, timeout_ms);
+}
+
+bool TimerBasedAbstractLock::LockTimedWaitStealOld(
+ int64 wait_ms, int64 timeout_ms) {
+ return
+ (TryLock() ||
+ SpinFor(&TimerBasedAbstractLock::TryLockStealOld, timeout_ms, wait_ms));
+}
+
+// For uniformity, we implement spinning without regard to whether the
+// underlying lock primitive can time out or not. This wrapper method is just
+// TryLock with a bogus timeout parameter for uniformity.
+bool TimerBasedAbstractLock::TryLockIgnoreTimeout(int64 timeout_ignored) {
+ return TryLock();
+}
+
+// Actively attempt to take lock without pausing.
+bool TimerBasedAbstractLock::BusySpin(TryLockMethod try_lock,
+ int64 timeout_ms) {
+ for (int i = 0; i < kBusySpinIterations; i++) {
+ if ((this->*try_lock)(timeout_ms)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Attempt to take lock, backing off forever.
+void TimerBasedAbstractLock::Spin(TryLockMethod try_lock, int64 timeout_ms) {
+ Timer* the_timer = timer();
+ int64 interval_ms = 1;
+ while (!(this->*try_lock)(timeout_ms)) {
+ the_timer->SleepMs(interval_ms);
+ interval_ms = Backoff(interval_ms);
+ // interval_ms can't realistically overflow in geologic time.
+ }
+}
+
+// Attempt to take lock until end_time_ms.
+bool TimerBasedAbstractLock::SpinFor(TryLockMethod try_lock, int64 timeout_ms,
+ int64 wait_ms) {
+ int64 end_time_ms = timer()->NowMs() + wait_ms;
+ if (BusySpin(try_lock, timeout_ms)) {
+ return true;
+ }
+ Timer* the_timer = timer();
+ int64 interval_ms = IntervalWithEnd(the_timer, 0, end_time_ms);
+ bool result;
+ // Ugh. Here we're spinning until result is true (we got the lock), or we run
+ // out of time.
+ while (!((result = (this->*try_lock)(timeout_ms))) && interval_ms > 0) {
+ the_timer->SleepMs(interval_ms);
+ interval_ms = IntervalWithEnd(the_timer, interval_ms, end_time_ms);
+ }
+ return result;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/url_async_fetcher.cc b/trunk/src/net/instaweb/util/url_async_fetcher.cc
new file mode 100644
index 0000000..4ed8674
--- /dev/null
+++ b/trunk/src/net/instaweb/util/url_async_fetcher.cc
@@ -0,0 +1,39 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/url_async_fetcher.h"
+#include "net/instaweb/util/public/meta_data.h"
+#include "net/instaweb/util/public/string_writer.h"
+
+namespace net_instaweb {
+
+const int64 UrlAsyncFetcher::kUnspecifiedTimeout = 0;
+
+UrlAsyncFetcher::~UrlAsyncFetcher() {
+}
+
+UrlAsyncFetcher::Callback::~Callback() {
+}
+
+bool UrlAsyncFetcher::Callback::EnableThreaded() const {
+ // Most fetcher callbacks are not prepared to be called from a different
+ // thread.
+ return false;
+}
+
+} // namespace instaweb
diff --git a/trunk/src/net/instaweb/util/url_escaper.cc b/trunk/src/net/instaweb/util/url_escaper.cc
new file mode 100644
index 0000000..6b75592
--- /dev/null
+++ b/trunk/src/net/instaweb/util/url_escaper.cc
@@ -0,0 +1,149 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/url_escaper.h"
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+namespace {
+
+// Firefox converts ^ to a % sequence.
+// Apache rejects requests with % sequences it does not understand.
+// So limit the pass-through characters as follows, and use ',' as
+// an escaper.
+//
+// Unfortunately this makes longer filenames because ',' is also used
+// in the filenam encoder.
+//
+// TODO(jmarantz): Pass through '.', and exploit '/' as a legal character
+// in URLs. This requires redefining the constraints of a 'segment', which
+// currently excludes both '.' and '/' due to rules enforced primarily
+// in net/instaweb/rewriter/resource_manager.cc, but are distributed a bit
+// more widely.
+const char kPassThroughChars[] = "._=+-";
+
+// Checks for 'search' at start of 'src'. If found, appends
+// 'replacement' into 'out', and advances the start-point in 'src'
+// past the search string, returning true.
+bool ReplaceSubstring(const StringPiece& search, const char* replacement,
+ StringPiece* src, std::string* out) {
+ bool ret = false;
+ if ((src->size() >= search.size()) &&
+ (memcmp(src->data(), search.data(), search.size()) == 0)) {
+ out->append(replacement);
+ *src = src->substr(search.size());
+ ret = true;
+ }
+ return ret;
+}
+
+} // namespace
+
+UrlEscaper::~UrlEscaper() { }
+
+void UrlEscaper::EncodeToUrlSegment(const StringPiece& in,
+ std::string* url_segment) {
+ for (StringPiece src = in; src.size() != 0; ) {
+ // We need to check for common prefixes that begin with pass-through
+ // characters before doing the isalnum check.
+ if (!ReplaceSubstring("http://", ",h", &src, url_segment) &&
+ !ReplaceSubstring(".pagespeed.", ",M", &src, url_segment)) {
+ char c = src[0];
+ if (isalnum(c) || (strchr(kPassThroughChars, c) != NULL)) {
+ url_segment->append(1, c);
+ src = src.substr(1);
+ } else if (
+ // TODO(jmarantz): put these in a static table and generate
+ // an FSM so we don't have so much lookahed scanning, and we
+ // don't have to work hard to keep the encoder and decoder
+ // in sync.
+ !ReplaceSubstring("^", ",u", &src, url_segment) &&
+ !ReplaceSubstring("%", ",P", &src, url_segment) &&
+ !ReplaceSubstring("/", ",_", &src, url_segment) &&
+ !ReplaceSubstring("\\", ",-", &src, url_segment) &&
+ !ReplaceSubstring(",", ",,", &src, url_segment) &&
+ !ReplaceSubstring("?", ",q", &src, url_segment) &&
+ !ReplaceSubstring("&", ",a", &src, url_segment)) {
+ url_segment->append(StringPrintf(",%02X",
+ static_cast<unsigned char>(c)));
+ src = src.substr(1);
+ }
+ }
+ }
+}
+
+bool UrlEscaper::DecodeFromUrlSegment(const StringPiece& url_segment,
+ std::string* out) {
+ int remaining = url_segment.size();
+ for (const char* p = url_segment.data(); remaining != 0; ++p, --remaining) {
+ char c = *p;
+ if (isalnum(c) || (strchr(kPassThroughChars, c) != NULL)) {
+ out->append(&c, 1);
+ } else if ((c != ',') || (remaining < 2)) {
+ return false; // unknown char or trailing ,; this is an invalid encoding.
+ } else {
+ ++p;
+ --remaining;
+ switch (*p) {
+ case '_': *out += "/"; break;
+ case '-': *out += "\\"; break;
+ case ',': *out += ","; break;
+ case 'a': *out += "&"; break;
+ case 'M': *out += ".pagespeed."; break;
+ case 'P': *out += "%"; break;
+ case 'q': *out += "?"; break;
+ case 'u': *out += "^"; break;
+
+ // The following legacy encodings are no longer made. However
+ // we should continue to decode what we previously encoded in
+ // November 2010 to avoid (for example) breaking image search.
+ case 'c': *out += ".com"; break;
+ case 'e': *out += ".edu"; break;
+ case 'g': *out += ".gif"; break;
+ case 'h': *out += "http://"; break;
+ case 'j': *out += ".jpg"; break;
+ case 'k': *out += ".jpeg"; break;
+ case 'l': *out += ".js"; break;
+ case 'n': *out += ".net"; break;
+ case 'o': *out += "."; break;
+ case 'p': *out += ".png"; break;
+ case 's': *out += ".css"; break;
+ case 't': *out += ".html"; break;
+ case 'w': *out += "www."; break;
+
+ default:
+ if (remaining < 2) {
+ return false;
+ }
+ --remaining;
+ int char_val = 0;
+ if (AccumulateHexValue(*p++, &char_val) &&
+ AccumulateHexValue(*p, &char_val)) {
+ out->append(1, static_cast<char>(char_val));
+ } else {
+ return false;
+ }
+ break;
+ }
+ }
+ }
+ return true;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/url_escaper_test.cc b/trunk/src/net/instaweb/util/url_escaper_test.cc
new file mode 100644
index 0000000..f314c5a
--- /dev/null
+++ b/trunk/src/net/instaweb/util/url_escaper_test.cc
@@ -0,0 +1,131 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/url_escaper.h"
+#include "net/instaweb/util/public/gtest.h"
+#include "net/instaweb/util/public/string_util.h"
+
+namespace {
+
+// We pass through a few special characters unchanged, and we
+// accept those characters, plus ',', as acceptable in the encoded
+// URLs.
+static const char kAcceptableSpecialChars[] = ",._+-=";
+static const char kPassThruChars[] = "._+-=";
+
+} // namespace
+
+namespace net_instaweb {
+
+class UrlEscaperTest : public testing::Test {
+ protected:
+ void CheckEncoding(const StringPiece& url) {
+ std::string encoded, decoded;
+ escaper_.EncodeToUrlSegment(url, &encoded);
+
+ // Make sure there are only alphanumerics and _+-=%
+ for (size_t i = 0; i < encoded.size(); ++i) {
+ char c = encoded[i];
+ EXPECT_TRUE(isalnum(c) || (strchr(kAcceptableSpecialChars, c) != NULL));
+ }
+
+ EXPECT_TRUE(escaper_.DecodeFromUrlSegment(encoded, &decoded));
+ EXPECT_EQ(url, decoded);
+ }
+
+ // Some basic text should be completely unchanged upon encode/decode.
+ void CheckUnchanged(const StringPiece& url) {
+ std::string encoded, decoded;
+ escaper_.EncodeToUrlSegment(url, &encoded);
+ EXPECT_EQ(url, encoded);
+ EXPECT_TRUE(escaper_.DecodeFromUrlSegment(encoded, &decoded));
+ EXPECT_EQ(url, decoded);
+ }
+
+ std::string Decode(const StringPiece& encoding) {
+ std::string decoded;
+ EXPECT_TRUE(escaper_.DecodeFromUrlSegment(encoding, &decoded));
+ return decoded;
+ }
+
+ std::string Encode(const StringPiece& url) {
+ std::string encoded;
+ escaper_.EncodeToUrlSegment(url, &encoded);
+ return encoded;
+ }
+
+ UrlEscaper escaper_;
+};
+
+TEST_F(UrlEscaperTest, TestUrls) {
+ CheckEncoding("http://www.google.com");
+ // Test encoding of % and lack of leading http:// (beware of double encoding):
+ CheckEncoding("//web.mit.edu/foo.cgi?bar%baz");
+ CheckEncoding("http://x.com/images/hacks.js.pagespeed.jm.GSLMcHP-fl.js");
+ CheckEncoding("http://www.foo.bar/z1234/b_c.d?e=f&g=h");
+ CheckEncoding("http://china.com/\u591a\u5e74\u7ecf\u5178\u5361\u7247\u673a");
+ CheckEncoding("http://中国 汪 世 孟");
+ CheckEncoding("/static/f.1.js?v=120");
+ CheckEncoding("!@#$%^&*()_+=-[]{}?><,./");
+}
+
+TEST_F(UrlEscaperTest, TestUnchanged) {
+ CheckUnchanged("abcdefghijklmnopqrstuvwxyz");
+ CheckUnchanged("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+ CheckUnchanged("0123456789");
+ CheckUnchanged("=+-_");
+ CheckUnchanged(kPassThruChars);
+}
+
+TEST_F(UrlEscaperTest, LegacyDecode) {
+ EXPECT_EQ("a.css", Decode("a,s"));
+ EXPECT_EQ("b.jpg", Decode("b,j"));
+ EXPECT_EQ("c.png", Decode("c,p"));
+ EXPECT_EQ("d.gif", Decode("d,g"));
+ EXPECT_EQ("e.jpeg", Decode("e,k"));
+ EXPECT_EQ("f.js", Decode("f,l"));
+ EXPECT_EQ("g.anything", Decode("g,oanything"));
+ EXPECT_EQ("http://www.myhost.com", Decode(",h,wmyhost,c"));
+}
+
+TEST_F(UrlEscaperTest, TestEncoding) {
+ // Special case encoding a common sequence that would be long and
+ // ugly to escape char-by-char. We used to encode more than this
+ // (e.g. .com -> ,c) but now that we can allow '.' in encoded names,
+ // we favor legibility over compactness and have dropped the encoding
+ // of ".com" and others. However http:// requires three characters
+ // to be decoded so we'll encode it in one piece.
+ EXPECT_EQ(",h", Encode("http://"));
+
+ // These common characters get special-case encodings.
+ EXPECT_EQ(",u", Encode("^"));
+ EXPECT_EQ(",P", Encode("%"));
+ EXPECT_EQ(",_", Encode("/"));
+ EXPECT_EQ(",-", Encode("\\"));
+ EXPECT_EQ(",,", Encode(","));
+ EXPECT_EQ(",q", Encode("?"));
+ EXPECT_EQ(",a", Encode("&"));
+ EXPECT_EQ(",M", Encode(".pagespeed."));
+ EXPECT_EQ(",hx.com,_images,_hacks.js,Mjm.GSLMcHP-fl.js",
+ Encode("http://x.com/images/hacks.js.pagespeed.jm.GSLMcHP-fl.js"));
+
+ // Other characters are simply hexified.
+ EXPECT_EQ(",3A", Encode(":"));
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/url_fetcher.cc b/trunk/src/net/instaweb/util/url_fetcher.cc
new file mode 100644
index 0000000..5156bde
--- /dev/null
+++ b/trunk/src/net/instaweb/util/url_fetcher.cc
@@ -0,0 +1,36 @@
+/**
+ * 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/util/public/url_fetcher.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include "net/instaweb/util/public/string_writer.h"
+
+namespace net_instaweb {
+
+UrlFetcher::~UrlFetcher() {
+}
+
+bool UrlFetcher::FetchUrl(const std::string& url, std::string* content,
+ MessageHandler* message_handler) {
+ StringWriter writer(content);
+ SimpleMetaData request_headers, response_headers;
+ return StreamingFetchUrl(url, request_headers, &response_headers, &writer,
+ message_handler);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/url_multipart_encoder.cc b/trunk/src/net/instaweb/util/url_multipart_encoder.cc
new file mode 100644
index 0000000..3aa057f
--- /dev/null
+++ b/trunk/src/net/instaweb/util/url_multipart_encoder.cc
@@ -0,0 +1,97 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/url_multipart_encoder.h"
+#include "net/instaweb/util/public/message_handler.h"
+
+namespace {
+
+// Ultimately these will be encoded by the URL Escaper so we want to
+// stay within legal URL space so we don't blow up. We'll have to
+// see how we like this aesthetically. We want to stay within legal
+// filename space as well so the filenames don't look too ugly.
+
+const char kEscape = '='; // Nice if this is filename-legal
+const char kEscapedEscape[] = "==";
+const char kSeparator = '+';
+const char kEscapedSeparator[] = "=+";
+
+} // namespace
+
+namespace net_instaweb {
+
+std::string UrlMultipartEncoder::Encode() const {
+ std::string encoding;
+ for (int i = 0, n = urls_.size(); i <n; ++i) {
+ if (i != 0) {
+ encoding += kSeparator;
+ }
+ const std::string& url = urls_[i];
+ for (int c = 0, nc = url.size(); c < nc; ++c) {
+ char ch = url[c];
+ if (ch == kEscape) {
+ encoding += kEscapedEscape;
+ } else if (ch == kSeparator) {
+ encoding += kEscapedSeparator;
+ } else {
+ encoding += ch;
+ }
+ }
+ }
+ return encoding;
+}
+
+bool UrlMultipartEncoder::Decode(const StringPiece& encoding,
+ MessageHandler* handler) {
+ urls_.clear();
+ std::string url;
+ bool append_last = false;
+ for (int c = 0, nc = encoding.size(); c < nc; ++c) {
+ char ch = encoding[c];
+ if (ch == kSeparator) {
+ urls_.push_back(url);
+ url.clear();
+ append_last = true;
+ // ensure that a "a+b+" results in 3 urls with the last one empty.
+ } else {
+ if (ch == kEscape) {
+ ++c;
+ if (c == nc) {
+ handler->Message(kError,
+ "Invalid encoding: escape at end of string %s",
+ encoding.as_string().c_str());
+ return false;
+ }
+ ch = encoding[c];
+ if ((ch != kEscape) && (ch != kSeparator)) {
+ handler->Message(kError,
+ "Invalid character `%c', after escape `%c' in %s",
+ ch, kEscape, encoding.as_string().c_str());
+ return false;
+ }
+ }
+ url += ch;
+ }
+ }
+ if (append_last || !url.empty()) {
+ urls_.push_back(url);
+ }
+ return true;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/url_multipart_encoder_test.cc b/trunk/src/net/instaweb/util/url_multipart_encoder_test.cc
new file mode 100644
index 0000000..22c7d84
--- /dev/null
+++ b/trunk/src/net/instaweb/util/url_multipart_encoder_test.cc
@@ -0,0 +1,65 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/url_escaper.h"
+#include "net/instaweb/util/public/google_message_handler.h"
+#include "net/instaweb/util/public/gtest.h"
+#include "net/instaweb/util/public/string_util.h"
+#include "net/instaweb/util/public/url_multipart_encoder.h"
+
+namespace net_instaweb {
+
+class UrlMultipartEncoderTest : public testing::Test {
+ protected:
+ UrlMultipartEncoder encoder_;
+ GoogleMessageHandler handler_;
+};
+
+TEST_F(UrlMultipartEncoderTest, EscapeSeparatorsAndEscapes) {
+ encoder_.AddUrl("abc");
+ encoder_.AddUrl("def");
+ encoder_.AddUrl("a=b+c"); // escape and separate characters
+ std::string encoding = encoder_.Encode();
+ encoder_.clear();
+ ASSERT_TRUE(encoder_.Decode(encoding, &handler_));
+ ASSERT_EQ(3, encoder_.num_urls());
+ EXPECT_EQ(std::string("abc"), encoder_.url(0));
+ EXPECT_EQ(std::string("def"), encoder_.url(1));
+ EXPECT_EQ(std::string("a=b+c"), encoder_.url(2));
+}
+
+TEST_F(UrlMultipartEncoderTest, Empty) {
+ ASSERT_TRUE(encoder_.Decode("", &handler_));
+ EXPECT_EQ(0, encoder_.num_urls());
+}
+
+TEST_F(UrlMultipartEncoderTest, LastIsEmpty) {
+ ASSERT_TRUE(encoder_.Decode("a+b+", &handler_));
+ ASSERT_EQ(3, encoder_.num_urls());
+ EXPECT_EQ(std::string("a"), encoder_.url(0));
+ EXPECT_EQ(std::string("b"), encoder_.url(1));
+ EXPECT_EQ(std::string(""), encoder_.url(2));
+}
+
+TEST_F(UrlMultipartEncoderTest, One) {
+ ASSERT_TRUE(encoder_.Decode("a", &handler_));
+ ASSERT_EQ(1, encoder_.num_urls());
+ EXPECT_EQ(std::string("a"), encoder_.url(0));
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/url_segment_encoder.cc b/trunk/src/net/instaweb/util/url_segment_encoder.cc
new file mode 100644
index 0000000..ba490c7
--- /dev/null
+++ b/trunk/src/net/instaweb/util/url_segment_encoder.cc
@@ -0,0 +1,24 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#include "net/instaweb/util/public/url_segment_encoder.h"
+
+namespace net_instaweb {
+// This file exists *only* to declare the virtual destructor implementation.
+UrlSegmentEncoder::~UrlSegmentEncoder() { }
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/user_agent.cc b/trunk/src/net/instaweb/util/user_agent.cc
new file mode 100644
index 0000000..354ffee
--- /dev/null
+++ b/trunk/src/net/instaweb/util/user_agent.cc
@@ -0,0 +1,42 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+//
+// 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.
+
+#include "net/instaweb/util/public/user_agent.h"
+
+namespace net_instaweb {
+
+UserAgent::UserAgent() {
+}
+
+void UserAgent::set_user_agent(const char* user_agent) {
+ if (user_agent) {
+ user_agent_.assign(user_agent);
+ } else {
+ user_agent_.clear();
+ }
+}
+
+bool UserAgent::IsIe() const {
+ return user_agent_.find(" MSIE ") != std::string::npos;
+}
+
+bool UserAgent::IsIe6() const {
+ return user_agent_.find(" MSIE 6.") != std::string::npos;
+}
+
+bool UserAgent::IsIe7() const {
+ return user_agent_.find(" MSIE 7.") != std::string::npos;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/user_agent_test.cc b/trunk/src/net/instaweb/util/user_agent_test.cc
new file mode 100644
index 0000000..a969fec
--- /dev/null
+++ b/trunk/src/net/instaweb/util/user_agent_test.cc
@@ -0,0 +1,86 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+//
+// 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.
+
+#include "net/instaweb/util/public/user_agent.h"
+#include "net/instaweb/util/public/gtest.h"
+
+namespace {
+
+// User Agent strings are from http://www.useragentstring.com/.
+// IE: http://www.useragentstring.com/pages/Internet%20Explorer/
+// FireFox: http://www.useragentstring.com/pages/Firefox/
+// Chrome: http://www.useragentstring.com/pages/Chrome/
+// And there are many more.
+
+const char kIe6UserAgent[] =
+ "Mozilla/5.0 (Windows; U; MSIE 6.0; Windows NT 5.1; SV1;"
+ " .NET CLR 2.0.50727)";
+const char kIe7UserAgent[] =
+ "Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)";
+const char kIe8UserAgent[] =
+ "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64;"
+ " Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729;"
+ " .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2;"
+ " .NET4.0C; .NET4.0E; FDM)";
+const char kIe9UserAgent[] =
+ "Mozilla/5.0 (Windows; U; MSIE 9.0; WIndows NT 9.0; en-US))";
+const char kFirefoxUserAgent[] =
+ "Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) "
+ "Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10";
+const char kChromeUserAgent[] =
+ "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) "
+ "AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.A.B.C Safari/525.13";
+
+} // namespace
+
+namespace net_instaweb {
+
+class UserAgentTest : public testing::Test {
+ protected:
+ void SetUserAgent(const char* agent) {
+ user_agent_.set_user_agent(agent);
+ }
+ UserAgent user_agent_;
+};
+
+TEST_F(UserAgentTest, IsIeTest) {
+ SetUserAgent(kIe6UserAgent);
+ EXPECT_TRUE(user_agent_.IsIe());
+ EXPECT_TRUE(user_agent_.IsIe6());
+ EXPECT_FALSE(user_agent_.IsIe7());
+ EXPECT_TRUE(user_agent_.IsIe6or7());
+
+ SetUserAgent(kIe7UserAgent);
+ EXPECT_TRUE(user_agent_.IsIe());
+ EXPECT_TRUE(user_agent_.IsIe7());
+ EXPECT_FALSE(user_agent_.IsIe6());
+ EXPECT_TRUE(user_agent_.IsIe6or7());
+
+ SetUserAgent(kIe8UserAgent);
+ EXPECT_TRUE(user_agent_.IsIe());
+ EXPECT_FALSE(user_agent_.IsIe6());
+ EXPECT_FALSE(user_agent_.IsIe7());
+ EXPECT_FALSE(user_agent_.IsIe6or7());
+
+
+}
+
+TEST_F(UserAgentTest, IsNotIeTest) {
+ SetUserAgent(kFirefoxUserAgent);
+ EXPECT_FALSE(user_agent_.IsIe());
+ EXPECT_FALSE(user_agent_.IsIe6());
+ EXPECT_FALSE(user_agent_.IsIe6or7());
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/wait_url_async_fetcher.cc b/trunk/src/net/instaweb/util/wait_url_async_fetcher.cc
new file mode 100644
index 0000000..91e2424
--- /dev/null
+++ b/trunk/src/net/instaweb/util/wait_url_async_fetcher.cc
@@ -0,0 +1,81 @@
+/*
+ * 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/util/public/wait_url_async_fetcher.h"
+
+#include "base/basictypes.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include "net/instaweb/util/public/stl_util.h"
+#include "net/instaweb/util/public/url_fetcher.h"
+
+namespace net_instaweb {
+
+class WaitUrlAsyncFetcher::DelayedFetch {
+ public:
+ DelayedFetch(UrlFetcher* base_fetcher,
+ const std::string& url, const MetaData& request_headers,
+ MetaData* response_headers, Writer* response_writer,
+ MessageHandler* handler, Callback* callback)
+ : base_fetcher_(base_fetcher), url_(url),
+ response_headers_(response_headers), response_writer_(response_writer),
+ handler_(handler), callback_(callback) {
+ request_headers_.CopyFrom(request_headers);
+ }
+
+ void FetchNow() {
+ bool status = base_fetcher_->StreamingFetchUrl(
+ url_, request_headers_, response_headers_, response_writer_, handler_);
+ callback_->Done(status);
+ }
+
+ private:
+ UrlFetcher* base_fetcher_;
+ std::string url_;
+ SimpleMetaData request_headers_;
+ MetaData* response_headers_;
+ Writer* response_writer_;
+ MessageHandler* handler_;
+ Callback* callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(DelayedFetch);
+};
+
+WaitUrlAsyncFetcher::~WaitUrlAsyncFetcher() {}
+
+bool WaitUrlAsyncFetcher::StreamingFetch(const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* response_writer,
+ MessageHandler* handler,
+ Callback* callback) {
+ // Don't call the blocking fetcher until CallCallbacks.
+ delayed_fetches_.push_back(new DelayedFetch(
+ url_fetcher_, url, request_headers, response_headers, response_writer,
+ handler, callback));
+ return false;
+}
+
+void WaitUrlAsyncFetcher::CallCallbacks() {
+ for (int i = 0, n = delayed_fetches_.size(); i < n; ++i) {
+ delayed_fetches_[i]->FetchNow();
+ }
+ STLDeleteElements(&delayed_fetches_);
+ delayed_fetches_.clear();
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/wait_url_async_fetcher_test.cc b/trunk/src/net/instaweb/util/wait_url_async_fetcher_test.cc
new file mode 100644
index 0000000..e82000e
--- /dev/null
+++ b/trunk/src/net/instaweb/util/wait_url_async_fetcher_test.cc
@@ -0,0 +1,85 @@
+/*
+ * 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/util/public/wait_url_async_fetcher.h"
+
+#include "net/instaweb/util/public/google_message_handler.h"
+#include "net/instaweb/util/public/gtest.h"
+#include "net/instaweb/util/public/string_writer.h"
+#include "net/instaweb/util/public/mock_url_fetcher.h"
+
+namespace net_instaweb {
+
+namespace {
+
+// Callback that just checks that it was called with success.
+class DummyCallback : public UrlAsyncFetcher::Callback {
+ public:
+ DummyCallback() : done_(false) {}
+ virtual ~DummyCallback() {
+ EXPECT_TRUE(done_);
+ }
+ virtual void Done(bool success) {
+ EXPECT_FALSE(done_) << "Already Done; perhaps you reused without Reset()";
+ done_ = true;
+ EXPECT_TRUE(success);
+ }
+ bool done_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DummyCallback);
+};
+
+class WaitUrlAsyncFetcherTest : public ::testing::Test {};
+
+TEST_F(WaitUrlAsyncFetcherTest, FetcherWaits) {
+ MockUrlFetcher base_fetcher;
+ WaitUrlAsyncFetcher wait_fetcher(&base_fetcher);
+
+ const char url[] = "http://www.example.com/";
+ SimpleMetaData header;
+ header.set_first_line(1, 1, 200, "OK");
+ const char body[] = "Contents.";
+
+ base_fetcher.SetResponse(url, header, body);
+
+ const SimpleMetaData request_headers;
+ SimpleMetaData response_headers;
+ std::string response_body;
+ StringWriter response_writer(&response_body);
+ GoogleMessageHandler handler;
+ DummyCallback callback;
+
+ EXPECT_FALSE(wait_fetcher.StreamingFetch(url, request_headers,
+ &response_headers, &response_writer,
+ &handler, &callback));
+
+ // Nothing gets set ...
+ EXPECT_EQ(false, callback.done_);
+ EXPECT_EQ("", response_body);
+
+ // ... until we CallCallbacks.
+ wait_fetcher.CallCallbacks();
+ EXPECT_EQ(true, callback.done_);
+ EXPECT_EQ(body, response_body);
+
+}
+
+} // namespace
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/wget_url_fetcher.cc b/trunk/src/net/instaweb/util/wget_url_fetcher.cc
new file mode 100644
index 0000000..db33428
--- /dev/null
+++ b/trunk/src/net/instaweb/util/wget_url_fetcher.cc
@@ -0,0 +1,113 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/wget_url_fetcher.h"
+
+#include <errno.h>
+#include "net/instaweb/util/public/http_response_parser.h"
+#include "net/instaweb/util/public/message_handler.h"
+#include "net/instaweb/util/public/simple_meta_data.h"
+#include "net/instaweb/util/public/writer.h"
+
+namespace {
+
+// It turns out to be harder to quote in bash with single-quote
+// than double-quote. From man sh:
+//
+// Single Quotes
+// Enclosing characters in single quotes preserves the literal meaning of
+// all the characters (except single quotes, making it impossible to put
+// single-quotes in a single-quoted string).
+//
+// Double Quotes
+// Enclosing characters within double quotes preserves the literal meaning
+// of all characters except dollarsign ($), backquote (‘), and backslash
+// (\). The backslash inside double quotes is historically weird, and
+// serves to quote only the following characters:
+// $ ‘ " \ <newline>.
+// Otherwise it remains literal.
+//
+// So we put double-quotes around most strings, after first escaping
+// any of these characters:
+const char kEscapeChars[] = "\"$`\\";
+}
+
+namespace net_instaweb {
+
+// Default user agent to a Chrome user agent, so that we get real website.
+const char WgetUrlFetcher::kDefaultUserAgent[] =
+ "Mozilla/5.0 (X11; U; Linux x86_64; en-US) "
+ "AppleWebKit/534.0 (KHTML, like Gecko) Chrome/6.0.408.1 Safari/534.0";
+
+WgetUrlFetcher::~WgetUrlFetcher() {
+}
+
+bool WgetUrlFetcher::StreamingFetchUrl(const std::string& url,
+ const MetaData& request_headers,
+ MetaData* response_headers,
+ Writer* writer,
+ MessageHandler* handler) {
+ std::string cmd("/usr/bin/wget --save-headers -q -O -"), escaped_url;
+
+ // Use default user-agent if none is set in headers.
+ CharStarVector values;
+ request_headers.Lookup("user-agent", &values);
+ if (values.empty()) {
+ cmd += StrCat(" --user-agent=\"", kDefaultUserAgent, "\"");
+ }
+
+ for (int i = 0, n = request_headers.NumAttributes(); i < n; ++i) {
+ std::string escaped_name, escaped_value;
+
+ BackslashEscape(request_headers.Name(i), kEscapeChars, &escaped_name);
+ BackslashEscape(request_headers.Value(i), kEscapeChars, &escaped_value);
+ cmd += StrCat(" --header=\"", escaped_name, ": ", escaped_value, "\"");
+ }
+
+ BackslashEscape(url, kEscapeChars, &escaped_url);
+ cmd += StrCat(" \"", escaped_url, "\"");
+ handler->Message(kInfo, "wget %s\n", url.c_str());
+ FILE* wget_stdout = popen(cmd.c_str(), "r");
+
+ bool ret = false;
+ if (wget_stdout == NULL) {
+ handler->Message(kError, "Wget popen failed on url %s: %s",
+ url.c_str(), strerror(errno));
+ } else {
+ HttpResponseParser parser(response_headers, writer, handler);
+ ret = parser.Parse(wget_stdout);
+ int exit_status = pclose(wget_stdout);
+ if (exit_status != 0) {
+ // The wget failed. wget does not always (ever?) write appropriate
+ // headers when it fails, so invent some.
+ if (response_headers->status_code() == 0) {
+ response_headers->set_first_line(1, 1, HttpStatus::kBadRequest,
+ "Wget Failed");
+ response_headers->ComputeCaching();
+ response_headers->set_headers_complete(true);
+ writer->Write("wget failed: ", handler);
+ writer->Write(url, handler);
+ writer->Write("<br>\nExit Status: ", handler);
+ writer->Write(IntegerToString(exit_status), handler);
+ }
+ }
+ }
+ return ret;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/wildcard.cc b/trunk/src/net/instaweb/util/wildcard.cc
new file mode 100644
index 0000000..0c0f776
--- /dev/null
+++ b/trunk/src/net/instaweb/util/wildcard.cc
@@ -0,0 +1,150 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/wildcard.h"
+#include "net/instaweb/util/public/string_util.h"
+
+namespace net_instaweb {
+
+const char Wildcard::kMatchAny = '*';
+const char Wildcard::kMatchOne = '?';
+
+Wildcard::Wildcard(const StringPiece& wildcard_spec)
+ : storage_(wildcard_spec.data(), wildcard_spec.size()) {
+ InitFromStorage();
+}
+
+void Wildcard::InitFromStorage() {
+ // Pre-scan the wildcard spec into an array of StringPieces. We will
+ // copy the original string spec into storage_ but that will only be
+ // backing-store for the StringPieces.
+ //
+ // E.g. we will transform "a?c*def**?xyz?" into
+ // {"a", "?", "c", "*", "def", "*", "?", "xyz", "?"}
+ // Note that multiple consecutive '*' will be collapsed into one.
+ //
+ // TODO(jmarantz): jmaessen suggests moving ? ahead of * as it will
+ // reduce code complexity and the cost of backtracking.
+ int num_pieces = 0;
+ char prev_c = '\0';
+ for (int i = 0, n = storage_.size(); i < n; ++i) {
+ char c = storage_[i];
+ bool add_to_previous = (num_pieces != 0);
+ bool skip = false;
+ if (add_to_previous) {
+ if (c == kMatchAny) {
+ add_to_previous = false;
+ skip = (prev_c == kMatchAny); // multiple * in a row means nothing
+ } else if (c == kMatchOne) {
+ add_to_previous = false;
+ } else {
+ add_to_previous = ((prev_c != kMatchAny) && (prev_c != kMatchOne));
+ }
+ }
+ if (add_to_previous) {
+ pieces_[num_pieces - 1] = StringPiece(pieces_[num_pieces - 1].data(),
+ pieces_[num_pieces - 1].size() + 1);
+ } else if (!skip) {
+ pieces_.push_back(StringPiece(storage_.data() + i, 1));
+ ++num_pieces;
+ }
+ prev_c = c;
+ }
+}
+
+bool Wildcard::MatchHelper(int piece_index, const StringPiece& str) {
+ bool prev_was_any = false;
+ size_t num_skips_after_any = 0;
+ size_t str_index = 0;
+
+ // Walk through the pieces parsed out in the constructor, matching them
+ // against str. Our algorithm will walk linealy through str, although we
+ // may need to backtrack if there are multiple matches for the segment
+ // following a *.
+ for (int num_pieces = pieces_.size(); piece_index < num_pieces; ++piece_index) {
+ StringPiece piece = pieces_[piece_index];
+ if (piece[0] == kMatchAny) {
+ prev_was_any = true;
+ } else if (piece[0] == kMatchOne) {
+ if (prev_was_any) {
+ ++num_skips_after_any;
+ } else {
+ ++str_index;
+ if (str_index > str.size()) {
+ return false;
+ }
+ }
+ } else if (prev_was_any) {
+ str_index += num_skips_after_any;
+ if (str_index > str.size()) {
+ return false;
+ }
+
+ // Now we have the unenviable task of figuring out how many
+ // characters of 'str' to swallow. Consider this complexity.
+ // CHECK(Wildcard("*abcd?").Match("abcabcdabcdabcdabcde"));
+ // If we greedily match the "a" in the wildcard against any of
+ // the first 4 'a's in the string then we are screwed -- we
+ // won't find the d. Even if we match against the first or
+ // second "abcd" we will get a failure because we will have
+ // string left, but no more pattern.
+ //
+ // There are probably more efficient ways to do this, such as in
+ // http://code.google.com/p/re2/, but we will, for short-term
+ // expediency, use recursion to search all the possible matches
+ // for the current piece in str.
+ while ((str_index = str.find(piece, str_index)) != StringPiece::npos) {
+ if (MatchHelper(piece_index + 1,
+ str.substr(str_index + piece.size()))) {
+ return true;
+ }
+ ++str_index;
+ }
+ return false;
+ } else if ((str.size() - str_index) < piece.size()) {
+ return false;
+ } else if (str.substr(str_index, piece.size()) == piece) {
+ str_index += piece.size();
+ } else {
+ return false;
+ }
+ }
+ if (prev_was_any) {
+ return (str.size() >= num_skips_after_any);
+ }
+ return (str_index == str.size());
+}
+
+bool Wildcard::IsSimple() const {
+ if (pieces_.size() == 0) {
+ return true;
+ }
+ if (pieces_.size() != 1) {
+ return false;
+ }
+ StringPiece piece = pieces_[0];
+ CHECK(!piece.empty());
+ char ch = piece[0];
+ return ((ch != kMatchAny) && (ch != kMatchOne));
+}
+
+Wildcard* Wildcard::Duplicate() const {
+ return new Wildcard(storage_);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/wildcard_group.cc b/trunk/src/net/instaweb/util/wildcard_group.cc
new file mode 100644
index 0000000..359d380
--- /dev/null
+++ b/trunk/src/net/instaweb/util/wildcard_group.cc
@@ -0,0 +1,70 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/wildcard_group.h"
+#include "net/instaweb/util/public/stl_util.h"
+#include "net/instaweb/util/public/wildcard.h"
+
+namespace net_instaweb {
+
+WildcardGroup::~WildcardGroup() {
+ STLDeleteElements(&wildcards_);
+}
+
+void WildcardGroup::Allow(const StringPiece& expr) {
+ Wildcard* wildcard = new Wildcard(expr);
+ wildcards_.push_back(wildcard);
+ allow_.push_back(true);
+}
+
+void WildcardGroup::Disallow(const StringPiece& expr) {
+ Wildcard* wildcard = new Wildcard(expr);
+ wildcards_.push_back(wildcard);
+ allow_.push_back(false);
+}
+
+bool WildcardGroup::Match(const StringPiece& str) const {
+ bool allow = true;
+ CHECK_EQ(wildcards_.size(), allow_.size());
+ for (int i = 0, n = wildcards_.size(); i < n; ++i) {
+ // Do not bother to execute the wildcard match if a match would
+ // not change the current 'allow' status. E.g. once we have found
+ // an 'allow' match, we can ignore all subsequent 'allow' tests
+ // until a rule disallows the match.
+ if ((allow != allow_[i]) && wildcards_[i]->Match(str)) {
+ allow = !allow;
+ }
+ }
+ return allow;
+}
+
+void WildcardGroup::CopyFrom(const WildcardGroup& src) {
+ wildcards_.clear();
+ allow_.clear();
+ AppendFrom(src);
+}
+
+void WildcardGroup::AppendFrom(const WildcardGroup& src) {
+ CHECK_EQ(src.wildcards_.size(), src.allow_.size());
+ for (int i = 0, n = src.wildcards_.size(); i < n; ++i) {
+ wildcards_.push_back(src.wildcards_[i]->Duplicate());
+ allow_.push_back(src.allow_[i]);
+ }
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/wildcard_group_test.cc b/trunk/src/net/instaweb/util/wildcard_group_test.cc
new file mode 100644
index 0000000..6a42194
--- /dev/null
+++ b/trunk/src/net/instaweb/util/wildcard_group_test.cc
@@ -0,0 +1,55 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/wildcard_group.h"
+#include "net/instaweb/util/public/gtest.h"
+
+namespace net_instaweb {
+
+class WildcardGroupTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ group_.Allow("*.cc");
+ group_.Allow("*.h");
+ group_.Disallow("a*.h");
+ group_.Allow("ab*.h");
+ group_.Disallow("c*.cc");
+ }
+
+ void TestGroup(const WildcardGroup& group) {
+ EXPECT_TRUE(group.Match("x.cc"));
+ EXPECT_FALSE(group.Match("c.cc"));
+ EXPECT_TRUE(group.Match("y.h"));
+ EXPECT_FALSE(group.Match("a.h"));
+ EXPECT_TRUE(group.Match("ab.h"));
+ }
+
+ WildcardGroup group_;
+};
+
+TEST_F(WildcardGroupTest, Sequence) {
+ TestGroup(group_);
+}
+
+TEST_F(WildcardGroupTest, CopySequence) {
+ WildcardGroup copy;
+ copy.CopyFrom(group_);
+ TestGroup(copy);
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/wildcard_test.cc b/trunk/src/net/instaweb/util/wildcard_test.cc
new file mode 100644
index 0000000..484daec
--- /dev/null
+++ b/trunk/src/net/instaweb/util/wildcard_test.cc
@@ -0,0 +1,104 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/wildcard.h"
+#include "net/instaweb/util/public/gtest.h"
+
+namespace net_instaweb {
+
+class WildcardTest : public testing::Test {
+};
+
+TEST_F(WildcardTest, Identity) {
+ EXPECT_TRUE(Wildcard("Hello").Match("Hello"));
+}
+
+TEST_F(WildcardTest, IdentityExtra) {
+ EXPECT_FALSE(Wildcard("Hello").Match("xHello"));
+ EXPECT_FALSE(Wildcard("Hello").Match("HelloxX"));
+}
+
+TEST_F(WildcardTest, OneStar) {
+ EXPECT_TRUE(Wildcard("H*o").Match("Hello"));
+}
+
+TEST_F(WildcardTest, OneQuestion) {
+ EXPECT_TRUE(Wildcard("H?llo").Match("Hello"));
+}
+
+TEST_F(WildcardTest, TwoQuestionSplit) {
+ EXPECT_TRUE(Wildcard("H?l?o").Match("Hello"));
+}
+
+TEST_F(WildcardTest, ThreeQuestionAdjacent) {
+ EXPECT_TRUE(Wildcard("H???o").Match("Hello"));
+}
+
+TEST_F(WildcardTest, SimpleMismatch) {
+ EXPECT_FALSE(Wildcard("Hello").Match("Goodbye"));
+}
+
+TEST_F(WildcardTest, GreedyTrap1) {
+ EXPECT_TRUE(Wildcard("*abcd").Match("abcabcabcabcabcd"));
+}
+
+TEST_F(WildcardTest, GreedyTrap2) {
+ EXPECT_FALSE(Wildcard("*abcd?").Match("abcabcabcabcabcd"));
+ EXPECT_TRUE(Wildcard("*abcd*").Match("abcabcabcabcabcd"));
+}
+
+TEST_F(WildcardTest, GreedyTrap3) {
+ EXPECT_TRUE(Wildcard("*abcd?").Match("abcabcabcabcabcdabcde"));
+}
+
+TEST_F(WildcardTest, StarAtBeginning) {
+ EXPECT_TRUE(Wildcard("*Hello").Match("Hello"));
+ EXPECT_TRUE(Wildcard("*ello").Match("Hello"));
+}
+
+TEST_F(WildcardTest, StarAtEnd) {
+ EXPECT_TRUE(Wildcard("Hello*").Match("Hello"));
+ EXPECT_TRUE(Wildcard("Hell*").Match("Hello"));
+}
+
+TEST_F(WildcardTest, QuestionAtBeginning) {
+ EXPECT_FALSE(Wildcard("?Hello").Match("Hello"));
+ EXPECT_TRUE(Wildcard("?ello").Match("Hello"));
+}
+
+TEST_F(WildcardTest, QuestionAtEnd) {
+ EXPECT_FALSE(Wildcard("Hello?").Match("Hello"));
+ EXPECT_TRUE(Wildcard("Hell?").Match("Hello"));
+}
+
+TEST_F(WildcardTest, Empty) {
+ EXPECT_TRUE(Wildcard("").Match(""));
+ EXPECT_FALSE(Wildcard("").Match("x"));
+ EXPECT_TRUE(Wildcard("*").Match(""));
+ EXPECT_FALSE(Wildcard("?").Match(""));
+}
+
+TEST_F(WildcardTest, Simple) {
+ EXPECT_FALSE(Wildcard("H*o").IsSimple());
+ EXPECT_TRUE(Wildcard("Hello").IsSimple());
+ EXPECT_TRUE(Wildcard("").IsSimple());
+ EXPECT_FALSE(Wildcard("*").IsSimple());
+ EXPECT_FALSE(Wildcard("?").IsSimple());
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/work_bound.cc b/trunk/src/net/instaweb/util/work_bound.cc
new file mode 100644
index 0000000..2cbd866
--- /dev/null
+++ b/trunk/src/net/instaweb/util/work_bound.cc
@@ -0,0 +1,25 @@
+/**
+ * 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: jmaessen@google.com (Jan Maessen)
+
+#include "net/instaweb/util/public/work_bound.h"
+
+namespace net_instaweb {
+
+WorkBound::~WorkBound() { }
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/write_through_cache.cc b/trunk/src/net/instaweb/util/write_through_cache.cc
new file mode 100644
index 0000000..28dcee2
--- /dev/null
+++ b/trunk/src/net/instaweb/util/write_through_cache.cc
@@ -0,0 +1,73 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/write_through_cache.h"
+#include "net/instaweb/util/public/shared_string.h"
+
+namespace net_instaweb {
+
+const size_t WriteThroughCache::kUnlimited = static_cast<size_t>(-1);
+
+WriteThroughCache::~WriteThroughCache() {
+}
+
+void WriteThroughCache::PutInCache1(const std::string& key,
+ SharedString* value) {
+ if ((cache1_size_limit_ == kUnlimited) ||
+ (key.size() + value->size() < cache1_size_limit_)) {
+ cache1_->Put(key, value);
+ }
+}
+
+bool WriteThroughCache::Get(const std::string& key, SharedString* value) {
+ bool ret = cache1_->Get(key, value);
+ if (!ret) {
+ ret = cache2_->Get(key, value);
+ if (ret) {
+ PutInCache1(key, value);
+ }
+ }
+ return ret;
+}
+
+void WriteThroughCache::Put(const std::string& key, SharedString* value) {
+ PutInCache1(key, value);
+ cache2_->Put(key, value);
+}
+
+void WriteThroughCache::Delete(const std::string& key) {
+ cache1_->Delete(key);
+ cache2_->Delete(key);
+}
+
+CacheInterface::KeyState WriteThroughCache::Query(const std::string& key) {
+ // There are 3 possible states, kAvailable, kInTransit, and kNotFound.
+ // We want to 'promote' the state obtained from cache1. But we don't want
+ // to demote. Specifically, if cache1 says kInTransit, and cache2 says
+ // kNotFound, we'd want to return kInTransit.
+ CacheInterface::KeyState state = cache1_->Query(key);
+ if (state != kAvailable) {
+ CacheInterface::KeyState state2 = cache2_->Query(key);
+ if (state2 != kNotFound) {
+ state = state2;
+ }
+ }
+ return state;
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/write_through_cache_test.cc b/trunk/src/net/instaweb/util/write_through_cache_test.cc
new file mode 100644
index 0000000..a6cd28d
--- /dev/null
+++ b/trunk/src/net/instaweb/util/write_through_cache_test.cc
@@ -0,0 +1,126 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+// Unit-test the write-through cache
+
+#include "net/instaweb/util/public/write_through_cache.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "net/instaweb/util/public/gtest.h"
+#include "net/instaweb/util/public/lru_cache.h"
+#include "net/instaweb/util/public/shared_string.h"
+#include <string>
+
+namespace net_instaweb {
+
+class WriteThroughCacheTest : public testing::Test {
+ protected:
+ WriteThroughCacheTest()
+ : small_cache_(new LRUCache(15)),
+ big_cache_(new LRUCache(1000)),
+ write_through_cache_(small_cache_, big_cache_) {
+ }
+
+ void CheckGet(CacheInterface* cache, const char* key,
+ const std::string& expected_value) {
+ SharedString value_buffer;
+ EXPECT_TRUE(cache->Get(key, &value_buffer));
+ EXPECT_EQ(expected_value, *value_buffer);
+ EXPECT_EQ(CacheInterface::kAvailable, cache->Query(key));
+ }
+
+ void Put(const char* key, const char* value) {
+ SharedString put_buffer(value);
+ write_through_cache_.Put(key, &put_buffer);
+ }
+
+ void CheckNotFound(CacheInterface* cache, const char* key) {
+ SharedString value_buffer;
+ EXPECT_FALSE(cache->Get(key, &value_buffer));
+ EXPECT_EQ(CacheInterface::kNotFound, cache->Query(key));
+ }
+
+ LRUCache* small_cache_;
+ LRUCache* big_cache_;
+ WriteThroughCache write_through_cache_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WriteThroughCacheTest);
+};
+
+// Simple flow of putting in an item, getting it, deleting it.
+TEST_F(WriteThroughCacheTest, PutGetDelete) {
+ // First, put some small data into the write-through. It should
+ // be available in both caches.
+ Put("Name", "Value");
+ CheckGet(&write_through_cache_, "Name", "Value");
+ CheckGet(small_cache_, "Name", "Value");
+ CheckGet(big_cache_, "Name", "Value");
+
+ CheckNotFound(&write_through_cache_, "Another Name");
+
+ // next, put another value in. This will evict the first item
+ // out of the small cache,
+ Put("Name2", "NewValue");
+ CheckGet(&write_through_cache_, "Name2", "NewValue");
+ CheckGet(small_cache_, "Name2", "NewValue");
+ CheckGet(big_cache_, "Name2", "NewValue");
+
+ // The first item will still be available in the write-through,
+ // and in the big-cache, but will have been evicted from the
+ // small cache.
+ CheckNotFound(small_cache_, "Name");
+ CheckGet(big_cache_, "Name", "Value");
+ CheckNotFound(small_cache_, "Name");
+
+ CheckGet(&write_through_cache_, "Name", "Value");
+
+ // But now, once we've gotten in out of the write-through cache,
+ // the small cache will have the value "freshened."
+ CheckGet(small_cache_, "Name", "Value");
+
+ write_through_cache_.Delete("Name2");
+ CheckNotFound(&write_through_cache_, "Name2");
+ CheckNotFound(small_cache_, "Name2");
+ CheckNotFound(big_cache_, "Name2");
+}
+
+// Check size-limits for the small cache
+TEST_F(WriteThroughCacheTest, SizeLimit) {
+ write_through_cache_.set_cache1_limit(10);
+
+ // This one will fit.
+ Put("Name", "Value");
+ CheckGet(&write_through_cache_, "Name", "Value");
+ CheckGet(small_cache_, "Name", "Value");
+ CheckGet(big_cache_, "Name", "Value");
+
+ // This one will not.
+ Put("Name2", "TooBig");
+ CheckGet(&write_through_cache_, "Name2", "TooBig");
+ CheckNotFound(small_cache_, "Name2");
+ CheckGet(big_cache_, "Name2", "TooBig");
+
+ // However "Name" is still in both caches.
+ CheckGet(small_cache_, "Name", "Value");
+ CheckGet(&write_through_cache_, "Name", "Value");
+ CheckGet(big_cache_, "Name", "Value");
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/net/instaweb/util/writer.cc b/trunk/src/net/instaweb/util/writer.cc
new file mode 100644
index 0000000..7a8cda9
--- /dev/null
+++ b/trunk/src/net/instaweb/util/writer.cc
@@ -0,0 +1,26 @@
+/**
+ * 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: jmarantz@google.com (Joshua Marantz)
+
+#include "net/instaweb/util/public/writer.h"
+
+namespace net_instaweb {
+
+Writer::~Writer() {
+}
+
+} // namespace net_instaweb
diff --git a/trunk/src/third_party/base64/README.google b/trunk/src/third_party/base64/README.google
new file mode 100644
index 0000000..b0519f0
--- /dev/null
+++ b/trunk/src/third_party/base64/README.google
@@ -0,0 +1,36 @@
+Modified version of base64.
+
+URL:http://www.adp-gmbh.ch/cpp/common/base64.html
+
+ base64.cpp and base64.h
+
+ Copyright (C) 2004-2008 René Nyffenegger
+
+ This source code is provided 'as-is', without any express or implied
+ warranty. In no event will the author be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this source code must not be misrepresented; you must not
+ claim that you wrote the original source code. If you use this source code
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original source code.
+
+ 3. This notice may not be removed or altered from any source distribution.
+
+ René Nyffenegger rene.nyffenegger@adp-gmbh.ch
+
+Modified by jmarantz@google.com to:
+ - Add a static map and an initializer function
+ - add a web-safe variant that uses '-' and '_' in lieu of '+' and '/'
+ - change the 'decode' interface to return false if the input contains
+ characters not in the expected char-set.
+ - removed is_base64 helper function and instead rely on the char_maps.
+ - use a static constant for the padding character '='
+
diff --git a/trunk/src/third_party/base64/base64.cc b/trunk/src/third_party/base64/base64.cc
new file mode 100644
index 0000000..6f02fe3
--- /dev/null
+++ b/trunk/src/third_party/base64/base64.cc
@@ -0,0 +1,199 @@
+/*
+ From: http://www.adp-gmbh.ch/cpp/common/base64.html
+
+ base64.cpp and base64.h
+
+ Copyright (C) 2004-2008 René Nyffenegger
+
+ This source code is provided 'as-is', without any express or implied
+ warranty. In no event will the author be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this source code must not be misrepresented; you must not
+ claim that you wrote the original source code. If you use this source code
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original source code.
+
+ 3. This notice may not be removed or altered from any source distribution.
+
+ René Nyffenegger rene.nyffenegger@adp-gmbh.ch
+
+ This file was modified by jmarantz@google.com to:
+ - Add a static map and an initializer function
+ - add a web-safe variant that uses '-' and '_' in lieu of '+' and '/'
+ - change the 'decode' interface to return false if the input contains
+ characters not in the expected char-set.
+ - removed is_base64 helper function and instead rely on the char_maps.
+ - use a static constant for the padding character '='
+*/
+
+#include "base64.h"
+#include <assert.h>
+
+static const char base64_chars[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789+/";
+
+static const char web64_chars[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789-_";
+
+static unsigned int base64_char_map[256], web64_char_map[256];
+static bool initialized = false;
+static const unsigned int kInvalidChar = 0xffffffff;
+static const char kPadChar = '=';
+
+// Be sure to call this function before calling any other functions
+// in this file when using multiple threads.
+void base64_init() {
+ if (!initialized) {
+ initialized = true;
+ for (int i = 0; i < 256; ++i) {
+ base64_char_map[i] = kInvalidChar;
+ web64_char_map[i] = kInvalidChar;
+ }
+ for (int i = 0; i < sizeof(base64_chars) - 1; ++i) {
+ base64_char_map[base64_chars[i]] = i;
+ }
+ for (int i = 0; i < sizeof(web64_chars) - 1; ++i) {
+ web64_char_map[web64_chars[i]] = i;
+ }
+ }
+}
+
+static inline std::string encode(
+ const char* char_set, unsigned char const* bytes_to_encode,
+ unsigned int in_len) {
+ base64_init();
+ std::string ret;
+ int i = 0;
+ int j = 0;
+ unsigned char char_array_3[3];
+ unsigned char char_array_4[4];
+
+ while (in_len--) {
+ char_array_3[i++] = *(bytes_to_encode++);
+ if (i == 3) {
+ char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
+ char_array_4[1] = ((char_array_3[0] & 0x03) << 4) +
+ ((char_array_3[1] & 0xf0) >> 4);
+ char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) +
+ ((char_array_3[2] & 0xc0) >> 6);
+ char_array_4[3] = char_array_3[2] & 0x3f;
+
+ for (i = 0; i < 4 ; i++)
+ ret += char_set[char_array_4[i]];
+ i = 0;
+ }
+ }
+
+ if (i) {
+ for (j = i; j < 3; j++) {
+ char_array_3[j] = '\0';
+ }
+
+ char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
+ char_array_4[1] = ((char_array_3[0] & 0x03) << 4) +
+ ((char_array_3[1] & 0xf0) >> 4);
+ char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) +
+ ((char_array_3[2] & 0xc0) >> 6);
+ char_array_4[3] = char_array_3[2] & 0x3f;
+
+ for (j = 0; (j < i + 1); j++)
+ ret += char_set[char_array_4[j]];
+
+ while ((i++ < 3)) {
+ ret += kPadChar;
+ }
+ }
+
+ return ret;
+}
+
+static inline bool decode(
+ unsigned int* char_map, const std::string& encoded_string,
+ std::string* output) {
+ base64_init();
+ int in_len = encoded_string.size();
+ int i = 0;
+ int j = 0;
+ int in_ = 0;
+ unsigned char char_array_4[4], char_array_3[3];
+ std::string ret;
+
+ while (in_len-- && (encoded_string[in_] != kPadChar)) {
+ char_array_4[i++] = encoded_string[in_];
+ in_++;
+ if (i == 4) {
+ for (i = 0; i <4; i++) {
+ unsigned int char_index = char_map[char_array_4[i]];
+ if (char_index == kInvalidChar) {
+ return false;
+ }
+ char_array_4[i] = char_index;
+ }
+
+ char_array_3[0] = (char_array_4[0] << 2) +
+ ((char_array_4[1] & 0x30) >> 4);
+ char_array_3[1] = ((char_array_4[1] & 0xf) << 4) +
+ ((char_array_4[2] & 0x3c) >> 2);
+ char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
+
+ for (i = 0; (i < 3); i++)
+ *output += char_array_3[i];
+ i = 0;
+ }
+ }
+
+ if (i) {
+ for (j = i; j < 4; j++)
+ char_array_4[j] = 0;
+
+ for (j = 0; j < i; j++) {
+ unsigned int char_index = char_map[char_array_4[j]];
+ if (char_index == kInvalidChar) {
+ return false;
+ }
+ char_array_4[j] = char_index;
+ }
+
+ char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
+ char_array_3[1] = ((char_array_4[1] & 0xf) << 4) +
+ ((char_array_4[2] & 0x3c) >> 2);
+ char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
+
+ for (j = 0; (j < i - 1); j++) {
+ *output += char_array_3[j];
+ }
+ }
+
+ return true;
+}
+
+
+std::string base64_encode(unsigned char const* bytes_to_encode,
+ unsigned int in_len) {
+ return encode(base64_chars, bytes_to_encode, in_len);
+}
+
+std::string web64_encode(unsigned char const* bytes_to_encode,
+ unsigned int in_len) {
+ return encode(web64_chars, bytes_to_encode, in_len);
+}
+
+bool base64_decode(const std::string& encoded_string, std::string* output) {
+ return decode(base64_char_map, encoded_string, output);
+}
+
+bool web64_decode(const std::string& encoded_string, std::string* output) {
+ return decode(web64_char_map, encoded_string, output);
+}
diff --git a/trunk/src/third_party/base64/base64.gyp b/trunk/src/third_party/base64/base64.gyp
new file mode 100644
index 0000000..7623f74
--- /dev/null
+++ b/trunk/src/third_party/base64/base64.gyp
@@ -0,0 +1,29 @@
+# Copyright 2009 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.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'base64',
+ 'type': '<(library)',
+ 'sources': [
+ 'base64.cc',
+ ],
+ 'include_dirs': [
+ '.',
+ ],
+ }
+ ],
+}
+
diff --git a/trunk/src/third_party/base64/base64.h b/trunk/src/third_party/base64/base64.h
new file mode 100644
index 0000000..8d32b1f
--- /dev/null
+++ b/trunk/src/third_party/base64/base64.h
@@ -0,0 +1,51 @@
+/*
+ From: http://www.adp-gmbh.ch/cpp/common/base64.html
+
+ base64.cpp and base64.h
+
+ Copyright (C) 2004-2008 René Nyffenegger
+
+ This source code is provided 'as-is', without any express or implied
+ warranty. In no event will the author be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this source code must not be misrepresented; you must not
+ claim that you wrote the original source code. If you use this source code
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original source code.
+
+ 3. This notice may not be removed or altered from any source distribution.
+
+ René Nyffenegger rene.nyffenegger@adp-gmbh.ch
+
+ This file was modified by jmarantz@google.com to:
+ - add a web-safe variant that uses '-' and '_' in lieu of '+' and '/'
+ - change the 'decode' interface to return false if the input contains
+ characters not in the expected char-set.
+*/
+
+#ifndef THIRD_PARTY_BASE64_BASE64_H_
+#define THIRD_PARTY_BASE64_BASE64_H_
+
+#include <string>
+
+// Initialize the base64 library. Call this before other methods if
+// multi-threaded access is required.
+void base64_init();
+
+// Encode/deocde strings in common base64 encoding
+std::string base64_encode(const unsigned char* data, unsigned int len);
+bool base64_decode(const std::string& s, std::string* output);
+
+// Encode/deocde strings in a base64 encoding for URL values.
+std::string web64_encode(const unsigned char* data, unsigned int len);
+bool web64_decode(const std::string& s, std::string* output);
+
+#endif // THIRD_PARTY_BASE64_BASE64_H_
diff --git a/trunk/src/third_party/chromium/src/net/tools/dump_cache.gyp b/trunk/src/third_party/chromium/src/net/tools/dump_cache.gyp
new file mode 100644
index 0000000..6c6bb05
--- /dev/null
+++ b/trunk/src/third_party/chromium/src/net/tools/dump_cache.gyp
@@ -0,0 +1,43 @@
+# 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.
+
+{
+ 'variables': {
+ # chromium_code indicates that the code is not
+ # third-party code and should be subjected to strict compiler
+ # warnings/errors in order to catch programming mistakes.
+ 'chromium_code': 1,
+ },
+
+ 'targets': [
+ {
+ 'target_name': 'url_to_filename_encoder',
+ 'type': '<(library)',
+ 'dependencies': [
+ '<(DEPTH)/base/base.gyp:base',
+ ],
+ 'include_dirs': [
+ 'dump_cache',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ 'dump_cache',
+ ],
+ },
+ 'sources': [
+ 'dump_cache/url_to_filename_encoder.cc',
+ ],
+ },
+ ],
+}
diff --git a/trunk/src/third_party/css_parser/css_parser.gyp b/trunk/src/third_party/css_parser/css_parser.gyp
new file mode 100644
index 0000000..57be7dc
--- /dev/null
+++ b/trunk/src/third_party/css_parser/css_parser.gyp
@@ -0,0 +1,144 @@
+# Copyright 2009 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.
+
+{
+ 'variables': {
+ # chromium_code indicates that the code is not
+ # third-party code and should be subjected to strict compiler
+ # warnings/errors in order to catch programming mistakes.
+ 'chromium_code': 1,
+ 'css_parser_root': 'src',
+ },
+
+ 'targets': [
+ {
+ 'target_name': 'css_parser',
+ 'type': '<(library)',
+ 'dependencies': [
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/third_party/gflags/gflags.gyp:gflags',
+ '<(DEPTH)/third_party/google-sparsehash/google-sparsehash.gyp:include',
+ ],
+ 'export_dependent_settings': [
+ '<(DEPTH)/third_party/google-sparsehash/google-sparsehash.gyp:include',
+ ],
+ 'include_dirs': [
+ '<(css_parser_root)',
+ '<(DEPTH)',
+ ],
+ 'cflags': ['-funsigned-char', '-Wno-sign-compare', '-Wno-return-type'],
+ 'sources': [
+ '<(css_parser_root)/string_using.h',
+ '<(css_parser_root)/webutil/css/identifier.cc',
+ '<(css_parser_root)/webutil/css/identifier.h',
+ '<(css_parser_root)/webutil/css/parser.cc',
+ '<(css_parser_root)/webutil/css/parser.h',
+ '<(css_parser_root)/webutil/css/property.cc',
+ '<(css_parser_root)/webutil/css/property.h',
+ '<(css_parser_root)/webutil/css/selector.cc',
+ '<(css_parser_root)/webutil/css/selector.h',
+ '<(css_parser_root)/webutil/css/string.h',
+ '<(css_parser_root)/webutil/css/string_util.cc',
+ '<(css_parser_root)/webutil/css/string_util.h',
+ '<(css_parser_root)/webutil/css/tostring.cc',
+ '<(css_parser_root)/webutil/css/util.cc',
+ '<(css_parser_root)/webutil/css/util.h',
+ '<(css_parser_root)/webutil/css/value.cc',
+ '<(css_parser_root)/webutil/css/value.h',
+ '<(css_parser_root)/webutil/css/valuevalidator.cc',
+ '<(css_parser_root)/webutil/css/valuevalidator.h',
+
+ #'<(css_parser_root)/webutil/css/parse_arg.cc',
+ # Tests
+ #'<(css_parser_root)/webutil/css/gtest_main.cc',
+ #'<(css_parser_root)/webutil/css/identifier_test.cc',
+ #'<(css_parser_root)/webutil/css/parser_unittest.cc',
+ #'<(css_parser_root)/webutil/css/property_test.cc',
+ #'<(css_parser_root)/webutil/css/tostring_test.cc',
+ #'<(css_parser_root)/webutil/css/util_test.cc',
+ #'<(css_parser_root)/webutil/css/valuevalidator_test.cc',
+
+ '<(css_parser_root)/webutil/html/htmlcolor.cc',
+ '<(css_parser_root)/webutil/html/htmlcolor.h',
+ '<(css_parser_root)/webutil/html/htmltagenum.cc',
+ '<(css_parser_root)/webutil/html/htmltagenum.h',
+ '<(css_parser_root)/webutil/html/htmltagindex.cc',
+ '<(css_parser_root)/webutil/html/htmltagindex.h',
+
+ # UnicodeText
+ '<(css_parser_root)/util/utf8/internal/unicodetext.cc',
+ '<(css_parser_root)/util/utf8/internal/unilib.cc',
+ '<(css_parser_root)/util/utf8/public/config.h',
+ '<(css_parser_root)/util/utf8/public/unicodetext.h',
+ '<(css_parser_root)/util/utf8/public/unilib.h',
+
+ # libutf
+ #'<(css_parser_root)/third_party/utf/Make.Linux-x86_64',
+ #'<(css_parser_root)/third_party/utf/Makefile',
+ #'<(css_parser_root)/third_party/utf/NOTICE',
+ #'<(css_parser_root)/third_party/utf/README',
+ '<(css_parser_root)/third_party/utf/rune.c',
+ '<(css_parser_root)/third_party/utf/runestrcat.c',
+ '<(css_parser_root)/third_party/utf/runestrchr.c',
+ '<(css_parser_root)/third_party/utf/runestrcmp.c',
+ '<(css_parser_root)/third_party/utf/runestrcpy.c',
+ '<(css_parser_root)/third_party/utf/runestrecpy.c',
+ '<(css_parser_root)/third_party/utf/runestrlen.c',
+ '<(css_parser_root)/third_party/utf/runestrncat.c',
+ '<(css_parser_root)/third_party/utf/runestrncmp.c',
+ '<(css_parser_root)/third_party/utf/runestrncpy.c',
+ '<(css_parser_root)/third_party/utf/runestrrchr.c',
+ '<(css_parser_root)/third_party/utf/runestrstr.c',
+ '<(css_parser_root)/third_party/utf/runetype.c',
+ # TODO(sligocki): What is the correct format for this?
+ # runetypebody-5.0.0.c should not be compiled by itself, only #included.
+ #'<(css_parser_root)/third_party/utf/runetypebody-5.0.0.c',
+ '<(css_parser_root)/third_party/utf/utf.h',
+ '<(css_parser_root)/third_party/utf/utfdef.h',
+ '<(css_parser_root)/third_party/utf/utfecpy.c',
+ '<(css_parser_root)/third_party/utf/utflen.c',
+ '<(css_parser_root)/third_party/utf/utfnlen.c',
+ '<(css_parser_root)/third_party/utf/utfrrune.c',
+ '<(css_parser_root)/third_party/utf/utfrune.c',
+ '<(css_parser_root)/third_party/utf/utfutf.c',
+
+ # Supporting interfaces.
+ '<(css_parser_root)/base/commandlineflags.h',
+ '<(css_parser_root)/base/googleinit.h',
+ '<(css_parser_root)/base/macros.h',
+ '<(css_parser_root)/base/paranoid.h',
+ '<(css_parser_root)/base/stringprintf.h',
+ '<(css_parser_root)/string_using.h',
+ '<(css_parser_root)/strings/ascii_ctype.cc',
+ '<(css_parser_root)/strings/ascii_ctype.h',
+ '<(css_parser_root)/strings/escaping.h',
+ '<(css_parser_root)/strings/join.h',
+ '<(css_parser_root)/strings/memutil.h',
+ '<(css_parser_root)/strings/stringpiece.h',
+ '<(css_parser_root)/strings/stringpiece_utils.cc',
+ '<(css_parser_root)/strings/stringpiece_utils.h',
+ '<(css_parser_root)/strings/stringprintf.h',
+ '<(css_parser_root)/strings/strutil.h',
+ #'<(css_parser_root)/testing/base/public/googletest.h',
+ #'<(css_parser_root)/testing/base/public/gunit.h',
+ '<(css_parser_root)/testing/production_stub/public/gunit_prod.h',
+ '<(css_parser_root)/util/gtl/dense_hash_map.h',
+ '<(css_parser_root)/util/gtl/map-util.h',
+ '<(css_parser_root)/util/gtl/singleton.h',
+ '<(css_parser_root)/util/gtl/stl_util-inl.h',
+ '<(css_parser_root)/util/hash/hash.h',
+ ],
+ },
+ ],
+}
diff --git a/trunk/src/third_party/css_parser/src/Makefile b/trunk/src/third_party/css_parser/src/Makefile
new file mode 100644
index 0000000..f1d7099
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/Makefile
@@ -0,0 +1,110 @@
+CXX = g++
+COMPILE_OPTS = -g -Wall -Werror -Wno-sign-compare -Wno-return-type \
+ -funsigned-char -fdiagnostics-show-option
+# TODO(sligocki): -Wno-error=sign-compare -Wno-error=return-type
+# TODO(sligocki): Why is LOG(FATAL) not considered a return?
+# TODO(sligocki): Why isn't -fdiagnostics-show-option working?
+#GPERF = false # Must explicitly set GPERF
+#GPERF_OPTS = --ignore-case -L C++ -S 1 -t -C -c -E -G -o
+
+SRC_DIR = /home/sligocki/src/css_parsing
+PAGE_SPEED_DIR = $(SRC_DIR)/pagespeed/src
+CHROMIUM_DIR = $(PAGE_SPEED_DIR)/third_party/chromium/src
+GFLAGS_ROOT = $(SRC_DIR)/gflags-1.3/install
+GTEST_ROOT = $(SRC_DIR)/gtest-1.5.0/install
+GHASH_DIR = $(SRC_DIR)/sparsehash-1.8.1/install/include
+
+INCLUDES = -I. -I$(SRC_DIR) -I$(CHROMIUM_DIR) -I$(GFLAGS_ROOT)/include -I$(GHASH_DIR)
+
+PAGE_SPEED_LIBS = -L$(PAGE_SPEED_DIR)/out/Release/obj.target/base -lbase -lrt
+GFLAGS_LIBS = $(GFLAGS_ROOT)/lib/libgflags.a
+GTEST_LIBS = $(GTEST_ROOT)/lib/libgtest.a
+
+LIBS = $(PAGE_SPEED_LIBS) $(GFLAGS_LIBS) $(GTEST_LIBS)
+
+OBJ_DIR = genfiles
+
+
+# Generated files are included, you do not need to build them.
+#CSS_GPERFS = $(wildcard webutil/css/*.gperf)
+#CSS_GPERF_CC = $(CSS_GPERFS:%.gperf=$(OBJ_DIR)/%.cc) # Generated files.
+
+CSS_HDRS = $(wildcard webutil/css/*.h)
+CSS_SRCS = webutil/css/parser.cc webutil/css/identifier.cc webutil/css/property.cc webutil/css/selector.cc webutil/css/string_util.cc webutil/css/tostring.cc webutil/css/util.cc webutil/css/value.cc webutil/css/valuevalidator.cc $(CSS_GPERF_CC)
+CSS_OBJS = $(CSS_SRCS:%.cc=$(OBJ_DIR)/%.o)
+
+CSS_TEST_SRCS = $(wildcard webutil/css/*test.cc)
+CSS_TEST_OBJS = $(CSS_TEST_SRCS:%.cc=$(OBJ_DIR)/%.o)
+CSS_TESTS = $(CSS_TEST_SRCS:%.cc=$(OBJ_DIR)/%)
+
+HTML_HDRS = $(wildcard webutil/html/*.h)
+HTML_SRCS = $(wildcard webutil/html/*.cc)
+HTML_OBJS = $(HTML_SRCS:%.cc=$(OBJ_DIR)/%.o)
+
+UNICODETEXT_HDRS = $(wildcard util/utf8/public/*.h)
+UNICODETEXT_SRCS = $(wildcard util/utf8/internal/*.cc)
+UNICODETEXT_OBJS = $(UNICODETEXT_SRCS:%.cc=$(OBJ_DIR)/%.o)
+
+OTHER_SRCS = strings/ascii_ctype.cc strings/stringpiece_utils.cc
+OTHER_OBJS = $(OTHER_SRCS:%.cc=$(OBJ_DIR)/%.o) third_party/utf/libutf.a
+
+OBJS = $(CSS_OBJS) $(HTML_OBJS) $(UNICODETEXT_OBJS) $(OTHER_OBJS)
+
+all: parse_args run_tests
+
+PARSE_ARGS_MAIN = $(OBJ_DIR)/webutil/css/parse_arg.o
+parse_args: $(PARSE_ARGS_MAIN) $(OBJS)
+ $(CXX) $^ $(LIBS) -o $@
+
+run_tests: $(CSS_TESTS)
+ STATUS=0; \
+ for x in $^; do \
+ echo -e "\n** Running $$x **"; \
+ ./$$x || STATUS=$$?; \
+ done; \
+ exit $$STATUS # So that build fails if any test fails
+
+TEST_MAIN = $(OBJ_DIR)/webutil/css/gtest_main.o
+%test: %test.o $(TEST_MAIN) $(OBJS)
+ $(CXX) $^ $(LIBS) -o $@
+
+$(OBJ_DIR)/%.o: %.cc # TODO(sligocki): Add dep on headers.
+ mkdir -p `dirname $@`
+ $(CXX) $(COMPILE_OPTS) $< -c $(INCLUDES) -o $@
+
+
+#$(OBJ_DIR)/webutil/css/property.cc: $(OBJ_DIR)/%.cc: %.gperf $(CSS_HDRS)
+# mkdir -p `dirname $@`
+# $(GPERF) $(GPERF_OPTS) -ZPropertyMapper $< > $@
+#
+#$(OBJ_DIR)/webutil/css/identifier.cc: $(OBJ_DIR)/%.cc: %.gperf $(CSS_HDRS)
+# mkdir -p `dirname $@`
+# $(GPERF) $(GPERF_OPTS) -ZIdentifierMapper $< > $@
+
+
+third_party/utf/libutf.a: $(wildcard third_party/utf/*.cc) $(wildcard third_party/utf/*.h)
+ make -C third_party/utf CC=$(CXX)
+
+
+clean:
+ chmod +w -R .
+ rm -f $(OBJ_DIR) css
+
+
+# Prepare files by adding license and other details needed for open-source.
+prepare_files:
+# Prepare webutil/html files (first by adding #include "string_using.h")
+ mkdir -p webutil/html; \
+ for f in `ls original/webutil/html/`; do \
+ cp apache-license.txt webutil/html/$$f; \
+ sed 's@#include <string>@\0\n#include "string_using.h"@' \
+ < original/webutil/html/$$f \
+ >> webutil/html/$$f; \
+ rm -f original/webutil/html/$$f; \
+ done
+# The rest of the files just need to have licenses added.
+ for f in `cd original/ && find . -type f`; do \
+ mkdir -p `dirname $$f`; \
+ cat apache-license.txt original/$$f > $$f; \
+ done
+ rm -rf original/
\ No newline at end of file
diff --git a/trunk/src/third_party/css_parser/src/README b/trunk/src/third_party/css_parser/src/README
new file mode 100644
index 0000000..1eb4590
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/README
@@ -0,0 +1,75 @@
+==================== Dependencies ==========================
+
+CSS parser depends upon:
+ * gflags: http://code.google.com/p/google-gflags/
+ * googletest: http://code.google.com/p/googletest/
+ * google-sparsehash: http://code.google.com/p/google-sparsehash/
+ * page-speed: http://code.google.com/p/page-speed/
+
+It also uses
+ * gperf: http://www.gnu.org/software/gperf/
+to generate .cc files. However, the generated .cc files are included in this
+distribution for convenience, so that gperf is not needed to build.
+
+To prepare gflags:
+
+$ cd $SRC_DIR
+$ wget http://google-gflags.googlecode.com/files/gflags-1.3.tar.gz
+$ tar -xzf gflags-1.3.tar.gz
+$ cd gflags-1.3
+$ ./configure --prefix=$PWD/install
+$ make
+$ make install
+
+To prepare googletest:
+
+$ cd $SRC_DIR
+$ wget http://googletest.googlecode.com/files/gtest-1.5.0.tar.gz
+$ tar -xzf gtest-1.5.0.tar.gz
+$ cd gtext-1.5.0
+$ ./configure --prefix=$PWD/install
+$ make
+$ make install
+
+To prepare google-sparsehash:
+
+$ cd $SRC_DIR
+$ wget http://google-sparsehash.googlecode.com/files/sparsehash-1.8.1.tar.gz
+$ tar -xzf sparsehash-1.8.1.tar.gz
+$ cd sparsehash-1.8.1
+$ ./configure --prefix=$PWD/install
+$ make
+$ make install
+
+To prepare pagespeed:
+
+$ cd $SRC_DIR
+$ mkdir pagespeed
+$ cd pagespeed
+$ gclient config http://page-speed.googlecode.com/svn/lib/trunk/src
+$ gclient update --force
+$ cd src
+$ BUILDTYPE=Release make -j4
+
+You will need gclient installed, see pagespeed documentation:
+ http://code.google.com/p/page-speed/wiki/HowToBuildNativeLibraries
+
+
+==================== Building ==============================
+
+Once the dependencies have been built as described above (in some directory
+$SRC_DIR) build UnicodeText:
+
+$ make SRC_DIR=$SRC_DIR
+
+This builds the libraries and a simple program parse_args you can use
+to test. Ex:
+
+$ ./parse_args "body,p,h1 { font-size:70%; color:#000000; background-color:#f1f1ed;} this.is¬#valid"
+Parsing:
+body,p,h1 { font-size:70%; color:#000000; background-color:#f1f1ed;} this.is¬#valid
+
+Stylesheet:
+/* AUTHOR */
+
+body,p,h1 {font-size: 70%;color: #000000;background-color: #f1f1ed}
diff --git a/trunk/src/third_party/css_parser/src/apache-license.txt b/trunk/src/third_party/css_parser/src/apache-license.txt
new file mode 100644
index 0000000..d74e930
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/apache-license.txt
@@ -0,0 +1,16 @@
+/**
+ * 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.
+ */
+
diff --git a/trunk/src/third_party/css_parser/src/base/commandlineflags.h b/trunk/src/third_party/css_parser/src/base/commandlineflags.h
new file mode 100644
index 0000000..fe8d85e
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/base/commandlineflags.h
@@ -0,0 +1,22 @@
+/**
+ * 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.
+ */
+#ifndef BASE_COMMANDLINEFLAGS_H_
+#define BASE_COMMANDLINEFLAGS_H_
+
+//#include "third_party/gflags/src/gflags/gflags.h"
+#include "gflags/gflags.h"
+
+#endif // BASE_COMMANDLINEFLAGS_H_
diff --git a/trunk/src/third_party/css_parser/src/base/googleinit.h b/trunk/src/third_party/css_parser/src/base/googleinit.h
new file mode 100644
index 0000000..5f49ea2
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/base/googleinit.h
@@ -0,0 +1,52 @@
+
+
+// Copyright (c) 2005, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// ---
+
+#ifndef _GOOGLEINIT_H
+#define _GOOGLEINIT_H
+
+class GoogleInitializer {
+ public:
+ typedef void (*void_function)(void);
+ GoogleInitializer(const char* name, void_function f) {
+ f();
+ }
+};
+
+#define REGISTER_MODULE_INITIALIZER(name, body) \
+ namespace { \
+ static void google_init_module_##name () { body; } \
+ GoogleInitializer google_initializer_module_##name(#name, \
+ google_init_module_##name); \
+ }
+
+#endif /* _GOOGLEINIT_H */
diff --git a/trunk/src/third_party/css_parser/src/base/macros.h b/trunk/src/third_party/css_parser/src/base/macros.h
new file mode 100644
index 0000000..aed0b77
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/base/macros.h
@@ -0,0 +1,23 @@
+/**
+ * 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.
+ */
+#ifndef BASE_MACROS_H_
+#define BASE_MACROS_H_
+
+//#include "third_party/chromium/src/base/basictypes.h"
+#include "base/basictypes.h"
+// For DISALLOW_COPY_AND_ASSIGN.
+
+#endif // BASE_MACROS_H_
diff --git a/trunk/src/third_party/css_parser/src/base/paranoid.h b/trunk/src/third_party/css_parser/src/base/paranoid.h
new file mode 100644
index 0000000..571ec0f
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/base/paranoid.h
@@ -0,0 +1,22 @@
+/**
+ * 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.
+ */
+
+#ifndef BASE_PARANOID_H_
+#define BASE_PARANOID_H_
+
+inline bool SanitizeBool(bool b) { return b; }
+
+#endif // BASE_PARANOID_H_
diff --git a/trunk/src/third_party/css_parser/src/base/stringprintf.h b/trunk/src/third_party/css_parser/src/base/stringprintf.h
new file mode 100644
index 0000000..e01096f
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/base/stringprintf.h
@@ -0,0 +1,23 @@
+/**
+ * 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.
+ */
+
+#ifndef BASE_STRINGPRINTF_H_
+#define BASE_STRINGPRINTF_H_
+
+//#include "third_party/chromium/src/base/string_util.h"
+#include "base/string_util.h"
+
+#endif // BASE_STRINGPRINTF_H_
diff --git a/trunk/src/third_party/css_parser/src/string_using.h b/trunk/src/third_party/css_parser/src/string_using.h
new file mode 100644
index 0000000..f460dbc
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/string_using.h
@@ -0,0 +1,9 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+// Author: sligocki@google.com (Shawn Ligocki)
+
+#ifndef WEBUTIL_CSS_OPEN_SOURCE_STRING_USING_H_
+#define WEBUTIL_CSS_OPEN_SOURCE_STRING_USING_H_
+
+using std::string;
+
+#endif // WEBUTIL_CSS_OPEN_SOURCE_STRING_USING_H_
diff --git a/trunk/src/third_party/css_parser/src/strings/ascii_ctype.cc b/trunk/src/third_party/css_parser/src/strings/ascii_ctype.cc
new file mode 100644
index 0000000..3b4229f
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/strings/ascii_ctype.cc
@@ -0,0 +1,103 @@
+/**
+ * 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.
+ */
+
+// Copyright 2007 Google Inc. All Rights Reserved.
+// Author: dpeng@google.com (Daniel Peng)
+
+#include "strings/ascii_ctype.h"
+
+// # Table generated by this Python code (bit 0x02 is currently unused):
+// def Hex2(n):
+// return '0x' + hex(n/16)[2:] + hex(n%16)[2:]
+// def IsPunct(ch):
+// return (ord(ch) >= 32 and ord(ch) < 127 and
+// not ch.isspace() and not ch.isalnum())
+// def IsBlank(ch):
+// return ch in ' \t'
+// def IsCntrl(ch):
+// return ord(ch) < 32 or ord(ch) == 127
+// def IsXDigit(ch):
+// return ch.isdigit() or ch.lower() in 'abcdef'
+// for i in range(128):
+// ch = chr(i)
+// mask = ((ch.isalpha() and 0x01 or 0) |
+// (ch.isalnum() and 0x04 or 0) |
+// (ch.isspace() and 0x08 or 0) |
+// (IsPunct(ch) and 0x10 or 0) |
+// (IsBlank(ch) and 0x20 or 0) |
+// (IsCntrl(ch) and 0x40 or 0) |
+// (IsXDigit(ch) and 0x80 or 0))
+// print Hex2(mask) + ',',
+// if i % 16 == 7:
+// print ' //', Hex2(i & 0x78)
+// elif i % 16 == 15:
+// print
+const uint8 kAsciiPropertyBits[256] = {
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, // 0x00
+ 0x40, 0x68, 0x48, 0x48, 0x48, 0x48, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, // 0x10
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x28, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, // 0x20
+ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, // 0x30
+ 0x84, 0x84, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
+ 0x10, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x05, // 0x40
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, // 0x50
+ 0x05, 0x05, 0x05, 0x10, 0x10, 0x10, 0x10, 0x10,
+ 0x10, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x05, // 0x60
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, // 0x70
+ 0x05, 0x05, 0x05, 0x10, 0x10, 0x10, 0x10, 0x40,
+};
+
+const char kAsciiToLower[256] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+ 64, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 91, 92, 93, 94, 95,
+ 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
+ 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,
+ 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
+ 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
+ 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175,
+ 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191,
+ 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207,
+ 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223,
+ 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
+ 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255
+};
+
+const char kAsciiToUpper[256] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
+ 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
+ 96, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
+ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 123, 124, 125, 126, 127,
+ 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
+ 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
+ 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175,
+ 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191,
+ 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207,
+ 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223,
+ 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
+ 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255
+};
diff --git a/trunk/src/third_party/css_parser/src/strings/ascii_ctype.h b/trunk/src/third_party/css_parser/src/strings/ascii_ctype.h
new file mode 100644
index 0000000..20ab628
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/strings/ascii_ctype.h
@@ -0,0 +1,112 @@
+/**
+ * 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.
+ */
+
+// Copyright 2007 Google Inc. All Rights Reserved.
+// Author: dpeng@google.com (Daniel Peng)
+
+#ifndef STRINGS_ASCII_CTYPE_H__
+#define STRINGS_ASCII_CTYPE_H__
+
+#include "base/basictypes.h"
+
+// ----------------------------------------------------------------------
+// ascii_isalpha()
+// ascii_isdigit()
+// ascii_isalnum()
+// ascii_isspace()
+// ascii_ispunct()
+// ascii_isblank()
+// ascii_iscntrl()
+// ascii_isxdigit()
+// ascii_isprint()
+// ascii_isgraph()
+// ascii_isupper()
+// ascii_islower()
+// ascii_tolower()
+// ascii_toupper()
+// The ctype.h versions of these routines are slow with some
+// compilers and/or architectures, perhaps because of locale
+// issues. These versions work for ascii only: they return
+// false for everything above \x7f (which means they return
+// false for any byte from any non-ascii UTF8 character).
+//
+// The individual bits do not have names because the array definition
+// is already tightly coupled to this, and names would make it harder
+// to read and debug.
+//
+// This is an example of the benchmark times from the unittest:
+// $ strings/ascii_ctype_test --benchmarks=all --heap_check=
+// Benchmark Time(ns) CPU(ns) Iterations
+// --------------------------------------------------
+// BM_Identity 121 120 5785985 2027.0 MB/s
+// BM_isalpha 1603 1597 511027 152.9 MB/s
+// BM_ascii_isalpha 223 224 3111595 1088.5 MB/s
+// BM_isdigit 181 183 3825722 1336.4 MB/s
+// BM_ascii_isdigit 236 239 2929312 1023.3 MB/s
+// BM_isalnum 1623 1615 460596 151.2 MB/s
+// BM_ascii_isalnum 253 255 2745518 959.1 MB/s
+// BM_isspace 1264 1258 555639 194.1 MB/s
+// BM_ascii_isspace 253 255 2745507 959.1 MB/s
+// BM_ispunct 1324 1317 555639 185.3 MB/s
+// BM_ascii_ispunct 252 255 2745507 959.1 MB/s
+// BM_isblank 1433 1426 511027 171.2 MB/s
+// BM_ascii_isblank 253 254 2745518 960.5 MB/s
+// BM_iscntrl 1643 1634 530383 149.4 MB/s
+// BM_ascii_iscntrl 252 255 2745518 959.1 MB/s
+// BM_isxdigit 1826 1817 414265 134.3 MB/s
+// BM_ascii_isxdigit 258 260 2692712 939.3 MB/s
+// BM_isprint 1677 1669 419224 146.2 MB/s
+// BM_ascii_isprint 237 239 2929312 1021.8 MB/s
+// BM_isgraph 1436 1429 507324 170.9 MB/s
+// BM_ascii_isgraph 237 239 2929312 1021.8 MB/s
+// BM_isupper 1550 1544 463647 158.1 MB/s
+// BM_ascii_isupper 237 239 2929312 1021.8 MB/s
+// BM_islower 1301 1294 538544 188.7 MB/s
+// BM_ascii_islower 237 239 2929312 1023.3 MB/s
+// BM_isascii 182 181 3846746 1345.7 MB/s
+// BM_ascii_isascii 209 211 3318039 1159.1 MB/s
+// BM_tolower 1743 1764 397786 138.4 MB/s
+// BM_ascii_tolower 210 211 3318039 1155.8 MB/s
+// BM_toupper 1742 1764 397788 138.4 MB/s
+// BM_ascii_toupper 212 211 3302401 1156.9 MB/s
+//
+// ----------------------------------------------------------------------
+
+#define kApb kAsciiPropertyBits
+extern const uint8 kAsciiPropertyBits[256];
+static inline bool ascii_isalpha(unsigned char c) { return kApb[c] & 0x01; }
+static inline bool ascii_isalnum(unsigned char c) { return kApb[c] & 0x04; }
+static inline bool ascii_isspace(unsigned char c) { return kApb[c] & 0x08; }
+static inline bool ascii_ispunct(unsigned char c) { return kApb[c] & 0x10; }
+static inline bool ascii_isblank(unsigned char c) { return kApb[c] & 0x20; }
+static inline bool ascii_iscntrl(unsigned char c) { return kApb[c] & 0x40; }
+static inline bool ascii_isxdigit(unsigned char c) { return kApb[c] & 0x80; }
+static inline bool ascii_isdigit(unsigned char c) { return c >= '0' && c <= '9'; }
+static inline bool ascii_isprint(unsigned char c) { return c >= 32 && c < 127; }
+static inline bool ascii_isgraph(unsigned char c) { return c > 32 && c < 127; }
+static inline bool ascii_isupper(unsigned char c) { return c >= 'A' && c <= 'Z'; }
+static inline bool ascii_islower(unsigned char c) { return c >= 'a' && c <= 'z'; }
+static inline bool ascii_isascii(unsigned char c) {
+ return static_cast<signed char>(c) >= 0;
+}
+#undef kApb
+
+extern const char kAsciiToLower[256];
+static inline char ascii_tolower(unsigned char c) { return kAsciiToLower[c]; }
+extern const char kAsciiToUpper[256];
+static inline char ascii_toupper(unsigned char c) { return kAsciiToUpper[c]; }
+
+#endif // STRINGS_ASCII_CTYPE_H__
diff --git a/trunk/src/third_party/css_parser/src/strings/escaping.h b/trunk/src/third_party/css_parser/src/strings/escaping.h
new file mode 100644
index 0000000..8f84ca7
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/strings/escaping.h
@@ -0,0 +1,32 @@
+/**
+ * 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.
+ */
+#ifndef STRINGS_ESCAPING_H_
+#define STRINGS_ESCAPING_H_
+
+#include "base/logging.h"
+
+inline int hex_digit_to_int(char c) {
+ /* Assume ASCII. */
+ DCHECK('0' == 0x30 && 'A' == 0x41 && 'a' == 0x61);
+ DCHECK(isxdigit(c));
+ int x = static_cast<unsigned char>(c);
+ if (x > '9') {
+ x += 9;
+ }
+ return x & 0xf;
+}
+
+#endif // STRINGS_ESCAPING_H_
diff --git a/trunk/src/third_party/css_parser/src/strings/join.h b/trunk/src/third_party/css_parser/src/strings/join.h
new file mode 100644
index 0000000..30f61b2
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/strings/join.h
@@ -0,0 +1,37 @@
+/**
+ * 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.
+ */
+#ifndef STRINGS_JOIN_H_
+#define STRINGS_JOIN_H_
+
+#include <string>
+#include <vector>
+
+// TODO(sligocki): Add this to Chromium string_util.h
+inline void JoinStrings(const std::vector<std::string>& parts,
+ const char* delim, std::string* result) {
+ if (parts.size() != 0) {
+ result->append(parts[0]);
+ std::vector<std::string>::const_iterator iter = parts.begin();
+ ++iter;
+
+ for (; iter != parts.end(); ++iter) {
+ result->append(delim);
+ result->append(*iter);
+ }
+ }
+}
+
+#endif // STRINGS_JOIN_H_
diff --git a/trunk/src/third_party/css_parser/src/strings/memutil.h b/trunk/src/third_party/css_parser/src/strings/memutil.h
new file mode 100644
index 0000000..f9a48a6
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/strings/memutil.h
@@ -0,0 +1,42 @@
+/**
+ * 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.
+ */
+#ifndef MEMUTIL_H_
+#define MEMUTIL_H_
+
+// The ""'s catch people who don't pass in a literal for "str"
+#define strliterallen(str) (sizeof("" str "")-1)
+
+static int memcasecmp(const char *s1, const char *s2, size_t len) {
+ const unsigned char *us1 = reinterpret_cast<const unsigned char *>(s1);
+ const unsigned char *us2 = reinterpret_cast<const unsigned char *>(s2);
+
+ for ( size_t i = 0; i < len; i++ ) {
+ const int diff = tolower(us1[i]) - tolower(us2[i]);
+ if (diff != 0) return diff;
+ }
+ return 0;
+}
+
+#define memcaseis(str, len, literal) \
+ ( (((len) == strliterallen(literal)) \
+ && memcasecmp(str, literal, strliterallen(literal)) == 0) )
+
+#define memis(str, len, literal) \
+ ( (((len) == strliterallen(literal)) \
+ && memcmp(str, literal, strliterallen(literal)) == 0) )
+
+
+#endif // MEMUTIL_H_
diff --git a/trunk/src/third_party/css_parser/src/strings/stringpiece.h b/trunk/src/third_party/css_parser/src/strings/stringpiece.h
new file mode 100644
index 0000000..7b56772
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/strings/stringpiece.h
@@ -0,0 +1,24 @@
+/**
+ * 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.
+ */
+#ifndef STRINGS_STRINGPIECE_H_
+#define STRINGS_STRINGPIECE_H_
+
+//#include "third_party/chromium/src/base/string_piece.h"
+#include "base/string_piece.h"
+
+using base::StringPiece;
+
+#endif // STRINGS_STRINGPIECE_H_
diff --git a/trunk/src/third_party/css_parser/src/strings/stringpiece_utils.cc b/trunk/src/third_party/css_parser/src/strings/stringpiece_utils.cc
new file mode 100644
index 0000000..4c987c9
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/strings/stringpiece_utils.cc
@@ -0,0 +1,83 @@
+/**
+ * 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.
+ */
+#include "strings/stringpiece_utils.h"
+
+#include <vector>
+#include "strings/ascii_ctype.h"
+
+namespace StringPieceUtils {
+
+int RemoveLeadingWhitespace(StringPiece* text) {
+ int count = 0;
+ const char* ptr = text->data();
+ while (count < text->size() && ascii_isspace(*ptr)) {
+ count++;
+ ptr++;
+ }
+ text->remove_prefix(count);
+ return count;
+}
+
+int RemoveTrailingWhitespace(StringPiece* text) {
+ int count = 0;
+ const char* ptr = text->data() + text->size() - 1;
+ while (count < text->size() && ascii_isspace(*ptr)) {
+ ++count;
+ --ptr;
+ }
+ text->remove_suffix(count);
+ return count;
+}
+
+int RemoveWhitespaceContext(StringPiece* text) {
+ // use RemoveLeadingWhitespace() and RemoveTrailingWhitespace() to do the job
+ return (RemoveLeadingWhitespace(text) + RemoveTrailingWhitespace(text));
+}
+
+
+template <typename DelimType>
+static void SplitInternal(const StringPiece& full, DelimType delim,
+ std::vector<StringPiece>* result) {
+ StringPiece::size_type begin_index, end_index;
+ begin_index = full.find_first_not_of(delim);
+ while (begin_index != StringPiece::npos) {
+ end_index = full.find_first_of(delim, begin_index);
+ if (end_index == StringPiece::npos) {
+ result->push_back(StringPiece(full.data() + begin_index,
+ full.size() - begin_index));
+ return;
+ }
+ result->push_back(StringPiece(full.data() + begin_index,
+ end_index - begin_index));
+ begin_index = full.find_first_not_of(delim, end_index);
+ }
+}
+
+
+// the code below is similar to SplitStringUsing in strings/strutil.cc but
+// this one returns a vector of pieces in the original string thus eliminating
+// the allocation/copy for each string in the result vector.
+/* static */
+void Split(const StringPiece& full, const char* delim,
+ std::vector<StringPiece>* result) {
+ if (delim[0] != '\0' && delim[1] == '\0') {
+ SplitInternal(full, delim[0], result);
+ } else {
+ SplitInternal(full, delim, result);
+ }
+}
+
+} // namespace StringPieceUtils
diff --git a/trunk/src/third_party/css_parser/src/strings/stringpiece_utils.h b/trunk/src/third_party/css_parser/src/strings/stringpiece_utils.h
new file mode 100644
index 0000000..a4576c0
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/strings/stringpiece_utils.h
@@ -0,0 +1,42 @@
+/**
+ * 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.
+ */
+
+// Copyright 2003 and onwards Google Inc.
+//
+// Utility functions for operating on StringPieces
+// Collected here for convenience
+
+#ifndef STRINGS_STRINGPIECE_UTILS_H_
+#define STRINGS_STRINGPIECE_UTILS_H_
+
+#include <vector>
+#include "strings/stringpiece.h"
+
+namespace StringPieceUtils {
+
+// removes leading and trailing ascii_isspace() chars. Returns
+// number of chars removed
+int RemoveWhitespaceContext(StringPiece* text);
+
+// similar to SplitStringUsing (see strings/split.h).
+// this one returns a vector of pieces in the original string thus eliminating
+// the allocation/copy for each string in the result vector.
+void Split(const StringPiece& full, const char* delim,
+ std::vector<StringPiece>* pieces);
+
+} // namespace StringPieceUtils
+
+#endif // STRINGS_STRINGPIECE_UTILS_H_
diff --git a/trunk/src/third_party/css_parser/src/strings/stringprintf.h b/trunk/src/third_party/css_parser/src/strings/stringprintf.h
new file mode 100644
index 0000000..208d338
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/strings/stringprintf.h
@@ -0,0 +1,22 @@
+/**
+ * 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.
+ */
+#ifndef STRINGS_STRINGPRINTF_H_
+#define STRINGS_STRINGPRINTF_H_
+
+//#include "third_party/chromium/src/base/string_util.h"
+#include "base/string_util.h"
+
+#endif // STRINGS_STRINGPRINTF_H_
diff --git a/trunk/src/third_party/css_parser/src/strings/strutil.h b/trunk/src/third_party/css_parser/src/strings/strutil.h
new file mode 100644
index 0000000..5598843
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/strings/strutil.h
@@ -0,0 +1,34 @@
+/**
+ * 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.
+ */
+#ifndef STRINGS_STRUTIL_H_
+#define STRINGS_STRUTIL_H_
+
+//#include "third_party/chromium/src/base/string_util.h"
+#include "base/string_util.h"
+
+// Like the CONSIDER macro above except that it supports enums from another
+// class -- e.g., if: enum Status { VERIFIED, NOT_VERIFIED, WHITE_LISTED }
+// is in class Foo and you are using it in another class, use:
+// switch (val) {
+// CONSIDER_IN_CLASS(Foo, VERIFIED);
+// CONSIDER_IN_CLASS(Foo, NOT_VERIFIED);
+// CONSIDER_IN_CLASS(Foo, WHITE_LISTED);
+// default: return "UNKNOWN value";
+// }
+// Only the enum string will be returned (i.e., without the "Foo::" prefix).
+#define CONSIDER_IN_CLASS(cls,val) case cls::val: return #val
+
+#endif // WEBUTIL_CSS_OPEN_SOURCE_STRINGS_STRUTIL_H_
diff --git a/trunk/src/third_party/css_parser/src/testing/base/public/googletest.h b/trunk/src/third_party/css_parser/src/testing/base/public/googletest.h
new file mode 100644
index 0000000..d09072c
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/testing/base/public/googletest.h
@@ -0,0 +1,15 @@
+/**
+ * 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.
+ */
diff --git a/trunk/src/third_party/css_parser/src/testing/base/public/gunit.h b/trunk/src/third_party/css_parser/src/testing/base/public/gunit.h
new file mode 100644
index 0000000..a9ad0b0
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/testing/base/public/gunit.h
@@ -0,0 +1,21 @@
+/**
+ * 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.
+ */
+#ifndef WEBUTIL_CSS_OPEN_SOURCE_TESTING_BASE_PUBLIC_GUNIT_H_
+#define WEBUTIL_CSS_OPEN_SOURCE_TESTING_BASE_PUBLIC_GUNIT_H_
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+#endif // WEBUTIL_CSS_OPEN_SOURCE_TESTING_BASE_PUBLIC_GUNIT_H_
diff --git a/trunk/src/third_party/css_parser/src/testing/production_stub/public/gunit_prod.h b/trunk/src/third_party/css_parser/src/testing/production_stub/public/gunit_prod.h
new file mode 100644
index 0000000..d27ec4c
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/testing/production_stub/public/gunit_prod.h
@@ -0,0 +1,21 @@
+/**
+ * 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.
+ */
+#ifndef WEBUTIL_CSS_OPEN_SOURCE_TESTING_PRODUCTION_STUB_PUBLIC_GUNIT_PROD_H_
+#define WEBUTIL_CSS_OPEN_SOURCE_TESTING_PRODUCTION_STUB_PUBLIC_GUNIT_PROD_H_
+
+#include "testing/gtest/include/gtest/gtest_prod.h"
+
+#endif // WEBUTIL_CSS_OPEN_SOURCE_TESTING_PRODUCTION_STUB_PUBLIC_GUNIT_PROD_H_
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/Make.Linux-x86_64 b/trunk/src/third_party/css_parser/src/third_party/utf/Make.Linux-x86_64
new file mode 100644
index 0000000..d5ecd0f
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/Make.Linux-x86_64
@@ -0,0 +1,7 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -fPIC -O2 -g -c -I. -I../..
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O # default, can be overriden by Make.$(SYSNAME)
+NAN=nan64.$O
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/Makefile b/trunk/src/third_party/css_parser/src/third_party/utf/Makefile
new file mode 100644
index 0000000..2823aaa
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/Makefile
@@ -0,0 +1,116 @@
+
+# this works in gnu make
+SYSNAME:=${shell uname}
+OBJTYPE:=${shell uname -m | sed 's;i.86;386;; s;/.*;;'}
+
+# this works in bsd make
+SYSNAME!=uname
+OBJTYPE!=uname -m | sed 's;i.86;386;; s;/.*;;'
+
+# the gnu rules will mess up bsd but not vice versa,
+# hence the gnu rules come first.
+
+include Make.$(SYSNAME)-$(OBJTYPE)
+
+PREFIX=/usr/local
+
+NUKEFILES=
+
+TGZFILES=
+
+LIB=libutf.a
+VERSION=2.0
+PORTPLACE=devel/libutf
+NAME=libutf
+
+OFILES=\
+ rune.$O\
+ runestrcat.$O\
+ runestrchr.$O\
+ runestrcmp.$O\
+ runestrcpy.$O\
+ runestrdup.$O\
+ runestrlen.$O\
+ runestrecpy.$O\
+ runestrncat.$O\
+ runestrncmp.$O\
+ runestrncpy.$O\
+ runestrrchr.$O\
+ runestrstr.$O\
+ runetype.$O\
+ utfecpy.$O\
+ utflen.$O\
+ utfnlen.$O\
+ utfrrune.$O\
+ utfrune.$O\
+ utfutf.$O\
+
+HFILES=\
+ utf.h\
+
+all: $(LIB)
+
+install: $(LIB)
+ test -d $(PREFIX)/man/man3 || mkdir $(PREFIX)/man/man3
+ install -m 0644 isalpharune.3 $(PREFIX)/man/man3/isalpharune.3
+ install -m 0644 utf.7 $(PREFIX)/man/man7/utf.7
+ install -m 0644 rune.3 $(PREFIX)/man/man3/rune.3
+ install -m 0644 runestrcat.3 $(PREFIX)/man/man3/runestrcat.3
+ install -m 0644 utf.h $(PREFIX)/include/utf.h
+ install -m 0644 $(LIB) $(PREFIX)/lib/$(LIB)
+
+# for now - we probably should connect this with the Google makefile so
+# we use the same compiler at all times...
+CC = /home/build/buildtools/production/gcc-2.95.3/bin/i686-google-linux-gcc
+
+$(LIB): $(OFILES)
+ $(AR) $(ARFLAGS) $(LIB) $(OFILES)
+
+NUKEFILES+=$(LIB)
+.c.$O:
+ $(CC) $(CFLAGS) -I$(PREFIX)/include $*.c
+
+%.$O: %.c
+ $(CC) $(CFLAGS) -I$(PREFIX)/include $*.c
+
+
+$(OFILES): $(HFILES)
+
+tgz:
+ rm -rf $(NAME)-$(VERSION)
+ mkdir $(NAME)-$(VERSION)
+ cp Makefile Make.* README LICENSE NOTICE *.[ch137] rpm.spec bundle.ports $(TGZFILES) $(NAME)-$(VERSION)
+ tar cf - $(NAME)-$(VERSION) | gzip >$(NAME)-$(VERSION).tgz
+ rm -rf $(NAME)-$(VERSION)
+
+clean:
+ rm -f $(OFILES)
+
+nuke:
+ rm -f $(OFILES) *.tgz *.rpm $(NUKEFILES)
+
+rpm:
+ make tgz
+ cp $(NAME)-$(VERSION).tgz /usr/src/RPM/SOURCES
+ rpm -ba rpm.spec
+ cp /usr/src/RPM/SRPMS/$(NAME)-$(VERSION)-1.src.rpm .
+ cp /usr/src/RPM/RPMS/i586/$(NAME)-$(VERSION)-1.i586.rpm .
+ scp *.rpm rsc@amsterdam.lcs.mit.edu:public_html/software
+
+PORTDIR=/usr/ports/$(PORTPLACE)
+
+ports:
+ make tgz
+ rm -rf $(PORTDIR)
+ mkdir $(PORTDIR)
+ cp $(NAME)-$(VERSION).tgz /usr/ports/distfiles
+ cat bundle.ports | (cd $(PORTDIR) && awk '$$1=="---" && $$3=="---" { ofile=$$2; next} {if(ofile) print >ofile}')
+ (cd $(PORTDIR); make makesum)
+ (cd $(PORTDIR); make)
+ (cd $(PORTDIR); /usr/local/bin/portlint)
+ rm -rf $(PORTDIR)/work
+ shar `find $(PORTDIR)` > ports.shar
+ (cd $(PORTDIR); tar cf - *) | gzip >$(NAME)-$(VERSION)-ports.tgz
+ scp *.tgz rsc@amsterdam.lcs.mit.edu:public_html/software
+
+.phony: all clean nuke install tgz rpm ports
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/NOTICE b/trunk/src/third_party/css_parser/src/third_party/utf/NOTICE
new file mode 100644
index 0000000..ad76cd5
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/NOTICE
@@ -0,0 +1,13 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 1998-2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/README b/trunk/src/third_party/css_parser/src/third_party/utf/README
new file mode 100644
index 0000000..ad76cd5
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/README
@@ -0,0 +1,13 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 1998-2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/rune.c b/trunk/src/third_party/css_parser/src/third_party/utf/rune.c
new file mode 100644
index 0000000..5fb31cd
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/rune.c
@@ -0,0 +1,350 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "third_party/utf/utf.h"
+#include "third_party/utf/utfdef.h"
+
+enum
+{
+ Bit1 = 7,
+ Bitx = 6,
+ Bit2 = 5,
+ Bit3 = 4,
+ Bit4 = 3,
+ Bit5 = 2,
+
+ T1 = ((1<<(Bit1+1))-1) ^ 0xFF, /* 0000 0000 */
+ Tx = ((1<<(Bitx+1))-1) ^ 0xFF, /* 1000 0000 */
+ T2 = ((1<<(Bit2+1))-1) ^ 0xFF, /* 1100 0000 */
+ T3 = ((1<<(Bit3+1))-1) ^ 0xFF, /* 1110 0000 */
+ T4 = ((1<<(Bit4+1))-1) ^ 0xFF, /* 1111 0000 */
+ T5 = ((1<<(Bit5+1))-1) ^ 0xFF, /* 1111 1000 */
+
+ Rune1 = (1<<(Bit1+0*Bitx))-1, /* 0000 0000 0111 1111 */
+ Rune2 = (1<<(Bit2+1*Bitx))-1, /* 0000 0111 1111 1111 */
+ Rune3 = (1<<(Bit3+2*Bitx))-1, /* 1111 1111 1111 1111 */
+ Rune4 = (1<<(Bit4+3*Bitx))-1,
+ /* 0001 1111 1111 1111 1111 1111 */
+
+ Maskx = (1<<Bitx)-1, /* 0011 1111 */
+ Testx = Maskx ^ 0xFF, /* 1100 0000 */
+
+ Bad = Runeerror,
+};
+
+/*
+ * Modified by Wei-Hwa Huang, Google Inc., on 2004-09-24
+ * This is a slower but "safe" version of the old chartorune
+ * that works on strings that are not necessarily null-terminated.
+ *
+ * If you know for sure that your string is null-terminated,
+ * chartorune will be a bit faster.
+ *
+ * It is guaranteed not to attempt to access "length"
+ * past the incoming pointer. This is to avoid
+ * possible access violations. If the string appears to be
+ * well-formed but incomplete (i.e., to get the whole Rune
+ * we'd need to read past str+length) then we'll set the Rune
+ * to Bad and return 0.
+ *
+ * Note that if we have decoding problems for other
+ * reasons, we return 1 instead of 0.
+ */
+int
+charntorune(Rune *rune, const char *str, int length)
+{
+ int c, c1, c2, c3;
+ long l;
+
+ /* When we're not allowed to read anything */
+ if(length <= 0) {
+ goto badlen;
+ }
+
+ /*
+ * one character sequence (7-bit value)
+ * 00000-0007F => T1
+ */
+ c = *(uchar*)str;
+ if(c < Tx) {
+ *rune = c;
+ return 1;
+ }
+
+ // If we can't read more than one character we must stop
+ if(length <= 1) {
+ goto badlen;
+ }
+
+ /*
+ * two character sequence (11-bit value)
+ * 0080-07FF => T2 Tx
+ */
+ c1 = *(uchar*)(str+1) ^ Tx;
+ if(c1 & Testx)
+ goto bad;
+ if(c < T3) {
+ if(c < T2)
+ goto bad;
+ l = ((c << Bitx) | c1) & Rune2;
+ if(l <= Rune1)
+ goto bad;
+ *rune = l;
+ return 2;
+ }
+
+ // If we can't read more than two characters we must stop
+ if(length <= 2) {
+ goto badlen;
+ }
+
+ /*
+ * three character sequence (16-bit value)
+ * 0800-FFFF => T3 Tx Tx
+ */
+ c2 = *(uchar*)(str+2) ^ Tx;
+ if(c2 & Testx)
+ goto bad;
+ if(c < T4) {
+ l = ((((c << Bitx) | c1) << Bitx) | c2) & Rune3;
+ if(l <= Rune2)
+ goto bad;
+ *rune = l;
+ return 3;
+ }
+
+ if (length <= 3)
+ goto badlen;
+
+ /*
+ * four character sequence (21-bit value)
+ * 10000-1FFFFF => T4 Tx Tx Tx
+ */
+ c3 = *(uchar*)(str+3) ^ Tx;
+ if (c3 & Testx)
+ goto bad;
+ if (c < T5) {
+ l = ((((((c << Bitx) | c1) << Bitx) | c2) << Bitx) | c3) & Rune4;
+ if (l <= Rune3)
+ goto bad;
+ *rune = l;
+ return 4;
+ }
+
+ // Support for 5-byte or longer UTF-8 would go here, but
+ // since we don't have that, we'll just fall through to bad.
+
+ /*
+ * bad decoding
+ */
+bad:
+ *rune = Bad;
+ return 1;
+badlen:
+ *rune = Bad;
+ return 0;
+
+}
+
+
+/*
+ * This is the older "unsafe" version, which works fine on
+ * null-terminated strings.
+ */
+int
+chartorune(Rune *rune, const char *str)
+{
+ int c, c1, c2, c3;
+ long l;
+
+ /*
+ * one character sequence
+ * 00000-0007F => T1
+ */
+ c = *(uchar*)str;
+ if(c < Tx) {
+ *rune = c;
+ return 1;
+ }
+
+ /*
+ * two character sequence
+ * 0080-07FF => T2 Tx
+ */
+ c1 = *(uchar*)(str+1) ^ Tx;
+ if(c1 & Testx)
+ goto bad;
+ if(c < T3) {
+ if(c < T2)
+ goto bad;
+ l = ((c << Bitx) | c1) & Rune2;
+ if(l <= Rune1)
+ goto bad;
+ *rune = l;
+ return 2;
+ }
+
+ /*
+ * three character sequence
+ * 0800-FFFF => T3 Tx Tx
+ */
+ c2 = *(uchar*)(str+2) ^ Tx;
+ if(c2 & Testx)
+ goto bad;
+ if(c < T4) {
+ l = ((((c << Bitx) | c1) << Bitx) | c2) & Rune3;
+ if(l <= Rune2)
+ goto bad;
+ *rune = l;
+ return 3;
+ }
+
+ /*
+ * four character sequence (21-bit value)
+ * 10000-1FFFFF => T4 Tx Tx Tx
+ */
+ c3 = *(uchar*)(str+3) ^ Tx;
+ if (c3 & Testx)
+ goto bad;
+ if (c < T5) {
+ l = ((((((c << Bitx) | c1) << Bitx) | c2) << Bitx) | c3) & Rune4;
+ if (l <= Rune3)
+ goto bad;
+ *rune = l;
+ return 4;
+ }
+
+ /*
+ * Support for 5-byte or longer UTF-8 would go here, but
+ * since we don't have that, we'll just fall through to bad.
+ */
+
+ /*
+ * bad decoding
+ */
+bad:
+ *rune = Bad;
+ return 1;
+}
+
+int
+isvalidcharntorune(const char* str, int length, Rune* rune, int* consumed) {
+ *consumed = charntorune(rune, str, length);
+ return *rune != Runeerror || *consumed == 3;
+}
+
+int
+runetochar(char *str, const Rune *rune)
+{
+ /* Runes are signed, so convert to unsigned for range check. */
+ unsigned long c;
+
+ /*
+ * one character sequence
+ * 00000-0007F => 00-7F
+ */
+ c = *rune;
+ if(c <= Rune1) {
+ str[0] = c;
+ return 1;
+ }
+
+ /*
+ * two character sequence
+ * 0080-07FF => T2 Tx
+ */
+ if(c <= Rune2) {
+ str[0] = T2 | (c >> 1*Bitx);
+ str[1] = Tx | (c & Maskx);
+ return 2;
+ }
+
+ /*
+ * If the Rune is out of range, convert it to the error rune.
+ * Do this test here because the error rune encodes to three bytes.
+ * Doing it earlier would duplicate work, since an out of range
+ * Rune wouldn't have fit in one or two bytes.
+ */
+ if (c > Runemax)
+ c = Runeerror;
+
+ /*
+ * three character sequence
+ * 0800-FFFF => T3 Tx Tx
+ */
+ if (c <= Rune3) {
+ str[0] = T3 | (c >> 2*Bitx);
+ str[1] = Tx | ((c >> 1*Bitx) & Maskx);
+ str[2] = Tx | (c & Maskx);
+ return 3;
+ }
+
+ /*
+ * four character sequence (21-bit value)
+ * 10000-1FFFFF => T4 Tx Tx Tx
+ */
+ str[0] = T4 | (c >> 3*Bitx);
+ str[1] = Tx | ((c >> 2*Bitx) & Maskx);
+ str[2] = Tx | ((c >> 1*Bitx) & Maskx);
+ str[3] = Tx | (c & Maskx);
+ return 4;
+}
+
+int
+runelen(Rune rune)
+{
+ char str[10];
+
+ return runetochar(str, &rune);
+}
+
+int
+runenlen(const Rune *r, int nrune)
+{
+ int nb, c;
+
+ nb = 0;
+ while(nrune--) {
+ c = *r++;
+ if (c <= Rune1)
+ nb++;
+ else if (c <= Rune2)
+ nb += 2;
+ else if (c <= Rune3)
+ nb += 3;
+ else /* assert(c <= Rune4) */
+ nb += 4;
+ }
+ return nb;
+}
+
+int
+fullrune(const char *str, int n)
+{
+ if (n > 0) {
+ int c = *(uchar*)str;
+ if (c < Tx)
+ return 1;
+ if (n > 1) {
+ if (c < T3)
+ return 1;
+ if (n > 2) {
+ if (c < T4 || n > 3)
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/runestrcat.c b/trunk/src/third_party/css_parser/src/third_party/utf/runestrcat.c
new file mode 100644
index 0000000..d68f595
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/runestrcat.c
@@ -0,0 +1,25 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "third_party/utf/utf.h"
+#include "third_party/utf/utfdef.h"
+
+Rune*
+runestrcat(Rune *s1, const Rune *s2)
+{
+
+ runestrcpy((Rune*)runestrchr(s1, 0), s2);
+ return s1;
+}
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/runestrchr.c b/trunk/src/third_party/css_parser/src/third_party/utf/runestrchr.c
new file mode 100644
index 0000000..d5bd97d
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/runestrchr.c
@@ -0,0 +1,36 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "third_party/utf/utf.h"
+#include "third_party/utf/utfdef.h"
+
+const
+Rune*
+runestrchr(const Rune *s, Rune c)
+{
+ Rune c0 = c;
+ Rune c1;
+
+ if(c == 0) {
+ while(*s++)
+ ;
+ return s-1;
+ }
+
+ while((c1 = *s++) != 0)
+ if(c1 == c0)
+ return s-1;
+ return 0;
+}
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/runestrcmp.c b/trunk/src/third_party/css_parser/src/third_party/utf/runestrcmp.c
new file mode 100644
index 0000000..ac9d0e5
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/runestrcmp.c
@@ -0,0 +1,35 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "third_party/utf/utf.h"
+#include "third_party/utf/utfdef.h"
+
+int
+runestrcmp(const Rune *s1, const Rune *s2)
+{
+ Rune c1, c2;
+
+ for(;;) {
+ c1 = *s1++;
+ c2 = *s2++;
+ if(c1 != c2) {
+ if(c1 > c2)
+ return 1;
+ return -1;
+ }
+ if(c1 == 0)
+ return 0;
+ }
+}
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/runestrcpy.c b/trunk/src/third_party/css_parser/src/third_party/utf/runestrcpy.c
new file mode 100644
index 0000000..eb41413
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/runestrcpy.c
@@ -0,0 +1,28 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "third_party/utf/utf.h"
+#include "third_party/utf/utfdef.h"
+
+Rune*
+runestrcpy(Rune *s1, const Rune *s2)
+{
+ Rune *os1;
+
+ os1 = s1;
+ while((*s1++ = *s2++) != 0)
+ ;
+ return os1;
+}
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/runestrdup.c b/trunk/src/third_party/css_parser/src/third_party/utf/runestrdup.c
new file mode 100644
index 0000000..2b15035
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/runestrdup.c
@@ -0,0 +1,30 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include <stdlib.h>
+#include "third_party/utf/utf.h"
+#include "third_party/utf/utfdef.h"
+
+Rune*
+runestrdup(const Rune *s)
+{
+ Rune *ns;
+
+ ns = (Rune*)malloc(sizeof(Rune)*(runestrlen(s) + 1));
+ if(ns == 0)
+ return 0;
+
+ return runestrcpy(ns, s);
+}
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/runestrecpy.c b/trunk/src/third_party/css_parser/src/third_party/utf/runestrecpy.c
new file mode 100644
index 0000000..b56abe7
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/runestrecpy.c
@@ -0,0 +1,32 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "third_party/utf/utf.h"
+#include "third_party/utf/utfdef.h"
+
+Rune*
+runestrecpy(Rune *s1, Rune *es1, const Rune *s2)
+{
+ if(s1 >= es1)
+ return s1;
+
+ while((*s1++ = *s2++) != 0){
+ if(s1 == es1){
+ *--s1 = '\0';
+ break;
+ }
+ }
+ return s1;
+}
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/runestrlen.c b/trunk/src/third_party/css_parser/src/third_party/utf/runestrlen.c
new file mode 100644
index 0000000..e019881
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/runestrlen.c
@@ -0,0 +1,24 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "third_party/utf/utf.h"
+#include "third_party/utf/utfdef.h"
+
+long
+runestrlen(const Rune *s)
+{
+
+ return runestrchr(s, 0) - s;
+}
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/runestrncat.c b/trunk/src/third_party/css_parser/src/third_party/utf/runestrncat.c
new file mode 100644
index 0000000..bca42f4
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/runestrncat.c
@@ -0,0 +1,32 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "third_party/utf/utf.h"
+#include "third_party/utf/utfdef.h"
+
+Rune*
+runestrncat(Rune *s1, const Rune *s2, long n)
+{
+ Rune *os1;
+
+ os1 = s1;
+ s1 = (Rune*)runestrchr(s1, 0);
+ while((*s1++ = *s2++) != 0)
+ if(--n < 0) {
+ s1[-1] = 0;
+ break;
+ }
+ return os1;
+}
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/runestrncmp.c b/trunk/src/third_party/css_parser/src/third_party/utf/runestrncmp.c
new file mode 100644
index 0000000..02925b1
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/runestrncmp.c
@@ -0,0 +1,37 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "third_party/utf/utf.h"
+#include "third_party/utf/utfdef.h"
+
+int
+runestrncmp(const Rune *s1, const Rune *s2, long n)
+{
+ Rune c1, c2;
+
+ while(n > 0) {
+ c1 = *s1++;
+ c2 = *s2++;
+ n--;
+ if(c1 != c2) {
+ if(c1 > c2)
+ return 1;
+ return -1;
+ }
+ if(c1 == 0)
+ break;
+ }
+ return 0;
+}
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/runestrncpy.c b/trunk/src/third_party/css_parser/src/third_party/utf/runestrncpy.c
new file mode 100644
index 0000000..95ea9dd
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/runestrncpy.c
@@ -0,0 +1,33 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "third_party/utf/utf.h"
+#include "third_party/utf/utfdef.h"
+
+Rune*
+runestrncpy(Rune *s1, const Rune *s2, long n)
+{
+ int i;
+ Rune *os1;
+
+ os1 = s1;
+ for(i = 0; i < n; i++)
+ if((*s1++ = *s2++) == 0) {
+ while(++i < n)
+ *s1++ = 0;
+ return os1;
+ }
+ return os1;
+}
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/runestrrchr.c b/trunk/src/third_party/css_parser/src/third_party/utf/runestrrchr.c
new file mode 100644
index 0000000..7349f23
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/runestrrchr.c
@@ -0,0 +1,31 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "third_party/utf/utf.h"
+#include "third_party/utf/utfdef.h"
+
+const
+Rune*
+runestrrchr(const Rune *s, Rune c)
+{
+ const Rune *r;
+
+ if(c == 0)
+ return runestrchr(s, 0);
+ r = 0;
+ while((s = runestrchr(s, c)) != 0)
+ r = s++;
+ return r;
+}
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/runestrstr.c b/trunk/src/third_party/css_parser/src/third_party/utf/runestrstr.c
new file mode 100644
index 0000000..1d53f9c
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/runestrstr.c
@@ -0,0 +1,45 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "third_party/utf/utf.h"
+#include "third_party/utf/utfdef.h"
+
+/*
+ * Return pointer to first occurrence of s2 in s1,
+ * 0 if none
+ */
+const
+Rune*
+runestrstr(const Rune *s1, const Rune *s2)
+{
+ const Rune *p, *pa, *pb;
+ int c0, c;
+
+ c0 = *s2;
+ if(c0 == 0)
+ return s1;
+ s2++;
+ for(p=runestrchr(s1, c0); p; p=runestrchr(p+1, c0)) {
+ pa = p;
+ for(pb=s2;; pb++) {
+ c = *pb;
+ if(c == 0)
+ return p;
+ if(c != *++pa)
+ break;
+ }
+ }
+ return 0;
+}
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/runetype.c b/trunk/src/third_party/css_parser/src/third_party/utf/runetype.c
new file mode 100644
index 0000000..da49d39
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/runetype.c
@@ -0,0 +1,70 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include "third_party/utf/utf.h"
+#include "third_party/utf/utfdef.h"
+
+static
+Rune*
+rbsearch(Rune c, Rune *t, int n, int ne)
+{
+ Rune *p;
+ int m;
+
+ while(n > 1) {
+ m = n >> 1;
+ p = t + m*ne;
+ if(c >= p[0]) {
+ t = p;
+ n = n-m;
+ } else
+ n = m;
+ }
+ if(n && c >= t[0])
+ return t;
+ return 0;
+}
+
+/*
+ * The "ideographic" property is hard to extract from UnicodeData.txt,
+ * so it is hard coded here.
+ *
+ * It is defined in the Unicode PropList.txt file, for example
+ * PropList-3.0.0.txt. Unlike the UnicodeData.txt file, the format of
+ * PropList changes between versions. This property appears relatively static;
+ * it is the same in version 4.0.1, except that version defines some >16 bit
+ * chars as ideographic as well: 20000..2a6d6, and 2f800..2Fa1d.
+ */
+static Rune __isideographicr[] = {
+ 0x3006, 0x3007, /* 3006 not in Unicode 2, in 2.1 */
+ 0x3021, 0x3029,
+ 0x3038, 0x303a, /* not in Unicode 2 or 2.1 */
+ 0x3400, 0x4db5, /* not in Unicode 2 or 2.1 */
+ 0x4e00, 0x9fbb, /* 0x9FA6..0x9FBB added for 4.1.0? */
+ 0xf900, 0xfa2d,
+ 0x20000, 0x2A6D6,
+ 0x2F800, 0x2FA1D,
+};
+
+int
+isideographicrune(Rune c)
+{
+ Rune *p;
+
+ p = rbsearch(c, __isideographicr, nelem(__isideographicr)/2, 2);
+ if(p && c >= p[0] && c <= p[1])
+ return 1;
+ return 0;
+}
+
+#include "third_party/utf/runetypebody-5.0.0.c"
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/runetypebody-5.0.0.c b/trunk/src/third_party/css_parser/src/third_party/utf/runetypebody-5.0.0.c
new file mode 100644
index 0000000..67a645d
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/runetypebody-5.0.0.c
@@ -0,0 +1,1361 @@
+/* generated automatically by mkrunetype.c from UnicodeData-5.0.0.txt */
+
+static Rune __isspacer[] = {
+ 0x0009, 0x000d,
+ 0x0020, 0x0020,
+ 0x0085, 0x0085,
+ 0x00a0, 0x00a0,
+ 0x1680, 0x1680,
+ 0x180e, 0x180e,
+ 0x2000, 0x200a,
+ 0x2028, 0x2029,
+ 0x202f, 0x202f,
+ 0x205f, 0x205f,
+ 0x3000, 0x3000,
+ 0xfeff, 0xfeff,
+};
+
+int
+isspacerune(Rune c)
+{
+ Rune *p;
+
+ p = rbsearch(c, __isspacer, nelem(__isspacer)/2, 2);
+ if(p && c >= p[0] && c <= p[1])
+ return 1;
+ return 0;
+}
+
+static Rune __isdigitr[] = {
+ 0x0030, 0x0039,
+ 0x0660, 0x0669,
+ 0x06f0, 0x06f9,
+ 0x07c0, 0x07c9,
+ 0x0966, 0x096f,
+ 0x09e6, 0x09ef,
+ 0x0a66, 0x0a6f,
+ 0x0ae6, 0x0aef,
+ 0x0b66, 0x0b6f,
+ 0x0be6, 0x0bef,
+ 0x0c66, 0x0c6f,
+ 0x0ce6, 0x0cef,
+ 0x0d66, 0x0d6f,
+ 0x0e50, 0x0e59,
+ 0x0ed0, 0x0ed9,
+ 0x0f20, 0x0f29,
+ 0x1040, 0x1049,
+ 0x17e0, 0x17e9,
+ 0x1810, 0x1819,
+ 0x1946, 0x194f,
+ 0x19d0, 0x19d9,
+ 0x1b50, 0x1b59,
+ 0xff10, 0xff19,
+ 0x104a0, 0x104a9,
+ 0x1d7ce, 0x1d7ff,
+};
+
+int
+isdigitrune(Rune c)
+{
+ Rune *p;
+
+ p = rbsearch(c, __isdigitr, nelem(__isdigitr)/2, 2);
+ if(p && c >= p[0] && c <= p[1])
+ return 1;
+ return 0;
+}
+
+static Rune __isalphar[] = {
+ 0x0041, 0x005a,
+ 0x0061, 0x007a,
+ 0x00c0, 0x00d6,
+ 0x00d8, 0x00f6,
+ 0x00f8, 0x02c1,
+ 0x02c6, 0x02d1,
+ 0x02e0, 0x02e4,
+ 0x037a, 0x037d,
+ 0x0388, 0x038a,
+ 0x038e, 0x03a1,
+ 0x03a3, 0x03ce,
+ 0x03d0, 0x03f5,
+ 0x03f7, 0x0481,
+ 0x048a, 0x0513,
+ 0x0531, 0x0556,
+ 0x0561, 0x0587,
+ 0x05d0, 0x05ea,
+ 0x05f0, 0x05f2,
+ 0x0621, 0x063a,
+ 0x0640, 0x064a,
+ 0x066e, 0x066f,
+ 0x0671, 0x06d3,
+ 0x06e5, 0x06e6,
+ 0x06ee, 0x06ef,
+ 0x06fa, 0x06fc,
+ 0x0712, 0x072f,
+ 0x074d, 0x076d,
+ 0x0780, 0x07a5,
+ 0x07ca, 0x07ea,
+ 0x07f4, 0x07f5,
+ 0x0904, 0x0939,
+ 0x0958, 0x0961,
+ 0x097b, 0x097f,
+ 0x0985, 0x098c,
+ 0x098f, 0x0990,
+ 0x0993, 0x09a8,
+ 0x09aa, 0x09b0,
+ 0x09b6, 0x09b9,
+ 0x09dc, 0x09dd,
+ 0x09df, 0x09e1,
+ 0x09f0, 0x09f1,
+ 0x0a05, 0x0a0a,
+ 0x0a0f, 0x0a10,
+ 0x0a13, 0x0a28,
+ 0x0a2a, 0x0a30,
+ 0x0a32, 0x0a33,
+ 0x0a35, 0x0a36,
+ 0x0a38, 0x0a39,
+ 0x0a59, 0x0a5c,
+ 0x0a72, 0x0a74,
+ 0x0a85, 0x0a8d,
+ 0x0a8f, 0x0a91,
+ 0x0a93, 0x0aa8,
+ 0x0aaa, 0x0ab0,
+ 0x0ab2, 0x0ab3,
+ 0x0ab5, 0x0ab9,
+ 0x0ae0, 0x0ae1,
+ 0x0b05, 0x0b0c,
+ 0x0b0f, 0x0b10,
+ 0x0b13, 0x0b28,
+ 0x0b2a, 0x0b30,
+ 0x0b32, 0x0b33,
+ 0x0b35, 0x0b39,
+ 0x0b5c, 0x0b5d,
+ 0x0b5f, 0x0b61,
+ 0x0b85, 0x0b8a,
+ 0x0b8e, 0x0b90,
+ 0x0b92, 0x0b95,
+ 0x0b99, 0x0b9a,
+ 0x0b9e, 0x0b9f,
+ 0x0ba3, 0x0ba4,
+ 0x0ba8, 0x0baa,
+ 0x0bae, 0x0bb9,
+ 0x0c05, 0x0c0c,
+ 0x0c0e, 0x0c10,
+ 0x0c12, 0x0c28,
+ 0x0c2a, 0x0c33,
+ 0x0c35, 0x0c39,
+ 0x0c60, 0x0c61,
+ 0x0c85, 0x0c8c,
+ 0x0c8e, 0x0c90,
+ 0x0c92, 0x0ca8,
+ 0x0caa, 0x0cb3,
+ 0x0cb5, 0x0cb9,
+ 0x0ce0, 0x0ce1,
+ 0x0d05, 0x0d0c,
+ 0x0d0e, 0x0d10,
+ 0x0d12, 0x0d28,
+ 0x0d2a, 0x0d39,
+ 0x0d60, 0x0d61,
+ 0x0d85, 0x0d96,
+ 0x0d9a, 0x0db1,
+ 0x0db3, 0x0dbb,
+ 0x0dc0, 0x0dc6,
+ 0x0e01, 0x0e30,
+ 0x0e32, 0x0e33,
+ 0x0e40, 0x0e46,
+ 0x0e81, 0x0e82,
+ 0x0e87, 0x0e88,
+ 0x0e94, 0x0e97,
+ 0x0e99, 0x0e9f,
+ 0x0ea1, 0x0ea3,
+ 0x0eaa, 0x0eab,
+ 0x0ead, 0x0eb0,
+ 0x0eb2, 0x0eb3,
+ 0x0ec0, 0x0ec4,
+ 0x0edc, 0x0edd,
+ 0x0f40, 0x0f47,
+ 0x0f49, 0x0f6a,
+ 0x0f88, 0x0f8b,
+ 0x1000, 0x1021,
+ 0x1023, 0x1027,
+ 0x1029, 0x102a,
+ 0x1050, 0x1055,
+ 0x10a0, 0x10c5,
+ 0x10d0, 0x10fa,
+ 0x1100, 0x1159,
+ 0x115f, 0x11a2,
+ 0x11a8, 0x11f9,
+ 0x1200, 0x1248,
+ 0x124a, 0x124d,
+ 0x1250, 0x1256,
+ 0x125a, 0x125d,
+ 0x1260, 0x1288,
+ 0x128a, 0x128d,
+ 0x1290, 0x12b0,
+ 0x12b2, 0x12b5,
+ 0x12b8, 0x12be,
+ 0x12c2, 0x12c5,
+ 0x12c8, 0x12d6,
+ 0x12d8, 0x1310,
+ 0x1312, 0x1315,
+ 0x1318, 0x135a,
+ 0x1380, 0x138f,
+ 0x13a0, 0x13f4,
+ 0x1401, 0x166c,
+ 0x166f, 0x1676,
+ 0x1681, 0x169a,
+ 0x16a0, 0x16ea,
+ 0x1700, 0x170c,
+ 0x170e, 0x1711,
+ 0x1720, 0x1731,
+ 0x1740, 0x1751,
+ 0x1760, 0x176c,
+ 0x176e, 0x1770,
+ 0x1780, 0x17b3,
+ 0x1820, 0x1877,
+ 0x1880, 0x18a8,
+ 0x1900, 0x191c,
+ 0x1950, 0x196d,
+ 0x1970, 0x1974,
+ 0x1980, 0x19a9,
+ 0x19c1, 0x19c7,
+ 0x1a00, 0x1a16,
+ 0x1b05, 0x1b33,
+ 0x1b45, 0x1b4b,
+ 0x1d00, 0x1dbf,
+ 0x1e00, 0x1e9b,
+ 0x1ea0, 0x1ef9,
+ 0x1f00, 0x1f15,
+ 0x1f18, 0x1f1d,
+ 0x1f20, 0x1f45,
+ 0x1f48, 0x1f4d,
+ 0x1f50, 0x1f57,
+ 0x1f5f, 0x1f7d,
+ 0x1f80, 0x1fb4,
+ 0x1fb6, 0x1fbc,
+ 0x1fc2, 0x1fc4,
+ 0x1fc6, 0x1fcc,
+ 0x1fd0, 0x1fd3,
+ 0x1fd6, 0x1fdb,
+ 0x1fe0, 0x1fec,
+ 0x1ff2, 0x1ff4,
+ 0x1ff6, 0x1ffc,
+ 0x2090, 0x2094,
+ 0x210a, 0x2113,
+ 0x2119, 0x211d,
+ 0x212a, 0x212d,
+ 0x212f, 0x2139,
+ 0x213c, 0x213f,
+ 0x2145, 0x2149,
+ 0x2183, 0x2184,
+ 0x2c00, 0x2c2e,
+ 0x2c30, 0x2c5e,
+ 0x2c60, 0x2c6c,
+ 0x2c74, 0x2c77,
+ 0x2c80, 0x2ce4,
+ 0x2d00, 0x2d25,
+ 0x2d30, 0x2d65,
+ 0x2d80, 0x2d96,
+ 0x2da0, 0x2da6,
+ 0x2da8, 0x2dae,
+ 0x2db0, 0x2db6,
+ 0x2db8, 0x2dbe,
+ 0x2dc0, 0x2dc6,
+ 0x2dc8, 0x2dce,
+ 0x2dd0, 0x2dd6,
+ 0x2dd8, 0x2dde,
+ 0x3005, 0x3006,
+ 0x3031, 0x3035,
+ 0x303b, 0x303c,
+ 0x3041, 0x3096,
+ 0x309d, 0x309f,
+ 0x30a1, 0x30fa,
+ 0x30fc, 0x30ff,
+ 0x3105, 0x312c,
+ 0x3131, 0x318e,
+ 0x31a0, 0x31b7,
+ 0x31f0, 0x31ff,
+ 0x3400, 0x4db5,
+ 0x4e00, 0x9fbb,
+ 0xa000, 0xa48c,
+ 0xa717, 0xa71a,
+ 0xa800, 0xa801,
+ 0xa803, 0xa805,
+ 0xa807, 0xa80a,
+ 0xa80c, 0xa822,
+ 0xa840, 0xa873,
+ 0xac00, 0xd7a3,
+ 0xf900, 0xfa2d,
+ 0xfa30, 0xfa6a,
+ 0xfa70, 0xfad9,
+ 0xfb00, 0xfb06,
+ 0xfb13, 0xfb17,
+ 0xfb1f, 0xfb28,
+ 0xfb2a, 0xfb36,
+ 0xfb38, 0xfb3c,
+ 0xfb40, 0xfb41,
+ 0xfb43, 0xfb44,
+ 0xfb46, 0xfbb1,
+ 0xfbd3, 0xfd3d,
+ 0xfd50, 0xfd8f,
+ 0xfd92, 0xfdc7,
+ 0xfdf0, 0xfdfb,
+ 0xfe70, 0xfe74,
+ 0xfe76, 0xfefc,
+ 0xff21, 0xff3a,
+ 0xff41, 0xff5a,
+ 0xff66, 0xffbe,
+ 0xffc2, 0xffc7,
+ 0xffca, 0xffcf,
+ 0xffd2, 0xffd7,
+ 0xffda, 0xffdc,
+ 0x10000, 0x1000b,
+ 0x1000d, 0x10026,
+ 0x10028, 0x1003a,
+ 0x1003c, 0x1003d,
+ 0x1003f, 0x1004d,
+ 0x10050, 0x1005d,
+ 0x10080, 0x100fa,
+ 0x10300, 0x1031e,
+ 0x10330, 0x10340,
+ 0x10342, 0x10349,
+ 0x10380, 0x1039d,
+ 0x103a0, 0x103c3,
+ 0x103c8, 0x103cf,
+ 0x10400, 0x1049d,
+ 0x10800, 0x10805,
+ 0x1080a, 0x10835,
+ 0x10837, 0x10838,
+ 0x10900, 0x10915,
+ 0x10a10, 0x10a13,
+ 0x10a15, 0x10a17,
+ 0x10a19, 0x10a33,
+ 0x12000, 0x1236e,
+ 0x1d400, 0x1d454,
+ 0x1d456, 0x1d49c,
+ 0x1d49e, 0x1d49f,
+ 0x1d4a5, 0x1d4a6,
+ 0x1d4a9, 0x1d4ac,
+ 0x1d4ae, 0x1d4b9,
+ 0x1d4bd, 0x1d4c3,
+ 0x1d4c5, 0x1d505,
+ 0x1d507, 0x1d50a,
+ 0x1d50d, 0x1d514,
+ 0x1d516, 0x1d51c,
+ 0x1d51e, 0x1d539,
+ 0x1d53b, 0x1d53e,
+ 0x1d540, 0x1d544,
+ 0x1d54a, 0x1d550,
+ 0x1d552, 0x1d6a5,
+ 0x1d6a8, 0x1d6c0,
+ 0x1d6c2, 0x1d6da,
+ 0x1d6dc, 0x1d6fa,
+ 0x1d6fc, 0x1d714,
+ 0x1d716, 0x1d734,
+ 0x1d736, 0x1d74e,
+ 0x1d750, 0x1d76e,
+ 0x1d770, 0x1d788,
+ 0x1d78a, 0x1d7a8,
+ 0x1d7aa, 0x1d7c2,
+ 0x1d7c4, 0x1d7cb,
+ 0x20000, 0x2a6d6,
+ 0x2f800, 0x2fa1d,
+};
+
+static Rune __isalphas[] = {
+ 0x00aa,
+ 0x00b5,
+ 0x00ba,
+ 0x02ee,
+ 0x0386,
+ 0x038c,
+ 0x0559,
+ 0x06d5,
+ 0x06ff,
+ 0x0710,
+ 0x07b1,
+ 0x07fa,
+ 0x093d,
+ 0x0950,
+ 0x09b2,
+ 0x09bd,
+ 0x09ce,
+ 0x0a5e,
+ 0x0abd,
+ 0x0ad0,
+ 0x0b3d,
+ 0x0b71,
+ 0x0b83,
+ 0x0b9c,
+ 0x0cbd,
+ 0x0cde,
+ 0x0dbd,
+ 0x0e84,
+ 0x0e8a,
+ 0x0e8d,
+ 0x0ea5,
+ 0x0ea7,
+ 0x0ebd,
+ 0x0ec6,
+ 0x0f00,
+ 0x10fc,
+ 0x1258,
+ 0x12c0,
+ 0x17d7,
+ 0x17dc,
+ 0x1f59,
+ 0x1f5b,
+ 0x1f5d,
+ 0x1fbe,
+ 0x2071,
+ 0x207f,
+ 0x2102,
+ 0x2107,
+ 0x2115,
+ 0x2124,
+ 0x2126,
+ 0x2128,
+ 0x214e,
+ 0x2d6f,
+ 0xfb1d,
+ 0xfb3e,
+ 0x10808,
+ 0x1083c,
+ 0x1083f,
+ 0x10a00,
+ 0x1d4a2,
+ 0x1d4bb,
+ 0x1d546,
+};
+
+int
+isalpharune(Rune c)
+{
+ Rune *p;
+
+ p = rbsearch(c, __isalphar, nelem(__isalphar)/2, 2);
+ if(p && c >= p[0] && c <= p[1])
+ return 1;
+ p = rbsearch(c, __isalphas, nelem(__isalphas), 1);
+ if(p && c == p[0])
+ return 1;
+ return 0;
+}
+
+static Rune __isupperr[] = {
+ 0x0041, 0x005a,
+ 0x00c0, 0x00d6,
+ 0x00d8, 0x00de,
+ 0x0178, 0x0179,
+ 0x0181, 0x0182,
+ 0x0186, 0x0187,
+ 0x0189, 0x018b,
+ 0x018e, 0x0191,
+ 0x0193, 0x0194,
+ 0x0196, 0x0198,
+ 0x019c, 0x019d,
+ 0x019f, 0x01a0,
+ 0x01a6, 0x01a7,
+ 0x01ae, 0x01af,
+ 0x01b1, 0x01b3,
+ 0x01b7, 0x01b8,
+ 0x01f6, 0x01f8,
+ 0x023a, 0x023b,
+ 0x023d, 0x023e,
+ 0x0243, 0x0246,
+ 0x0388, 0x038a,
+ 0x038e, 0x038f,
+ 0x0391, 0x03a1,
+ 0x03a3, 0x03ab,
+ 0x03d2, 0x03d4,
+ 0x03f9, 0x03fa,
+ 0x03fd, 0x042f,
+ 0x04c0, 0x04c1,
+ 0x0531, 0x0556,
+ 0x10a0, 0x10c5,
+ 0x1f08, 0x1f0f,
+ 0x1f18, 0x1f1d,
+ 0x1f28, 0x1f2f,
+ 0x1f38, 0x1f3f,
+ 0x1f48, 0x1f4d,
+ 0x1f68, 0x1f6f,
+ 0x1f88, 0x1f8f,
+ 0x1f98, 0x1f9f,
+ 0x1fa8, 0x1faf,
+ 0x1fb8, 0x1fbc,
+ 0x1fc8, 0x1fcc,
+ 0x1fd8, 0x1fdb,
+ 0x1fe8, 0x1fec,
+ 0x1ff8, 0x1ffc,
+ 0x210b, 0x210d,
+ 0x2110, 0x2112,
+ 0x2119, 0x211d,
+ 0x212a, 0x212d,
+ 0x2130, 0x2133,
+ 0x213e, 0x213f,
+ 0x2160, 0x216f,
+ 0x24b6, 0x24cf,
+ 0x2c00, 0x2c2e,
+ 0x2c62, 0x2c64,
+ 0xff21, 0xff3a,
+ 0x10400, 0x10427,
+ 0x1d400, 0x1d419,
+ 0x1d434, 0x1d44d,
+ 0x1d468, 0x1d481,
+ 0x1d49e, 0x1d49f,
+ 0x1d4a5, 0x1d4a6,
+ 0x1d4a9, 0x1d4ac,
+ 0x1d4ae, 0x1d4b5,
+ 0x1d4d0, 0x1d4e9,
+ 0x1d504, 0x1d505,
+ 0x1d507, 0x1d50a,
+ 0x1d50d, 0x1d514,
+ 0x1d516, 0x1d51c,
+ 0x1d538, 0x1d539,
+ 0x1d53b, 0x1d53e,
+ 0x1d540, 0x1d544,
+ 0x1d54a, 0x1d550,
+ 0x1d56c, 0x1d585,
+ 0x1d5a0, 0x1d5b9,
+ 0x1d5d4, 0x1d5ed,
+ 0x1d608, 0x1d621,
+ 0x1d63c, 0x1d655,
+ 0x1d670, 0x1d689,
+ 0x1d6a8, 0x1d6c0,
+ 0x1d6e2, 0x1d6fa,
+ 0x1d71c, 0x1d734,
+ 0x1d756, 0x1d76e,
+ 0x1d790, 0x1d7a8,
+};
+
+static Rune __isupperp[] = {
+ 0x0100, 0x0136,
+ 0x0139, 0x0147,
+ 0x014a, 0x0176,
+ 0x017b, 0x017d,
+ 0x01a2, 0x01a4,
+ 0x01cd, 0x01db,
+ 0x01de, 0x01ee,
+ 0x01fa, 0x0232,
+ 0x0248, 0x024e,
+ 0x03d8, 0x03ee,
+ 0x0460, 0x0480,
+ 0x048a, 0x04be,
+ 0x04c3, 0x04cd,
+ 0x04d0, 0x0512,
+ 0x1e00, 0x1e94,
+ 0x1ea0, 0x1ef8,
+ 0x1f59, 0x1f5f,
+ 0x2124, 0x2128,
+ 0x2c67, 0x2c6b,
+ 0x2c80, 0x2ce2,
+};
+
+static Rune __isuppers[] = {
+ 0x0184,
+ 0x01a9,
+ 0x01ac,
+ 0x01b5,
+ 0x01bc,
+ 0x01c4,
+ 0x01c7,
+ 0x01ca,
+ 0x01f1,
+ 0x01f4,
+ 0x0241,
+ 0x0386,
+ 0x038c,
+ 0x03f4,
+ 0x03f7,
+ 0x2102,
+ 0x2107,
+ 0x2115,
+ 0x2145,
+ 0x2183,
+ 0x2c60,
+ 0x2c75,
+ 0x1d49c,
+ 0x1d4a2,
+ 0x1d546,
+ 0x1d7ca,
+};
+
+int
+isupperrune(Rune c)
+{
+ Rune *p;
+
+ p = rbsearch(c, __isupperr, nelem(__isupperr)/2, 2);
+ if(p && c >= p[0] && c <= p[1])
+ return 1;
+ p = rbsearch(c, __isupperp, nelem(__isupperp)/2, 2);
+ if(p && c >= p[0] && c <= p[1] && !((c - p[0]) & 1))
+ return 1;
+ p = rbsearch(c, __isuppers, nelem(__isuppers), 1);
+ if(p && c == p[0])
+ return 1;
+ return 0;
+}
+
+static Rune __islowerr[] = {
+ 0x0061, 0x007a,
+ 0x00df, 0x00f6,
+ 0x00f8, 0x00ff,
+ 0x0137, 0x0138,
+ 0x0148, 0x0149,
+ 0x017e, 0x0180,
+ 0x018c, 0x018d,
+ 0x0199, 0x019b,
+ 0x01aa, 0x01ab,
+ 0x01b9, 0x01ba,
+ 0x01bd, 0x01bf,
+ 0x01dc, 0x01dd,
+ 0x01ef, 0x01f0,
+ 0x0233, 0x0239,
+ 0x023f, 0x0240,
+ 0x024f, 0x0293,
+ 0x0295, 0x02af,
+ 0x037b, 0x037d,
+ 0x03ac, 0x03ce,
+ 0x03d0, 0x03d1,
+ 0x03d5, 0x03d7,
+ 0x03ef, 0x03f3,
+ 0x03fb, 0x03fc,
+ 0x0430, 0x045f,
+ 0x04ce, 0x04cf,
+ 0x0561, 0x0587,
+ 0x1d00, 0x1d2b,
+ 0x1d62, 0x1d77,
+ 0x1d79, 0x1d9a,
+ 0x1e95, 0x1e9b,
+ 0x1f00, 0x1f07,
+ 0x1f10, 0x1f15,
+ 0x1f20, 0x1f27,
+ 0x1f30, 0x1f37,
+ 0x1f40, 0x1f45,
+ 0x1f50, 0x1f57,
+ 0x1f60, 0x1f67,
+ 0x1f70, 0x1f7d,
+ 0x1f80, 0x1f87,
+ 0x1f90, 0x1f97,
+ 0x1fa0, 0x1fa7,
+ 0x1fb0, 0x1fb4,
+ 0x1fb6, 0x1fb7,
+ 0x1fc2, 0x1fc4,
+ 0x1fc6, 0x1fc7,
+ 0x1fd0, 0x1fd3,
+ 0x1fd6, 0x1fd7,
+ 0x1fe0, 0x1fe7,
+ 0x1ff2, 0x1ff4,
+ 0x1ff6, 0x1ff7,
+ 0x210e, 0x210f,
+ 0x213c, 0x213d,
+ 0x2146, 0x2149,
+ 0x2170, 0x217f,
+ 0x24d0, 0x24e9,
+ 0x2c30, 0x2c5e,
+ 0x2c65, 0x2c66,
+ 0x2c76, 0x2c77,
+ 0x2ce3, 0x2ce4,
+ 0x2d00, 0x2d25,
+ 0xfb00, 0xfb06,
+ 0xfb13, 0xfb17,
+ 0xff41, 0xff5a,
+ 0x10428, 0x1044f,
+ 0x1d41a, 0x1d433,
+ 0x1d44e, 0x1d454,
+ 0x1d456, 0x1d467,
+ 0x1d482, 0x1d49b,
+ 0x1d4b6, 0x1d4b9,
+ 0x1d4bd, 0x1d4c3,
+ 0x1d4c5, 0x1d4cf,
+ 0x1d4ea, 0x1d503,
+ 0x1d51e, 0x1d537,
+ 0x1d552, 0x1d56b,
+ 0x1d586, 0x1d59f,
+ 0x1d5ba, 0x1d5d3,
+ 0x1d5ee, 0x1d607,
+ 0x1d622, 0x1d63b,
+ 0x1d656, 0x1d66f,
+ 0x1d68a, 0x1d6a5,
+ 0x1d6c2, 0x1d6da,
+ 0x1d6dc, 0x1d6e1,
+ 0x1d6fc, 0x1d714,
+ 0x1d716, 0x1d71b,
+ 0x1d736, 0x1d74e,
+ 0x1d750, 0x1d755,
+ 0x1d770, 0x1d788,
+ 0x1d78a, 0x1d78f,
+ 0x1d7aa, 0x1d7c2,
+ 0x1d7c4, 0x1d7c9,
+};
+
+static Rune __islowerp[] = {
+ 0x0101, 0x0135,
+ 0x013a, 0x0146,
+ 0x014b, 0x0177,
+ 0x017a, 0x017c,
+ 0x0183, 0x0185,
+ 0x01a1, 0x01a5,
+ 0x01b4, 0x01b6,
+ 0x01cc, 0x01da,
+ 0x01df, 0x01ed,
+ 0x01f3, 0x01f5,
+ 0x01f9, 0x0231,
+ 0x0247, 0x024d,
+ 0x03d9, 0x03ed,
+ 0x0461, 0x0481,
+ 0x048b, 0x04bf,
+ 0x04c2, 0x04cc,
+ 0x04d1, 0x0513,
+ 0x1e01, 0x1e93,
+ 0x1ea1, 0x1ef9,
+ 0x2c68, 0x2c6c,
+ 0x2c81, 0x2ce1,
+};
+
+static Rune __islowers[] = {
+ 0x00aa,
+ 0x00b5,
+ 0x00ba,
+ 0x0188,
+ 0x0192,
+ 0x0195,
+ 0x019e,
+ 0x01a8,
+ 0x01ad,
+ 0x01b0,
+ 0x01c6,
+ 0x01c9,
+ 0x023c,
+ 0x0242,
+ 0x0390,
+ 0x03f5,
+ 0x03f8,
+ 0x1fbe,
+ 0x2071,
+ 0x207f,
+ 0x210a,
+ 0x2113,
+ 0x212f,
+ 0x2134,
+ 0x2139,
+ 0x214e,
+ 0x2184,
+ 0x2c61,
+ 0x2c74,
+ 0x1d4bb,
+ 0x1d7cb,
+};
+
+int
+islowerrune(Rune c)
+{
+ Rune *p;
+
+ p = rbsearch(c, __islowerr, nelem(__islowerr)/2, 2);
+ if(p && c >= p[0] && c <= p[1])
+ return 1;
+ p = rbsearch(c, __islowerp, nelem(__islowerp)/2, 2);
+ if(p && c >= p[0] && c <= p[1] && !((c - p[0]) & 1))
+ return 1;
+ p = rbsearch(c, __islowers, nelem(__islowers), 1);
+ if(p && c == p[0])
+ return 1;
+ return 0;
+}
+
+static Rune __istitler[] = {
+ 0x0041, 0x005a,
+ 0x00c0, 0x00d6,
+ 0x00d8, 0x00de,
+ 0x0178, 0x0179,
+ 0x0181, 0x0182,
+ 0x0186, 0x0187,
+ 0x0189, 0x018b,
+ 0x018e, 0x0191,
+ 0x0193, 0x0194,
+ 0x0196, 0x0198,
+ 0x019c, 0x019d,
+ 0x019f, 0x01a0,
+ 0x01a6, 0x01a7,
+ 0x01ae, 0x01af,
+ 0x01b1, 0x01b3,
+ 0x01b7, 0x01b8,
+ 0x01f6, 0x01f8,
+ 0x023a, 0x023b,
+ 0x023d, 0x023e,
+ 0x0243, 0x0246,
+ 0x0388, 0x038a,
+ 0x038e, 0x038f,
+ 0x0391, 0x03a1,
+ 0x03a3, 0x03ab,
+ 0x03f9, 0x03fa,
+ 0x03fd, 0x042f,
+ 0x04c0, 0x04c1,
+ 0x0531, 0x0556,
+ 0x10a0, 0x10c5,
+ 0x1f08, 0x1f0f,
+ 0x1f18, 0x1f1d,
+ 0x1f28, 0x1f2f,
+ 0x1f38, 0x1f3f,
+ 0x1f48, 0x1f4d,
+ 0x1f68, 0x1f6f,
+ 0x1f88, 0x1f8f,
+ 0x1f98, 0x1f9f,
+ 0x1fa8, 0x1faf,
+ 0x1fb8, 0x1fbc,
+ 0x1fc8, 0x1fcc,
+ 0x1fd8, 0x1fdb,
+ 0x1fe8, 0x1fec,
+ 0x1ff8, 0x1ffc,
+ 0x2160, 0x216f,
+ 0x24b6, 0x24cf,
+ 0x2c00, 0x2c2e,
+ 0x2c62, 0x2c64,
+ 0xff21, 0xff3a,
+ 0x10400, 0x10427,
+};
+
+static Rune __istitlep[] = {
+ 0x0100, 0x012e,
+ 0x0132, 0x0136,
+ 0x0139, 0x0147,
+ 0x014a, 0x0176,
+ 0x017b, 0x017d,
+ 0x01a2, 0x01a4,
+ 0x01cb, 0x01db,
+ 0x01de, 0x01ee,
+ 0x01f2, 0x01f4,
+ 0x01fa, 0x0232,
+ 0x0248, 0x024e,
+ 0x03d8, 0x03ee,
+ 0x0460, 0x0480,
+ 0x048a, 0x04be,
+ 0x04c3, 0x04cd,
+ 0x04d0, 0x0512,
+ 0x1e00, 0x1e94,
+ 0x1ea0, 0x1ef8,
+ 0x1f59, 0x1f5f,
+ 0x2c67, 0x2c6b,
+ 0x2c80, 0x2ce2,
+};
+
+static Rune __istitles[] = {
+ 0x0184,
+ 0x01a9,
+ 0x01ac,
+ 0x01b5,
+ 0x01bc,
+ 0x01c5,
+ 0x01c8,
+ 0x0241,
+ 0x0386,
+ 0x038c,
+ 0x03f7,
+ 0x2132,
+ 0x2183,
+ 0x2c60,
+ 0x2c75,
+};
+
+int
+istitlerune(Rune c)
+{
+ Rune *p;
+
+ p = rbsearch(c, __istitler, nelem(__istitler)/2, 2);
+ if(p && c >= p[0] && c <= p[1])
+ return 1;
+ p = rbsearch(c, __istitlep, nelem(__istitlep)/2, 2);
+ if(p && c >= p[0] && c <= p[1] && !((c - p[0]) & 1))
+ return 1;
+ p = rbsearch(c, __istitles, nelem(__istitles), 1);
+ if(p && c == p[0])
+ return 1;
+ return 0;
+}
+
+static Rune __toupperr[] = {
+ 0x0061, 0x007a, 1048544,
+ 0x00e0, 0x00f6, 1048544,
+ 0x00f8, 0x00fe, 1048544,
+ 0x0256, 0x0257, 1048371,
+ 0x028a, 0x028b, 1048359,
+ 0x037b, 0x037d, 1048706,
+ 0x03ad, 0x03af, 1048539,
+ 0x03b1, 0x03c1, 1048544,
+ 0x03c3, 0x03cb, 1048544,
+ 0x03cd, 0x03ce, 1048513,
+ 0x0430, 0x044f, 1048544,
+ 0x0450, 0x045f, 1048496,
+ 0x0561, 0x0586, 1048528,
+ 0x1f00, 0x1f07, 1048584,
+ 0x1f10, 0x1f15, 1048584,
+ 0x1f20, 0x1f27, 1048584,
+ 0x1f30, 0x1f37, 1048584,
+ 0x1f40, 0x1f45, 1048584,
+ 0x1f60, 0x1f67, 1048584,
+ 0x1f70, 0x1f71, 1048650,
+ 0x1f72, 0x1f75, 1048662,
+ 0x1f76, 0x1f77, 1048676,
+ 0x1f78, 0x1f79, 1048704,
+ 0x1f7a, 0x1f7b, 1048688,
+ 0x1f7c, 0x1f7d, 1048702,
+ 0x1f80, 0x1f87, 1048584,
+ 0x1f90, 0x1f97, 1048584,
+ 0x1fa0, 0x1fa7, 1048584,
+ 0x1fb0, 0x1fb1, 1048584,
+ 0x1fd0, 0x1fd1, 1048584,
+ 0x1fe0, 0x1fe1, 1048584,
+ 0x2170, 0x217f, 1048560,
+ 0x24d0, 0x24e9, 1048550,
+ 0x2c30, 0x2c5e, 1048528,
+ 0x2d00, 0x2d25, 1041312,
+ 0xff41, 0xff5a, 1048544,
+ 0x10428, 0x1044f, 1048536,
+};
+
+static Rune __toupperp[] = {
+ 0x0101, 0x012f, 1048575,
+ 0x0133, 0x0137, 1048575,
+ 0x013a, 0x0148, 1048575,
+ 0x014b, 0x0177, 1048575,
+ 0x017a, 0x017e, 1048575,
+ 0x0183, 0x0185, 1048575,
+ 0x01a1, 0x01a5, 1048575,
+ 0x01b4, 0x01b6, 1048575,
+ 0x01ce, 0x01dc, 1048575,
+ 0x01df, 0x01ef, 1048575,
+ 0x01f9, 0x021f, 1048575,
+ 0x0223, 0x0233, 1048575,
+ 0x0247, 0x024f, 1048575,
+ 0x03d9, 0x03ef, 1048575,
+ 0x0461, 0x0481, 1048575,
+ 0x048b, 0x04bf, 1048575,
+ 0x04c2, 0x04ce, 1048575,
+ 0x04d1, 0x0513, 1048575,
+ 0x1e01, 0x1e95, 1048575,
+ 0x1ea1, 0x1ef9, 1048575,
+ 0x1f51, 0x1f57, 1048584,
+ 0x2c68, 0x2c6c, 1048575,
+ 0x2c81, 0x2ce3, 1048575,
+};
+
+static Rune __touppers[] = {
+ 0x00b5, 1049319,
+ 0x00ff, 1048697,
+ 0x0131, 1048344,
+ 0x017f, 1048276,
+ 0x0180, 1048771,
+ 0x0188, 1048575,
+ 0x018c, 1048575,
+ 0x0192, 1048575,
+ 0x0195, 1048673,
+ 0x0199, 1048575,
+ 0x019a, 1048739,
+ 0x019e, 1048706,
+ 0x01a8, 1048575,
+ 0x01ad, 1048575,
+ 0x01b0, 1048575,
+ 0x01b9, 1048575,
+ 0x01bd, 1048575,
+ 0x01bf, 1048632,
+ 0x01c5, 1048575,
+ 0x01c6, 1048574,
+ 0x01c8, 1048575,
+ 0x01c9, 1048574,
+ 0x01cb, 1048575,
+ 0x01cc, 1048574,
+ 0x01dd, 1048497,
+ 0x01f2, 1048575,
+ 0x01f3, 1048574,
+ 0x01f5, 1048575,
+ 0x023c, 1048575,
+ 0x0242, 1048575,
+ 0x0253, 1048366,
+ 0x0254, 1048370,
+ 0x0259, 1048374,
+ 0x025b, 1048373,
+ 0x0260, 1048371,
+ 0x0263, 1048369,
+ 0x0268, 1048367,
+ 0x0269, 1048365,
+ 0x026b, 1059319,
+ 0x026f, 1048365,
+ 0x0272, 1048363,
+ 0x0275, 1048362,
+ 0x027d, 1059303,
+ 0x0280, 1048358,
+ 0x0283, 1048358,
+ 0x0288, 1048358,
+ 0x0289, 1048507,
+ 0x028c, 1048505,
+ 0x0292, 1048357,
+ 0x0345, 1048660,
+ 0x03ac, 1048538,
+ 0x03c2, 1048545,
+ 0x03cc, 1048512,
+ 0x03d0, 1048514,
+ 0x03d1, 1048519,
+ 0x03d5, 1048529,
+ 0x03d6, 1048522,
+ 0x03f0, 1048490,
+ 0x03f1, 1048496,
+ 0x03f2, 1048583,
+ 0x03f5, 1048480,
+ 0x03f8, 1048575,
+ 0x03fb, 1048575,
+ 0x04cf, 1048561,
+ 0x1d7d, 1052390,
+ 0x1e9b, 1048517,
+ 0x1fb3, 1048585,
+ 0x1fbe, 1041371,
+ 0x1fc3, 1048585,
+ 0x1fe5, 1048583,
+ 0x1ff3, 1048585,
+ 0x214e, 1048548,
+ 0x2184, 1048575,
+ 0x2c61, 1048575,
+ 0x2c65, 1037781,
+ 0x2c66, 1037784,
+ 0x2c76, 1048575,
+};
+
+Rune
+toupperrune(Rune c)
+{
+ Rune *p;
+
+ p = rbsearch(c, __toupperr, nelem(__toupperr)/3, 3);
+ if(p && c >= p[0] && c <= p[1])
+ return c + p[2] - 1048576;
+ p = rbsearch(c, __toupperp, nelem(__toupperp)/3, 3);
+ if(p && c >= p[0] && c <= p[1] && !((c - p[0]) & 1))
+ return c + p[2] - 1048576;
+ p = rbsearch(c, __touppers, nelem(__touppers)/2, 2);
+ if(p && c == p[0])
+ return c + p[1] - 1048576;
+ return c;
+}
+
+static Rune __tolowerr[] = {
+ 0x0041, 0x005a, 1048608,
+ 0x00c0, 0x00d6, 1048608,
+ 0x00d8, 0x00de, 1048608,
+ 0x0189, 0x018a, 1048781,
+ 0x01b1, 0x01b2, 1048793,
+ 0x0388, 0x038a, 1048613,
+ 0x038e, 0x038f, 1048639,
+ 0x0391, 0x03a1, 1048608,
+ 0x03a3, 0x03ab, 1048608,
+ 0x03fd, 0x03ff, 1048446,
+ 0x0400, 0x040f, 1048656,
+ 0x0410, 0x042f, 1048608,
+ 0x0531, 0x0556, 1048624,
+ 0x10a0, 0x10c5, 1055840,
+ 0x1f08, 0x1f0f, 1048568,
+ 0x1f18, 0x1f1d, 1048568,
+ 0x1f28, 0x1f2f, 1048568,
+ 0x1f38, 0x1f3f, 1048568,
+ 0x1f48, 0x1f4d, 1048568,
+ 0x1f68, 0x1f6f, 1048568,
+ 0x1f88, 0x1f8f, 1048568,
+ 0x1f98, 0x1f9f, 1048568,
+ 0x1fa8, 0x1faf, 1048568,
+ 0x1fb8, 0x1fb9, 1048568,
+ 0x1fba, 0x1fbb, 1048502,
+ 0x1fc8, 0x1fcb, 1048490,
+ 0x1fd8, 0x1fd9, 1048568,
+ 0x1fda, 0x1fdb, 1048476,
+ 0x1fe8, 0x1fe9, 1048568,
+ 0x1fea, 0x1feb, 1048464,
+ 0x1ff8, 0x1ff9, 1048448,
+ 0x1ffa, 0x1ffb, 1048450,
+ 0x2160, 0x216f, 1048592,
+ 0x24b6, 0x24cf, 1048602,
+ 0x2c00, 0x2c2e, 1048624,
+ 0xff21, 0xff3a, 1048608,
+ 0x10400, 0x10427, 1048616,
+};
+
+static Rune __tolowerp[] = {
+ 0x0100, 0x012e, 1048577,
+ 0x0132, 0x0136, 1048577,
+ 0x0139, 0x0147, 1048577,
+ 0x014a, 0x0176, 1048577,
+ 0x017b, 0x017d, 1048577,
+ 0x01a2, 0x01a4, 1048577,
+ 0x01b3, 0x01b5, 1048577,
+ 0x01cd, 0x01db, 1048577,
+ 0x01de, 0x01ee, 1048577,
+ 0x01f8, 0x021e, 1048577,
+ 0x0222, 0x0232, 1048577,
+ 0x0248, 0x024e, 1048577,
+ 0x03d8, 0x03ee, 1048577,
+ 0x0460, 0x0480, 1048577,
+ 0x048a, 0x04be, 1048577,
+ 0x04c3, 0x04cd, 1048577,
+ 0x04d0, 0x0512, 1048577,
+ 0x1e00, 0x1e94, 1048577,
+ 0x1ea0, 0x1ef8, 1048577,
+ 0x1f59, 0x1f5f, 1048568,
+ 0x2c67, 0x2c6b, 1048577,
+ 0x2c80, 0x2ce2, 1048577,
+};
+
+static Rune __tolowers[] = {
+ 0x0130, 1048377,
+ 0x0178, 1048455,
+ 0x0179, 1048577,
+ 0x0181, 1048786,
+ 0x0182, 1048577,
+ 0x0184, 1048577,
+ 0x0186, 1048782,
+ 0x0187, 1048577,
+ 0x018b, 1048577,
+ 0x018e, 1048655,
+ 0x018f, 1048778,
+ 0x0190, 1048779,
+ 0x0191, 1048577,
+ 0x0193, 1048781,
+ 0x0194, 1048783,
+ 0x0196, 1048787,
+ 0x0197, 1048785,
+ 0x0198, 1048577,
+ 0x019c, 1048787,
+ 0x019d, 1048789,
+ 0x019f, 1048790,
+ 0x01a0, 1048577,
+ 0x01a6, 1048794,
+ 0x01a7, 1048577,
+ 0x01a9, 1048794,
+ 0x01ac, 1048577,
+ 0x01ae, 1048794,
+ 0x01af, 1048577,
+ 0x01b7, 1048795,
+ 0x01b8, 1048577,
+ 0x01bc, 1048577,
+ 0x01c4, 1048578,
+ 0x01c5, 1048577,
+ 0x01c7, 1048578,
+ 0x01c8, 1048577,
+ 0x01ca, 1048578,
+ 0x01cb, 1048577,
+ 0x01f1, 1048578,
+ 0x01f2, 1048577,
+ 0x01f4, 1048577,
+ 0x01f6, 1048479,
+ 0x01f7, 1048520,
+ 0x0220, 1048446,
+ 0x023a, 1059371,
+ 0x023b, 1048577,
+ 0x023d, 1048413,
+ 0x023e, 1059368,
+ 0x0241, 1048577,
+ 0x0243, 1048381,
+ 0x0244, 1048645,
+ 0x0245, 1048647,
+ 0x0246, 1048577,
+ 0x0386, 1048614,
+ 0x038c, 1048640,
+ 0x03f4, 1048516,
+ 0x03f7, 1048577,
+ 0x03f9, 1048569,
+ 0x03fa, 1048577,
+ 0x04c0, 1048591,
+ 0x04c1, 1048577,
+ 0x1fbc, 1048567,
+ 0x1fcc, 1048567,
+ 0x1fec, 1048569,
+ 0x1ffc, 1048567,
+ 0x2126, 1041059,
+ 0x212a, 1040193,
+ 0x212b, 1040314,
+ 0x2132, 1048604,
+ 0x2183, 1048577,
+ 0x2c60, 1048577,
+ 0x2c62, 1037833,
+ 0x2c63, 1044762,
+ 0x2c64, 1037849,
+ 0x2c75, 1048577,
+};
+
+Rune
+tolowerrune(Rune c)
+{
+ Rune *p;
+
+ p = rbsearch(c, __tolowerr, nelem(__tolowerr)/3, 3);
+ if(p && c >= p[0] && c <= p[1])
+ return c + p[2] - 1048576;
+ p = rbsearch(c, __tolowerp, nelem(__tolowerp)/3, 3);
+ if(p && c >= p[0] && c <= p[1] && !((c - p[0]) & 1))
+ return c + p[2] - 1048576;
+ p = rbsearch(c, __tolowers, nelem(__tolowers)/2, 2);
+ if(p && c == p[0])
+ return c + p[1] - 1048576;
+ return c;
+}
+
+static Rune __totitler[] = {
+ 0x0061, 0x007a, 1048544,
+ 0x00e0, 0x00f6, 1048544,
+ 0x00f8, 0x00fe, 1048544,
+ 0x0256, 0x0257, 1048371,
+ 0x028a, 0x028b, 1048359,
+ 0x037b, 0x037d, 1048706,
+ 0x03ad, 0x03af, 1048539,
+ 0x03b1, 0x03c1, 1048544,
+ 0x03c3, 0x03cb, 1048544,
+ 0x03cd, 0x03ce, 1048513,
+ 0x0430, 0x044f, 1048544,
+ 0x0450, 0x045f, 1048496,
+ 0x0561, 0x0586, 1048528,
+ 0x1f00, 0x1f07, 1048584,
+ 0x1f10, 0x1f15, 1048584,
+ 0x1f20, 0x1f27, 1048584,
+ 0x1f30, 0x1f37, 1048584,
+ 0x1f40, 0x1f45, 1048584,
+ 0x1f60, 0x1f67, 1048584,
+ 0x1f70, 0x1f71, 1048650,
+ 0x1f72, 0x1f75, 1048662,
+ 0x1f76, 0x1f77, 1048676,
+ 0x1f78, 0x1f79, 1048704,
+ 0x1f7a, 0x1f7b, 1048688,
+ 0x1f7c, 0x1f7d, 1048702,
+ 0x1f80, 0x1f87, 1048584,
+ 0x1f90, 0x1f97, 1048584,
+ 0x1fa0, 0x1fa7, 1048584,
+ 0x1fb0, 0x1fb1, 1048584,
+ 0x1fd0, 0x1fd1, 1048584,
+ 0x1fe0, 0x1fe1, 1048584,
+ 0x2170, 0x217f, 1048560,
+ 0x24d0, 0x24e9, 1048550,
+ 0x2c30, 0x2c5e, 1048528,
+ 0x2d00, 0x2d25, 1041312,
+ 0xff41, 0xff5a, 1048544,
+ 0x10428, 0x1044f, 1048536,
+};
+
+static Rune __totitlep[] = {
+ 0x0101, 0x012f, 1048575,
+ 0x0133, 0x0137, 1048575,
+ 0x013a, 0x0148, 1048575,
+ 0x014b, 0x0177, 1048575,
+ 0x017a, 0x017e, 1048575,
+ 0x0183, 0x0185, 1048575,
+ 0x01a1, 0x01a5, 1048575,
+ 0x01b4, 0x01b6, 1048575,
+ 0x01cc, 0x01dc, 1048575,
+ 0x01df, 0x01ef, 1048575,
+ 0x01f3, 0x01f5, 1048575,
+ 0x01f9, 0x021f, 1048575,
+ 0x0223, 0x0233, 1048575,
+ 0x0247, 0x024f, 1048575,
+ 0x03d9, 0x03ef, 1048575,
+ 0x0461, 0x0481, 1048575,
+ 0x048b, 0x04bf, 1048575,
+ 0x04c2, 0x04ce, 1048575,
+ 0x04d1, 0x0513, 1048575,
+ 0x1e01, 0x1e95, 1048575,
+ 0x1ea1, 0x1ef9, 1048575,
+ 0x1f51, 0x1f57, 1048584,
+ 0x2c68, 0x2c6c, 1048575,
+ 0x2c81, 0x2ce3, 1048575,
+};
+
+static Rune __totitles[] = {
+ 0x00b5, 1049319,
+ 0x00ff, 1048697,
+ 0x0131, 1048344,
+ 0x017f, 1048276,
+ 0x0180, 1048771,
+ 0x0188, 1048575,
+ 0x018c, 1048575,
+ 0x0192, 1048575,
+ 0x0195, 1048673,
+ 0x0199, 1048575,
+ 0x019a, 1048739,
+ 0x019e, 1048706,
+ 0x01a8, 1048575,
+ 0x01ad, 1048575,
+ 0x01b0, 1048575,
+ 0x01b9, 1048575,
+ 0x01bd, 1048575,
+ 0x01bf, 1048632,
+ 0x01c4, 1048577,
+ 0x01c6, 1048575,
+ 0x01c7, 1048577,
+ 0x01c9, 1048575,
+ 0x01ca, 1048577,
+ 0x01dd, 1048497,
+ 0x01f1, 1048577,
+ 0x023c, 1048575,
+ 0x0242, 1048575,
+ 0x0253, 1048366,
+ 0x0254, 1048370,
+ 0x0259, 1048374,
+ 0x025b, 1048373,
+ 0x0260, 1048371,
+ 0x0263, 1048369,
+ 0x0268, 1048367,
+ 0x0269, 1048365,
+ 0x026b, 1059319,
+ 0x026f, 1048365,
+ 0x0272, 1048363,
+ 0x0275, 1048362,
+ 0x027d, 1059303,
+ 0x0280, 1048358,
+ 0x0283, 1048358,
+ 0x0288, 1048358,
+ 0x0289, 1048507,
+ 0x028c, 1048505,
+ 0x0292, 1048357,
+ 0x0345, 1048660,
+ 0x03ac, 1048538,
+ 0x03c2, 1048545,
+ 0x03cc, 1048512,
+ 0x03d0, 1048514,
+ 0x03d1, 1048519,
+ 0x03d5, 1048529,
+ 0x03d6, 1048522,
+ 0x03f0, 1048490,
+ 0x03f1, 1048496,
+ 0x03f2, 1048583,
+ 0x03f5, 1048480,
+ 0x03f8, 1048575,
+ 0x03fb, 1048575,
+ 0x04cf, 1048561,
+ 0x1d7d, 1052390,
+ 0x1e9b, 1048517,
+ 0x1fb3, 1048585,
+ 0x1fbe, 1041371,
+ 0x1fc3, 1048585,
+ 0x1fe5, 1048583,
+ 0x1ff3, 1048585,
+ 0x214e, 1048548,
+ 0x2184, 1048575,
+ 0x2c61, 1048575,
+ 0x2c65, 1037781,
+ 0x2c66, 1037784,
+ 0x2c76, 1048575,
+};
+
+Rune
+totitlerune(Rune c)
+{
+ Rune *p;
+
+ p = rbsearch(c, __totitler, nelem(__totitler)/3, 3);
+ if(p && c >= p[0] && c <= p[1])
+ return c + p[2] - 1048576;
+ p = rbsearch(c, __totitlep, nelem(__totitlep)/3, 3);
+ if(p && c >= p[0] && c <= p[1] && !((c - p[0]) & 1))
+ return c + p[2] - 1048576;
+ p = rbsearch(c, __totitles, nelem(__totitles)/2, 2);
+ if(p && c == p[0])
+ return c + p[1] - 1048576;
+ return c;
+}
+
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/utf.h b/trunk/src/third_party/css_parser/src/third_party/utf/utf.h
new file mode 100644
index 0000000..02ba472
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/utf.h
@@ -0,0 +1,233 @@
+#ifndef _UTFH_
+#define _UTFH_ 1
+
+#include <stdint.h>
+
+typedef signed int Rune; /* Code-point values in Unicode 4.0 are 21 bits wide.*/
+
+enum
+{
+ UTFmax = 4, /* maximum bytes per rune */
+ Runesync = 0x80, /* cannot represent part of a UTF sequence (<) */
+ Runeself = 0x80, /* rune and UTF sequences are the same (<) */
+ Runeerror = 0xFFFD, /* decoding error in UTF */
+ Runemax = 0x10FFFF, /* maximum rune value */
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * rune routines
+ */
+
+/*
+ * These routines were written by Rob Pike and Ken Thompson
+ * and first appeared in Plan 9.
+ * SEE ALSO
+ * utf (7)
+ * tcs (1)
+*/
+
+// runetochar copies (encodes) one rune, pointed to by r, to at most
+// UTFmax bytes starting at s and returns the number of bytes generated.
+
+int runetochar(char* s, const Rune* r);
+
+
+// chartorune copies (decodes) at most UTFmax bytes starting at s to
+// one rune, pointed to by r, and returns the number of bytes consumed.
+// If the input is not exactly in UTF format, chartorune will set *r
+// to Runeerror and return 1.
+//
+// Note: There is no special case for a "null-terminated" string. A
+// string whose first byte has the value 0 is the UTF8 encoding of the
+// Unicode value 0 (i.e., ASCII NULL). A byte value of 0 is illegal
+// anywhere else in a UTF sequence.
+
+int chartorune(Rune* r, const char* s);
+
+
+// charntorune is like chartorune, except that it will access at most
+// n bytes of s. If the UTF sequence is incomplete within n bytes,
+// charntorune will set *r to Runeerror and return 0. If it is complete
+// but not in UTF format, it will set *r to Runeerror and return 1.
+//
+// Added 2004-09-24 by Wei-Hwa Huang
+
+int charntorune(Rune* r, const char* s, int n);
+
+// isvalidcharntorune(str, n, r, consumed)
+// is a convenience function that calls "*consumed = charntorune(r, str, n)"
+// and returns an int (logically boolean) indicating whether the first
+// n bytes of str was a valid and complete UTF sequence.
+
+int isvalidcharntorune(const char* str, int n, Rune* r, int* consumed);
+
+// runelen returns the number of bytes required to convert r into UTF.
+
+int runelen(Rune r);
+
+
+// runenlen returns the number of bytes required to convert the n
+// runes pointed to by r into UTF.
+
+int runenlen(const Rune* r, int n);
+
+
+// fullrune returns 1 if the string s of length n is long enough to be
+// decoded by chartorune, and 0 otherwise. This does not guarantee
+// that the string contains a legal UTF encoding. This routine is used
+// by programs that obtain input one byte at a time and need to know
+// when a full rune has arrived.
+
+int fullrune(const char* s, int n);
+
+// The following routines are analogous to the corresponding string
+// routines with "utf" substituted for "str", and "rune" substituted
+// for "chr".
+
+// utflen returns the number of runes that are represented by the UTF
+// string s. (cf. strlen)
+
+int utflen(const char* s);
+
+
+// utfnlen returns the number of complete runes that are represented
+// by the first n bytes of the UTF string s. If the last few bytes of
+// the string contain an incompletely coded rune, utfnlen will not
+// count them; in this way, it differs from utflen, which includes
+// every byte of the string. (cf. strnlen)
+
+int utfnlen(const char* s, long n);
+
+
+// utfrune returns a pointer to the first occurrence of rune r in the
+// UTF string s, or 0 if r does not occur in the string. The NULL
+// byte terminating a string is considered to be part of the string s.
+// (cf. strchr)
+
+const char* utfrune(const char* s, Rune r);
+
+
+// utfrrune returns a pointer to the last occurrence of rune r in the
+// UTF string s, or 0 if r does not occur in the string. The NULL
+// byte terminating a string is considered to be part of the string s.
+// (cf. strrchr)
+
+const char* utfrrune(const char* s, Rune r);
+
+
+// utfutf returns a pointer to the first occurrence of the UTF string
+// s2 as a UTF substring of s1, or 0 if there is none. If s2 is the
+// null string, utfutf returns s1. (cf. strstr)
+
+const char* utfutf(const char* s1, const char* s2);
+
+
+// utfecpy copies UTF sequences until a null sequence has been copied,
+// but writes no sequences beyond es1. If any sequences are copied,
+// s1 is terminated by a null sequence, and a pointer to that sequence
+// is returned. Otherwise, the original s1 is returned. (cf. strecpy)
+
+char* utfecpy(char *s1, char *es1, const char *s2);
+
+
+
+// These functions are rune-string analogues of the corresponding
+// functions in strcat (3).
+//
+// These routines first appeared in Plan 9.
+// SEE ALSO
+// memmove (3)
+// rune (3)
+// strcat (2)
+//
+// BUGS: The outcome of overlapping moves varies among implementations.
+
+Rune* runestrcat(Rune* s1, const Rune* s2);
+Rune* runestrncat(Rune* s1, const Rune* s2, long n);
+
+const Rune* runestrchr(const Rune* s, Rune c);
+
+int runestrcmp(const Rune* s1, const Rune* s2);
+int runestrncmp(const Rune* s1, const Rune* s2, long n);
+
+Rune* runestrcpy(Rune* s1, const Rune* s2);
+Rune* runestrncpy(Rune* s1, const Rune* s2, long n);
+Rune* runestrecpy(Rune* s1, Rune* es1, const Rune* s2);
+
+Rune* runestrdup(const Rune* s);
+
+const Rune* runestrrchr(const Rune* s, Rune c);
+long runestrlen(const Rune* s);
+const Rune* runestrstr(const Rune* s1, const Rune* s2);
+
+
+
+// The following routines test types and modify cases for Unicode
+// characters. Unicode defines some characters as letters and
+// specifies three cases: upper, lower, and title. Mappings among the
+// cases are also defined, although they are not exhaustive: some
+// upper case letters have no lower case mapping, and so on. Unicode
+// also defines several character properties, a subset of which are
+// checked by these routines. These routines are based on Unicode
+// version 3.0.0.
+//
+// NOTE: The routines are implemented in C, so the boolean functions
+// (e.g., isupperrune) return 0 for false and 1 for true.
+//
+//
+// toupperrune, tolowerrune, and totitlerune are the Unicode case
+// mappings. These routines return the character unchanged if it has
+// no defined mapping.
+
+Rune toupperrune(Rune r);
+Rune tolowerrune(Rune r);
+Rune totitlerune(Rune r);
+
+
+// isupperrune tests for upper case characters, including Unicode
+// upper case letters and targets of the toupper mapping. islowerrune
+// and istitlerune are defined analogously.
+
+int isupperrune(Rune r);
+int islowerrune(Rune r);
+int istitlerune(Rune r);
+
+
+// isalpharune tests for Unicode letters; this includes ideographs in
+// addition to alphabetic characters.
+
+int isalpharune(Rune r);
+
+
+// isdigitrune tests for digits. Non-digit numbers, such as Roman
+// numerals, are not included.
+
+int isdigitrune(Rune r);
+
+
+// isideographicrune tests for ideographic characters and numbers, as
+// defined by the Unicode standard.
+
+int isideographicrune(Rune r);
+
+
+// isspacerune tests for whitespace characters, including "C" locale
+// whitespace, Unicode defined whitespace, and the "zero-width
+// non-break space" character.
+
+int isspacerune(Rune r);
+
+
+// (The comments in this file were copied from the manpage files rune.3,
+// isalpharune.3, and runestrcat.3. Some formatting changes were also made
+// to conform to Google style. /JRM 11/11/05)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/utfdef.h b/trunk/src/third_party/css_parser/src/third_party/utf/utfdef.h
new file mode 100644
index 0000000..4b58ae8
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/utfdef.h
@@ -0,0 +1,14 @@
+#define uchar _utfuchar
+#define ushort _utfushort
+#define uint _utfuint
+#define ulong _utfulong
+#define vlong _utfvlong
+#define uvlong _utfuvlong
+
+typedef unsigned char uchar;
+typedef unsigned short ushort;
+typedef unsigned int uint;
+typedef unsigned long ulong;
+
+#define nelem(x) (sizeof(x)/sizeof((x)[0]))
+#define nil ((void*)0)
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/utfecpy.c b/trunk/src/third_party/css_parser/src/third_party/utf/utfecpy.c
new file mode 100644
index 0000000..5cf19af
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/utfecpy.c
@@ -0,0 +1,36 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "third_party/utf/utf.h"
+#include "third_party/utf/utfdef.h"
+
+char*
+utfecpy(char *to, char *e, const char *from)
+{
+ char *end;
+
+ if(to >= e)
+ return to;
+ end = (char*)memccpy(to, from, '\0', e - to);
+ if(end == nil){
+ end = e-1;
+ while(end>to && (*--end&0xC0)==0x80)
+ ;
+ *end = '\0';
+ }else{
+ end--;
+ }
+ return end;
+}
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/utflen.c b/trunk/src/third_party/css_parser/src/third_party/utf/utflen.c
new file mode 100644
index 0000000..5a23e0a
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/utflen.c
@@ -0,0 +1,38 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "third_party/utf/utf.h"
+#include "third_party/utf/utfdef.h"
+
+int
+utflen(const char *s)
+{
+ int c;
+ long n;
+ Rune rune;
+
+ n = 0;
+ for(;;) {
+ c = *(uchar*)s;
+ if(c < Runeself) {
+ if(c == 0)
+ return n;
+ s++;
+ } else
+ s += chartorune(&rune, s);
+ n++;
+ }
+ return 0;
+}
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/utfnlen.c b/trunk/src/third_party/css_parser/src/third_party/utf/utfnlen.c
new file mode 100644
index 0000000..1f191fd
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/utfnlen.c
@@ -0,0 +1,41 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "third_party/utf/utf.h"
+#include "third_party/utf/utfdef.h"
+
+int
+utfnlen(const char *s, long m)
+{
+ int c;
+ long n;
+ Rune rune;
+ const char *es;
+
+ es = s + m;
+ for(n = 0; s < es; n++) {
+ c = *(uchar*)s;
+ if(c < Runeself){
+ if(c == '\0')
+ break;
+ s++;
+ continue;
+ }
+ if(!fullrune(s, es-s))
+ break;
+ s += chartorune(&rune, s);
+ }
+ return n;
+}
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/utfrrune.c b/trunk/src/third_party/css_parser/src/third_party/utf/utfrrune.c
new file mode 100644
index 0000000..bd3351e
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/utfrrune.c
@@ -0,0 +1,47 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "third_party/utf/utf.h"
+#include "third_party/utf/utfdef.h"
+
+const
+char*
+utfrrune(const char *s, Rune c)
+{
+ long c1;
+ Rune r;
+ const char *s1;
+
+ if(c < Runesync) /* not part of utf sequence */
+ return strrchr(s, c);
+
+ s1 = 0;
+ for(;;) {
+ c1 = *(uchar*)s;
+ if(c1 < Runeself) { /* one byte rune */
+ if(c1 == 0)
+ return s1;
+ if(c1 == c)
+ s1 = s;
+ s++;
+ continue;
+ }
+ c1 = chartorune(&r, s);
+ if(r == c)
+ s1 = s;
+ s += c1;
+ }
+ return 0;
+}
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/utfrune.c b/trunk/src/third_party/css_parser/src/third_party/utf/utfrune.c
new file mode 100644
index 0000000..2d8fa18
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/utfrune.c
@@ -0,0 +1,46 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "third_party/utf/utf.h"
+#include "third_party/utf/utfdef.h"
+
+const
+char*
+utfrune(const char *s, Rune c)
+{
+ long c1;
+ Rune r;
+ int n;
+
+ if(c < Runesync) /* not part of utf sequence */
+ return strchr(s, c);
+
+ for(;;) {
+ c1 = *(uchar*)s;
+ if(c1 < Runeself) { /* one byte rune */
+ if(c1 == 0)
+ return 0;
+ if(c1 == c)
+ return s;
+ s++;
+ continue;
+ }
+ n = chartorune(&r, s);
+ if(r == c)
+ return s;
+ s += n;
+ }
+ return 0;
+}
diff --git a/trunk/src/third_party/css_parser/src/third_party/utf/utfutf.c b/trunk/src/third_party/css_parser/src/third_party/utf/utfutf.c
new file mode 100644
index 0000000..df242d1
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/third_party/utf/utfutf.c
@@ -0,0 +1,42 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "third_party/utf/utf.h"
+#include "third_party/utf/utfdef.h"
+
+
+/*
+ * Return pointer to first occurrence of s2 in s1,
+ * 0 if none
+ */
+const
+char*
+utfutf(const char *s1, const char *s2)
+{
+ const char *p;
+ long f, n1, n2;
+ Rune r;
+
+ n1 = chartorune(&r, s2);
+ f = r;
+ if(f <= Runesync) /* represents self */
+ return strstr(s1, s2);
+
+ n2 = strlen(s2);
+ for(p=s1; (p=utfrune(p, f)) != 0; p+=n1)
+ if(strncmp(p, s2, n2) == 0)
+ return p;
+ return 0;
+}
diff --git a/trunk/src/third_party/css_parser/src/util/gtl/dense_hash_map.h b/trunk/src/third_party/css_parser/src/util/gtl/dense_hash_map.h
new file mode 100644
index 0000000..7de44ea
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/util/gtl/dense_hash_map.h
@@ -0,0 +1,24 @@
+/**
+ * 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.
+ */
+#ifndef UTIL_GTL_DENSE_HASH_MAP_H_
+#define UTIL_GTL_DENSE_HASH_MAP_H_
+
+//#include "third_party/google-sparsehash/src/google/dense_hash_map"
+#include <google/dense_hash_map>
+
+using google::dense_hash_map;
+
+#endif // UTIL_GTL_DENSE_HASH_MAP_H_
diff --git a/trunk/src/third_party/css_parser/src/util/gtl/map-util.h b/trunk/src/third_party/css_parser/src/util/gtl/map-util.h
new file mode 100644
index 0000000..6bc8344
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/util/gtl/map-util.h
@@ -0,0 +1,22 @@
+/**
+ * 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.
+ */
+#ifndef UTIL_GTL_MAP_UTIL_H_
+#define UTIL_GTL_MAP_UTIL_H_
+
+//#include "third_party/chromium/src/base/stl_util-inl.h"
+#include "base/stl_util-inl.h"
+
+#endif // UTIL_GTL_MAP_UTIL_H_
diff --git a/trunk/src/third_party/css_parser/src/util/gtl/singleton.h b/trunk/src/third_party/css_parser/src/util/gtl/singleton.h
new file mode 100644
index 0000000..ff03672
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/util/gtl/singleton.h
@@ -0,0 +1,22 @@
+/**
+ * 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.
+ */
+#ifndef UTIL_GTL_SINGLETON_H_
+#define UTIL_GTL_SINGLETON_H_
+
+//#include "third_party/chromium/src/base/singleton.h"
+#include "base/singleton.h"
+
+#endif // UTIL_GTL_SINGLETON_H_
diff --git a/trunk/src/third_party/css_parser/src/util/gtl/stl_util-inl.h b/trunk/src/third_party/css_parser/src/util/gtl/stl_util-inl.h
new file mode 100644
index 0000000..d3f1326
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/util/gtl/stl_util-inl.h
@@ -0,0 +1,22 @@
+/**
+ * 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.
+ */
+#ifndef UTIL_GTL_STL_UTIL_INL_H_
+#define UTIL_GTL_STL_UTIL_INL_H_
+
+//#include "third_party/chromium/src/base/stl_util-inl.h"
+#include "base/stl_util-inl.h"
+
+#endif // UTIL_GTL_STL_UTIL_INL_H_
diff --git a/trunk/src/third_party/css_parser/src/util/hash/hash.h b/trunk/src/third_party/css_parser/src/util/hash/hash.h
new file mode 100644
index 0000000..d09072c
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/util/hash/hash.h
@@ -0,0 +1,15 @@
+/**
+ * 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.
+ */
diff --git a/trunk/src/third_party/css_parser/src/util/utf8/internal/unicodetext.cc b/trunk/src/third_party/css_parser/src/util/utf8/internal/unicodetext.cc
new file mode 100644
index 0000000..c1a885b
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/util/utf8/internal/unicodetext.cc
@@ -0,0 +1,487 @@
+/**
+ * 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.
+ */
+
+// Copyright 2006 Google Inc. All Rights Reserved.
+// Author: jrm@google.com (Jim Meehan)
+
+
+#include "util/utf8/public/unicodetext.h"
+
+#include "base/logging.h"
+#include "strings/stringpiece.h"
+#include "strings/stringprintf.h"
+#include "third_party/utf/utf.h"
+#include "util/utf8/public/unilib.h"
+
+static int CodepointDistance(const char* start, const char* end) {
+ int n = 0;
+ // Increment n on every non-trail-byte.
+ for (const char* p = start; p < end; ++p) {
+ n += (*reinterpret_cast<const signed char*>(p) >= -0x40);
+ }
+ return n;
+}
+
+static int CodepointCount(const char* utf8, int len) {
+ return CodepointDistance(utf8, utf8 + len);
+}
+
+UnicodeText::const_iterator::difference_type
+distance(const UnicodeText::const_iterator& first,
+ const UnicodeText::const_iterator& last) {
+ return CodepointDistance(first.it_, last.it_);
+}
+
+// ---------- Utility ----------
+
+static int ConvertToInterchangeValid(char* start, int len) {
+ // This routine is called only when we've discovered that a UTF-8 buffer
+ // that was passed to CopyUTF8, TakeOwnershipOfUTF8, or PointToUTF8
+ // was not interchange valid. This indicates a bug in the caller, and
+ // a LOG(WARNING) is done in that case.
+ // This is similar to CoerceToInterchangeValid, but it replaces each
+ // structurally valid byte with a space, and each non-interchange
+ // character with a space, even when that character requires more
+ // than one byte in UTF8. E.g., "\xEF\xB7\x90" (U+FDD0) is
+ // structurally valid UTF8, but U+FDD0 is not an interchange-valid
+ // code point. The result should contain one space, not three.
+ //
+ // Since the conversion never needs to write more data than it
+ // reads, it is safe to change the buffer in place. It returns the
+ // number of bytes written.
+ char* const in = start;
+ char* out = start;
+ char* const end = start + len;
+ while (start < end) {
+ int good = UniLib::SpanInterchangeValid(start, end - start);
+ if (good > 0) {
+ if (out != start) {
+ memmove(out, start, good);
+ }
+ out += good;
+ start += good;
+ if (start == end) {
+ break;
+ }
+ }
+ // Is the current string invalid UTF8 or just non-interchange UTF8?
+ char32 rune;
+ int n;
+ if (isvalidcharntorune(start, end - start, &rune, &n)) {
+ // structurally valid UTF8, but not interchange valid
+ start += n; // Skip over the whole character.
+ } else { // bad UTF8
+ start += 1; // Skip over just one byte
+ }
+ *out++ = ' ';
+ }
+ return out - in;
+}
+
+
+// *************** Data representation **********
+
+// Note: the copy constructor is undefined.
+
+// After reserve(), resize(), or clear(), we're an owner, not an alias.
+
+void UnicodeText::Repr::reserve(int new_capacity) {
+ // If there's already enough capacity, and we're an owner, do nothing.
+ if (capacity_ >= new_capacity && ours_) return;
+
+ // Otherwise, allocate a new buffer.
+ capacity_ = max(new_capacity, (3 * capacity_) / 2 + 20);
+ char* new_data = new char[capacity_];
+
+ // If there is an old buffer, copy it into the new buffer.
+ if (data_) {
+ memcpy(new_data, data_, size_);
+ if (ours_) delete[] data_; // If we owned the old buffer, free it.
+ }
+ data_ = new_data;
+ ours_ = true; // We own the new buffer.
+ // size_ is unchanged.
+}
+
+void UnicodeText::Repr::resize(int new_size) {
+ if (new_size == 0) {
+ clear();
+ } else {
+ if (!ours_ || new_size > capacity_) reserve(new_size);
+ // Clear the memory in the expanded part.
+ if (size_ < new_size) memset(data_ + size_, 0, new_size - size_);
+ size_ = new_size;
+ ours_ = true;
+ }
+}
+
+// This implementation of clear() deallocates the buffer if we're an owner.
+// That's not strictly necessary; we could just set size_ to 0.
+void UnicodeText::Repr::clear() {
+ if (ours_) delete[] data_;
+ data_ = NULL;
+ size_ = capacity_ = 0;
+ ours_ = true;
+}
+
+void UnicodeText::Repr::Copy(const char* data, int size) {
+ resize(size);
+ memcpy(data_, data, size);
+}
+
+void UnicodeText::Repr::TakeOwnershipOf(char* data, int size, int capacity) {
+ if (data == data_) return; // We already own this memory. (Weird case.)
+ if (ours_ && data_) delete[] data_; // If we owned the old buffer, free it.
+ data_ = data;
+ size_ = size;
+ capacity_ = capacity;
+ ours_ = true;
+}
+
+void UnicodeText::Repr::PointTo(const char* data, int size) {
+ if (ours_ && data_) delete[] data_; // If we owned the old buffer, free it.
+ data_ = const_cast<char*>(data);
+ size_ = size;
+ capacity_ = size;
+ ours_ = false;
+}
+
+void UnicodeText::Repr::append(const char* bytes, int byte_length) {
+ reserve(size_ + byte_length);
+ memcpy(data_ + size_, bytes, byte_length);
+ size_ += byte_length;
+}
+
+string UnicodeText::Repr::DebugString() const {
+ return StringPrintf("{Repr %p data=%p size=%d capacity=%d %s}",
+ this,
+ data_, size_, capacity_,
+ ours_ ? "Owned" : "Alias");
+}
+
+
+
+// *************** UnicodeText ******************
+
+// ----- Constructors -----
+
+// Default constructor
+UnicodeText::UnicodeText() {
+}
+
+// Copy constructor
+UnicodeText::UnicodeText(const UnicodeText& src) {
+ Copy(src);
+}
+
+// Substring constructor
+UnicodeText::UnicodeText(const UnicodeText::const_iterator& first,
+ const UnicodeText::const_iterator& last) {
+ CHECK(first <= last) << " Incompatible iterators";
+ repr_.append(first.it_, last.it_ - first.it_);
+}
+
+string UnicodeText::UTF8Substring(const const_iterator& first,
+ const const_iterator& last) {
+ CHECK(first <= last) << " Incompatible iterators";
+ return string(first.it_, last.it_ - first.it_);
+}
+
+
+// ----- Copy -----
+
+UnicodeText& UnicodeText::operator=(const UnicodeText& src) {
+ if (this != &src) {
+ Copy(src);
+ }
+ return *this;
+}
+
+UnicodeText& UnicodeText::Copy(const UnicodeText& src) {
+ repr_.Copy(src.repr_.data_, src.repr_.size_);
+ return *this;
+}
+
+UnicodeText& UnicodeText::CopyUTF8(const char* buffer, int byte_length) {
+ repr_.Copy(buffer, byte_length);
+ if (!UniLib:: IsInterchangeValid(buffer, byte_length)) {
+ LOG(WARNING) << "UTF-8 buffer is not interchange-valid.";
+ repr_.size_ = ConvertToInterchangeValid(repr_.data_, byte_length);
+ }
+ return *this;
+}
+
+UnicodeText& UnicodeText::UnsafeCopyUTF8(const char* buffer,
+ int byte_length) {
+ repr_.Copy(buffer, byte_length);
+ return *this;
+}
+
+// ----- TakeOwnershipOf -----
+
+UnicodeText& UnicodeText::TakeOwnershipOfUTF8(char* buffer,
+ int byte_length,
+ int byte_capacity) {
+ repr_.TakeOwnershipOf(buffer, byte_length, byte_capacity);
+ if (!UniLib:: IsInterchangeValid(buffer, byte_length)) {
+ LOG(WARNING) << "UTF-8 buffer is not interchange-valid.";
+ repr_.size_ = ConvertToInterchangeValid(repr_.data_, byte_length);
+ }
+ return *this;
+}
+
+UnicodeText& UnicodeText::UnsafeTakeOwnershipOfUTF8(char* buffer,
+ int byte_length,
+ int byte_capacity) {
+ repr_.TakeOwnershipOf(buffer, byte_length, byte_capacity);
+ return *this;
+}
+
+// ----- PointTo -----
+
+UnicodeText& UnicodeText::PointToUTF8(const char* buffer, int byte_length) {
+ if (UniLib:: IsInterchangeValid(buffer, byte_length)) {
+ repr_.PointTo(buffer, byte_length);
+ } else {
+ LOG(WARNING) << "UTF-8 buffer is not interchange-valid.";
+ repr_.Copy(buffer, byte_length);
+ repr_.size_ = ConvertToInterchangeValid(repr_.data_, byte_length);
+ }
+ return *this;
+}
+
+UnicodeText& UnicodeText::UnsafePointToUTF8(const char* buffer,
+ int byte_length) {
+ repr_.PointTo(buffer, byte_length);
+ return *this;
+}
+
+UnicodeText& UnicodeText::PointTo(const UnicodeText& src) {
+ repr_.PointTo(src.repr_.data_, src.repr_.size_);
+ return *this;
+}
+
+UnicodeText& UnicodeText::PointTo(const const_iterator &first,
+ const const_iterator &last) {
+ CHECK(first <= last) << " Incompatible iterators";
+ repr_.PointTo(first.utf8_data(), last.utf8_data() - first.utf8_data());
+ return *this;
+}
+
+// ----- Append -----
+
+UnicodeText& UnicodeText::append(const UnicodeText& u) {
+ repr_.append(u.repr_.data_, u.repr_.size_);
+ return *this;
+}
+
+UnicodeText& UnicodeText::append(const const_iterator& first,
+ const const_iterator& last) {
+ CHECK(first <= last) << " Incompatible iterators";
+ repr_.append(first.it_, last.it_ - first.it_);
+ return *this;
+}
+
+UnicodeText& UnicodeText::UnsafeAppendUTF8(const char* utf8, int len) {
+ repr_.append(utf8, len);
+ return *this;
+}
+
+// ----- substring searching -----
+
+UnicodeText::const_iterator UnicodeText::find(const UnicodeText& look,
+ const_iterator start_pos) const {
+ CHECK_GE(start_pos.utf8_data(), utf8_data());
+ CHECK_LE(start_pos.utf8_data(), utf8_data() + utf8_length());
+ return UnsafeFind(look, start_pos);
+}
+
+UnicodeText::const_iterator UnicodeText::find(const UnicodeText& look) const {
+ return UnsafeFind(look, begin());
+}
+
+UnicodeText::const_iterator UnicodeText::UnsafeFind(
+ const UnicodeText& look, const_iterator start_pos) const {
+ // Due to the magic of the UTF8 encoding, searching for a sequence of
+ // letters is equivalent to substring search.
+ StringPiece searching(utf8_data(), utf8_length());
+ StringPiece look_piece(look.utf8_data(), look.utf8_length());
+ StringPiece::size_type found =
+ searching.find(look_piece, start_pos.utf8_data() - utf8_data());
+ if (found == StringPiece::npos) return end();
+ return const_iterator(utf8_data() + found);
+}
+
+bool UnicodeText::HasReplacementChar() const {
+ // Equivalent to:
+ // UnicodeText replacement_char;
+ // replacement_char.push_back(0xFFFD);
+ // return find(replacement_char) != end();
+ StringPiece searching(utf8_data(), utf8_length());
+ StringPiece looking_for("\xEF\xBF\xBD", 3);
+ return searching.find(looking_for) != StringPiece::npos;
+}
+
+// ----- other methods -----
+
+// Clear operator
+void UnicodeText::clear() {
+ repr_.clear();
+}
+
+// Destructor
+UnicodeText::~UnicodeText() {}
+
+
+void UnicodeText::push_back(char32 c) {
+ if (UniLib::IsValidCodepoint(c)) {
+ char buf[UTFmax];
+ int len = runetochar(buf, &c);
+ if (UniLib::IsInterchangeValid(buf, len)) {
+ repr_.append(buf, len);
+ } else {
+ LOG(WARNING) << "Unicode value 0x" << hex << c
+ << " is not valid for interchange";
+ repr_.append(" ", 1);
+ }
+ } else {
+ LOG(WARNING) << "Illegal Unicode value: 0x" << hex << c;
+ repr_.append(" ", 1);
+ }
+}
+
+int UnicodeText::size() const {
+ return CodepointCount(repr_.data_, repr_.size_);
+}
+
+bool operator==(const UnicodeText& lhs, const UnicodeText& rhs) {
+ if (&lhs == &rhs) return true;
+ if (lhs.repr_.size_ != rhs.repr_.size_) return false;
+ return memcmp(lhs.repr_.data_, rhs.repr_.data_, lhs.repr_.size_) == 0;
+}
+
+string UnicodeText::DebugString() const {
+ return StringPrintf("{UnicodeText %p chars=%d repr=%s}",
+ this,
+ size(),
+ repr_.DebugString().c_str());
+}
+
+
+// ******************* UnicodeText::const_iterator *********************
+
+// The implementation of const_iterator would be nicer if it
+// inherited from boost::iterator_facade
+// (http://boost.org/libs/iterator/doc/iterator_facade.html).
+
+UnicodeText::const_iterator::const_iterator() : it_(0) {}
+
+UnicodeText::const_iterator::const_iterator(const const_iterator& other)
+ : it_(other.it_) {
+}
+
+UnicodeText::const_iterator&
+UnicodeText::const_iterator::operator=(const const_iterator& other) {
+ if (&other != this)
+ it_ = other.it_;
+ return *this;
+}
+
+UnicodeText::const_iterator UnicodeText::begin() const {
+ return const_iterator(repr_.data_);
+}
+
+UnicodeText::const_iterator UnicodeText::end() const {
+ return const_iterator(repr_.data_ + repr_.size_);
+}
+
+bool operator<(const UnicodeText::const_iterator& lhs,
+ const UnicodeText::const_iterator& rhs) {
+ return lhs.it_ < rhs.it_;
+}
+
+char32 UnicodeText::const_iterator::operator*() const {
+ // (We could call chartorune here, but that does some
+ // error-checking, and we're guaranteed that our data is valid
+ // UTF-8. Also, we expect this routine to be called very often. So
+ // for speed, we do the calculation ourselves.)
+
+ // Convert from UTF-8
+ int byte1 = it_[0];
+ if (byte1 < 0x80)
+ return byte1;
+
+ int byte2 = it_[1];
+ if (byte1 < 0xE0)
+ return ((byte1 & 0x1F) << 6)
+ | (byte2 & 0x3F);
+
+ int byte3 = it_[2];
+ if (byte1 < 0xF0)
+ return ((byte1 & 0x0F) << 12)
+ | ((byte2 & 0x3F) << 6)
+ | (byte3 & 0x3F);
+
+ int byte4 = it_[3];
+ return ((byte1 & 0x07) << 18)
+ | ((byte2 & 0x3F) << 12)
+ | ((byte3 & 0x3F) << 6)
+ | (byte4 & 0x3F);
+}
+
+UnicodeText::const_iterator& UnicodeText::const_iterator::operator++() {
+ it_ += UniLib::OneCharLen(it_);
+ return *this;
+}
+
+UnicodeText::const_iterator& UnicodeText::const_iterator::operator--() {
+ while (UniLib::IsTrailByte(*--it_));
+ return *this;
+}
+
+int UnicodeText::const_iterator::get_utf8(char* utf8_output) const {
+ utf8_output[0] = it_[0]; if (it_[0] < 0x80) return 1;
+ utf8_output[1] = it_[1]; if (it_[0] < 0xE0) return 2;
+ utf8_output[2] = it_[2]; if (it_[0] < 0xF0) return 3;
+ utf8_output[3] = it_[3];
+ return 4;
+}
+
+
+UnicodeText::const_iterator UnicodeText::MakeIterator(const char* p) const {
+ CHECK(p != NULL);
+ const char* start = utf8_data();
+ int len = utf8_length();
+ const char* end = start + len;
+ CHECK(p >= start);
+ CHECK(p <= end);
+ CHECK(p == end || !UniLib::IsTrailByte(*p));
+ return const_iterator(p);
+}
+
+string UnicodeText::const_iterator::DebugString() const {
+ return StringPrintf("{iter %p}", it_);
+}
+
+
+// *************************** Utilities *************************
+
+string CodepointString(const UnicodeText& t) {
+ string s;
+ UnicodeText::const_iterator it = t.begin(), end = t.end();
+ while (it != end) StringAppendF(&s, "%X ", *it++);
+ return s;
+}
diff --git a/trunk/src/third_party/css_parser/src/util/utf8/internal/unilib.cc b/trunk/src/third_party/css_parser/src/util/utf8/internal/unilib.cc
new file mode 100644
index 0000000..5efa47b
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/util/utf8/internal/unilib.cc
@@ -0,0 +1,64 @@
+/**
+ * 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 "util/utf8/public/unilib.h"
+
+#include "base/basictypes.h"
+#include "third_party/utf/utf.h"
+
+namespace UniLib {
+
+namespace {
+
+// MOE: start_strip
+// MOE: end_strip
+// Codepoints not allowed for interchange are:
+// C0 (ASCII) controls: U+0000 to U+001F excluding Space (SP, U+0020),
+// Horizontal Tab (HT, U+0009), Line-Feed (LF, U+000A),
+// Form Feed (FF, U+000C) and Carriage-Return (CR, U+000D)
+// C1 controls: U+007F to U+009F
+// Surrogates: U+D800 to U+DFFF
+// Non-characters: U+FDD0 to U+FDEF and U+xxFFFE to U+xxFFFF for all xx
+inline bool IsInterchangeValidCodepoint(char32 c) {
+ return !((c >= 0x00 && c <= 0x08) || c == 0x0B || (c >= 0x0E && c <= 0x1F) ||
+ (c >= 0x7F && c <= 0x9F) ||
+ (c >= 0xD800 && c <= 0xDFFF) ||
+ (c >= 0xFDD0 && c <= 0xFDEF) || (c&0xFFFE) == 0xFFFE);
+}
+
+} // namespace
+
+int SpanInterchangeValid(const char* begin, int byte_length) {
+ char32 rune;
+ const char* p = begin;
+ const char* end = begin + byte_length;
+ while (p < end) {
+ int bytes_consumed = charntorune(&rune, p, end - p);
+ // We want to accept Runeerror == U+FFFD as a valid char, but it is used
+ // by chartorune to indicate error. Luckily, the real codepoint is size 3
+ // while errors return bytes_consumed == 1.
+ if ((rune == Runeerror && bytes_consumed == 1) ||
+ !IsInterchangeValidCodepoint(rune)) {
+ break; // Found
+ }
+ p += bytes_consumed;
+ }
+ return p - begin;
+}
+
+} // namespace UniLib
diff --git a/trunk/src/third_party/css_parser/src/util/utf8/public/config.h b/trunk/src/third_party/css_parser/src/util/utf8/public/config.h
new file mode 100644
index 0000000..9fb78b4
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/util/utf8/public/config.h
@@ -0,0 +1,24 @@
+/**
+ * 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)
+
+#ifndef UTIL_UTF8_PUBLIC_CONFIG_H_
+#define UTIL_UTF8_PUBLIC_CONFIG_H_
+
+using namespace std;
+
+#endif // UTIL_UTF8_PUBLIC_CONFIG_H_
diff --git a/trunk/src/third_party/css_parser/src/util/utf8/public/unicodetext.h b/trunk/src/third_party/css_parser/src/util/utf8/public/unicodetext.h
new file mode 100644
index 0000000..0741365
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/util/utf8/public/unicodetext.h
@@ -0,0 +1,462 @@
+/**
+ * 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.
+ */
+
+// Copyright 2006 Google Inc. All Rights Reserved.
+// Author: jrm@google.com (Jim Meehan)
+
+
+#ifndef UTIL_UTF8_UNICODETEXT_H__
+#define UTIL_UTF8_UNICODETEXT_H__
+
+#include <iterator>
+#include <string>
+#include <utility>
+#include "base/basictypes.h"
+#include "util/utf8/public/config.h"
+
+// ***************************** UnicodeText **************************
+//
+// A UnicodeText object is a container for a sequence of Unicode
+// codepoint values. It has default, copy, and assignment constructors.
+// Data can be appended to it from another UnicodeText, from
+// iterators, or from a single codepoint.
+//
+// The internal representation of the text is UTF-8. Since UTF-8 is a
+// variable-width format, UnicodeText does not provide random access
+// to the text, and changes to the text are permitted only at the end.
+//
+// The UnicodeText class defines a const_iterator. The dereferencing
+// operator (*) returns a codepoint (char32). The iterator is a
+// bidirectional, read-only iterator. It becomes invalid if the text
+// is changed.
+//
+// There are methods for appending and retrieving UTF-8 data directly.
+// The 'utf8_data' method returns a const char* that contains the
+// UTF-8-encoded version of the text; 'utf8_length' returns the number
+// of bytes in the UTF-8 data. An iterator's 'get' method stores up to
+// 4 bytes of UTF-8 data in a char array and returns the number of
+// bytes that it stored.
+//
+// Codepoints are integers in the range [0, 0xD7FF] or [0xE000,
+// 0x10FFFF], but UnicodeText has the additional restriction that it
+// can contain only those characters that are valid for interchange on
+// the Web. This excludes all of the control codes except for carriage
+// return, line feed, and horizontal tab. It also excludes
+// non-characters, but codepoints that are in the Private Use regions
+// are allowed, as are codepoints that are unassigned. (See the
+// Unicode reference for details.) The function UniLib::IsInterchangeValid
+// can be used as a test for this property.
+//
+// UnicodeTexts are safe. Every method that constructs or modifies a
+// UnicodeText tests for interchange-validity, and will substitute a
+// space for the invalid data. Such cases are reported via
+// LOG(WARNING).
+//
+// MEMORY MANAGEMENT: copy, take ownership, or point to
+//
+// A UnicodeText is either an "owner", meaning that it owns the memory
+// for the data buffer and will free it when the UnicodeText is
+// destroyed, or it is an "alias", meaning that it does not.
+//
+// There are three methods for storing UTF-8 data in a UnicodeText:
+//
+// CopyUTF8(buffer, len) copies buffer.
+//
+// TakeOwnershipOfUTF8(buffer, size, capacity) takes ownership of buffer.
+//
+// PointToUTF8(buffer, size) creates an alias pointing to buffer.
+//
+// All three methods perform a validity check on the buffer. There are
+// private, "unsafe" versions of these functions that bypass the
+// validity check. They are used internally and by friend-functions
+// that are handling UTF-8 data that has already been validated.
+//
+// The purpose of an alias is to avoid making an unnecessary copy of a
+// UTF-8 buffer while still providing access to the Unicode values
+// within that text through iterators or the fast scanners that are
+// based on UTF-8 state tables. The lifetime of an alias must not
+// exceed the lifetime of the buffer from which it was constructed.
+//
+// The semantics of an alias might be described as "copy on write or
+// repair." The source data is never modified. If push_back() or
+// append() is called on an alias, a copy of the data will be created,
+// and the UnicodeText will become an owner. If clear() is called on
+// an alias, it becomes an (empty) owner.
+//
+// The copy constructor and the assignment operator produce an owner.
+// That is, after direct initialization ("UnicodeText x(y);") or copy
+// initialization ("UnicodeText x = y;") x will be an owner, even if y
+// was an alias. The assignment operator ("x = y;") also produces an
+// owner unless x and y are the same object and y is an alias.
+//
+// Aliases should be used with care. If the source from which an alias
+// was created is freed, or if the contents are changed, while the
+// alias is still in use, fatal errors could result. But it can be
+// quite useful to have a UnicodeText "window" through which to see a
+// UTF-8 buffer without having to pay the price of making a copy.
+//
+// UTILITIES
+//
+// The interfaces in util/utf8/public/textutils.h provide higher-level
+// utilities for dealing with UnicodeTexts, including routines for
+// creating UnicodeTexts (both owners and aliases) from UTF-8 buffers or
+// strings, creating strings from UnicodeTexts, normalizing text for
+// efficient matching or display, and others.
+
+class UnicodeText {
+ public:
+ class const_iterator;
+
+ typedef char32 value_type;
+
+ // Constructors. These always produce owners.
+ UnicodeText(); // Create an empty text.
+ UnicodeText(const UnicodeText& src); // copy constructor
+ // Construct a substring (copies the data).
+ UnicodeText(const const_iterator& first, const const_iterator& last);
+
+ // Assignment operator. This copies the data and produces an owner
+ // unless this == &src, e.g., "x = x;", which is a no-op.
+ UnicodeText& operator=(const UnicodeText& src);
+
+ // x.Copy(y) copies the data from y into x.
+ UnicodeText& Copy(const UnicodeText& src);
+ inline UnicodeText& assign(const UnicodeText& src) { return Copy(src); }
+
+ // x.PointTo(y) changes x so that it points to y's data.
+ // It does not copy y or take ownership of y's data.
+ UnicodeText& PointTo(const UnicodeText& src);
+ UnicodeText& PointTo(const const_iterator& first,
+ const const_iterator& last);
+
+ ~UnicodeText();
+
+ void clear(); // Clear text.
+ bool empty() { return repr_.size_ == 0; } // Test if text is empty.
+
+ // Add a codepoint to the end of the text.
+ // If the codepoint is not interchange-valid, add a space instead
+ // and log a warning.
+ void push_back(char32 codepoint);
+
+ // Generic appending operation.
+ // iterator_traits<ForwardIterator>::value_type must be implicitly
+ // convertible to char32. Typical uses of this method might include:
+ // char32 chars[] = {0x1, 0x2, ...};
+ // vector<char32> more_chars = ...;
+ // utext.append(chars, chars+arraysize(chars));
+ // utext.append(more_chars.begin(), more_chars.end());
+ template<typename ForwardIterator>
+ UnicodeText& append(ForwardIterator first, const ForwardIterator last) {
+ while (first != last) { push_back(*first++); }
+ return *this;
+ }
+
+ // A specialization of the generic append() method.
+ UnicodeText& append(const const_iterator& first, const const_iterator& last);
+
+ // An optimization of append(source.begin(), source.end()).
+ UnicodeText& append(const UnicodeText& source);
+
+ int size() const; // the number of Unicode characters (codepoints)
+
+ friend bool operator==(const UnicodeText& lhs, const UnicodeText& rhs);
+ friend bool operator!=(const UnicodeText& lhs, const UnicodeText& rhs);
+
+ class const_iterator {
+ typedef const_iterator CI;
+ public:
+ typedef bidirectional_iterator_tag iterator_category;
+ typedef char32 value_type;
+ typedef ptrdiff_t difference_type;
+ typedef void pointer; // (Not needed.)
+ typedef const char32 reference; // (Needed for const_reverse_iterator)
+
+ // Iterators are default-constructible.
+ const_iterator();
+
+ // It's safe to make multiple passes over a UnicodeText.
+ const_iterator(const const_iterator& other);
+ const_iterator& operator=(const const_iterator& other);
+
+ char32 operator*() const; // Dereference
+
+ const_iterator& operator++(); // Advance (++iter)
+ const_iterator operator++(int) { // (iter++)
+ const_iterator result(*this);
+ ++*this;
+ return result;
+ }
+
+ const_iterator& operator--(); // Retreat (--iter)
+ const_iterator operator--(int) { // (iter--)
+ const_iterator result(*this);
+ --*this;
+ return result;
+ }
+
+ // We love relational operators.
+ friend bool operator==(const CI& lhs, const CI& rhs) {
+ return lhs.it_ == rhs.it_; }
+ friend bool operator!=(const CI& lhs, const CI& rhs) {
+ return !(lhs == rhs); }
+ friend bool operator<(const CI& lhs, const CI& rhs);
+ friend bool operator>(const CI& lhs, const CI& rhs) {
+ return rhs < lhs; }
+ friend bool operator<=(const CI& lhs, const CI& rhs) {
+ return !(rhs < lhs); }
+ friend bool operator>=(const CI& lhs, const CI& rhs) {
+ return !(lhs < rhs); }
+
+ friend difference_type distance(const CI& first, const CI& last);
+
+ // UTF-8-specific methods
+ // Store the UTF-8 encoding of the current codepoint into buf,
+ // which must be at least 4 bytes long. Return the number of
+ // bytes written.
+ int get_utf8(char* buf) const;
+ // Return the iterator's pointer into the UTF-8 data.
+ const char* utf8_data() const { return it_; }
+
+ string DebugString() const;
+
+ private:
+ friend class UnicodeText;
+ friend class UnicodeTextUtils;
+ friend class UTF8StateTableProperty;
+ explicit const_iterator(const char* it) : it_(it) {}
+
+ const char* it_;
+ };
+
+ const_iterator begin() const;
+ const_iterator end() const;
+
+ class const_reverse_iterator : public std::reverse_iterator<const_iterator> {
+ public:
+ const_reverse_iterator(const_iterator it) :
+ std::reverse_iterator<const_iterator>(it) {}
+ const char* utf8_data() const {
+ const_iterator tmp_it = base();
+ return (--tmp_it).utf8_data();
+ }
+ int get_utf8(char* buf) const {
+ const_iterator tmp_it = base();
+ return (--tmp_it).get_utf8(buf);
+ }
+ };
+ const_reverse_iterator rbegin() const {
+ return const_reverse_iterator(end());
+ }
+ const_reverse_iterator rend() const {
+ return const_reverse_iterator(begin());
+ }
+
+ // Substring searching. Returns the beginning of the first
+ // occurrence of "look", or end() if not found.
+ const_iterator find(const UnicodeText& look, const_iterator start_pos) const;
+ // Equivalent to find(look, begin())
+ const_iterator find(const UnicodeText& look) const;
+
+ // Returns whether this contains the character U+FFFD. This can
+ // occur, for example, if the input to Encodings::Decode() had byte
+ // sequences that were invalid in the source encoding.
+ bool HasReplacementChar() const;
+
+ // UTF-8-specific methods
+ //
+ // Return the data, length, and capacity of UTF-8-encoded version of
+ // the text. Length and capacity are measured in bytes.
+ const char* utf8_data() const { return repr_.data_; }
+ int utf8_length() const { return repr_.size_; }
+ int utf8_capacity() const { return repr_.capacity_; }
+
+ // Return the UTF-8 data as a string.
+ static string UTF8Substring(const const_iterator& first,
+ const const_iterator& last);
+
+ // There are three methods for initializing a UnicodeText from UTF-8
+ // data. They vary in details of memory management. In all cases,
+ // the data is tested for interchange-validity. If it is not
+ // interchange-valid, a LOG(WARNING) is issued, and each
+ // structurally invalid byte and each interchange-invalid codepoint
+ // is replaced with a space.
+
+ // x.CopyUTF8(buf, len) copies buf into x.
+ UnicodeText& CopyUTF8(const char* utf8_buffer, int byte_length);
+
+ // x.TakeOwnershipOfUTF8(buf, len, capacity). x takes ownership of
+ // buf. buf is not copied.
+ UnicodeText& TakeOwnershipOfUTF8(char* utf8_buffer,
+ int byte_length,
+ int byte_capacity);
+
+ // x.PointToUTF8(buf,len) changes x so that it points to buf
+ // ("becomes an alias"). It does not take ownership or copy buf.
+ // If the buffer is not valid, this has the same effect as
+ // CopyUTF8(utf8_buffer, byte_length).
+ UnicodeText& PointToUTF8(const char* utf8_buffer, int byte_length);
+
+ // Occasionally it is necessary to use functions that operate on the
+ // pointer returned by utf8_data(). MakeIterator(p) provides a way
+ // to get back to the UnicodeText level. It uses CHECK to ensure
+ // that p is a pointer within this object's UTF-8 data, and that it
+ // points to the beginning of a character.
+ const_iterator MakeIterator(const char* p) const;
+
+ string DebugString() const;
+
+ private:
+ friend class const_iterator;
+ friend class UnicodeTextUtils;
+
+ class Repr { // A byte-string.
+ public:
+ char* data_;
+ int size_;
+ int capacity_;
+ bool ours_; // Do we own data_?
+
+ Repr() : data_(NULL), size_(0), capacity_(0), ours_(true) {}
+ ~Repr() { if (ours_) delete[] data_; }
+
+ void clear();
+ void reserve(int capacity);
+ void resize(int size);
+
+ void append(const char* bytes, int byte_length);
+ void Copy(const char* data, int size);
+ void TakeOwnershipOf(char* data, int size, int capacity);
+ void PointTo(const char* data, int size);
+
+ string DebugString() const;
+
+ private:
+ Repr& operator=(const Repr&);
+ Repr(const Repr& other);
+ };
+
+ Repr repr_;
+
+ // UTF-8-specific private methods.
+ // These routines do not perform a validity check when compiled
+ // in opt mode.
+ // It is an error to call these methods with UTF-8 data that
+ // is not interchange-valid.
+ //
+ UnicodeText& UnsafeCopyUTF8(const char* utf8_buffer, int byte_length);
+ UnicodeText& UnsafeTakeOwnershipOfUTF8(
+ char* utf8_buffer, int byte_length, int byte_capacity);
+ UnicodeText& UnsafePointToUTF8(const char* utf8_buffer, int byte_length);
+ UnicodeText& UnsafeAppendUTF8(const char* utf8_buffer, int byte_length);
+ const_iterator UnsafeFind(const UnicodeText& look,
+ const_iterator start_pos) const;
+};
+
+bool operator==(const UnicodeText& lhs, const UnicodeText& rhs);
+
+inline bool operator!=(const UnicodeText& lhs, const UnicodeText& rhs) {
+ return !(lhs == rhs);
+}
+
+// UnicodeTextRange is a pair of iterators, useful for specifying text
+// segments. If the iterators are ==, the segment is empty.
+typedef pair<UnicodeText::const_iterator,
+ UnicodeText::const_iterator> UnicodeTextRange;
+
+inline bool UnicodeTextRangeIsEmpty(const UnicodeTextRange& r) {
+ return r.first == r.second;
+}
+
+
+// *************************** Utilities *************************
+
+// A factory function for creating a UnicodeText from a buffer of
+// UTF-8 data. The new UnicodeText takes ownership of the buffer. (It
+// is an "owner.")
+//
+// Each byte that is structurally invalid will be replaced with a
+// space. Each codepoint that is interchange-invalid will also be
+// replaced with a space, even if the codepoint was represented with a
+// multibyte sequence in the UTF-8 data.
+//
+inline UnicodeText MakeUnicodeTextAcceptingOwnership(
+ char* utf8_buffer, int byte_length, int byte_capacity) {
+ return UnicodeText().TakeOwnershipOfUTF8(
+ utf8_buffer, byte_length, byte_capacity);
+}
+
+// A factory function for creating a UnicodeText from a buffer of
+// UTF-8 data. The new UnicodeText does not take ownership of the
+// buffer. (It is an "alias.")
+//
+inline UnicodeText MakeUnicodeTextWithoutAcceptingOwnership(
+ const char* utf8_buffer, int byte_length) {
+ return UnicodeText().PointToUTF8(utf8_buffer, byte_length);
+}
+
+// Create a UnicodeText from a UTF-8 string or buffer.
+//
+// If do_copy is true, then a copy of the string is made. The copy is
+// owned by the resulting UnicodeText object and will be freed when
+// the object is destroyed. This UnicodeText object is referred to
+// as an "owner."
+//
+// If do_copy is false, then no copy is made. The resulting
+// UnicodeText object does NOT take ownership of the string; in this
+// case, the lifetime of the UnicodeText object must not exceed the
+// lifetime of the string. This Unicodetext object is referred to as
+// an "alias." This is the same as MakeUnicodeTextWithoutAcceptingOwnership.
+//
+// If the input string does not contain valid UTF-8, then a copy is
+// made (as if do_copy were true) and coerced to valid UTF-8 by
+// replacing each invalid byte with a space.
+//
+inline UnicodeText UTF8ToUnicodeText(const char* utf8_buf, int len,
+ bool do_copy) {
+ UnicodeText t;
+ if (do_copy) {
+ t.CopyUTF8(utf8_buf, len);
+ } else {
+ t.PointToUTF8(utf8_buf, len);
+ }
+ return t;
+}
+
+inline UnicodeText UTF8ToUnicodeText(const string& utf_string, bool do_copy) {
+ return UTF8ToUnicodeText(utf_string.data(), utf_string.size(), do_copy);
+}
+
+inline UnicodeText UTF8ToUnicodeText(const char* utf8_buf, int len) {
+ return UTF8ToUnicodeText(utf8_buf, len, true);
+}
+inline UnicodeText UTF8ToUnicodeText(const string& utf8_string) {
+ return UTF8ToUnicodeText(utf8_string, true);
+}
+
+// Return a string containing the UTF-8 encoded version of all the
+// Unicode characters in t.
+inline string UnicodeTextToUTF8(const UnicodeText& t) {
+ return string(t.utf8_data(), t.utf8_length());
+}
+
+
+// For debugging. Return a string of integers, written in uppercase
+// hex (%X), corresponding to the codepoints within the text. Each
+// integer is followed by a space. E.g., "61 62 6A 3005 ".
+string CodepointString(const UnicodeText& t);
+
+#endif // UTIL_UTF8_UNICODETEXT_H__
diff --git a/trunk/src/third_party/css_parser/src/util/utf8/public/unilib.h b/trunk/src/third_party/css_parser/src/util/utf8/public/unilib.h
new file mode 100644
index 0000000..4cfc787
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/util/utf8/public/unilib.h
@@ -0,0 +1,95 @@
+/**
+ * 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.
+ */
+
+// Routines to do manipulation of Unicode characters or text
+//
+// The StructurallyValid routines accept buffers of arbitrary bytes.
+// For CoerceToStructurallyValid(), the input buffer and output buffers may
+// point to exactly the same memory.
+//
+// In all other cases, the UTF-8 string must be structurally valid and
+// have all codepoints in the range U+0000 to U+D7FF or U+E000 to U+10FFFF.
+// Debug builds take a fatal error for invalid UTF-8 input.
+// The input and output buffers may not overlap at all.
+//
+// The char32 routines are here only for convenience; they convert to UTF-8
+// internally and use the UTF-8 routines.
+
+#ifndef UTIL_UTF8_UNILIB_H__
+#define UTIL_UTF8_UNILIB_H__
+
+#include <string>
+#include "base/basictypes.h"
+
+namespace UniLib {
+
+// Returns true unless a surrogate code point
+inline bool IsValidCodepoint(char32 c) {
+ // In the range [0, 0xD800) or [0xE000, 0x10FFFF]
+ return (static_cast<uint32>(c) < 0xD800)
+ || (c >= 0xE000 && c <= 0x10FFFF);
+}
+
+// Table of UTF-8 character lengths, based on first byte
+static const unsigned char kUTF8LenTbl[256] = {
+ 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
+
+ 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
+ 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
+ 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4
+};
+
+// Return length of a single UTF-8 source character
+inline int OneCharLen(const char* src) {
+ return kUTF8LenTbl[*reinterpret_cast<const uint8*>(src)];
+}
+
+// Return length of a single UTF-8 source character
+inline int OneCharLen(const uint8* src) {
+ return kUTF8LenTbl[*src];
+}
+
+// Return true if this byte is a trailing UTF-8 byte (10xx xxxx)
+inline bool IsTrailByte(char x) {
+ // return (x & 0xC0) == 0x80;
+ // Since trail bytes are always in [0x80, 0xBF], we can optimize:
+ return static_cast<signed char>(x) < -0x40;
+}
+
+// Returns the length in bytes of the prefix of src that is all
+// interchange valid UTF-8
+int SpanInterchangeValid(const char* src, int byte_length);
+inline int SpanInterchangeValid(const std::string& src) {
+ return SpanInterchangeValid(src.data(), src.size());
+}
+
+// Returns true if the source is all interchange valid UTF-8
+// "Interchange valid" is a stronger than structurally valid --
+// no C0 or C1 control codes (other than CR LF HT FF) and no non-characters.
+inline bool IsInterchangeValid(const char* src, int byte_length) {
+ return (byte_length == SpanInterchangeValid(src, byte_length));
+}
+inline bool IsInterchangeValid(const std::string& src) {
+ return IsInterchangeValid(src.data(), src.size());
+}
+
+} // namespace UniLib
+
+#endif // UTIL_UTF8_PUBLIC_UNILIB_H_
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/gtest_main.cc b/trunk/src/third_party/css_parser/src/webutil/css/gtest_main.cc
new file mode 100644
index 0000000..1d97136
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/gtest_main.cc
@@ -0,0 +1,26 @@
+/**
+ * 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)
+//
+// Build all tests with this main to run all tests.
+
+#include "gtest-1.5.0/install/include/gtest/gtest.h"
+
+int main(int argc, char **argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/identifier.cc b/trunk/src/third_party/css_parser/src/webutil/css/identifier.cc
new file mode 100644
index 0000000..eaad0e5
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/identifier.cc
@@ -0,0 +1,985 @@
+/**
+ * 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.
+ */
+
+/* C++ code produced by gperf version 3.0.3 */
+/* Computed positions: -k'1-3,10,$' */
+
+#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \
+ && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \
+ && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \
+ && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \
+ && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \
+ && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \
+ && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \
+ && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \
+ && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \
+ && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \
+ && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \
+ && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \
+ && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \
+ && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \
+ && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \
+ && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \
+ && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \
+ && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \
+ && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \
+ && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \
+ && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \
+ && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \
+ && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126))
+/* The character set is not based on ISO-646. */
+#error "gperf generated tables don't work with this execution character set. Please report a bug to <bug-gnu-gperf@gnu.org>."
+#endif
+
+#line 1 "webutil/css/identifier.gperf"
+
+#include "webutil/css/identifier.h"
+
+#include "base/googleinit.h"
+#include "base/logging.h"
+#include "webutil/css/string_util.h"
+
+namespace Css {
+#line 11 "webutil/css/identifier.gperf"
+struct idents {
+ const char *name;
+ Identifier::Ident id;
+};
+enum
+ {
+ TOTAL_KEYWORDS = 144,
+ MIN_WORD_LENGTH = 3,
+ MAX_WORD_LENGTH = 24,
+ MIN_HASH_VALUE = 5,
+ MAX_HASH_VALUE = 401
+ };
+
+/* maximum key range = 397, duplicates = 0 */
+
+#ifndef GPERF_DOWNCASE
+#define GPERF_DOWNCASE 1
+static unsigned char gperf_downcase[256] =
+ {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
+ 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
+ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
+ 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106,
+ 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121,
+ 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104,
+ 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
+ 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134,
+ 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
+ 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164,
+ 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
+ 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194,
+ 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209,
+ 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224,
+ 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
+ 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254,
+ 255
+ };
+#endif
+
+#ifndef GPERF_CASE_STRNCMP
+#define GPERF_CASE_STRNCMP 1
+static int
+gperf_case_strncmp (register const char *s1, register const char *s2, register unsigned int n)
+{
+ for (; n > 0;)
+ {
+ unsigned char c1 = gperf_downcase[(unsigned char)*s1++];
+ unsigned char c2 = gperf_downcase[(unsigned char)*s2++];
+ if (c1 != 0 && c1 == c2)
+ {
+ n--;
+ continue;
+ }
+ return (int)c1 - (int)c2;
+ }
+ return 0;
+}
+#endif
+
+class IdentifierMapper
+{
+private:
+ static inline unsigned int hash (const char *str, unsigned int len);
+public:
+ static const struct idents *in_word_set (const char *str, unsigned int len);
+};
+
+inline unsigned int
+IdentifierMapper::hash (register const char *str, register unsigned int len)
+{
+ static const unsigned short asso_values[] =
+ {
+ 402, 402, 402, 402, 402, 402, 402, 402, 402, 402,
+ 402, 402, 402, 402, 402, 402, 402, 402, 402, 402,
+ 402, 402, 402, 402, 402, 402, 402, 402, 402, 402,
+ 402, 402, 402, 402, 402, 402, 402, 402, 402, 402,
+ 402, 402, 402, 402, 402, 60, 125, 402, 402, 402,
+ 402, 402, 402, 402, 402, 402, 402, 402, 402, 402,
+ 402, 402, 402, 402, 402, 30, 0, 50, 5, 0,
+ 25, 30, 145, 65, 110, 50, 10, 140, 45, 75,
+ 15, 155, 20, 5, 0, 45, 110, 20, 75, 95,
+ 402, 402, 402, 402, 402, 402, 402, 30, 0, 50,
+ 5, 0, 25, 30, 145, 65, 110, 50, 10, 140,
+ 45, 75, 15, 155, 20, 5, 0, 45, 110, 20,
+ 75, 95, 402, 402, 402, 402, 402, 402, 402, 402,
+ 402, 402, 402, 402, 402, 402, 402, 402, 402, 402,
+ 402, 402, 402, 402, 402, 402, 402, 402, 402, 402,
+ 402, 402, 402, 402, 402, 402, 402, 402, 402, 402,
+ 402, 402, 402, 402, 402, 402, 402, 402, 402, 402,
+ 402, 402, 402, 402, 402, 402, 402, 402, 402, 402,
+ 402, 402, 402, 402, 402, 402, 402, 402, 402, 402,
+ 402, 402, 402, 402, 402, 402, 402, 402, 402, 402,
+ 402, 402, 402, 402, 402, 402, 402, 402, 402, 402,
+ 402, 402, 402, 402, 402, 402, 402, 402, 402, 402,
+ 402, 402, 402, 402, 402, 402, 402, 402, 402, 402,
+ 402, 402, 402, 402, 402, 402, 402, 402, 402, 402,
+ 402, 402, 402, 402, 402, 402, 402, 402, 402, 402,
+ 402, 402, 402, 402, 402, 402, 402
+ };
+ register int hval = len;
+
+ switch (hval)
+ {
+ default:
+ hval += asso_values[(unsigned char)str[9]];
+ /*FALLTHROUGH*/
+ case 9:
+ case 8:
+ case 7:
+ case 6:
+ case 5:
+ case 4:
+ case 3:
+ hval += asso_values[(unsigned char)str[2]];
+ /*FALLTHROUGH*/
+ case 2:
+ hval += asso_values[(unsigned char)str[1]+1];
+ /*FALLTHROUGH*/
+ case 1:
+ hval += asso_values[(unsigned char)str[0]];
+ break;
+ }
+ return hval + asso_values[(unsigned char)str[len - 1]];
+}
+
+static const struct idents wordlist[] =
+ {
+#line 78 "webutil/css/identifier.gperf"
+ {"table", Identifier::TABLE},
+#line 145 "webutil/css/identifier.gperf"
+ {"baseline", Identifier::BASELINE},
+#line 42 "webutil/css/identifier.gperf"
+ {"dashed", Identifier::DASHED},
+#line 151 "webutil/css/identifier.gperf"
+ {"pre", Identifier::PRE},
+#line 154 "webutil/css/identifier.gperf"
+ {"pre-line", Identifier::PRE_LINE},
+#line 83 "webutil/css/identifier.gperf"
+ {"table-row", Identifier::TABLE_ROW},
+#line 86 "webutil/css/identifier.gperf"
+ {"table-cell", Identifier::TABLE_CELL},
+#line 41 "webutil/css/identifier.gperf"
+ {"dotted", Identifier::DOTTED},
+#line 82 "webutil/css/identifier.gperf"
+ {"table-footer-group", Identifier::TABLE_FOOTER_GROUP},
+#line 106 "webutil/css/identifier.gperf"
+ {"bold", Identifier::BOLD},
+#line 98 "webutil/css/identifier.gperf"
+ {"large", Identifier::LARGE},
+#line 81 "webutil/css/identifier.gperf"
+ {"table-header-group", Identifier::TABLE_HEADER_GROUP},
+#line 43 "webutil/css/identifier.gperf"
+ {"solid", Identifier::SOLID},
+#line 153 "webutil/css/identifier.gperf"
+ {"pre-wrap", Identifier::PRE_WRAP},
+#line 34 "webutil/css/identifier.gperf"
+ {"scroll", Identifier::SCROLL},
+#line 31 "webutil/css/identifier.gperf"
+ {"top", Identifier::TOP},
+#line 107 "webutil/css/identifier.gperf"
+ {"bolder", Identifier::BOLDER},
+#line 40 "webutil/css/identifier.gperf"
+ {"separate", Identifier::SEPARATE},
+#line 142 "webutil/css/identifier.gperf"
+ {"lowercase", Identifier::LOWERCASE},
+#line 143 "webutil/css/identifier.gperf"
+ {"embed", Identifier::EMBED},
+#line 102 "webutil/css/identifier.gperf"
+ {"larger", Identifier::LARGER},
+#line 87 "webutil/css/identifier.gperf"
+ {"table-caption", Identifier::TABLE_CAPTION},
+#line 56 "webutil/css/identifier.gperf"
+ {"default", Identifier::DEFAULT},
+#line 133 "webutil/css/identifier.gperf"
+ {"relative", Identifier::RELATIVE},
+#line 28 "webutil/css/identifier.gperf"
+ {"left", Identifier::LEFT},
+#line 26 "webutil/css/identifier.gperf"
+ {"repeat", Identifier::REPEAT},
+#line 44 "webutil/css/identifier.gperf"
+ {"double", Identifier::DOUBLE},
+#line 140 "webutil/css/identifier.gperf"
+ {"capitalize", Identifier::CAPITALIZE},
+#line 119 "webutil/css/identifier.gperf"
+ {"square", Identifier::SQUARE},
+#line 84 "webutil/css/identifier.gperf"
+ {"table-column-group", Identifier::TABLE_COLUMN_GROUP},
+#line 90 "webutil/css/identifier.gperf"
+ {"serif", Identifier::SERIF},
+#line 27 "webutil/css/identifier.gperf"
+ {"collapse", Identifier::COLLAPSE},
+#line 72 "webutil/css/identifier.gperf"
+ {"rtl", Identifier::RTL,},
+#line 68 "webutil/css/identifier.gperf"
+ {"wait", Identifier::WAIT},
+#line 80 "webutil/css/identifier.gperf"
+ {"table-row-group", Identifier::TABLE_ROW_GROUP},
+#line 36 "webutil/css/identifier.gperf"
+ {"transparent", Identifier::TRANSPARENT},
+#line 134 "webutil/css/identifier.gperf"
+ {"absolute", Identifier::ABSOLUTE},
+#line 97 "webutil/css/identifier.gperf"
+ {"small", Identifier::SMALL},
+#line 20 "webutil/css/identifier.gperf"
+ {"normal", Identifier::NORMAL},
+#line 120 "webutil/css/identifier.gperf"
+ {"decimal", Identifier::DECIMAL},
+#line 71 "webutil/css/identifier.gperf"
+ {"ltr", Identifier::LTR,},
+#line 63 "webutil/css/identifier.gperf"
+ {"se-resize", Identifier::SE_RESIZE},
+#line 105 "webutil/css/identifier.gperf"
+ {"small-caps", Identifier::SMALL_CAPS},
+#line 152 "webutil/css/identifier.gperf"
+ {"nowrap", Identifier::NOWRAP},
+#line 85 "webutil/css/identifier.gperf"
+ {"table-column", Identifier::TABLE_COLUMN},
+#line 137 "webutil/css/identifier.gperf"
+ {"overline", Identifier::OVERLINE},
+#line 67 "webutil/css/identifier.gperf"
+ {"text", Identifier::TEXT},
+#line 124 "webutil/css/identifier.gperf"
+ {"lower-greek", Identifier::LOWER_GREEK},
+#line 101 "webutil/css/identifier.gperf"
+ {"smaller", Identifier::SMALLER},
+#line 70 "webutil/css/identifier.gperf"
+ {"progress", Identifier::PROGRESS},
+#line 18 "webutil/css/identifier.gperf"
+ {"none", Identifier::NONE},
+#line 91 "webutil/css/identifier.gperf"
+ {"sans-serif", Identifier::SANS_SERIF},
+#line 45 "webutil/css/identifier.gperf"
+ {"groove", Identifier::GROOVE},
+#line 109 "webutil/css/identifier.gperf"
+ {"caption", Identifier::CAPTION},
+#line 146 "webutil/css/identifier.gperf"
+ {"sub", Identifier::SUB},
+#line 57 "webutil/css/identifier.gperf"
+ {"pointer", Identifier::POINTER},
+#line 148 "webutil/css/identifier.gperf"
+ {"text-top", Identifier::TEXT_TOP},
+#line 39 "webutil/css/identifier.gperf"
+ {"no-repeat", Identifier::NO_REPEAT},
+#line 114 "webutil/css/identifier.gperf"
+ {"status-bar", Identifier::STATUS_BAR},
+#line 122 "webutil/css/identifier.gperf"
+ {"lower-roman", Identifier::LOWER_ROMAN},
+#line 136 "webutil/css/identifier.gperf"
+ {"underline", Identifier::UNDERLINE},
+#line 24 "webutil/css/identifier.gperf"
+ {"avoid", Identifier::AVOID},
+#line 132 "webutil/css/identifier.gperf"
+ {"static", Identifier::STATIC},
+#line 113 "webutil/css/identifier.gperf"
+ {"small-caption", Identifier::SMALL_CAPTION},
+#line 60 "webutil/css/identifier.gperf"
+ {"ne-resize", Identifier::NE_RESIZE},
+#line 46 "webutil/css/identifier.gperf"
+ {"ridge", Identifier::RIDGE},
+#line 104 "webutil/css/identifier.gperf"
+ {"oblique", Identifier::OBLIQUE},
+#line 37 "webutil/css/identifier.gperf"
+ {"repeat-x", Identifier::REPEAT_X},
+#line 29 "webutil/css/identifier.gperf"
+ {"center", Identifier::CENTER},
+#line 144 "webutil/css/identifier.gperf"
+ {"bidi-override", Identifier::BIDI_OVERRIDE},
+#line 64 "webutil/css/identifier.gperf"
+ {"sw-resize", Identifier::SW_RESIZE},
+#line 47 "webutil/css/identifier.gperf"
+ {"inset", Identifier::INSET},
+#line 115 "webutil/css/identifier.gperf"
+ {"inside", Identifier::INSIDE},
+#line 59 "webutil/css/identifier.gperf"
+ {"e-resize", Identifier::E_RESIZE},
+#line 147 "webutil/css/identifier.gperf"
+ {"super", Identifier::SUPER},
+#line 73 "webutil/css/identifier.gperf"
+ {"inline", Identifier::INLINE},
+#line 65 "webutil/css/identifier.gperf"
+ {"s-resize", Identifier::S_RESIZE},
+#line 55 "webutil/css/identifier.gperf"
+ {"crosshair", Identifier::CROSSHAIR},
+#line 32 "webutil/css/identifier.gperf"
+ {"bottom", Identifier::BOTTOM},
+#line 79 "webutil/css/identifier.gperf"
+ {"inline-table", Identifier::INLINE_TABLE},
+#line 38 "webutil/css/identifier.gperf"
+ {"repeat-y", Identifier::REPEAT_Y},
+#line 33 "webutil/css/identifier.gperf"
+ {"both", Identifier::BOTH},
+#line 30 "webutil/css/identifier.gperf"
+ {"right", Identifier::RIGHT},
+#line 125 "webutil/css/identifier.gperf"
+ {"lower-latin", Identifier::LOWER_LATIN,},
+#line 88 "webutil/css/identifier.gperf"
+ {"show", Identifier::SHOW},
+#line 93 "webutil/css/identifier.gperf"
+ {"fantasy", Identifier::FANTASY},
+#line 66 "webutil/css/identifier.gperf"
+ {"w-resize", Identifier::W_RESIZE},
+#line 117 "webutil/css/identifier.gperf"
+ {"disc", Identifier::DISC},
+#line 121 "webutil/css/identifier.gperf"
+ {"decimal-leading-zero", Identifier::DECIMAL_LEADING_ZERO},
+#line 108 "webutil/css/identifier.gperf"
+ {"lighter", Identifier::LIGHTER},
+#line 53 "webutil/css/identifier.gperf"
+ {"no-open-quote", Identifier::NO_OPEN_QUOTE},
+#line 49 "webutil/css/identifier.gperf"
+ {"thin", Identifier::THIN},
+#line 128 "webutil/css/identifier.gperf"
+ {"georgian", Identifier::GEORGIAN},
+#line 50 "webutil/css/identifier.gperf"
+ {"thick", Identifier::THICK},
+#line 118 "webutil/css/identifier.gperf"
+ {"circle", Identifier::CIRCLE},
+#line 92 "webutil/css/identifier.gperf"
+ {"cursive", Identifier::CURSIVE},
+#line 61 "webutil/css/identifier.gperf"
+ {"nw-resize", Identifier::NW_RESIZE},
+#line 48 "webutil/css/identifier.gperf"
+ {"outset", Identifier::OUTSET},
+#line 116 "webutil/css/identifier.gperf"
+ {"outside", Identifier::OUTSIDE},
+#line 110 "webutil/css/identifier.gperf"
+ {"icon", Identifier::ICON},
+#line 103 "webutil/css/identifier.gperf"
+ {"italic", Identifier::ITALIC},
+#line 62 "webutil/css/identifier.gperf"
+ {"n-resize", Identifier::N_RESIZE},
+#line 69 "webutil/css/identifier.gperf"
+ {"help", Identifier::HELP},
+#line 23 "webutil/css/identifier.gperf"
+ {"always", Identifier::ALWAYS},
+#line 94 "webutil/css/identifier.gperf"
+ {"monospace", Identifier::MONOSPACE},
+#line 99 "webutil/css/identifier.gperf"
+ {"x-large", Identifier::X_LARGE},
+#line 19 "webutil/css/identifier.gperf"
+ {"auto", Identifier::AUTO},
+#line 35 "webutil/css/identifier.gperf"
+ {"fixed", Identifier::FIXED},
+#line 96 "webutil/css/identifier.gperf"
+ {"x-small", Identifier::X_SMALL},
+#line 141 "webutil/css/identifier.gperf"
+ {"uppercase", Identifier::UPPERCASE},
+#line 76 "webutil/css/identifier.gperf"
+ {"run-in", Identifier::RUN_IN},
+#line 127 "webutil/css/identifier.gperf"
+ {"armenian", Identifier::ARMENIAN},
+#line 129 "webutil/css/identifier.gperf"
+ {"lower-alpha", Identifier::LOWER_ALPHA},
+#line 21 "webutil/css/identifier.gperf"
+ {"visible", Identifier::VISIBLE},
+#line 100 "webutil/css/identifier.gperf"
+ {"xx-large", Identifier::XX_LARGE},
+#line 51 "webutil/css/identifier.gperf"
+ {"open-quote", Identifier::OPEN_QUOTE},
+#line 95 "webutil/css/identifier.gperf"
+ {"xx-small", Identifier::XX_SMALL},
+#line 131 "webutil/css/identifier.gperf"
+ {"invert", Identifier::INVERT},
+#line 111 "webutil/css/identifier.gperf"
+ {"menu", Identifier::MENU},
+#line 139 "webutil/css/identifier.gperf"
+ {"blink", Identifier::BLINK},
+#line 149 "webutil/css/identifier.gperf"
+ {"middle", Identifier::MIDDLE},
+#line 89 "webutil/css/identifier.gperf"
+ {"hide", Identifier::HIDE},
+#line 58 "webutil/css/identifier.gperf"
+ {"move", Identifier::MOVE},
+#line 74 "webutil/css/identifier.gperf"
+ {"block", Identifier::BLOCK},
+#line 75 "webutil/css/identifier.gperf"
+ {"list-item", Identifier::LIST_ITEM},
+#line 52 "webutil/css/identifier.gperf"
+ {"close-quote", Identifier::CLOSE_QUOTE},
+#line 77 "webutil/css/identifier.gperf"
+ {"inline-block", Identifier::INLINE_BLOCK},
+#line 54 "webutil/css/identifier.gperf"
+ {"no-close-quote", Identifier::NO_CLOSE_QUOTE},
+#line 17 "webutil/css/identifier.gperf"
+ {"inherit", Identifier::INHERIT},
+#line 156 "webutil/css/identifier.gperf"
+ {"--goog-body-color--", Identifier::GOOG_BODY_COLOR},
+#line 123 "webutil/css/identifier.gperf"
+ {"upper-roman", Identifier::UPPER_ROMAN},
+#line 157 "webutil/css/identifier.gperf"
+ {"--goog-body-link-color--", Identifier::GOOG_BODY_LINK_COLOR},
+#line 22 "webutil/css/identifier.gperf"
+ {"hidden", Identifier::HIDDEN},
+#line 25 "webutil/css/identifier.gperf"
+ {"medium", Identifier::MEDIUM},
+#line 158 "webutil/css/identifier.gperf"
+ {"--goog-big--", Identifier::GOOG_BIG},
+#line 159 "webutil/css/identifier.gperf"
+ {"--goog-small--", Identifier::GOOG_SMALL},
+#line 150 "webutil/css/identifier.gperf"
+ {"text-bottom", Identifier::TEXT_BOTTOM},
+#line 135 "webutil/css/identifier.gperf"
+ {"justify", Identifier::JUSTIFY},
+#line 112 "webutil/css/identifier.gperf"
+ {"message-box", Identifier::MESSAGE_BOX},
+#line 126 "webutil/css/identifier.gperf"
+ {"upper-latin", Identifier::UPPER_LATIN},
+#line 16 "webutil/css/identifier.gperf"
+ {"--goog-unknown--", Identifier::GOOG_UNKNOWN},
+#line 155 "webutil/css/identifier.gperf"
+ {"--goog-initial--", Identifier::GOOG_INITIAL},
+#line 138 "webutil/css/identifier.gperf"
+ {"line-through", Identifier::LINE_THROUGH},
+#line 130 "webutil/css/identifier.gperf"
+ {"upper-alpha", Identifier::UPPER_ALPHA}
+ };
+
+const struct idents *
+IdentifierMapper::in_word_set (register const char *str, register unsigned int len)
+{
+ if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH)
+ {
+ register int key = hash (str, len);
+
+ if (key <= MAX_HASH_VALUE && key >= MIN_HASH_VALUE)
+ {
+ register const struct idents *resword;
+
+ switch (key - 5)
+ {
+ case 0:
+ resword = &wordlist[0];
+ goto compare;
+ case 8:
+ resword = &wordlist[1];
+ goto compare;
+ case 16:
+ resword = &wordlist[2];
+ goto compare;
+ case 18:
+ resword = &wordlist[3];
+ goto compare;
+ case 23:
+ resword = &wordlist[4];
+ goto compare;
+ case 24:
+ resword = &wordlist[5];
+ goto compare;
+ case 25:
+ resword = &wordlist[6];
+ goto compare;
+ case 26:
+ resword = &wordlist[7];
+ goto compare;
+ case 28:
+ resword = &wordlist[8];
+ goto compare;
+ case 29:
+ resword = &wordlist[9];
+ goto compare;
+ case 30:
+ resword = &wordlist[10];
+ goto compare;
+ case 33:
+ resword = &wordlist[11];
+ goto compare;
+ case 35:
+ resword = &wordlist[12];
+ goto compare;
+ case 38:
+ resword = &wordlist[13];
+ goto compare;
+ case 41:
+ resword = &wordlist[14];
+ goto compare;
+ case 43:
+ resword = &wordlist[15];
+ goto compare;
+ case 46:
+ resword = &wordlist[16];
+ goto compare;
+ case 48:
+ resword = &wordlist[17];
+ goto compare;
+ case 49:
+ resword = &wordlist[18];
+ goto compare;
+ case 50:
+ resword = &wordlist[19];
+ goto compare;
+ case 51:
+ resword = &wordlist[20];
+ goto compare;
+ case 53:
+ resword = &wordlist[21];
+ goto compare;
+ case 57:
+ resword = &wordlist[22];
+ goto compare;
+ case 58:
+ resword = &wordlist[23];
+ goto compare;
+ case 59:
+ resword = &wordlist[24];
+ goto compare;
+ case 61:
+ resword = &wordlist[25];
+ goto compare;
+ case 66:
+ resword = &wordlist[26];
+ goto compare;
+ case 70:
+ resword = &wordlist[27];
+ goto compare;
+ case 71:
+ resword = &wordlist[28];
+ goto compare;
+ case 73:
+ resword = &wordlist[29];
+ goto compare;
+ case 75:
+ resword = &wordlist[30];
+ goto compare;
+ case 78:
+ resword = &wordlist[31];
+ goto compare;
+ case 83:
+ resword = &wordlist[32];
+ goto compare;
+ case 84:
+ resword = &wordlist[33];
+ goto compare;
+ case 85:
+ resword = &wordlist[34];
+ goto compare;
+ case 86:
+ resword = &wordlist[35];
+ goto compare;
+ case 88:
+ resword = &wordlist[36];
+ goto compare;
+ case 90:
+ resword = &wordlist[37];
+ goto compare;
+ case 91:
+ resword = &wordlist[38];
+ goto compare;
+ case 92:
+ resword = &wordlist[39];
+ goto compare;
+ case 93:
+ resword = &wordlist[40];
+ goto compare;
+ case 94:
+ resword = &wordlist[41];
+ goto compare;
+ case 95:
+ resword = &wordlist[42];
+ goto compare;
+ case 96:
+ resword = &wordlist[43];
+ goto compare;
+ case 97:
+ resword = &wordlist[44];
+ goto compare;
+ case 98:
+ resword = &wordlist[45];
+ goto compare;
+ case 99:
+ resword = &wordlist[46];
+ goto compare;
+ case 101:
+ resword = &wordlist[47];
+ goto compare;
+ case 102:
+ resword = &wordlist[48];
+ goto compare;
+ case 103:
+ resword = &wordlist[49];
+ goto compare;
+ case 104:
+ resword = &wordlist[50];
+ goto compare;
+ case 105:
+ resword = &wordlist[51];
+ goto compare;
+ case 111:
+ resword = &wordlist[52];
+ goto compare;
+ case 112:
+ resword = &wordlist[53];
+ goto compare;
+ case 113:
+ resword = &wordlist[54];
+ goto compare;
+ case 117:
+ resword = &wordlist[55];
+ goto compare;
+ case 118:
+ resword = &wordlist[56];
+ goto compare;
+ case 124:
+ resword = &wordlist[57];
+ goto compare;
+ case 125:
+ resword = &wordlist[58];
+ goto compare;
+ case 126:
+ resword = &wordlist[59];
+ goto compare;
+ case 129:
+ resword = &wordlist[60];
+ goto compare;
+ case 130:
+ resword = &wordlist[61];
+ goto compare;
+ case 131:
+ resword = &wordlist[62];
+ goto compare;
+ case 133:
+ resword = &wordlist[63];
+ goto compare;
+ case 134:
+ resword = &wordlist[64];
+ goto compare;
+ case 135:
+ resword = &wordlist[65];
+ goto compare;
+ case 137:
+ resword = &wordlist[66];
+ goto compare;
+ case 138:
+ resword = &wordlist[67];
+ goto compare;
+ case 141:
+ resword = &wordlist[68];
+ goto compare;
+ case 143:
+ resword = &wordlist[69];
+ goto compare;
+ case 144:
+ resword = &wordlist[70];
+ goto compare;
+ case 145:
+ resword = &wordlist[71];
+ goto compare;
+ case 146:
+ resword = &wordlist[72];
+ goto compare;
+ case 148:
+ resword = &wordlist[73];
+ goto compare;
+ case 150:
+ resword = &wordlist[74];
+ goto compare;
+ case 151:
+ resword = &wordlist[75];
+ goto compare;
+ case 153:
+ resword = &wordlist[76];
+ goto compare;
+ case 154:
+ resword = &wordlist[77];
+ goto compare;
+ case 156:
+ resword = &wordlist[78];
+ goto compare;
+ case 157:
+ resword = &wordlist[79];
+ goto compare;
+ case 158:
+ resword = &wordlist[80];
+ goto compare;
+ case 159:
+ resword = &wordlist[81];
+ goto compare;
+ case 160:
+ resword = &wordlist[82];
+ goto compare;
+ case 161:
+ resword = &wordlist[83];
+ goto compare;
+ case 164:
+ resword = &wordlist[84];
+ goto compare;
+ case 167:
+ resword = &wordlist[85];
+ goto compare;
+ case 168:
+ resword = &wordlist[86];
+ goto compare;
+ case 169:
+ resword = &wordlist[87];
+ goto compare;
+ case 170:
+ resword = &wordlist[88];
+ goto compare;
+ case 172:
+ resword = &wordlist[89];
+ goto compare;
+ case 173:
+ resword = &wordlist[90];
+ goto compare;
+ case 174:
+ resword = &wordlist[91];
+ goto compare;
+ case 178:
+ resword = &wordlist[92];
+ goto compare;
+ case 180:
+ resword = &wordlist[93];
+ goto compare;
+ case 181:
+ resword = &wordlist[94];
+ goto compare;
+ case 182:
+ resword = &wordlist[95];
+ goto compare;
+ case 184:
+ resword = &wordlist[96];
+ goto compare;
+ case 186:
+ resword = &wordlist[97];
+ goto compare;
+ case 187:
+ resword = &wordlist[98];
+ goto compare;
+ case 189:
+ resword = &wordlist[99];
+ goto compare;
+ case 191:
+ resword = &wordlist[100];
+ goto compare;
+ case 193:
+ resword = &wordlist[101];
+ goto compare;
+ case 194:
+ resword = &wordlist[102];
+ goto compare;
+ case 196:
+ resword = &wordlist[103];
+ goto compare;
+ case 204:
+ resword = &wordlist[104];
+ goto compare;
+ case 212:
+ resword = &wordlist[105];
+ goto compare;
+ case 214:
+ resword = &wordlist[106];
+ goto compare;
+ case 215:
+ resword = &wordlist[107];
+ goto compare;
+ case 217:
+ resword = &wordlist[108];
+ goto compare;
+ case 219:
+ resword = &wordlist[109];
+ goto compare;
+ case 221:
+ resword = &wordlist[110];
+ goto compare;
+ case 223:
+ resword = &wordlist[111];
+ goto compare;
+ case 226:
+ resword = &wordlist[112];
+ goto compare;
+ case 227:
+ resword = &wordlist[113];
+ goto compare;
+ case 233:
+ resword = &wordlist[114];
+ goto compare;
+ case 235:
+ resword = &wordlist[115];
+ goto compare;
+ case 243:
+ resword = &wordlist[116];
+ goto compare;
+ case 251:
+ resword = &wordlist[117];
+ goto compare;
+ case 254:
+ resword = &wordlist[118];
+ goto compare;
+ case 255:
+ resword = &wordlist[119];
+ goto compare;
+ case 256:
+ resword = &wordlist[120];
+ goto compare;
+ case 259:
+ resword = &wordlist[121];
+ goto compare;
+ case 264:
+ resword = &wordlist[122];
+ goto compare;
+ case 265:
+ resword = &wordlist[123];
+ goto compare;
+ case 269:
+ resword = &wordlist[124];
+ goto compare;
+ case 271:
+ resword = &wordlist[125];
+ goto compare;
+ case 282:
+ resword = &wordlist[126];
+ goto compare;
+ case 284:
+ resword = &wordlist[127];
+ goto compare;
+ case 287:
+ resword = &wordlist[128];
+ goto compare;
+ case 294:
+ resword = &wordlist[129];
+ goto compare;
+ case 296:
+ resword = &wordlist[130];
+ goto compare;
+ case 299:
+ resword = &wordlist[131];
+ goto compare;
+ case 306:
+ resword = &wordlist[132];
+ goto compare;
+ case 311:
+ resword = &wordlist[133];
+ goto compare;
+ case 312:
+ resword = &wordlist[134];
+ goto compare;
+ case 314:
+ resword = &wordlist[135];
+ goto compare;
+ case 321:
+ resword = &wordlist[136];
+ goto compare;
+ case 322:
+ resword = &wordlist[137];
+ goto compare;
+ case 326:
+ resword = &wordlist[138];
+ goto compare;
+ case 331:
+ resword = &wordlist[139];
+ goto compare;
+ case 336:
+ resword = &wordlist[140];
+ goto compare;
+ case 351:
+ resword = &wordlist[141];
+ goto compare;
+ case 362:
+ resword = &wordlist[142];
+ goto compare;
+ case 396:
+ resword = &wordlist[143];
+ goto compare;
+ }
+ return 0;
+ compare:
+ {
+ register const char *s = resword->name;
+
+ if ((((unsigned char)*str ^ (unsigned char)*s) & ~32) == 0 && !gperf_case_strncmp (str, s, len) && s[len] == '\0')
+ return resword;
+ }
+ }
+ }
+ return 0;
+}
+#line 160 "webutil/css/identifier.gperf"
+
+
+//
+// Constructor.
+//
+
+Identifier::Identifier(const UnicodeText& s) : ident_(IdentFromText(s)) {
+ if (ident_ == OTHER)
+ other_ = LowercaseAscii(s);
+}
+
+//
+// Static methods mapping Ident's to strings
+//
+
+Identifier::Ident Identifier::IdentFromText(const UnicodeText& s) {
+ const idents* a = IdentifierMapper::in_word_set(s.utf8_data(),
+ s.utf8_length());
+ if (a)
+ return a->id;
+ else
+ return OTHER;
+}
+
+static struct {
+ const char* name;
+ int len;
+} gKnownIdentifiers[TOTAL_KEYWORDS];
+
+static void InitializeIdentifierNameLookupTable() {
+ for (int i = 0; i < TOTAL_KEYWORDS; ++i) {
+ gKnownIdentifiers[wordlist[i].id].name = wordlist[i].name;
+ gKnownIdentifiers[wordlist[i].id].len = strlen(wordlist[i].name);
+ }
+}
+
+UnicodeText Identifier::TextFromIdent(Ident p) {
+ if (p == OTHER) {
+ return UTF8ToUnicodeText("OTHER", 5, false);
+ } else {
+ DCHECK_LT(p, OTHER);
+ return UTF8ToUnicodeText(gKnownIdentifiers[p].name,
+ gKnownIdentifiers[p].len,
+ false);
+ }
+}
+
+} // namespace
+
+REGISTER_MODULE_INITIALIZER(identifier, {
+ Css::InitializeIdentifierNameLookupTable();
+});
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/identifier.gperf b/trunk/src/third_party/css_parser/src/webutil/css/identifier.gperf
new file mode 100644
index 0000000..2c11fb9
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/identifier.gperf
@@ -0,0 +1,227 @@
+/**
+ * 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.
+ */
+
+%{
+#include "webutil/css/identifier.h"
+
+#include "base/googleinit.h"
+#include "base/logging.h"
+#include "webutil/css/string_util.h"
+
+namespace Css {
+%}
+// This is the set of identifiers.
+struct idents {
+ const char *name;
+ Identifier::Ident id;
+};
+%%
+--goog-unknown--, Identifier::GOOG_UNKNOWN
+inherit, Identifier::INHERIT
+none, Identifier::NONE
+auto, Identifier::AUTO
+normal, Identifier::NORMAL
+visible, Identifier::VISIBLE
+hidden, Identifier::HIDDEN
+always, Identifier::ALWAYS
+avoid, Identifier::AVOID
+medium, Identifier::MEDIUM
+repeat, Identifier::REPEAT
+collapse, Identifier::COLLAPSE
+left, Identifier::LEFT
+center, Identifier::CENTER
+right, Identifier::RIGHT
+top, Identifier::TOP
+bottom, Identifier::BOTTOM
+both, Identifier::BOTH
+scroll, Identifier::SCROLL
+fixed, Identifier::FIXED
+transparent, Identifier::TRANSPARENT
+repeat-x, Identifier::REPEAT_X
+repeat-y, Identifier::REPEAT_Y
+no-repeat, Identifier::NO_REPEAT
+separate, Identifier::SEPARATE
+dotted, Identifier::DOTTED
+dashed, Identifier::DASHED
+solid, Identifier::SOLID
+double, Identifier::DOUBLE
+groove, Identifier::GROOVE
+ridge, Identifier::RIDGE
+inset, Identifier::INSET
+outset, Identifier::OUTSET
+thin, Identifier::THIN
+thick, Identifier::THICK
+open-quote, Identifier::OPEN_QUOTE
+close-quote, Identifier::CLOSE_QUOTE
+no-open-quote, Identifier::NO_OPEN_QUOTE
+no-close-quote, Identifier::NO_CLOSE_QUOTE
+crosshair, Identifier::CROSSHAIR
+default, Identifier::DEFAULT
+pointer, Identifier::POINTER
+move, Identifier::MOVE
+e-resize, Identifier::E_RESIZE
+ne-resize, Identifier::NE_RESIZE
+nw-resize, Identifier::NW_RESIZE
+n-resize, Identifier::N_RESIZE
+se-resize, Identifier::SE_RESIZE
+sw-resize, Identifier::SW_RESIZE
+s-resize, Identifier::S_RESIZE
+w-resize, Identifier::W_RESIZE
+text, Identifier::TEXT
+wait, Identifier::WAIT
+help, Identifier::HELP
+progress, Identifier::PROGRESS
+ltr, Identifier::LTR,
+rtl, Identifier::RTL,
+inline, Identifier::INLINE
+block, Identifier::BLOCK
+list-item, Identifier::LIST_ITEM
+run-in, Identifier::RUN_IN
+inline-block, Identifier::INLINE_BLOCK
+table, Identifier::TABLE
+inline-table, Identifier::INLINE_TABLE
+table-row-group, Identifier::TABLE_ROW_GROUP
+table-header-group, Identifier::TABLE_HEADER_GROUP
+table-footer-group, Identifier::TABLE_FOOTER_GROUP
+table-row, Identifier::TABLE_ROW
+table-column-group, Identifier::TABLE_COLUMN_GROUP
+table-column, Identifier::TABLE_COLUMN
+table-cell, Identifier::TABLE_CELL
+table-caption, Identifier::TABLE_CAPTION
+show, Identifier::SHOW
+hide, Identifier::HIDE
+serif, Identifier::SERIF
+sans-serif, Identifier::SANS_SERIF
+cursive, Identifier::CURSIVE
+fantasy, Identifier::FANTASY
+monospace, Identifier::MONOSPACE
+xx-small, Identifier::XX_SMALL
+x-small, Identifier::X_SMALL
+small, Identifier::SMALL
+large, Identifier::LARGE
+x-large, Identifier::X_LARGE
+xx-large, Identifier::XX_LARGE
+smaller, Identifier::SMALLER
+larger, Identifier::LARGER
+italic, Identifier::ITALIC
+oblique, Identifier::OBLIQUE
+small-caps, Identifier::SMALL_CAPS
+bold, Identifier::BOLD
+bolder, Identifier::BOLDER
+lighter, Identifier::LIGHTER
+caption, Identifier::CAPTION
+icon, Identifier::ICON
+menu, Identifier::MENU
+message-box, Identifier::MESSAGE_BOX
+small-caption, Identifier::SMALL_CAPTION
+status-bar, Identifier::STATUS_BAR
+inside, Identifier::INSIDE
+outside, Identifier::OUTSIDE
+disc, Identifier::DISC
+circle, Identifier::CIRCLE
+square, Identifier::SQUARE
+decimal, Identifier::DECIMAL
+decimal-leading-zero, Identifier::DECIMAL_LEADING_ZERO
+lower-roman, Identifier::LOWER_ROMAN
+upper-roman, Identifier::UPPER_ROMAN
+lower-greek, Identifier::LOWER_GREEK
+lower-latin, Identifier::LOWER_LATIN,
+upper-latin, Identifier::UPPER_LATIN
+armenian, Identifier::ARMENIAN
+georgian, Identifier::GEORGIAN
+lower-alpha, Identifier::LOWER_ALPHA
+upper-alpha, Identifier::UPPER_ALPHA
+invert, Identifier::INVERT
+static, Identifier::STATIC
+relative, Identifier::RELATIVE
+absolute, Identifier::ABSOLUTE
+justify, Identifier::JUSTIFY
+underline, Identifier::UNDERLINE
+overline, Identifier::OVERLINE
+line-through, Identifier::LINE_THROUGH
+blink, Identifier::BLINK
+capitalize, Identifier::CAPITALIZE
+uppercase, Identifier::UPPERCASE
+lowercase, Identifier::LOWERCASE
+embed, Identifier::EMBED
+bidi-override, Identifier::BIDI_OVERRIDE
+baseline, Identifier::BASELINE
+sub, Identifier::SUB
+super, Identifier::SUPER
+text-top, Identifier::TEXT_TOP
+middle, Identifier::MIDDLE
+text-bottom, Identifier::TEXT_BOTTOM
+pre, Identifier::PRE
+nowrap, Identifier::NOWRAP
+pre-wrap, Identifier::PRE_WRAP
+pre-line, Identifier::PRE_LINE
+--goog-initial--, Identifier::GOOG_INITIAL
+--goog-body-color--, Identifier::GOOG_BODY_COLOR
+--goog-body-link-color--, Identifier::GOOG_BODY_LINK_COLOR
+--goog-big--, Identifier::GOOG_BIG
+--goog-small--, Identifier::GOOG_SMALL
+%%
+
+//
+// Constructor.
+//
+
+Identifier::Identifier(const UnicodeText& s) : ident_(IdentFromText(s)) {
+ if (ident_ == OTHER)
+ other_ = LowercaseAscii(s);
+}
+
+//
+// Static methods mapping Ident's to strings
+//
+
+Identifier::Ident Identifier::IdentFromText(const UnicodeText& s) {
+ const idents* a = IdentifierMapper::in_word_set(s.utf8_data(),
+ s.utf8_length());
+ if (a)
+ return a->id;
+ else
+ return OTHER;
+}
+
+static struct {
+ const char* name;
+ int len;
+} gKnownIdentifiers[TOTAL_KEYWORDS];
+
+static void InitializeIdentifierNameLookupTable() {
+ for (int i = 0; i < TOTAL_KEYWORDS; ++i) {
+ gKnownIdentifiers[wordlist[i].id].name = wordlist[i].name;
+ gKnownIdentifiers[wordlist[i].id].len = strlen(wordlist[i].name);
+ }
+}
+
+UnicodeText Identifier::TextFromIdent(Ident p) {
+ if (p == OTHER) {
+ return UTF8ToUnicodeText("OTHER", 5, false);
+ } else {
+ DCHECK_LT(p, OTHER);
+ return UTF8ToUnicodeText(gKnownIdentifiers[p].name,
+ gKnownIdentifiers[p].len,
+ false);
+ }
+}
+
+} // namespace
+
+REGISTER_MODULE_INITIALIZER(identifier, {
+ Css::InitializeIdentifierNameLookupTable();
+});
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/identifier.h b/trunk/src/third_party/css_parser/src/webutil/css/identifier.h
new file mode 100644
index 0000000..f9d8ac0
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/identifier.h
@@ -0,0 +1,151 @@
+/**
+ * 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.
+ */
+
+// Copyright 2007 Google Inc. All Rights Reserved.
+// Author: yian@google.com (Yi-an Huang)
+//
+// Identifier represents the value of a CSS identifier (e.g.,
+// normal, repeat, small, inherit). If we recognize the ident,
+// we store it as an enum. Otherwise, we store the text for the
+// string value of the identifier.
+//
+// This code is based on CSS 2.1.
+
+#ifndef WEBUTIL_CSS_IDENTIFIER_H__
+#define WEBUTIL_CSS_IDENTIFIER_H__
+
+#include "util/utf8/public/unicodetext.h"
+
+namespace Css {
+
+class Identifier {
+ public:
+ enum Ident {
+ // local add
+ // UNKNOWN identifiers. Reserved for internal use.
+ GOOG_UNKNOWN,
+ // common values
+ INHERIT, NONE, AUTO, NORMAL, VISIBLE, HIDDEN, ALWAYS, AVOID, MEDIUM,
+ REPEAT, COLLAPSE, LEFT, CENTER, RIGHT, TOP, BOTTOM, BOTH,
+ SCROLL, FIXED,
+ // background-color
+ TRANSPARENT,
+ // background-repeat
+ REPEAT_X, REPEAT_Y, NO_REPEAT,
+ // border-collapse
+ SEPARATE,
+ // border-style
+ DOTTED, DASHED, SOLID, DOUBLE, GROOVE, RIDGE, INSET, OUTSET,
+ // border-width
+ THIN, THICK,
+ // content
+ OPEN_QUOTE, CLOSE_QUOTE, NO_OPEN_QUOTE, NO_CLOSE_QUOTE,
+ // cursor
+ CROSSHAIR, DEFAULT, POINTER, MOVE, E_RESIZE, NE_RESIZE, NW_RESIZE,
+ N_RESIZE, SE_RESIZE, SW_RESIZE, S_RESIZE, W_RESIZE, TEXT, WAIT, HELP,
+ PROGRESS,
+ // direction,
+ LTR, RTL,
+ // display
+ INLINE, BLOCK, LIST_ITEM, RUN_IN, INLINE_BLOCK, TABLE, INLINE_TABLE,
+ TABLE_ROW_GROUP, TABLE_HEADER_GROUP, TABLE_FOOTER_GROUP, TABLE_ROW,
+ TABLE_COLUMN_GROUP, TABLE_COLUMN, TABLE_CELL, TABLE_CAPTION,
+ // empty-cells
+ SHOW, HIDE,
+ // font-family
+ SERIF, SANS_SERIF, CURSIVE, FANTASY, MONOSPACE,
+ // font-size
+ XX_SMALL, X_SMALL, SMALL, LARGE, X_LARGE, XX_LARGE, SMALLER, LARGER,
+ // font-style
+ ITALIC, OBLIQUE,
+ // font-variant
+ SMALL_CAPS,
+ // font-weight
+ BOLD, BOLDER, LIGHTER,
+ // font
+ CAPTION, ICON, MENU, MESSAGE_BOX, SMALL_CAPTION, STATUS_BAR,
+ // list-style-position
+ INSIDE, OUTSIDE,
+ // list-style-type
+ DISC, CIRCLE, SQUARE, DECIMAL, DECIMAL_LEADING_ZERO, LOWER_ROMAN,
+ UPPER_ROMAN, LOWER_GREEK, LOWER_LATIN, UPPER_LATIN, ARMENIAN, GEORGIAN,
+ LOWER_ALPHA, UPPER_ALPHA,
+ // outline-color
+ INVERT,
+ // position
+ STATIC, RELATIVE, ABSOLUTE,
+ // text-align
+ JUSTIFY,
+ // text-decoration
+ UNDERLINE, OVERLINE, LINE_THROUGH, BLINK,
+ // text-transform
+ CAPITALIZE, UPPERCASE, LOWERCASE,
+ // unicode-bidi
+ EMBED, BIDI_OVERRIDE,
+ // vertical-align
+ BASELINE, SUB, SUPER, TEXT_TOP, MIDDLE, TEXT_BOTTOM,
+ // white-space
+ PRE, NOWRAP, PRE_WRAP, PRE_LINE,
+ // google specific. Internal use only.
+ // For property with context-dependent initial values. such as border-color
+ // and text-align.
+ GOOG_INITIAL,
+ // color specified by <body text=color>
+ GOOG_BODY_COLOR,
+ // color specified by <body link=color>
+ GOOG_BODY_LINK_COLOR,
+ // identifier reserved for font-size in <big> and <small>. IE has special
+ // semantics for them.
+ GOOG_BIG, GOOG_SMALL,
+ OTHER
+ };
+
+ // Constructor.
+ Identifier() : ident_(GOOG_UNKNOWN) { }
+ explicit Identifier(const UnicodeText& s);
+ explicit Identifier(Ident ident) : ident_(ident) { }
+
+ // Accessors.
+ //
+ // ident() returns the ident enum -- OTHER if unrecognized.
+ Ident ident() const { return ident_; }
+
+ // ident_text() returns the identifier as a string.
+ UnicodeText ident_text() const {
+ if (ident_ == OTHER)
+ return other_;
+ else
+ return TextFromIdent(ident_);
+ }
+
+ // Static methods mapping between Ident and strings:
+ //
+ // Given the text of a CSS identifier, IdentFromText returns the
+ // corresponding enum. If no such identifier is found, IdentromText returns
+ // OTHER.
+ static Ident IdentFromText(const UnicodeText& s);
+ // Given a Ident, returns its string representation. If u is OTHER, we
+ // return "OTHER", but this may not be what you want.
+ static UnicodeText TextFromIdent(Ident p);
+
+ private:
+ Ident ident_;
+ UnicodeText other_; // valid if ident_ is OTHER.
+};
+
+} // namespace
+
+#endif // WEBUTIL_CSS_IDENTIFIER_H__
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/identifier_test.cc b/trunk/src/third_party/css_parser/src/webutil/css/identifier_test.cc
new file mode 100644
index 0000000..fd2da6a
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/identifier_test.cc
@@ -0,0 +1,66 @@
+/**
+ * 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.
+ */
+
+// Copyright 2007 Google Inc. All Rights Reserved.
+// Author: yian@google.com (Yi-an Huang)
+
+#include "webutil/css/identifier.h"
+
+#include <string>
+
+#include "testing/base/public/googletest.h"
+#include "testing/base/public/gunit.h"
+#include "webutil/css/string.h"
+
+namespace {
+
+class IdentifierTest : public testing::Test {
+};
+
+TEST_F(IdentifierTest, IdentFromText) {
+ UnicodeText s = UTF8ToUnicodeText(string("Inherit"), true);
+ EXPECT_EQ(Css::Identifier::INHERIT, Css::Identifier::IdentFromText(s));
+ EXPECT_EQ(Css::Identifier::INHERIT, Css::Identifier(s).ident());
+ EXPECT_EQ("inherit", UnicodeTextToUTF8(Css::Identifier(s).ident_text()));
+}
+
+TEST_F(IdentifierTest, TextFromIdent) {
+ EXPECT_EQ("inherit",
+ UnicodeTextToUTF8(Css::Identifier::TextFromIdent(
+ Css::Identifier::INHERIT)));
+ EXPECT_EQ("OTHER",
+ UnicodeTextToUTF8(Css::Identifier::TextFromIdent(
+ Css::Identifier::OTHER)));
+}
+
+TEST_F(IdentifierTest, UnicodeText) {
+ UnicodeText s = UTF8ToUnicodeText(string("宋体"), true);
+ EXPECT_EQ(Css::Identifier::OTHER, Css::Identifier::IdentFromText(s));
+ Css::Identifier id(s);
+ EXPECT_EQ(Css::Identifier::OTHER, id.ident());
+ EXPECT_EQ("宋体", UnicodeTextToUTF8(id.ident_text()));
+}
+
+TEST_F(IdentifierTest, Inverses) {
+ for (int i = 0; i < Css::Identifier::OTHER; ++i) {
+ UnicodeText s = Css::Identifier::TextFromIdent(
+ static_cast<Css::Identifier::Ident>(i));
+ EXPECT_EQ(static_cast<Css::Identifier::Ident>(i),
+ Css::Identifier::IdentFromText(s));
+ }
+}
+
+} // namespace
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/parse_arg.cc b/trunk/src/third_party/css_parser/src/webutil/css/parse_arg.cc
new file mode 100644
index 0000000..b1110e6
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/parse_arg.cc
@@ -0,0 +1,40 @@
+/**
+ * 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 <stdio.h>
+#include <stdlib.h>
+
+#include <string>
+
+#include "webutil/css/parser.h"
+
+int main(int argc, char** argv) {
+ if (argc > 1) {
+ std::string text(argv[1]);
+
+ printf("Parsing:\n%s\n\n", text.c_str());
+ Css::Parser parser(text);
+ Css::Stylesheet* stylesheet = parser.ParseStylesheet();
+
+ printf("Stylesheet:\n%s\n", stylesheet->ToString().c_str());
+
+ delete stylesheet;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/parser.cc b/trunk/src/third_party/css_parser/src/webutil/css/parser.cc
new file mode 100644
index 0000000..d92b7a0
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/parser.cc
@@ -0,0 +1,1802 @@
+/**
+ * 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.
+ */
+
+// Copyright 2006 Google Inc. All Rights Reserved.
+// Author: dpeng@google.com (Daniel Peng)
+
+#include "webutil/css/parser.h"
+
+#include <ctype.h> // isascii
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "strings/memutil.h"
+#include "strings/strutil.h"
+#include "third_party/utf/utf.h"
+#include "util/gtl/stl_util-inl.h"
+#include "webutil/css/string.h"
+#include "webutil/css/string_util.h"
+#include "webutil/css/util.h"
+#include "webutil/css/value.h"
+#include "webutil/css/valuevalidator.h"
+
+
+namespace Css {
+
+const uint64 Parser::kNoError;
+const uint64 Parser::kUtf8Error;
+const uint64 Parser::kDeclarationError;
+const uint64 Parser::kSelectorError;
+const uint64 Parser::kFunctionError;
+const uint64 Parser::kMediaError;
+
+
+// Using isascii with signed chars is unfortunately undefined.
+static inline bool IsAscii(char c) {
+ return isascii(static_cast<unsigned char>(c));
+}
+
+class Tracer { // in opt mode, do nothing.
+ public:
+ Tracer(const char* name, const char** in) { }
+};
+
+
+// ****************
+// constructors
+// ****************
+
+Parser::Parser(const char* utf8text, const char* textend)
+ : in_(utf8text),
+ end_(textend),
+ quirks_mode_(true),
+ errors_seen_mask_(kNoError) {
+}
+
+Parser::Parser(const char* utf8text)
+ : in_(utf8text),
+ end_(utf8text + strlen(utf8text)),
+ quirks_mode_(true),
+ errors_seen_mask_(kNoError) {
+}
+
+Parser::Parser(StringPiece s)
+ : in_(s.begin()),
+ end_(s.end()),
+ quirks_mode_(true),
+ errors_seen_mask_(kNoError) {
+}
+
+// ****************
+// Helper functions
+// ****************
+
+// is c a space? Only the characters "space" (Unicode code 32), "tab"
+// (9), "line feed" (10), "carriage return" (13), and "form feed" (12)
+// can occur in whitespace. Other space-like characters, such as
+// "em-space" (8195) and "ideographic space" (12288), are never part
+// of whitespace.
+// http://www.w3.org/TR/REC-CSS2/syndata.html#whitespace
+static bool IsSpace(char c) {
+ switch (c) {
+ case ' ': case '\t': case '\r': case '\n': case '\f':
+ return true;
+ default:
+ return false;
+ }
+}
+
+// If the character c is a hex digit, DeHex returns the number it
+// represents ('0' => 0, 'A' => 10, 'F' => 15). Otherwise, DeHex
+// returns -1.
+static int DeHex(char c) {
+ if (c >= '0' && c <= '9') {
+ return c - '0';
+ } else if (c >= 'A' && c <= 'F') {
+ return (c - 'A') + 10;
+ } else if (c >= 'a' && c <= 'f') {
+ return (c - 'a') + 10;
+ } else {
+ return -1;
+ }
+}
+
+// ****************
+// Recursive-descent functions.
+//
+// The best documentation for these is in cssparser.h.
+//
+// ****************
+
+// consume whitespace and comments.
+void Parser::SkipSpace() {
+ Tracer trace(__func__, &in_);
+ while (in_ < end_) {
+ if (IsSpace(*in_))
+ in_++;
+ else if (in_ + 1 < end_ && in_[0] == '/' && in_[1] == '*')
+ SkipComment();
+ else
+ return;
+ }
+}
+
+// consume comment /* aoeuaoe */
+void Parser::SkipComment() {
+ DCHECK(in_ + 2 <= end_ && in_[0] == '/' && in_[1] == '*');
+ in_ += 2; // skip the /*
+ while (in_ + 1 < end_) {
+ if (in_[0] == '*' && in_[1] == '/') {
+ in_ += 2;
+ return;
+ } else {
+ in_++;
+ }
+ }
+ in_ = end_;
+}
+
+// skips until delim is seen or end-of-stream. returns if delim is actually
+// seen.
+bool Parser::SkipPastDelimiter(char delim) {
+ SkipSpace();
+ while (in_ < end_ && *in_ != delim) {
+ ++in_;
+ SkipSpace();
+ }
+
+ if (Done()) return false;
+ ++in_;
+ return true;
+}
+
+// returns true if there might be a token to read
+bool Parser::SkipToNextToken() {
+ Tracer trace(__func__, &in_);
+
+ SkipSpace();
+ while (in_ < end_) {
+ switch (*in_) {
+ case '{':
+ ParseBlock(); // ignore
+ break;
+ case '@':
+ in_++;
+ ParseIdent(); // ignore
+ break;
+ case ';': case '}':
+ case '!':
+ return false;
+ default:
+ return true;
+ }
+ SkipSpace();
+ }
+ return false;
+}
+
+// In CSS2, identifiers (including element names, classes, and IDs in
+// selectors) can contain only the characters [A-Za-z0-9] and ISO
+// 10646 characters 161 and higher, plus the hyphen (-); they cannot
+// start with a hyphen or a digit. They can also contain escaped
+// characters and any ISO 10646 character as a numeric code (see next
+// item). For instance, the identifier "B&W?" may be written as
+// "B\&W\?" or "B\26 W\3F".
+//
+// We're a little more forgiving than the standard and permit hyphens
+// and digits to start identifiers.
+//
+// FIXME(yian): actually, IE is more forgiving than Firefox in using a class
+// selector starting with digits.
+//
+// http://www.w3.org/TR/REC-CSS2/syndata.html#value-def-identifier
+static bool StartsIdent(char c) {
+ return ((c >= 'A' && c <= 'Z')
+ || (c >= 'a' && c <= 'z')
+ || (c >= '0' && c <= '9')
+ || c == '-' || c == '_'
+ || !IsAscii(c));
+}
+
+UnicodeText Parser::ParseIdent() {
+ Tracer trace(__func__, &in_);
+ UnicodeText s;
+ while (in_ < end_) {
+ if ((*in_ >= 'A' && *in_ <= 'Z')
+ || (*in_ >= 'a' && *in_ <= 'z')
+ || (*in_ >= '0' && *in_ <= '9')
+ || *in_ == '-' || *in_ == '_') {
+ s.push_back(*in_);
+ in_++;
+ } else if (!IsAscii(*in_)) {
+ Rune rune;
+ int len = charntorune(&rune, in_, end_-in_);
+ if (len && rune != Runeerror) {
+ if (rune >= 161) {
+ s.push_back(rune);
+ in_ += len;
+ } else { // characters 128-160 can't be in identifiers.
+ return s;
+ }
+ } else { // Encoding error. Be a little forgiving.
+ errors_seen_mask_ |= kUtf8Error;
+ in_++;
+ }
+ } else if (*in_ == '\\') {
+ s.push_back(ParseEscape());
+ } else {
+ return s;
+ }
+ }
+ return s;
+}
+
+// Returns the codepoint for the current escape.
+// \abcdef => codepoint 0xabcdef. also consumes whitespace afterwards.
+// \(UTF8-encoded unicode character) => codepoint for that character
+char32 Parser::ParseEscape() {
+ SkipSpace();
+ DCHECK_LT(in_, end_);
+ DCHECK_EQ(*in_, '\\');
+ in_++;
+ if (Done()) return static_cast<char32>('\\');
+
+ int dehexed = DeHex(*in_);
+ if (dehexed == -1) {
+ Rune rune;
+ int len = charntorune(&rune, in_, end_-in_);
+ if (len && rune != Runeerror) {
+ in_ += len;
+ } else {
+ errors_seen_mask_ |= kUtf8Error;
+ in_++;
+ }
+ return rune;
+ } else {
+ char32 codepoint = 0;
+ for (int count = 0; count < 6 && in_ < end_; count++) {
+ dehexed = DeHex(*in_);
+ if (dehexed == -1)
+ break;
+ in_++;
+ codepoint = codepoint << 4 | dehexed;
+ }
+ if (end_ - in_ >= 2 && memcmp(in_, "\r\n", 2) == 0)
+ in_ += 2;
+ else if (IsSpace(*in_))
+ in_++;
+ return codepoint;
+ }
+}
+
+// Starts at delim.
+template<char delim>
+UnicodeText Parser::ParseString() {
+ Tracer trace(__func__, &in_);
+
+ SkipSpace();
+ DCHECK_LT(in_, end_);
+ DCHECK_EQ(*in_, delim);
+ in_++;
+ if (Done()) return UnicodeText();
+
+ UnicodeText s;
+ while (in_ < end_) {
+ switch (*in_) {
+ case delim:
+ in_++;
+ return s;
+ case '\n':
+ return s;
+ case '\\':
+ if (in_ + 1 < end_ && in_[1] == '\n') {
+ in_ += 2;
+ } else {
+ s.push_back(ParseEscape());
+ }
+ break;
+ default:
+ if (!IsAscii(*in_)) {
+ Rune rune;
+ int len = charntorune(&rune, in_, end_-in_);
+ if (len && rune != Runeerror) {
+ s.push_back(rune);
+ in_ += len;
+ } else {
+ errors_seen_mask_ |= kUtf8Error;
+ in_++;
+ }
+ } else {
+ s.push_back(*in_);
+ in_++;
+ }
+ break;
+ }
+ }
+ return s;
+}
+
+// parse ident or 'string'
+UnicodeText Parser::ParseStringOrIdent() {
+ Tracer trace(__func__, &in_);
+
+ SkipSpace();
+ if (Done()) return UnicodeText();
+ DCHECK_LT(in_, end_);
+
+ if (*in_ == '\'') {
+ return ParseString<'\''>();
+ } else if (*in_ == '"') {
+ return ParseString<'"'>();
+ } else {
+ return ParseIdent();
+ }
+}
+
+// Parse a CSS number, including unit or percent sign.
+Value* Parser::ParseNumber() {
+ Tracer trace(__func__, &in_);
+
+ SkipSpace();
+ if (Done()) return NULL;
+ DCHECK_LT(in_, end_);
+
+ const char* begin = in_;
+ if (in_ < end_ && (*in_ == '-' || *in_ == '+')) // sign
+ in_++;
+ while (in_ < end_ && isdigit(*in_)) {
+ in_++;
+ }
+ if (*in_ == '.') {
+ in_++;
+
+ while (in_ < end_ && isdigit(*in_)) {
+ in_++;
+ }
+ }
+ double num = 0;
+ if (in_ == begin || !ParseDouble(begin, in_ - begin, &num)) {
+ return NULL;
+ }
+ if (*in_ == '%') {
+ in_++;
+ return new Value(num, Value::PERCENT);
+ } else if (StartsIdent(*in_)) {
+ return new Value(num, ParseIdent());
+ } else {
+ return new Value(num, Value::NO_UNIT);
+ }
+}
+
+HtmlColor Parser::ParseColor() {
+ Tracer trace(__func__, &in_);
+
+ SkipSpace();
+ if (Done()) return HtmlColor("", 0);
+ DCHECK_LT(in_, end_);
+
+ unsigned char hexdigits[6] = {0};
+ int dehexed;
+ int i = 0;
+
+ const char* oldin = in_;
+
+ // To further mess things up, IE also accepts string values happily.
+ if (*in_ == '"' || *in_ == '\'') {
+ in_++;
+ if (Done()) return HtmlColor("", 0);
+ }
+
+ bool rgb_valid = quirks_mode_ || *in_ == '#';
+
+ if (*in_ == '#') in_++;
+
+ while (in_ < end_ && i < 6 && (dehexed = DeHex(*in_)) != -1) {
+ hexdigits[i] = static_cast<unsigned char>(dehexed);
+ i++;
+ in_++;
+ }
+
+ // close strings. Assume a named color if there are trailing characters
+ if (*oldin == '"' || *oldin == '\'') {
+ if (Done() || *in_ != *oldin) // no need to touch in_, will redo anyway.
+ i = 0;
+ else
+ in_++;
+ }
+
+ // Normally, ParseXXX() routines stop wherever it cannot be consumed and
+ // doesn't check whether the next character is valid. which should be caught
+ // by the next ParseXXX() routine. But ParseColor may be called to test
+ // whether a numerical value can be used as color, and fail over to a normal
+ // ParseAny(). We need to do an immediate check here to guarantine a valid
+ // non-color number (such as 100%) will not be accepted as a color.
+ //
+ // We also do not want rrggbb (without #) to be accepted in non-quirks mode,
+ // but HtmlColor will happily accept it anyway. Do a sanity check here.
+ if (i == 3 || i == 6) {
+ if (!rgb_valid ||
+ (!Done() && (*in_ == '%' || StartsIdent(*in_))))
+ return HtmlColor("", 0);
+ }
+
+ if (i == 3) {
+ return HtmlColor(hexdigits[0] | hexdigits[0] << 4,
+ hexdigits[1] | hexdigits[1] << 4,
+ hexdigits[2] | hexdigits[2] << 4);
+ } else if (i == 6) {
+ return HtmlColor(hexdigits[1] | hexdigits[0] << 4,
+ hexdigits[3] | hexdigits[2] << 4,
+ hexdigits[5] | hexdigits[4] << 4);
+ } else {
+ in_ = oldin;
+
+ // A named color must not begin with #, but we need to parse it anyway and
+ // report failure later.
+ bool name_valid = true;
+ if (*in_ == '#') {
+ in_++;
+ name_valid = false;
+ }
+
+ string ident = UnicodeTextToUTF8(ParseStringOrIdent());
+ HtmlColor val("", 0);
+ if (name_valid) {
+ val.SetValueFromName(ident.c_str());
+ if (!val.IsDefined())
+ Util::GetSystemColor(ident, &val);
+ }
+ return val;
+ }
+}
+
+// Returns the 0-255 RGB value corresponding to Value v. Only
+// unusual thing is percentages are interpreted as percentages of
+// 255.0.
+unsigned char Parser::ValueToRGB(Value* v) {
+ int toret = 0;
+ if (v == NULL) {
+ toret = 0;
+ } else if (v->GetLexicalUnitType() == Value::NUMBER) {
+ if (v->GetDimension() == Value::PERCENT) {
+ toret = static_cast<int>(v->GetFloatValue()/100.0 * 255.0);
+ } else {
+ toret = v->GetIntegerValue();
+ }
+ } else {
+ toret = 0;
+ }
+
+ // RGB values outside the device gamut should be clipped according to spec.
+ if (toret > 255)
+ toret = 255;
+ if (toret < 0)
+ toret = 0;
+ return static_cast<unsigned char>(toret);
+}
+
+// parse RGB color 25, 32, 12 or 25%, 1%, 7%.
+// stops without consuming final right-paren
+Value* Parser::ParseRgbColor() {
+ Tracer trace(__func__, &in_);
+
+ SkipSpace();
+ if (Done()) return NULL;
+ DCHECK_LT(in_, end_);
+
+ unsigned char rgb[3];
+
+ for (int i = 0; i < 3; i++) {
+ scoped_ptr<Value> val(ParseNumber());
+ if (!val.get() || val->GetLexicalUnitType() != Value::NUMBER ||
+ (val->GetDimension() != Value::PERCENT &&
+ val->GetDimension() != Value::NO_UNIT))
+ break;
+ rgb[i] = ValueToRGB(val.get());
+ SkipSpace();
+ // Make sure the correct syntax is followed.
+ if (Done() || (*in_ != ',' && *in_ != ')') || (*in_ == ')' && i != 2))
+ break;
+
+ if (*in_ == ')')
+ return new Value(HtmlColor(rgb[0], rgb[1], rgb[2]));
+ in_++; // ','
+ }
+
+ return NULL;
+}
+
+// parse url yellow.png or 'yellow.png'
+// (doesn't consume subsequent right-paren).
+Value* Parser::ParseUrl() {
+ Tracer trace(__func__, &in_);
+
+ SkipSpace();
+ if (Done()) return NULL;
+ DCHECK_LT(in_, end_);
+
+ UnicodeText s;
+ if (*in_ == '\'') {
+ s = ParseString<'\''>();
+ } else if (*in_ == '"') {
+ s = ParseString<'"'>();
+ } else {
+ while (in_ < end_) {
+ if (IsSpace(*in_) || *in_ == ')') {
+ break;
+ } else if (*in_ == '\\') {
+ s.push_back(ParseEscape());
+ } else if (!IsAscii(*in_)) {
+ Rune rune;
+ int len = charntorune(&rune, in_, end_-in_);
+ if (len && rune != Runeerror) {
+ s.push_back(rune);
+ in_ += len;
+ } else {
+ errors_seen_mask_ |= kUtf8Error;
+ in_++;
+ }
+ } else {
+ s.push_back(*in_);
+ in_++;
+ }
+ }
+ }
+ SkipSpace();
+ if (!Done() && *in_ == ')')
+ return new Value(Value::URI, s);
+
+ return NULL;
+}
+
+// parse rect(top, right, bottom, left) without consuming final right-paren.
+// Spaces are allowed as delimiters here for historical reasons.
+Value* Parser::ParseRect() {
+ scoped_ptr<Values> params(new Values);
+
+ SkipSpace();
+ if (Done()) return NULL;
+ DCHECK_LT(in_, end_);
+
+ // Never parse after the final right-paren!
+ if (*in_ == ')') return NULL;
+
+ for (int i = 0; i < 4; i++) {
+ scoped_ptr<Value> val(ParseAny());
+ if (!val.get())
+ break;
+ params->push_back(val.release());
+ SkipSpace();
+ // Make sure the correct syntax is followed.
+ if (Done() || (*in_ == ')' && i != 3))
+ break;
+
+ if (*in_ == ')')
+ return new Value(Value::RECT, params.release());
+ else if (*in_ == ',')
+ in_++;
+ }
+
+ return NULL;
+}
+
+Value* Parser::ParseAnyExpectingColor() {
+ Tracer trace(__func__, &in_);
+ Value* toret = NULL;
+
+ SkipSpace();
+ if (Done()) return NULL;
+ DCHECK_LT(in_, end_);
+
+ const char* oldin = in_;
+ HtmlColor c = ParseColor();
+ if (c.IsDefined()) {
+ toret = new Value(c);
+ } else {
+ in_ = oldin; // no valid color. rollback.
+ toret = ParseAny();
+ }
+ return toret;
+}
+
+// Parses a CSS value. Could be just about anything.
+Value* Parser::ParseAny() {
+ Tracer trace(__func__, &in_);
+ Value* toret = NULL;
+
+ SkipSpace();
+ if (Done()) return NULL;
+ DCHECK_LT(in_, end_);
+
+ const char* oldin = in_;
+ switch (*in_) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '.':
+ toret = ParseNumber();
+ break;
+ case '(': case '[': {
+ char delim = *in_ == '(' ? ')' : ']';
+ SkipPastDelimiter(delim);
+ toret = NULL; // we don't understand this construct.
+ break;
+ }
+ case '"':
+ toret = new Value(Value::STRING, ParseString<'"'>());
+ break;
+ case '\'':
+ toret = new Value(Value::STRING, ParseString<'\''>());
+ break;
+ case '#': {
+ HtmlColor color = ParseColor();
+ if (color.IsDefined())
+ toret = new Value(color);
+ else
+ toret = NULL;
+ break;
+ }
+ case '+':
+ toret = ParseNumber();
+ break;
+ case '-':
+ // ambiguity between a negative number and an identifier starting with -.
+ if (in_ < end_ - 1 &&
+ ((*(in_ + 1) >= '0' && *(in_ + 1) <= '9') || *(in_ + 1) == '.')) {
+ toret = ParseNumber();
+ break;
+ }
+ // fail through
+ default: {
+ UnicodeText id = ParseIdent();
+ if (id.empty()) {
+ toret = NULL;
+ } else if (*in_ == '(') {
+ in_++;
+ if (id.utf8_length() == 3
+ && memcasecmp("url", id.utf8_data(), 3) == 0) {
+ toret = ParseUrl();
+ } else if (id.utf8_length() == 7
+ && memcasecmp("counter", id.utf8_data(), 7) == 0) {
+ // TODO(yian): parse COUNTER parameters
+ toret = new Value(Value::COUNTER, new Values());
+ } else if (id.utf8_length() == 8
+ && memcasecmp("counters", id.utf8_data(), 8) == 0) {
+ // TODO(yian): parse COUNTERS parameters
+ toret = new Value(Value::COUNTER, new Values());
+ } else if (id.utf8_length() == 3
+ && memcasecmp("rgb", id.utf8_data(), 3) == 0) {
+ toret = ParseRgbColor();
+ } else if (id.utf8_length() == 4
+ && memcasecmp("rect", id.utf8_data(), 4) == 0) {
+ toret = ParseRect();
+ } else {
+ errors_seen_mask_ |= kFunctionError;
+ // TODO(yian): parse FUNCTION parameters
+ toret = new Value(id, new Values());
+ }
+ SkipPastDelimiter(')');
+ } else {
+ toret = new Value(Identifier(id));
+ }
+ break;
+ }
+ }
+ // Deadlock prevention: always make progress even if nothing can be parsed.
+ if (toret == NULL && in_ == oldin) ++in_;
+ return toret;
+}
+
+static bool IsPropExpectingColor(Property::Prop prop) {
+ switch (prop) {
+ case Property::BORDER_COLOR:
+ case Property::BORDER_TOP_COLOR:
+ case Property::BORDER_RIGHT_COLOR:
+ case Property::BORDER_BOTTOM_COLOR:
+ case Property::BORDER_LEFT_COLOR:
+ case Property::BORDER:
+ case Property::BORDER_TOP:
+ case Property::BORDER_RIGHT:
+ case Property::BORDER_BOTTOM:
+ case Property::BORDER_LEFT:
+ case Property::BACKGROUND_COLOR:
+ case Property::BACKGROUND:
+ case Property::COLOR:
+ case Property::OUTLINE_COLOR:
+ case Property::OUTLINE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+// Parse values like "12pt Arial"
+// If you make any change to this function, please also update
+// ParseBackground, ParseFont and ParseFontFamily accordingly.
+Values* Parser::ParseValues(Property::Prop prop) {
+ Tracer trace(__func__, &in_);
+
+ SkipSpace();
+ if (Done()) return new Values();
+ DCHECK_LT(in_, end_);
+
+ // If expecting_color is true, color values are expected.
+ bool expecting_color = IsPropExpectingColor(prop);
+
+ scoped_ptr<Values> values(new Values);
+ while (SkipToNextToken()) {
+ scoped_ptr<Value> v(expecting_color ?
+ ParseAnyExpectingColor() :
+ ParseAny());
+ if (v.get() && ValueValidator::Get()->IsValidValue(prop, *v, quirks_mode_))
+ values->push_back(v.release());
+ else
+ return NULL;
+ }
+ return values.release();
+}
+
+// Parse background. It is a shortcut property for individual background
+// properties.
+//
+// The output is a tuple in the following order:
+// "background-color background-image background-repeat background-attachment
+// background-position-x background-position-y"
+// or NULL if invalid
+//
+// The x-y position parsing is somewhat complicated. The following spec is from
+// CSS 2.1.
+// http://www.w3.org/TR/CSS21/colors.html#propdef-background-position
+//
+// "If a background image has been specified, this property specifies its
+// initial position. If only one value is specified, the second value is
+// assumed to be 'center'. If at least one value is not a keyword, then the
+// first value represents the horizontal position and the second represents the
+// vertical position. Negative <percentage> and <length> values are allowed.
+// <percentage> ...
+// <length> ...
+// top ...
+// right ...
+// bottom ...
+// left ...
+// center ..."
+//
+// In addition, we have some IE specific behavior:
+// 1) you can specifiy more than two values, but once both x and y have
+// specified values, further values will be discarded.
+// 2) if y is not specified and x has seen two or more values, the last value
+// counts. The same for y.
+// 3) [length, left/right] is valid and the length becomes a value for y.
+// [top/bottom, length] is also valid and the length becomes a value for x.
+// If you make any change to this function, please also update ParseValues,
+// ParseFont and ParseFontFamily if applicable.
+bool Parser::ExpandBackground(const Declaration& original_declaration,
+ Declarations* new_declarations) {
+ const Values* vals = original_declaration.values();
+ bool important = original_declaration.IsImportant();
+ DCHECK(vals != NULL);
+
+ Value background_color(Identifier::TRANSPARENT);
+ Value background_image(Identifier::NONE);
+ Value background_repeat(Identifier::REPEAT);
+ Value background_attachment(Identifier::SCROLL);
+ scoped_ptr<Value> background_position_x;
+ scoped_ptr<Value> background_position_y;
+
+ bool is_first = true;
+
+ // The following flag is used to implement IE quirks #3. When the first
+ // positional value is a length or CENTER, it is stored in
+ // background-position-x, but the value may actually be used as
+ // background-position-y if a keyword LEFT or RIGHT appears later.
+ bool first_is_ambiguous = false; // Value::NUMBER or Identifier::CENTER
+
+ for (Values::const_iterator iter = vals->begin(); iter != vals->end();
+ ++iter) {
+ const Value* val = *iter;
+
+ // Firefox allows only one value to be set per property, IE need not.
+ switch (val->GetLexicalUnitType()) {
+ case Value::COLOR:
+ // background_color, etc. take ownership of val. We will clear vals
+ // at the end to make sure we don't have double ownership.
+ background_color = *val;
+ break;
+ case Value::URI:
+ background_image = *val;
+ break;
+ case Value::NUMBER:
+ if (!background_position_x.get()) {
+ background_position_x.reset(new Value(*val));
+ first_is_ambiguous = true;
+ } else if (!background_position_y.get()) {
+ background_position_y.reset(new Value(*val));
+ }
+ break;
+ case Value::IDENT:
+ switch (val->GetIdentifier().ident()) {
+ case Identifier::CENTER:
+ if (!background_position_x.get()) {
+ background_position_x.reset(new Value(*val));
+ first_is_ambiguous = true;
+ } else if (!background_position_y.get()) {
+ background_position_y.reset(new Value(*val));
+ }
+ break;
+ case Identifier::LEFT:
+ case Identifier::RIGHT:
+ // This is IE-specific behavior.
+ if (!background_position_x.get() || !background_position_y.get()) {
+ if (background_position_x.get() && first_is_ambiguous)
+ background_position_y.reset(background_position_x.release());
+ background_position_x.reset(new Value(*val));
+ first_is_ambiguous = false;
+ }
+ break;
+ case Identifier::TOP:
+ case Identifier::BOTTOM:
+ if (!background_position_x.get() || !background_position_y.get())
+ background_position_y.reset(new Value(*val));
+ break;
+ case Identifier::REPEAT:
+ case Identifier::REPEAT_X:
+ case Identifier::REPEAT_Y:
+ case Identifier::NO_REPEAT:
+ background_repeat = *val;
+ break;
+ case Identifier::SCROLL:
+ case Identifier::FIXED:
+ background_attachment = *val;
+ break;
+ case Identifier::TRANSPARENT:
+ background_color = *val;
+ break;
+ case Identifier::NONE:
+ background_image = *val;
+ break;
+ case Identifier::INHERIT:
+ // Inherit must be the one and only value.
+ if (!(iter == vals->begin() && vals->size() == 1))
+ return false;
+ // We copy the inherit value into each background_* value.
+ background_color = *val;
+ background_image = *val;
+ background_repeat = *val;
+ background_attachment = *val;
+ background_position_x.reset(new Value(*val));
+ background_position_y.reset(new Value(*val));
+ break;
+ default:
+ return false;
+ }
+ break;
+ default:
+ return false;
+ }
+ is_first = false;
+ }
+ if (is_first) return false;
+
+ new_declarations->push_back(new Declaration(Property::BACKGROUND_COLOR,
+ background_color,
+ important));
+ new_declarations->push_back(new Declaration(Property::BACKGROUND_IMAGE,
+ background_image,
+ important));
+ new_declarations->push_back(new Declaration(Property::BACKGROUND_REPEAT,
+ background_repeat,
+ important));
+ new_declarations->push_back(new Declaration(Property::BACKGROUND_ATTACHMENT,
+ background_attachment,
+ important));
+
+ // Fix up x and y position.
+ if (!background_position_x.get() && !background_position_y.get()) {
+ background_position_x.reset(new Value(0, Value::PERCENT));
+ background_position_y.reset(new Value(0, Value::PERCENT));
+ } else if (!background_position_x.get()) {
+ background_position_x.reset(new Value(50, Value::PERCENT));
+ } else if (!background_position_y.get()) {
+ background_position_y.reset(new Value(50, Value::PERCENT));
+ }
+ new_declarations->push_back(new Declaration(Property::BACKGROUND_POSITION_X,
+ *background_position_x,
+ important));
+ new_declarations->push_back(new Declaration(Property::BACKGROUND_POSITION_Y,
+ *background_position_y,
+ important));
+
+ return true;
+}
+
+// Parses font-family. It is special in that it uses commas as delimiters. It
+// also concatenates adjacent idents into one name. Strings can be also used
+// and they are separate from others even without commas.
+// E.g, Courier New, Sans -> "Courier New", "Sans"
+// Arial "MS Times" monospace -> "Arial", "MS Times", "monospace".
+// If you make any change to this function, please also update ParseValues,
+// ParseBackground and ParseFont if applicable.
+bool Parser::ParseFontFamily(Values* values) {
+ Tracer trace(__func__, &in_);
+
+ SkipSpace();
+ if (Done()) return true;
+ DCHECK_LT(in_, end_);
+
+ UnicodeText family;
+ while (SkipToNextToken()) {
+ if (*in_ == ',') {
+ if (!family.empty()) {
+ values->push_back(new Value(Identifier(family)));
+ family.clear();
+ }
+ in_++;
+ } else {
+ scoped_ptr<Value> v(ParseAny());
+ if (!v.get()) return false;
+ switch (v->GetLexicalUnitType()) {
+ case Value::STRING:
+ if (!family.empty()) {
+ values->push_back(new Value(Identifier(family)));
+ family.clear();
+ }
+ values->push_back(v.release());
+ break;
+ case Value::IDENT:
+ if (!family.empty())
+ family.push_back(static_cast<char32>(' '));
+ family.append(v->GetIdentifierText());
+ break;
+ default:
+ return false;
+ }
+ }
+ }
+ if (!family.empty())
+ values->push_back(new Value(Identifier(family)));
+ return true;
+}
+
+// Parse font. It is special in that it uses a special format (see spec):
+// [ [ <'font-style'> || <'font-variant'> || <'font-weight'> ]?
+// <'font-size'> [ / <'line-height'> ]? <'font-family'> ]
+// | caption | icon | menu | message-box | small-caption | status-bar | inherit
+//
+// The output is a tuple in the following order:
+// "font-style font-variant font-weight font-size line-height font-family*"
+// or NULL if invalid
+// IE pecularity: font-family is optional (hence the *).
+// If you make any change to this function, please also update ParseValues,
+// ParseBackground and ParseFontFamily if applicable.
+Values* Parser::ParseFont() {
+ Tracer trace(__func__, &in_);
+
+ SkipSpace();
+ if (Done()) return NULL;
+ DCHECK_LT(in_, end_);
+
+ scoped_ptr<Values> values(new Values);
+
+ if (!SkipToNextToken())
+ return NULL;
+
+ scoped_ptr<Value> v(ParseAny());
+ if (!v.get()) return NULL;
+
+ // For special one-valued font: notations, just return with that one value.
+ // Note: these can be expanded by ExpandShorthandProperties
+ if (v->GetLexicalUnitType() == Value::IDENT) {
+ switch (v->GetIdentifier().ident()) {
+ case Identifier::CAPTION:
+ case Identifier::ICON:
+ case Identifier::MENU:
+ case Identifier::MESSAGE_BOX:
+ case Identifier::SMALL_CAPTION:
+ case Identifier::STATUS_BAR:
+ case Identifier::INHERIT:
+ // These special identifiers must be the only one in a declaration.
+ // Fail if there are others.
+ // TODO(sligocki): We should probably raise an error here.
+ if (SkipToNextToken()) return NULL;
+ // If everything is good, push these out.
+ values->push_back(v.release());
+ return values.release();
+ default:
+ break;
+ }
+ }
+
+ scoped_ptr<Value> font_style(new Value(Identifier::NORMAL));
+ scoped_ptr<Value> font_variant(new Value(Identifier::NORMAL));
+ scoped_ptr<Value> font_weight(new Value(Identifier::NORMAL));
+ scoped_ptr<Value> font_size(new Value(Identifier::MEDIUM));
+ scoped_ptr<Value> line_height(new Value(Identifier::NORMAL));
+ scoped_ptr<Value> font_family;
+
+ // parse style, variant and weight
+ while (true) {
+ // Firefox allows only one value to be set per property, IE need not.
+ if (v->GetLexicalUnitType() == Value::IDENT) {
+ switch (v->GetIdentifier().ident()) {
+ case Identifier::NORMAL:
+ // no-op
+ break;
+ case Identifier::ITALIC:
+ case Identifier::OBLIQUE:
+ font_style.reset(v.release());
+ break;
+ case Identifier::SMALL_CAPS:
+ font_variant.reset(v.release());
+ break;
+ case Identifier::BOLD:
+ case Identifier::BOLDER:
+ case Identifier::LIGHTER:
+ font_weight.reset(v.release());
+ break;
+ default:
+ goto check_fontsize;
+ }
+ } else if (v->GetLexicalUnitType() == Value::NUMBER &&
+ v->GetDimension() == Value::NO_UNIT) {
+ switch (v->GetIntegerValue()) {
+ // Different browsers handle this quite differently. But there
+ // is at least a test that is consistent between IE and
+ // firefox: try <span style="font:120 serif"> and <span
+ // style="font:100 serif">, the first one treats 120 as
+ // font-size, and the second does not.
+ case 100: case 200: case 300: case 400:
+ case 500: case 600: case 700: case 800:
+ case 900:
+ font_weight.reset(v.release());
+ break;
+ default:
+ goto check_fontsize;
+ }
+ } else {
+ goto check_fontsize;
+ }
+ if (!SkipToNextToken())
+ return NULL;
+ v.reset(ParseAny());
+ if (!v.get()) return NULL;
+ }
+
+ check_fontsize:
+ // parse font-size
+ switch (v->GetLexicalUnitType()) {
+ case Value::IDENT:
+ switch (v->GetIdentifier().ident()) {
+ case Identifier::XX_SMALL:
+ case Identifier::X_SMALL:
+ case Identifier::SMALL:
+ case Identifier::MEDIUM:
+ case Identifier::LARGE:
+ case Identifier::X_LARGE:
+ case Identifier::XX_LARGE:
+ case Identifier::LARGER:
+ case Identifier::SMALLER:
+ font_size.reset(v.release());
+ break;
+ default:
+ return NULL;
+ }
+ break;
+ case Value::NUMBER:
+ font_size.reset(v.release());
+ break;
+ default:
+ return NULL;
+ }
+
+ // parse line-height if '/' is seen, or use the default line-height
+ if (SkipToNextToken() && *in_ == '/') {
+ in_++;
+ if (!SkipToNextToken()) return NULL;
+ v.reset(ParseAny());
+ if (!v.get()) return NULL;
+
+ switch (v->GetLexicalUnitType()) {
+ case Value::IDENT:
+ if (v->GetIdentifier().ident() == Identifier::NORMAL)
+ break;
+ else
+ return NULL;
+ case Value::NUMBER:
+ line_height.reset(v.release());
+ break;
+ default:
+ return NULL;
+ }
+ }
+
+ values->push_back(font_style.release());
+ values->push_back(font_variant.release());
+ values->push_back(font_weight.release());
+ values->push_back(font_size.release());
+ values->push_back(line_height.release());
+
+ if (!ParseFontFamily(values.get())) // empty is okay.
+ return NULL;
+ return values.release();
+}
+
+static void ExpandShorthandProperties(Declarations* declarations,
+ const Declaration& declaration) {
+ Property prop = declaration.property();
+ const Values* vals = declaration.values();
+ bool important = declaration.IsImportant();
+
+ // Buffer to build up values used instead of vals above.
+ scoped_ptr<Values> edit_vals;
+ switch (prop.prop()) {
+ case Property::FONT: {
+ // Expand the value vector for special font: values.
+ if (vals->size() == 1) {
+ const Value* val = vals->at(0);
+ switch (val->GetIdentifier().ident()) {
+ case Identifier::CAPTION:
+ case Identifier::ICON:
+ case Identifier::MENU:
+ case Identifier::MESSAGE_BOX:
+ case Identifier::SMALL_CAPTION:
+ case Identifier::STATUS_BAR:
+ edit_vals.reset(new Values());
+ // Reasonable defaults to use for special font: declarations.
+ edit_vals->push_back(new Value(Identifier::NORMAL)); // font-style
+ edit_vals->push_back(new Value(Identifier::NORMAL)); // font-variant
+ edit_vals->push_back(new Value(Identifier::NORMAL)); // font-weight
+ // In this case, the actual font size will depend on browser,
+ // this is a common value found in IE and Firefox:
+ edit_vals->push_back(new Value(32.0/3, Value::PX)); // font-size
+ edit_vals->push_back(new Value(Identifier::NORMAL)); // line-height
+ // We store the special font type as font-family:
+ edit_vals->push_back(new Value(*val)); // font-family
+ vals = edit_vals.get(); // Move pointer to new, built-up values.
+ break;
+ case Identifier::INHERIT:
+ edit_vals.reset(new Values());
+ // font: inherit means all properties inherit.
+ edit_vals->push_back(new Value(*val)); // font-style
+ edit_vals->push_back(new Value(*val)); // font-variant
+ edit_vals->push_back(new Value(*val)); // font-weight
+ edit_vals->push_back(new Value(*val)); // font-size
+ edit_vals->push_back(new Value(*val)); // line-height
+ edit_vals->push_back(new Value(*val)); // font-family
+ vals = edit_vals.get(); // Move pointer to new, built-up values.
+ break;
+ default:
+ break;
+ }
+ }
+ // Only expand valid font: declarations (ones created by ParseFont, which
+ // requires at least 5 values in a specific order).
+ if (vals->size() < 5) {
+ LOG(ERROR) << "font: values are not in the correct format.\n" << vals;
+ break;
+ }
+ declarations->push_back(
+ new Declaration(Property::FONT_STYLE, *vals->get(0), important));
+ declarations->push_back(
+ new Declaration(Property::FONT_VARIANT, *vals->get(1), important));
+ declarations->push_back(
+ new Declaration(Property::FONT_WEIGHT, *vals->get(2), important));
+ declarations->push_back(
+ new Declaration(Property::FONT_SIZE, *vals->get(3), important));
+ declarations->push_back(
+ new Declaration(Property::LINE_HEIGHT, *vals->get(4), important));
+ if (vals->size() > 5) {
+ Values* family_vals = new Values;
+ for (int i = 5, n = vals->size(); i < n; ++i)
+ family_vals->push_back(new Value(*vals->get(i)));
+ declarations->push_back(
+ new Declaration(Property::FONT_FAMILY, family_vals, important));
+ }
+ }
+ break;
+ default:
+ // TODO(yian): other shorthand properties:
+ // background-position
+ // border-color border-style border-width
+ // border-top border-right border-bottom border-left
+ // border
+ // margin padding
+ // outline
+ break;
+ }
+}
+
+// Parse declarations like "background: white; color: #333; line-height: 1.3;"
+Declarations* Parser::ParseRawDeclarations() {
+ Tracer trace(__func__, &in_);
+
+ SkipSpace();
+ if (Done()) return new Declarations();
+ DCHECK_LT(in_, end_);
+
+ Declarations* declarations = new Declarations();
+ while (in_ < end_) {
+ bool ignore_this_decl = false;
+ switch (*in_) {
+ case ';':
+ in_++;
+ break;
+ case '}':
+ return declarations;
+ default: {
+ UnicodeText id = ParseIdent();
+ if (id.empty()) {
+ ignore_this_decl = true;
+ break;
+ }
+ Property prop(id);
+ SkipSpace();
+ if (Done() || *in_ != ':') {
+ ignore_this_decl = true;
+ break;
+ }
+ in_++;
+
+ Values* vals;
+ switch (prop.prop()) {
+ // TODO(sligocki): stop special-casing.
+ case Property::FONT:
+ vals = ParseFont();
+ break;
+ case Property::FONT_FAMILY:
+ vals = new Values();
+ if (!ParseFontFamily(vals) || vals->empty()) {
+ delete vals;
+ vals = NULL;
+ }
+ break;
+ default:
+ vals = ParseValues(prop.prop());
+ break;
+ }
+
+ if (vals == NULL) {
+ ignore_this_decl = true;
+ break;
+ }
+
+ bool important = false;
+ if (in_ < end_ && *in_ == '!') {
+ in_++;
+ SkipSpace();
+ UnicodeText ident = ParseIdent();
+ if (ident.utf8_length() == 9 &&
+ !memcasecmp(ident.utf8_data(), "important", 9))
+ important = true;
+ }
+ declarations->push_back(new Declaration(prop, vals, important));
+ }
+ }
+ SkipSpace();
+ if (ignore_this_decl) { // on bad syntax, we skip till the next declaration
+ errors_seen_mask_ |= kDeclarationError;
+ while (in_ < end_ && *in_ != ';' && *in_ != '}') {
+ // IE (and IE only) ignores {} blocks in quirks mode.
+ if (*in_ == '{' && !quirks_mode_) {
+ ParseBlock(); // ignore
+ } else {
+ in_++;
+ SkipSpace();
+ }
+ }
+ }
+ }
+ return declarations;
+}
+
+Declarations* Parser::ExpandDeclarations(Declarations* orig_declarations) {
+ scoped_ptr<Declarations> new_declarations(new Declarations);
+ for (int j = 0; j < orig_declarations->size(); ++j) {
+ // new_declarations takes ownership of declaration.
+ Declaration* declaration = orig_declarations->at(j);
+ orig_declarations->at(j) = NULL;
+ // TODO(yian): We currently store both expanded properties and the original
+ // property because only limited expansion is supported. In future, we
+ // should discard the original property after expansion.
+ new_declarations->push_back(declaration);
+ ExpandShorthandProperties(new_declarations.get(), *declaration);
+ // TODO(sligocki): Get ExpandBackground back into ExpandShorthandProperties.
+ switch (declaration->property().prop()) {
+ case Css::Property::BACKGROUND: {
+ ExpandBackground(*declaration, new_declarations.get());
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ return new_declarations.release();
+}
+
+Declarations* Parser::ParseDeclarations() {
+ scoped_ptr<Declarations> orig_declarations(ParseRawDeclarations());
+ return ExpandDeclarations(orig_declarations.get());
+}
+
+// Starts from [ and parses to the closing ]
+// in [ foo ~= bar ].
+// Whitespace is not skipped at beginning or the end.
+SimpleSelector* Parser::ParseAttributeSelector() {
+ Tracer trace(__func__, &in_);
+
+ DCHECK_LT(in_, end_);
+ DCHECK_EQ('[', *in_);
+ in_++;
+ SkipSpace();
+
+ UnicodeText attr = ParseIdent();
+ SkipSpace();
+ scoped_ptr<SimpleSelector> newcond;
+ if (!attr.empty() && in_ < end_) {
+ char oper = *in_;
+ switch (*in_) {
+ case '~':
+ case '|':
+ case '^':
+ case '$':
+ case '*':
+ in_++;
+ if (Done() || *in_ != '=')
+ break;
+ // fall through
+ case '=': {
+ in_++;
+ UnicodeText value = ParseStringOrIdent();
+ if (!value.empty())
+ newcond.reset(SimpleSelector::NewBinaryAttribute(
+ SimpleSelector::AttributeTypeFromOperator(oper),
+ attr,
+ value));
+ break;
+ }
+ default:
+ newcond.reset(SimpleSelector::NewExistAttribute(attr));
+ break;
+ }
+ }
+ if (SkipPastDelimiter(']'))
+ return newcond.release();
+ else
+ return NULL;
+}
+
+SimpleSelector* Parser::ParseSimpleSelector() {
+ Tracer trace(__func__, &in_);
+
+ if (Done()) return NULL;
+ DCHECK_LT(in_, end_);
+
+ switch (*in_) {
+ case '#': {
+ in_++;
+ UnicodeText id = ParseIdent();
+ if (!id.empty())
+ return SimpleSelector::NewId(id);
+ break;
+ }
+ case '.': {
+ in_++;
+ UnicodeText classname = ParseIdent();
+ if (!classname.empty())
+ return SimpleSelector::NewClass(classname);
+ break;
+ }
+ case ':': {
+ in_++;
+ UnicodeText pseudoclass = ParseIdent();
+ // FIXME(yian): skip constructs "(en)" in lang(en) for now.
+ if (in_ < end_ && *in_ == '(') {
+ in_++;
+ SkipSpace();
+ ParseIdent();
+ if (!SkipPastDelimiter(')'))
+ break;
+ }
+ if (!pseudoclass.empty())
+ return SimpleSelector::NewPseudoclass(pseudoclass);
+ break;
+ }
+ case '[': {
+ SimpleSelector* newcond = ParseAttributeSelector();
+ if (newcond)
+ return newcond;
+ break;
+ }
+ case '*':
+ in_++;
+ return SimpleSelector::NewUniversal();
+ break;
+ default: {
+ UnicodeText ident = ParseIdent();
+ if (!ident.empty())
+ return SimpleSelector::NewElementType(ident);
+ break;
+ }
+ }
+ // We did not parse anything or we parsed something incorrectly.
+ return NULL;
+}
+
+bool Parser::AtValidSimpleSelectorsTerminator() const {
+ if (Done()) return true;
+ switch (*in_) {
+ case ' ': case '\t': case '\r': case '\n': case '\f':
+ case ',': case '{': case '>': case '+':
+ return true;
+ case '/':
+ if (in_ + 1 < end_ && *(in_ + 1) == '*')
+ return true;
+ break;
+ }
+ return false;
+}
+
+SimpleSelectors* Parser::ParseSimpleSelectors(bool expecting_combinator) {
+ Tracer trace(__func__, &in_);
+
+ SkipSpace();
+ if (Done()) return NULL;
+ DCHECK_LT(in_, end_);
+
+ SimpleSelectors::Combinator combinator;
+ if (!expecting_combinator)
+ combinator = SimpleSelectors::NONE;
+ else
+ switch (*in_) {
+ case '>':
+ in_++;
+ combinator = SimpleSelectors::CHILD;
+ break;
+ case '+':
+ in_++;
+ combinator = SimpleSelectors::SIBLING;
+ break;
+ default:
+ combinator = SimpleSelectors::DESCENDANT;
+ break;
+ }
+
+ scoped_ptr<SimpleSelectors> selectors(
+ new SimpleSelectors(combinator));
+
+ SkipSpace();
+ if (Done()) return NULL;
+
+ const char* oldin = in_;
+ while (SimpleSelector* simpleselector = ParseSimpleSelector()) {
+ selectors->push_back(simpleselector);
+ oldin = in_;
+ }
+
+ if (selectors->size() > 0 && // at least one simple selector stored
+ in_ == oldin && // the last NULL does not make progress
+ AtValidSimpleSelectorsTerminator()) // stop at a valid terminator
+ return selectors.release();
+
+ return NULL;
+}
+
+Selectors* Parser::ParseSelectors() {
+ Tracer trace(__func__, &in_);
+
+ SkipSpace();
+ if (Done()) return NULL;
+ DCHECK_LT(in_, end_);
+
+ // Remember whether anything goes wrong, but continue parsing until the
+ // declaration starts or the position comes to the end. Then discard the
+ // selectors.
+ bool success = true;
+
+ scoped_ptr<Selectors> selectors(new Selectors());
+ Selector* selector = new Selector();
+ selectors->push_back(selector);
+
+ // The first simple selector sequence in a chain of simple selector
+ // sequences does not have a combinator. ParseSimpleSelectors needs
+ // to know this, so we set this to false here and after ',', and
+ // true after we see a simple selector sequence.
+ bool expecting_combinator = false;
+ while (in_ < end_ && *in_ != '{') {
+ switch (*in_) {
+ case ',':
+ if (selector->size() == 0) {
+ success = false;
+ } else {
+ selector = new Selector();
+ selectors->push_back(selector);
+ }
+ in_++;
+ expecting_combinator = false;
+ break;
+ default: {
+ const char* oldin = in_;
+ SimpleSelectors* simple_selectors
+ = ParseSimpleSelectors(expecting_combinator);
+ if (!simple_selectors) {
+ success = false;
+ if (in_ == oldin)
+ in_++;
+ } else {
+ selector->push_back(simple_selectors);
+ }
+
+ expecting_combinator = true;
+ break;
+ }
+ }
+ SkipSpace();
+ }
+
+ if (selector->size() == 0)
+ success = false;
+
+ if (success)
+ return selectors.release();
+ else
+ return NULL;
+}
+
+Ruleset* Parser::ParseRuleset() {
+ Tracer trace(__func__, &in_);
+
+ SkipSpace();
+ if (Done()) return NULL;
+ DCHECK_LT(in_, end_);
+
+ // Remember whether anything goes wrong, but continue parsing until the
+ // closing }. Then discard the whole ruleset if necessary. This allows the
+ // parser to make progress anyway.
+ bool success = true;
+
+ scoped_ptr<Ruleset> ruleset(new Ruleset());
+ scoped_ptr<Selectors> selectors(ParseSelectors());
+
+ if (Done())
+ return NULL;
+
+ if (selectors.get() == NULL) {
+ // http://www.w3.org/TR/CSS21/syndata.html#rule-sets
+ // When a user agent can't parse the selector (i.e., it is not
+ // valid CSS 2.1), it must ignore the declaration block as
+ // well.
+ success = false;
+ errors_seen_mask_ |= kSelectorError;
+ } else {
+ ruleset->set_selectors(selectors.release());
+ }
+
+ in_++; // '{'
+ ruleset->set_declarations(ParseRawDeclarations());
+ SkipPastDelimiter('}');
+
+ if (success)
+ return ruleset.release();
+ else
+ return NULL;
+}
+
+void Parser::ParseMediumList(std::vector<UnicodeText>* media) {
+ Tracer trace(__func__, &in_);
+
+ SkipSpace();
+ if (Done()) return;
+ DCHECK_LT(in_, end_);
+
+ while (in_ < end_) {
+ switch (*in_) {
+ case ';':
+ case '{':
+ return;
+ case ',':
+ in_++;
+ break;
+ default:
+ scoped_ptr<Value> v(ParseAny());
+ if (v.get() && v->GetLexicalUnitType() == Value::IDENT)
+ media->push_back(v->GetIdentifierText());
+ else
+ errors_seen_mask_ |= kMediaError;
+ break;
+ }
+ SkipSpace();
+ }
+}
+
+// Start after @import is parsed.
+Import* Parser::ParseImport() {
+ Tracer trace(__func__, &in_);
+
+ SkipSpace();
+ if (Done()) return NULL;
+ DCHECK_LT(in_, end_);
+
+ scoped_ptr<Value> v(ParseAny());
+ if (!v.get() || (v->GetLexicalUnitType() != Value::STRING &&
+ v->GetLexicalUnitType() != Value::URI))
+ return NULL;
+
+ Import* import = new Import();
+ import->link = v->GetStringValue();
+
+ ParseMediumList(&import->media);
+ if (in_ < end_ && *in_ == ';') in_++;
+ return import;
+}
+
+void Parser::ParseAtrule(Stylesheet* stylesheet) {
+ Tracer trace(__func__, &in_);
+
+ SkipSpace();
+ DCHECK_LT(in_, end_);
+ DCHECK_EQ('@', *in_);
+ in_++;
+
+ UnicodeText ident = ParseIdent();
+
+ // @import string|uri medium-list ? ;
+ if (ident.utf8_length() == 6 &&
+ memcasecmp(ident.utf8_data(), "import", 6) == 0) {
+ scoped_ptr<Import> import(ParseImport());
+ if (import.get() && stylesheet)
+ stylesheet->mutable_imports().push_back(import.release());
+
+ // @charset string ;
+ } else if (ident.utf8_length() == 7 &&
+ memcasecmp(ident.utf8_data(), "charset", 7) == 0) {
+ SkipPastDelimiter(';');
+
+ // @media medium-list { ruleset-list }
+ } else if (ident.utf8_length() == 5 &&
+ memcasecmp(ident.utf8_data(), "media", 5) == 0) {
+ std::vector<UnicodeText> media;
+ ParseMediumList(&media);
+ if (Done() || *in_ != '{')
+ return;
+ in_++;
+ SkipSpace();
+ while (in_ < end_ && *in_ != '}') {
+ const char* oldin = in_;
+ scoped_ptr<Ruleset> ruleset(ParseRuleset());
+ if (!ruleset.get() && in_ == oldin)
+ in_++;
+ if (ruleset.get()) {
+ ruleset->set_media(media);
+ stylesheet->mutable_rulesets().push_back(ruleset.release());
+ }
+ SkipSpace();
+ }
+ if (in_ < end_) in_++;
+
+ // @page pseudo_page? { declaration-list }
+ } else if (ident.utf8_length() == 4 &&
+ memcasecmp(ident.utf8_data(), "page", 4) == 0) {
+ delete ParseRuleset();
+ }
+}
+
+// TODO(dpeng): What exactly does this code do?
+void Parser::ParseBlock() {
+ Tracer trace(__func__, &in_);
+
+ SkipSpace();
+ DCHECK_LT(in_, end_);
+ DCHECK_EQ('{', *in_);
+ int depth = 0;
+ while (in_ < end_) {
+ switch (*in_) {
+ case '{':
+ in_++;
+ depth++;
+ break;
+ case '@':
+ in_++;
+ ParseIdent();
+ break;
+ case ';':
+ in_++;
+ break;
+ case '}':
+ in_++;
+ depth--;
+ if (depth == 0)
+ return;
+ break;
+ default:
+ scoped_ptr<Value> v(ParseAny());
+ break;
+ }
+ SkipSpace();
+ }
+}
+
+Stylesheet* Parser::ParseRawStylesheet() {
+ Tracer trace(__func__, &in_);
+
+ SkipSpace();
+ if (Done()) return new Stylesheet();
+ DCHECK_LT(in_, end_);
+
+ Stylesheet* stylesheet = new Stylesheet();
+ while (in_ < end_) {
+ switch (*in_) {
+ case '<':
+ in_++;
+ if (end_ - in_ >= 3 && memcmp(in_, "!--", 3) == 0) {
+ in_ += 3;
+ }
+ break;
+ case '-':
+ in_++;
+ if (end_ - in_ >= 2 && memcmp(in_, "->", 2) == 0) {
+ in_ += 2;
+ }
+ break;
+ case '@':
+ ParseAtrule(stylesheet);
+ break;
+ default: {
+ const char* oldin = in_;
+ scoped_ptr<Ruleset> ruleset(ParseRuleset());
+ if (!ruleset.get() && oldin == in_)
+ in_++;
+ if (ruleset.get())
+ stylesheet->mutable_rulesets().push_back(ruleset.release());
+ break;
+ }
+ }
+ SkipSpace();
+ }
+ return stylesheet;
+}
+
+Stylesheet* Parser::ParseStylesheet() {
+ Tracer trace(__func__, &in_);
+
+ Stylesheet* stylesheet = ParseRawStylesheet();
+
+ Rulesets& rulesets = stylesheet->mutable_rulesets();
+ for (int i = 0; i < rulesets.size(); ++i) {
+ Declarations& orig_declarations = rulesets[i]->mutable_declarations();
+ rulesets[i]->set_declarations(ExpandDeclarations(&orig_declarations));
+ }
+
+ return stylesheet;
+}
+
+//
+// Some destructors that need STLDeleteElements() from stl_util-inl.h
+//
+
+Declarations::~Declarations() { STLDeleteElements(this); }
+Rulesets::~Rulesets() { STLDeleteElements(this); }
+Imports::~Imports() { STLDeleteElements(this); }
+
+} // namespace
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/parser.h b/trunk/src/third_party/css_parser/src/webutil/css/parser.h
new file mode 100644
index 0000000..e421dd7
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/parser.h
@@ -0,0 +1,642 @@
+/**
+ * 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.
+ */
+
+// Copyright 2006 Google Inc. All Rights Reserved.
+// Author: dpeng@google.com (Daniel Peng)
+
+#ifndef WEBUTIL_CSS_PARSER_H__
+#define WEBUTIL_CSS_PARSER_H__
+
+#include <string>
+#include <vector>
+
+#include "base/scoped_ptr.h"
+#include "strings/stringpiece.h"
+#include "testing/production_stub/public/gunit_prod.h"
+#include "util/utf8/public/unicodetext.h"
+#include "webutil/css/property.h" // while these CSS includes can be
+#include "webutil/css/selector.h" // forward-declared, who is really
+#include "webutil/css/string.h"
+#include "webutil/css/value.h" // going to want parser.h but not values.h?
+#include "webutil/html/htmlcolor.h"
+
+namespace Css {
+
+// These are defined below Parser.
+struct Import;
+class Declaration;
+class Declarations;
+class Stylesheet;
+class Ruleset;
+
+// Recursive descent parser for CSS.
+// Based on: http://www.w3.org/TR/CSS21/syndata.html
+// http://www.w3.org/TR/CSS21/grammar.html
+//
+// Say you want to parse a fragment of CSS. Then construct a new
+// Parser object (this is very lightweight) and pass in the fragment to parse.
+// Then, call the top-level ParseXXX() function for what you want to parse.
+// This parses the fragment and returns a pointer to the abstract syntax tree.
+// You own this pointer and must delete it when you're done.
+//
+// The data structures comprising the abstract syntax tree are described in
+// cssvalue.h, cssparser-inl.h, csscondition.h, and cssproperty.h.
+//
+// Essentially, each stylesheet is a collection of rulesets.
+
+// Each ruleset has some selectors to describe what HTML elements it
+// applies to and some declarations to describe how the HTML elements
+// should be styled.
+// The ruleset may apply to multiple comma-separated selectors, which
+// means they apply to an element matching any of the selectors.
+// Ex: h1, h2 > p, h3 { color: red; }
+//
+// Each selector consists of a chain of simple selectors, separated by
+// combinators.
+// Ex: h2 > p selects a P element that is a child of an H2 element.
+//
+// Each simple selector may have conditions which impose further
+// restrictions, such as [foo], #id, .class, or :hover. We represent
+// these as a list, which are semantically AND-ed together.
+//
+// Each declaration consists of a property and a list of values.
+//
+// Say, for example, you wish to parse a CSS declaration:
+// Parser a("background: white; color: #333; line-height: 1.3;
+// text-align: justify; font-family: \"Gill Sans MT\",
+// \"Gill Sans\", GillSans, Arial, Helvetica, sans-serif");
+// scoped_ptr<Declarations> t(a.ParseDeclarations());
+// See the 'declarations' unit test case for more details.
+//
+// We've made most of the recursive-descent ParseXXX functions private
+// to shrink the size of the public interface. We expose
+// ParseStylesheet() and ParseDeclarations() because those are the
+// top-level functions necessary to parse stylesheets in HTML
+// documents. And ParseSelectors is exposed to parse selectors. If it's
+// useful to expose more of the functions, please just send a CL for approval,
+// so we know what people depend on.
+//
+// The CSS parser runs in either quirks mode (the default) and standard
+// compliant mode. The latter is stricter in many aspects. Currently, it
+// affects color parsing (see below for details). Please refer to:
+// http://developer.mozilla.org/en/docs/Mozilla_Quirks_Mode_Behavior
+// for the difference in Mozilla browsers.
+class Parser {
+ public:
+ Parser(const char* utf8text, const char* textend);
+ explicit Parser(const char* utf8text);
+ explicit Parser(StringPiece s);
+
+ // ParseRawSytlesheet and ParseStylesheet consume the entire document and
+ // return a Stylesheet* containing all the imports and rulesets that it
+ // found. You must delete the return pointer.
+
+ // ParseRawStylesheet simply parses the document into an abstract syntax tree.
+ Stylesheet* ParseRawStylesheet();
+ // ParseStylesheet also runs a second pass to convert shorthand
+ // declarations such as background, font and font-family into sets of
+ // declarations that they represent.
+ Stylesheet* ParseStylesheet();
+
+ // ParserRawDeclarations and ParseDeclarations parse declarations like
+ // "background: white; color: #333; line-height: 1.3;", consuming until
+ // (but not including) the closing '}' or EOF. You must delete the return
+ // pointer.
+
+ // ParseRawDeclarations simply parses the declarations into an AST.
+ Declarations* ParseRawDeclarations();
+
+ // ParseDeclarations also runs a second pass to convert *some* syntactic
+ // sugar declarations such as background, font and font-family.
+ // Currently, both the expanded properties (such as background-color) and the
+ // original property (background) are stored because the impl. is incomplete.
+ // For details, see parser.cc.
+ Declarations* ParseDeclarations();
+
+ // Expand the values of shorthand declarations. Currently expands background
+ // and font. Clears (but does not delete) input orig_declarartions in the
+ // process. orig_declarations should be a std::vector of NULLs on exit.
+ Declarations* ExpandDeclarations(Declarations* orig_declarations);
+
+ // Starting at the first simple selector or whitespace, ParseSelectors parses
+ // a sequence of selectors. Return NULL if the parsing fails. The parser would
+ // consume anything up to the declaration starting '{' or the end of document.
+ Selectors* ParseSelectors();
+
+ // current position in the parse.
+ const char* getpos() const { return in_; }
+
+ // Done with the parse?
+ bool Done() const { return in_ == end_; }
+
+ // Whether quirks mode (the default) is used in parsing. Standard compliant
+ // (non-quirks) mode is stricter in color parsing, where a form of "rrgbbb"
+ // without a leading # is not allowed.
+ bool quirks_mode() const { return quirks_mode_; }
+ void set_quirks_mode(bool quirks_mode) { quirks_mode_ = quirks_mode; }
+
+ // This is a bitmask of errors seen during the parse. This is decidedly
+ // incomplete --- there are definitely many errors that are not reported here.
+ static const uint64 kNoError = 0;
+ static const uint64 kUtf8Error = 1ULL << 0;
+ static const uint64 kDeclarationError = 1ULL << 1;
+ static const uint64 kSelectorError = 1ULL << 2;
+ static const uint64 kFunctionError = 1ULL << 3;
+ static const uint64 kMediaError = 1ULL << 4;
+ uint64 errors_seen_mask() const { return errors_seen_mask_; }
+
+ friend class ParserTest; // we need to unit test private Parse functions.
+
+ private:
+ //
+ // Syntactic methods
+ //
+
+ // SkipSpace() skips whitespace ([ \t\r\n\f]) and comments
+ // (/* .... */) until we reach a non-whitespace, non-comment
+ // character, or the end of the document.
+ void SkipSpace();
+
+ // Starting at /*, SkipComment() skips past the matching */ or to
+ // the end of the document.
+ void SkipComment();
+
+ // Skips following characters until delimiter delim or end is seen, delim is
+ // consumed if found. Be smart enough not to stop at character delim in
+ // comments. Returns whether delim is actually seen.
+ bool SkipPastDelimiter(char delim);
+
+ // Skips whitespace, comments, blocks ({..}), and @tokens, and returns true
+ // unless we are at the end of the document or the next character is a token
+ // ending delimiter ([;}!]).
+ bool SkipToNextToken();
+
+ //
+ // Parse functions.
+ //
+ // When the comment reads 'starting at foo', it's a dchecked runtime
+ // error to call the function if the input does not start with
+ // 'foo'.
+ //
+ // If a ParseXXX method returns a pointer, you own it and must
+ // delete it.
+
+ //
+ // 'leaves' of the parse tree: strings, urls, identifiers, numbers,
+ // etc
+ //
+
+ // ParseIdent() consumes the identifier and returns its unescaped
+ // representation. If we are at the end of the document, or if no
+ // identifier is found, ParseIdent() returns the empty string.
+ //
+ // In CSS2, identifiers (including element names, classes, and IDs in
+ // selectors) can contain only the characters [A-Za-z0-9] and ISO
+ // 10646 characters 161 and higher, plus the hyphen (-); they cannot
+ // start with a hyphen or a digit. They can also contain escaped
+ // characters and any ISO 10646 character as a numeric code (see next
+ // item). For instance, the identifier "B&W?" may be written as
+ // "B\&W\?" or "B\26 W\3F".
+ // http://www.w3.org/TR/REC-CSS2/syndata.html#value-def-identifier
+ //
+ // We're a little more forgiving than the standard and permit hyphens
+ // and digits to start identifiers.
+ // This method does not skip spaces like most other methods do, because it
+ // may be used to identify things like "import" in "@import", which is
+ // different from "@ import".
+ UnicodeText ParseIdent(); // parse an identifier like justify
+
+ // Starting at \, parse the escape and return the corresponding
+ // unicode codepoint. If the \ is the last character in the
+ // document, we return '\'; there is no other malformed input. This
+ // implements the second and third types of character escapes at
+ // http://www.w3.org/TR/REC-CSS2/syndata.html#escaped-characters
+ //
+ // 2) It cancels the meaning of special CSS characters. Any
+ // character (except a hexadecimal digit) can be escaped with a
+ // backslash to remove its special meaning. For example,
+ // ParseEscape() returns 0x6240 for \所 and 71 for \G (but \C is a
+ // hex escape, treated below:)
+ //
+ // 3) Backslash escapes allow authors to refer to characters
+ // they can't easily put in a document. In this case, the backslash
+ // is followed by at most six hexadecimal digits (0..9A..Fa..f), which
+ // stand for the ISO 10646 ([ISO10646]) character with that
+ // number. If a digit or letter follows the hexadecimal number, the
+ // end of the number needs to be made clear. There are two ways to
+ // do that:
+ // 1. with a space (or other whitespace character): "\26 B" ("&B")
+ // 2. by providing exactly 6 hexadecimal digits: "\000026B" ("&B")
+ //
+ // So, if the escape sequence is a hex escape and the character following
+ // the last hex digit is a space, then ParseEscape() consumes it.
+ char32 ParseEscape(); // return the codepoint for the current escape \12a76f
+
+ // Starting at delim, ParseString<char delim>() consumes the string,
+ // including the matching end-delim, and returns its unescaped
+ // representation, without the delimiters. If we fail to find the
+ // matching delimiter, we consume the rest of the document and
+ // return it.
+ //
+ // Strings can either be written with double quotes or with single
+ // quotes. Double quotes cannot occur inside double quotes, unless
+ // escaped (as '\"' or as '\22'). Analogously for single quotes
+ // ("\'" or "\27"). A string cannot directly contain a newline,
+ // unless hex-escaped as "\A".
+ //
+ // It is possible to break strings over several lines, for aesthetic
+ // or other reasons, but in such a case the newline itself has to be
+ // escaped with a backslash (\). For instance, the following two
+ // selectors are exactly the same:
+ // http://www.w3.org/TR/REC-CSS2/syndata.html#strings
+ template<char delim> UnicodeText ParseString();
+
+ // If the current character is a string-delimiter (' or "),
+ // ParseStringOrIdent() parses a string and returns the contents.
+ // Otherwise, it tries to parse an identifier. We must not be at
+ // the end of the document.
+ UnicodeText ParseStringOrIdent();
+
+ // ParseNumber parses a number and an optional unit, consuming to
+ // the end of the number or unit and returning a Value*.
+ // Real numbers and integers are specified in decimal notation
+ // only. An <integer> consists of one or more digits "0" to "9". A
+ // <number> can either be an <integer>, or it can be zero or more
+ // digits followed by a dot (.) followed by one or more digits. Both
+ // integers and real numbers may be preceded by a "-" or "+" to
+ // indicate the sign.
+ //
+ // If no number is found, ParseNumber returns NULL.
+ Value* ParseNumber();
+
+ // ParseColor parses several different representations of colors:
+ // 1) rgb
+ // 2) #rgb
+ // 3) rrggbb
+ // 4) #rrggbb
+ // 5) The 16 HTML4 color names (aqua, black, blue,
+ // fuchsia, gray, green, lime, maroon, navy, olive, purple, red,
+ // silver, teal, white, and yellow), with or without quotes (' or ").
+ // It's designed to handle all the ill-formed CSS color values out there.
+ // It consumes the color if it finds a valid color. Otherwise, it returns
+ // an undefined HtmlColor (HtmlColor::IsDefined()) and does not consume
+ // anything.
+ //
+ // However, if quirks_mode_ is false (standard compliant mode), forms 1 and 3
+ // (without #) would not be accepted.
+ HtmlColor ParseColor(); // parse a hex or named
+ // color like #fff, #bcdefa
+ // or black
+
+ //
+ // FUNCTION-like objects: rgb(), url(), rect()
+ //
+
+ // Converts a Value number or percentage to an RGB value.
+ static unsigned char ValueToRGB(Value* v);
+
+ // ParseRgbColor parsers the part between the parentheses of rgb( )
+ // according to http://www.w3.org/TR/REC-CSS2/syndata.html#color-units .
+ //
+ // The format of an RGB value in the functional notation is 'rgb('
+ // followed by a comma-separated list of three numerical values
+ // (either three integer values or three percentage values)
+ // followed by ')'. The integer value 255 corresponds to 100%, and
+ // to F or FF in the hexadecimal notation: rgb(255,255,255) =
+ // rgb(100%,100%,100%) = #FFF. Whitespace characters are allowed
+ // around the numerical values.
+ //
+ // Starting just past 'rgb(', ParseColor() consumes up to (but not
+ // including) the closing ) and returns the color it finds.
+ // Returns NULL if mal-formed.
+ Value* ParseRgbColor(); // parse an rgbcolor like 125, 25, 12
+ // or 12%, 57%, 89%
+
+ // ParseUrl parses the part between the parentheses of url( )
+ // according to http://www.w3.org/TR/REC-CSS2/syndata.html#uri .
+ //
+ // The format of a URI value is 'url(' followed by optional
+ // whitespace followed by an optional single quote (') or double
+ // quote (") character followed by the URI itself, followed by an
+ // optional single quote (') or double quote (") character followed
+ // by optional whitespace followed by ')'. The two quote characters
+ // must be the same.
+ //
+ // Starting just past 'url(', ParseUrl() consumes the url as well as
+ // the optional whitespace. If the url is well-formed, the next
+ // character must be ')'.
+ // Returns NULL for mal-formed URLs.
+ Value* ParseUrl(); // parse a url like yellow.png or 'blah.png'
+
+ // Parses between the parentheses of rect(top, right, bottom, left).
+ //
+ // The contents should be a comma or space separated list of four numerical
+ // values or the keyword "auto". Note that spaces are allowed to separate
+ // values for historical reasons.
+ //
+ // Returns NULL if the contents is not well-formed.
+ Value* ParseRect(); // parse rect(top, right, bottom, left)
+
+ //
+ // Value and Values
+ //
+
+ // Parses a value which is expected to be color values. It can be
+ // different from ParseAny, for example, for black or ccddff, both
+ // are translated into color values here but are returned as idents
+ // in the latter case. We call this instead of ParseAny() after
+ // color, background-color, and background properties to accomodate bad CSS.
+ // If no value is found, ParseAnyExpectingColor returns NULL.
+ Value* ParseAnyExpectingColor();
+
+ // ParseAny() parses a css value and consumes it. It does not skip
+ // leading or trailing whitespace.
+ // If no value is found, ParseAny returns NULL and make sure at least one
+ // character is consumed (to make progress).
+ Value* ParseAny(); // parse a value, which is pretty much anything.
+
+ // Parse a list of values for the given property.
+ // We parse until we see a !, ;, or } delimiter. However, if there are any
+ // malformed values, stop parsing and return NULL immediately.
+ // For special shortcut properties, use the following specialized methods
+ // instead.
+ Values* ParseValues(Property::Prop prop);
+
+ // Expand a background property into all the sub-properties (background-color,
+ // background-image, etc.). Return false on malformed original_declaration.
+ static bool ExpandBackground(const Declaration& original_declaration,
+ Declarations* new_declarations);
+
+ // Parses FONT. Returnss NULL if malformed. Otherwise, the output is a tuple
+ // in the following order
+ // "font-style font-variant font-weight font-size line-height font-family+"
+ Values* ParseFont();
+
+ // Parses FONT-FAMILY and the tailing part in FONT and appends the results in
+ // values. Returns false if there are any malformed values.
+ // This interface is different from the others because it is also used by
+ // ParseFont(), where family names are appended to other CSS values.
+ bool ParseFontFamily(Values* values);
+
+ //
+ // Selectors and Rulesets
+ //
+
+ // ParseAttributeSelector() starts at [ and parses an attribute
+ // selector like [ foo ~= bar], consuming the final ]. Returns NULL
+ // on error but still consumes to the matching ].
+ // This method does not skip spaces like most other methods do.
+ // Whitespace is syntactically significant here, because a sequence of simple
+ // selectors contains no whitespace. 'div[align=center]' is a sequence of
+ // simple selectors, but 'div [align=center]' is a syntax error (though we
+ // will parse it as a selector, i.e., two simple selector sequences separated
+ // by a whitespace combinator).
+ SimpleSelector* ParseAttributeSelector();
+
+ // ParseSimpleSelector() parses one simple sector. Starts from
+ // anything and returns NULL if no simple selector found or parse error.
+ // This method does not skip spaces like most other methods do.
+ // See comment above.
+ SimpleSelector* ParseSimpleSelector();
+
+ // Checks if the parser stops at a character (or characters) that will
+ // legally terminate a SimpleSelectors. The checked characters are not eaten.
+ // Valid terminators are whitespaces, comments, combinators ('>', '+'), ','
+ // and '{'. A stop at the end is also considered valid.
+ bool AtValidSimpleSelectorsTerminator() const;
+
+ // Starting at whitespace, a combinator, or the first simple
+ // selector, ParseSimpleSelectors parses a sequence of simple
+ // selectors, i.e., a chain of simple selectors that are not
+ // separated by a combinator. The chain itself may be preceeded by
+ // a combinator, in which case you should pass true for
+ // expecting_combinator, and we will parse the combinator.
+ // Typically, when you're parsing a selector (i.e., a chain of
+ // sequences of simple selectors separated by combinators), you pass
+ // false on the first simple selector and true on the subsequent
+ // ones.
+ SimpleSelectors* ParseSimpleSelectors(bool expecting_combinator);
+
+ // ParseRuleset() starts from the first character of the first
+ // selector (note: it does not skip whitespace) and consumes the
+ // ruleset, including the closing '}'. Return NULL if the parsing fails.
+ // However, the parser would consume anything up to the closing '}', if any,
+ // even if it fails somehow in the middle, per CSS spec.
+ Ruleset* ParseRuleset();
+
+ //
+ // Miscellaneous
+ //
+
+ // Starting at whitespace or the first medium, ParseMediumList
+ // parses a medium list separated by commas and whitespace, stopping
+ // at (without consuming) ; or {. It adds each medium to the back of media.
+ void ParseMediumList(std::vector<UnicodeText>* media);
+
+ // ParseImport starts just after @import and consumes the import
+ // declaration, including the closing ;. It returns a Import*
+ // containing the imported name and the media.
+ Import* ParseImport();
+
+ // Starting at @, ParseAtrule parses @import, @charset, @medium, and
+ // @page declarations and adds the information to the stylesheet.
+ // Consumes the @-rule, including the closing ';' or '}'. Does not
+ // consume trailing whitespace.
+ void ParseAtrule(Stylesheet* stylesheet); // parse @ rules.
+
+ // Starting at '{', ParseBlock consumes to the matching '}', respecting
+ // nested blocks. We discard the result.
+ void ParseBlock();
+
+ const char *in_; // The current point in the parse.
+ const char *end_; // The end of the document to parse.
+
+ bool quirks_mode_; // Whether we are in quirks mode.
+ uint64 errors_seen_mask_;
+
+ FRIEND_TEST(ParserTest, color);
+ FRIEND_TEST(ParserTest, url);
+ FRIEND_TEST(ParserTest, rect);
+ FRIEND_TEST(ParserTest, background);
+ FRIEND_TEST(ParserTest, font_family);
+ friend void ParseFontFamily(Parser* parser);
+ FRIEND_TEST(ParserTest, ParseBlock);
+ FRIEND_TEST(ParserTest, font);
+ FRIEND_TEST(ParserTest, values);
+ FRIEND_TEST(ParserTest, declarations);
+ FRIEND_TEST(ParserTest, universalselector);
+ FRIEND_TEST(ParserTest, universalselectorcondition);
+ FRIEND_TEST(ParserTest, comment_breaking_descendant_combinator);
+ FRIEND_TEST(ParserTest, comment_breaking_child_combinator);
+ FRIEND_TEST(ParserTest, simple_selectors);
+ FRIEND_TEST(ParserTest, bad_simple_selectors);
+ FRIEND_TEST(ParserTest, rulesets);
+ FRIEND_TEST(ParserTest, ruleset_starts_with_combinator);
+ FRIEND_TEST(ParserTest, atrules);
+ FRIEND_TEST(ParserTest, percentage_colors);
+ DISALLOW_COPY_AND_ASSIGN(Parser);
+};
+
+// Definitions of various data structures returned by the parser.
+// More in selector.h and value.h.
+
+// A single declaration such as font: 12pt Arial.
+// A declaration consists of a property name (Property) and a list
+// of values (Values*).
+// It could also be important (font: 12pt Arial !important).
+class Declaration {
+ public:
+ // constructor. We take ownership of v.
+ Declaration(Property p, Values* v, bool important) :
+ property_(p), values_(v), important_(important) { }
+ // constructor with a single Value. We make a copy of the value.
+ Declaration(Property p, const Value& v, bool important)
+ : property_(p), values_(new Values), important_(important) {
+ values_->push_back(new Value(v));
+ }
+
+ // accessors
+ Property property() const { return property_; }
+ const Values* values() const { return values_.get(); }
+ bool IsImportant() const { return important_; }
+
+ // convenience accessors
+ Property::Prop prop() const { return property_.prop(); }
+ string prop_text() const { return property_.prop_text(); }
+
+ Values* mutable_values() { return values_.get(); }
+
+ void set_property(Property property) { property_ = property; }
+ // Takes ownership of values.
+ void set_values(Values* values) { values_.reset(values); }
+ void set_important(bool important) { important_ = important; }
+
+ string ToString() const;
+ private:
+ Property property_;
+ scoped_ptr<Values> values_;
+ bool important_; // Whether !important is declared on this declaration.
+
+ DISALLOW_COPY_AND_ASSIGN(Declaration);
+};
+
+// Declarations is a vector of Declaration*, which we own and
+// will delete upon destruction. If you remove elements from
+// Declarations, you are responsible for deleting them.
+// Also, be careful --- there's no virtual destructor, so this must be
+// deleted as a Declarations.
+class Declarations : public std::vector<Declaration*> {
+ public:
+ Declarations() : std::vector<Declaration*>() { }
+ ~Declarations();
+
+ // We provide syntactic sugar for accessing elements.
+ // declarations->get(i) looks better than (*declarations)[i])
+ const Declaration* get(int i) const { return (*this)[i]; }
+
+ string ToString() const;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Declarations);
+};
+
+// A ruleset consists of a list of selectors followed by a declaration block.
+// It can also optionally include a list of medium description.
+class Ruleset {
+ public:
+ Ruleset() : selectors_(new Selectors), declarations_(new Declarations) { }
+ // Takes ownership of selectors and declarations.
+ Ruleset(Selectors* selectors, const std::vector<UnicodeText>& media,
+ Declarations* declarations)
+ : selectors_(selectors), media_(media), declarations_(declarations) { }
+ ~Ruleset() { }
+
+ const Selectors& selectors() const { return *selectors_; }
+ const Selector& selector(int i) const { return *selectors_->at(i); }
+ const std::vector<UnicodeText>& media() const { return media_; }
+ const UnicodeText& medium(int i) const { return media_.at(i); }
+ const Declarations& declarations() const { return *declarations_; }
+ const Declaration& declaration(int i) const { return *declarations_->at(i); }
+
+ Selectors& mutable_selectors() { return *selectors_; }
+ Declarations& mutable_declarations() { return *declarations_; }
+
+ // set_media copies input media.
+ void set_media(const std::vector<UnicodeText>& media) {
+ media_.assign(media.begin(), media.end());
+ }
+ // set_selectors and _declarations take ownership of parameters.
+ void set_selectors(Selectors* selectors) { selectors_.reset(selectors); }
+ void set_declarations(Declarations* decls) { declarations_.reset(decls); }
+
+ string ToString() const;
+ private:
+ scoped_ptr<Selectors> selectors_;
+ std::vector<UnicodeText> media_;
+ scoped_ptr<Declarations> declarations_;
+
+ DISALLOW_COPY_AND_ASSIGN(Ruleset);
+};
+
+class Rulesets : public std::vector<Css::Ruleset*> {
+ public:
+ Rulesets() : std::vector<Css::Ruleset*>() { }
+ ~Rulesets();
+};
+
+struct Import {
+ std::vector<UnicodeText> media;
+ UnicodeText link;
+
+ string ToString() const;
+};
+
+class Imports : public std::vector<Css::Import*> {
+ public:
+ Imports() : std::vector<Css::Import*>() { }
+ ~Imports();
+};
+
+// A stylesheet consists of a list of import information and a list of
+// rulesets.
+class Stylesheet {
+ public:
+ Stylesheet() : type_(AUTHOR) {}
+
+ // USER is currently unused.
+ enum StylesheetType { AUTHOR, USER, SYSTEM };
+ StylesheetType type() const { return type_; }
+ const Imports& imports() const { return imports_; }
+ const Rulesets& rulesets() const { return rulesets_; }
+
+ const Import& import(int i) const { return *imports_[i]; }
+ const Ruleset& ruleset(int i) const { return *rulesets_[i]; }
+
+ void set_type(StylesheetType type) { type_ = type; }
+ Imports& mutable_imports() { return imports_; }
+ Rulesets& mutable_rulesets() { return rulesets_; }
+
+ string ToString() const;
+ private:
+ StylesheetType type_;
+ Imports imports_;
+ Rulesets rulesets_;
+
+ DISALLOW_COPY_AND_ASSIGN(Stylesheet);
+};
+
+} // namespace
+
+#endif // WEBUTIL_CSS_PARSER_H__
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/parser_unittest.cc b/trunk/src/third_party/css_parser/src/webutil/css/parser_unittest.cc
new file mode 100644
index 0000000..db1626d
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/parser_unittest.cc
@@ -0,0 +1,1455 @@
+/**
+ * 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.
+ */
+
+// Copyright 2006 Google Inc. All Rights Reserved.
+// Author: dpeng@google.com (Daniel Peng)
+
+#include "webutil/css/parser.h"
+
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/scoped_ptr.h"
+#include "testing/base/public/googletest.h"
+#include "testing/base/public/gunit.h"
+
+
+namespace Css {
+
+class ParserTest : public testing::Test {
+ protected:
+ // The various Test* functions below check that parselen characters
+ // are parsed. Pass -1 to indicate that the entire string should be
+ // parsed.
+
+ // Checks that unescaping s returns value.
+ void TestUnescape(const char* s, int parselen, char32 value) {
+ SCOPED_TRACE(s);
+ Parser a(s);
+ if (parselen == -1) parselen = strlen(s);
+ EXPECT_EQ(value, a.ParseEscape());
+ EXPECT_EQ(parselen, a.getpos() - s);
+ }
+
+ // Checks that ParseIdent(s) returns utf8golden.
+ void TestIdent(const char* s, int parselen, string utf8golden) {
+ SCOPED_TRACE(s);
+ Parser a(s);
+ if (parselen == -1) parselen = strlen(s);
+ EXPECT_EQ(utf8golden, UnicodeTextToUTF8(a.ParseIdent()));
+ EXPECT_EQ(parselen, a.getpos() - s);
+ }
+
+ // Checks that ParseString<">(s) returns utf8golden.
+ void TestDstring(const char* s, int parselen, string utf8golden) {
+ SCOPED_TRACE(s);
+ Parser a(s);
+ if (parselen == -1) parselen = strlen(s);
+ EXPECT_EQ(utf8golden, UnicodeTextToUTF8(a.ParseString<'"'>()));
+ EXPECT_EQ(parselen, a.getpos() - s);
+ }
+
+ // Checks that ParseString<'>(s) returns utf8golden.
+ void TestSstring(const char* s, int parselen, string utf8golden) {
+ SCOPED_TRACE(s);
+ Parser a(s);
+ if (parselen == -1) parselen = strlen(s);
+ EXPECT_EQ(utf8golden, UnicodeTextToUTF8(a.ParseString<'\''>()));
+ EXPECT_EQ(parselen, a.getpos() - s);
+ }
+
+ // Checks that ParseAny(s) returns goldennum with goldenunit unit.
+ void TestAnyNum(const char* s, int parselen, float goldennum,
+ Value::Unit goldenunit) {
+ SCOPED_TRACE(s);
+ Parser a(s);
+ if (parselen == -1) parselen = strlen(s);
+ scoped_ptr<Value> t(a.ParseAny());
+ EXPECT_EQ(t->GetLexicalUnitType(), Value::NUMBER);
+ EXPECT_EQ(t->GetDimension(), goldenunit);
+ EXPECT_FLOAT_EQ(t->GetFloatValue(), goldennum);
+ EXPECT_EQ(parselen, a.getpos() - s);
+ }
+
+ // Checks that ParseAny(s) returns goldennum with OTHER unit (with
+ // unit text goldenunit).
+ void TestAnyNumOtherUnit(const char* s, int parselen, float goldennum,
+ string goldenunit) {
+ SCOPED_TRACE(s);
+ Parser a(s);
+ if (parselen == -1) parselen = strlen(s);
+ scoped_ptr<Value> t(a.ParseAny());
+ EXPECT_EQ(t->GetLexicalUnitType(), Value::NUMBER);
+ EXPECT_EQ(t->GetDimension(), Value::OTHER);
+ EXPECT_EQ(t->GetDimensionUnitText(), goldenunit);
+ EXPECT_EQ(parselen, a.getpos() - s);
+ }
+
+ // Checks that ParseAny(s) returns a string-type value with type goldenty
+ // and value utf8golden.
+ void TestAnyString(const char* s, int parselen, Value::ValueType goldenty,
+ string utf8golden) {
+ SCOPED_TRACE(s);
+ Parser a(s);
+ if (parselen == -1) parselen = strlen(s);
+ scoped_ptr<Value> t(a.ParseAny());
+ EXPECT_EQ(goldenty, t->GetLexicalUnitType());
+ EXPECT_EQ(utf8golden, UnicodeTextToUTF8(t->GetStringValue()));
+ EXPECT_EQ(parselen, a.getpos() - s);
+ }
+
+ // Checks that ParseAny(s) returns a ident value with identifier goldenty
+ // and value utf8golden.
+ void TestAnyIdent(const char* s, int parselen, Identifier::Ident goldenty) {
+ SCOPED_TRACE(s);
+ Parser a(s);
+ if (parselen == -1) parselen = strlen(s);
+ scoped_ptr<Value> t(a.ParseAny());
+ EXPECT_EQ(Value::IDENT, t->GetLexicalUnitType());
+ EXPECT_EQ(goldenty, t->GetIdentifier().ident());
+ EXPECT_EQ(parselen, a.getpos() - s);
+ }
+
+ // Checks that ParseAny(s) returns OTHER identifier (with text goldenident).
+ void TestAnyOtherIdent(const char* s, int parselen,
+ const string& goldenident) {
+ SCOPED_TRACE(s);
+ Parser a(s);
+ if (parselen == -1) parselen = strlen(s);
+ scoped_ptr<Value> t(a.ParseAny());
+ EXPECT_EQ(Value::IDENT, t->GetLexicalUnitType());
+ EXPECT_EQ(Identifier::OTHER, t->GetIdentifier().ident());
+ EXPECT_EQ(goldenident, UnicodeTextToUTF8(t->GetIdentifierText()));
+ EXPECT_EQ(parselen, a.getpos() - s);
+ }
+
+ Declarations* ParseAndExpandBackground(const string& str,
+ bool quirks_mode=true) {
+ scoped_ptr<Parser> p(new Parser(str));
+ p->set_quirks_mode(quirks_mode);
+ scoped_ptr<Values> vals(p->ParseValues(Property::BACKGROUND));
+ if (vals.get() == NULL || vals->size() == 0) {
+ return NULL;
+ }
+ Declaration background(Property::BACKGROUND, vals.release(), false);
+ scoped_ptr<Declarations> decls(new Declarations);
+ Parser::ExpandBackground(background, decls.get());
+ if (decls->size() == 0) {
+ return NULL;
+ }
+ return decls.release();
+ }
+
+ void TestBackgroundPosition(const string& str, const string& x,
+ const string& y) {
+ scoped_ptr<Declarations> decls(ParseAndExpandBackground(str));
+ // Find and check position x and y values.
+ bool found_x = false;
+ bool found_y = false;
+ for (Declarations::const_iterator iter = decls->begin();
+ iter != decls->end(); ++iter) {
+ Declaration* decl = *iter;
+ switch (decl->prop()) {
+ case Property::BACKGROUND_POSITION_X:
+ EXPECT_EQ(x, decl->values()->get(0)->ToString());
+ found_x = true;
+ break;
+ case Property::BACKGROUND_POSITION_Y:
+ EXPECT_EQ(y, decl->values()->get(0)->ToString());
+ found_y = true;
+ break;
+ default:
+ break;
+ }
+ }
+ EXPECT_TRUE(found_x);
+ EXPECT_TRUE(found_y);
+ }
+};
+
+
+
+TEST_F(ParserTest, unescape) {
+ TestUnescape("\\abcdef aabc", 8, 0xABCDEF);
+ TestUnescape("\\A", 2, 0xA);
+ TestUnescape("\\A0b5C\r\n", 8, 0xa0b5C);
+ TestUnescape("\\AB ", 4, 0xAB);
+}
+
+TEST_F(ParserTest, ident) {
+ // We're a little more forgiving than the standard:
+ //
+ // In CSS 2.1, identifiers (including element names, classes, and
+ // IDs in selectors) can contain only the characters [A-Za-z0-9]
+ // and ISO 10646 characters U+00A1 and higher, plus the hyphen (-)
+ // and the underscore (_); they cannot start with a digit, or a
+ // hyphen followed by a digit. Only properties, values, units,
+ // pseudo-classes, pseudo-elements, and at-rules may start with a
+ // hyphen (-); other identifiers (e.g. element names, classes, or
+ // IDs) may not. Identifiers can also contain escaped characters
+ // and any ISO 10646 character as a numeric code (see next
+ // item). For instance, the identifier "B&W?" may be written as
+ // "B\&W\?" or "B\26 W\3F".
+ TestIdent("abcd rexo\n", 4, "abcd");
+ TestIdent("台灣華語", 12, "台灣華語");
+ TestIdent("\\41\\42 \\43 \\44", 14, "ABCD");
+ TestIdent("\\41\\42 \\43 \\44g'r,'rcg.,',", 15, "ABCDg");
+ TestIdent("\\41\\42 \\43 \\44\r\ng'r,'rcg.,',", 17, "ABCDg");
+ TestIdent("-blah-_67", 9, "-blah-_67");
+ TestIdent("\\!\\&\\^\\*\\\\e", 11, "!&^*\\e");
+}
+
+TEST_F(ParserTest, string) {
+ TestSstring("'ab\\'aoe\"\\'eo灣'灣", 17, "ab'aoe\"'eo灣");
+ TestDstring("\"ab'aoe\\\"'eo灣\"灣", 16, "ab'aoe\"'eo灣");
+ TestSstring("'ab\naoeu", 3, "ab");
+ TestDstring("\"ab\naoeu", 3, "ab");
+ TestDstring("\"ab\\\naoeu\"", 10, "abaoeu");
+}
+
+TEST_F(ParserTest, anynum) {
+ TestAnyNum("3.1415 4aone", 6, 3.1415, Value::NO_UNIT);
+ TestAnyNum(".1415 4aone", 5, .1415, Value::NO_UNIT);
+ TestAnyNum("5 4aone", 1, 5, Value::NO_UNIT);
+
+ TestAnyNum("3.1415pt 4aone", 8, 3.1415, Value::PT);
+ TestAnyNum(".1415pc 4aone", 7, .1415, Value::PC);
+ TestAnyNum("5s 4aone", 2, 5, Value::S);
+
+ TestAnyNumOtherUnit("5sacks 4aone", 6, 5, "sacks");
+ TestAnyNumOtherUnit("5灣 4aone", 4, 5, "灣");
+}
+
+TEST_F(ParserTest, anystring) {
+ TestAnyIdent("none b c d e", 4, Identifier::NONE);
+ TestAnyIdent("none; b c d e", 4, Identifier::NONE);
+ TestAnyIdent("none ; b c d e", 4, Identifier::NONE);
+ TestAnyOtherIdent("a b c d e", 1, "a");
+ TestAnyOtherIdent("a; b c d e", 1, "a");
+ TestAnyOtherIdent("a ; b c d e", 1, "a");
+ TestAnyString("'ab\\'aoe\"\\'eo灣'灣 ; b c d e", 17,
+ Value::STRING, "ab'aoe\"'eo灣");
+}
+
+TEST_F(ParserTest, color) {
+ // allowed in quirks mode
+ scoped_ptr<Parser> a(new Parser("abCdEF brc.,aoek"));
+ EXPECT_EQ(a->ParseColor().ToString(), "#abcdef");
+
+ // not allowed in stanard compliant mode.
+ a.reset(new Parser("abCdEF brc.,aoek"));
+ a->set_quirks_mode(false);
+ EXPECT_FALSE(a->ParseColor().IsDefined());
+
+ // this is allowed
+ a.reset(new Parser("#abCdEF brc.,aoek"));
+ a->set_quirks_mode(false);
+ EXPECT_EQ(a->ParseColor().ToString(), "#abcdef");
+
+ a.reset(new Parser("abC btneo"));
+ EXPECT_EQ(a->ParseColor().ToString(), "#aabbcc");
+
+ // no longer allowed
+ a.reset(new Parser("#white something"));
+ EXPECT_FALSE(a->ParseColor().IsDefined());
+
+ a.reset(new Parser("#white something"));
+ a->set_quirks_mode(false);
+ EXPECT_FALSE(a->ParseColor().IsDefined());
+
+ // this is allowed
+ a.reset(new Parser("white something"));
+ EXPECT_EQ(a->ParseColor().ToString(), "#ffffff");
+
+ a.reset(new Parser("white something"));
+ a->set_quirks_mode(false);
+ EXPECT_EQ(a->ParseColor().ToString(), "#ffffff");
+
+ // system color
+ a.reset(new Parser("buttonface something"));
+ EXPECT_EQ(a->ParseColor().ToString(), "#ece9d8");
+
+ // string patterns
+
+ a.reset(new Parser("'abCdEF' brc.,aoek"));
+ EXPECT_EQ(a->ParseColor().ToString(), "#abcdef");
+
+ a.reset(new Parser("'abCdEF' brc.,aoek"));
+ a->set_quirks_mode(false);
+ EXPECT_FALSE(a->ParseColor().IsDefined());
+
+ // this is not allowed since color values must end on string boundary
+ a.reset(new Parser("'#abCdEF brc'.,aoek"));
+ a->set_quirks_mode(false);
+ EXPECT_FALSE(a->ParseColor().IsDefined());
+
+ a.reset(new Parser("\"abC\" btneo"));
+ EXPECT_EQ(a->ParseColor().ToString(), "#aabbcc");
+
+ // no longer allowed
+ a.reset(new Parser("'#white' something"));
+ EXPECT_FALSE(a->ParseColor().IsDefined());
+
+ a.reset(new Parser("'#white' something"));
+ a->set_quirks_mode(false);
+ EXPECT_FALSE(a->ParseColor().IsDefined());
+
+ // this is allowed
+ a.reset(new Parser("'white' something"));
+ EXPECT_EQ(a->ParseColor().ToString(), "#ffffff");
+
+ a.reset(new Parser("'white' something"));
+ a->set_quirks_mode(false);
+ EXPECT_EQ(a->ParseColor().ToString(), "#ffffff");
+
+ // no longer allowed
+ a.reset(new Parser("100%"));
+ EXPECT_FALSE(a->ParseColor().IsDefined());
+
+ // no longer allowed
+ a.reset(new Parser("100px"));
+ EXPECT_FALSE(a->ParseColor().IsDefined());
+
+ // this is allowed
+ a.reset(new Parser("100"));
+ EXPECT_EQ(a->ParseColor().ToString(), "#110000");
+
+ // should be parsed as a number
+ a.reset(new Parser("100px"));
+ scoped_ptr<Value> t(a->ParseAnyExpectingColor());
+ EXPECT_EQ(Value::NUMBER, t->GetLexicalUnitType());
+ EXPECT_EQ("100px", t->ToString());
+
+ a.reset(new Parser("rgb(12,25,30)"));
+ t.reset(a->ParseAny());
+ EXPECT_EQ(t->GetColorValue().ToString(), "#0c191e");
+
+ a.reset(new Parser("rgb( 12% , 25%, 30%)"));
+ t.reset(a->ParseAny());
+ EXPECT_EQ(t->GetColorValue().ToString(), "#1e3f4c");
+
+ a.reset(new Parser("rgb( 12% , 25% 30%)"));
+ t.reset(a->ParseAny());
+ EXPECT_FALSE(t.get());
+}
+
+TEST_F(ParserTest, url) {
+ scoped_ptr<Parser> a(new Parser("url(blah)"));
+ scoped_ptr<Value> t(a->ParseAny());
+
+ EXPECT_EQ(Value::URI, t->GetLexicalUnitType());
+ EXPECT_EQ("blah", UnicodeTextToUTF8(t->GetStringValue()));
+
+ a.reset(new Parser("url( blah )"));
+ t.reset(a->ParseAny());
+
+ EXPECT_EQ(Value::URI, t->GetLexicalUnitType());
+ EXPECT_EQ("blah", UnicodeTextToUTF8(t->GetStringValue()));
+
+ a.reset(new Parser("url( blah extra)"));
+ t.reset(a->ParseAny());
+
+ EXPECT_EQ(static_cast<Value *>(NULL), t.get());
+}
+
+TEST_F(ParserTest, rect) {
+ // rect can be either comma or space delimited
+ scoped_ptr<Parser> a(new Parser("rect( 12, 10,auto 200px)"));
+ scoped_ptr<Value> t(a->ParseAny());
+
+ EXPECT_EQ(Value::RECT, t->GetLexicalUnitType());
+ ASSERT_EQ(4, t->GetParameters()->size());
+ EXPECT_EQ(Value::NUMBER,
+ t->GetParameters()->get(0)->GetLexicalUnitType());
+ EXPECT_EQ(12, t->GetParameters()->get(0)->GetIntegerValue());
+ EXPECT_EQ(Value::IDENT,
+ t->GetParameters()->get(2)->GetLexicalUnitType());
+ EXPECT_EQ(Identifier::AUTO,
+ t->GetParameters()->get(2)->GetIdentifier().ident());
+
+ a.reset(new Parser("rect(auto)"));
+ t.reset(a->ParseAny());
+
+ EXPECT_EQ(static_cast<Value *>(NULL), t.get());
+
+ a.reset(new Parser("rect()"));
+ t.reset(a->ParseAny());
+
+ EXPECT_EQ(static_cast<Value *>(NULL), t.get());
+
+ a.reset(new Parser("rect(13 10 auto 4)"));
+ t.reset(a->ParseAny());
+
+ EXPECT_EQ(13, t->GetParameters()->get(0)->GetIntegerValue());
+
+ a.reset(new Parser("rect(14,10,1,2)"));
+ t.reset(a->ParseAny());
+
+ EXPECT_EQ(14, t->GetParameters()->get(0)->GetIntegerValue());
+
+ a.reset(new Parser("rect(15 10 1)"));
+ t.reset(a->ParseAny());
+
+ EXPECT_EQ(static_cast<Value *>(NULL), t.get());
+
+ a.reset(new Parser("rect(16 10 1 2 3)"));
+ t.reset(a->ParseAny());
+
+ EXPECT_EQ(static_cast<Value *>(NULL), t.get());
+}
+
+TEST_F(ParserTest, background) {
+ scoped_ptr<Declarations> decls(ParseAndExpandBackground("#333"));
+
+ EXPECT_EQ(6, decls->size());
+
+ decls.reset(ParseAndExpandBackground("fff"));
+ EXPECT_TRUE(decls.get());
+ // Not valid for quirks_mode=false
+ EXPECT_FALSE(ParseAndExpandBackground("fff", false));
+
+ decls.reset(ParseAndExpandBackground("fff000"));
+ EXPECT_TRUE(decls.get());
+ // Not valid for quirks_mode=false
+ EXPECT_FALSE(ParseAndExpandBackground("fff000", false));
+
+ // This should now be parsed as background position instead of color.
+ decls.reset(ParseAndExpandBackground("100%"));
+ ASSERT_TRUE(decls.get());
+ ASSERT_EQ(6, decls->size());
+ EXPECT_EQ(Property::BACKGROUND_COLOR, decls->get(0)->prop());
+ EXPECT_EQ(Identifier::TRANSPARENT,
+ decls->get(0)->values()->get(0)->GetIdentifier().ident());
+ EXPECT_EQ(Property::BACKGROUND_POSITION_X, decls->get(4)->prop());
+ EXPECT_EQ("100%", decls->get(4)->values()->get(0)->ToString());
+
+ EXPECT_FALSE(ParseAndExpandBackground(""));
+ EXPECT_FALSE(ParseAndExpandBackground(";"));
+ EXPECT_FALSE(ParseAndExpandBackground("\"string\""));
+ EXPECT_FALSE(ParseAndExpandBackground("normal"));
+
+ decls.reset(ParseAndExpandBackground("inherit"));
+ ASSERT_EQ(6, decls->size());
+ for (int i = 0; i < 6; ++i) {
+ EXPECT_EQ(Identifier::INHERIT,
+ decls->get(i)->values()->get(0)->GetIdentifier().ident());
+ }
+
+ EXPECT_FALSE(ParseAndExpandBackground("inherit none"));
+ EXPECT_FALSE(ParseAndExpandBackground("none inherit"));
+
+ decls.reset(ParseAndExpandBackground("none"));
+ EXPECT_EQ(Identifier::TRANSPARENT,
+ decls->get(0)->values()->get(0)->GetIdentifier().ident());
+ EXPECT_EQ(Identifier::NONE,
+ decls->get(1)->values()->get(0)->GetIdentifier().ident());
+ EXPECT_EQ(Identifier::REPEAT,
+ decls->get(2)->values()->get(0)->GetIdentifier().ident());
+ EXPECT_EQ(Identifier::SCROLL,
+ decls->get(3)->values()->get(0)->GetIdentifier().ident());
+
+ decls.reset(ParseAndExpandBackground("fixed"));
+ EXPECT_EQ(Identifier::FIXED,
+ decls->get(3)->values()->get(0)->GetIdentifier().ident());
+
+ decls.reset(ParseAndExpandBackground("transparent"));
+ EXPECT_EQ(Identifier::TRANSPARENT,
+ decls->get(0)->values()->get(0)->GetIdentifier().ident());
+
+ // IE specific. Firefox should bail out.
+ decls.reset(ParseAndExpandBackground("none url(abc)"));
+ EXPECT_EQ(Value::URI, decls->get(1)->values()->get(0)->GetLexicalUnitType());
+
+ decls.reset(ParseAndExpandBackground("none red fixed"));
+ EXPECT_EQ("#ff0000",
+ decls->get(0)->values()->get(0)->GetColorValue().ToString());
+ EXPECT_EQ(Identifier::NONE,
+ decls->get(1)->values()->get(0)->GetIdentifier().ident());
+ EXPECT_EQ(Identifier::FIXED,
+ decls->get(3)->values()->get(0)->GetIdentifier().ident());
+
+ // The rest are position tests
+ TestBackgroundPosition("none", "0%", "0%");
+ TestBackgroundPosition("10", "10", "50%");
+ TestBackgroundPosition("10 20%", "10", "20%");
+ TestBackgroundPosition("10 100%", "10", "100%");
+ TestBackgroundPosition("top left", "left", "top");
+ TestBackgroundPosition("left top", "left", "top");
+ TestBackgroundPosition("bottom", "50%", "bottom");
+ TestBackgroundPosition("bottom center", "center", "bottom");
+ TestBackgroundPosition("center bottom", "center", "bottom");
+ TestBackgroundPosition("left", "left", "50%");
+ TestBackgroundPosition("left center", "left", "center");
+ TestBackgroundPosition("center left", "left", "center");
+ TestBackgroundPosition("center", "center", "50%");
+ TestBackgroundPosition("center center", "center", "center");
+ TestBackgroundPosition("center 30%", "center", "30%");
+ TestBackgroundPosition("30% center", "30%", "center");
+ TestBackgroundPosition("30% bottom", "30%", "bottom");
+ TestBackgroundPosition("left 30%", "left", "30%");
+ TestBackgroundPosition("30% left", "left", "30%");
+ // IE specific
+ TestBackgroundPosition("30% 20% 50%", "30%", "20%");
+ TestBackgroundPosition("bottom center right", "center", "bottom");
+ TestBackgroundPosition("bottom right top", "right", "bottom");
+ TestBackgroundPosition("bottom top right", "right", "top");
+ TestBackgroundPosition("top right left", "right", "top");
+ TestBackgroundPosition("right left top", "left", "top");
+}
+
+TEST_F(ParserTest, font_family) {
+ scoped_ptr<Parser> a(new Parser(
+ " Arial font 'Sans' system, menu new "));
+ scoped_ptr<Values> t(new Values);
+
+ EXPECT_TRUE(a->ParseFontFamily(t.get()));
+ ASSERT_EQ(4, t->size());
+ EXPECT_EQ(Value::IDENT, t->get(0)->GetLexicalUnitType());
+ EXPECT_EQ("arial font", UnicodeTextToUTF8(t->get(0)->GetIdentifierText()));
+ EXPECT_EQ(Value::STRING, t->get(1)->GetLexicalUnitType());
+ EXPECT_EQ("system", UnicodeTextToUTF8(t->get(2)->GetIdentifierText()));
+ EXPECT_EQ("menu new", UnicodeTextToUTF8(t->get(3)->GetIdentifierText()));
+
+ a.reset(new Parser("Verdana 3"));
+ t.reset(new Values);
+ EXPECT_FALSE(a->ParseFontFamily(t.get()));
+
+ a.reset(new Parser("Verdana :"));
+ t.reset(new Values);
+ EXPECT_FALSE(a->ParseFontFamily(t.get()));
+
+ a.reset(new Parser("Verdana ;"));
+ t.reset(new Values);
+ EXPECT_TRUE(a->ParseFontFamily(t.get()));
+ ASSERT_EQ(1, t->size());
+ EXPECT_EQ(Value::IDENT, t->get(0)->GetLexicalUnitType());
+ EXPECT_EQ("verdana", UnicodeTextToUTF8(t->get(0)->GetIdentifierText()));
+}
+
+TEST_F(ParserTest, font) {
+ scoped_ptr<Parser> a(new Parser("font: caption"));
+ scoped_ptr<Declarations> declarations(a->ParseDeclarations());
+ const char expected_caption_expansion[] =
+ "font: caption; "
+ "font-style: normal; "
+ "font-variant: normal; "
+ "font-weight: normal; "
+ "font-size: 10.6667px; "
+ "line-height: normal; "
+ "font-family: caption";
+ EXPECT_EQ(expected_caption_expansion, declarations->ToString());
+
+ a.reset(new Parser("font: inherit"));
+ declarations.reset(a->ParseDeclarations());
+ const char expected_inherit_expansion[] =
+ "font: inherit; "
+ "font-style: inherit; "
+ "font-variant: inherit; "
+ "font-weight: inherit; "
+ "font-size: inherit; "
+ "line-height: inherit; "
+ "font-family: inherit";
+ EXPECT_EQ(expected_inherit_expansion, declarations->ToString());
+
+ a.reset(new Parser("normal 10px /120% Arial 'Sans'"));
+ scoped_ptr<Values> t(a->ParseFont());
+ ASSERT_EQ(7, t->size());
+ EXPECT_FLOAT_EQ(10, t->get(3)->GetFloatValue());
+ EXPECT_EQ(Value::PERCENT, t->get(4)->GetDimension());
+
+ a.reset(new Parser("italic 10px Arial, Sans"));
+ t.reset(a->ParseFont());
+ ASSERT_EQ(7, t->size());
+ EXPECT_FLOAT_EQ(10, t->get(3)->GetFloatValue());
+ EXPECT_EQ(Identifier::NORMAL, t->get(4)->GetIdentifier().ident());
+
+ a.reset(new Parser("SMALL-caps normal x-large Arial"));
+ t.reset(a->ParseFont());
+ ASSERT_EQ(6, t->size());
+ EXPECT_EQ(Identifier::NORMAL, t->get(0)->GetIdentifier().ident());
+ EXPECT_EQ(Identifier::SMALL_CAPS, t->get(1)->GetIdentifier().ident());
+ EXPECT_EQ(Identifier::X_LARGE, t->get(3)->GetIdentifier().ident());
+ EXPECT_EQ(Identifier::NORMAL, t->get(4)->GetIdentifier().ident());
+
+ a.reset(new Parser("bolder 100 120 Arial"));
+ t.reset(a->ParseFont());
+ ASSERT_EQ(6, t->size());
+ EXPECT_EQ(100, t->get(2)->GetIntegerValue());
+ EXPECT_EQ(120, t->get(3)->GetIntegerValue());
+ EXPECT_EQ(Identifier::NORMAL, t->get(4)->GetIdentifier().ident());
+
+ a.reset(new Parser("10px normal"));
+ t.reset(a->ParseFont());
+ ASSERT_EQ(6, t->size());
+ EXPECT_EQ(10, t->get(3)->GetIntegerValue());
+ EXPECT_EQ(Identifier::NORMAL, t->get(5)->GetIdentifier().ident());
+
+ a.reset(new Parser("normal 10px "));
+ t.reset(a->ParseFont());
+ EXPECT_EQ(5, t->size()) << "missing font-family should be allowed";
+
+ a.reset(new Parser("10px/12pt "));
+ t.reset(a->ParseFont());
+ EXPECT_EQ(5, t->size()) << "missing font-family should be allowed";
+
+ a.reset(new Parser("menu 10px"));
+ t.reset(a->ParseFont());
+ EXPECT_EQ(static_cast<Values *>(NULL), t.get())
+ << "system font with extra value";
+
+ a.reset(new Parser("Arial, menu "));
+ t.reset(a->ParseFont());
+ EXPECT_EQ(static_cast<Values *>(NULL), t.get()) << "missing font-size";
+
+ a.reset(new Parser("transparent 10px "));
+ t.reset(a->ParseFont());
+ EXPECT_EQ(static_cast<Values *>(NULL), t.get()) << "unknown property";
+
+ a.reset(new Parser("normal / 10px Arial"));
+ t.reset(a->ParseFont());
+ EXPECT_EQ(static_cast<Values *>(NULL), t.get())
+ << "line-height without font-size";
+
+ a.reset(new Parser("normal 10px/ Arial"));
+ t.reset(a->ParseFont());
+ EXPECT_EQ(static_cast<Values *>(NULL), t.get())
+ << "slash without line-height";
+
+ a.reset(new Parser("normal 10px Arial #333"));
+ t.reset(a->ParseFont());
+ EXPECT_EQ(static_cast<Values *>(NULL), t.get()) << "invalid type";
+}
+
+TEST_F(ParserTest, values) {
+ scoped_ptr<Parser> a(new Parser(
+ "rgb(12,25,30) url(blah) url('blah.png') 12% !important 'arial'"));
+ scoped_ptr<Values> t(a->ParseValues(Property::OTHER));
+
+ ASSERT_EQ(4, t->size());
+ EXPECT_EQ(Value::COLOR, t->get(0)->GetLexicalUnitType());
+ EXPECT_EQ(Value::URI, t->get(1)->GetLexicalUnitType());
+ EXPECT_EQ(Value::URI, t->get(2)->GetLexicalUnitType());
+ EXPECT_EQ(Value::NUMBER, t->get(3)->GetLexicalUnitType());
+ EXPECT_EQ(Value::PERCENT, t->get(3)->GetDimension());
+
+ a.reset(new Parser("rgb( 12, 25,30) @ignored url( blah )"
+ " rect(12 10 auto 200px)"
+ " { should be {nested }discarded } ident;"));
+ t.reset(a->ParseValues(Property::OTHER));
+
+ ASSERT_EQ(4, t->size());
+ EXPECT_EQ(Value::COLOR, t->get(0)->GetLexicalUnitType());
+ EXPECT_EQ(Value::URI, t->get(1)->GetLexicalUnitType());
+ EXPECT_EQ(Value::RECT, t->get(2)->GetLexicalUnitType());
+ EXPECT_EQ(Value::IDENT, t->get(3)->GetLexicalUnitType());
+ EXPECT_EQ("ident", UnicodeTextToUTF8(t->get(3)->GetIdentifierText()));
+
+ // test value copy constructor.
+ scoped_ptr<Value> val(new Value(*(t->get(2))));
+ EXPECT_EQ(Value::RECT, val->GetLexicalUnitType());
+ ASSERT_EQ(4, val->GetParameters()->size());
+ EXPECT_EQ(Value::NUMBER,
+ val->GetParameters()->get(0)->GetLexicalUnitType());
+ EXPECT_EQ(12, val->GetParameters()->get(0)->GetIntegerValue());
+ EXPECT_EQ(Value::IDENT,
+ val->GetParameters()->get(2)->GetLexicalUnitType());
+ EXPECT_EQ("auto", UnicodeTextToUTF8(val->GetParameters()->get(2)
+ ->GetIdentifierText()));
+}
+
+void ParseFontFamily(Parser* parser) {
+ Values values;
+ parser->ParseFontFamily(&values);
+}
+
+TEST_F(ParserTest, ParseBlock) {
+ static const char* truetestcases[] = {
+ "{{{{}}}} serif",
+ "{ { { { } } } } serif", // whitespace
+ "{@ident1{{ @ident {}}}} serif", // @-idents
+ "{{}} @ident {{}} @ident serif", // multiple blocks
+ "{{ident{{}ident2}}} serif", // idents
+ };
+ for (int i = 0; i < arraysize(truetestcases); ++i) {
+ SCOPED_TRACE(truetestcases[i]);
+ Values values;
+ EXPECT_TRUE(Parser(truetestcases[i]).ParseFontFamily(&values));
+ ASSERT_EQ(1, values.size());
+ EXPECT_EQ(Value::IDENT, values.get(0)->GetLexicalUnitType());
+ EXPECT_EQ("serif", UnicodeTextToUTF8(values.get(0)->GetIdentifierText()));
+ }
+
+ static const char* falsetestcases[] = {
+ "{{{{}}} serif", // too many opens
+ "{{{{}}}}} serif", // too many closes
+ "{{{{}}}}}", // no tokenxs
+ };
+ for (int i = 0; i < arraysize(falsetestcases); ++i) {
+ SCOPED_TRACE(falsetestcases[i]);
+ Values values;
+ Parser(falsetestcases[i]).ParseFontFamily(&values);
+ EXPECT_EQ(0, values.size());
+ }
+
+}
+
+
+TEST_F(ParserTest, declarations) {
+ scoped_ptr<Parser> a(new Parser(
+ "color: #333; line-height: 1.3;"
+ "text-align: justify; font-family: \"Gill Sans MT\","
+ "\"Gill Sans\", GillSans, Arial, Helvetica, sans-serif"));
+ scoped_ptr<Declarations> t(a->ParseDeclarations());
+
+ // Declarations is a vector of Declaration, and we go through them:
+ ASSERT_EQ(4, t->size());
+ EXPECT_EQ(Property::COLOR, t->get(0)->prop());
+ EXPECT_EQ(Property::LINE_HEIGHT, t->get(1)->prop());
+ EXPECT_EQ(Property::TEXT_ALIGN, t->get(2)->prop());
+ EXPECT_EQ(Property::FONT_FAMILY, t->get(3)->prop());
+
+ ASSERT_EQ(1, t->get(0)->values()->size());
+ EXPECT_EQ(Value::COLOR, t->get(0)->values()->get(0)->GetLexicalUnitType());
+ EXPECT_EQ("#333333", t->get(0)->values()->get(0)->GetColorValue().ToString());
+
+ ASSERT_EQ(6, (*t->get(3)->values()).size());
+ EXPECT_EQ(Value::STRING,
+ t->get(3)->values()->get(0)->GetLexicalUnitType());
+ EXPECT_EQ("Gill Sans MT",
+ UnicodeTextToUTF8(t->get(3)->values()->get(0)->GetStringValue()));
+
+ a.reset(new Parser(
+ "background-color: 333; color: \"abcdef\";"
+ "background-color: #red; color: \"white\";"
+ "background-color: rgb(255, 10%, 10)"));
+ t.reset(a->ParseDeclarations());
+
+ ASSERT_EQ(4, t->size()) << "#red is not valid";
+ EXPECT_EQ(Property::BACKGROUND_COLOR, t->get(0)->prop());
+ EXPECT_EQ(Property::COLOR, t->get(1)->prop());
+ EXPECT_EQ(Property::COLOR, t->get(2)->prop());
+ EXPECT_EQ(Property::BACKGROUND_COLOR, t->get(3)->prop());
+ ASSERT_EQ(1, t->get(0)->values()->size());
+ EXPECT_EQ(Value::COLOR, t->get(0)->values()->get(0)->GetLexicalUnitType());
+ EXPECT_EQ("#333333", t->get(0)->values()->get(0)->GetColorValue().ToString());
+ ASSERT_EQ(1, t->get(1)->values()->size());
+ EXPECT_EQ(Value::COLOR, t->get(1)->values()->get(0)->GetLexicalUnitType());
+ EXPECT_EQ("#abcdef", t->get(1)->values()->get(0)->GetColorValue().ToString());
+ ASSERT_EQ(1, t->get(2)->values()->size());
+ EXPECT_EQ(Value::COLOR, t->get(2)->values()->get(0)->GetLexicalUnitType());
+ EXPECT_EQ("#ffffff", t->get(2)->values()->get(0)->GetColorValue().ToString());
+ ASSERT_EQ(1, t->get(3)->values()->size());
+ EXPECT_EQ(Value::COLOR, t->get(3)->values()->get(0)->GetLexicalUnitType());
+ EXPECT_EQ("#ff190a", t->get(3)->values()->get(0)->GetColorValue().ToString());
+
+ // expand background
+ a.reset(new Parser("background: #333 fixed no-repeat; "));
+ t.reset(a->ParseDeclarations());
+ ASSERT_EQ(7, t->size());
+ EXPECT_EQ(Property::BACKGROUND, t->get(0)->prop());
+ EXPECT_EQ(3, t->get(0)->values()->size());
+ EXPECT_EQ(Property::BACKGROUND_COLOR, t->get(1)->prop());
+ EXPECT_EQ(1, t->get(1)->values()->size());
+ EXPECT_EQ(Property::BACKGROUND_IMAGE, t->get(2)->prop());
+ EXPECT_EQ(1, t->get(2)->values()->size());
+ EXPECT_EQ(Property::BACKGROUND_REPEAT, t->get(3)->prop());
+ EXPECT_EQ(1, t->get(3)->values()->size());
+ EXPECT_EQ(Property::BACKGROUND_ATTACHMENT, t->get(4)->prop());
+ EXPECT_EQ(1, t->get(4)->values()->size());
+ EXPECT_EQ(Property::BACKGROUND_POSITION_X, t->get(5)->prop());
+ EXPECT_EQ(1, t->get(5)->values()->size());
+ EXPECT_EQ(Property::BACKGROUND_POSITION_Y, t->get(6)->prop());
+ EXPECT_EQ(1, t->get(6)->values()->size());
+
+ // expand font
+ a.reset(new Parser("font: small-caps 24px Arial 'Sans', monospace; "));
+ t.reset(a->ParseDeclarations());
+ ASSERT_EQ(7, t->size());
+ EXPECT_EQ(Property::FONT, t->get(0)->prop());
+ EXPECT_EQ(8, t->get(0)->values()->size());
+ EXPECT_EQ(Property::FONT_STYLE, t->get(1)->prop());
+ EXPECT_EQ(Property::FONT_VARIANT, t->get(2)->prop());
+ EXPECT_EQ(Property::FONT_WEIGHT, t->get(3)->prop());
+ EXPECT_EQ(Property::FONT_SIZE, t->get(4)->prop());
+ EXPECT_EQ(Property::LINE_HEIGHT, t->get(5)->prop());
+ EXPECT_EQ(Property::FONT_FAMILY, t->get(6)->prop());
+ ASSERT_EQ(3, t->get(6)->values()->size());
+ EXPECT_EQ("monospace", UnicodeTextToUTF8(t->get(6)->values()->get(2)->
+ GetIdentifierText()));
+
+ a.reset(new Parser(
+ "{font-size: #333; color:red"));
+ t.reset(a->ParseDeclarations());
+ ASSERT_EQ(1, t->size());
+ EXPECT_EQ(Property::COLOR, t->get(0)->prop());
+
+ a.reset(new Parser(
+ "{font-size: #333; color:red"));
+ a->set_quirks_mode(false);
+ t.reset(a->ParseDeclarations());
+ EXPECT_EQ(0, t->size());
+
+ a.reset(new Parser(
+ "font-size {background: #333; color:red"));
+ t.reset(a->ParseDeclarations());
+ ASSERT_EQ(1, t->size());
+ EXPECT_EQ(Property::COLOR, t->get(0)->prop());
+
+ a.reset(new Parser(
+ "font-size {background: #333; color:red"));
+ a->set_quirks_mode(false);
+ t.reset(a->ParseDeclarations());
+ EXPECT_EQ(0, t->size());
+
+ a.reset(new Parser(
+ "font-size }background: #333; color:red"));
+ t.reset(a->ParseDeclarations());
+ EXPECT_EQ(0, t->size());
+
+ a.reset(new Parser(
+ "font-size }background: #333; color:red"));
+ a->set_quirks_mode(false);
+ t.reset(a->ParseDeclarations());
+ EXPECT_EQ(0, t->size());
+
+ a.reset(new Parser(
+ "top:1px; {font-size: #333; color:red}"));
+ t.reset(a->ParseDeclarations());
+ ASSERT_EQ(2, t->size());
+ EXPECT_EQ(Property::TOP, t->get(0)->prop());
+ EXPECT_EQ(Property::COLOR, t->get(1)->prop());
+
+ a.reset(new Parser(
+ "top:1px; {font-size: #333; color:red}"));
+ a->set_quirks_mode(false);
+ t.reset(a->ParseDeclarations());
+ ASSERT_EQ(1, t->size());
+ EXPECT_EQ(Property::TOP, t->get(0)->prop());
+}
+
+TEST_F(ParserTest, illegal_constructs) {
+ scoped_ptr<Parser> a(new Parser("width: {$width}"));
+ scoped_ptr<Declarations> t(a->ParseDeclarations());
+
+ ASSERT_EQ(1, t->size());
+ EXPECT_EQ(Property::WIDTH, t->get(0)->prop());
+ EXPECT_EQ(0, t->get(0)->values()->size());
+
+ a.reset(new Parser("font-family: \"Gill Sans MT;"));
+ t.reset(a->ParseDeclarations());
+
+ ASSERT_EQ(1, t->size());
+ EXPECT_EQ(Property::FONT_FAMILY, t->get(0)->prop());
+ ASSERT_EQ(1, t->get(0)->values()->size());
+ EXPECT_EQ(Value::STRING, t->get(0)->values()->get(0)->GetLexicalUnitType());
+ EXPECT_EQ("Gill Sans MT;",
+ UnicodeTextToUTF8(t->get(0)->values()->get(0)->GetStringValue()));
+
+ a.reset(new Parser("font-family: 'Gill Sans MT"));
+ t.reset(a->ParseDeclarations());
+
+ ASSERT_EQ(1, t->size());
+ EXPECT_EQ(Property::FONT_FAMILY, t->get(0)->prop());
+ ASSERT_EQ(1, t->get(0)->values()->size());
+ EXPECT_EQ(Value::STRING, t->get(0)->values()->get(0)->GetLexicalUnitType());
+ EXPECT_EQ("Gill Sans MT",
+ UnicodeTextToUTF8(t->get(0)->values()->get(0)->GetStringValue()));
+}
+
+TEST_F(ParserTest, value_validation) {
+ scoped_ptr<Parser> a(new Parser("width: {$width}"));
+ scoped_ptr<Declarations> t(a->ParseDeclarations());
+
+ // Let's take border-color as an example. It only accepts color and the
+ // transparent keyword in particular (and inherit is a common one).
+ a.reset(new Parser(
+ "border-color: \"string\"; "
+ "border-color: url(\"abc\"); "
+ "border-color: 12345; "
+ "border-color: none; "
+ "border-color: inherited; "
+ "border-color: red; "
+ "border-color: #123456; "
+ "border-color: transparent; "
+ "border-color: inherit; "
+ "border-color: unknown; "
+ ));
+ t.reset(a->ParseDeclarations());
+
+ ASSERT_EQ(4, t->size());
+ EXPECT_EQ(Value::COLOR, t->get(0)->values()->get(0)->GetLexicalUnitType());
+ EXPECT_EQ(Value::COLOR, t->get(1)->values()->get(0)->GetLexicalUnitType());
+ EXPECT_EQ(Value::IDENT, t->get(2)->values()->get(0)->GetLexicalUnitType());
+ EXPECT_EQ(Identifier::TRANSPARENT,
+ t->get(2)->values()->get(0)->GetIdentifier().ident());
+ EXPECT_EQ(Value::IDENT, t->get(3)->values()->get(0)->GetLexicalUnitType());
+ EXPECT_EQ(Identifier::INHERIT,
+ t->get(3)->values()->get(0)->GetIdentifier().ident());
+}
+
+TEST_F(ParserTest, universalselector) {
+ Parser p("*");
+ scoped_ptr<SimpleSelectors> t(p.ParseSimpleSelectors(false));
+
+ EXPECT_EQ(SimpleSelectors::NONE, t->combinator());
+ ASSERT_EQ(1, t->size());
+ EXPECT_EQ(SimpleSelector::UNIVERSAL, t->get(0)->type());
+}
+
+TEST_F(ParserTest, universalselectorcondition) {
+ scoped_ptr<Parser> a(new Parser(" *[foo=bar]"));
+ scoped_ptr<SimpleSelectors> t(a->ParseSimpleSelectors(true));
+
+ EXPECT_EQ(SimpleSelectors::DESCENDANT, t->combinator());
+ ASSERT_EQ(2, t->size());
+ EXPECT_EQ(SimpleSelector::UNIVERSAL, t->get(0)->type());
+ EXPECT_EQ(SimpleSelector::EXACT_ATTRIBUTE, t->get(1)->type());
+
+ a.reset(new Parser(" *[foo="));
+ t.reset(a->ParseSimpleSelectors(true));
+
+ // This is not a valid selector.
+ EXPECT_FALSE(t.get());
+}
+
+TEST_F(ParserTest, comment_breaking_descendant_combinator) {
+ Parser p(" a b/*foo*/c /*foo*/d/*foo*/ e { }");
+ scoped_ptr<Ruleset> t(p.ParseRuleset());
+
+ ASSERT_EQ(1, t->selectors().size());
+ const Selector* s = t->selectors().get(0);
+ ASSERT_EQ(5, s->size());
+ EXPECT_EQ(SimpleSelectors::NONE, s->get(0)->combinator());
+ EXPECT_EQ(SimpleSelectors::DESCENDANT, s->get(1)->combinator());
+ EXPECT_EQ(SimpleSelectors::DESCENDANT, s->get(2)->combinator());
+ EXPECT_EQ(SimpleSelectors::DESCENDANT, s->get(3)->combinator());
+ EXPECT_EQ(SimpleSelectors::DESCENDANT, s->get(4)->combinator());
+}
+
+TEST_F(ParserTest, comment_breaking_child_combinator) {
+ Parser p(" a >b/*f>oo*/>c /*fo>o*/>d/*f>oo*/ > e>f { }");
+ scoped_ptr<Ruleset> t(p.ParseRuleset());
+
+ ASSERT_EQ(1, t->selectors().size());
+ const Selector* s = t->selectors().get(0);
+ ASSERT_EQ(6, s->size());
+ EXPECT_EQ(SimpleSelectors::NONE, s->get(0)->combinator());
+ EXPECT_EQ(SimpleSelectors::CHILD, s->get(1)->combinator());
+ EXPECT_EQ(SimpleSelectors::CHILD, s->get(2)->combinator());
+ EXPECT_EQ(SimpleSelectors::CHILD, s->get(3)->combinator());
+ EXPECT_EQ(SimpleSelectors::CHILD, s->get(4)->combinator());
+ EXPECT_EQ(SimpleSelectors::CHILD, s->get(5)->combinator());
+}
+
+TEST_F(ParserTest, ruleset_starts_with_combinator) {
+ Parser p(" >a { }");
+ scoped_ptr<Ruleset> t(p.ParseRuleset());
+
+ // This is not a valid selector.
+ EXPECT_FALSE(t.get());
+}
+
+
+TEST_F(ParserTest, simple_selectors) {
+ // First, a basic case
+ scoped_ptr<Parser> a(new Parser("*[lang|=fr]"));
+ scoped_ptr<SimpleSelectors> t(a->ParseSimpleSelectors(false));
+
+ EXPECT_EQ(SimpleSelectors::NONE, t->combinator());
+
+ {
+ const SimpleSelector* c = t->get(0);
+ ASSERT_TRUE(c != NULL);
+ ASSERT_EQ(SimpleSelector::UNIVERSAL, c->type());
+ }
+ {
+ const SimpleSelector* c = t->get(1);
+ ASSERT_TRUE(c != NULL);
+ ASSERT_EQ(SimpleSelector::BEGIN_HYPHEN_ATTRIBUTE, c->type());
+ EXPECT_EQ("lang", UnicodeTextToUTF8(c->attribute()));
+ EXPECT_EQ("fr", UnicodeTextToUTF8(c->value()));
+ }
+
+ // Now, a very complex one.
+ a.reset(new Parser(
+ "> P:first_child:hover[class~='hidden'][width]#content"
+ "[id*=logo][id^=logo][id$=\"logo\"]"
+ "[lang=en].anotherclass.moreclass #next"));
+ t.reset(a->ParseSimpleSelectors(true));
+
+ EXPECT_EQ(SimpleSelectors::CHILD, t->combinator());
+
+ // We're going to go through the conditions in reverse order, for kicks.
+ SimpleSelectors::const_reverse_iterator it = t->rbegin();
+
+ // .moreclass
+ {
+ SimpleSelector* c = *it++;
+ ASSERT_EQ(SimpleSelector::CLASS, c->type());
+ EXPECT_EQ("moreclass", UnicodeTextToUTF8(c->value()));
+ }
+
+ // .anotherclass
+ {
+ SimpleSelector* c = *it++;
+ ASSERT_EQ(SimpleSelector::CLASS, c->type());
+ EXPECT_EQ("anotherclass", UnicodeTextToUTF8(c->value()));
+ }
+
+ // EXACT_ATTRIBUTE [lang=en]
+ {
+ SimpleSelector* c = *it++;
+ ASSERT_EQ(SimpleSelector::EXACT_ATTRIBUTE, c->type());
+ EXPECT_EQ("lang", UnicodeTextToUTF8(c->attribute()));
+ EXPECT_EQ("en", UnicodeTextToUTF8(c->value()));
+ }
+
+ // END_WITH_ATTRIBUTE [id$="logo"]
+ {
+ SimpleSelector* c = *it++;
+ ASSERT_EQ(SimpleSelector::END_WITH_ATTRIBUTE, c->type());
+ EXPECT_EQ("id", UnicodeTextToUTF8(c->attribute()));
+ EXPECT_EQ("logo", UnicodeTextToUTF8(c->value()));
+ }
+
+ // BEGIN_WITH_ATTRIBUTE [id^=logo]
+ {
+ SimpleSelector* c = *it++;
+ ASSERT_EQ(SimpleSelector::BEGIN_WITH_ATTRIBUTE, c->type());
+ EXPECT_EQ("id", UnicodeTextToUTF8(c->attribute()));
+ EXPECT_EQ("logo", UnicodeTextToUTF8(c->value()));
+ }
+
+ // SUBSTRING_ATTRIBUTE [id*=logo]
+ {
+ SimpleSelector* c = *it++;
+ ASSERT_EQ(SimpleSelector::SUBSTRING_ATTRIBUTE, c->type());
+ EXPECT_EQ("id", UnicodeTextToUTF8(c->attribute()));
+ EXPECT_EQ("logo", UnicodeTextToUTF8(c->value()));
+ }
+
+ // ID #content
+ {
+ SimpleSelector* c = *it++;
+ ASSERT_EQ(SimpleSelector::ID, c->type());
+ EXPECT_EQ("content", UnicodeTextToUTF8(c->value()));
+ }
+
+ // EXIST_ATTRIBUTE [width]
+ {
+ SimpleSelector* c = *it++;
+ ASSERT_EQ(SimpleSelector::EXIST_ATTRIBUTE, c->type());
+ EXPECT_EQ("width", UnicodeTextToUTF8(c->attribute()));
+ }
+
+ // ONE_OF_ATTRIBUTE [class~=hidden]
+ {
+ SimpleSelector* c = *it++;
+ ASSERT_EQ(SimpleSelector::ONE_OF_ATTRIBUTE, c->type());
+ EXPECT_EQ("class", UnicodeTextToUTF8(c->attribute()));
+ EXPECT_EQ("hidden", UnicodeTextToUTF8(c->value()));
+ }
+
+ // PSEUDOCLASS :hover
+ {
+ SimpleSelector* c = *it++;
+ ASSERT_EQ(SimpleSelector::PSEUDOCLASS, c->type());
+ EXPECT_EQ("hover", UnicodeTextToUTF8(c->pseudoclass()));
+ }
+
+ // PSEUDOCLASS :first_child
+ {
+ SimpleSelector* c = *it++;
+ ASSERT_EQ(SimpleSelector::PSEUDOCLASS, c->type());
+ EXPECT_EQ("first_child", UnicodeTextToUTF8(c->pseudoclass()));
+ }
+
+ // P
+ {
+ SimpleSelector* c = *it++;
+ ASSERT_EQ(SimpleSelector::ELEMENT_TYPE, c->type());
+ EXPECT_EQ("P", UnicodeTextToUTF8(c->element_text()));
+ EXPECT_EQ(kHtmlTagP, c->element_type());
+ }
+
+
+ ASSERT_TRUE(it == t->rend());
+}
+
+TEST_F(ParserTest, bad_simple_selectors) {
+ scoped_ptr<Parser> a;
+ scoped_ptr<SimpleSelectors> t;
+
+ // valid or not?
+ a.reset(new Parser(""));
+ t.reset(a->ParseSimpleSelectors(false));
+ EXPECT_FALSE(t.get());
+
+ a.reset(new Parser("{}"));
+ t.reset(a->ParseSimpleSelectors(false));
+ EXPECT_FALSE(t.get());
+
+ a.reset(new Parser("#"));
+ t.reset(a->ParseSimpleSelectors(false));
+ EXPECT_FALSE(t.get());
+
+ a.reset(new Parser("# {"));
+ t.reset(a->ParseSimpleSelectors(false));
+ EXPECT_FALSE(t.get());
+
+ a.reset(new Parser("#{"));
+ t.reset(a->ParseSimpleSelectors(false));
+ EXPECT_FALSE(t.get());
+
+ a.reset(new Parser("##"));
+ t.reset(a->ParseSimpleSelectors(false));
+ EXPECT_FALSE(t.get());
+
+ a.reset(new Parser("*[class="));
+ t.reset(a->ParseSimpleSelectors(false));
+ EXPECT_FALSE(t.get());
+
+ a.reset(new Parser("*[class=hidden];"));
+ t.reset(a->ParseSimpleSelectors(false));
+ EXPECT_FALSE(t.get());
+
+ a.reset(new Parser("*[class=hidden].;"));
+ t.reset(a->ParseSimpleSelectors(false));
+ EXPECT_FALSE(t.get());
+
+ a.reset(new Parser("#a {"));
+ t.reset(a->ParseSimpleSelectors(false));
+ EXPECT_TRUE(t.get());
+}
+
+TEST_F(ParserTest, selectors) {
+ scoped_ptr<Parser> a(new Parser("h1 p #id {"));
+ scoped_ptr<Selectors> t(a->ParseSelectors());
+ EXPECT_TRUE(t.get());
+ ASSERT_EQ(1, t->size());
+ EXPECT_EQ(3, (*t)[0]->size());
+ EXPECT_EQ('{', *a->getpos());
+
+ a.reset(new Parser(" h1 p #id , div.p > h2 > div.t #id"));
+ t.reset(a->ParseSelectors());
+ EXPECT_TRUE(t.get());
+ ASSERT_EQ(2, t->size());
+ EXPECT_EQ(3, (*t)[0]->size());
+ EXPECT_EQ(4, (*t)[1]->size());
+ EXPECT_TRUE(a->Done());
+
+ a.reset(new Parser("/*c*/h1 p #id/*c*/,/*c*/div.p > h2 > div.t #id/*c*/"));
+ t.reset(a->ParseSelectors());
+ EXPECT_TRUE(t.get());
+ ASSERT_EQ(2, t->size());
+ EXPECT_EQ(3, (*t)[0]->size());
+ EXPECT_EQ(4, (*t)[1]->size());
+ EXPECT_TRUE(a->Done());
+
+ a.reset(new Parser("{}"));
+ t.reset(a->ParseSelectors());
+ EXPECT_FALSE(t.get());
+ EXPECT_EQ('{', *a->getpos());
+
+ a.reset(new Parser(""));
+ t.reset(a->ParseSelectors());
+ EXPECT_FALSE(t.get());
+ EXPECT_TRUE(a->Done());
+
+ a.reset(new Parser(" ,h1 p #id {"));
+ t.reset(a->ParseSelectors());
+ EXPECT_FALSE(t.get());
+ EXPECT_EQ('{', *a->getpos());
+
+ a.reset(new Parser(" , {"));
+ t.reset(a->ParseSelectors());
+ EXPECT_FALSE(t.get());
+ EXPECT_EQ('{', *a->getpos());
+
+ a.reset(new Parser("h1 p #id, {"));
+ t.reset(a->ParseSelectors());
+ EXPECT_FALSE(t.get());
+ EXPECT_EQ('{', *a->getpos());
+
+ a.reset(new Parser("h1 p #id, {"));
+ t.reset(a->ParseSelectors());
+ EXPECT_FALSE(t.get());
+ EXPECT_EQ('{', *a->getpos());
+
+ a.reset(new Parser("h1 p #id;"));
+ t.reset(a->ParseSelectors());
+ EXPECT_FALSE(t.get());
+ EXPECT_TRUE(a->Done());
+
+ a.reset(new Parser(" h1 p[class=/*{*/ #id , h2 #id"));
+ t.reset(a->ParseSelectors());
+ EXPECT_FALSE(t.get());
+ EXPECT_TRUE(a->Done());
+
+ a.reset(new Parser(" h1 #id. , h2 #id"));
+ t.reset(a->ParseSelectors());
+ EXPECT_FALSE(t.get());
+ EXPECT_TRUE(a->Done());
+}
+
+TEST_F(ParserTest, rulesets) {
+ scoped_ptr<Parser> a(new Parser("h1 p #id ;"));
+ scoped_ptr<Ruleset> t(a->ParseRuleset());
+
+ EXPECT_EQ(static_cast<Ruleset *>(NULL), t.get());
+
+ a.reset(new Parser(", { }"));
+ t.reset(a->ParseRuleset());
+
+ EXPECT_FALSE(t.get());
+
+ a.reset(new Parser(", h1 p #id, { };"));
+ t.reset(a->ParseRuleset());
+
+ EXPECT_FALSE(t.get());
+
+ a.reset(new Parser(
+ "h1 p + #id { font-size: 7px; width:10pt !important;}"));
+ t.reset(a->ParseRuleset());
+
+ ASSERT_EQ(1, (*t).selectors().size());
+ ASSERT_EQ(3, (*t).selector(0).size());
+ EXPECT_EQ(SimpleSelectors::SIBLING,
+ (*(*t).selectors()[0])[2]->combinator());
+ ASSERT_EQ(2, (*t).declarations().size());
+ EXPECT_EQ(false, (*t).declarations()[0]->IsImportant());
+ EXPECT_EQ(Property::WIDTH, (*t).declarations()[1]->prop());
+ EXPECT_EQ(true, (*t).declarations()[1]->IsImportant());
+
+ a.reset(new Parser("h1 p + #id , h1:first_child { font-size: 10px; }"));
+ t.reset(a->ParseRuleset());
+
+ ASSERT_EQ(2, (*t).selectors().size());
+ ASSERT_EQ(3, (*t).selector(0).size());
+ ASSERT_EQ(1, (*t).selector(1).size());
+ EXPECT_EQ(SimpleSelectors::SIBLING,
+ (*(*t).selectors()[0])[2]->combinator());
+ ASSERT_EQ(1, (*t).declarations().size());
+ EXPECT_EQ(false, (*t).declarations()[0]->IsImportant());
+}
+
+TEST_F(ParserTest, atrules) {
+ scoped_ptr<Parser> a(new Parser(
+ "@IMPORT url(assets/style.css) screen,printer"));
+ scoped_ptr<Stylesheet> t(new Stylesheet());
+ a->ParseAtrule(t.get());
+
+ ASSERT_EQ(1, (*t).imports().size());
+ EXPECT_EQ("assets/style.css", UnicodeTextToUTF8((*t).import(0).link));
+ EXPECT_EQ(true, a->Done());
+
+ a.reset(new Parser("@charset \"ISO-8859-1\" ;"));
+ t.reset(new Stylesheet());
+ a->ParseAtrule(t.get());
+
+ EXPECT_EQ(true, a->Done());
+
+ a.reset(new Parser(
+ "@media print,screen {\n\tbody { font-size: 10pt }\n}"));
+ t.reset(new Stylesheet());
+ a->ParseAtrule(t.get());
+
+ ASSERT_EQ(1, (*t).rulesets().size());
+ ASSERT_EQ(1, (*t).ruleset(0).selectors().size());
+ ASSERT_EQ(2, (*t).ruleset(0).media().size());
+ EXPECT_EQ("print", UnicodeTextToUTF8((*t).ruleset(0).medium(0)));
+ EXPECT_EQ("screen", UnicodeTextToUTF8((*t).ruleset(0).medium(1)));
+ ASSERT_EQ(1, (*(*t).ruleset(0).selectors()[0]).size());
+ EXPECT_EQ(kHtmlTagBody,
+ (*t).ruleset(0).selector(0)[0]->get(0)->element_type());
+ EXPECT_EQ(Property::FONT_SIZE,
+ (*t).ruleset(0).declarations()[0]->prop());
+ EXPECT_EQ(true, a->Done());
+
+ a.reset(new Parser(
+ "@page :left { margin-left: 4cm; margin-right: 3cm; }"));
+ t.reset(new Stylesheet());
+ a->ParseAtrule(t.get());
+
+ EXPECT_EQ(0, (*t).rulesets().size());
+ EXPECT_EQ(true, a->Done());
+
+ // Make sure media strings can be shared between multiple rulesets.
+ a.reset(new Parser(
+ "@media print { a { color: red; } p { color: blue; } }"));
+ t.reset(new Stylesheet());
+ a->ParseAtrule(t.get());
+
+ ASSERT_EQ(2, t->rulesets().size());
+ EXPECT_EQ("print", UnicodeTextToUTF8(t->ruleset(0).medium(0)));
+ EXPECT_EQ("print", UnicodeTextToUTF8(t->ruleset(1).medium(0)));
+ t->ToString(); // Make sure it can be written as a string.
+}
+
+TEST_F(ParserTest, stylesheets) {
+ scoped_ptr<Parser> a(new Parser("\n"
+ "\t@import \"mystyle.css\" all; "
+ "@import url(\"mystyle.css\" );\n"
+ "\tBODY {\n"
+ "color:black !important; \n"
+ "background: white !important; }\n"
+ "* {\n"
+ "\tcolor: inherit !important;\n"
+ "background: transparent;\n"
+ "}\n"
+ "\n"
+ "<!-- html comments * { font-size: 1 } -->\n"
+ "H1 + *[REL-up] {}"));
+
+ scoped_ptr<Stylesheet> t(a->ParseStylesheet());
+ EXPECT_EQ(Parser::kNoError, a->errors_seen_mask());
+ ASSERT_EQ(2, t->imports().size());
+ EXPECT_EQ("mystyle.css", UnicodeTextToUTF8(t->import(0).link));
+ ASSERT_EQ(1, t->import(0).media.size());
+ EXPECT_EQ("all", UnicodeTextToUTF8(t->import(0).media[0]));
+ EXPECT_EQ("mystyle.css", UnicodeTextToUTF8(t->import(1).link));
+ // html-style comment should NOT work
+ EXPECT_EQ(4, t->rulesets().size());
+ EXPECT_TRUE(a->Done());
+}
+
+TEST_F(ParserTest, ParseRawStylesheetDoesNotExpand) {
+ {
+ Parser p("a { background: none; }");
+ scoped_ptr<Stylesheet> stylesheet(p.ParseRawStylesheet());
+ ASSERT_EQ(1, stylesheet->rulesets().size());
+ ASSERT_EQ(1, stylesheet->ruleset(0).declarations().size());
+ ASSERT_EQ(1, stylesheet->ruleset(0).declaration(0).values()->size());
+ EXPECT_TRUE(p.Done());
+ }
+ {
+ Parser p("a { font: 12px verdana; }");
+ scoped_ptr<Stylesheet> stylesheet(p.ParseRawStylesheet());
+ ASSERT_EQ(1, stylesheet->rulesets().size());
+ ASSERT_EQ(1, stylesheet->ruleset(0).declarations().size());
+ const Values& values = *stylesheet->ruleset(0).declaration(0).values();
+ ASSERT_EQ(6, values.size());
+ // ParseRaw will expand the values out to:
+ // font: normal normal normal 12px/normal verdana
+ // But it will not expand out the six other declarations.
+ // TODO(sligocki): There has got to be a nicer way to test this.
+ EXPECT_EQ(Identifier::NORMAL, values[0]->GetIdentifier().ident());
+ EXPECT_EQ(Identifier::NORMAL, values[1]->GetIdentifier().ident());
+ EXPECT_EQ(Identifier::NORMAL, values[2]->GetIdentifier().ident());
+ EXPECT_EQ(12.0, values[3]->GetFloatValue());
+ EXPECT_EQ(Value::PX, values[3]->GetDimension());
+ EXPECT_EQ(Identifier::NORMAL, values[4]->GetIdentifier().ident());
+ EXPECT_EQ("verdana", UnicodeTextToUTF8(values[5]->GetIdentifierText()));
+ EXPECT_TRUE(p.Done());
+ }
+}
+
+TEST_F(ParserTest, ParseStylesheetDoesExpand) {
+ {
+ Parser p("a { background: none; }");
+ scoped_ptr<Stylesheet> stylesheet(p.ParseStylesheet());
+ ASSERT_EQ(1, stylesheet->rulesets().size());
+ const Declarations& declarations = stylesheet->ruleset(0).declarations();
+ ASSERT_EQ(7, declarations.size());
+ EXPECT_EQ(Property::BACKGROUND, declarations[0]->prop());
+ EXPECT_EQ(Property::BACKGROUND_COLOR, declarations[1]->prop());
+ EXPECT_EQ(Property::BACKGROUND_IMAGE, declarations[2]->prop());
+ EXPECT_EQ(Property::BACKGROUND_REPEAT, declarations[3]->prop());
+ EXPECT_EQ(Property::BACKGROUND_ATTACHMENT, declarations[4]->prop());
+ EXPECT_EQ(Property::BACKGROUND_POSITION_X, declarations[5]->prop());
+ EXPECT_EQ(Property::BACKGROUND_POSITION_Y, declarations[6]->prop());
+ EXPECT_TRUE(p.Done());
+ }
+ {
+ Parser p("a { font: 12px verdana; }");
+ scoped_ptr<Stylesheet> stylesheet(p.ParseStylesheet());
+ ASSERT_EQ(1, stylesheet->rulesets().size());
+ const Declarations& declarations = stylesheet->ruleset(0).declarations();
+ ASSERT_EQ(7, declarations.size());
+ EXPECT_EQ(Property::FONT, declarations[0]->prop());
+ EXPECT_EQ(6, declarations[0]->values()->size());
+ EXPECT_EQ(Property::FONT_STYLE, declarations[1]->prop());
+ EXPECT_EQ(Property::FONT_VARIANT, declarations[2]->prop());
+ EXPECT_EQ(Property::FONT_WEIGHT, declarations[3]->prop());
+ EXPECT_EQ(Property::FONT_SIZE, declarations[4]->prop());
+ EXPECT_EQ(Property::LINE_HEIGHT, declarations[5]->prop());
+ EXPECT_EQ(Property::FONT_FAMILY, declarations[6]->prop());
+ }
+}
+
+TEST_F(ParserTest, percentage_colors) {
+ Value hundred(100.0, Value::PERCENT);
+ EXPECT_EQ(255, Parser::ValueToRGB(&hundred));
+ Value zero(0.0, Value::PERCENT);
+ EXPECT_EQ(0, Parser::ValueToRGB(&zero));
+}
+
+TEST_F(ParserTest, value_equality) {
+ Value hundred(100.0, Value::PERCENT);
+ Value hundred2(100.0, Value::PERCENT);
+ Value zero(0.0, Value::PERCENT);
+ Identifier auto_ident(Identifier::AUTO);
+ Value ident(auto_ident);
+ EXPECT_TRUE(hundred.Equals(hundred2));
+ EXPECT_FALSE(hundred.Equals(zero));
+ EXPECT_FALSE(hundred.Equals(ident));
+}
+
+TEST_F(ParserTest, Utf8Error) {
+ Parser p("font-family: \"\xCB\xCE\xCC\xE5\"");
+ scoped_ptr<Declarations> declarations(p.ParseDeclarations());
+ EXPECT_EQ(1, declarations->size());
+ EXPECT_EQ(Parser::kUtf8Error, p.errors_seen_mask());
+}
+
+TEST_F(ParserTest, DeclarationError) {
+ Parser p("font-family ; ");
+ scoped_ptr<Declarations> declarations(p.ParseDeclarations());
+ EXPECT_EQ(0, declarations->size());
+ EXPECT_EQ(Parser::kDeclarationError, p.errors_seen_mask());
+}
+
+TEST_F(ParserTest, SelectorError) {
+ Parser p(".bold: { font-weight: bold }");
+ scoped_ptr<Stylesheet> stylesheet(p.ParseStylesheet());
+ EXPECT_EQ(0, stylesheet->rulesets().size());
+ EXPECT_EQ(Parser::kSelectorError, p.errors_seen_mask());
+}
+
+TEST_F(ParserTest, FunctionError) {
+ Parser p("box-shadow: -1px -2px 2px rgba(0, 0, 0, .15)");
+ scoped_ptr<Declarations> declarations(p.ParseDeclarations());
+ EXPECT_EQ(1, declarations->size());
+ EXPECT_EQ(Parser::kFunctionError, p.errors_seen_mask());
+}
+
+TEST_F(ParserTest, MediaError) {
+ Parser p("@media screen and (max-width: 290px) {}");
+ scoped_ptr<Stylesheet> stylesheet(p.ParseStylesheet());
+ EXPECT_EQ(0, stylesheet->rulesets().size());
+ EXPECT_EQ(Parser::kMediaError, p.errors_seen_mask());
+}
+
+TEST_F(ParserTest, AcceptCorrectValues) {
+ // http://code.google.com/p/modpagespeed/issues/detail?id=128
+ Parser p("list-style-type: none");
+ scoped_ptr<Declarations> declarations(p.ParseDeclarations());
+ EXPECT_EQ(1, declarations->size());
+ EXPECT_EQ(Parser::kNoError, p.errors_seen_mask());
+ EXPECT_EQ("list-style-type: none", declarations->ToString());
+}
+
+} // namespace
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/property.cc b/trunk/src/third_party/css_parser/src/webutil/css/property.cc
new file mode 100644
index 0000000..341590d
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/property.cc
@@ -0,0 +1,1150 @@
+/**
+ * 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.
+ */
+
+/* C++ code produced by gperf version 3.0.3 */
+/* Computed positions: -k'1-2,13,$' */
+
+#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \
+ && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \
+ && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \
+ && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \
+ && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \
+ && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \
+ && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \
+ && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \
+ && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \
+ && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \
+ && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \
+ && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \
+ && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \
+ && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \
+ && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \
+ && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \
+ && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \
+ && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \
+ && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \
+ && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \
+ && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \
+ && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \
+ && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126))
+/* The character set is not based on ISO-646. */
+#error "gperf generated tables don't work with this execution character set. Please report a bug to <bug-gnu-gperf@gnu.org>."
+#endif
+
+#line 1 "webutil/css/property.gperf"
+
+#include "webutil/css/property.h"
+
+#include "base/googleinit.h"
+#include "base/logging.h"
+#include "webutil/css/string_util.h"
+
+namespace Css {
+#line 11 "webutil/css/property.gperf"
+struct props {
+ const char *name;
+ Property::Prop id;
+};
+enum
+ {
+ TOTAL_KEYWORDS = 178,
+ MIN_WORD_LENGTH = 3,
+ MAX_WORD_LENGTH = 43,
+ MIN_HASH_VALUE = 17,
+ MAX_HASH_VALUE = 563
+ };
+
+/* maximum key range = 547, duplicates = 0 */
+
+#ifndef GPERF_DOWNCASE
+#define GPERF_DOWNCASE 1
+static unsigned char gperf_downcase[256] =
+ {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
+ 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
+ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
+ 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106,
+ 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121,
+ 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104,
+ 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
+ 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134,
+ 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
+ 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164,
+ 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
+ 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194,
+ 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209,
+ 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224,
+ 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
+ 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254,
+ 255
+ };
+#endif
+
+#ifndef GPERF_CASE_STRNCMP
+#define GPERF_CASE_STRNCMP 1
+static int
+gperf_case_strncmp (register const char *s1, register const char *s2, register unsigned int n)
+{
+ for (; n > 0;)
+ {
+ unsigned char c1 = gperf_downcase[(unsigned char)*s1++];
+ unsigned char c2 = gperf_downcase[(unsigned char)*s2++];
+ if (c1 != 0 && c1 == c2)
+ {
+ n--;
+ continue;
+ }
+ return (int)c1 - (int)c2;
+ }
+ return 0;
+}
+#endif
+
+class PropertyMapper
+{
+private:
+ static inline unsigned int hash (const char *str, unsigned int len);
+public:
+ static const struct props *in_word_set (const char *str, unsigned int len);
+};
+
+inline unsigned int
+PropertyMapper::hash (register const char *str, register unsigned int len)
+{
+ static const unsigned short asso_values[] =
+ {
+ 564, 564, 564, 564, 564, 564, 564, 564, 564, 564,
+ 564, 564, 564, 564, 564, 564, 564, 564, 564, 564,
+ 564, 564, 564, 564, 564, 564, 564, 564, 564, 564,
+ 564, 564, 564, 564, 564, 564, 564, 564, 564, 564,
+ 564, 564, 564, 564, 564, 0, 564, 564, 564, 564,
+ 564, 564, 564, 564, 564, 564, 564, 564, 564, 564,
+ 564, 564, 564, 564, 564, 50, 25, 130, 140, 0,
+ 144, 60, 155, 150, 564, 0, 130, 70, 105, 10,
+ 35, 5, 95, 185, 15, 80, 20, 5, 10, 210,
+ 0, 564, 564, 564, 564, 564, 564, 50, 25, 130,
+ 140, 0, 144, 60, 155, 150, 564, 0, 130, 70,
+ 105, 10, 35, 5, 95, 185, 15, 80, 20, 5,
+ 10, 210, 0, 564, 564, 564, 564, 564, 564, 564,
+ 564, 564, 564, 564, 564, 564, 564, 564, 564, 564,
+ 564, 564, 564, 564, 564, 564, 564, 564, 564, 564,
+ 564, 564, 564, 564, 564, 564, 564, 564, 564, 564,
+ 564, 564, 564, 564, 564, 564, 564, 564, 564, 564,
+ 564, 564, 564, 564, 564, 564, 564, 564, 564, 564,
+ 564, 564, 564, 564, 564, 564, 564, 564, 564, 564,
+ 564, 564, 564, 564, 564, 564, 564, 564, 564, 564,
+ 564, 564, 564, 564, 564, 564, 564, 564, 564, 564,
+ 564, 564, 564, 564, 564, 564, 564, 564, 564, 564,
+ 564, 564, 564, 564, 564, 564, 564, 564, 564, 564,
+ 564, 564, 564, 564, 564, 564, 564, 564, 564, 564,
+ 564, 564, 564, 564, 564, 564, 564, 564, 564, 564,
+ 564, 564, 564, 564, 564, 564
+ };
+ register int hval = len;
+
+ switch (hval)
+ {
+ default:
+ hval += asso_values[(unsigned char)str[12]];
+ /*FALLTHROUGH*/
+ case 12:
+ case 11:
+ case 10:
+ case 9:
+ case 8:
+ case 7:
+ case 6:
+ case 5:
+ case 4:
+ case 3:
+ case 2:
+ hval += asso_values[(unsigned char)str[1]];
+ /*FALLTHROUGH*/
+ case 1:
+ hval += asso_values[(unsigned char)str[0]];
+ break;
+ }
+ return hval + asso_values[(unsigned char)str[len - 1]];
+}
+
+static const struct props wordlist[] =
+ {
+#line 170 "webutil/css/property.gperf"
+ {"z-index", Property::Z_INDEX},
+#line 109 "webutil/css/property.gperf"
+ {"-webkit-nbsp-mode", Property::_WEBKIT_NBSP_MODE},
+#line 93 "webutil/css/property.gperf"
+ {"-webkit-line-break", Property::_WEBKIT_LINE_BREAK},
+#line 30 "webutil/css/property.gperf"
+ {"-webkit-border-image", Property::_WEBKIT_BORDER_IMAGE},
+#line 142 "webutil/css/property.gperf"
+ {"text-overline", Property::TEXT_OVERLINE},
+#line 148 "webutil/css/property.gperf"
+ {"text-shadow", Property::TEXT_SHADOW},
+#line 144 "webutil/css/property.gperf"
+ {"text-overline-mode", Property::TEXT_OVERLINE_MODE},
+#line 145 "webutil/css/property.gperf"
+ {"text-overline-style", Property::TEXT_OVERLINE_STYLE},
+#line 141 "webutil/css/property.gperf"
+ {"text-overflow", Property::TEXT_OVERFLOW},
+#line 162 "webutil/css/property.gperf"
+ {"-webkit-user-select", Property::_WEBKIT_USER_SELECT},
+#line 135 "webutil/css/property.gperf"
+ {"text-indent", Property::TEXT_INDENT},
+#line 116 "webutil/css/property.gperf"
+ {"overflow", Property::OVERFLOW},
+#line 156 "webutil/css/property.gperf"
+ {"-webkit-text-size-adjust", Property::_WEBKIT_TEXT_SIZE_ADJUST},
+#line 174 "webutil/css/property.gperf"
+ {"border-style", Property::BORDER_STYLE},
+#line 58 "webutil/css/property.gperf"
+ {"-webkit-box-orient", Property::_WEBKIT_BOX_ORIENT},
+#line 117 "webutil/css/property.gperf"
+ {"overflow-x", Property::OVERFLOW_X},
+#line 44 "webutil/css/property.gperf"
+ {"border-right-style", Property::BORDER_RIGHT_STYLE},
+#line 192 "webutil/css/property.gperf"
+ {"-webkit-text-decorations-in-effect", Property::_WEBKIT_TEXT_DECORATIONS_IN_EFFECT},
+#line 59 "webutil/css/property.gperf"
+ {"-webkit-box-pack", Property::_WEBKIT_BOX_PACK},
+#line 84 "webutil/css/property.gperf"
+ {"-webkit-line-clamp", Property::_WEBKIT_LINE_CLAMP},
+#line 168 "webutil/css/property.gperf"
+ {"word-wrap", Property::WORD_WRAP},
+#line 178 "webutil/css/property.gperf"
+ {"border-left", Property::BORDER_LEFT},
+#line 176 "webutil/css/property.gperf"
+ {"border-right", Property::BORDER_RIGHT},
+#line 158 "webutil/css/property.gperf"
+ {"top", Property::TOP},
+#line 43 "webutil/css/property.gperf"
+ {"border-top-style", Property::BORDER_TOP_STYLE},
+#line 16 "webutil/css/property.gperf"
+ {"-webkit-appearance", Property::_WEBKIT_APPEARANCE},
+#line 57 "webutil/css/property.gperf"
+ {"-webkit-box-ordinal-group", Property::_WEBKIT_BOX_ORDINAL_GROUP},
+#line 75 "webutil/css/property.gperf"
+ {"-webkit-font-size-delta", Property::_WEBKIT_FONT_SIZE_DELTA},
+#line 175 "webutil/css/property.gperf"
+ {"border-top", Property::BORDER_TOP},
+#line 160 "webutil/css/property.gperf"
+ {"-webkit-user-drag", Property::_WEBKIT_USER_DRAG},
+#line 29 "webutil/css/property.gperf"
+ {"border-collapse", Property::BORDER_COLLAPSE},
+#line 169 "webutil/css/property.gperf"
+ {"word-spacing", Property::WORD_SPACING},
+#line 27 "webutil/css/property.gperf"
+ {"-webkit-background-size", Property::_WEBKIT_BACKGROUND_SIZE},
+#line 124 "webutil/css/property.gperf"
+ {"page", Property::PAGE},
+#line 132 "webutil/css/property.gperf"
+ {"table-layout", Property::TABLE_LAYOUT},
+#line 20 "webutil/css/property.gperf"
+ {"-webkit-background-composite", Property::_WEBKIT_BACKGROUND_COMPOSITE},
+#line 193 "webutil/css/property.gperf"
+ {"-webkit-rtl-ordering", Property::_WEBKIT_RTL_ORDERING},
+#line 33 "webutil/css/property.gperf"
+ {"-webkit-border-vertical-spacing", Property::_WEBKIT_BORDER_VERTICAL_SPACING},
+#line 183 "webutil/css/property.gperf"
+ {"outline", Property::OUTLINE},
+#line 32 "webutil/css/property.gperf"
+ {"-webkit-border-horizontal-spacing", Property::_WEBKIT_BORDER_HORIZONTAL_SPACING},
+#line 98 "webutil/css/property.gperf"
+ {"-webkit-marquee", Property::_WEBKIT_MARQUEE},
+#line 155 "webutil/css/property.gperf"
+ {"resize", Property::RESIZE},
+#line 126 "webutil/css/property.gperf"
+ {"page-break-before", Property::PAGE_BREAK_BEFORE},
+#line 114 "webutil/css/property.gperf"
+ {"outline-style", Property::OUTLINE_STYLE},
+#line 60 "webutil/css/property.gperf"
+ {"box-sizing", Property::BOX_SIZING},
+#line 103 "webutil/css/property.gperf"
+ {"-webkit-marquee-style", Property::_WEBKIT_MARQUEE_STYLE},
+#line 26 "webutil/css/property.gperf"
+ {"background-repeat", Property::BACKGROUND_REPEAT},
+#line 51 "webutil/css/property.gperf"
+ {"bottom", Property::BOTTOM},
+#line 122 "webutil/css/property.gperf"
+ {"padding-left", Property::PADDING_LEFT},
+#line 24 "webutil/css/property.gperf"
+ {"background-position-x", Property::BACKGROUND_POSITION_X},
+#line 113 "webutil/css/property.gperf"
+ {"outline-offset", Property::OUTLINE_OFFSET},
+#line 18 "webutil/css/property.gperf"
+ {"-webkit-background-clip", Property::_WEBKIT_BACKGROUND_CLIP},
+#line 45 "webutil/css/property.gperf"
+ {"border-bottom-style", Property::BORDER_BOTTOM_STYLE},
+#line 100 "webutil/css/property.gperf"
+ {"-webkit-marquee-increment", Property::_WEBKIT_MARQUEE_INCREMENT},
+#line 17 "webutil/css/property.gperf"
+ {"background-attachment", Property::BACKGROUND_ATTACHMENT},
+#line 120 "webutil/css/property.gperf"
+ {"padding-right", Property::PADDING_RIGHT},
+#line 143 "webutil/css/property.gperf"
+ {"text-overline-color", Property::TEXT_OVERLINE_COLOR},
+#line 133 "webutil/css/property.gperf"
+ {"text-align", Property::TEXT_ALIGN},
+#line 119 "webutil/css/property.gperf"
+ {"padding-top", Property::PADDING_TOP},
+#line 138 "webutil/css/property.gperf"
+ {"text-line-through-mode", Property::TEXT_LINE_THROUGH_MODE},
+#line 139 "webutil/css/property.gperf"
+ {"text-line-through-style", Property::TEXT_LINE_THROUGH_STYLE},
+#line 150 "webutil/css/property.gperf"
+ {"text-underline", Property::TEXT_UNDERLINE},
+#line 172 "webutil/css/property.gperf"
+ {"border", Property::BORDER},
+#line 152 "webutil/css/property.gperf"
+ {"text-underline-mode", Property::TEXT_UNDERLINE_MODE},
+#line 153 "webutil/css/property.gperf"
+ {"text-underline-style", Property::TEXT_UNDERLINE_STYLE},
+#line 173 "webutil/css/property.gperf"
+ {"border-color", Property::BORDER_COLOR},
+#line 105 "webutil/css/property.gperf"
+ {"max-height", Property::MAX_HEIGHT},
+#line 92 "webutil/css/property.gperf"
+ {"margin-left", Property::MARGIN_LEFT},
+#line 90 "webutil/css/property.gperf"
+ {"margin-right", Property::MARGIN_RIGHT},
+#line 40 "webutil/css/property.gperf"
+ {"border-right-color", Property::BORDER_RIGHT_COLOR},
+#line 82 "webutil/css/property.gperf"
+ {"left", Property::LEFT},
+#line 184 "webutil/css/property.gperf"
+ {"padding", Property::PADDING},
+#line 39 "webutil/css/property.gperf"
+ {"border-top-color", Property::BORDER_TOP_COLOR},
+#line 128 "webutil/css/property.gperf"
+ {"position", Property::POSITION},
+#line 157 "webutil/css/property.gperf"
+ {"-webkit-dashboard-region", Property::_WEBKIT_DASHBOARD_REGION},
+#line 21 "webutil/css/property.gperf"
+ {"background-image", Property::BACKGROUND_IMAGE},
+#line 65 "webutil/css/property.gperf"
+ {"content", Property::CONTENT},
+#line 74 "webutil/css/property.gperf"
+ {"font-size", Property::FONT_SIZE},
+#line 77 "webutil/css/property.gperf"
+ {"font-style", Property::FONT_STYLE},
+#line 89 "webutil/css/property.gperf"
+ {"margin-top", Property::MARGIN_TOP},
+#line 81 "webutil/css/property.gperf"
+ {"-webkit-highlight", Property::_WEBKIT_HIGHLIGHT},
+#line 165 "webutil/css/property.gperf"
+ {"white-space", Property::WHITE_SPACE},
+#line 66 "webutil/css/property.gperf"
+ {"counter-increment", Property::COUNTER_INCREMENT},
+#line 180 "webutil/css/property.gperf"
+ {"font", Property::FONT},
+#line 54 "webutil/css/property.gperf"
+ {"-webkit-box-flex", Property::_WEBKIT_BOX_FLEX},
+#line 80 "webutil/css/property.gperf"
+ {"height", Property::HEIGHT},
+#line 52 "webutil/css/property.gperf"
+ {"-webkit-box-align", Property::_WEBKIT_BOX_ALIGN},
+#line 94 "webutil/css/property.gperf"
+ {"-webkit-margin-collapse", Property::_WEBKIT_MARGIN_COLLAPSE},
+#line 121 "webutil/css/property.gperf"
+ {"padding-bottom", Property::PADDING_BOTTOM},
+#line 79 "webutil/css/property.gperf"
+ {"font-weight", Property::FONT_WEIGHT},
+#line 78 "webutil/css/property.gperf"
+ {"font-variant", Property::FONT_VARIANT},
+#line 95 "webutil/css/property.gperf"
+ {"-webkit-margin-top-collapse", Property::_WEBKIT_MARGIN_TOP_COLLAPSE},
+#line 67 "webutil/css/property.gperf"
+ {"counter-reset", Property::COUNTER_RESET},
+#line 96 "webutil/css/property.gperf"
+ {"-webkit-margin-bottom-collapse", Property::_WEBKIT_MARGIN_BOTTOM_COLLAPSE},
+#line 177 "webutil/css/property.gperf"
+ {"border-bottom", Property::BORDER_BOTTOM},
+#line 146 "webutil/css/property.gperf"
+ {"text-overline-width", Property::TEXT_OVERLINE_WIDTH},
+#line 97 "webutil/css/property.gperf"
+ {"-webkit-margin-start", Property::_WEBKIT_MARGIN_START},
+#line 123 "webutil/css/property.gperf"
+ {"-webkit-padding-start", Property::_WEBKIT_PADDING_START},
+#line 61 "webutil/css/property.gperf"
+ {"caption-side", Property::CAPTION_SIDE},
+#line 149 "webutil/css/property.gperf"
+ {"text-transform", Property::TEXT_TRANSFORM},
+#line 22 "webutil/css/property.gperf"
+ {"-webkit-background-origin", Property::_WEBKIT_BACKGROUND_ORIGIN},
+#line 19 "webutil/css/property.gperf"
+ {"background-color", Property::BACKGROUND_COLOR},
+#line 163 "webutil/css/property.gperf"
+ {"vertical-align", Property::VERTICAL_ALIGN},
+#line 179 "webutil/css/property.gperf"
+ {"border-width", Property::BORDER_WIDTH},
+#line 55 "webutil/css/property.gperf"
+ {"-webkit-box-flex-group", Property::_WEBKIT_BOX_FLEX_GROUP},
+#line 127 "webutil/css/property.gperf"
+ {"page-break-inside", Property::PAGE_BREAK_INSIDE},
+#line 48 "webutil/css/property.gperf"
+ {"border-right-width", Property::BORDER_RIGHT_WIDTH},
+#line 23 "webutil/css/property.gperf"
+ {"background-position", Property::BACKGROUND_POSITION},
+#line 34 "webutil/css/property.gperf"
+ {"-webkit-border-radius", Property::_WEBKIT_BORDER_RADIUS},
+#line 50 "webutil/css/property.gperf"
+ {"border-left-width", Property::BORDER_LEFT_WIDTH},
+#line 31 "webutil/css/property.gperf"
+ {"border-spacing", Property::BORDER_SPACING},
+#line 99 "webutil/css/property.gperf"
+ {"-webkit-marquee-direction", Property::_WEBKIT_MARQUEE_DIRECTION},
+#line 101 "webutil/css/property.gperf"
+ {"-webkit-marquee-repetition", Property::_WEBKIT_MARQUEE_REPETITION},
+#line 41 "webutil/css/property.gperf"
+ {"border-bottom-color", Property::BORDER_BOTTOM_COLOR},
+#line 35 "webutil/css/property.gperf"
+ {"-webkit-border-top-left-radius", Property::_WEBKIT_BORDER_TOP_LEFT_RADIUS},
+#line 36 "webutil/css/property.gperf"
+ {"-webkit-border-top-right-radius", Property::_WEBKIT_BORDER_TOP_RIGHT_RADIUS},
+#line 37 "webutil/css/property.gperf"
+ {"-webkit-border-bottom-left-radius", Property::_WEBKIT_BORDER_BOTTOM_LEFT_RADIUS},
+#line 38 "webutil/css/property.gperf"
+ {"-webkit-border-bottom-right-radius", Property::_WEBKIT_BORDER_BOTTOM_RIGHT_RADIUS},
+#line 171 "webutil/css/property.gperf"
+ {"background", Property::BACKGROUND},
+#line 137 "webutil/css/property.gperf"
+ {"text-line-through-color", Property::TEXT_LINE_THROUGH_COLOR},
+#line 28 "webutil/css/property.gperf"
+ {"-webkit-binding", Property::_WEBKIT_BINDING},
+#line 182 "webutil/css/property.gperf"
+ {"margin", Property::MARGIN},
+#line 161 "webutil/css/property.gperf"
+ {"-webkit-user-modify", Property::_WEBKIT_USER_MODIFY},
+#line 151 "webutil/css/property.gperf"
+ {"text-underline-color", Property::TEXT_UNDERLINE_COLOR},
+#line 147 "webutil/css/property.gperf"
+ {"-webkit-text-security", Property::_WEBKIT_TEXT_SECURITY},
+#line 46 "webutil/css/property.gperf"
+ {"border-left-style", Property::BORDER_LEFT_STYLE},
+#line 64 "webutil/css/property.gperf"
+ {"color", Property::COLOR},
+#line 107 "webutil/css/property.gperf"
+ {"min-height", Property::MIN_HEIGHT},
+#line 102 "webutil/css/property.gperf"
+ {"-webkit-marquee-speed", Property::_WEBKIT_MARQUEE_SPEED},
+#line 118 "webutil/css/property.gperf"
+ {"overflow-y", Property::OVERFLOW_Y},
+#line 110 "webutil/css/property.gperf"
+ {"opacity", Property::OPACITY},
+#line 130 "webutil/css/property.gperf"
+ {"right", Property::RIGHT},
+#line 71 "webutil/css/property.gperf"
+ {"empty-cells", Property::EMPTY_CELLS},
+#line 53 "webutil/css/property.gperf"
+ {"-webkit-box-direction", Property::_WEBKIT_BOX_DIRECTION},
+#line 91 "webutil/css/property.gperf"
+ {"margin-bottom", Property::MARGIN_BOTTOM},
+#line 129 "webutil/css/property.gperf"
+ {"quotes", Property::QUOTES},
+#line 42 "webutil/css/property.gperf"
+ {"border-left-color", Property::BORDER_LEFT_COLOR},
+#line 49 "webutil/css/property.gperf"
+ {"border-bottom-width", Property::BORDER_BOTTOM_WIDTH},
+#line 136 "webutil/css/property.gperf"
+ {"text-line-through", Property::TEXT_LINE_THROUGH},
+#line 106 "webutil/css/property.gperf"
+ {"max-width", Property::MAX_WIDTH},
+#line 134 "webutil/css/property.gperf"
+ {"text-decoration", Property::TEXT_DECORATION},
+#line 140 "webutil/css/property.gperf"
+ {"text-line-through-width", Property::TEXT_LINE_THROUGH_WIDTH},
+#line 181 "webutil/css/property.gperf"
+ {"list-style", Property::LIST_STYLE},
+#line 112 "webutil/css/property.gperf"
+ {"outline-color", Property::OUTLINE_COLOR},
+#line 72 "webutil/css/property.gperf"
+ {"float", Property::FLOAT},
+#line 154 "webutil/css/property.gperf"
+ {"text-underline-width", Property::TEXT_UNDERLINE_WIDTH},
+#line 111 "webutil/css/property.gperf"
+ {"orphans", Property::ORPHANS},
+#line 104 "webutil/css/property.gperf"
+ {"-webkit-match-nearest-mail-blockquote-color", Property::_WEBKIT_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR},
+#line 63 "webutil/css/property.gperf"
+ {"clip", Property::CLIP},
+#line 85 "webutil/css/property.gperf"
+ {"line-height", Property::LINE_HEIGHT},
+#line 83 "webutil/css/property.gperf"
+ {"letter-spacing", Property::LETTER_SPACING},
+#line 68 "webutil/css/property.gperf"
+ {"cursor", Property::CURSOR},
+#line 167 "webutil/css/property.gperf"
+ {"width", Property::WIDTH},
+#line 25 "webutil/css/property.gperf"
+ {"background-position-y", Property::BACKGROUND_POSITION_Y},
+#line 76 "webutil/css/property.gperf"
+ {"font-stretch", Property::FONT_STRETCH},
+#line 56 "webutil/css/property.gperf"
+ {"-webkit-box-lines", Property::_WEBKIT_BOX_LINES},
+#line 131 "webutil/css/property.gperf"
+ {"size", Property::SIZE},
+#line 125 "webutil/css/property.gperf"
+ {"page-break-after", Property::PAGE_BREAK_AFTER},
+#line 166 "webutil/css/property.gperf"
+ {"widows", Property::WIDOWS},
+#line 159 "webutil/css/property.gperf"
+ {"unicode-bidi", Property::UNICODE_BIDI},
+#line 47 "webutil/css/property.gperf"
+ {"border-top-width", Property::BORDER_TOP_WIDTH},
+#line 62 "webutil/css/property.gperf"
+ {"clear", Property::CLEAR},
+#line 86 "webutil/css/property.gperf"
+ {"list-style-image", Property::LIST_STYLE_IMAGE},
+#line 73 "webutil/css/property.gperf"
+ {"font-family", Property::FONT_FAMILY},
+#line 108 "webutil/css/property.gperf"
+ {"min-width", Property::MIN_WIDTH},
+#line 164 "webutil/css/property.gperf"
+ {"visibility", Property::VISIBILITY},
+#line 69 "webutil/css/property.gperf"
+ {"direction", Property::DIRECTION},
+#line 115 "webutil/css/property.gperf"
+ {"outline-width", Property::OUTLINE_WIDTH},
+#line 87 "webutil/css/property.gperf"
+ {"list-style-position", Property::LIST_STYLE_POSITION},
+#line 190 "webutil/css/property.gperf"
+ {"scrollbar-track-color", Property::SCROLLBAR_TRACK_COLOR},
+#line 186 "webutil/css/property.gperf"
+ {"scrollbar-shadow-color", Property::SCROLLBAR_SHADOW_COLOR},
+#line 187 "webutil/css/property.gperf"
+ {"scrollbar-highlight-color", Property::SCROLLBAR_HIGHLIGHT_COLOR},
+#line 88 "webutil/css/property.gperf"
+ {"list-style-type", Property::LIST_STYLE_TYPE},
+#line 70 "webutil/css/property.gperf"
+ {"display", Property::DISPLAY},
+#line 191 "webutil/css/property.gperf"
+ {"scrollbar-arrow-color", Property::SCROLLBAR_ARROW_COLOR},
+#line 189 "webutil/css/property.gperf"
+ {"scrollbar-darkshadow-color", Property::SCROLLBAR_DARKSHADOW_COLOR},
+#line 185 "webutil/css/property.gperf"
+ {"scrollbar-face-color", Property::SCROLLBAR_FACE_COLOR},
+#line 188 "webutil/css/property.gperf"
+ {"scrollbar-3dlight-color", Property::SCROLLBAR_3DLIGHT_COLOR}
+ };
+
+const struct props *
+PropertyMapper::in_word_set (register const char *str, register unsigned int len)
+{
+ if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH)
+ {
+ register int key = hash (str, len);
+
+ if (key <= MAX_HASH_VALUE && key >= MIN_HASH_VALUE)
+ {
+ register const struct props *resword;
+
+ switch (key - 17)
+ {
+ case 0:
+ resword = &wordlist[0];
+ goto compare;
+ case 5:
+ resword = &wordlist[1];
+ goto compare;
+ case 6:
+ resword = &wordlist[2];
+ goto compare;
+ case 8:
+ resword = &wordlist[3];
+ goto compare;
+ case 11:
+ resword = &wordlist[4];
+ goto compare;
+ case 14:
+ resword = &wordlist[5];
+ goto compare;
+ case 16:
+ resword = &wordlist[6];
+ goto compare;
+ case 17:
+ resword = &wordlist[7];
+ goto compare;
+ case 21:
+ resword = &wordlist[8];
+ goto compare;
+ case 22:
+ resword = &wordlist[9];
+ goto compare;
+ case 24:
+ resword = &wordlist[10];
+ goto compare;
+ case 26:
+ resword = &wordlist[11];
+ goto compare;
+ case 27:
+ resword = &wordlist[12];
+ goto compare;
+ case 30:
+ resword = &wordlist[13];
+ goto compare;
+ case 31:
+ resword = &wordlist[14];
+ goto compare;
+ case 33:
+ resword = &wordlist[15];
+ goto compare;
+ case 36:
+ resword = &wordlist[16];
+ goto compare;
+ case 37:
+ resword = &wordlist[17];
+ goto compare;
+ case 39:
+ resword = &wordlist[18];
+ goto compare;
+ case 41:
+ resword = &wordlist[19];
+ goto compare;
+ case 42:
+ resword = &wordlist[20];
+ goto compare;
+ case 44:
+ resword = &wordlist[21];
+ goto compare;
+ case 45:
+ resword = &wordlist[22];
+ goto compare;
+ case 46:
+ resword = &wordlist[23];
+ goto compare;
+ case 49:
+ resword = &wordlist[24];
+ goto compare;
+ case 56:
+ resword = &wordlist[25];
+ goto compare;
+ case 58:
+ resword = &wordlist[26];
+ goto compare;
+ case 61:
+ resword = &wordlist[27];
+ goto compare;
+ case 63:
+ resword = &wordlist[28];
+ goto compare;
+ case 65:
+ resword = &wordlist[29];
+ goto compare;
+ case 68:
+ resword = &wordlist[30];
+ goto compare;
+ case 70:
+ resword = &wordlist[31];
+ goto compare;
+ case 71:
+ resword = &wordlist[32];
+ goto compare;
+ case 72:
+ resword = &wordlist[33];
+ goto compare;
+ case 75:
+ resword = &wordlist[34];
+ goto compare;
+ case 76:
+ resword = &wordlist[35];
+ goto compare;
+ case 78:
+ resword = &wordlist[36];
+ goto compare;
+ case 79:
+ resword = &wordlist[37];
+ goto compare;
+ case 80:
+ resword = &wordlist[38];
+ goto compare;
+ case 81:
+ resword = &wordlist[39];
+ goto compare;
+ case 83:
+ resword = &wordlist[40];
+ goto compare;
+ case 84:
+ resword = &wordlist[41];
+ goto compare;
+ case 85:
+ resword = &wordlist[42];
+ goto compare;
+ case 86:
+ resword = &wordlist[43];
+ goto compare;
+ case 88:
+ resword = &wordlist[44];
+ goto compare;
+ case 89:
+ resword = &wordlist[45];
+ goto compare;
+ case 90:
+ resword = &wordlist[46];
+ goto compare;
+ case 94:
+ resword = &wordlist[47];
+ goto compare;
+ case 95:
+ resword = &wordlist[48];
+ goto compare;
+ case 99:
+ resword = &wordlist[49];
+ goto compare;
+ case 102:
+ resword = &wordlist[50];
+ goto compare;
+ case 106:
+ resword = &wordlist[51];
+ goto compare;
+ case 107:
+ resword = &wordlist[52];
+ goto compare;
+ case 108:
+ resword = &wordlist[53];
+ goto compare;
+ case 109:
+ resword = &wordlist[54];
+ goto compare;
+ case 111:
+ resword = &wordlist[55];
+ goto compare;
+ case 112:
+ resword = &wordlist[56];
+ goto compare;
+ case 113:
+ resword = &wordlist[57];
+ goto compare;
+ case 114:
+ resword = &wordlist[58];
+ goto compare;
+ case 115:
+ resword = &wordlist[59];
+ goto compare;
+ case 116:
+ resword = &wordlist[60];
+ goto compare;
+ case 117:
+ resword = &wordlist[61];
+ goto compare;
+ case 119:
+ resword = &wordlist[62];
+ goto compare;
+ case 122:
+ resword = &wordlist[63];
+ goto compare;
+ case 123:
+ resword = &wordlist[64];
+ goto compare;
+ case 125:
+ resword = &wordlist[65];
+ goto compare;
+ case 128:
+ resword = &wordlist[66];
+ goto compare;
+ case 129:
+ resword = &wordlist[67];
+ goto compare;
+ case 130:
+ resword = &wordlist[68];
+ goto compare;
+ case 131:
+ resword = &wordlist[69];
+ goto compare;
+ case 132:
+ resword = &wordlist[70];
+ goto compare;
+ case 135:
+ resword = &wordlist[71];
+ goto compare;
+ case 139:
+ resword = &wordlist[72];
+ goto compare;
+ case 141:
+ resword = &wordlist[73];
+ goto compare;
+ case 142:
+ resword = &wordlist[74];
+ goto compare;
+ case 144:
+ resword = &wordlist[75];
+ goto compare;
+ case 145:
+ resword = &wordlist[76];
+ goto compare;
+ case 146:
+ resword = &wordlist[77];
+ goto compare;
+ case 147:
+ resword = &wordlist[78];
+ goto compare;
+ case 148:
+ resword = &wordlist[79];
+ goto compare;
+ case 150:
+ resword = &wordlist[80];
+ goto compare;
+ case 154:
+ resword = &wordlist[81];
+ goto compare;
+ case 155:
+ resword = &wordlist[82];
+ goto compare;
+ case 156:
+ resword = &wordlist[83];
+ goto compare;
+ case 158:
+ resword = &wordlist[84];
+ goto compare;
+ case 159:
+ resword = &wordlist[85];
+ goto compare;
+ case 160:
+ resword = &wordlist[86];
+ goto compare;
+ case 161:
+ resword = &wordlist[87];
+ goto compare;
+ case 162:
+ resword = &wordlist[88];
+ goto compare;
+ case 163:
+ resword = &wordlist[89];
+ goto compare;
+ case 164:
+ resword = &wordlist[90];
+ goto compare;
+ case 165:
+ resword = &wordlist[91];
+ goto compare;
+ case 166:
+ resword = &wordlist[92];
+ goto compare;
+ case 168:
+ resword = &wordlist[93];
+ goto compare;
+ case 171:
+ resword = &wordlist[94];
+ goto compare;
+ case 172:
+ resword = &wordlist[95];
+ goto compare;
+ case 173:
+ resword = &wordlist[96];
+ goto compare;
+ case 174:
+ resword = &wordlist[97];
+ goto compare;
+ case 175:
+ resword = &wordlist[98];
+ goto compare;
+ case 177:
+ resword = &wordlist[99];
+ goto compare;
+ case 178:
+ resword = &wordlist[100];
+ goto compare;
+ case 179:
+ resword = &wordlist[101];
+ goto compare;
+ case 182:
+ resword = &wordlist[102];
+ goto compare;
+ case 185:
+ resword = &wordlist[103];
+ goto compare;
+ case 189:
+ resword = &wordlist[104];
+ goto compare;
+ case 190:
+ resword = &wordlist[105];
+ goto compare;
+ case 191:
+ resword = &wordlist[106];
+ goto compare;
+ case 192:
+ resword = &wordlist[107];
+ goto compare;
+ case 194:
+ resword = &wordlist[108];
+ goto compare;
+ case 195:
+ resword = &wordlist[109];
+ goto compare;
+ case 197:
+ resword = &wordlist[110];
+ goto compare;
+ case 198:
+ resword = &wordlist[111];
+ goto compare;
+ case 199:
+ resword = &wordlist[112];
+ goto compare;
+ case 202:
+ resword = &wordlist[113];
+ goto compare;
+ case 203:
+ resword = &wordlist[114];
+ goto compare;
+ case 204:
+ resword = &wordlist[115];
+ goto compare;
+ case 206:
+ resword = &wordlist[116];
+ goto compare;
+ case 207:
+ resword = &wordlist[117];
+ goto compare;
+ case 208:
+ resword = &wordlist[118];
+ goto compare;
+ case 211:
+ resword = &wordlist[119];
+ goto compare;
+ case 213:
+ resword = &wordlist[120];
+ goto compare;
+ case 214:
+ resword = &wordlist[121];
+ goto compare;
+ case 217:
+ resword = &wordlist[122];
+ goto compare;
+ case 218:
+ resword = &wordlist[123];
+ goto compare;
+ case 219:
+ resword = &wordlist[124];
+ goto compare;
+ case 220:
+ resword = &wordlist[125];
+ goto compare;
+ case 223:
+ resword = &wordlist[126];
+ goto compare;
+ case 228:
+ resword = &wordlist[127];
+ goto compare;
+ case 229:
+ resword = &wordlist[128];
+ goto compare;
+ case 233:
+ resword = &wordlist[129];
+ goto compare;
+ case 245:
+ resword = &wordlist[130];
+ goto compare;
+ case 248:
+ resword = &wordlist[131];
+ goto compare;
+ case 249:
+ resword = &wordlist[132];
+ goto compare;
+ case 254:
+ resword = &wordlist[133];
+ goto compare;
+ case 256:
+ resword = &wordlist[134];
+ goto compare;
+ case 259:
+ resword = &wordlist[135];
+ goto compare;
+ case 260:
+ resword = &wordlist[136];
+ goto compare;
+ case 262:
+ resword = &wordlist[137];
+ goto compare;
+ case 265:
+ resword = &wordlist[138];
+ goto compare;
+ case 267:
+ resword = &wordlist[139];
+ goto compare;
+ case 268:
+ resword = &wordlist[140];
+ goto compare;
+ case 271:
+ resword = &wordlist[141];
+ goto compare;
+ case 273:
+ resword = &wordlist[142];
+ goto compare;
+ case 276:
+ resword = &wordlist[143];
+ goto compare;
+ case 277:
+ resword = &wordlist[144];
+ goto compare;
+ case 278:
+ resword = &wordlist[145];
+ goto compare;
+ case 280:
+ resword = &wordlist[146];
+ goto compare;
+ case 281:
+ resword = &wordlist[147];
+ goto compare;
+ case 282:
+ resword = &wordlist[148];
+ goto compare;
+ case 289:
+ resword = &wordlist[149];
+ goto compare;
+ case 292:
+ resword = &wordlist[150];
+ goto compare;
+ case 294:
+ resword = &wordlist[151];
+ goto compare;
+ case 298:
+ resword = &wordlist[152];
+ goto compare;
+ case 299:
+ resword = &wordlist[153];
+ goto compare;
+ case 304:
+ resword = &wordlist[154];
+ goto compare;
+ case 320:
+ resword = &wordlist[155];
+ goto compare;
+ case 322:
+ resword = &wordlist[156];
+ goto compare;
+ case 323:
+ resword = &wordlist[157];
+ goto compare;
+ case 329:
+ resword = &wordlist[158];
+ goto compare;
+ case 330:
+ resword = &wordlist[159];
+ goto compare;
+ case 339:
+ resword = &wordlist[160];
+ goto compare;
+ case 343:
+ resword = &wordlist[161];
+ goto compare;
+ case 349:
+ resword = &wordlist[162];
+ goto compare;
+ case 358:
+ resword = &wordlist[163];
+ goto compare;
+ case 367:
+ resword = &wordlist[164];
+ goto compare;
+ case 373:
+ resword = &wordlist[165];
+ goto compare;
+ case 387:
+ resword = &wordlist[166];
+ goto compare;
+ case 396:
+ resword = &wordlist[167];
+ goto compare;
+ case 397:
+ resword = &wordlist[168];
+ goto compare;
+ case 464:
+ resword = &wordlist[169];
+ goto compare;
+ case 465:
+ resword = &wordlist[170];
+ goto compare;
+ case 478:
+ resword = &wordlist[171];
+ goto compare;
+ case 488:
+ resword = &wordlist[172];
+ goto compare;
+ case 490:
+ resword = &wordlist[173];
+ goto compare;
+ case 509:
+ resword = &wordlist[174];
+ goto compare;
+ case 514:
+ resword = &wordlist[175];
+ goto compare;
+ case 543:
+ resword = &wordlist[176];
+ goto compare;
+ case 546:
+ resword = &wordlist[177];
+ goto compare;
+ }
+ return 0;
+ compare:
+ {
+ register const char *s = resword->name;
+
+ if ((((unsigned char)*str ^ (unsigned char)*s) & ~32) == 0 && !gperf_case_strncmp (str, s, len) && s[len] == '\0')
+ return resword;
+ }
+ }
+ }
+ return 0;
+}
+#line 194 "webutil/css/property.gperf"
+
+
+//
+// Constructor.
+//
+
+Property::Property(UnicodeText s)
+ : prop_(PropFromText(s.utf8_data(), s.utf8_length()))
+{
+ if (prop_ == OTHER)
+ other_ = LowercaseAscii(s);
+}
+
+//
+// Static methods mapping Prop's to strings
+//
+
+Property::Prop Property::PropFromText(const char* str, int len) {
+ const props* a = PropertyMapper::in_word_set(str, len);
+ if (a)
+ return a->id;
+ else
+ return OTHER;
+}
+
+static const char* name_lookup[TOTAL_KEYWORDS];
+
+static void InitializeNameLookupTable() {
+ for (int i = 0; i < TOTAL_KEYWORDS; ++i)
+ name_lookup[wordlist[i].id] = wordlist[i].name;
+}
+
+const char* Property::TextFromProp(Prop p) {
+ if (p == OTHER) {
+ return "OTHER";
+ } else {
+ DCHECK_LT(p, OTHER);
+ return name_lookup[p];
+ }
+}
+
+} // namespace
+
+REGISTER_MODULE_INITIALIZER(property, {
+ Css::InitializeNameLookupTable();
+});
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/property.gperf b/trunk/src/third_party/css_parser/src/webutil/css/property.gperf
new file mode 100644
index 0000000..5311ae8
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/property.gperf
@@ -0,0 +1,255 @@
+/**
+ * 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.
+ */
+
+%{
+#include "webutil/css/property.h"
+
+#include "base/googleinit.h"
+#include "base/logging.h"
+#include "webutil/css/string_util.h"
+
+namespace Css {
+%}
+// This is the set of properties supported by WebKit.
+struct props {
+ const char *name;
+ Property::Prop id;
+};
+%%
+-webkit-appearance, Property::_WEBKIT_APPEARANCE
+background-attachment, Property::BACKGROUND_ATTACHMENT
+-webkit-background-clip, Property::_WEBKIT_BACKGROUND_CLIP
+background-color, Property::BACKGROUND_COLOR
+-webkit-background-composite, Property::_WEBKIT_BACKGROUND_COMPOSITE
+background-image, Property::BACKGROUND_IMAGE
+-webkit-background-origin, Property::_WEBKIT_BACKGROUND_ORIGIN
+background-position, Property::BACKGROUND_POSITION
+background-position-x, Property::BACKGROUND_POSITION_X
+background-position-y, Property::BACKGROUND_POSITION_Y
+background-repeat, Property::BACKGROUND_REPEAT
+-webkit-background-size, Property::_WEBKIT_BACKGROUND_SIZE
+-webkit-binding, Property::_WEBKIT_BINDING
+border-collapse, Property::BORDER_COLLAPSE
+-webkit-border-image, Property::_WEBKIT_BORDER_IMAGE
+border-spacing, Property::BORDER_SPACING
+-webkit-border-horizontal-spacing, Property::_WEBKIT_BORDER_HORIZONTAL_SPACING
+-webkit-border-vertical-spacing, Property::_WEBKIT_BORDER_VERTICAL_SPACING
+-webkit-border-radius, Property::_WEBKIT_BORDER_RADIUS
+-webkit-border-top-left-radius, Property::_WEBKIT_BORDER_TOP_LEFT_RADIUS
+-webkit-border-top-right-radius, Property::_WEBKIT_BORDER_TOP_RIGHT_RADIUS
+-webkit-border-bottom-left-radius, Property::_WEBKIT_BORDER_BOTTOM_LEFT_RADIUS
+-webkit-border-bottom-right-radius, Property::_WEBKIT_BORDER_BOTTOM_RIGHT_RADIUS
+border-top-color, Property::BORDER_TOP_COLOR
+border-right-color, Property::BORDER_RIGHT_COLOR
+border-bottom-color, Property::BORDER_BOTTOM_COLOR
+border-left-color, Property::BORDER_LEFT_COLOR
+border-top-style, Property::BORDER_TOP_STYLE
+border-right-style, Property::BORDER_RIGHT_STYLE
+border-bottom-style, Property::BORDER_BOTTOM_STYLE
+border-left-style, Property::BORDER_LEFT_STYLE
+border-top-width, Property::BORDER_TOP_WIDTH
+border-right-width, Property::BORDER_RIGHT_WIDTH
+border-bottom-width, Property::BORDER_BOTTOM_WIDTH
+border-left-width, Property::BORDER_LEFT_WIDTH
+bottom, Property::BOTTOM
+-webkit-box-align, Property::_WEBKIT_BOX_ALIGN
+-webkit-box-direction, Property::_WEBKIT_BOX_DIRECTION
+-webkit-box-flex, Property::_WEBKIT_BOX_FLEX
+-webkit-box-flex-group, Property::_WEBKIT_BOX_FLEX_GROUP
+-webkit-box-lines, Property::_WEBKIT_BOX_LINES
+-webkit-box-ordinal-group, Property::_WEBKIT_BOX_ORDINAL_GROUP
+-webkit-box-orient, Property::_WEBKIT_BOX_ORIENT
+-webkit-box-pack, Property::_WEBKIT_BOX_PACK
+box-sizing, Property::BOX_SIZING
+caption-side, Property::CAPTION_SIDE
+clear, Property::CLEAR
+clip, Property::CLIP
+color, Property::COLOR
+content, Property::CONTENT
+counter-increment, Property::COUNTER_INCREMENT
+counter-reset, Property::COUNTER_RESET
+cursor, Property::CURSOR
+direction, Property::DIRECTION
+display, Property::DISPLAY
+empty-cells, Property::EMPTY_CELLS
+float, Property::FLOAT
+font-family, Property::FONT_FAMILY
+font-size, Property::FONT_SIZE
+-webkit-font-size-delta, Property::_WEBKIT_FONT_SIZE_DELTA
+font-stretch, Property::FONT_STRETCH
+font-style, Property::FONT_STYLE
+font-variant, Property::FONT_VARIANT
+font-weight, Property::FONT_WEIGHT
+height, Property::HEIGHT
+-webkit-highlight, Property::_WEBKIT_HIGHLIGHT
+left, Property::LEFT
+letter-spacing, Property::LETTER_SPACING
+-webkit-line-clamp, Property::_WEBKIT_LINE_CLAMP
+line-height, Property::LINE_HEIGHT
+list-style-image, Property::LIST_STYLE_IMAGE
+list-style-position, Property::LIST_STYLE_POSITION
+list-style-type, Property::LIST_STYLE_TYPE
+margin-top, Property::MARGIN_TOP
+margin-right, Property::MARGIN_RIGHT
+margin-bottom, Property::MARGIN_BOTTOM
+margin-left, Property::MARGIN_LEFT
+-webkit-line-break, Property::_WEBKIT_LINE_BREAK
+-webkit-margin-collapse, Property::_WEBKIT_MARGIN_COLLAPSE
+-webkit-margin-top-collapse, Property::_WEBKIT_MARGIN_TOP_COLLAPSE
+-webkit-margin-bottom-collapse, Property::_WEBKIT_MARGIN_BOTTOM_COLLAPSE
+-webkit-margin-start, Property::_WEBKIT_MARGIN_START
+-webkit-marquee, Property::_WEBKIT_MARQUEE
+-webkit-marquee-direction, Property::_WEBKIT_MARQUEE_DIRECTION
+-webkit-marquee-increment, Property::_WEBKIT_MARQUEE_INCREMENT
+-webkit-marquee-repetition, Property::_WEBKIT_MARQUEE_REPETITION
+-webkit-marquee-speed, Property::_WEBKIT_MARQUEE_SPEED
+-webkit-marquee-style, Property::_WEBKIT_MARQUEE_STYLE
+-webkit-match-nearest-mail-blockquote-color, Property::_WEBKIT_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR
+max-height, Property::MAX_HEIGHT
+max-width, Property::MAX_WIDTH
+min-height, Property::MIN_HEIGHT
+min-width, Property::MIN_WIDTH
+-webkit-nbsp-mode, Property::_WEBKIT_NBSP_MODE
+opacity, Property::OPACITY
+orphans, Property::ORPHANS
+outline-color, Property::OUTLINE_COLOR
+outline-offset, Property::OUTLINE_OFFSET
+outline-style, Property::OUTLINE_STYLE
+outline-width, Property::OUTLINE_WIDTH
+overflow, Property::OVERFLOW
+overflow-x, Property::OVERFLOW_X
+overflow-y, Property::OVERFLOW_Y
+padding-top, Property::PADDING_TOP
+padding-right, Property::PADDING_RIGHT
+padding-bottom, Property::PADDING_BOTTOM
+padding-left, Property::PADDING_LEFT
+-webkit-padding-start, Property::_WEBKIT_PADDING_START
+page, Property::PAGE
+page-break-after, Property::PAGE_BREAK_AFTER
+page-break-before, Property::PAGE_BREAK_BEFORE
+page-break-inside, Property::PAGE_BREAK_INSIDE
+position, Property::POSITION
+quotes, Property::QUOTES
+right, Property::RIGHT
+size, Property::SIZE
+table-layout, Property::TABLE_LAYOUT
+text-align, Property::TEXT_ALIGN
+text-decoration, Property::TEXT_DECORATION
+text-indent, Property::TEXT_INDENT
+text-line-through, Property::TEXT_LINE_THROUGH
+text-line-through-color, Property::TEXT_LINE_THROUGH_COLOR
+text-line-through-mode, Property::TEXT_LINE_THROUGH_MODE
+text-line-through-style, Property::TEXT_LINE_THROUGH_STYLE
+text-line-through-width, Property::TEXT_LINE_THROUGH_WIDTH
+text-overflow, Property::TEXT_OVERFLOW
+text-overline, Property::TEXT_OVERLINE
+text-overline-color, Property::TEXT_OVERLINE_COLOR
+text-overline-mode, Property::TEXT_OVERLINE_MODE
+text-overline-style, Property::TEXT_OVERLINE_STYLE
+text-overline-width, Property::TEXT_OVERLINE_WIDTH
+-webkit-text-security, Property::_WEBKIT_TEXT_SECURITY
+text-shadow, Property::TEXT_SHADOW
+text-transform, Property::TEXT_TRANSFORM
+text-underline, Property::TEXT_UNDERLINE
+text-underline-color, Property::TEXT_UNDERLINE_COLOR
+text-underline-mode, Property::TEXT_UNDERLINE_MODE
+text-underline-style, Property::TEXT_UNDERLINE_STYLE
+text-underline-width, Property::TEXT_UNDERLINE_WIDTH
+resize, Property::RESIZE
+-webkit-text-size-adjust, Property::_WEBKIT_TEXT_SIZE_ADJUST
+-webkit-dashboard-region, Property::_WEBKIT_DASHBOARD_REGION
+top, Property::TOP
+unicode-bidi, Property::UNICODE_BIDI
+-webkit-user-drag, Property::_WEBKIT_USER_DRAG
+-webkit-user-modify, Property::_WEBKIT_USER_MODIFY
+-webkit-user-select, Property::_WEBKIT_USER_SELECT
+vertical-align, Property::VERTICAL_ALIGN
+visibility, Property::VISIBILITY
+white-space, Property::WHITE_SPACE
+widows, Property::WIDOWS
+width, Property::WIDTH
+word-wrap, Property::WORD_WRAP
+word-spacing, Property::WORD_SPACING
+z-index, Property::Z_INDEX
+background, Property::BACKGROUND
+border, Property::BORDER
+border-color, Property::BORDER_COLOR
+border-style, Property::BORDER_STYLE
+border-top, Property::BORDER_TOP
+border-right, Property::BORDER_RIGHT
+border-bottom, Property::BORDER_BOTTOM
+border-left, Property::BORDER_LEFT
+border-width, Property::BORDER_WIDTH
+font, Property::FONT
+list-style, Property::LIST_STYLE
+margin, Property::MARGIN
+outline, Property::OUTLINE
+padding, Property::PADDING
+scrollbar-face-color, Property::SCROLLBAR_FACE_COLOR
+scrollbar-shadow-color, Property::SCROLLBAR_SHADOW_COLOR
+scrollbar-highlight-color, Property::SCROLLBAR_HIGHLIGHT_COLOR
+scrollbar-3dlight-color, Property::SCROLLBAR_3DLIGHT_COLOR
+scrollbar-darkshadow-color, Property::SCROLLBAR_DARKSHADOW_COLOR
+scrollbar-track-color, Property::SCROLLBAR_TRACK_COLOR
+scrollbar-arrow-color, Property::SCROLLBAR_ARROW_COLOR
+-webkit-text-decorations-in-effect, Property::_WEBKIT_TEXT_DECORATIONS_IN_EFFECT
+-webkit-rtl-ordering, Property::_WEBKIT_RTL_ORDERING
+%%
+
+//
+// Constructor.
+//
+
+Property::Property(UnicodeText s)
+ : prop_(PropFromText(s.utf8_data(), s.utf8_length()))
+{
+ if (prop_ == OTHER)
+ other_ = LowercaseAscii(s);
+}
+
+//
+// Static methods mapping Prop's to strings
+//
+
+Property::Prop Property::PropFromText(const char* str, int len) {
+ const props* a = PropertyMapper::in_word_set(str, len);
+ if (a)
+ return a->id;
+ else
+ return OTHER;
+}
+
+static const char* name_lookup[TOTAL_KEYWORDS];
+
+static void InitializeNameLookupTable() {
+ for (int i = 0; i < TOTAL_KEYWORDS; ++i)
+ name_lookup[wordlist[i].id] = wordlist[i].name;
+}
+
+const char* Property::TextFromProp(Prop p) {
+ if (p == OTHER) {
+ return "OTHER";
+ } else {
+ DCHECK_LT(p, OTHER);
+ return name_lookup[p];
+ }
+}
+
+} // namespace
+
+REGISTER_MODULE_INITIALIZER(property, {
+ Css::InitializeNameLookupTable();
+});
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/property.h b/trunk/src/third_party/css_parser/src/webutil/css/property.h
new file mode 100644
index 0000000..43af536
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/property.h
@@ -0,0 +1,140 @@
+/**
+ * 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.
+ */
+
+// Copyright 2006 Google Inc. All Rights Reserved.
+// Author: dpeng@google.com (Daniel Peng)
+//
+// Property represents the name of a CSS property (e.g.,
+// background, font-face, font-size). If we recognize the property,
+// we store it as an enum. Otherwise, we store the text for the name
+// of the property.
+
+
+#ifndef WEBUTIL_CSS_PROPERTY_H__
+#define WEBUTIL_CSS_PROPERTY_H__
+
+#include <cmath>
+#include <string>
+
+#include "util/utf8/public/unicodetext.h"
+#include "webutil/css/string.h"
+
+// resolving conflict on macro OVERFLOW defined in math.h.
+#ifdef OVERFLOW
+const int OVERFLOW_temporary = OVERFLOW;
+#undef OVERFLOW
+const int OVERFLOW = OVERFLOW_temporary;
+#endif
+
+namespace Css {
+
+class Property {
+ public:
+ enum Prop {
+ _WEBKIT_APPEARANCE, BACKGROUND_ATTACHMENT,
+ _WEBKIT_BACKGROUND_CLIP, BACKGROUND_COLOR,
+ _WEBKIT_BACKGROUND_COMPOSITE, BACKGROUND_IMAGE,
+ _WEBKIT_BACKGROUND_ORIGIN, BACKGROUND_POSITION, BACKGROUND_POSITION_X,
+ BACKGROUND_POSITION_Y, BACKGROUND_REPEAT, _WEBKIT_BACKGROUND_SIZE,
+ _WEBKIT_BINDING, BORDER_COLLAPSE, _WEBKIT_BORDER_IMAGE,
+ BORDER_SPACING, _WEBKIT_BORDER_HORIZONTAL_SPACING,
+ _WEBKIT_BORDER_VERTICAL_SPACING, _WEBKIT_BORDER_RADIUS,
+ _WEBKIT_BORDER_TOP_LEFT_RADIUS, _WEBKIT_BORDER_TOP_RIGHT_RADIUS,
+ _WEBKIT_BORDER_BOTTOM_LEFT_RADIUS, _WEBKIT_BORDER_BOTTOM_RIGHT_RADIUS,
+ BORDER_TOP_COLOR, BORDER_RIGHT_COLOR, BORDER_BOTTOM_COLOR,
+ BORDER_LEFT_COLOR, BORDER_TOP_STYLE, BORDER_RIGHT_STYLE,
+ BORDER_BOTTOM_STYLE, BORDER_LEFT_STYLE, BORDER_TOP_WIDTH,
+ BORDER_RIGHT_WIDTH, BORDER_BOTTOM_WIDTH, BORDER_LEFT_WIDTH, BOTTOM,
+ _WEBKIT_BOX_ALIGN, _WEBKIT_BOX_DIRECTION, _WEBKIT_BOX_FLEX,
+ _WEBKIT_BOX_FLEX_GROUP, _WEBKIT_BOX_LINES, _WEBKIT_BOX_ORDINAL_GROUP,
+ _WEBKIT_BOX_ORIENT, _WEBKIT_BOX_PACK, BOX_SIZING, CAPTION_SIDE, CLEAR,
+ CLIP, COLOR, CONTENT, COUNTER_INCREMENT, COUNTER_RESET, CURSOR,
+ DIRECTION, DISPLAY, EMPTY_CELLS, FLOAT, FONT_FAMILY, FONT_SIZE,
+ _WEBKIT_FONT_SIZE_DELTA, FONT_STRETCH, FONT_STYLE, FONT_VARIANT,
+ FONT_WEIGHT, HEIGHT, _WEBKIT_HIGHLIGHT, LEFT, LETTER_SPACING,
+ _WEBKIT_LINE_CLAMP, LINE_HEIGHT, LIST_STYLE_IMAGE,
+ LIST_STYLE_POSITION, LIST_STYLE_TYPE, MARGIN_TOP, MARGIN_RIGHT,
+ MARGIN_BOTTOM, MARGIN_LEFT, _WEBKIT_LINE_BREAK,
+ _WEBKIT_MARGIN_COLLAPSE, _WEBKIT_MARGIN_TOP_COLLAPSE,
+ _WEBKIT_MARGIN_BOTTOM_COLLAPSE, _WEBKIT_MARGIN_START, _WEBKIT_MARQUEE,
+ _WEBKIT_MARQUEE_DIRECTION, _WEBKIT_MARQUEE_INCREMENT,
+ _WEBKIT_MARQUEE_REPETITION, _WEBKIT_MARQUEE_SPEED,
+ _WEBKIT_MARQUEE_STYLE, _WEBKIT_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR,
+ MAX_HEIGHT, MAX_WIDTH, MIN_HEIGHT, MIN_WIDTH, _WEBKIT_NBSP_MODE,
+ OPACITY, ORPHANS, OUTLINE_COLOR, OUTLINE_OFFSET, OUTLINE_STYLE,
+ OUTLINE_WIDTH, OVERFLOW, OVERFLOW_X, OVERFLOW_Y, PADDING_TOP,
+ PADDING_RIGHT, PADDING_BOTTOM, PADDING_LEFT, _WEBKIT_PADDING_START,
+ PAGE, PAGE_BREAK_AFTER, PAGE_BREAK_BEFORE, PAGE_BREAK_INSIDE,
+ POSITION, QUOTES, RIGHT, SIZE, TABLE_LAYOUT, TEXT_ALIGN,
+ TEXT_DECORATION, TEXT_INDENT, TEXT_LINE_THROUGH,
+ TEXT_LINE_THROUGH_COLOR, TEXT_LINE_THROUGH_MODE,
+ TEXT_LINE_THROUGH_STYLE, TEXT_LINE_THROUGH_WIDTH, TEXT_OVERFLOW,
+ TEXT_OVERLINE, TEXT_OVERLINE_COLOR, TEXT_OVERLINE_MODE,
+ TEXT_OVERLINE_STYLE, TEXT_OVERLINE_WIDTH, _WEBKIT_TEXT_SECURITY,
+ TEXT_SHADOW, TEXT_TRANSFORM, TEXT_UNDERLINE, TEXT_UNDERLINE_COLOR,
+ TEXT_UNDERLINE_MODE, TEXT_UNDERLINE_STYLE, TEXT_UNDERLINE_WIDTH,
+ RESIZE, _WEBKIT_TEXT_SIZE_ADJUST, _WEBKIT_DASHBOARD_REGION, TOP,
+ UNICODE_BIDI, _WEBKIT_USER_DRAG, _WEBKIT_USER_MODIFY,
+ _WEBKIT_USER_SELECT, VERTICAL_ALIGN, VISIBILITY, WHITE_SPACE, WIDOWS,
+ WIDTH, WORD_WRAP, WORD_SPACING, Z_INDEX, BACKGROUND, BORDER,
+ BORDER_COLOR, BORDER_STYLE, BORDER_TOP, BORDER_RIGHT, BORDER_BOTTOM,
+ BORDER_LEFT, BORDER_WIDTH, FONT, LIST_STYLE, MARGIN, OUTLINE, PADDING,
+ SCROLLBAR_FACE_COLOR, SCROLLBAR_SHADOW_COLOR,
+ SCROLLBAR_HIGHLIGHT_COLOR, SCROLLBAR_3DLIGHT_COLOR,
+ SCROLLBAR_DARKSHADOW_COLOR, SCROLLBAR_TRACK_COLOR,
+ SCROLLBAR_ARROW_COLOR, _WEBKIT_TEXT_DECORATIONS_IN_EFFECT,
+ _WEBKIT_RTL_ORDERING, OTHER
+ };
+
+ // Constructor.
+ explicit Property(UnicodeText s);
+ Property(Prop prop) : prop_(prop) { }
+
+ // Accessors.
+ //
+ // prop() returns the property enum -- OTHER if unrecognized.
+ Prop prop() const { return prop_; }
+
+ // prop_text() returns the property as a string.
+ string prop_text() const {
+ if (prop_ == OTHER)
+ return string(other_.utf8_data(), other_.utf8_length());
+ else
+ return TextFromProp(prop_);
+ }
+
+ // Static methods mapping between Prop and strings:
+ //
+ // Given the text of a CSS property, PropFromText returns the
+ // corresponding enum. If no such property is found, UnitFromText
+ // returns OTHER. Since all CSS properties are ASCII, we are happy
+ // with ASCII, UTF8, Latin-1, etc.
+ static Prop PropFromText(const char* s, int len);
+ // Given a Prop, returns its string representation. If u is
+ // NO_UNIT, returns "". If u is OTHER, we return "OTHER", but this
+ // may not be what you want.
+ static const char* TextFromProp(Prop p);
+
+ private:
+ Prop prop_;
+ UnicodeText other_; // valid if prop_ is OTHER.
+
+ Property(); // sorry, not default-constructible.
+};
+
+} // namespace
+
+#endif // WEBUTIL_CSS_PROPERTY_H__
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/property_test.cc b/trunk/src/third_party/css_parser/src/webutil/css/property_test.cc
new file mode 100644
index 0000000..82b5287
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/property_test.cc
@@ -0,0 +1,49 @@
+/**
+ * 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.
+ */
+
+// Copyright 2007 Google Inc. All Rights Reserved.
+// Author: dpeng@google.com (Daniel Peng)
+
+#include "webutil/css/property.h"
+
+#include "testing/base/public/googletest.h"
+#include "testing/base/public/gunit.h"
+
+namespace {
+
+class PropertyTest : public testing::Test {
+};
+
+TEST_F(PropertyTest, PropFromText) {
+ string s("border-width");
+ EXPECT_EQ(Css::Property::BORDER_WIDTH,
+ Css::Property::PropFromText(s.c_str(), s.length()));
+}
+
+TEST_F(PropertyTest, TextFromProp) {
+ EXPECT_STREQ("border-width",
+ Css::Property::TextFromProp(Css::Property::BORDER_WIDTH));
+}
+
+TEST_F(PropertyTest, Inverses) {
+ for(int i = 0; i < Css::Property::OTHER; ++i) {
+ string s(Css::Property::TextFromProp(static_cast<Css::Property::Prop>(i)));
+ EXPECT_EQ(static_cast<Css::Property::Prop>(i),
+ Css::Property::PropFromText(s.c_str(), s.length()));
+ }
+}
+
+} // namespace
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/selector.cc b/trunk/src/third_party/css_parser/src/webutil/css/selector.cc
new file mode 100644
index 0000000..3428207
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/selector.cc
@@ -0,0 +1,92 @@
+/**
+ * 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.
+ */
+
+// Copyright 2006 Google Inc. All Rights Reserved.
+// Author: dpeng@google.com (Daniel Peng)
+
+#include "util/gtl/stl_util-inl.h"
+
+#include "webutil/css/selector.h"
+
+namespace Css {
+
+//
+// static
+//
+
+const HtmlTagIndex SimpleSelector::tagindex_;
+
+//
+// SimpleSelector factory methods
+//
+
+SimpleSelector* SimpleSelector::NewElementType(const UnicodeText& name) {
+ HtmlTagEnum tag = static_cast<HtmlTagEnum>(
+ tagindex_.FindHtmlTag(name.utf8_data(), name.utf8_length()));
+ return new SimpleSelector(tag, name);
+}
+
+SimpleSelector* SimpleSelector::NewUniversal() {
+ return new SimpleSelector(SimpleSelector::UNIVERSAL,
+ UnicodeText(), UnicodeText());
+}
+
+SimpleSelector* SimpleSelector::NewExistAttribute(
+ const UnicodeText& attribute) {
+ return new SimpleSelector(SimpleSelector::EXIST_ATTRIBUTE,
+ attribute, UnicodeText());
+}
+
+SimpleSelector* SimpleSelector::NewBinaryAttribute(
+ Type type, const UnicodeText& attribute, const UnicodeText& value) {
+ return new SimpleSelector(type, attribute, value);
+}
+
+static const char kClassText[] = "class";
+SimpleSelector* SimpleSelector::NewClass(const UnicodeText& classname) {
+ static const UnicodeText kClass =
+ UTF8ToUnicodeText(kClassText, strlen(kClassText));
+ return new SimpleSelector(SimpleSelector::CLASS,
+ kClass, classname);
+}
+
+static const char kIdText[] = "id";
+SimpleSelector* SimpleSelector::NewId(const UnicodeText& id) {
+ static const UnicodeText kId = UTF8ToUnicodeText(kIdText, strlen(kIdText));
+ return new SimpleSelector(SimpleSelector::ID,
+ kId, id);
+}
+
+SimpleSelector* SimpleSelector::NewPseudoclass(
+ const UnicodeText& pseudoclass) {
+ return new SimpleSelector(SimpleSelector::PSEUDOCLASS,
+ UnicodeText(), pseudoclass);
+}
+
+SimpleSelector* SimpleSelector::NewLang(const UnicodeText& lang) {
+ return new SimpleSelector(SimpleSelector::LANG,
+ UnicodeText(), lang);
+}
+
+//
+// Some destructors that need STLDeleteElements() from stl_util-inl.h
+//
+
+SimpleSelectors::~SimpleSelectors() { STLDeleteElements(this); }
+Selector::~Selector() { STLDeleteElements(this); }
+Selectors::~Selectors() { STLDeleteElements(this); }
+
+} // namespace
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/selector.h b/trunk/src/third_party/css_parser/src/webutil/css/selector.h
new file mode 100644
index 0000000..6d405b3
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/selector.h
@@ -0,0 +1,305 @@
+/**
+ * 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.
+ */
+
+// Copyright 2006 Google Inc. All Rights Reserved.
+// Author: dpeng@google.com (Daniel Peng)
+
+#ifndef WEBUTIL_CSS_SELECTOR_H__
+#define WEBUTIL_CSS_SELECTOR_H__
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "util/utf8/public/unicodetext.h"
+#include "webutil/css/string.h"
+#include "webutil/html/htmltagenum.h"
+#include "webutil/html/htmltagindex.h"
+
+namespace Css {
+
+// -------------
+// Overview:
+//
+// We adopt CSS3's naming conventions:
+// http://www.w3.org/TR/css3-selectors/#selector-syntax
+//
+// A selector is a chain of one or more sequences of simple selectors
+// separated by combinators. We represent this in the Selector
+// class.
+// Ex: div[align=center] > h1
+//
+// A sequence of simple selectors is a chain of simple selectors that
+// are not separated by a combinator. It always begins with a type
+// selector or a universal selector. No other type selector or
+// universal selector is allowed in the sequence. We represent this
+// in the SimpleSelectors class as just a list of simple selectors;
+// the simple selectors are logically AND-ed together.
+// Ex: div[align=center] is a sequence of two simple selectors, 'div'
+// and '[align=center]'
+//
+// A simple selector is either a type selector, universal selector,
+// attribute selector, class selector, ID selector, content selector,
+// or pseudo-class. One pseudo-element may be appended to the last
+// sequence of simple selectors. We represent this in the
+// SimpleSelector class.
+// Ex: [align=center]
+//
+// ------------
+
+// ------------
+// SimpleSelector:
+//
+// A SimpleSelector represents a simple selector. We currently
+// don't distinguish pseudo-elements (:first-line) from pseudo-classes
+// (:hover).
+//
+// For example, 'div' is a simple selector of type TYPE_SELECTOR,
+// matching HTML div tags. '[align=center]' is simple selector
+// of type EXACT_ATTRIBUTE, matching tags with attribute 'align' equal
+// to 'center'. If you put them together as div[align=center], you
+// get a chain of simple selectors matching, e.g., <div align=center
+// foo=bar>.
+//
+// There are several different types of simple selectors, so you can
+// think of a SimpleSelector as a tagged union of various values.
+// The tag is set by the factory method and accessed with type(). The
+// values are also set by the factory and accessed with the various
+// accessors. Each accessor is valid with certain types.
+// ------------
+class SimpleSelector {
+ public:
+ enum Type {
+ // An element type selector matches the HTML element type (e.g., h1, h2, h3)
+ ELEMENT_TYPE,
+
+ // This type of simple selector matches anything ('*').
+ UNIVERSAL,
+
+ // These types check HTML element attributes in various ways.
+ EXIST_ATTRIBUTE, // [attr]: element sets the "attr" attribute
+ EXACT_ATTRIBUTE, // [attr=val]: element's "attr" attribute value is "val".
+ ONE_OF_ATTRIBUTE, // [attr~=val]: element's "attr" attribute value is a
+ // space-separated list of words, one of which
+ // is exactly "val".
+ BEGIN_HYPHEN_ATTRIBUTE, // [attr|=val] element's "attr" attribute value
+ // is a hypen-separated list that begins
+ // with "val-".
+
+ // New in CSS3, but well supported.
+ BEGIN_WITH_ATTRIBUTE, // [attr^=val]: attribute value starts with "val".
+ END_WITH_ATTRIBUTE, // [attr$=val]: attribute value ends with "val".
+ SUBSTRING_ATTRIBUTE, // [attr*=val]: attribute value contains "val".
+
+ // CLASS and ID are equivalent to ONE_OF_ATTRIBUTE and
+ // EXACT_ATTRIBUTE, but CSS provides special syntax for them, and
+ // they're very common. GetLocalName() returns "class" and "id"
+ // for these:
+ CLASS, // .class: element's class attribute is "val"
+ ID, // #id: the element's id attribute is "id"
+
+ // Miscellaneous conditions:
+ PSEUDOCLASS, // a:hover matches <a href=blah> when mouse is hovering
+ LANG, // :lang(en) matches if the element is in English.
+
+ // We don't implement these (yet).
+ // AND, OR, NOT, ONLY_CHILD, ONLY_TYPE, CONTENT, POSITIONAL
+ // ROOT, TEXT, PSEUDOELEMENT, PROCESSING_INSTRUCTION,
+ // NEGATIVE, COMMENT, CDATA_SECTION,
+ };
+
+ // Factory methods to generate SimpleSelectors of various types.
+ static SimpleSelector* NewElementType(const UnicodeText& name);
+ static SimpleSelector* NewUniversal();
+ static SimpleSelector* NewExistAttribute(const UnicodeText& attribute);
+ // *_ATTRIBUTE.
+ static SimpleSelector* NewBinaryAttribute(Type type,
+ const UnicodeText& attribute,
+ const UnicodeText& value);
+ static SimpleSelector* NewClass(const UnicodeText& classname);
+ static SimpleSelector* NewId(const UnicodeText& id);
+ static SimpleSelector* NewPseudoclass(const UnicodeText& pseudoclass);
+ static SimpleSelector* NewLang(const UnicodeText& lang);
+
+ // oper is '=' for EXACT_ATTRIBUTE, or the first character of the attribute
+ // selector operator, i.e. '~', '|', etc.
+ static Type AttributeTypeFromOperator(char oper) {
+ switch (oper) {
+ case '=':
+ return EXACT_ATTRIBUTE;
+ case '~':
+ return ONE_OF_ATTRIBUTE;
+ case '|':
+ return BEGIN_HYPHEN_ATTRIBUTE;
+ case '^':
+ return BEGIN_WITH_ATTRIBUTE;
+ case '$':
+ return END_WITH_ATTRIBUTE;
+ case '*':
+ return SUBSTRING_ATTRIBUTE;
+ default:
+ LOG(FATAL) << "Invalid attribute operator " << oper;
+ }
+ }
+
+ // Accessors.
+ //
+ Type type() const { return type_; } // The type of selector
+
+ // ELEMENT_TYPE accessor
+ // element_type() returns kHtmlTagUnknown if we don't recognize the tag.
+ HtmlTagEnum element_type() const { return element_type_; }
+ // element_text() returns the element text. We preserve the original case.
+ const UnicodeText& element_text() const { return element_text_; }
+
+ // *_ATTRIBUTE, CLASS, ID accessors
+ const UnicodeText& namespace_uri(); // Not implemented.
+ const UnicodeText& attribute() const {
+ DCHECK(IsAttributeCondition());
+ return attribute_;
+ }
+ const UnicodeText& value() const {
+ DCHECK(IsAttributeCondition());
+ return value_;
+ }
+ // IsAttributeCondition indicates whether this is an attribute
+ // selector, with valid attribute() and value() fields.
+ bool IsAttributeCondition() const {
+ return (EXIST_ATTRIBUTE == type_ || EXACT_ATTRIBUTE == type_
+ || ONE_OF_ATTRIBUTE == type_ || BEGIN_HYPHEN_ATTRIBUTE == type_
+ || BEGIN_WITH_ATTRIBUTE == type_ || END_WITH_ATTRIBUTE == type_
+ || SUBSTRING_ATTRIBUTE == type_ || CLASS == type_ || ID == type_);
+ }
+
+ // PSEUDOCLASS accessor:
+ const UnicodeText& pseudoclass() const {
+ DCHECK_EQ(PSEUDOCLASS, type_);
+ return value_;
+ }
+
+ // lang accessor
+ const UnicodeText& lang() const {
+ DCHECK_EQ(LANG, type_);
+ return value_;
+ }
+
+ string ToString() const;
+ private:
+ Type type_;
+
+ HtmlTagEnum element_type_; // ELEMENT_TYPE
+ UnicodeText element_text_; // ELEMENT_TYPE
+ static const HtmlTagIndex tagindex_; // Look up HTML tags. Thread-safe.
+
+ UnicodeText attribute_; // Attribute name, valid for *_ATTRIBUTE, CLASS, ID
+ UnicodeText value_; // Valid for *_ATTRIBUTE, CLASS, ID, PSEUDOCLASS, LANG
+
+ // Private constructors, for use by factory methods
+ SimpleSelector(Type type,
+ const UnicodeText& attribute, const UnicodeText& value)
+ : type_(type), attribute_(attribute), value_(value) { }
+ SimpleSelector(HtmlTagEnum element_type, const UnicodeText& element_text)
+ : type_(ELEMENT_TYPE),
+ element_type_(element_type), element_text_(element_text) { }
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(SimpleSelector);
+};
+
+// ------------
+// SimpleSelectors:
+//
+// SimpleSelectors is a vector of SimpleSelector*, which we own and
+// will delete upon destruction. If you remove elements from
+// SimpleSelectors, you are responsible for deleting them.
+//
+// Semantically, SimpleSelectors is the AND of each of its constituent
+// simple selectors. Although SAC permits other logical connectives like OR
+// and NOT, it appears that CSS3 will not, so we favor the simplicity
+// of this list representation over the complexity of a full logical tree
+// (and its pie-in-the-sky possibilities). CSS3's :not pseudoclass
+// takes an entire simple selector as negation, not just a condition.
+//
+// SimpleSelectors also has the combinator() field, which describes
+// how it relates to the previous SimpleSelectors in the
+// Selector chain. For example, consider E > F + G. E's
+// combinator() is NONE, F's combinator is CHILD, and G's combinator
+// is SIBLING.
+// ------------
+class SimpleSelectors : public std::vector<SimpleSelector*> {
+ public:
+ enum Combinator {
+ NONE, // first one in the chain
+ DESCENDANT, // this one is a descendant of the previous one
+ CHILD, // this one is a child (direct descendant) of the previous one
+ SIBLING // this one is an adjacent sibling of the previous
+ // one, non-element nodes (such as text nodes and
+ // comments) excluded.
+ };
+
+ SimpleSelectors(Combinator c)
+ : std::vector<SimpleSelector*>(), combinator_(c) { }
+ ~SimpleSelectors();
+
+ Combinator combinator() const { return combinator_; }
+ const SimpleSelector* get(int i) const { return (*this)[i]; } // sugar.
+
+ string ToString() const;
+ private:
+ const Combinator combinator_;
+ DISALLOW_COPY_AND_ASSIGN(SimpleSelectors);
+};
+
+// ------------
+// Selector:
+//
+// A selector is a chain of sequences of simple selectors separated by
+// combinators. Each SimpleSelectors stores the combinator between
+// it and the previous one in the chain.
+// ------------
+class Selector: public std::vector<SimpleSelectors*> {
+ public:
+ Selector() { }
+ ~Selector();
+ // We provide syntactic sugar for accessing elements.
+ // conditions->get(i) looks better than (*conditions)[i])
+ const SimpleSelectors* get(int i) const { return (*this)[i]; }
+
+ string ToString() const;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Selector);
+};
+
+// ------------
+// Selectors:
+//
+// When several selectors share the same declarations, they may be
+// grouped into a comma-separated list:
+// ------------
+class Selectors: public std::vector<Selector*> {
+ public:
+ Selectors() { }
+ ~Selectors();
+ const Selector* get(int i) const { return (*this)[i]; }
+
+ string ToString() const;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Selectors);
+};
+
+} // namespace
+
+#endif // WEBUTIL_CSS_SELECTOR_H__
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/string.h b/trunk/src/third_party/css_parser/src/webutil/css/string.h
new file mode 100644
index 0000000..60f1bd8
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/string.h
@@ -0,0 +1,23 @@
+/**
+ * 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)
+
+#ifndef WEBUTIL_CSS_STRING_H_
+#define WEBUTIL_CSS_STRING_H_
+
+#include "string_using.h"
+
+#endif // WEBUTIL_CSS_STRING_H_
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/string_util.cc b/trunk/src/third_party/css_parser/src/webutil/css/string_util.cc
new file mode 100644
index 0000000..e427c32
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/string_util.cc
@@ -0,0 +1,75 @@
+/**
+ * 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.
+ */
+
+// Copyright 2010 Google Inc. All Rights Reserved.
+// Author: sligocki@google.com (Shawn Ligocki)
+
+#include "webutil/css/string_util.h"
+
+#include <cerrno>
+#include <cstdlib> // strtod
+#include <cstring> // memcpy
+
+#include "strings/ascii_ctype.h" // ascii_tolower
+#include "util/utf8/public/unicodetext.h"
+
+namespace Css {
+
+// Addapted from RE2::Arg::parse_double() from the Regular Expressions package
+// RE2 (http://code.google.com/p/re2/).
+bool ParseDouble(const char* str, int len, double* dest) {
+ static const int kMaxLength = 200;
+ if (dest == NULL || len == 0 || len >= kMaxLength) {
+ return false;
+ }
+ char buf[kMaxLength];
+ memcpy(buf, str, len);
+ buf[len] = '\0';
+
+ char* end;
+ errno = 0;
+ *dest = strtod(buf, &end);
+ if (errno != 0 || end != buf + len) {
+ return false;
+ }
+ return true;
+}
+
+namespace {
+
+inline bool IsAscii(char32 c) {
+ return c < 0x80 && c >= 0;
+}
+
+} // namespace
+
+UnicodeText LowercaseAscii(const UnicodeText& in_text) {
+ UnicodeText out_text;
+ // TODO(sligocki): out_text.reserve(in_text.utf8_length())
+ for (UnicodeText::const_iterator iter = in_text.begin();
+ iter < in_text.end(); ++iter) {
+ char32 c = *iter;
+ if (IsAscii(c)) {
+ out_text.push_back(ascii_tolower(c));
+ } else {
+ out_text.push_back(c);
+ }
+ }
+ return out_text;
+}
+
+
+} // namespace Css
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/string_util.h b/trunk/src/third_party/css_parser/src/webutil/css/string_util.h
new file mode 100644
index 0000000..6208426
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/string_util.h
@@ -0,0 +1,38 @@
+/**
+ * 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.
+ */
+
+// Copyright 2010 Google Inc. All Rights Reserved.
+// Author: sligocki@google.com (Shawn Ligocki)
+//
+// Useful string utils.
+
+#ifndef WEBUTIL_CSS_STRING_UTIL_H_
+#define WEBUTIL_CSS_STRING_UTIL_H_
+
+class UnicodeText;
+
+namespace Css {
+
+// Convert a given block of chars to a double.
+bool ParseDouble(const char* str, int len, double* dest);
+
+// Lowercase all ASCII chars in the UnicodeText in_text.
+// Leaves non-ASCII chars alone.
+UnicodeText LowercaseAscii(const UnicodeText& in_text);
+
+} // namespace Css
+
+#endif // WEBUTIL_CSS_STRING_UTIL_H_
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/tostring.cc b/trunk/src/third_party/css_parser/src/webutil/css/tostring.cc
new file mode 100644
index 0000000..c7b9c2d
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/tostring.cc
@@ -0,0 +1,273 @@
+/**
+ * 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.
+ */
+
+// Copyright 2007 Google Inc. All Rights Reserved.
+// Author: yian@google.com (Yi-An Huang)
+
+#include <string>
+#include <vector>
+
+#include "base/stringprintf.h"
+#include "strings/join.h"
+#include "strings/strutil.h"
+#include "webutil/css/parser.h"
+#include "webutil/css/string.h"
+
+namespace Css {
+
+// Escape [(), \t\r\n\\'"]
+// Based on CEscape/CEscapeString from strings/strutil.cc.
+static string CSSEscapeString(const StringPiece& src) {
+ const int dest_length = src.size() * 2 + 1; // Maximum possible expansion
+ scoped_array<char> dest(new char[dest_length]);
+
+ const char* src_end = src.data() + src.size();
+ int used = 0;
+
+ for (const char* p = src.data(); p < src_end; p++) {
+ switch (*p) {
+ case '\n': dest[used++] = '\\'; dest[used++] = 'n'; break;
+ case '\r': dest[used++] = '\\'; dest[used++] = 'r'; break;
+ case '\t': dest[used++] = '\\'; dest[used++] = 't'; break;
+ case '\"': case '\'': case '\\': case ',': case '(': case ')':
+ dest[used++] = '\\';
+ dest[used++] = *p;
+ break;
+ default: dest[used++] = *p; break;
+ }
+ }
+
+ return string(dest.get(), used);
+}
+
+static string CSSEscapeString(const UnicodeText& src) {
+ return CSSEscapeString(StringPiece(src.utf8_data(), src.utf8_length()));
+}
+
+template <typename Container>
+static string JoinElementStrings(const Container& c, const char* delim) {
+ std::vector<string> vals;
+ vals.reserve(c.size());
+ for (typename Container::const_iterator it = c.begin(); it != c.end(); ++it)
+ vals.push_back((*it)->ToString());
+ string result;
+ JoinStrings(vals, delim, &result);
+ return result;
+}
+
+static string JoinMediaStrings(const std::vector<UnicodeText>& media,
+ const char* delim) {
+ std::vector<string> vals;
+ vals.reserve(media.size());
+ for (std::vector<UnicodeText>::const_iterator
+ it = media.begin(); it != media.end(); ++it)
+ vals.push_back(CSSEscapeString(*it));
+ string result;
+ JoinStrings(vals, delim, &result);
+ return result;
+}
+
+static string StylesheetTypeString(Stylesheet::StylesheetType type) {
+ switch (type) {
+ CONSIDER_IN_CLASS(Stylesheet, AUTHOR);
+ CONSIDER_IN_CLASS(Stylesheet, USER);
+ CONSIDER_IN_CLASS(Stylesheet, SYSTEM);
+ default:
+ LOG(FATAL) << "Invalid type";
+ }
+}
+
+string Value::ToString() const {
+ switch (GetLexicalUnitType()) {
+ case NUMBER:
+ return StringPrintf("%g%s",
+ GetFloatValue(),
+ GetDimensionUnitText().c_str());
+ case URI:
+ return StringPrintf("url(%s)",
+ CSSEscapeString(GetStringValue()).c_str());
+ case COUNTER:
+ return StringPrintf("counter(%s)",
+ GetParameters()->ToString().c_str());
+ case FUNCTION:
+ return StringPrintf("%s(%s)",
+ CSSEscapeString(GetFunctionName()).c_str(),
+ GetParameters()->ToString().c_str());
+ case RECT:
+ return StringPrintf("rect(%s)",
+ GetParameters()->ToString().c_str());
+ case COLOR:
+ if (GetColorValue().IsDefined())
+ return GetColorValue().ToString();
+ else
+ return "bad";
+ case STRING:
+ return StringPrintf("\"%s\"",
+ CSSEscapeString(GetStringValue()).c_str());
+ case IDENT:
+ return CSSEscapeString(GetIdentifierText()).c_str();
+ case UNKNOWN:
+ return "UNKNOWN";
+ case DEFAULT:
+ return "";
+ default:
+ LOG(FATAL) << "Invalid type";
+ }
+}
+
+string Values::ToString() const {
+ return JoinElementStrings(*this, " ");
+}
+
+string SimpleSelector::ToString() const {
+ switch (type()) {
+ case ELEMENT_TYPE:
+ return UnicodeTextToUTF8(element_text());
+ case UNIVERSAL:
+ return "*";
+ case EXIST_ATTRIBUTE:
+ return StringPrintf("[%s]",
+ CSSEscapeString(attribute()).c_str());
+ case EXACT_ATTRIBUTE:
+ return StringPrintf("[%s=%s]",
+ CSSEscapeString(attribute()).c_str(),
+ CSSEscapeString(value()).c_str());
+ case ONE_OF_ATTRIBUTE:
+ return StringPrintf("[%s~=%s]",
+ CSSEscapeString(attribute()).c_str(),
+ CSSEscapeString(value()).c_str());
+ case BEGIN_HYPHEN_ATTRIBUTE:
+ return StringPrintf("[%s|=%s]",
+ CSSEscapeString(attribute()).c_str(),
+ CSSEscapeString(value()).c_str());
+ case SUBSTRING_ATTRIBUTE:
+ return StringPrintf("[%s*=%s]",
+ CSSEscapeString(attribute()).c_str(),
+ CSSEscapeString(value()).c_str());
+ case BEGIN_WITH_ATTRIBUTE:
+ return StringPrintf("[%s^=%s]",
+ CSSEscapeString(attribute()).c_str(),
+ CSSEscapeString(value()).c_str());
+ case END_WITH_ATTRIBUTE:
+ return StringPrintf("[%s$=%s]",
+ CSSEscapeString(attribute()).c_str(),
+ CSSEscapeString(value()).c_str());
+ case CLASS:
+ return StringPrintf(".%s",
+ CSSEscapeString(value()).c_str());
+ case ID:
+ return StringPrintf("#%s",
+ CSSEscapeString(value()).c_str());
+ case PSEUDOCLASS:
+ return StringPrintf(":%s",
+ CSSEscapeString(pseudoclass()).c_str());
+ case LANG:
+ return StringPrintf(":lang(%s)",
+ CSSEscapeString(lang()).c_str());
+ default:
+ LOG(FATAL) << "Invalid type";
+ }
+}
+
+string SimpleSelectors::ToString() const {
+ string prefix;
+ switch (combinator()) {
+ case CHILD:
+ prefix = "> ";
+ break;
+ case SIBLING:
+ prefix = "+ ";
+ break;
+ default:
+ break;
+ }
+ return prefix + JoinElementStrings(*this, "");
+}
+
+string Selector::ToString() const {
+ return JoinElementStrings(*this, " ");
+}
+
+string Selectors::ToString() const {
+ return JoinElementStrings(*this, ", ");
+}
+
+string Declaration::ToString() const {
+ string result = prop_text() + ": ";
+ switch (prop()) {
+ case Property::FONT_FAMILY:
+ result += JoinElementStrings(*values(), ",");
+ break;
+ case Property::FONT:
+ if (values()->size() == 1) {
+ // Special one-value font: notations like "font: menu".
+ result += values()->ToString();
+ } else if (values()->size() < 5) {
+ result += "bad";
+ } else {
+ string tmp;
+ tmp = values()->get(0)->ToString();
+ if (tmp != "normal") result += tmp + " ";
+ tmp = values()->get(1)->ToString();
+ if (tmp != "normal") result += tmp + " ";
+ tmp = values()->get(2)->ToString();
+ if (tmp != "normal") result += tmp + " ";
+ result += values()->get(3)->ToString();
+ tmp = values()->get(4)->ToString();
+ if (tmp != "normal") result += "/" + tmp;
+ for (int i = 5, n = values()->size(); i < n; ++i)
+ result += (i == 5 ? " " : ",") + values()->get(i)->ToString();
+ }
+ break;
+ default:
+ result += values()->ToString();
+ break;
+ }
+ if (IsImportant())
+ result += " !important";
+ return result;
+}
+
+string Declarations::ToString() const {
+ return JoinElementStrings(*this, "; ");
+}
+
+string Ruleset::ToString() const {
+ string result;
+ if (!media().empty())
+ result += StringPrintf("@media %s { ",
+ JoinMediaStrings(media(), ",").c_str());
+ result += selectors().ToString() + " {" + declarations().ToString() + "}";
+ if (!media().empty())
+ result += " }";
+ return result;
+}
+
+string Import::ToString() const {
+ return StringPrintf("@import url(\"%s\") %s;",
+ CSSEscapeString(link).c_str(),
+ JoinMediaStrings(media, ",").c_str());
+}
+
+string Stylesheet::ToString() const {
+ string result;
+ result += "/* " + StylesheetTypeString(type()) + " */\n";
+ result += JoinElementStrings(imports(), "\n") + "\n";
+ result += JoinElementStrings(rulesets(), "\n") + "\n";
+ return result;
+}
+
+} // namespace
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/tostring_test.cc b/trunk/src/third_party/css_parser/src/webutil/css/tostring_test.cc
new file mode 100644
index 0000000..bb8ae18
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/tostring_test.cc
@@ -0,0 +1,95 @@
+/**
+ * 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.
+ */
+
+// Copyright 2007 Google Inc. All Rights Reserved.
+// Author: yian@google.com (Yi-An Huang)
+
+#include <string>
+
+#include "base/scoped_ptr.h"
+#include "base/stringprintf.h"
+#include "testing/base/public/googletest.h"
+#include "testing/base/public/gunit.h"
+#include "webutil/css/parser.h"
+#include "webutil/css/string.h"
+
+namespace {
+
+#define TESTDECLARATIONS(css) TestDeclarations(css, __LINE__)
+#define TESTSTYLESHEET(css) TestStylesheet(css, __LINE__)
+
+class ToStringTest : public testing::Test {
+ protected:
+ // Checks if ParseDeclarations()->ToString() returns identity.
+ void TestDeclarations(const string& css, int line) {
+ SCOPED_TRACE(StringPrintf("at line %d", line));
+ Css::Parser parser(css);
+ scoped_ptr<Css::Declarations> decls(parser.ParseDeclarations());
+ EXPECT_EQ(css, decls->ToString());
+ }
+
+ // Checks if ParseStylesheet()->ToString() returns identity.
+ void TestStylesheet(const string& css, int line) {
+ SCOPED_TRACE(StringPrintf("at line %d", line));
+ Css::Parser parser(css);
+ scoped_ptr<Css::Stylesheet> stylesheet(parser.ParseStylesheet());
+ EXPECT_EQ(css, stylesheet->ToString());
+ }
+};
+
+TEST_F(ToStringTest, declarations) {
+ TESTDECLARATIONS("left: inherit; "
+ "color: #abcdef; "
+ "content: \"text\"; "
+ "top: 1; right: 2px !important; "
+ "background-image: url(link\\(a\\,b\\,\\\"c\\\"\\).html)");
+ TESTDECLARATIONS("content: counter(); clip: rect(auto 1px 2em auto)");
+ TESTDECLARATIONS("font-family: arial,serif,\"Courier New\"");
+
+ // FONT is special
+ Css::Parser parser("font: 3px/1.1 Arial");
+ scoped_ptr<Css::Declarations> decls(parser.ParseDeclarations());
+ EXPECT_EQ("font: 3px/1.1 arial; "
+ "font-style: normal; font-variant: normal; font-weight: normal; "
+ "font-size: 3px; line-height: 1.1; font-family: arial",
+ decls->ToString());
+}
+
+TEST_F(ToStringTest, selectors) {
+ TESTSTYLESHEET("/* AUTHOR */\n\n"
+ "a, *, b#id, c.class, :hover:focus {top: 1}\n");
+ TESTSTYLESHEET("/* AUTHOR */\n\n"
+ "table[width], [disable=no], [x~=y], [lang|=fr] {top: 1}\n");
+ TESTSTYLESHEET("/* AUTHOR */\n\n"
+ "a > b, a + b, a b + c > d > e f {top: 1}\n");
+}
+
+TEST_F(ToStringTest, misc) {
+ TESTSTYLESHEET("/* AUTHOR */\n"
+ "@import url(\"a.html\") ;\n"
+ "@import url(\"b.html\") print;\n"
+ "@media print,screen { a {top: 1} }\n"
+ "b {color: #ff0000}\n");
+
+ Css::Parser parser("a {top: 1}");
+ scoped_ptr<Css::Stylesheet> stylesheet(parser.ParseStylesheet());
+ stylesheet->set_type(Css::Stylesheet::SYSTEM);
+ EXPECT_EQ("/* SYSTEM */\n\n"
+ "a {top: 1}\n",
+ stylesheet->ToString());
+}
+
+} // namespace
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/util.cc b/trunk/src/third_party/css_parser/src/webutil/css/util.cc
new file mode 100644
index 0000000..b8c7575
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/util.cc
@@ -0,0 +1,309 @@
+/**
+ * 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.
+ */
+
+// Copyright 2007 Google Inc. All Rights Reserved.
+// Author: dpeng@google.com (Daniel Peng)
+
+#include "webutil/css/util.h"
+
+#include "base/commandlineflags.h"
+#include "strings/memutil.h"
+#include "strings/stringpiece_utils.h"
+#include "strings/strutil.h"
+#include "util/utf8/public/unicodetext.h"
+#include "webutil/css/parser.h"
+
+DEFINE_double(font_size_adjustment,
+ 0.58,
+ "Ratio of x-height to font-size in CSS terms");
+
+namespace Css {
+namespace Util {
+
+HtmlColor GetCssColor(const Css::Value* val,
+ const HtmlColor& def,
+ COLOR_ATTR* attr) {
+ if (val) {
+ switch (val->GetLexicalUnitType()) {
+ case Value::COLOR:
+ if (val->GetColorValue().IsDefined()) {
+ if (attr) *attr = ORIGINAL;
+ return val->GetColorValue();
+ }
+ break;
+ case Value::UNKNOWN:
+ if (attr) *attr = UNKNOWN;
+ return def;
+ case Value::IDENT:
+ switch (val->GetIdentifier().ident()) {
+ case Identifier::INHERIT:
+ if (attr) *attr = INHERIT;
+ return def;
+ case Identifier::TRANSPARENT:
+ if (attr) *attr = TRANSPARENT;
+ return def;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ if (attr) *attr = TRANSPARENT;
+ return def;
+}
+
+bool GetCssLength(const Css::Value* val, double parent_size,
+ double font_size, double unit,
+ bool can_negative, bool can_unitless,
+ double* size) {
+ if (val == NULL || val->GetLexicalUnitType() != Css::Value::NUMBER ||
+ (!can_negative && val->GetFloatValue() < 0))
+ return false;
+
+ switch (val->GetDimension()) {
+ case Css::Value::PERCENT:
+ if (parent_size != -1) {
+ *size = val->GetFloatValue() * parent_size / 100.0;
+ return true;
+ }
+ break;
+ case Css::Value::PX:
+ *size = val->GetFloatValue();
+ return true;
+ case Css::Value::EM:
+ *size = val->GetFloatValue() * font_size;
+ return true;
+ case Css::Value::EX:
+ *size = val->GetFloatValue() * font_size * FLAGS_font_size_adjustment;
+ return true;
+ case Css::Value::MM:
+ *size = val->GetFloatValue() / 0.265;
+ return true;
+ case Css::Value::CM:
+ *size = val->GetFloatValue() / 0.265 * 10;
+ return true;
+ case Css::Value::IN:
+ *size = val->GetFloatValue() * 96;
+ return true;
+ case Css::Value::PT:
+ *size = val->GetFloatValue() * 4 / 3;
+ return true;
+ case Css::Value::PC:
+ *size = val->GetFloatValue() * 16;
+ return true;
+ case Css::Value::NO_UNIT:
+ // 0 without unit is always allowed
+ if (can_unitless || val->GetFloatValue() == 0.0) {
+ *size = val->GetFloatValue() * unit;
+ return true;
+ }
+ break;
+ default:
+ break;
+ }
+ return false;
+}
+
+// Css system colors.
+// The structure is nearly literally copied from webutil/html/htmlcolor.cc.
+
+typedef struct RgbValue {
+ unsigned char r_;
+ unsigned char g_;
+ unsigned char b_;
+} RgbValue;
+
+// Color table for system colors.
+// This is only a rough estimation based on a typical setup.
+// when making change to known_system_color_values, please
+// also change the GetKnownSystemColorValue function because
+// entire table is hardcoded into the function for efficiency
+static const RgbValue known_system_color_values[] = {
+/* 0 activeborder */{212, 208, 200},
+/* 1 activecaption */{ 0, 84, 227},
+/* 2 appworkspace */{128, 128, 128},
+/* 3 background */{ 0, 78, 152},
+/* 4 buttonface */{236, 233, 216},
+/* 5 buttonhighlight */{255, 255, 255},
+/* 6 buttonshadow */{172, 168, 153},
+/* 7 buttontext */{ 0, 0, 0},
+/* 8 captiontext */{255, 255, 255},
+/* 9 graytext */{172, 168, 153},
+/*10 highlight */{ 49, 106, 197},
+/*11 highlighttext */{255, 255, 255},
+/*12 inactiveborder */{212, 208, 200},
+/*13 inactivecaption */{122, 150, 223},
+/*14 inactivecaptiontext */{216, 228, 248},
+/*15 infobackground */{255, 255, 225},
+/*16 infotext */{ 0, 0, 0},
+/*17 menu */{255, 255, 255},
+/*18 menutext */{ 0, 0, 0},
+/*19 scrollbar */{212, 208, 200},
+/*20 threeddarkshadow */{113, 111, 100},
+/*21 threedface */{236, 233, 216},
+/*22 threedhighlight */{255, 255, 255},
+/*23 threedlightshadow */{241, 239, 226},
+/*24 threedshadow */{172, 168, 153},
+/*25 window */{255, 255, 255},
+/*26 windowframe */{ 0, 0, 0},
+/*27 windowtext */{ 0, 0, 0},
+};
+
+const RgbValue* GetKnownSystemColorValue(const char *colorstr) {
+ switch (tolower(colorstr[0])) {
+ case 'a':
+ switch (tolower(colorstr[1])) {
+ case 'c':
+ if (!strcasecmp("activeborder", colorstr)) {
+ return &known_system_color_values[0];
+ } else if (!strcasecmp("activecaption", colorstr)) {
+ return &known_system_color_values[1];
+ }
+ return NULL;
+ case 'p':
+ if (!strcasecmp("appworkspace", colorstr)) {
+ return &known_system_color_values[2];
+ }
+ return NULL;
+ }
+ return NULL;
+ case 'b':
+ switch (tolower(colorstr[1])) {
+ case 'a':
+ if (!strcasecmp("background", colorstr)) {
+ return &known_system_color_values[3];
+ }
+ return NULL;
+ case 'u':
+ if (!strcasecmp("buttonface", colorstr)) {
+ return &known_system_color_values[4];
+ } else if (!strcasecmp("buttonhighlight", colorstr)) {
+ return &known_system_color_values[5];
+ } else if (!strcasecmp("buttonshadow", colorstr)) {
+ return &known_system_color_values[6];
+ } else if (!strcasecmp("buttontext", colorstr)) {
+ return &known_system_color_values[7];
+ }
+ return NULL;
+ }
+ return NULL;
+ case 'c':
+ if (!strcasecmp("captiontext", colorstr)) {
+ return &known_system_color_values[8];
+ }
+ return NULL;
+ case 'g':
+ if (!strcasecmp("graytext", colorstr)) {
+ return &known_system_color_values[9];
+ }
+ return NULL;
+ case 'h':
+ if (!strcasecmp("highlight", colorstr)) {
+ return &known_system_color_values[10];
+ } else if (!strcasecmp("highlighttext", colorstr)) {
+ return &known_system_color_values[11];
+ }
+ return NULL;
+ case 'i':
+ if (!strcasecmp("inactiveborder", colorstr)) {
+ return &known_system_color_values[12];
+ } else if (!strcasecmp("inactivecaption", colorstr)) {
+ return &known_system_color_values[13];
+ } else if (!strcasecmp("inactivecaptiontext", colorstr)) {
+ return &known_system_color_values[14];
+ } else if (!strcasecmp("infobackground", colorstr)) {
+ return &known_system_color_values[15];
+ } else if (!strcasecmp("infotext", colorstr)) {
+ return &known_system_color_values[16];
+ }
+ return NULL;
+ case 'm':
+ if (!strcasecmp("menu", colorstr)) {
+ return &known_system_color_values[17];
+ } else if (!strcasecmp("menutext", colorstr)) {
+ return &known_system_color_values[18];
+ }
+ return NULL;
+ case 's':
+ if (!strcasecmp("scrollbar", colorstr)) {
+ return &known_system_color_values[19];
+ }
+ return NULL;
+ case 't':
+ if (!strcasecmp("threeddarkshadow", colorstr)) {
+ return &known_system_color_values[20];
+ } else if (!strcasecmp("threedface", colorstr)) {
+ return &known_system_color_values[21];
+ } else if (!strcasecmp("threedhighlight", colorstr)) {
+ return &known_system_color_values[22];
+ } else if (!strcasecmp("threedlightshadow", colorstr)) {
+ return &known_system_color_values[23];
+ } else if (!strcasecmp("threedshadow", colorstr)) {
+ return &known_system_color_values[24];
+ }
+ return NULL;
+ case 'w':
+ if (!strcasecmp("window", colorstr)) {
+ return &known_system_color_values[25];
+ } else if (!strcasecmp("windowframe", colorstr)) {
+ return &known_system_color_values[26];
+ } else if (!strcasecmp("windowtext", colorstr)) {
+ return &known_system_color_values[27];
+ }
+ return NULL;
+ }
+ return NULL;
+}
+
+bool GetSystemColor(const string& colorstr, HtmlColor* color) {
+ const RgbValue *p_rgb = GetKnownSystemColorValue(colorstr.c_str());
+ if (p_rgb) {
+ color->SetValueFromRGB(p_rgb->r_, p_rgb->g_, p_rgb->b_);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool MediaAppliesToScreen(const StringPiece& media) {
+ std::vector<StringPiece> values;
+ StringPieceUtils::Split(media, ",", &values);
+ for (std::vector<StringPiece>::iterator iter = values.begin();
+ iter < values.end(); ++iter) {
+ StringPieceUtils::RemoveWhitespaceContext(&(*iter));
+ if (memcaseis(iter->data(), iter->size(), "all") ||
+ memcaseis(iter->data(), iter->size(), "screen"))
+ return true;
+ }
+ return false;
+}
+
+bool MediaAppliesToScreen(const std::vector<UnicodeText>& media) {
+ if (media.size() == 0) return true;
+
+ for (std::vector<UnicodeText>::const_iterator iter = media.begin();
+ iter < media.end(); ++iter) {
+ if (memcaseis(iter->utf8_data(), iter->utf8_length(), "all") ||
+ memcaseis(iter->utf8_data(), iter->utf8_length(), "screen"))
+ return true;
+ }
+ return false;
+}
+
+} // namespace Util
+} // namespace Css
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/util.h b/trunk/src/third_party/css_parser/src/webutil/css/util.h
new file mode 100644
index 0000000..cbd0f58
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/util.h
@@ -0,0 +1,82 @@
+/**
+ * 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.
+ */
+
+// Copyright 2007 Google Inc. All Rights Reserved.
+// Author: yian@google.com (Yian Huang)
+// Author: dpeng@google.com (Daniel Peng)
+
+#ifndef WEBUTIL_CSS_UTIL_H__
+#define WEBUTIL_CSS_UTIL_H__
+
+#include <string>
+#include <vector>
+
+#include "strings/stringpiece.h"
+#include "webutil/css/string.h"
+
+class HtmlColor;
+class UnicodeText;
+
+namespace Css {
+
+class Value;
+
+namespace Util {
+
+enum COLOR_ATTR {ORIGINAL, TRANSPARENT, UNKNOWN, INHERIT};
+
+// Parses CSS color value (may be a string, identifier or a color) into
+// HtmlColor. returns def if the color is invalid. set attr to one of the
+// following attributes: ORIGINAL if the color is valid, INHERIT if it is the
+// keyword inherit, UNKNOWN if it is the keywoard unknown, TRANSPARENT
+// if it is invalid otherwise.
+HtmlColor GetCssColor(const Css::Value* val, const HtmlColor& def,
+ COLOR_ATTR* attr);
+
+// Converts length or percentage string to absolute px units. Refer to
+// parent_size when seeing 10% (invalid if parent_size is -1). Refer to
+// font_size when seeing 1.2EM or 1.2EX. Invalid if val is NULL or it is not
+// a number. It can also be invalid if can_negative is set and the value is
+// negative, can_unitless works similarly. Returns if parsing succeeds, and
+// if so, size stores the result.
+bool GetCssLength(const Css::Value* val, double parent_size,
+ double font_size, double unit, bool can_negative,
+ bool can_unitless, double* size);
+
+// Updates color with system color specified in colorstr. The change is only
+// done only when the conversion succeeds, indicated by the return value.
+// For a list of system colors, please see
+// http://www.w3.org/TR/CSS21/ui.html#system-colors
+// Actual system colors depend on OS's graphic environment. For the purpose
+// of hidden text detection, we assume a typical setting based on Windows XP
+// default theme.
+bool GetSystemColor(const string& colorstr, HtmlColor* color);
+
+// Whether a media string (comma separated list of media) is compatible with
+// screen-oriented applications. It is valid if no media is specified or some
+// medium has the name "screen" or "all".
+bool MediaAppliesToScreen(const StringPiece& media);
+
+// Whether a media list is compatible with screen-oriented applications. It
+// is valid if no media is specified or some medium has the name "screen" or
+// "all".
+bool MediaAppliesToScreen(const std::vector<UnicodeText>& media);
+
+
+} // namespace Util
+} // namespace Css
+
+#endif // WEBUTIL_CSS_UTIL_H__
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/util_test.cc b/trunk/src/third_party/css_parser/src/webutil/css/util_test.cc
new file mode 100644
index 0000000..d6740aa
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/util_test.cc
@@ -0,0 +1,78 @@
+/**
+ * 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.
+ */
+
+// Copyright 2007 Google Inc. All Rights Reserved.
+// Author: yian@google.com (Yi-An Huang)
+
+#include "webutil/css/util.h"
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "testing/base/public/googletest.h"
+#include "testing/base/public/gunit.h"
+#include "webutil/css/string.h"
+#include "webutil/html/htmlcolor.h"
+
+namespace {
+
+class CsssystemcolorTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ color_.reset(new HtmlColor(0, 0, 0));
+ }
+
+ virtual void TearDown() {
+ }
+
+ void TestColor(const char* name, const char* mapped_to) {
+ CHECK(Css::Util::GetSystemColor(name, color_.get()));
+ // TODO(sligocki): Chromium CHECK_STREQ appears to be buggy. Fixit.
+ //CHECK_STREQ(color_->ToString().c_str(), mapped_to);
+ CHECK_EQ(color_->ToString(), string(mapped_to));
+ }
+
+ void TestInvalidColor(const char* name) {
+ CHECK(!Css::Util::GetSystemColor(name, color_.get()));
+ }
+
+ private:
+ scoped_ptr<HtmlColor> color_;
+};
+
+TEST_F(CsssystemcolorTest, common_colors) {
+ // test some "intuitive" colors
+ TestColor("menu", "#ffffff"); // menu background is white
+ TestColor("menutext", "#000000"); // menu foreground is black
+ TestColor("buttonface", "#ece9d8"); // button background is grey
+ TestColor("captiontext", "#ffffff"); // caption text is white
+ TestColor("windowtext", "#000000"); // window text is black
+}
+
+TEST_F(CsssystemcolorTest, case_sensitiveness) {
+ // colors are case insensitive
+ TestColor("activeBorder", "#d4d0c8");
+ TestColor("BACKGROUND", "#004e98");
+}
+
+TEST_F(CsssystemcolorTest, invalid_colors) {
+ TestInvalidColor("menux");
+ TestInvalidColor("text");
+ TestInvalidColor("win");
+}
+
+} // namespace
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/value.cc b/trunk/src/third_party/css_parser/src/webutil/css/value.cc
new file mode 100644
index 0000000..e6e675f
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/value.cc
@@ -0,0 +1,323 @@
+/**
+ * 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.
+ */
+
+// Copyright 2006 Google Inc. All Rights Reserved.
+// Author: dpeng@google.com (Daniel Peng)
+
+#include "util/gtl/stl_util-inl.h"
+
+#include "webutil/css/value.h"
+
+#include "base/logging.h"
+#include "strings/memutil.h"
+#include "webutil/html/htmlcolor.h"
+
+namespace Css {
+
+//
+// Static constants.
+//
+
+const char* const Value::kDimensionUnitText[] = {
+ "em", "ex", "px", "cm", "mm", "in", "pt", "pc",
+ "deg", "rad", "grad", "ms", "s", "hz", "khz", "%", "OTHER", "" };
+
+//
+// Constructors.
+//
+
+Value::Value(ValueType ty)
+ : type_(ty),
+ color_(0, 0, 0) {
+ DCHECK(ty == DEFAULT || ty == UNKNOWN);
+}
+
+Value::Value(float num, const UnicodeText& unit)
+ : type_(NUMBER),
+ num_(num),
+ color_(0, 0, 0) {
+ unit_ = UnitFromText(unit.utf8_data(), unit.utf8_length());
+ if (unit_ == OTHER)
+ str_ = unit;
+}
+
+Value::Value(float num, Unit unit)
+ : type_(NUMBER),
+ num_(num),
+ unit_(unit),
+ color_(0, 0, 0) {
+ DCHECK_NE(unit, OTHER);
+}
+
+Value::Value(ValueType ty, const UnicodeText& str)
+ : type_(ty),
+ str_(str),
+ color_(0, 0, 0) {
+ DCHECK(ty == STRING || ty == URI);
+}
+
+Value::Value(const Identifier& identifier)
+ : type_(IDENT),
+ identifier_(identifier),
+ color_(0, 0, 0) {
+}
+
+Value::Value(const Identifier::Ident ident)
+ : type_(IDENT),
+ identifier_(Identifier(ident)),
+ color_(0, 0, 0) {
+}
+
+Value::Value(ValueType ty, Values* params)
+ : type_(ty),
+ params_(params),
+ color_(0, 0, 0) {
+ DCHECK(params != NULL);
+ DCHECK(ty == RECT || ty == COUNTER);
+}
+
+Value::Value(const UnicodeText& func, Values* params)
+ : type_(FUNCTION),
+ str_(func),
+ params_(params),
+ color_(0, 0, 0) {
+ DCHECK(params != NULL);
+}
+
+Value::Value(HtmlColor c)
+ : type_(COLOR),
+ color_(c) {
+}
+
+Value::Value(const Value& other)
+ : type_(other.type_),
+ num_(other.num_),
+ unit_(other.unit_),
+ identifier_(other.identifier_),
+ str_(other.str_),
+ color_(other.color_) {
+ if (other.params_.get() != NULL) {
+ Values* vals = new Values;
+ vals->reserve(other.params_->size());
+ for (Values::const_iterator iter = other.params_->begin();
+ iter < other.params_->end(); ++iter)
+ vals->push_back(new Value(**iter));
+ params_.reset(vals);
+ }
+}
+
+Value& Value::operator=(const Value& other) {
+ if (this == &other) return *this;
+ type_ = other.type_;
+ num_ = other.num_;
+ unit_ = other.unit_;
+ identifier_ = other.identifier_;
+ str_ = other.str_;
+ color_ = other.color_;
+ if (other.params_.get() != NULL) {
+ Values* vals = new Values;
+ vals->reserve(other.params_->size());
+ for (Values::const_iterator iter = other.params_->begin();
+ iter < other.params_->end(); ++iter)
+ vals->push_back(new Value(**iter));
+ params_.reset(vals);
+ } else {
+ params_.reset();
+ }
+ return *this;
+}
+
+bool Value::Equals(const Value& other) const {
+ if (type_ != other.type_) return false;
+ switch (type_) {
+ case DEFAULT:
+ case UNKNOWN:
+ return true;
+ case NUMBER:
+ return unit_ == other.unit_ && num_ == other.num_;
+ case URI:
+ case STRING:
+ return str_ == other.str_;
+ case IDENT:
+ if (identifier_.ident() != other.identifier_.ident())
+ return false;
+ if (identifier_.ident() == Identifier::OTHER)
+ return identifier_.ident_text() == other.identifier_.ident_text();
+ return true;
+ case COLOR:
+ if (color_.IsDefined() != other.color_.IsDefined())
+ return false;
+ if (color_.IsDefined())
+ return color_.rgb() == other.color_.rgb();
+ return true;
+ case FUNCTION:
+ if (str_ != other.str_)
+ return false;
+ // pass through
+ case COUNTER:
+ case RECT:
+ if (params_.get() == NULL)
+ return other.params_.get() == NULL;
+ if (params_->size() != other.params_->size())
+ return false;
+ for (Values::const_iterator iter = params_->begin(),
+ other_iter = other.params_->begin();
+ iter < params_->end(); ++iter, ++other_iter)
+ if (!(**iter).Equals(**other_iter))
+ return false;
+ return true;
+ default:
+ LOG(FATAL) << "Unknown type:" << type_;
+ }
+}
+
+//
+// Static functions mapping between units and strings
+//
+
+Value::Unit Value::UnitFromText(const char* str, int len) {
+ switch (len) {
+ case 0:
+ return NO_UNIT;
+ case 1:
+ switch (str[0]) {
+ case '%': return PERCENT;
+ case 's': case 'S': return S;
+ default: return OTHER;
+ }
+ case 2:
+ switch (str[0]) {
+ case 'e': case 'E':
+ switch (str[1]) {
+ case 'm': case 'M': return EM;
+ case 'x': case 'X': return EX;
+ default: return OTHER;
+ }
+ case 'p': case 'P':
+ switch (str[1]) {
+ case 'x': case 'X': return PX;
+ case 't': case 'T': return PT;
+ case 'c': case 'C': return PC;
+ default: return OTHER;
+ }
+ case 'c': case 'C':
+ switch (str[1]) {
+ case 'm': case 'M': return CM;
+ default: return OTHER;
+ }
+ case 'm': case 'M':
+ switch (str[1]) {
+ case 'm': case 'M': return MM;
+ case 's': case 'S': return MS;
+ default: return OTHER;
+ }
+ case 'i': case 'I':
+ switch (str[1]) {
+ case 'n': case 'N': return IN;
+ default: return OTHER;
+ }
+ case 'h': case 'H':
+ switch (str[1]) {
+ case 'z': case 'Z': return HZ;
+ default: return OTHER;
+ }
+ default:
+ return OTHER;
+ }
+ case 3:
+ if (memcasecmp(str, "deg", 3) == 0) return DEG;
+ if (memcasecmp(str, "rad", 3) == 0) return RAD;
+ if (memcasecmp(str, "khz", 3) == 0) return KHZ;
+ return OTHER;
+ case 4:
+ if (memcasecmp(str, "grad", 3) == 0) return GRAD;
+ return OTHER;
+ default:
+ return OTHER;
+ }
+}
+
+const char* Value::TextFromUnit(Unit u) {
+ DCHECK_LT(u, NUM_UNITS);
+ COMPILE_ASSERT(arraysize(kDimensionUnitText) == NUM_UNITS, dimensionunitsize);
+
+ return kDimensionUnitText[u];
+}
+
+//
+// Accessors.
+//
+
+Value::ValueType Value::GetLexicalUnitType() const {
+ return type_;
+}
+
+string Value::GetDimensionUnitText() const {
+ DCHECK_EQ(type_, NUMBER);
+ if (unit_ == OTHER)
+ return string(str_.utf8_data(), str_.utf8_length());
+ else
+ return TextFromUnit(unit_);
+}
+
+Value::Unit Value::GetDimension() const {
+ DCHECK_EQ(type_, NUMBER);
+ return unit_;
+}
+
+int Value::GetIntegerValue() const {
+ DCHECK_EQ(type_, NUMBER);
+ return static_cast<int>(num_);
+}
+
+float Value::GetFloatValue() const {
+ DCHECK_EQ(type_, NUMBER);
+ return num_;
+}
+
+const Values* Value::GetParameters() const {
+ DCHECK(type_ == FUNCTION || type_ == RECT || type_ == COUNTER);
+ return params_.get();
+}
+
+const UnicodeText& Value::GetFunctionName() const {
+ DCHECK_EQ(type_, FUNCTION);
+ return str_;
+}
+
+const UnicodeText& Value::GetStringValue() const {
+ DCHECK(type_ == URI || type_ == STRING);
+ return str_;
+}
+
+UnicodeText Value::GetIdentifierText() const {
+ DCHECK_EQ(type_, IDENT);
+ return identifier_.ident_text();
+}
+
+const Identifier& Value::GetIdentifier() const {
+ DCHECK_EQ(type_, IDENT);
+ return identifier_;
+}
+
+const HtmlColor& Value::GetColorValue() const {
+ DCHECK_EQ(type_, COLOR);
+ return color_;
+}
+
+Values::~Values() { STLDeleteElements(this); }
+
+} // namespace
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/value.h b/trunk/src/third_party/css_parser/src/webutil/css/value.h
new file mode 100644
index 0000000..c3402c7
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/value.h
@@ -0,0 +1,166 @@
+/**
+ * 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.
+ */
+
+// Copyright 2006 Google Inc. All Rights Reserved.
+// Author: dpeng@google.com (Daniel Peng)
+
+#ifndef WEBUTIL_CSS_VALUE_H__
+#define WEBUTIL_CSS_VALUE_H__
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/scoped_ptr.h"
+#include "util/utf8/public/unicodetext.h"
+#include "webutil/css/identifier.h"
+#include "webutil/css/string.h"
+#include "webutil/html/htmlcolor.h"
+
+// resolving conflict on macro HZ defined elsewhere.
+#ifdef HZ
+const int HZ_temporary = HZ;
+#undef HZ
+const int HZ = HZ_temporary;
+#endif
+
+namespace Css {
+
+class Values;
+
+// A Value represents a value in CSS (maybe more generally, a
+// lexical unit). There are many different types of these, so you can
+// think of a Value as a tagged union of various values. The tag
+// is set by the constructor and accessed with GetLexicalUnitType().
+// The values are also set by the constructor and accessed with the
+// various accessors.
+class Value {
+ public:
+ enum ValueType { NUMBER, URI, COUNTER, FUNCTION, RECT,
+ COLOR, STRING, IDENT, UNKNOWN, DEFAULT };
+ enum Unit { EM, EX, PX, CM, MM, IN, PT, PC,
+ DEG, RAD, GRAD, MS, S, HZ, KHZ, PERCENT, OTHER, NO_UNIT,
+ NUM_UNITS };
+
+ // These constructors generate Values of various types.
+
+ Value() : type_(DEFAULT), color_(0, 0, 0) { }
+
+ // UNKNOWN or DEFAULT
+ Value(ValueType ty); // NOLINT
+
+ // NUMBER with unit. OTHER is not a valid unit here. Use the next form:
+ Value(float num, Unit unit);
+
+ // NUMBER with unit; we convert unit to an enum for you. If it's
+ // not a known unit, we use the OTHER enum and save the text.
+ Value(float num, const UnicodeText& unit);
+
+ // Any of the string types (URI, STRING). For IDENT, use the next
+ // constructor instead.
+ Value(ValueType ty, const UnicodeText& str);
+
+ // IDENT from an identifier.
+ explicit Value(const Identifier& identifier);
+ explicit Value(const Identifier::Ident ident);
+
+ // Any of the special function types (COUNTER, RECT)
+ // NOTE: The ownership of params will be taken.
+ // params cannot be NULL, if no parameters are needed, pass an empty Values.
+ explicit Value(ValueType ty, Values* params);
+
+ // FUNCTION with name func
+ // NOTE: The ownership of params will be taken.
+ // params cannot be NULL, if no parameters are needed, pass an empty Values.
+ explicit Value(const UnicodeText& func, Values* params);
+
+ // COLOR.
+ explicit Value(HtmlColor color);
+
+ // copy constructor and assignment operator
+ Value(const Value& other);
+ Value& operator=(const Value& other);
+
+ // equality.
+ bool Equals(const Value& other) const;
+
+ // Given the text of a CSS unit, UnitFromText returns the
+ // corresponding enum. If no such unit is found, UnitFromText
+ // returns OTHER. Since all CSS units are ASCII, we are happy with
+ // ASCII, UTF8, Latin-1, etc.
+ static Unit UnitFromText(const char* s, int len);
+ // Given a unit, returns its string representation. If u is
+ // NO_UNIT, returns "". If u is OTHER, we return "OTHER", but this
+ // may not be what you want.
+ static const char* TextFromUnit(Unit u);
+
+ // Returns a string representation of the value.
+ string ToString() const;
+
+ // Accessors. Modeled after
+ // http://www.w3.org/Style/CSS/SAC/doc/org/w3c/css/sac/LexicalUnit.html
+
+ ValueType GetLexicalUnitType() const; // The type of value
+
+ // Each of these accessors is only valid for certain types. The
+ // comment indicates for which types they are valid; we DCHECK this
+ // precondition.
+ string GetDimensionUnitText() const; // NUMBER: the unit as a string.
+ Unit GetDimension() const; // NUMBER: the unit.
+ int GetIntegerValue() const; // NUMBER: the integer value.
+ float GetFloatValue() const; // NUMBER: the float value.
+ const Values* GetParameters() const; // FUNCTION: the function params
+ const UnicodeText& GetFunctionName() const; // FUNCTION: the function name.
+ const UnicodeText& GetStringValue() const; // URI, STRING: the string value
+ UnicodeText GetIdentifierText() const; // IDENT: the ident as a string.
+ const Identifier& GetIdentifier() const; // IDENT: identifier.
+ const HtmlColor& GetColorValue() const; // COLOR: the color value
+
+ private:
+ ValueType type_; // indicates the type of value. Always valid.
+ float num_; // for NUMBER (integer values are stored as floats)
+ Unit unit_; // for NUMBER
+ Identifier identifier_; // for IDENT
+ UnicodeText str_; // for NUMBER (OTHER unit_), URI, STRING, FUNCTION
+ scoped_ptr<Values> params_; // FUNCTION, COUNTER and RECT params
+ HtmlColor color_; // COLOR
+
+ // kDimensionUnitText stores the name of each unit (see TextFromUnit)
+ static const char* const kDimensionUnitText[];
+};
+
+// Values is a vector of Value*, which we own and will delete
+// upon destruction. If you remove elements from Values, you are
+// responsible for deleting them.
+// Also, be careful --- there's no virtual destructor, so this must be
+// deleted as a Values.
+class Values : public std::vector<Value*> {
+ public:
+ Values() : std::vector<Value*>() { }
+ ~Values();
+
+ // We provide syntactic sugar for accessing elements.
+ // values->get(i) looks better than (*values)[i])
+ const Value* get(int i) const { return (*this)[i]; }
+
+ string ToString() const;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Values);
+};
+
+} // namespace
+
+#endif // WEBUTIL_CSS_VALUE_H__
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/valuevalidator.cc b/trunk/src/third_party/css_parser/src/webutil/css/valuevalidator.cc
new file mode 100644
index 0000000..0af595c
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/valuevalidator.cc
@@ -0,0 +1,601 @@
+/**
+ * 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.
+ */
+
+// Copyright 2007 Google Inc. All Rights Reserved.
+// Author: yian@google.com (Yi-An Huang)
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "util/gtl/map-util.h"
+#include "util/gtl/stl_util-inl.h"
+#include "webutil/css/valuevalidator.h"
+
+namespace Css {
+
+const Property::Prop kEndProp = static_cast<Property::Prop>(-1);
+const Value::ValueType kEndType = static_cast<Value::ValueType>(-1);
+const Identifier::Ident kEndIdent = static_cast<Identifier::Ident>(-1);
+
+// For each CSS property (or a group of properties), the valid value types and
+// valid IDENT values. We do not list the IDENT, DEFAULT and UNKOWN types
+// because they are always valid, we also do not list INHERIT values explicitly
+// for the same reason. Arrays are terminated with -1.
+static struct valid_prop_info_t {
+ Property::Prop props[10];
+ Value::ValueType types[10];
+ Identifier::Ident idents[30];
+ // the following properties make sense for numbers.
+ bool accept_percent;
+ bool accept_no_unit;
+ bool accept_length;
+ bool accept_negative;
+} kValidPropInfo[] = {
+ // Chapter 8: Box model
+
+ { { Property::BORDER_COLOR, Property::BORDER_TOP_COLOR,
+ Property::BORDER_RIGHT_COLOR, Property::BORDER_BOTTOM_COLOR,
+ Property::BORDER_LEFT_COLOR, kEndProp },
+ { Value::COLOR, kEndType },
+ { Identifier::TRANSPARENT, Identifier::GOOG_INITIAL, kEndIdent },
+ },
+ { { Property::BORDER_STYLE, Property::BORDER_TOP_STYLE,
+ Property::BORDER_RIGHT_STYLE, Property::BORDER_BOTTOM_STYLE,
+ Property::BORDER_LEFT_STYLE, kEndProp },
+ { kEndType },
+ { Identifier::NONE, Identifier::HIDDEN, Identifier::DOTTED,
+ Identifier::DASHED, Identifier::SOLID, Identifier::DOUBLE,
+ Identifier::GROOVE, Identifier::RIDGE, Identifier::INSET,
+ Identifier::OUTSET, kEndIdent },
+ },
+ { { Property::BORDER_WIDTH, Property::BORDER_TOP_WIDTH,
+ Property::BORDER_RIGHT_WIDTH, Property::BORDER_BOTTOM_WIDTH,
+ Property::BORDER_LEFT_WIDTH, kEndProp },
+ { Value::NUMBER, kEndType },
+ { Identifier::THIN, Identifier::MEDIUM, Identifier::THICK, kEndIdent },
+ false, false, true, false,
+ },
+ { { Property::BORDER, Property::BORDER_TOP, Property::BORDER_RIGHT,
+ Property::BORDER_BOTTOM, Property::BORDER_LEFT, kEndProp },
+ { Value::COLOR, Value::NUMBER, kEndType },
+ { Identifier::TRANSPARENT, Identifier::GOOG_INITIAL,
+ Identifier::NONE, Identifier::HIDDEN, Identifier::DOTTED,
+ Identifier::DASHED, Identifier::SOLID, Identifier::DOUBLE,
+ Identifier::GROOVE, Identifier::RIDGE, Identifier::INSET,
+ Identifier::OUTSET,
+ Identifier::THIN, Identifier::MEDIUM, Identifier::THICK, kEndIdent },
+ false, false, true, false,
+ },
+
+ { { Property::MARGIN, Property::MARGIN_RIGHT, Property::MARGIN_LEFT,
+ Property::MARGIN_TOP, Property::MARGIN_BOTTOM, kEndProp },
+ { Value::NUMBER, kEndType },
+ { Identifier::AUTO, kEndIdent },
+ true, false, true, true,
+ },
+
+ { { Property::PADDING, Property::PADDING_RIGHT, Property::PADDING_LEFT,
+ Property::PADDING_TOP, Property::PADDING_BOTTOM, kEndProp },
+ { Value::NUMBER, kEndType },
+ { kEndIdent },
+ true, false, true, false,
+ },
+
+ // Chapter 9: Visual formatting model
+
+ { { Property::BOTTOM, Property::LEFT, Property::RIGHT, Property::TOP,
+ kEndProp },
+ { Value::NUMBER, kEndType },
+ { Identifier::AUTO, kEndIdent },
+ true, false, true, true,
+ },
+
+ { { Property::CLEAR, kEndProp },
+ { kEndType },
+ { Identifier::NONE, Identifier::LEFT, Identifier::RIGHT, Identifier::BOTH,
+ kEndIdent },
+ },
+
+ { { Property::DIRECTION, kEndProp },
+ { kEndType },
+ { Identifier::LTR, Identifier::RTL, kEndIdent },
+ },
+
+ { { Property::DISPLAY, kEndProp },
+ { kEndType },
+ { Identifier::INLINE, Identifier::BLOCK, Identifier::LIST_ITEM,
+ Identifier::RUN_IN, Identifier::INLINE_BLOCK, Identifier::TABLE,
+ Identifier::INLINE_TABLE, Identifier::TABLE_ROW_GROUP,
+ Identifier::TABLE_HEADER_GROUP, Identifier::TABLE_FOOTER_GROUP,
+ Identifier::TABLE_ROW, Identifier::TABLE_COLUMN_GROUP,
+ Identifier::TABLE_COLUMN, Identifier::TABLE_CELL,
+ Identifier::TABLE_CAPTION, Identifier::NONE, kEndIdent },
+ },
+
+ { { Property::FLOAT, kEndProp },
+ { kEndType },
+ { Identifier::LEFT, Identifier::RIGHT, Identifier::NONE, kEndIdent },
+ },
+
+ { { Property::POSITION, kEndProp },
+ { kEndType },
+ { Identifier::STATIC, Identifier::RELATIVE, Identifier::ABSOLUTE,
+ Identifier::FIXED, kEndIdent },
+ },
+
+ { { Property::UNICODE_BIDI, kEndProp },
+ { kEndType },
+ { Identifier::NORMAL, Identifier::EMBED, Identifier::BIDI_OVERRIDE,
+ kEndIdent },
+ },
+
+ { { Property::Z_INDEX, kEndProp },
+ { Value::NUMBER, kEndType },
+ { Identifier::AUTO, kEndIdent },
+ false, true, false, true,
+ },
+
+ // Chapter 10: Visual formatting model details
+
+ { { Property::HEIGHT, Property::WIDTH, kEndProp },
+ { Value::NUMBER, kEndType },
+ { Identifier::AUTO, kEndIdent },
+ true, false, true, false,
+ },
+
+ { { Property::LINE_HEIGHT, kEndProp },
+ { Value::NUMBER, kEndType },
+ { Identifier::NORMAL, kEndIdent },
+ true, true, true, false,
+ },
+
+ { { Property::MAX_HEIGHT, Property::MAX_WIDTH, kEndProp },
+ { Value::NUMBER, kEndType },
+ { Identifier::NONE, kEndIdent },
+ true, false, true, false,
+ },
+
+ { { Property::MIN_HEIGHT, Property::MIN_WIDTH, kEndProp },
+ { Value::NUMBER, kEndType },
+ { kEndIdent },
+ true, false, true, false,
+ },
+
+ { { Property::VERTICAL_ALIGN, kEndProp },
+ { Value::NUMBER, kEndType },
+ { Identifier::BASELINE, Identifier::SUB, Identifier::SUPER,
+ Identifier::TOP, Identifier::TEXT_TOP, Identifier::MIDDLE,
+ Identifier::BOTTOM, Identifier::TEXT_BOTTOM, kEndIdent },
+ true, false, true, true,
+ },
+
+ // Chapter 11: Visual effects
+
+ { { Property::CLIP, kEndProp },
+ { Value::RECT, kEndType },
+ { Identifier::AUTO, kEndIdent },
+ false, false, true, true,
+ },
+
+ { { Property::OVERFLOW, kEndProp },
+ { kEndType },
+ { Identifier::VISIBLE, Identifier::HIDDEN, Identifier::SCROLL,
+ Identifier::AUTO, kEndIdent },
+ },
+
+ { { Property::VISIBILITY, kEndProp },
+ { kEndType },
+ { Identifier::VISIBLE, Identifier::HIDDEN, Identifier::COLLAPSE, kEndIdent
+ },
+ },
+
+ // Chapter 12: Generated content, automatic numbering, and lists
+
+ { { Property::CONTENT, kEndProp },
+ { Value::STRING, Value::URI, Value::COUNTER, Value::FUNCTION, kEndType },
+ { Identifier::NORMAL, Identifier::NONE,
+ Identifier::OPEN_QUOTE, Identifier::CLOSE_QUOTE,
+ Identifier::NO_OPEN_QUOTE, Identifier::NO_CLOSE_QUOTE, kEndIdent },
+ },
+
+ { { Property::COUNTER_INCREMENT, Property::COUNTER_RESET, kEndProp },
+ { Value::NUMBER, kEndType },
+ { Identifier::NONE, Identifier::OTHER, kEndIdent },
+ false, true, false, true,
+ },
+
+ { { Property::LIST_STYLE_IMAGE, kEndProp },
+ { Value::URI, kEndType },
+ { Identifier::NONE, kEndIdent },
+ },
+ { { Property::LIST_STYLE_POSITION, kEndProp },
+ { kEndType },
+ { Identifier::INSIDE, Identifier::OUTSIDE, kEndIdent },
+ },
+ { { Property::LIST_STYLE_TYPE, kEndProp },
+ { kEndType },
+ { Identifier::DISC, Identifier::CIRCLE, Identifier::SQUARE,
+ Identifier::DECIMAL, Identifier::DECIMAL_LEADING_ZERO,
+ Identifier::LOWER_ROMAN, Identifier::UPPER_ROMAN,
+ Identifier::LOWER_GREEK, Identifier::LOWER_LATIN,
+ Identifier::UPPER_LATIN, Identifier::ARMENIAN, Identifier::GEORGIAN,
+ Identifier::LOWER_ALPHA, Identifier::UPPER_ALPHA,
+ Identifier::NONE, kEndIdent },
+ },
+ { { Property::LIST_STYLE, kEndProp },
+ { Value::URI, kEndType },
+ { Identifier::NONE, Identifier::INSIDE, Identifier::OUTSIDE,
+ Identifier::DISC, Identifier::CIRCLE, Identifier::SQUARE,
+ Identifier::DECIMAL, Identifier::DECIMAL_LEADING_ZERO,
+ Identifier::LOWER_ROMAN, Identifier::UPPER_ROMAN,
+ Identifier::LOWER_GREEK, Identifier::LOWER_LATIN,
+ Identifier::UPPER_LATIN, Identifier::ARMENIAN, Identifier::GEORGIAN,
+ Identifier::LOWER_ALPHA, Identifier::UPPER_ALPHA, kEndIdent },
+ },
+
+ { { Property::QUOTES, kEndProp },
+ { Value::STRING, kEndType },
+ { Identifier::NONE, kEndIdent },
+ },
+
+ // Chapter 13: Paged media
+
+ { { Property::ORPHANS, kEndProp },
+ { Value::NUMBER, kEndType },
+ { kEndIdent },
+ false, true, false, false,
+ },
+
+ { { Property::PAGE_BREAK_AFTER, Property::PAGE_BREAK_BEFORE, kEndProp },
+ { kEndType },
+ { Identifier::AUTO, Identifier::ALWAYS, Identifier::AVOID,
+ Identifier::LEFT, Identifier::RIGHT, kEndIdent },
+ },
+ { { Property::PAGE_BREAK_INSIDE, kEndProp },
+ { kEndType },
+ { Identifier::AVOID, Identifier::AUTO, kEndIdent },
+ },
+
+ { { Property::WIDOWS, kEndProp },
+ { Value::NUMBER, kEndType },
+ { kEndIdent },
+ false, true, false, false,
+ },
+
+ // Chapter 14: Colors and Backgrounds
+
+ { { Property::BACKGROUND_ATTACHMENT, kEndProp },
+ { kEndType },
+ { Identifier::SCROLL, Identifier::FIXED, kEndIdent },
+ },
+ { { Property::BACKGROUND_COLOR, kEndProp },
+ { Value::COLOR, kEndType },
+ { Identifier::TRANSPARENT, kEndIdent },
+ },
+ { { Property::BACKGROUND_IMAGE, kEndProp },
+ { Value::URI, kEndType },
+ { Identifier::NONE, kEndIdent },
+ },
+ { { Property::BACKGROUND_POSITION, kEndProp },
+ { Value::NUMBER, kEndType },
+ { Identifier::LEFT, Identifier::CENTER, Identifier::RIGHT, Identifier::TOP,
+ Identifier::BOTTOM, kEndIdent },
+ true, false, true, true,
+ },
+ { { Property::BACKGROUND_REPEAT, kEndProp },
+ { kEndType },
+ { Identifier::REPEAT, Identifier::REPEAT_X, Identifier::REPEAT_Y,
+ Identifier::NO_REPEAT, kEndIdent },
+ },
+ { { Property::BACKGROUND, kEndProp },
+ { Value::COLOR, Value::URI, Value::NUMBER, kEndType },
+ { Identifier::SCROLL, Identifier::FIXED, Identifier::TRANSPARENT,
+ Identifier::NONE, Identifier::LEFT, Identifier::CENTER,
+ Identifier::RIGHT, Identifier::TOP, Identifier::BOTTOM,
+ Identifier::REPEAT, Identifier::REPEAT_X, Identifier::REPEAT_Y,
+ Identifier::NO_REPEAT, kEndIdent },
+ true, false, true, true,
+ },
+
+ { { Property::COLOR, kEndProp },
+ { Value::COLOR, kEndType },
+ { Identifier::GOOG_BODY_COLOR, Identifier::GOOG_BODY_LINK_COLOR,
+ kEndIdent },
+ },
+
+ // Chapter 15: Fonts
+
+ { { Property::FONT_FAMILY, kEndProp },
+ { Value::STRING, kEndType },
+ { Identifier::SERIF, Identifier::SANS_SERIF, Identifier::CURSIVE,
+ Identifier::FANTASY, Identifier::MONOSPACE, Identifier::OTHER, kEndIdent
+ },
+ },
+ { { Property::FONT_SIZE, kEndProp },
+ { Value::NUMBER, kEndType },
+ { Identifier::XX_SMALL, Identifier::X_SMALL, Identifier::SMALL,
+ Identifier::MEDIUM, Identifier::LARGE, Identifier::X_LARGE,
+ Identifier::XX_LARGE, Identifier::LARGER, Identifier::SMALLER,
+ Identifier::GOOG_BIG, Identifier::GOOG_SMALL, kEndIdent
+ },
+ true, false, true, false,
+ },
+ { { Property::FONT_STYLE, kEndProp },
+ { kEndType },
+ { Identifier::NORMAL, Identifier::ITALIC, Identifier::OBLIQUE, kEndIdent },
+ },
+ { { Property::FONT_VARIANT, kEndProp },
+ { kEndType },
+ { Identifier::NORMAL, Identifier::SMALL_CAPS, kEndIdent },
+ },
+ { { Property::FONT_WEIGHT, kEndProp },
+ { Value::NUMBER, kEndType },
+ { Identifier::NORMAL, Identifier::BOLD, Identifier::BOLDER,
+ Identifier::LIGHTER, kEndIdent },
+ false, true, true, false,
+ },
+ { { Property::FONT, kEndProp },
+ { Value::STRING, Value::NUMBER, kEndType },
+ { Identifier::SERIF, Identifier::SANS_SERIF, Identifier::CURSIVE,
+ Identifier::FANTASY, Identifier::MONOSPACE, Identifier::OTHER,
+ Identifier::XX_SMALL, Identifier::X_SMALL, Identifier::SMALL,
+ Identifier::MEDIUM, Identifier::LARGE, Identifier::X_LARGE,
+ Identifier::XX_LARGE, Identifier::LARGER, Identifier::SMALLER,
+ Identifier::NORMAL, Identifier::ITALIC, Identifier::OBLIQUE,
+ Identifier::SMALL_CAPS, Identifier::BOLD, Identifier::BOLDER,
+ Identifier::LIGHTER,
+ Identifier::CAPTION, Identifier::ICON, Identifier::MENU,
+ Identifier::MESSAGE_BOX, Identifier::MESSAGE_BOX,
+ Identifier::SMALL_CAPTION, Identifier::STATUS_BAR, kEndIdent },
+ true, true, true, false,
+ },
+
+ // Chapter 16: Text
+
+ { { Property::LETTER_SPACING, kEndProp },
+ { Value::NUMBER, kEndType },
+ { Identifier::NORMAL, kEndIdent },
+ false, false, true, true,
+ },
+
+ { { Property::TEXT_ALIGN, kEndProp },
+ { kEndType },
+ { Identifier::LEFT, Identifier::RIGHT, Identifier::CENTER,
+ Identifier::JUSTIFY, Identifier::GOOG_INITIAL, kEndIdent },
+ },
+ { { Property::TEXT_DECORATION, kEndProp },
+ { kEndType },
+ { Identifier::NONE, Identifier::UNDERLINE, Identifier::OVERLINE,
+ Identifier::LINE_THROUGH, Identifier::BLINK, kEndIdent },
+ },
+ { { Property::TEXT_INDENT, kEndProp },
+ { Value::NUMBER, kEndType },
+ { kEndIdent },
+ true, false, true, true,
+ },
+ { { Property::TEXT_TRANSFORM, kEndProp },
+ { kEndType },
+ { Identifier::CAPITALIZE, Identifier::UPPERCASE, Identifier::LOWERCASE,
+ Identifier::NONE, kEndIdent },
+ },
+
+ { { Property::WHITE_SPACE, kEndProp },
+ { kEndType },
+ { Identifier::NORMAL, Identifier::PRE, Identifier::NOWRAP,
+ Identifier::PRE_WRAP, Identifier::PRE_LINE, kEndIdent },
+ },
+
+ { { Property::WORD_SPACING, kEndProp },
+ { Value::NUMBER, kEndType },
+ { Identifier::NORMAL, kEndIdent },
+ false, false, true, true,
+ },
+
+ // Chapter 17: Tables
+
+ { { Property::BORDER_COLLAPSE, kEndProp },
+ { kEndType },
+ { Identifier::COLLAPSE, Identifier::SEPARATE, kEndIdent },
+ },
+ { { Property::BORDER_SPACING, kEndProp },
+ { Value::NUMBER, kEndType },
+ { kEndIdent },
+ false, false, true, false,
+ },
+
+ { { Property::CAPTION_SIDE, kEndProp },
+ { kEndType },
+ { Identifier::TOP, Identifier::BOTTOM, kEndIdent },
+ },
+
+ { { Property::EMPTY_CELLS, kEndProp },
+ { kEndType },
+ { Identifier::SHOW, Identifier::HIDE, kEndIdent },
+ },
+
+ { { Property::TABLE_LAYOUT, kEndProp },
+ { kEndType },
+ { Identifier::AUTO, Identifier::FIXED, kEndIdent },
+ },
+
+ // Chapter 18: User interface
+
+ { { Property::CURSOR, kEndProp },
+ { Value::URI, kEndType },
+ { Identifier::AUTO, Identifier::CROSSHAIR, Identifier::DEFAULT,
+ Identifier::POINTER, Identifier::MOVE, Identifier::E_RESIZE,
+ Identifier::NE_RESIZE, Identifier::NW_RESIZE, Identifier::N_RESIZE,
+ Identifier::SE_RESIZE, Identifier::SW_RESIZE, Identifier::S_RESIZE,
+ Identifier::W_RESIZE, Identifier::TEXT, Identifier::WAIT,
+ Identifier::HELP, Identifier::PROGRESS, kEndIdent },
+ },
+
+ { { Property::OUTLINE_COLOR, kEndProp },
+ { Value::COLOR, kEndType },
+ { Identifier::INVERT, kEndIdent },
+ },
+ { { Property::OUTLINE_STYLE, kEndProp },
+ { kEndType },
+ { Identifier::NONE, Identifier::DOTTED, Identifier::DASHED,
+ Identifier::SOLID, Identifier::DOUBLE, Identifier::GROOVE,
+ Identifier::RIDGE, Identifier::INSET, Identifier::OUTSET, kEndIdent },
+ },
+ { { Property::OUTLINE_WIDTH, kEndProp },
+ { Value::NUMBER, kEndType },
+ { Identifier::THIN, Identifier::MEDIUM, Identifier::THICK, kEndIdent },
+ false, false, true, false,
+ },
+ { { Property::OUTLINE, kEndProp },
+ { Value::COLOR, Value::NUMBER, kEndType },
+ { Identifier::INVERT, Identifier::NONE, Identifier::DOTTED,
+ Identifier::DASHED, Identifier::SOLID, Identifier::DOUBLE,
+ Identifier::GROOVE, Identifier::RIDGE, Identifier::INSET,
+ Identifier::OUTSET, Identifier::THIN, Identifier::MEDIUM,
+ Identifier::THICK, kEndIdent },
+ false, false, true, false,
+ }
+};
+
+struct PropertyValidationInfo {
+ std::set<Value::ValueType> valid_types;
+ std::set<Identifier::Ident> valid_idents;
+ bool accept_percent;
+ bool accept_no_unit;
+ bool accept_length;
+ bool accept_negative;
+};
+
+ValueValidator::ValueValidator() {
+ validation_info_.resize(Property::OTHER + 1);
+ for (int i = 0, n = arraysize(kValidPropInfo); i < n; ++i) {
+ std::set<Value::ValueType> t;
+ t.insert(Value::IDENT);
+ t.insert(Value::UNKNOWN);
+ t.insert(Value::DEFAULT);
+ for (int j = 0; kValidPropInfo[i].types[j] != -1; ++j) {
+ CHECK_LT(j, arraysize(kValidPropInfo[i].types));
+ t.insert(kValidPropInfo[i].types[j]);
+ }
+
+ std::set<Identifier::Ident> s;
+ s.insert(Identifier::INHERIT);
+ for (int j = 0; kValidPropInfo[i].idents[j] != -1; ++j) {
+ CHECK_LT(j, arraysize(kValidPropInfo[i].idents));
+ s.insert(kValidPropInfo[i].idents[j]);
+ }
+
+ for (int j = 0; kValidPropInfo[i].props[j] != -1; ++j) {
+ CHECK_LT(j, arraysize(kValidPropInfo[i].props));
+ PropertyValidationInfo* info = new PropertyValidationInfo;
+ validation_info_[kValidPropInfo[i].props[j]] = info;
+ info->valid_types = t;
+ info->valid_idents = s;
+ info->accept_percent = kValidPropInfo[i].accept_percent;
+ info->accept_no_unit = kValidPropInfo[i].accept_no_unit;
+ info->accept_length = kValidPropInfo[i].accept_length;
+ info->accept_negative = kValidPropInfo[i].accept_negative;
+ }
+ }
+}
+
+ValueValidator::~ValueValidator() {
+ STLDeleteElements(&validation_info_);
+}
+
+bool ValueValidator::IsValidValue(Property::Prop prop,
+ const Value& value,
+ bool quirks_mode) const {
+ if (!IsValidType(prop, value.GetLexicalUnitType()))
+ return false;
+ if (value.GetLexicalUnitType() == Value::IDENT &&
+ !IsValidIdentifier(prop, value.GetIdentifier().ident()))
+ return false;
+ if (value.GetLexicalUnitType() == Value::NUMBER &&
+ !IsValidNumber(prop, value, quirks_mode))
+ return false;
+ if (value.GetLexicalUnitType() == Value::RECT) {
+ const Values* params = value.GetParameters();
+ CHECK(params != NULL && params->size() == 4);
+ for (Values::const_iterator iter = params->begin();
+ iter < params->end(); ++iter) {
+ const Value* param = *iter;
+ if (param->GetLexicalUnitType() == Value::IDENT) {
+ if (!IsValidIdentifier(prop, param->GetIdentifier().ident()))
+ return false;
+ } else if (param->GetLexicalUnitType() == Value::NUMBER) {
+ if (!IsValidNumber(prop, *param, quirks_mode))
+ return false;
+ } else {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+bool ValueValidator::IsValidType(Property::Prop prop,
+ Value::ValueType type) const {
+ const PropertyValidationInfo* info = validation_info_[prop];
+ // If we don't have information about prop, then all values are accepted.
+ return info == NULL || ContainsKey(info->valid_types, type);
+}
+
+bool ValueValidator::IsValidIdentifier(Property::Prop prop,
+ Identifier::Ident ident) const {
+ const PropertyValidationInfo* info = validation_info_[prop];
+ // If we don't have information about prop, then all values are accepted.
+ return info == NULL || ContainsKey(info->valid_idents, ident);
+}
+
+bool ValueValidator::IsValidNumber(Property::Prop prop, const Value& value,
+ bool quirks_mode) const {
+ const PropertyValidationInfo* info = validation_info_[prop];
+ // If we don't have information about prop, then all values are accepted.
+ if (info == NULL) return true;
+
+ switch (value.GetDimension()) {
+ case Value::OTHER:
+ return false;
+ case Value::DEG:
+ case Value::RAD:
+ case Value::GRAD:
+ case Value::HZ:
+ case Value::KHZ:
+ case Value::MS:
+ case Value::S:
+ return false; // unless we handle aural properties
+ case Value::PERCENT:
+ if (!info->accept_percent)
+ return false;
+ break;
+ case Value::NO_UNIT:
+ // We accept no-unit numbers if any of
+ // 1) accepted by the property,
+ // 2) 0 is always accepted,
+ // 3) in quirks mode.
+ if (!info->accept_no_unit && !quirks_mode &&
+ value.GetFloatValue() != 0.0)
+ return false;
+ break;
+ default:
+ if (!info->accept_length)
+ return false;
+ break;
+ }
+ if (value.GetFloatValue() < 0 && !info->accept_negative)
+ return false;
+ return true;
+}
+} // namespace
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/valuevalidator.h b/trunk/src/third_party/css_parser/src/webutil/css/valuevalidator.h
new file mode 100644
index 0000000..93bd228
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/valuevalidator.h
@@ -0,0 +1,71 @@
+/**
+ * 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.
+ */
+
+// Copyright 2007 Google Inc. All Rights Reserved.
+// Author: yian@google.com (Yi-An Huang)
+
+#ifndef WEBUTIL_CSS_VALUEVALIDATOR_H__
+#define WEBUTIL_CSS_VALUEVALIDATOR_H__
+
+#include <vector>
+
+#include "testing/production_stub/public/gunit_prod.h"
+#include "util/gtl/singleton.h"
+#include "webutil/css/property.h"
+#include "webutil/css/value.h"
+#include "webutil/css/identifier.h"
+
+namespace Css {
+
+struct PropertyValidationInfo;
+
+class ValueValidator {
+ public:
+ static ValueValidator* Get() { return Singleton<ValueValidator>::get(); }
+
+ // Is value is a valid value for property prop?
+ bool IsValidValue(Property::Prop prop, const Value& value,
+ bool quirks_mode) const;
+
+ // TODO(sligocki): Chromium's Singleton<> is not playing well with this class
+ // and so we've had to make the constructor/destructor public. Look into this.
+ // Default Constructor; only used for the Singleton, default instance
+ ValueValidator();
+ ~ValueValidator();
+
+private:
+ // Is type is a valid type for property prop?
+ bool IsValidType(Property::Prop prop, Value::ValueType type) const;
+
+ // Is ident is a valid identifier for property prop?
+ bool IsValidIdentifier(Property::Prop prop, Identifier::Ident ident) const;
+
+ // Is number valid for property prop?
+ bool IsValidNumber(Property::Prop prop, const Value& value,
+ bool quirks_mode) const;
+
+ std::vector<PropertyValidationInfo*> validation_info_;
+ friend class Singleton<ValueValidator>;
+ FRIEND_TEST(ValueValidatorTest, types);
+ FRIEND_TEST(ValueValidatorTest, identifiers);
+ FRIEND_TEST(ValueValidatorTest, numbers);
+
+ DISALLOW_COPY_AND_ASSIGN(ValueValidator);
+};
+
+} // namespace
+
+#endif // WEBUTIL_CSS_VALUEVALIDATOR_H__
diff --git a/trunk/src/third_party/css_parser/src/webutil/css/valuevalidator_test.cc b/trunk/src/third_party/css_parser/src/webutil/css/valuevalidator_test.cc
new file mode 100644
index 0000000..595f0c8
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/css/valuevalidator_test.cc
@@ -0,0 +1,105 @@
+/**
+ * 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.
+ */
+
+// Copyright 2008 Google Inc. All Rights Reserved.
+// Author: yian@google.com (Yi-An Huang)
+
+#include "webutil/css/valuevalidator.h"
+
+#include <string>
+
+#include "testing/base/public/googletest.h"
+#include "testing/base/public/gunit.h"
+#include "webutil/css/string.h"
+
+namespace Css {
+
+class ValueValidatorTest : public testing::Test {
+ protected:
+ const ValueValidator& validator() {
+ return *ValueValidator::Get();
+ }
+};
+
+TEST_F(ValueValidatorTest, types) {
+ EXPECT_TRUE(validator().IsValidType(Property::COLOR, Value::IDENT));
+ EXPECT_TRUE(validator().IsValidType(Property::COLOR, Value::DEFAULT));
+ EXPECT_TRUE(validator().IsValidType(Property::COLOR, Value::UNKNOWN));
+ EXPECT_TRUE(validator().IsValidType(Property::COLOR, Value::COLOR));
+ EXPECT_FALSE(validator().IsValidType(Property::COLOR, Value::STRING));
+ EXPECT_FALSE(validator().IsValidType(Property::COLOR, Value::URI));
+ EXPECT_FALSE(validator().IsValidType(Property::COLOR, Value::FUNCTION));
+}
+
+TEST_F(ValueValidatorTest, identifiers) {
+ EXPECT_TRUE(validator().IsValidIdentifier(Property::COLOR,
+ Identifier::INHERIT));
+ EXPECT_FALSE(validator().IsValidIdentifier(Property::COLOR,
+ Identifier::OTHER));
+ // font-family take all identifiers.
+ EXPECT_TRUE(validator().IsValidIdentifier(Property::FONT_FAMILY,
+ Identifier::SERIF));
+ EXPECT_TRUE(validator().IsValidIdentifier(Property::FONT_FAMILY,
+ Identifier::OTHER));
+ // FIXME(yian): Is this right?
+ EXPECT_FALSE(validator().IsValidIdentifier(Property::FONT_FAMILY,
+ Identifier::NORMAL));
+}
+
+TEST_F(ValueValidatorTest, numbers) {
+ // misc units
+ EXPECT_FALSE(validator().IsValidNumber(
+ Property::HEIGHT, Value(0, UTF8ToUnicodeText(string("unit"), false)),
+ false));
+ EXPECT_FALSE(validator().IsValidNumber(
+ Property::HEIGHT, Value(0, Value::RAD), false));
+ // percent
+ EXPECT_TRUE(validator().IsValidNumber(
+ Property::HEIGHT, Value(0, Value::PERCENT), false));
+ EXPECT_FALSE(validator().IsValidNumber(
+ Property::Z_INDEX, Value(0, Value::PERCENT), false));
+ // no-unit
+ EXPECT_FALSE(validator().IsValidNumber(
+ Property::HEIGHT, Value(1, Value::NO_UNIT), false));
+ EXPECT_TRUE(validator().IsValidNumber(
+ Property::HEIGHT, Value(0, Value::NO_UNIT), false));
+ EXPECT_TRUE(validator().IsValidNumber(
+ Property::HEIGHT, Value(1, Value::NO_UNIT), true));
+ EXPECT_TRUE(validator().IsValidNumber(
+ Property::Z_INDEX, Value(1, Value::NO_UNIT), false));
+ // lengths
+ EXPECT_TRUE(validator().IsValidNumber(
+ Property::HEIGHT, Value(1, Value::PX), false));
+ EXPECT_FALSE(validator().IsValidNumber(
+ Property::Z_INDEX, Value(1, Value::PX), false));
+ // negative
+ EXPECT_TRUE(validator().IsValidNumber(
+ Property::BOTTOM, Value(-1, Value::PX), false));
+ EXPECT_FALSE(validator().IsValidNumber(
+ Property::HEIGHT, Value(-1, Value::PX), false));
+}
+
+TEST_F(ValueValidatorTest, combined) {
+ Identifier transparent_identifier(Identifier::TRANSPARENT);
+ Value transparent(transparent_identifier);
+ Value some_string(Value::STRING, UTF8ToUnicodeText(string("string"), false));
+ EXPECT_TRUE(validator().IsValidValue(Property::BACKGROUND_COLOR,
+ transparent, false));
+ EXPECT_FALSE(validator().IsValidValue(Property::COLOR, transparent, false));
+ EXPECT_FALSE(validator().IsValidValue(Property::COLOR, some_string, false));
+}
+
+} // namespace
diff --git a/trunk/src/third_party/css_parser/src/webutil/html/htmlcolor.cc b/trunk/src/third_party/css_parser/src/webutil/html/htmlcolor.cc
new file mode 100644
index 0000000..1d623ea
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/html/htmlcolor.cc
@@ -0,0 +1,1001 @@
+/**
+ * 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.
+ */
+
+// Copyright (C) 2000 and onwards Google, Inc.
+//
+//
+// .cc for the HtmlColor class
+
+#include "webutil/html/htmlcolor.h"
+
+#include <cmath>
+
+#include "base/stringprintf.h"
+#include "strings/ascii_ctype.h"
+#include "strings/escaping.h"
+#include "strings/strutil.h"
+
+typedef struct RgbValue {
+ unsigned char r_;
+ unsigned char g_;
+ unsigned char b_;
+} RgbValue;
+
+// color table
+// when making change to known_color_values, please
+// also change the GetKnownColorValue function because
+// entire table is hardcoded into the function for efficiency
+static const RgbValue known_color_values[] = {
+/* 49 aliceblue */{240, 248, 255},
+/* 50 antiquewhite */{250, 235, 215},
+/* 51 aqua */{ 0, 255, 255},
+/* 52 aquamarine */{127, 255, 212},
+/* 53 azure */{240, 255, 255},
+/* 54 beige */{245, 245, 220},
+/* 55 bisque */{255, 228, 196},
+/* 56 black */{ 0, 0, 0},
+/* 57 blanchedalmond */{255, 235, 205},
+/* 58 blue */{ 0, 0, 255},
+/* 59 blueviolet */{138, 43, 226},
+/* 60 brown */{165, 42, 42},
+/* 61 burlywood */{222, 184, 135},
+/* 62 cadetblue */{ 95, 158, 160},
+/* 63 chartreuse */{127, 255, 0},
+/* 64 chocolate */{210, 105, 30},
+/* 65 coral */{255, 127, 80},
+/* 66 cornflowerblue */{100, 149, 237},
+/* 67 cornsilk */{255, 248, 220},
+/* 68 crimson */{220, 20, 60},
+/* 69 cyan */{ 0, 255, 255},
+/* 70 darkblue */{ 0, 0, 139},
+/* 71 darkcyan */{ 0, 139, 139},
+/* 72 darkgoldenrod */{184, 134, 11},
+/* 73 darkgray */{169, 169, 169},
+/* 74 darkgreen */{ 0, 100, 0},
+/* 75 darkgrey */{169, 169, 169},
+/* 76 darkkhaki */{189, 183, 107},
+/* 77 darkmagenta */{139, 0, 139},
+/* 78 darkolivegreen */{ 85, 107, 47},
+/* 79 darkorange */{255, 140, 0},
+/* 80 darkorchid */{153, 50, 204},
+/* 81 darkred */{139, 0, 0},
+/* 82 darksalmon */{233, 150, 122},
+/* 83 darkseagreen */{143, 188, 143},
+/* 84 darkslateblue */{ 72, 61, 139},
+/* 85 darkslategray */{ 47, 79, 79},
+/* 86 darkslategrey */{ 47, 79, 79},
+/* 87 darkturquoise */{ 0, 206, 209},
+/* 88 darkviolet */{148, 0, 211},
+/* 89 deeppink */{255, 20, 147},
+/* 90 deepskyblue */{ 0, 191, 255},
+/* 91 dimgray */{105, 105, 105},
+/* 92 dimgrey */{105, 105, 105},
+/* 93 dodgerblue */{ 30, 144, 255},
+/* 94 firebrick */{178, 34, 34},
+/* 95 floralwhite */{255, 250, 240},
+/* 96 forestgreen */{ 34, 139, 34},
+/* 97 fuchsia */{255, 0, 255},
+/* 98 gainsboro */{220, 220, 220},
+/* 99 ghostwhite */{248, 248, 255},
+/*100 gold */{255, 215, 0},
+/*101 goldenrod */{218, 165, 32},
+/*102 gray */{128, 128, 128},
+/*103 green */{ 0, 128, 0},
+/*104 grey */{128, 128, 128},
+/*105 greenyellow */{173, 255, 47},
+/*106 honeydew */{240, 255, 240},
+/*107 hotpink */{255, 105, 180},
+/*108 indianred */{205, 92, 92},
+/*109 indigo */{ 75, 0, 130},
+/*110 ivory */{255, 255, 240},
+/*111 khaki */{240, 230, 140},
+/*112 lavender */{230, 230, 250},
+/*113 lavenderblush */{255, 240, 245},
+/*114 lawngreen */{124, 252, 0},
+/*115 lemonchiffon */{255, 250, 205},
+/*116 lightblue */{173, 216, 230},
+/*117 lightcoral */{240, 128, 128},
+/*118 lightcyan */{224, 255, 255},
+/*119 lightgoldenrodyellow */{250, 250, 210},
+/*120 lightgray */{211, 211, 211},
+/*121 lightgreen */{144, 238, 144},
+/*122 lightgrey */{211, 211, 211},
+/*123 lightpink */{255, 182, 193},
+/*124 lightsalmon */{255, 160, 122},
+/*125 lightseagreen */{ 32, 178, 170},
+/*126 lightskyblue */{135, 206, 250},
+/*127 lightslategray */{119, 136, 153},
+/*128 lightslategrey */{119, 136, 153},
+/*129 lightsteelblue */{176, 196, 222},
+/*130 lightyellow */{255, 255, 224},
+/*131 lime */{ 0, 255, 0},
+/*132 limegreen */{ 50, 205, 50},
+/*133 linen */{250, 240, 230},
+/*134 magenta */{255, 0, 255},
+/*135 maroon */{128, 0, 0},
+/*136 mediumaquamarine */{102, 205, 170},
+/*137 mediumblue */{ 0, 0, 205},
+/*138 mediumorchid */{186, 85, 211},
+/*139 mediumpurple */{147, 112, 219},
+/*140 mediumseagreen */{ 60, 179, 113},
+/*141 mediumslateblue */{123, 104, 238},
+/*142 mediumspringgreen */{ 0, 250, 154},
+/*143 mediumturquoise */{ 72, 209, 204},
+/*144 mediumvioletred */{199, 21, 133},
+/*145 midnightblue */{ 25, 25, 112},
+/*146 mintcream */{245, 255, 250},
+/*147 mistyrose */{255, 228, 225},
+/*148 moccasin */{255, 228, 181},
+/*149 navajowhite */{255, 222, 173},
+/*150 navy */{ 0, 0, 128},
+/*151 oldlace */{253, 245, 230},
+/*152 olive */{128, 128, 0},
+/*153 olivedrab */{107, 142, 35},
+/*154 orange */{255, 165, 0},
+/*155 orangered */{255, 69, 0},
+/*156 orchid */{218, 112, 214},
+/*157 palegoldenrod */{238, 232, 170},
+/*158 palegreen */{152, 251, 152},
+/*159 paleturquoise */{175, 238, 238},
+/*160 palevioletred */{219, 112, 147},
+/*161 papayawhip */{255, 239, 213},
+/*162 peachpuff */{255, 218, 185},
+/*163 peru */{205, 133, 63},
+/*164 pink */{255, 192, 203},
+/*165 plum */{221, 160, 221},
+/*166 powderblue */{176, 224, 230},
+/*167 purple */{128, 0, 128},
+/*168 red */{255, 0, 0},
+/*169 rosybrown */{188, 143, 143},
+/*170 royalblue */{ 65, 105, 225},
+/*171 saddlebrown */{139, 69, 19},
+/*172 salmon */{250, 128, 114},
+/*173 sandybrown */{244, 164, 96},
+/*174 seagreen */{ 46, 139, 87},
+/*175 seashell */{255, 245, 238},
+/*176 sienna */{160, 82, 45},
+/*177 silver */{192, 192, 192},
+/*178 skyblue */{135, 206, 235},
+/*179 slateblue */{106, 90, 205},
+/*180 slategray */{112, 128, 144},
+/*181 slategrey */{112, 128, 144},
+/*182 snow */{255, 250, 250},
+/*183 springgreen */{ 0, 255, 127},
+/*184 steelblue */{ 70, 130, 180},
+/*185 tan */{210, 180, 140},
+/*186 teal */{ 0, 128, 128},
+/*187 thistle */{216, 191, 216},
+/*188 tomato */{255, 99, 71},
+/*189 turquoise */{ 64, 224, 208},
+/*190 violet */{238, 130, 238},
+/*191 wheat */{245, 222, 179},
+/*192 white */{255, 255, 255},
+/*193 whitesmoke */{245, 245, 245},
+/*194 yellow */{255, 255, 0},
+/*195 yellowgreen */{154, 205, 50},
+};
+
+// here the entiry table is hardcoded into the function
+// mainly because of consideration of efficiency
+static const RgbValue * GetKnownColorValue(const char *colorstr) {
+ switch(ascii_tolower(colorstr[0])) {
+ case 'a':
+ switch(ascii_tolower(colorstr[1])) {
+ case 'l':
+ if (!strcasecmp("aliceblue", colorstr)) {
+ return &known_color_values[0];
+ }
+ return NULL;
+ case 'n':
+ if (!strcasecmp("antiquewhite", colorstr)) {
+ return &known_color_values[1];
+ }
+ return NULL;
+ case 'q':
+ if (!strcasecmp("aqua", colorstr)) {
+ return &known_color_values[2];
+ } else if (!strcasecmp("aquamarine", colorstr)) {
+ return &known_color_values[3];
+ }
+ return NULL;
+ case 'z':
+ if (!strcasecmp("azure", colorstr)) {
+ return &known_color_values[4];
+ }
+ return NULL;
+ }
+ return NULL;
+ case 'b':
+ switch(ascii_tolower(colorstr[1])) {
+ case 'e':
+ if (!strcasecmp("beige", colorstr)) {
+ return &known_color_values[5];
+ }
+ return NULL;
+ case 'i':
+ if (!strcasecmp("bisque", colorstr)) {
+ return &known_color_values[6];
+ }
+ return NULL;
+ case 'l':
+ if (!strcasecmp("black", colorstr)) {
+ return &known_color_values[7];
+ } else if (!strcasecmp("blanchedalmond", colorstr)) {
+ return &known_color_values[8];
+ } else if (!strcasecmp("blue", colorstr)) {
+ return &known_color_values[9];
+ } else if (!strcasecmp("blueviolet", colorstr)) {
+ return &known_color_values[10];
+ }
+ return NULL;
+ case 'r':
+ if (!strcasecmp("brown", colorstr)) {
+ return &known_color_values[11];
+ }
+ return NULL;
+ case 'u':
+ if (!strcasecmp("burlywood", colorstr)) {
+ return &known_color_values[12];
+ }
+ return NULL;
+ }
+ return NULL;
+ case 'c':
+ switch(ascii_tolower(colorstr[1])) {
+ case 'a':
+ if (!strcasecmp("cadetblue", colorstr)) {
+ return &known_color_values[13];
+ }
+ return NULL;
+ case 'h':
+ if (!strcasecmp("chartreuse", colorstr)) {
+ return &known_color_values[14];
+ } else if (!strcasecmp("chocolate", colorstr)) {
+ return &known_color_values[15];
+ }
+ return NULL;
+ case 'o':
+ if (!strcasecmp("coral", colorstr)) {
+ return &known_color_values[16];
+ } else if (!strcasecmp("cornflowerblue", colorstr)) {
+ return &known_color_values[17];
+ } else if (!strcasecmp("cornsilk", colorstr)) {
+ return &known_color_values[18];
+ }
+ return NULL;
+ case 'r':
+ if (!strcasecmp("crimson", colorstr)) {
+ return &known_color_values[19];
+ }
+ return NULL;
+ case 'y':
+ if (!strcasecmp("cyan", colorstr)) {
+ return &known_color_values[20];
+ }
+ return NULL;
+ }
+
+ return NULL;
+ case 'd':
+ switch(ascii_tolower(colorstr[1])) {
+ case 'a':
+ if (!strcasecmp("darkblue", colorstr)) {
+ return &known_color_values[21];
+ } else if (!strcasecmp("darkcyan", colorstr)) {
+ return &known_color_values[22];
+ } else if (!strcasecmp("darkgoldenrod", colorstr)) {
+ return &known_color_values[23];
+ } else if (!strcasecmp("darkgray", colorstr)) {
+ return &known_color_values[24];
+ } else if (!strcasecmp("darkgreen", colorstr)) {
+ return &known_color_values[25];
+ } else if (!strcasecmp("darkgrey", colorstr)) {
+ return &known_color_values[26];
+ } else if (!strcasecmp("darkkhaki", colorstr)) {
+ return &known_color_values[27];
+ } else if (!strcasecmp("darkmagenta", colorstr)) {
+ return &known_color_values[28];
+ } else if (!strcasecmp("darkolivegreen", colorstr)) {
+ return &known_color_values[29];
+ } else if (!strcasecmp("darkorange", colorstr)) {
+ return &known_color_values[30];
+ } else if (!strcasecmp("darkorchid", colorstr)) {
+ return &known_color_values[31];
+ } else if (!strcasecmp("darkred", colorstr)) {
+ return &known_color_values[32];
+ } else if (!strcasecmp("darksalmon", colorstr)) {
+ return &known_color_values[33];
+ } else if (!strcasecmp("darkseagreen", colorstr)) {
+ return &known_color_values[34];
+ } else if (!strcasecmp("darkslateblue", colorstr)) {
+ return &known_color_values[35];
+ } else if (!strcasecmp("darkslategray", colorstr)) {
+ return &known_color_values[36];
+ } else if (!strcasecmp("darkslategrey", colorstr)) {
+ return &known_color_values[37];
+ } else if (!strcasecmp("darkturquoise", colorstr)) {
+ return &known_color_values[38];
+ } else if (!strcasecmp("darkviolet", colorstr)) {
+ return &known_color_values[39];
+ }
+ return NULL;
+ case 'e':
+ if (!strcasecmp("deeppink", colorstr)) {
+ return &known_color_values[40];
+ } else if (!strcasecmp("deepskyblue", colorstr)) {
+ return &known_color_values[41];
+ }
+ return NULL;
+ case 'i':
+ if (!strcasecmp("dimgray", colorstr)) {
+ return &known_color_values[42];
+ } else if (!strcasecmp("dimgrey", colorstr)) {
+ return &known_color_values[43];
+ }
+ return NULL;
+ case 'o':
+ if (!strcasecmp("dodgerblue", colorstr)) {
+ return &known_color_values[44];
+ }
+ return NULL;
+ }
+ return NULL;
+ case 'f':
+ switch(ascii_tolower(colorstr[1])) {
+ case 'i':
+ if (!strcasecmp("firebrick", colorstr)) {
+ return &known_color_values[45];
+ }
+ return NULL;
+ case 'l':
+ if (!strcasecmp("floralwhite", colorstr)) {
+ return &known_color_values[46];
+ }
+ return NULL;
+ case 'o':
+ if (!strcasecmp("forestgreen", colorstr)) {
+ return &known_color_values[47];
+ }
+ return NULL;
+ case 'u':
+ if (!strcasecmp("fuchsia", colorstr)) {
+ return &known_color_values[48];
+ }
+ return NULL;
+ }
+ return NULL;
+ case 'g':
+ switch(ascii_tolower(colorstr[1])) {
+ case 'a':
+ if (!strcasecmp("gainsboro", colorstr)) {
+ return &known_color_values[49];
+ }
+ return NULL;
+ case 'h':
+ if (!strcasecmp("ghostwhite", colorstr)) {
+ return &known_color_values[50];
+ }
+ return NULL;
+ case 'o':
+ if (!strcasecmp("gold", colorstr)) {
+ return &known_color_values[51];
+ } else if (!strcasecmp("goldenrod", colorstr)) {
+ return &known_color_values[52];
+ }
+ return NULL;
+ case 'r':
+ if (!strcasecmp("gray", colorstr)) {
+ return &known_color_values[53];
+ } else if (!strcasecmp("green", colorstr)) {
+ return &known_color_values[54];
+ } else if (!strcasecmp("grey", colorstr)) {
+ return &known_color_values[55];
+ } else if (!strcasecmp("greenyellow", colorstr)) {
+ return &known_color_values[56];
+ }
+ return NULL;
+ }
+ return NULL;
+ case 'h':
+ if (!strcasecmp("honeydew", colorstr)) {
+ return &known_color_values[57];
+ } else if (!strcasecmp("hotpink", colorstr)) {
+ return &known_color_values[58];
+ }
+ return NULL;
+ case 'i':
+ if (!strcasecmp("indianred", colorstr)) {
+ return &known_color_values[59];
+ } else if (!strcasecmp("indigo", colorstr)) {
+ return &known_color_values[60];
+ } else if (!strcasecmp("ivory", colorstr)) {
+ return &known_color_values[61];
+ }
+ return NULL;
+ case 'k':
+ if (!strcasecmp("khaki", colorstr)) {
+ return &known_color_values[62];
+ }
+ return NULL;
+ case 'l':
+ switch(ascii_tolower(colorstr[1])) {
+ case 'a':
+ if (!strcasecmp("lavender", colorstr)) {
+ return &known_color_values[63];
+ } else if (!strcasecmp("lavenderblush", colorstr)) {
+ return &known_color_values[64];
+ } else if (!strcasecmp("lawngreen", colorstr)) {
+ return &known_color_values[65];
+ }
+ return NULL;
+ case 'e':
+ if (!strcasecmp("lemonchiffon", colorstr)) {
+ return &known_color_values[66];
+ }
+ return NULL;
+ case 'i':
+ if (!strcasecmp("lightblue", colorstr)) {
+ return &known_color_values[67];
+ } else if (!strcasecmp("lightcoral", colorstr)) {
+ return &known_color_values[68];
+ } else if (!strcasecmp("lightcyan", colorstr)) {
+ return &known_color_values[69];
+ } else if (!strcasecmp("lightgoldenrodyellow", colorstr)) {
+ return &known_color_values[70];
+ } else if (!strcasecmp("lightgray", colorstr)) {
+ return &known_color_values[71];
+ } else if (!strcasecmp("lightgreen", colorstr)) {
+ return &known_color_values[72];
+ } else if (!strcasecmp("lightgrey", colorstr)) {
+ return &known_color_values[73];
+ } else if (!strcasecmp("lightpink", colorstr)) {
+ return &known_color_values[74];
+ } else if (!strcasecmp("lightsalmon", colorstr)) {
+ return &known_color_values[75];
+ } else if (!strcasecmp("lightseagreen", colorstr)) {
+ return &known_color_values[76];
+ } else if (!strcasecmp("lightskyblue", colorstr)) {
+ return &known_color_values[77];
+ } else if (!strcasecmp("lightslategray", colorstr)) {
+ return &known_color_values[78];
+ } else if (!strcasecmp("lightslategrey", colorstr)) {
+ return &known_color_values[79];
+ } else if (!strcasecmp("lightsteelblue", colorstr)) {
+ return &known_color_values[80];
+ } else if (!strcasecmp("lightyellow", colorstr)) {
+ return &known_color_values[81];
+ } else if (!strcasecmp("lime", colorstr)) {
+ return &known_color_values[82];
+ } else if (!strcasecmp("limegreen", colorstr)) {
+ return &known_color_values[83];
+ } else if (!strcasecmp("linen", colorstr)) {
+ return &known_color_values[84];
+ }
+ return NULL;
+ }
+ return NULL;
+ case 'm':
+ switch(ascii_tolower(colorstr[1])) {
+ case 'a':
+ if (!strcasecmp("magenta", colorstr)) {
+ return &known_color_values[85];
+ } else if (!strcasecmp("maroon", colorstr)) {
+ return &known_color_values[86];
+ }
+ return NULL;
+ case 'e':
+ if (!strcasecmp("mediumaquamarine", colorstr)) {
+ return &known_color_values[87];
+ } else if (!strcasecmp("mediumblue", colorstr)) {
+ return &known_color_values[88];
+ } else if (!strcasecmp("mediumorchid", colorstr)) {
+ return &known_color_values[89];
+ } else if (!strcasecmp("mediumpurple", colorstr)) {
+ return &known_color_values[90];
+ } else if (!strcasecmp("mediumseagreen", colorstr)) {
+ return &known_color_values[91];
+ } else if (!strcasecmp("mediumslateblue", colorstr)) {
+ return &known_color_values[92];
+ } else if (!strcasecmp("mediumspringgreen", colorstr)) {
+ return &known_color_values[93];
+ } else if (!strcasecmp("mediumturquoise", colorstr)) {
+ return &known_color_values[94];
+ } else if (!strcasecmp("mediumvioletred", colorstr)) {
+ return &known_color_values[95];
+ }
+ return NULL;
+ case 'i':
+ if (!strcasecmp("midnightblue", colorstr)) {
+ return &known_color_values[96];
+ } else if (!strcasecmp("mintcream", colorstr)) {
+ return &known_color_values[97];
+ } else if (!strcasecmp("mistyrose", colorstr)) {
+ return &known_color_values[98];
+ }
+ return NULL;
+ case 'o':
+ if (!strcasecmp("moccasin", colorstr)) {
+ return &known_color_values[99];
+ }
+ return NULL;
+ }
+ return NULL;
+ case 'n':
+ if (!strcasecmp("navajowhite", colorstr)) {
+ return &known_color_values[100];
+ } else if (!strcasecmp("navy", colorstr)) {
+ return &known_color_values[101];
+ }
+ return NULL;
+ case 'o':
+ switch(ascii_tolower(colorstr[1])) {
+ case 'l':
+ if (!strcasecmp("oldlace", colorstr)) {
+ return &known_color_values[102];
+ } else if (!strcasecmp("olive", colorstr)) {
+ return &known_color_values[103];
+ } else if (!strcasecmp("olivedrab", colorstr)) {
+ return &known_color_values[104];
+ }
+ return NULL;
+ case 'r':
+ if (!strcasecmp("orange", colorstr)) {
+ return &known_color_values[105];
+ } else if (!strcasecmp("orangered", colorstr)) {
+ return &known_color_values[106];
+ } else if (!strcasecmp("orchid", colorstr)) {
+ return &known_color_values[107];
+ }
+ return NULL;
+ }
+ return NULL;
+ case 'p':
+ switch(ascii_tolower(colorstr[1])) {
+ case 'a':
+ if (!strcasecmp("palegoldenrod", colorstr)) {
+ return &known_color_values[108];
+ } else if (!strcasecmp("palegreen", colorstr)) {
+ return &known_color_values[109];
+ } else if (!strcasecmp("paleturquoise", colorstr)) {
+ return &known_color_values[110];
+ } else if (!strcasecmp("palevioletred", colorstr)) {
+ return &known_color_values[111];
+ } else if (!strcasecmp("papayawhip", colorstr)) {
+ return &known_color_values[112];
+ }
+ return NULL;
+ case 'e':
+ if (!strcasecmp("peachpuff", colorstr)) {
+ return &known_color_values[113];
+ } else if (!strcasecmp("peru", colorstr)) {
+ return &known_color_values[114];
+ }
+ return NULL;
+ case 'i':
+ if (!strcasecmp("pink", colorstr)) {
+ return &known_color_values[115];
+ }
+ return NULL;
+ case 'l':
+ if (!strcasecmp("plum", colorstr)) {
+ return &known_color_values[116];
+ }
+ return NULL;
+ case 'o':
+ if (!strcasecmp("powderblue", colorstr)) {
+ return &known_color_values[117];
+ }
+ return NULL;
+ case 'u':
+ if (!strcasecmp("purple", colorstr)) {
+ return &known_color_values[118];
+ }
+ return NULL;
+ }
+ return NULL;
+ case 'r':
+ if (!strcasecmp("red", colorstr)) {
+ return &known_color_values[119];
+ } else if (!strcasecmp("rosybrown", colorstr)) {
+ return &known_color_values[120];
+ } else if (!strcasecmp("royalblue", colorstr)) {
+ return &known_color_values[121];
+ }
+ return NULL;
+ case 's':
+ switch(ascii_tolower(colorstr[1])) {
+ case 'a':
+ if (!strcasecmp("saddlebrown", colorstr)) {
+ return &known_color_values[122];
+ } else if (!strcasecmp("salmon", colorstr)) {
+ return &known_color_values[123];
+ } else if (!strcasecmp("sandybrown", colorstr)) {
+ return &known_color_values[124];
+ }
+ return NULL;
+ case 'e':
+ if (!strcasecmp("seagreen", colorstr)) {
+ return &known_color_values[125];
+ } else if (!strcasecmp("seashell", colorstr)) {
+ return &known_color_values[126];
+ }
+ return NULL;
+ case 'i':
+ if (!strcasecmp("sienna", colorstr)) {
+ return &known_color_values[127];
+ } else if (!strcasecmp("silver", colorstr)) {
+ return &known_color_values[128];
+ }
+ return NULL;
+ case 'k':
+ if (!strcasecmp("skyblue", colorstr)) {
+ return &known_color_values[129];
+ }
+ return NULL;
+ case 'l':
+ if (!strcasecmp("slateblue", colorstr)) {
+ return &known_color_values[130];
+ } else if (!strcasecmp("slategray", colorstr)) {
+ return &known_color_values[131];
+ } else if (!strcasecmp("slategrey", colorstr)) {
+ return &known_color_values[132];
+ }
+ return NULL;
+ case 'n':
+ if (!strcasecmp("snow", colorstr)) {
+ return &known_color_values[133];
+ }
+ return NULL;
+ case 'p':
+ if (!strcasecmp("springgreen", colorstr)) {
+ return &known_color_values[134];
+ }
+ return NULL;
+ case 't':
+ if (!strcasecmp("steelblue", colorstr)) {
+ return &known_color_values[135];
+ }
+ return NULL;
+ }
+ return NULL;
+ case 't':
+ switch(ascii_tolower(colorstr[1])) {
+ case 'a':
+ if (!strcasecmp("tan", colorstr)) {
+ return &known_color_values[136];
+ }
+ return NULL;
+ case 'e':
+ if (!strcasecmp("teal", colorstr)) {
+ return &known_color_values[137];
+ }
+ return NULL;
+ case 'h':
+ if (!strcasecmp("thistle", colorstr)) {
+ return &known_color_values[138];
+ }
+ return NULL;
+ case 'o':
+ if (!strcasecmp("tomato", colorstr)) {
+ return &known_color_values[139];
+ }
+ return NULL;
+ case 'u':
+ if (!strcasecmp("turquoise", colorstr)) {
+ return &known_color_values[140];
+ }
+ return NULL;
+ }
+ return NULL;
+ case 'v':
+ if (!strcasecmp("violet", colorstr)) {
+ return &known_color_values[141];
+ }
+ return NULL;
+ case 'w':
+ if (!strcasecmp("wheat", colorstr)) {
+ return &known_color_values[142];
+ } else if (!strcasecmp("white", colorstr)) {
+ return &known_color_values[143];
+ } else if (!strcasecmp("whitesmoke", colorstr)) {
+ return &known_color_values[144];
+ }
+ return NULL;
+ case 'y':
+ if (!strcasecmp("yellow", colorstr)) {
+ return &known_color_values[145];
+ } else if (!strcasecmp("yellowgreen", colorstr)) {
+ return &known_color_values[146];
+ }
+ return NULL;
+ };
+
+ return NULL;
+}
+
+const unsigned char HtmlColor::kGoodColorValue;
+const unsigned char HtmlColor::kBadColorName;
+const unsigned char HtmlColor::kBadColorHex;
+
+static inline int TwoXDigitsToNum(const char *xstr) {
+ return (hex_digit_to_int(xstr[0])*16 + hex_digit_to_int(xstr[1]));
+}
+
+void HtmlColor::SetValueFromHexStr(const char *hexstr) {
+ int hexstr_len = strlen(hexstr);
+ const char* finalstr = hexstr;
+ char hexbuf[7];
+
+ if (hexstr_len == 3) {
+ for (int i = 0; i < 3; i++) {
+ if (!ascii_isxdigit(hexstr[i])) {
+ SetBadHexValue();
+ return;
+ }
+ hexbuf[2 * i] = hexstr[i];
+ hexbuf[2 * i + 1] = hexstr[i];
+ }
+ hexbuf[6] = '\0';
+ finalstr = hexbuf;
+ } else if (hexstr_len == 6) {
+ for (int i = 0; i < 6; i++) {
+ if (!ascii_isxdigit(hexstr[i])) {
+ SetBadHexValue();
+ return;
+ }
+ }
+ } else {
+ SetBadHexValue();
+ return;
+ }
+
+ r_ = static_cast<unsigned char>(TwoXDigitsToNum(finalstr));
+ g_ = static_cast<unsigned char>(TwoXDigitsToNum(finalstr + 2));
+ b_ = static_cast<unsigned char>(TwoXDigitsToNum(finalstr + 4));
+ is_bad_value_ = kGoodColorValue;
+}
+
+HtmlColor::HtmlColor(const string& str) {
+ SetValueFromStr(str.c_str());
+}
+
+HtmlColor::HtmlColor(const char *str, int colorstrlen) {
+ string tmp(str,colorstrlen);
+ SetValueFromStr(tmp.c_str());
+}
+
+void HtmlColor::SetValueFromStr(const char* colorstr) {
+ if(colorstr[0]=='#') { // rgb value
+ SetValueFromHexStr(colorstr + 1);
+ } else {
+ SetValueFromName(colorstr);
+ if (!IsDefined() && strlen(colorstr) == 6) {
+ SetValueFromHexStr(colorstr);
+ if (!IsDefined())
+ SetBadNameValue();
+ }
+ }
+}
+
+void HtmlColor::SetValueFromName(const char* str) {
+ const RgbValue *p_rgb = GetKnownColorValue(str);
+ if (p_rgb) {
+ r_ = p_rgb->r_;
+ g_ = p_rgb->g_;
+ b_ = p_rgb->b_;
+ is_bad_value_ = kGoodColorValue;
+ } else {
+ SetBadNameValue();
+ }
+}
+
+void HtmlColor::SetValueFromRGB(unsigned char r, unsigned char g,
+ unsigned char b) {
+ r_ = r;
+ g_ = g;
+ b_ = b;
+ is_bad_value_ = kGoodColorValue;
+}
+
+HtmlColor::HtmlColor(unsigned char r, unsigned char g, unsigned char b) {
+ SetValueFromRGB(r, g, b);
+}
+
+static const float kLumR = 0.30;
+static const float kLumG = 0.59;
+static const float kLumB = 0.11;
+
+// Converts from RGB to HSL. This is an algorithm derived from Fundamentals of
+// Interactive Computer Graphics by Foley and van Dam (1982). A (slightly)
+// modified formula can be found at
+// http://en.wikipedia.org/wiki/HSL_color_space
+// We adopt the notation that HSL values are expressed in the range of 0 to 1.
+// Specifically, H is in [0, 1), while S and L are in [0, 1].
+// (Another popular scheme normalizes HSL to [0, 360), [0, 100], and [0, 100],
+// respectively).
+static void RGBtoHSL(const HtmlColor& rgb, double* h, double* s, double* l) {
+ int r = rgb.r();
+ int g = rgb.g();
+ int b = rgb.b();
+ int max_v = (r < g) ? ((g < b) ? b : g) : ((b < r) ? r : b);
+ int min_v = (r < g) ? ((r < b) ? r : b) : ((b < g) ? b : g);
+ int sum = max_v + min_v;
+ double delta = static_cast<double>(max_v - min_v);
+
+ double dR = (max_v - r) / delta;
+ double dG = (max_v - g) / delta;
+ double dB = (max_v - b) / delta;
+
+ if (min_v == max_v)
+ *h = 0.0;
+ else if (r == max_v)
+ *h = (dB - dG) / 6.0;
+ else if (g == max_v)
+ *h = (2.0 + dR - dB) / 6.0;
+ else
+ *h = (4.0 + dG - dR) / 6.0;
+
+ if (*h < 0.0)
+ *h += 1.0;
+ if (*h >= 1.0)
+ *h -= 1.0;
+
+ *l = 0.5 * sum / 255.0;
+
+ if (max_v == 0 || min_v == 255)
+ *s = 0.0;
+ else if (sum <= 255)
+ *s = delta / sum;
+ else
+ *s = delta / (2 * 255 - sum);
+}
+
+// Calculates the Euclidean distance between two color vectors on a HSL sphere.
+// A demo of the sphere can also be found at:
+// http://en.wikipedia.org/wiki/HSL_color_space
+// In short, a vector for color (H, S, L) in this system can be expressed as
+// (S*L'*cos(2*PI*H), S*L'*sin(2*PI*H), L), where L' = abs(L - 0.5).
+// And we simply calculate the l-2 distance using these coordinates.
+static double HSLDistance(double h1, double s1, double l1, double h2,
+ double s2, double l2) {
+ double sl1, sl2;
+ if (l1 <= 0.5)
+ sl1 = s1 * l1;
+ else
+ sl1 = s1 * (1.0 - l1);
+
+ if (l2 <= 0.5)
+ sl2 = s2 * l2;
+ else
+ sl2 = s2 * (1.0 - l2);
+
+ double dH = (h1 - h2) * 2.0 * M_PI;
+ return (l1 - l2) * (l1 - l2) +
+ sl1 * sl1 + sl2 * sl2 - 2 * sl1 * sl2 * cos(dH);
+}
+
+bool HtmlColor::IsSimilarInHSL(const HtmlColor &color, double level) const {
+ double h1, s1, l1, h2, s2, l2;
+ RGBtoHSL(*this, &h1, &s1, &l1);
+ RGBtoHSL(color, &h2, &s2, &l2);
+ return HSLDistance(h1, s1, l1, h2, s2, l2) <= level;
+}
+
+// Calculate the luminance of the color
+// Luminance is an integer value from 0 - 255, which
+// represents the gray level that most closely corresponds
+// to the perceived brightness of the color, based on
+// the way the human eye sees color. The weights in
+// the function below are pretty standard. See
+// http://www.google.com/search?q=rgb+luminance+formula
+int HtmlColor::Luminance() const {
+ if (!IsDefined())
+ return 0;
+
+ float luminance = (kLumR * static_cast<float>(r_)) +
+ (kLumG * static_cast<float>(g_)) +
+ (kLumB * static_cast<float>(b_));
+ return static_cast<int>(luminance);
+}
+
+void HtmlColor::Lighten(float factor) {
+ HtmlColor white("ffffff", 6);
+ BlendWithColor(1.0-factor, white);
+}
+
+void HtmlColor::Darken(float factor) {
+ HtmlColor black("000000", 6);
+ BlendWithColor(1.0-factor, black);
+}
+
+void HtmlColor::Desaturate(float factor) {
+ if (!IsDefined() || factor < 0 || factor > 1)
+ return;
+
+ unsigned char lum = static_cast<unsigned char>(Luminance());
+ HtmlColor gray(lum, lum, lum);
+ BlendWithColor(1.0-factor, gray);
+}
+
+void HtmlColor::BlendWithColor(float factor, const HtmlColor& c) {
+ if (!IsDefined() || factor < 0 || factor > 1)
+ return;
+
+ r_ = static_cast<unsigned char>(factor * r_ + (1-factor) * c.r_);
+ g_ = static_cast<unsigned char>(factor * g_ + (1-factor) * c.g_);
+ b_ = static_cast<unsigned char>(factor * b_ + (1-factor) * c.b_);
+}
+
+// Return the color as a string for use in HTML
+// This isn't the most efficient function. If you're using it a lot,
+// you might want to rewrite it.
+string HtmlColor::ToString() const {
+ return StringPrintf("#%02x%02x%02x", r_, g_, b_);
+}
+
+
+//
+// == HtmlColorUtils ==
+//
+string HtmlColorUtils::MaybeConvertToCssShorthand(const char* orig_color) {
+ HtmlColor color(orig_color);
+ if ( !color.IsDefined() )
+ return orig_color;
+
+ string shorthand = MaybeConvertToCssShorthand(color);
+ if (shorthand.size() < strlen(orig_color)) {
+ return shorthand;
+ } else {
+ return orig_color;
+ }
+}
+
+string HtmlColorUtils::MaybeConvertToCssShorthand(const HtmlColor& color) {
+ // There are 16 color names which are supported by all known CSS compliant
+ // browsers. Of these 16, 9 are shorter than their hex equivalents. For
+ // this reason, we prefer to use the shorter color names in order to save
+ // bytes.
+ switch (color.rgb()) {
+ case 0x000080:
+ return "navy";
+ case 0x008000:
+ return "green";
+ case 0x008080:
+ return "teal";
+ case 0x800000:
+ return "maroon";
+ case 0x800080:
+ return "purple";
+ case 0x808000:
+ return "olive";
+ case 0x808080:
+ return "gray";
+ case 0xC0C0C0:
+ return "silver";
+ case 0xFF0000:
+ return "red";
+ }
+
+ if ( (color.r() >> 4) != (color.r() & 0xF) ||
+ (color.g() >> 4) != (color.g() & 0xF) ||
+ (color.b() >> 4) != (color.b() & 0xF) )
+ return color.ToString();
+
+ return StringPrintf("#%01x%01x%01x",
+ color.r() & 0xF,
+ color.g() & 0xF,
+ color.b() & 0xF);
+}
diff --git a/trunk/src/third_party/css_parser/src/webutil/html/htmlcolor.h b/trunk/src/third_party/css_parser/src/webutil/html/htmlcolor.h
new file mode 100644
index 0000000..46c0885
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/html/htmlcolor.h
@@ -0,0 +1,155 @@
+/**
+ * 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.
+ */
+
+// Copyright (C) 2000 and onwards Google, Inc.
+//
+//
+// .h for the HtmlColor class
+// HtmlColor provides 'IsSimilar' for comparing HTML colors
+// of different representations ( "#xxxxxx" or color names such as "white").
+// Before doing comparison, check 'IsDefined' first, it's because
+// that not all given HTML color strings are valid.
+
+#ifndef _HTML_COLOR_H_
+#define _HTML_COLOR_H_
+
+#include <string>
+#include "string_using.h"
+#include <stdlib.h>
+
+class HtmlColor {
+ private:
+ unsigned char r_;
+ unsigned char g_;
+ unsigned char b_;
+
+ // a colorstr is well defined if it is "#xxxxxx" ('x' is a hexdigit)
+ // or it's a known color name such as "black".
+ // 0: the RGB value is good!
+ // 1: bad (name) value, caused by bad color name
+ // 2: bad (hex) value, caused by bad hex string value
+ // -- with the browser (Netscape Communicator 4.75, linux-2.2.14,
+ // it shows that the color displayed is sometimes 'black' under case '2'.
+ unsigned char is_bad_value_;
+ static const unsigned char kGoodColorValue = 0x00;
+ static const unsigned char kBadColorName = 0x01;
+ static const unsigned char kBadColorHex = 0x02;
+
+ void SetBadNameValue() {
+ r_ = g_ = b_ = 0x00;
+ is_bad_value_ = kBadColorName;
+ }
+ void SetBadHexValue() {
+ r_ = g_ = b_ = 0x00;
+ is_bad_value_ = kBadColorHex;
+ }
+ void SetDefaultValue() {
+ r_ = g_ = b_ = 0x00;
+ is_bad_value_ = kGoodColorValue;
+ }
+
+ public:
+ enum TolerateLevel { EXACTLY_SAME=0,
+ HIGHLY_SIMILAR=5,
+ SIMILAR=10 };
+
+ // These methods also accept a CSS shorthand string "#xyz" for convenience.
+ // "#xyz" is expanded to "#xxyyzz" before processing.
+ explicit HtmlColor(const string& colorstr);
+ explicit HtmlColor(const char *colorstr, int colorstrlen);
+ explicit HtmlColor(unsigned char r, unsigned char g, unsigned char b);
+
+ bool IsDefined() const { return (is_bad_value_==0); }
+
+ bool IsSimilar(const HtmlColor &color, int level) const {
+ if (!IsDefined() || !color.IsDefined())
+ return false;
+
+ if ( (abs(static_cast<int>(r_) - static_cast<int>(color.r_)) <= level) &&
+ (abs(static_cast<int>(g_) - static_cast<int>(color.g_)) <= level) &&
+ (abs(static_cast<int>(b_) - static_cast<int>(color.b_)) <= level)
+ )
+ return true;
+ return false;
+ }
+
+ // Compares color similarity in HSL (Hue, Saturation, Lightness) space.
+ // This is assumed to be more accurate based on human perception.
+ // Note the difference level is a float number and it may vary from 0.0 to
+ // 1.0, inclusive. A suggested value for level is 0.02.
+ // WARNING: this is more expensive than IsSimilar() as it involves float
+ // arithmetic and cosine operations.
+ // TODO(yian): may need to disintegrate it into a separate HSL class.
+ bool IsSimilarInHSL(const HtmlColor &color, double level) const;
+
+ // return the luminance (0-255) of the color.
+ // this corresponds to a human's perception of the color's brightness
+ int Luminance() const;
+
+ // Lighten or darken the color by a given factor (between 0 and 1)
+ // Lightening with factor 1.0 => white
+ // Darkening with factor 1.0 => black
+ void Lighten(float factor);
+ void Darken(float factor);
+
+ // Desaturate the color (0.0 = no change, 1.0 = equivalent shade of gray)
+ void Desaturate(float factor);
+
+ // Blend the color with a second color by a certain factor between 0 and 1
+ // 1.0 => original color
+ // 0.0 => other color
+ void BlendWithColor(float factor, const HtmlColor& c);
+
+ string ToString() const;
+
+ // hexstr is in form of "xxxxxx"
+ void SetValueFromHexStr(const char *hexstr);
+
+ // either a color name or a hex string "#xxxxxx"
+ // This method also accepts a CSS shorthand string "#xyz" for convenience.
+ // "#xyz" is expanded to "#xxyyzz" before processing.
+ void SetValueFromStr(const char* str);
+
+ // Set the html color object from rgb values
+ void SetValueFromRGB(unsigned char r, unsigned char g,
+ unsigned char b);
+
+ // must be a color name. It can be one of 147 colors defined in CSS3 color
+ // module or SVG 1.0, which is supported by all major browsers. A reference
+ // can be found at:
+ // http://www.w3.org/TR/css3-color/#svg-color
+ void SetValueFromName(const char* str);
+
+ int r() const { return static_cast<int>(r_); }
+ int g() const { return static_cast<int>(g_); }
+ int b() const { return static_cast<int>(b_); }
+ int rgb() const { return b() + (g() << 8) + (r() << 16); }
+};
+
+class HtmlColorUtils {
+ public:
+
+ // Converts a color into its shortest possible CSS representation.
+ // For 9 colors, that is their color name. Example: "#008000" returns "green".
+ // For colors in the form #rrggbb, where r=r, g=g, and b=b, that is #rgb.
+ // Example: "#aabbcc" returns "#abc".
+ // For all other colors, the six hex-digit representation is shortest.
+ // Example: "lightgoldenrodyellow" returns "#FAFAD2"
+ static string MaybeConvertToCssShorthand(const HtmlColor& color);
+ static string MaybeConvertToCssShorthand(const char* orig);
+};
+
+#endif // _HTML_COLOR_H_
diff --git a/trunk/src/third_party/css_parser/src/webutil/html/htmltagenum.cc b/trunk/src/third_party/css_parser/src/webutil/html/htmltagenum.cc
new file mode 100644
index 0000000..0348545
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/html/htmltagenum.cc
@@ -0,0 +1,82 @@
+/**
+ * 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.
+ */
+
+// Copyright 2006 Google Inc. All Rights Reserved.
+// Author: dpeng@google.com (Daniel Peng)
+
+#include "base/basictypes.h"
+#include "webutil/html/htmltagenum.h"
+#include "strings/stringprintf.h"
+
+const char* HtmlTagEnumNames[] =
+{ "Unknown",
+ "A", "Abbr", "Acronym", "Address", "Applet",
+ "Area", "B", "Base", "Basefont", "Bdo",
+ "Big", "Blockquote", "Body", "Br", "Button",
+ "Caption", "Center", "Cite", "Code", "Col",
+ "Colgroup", "Dd", "Del", "Dfn", "Dir",
+ "Div", "Dl", "Dt", "Em", "Fieldset",
+ "Font", "Form", "Frame", "Frameset", "H1",
+ "H2", "H3", "H4", "H5", "H6", "Head",
+ "Hr", "Html", "I", "Iframe", "Img",
+ "Input", "Ins", "Isindex", "Kbd", "Label",
+ "Legend", "Li", "Link", "Map", "Menu",
+ "Meta", "Noframes", "Noscript", "Object",
+ "Ol", "Optgroup", "Option", "P", "Param",
+ "Pre", "Q", "S", "Samp", "Script",
+ "Select", "Small", "Span", "Strike",
+ "Strong", "Style", "Sub", "Sup", "Table",
+ "Tbody", "Td", "Textarea", "Tfoot", "Th",
+ "Thead", "Title", "Tr", "Tt",
+ "U", "Ul", "Var",
+ // Empty tag
+ "ZeroLength",
+ // Used in repository/lexer/html_lexer.cc
+ "!--", "Blink",
+ // Used in repository/parsers/base/handler-parser.cc
+ "Embed", "Marquee",
+ // Legacy backwards-compatible tags mentioned in HTML5.
+ "Nobr", "Wbr", "Bgsound", "Image",
+ "Listing", "Noembed", "Plaintext", "Spacer",
+ "Xmp",
+ // From Netscape Navigator 4.0
+ "Ilayer", "Keygen", "Layer", "Multicol", "Nolayer", "Server",
+ // !doctype
+ "!Doctype",
+ // Legacy tag used mostly by Russian sites.
+ "Noindex",
+ // Old style comments,
+ "!Comment",
+};
+
+COMPILE_ASSERT(arraysize(HtmlTagEnumNames) == kHtmlTagBuiltinMax,
+ ForgotToAddTagToHtmlTagEnumNames);
+
+const char* HtmlTagName(HtmlTagEnum tag) {
+ if (tag < kHtmlTagBuiltinMax) {
+ return HtmlTagEnumNames[tag];
+ } else {
+ return NULL;
+ }
+}
+
+string HtmlTagNameOrUnknown(int i) {
+ if (i < kHtmlTagBuiltinMax) {
+ return HtmlTagEnumNames[i];
+ } else {
+ return StringPrintf("UNKNOWN%d", i);
+ }
+}
diff --git a/trunk/src/third_party/css_parser/src/webutil/html/htmltagenum.h b/trunk/src/third_party/css_parser/src/webutil/html/htmltagenum.h
new file mode 100644
index 0000000..09c5762
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/html/htmltagenum.h
@@ -0,0 +1,194 @@
+/**
+ * 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.
+ */
+
+// Copyright 2006 Google Inc. All Rights Reserved.
+// Author: mec@google.com (Michael Chastain)
+// Author: dpeng@google.com (Daniel Peng)
+
+#ifndef WEBUTIL_HTML_HTMLTAGENUM_H__
+#define WEBUTIL_HTML_HTMLTAGENUM_H__
+
+#include <string>
+#include "string_using.h"
+
+// This is public at the top level because I think a lot of people
+// will want to use it.
+//
+// NOTE: These values may be stored in proto buffers. Do not change or remove
+// any existing values. If you want to add support for a new tag, use the
+// next available value (as specified by kHtmlTagBuiltinMax), and increment
+// kHtmlTagBuiltinMax. Also make sure to add the new tag to HtmlTagEnumNames
+// in htmltagenum.cc.
+//
+// This tag list came from:
+// http://www.w3.org/TR/REC-html40/index/elements.html
+// With additional tags used in:
+// repository/lexer/html_lexer.cc
+// repository/parsers/base/handler-parser.cc
+// And some additional legacy backwards-compatible tags from HTML5:
+// http://whatwg.org/specs/web-apps/current-work/#stack
+// [accessed 2006-10-10]
+// Plus some Netscape Navigator 4.0 tags from:
+// http://devedge-temp.mozilla.org/library/manuals/1998/htmlguide/
+// [accessed 2006-11-21]
+// The !doctype tag defines a reference to DTD (doctype type definition).
+// It is a SGML tag and is somehow mentioned in the HTML 4.01 spec.
+// http://www.w3.org/TR/html401/intro/sgmltut.html
+// The noindex tag is a non-standard tag used mostly by Russian sites:
+// http://ru.wikipedia.org/wiki/Noindex
+// http://translate.google.com/translate?u=http://ru.wikipedia.org/wiki/Noindex&sl=ru&tl=en
+
+enum HtmlTagEnum {
+ // Unknown tag: must be 0
+ kHtmlTagUnknown = 0,
+ // From html 4.01 spec
+ kHtmlTagA = 1,
+ kHtmlTagAbbr = 2,
+ kHtmlTagAcronym = 3,
+ kHtmlTagAddress = 4,
+ kHtmlTagApplet = 5,
+ kHtmlTagArea = 6,
+ kHtmlTagB = 7,
+ kHtmlTagBase = 8,
+ kHtmlTagBasefont = 9,
+ kHtmlTagBdo = 10,
+ kHtmlTagBig = 11,
+ kHtmlTagBlockquote = 12,
+ kHtmlTagBody = 13,
+ kHtmlTagBr = 14,
+ kHtmlTagButton = 15,
+ kHtmlTagCaption = 16,
+ kHtmlTagCenter = 17,
+ kHtmlTagCite = 18,
+ kHtmlTagCode = 19,
+ kHtmlTagCol = 20,
+ kHtmlTagColgroup = 21,
+ kHtmlTagDd = 22,
+ kHtmlTagDel = 23,
+ kHtmlTagDfn = 24,
+ kHtmlTagDir = 25,
+ kHtmlTagDiv = 26,
+ kHtmlTagDl = 27,
+ kHtmlTagDt = 28,
+ kHtmlTagEm = 29,
+ kHtmlTagFieldset = 30,
+ kHtmlTagFont = 31,
+ kHtmlTagForm = 32,
+ kHtmlTagFrame = 33,
+ kHtmlTagFrameset = 34,
+ kHtmlTagH1 = 35,
+ kHtmlTagH2 = 36,
+ kHtmlTagH3 = 37,
+ kHtmlTagH4 = 38,
+ kHtmlTagH5 = 39,
+ kHtmlTagH6 = 40,
+ kHtmlTagHead = 41,
+ kHtmlTagHr = 42,
+ kHtmlTagHtml = 43,
+ kHtmlTagI = 44,
+ kHtmlTagIframe = 45,
+ kHtmlTagImg = 46,
+ kHtmlTagInput = 47,
+ kHtmlTagIns = 48,
+ kHtmlTagIsindex = 49,
+ kHtmlTagKbd = 50,
+ kHtmlTagLabel = 51,
+ kHtmlTagLegend = 52,
+ kHtmlTagLi = 53,
+ kHtmlTagLink = 54,
+ kHtmlTagMap = 55,
+ kHtmlTagMenu = 56,
+ kHtmlTagMeta = 57,
+ kHtmlTagNoframes = 58,
+ kHtmlTagNoscript = 59,
+ kHtmlTagObject = 60,
+ kHtmlTagOl = 61,
+ kHtmlTagOptgroup = 62,
+ kHtmlTagOption = 63,
+ kHtmlTagP = 64,
+ kHtmlTagParam = 65,
+ kHtmlTagPre = 66,
+ kHtmlTagQ = 67,
+ kHtmlTagS = 68,
+ kHtmlTagSamp = 69,
+ kHtmlTagScript = 70,
+ kHtmlTagSelect = 71,
+ kHtmlTagSmall = 72,
+ kHtmlTagSpan = 73,
+ kHtmlTagStrike = 74,
+ kHtmlTagStrong = 75,
+ kHtmlTagStyle = 76,
+ kHtmlTagSub = 77,
+ kHtmlTagSup = 78,
+ kHtmlTagTable = 79,
+ kHtmlTagTbody = 80,
+ kHtmlTagTd = 81,
+ kHtmlTagTextarea = 82,
+ kHtmlTagTfoot = 83,
+ kHtmlTagTh = 84,
+ kHtmlTagThead = 85,
+ kHtmlTagTitle = 86,
+ kHtmlTagTr = 87,
+ kHtmlTagTt = 88,
+ kHtmlTagU = 89,
+ kHtmlTagUl = 90,
+ kHtmlTagVar = 91,
+ // Empty tag
+ kHtmlTagZeroLength = 92,
+ // Used in repository/lexer/html_lexer.cc
+ kHtmlTagBangDashDash = 93,
+ kHtmlTagBlink = 94,
+ // Used in repository/parsers/base/handler-parser.cc
+ kHtmlTagEmbed = 95,
+ kHtmlTagMarquee = 96,
+ // Legacy backwards-compatible tags mentioned in HTML5.
+ kHtmlTagNobr = 97,
+ kHtmlTagWbr = 98,
+ kHtmlTagBgsound = 99,
+ kHtmlTagImage = 100,
+ kHtmlTagListing = 101,
+ kHtmlTagNoembed = 102,
+ kHtmlTagPlaintext = 103,
+ kHtmlTagSpacer = 104,
+ kHtmlTagXmp = 105,
+ // From Netscape Navigator 4.0
+ kHtmlTagIlayer = 106,
+ kHtmlTagKeygen = 107,
+ kHtmlTagLayer = 108,
+ kHtmlTagMulticol = 109,
+ kHtmlTagNolayer = 110,
+ kHtmlTagServer = 111,
+ // !doctype from SGML and also from HTML 4.01 spec.
+ kHtmlTagBangDoctype = 112,
+ // Legacy tag used mostly by Russian sites.
+ kHtmlTagNoindex = 113,
+ // Anything starts with ! (except those marked above) or ?
+ kHtmlTagBogusComment = 114,
+
+ // Add new tag values here. Make sure you also add new tags to
+ // HtmlTagEnumNames in htmltagenum.cc and update kHtmlTagBuiltinMax.
+
+ // Sentinel.
+ kHtmlTagBuiltinMax = 115
+};
+
+// NULL if tag >= kHtmlTagBuiltinMax.
+extern const char* HtmlTagName(HtmlTagEnum tag);
+
+// StringPrintf("UNKNOWN%d", tag) if tag >= kHtmlTag
+extern string HtmlTagNameOrUnknown(int i);
+
+#endif // WEBUTIL_HTML_HTMLTAGENUM_H__
diff --git a/trunk/src/third_party/css_parser/src/webutil/html/htmltagindex.cc b/trunk/src/third_party/css_parser/src/webutil/html/htmltagindex.cc
new file mode 100644
index 0000000..f6327bf
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/html/htmltagindex.cc
@@ -0,0 +1,425 @@
+/**
+ * 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.
+ */
+
+// Copyright 2006, Google Inc. All rights reserved.
+// Author: mec@google.com (Michael Chastain)
+
+#include <ctype.h>
+#include <string.h>
+#include "webutil/html/htmltagindex.h"
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/paranoid.h"
+#include "strings/ascii_ctype.h"
+#include "util/gtl/dense_hash_map.h"
+#include "util/hash/hash.h"
+
+// Assert character properties that the fast path depends on.
+// (Uppercase letter | 0x20) == lowercase letter
+// All digits have 0x20 bit set
+// Several special chars have 0x20 bit set
+COMPILE_ASSERT(('A'|0x20)=='a', A_a);
+COMPILE_ASSERT(('B'|0x20)=='b', B_b);
+COMPILE_ASSERT(('C'|0x20)=='c', C_c);
+COMPILE_ASSERT(('D'|0x20)=='d', D_d);
+COMPILE_ASSERT(('E'|0x20)=='e', E_e);
+COMPILE_ASSERT(('F'|0x20)=='f', F_f);
+COMPILE_ASSERT(('G'|0x20)=='g', G_g);
+COMPILE_ASSERT(('H'|0x20)=='h', H_h);
+COMPILE_ASSERT(('I'|0x20)=='i', I_i);
+COMPILE_ASSERT(('J'|0x20)=='j', J_j);
+COMPILE_ASSERT(('K'|0x20)=='k', K_k);
+COMPILE_ASSERT(('L'|0x20)=='l', L_l);
+COMPILE_ASSERT(('M'|0x20)=='m', M_m);
+COMPILE_ASSERT(('N'|0x20)=='n', N_n);
+COMPILE_ASSERT(('O'|0x20)=='o', O_o);
+COMPILE_ASSERT(('P'|0x20)=='p', P_p);
+COMPILE_ASSERT(('Q'|0x20)=='q', Q_q);
+COMPILE_ASSERT(('R'|0x20)=='r', R_r);
+COMPILE_ASSERT(('S'|0x20)=='s', S_s);
+COMPILE_ASSERT(('T'|0x20)=='t', T_t);
+COMPILE_ASSERT(('U'|0x20)=='u', U_u);
+COMPILE_ASSERT(('V'|0x20)=='v', V_v);
+COMPILE_ASSERT(('W'|0x20)=='w', W_w);
+COMPILE_ASSERT(('X'|0x20)=='x', X_x);
+COMPILE_ASSERT(('Y'|0x20)=='y', Y_y);
+COMPILE_ASSERT(('Z'|0x20)=='z', Z_z);
+COMPILE_ASSERT('0'&0x20, d0);
+COMPILE_ASSERT('1'&0x20, d1);
+COMPILE_ASSERT('2'&0x20, d2);
+COMPILE_ASSERT('3'&0x20, d3);
+COMPILE_ASSERT('4'&0x20, d4);
+COMPILE_ASSERT('5'&0x20, d5);
+COMPILE_ASSERT('6'&0x20, d6);
+COMPILE_ASSERT('7'&0x20, d7);
+COMPILE_ASSERT('8'&0x20, d8);
+COMPILE_ASSERT('9'&0x20, d9);
+COMPILE_ASSERT('!'&0x20, cBang);
+COMPILE_ASSERT('-'&0x20, cDash);
+COMPILE_ASSERT('?'&0x20, cQuestion);
+
+// Ctor.
+
+HtmlTagIndex::HtmlTagIndex()
+ : case_sensitive_fixed_(false),
+ index_max_(kHtmlTagBuiltinMax),
+ custom_tag_map_(NULL) {
+ this->SetCaseSensitive(false);
+}
+
+// Dtor.
+
+HtmlTagIndex::~HtmlTagIndex() {
+}
+
+// Case sensitivity.
+
+void HtmlTagIndex::SetCaseSensitive(bool case_sensitive) {
+ CHECK(!case_sensitive_fixed_);
+ case_sensitive_ = SanitizeBool(case_sensitive);
+ if (case_sensitive_) {
+ case_mask_1_ = case_mask_2_ = case_mask_3_ = case_mask_4_ = 0;
+ case_mask_5_ = case_mask_6_ = case_mask_7_ = case_mask_8_ = 0;
+ } else {
+ case_mask_1_ = 0x20;
+ case_mask_2_ = 0x2020;
+ case_mask_3_ = 0x202020;
+ case_mask_4_ = 0x20202020;
+ case_mask_5_ = 0x2020202020ull;
+ case_mask_6_ = 0x202020202020ull;
+ case_mask_7_ = 0x20202020202020ull;
+ case_mask_8_ = 0x2020202020202020ull;
+ }
+}
+
+// Return a case-aware string. If case-sensitive, this is the same string.
+// If case-insensitive, this is the lower-case version.
+
+static string CaseAwareString(bool case_sensitive,
+ const char* tag,
+ int length) {
+ CHECK_GE(length, 0);
+ string str;
+ if (case_sensitive) {
+ str.assign(tag, length);
+ } else {
+ for (int i = 0; i < length; ++i) {
+ str += ascii_tolower(tag[i]);
+ }
+ }
+ return str;
+}
+
+// Add a tag.
+
+int HtmlTagIndex::AddHtmlTag(const char* tag, int length) {
+ // No more changing the case sensitivity.
+ case_sensitive_fixed_ = true;
+
+ // Look for existing tag.
+ const int tag_id = FindHtmlTag(tag, length);
+ if (tag_id != kHtmlTagUnknown)
+ return tag_id;
+
+ // Add to the custom table.
+ if (custom_tag_map_.get() == NULL) {
+ custom_tag_map_.reset(new CustomTagMap);
+ custom_tag_map_->set_empty_key(string(""));
+ }
+ string tag_copy(CaseAwareString(case_sensitive_, tag, length));
+ (*custom_tag_map_)[tag_copy] = index_max_;
+ return index_max_++;
+}
+
+// Find a tag.
+// This is hard-wired to go fast.
+
+int HtmlTagIndex::FindHtmlTag(const char* tag, int length) const {
+// These macros convert a string to a uint32 or uint64
+#define S1(s) ((s)[0])
+#define S2(s) (S1(s) | ((s)[1] << 8))
+#define S3(s) (S2(s) | ((s)[2] << 16))
+#define S4(s) (S3(s) | ((s)[3] << 24))
+#define S5(s) (S4(s) | (static_cast<uint64>((s)[4]) << 32))
+#define S6(s) (S5(s) | (static_cast<uint64>((s)[5]) << 40))
+#define S7(s) (S6(s) | (static_cast<uint64>((s)[6]) << 48))
+#define S8(s) (S7(s) | (static_cast<uint64>((s)[7]) << 56))
+
+// These macros convert a sequence of characters to a uint32 or uint64
+#define C1(c0) (c0)
+#define C2(c0, c1) (C1(c0) | ((c1) << 8))
+#define C3(c0, c1, c2) (C2(c0, c1) | ((c2) << 16))
+#define C4(c0, c1, c2, c3) (C3(c0, c1, c2) | ((c3) << 24))
+#define C5(c0, c1, c2, c3, c4) \
+ (C4(c0, c1, c2, c3) | (static_cast<uint64>(c4) << 32))
+#define C6(c0, c1, c2, c3, c4, c5) \
+ (C5(c0, c1, c2, c3, c4) | (static_cast<uint64>(c5) << 40))
+#define C7(c0, c1, c2, c3, c4, c5, c6) \
+ (C6(c0, c1, c2, c3, c4, c5) | (static_cast<uint64>(c6) << 48))
+#define C8(c0, c1, c2, c3, c4, c5, c6, c7) \
+ (C7(c0, c1, c2, c3, c4, c5, c6) | (static_cast<uint64>(c7) << 56))
+
+ switch (length) {
+ case 0:
+ // Empty tag
+ return kHtmlTagZeroLength;
+ case 1:
+ {
+ switch (S1(tag)|case_mask_1_) {
+ default: break;
+ // From html 4.01 spec
+ case C1('a'): return kHtmlTagA;
+ case C1('b'): return kHtmlTagB;
+ case C1('i'): return kHtmlTagI;
+ case C1('p'): return kHtmlTagP;
+ case C1('q'): return kHtmlTagQ;
+ case C1('s'): return kHtmlTagS;
+ case C1('u'): return kHtmlTagU;
+ }
+ }
+ break;
+ case 2:
+ {
+ switch (S2(tag)|case_mask_2_) {
+ default: break;
+ // From html 4.01 spec
+ case C2('b','r'): return kHtmlTagBr;
+ case C2('d','d'): return kHtmlTagDd;
+ case C2('d','l'): return kHtmlTagDl;
+ case C2('d','t'): return kHtmlTagDt;
+ case C2('e','m'): return kHtmlTagEm;
+ // Beware of matching "h\x11" or "H\x11" in case-insensitive mode.
+ case C2('h','1'): if (tag[1] == '1') return kHtmlTagH1; else break;
+ case C2('h','2'): if (tag[1] == '2') return kHtmlTagH2; else break;
+ case C2('h','3'): if (tag[1] == '3') return kHtmlTagH3; else break;
+ case C2('h','4'): if (tag[1] == '4') return kHtmlTagH4; else break;
+ case C2('h','5'): if (tag[1] == '5') return kHtmlTagH5; else break;
+ case C2('h','6'): if (tag[1] == '6') return kHtmlTagH6; else break;
+ case C2('h','r'): return kHtmlTagHr;
+ case C2('l','i'): return kHtmlTagLi;
+ case C2('o','l'): return kHtmlTagOl;
+ case C2('t','d'): return kHtmlTagTd;
+ case C2('t','h'): return kHtmlTagTh;
+ case C2('t','r'): return kHtmlTagTr;
+ case C2('t','t'): return kHtmlTagTt;
+ case C2('u','l'): return kHtmlTagUl;
+ }
+ }
+ break;
+ case 3:
+ {
+ switch (S3(tag)|case_mask_3_) {
+ default: break;
+ // From html 4.01 spec
+ case C3('b','d','o'): return kHtmlTagBdo;
+ case C3('b','i','g'): return kHtmlTagBig;
+ case C3('c','o','l'): return kHtmlTagCol;
+ case C3('d','e','l'): return kHtmlTagDel;
+ case C3('d','i','r'): return kHtmlTagDir;
+ case C3('d','i','v'): return kHtmlTagDiv;
+ case C3('d','f','n'): return kHtmlTagDfn;
+ case C3('i','m','g'): return kHtmlTagImg;
+ case C3('i','n','s'): return kHtmlTagIns;
+ case C3('k','b','d'): return kHtmlTagKbd;
+ case C3('m','a','p'): return kHtmlTagMap;
+ case C3('p','r','e'): return kHtmlTagPre;
+ case C3('s','u','b'): return kHtmlTagSub;
+ case C3('s','u','p'): return kHtmlTagSup;
+ case C3('v','a','r'): return kHtmlTagVar;
+ case C3('w','b','r'): return kHtmlTagWbr;
+ case C3('x','m','p'): return kHtmlTagXmp;
+ // Used in repository/lexer/html_lexer.cc
+ case C3('!','-','-'):
+ // These chars have no upper-lower case form so I haev to rematch.
+ if (S3(tag) == C3('!','-','-'))
+ return kHtmlTagBangDashDash;
+ break;
+ }
+ }
+ break;
+ case 4:
+ {
+ switch (S4(tag)|case_mask_4_) {
+ default: break;
+ // From html 4.01 spec
+ case C4('a','b','b','r'): return kHtmlTagAbbr;
+ case C4('a','r','e','a'): return kHtmlTagArea;
+ case C4('b','a','s','e'): return kHtmlTagBase;
+ case C4('b','o','d','y'): return kHtmlTagBody;
+ case C4('c','i','t','e'): return kHtmlTagCite;
+ case C4('c','o','d','e'): return kHtmlTagCode;
+ case C4('f','o','n','t'): return kHtmlTagFont;
+ case C4('f','o','r','m'): return kHtmlTagForm;
+ case C4('h','e','a','d'): return kHtmlTagHead;
+ case C4('h','t','m','l'): return kHtmlTagHtml;
+ case C4('l','i','n','k'): return kHtmlTagLink;
+ case C4('m','e','n','u'): return kHtmlTagMenu;
+ case C4('m','e','t','a'): return kHtmlTagMeta;
+ case C4('s','a','m','p'): return kHtmlTagSamp;
+ case C4('s','p','a','n'): return kHtmlTagSpan;
+ case C4('n','o','b','r'): return kHtmlTagNobr;
+ }
+ }
+ break;
+ case 5:
+ {
+ switch (S5(tag)|case_mask_5_) {
+ default: break;
+ // From html 4.01 spec
+ case C5('f','r','a','m','e'): return kHtmlTagFrame;
+ case C5('i','n','p','u','t'): return kHtmlTagInput;
+ case C5('l','a','b','e','l'): return kHtmlTagLabel;
+ case C5('p','a','r','a','m'): return kHtmlTagParam;
+ case C5('s','m','a','l','l'): return kHtmlTagSmall;
+ case C5('s','t','y','l','e'): return kHtmlTagStyle;
+ case C5('t','a','b','l','e'): return kHtmlTagTable;
+ case C5('t','b','o','d','y'): return kHtmlTagTbody;
+ case C5('t','f','o','o','t'): return kHtmlTagTfoot;
+ case C5('t','h','e','a','d'): return kHtmlTagThead;
+ case C5('t','i','t','l','e'): return kHtmlTagTitle;
+ // Used in repository/lexer/html_lexer.cc
+ case C5('b','l','i','n','k'): return kHtmlTagBlink;
+ // Used in repository/parsers/base/handler-parser.cc
+ case C5('e','m','b','e','d'): return kHtmlTagEmbed;
+ case C5('i','m','a','g','e'): return kHtmlTagImage;
+ // From Netscape Navigator 4.0
+ case C5('l','a','y','e','r'): return kHtmlTagLayer;
+ }
+ }
+ break;
+ case 6:
+ {
+ switch (S6(tag)|case_mask_6_) {
+ default: break;
+ // From html 4.01 spec
+ case C6('a','p','p','l','e','t'): return kHtmlTagApplet;
+ case C6('b','u','t','t','o','n'): return kHtmlTagButton;
+ case C6('c','e','n','t','e','r'): return kHtmlTagCenter;
+ case C6('i','f','r','a','m','e'): return kHtmlTagIframe;
+ case C6('l','e','g','e','n','d'): return kHtmlTagLegend;
+ case C6('o','b','j','e','c','t'): return kHtmlTagObject;
+ case C6('o','p','t','i','o','n'): return kHtmlTagOption;
+ case C6('s','c','r','i','p','t'): return kHtmlTagScript;
+ case C6('s','e','l','e','c','t'): return kHtmlTagSelect;
+ case C6('s','t','r','i','k','e'): return kHtmlTagStrike;
+ case C6('s','t','r','o','n','g'): return kHtmlTagStrong;
+ case C6('s','p','a','c','e','r'): return kHtmlTagSpacer;
+ // From Netscape Navigator 4.0
+ case C6('i','l','a','y','e','r'): return kHtmlTagIlayer;
+ case C6('k','e','y','g','e','n'): return kHtmlTagKeygen;
+ case C6('s','e','r','v','e','r'): return kHtmlTagServer;
+ }
+ }
+ break;
+ case 7:
+ {
+ switch (S7(tag)|case_mask_7_) {
+ default: break;
+ // From html 4.01 spec
+ case C7('a','c','r','o','n','y','m'): return kHtmlTagAcronym;
+ case C7('a','d','d','r','e','s','s'): return kHtmlTagAddress;
+ case C7('c','a','p','t','i','o','n'): return kHtmlTagCaption;
+ case C7('i','s','i','n','d','e','x'): return kHtmlTagIsindex;
+ // Used in repository/parsers/base/handler-parser.cc
+ case C7('m','a','r','q','u','e','e'): return kHtmlTagMarquee;
+ case C7('b','g','s','o','u','n','d'): return kHtmlTagBgsound;
+ case C7('l','i','s','t','i','n','g'): return kHtmlTagListing;
+ case C7('n','o','e','m','b','e','d'): return kHtmlTagNoembed;
+ // From Netscape Navigator 4.0
+ case C7('n','o','l','a','y','e','r'): return kHtmlTagNolayer;
+ // Legacy tag used mostly by Russian sites
+ case C7('n','o','i','n','d','e','x'): return kHtmlTagNoindex;
+ }
+ }
+ break;
+ case 8:
+ {
+ switch (S8(tag)|case_mask_8_) {
+ default: break;
+ // From html 4.01 spec
+ case C8('b','a','s','e','f','o','n','t'): return kHtmlTagBasefont;
+ case C8('c','o','l','g','r','o','u','p'): return kHtmlTagColgroup;
+ case C8('f','i','e','l','d','s','e','t'): return kHtmlTagFieldset;
+ case C8('f','r','a','m','e','s','e','t'): return kHtmlTagFrameset;
+ case C8('n','o','f','r','a','m','e','s'): return kHtmlTagNoframes;
+ case C8('n','o','s','c','r','i','p','t'): return kHtmlTagNoscript;
+ case C8('o','p','t','g','r','o','u','p'): return kHtmlTagOptgroup;
+ case C8('t','e','x','t','a','r','e','a'): return kHtmlTagTextarea;
+ // From Netscape Navigator 4.0
+ case C8('m','u','l','t','i','c','o','l'): return kHtmlTagMulticol;
+ }
+ }
+ break;
+ case 9:
+ {
+ if ((S4(tag+0)|case_mask_4_) == C4('p','l','a','i') &&
+ (S4(tag+4)|case_mask_4_) == C4('n','t','e','x') &&
+ (S1(tag+8)|case_mask_1_) == C1('t'))
+ return kHtmlTagPlaintext;
+ }
+ break;
+ case 10:
+ {
+ // From html 4.01 spec
+ if ((S4(tag+0)|case_mask_4_) == C4('b','l','o','c') &&
+ (S4(tag+4)|case_mask_4_) == C4('k','q','u','o') &&
+ (S2(tag+8)|case_mask_2_) == C2('t','e'))
+ return kHtmlTagBlockquote;
+ }
+ break;
+ }
+
+ // !doctype is special. Any tag name starting with !doctype (no need for
+ // exact match) is considered !doctype. Tested on IE 7.0 and Firefox 2.0.
+ // You can also refer to (ongoing work):
+ // http://whatwg.org/specs/web-apps/current-work/#markup
+ if (length >= 8 && S1(tag) == C1('!') &&
+ (S8(tag)|case_mask_8_) == C8('!','d','o','c','t','y','p','e'))
+ return kHtmlTagBangDoctype;
+
+ // Otherwise, !blahblah and ?blahblah are comments.
+ if (S1(tag) == C1('!') || S1(tag) == C1('?'))
+ return kHtmlTagBogusComment;
+
+#undef C8
+#undef C7
+#undef C6
+#undef C5
+#undef C4
+#undef C3
+#undef C2
+#undef C1
+#undef S8
+#undef S7
+#undef S6
+#undef S5
+#undef S4
+#undef S3
+#undef S2
+#undef S1
+
+ // Look in the custom table.
+ if (custom_tag_map_.get() != NULL) {
+ string tag_copy(CaseAwareString(case_sensitive_, tag, length));
+ CustomTagMap::const_iterator it = custom_tag_map_->find(tag_copy);
+ if (it != custom_tag_map_->end()) {
+ return it->second;
+ }
+ }
+
+ // Unknown tag.
+ return kHtmlTagUnknown;
+}
diff --git a/trunk/src/third_party/css_parser/src/webutil/html/htmltagindex.h b/trunk/src/third_party/css_parser/src/webutil/html/htmltagindex.h
new file mode 100644
index 0000000..827ff10
--- /dev/null
+++ b/trunk/src/third_party/css_parser/src/webutil/html/htmltagindex.h
@@ -0,0 +1,89 @@
+/**
+ * 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.
+ */
+
+// Copyright 2006, Google Inc. All rights reserved.
+// Author: mec@google.com (Michael Chastain)
+//
+// Map an html tag to a dense index number.
+// Hardwired for speed on builtin tags.
+// Caller can add tags on top of the builtins.
+// Caller can choose case-sensitive or case-insensitive.
+//
+// TODO(mec): merge this with webutil/html/htmltag
+
+#ifndef WEBUTIL_HTML_HTML_TAG_INDEX_H__
+#define WEBUTIL_HTML_HTML_TAG_INDEX_H__
+
+#include <string>
+#include "string_using.h"
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "util/gtl/dense_hash_map.h"
+#include "webutil/html/htmltagenum.h"
+
+class HtmlTagIndex {
+ public:
+ HtmlTagIndex();
+ ~HtmlTagIndex();
+
+ // Add a tag and return its index. It is okay to add a builtin
+ // tag or to add the same tag more than once.
+ int AddHtmlTag(const char* tag, int length);
+ int AddHtmlTag(const char* tag) {
+ return AddHtmlTag(tag, strlen(tag));
+ }
+
+ // Find returns a value in the half-open range [0..GetIndexMax()).
+ // 0 == unknown tag.
+ COMPILE_ASSERT(kHtmlTagUnknown == 0, unknown_tag_equals_zero);
+ int FindHtmlTag(const char* tag, int length) const;
+ int FindHtmlTag(const char* tag) const {
+ return FindHtmlTag(tag, strlen(tag));
+ }
+
+ // Return the half-open upper bound on lookup return value.
+ // If GetIndexMax returns 10, then find will return [0..9).
+ int GetIndexMax() const {
+ return index_max_;
+ };
+
+ // Set case sensitivity. This cannot be done after any calls to AddHtmlTag.
+ void SetCaseSensitive(bool case_sensitive);
+ bool IsCaseSensitive() const {
+ return case_sensitive_;
+ }
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(HtmlTagIndex);
+
+ // Case sensitive stuff.
+ bool case_sensitive_fixed_;
+ bool case_sensitive_;
+ uint32 case_mask_1_;
+ uint32 case_mask_2_;
+ uint32 case_mask_3_;
+ uint32 case_mask_4_;
+ uint64 case_mask_5_;
+ uint64 case_mask_6_;
+ uint64 case_mask_7_;
+ uint64 case_mask_8_;
+
+ int index_max_;
+ typedef dense_hash_map<string, int> CustomTagMap;
+ scoped_ptr<CustomTagMap> custom_tag_map_;
+};
+
+#endif // WEBUTIL_HTML_HTML_TAG_INDEX_H__
diff --git a/trunk/src/third_party/gflags/gen/arch/linux/ia32/include/gflags/gflags.h b/trunk/src/third_party/gflags/gen/arch/linux/ia32/include/gflags/gflags.h
new file mode 100644
index 0000000..821d194
--- /dev/null
+++ b/trunk/src/third_party/gflags/gen/arch/linux/ia32/include/gflags/gflags.h
@@ -0,0 +1,538 @@
+// Copyright (c) 2006, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// ---
+// Author: Ray Sidney
+// Revamped and reorganized by Craig Silverstein
+//
+// This is the file that should be included by any file which declares
+// or defines a command line flag or wants to parse command line flags
+// or print a program usage message (which will include information about
+// flags). Executive summary, in the form of an example foo.cc file:
+//
+// #include "foo.h" // foo.h has a line "DECLARE_int32(start);"
+//
+// DEFINE_int32(end, 1000, "The last record to read");
+// DECLARE_bool(verbose); // some other file has a DEFINE_bool(verbose, ...)
+//
+// void MyFunc() {
+// if (FLAGS_verbose) printf("Records %d-%d\n", FLAGS_start, FLAGS_end);
+// }
+//
+// Then, at the command-line:
+// ./foo --noverbose --start=5 --end=100
+//
+// For more details, see
+// doc/gflags.html
+//
+// --- A note about thread-safety:
+//
+// We describe many functions in this routine as being thread-hostile,
+// thread-compatible, or thread-safe. Here are the meanings we use:
+//
+// thread-safe: it is safe for multiple threads to call this routine
+// (or, when referring to a class, methods of this class)
+// concurrently.
+// thread-hostile: it is not safe for multiple threads to call this
+// routine (or methods of this class) concurrently. In gflags,
+// most thread-hostile routines are intended to be called early in,
+// or even before, main() -- that is, before threads are spawned.
+// thread-compatible: it is safe for multiple threads to read from
+// this variable (when applied to variables), or to call const
+// methods of this class (when applied to classes), as long as no
+// other thread is writing to the variable or calling non-const
+// methods of this class.
+
+#ifndef GOOGLE_GFLAGS_H_
+#define GOOGLE_GFLAGS_H_
+
+#include <string>
+#include <vector>
+
+// We care a lot about number of bits things take up. Unfortunately,
+// systems define their bit-specific ints in a lot of different ways.
+// We use our own way, and have a typedef to get there.
+// Note: these commands below may look like "#if 1" or "#if 0", but
+// that's because they were constructed that way at ./configure time.
+// Look at gflags.h.in to see how they're calculated (based on your config).
+#if 1
+#include <stdint.h> // the normal place uint16_t is defined
+#endif
+#if 1
+#include <sys/types.h> // the normal place u_int16_t is defined
+#endif
+#if 1
+#include <inttypes.h> // a third place for uint16_t or u_int16_t
+#endif
+
+namespace google {
+
+#if 1 // the C99 format
+typedef int32_t int32;
+typedef uint32_t uint32;
+typedef int64_t int64;
+typedef uint64_t uint64;
+#elif 1 // the BSD format
+typedef int32_t int32;
+typedef u_int32_t uint32;
+typedef int64_t int64;
+typedef u_int64_t uint64;
+#elif 0 // the windows (vc7) format
+typedef __int32 int32;
+typedef unsigned __int32 uint32;
+typedef __int64 int64;
+typedef unsigned __int64 uint64;
+#else
+#error Do not know how to define a 32-bit integer quantity on your system
+#endif
+
+// --------------------------------------------------------------------
+// To actually define a flag in a file, use DEFINE_bool,
+// DEFINE_string, etc. at the bottom of this file. You may also find
+// it useful to register a validator with the flag. This ensures that
+// when the flag is parsed from the commandline, or is later set via
+// SetCommandLineOption, we call the validation function.
+//
+// The validation function should return true if the flag value is valid, and
+// false otherwise. If the function returns false for the new setting of the
+// flag, the flag will retain its current value. If it returns false for the
+// default value, InitGoogle will die.
+//
+// This function is safe to call at global construct time (as in the
+// example below).
+//
+// Example use:
+// static bool ValidatePort(const char* flagname, int32 value) {
+// if (value > 0 && value < 32768) // value is ok
+// return true;
+// printf("Invalid value for --%s: %d\n", flagname, (int)value);
+// return false;
+// }
+// DEFINE_int32(port, 0, "What port to listen on");
+// static bool dummy = RegisterFlagValidator(&FLAGS_port, &ValidatePort);
+
+// Returns true if successfully registered, false if not (because the
+// first argument doesn't point to a command-line flag, or because a
+// validator is already registered for this flag).
+bool RegisterFlagValidator(const bool* flag,
+ bool (*validate_fn)(const char*, bool));
+bool RegisterFlagValidator(const int32* flag,
+ bool (*validate_fn)(const char*, int32));
+bool RegisterFlagValidator(const int64* flag,
+ bool (*validate_fn)(const char*, int64));
+bool RegisterFlagValidator(const uint64* flag,
+ bool (*validate_fn)(const char*, uint64));
+bool RegisterFlagValidator(const double* flag,
+ bool (*validate_fn)(const char*, double));
+bool RegisterFlagValidator(const std::string* flag,
+ bool (*validate_fn)(const char*, const std::string&));
+
+
+// --------------------------------------------------------------------
+// These methods are the best way to get access to info about the
+// list of commandline flags. Note that these routines are pretty slow.
+// GetAllFlags: mostly-complete info about the list, sorted by file.
+// ShowUsageWithFlags: pretty-prints the list to stdout (what --help does)
+// ShowUsageWithFlagsRestrict: limit to filenames with restrict as a substr
+//
+// In addition to accessing flags, you can also access argv[0] (the program
+// name) and argv (the entire commandline), which we sock away a copy of.
+// These variables are static, so you should only set them once.
+
+struct CommandLineFlagInfo {
+ std::string name; // the name of the flag
+ std::string type; // the type of the flag: int32, etc
+ std::string description; // the "help text" associated with the flag
+ std::string current_value; // the current value, as a string
+ std::string default_value; // the default value, as a string
+ std::string filename; // 'cleaned' version of filename holding the flag
+ bool has_validator_fn; // true if RegisterFlagValidator called on flag
+ bool is_default; // true if the flag has default value
+};
+
+// Using this inside of a validator is a recipe for a deadlock.
+// TODO(wojtekm) Fix locking when validators are running, to make it safe to
+// call validators during ParseAllFlags.
+// Also make sure then to uncomment the corresponding unit test in
+// commandlineflags_unittest.sh
+extern void GetAllFlags(std::vector<CommandLineFlagInfo>* OUTPUT);
+// These two are actually defined in commandlineflags_reporting.cc.
+extern void ShowUsageWithFlags(const char *argv0); // what --help does
+extern void ShowUsageWithFlagsRestrict(const char *argv0, const char *restrict);
+
+// Create a descriptive string for a flag.
+// Goes to some trouble to make pretty line breaks.
+extern std::string DescribeOneFlag(const CommandLineFlagInfo& flag);
+
+// Thread-hostile; meant to be called before any threads are spawned.
+extern void SetArgv(int argc, const char** argv);
+// The following functions are thread-safe as long as SetArgv() is
+// only called before any threads start.
+extern const std::vector<std::string>& GetArgvs(); // all of argv as a vector
+extern const char* GetArgv(); // all of argv as a string
+extern const char* GetArgv0(); // only argv0
+extern uint32 GetArgvSum(); // simple checksum of argv
+extern const char* ProgramInvocationName(); // argv0, or "UNKNOWN" if not set
+extern const char* ProgramInvocationShortName(); // basename(argv0)
+// ProgramUsage() is thread-safe as long as SetUsageMessage() is only
+// called before any threads start.
+extern const char* ProgramUsage(); // string set by SetUsageMessage()
+
+
+// --------------------------------------------------------------------
+// Normally you access commandline flags by just saying "if (FLAGS_foo)"
+// or whatever, and set them by calling "FLAGS_foo = bar" (or, more
+// commonly, via the DEFINE_foo macro). But if you need a bit more
+// control, we have programmatic ways to get/set the flags as well.
+// These programmatic ways to access flags are thread-safe, but direct
+// access is only thread-compatible.
+
+// Return true iff the flagname was found.
+// OUTPUT is set to the flag's value, or unchanged if we return false.
+extern bool GetCommandLineOption(const char* name, std::string* OUTPUT);
+
+// Return true iff the flagname was found. OUTPUT is set to the flag's
+// CommandLineFlagInfo or unchanged if we return false.
+extern bool GetCommandLineFlagInfo(const char* name,
+ CommandLineFlagInfo* OUTPUT);
+
+// Return the CommandLineFlagInfo of the flagname. exit() if name not found.
+// Example usage, to check if a flag's value is currently the default value:
+// if (GetCommandLineFlagInfoOrDie("foo").is_default) ...
+extern CommandLineFlagInfo GetCommandLineFlagInfoOrDie(const char* name);
+
+enum FlagSettingMode {
+ // update the flag's value (can call this multiple times).
+ SET_FLAGS_VALUE,
+ // update the flag's value, but *only if* it has not yet been updated
+ // with SET_FLAGS_VALUE, SET_FLAG_IF_DEFAULT, or "FLAGS_xxx = nondef".
+ SET_FLAG_IF_DEFAULT,
+ // set the flag's default value to this. If the flag has not yet updated
+ // yet (via SET_FLAGS_VALUE, SET_FLAG_IF_DEFAULT, or "FLAGS_xxx = nondef")
+ // change the flag's current value to the new default value as well.
+ SET_FLAGS_DEFAULT
+};
+
+// Set a particular flag ("command line option"). Returns a string
+// describing the new value that the option has been set to. The
+// return value API is not well-specified, so basically just depend on
+// it to be empty if the setting failed for some reason -- the name is
+// not a valid flag name, or the value is not a valid value -- and
+// non-empty else.
+
+// SetCommandLineOption uses set_mode == SET_FLAGS_VALUE (the common case)
+extern std::string SetCommandLineOption(const char* name, const char* value);
+extern std::string SetCommandLineOptionWithMode(const char* name, const char* value,
+ FlagSettingMode set_mode);
+
+
+// --------------------------------------------------------------------
+// Saves the states (value, default value, whether the user has set
+// the flag, registered validators, etc) of all flags, and restores
+// them when the FlagSaver is destroyed. This is very useful in
+// tests, say, when you want to let your tests change the flags, but
+// make sure that they get reverted to the original states when your
+// test is complete.
+//
+// Example usage:
+// void TestFoo() {
+// FlagSaver s1;
+// FLAG_foo = false;
+// FLAG_bar = "some value";
+//
+// // test happens here. You can return at any time
+// // without worrying about restoring the FLAG values.
+// }
+//
+// Note: This class is marked with __attribute__((unused)) because all the
+// work is done in the constructor and destructor, so in the standard
+// usage example above, the compiler would complain that it's an
+// unused variable.
+//
+// This class is thread-safe.
+
+class FlagSaver {
+ public:
+ FlagSaver();
+ ~FlagSaver();
+
+ private:
+ class FlagSaverImpl* impl_; // we use pimpl here to keep API steady
+
+ FlagSaver(const FlagSaver&); // no copying!
+ void operator=(const FlagSaver&);
+} __attribute__ ((unused));
+
+// --------------------------------------------------------------------
+// Some deprecated or hopefully-soon-to-be-deprecated functions.
+
+// This is often used for logging. TODO(csilvers): figure out a better way
+extern std::string CommandlineFlagsIntoString();
+// Usually where this is used, a FlagSaver should be used instead.
+extern bool ReadFlagsFromString(const std::string& flagfilecontents,
+ const char* prog_name,
+ bool errors_are_fatal); // uses SET_FLAGS_VALUE
+
+// These let you manually implement --flagfile functionality.
+// DEPRECATED.
+extern bool AppendFlagsIntoFile(const std::string& filename, const char* prog_name);
+extern bool SaveCommandFlags(); // actually defined in google.cc !
+extern bool ReadFromFlagsFile(const std::string& filename, const char* prog_name,
+ bool errors_are_fatal); // uses SET_FLAGS_VALUE
+
+
+// --------------------------------------------------------------------
+// Useful routines for initializing flags from the environment.
+// In each case, if 'varname' does not exist in the environment
+// return defval. If 'varname' does exist but is not valid
+// (e.g., not a number for an int32 flag), abort with an error.
+// Otherwise, return the value. NOTE: for booleans, for true use
+// 't' or 'T' or 'true' or '1', for false 'f' or 'F' or 'false' or '0'.
+
+extern bool BoolFromEnv(const char *varname, bool defval);
+extern int32 Int32FromEnv(const char *varname, int32 defval);
+extern int64 Int64FromEnv(const char *varname, int64 defval);
+extern uint64 Uint64FromEnv(const char *varname, uint64 defval);
+extern double DoubleFromEnv(const char *varname, double defval);
+extern const char *StringFromEnv(const char *varname, const char *defval);
+
+
+// --------------------------------------------------------------------
+// The next two functions parse commandlineflags from main():
+
+// Set the "usage" message for this program. For example:
+// string usage("This program does nothing. Sample usage:\n");
+// usage += argv[0] + " <uselessarg1> <uselessarg2>";
+// SetUsageMessage(usage);
+// Do not include commandline flags in the usage: we do that for you!
+// Thread-hostile; meant to be called before any threads are spawned.
+extern void SetUsageMessage(const std::string& usage);
+
+// Looks for flags in argv and parses them. Rearranges argv to put
+// flags first, or removes them entirely if remove_flags is true.
+// If a flag is defined more than once in the command line or flag
+// file, the last definition is used.
+// See top-of-file for more details on this function.
+#ifndef SWIG // In swig, use ParseCommandLineFlagsScript() instead.
+extern uint32 ParseCommandLineFlags(int *argc, char*** argv,
+ bool remove_flags);
+#endif
+
+
+// Calls to ParseCommandLineNonHelpFlags and then to
+// HandleCommandLineHelpFlags can be used instead of a call to
+// ParseCommandLineFlags during initialization, in order to allow for
+// changing default values for some FLAGS (via
+// e.g. SetCommandLineOptionWithMode calls) between the time of
+// command line parsing and the time of dumping help information for
+// the flags as a result of command line parsing.
+// If a flag is defined more than once in the command line or flag
+// file, the last definition is used.
+extern uint32 ParseCommandLineNonHelpFlags(int *argc, char*** argv,
+ bool remove_flags);
+// This is actually defined in commandlineflags_reporting.cc.
+// This function is misnamed (it also handles --version, etc.), but
+// it's too late to change that now. :-(
+extern void HandleCommandLineHelpFlags(); // in commandlineflags_reporting.cc
+
+// Allow command line reparsing. Disables the error normally
+// generated when an unknown flag is found, since it may be found in a
+// later parse. Thread-hostile; meant to be called before any threads
+// are spawned.
+extern void AllowCommandLineReparsing();
+
+// Reparse the flags that have not yet been recognized.
+// Only flags registered since the last parse will be recognized.
+// Any flag value must be provided as part of the argument using "=",
+// not as a separate command line argument that follows the flag argument.
+// Intended for handling flags from dynamically loaded libraries,
+// since their flags are not registered until they are loaded.
+extern uint32 ReparseCommandLineNonHelpFlags();
+
+
+// --------------------------------------------------------------------
+// Now come the command line flag declaration/definition macros that
+// will actually be used. They're kind of hairy. A major reason
+// for this is initialization: we want people to be able to access
+// variables in global constructors and have that not crash, even if
+// their global constructor runs before the global constructor here.
+// (Obviously, we can't guarantee the flags will have the correct
+// default value in that case, but at least accessing them is safe.)
+// The only way to do that is have flags point to a static buffer.
+// So we make one, using a union to ensure proper alignment, and
+// then use placement-new to actually set up the flag with the
+// correct default value. In the same vein, we have to worry about
+// flag access in global destructors, so FlagRegisterer has to be
+// careful never to destroy the flag-values it constructs.
+//
+// Note that when we define a flag variable FLAGS_<name>, we also
+// preemptively define a junk variable, FLAGS_no<name>. This is to
+// cause a link-time error if someone tries to define 2 flags with
+// names like "logging" and "nologging". We do this because a bool
+// flag FLAG can be set from the command line to true with a "-FLAG"
+// argument, and to false with a "-noFLAG" argument, and so this can
+// potentially avert confusion.
+//
+// We also put flags into their own namespace. It is purposefully
+// named in an opaque way that people should have trouble typing
+// directly. The idea is that DEFINE puts the flag in the weird
+// namespace, and DECLARE imports the flag from there into the current
+// namespace. The net result is to force people to use DECLARE to get
+// access to a flag, rather than saying "extern bool FLAGS_whatever;"
+// or some such instead. We want this so we can put extra
+// functionality (like sanity-checking) in DECLARE if we want, and
+// make sure it is picked up everywhere.
+//
+// We also put the type of the variable in the namespace, so that
+// people can't DECLARE_int32 something that they DEFINE_bool'd
+// elsewhere.
+
+class FlagRegisterer {
+ public:
+ FlagRegisterer(const char* name, const char* type,
+ const char* help, const char* filename,
+ void* current_storage, void* defvalue_storage);
+};
+
+extern bool FlagsTypeWarn(const char *name);
+
+// If your application #defines STRIP_FLAG_HELP to a non-zero value
+// before #including this file, we remove the help message from the
+// binary file. This can reduce the size of the resulting binary
+// somewhat, and may also be useful for security reasons.
+
+extern const char kStrippedFlagHelp[];
+
+}
+
+#ifndef SWIG // In swig, ignore the main flag declarations
+
+#if defined(STRIP_FLAG_HELP) && STRIP_FLAG_HELP > 0
+// Need this construct to avoid the 'defined but not used' warning.
+#define MAYBE_STRIPPED_HELP(txt) (false ? (txt) : kStrippedFlagHelp)
+#else
+#define MAYBE_STRIPPED_HELP(txt) txt
+#endif
+
+// Each command-line flag has two variables associated with it: one
+// with the current value, and one with the default value. However,
+// we have a third variable, which is where value is assigned; it's a
+// constant. This guarantees that FLAG_##value is initialized at
+// static initialization time (e.g. before program-start) rather than
+// than global construction time (which is after program-start but
+// before main), at least when 'value' is a compile-time constant. We
+// use a small trick for the "default value" variable, and call it
+// FLAGS_no<name>. This serves the second purpose of assuring a
+// compile error if someone tries to define a flag named no<name>
+// which is illegal (--foo and --nofoo both affect the "foo" flag).
+#define DEFINE_VARIABLE(type, shorttype, name, value, help) \
+ namespace fL##shorttype { \
+ static const type FLAGS_nono##name = value; \
+ type FLAGS_##name = FLAGS_nono##name; \
+ type FLAGS_no##name = FLAGS_nono##name; \
+ static ::google::FlagRegisterer o_##name( \
+ #name, #type, MAYBE_STRIPPED_HELP(help), __FILE__, \
+ &FLAGS_##name, &FLAGS_no##name); \
+ } \
+ using fL##shorttype::FLAGS_##name
+
+#define DECLARE_VARIABLE(type, shorttype, name) \
+ namespace fL##shorttype { \
+ extern type FLAGS_##name; \
+ } \
+ using fL##shorttype::FLAGS_##name
+
+// For DEFINE_bool, we want to do the extra check that the passed-in
+// value is actually a bool, and not a string or something that can be
+// coerced to a bool. These declarations (no definition needed!) will
+// help us do that, and never evaluate From, which is important.
+// We'll use 'sizeof(IsBool(val))' to distinguish. This code requires
+// that the compiler have different sizes for bool & double. Since
+// this is not guaranteed by the standard, we check it with a
+// compile-time assert (msg[-1] will give a compile-time error).
+namespace fLB {
+struct CompileAssert {};
+typedef CompileAssert expected_sizeof_double_neq_sizeof_bool[
+ (sizeof(double) != sizeof(bool)) ? 1 : -1];
+template<typename From> double IsBoolFlag(const From& from);
+bool IsBoolFlag(bool from);
+} // namespace fLB
+
+#define DECLARE_bool(name) DECLARE_VARIABLE(bool, B, name)
+#define DEFINE_bool(name, val, txt) \
+ namespace fLB { \
+ typedef CompileAssert FLAG_##name##_value_is_not_a_bool[ \
+ (sizeof(::fLB::IsBoolFlag(val)) != sizeof(double)) ? 1 : -1]; \
+ } \
+ DEFINE_VARIABLE(bool, B, name, val, txt)
+
+#define DECLARE_int32(name) DECLARE_VARIABLE(::google::int32, I, name)
+#define DEFINE_int32(name,val,txt) DEFINE_VARIABLE(::google::int32, I, name, val, txt)
+
+#define DECLARE_int64(name) DECLARE_VARIABLE(::google::int64, I64, name)
+#define DEFINE_int64(name,val,txt) DEFINE_VARIABLE(::google::int64, I64, name, val, txt)
+
+#define DECLARE_uint64(name) DECLARE_VARIABLE(::google::uint64, U64, name)
+#define DEFINE_uint64(name,val,txt) DEFINE_VARIABLE(::google::uint64, U64, name, val, txt)
+
+#define DECLARE_double(name) DECLARE_VARIABLE(double, D, name)
+#define DEFINE_double(name, val, txt) DEFINE_VARIABLE(double, D, name, val, txt)
+
+// Strings are trickier, because they're not a POD, so we can't
+// construct them at static-initialization time (instead they get
+// constructed at global-constructor time, which is much later). To
+// try to avoid crashes in that case, we use a char buffer to store
+// the string, which we can static-initialize, and then placement-new
+// into it later. It's not perfect, but the best we can do.
+#define DECLARE_string(name) namespace fLS { extern std::string& FLAGS_##name; } \
+ using fLS::FLAGS_##name
+
+// We need to define a var named FLAGS_no##name so people don't define
+// --string and --nostring. And we need a temporary place to put val
+// so we don't have to evaluate it twice. Two great needs that go
+// great together!
+// The weird 'using' + 'extern' inside the fLS namespace is to work around
+// an unknown compiler bug/issue with the gcc 4.2.1 on SUSE 10. See
+// http://code.google.com/p/google-gflags/issues/detail?id=20
+#define DEFINE_string(name, val, txt) \
+ namespace fLS { \
+ static union { void* align; char s[sizeof(std::string)]; } s_##name[2]; \
+ const std::string* const FLAGS_no##name = new (s_##name[0].s) std::string(val); \
+ static ::google::FlagRegisterer o_##name( \
+ #name, "string", MAYBE_STRIPPED_HELP(txt), __FILE__, \
+ s_##name[0].s, new (s_##name[1].s) std::string(*FLAGS_no##name)); \
+ extern std::string& FLAGS_##name; \
+ using fLS::FLAGS_##name; \
+ std::string& FLAGS_##name = *(reinterpret_cast<std::string*>(s_##name[0].s)); \
+ } \
+ using fLS::FLAGS_##name
+
+#endif // SWIG
+
+#endif // GOOGLE_GFLAGS_H_
diff --git a/trunk/src/third_party/gflags/gen/arch/linux/ia32/include/gflags/gflags_completions.h b/trunk/src/third_party/gflags/gen/arch/linux/ia32/include/gflags/gflags_completions.h
new file mode 100644
index 0000000..9d9ce7a
--- /dev/null
+++ b/trunk/src/third_party/gflags/gen/arch/linux/ia32/include/gflags/gflags_completions.h
@@ -0,0 +1,121 @@
+// Copyright (c) 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// ---
+// Author: Dave Nicponski
+//
+// Implement helpful bash-style command line flag completions
+//
+// ** Functional API:
+// HandleCommandLineCompletions() should be called early during
+// program startup, but after command line flag code has been
+// initialized, such as the beginning of HandleCommandLineHelpFlags().
+// It checks the value of the flag --tab_completion_word. If this
+// flag is empty, nothing happens here. If it contains a string,
+// however, then HandleCommandLineCompletions() will hijack the
+// process, attempting to identify the intention behind this
+// completion. Regardless of the outcome of this deduction, the
+// process will be terminated, similar to --helpshort flag
+// handling.
+//
+// ** Overview of Bash completions:
+// Bash can be told to programatically determine completions for the
+// current 'cursor word'. It does this by (in this case) invoking a
+// command with some additional arguments identifying the command
+// being executed, the word being completed, and the previous word
+// (if any). Bash then expects a sequence of output lines to be
+// printed to stdout. If these lines all contain a common prefix
+// longer than the cursor word, bash will replace the cursor word
+// with that common prefix, and display nothing. If there isn't such
+// a common prefix, bash will display the lines in pages using 'more'.
+//
+// ** Strategy taken for command line completions:
+// If we can deduce either the exact flag intended, or a common flag
+// prefix, we'll output exactly that. Otherwise, if information
+// must be displayed to the user, we'll take the opportunity to add
+// some helpful information beyond just the flag name (specifically,
+// we'll include the default flag value and as much of the flag's
+// description as can fit on a single terminal line width, as specified
+// by the flag --tab_completion_columns). Furthermore, we'll try to
+// make bash order the output such that the most useful or relevent
+// flags are the most likely to be shown at the top.
+//
+// ** Additional features:
+// To assist in finding that one really useful flag, substring matching
+// was implemented. Before pressing a <TAB> to get completion for the
+// current word, you can append one or more '?' to the flag to do
+// substring matching. Here's the semantics:
+// --foo<TAB> Show me all flags with names prefixed by 'foo'
+// --foo?<TAB> Show me all flags with 'foo' somewhere in the name
+// --foo??<TAB> Same as prior case, but also search in module
+// definition path for 'foo'
+// --foo???<TAB> Same as prior case, but also search in flag
+// descriptions for 'foo'
+// Finally, we'll trim the output to a relatively small number of
+// flags to keep bash quiet about the verbosity of output. If one
+// really wanted to see all possible matches, appending a '+' to the
+// search word will force the exhaustive list of matches to be printed.
+//
+// ** How to have bash accept completions from a binary:
+// Bash requires that it be informed about each command that programmatic
+// completion should be enabled for. Example addition to a .bashrc
+// file would be (your path to gflags_completions.sh file may differ):
+
+/*
+$ complete -o bashdefault -o default -o nospace -C \
+ '/usr/local/bin/gflags_completions.sh --tab_completion_columns $COLUMNS' \
+ time env binary_name another_binary [...]
+*/
+
+// This would allow the following to work:
+// $ /path/to/binary_name --vmodule<TAB>
+// Or:
+// $ ./bin/path/another_binary --gfs_u<TAB>
+// (etc)
+//
+// Sadly, it appears that bash gives no easy way to force this behavior for
+// all commands. That's where the "time" in the above example comes in.
+// If you haven't specifically added a command to the list of completion
+// supported commands, you can still get completions by prefixing the
+// entire command with "env".
+// $ env /some/brand/new/binary --vmod<TAB>
+// Assuming that "binary" is a newly compiled binary, this should still
+// produce the expected completion output.
+
+
+#ifndef GOOGLE_GFLAGS_COMPLETIONS_H_
+#define GOOGLE_GFLAGS_COMPLETIONS_H_
+
+namespace google {
+
+void HandleCommandLineCompletions(void);
+
+}
+
+#endif // GOOGLE_GFLAGS_COMPLETIONS_H_
diff --git a/trunk/src/third_party/gflags/gen/arch/linux/ia32/include/private/config.h b/trunk/src/third_party/gflags/gen/arch/linux/ia32/include/private/config.h
new file mode 100644
index 0000000..b46ed79
--- /dev/null
+++ b/trunk/src/third_party/gflags/gen/arch/linux/ia32/include/private/config.h
@@ -0,0 +1,106 @@
+/* src/config.h. Generated from config.h.in by configure. */
+/* src/config.h.in. Generated from configure.ac by autoheader. */
+
+/* Always the empty-string on non-windows systems. On windows, should be
+ "__declspec(dllexport)". This way, when we compile the dll, we export our
+ functions/classes. It's safe to define this here because config.h is only
+ used internally, to compile the DLL, and every DLL source file #includes
+ "config.h" before anything else. */
+#define GFLAGS_DLL_DECL /**/
+
+/* Namespace for Google classes */
+#define GOOGLE_NAMESPACE ::google
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#define HAVE_DLFCN_H 1
+
+/* Define to 1 if you have the <fnmatch.h> header file. */
+#define HAVE_FNMATCH_H 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* define if the compiler implements namespaces */
+#define HAVE_NAMESPACES 1
+
+/* Define if you have POSIX threads libraries and header files. */
+#define HAVE_PTHREAD 1
+
+/* Define to 1 if you have the `putenv' function. */
+#define HAVE_PUTENV 1
+
+/* Define to 1 if you have the `setenv' function. */
+#define HAVE_SETENV 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strtoll' function. */
+#define HAVE_STRTOLL 1
+
+/* Define to 1 if you have the `strtoq' function. */
+#define HAVE_STRTOQ 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* define if your compiler has __attribute__ */
+#define HAVE___ATTRIBUTE__ 1
+
+/* Name of package */
+#define PACKAGE "gflags"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "opensource@google.com"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "gflags"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "gflags 1.3"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "gflags"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "1.3"
+
+/* Define to necessary symbol if this constant uses a non-standard name on
+ your system. */
+/* #undef PTHREAD_CREATE_JOINABLE */
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* the namespace where STL code like vector<> is defined */
+#define STL_NAMESPACE std
+
+/* Version number of package */
+#define VERSION "1.3"
+
+/* Stops putting the code inside the Google namespace */
+#define _END_GOOGLE_NAMESPACE_ }
+
+/* Puts following code inside the Google namespace */
+#define _START_GOOGLE_NAMESPACE_ namespace google {
diff --git a/trunk/src/third_party/gflags/gen/arch/linux/x64/include/gflags/gflags.h b/trunk/src/third_party/gflags/gen/arch/linux/x64/include/gflags/gflags.h
new file mode 100644
index 0000000..821d194
--- /dev/null
+++ b/trunk/src/third_party/gflags/gen/arch/linux/x64/include/gflags/gflags.h
@@ -0,0 +1,538 @@
+// Copyright (c) 2006, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// ---
+// Author: Ray Sidney
+// Revamped and reorganized by Craig Silverstein
+//
+// This is the file that should be included by any file which declares
+// or defines a command line flag or wants to parse command line flags
+// or print a program usage message (which will include information about
+// flags). Executive summary, in the form of an example foo.cc file:
+//
+// #include "foo.h" // foo.h has a line "DECLARE_int32(start);"
+//
+// DEFINE_int32(end, 1000, "The last record to read");
+// DECLARE_bool(verbose); // some other file has a DEFINE_bool(verbose, ...)
+//
+// void MyFunc() {
+// if (FLAGS_verbose) printf("Records %d-%d\n", FLAGS_start, FLAGS_end);
+// }
+//
+// Then, at the command-line:
+// ./foo --noverbose --start=5 --end=100
+//
+// For more details, see
+// doc/gflags.html
+//
+// --- A note about thread-safety:
+//
+// We describe many functions in this routine as being thread-hostile,
+// thread-compatible, or thread-safe. Here are the meanings we use:
+//
+// thread-safe: it is safe for multiple threads to call this routine
+// (or, when referring to a class, methods of this class)
+// concurrently.
+// thread-hostile: it is not safe for multiple threads to call this
+// routine (or methods of this class) concurrently. In gflags,
+// most thread-hostile routines are intended to be called early in,
+// or even before, main() -- that is, before threads are spawned.
+// thread-compatible: it is safe for multiple threads to read from
+// this variable (when applied to variables), or to call const
+// methods of this class (when applied to classes), as long as no
+// other thread is writing to the variable or calling non-const
+// methods of this class.
+
+#ifndef GOOGLE_GFLAGS_H_
+#define GOOGLE_GFLAGS_H_
+
+#include <string>
+#include <vector>
+
+// We care a lot about number of bits things take up. Unfortunately,
+// systems define their bit-specific ints in a lot of different ways.
+// We use our own way, and have a typedef to get there.
+// Note: these commands below may look like "#if 1" or "#if 0", but
+// that's because they were constructed that way at ./configure time.
+// Look at gflags.h.in to see how they're calculated (based on your config).
+#if 1
+#include <stdint.h> // the normal place uint16_t is defined
+#endif
+#if 1
+#include <sys/types.h> // the normal place u_int16_t is defined
+#endif
+#if 1
+#include <inttypes.h> // a third place for uint16_t or u_int16_t
+#endif
+
+namespace google {
+
+#if 1 // the C99 format
+typedef int32_t int32;
+typedef uint32_t uint32;
+typedef int64_t int64;
+typedef uint64_t uint64;
+#elif 1 // the BSD format
+typedef int32_t int32;
+typedef u_int32_t uint32;
+typedef int64_t int64;
+typedef u_int64_t uint64;
+#elif 0 // the windows (vc7) format
+typedef __int32 int32;
+typedef unsigned __int32 uint32;
+typedef __int64 int64;
+typedef unsigned __int64 uint64;
+#else
+#error Do not know how to define a 32-bit integer quantity on your system
+#endif
+
+// --------------------------------------------------------------------
+// To actually define a flag in a file, use DEFINE_bool,
+// DEFINE_string, etc. at the bottom of this file. You may also find
+// it useful to register a validator with the flag. This ensures that
+// when the flag is parsed from the commandline, or is later set via
+// SetCommandLineOption, we call the validation function.
+//
+// The validation function should return true if the flag value is valid, and
+// false otherwise. If the function returns false for the new setting of the
+// flag, the flag will retain its current value. If it returns false for the
+// default value, InitGoogle will die.
+//
+// This function is safe to call at global construct time (as in the
+// example below).
+//
+// Example use:
+// static bool ValidatePort(const char* flagname, int32 value) {
+// if (value > 0 && value < 32768) // value is ok
+// return true;
+// printf("Invalid value for --%s: %d\n", flagname, (int)value);
+// return false;
+// }
+// DEFINE_int32(port, 0, "What port to listen on");
+// static bool dummy = RegisterFlagValidator(&FLAGS_port, &ValidatePort);
+
+// Returns true if successfully registered, false if not (because the
+// first argument doesn't point to a command-line flag, or because a
+// validator is already registered for this flag).
+bool RegisterFlagValidator(const bool* flag,
+ bool (*validate_fn)(const char*, bool));
+bool RegisterFlagValidator(const int32* flag,
+ bool (*validate_fn)(const char*, int32));
+bool RegisterFlagValidator(const int64* flag,
+ bool (*validate_fn)(const char*, int64));
+bool RegisterFlagValidator(const uint64* flag,
+ bool (*validate_fn)(const char*, uint64));
+bool RegisterFlagValidator(const double* flag,
+ bool (*validate_fn)(const char*, double));
+bool RegisterFlagValidator(const std::string* flag,
+ bool (*validate_fn)(const char*, const std::string&));
+
+
+// --------------------------------------------------------------------
+// These methods are the best way to get access to info about the
+// list of commandline flags. Note that these routines are pretty slow.
+// GetAllFlags: mostly-complete info about the list, sorted by file.
+// ShowUsageWithFlags: pretty-prints the list to stdout (what --help does)
+// ShowUsageWithFlagsRestrict: limit to filenames with restrict as a substr
+//
+// In addition to accessing flags, you can also access argv[0] (the program
+// name) and argv (the entire commandline), which we sock away a copy of.
+// These variables are static, so you should only set them once.
+
+struct CommandLineFlagInfo {
+ std::string name; // the name of the flag
+ std::string type; // the type of the flag: int32, etc
+ std::string description; // the "help text" associated with the flag
+ std::string current_value; // the current value, as a string
+ std::string default_value; // the default value, as a string
+ std::string filename; // 'cleaned' version of filename holding the flag
+ bool has_validator_fn; // true if RegisterFlagValidator called on flag
+ bool is_default; // true if the flag has default value
+};
+
+// Using this inside of a validator is a recipe for a deadlock.
+// TODO(wojtekm) Fix locking when validators are running, to make it safe to
+// call validators during ParseAllFlags.
+// Also make sure then to uncomment the corresponding unit test in
+// commandlineflags_unittest.sh
+extern void GetAllFlags(std::vector<CommandLineFlagInfo>* OUTPUT);
+// These two are actually defined in commandlineflags_reporting.cc.
+extern void ShowUsageWithFlags(const char *argv0); // what --help does
+extern void ShowUsageWithFlagsRestrict(const char *argv0, const char *restrict);
+
+// Create a descriptive string for a flag.
+// Goes to some trouble to make pretty line breaks.
+extern std::string DescribeOneFlag(const CommandLineFlagInfo& flag);
+
+// Thread-hostile; meant to be called before any threads are spawned.
+extern void SetArgv(int argc, const char** argv);
+// The following functions are thread-safe as long as SetArgv() is
+// only called before any threads start.
+extern const std::vector<std::string>& GetArgvs(); // all of argv as a vector
+extern const char* GetArgv(); // all of argv as a string
+extern const char* GetArgv0(); // only argv0
+extern uint32 GetArgvSum(); // simple checksum of argv
+extern const char* ProgramInvocationName(); // argv0, or "UNKNOWN" if not set
+extern const char* ProgramInvocationShortName(); // basename(argv0)
+// ProgramUsage() is thread-safe as long as SetUsageMessage() is only
+// called before any threads start.
+extern const char* ProgramUsage(); // string set by SetUsageMessage()
+
+
+// --------------------------------------------------------------------
+// Normally you access commandline flags by just saying "if (FLAGS_foo)"
+// or whatever, and set them by calling "FLAGS_foo = bar" (or, more
+// commonly, via the DEFINE_foo macro). But if you need a bit more
+// control, we have programmatic ways to get/set the flags as well.
+// These programmatic ways to access flags are thread-safe, but direct
+// access is only thread-compatible.
+
+// Return true iff the flagname was found.
+// OUTPUT is set to the flag's value, or unchanged if we return false.
+extern bool GetCommandLineOption(const char* name, std::string* OUTPUT);
+
+// Return true iff the flagname was found. OUTPUT is set to the flag's
+// CommandLineFlagInfo or unchanged if we return false.
+extern bool GetCommandLineFlagInfo(const char* name,
+ CommandLineFlagInfo* OUTPUT);
+
+// Return the CommandLineFlagInfo of the flagname. exit() if name not found.
+// Example usage, to check if a flag's value is currently the default value:
+// if (GetCommandLineFlagInfoOrDie("foo").is_default) ...
+extern CommandLineFlagInfo GetCommandLineFlagInfoOrDie(const char* name);
+
+enum FlagSettingMode {
+ // update the flag's value (can call this multiple times).
+ SET_FLAGS_VALUE,
+ // update the flag's value, but *only if* it has not yet been updated
+ // with SET_FLAGS_VALUE, SET_FLAG_IF_DEFAULT, or "FLAGS_xxx = nondef".
+ SET_FLAG_IF_DEFAULT,
+ // set the flag's default value to this. If the flag has not yet updated
+ // yet (via SET_FLAGS_VALUE, SET_FLAG_IF_DEFAULT, or "FLAGS_xxx = nondef")
+ // change the flag's current value to the new default value as well.
+ SET_FLAGS_DEFAULT
+};
+
+// Set a particular flag ("command line option"). Returns a string
+// describing the new value that the option has been set to. The
+// return value API is not well-specified, so basically just depend on
+// it to be empty if the setting failed for some reason -- the name is
+// not a valid flag name, or the value is not a valid value -- and
+// non-empty else.
+
+// SetCommandLineOption uses set_mode == SET_FLAGS_VALUE (the common case)
+extern std::string SetCommandLineOption(const char* name, const char* value);
+extern std::string SetCommandLineOptionWithMode(const char* name, const char* value,
+ FlagSettingMode set_mode);
+
+
+// --------------------------------------------------------------------
+// Saves the states (value, default value, whether the user has set
+// the flag, registered validators, etc) of all flags, and restores
+// them when the FlagSaver is destroyed. This is very useful in
+// tests, say, when you want to let your tests change the flags, but
+// make sure that they get reverted to the original states when your
+// test is complete.
+//
+// Example usage:
+// void TestFoo() {
+// FlagSaver s1;
+// FLAG_foo = false;
+// FLAG_bar = "some value";
+//
+// // test happens here. You can return at any time
+// // without worrying about restoring the FLAG values.
+// }
+//
+// Note: This class is marked with __attribute__((unused)) because all the
+// work is done in the constructor and destructor, so in the standard
+// usage example above, the compiler would complain that it's an
+// unused variable.
+//
+// This class is thread-safe.
+
+class FlagSaver {
+ public:
+ FlagSaver();
+ ~FlagSaver();
+
+ private:
+ class FlagSaverImpl* impl_; // we use pimpl here to keep API steady
+
+ FlagSaver(const FlagSaver&); // no copying!
+ void operator=(const FlagSaver&);
+} __attribute__ ((unused));
+
+// --------------------------------------------------------------------
+// Some deprecated or hopefully-soon-to-be-deprecated functions.
+
+// This is often used for logging. TODO(csilvers): figure out a better way
+extern std::string CommandlineFlagsIntoString();
+// Usually where this is used, a FlagSaver should be used instead.
+extern bool ReadFlagsFromString(const std::string& flagfilecontents,
+ const char* prog_name,
+ bool errors_are_fatal); // uses SET_FLAGS_VALUE
+
+// These let you manually implement --flagfile functionality.
+// DEPRECATED.
+extern bool AppendFlagsIntoFile(const std::string& filename, const char* prog_name);
+extern bool SaveCommandFlags(); // actually defined in google.cc !
+extern bool ReadFromFlagsFile(const std::string& filename, const char* prog_name,
+ bool errors_are_fatal); // uses SET_FLAGS_VALUE
+
+
+// --------------------------------------------------------------------
+// Useful routines for initializing flags from the environment.
+// In each case, if 'varname' does not exist in the environment
+// return defval. If 'varname' does exist but is not valid
+// (e.g., not a number for an int32 flag), abort with an error.
+// Otherwise, return the value. NOTE: for booleans, for true use
+// 't' or 'T' or 'true' or '1', for false 'f' or 'F' or 'false' or '0'.
+
+extern bool BoolFromEnv(const char *varname, bool defval);
+extern int32 Int32FromEnv(const char *varname, int32 defval);
+extern int64 Int64FromEnv(const char *varname, int64 defval);
+extern uint64 Uint64FromEnv(const char *varname, uint64 defval);
+extern double DoubleFromEnv(const char *varname, double defval);
+extern const char *StringFromEnv(const char *varname, const char *defval);
+
+
+// --------------------------------------------------------------------
+// The next two functions parse commandlineflags from main():
+
+// Set the "usage" message for this program. For example:
+// string usage("This program does nothing. Sample usage:\n");
+// usage += argv[0] + " <uselessarg1> <uselessarg2>";
+// SetUsageMessage(usage);
+// Do not include commandline flags in the usage: we do that for you!
+// Thread-hostile; meant to be called before any threads are spawned.
+extern void SetUsageMessage(const std::string& usage);
+
+// Looks for flags in argv and parses them. Rearranges argv to put
+// flags first, or removes them entirely if remove_flags is true.
+// If a flag is defined more than once in the command line or flag
+// file, the last definition is used.
+// See top-of-file for more details on this function.
+#ifndef SWIG // In swig, use ParseCommandLineFlagsScript() instead.
+extern uint32 ParseCommandLineFlags(int *argc, char*** argv,
+ bool remove_flags);
+#endif
+
+
+// Calls to ParseCommandLineNonHelpFlags and then to
+// HandleCommandLineHelpFlags can be used instead of a call to
+// ParseCommandLineFlags during initialization, in order to allow for
+// changing default values for some FLAGS (via
+// e.g. SetCommandLineOptionWithMode calls) between the time of
+// command line parsing and the time of dumping help information for
+// the flags as a result of command line parsing.
+// If a flag is defined more than once in the command line or flag
+// file, the last definition is used.
+extern uint32 ParseCommandLineNonHelpFlags(int *argc, char*** argv,
+ bool remove_flags);
+// This is actually defined in commandlineflags_reporting.cc.
+// This function is misnamed (it also handles --version, etc.), but
+// it's too late to change that now. :-(
+extern void HandleCommandLineHelpFlags(); // in commandlineflags_reporting.cc
+
+// Allow command line reparsing. Disables the error normally
+// generated when an unknown flag is found, since it may be found in a
+// later parse. Thread-hostile; meant to be called before any threads
+// are spawned.
+extern void AllowCommandLineReparsing();
+
+// Reparse the flags that have not yet been recognized.
+// Only flags registered since the last parse will be recognized.
+// Any flag value must be provided as part of the argument using "=",
+// not as a separate command line argument that follows the flag argument.
+// Intended for handling flags from dynamically loaded libraries,
+// since their flags are not registered until they are loaded.
+extern uint32 ReparseCommandLineNonHelpFlags();
+
+
+// --------------------------------------------------------------------
+// Now come the command line flag declaration/definition macros that
+// will actually be used. They're kind of hairy. A major reason
+// for this is initialization: we want people to be able to access
+// variables in global constructors and have that not crash, even if
+// their global constructor runs before the global constructor here.
+// (Obviously, we can't guarantee the flags will have the correct
+// default value in that case, but at least accessing them is safe.)
+// The only way to do that is have flags point to a static buffer.
+// So we make one, using a union to ensure proper alignment, and
+// then use placement-new to actually set up the flag with the
+// correct default value. In the same vein, we have to worry about
+// flag access in global destructors, so FlagRegisterer has to be
+// careful never to destroy the flag-values it constructs.
+//
+// Note that when we define a flag variable FLAGS_<name>, we also
+// preemptively define a junk variable, FLAGS_no<name>. This is to
+// cause a link-time error if someone tries to define 2 flags with
+// names like "logging" and "nologging". We do this because a bool
+// flag FLAG can be set from the command line to true with a "-FLAG"
+// argument, and to false with a "-noFLAG" argument, and so this can
+// potentially avert confusion.
+//
+// We also put flags into their own namespace. It is purposefully
+// named in an opaque way that people should have trouble typing
+// directly. The idea is that DEFINE puts the flag in the weird
+// namespace, and DECLARE imports the flag from there into the current
+// namespace. The net result is to force people to use DECLARE to get
+// access to a flag, rather than saying "extern bool FLAGS_whatever;"
+// or some such instead. We want this so we can put extra
+// functionality (like sanity-checking) in DECLARE if we want, and
+// make sure it is picked up everywhere.
+//
+// We also put the type of the variable in the namespace, so that
+// people can't DECLARE_int32 something that they DEFINE_bool'd
+// elsewhere.
+
+class FlagRegisterer {
+ public:
+ FlagRegisterer(const char* name, const char* type,
+ const char* help, const char* filename,
+ void* current_storage, void* defvalue_storage);
+};
+
+extern bool FlagsTypeWarn(const char *name);
+
+// If your application #defines STRIP_FLAG_HELP to a non-zero value
+// before #including this file, we remove the help message from the
+// binary file. This can reduce the size of the resulting binary
+// somewhat, and may also be useful for security reasons.
+
+extern const char kStrippedFlagHelp[];
+
+}
+
+#ifndef SWIG // In swig, ignore the main flag declarations
+
+#if defined(STRIP_FLAG_HELP) && STRIP_FLAG_HELP > 0
+// Need this construct to avoid the 'defined but not used' warning.
+#define MAYBE_STRIPPED_HELP(txt) (false ? (txt) : kStrippedFlagHelp)
+#else
+#define MAYBE_STRIPPED_HELP(txt) txt
+#endif
+
+// Each command-line flag has two variables associated with it: one
+// with the current value, and one with the default value. However,
+// we have a third variable, which is where value is assigned; it's a
+// constant. This guarantees that FLAG_##value is initialized at
+// static initialization time (e.g. before program-start) rather than
+// than global construction time (which is after program-start but
+// before main), at least when 'value' is a compile-time constant. We
+// use a small trick for the "default value" variable, and call it
+// FLAGS_no<name>. This serves the second purpose of assuring a
+// compile error if someone tries to define a flag named no<name>
+// which is illegal (--foo and --nofoo both affect the "foo" flag).
+#define DEFINE_VARIABLE(type, shorttype, name, value, help) \
+ namespace fL##shorttype { \
+ static const type FLAGS_nono##name = value; \
+ type FLAGS_##name = FLAGS_nono##name; \
+ type FLAGS_no##name = FLAGS_nono##name; \
+ static ::google::FlagRegisterer o_##name( \
+ #name, #type, MAYBE_STRIPPED_HELP(help), __FILE__, \
+ &FLAGS_##name, &FLAGS_no##name); \
+ } \
+ using fL##shorttype::FLAGS_##name
+
+#define DECLARE_VARIABLE(type, shorttype, name) \
+ namespace fL##shorttype { \
+ extern type FLAGS_##name; \
+ } \
+ using fL##shorttype::FLAGS_##name
+
+// For DEFINE_bool, we want to do the extra check that the passed-in
+// value is actually a bool, and not a string or something that can be
+// coerced to a bool. These declarations (no definition needed!) will
+// help us do that, and never evaluate From, which is important.
+// We'll use 'sizeof(IsBool(val))' to distinguish. This code requires
+// that the compiler have different sizes for bool & double. Since
+// this is not guaranteed by the standard, we check it with a
+// compile-time assert (msg[-1] will give a compile-time error).
+namespace fLB {
+struct CompileAssert {};
+typedef CompileAssert expected_sizeof_double_neq_sizeof_bool[
+ (sizeof(double) != sizeof(bool)) ? 1 : -1];
+template<typename From> double IsBoolFlag(const From& from);
+bool IsBoolFlag(bool from);
+} // namespace fLB
+
+#define DECLARE_bool(name) DECLARE_VARIABLE(bool, B, name)
+#define DEFINE_bool(name, val, txt) \
+ namespace fLB { \
+ typedef CompileAssert FLAG_##name##_value_is_not_a_bool[ \
+ (sizeof(::fLB::IsBoolFlag(val)) != sizeof(double)) ? 1 : -1]; \
+ } \
+ DEFINE_VARIABLE(bool, B, name, val, txt)
+
+#define DECLARE_int32(name) DECLARE_VARIABLE(::google::int32, I, name)
+#define DEFINE_int32(name,val,txt) DEFINE_VARIABLE(::google::int32, I, name, val, txt)
+
+#define DECLARE_int64(name) DECLARE_VARIABLE(::google::int64, I64, name)
+#define DEFINE_int64(name,val,txt) DEFINE_VARIABLE(::google::int64, I64, name, val, txt)
+
+#define DECLARE_uint64(name) DECLARE_VARIABLE(::google::uint64, U64, name)
+#define DEFINE_uint64(name,val,txt) DEFINE_VARIABLE(::google::uint64, U64, name, val, txt)
+
+#define DECLARE_double(name) DECLARE_VARIABLE(double, D, name)
+#define DEFINE_double(name, val, txt) DEFINE_VARIABLE(double, D, name, val, txt)
+
+// Strings are trickier, because they're not a POD, so we can't
+// construct them at static-initialization time (instead they get
+// constructed at global-constructor time, which is much later). To
+// try to avoid crashes in that case, we use a char buffer to store
+// the string, which we can static-initialize, and then placement-new
+// into it later. It's not perfect, but the best we can do.
+#define DECLARE_string(name) namespace fLS { extern std::string& FLAGS_##name; } \
+ using fLS::FLAGS_##name
+
+// We need to define a var named FLAGS_no##name so people don't define
+// --string and --nostring. And we need a temporary place to put val
+// so we don't have to evaluate it twice. Two great needs that go
+// great together!
+// The weird 'using' + 'extern' inside the fLS namespace is to work around
+// an unknown compiler bug/issue with the gcc 4.2.1 on SUSE 10. See
+// http://code.google.com/p/google-gflags/issues/detail?id=20
+#define DEFINE_string(name, val, txt) \
+ namespace fLS { \
+ static union { void* align; char s[sizeof(std::string)]; } s_##name[2]; \
+ const std::string* const FLAGS_no##name = new (s_##name[0].s) std::string(val); \
+ static ::google::FlagRegisterer o_##name( \
+ #name, "string", MAYBE_STRIPPED_HELP(txt), __FILE__, \
+ s_##name[0].s, new (s_##name[1].s) std::string(*FLAGS_no##name)); \
+ extern std::string& FLAGS_##name; \
+ using fLS::FLAGS_##name; \
+ std::string& FLAGS_##name = *(reinterpret_cast<std::string*>(s_##name[0].s)); \
+ } \
+ using fLS::FLAGS_##name
+
+#endif // SWIG
+
+#endif // GOOGLE_GFLAGS_H_
diff --git a/trunk/src/third_party/gflags/gen/arch/linux/x64/include/gflags/gflags_completions.h b/trunk/src/third_party/gflags/gen/arch/linux/x64/include/gflags/gflags_completions.h
new file mode 100644
index 0000000..9d9ce7a
--- /dev/null
+++ b/trunk/src/third_party/gflags/gen/arch/linux/x64/include/gflags/gflags_completions.h
@@ -0,0 +1,121 @@
+// Copyright (c) 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// ---
+// Author: Dave Nicponski
+//
+// Implement helpful bash-style command line flag completions
+//
+// ** Functional API:
+// HandleCommandLineCompletions() should be called early during
+// program startup, but after command line flag code has been
+// initialized, such as the beginning of HandleCommandLineHelpFlags().
+// It checks the value of the flag --tab_completion_word. If this
+// flag is empty, nothing happens here. If it contains a string,
+// however, then HandleCommandLineCompletions() will hijack the
+// process, attempting to identify the intention behind this
+// completion. Regardless of the outcome of this deduction, the
+// process will be terminated, similar to --helpshort flag
+// handling.
+//
+// ** Overview of Bash completions:
+// Bash can be told to programatically determine completions for the
+// current 'cursor word'. It does this by (in this case) invoking a
+// command with some additional arguments identifying the command
+// being executed, the word being completed, and the previous word
+// (if any). Bash then expects a sequence of output lines to be
+// printed to stdout. If these lines all contain a common prefix
+// longer than the cursor word, bash will replace the cursor word
+// with that common prefix, and display nothing. If there isn't such
+// a common prefix, bash will display the lines in pages using 'more'.
+//
+// ** Strategy taken for command line completions:
+// If we can deduce either the exact flag intended, or a common flag
+// prefix, we'll output exactly that. Otherwise, if information
+// must be displayed to the user, we'll take the opportunity to add
+// some helpful information beyond just the flag name (specifically,
+// we'll include the default flag value and as much of the flag's
+// description as can fit on a single terminal line width, as specified
+// by the flag --tab_completion_columns). Furthermore, we'll try to
+// make bash order the output such that the most useful or relevent
+// flags are the most likely to be shown at the top.
+//
+// ** Additional features:
+// To assist in finding that one really useful flag, substring matching
+// was implemented. Before pressing a <TAB> to get completion for the
+// current word, you can append one or more '?' to the flag to do
+// substring matching. Here's the semantics:
+// --foo<TAB> Show me all flags with names prefixed by 'foo'
+// --foo?<TAB> Show me all flags with 'foo' somewhere in the name
+// --foo??<TAB> Same as prior case, but also search in module
+// definition path for 'foo'
+// --foo???<TAB> Same as prior case, but also search in flag
+// descriptions for 'foo'
+// Finally, we'll trim the output to a relatively small number of
+// flags to keep bash quiet about the verbosity of output. If one
+// really wanted to see all possible matches, appending a '+' to the
+// search word will force the exhaustive list of matches to be printed.
+//
+// ** How to have bash accept completions from a binary:
+// Bash requires that it be informed about each command that programmatic
+// completion should be enabled for. Example addition to a .bashrc
+// file would be (your path to gflags_completions.sh file may differ):
+
+/*
+$ complete -o bashdefault -o default -o nospace -C \
+ '/usr/local/bin/gflags_completions.sh --tab_completion_columns $COLUMNS' \
+ time env binary_name another_binary [...]
+*/
+
+// This would allow the following to work:
+// $ /path/to/binary_name --vmodule<TAB>
+// Or:
+// $ ./bin/path/another_binary --gfs_u<TAB>
+// (etc)
+//
+// Sadly, it appears that bash gives no easy way to force this behavior for
+// all commands. That's where the "time" in the above example comes in.
+// If you haven't specifically added a command to the list of completion
+// supported commands, you can still get completions by prefixing the
+// entire command with "env".
+// $ env /some/brand/new/binary --vmod<TAB>
+// Assuming that "binary" is a newly compiled binary, this should still
+// produce the expected completion output.
+
+
+#ifndef GOOGLE_GFLAGS_COMPLETIONS_H_
+#define GOOGLE_GFLAGS_COMPLETIONS_H_
+
+namespace google {
+
+void HandleCommandLineCompletions(void);
+
+}
+
+#endif // GOOGLE_GFLAGS_COMPLETIONS_H_
diff --git a/trunk/src/third_party/gflags/gen/arch/linux/x64/include/private/config.h b/trunk/src/third_party/gflags/gen/arch/linux/x64/include/private/config.h
new file mode 100644
index 0000000..b46ed79
--- /dev/null
+++ b/trunk/src/third_party/gflags/gen/arch/linux/x64/include/private/config.h
@@ -0,0 +1,106 @@
+/* src/config.h. Generated from config.h.in by configure. */
+/* src/config.h.in. Generated from configure.ac by autoheader. */
+
+/* Always the empty-string on non-windows systems. On windows, should be
+ "__declspec(dllexport)". This way, when we compile the dll, we export our
+ functions/classes. It's safe to define this here because config.h is only
+ used internally, to compile the DLL, and every DLL source file #includes
+ "config.h" before anything else. */
+#define GFLAGS_DLL_DECL /**/
+
+/* Namespace for Google classes */
+#define GOOGLE_NAMESPACE ::google
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#define HAVE_DLFCN_H 1
+
+/* Define to 1 if you have the <fnmatch.h> header file. */
+#define HAVE_FNMATCH_H 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* define if the compiler implements namespaces */
+#define HAVE_NAMESPACES 1
+
+/* Define if you have POSIX threads libraries and header files. */
+#define HAVE_PTHREAD 1
+
+/* Define to 1 if you have the `putenv' function. */
+#define HAVE_PUTENV 1
+
+/* Define to 1 if you have the `setenv' function. */
+#define HAVE_SETENV 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strtoll' function. */
+#define HAVE_STRTOLL 1
+
+/* Define to 1 if you have the `strtoq' function. */
+#define HAVE_STRTOQ 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* define if your compiler has __attribute__ */
+#define HAVE___ATTRIBUTE__ 1
+
+/* Name of package */
+#define PACKAGE "gflags"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "opensource@google.com"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "gflags"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "gflags 1.3"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "gflags"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "1.3"
+
+/* Define to necessary symbol if this constant uses a non-standard name on
+ your system. */
+/* #undef PTHREAD_CREATE_JOINABLE */
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* the namespace where STL code like vector<> is defined */
+#define STL_NAMESPACE std
+
+/* Version number of package */
+#define VERSION "1.3"
+
+/* Stops putting the code inside the Google namespace */
+#define _END_GOOGLE_NAMESPACE_ }
+
+/* Puts following code inside the Google namespace */
+#define _START_GOOGLE_NAMESPACE_ namespace google {
diff --git a/trunk/src/third_party/gflags/gen/arch/linux/x64/src/config.h b/trunk/src/third_party/gflags/gen/arch/linux/x64/src/config.h
new file mode 100644
index 0000000..b46ed79
--- /dev/null
+++ b/trunk/src/third_party/gflags/gen/arch/linux/x64/src/config.h
@@ -0,0 +1,106 @@
+/* src/config.h. Generated from config.h.in by configure. */
+/* src/config.h.in. Generated from configure.ac by autoheader. */
+
+/* Always the empty-string on non-windows systems. On windows, should be
+ "__declspec(dllexport)". This way, when we compile the dll, we export our
+ functions/classes. It's safe to define this here because config.h is only
+ used internally, to compile the DLL, and every DLL source file #includes
+ "config.h" before anything else. */
+#define GFLAGS_DLL_DECL /**/
+
+/* Namespace for Google classes */
+#define GOOGLE_NAMESPACE ::google
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#define HAVE_DLFCN_H 1
+
+/* Define to 1 if you have the <fnmatch.h> header file. */
+#define HAVE_FNMATCH_H 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* define if the compiler implements namespaces */
+#define HAVE_NAMESPACES 1
+
+/* Define if you have POSIX threads libraries and header files. */
+#define HAVE_PTHREAD 1
+
+/* Define to 1 if you have the `putenv' function. */
+#define HAVE_PUTENV 1
+
+/* Define to 1 if you have the `setenv' function. */
+#define HAVE_SETENV 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strtoll' function. */
+#define HAVE_STRTOLL 1
+
+/* Define to 1 if you have the `strtoq' function. */
+#define HAVE_STRTOQ 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* define if your compiler has __attribute__ */
+#define HAVE___ATTRIBUTE__ 1
+
+/* Name of package */
+#define PACKAGE "gflags"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "opensource@google.com"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "gflags"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "gflags 1.3"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "gflags"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "1.3"
+
+/* Define to necessary symbol if this constant uses a non-standard name on
+ your system. */
+/* #undef PTHREAD_CREATE_JOINABLE */
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* the namespace where STL code like vector<> is defined */
+#define STL_NAMESPACE std
+
+/* Version number of package */
+#define VERSION "1.3"
+
+/* Stops putting the code inside the Google namespace */
+#define _END_GOOGLE_NAMESPACE_ }
+
+/* Puts following code inside the Google namespace */
+#define _START_GOOGLE_NAMESPACE_ namespace google {
diff --git a/trunk/src/third_party/gflags/gflags.diff b/trunk/src/third_party/gflags/gflags.diff
new file mode 100644
index 0000000..aeab940
--- /dev/null
+++ b/trunk/src/third_party/gflags/gflags.diff
@@ -0,0 +1,64 @@
+Index: gflags.cc
+===================================================================
+--- gflags.cc (revision 42)
++++ gflags.cc (working copy)
+@@ -275,6 +275,10 @@
+ }
+
+ FlagValue::~FlagValue() {
++ /*
++ This class doesn't really own its value_buffer. I suspect that
++ this destructor is never called, otherwise it would surely crash.
++
+ switch (type_) {
+ case FV_BOOL: delete reinterpret_cast<bool*>(value_buffer_); break;
+ case FV_INT32: delete reinterpret_cast<int32*>(value_buffer_); break;
+@@ -283,6 +287,7 @@
+ case FV_DOUBLE: delete reinterpret_cast<double*>(value_buffer_); break;
+ case FV_STRING: delete reinterpret_cast<string*>(value_buffer_); break;
+ }
++ */
+ }
+
+ bool FlagValue::ParseFrom(const char* value) {
+@@ -611,6 +616,12 @@
+ class FlagRegistry {
+ public:
+ FlagRegistry() { }
++ ~FlagRegistry() {
++ for (FlagMap::iterator p = flags_.begin(), e = flags_.end(); p != e; ++p) {
++ CommandLineFlag* flag = p->second;
++ delete flag;
++ }
++ }
+
+ void Lock() { lock_.Lock(); }
+ void Unlock() { lock_.Unlock(); }
+@@ -642,6 +653,12 @@
+ FlagSettingMode set_mode, string* msg);
+
+ static FlagRegistry* GlobalRegistry(); // returns a singleton registry
++ static void Cleanup() {
++ if (global_registry_ != NULL) {
++ delete global_registry_;
++ global_registry_ = NULL;
++ }
++ }
+
+ private:
+ friend class GOOGLE_NAMESPACE::FlagSaverImpl; // reads all the flags in order to copy them
+@@ -679,6 +696,14 @@
+ return global_registry_;
+ }
+
++// It would be preferable to provide a cleanup method, rather than using
++// a static destructor, but this is easier to patch in.
++class CleanupGlobalRegistry {
++ public:
++ ~CleanupGlobalRegistry() { FlagRegistry::Cleanup(); }
++};
++CleanupGlobalRegistry cleanup_global_registry;
++
+ void FlagRegistry::RegisterFlag(CommandLineFlag* flag) {
+ Lock();
+ pair<FlagIterator, bool> ins =
diff --git a/trunk/src/third_party/gflags/gflags.gyp b/trunk/src/third_party/gflags/gflags.gyp
new file mode 100644
index 0000000..9c6dff6
--- /dev/null
+++ b/trunk/src/third_party/gflags/gflags.gyp
@@ -0,0 +1,42 @@
+# 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.
+
+{
+ 'variables': {
+ 'gflags_root': '<(DEPTH)/third_party/gflags',
+ 'gflags_gen_arch_root': '<(gflags_root)/gen/arch/<(OS)/<(target_arch)',
+ },
+ 'targets': [
+ {
+ 'target_name': 'gflags',
+ 'type': '<(library)',
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(gflags_gen_arch_root)/include', # For configured files.
+ '<(gflags_root)/src', # For everything else.
+ ],
+ },
+ 'include_dirs': [
+ '<(gflags_gen_arch_root)/include/private', # For config.h
+ '<(gflags_gen_arch_root)/include', # For configured files.
+ '<(gflags_root)/src', # For everything else.
+ ],
+ 'sources': [
+ 'src/gflags.cc',
+ 'src/gflags_completions.cc',
+ 'src/gflags_reporting.cc',
+ ],
+ },
+ ],
+}
diff --git a/trunk/src/third_party/google-sparsehash/gen/arch/linux/ia32/include/google/sparsehash/sparseconfig.h b/trunk/src/third_party/google-sparsehash/gen/arch/linux/ia32/include/google/sparsehash/sparseconfig.h
new file mode 100644
index 0000000..f397d3b
--- /dev/null
+++ b/trunk/src/third_party/google-sparsehash/gen/arch/linux/ia32/include/google/sparsehash/sparseconfig.h
@@ -0,0 +1,49 @@
+/*
+ * NOTE: This file is for internal use only.
+ * Do not use these #defines in your own program!
+ */
+
+/* Namespace for Google classes */
+#define GOOGLE_NAMESPACE ::google
+
+/* the location of the header defining hash functions */
+#define HASH_FUN_H <tr1/functional>
+
+/* the namespace of the hash<> function */
+#define HASH_NAMESPACE std::tr1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if the system has the type `long long'. */
+#define HAVE_LONG_LONG 1
+
+/* Define to 1 if you have the `memcpy' function. */
+#define HAVE_MEMCPY 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if the system has the type `uint16_t'. */
+#define HAVE_UINT16_T 1
+
+/* Define to 1 if the system has the type `u_int16_t'. */
+#define HAVE_U_INT16_T 1
+
+/* Define to 1 if the system has the type `__uint16'. */
+/* #undef HAVE___UINT16 */
+
+/* The system-provided hash function including the namespace. */
+#define SPARSEHASH_HASH HASH_NAMESPACE::hash
+
+/* the namespace where STL code like vector<> is defined */
+#define STL_NAMESPACE std
+
+/* Stops putting the code inside the Google namespace */
+#define _END_GOOGLE_NAMESPACE_ }
+
+/* Puts following code inside the Google namespace */
+#define _START_GOOGLE_NAMESPACE_ namespace google {
diff --git a/trunk/src/third_party/google-sparsehash/gen/arch/linux/x64/include/google/sparsehash/sparseconfig.h b/trunk/src/third_party/google-sparsehash/gen/arch/linux/x64/include/google/sparsehash/sparseconfig.h
new file mode 100644
index 0000000..f397d3b
--- /dev/null
+++ b/trunk/src/third_party/google-sparsehash/gen/arch/linux/x64/include/google/sparsehash/sparseconfig.h
@@ -0,0 +1,49 @@
+/*
+ * NOTE: This file is for internal use only.
+ * Do not use these #defines in your own program!
+ */
+
+/* Namespace for Google classes */
+#define GOOGLE_NAMESPACE ::google
+
+/* the location of the header defining hash functions */
+#define HASH_FUN_H <tr1/functional>
+
+/* the namespace of the hash<> function */
+#define HASH_NAMESPACE std::tr1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if the system has the type `long long'. */
+#define HAVE_LONG_LONG 1
+
+/* Define to 1 if you have the `memcpy' function. */
+#define HAVE_MEMCPY 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if the system has the type `uint16_t'. */
+#define HAVE_UINT16_T 1
+
+/* Define to 1 if the system has the type `u_int16_t'. */
+#define HAVE_U_INT16_T 1
+
+/* Define to 1 if the system has the type `__uint16'. */
+/* #undef HAVE___UINT16 */
+
+/* The system-provided hash function including the namespace. */
+#define SPARSEHASH_HASH HASH_NAMESPACE::hash
+
+/* the namespace where STL code like vector<> is defined */
+#define STL_NAMESPACE std
+
+/* Stops putting the code inside the Google namespace */
+#define _END_GOOGLE_NAMESPACE_ }
+
+/* Puts following code inside the Google namespace */
+#define _START_GOOGLE_NAMESPACE_ namespace google {
diff --git a/trunk/src/third_party/google-sparsehash/google-sparsehash.gyp b/trunk/src/third_party/google-sparsehash/google-sparsehash.gyp
new file mode 100644
index 0000000..4df3352
--- /dev/null
+++ b/trunk/src/third_party/google-sparsehash/google-sparsehash.gyp
@@ -0,0 +1,32 @@
+# 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.
+
+{
+ 'variables': {
+ 'sparsehash_root': '<(DEPTH)/third_party/google-sparsehash',
+ 'sparsehash_gen_arch_root': '<(sparsehash_root)/gen/arch/<(OS)/<(target_arch)',
+ },
+ 'targets': [
+ {
+ 'target_name': 'include',
+ 'type': 'none',
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(sparsehash_gen_arch_root)/include', # For sparseconfig.h
+ '<(sparsehash_root)/src', # For everything else.
+ ],
+ },
+ },
+ ],
+}
diff --git a/trunk/src/third_party/libpng/LICENSE b/trunk/src/third_party/libpng/LICENSE
new file mode 100644
index 0000000..e5561c2
--- /dev/null
+++ b/trunk/src/third_party/libpng/LICENSE
@@ -0,0 +1,111 @@
+
+This copy of the libpng notices is provided for your convenience. In case of
+any discrepancy between this copy and the notices in the file png.h that is
+included in the libpng distribution, the latter shall prevail.
+
+COPYRIGHT NOTICE, DISCLAIMER, and LICENSE:
+
+If you modify libpng you may insert additional notices immediately following
+this sentence.
+
+This code is released under the libpng license.
+
+libpng versions 1.2.6, August 15, 2004, through 1.2.44, June 26, 2010, are
+Copyright (c) 2004, 2006-2009 Glenn Randers-Pehrson, and are
+distributed according to the same disclaimer and license as libpng-1.2.5
+with the following individual added to the list of Contributing Authors
+
+ Cosmin Truta
+
+libpng versions 1.0.7, July 1, 2000, through 1.2.5 - October 3, 2002, are
+Copyright (c) 2000-2002 Glenn Randers-Pehrson, and are
+distributed according to the same disclaimer and license as libpng-1.0.6
+with the following individuals added to the list of Contributing Authors
+
+ Simon-Pierre Cadieux
+ Eric S. Raymond
+ Gilles Vollant
+
+and with the following additions to the disclaimer:
+
+ There is no warranty against interference with your enjoyment of the
+ library or against infringement. There is no warranty that our
+ efforts or the library will fulfill any of your particular purposes
+ or needs. This library is provided with all faults, and the entire
+ risk of satisfactory quality, performance, accuracy, and effort is with
+ the user.
+
+libpng versions 0.97, January 1998, through 1.0.6, March 20, 2000, are
+Copyright (c) 1998, 1999 Glenn Randers-Pehrson, and are
+distributed according to the same disclaimer and license as libpng-0.96,
+with the following individuals added to the list of Contributing Authors:
+
+ Tom Lane
+ Glenn Randers-Pehrson
+ Willem van Schaik
+
+libpng versions 0.89, June 1996, through 0.96, May 1997, are
+Copyright (c) 1996, 1997 Andreas Dilger
+Distributed according to the same disclaimer and license as libpng-0.88,
+with the following individuals added to the list of Contributing Authors:
+
+ John Bowler
+ Kevin Bracey
+ Sam Bushell
+ Magnus Holmgren
+ Greg Roelofs
+ Tom Tanner
+
+libpng versions 0.5, May 1995, through 0.88, January 1996, are
+Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.
+
+For the purposes of this copyright and license, "Contributing Authors"
+is defined as the following set of individuals:
+
+ Andreas Dilger
+ Dave Martindale
+ Guy Eric Schalnat
+ Paul Schmidt
+ Tim Wegner
+
+The PNG Reference Library is supplied "AS IS". The Contributing Authors
+and Group 42, Inc. disclaim all warranties, expressed or implied,
+including, without limitation, the warranties of merchantability and of
+fitness for any purpose. The Contributing Authors and Group 42, Inc.
+assume no liability for direct, indirect, incidental, special, exemplary,
+or consequential damages, which may result from the use of the PNG
+Reference Library, even if advised of the possibility of such damage.
+
+Permission is hereby granted to use, copy, modify, and distribute this
+source code, or portions hereof, for any purpose, without fee, subject
+to the following restrictions:
+
+1. The origin of this source code must not be misrepresented.
+
+2. Altered versions must be plainly marked as such and must not
+ be misrepresented as being the original source.
+
+3. This Copyright notice may not be removed or altered from any
+ source or altered source distribution.
+
+The Contributing Authors and Group 42, Inc. specifically permit, without
+fee, and encourage the use of this source code as a component to
+supporting the PNG file format in commercial products. If you use this
+source code in a product, acknowledgment is not required but would be
+appreciated.
+
+
+A "png_get_copyright" function is available, for convenient use in "about"
+boxes and the like:
+
+ printf("%s",png_get_copyright(NULL));
+
+Also, the PNG logo (in PNG format, of course) is supplied in the
+files "pngbar.png" and "pngbar.jpg (88x31) and "pngnow.png" (98x31).
+
+Libpng is OSI Certified Open Source Software. OSI Certified Open Source is a
+certification mark of the Open Source Initiative.
+
+Glenn Randers-Pehrson
+glennrp at users.sourceforge.net
+June 26, 2010
diff --git a/trunk/src/third_party/libpng/README b/trunk/src/third_party/libpng/README
new file mode 100644
index 0000000..ff7ac1f
--- /dev/null
+++ b/trunk/src/third_party/libpng/README
@@ -0,0 +1,275 @@
+README for libpng version 1.2.44 - June 26, 2010 (shared library 12.0)
+See the note about version numbers near the top of png.h
+
+See INSTALL for instructions on how to install libpng.
+
+Libpng comes in several distribution formats. Get libpng-*.tar.gz,
+libpng-*.tar.xz, or libpng-*.tar.bz2 if you want UNIX-style line
+endings in the text files, or lpng*.7z or lpng*.zip if you want DOS-style
+line endings. You can get UNIX-style line endings from the *.zip file
+by using "unzip -a" but there seems to be no simple way to recover
+UNIX-style line endings from the *.7z file. The *.tar.xz file is
+recommended for *NIX users instead.
+
+Version 0.89 was the first official release of libpng. Don't let the
+fact that it's the first release fool you. The libpng library has been in
+extensive use and testing since mid-1995. By late 1997 it had
+finally gotten to the stage where there hadn't been significant
+changes to the API in some time, and people have a bad feeling about
+libraries with versions < 1.0. Version 1.0.0 was released in
+March 1998.
+
+****
+Note that some of the changes to the png_info structure render this
+version of the library binary incompatible with libpng-0.89 or
+earlier versions if you are using a shared library. The type of the
+"filler" parameter for png_set_filler() has changed from png_byte to
+png_uint_32, which will affect shared-library applications that use
+this function.
+
+To avoid problems with changes to the internals of png_info_struct,
+new APIs have been made available in 0.95 to avoid direct application
+access to info_ptr. These functions are the png_set_<chunk> and
+png_get_<chunk> functions. These functions should be used when
+accessing/storing the info_struct data, rather than manipulating it
+directly, to avoid such problems in the future.
+
+It is important to note that the APIs do not make current programs
+that access the info struct directly incompatible with the new
+library. However, it is strongly suggested that new programs use
+the new APIs (as shown in example.c and pngtest.c), and older programs
+be converted to the new format, to facilitate upgrades in the future.
+****
+
+Additions since 0.90 include the ability to compile libpng as a
+Windows DLL, and new APIs for accessing data in the info struct.
+Experimental functions include the ability to set weighting and cost
+factors for row filter selection, direct reads of integers from buffers
+on big-endian processors that support misaligned data access, faster
+methods of doing alpha composition, and more accurate 16->8 bit color
+conversion.
+
+The additions since 0.89 include the ability to read from a PNG stream
+which has had some (or all) of the signature bytes read by the calling
+application. This also allows the reading of embedded PNG streams that
+do not have the PNG file signature. As well, it is now possible to set
+the library action on the detection of chunk CRC errors. It is possible
+to set different actions based on whether the CRC error occurred in a
+critical or an ancillary chunk.
+
+The changes made to the library, and bugs fixed are based on discussions
+on the png-mng-implement mailing list and not on material submitted
+privately to Guy, Andreas, or Glenn. They will forward any good
+suggestions to the list.
+
+For a detailed description on using libpng, read libpng.txt. For
+examples of libpng in a program, see example.c and pngtest.c. For usage
+information and restrictions (what little they are) on libpng, see
+png.h. For a description on using zlib (the compression library used by
+libpng) and zlib's restrictions, see zlib.h
+
+I have included a general makefile, as well as several machine and
+compiler specific ones, but you may have to modify one for your own needs.
+
+You should use zlib 1.0.4 or later to run this, but it MAY work with
+versions as old as zlib 0.95. Even so, there are bugs in older zlib
+versions which can cause the output of invalid compression streams for
+some images. You will definitely need zlib 1.0.4 or later if you are
+taking advantage of the MS-DOS "far" structure allocation for the small
+and medium memory models. You should also note that zlib is a
+compression library that is useful for more things than just PNG files.
+You can use zlib as a drop-in replacement for fread() and fwrite() if
+you are so inclined.
+
+zlib should be available at the same place that libpng is, or at
+ftp://ftp.simplesystems.org/pub/png/src/
+
+You may also want a copy of the PNG specification. It is available
+as an RFC, a W3C Recommendation, and an ISO/IEC Standard. You can find
+these at http://www.libpng.org/pub/png/pngdocs.html
+
+This code is currently being archived at libpng.sf.net in the
+[DOWNLOAD] area, and on CompuServe, Lib 20 (PNG SUPPORT)
+at GO GRAPHSUP. If you can't find it in any of those places,
+e-mail me, and I'll help you find it.
+
+If you have any code changes, requests, problems, etc., please e-mail
+them to me. Also, I'd appreciate any make files or project files,
+and any modifications you needed to make to get libpng to compile,
+along with a #define variable to tell what compiler/system you are on.
+If you needed to add transformations to libpng, or wish libpng would
+provide the image in a different way, drop me a note (and code, if
+possible), so I can consider supporting the transformation.
+Finally, if you get any warning messages when compiling libpng
+(note: not zlib), and they are easy to fix, I'd appreciate the
+fix. Please mention "libpng" somewhere in the subject line. Thanks.
+
+This release was created and will be supported by myself (of course
+based in a large way on Guy's and Andreas' earlier work), and the PNG
+development group.
+
+Send comments/corrections/commendations to png-mng-implement at lists.sf.net
+(subscription required; visit
+https://lists.sourceforge.net/lists/listinfo/png-mng-implement
+to subscribe) or to glennrp at users.sourceforge.net
+
+You can't reach Guy, the original libpng author, at the addresses
+given in previous versions of this document. He and Andreas will
+read mail addressed to the png-mng-implement list, however.
+
+Please do not send general questions about PNG. Send them to
+the (png-mng-misc at lists.sourceforge.net, subscription required, visit
+https://lists.sourceforge.net/lists/listinfo/png-mng-misc to
+subscribe). On the other hand, please do not send libpng questions to
+that address, send them to me or to the png-mng-implement list. I'll
+get them in the end anyway. If you have a question about something
+in the PNG specification that is related to using libpng, send it
+to me. Send me any questions that start with "I was using libpng,
+and ...". If in doubt, send questions to me. I'll bounce them
+to others, if necessary.
+
+Please do not send suggestions on how to change PNG. We have
+been discussing PNG for twelve years now, and it is official and
+finished. If you have suggestions for libpng, however, I'll
+gladly listen. Even if your suggestion is not used immediately,
+it may be used later.
+
+Files in this distribution:
+
+ ANNOUNCE => Announcement of this version, with recent changes
+ CHANGES => Description of changes between libpng versions
+ KNOWNBUG => List of known bugs and deficiencies
+ LICENSE => License to use and redistribute libpng
+ README => This file
+ TODO => Things not implemented in the current library
+ Y2KINFO => Statement of Y2K compliance
+ example.c => Example code for using libpng functions
+ libpng-*-*-diff.txt => Diff from previous release
+ libpng.3 => manual page for libpng (includes libpng.txt)
+ libpng.txt => Description of libpng and its functions
+ libpngpf.3 => manual page for libpng's private functions
+ png.5 => manual page for the PNG format
+ png.c => Basic interface functions common to library
+ png.h => Library function and interface declarations
+ pngconf.h => System specific library configuration
+ pngerror.c => Error/warning message I/O functions
+ pngget.c => Functions for retrieving info from struct
+ pngmem.c => Memory handling functions
+ pngbar.png => PNG logo, 88x31
+ pngnow.png => PNG logo, 98x31
+ pngpread.c => Progressive reading functions
+ pngread.c => Read data/helper high-level functions
+ pngrio.c => Lowest-level data read I/O functions
+ pngrtran.c => Read data transformation functions
+ pngrutil.c => Read data utility functions
+ pngset.c => Functions for storing data into the info_struct
+ pngtest.c => Library test program
+ pngtest.png => Library test sample image
+ pngtrans.c => Common data transformation functions
+ pngwio.c => Lowest-level write I/O functions
+ pngwrite.c => High-level write functions
+ pngwtran.c => Write data transformations
+ pngwutil.c => Write utility functions
+ contrib => Contributions
+ gregbook => source code for PNG reading and writing, from
+ Greg Roelofs' "PNG: The Definitive Guide",
+ O'Reilly, 1999
+ msvctest => Builds and runs pngtest using a MSVC workspace
+ pngminim => Simple pnm2pngm and png2pnmm programs
+ pngminus => Simple pnm2png and png2pnm programs
+ pngsuite => Test images
+ visupng => Contains a MSVC workspace for VisualPng
+ projects => Contains project files and workspaces for
+ building a DLL
+ beos => Contains a Beos workspace for building libpng
+ c5builder => Contains a Borland workspace for building
+ libpng and zlib
+ netware.txt => Contains instructions for downloading a set
+ of project files for building libpng and
+ zlib on Netware.
+ visualc6 => Contains a Microsoft Visual C++ (MSVC)
+ workspace for building libpng and zlib
+ wince.txt => Contains instructions for downloading a
+ Microsoft Visual C++ (Windows CD Toolkit)
+ workspace for building libpng and zlib on
+ WindowsCE
+ xcode => Contains xcode project files
+ scripts => Directory containing scripts for building libpng:
+ descrip.mms => VMS makefile for MMS or MMK
+ makefile.std => Generic UNIX makefile (cc, creates static
+ libpng.a)
+ makefile.elf => Linux/ELF gcc makefile symbol versioning,
+ creates libpng12.so.0.1.2.44)
+ makefile.linux => Linux/ELF makefile (gcc, creates
+ libpng12.so.0.1.2.44)
+ makefile.gcmmx => Linux/ELF makefile (gcc, creates
+ libpng12.so.0.1.2.44, previously
+ used assembler code tuned for Intel MMX
+ platform)
+ makefile.gcc => Generic makefile (gcc, creates static
+ libpng.a)
+ makefile.knr => Archaic UNIX Makefile that converts files
+ with ansi2knr (Requires ansi2knr.c from
+ ftp://ftp.cs.wisc.edu/ghost)
+ makefile.aix => AIX makefile
+ makefile.cygwin => Cygwin/gcc makefile
+ makefile.darwin => Darwin makefile
+ makefile.dec => DEC Alpha UNIX makefile
+ makefile.freebsd => FreeBSD makefile
+ makefile.hpgcc => HPUX makefile using gcc
+ makefile.hpux => HPUX (10.20 and 11.00) makefile
+ makefile.hp64 => HPUX (10.20 and 11.00) makefile, 64 bit
+ makefile.ibmc => IBM C/C++ version 3.x for Win32 and OS/2
+ (static)
+ makefile.intel => Intel C/C++ version 4.0 and later
+ libpng.icc => Project file, IBM VisualAge/C++ 4.0 or later
+ makefile.netbsd => NetBSD/cc makefile, makes libpng.so.
+ makefile.ne12bsd => NetBSD/cc makefile, makes libpng12.so
+ makefile.openbsd => OpenBSD makefile
+ makefile.sgi => Silicon Graphics IRIX (cc, creates static lib)
+ makefile.sggcc => Silicon Graphics
+ (gcc, creates libpng12.so.0.1.2.44)
+ makefile.sunos => Sun makefile
+ makefile.solaris => Solaris 2.X makefile
+ (gcc, creates libpng12.so.0.1.2.44)
+ makefile.so9 => Solaris 9 makefile
+ (gcc, creates libpng12.so.0.1.2.44)
+ makefile.32sunu => Sun Ultra 32-bit makefile
+ makefile.64sunu => Sun Ultra 64-bit makefile
+ makefile.sco => For SCO OSr5 ELF and Unixware 7 with Native cc
+ makefile.mips => MIPS makefile
+ makefile.acorn => Acorn makefile
+ makefile.amiga => Amiga makefile
+ smakefile.ppc => AMIGA smakefile for SAS C V6.58/7.00 PPC
+ compiler (Requires SCOPTIONS, copied from
+ scripts/SCOPTIONS.ppc)
+ makefile.atari => Atari makefile
+ makefile.beos => BEOS makefile for X86
+ makefile.bor => Borland makefile (uses bcc)
+ makefile.bc32 => 32-bit Borland C++ (all modules compiled in C mode)
+ makefile.tc3 => Turbo C 3.0 makefile
+ makefile.dj2 => DJGPP 2 makefile
+ makefile.msc => Microsoft C makefile
+ makefile.vcawin32=> makefile for Microsoft Visual C++ 5.0 and
+ later (previously used assembler code tuned
+ for Intel MMX platform)
+ makefile.vcwin32 => makefile for Microsoft Visual C++ 4.0 and
+ later (does not use assembler code)
+ makefile.os2 => OS/2 Makefile (gcc and emx, requires pngos2.def)
+ pngos2.def => OS/2 module definition file used by makefile.os2
+ makefile.watcom => Watcom 10a+ Makefile, 32-bit flat memory model
+ makevms.com => VMS build script
+ SCOPTIONS.ppc => Used with smakefile.ppc
+
+Good luck, and happy coding.
+
+-Glenn Randers-Pehrson (current maintainer, since 1998)
+ Internet: glennrp at users.sourceforge.net
+
+-Andreas Eric Dilger (former maintainer, 1996-1997)
+ Internet: adilger at enel.ucalgary.ca
+ Web: http://members.shaw.ca/adilger/
+
+-Guy Eric Schalnat (original author and former maintainer, 1995-1996)
+ (formerly of Group 42, Inc)
+ Internet: gschal at infinet.com
diff --git a/trunk/src/third_party/libpng/README.modpagespeed b/trunk/src/third_party/libpng/README.modpagespeed
new file mode 100644
index 0000000..f80cfc6
--- /dev/null
+++ b/trunk/src/third_party/libpng/README.modpagespeed
@@ -0,0 +1,5 @@
+Name: libpng
+URL: http://libpng.org/
+
+No changes to the source files.
+
diff --git a/trunk/src/third_party/libpng/libpng.gyp b/trunk/src/third_party/libpng/libpng.gyp
new file mode 100644
index 0000000..d46080f
--- /dev/null
+++ b/trunk/src/third_party/libpng/libpng.gyp
@@ -0,0 +1,117 @@
+# Copyright (c) 2009 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+ 'variables': {
+ 'conditions': [
+ [ 'OS=="linux" or OS=="freebsd" or OS=="openbsd"', {
+ # Link to system .so since we already use it due to GTK.
+ 'use_system_libpng%': 1,
+ }, { # OS!="linux" and OS!="freebsd" and OS!="openbsd"
+ 'use_system_libpng%': 0,
+ }],
+ ],
+ },
+ 'conditions': [
+ ['use_system_libpng==0', {
+ 'targets': [
+ {
+ 'target_name': 'libpng',
+ 'type': '<(component)',
+ 'dependencies': [
+ '../zlib/zlib.gyp:zlib',
+ ],
+ 'msvs_guid': 'C564F145-9172-42C3-BFCB-6014CA97DBCD',
+ 'sources': [
+ 'png.c',
+ 'png.h',
+ 'pngconf.h',
+ 'pngerror.c',
+ 'pnggccrd.c',
+ 'pngget.c',
+ 'pngmem.c',
+ 'pngpread.c',
+ 'pngread.c',
+ 'pngrio.c',
+ 'pngrtran.c',
+ 'pngrutil.c',
+ 'pngset.c',
+ 'pngtrans.c',
+ 'pngusr.h',
+ 'pngvcrd.c',
+ 'pngwio.c',
+ 'pngwrite.c',
+ 'pngwtran.c',
+ 'pngwutil.c',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '.',
+ ],
+ },
+ 'export_dependent_settings': [
+ '../zlib/zlib.gyp:zlib',
+ ],
+ 'conditions': [
+ ['OS!="win"', {'product_name': 'png'}],
+ ['OS=="win" and component=="shared_library"', {
+ 'defines': [
+ 'PNG_BUILD_DLL',
+ 'PNG_NO_MODULEDEF',
+ ],
+ 'direct_dependent_settings': {
+ 'defines': [
+ 'PNG_USE_DLL',
+ ],
+ },
+ }],
+ ],
+ },
+ ]
+ }, {
+ 'conditions': [
+ ['sysroot!=""', {
+ 'variables': {
+ 'pkg-config': '../../build/linux/pkg-config-wrapper "<(sysroot)"',
+ },
+ }, {
+ 'variables': {
+ 'pkg-config': 'pkg-config'
+ },
+ }],
+ ],
+ 'targets': [
+ {
+ 'target_name': 'libpng',
+ 'type': 'settings',
+ 'dependencies': [
+ '../zlib/zlib.gyp:zlib',
+ ],
+ 'direct_dependent_settings': {
+ 'cflags': [
+ '<!@(<(pkg-config) --cflags libpng)',
+ ],
+ 'defines': [
+ 'USE_SYSTEM_LIBPNG',
+ ],
+ },
+ 'link_settings': {
+ 'ldflags': [
+ '<!@(<(pkg-config) --libs-only-L --libs-only-other libpng)',
+ ],
+ 'libraries': [
+ '<!@(<(pkg-config) --libs-only-l libpng)',
+ ],
+ },
+ },
+ ],
+ }],
+ ],
+}
+
+# Local Variables:
+# tab-width:2
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=2 shiftwidth=2:
diff --git a/trunk/src/third_party/libpng/png.c b/trunk/src/third_party/libpng/png.c
new file mode 100644
index 0000000..7ad9538
--- /dev/null
+++ b/trunk/src/third_party/libpng/png.c
@@ -0,0 +1,1100 @@
+
+/* png.c - location for general purpose libpng functions
+ *
+ * Last changed in libpng 1.2.43 [February 25, 2010]
+ * Copyright (c) 1998-2010 Glenn Randers-Pehrson
+ * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger)
+ * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.)
+ *
+ * This code is released under the libpng license.
+ * For conditions of distribution and use, see the disclaimer
+ * and license in png.h
+ */
+
+#define PNG_INTERNAL
+#define PNG_NO_EXTERN
+#define PNG_NO_PEDANTIC_WARNINGS
+#include "png.h"
+
+/* Generate a compiler error if there is an old png.h in the search path. */
+typedef version_1_2_44 Your_png_h_is_not_version_1_2_44;
+
+/* Version information for C files. This had better match the version
+ * string defined in png.h.
+ */
+
+#ifdef PNG_USE_GLOBAL_ARRAYS
+/* png_libpng_ver was changed to a function in version 1.0.5c */
+PNG_CONST char png_libpng_ver[18] = PNG_LIBPNG_VER_STRING;
+
+#ifdef PNG_READ_SUPPORTED
+
+/* png_sig was changed to a function in version 1.0.5c */
+/* Place to hold the signature string for a PNG file. */
+PNG_CONST png_byte FARDATA png_sig[8] = {137, 80, 78, 71, 13, 10, 26, 10};
+#endif /* PNG_READ_SUPPORTED */
+
+/* Invoke global declarations for constant strings for known chunk types */
+PNG_IHDR;
+PNG_IDAT;
+PNG_IEND;
+PNG_PLTE;
+PNG_bKGD;
+PNG_cHRM;
+PNG_gAMA;
+PNG_hIST;
+PNG_iCCP;
+PNG_iTXt;
+PNG_oFFs;
+PNG_pCAL;
+PNG_sCAL;
+PNG_pHYs;
+PNG_sBIT;
+PNG_sPLT;
+PNG_sRGB;
+PNG_tEXt;
+PNG_tIME;
+PNG_tRNS;
+PNG_zTXt;
+
+#ifdef PNG_READ_SUPPORTED
+/* Arrays to facilitate easy interlacing - use pass (0 - 6) as index */
+
+/* Start of interlace block */
+PNG_CONST int FARDATA png_pass_start[] = {0, 4, 0, 2, 0, 1, 0};
+
+/* Offset to next interlace block */
+PNG_CONST int FARDATA png_pass_inc[] = {8, 8, 4, 4, 2, 2, 1};
+
+/* Start of interlace block in the y direction */
+PNG_CONST int FARDATA png_pass_ystart[] = {0, 0, 4, 0, 2, 0, 1};
+
+/* Offset to next interlace block in the y direction */
+PNG_CONST int FARDATA png_pass_yinc[] = {8, 8, 8, 4, 4, 2, 2};
+
+/* Height of interlace block. This is not currently used - if you need
+ * it, uncomment it here and in png.h
+PNG_CONST int FARDATA png_pass_height[] = {8, 8, 4, 4, 2, 2, 1};
+*/
+
+/* Mask to determine which pixels are valid in a pass */
+PNG_CONST int FARDATA png_pass_mask[] =
+ {0x80, 0x08, 0x88, 0x22, 0xaa, 0x55, 0xff};
+
+/* Mask to determine which pixels to overwrite while displaying */
+PNG_CONST int FARDATA png_pass_dsp_mask[]
+ = {0xff, 0x0f, 0xff, 0x33, 0xff, 0x55, 0xff};
+
+#endif /* PNG_READ_SUPPORTED */
+#endif /* PNG_USE_GLOBAL_ARRAYS */
+
+/* Tells libpng that we have already handled the first "num_bytes" bytes
+ * of the PNG file signature. If the PNG data is embedded into another
+ * stream we can set num_bytes = 8 so that libpng will not attempt to read
+ * or write any of the magic bytes before it starts on the IHDR.
+ */
+
+#ifdef PNG_READ_SUPPORTED
+void PNGAPI
+png_set_sig_bytes(png_structp png_ptr, int num_bytes)
+{
+ png_debug(1, "in png_set_sig_bytes");
+
+ if (png_ptr == NULL)
+ return;
+
+ if (num_bytes > 8)
+ png_error(png_ptr, "Too many bytes for PNG signature.");
+
+ png_ptr->sig_bytes = (png_byte)(num_bytes < 0 ? 0 : num_bytes);
+}
+
+/* Checks whether the supplied bytes match the PNG signature. We allow
+ * checking less than the full 8-byte signature so that those apps that
+ * already read the first few bytes of a file to determine the file type
+ * can simply check the remaining bytes for extra assurance. Returns
+ * an integer less than, equal to, or greater than zero if sig is found,
+ * respectively, to be less than, to match, or be greater than the correct
+ * PNG signature (this is the same behaviour as strcmp, memcmp, etc).
+ */
+int PNGAPI
+png_sig_cmp(png_bytep sig, png_size_t start, png_size_t num_to_check)
+{
+ png_byte png_signature[8] = {137, 80, 78, 71, 13, 10, 26, 10};
+ if (num_to_check > 8)
+ num_to_check = 8;
+ else if (num_to_check < 1)
+ return (-1);
+
+ if (start > 7)
+ return (-1);
+
+ if (start + num_to_check > 8)
+ num_to_check = 8 - start;
+
+ return ((int)(png_memcmp(&sig[start], &png_signature[start], num_to_check)));
+}
+
+#if defined(PNG_1_0_X) || defined(PNG_1_2_X)
+/* (Obsolete) function to check signature bytes. It does not allow one
+ * to check a partial signature. This function might be removed in the
+ * future - use png_sig_cmp(). Returns true (nonzero) if the file is PNG.
+ */
+int PNGAPI
+png_check_sig(png_bytep sig, int num)
+{
+ return ((int)!png_sig_cmp(sig, (png_size_t)0, (png_size_t)num));
+}
+#endif
+#endif /* PNG_READ_SUPPORTED */
+
+#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED)
+/* Function to allocate memory for zlib and clear it to 0. */
+#ifdef PNG_1_0_X
+voidpf PNGAPI
+#else
+voidpf /* PRIVATE */
+#endif
+png_zalloc(voidpf png_ptr, uInt items, uInt size)
+{
+ png_voidp ptr;
+ png_structp p=(png_structp)png_ptr;
+ png_uint_32 save_flags=p->flags;
+ png_uint_32 num_bytes;
+
+ if (png_ptr == NULL)
+ return (NULL);
+ if (items > PNG_UINT_32_MAX/size)
+ {
+ png_warning (p, "Potential overflow in png_zalloc()");
+ return (NULL);
+ }
+ num_bytes = (png_uint_32)items * size;
+
+ p->flags|=PNG_FLAG_MALLOC_NULL_MEM_OK;
+ ptr = (png_voidp)png_malloc((png_structp)png_ptr, num_bytes);
+ p->flags=save_flags;
+
+#if defined(PNG_1_0_X) && !defined(PNG_NO_ZALLOC_ZERO)
+ if (ptr == NULL)
+ return ((voidpf)ptr);
+
+ if (num_bytes > (png_uint_32)0x8000L)
+ {
+ png_memset(ptr, 0, (png_size_t)0x8000L);
+ png_memset((png_bytep)ptr + (png_size_t)0x8000L, 0,
+ (png_size_t)(num_bytes - (png_uint_32)0x8000L));
+ }
+ else
+ {
+ png_memset(ptr, 0, (png_size_t)num_bytes);
+ }
+#endif
+ return ((voidpf)ptr);
+}
+
+/* Function to free memory for zlib */
+#ifdef PNG_1_0_X
+void PNGAPI
+#else
+void /* PRIVATE */
+#endif
+png_zfree(voidpf png_ptr, voidpf ptr)
+{
+ png_free((png_structp)png_ptr, (png_voidp)ptr);
+}
+
+/* Reset the CRC variable to 32 bits of 1's. Care must be taken
+ * in case CRC is > 32 bits to leave the top bits 0.
+ */
+void /* PRIVATE */
+png_reset_crc(png_structp png_ptr)
+{
+ png_ptr->crc = crc32(0, Z_NULL, 0);
+}
+
+/* Calculate the CRC over a section of data. We can only pass as
+ * much data to this routine as the largest single buffer size. We
+ * also check that this data will actually be used before going to the
+ * trouble of calculating it.
+ */
+void /* PRIVATE */
+png_calculate_crc(png_structp png_ptr, png_bytep ptr, png_size_t length)
+{
+ int need_crc = 1;
+
+ if (png_ptr->chunk_name[0] & 0x20) /* ancillary */
+ {
+ if ((png_ptr->flags & PNG_FLAG_CRC_ANCILLARY_MASK) ==
+ (PNG_FLAG_CRC_ANCILLARY_USE | PNG_FLAG_CRC_ANCILLARY_NOWARN))
+ need_crc = 0;
+ }
+ else /* critical */
+ {
+ if (png_ptr->flags & PNG_FLAG_CRC_CRITICAL_IGNORE)
+ need_crc = 0;
+ }
+
+ if (need_crc)
+ png_ptr->crc = crc32(png_ptr->crc, ptr, (uInt)length);
+}
+
+/* Allocate the memory for an info_struct for the application. We don't
+ * really need the png_ptr, but it could potentially be useful in the
+ * future. This should be used in favour of malloc(png_sizeof(png_info))
+ * and png_info_init() so that applications that want to use a shared
+ * libpng don't have to be recompiled if png_info changes size.
+ */
+png_infop PNGAPI
+png_create_info_struct(png_structp png_ptr)
+{
+ png_infop info_ptr;
+
+ png_debug(1, "in png_create_info_struct");
+
+ if (png_ptr == NULL)
+ return (NULL);
+
+#ifdef PNG_USER_MEM_SUPPORTED
+ info_ptr = (png_infop)png_create_struct_2(PNG_STRUCT_INFO,
+ png_ptr->malloc_fn, png_ptr->mem_ptr);
+#else
+ info_ptr = (png_infop)png_create_struct(PNG_STRUCT_INFO);
+#endif
+ if (info_ptr != NULL)
+ png_info_init_3(&info_ptr, png_sizeof(png_info));
+
+ return (info_ptr);
+}
+
+/* This function frees the memory associated with a single info struct.
+ * Normally, one would use either png_destroy_read_struct() or
+ * png_destroy_write_struct() to free an info struct, but this may be
+ * useful for some applications.
+ */
+void PNGAPI
+png_destroy_info_struct(png_structp png_ptr, png_infopp info_ptr_ptr)
+{
+ png_infop info_ptr = NULL;
+
+ png_debug(1, "in png_destroy_info_struct");
+
+ if (png_ptr == NULL)
+ return;
+
+ if (info_ptr_ptr != NULL)
+ info_ptr = *info_ptr_ptr;
+
+ if (info_ptr != NULL)
+ {
+ png_info_destroy(png_ptr, info_ptr);
+
+#ifdef PNG_USER_MEM_SUPPORTED
+ png_destroy_struct_2((png_voidp)info_ptr, png_ptr->free_fn,
+ png_ptr->mem_ptr);
+#else
+ png_destroy_struct((png_voidp)info_ptr);
+#endif
+ *info_ptr_ptr = NULL;
+ }
+}
+
+/* Initialize the info structure. This is now an internal function (0.89)
+ * and applications using it are urged to use png_create_info_struct()
+ * instead.
+ */
+#if defined(PNG_1_0_X) || defined(PNG_1_2_X)
+#undef png_info_init
+void PNGAPI
+png_info_init(png_infop info_ptr)
+{
+ /* We only come here via pre-1.0.12-compiled applications */
+ png_info_init_3(&info_ptr, 0);
+}
+#endif
+
+void PNGAPI
+png_info_init_3(png_infopp ptr_ptr, png_size_t png_info_struct_size)
+{
+ png_infop info_ptr = *ptr_ptr;
+
+ png_debug(1, "in png_info_init_3");
+
+ if (info_ptr == NULL)
+ return;
+
+ if (png_sizeof(png_info) > png_info_struct_size)
+ {
+ png_destroy_struct(info_ptr);
+ info_ptr = (png_infop)png_create_struct(PNG_STRUCT_INFO);
+ *ptr_ptr = info_ptr;
+ }
+
+ /* Set everything to 0 */
+ png_memset(info_ptr, 0, png_sizeof(png_info));
+}
+
+#ifdef PNG_FREE_ME_SUPPORTED
+void PNGAPI
+png_data_freer(png_structp png_ptr, png_infop info_ptr,
+ int freer, png_uint_32 mask)
+{
+ png_debug(1, "in png_data_freer");
+
+ if (png_ptr == NULL || info_ptr == NULL)
+ return;
+
+ if (freer == PNG_DESTROY_WILL_FREE_DATA)
+ info_ptr->free_me |= mask;
+ else if (freer == PNG_USER_WILL_FREE_DATA)
+ info_ptr->free_me &= ~mask;
+ else
+ png_warning(png_ptr,
+ "Unknown freer parameter in png_data_freer.");
+}
+#endif
+
+void PNGAPI
+png_free_data(png_structp png_ptr, png_infop info_ptr, png_uint_32 mask,
+ int num)
+{
+ png_debug(1, "in png_free_data");
+
+ if (png_ptr == NULL || info_ptr == NULL)
+ return;
+
+#ifdef PNG_TEXT_SUPPORTED
+ /* Free text item num or (if num == -1) all text items */
+#ifdef PNG_FREE_ME_SUPPORTED
+ if ((mask & PNG_FREE_TEXT) & info_ptr->free_me)
+#else
+ if (mask & PNG_FREE_TEXT)
+#endif
+ {
+ if (num != -1)
+ {
+ if (info_ptr->text && info_ptr->text[num].key)
+ {
+ png_free(png_ptr, info_ptr->text[num].key);
+ info_ptr->text[num].key = NULL;
+ }
+ }
+ else
+ {
+ int i;
+ for (i = 0; i < info_ptr->num_text; i++)
+ png_free_data(png_ptr, info_ptr, PNG_FREE_TEXT, i);
+ png_free(png_ptr, info_ptr->text);
+ info_ptr->text = NULL;
+ info_ptr->num_text=0;
+ }
+ }
+#endif
+
+#ifdef PNG_tRNS_SUPPORTED
+ /* Free any tRNS entry */
+#ifdef PNG_FREE_ME_SUPPORTED
+ if ((mask & PNG_FREE_TRNS) & info_ptr->free_me)
+#else
+ if ((mask & PNG_FREE_TRNS) && (png_ptr->flags & PNG_FLAG_FREE_TRNS))
+#endif
+ {
+ png_free(png_ptr, info_ptr->trans);
+ info_ptr->trans = NULL;
+ info_ptr->valid &= ~PNG_INFO_tRNS;
+#ifndef PNG_FREE_ME_SUPPORTED
+ png_ptr->flags &= ~PNG_FLAG_FREE_TRNS;
+#endif
+ }
+#endif
+
+#ifdef PNG_sCAL_SUPPORTED
+ /* Free any sCAL entry */
+#ifdef PNG_FREE_ME_SUPPORTED
+ if ((mask & PNG_FREE_SCAL) & info_ptr->free_me)
+#else
+ if (mask & PNG_FREE_SCAL)
+#endif
+ {
+#if defined(PNG_FIXED_POINT_SUPPORTED) && !defined(PNG_FLOATING_POINT_SUPPORTED)
+ png_free(png_ptr, info_ptr->scal_s_width);
+ png_free(png_ptr, info_ptr->scal_s_height);
+ info_ptr->scal_s_width = NULL;
+ info_ptr->scal_s_height = NULL;
+#endif
+ info_ptr->valid &= ~PNG_INFO_sCAL;
+ }
+#endif
+
+#ifdef PNG_pCAL_SUPPORTED
+ /* Free any pCAL entry */
+#ifdef PNG_FREE_ME_SUPPORTED
+ if ((mask & PNG_FREE_PCAL) & info_ptr->free_me)
+#else
+ if (mask & PNG_FREE_PCAL)
+#endif
+ {
+ png_free(png_ptr, info_ptr->pcal_purpose);
+ png_free(png_ptr, info_ptr->pcal_units);
+ info_ptr->pcal_purpose = NULL;
+ info_ptr->pcal_units = NULL;
+ if (info_ptr->pcal_params != NULL)
+ {
+ int i;
+ for (i = 0; i < (int)info_ptr->pcal_nparams; i++)
+ {
+ png_free(png_ptr, info_ptr->pcal_params[i]);
+ info_ptr->pcal_params[i] = NULL;
+ }
+ png_free(png_ptr, info_ptr->pcal_params);
+ info_ptr->pcal_params = NULL;
+ }
+ info_ptr->valid &= ~PNG_INFO_pCAL;
+ }
+#endif
+
+#ifdef PNG_iCCP_SUPPORTED
+ /* Free any iCCP entry */
+#ifdef PNG_FREE_ME_SUPPORTED
+ if ((mask & PNG_FREE_ICCP) & info_ptr->free_me)
+#else
+ if (mask & PNG_FREE_ICCP)
+#endif
+ {
+ png_free(png_ptr, info_ptr->iccp_name);
+ png_free(png_ptr, info_ptr->iccp_profile);
+ info_ptr->iccp_name = NULL;
+ info_ptr->iccp_profile = NULL;
+ info_ptr->valid &= ~PNG_INFO_iCCP;
+ }
+#endif
+
+#ifdef PNG_sPLT_SUPPORTED
+ /* Free a given sPLT entry, or (if num == -1) all sPLT entries */
+#ifdef PNG_FREE_ME_SUPPORTED
+ if ((mask & PNG_FREE_SPLT) & info_ptr->free_me)
+#else
+ if (mask & PNG_FREE_SPLT)
+#endif
+ {
+ if (num != -1)
+ {
+ if (info_ptr->splt_palettes)
+ {
+ png_free(png_ptr, info_ptr->splt_palettes[num].name);
+ png_free(png_ptr, info_ptr->splt_palettes[num].entries);
+ info_ptr->splt_palettes[num].name = NULL;
+ info_ptr->splt_palettes[num].entries = NULL;
+ }
+ }
+ else
+ {
+ if (info_ptr->splt_palettes_num)
+ {
+ int i;
+ for (i = 0; i < (int)info_ptr->splt_palettes_num; i++)
+ png_free_data(png_ptr, info_ptr, PNG_FREE_SPLT, i);
+
+ png_free(png_ptr, info_ptr->splt_palettes);
+ info_ptr->splt_palettes = NULL;
+ info_ptr->splt_palettes_num = 0;
+ }
+ info_ptr->valid &= ~PNG_INFO_sPLT;
+ }
+ }
+#endif
+
+#ifdef PNG_UNKNOWN_CHUNKS_SUPPORTED
+ if (png_ptr->unknown_chunk.data)
+ {
+ png_free(png_ptr, png_ptr->unknown_chunk.data);
+ png_ptr->unknown_chunk.data = NULL;
+ }
+
+#ifdef PNG_FREE_ME_SUPPORTED
+ if ((mask & PNG_FREE_UNKN) & info_ptr->free_me)
+#else
+ if (mask & PNG_FREE_UNKN)
+#endif
+ {
+ if (num != -1)
+ {
+ if (info_ptr->unknown_chunks)
+ {
+ png_free(png_ptr, info_ptr->unknown_chunks[num].data);
+ info_ptr->unknown_chunks[num].data = NULL;
+ }
+ }
+ else
+ {
+ int i;
+
+ if (info_ptr->unknown_chunks_num)
+ {
+ for (i = 0; i < (int)info_ptr->unknown_chunks_num; i++)
+ png_free_data(png_ptr, info_ptr, PNG_FREE_UNKN, i);
+
+ png_free(png_ptr, info_ptr->unknown_chunks);
+ info_ptr->unknown_chunks = NULL;
+ info_ptr->unknown_chunks_num = 0;
+ }
+ }
+ }
+#endif
+
+#ifdef PNG_hIST_SUPPORTED
+ /* Free any hIST entry */
+#ifdef PNG_FREE_ME_SUPPORTED
+ if ((mask & PNG_FREE_HIST) & info_ptr->free_me)
+#else
+ if ((mask & PNG_FREE_HIST) && (png_ptr->flags & PNG_FLAG_FREE_HIST))
+#endif
+ {
+ png_free(png_ptr, info_ptr->hist);
+ info_ptr->hist = NULL;
+ info_ptr->valid &= ~PNG_INFO_hIST;
+#ifndef PNG_FREE_ME_SUPPORTED
+ png_ptr->flags &= ~PNG_FLAG_FREE_HIST;
+#endif
+ }
+#endif
+
+ /* Free any PLTE entry that was internally allocated */
+#ifdef PNG_FREE_ME_SUPPORTED
+ if ((mask & PNG_FREE_PLTE) & info_ptr->free_me)
+#else
+ if ((mask & PNG_FREE_PLTE) && (png_ptr->flags & PNG_FLAG_FREE_PLTE))
+#endif
+ {
+ png_zfree(png_ptr, info_ptr->palette);
+ info_ptr->palette = NULL;
+ info_ptr->valid &= ~PNG_INFO_PLTE;
+#ifndef PNG_FREE_ME_SUPPORTED
+ png_ptr->flags &= ~PNG_FLAG_FREE_PLTE;
+#endif
+ info_ptr->num_palette = 0;
+ }
+
+#ifdef PNG_INFO_IMAGE_SUPPORTED
+ /* Free any image bits attached to the info structure */
+#ifdef PNG_FREE_ME_SUPPORTED
+ if ((mask & PNG_FREE_ROWS) & info_ptr->free_me)
+#else
+ if (mask & PNG_FREE_ROWS)
+#endif
+ {
+ if (info_ptr->row_pointers)
+ {
+ int row;
+ for (row = 0; row < (int)info_ptr->height; row++)
+ {
+ png_free(png_ptr, info_ptr->row_pointers[row]);
+ info_ptr->row_pointers[row] = NULL;
+ }
+ png_free(png_ptr, info_ptr->row_pointers);
+ info_ptr->row_pointers = NULL;
+ }
+ info_ptr->valid &= ~PNG_INFO_IDAT;
+ }
+#endif
+
+#ifdef PNG_FREE_ME_SUPPORTED
+ if (num == -1)
+ info_ptr->free_me &= ~mask;
+ else
+ info_ptr->free_me &= ~(mask & ~PNG_FREE_MUL);
+#endif
+}
+
+/* This is an internal routine to free any memory that the info struct is
+ * pointing to before re-using it or freeing the struct itself. Recall
+ * that png_free() checks for NULL pointers for us.
+ */
+void /* PRIVATE */
+png_info_destroy(png_structp png_ptr, png_infop info_ptr)
+{
+ png_debug(1, "in png_info_destroy");
+
+ png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1);
+
+#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
+ if (png_ptr->num_chunk_list)
+ {
+ png_free(png_ptr, png_ptr->chunk_list);
+ png_ptr->chunk_list = NULL;
+ png_ptr->num_chunk_list = 0;
+ }
+#endif
+
+ png_info_init_3(&info_ptr, png_sizeof(png_info));
+}
+#endif /* defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) */
+
+/* This function returns a pointer to the io_ptr associated with the user
+ * functions. The application should free any memory associated with this
+ * pointer before png_write_destroy() or png_read_destroy() are called.
+ */
+png_voidp PNGAPI
+png_get_io_ptr(png_structp png_ptr)
+{
+ if (png_ptr == NULL)
+ return (NULL);
+ return (png_ptr->io_ptr);
+}
+
+#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED)
+#ifdef PNG_STDIO_SUPPORTED
+/* Initialize the default input/output functions for the PNG file. If you
+ * use your own read or write routines, you can call either png_set_read_fn()
+ * or png_set_write_fn() instead of png_init_io(). If you have defined
+ * PNG_NO_STDIO, you must use a function of your own because "FILE *" isn't
+ * necessarily available.
+ */
+void PNGAPI
+png_init_io(png_structp png_ptr, png_FILE_p fp)
+{
+ png_debug(1, "in png_init_io");
+
+ if (png_ptr == NULL)
+ return;
+
+ png_ptr->io_ptr = (png_voidp)fp;
+}
+#endif
+
+#ifdef PNG_TIME_RFC1123_SUPPORTED
+/* Convert the supplied time into an RFC 1123 string suitable for use in
+ * a "Creation Time" or other text-based time string.
+ */
+png_charp PNGAPI
+png_convert_to_rfc1123(png_structp png_ptr, png_timep ptime)
+{
+ static PNG_CONST char short_months[12][4] =
+ {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+
+ if (png_ptr == NULL)
+ return (NULL);
+ if (png_ptr->time_buffer == NULL)
+ {
+ png_ptr->time_buffer = (png_charp)png_malloc(png_ptr, (png_uint_32)(29*
+ png_sizeof(char)));
+ }
+
+#ifdef _WIN32_WCE
+ {
+ wchar_t time_buf[29];
+ wsprintf(time_buf, TEXT("%d %S %d %02d:%02d:%02d +0000"),
+ ptime->day % 32, short_months[(ptime->month - 1) % 12],
+ ptime->year, ptime->hour % 24, ptime->minute % 60,
+ ptime->second % 61);
+ WideCharToMultiByte(CP_ACP, 0, time_buf, -1, png_ptr->time_buffer,
+ 29, NULL, NULL);
+ }
+#else
+#ifdef USE_FAR_KEYWORD
+ {
+ char near_time_buf[29];
+ png_snprintf6(near_time_buf, 29, "%d %s %d %02d:%02d:%02d +0000",
+ ptime->day % 32, short_months[(ptime->month - 1) % 12],
+ ptime->year, ptime->hour % 24, ptime->minute % 60,
+ ptime->second % 61);
+ png_memcpy(png_ptr->time_buffer, near_time_buf,
+ 29*png_sizeof(char));
+ }
+#else
+ png_snprintf6(png_ptr->time_buffer, 29, "%d %s %d %02d:%02d:%02d +0000",
+ ptime->day % 32, short_months[(ptime->month - 1) % 12],
+ ptime->year, ptime->hour % 24, ptime->minute % 60,
+ ptime->second % 61);
+#endif
+#endif /* _WIN32_WCE */
+ return ((png_charp)png_ptr->time_buffer);
+}
+#endif /* PNG_TIME_RFC1123_SUPPORTED */
+
+#endif /* defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) */
+
+png_charp PNGAPI
+png_get_copyright(png_structp png_ptr)
+{
+ png_ptr = png_ptr; /* Silence compiler warning about unused png_ptr */
+#ifdef PNG_STRING_COPYRIGHT
+ return PNG_STRING_COPYRIGHT
+#else
+#ifdef __STDC__
+ return ((png_charp) PNG_STRING_NEWLINE \
+ "libpng version 1.2.44 - June 26, 2010" PNG_STRING_NEWLINE \
+ "Copyright (c) 1998-2010 Glenn Randers-Pehrson" PNG_STRING_NEWLINE \
+ "Copyright (c) 1996-1997 Andreas Dilger" PNG_STRING_NEWLINE \
+ "Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc." \
+ PNG_STRING_NEWLINE);
+#else
+ return ((png_charp) "libpng version 1.2.44 - June 26, 2010\
+ Copyright (c) 1998-2010 Glenn Randers-Pehrson\
+ Copyright (c) 1996-1997 Andreas Dilger\
+ Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.");
+#endif
+#endif
+}
+
+/* The following return the library version as a short string in the
+ * format 1.0.0 through 99.99.99zz. To get the version of *.h files
+ * used with your application, print out PNG_LIBPNG_VER_STRING, which
+ * is defined in png.h.
+ * Note: now there is no difference between png_get_libpng_ver() and
+ * png_get_header_ver(). Due to the version_nn_nn_nn typedef guard,
+ * it is guaranteed that png.c uses the correct version of png.h.
+ */
+png_charp PNGAPI
+png_get_libpng_ver(png_structp png_ptr)
+{
+ /* Version of *.c files used when building libpng */
+ png_ptr = png_ptr; /* Silence compiler warning about unused png_ptr */
+ return ((png_charp) PNG_LIBPNG_VER_STRING);
+}
+
+png_charp PNGAPI
+png_get_header_ver(png_structp png_ptr)
+{
+ /* Version of *.h files used when building libpng */
+ png_ptr = png_ptr; /* Silence compiler warning about unused png_ptr */
+ return ((png_charp) PNG_LIBPNG_VER_STRING);
+}
+
+png_charp PNGAPI
+png_get_header_version(png_structp png_ptr)
+{
+ /* Returns longer string containing both version and date */
+ png_ptr = png_ptr; /* Silence compiler warning about unused png_ptr */
+#ifdef __STDC__
+ return ((png_charp) PNG_HEADER_VERSION_STRING
+#ifndef PNG_READ_SUPPORTED
+ " (NO READ SUPPORT)"
+#endif
+ PNG_STRING_NEWLINE);
+#else
+ return ((png_charp) PNG_HEADER_VERSION_STRING);
+#endif
+}
+
+#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED)
+#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
+int PNGAPI
+png_handle_as_unknown(png_structp png_ptr, png_bytep chunk_name)
+{
+ /* Check chunk_name and return "keep" value if it's on the list, else 0 */
+ int i;
+ png_bytep p;
+ if (png_ptr == NULL || chunk_name == NULL || png_ptr->num_chunk_list<=0)
+ return 0;
+ p = png_ptr->chunk_list + png_ptr->num_chunk_list*5 - 5;
+ for (i = png_ptr->num_chunk_list; i; i--, p -= 5)
+ if (!png_memcmp(chunk_name, p, 4))
+ return ((int)*(p + 4));
+ return 0;
+}
+#endif
+
+/* This function, added to libpng-1.0.6g, is untested. */
+int PNGAPI
+png_reset_zstream(png_structp png_ptr)
+{
+ if (png_ptr == NULL)
+ return Z_STREAM_ERROR;
+ return (inflateReset(&png_ptr->zstream));
+}
+#endif /* defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) */
+
+/* This function was added to libpng-1.0.7 */
+png_uint_32 PNGAPI
+png_access_version_number(void)
+{
+ /* Version of *.c files used when building libpng */
+ return((png_uint_32) PNG_LIBPNG_VER);
+}
+
+
+#if defined(PNG_READ_SUPPORTED) && defined(PNG_ASSEMBLER_CODE_SUPPORTED)
+#ifndef PNG_1_0_X
+/* This function was added to libpng 1.2.0 */
+int PNGAPI
+png_mmx_support(void)
+{
+ /* Obsolete, to be removed from libpng-1.4.0 */
+ return -1;
+}
+#endif /* PNG_1_0_X */
+#endif /* PNG_READ_SUPPORTED && PNG_ASSEMBLER_CODE_SUPPORTED */
+
+#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED)
+#ifdef PNG_SIZE_T
+/* Added at libpng version 1.2.6 */
+ PNG_EXTERN png_size_t PNGAPI png_convert_size PNGARG((size_t size));
+png_size_t PNGAPI
+png_convert_size(size_t size)
+{
+ if (size > (png_size_t)-1)
+ PNG_ABORT(); /* We haven't got access to png_ptr, so no png_error() */
+ return ((png_size_t)size);
+}
+#endif /* PNG_SIZE_T */
+
+/* Added at libpng version 1.2.34 and 1.4.0 (moved from pngset.c) */
+#ifdef PNG_cHRM_SUPPORTED
+#ifdef PNG_CHECK_cHRM_SUPPORTED
+
+/*
+ * Multiply two 32-bit numbers, V1 and V2, using 32-bit
+ * arithmetic, to produce a 64 bit result in the HI/LO words.
+ *
+ * A B
+ * x C D
+ * ------
+ * AD || BD
+ * AC || CB || 0
+ *
+ * where A and B are the high and low 16-bit words of V1,
+ * C and D are the 16-bit words of V2, AD is the product of
+ * A and D, and X || Y is (X << 16) + Y.
+*/
+
+void /* PRIVATE */
+png_64bit_product (long v1, long v2, unsigned long *hi_product,
+ unsigned long *lo_product)
+{
+ int a, b, c, d;
+ long lo, hi, x, y;
+
+ a = (v1 >> 16) & 0xffff;
+ b = v1 & 0xffff;
+ c = (v2 >> 16) & 0xffff;
+ d = v2 & 0xffff;
+
+ lo = b * d; /* BD */
+ x = a * d + c * b; /* AD + CB */
+ y = ((lo >> 16) & 0xffff) + x;
+
+ lo = (lo & 0xffff) | ((y & 0xffff) << 16);
+ hi = (y >> 16) & 0xffff;
+
+ hi += a * c; /* AC */
+
+ *hi_product = (unsigned long)hi;
+ *lo_product = (unsigned long)lo;
+}
+
+int /* PRIVATE */
+png_check_cHRM_fixed(png_structp png_ptr,
+ png_fixed_point white_x, png_fixed_point white_y, png_fixed_point red_x,
+ png_fixed_point red_y, png_fixed_point green_x, png_fixed_point green_y,
+ png_fixed_point blue_x, png_fixed_point blue_y)
+{
+ int ret = 1;
+ unsigned long xy_hi,xy_lo,yx_hi,yx_lo;
+
+ png_debug(1, "in function png_check_cHRM_fixed");
+
+ if (png_ptr == NULL)
+ return 0;
+
+ if (white_x < 0 || white_y <= 0 ||
+ red_x < 0 || red_y < 0 ||
+ green_x < 0 || green_y < 0 ||
+ blue_x < 0 || blue_y < 0)
+ {
+ png_warning(png_ptr,
+ "Ignoring attempt to set negative chromaticity value");
+ ret = 0;
+ }
+ if (white_x > (png_fixed_point) PNG_UINT_31_MAX ||
+ white_y > (png_fixed_point) PNG_UINT_31_MAX ||
+ red_x > (png_fixed_point) PNG_UINT_31_MAX ||
+ red_y > (png_fixed_point) PNG_UINT_31_MAX ||
+ green_x > (png_fixed_point) PNG_UINT_31_MAX ||
+ green_y > (png_fixed_point) PNG_UINT_31_MAX ||
+ blue_x > (png_fixed_point) PNG_UINT_31_MAX ||
+ blue_y > (png_fixed_point) PNG_UINT_31_MAX )
+ {
+ png_warning(png_ptr,
+ "Ignoring attempt to set chromaticity value exceeding 21474.83");
+ ret = 0;
+ }
+ if (white_x > 100000L - white_y)
+ {
+ png_warning(png_ptr, "Invalid cHRM white point");
+ ret = 0;
+ }
+ if (red_x > 100000L - red_y)
+ {
+ png_warning(png_ptr, "Invalid cHRM red point");
+ ret = 0;
+ }
+ if (green_x > 100000L - green_y)
+ {
+ png_warning(png_ptr, "Invalid cHRM green point");
+ ret = 0;
+ }
+ if (blue_x > 100000L - blue_y)
+ {
+ png_warning(png_ptr, "Invalid cHRM blue point");
+ ret = 0;
+ }
+
+ png_64bit_product(green_x - red_x, blue_y - red_y, &xy_hi, &xy_lo);
+ png_64bit_product(green_y - red_y, blue_x - red_x, &yx_hi, &yx_lo);
+
+ if (xy_hi == yx_hi && xy_lo == yx_lo)
+ {
+ png_warning(png_ptr,
+ "Ignoring attempt to set cHRM RGB triangle with zero area");
+ ret = 0;
+ }
+
+ return ret;
+}
+#endif /* PNG_CHECK_cHRM_SUPPORTED */
+#endif /* PNG_cHRM_SUPPORTED */
+
+void /* PRIVATE */
+png_check_IHDR(png_structp png_ptr,
+ png_uint_32 width, png_uint_32 height, int bit_depth,
+ int color_type, int interlace_type, int compression_type,
+ int filter_type)
+{
+ int error = 0;
+
+ /* Check for width and height valid values */
+ if (width == 0)
+ {
+ png_warning(png_ptr, "Image width is zero in IHDR");
+ error = 1;
+ }
+
+ if (height == 0)
+ {
+ png_warning(png_ptr, "Image height is zero in IHDR");
+ error = 1;
+ }
+
+#ifdef PNG_SET_USER_LIMITS_SUPPORTED
+ if (width > png_ptr->user_width_max || width > PNG_USER_WIDTH_MAX)
+#else
+ if (width > PNG_USER_WIDTH_MAX)
+#endif
+ {
+ png_warning(png_ptr, "Image width exceeds user limit in IHDR");
+ error = 1;
+ }
+
+#ifdef PNG_SET_USER_LIMITS_SUPPORTED
+ if (height > png_ptr->user_height_max || height > PNG_USER_HEIGHT_MAX)
+#else
+ if (height > PNG_USER_HEIGHT_MAX)
+#endif
+ {
+ png_warning(png_ptr, "Image height exceeds user limit in IHDR");
+ error = 1;
+ }
+
+ if (width > PNG_UINT_31_MAX)
+ {
+ png_warning(png_ptr, "Invalid image width in IHDR");
+ error = 1;
+ }
+
+ if ( height > PNG_UINT_31_MAX)
+ {
+ png_warning(png_ptr, "Invalid image height in IHDR");
+ error = 1;
+ }
+
+ if ( width > (PNG_UINT_32_MAX
+ >> 3) /* 8-byte RGBA pixels */
+ - 64 /* bigrowbuf hack */
+ - 1 /* filter byte */
+ - 7*8 /* rounding of width to multiple of 8 pixels */
+ - 8) /* extra max_pixel_depth pad */
+ png_warning(png_ptr, "Width is too large for libpng to process pixels");
+
+ /* Check other values */
+ if (bit_depth != 1 && bit_depth != 2 && bit_depth != 4 &&
+ bit_depth != 8 && bit_depth != 16)
+ {
+ png_warning(png_ptr, "Invalid bit depth in IHDR");
+ error = 1;
+ }
+
+ if (color_type < 0 || color_type == 1 ||
+ color_type == 5 || color_type > 6)
+ {
+ png_warning(png_ptr, "Invalid color type in IHDR");
+ error = 1;
+ }
+
+ if (((color_type == PNG_COLOR_TYPE_PALETTE) && bit_depth > 8) ||
+ ((color_type == PNG_COLOR_TYPE_RGB ||
+ color_type == PNG_COLOR_TYPE_GRAY_ALPHA ||
+ color_type == PNG_COLOR_TYPE_RGB_ALPHA) && bit_depth < 8))
+ {
+ png_warning(png_ptr, "Invalid color type/bit depth combination in IHDR");
+ error = 1;
+ }
+
+ if (interlace_type >= PNG_INTERLACE_LAST)
+ {
+ png_warning(png_ptr, "Unknown interlace method in IHDR");
+ error = 1;
+ }
+
+ if (compression_type != PNG_COMPRESSION_TYPE_BASE)
+ {
+ png_warning(png_ptr, "Unknown compression method in IHDR");
+ error = 1;
+ }
+
+#ifdef PNG_MNG_FEATURES_SUPPORTED
+ /* Accept filter_method 64 (intrapixel differencing) only if
+ * 1. Libpng was compiled with PNG_MNG_FEATURES_SUPPORTED and
+ * 2. Libpng did not read a PNG signature (this filter_method is only
+ * used in PNG datastreams that are embedded in MNG datastreams) and
+ * 3. The application called png_permit_mng_features with a mask that
+ * included PNG_FLAG_MNG_FILTER_64 and
+ * 4. The filter_method is 64 and
+ * 5. The color_type is RGB or RGBA
+ */
+ if ((png_ptr->mode & PNG_HAVE_PNG_SIGNATURE) &&
+ png_ptr->mng_features_permitted)
+ png_warning(png_ptr, "MNG features are not allowed in a PNG datastream");
+
+ if (filter_type != PNG_FILTER_TYPE_BASE)
+ {
+ if (!((png_ptr->mng_features_permitted & PNG_FLAG_MNG_FILTER_64) &&
+ (filter_type == PNG_INTRAPIXEL_DIFFERENCING) &&
+ ((png_ptr->mode & PNG_HAVE_PNG_SIGNATURE) == 0) &&
+ (color_type == PNG_COLOR_TYPE_RGB ||
+ color_type == PNG_COLOR_TYPE_RGB_ALPHA)))
+ {
+ png_warning(png_ptr, "Unknown filter method in IHDR");
+ error = 1;
+ }
+
+ if (png_ptr->mode & PNG_HAVE_PNG_SIGNATURE)
+ {
+ png_warning(png_ptr, "Invalid filter method in IHDR");
+ error = 1;
+ }
+ }
+
+#else
+ if (filter_type != PNG_FILTER_TYPE_BASE)
+ {
+ png_warning(png_ptr, "Unknown filter method in IHDR");
+ error = 1;
+ }
+#endif
+
+ if (error == 1)
+ png_error(png_ptr, "Invalid IHDR data");
+}
+#endif /* defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) */
diff --git a/trunk/src/third_party/libpng/png.h b/trunk/src/third_party/libpng/png.h
new file mode 100644
index 0000000..cc1915d
--- /dev/null
+++ b/trunk/src/third_party/libpng/png.h
@@ -0,0 +1,3788 @@
+/* png.h - header file for PNG reference library
+ *
+ * libpng version 1.2.44 - June 26, 2010
+ * Copyright (c) 1998-2010 Glenn Randers-Pehrson
+ * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger)
+ * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.)
+ *
+ * This code is released under the libpng license (See LICENSE, below)
+ *
+ * Authors and maintainers:
+ * libpng versions 0.71, May 1995, through 0.88, January 1996: Guy Schalnat
+ * libpng versions 0.89c, June 1996, through 0.96, May 1997: Andreas Dilger
+ * libpng versions 0.97, January 1998, through 1.2.44 - June 26, 2010: Glenn
+ * See also "Contributing Authors", below.
+ *
+ * Note about libpng version numbers:
+ *
+ * Due to various miscommunications, unforeseen code incompatibilities
+ * and occasional factors outside the authors' control, version numbering
+ * on the library has not always been consistent and straightforward.
+ * The following table summarizes matters since version 0.89c, which was
+ * the first widely used release:
+ *
+ * source png.h png.h shared-lib
+ * version string int version
+ * ------- ------ ----- ----------
+ * 0.89c "1.0 beta 3" 0.89 89 1.0.89
+ * 0.90 "1.0 beta 4" 0.90 90 0.90 [should have been 2.0.90]
+ * 0.95 "1.0 beta 5" 0.95 95 0.95 [should have been 2.0.95]
+ * 0.96 "1.0 beta 6" 0.96 96 0.96 [should have been 2.0.96]
+ * 0.97b "1.00.97 beta 7" 1.00.97 97 1.0.1 [should have been 2.0.97]
+ * 0.97c 0.97 97 2.0.97
+ * 0.98 0.98 98 2.0.98
+ * 0.99 0.99 98 2.0.99
+ * 0.99a-m 0.99 99 2.0.99
+ * 1.00 1.00 100 2.1.0 [100 should be 10000]
+ * 1.0.0 (from here on, the 100 2.1.0 [100 should be 10000]
+ * 1.0.1 png.h string is 10001 2.1.0
+ * 1.0.1a-e identical to the 10002 from here on, the shared library
+ * 1.0.2 source version) 10002 is 2.V where V is the source code
+ * 1.0.2a-b 10003 version, except as noted.
+ * 1.0.3 10003
+ * 1.0.3a-d 10004
+ * 1.0.4 10004
+ * 1.0.4a-f 10005
+ * 1.0.5 (+ 2 patches) 10005
+ * 1.0.5a-d 10006
+ * 1.0.5e-r 10100 (not source compatible)
+ * 1.0.5s-v 10006 (not binary compatible)
+ * 1.0.6 (+ 3 patches) 10006 (still binary incompatible)
+ * 1.0.6d-f 10007 (still binary incompatible)
+ * 1.0.6g 10007
+ * 1.0.6h 10007 10.6h (testing xy.z so-numbering)
+ * 1.0.6i 10007 10.6i
+ * 1.0.6j 10007 2.1.0.6j (incompatible with 1.0.0)
+ * 1.0.7beta11-14 DLLNUM 10007 2.1.0.7beta11-14 (binary compatible)
+ * 1.0.7beta15-18 1 10007 2.1.0.7beta15-18 (binary compatible)
+ * 1.0.7rc1-2 1 10007 2.1.0.7rc1-2 (binary compatible)
+ * 1.0.7 1 10007 (still compatible)
+ * 1.0.8beta1-4 1 10008 2.1.0.8beta1-4
+ * 1.0.8rc1 1 10008 2.1.0.8rc1
+ * 1.0.8 1 10008 2.1.0.8
+ * 1.0.9beta1-6 1 10009 2.1.0.9beta1-6
+ * 1.0.9rc1 1 10009 2.1.0.9rc1
+ * 1.0.9beta7-10 1 10009 2.1.0.9beta7-10
+ * 1.0.9rc2 1 10009 2.1.0.9rc2
+ * 1.0.9 1 10009 2.1.0.9
+ * 1.0.10beta1 1 10010 2.1.0.10beta1
+ * 1.0.10rc1 1 10010 2.1.0.10rc1
+ * 1.0.10 1 10010 2.1.0.10
+ * 1.0.11beta1-3 1 10011 2.1.0.11beta1-3
+ * 1.0.11rc1 1 10011 2.1.0.11rc1
+ * 1.0.11 1 10011 2.1.0.11
+ * 1.0.12beta1-2 2 10012 2.1.0.12beta1-2
+ * 1.0.12rc1 2 10012 2.1.0.12rc1
+ * 1.0.12 2 10012 2.1.0.12
+ * 1.1.0a-f - 10100 2.1.1.0a-f (branch abandoned)
+ * 1.2.0beta1-2 2 10200 2.1.2.0beta1-2
+ * 1.2.0beta3-5 3 10200 3.1.2.0beta3-5
+ * 1.2.0rc1 3 10200 3.1.2.0rc1
+ * 1.2.0 3 10200 3.1.2.0
+ * 1.2.1beta1-4 3 10201 3.1.2.1beta1-4
+ * 1.2.1rc1-2 3 10201 3.1.2.1rc1-2
+ * 1.2.1 3 10201 3.1.2.1
+ * 1.2.2beta1-6 12 10202 12.so.0.1.2.2beta1-6
+ * 1.0.13beta1 10 10013 10.so.0.1.0.13beta1
+ * 1.0.13rc1 10 10013 10.so.0.1.0.13rc1
+ * 1.2.2rc1 12 10202 12.so.0.1.2.2rc1
+ * 1.0.13 10 10013 10.so.0.1.0.13
+ * 1.2.2 12 10202 12.so.0.1.2.2
+ * 1.2.3rc1-6 12 10203 12.so.0.1.2.3rc1-6
+ * 1.2.3 12 10203 12.so.0.1.2.3
+ * 1.2.4beta1-3 13 10204 12.so.0.1.2.4beta1-3
+ * 1.0.14rc1 13 10014 10.so.0.1.0.14rc1
+ * 1.2.4rc1 13 10204 12.so.0.1.2.4rc1
+ * 1.0.14 10 10014 10.so.0.1.0.14
+ * 1.2.4 13 10204 12.so.0.1.2.4
+ * 1.2.5beta1-2 13 10205 12.so.0.1.2.5beta1-2
+ * 1.0.15rc1-3 10 10015 10.so.0.1.0.15rc1-3
+ * 1.2.5rc1-3 13 10205 12.so.0.1.2.5rc1-3
+ * 1.0.15 10 10015 10.so.0.1.0.15
+ * 1.2.5 13 10205 12.so.0.1.2.5
+ * 1.2.6beta1-4 13 10206 12.so.0.1.2.6beta1-4
+ * 1.0.16 10 10016 10.so.0.1.0.16
+ * 1.2.6 13 10206 12.so.0.1.2.6
+ * 1.2.7beta1-2 13 10207 12.so.0.1.2.7beta1-2
+ * 1.0.17rc1 10 10017 10.so.0.1.0.17rc1
+ * 1.2.7rc1 13 10207 12.so.0.1.2.7rc1
+ * 1.0.17 10 10017 10.so.0.1.0.17
+ * 1.2.7 13 10207 12.so.0.1.2.7
+ * 1.2.8beta1-5 13 10208 12.so.0.1.2.8beta1-5
+ * 1.0.18rc1-5 10 10018 10.so.0.1.0.18rc1-5
+ * 1.2.8rc1-5 13 10208 12.so.0.1.2.8rc1-5
+ * 1.0.18 10 10018 10.so.0.1.0.18
+ * 1.2.8 13 10208 12.so.0.1.2.8
+ * 1.2.9beta1-3 13 10209 12.so.0.1.2.9beta1-3
+ * 1.2.9beta4-11 13 10209 12.so.0.9[.0]
+ * 1.2.9rc1 13 10209 12.so.0.9[.0]
+ * 1.2.9 13 10209 12.so.0.9[.0]
+ * 1.2.10beta1-8 13 10210 12.so.0.10[.0]
+ * 1.2.10rc1-3 13 10210 12.so.0.10[.0]
+ * 1.2.10 13 10210 12.so.0.10[.0]
+ * 1.2.11beta1-4 13 10211 12.so.0.11[.0]
+ * 1.0.19rc1-5 10 10019 10.so.0.19[.0]
+ * 1.2.11rc1-5 13 10211 12.so.0.11[.0]
+ * 1.0.19 10 10019 10.so.0.19[.0]
+ * 1.2.11 13 10211 12.so.0.11[.0]
+ * 1.0.20 10 10020 10.so.0.20[.0]
+ * 1.2.12 13 10212 12.so.0.12[.0]
+ * 1.2.13beta1 13 10213 12.so.0.13[.0]
+ * 1.0.21 10 10021 10.so.0.21[.0]
+ * 1.2.13 13 10213 12.so.0.13[.0]
+ * 1.2.14beta1-2 13 10214 12.so.0.14[.0]
+ * 1.0.22rc1 10 10022 10.so.0.22[.0]
+ * 1.2.14rc1 13 10214 12.so.0.14[.0]
+ * 1.0.22 10 10022 10.so.0.22[.0]
+ * 1.2.14 13 10214 12.so.0.14[.0]
+ * 1.2.15beta1-6 13 10215 12.so.0.15[.0]
+ * 1.0.23rc1-5 10 10023 10.so.0.23[.0]
+ * 1.2.15rc1-5 13 10215 12.so.0.15[.0]
+ * 1.0.23 10 10023 10.so.0.23[.0]
+ * 1.2.15 13 10215 12.so.0.15[.0]
+ * 1.2.16beta1-2 13 10216 12.so.0.16[.0]
+ * 1.2.16rc1 13 10216 12.so.0.16[.0]
+ * 1.0.24 10 10024 10.so.0.24[.0]
+ * 1.2.16 13 10216 12.so.0.16[.0]
+ * 1.2.17beta1-2 13 10217 12.so.0.17[.0]
+ * 1.0.25rc1 10 10025 10.so.0.25[.0]
+ * 1.2.17rc1-3 13 10217 12.so.0.17[.0]
+ * 1.0.25 10 10025 10.so.0.25[.0]
+ * 1.2.17 13 10217 12.so.0.17[.0]
+ * 1.0.26 10 10026 10.so.0.26[.0]
+ * 1.2.18 13 10218 12.so.0.18[.0]
+ * 1.2.19beta1-31 13 10219 12.so.0.19[.0]
+ * 1.0.27rc1-6 10 10027 10.so.0.27[.0]
+ * 1.2.19rc1-6 13 10219 12.so.0.19[.0]
+ * 1.0.27 10 10027 10.so.0.27[.0]
+ * 1.2.19 13 10219 12.so.0.19[.0]
+ * 1.2.20beta01-04 13 10220 12.so.0.20[.0]
+ * 1.0.28rc1-6 10 10028 10.so.0.28[.0]
+ * 1.2.20rc1-6 13 10220 12.so.0.20[.0]
+ * 1.0.28 10 10028 10.so.0.28[.0]
+ * 1.2.20 13 10220 12.so.0.20[.0]
+ * 1.2.21beta1-2 13 10221 12.so.0.21[.0]
+ * 1.2.21rc1-3 13 10221 12.so.0.21[.0]
+ * 1.0.29 10 10029 10.so.0.29[.0]
+ * 1.2.21 13 10221 12.so.0.21[.0]
+ * 1.2.22beta1-4 13 10222 12.so.0.22[.0]
+ * 1.0.30rc1 10 10030 10.so.0.30[.0]
+ * 1.2.22rc1 13 10222 12.so.0.22[.0]
+ * 1.0.30 10 10030 10.so.0.30[.0]
+ * 1.2.22 13 10222 12.so.0.22[.0]
+ * 1.2.23beta01-05 13 10223 12.so.0.23[.0]
+ * 1.2.23rc01 13 10223 12.so.0.23[.0]
+ * 1.2.23 13 10223 12.so.0.23[.0]
+ * 1.2.24beta01-02 13 10224 12.so.0.24[.0]
+ * 1.2.24rc01 13 10224 12.so.0.24[.0]
+ * 1.2.24 13 10224 12.so.0.24[.0]
+ * 1.2.25beta01-06 13 10225 12.so.0.25[.0]
+ * 1.2.25rc01-02 13 10225 12.so.0.25[.0]
+ * 1.0.31 10 10031 10.so.0.31[.0]
+ * 1.2.25 13 10225 12.so.0.25[.0]
+ * 1.2.26beta01-06 13 10226 12.so.0.26[.0]
+ * 1.2.26rc01 13 10226 12.so.0.26[.0]
+ * 1.2.26 13 10226 12.so.0.26[.0]
+ * 1.0.32 10 10032 10.so.0.32[.0]
+ * 1.2.27beta01-06 13 10227 12.so.0.27[.0]
+ * 1.2.27rc01 13 10227 12.so.0.27[.0]
+ * 1.0.33 10 10033 10.so.0.33[.0]
+ * 1.2.27 13 10227 12.so.0.27[.0]
+ * 1.0.34 10 10034 10.so.0.34[.0]
+ * 1.2.28 13 10228 12.so.0.28[.0]
+ * 1.2.29beta01-03 13 10229 12.so.0.29[.0]
+ * 1.2.29rc01 13 10229 12.so.0.29[.0]
+ * 1.0.35 10 10035 10.so.0.35[.0]
+ * 1.2.29 13 10229 12.so.0.29[.0]
+ * 1.0.37 10 10037 10.so.0.37[.0]
+ * 1.2.30beta01-04 13 10230 12.so.0.30[.0]
+ * 1.0.38rc01-08 10 10038 10.so.0.38[.0]
+ * 1.2.30rc01-08 13 10230 12.so.0.30[.0]
+ * 1.0.38 10 10038 10.so.0.38[.0]
+ * 1.2.30 13 10230 12.so.0.30[.0]
+ * 1.0.39rc01-03 10 10039 10.so.0.39[.0]
+ * 1.2.31rc01-03 13 10231 12.so.0.31[.0]
+ * 1.0.39 10 10039 10.so.0.39[.0]
+ * 1.2.31 13 10231 12.so.0.31[.0]
+ * 1.2.32beta01-02 13 10232 12.so.0.32[.0]
+ * 1.0.40rc01 10 10040 10.so.0.40[.0]
+ * 1.2.32rc01 13 10232 12.so.0.32[.0]
+ * 1.0.40 10 10040 10.so.0.40[.0]
+ * 1.2.32 13 10232 12.so.0.32[.0]
+ * 1.2.33beta01-02 13 10233 12.so.0.33[.0]
+ * 1.2.33rc01-02 13 10233 12.so.0.33[.0]
+ * 1.0.41rc01 10 10041 10.so.0.41[.0]
+ * 1.2.33 13 10233 12.so.0.33[.0]
+ * 1.0.41 10 10041 10.so.0.41[.0]
+ * 1.2.34beta01-07 13 10234 12.so.0.34[.0]
+ * 1.0.42rc01 10 10042 10.so.0.42[.0]
+ * 1.2.34rc01 13 10234 12.so.0.34[.0]
+ * 1.0.42 10 10042 10.so.0.42[.0]
+ * 1.2.34 13 10234 12.so.0.34[.0]
+ * 1.2.35beta01-03 13 10235 12.so.0.35[.0]
+ * 1.0.43rc01-02 10 10043 10.so.0.43[.0]
+ * 1.2.35rc01-02 13 10235 12.so.0.35[.0]
+ * 1.0.43 10 10043 10.so.0.43[.0]
+ * 1.2.35 13 10235 12.so.0.35[.0]
+ * 1.2.36beta01-05 13 10236 12.so.0.36[.0]
+ * 1.2.36rc01 13 10236 12.so.0.36[.0]
+ * 1.0.44 10 10044 10.so.0.44[.0]
+ * 1.2.36 13 10236 12.so.0.36[.0]
+ * 1.2.37beta01-03 13 10237 12.so.0.37[.0]
+ * 1.2.37rc01 13 10237 12.so.0.37[.0]
+ * 1.2.37 13 10237 12.so.0.37[.0]
+ * 1.2.45 10 10045 12.so.0.45[.0]
+ * 1.0.46 10 10046 10.so.0.46[.0]
+ * 1.2.38beta01 13 10238 12.so.0.38[.0]
+ * 1.2.38rc01-03 13 10238 12.so.0.38[.0]
+ * 1.0.47 10 10047 10.so.0.47[.0]
+ * 1.2.38 13 10238 12.so.0.38[.0]
+ * 1.2.39beta01-05 13 10239 12.so.0.39[.0]
+ * 1.2.39rc01 13 10239 12.so.0.39[.0]
+ * 1.0.48 10 10048 10.so.0.48[.0]
+ * 1.2.39 13 10239 12.so.0.39[.0]
+ * 1.2.40beta01 13 10240 12.so.0.40[.0]
+ * 1.2.40rc01 13 10240 12.so.0.40[.0]
+ * 1.0.49 10 10049 10.so.0.49[.0]
+ * 1.2.40 13 10240 12.so.0.40[.0]
+ * 1.2.41beta01-18 13 10241 12.so.0.41[.0]
+ * 1.0.51rc01 10 10051 10.so.0.51[.0]
+ * 1.2.41rc01-03 13 10241 12.so.0.41[.0]
+ * 1.0.51 10 10051 10.so.0.51[.0]
+ * 1.2.41 13 10241 12.so.0.41[.0]
+ * 1.2.42beta01-02 13 10242 12.so.0.42[.0]
+ * 1.2.42rc01-05 13 10242 12.so.0.42[.0]
+ * 1.0.52 10 10052 10.so.0.52[.0]
+ * 1.2.42 13 10242 12.so.0.42[.0]
+ * 1.2.43beta01-05 13 10243 12.so.0.43[.0]
+ * 1.0.53rc01-02 10 10053 10.so.0.53[.0]
+ * 1.2.43rc01-02 13 10243 12.so.0.43[.0]
+ * 1.0.53 10 10053 10.so.0.53[.0]
+ * 1.2.43 13 10243 12.so.0.43[.0]
+ * 1.2.44beta01-03 13 10244 12.so.0.44[.0]
+ * 1.2.44rc01-03 13 10244 12.so.0.44[.0]
+ * 1.2.44 13 10244 12.so.0.44[.0]
+ *
+ * Henceforth the source version will match the shared-library major
+ * and minor numbers; the shared-library major version number will be
+ * used for changes in backward compatibility, as it is intended. The
+ * PNG_LIBPNG_VER macro, which is not used within libpng but is available
+ * for applications, is an unsigned integer of the form xyyzz corresponding
+ * to the source version x.y.z (leading zeros in y and z). Beta versions
+ * were given the previous public release number plus a letter, until
+ * version 1.0.6j; from then on they were given the upcoming public
+ * release number plus "betaNN" or "rcNN".
+ *
+ * Binary incompatibility exists only when applications make direct access
+ * to the info_ptr or png_ptr members through png.h, and the compiled
+ * application is loaded with a different version of the library.
+ *
+ * DLLNUM will change each time there are forward or backward changes
+ * in binary compatibility (e.g., when a new feature is added).
+ *
+ * See libpng.txt or libpng.3 for more information. The PNG specification
+ * is available as a W3C Recommendation and as an ISO Specification,
+ * <http://www.w3.org/TR/2003/REC-PNG-20031110/
+ */
+
+/*
+ * COPYRIGHT NOTICE, DISCLAIMER, and LICENSE:
+ *
+ * If you modify libpng you may insert additional notices immediately following
+ * this sentence.
+ *
+ * This code is released under the libpng license.
+ *
+ * libpng versions 1.2.6, August 15, 2004, through 1.2.44, June 26, 2010, are
+ * Copyright (c) 2004, 2006-2010 Glenn Randers-Pehrson, and are
+ * distributed according to the same disclaimer and license as libpng-1.2.5
+ * with the following individual added to the list of Contributing Authors:
+ *
+ * Cosmin Truta
+ *
+ * libpng versions 1.0.7, July 1, 2000, through 1.2.5, October 3, 2002, are
+ * Copyright (c) 2000-2002 Glenn Randers-Pehrson, and are
+ * distributed according to the same disclaimer and license as libpng-1.0.6
+ * with the following individuals added to the list of Contributing Authors:
+ *
+ * Simon-Pierre Cadieux
+ * Eric S. Raymond
+ * Gilles Vollant
+ *
+ * and with the following additions to the disclaimer:
+ *
+ * There is no warranty against interference with your enjoyment of the
+ * library or against infringement. There is no warranty that our
+ * efforts or the library will fulfill any of your particular purposes
+ * or needs. This library is provided with all faults, and the entire
+ * risk of satisfactory quality, performance, accuracy, and effort is with
+ * the user.
+ *
+ * libpng versions 0.97, January 1998, through 1.0.6, March 20, 2000, are
+ * Copyright (c) 1998, 1999, 2000 Glenn Randers-Pehrson, and are
+ * distributed according to the same disclaimer and license as libpng-0.96,
+ * with the following individuals added to the list of Contributing Authors:
+ *
+ * Tom Lane
+ * Glenn Randers-Pehrson
+ * Willem van Schaik
+ *
+ * libpng versions 0.89, June 1996, through 0.96, May 1997, are
+ * Copyright (c) 1996, 1997 Andreas Dilger
+ * Distributed according to the same disclaimer and license as libpng-0.88,
+ * with the following individuals added to the list of Contributing Authors:
+ *
+ * John Bowler
+ * Kevin Bracey
+ * Sam Bushell
+ * Magnus Holmgren
+ * Greg Roelofs
+ * Tom Tanner
+ *
+ * libpng versions 0.5, May 1995, through 0.88, January 1996, are
+ * Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.
+ *
+ * For the purposes of this copyright and license, "Contributing Authors"
+ * is defined as the following set of individuals:
+ *
+ * Andreas Dilger
+ * Dave Martindale
+ * Guy Eric Schalnat
+ * Paul Schmidt
+ * Tim Wegner
+ *
+ * The PNG Reference Library is supplied "AS IS". The Contributing Authors
+ * and Group 42, Inc. disclaim all warranties, expressed or implied,
+ * including, without limitation, the warranties of merchantability and of
+ * fitness for any purpose. The Contributing Authors and Group 42, Inc.
+ * assume no liability for direct, indirect, incidental, special, exemplary,
+ * or consequential damages, which may result from the use of the PNG
+ * Reference Library, even if advised of the possibility of such damage.
+ *
+ * Permission is hereby granted to use, copy, modify, and distribute this
+ * source code, or portions hereof, for any purpose, without fee, subject
+ * to the following restrictions:
+ *
+ * 1. The origin of this source code must not be misrepresented.
+ *
+ * 2. Altered versions must be plainly marked as such and
+ * must not be misrepresented as being the original source.
+ *
+ * 3. This Copyright notice may not be removed or altered from
+ * any source or altered source distribution.
+ *
+ * The Contributing Authors and Group 42, Inc. specifically permit, without
+ * fee, and encourage the use of this source code as a component to
+ * supporting the PNG file format in commercial products. If you use this
+ * source code in a product, acknowledgment is not required but would be
+ * appreciated.
+ */
+
+/*
+ * A "png_get_copyright" function is available, for convenient use in "about"
+ * boxes and the like:
+ *
+ * printf("%s",png_get_copyright(NULL));
+ *
+ * Also, the PNG logo (in PNG format, of course) is supplied in the
+ * files "pngbar.png" and "pngbar.jpg (88x31) and "pngnow.png" (98x31).
+ */
+
+/*
+ * Libpng is OSI Certified Open Source Software. OSI Certified is a
+ * certification mark of the Open Source Initiative.
+ */
+
+/*
+ * The contributing authors would like to thank all those who helped
+ * with testing, bug fixes, and patience. This wouldn't have been
+ * possible without all of you.
+ *
+ * Thanks to Frank J. T. Wojcik for helping with the documentation.
+ */
+
+/*
+ * Y2K compliance in libpng:
+ * =========================
+ *
+ * June 26, 2010
+ *
+ * Since the PNG Development group is an ad-hoc body, we can't make
+ * an official declaration.
+ *
+ * This is your unofficial assurance that libpng from version 0.71 and
+ * upward through 1.2.44 are Y2K compliant. It is my belief that earlier
+ * versions were also Y2K compliant.
+ *
+ * Libpng only has three year fields. One is a 2-byte unsigned integer
+ * that will hold years up to 65535. The other two hold the date in text
+ * format, and will hold years up to 9999.
+ *
+ * The integer is
+ * "png_uint_16 year" in png_time_struct.
+ *
+ * The strings are
+ * "png_charp time_buffer" in png_struct and
+ * "near_time_buffer", which is a local character string in png.c.
+ *
+ * There are seven time-related functions:
+ * png.c: png_convert_to_rfc_1123() in png.c
+ * (formerly png_convert_to_rfc_1152() in error)
+ * png_convert_from_struct_tm() in pngwrite.c, called in pngwrite.c
+ * png_convert_from_time_t() in pngwrite.c
+ * png_get_tIME() in pngget.c
+ * png_handle_tIME() in pngrutil.c, called in pngread.c
+ * png_set_tIME() in pngset.c
+ * png_write_tIME() in pngwutil.c, called in pngwrite.c
+ *
+ * All handle dates properly in a Y2K environment. The
+ * png_convert_from_time_t() function calls gmtime() to convert from system
+ * clock time, which returns (year - 1900), which we properly convert to
+ * the full 4-digit year. There is a possibility that applications using
+ * libpng are not passing 4-digit years into the png_convert_to_rfc_1123()
+ * function, or that they are incorrectly passing only a 2-digit year
+ * instead of "year - 1900" into the png_convert_from_struct_tm() function,
+ * but this is not under our control. The libpng documentation has always
+ * stated that it works with 4-digit years, and the APIs have been
+ * documented as such.
+ *
+ * The tIME chunk itself is also Y2K compliant. It uses a 2-byte unsigned
+ * integer to hold the year, and can hold years as large as 65535.
+ *
+ * zlib, upon which libpng depends, is also Y2K compliant. It contains
+ * no date-related code.
+ *
+ * Glenn Randers-Pehrson
+ * libpng maintainer
+ * PNG Development Group
+ */
+
+#ifndef PNG_H
+#define PNG_H
+
+/* This is not the place to learn how to use libpng. The file libpng.txt
+ * describes how to use libpng, and the file example.c summarizes it
+ * with some code on which to build. This file is useful for looking
+ * at the actual function definitions and structure components.
+ */
+
+/* Version information for png.h - this should match the version in png.c */
+#define PNG_LIBPNG_VER_STRING "1.2.44"
+#define PNG_HEADER_VERSION_STRING \
+ " libpng version 1.2.44 - June 26, 2010\n"
+
+#define PNG_LIBPNG_VER_SONUM 0
+#define PNG_LIBPNG_VER_DLLNUM 13
+
+/* These should match the first 3 components of PNG_LIBPNG_VER_STRING: */
+#define PNG_LIBPNG_VER_MAJOR 1
+#define PNG_LIBPNG_VER_MINOR 2
+#define PNG_LIBPNG_VER_RELEASE 44
+/* This should match the numeric part of the final component of
+ * PNG_LIBPNG_VER_STRING, omitting any leading zero:
+ */
+
+#define PNG_LIBPNG_VER_BUILD 0
+
+/* Release Status */
+#define PNG_LIBPNG_BUILD_ALPHA 1
+#define PNG_LIBPNG_BUILD_BETA 2
+#define PNG_LIBPNG_BUILD_RC 3
+#define PNG_LIBPNG_BUILD_STABLE 4
+#define PNG_LIBPNG_BUILD_RELEASE_STATUS_MASK 7
+
+/* Release-Specific Flags */
+#define PNG_LIBPNG_BUILD_PATCH 8 /* Can be OR'ed with
+ PNG_LIBPNG_BUILD_STABLE only */
+#define PNG_LIBPNG_BUILD_PRIVATE 16 /* Cannot be OR'ed with
+ PNG_LIBPNG_BUILD_SPECIAL */
+#define PNG_LIBPNG_BUILD_SPECIAL 32 /* Cannot be OR'ed with
+ PNG_LIBPNG_BUILD_PRIVATE */
+
+#define PNG_LIBPNG_BUILD_BASE_TYPE PNG_LIBPNG_BUILD_STABLE
+
+/* Careful here. At one time, Guy wanted to use 082, but that would be octal.
+ * We must not include leading zeros.
+ * Versions 0.7 through 1.0.0 were in the range 0 to 100 here (only
+ * version 1.0.0 was mis-numbered 100 instead of 10000). From
+ * version 1.0.1 it's xxyyzz, where x=major, y=minor, z=release
+ */
+#define PNG_LIBPNG_VER 10244 /* 1.2.44 */
+
+#ifndef PNG_VERSION_INFO_ONLY
+/* Include the compression library's header */
+#include "zlib.h"
+#endif
+
+/* Include all user configurable info, including optional assembler routines */
+#include "pngconf.h"
+
+/*
+ * Added at libpng-1.2.8 */
+/* Ref MSDN: Private as priority over Special
+ * VS_FF_PRIVATEBUILD File *was not* built using standard release
+ * procedures. If this value is given, the StringFileInfo block must
+ * contain a PrivateBuild string.
+ *
+ * VS_FF_SPECIALBUILD File *was* built by the original company using
+ * standard release procedures but is a variation of the standard
+ * file of the same version number. If this value is given, the
+ * StringFileInfo block must contain a SpecialBuild string.
+ */
+
+#ifdef PNG_USER_PRIVATEBUILD
+# define PNG_LIBPNG_BUILD_TYPE \
+ (PNG_LIBPNG_BUILD_BASE_TYPE | PNG_LIBPNG_BUILD_PRIVATE)
+#else
+# ifdef PNG_LIBPNG_SPECIALBUILD
+# define PNG_LIBPNG_BUILD_TYPE \
+ (PNG_LIBPNG_BUILD_BASE_TYPE | PNG_LIBPNG_BUILD_SPECIAL)
+# else
+# define PNG_LIBPNG_BUILD_TYPE (PNG_LIBPNG_BUILD_BASE_TYPE)
+# endif
+#endif
+
+#ifndef PNG_VERSION_INFO_ONLY
+
+/* Inhibit C++ name-mangling for libpng functions but not for system calls. */
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* This file is arranged in several sections. The first section contains
+ * structure and type definitions. The second section contains the external
+ * library functions, while the third has the internal library functions,
+ * which applications aren't expected to use directly.
+ */
+
+#ifndef PNG_NO_TYPECAST_NULL
+#define int_p_NULL (int *)NULL
+#define png_bytep_NULL (png_bytep)NULL
+#define png_bytepp_NULL (png_bytepp)NULL
+#define png_doublep_NULL (png_doublep)NULL
+#define png_error_ptr_NULL (png_error_ptr)NULL
+#define png_flush_ptr_NULL (png_flush_ptr)NULL
+#define png_free_ptr_NULL (png_free_ptr)NULL
+#define png_infopp_NULL (png_infopp)NULL
+#define png_malloc_ptr_NULL (png_malloc_ptr)NULL
+#define png_read_status_ptr_NULL (png_read_status_ptr)NULL
+#define png_rw_ptr_NULL (png_rw_ptr)NULL
+#define png_structp_NULL (png_structp)NULL
+#define png_uint_16p_NULL (png_uint_16p)NULL
+#define png_voidp_NULL (png_voidp)NULL
+#define png_write_status_ptr_NULL (png_write_status_ptr)NULL
+#else
+#define int_p_NULL NULL
+#define png_bytep_NULL NULL
+#define png_bytepp_NULL NULL
+#define png_doublep_NULL NULL
+#define png_error_ptr_NULL NULL
+#define png_flush_ptr_NULL NULL
+#define png_free_ptr_NULL NULL
+#define png_infopp_NULL NULL
+#define png_malloc_ptr_NULL NULL
+#define png_read_status_ptr_NULL NULL
+#define png_rw_ptr_NULL NULL
+#define png_structp_NULL NULL
+#define png_uint_16p_NULL NULL
+#define png_voidp_NULL NULL
+#define png_write_status_ptr_NULL NULL
+#endif
+
+/* Variables declared in png.c - only it needs to define PNG_NO_EXTERN */
+#if !defined(PNG_NO_EXTERN) || defined(PNG_ALWAYS_EXTERN)
+/* Version information for C files, stored in png.c. This had better match
+ * the version above.
+ */
+#ifdef PNG_USE_GLOBAL_ARRAYS
+PNG_EXPORT_VAR (PNG_CONST char) png_libpng_ver[18];
+ /* Need room for 99.99.99beta99z */
+#else
+#define png_libpng_ver png_get_header_ver(NULL)
+#endif
+
+#ifdef PNG_USE_GLOBAL_ARRAYS
+/* This was removed in version 1.0.5c */
+/* Structures to facilitate easy interlacing. See png.c for more details */
+PNG_EXPORT_VAR (PNG_CONST int FARDATA) png_pass_start[7];
+PNG_EXPORT_VAR (PNG_CONST int FARDATA) png_pass_inc[7];
+PNG_EXPORT_VAR (PNG_CONST int FARDATA) png_pass_ystart[7];
+PNG_EXPORT_VAR (PNG_CONST int FARDATA) png_pass_yinc[7];
+PNG_EXPORT_VAR (PNG_CONST int FARDATA) png_pass_mask[7];
+PNG_EXPORT_VAR (PNG_CONST int FARDATA) png_pass_dsp_mask[7];
+/* This isn't currently used. If you need it, see png.c for more details.
+PNG_EXPORT_VAR (PNG_CONST int FARDATA) png_pass_height[7];
+*/
+#endif
+
+#endif /* PNG_NO_EXTERN */
+
+/* Three color definitions. The order of the red, green, and blue, (and the
+ * exact size) is not important, although the size of the fields need to
+ * be png_byte or png_uint_16 (as defined below).
+ */
+typedef struct png_color_struct
+{
+ png_byte red;
+ png_byte green;
+ png_byte blue;
+} png_color;
+typedef png_color FAR * png_colorp;
+typedef png_color FAR * FAR * png_colorpp;
+
+typedef struct png_color_16_struct
+{
+ png_byte index; /* used for palette files */
+ png_uint_16 red; /* for use in red green blue files */
+ png_uint_16 green;
+ png_uint_16 blue;
+ png_uint_16 gray; /* for use in grayscale files */
+} png_color_16;
+typedef png_color_16 FAR * png_color_16p;
+typedef png_color_16 FAR * FAR * png_color_16pp;
+
+typedef struct png_color_8_struct
+{
+ png_byte red; /* for use in red green blue files */
+ png_byte green;
+ png_byte blue;
+ png_byte gray; /* for use in grayscale files */
+ png_byte alpha; /* for alpha channel files */
+} png_color_8;
+typedef png_color_8 FAR * png_color_8p;
+typedef png_color_8 FAR * FAR * png_color_8pp;
+
+/*
+ * The following two structures are used for the in-core representation
+ * of sPLT chunks.
+ */
+typedef struct png_sPLT_entry_struct
+{
+ png_uint_16 red;
+ png_uint_16 green;
+ png_uint_16 blue;
+ png_uint_16 alpha;
+ png_uint_16 frequency;
+} png_sPLT_entry;
+typedef png_sPLT_entry FAR * png_sPLT_entryp;
+typedef png_sPLT_entry FAR * FAR * png_sPLT_entrypp;
+
+/* When the depth of the sPLT palette is 8 bits, the color and alpha samples
+ * occupy the LSB of their respective members, and the MSB of each member
+ * is zero-filled. The frequency member always occupies the full 16 bits.
+ */
+
+typedef struct png_sPLT_struct
+{
+ png_charp name; /* palette name */
+ png_byte depth; /* depth of palette samples */
+ png_sPLT_entryp entries; /* palette entries */
+ png_int_32 nentries; /* number of palette entries */
+} png_sPLT_t;
+typedef png_sPLT_t FAR * png_sPLT_tp;
+typedef png_sPLT_t FAR * FAR * png_sPLT_tpp;
+
+#ifdef PNG_TEXT_SUPPORTED
+/* png_text holds the contents of a text/ztxt/itxt chunk in a PNG file,
+ * and whether that contents is compressed or not. The "key" field
+ * points to a regular zero-terminated C string. The "text", "lang", and
+ * "lang_key" fields can be regular C strings, empty strings, or NULL pointers.
+ * However, the * structure returned by png_get_text() will always contain
+ * regular zero-terminated C strings (possibly empty), never NULL pointers,
+ * so they can be safely used in printf() and other string-handling functions.
+ */
+typedef struct png_text_struct
+{
+ int compression; /* compression value:
+ -1: tEXt, none
+ 0: zTXt, deflate
+ 1: iTXt, none
+ 2: iTXt, deflate */
+ png_charp key; /* keyword, 1-79 character description of "text" */
+ png_charp text; /* comment, may be an empty string (ie "")
+ or a NULL pointer */
+ png_size_t text_length; /* length of the text string */
+#ifdef PNG_iTXt_SUPPORTED
+ png_size_t itxt_length; /* length of the itxt string */
+ png_charp lang; /* language code, 0-79 characters
+ or a NULL pointer */
+ png_charp lang_key; /* keyword translated UTF-8 string, 0 or more
+ chars or a NULL pointer */
+#endif
+} png_text;
+typedef png_text FAR * png_textp;
+typedef png_text FAR * FAR * png_textpp;
+#endif
+
+/* Supported compression types for text in PNG files (tEXt, and zTXt).
+ * The values of the PNG_TEXT_COMPRESSION_ defines should NOT be changed.
+ */
+#define PNG_TEXT_COMPRESSION_NONE_WR -3
+#define PNG_TEXT_COMPRESSION_zTXt_WR -2
+#define PNG_TEXT_COMPRESSION_NONE -1
+#define PNG_TEXT_COMPRESSION_zTXt 0
+#define PNG_ITXT_COMPRESSION_NONE 1
+#define PNG_ITXT_COMPRESSION_zTXt 2
+#define PNG_TEXT_COMPRESSION_LAST 3 /* Not a valid value */
+
+/* png_time is a way to hold the time in an machine independent way.
+ * Two conversions are provided, both from time_t and struct tm. There
+ * is no portable way to convert to either of these structures, as far
+ * as I know. If you know of a portable way, send it to me. As a side
+ * note - PNG has always been Year 2000 compliant!
+ */
+typedef struct png_time_struct
+{
+ png_uint_16 year; /* full year, as in, 1995 */
+ png_byte month; /* month of year, 1 - 12 */
+ png_byte day; /* day of month, 1 - 31 */
+ png_byte hour; /* hour of day, 0 - 23 */
+ png_byte minute; /* minute of hour, 0 - 59 */
+ png_byte second; /* second of minute, 0 - 60 (for leap seconds) */
+} png_time;
+typedef png_time FAR * png_timep;
+typedef png_time FAR * FAR * png_timepp;
+
+#if defined(PNG_UNKNOWN_CHUNKS_SUPPORTED) || \
+ defined(PNG_HANDLE_AS_UNKNOWN_SUPPORTED)
+/* png_unknown_chunk is a structure to hold queued chunks for which there is
+ * no specific support. The idea is that we can use this to queue
+ * up private chunks for output even though the library doesn't actually
+ * know about their semantics.
+ */
+#define PNG_CHUNK_NAME_LENGTH 5
+typedef struct png_unknown_chunk_t
+{
+ png_byte name[PNG_CHUNK_NAME_LENGTH];
+ png_byte *data;
+ png_size_t size;
+
+ /* libpng-using applications should NOT directly modify this byte. */
+ png_byte location; /* mode of operation at read time */
+}
+png_unknown_chunk;
+typedef png_unknown_chunk FAR * png_unknown_chunkp;
+typedef png_unknown_chunk FAR * FAR * png_unknown_chunkpp;
+#endif
+
+/* png_info is a structure that holds the information in a PNG file so
+ * that the application can find out the characteristics of the image.
+ * If you are reading the file, this structure will tell you what is
+ * in the PNG file. If you are writing the file, fill in the information
+ * you want to put into the PNG file, then call png_write_info().
+ * The names chosen should be very close to the PNG specification, so
+ * consult that document for information about the meaning of each field.
+ *
+ * With libpng < 0.95, it was only possible to directly set and read the
+ * the values in the png_info_struct, which meant that the contents and
+ * order of the values had to remain fixed. With libpng 0.95 and later,
+ * however, there are now functions that abstract the contents of
+ * png_info_struct from the application, so this makes it easier to use
+ * libpng with dynamic libraries, and even makes it possible to use
+ * libraries that don't have all of the libpng ancillary chunk-handing
+ * functionality.
+ *
+ * In any case, the order of the parameters in png_info_struct should NOT
+ * be changed for as long as possible to keep compatibility with applications
+ * that use the old direct-access method with png_info_struct.
+ *
+ * The following members may have allocated storage attached that should be
+ * cleaned up before the structure is discarded: palette, trans, text,
+ * pcal_purpose, pcal_units, pcal_params, hist, iccp_name, iccp_profile,
+ * splt_palettes, scal_unit, row_pointers, and unknowns. By default, these
+ * are automatically freed when the info structure is deallocated, if they were
+ * allocated internally by libpng. This behavior can be changed by means
+ * of the png_data_freer() function.
+ *
+ * More allocation details: all the chunk-reading functions that
+ * change these members go through the corresponding png_set_*
+ * functions. A function to clear these members is available: see
+ * png_free_data(). The png_set_* functions do not depend on being
+ * able to point info structure members to any of the storage they are
+ * passed (they make their own copies), EXCEPT that the png_set_text
+ * functions use the same storage passed to them in the text_ptr or
+ * itxt_ptr structure argument, and the png_set_rows and png_set_unknowns
+ * functions do not make their own copies.
+ */
+typedef struct png_info_struct
+{
+ /* The following are necessary for every PNG file */
+ png_uint_32 width PNG_DEPSTRUCT; /* width of image in pixels (from IHDR) */
+ png_uint_32 height PNG_DEPSTRUCT; /* height of image in pixels (from IHDR) */
+ png_uint_32 valid PNG_DEPSTRUCT; /* valid chunk data (see PNG_INFO_ below) */
+ png_uint_32 rowbytes PNG_DEPSTRUCT; /* bytes needed to hold an untransformed row */
+ png_colorp palette PNG_DEPSTRUCT; /* array of color values (valid & PNG_INFO_PLTE) */
+ png_uint_16 num_palette PNG_DEPSTRUCT; /* number of color entries in "palette" (PLTE) */
+ png_uint_16 num_trans PNG_DEPSTRUCT; /* number of transparent palette color (tRNS) */
+ png_byte bit_depth PNG_DEPSTRUCT; /* 1, 2, 4, 8, or 16 bits/channel (from IHDR) */
+ png_byte color_type PNG_DEPSTRUCT; /* see PNG_COLOR_TYPE_ below (from IHDR) */
+ /* The following three should have been named *_method not *_type */
+ png_byte compression_type PNG_DEPSTRUCT; /* must be PNG_COMPRESSION_TYPE_BASE (IHDR) */
+ png_byte filter_type PNG_DEPSTRUCT; /* must be PNG_FILTER_TYPE_BASE (from IHDR) */
+ png_byte interlace_type PNG_DEPSTRUCT; /* One of PNG_INTERLACE_NONE, PNG_INTERLACE_ADAM7 */
+
+ /* The following is informational only on read, and not used on writes. */
+ png_byte channels PNG_DEPSTRUCT; /* number of data channels per pixel (1, 2, 3, 4) */
+ png_byte pixel_depth PNG_DEPSTRUCT; /* number of bits per pixel */
+ png_byte spare_byte PNG_DEPSTRUCT; /* to align the data, and for future use */
+ png_byte signature[8] PNG_DEPSTRUCT; /* magic bytes read by libpng from start of file */
+
+ /* The rest of the data is optional. If you are reading, check the
+ * valid field to see if the information in these are valid. If you
+ * are writing, set the valid field to those chunks you want written,
+ * and initialize the appropriate fields below.
+ */
+
+#if defined(PNG_gAMA_SUPPORTED) && defined(PNG_FLOATING_POINT_SUPPORTED)
+ /* The gAMA chunk describes the gamma characteristics of the system
+ * on which the image was created, normally in the range [1.0, 2.5].
+ * Data is valid if (valid & PNG_INFO_gAMA) is non-zero.
+ */
+ float gamma PNG_DEPSTRUCT; /* gamma value of image, if (valid & PNG_INFO_gAMA) */
+#endif
+
+#ifdef PNG_sRGB_SUPPORTED
+ /* GR-P, 0.96a */
+ /* Data valid if (valid & PNG_INFO_sRGB) non-zero. */
+ png_byte srgb_intent PNG_DEPSTRUCT; /* sRGB rendering intent [0, 1, 2, or 3] */
+#endif
+
+#ifdef PNG_TEXT_SUPPORTED
+ /* The tEXt, and zTXt chunks contain human-readable textual data in
+ * uncompressed, compressed, and optionally compressed forms, respectively.
+ * The data in "text" is an array of pointers to uncompressed,
+ * null-terminated C strings. Each chunk has a keyword that describes the
+ * textual data contained in that chunk. Keywords are not required to be
+ * unique, and the text string may be empty. Any number of text chunks may
+ * be in an image.
+ */
+ int num_text PNG_DEPSTRUCT; /* number of comments read/to write */
+ int max_text PNG_DEPSTRUCT; /* current size of text array */
+ png_textp text PNG_DEPSTRUCT; /* array of comments read/to write */
+#endif /* PNG_TEXT_SUPPORTED */
+
+#ifdef PNG_tIME_SUPPORTED
+ /* The tIME chunk holds the last time the displayed image data was
+ * modified. See the png_time struct for the contents of this struct.
+ */
+ png_time mod_time PNG_DEPSTRUCT;
+#endif
+
+#ifdef PNG_sBIT_SUPPORTED
+ /* The sBIT chunk specifies the number of significant high-order bits
+ * in the pixel data. Values are in the range [1, bit_depth], and are
+ * only specified for the channels in the pixel data. The contents of
+ * the low-order bits is not specified. Data is valid if
+ * (valid & PNG_INFO_sBIT) is non-zero.
+ */
+ png_color_8 sig_bit PNG_DEPSTRUCT; /* significant bits in color channels */
+#endif
+
+#if defined(PNG_tRNS_SUPPORTED) || defined(PNG_READ_EXPAND_SUPPORTED) || \
+defined(PNG_READ_BACKGROUND_SUPPORTED)
+ /* The tRNS chunk supplies transparency data for paletted images and
+ * other image types that don't need a full alpha channel. There are
+ * "num_trans" transparency values for a paletted image, stored in the
+ * same order as the palette colors, starting from index 0. Values
+ * for the data are in the range [0, 255], ranging from fully transparent
+ * to fully opaque, respectively. For non-paletted images, there is a
+ * single color specified that should be treated as fully transparent.
+ * Data is valid if (valid & PNG_INFO_tRNS) is non-zero.
+ */
+ png_bytep trans PNG_DEPSTRUCT; /* transparent values for paletted image */
+ png_color_16 trans_values PNG_DEPSTRUCT; /* transparent color for non-palette image */
+#endif
+
+#if defined(PNG_bKGD_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED)
+ /* The bKGD chunk gives the suggested image background color if the
+ * display program does not have its own background color and the image
+ * is needs to composited onto a background before display. The colors
+ * in "background" are normally in the same color space/depth as the
+ * pixel data. Data is valid if (valid & PNG_INFO_bKGD) is non-zero.
+ */
+ png_color_16 background PNG_DEPSTRUCT;
+#endif
+
+#ifdef PNG_oFFs_SUPPORTED
+ /* The oFFs chunk gives the offset in "offset_unit_type" units rightwards
+ * and downwards from the top-left corner of the display, page, or other
+ * application-specific co-ordinate space. See the PNG_OFFSET_ defines
+ * below for the unit types. Valid if (valid & PNG_INFO_oFFs) non-zero.
+ */
+ png_int_32 x_offset PNG_DEPSTRUCT; /* x offset on page */
+ png_int_32 y_offset PNG_DEPSTRUCT; /* y offset on page */
+ png_byte offset_unit_type PNG_DEPSTRUCT; /* offset units type */
+#endif
+
+#ifdef PNG_pHYs_SUPPORTED
+ /* The pHYs chunk gives the physical pixel density of the image for
+ * display or printing in "phys_unit_type" units (see PNG_RESOLUTION_
+ * defines below). Data is valid if (valid & PNG_INFO_pHYs) is non-zero.
+ */
+ png_uint_32 x_pixels_per_unit PNG_DEPSTRUCT; /* horizontal pixel density */
+ png_uint_32 y_pixels_per_unit PNG_DEPSTRUCT; /* vertical pixel density */
+ png_byte phys_unit_type PNG_DEPSTRUCT; /* resolution type (see PNG_RESOLUTION_ below) */
+#endif
+
+#ifdef PNG_hIST_SUPPORTED
+ /* The hIST chunk contains the relative frequency or importance of the
+ * various palette entries, so that a viewer can intelligently select a
+ * reduced-color palette, if required. Data is an array of "num_palette"
+ * values in the range [0,65535]. Data valid if (valid & PNG_INFO_hIST)
+ * is non-zero.
+ */
+ png_uint_16p hist PNG_DEPSTRUCT;
+#endif
+
+#ifdef PNG_cHRM_SUPPORTED
+ /* The cHRM chunk describes the CIE color characteristics of the monitor
+ * on which the PNG was created. This data allows the viewer to do gamut
+ * mapping of the input image to ensure that the viewer sees the same
+ * colors in the image as the creator. Values are in the range
+ * [0.0, 0.8]. Data valid if (valid & PNG_INFO_cHRM) non-zero.
+ */
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+ float x_white PNG_DEPSTRUCT;
+ float y_white PNG_DEPSTRUCT;
+ float x_red PNG_DEPSTRUCT;
+ float y_red PNG_DEPSTRUCT;
+ float x_green PNG_DEPSTRUCT;
+ float y_green PNG_DEPSTRUCT;
+ float x_blue PNG_DEPSTRUCT;
+ float y_blue PNG_DEPSTRUCT;
+#endif
+#endif
+
+#ifdef PNG_pCAL_SUPPORTED
+ /* The pCAL chunk describes a transformation between the stored pixel
+ * values and original physical data values used to create the image.
+ * The integer range [0, 2^bit_depth - 1] maps to the floating-point
+ * range given by [pcal_X0, pcal_X1], and are further transformed by a
+ * (possibly non-linear) transformation function given by "pcal_type"
+ * and "pcal_params" into "pcal_units". Please see the PNG_EQUATION_
+ * defines below, and the PNG-Group's PNG extensions document for a
+ * complete description of the transformations and how they should be
+ * implemented, and for a description of the ASCII parameter strings.
+ * Data values are valid if (valid & PNG_INFO_pCAL) non-zero.
+ */
+ png_charp pcal_purpose PNG_DEPSTRUCT; /* pCAL chunk description string */
+ png_int_32 pcal_X0 PNG_DEPSTRUCT; /* minimum value */
+ png_int_32 pcal_X1 PNG_DEPSTRUCT; /* maximum value */
+ png_charp pcal_units PNG_DEPSTRUCT; /* Latin-1 string giving physical units */
+ png_charpp pcal_params PNG_DEPSTRUCT; /* ASCII strings containing parameter values */
+ png_byte pcal_type PNG_DEPSTRUCT; /* equation type (see PNG_EQUATION_ below) */
+ png_byte pcal_nparams PNG_DEPSTRUCT; /* number of parameters given in pcal_params */
+#endif
+
+/* New members added in libpng-1.0.6 */
+#ifdef PNG_FREE_ME_SUPPORTED
+ png_uint_32 free_me PNG_DEPSTRUCT; /* flags items libpng is responsible for freeing */
+#endif
+
+#if defined(PNG_UNKNOWN_CHUNKS_SUPPORTED) || \
+ defined(PNG_HANDLE_AS_UNKNOWN_SUPPORTED)
+ /* Storage for unknown chunks that the library doesn't recognize. */
+ png_unknown_chunkp unknown_chunks PNG_DEPSTRUCT;
+ png_size_t unknown_chunks_num PNG_DEPSTRUCT;
+#endif
+
+#ifdef PNG_iCCP_SUPPORTED
+ /* iCCP chunk data. */
+ png_charp iccp_name PNG_DEPSTRUCT; /* profile name */
+ png_charp iccp_profile PNG_DEPSTRUCT; /* International Color Consortium profile data */
+ /* Note to maintainer: should be png_bytep */
+ png_uint_32 iccp_proflen PNG_DEPSTRUCT; /* ICC profile data length */
+ png_byte iccp_compression PNG_DEPSTRUCT; /* Always zero */
+#endif
+
+#ifdef PNG_sPLT_SUPPORTED
+ /* Data on sPLT chunks (there may be more than one). */
+ png_sPLT_tp splt_palettes PNG_DEPSTRUCT;
+ png_uint_32 splt_palettes_num PNG_DEPSTRUCT;
+#endif
+
+#ifdef PNG_sCAL_SUPPORTED
+ /* The sCAL chunk describes the actual physical dimensions of the
+ * subject matter of the graphic. The chunk contains a unit specification
+ * a byte value, and two ASCII strings representing floating-point
+ * values. The values are width and height corresponsing to one pixel
+ * in the image. This external representation is converted to double
+ * here. Data values are valid if (valid & PNG_INFO_sCAL) is non-zero.
+ */
+ png_byte scal_unit PNG_DEPSTRUCT; /* unit of physical scale */
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+ double scal_pixel_width PNG_DEPSTRUCT; /* width of one pixel */
+ double scal_pixel_height PNG_DEPSTRUCT; /* height of one pixel */
+#endif
+#ifdef PNG_FIXED_POINT_SUPPORTED
+ png_charp scal_s_width PNG_DEPSTRUCT; /* string containing height */
+ png_charp scal_s_height PNG_DEPSTRUCT; /* string containing width */
+#endif
+#endif
+
+#ifdef PNG_INFO_IMAGE_SUPPORTED
+ /* Memory has been allocated if (valid & PNG_ALLOCATED_INFO_ROWS) non-zero */
+ /* Data valid if (valid & PNG_INFO_IDAT) non-zero */
+ png_bytepp row_pointers PNG_DEPSTRUCT; /* the image bits */
+#endif
+
+#if defined(PNG_FIXED_POINT_SUPPORTED) && defined(PNG_gAMA_SUPPORTED)
+ png_fixed_point int_gamma PNG_DEPSTRUCT; /* gamma of image, if (valid & PNG_INFO_gAMA) */
+#endif
+
+#if defined(PNG_cHRM_SUPPORTED) && defined(PNG_FIXED_POINT_SUPPORTED)
+ png_fixed_point int_x_white PNG_DEPSTRUCT;
+ png_fixed_point int_y_white PNG_DEPSTRUCT;
+ png_fixed_point int_x_red PNG_DEPSTRUCT;
+ png_fixed_point int_y_red PNG_DEPSTRUCT;
+ png_fixed_point int_x_green PNG_DEPSTRUCT;
+ png_fixed_point int_y_green PNG_DEPSTRUCT;
+ png_fixed_point int_x_blue PNG_DEPSTRUCT;
+ png_fixed_point int_y_blue PNG_DEPSTRUCT;
+#endif
+
+} png_info;
+
+typedef png_info FAR * png_infop;
+typedef png_info FAR * FAR * png_infopp;
+
+/* Maximum positive integer used in PNG is (2^31)-1 */
+#define PNG_UINT_31_MAX ((png_uint_32)0x7fffffffL)
+#define PNG_UINT_32_MAX ((png_uint_32)(-1))
+#define PNG_SIZE_MAX ((png_size_t)(-1))
+#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
+/* PNG_MAX_UINT is deprecated; use PNG_UINT_31_MAX instead. */
+#define PNG_MAX_UINT PNG_UINT_31_MAX
+#endif
+
+/* These describe the color_type field in png_info. */
+/* color type masks */
+#define PNG_COLOR_MASK_PALETTE 1
+#define PNG_COLOR_MASK_COLOR 2
+#define PNG_COLOR_MASK_ALPHA 4
+
+/* color types. Note that not all combinations are legal */
+#define PNG_COLOR_TYPE_GRAY 0
+#define PNG_COLOR_TYPE_PALETTE (PNG_COLOR_MASK_COLOR | PNG_COLOR_MASK_PALETTE)
+#define PNG_COLOR_TYPE_RGB (PNG_COLOR_MASK_COLOR)
+#define PNG_COLOR_TYPE_RGB_ALPHA (PNG_COLOR_MASK_COLOR | PNG_COLOR_MASK_ALPHA)
+#define PNG_COLOR_TYPE_GRAY_ALPHA (PNG_COLOR_MASK_ALPHA)
+/* aliases */
+#define PNG_COLOR_TYPE_RGBA PNG_COLOR_TYPE_RGB_ALPHA
+#define PNG_COLOR_TYPE_GA PNG_COLOR_TYPE_GRAY_ALPHA
+
+/* This is for compression type. PNG 1.0-1.2 only define the single type. */
+#define PNG_COMPRESSION_TYPE_BASE 0 /* Deflate method 8, 32K window */
+#define PNG_COMPRESSION_TYPE_DEFAULT PNG_COMPRESSION_TYPE_BASE
+
+/* This is for filter type. PNG 1.0-1.2 only define the single type. */
+#define PNG_FILTER_TYPE_BASE 0 /* Single row per-byte filtering */
+#define PNG_INTRAPIXEL_DIFFERENCING 64 /* Used only in MNG datastreams */
+#define PNG_FILTER_TYPE_DEFAULT PNG_FILTER_TYPE_BASE
+
+/* These are for the interlacing type. These values should NOT be changed. */
+#define PNG_INTERLACE_NONE 0 /* Non-interlaced image */
+#define PNG_INTERLACE_ADAM7 1 /* Adam7 interlacing */
+#define PNG_INTERLACE_LAST 2 /* Not a valid value */
+
+/* These are for the oFFs chunk. These values should NOT be changed. */
+#define PNG_OFFSET_PIXEL 0 /* Offset in pixels */
+#define PNG_OFFSET_MICROMETER 1 /* Offset in micrometers (1/10^6 meter) */
+#define PNG_OFFSET_LAST 2 /* Not a valid value */
+
+/* These are for the pCAL chunk. These values should NOT be changed. */
+#define PNG_EQUATION_LINEAR 0 /* Linear transformation */
+#define PNG_EQUATION_BASE_E 1 /* Exponential base e transform */
+#define PNG_EQUATION_ARBITRARY 2 /* Arbitrary base exponential transform */
+#define PNG_EQUATION_HYPERBOLIC 3 /* Hyperbolic sine transformation */
+#define PNG_EQUATION_LAST 4 /* Not a valid value */
+
+/* These are for the sCAL chunk. These values should NOT be changed. */
+#define PNG_SCALE_UNKNOWN 0 /* unknown unit (image scale) */
+#define PNG_SCALE_METER 1 /* meters per pixel */
+#define PNG_SCALE_RADIAN 2 /* radians per pixel */
+#define PNG_SCALE_LAST 3 /* Not a valid value */
+
+/* These are for the pHYs chunk. These values should NOT be changed. */
+#define PNG_RESOLUTION_UNKNOWN 0 /* pixels/unknown unit (aspect ratio) */
+#define PNG_RESOLUTION_METER 1 /* pixels/meter */
+#define PNG_RESOLUTION_LAST 2 /* Not a valid value */
+
+/* These are for the sRGB chunk. These values should NOT be changed. */
+#define PNG_sRGB_INTENT_PERCEPTUAL 0
+#define PNG_sRGB_INTENT_RELATIVE 1
+#define PNG_sRGB_INTENT_SATURATION 2
+#define PNG_sRGB_INTENT_ABSOLUTE 3
+#define PNG_sRGB_INTENT_LAST 4 /* Not a valid value */
+
+/* This is for text chunks */
+#define PNG_KEYWORD_MAX_LENGTH 79
+
+/* Maximum number of entries in PLTE/sPLT/tRNS arrays */
+#define PNG_MAX_PALETTE_LENGTH 256
+
+/* These determine if an ancillary chunk's data has been successfully read
+ * from the PNG header, or if the application has filled in the corresponding
+ * data in the info_struct to be written into the output file. The values
+ * of the PNG_INFO_<chunk> defines should NOT be changed.
+ */
+#define PNG_INFO_gAMA 0x0001
+#define PNG_INFO_sBIT 0x0002
+#define PNG_INFO_cHRM 0x0004
+#define PNG_INFO_PLTE 0x0008
+#define PNG_INFO_tRNS 0x0010
+#define PNG_INFO_bKGD 0x0020
+#define PNG_INFO_hIST 0x0040
+#define PNG_INFO_pHYs 0x0080
+#define PNG_INFO_oFFs 0x0100
+#define PNG_INFO_tIME 0x0200
+#define PNG_INFO_pCAL 0x0400
+#define PNG_INFO_sRGB 0x0800 /* GR-P, 0.96a */
+#define PNG_INFO_iCCP 0x1000 /* ESR, 1.0.6 */
+#define PNG_INFO_sPLT 0x2000 /* ESR, 1.0.6 */
+#define PNG_INFO_sCAL 0x4000 /* ESR, 1.0.6 */
+#define PNG_INFO_IDAT 0x8000L /* ESR, 1.0.6 */
+
+/* This is used for the transformation routines, as some of them
+ * change these values for the row. It also should enable using
+ * the routines for other purposes.
+ */
+typedef struct png_row_info_struct
+{
+ png_uint_32 width; /* width of row */
+ png_uint_32 rowbytes; /* number of bytes in row */
+ png_byte color_type; /* color type of row */
+ png_byte bit_depth; /* bit depth of row */
+ png_byte channels; /* number of channels (1, 2, 3, or 4) */
+ png_byte pixel_depth; /* bits per pixel (depth * channels) */
+} png_row_info;
+
+typedef png_row_info FAR * png_row_infop;
+typedef png_row_info FAR * FAR * png_row_infopp;
+
+/* These are the function types for the I/O functions and for the functions
+ * that allow the user to override the default I/O functions with his or her
+ * own. The png_error_ptr type should match that of user-supplied warning
+ * and error functions, while the png_rw_ptr type should match that of the
+ * user read/write data functions.
+ */
+typedef struct png_struct_def png_struct;
+typedef png_struct FAR * png_structp;
+
+typedef void (PNGAPI *png_error_ptr) PNGARG((png_structp, png_const_charp));
+typedef void (PNGAPI *png_rw_ptr) PNGARG((png_structp, png_bytep, png_size_t));
+typedef void (PNGAPI *png_flush_ptr) PNGARG((png_structp));
+typedef void (PNGAPI *png_read_status_ptr) PNGARG((png_structp, png_uint_32,
+ int));
+typedef void (PNGAPI *png_write_status_ptr) PNGARG((png_structp, png_uint_32,
+ int));
+
+#ifdef PNG_PROGRESSIVE_READ_SUPPORTED
+typedef void (PNGAPI *png_progressive_info_ptr) PNGARG((png_structp, png_infop));
+typedef void (PNGAPI *png_progressive_end_ptr) PNGARG((png_structp, png_infop));
+typedef void (PNGAPI *png_progressive_row_ptr) PNGARG((png_structp, png_bytep,
+ png_uint_32, int));
+#endif
+
+#if defined(PNG_READ_USER_TRANSFORM_SUPPORTED) || \
+ defined(PNG_WRITE_USER_TRANSFORM_SUPPORTED) || \
+ defined(PNG_LEGACY_SUPPORTED)
+typedef void (PNGAPI *png_user_transform_ptr) PNGARG((png_structp,
+ png_row_infop, png_bytep));
+#endif
+
+#ifdef PNG_USER_CHUNKS_SUPPORTED
+typedef int (PNGAPI *png_user_chunk_ptr) PNGARG((png_structp, png_unknown_chunkp));
+#endif
+#ifdef PNG_UNKNOWN_CHUNKS_SUPPORTED
+typedef void (PNGAPI *png_unknown_chunk_ptr) PNGARG((png_structp));
+#endif
+
+/* Transform masks for the high-level interface */
+#define PNG_TRANSFORM_IDENTITY 0x0000 /* read and write */
+#define PNG_TRANSFORM_STRIP_16 0x0001 /* read only */
+#define PNG_TRANSFORM_STRIP_ALPHA 0x0002 /* read only */
+#define PNG_TRANSFORM_PACKING 0x0004 /* read and write */
+#define PNG_TRANSFORM_PACKSWAP 0x0008 /* read and write */
+#define PNG_TRANSFORM_EXPAND 0x0010 /* read only */
+#define PNG_TRANSFORM_INVERT_MONO 0x0020 /* read and write */
+#define PNG_TRANSFORM_SHIFT 0x0040 /* read and write */
+#define PNG_TRANSFORM_BGR 0x0080 /* read and write */
+#define PNG_TRANSFORM_SWAP_ALPHA 0x0100 /* read and write */
+#define PNG_TRANSFORM_SWAP_ENDIAN 0x0200 /* read and write */
+#define PNG_TRANSFORM_INVERT_ALPHA 0x0400 /* read and write */
+#define PNG_TRANSFORM_STRIP_FILLER 0x0800 /* write only, deprecated */
+/* Added to libpng-1.2.34 */
+#define PNG_TRANSFORM_STRIP_FILLER_BEFORE 0x0800 /* write only */
+#define PNG_TRANSFORM_STRIP_FILLER_AFTER 0x1000 /* write only */
+/* Added to libpng-1.2.41 */
+#define PNG_TRANSFORM_GRAY_TO_RGB 0x2000 /* read only */
+
+/* Flags for MNG supported features */
+#define PNG_FLAG_MNG_EMPTY_PLTE 0x01
+#define PNG_FLAG_MNG_FILTER_64 0x04
+#define PNG_ALL_MNG_FEATURES 0x05
+
+typedef png_voidp (*png_malloc_ptr) PNGARG((png_structp, png_size_t));
+typedef void (*png_free_ptr) PNGARG((png_structp, png_voidp));
+
+/* The structure that holds the information to read and write PNG files.
+ * The only people who need to care about what is inside of this are the
+ * people who will be modifying the library for their own special needs.
+ * It should NOT be accessed directly by an application, except to store
+ * the jmp_buf.
+ */
+
+struct png_struct_def
+{
+#ifdef PNG_SETJMP_SUPPORTED
+ jmp_buf jmpbuf; /* used in png_error */
+#endif
+ png_error_ptr error_fn PNG_DEPSTRUCT; /* function for printing errors and aborting */
+ png_error_ptr warning_fn PNG_DEPSTRUCT; /* function for printing warnings */
+ png_voidp error_ptr PNG_DEPSTRUCT; /* user supplied struct for error functions */
+ png_rw_ptr write_data_fn PNG_DEPSTRUCT; /* function for writing output data */
+ png_rw_ptr read_data_fn PNG_DEPSTRUCT; /* function for reading input data */
+ png_voidp io_ptr PNG_DEPSTRUCT; /* ptr to application struct for I/O functions */
+
+#ifdef PNG_READ_USER_TRANSFORM_SUPPORTED
+ png_user_transform_ptr read_user_transform_fn PNG_DEPSTRUCT; /* user read transform */
+#endif
+
+#ifdef PNG_WRITE_USER_TRANSFORM_SUPPORTED
+ png_user_transform_ptr write_user_transform_fn PNG_DEPSTRUCT; /* user write transform */
+#endif
+
+/* These were added in libpng-1.0.2 */
+#ifdef PNG_USER_TRANSFORM_PTR_SUPPORTED
+#if defined(PNG_READ_USER_TRANSFORM_SUPPORTED) || \
+ defined(PNG_WRITE_USER_TRANSFORM_SUPPORTED)
+ png_voidp user_transform_ptr PNG_DEPSTRUCT; /* user supplied struct for user transform */
+ png_byte user_transform_depth PNG_DEPSTRUCT; /* bit depth of user transformed pixels */
+ png_byte user_transform_channels PNG_DEPSTRUCT; /* channels in user transformed pixels */
+#endif
+#endif
+
+ png_uint_32 mode PNG_DEPSTRUCT; /* tells us where we are in the PNG file */
+ png_uint_32 flags PNG_DEPSTRUCT; /* flags indicating various things to libpng */
+ png_uint_32 transformations PNG_DEPSTRUCT; /* which transformations to perform */
+
+ z_stream zstream PNG_DEPSTRUCT; /* pointer to decompression structure (below) */
+ png_bytep zbuf PNG_DEPSTRUCT; /* buffer for zlib */
+ png_size_t zbuf_size PNG_DEPSTRUCT; /* size of zbuf */
+ int zlib_level PNG_DEPSTRUCT; /* holds zlib compression level */
+ int zlib_method PNG_DEPSTRUCT; /* holds zlib compression method */
+ int zlib_window_bits PNG_DEPSTRUCT; /* holds zlib compression window bits */
+ int zlib_mem_level PNG_DEPSTRUCT; /* holds zlib compression memory level */
+ int zlib_strategy PNG_DEPSTRUCT; /* holds zlib compression strategy */
+
+ png_uint_32 width PNG_DEPSTRUCT; /* width of image in pixels */
+ png_uint_32 height PNG_DEPSTRUCT; /* height of image in pixels */
+ png_uint_32 num_rows PNG_DEPSTRUCT; /* number of rows in current pass */
+ png_uint_32 usr_width PNG_DEPSTRUCT; /* width of row at start of write */
+ png_uint_32 rowbytes PNG_DEPSTRUCT; /* size of row in bytes */
+#if 0 /* Replaced with the following in libpng-1.2.43 */
+ png_size_t irowbytes PNG_DEPSTRUCT;
+#endif
+/* Added in libpng-1.2.43 */
+#ifdef PNG_USER_LIMITS_SUPPORTED
+ /* Added in libpng-1.4.0: Total number of sPLT, text, and unknown
+ * chunks that can be stored (0 means unlimited).
+ */
+ png_uint_32 user_chunk_cache_max PNG_DEPSTRUCT;
+#endif
+ png_uint_32 iwidth PNG_DEPSTRUCT; /* width of current interlaced row in pixels */
+ png_uint_32 row_number PNG_DEPSTRUCT; /* current row in interlace pass */
+ png_bytep prev_row PNG_DEPSTRUCT; /* buffer to save previous (unfiltered) row */
+ png_bytep row_buf PNG_DEPSTRUCT; /* buffer to save current (unfiltered) row */
+#ifndef PNG_NO_WRITE_FILTER
+ png_bytep sub_row PNG_DEPSTRUCT; /* buffer to save "sub" row when filtering */
+ png_bytep up_row PNG_DEPSTRUCT; /* buffer to save "up" row when filtering */
+ png_bytep avg_row PNG_DEPSTRUCT; /* buffer to save "avg" row when filtering */
+ png_bytep paeth_row PNG_DEPSTRUCT; /* buffer to save "Paeth" row when filtering */
+#endif
+ png_row_info row_info PNG_DEPSTRUCT; /* used for transformation routines */
+
+ png_uint_32 idat_size PNG_DEPSTRUCT; /* current IDAT size for read */
+ png_uint_32 crc PNG_DEPSTRUCT; /* current chunk CRC value */
+ png_colorp palette PNG_DEPSTRUCT; /* palette from the input file */
+ png_uint_16 num_palette PNG_DEPSTRUCT; /* number of color entries in palette */
+ png_uint_16 num_trans PNG_DEPSTRUCT; /* number of transparency values */
+ png_byte chunk_name[5] PNG_DEPSTRUCT; /* null-terminated name of current chunk */
+ png_byte compression PNG_DEPSTRUCT; /* file compression type (always 0) */
+ png_byte filter PNG_DEPSTRUCT; /* file filter type (always 0) */
+ png_byte interlaced PNG_DEPSTRUCT; /* PNG_INTERLACE_NONE, PNG_INTERLACE_ADAM7 */
+ png_byte pass PNG_DEPSTRUCT; /* current interlace pass (0 - 6) */
+ png_byte do_filter PNG_DEPSTRUCT; /* row filter flags (see PNG_FILTER_ below ) */
+ png_byte color_type PNG_DEPSTRUCT; /* color type of file */
+ png_byte bit_depth PNG_DEPSTRUCT; /* bit depth of file */
+ png_byte usr_bit_depth PNG_DEPSTRUCT; /* bit depth of users row */
+ png_byte pixel_depth PNG_DEPSTRUCT; /* number of bits per pixel */
+ png_byte channels PNG_DEPSTRUCT; /* number of channels in file */
+ png_byte usr_channels PNG_DEPSTRUCT; /* channels at start of write */
+ png_byte sig_bytes PNG_DEPSTRUCT; /* magic bytes read/written from start of file */
+
+#if defined(PNG_READ_FILLER_SUPPORTED) || defined(PNG_WRITE_FILLER_SUPPORTED)
+#ifdef PNG_LEGACY_SUPPORTED
+ png_byte filler PNG_DEPSTRUCT; /* filler byte for pixel expansion */
+#else
+ png_uint_16 filler PNG_DEPSTRUCT; /* filler bytes for pixel expansion */
+#endif
+#endif
+
+#ifdef PNG_bKGD_SUPPORTED
+ png_byte background_gamma_type PNG_DEPSTRUCT;
+# ifdef PNG_FLOATING_POINT_SUPPORTED
+ float background_gamma PNG_DEPSTRUCT;
+# endif
+ png_color_16 background PNG_DEPSTRUCT; /* background color in screen gamma space */
+#ifdef PNG_READ_GAMMA_SUPPORTED
+ png_color_16 background_1 PNG_DEPSTRUCT; /* background normalized to gamma 1.0 */
+#endif
+#endif /* PNG_bKGD_SUPPORTED */
+
+#ifdef PNG_WRITE_FLUSH_SUPPORTED
+ png_flush_ptr output_flush_fn PNG_DEPSTRUCT; /* Function for flushing output */
+ png_uint_32 flush_dist PNG_DEPSTRUCT; /* how many rows apart to flush, 0 - no flush */
+ png_uint_32 flush_rows PNG_DEPSTRUCT; /* number of rows written since last flush */
+#endif
+
+#if defined(PNG_READ_GAMMA_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED)
+ int gamma_shift PNG_DEPSTRUCT; /* number of "insignificant" bits 16-bit gamma */
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+ float gamma PNG_DEPSTRUCT; /* file gamma value */
+ float screen_gamma PNG_DEPSTRUCT; /* screen gamma value (display_exponent) */
+#endif
+#endif
+
+#if defined(PNG_READ_GAMMA_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED)
+ png_bytep gamma_table PNG_DEPSTRUCT; /* gamma table for 8-bit depth files */
+ png_bytep gamma_from_1 PNG_DEPSTRUCT; /* converts from 1.0 to screen */
+ png_bytep gamma_to_1 PNG_DEPSTRUCT; /* converts from file to 1.0 */
+ png_uint_16pp gamma_16_table PNG_DEPSTRUCT; /* gamma table for 16-bit depth files */
+ png_uint_16pp gamma_16_from_1 PNG_DEPSTRUCT; /* converts from 1.0 to screen */
+ png_uint_16pp gamma_16_to_1 PNG_DEPSTRUCT; /* converts from file to 1.0 */
+#endif
+
+#if defined(PNG_READ_GAMMA_SUPPORTED) || defined(PNG_sBIT_SUPPORTED)
+ png_color_8 sig_bit PNG_DEPSTRUCT; /* significant bits in each available channel */
+#endif
+
+#if defined(PNG_READ_SHIFT_SUPPORTED) || defined(PNG_WRITE_SHIFT_SUPPORTED)
+ png_color_8 shift PNG_DEPSTRUCT; /* shift for significant bit tranformation */
+#endif
+
+#if defined(PNG_tRNS_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED) \
+ || defined(PNG_READ_EXPAND_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED)
+ png_bytep trans PNG_DEPSTRUCT; /* transparency values for paletted files */
+ png_color_16 trans_values PNG_DEPSTRUCT; /* transparency values for non-paletted files */
+#endif
+
+ png_read_status_ptr read_row_fn PNG_DEPSTRUCT; /* called after each row is decoded */
+ png_write_status_ptr write_row_fn PNG_DEPSTRUCT; /* called after each row is encoded */
+#ifdef PNG_PROGRESSIVE_READ_SUPPORTED
+ png_progressive_info_ptr info_fn PNG_DEPSTRUCT; /* called after header data fully read */
+ png_progressive_row_ptr row_fn PNG_DEPSTRUCT; /* called after each prog. row is decoded */
+ png_progressive_end_ptr end_fn PNG_DEPSTRUCT; /* called after image is complete */
+ png_bytep save_buffer_ptr PNG_DEPSTRUCT; /* current location in save_buffer */
+ png_bytep save_buffer PNG_DEPSTRUCT; /* buffer for previously read data */
+ png_bytep current_buffer_ptr PNG_DEPSTRUCT; /* current location in current_buffer */
+ png_bytep current_buffer PNG_DEPSTRUCT; /* buffer for recently used data */
+ png_uint_32 push_length PNG_DEPSTRUCT; /* size of current input chunk */
+ png_uint_32 skip_length PNG_DEPSTRUCT; /* bytes to skip in input data */
+ png_size_t save_buffer_size PNG_DEPSTRUCT; /* amount of data now in save_buffer */
+ png_size_t save_buffer_max PNG_DEPSTRUCT; /* total size of save_buffer */
+ png_size_t buffer_size PNG_DEPSTRUCT; /* total amount of available input data */
+ png_size_t current_buffer_size PNG_DEPSTRUCT; /* amount of data now in current_buffer */
+ int process_mode PNG_DEPSTRUCT; /* what push library is currently doing */
+ int cur_palette PNG_DEPSTRUCT; /* current push library palette index */
+
+# ifdef PNG_TEXT_SUPPORTED
+ png_size_t current_text_size PNG_DEPSTRUCT; /* current size of text input data */
+ png_size_t current_text_left PNG_DEPSTRUCT; /* how much text left to read in input */
+ png_charp current_text PNG_DEPSTRUCT; /* current text chunk buffer */
+ png_charp current_text_ptr PNG_DEPSTRUCT; /* current location in current_text */
+# endif /* PNG_TEXT_SUPPORTED */
+#endif /* PNG_PROGRESSIVE_READ_SUPPORTED */
+
+#if defined(__TURBOC__) && !defined(_Windows) && !defined(__FLAT__)
+/* for the Borland special 64K segment handler */
+ png_bytepp offset_table_ptr PNG_DEPSTRUCT;
+ png_bytep offset_table PNG_DEPSTRUCT;
+ png_uint_16 offset_table_number PNG_DEPSTRUCT;
+ png_uint_16 offset_table_count PNG_DEPSTRUCT;
+ png_uint_16 offset_table_count_free PNG_DEPSTRUCT;
+#endif
+
+#ifdef PNG_READ_DITHER_SUPPORTED
+ png_bytep palette_lookup PNG_DEPSTRUCT; /* lookup table for dithering */
+ png_bytep dither_index PNG_DEPSTRUCT; /* index translation for palette files */
+#endif
+
+#if defined(PNG_READ_DITHER_SUPPORTED) || defined(PNG_hIST_SUPPORTED)
+ png_uint_16p hist PNG_DEPSTRUCT; /* histogram */
+#endif
+
+#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED
+ png_byte heuristic_method PNG_DEPSTRUCT; /* heuristic for row filter selection */
+ png_byte num_prev_filters PNG_DEPSTRUCT; /* number of weights for previous rows */
+ png_bytep prev_filters PNG_DEPSTRUCT; /* filter type(s) of previous row(s) */
+ png_uint_16p filter_weights PNG_DEPSTRUCT; /* weight(s) for previous line(s) */
+ png_uint_16p inv_filter_weights PNG_DEPSTRUCT; /* 1/weight(s) for previous line(s) */
+ png_uint_16p filter_costs PNG_DEPSTRUCT; /* relative filter calculation cost */
+ png_uint_16p inv_filter_costs PNG_DEPSTRUCT; /* 1/relative filter calculation cost */
+#endif
+
+#ifdef PNG_TIME_RFC1123_SUPPORTED
+ png_charp time_buffer PNG_DEPSTRUCT; /* String to hold RFC 1123 time text */
+#endif
+
+/* New members added in libpng-1.0.6 */
+
+#ifdef PNG_FREE_ME_SUPPORTED
+ png_uint_32 free_me PNG_DEPSTRUCT; /* flags items libpng is responsible for freeing */
+#endif
+
+#ifdef PNG_USER_CHUNKS_SUPPORTED
+ png_voidp user_chunk_ptr PNG_DEPSTRUCT;
+ png_user_chunk_ptr read_user_chunk_fn PNG_DEPSTRUCT; /* user read chunk handler */
+#endif
+
+#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
+ int num_chunk_list PNG_DEPSTRUCT;
+ png_bytep chunk_list PNG_DEPSTRUCT;
+#endif
+
+/* New members added in libpng-1.0.3 */
+#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED
+ png_byte rgb_to_gray_status PNG_DEPSTRUCT;
+ /* These were changed from png_byte in libpng-1.0.6 */
+ png_uint_16 rgb_to_gray_red_coeff PNG_DEPSTRUCT;
+ png_uint_16 rgb_to_gray_green_coeff PNG_DEPSTRUCT;
+ png_uint_16 rgb_to_gray_blue_coeff PNG_DEPSTRUCT;
+#endif
+
+/* New member added in libpng-1.0.4 (renamed in 1.0.9) */
+#if defined(PNG_MNG_FEATURES_SUPPORTED) || \
+ defined(PNG_READ_EMPTY_PLTE_SUPPORTED) || \
+ defined(PNG_WRITE_EMPTY_PLTE_SUPPORTED)
+/* Changed from png_byte to png_uint_32 at version 1.2.0 */
+#ifdef PNG_1_0_X
+ png_byte mng_features_permitted PNG_DEPSTRUCT;
+#else
+ png_uint_32 mng_features_permitted PNG_DEPSTRUCT;
+#endif /* PNG_1_0_X */
+#endif
+
+/* New member added in libpng-1.0.7 */
+#if defined(PNG_READ_GAMMA_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED)
+ png_fixed_point int_gamma PNG_DEPSTRUCT;
+#endif
+
+/* New member added in libpng-1.0.9, ifdef'ed out in 1.0.12, enabled in 1.2.0 */
+#ifdef PNG_MNG_FEATURES_SUPPORTED
+ png_byte filter_type PNG_DEPSTRUCT;
+#endif
+
+#ifdef PNG_1_0_X
+/* New member added in libpng-1.0.10, ifdef'ed out in 1.2.0 */
+ png_uint_32 row_buf_size PNG_DEPSTRUCT;
+#endif
+
+/* New members added in libpng-1.2.0 */
+#ifdef PNG_ASSEMBLER_CODE_SUPPORTED
+# ifndef PNG_1_0_X
+# ifdef PNG_MMX_CODE_SUPPORTED
+ png_byte mmx_bitdepth_threshold PNG_DEPSTRUCT;
+ png_uint_32 mmx_rowbytes_threshold PNG_DEPSTRUCT;
+# endif
+ png_uint_32 asm_flags PNG_DEPSTRUCT;
+# endif
+#endif
+
+/* New members added in libpng-1.0.2 but first enabled by default in 1.2.0 */
+#ifdef PNG_USER_MEM_SUPPORTED
+ png_voidp mem_ptr PNG_DEPSTRUCT; /* user supplied struct for mem functions */
+ png_malloc_ptr malloc_fn PNG_DEPSTRUCT; /* function for allocating memory */
+ png_free_ptr free_fn PNG_DEPSTRUCT; /* function for freeing memory */
+#endif
+
+/* New member added in libpng-1.0.13 and 1.2.0 */
+ png_bytep big_row_buf PNG_DEPSTRUCT; /* buffer to save current (unfiltered) row */
+
+#ifdef PNG_READ_DITHER_SUPPORTED
+/* The following three members were added at version 1.0.14 and 1.2.4 */
+ png_bytep dither_sort PNG_DEPSTRUCT; /* working sort array */
+ png_bytep index_to_palette PNG_DEPSTRUCT; /* where the original index currently is */
+ /* in the palette */
+ png_bytep palette_to_index PNG_DEPSTRUCT; /* which original index points to this */
+ /* palette color */
+#endif
+
+/* New members added in libpng-1.0.16 and 1.2.6 */
+ png_byte compression_type PNG_DEPSTRUCT;
+
+#ifdef PNG_USER_LIMITS_SUPPORTED
+ png_uint_32 user_width_max PNG_DEPSTRUCT;
+ png_uint_32 user_height_max PNG_DEPSTRUCT;
+#endif
+
+/* New member added in libpng-1.0.25 and 1.2.17 */
+#ifdef PNG_UNKNOWN_CHUNKS_SUPPORTED
+ /* Storage for unknown chunk that the library doesn't recognize. */
+ png_unknown_chunk unknown_chunk PNG_DEPSTRUCT;
+#endif
+
+/* New members added in libpng-1.2.26 */
+ png_uint_32 old_big_row_buf_size PNG_DEPSTRUCT;
+ png_uint_32 old_prev_row_size PNG_DEPSTRUCT;
+
+/* New member added in libpng-1.2.30 */
+ png_charp chunkdata PNG_DEPSTRUCT; /* buffer for reading chunk data */
+
+
+};
+
+
+/* This triggers a compiler error in png.c, if png.c and png.h
+ * do not agree upon the version number.
+ */
+typedef png_structp version_1_2_44;
+
+typedef png_struct FAR * FAR * png_structpp;
+
+/* Here are the function definitions most commonly used. This is not
+ * the place to find out how to use libpng. See libpng.txt for the
+ * full explanation, see example.c for the summary. This just provides
+ * a simple one line description of the use of each function.
+ */
+
+/* Returns the version number of the library */
+extern PNG_EXPORT(png_uint_32,png_access_version_number) PNGARG((void));
+
+/* Tell lib we have already handled the first <num_bytes> magic bytes.
+ * Handling more than 8 bytes from the beginning of the file is an error.
+ */
+extern PNG_EXPORT(void,png_set_sig_bytes) PNGARG((png_structp png_ptr,
+ int num_bytes));
+
+/* Check sig[start] through sig[start + num_to_check - 1] to see if it's a
+ * PNG file. Returns zero if the supplied bytes match the 8-byte PNG
+ * signature, and non-zero otherwise. Having num_to_check == 0 or
+ * start > 7 will always fail (ie return non-zero).
+ */
+extern PNG_EXPORT(int,png_sig_cmp) PNGARG((png_bytep sig, png_size_t start,
+ png_size_t num_to_check));
+
+/* Simple signature checking function. This is the same as calling
+ * png_check_sig(sig, n) := !png_sig_cmp(sig, 0, n).
+ */
+extern PNG_EXPORT(int,png_check_sig) PNGARG((png_bytep sig, int num)) PNG_DEPRECATED;
+
+/* Allocate and initialize png_ptr struct for reading, and any other memory. */
+extern PNG_EXPORT(png_structp,png_create_read_struct)
+ PNGARG((png_const_charp user_png_ver, png_voidp error_ptr,
+ png_error_ptr error_fn, png_error_ptr warn_fn)) PNG_ALLOCATED;
+
+/* Allocate and initialize png_ptr struct for writing, and any other memory */
+extern PNG_EXPORT(png_structp,png_create_write_struct)
+ PNGARG((png_const_charp user_png_ver, png_voidp error_ptr,
+ png_error_ptr error_fn, png_error_ptr warn_fn)) PNG_ALLOCATED;
+
+#ifdef PNG_WRITE_SUPPORTED
+extern PNG_EXPORT(png_uint_32,png_get_compression_buffer_size)
+ PNGARG((png_structp png_ptr));
+#endif
+
+#ifdef PNG_WRITE_SUPPORTED
+extern PNG_EXPORT(void,png_set_compression_buffer_size)
+ PNGARG((png_structp png_ptr, png_uint_32 size));
+#endif
+
+/* Reset the compression stream */
+extern PNG_EXPORT(int,png_reset_zstream) PNGARG((png_structp png_ptr));
+
+/* New functions added in libpng-1.0.2 (not enabled by default until 1.2.0) */
+#ifdef PNG_USER_MEM_SUPPORTED
+extern PNG_EXPORT(png_structp,png_create_read_struct_2)
+ PNGARG((png_const_charp user_png_ver, png_voidp error_ptr,
+ png_error_ptr error_fn, png_error_ptr warn_fn, png_voidp mem_ptr,
+ png_malloc_ptr malloc_fn, png_free_ptr free_fn)) PNG_ALLOCATED;
+extern PNG_EXPORT(png_structp,png_create_write_struct_2)
+ PNGARG((png_const_charp user_png_ver, png_voidp error_ptr,
+ png_error_ptr error_fn, png_error_ptr warn_fn, png_voidp mem_ptr,
+ png_malloc_ptr malloc_fn, png_free_ptr free_fn)) PNG_ALLOCATED;
+#endif
+
+/* Write a PNG chunk - size, type, (optional) data, CRC. */
+extern PNG_EXPORT(void,png_write_chunk) PNGARG((png_structp png_ptr,
+ png_bytep chunk_name, png_bytep data, png_size_t length));
+
+/* Write the start of a PNG chunk - length and chunk name. */
+extern PNG_EXPORT(void,png_write_chunk_start) PNGARG((png_structp png_ptr,
+ png_bytep chunk_name, png_uint_32 length));
+
+/* Write the data of a PNG chunk started with png_write_chunk_start(). */
+extern PNG_EXPORT(void,png_write_chunk_data) PNGARG((png_structp png_ptr,
+ png_bytep data, png_size_t length));
+
+/* Finish a chunk started with png_write_chunk_start() (includes CRC). */
+extern PNG_EXPORT(void,png_write_chunk_end) PNGARG((png_structp png_ptr));
+
+/* Allocate and initialize the info structure */
+extern PNG_EXPORT(png_infop,png_create_info_struct)
+ PNGARG((png_structp png_ptr)) PNG_ALLOCATED;
+
+#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
+/* Initialize the info structure (old interface - DEPRECATED) */
+extern PNG_EXPORT(void,png_info_init) PNGARG((png_infop info_ptr))
+ PNG_DEPRECATED;
+#undef png_info_init
+#define png_info_init(info_ptr) png_info_init_3(&info_ptr,\
+ png_sizeof(png_info));
+#endif
+
+extern PNG_EXPORT(void,png_info_init_3) PNGARG((png_infopp info_ptr,
+ png_size_t png_info_struct_size));
+
+/* Writes all the PNG information before the image. */
+extern PNG_EXPORT(void,png_write_info_before_PLTE) PNGARG((png_structp png_ptr,
+ png_infop info_ptr));
+extern PNG_EXPORT(void,png_write_info) PNGARG((png_structp png_ptr,
+ png_infop info_ptr));
+
+#ifdef PNG_SEQUENTIAL_READ_SUPPORTED
+/* Read the information before the actual image data. */
+extern PNG_EXPORT(void,png_read_info) PNGARG((png_structp png_ptr,
+ png_infop info_ptr));
+#endif
+
+#ifdef PNG_TIME_RFC1123_SUPPORTED
+extern PNG_EXPORT(png_charp,png_convert_to_rfc1123)
+ PNGARG((png_structp png_ptr, png_timep ptime));
+#endif
+
+#ifdef PNG_CONVERT_tIME_SUPPORTED
+/* Convert from a struct tm to png_time */
+extern PNG_EXPORT(void,png_convert_from_struct_tm) PNGARG((png_timep ptime,
+ struct tm FAR * ttime));
+
+/* Convert from time_t to png_time. Uses gmtime() */
+extern PNG_EXPORT(void,png_convert_from_time_t) PNGARG((png_timep ptime,
+ time_t ttime));
+#endif /* PNG_CONVERT_tIME_SUPPORTED */
+
+#ifdef PNG_READ_EXPAND_SUPPORTED
+/* Expand data to 24-bit RGB, or 8-bit grayscale, with alpha if available. */
+extern PNG_EXPORT(void,png_set_expand) PNGARG((png_structp png_ptr));
+#ifndef PNG_1_0_X
+extern PNG_EXPORT(void,png_set_expand_gray_1_2_4_to_8) PNGARG((png_structp
+ png_ptr));
+#endif
+extern PNG_EXPORT(void,png_set_palette_to_rgb) PNGARG((png_structp png_ptr));
+extern PNG_EXPORT(void,png_set_tRNS_to_alpha) PNGARG((png_structp png_ptr));
+#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
+/* Deprecated */
+extern PNG_EXPORT(void,png_set_gray_1_2_4_to_8) PNGARG((png_structp
+ png_ptr)) PNG_DEPRECATED;
+#endif
+#endif
+
+#if defined(PNG_READ_BGR_SUPPORTED) || defined(PNG_WRITE_BGR_SUPPORTED)
+/* Use blue, green, red order for pixels. */
+extern PNG_EXPORT(void,png_set_bgr) PNGARG((png_structp png_ptr));
+#endif
+
+#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED
+/* Expand the grayscale to 24-bit RGB if necessary. */
+extern PNG_EXPORT(void,png_set_gray_to_rgb) PNGARG((png_structp png_ptr));
+#endif
+
+#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED
+/* Reduce RGB to grayscale. */
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+extern PNG_EXPORT(void,png_set_rgb_to_gray) PNGARG((png_structp png_ptr,
+ int error_action, double red, double green ));
+#endif
+extern PNG_EXPORT(void,png_set_rgb_to_gray_fixed) PNGARG((png_structp png_ptr,
+ int error_action, png_fixed_point red, png_fixed_point green ));
+extern PNG_EXPORT(png_byte,png_get_rgb_to_gray_status) PNGARG((png_structp
+ png_ptr));
+#endif
+
+extern PNG_EXPORT(void,png_build_grayscale_palette) PNGARG((int bit_depth,
+ png_colorp palette));
+
+#ifdef PNG_READ_STRIP_ALPHA_SUPPORTED
+extern PNG_EXPORT(void,png_set_strip_alpha) PNGARG((png_structp png_ptr));
+#endif
+
+#if defined(PNG_READ_SWAP_ALPHA_SUPPORTED) || \
+ defined(PNG_WRITE_SWAP_ALPHA_SUPPORTED)
+extern PNG_EXPORT(void,png_set_swap_alpha) PNGARG((png_structp png_ptr));
+#endif
+
+#if defined(PNG_READ_INVERT_ALPHA_SUPPORTED) || \
+ defined(PNG_WRITE_INVERT_ALPHA_SUPPORTED)
+extern PNG_EXPORT(void,png_set_invert_alpha) PNGARG((png_structp png_ptr));
+#endif
+
+#if defined(PNG_READ_FILLER_SUPPORTED) || defined(PNG_WRITE_FILLER_SUPPORTED)
+/* Add a filler byte to 8-bit Gray or 24-bit RGB images. */
+extern PNG_EXPORT(void,png_set_filler) PNGARG((png_structp png_ptr,
+ png_uint_32 filler, int flags));
+/* The values of the PNG_FILLER_ defines should NOT be changed */
+#define PNG_FILLER_BEFORE 0
+#define PNG_FILLER_AFTER 1
+/* Add an alpha byte to 8-bit Gray or 24-bit RGB images. */
+#ifndef PNG_1_0_X
+extern PNG_EXPORT(void,png_set_add_alpha) PNGARG((png_structp png_ptr,
+ png_uint_32 filler, int flags));
+#endif
+#endif /* PNG_READ_FILLER_SUPPORTED || PNG_WRITE_FILLER_SUPPORTED */
+
+#if defined(PNG_READ_SWAP_SUPPORTED) || defined(PNG_WRITE_SWAP_SUPPORTED)
+/* Swap bytes in 16-bit depth files. */
+extern PNG_EXPORT(void,png_set_swap) PNGARG((png_structp png_ptr));
+#endif
+
+#if defined(PNG_READ_PACK_SUPPORTED) || defined(PNG_WRITE_PACK_SUPPORTED)
+/* Use 1 byte per pixel in 1, 2, or 4-bit depth files. */
+extern PNG_EXPORT(void,png_set_packing) PNGARG((png_structp png_ptr));
+#endif
+
+#if defined(PNG_READ_PACKSWAP_SUPPORTED) || defined(PNG_WRITE_PACKSWAP_SUPPORTED)
+/* Swap packing order of pixels in bytes. */
+extern PNG_EXPORT(void,png_set_packswap) PNGARG((png_structp png_ptr));
+#endif
+
+#if defined(PNG_READ_SHIFT_SUPPORTED) || defined(PNG_WRITE_SHIFT_SUPPORTED)
+/* Converts files to legal bit depths. */
+extern PNG_EXPORT(void,png_set_shift) PNGARG((png_structp png_ptr,
+ png_color_8p true_bits));
+#endif
+
+#if defined(PNG_READ_INTERLACING_SUPPORTED) || \
+ defined(PNG_WRITE_INTERLACING_SUPPORTED)
+/* Have the code handle the interlacing. Returns the number of passes. */
+extern PNG_EXPORT(int,png_set_interlace_handling) PNGARG((png_structp png_ptr));
+#endif
+
+#if defined(PNG_READ_INVERT_SUPPORTED) || defined(PNG_WRITE_INVERT_SUPPORTED)
+/* Invert monochrome files */
+extern PNG_EXPORT(void,png_set_invert_mono) PNGARG((png_structp png_ptr));
+#endif
+
+#ifdef PNG_READ_BACKGROUND_SUPPORTED
+/* Handle alpha and tRNS by replacing with a background color. */
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+extern PNG_EXPORT(void,png_set_background) PNGARG((png_structp png_ptr,
+ png_color_16p background_color, int background_gamma_code,
+ int need_expand, double background_gamma));
+#endif
+#define PNG_BACKGROUND_GAMMA_UNKNOWN 0
+#define PNG_BACKGROUND_GAMMA_SCREEN 1
+#define PNG_BACKGROUND_GAMMA_FILE 2
+#define PNG_BACKGROUND_GAMMA_UNIQUE 3
+#endif
+
+#ifdef PNG_READ_16_TO_8_SUPPORTED
+/* Strip the second byte of information from a 16-bit depth file. */
+extern PNG_EXPORT(void,png_set_strip_16) PNGARG((png_structp png_ptr));
+#endif
+
+#ifdef PNG_READ_DITHER_SUPPORTED
+/* Turn on dithering, and reduce the palette to the number of colors available. */
+extern PNG_EXPORT(void,png_set_dither) PNGARG((png_structp png_ptr,
+ png_colorp palette, int num_palette, int maximum_colors,
+ png_uint_16p histogram, int full_dither));
+#endif
+
+#ifdef PNG_READ_GAMMA_SUPPORTED
+/* Handle gamma correction. Screen_gamma=(display_exponent) */
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+extern PNG_EXPORT(void,png_set_gamma) PNGARG((png_structp png_ptr,
+ double screen_gamma, double default_file_gamma));
+#endif
+#endif
+
+#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
+#if defined(PNG_READ_EMPTY_PLTE_SUPPORTED) || \
+ defined(PNG_WRITE_EMPTY_PLTE_SUPPORTED)
+/* Permit or disallow empty PLTE (0: not permitted, 1: permitted) */
+/* Deprecated and will be removed. Use png_permit_mng_features() instead. */
+extern PNG_EXPORT(void,png_permit_empty_plte) PNGARG((png_structp png_ptr,
+ int empty_plte_permitted)) PNG_DEPRECATED;
+#endif
+#endif
+
+#ifdef PNG_WRITE_FLUSH_SUPPORTED
+/* Set how many lines between output flushes - 0 for no flushing */
+extern PNG_EXPORT(void,png_set_flush) PNGARG((png_structp png_ptr, int nrows));
+/* Flush the current PNG output buffer */
+extern PNG_EXPORT(void,png_write_flush) PNGARG((png_structp png_ptr));
+#endif
+
+/* Optional update palette with requested transformations */
+extern PNG_EXPORT(void,png_start_read_image) PNGARG((png_structp png_ptr));
+
+/* Optional call to update the users info structure */
+extern PNG_EXPORT(void,png_read_update_info) PNGARG((png_structp png_ptr,
+ png_infop info_ptr));
+
+#ifndef PNG_NO_SEQUENTIAL_READ_SUPPORTED
+/* Read one or more rows of image data. */
+extern PNG_EXPORT(void,png_read_rows) PNGARG((png_structp png_ptr,
+ png_bytepp row, png_bytepp display_row, png_uint_32 num_rows));
+#endif
+
+#ifndef PNG_NO_SEQUENTIAL_READ_SUPPORTED
+/* Read a row of data. */
+extern PNG_EXPORT(void,png_read_row) PNGARG((png_structp png_ptr,
+ png_bytep row,
+ png_bytep display_row));
+#endif
+
+#ifndef PNG_NO_SEQUENTIAL_READ_SUPPORTED
+/* Read the whole image into memory at once. */
+extern PNG_EXPORT(void,png_read_image) PNGARG((png_structp png_ptr,
+ png_bytepp image));
+#endif
+
+/* Write a row of image data */
+extern PNG_EXPORT(void,png_write_row) PNGARG((png_structp png_ptr,
+ png_bytep row));
+
+/* Write a few rows of image data */
+extern PNG_EXPORT(void,png_write_rows) PNGARG((png_structp png_ptr,
+ png_bytepp row, png_uint_32 num_rows));
+
+/* Write the image data */
+extern PNG_EXPORT(void,png_write_image) PNGARG((png_structp png_ptr,
+ png_bytepp image));
+
+/* Writes the end of the PNG file. */
+extern PNG_EXPORT(void,png_write_end) PNGARG((png_structp png_ptr,
+ png_infop info_ptr));
+
+#ifndef PNG_NO_SEQUENTIAL_READ_SUPPORTED
+/* Read the end of the PNG file. */
+extern PNG_EXPORT(void,png_read_end) PNGARG((png_structp png_ptr,
+ png_infop info_ptr));
+#endif
+
+/* Free any memory associated with the png_info_struct */
+extern PNG_EXPORT(void,png_destroy_info_struct) PNGARG((png_structp png_ptr,
+ png_infopp info_ptr_ptr));
+
+/* Free any memory associated with the png_struct and the png_info_structs */
+extern PNG_EXPORT(void,png_destroy_read_struct) PNGARG((png_structpp
+ png_ptr_ptr, png_infopp info_ptr_ptr, png_infopp end_info_ptr_ptr));
+
+/* Free all memory used by the read (old method - NOT DLL EXPORTED) */
+extern void png_read_destroy PNGARG((png_structp png_ptr, png_infop info_ptr,
+ png_infop end_info_ptr)) PNG_DEPRECATED;
+
+/* Free any memory associated with the png_struct and the png_info_structs */
+extern PNG_EXPORT(void,png_destroy_write_struct)
+ PNGARG((png_structpp png_ptr_ptr, png_infopp info_ptr_ptr));
+
+/* Free any memory used in png_ptr struct (old method - NOT DLL EXPORTED) */
+extern void png_write_destroy PNGARG((png_structp png_ptr)) PNG_DEPRECATED;
+
+/* Set the libpng method of handling chunk CRC errors */
+extern PNG_EXPORT(void,png_set_crc_action) PNGARG((png_structp png_ptr,
+ int crit_action, int ancil_action));
+
+/* Values for png_set_crc_action() to say how to handle CRC errors in
+ * ancillary and critical chunks, and whether to use the data contained
+ * therein. Note that it is impossible to "discard" data in a critical
+ * chunk. For versions prior to 0.90, the action was always error/quit,
+ * whereas in version 0.90 and later, the action for CRC errors in ancillary
+ * chunks is warn/discard. These values should NOT be changed.
+ *
+ * value action:critical action:ancillary
+ */
+#define PNG_CRC_DEFAULT 0 /* error/quit warn/discard data */
+#define PNG_CRC_ERROR_QUIT 1 /* error/quit error/quit */
+#define PNG_CRC_WARN_DISCARD 2 /* (INVALID) warn/discard data */
+#define PNG_CRC_WARN_USE 3 /* warn/use data warn/use data */
+#define PNG_CRC_QUIET_USE 4 /* quiet/use data quiet/use data */
+#define PNG_CRC_NO_CHANGE 5 /* use current value use current value */
+
+/* These functions give the user control over the scan-line filtering in
+ * libpng and the compression methods used by zlib. These functions are
+ * mainly useful for testing, as the defaults should work with most users.
+ * Those users who are tight on memory or want faster performance at the
+ * expense of compression can modify them. See the compression library
+ * header file (zlib.h) for an explination of the compression functions.
+ */
+
+/* Set the filtering method(s) used by libpng. Currently, the only valid
+ * value for "method" is 0.
+ */
+extern PNG_EXPORT(void,png_set_filter) PNGARG((png_structp png_ptr, int method,
+ int filters));
+
+/* Flags for png_set_filter() to say which filters to use. The flags
+ * are chosen so that they don't conflict with real filter types
+ * below, in case they are supplied instead of the #defined constants.
+ * These values should NOT be changed.
+ */
+#define PNG_NO_FILTERS 0x00
+#define PNG_FILTER_NONE 0x08
+#define PNG_FILTER_SUB 0x10
+#define PNG_FILTER_UP 0x20
+#define PNG_FILTER_AVG 0x40
+#define PNG_FILTER_PAETH 0x80
+#define PNG_ALL_FILTERS (PNG_FILTER_NONE | PNG_FILTER_SUB | PNG_FILTER_UP | \
+ PNG_FILTER_AVG | PNG_FILTER_PAETH)
+
+/* Filter values (not flags) - used in pngwrite.c, pngwutil.c for now.
+ * These defines should NOT be changed.
+ */
+#define PNG_FILTER_VALUE_NONE 0
+#define PNG_FILTER_VALUE_SUB 1
+#define PNG_FILTER_VALUE_UP 2
+#define PNG_FILTER_VALUE_AVG 3
+#define PNG_FILTER_VALUE_PAETH 4
+#define PNG_FILTER_VALUE_LAST 5
+
+#if defined(PNG_WRITE_WEIGHTED_FILTER_SUPPORTED) /* EXPERIMENTAL */
+/* The "heuristic_method" is given by one of the PNG_FILTER_HEURISTIC_
+ * defines, either the default (minimum-sum-of-absolute-differences), or
+ * the experimental method (weighted-minimum-sum-of-absolute-differences).
+ *
+ * Weights are factors >= 1.0, indicating how important it is to keep the
+ * filter type consistent between rows. Larger numbers mean the current
+ * filter is that many times as likely to be the same as the "num_weights"
+ * previous filters. This is cumulative for each previous row with a weight.
+ * There needs to be "num_weights" values in "filter_weights", or it can be
+ * NULL if the weights aren't being specified. Weights have no influence on
+ * the selection of the first row filter. Well chosen weights can (in theory)
+ * improve the compression for a given image.
+ *
+ * Costs are factors >= 1.0 indicating the relative decoding costs of a
+ * filter type. Higher costs indicate more decoding expense, and are
+ * therefore less likely to be selected over a filter with lower computational
+ * costs. There needs to be a value in "filter_costs" for each valid filter
+ * type (given by PNG_FILTER_VALUE_LAST), or it can be NULL if you aren't
+ * setting the costs. Costs try to improve the speed of decompression without
+ * unduly increasing the compressed image size.
+ *
+ * A negative weight or cost indicates the default value is to be used, and
+ * values in the range [0.0, 1.0) indicate the value is to remain unchanged.
+ * The default values for both weights and costs are currently 1.0, but may
+ * change if good general weighting/cost heuristics can be found. If both
+ * the weights and costs are set to 1.0, this degenerates the WEIGHTED method
+ * to the UNWEIGHTED method, but with added encoding time/computation.
+ */
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+extern PNG_EXPORT(void,png_set_filter_heuristics) PNGARG((png_structp png_ptr,
+ int heuristic_method, int num_weights, png_doublep filter_weights,
+ png_doublep filter_costs));
+#endif
+#endif /* PNG_WRITE_WEIGHTED_FILTER_SUPPORTED */
+
+/* Heuristic used for row filter selection. These defines should NOT be
+ * changed.
+ */
+#define PNG_FILTER_HEURISTIC_DEFAULT 0 /* Currently "UNWEIGHTED" */
+#define PNG_FILTER_HEURISTIC_UNWEIGHTED 1 /* Used by libpng < 0.95 */
+#define PNG_FILTER_HEURISTIC_WEIGHTED 2 /* Experimental feature */
+#define PNG_FILTER_HEURISTIC_LAST 3 /* Not a valid value */
+
+/* Set the library compression level. Currently, valid values range from
+ * 0 - 9, corresponding directly to the zlib compression levels 0 - 9
+ * (0 - no compression, 9 - "maximal" compression). Note that tests have
+ * shown that zlib compression levels 3-6 usually perform as well as level 9
+ * for PNG images, and do considerably fewer caclulations. In the future,
+ * these values may not correspond directly to the zlib compression levels.
+ */
+extern PNG_EXPORT(void,png_set_compression_level) PNGARG((png_structp png_ptr,
+ int level));
+
+extern PNG_EXPORT(void,png_set_compression_mem_level)
+ PNGARG((png_structp png_ptr, int mem_level));
+
+extern PNG_EXPORT(void,png_set_compression_strategy)
+ PNGARG((png_structp png_ptr, int strategy));
+
+extern PNG_EXPORT(void,png_set_compression_window_bits)
+ PNGARG((png_structp png_ptr, int window_bits));
+
+extern PNG_EXPORT(void,png_set_compression_method) PNGARG((png_structp png_ptr,
+ int method));
+
+/* These next functions are called for input/output, memory, and error
+ * handling. They are in the file pngrio.c, pngwio.c, and pngerror.c,
+ * and call standard C I/O routines such as fread(), fwrite(), and
+ * fprintf(). These functions can be made to use other I/O routines
+ * at run time for those applications that need to handle I/O in a
+ * different manner by calling png_set_???_fn(). See libpng.txt for
+ * more information.
+ */
+
+#ifdef PNG_STDIO_SUPPORTED
+/* Initialize the input/output for the PNG file to the default functions. */
+extern PNG_EXPORT(void,png_init_io) PNGARG((png_structp png_ptr, png_FILE_p fp));
+#endif
+
+/* Replace the (error and abort), and warning functions with user
+ * supplied functions. If no messages are to be printed you must still
+ * write and use replacement functions. The replacement error_fn should
+ * still do a longjmp to the last setjmp location if you are using this
+ * method of error handling. If error_fn or warning_fn is NULL, the
+ * default function will be used.
+ */
+
+extern PNG_EXPORT(void,png_set_error_fn) PNGARG((png_structp png_ptr,
+ png_voidp error_ptr, png_error_ptr error_fn, png_error_ptr warning_fn));
+
+/* Return the user pointer associated with the error functions */
+extern PNG_EXPORT(png_voidp,png_get_error_ptr) PNGARG((png_structp png_ptr));
+
+/* Replace the default data output functions with a user supplied one(s).
+ * If buffered output is not used, then output_flush_fn can be set to NULL.
+ * If PNG_WRITE_FLUSH_SUPPORTED is not defined at libpng compile time
+ * output_flush_fn will be ignored (and thus can be NULL).
+ * It is probably a mistake to use NULL for output_flush_fn if
+ * write_data_fn is not also NULL unless you have built libpng with
+ * PNG_WRITE_FLUSH_SUPPORTED undefined, because in this case libpng's
+ * default flush function, which uses the standard *FILE structure, will
+ * be used.
+ */
+extern PNG_EXPORT(void,png_set_write_fn) PNGARG((png_structp png_ptr,
+ png_voidp io_ptr, png_rw_ptr write_data_fn, png_flush_ptr output_flush_fn));
+
+/* Replace the default data input function with a user supplied one. */
+extern PNG_EXPORT(void,png_set_read_fn) PNGARG((png_structp png_ptr,
+ png_voidp io_ptr, png_rw_ptr read_data_fn));
+
+/* Return the user pointer associated with the I/O functions */
+extern PNG_EXPORT(png_voidp,png_get_io_ptr) PNGARG((png_structp png_ptr));
+
+extern PNG_EXPORT(void,png_set_read_status_fn) PNGARG((png_structp png_ptr,
+ png_read_status_ptr read_row_fn));
+
+extern PNG_EXPORT(void,png_set_write_status_fn) PNGARG((png_structp png_ptr,
+ png_write_status_ptr write_row_fn));
+
+#ifdef PNG_USER_MEM_SUPPORTED
+/* Replace the default memory allocation functions with user supplied one(s). */
+extern PNG_EXPORT(void,png_set_mem_fn) PNGARG((png_structp png_ptr,
+ png_voidp mem_ptr, png_malloc_ptr malloc_fn, png_free_ptr free_fn));
+/* Return the user pointer associated with the memory functions */
+extern PNG_EXPORT(png_voidp,png_get_mem_ptr) PNGARG((png_structp png_ptr));
+#endif
+
+#if defined(PNG_READ_USER_TRANSFORM_SUPPORTED) || \
+ defined(PNG_LEGACY_SUPPORTED)
+extern PNG_EXPORT(void,png_set_read_user_transform_fn) PNGARG((png_structp
+ png_ptr, png_user_transform_ptr read_user_transform_fn));
+#endif
+
+#if defined(PNG_WRITE_USER_TRANSFORM_SUPPORTED) || \
+ defined(PNG_LEGACY_SUPPORTED)
+extern PNG_EXPORT(void,png_set_write_user_transform_fn) PNGARG((png_structp
+ png_ptr, png_user_transform_ptr write_user_transform_fn));
+#endif
+
+#if defined(PNG_READ_USER_TRANSFORM_SUPPORTED) || \
+ defined(PNG_WRITE_USER_TRANSFORM_SUPPORTED) || \
+ defined(PNG_LEGACY_SUPPORTED)
+extern PNG_EXPORT(void,png_set_user_transform_info) PNGARG((png_structp
+ png_ptr, png_voidp user_transform_ptr, int user_transform_depth,
+ int user_transform_channels));
+/* Return the user pointer associated with the user transform functions */
+extern PNG_EXPORT(png_voidp,png_get_user_transform_ptr)
+ PNGARG((png_structp png_ptr));
+#endif
+
+#ifdef PNG_USER_CHUNKS_SUPPORTED
+extern PNG_EXPORT(void,png_set_read_user_chunk_fn) PNGARG((png_structp png_ptr,
+ png_voidp user_chunk_ptr, png_user_chunk_ptr read_user_chunk_fn));
+extern PNG_EXPORT(png_voidp,png_get_user_chunk_ptr) PNGARG((png_structp
+ png_ptr));
+#endif
+
+#ifdef PNG_PROGRESSIVE_READ_SUPPORTED
+/* Sets the function callbacks for the push reader, and a pointer to a
+ * user-defined structure available to the callback functions.
+ */
+extern PNG_EXPORT(void,png_set_progressive_read_fn) PNGARG((png_structp png_ptr,
+ png_voidp progressive_ptr,
+ png_progressive_info_ptr info_fn, png_progressive_row_ptr row_fn,
+ png_progressive_end_ptr end_fn));
+
+/* Returns the user pointer associated with the push read functions */
+extern PNG_EXPORT(png_voidp,png_get_progressive_ptr)
+ PNGARG((png_structp png_ptr));
+
+/* Function to be called when data becomes available */
+extern PNG_EXPORT(void,png_process_data) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_bytep buffer, png_size_t buffer_size));
+
+/* Function that combines rows. Not very much different than the
+ * png_combine_row() call. Is this even used?????
+ */
+extern PNG_EXPORT(void,png_progressive_combine_row) PNGARG((png_structp png_ptr,
+ png_bytep old_row, png_bytep new_row));
+#endif /* PNG_PROGRESSIVE_READ_SUPPORTED */
+
+extern PNG_EXPORT(png_voidp,png_malloc) PNGARG((png_structp png_ptr,
+ png_uint_32 size)) PNG_ALLOCATED;
+
+#ifdef PNG_1_0_X
+# define png_malloc_warn png_malloc
+#else
+/* Added at libpng version 1.2.4 */
+extern PNG_EXPORT(png_voidp,png_malloc_warn) PNGARG((png_structp png_ptr,
+ png_uint_32 size)) PNG_ALLOCATED;
+#endif
+
+/* Frees a pointer allocated by png_malloc() */
+extern PNG_EXPORT(void,png_free) PNGARG((png_structp png_ptr, png_voidp ptr));
+
+#ifdef PNG_1_0_X
+/* Function to allocate memory for zlib. */
+extern PNG_EXPORT(voidpf,png_zalloc) PNGARG((voidpf png_ptr, uInt items,
+ uInt size));
+
+/* Function to free memory for zlib */
+extern PNG_EXPORT(void,png_zfree) PNGARG((voidpf png_ptr, voidpf ptr));
+#endif
+
+/* Free data that was allocated internally */
+extern PNG_EXPORT(void,png_free_data) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_uint_32 free_me, int num));
+#ifdef PNG_FREE_ME_SUPPORTED
+/* Reassign responsibility for freeing existing data, whether allocated
+ * by libpng or by the application
+ */
+extern PNG_EXPORT(void,png_data_freer) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, int freer, png_uint_32 mask));
+#endif
+/* Assignments for png_data_freer */
+#define PNG_DESTROY_WILL_FREE_DATA 1
+#define PNG_SET_WILL_FREE_DATA 1
+#define PNG_USER_WILL_FREE_DATA 2
+/* Flags for png_ptr->free_me and info_ptr->free_me */
+#define PNG_FREE_HIST 0x0008
+#define PNG_FREE_ICCP 0x0010
+#define PNG_FREE_SPLT 0x0020
+#define PNG_FREE_ROWS 0x0040
+#define PNG_FREE_PCAL 0x0080
+#define PNG_FREE_SCAL 0x0100
+#define PNG_FREE_UNKN 0x0200
+#define PNG_FREE_LIST 0x0400
+#define PNG_FREE_PLTE 0x1000
+#define PNG_FREE_TRNS 0x2000
+#define PNG_FREE_TEXT 0x4000
+#define PNG_FREE_ALL 0x7fff
+#define PNG_FREE_MUL 0x4220 /* PNG_FREE_SPLT|PNG_FREE_TEXT|PNG_FREE_UNKN */
+
+#ifdef PNG_USER_MEM_SUPPORTED
+extern PNG_EXPORT(png_voidp,png_malloc_default) PNGARG((png_structp png_ptr,
+ png_uint_32 size)) PNG_ALLOCATED;
+extern PNG_EXPORT(void,png_free_default) PNGARG((png_structp png_ptr,
+ png_voidp ptr));
+#endif
+
+extern PNG_EXPORT(png_voidp,png_memcpy_check) PNGARG((png_structp png_ptr,
+ png_voidp s1, png_voidp s2, png_uint_32 size)) PNG_DEPRECATED;
+
+extern PNG_EXPORT(png_voidp,png_memset_check) PNGARG((png_structp png_ptr,
+ png_voidp s1, int value, png_uint_32 size)) PNG_DEPRECATED;
+
+#if defined(USE_FAR_KEYWORD) /* memory model conversion function */
+extern void *png_far_to_near PNGARG((png_structp png_ptr,png_voidp ptr,
+ int check));
+#endif /* USE_FAR_KEYWORD */
+
+#ifndef PNG_NO_ERROR_TEXT
+/* Fatal error in PNG image of libpng - can't continue */
+extern PNG_EXPORT(void,png_error) PNGARG((png_structp png_ptr,
+ png_const_charp error_message)) PNG_NORETURN;
+
+/* The same, but the chunk name is prepended to the error string. */
+extern PNG_EXPORT(void,png_chunk_error) PNGARG((png_structp png_ptr,
+ png_const_charp error_message)) PNG_NORETURN;
+#else
+/* Fatal error in PNG image of libpng - can't continue */
+extern PNG_EXPORT(void,png_err) PNGARG((png_structp png_ptr)) PNG_NORETURN;
+#endif
+
+#ifndef PNG_NO_WARNINGS
+/* Non-fatal error in libpng. Can continue, but may have a problem. */
+extern PNG_EXPORT(void,png_warning) PNGARG((png_structp png_ptr,
+ png_const_charp warning_message));
+
+#ifdef PNG_READ_SUPPORTED
+/* Non-fatal error in libpng, chunk name is prepended to message. */
+extern PNG_EXPORT(void,png_chunk_warning) PNGARG((png_structp png_ptr,
+ png_const_charp warning_message));
+#endif /* PNG_READ_SUPPORTED */
+#endif /* PNG_NO_WARNINGS */
+
+/* The png_set_<chunk> functions are for storing values in the png_info_struct.
+ * Similarly, the png_get_<chunk> calls are used to read values from the
+ * png_info_struct, either storing the parameters in the passed variables, or
+ * setting pointers into the png_info_struct where the data is stored. The
+ * png_get_<chunk> functions return a non-zero value if the data was available
+ * in info_ptr, or return zero and do not change any of the parameters if the
+ * data was not available.
+ *
+ * These functions should be used instead of directly accessing png_info
+ * to avoid problems with future changes in the size and internal layout of
+ * png_info_struct.
+ */
+/* Returns "flag" if chunk data is valid in info_ptr. */
+extern PNG_EXPORT(png_uint_32,png_get_valid) PNGARG((png_structp png_ptr,
+png_infop info_ptr, png_uint_32 flag));
+
+/* Returns number of bytes needed to hold a transformed row. */
+extern PNG_EXPORT(png_uint_32,png_get_rowbytes) PNGARG((png_structp png_ptr,
+png_infop info_ptr));
+
+#ifdef PNG_INFO_IMAGE_SUPPORTED
+/* Returns row_pointers, which is an array of pointers to scanlines that was
+ * returned from png_read_png().
+ */
+extern PNG_EXPORT(png_bytepp,png_get_rows) PNGARG((png_structp png_ptr,
+png_infop info_ptr));
+/* Set row_pointers, which is an array of pointers to scanlines for use
+ * by png_write_png().
+ */
+extern PNG_EXPORT(void,png_set_rows) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_bytepp row_pointers));
+#endif
+
+/* Returns number of color channels in image. */
+extern PNG_EXPORT(png_byte,png_get_channels) PNGARG((png_structp png_ptr,
+png_infop info_ptr));
+
+#ifdef PNG_EASY_ACCESS_SUPPORTED
+/* Returns image width in pixels. */
+extern PNG_EXPORT(png_uint_32, png_get_image_width) PNGARG((png_structp
+png_ptr, png_infop info_ptr));
+
+/* Returns image height in pixels. */
+extern PNG_EXPORT(png_uint_32, png_get_image_height) PNGARG((png_structp
+png_ptr, png_infop info_ptr));
+
+/* Returns image bit_depth. */
+extern PNG_EXPORT(png_byte, png_get_bit_depth) PNGARG((png_structp
+png_ptr, png_infop info_ptr));
+
+/* Returns image color_type. */
+extern PNG_EXPORT(png_byte, png_get_color_type) PNGARG((png_structp
+png_ptr, png_infop info_ptr));
+
+/* Returns image filter_type. */
+extern PNG_EXPORT(png_byte, png_get_filter_type) PNGARG((png_structp
+png_ptr, png_infop info_ptr));
+
+/* Returns image interlace_type. */
+extern PNG_EXPORT(png_byte, png_get_interlace_type) PNGARG((png_structp
+png_ptr, png_infop info_ptr));
+
+/* Returns image compression_type. */
+extern PNG_EXPORT(png_byte, png_get_compression_type) PNGARG((png_structp
+png_ptr, png_infop info_ptr));
+
+/* Returns image resolution in pixels per meter, from pHYs chunk data. */
+extern PNG_EXPORT(png_uint_32, png_get_pixels_per_meter) PNGARG((png_structp
+png_ptr, png_infop info_ptr));
+extern PNG_EXPORT(png_uint_32, png_get_x_pixels_per_meter) PNGARG((png_structp
+png_ptr, png_infop info_ptr));
+extern PNG_EXPORT(png_uint_32, png_get_y_pixels_per_meter) PNGARG((png_structp
+png_ptr, png_infop info_ptr));
+
+/* Returns pixel aspect ratio, computed from pHYs chunk data. */
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+extern PNG_EXPORT(float, png_get_pixel_aspect_ratio) PNGARG((png_structp
+png_ptr, png_infop info_ptr));
+#endif
+
+/* Returns image x, y offset in pixels or microns, from oFFs chunk data. */
+extern PNG_EXPORT(png_int_32, png_get_x_offset_pixels) PNGARG((png_structp
+png_ptr, png_infop info_ptr));
+extern PNG_EXPORT(png_int_32, png_get_y_offset_pixels) PNGARG((png_structp
+png_ptr, png_infop info_ptr));
+extern PNG_EXPORT(png_int_32, png_get_x_offset_microns) PNGARG((png_structp
+png_ptr, png_infop info_ptr));
+extern PNG_EXPORT(png_int_32, png_get_y_offset_microns) PNGARG((png_structp
+png_ptr, png_infop info_ptr));
+
+#endif /* PNG_EASY_ACCESS_SUPPORTED */
+
+/* Returns pointer to signature string read from PNG header */
+extern PNG_EXPORT(png_bytep,png_get_signature) PNGARG((png_structp png_ptr,
+png_infop info_ptr));
+
+#ifdef PNG_bKGD_SUPPORTED
+extern PNG_EXPORT(png_uint_32,png_get_bKGD) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_color_16p *background));
+#endif
+
+#ifdef PNG_bKGD_SUPPORTED
+extern PNG_EXPORT(void,png_set_bKGD) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_color_16p background));
+#endif
+
+#ifdef PNG_cHRM_SUPPORTED
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+extern PNG_EXPORT(png_uint_32,png_get_cHRM) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, double *white_x, double *white_y, double *red_x,
+ double *red_y, double *green_x, double *green_y, double *blue_x,
+ double *blue_y));
+#endif
+#ifdef PNG_FIXED_POINT_SUPPORTED
+extern PNG_EXPORT(png_uint_32,png_get_cHRM_fixed) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_fixed_point *int_white_x, png_fixed_point
+ *int_white_y, png_fixed_point *int_red_x, png_fixed_point *int_red_y,
+ png_fixed_point *int_green_x, png_fixed_point *int_green_y, png_fixed_point
+ *int_blue_x, png_fixed_point *int_blue_y));
+#endif
+#endif
+
+#ifdef PNG_cHRM_SUPPORTED
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+extern PNG_EXPORT(void,png_set_cHRM) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, double white_x, double white_y, double red_x,
+ double red_y, double green_x, double green_y, double blue_x, double blue_y));
+#endif
+#ifdef PNG_FIXED_POINT_SUPPORTED
+extern PNG_EXPORT(void,png_set_cHRM_fixed) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_fixed_point int_white_x, png_fixed_point int_white_y,
+ png_fixed_point int_red_x, png_fixed_point int_red_y, png_fixed_point
+ int_green_x, png_fixed_point int_green_y, png_fixed_point int_blue_x,
+ png_fixed_point int_blue_y));
+#endif
+#endif
+
+#ifdef PNG_gAMA_SUPPORTED
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+extern PNG_EXPORT(png_uint_32,png_get_gAMA) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, double *file_gamma));
+#endif
+extern PNG_EXPORT(png_uint_32,png_get_gAMA_fixed) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_fixed_point *int_file_gamma));
+#endif
+
+#ifdef PNG_gAMA_SUPPORTED
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+extern PNG_EXPORT(void,png_set_gAMA) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, double file_gamma));
+#endif
+extern PNG_EXPORT(void,png_set_gAMA_fixed) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_fixed_point int_file_gamma));
+#endif
+
+#ifdef PNG_hIST_SUPPORTED
+extern PNG_EXPORT(png_uint_32,png_get_hIST) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_uint_16p *hist));
+#endif
+
+#ifdef PNG_hIST_SUPPORTED
+extern PNG_EXPORT(void,png_set_hIST) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_uint_16p hist));
+#endif
+
+extern PNG_EXPORT(png_uint_32,png_get_IHDR) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_uint_32 *width, png_uint_32 *height,
+ int *bit_depth, int *color_type, int *interlace_method,
+ int *compression_method, int *filter_method));
+
+extern PNG_EXPORT(void,png_set_IHDR) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_uint_32 width, png_uint_32 height, int bit_depth,
+ int color_type, int interlace_method, int compression_method,
+ int filter_method));
+
+#ifdef PNG_oFFs_SUPPORTED
+extern PNG_EXPORT(png_uint_32,png_get_oFFs) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_int_32 *offset_x, png_int_32 *offset_y,
+ int *unit_type));
+#endif
+
+#ifdef PNG_oFFs_SUPPORTED
+extern PNG_EXPORT(void,png_set_oFFs) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_int_32 offset_x, png_int_32 offset_y,
+ int unit_type));
+#endif
+
+#ifdef PNG_pCAL_SUPPORTED
+extern PNG_EXPORT(png_uint_32,png_get_pCAL) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_charp *purpose, png_int_32 *X0, png_int_32 *X1,
+ int *type, int *nparams, png_charp *units, png_charpp *params));
+#endif
+
+#ifdef PNG_pCAL_SUPPORTED
+extern PNG_EXPORT(void,png_set_pCAL) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_charp purpose, png_int_32 X0, png_int_32 X1,
+ int type, int nparams, png_charp units, png_charpp params));
+#endif
+
+#ifdef PNG_pHYs_SUPPORTED
+extern PNG_EXPORT(png_uint_32,png_get_pHYs) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_uint_32 *res_x, png_uint_32 *res_y, int *unit_type));
+#endif
+
+#ifdef PNG_pHYs_SUPPORTED
+extern PNG_EXPORT(void,png_set_pHYs) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_uint_32 res_x, png_uint_32 res_y, int unit_type));
+#endif
+
+extern PNG_EXPORT(png_uint_32,png_get_PLTE) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_colorp *palette, int *num_palette));
+
+extern PNG_EXPORT(void,png_set_PLTE) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_colorp palette, int num_palette));
+
+#ifdef PNG_sBIT_SUPPORTED
+extern PNG_EXPORT(png_uint_32,png_get_sBIT) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_color_8p *sig_bit));
+#endif
+
+#ifdef PNG_sBIT_SUPPORTED
+extern PNG_EXPORT(void,png_set_sBIT) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_color_8p sig_bit));
+#endif
+
+#ifdef PNG_sRGB_SUPPORTED
+extern PNG_EXPORT(png_uint_32,png_get_sRGB) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, int *intent));
+#endif
+
+#ifdef PNG_sRGB_SUPPORTED
+extern PNG_EXPORT(void,png_set_sRGB) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, int intent));
+extern PNG_EXPORT(void,png_set_sRGB_gAMA_and_cHRM) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, int intent));
+#endif
+
+#ifdef PNG_iCCP_SUPPORTED
+extern PNG_EXPORT(png_uint_32,png_get_iCCP) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_charpp name, int *compression_type,
+ png_charpp profile, png_uint_32 *proflen));
+ /* Note to maintainer: profile should be png_bytepp */
+#endif
+
+#ifdef PNG_iCCP_SUPPORTED
+extern PNG_EXPORT(void,png_set_iCCP) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_charp name, int compression_type,
+ png_charp profile, png_uint_32 proflen));
+ /* Note to maintainer: profile should be png_bytep */
+#endif
+
+#ifdef PNG_sPLT_SUPPORTED
+extern PNG_EXPORT(png_uint_32,png_get_sPLT) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_sPLT_tpp entries));
+#endif
+
+#ifdef PNG_sPLT_SUPPORTED
+extern PNG_EXPORT(void,png_set_sPLT) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_sPLT_tp entries, int nentries));
+#endif
+
+#ifdef PNG_TEXT_SUPPORTED
+/* png_get_text also returns the number of text chunks in *num_text */
+extern PNG_EXPORT(png_uint_32,png_get_text) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_textp *text_ptr, int *num_text));
+#endif
+
+/*
+ * Note while png_set_text() will accept a structure whose text,
+ * language, and translated keywords are NULL pointers, the structure
+ * returned by png_get_text will always contain regular
+ * zero-terminated C strings. They might be empty strings but
+ * they will never be NULL pointers.
+ */
+
+#ifdef PNG_TEXT_SUPPORTED
+extern PNG_EXPORT(void,png_set_text) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_textp text_ptr, int num_text));
+#endif
+
+#ifdef PNG_tIME_SUPPORTED
+extern PNG_EXPORT(png_uint_32,png_get_tIME) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_timep *mod_time));
+#endif
+
+#ifdef PNG_tIME_SUPPORTED
+extern PNG_EXPORT(void,png_set_tIME) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_timep mod_time));
+#endif
+
+#ifdef PNG_tRNS_SUPPORTED
+extern PNG_EXPORT(png_uint_32,png_get_tRNS) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_bytep *trans, int *num_trans,
+ png_color_16p *trans_values));
+#endif
+
+#ifdef PNG_tRNS_SUPPORTED
+extern PNG_EXPORT(void,png_set_tRNS) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_bytep trans, int num_trans,
+ png_color_16p trans_values));
+#endif
+
+#ifdef PNG_tRNS_SUPPORTED
+#endif
+
+#ifdef PNG_sCAL_SUPPORTED
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+extern PNG_EXPORT(png_uint_32,png_get_sCAL) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, int *unit, double *width, double *height));
+#else
+#ifdef PNG_FIXED_POINT_SUPPORTED
+extern PNG_EXPORT(png_uint_32,png_get_sCAL_s) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, int *unit, png_charpp swidth, png_charpp sheight));
+#endif
+#endif
+#endif /* PNG_sCAL_SUPPORTED */
+
+#ifdef PNG_sCAL_SUPPORTED
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+extern PNG_EXPORT(void,png_set_sCAL) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, int unit, double width, double height));
+#else
+#ifdef PNG_FIXED_POINT_SUPPORTED
+extern PNG_EXPORT(void,png_set_sCAL_s) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, int unit, png_charp swidth, png_charp sheight));
+#endif
+#endif
+#endif /* PNG_sCAL_SUPPORTED || PNG_WRITE_sCAL_SUPPORTED */
+
+#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
+/* Provide a list of chunks and how they are to be handled, if the built-in
+ handling or default unknown chunk handling is not desired. Any chunks not
+ listed will be handled in the default manner. The IHDR and IEND chunks
+ must not be listed.
+ keep = 0: follow default behaviour
+ = 1: do not keep
+ = 2: keep only if safe-to-copy
+ = 3: keep even if unsafe-to-copy
+*/
+extern PNG_EXPORT(void, png_set_keep_unknown_chunks) PNGARG((png_structp
+ png_ptr, int keep, png_bytep chunk_list, int num_chunks));
+PNG_EXPORT(int,png_handle_as_unknown) PNGARG((png_structp png_ptr, png_bytep
+ chunk_name));
+#endif
+#ifdef PNG_UNKNOWN_CHUNKS_SUPPORTED
+extern PNG_EXPORT(void, png_set_unknown_chunks) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_unknown_chunkp unknowns, int num_unknowns));
+extern PNG_EXPORT(void, png_set_unknown_chunk_location)
+ PNGARG((png_structp png_ptr, png_infop info_ptr, int chunk, int location));
+extern PNG_EXPORT(png_uint_32,png_get_unknown_chunks) PNGARG((png_structp
+ png_ptr, png_infop info_ptr, png_unknown_chunkpp entries));
+#endif
+
+/* Png_free_data() will turn off the "valid" flag for anything it frees.
+ * If you need to turn it off for a chunk that your application has freed,
+ * you can use png_set_invalid(png_ptr, info_ptr, PNG_INFO_CHNK);
+ */
+extern PNG_EXPORT(void, png_set_invalid) PNGARG((png_structp png_ptr,
+ png_infop info_ptr, int mask));
+
+#ifdef PNG_INFO_IMAGE_SUPPORTED
+/* The "params" pointer is currently not used and is for future expansion. */
+extern PNG_EXPORT(void, png_read_png) PNGARG((png_structp png_ptr,
+ png_infop info_ptr,
+ int transforms,
+ png_voidp params));
+extern PNG_EXPORT(void, png_write_png) PNGARG((png_structp png_ptr,
+ png_infop info_ptr,
+ int transforms,
+ png_voidp params));
+#endif
+
+/* Define PNG_DEBUG at compile time for debugging information. Higher
+ * numbers for PNG_DEBUG mean more debugging information. This has
+ * only been added since version 0.95 so it is not implemented throughout
+ * libpng yet, but more support will be added as needed.
+ */
+#ifdef PNG_DEBUG
+#if (PNG_DEBUG > 0)
+#if !defined(PNG_DEBUG_FILE) && defined(_MSC_VER)
+#include <crtdbg.h>
+#if (PNG_DEBUG > 1)
+#ifndef _DEBUG
+# define _DEBUG
+#endif
+#ifndef png_debug
+#define png_debug(l,m) _RPT0(_CRT_WARN,m PNG_STRING_NEWLINE)
+#endif
+#ifndef png_debug1
+#define png_debug1(l,m,p1) _RPT1(_CRT_WARN,m PNG_STRING_NEWLINE,p1)
+#endif
+#ifndef png_debug2
+#define png_debug2(l,m,p1,p2) _RPT2(_CRT_WARN,m PNG_STRING_NEWLINE,p1,p2)
+#endif
+#endif
+#else /* PNG_DEBUG_FILE || !_MSC_VER */
+#ifndef PNG_DEBUG_FILE
+#define PNG_DEBUG_FILE stderr
+#endif /* PNG_DEBUG_FILE */
+
+#if (PNG_DEBUG > 1)
+/* Note: ["%s"m PNG_STRING_NEWLINE] probably does not work on non-ISO
+ * compilers.
+ */
+# ifdef __STDC__
+# ifndef png_debug
+# define png_debug(l,m) \
+ { \
+ int num_tabs=l; \
+ fprintf(PNG_DEBUG_FILE,"%s"m PNG_STRING_NEWLINE,(num_tabs==1 ? "\t" : \
+ (num_tabs==2 ? "\t\t":(num_tabs>2 ? "\t\t\t":"")))); \
+ }
+# endif
+# ifndef png_debug1
+# define png_debug1(l,m,p1) \
+ { \
+ int num_tabs=l; \
+ fprintf(PNG_DEBUG_FILE,"%s"m PNG_STRING_NEWLINE,(num_tabs==1 ? "\t" : \
+ (num_tabs==2 ? "\t\t":(num_tabs>2 ? "\t\t\t":""))),p1); \
+ }
+# endif
+# ifndef png_debug2
+# define png_debug2(l,m,p1,p2) \
+ { \
+ int num_tabs=l; \
+ fprintf(PNG_DEBUG_FILE,"%s"m PNG_STRING_NEWLINE,(num_tabs==1 ? "\t" : \
+ (num_tabs==2 ? "\t\t":(num_tabs>2 ? "\t\t\t":""))),p1,p2); \
+ }
+# endif
+# else /* __STDC __ */
+# ifndef png_debug
+# define png_debug(l,m) \
+ { \
+ int num_tabs=l; \
+ char format[256]; \
+ snprintf(format,256,"%s%s%s",(num_tabs==1 ? "\t" : \
+ (num_tabs==2 ? "\t\t":(num_tabs>2 ? "\t\t\t":""))), \
+ m,PNG_STRING_NEWLINE); \
+ fprintf(PNG_DEBUG_FILE,format); \
+ }
+# endif
+# ifndef png_debug1
+# define png_debug1(l,m,p1) \
+ { \
+ int num_tabs=l; \
+ char format[256]; \
+ snprintf(format,256,"%s%s%s",(num_tabs==1 ? "\t" : \
+ (num_tabs==2 ? "\t\t":(num_tabs>2 ? "\t\t\t":""))), \
+ m,PNG_STRING_NEWLINE); \
+ fprintf(PNG_DEBUG_FILE,format,p1); \
+ }
+# endif
+# ifndef png_debug2
+# define png_debug2(l,m,p1,p2) \
+ { \
+ int num_tabs=l; \
+ char format[256]; \
+ snprintf(format,256,"%s%s%s",(num_tabs==1 ? "\t" : \
+ (num_tabs==2 ? "\t\t":(num_tabs>2 ? "\t\t\t":""))), \
+ m,PNG_STRING_NEWLINE); \
+ fprintf(PNG_DEBUG_FILE,format,p1,p2); \
+ }
+# endif
+# endif /* __STDC __ */
+#endif /* (PNG_DEBUG > 1) */
+
+#endif /* _MSC_VER */
+#endif /* (PNG_DEBUG > 0) */
+#endif /* PNG_DEBUG */
+#ifndef png_debug
+#define png_debug(l, m)
+#endif
+#ifndef png_debug1
+#define png_debug1(l, m, p1)
+#endif
+#ifndef png_debug2
+#define png_debug2(l, m, p1, p2)
+#endif
+
+extern PNG_EXPORT(png_charp,png_get_copyright) PNGARG((png_structp png_ptr));
+extern PNG_EXPORT(png_charp,png_get_header_ver) PNGARG((png_structp png_ptr));
+extern PNG_EXPORT(png_charp,png_get_header_version) PNGARG((png_structp png_ptr));
+extern PNG_EXPORT(png_charp,png_get_libpng_ver) PNGARG((png_structp png_ptr));
+
+#ifdef PNG_MNG_FEATURES_SUPPORTED
+extern PNG_EXPORT(png_uint_32,png_permit_mng_features) PNGARG((png_structp
+ png_ptr, png_uint_32 mng_features_permitted));
+#endif
+
+/* For use in png_set_keep_unknown, added to version 1.2.6 */
+#define PNG_HANDLE_CHUNK_AS_DEFAULT 0
+#define PNG_HANDLE_CHUNK_NEVER 1
+#define PNG_HANDLE_CHUNK_IF_SAFE 2
+#define PNG_HANDLE_CHUNK_ALWAYS 3
+
+/* Added to version 1.2.0 */
+#ifdef PNG_ASSEMBLER_CODE_SUPPORTED
+#ifdef PNG_MMX_CODE_SUPPORTED
+#define PNG_ASM_FLAG_MMX_SUPPORT_COMPILED 0x01 /* not user-settable */
+#define PNG_ASM_FLAG_MMX_SUPPORT_IN_CPU 0x02 /* not user-settable */
+#define PNG_ASM_FLAG_MMX_READ_COMBINE_ROW 0x04
+#define PNG_ASM_FLAG_MMX_READ_INTERLACE 0x08
+#define PNG_ASM_FLAG_MMX_READ_FILTER_SUB 0x10
+#define PNG_ASM_FLAG_MMX_READ_FILTER_UP 0x20
+#define PNG_ASM_FLAG_MMX_READ_FILTER_AVG 0x40
+#define PNG_ASM_FLAG_MMX_READ_FILTER_PAETH 0x80
+#define PNG_ASM_FLAGS_INITIALIZED 0x80000000 /* not user-settable */
+
+#define PNG_MMX_READ_FLAGS ( PNG_ASM_FLAG_MMX_READ_COMBINE_ROW \
+ | PNG_ASM_FLAG_MMX_READ_INTERLACE \
+ | PNG_ASM_FLAG_MMX_READ_FILTER_SUB \
+ | PNG_ASM_FLAG_MMX_READ_FILTER_UP \
+ | PNG_ASM_FLAG_MMX_READ_FILTER_AVG \
+ | PNG_ASM_FLAG_MMX_READ_FILTER_PAETH )
+#define PNG_MMX_WRITE_FLAGS ( 0 )
+
+#define PNG_MMX_FLAGS ( PNG_ASM_FLAG_MMX_SUPPORT_COMPILED \
+ | PNG_ASM_FLAG_MMX_SUPPORT_IN_CPU \
+ | PNG_MMX_READ_FLAGS \
+ | PNG_MMX_WRITE_FLAGS )
+
+#define PNG_SELECT_READ 1
+#define PNG_SELECT_WRITE 2
+#endif /* PNG_MMX_CODE_SUPPORTED */
+
+#ifndef PNG_1_0_X
+/* pngget.c */
+extern PNG_EXPORT(png_uint_32,png_get_mmx_flagmask)
+ PNGARG((int flag_select, int *compilerID));
+
+/* pngget.c */
+extern PNG_EXPORT(png_uint_32,png_get_asm_flagmask)
+ PNGARG((int flag_select));
+
+/* pngget.c */
+extern PNG_EXPORT(png_uint_32,png_get_asm_flags)
+ PNGARG((png_structp png_ptr));
+
+/* pngget.c */
+extern PNG_EXPORT(png_byte,png_get_mmx_bitdepth_threshold)
+ PNGARG((png_structp png_ptr));
+
+/* pngget.c */
+extern PNG_EXPORT(png_uint_32,png_get_mmx_rowbytes_threshold)
+ PNGARG((png_structp png_ptr));
+
+/* pngset.c */
+extern PNG_EXPORT(void,png_set_asm_flags)
+ PNGARG((png_structp png_ptr, png_uint_32 asm_flags));
+
+/* pngset.c */
+extern PNG_EXPORT(void,png_set_mmx_thresholds)
+ PNGARG((png_structp png_ptr, png_byte mmx_bitdepth_threshold,
+ png_uint_32 mmx_rowbytes_threshold));
+
+#endif /* PNG_1_0_X */
+
+#ifndef PNG_1_0_X
+/* png.c, pnggccrd.c, or pngvcrd.c */
+extern PNG_EXPORT(int,png_mmx_support) PNGARG((void));
+#endif /* PNG_1_0_X */
+#endif /* PNG_ASSEMBLER_CODE_SUPPORTED */
+
+/* Strip the prepended error numbers ("#nnn ") from error and warning
+ * messages before passing them to the error or warning handler.
+ */
+#ifdef PNG_ERROR_NUMBERS_SUPPORTED
+extern PNG_EXPORT(void,png_set_strip_error_numbers) PNGARG((png_structp
+ png_ptr, png_uint_32 strip_mode));
+#endif
+
+/* Added at libpng-1.2.6 */
+#ifdef PNG_SET_USER_LIMITS_SUPPORTED
+extern PNG_EXPORT(void,png_set_user_limits) PNGARG((png_structp
+ png_ptr, png_uint_32 user_width_max, png_uint_32 user_height_max));
+extern PNG_EXPORT(png_uint_32,png_get_user_width_max) PNGARG((png_structp
+ png_ptr));
+extern PNG_EXPORT(png_uint_32,png_get_user_height_max) PNGARG((png_structp
+ png_ptr));
+#endif
+/* Maintainer: Put new public prototypes here ^, in libpng.3, and in
+ * project defs
+ */
+
+#ifdef PNG_READ_COMPOSITE_NODIV_SUPPORTED
+/* With these routines we avoid an integer divide, which will be slower on
+ * most machines. However, it does take more operations than the corresponding
+ * divide method, so it may be slower on a few RISC systems. There are two
+ * shifts (by 8 or 16 bits) and an addition, versus a single integer divide.
+ *
+ * Note that the rounding factors are NOT supposed to be the same! 128 and
+ * 32768 are correct for the NODIV code; 127 and 32767 are correct for the
+ * standard method.
+ *
+ * [Optimized code by Greg Roelofs and Mark Adler...blame us for bugs. :-) ]
+ */
+
+ /* fg and bg should be in `gamma 1.0' space; alpha is the opacity */
+
+# define png_composite(composite, fg, alpha, bg) \
+ { png_uint_16 temp = (png_uint_16)((png_uint_16)(fg) * (png_uint_16)(alpha) \
+ + (png_uint_16)(bg)*(png_uint_16)(255 - \
+ (png_uint_16)(alpha)) + (png_uint_16)128); \
+ (composite) = (png_byte)((temp + (temp >> 8)) >> 8); }
+
+# define png_composite_16(composite, fg, alpha, bg) \
+ { png_uint_32 temp = (png_uint_32)((png_uint_32)(fg) * (png_uint_32)(alpha) \
+ + (png_uint_32)(bg)*(png_uint_32)(65535L - \
+ (png_uint_32)(alpha)) + (png_uint_32)32768L); \
+ (composite) = (png_uint_16)((temp + (temp >> 16)) >> 16); }
+
+#else /* Standard method using integer division */
+
+# define png_composite(composite, fg, alpha, bg) \
+ (composite) = (png_byte)(((png_uint_16)(fg) * (png_uint_16)(alpha) + \
+ (png_uint_16)(bg) * (png_uint_16)(255 - (png_uint_16)(alpha)) + \
+ (png_uint_16)127) / 255)
+
+# define png_composite_16(composite, fg, alpha, bg) \
+ (composite) = (png_uint_16)(((png_uint_32)(fg) * (png_uint_32)(alpha) + \
+ (png_uint_32)(bg)*(png_uint_32)(65535L - (png_uint_32)(alpha)) + \
+ (png_uint_32)32767) / (png_uint_32)65535L)
+
+#endif /* PNG_READ_COMPOSITE_NODIV_SUPPORTED */
+
+/* Inline macros to do direct reads of bytes from the input buffer. These
+ * require that you are using an architecture that uses PNG byte ordering
+ * (MSB first) and supports unaligned data storage. I think that PowerPC
+ * in big-endian mode and 680x0 are the only ones that will support this.
+ * The x86 line of processors definitely do not. The png_get_int_32()
+ * routine also assumes we are using two's complement format for negative
+ * values, which is almost certainly true.
+ */
+#ifdef PNG_READ_BIG_ENDIAN_SUPPORTED
+# define png_get_uint_32(buf) ( *((png_uint_32p) (buf)))
+# define png_get_uint_16(buf) ( *((png_uint_16p) (buf)))
+# define png_get_int_32(buf) ( *((png_int_32p) (buf)))
+#else
+extern PNG_EXPORT(png_uint_32,png_get_uint_32) PNGARG((png_bytep buf));
+extern PNG_EXPORT(png_uint_16,png_get_uint_16) PNGARG((png_bytep buf));
+extern PNG_EXPORT(png_int_32,png_get_int_32) PNGARG((png_bytep buf));
+#endif /* !PNG_READ_BIG_ENDIAN_SUPPORTED */
+extern PNG_EXPORT(png_uint_32,png_get_uint_31)
+ PNGARG((png_structp png_ptr, png_bytep buf));
+/* No png_get_int_16 -- may be added if there's a real need for it. */
+
+/* Place a 32-bit number into a buffer in PNG byte order (big-endian).
+ */
+extern PNG_EXPORT(void,png_save_uint_32)
+ PNGARG((png_bytep buf, png_uint_32 i));
+extern PNG_EXPORT(void,png_save_int_32)
+ PNGARG((png_bytep buf, png_int_32 i));
+
+/* Place a 16-bit number into a buffer in PNG byte order.
+ * The parameter is declared unsigned int, not png_uint_16,
+ * just to avoid potential problems on pre-ANSI C compilers.
+ */
+extern PNG_EXPORT(void,png_save_uint_16)
+ PNGARG((png_bytep buf, unsigned int i));
+/* No png_save_int_16 -- may be added if there's a real need for it. */
+
+/* ************************************************************************* */
+
+/* These next functions are used internally in the code. They generally
+ * shouldn't be used unless you are writing code to add or replace some
+ * functionality in libpng. More information about most functions can
+ * be found in the files where the functions are located.
+ */
+
+
+/* Various modes of operation, that are visible to applications because
+ * they are used for unknown chunk location.
+ */
+#define PNG_HAVE_IHDR 0x01
+#define PNG_HAVE_PLTE 0x02
+#define PNG_HAVE_IDAT 0x04
+#define PNG_AFTER_IDAT 0x08 /* Have complete zlib datastream */
+#define PNG_HAVE_IEND 0x10
+
+#ifdef PNG_INTERNAL
+
+/* More modes of operation. Note that after an init, mode is set to
+ * zero automatically when the structure is created.
+ */
+#define PNG_HAVE_gAMA 0x20
+#define PNG_HAVE_cHRM 0x40
+#define PNG_HAVE_sRGB 0x80
+#define PNG_HAVE_CHUNK_HEADER 0x100
+#define PNG_WROTE_tIME 0x200
+#define PNG_WROTE_INFO_BEFORE_PLTE 0x400
+#define PNG_BACKGROUND_IS_GRAY 0x800
+#define PNG_HAVE_PNG_SIGNATURE 0x1000
+#define PNG_HAVE_CHUNK_AFTER_IDAT 0x2000 /* Have another chunk after IDAT */
+
+/* Flags for the transformations the PNG library does on the image data */
+#define PNG_BGR 0x0001
+#define PNG_INTERLACE 0x0002
+#define PNG_PACK 0x0004
+#define PNG_SHIFT 0x0008
+#define PNG_SWAP_BYTES 0x0010
+#define PNG_INVERT_MONO 0x0020
+#define PNG_DITHER 0x0040
+#define PNG_BACKGROUND 0x0080
+#define PNG_BACKGROUND_EXPAND 0x0100
+ /* 0x0200 unused */
+#define PNG_16_TO_8 0x0400
+#define PNG_RGBA 0x0800
+#define PNG_EXPAND 0x1000
+#define PNG_GAMMA 0x2000
+#define PNG_GRAY_TO_RGB 0x4000
+#define PNG_FILLER 0x8000L
+#define PNG_PACKSWAP 0x10000L
+#define PNG_SWAP_ALPHA 0x20000L
+#define PNG_STRIP_ALPHA 0x40000L
+#define PNG_INVERT_ALPHA 0x80000L
+#define PNG_USER_TRANSFORM 0x100000L
+#define PNG_RGB_TO_GRAY_ERR 0x200000L
+#define PNG_RGB_TO_GRAY_WARN 0x400000L
+#define PNG_RGB_TO_GRAY 0x600000L /* two bits, RGB_TO_GRAY_ERR|WARN */
+ /* 0x800000L Unused */
+#define PNG_ADD_ALPHA 0x1000000L /* Added to libpng-1.2.7 */
+#define PNG_EXPAND_tRNS 0x2000000L /* Added to libpng-1.2.9 */
+#define PNG_PREMULTIPLY_ALPHA 0x4000000L /* Added to libpng-1.2.41 */
+ /* by volker */
+ /* 0x8000000L unused */
+ /* 0x10000000L unused */
+ /* 0x20000000L unused */
+ /* 0x40000000L unused */
+
+/* Flags for png_create_struct */
+#define PNG_STRUCT_PNG 0x0001
+#define PNG_STRUCT_INFO 0x0002
+
+/* Scaling factor for filter heuristic weighting calculations */
+#define PNG_WEIGHT_SHIFT 8
+#define PNG_WEIGHT_FACTOR (1<<(PNG_WEIGHT_SHIFT))
+#define PNG_COST_SHIFT 3
+#define PNG_COST_FACTOR (1<<(PNG_COST_SHIFT))
+
+/* Flags for the png_ptr->flags rather than declaring a byte for each one */
+#define PNG_FLAG_ZLIB_CUSTOM_STRATEGY 0x0001
+#define PNG_FLAG_ZLIB_CUSTOM_LEVEL 0x0002
+#define PNG_FLAG_ZLIB_CUSTOM_MEM_LEVEL 0x0004
+#define PNG_FLAG_ZLIB_CUSTOM_WINDOW_BITS 0x0008
+#define PNG_FLAG_ZLIB_CUSTOM_METHOD 0x0010
+#define PNG_FLAG_ZLIB_FINISHED 0x0020
+#define PNG_FLAG_ROW_INIT 0x0040
+#define PNG_FLAG_FILLER_AFTER 0x0080
+#define PNG_FLAG_CRC_ANCILLARY_USE 0x0100
+#define PNG_FLAG_CRC_ANCILLARY_NOWARN 0x0200
+#define PNG_FLAG_CRC_CRITICAL_USE 0x0400
+#define PNG_FLAG_CRC_CRITICAL_IGNORE 0x0800
+#define PNG_FLAG_FREE_PLTE 0x1000
+#define PNG_FLAG_FREE_TRNS 0x2000
+#define PNG_FLAG_FREE_HIST 0x4000
+#define PNG_FLAG_KEEP_UNKNOWN_CHUNKS 0x8000L
+#define PNG_FLAG_KEEP_UNSAFE_CHUNKS 0x10000L
+#define PNG_FLAG_LIBRARY_MISMATCH 0x20000L
+#define PNG_FLAG_STRIP_ERROR_NUMBERS 0x40000L
+#define PNG_FLAG_STRIP_ERROR_TEXT 0x80000L
+#define PNG_FLAG_MALLOC_NULL_MEM_OK 0x100000L
+#define PNG_FLAG_ADD_ALPHA 0x200000L /* Added to libpng-1.2.8 */
+#define PNG_FLAG_STRIP_ALPHA 0x400000L /* Added to libpng-1.2.8 */
+ /* 0x800000L unused */
+ /* 0x1000000L unused */
+ /* 0x2000000L unused */
+ /* 0x4000000L unused */
+ /* 0x8000000L unused */
+ /* 0x10000000L unused */
+ /* 0x20000000L unused */
+ /* 0x40000000L unused */
+
+#define PNG_FLAG_CRC_ANCILLARY_MASK (PNG_FLAG_CRC_ANCILLARY_USE | \
+ PNG_FLAG_CRC_ANCILLARY_NOWARN)
+
+#define PNG_FLAG_CRC_CRITICAL_MASK (PNG_FLAG_CRC_CRITICAL_USE | \
+ PNG_FLAG_CRC_CRITICAL_IGNORE)
+
+#define PNG_FLAG_CRC_MASK (PNG_FLAG_CRC_ANCILLARY_MASK | \
+ PNG_FLAG_CRC_CRITICAL_MASK)
+
+/* Save typing and make code easier to understand */
+
+#define PNG_COLOR_DIST(c1, c2) (abs((int)((c1).red) - (int)((c2).red)) + \
+ abs((int)((c1).green) - (int)((c2).green)) + \
+ abs((int)((c1).blue) - (int)((c2).blue)))
+
+/* Added to libpng-1.2.6 JB */
+#define PNG_ROWBYTES(pixel_bits, width) \
+ ((pixel_bits) >= 8 ? \
+ ((width) * (((png_uint_32)(pixel_bits)) >> 3)) : \
+ (( ((width) * ((png_uint_32)(pixel_bits))) + 7) >> 3) )
+
+/* PNG_OUT_OF_RANGE returns true if value is outside the range
+ * ideal-delta..ideal+delta. Each argument is evaluated twice.
+ * "ideal" and "delta" should be constants, normally simple
+ * integers, "value" a variable. Added to libpng-1.2.6 JB
+ */
+#define PNG_OUT_OF_RANGE(value, ideal, delta) \
+ ( (value) < (ideal)-(delta) || (value) > (ideal)+(delta) )
+
+/* Variables declared in png.c - only it needs to define PNG_NO_EXTERN */
+#if !defined(PNG_NO_EXTERN) || defined(PNG_ALWAYS_EXTERN)
+/* Place to hold the signature string for a PNG file. */
+#ifdef PNG_USE_GLOBAL_ARRAYS
+ PNG_EXPORT_VAR (PNG_CONST png_byte FARDATA) png_sig[8];
+#else
+#endif
+#endif /* PNG_NO_EXTERN */
+
+/* Constant strings for known chunk types. If you need to add a chunk,
+ * define the name here, and add an invocation of the macro in png.c and
+ * wherever it's needed.
+ */
+#define PNG_IHDR png_byte png_IHDR[5] = { 73, 72, 68, 82, '\0'}
+#define PNG_IDAT png_byte png_IDAT[5] = { 73, 68, 65, 84, '\0'}
+#define PNG_IEND png_byte png_IEND[5] = { 73, 69, 78, 68, '\0'}
+#define PNG_PLTE png_byte png_PLTE[5] = { 80, 76, 84, 69, '\0'}
+#define PNG_bKGD png_byte png_bKGD[5] = { 98, 75, 71, 68, '\0'}
+#define PNG_cHRM png_byte png_cHRM[5] = { 99, 72, 82, 77, '\0'}
+#define PNG_gAMA png_byte png_gAMA[5] = {103, 65, 77, 65, '\0'}
+#define PNG_hIST png_byte png_hIST[5] = {104, 73, 83, 84, '\0'}
+#define PNG_iCCP png_byte png_iCCP[5] = {105, 67, 67, 80, '\0'}
+#define PNG_iTXt png_byte png_iTXt[5] = {105, 84, 88, 116, '\0'}
+#define PNG_oFFs png_byte png_oFFs[5] = {111, 70, 70, 115, '\0'}
+#define PNG_pCAL png_byte png_pCAL[5] = {112, 67, 65, 76, '\0'}
+#define PNG_sCAL png_byte png_sCAL[5] = {115, 67, 65, 76, '\0'}
+#define PNG_pHYs png_byte png_pHYs[5] = {112, 72, 89, 115, '\0'}
+#define PNG_sBIT png_byte png_sBIT[5] = {115, 66, 73, 84, '\0'}
+#define PNG_sPLT png_byte png_sPLT[5] = {115, 80, 76, 84, '\0'}
+#define PNG_sRGB png_byte png_sRGB[5] = {115, 82, 71, 66, '\0'}
+#define PNG_tEXt png_byte png_tEXt[5] = {116, 69, 88, 116, '\0'}
+#define PNG_tIME png_byte png_tIME[5] = {116, 73, 77, 69, '\0'}
+#define PNG_tRNS png_byte png_tRNS[5] = {116, 82, 78, 83, '\0'}
+#define PNG_zTXt png_byte png_zTXt[5] = {122, 84, 88, 116, '\0'}
+
+#ifdef PNG_USE_GLOBAL_ARRAYS
+PNG_EXPORT_VAR (png_byte FARDATA) png_IHDR[5];
+PNG_EXPORT_VAR (png_byte FARDATA) png_IDAT[5];
+PNG_EXPORT_VAR (png_byte FARDATA) png_IEND[5];
+PNG_EXPORT_VAR (png_byte FARDATA) png_PLTE[5];
+PNG_EXPORT_VAR (png_byte FARDATA) png_bKGD[5];
+PNG_EXPORT_VAR (png_byte FARDATA) png_cHRM[5];
+PNG_EXPORT_VAR (png_byte FARDATA) png_gAMA[5];
+PNG_EXPORT_VAR (png_byte FARDATA) png_hIST[5];
+PNG_EXPORT_VAR (png_byte FARDATA) png_iCCP[5];
+PNG_EXPORT_VAR (png_byte FARDATA) png_iTXt[5];
+PNG_EXPORT_VAR (png_byte FARDATA) png_oFFs[5];
+PNG_EXPORT_VAR (png_byte FARDATA) png_pCAL[5];
+PNG_EXPORT_VAR (png_byte FARDATA) png_sCAL[5];
+PNG_EXPORT_VAR (png_byte FARDATA) png_pHYs[5];
+PNG_EXPORT_VAR (png_byte FARDATA) png_sBIT[5];
+PNG_EXPORT_VAR (png_byte FARDATA) png_sPLT[5];
+PNG_EXPORT_VAR (png_byte FARDATA) png_sRGB[5];
+PNG_EXPORT_VAR (png_byte FARDATA) png_tEXt[5];
+PNG_EXPORT_VAR (png_byte FARDATA) png_tIME[5];
+PNG_EXPORT_VAR (png_byte FARDATA) png_tRNS[5];
+PNG_EXPORT_VAR (png_byte FARDATA) png_zTXt[5];
+#endif /* PNG_USE_GLOBAL_ARRAYS */
+
+#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
+/* Initialize png_ptr struct for reading, and allocate any other memory.
+ * (old interface - DEPRECATED - use png_create_read_struct instead).
+ */
+extern PNG_EXPORT(void,png_read_init) PNGARG((png_structp png_ptr))
+ PNG_DEPRECATED;
+#undef png_read_init
+#define png_read_init(png_ptr) png_read_init_3(&png_ptr, \
+ PNG_LIBPNG_VER_STRING, png_sizeof(png_struct));
+#endif
+
+extern PNG_EXPORT(void,png_read_init_3) PNGARG((png_structpp ptr_ptr,
+ png_const_charp user_png_ver, png_size_t png_struct_size));
+#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
+extern PNG_EXPORT(void,png_read_init_2) PNGARG((png_structp png_ptr,
+ png_const_charp user_png_ver, png_size_t png_struct_size, png_size_t
+ png_info_size));
+#endif
+
+#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
+/* Initialize png_ptr struct for writing, and allocate any other memory.
+ * (old interface - DEPRECATED - use png_create_write_struct instead).
+ */
+extern PNG_EXPORT(void,png_write_init) PNGARG((png_structp png_ptr))
+ PNG_DEPRECATED;
+#undef png_write_init
+#define png_write_init(png_ptr) png_write_init_3(&png_ptr, \
+ PNG_LIBPNG_VER_STRING, png_sizeof(png_struct));
+#endif
+
+extern PNG_EXPORT(void,png_write_init_3) PNGARG((png_structpp ptr_ptr,
+ png_const_charp user_png_ver, png_size_t png_struct_size));
+extern PNG_EXPORT(void,png_write_init_2) PNGARG((png_structp png_ptr,
+ png_const_charp user_png_ver, png_size_t png_struct_size, png_size_t
+ png_info_size));
+
+/* Allocate memory for an internal libpng struct */
+PNG_EXTERN png_voidp png_create_struct PNGARG((int type)) PNG_PRIVATE;
+
+/* Free memory from internal libpng struct */
+PNG_EXTERN void png_destroy_struct PNGARG((png_voidp struct_ptr)) PNG_PRIVATE;
+
+PNG_EXTERN png_voidp png_create_struct_2 PNGARG((int type, png_malloc_ptr
+ malloc_fn, png_voidp mem_ptr)) PNG_PRIVATE;
+PNG_EXTERN void png_destroy_struct_2 PNGARG((png_voidp struct_ptr,
+ png_free_ptr free_fn, png_voidp mem_ptr)) PNG_PRIVATE;
+
+/* Free any memory that info_ptr points to and reset struct. */
+PNG_EXTERN void png_info_destroy PNGARG((png_structp png_ptr,
+ png_infop info_ptr)) PNG_PRIVATE;
+
+#ifndef PNG_1_0_X
+/* Function to allocate memory for zlib. */
+PNG_EXTERN voidpf png_zalloc PNGARG((voidpf png_ptr, uInt items,
+ uInt size)) PNG_PRIVATE;
+
+/* Function to free memory for zlib */
+PNG_EXTERN void png_zfree PNGARG((voidpf png_ptr, voidpf ptr)) PNG_PRIVATE;
+
+#ifdef PNG_SIZE_T
+/* Function to convert a sizeof an item to png_sizeof item */
+ PNG_EXTERN png_size_t PNGAPI png_convert_size PNGARG((size_t size))
+ PNG_PRIVATE;
+#endif
+
+/* Next four functions are used internally as callbacks. PNGAPI is required
+ * but not PNG_EXPORT. PNGAPI added at libpng version 1.2.3.
+ */
+
+PNG_EXTERN void PNGAPI png_default_read_data PNGARG((png_structp png_ptr,
+ png_bytep data, png_size_t length)) PNG_PRIVATE;
+
+#ifdef PNG_PROGRESSIVE_READ_SUPPORTED
+PNG_EXTERN void PNGAPI png_push_fill_buffer PNGARG((png_structp png_ptr,
+ png_bytep buffer, png_size_t length)) PNG_PRIVATE;
+#endif
+
+PNG_EXTERN void PNGAPI png_default_write_data PNGARG((png_structp png_ptr,
+ png_bytep data, png_size_t length)) PNG_PRIVATE;
+
+#ifdef PNG_WRITE_FLUSH_SUPPORTED
+#ifdef PNG_STDIO_SUPPORTED
+PNG_EXTERN void PNGAPI png_default_flush PNGARG((png_structp png_ptr))
+ PNG_PRIVATE;
+#endif
+#endif
+#else /* PNG_1_0_X */
+#ifdef PNG_PROGRESSIVE_READ_SUPPORTED
+PNG_EXTERN void png_push_fill_buffer PNGARG((png_structp png_ptr,
+ png_bytep buffer, png_size_t length)) PNG_PRIVATE;
+#endif
+#endif /* PNG_1_0_X */
+
+/* Reset the CRC variable */
+PNG_EXTERN void png_reset_crc PNGARG((png_structp png_ptr)) PNG_PRIVATE;
+
+/* Write the "data" buffer to whatever output you are using. */
+PNG_EXTERN void png_write_data PNGARG((png_structp png_ptr, png_bytep data,
+ png_size_t length)) PNG_PRIVATE;
+
+/* Read data from whatever input you are using into the "data" buffer */
+PNG_EXTERN void png_read_data PNGARG((png_structp png_ptr, png_bytep data,
+ png_size_t length)) PNG_PRIVATE;
+
+/* Read bytes into buf, and update png_ptr->crc */
+PNG_EXTERN void png_crc_read PNGARG((png_structp png_ptr, png_bytep buf,
+ png_size_t length)) PNG_PRIVATE;
+
+/* Decompress data in a chunk that uses compression */
+#if defined(PNG_zTXt_SUPPORTED) || defined(PNG_iTXt_SUPPORTED) || \
+ defined(PNG_iCCP_SUPPORTED) || defined(PNG_sPLT_SUPPORTED)
+PNG_EXTERN void png_decompress_chunk PNGARG((png_structp png_ptr,
+ int comp_type, png_size_t chunklength,
+ png_size_t prefix_length, png_size_t *data_length)) PNG_PRIVATE;
+#endif
+
+/* Read "skip" bytes, read the file crc, and (optionally) verify png_ptr->crc */
+PNG_EXTERN int png_crc_finish PNGARG((png_structp png_ptr, png_uint_32 skip)
+ PNG_PRIVATE);
+
+/* Read the CRC from the file and compare it to the libpng calculated CRC */
+PNG_EXTERN int png_crc_error PNGARG((png_structp png_ptr)) PNG_PRIVATE;
+
+/* Calculate the CRC over a section of data. Note that we are only
+ * passing a maximum of 64K on systems that have this as a memory limit,
+ * since this is the maximum buffer size we can specify.
+ */
+PNG_EXTERN void png_calculate_crc PNGARG((png_structp png_ptr, png_bytep ptr,
+ png_size_t length)) PNG_PRIVATE;
+
+#ifdef PNG_WRITE_FLUSH_SUPPORTED
+PNG_EXTERN void png_flush PNGARG((png_structp png_ptr)) PNG_PRIVATE;
+#endif
+
+/* Simple function to write the signature */
+PNG_EXTERN void png_write_sig PNGARG((png_structp png_ptr)) PNG_PRIVATE;
+
+/* Write various chunks */
+
+/* Write the IHDR chunk, and update the png_struct with the necessary
+ * information.
+ */
+PNG_EXTERN void png_write_IHDR PNGARG((png_structp png_ptr, png_uint_32 width,
+ png_uint_32 height,
+ int bit_depth, int color_type, int compression_method, int filter_method,
+ int interlace_method)) PNG_PRIVATE;
+
+PNG_EXTERN void png_write_PLTE PNGARG((png_structp png_ptr, png_colorp palette,
+ png_uint_32 num_pal)) PNG_PRIVATE;
+
+PNG_EXTERN void png_write_IDAT PNGARG((png_structp png_ptr, png_bytep data,
+ png_size_t length)) PNG_PRIVATE;
+
+PNG_EXTERN void png_write_IEND PNGARG((png_structp png_ptr)) PNG_PRIVATE;
+
+#ifdef PNG_WRITE_gAMA_SUPPORTED
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+PNG_EXTERN void png_write_gAMA PNGARG((png_structp png_ptr, double file_gamma))
+ PNG_PRIVATE;
+#endif
+#ifdef PNG_FIXED_POINT_SUPPORTED
+PNG_EXTERN void png_write_gAMA_fixed PNGARG((png_structp png_ptr,
+ png_fixed_point file_gamma)) PNG_PRIVATE;
+#endif
+#endif
+
+#ifdef PNG_WRITE_sBIT_SUPPORTED
+PNG_EXTERN void png_write_sBIT PNGARG((png_structp png_ptr, png_color_8p sbit,
+ int color_type)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_WRITE_cHRM_SUPPORTED
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+PNG_EXTERN void png_write_cHRM PNGARG((png_structp png_ptr,
+ double white_x, double white_y,
+ double red_x, double red_y, double green_x, double green_y,
+ double blue_x, double blue_y)) PNG_PRIVATE;
+#endif
+#ifdef PNG_FIXED_POINT_SUPPORTED
+PNG_EXTERN void png_write_cHRM_fixed PNGARG((png_structp png_ptr,
+ png_fixed_point int_white_x, png_fixed_point int_white_y,
+ png_fixed_point int_red_x, png_fixed_point int_red_y, png_fixed_point
+ int_green_x, png_fixed_point int_green_y, png_fixed_point int_blue_x,
+ png_fixed_point int_blue_y)) PNG_PRIVATE;
+#endif
+#endif
+
+#ifdef PNG_WRITE_sRGB_SUPPORTED
+PNG_EXTERN void png_write_sRGB PNGARG((png_structp png_ptr,
+ int intent)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_WRITE_iCCP_SUPPORTED
+PNG_EXTERN void png_write_iCCP PNGARG((png_structp png_ptr,
+ png_charp name, int compression_type,
+ png_charp profile, int proflen)) PNG_PRIVATE;
+ /* Note to maintainer: profile should be png_bytep */
+#endif
+
+#ifdef PNG_WRITE_sPLT_SUPPORTED
+PNG_EXTERN void png_write_sPLT PNGARG((png_structp png_ptr,
+ png_sPLT_tp palette)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_WRITE_tRNS_SUPPORTED
+PNG_EXTERN void png_write_tRNS PNGARG((png_structp png_ptr, png_bytep trans,
+ png_color_16p values, int number, int color_type)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_WRITE_bKGD_SUPPORTED
+PNG_EXTERN void png_write_bKGD PNGARG((png_structp png_ptr,
+ png_color_16p values, int color_type)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_WRITE_hIST_SUPPORTED
+PNG_EXTERN void png_write_hIST PNGARG((png_structp png_ptr, png_uint_16p hist,
+ int num_hist)) PNG_PRIVATE;
+#endif
+
+#if defined(PNG_WRITE_TEXT_SUPPORTED) || defined(PNG_WRITE_pCAL_SUPPORTED) || \
+ defined(PNG_WRITE_iCCP_SUPPORTED) || defined(PNG_WRITE_sPLT_SUPPORTED)
+PNG_EXTERN png_size_t png_check_keyword PNGARG((png_structp png_ptr,
+ png_charp key, png_charpp new_key)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_WRITE_tEXt_SUPPORTED
+PNG_EXTERN void png_write_tEXt PNGARG((png_structp png_ptr, png_charp key,
+ png_charp text, png_size_t text_len)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_WRITE_zTXt_SUPPORTED
+PNG_EXTERN void png_write_zTXt PNGARG((png_structp png_ptr, png_charp key,
+ png_charp text, png_size_t text_len, int compression)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_WRITE_iTXt_SUPPORTED
+PNG_EXTERN void png_write_iTXt PNGARG((png_structp png_ptr,
+ int compression, png_charp key, png_charp lang, png_charp lang_key,
+ png_charp text)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_TEXT_SUPPORTED /* Added at version 1.0.14 and 1.2.4 */
+PNG_EXTERN int png_set_text_2 PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_textp text_ptr, int num_text)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_WRITE_oFFs_SUPPORTED
+PNG_EXTERN void png_write_oFFs PNGARG((png_structp png_ptr,
+ png_int_32 x_offset, png_int_32 y_offset, int unit_type)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_WRITE_pCAL_SUPPORTED
+PNG_EXTERN void png_write_pCAL PNGARG((png_structp png_ptr, png_charp purpose,
+ png_int_32 X0, png_int_32 X1, int type, int nparams,
+ png_charp units, png_charpp params)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_WRITE_pHYs_SUPPORTED
+PNG_EXTERN void png_write_pHYs PNGARG((png_structp png_ptr,
+ png_uint_32 x_pixels_per_unit, png_uint_32 y_pixels_per_unit,
+ int unit_type)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_WRITE_tIME_SUPPORTED
+PNG_EXTERN void png_write_tIME PNGARG((png_structp png_ptr,
+ png_timep mod_time)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_WRITE_sCAL_SUPPORTED
+#if defined(PNG_FLOATING_POINT_SUPPORTED) && !defined(PNG_NO_STDIO)
+PNG_EXTERN void png_write_sCAL PNGARG((png_structp png_ptr,
+ int unit, double width, double height)) PNG_PRIVATE;
+#else
+#ifdef PNG_FIXED_POINT_SUPPORTED
+PNG_EXTERN void png_write_sCAL_s PNGARG((png_structp png_ptr,
+ int unit, png_charp width, png_charp height)) PNG_PRIVATE;
+#endif
+#endif
+#endif
+
+/* Called when finished processing a row of data */
+PNG_EXTERN void png_write_finish_row PNGARG((png_structp png_ptr)) PNG_PRIVATE;
+
+/* Internal use only. Called before first row of data */
+PNG_EXTERN void png_write_start_row PNGARG((png_structp png_ptr)) PNG_PRIVATE;
+
+#ifdef PNG_READ_GAMMA_SUPPORTED
+PNG_EXTERN void png_build_gamma_table PNGARG((png_structp png_ptr)) PNG_PRIVATE;
+#endif
+
+/* Combine a row of data, dealing with alpha, etc. if requested */
+PNG_EXTERN void png_combine_row PNGARG((png_structp png_ptr, png_bytep row,
+ int mask)) PNG_PRIVATE;
+
+#ifdef PNG_READ_INTERLACING_SUPPORTED
+/* Expand an interlaced row */
+/* OLD pre-1.0.9 interface:
+PNG_EXTERN void png_do_read_interlace PNGARG((png_row_infop row_info,
+ png_bytep row, int pass, png_uint_32 transformations)) PNG_PRIVATE;
+ */
+PNG_EXTERN void png_do_read_interlace PNGARG((png_structp png_ptr)) PNG_PRIVATE;
+#endif
+
+/* GRR TO DO (2.0 or whenever): simplify other internal calling interfaces */
+
+#ifdef PNG_WRITE_INTERLACING_SUPPORTED
+/* Grab pixels out of a row for an interlaced pass */
+PNG_EXTERN void png_do_write_interlace PNGARG((png_row_infop row_info,
+ png_bytep row, int pass)) PNG_PRIVATE;
+#endif
+
+/* Unfilter a row */
+PNG_EXTERN void png_read_filter_row PNGARG((png_structp png_ptr,
+ png_row_infop row_info, png_bytep row, png_bytep prev_row,
+ int filter)) PNG_PRIVATE;
+
+/* Choose the best filter to use and filter the row data */
+PNG_EXTERN void png_write_find_filter PNGARG((png_structp png_ptr,
+ png_row_infop row_info)) PNG_PRIVATE;
+
+/* Write out the filtered row. */
+PNG_EXTERN void png_write_filtered_row PNGARG((png_structp png_ptr,
+ png_bytep filtered_row)) PNG_PRIVATE;
+/* Finish a row while reading, dealing with interlacing passes, etc. */
+PNG_EXTERN void png_read_finish_row PNGARG((png_structp png_ptr));
+
+/* Initialize the row buffers, etc. */
+PNG_EXTERN void png_read_start_row PNGARG((png_structp png_ptr)) PNG_PRIVATE;
+/* Optional call to update the users info structure */
+PNG_EXTERN void png_read_transform_info PNGARG((png_structp png_ptr,
+ png_infop info_ptr)) PNG_PRIVATE;
+
+/* These are the functions that do the transformations */
+#ifdef PNG_READ_FILLER_SUPPORTED
+PNG_EXTERN void png_do_read_filler PNGARG((png_row_infop row_info,
+ png_bytep row, png_uint_32 filler, png_uint_32 flags)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_READ_SWAP_ALPHA_SUPPORTED
+PNG_EXTERN void png_do_read_swap_alpha PNGARG((png_row_infop row_info,
+ png_bytep row)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_WRITE_SWAP_ALPHA_SUPPORTED
+PNG_EXTERN void png_do_write_swap_alpha PNGARG((png_row_infop row_info,
+ png_bytep row)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_READ_INVERT_ALPHA_SUPPORTED
+PNG_EXTERN void png_do_read_invert_alpha PNGARG((png_row_infop row_info,
+ png_bytep row)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_WRITE_INVERT_ALPHA_SUPPORTED
+PNG_EXTERN void png_do_write_invert_alpha PNGARG((png_row_infop row_info,
+ png_bytep row)) PNG_PRIVATE;
+#endif
+
+#if defined(PNG_WRITE_FILLER_SUPPORTED) || \
+ defined(PNG_READ_STRIP_ALPHA_SUPPORTED)
+PNG_EXTERN void png_do_strip_filler PNGARG((png_row_infop row_info,
+ png_bytep row, png_uint_32 flags)) PNG_PRIVATE;
+#endif
+
+#if defined(PNG_READ_SWAP_SUPPORTED) || defined(PNG_WRITE_SWAP_SUPPORTED)
+PNG_EXTERN void png_do_swap PNGARG((png_row_infop row_info,
+ png_bytep row)) PNG_PRIVATE;
+#endif
+
+#if defined(PNG_READ_PACKSWAP_SUPPORTED) || defined(PNG_WRITE_PACKSWAP_SUPPORTED)
+PNG_EXTERN void png_do_packswap PNGARG((png_row_infop row_info,
+ png_bytep row)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED
+PNG_EXTERN int png_do_rgb_to_gray PNGARG((png_structp png_ptr, png_row_infop
+ row_info, png_bytep row)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED
+PNG_EXTERN void png_do_gray_to_rgb PNGARG((png_row_infop row_info,
+ png_bytep row)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_READ_PACK_SUPPORTED
+PNG_EXTERN void png_do_unpack PNGARG((png_row_infop row_info,
+ png_bytep row)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_READ_SHIFT_SUPPORTED
+PNG_EXTERN void png_do_unshift PNGARG((png_row_infop row_info, png_bytep row,
+ png_color_8p sig_bits)) PNG_PRIVATE;
+#endif
+
+#if defined(PNG_READ_INVERT_SUPPORTED) || defined(PNG_WRITE_INVERT_SUPPORTED)
+PNG_EXTERN void png_do_invert PNGARG((png_row_infop row_info,
+ png_bytep row)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_READ_16_TO_8_SUPPORTED
+PNG_EXTERN void png_do_chop PNGARG((png_row_infop row_info,
+ png_bytep row)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_READ_DITHER_SUPPORTED
+PNG_EXTERN void png_do_dither PNGARG((png_row_infop row_info,
+ png_bytep row, png_bytep palette_lookup,
+ png_bytep dither_lookup)) PNG_PRIVATE;
+
+# ifdef PNG_CORRECT_PALETTE_SUPPORTED
+PNG_EXTERN void png_correct_palette PNGARG((png_structp png_ptr,
+ png_colorp palette, int num_palette)) PNG_PRIVATE;
+# endif
+#endif
+
+#if defined(PNG_READ_BGR_SUPPORTED) || defined(PNG_WRITE_BGR_SUPPORTED)
+PNG_EXTERN void png_do_bgr PNGARG((png_row_infop row_info,
+ png_bytep row)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_WRITE_PACK_SUPPORTED
+PNG_EXTERN void png_do_pack PNGARG((png_row_infop row_info,
+ png_bytep row, png_uint_32 bit_depth)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_WRITE_SHIFT_SUPPORTED
+PNG_EXTERN void png_do_shift PNGARG((png_row_infop row_info, png_bytep row,
+ png_color_8p bit_depth)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_READ_BACKGROUND_SUPPORTED
+#ifdef PNG_READ_GAMMA_SUPPORTED
+PNG_EXTERN void png_do_background PNGARG((png_row_infop row_info, png_bytep row,
+ png_color_16p trans_values, png_color_16p background,
+ png_color_16p background_1,
+ png_bytep gamma_table, png_bytep gamma_from_1, png_bytep gamma_to_1,
+ png_uint_16pp gamma_16, png_uint_16pp gamma_16_from_1,
+ png_uint_16pp gamma_16_to_1, int gamma_shift)) PNG_PRIVATE;
+#else
+PNG_EXTERN void png_do_background PNGARG((png_row_infop row_info, png_bytep row,
+ png_color_16p trans_values, png_color_16p background)) PNG_PRIVATE;
+#endif
+#endif
+
+#ifdef PNG_READ_GAMMA_SUPPORTED
+PNG_EXTERN void png_do_gamma PNGARG((png_row_infop row_info, png_bytep row,
+ png_bytep gamma_table, png_uint_16pp gamma_16_table,
+ int gamma_shift)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_READ_EXPAND_SUPPORTED
+PNG_EXTERN void png_do_expand_palette PNGARG((png_row_infop row_info,
+ png_bytep row, png_colorp palette, png_bytep trans,
+ int num_trans)) PNG_PRIVATE;
+PNG_EXTERN void png_do_expand PNGARG((png_row_infop row_info,
+ png_bytep row, png_color_16p trans_value)) PNG_PRIVATE;
+#endif
+
+/* The following decodes the appropriate chunks, and does error correction,
+ * then calls the appropriate callback for the chunk if it is valid.
+ */
+
+/* Decode the IHDR chunk */
+PNG_EXTERN void png_handle_IHDR PNGARG((png_structp png_ptr, png_infop info_ptr,
+ png_uint_32 length)) PNG_PRIVATE;
+PNG_EXTERN void png_handle_PLTE PNGARG((png_structp png_ptr, png_infop info_ptr,
+ png_uint_32 length));
+PNG_EXTERN void png_handle_IEND PNGARG((png_structp png_ptr, png_infop info_ptr,
+ png_uint_32 length));
+
+#ifdef PNG_READ_bKGD_SUPPORTED
+PNG_EXTERN void png_handle_bKGD PNGARG((png_structp png_ptr, png_infop info_ptr,
+ png_uint_32 length)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_READ_cHRM_SUPPORTED
+PNG_EXTERN void png_handle_cHRM PNGARG((png_structp png_ptr, png_infop info_ptr,
+ png_uint_32 length)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_READ_gAMA_SUPPORTED
+PNG_EXTERN void png_handle_gAMA PNGARG((png_structp png_ptr, png_infop info_ptr,
+ png_uint_32 length)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_READ_hIST_SUPPORTED
+PNG_EXTERN void png_handle_hIST PNGARG((png_structp png_ptr, png_infop info_ptr,
+ png_uint_32 length)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_READ_iCCP_SUPPORTED
+extern void png_handle_iCCP PNGARG((png_structp png_ptr, png_infop info_ptr,
+ png_uint_32 length));
+#endif /* PNG_READ_iCCP_SUPPORTED */
+
+#ifdef PNG_READ_iTXt_SUPPORTED
+PNG_EXTERN void png_handle_iTXt PNGARG((png_structp png_ptr, png_infop info_ptr,
+ png_uint_32 length)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_READ_oFFs_SUPPORTED
+PNG_EXTERN void png_handle_oFFs PNGARG((png_structp png_ptr, png_infop info_ptr,
+ png_uint_32 length)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_READ_pCAL_SUPPORTED
+PNG_EXTERN void png_handle_pCAL PNGARG((png_structp png_ptr, png_infop info_ptr,
+ png_uint_32 length)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_READ_pHYs_SUPPORTED
+PNG_EXTERN void png_handle_pHYs PNGARG((png_structp png_ptr, png_infop info_ptr,
+ png_uint_32 length)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_READ_sBIT_SUPPORTED
+PNG_EXTERN void png_handle_sBIT PNGARG((png_structp png_ptr, png_infop info_ptr,
+ png_uint_32 length)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_READ_sCAL_SUPPORTED
+PNG_EXTERN void png_handle_sCAL PNGARG((png_structp png_ptr, png_infop info_ptr,
+ png_uint_32 length)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_READ_sPLT_SUPPORTED
+extern void png_handle_sPLT PNGARG((png_structp png_ptr, png_infop info_ptr,
+ png_uint_32 length)) PNG_PRIVATE;
+#endif /* PNG_READ_sPLT_SUPPORTED */
+
+#ifdef PNG_READ_sRGB_SUPPORTED
+PNG_EXTERN void png_handle_sRGB PNGARG((png_structp png_ptr, png_infop info_ptr,
+ png_uint_32 length)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_READ_tEXt_SUPPORTED
+PNG_EXTERN void png_handle_tEXt PNGARG((png_structp png_ptr, png_infop info_ptr,
+ png_uint_32 length)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_READ_tIME_SUPPORTED
+PNG_EXTERN void png_handle_tIME PNGARG((png_structp png_ptr, png_infop info_ptr,
+ png_uint_32 length)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_READ_tRNS_SUPPORTED
+PNG_EXTERN void png_handle_tRNS PNGARG((png_structp png_ptr, png_infop info_ptr,
+ png_uint_32 length)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_READ_zTXt_SUPPORTED
+PNG_EXTERN void png_handle_zTXt PNGARG((png_structp png_ptr, png_infop info_ptr,
+ png_uint_32 length)) PNG_PRIVATE;
+#endif
+
+PNG_EXTERN void png_handle_unknown PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_uint_32 length)) PNG_PRIVATE;
+
+PNG_EXTERN void png_check_chunk_name PNGARG((png_structp png_ptr,
+ png_bytep chunk_name)) PNG_PRIVATE;
+
+/* Handle the transformations for reading and writing */
+PNG_EXTERN void png_do_read_transformations
+ PNGARG((png_structp png_ptr)) PNG_PRIVATE;
+PNG_EXTERN void png_do_write_transformations
+ PNGARG((png_structp png_ptr)) PNG_PRIVATE;
+
+PNG_EXTERN void png_init_read_transformations
+ PNGARG((png_structp png_ptr)) PNG_PRIVATE;
+
+#ifdef PNG_PROGRESSIVE_READ_SUPPORTED
+PNG_EXTERN void png_push_read_chunk PNGARG((png_structp png_ptr,
+ png_infop info_ptr)) PNG_PRIVATE;
+PNG_EXTERN void png_push_read_sig PNGARG((png_structp png_ptr,
+ png_infop info_ptr)) PNG_PRIVATE;
+PNG_EXTERN void png_push_check_crc PNGARG((png_structp png_ptr)) PNG_PRIVATE;
+PNG_EXTERN void png_push_crc_skip PNGARG((png_structp png_ptr,
+ png_uint_32 length)) PNG_PRIVATE;
+PNG_EXTERN void png_push_crc_finish PNGARG((png_structp png_ptr)) PNG_PRIVATE;
+PNG_EXTERN void png_push_save_buffer PNGARG((png_structp png_ptr)) PNG_PRIVATE;
+PNG_EXTERN void png_push_restore_buffer PNGARG((png_structp png_ptr,
+ png_bytep buffer, png_size_t buffer_length)) PNG_PRIVATE;
+PNG_EXTERN void png_push_read_IDAT PNGARG((png_structp png_ptr)) PNG_PRIVATE;
+PNG_EXTERN void png_process_IDAT_data PNGARG((png_structp png_ptr,
+ png_bytep buffer, png_size_t buffer_length)) PNG_PRIVATE;
+PNG_EXTERN void png_push_process_row PNGARG((png_structp png_ptr)) PNG_PRIVATE;
+PNG_EXTERN void png_push_handle_unknown PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_uint_32 length)) PNG_PRIVATE;
+PNG_EXTERN void png_push_have_info PNGARG((png_structp png_ptr,
+ png_infop info_ptr)) PNG_PRIVATE;
+PNG_EXTERN void png_push_have_end PNGARG((png_structp png_ptr,
+ png_infop info_ptr)) PNG_PRIVATE;
+PNG_EXTERN void png_push_have_row PNGARG((png_structp png_ptr,
+ png_bytep row)) PNG_PRIVATE;
+PNG_EXTERN void png_push_read_end PNGARG((png_structp png_ptr,
+ png_infop info_ptr)) PNG_PRIVATE;
+PNG_EXTERN void png_process_some_data PNGARG((png_structp png_ptr,
+ png_infop info_ptr)) PNG_PRIVATE;
+PNG_EXTERN void png_read_push_finish_row
+ PNGARG((png_structp png_ptr)) PNG_PRIVATE;
+#ifdef PNG_READ_tEXt_SUPPORTED
+PNG_EXTERN void png_push_handle_tEXt PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_uint_32 length)) PNG_PRIVATE;
+PNG_EXTERN void png_push_read_tEXt PNGARG((png_structp png_ptr,
+ png_infop info_ptr)) PNG_PRIVATE;
+#endif
+#ifdef PNG_READ_zTXt_SUPPORTED
+PNG_EXTERN void png_push_handle_zTXt PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_uint_32 length)) PNG_PRIVATE;
+PNG_EXTERN void png_push_read_zTXt PNGARG((png_structp png_ptr,
+ png_infop info_ptr)) PNG_PRIVATE;
+#endif
+#ifdef PNG_READ_iTXt_SUPPORTED
+PNG_EXTERN void png_push_handle_iTXt PNGARG((png_structp png_ptr,
+ png_infop info_ptr, png_uint_32 length)) PNG_PRIVATE;
+PNG_EXTERN void png_push_read_iTXt PNGARG((png_structp png_ptr,
+ png_infop info_ptr)) PNG_PRIVATE;
+#endif
+
+#endif /* PNG_PROGRESSIVE_READ_SUPPORTED */
+
+#ifdef PNG_MNG_FEATURES_SUPPORTED
+PNG_EXTERN void png_do_read_intrapixel PNGARG((png_row_infop row_info,
+ png_bytep row)) PNG_PRIVATE;
+PNG_EXTERN void png_do_write_intrapixel PNGARG((png_row_infop row_info,
+ png_bytep row)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_ASSEMBLER_CODE_SUPPORTED
+#ifdef PNG_MMX_CODE_SUPPORTED
+/* png.c */ /* PRIVATE */
+PNG_EXTERN void png_init_mmx_flags PNGARG((png_structp png_ptr)) PNG_PRIVATE;
+#endif
+#endif
+
+
+/* The following six functions will be exported in libpng-1.4.0. */
+#if defined(PNG_INCH_CONVERSIONS) && defined(PNG_FLOATING_POINT_SUPPORTED)
+PNG_EXTERN png_uint_32 png_get_pixels_per_inch PNGARG((png_structp png_ptr,
+png_infop info_ptr));
+
+PNG_EXTERN png_uint_32 png_get_x_pixels_per_inch PNGARG((png_structp png_ptr,
+png_infop info_ptr));
+
+PNG_EXTERN png_uint_32 png_get_y_pixels_per_inch PNGARG((png_structp png_ptr,
+png_infop info_ptr));
+
+PNG_EXTERN float png_get_x_offset_inches PNGARG((png_structp png_ptr,
+png_infop info_ptr));
+
+PNG_EXTERN float png_get_y_offset_inches PNGARG((png_structp png_ptr,
+png_infop info_ptr));
+
+#ifdef PNG_pHYs_SUPPORTED
+PNG_EXTERN png_uint_32 png_get_pHYs_dpi PNGARG((png_structp png_ptr,
+png_infop info_ptr, png_uint_32 *res_x, png_uint_32 *res_y, int *unit_type));
+#endif /* PNG_pHYs_SUPPORTED */
+#endif /* PNG_INCH_CONVERSIONS && PNG_FLOATING_POINT_SUPPORTED */
+
+/* Read the chunk header (length + type name) */
+PNG_EXTERN png_uint_32 png_read_chunk_header
+ PNGARG((png_structp png_ptr)) PNG_PRIVATE;
+
+/* Added at libpng version 1.2.34 */
+#ifdef PNG_cHRM_SUPPORTED
+PNG_EXTERN int png_check_cHRM_fixed PNGARG((png_structp png_ptr,
+ png_fixed_point int_white_x, png_fixed_point int_white_y,
+ png_fixed_point int_red_x, png_fixed_point int_red_y, png_fixed_point
+ int_green_x, png_fixed_point int_green_y, png_fixed_point int_blue_x,
+ png_fixed_point int_blue_y)) PNG_PRIVATE;
+#endif
+
+#ifdef PNG_cHRM_SUPPORTED
+#ifdef PNG_CHECK_cHRM_SUPPORTED
+/* Added at libpng version 1.2.34 */
+PNG_EXTERN void png_64bit_product PNGARG((long v1, long v2,
+ unsigned long *hi_product, unsigned long *lo_product)) PNG_PRIVATE;
+#endif
+#endif
+
+/* Added at libpng version 1.2.41 */
+PNG_EXTERN void png_check_IHDR PNGARG((png_structp png_ptr,
+ png_uint_32 width, png_uint_32 height, int bit_depth,
+ int color_type, int interlace_type, int compression_type,
+ int filter_type)) PNG_PRIVATE;
+
+/* Added at libpng version 1.2.41 */
+PNG_EXTERN png_voidp png_calloc PNGARG((png_structp png_ptr,
+ png_uint_32 size));
+
+/* Maintainer: Put new private prototypes here ^ and in libpngpf.3 */
+
+#endif /* PNG_INTERNAL */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PNG_VERSION_INFO_ONLY */
+/* Do not put anything past this line */
+#endif /* PNG_H */
diff --git a/trunk/src/third_party/libpng/pngconf.h b/trunk/src/third_party/libpng/pngconf.h
new file mode 100644
index 0000000..defc16d
--- /dev/null
+++ b/trunk/src/third_party/libpng/pngconf.h
@@ -0,0 +1,1665 @@
+
+/* pngconf.h - machine configurable file for libpng
+ *
+ * libpng version 1.2.44 - June 26, 2010
+ * Copyright (c) 1998-2010 Glenn Randers-Pehrson
+ * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger)
+ * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.)
+ *
+ * This code is released under the libpng license.
+ * For conditions of distribution and use, see the disclaimer
+ * and license in png.h
+ */
+
+/* Any machine specific code is near the front of this file, so if you
+ * are configuring libpng for a machine, you may want to read the section
+ * starting here down to where it starts to typedef png_color, png_text,
+ * and png_info.
+ */
+
+#ifndef PNGCONF_H
+#define PNGCONF_H
+
+#define PNG_1_2_X
+
+/*
+ * PNG_USER_CONFIG has to be defined on the compiler command line. This
+ * includes the resource compiler for Windows DLL configurations.
+ */
+#ifdef PNG_USER_CONFIG
+# ifndef PNG_USER_PRIVATEBUILD
+# define PNG_USER_PRIVATEBUILD
+# endif
+#include "pngusr.h"
+#endif
+
+/* PNG_CONFIGURE_LIBPNG is set by the "configure" script. */
+#ifdef PNG_CONFIGURE_LIBPNG
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#endif
+
+/*
+ * Added at libpng-1.2.8
+ *
+ * If you create a private DLL you need to define in "pngusr.h" the followings:
+ * #define PNG_USER_PRIVATEBUILD <Describes by whom and why this version of
+ * the DLL was built>
+ * e.g. #define PNG_USER_PRIVATEBUILD "Build by MyCompany for xyz reasons."
+ * #define PNG_USER_DLLFNAME_POSTFIX <two-letter postfix that serve to
+ * distinguish your DLL from those of the official release. These
+ * correspond to the trailing letters that come after the version
+ * number and must match your private DLL name>
+ * e.g. // private DLL "libpng13gx.dll"
+ * #define PNG_USER_DLLFNAME_POSTFIX "gx"
+ *
+ * The following macros are also at your disposal if you want to complete the
+ * DLL VERSIONINFO structure.
+ * - PNG_USER_VERSIONINFO_COMMENTS
+ * - PNG_USER_VERSIONINFO_COMPANYNAME
+ * - PNG_USER_VERSIONINFO_LEGALTRADEMARKS
+ */
+
+#ifdef __STDC__
+#ifdef SPECIALBUILD
+# pragma message("PNG_LIBPNG_SPECIALBUILD (and deprecated SPECIALBUILD)\
+ are now LIBPNG reserved macros. Use PNG_USER_PRIVATEBUILD instead.")
+#endif
+
+#ifdef PRIVATEBUILD
+# pragma message("PRIVATEBUILD is deprecated.\
+ Use PNG_USER_PRIVATEBUILD instead.")
+# define PNG_USER_PRIVATEBUILD PRIVATEBUILD
+#endif
+#endif /* __STDC__ */
+
+#ifndef PNG_VERSION_INFO_ONLY
+
+/* End of material added to libpng-1.2.8 */
+
+/* Added at libpng-1.2.19, removed at libpng-1.2.20 because it caused trouble
+ Restored at libpng-1.2.21 */
+#if !defined(PNG_NO_WARN_UNINITIALIZED_ROW) && \
+ !defined(PNG_WARN_UNINITIALIZED_ROW)
+# define PNG_WARN_UNINITIALIZED_ROW 1
+#endif
+/* End of material added at libpng-1.2.19/1.2.21 */
+
+/* This is the size of the compression buffer, and thus the size of
+ * an IDAT chunk. Make this whatever size you feel is best for your
+ * machine. One of these will be allocated per png_struct. When this
+ * is full, it writes the data to the disk, and does some other
+ * calculations. Making this an extremely small size will slow
+ * the library down, but you may want to experiment to determine
+ * where it becomes significant, if you are concerned with memory
+ * usage. Note that zlib allocates at least 32Kb also. For readers,
+ * this describes the size of the buffer available to read the data in.
+ * Unless this gets smaller than the size of a row (compressed),
+ * it should not make much difference how big this is.
+ */
+
+#ifndef PNG_ZBUF_SIZE
+# define PNG_ZBUF_SIZE 8192
+#endif
+
+/* Enable if you want a write-only libpng */
+
+#ifndef PNG_NO_READ_SUPPORTED
+# define PNG_READ_SUPPORTED
+#endif
+
+/* Enable if you want a read-only libpng */
+
+#ifndef PNG_NO_WRITE_SUPPORTED
+# define PNG_WRITE_SUPPORTED
+#endif
+
+/* Enabled in 1.2.41. */
+#ifdef PNG_ALLOW_BENIGN_ERRORS
+# define png_benign_error png_warning
+# define png_chunk_benign_error png_chunk_warning
+#else
+# ifndef PNG_BENIGN_ERRORS_SUPPORTED
+# define png_benign_error png_error
+# define png_chunk_benign_error png_chunk_error
+# endif
+#endif
+
+/* Added in libpng-1.2.41 */
+#if !defined(PNG_NO_WARNINGS) && !defined(PNG_WARNINGS_SUPPORTED)
+# define PNG_WARNINGS_SUPPORTED
+#endif
+
+#if !defined(PNG_NO_ERROR_TEXT) && !defined(PNG_ERROR_TEXT_SUPPORTED)
+# define PNG_ERROR_TEXT_SUPPORTED
+#endif
+
+#if !defined(PNG_NO_CHECK_cHRM) && !defined(PNG_CHECK_cHRM_SUPPORTED)
+# define PNG_CHECK_cHRM_SUPPORTED
+#endif
+
+/* Enabled by default in 1.2.0. You can disable this if you don't need to
+ * support PNGs that are embedded in MNG datastreams
+ */
+#if !defined(PNG_1_0_X) && !defined(PNG_NO_MNG_FEATURES)
+# ifndef PNG_MNG_FEATURES_SUPPORTED
+# define PNG_MNG_FEATURES_SUPPORTED
+# endif
+#endif
+
+#ifndef PNG_NO_FLOATING_POINT_SUPPORTED
+# ifndef PNG_FLOATING_POINT_SUPPORTED
+# define PNG_FLOATING_POINT_SUPPORTED
+# endif
+#endif
+
+/* If you are running on a machine where you cannot allocate more
+ * than 64K of memory at once, uncomment this. While libpng will not
+ * normally need that much memory in a chunk (unless you load up a very
+ * large file), zlib needs to know how big of a chunk it can use, and
+ * libpng thus makes sure to check any memory allocation to verify it
+ * will fit into memory.
+#define PNG_MAX_MALLOC_64K
+ */
+#if defined(MAXSEG_64K) && !defined(PNG_MAX_MALLOC_64K)
+# define PNG_MAX_MALLOC_64K
+#endif
+
+/* Special munging to support doing things the 'cygwin' way:
+ * 'Normal' png-on-win32 defines/defaults:
+ * PNG_BUILD_DLL -- building dll
+ * PNG_USE_DLL -- building an application, linking to dll
+ * (no define) -- building static library, or building an
+ * application and linking to the static lib
+ * 'Cygwin' defines/defaults:
+ * PNG_BUILD_DLL -- (ignored) building the dll
+ * (no define) -- (ignored) building an application, linking to the dll
+ * PNG_STATIC -- (ignored) building the static lib, or building an
+ * application that links to the static lib.
+ * ALL_STATIC -- (ignored) building various static libs, or building an
+ * application that links to the static libs.
+ * Thus,
+ * a cygwin user should define either PNG_BUILD_DLL or PNG_STATIC, and
+ * this bit of #ifdefs will define the 'correct' config variables based on
+ * that. If a cygwin user *wants* to define 'PNG_USE_DLL' that's okay, but
+ * unnecessary.
+ *
+ * Also, the precedence order is:
+ * ALL_STATIC (since we can't #undef something outside our namespace)
+ * PNG_BUILD_DLL
+ * PNG_STATIC
+ * (nothing) == PNG_USE_DLL
+ *
+ * CYGWIN (2002-01-20): The preceding is now obsolete. With the advent
+ * of auto-import in binutils, we no longer need to worry about
+ * __declspec(dllexport) / __declspec(dllimport) and friends. Therefore,
+ * we don't need to worry about PNG_STATIC or ALL_STATIC when it comes
+ * to __declspec() stuff. However, we DO need to worry about
+ * PNG_BUILD_DLL and PNG_STATIC because those change some defaults
+ * such as CONSOLE_IO and whether GLOBAL_ARRAYS are allowed.
+ */
+#ifdef __CYGWIN__
+# ifdef ALL_STATIC
+# ifdef PNG_BUILD_DLL
+# undef PNG_BUILD_DLL
+# endif
+# ifdef PNG_USE_DLL
+# undef PNG_USE_DLL
+# endif
+# ifdef PNG_DLL
+# undef PNG_DLL
+# endif
+# ifndef PNG_STATIC
+# define PNG_STATIC
+# endif
+# else
+# ifdef PNG_BUILD_DLL
+# ifdef PNG_STATIC
+# undef PNG_STATIC
+# endif
+# ifdef PNG_USE_DLL
+# undef PNG_USE_DLL
+# endif
+# ifndef PNG_DLL
+# define PNG_DLL
+# endif
+# else
+# ifdef PNG_STATIC
+# ifdef PNG_USE_DLL
+# undef PNG_USE_DLL
+# endif
+# ifdef PNG_DLL
+# undef PNG_DLL
+# endif
+# else
+# ifndef PNG_USE_DLL
+# define PNG_USE_DLL
+# endif
+# ifndef PNG_DLL
+# define PNG_DLL
+# endif
+# endif
+# endif
+# endif
+#endif
+
+/* This protects us against compilers that run on a windowing system
+ * and thus don't have or would rather us not use the stdio types:
+ * stdin, stdout, and stderr. The only one currently used is stderr
+ * in png_error() and png_warning(). #defining PNG_NO_CONSOLE_IO will
+ * prevent these from being compiled and used. #defining PNG_NO_STDIO
+ * will also prevent these, plus will prevent the entire set of stdio
+ * macros and functions (FILE *, printf, etc.) from being compiled and used,
+ * unless (PNG_DEBUG > 0) has been #defined.
+ *
+ * #define PNG_NO_CONSOLE_IO
+ * #define PNG_NO_STDIO
+ */
+
+#if !defined(PNG_NO_STDIO) && !defined(PNG_STDIO_SUPPORTED)
+# define PNG_STDIO_SUPPORTED
+#endif
+
+#ifdef _WIN32_WCE
+# include <windows.h>
+ /* Console I/O functions are not supported on WindowsCE */
+# define PNG_NO_CONSOLE_IO
+ /* abort() may not be supported on some/all Windows CE platforms */
+# define PNG_ABORT() exit(-1)
+# ifdef PNG_DEBUG
+# undef PNG_DEBUG
+# endif
+#endif
+
+#ifdef PNG_BUILD_DLL
+# ifndef PNG_CONSOLE_IO_SUPPORTED
+# ifndef PNG_NO_CONSOLE_IO
+# define PNG_NO_CONSOLE_IO
+# endif
+# endif
+#endif
+
+# ifdef PNG_NO_STDIO
+# ifndef PNG_NO_CONSOLE_IO
+# define PNG_NO_CONSOLE_IO
+# endif
+# ifdef PNG_DEBUG
+# if (PNG_DEBUG > 0)
+# include <stdio.h>
+# endif
+# endif
+# else
+# ifndef _WIN32_WCE
+/* "stdio.h" functions are not supported on WindowsCE */
+# include <stdio.h>
+# endif
+# endif
+
+#if !(defined PNG_NO_CONSOLE_IO) && !defined(PNG_CONSOLE_IO_SUPPORTED)
+# define PNG_CONSOLE_IO_SUPPORTED
+#endif
+
+/* This macro protects us against machines that don't have function
+ * prototypes (ie K&R style headers). If your compiler does not handle
+ * function prototypes, define this macro and use the included ansi2knr.
+ * I've always been able to use _NO_PROTO as the indicator, but you may
+ * need to drag the empty declaration out in front of here, or change the
+ * ifdef to suit your own needs.
+ */
+#ifndef PNGARG
+
+#ifdef OF /* zlib prototype munger */
+# define PNGARG(arglist) OF(arglist)
+#else
+
+#ifdef _NO_PROTO
+# define PNGARG(arglist) ()
+# ifndef PNG_TYPECAST_NULL
+# define PNG_TYPECAST_NULL
+# endif
+#else
+# define PNGARG(arglist) arglist
+#endif /* _NO_PROTO */
+
+
+#endif /* OF */
+
+#endif /* PNGARG */
+
+/* Try to determine if we are compiling on a Mac. Note that testing for
+ * just __MWERKS__ is not good enough, because the Codewarrior is now used
+ * on non-Mac platforms.
+ */
+#ifndef MACOS
+# if (defined(__MWERKS__) && defined(macintosh)) || defined(applec) || \
+ defined(THINK_C) || defined(__SC__) || defined(TARGET_OS_MAC)
+# define MACOS
+# endif
+#endif
+
+/* enough people need this for various reasons to include it here */
+#if !defined(MACOS) && !defined(RISCOS) && !defined(_WIN32_WCE)
+# include <sys/types.h>
+#endif
+
+#if !defined(PNG_SETJMP_NOT_SUPPORTED) && !defined(PNG_NO_SETJMP_SUPPORTED)
+# define PNG_SETJMP_SUPPORTED
+#endif
+
+#ifdef PNG_SETJMP_SUPPORTED
+/* This is an attempt to force a single setjmp behaviour on Linux. If
+ * the X config stuff didn't define _BSD_SOURCE we wouldn't need this.
+ *
+ * You can bypass this test if you know that your application uses exactly
+ * the same setjmp.h that was included when libpng was built. Only define
+ * PNG_SKIP_SETJMP_CHECK while building your application, prior to the
+ * application's '#include "png.h"'. Don't define PNG_SKIP_SETJMP_CHECK
+ * while building a separate libpng library for general use.
+ */
+
+# ifndef PNG_SKIP_SETJMP_CHECK
+# ifdef __linux__
+# ifdef _BSD_SOURCE
+# define PNG_SAVE_BSD_SOURCE
+# undef _BSD_SOURCE
+# endif
+# ifdef _SETJMP_H
+ /* If you encounter a compiler error here, see the explanation
+ * near the end of INSTALL.
+ */
+ __pngconf.h__ in libpng already includes setjmp.h;
+ __dont__ include it again.;
+# endif
+# endif /* __linux__ */
+# endif /* PNG_SKIP_SETJMP_CHECK */
+
+ /* include setjmp.h for error handling */
+# include <setjmp.h>
+
+# ifdef __linux__
+# ifdef PNG_SAVE_BSD_SOURCE
+# ifndef _BSD_SOURCE
+# define _BSD_SOURCE
+# endif
+# undef PNG_SAVE_BSD_SOURCE
+# endif
+# endif /* __linux__ */
+#endif /* PNG_SETJMP_SUPPORTED */
+
+#ifdef BSD
+# include <strings.h>
+#else
+# include <string.h>
+#endif
+
+/* Other defines for things like memory and the like can go here. */
+#ifdef PNG_INTERNAL
+
+#include <stdlib.h>
+
+/* The functions exported by PNG_EXTERN are PNG_INTERNAL functions, which
+ * aren't usually used outside the library (as far as I know), so it is
+ * debatable if they should be exported at all. In the future, when it is
+ * possible to have run-time registry of chunk-handling functions, some of
+ * these will be made available again.
+#define PNG_EXTERN extern
+ */
+#define PNG_EXTERN
+
+/* Other defines specific to compilers can go here. Try to keep
+ * them inside an appropriate ifdef/endif pair for portability.
+ */
+
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+# ifdef MACOS
+ /* We need to check that <math.h> hasn't already been included earlier
+ * as it seems it doesn't agree with <fp.h>, yet we should really use
+ * <fp.h> if possible.
+ */
+# if !defined(__MATH_H__) && !defined(__MATH_H) && !defined(__cmath__)
+# include <fp.h>
+# endif
+# else
+# include <math.h>
+# endif
+# if defined(_AMIGA) && defined(__SASC) && defined(_M68881)
+ /* Amiga SAS/C: We must include builtin FPU functions when compiling using
+ * MATH=68881
+ */
+# include <m68881.h>
+# endif
+#endif
+
+/* Codewarrior on NT has linking problems without this. */
+#if (defined(__MWERKS__) && defined(WIN32)) || defined(__STDC__)
+# define PNG_ALWAYS_EXTERN
+#endif
+
+/* This provides the non-ANSI (far) memory allocation routines. */
+#if defined(__TURBOC__) && defined(__MSDOS__)
+# include <mem.h>
+# include <alloc.h>
+#endif
+
+/* I have no idea why is this necessary... */
+#if defined(_MSC_VER) && (defined(WIN32) || defined(_Windows) || \
+ defined(_WINDOWS) || defined(_WIN32) || defined(__WIN32__))
+# include <malloc.h>
+#endif
+
+/* This controls how fine the dithering gets. As this allocates
+ * a largish chunk of memory (32K), those who are not as concerned
+ * with dithering quality can decrease some or all of these.
+ */
+#ifndef PNG_DITHER_RED_BITS
+# define PNG_DITHER_RED_BITS 5
+#endif
+#ifndef PNG_DITHER_GREEN_BITS
+# define PNG_DITHER_GREEN_BITS 5
+#endif
+#ifndef PNG_DITHER_BLUE_BITS
+# define PNG_DITHER_BLUE_BITS 5
+#endif
+
+/* This controls how fine the gamma correction becomes when you
+ * are only interested in 8 bits anyway. Increasing this value
+ * results in more memory being used, and more pow() functions
+ * being called to fill in the gamma tables. Don't set this value
+ * less then 8, and even that may not work (I haven't tested it).
+ */
+
+#ifndef PNG_MAX_GAMMA_8
+# define PNG_MAX_GAMMA_8 11
+#endif
+
+/* This controls how much a difference in gamma we can tolerate before
+ * we actually start doing gamma conversion.
+ */
+#ifndef PNG_GAMMA_THRESHOLD
+# define PNG_GAMMA_THRESHOLD 0.05
+#endif
+
+#endif /* PNG_INTERNAL */
+
+/* The following uses const char * instead of char * for error
+ * and warning message functions, so some compilers won't complain.
+ * If you do not want to use const, define PNG_NO_CONST here.
+ */
+
+#ifndef PNG_NO_CONST
+# define PNG_CONST const
+#else
+# define PNG_CONST
+#endif
+
+/* The following defines give you the ability to remove code from the
+ * library that you will not be using. I wish I could figure out how to
+ * automate this, but I can't do that without making it seriously hard
+ * on the users. So if you are not using an ability, change the #define
+ * to and #undef, and that part of the library will not be compiled. If
+ * your linker can't find a function, you may want to make sure the
+ * ability is defined here. Some of these depend upon some others being
+ * defined. I haven't figured out all the interactions here, so you may
+ * have to experiment awhile to get everything to compile. If you are
+ * creating or using a shared library, you probably shouldn't touch this,
+ * as it will affect the size of the structures, and this will cause bad
+ * things to happen if the library and/or application ever change.
+ */
+
+/* Any features you will not be using can be undef'ed here */
+
+/* GR-P, 0.96a: Set "*TRANSFORMS_SUPPORTED as default but allow user
+ * to turn it off with "*TRANSFORMS_NOT_SUPPORTED" or *PNG_NO_*_TRANSFORMS
+ * on the compile line, then pick and choose which ones to define without
+ * having to edit this file. It is safe to use the *TRANSFORMS_NOT_SUPPORTED
+ * if you only want to have a png-compliant reader/writer but don't need
+ * any of the extra transformations. This saves about 80 kbytes in a
+ * typical installation of the library. (PNG_NO_* form added in version
+ * 1.0.1c, for consistency)
+ */
+
+/* The size of the png_text structure changed in libpng-1.0.6 when
+ * iTXt support was added. iTXt support was turned off by default through
+ * libpng-1.2.x, to support old apps that malloc the png_text structure
+ * instead of calling png_set_text() and letting libpng malloc it. It
+ * will be turned on by default in libpng-1.4.0.
+ */
+
+#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
+# ifndef PNG_NO_iTXt_SUPPORTED
+# define PNG_NO_iTXt_SUPPORTED
+# endif
+# ifndef PNG_NO_READ_iTXt
+# define PNG_NO_READ_iTXt
+# endif
+# ifndef PNG_NO_WRITE_iTXt
+# define PNG_NO_WRITE_iTXt
+# endif
+#endif
+
+#if !defined(PNG_NO_iTXt_SUPPORTED)
+# if !defined(PNG_READ_iTXt_SUPPORTED) && !defined(PNG_NO_READ_iTXt)
+# define PNG_READ_iTXt
+# endif
+# if !defined(PNG_WRITE_iTXt_SUPPORTED) && !defined(PNG_NO_WRITE_iTXt)
+# define PNG_WRITE_iTXt
+# endif
+#endif
+
+/* The following support, added after version 1.0.0, can be turned off here en
+ * masse by defining PNG_LEGACY_SUPPORTED in case you need binary compatibility
+ * with old applications that require the length of png_struct and png_info
+ * to remain unchanged.
+ */
+
+#ifdef PNG_LEGACY_SUPPORTED
+# define PNG_NO_FREE_ME
+# define PNG_NO_READ_UNKNOWN_CHUNKS
+# define PNG_NO_WRITE_UNKNOWN_CHUNKS
+# define PNG_NO_HANDLE_AS_UNKNOWN
+# define PNG_NO_READ_USER_CHUNKS
+# define PNG_NO_READ_iCCP
+# define PNG_NO_WRITE_iCCP
+# define PNG_NO_READ_iTXt
+# define PNG_NO_WRITE_iTXt
+# define PNG_NO_READ_sCAL
+# define PNG_NO_WRITE_sCAL
+# define PNG_NO_READ_sPLT
+# define PNG_NO_WRITE_sPLT
+# define PNG_NO_INFO_IMAGE
+# define PNG_NO_READ_RGB_TO_GRAY
+# define PNG_NO_READ_USER_TRANSFORM
+# define PNG_NO_WRITE_USER_TRANSFORM
+# define PNG_NO_USER_MEM
+# define PNG_NO_READ_EMPTY_PLTE
+# define PNG_NO_MNG_FEATURES
+# define PNG_NO_FIXED_POINT_SUPPORTED
+#endif
+
+/* Ignore attempt to turn off both floating and fixed point support */
+#if !defined(PNG_FLOATING_POINT_SUPPORTED) || \
+ !defined(PNG_NO_FIXED_POINT_SUPPORTED)
+# define PNG_FIXED_POINT_SUPPORTED
+#endif
+
+#ifndef PNG_NO_FREE_ME
+# define PNG_FREE_ME_SUPPORTED
+#endif
+
+#ifdef PNG_READ_SUPPORTED
+
+#if !defined(PNG_READ_TRANSFORMS_NOT_SUPPORTED) && \
+ !defined(PNG_NO_READ_TRANSFORMS)
+# define PNG_READ_TRANSFORMS_SUPPORTED
+#endif
+
+#ifdef PNG_READ_TRANSFORMS_SUPPORTED
+# ifndef PNG_NO_READ_EXPAND
+# define PNG_READ_EXPAND_SUPPORTED
+# endif
+# ifndef PNG_NO_READ_SHIFT
+# define PNG_READ_SHIFT_SUPPORTED
+# endif
+# ifndef PNG_NO_READ_PACK
+# define PNG_READ_PACK_SUPPORTED
+# endif
+# ifndef PNG_NO_READ_BGR
+# define PNG_READ_BGR_SUPPORTED
+# endif
+# ifndef PNG_NO_READ_SWAP
+# define PNG_READ_SWAP_SUPPORTED
+# endif
+# ifndef PNG_NO_READ_PACKSWAP
+# define PNG_READ_PACKSWAP_SUPPORTED
+# endif
+# ifndef PNG_NO_READ_INVERT
+# define PNG_READ_INVERT_SUPPORTED
+# endif
+# ifndef PNG_NO_READ_DITHER
+# define PNG_READ_DITHER_SUPPORTED
+# endif
+# ifndef PNG_NO_READ_BACKGROUND
+# define PNG_READ_BACKGROUND_SUPPORTED
+# endif
+# ifndef PNG_NO_READ_16_TO_8
+# define PNG_READ_16_TO_8_SUPPORTED
+# endif
+# ifndef PNG_NO_READ_FILLER
+# define PNG_READ_FILLER_SUPPORTED
+# endif
+# ifndef PNG_NO_READ_GAMMA
+# define PNG_READ_GAMMA_SUPPORTED
+# endif
+# ifndef PNG_NO_READ_GRAY_TO_RGB
+# define PNG_READ_GRAY_TO_RGB_SUPPORTED
+# endif
+# ifndef PNG_NO_READ_SWAP_ALPHA
+# define PNG_READ_SWAP_ALPHA_SUPPORTED
+# endif
+# ifndef PNG_NO_READ_INVERT_ALPHA
+# define PNG_READ_INVERT_ALPHA_SUPPORTED
+# endif
+# ifndef PNG_NO_READ_STRIP_ALPHA
+# define PNG_READ_STRIP_ALPHA_SUPPORTED
+# endif
+# ifndef PNG_NO_READ_USER_TRANSFORM
+# define PNG_READ_USER_TRANSFORM_SUPPORTED
+# endif
+# ifndef PNG_NO_READ_RGB_TO_GRAY
+# define PNG_READ_RGB_TO_GRAY_SUPPORTED
+# endif
+#endif /* PNG_READ_TRANSFORMS_SUPPORTED */
+
+/* PNG_PROGRESSIVE_READ_NOT_SUPPORTED is deprecated. */
+#if !defined(PNG_NO_PROGRESSIVE_READ) && \
+ !defined(PNG_PROGRESSIVE_READ_NOT_SUPPORTED) /* if you don't do progressive */
+# define PNG_PROGRESSIVE_READ_SUPPORTED /* reading. This is not talking */
+#endif /* about interlacing capability! You'll */
+ /* still have interlacing unless you change the following define: */
+#define PNG_READ_INTERLACING_SUPPORTED /* required for PNG-compliant decoders */
+
+/* PNG_NO_SEQUENTIAL_READ_SUPPORTED is deprecated. */
+#if !defined(PNG_NO_SEQUENTIAL_READ) && \
+ !defined(PNG_SEQUENTIAL_READ_SUPPORTED) && \
+ !defined(PNG_NO_SEQUENTIAL_READ_SUPPORTED)
+# define PNG_SEQUENTIAL_READ_SUPPORTED
+#endif
+
+#define PNG_READ_INTERLACING_SUPPORTED /* required in PNG-compliant decoders */
+
+#ifndef PNG_NO_READ_COMPOSITE_NODIV
+# ifndef PNG_NO_READ_COMPOSITED_NODIV /* libpng-1.0.x misspelling */
+# define PNG_READ_COMPOSITE_NODIV_SUPPORTED /* well tested on Intel, SGI */
+# endif
+#endif
+
+#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
+/* Deprecated, will be removed from version 2.0.0.
+ Use PNG_MNG_FEATURES_SUPPORTED instead. */
+#ifndef PNG_NO_READ_EMPTY_PLTE
+# define PNG_READ_EMPTY_PLTE_SUPPORTED
+#endif
+#endif
+
+#endif /* PNG_READ_SUPPORTED */
+
+#ifdef PNG_WRITE_SUPPORTED
+
+# if !defined(PNG_WRITE_TRANSFORMS_NOT_SUPPORTED) && \
+ !defined(PNG_NO_WRITE_TRANSFORMS)
+# define PNG_WRITE_TRANSFORMS_SUPPORTED
+#endif
+
+#ifdef PNG_WRITE_TRANSFORMS_SUPPORTED
+# ifndef PNG_NO_WRITE_SHIFT
+# define PNG_WRITE_SHIFT_SUPPORTED
+# endif
+# ifndef PNG_NO_WRITE_PACK
+# define PNG_WRITE_PACK_SUPPORTED
+# endif
+# ifndef PNG_NO_WRITE_BGR
+# define PNG_WRITE_BGR_SUPPORTED
+# endif
+# ifndef PNG_NO_WRITE_SWAP
+# define PNG_WRITE_SWAP_SUPPORTED
+# endif
+# ifndef PNG_NO_WRITE_PACKSWAP
+# define PNG_WRITE_PACKSWAP_SUPPORTED
+# endif
+# ifndef PNG_NO_WRITE_INVERT
+# define PNG_WRITE_INVERT_SUPPORTED
+# endif
+# ifndef PNG_NO_WRITE_FILLER
+# define PNG_WRITE_FILLER_SUPPORTED /* same as WRITE_STRIP_ALPHA */
+# endif
+# ifndef PNG_NO_WRITE_SWAP_ALPHA
+# define PNG_WRITE_SWAP_ALPHA_SUPPORTED
+# endif
+#ifndef PNG_1_0_X
+# ifndef PNG_NO_WRITE_INVERT_ALPHA
+# define PNG_WRITE_INVERT_ALPHA_SUPPORTED
+# endif
+#endif
+# ifndef PNG_NO_WRITE_USER_TRANSFORM
+# define PNG_WRITE_USER_TRANSFORM_SUPPORTED
+# endif
+#endif /* PNG_WRITE_TRANSFORMS_SUPPORTED */
+
+#if !defined(PNG_NO_WRITE_INTERLACING_SUPPORTED) && \
+ !defined(PNG_WRITE_INTERLACING_SUPPORTED)
+#define PNG_WRITE_INTERLACING_SUPPORTED /* not required for PNG-compliant
+ encoders, but can cause trouble
+ if left undefined */
+#endif
+
+#if !defined(PNG_NO_WRITE_WEIGHTED_FILTER) && \
+ !defined(PNG_WRITE_WEIGHTED_FILTER) && \
+ defined(PNG_FLOATING_POINT_SUPPORTED)
+# define PNG_WRITE_WEIGHTED_FILTER_SUPPORTED
+#endif
+
+#ifndef PNG_NO_WRITE_FLUSH
+# define PNG_WRITE_FLUSH_SUPPORTED
+#endif
+
+#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
+/* Deprecated, see PNG_MNG_FEATURES_SUPPORTED, above */
+#ifndef PNG_NO_WRITE_EMPTY_PLTE
+# define PNG_WRITE_EMPTY_PLTE_SUPPORTED
+#endif
+#endif
+
+#endif /* PNG_WRITE_SUPPORTED */
+
+#ifndef PNG_1_0_X
+# ifndef PNG_NO_ERROR_NUMBERS
+# define PNG_ERROR_NUMBERS_SUPPORTED
+# endif
+#endif /* PNG_1_0_X */
+
+#if defined(PNG_READ_USER_TRANSFORM_SUPPORTED) || \
+ defined(PNG_WRITE_USER_TRANSFORM_SUPPORTED)
+# ifndef PNG_NO_USER_TRANSFORM_PTR
+# define PNG_USER_TRANSFORM_PTR_SUPPORTED
+# endif
+#endif
+
+#ifndef PNG_NO_STDIO
+# define PNG_TIME_RFC1123_SUPPORTED
+#endif
+
+/* This adds extra functions in pngget.c for accessing data from the
+ * info pointer (added in version 0.99)
+ * png_get_image_width()
+ * png_get_image_height()
+ * png_get_bit_depth()
+ * png_get_color_type()
+ * png_get_compression_type()
+ * png_get_filter_type()
+ * png_get_interlace_type()
+ * png_get_pixel_aspect_ratio()
+ * png_get_pixels_per_meter()
+ * png_get_x_offset_pixels()
+ * png_get_y_offset_pixels()
+ * png_get_x_offset_microns()
+ * png_get_y_offset_microns()
+ */
+#if !defined(PNG_NO_EASY_ACCESS) && !defined(PNG_EASY_ACCESS_SUPPORTED)
+# define PNG_EASY_ACCESS_SUPPORTED
+#endif
+
+/* PNG_ASSEMBLER_CODE was enabled by default in version 1.2.0
+ * and removed from version 1.2.20. The following will be removed
+ * from libpng-1.4.0
+*/
+
+#if defined(PNG_READ_SUPPORTED) && !defined(PNG_NO_OPTIMIZED_CODE)
+# ifndef PNG_OPTIMIZED_CODE_SUPPORTED
+# define PNG_OPTIMIZED_CODE_SUPPORTED
+# endif
+#endif
+
+#if defined(PNG_READ_SUPPORTED) && !defined(PNG_NO_ASSEMBLER_CODE)
+# ifndef PNG_ASSEMBLER_CODE_SUPPORTED
+# define PNG_ASSEMBLER_CODE_SUPPORTED
+# endif
+
+# if defined(__GNUC__) && defined(__x86_64__) && (__GNUC__ < 4)
+ /* work around 64-bit gcc compiler bugs in gcc-3.x */
+# if !defined(PNG_MMX_CODE_SUPPORTED) && !defined(PNG_NO_MMX_CODE)
+# define PNG_NO_MMX_CODE
+# endif
+# endif
+
+# ifdef __APPLE__
+# if !defined(PNG_MMX_CODE_SUPPORTED) && !defined(PNG_NO_MMX_CODE)
+# define PNG_NO_MMX_CODE
+# endif
+# endif
+
+# if (defined(__MWERKS__) && ((__MWERKS__ < 0x0900) || macintosh))
+# if !defined(PNG_MMX_CODE_SUPPORTED) && !defined(PNG_NO_MMX_CODE)
+# define PNG_NO_MMX_CODE
+# endif
+# endif
+
+# if !defined(PNG_MMX_CODE_SUPPORTED) && !defined(PNG_NO_MMX_CODE)
+# define PNG_MMX_CODE_SUPPORTED
+# endif
+
+#endif
+/* end of obsolete code to be removed from libpng-1.4.0 */
+
+/* Added at libpng-1.2.0 */
+#ifndef PNG_1_0_X
+#if !defined(PNG_NO_USER_MEM) && !defined(PNG_USER_MEM_SUPPORTED)
+# define PNG_USER_MEM_SUPPORTED
+#endif
+#endif /* PNG_1_0_X */
+
+/* Added at libpng-1.2.6 */
+#ifndef PNG_1_0_X
+# ifndef PNG_SET_USER_LIMITS_SUPPORTED
+# ifndef PNG_NO_SET_USER_LIMITS
+# define PNG_SET_USER_LIMITS_SUPPORTED
+# endif
+# endif
+#endif /* PNG_1_0_X */
+
+/* Added at libpng-1.0.53 and 1.2.43 */
+#ifndef PNG_USER_LIMITS_SUPPORTED
+# ifndef PNG_NO_USER_LIMITS
+# define PNG_USER_LIMITS_SUPPORTED
+# endif
+#endif
+
+/* Added at libpng-1.0.16 and 1.2.6. To accept all valid PNGS no matter
+ * how large, set these limits to 0x7fffffffL
+ */
+#ifndef PNG_USER_WIDTH_MAX
+# define PNG_USER_WIDTH_MAX 1000000L
+#endif
+#ifndef PNG_USER_HEIGHT_MAX
+# define PNG_USER_HEIGHT_MAX 1000000L
+#endif
+
+/* Added at libpng-1.2.43. To accept all valid PNGs no matter
+ * how large, set these two limits to 0.
+ */
+#ifndef PNG_USER_CHUNK_CACHE_MAX
+# define PNG_USER_CHUNK_CACHE_MAX 0
+#endif
+
+/* Added at libpng-1.2.43 */
+#ifndef PNG_USER_CHUNK_MALLOC_MAX
+# define PNG_USER_CHUNK_MALLOC_MAX 0
+#endif
+
+#ifndef PNG_LITERAL_SHARP
+# define PNG_LITERAL_SHARP 0x23
+#endif
+#ifndef PNG_LITERAL_LEFT_SQUARE_BRACKET
+# define PNG_LITERAL_LEFT_SQUARE_BRACKET 0x5b
+#endif
+#ifndef PNG_LITERAL_RIGHT_SQUARE_BRACKET
+# define PNG_LITERAL_RIGHT_SQUARE_BRACKET 0x5d
+#endif
+
+/* Added at libpng-1.2.34 */
+#ifndef PNG_STRING_NEWLINE
+#define PNG_STRING_NEWLINE "\n"
+#endif
+
+/* These are currently experimental features, define them if you want */
+
+/* very little testing */
+/*
+#ifdef PNG_READ_SUPPORTED
+# ifndef PNG_READ_16_TO_8_ACCURATE_SCALE_SUPPORTED
+# define PNG_READ_16_TO_8_ACCURATE_SCALE_SUPPORTED
+# endif
+#endif
+*/
+
+/* This is only for PowerPC big-endian and 680x0 systems */
+/* some testing */
+/*
+#ifndef PNG_READ_BIG_ENDIAN_SUPPORTED
+# define PNG_READ_BIG_ENDIAN_SUPPORTED
+#endif
+*/
+
+/* Buggy compilers (e.g., gcc 2.7.2.2) need this */
+/*
+#define PNG_NO_POINTER_INDEXING
+*/
+
+#if !defined(PNG_NO_POINTER_INDEXING) && \
+ !defined(PNG_POINTER_INDEXING_SUPPORTED)
+# define PNG_POINTER_INDEXING_SUPPORTED
+#endif
+
+/* These functions are turned off by default, as they will be phased out. */
+/*
+#define PNG_USELESS_TESTS_SUPPORTED
+#define PNG_CORRECT_PALETTE_SUPPORTED
+*/
+
+/* Any chunks you are not interested in, you can undef here. The
+ * ones that allocate memory may be expecially important (hIST,
+ * tEXt, zTXt, tRNS, pCAL). Others will just save time and make png_info
+ * a bit smaller.
+ */
+
+#if defined(PNG_READ_SUPPORTED) && \
+ !defined(PNG_READ_ANCILLARY_CHUNKS_NOT_SUPPORTED) && \
+ !defined(PNG_NO_READ_ANCILLARY_CHUNKS)
+# define PNG_READ_ANCILLARY_CHUNKS_SUPPORTED
+#endif
+
+#if defined(PNG_WRITE_SUPPORTED) && \
+ !defined(PNG_WRITE_ANCILLARY_CHUNKS_NOT_SUPPORTED) && \
+ !defined(PNG_NO_WRITE_ANCILLARY_CHUNKS)
+# define PNG_WRITE_ANCILLARY_CHUNKS_SUPPORTED
+#endif
+
+#ifdef PNG_READ_ANCILLARY_CHUNKS_SUPPORTED
+
+#ifdef PNG_NO_READ_TEXT
+# define PNG_NO_READ_iTXt
+# define PNG_NO_READ_tEXt
+# define PNG_NO_READ_zTXt
+#endif
+#ifndef PNG_NO_READ_bKGD
+# define PNG_READ_bKGD_SUPPORTED
+# define PNG_bKGD_SUPPORTED
+#endif
+#ifndef PNG_NO_READ_cHRM
+# define PNG_READ_cHRM_SUPPORTED
+# define PNG_cHRM_SUPPORTED
+#endif
+#ifndef PNG_NO_READ_gAMA
+# define PNG_READ_gAMA_SUPPORTED
+# define PNG_gAMA_SUPPORTED
+#endif
+#ifndef PNG_NO_READ_hIST
+# define PNG_READ_hIST_SUPPORTED
+# define PNG_hIST_SUPPORTED
+#endif
+#ifndef PNG_NO_READ_iCCP
+# define PNG_READ_iCCP_SUPPORTED
+# define PNG_iCCP_SUPPORTED
+#endif
+#ifndef PNG_NO_READ_iTXt
+# ifndef PNG_READ_iTXt_SUPPORTED
+# define PNG_READ_iTXt_SUPPORTED
+# endif
+# ifndef PNG_iTXt_SUPPORTED
+# define PNG_iTXt_SUPPORTED
+# endif
+#endif
+#ifndef PNG_NO_READ_oFFs
+# define PNG_READ_oFFs_SUPPORTED
+# define PNG_oFFs_SUPPORTED
+#endif
+#ifndef PNG_NO_READ_pCAL
+# define PNG_READ_pCAL_SUPPORTED
+# define PNG_pCAL_SUPPORTED
+#endif
+#ifndef PNG_NO_READ_sCAL
+# define PNG_READ_sCAL_SUPPORTED
+# define PNG_sCAL_SUPPORTED
+#endif
+#ifndef PNG_NO_READ_pHYs
+# define PNG_READ_pHYs_SUPPORTED
+# define PNG_pHYs_SUPPORTED
+#endif
+#ifndef PNG_NO_READ_sBIT
+# define PNG_READ_sBIT_SUPPORTED
+# define PNG_sBIT_SUPPORTED
+#endif
+#ifndef PNG_NO_READ_sPLT
+# define PNG_READ_sPLT_SUPPORTED
+# define PNG_sPLT_SUPPORTED
+#endif
+#ifndef PNG_NO_READ_sRGB
+# define PNG_READ_sRGB_SUPPORTED
+# define PNG_sRGB_SUPPORTED
+#endif
+#ifndef PNG_NO_READ_tEXt
+# define PNG_READ_tEXt_SUPPORTED
+# define PNG_tEXt_SUPPORTED
+#endif
+#ifndef PNG_NO_READ_tIME
+# define PNG_READ_tIME_SUPPORTED
+# define PNG_tIME_SUPPORTED
+#endif
+#ifndef PNG_NO_READ_tRNS
+# define PNG_READ_tRNS_SUPPORTED
+# define PNG_tRNS_SUPPORTED
+#endif
+#ifndef PNG_NO_READ_zTXt
+# define PNG_READ_zTXt_SUPPORTED
+# define PNG_zTXt_SUPPORTED
+#endif
+#ifndef PNG_NO_READ_OPT_PLTE
+# define PNG_READ_OPT_PLTE_SUPPORTED /* only affects support of the */
+#endif /* optional PLTE chunk in RGB and RGBA images */
+#if defined(PNG_READ_iTXt_SUPPORTED) || defined(PNG_READ_tEXt_SUPPORTED) || \
+ defined(PNG_READ_zTXt_SUPPORTED)
+# define PNG_READ_TEXT_SUPPORTED
+# define PNG_TEXT_SUPPORTED
+#endif
+
+#endif /* PNG_READ_ANCILLARY_CHUNKS_SUPPORTED */
+
+#ifndef PNG_NO_READ_UNKNOWN_CHUNKS
+# define PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
+# ifndef PNG_UNKNOWN_CHUNKS_SUPPORTED
+# define PNG_UNKNOWN_CHUNKS_SUPPORTED
+# endif
+#endif
+#if !defined(PNG_NO_READ_USER_CHUNKS) && \
+ defined(PNG_READ_UNKNOWN_CHUNKS_SUPPORTED)
+# define PNG_READ_USER_CHUNKS_SUPPORTED
+# define PNG_USER_CHUNKS_SUPPORTED
+# ifdef PNG_NO_READ_UNKNOWN_CHUNKS
+# undef PNG_NO_READ_UNKNOWN_CHUNKS
+# endif
+# ifdef PNG_NO_HANDLE_AS_UNKNOWN
+# undef PNG_NO_HANDLE_AS_UNKNOWN
+# endif
+#endif
+
+#ifndef PNG_NO_HANDLE_AS_UNKNOWN
+# ifndef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
+# define PNG_HANDLE_AS_UNKNOWN_SUPPORTED
+# endif
+#endif
+
+#ifdef PNG_WRITE_SUPPORTED
+#ifdef PNG_WRITE_ANCILLARY_CHUNKS_SUPPORTED
+
+#ifdef PNG_NO_WRITE_TEXT
+# define PNG_NO_WRITE_iTXt
+# define PNG_NO_WRITE_tEXt
+# define PNG_NO_WRITE_zTXt
+#endif
+#ifndef PNG_NO_WRITE_bKGD
+# define PNG_WRITE_bKGD_SUPPORTED
+# ifndef PNG_bKGD_SUPPORTED
+# define PNG_bKGD_SUPPORTED
+# endif
+#endif
+#ifndef PNG_NO_WRITE_cHRM
+# define PNG_WRITE_cHRM_SUPPORTED
+# ifndef PNG_cHRM_SUPPORTED
+# define PNG_cHRM_SUPPORTED
+# endif
+#endif
+#ifndef PNG_NO_WRITE_gAMA
+# define PNG_WRITE_gAMA_SUPPORTED
+# ifndef PNG_gAMA_SUPPORTED
+# define PNG_gAMA_SUPPORTED
+# endif
+#endif
+#ifndef PNG_NO_WRITE_hIST
+# define PNG_WRITE_hIST_SUPPORTED
+# ifndef PNG_hIST_SUPPORTED
+# define PNG_hIST_SUPPORTED
+# endif
+#endif
+#ifndef PNG_NO_WRITE_iCCP
+# define PNG_WRITE_iCCP_SUPPORTED
+# ifndef PNG_iCCP_SUPPORTED
+# define PNG_iCCP_SUPPORTED
+# endif
+#endif
+#ifndef PNG_NO_WRITE_iTXt
+# ifndef PNG_WRITE_iTXt_SUPPORTED
+# define PNG_WRITE_iTXt_SUPPORTED
+# endif
+# ifndef PNG_iTXt_SUPPORTED
+# define PNG_iTXt_SUPPORTED
+# endif
+#endif
+#ifndef PNG_NO_WRITE_oFFs
+# define PNG_WRITE_oFFs_SUPPORTED
+# ifndef PNG_oFFs_SUPPORTED
+# define PNG_oFFs_SUPPORTED
+# endif
+#endif
+#ifndef PNG_NO_WRITE_pCAL
+# define PNG_WRITE_pCAL_SUPPORTED
+# ifndef PNG_pCAL_SUPPORTED
+# define PNG_pCAL_SUPPORTED
+# endif
+#endif
+#ifndef PNG_NO_WRITE_sCAL
+# define PNG_WRITE_sCAL_SUPPORTED
+# ifndef PNG_sCAL_SUPPORTED
+# define PNG_sCAL_SUPPORTED
+# endif
+#endif
+#ifndef PNG_NO_WRITE_pHYs
+# define PNG_WRITE_pHYs_SUPPORTED
+# ifndef PNG_pHYs_SUPPORTED
+# define PNG_pHYs_SUPPORTED
+# endif
+#endif
+#ifndef PNG_NO_WRITE_sBIT
+# define PNG_WRITE_sBIT_SUPPORTED
+# ifndef PNG_sBIT_SUPPORTED
+# define PNG_sBIT_SUPPORTED
+# endif
+#endif
+#ifndef PNG_NO_WRITE_sPLT
+# define PNG_WRITE_sPLT_SUPPORTED
+# ifndef PNG_sPLT_SUPPORTED
+# define PNG_sPLT_SUPPORTED
+# endif
+#endif
+#ifndef PNG_NO_WRITE_sRGB
+# define PNG_WRITE_sRGB_SUPPORTED
+# ifndef PNG_sRGB_SUPPORTED
+# define PNG_sRGB_SUPPORTED
+# endif
+#endif
+#ifndef PNG_NO_WRITE_tEXt
+# define PNG_WRITE_tEXt_SUPPORTED
+# ifndef PNG_tEXt_SUPPORTED
+# define PNG_tEXt_SUPPORTED
+# endif
+#endif
+#ifndef PNG_NO_WRITE_tIME
+# define PNG_WRITE_tIME_SUPPORTED
+# ifndef PNG_tIME_SUPPORTED
+# define PNG_tIME_SUPPORTED
+# endif
+#endif
+#ifndef PNG_NO_WRITE_tRNS
+# define PNG_WRITE_tRNS_SUPPORTED
+# ifndef PNG_tRNS_SUPPORTED
+# define PNG_tRNS_SUPPORTED
+# endif
+#endif
+#ifndef PNG_NO_WRITE_zTXt
+# define PNG_WRITE_zTXt_SUPPORTED
+# ifndef PNG_zTXt_SUPPORTED
+# define PNG_zTXt_SUPPORTED
+# endif
+#endif
+#if defined(PNG_WRITE_iTXt_SUPPORTED) || defined(PNG_WRITE_tEXt_SUPPORTED) || \
+ defined(PNG_WRITE_zTXt_SUPPORTED)
+# define PNG_WRITE_TEXT_SUPPORTED
+# ifndef PNG_TEXT_SUPPORTED
+# define PNG_TEXT_SUPPORTED
+# endif
+#endif
+
+#ifdef PNG_WRITE_tIME_SUPPORTED
+# ifndef PNG_NO_CONVERT_tIME
+# ifndef _WIN32_WCE
+/* The "tm" structure is not supported on WindowsCE */
+# ifndef PNG_CONVERT_tIME_SUPPORTED
+# define PNG_CONVERT_tIME_SUPPORTED
+# endif
+# endif
+# endif
+#endif
+
+#endif /* PNG_WRITE_ANCILLARY_CHUNKS_SUPPORTED */
+
+#if !defined(PNG_NO_WRITE_FILTER) && !defined(PNG_WRITE_FILTER_SUPPORTED)
+# define PNG_WRITE_FILTER_SUPPORTED
+#endif
+
+#ifndef PNG_NO_WRITE_UNKNOWN_CHUNKS
+# define PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED
+# ifndef PNG_UNKNOWN_CHUNKS_SUPPORTED
+# define PNG_UNKNOWN_CHUNKS_SUPPORTED
+# endif
+#endif
+
+#ifndef PNG_NO_HANDLE_AS_UNKNOWN
+# ifndef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
+# define PNG_HANDLE_AS_UNKNOWN_SUPPORTED
+# endif
+#endif
+#endif /* PNG_WRITE_SUPPORTED */
+
+/* Turn this off to disable png_read_png() and
+ * png_write_png() and leave the row_pointers member
+ * out of the info structure.
+ */
+#ifndef PNG_NO_INFO_IMAGE
+# define PNG_INFO_IMAGE_SUPPORTED
+#endif
+
+/* Need the time information for converting tIME chunks */
+#ifdef PNG_CONVERT_tIME_SUPPORTED
+ /* "time.h" functions are not supported on WindowsCE */
+# include <time.h>
+#endif
+
+/* Some typedefs to get us started. These should be safe on most of the
+ * common platforms. The typedefs should be at least as large as the
+ * numbers suggest (a png_uint_32 must be at least 32 bits long), but they
+ * don't have to be exactly that size. Some compilers dislike passing
+ * unsigned shorts as function parameters, so you may be better off using
+ * unsigned int for png_uint_16. Likewise, for 64-bit systems, you may
+ * want to have unsigned int for png_uint_32 instead of unsigned long.
+ */
+
+typedef unsigned long png_uint_32;
+typedef long png_int_32;
+typedef unsigned short png_uint_16;
+typedef short png_int_16;
+typedef unsigned char png_byte;
+
+/* This is usually size_t. It is typedef'ed just in case you need it to
+ change (I'm not sure if you will or not, so I thought I'd be safe) */
+#ifdef PNG_SIZE_T
+ typedef PNG_SIZE_T png_size_t;
+# define png_sizeof(x) png_convert_size(sizeof(x))
+#else
+ typedef size_t png_size_t;
+# define png_sizeof(x) sizeof(x)
+#endif
+
+/* The following is needed for medium model support. It cannot be in the
+ * PNG_INTERNAL section. Needs modification for other compilers besides
+ * MSC. Model independent support declares all arrays and pointers to be
+ * large using the far keyword. The zlib version used must also support
+ * model independent data. As of version zlib 1.0.4, the necessary changes
+ * have been made in zlib. The USE_FAR_KEYWORD define triggers other
+ * changes that are needed. (Tim Wegner)
+ */
+
+/* Separate compiler dependencies (problem here is that zlib.h always
+ defines FAR. (SJT) */
+#ifdef __BORLANDC__
+# if defined(__LARGE__) || defined(__HUGE__) || defined(__COMPACT__)
+# define LDATA 1
+# else
+# define LDATA 0
+# endif
+ /* GRR: why is Cygwin in here? Cygwin is not Borland C... */
+# if !defined(__WIN32__) && !defined(__FLAT__) && !defined(__CYGWIN__)
+# define PNG_MAX_MALLOC_64K
+# if (LDATA != 1)
+# ifndef FAR
+# define FAR __far
+# endif
+# define USE_FAR_KEYWORD
+# endif /* LDATA != 1 */
+ /* Possibly useful for moving data out of default segment.
+ * Uncomment it if you want. Could also define FARDATA as
+ * const if your compiler supports it. (SJT)
+# define FARDATA FAR
+ */
+# endif /* __WIN32__, __FLAT__, __CYGWIN__ */
+#endif /* __BORLANDC__ */
+
+
+/* Suggest testing for specific compiler first before testing for
+ * FAR. The Watcom compiler defines both __MEDIUM__ and M_I86MM,
+ * making reliance oncertain keywords suspect. (SJT)
+ */
+
+/* MSC Medium model */
+#ifdef FAR
+# ifdef M_I86MM
+# define USE_FAR_KEYWORD
+# define FARDATA FAR
+# include <dos.h>
+# endif
+#endif
+
+/* SJT: default case */
+#ifndef FAR
+# define FAR
+#endif
+
+/* At this point FAR is always defined */
+#ifndef FARDATA
+# define FARDATA
+#endif
+
+/* Typedef for floating-point numbers that are converted
+ to fixed-point with a multiple of 100,000, e.g., int_gamma */
+typedef png_int_32 png_fixed_point;
+
+/* Add typedefs for pointers */
+typedef void FAR * png_voidp;
+typedef png_byte FAR * png_bytep;
+typedef png_uint_32 FAR * png_uint_32p;
+typedef png_int_32 FAR * png_int_32p;
+typedef png_uint_16 FAR * png_uint_16p;
+typedef png_int_16 FAR * png_int_16p;
+typedef PNG_CONST char FAR * png_const_charp;
+typedef char FAR * png_charp;
+typedef png_fixed_point FAR * png_fixed_point_p;
+
+#ifndef PNG_NO_STDIO
+#ifdef _WIN32_WCE
+typedef HANDLE png_FILE_p;
+#else
+typedef FILE * png_FILE_p;
+#endif
+#endif
+
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+typedef double FAR * png_doublep;
+#endif
+
+/* Pointers to pointers; i.e. arrays */
+typedef png_byte FAR * FAR * png_bytepp;
+typedef png_uint_32 FAR * FAR * png_uint_32pp;
+typedef png_int_32 FAR * FAR * png_int_32pp;
+typedef png_uint_16 FAR * FAR * png_uint_16pp;
+typedef png_int_16 FAR * FAR * png_int_16pp;
+typedef PNG_CONST char FAR * FAR * png_const_charpp;
+typedef char FAR * FAR * png_charpp;
+typedef png_fixed_point FAR * FAR * png_fixed_point_pp;
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+typedef double FAR * FAR * png_doublepp;
+#endif
+
+/* Pointers to pointers to pointers; i.e., pointer to array */
+typedef char FAR * FAR * FAR * png_charppp;
+
+#if defined(PNG_1_0_X) || defined(PNG_1_2_X)
+/* SPC - Is this stuff deprecated? */
+/* It'll be removed as of libpng-1.4.0 - GR-P */
+/* libpng typedefs for types in zlib. If zlib changes
+ * or another compression library is used, then change these.
+ * Eliminates need to change all the source files.
+ */
+typedef charf * png_zcharp;
+typedef charf * FAR * png_zcharpp;
+typedef z_stream FAR * png_zstreamp;
+#endif /* (PNG_1_0_X) || defined(PNG_1_2_X) */
+
+/*
+ * Define PNG_BUILD_DLL if the module being built is a Windows
+ * LIBPNG DLL.
+ *
+ * Define PNG_USE_DLL if you want to *link* to the Windows LIBPNG DLL.
+ * It is equivalent to Microsoft predefined macro _DLL that is
+ * automatically defined when you compile using the share
+ * version of the CRT (C Run-Time library)
+ *
+ * The cygwin mods make this behavior a little different:
+ * Define PNG_BUILD_DLL if you are building a dll for use with cygwin
+ * Define PNG_STATIC if you are building a static library for use with cygwin,
+ * -or- if you are building an application that you want to link to the
+ * static library.
+ * PNG_USE_DLL is defined by default (no user action needed) unless one of
+ * the other flags is defined.
+ */
+
+#if !defined(PNG_DLL) && (defined(PNG_BUILD_DLL) || defined(PNG_USE_DLL))
+# define PNG_DLL
+#endif
+/* If CYGWIN, then disallow GLOBAL ARRAYS unless building a static lib.
+ * When building a static lib, default to no GLOBAL ARRAYS, but allow
+ * command-line override
+ */
+#ifdef __CYGWIN__
+# ifndef PNG_STATIC
+# ifdef PNG_USE_GLOBAL_ARRAYS
+# undef PNG_USE_GLOBAL_ARRAYS
+# endif
+# ifndef PNG_USE_LOCAL_ARRAYS
+# define PNG_USE_LOCAL_ARRAYS
+# endif
+# else
+# if defined(PNG_USE_LOCAL_ARRAYS) || defined(PNG_NO_GLOBAL_ARRAYS)
+# ifdef PNG_USE_GLOBAL_ARRAYS
+# undef PNG_USE_GLOBAL_ARRAYS
+# endif
+# endif
+# endif
+# if !defined(PNG_USE_LOCAL_ARRAYS) && !defined(PNG_USE_GLOBAL_ARRAYS)
+# define PNG_USE_LOCAL_ARRAYS
+# endif
+#endif
+
+/* Do not use global arrays (helps with building DLL's)
+ * They are no longer used in libpng itself, since version 1.0.5c,
+ * but might be required for some pre-1.0.5c applications.
+ */
+#if !defined(PNG_USE_LOCAL_ARRAYS) && !defined(PNG_USE_GLOBAL_ARRAYS)
+# if defined(PNG_NO_GLOBAL_ARRAYS) || \
+ (defined(__GNUC__) && defined(PNG_DLL)) || defined(_MSC_VER)
+# define PNG_USE_LOCAL_ARRAYS
+# else
+# define PNG_USE_GLOBAL_ARRAYS
+# endif
+#endif
+
+#ifdef __CYGWIN__
+# undef PNGAPI
+# define PNGAPI __cdecl
+# undef PNG_IMPEXP
+# define PNG_IMPEXP
+#endif
+
+/* If you define PNGAPI, e.g., with compiler option "-DPNGAPI=__stdcall",
+ * you may get warnings regarding the linkage of png_zalloc and png_zfree.
+ * Don't ignore those warnings; you must also reset the default calling
+ * convention in your compiler to match your PNGAPI, and you must build
+ * zlib and your applications the same way you build libpng.
+ */
+
+#if defined(__MINGW32__) && !defined(PNG_MODULEDEF)
+# ifndef PNG_NO_MODULEDEF
+# define PNG_NO_MODULEDEF
+# endif
+#endif
+
+#if !defined(PNG_IMPEXP) && defined(PNG_BUILD_DLL) && !defined(PNG_NO_MODULEDEF)
+# define PNG_IMPEXP
+#endif
+
+#if defined(PNG_DLL) || defined(_DLL) || defined(__DLL__ ) || \
+ (( defined(_Windows) || defined(_WINDOWS) || \
+ defined(WIN32) || defined(_WIN32) || defined(__WIN32__) ))
+
+# ifndef PNGAPI
+# if defined(__GNUC__) || (defined (_MSC_VER) && (_MSC_VER >= 800))
+# define PNGAPI __cdecl
+# else
+# define PNGAPI _cdecl
+# endif
+# endif
+
+# if !defined(PNG_IMPEXP) && (!defined(PNG_DLL) || \
+ 0 /* WINCOMPILER_WITH_NO_SUPPORT_FOR_DECLIMPEXP */)
+# define PNG_IMPEXP
+# endif
+
+# ifndef PNG_IMPEXP
+
+# define PNG_EXPORT_TYPE1(type,symbol) PNG_IMPEXP type PNGAPI symbol
+# define PNG_EXPORT_TYPE2(type,symbol) type PNG_IMPEXP PNGAPI symbol
+
+ /* Borland/Microsoft */
+# if defined(_MSC_VER) || defined(__BORLANDC__)
+# if (_MSC_VER >= 800) || (__BORLANDC__ >= 0x500)
+# define PNG_EXPORT PNG_EXPORT_TYPE1
+# else
+# define PNG_EXPORT PNG_EXPORT_TYPE2
+# ifdef PNG_BUILD_DLL
+# define PNG_IMPEXP __export
+# else
+# define PNG_IMPEXP /*__import */ /* doesn't exist AFAIK in
+ VC++ */
+# endif /* Exists in Borland C++ for
+ C++ classes (== huge) */
+# endif
+# endif
+
+# ifndef PNG_IMPEXP
+# ifdef PNG_BUILD_DLL
+# define PNG_IMPEXP __declspec(dllexport)
+# else
+# define PNG_IMPEXP __declspec(dllimport)
+# endif
+# endif
+# endif /* PNG_IMPEXP */
+#else /* !(DLL || non-cygwin WINDOWS) */
+# if (defined(__IBMC__) || defined(__IBMCPP__)) && defined(__OS2__)
+# ifndef PNGAPI
+# define PNGAPI _System
+# endif
+# else
+# if 0 /* ... other platforms, with other meanings */
+# endif
+# endif
+#endif
+
+#ifndef PNGAPI
+# define PNGAPI
+#endif
+#ifndef PNG_IMPEXP
+# define PNG_IMPEXP
+#endif
+
+#ifdef PNG_BUILDSYMS
+# ifndef PNG_EXPORT
+# define PNG_EXPORT(type,symbol) PNG_FUNCTION_EXPORT symbol END
+# endif
+# ifdef PNG_USE_GLOBAL_ARRAYS
+# ifndef PNG_EXPORT_VAR
+# define PNG_EXPORT_VAR(type) PNG_DATA_EXPORT
+# endif
+# endif
+#endif
+
+#ifndef PNG_EXPORT
+# define PNG_EXPORT(type,symbol) PNG_IMPEXP type PNGAPI symbol
+#endif
+
+#ifdef PNG_USE_GLOBAL_ARRAYS
+# ifndef PNG_EXPORT_VAR
+# define PNG_EXPORT_VAR(type) extern PNG_IMPEXP type
+# endif
+#endif
+
+#ifdef PNG_PEDANTIC_WARNINGS
+# ifndef PNG_PEDANTIC_WARNINGS_SUPPORTED
+# define PNG_PEDANTIC_WARNINGS_SUPPORTED
+# endif
+#endif
+
+#ifdef PNG_PEDANTIC_WARNINGS_SUPPORTED
+/* Support for compiler specific function attributes. These are used
+ * so that where compiler support is available incorrect use of API
+ * functions in png.h will generate compiler warnings. Added at libpng
+ * version 1.2.41.
+ */
+# ifdef __GNUC__
+# ifndef PNG_USE_RESULT
+# define PNG_USE_RESULT __attribute__((__warn_unused_result__))
+# endif
+# ifndef PNG_NORETURN
+# define PNG_NORETURN __attribute__((__noreturn__))
+# endif
+# ifndef PNG_ALLOCATED
+# define PNG_ALLOCATED __attribute__((__malloc__))
+# endif
+
+ /* This specifically protects structure members that should only be
+ * accessed from within the library, therefore should be empty during
+ * a library build.
+ */
+# ifndef PNG_DEPRECATED
+# define PNG_DEPRECATED __attribute__((__deprecated__))
+# endif
+# ifndef PNG_DEPSTRUCT
+# define PNG_DEPSTRUCT __attribute__((__deprecated__))
+# endif
+# ifndef PNG_PRIVATE
+# if 0 /* Doesn't work so we use deprecated instead*/
+# define PNG_PRIVATE \
+ __attribute__((warning("This function is not exported by libpng.")))
+# else
+# define PNG_PRIVATE \
+ __attribute__((__deprecated__))
+# endif
+# endif /* PNG_PRIVATE */
+# endif /* __GNUC__ */
+#endif /* PNG_PEDANTIC_WARNINGS */
+
+#ifndef PNG_DEPRECATED
+# define PNG_DEPRECATED /* Use of this function is deprecated */
+#endif
+#ifndef PNG_USE_RESULT
+# define PNG_USE_RESULT /* The result of this function must be checked */
+#endif
+#ifndef PNG_NORETURN
+# define PNG_NORETURN /* This function does not return */
+#endif
+#ifndef PNG_ALLOCATED
+# define PNG_ALLOCATED /* The result of the function is new memory */
+#endif
+#ifndef PNG_DEPSTRUCT
+# define PNG_DEPSTRUCT /* Access to this struct member is deprecated */
+#endif
+#ifndef PNG_PRIVATE
+# define PNG_PRIVATE /* This is a private libpng function */
+#endif
+
+/* User may want to use these so they are not in PNG_INTERNAL. Any library
+ * functions that are passed far data must be model independent.
+ */
+
+#ifndef PNG_ABORT
+# define PNG_ABORT() abort()
+#endif
+
+#ifdef PNG_SETJMP_SUPPORTED
+# define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf)
+#else
+# define png_jmpbuf(png_ptr) \
+ (LIBPNG_WAS_COMPILED_WITH__PNG_SETJMP_NOT_SUPPORTED)
+#endif
+
+#ifdef USE_FAR_KEYWORD /* memory model independent fns */
+/* Use this to make far-to-near assignments */
+# define CHECK 1
+# define NOCHECK 0
+# define CVT_PTR(ptr) (png_far_to_near(png_ptr,ptr,CHECK))
+# define CVT_PTR_NOCHECK(ptr) (png_far_to_near(png_ptr,ptr,NOCHECK))
+# define png_snprintf _fsnprintf /* Added to v 1.2.19 */
+# define png_strlen _fstrlen
+# define png_memcmp _fmemcmp /* SJT: added */
+# define png_memcpy _fmemcpy
+# define png_memset _fmemset
+#else /* Use the usual functions */
+# define CVT_PTR(ptr) (ptr)
+# define CVT_PTR_NOCHECK(ptr) (ptr)
+# ifndef PNG_NO_SNPRINTF
+# ifdef _MSC_VER
+# define png_snprintf _snprintf /* Added to v 1.2.19 */
+# define png_snprintf2 _snprintf
+# define png_snprintf6 _snprintf
+# else
+# define png_snprintf snprintf /* Added to v 1.2.19 */
+# define png_snprintf2 snprintf
+# define png_snprintf6 snprintf
+# endif
+# else
+ /* You don't have or don't want to use snprintf(). Caution: Using
+ * sprintf instead of snprintf exposes your application to accidental
+ * or malevolent buffer overflows. If you don't have snprintf()
+ * as a general rule you should provide one (you can get one from
+ * Portable OpenSSH).
+ */
+# define png_snprintf(s1,n,fmt,x1) sprintf(s1,fmt,x1)
+# define png_snprintf2(s1,n,fmt,x1,x2) sprintf(s1,fmt,x1,x2)
+# define png_snprintf6(s1,n,fmt,x1,x2,x3,x4,x5,x6) \
+ sprintf(s1,fmt,x1,x2,x3,x4,x5,x6)
+# endif
+# define png_strlen strlen
+# define png_memcmp memcmp /* SJT: added */
+# define png_memcpy memcpy
+# define png_memset memset
+#endif
+/* End of memory model independent support */
+
+/* Just a little check that someone hasn't tried to define something
+ * contradictory.
+ */
+#if (PNG_ZBUF_SIZE > 65536L) && defined(PNG_MAX_MALLOC_64K)
+# undef PNG_ZBUF_SIZE
+# define PNG_ZBUF_SIZE 65536L
+#endif
+
+/* Added at libpng-1.2.8 */
+#endif /* PNG_VERSION_INFO_ONLY */
+
+#endif /* PNGCONF_H */
diff --git a/trunk/src/third_party/libpng/pngerror.c b/trunk/src/third_party/libpng/pngerror.c
new file mode 100644
index 0000000..7bc98fb
--- /dev/null
+++ b/trunk/src/third_party/libpng/pngerror.c
@@ -0,0 +1,386 @@
+
+/* pngerror.c - stub functions for i/o and memory allocation
+ *
+ * Last changed in libpng 1.2.41 [December 3, 2009]
+ * Copyright (c) 1998-2009 Glenn Randers-Pehrson
+ * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger)
+ * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.)
+ *
+ * This code is released under the libpng license.
+ * For conditions of distribution and use, see the disclaimer
+ * and license in png.h
+ *
+ * This file provides a location for all error handling. Users who
+ * need special error handling are expected to write replacement functions
+ * and use png_set_error_fn() to use those functions. See the instructions
+ * at each function.
+ */
+
+#define PNG_INTERNAL
+#define PNG_NO_PEDANTIC_WARNINGS
+#include "png.h"
+#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED)
+
+static void /* PRIVATE */
+png_default_error PNGARG((png_structp png_ptr,
+ png_const_charp error_message)) PNG_NORETURN;
+#ifdef PNG_WARNINGS_SUPPORTED
+static void /* PRIVATE */
+png_default_warning PNGARG((png_structp png_ptr,
+ png_const_charp warning_message));
+#endif /* PNG_WARNINGS_SUPPORTED */
+
+/* This function is called whenever there is a fatal error. This function
+ * should not be changed. If there is a need to handle errors differently,
+ * you should supply a replacement error function and use png_set_error_fn()
+ * to replace the error function at run-time.
+ */
+#ifdef PNG_ERROR_TEXT_SUPPORTED
+void PNGAPI
+png_error(png_structp png_ptr, png_const_charp error_message)
+{
+#ifdef PNG_ERROR_NUMBERS_SUPPORTED
+ char msg[16];
+ if (png_ptr != NULL)
+ {
+ if (png_ptr->flags&
+ (PNG_FLAG_STRIP_ERROR_NUMBERS|PNG_FLAG_STRIP_ERROR_TEXT))
+ {
+ if (*error_message == PNG_LITERAL_SHARP)
+ {
+ /* Strip "#nnnn " from beginning of error message. */
+ int offset;
+ for (offset = 1; offset<15; offset++)
+ if (error_message[offset] == ' ')
+ break;
+ if (png_ptr->flags&PNG_FLAG_STRIP_ERROR_TEXT)
+ {
+ int i;
+ for (i = 0; i < offset - 1; i++)
+ msg[i] = error_message[i + 1];
+ msg[i - 1] = '\0';
+ error_message = msg;
+ }
+ else
+ error_message += offset;
+ }
+ else
+ {
+ if (png_ptr->flags&PNG_FLAG_STRIP_ERROR_TEXT)
+ {
+ msg[0] = '0';
+ msg[1] = '\0';
+ error_message = msg;
+ }
+ }
+ }
+ }
+#endif
+ if (png_ptr != NULL && png_ptr->error_fn != NULL)
+ (*(png_ptr->error_fn))(png_ptr, error_message);
+
+ /* If the custom handler doesn't exist, or if it returns,
+ use the default handler, which will not return. */
+ png_default_error(png_ptr, error_message);
+}
+#else
+void PNGAPI
+png_err(png_structp png_ptr)
+{
+ if (png_ptr != NULL && png_ptr->error_fn != NULL)
+ (*(png_ptr->error_fn))(png_ptr, '\0');
+
+ /* If the custom handler doesn't exist, or if it returns,
+ use the default handler, which will not return. */
+ png_default_error(png_ptr, '\0');
+}
+#endif /* PNG_ERROR_TEXT_SUPPORTED */
+
+#ifdef PNG_WARNINGS_SUPPORTED
+/* This function is called whenever there is a non-fatal error. This function
+ * should not be changed. If there is a need to handle warnings differently,
+ * you should supply a replacement warning function and use
+ * png_set_error_fn() to replace the warning function at run-time.
+ */
+void PNGAPI
+png_warning(png_structp png_ptr, png_const_charp warning_message)
+{
+ int offset = 0;
+ if (png_ptr != NULL)
+ {
+#ifdef PNG_ERROR_NUMBERS_SUPPORTED
+ if (png_ptr->flags&
+ (PNG_FLAG_STRIP_ERROR_NUMBERS|PNG_FLAG_STRIP_ERROR_TEXT))
+#endif
+ {
+ if (*warning_message == PNG_LITERAL_SHARP)
+ {
+ for (offset = 1; offset < 15; offset++)
+ if (warning_message[offset] == ' ')
+ break;
+ }
+ }
+ }
+ if (png_ptr != NULL && png_ptr->warning_fn != NULL)
+ (*(png_ptr->warning_fn))(png_ptr, warning_message + offset);
+ else
+ png_default_warning(png_ptr, warning_message + offset);
+}
+#endif /* PNG_WARNINGS_SUPPORTED */
+
+#ifdef PNG_BENIGN_ERRORS_SUPPORTED
+void PNGAPI
+png_benign_error(png_structp png_ptr, png_const_charp error_message)
+{
+ if (png_ptr->flags & PNG_FLAG_BENIGN_ERRORS_WARN)
+ png_warning(png_ptr, error_message);
+ else
+ png_error(png_ptr, error_message);
+}
+#endif
+
+/* These utilities are used internally to build an error message that relates
+ * to the current chunk. The chunk name comes from png_ptr->chunk_name,
+ * this is used to prefix the message. The message is limited in length
+ * to 63 bytes, the name characters are output as hex digits wrapped in []
+ * if the character is invalid.
+ */
+#define isnonalpha(c) ((c) < 65 || (c) > 122 || ((c) > 90 && (c) < 97))
+static PNG_CONST char png_digit[16] = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'A', 'B', 'C', 'D', 'E', 'F'
+};
+
+#define PNG_MAX_ERROR_TEXT 64
+#if defined(PNG_WARNINGS_SUPPORTED) || defined(PNG_ERROR_TEXT_SUPPORTED)
+static void /* PRIVATE */
+png_format_buffer(png_structp png_ptr, png_charp buffer, png_const_charp
+ error_message)
+{
+ int iout = 0, iin = 0;
+
+ while (iin < 4)
+ {
+ int c = png_ptr->chunk_name[iin++];
+ if (isnonalpha(c))
+ {
+ buffer[iout++] = PNG_LITERAL_LEFT_SQUARE_BRACKET;
+ buffer[iout++] = png_digit[(c & 0xf0) >> 4];
+ buffer[iout++] = png_digit[c & 0x0f];
+ buffer[iout++] = PNG_LITERAL_RIGHT_SQUARE_BRACKET;
+ }
+ else
+ {
+ buffer[iout++] = (png_byte)c;
+ }
+ }
+
+ if (error_message == NULL)
+ buffer[iout] = '\0';
+ else
+ {
+ buffer[iout++] = ':';
+ buffer[iout++] = ' ';
+ png_memcpy(buffer + iout, error_message, PNG_MAX_ERROR_TEXT);
+ buffer[iout + PNG_MAX_ERROR_TEXT - 1] = '\0';
+ }
+}
+
+#ifdef PNG_READ_SUPPORTED
+void PNGAPI
+png_chunk_error(png_structp png_ptr, png_const_charp error_message)
+{
+ char msg[18+PNG_MAX_ERROR_TEXT];
+ if (png_ptr == NULL)
+ png_error(png_ptr, error_message);
+ else
+ {
+ png_format_buffer(png_ptr, msg, error_message);
+ png_error(png_ptr, msg);
+ }
+}
+#endif /* PNG_READ_SUPPORTED */
+#endif /* PNG_WARNINGS_SUPPORTED || PNG_ERROR_TEXT_SUPPORTED */
+
+#ifdef PNG_WARNINGS_SUPPORTED
+void PNGAPI
+png_chunk_warning(png_structp png_ptr, png_const_charp warning_message)
+{
+ char msg[18+PNG_MAX_ERROR_TEXT];
+ if (png_ptr == NULL)
+ png_warning(png_ptr, warning_message);
+ else
+ {
+ png_format_buffer(png_ptr, msg, warning_message);
+ png_warning(png_ptr, msg);
+ }
+}
+#endif /* PNG_WARNINGS_SUPPORTED */
+
+#ifdef PNG_READ_SUPPORTED
+#ifdef PNG_BENIGN_ERRORS_SUPPORTED
+void PNGAPI
+png_chunk_benign_error(png_structp png_ptr, png_const_charp error_message)
+{
+ if (png_ptr->flags & PNG_FLAG_BENIGN_ERRORS_WARN)
+ png_chunk_warning(png_ptr, error_message);
+ else
+ png_chunk_error(png_ptr, error_message);
+}
+#endif
+#endif /* PNG_READ_SUPPORTED */
+
+/* This is the default error handling function. Note that replacements for
+ * this function MUST NOT RETURN, or the program will likely crash. This
+ * function is used by default, or if the program supplies NULL for the
+ * error function pointer in png_set_error_fn().
+ */
+static void /* PRIVATE */
+png_default_error(png_structp png_ptr, png_const_charp error_message)
+{
+#ifdef PNG_CONSOLE_IO_SUPPORTED
+#ifdef PNG_ERROR_NUMBERS_SUPPORTED
+ if (*error_message == PNG_LITERAL_SHARP)
+ {
+ /* Strip "#nnnn " from beginning of error message. */
+ int offset;
+ char error_number[16];
+ for (offset = 0; offset<15; offset++)
+ {
+ error_number[offset] = error_message[offset + 1];
+ if (error_message[offset] == ' ')
+ break;
+ }
+ if ((offset > 1) && (offset < 15))
+ {
+ error_number[offset - 1] = '\0';
+ fprintf(stderr, "libpng error no. %s: %s",
+ error_number, error_message + offset + 1);
+ fprintf(stderr, PNG_STRING_NEWLINE);
+ }
+ else
+ {
+ fprintf(stderr, "libpng error: %s, offset=%d",
+ error_message, offset);
+ fprintf(stderr, PNG_STRING_NEWLINE);
+ }
+ }
+ else
+#endif
+ {
+ fprintf(stderr, "libpng error: %s", error_message);
+ fprintf(stderr, PNG_STRING_NEWLINE);
+ }
+#endif
+
+#ifdef PNG_SETJMP_SUPPORTED
+ if (png_ptr)
+ {
+# ifdef USE_FAR_KEYWORD
+ {
+ jmp_buf jmpbuf;
+ png_memcpy(jmpbuf, png_ptr->jmpbuf, png_sizeof(jmp_buf));
+ longjmp(jmpbuf,1);
+ }
+# else
+ longjmp(png_ptr->jmpbuf, 1);
+# endif
+ }
+#endif
+ /* Here if not setjmp support or if png_ptr is null. */
+ PNG_ABORT();
+#ifndef PNG_CONSOLE_IO_SUPPORTED
+ error_message = error_message; /* Make compiler happy */
+#endif
+}
+
+#ifdef PNG_WARNINGS_SUPPORTED
+/* This function is called when there is a warning, but the library thinks
+ * it can continue anyway. Replacement functions don't have to do anything
+ * here if you don't want them to. In the default configuration, png_ptr is
+ * not used, but it is passed in case it may be useful.
+ */
+static void /* PRIVATE */
+png_default_warning(png_structp png_ptr, png_const_charp warning_message)
+{
+#ifdef PNG_CONSOLE_IO_SUPPORTED
+# ifdef PNG_ERROR_NUMBERS_SUPPORTED
+ if (*warning_message == PNG_LITERAL_SHARP)
+ {
+ int offset;
+ char warning_number[16];
+ for (offset = 0; offset < 15; offset++)
+ {
+ warning_number[offset] = warning_message[offset + 1];
+ if (warning_message[offset] == ' ')
+ break;
+ }
+ if ((offset > 1) && (offset < 15))
+ {
+ warning_number[offset + 1] = '\0';
+ fprintf(stderr, "libpng warning no. %s: %s",
+ warning_number, warning_message + offset);
+ fprintf(stderr, PNG_STRING_NEWLINE);
+ }
+ else
+ {
+ fprintf(stderr, "libpng warning: %s",
+ warning_message);
+ fprintf(stderr, PNG_STRING_NEWLINE);
+ }
+ }
+ else
+# endif
+ {
+ fprintf(stderr, "libpng warning: %s", warning_message);
+ fprintf(stderr, PNG_STRING_NEWLINE);
+ }
+#else
+ warning_message = warning_message; /* Make compiler happy */
+#endif
+ png_ptr = png_ptr; /* Make compiler happy */
+}
+#endif /* PNG_WARNINGS_SUPPORTED */
+
+/* This function is called when the application wants to use another method
+ * of handling errors and warnings. Note that the error function MUST NOT
+ * return to the calling routine or serious problems will occur. The return
+ * method used in the default routine calls longjmp(png_ptr->jmpbuf, 1)
+ */
+void PNGAPI
+png_set_error_fn(png_structp png_ptr, png_voidp error_ptr,
+ png_error_ptr error_fn, png_error_ptr warning_fn)
+{
+ if (png_ptr == NULL)
+ return;
+ png_ptr->error_ptr = error_ptr;
+ png_ptr->error_fn = error_fn;
+ png_ptr->warning_fn = warning_fn;
+}
+
+
+/* This function returns a pointer to the error_ptr associated with the user
+ * functions. The application should free any memory associated with this
+ * pointer before png_write_destroy and png_read_destroy are called.
+ */
+png_voidp PNGAPI
+png_get_error_ptr(png_structp png_ptr)
+{
+ if (png_ptr == NULL)
+ return NULL;
+ return ((png_voidp)png_ptr->error_ptr);
+}
+
+
+#ifdef PNG_ERROR_NUMBERS_SUPPORTED
+void PNGAPI
+png_set_strip_error_numbers(png_structp png_ptr, png_uint_32 strip_mode)
+{
+ if (png_ptr != NULL)
+ {
+ png_ptr->flags &=
+ ((~(PNG_FLAG_STRIP_ERROR_NUMBERS|PNG_FLAG_STRIP_ERROR_TEXT))&strip_mode);
+ }
+}
+#endif
+#endif /* PNG_READ_SUPPORTED || PNG_WRITE_SUPPORTED */
diff --git a/trunk/src/third_party/libpng/pnggccrd.c b/trunk/src/third_party/libpng/pnggccrd.c
new file mode 100644
index 0000000..78b8a7e
--- /dev/null
+++ b/trunk/src/third_party/libpng/pnggccrd.c
@@ -0,0 +1,103 @@
+/* pnggccrd.c was removed from libpng-1.2.20. */
+
+/* This code snippet is for use by configure's compilation test. */
+
+#if (!defined _MSC_VER) && \
+ defined(PNG_ASSEMBLER_CODE_SUPPORTED) && \
+ defined(PNG_MMX_CODE_SUPPORTED)
+
+int PNGAPI png_dummy_mmx_support(void);
+
+static int _mmx_supported = 2; // 0: no MMX; 1: MMX supported; 2: not tested
+
+int PNGAPI
+png_dummy_mmx_support(void) __attribute__((noinline));
+
+int PNGAPI
+png_dummy_mmx_support(void)
+{
+ int result;
+#ifdef PNG_MMX_CODE_SUPPORTED // superfluous, but what the heck
+ __asm__ __volatile__ (
+#ifdef __x86_64__
+ "pushq %%rbx \n\t" // rbx gets clobbered by CPUID instruction
+ "pushq %%rcx \n\t" // so does rcx...
+ "pushq %%rdx \n\t" // ...and rdx (but rcx & rdx safe on Linux)
+ "pushfq \n\t" // save Eflag to stack
+ "popq %%rax \n\t" // get Eflag from stack into rax
+ "movq %%rax, %%rcx \n\t" // make another copy of Eflag in rcx
+ "xorl $0x200000, %%eax \n\t" // toggle ID bit in Eflag (i.e., bit 21)
+ "pushq %%rax \n\t" // save modified Eflag back to stack
+ "popfq \n\t" // restore modified value to Eflag reg
+ "pushfq \n\t" // save Eflag to stack
+ "popq %%rax \n\t" // get Eflag from stack
+ "pushq %%rcx \n\t" // save original Eflag to stack
+ "popfq \n\t" // restore original Eflag
+#else
+ "pushl %%ebx \n\t" // ebx gets clobbered by CPUID instruction
+ "pushl %%ecx \n\t" // so does ecx...
+ "pushl %%edx \n\t" // ...and edx (but ecx & edx safe on Linux)
+ "pushfl \n\t" // save Eflag to stack
+ "popl %%eax \n\t" // get Eflag from stack into eax
+ "movl %%eax, %%ecx \n\t" // make another copy of Eflag in ecx
+ "xorl $0x200000, %%eax \n\t" // toggle ID bit in Eflag (i.e., bit 21)
+ "pushl %%eax \n\t" // save modified Eflag back to stack
+ "popfl \n\t" // restore modified value to Eflag reg
+ "pushfl \n\t" // save Eflag to stack
+ "popl %%eax \n\t" // get Eflag from stack
+ "pushl %%ecx \n\t" // save original Eflag to stack
+ "popfl \n\t" // restore original Eflag
+#endif
+ "xorl %%ecx, %%eax \n\t" // compare new Eflag with original Eflag
+ "jz 0f \n\t" // if same, CPUID instr. is not supported
+
+ "xorl %%eax, %%eax \n\t" // set eax to zero
+// ".byte 0x0f, 0xa2 \n\t" // CPUID instruction (two-byte opcode)
+ "cpuid \n\t" // get the CPU identification info
+ "cmpl $1, %%eax \n\t" // make sure eax return non-zero value
+ "jl 0f \n\t" // if eax is zero, MMX is not supported
+
+ "xorl %%eax, %%eax \n\t" // set eax to zero and...
+ "incl %%eax \n\t" // ...increment eax to 1. This pair is
+ // faster than the instruction "mov eax, 1"
+ "cpuid \n\t" // get the CPU identification info again
+ "andl $0x800000, %%edx \n\t" // mask out all bits but MMX bit (23)
+ "cmpl $0, %%edx \n\t" // 0 = MMX not supported
+ "jz 0f \n\t" // non-zero = yes, MMX IS supported
+
+ "movl $1, %%eax \n\t" // set return value to 1
+ "jmp 1f \n\t" // DONE: have MMX support
+
+ "0: \n\t" // .NOT_SUPPORTED: target label for jump instructions
+ "movl $0, %%eax \n\t" // set return value to 0
+ "1: \n\t" // .RETURN: target label for jump instructions
+#ifdef __x86_64__
+ "popq %%rdx \n\t" // restore rdx
+ "popq %%rcx \n\t" // restore rcx
+ "popq %%rbx \n\t" // restore rbx
+#else
+ "popl %%edx \n\t" // restore edx
+ "popl %%ecx \n\t" // restore ecx
+ "popl %%ebx \n\t" // restore ebx
+#endif
+
+// "ret \n\t" // DONE: no MMX support
+ // (fall through to standard C "ret")
+
+ : "=a" (result) // output list
+
+ : // any variables used on input (none)
+
+ // no clobber list
+// , "%ebx", "%ecx", "%edx" // GRR: we handle these manually
+// , "memory" // if write to a variable gcc thought was in a reg
+// , "cc" // "condition codes" (flag bits)
+ );
+ _mmx_supported = result;
+#else
+ _mmx_supported = 0;
+#endif /* PNG_MMX_CODE_SUPPORTED */
+
+ return _mmx_supported;
+}
+#endif
diff --git a/trunk/src/third_party/libpng/pngget.c b/trunk/src/third_party/libpng/pngget.c
new file mode 100644
index 0000000..d397329
--- /dev/null
+++ b/trunk/src/third_party/libpng/pngget.c
@@ -0,0 +1,944 @@
+
+/* pngget.c - retrieval of values from info struct
+ *
+ * Last changed in libpng 1.2.43 [February 25, 2010]
+ * Copyright (c) 1998-2010 Glenn Randers-Pehrson
+ * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger)
+ * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.)
+ *
+ * This code is released under the libpng license.
+ * For conditions of distribution and use, see the disclaimer
+ * and license in png.h
+ *
+ */
+
+#define PNG_INTERNAL
+#define PNG_NO_PEDANTIC_WARNINGS
+#include "png.h"
+#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED)
+
+png_uint_32 PNGAPI
+png_get_valid(png_structp png_ptr, png_infop info_ptr, png_uint_32 flag)
+{
+ if (png_ptr != NULL && info_ptr != NULL)
+ return(info_ptr->valid & flag);
+
+ else
+ return(0);
+}
+
+png_uint_32 PNGAPI
+png_get_rowbytes(png_structp png_ptr, png_infop info_ptr)
+{
+ if (png_ptr != NULL && info_ptr != NULL)
+ return(info_ptr->rowbytes);
+
+ else
+ return(0);
+}
+
+#ifdef PNG_INFO_IMAGE_SUPPORTED
+png_bytepp PNGAPI
+png_get_rows(png_structp png_ptr, png_infop info_ptr)
+{
+ if (png_ptr != NULL && info_ptr != NULL)
+ return(info_ptr->row_pointers);
+
+ else
+ return(0);
+}
+#endif
+
+#ifdef PNG_EASY_ACCESS_SUPPORTED
+/* Easy access to info, added in libpng-0.99 */
+png_uint_32 PNGAPI
+png_get_image_width(png_structp png_ptr, png_infop info_ptr)
+{
+ if (png_ptr != NULL && info_ptr != NULL)
+ return info_ptr->width;
+
+ return (0);
+}
+
+png_uint_32 PNGAPI
+png_get_image_height(png_structp png_ptr, png_infop info_ptr)
+{
+ if (png_ptr != NULL && info_ptr != NULL)
+ return info_ptr->height;
+
+ return (0);
+}
+
+png_byte PNGAPI
+png_get_bit_depth(png_structp png_ptr, png_infop info_ptr)
+{
+ if (png_ptr != NULL && info_ptr != NULL)
+ return info_ptr->bit_depth;
+
+ return (0);
+}
+
+png_byte PNGAPI
+png_get_color_type(png_structp png_ptr, png_infop info_ptr)
+{
+ if (png_ptr != NULL && info_ptr != NULL)
+ return info_ptr->color_type;
+
+ return (0);
+}
+
+png_byte PNGAPI
+png_get_filter_type(png_structp png_ptr, png_infop info_ptr)
+{
+ if (png_ptr != NULL && info_ptr != NULL)
+ return info_ptr->filter_type;
+
+ return (0);
+}
+
+png_byte PNGAPI
+png_get_interlace_type(png_structp png_ptr, png_infop info_ptr)
+{
+ if (png_ptr != NULL && info_ptr != NULL)
+ return info_ptr->interlace_type;
+
+ return (0);
+}
+
+png_byte PNGAPI
+png_get_compression_type(png_structp png_ptr, png_infop info_ptr)
+{
+ if (png_ptr != NULL && info_ptr != NULL)
+ return info_ptr->compression_type;
+
+ return (0);
+}
+
+png_uint_32 PNGAPI
+png_get_x_pixels_per_meter(png_structp png_ptr, png_infop info_ptr)
+{
+ if (png_ptr != NULL && info_ptr != NULL)
+#ifdef PNG_pHYs_SUPPORTED
+ if (info_ptr->valid & PNG_INFO_pHYs)
+ {
+ png_debug1(1, "in %s retrieval function", "png_get_x_pixels_per_meter");
+
+ if (info_ptr->phys_unit_type != PNG_RESOLUTION_METER)
+ return (0);
+
+ else
+ return (info_ptr->x_pixels_per_unit);
+ }
+#else
+ return (0);
+#endif
+ return (0);
+}
+
+png_uint_32 PNGAPI
+png_get_y_pixels_per_meter(png_structp png_ptr, png_infop info_ptr)
+{
+ if (png_ptr != NULL && info_ptr != NULL)
+#ifdef PNG_pHYs_SUPPORTED
+ if (info_ptr->valid & PNG_INFO_pHYs)
+ {
+ png_debug1(1, "in %s retrieval function", "png_get_y_pixels_per_meter");
+
+ if (info_ptr->phys_unit_type != PNG_RESOLUTION_METER)
+ return (0);
+
+ else
+ return (info_ptr->y_pixels_per_unit);
+ }
+#else
+ return (0);
+#endif
+ return (0);
+}
+
+png_uint_32 PNGAPI
+png_get_pixels_per_meter(png_structp png_ptr, png_infop info_ptr)
+{
+ if (png_ptr != NULL && info_ptr != NULL)
+#ifdef PNG_pHYs_SUPPORTED
+ if (info_ptr->valid & PNG_INFO_pHYs)
+ {
+ png_debug1(1, "in %s retrieval function", "png_get_pixels_per_meter");
+
+ if (info_ptr->phys_unit_type != PNG_RESOLUTION_METER ||
+ info_ptr->x_pixels_per_unit != info_ptr->y_pixels_per_unit)
+ return (0);
+
+ else
+ return (info_ptr->x_pixels_per_unit);
+ }
+#else
+ return (0);
+#endif
+ return (0);
+}
+
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+float PNGAPI
+png_get_pixel_aspect_ratio(png_structp png_ptr, png_infop info_ptr)
+ {
+ if (png_ptr != NULL && info_ptr != NULL)
+#ifdef PNG_pHYs_SUPPORTED
+
+ if (info_ptr->valid & PNG_INFO_pHYs)
+ {
+ png_debug1(1, "in %s retrieval function", "png_get_aspect_ratio");
+
+ if (info_ptr->x_pixels_per_unit == 0)
+ return ((float)0.0);
+
+ else
+ return ((float)((float)info_ptr->y_pixels_per_unit
+ /(float)info_ptr->x_pixels_per_unit));
+ }
+#else
+ return (0.0);
+#endif
+ return ((float)0.0);
+}
+#endif
+
+png_int_32 PNGAPI
+png_get_x_offset_microns(png_structp png_ptr, png_infop info_ptr)
+{
+ if (png_ptr != NULL && info_ptr != NULL)
+#ifdef PNG_oFFs_SUPPORTED
+
+ if (info_ptr->valid & PNG_INFO_oFFs)
+ {
+ png_debug1(1, "in %s retrieval function", "png_get_x_offset_microns");
+
+ if (info_ptr->offset_unit_type != PNG_OFFSET_MICROMETER)
+ return (0);
+
+ else
+ return (info_ptr->x_offset);
+ }
+#else
+ return (0);
+#endif
+ return (0);
+}
+
+png_int_32 PNGAPI
+png_get_y_offset_microns(png_structp png_ptr, png_infop info_ptr)
+{
+ if (png_ptr != NULL && info_ptr != NULL)
+
+#ifdef PNG_oFFs_SUPPORTED
+ if (info_ptr->valid & PNG_INFO_oFFs)
+ {
+ png_debug1(1, "in %s retrieval function", "png_get_y_offset_microns");
+
+ if (info_ptr->offset_unit_type != PNG_OFFSET_MICROMETER)
+ return (0);
+
+ else
+ return (info_ptr->y_offset);
+ }
+#else
+ return (0);
+#endif
+ return (0);
+}
+
+png_int_32 PNGAPI
+png_get_x_offset_pixels(png_structp png_ptr, png_infop info_ptr)
+{
+ if (png_ptr != NULL && info_ptr != NULL)
+
+#ifdef PNG_oFFs_SUPPORTED
+ if (info_ptr->valid & PNG_INFO_oFFs)
+ {
+ png_debug1(1, "in %s retrieval function", "png_get_x_offset_microns");
+
+ if (info_ptr->offset_unit_type != PNG_OFFSET_PIXEL)
+ return (0);
+
+ else
+ return (info_ptr->x_offset);
+ }
+#else
+ return (0);
+#endif
+ return (0);
+}
+
+png_int_32 PNGAPI
+png_get_y_offset_pixels(png_structp png_ptr, png_infop info_ptr)
+{
+ if (png_ptr != NULL && info_ptr != NULL)
+
+#ifdef PNG_oFFs_SUPPORTED
+ if (info_ptr->valid & PNG_INFO_oFFs)
+ {
+ png_debug1(1, "in %s retrieval function", "png_get_y_offset_microns");
+
+ if (info_ptr->offset_unit_type != PNG_OFFSET_PIXEL)
+ return (0);
+
+ else
+ return (info_ptr->y_offset);
+ }
+#else
+ return (0);
+#endif
+ return (0);
+}
+
+#if defined(PNG_INCH_CONVERSIONS) && defined(PNG_FLOATING_POINT_SUPPORTED)
+png_uint_32 PNGAPI
+png_get_pixels_per_inch(png_structp png_ptr, png_infop info_ptr)
+{
+ return ((png_uint_32)((float)png_get_pixels_per_meter(png_ptr, info_ptr)
+ *.0254 +.5));
+}
+
+png_uint_32 PNGAPI
+png_get_x_pixels_per_inch(png_structp png_ptr, png_infop info_ptr)
+{
+ return ((png_uint_32)((float)png_get_x_pixels_per_meter(png_ptr, info_ptr)
+ *.0254 +.5));
+}
+
+png_uint_32 PNGAPI
+png_get_y_pixels_per_inch(png_structp png_ptr, png_infop info_ptr)
+{
+ return ((png_uint_32)((float)png_get_y_pixels_per_meter(png_ptr, info_ptr)
+ *.0254 +.5));
+}
+
+float PNGAPI
+png_get_x_offset_inches(png_structp png_ptr, png_infop info_ptr)
+{
+ return ((float)png_get_x_offset_microns(png_ptr, info_ptr)
+ *.00003937);
+}
+
+float PNGAPI
+png_get_y_offset_inches(png_structp png_ptr, png_infop info_ptr)
+{
+ return ((float)png_get_y_offset_microns(png_ptr, info_ptr)
+ *.00003937);
+}
+
+#ifdef PNG_pHYs_SUPPORTED
+png_uint_32 PNGAPI
+png_get_pHYs_dpi(png_structp png_ptr, png_infop info_ptr,
+ png_uint_32 *res_x, png_uint_32 *res_y, int *unit_type)
+{
+ png_uint_32 retval = 0;
+
+ if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_pHYs))
+ {
+ png_debug1(1, "in %s retrieval function", "pHYs");
+
+ if (res_x != NULL)
+ {
+ *res_x = info_ptr->x_pixels_per_unit;
+ retval |= PNG_INFO_pHYs;
+ }
+ if (res_y != NULL)
+ {
+ *res_y = info_ptr->y_pixels_per_unit;
+ retval |= PNG_INFO_pHYs;
+ }
+ if (unit_type != NULL)
+ {
+ *unit_type = (int)info_ptr->phys_unit_type;
+ retval |= PNG_INFO_pHYs;
+ if (*unit_type == 1)
+ {
+ if (res_x != NULL) *res_x = (png_uint_32)(*res_x * .0254 + .50);
+ if (res_y != NULL) *res_y = (png_uint_32)(*res_y * .0254 + .50);
+ }
+ }
+ }
+ return (retval);
+}
+#endif /* PNG_pHYs_SUPPORTED */
+#endif /* PNG_INCH_CONVERSIONS && PNG_FLOATING_POINT_SUPPORTED */
+
+/* png_get_channels really belongs in here, too, but it's been around longer */
+
+#endif /* PNG_EASY_ACCESS_SUPPORTED */
+
+png_byte PNGAPI
+png_get_channels(png_structp png_ptr, png_infop info_ptr)
+{
+ if (png_ptr != NULL && info_ptr != NULL)
+ return(info_ptr->channels);
+ else
+ return (0);
+}
+
+png_bytep PNGAPI
+png_get_signature(png_structp png_ptr, png_infop info_ptr)
+{
+ if (png_ptr != NULL && info_ptr != NULL)
+ return(info_ptr->signature);
+ else
+ return (NULL);
+}
+
+#ifdef PNG_bKGD_SUPPORTED
+png_uint_32 PNGAPI
+png_get_bKGD(png_structp png_ptr, png_infop info_ptr,
+ png_color_16p *background)
+{
+ if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_bKGD)
+ && background != NULL)
+ {
+ png_debug1(1, "in %s retrieval function", "bKGD");
+
+ *background = &(info_ptr->background);
+ return (PNG_INFO_bKGD);
+ }
+ return (0);
+}
+#endif
+
+#ifdef PNG_cHRM_SUPPORTED
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+png_uint_32 PNGAPI
+png_get_cHRM(png_structp png_ptr, png_infop info_ptr,
+ double *white_x, double *white_y, double *red_x, double *red_y,
+ double *green_x, double *green_y, double *blue_x, double *blue_y)
+{
+ if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_cHRM))
+ {
+ png_debug1(1, "in %s retrieval function", "cHRM");
+
+ if (white_x != NULL)
+ *white_x = (double)info_ptr->x_white;
+ if (white_y != NULL)
+ *white_y = (double)info_ptr->y_white;
+ if (red_x != NULL)
+ *red_x = (double)info_ptr->x_red;
+ if (red_y != NULL)
+ *red_y = (double)info_ptr->y_red;
+ if (green_x != NULL)
+ *green_x = (double)info_ptr->x_green;
+ if (green_y != NULL)
+ *green_y = (double)info_ptr->y_green;
+ if (blue_x != NULL)
+ *blue_x = (double)info_ptr->x_blue;
+ if (blue_y != NULL)
+ *blue_y = (double)info_ptr->y_blue;
+ return (PNG_INFO_cHRM);
+ }
+ return (0);
+}
+#endif
+#ifdef PNG_FIXED_POINT_SUPPORTED
+png_uint_32 PNGAPI
+png_get_cHRM_fixed(png_structp png_ptr, png_infop info_ptr,
+ png_fixed_point *white_x, png_fixed_point *white_y, png_fixed_point *red_x,
+ png_fixed_point *red_y, png_fixed_point *green_x, png_fixed_point *green_y,
+ png_fixed_point *blue_x, png_fixed_point *blue_y)
+{
+ png_debug1(1, "in %s retrieval function", "cHRM");
+
+ if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_cHRM))
+ {
+ if (white_x != NULL)
+ *white_x = info_ptr->int_x_white;
+ if (white_y != NULL)
+ *white_y = info_ptr->int_y_white;
+ if (red_x != NULL)
+ *red_x = info_ptr->int_x_red;
+ if (red_y != NULL)
+ *red_y = info_ptr->int_y_red;
+ if (green_x != NULL)
+ *green_x = info_ptr->int_x_green;
+ if (green_y != NULL)
+ *green_y = info_ptr->int_y_green;
+ if (blue_x != NULL)
+ *blue_x = info_ptr->int_x_blue;
+ if (blue_y != NULL)
+ *blue_y = info_ptr->int_y_blue;
+ return (PNG_INFO_cHRM);
+ }
+ return (0);
+}
+#endif
+#endif
+
+#ifdef PNG_gAMA_SUPPORTED
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+png_uint_32 PNGAPI
+png_get_gAMA(png_structp png_ptr, png_infop info_ptr, double *file_gamma)
+{
+ png_debug1(1, "in %s retrieval function", "gAMA");
+
+ if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_gAMA)
+ && file_gamma != NULL)
+ {
+ *file_gamma = (double)info_ptr->gamma;
+ return (PNG_INFO_gAMA);
+ }
+ return (0);
+}
+#endif
+#ifdef PNG_FIXED_POINT_SUPPORTED
+png_uint_32 PNGAPI
+png_get_gAMA_fixed(png_structp png_ptr, png_infop info_ptr,
+ png_fixed_point *int_file_gamma)
+{
+ png_debug1(1, "in %s retrieval function", "gAMA");
+
+ if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_gAMA)
+ && int_file_gamma != NULL)
+ {
+ *int_file_gamma = info_ptr->int_gamma;
+ return (PNG_INFO_gAMA);
+ }
+ return (0);
+}
+#endif
+#endif
+
+#ifdef PNG_sRGB_SUPPORTED
+png_uint_32 PNGAPI
+png_get_sRGB(png_structp png_ptr, png_infop info_ptr, int *file_srgb_intent)
+{
+ png_debug1(1, "in %s retrieval function", "sRGB");
+
+ if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_sRGB)
+ && file_srgb_intent != NULL)
+ {
+ *file_srgb_intent = (int)info_ptr->srgb_intent;
+ return (PNG_INFO_sRGB);
+ }
+ return (0);
+}
+#endif
+
+#ifdef PNG_iCCP_SUPPORTED
+png_uint_32 PNGAPI
+png_get_iCCP(png_structp png_ptr, png_infop info_ptr,
+ png_charpp name, int *compression_type,
+ png_charpp profile, png_uint_32 *proflen)
+{
+ png_debug1(1, "in %s retrieval function", "iCCP");
+
+ if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_iCCP)
+ && name != NULL && profile != NULL && proflen != NULL)
+ {
+ *name = info_ptr->iccp_name;
+ *profile = info_ptr->iccp_profile;
+ /* Compression_type is a dummy so the API won't have to change
+ * if we introduce multiple compression types later.
+ */
+ *proflen = (int)info_ptr->iccp_proflen;
+ *compression_type = (int)info_ptr->iccp_compression;
+ return (PNG_INFO_iCCP);
+ }
+ return (0);
+}
+#endif
+
+#ifdef PNG_sPLT_SUPPORTED
+png_uint_32 PNGAPI
+png_get_sPLT(png_structp png_ptr, png_infop info_ptr,
+ png_sPLT_tpp spalettes)
+{
+ if (png_ptr != NULL && info_ptr != NULL && spalettes != NULL)
+ {
+ *spalettes = info_ptr->splt_palettes;
+ return ((png_uint_32)info_ptr->splt_palettes_num);
+ }
+ return (0);
+}
+#endif
+
+#ifdef PNG_hIST_SUPPORTED
+png_uint_32 PNGAPI
+png_get_hIST(png_structp png_ptr, png_infop info_ptr, png_uint_16p *hist)
+{
+ png_debug1(1, "in %s retrieval function", "hIST");
+
+ if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_hIST)
+ && hist != NULL)
+ {
+ *hist = info_ptr->hist;
+ return (PNG_INFO_hIST);
+ }
+ return (0);
+}
+#endif
+
+png_uint_32 PNGAPI
+png_get_IHDR(png_structp png_ptr, png_infop info_ptr,
+ png_uint_32 *width, png_uint_32 *height, int *bit_depth,
+ int *color_type, int *interlace_type, int *compression_type,
+ int *filter_type)
+
+{
+ png_debug1(1, "in %s retrieval function", "IHDR");
+
+ if (png_ptr == NULL || info_ptr == NULL || width == NULL ||
+ height == NULL || bit_depth == NULL || color_type == NULL)
+ return (0);
+
+ *width = info_ptr->width;
+ *height = info_ptr->height;
+ *bit_depth = info_ptr->bit_depth;
+ *color_type = info_ptr->color_type;
+
+ if (compression_type != NULL)
+ *compression_type = info_ptr->compression_type;
+
+ if (filter_type != NULL)
+ *filter_type = info_ptr->filter_type;
+
+ if (interlace_type != NULL)
+ *interlace_type = info_ptr->interlace_type;
+
+ /* This is redundant if we can be sure that the info_ptr values were all
+ * assigned in png_set_IHDR(). We do the check anyhow in case an
+ * application has ignored our advice not to mess with the members
+ * of info_ptr directly.
+ */
+ png_check_IHDR (png_ptr, info_ptr->width, info_ptr->height,
+ info_ptr->bit_depth, info_ptr->color_type, info_ptr->interlace_type,
+ info_ptr->compression_type, info_ptr->filter_type);
+
+ return (1);
+}
+
+#ifdef PNG_oFFs_SUPPORTED
+png_uint_32 PNGAPI
+png_get_oFFs(png_structp png_ptr, png_infop info_ptr,
+ png_int_32 *offset_x, png_int_32 *offset_y, int *unit_type)
+{
+ png_debug1(1, "in %s retrieval function", "oFFs");
+
+ if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_oFFs)
+ && offset_x != NULL && offset_y != NULL && unit_type != NULL)
+ {
+ *offset_x = info_ptr->x_offset;
+ *offset_y = info_ptr->y_offset;
+ *unit_type = (int)info_ptr->offset_unit_type;
+ return (PNG_INFO_oFFs);
+ }
+ return (0);
+}
+#endif
+
+#ifdef PNG_pCAL_SUPPORTED
+png_uint_32 PNGAPI
+png_get_pCAL(png_structp png_ptr, png_infop info_ptr,
+ png_charp *purpose, png_int_32 *X0, png_int_32 *X1, int *type, int *nparams,
+ png_charp *units, png_charpp *params)
+{
+ png_debug1(1, "in %s retrieval function", "pCAL");
+
+ if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_pCAL)
+ && purpose != NULL && X0 != NULL && X1 != NULL && type != NULL &&
+ nparams != NULL && units != NULL && params != NULL)
+ {
+ *purpose = info_ptr->pcal_purpose;
+ *X0 = info_ptr->pcal_X0;
+ *X1 = info_ptr->pcal_X1;
+ *type = (int)info_ptr->pcal_type;
+ *nparams = (int)info_ptr->pcal_nparams;
+ *units = info_ptr->pcal_units;
+ *params = info_ptr->pcal_params;
+ return (PNG_INFO_pCAL);
+ }
+ return (0);
+}
+#endif
+
+#ifdef PNG_sCAL_SUPPORTED
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+png_uint_32 PNGAPI
+png_get_sCAL(png_structp png_ptr, png_infop info_ptr,
+ int *unit, double *width, double *height)
+{
+ if (png_ptr != NULL && info_ptr != NULL &&
+ (info_ptr->valid & PNG_INFO_sCAL))
+ {
+ *unit = info_ptr->scal_unit;
+ *width = info_ptr->scal_pixel_width;
+ *height = info_ptr->scal_pixel_height;
+ return (PNG_INFO_sCAL);
+ }
+ return(0);
+}
+#else
+#ifdef PNG_FIXED_POINT_SUPPORTED
+png_uint_32 PNGAPI
+png_get_sCAL_s(png_structp png_ptr, png_infop info_ptr,
+ int *unit, png_charpp width, png_charpp height)
+{
+ if (png_ptr != NULL && info_ptr != NULL &&
+ (info_ptr->valid & PNG_INFO_sCAL))
+ {
+ *unit = info_ptr->scal_unit;
+ *width = info_ptr->scal_s_width;
+ *height = info_ptr->scal_s_height;
+ return (PNG_INFO_sCAL);
+ }
+ return(0);
+}
+#endif
+#endif
+#endif
+
+#ifdef PNG_pHYs_SUPPORTED
+png_uint_32 PNGAPI
+png_get_pHYs(png_structp png_ptr, png_infop info_ptr,
+ png_uint_32 *res_x, png_uint_32 *res_y, int *unit_type)
+{
+ png_uint_32 retval = 0;
+
+ png_debug1(1, "in %s retrieval function", "pHYs");
+
+ if (png_ptr != NULL && info_ptr != NULL &&
+ (info_ptr->valid & PNG_INFO_pHYs))
+ {
+ if (res_x != NULL)
+ {
+ *res_x = info_ptr->x_pixels_per_unit;
+ retval |= PNG_INFO_pHYs;
+ }
+
+ if (res_y != NULL)
+ {
+ *res_y = info_ptr->y_pixels_per_unit;
+ retval |= PNG_INFO_pHYs;
+ }
+
+ if (unit_type != NULL)
+ {
+ *unit_type = (int)info_ptr->phys_unit_type;
+ retval |= PNG_INFO_pHYs;
+ }
+ }
+ return (retval);
+}
+#endif
+
+png_uint_32 PNGAPI
+png_get_PLTE(png_structp png_ptr, png_infop info_ptr, png_colorp *palette,
+ int *num_palette)
+{
+ png_debug1(1, "in %s retrieval function", "PLTE");
+
+ if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_PLTE)
+ && palette != NULL)
+ {
+ *palette = info_ptr->palette;
+ *num_palette = info_ptr->num_palette;
+ png_debug1(3, "num_palette = %d", *num_palette);
+ return (PNG_INFO_PLTE);
+ }
+ return (0);
+}
+
+#ifdef PNG_sBIT_SUPPORTED
+png_uint_32 PNGAPI
+png_get_sBIT(png_structp png_ptr, png_infop info_ptr, png_color_8p *sig_bit)
+{
+ png_debug1(1, "in %s retrieval function", "sBIT");
+
+ if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_sBIT)
+ && sig_bit != NULL)
+ {
+ *sig_bit = &(info_ptr->sig_bit);
+ return (PNG_INFO_sBIT);
+ }
+ return (0);
+}
+#endif
+
+#ifdef PNG_TEXT_SUPPORTED
+png_uint_32 PNGAPI
+png_get_text(png_structp png_ptr, png_infop info_ptr, png_textp *text_ptr,
+ int *num_text)
+{
+ if (png_ptr != NULL && info_ptr != NULL && info_ptr->num_text > 0)
+ {
+ png_debug1(1, "in %s retrieval function",
+ (png_ptr->chunk_name[0] == '\0' ? "text"
+ : (png_const_charp)png_ptr->chunk_name));
+
+ if (text_ptr != NULL)
+ *text_ptr = info_ptr->text;
+
+ if (num_text != NULL)
+ *num_text = info_ptr->num_text;
+
+ return ((png_uint_32)info_ptr->num_text);
+ }
+ if (num_text != NULL)
+ *num_text = 0;
+ return(0);
+}
+#endif
+
+#ifdef PNG_tIME_SUPPORTED
+png_uint_32 PNGAPI
+png_get_tIME(png_structp png_ptr, png_infop info_ptr, png_timep *mod_time)
+{
+ png_debug1(1, "in %s retrieval function", "tIME");
+
+ if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_tIME)
+ && mod_time != NULL)
+ {
+ *mod_time = &(info_ptr->mod_time);
+ return (PNG_INFO_tIME);
+ }
+ return (0);
+}
+#endif
+
+#ifdef PNG_tRNS_SUPPORTED
+png_uint_32 PNGAPI
+png_get_tRNS(png_structp png_ptr, png_infop info_ptr,
+ png_bytep *trans, int *num_trans, png_color_16p *trans_values)
+{
+ png_uint_32 retval = 0;
+ if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_tRNS))
+ {
+ png_debug1(1, "in %s retrieval function", "tRNS");
+
+ if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE)
+ {
+ if (trans != NULL)
+ {
+ *trans = info_ptr->trans;
+ retval |= PNG_INFO_tRNS;
+ }
+
+ if (trans_values != NULL)
+ *trans_values = &(info_ptr->trans_values);
+ }
+ else /* if (info_ptr->color_type != PNG_COLOR_TYPE_PALETTE) */
+ {
+ if (trans_values != NULL)
+ {
+ *trans_values = &(info_ptr->trans_values);
+ retval |= PNG_INFO_tRNS;
+ }
+
+ if (trans != NULL)
+ *trans = NULL;
+ }
+ if (num_trans != NULL)
+ {
+ *num_trans = info_ptr->num_trans;
+ retval |= PNG_INFO_tRNS;
+ }
+ }
+ return (retval);
+}
+#endif
+
+#ifdef PNG_UNKNOWN_CHUNKS_SUPPORTED
+png_uint_32 PNGAPI
+png_get_unknown_chunks(png_structp png_ptr, png_infop info_ptr,
+ png_unknown_chunkpp unknowns)
+{
+ if (png_ptr != NULL && info_ptr != NULL && unknowns != NULL)
+ {
+ *unknowns = info_ptr->unknown_chunks;
+ return ((png_uint_32)info_ptr->unknown_chunks_num);
+ }
+ return (0);
+}
+#endif
+
+#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED
+png_byte PNGAPI
+png_get_rgb_to_gray_status (png_structp png_ptr)
+{
+ return (png_byte)(png_ptr? png_ptr->rgb_to_gray_status : 0);
+}
+#endif
+
+#ifdef PNG_USER_CHUNKS_SUPPORTED
+png_voidp PNGAPI
+png_get_user_chunk_ptr(png_structp png_ptr)
+{
+ return (png_ptr? png_ptr->user_chunk_ptr : NULL);
+}
+#endif
+
+png_uint_32 PNGAPI
+png_get_compression_buffer_size(png_structp png_ptr)
+{
+ return (png_uint_32)(png_ptr? png_ptr->zbuf_size : 0L);
+}
+
+#ifdef PNG_ASSEMBLER_CODE_SUPPORTED
+#ifndef PNG_1_0_X
+/* This function was added to libpng 1.2.0 and should exist by default */
+png_uint_32 PNGAPI
+png_get_asm_flags (png_structp png_ptr)
+{
+ /* Obsolete, to be removed from libpng-1.4.0 */
+ return (png_ptr? 0L: 0L);
+}
+
+/* This function was added to libpng 1.2.0 and should exist by default */
+png_uint_32 PNGAPI
+png_get_asm_flagmask (int flag_select)
+{
+ /* Obsolete, to be removed from libpng-1.4.0 */
+ flag_select=flag_select;
+ return 0L;
+}
+
+ /* GRR: could add this: && defined(PNG_MMX_CODE_SUPPORTED) */
+/* This function was added to libpng 1.2.0 */
+png_uint_32 PNGAPI
+png_get_mmx_flagmask (int flag_select, int *compilerID)
+{
+ /* Obsolete, to be removed from libpng-1.4.0 */
+ flag_select=flag_select;
+ *compilerID = -1; /* unknown (i.e., no asm/MMX code compiled) */
+ return 0L;
+}
+
+/* This function was added to libpng 1.2.0 */
+png_byte PNGAPI
+png_get_mmx_bitdepth_threshold (png_structp png_ptr)
+{
+ /* Obsolete, to be removed from libpng-1.4.0 */
+ return (png_ptr? 0: 0);
+}
+
+/* This function was added to libpng 1.2.0 */
+png_uint_32 PNGAPI
+png_get_mmx_rowbytes_threshold (png_structp png_ptr)
+{
+ /* Obsolete, to be removed from libpng-1.4.0 */
+ return (png_ptr? 0L: 0L);
+}
+#endif /* ?PNG_1_0_X */
+#endif /* ?PNG_ASSEMBLER_CODE_SUPPORTED */
+
+#ifdef PNG_SET_USER_LIMITS_SUPPORTED
+/* These functions were added to libpng 1.2.6 but not enabled
+* by default. They will be enabled in libpng-1.4.0 */
+png_uint_32 PNGAPI
+png_get_user_width_max (png_structp png_ptr)
+{
+ return (png_ptr? png_ptr->user_width_max : 0);
+}
+png_uint_32 PNGAPI
+png_get_user_height_max (png_structp png_ptr)
+{
+ return (png_ptr? png_ptr->user_height_max : 0);
+}
+#endif /* ?PNG_SET_USER_LIMITS_SUPPORTED */
+
+#endif /* PNG_READ_SUPPORTED || PNG_WRITE_SUPPORTED */
diff --git a/trunk/src/third_party/libpng/pngmem.c b/trunk/src/third_party/libpng/pngmem.c
new file mode 100644
index 0000000..91f2765
--- /dev/null
+++ b/trunk/src/third_party/libpng/pngmem.c
@@ -0,0 +1,641 @@
+
+/* pngmem.c - stub functions for memory allocation
+ *
+ * Last changed in libpng 1.2.41 [February 25, 2010]
+ * Copyright (c) 1998-2010 Glenn Randers-Pehrson
+ * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger)
+ * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.)
+ *
+ * This code is released under the libpng license.
+ * For conditions of distribution and use, see the disclaimer
+ * and license in png.h
+ *
+ * This file provides a location for all memory allocation. Users who
+ * need special memory handling are expected to supply replacement
+ * functions for png_malloc() and png_free(), and to use
+ * png_create_read_struct_2() and png_create_write_struct_2() to
+ * identify the replacement functions.
+ */
+
+#define PNG_INTERNAL
+#define PNG_NO_PEDANTIC_WARNINGS
+#include "png.h"
+#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED)
+
+/* Borland DOS special memory handler */
+#if defined(__TURBOC__) && !defined(_Windows) && !defined(__FLAT__)
+/* If you change this, be sure to change the one in png.h also */
+
+/* Allocate memory for a png_struct. The malloc and memset can be replaced
+ by a single call to calloc() if this is thought to improve performance. */
+png_voidp /* PRIVATE */
+png_create_struct(int type)
+{
+#ifdef PNG_USER_MEM_SUPPORTED
+ return (png_create_struct_2(type, png_malloc_ptr_NULL, png_voidp_NULL));
+}
+
+/* Alternate version of png_create_struct, for use with user-defined malloc. */
+png_voidp /* PRIVATE */
+png_create_struct_2(int type, png_malloc_ptr malloc_fn, png_voidp mem_ptr)
+{
+#endif /* PNG_USER_MEM_SUPPORTED */
+ png_size_t size;
+ png_voidp struct_ptr;
+
+ if (type == PNG_STRUCT_INFO)
+ size = png_sizeof(png_info);
+ else if (type == PNG_STRUCT_PNG)
+ size = png_sizeof(png_struct);
+ else
+ return (png_get_copyright(NULL));
+
+#ifdef PNG_USER_MEM_SUPPORTED
+ if (malloc_fn != NULL)
+ {
+ png_struct dummy_struct;
+ png_structp png_ptr = &dummy_struct;
+ png_ptr->mem_ptr=mem_ptr;
+ struct_ptr = (*(malloc_fn))(png_ptr, (png_uint_32)size);
+ }
+ else
+#endif /* PNG_USER_MEM_SUPPORTED */
+ struct_ptr = (png_voidp)farmalloc(size);
+ if (struct_ptr != NULL)
+ png_memset(struct_ptr, 0, size);
+ return (struct_ptr);
+}
+
+/* Free memory allocated by a png_create_struct() call */
+void /* PRIVATE */
+png_destroy_struct(png_voidp struct_ptr)
+{
+#ifdef PNG_USER_MEM_SUPPORTED
+ png_destroy_struct_2(struct_ptr, png_free_ptr_NULL, png_voidp_NULL);
+}
+
+/* Free memory allocated by a png_create_struct() call */
+void /* PRIVATE */
+png_destroy_struct_2(png_voidp struct_ptr, png_free_ptr free_fn,
+ png_voidp mem_ptr)
+{
+#endif
+ if (struct_ptr != NULL)
+ {
+#ifdef PNG_USER_MEM_SUPPORTED
+ if (free_fn != NULL)
+ {
+ png_struct dummy_struct;
+ png_structp png_ptr = &dummy_struct;
+ png_ptr->mem_ptr=mem_ptr;
+ (*(free_fn))(png_ptr, struct_ptr);
+ return;
+ }
+#endif /* PNG_USER_MEM_SUPPORTED */
+ farfree (struct_ptr);
+ }
+}
+
+/* Allocate memory. For reasonable files, size should never exceed
+ * 64K. However, zlib may allocate more then 64K if you don't tell
+ * it not to. See zconf.h and png.h for more information. zlib does
+ * need to allocate exactly 64K, so whatever you call here must
+ * have the ability to do that.
+ *
+ * Borland seems to have a problem in DOS mode for exactly 64K.
+ * It gives you a segment with an offset of 8 (perhaps to store its
+ * memory stuff). zlib doesn't like this at all, so we have to
+ * detect and deal with it. This code should not be needed in
+ * Windows or OS/2 modes, and only in 16 bit mode. This code has
+ * been updated by Alexander Lehmann for version 0.89 to waste less
+ * memory.
+ *
+ * Note that we can't use png_size_t for the "size" declaration,
+ * since on some systems a png_size_t is a 16-bit quantity, and as a
+ * result, we would be truncating potentially larger memory requests
+ * (which should cause a fatal error) and introducing major problems.
+ */
+png_voidp /* PRIVATE */
+png_calloc(png_structp png_ptr, png_uint_32 size)
+{
+ png_voidp ret;
+
+ ret = (png_malloc(png_ptr, size));
+ if (ret != NULL)
+ png_memset(ret,0,(png_size_t)size);
+ return (ret);
+}
+
+png_voidp PNGAPI
+png_malloc(png_structp png_ptr, png_uint_32 size)
+{
+ png_voidp ret;
+
+ if (png_ptr == NULL || size == 0)
+ return (NULL);
+
+#ifdef PNG_USER_MEM_SUPPORTED
+ if (png_ptr->malloc_fn != NULL)
+ ret = ((png_voidp)(*(png_ptr->malloc_fn))(png_ptr, (png_size_t)size));
+ else
+ ret = (png_malloc_default(png_ptr, size));
+ if (ret == NULL && (png_ptr->flags&PNG_FLAG_MALLOC_NULL_MEM_OK) == 0)
+ png_error(png_ptr, "Out of memory!");
+ return (ret);
+}
+
+png_voidp PNGAPI
+png_malloc_default(png_structp png_ptr, png_uint_32 size)
+{
+ png_voidp ret;
+#endif /* PNG_USER_MEM_SUPPORTED */
+
+ if (png_ptr == NULL || size == 0)
+ return (NULL);
+
+#ifdef PNG_MAX_MALLOC_64K
+ if (size > (png_uint_32)65536L)
+ {
+ png_warning(png_ptr, "Cannot Allocate > 64K");
+ ret = NULL;
+ }
+ else
+#endif
+
+ if (size != (size_t)size)
+ ret = NULL;
+ else if (size == (png_uint_32)65536L)
+ {
+ if (png_ptr->offset_table == NULL)
+ {
+ /* Try to see if we need to do any of this fancy stuff */
+ ret = farmalloc(size);
+ if (ret == NULL || ((png_size_t)ret & 0xffff))
+ {
+ int num_blocks;
+ png_uint_32 total_size;
+ png_bytep table;
+ int i;
+ png_byte huge * hptr;
+
+ if (ret != NULL)
+ {
+ farfree(ret);
+ ret = NULL;
+ }
+
+ if (png_ptr->zlib_window_bits > 14)
+ num_blocks = (int)(1 << (png_ptr->zlib_window_bits - 14));
+ else
+ num_blocks = 1;
+ if (png_ptr->zlib_mem_level >= 7)
+ num_blocks += (int)(1 << (png_ptr->zlib_mem_level - 7));
+ else
+ num_blocks++;
+
+ total_size = ((png_uint_32)65536L) * (png_uint_32)num_blocks+16;
+
+ table = farmalloc(total_size);
+
+ if (table == NULL)
+ {
+#ifndef PNG_USER_MEM_SUPPORTED
+ if ((png_ptr->flags&PNG_FLAG_MALLOC_NULL_MEM_OK) == 0)
+ png_error(png_ptr, "Out Of Memory."); /* Note "O", "M" */
+ else
+ png_warning(png_ptr, "Out Of Memory.");
+#endif
+ return (NULL);
+ }
+
+ if ((png_size_t)table & 0xfff0)
+ {
+#ifndef PNG_USER_MEM_SUPPORTED
+ if ((png_ptr->flags&PNG_FLAG_MALLOC_NULL_MEM_OK) == 0)
+ png_error(png_ptr,
+ "Farmalloc didn't return normalized pointer");
+ else
+ png_warning(png_ptr,
+ "Farmalloc didn't return normalized pointer");
+#endif
+ return (NULL);
+ }
+
+ png_ptr->offset_table = table;
+ png_ptr->offset_table_ptr = farmalloc(num_blocks *
+ png_sizeof(png_bytep));
+
+ if (png_ptr->offset_table_ptr == NULL)
+ {
+#ifndef PNG_USER_MEM_SUPPORTED
+ if ((png_ptr->flags&PNG_FLAG_MALLOC_NULL_MEM_OK) == 0)
+ png_error(png_ptr, "Out Of memory."); /* Note "O", "m" */
+ else
+ png_warning(png_ptr, "Out Of memory.");
+#endif
+ return (NULL);
+ }
+
+ hptr = (png_byte huge *)table;
+ if ((png_size_t)hptr & 0xf)
+ {
+ hptr = (png_byte huge *)((long)(hptr) & 0xfffffff0L);
+ hptr = hptr + 16L; /* "hptr += 16L" fails on Turbo C++ 3.0 */
+ }
+ for (i = 0; i < num_blocks; i++)
+ {
+ png_ptr->offset_table_ptr[i] = (png_bytep)hptr;
+ hptr = hptr + (png_uint_32)65536L; /* "+=" fails on TC++3.0 */
+ }
+
+ png_ptr->offset_table_number = num_blocks;
+ png_ptr->offset_table_count = 0;
+ png_ptr->offset_table_count_free = 0;
+ }
+ }
+
+ if (png_ptr->offset_table_count >= png_ptr->offset_table_number)
+ {
+#ifndef PNG_USER_MEM_SUPPORTED
+ if ((png_ptr->flags&PNG_FLAG_MALLOC_NULL_MEM_OK) == 0)
+ png_error(png_ptr, "Out of Memory."); /* Note "o" and "M" */
+ else
+ png_warning(png_ptr, "Out of Memory.");
+#endif
+ return (NULL);
+ }
+
+ ret = png_ptr->offset_table_ptr[png_ptr->offset_table_count++];
+ }
+ else
+ ret = farmalloc(size);
+
+#ifndef PNG_USER_MEM_SUPPORTED
+ if (ret == NULL)
+ {
+ if ((png_ptr->flags&PNG_FLAG_MALLOC_NULL_MEM_OK) == 0)
+ png_error(png_ptr, "Out of memory."); /* Note "o" and "m" */
+ else
+ png_warning(png_ptr, "Out of memory."); /* Note "o" and "m" */
+ }
+#endif
+
+ return (ret);
+}
+
+/* Free a pointer allocated by png_malloc(). In the default
+ * configuration, png_ptr is not used, but is passed in case it
+ * is needed. If ptr is NULL, return without taking any action.
+ */
+void PNGAPI
+png_free(png_structp png_ptr, png_voidp ptr)
+{
+ if (png_ptr == NULL || ptr == NULL)
+ return;
+
+#ifdef PNG_USER_MEM_SUPPORTED
+ if (png_ptr->free_fn != NULL)
+ {
+ (*(png_ptr->free_fn))(png_ptr, ptr);
+ return;
+ }
+ else
+ png_free_default(png_ptr, ptr);
+}
+
+void PNGAPI
+png_free_default(png_structp png_ptr, png_voidp ptr)
+{
+#endif /* PNG_USER_MEM_SUPPORTED */
+
+ if (png_ptr == NULL || ptr == NULL)
+ return;
+
+ if (png_ptr->offset_table != NULL)
+ {
+ int i;
+
+ for (i = 0; i < png_ptr->offset_table_count; i++)
+ {
+ if (ptr == png_ptr->offset_table_ptr[i])
+ {
+ ptr = NULL;
+ png_ptr->offset_table_count_free++;
+ break;
+ }
+ }
+ if (png_ptr->offset_table_count_free == png_ptr->offset_table_count)
+ {
+ farfree(png_ptr->offset_table);
+ farfree(png_ptr->offset_table_ptr);
+ png_ptr->offset_table = NULL;
+ png_ptr->offset_table_ptr = NULL;
+ }
+ }
+
+ if (ptr != NULL)
+ {
+ farfree(ptr);
+ }
+}
+
+#else /* Not the Borland DOS special memory handler */
+
+/* Allocate memory for a png_struct or a png_info. The malloc and
+ memset can be replaced by a single call to calloc() if this is thought
+ to improve performance noticably. */
+png_voidp /* PRIVATE */
+png_create_struct(int type)
+{
+#ifdef PNG_USER_MEM_SUPPORTED
+ return (png_create_struct_2(type, png_malloc_ptr_NULL, png_voidp_NULL));
+}
+
+/* Allocate memory for a png_struct or a png_info. The malloc and
+ memset can be replaced by a single call to calloc() if this is thought
+ to improve performance noticably. */
+png_voidp /* PRIVATE */
+png_create_struct_2(int type, png_malloc_ptr malloc_fn, png_voidp mem_ptr)
+{
+#endif /* PNG_USER_MEM_SUPPORTED */
+ png_size_t size;
+ png_voidp struct_ptr;
+
+ if (type == PNG_STRUCT_INFO)
+ size = png_sizeof(png_info);
+ else if (type == PNG_STRUCT_PNG)
+ size = png_sizeof(png_struct);
+ else
+ return (NULL);
+
+#ifdef PNG_USER_MEM_SUPPORTED
+ if (malloc_fn != NULL)
+ {
+ png_struct dummy_struct;
+ png_structp png_ptr = &dummy_struct;
+ png_ptr->mem_ptr=mem_ptr;
+ struct_ptr = (*(malloc_fn))(png_ptr, size);
+ if (struct_ptr != NULL)
+ png_memset(struct_ptr, 0, size);
+ return (struct_ptr);
+ }
+#endif /* PNG_USER_MEM_SUPPORTED */
+
+#if defined(__TURBOC__) && !defined(__FLAT__)
+ struct_ptr = (png_voidp)farmalloc(size);
+#else
+# if defined(_MSC_VER) && defined(MAXSEG_64K)
+ struct_ptr = (png_voidp)halloc(size, 1);
+# else
+ struct_ptr = (png_voidp)malloc(size);
+# endif
+#endif
+ if (struct_ptr != NULL)
+ png_memset(struct_ptr, 0, size);
+
+ return (struct_ptr);
+}
+
+
+/* Free memory allocated by a png_create_struct() call */
+void /* PRIVATE */
+png_destroy_struct(png_voidp struct_ptr)
+{
+#ifdef PNG_USER_MEM_SUPPORTED
+ png_destroy_struct_2(struct_ptr, png_free_ptr_NULL, png_voidp_NULL);
+}
+
+/* Free memory allocated by a png_create_struct() call */
+void /* PRIVATE */
+png_destroy_struct_2(png_voidp struct_ptr, png_free_ptr free_fn,
+ png_voidp mem_ptr)
+{
+#endif /* PNG_USER_MEM_SUPPORTED */
+ if (struct_ptr != NULL)
+ {
+#ifdef PNG_USER_MEM_SUPPORTED
+ if (free_fn != NULL)
+ {
+ png_struct dummy_struct;
+ png_structp png_ptr = &dummy_struct;
+ png_ptr->mem_ptr=mem_ptr;
+ (*(free_fn))(png_ptr, struct_ptr);
+ return;
+ }
+#endif /* PNG_USER_MEM_SUPPORTED */
+#if defined(__TURBOC__) && !defined(__FLAT__)
+ farfree(struct_ptr);
+#else
+# if defined(_MSC_VER) && defined(MAXSEG_64K)
+ hfree(struct_ptr);
+# else
+ free(struct_ptr);
+# endif
+#endif
+ }
+}
+
+/* Allocate memory. For reasonable files, size should never exceed
+ * 64K. However, zlib may allocate more then 64K if you don't tell
+ * it not to. See zconf.h and png.h for more information. zlib does
+ * need to allocate exactly 64K, so whatever you call here must
+ * have the ability to do that.
+ */
+
+png_voidp PNGAPI
+png_calloc(png_structp png_ptr, png_uint_32 size)
+{
+ png_voidp ret;
+
+ ret = (png_malloc(png_ptr, size));
+ if (ret != NULL)
+ png_memset(ret,0,(png_size_t)size);
+ return (ret);
+}
+
+png_voidp PNGAPI
+png_malloc(png_structp png_ptr, png_uint_32 size)
+{
+ png_voidp ret;
+
+#ifdef PNG_USER_MEM_SUPPORTED
+ if (png_ptr == NULL || size == 0)
+ return (NULL);
+
+ if (png_ptr->malloc_fn != NULL)
+ ret = ((png_voidp)(*(png_ptr->malloc_fn))(png_ptr, (png_size_t)size));
+ else
+ ret = (png_malloc_default(png_ptr, size));
+ if (ret == NULL && (png_ptr->flags&PNG_FLAG_MALLOC_NULL_MEM_OK) == 0)
+ png_error(png_ptr, "Out of Memory!");
+ return (ret);
+}
+
+png_voidp PNGAPI
+png_malloc_default(png_structp png_ptr, png_uint_32 size)
+{
+ png_voidp ret;
+#endif /* PNG_USER_MEM_SUPPORTED */
+
+ if (png_ptr == NULL || size == 0)
+ return (NULL);
+
+#ifdef PNG_MAX_MALLOC_64K
+ if (size > (png_uint_32)65536L)
+ {
+#ifndef PNG_USER_MEM_SUPPORTED
+ if ((png_ptr->flags&PNG_FLAG_MALLOC_NULL_MEM_OK) == 0)
+ png_error(png_ptr, "Cannot Allocate > 64K");
+ else
+#endif
+ return NULL;
+ }
+#endif
+
+ /* Check for overflow */
+#if defined(__TURBOC__) && !defined(__FLAT__)
+ if (size != (unsigned long)size)
+ ret = NULL;
+ else
+ ret = farmalloc(size);
+#else
+# if defined(_MSC_VER) && defined(MAXSEG_64K)
+ if (size != (unsigned long)size)
+ ret = NULL;
+ else
+ ret = halloc(size, 1);
+# else
+ if (size != (size_t)size)
+ ret = NULL;
+ else
+ ret = malloc((size_t)size);
+# endif
+#endif
+
+#ifndef PNG_USER_MEM_SUPPORTED
+ if (ret == NULL && (png_ptr->flags&PNG_FLAG_MALLOC_NULL_MEM_OK) == 0)
+ png_error(png_ptr, "Out of Memory");
+#endif
+
+ return (ret);
+}
+
+/* Free a pointer allocated by png_malloc(). If ptr is NULL, return
+ * without taking any action.
+ */
+void PNGAPI
+png_free(png_structp png_ptr, png_voidp ptr)
+{
+ if (png_ptr == NULL || ptr == NULL)
+ return;
+
+#ifdef PNG_USER_MEM_SUPPORTED
+ if (png_ptr->free_fn != NULL)
+ {
+ (*(png_ptr->free_fn))(png_ptr, ptr);
+ return;
+ }
+ else
+ png_free_default(png_ptr, ptr);
+}
+void PNGAPI
+png_free_default(png_structp png_ptr, png_voidp ptr)
+{
+ if (png_ptr == NULL || ptr == NULL)
+ return;
+
+#endif /* PNG_USER_MEM_SUPPORTED */
+
+#if defined(__TURBOC__) && !defined(__FLAT__)
+ farfree(ptr);
+#else
+# if defined(_MSC_VER) && defined(MAXSEG_64K)
+ hfree(ptr);
+# else
+ free(ptr);
+# endif
+#endif
+}
+
+#endif /* Not Borland DOS special memory handler */
+
+#ifdef PNG_1_0_X
+# define png_malloc_warn png_malloc
+#else
+/* This function was added at libpng version 1.2.3. The png_malloc_warn()
+ * function will set up png_malloc() to issue a png_warning and return NULL
+ * instead of issuing a png_error, if it fails to allocate the requested
+ * memory.
+ */
+png_voidp PNGAPI
+png_malloc_warn(png_structp png_ptr, png_uint_32 size)
+{
+ png_voidp ptr;
+ png_uint_32 save_flags;
+ if (png_ptr == NULL)
+ return (NULL);
+
+ save_flags = png_ptr->flags;
+ png_ptr->flags|=PNG_FLAG_MALLOC_NULL_MEM_OK;
+ ptr = (png_voidp)png_malloc((png_structp)png_ptr, size);
+ png_ptr->flags=save_flags;
+ return(ptr);
+}
+#endif
+
+png_voidp PNGAPI
+png_memcpy_check (png_structp png_ptr, png_voidp s1, png_voidp s2,
+ png_uint_32 length)
+{
+ png_size_t size;
+
+ size = (png_size_t)length;
+ if ((png_uint_32)size != length)
+ png_error(png_ptr, "Overflow in png_memcpy_check.");
+
+ return(png_memcpy (s1, s2, size));
+}
+
+png_voidp PNGAPI
+png_memset_check (png_structp png_ptr, png_voidp s1, int value,
+ png_uint_32 length)
+{
+ png_size_t size;
+
+ size = (png_size_t)length;
+ if ((png_uint_32)size != length)
+ png_error(png_ptr, "Overflow in png_memset_check.");
+
+ return (png_memset (s1, value, size));
+
+}
+
+#ifdef PNG_USER_MEM_SUPPORTED
+/* This function is called when the application wants to use another method
+ * of allocating and freeing memory.
+ */
+void PNGAPI
+png_set_mem_fn(png_structp png_ptr, png_voidp mem_ptr, png_malloc_ptr
+ malloc_fn, png_free_ptr free_fn)
+{
+ if (png_ptr != NULL)
+ {
+ png_ptr->mem_ptr = mem_ptr;
+ png_ptr->malloc_fn = malloc_fn;
+ png_ptr->free_fn = free_fn;
+ }
+}
+
+/* This function returns a pointer to the mem_ptr associated with the user
+ * functions. The application should free any memory associated with this
+ * pointer before png_write_destroy and png_read_destroy are called.
+ */
+png_voidp PNGAPI
+png_get_mem_ptr(png_structp png_ptr)
+{
+ if (png_ptr == NULL)
+ return (NULL);
+ return ((png_voidp)png_ptr->mem_ptr);
+}
+#endif /* PNG_USER_MEM_SUPPORTED */
+#endif /* PNG_READ_SUPPORTED || PNG_WRITE_SUPPORTED */
diff --git a/trunk/src/third_party/libpng/pngpread.c b/trunk/src/third_party/libpng/pngpread.c
new file mode 100644
index 0000000..d066944
--- /dev/null
+++ b/trunk/src/third_party/libpng/pngpread.c
@@ -0,0 +1,1774 @@
+
+/* pngpread.c - read a png file in push mode
+ *
+ * Last changed in libpng 1.2.44 [June 26, 2010]
+ * Copyright (c) 1998-2010 Glenn Randers-Pehrson
+ * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger)
+ * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.)
+ *
+ * This code is released under the libpng license.
+ * For conditions of distribution and use, see the disclaimer
+ * and license in png.h
+ */
+
+#define PNG_INTERNAL
+#define PNG_NO_PEDANTIC_WARNINGS
+#include "png.h"
+#ifdef PNG_PROGRESSIVE_READ_SUPPORTED
+
+/* Push model modes */
+#define PNG_READ_SIG_MODE 0
+#define PNG_READ_CHUNK_MODE 1
+#define PNG_READ_IDAT_MODE 2
+#define PNG_SKIP_MODE 3
+#define PNG_READ_tEXt_MODE 4
+#define PNG_READ_zTXt_MODE 5
+#define PNG_READ_DONE_MODE 6
+#define PNG_READ_iTXt_MODE 7
+#define PNG_ERROR_MODE 8
+
+void PNGAPI
+png_process_data(png_structp png_ptr, png_infop info_ptr,
+ png_bytep buffer, png_size_t buffer_size)
+{
+ if (png_ptr == NULL || info_ptr == NULL)
+ return;
+
+ png_push_restore_buffer(png_ptr, buffer, buffer_size);
+
+ while (png_ptr->buffer_size)
+ {
+ png_process_some_data(png_ptr, info_ptr);
+ }
+}
+
+/* What we do with the incoming data depends on what we were previously
+ * doing before we ran out of data...
+ */
+void /* PRIVATE */
+png_process_some_data(png_structp png_ptr, png_infop info_ptr)
+{
+ if (png_ptr == NULL)
+ return;
+
+ switch (png_ptr->process_mode)
+ {
+ case PNG_READ_SIG_MODE:
+ {
+ png_push_read_sig(png_ptr, info_ptr);
+ break;
+ }
+
+ case PNG_READ_CHUNK_MODE:
+ {
+ png_push_read_chunk(png_ptr, info_ptr);
+ break;
+ }
+
+ case PNG_READ_IDAT_MODE:
+ {
+ png_push_read_IDAT(png_ptr);
+ break;
+ }
+
+#ifdef PNG_READ_tEXt_SUPPORTED
+ case PNG_READ_tEXt_MODE:
+ {
+ png_push_read_tEXt(png_ptr, info_ptr);
+ break;
+ }
+
+#endif
+#ifdef PNG_READ_zTXt_SUPPORTED
+ case PNG_READ_zTXt_MODE:
+ {
+ png_push_read_zTXt(png_ptr, info_ptr);
+ break;
+ }
+
+#endif
+#ifdef PNG_READ_iTXt_SUPPORTED
+ case PNG_READ_iTXt_MODE:
+ {
+ png_push_read_iTXt(png_ptr, info_ptr);
+ break;
+ }
+
+#endif
+ case PNG_SKIP_MODE:
+ {
+ png_push_crc_finish(png_ptr);
+ break;
+ }
+
+ default:
+ {
+ png_ptr->buffer_size = 0;
+ break;
+ }
+ }
+}
+
+/* Read any remaining signature bytes from the stream and compare them with
+ * the correct PNG signature. It is possible that this routine is called
+ * with bytes already read from the signature, either because they have been
+ * checked by the calling application, or because of multiple calls to this
+ * routine.
+ */
+void /* PRIVATE */
+png_push_read_sig(png_structp png_ptr, png_infop info_ptr)
+{
+ png_size_t num_checked = png_ptr->sig_bytes,
+ num_to_check = 8 - num_checked;
+
+ if (png_ptr->buffer_size < num_to_check)
+ {
+ num_to_check = png_ptr->buffer_size;
+ }
+
+ png_push_fill_buffer(png_ptr, &(info_ptr->signature[num_checked]),
+ num_to_check);
+ png_ptr->sig_bytes = (png_byte)(png_ptr->sig_bytes + num_to_check);
+
+ if (png_sig_cmp(info_ptr->signature, num_checked, num_to_check))
+ {
+ if (num_checked < 4 &&
+ png_sig_cmp(info_ptr->signature, num_checked, num_to_check - 4))
+ png_error(png_ptr, "Not a PNG file");
+ else
+ png_error(png_ptr, "PNG file corrupted by ASCII conversion");
+ }
+ else
+ {
+ if (png_ptr->sig_bytes >= 8)
+ {
+ png_ptr->process_mode = PNG_READ_CHUNK_MODE;
+ }
+ }
+}
+
+void /* PRIVATE */
+png_push_read_chunk(png_structp png_ptr, png_infop info_ptr)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_CONST PNG_IHDR;
+ PNG_CONST PNG_IDAT;
+ PNG_CONST PNG_IEND;
+ PNG_CONST PNG_PLTE;
+#ifdef PNG_READ_bKGD_SUPPORTED
+ PNG_CONST PNG_bKGD;
+#endif
+#ifdef PNG_READ_cHRM_SUPPORTED
+ PNG_CONST PNG_cHRM;
+#endif
+#ifdef PNG_READ_gAMA_SUPPORTED
+ PNG_CONST PNG_gAMA;
+#endif
+#ifdef PNG_READ_hIST_SUPPORTED
+ PNG_CONST PNG_hIST;
+#endif
+#ifdef PNG_READ_iCCP_SUPPORTED
+ PNG_CONST PNG_iCCP;
+#endif
+#ifdef PNG_READ_iTXt_SUPPORTED
+ PNG_CONST PNG_iTXt;
+#endif
+#ifdef PNG_READ_oFFs_SUPPORTED
+ PNG_CONST PNG_oFFs;
+#endif
+#ifdef PNG_READ_pCAL_SUPPORTED
+ PNG_CONST PNG_pCAL;
+#endif
+#ifdef PNG_READ_pHYs_SUPPORTED
+ PNG_CONST PNG_pHYs;
+#endif
+#ifdef PNG_READ_sBIT_SUPPORTED
+ PNG_CONST PNG_sBIT;
+#endif
+#ifdef PNG_READ_sCAL_SUPPORTED
+ PNG_CONST PNG_sCAL;
+#endif
+#ifdef PNG_READ_sRGB_SUPPORTED
+ PNG_CONST PNG_sRGB;
+#endif
+#ifdef PNG_READ_sPLT_SUPPORTED
+ PNG_CONST PNG_sPLT;
+#endif
+#ifdef PNG_READ_tEXt_SUPPORTED
+ PNG_CONST PNG_tEXt;
+#endif
+#ifdef PNG_READ_tIME_SUPPORTED
+ PNG_CONST PNG_tIME;
+#endif
+#ifdef PNG_READ_tRNS_SUPPORTED
+ PNG_CONST PNG_tRNS;
+#endif
+#ifdef PNG_READ_zTXt_SUPPORTED
+ PNG_CONST PNG_zTXt;
+#endif
+#endif /* PNG_USE_LOCAL_ARRAYS */
+
+ /* First we make sure we have enough data for the 4 byte chunk name
+ * and the 4 byte chunk length before proceeding with decoding the
+ * chunk data. To fully decode each of these chunks, we also make
+ * sure we have enough data in the buffer for the 4 byte CRC at the
+ * end of every chunk (except IDAT, which is handled separately).
+ */
+ if (!(png_ptr->mode & PNG_HAVE_CHUNK_HEADER))
+ {
+ png_byte chunk_length[4];
+
+ if (png_ptr->buffer_size < 8)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ png_push_fill_buffer(png_ptr, chunk_length, 4);
+ png_ptr->push_length = png_get_uint_31(png_ptr, chunk_length);
+ png_reset_crc(png_ptr);
+ png_crc_read(png_ptr, png_ptr->chunk_name, 4);
+ png_check_chunk_name(png_ptr, png_ptr->chunk_name);
+ png_ptr->mode |= PNG_HAVE_CHUNK_HEADER;
+ }
+
+ if (!png_memcmp(png_ptr->chunk_name, png_IDAT, 4))
+ if (png_ptr->mode & PNG_AFTER_IDAT)
+ png_ptr->mode |= PNG_HAVE_CHUNK_AFTER_IDAT;
+
+ if (!png_memcmp(png_ptr->chunk_name, png_IHDR, 4))
+ {
+ if (png_ptr->push_length != 13)
+ png_error(png_ptr, "Invalid IHDR length");
+
+ if (png_ptr->push_length + 4 > png_ptr->buffer_size)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ png_handle_IHDR(png_ptr, info_ptr, png_ptr->push_length);
+ }
+
+ else if (!png_memcmp(png_ptr->chunk_name, png_IEND, 4))
+ {
+ if (png_ptr->push_length + 4 > png_ptr->buffer_size)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ png_handle_IEND(png_ptr, info_ptr, png_ptr->push_length);
+
+ png_ptr->process_mode = PNG_READ_DONE_MODE;
+ png_push_have_end(png_ptr, info_ptr);
+ }
+
+#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
+ else if (png_handle_as_unknown(png_ptr, png_ptr->chunk_name))
+ {
+ if (png_ptr->push_length + 4 > png_ptr->buffer_size)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ if (!png_memcmp(png_ptr->chunk_name, png_IDAT, 4))
+ png_ptr->mode |= PNG_HAVE_IDAT;
+
+ png_handle_unknown(png_ptr, info_ptr, png_ptr->push_length);
+
+ if (!png_memcmp(png_ptr->chunk_name, png_PLTE, 4))
+ png_ptr->mode |= PNG_HAVE_PLTE;
+
+ else if (!png_memcmp(png_ptr->chunk_name, png_IDAT, 4))
+ {
+ if (!(png_ptr->mode & PNG_HAVE_IHDR))
+ png_error(png_ptr, "Missing IHDR before IDAT");
+
+ else if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE &&
+ !(png_ptr->mode & PNG_HAVE_PLTE))
+ png_error(png_ptr, "Missing PLTE before IDAT");
+ }
+ }
+
+#endif
+ else if (!png_memcmp(png_ptr->chunk_name, png_PLTE, 4))
+ {
+ if (png_ptr->push_length + 4 > png_ptr->buffer_size)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+ png_handle_PLTE(png_ptr, info_ptr, png_ptr->push_length);
+ }
+
+ else if (!png_memcmp(png_ptr->chunk_name, png_IDAT, 4))
+ {
+ /* If we reach an IDAT chunk, this means we have read all of the
+ * header chunks, and we can start reading the image (or if this
+ * is called after the image has been read - we have an error).
+ */
+
+ if (!(png_ptr->mode & PNG_HAVE_IHDR))
+ png_error(png_ptr, "Missing IHDR before IDAT");
+
+ else if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE &&
+ !(png_ptr->mode & PNG_HAVE_PLTE))
+ png_error(png_ptr, "Missing PLTE before IDAT");
+
+ if (png_ptr->mode & PNG_HAVE_IDAT)
+ {
+ if (!(png_ptr->mode & PNG_HAVE_CHUNK_AFTER_IDAT))
+ if (png_ptr->push_length == 0)
+ return;
+
+ if (png_ptr->mode & PNG_AFTER_IDAT)
+ png_error(png_ptr, "Too many IDAT's found");
+ }
+
+ png_ptr->idat_size = png_ptr->push_length;
+ png_ptr->mode |= PNG_HAVE_IDAT;
+ png_ptr->process_mode = PNG_READ_IDAT_MODE;
+ png_push_have_info(png_ptr, info_ptr);
+ png_ptr->zstream.avail_out =
+ (uInt) PNG_ROWBYTES(png_ptr->pixel_depth,
+ png_ptr->iwidth) + 1;
+ png_ptr->zstream.next_out = png_ptr->row_buf;
+ return;
+ }
+
+#ifdef PNG_READ_gAMA_SUPPORTED
+ else if (!png_memcmp(png_ptr->chunk_name, png_gAMA, 4))
+ {
+ if (png_ptr->push_length + 4 > png_ptr->buffer_size)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ png_handle_gAMA(png_ptr, info_ptr, png_ptr->push_length);
+ }
+
+#endif
+#ifdef PNG_READ_sBIT_SUPPORTED
+ else if (!png_memcmp(png_ptr->chunk_name, png_sBIT, 4))
+ {
+ if (png_ptr->push_length + 4 > png_ptr->buffer_size)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ png_handle_sBIT(png_ptr, info_ptr, png_ptr->push_length);
+ }
+
+#endif
+#ifdef PNG_READ_cHRM_SUPPORTED
+ else if (!png_memcmp(png_ptr->chunk_name, png_cHRM, 4))
+ {
+ if (png_ptr->push_length + 4 > png_ptr->buffer_size)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ png_handle_cHRM(png_ptr, info_ptr, png_ptr->push_length);
+ }
+
+#endif
+#ifdef PNG_READ_sRGB_SUPPORTED
+ else if (!png_memcmp(png_ptr->chunk_name, png_sRGB, 4))
+ {
+ if (png_ptr->push_length + 4 > png_ptr->buffer_size)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ png_handle_sRGB(png_ptr, info_ptr, png_ptr->push_length);
+ }
+
+#endif
+#ifdef PNG_READ_iCCP_SUPPORTED
+ else if (!png_memcmp(png_ptr->chunk_name, png_iCCP, 4))
+ {
+ if (png_ptr->push_length + 4 > png_ptr->buffer_size)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ png_handle_iCCP(png_ptr, info_ptr, png_ptr->push_length);
+ }
+
+#endif
+#ifdef PNG_READ_sPLT_SUPPORTED
+ else if (!png_memcmp(png_ptr->chunk_name, png_sPLT, 4))
+ {
+ if (png_ptr->push_length + 4 > png_ptr->buffer_size)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ png_handle_sPLT(png_ptr, info_ptr, png_ptr->push_length);
+ }
+
+#endif
+#ifdef PNG_READ_tRNS_SUPPORTED
+ else if (!png_memcmp(png_ptr->chunk_name, png_tRNS, 4))
+ {
+ if (png_ptr->push_length + 4 > png_ptr->buffer_size)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ png_handle_tRNS(png_ptr, info_ptr, png_ptr->push_length);
+ }
+
+#endif
+#ifdef PNG_READ_bKGD_SUPPORTED
+ else if (!png_memcmp(png_ptr->chunk_name, png_bKGD, 4))
+ {
+ if (png_ptr->push_length + 4 > png_ptr->buffer_size)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ png_handle_bKGD(png_ptr, info_ptr, png_ptr->push_length);
+ }
+
+#endif
+#ifdef PNG_READ_hIST_SUPPORTED
+ else if (!png_memcmp(png_ptr->chunk_name, png_hIST, 4))
+ {
+ if (png_ptr->push_length + 4 > png_ptr->buffer_size)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ png_handle_hIST(png_ptr, info_ptr, png_ptr->push_length);
+ }
+
+#endif
+#ifdef PNG_READ_pHYs_SUPPORTED
+ else if (!png_memcmp(png_ptr->chunk_name, png_pHYs, 4))
+ {
+ if (png_ptr->push_length + 4 > png_ptr->buffer_size)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ png_handle_pHYs(png_ptr, info_ptr, png_ptr->push_length);
+ }
+
+#endif
+#ifdef PNG_READ_oFFs_SUPPORTED
+ else if (!png_memcmp(png_ptr->chunk_name, png_oFFs, 4))
+ {
+ if (png_ptr->push_length + 4 > png_ptr->buffer_size)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ png_handle_oFFs(png_ptr, info_ptr, png_ptr->push_length);
+ }
+#endif
+
+#ifdef PNG_READ_pCAL_SUPPORTED
+ else if (!png_memcmp(png_ptr->chunk_name, png_pCAL, 4))
+ {
+ if (png_ptr->push_length + 4 > png_ptr->buffer_size)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ png_handle_pCAL(png_ptr, info_ptr, png_ptr->push_length);
+ }
+
+#endif
+#ifdef PNG_READ_sCAL_SUPPORTED
+ else if (!png_memcmp(png_ptr->chunk_name, png_sCAL, 4))
+ {
+ if (png_ptr->push_length + 4 > png_ptr->buffer_size)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ png_handle_sCAL(png_ptr, info_ptr, png_ptr->push_length);
+ }
+
+#endif
+#ifdef PNG_READ_tIME_SUPPORTED
+ else if (!png_memcmp(png_ptr->chunk_name, png_tIME, 4))
+ {
+ if (png_ptr->push_length + 4 > png_ptr->buffer_size)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ png_handle_tIME(png_ptr, info_ptr, png_ptr->push_length);
+ }
+
+#endif
+#ifdef PNG_READ_tEXt_SUPPORTED
+ else if (!png_memcmp(png_ptr->chunk_name, png_tEXt, 4))
+ {
+ if (png_ptr->push_length + 4 > png_ptr->buffer_size)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ png_push_handle_tEXt(png_ptr, info_ptr, png_ptr->push_length);
+ }
+
+#endif
+#ifdef PNG_READ_zTXt_SUPPORTED
+ else if (!png_memcmp(png_ptr->chunk_name, png_zTXt, 4))
+ {
+ if (png_ptr->push_length + 4 > png_ptr->buffer_size)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ png_push_handle_zTXt(png_ptr, info_ptr, png_ptr->push_length);
+ }
+
+#endif
+#ifdef PNG_READ_iTXt_SUPPORTED
+ else if (!png_memcmp(png_ptr->chunk_name, png_iTXt, 4))
+ {
+ if (png_ptr->push_length + 4 > png_ptr->buffer_size)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ png_push_handle_iTXt(png_ptr, info_ptr, png_ptr->push_length);
+ }
+
+#endif
+ else
+ {
+ if (png_ptr->push_length + 4 > png_ptr->buffer_size)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+ png_push_handle_unknown(png_ptr, info_ptr, png_ptr->push_length);
+ }
+
+ png_ptr->mode &= ~PNG_HAVE_CHUNK_HEADER;
+}
+
+void /* PRIVATE */
+png_push_crc_skip(png_structp png_ptr, png_uint_32 skip)
+{
+ png_ptr->process_mode = PNG_SKIP_MODE;
+ png_ptr->skip_length = skip;
+}
+
+void /* PRIVATE */
+png_push_crc_finish(png_structp png_ptr)
+{
+ if (png_ptr->skip_length && png_ptr->save_buffer_size)
+ {
+ png_size_t save_size;
+
+ if (png_ptr->skip_length < (png_uint_32)png_ptr->save_buffer_size)
+ save_size = (png_size_t)png_ptr->skip_length;
+ else
+ save_size = png_ptr->save_buffer_size;
+
+ png_calculate_crc(png_ptr, png_ptr->save_buffer_ptr, save_size);
+
+ png_ptr->skip_length -= save_size;
+ png_ptr->buffer_size -= save_size;
+ png_ptr->save_buffer_size -= save_size;
+ png_ptr->save_buffer_ptr += save_size;
+ }
+ if (png_ptr->skip_length && png_ptr->current_buffer_size)
+ {
+ png_size_t save_size;
+
+ if (png_ptr->skip_length < (png_uint_32)png_ptr->current_buffer_size)
+ save_size = (png_size_t)png_ptr->skip_length;
+ else
+ save_size = png_ptr->current_buffer_size;
+
+ png_calculate_crc(png_ptr, png_ptr->current_buffer_ptr, save_size);
+
+ png_ptr->skip_length -= save_size;
+ png_ptr->buffer_size -= save_size;
+ png_ptr->current_buffer_size -= save_size;
+ png_ptr->current_buffer_ptr += save_size;
+ }
+ if (!png_ptr->skip_length)
+ {
+ if (png_ptr->buffer_size < 4)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ png_crc_finish(png_ptr, 0);
+ png_ptr->process_mode = PNG_READ_CHUNK_MODE;
+ }
+}
+
+void PNGAPI
+png_push_fill_buffer(png_structp png_ptr, png_bytep buffer, png_size_t length)
+{
+ png_bytep ptr;
+
+ if (png_ptr == NULL)
+ return;
+
+ ptr = buffer;
+ if (png_ptr->save_buffer_size)
+ {
+ png_size_t save_size;
+
+ if (length < png_ptr->save_buffer_size)
+ save_size = length;
+ else
+ save_size = png_ptr->save_buffer_size;
+
+ png_memcpy(ptr, png_ptr->save_buffer_ptr, save_size);
+ length -= save_size;
+ ptr += save_size;
+ png_ptr->buffer_size -= save_size;
+ png_ptr->save_buffer_size -= save_size;
+ png_ptr->save_buffer_ptr += save_size;
+ }
+ if (length && png_ptr->current_buffer_size)
+ {
+ png_size_t save_size;
+
+ if (length < png_ptr->current_buffer_size)
+ save_size = length;
+
+ else
+ save_size = png_ptr->current_buffer_size;
+
+ png_memcpy(ptr, png_ptr->current_buffer_ptr, save_size);
+ png_ptr->buffer_size -= save_size;
+ png_ptr->current_buffer_size -= save_size;
+ png_ptr->current_buffer_ptr += save_size;
+ }
+}
+
+void /* PRIVATE */
+png_push_save_buffer(png_structp png_ptr)
+{
+ if (png_ptr->save_buffer_size)
+ {
+ if (png_ptr->save_buffer_ptr != png_ptr->save_buffer)
+ {
+ png_size_t i, istop;
+ png_bytep sp;
+ png_bytep dp;
+
+ istop = png_ptr->save_buffer_size;
+ for (i = 0, sp = png_ptr->save_buffer_ptr, dp = png_ptr->save_buffer;
+ i < istop; i++, sp++, dp++)
+ {
+ *dp = *sp;
+ }
+ }
+ }
+ if (png_ptr->save_buffer_size + png_ptr->current_buffer_size >
+ png_ptr->save_buffer_max)
+ {
+ png_size_t new_max;
+ png_bytep old_buffer;
+
+ if (png_ptr->save_buffer_size > PNG_SIZE_MAX -
+ (png_ptr->current_buffer_size + 256))
+ {
+ png_error(png_ptr, "Potential overflow of save_buffer");
+ }
+
+ new_max = png_ptr->save_buffer_size + png_ptr->current_buffer_size + 256;
+ old_buffer = png_ptr->save_buffer;
+ png_ptr->save_buffer = (png_bytep)png_malloc_warn(png_ptr,
+ (png_uint_32)new_max);
+ if (png_ptr->save_buffer == NULL)
+ {
+ png_free(png_ptr, old_buffer);
+ png_error(png_ptr, "Insufficient memory for save_buffer");
+ }
+ png_memcpy(png_ptr->save_buffer, old_buffer, png_ptr->save_buffer_size);
+ png_free(png_ptr, old_buffer);
+ png_ptr->save_buffer_max = new_max;
+ }
+ if (png_ptr->current_buffer_size)
+ {
+ png_memcpy(png_ptr->save_buffer + png_ptr->save_buffer_size,
+ png_ptr->current_buffer_ptr, png_ptr->current_buffer_size);
+ png_ptr->save_buffer_size += png_ptr->current_buffer_size;
+ png_ptr->current_buffer_size = 0;
+ }
+ png_ptr->save_buffer_ptr = png_ptr->save_buffer;
+ png_ptr->buffer_size = 0;
+}
+
+void /* PRIVATE */
+png_push_restore_buffer(png_structp png_ptr, png_bytep buffer,
+ png_size_t buffer_length)
+{
+ png_ptr->current_buffer = buffer;
+ png_ptr->current_buffer_size = buffer_length;
+ png_ptr->buffer_size = buffer_length + png_ptr->save_buffer_size;
+ png_ptr->current_buffer_ptr = png_ptr->current_buffer;
+}
+
+void /* PRIVATE */
+png_push_read_IDAT(png_structp png_ptr)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_CONST PNG_IDAT;
+#endif
+ if (!(png_ptr->mode & PNG_HAVE_CHUNK_HEADER))
+ {
+ png_byte chunk_length[4];
+
+ if (png_ptr->buffer_size < 8)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ png_push_fill_buffer(png_ptr, chunk_length, 4);
+ png_ptr->push_length = png_get_uint_31(png_ptr, chunk_length);
+ png_reset_crc(png_ptr);
+ png_crc_read(png_ptr, png_ptr->chunk_name, 4);
+ png_ptr->mode |= PNG_HAVE_CHUNK_HEADER;
+
+ if (png_memcmp(png_ptr->chunk_name, png_IDAT, 4))
+ {
+ png_ptr->process_mode = PNG_READ_CHUNK_MODE;
+ if (!(png_ptr->flags & PNG_FLAG_ZLIB_FINISHED))
+ png_error(png_ptr, "Not enough compressed data");
+ return;
+ }
+
+ png_ptr->idat_size = png_ptr->push_length;
+ }
+ if (png_ptr->idat_size && png_ptr->save_buffer_size)
+ {
+ png_size_t save_size;
+
+ if (png_ptr->idat_size < (png_uint_32)png_ptr->save_buffer_size)
+ {
+ save_size = (png_size_t)png_ptr->idat_size;
+
+ /* Check for overflow */
+ if ((png_uint_32)save_size != png_ptr->idat_size)
+ png_error(png_ptr, "save_size overflowed in pngpread");
+ }
+ else
+ save_size = png_ptr->save_buffer_size;
+
+ png_calculate_crc(png_ptr, png_ptr->save_buffer_ptr, save_size);
+
+ png_process_IDAT_data(png_ptr, png_ptr->save_buffer_ptr, save_size);
+
+ png_ptr->idat_size -= save_size;
+ png_ptr->buffer_size -= save_size;
+ png_ptr->save_buffer_size -= save_size;
+ png_ptr->save_buffer_ptr += save_size;
+ }
+ if (png_ptr->idat_size && png_ptr->current_buffer_size)
+ {
+ png_size_t save_size;
+
+ if (png_ptr->idat_size < (png_uint_32)png_ptr->current_buffer_size)
+ {
+ save_size = (png_size_t)png_ptr->idat_size;
+
+ /* Check for overflow */
+ if ((png_uint_32)save_size != png_ptr->idat_size)
+ png_error(png_ptr, "save_size overflowed in pngpread");
+ }
+ else
+ save_size = png_ptr->current_buffer_size;
+
+ png_calculate_crc(png_ptr, png_ptr->current_buffer_ptr, save_size);
+
+ png_process_IDAT_data(png_ptr, png_ptr->current_buffer_ptr, save_size);
+
+ png_ptr->idat_size -= save_size;
+ png_ptr->buffer_size -= save_size;
+ png_ptr->current_buffer_size -= save_size;
+ png_ptr->current_buffer_ptr += save_size;
+ }
+ if (!png_ptr->idat_size)
+ {
+ if (png_ptr->buffer_size < 4)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ png_crc_finish(png_ptr, 0);
+ png_ptr->mode &= ~PNG_HAVE_CHUNK_HEADER;
+ png_ptr->mode |= PNG_AFTER_IDAT;
+ }
+}
+
+void /* PRIVATE */
+png_process_IDAT_data(png_structp png_ptr, png_bytep buffer,
+ png_size_t buffer_length)
+{
+ /* The caller checks for a non-zero buffer length. */
+ if (!(buffer_length > 0) || buffer == NULL)
+ png_error(png_ptr, "No IDAT data (internal error)");
+
+ /* This routine must process all the data it has been given
+ * before returning, calling the row callback as required to
+ * handle the uncompressed results.
+ */
+ png_ptr->zstream.next_in = buffer;
+ png_ptr->zstream.avail_in = (uInt)buffer_length;
+
+ /* Keep going until the decompressed data is all processed
+ * or the stream marked as finished.
+ */
+ while (png_ptr->zstream.avail_in > 0 &&
+ !(png_ptr->flags & PNG_FLAG_ZLIB_FINISHED))
+ {
+ int ret;
+
+ /* We have data for zlib, but we must check that zlib
+ * has somewhere to put the results. It doesn't matter
+ * if we don't expect any results -- it may be the input
+ * data is just the LZ end code.
+ */
+ if (!(png_ptr->zstream.avail_out > 0))
+ {
+ png_ptr->zstream.avail_out =
+ (uInt) PNG_ROWBYTES(png_ptr->pixel_depth,
+ png_ptr->iwidth) + 1;
+ png_ptr->zstream.next_out = png_ptr->row_buf;
+ }
+
+ /* Using Z_SYNC_FLUSH here means that an unterminated
+ * LZ stream can still be handled (a stream with a missing
+ * end code), otherwise (Z_NO_FLUSH) a future zlib
+ * implementation might defer output and, therefore,
+ * change the current behavior. (See comments in inflate.c
+ * for why this doesn't happen at present with zlib 1.2.5.)
+ */
+ ret = inflate(&png_ptr->zstream, Z_SYNC_FLUSH);
+
+ /* Check for any failure before proceeding. */
+ if (ret != Z_OK && ret != Z_STREAM_END)
+ {
+ /* Terminate the decompression. */
+ png_ptr->flags |= PNG_FLAG_ZLIB_FINISHED;
+
+ /* This may be a truncated stream (missing or
+ * damaged end code). Treat that as a warning.
+ */
+ if (png_ptr->row_number >= png_ptr->num_rows ||
+ png_ptr->pass > 6)
+ png_warning(png_ptr, "Truncated compressed data in IDAT");
+ else
+ png_error(png_ptr, "Decompression error in IDAT");
+
+ /* Skip the check on unprocessed input */
+ return;
+ }
+
+ /* Did inflate output any data? */
+ if (png_ptr->zstream.next_out != png_ptr->row_buf)
+ {
+ /* Is this unexpected data after the last row?
+ * If it is, artificially terminate the LZ output
+ * here.
+ */
+ if (png_ptr->row_number >= png_ptr->num_rows ||
+ png_ptr->pass > 6)
+ {
+ /* Extra data. */
+ png_warning(png_ptr, "Extra compressed data in IDAT");
+ png_ptr->flags |= PNG_FLAG_ZLIB_FINISHED;
+ /* Do no more processing; skip the unprocessed
+ * input check below.
+ */
+ return;
+ }
+
+ /* Do we have a complete row? */
+ if (png_ptr->zstream.avail_out == 0)
+ png_push_process_row(png_ptr);
+ }
+
+ /* And check for the end of the stream. */
+ if (ret == Z_STREAM_END)
+ png_ptr->flags |= PNG_FLAG_ZLIB_FINISHED;
+ }
+
+ /* All the data should have been processed, if anything
+ * is left at this point we have bytes of IDAT data
+ * after the zlib end code.
+ */
+ if (png_ptr->zstream.avail_in > 0)
+ png_warning(png_ptr, "Extra compression data");
+}
+
+void /* PRIVATE */
+png_push_process_row(png_structp png_ptr)
+{
+ png_ptr->row_info.color_type = png_ptr->color_type;
+ png_ptr->row_info.width = png_ptr->iwidth;
+ png_ptr->row_info.channels = png_ptr->channels;
+ png_ptr->row_info.bit_depth = png_ptr->bit_depth;
+ png_ptr->row_info.pixel_depth = png_ptr->pixel_depth;
+
+ png_ptr->row_info.rowbytes = PNG_ROWBYTES(png_ptr->row_info.pixel_depth,
+ png_ptr->row_info.width);
+
+ png_read_filter_row(png_ptr, &(png_ptr->row_info),
+ png_ptr->row_buf + 1, png_ptr->prev_row + 1,
+ (int)(png_ptr->row_buf[0]));
+
+ png_memcpy_check(png_ptr, png_ptr->prev_row, png_ptr->row_buf,
+ png_ptr->rowbytes + 1);
+
+ if (png_ptr->transformations || (png_ptr->flags&PNG_FLAG_STRIP_ALPHA))
+ png_do_read_transformations(png_ptr);
+
+#ifdef PNG_READ_INTERLACING_SUPPORTED
+ /* Blow up interlaced rows to full size */
+ if (png_ptr->interlaced && (png_ptr->transformations & PNG_INTERLACE))
+ {
+ if (png_ptr->pass < 6)
+/* old interface (pre-1.0.9):
+ png_do_read_interlace(&(png_ptr->row_info),
+ png_ptr->row_buf + 1, png_ptr->pass, png_ptr->transformations);
+ */
+ png_do_read_interlace(png_ptr);
+
+ switch (png_ptr->pass)
+ {
+ case 0:
+ {
+ int i;
+ for (i = 0; i < 8 && png_ptr->pass == 0; i++)
+ {
+ png_push_have_row(png_ptr, png_ptr->row_buf + 1);
+ png_read_push_finish_row(png_ptr); /* Updates png_ptr->pass */
+ }
+
+ if (png_ptr->pass == 2) /* Pass 1 might be empty */
+ {
+ for (i = 0; i < 4 && png_ptr->pass == 2; i++)
+ {
+ png_push_have_row(png_ptr, png_bytep_NULL);
+ png_read_push_finish_row(png_ptr);
+ }
+ }
+
+ if (png_ptr->pass == 4 && png_ptr->height <= 4)
+ {
+ for (i = 0; i < 2 && png_ptr->pass == 4; i++)
+ {
+ png_push_have_row(png_ptr, png_bytep_NULL);
+ png_read_push_finish_row(png_ptr);
+ }
+ }
+
+ if (png_ptr->pass == 6 && png_ptr->height <= 4)
+ {
+ png_push_have_row(png_ptr, png_bytep_NULL);
+ png_read_push_finish_row(png_ptr);
+ }
+
+ break;
+ }
+
+ case 1:
+ {
+ int i;
+ for (i = 0; i < 8 && png_ptr->pass == 1; i++)
+ {
+ png_push_have_row(png_ptr, png_ptr->row_buf + 1);
+ png_read_push_finish_row(png_ptr);
+ }
+
+ if (png_ptr->pass == 2) /* Skip top 4 generated rows */
+ {
+ for (i = 0; i < 4 && png_ptr->pass == 2; i++)
+ {
+ png_push_have_row(png_ptr, png_bytep_NULL);
+ png_read_push_finish_row(png_ptr);
+ }
+ }
+
+ break;
+ }
+
+ case 2:
+ {
+ int i;
+
+ for (i = 0; i < 4 && png_ptr->pass == 2; i++)
+ {
+ png_push_have_row(png_ptr, png_ptr->row_buf + 1);
+ png_read_push_finish_row(png_ptr);
+ }
+
+ for (i = 0; i < 4 && png_ptr->pass == 2; i++)
+ {
+ png_push_have_row(png_ptr, png_bytep_NULL);
+ png_read_push_finish_row(png_ptr);
+ }
+
+ if (png_ptr->pass == 4) /* Pass 3 might be empty */
+ {
+ for (i = 0; i < 2 && png_ptr->pass == 4; i++)
+ {
+ png_push_have_row(png_ptr, png_bytep_NULL);
+ png_read_push_finish_row(png_ptr);
+ }
+ }
+
+ break;
+ }
+
+ case 3:
+ {
+ int i;
+
+ for (i = 0; i < 4 && png_ptr->pass == 3; i++)
+ {
+ png_push_have_row(png_ptr, png_ptr->row_buf + 1);
+ png_read_push_finish_row(png_ptr);
+ }
+
+ if (png_ptr->pass == 4) /* Skip top two generated rows */
+ {
+ for (i = 0; i < 2 && png_ptr->pass == 4; i++)
+ {
+ png_push_have_row(png_ptr, png_bytep_NULL);
+ png_read_push_finish_row(png_ptr);
+ }
+ }
+
+ break;
+ }
+
+ case 4:
+ {
+ int i;
+
+ for (i = 0; i < 2 && png_ptr->pass == 4; i++)
+ {
+ png_push_have_row(png_ptr, png_ptr->row_buf + 1);
+ png_read_push_finish_row(png_ptr);
+ }
+
+ for (i = 0; i < 2 && png_ptr->pass == 4; i++)
+ {
+ png_push_have_row(png_ptr, png_bytep_NULL);
+ png_read_push_finish_row(png_ptr);
+ }
+
+ if (png_ptr->pass == 6) /* Pass 5 might be empty */
+ {
+ png_push_have_row(png_ptr, png_bytep_NULL);
+ png_read_push_finish_row(png_ptr);
+ }
+
+ break;
+ }
+
+ case 5:
+ {
+ int i;
+
+ for (i = 0; i < 2 && png_ptr->pass == 5; i++)
+ {
+ png_push_have_row(png_ptr, png_ptr->row_buf + 1);
+ png_read_push_finish_row(png_ptr);
+ }
+
+ if (png_ptr->pass == 6) /* Skip top generated row */
+ {
+ png_push_have_row(png_ptr, png_bytep_NULL);
+ png_read_push_finish_row(png_ptr);
+ }
+
+ break;
+ }
+ case 6:
+ {
+ png_push_have_row(png_ptr, png_ptr->row_buf + 1);
+ png_read_push_finish_row(png_ptr);
+
+ if (png_ptr->pass != 6)
+ break;
+
+ png_push_have_row(png_ptr, png_bytep_NULL);
+ png_read_push_finish_row(png_ptr);
+ }
+ }
+ }
+ else
+#endif
+ {
+ png_push_have_row(png_ptr, png_ptr->row_buf + 1);
+ png_read_push_finish_row(png_ptr);
+ }
+}
+
+void /* PRIVATE */
+png_read_push_finish_row(png_structp png_ptr)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ /* Arrays to facilitate easy interlacing - use pass (0 - 6) as index */
+
+ /* Start of interlace block */
+ PNG_CONST int FARDATA png_pass_start[] = {0, 4, 0, 2, 0, 1, 0};
+
+ /* Offset to next interlace block */
+ PNG_CONST int FARDATA png_pass_inc[] = {8, 8, 4, 4, 2, 2, 1};
+
+ /* Start of interlace block in the y direction */
+ PNG_CONST int FARDATA png_pass_ystart[] = {0, 0, 4, 0, 2, 0, 1};
+
+ /* Offset to next interlace block in the y direction */
+ PNG_CONST int FARDATA png_pass_yinc[] = {8, 8, 8, 4, 4, 2, 2};
+
+ /* Height of interlace block. This is not currently used - if you need
+ * it, uncomment it here and in png.h
+ PNG_CONST int FARDATA png_pass_height[] = {8, 8, 4, 4, 2, 2, 1};
+ */
+#endif
+
+ png_ptr->row_number++;
+ if (png_ptr->row_number < png_ptr->num_rows)
+ return;
+
+#ifdef PNG_READ_INTERLACING_SUPPORTED
+ if (png_ptr->interlaced)
+ {
+ png_ptr->row_number = 0;
+ png_memset_check(png_ptr, png_ptr->prev_row, 0,
+ png_ptr->rowbytes + 1);
+ do
+ {
+ png_ptr->pass++;
+ if ((png_ptr->pass == 1 && png_ptr->width < 5) ||
+ (png_ptr->pass == 3 && png_ptr->width < 3) ||
+ (png_ptr->pass == 5 && png_ptr->width < 2))
+ png_ptr->pass++;
+
+ if (png_ptr->pass > 7)
+ png_ptr->pass--;
+
+ if (png_ptr->pass >= 7)
+ break;
+
+ png_ptr->iwidth = (png_ptr->width +
+ png_pass_inc[png_ptr->pass] - 1 -
+ png_pass_start[png_ptr->pass]) /
+ png_pass_inc[png_ptr->pass];
+
+ if (png_ptr->transformations & PNG_INTERLACE)
+ break;
+
+ png_ptr->num_rows = (png_ptr->height +
+ png_pass_yinc[png_ptr->pass] - 1 -
+ png_pass_ystart[png_ptr->pass]) /
+ png_pass_yinc[png_ptr->pass];
+
+ } while (png_ptr->iwidth == 0 || png_ptr->num_rows == 0);
+ }
+#endif /* PNG_READ_INTERLACING_SUPPORTED */
+}
+
+#ifdef PNG_READ_tEXt_SUPPORTED
+void /* PRIVATE */
+png_push_handle_tEXt(png_structp png_ptr, png_infop info_ptr, png_uint_32
+ length)
+{
+ if (!(png_ptr->mode & PNG_HAVE_IHDR) || (png_ptr->mode & PNG_HAVE_IEND))
+ {
+ png_error(png_ptr, "Out of place tEXt");
+ info_ptr = info_ptr; /* To quiet some compiler warnings */
+ }
+
+#ifdef PNG_MAX_MALLOC_64K
+ png_ptr->skip_length = 0; /* This may not be necessary */
+
+ if (length > (png_uint_32)65535L) /* Can't hold entire string in memory */
+ {
+ png_warning(png_ptr, "tEXt chunk too large to fit in memory");
+ png_ptr->skip_length = length - (png_uint_32)65535L;
+ length = (png_uint_32)65535L;
+ }
+#endif
+
+ png_ptr->current_text = (png_charp)png_malloc(png_ptr,
+ (png_uint_32)(length + 1));
+ png_ptr->current_text[length] = '\0';
+ png_ptr->current_text_ptr = png_ptr->current_text;
+ png_ptr->current_text_size = (png_size_t)length;
+ png_ptr->current_text_left = (png_size_t)length;
+ png_ptr->process_mode = PNG_READ_tEXt_MODE;
+}
+
+void /* PRIVATE */
+png_push_read_tEXt(png_structp png_ptr, png_infop info_ptr)
+{
+ if (png_ptr->buffer_size && png_ptr->current_text_left)
+ {
+ png_size_t text_size;
+
+ if (png_ptr->buffer_size < png_ptr->current_text_left)
+ text_size = png_ptr->buffer_size;
+
+ else
+ text_size = png_ptr->current_text_left;
+
+ png_crc_read(png_ptr, (png_bytep)png_ptr->current_text_ptr, text_size);
+ png_ptr->current_text_left -= text_size;
+ png_ptr->current_text_ptr += text_size;
+ }
+ if (!(png_ptr->current_text_left))
+ {
+ png_textp text_ptr;
+ png_charp text;
+ png_charp key;
+ int ret;
+
+ if (png_ptr->buffer_size < 4)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ png_push_crc_finish(png_ptr);
+
+#ifdef PNG_MAX_MALLOC_64K
+ if (png_ptr->skip_length)
+ return;
+#endif
+
+ key = png_ptr->current_text;
+
+ for (text = key; *text; text++)
+ /* Empty loop */ ;
+
+ if (text < key + png_ptr->current_text_size)
+ text++;
+
+ text_ptr = (png_textp)png_malloc(png_ptr,
+ (png_uint_32)png_sizeof(png_text));
+ text_ptr->compression = PNG_TEXT_COMPRESSION_NONE;
+ text_ptr->key = key;
+#ifdef PNG_iTXt_SUPPORTED
+ text_ptr->lang = NULL;
+ text_ptr->lang_key = NULL;
+#endif
+ text_ptr->text = text;
+
+ ret = png_set_text_2(png_ptr, info_ptr, text_ptr, 1);
+
+ png_free(png_ptr, key);
+ png_free(png_ptr, text_ptr);
+ png_ptr->current_text = NULL;
+
+ if (ret)
+ png_warning(png_ptr, "Insufficient memory to store text chunk.");
+ }
+}
+#endif
+
+#ifdef PNG_READ_zTXt_SUPPORTED
+void /* PRIVATE */
+png_push_handle_zTXt(png_structp png_ptr, png_infop info_ptr, png_uint_32
+ length)
+{
+ if (!(png_ptr->mode & PNG_HAVE_IHDR) || (png_ptr->mode & PNG_HAVE_IEND))
+ {
+ png_error(png_ptr, "Out of place zTXt");
+ info_ptr = info_ptr; /* To quiet some compiler warnings */
+ }
+
+#ifdef PNG_MAX_MALLOC_64K
+ /* We can't handle zTXt chunks > 64K, since we don't have enough space
+ * to be able to store the uncompressed data. Actually, the threshold
+ * is probably around 32K, but it isn't as definite as 64K is.
+ */
+ if (length > (png_uint_32)65535L)
+ {
+ png_warning(png_ptr, "zTXt chunk too large to fit in memory");
+ png_push_crc_skip(png_ptr, length);
+ return;
+ }
+#endif
+
+ png_ptr->current_text = (png_charp)png_malloc(png_ptr,
+ (png_uint_32)(length + 1));
+ png_ptr->current_text[length] = '\0';
+ png_ptr->current_text_ptr = png_ptr->current_text;
+ png_ptr->current_text_size = (png_size_t)length;
+ png_ptr->current_text_left = (png_size_t)length;
+ png_ptr->process_mode = PNG_READ_zTXt_MODE;
+}
+
+void /* PRIVATE */
+png_push_read_zTXt(png_structp png_ptr, png_infop info_ptr)
+{
+ if (png_ptr->buffer_size && png_ptr->current_text_left)
+ {
+ png_size_t text_size;
+
+ if (png_ptr->buffer_size < (png_uint_32)png_ptr->current_text_left)
+ text_size = png_ptr->buffer_size;
+
+ else
+ text_size = png_ptr->current_text_left;
+
+ png_crc_read(png_ptr, (png_bytep)png_ptr->current_text_ptr, text_size);
+ png_ptr->current_text_left -= text_size;
+ png_ptr->current_text_ptr += text_size;
+ }
+ if (!(png_ptr->current_text_left))
+ {
+ png_textp text_ptr;
+ png_charp text;
+ png_charp key;
+ int ret;
+ png_size_t text_size, key_size;
+
+ if (png_ptr->buffer_size < 4)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ png_push_crc_finish(png_ptr);
+
+ key = png_ptr->current_text;
+
+ for (text = key; *text; text++)
+ /* Empty loop */ ;
+
+ /* zTXt can't have zero text */
+ if (text >= key + png_ptr->current_text_size)
+ {
+ png_ptr->current_text = NULL;
+ png_free(png_ptr, key);
+ return;
+ }
+
+ text++;
+
+ if (*text != PNG_TEXT_COMPRESSION_zTXt) /* Check compression byte */
+ {
+ png_ptr->current_text = NULL;
+ png_free(png_ptr, key);
+ return;
+ }
+
+ text++;
+
+ png_ptr->zstream.next_in = (png_bytep )text;
+ png_ptr->zstream.avail_in = (uInt)(png_ptr->current_text_size -
+ (text - key));
+ png_ptr->zstream.next_out = png_ptr->zbuf;
+ png_ptr->zstream.avail_out = (uInt)png_ptr->zbuf_size;
+
+ key_size = text - key;
+ text_size = 0;
+ text = NULL;
+ ret = Z_STREAM_END;
+
+ while (png_ptr->zstream.avail_in)
+ {
+ ret = inflate(&png_ptr->zstream, Z_PARTIAL_FLUSH);
+ if (ret != Z_OK && ret != Z_STREAM_END)
+ {
+ inflateReset(&png_ptr->zstream);
+ png_ptr->zstream.avail_in = 0;
+ png_ptr->current_text = NULL;
+ png_free(png_ptr, key);
+ png_free(png_ptr, text);
+ return;
+ }
+ if (!(png_ptr->zstream.avail_out) || ret == Z_STREAM_END)
+ {
+ if (text == NULL)
+ {
+ text = (png_charp)png_malloc(png_ptr,
+ (png_uint_32)(png_ptr->zbuf_size
+ - png_ptr->zstream.avail_out + key_size + 1));
+
+ png_memcpy(text + key_size, png_ptr->zbuf,
+ png_ptr->zbuf_size - png_ptr->zstream.avail_out);
+
+ png_memcpy(text, key, key_size);
+
+ text_size = key_size + png_ptr->zbuf_size -
+ png_ptr->zstream.avail_out;
+
+ *(text + text_size) = '\0';
+ }
+ else
+ {
+ png_charp tmp;
+
+ tmp = text;
+ text = (png_charp)png_malloc(png_ptr, text_size +
+ (png_uint_32)(png_ptr->zbuf_size
+ - png_ptr->zstream.avail_out + 1));
+
+ png_memcpy(text, tmp, text_size);
+ png_free(png_ptr, tmp);
+
+ png_memcpy(text + text_size, png_ptr->zbuf,
+ png_ptr->zbuf_size - png_ptr->zstream.avail_out);
+
+ text_size += png_ptr->zbuf_size - png_ptr->zstream.avail_out;
+ *(text + text_size) = '\0';
+ }
+ if (ret != Z_STREAM_END)
+ {
+ png_ptr->zstream.next_out = png_ptr->zbuf;
+ png_ptr->zstream.avail_out = (uInt)png_ptr->zbuf_size;
+ }
+ }
+ else
+ {
+ break;
+ }
+
+ if (ret == Z_STREAM_END)
+ break;
+ }
+
+ inflateReset(&png_ptr->zstream);
+ png_ptr->zstream.avail_in = 0;
+
+ if (ret != Z_STREAM_END)
+ {
+ png_ptr->current_text = NULL;
+ png_free(png_ptr, key);
+ png_free(png_ptr, text);
+ return;
+ }
+
+ png_ptr->current_text = NULL;
+ png_free(png_ptr, key);
+ key = text;
+ text += key_size;
+
+ text_ptr = (png_textp)png_malloc(png_ptr,
+ (png_uint_32)png_sizeof(png_text));
+ text_ptr->compression = PNG_TEXT_COMPRESSION_zTXt;
+ text_ptr->key = key;
+#ifdef PNG_iTXt_SUPPORTED
+ text_ptr->lang = NULL;
+ text_ptr->lang_key = NULL;
+#endif
+ text_ptr->text = text;
+
+ ret = png_set_text_2(png_ptr, info_ptr, text_ptr, 1);
+
+ png_free(png_ptr, key);
+ png_free(png_ptr, text_ptr);
+
+ if (ret)
+ png_warning(png_ptr, "Insufficient memory to store text chunk.");
+ }
+}
+#endif
+
+#ifdef PNG_READ_iTXt_SUPPORTED
+void /* PRIVATE */
+png_push_handle_iTXt(png_structp png_ptr, png_infop info_ptr, png_uint_32
+ length)
+{
+ if (!(png_ptr->mode & PNG_HAVE_IHDR) || (png_ptr->mode & PNG_HAVE_IEND))
+ {
+ png_error(png_ptr, "Out of place iTXt");
+ info_ptr = info_ptr; /* To quiet some compiler warnings */
+ }
+
+#ifdef PNG_MAX_MALLOC_64K
+ png_ptr->skip_length = 0; /* This may not be necessary */
+
+ if (length > (png_uint_32)65535L) /* Can't hold entire string in memory */
+ {
+ png_warning(png_ptr, "iTXt chunk too large to fit in memory");
+ png_ptr->skip_length = length - (png_uint_32)65535L;
+ length = (png_uint_32)65535L;
+ }
+#endif
+
+ png_ptr->current_text = (png_charp)png_malloc(png_ptr,
+ (png_uint_32)(length + 1));
+ png_ptr->current_text[length] = '\0';
+ png_ptr->current_text_ptr = png_ptr->current_text;
+ png_ptr->current_text_size = (png_size_t)length;
+ png_ptr->current_text_left = (png_size_t)length;
+ png_ptr->process_mode = PNG_READ_iTXt_MODE;
+}
+
+void /* PRIVATE */
+png_push_read_iTXt(png_structp png_ptr, png_infop info_ptr)
+{
+
+ if (png_ptr->buffer_size && png_ptr->current_text_left)
+ {
+ png_size_t text_size;
+
+ if (png_ptr->buffer_size < png_ptr->current_text_left)
+ text_size = png_ptr->buffer_size;
+
+ else
+ text_size = png_ptr->current_text_left;
+
+ png_crc_read(png_ptr, (png_bytep)png_ptr->current_text_ptr, text_size);
+ png_ptr->current_text_left -= text_size;
+ png_ptr->current_text_ptr += text_size;
+ }
+ if (!(png_ptr->current_text_left))
+ {
+ png_textp text_ptr;
+ png_charp key;
+ int comp_flag;
+ png_charp lang;
+ png_charp lang_key;
+ png_charp text;
+ int ret;
+
+ if (png_ptr->buffer_size < 4)
+ {
+ png_push_save_buffer(png_ptr);
+ return;
+ }
+
+ png_push_crc_finish(png_ptr);
+
+#ifdef PNG_MAX_MALLOC_64K
+ if (png_ptr->skip_length)
+ return;
+#endif
+
+ key = png_ptr->current_text;
+
+ for (lang = key; *lang; lang++)
+ /* Empty loop */ ;
+
+ if (lang < key + png_ptr->current_text_size - 3)
+ lang++;
+
+ comp_flag = *lang++;
+ lang++; /* Skip comp_type, always zero */
+
+ for (lang_key = lang; *lang_key; lang_key++)
+ /* Empty loop */ ;
+
+ lang_key++; /* Skip NUL separator */
+
+ text=lang_key;
+
+ if (lang_key < key + png_ptr->current_text_size - 1)
+ {
+ for (; *text; text++)
+ /* Empty loop */ ;
+ }
+
+ if (text < key + png_ptr->current_text_size)
+ text++;
+
+ text_ptr = (png_textp)png_malloc(png_ptr,
+ (png_uint_32)png_sizeof(png_text));
+
+ text_ptr->compression = comp_flag + 2;
+ text_ptr->key = key;
+ text_ptr->lang = lang;
+ text_ptr->lang_key = lang_key;
+ text_ptr->text = text;
+ text_ptr->text_length = 0;
+ text_ptr->itxt_length = png_strlen(text);
+
+ ret = png_set_text_2(png_ptr, info_ptr, text_ptr, 1);
+
+ png_ptr->current_text = NULL;
+
+ png_free(png_ptr, text_ptr);
+ if (ret)
+ png_warning(png_ptr, "Insufficient memory to store iTXt chunk.");
+ }
+}
+#endif
+
+/* This function is called when we haven't found a handler for this
+ * chunk. If there isn't a problem with the chunk itself (ie a bad chunk
+ * name or a critical chunk), the chunk is (currently) silently ignored.
+ */
+void /* PRIVATE */
+png_push_handle_unknown(png_structp png_ptr, png_infop info_ptr, png_uint_32
+ length)
+{
+ png_uint_32 skip = 0;
+
+ if (!(png_ptr->chunk_name[0] & 0x20))
+ {
+#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
+ if (png_handle_as_unknown(png_ptr, png_ptr->chunk_name) !=
+ PNG_HANDLE_CHUNK_ALWAYS
+#ifdef PNG_READ_USER_CHUNKS_SUPPORTED
+ && png_ptr->read_user_chunk_fn == NULL
+#endif
+ )
+#endif
+ png_chunk_error(png_ptr, "unknown critical chunk");
+
+ info_ptr = info_ptr; /* To quiet some compiler warnings */
+ }
+
+#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
+ if (png_ptr->flags & PNG_FLAG_KEEP_UNKNOWN_CHUNKS)
+ {
+#ifdef PNG_MAX_MALLOC_64K
+ if (length > (png_uint_32)65535L)
+ {
+ png_warning(png_ptr, "unknown chunk too large to fit in memory");
+ skip = length - (png_uint_32)65535L;
+ length = (png_uint_32)65535L;
+ }
+#endif
+ png_memcpy((png_charp)png_ptr->unknown_chunk.name,
+ (png_charp)png_ptr->chunk_name,
+ png_sizeof(png_ptr->unknown_chunk.name));
+ png_ptr->unknown_chunk.name[png_sizeof(png_ptr->unknown_chunk.name) - 1]
+ = '\0';
+
+ png_ptr->unknown_chunk.size = (png_size_t)length;
+
+ if (length == 0)
+ png_ptr->unknown_chunk.data = NULL;
+
+ else
+ {
+ png_ptr->unknown_chunk.data = (png_bytep)png_malloc(png_ptr,
+ (png_uint_32)length);
+ png_crc_read(png_ptr, (png_bytep)png_ptr->unknown_chunk.data, length);
+ }
+
+#ifdef PNG_READ_USER_CHUNKS_SUPPORTED
+ if (png_ptr->read_user_chunk_fn != NULL)
+ {
+ /* Callback to user unknown chunk handler */
+ int ret;
+ ret = (*(png_ptr->read_user_chunk_fn))
+ (png_ptr, &png_ptr->unknown_chunk);
+
+ if (ret < 0)
+ png_chunk_error(png_ptr, "error in user chunk");
+
+ if (ret == 0)
+ {
+ if (!(png_ptr->chunk_name[0] & 0x20))
+ if (png_handle_as_unknown(png_ptr, png_ptr->chunk_name) !=
+ PNG_HANDLE_CHUNK_ALWAYS)
+ png_chunk_error(png_ptr, "unknown critical chunk");
+ png_set_unknown_chunks(png_ptr, info_ptr,
+ &png_ptr->unknown_chunk, 1);
+ }
+ }
+
+ else
+#endif
+ png_set_unknown_chunks(png_ptr, info_ptr, &png_ptr->unknown_chunk, 1);
+ png_free(png_ptr, png_ptr->unknown_chunk.data);
+ png_ptr->unknown_chunk.data = NULL;
+ }
+
+ else
+#endif
+ skip=length;
+ png_push_crc_skip(png_ptr, skip);
+}
+
+void /* PRIVATE */
+png_push_have_info(png_structp png_ptr, png_infop info_ptr)
+{
+ if (png_ptr->info_fn != NULL)
+ (*(png_ptr->info_fn))(png_ptr, info_ptr);
+}
+
+void /* PRIVATE */
+png_push_have_end(png_structp png_ptr, png_infop info_ptr)
+{
+ if (png_ptr->end_fn != NULL)
+ (*(png_ptr->end_fn))(png_ptr, info_ptr);
+}
+
+void /* PRIVATE */
+png_push_have_row(png_structp png_ptr, png_bytep row)
+{
+ if (png_ptr->row_fn != NULL)
+ (*(png_ptr->row_fn))(png_ptr, row, png_ptr->row_number,
+ (int)png_ptr->pass);
+}
+
+void PNGAPI
+png_progressive_combine_row (png_structp png_ptr,
+ png_bytep old_row, png_bytep new_row)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_CONST int FARDATA png_pass_dsp_mask[7] =
+ {0xff, 0x0f, 0xff, 0x33, 0xff, 0x55, 0xff};
+#endif
+
+ if (png_ptr == NULL)
+ return;
+
+ if (new_row != NULL) /* new_row must == png_ptr->row_buf here. */
+ png_combine_row(png_ptr, old_row, png_pass_dsp_mask[png_ptr->pass]);
+}
+
+void PNGAPI
+png_set_progressive_read_fn(png_structp png_ptr, png_voidp progressive_ptr,
+ png_progressive_info_ptr info_fn, png_progressive_row_ptr row_fn,
+ png_progressive_end_ptr end_fn)
+{
+ if (png_ptr == NULL)
+ return;
+
+ png_ptr->info_fn = info_fn;
+ png_ptr->row_fn = row_fn;
+ png_ptr->end_fn = end_fn;
+
+ png_set_read_fn(png_ptr, progressive_ptr, png_push_fill_buffer);
+}
+
+png_voidp PNGAPI
+png_get_progressive_ptr(png_structp png_ptr)
+{
+ if (png_ptr == NULL)
+ return (NULL);
+
+ return png_ptr->io_ptr;
+}
+#endif /* PNG_PROGRESSIVE_READ_SUPPORTED */
diff --git a/trunk/src/third_party/libpng/pngread.c b/trunk/src/third_party/libpng/pngread.c
new file mode 100644
index 0000000..6207624
--- /dev/null
+++ b/trunk/src/third_party/libpng/pngread.c
@@ -0,0 +1,1528 @@
+
+/* pngread.c - read a PNG file
+ *
+ * Last changed in libpng 1.2.44 [June 26, 2010]
+ * Copyright (c) 1998-2010 Glenn Randers-Pehrson
+ * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger)
+ * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.)
+ *
+ * This code is released under the libpng license.
+ * For conditions of distribution and use, see the disclaimer
+ * and license in png.h
+ *
+ * This file contains routines that an application calls directly to
+ * read a PNG file or stream.
+ */
+
+#define PNG_INTERNAL
+#define PNG_NO_PEDANTIC_WARNINGS
+#include "png.h"
+#ifdef PNG_READ_SUPPORTED
+
+
+/* Create a PNG structure for reading, and allocate any memory needed. */
+png_structp PNGAPI
+png_create_read_struct(png_const_charp user_png_ver, png_voidp error_ptr,
+ png_error_ptr error_fn, png_error_ptr warn_fn)
+{
+
+#ifdef PNG_USER_MEM_SUPPORTED
+ return (png_create_read_struct_2(user_png_ver, error_ptr, error_fn,
+ warn_fn, png_voidp_NULL, png_malloc_ptr_NULL, png_free_ptr_NULL));
+}
+
+/* Alternate create PNG structure for reading, and allocate any memory
+ * needed.
+ */
+png_structp PNGAPI
+png_create_read_struct_2(png_const_charp user_png_ver, png_voidp error_ptr,
+ png_error_ptr error_fn, png_error_ptr warn_fn, png_voidp mem_ptr,
+ png_malloc_ptr malloc_fn, png_free_ptr free_fn)
+{
+#endif /* PNG_USER_MEM_SUPPORTED */
+
+#ifdef PNG_SETJMP_SUPPORTED
+ volatile
+#endif
+ png_structp png_ptr;
+
+#ifdef PNG_SETJMP_SUPPORTED
+#ifdef USE_FAR_KEYWORD
+ jmp_buf jmpbuf;
+#endif
+#endif
+
+ int i;
+
+ png_debug(1, "in png_create_read_struct");
+
+#ifdef PNG_USER_MEM_SUPPORTED
+ png_ptr = (png_structp)png_create_struct_2(PNG_STRUCT_PNG,
+ (png_malloc_ptr)malloc_fn, (png_voidp)mem_ptr);
+#else
+ png_ptr = (png_structp)png_create_struct(PNG_STRUCT_PNG);
+#endif
+ if (png_ptr == NULL)
+ return (NULL);
+
+ /* Added at libpng-1.2.6 */
+#ifdef PNG_USER_LIMITS_SUPPORTED
+ png_ptr->user_width_max = PNG_USER_WIDTH_MAX;
+ png_ptr->user_height_max = PNG_USER_HEIGHT_MAX;
+# ifdef PNG_USER_CHUNK_CACHE_MAX
+ /* Added at libpng-1.2.43 and 1.4.0 */
+ png_ptr->user_chunk_cache_max = PNG_USER_CHUNK_CACHE_MAX;
+# endif
+# ifdef PNG_SET_USER_CHUNK_MALLOC_MAX
+ /* Added at libpng-1.2.43 and 1.4.1 */
+ png_ptr->user_chunk_malloc_max = PNG_USER_CHUNK_MALLOC_MAX;
+# endif
+#endif
+
+#ifdef PNG_SETJMP_SUPPORTED
+#ifdef USE_FAR_KEYWORD
+ if (setjmp(jmpbuf))
+#else
+ if (setjmp(png_ptr->jmpbuf))
+#endif
+ {
+ png_free(png_ptr, png_ptr->zbuf);
+ png_ptr->zbuf = NULL;
+#ifdef PNG_USER_MEM_SUPPORTED
+ png_destroy_struct_2((png_voidp)png_ptr,
+ (png_free_ptr)free_fn, (png_voidp)mem_ptr);
+#else
+ png_destroy_struct((png_voidp)png_ptr);
+#endif
+ return (NULL);
+ }
+#ifdef USE_FAR_KEYWORD
+ png_memcpy(png_ptr->jmpbuf, jmpbuf, png_sizeof(jmp_buf));
+#endif
+#endif /* PNG_SETJMP_SUPPORTED */
+
+#ifdef PNG_USER_MEM_SUPPORTED
+ png_set_mem_fn(png_ptr, mem_ptr, malloc_fn, free_fn);
+#endif
+
+ png_set_error_fn(png_ptr, error_ptr, error_fn, warn_fn);
+
+ if (user_png_ver)
+ {
+ i = 0;
+ do
+ {
+ if (user_png_ver[i] != png_libpng_ver[i])
+ png_ptr->flags |= PNG_FLAG_LIBRARY_MISMATCH;
+ } while (png_libpng_ver[i++]);
+ }
+ else
+ png_ptr->flags |= PNG_FLAG_LIBRARY_MISMATCH;
+
+
+ if (png_ptr->flags & PNG_FLAG_LIBRARY_MISMATCH)
+ {
+ /* Libpng 0.90 and later are binary incompatible with libpng 0.89, so
+ * we must recompile any applications that use any older library version.
+ * For versions after libpng 1.0, we will be compatible, so we need
+ * only check the first digit.
+ */
+ if (user_png_ver == NULL || user_png_ver[0] != png_libpng_ver[0] ||
+ (user_png_ver[0] == '1' && user_png_ver[2] != png_libpng_ver[2]) ||
+ (user_png_ver[0] == '0' && user_png_ver[2] < '9'))
+ {
+#if defined(PNG_STDIO_SUPPORTED) && !defined(_WIN32_WCE)
+ char msg[80];
+ if (user_png_ver)
+ {
+ png_snprintf(msg, 80,
+ "Application was compiled with png.h from libpng-%.20s",
+ user_png_ver);
+ png_warning(png_ptr, msg);
+ }
+ png_snprintf(msg, 80,
+ "Application is running with png.c from libpng-%.20s",
+ png_libpng_ver);
+ png_warning(png_ptr, msg);
+#endif
+#ifdef PNG_ERROR_NUMBERS_SUPPORTED
+ png_ptr->flags = 0;
+#endif
+ png_error(png_ptr,
+ "Incompatible libpng version in application and library");
+ }
+ }
+
+ /* Initialize zbuf - compression buffer */
+ png_ptr->zbuf_size = PNG_ZBUF_SIZE;
+ png_ptr->zbuf = (png_bytep)png_malloc(png_ptr,
+ (png_uint_32)png_ptr->zbuf_size);
+ png_ptr->zstream.zalloc = png_zalloc;
+ png_ptr->zstream.zfree = png_zfree;
+ png_ptr->zstream.opaque = (voidpf)png_ptr;
+
+ switch (inflateInit(&png_ptr->zstream))
+ {
+ case Z_OK: /* Do nothing */ break;
+ case Z_MEM_ERROR:
+ case Z_STREAM_ERROR: png_error(png_ptr, "zlib memory error");
+ break;
+ case Z_VERSION_ERROR: png_error(png_ptr, "zlib version error");
+ break;
+ default: png_error(png_ptr, "Unknown zlib error");
+ }
+
+
+ png_ptr->zstream.next_out = png_ptr->zbuf;
+ png_ptr->zstream.avail_out = (uInt)png_ptr->zbuf_size;
+
+ png_set_read_fn(png_ptr, png_voidp_NULL, png_rw_ptr_NULL);
+
+#ifdef PNG_SETJMP_SUPPORTED
+/* Applications that neglect to set up their own setjmp() and then
+ encounter a png_error() will longjmp here. Since the jmpbuf is
+ then meaningless we abort instead of returning. */
+#ifdef USE_FAR_KEYWORD
+ if (setjmp(jmpbuf))
+ PNG_ABORT();
+ png_memcpy(png_ptr->jmpbuf, jmpbuf, png_sizeof(jmp_buf));
+#else
+ if (setjmp(png_ptr->jmpbuf))
+ PNG_ABORT();
+#endif
+#endif /* PNG_SETJMP_SUPPORTED */
+
+ return (png_ptr);
+}
+
+#if defined(PNG_1_0_X) || defined(PNG_1_2_X)
+/* Initialize PNG structure for reading, and allocate any memory needed.
+ * This interface is deprecated in favour of the png_create_read_struct(),
+ * and it will disappear as of libpng-1.3.0.
+ */
+#undef png_read_init
+void PNGAPI
+png_read_init(png_structp png_ptr)
+{
+ /* We only come here via pre-1.0.7-compiled applications */
+ png_read_init_2(png_ptr, "1.0.6 or earlier", 0, 0);
+}
+
+void PNGAPI
+png_read_init_2(png_structp png_ptr, png_const_charp user_png_ver,
+ png_size_t png_struct_size, png_size_t png_info_size)
+{
+ /* We only come here via pre-1.0.12-compiled applications */
+ if (png_ptr == NULL)
+ return;
+#if defined(PNG_STDIO_SUPPORTED) && !defined(_WIN32_WCE)
+ if (png_sizeof(png_struct) > png_struct_size ||
+ png_sizeof(png_info) > png_info_size)
+ {
+ char msg[80];
+ png_ptr->warning_fn = NULL;
+ if (user_png_ver)
+ {
+ png_snprintf(msg, 80,
+ "Application was compiled with png.h from libpng-%.20s",
+ user_png_ver);
+ png_warning(png_ptr, msg);
+ }
+ png_snprintf(msg, 80,
+ "Application is running with png.c from libpng-%.20s",
+ png_libpng_ver);
+ png_warning(png_ptr, msg);
+ }
+#endif
+ if (png_sizeof(png_struct) > png_struct_size)
+ {
+ png_ptr->error_fn = NULL;
+#ifdef PNG_ERROR_NUMBERS_SUPPORTED
+ png_ptr->flags = 0;
+#endif
+ png_error(png_ptr,
+ "The png struct allocated by the application for reading is"
+ " too small.");
+ }
+ if (png_sizeof(png_info) > png_info_size)
+ {
+ png_ptr->error_fn = NULL;
+#ifdef PNG_ERROR_NUMBERS_SUPPORTED
+ png_ptr->flags = 0;
+#endif
+ png_error(png_ptr,
+ "The info struct allocated by application for reading is"
+ " too small.");
+ }
+ png_read_init_3(&png_ptr, user_png_ver, png_struct_size);
+}
+#endif /* PNG_1_0_X || PNG_1_2_X */
+
+void PNGAPI
+png_read_init_3(png_structpp ptr_ptr, png_const_charp user_png_ver,
+ png_size_t png_struct_size)
+{
+#ifdef PNG_SETJMP_SUPPORTED
+ jmp_buf tmp_jmp; /* to save current jump buffer */
+#endif
+
+ int i = 0;
+
+ png_structp png_ptr=*ptr_ptr;
+
+ if (png_ptr == NULL)
+ return;
+
+ do
+ {
+ if (user_png_ver[i] != png_libpng_ver[i])
+ {
+#ifdef PNG_LEGACY_SUPPORTED
+ png_ptr->flags |= PNG_FLAG_LIBRARY_MISMATCH;
+#else
+ png_ptr->warning_fn = NULL;
+ png_warning(png_ptr,
+ "Application uses deprecated png_read_init() and should be"
+ " recompiled.");
+ break;
+#endif
+ }
+ } while (png_libpng_ver[i++]);
+
+ png_debug(1, "in png_read_init_3");
+
+#ifdef PNG_SETJMP_SUPPORTED
+ /* Save jump buffer and error functions */
+ png_memcpy(tmp_jmp, png_ptr->jmpbuf, png_sizeof(jmp_buf));
+#endif
+
+ if (png_sizeof(png_struct) > png_struct_size)
+ {
+ png_destroy_struct(png_ptr);
+ *ptr_ptr = (png_structp)png_create_struct(PNG_STRUCT_PNG);
+ png_ptr = *ptr_ptr;
+ }
+
+ /* Reset all variables to 0 */
+ png_memset(png_ptr, 0, png_sizeof(png_struct));
+
+#ifdef PNG_SETJMP_SUPPORTED
+ /* Restore jump buffer */
+ png_memcpy(png_ptr->jmpbuf, tmp_jmp, png_sizeof(jmp_buf));
+#endif
+
+ /* Added at libpng-1.2.6 */
+#ifdef PNG_SET_USER_LIMITS_SUPPORTED
+ png_ptr->user_width_max = PNG_USER_WIDTH_MAX;
+ png_ptr->user_height_max = PNG_USER_HEIGHT_MAX;
+#endif
+
+ /* Initialize zbuf - compression buffer */
+ png_ptr->zbuf_size = PNG_ZBUF_SIZE;
+ png_ptr->zstream.zalloc = png_zalloc;
+ png_ptr->zbuf = (png_bytep)png_malloc(png_ptr,
+ (png_uint_32)png_ptr->zbuf_size);
+ png_ptr->zstream.zalloc = png_zalloc;
+ png_ptr->zstream.zfree = png_zfree;
+ png_ptr->zstream.opaque = (voidpf)png_ptr;
+
+ switch (inflateInit(&png_ptr->zstream))
+ {
+ case Z_OK: /* Do nothing */ break;
+ case Z_STREAM_ERROR: png_error(png_ptr, "zlib memory error"); break;
+ case Z_VERSION_ERROR: png_error(png_ptr, "zlib version error");
+ break;
+ default: png_error(png_ptr, "Unknown zlib error");
+ }
+
+ png_ptr->zstream.next_out = png_ptr->zbuf;
+ png_ptr->zstream.avail_out = (uInt)png_ptr->zbuf_size;
+
+ png_set_read_fn(png_ptr, png_voidp_NULL, png_rw_ptr_NULL);
+}
+
+#ifdef PNG_SEQUENTIAL_READ_SUPPORTED
+/* Read the information before the actual image data. This has been
+ * changed in v0.90 to allow reading a file that already has the magic
+ * bytes read from the stream. You can tell libpng how many bytes have
+ * been read from the beginning of the stream (up to the maximum of 8)
+ * via png_set_sig_bytes(), and we will only check the remaining bytes
+ * here. The application can then have access to the signature bytes we
+ * read if it is determined that this isn't a valid PNG file.
+ */
+void PNGAPI
+png_read_info(png_structp png_ptr, png_infop info_ptr)
+{
+ png_debug(1, "in png_read_info");
+
+ if (png_ptr == NULL || info_ptr == NULL)
+ return;
+
+ /* If we haven't checked all of the PNG signature bytes, do so now. */
+ if (png_ptr->sig_bytes < 8)
+ {
+ png_size_t num_checked = png_ptr->sig_bytes,
+ num_to_check = 8 - num_checked;
+
+ png_read_data(png_ptr, &(info_ptr->signature[num_checked]), num_to_check);
+ png_ptr->sig_bytes = 8;
+
+ if (png_sig_cmp(info_ptr->signature, num_checked, num_to_check))
+ {
+ if (num_checked < 4 &&
+ png_sig_cmp(info_ptr->signature, num_checked, num_to_check - 4))
+ png_error(png_ptr, "Not a PNG file");
+ else
+ png_error(png_ptr, "PNG file corrupted by ASCII conversion");
+ }
+ if (num_checked < 3)
+ png_ptr->mode |= PNG_HAVE_PNG_SIGNATURE;
+ }
+
+ for (;;)
+ {
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_CONST PNG_IHDR;
+ PNG_CONST PNG_IDAT;
+ PNG_CONST PNG_IEND;
+ PNG_CONST PNG_PLTE;
+#ifdef PNG_READ_bKGD_SUPPORTED
+ PNG_CONST PNG_bKGD;
+#endif
+#ifdef PNG_READ_cHRM_SUPPORTED
+ PNG_CONST PNG_cHRM;
+#endif
+#ifdef PNG_READ_gAMA_SUPPORTED
+ PNG_CONST PNG_gAMA;
+#endif
+#ifdef PNG_READ_hIST_SUPPORTED
+ PNG_CONST PNG_hIST;
+#endif
+#ifdef PNG_READ_iCCP_SUPPORTED
+ PNG_CONST PNG_iCCP;
+#endif
+#ifdef PNG_READ_iTXt_SUPPORTED
+ PNG_CONST PNG_iTXt;
+#endif
+#ifdef PNG_READ_oFFs_SUPPORTED
+ PNG_CONST PNG_oFFs;
+#endif
+#ifdef PNG_READ_pCAL_SUPPORTED
+ PNG_CONST PNG_pCAL;
+#endif
+#ifdef PNG_READ_pHYs_SUPPORTED
+ PNG_CONST PNG_pHYs;
+#endif
+#ifdef PNG_READ_sBIT_SUPPORTED
+ PNG_CONST PNG_sBIT;
+#endif
+#ifdef PNG_READ_sCAL_SUPPORTED
+ PNG_CONST PNG_sCAL;
+#endif
+#ifdef PNG_READ_sPLT_SUPPORTED
+ PNG_CONST PNG_sPLT;
+#endif
+#ifdef PNG_READ_sRGB_SUPPORTED
+ PNG_CONST PNG_sRGB;
+#endif
+#ifdef PNG_READ_tEXt_SUPPORTED
+ PNG_CONST PNG_tEXt;
+#endif
+#ifdef PNG_READ_tIME_SUPPORTED
+ PNG_CONST PNG_tIME;
+#endif
+#ifdef PNG_READ_tRNS_SUPPORTED
+ PNG_CONST PNG_tRNS;
+#endif
+#ifdef PNG_READ_zTXt_SUPPORTED
+ PNG_CONST PNG_zTXt;
+#endif
+#endif /* PNG_USE_LOCAL_ARRAYS */
+ png_uint_32 length = png_read_chunk_header(png_ptr);
+ PNG_CONST png_bytep chunk_name = png_ptr->chunk_name;
+
+ /* This should be a binary subdivision search or a hash for
+ * matching the chunk name rather than a linear search.
+ */
+ if (!png_memcmp(chunk_name, png_IDAT, 4))
+ if (png_ptr->mode & PNG_AFTER_IDAT)
+ png_ptr->mode |= PNG_HAVE_CHUNK_AFTER_IDAT;
+
+ if (!png_memcmp(chunk_name, png_IHDR, 4))
+ png_handle_IHDR(png_ptr, info_ptr, length);
+ else if (!png_memcmp(chunk_name, png_IEND, 4))
+ png_handle_IEND(png_ptr, info_ptr, length);
+#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
+ else if (png_handle_as_unknown(png_ptr, chunk_name))
+ {
+ if (!png_memcmp(chunk_name, png_IDAT, 4))
+ png_ptr->mode |= PNG_HAVE_IDAT;
+ png_handle_unknown(png_ptr, info_ptr, length);
+ if (!png_memcmp(chunk_name, png_PLTE, 4))
+ png_ptr->mode |= PNG_HAVE_PLTE;
+ else if (!png_memcmp(chunk_name, png_IDAT, 4))
+ {
+ if (!(png_ptr->mode & PNG_HAVE_IHDR))
+ png_error(png_ptr, "Missing IHDR before IDAT");
+ else if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE &&
+ !(png_ptr->mode & PNG_HAVE_PLTE))
+ png_error(png_ptr, "Missing PLTE before IDAT");
+ break;
+ }
+ }
+#endif
+ else if (!png_memcmp(chunk_name, png_PLTE, 4))
+ png_handle_PLTE(png_ptr, info_ptr, length);
+ else if (!png_memcmp(chunk_name, png_IDAT, 4))
+ {
+ if (!(png_ptr->mode & PNG_HAVE_IHDR))
+ png_error(png_ptr, "Missing IHDR before IDAT");
+ else if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE &&
+ !(png_ptr->mode & PNG_HAVE_PLTE))
+ png_error(png_ptr, "Missing PLTE before IDAT");
+
+ png_ptr->idat_size = length;
+ png_ptr->mode |= PNG_HAVE_IDAT;
+ break;
+ }
+#ifdef PNG_READ_bKGD_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_bKGD, 4))
+ png_handle_bKGD(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_cHRM_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_cHRM, 4))
+ png_handle_cHRM(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_gAMA_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_gAMA, 4))
+ png_handle_gAMA(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_hIST_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_hIST, 4))
+ png_handle_hIST(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_oFFs_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_oFFs, 4))
+ png_handle_oFFs(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_pCAL_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_pCAL, 4))
+ png_handle_pCAL(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_sCAL_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_sCAL, 4))
+ png_handle_sCAL(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_pHYs_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_pHYs, 4))
+ png_handle_pHYs(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_sBIT_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_sBIT, 4))
+ png_handle_sBIT(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_sRGB_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_sRGB, 4))
+ png_handle_sRGB(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_iCCP_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_iCCP, 4))
+ png_handle_iCCP(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_sPLT_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_sPLT, 4))
+ png_handle_sPLT(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_tEXt_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_tEXt, 4))
+ png_handle_tEXt(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_tIME_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_tIME, 4))
+ png_handle_tIME(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_tRNS_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_tRNS, 4))
+ png_handle_tRNS(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_zTXt_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_zTXt, 4))
+ png_handle_zTXt(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_iTXt_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_iTXt, 4))
+ png_handle_iTXt(png_ptr, info_ptr, length);
+#endif
+ else
+ png_handle_unknown(png_ptr, info_ptr, length);
+ }
+}
+#endif /* PNG_SEQUENTIAL_READ_SUPPORTED */
+
+/* Optional call to update the users info_ptr structure */
+void PNGAPI
+png_read_update_info(png_structp png_ptr, png_infop info_ptr)
+{
+ png_debug(1, "in png_read_update_info");
+
+ if (png_ptr == NULL)
+ return;
+ if (!(png_ptr->flags & PNG_FLAG_ROW_INIT))
+ png_read_start_row(png_ptr);
+ else
+ png_warning(png_ptr,
+ "Ignoring extra png_read_update_info() call; row buffer not reallocated");
+
+ png_read_transform_info(png_ptr, info_ptr);
+}
+
+#ifdef PNG_SEQUENTIAL_READ_SUPPORTED
+/* Initialize palette, background, etc, after transformations
+ * are set, but before any reading takes place. This allows
+ * the user to obtain a gamma-corrected palette, for example.
+ * If the user doesn't call this, we will do it ourselves.
+ */
+void PNGAPI
+png_start_read_image(png_structp png_ptr)
+{
+ png_debug(1, "in png_start_read_image");
+
+ if (png_ptr == NULL)
+ return;
+ if (!(png_ptr->flags & PNG_FLAG_ROW_INIT))
+ png_read_start_row(png_ptr);
+}
+#endif /* PNG_SEQUENTIAL_READ_SUPPORTED */
+
+#ifdef PNG_SEQUENTIAL_READ_SUPPORTED
+void PNGAPI
+png_read_row(png_structp png_ptr, png_bytep row, png_bytep dsp_row)
+{
+ PNG_CONST PNG_IDAT;
+ PNG_CONST int png_pass_dsp_mask[7] = {0xff, 0x0f, 0xff, 0x33, 0xff, 0x55,
+ 0xff};
+ PNG_CONST int png_pass_mask[7] = {0x80, 0x08, 0x88, 0x22, 0xaa, 0x55, 0xff};
+ int ret;
+
+ if (png_ptr == NULL)
+ return;
+
+ png_debug2(1, "in png_read_row (row %lu, pass %d)",
+ png_ptr->row_number, png_ptr->pass);
+
+ if (!(png_ptr->flags & PNG_FLAG_ROW_INIT))
+ png_read_start_row(png_ptr);
+ if (png_ptr->row_number == 0 && png_ptr->pass == 0)
+ {
+ /* Check for transforms that have been set but were defined out */
+#if defined(PNG_WRITE_INVERT_SUPPORTED) && !defined(PNG_READ_INVERT_SUPPORTED)
+ if (png_ptr->transformations & PNG_INVERT_MONO)
+ png_warning(png_ptr, "PNG_READ_INVERT_SUPPORTED is not defined.");
+#endif
+#if defined(PNG_WRITE_FILLER_SUPPORTED) && !defined(PNG_READ_FILLER_SUPPORTED)
+ if (png_ptr->transformations & PNG_FILLER)
+ png_warning(png_ptr, "PNG_READ_FILLER_SUPPORTED is not defined.");
+#endif
+#if defined(PNG_WRITE_PACKSWAP_SUPPORTED) && \
+ !defined(PNG_READ_PACKSWAP_SUPPORTED)
+ if (png_ptr->transformations & PNG_PACKSWAP)
+ png_warning(png_ptr, "PNG_READ_PACKSWAP_SUPPORTED is not defined.");
+#endif
+#if defined(PNG_WRITE_PACK_SUPPORTED) && !defined(PNG_READ_PACK_SUPPORTED)
+ if (png_ptr->transformations & PNG_PACK)
+ png_warning(png_ptr, "PNG_READ_PACK_SUPPORTED is not defined.");
+#endif
+#if defined(PNG_WRITE_SHIFT_SUPPORTED) && !defined(PNG_READ_SHIFT_SUPPORTED)
+ if (png_ptr->transformations & PNG_SHIFT)
+ png_warning(png_ptr, "PNG_READ_SHIFT_SUPPORTED is not defined.");
+#endif
+#if defined(PNG_WRITE_BGR_SUPPORTED) && !defined(PNG_READ_BGR_SUPPORTED)
+ if (png_ptr->transformations & PNG_BGR)
+ png_warning(png_ptr, "PNG_READ_BGR_SUPPORTED is not defined.");
+#endif
+#if defined(PNG_WRITE_SWAP_SUPPORTED) && !defined(PNG_READ_SWAP_SUPPORTED)
+ if (png_ptr->transformations & PNG_SWAP_BYTES)
+ png_warning(png_ptr, "PNG_READ_SWAP_SUPPORTED is not defined.");
+#endif
+ }
+
+#ifdef PNG_READ_INTERLACING_SUPPORTED
+ /* If interlaced and we do not need a new row, combine row and return */
+ if (png_ptr->interlaced && (png_ptr->transformations & PNG_INTERLACE))
+ {
+ switch (png_ptr->pass)
+ {
+ case 0:
+ if (png_ptr->row_number & 0x07)
+ {
+ if (dsp_row != NULL)
+ png_combine_row(png_ptr, dsp_row,
+ png_pass_dsp_mask[png_ptr->pass]);
+ png_read_finish_row(png_ptr);
+ return;
+ }
+ break;
+ case 1:
+ if ((png_ptr->row_number & 0x07) || png_ptr->width < 5)
+ {
+ if (dsp_row != NULL)
+ png_combine_row(png_ptr, dsp_row,
+ png_pass_dsp_mask[png_ptr->pass]);
+ png_read_finish_row(png_ptr);
+ return;
+ }
+ break;
+ case 2:
+ if ((png_ptr->row_number & 0x07) != 4)
+ {
+ if (dsp_row != NULL && (png_ptr->row_number & 4))
+ png_combine_row(png_ptr, dsp_row,
+ png_pass_dsp_mask[png_ptr->pass]);
+ png_read_finish_row(png_ptr);
+ return;
+ }
+ break;
+ case 3:
+ if ((png_ptr->row_number & 3) || png_ptr->width < 3)
+ {
+ if (dsp_row != NULL)
+ png_combine_row(png_ptr, dsp_row,
+ png_pass_dsp_mask[png_ptr->pass]);
+ png_read_finish_row(png_ptr);
+ return;
+ }
+ break;
+ case 4:
+ if ((png_ptr->row_number & 3) != 2)
+ {
+ if (dsp_row != NULL && (png_ptr->row_number & 2))
+ png_combine_row(png_ptr, dsp_row,
+ png_pass_dsp_mask[png_ptr->pass]);
+ png_read_finish_row(png_ptr);
+ return;
+ }
+ break;
+ case 5:
+ if ((png_ptr->row_number & 1) || png_ptr->width < 2)
+ {
+ if (dsp_row != NULL)
+ png_combine_row(png_ptr, dsp_row,
+ png_pass_dsp_mask[png_ptr->pass]);
+ png_read_finish_row(png_ptr);
+ return;
+ }
+ break;
+ case 6:
+ if (!(png_ptr->row_number & 1))
+ {
+ png_read_finish_row(png_ptr);
+ return;
+ }
+ break;
+ }
+ }
+#endif
+
+ if (!(png_ptr->mode & PNG_HAVE_IDAT))
+ png_error(png_ptr, "Invalid attempt to read row data");
+
+ png_ptr->zstream.next_out = png_ptr->row_buf;
+ png_ptr->zstream.avail_out =
+ (uInt)(PNG_ROWBYTES(png_ptr->pixel_depth,
+ png_ptr->iwidth) + 1);
+ do
+ {
+ if (!(png_ptr->zstream.avail_in))
+ {
+ while (!png_ptr->idat_size)
+ {
+ png_crc_finish(png_ptr, 0);
+
+ png_ptr->idat_size = png_read_chunk_header(png_ptr);
+ if (png_memcmp(png_ptr->chunk_name, png_IDAT, 4))
+ png_error(png_ptr, "Not enough image data");
+ }
+ png_ptr->zstream.avail_in = (uInt)png_ptr->zbuf_size;
+ png_ptr->zstream.next_in = png_ptr->zbuf;
+ if (png_ptr->zbuf_size > png_ptr->idat_size)
+ png_ptr->zstream.avail_in = (uInt)png_ptr->idat_size;
+ png_crc_read(png_ptr, png_ptr->zbuf,
+ (png_size_t)png_ptr->zstream.avail_in);
+ png_ptr->idat_size -= png_ptr->zstream.avail_in;
+ }
+ ret = inflate(&png_ptr->zstream, Z_PARTIAL_FLUSH);
+ if (ret == Z_STREAM_END)
+ {
+ if (png_ptr->zstream.avail_out || png_ptr->zstream.avail_in ||
+ png_ptr->idat_size)
+ png_error(png_ptr, "Extra compressed data");
+ png_ptr->mode |= PNG_AFTER_IDAT;
+ png_ptr->flags |= PNG_FLAG_ZLIB_FINISHED;
+ break;
+ }
+ if (ret != Z_OK)
+ png_error(png_ptr, png_ptr->zstream.msg ? png_ptr->zstream.msg :
+ "Decompression error");
+
+ } while (png_ptr->zstream.avail_out);
+
+ png_ptr->row_info.color_type = png_ptr->color_type;
+ png_ptr->row_info.width = png_ptr->iwidth;
+ png_ptr->row_info.channels = png_ptr->channels;
+ png_ptr->row_info.bit_depth = png_ptr->bit_depth;
+ png_ptr->row_info.pixel_depth = png_ptr->pixel_depth;
+ png_ptr->row_info.rowbytes = PNG_ROWBYTES(png_ptr->row_info.pixel_depth,
+ png_ptr->row_info.width);
+
+ if (png_ptr->row_buf[0])
+ png_read_filter_row(png_ptr, &(png_ptr->row_info),
+ png_ptr->row_buf + 1, png_ptr->prev_row + 1,
+ (int)(png_ptr->row_buf[0]));
+
+ png_memcpy_check(png_ptr, png_ptr->prev_row, png_ptr->row_buf,
+ png_ptr->rowbytes + 1);
+
+#ifdef PNG_MNG_FEATURES_SUPPORTED
+ if ((png_ptr->mng_features_permitted & PNG_FLAG_MNG_FILTER_64) &&
+ (png_ptr->filter_type == PNG_INTRAPIXEL_DIFFERENCING))
+ {
+ /* Intrapixel differencing */
+ png_do_read_intrapixel(&(png_ptr->row_info), png_ptr->row_buf + 1);
+ }
+#endif
+
+
+ if (png_ptr->transformations || (png_ptr->flags&PNG_FLAG_STRIP_ALPHA))
+ png_do_read_transformations(png_ptr);
+
+#ifdef PNG_READ_INTERLACING_SUPPORTED
+ /* Blow up interlaced rows to full size */
+ if (png_ptr->interlaced &&
+ (png_ptr->transformations & PNG_INTERLACE))
+ {
+ if (png_ptr->pass < 6)
+ /* Old interface (pre-1.0.9):
+ * png_do_read_interlace(&(png_ptr->row_info),
+ * png_ptr->row_buf + 1, png_ptr->pass, png_ptr->transformations);
+ */
+ png_do_read_interlace(png_ptr);
+
+ if (dsp_row != NULL)
+ png_combine_row(png_ptr, dsp_row,
+ png_pass_dsp_mask[png_ptr->pass]);
+ if (row != NULL)
+ png_combine_row(png_ptr, row,
+ png_pass_mask[png_ptr->pass]);
+ }
+ else
+#endif
+ {
+ if (row != NULL)
+ png_combine_row(png_ptr, row, 0xff);
+ if (dsp_row != NULL)
+ png_combine_row(png_ptr, dsp_row, 0xff);
+ }
+ png_read_finish_row(png_ptr);
+
+ if (png_ptr->read_row_fn != NULL)
+ (*(png_ptr->read_row_fn))(png_ptr, png_ptr->row_number, png_ptr->pass);
+}
+#endif /* PNG_SEQUENTIAL_READ_SUPPORTED */
+
+#ifdef PNG_SEQUENTIAL_READ_SUPPORTED
+/* Read one or more rows of image data. If the image is interlaced,
+ * and png_set_interlace_handling() has been called, the rows need to
+ * contain the contents of the rows from the previous pass. If the
+ * image has alpha or transparency, and png_handle_alpha()[*] has been
+ * called, the rows contents must be initialized to the contents of the
+ * screen.
+ *
+ * "row" holds the actual image, and pixels are placed in it
+ * as they arrive. If the image is displayed after each pass, it will
+ * appear to "sparkle" in. "display_row" can be used to display a
+ * "chunky" progressive image, with finer detail added as it becomes
+ * available. If you do not want this "chunky" display, you may pass
+ * NULL for display_row. If you do not want the sparkle display, and
+ * you have not called png_handle_alpha(), you may pass NULL for rows.
+ * If you have called png_handle_alpha(), and the image has either an
+ * alpha channel or a transparency chunk, you must provide a buffer for
+ * rows. In this case, you do not have to provide a display_row buffer
+ * also, but you may. If the image is not interlaced, or if you have
+ * not called png_set_interlace_handling(), the display_row buffer will
+ * be ignored, so pass NULL to it.
+ *
+ * [*] png_handle_alpha() does not exist yet, as of this version of libpng
+ */
+
+void PNGAPI
+png_read_rows(png_structp png_ptr, png_bytepp row,
+ png_bytepp display_row, png_uint_32 num_rows)
+{
+ png_uint_32 i;
+ png_bytepp rp;
+ png_bytepp dp;
+
+ png_debug(1, "in png_read_rows");
+
+ if (png_ptr == NULL)
+ return;
+ rp = row;
+ dp = display_row;
+ if (rp != NULL && dp != NULL)
+ for (i = 0; i < num_rows; i++)
+ {
+ png_bytep rptr = *rp++;
+ png_bytep dptr = *dp++;
+
+ png_read_row(png_ptr, rptr, dptr);
+ }
+ else if (rp != NULL)
+ for (i = 0; i < num_rows; i++)
+ {
+ png_bytep rptr = *rp;
+ png_read_row(png_ptr, rptr, png_bytep_NULL);
+ rp++;
+ }
+ else if (dp != NULL)
+ for (i = 0; i < num_rows; i++)
+ {
+ png_bytep dptr = *dp;
+ png_read_row(png_ptr, png_bytep_NULL, dptr);
+ dp++;
+ }
+}
+#endif /* PNG_SEQUENTIAL_READ_SUPPORTED */
+
+#ifdef PNG_SEQUENTIAL_READ_SUPPORTED
+/* Read the entire image. If the image has an alpha channel or a tRNS
+ * chunk, and you have called png_handle_alpha()[*], you will need to
+ * initialize the image to the current image that PNG will be overlaying.
+ * We set the num_rows again here, in case it was incorrectly set in
+ * png_read_start_row() by a call to png_read_update_info() or
+ * png_start_read_image() if png_set_interlace_handling() wasn't called
+ * prior to either of these functions like it should have been. You can
+ * only call this function once. If you desire to have an image for
+ * each pass of a interlaced image, use png_read_rows() instead.
+ *
+ * [*] png_handle_alpha() does not exist yet, as of this version of libpng
+ */
+void PNGAPI
+png_read_image(png_structp png_ptr, png_bytepp image)
+{
+ png_uint_32 i, image_height;
+ int pass, j;
+ png_bytepp rp;
+
+ png_debug(1, "in png_read_image");
+
+ if (png_ptr == NULL)
+ return;
+
+#ifdef PNG_READ_INTERLACING_SUPPORTED
+ pass = png_set_interlace_handling(png_ptr);
+#else
+ if (png_ptr->interlaced)
+ png_error(png_ptr,
+ "Cannot read interlaced image -- interlace handler disabled.");
+ pass = 1;
+#endif
+
+
+ image_height=png_ptr->height;
+ png_ptr->num_rows = image_height; /* Make sure this is set correctly */
+
+ for (j = 0; j < pass; j++)
+ {
+ rp = image;
+ for (i = 0; i < image_height; i++)
+ {
+ png_read_row(png_ptr, *rp, png_bytep_NULL);
+ rp++;
+ }
+ }
+}
+#endif /* PNG_SEQUENTIAL_READ_SUPPORTED */
+
+#ifdef PNG_SEQUENTIAL_READ_SUPPORTED
+/* Read the end of the PNG file. Will not read past the end of the
+ * file, will verify the end is accurate, and will read any comments
+ * or time information at the end of the file, if info is not NULL.
+ */
+void PNGAPI
+png_read_end(png_structp png_ptr, png_infop info_ptr)
+{
+ png_debug(1, "in png_read_end");
+
+ if (png_ptr == NULL)
+ return;
+ png_crc_finish(png_ptr, 0); /* Finish off CRC from last IDAT chunk */
+
+ do
+ {
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_CONST PNG_IHDR;
+ PNG_CONST PNG_IDAT;
+ PNG_CONST PNG_IEND;
+ PNG_CONST PNG_PLTE;
+#ifdef PNG_READ_bKGD_SUPPORTED
+ PNG_CONST PNG_bKGD;
+#endif
+#ifdef PNG_READ_cHRM_SUPPORTED
+ PNG_CONST PNG_cHRM;
+#endif
+#ifdef PNG_READ_gAMA_SUPPORTED
+ PNG_CONST PNG_gAMA;
+#endif
+#ifdef PNG_READ_hIST_SUPPORTED
+ PNG_CONST PNG_hIST;
+#endif
+#ifdef PNG_READ_iCCP_SUPPORTED
+ PNG_CONST PNG_iCCP;
+#endif
+#ifdef PNG_READ_iTXt_SUPPORTED
+ PNG_CONST PNG_iTXt;
+#endif
+#ifdef PNG_READ_oFFs_SUPPORTED
+ PNG_CONST PNG_oFFs;
+#endif
+#ifdef PNG_READ_pCAL_SUPPORTED
+ PNG_CONST PNG_pCAL;
+#endif
+#ifdef PNG_READ_pHYs_SUPPORTED
+ PNG_CONST PNG_pHYs;
+#endif
+#ifdef PNG_READ_sBIT_SUPPORTED
+ PNG_CONST PNG_sBIT;
+#endif
+#ifdef PNG_READ_sCAL_SUPPORTED
+ PNG_CONST PNG_sCAL;
+#endif
+#ifdef PNG_READ_sPLT_SUPPORTED
+ PNG_CONST PNG_sPLT;
+#endif
+#ifdef PNG_READ_sRGB_SUPPORTED
+ PNG_CONST PNG_sRGB;
+#endif
+#ifdef PNG_READ_tEXt_SUPPORTED
+ PNG_CONST PNG_tEXt;
+#endif
+#ifdef PNG_READ_tIME_SUPPORTED
+ PNG_CONST PNG_tIME;
+#endif
+#ifdef PNG_READ_tRNS_SUPPORTED
+ PNG_CONST PNG_tRNS;
+#endif
+#ifdef PNG_READ_zTXt_SUPPORTED
+ PNG_CONST PNG_zTXt;
+#endif
+#endif /* PNG_USE_LOCAL_ARRAYS */
+ png_uint_32 length = png_read_chunk_header(png_ptr);
+ PNG_CONST png_bytep chunk_name = png_ptr->chunk_name;
+
+ if (!png_memcmp(chunk_name, png_IHDR, 4))
+ png_handle_IHDR(png_ptr, info_ptr, length);
+ else if (!png_memcmp(chunk_name, png_IEND, 4))
+ png_handle_IEND(png_ptr, info_ptr, length);
+#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
+ else if (png_handle_as_unknown(png_ptr, chunk_name))
+ {
+ if (!png_memcmp(chunk_name, png_IDAT, 4))
+ {
+ if ((length > 0) || (png_ptr->mode & PNG_HAVE_CHUNK_AFTER_IDAT))
+ png_error(png_ptr, "Too many IDAT's found");
+ }
+ png_handle_unknown(png_ptr, info_ptr, length);
+ if (!png_memcmp(chunk_name, png_PLTE, 4))
+ png_ptr->mode |= PNG_HAVE_PLTE;
+ }
+#endif
+ else if (!png_memcmp(chunk_name, png_IDAT, 4))
+ {
+ /* Zero length IDATs are legal after the last IDAT has been
+ * read, but not after other chunks have been read.
+ */
+ if ((length > 0) || (png_ptr->mode & PNG_HAVE_CHUNK_AFTER_IDAT))
+ png_error(png_ptr, "Too many IDAT's found");
+ png_crc_finish(png_ptr, length);
+ }
+ else if (!png_memcmp(chunk_name, png_PLTE, 4))
+ png_handle_PLTE(png_ptr, info_ptr, length);
+#ifdef PNG_READ_bKGD_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_bKGD, 4))
+ png_handle_bKGD(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_cHRM_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_cHRM, 4))
+ png_handle_cHRM(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_gAMA_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_gAMA, 4))
+ png_handle_gAMA(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_hIST_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_hIST, 4))
+ png_handle_hIST(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_oFFs_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_oFFs, 4))
+ png_handle_oFFs(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_pCAL_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_pCAL, 4))
+ png_handle_pCAL(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_sCAL_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_sCAL, 4))
+ png_handle_sCAL(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_pHYs_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_pHYs, 4))
+ png_handle_pHYs(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_sBIT_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_sBIT, 4))
+ png_handle_sBIT(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_sRGB_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_sRGB, 4))
+ png_handle_sRGB(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_iCCP_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_iCCP, 4))
+ png_handle_iCCP(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_sPLT_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_sPLT, 4))
+ png_handle_sPLT(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_tEXt_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_tEXt, 4))
+ png_handle_tEXt(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_tIME_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_tIME, 4))
+ png_handle_tIME(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_tRNS_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_tRNS, 4))
+ png_handle_tRNS(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_zTXt_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_zTXt, 4))
+ png_handle_zTXt(png_ptr, info_ptr, length);
+#endif
+#ifdef PNG_READ_iTXt_SUPPORTED
+ else if (!png_memcmp(chunk_name, png_iTXt, 4))
+ png_handle_iTXt(png_ptr, info_ptr, length);
+#endif
+ else
+ png_handle_unknown(png_ptr, info_ptr, length);
+ } while (!(png_ptr->mode & PNG_HAVE_IEND));
+}
+#endif /* PNG_SEQUENTIAL_READ_SUPPORTED */
+
+/* Free all memory used by the read */
+void PNGAPI
+png_destroy_read_struct(png_structpp png_ptr_ptr, png_infopp info_ptr_ptr,
+ png_infopp end_info_ptr_ptr)
+{
+ png_structp png_ptr = NULL;
+ png_infop info_ptr = NULL, end_info_ptr = NULL;
+#ifdef PNG_USER_MEM_SUPPORTED
+ png_free_ptr free_fn = NULL;
+ png_voidp mem_ptr = NULL;
+#endif
+
+ png_debug(1, "in png_destroy_read_struct");
+
+ if (png_ptr_ptr != NULL)
+ png_ptr = *png_ptr_ptr;
+ if (png_ptr == NULL)
+ return;
+
+#ifdef PNG_USER_MEM_SUPPORTED
+ free_fn = png_ptr->free_fn;
+ mem_ptr = png_ptr->mem_ptr;
+#endif
+
+ if (info_ptr_ptr != NULL)
+ info_ptr = *info_ptr_ptr;
+
+ if (end_info_ptr_ptr != NULL)
+ end_info_ptr = *end_info_ptr_ptr;
+
+ png_read_destroy(png_ptr, info_ptr, end_info_ptr);
+
+ if (info_ptr != NULL)
+ {
+#ifdef PNG_TEXT_SUPPORTED
+ png_free_data(png_ptr, info_ptr, PNG_FREE_TEXT, -1);
+#endif
+
+#ifdef PNG_USER_MEM_SUPPORTED
+ png_destroy_struct_2((png_voidp)info_ptr, (png_free_ptr)free_fn,
+ (png_voidp)mem_ptr);
+#else
+ png_destroy_struct((png_voidp)info_ptr);
+#endif
+ *info_ptr_ptr = NULL;
+ }
+
+ if (end_info_ptr != NULL)
+ {
+#ifdef PNG_READ_TEXT_SUPPORTED
+ png_free_data(png_ptr, end_info_ptr, PNG_FREE_TEXT, -1);
+#endif
+#ifdef PNG_USER_MEM_SUPPORTED
+ png_destroy_struct_2((png_voidp)end_info_ptr, (png_free_ptr)free_fn,
+ (png_voidp)mem_ptr);
+#else
+ png_destroy_struct((png_voidp)end_info_ptr);
+#endif
+ *end_info_ptr_ptr = NULL;
+ }
+
+ if (png_ptr != NULL)
+ {
+#ifdef PNG_USER_MEM_SUPPORTED
+ png_destroy_struct_2((png_voidp)png_ptr, (png_free_ptr)free_fn,
+ (png_voidp)mem_ptr);
+#else
+ png_destroy_struct((png_voidp)png_ptr);
+#endif
+ *png_ptr_ptr = NULL;
+ }
+}
+
+/* Free all memory used by the read (old method) */
+void /* PRIVATE */
+png_read_destroy(png_structp png_ptr, png_infop info_ptr,
+ png_infop end_info_ptr)
+{
+#ifdef PNG_SETJMP_SUPPORTED
+ jmp_buf tmp_jmp;
+#endif
+ png_error_ptr error_fn;
+ png_error_ptr warning_fn;
+ png_voidp error_ptr;
+#ifdef PNG_USER_MEM_SUPPORTED
+ png_free_ptr free_fn;
+#endif
+
+ png_debug(1, "in png_read_destroy");
+
+ if (info_ptr != NULL)
+ png_info_destroy(png_ptr, info_ptr);
+
+ if (end_info_ptr != NULL)
+ png_info_destroy(png_ptr, end_info_ptr);
+
+ png_free(png_ptr, png_ptr->zbuf);
+ png_free(png_ptr, png_ptr->big_row_buf);
+ png_free(png_ptr, png_ptr->prev_row);
+ png_free(png_ptr, png_ptr->chunkdata);
+#ifdef PNG_READ_DITHER_SUPPORTED
+ png_free(png_ptr, png_ptr->palette_lookup);
+ png_free(png_ptr, png_ptr->dither_index);
+#endif
+#ifdef PNG_READ_GAMMA_SUPPORTED
+ png_free(png_ptr, png_ptr->gamma_table);
+#endif
+#ifdef PNG_READ_BACKGROUND_SUPPORTED
+ png_free(png_ptr, png_ptr->gamma_from_1);
+ png_free(png_ptr, png_ptr->gamma_to_1);
+#endif
+#ifdef PNG_FREE_ME_SUPPORTED
+ if (png_ptr->free_me & PNG_FREE_PLTE)
+ png_zfree(png_ptr, png_ptr->palette);
+ png_ptr->free_me &= ~PNG_FREE_PLTE;
+#else
+ if (png_ptr->flags & PNG_FLAG_FREE_PLTE)
+ png_zfree(png_ptr, png_ptr->palette);
+ png_ptr->flags &= ~PNG_FLAG_FREE_PLTE;
+#endif
+#if defined(PNG_tRNS_SUPPORTED) || \
+ defined(PNG_READ_EXPAND_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED)
+#ifdef PNG_FREE_ME_SUPPORTED
+ if (png_ptr->free_me & PNG_FREE_TRNS)
+ png_free(png_ptr, png_ptr->trans);
+ png_ptr->free_me &= ~PNG_FREE_TRNS;
+#else
+ if (png_ptr->flags & PNG_FLAG_FREE_TRNS)
+ png_free(png_ptr, png_ptr->trans);
+ png_ptr->flags &= ~PNG_FLAG_FREE_TRNS;
+#endif
+#endif
+#ifdef PNG_READ_hIST_SUPPORTED
+#ifdef PNG_FREE_ME_SUPPORTED
+ if (png_ptr->free_me & PNG_FREE_HIST)
+ png_free(png_ptr, png_ptr->hist);
+ png_ptr->free_me &= ~PNG_FREE_HIST;
+#else
+ if (png_ptr->flags & PNG_FLAG_FREE_HIST)
+ png_free(png_ptr, png_ptr->hist);
+ png_ptr->flags &= ~PNG_FLAG_FREE_HIST;
+#endif
+#endif
+#ifdef PNG_READ_GAMMA_SUPPORTED
+ if (png_ptr->gamma_16_table != NULL)
+ {
+ int i;
+ int istop = (1 << (8 - png_ptr->gamma_shift));
+ for (i = 0; i < istop; i++)
+ {
+ png_free(png_ptr, png_ptr->gamma_16_table[i]);
+ }
+ png_free(png_ptr, png_ptr->gamma_16_table);
+ }
+#ifdef PNG_READ_BACKGROUND_SUPPORTED
+ if (png_ptr->gamma_16_from_1 != NULL)
+ {
+ int i;
+ int istop = (1 << (8 - png_ptr->gamma_shift));
+ for (i = 0; i < istop; i++)
+ {
+ png_free(png_ptr, png_ptr->gamma_16_from_1[i]);
+ }
+ png_free(png_ptr, png_ptr->gamma_16_from_1);
+ }
+ if (png_ptr->gamma_16_to_1 != NULL)
+ {
+ int i;
+ int istop = (1 << (8 - png_ptr->gamma_shift));
+ for (i = 0; i < istop; i++)
+ {
+ png_free(png_ptr, png_ptr->gamma_16_to_1[i]);
+ }
+ png_free(png_ptr, png_ptr->gamma_16_to_1);
+ }
+#endif
+#endif
+#ifdef PNG_TIME_RFC1123_SUPPORTED
+ png_free(png_ptr, png_ptr->time_buffer);
+#endif
+
+ inflateEnd(&png_ptr->zstream);
+#ifdef PNG_PROGRESSIVE_READ_SUPPORTED
+ png_free(png_ptr, png_ptr->save_buffer);
+#endif
+
+#ifdef PNG_PROGRESSIVE_READ_SUPPORTED
+#ifdef PNG_TEXT_SUPPORTED
+ png_free(png_ptr, png_ptr->current_text);
+#endif /* PNG_TEXT_SUPPORTED */
+#endif /* PNG_PROGRESSIVE_READ_SUPPORTED */
+
+ /* Save the important info out of the png_struct, in case it is
+ * being used again.
+ */
+#ifdef PNG_SETJMP_SUPPORTED
+ png_memcpy(tmp_jmp, png_ptr->jmpbuf, png_sizeof(jmp_buf));
+#endif
+
+ error_fn = png_ptr->error_fn;
+ warning_fn = png_ptr->warning_fn;
+ error_ptr = png_ptr->error_ptr;
+#ifdef PNG_USER_MEM_SUPPORTED
+ free_fn = png_ptr->free_fn;
+#endif
+
+ png_memset(png_ptr, 0, png_sizeof(png_struct));
+
+ png_ptr->error_fn = error_fn;
+ png_ptr->warning_fn = warning_fn;
+ png_ptr->error_ptr = error_ptr;
+#ifdef PNG_USER_MEM_SUPPORTED
+ png_ptr->free_fn = free_fn;
+#endif
+
+#ifdef PNG_SETJMP_SUPPORTED
+ png_memcpy(png_ptr->jmpbuf, tmp_jmp, png_sizeof(jmp_buf));
+#endif
+
+}
+
+void PNGAPI
+png_set_read_status_fn(png_structp png_ptr, png_read_status_ptr read_row_fn)
+{
+ if (png_ptr == NULL)
+ return;
+ png_ptr->read_row_fn = read_row_fn;
+}
+
+
+#ifdef PNG_SEQUENTIAL_READ_SUPPORTED
+#ifdef PNG_INFO_IMAGE_SUPPORTED
+void PNGAPI
+png_read_png(png_structp png_ptr, png_infop info_ptr,
+ int transforms,
+ voidp params)
+{
+ int row;
+
+ if (png_ptr == NULL)
+ return;
+#ifdef PNG_READ_INVERT_ALPHA_SUPPORTED
+ /* Invert the alpha channel from opacity to transparency
+ */
+ if (transforms & PNG_TRANSFORM_INVERT_ALPHA)
+ png_set_invert_alpha(png_ptr);
+#endif
+
+ /* png_read_info() gives us all of the information from the
+ * PNG file before the first IDAT (image data chunk).
+ */
+ png_read_info(png_ptr, info_ptr);
+ if (info_ptr->height > PNG_UINT_32_MAX/png_sizeof(png_bytep))
+ png_error(png_ptr, "Image is too high to process with png_read_png()");
+
+ /* -------------- image transformations start here ------------------- */
+
+#ifdef PNG_READ_16_TO_8_SUPPORTED
+ /* Tell libpng to strip 16 bit/color files down to 8 bits per color.
+ */
+ if (transforms & PNG_TRANSFORM_STRIP_16)
+ png_set_strip_16(png_ptr);
+#endif
+
+#ifdef PNG_READ_STRIP_ALPHA_SUPPORTED
+ /* Strip alpha bytes from the input data without combining with
+ * the background (not recommended).
+ */
+ if (transforms & PNG_TRANSFORM_STRIP_ALPHA)
+ png_set_strip_alpha(png_ptr);
+#endif
+
+#if defined(PNG_READ_PACK_SUPPORTED) && !defined(PNG_READ_EXPAND_SUPPORTED)
+ /* Extract multiple pixels with bit depths of 1, 2, or 4 from a single
+ * byte into separate bytes (useful for paletted and grayscale images).
+ */
+ if (transforms & PNG_TRANSFORM_PACKING)
+ png_set_packing(png_ptr);
+#endif
+
+#ifdef PNG_READ_PACKSWAP_SUPPORTED
+ /* Change the order of packed pixels to least significant bit first
+ * (not useful if you are using png_set_packing).
+ */
+ if (transforms & PNG_TRANSFORM_PACKSWAP)
+ png_set_packswap(png_ptr);
+#endif
+
+#ifdef PNG_READ_EXPAND_SUPPORTED
+ /* Expand paletted colors into true RGB triplets
+ * Expand grayscale images to full 8 bits from 1, 2, or 4 bits/pixel
+ * Expand paletted or RGB images with transparency to full alpha
+ * channels so the data will be available as RGBA quartets.
+ */
+ if (transforms & PNG_TRANSFORM_EXPAND)
+ if ((png_ptr->bit_depth < 8) ||
+ (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) ||
+ (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)))
+ png_set_expand(png_ptr);
+#endif
+
+ /* We don't handle background color or gamma transformation or dithering.
+ */
+
+#ifdef PNG_READ_INVERT_SUPPORTED
+ /* Invert monochrome files to have 0 as white and 1 as black
+ */
+ if (transforms & PNG_TRANSFORM_INVERT_MONO)
+ png_set_invert_mono(png_ptr);
+#endif
+
+#ifdef PNG_READ_SHIFT_SUPPORTED
+ /* If you want to shift the pixel values from the range [0,255] or
+ * [0,65535] to the original [0,7] or [0,31], or whatever range the
+ * colors were originally in:
+ */
+ if ((transforms & PNG_TRANSFORM_SHIFT)
+ && png_get_valid(png_ptr, info_ptr, PNG_INFO_sBIT))
+ {
+ png_color_8p sig_bit;
+
+ png_get_sBIT(png_ptr, info_ptr, &sig_bit);
+ png_set_shift(png_ptr, sig_bit);
+ }
+#endif
+
+#ifdef PNG_READ_BGR_SUPPORTED
+ /* Flip the RGB pixels to BGR (or RGBA to BGRA)
+ */
+ if (transforms & PNG_TRANSFORM_BGR)
+ png_set_bgr(png_ptr);
+#endif
+
+#ifdef PNG_READ_SWAP_ALPHA_SUPPORTED
+ /* Swap the RGBA or GA data to ARGB or AG (or BGRA to ABGR)
+ */
+ if (transforms & PNG_TRANSFORM_SWAP_ALPHA)
+ png_set_swap_alpha(png_ptr);
+#endif
+
+#ifdef PNG_READ_SWAP_SUPPORTED
+ /* Swap bytes of 16 bit files to least significant byte first
+ */
+ if (transforms & PNG_TRANSFORM_SWAP_ENDIAN)
+ png_set_swap(png_ptr);
+#endif
+
+/* Added at libpng-1.2.41 */
+#ifdef PNG_READ_INVERT_ALPHA_SUPPORTED
+ /* Invert the alpha channel from opacity to transparency
+ */
+ if (transforms & PNG_TRANSFORM_INVERT_ALPHA)
+ png_set_invert_alpha(png_ptr);
+#endif
+
+/* Added at libpng-1.2.41 */
+#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED
+ /* Expand grayscale image to RGB
+ */
+ if (transforms & PNG_TRANSFORM_GRAY_TO_RGB)
+ png_set_gray_to_rgb(png_ptr);
+#endif
+
+ /* We don't handle adding filler bytes */
+
+ /* Optional call to gamma correct and add the background to the palette
+ * and update info structure. REQUIRED if you are expecting libpng to
+ * update the palette for you (i.e., you selected such a transform above).
+ */
+ png_read_update_info(png_ptr, info_ptr);
+
+ /* -------------- image transformations end here ------------------- */
+
+#ifdef PNG_FREE_ME_SUPPORTED
+ png_free_data(png_ptr, info_ptr, PNG_FREE_ROWS, 0);
+#endif
+ if (info_ptr->row_pointers == NULL)
+ {
+ info_ptr->row_pointers = (png_bytepp)png_malloc(png_ptr,
+ info_ptr->height * png_sizeof(png_bytep));
+ png_memset(info_ptr->row_pointers, 0, info_ptr->height
+ * png_sizeof(png_bytep));
+
+#ifdef PNG_FREE_ME_SUPPORTED
+ info_ptr->free_me |= PNG_FREE_ROWS;
+#endif
+
+ for (row = 0; row < (int)info_ptr->height; row++)
+ info_ptr->row_pointers[row] = (png_bytep)png_malloc(png_ptr,
+ png_get_rowbytes(png_ptr, info_ptr));
+ }
+
+ png_read_image(png_ptr, info_ptr->row_pointers);
+ info_ptr->valid |= PNG_INFO_IDAT;
+
+ /* Read rest of file, and get additional chunks in info_ptr - REQUIRED */
+ png_read_end(png_ptr, info_ptr);
+
+ transforms = transforms; /* Quiet compiler warnings */
+ params = params;
+
+}
+#endif /* PNG_INFO_IMAGE_SUPPORTED */
+#endif /* PNG_SEQUENTIAL_READ_SUPPORTED */
+#endif /* PNG_READ_SUPPORTED */
diff --git a/trunk/src/third_party/libpng/pngrio.c b/trunk/src/third_party/libpng/pngrio.c
new file mode 100644
index 0000000..6978682
--- /dev/null
+++ b/trunk/src/third_party/libpng/pngrio.c
@@ -0,0 +1,180 @@
+
+/* pngrio.c - functions for data input
+ *
+ * Last changed in libpng 1.2.43 [February 25, 2010]
+ * Copyright (c) 1998-2010 Glenn Randers-Pehrson
+ * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger)
+ * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.)
+ *
+ * This code is released under the libpng license.
+ * For conditions of distribution and use, see the disclaimer
+ * and license in png.h
+ *
+ * This file provides a location for all input. Users who need
+ * special handling are expected to write a function that has the same
+ * arguments as this and performs a similar function, but that possibly
+ * has a different input method. Note that you shouldn't change this
+ * function, but rather write a replacement function and then make
+ * libpng use it at run time with png_set_read_fn(...).
+ */
+
+#define PNG_INTERNAL
+#define PNG_NO_PEDANTIC_WARNINGS
+#include "png.h"
+#ifdef PNG_READ_SUPPORTED
+
+/* Read the data from whatever input you are using. The default routine
+ * reads from a file pointer. Note that this routine sometimes gets called
+ * with very small lengths, so you should implement some kind of simple
+ * buffering if you are using unbuffered reads. This should never be asked
+ * to read more then 64K on a 16 bit machine.
+ */
+void /* PRIVATE */
+png_read_data(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+ png_debug1(4, "reading %d bytes", (int)length);
+
+ if (png_ptr->read_data_fn != NULL)
+ (*(png_ptr->read_data_fn))(png_ptr, data, length);
+ else
+ png_error(png_ptr, "Call to NULL read function");
+}
+
+#ifdef PNG_STDIO_SUPPORTED
+/* This is the function that does the actual reading of data. If you are
+ * not reading from a standard C stream, you should create a replacement
+ * read_data function and use it at run time with png_set_read_fn(), rather
+ * than changing the library.
+ */
+#ifndef USE_FAR_KEYWORD
+void PNGAPI
+png_default_read_data(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+ png_size_t check;
+
+ if (png_ptr == NULL)
+ return;
+ /* fread() returns 0 on error, so it is OK to store this in a png_size_t
+ * instead of an int, which is what fread() actually returns.
+ */
+#ifdef _WIN32_WCE
+ if ( !ReadFile((HANDLE)(png_ptr->io_ptr), data, length, &check, NULL) )
+ check = 0;
+#else
+ check = (png_size_t)fread(data, (png_size_t)1, length,
+ (png_FILE_p)png_ptr->io_ptr);
+#endif
+
+ if (check != length)
+ png_error(png_ptr, "Read Error");
+}
+#else
+/* This is the model-independent version. Since the standard I/O library
+ can't handle far buffers in the medium and small models, we have to copy
+ the data.
+*/
+
+#define NEAR_BUF_SIZE 1024
+#define MIN(a,b) (a <= b ? a : b)
+
+static void PNGAPI
+png_default_read_data(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+ int check;
+ png_byte *n_data;
+ png_FILE_p io_ptr;
+
+ if (png_ptr == NULL)
+ return;
+ /* Check if data really is near. If so, use usual code. */
+ n_data = (png_byte *)CVT_PTR_NOCHECK(data);
+ io_ptr = (png_FILE_p)CVT_PTR(png_ptr->io_ptr);
+ if ((png_bytep)n_data == data)
+ {
+#ifdef _WIN32_WCE
+ if ( !ReadFile((HANDLE)(png_ptr->io_ptr), data, length, &check,
+ NULL) )
+ check = 0;
+#else
+ check = fread(n_data, 1, length, io_ptr);
+#endif
+ }
+ else
+ {
+ png_byte buf[NEAR_BUF_SIZE];
+ png_size_t read, remaining, err;
+ check = 0;
+ remaining = length;
+ do
+ {
+ read = MIN(NEAR_BUF_SIZE, remaining);
+#ifdef _WIN32_WCE
+ if ( !ReadFile((HANDLE)(io_ptr), buf, read, &err, NULL) )
+ err = 0;
+#else
+ err = fread(buf, (png_size_t)1, read, io_ptr);
+#endif
+ png_memcpy(data, buf, read); /* copy far buffer to near buffer */
+ if (err != read)
+ break;
+ else
+ check += err;
+ data += read;
+ remaining -= read;
+ }
+ while (remaining != 0);
+ }
+ if ((png_uint_32)check != (png_uint_32)length)
+ png_error(png_ptr, "read Error");
+}
+#endif
+#endif
+
+/* This function allows the application to supply a new input function
+ * for libpng if standard C streams aren't being used.
+ *
+ * This function takes as its arguments:
+ * png_ptr - pointer to a png input data structure
+ * io_ptr - pointer to user supplied structure containing info about
+ * the input functions. May be NULL.
+ * read_data_fn - pointer to a new input function that takes as its
+ * arguments a pointer to a png_struct, a pointer to
+ * a location where input data can be stored, and a 32-bit
+ * unsigned int that is the number of bytes to be read.
+ * To exit and output any fatal error messages the new write
+ * function should call png_error(png_ptr, "Error msg").
+ * May be NULL, in which case libpng's default function will
+ * be used.
+ */
+void PNGAPI
+png_set_read_fn(png_structp png_ptr, png_voidp io_ptr,
+ png_rw_ptr read_data_fn)
+{
+ if (png_ptr == NULL)
+ return;
+ png_ptr->io_ptr = io_ptr;
+
+#ifdef PNG_STDIO_SUPPORTED
+ if (read_data_fn != NULL)
+ png_ptr->read_data_fn = read_data_fn;
+ else
+ png_ptr->read_data_fn = png_default_read_data;
+#else
+ png_ptr->read_data_fn = read_data_fn;
+#endif
+
+ /* It is an error to write to a read device */
+ if (png_ptr->write_data_fn != NULL)
+ {
+ png_ptr->write_data_fn = NULL;
+ png_warning(png_ptr,
+ "It's an error to set both read_data_fn and write_data_fn in the ");
+ png_warning(png_ptr,
+ "same structure. Resetting write_data_fn to NULL.");
+ }
+
+#ifdef PNG_WRITE_FLUSH_SUPPORTED
+ png_ptr->output_flush_fn = NULL;
+#endif
+}
+#endif /* PNG_READ_SUPPORTED */
diff --git a/trunk/src/third_party/libpng/pngrtran.c b/trunk/src/third_party/libpng/pngrtran.c
new file mode 100644
index 0000000..af1aa8e
--- /dev/null
+++ b/trunk/src/third_party/libpng/pngrtran.c
@@ -0,0 +1,4457 @@
+
+/* pngrtran.c - transforms the data in a row for PNG readers
+ *
+ * Last changed in libpng 1.2.43 [February 25, 2010]
+ * Copyright (c) 1998-2010 Glenn Randers-Pehrson
+ * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger)
+ * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.)
+ *
+ * This code is released under the libpng license.
+ * For conditions of distribution and use, see the disclaimer
+ * and license in png.h
+ *
+ * This file contains functions optionally called by an application
+ * in order to tell libpng how to handle data when reading a PNG.
+ * Transformations that are used in both reading and writing are
+ * in pngtrans.c.
+ */
+
+#define PNG_INTERNAL
+#define PNG_NO_PEDANTIC_WARNINGS
+#include "png.h"
+#ifdef PNG_READ_SUPPORTED
+
+/* Set the action on getting a CRC error for an ancillary or critical chunk. */
+void PNGAPI
+png_set_crc_action(png_structp png_ptr, int crit_action, int ancil_action)
+{
+ png_debug(1, "in png_set_crc_action");
+
+ if (png_ptr == NULL)
+ return;
+
+ /* Tell libpng how we react to CRC errors in critical chunks */
+ switch (crit_action)
+ {
+ case PNG_CRC_NO_CHANGE: /* Leave setting as is */
+ break;
+
+ case PNG_CRC_WARN_USE: /* Warn/use data */
+ png_ptr->flags &= ~PNG_FLAG_CRC_CRITICAL_MASK;
+ png_ptr->flags |= PNG_FLAG_CRC_CRITICAL_USE;
+ break;
+
+ case PNG_CRC_QUIET_USE: /* Quiet/use data */
+ png_ptr->flags &= ~PNG_FLAG_CRC_CRITICAL_MASK;
+ png_ptr->flags |= PNG_FLAG_CRC_CRITICAL_USE |
+ PNG_FLAG_CRC_CRITICAL_IGNORE;
+ break;
+
+ case PNG_CRC_WARN_DISCARD: /* Not a valid action for critical data */
+ png_warning(png_ptr,
+ "Can't discard critical data on CRC error.");
+ case PNG_CRC_ERROR_QUIT: /* Error/quit */
+
+ case PNG_CRC_DEFAULT:
+ default:
+ png_ptr->flags &= ~PNG_FLAG_CRC_CRITICAL_MASK;
+ break;
+ }
+
+ /* Tell libpng how we react to CRC errors in ancillary chunks */
+ switch (ancil_action)
+ {
+ case PNG_CRC_NO_CHANGE: /* Leave setting as is */
+ break;
+
+ case PNG_CRC_WARN_USE: /* Warn/use data */
+ png_ptr->flags &= ~PNG_FLAG_CRC_ANCILLARY_MASK;
+ png_ptr->flags |= PNG_FLAG_CRC_ANCILLARY_USE;
+ break;
+
+ case PNG_CRC_QUIET_USE: /* Quiet/use data */
+ png_ptr->flags &= ~PNG_FLAG_CRC_ANCILLARY_MASK;
+ png_ptr->flags |= PNG_FLAG_CRC_ANCILLARY_USE |
+ PNG_FLAG_CRC_ANCILLARY_NOWARN;
+ break;
+
+ case PNG_CRC_ERROR_QUIT: /* Error/quit */
+ png_ptr->flags &= ~PNG_FLAG_CRC_ANCILLARY_MASK;
+ png_ptr->flags |= PNG_FLAG_CRC_ANCILLARY_NOWARN;
+ break;
+
+ case PNG_CRC_WARN_DISCARD: /* Warn/discard data */
+
+ case PNG_CRC_DEFAULT:
+ default:
+ png_ptr->flags &= ~PNG_FLAG_CRC_ANCILLARY_MASK;
+ break;
+ }
+}
+
+#if defined(PNG_READ_BACKGROUND_SUPPORTED) && \
+ defined(PNG_FLOATING_POINT_SUPPORTED)
+/* Handle alpha and tRNS via a background color */
+void PNGAPI
+png_set_background(png_structp png_ptr,
+ png_color_16p background_color, int background_gamma_code,
+ int need_expand, double background_gamma)
+{
+ png_debug(1, "in png_set_background");
+
+ if (png_ptr == NULL)
+ return;
+ if (background_gamma_code == PNG_BACKGROUND_GAMMA_UNKNOWN)
+ {
+ png_warning(png_ptr, "Application must supply a known background gamma");
+ return;
+ }
+
+ png_ptr->transformations |= PNG_BACKGROUND;
+ png_memcpy(&(png_ptr->background), background_color,
+ png_sizeof(png_color_16));
+ png_ptr->background_gamma = (float)background_gamma;
+ png_ptr->background_gamma_type = (png_byte)(background_gamma_code);
+ png_ptr->transformations |= (need_expand ? PNG_BACKGROUND_EXPAND : 0);
+}
+#endif
+
+#ifdef PNG_READ_16_TO_8_SUPPORTED
+/* Strip 16 bit depth files to 8 bit depth */
+void PNGAPI
+png_set_strip_16(png_structp png_ptr)
+{
+ png_debug(1, "in png_set_strip_16");
+
+ if (png_ptr == NULL)
+ return;
+ png_ptr->transformations |= PNG_16_TO_8;
+}
+#endif
+
+#ifdef PNG_READ_STRIP_ALPHA_SUPPORTED
+void PNGAPI
+png_set_strip_alpha(png_structp png_ptr)
+{
+ png_debug(1, "in png_set_strip_alpha");
+
+ if (png_ptr == NULL)
+ return;
+ png_ptr->flags |= PNG_FLAG_STRIP_ALPHA;
+}
+#endif
+
+#ifdef PNG_READ_DITHER_SUPPORTED
+/* Dither file to 8 bit. Supply a palette, the current number
+ * of elements in the palette, the maximum number of elements
+ * allowed, and a histogram if possible. If the current number
+ * of colors is greater then the maximum number, the palette will be
+ * modified to fit in the maximum number. "full_dither" indicates
+ * whether we need a dithering cube set up for RGB images, or if we
+ * simply are reducing the number of colors in a paletted image.
+ */
+
+typedef struct png_dsort_struct
+{
+ struct png_dsort_struct FAR * next;
+ png_byte left;
+ png_byte right;
+} png_dsort;
+typedef png_dsort FAR * png_dsortp;
+typedef png_dsort FAR * FAR * png_dsortpp;
+
+void PNGAPI
+png_set_dither(png_structp png_ptr, png_colorp palette,
+ int num_palette, int maximum_colors, png_uint_16p histogram,
+ int full_dither)
+{
+ png_debug(1, "in png_set_dither");
+
+ if (png_ptr == NULL)
+ return;
+ png_ptr->transformations |= PNG_DITHER;
+
+ if (!full_dither)
+ {
+ int i;
+
+ png_ptr->dither_index = (png_bytep)png_malloc(png_ptr,
+ (png_uint_32)(num_palette * png_sizeof(png_byte)));
+ for (i = 0; i < num_palette; i++)
+ png_ptr->dither_index[i] = (png_byte)i;
+ }
+
+ if (num_palette > maximum_colors)
+ {
+ if (histogram != NULL)
+ {
+ /* This is easy enough, just throw out the least used colors.
+ * Perhaps not the best solution, but good enough.
+ */
+
+ int i;
+
+ /* Initialize an array to sort colors */
+ png_ptr->dither_sort = (png_bytep)png_malloc(png_ptr,
+ (png_uint_32)(num_palette * png_sizeof(png_byte)));
+
+ /* Initialize the dither_sort array */
+ for (i = 0; i < num_palette; i++)
+ png_ptr->dither_sort[i] = (png_byte)i;
+
+ /* Find the least used palette entries by starting a
+ * bubble sort, and running it until we have sorted
+ * out enough colors. Note that we don't care about
+ * sorting all the colors, just finding which are
+ * least used.
+ */
+
+ for (i = num_palette - 1; i >= maximum_colors; i--)
+ {
+ int done; /* To stop early if the list is pre-sorted */
+ int j;
+
+ done = 1;
+ for (j = 0; j < i; j++)
+ {
+ if (histogram[png_ptr->dither_sort[j]]
+ < histogram[png_ptr->dither_sort[j + 1]])
+ {
+ png_byte t;
+
+ t = png_ptr->dither_sort[j];
+ png_ptr->dither_sort[j] = png_ptr->dither_sort[j + 1];
+ png_ptr->dither_sort[j + 1] = t;
+ done = 0;
+ }
+ }
+ if (done)
+ break;
+ }
+
+ /* Swap the palette around, and set up a table, if necessary */
+ if (full_dither)
+ {
+ int j = num_palette;
+
+ /* Put all the useful colors within the max, but don't
+ * move the others.
+ */
+ for (i = 0; i < maximum_colors; i++)
+ {
+ if ((int)png_ptr->dither_sort[i] >= maximum_colors)
+ {
+ do
+ j--;
+ while ((int)png_ptr->dither_sort[j] >= maximum_colors);
+ palette[i] = palette[j];
+ }
+ }
+ }
+ else
+ {
+ int j = num_palette;
+
+ /* Move all the used colors inside the max limit, and
+ * develop a translation table.
+ */
+ for (i = 0; i < maximum_colors; i++)
+ {
+ /* Only move the colors we need to */
+ if ((int)png_ptr->dither_sort[i] >= maximum_colors)
+ {
+ png_color tmp_color;
+
+ do
+ j--;
+ while ((int)png_ptr->dither_sort[j] >= maximum_colors);
+
+ tmp_color = palette[j];
+ palette[j] = palette[i];
+ palette[i] = tmp_color;
+ /* Indicate where the color went */
+ png_ptr->dither_index[j] = (png_byte)i;
+ png_ptr->dither_index[i] = (png_byte)j;
+ }
+ }
+
+ /* Find closest color for those colors we are not using */
+ for (i = 0; i < num_palette; i++)
+ {
+ if ((int)png_ptr->dither_index[i] >= maximum_colors)
+ {
+ int min_d, k, min_k, d_index;
+
+ /* Find the closest color to one we threw out */
+ d_index = png_ptr->dither_index[i];
+ min_d = PNG_COLOR_DIST(palette[d_index], palette[0]);
+ for (k = 1, min_k = 0; k < maximum_colors; k++)
+ {
+ int d;
+
+ d = PNG_COLOR_DIST(palette[d_index], palette[k]);
+
+ if (d < min_d)
+ {
+ min_d = d;
+ min_k = k;
+ }
+ }
+ /* Point to closest color */
+ png_ptr->dither_index[i] = (png_byte)min_k;
+ }
+ }
+ }
+ png_free(png_ptr, png_ptr->dither_sort);
+ png_ptr->dither_sort = NULL;
+ }
+ else
+ {
+ /* This is much harder to do simply (and quickly). Perhaps
+ * we need to go through a median cut routine, but those
+ * don't always behave themselves with only a few colors
+ * as input. So we will just find the closest two colors,
+ * and throw out one of them (chosen somewhat randomly).
+ * [We don't understand this at all, so if someone wants to
+ * work on improving it, be our guest - AED, GRP]
+ */
+ int i;
+ int max_d;
+ int num_new_palette;
+ png_dsortp t;
+ png_dsortpp hash;
+
+ t = NULL;
+
+ /* Initialize palette index arrays */
+ png_ptr->index_to_palette = (png_bytep)png_malloc(png_ptr,
+ (png_uint_32)(num_palette * png_sizeof(png_byte)));
+ png_ptr->palette_to_index = (png_bytep)png_malloc(png_ptr,
+ (png_uint_32)(num_palette * png_sizeof(png_byte)));
+
+ /* Initialize the sort array */
+ for (i = 0; i < num_palette; i++)
+ {
+ png_ptr->index_to_palette[i] = (png_byte)i;
+ png_ptr->palette_to_index[i] = (png_byte)i;
+ }
+
+ hash = (png_dsortpp)png_calloc(png_ptr, (png_uint_32)(769 *
+ png_sizeof(png_dsortp)));
+
+ num_new_palette = num_palette;
+
+ /* Initial wild guess at how far apart the farthest pixel
+ * pair we will be eliminating will be. Larger
+ * numbers mean more areas will be allocated, Smaller
+ * numbers run the risk of not saving enough data, and
+ * having to do this all over again.
+ *
+ * I have not done extensive checking on this number.
+ */
+ max_d = 96;
+
+ while (num_new_palette > maximum_colors)
+ {
+ for (i = 0; i < num_new_palette - 1; i++)
+ {
+ int j;
+
+ for (j = i + 1; j < num_new_palette; j++)
+ {
+ int d;
+
+ d = PNG_COLOR_DIST(palette[i], palette[j]);
+
+ if (d <= max_d)
+ {
+
+ t = (png_dsortp)png_malloc_warn(png_ptr,
+ (png_uint_32)(png_sizeof(png_dsort)));
+ if (t == NULL)
+ break;
+ t->next = hash[d];
+ t->left = (png_byte)i;
+ t->right = (png_byte)j;
+ hash[d] = t;
+ }
+ }
+ if (t == NULL)
+ break;
+ }
+
+ if (t != NULL)
+ for (i = 0; i <= max_d; i++)
+ {
+ if (hash[i] != NULL)
+ {
+ png_dsortp p;
+
+ for (p = hash[i]; p; p = p->next)
+ {
+ if ((int)png_ptr->index_to_palette[p->left]
+ < num_new_palette &&
+ (int)png_ptr->index_to_palette[p->right]
+ < num_new_palette)
+ {
+ int j, next_j;
+
+ if (num_new_palette & 0x01)
+ {
+ j = p->left;
+ next_j = p->right;
+ }
+ else
+ {
+ j = p->right;
+ next_j = p->left;
+ }
+
+ num_new_palette--;
+ palette[png_ptr->index_to_palette[j]]
+ = palette[num_new_palette];
+ if (!full_dither)
+ {
+ int k;
+
+ for (k = 0; k < num_palette; k++)
+ {
+ if (png_ptr->dither_index[k] ==
+ png_ptr->index_to_palette[j])
+ png_ptr->dither_index[k] =
+ png_ptr->index_to_palette[next_j];
+ if ((int)png_ptr->dither_index[k] ==
+ num_new_palette)
+ png_ptr->dither_index[k] =
+ png_ptr->index_to_palette[j];
+ }
+ }
+
+ png_ptr->index_to_palette[png_ptr->palette_to_index
+ [num_new_palette]] = png_ptr->index_to_palette[j];
+ png_ptr->palette_to_index[png_ptr->index_to_palette[j]]
+ = png_ptr->palette_to_index[num_new_palette];
+
+ png_ptr->index_to_palette[j] =
+ (png_byte)num_new_palette;
+ png_ptr->palette_to_index[num_new_palette] =
+ (png_byte)j;
+ }
+ if (num_new_palette <= maximum_colors)
+ break;
+ }
+ if (num_new_palette <= maximum_colors)
+ break;
+ }
+ }
+
+ for (i = 0; i < 769; i++)
+ {
+ if (hash[i] != NULL)
+ {
+ png_dsortp p = hash[i];
+ while (p)
+ {
+ t = p->next;
+ png_free(png_ptr, p);
+ p = t;
+ }
+ }
+ hash[i] = 0;
+ }
+ max_d += 96;
+ }
+ png_free(png_ptr, hash);
+ png_free(png_ptr, png_ptr->palette_to_index);
+ png_free(png_ptr, png_ptr->index_to_palette);
+ png_ptr->palette_to_index = NULL;
+ png_ptr->index_to_palette = NULL;
+ }
+ num_palette = maximum_colors;
+ }
+ if (png_ptr->palette == NULL)
+ {
+ png_ptr->palette = palette;
+ }
+ png_ptr->num_palette = (png_uint_16)num_palette;
+
+ if (full_dither)
+ {
+ int i;
+ png_bytep distance;
+ int total_bits = PNG_DITHER_RED_BITS + PNG_DITHER_GREEN_BITS +
+ PNG_DITHER_BLUE_BITS;
+ int num_red = (1 << PNG_DITHER_RED_BITS);
+ int num_green = (1 << PNG_DITHER_GREEN_BITS);
+ int num_blue = (1 << PNG_DITHER_BLUE_BITS);
+ png_size_t num_entries = ((png_size_t)1 << total_bits);
+
+ png_ptr->palette_lookup = (png_bytep )png_calloc(png_ptr,
+ (png_uint_32)(num_entries * png_sizeof(png_byte)));
+
+ distance = (png_bytep)png_malloc(png_ptr, (png_uint_32)(num_entries *
+ png_sizeof(png_byte)));
+ png_memset(distance, 0xff, num_entries * png_sizeof(png_byte));
+
+ for (i = 0; i < num_palette; i++)
+ {
+ int ir, ig, ib;
+ int r = (palette[i].red >> (8 - PNG_DITHER_RED_BITS));
+ int g = (palette[i].green >> (8 - PNG_DITHER_GREEN_BITS));
+ int b = (palette[i].blue >> (8 - PNG_DITHER_BLUE_BITS));
+
+ for (ir = 0; ir < num_red; ir++)
+ {
+ /* int dr = abs(ir - r); */
+ int dr = ((ir > r) ? ir - r : r - ir);
+ int index_r = (ir << (PNG_DITHER_BLUE_BITS +
+ PNG_DITHER_GREEN_BITS));
+
+ for (ig = 0; ig < num_green; ig++)
+ {
+ /* int dg = abs(ig - g); */
+ int dg = ((ig > g) ? ig - g : g - ig);
+ int dt = dr + dg;
+ int dm = ((dr > dg) ? dr : dg);
+ int index_g = index_r | (ig << PNG_DITHER_BLUE_BITS);
+
+ for (ib = 0; ib < num_blue; ib++)
+ {
+ int d_index = index_g | ib;
+ /* int db = abs(ib - b); */
+ int db = ((ib > b) ? ib - b : b - ib);
+ int dmax = ((dm > db) ? dm : db);
+ int d = dmax + dt + db;
+
+ if (d < (int)distance[d_index])
+ {
+ distance[d_index] = (png_byte)d;
+ png_ptr->palette_lookup[d_index] = (png_byte)i;
+ }
+ }
+ }
+ }
+ }
+
+ png_free(png_ptr, distance);
+ }
+}
+#endif
+
+#if defined(PNG_READ_GAMMA_SUPPORTED) && defined(PNG_FLOATING_POINT_SUPPORTED)
+/* Transform the image from the file_gamma to the screen_gamma. We
+ * only do transformations on images where the file_gamma and screen_gamma
+ * are not close reciprocals, otherwise it slows things down slightly, and
+ * also needlessly introduces small errors.
+ *
+ * We will turn off gamma transformation later if no semitransparent entries
+ * are present in the tRNS array for palette images. We can't do it here
+ * because we don't necessarily have the tRNS chunk yet.
+ */
+void PNGAPI
+png_set_gamma(png_structp png_ptr, double scrn_gamma, double file_gamma)
+{
+ png_debug(1, "in png_set_gamma");
+
+ if (png_ptr == NULL)
+ return;
+
+ if ((fabs(scrn_gamma * file_gamma - 1.0) > PNG_GAMMA_THRESHOLD) ||
+ (png_ptr->color_type & PNG_COLOR_MASK_ALPHA) ||
+ (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE))
+ png_ptr->transformations |= PNG_GAMMA;
+ png_ptr->gamma = (float)file_gamma;
+ png_ptr->screen_gamma = (float)scrn_gamma;
+}
+#endif
+
+#ifdef PNG_READ_EXPAND_SUPPORTED
+/* Expand paletted images to RGB, expand grayscale images of
+ * less than 8-bit depth to 8-bit depth, and expand tRNS chunks
+ * to alpha channels.
+ */
+void PNGAPI
+png_set_expand(png_structp png_ptr)
+{
+ png_debug(1, "in png_set_expand");
+
+ if (png_ptr == NULL)
+ return;
+
+ png_ptr->transformations |= (PNG_EXPAND | PNG_EXPAND_tRNS);
+ png_ptr->flags &= ~PNG_FLAG_ROW_INIT;
+}
+
+/* GRR 19990627: the following three functions currently are identical
+ * to png_set_expand(). However, it is entirely reasonable that someone
+ * might wish to expand an indexed image to RGB but *not* expand a single,
+ * fully transparent palette entry to a full alpha channel--perhaps instead
+ * convert tRNS to the grayscale/RGB format (16-bit RGB value), or replace
+ * the transparent color with a particular RGB value, or drop tRNS entirely.
+ * IOW, a future version of the library may make the transformations flag
+ * a bit more fine-grained, with separate bits for each of these three
+ * functions.
+ *
+ * More to the point, these functions make it obvious what libpng will be
+ * doing, whereas "expand" can (and does) mean any number of things.
+ *
+ * GRP 20060307: In libpng-1.2.9, png_set_gray_1_2_4_to_8() was modified
+ * to expand only the sample depth but not to expand the tRNS to alpha
+ * and its name was changed to png_set_expand_gray_1_2_4_to_8().
+ */
+
+/* Expand paletted images to RGB. */
+void PNGAPI
+png_set_palette_to_rgb(png_structp png_ptr)
+{
+ png_debug(1, "in png_set_palette_to_rgb");
+
+ if (png_ptr == NULL)
+ return;
+
+ png_ptr->transformations |= (PNG_EXPAND | PNG_EXPAND_tRNS);
+ png_ptr->flags &= ~PNG_FLAG_ROW_INIT;
+}
+
+#ifndef PNG_1_0_X
+/* Expand grayscale images of less than 8-bit depth to 8 bits. */
+void PNGAPI
+png_set_expand_gray_1_2_4_to_8(png_structp png_ptr)
+{
+ png_debug(1, "in png_set_expand_gray_1_2_4_to_8");
+
+ if (png_ptr == NULL)
+ return;
+
+ png_ptr->transformations |= PNG_EXPAND;
+ png_ptr->flags &= ~PNG_FLAG_ROW_INIT;
+}
+#endif
+
+#if defined(PNG_1_0_X) || defined(PNG_1_2_X)
+/* Expand grayscale images of less than 8-bit depth to 8 bits. */
+/* Deprecated as of libpng-1.2.9 */
+void PNGAPI
+png_set_gray_1_2_4_to_8(png_structp png_ptr)
+{
+ png_debug(1, "in png_set_gray_1_2_4_to_8");
+
+ if (png_ptr == NULL)
+ return;
+
+ png_ptr->transformations |= (PNG_EXPAND | PNG_EXPAND_tRNS);
+}
+#endif
+
+
+/* Expand tRNS chunks to alpha channels. */
+void PNGAPI
+png_set_tRNS_to_alpha(png_structp png_ptr)
+{
+ png_debug(1, "in png_set_tRNS_to_alpha");
+
+ png_ptr->transformations |= (PNG_EXPAND | PNG_EXPAND_tRNS);
+ png_ptr->flags &= ~PNG_FLAG_ROW_INIT;
+}
+#endif /* defined(PNG_READ_EXPAND_SUPPORTED) */
+
+#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED
+void PNGAPI
+png_set_gray_to_rgb(png_structp png_ptr)
+{
+ png_debug(1, "in png_set_gray_to_rgb");
+
+ png_ptr->transformations |= PNG_GRAY_TO_RGB;
+ png_ptr->flags &= ~PNG_FLAG_ROW_INIT;
+}
+#endif
+
+#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+/* Convert a RGB image to a grayscale of the same width. This allows us,
+ * for example, to convert a 24 bpp RGB image into an 8 bpp grayscale image.
+ */
+
+void PNGAPI
+png_set_rgb_to_gray(png_structp png_ptr, int error_action, double red,
+ double green)
+{
+ int red_fixed = (int)((float)red*100000.0 + 0.5);
+ int green_fixed = (int)((float)green*100000.0 + 0.5);
+ if (png_ptr == NULL)
+ return;
+ png_set_rgb_to_gray_fixed(png_ptr, error_action, red_fixed, green_fixed);
+}
+#endif
+
+void PNGAPI
+png_set_rgb_to_gray_fixed(png_structp png_ptr, int error_action,
+ png_fixed_point red, png_fixed_point green)
+{
+ png_debug(1, "in png_set_rgb_to_gray");
+
+ if (png_ptr == NULL)
+ return;
+
+ switch(error_action)
+ {
+ case 1: png_ptr->transformations |= PNG_RGB_TO_GRAY;
+ break;
+
+ case 2: png_ptr->transformations |= PNG_RGB_TO_GRAY_WARN;
+ break;
+
+ case 3: png_ptr->transformations |= PNG_RGB_TO_GRAY_ERR;
+ }
+ if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE)
+#ifdef PNG_READ_EXPAND_SUPPORTED
+ png_ptr->transformations |= PNG_EXPAND;
+#else
+ {
+ png_warning(png_ptr,
+ "Cannot do RGB_TO_GRAY without EXPAND_SUPPORTED.");
+ png_ptr->transformations &= ~PNG_RGB_TO_GRAY;
+ }
+#endif
+ {
+ png_uint_16 red_int, green_int;
+ if (red < 0 || green < 0)
+ {
+ red_int = 6968; /* .212671 * 32768 + .5 */
+ green_int = 23434; /* .715160 * 32768 + .5 */
+ }
+ else if (red + green < 100000L)
+ {
+ red_int = (png_uint_16)(((png_uint_32)red*32768L)/100000L);
+ green_int = (png_uint_16)(((png_uint_32)green*32768L)/100000L);
+ }
+ else
+ {
+ png_warning(png_ptr, "ignoring out of range rgb_to_gray coefficients");
+ red_int = 6968;
+ green_int = 23434;
+ }
+ png_ptr->rgb_to_gray_red_coeff = red_int;
+ png_ptr->rgb_to_gray_green_coeff = green_int;
+ png_ptr->rgb_to_gray_blue_coeff =
+ (png_uint_16)(32768 - red_int - green_int);
+ }
+}
+#endif
+
+#if defined(PNG_READ_USER_TRANSFORM_SUPPORTED) || \
+ defined(PNG_LEGACY_SUPPORTED) || \
+ defined(PNG_WRITE_USER_TRANSFORM_SUPPORTED)
+void PNGAPI
+png_set_read_user_transform_fn(png_structp png_ptr, png_user_transform_ptr
+ read_user_transform_fn)
+{
+ png_debug(1, "in png_set_read_user_transform_fn");
+
+ if (png_ptr == NULL)
+ return;
+
+#ifdef PNG_READ_USER_TRANSFORM_SUPPORTED
+ png_ptr->transformations |= PNG_USER_TRANSFORM;
+ png_ptr->read_user_transform_fn = read_user_transform_fn;
+#endif
+#ifdef PNG_LEGACY_SUPPORTED
+ if (read_user_transform_fn)
+ png_warning(png_ptr,
+ "This version of libpng does not support user transforms");
+#endif
+}
+#endif
+
+/* Initialize everything needed for the read. This includes modifying
+ * the palette.
+ */
+void /* PRIVATE */
+png_init_read_transformations(png_structp png_ptr)
+{
+ png_debug(1, "in png_init_read_transformations");
+
+#ifdef PNG_USELESS_TESTS_SUPPORTED
+ if (png_ptr != NULL)
+#endif
+ {
+#if defined(PNG_READ_BACKGROUND_SUPPORTED) || \
+ defined(PNG_READ_SHIFT_SUPPORTED) || \
+ defined(PNG_READ_GAMMA_SUPPORTED)
+ int color_type = png_ptr->color_type;
+#endif
+
+#if defined(PNG_READ_EXPAND_SUPPORTED) && defined(PNG_READ_BACKGROUND_SUPPORTED)
+
+#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED
+ /* Detect gray background and attempt to enable optimization
+ * for gray --> RGB case
+ *
+ * Note: if PNG_BACKGROUND_EXPAND is set and color_type is either RGB or
+ * RGB_ALPHA (in which case need_expand is superfluous anyway), the
+ * background color might actually be gray yet not be flagged as such.
+ * This is not a problem for the current code, which uses
+ * PNG_BACKGROUND_IS_GRAY only to decide when to do the
+ * png_do_gray_to_rgb() transformation.
+ */
+ if ((png_ptr->transformations & PNG_BACKGROUND_EXPAND) &&
+ !(color_type & PNG_COLOR_MASK_COLOR))
+ {
+ png_ptr->mode |= PNG_BACKGROUND_IS_GRAY;
+ } else if ((png_ptr->transformations & PNG_BACKGROUND) &&
+ !(png_ptr->transformations & PNG_BACKGROUND_EXPAND) &&
+ (png_ptr->transformations & PNG_GRAY_TO_RGB) &&
+ png_ptr->background.red == png_ptr->background.green &&
+ png_ptr->background.red == png_ptr->background.blue)
+ {
+ png_ptr->mode |= PNG_BACKGROUND_IS_GRAY;
+ png_ptr->background.gray = png_ptr->background.red;
+ }
+#endif
+
+ if ((png_ptr->transformations & PNG_BACKGROUND_EXPAND) &&
+ (png_ptr->transformations & PNG_EXPAND))
+ {
+ if (!(color_type & PNG_COLOR_MASK_COLOR)) /* i.e., GRAY or GRAY_ALPHA */
+ {
+ /* Expand background and tRNS chunks */
+ switch (png_ptr->bit_depth)
+ {
+ case 1:
+ png_ptr->background.gray *= (png_uint_16)0xff;
+ png_ptr->background.red = png_ptr->background.green
+ = png_ptr->background.blue = png_ptr->background.gray;
+ if (!(png_ptr->transformations & PNG_EXPAND_tRNS))
+ {
+ png_ptr->trans_values.gray *= (png_uint_16)0xff;
+ png_ptr->trans_values.red = png_ptr->trans_values.green
+ = png_ptr->trans_values.blue = png_ptr->trans_values.gray;
+ }
+ break;
+
+ case 2:
+ png_ptr->background.gray *= (png_uint_16)0x55;
+ png_ptr->background.red = png_ptr->background.green
+ = png_ptr->background.blue = png_ptr->background.gray;
+ if (!(png_ptr->transformations & PNG_EXPAND_tRNS))
+ {
+ png_ptr->trans_values.gray *= (png_uint_16)0x55;
+ png_ptr->trans_values.red = png_ptr->trans_values.green
+ = png_ptr->trans_values.blue = png_ptr->trans_values.gray;
+ }
+ break;
+
+ case 4:
+ png_ptr->background.gray *= (png_uint_16)0x11;
+ png_ptr->background.red = png_ptr->background.green
+ = png_ptr->background.blue = png_ptr->background.gray;
+ if (!(png_ptr->transformations & PNG_EXPAND_tRNS))
+ {
+ png_ptr->trans_values.gray *= (png_uint_16)0x11;
+ png_ptr->trans_values.red = png_ptr->trans_values.green
+ = png_ptr->trans_values.blue = png_ptr->trans_values.gray;
+ }
+ break;
+
+ case 8:
+
+ case 16:
+ png_ptr->background.red = png_ptr->background.green
+ = png_ptr->background.blue = png_ptr->background.gray;
+ break;
+ }
+ }
+ else if (color_type == PNG_COLOR_TYPE_PALETTE)
+ {
+ png_ptr->background.red =
+ png_ptr->palette[png_ptr->background.index].red;
+ png_ptr->background.green =
+ png_ptr->palette[png_ptr->background.index].green;
+ png_ptr->background.blue =
+ png_ptr->palette[png_ptr->background.index].blue;
+
+#ifdef PNG_READ_INVERT_ALPHA_SUPPORTED
+ if (png_ptr->transformations & PNG_INVERT_ALPHA)
+ {
+#ifdef PNG_READ_EXPAND_SUPPORTED
+ if (!(png_ptr->transformations & PNG_EXPAND_tRNS))
+#endif
+ {
+ /* Invert the alpha channel (in tRNS) unless the pixels are
+ * going to be expanded, in which case leave it for later
+ */
+ int i, istop;
+ istop=(int)png_ptr->num_trans;
+ for (i=0; i<istop; i++)
+ png_ptr->trans[i] = (png_byte)(255 - png_ptr->trans[i]);
+ }
+ }
+#endif
+
+ }
+ }
+#endif
+
+#if defined(PNG_READ_BACKGROUND_SUPPORTED) && defined(PNG_READ_GAMMA_SUPPORTED)
+ png_ptr->background_1 = png_ptr->background;
+#endif
+#if defined(PNG_READ_GAMMA_SUPPORTED) && defined(PNG_FLOATING_POINT_SUPPORTED)
+
+ if ((color_type == PNG_COLOR_TYPE_PALETTE && png_ptr->num_trans != 0)
+ && (fabs(png_ptr->screen_gamma * png_ptr->gamma - 1.0)
+ < PNG_GAMMA_THRESHOLD))
+ {
+ int i, k;
+ k=0;
+ for (i=0; i<png_ptr->num_trans; i++)
+ {
+ if (png_ptr->trans[i] != 0 && png_ptr->trans[i] != 0xff)
+ k=1; /* Partial transparency is present */
+ }
+ if (k == 0)
+ png_ptr->transformations &= ~PNG_GAMMA;
+ }
+
+ if ((png_ptr->transformations & (PNG_GAMMA | PNG_RGB_TO_GRAY)) &&
+ png_ptr->gamma != 0.0)
+ {
+ png_build_gamma_table(png_ptr);
+
+#ifdef PNG_READ_BACKGROUND_SUPPORTED
+ if (png_ptr->transformations & PNG_BACKGROUND)
+ {
+ if (color_type == PNG_COLOR_TYPE_PALETTE)
+ {
+ /* Could skip if no transparency */
+ png_color back, back_1;
+ png_colorp palette = png_ptr->palette;
+ int num_palette = png_ptr->num_palette;
+ int i;
+ if (png_ptr->background_gamma_type == PNG_BACKGROUND_GAMMA_FILE)
+ {
+ back.red = png_ptr->gamma_table[png_ptr->background.red];
+ back.green = png_ptr->gamma_table[png_ptr->background.green];
+ back.blue = png_ptr->gamma_table[png_ptr->background.blue];
+
+ back_1.red = png_ptr->gamma_to_1[png_ptr->background.red];
+ back_1.green = png_ptr->gamma_to_1[png_ptr->background.green];
+ back_1.blue = png_ptr->gamma_to_1[png_ptr->background.blue];
+ }
+ else
+ {
+ double g, gs;
+
+ switch (png_ptr->background_gamma_type)
+ {
+ case PNG_BACKGROUND_GAMMA_SCREEN:
+ g = (png_ptr->screen_gamma);
+ gs = 1.0;
+ break;
+
+ case PNG_BACKGROUND_GAMMA_FILE:
+ g = 1.0 / (png_ptr->gamma);
+ gs = 1.0 / (png_ptr->gamma * png_ptr->screen_gamma);
+ break;
+
+ case PNG_BACKGROUND_GAMMA_UNIQUE:
+ g = 1.0 / (png_ptr->background_gamma);
+ gs = 1.0 / (png_ptr->background_gamma *
+ png_ptr->screen_gamma);
+ break;
+ default:
+ g = 1.0; /* back_1 */
+ gs = 1.0; /* back */
+ }
+
+ if ( fabs(gs - 1.0) < PNG_GAMMA_THRESHOLD)
+ {
+ back.red = (png_byte)png_ptr->background.red;
+ back.green = (png_byte)png_ptr->background.green;
+ back.blue = (png_byte)png_ptr->background.blue;
+ }
+ else
+ {
+ back.red = (png_byte)(pow(
+ (double)png_ptr->background.red/255, gs) * 255.0 + .5);
+ back.green = (png_byte)(pow(
+ (double)png_ptr->background.green/255, gs) * 255.0
+ + .5);
+ back.blue = (png_byte)(pow(
+ (double)png_ptr->background.blue/255, gs) * 255.0 + .5);
+ }
+
+ back_1.red = (png_byte)(pow(
+ (double)png_ptr->background.red/255, g) * 255.0 + .5);
+ back_1.green = (png_byte)(pow(
+ (double)png_ptr->background.green/255, g) * 255.0 + .5);
+ back_1.blue = (png_byte)(pow(
+ (double)png_ptr->background.blue/255, g) * 255.0 + .5);
+ }
+ for (i = 0; i < num_palette; i++)
+ {
+ if (i < (int)png_ptr->num_trans && png_ptr->trans[i] != 0xff)
+ {
+ if (png_ptr->trans[i] == 0)
+ {
+ palette[i] = back;
+ }
+ else /* if (png_ptr->trans[i] != 0xff) */
+ {
+ png_byte v, w;
+
+ v = png_ptr->gamma_to_1[palette[i].red];
+ png_composite(w, v, png_ptr->trans[i], back_1.red);
+ palette[i].red = png_ptr->gamma_from_1[w];
+
+ v = png_ptr->gamma_to_1[palette[i].green];
+ png_composite(w, v, png_ptr->trans[i], back_1.green);
+ palette[i].green = png_ptr->gamma_from_1[w];
+
+ v = png_ptr->gamma_to_1[palette[i].blue];
+ png_composite(w, v, png_ptr->trans[i], back_1.blue);
+ palette[i].blue = png_ptr->gamma_from_1[w];
+ }
+ }
+ else
+ {
+ palette[i].red = png_ptr->gamma_table[palette[i].red];
+ palette[i].green = png_ptr->gamma_table[palette[i].green];
+ palette[i].blue = png_ptr->gamma_table[palette[i].blue];
+ }
+ }
+ /* Prevent the transformations being done again, and make sure
+ * that the now spurious alpha channel is stripped - the code
+ * has just reduced background composition and gamma correction
+ * to a simple alpha channel strip.
+ */
+ png_ptr->transformations &= ~PNG_BACKGROUND;
+ png_ptr->transformations &= ~PNG_GAMMA;
+ png_ptr->transformations |= PNG_STRIP_ALPHA;
+ }
+ /* if (png_ptr->background_gamma_type!=PNG_BACKGROUND_GAMMA_UNKNOWN) */
+ else
+ /* color_type != PNG_COLOR_TYPE_PALETTE */
+ {
+ double m = (double)(((png_uint_32)1 << png_ptr->bit_depth) - 1);
+ double g = 1.0;
+ double gs = 1.0;
+
+ switch (png_ptr->background_gamma_type)
+ {
+ case PNG_BACKGROUND_GAMMA_SCREEN:
+ g = (png_ptr->screen_gamma);
+ gs = 1.0;
+ break;
+
+ case PNG_BACKGROUND_GAMMA_FILE:
+ g = 1.0 / (png_ptr->gamma);
+ gs = 1.0 / (png_ptr->gamma * png_ptr->screen_gamma);
+ break;
+
+ case PNG_BACKGROUND_GAMMA_UNIQUE:
+ g = 1.0 / (png_ptr->background_gamma);
+ gs = 1.0 / (png_ptr->background_gamma *
+ png_ptr->screen_gamma);
+ break;
+ }
+
+ png_ptr->background_1.gray = (png_uint_16)(pow(
+ (double)png_ptr->background.gray / m, g) * m + .5);
+ png_ptr->background.gray = (png_uint_16)(pow(
+ (double)png_ptr->background.gray / m, gs) * m + .5);
+
+ if ((png_ptr->background.red != png_ptr->background.green) ||
+ (png_ptr->background.red != png_ptr->background.blue) ||
+ (png_ptr->background.red != png_ptr->background.gray))
+ {
+ /* RGB or RGBA with color background */
+ png_ptr->background_1.red = (png_uint_16)(pow(
+ (double)png_ptr->background.red / m, g) * m + .5);
+ png_ptr->background_1.green = (png_uint_16)(pow(
+ (double)png_ptr->background.green / m, g) * m + .5);
+ png_ptr->background_1.blue = (png_uint_16)(pow(
+ (double)png_ptr->background.blue / m, g) * m + .5);
+ png_ptr->background.red = (png_uint_16)(pow(
+ (double)png_ptr->background.red / m, gs) * m + .5);
+ png_ptr->background.green = (png_uint_16)(pow(
+ (double)png_ptr->background.green / m, gs) * m + .5);
+ png_ptr->background.blue = (png_uint_16)(pow(
+ (double)png_ptr->background.blue / m, gs) * m + .5);
+ }
+ else
+ {
+ /* GRAY, GRAY ALPHA, RGB, or RGBA with gray background */
+ png_ptr->background_1.red = png_ptr->background_1.green
+ = png_ptr->background_1.blue = png_ptr->background_1.gray;
+ png_ptr->background.red = png_ptr->background.green
+ = png_ptr->background.blue = png_ptr->background.gray;
+ }
+ }
+ }
+ else
+ /* Transformation does not include PNG_BACKGROUND */
+#endif /* PNG_READ_BACKGROUND_SUPPORTED */
+ if (color_type == PNG_COLOR_TYPE_PALETTE)
+ {
+ png_colorp palette = png_ptr->palette;
+ int num_palette = png_ptr->num_palette;
+ int i;
+
+ for (i = 0; i < num_palette; i++)
+ {
+ palette[i].red = png_ptr->gamma_table[palette[i].red];
+ palette[i].green = png_ptr->gamma_table[palette[i].green];
+ palette[i].blue = png_ptr->gamma_table[palette[i].blue];
+ }
+
+ /* Done the gamma correction. */
+ png_ptr->transformations &= ~PNG_GAMMA;
+ }
+ }
+#ifdef PNG_READ_BACKGROUND_SUPPORTED
+ else
+#endif
+#endif /* PNG_READ_GAMMA_SUPPORTED && PNG_FLOATING_POINT_SUPPORTED */
+#ifdef PNG_READ_BACKGROUND_SUPPORTED
+ /* No GAMMA transformation */
+ if ((png_ptr->transformations & PNG_BACKGROUND) &&
+ (color_type == PNG_COLOR_TYPE_PALETTE))
+ {
+ int i;
+ int istop = (int)png_ptr->num_trans;
+ png_color back;
+ png_colorp palette = png_ptr->palette;
+
+ back.red = (png_byte)png_ptr->background.red;
+ back.green = (png_byte)png_ptr->background.green;
+ back.blue = (png_byte)png_ptr->background.blue;
+
+ for (i = 0; i < istop; i++)
+ {
+ if (png_ptr->trans[i] == 0)
+ {
+ palette[i] = back;
+ }
+ else if (png_ptr->trans[i] != 0xff)
+ {
+ /* The png_composite() macro is defined in png.h */
+ png_composite(palette[i].red, palette[i].red,
+ png_ptr->trans[i], back.red);
+ png_composite(palette[i].green, palette[i].green,
+ png_ptr->trans[i], back.green);
+ png_composite(palette[i].blue, palette[i].blue,
+ png_ptr->trans[i], back.blue);
+ }
+ }
+
+ /* Handled alpha, still need to strip the channel. */
+ png_ptr->transformations &= ~PNG_BACKGROUND;
+ png_ptr->transformations |= PNG_STRIP_ALPHA;
+ }
+#endif /* PNG_READ_BACKGROUND_SUPPORTED */
+
+#ifdef PNG_READ_SHIFT_SUPPORTED
+ if ((png_ptr->transformations & PNG_SHIFT) &&
+ (color_type == PNG_COLOR_TYPE_PALETTE))
+ {
+ png_uint_16 i;
+ png_uint_16 istop = png_ptr->num_palette;
+ int sr = 8 - png_ptr->sig_bit.red;
+ int sg = 8 - png_ptr->sig_bit.green;
+ int sb = 8 - png_ptr->sig_bit.blue;
+
+ if (sr < 0 || sr > 8)
+ sr = 0;
+ if (sg < 0 || sg > 8)
+ sg = 0;
+ if (sb < 0 || sb > 8)
+ sb = 0;
+ for (i = 0; i < istop; i++)
+ {
+ png_ptr->palette[i].red >>= sr;
+ png_ptr->palette[i].green >>= sg;
+ png_ptr->palette[i].blue >>= sb;
+ }
+ }
+#endif /* PNG_READ_SHIFT_SUPPORTED */
+ }
+#if !defined(PNG_READ_GAMMA_SUPPORTED) && !defined(PNG_READ_SHIFT_SUPPORTED) \
+ && !defined(PNG_READ_BACKGROUND_SUPPORTED)
+ if (png_ptr)
+ return;
+#endif
+}
+
+/* Modify the info structure to reflect the transformations. The
+ * info should be updated so a PNG file could be written with it,
+ * assuming the transformations result in valid PNG data.
+ */
+void /* PRIVATE */
+png_read_transform_info(png_structp png_ptr, png_infop info_ptr)
+{
+ png_debug(1, "in png_read_transform_info");
+
+#ifdef PNG_READ_EXPAND_SUPPORTED
+ if (png_ptr->transformations & PNG_EXPAND)
+ {
+ if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE)
+ {
+ if (png_ptr->num_trans &&
+ (png_ptr->transformations & PNG_EXPAND_tRNS))
+ info_ptr->color_type = PNG_COLOR_TYPE_RGB_ALPHA;
+ else
+ info_ptr->color_type = PNG_COLOR_TYPE_RGB;
+ info_ptr->bit_depth = 8;
+ info_ptr->num_trans = 0;
+ }
+ else
+ {
+ if (png_ptr->num_trans)
+ {
+ if (png_ptr->transformations & PNG_EXPAND_tRNS)
+ info_ptr->color_type |= PNG_COLOR_MASK_ALPHA;
+ }
+ if (info_ptr->bit_depth < 8)
+ info_ptr->bit_depth = 8;
+ info_ptr->num_trans = 0;
+ }
+ }
+#endif
+
+#ifdef PNG_READ_BACKGROUND_SUPPORTED
+ if (png_ptr->transformations & PNG_BACKGROUND)
+ {
+ info_ptr->color_type &= ~PNG_COLOR_MASK_ALPHA;
+ info_ptr->num_trans = 0;
+ info_ptr->background = png_ptr->background;
+ }
+#endif
+
+#ifdef PNG_READ_GAMMA_SUPPORTED
+ if (png_ptr->transformations & PNG_GAMMA)
+ {
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+ info_ptr->gamma = png_ptr->gamma;
+#endif
+#ifdef PNG_FIXED_POINT_SUPPORTED
+ info_ptr->int_gamma = png_ptr->int_gamma;
+#endif
+ }
+#endif
+
+#ifdef PNG_READ_16_TO_8_SUPPORTED
+ if ((png_ptr->transformations & PNG_16_TO_8) && (info_ptr->bit_depth == 16))
+ info_ptr->bit_depth = 8;
+#endif
+
+#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED
+ if (png_ptr->transformations & PNG_GRAY_TO_RGB)
+ info_ptr->color_type |= PNG_COLOR_MASK_COLOR;
+#endif
+
+#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED
+ if (png_ptr->transformations & PNG_RGB_TO_GRAY)
+ info_ptr->color_type &= ~PNG_COLOR_MASK_COLOR;
+#endif
+
+#ifdef PNG_READ_DITHER_SUPPORTED
+ if (png_ptr->transformations & PNG_DITHER)
+ {
+ if (((info_ptr->color_type == PNG_COLOR_TYPE_RGB) ||
+ (info_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA)) &&
+ png_ptr->palette_lookup && info_ptr->bit_depth == 8)
+ {
+ info_ptr->color_type = PNG_COLOR_TYPE_PALETTE;
+ }
+ }
+#endif
+
+#ifdef PNG_READ_PACK_SUPPORTED
+ if ((png_ptr->transformations & PNG_PACK) && (info_ptr->bit_depth < 8))
+ info_ptr->bit_depth = 8;
+#endif
+
+ if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE)
+ info_ptr->channels = 1;
+ else if (info_ptr->color_type & PNG_COLOR_MASK_COLOR)
+ info_ptr->channels = 3;
+ else
+ info_ptr->channels = 1;
+
+#ifdef PNG_READ_STRIP_ALPHA_SUPPORTED
+ if (png_ptr->flags & PNG_FLAG_STRIP_ALPHA)
+ info_ptr->color_type &= ~PNG_COLOR_MASK_ALPHA;
+#endif
+
+ if (info_ptr->color_type & PNG_COLOR_MASK_ALPHA)
+ info_ptr->channels++;
+
+#ifdef PNG_READ_FILLER_SUPPORTED
+ /* STRIP_ALPHA and FILLER allowed: MASK_ALPHA bit stripped above */
+ if ((png_ptr->transformations & PNG_FILLER) &&
+ ((info_ptr->color_type == PNG_COLOR_TYPE_RGB) ||
+ (info_ptr->color_type == PNG_COLOR_TYPE_GRAY)))
+ {
+ info_ptr->channels++;
+ /* If adding a true alpha channel not just filler */
+#ifndef PNG_1_0_X
+ if (png_ptr->transformations & PNG_ADD_ALPHA)
+ info_ptr->color_type |= PNG_COLOR_MASK_ALPHA;
+#endif
+ }
+#endif
+
+#if defined(PNG_USER_TRANSFORM_PTR_SUPPORTED) && \
+defined(PNG_READ_USER_TRANSFORM_SUPPORTED)
+ if (png_ptr->transformations & PNG_USER_TRANSFORM)
+ {
+ if (info_ptr->bit_depth < png_ptr->user_transform_depth)
+ info_ptr->bit_depth = png_ptr->user_transform_depth;
+ if (info_ptr->channels < png_ptr->user_transform_channels)
+ info_ptr->channels = png_ptr->user_transform_channels;
+ }
+#endif
+
+ info_ptr->pixel_depth = (png_byte)(info_ptr->channels *
+ info_ptr->bit_depth);
+
+ info_ptr->rowbytes = PNG_ROWBYTES(info_ptr->pixel_depth, info_ptr->width);
+
+#ifndef PNG_READ_EXPAND_SUPPORTED
+ if (png_ptr)
+ return;
+#endif
+}
+
+/* Transform the row. The order of transformations is significant,
+ * and is very touchy. If you add a transformation, take care to
+ * decide how it fits in with the other transformations here.
+ */
+void /* PRIVATE */
+png_do_read_transformations(png_structp png_ptr)
+{
+ png_debug(1, "in png_do_read_transformations");
+
+ if (png_ptr->row_buf == NULL)
+ {
+#if defined(PNG_STDIO_SUPPORTED) && !defined(_WIN32_WCE)
+ char msg[50];
+
+ png_snprintf2(msg, 50,
+ "NULL row buffer for row %ld, pass %d", (long)png_ptr->row_number,
+ png_ptr->pass);
+ png_error(png_ptr, msg);
+#else
+ png_error(png_ptr, "NULL row buffer");
+#endif
+ }
+#ifdef PNG_WARN_UNINITIALIZED_ROW
+ if (!(png_ptr->flags & PNG_FLAG_ROW_INIT))
+ /* Application has failed to call either png_read_start_image()
+ * or png_read_update_info() after setting transforms that expand
+ * pixels. This check added to libpng-1.2.19
+ */
+#if (PNG_WARN_UNINITIALIZED_ROW==1)
+ png_error(png_ptr, "Uninitialized row");
+#else
+ png_warning(png_ptr, "Uninitialized row");
+#endif
+#endif
+
+#ifdef PNG_READ_EXPAND_SUPPORTED
+ if (png_ptr->transformations & PNG_EXPAND)
+ {
+ if (png_ptr->row_info.color_type == PNG_COLOR_TYPE_PALETTE)
+ {
+ png_do_expand_palette(&(png_ptr->row_info), png_ptr->row_buf + 1,
+ png_ptr->palette, png_ptr->trans, png_ptr->num_trans);
+ }
+ else
+ {
+ if (png_ptr->num_trans &&
+ (png_ptr->transformations & PNG_EXPAND_tRNS))
+ png_do_expand(&(png_ptr->row_info), png_ptr->row_buf + 1,
+ &(png_ptr->trans_values));
+ else
+ png_do_expand(&(png_ptr->row_info), png_ptr->row_buf + 1,
+ NULL);
+ }
+ }
+#endif
+
+#ifdef PNG_READ_STRIP_ALPHA_SUPPORTED
+ if (png_ptr->flags & PNG_FLAG_STRIP_ALPHA)
+ png_do_strip_filler(&(png_ptr->row_info), png_ptr->row_buf + 1,
+ PNG_FLAG_FILLER_AFTER | (png_ptr->flags & PNG_FLAG_STRIP_ALPHA));
+#endif
+
+#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED
+ if (png_ptr->transformations & PNG_RGB_TO_GRAY)
+ {
+ int rgb_error =
+ png_do_rgb_to_gray(png_ptr, &(png_ptr->row_info),
+ png_ptr->row_buf + 1);
+ if (rgb_error)
+ {
+ png_ptr->rgb_to_gray_status=1;
+ if ((png_ptr->transformations & PNG_RGB_TO_GRAY) ==
+ PNG_RGB_TO_GRAY_WARN)
+ png_warning(png_ptr, "png_do_rgb_to_gray found nongray pixel");
+ if ((png_ptr->transformations & PNG_RGB_TO_GRAY) ==
+ PNG_RGB_TO_GRAY_ERR)
+ png_error(png_ptr, "png_do_rgb_to_gray found nongray pixel");
+ }
+ }
+#endif
+
+/* From Andreas Dilger e-mail to png-implement, 26 March 1998:
+ *
+ * In most cases, the "simple transparency" should be done prior to doing
+ * gray-to-RGB, or you will have to test 3x as many bytes to check if a
+ * pixel is transparent. You would also need to make sure that the
+ * transparency information is upgraded to RGB.
+ *
+ * To summarize, the current flow is:
+ * - Gray + simple transparency -> compare 1 or 2 gray bytes and composite
+ * with background "in place" if transparent,
+ * convert to RGB if necessary
+ * - Gray + alpha -> composite with gray background and remove alpha bytes,
+ * convert to RGB if necessary
+ *
+ * To support RGB backgrounds for gray images we need:
+ * - Gray + simple transparency -> convert to RGB + simple transparency,
+ * compare 3 or 6 bytes and composite with
+ * background "in place" if transparent
+ * (3x compare/pixel compared to doing
+ * composite with gray bkgrnd)
+ * - Gray + alpha -> convert to RGB + alpha, composite with background and
+ * remove alpha bytes (3x float
+ * operations/pixel compared with composite
+ * on gray background)
+ *
+ * Greg's change will do this. The reason it wasn't done before is for
+ * performance, as this increases the per-pixel operations. If we would check
+ * in advance if the background was gray or RGB, and position the gray-to-RGB
+ * transform appropriately, then it would save a lot of work/time.
+ */
+
+#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED
+ /* If gray -> RGB, do so now only if background is non-gray; else do later
+ * for performance reasons
+ */
+ if ((png_ptr->transformations & PNG_GRAY_TO_RGB) &&
+ !(png_ptr->mode & PNG_BACKGROUND_IS_GRAY))
+ png_do_gray_to_rgb(&(png_ptr->row_info), png_ptr->row_buf + 1);
+#endif
+
+#ifdef PNG_READ_BACKGROUND_SUPPORTED
+ if ((png_ptr->transformations & PNG_BACKGROUND) &&
+ ((png_ptr->num_trans != 0 ) ||
+ (png_ptr->color_type & PNG_COLOR_MASK_ALPHA)))
+ png_do_background(&(png_ptr->row_info), png_ptr->row_buf + 1,
+ &(png_ptr->trans_values), &(png_ptr->background)
+#ifdef PNG_READ_GAMMA_SUPPORTED
+ , &(png_ptr->background_1),
+ png_ptr->gamma_table, png_ptr->gamma_from_1,
+ png_ptr->gamma_to_1, png_ptr->gamma_16_table,
+ png_ptr->gamma_16_from_1, png_ptr->gamma_16_to_1,
+ png_ptr->gamma_shift
+#endif
+);
+#endif
+
+#ifdef PNG_READ_GAMMA_SUPPORTED
+ if ((png_ptr->transformations & PNG_GAMMA) &&
+#ifdef PNG_READ_BACKGROUND_SUPPORTED
+ !((png_ptr->transformations & PNG_BACKGROUND) &&
+ ((png_ptr->num_trans != 0) ||
+ (png_ptr->color_type & PNG_COLOR_MASK_ALPHA))) &&
+#endif
+ (png_ptr->color_type != PNG_COLOR_TYPE_PALETTE))
+ png_do_gamma(&(png_ptr->row_info), png_ptr->row_buf + 1,
+ png_ptr->gamma_table, png_ptr->gamma_16_table,
+ png_ptr->gamma_shift);
+#endif
+
+#ifdef PNG_READ_16_TO_8_SUPPORTED
+ if (png_ptr->transformations & PNG_16_TO_8)
+ png_do_chop(&(png_ptr->row_info), png_ptr->row_buf + 1);
+#endif
+
+#ifdef PNG_READ_DITHER_SUPPORTED
+ if (png_ptr->transformations & PNG_DITHER)
+ {
+ png_do_dither((png_row_infop)&(png_ptr->row_info), png_ptr->row_buf + 1,
+ png_ptr->palette_lookup, png_ptr->dither_index);
+ if (png_ptr->row_info.rowbytes == (png_uint_32)0)
+ png_error(png_ptr, "png_do_dither returned rowbytes=0");
+ }
+#endif
+
+#ifdef PNG_READ_INVERT_SUPPORTED
+ if (png_ptr->transformations & PNG_INVERT_MONO)
+ png_do_invert(&(png_ptr->row_info), png_ptr->row_buf + 1);
+#endif
+
+#ifdef PNG_READ_SHIFT_SUPPORTED
+ if (png_ptr->transformations & PNG_SHIFT)
+ png_do_unshift(&(png_ptr->row_info), png_ptr->row_buf + 1,
+ &(png_ptr->shift));
+#endif
+
+#ifdef PNG_READ_PACK_SUPPORTED
+ if (png_ptr->transformations & PNG_PACK)
+ png_do_unpack(&(png_ptr->row_info), png_ptr->row_buf + 1);
+#endif
+
+#ifdef PNG_READ_BGR_SUPPORTED
+ if (png_ptr->transformations & PNG_BGR)
+ png_do_bgr(&(png_ptr->row_info), png_ptr->row_buf + 1);
+#endif
+
+#ifdef PNG_READ_PACKSWAP_SUPPORTED
+ if (png_ptr->transformations & PNG_PACKSWAP)
+ png_do_packswap(&(png_ptr->row_info), png_ptr->row_buf + 1);
+#endif
+
+#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED
+ /* If gray -> RGB, do so now only if we did not do so above */
+ if ((png_ptr->transformations & PNG_GRAY_TO_RGB) &&
+ (png_ptr->mode & PNG_BACKGROUND_IS_GRAY))
+ png_do_gray_to_rgb(&(png_ptr->row_info), png_ptr->row_buf + 1);
+#endif
+
+#ifdef PNG_READ_FILLER_SUPPORTED
+ if (png_ptr->transformations & PNG_FILLER)
+ png_do_read_filler(&(png_ptr->row_info), png_ptr->row_buf + 1,
+ (png_uint_32)png_ptr->filler, png_ptr->flags);
+#endif
+
+#ifdef PNG_READ_INVERT_ALPHA_SUPPORTED
+ if (png_ptr->transformations & PNG_INVERT_ALPHA)
+ png_do_read_invert_alpha(&(png_ptr->row_info), png_ptr->row_buf + 1);
+#endif
+
+#ifdef PNG_READ_SWAP_ALPHA_SUPPORTED
+ if (png_ptr->transformations & PNG_SWAP_ALPHA)
+ png_do_read_swap_alpha(&(png_ptr->row_info), png_ptr->row_buf + 1);
+#endif
+
+#ifdef PNG_READ_SWAP_SUPPORTED
+ if (png_ptr->transformations & PNG_SWAP_BYTES)
+ png_do_swap(&(png_ptr->row_info), png_ptr->row_buf + 1);
+#endif
+
+#ifdef PNG_READ_USER_TRANSFORM_SUPPORTED
+ if (png_ptr->transformations & PNG_USER_TRANSFORM)
+ {
+ if (png_ptr->read_user_transform_fn != NULL)
+ (*(png_ptr->read_user_transform_fn)) /* User read transform function */
+ (png_ptr, /* png_ptr */
+ &(png_ptr->row_info), /* row_info: */
+ /* png_uint_32 width; width of row */
+ /* png_uint_32 rowbytes; number of bytes in row */
+ /* png_byte color_type; color type of pixels */
+ /* png_byte bit_depth; bit depth of samples */
+ /* png_byte channels; number of channels (1-4) */
+ /* png_byte pixel_depth; bits per pixel (depth*channels) */
+ png_ptr->row_buf + 1); /* start of pixel data for row */
+#ifdef PNG_USER_TRANSFORM_PTR_SUPPORTED
+ if (png_ptr->user_transform_depth)
+ png_ptr->row_info.bit_depth = png_ptr->user_transform_depth;
+ if (png_ptr->user_transform_channels)
+ png_ptr->row_info.channels = png_ptr->user_transform_channels;
+#endif
+ png_ptr->row_info.pixel_depth = (png_byte)(png_ptr->row_info.bit_depth *
+ png_ptr->row_info.channels);
+ png_ptr->row_info.rowbytes = PNG_ROWBYTES(png_ptr->row_info.pixel_depth,
+ png_ptr->row_info.width);
+ }
+#endif
+
+}
+
+#ifdef PNG_READ_PACK_SUPPORTED
+/* Unpack pixels of 1, 2, or 4 bits per pixel into 1 byte per pixel,
+ * without changing the actual values. Thus, if you had a row with
+ * a bit depth of 1, you would end up with bytes that only contained
+ * the numbers 0 or 1. If you would rather they contain 0 and 255, use
+ * png_do_shift() after this.
+ */
+void /* PRIVATE */
+png_do_unpack(png_row_infop row_info, png_bytep row)
+{
+ png_debug(1, "in png_do_unpack");
+
+#ifdef PNG_USELESS_TESTS_SUPPORTED
+ if (row != NULL && row_info != NULL && row_info->bit_depth < 8)
+#else
+ if (row_info->bit_depth < 8)
+#endif
+ {
+ png_uint_32 i;
+ png_uint_32 row_width=row_info->width;
+
+ switch (row_info->bit_depth)
+ {
+ case 1:
+ {
+ png_bytep sp = row + (png_size_t)((row_width - 1) >> 3);
+ png_bytep dp = row + (png_size_t)row_width - 1;
+ png_uint_32 shift = 7 - (int)((row_width + 7) & 0x07);
+ for (i = 0; i < row_width; i++)
+ {
+ *dp = (png_byte)((*sp >> shift) & 0x01);
+ if (shift == 7)
+ {
+ shift = 0;
+ sp--;
+ }
+ else
+ shift++;
+
+ dp--;
+ }
+ break;
+ }
+
+ case 2:
+ {
+
+ png_bytep sp = row + (png_size_t)((row_width - 1) >> 2);
+ png_bytep dp = row + (png_size_t)row_width - 1;
+ png_uint_32 shift = (int)((3 - ((row_width + 3) & 0x03)) << 1);
+ for (i = 0; i < row_width; i++)
+ {
+ *dp = (png_byte)((*sp >> shift) & 0x03);
+ if (shift == 6)
+ {
+ shift = 0;
+ sp--;
+ }
+ else
+ shift += 2;
+
+ dp--;
+ }
+ break;
+ }
+
+ case 4:
+ {
+ png_bytep sp = row + (png_size_t)((row_width - 1) >> 1);
+ png_bytep dp = row + (png_size_t)row_width - 1;
+ png_uint_32 shift = (int)((1 - ((row_width + 1) & 0x01)) << 2);
+ for (i = 0; i < row_width; i++)
+ {
+ *dp = (png_byte)((*sp >> shift) & 0x0f);
+ if (shift == 4)
+ {
+ shift = 0;
+ sp--;
+ }
+ else
+ shift = 4;
+
+ dp--;
+ }
+ break;
+ }
+ }
+ row_info->bit_depth = 8;
+ row_info->pixel_depth = (png_byte)(8 * row_info->channels);
+ row_info->rowbytes = row_width * row_info->channels;
+ }
+}
+#endif
+
+#ifdef PNG_READ_SHIFT_SUPPORTED
+/* Reverse the effects of png_do_shift. This routine merely shifts the
+ * pixels back to their significant bits values. Thus, if you have
+ * a row of bit depth 8, but only 5 are significant, this will shift
+ * the values back to 0 through 31.
+ */
+void /* PRIVATE */
+png_do_unshift(png_row_infop row_info, png_bytep row, png_color_8p sig_bits)
+{
+ png_debug(1, "in png_do_unshift");
+
+ if (
+#ifdef PNG_USELESS_TESTS_SUPPORTED
+ row != NULL && row_info != NULL && sig_bits != NULL &&
+#endif
+ row_info->color_type != PNG_COLOR_TYPE_PALETTE)
+ {
+ int shift[4];
+ int channels = 0;
+ int c;
+ png_uint_16 value = 0;
+ png_uint_32 row_width = row_info->width;
+
+ if (row_info->color_type & PNG_COLOR_MASK_COLOR)
+ {
+ shift[channels++] = row_info->bit_depth - sig_bits->red;
+ shift[channels++] = row_info->bit_depth - sig_bits->green;
+ shift[channels++] = row_info->bit_depth - sig_bits->blue;
+ }
+ else
+ {
+ shift[channels++] = row_info->bit_depth - sig_bits->gray;
+ }
+ if (row_info->color_type & PNG_COLOR_MASK_ALPHA)
+ {
+ shift[channels++] = row_info->bit_depth - sig_bits->alpha;
+ }
+
+ for (c = 0; c < channels; c++)
+ {
+ if (shift[c] <= 0)
+ shift[c] = 0;
+ else
+ value = 1;
+ }
+
+ if (!value)
+ return;
+
+ switch (row_info->bit_depth)
+ {
+ case 2:
+ {
+ png_bytep bp;
+ png_uint_32 i;
+ png_uint_32 istop = row_info->rowbytes;
+
+ for (bp = row, i = 0; i < istop; i++)
+ {
+ *bp >>= 1;
+ *bp++ &= 0x55;
+ }
+ break;
+ }
+
+ case 4:
+ {
+ png_bytep bp = row;
+ png_uint_32 i;
+ png_uint_32 istop = row_info->rowbytes;
+ png_byte mask = (png_byte)((((int)0xf0 >> shift[0]) & (int)0xf0) |
+ (png_byte)((int)0xf >> shift[0]));
+
+ for (i = 0; i < istop; i++)
+ {
+ *bp >>= shift[0];
+ *bp++ &= mask;
+ }
+ break;
+ }
+
+ case 8:
+ {
+ png_bytep bp = row;
+ png_uint_32 i;
+ png_uint_32 istop = row_width * channels;
+
+ for (i = 0; i < istop; i++)
+ {
+ *bp++ >>= shift[i%channels];
+ }
+ break;
+ }
+
+ case 16:
+ {
+ png_bytep bp = row;
+ png_uint_32 i;
+ png_uint_32 istop = channels * row_width;
+
+ for (i = 0; i < istop; i++)
+ {
+ value = (png_uint_16)((*bp << 8) + *(bp + 1));
+ value >>= shift[i%channels];
+ *bp++ = (png_byte)(value >> 8);
+ *bp++ = (png_byte)(value & 0xff);
+ }
+ break;
+ }
+ }
+ }
+}
+#endif
+
+#ifdef PNG_READ_16_TO_8_SUPPORTED
+/* Chop rows of bit depth 16 down to 8 */
+void /* PRIVATE */
+png_do_chop(png_row_infop row_info, png_bytep row)
+{
+ png_debug(1, "in png_do_chop");
+
+#ifdef PNG_USELESS_TESTS_SUPPORTED
+ if (row != NULL && row_info != NULL && row_info->bit_depth == 16)
+#else
+ if (row_info->bit_depth == 16)
+#endif
+ {
+ png_bytep sp = row;
+ png_bytep dp = row;
+ png_uint_32 i;
+ png_uint_32 istop = row_info->width * row_info->channels;
+
+ for (i = 0; i<istop; i++, sp += 2, dp++)
+ {
+#ifdef PNG_READ_16_TO_8_ACCURATE_SCALE_SUPPORTED
+ /* This does a more accurate scaling of the 16-bit color
+ * value, rather than a simple low-byte truncation.
+ *
+ * What the ideal calculation should be:
+ * *dp = (((((png_uint_32)(*sp) << 8) |
+ * (png_uint_32)(*(sp + 1))) * 255 + 127)
+ * / (png_uint_32)65535L;
+ *
+ * GRR: no, I think this is what it really should be:
+ * *dp = (((((png_uint_32)(*sp) << 8) |
+ * (png_uint_32)(*(sp + 1))) + 128L)
+ * / (png_uint_32)257L;
+ *
+ * GRR: here's the exact calculation with shifts:
+ * temp = (((png_uint_32)(*sp) << 8) |
+ * (png_uint_32)(*(sp + 1))) + 128L;
+ * *dp = (temp - (temp >> 8)) >> 8;
+ *
+ * Approximate calculation with shift/add instead of multiply/divide:
+ * *dp = ((((png_uint_32)(*sp) << 8) |
+ * (png_uint_32)((int)(*(sp + 1)) - *sp)) + 128) >> 8;
+ *
+ * What we actually do to avoid extra shifting and conversion:
+ */
+
+ *dp = *sp + ((((int)(*(sp + 1)) - *sp) > 128) ? 1 : 0);
+#else
+ /* Simply discard the low order byte */
+ *dp = *sp;
+#endif
+ }
+ row_info->bit_depth = 8;
+ row_info->pixel_depth = (png_byte)(8 * row_info->channels);
+ row_info->rowbytes = row_info->width * row_info->channels;
+ }
+}
+#endif
+
+#ifdef PNG_READ_SWAP_ALPHA_SUPPORTED
+void /* PRIVATE */
+png_do_read_swap_alpha(png_row_infop row_info, png_bytep row)
+{
+ png_debug(1, "in png_do_read_swap_alpha");
+
+#ifdef PNG_USELESS_TESTS_SUPPORTED
+ if (row != NULL && row_info != NULL)
+#endif
+ {
+ png_uint_32 row_width = row_info->width;
+ if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA)
+ {
+ /* This converts from RGBA to ARGB */
+ if (row_info->bit_depth == 8)
+ {
+ png_bytep sp = row + row_info->rowbytes;
+ png_bytep dp = sp;
+ png_byte save;
+ png_uint_32 i;
+
+ for (i = 0; i < row_width; i++)
+ {
+ save = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = save;
+ }
+ }
+ /* This converts from RRGGBBAA to AARRGGBB */
+ else
+ {
+ png_bytep sp = row + row_info->rowbytes;
+ png_bytep dp = sp;
+ png_byte save[2];
+ png_uint_32 i;
+
+ for (i = 0; i < row_width; i++)
+ {
+ save[0] = *(--sp);
+ save[1] = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = save[0];
+ *(--dp) = save[1];
+ }
+ }
+ }
+ else if (row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
+ {
+ /* This converts from GA to AG */
+ if (row_info->bit_depth == 8)
+ {
+ png_bytep sp = row + row_info->rowbytes;
+ png_bytep dp = sp;
+ png_byte save;
+ png_uint_32 i;
+
+ for (i = 0; i < row_width; i++)
+ {
+ save = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = save;
+ }
+ }
+ /* This converts from GGAA to AAGG */
+ else
+ {
+ png_bytep sp = row + row_info->rowbytes;
+ png_bytep dp = sp;
+ png_byte save[2];
+ png_uint_32 i;
+
+ for (i = 0; i < row_width; i++)
+ {
+ save[0] = *(--sp);
+ save[1] = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = save[0];
+ *(--dp) = save[1];
+ }
+ }
+ }
+ }
+}
+#endif
+
+#ifdef PNG_READ_INVERT_ALPHA_SUPPORTED
+void /* PRIVATE */
+png_do_read_invert_alpha(png_row_infop row_info, png_bytep row)
+{
+ png_debug(1, "in png_do_read_invert_alpha");
+
+#ifdef PNG_USELESS_TESTS_SUPPORTED
+ if (row != NULL && row_info != NULL)
+#endif
+ {
+ png_uint_32 row_width = row_info->width;
+ if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA)
+ {
+ /* This inverts the alpha channel in RGBA */
+ if (row_info->bit_depth == 8)
+ {
+ png_bytep sp = row + row_info->rowbytes;
+ png_bytep dp = sp;
+ png_uint_32 i;
+
+ for (i = 0; i < row_width; i++)
+ {
+ *(--dp) = (png_byte)(255 - *(--sp));
+
+/* This does nothing:
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ We can replace it with:
+*/
+ sp-=3;
+ dp=sp;
+ }
+ }
+ /* This inverts the alpha channel in RRGGBBAA */
+ else
+ {
+ png_bytep sp = row + row_info->rowbytes;
+ png_bytep dp = sp;
+ png_uint_32 i;
+
+ for (i = 0; i < row_width; i++)
+ {
+ *(--dp) = (png_byte)(255 - *(--sp));
+ *(--dp) = (png_byte)(255 - *(--sp));
+
+/* This does nothing:
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ We can replace it with:
+*/
+ sp-=6;
+ dp=sp;
+ }
+ }
+ }
+ else if (row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
+ {
+ /* This inverts the alpha channel in GA */
+ if (row_info->bit_depth == 8)
+ {
+ png_bytep sp = row + row_info->rowbytes;
+ png_bytep dp = sp;
+ png_uint_32 i;
+
+ for (i = 0; i < row_width; i++)
+ {
+ *(--dp) = (png_byte)(255 - *(--sp));
+ *(--dp) = *(--sp);
+ }
+ }
+ /* This inverts the alpha channel in GGAA */
+ else
+ {
+ png_bytep sp = row + row_info->rowbytes;
+ png_bytep dp = sp;
+ png_uint_32 i;
+
+ for (i = 0; i < row_width; i++)
+ {
+ *(--dp) = (png_byte)(255 - *(--sp));
+ *(--dp) = (png_byte)(255 - *(--sp));
+/*
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+*/
+ sp-=2;
+ dp=sp;
+ }
+ }
+ }
+ }
+}
+#endif
+
+#ifdef PNG_READ_FILLER_SUPPORTED
+/* Add filler channel if we have RGB color */
+void /* PRIVATE */
+png_do_read_filler(png_row_infop row_info, png_bytep row,
+ png_uint_32 filler, png_uint_32 flags)
+{
+ png_uint_32 i;
+ png_uint_32 row_width = row_info->width;
+
+ png_byte hi_filler = (png_byte)((filler>>8) & 0xff);
+ png_byte lo_filler = (png_byte)(filler & 0xff);
+
+ png_debug(1, "in png_do_read_filler");
+
+ if (
+#ifdef PNG_USELESS_TESTS_SUPPORTED
+ row != NULL && row_info != NULL &&
+#endif
+ row_info->color_type == PNG_COLOR_TYPE_GRAY)
+ {
+ if (row_info->bit_depth == 8)
+ {
+ /* This changes the data from G to GX */
+ if (flags & PNG_FLAG_FILLER_AFTER)
+ {
+ png_bytep sp = row + (png_size_t)row_width;
+ png_bytep dp = sp + (png_size_t)row_width;
+ for (i = 1; i < row_width; i++)
+ {
+ *(--dp) = lo_filler;
+ *(--dp) = *(--sp);
+ }
+ *(--dp) = lo_filler;
+ row_info->channels = 2;
+ row_info->pixel_depth = 16;
+ row_info->rowbytes = row_width * 2;
+ }
+ /* This changes the data from G to XG */
+ else
+ {
+ png_bytep sp = row + (png_size_t)row_width;
+ png_bytep dp = sp + (png_size_t)row_width;
+ for (i = 0; i < row_width; i++)
+ {
+ *(--dp) = *(--sp);
+ *(--dp) = lo_filler;
+ }
+ row_info->channels = 2;
+ row_info->pixel_depth = 16;
+ row_info->rowbytes = row_width * 2;
+ }
+ }
+ else if (row_info->bit_depth == 16)
+ {
+ /* This changes the data from GG to GGXX */
+ if (flags & PNG_FLAG_FILLER_AFTER)
+ {
+ png_bytep sp = row + (png_size_t)row_width * 2;
+ png_bytep dp = sp + (png_size_t)row_width * 2;
+ for (i = 1; i < row_width; i++)
+ {
+ *(--dp) = hi_filler;
+ *(--dp) = lo_filler;
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ }
+ *(--dp) = hi_filler;
+ *(--dp) = lo_filler;
+ row_info->channels = 2;
+ row_info->pixel_depth = 32;
+ row_info->rowbytes = row_width * 4;
+ }
+ /* This changes the data from GG to XXGG */
+ else
+ {
+ png_bytep sp = row + (png_size_t)row_width * 2;
+ png_bytep dp = sp + (png_size_t)row_width * 2;
+ for (i = 0; i < row_width; i++)
+ {
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = hi_filler;
+ *(--dp) = lo_filler;
+ }
+ row_info->channels = 2;
+ row_info->pixel_depth = 32;
+ row_info->rowbytes = row_width * 4;
+ }
+ }
+ } /* COLOR_TYPE == GRAY */
+ else if (row_info->color_type == PNG_COLOR_TYPE_RGB)
+ {
+ if (row_info->bit_depth == 8)
+ {
+ /* This changes the data from RGB to RGBX */
+ if (flags & PNG_FLAG_FILLER_AFTER)
+ {
+ png_bytep sp = row + (png_size_t)row_width * 3;
+ png_bytep dp = sp + (png_size_t)row_width;
+ for (i = 1; i < row_width; i++)
+ {
+ *(--dp) = lo_filler;
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ }
+ *(--dp) = lo_filler;
+ row_info->channels = 4;
+ row_info->pixel_depth = 32;
+ row_info->rowbytes = row_width * 4;
+ }
+ /* This changes the data from RGB to XRGB */
+ else
+ {
+ png_bytep sp = row + (png_size_t)row_width * 3;
+ png_bytep dp = sp + (png_size_t)row_width;
+ for (i = 0; i < row_width; i++)
+ {
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = lo_filler;
+ }
+ row_info->channels = 4;
+ row_info->pixel_depth = 32;
+ row_info->rowbytes = row_width * 4;
+ }
+ }
+ else if (row_info->bit_depth == 16)
+ {
+ /* This changes the data from RRGGBB to RRGGBBXX */
+ if (flags & PNG_FLAG_FILLER_AFTER)
+ {
+ png_bytep sp = row + (png_size_t)row_width * 6;
+ png_bytep dp = sp + (png_size_t)row_width * 2;
+ for (i = 1; i < row_width; i++)
+ {
+ *(--dp) = hi_filler;
+ *(--dp) = lo_filler;
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ }
+ *(--dp) = hi_filler;
+ *(--dp) = lo_filler;
+ row_info->channels = 4;
+ row_info->pixel_depth = 64;
+ row_info->rowbytes = row_width * 8;
+ }
+ /* This changes the data from RRGGBB to XXRRGGBB */
+ else
+ {
+ png_bytep sp = row + (png_size_t)row_width * 6;
+ png_bytep dp = sp + (png_size_t)row_width * 2;
+ for (i = 0; i < row_width; i++)
+ {
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = *(--sp);
+ *(--dp) = hi_filler;
+ *(--dp) = lo_filler;
+ }
+ row_info->channels = 4;
+ row_info->pixel_depth = 64;
+ row_info->rowbytes = row_width * 8;
+ }
+ }
+ } /* COLOR_TYPE == RGB */
+}
+#endif
+
+#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED
+/* Expand grayscale files to RGB, with or without alpha */
+void /* PRIVATE */
+png_do_gray_to_rgb(png_row_infop row_info, png_bytep row)
+{
+ png_uint_32 i;
+ png_uint_32 row_width = row_info->width;
+
+ png_debug(1, "in png_do_gray_to_rgb");
+
+ if (row_info->bit_depth >= 8 &&
+#ifdef PNG_USELESS_TESTS_SUPPORTED
+ row != NULL && row_info != NULL &&
+#endif
+ !(row_info->color_type & PNG_COLOR_MASK_COLOR))
+ {
+ if (row_info->color_type == PNG_COLOR_TYPE_GRAY)
+ {
+ if (row_info->bit_depth == 8)
+ {
+ png_bytep sp = row + (png_size_t)row_width - 1;
+ png_bytep dp = sp + (png_size_t)row_width * 2;
+ for (i = 0; i < row_width; i++)
+ {
+ *(dp--) = *sp;
+ *(dp--) = *sp;
+ *(dp--) = *(sp--);
+ }
+ }
+ else
+ {
+ png_bytep sp = row + (png_size_t)row_width * 2 - 1;
+ png_bytep dp = sp + (png_size_t)row_width * 4;
+ for (i = 0; i < row_width; i++)
+ {
+ *(dp--) = *sp;
+ *(dp--) = *(sp - 1);
+ *(dp--) = *sp;
+ *(dp--) = *(sp - 1);
+ *(dp--) = *(sp--);
+ *(dp--) = *(sp--);
+ }
+ }
+ }
+ else if (row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
+ {
+ if (row_info->bit_depth == 8)
+ {
+ png_bytep sp = row + (png_size_t)row_width * 2 - 1;
+ png_bytep dp = sp + (png_size_t)row_width * 2;
+ for (i = 0; i < row_width; i++)
+ {
+ *(dp--) = *(sp--);
+ *(dp--) = *sp;
+ *(dp--) = *sp;
+ *(dp--) = *(sp--);
+ }
+ }
+ else
+ {
+ png_bytep sp = row + (png_size_t)row_width * 4 - 1;
+ png_bytep dp = sp + (png_size_t)row_width * 4;
+ for (i = 0; i < row_width; i++)
+ {
+ *(dp--) = *(sp--);
+ *(dp--) = *(sp--);
+ *(dp--) = *sp;
+ *(dp--) = *(sp - 1);
+ *(dp--) = *sp;
+ *(dp--) = *(sp - 1);
+ *(dp--) = *(sp--);
+ *(dp--) = *(sp--);
+ }
+ }
+ }
+ row_info->channels += (png_byte)2;
+ row_info->color_type |= PNG_COLOR_MASK_COLOR;
+ row_info->pixel_depth = (png_byte)(row_info->channels *
+ row_info->bit_depth);
+ row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, row_width);
+ }
+}
+#endif
+
+#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED
+/* Reduce RGB files to grayscale, with or without alpha
+ * using the equation given in Poynton's ColorFAQ at
+ * <http://www.inforamp.net/~poynton/> (THIS LINK IS DEAD June 2008)
+ * New link:
+ * <http://www.poynton.com/notes/colour_and_gamma/>
+ * Charles Poynton poynton at poynton.com
+ *
+ * Y = 0.212671 * R + 0.715160 * G + 0.072169 * B
+ *
+ * We approximate this with
+ *
+ * Y = 0.21268 * R + 0.7151 * G + 0.07217 * B
+ *
+ * which can be expressed with integers as
+ *
+ * Y = (6969 * R + 23434 * G + 2365 * B)/32768
+ *
+ * The calculation is to be done in a linear colorspace.
+ *
+ * Other integer coefficents can be used via png_set_rgb_to_gray().
+ */
+int /* PRIVATE */
+png_do_rgb_to_gray(png_structp png_ptr, png_row_infop row_info, png_bytep row)
+
+{
+ png_uint_32 i;
+
+ png_uint_32 row_width = row_info->width;
+ int rgb_error = 0;
+
+ png_debug(1, "in png_do_rgb_to_gray");
+
+ if (
+#ifdef PNG_USELESS_TESTS_SUPPORTED
+ row != NULL && row_info != NULL &&
+#endif
+ (row_info->color_type & PNG_COLOR_MASK_COLOR))
+ {
+ png_uint_32 rc = png_ptr->rgb_to_gray_red_coeff;
+ png_uint_32 gc = png_ptr->rgb_to_gray_green_coeff;
+ png_uint_32 bc = png_ptr->rgb_to_gray_blue_coeff;
+
+ if (row_info->color_type == PNG_COLOR_TYPE_RGB)
+ {
+ if (row_info->bit_depth == 8)
+ {
+#if defined(PNG_READ_GAMMA_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED)
+ if (png_ptr->gamma_from_1 != NULL && png_ptr->gamma_to_1 != NULL)
+ {
+ png_bytep sp = row;
+ png_bytep dp = row;
+
+ for (i = 0; i < row_width; i++)
+ {
+ png_byte red = png_ptr->gamma_to_1[*(sp++)];
+ png_byte green = png_ptr->gamma_to_1[*(sp++)];
+ png_byte blue = png_ptr->gamma_to_1[*(sp++)];
+ if (red != green || red != blue)
+ {
+ rgb_error |= 1;
+ *(dp++) = png_ptr->gamma_from_1[
+ (rc*red + gc*green + bc*blue)>>15];
+ }
+ else
+ *(dp++) = *(sp - 1);
+ }
+ }
+ else
+#endif
+ {
+ png_bytep sp = row;
+ png_bytep dp = row;
+ for (i = 0; i < row_width; i++)
+ {
+ png_byte red = *(sp++);
+ png_byte green = *(sp++);
+ png_byte blue = *(sp++);
+ if (red != green || red != blue)
+ {
+ rgb_error |= 1;
+ *(dp++) = (png_byte)((rc*red + gc*green + bc*blue)>>15);
+ }
+ else
+ *(dp++) = *(sp - 1);
+ }
+ }
+ }
+
+ else /* RGB bit_depth == 16 */
+ {
+#if defined(PNG_READ_GAMMA_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED)
+ if (png_ptr->gamma_16_to_1 != NULL &&
+ png_ptr->gamma_16_from_1 != NULL)
+ {
+ png_bytep sp = row;
+ png_bytep dp = row;
+ for (i = 0; i < row_width; i++)
+ {
+ png_uint_16 red, green, blue, w;
+
+ red = (png_uint_16)(((*(sp))<<8) | *(sp+1)); sp+=2;
+ green = (png_uint_16)(((*(sp))<<8) | *(sp+1)); sp+=2;
+ blue = (png_uint_16)(((*(sp))<<8) | *(sp+1)); sp+=2;
+
+ if (red == green && red == blue)
+ w = red;
+ else
+ {
+ png_uint_16 red_1 = png_ptr->gamma_16_to_1[(red&0xff) >>
+ png_ptr->gamma_shift][red>>8];
+ png_uint_16 green_1 =
+ png_ptr->gamma_16_to_1[(green&0xff) >>
+ png_ptr->gamma_shift][green>>8];
+ png_uint_16 blue_1 = png_ptr->gamma_16_to_1[(blue&0xff) >>
+ png_ptr->gamma_shift][blue>>8];
+ png_uint_16 gray16 = (png_uint_16)((rc*red_1 + gc*green_1
+ + bc*blue_1)>>15);
+ w = png_ptr->gamma_16_from_1[(gray16&0xff) >>
+ png_ptr->gamma_shift][gray16 >> 8];
+ rgb_error |= 1;
+ }
+
+ *(dp++) = (png_byte)((w>>8) & 0xff);
+ *(dp++) = (png_byte)(w & 0xff);
+ }
+ }
+ else
+#endif
+ {
+ png_bytep sp = row;
+ png_bytep dp = row;
+ for (i = 0; i < row_width; i++)
+ {
+ png_uint_16 red, green, blue, gray16;
+
+ red = (png_uint_16)(((*(sp))<<8) | *(sp+1)); sp+=2;
+ green = (png_uint_16)(((*(sp))<<8) | *(sp+1)); sp+=2;
+ blue = (png_uint_16)(((*(sp))<<8) | *(sp+1)); sp+=2;
+
+ if (red != green || red != blue)
+ rgb_error |= 1;
+ gray16 = (png_uint_16)((rc*red + gc*green + bc*blue)>>15);
+ *(dp++) = (png_byte)((gray16>>8) & 0xff);
+ *(dp++) = (png_byte)(gray16 & 0xff);
+ }
+ }
+ }
+ }
+ if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA)
+ {
+ if (row_info->bit_depth == 8)
+ {
+#if defined(PNG_READ_GAMMA_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED)
+ if (png_ptr->gamma_from_1 != NULL && png_ptr->gamma_to_1 != NULL)
+ {
+ png_bytep sp = row;
+ png_bytep dp = row;
+ for (i = 0; i < row_width; i++)
+ {
+ png_byte red = png_ptr->gamma_to_1[*(sp++)];
+ png_byte green = png_ptr->gamma_to_1[*(sp++)];
+ png_byte blue = png_ptr->gamma_to_1[*(sp++)];
+ if (red != green || red != blue)
+ rgb_error |= 1;
+ *(dp++) = png_ptr->gamma_from_1
+ [(rc*red + gc*green + bc*blue)>>15];
+ *(dp++) = *(sp++); /* alpha */
+ }
+ }
+ else
+#endif
+ {
+ png_bytep sp = row;
+ png_bytep dp = row;
+ for (i = 0; i < row_width; i++)
+ {
+ png_byte red = *(sp++);
+ png_byte green = *(sp++);
+ png_byte blue = *(sp++);
+ if (red != green || red != blue)
+ rgb_error |= 1;
+ *(dp++) = (png_byte)((rc*red + gc*green + bc*blue)>>15);
+ *(dp++) = *(sp++); /* alpha */
+ }
+ }
+ }
+ else /* RGBA bit_depth == 16 */
+ {
+#if defined(PNG_READ_GAMMA_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED)
+ if (png_ptr->gamma_16_to_1 != NULL &&
+ png_ptr->gamma_16_from_1 != NULL)
+ {
+ png_bytep sp = row;
+ png_bytep dp = row;
+ for (i = 0; i < row_width; i++)
+ {
+ png_uint_16 red, green, blue, w;
+
+ red = (png_uint_16)(((*(sp))<<8) | *(sp+1)); sp+=2;
+ green = (png_uint_16)(((*(sp))<<8) | *(sp+1)); sp+=2;
+ blue = (png_uint_16)(((*(sp))<<8) | *(sp+1)); sp+=2;
+
+ if (red == green && red == blue)
+ w = red;
+ else
+ {
+ png_uint_16 red_1 = png_ptr->gamma_16_to_1[(red&0xff) >>
+ png_ptr->gamma_shift][red>>8];
+ png_uint_16 green_1 =
+ png_ptr->gamma_16_to_1[(green&0xff) >>
+ png_ptr->gamma_shift][green>>8];
+ png_uint_16 blue_1 = png_ptr->gamma_16_to_1[(blue&0xff) >>
+ png_ptr->gamma_shift][blue>>8];
+ png_uint_16 gray16 = (png_uint_16)((rc * red_1
+ + gc * green_1 + bc * blue_1)>>15);
+ w = png_ptr->gamma_16_from_1[(gray16&0xff) >>
+ png_ptr->gamma_shift][gray16 >> 8];
+ rgb_error |= 1;
+ }
+
+ *(dp++) = (png_byte)((w>>8) & 0xff);
+ *(dp++) = (png_byte)(w & 0xff);
+ *(dp++) = *(sp++); /* alpha */
+ *(dp++) = *(sp++);
+ }
+ }
+ else
+#endif
+ {
+ png_bytep sp = row;
+ png_bytep dp = row;
+ for (i = 0; i < row_width; i++)
+ {
+ png_uint_16 red, green, blue, gray16;
+ red = (png_uint_16)((*(sp)<<8) | *(sp+1)); sp+=2;
+ green = (png_uint_16)((*(sp)<<8) | *(sp+1)); sp+=2;
+ blue = (png_uint_16)((*(sp)<<8) | *(sp+1)); sp+=2;
+ if (red != green || red != blue)
+ rgb_error |= 1;
+ gray16 = (png_uint_16)((rc*red + gc*green + bc*blue)>>15);
+ *(dp++) = (png_byte)((gray16>>8) & 0xff);
+ *(dp++) = (png_byte)(gray16 & 0xff);
+ *(dp++) = *(sp++); /* alpha */
+ *(dp++) = *(sp++);
+ }
+ }
+ }
+ }
+ row_info->channels -= (png_byte)2;
+ row_info->color_type &= ~PNG_COLOR_MASK_COLOR;
+ row_info->pixel_depth = (png_byte)(row_info->channels *
+ row_info->bit_depth);
+ row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, row_width);
+ }
+ return rgb_error;
+}
+#endif
+
+/* Build a grayscale palette. Palette is assumed to be 1 << bit_depth
+ * large of png_color. This lets grayscale images be treated as
+ * paletted. Most useful for gamma correction and simplification
+ * of code.
+ */
+void PNGAPI
+png_build_grayscale_palette(int bit_depth, png_colorp palette)
+{
+ int num_palette;
+ int color_inc;
+ int i;
+ int v;
+
+ png_debug(1, "in png_do_build_grayscale_palette");
+
+ if (palette == NULL)
+ return;
+
+ switch (bit_depth)
+ {
+ case 1:
+ num_palette = 2;
+ color_inc = 0xff;
+ break;
+
+ case 2:
+ num_palette = 4;
+ color_inc = 0x55;
+ break;
+
+ case 4:
+ num_palette = 16;
+ color_inc = 0x11;
+ break;
+
+ case 8:
+ num_palette = 256;
+ color_inc = 1;
+ break;
+
+ default:
+ num_palette = 0;
+ color_inc = 0;
+ break;
+ }
+
+ for (i = 0, v = 0; i < num_palette; i++, v += color_inc)
+ {
+ palette[i].red = (png_byte)v;
+ palette[i].green = (png_byte)v;
+ palette[i].blue = (png_byte)v;
+ }
+}
+
+/* This function is currently unused. Do we really need it? */
+#if defined(PNG_READ_DITHER_SUPPORTED) && \
+ defined(PNG_CORRECT_PALETTE_SUPPORTED)
+void /* PRIVATE */
+png_correct_palette(png_structp png_ptr, png_colorp palette,
+ int num_palette)
+{
+ png_debug(1, "in png_correct_palette");
+
+#if defined(PNG_READ_BACKGROUND_SUPPORTED) && \
+ defined(PNG_READ_GAMMA_SUPPORTED) && \
+ defined(PNG_FLOATING_POINT_SUPPORTED)
+ if (png_ptr->transformations & (PNG_GAMMA | PNG_BACKGROUND))
+ {
+ png_color back, back_1;
+
+ if (png_ptr->background_gamma_type == PNG_BACKGROUND_GAMMA_FILE)
+ {
+ back.red = png_ptr->gamma_table[png_ptr->background.red];
+ back.green = png_ptr->gamma_table[png_ptr->background.green];
+ back.blue = png_ptr->gamma_table[png_ptr->background.blue];
+
+ back_1.red = png_ptr->gamma_to_1[png_ptr->background.red];
+ back_1.green = png_ptr->gamma_to_1[png_ptr->background.green];
+ back_1.blue = png_ptr->gamma_to_1[png_ptr->background.blue];
+ }
+ else
+ {
+ double g;
+
+ g = 1.0 / (png_ptr->background_gamma * png_ptr->screen_gamma);
+
+ if (png_ptr->background_gamma_type == PNG_BACKGROUND_GAMMA_SCREEN
+ || fabs(g - 1.0) < PNG_GAMMA_THRESHOLD)
+ {
+ back.red = png_ptr->background.red;
+ back.green = png_ptr->background.green;
+ back.blue = png_ptr->background.blue;
+ }
+ else
+ {
+ back.red =
+ (png_byte)(pow((double)png_ptr->background.red/255, g) *
+ 255.0 + 0.5);
+ back.green =
+ (png_byte)(pow((double)png_ptr->background.green/255, g) *
+ 255.0 + 0.5);
+ back.blue =
+ (png_byte)(pow((double)png_ptr->background.blue/255, g) *
+ 255.0 + 0.5);
+ }
+
+ g = 1.0 / png_ptr->background_gamma;
+
+ back_1.red =
+ (png_byte)(pow((double)png_ptr->background.red/255, g) *
+ 255.0 + 0.5);
+ back_1.green =
+ (png_byte)(pow((double)png_ptr->background.green/255, g) *
+ 255.0 + 0.5);
+ back_1.blue =
+ (png_byte)(pow((double)png_ptr->background.blue/255, g) *
+ 255.0 + 0.5);
+ }
+
+ if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE)
+ {
+ png_uint_32 i;
+
+ for (i = 0; i < (png_uint_32)num_palette; i++)
+ {
+ if (i < png_ptr->num_trans && png_ptr->trans[i] == 0)
+ {
+ palette[i] = back;
+ }
+ else if (i < png_ptr->num_trans && png_ptr->trans[i] != 0xff)
+ {
+ png_byte v, w;
+
+ v = png_ptr->gamma_to_1[png_ptr->palette[i].red];
+ png_composite(w, v, png_ptr->trans[i], back_1.red);
+ palette[i].red = png_ptr->gamma_from_1[w];
+
+ v = png_ptr->gamma_to_1[png_ptr->palette[i].green];
+ png_composite(w, v, png_ptr->trans[i], back_1.green);
+ palette[i].green = png_ptr->gamma_from_1[w];
+
+ v = png_ptr->gamma_to_1[png_ptr->palette[i].blue];
+ png_composite(w, v, png_ptr->trans[i], back_1.blue);
+ palette[i].blue = png_ptr->gamma_from_1[w];
+ }
+ else
+ {
+ palette[i].red = png_ptr->gamma_table[palette[i].red];
+ palette[i].green = png_ptr->gamma_table[palette[i].green];
+ palette[i].blue = png_ptr->gamma_table[palette[i].blue];
+ }
+ }
+ }
+ else
+ {
+ int i;
+
+ for (i = 0; i < num_palette; i++)
+ {
+ if (palette[i].red == (png_byte)png_ptr->trans_values.gray)
+ {
+ palette[i] = back;
+ }
+ else
+ {
+ palette[i].red = png_ptr->gamma_table[palette[i].red];
+ palette[i].green = png_ptr->gamma_table[palette[i].green];
+ palette[i].blue = png_ptr->gamma_table[palette[i].blue];
+ }
+ }
+ }
+ }
+ else
+#endif
+#ifdef PNG_READ_GAMMA_SUPPORTED
+ if (png_ptr->transformations & PNG_GAMMA)
+ {
+ int i;
+
+ for (i = 0; i < num_palette; i++)
+ {
+ palette[i].red = png_ptr->gamma_table[palette[i].red];
+ palette[i].green = png_ptr->gamma_table[palette[i].green];
+ palette[i].blue = png_ptr->gamma_table[palette[i].blue];
+ }
+ }
+#ifdef PNG_READ_BACKGROUND_SUPPORTED
+ else
+#endif
+#endif
+#ifdef PNG_READ_BACKGROUND_SUPPORTED
+ if (png_ptr->transformations & PNG_BACKGROUND)
+ {
+ if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE)
+ {
+ png_color back;
+
+ back.red = (png_byte)png_ptr->background.red;
+ back.green = (png_byte)png_ptr->background.green;
+ back.blue = (png_byte)png_ptr->background.blue;
+
+ for (i = 0; i < (int)png_ptr->num_trans; i++)
+ {
+ if (png_ptr->trans[i] == 0)
+ {
+ palette[i].red = back.red;
+ palette[i].green = back.green;
+ palette[i].blue = back.blue;
+ }
+ else if (png_ptr->trans[i] != 0xff)
+ {
+ png_composite(palette[i].red, png_ptr->palette[i].red,
+ png_ptr->trans[i], back.red);
+ png_composite(palette[i].green, png_ptr->palette[i].green,
+ png_ptr->trans[i], back.green);
+ png_composite(palette[i].blue, png_ptr->palette[i].blue,
+ png_ptr->trans[i], back.blue);
+ }
+ }
+ }
+ else /* Assume grayscale palette (what else could it be?) */
+ {
+ int i;
+
+ for (i = 0; i < num_palette; i++)
+ {
+ if (i == (png_byte)png_ptr->trans_values.gray)
+ {
+ palette[i].red = (png_byte)png_ptr->background.red;
+ palette[i].green = (png_byte)png_ptr->background.green;
+ palette[i].blue = (png_byte)png_ptr->background.blue;
+ }
+ }
+ }
+ }
+#endif
+}
+#endif
+
+#ifdef PNG_READ_BACKGROUND_SUPPORTED
+/* Replace any alpha or transparency with the supplied background color.
+ * "background" is already in the screen gamma, while "background_1" is
+ * at a gamma of 1.0. Paletted files have already been taken care of.
+ */
+void /* PRIVATE */
+png_do_background(png_row_infop row_info, png_bytep row,
+ png_color_16p trans_values, png_color_16p background
+#ifdef PNG_READ_GAMMA_SUPPORTED
+ , png_color_16p background_1,
+ png_bytep gamma_table, png_bytep gamma_from_1, png_bytep gamma_to_1,
+ png_uint_16pp gamma_16, png_uint_16pp gamma_16_from_1,
+ png_uint_16pp gamma_16_to_1, int gamma_shift
+#endif
+ )
+{
+ png_bytep sp, dp;
+ png_uint_32 i;
+ png_uint_32 row_width=row_info->width;
+ int shift;
+
+ png_debug(1, "in png_do_background");
+
+ if (background != NULL &&
+#ifdef PNG_USELESS_TESTS_SUPPORTED
+ row != NULL && row_info != NULL &&
+#endif
+ (!(row_info->color_type & PNG_COLOR_MASK_ALPHA) ||
+ (row_info->color_type != PNG_COLOR_TYPE_PALETTE && trans_values)))
+ {
+ switch (row_info->color_type)
+ {
+ case PNG_COLOR_TYPE_GRAY:
+ {
+ switch (row_info->bit_depth)
+ {
+ case 1:
+ {
+ sp = row;
+ shift = 7;
+ for (i = 0; i < row_width; i++)
+ {
+ if ((png_uint_16)((*sp >> shift) & 0x01)
+ == trans_values->gray)
+ {
+ *sp &= (png_byte)((0x7f7f >> (7 - shift)) & 0xff);
+ *sp |= (png_byte)(background->gray << shift);
+ }
+ if (!shift)
+ {
+ shift = 7;
+ sp++;
+ }
+ else
+ shift--;
+ }
+ break;
+ }
+
+ case 2:
+ {
+#ifdef PNG_READ_GAMMA_SUPPORTED
+ if (gamma_table != NULL)
+ {
+ sp = row;
+ shift = 6;
+ for (i = 0; i < row_width; i++)
+ {
+ if ((png_uint_16)((*sp >> shift) & 0x03)
+ == trans_values->gray)
+ {
+ *sp &= (png_byte)((0x3f3f >> (6 - shift)) & 0xff);
+ *sp |= (png_byte)(background->gray << shift);
+ }
+ else
+ {
+ png_byte p = (png_byte)((*sp >> shift) & 0x03);
+ png_byte g = (png_byte)((gamma_table [p | (p << 2) |
+ (p << 4) | (p << 6)] >> 6) & 0x03);
+ *sp &= (png_byte)((0x3f3f >> (6 - shift)) & 0xff);
+ *sp |= (png_byte)(g << shift);
+ }
+ if (!shift)
+ {
+ shift = 6;
+ sp++;
+ }
+ else
+ shift -= 2;
+ }
+ }
+ else
+#endif
+ {
+ sp = row;
+ shift = 6;
+ for (i = 0; i < row_width; i++)
+ {
+ if ((png_uint_16)((*sp >> shift) & 0x03)
+ == trans_values->gray)
+ {
+ *sp &= (png_byte)((0x3f3f >> (6 - shift)) & 0xff);
+ *sp |= (png_byte)(background->gray << shift);
+ }
+ if (!shift)
+ {
+ shift = 6;
+ sp++;
+ }
+ else
+ shift -= 2;
+ }
+ }
+ break;
+ }
+
+ case 4:
+ {
+#ifdef PNG_READ_GAMMA_SUPPORTED
+ if (gamma_table != NULL)
+ {
+ sp = row;
+ shift = 4;
+ for (i = 0; i < row_width; i++)
+ {
+ if ((png_uint_16)((*sp >> shift) & 0x0f)
+ == trans_values->gray)
+ {
+ *sp &= (png_byte)((0xf0f >> (4 - shift)) & 0xff);
+ *sp |= (png_byte)(background->gray << shift);
+ }
+ else
+ {
+ png_byte p = (png_byte)((*sp >> shift) & 0x0f);
+ png_byte g = (png_byte)((gamma_table[p |
+ (p << 4)] >> 4) & 0x0f);
+ *sp &= (png_byte)((0xf0f >> (4 - shift)) & 0xff);
+ *sp |= (png_byte)(g << shift);
+ }
+ if (!shift)
+ {
+ shift = 4;
+ sp++;
+ }
+ else
+ shift -= 4;
+ }
+ }
+ else
+#endif
+ {
+ sp = row;
+ shift = 4;
+ for (i = 0; i < row_width; i++)
+ {
+ if ((png_uint_16)((*sp >> shift) & 0x0f)
+ == trans_values->gray)
+ {
+ *sp &= (png_byte)((0xf0f >> (4 - shift)) & 0xff);
+ *sp |= (png_byte)(background->gray << shift);
+ }
+ if (!shift)
+ {
+ shift = 4;
+ sp++;
+ }
+ else
+ shift -= 4;
+ }
+ }
+ break;
+ }
+
+ case 8:
+ {
+#ifdef PNG_READ_GAMMA_SUPPORTED
+ if (gamma_table != NULL)
+ {
+ sp = row;
+ for (i = 0; i < row_width; i++, sp++)
+ {
+ if (*sp == trans_values->gray)
+ {
+ *sp = (png_byte)background->gray;
+ }
+ else
+ {
+ *sp = gamma_table[*sp];
+ }
+ }
+ }
+ else
+#endif
+ {
+ sp = row;
+ for (i = 0; i < row_width; i++, sp++)
+ {
+ if (*sp == trans_values->gray)
+ {
+ *sp = (png_byte)background->gray;
+ }
+ }
+ }
+ break;
+ }
+
+ case 16:
+ {
+#ifdef PNG_READ_GAMMA_SUPPORTED
+ if (gamma_16 != NULL)
+ {
+ sp = row;
+ for (i = 0; i < row_width; i++, sp += 2)
+ {
+ png_uint_16 v;
+
+ v = (png_uint_16)(((*sp) << 8) + *(sp + 1));
+ if (v == trans_values->gray)
+ {
+ /* Background is already in screen gamma */
+ *sp = (png_byte)((background->gray >> 8) & 0xff);
+ *(sp + 1) = (png_byte)(background->gray & 0xff);
+ }
+ else
+ {
+ v = gamma_16[*(sp + 1) >> gamma_shift][*sp];
+ *sp = (png_byte)((v >> 8) & 0xff);
+ *(sp + 1) = (png_byte)(v & 0xff);
+ }
+ }
+ }
+ else
+#endif
+ {
+ sp = row;
+ for (i = 0; i < row_width; i++, sp += 2)
+ {
+ png_uint_16 v;
+
+ v = (png_uint_16)(((*sp) << 8) + *(sp + 1));
+ if (v == trans_values->gray)
+ {
+ *sp = (png_byte)((background->gray >> 8) & 0xff);
+ *(sp + 1) = (png_byte)(background->gray & 0xff);
+ }
+ }
+ }
+ break;
+ }
+ }
+ break;
+ }
+
+ case PNG_COLOR_TYPE_RGB:
+ {
+ if (row_info->bit_depth == 8)
+ {
+#ifdef PNG_READ_GAMMA_SUPPORTED
+ if (gamma_table != NULL)
+ {
+ sp = row;
+ for (i = 0; i < row_width; i++, sp += 3)
+ {
+ if (*sp == trans_values->red &&
+ *(sp + 1) == trans_values->green &&
+ *(sp + 2) == trans_values->blue)
+ {
+ *sp = (png_byte)background->red;
+ *(sp + 1) = (png_byte)background->green;
+ *(sp + 2) = (png_byte)background->blue;
+ }
+ else
+ {
+ *sp = gamma_table[*sp];
+ *(sp + 1) = gamma_table[*(sp + 1)];
+ *(sp + 2) = gamma_table[*(sp + 2)];
+ }
+ }
+ }
+ else
+#endif
+ {
+ sp = row;
+ for (i = 0; i < row_width; i++, sp += 3)
+ {
+ if (*sp == trans_values->red &&
+ *(sp + 1) == trans_values->green &&
+ *(sp + 2) == trans_values->blue)
+ {
+ *sp = (png_byte)background->red;
+ *(sp + 1) = (png_byte)background->green;
+ *(sp + 2) = (png_byte)background->blue;
+ }
+ }
+ }
+ }
+ else /* if (row_info->bit_depth == 16) */
+ {
+#ifdef PNG_READ_GAMMA_SUPPORTED
+ if (gamma_16 != NULL)
+ {
+ sp = row;
+ for (i = 0; i < row_width; i++, sp += 6)
+ {
+ png_uint_16 r = (png_uint_16)(((*sp) << 8) + *(sp + 1));
+ png_uint_16 g = (png_uint_16)(((*(sp+2)) << 8) + *(sp+3));
+ png_uint_16 b = (png_uint_16)(((*(sp+4)) << 8) + *(sp+5));
+ if (r == trans_values->red && g == trans_values->green &&
+ b == trans_values->blue)
+ {
+ /* Background is already in screen gamma */
+ *sp = (png_byte)((background->red >> 8) & 0xff);
+ *(sp + 1) = (png_byte)(background->red & 0xff);
+ *(sp + 2) = (png_byte)((background->green >> 8) & 0xff);
+ *(sp + 3) = (png_byte)(background->green & 0xff);
+ *(sp + 4) = (png_byte)((background->blue >> 8) & 0xff);
+ *(sp + 5) = (png_byte)(background->blue & 0xff);
+ }
+ else
+ {
+ png_uint_16 v = gamma_16[*(sp + 1) >> gamma_shift][*sp];
+ *sp = (png_byte)((v >> 8) & 0xff);
+ *(sp + 1) = (png_byte)(v & 0xff);
+ v = gamma_16[*(sp + 3) >> gamma_shift][*(sp + 2)];
+ *(sp + 2) = (png_byte)((v >> 8) & 0xff);
+ *(sp + 3) = (png_byte)(v & 0xff);
+ v = gamma_16[*(sp + 5) >> gamma_shift][*(sp + 4)];
+ *(sp + 4) = (png_byte)((v >> 8) & 0xff);
+ *(sp + 5) = (png_byte)(v & 0xff);
+ }
+ }
+ }
+ else
+#endif
+ {
+ sp = row;
+ for (i = 0; i < row_width; i++, sp += 6)
+ {
+ png_uint_16 r = (png_uint_16)(((*sp) << 8) + *(sp+1));
+ png_uint_16 g = (png_uint_16)(((*(sp+2)) << 8) + *(sp+3));
+ png_uint_16 b = (png_uint_16)(((*(sp+4)) << 8) + *(sp+5));
+
+ if (r == trans_values->red && g == trans_values->green &&
+ b == trans_values->blue)
+ {
+ *sp = (png_byte)((background->red >> 8) & 0xff);
+ *(sp + 1) = (png_byte)(background->red & 0xff);
+ *(sp + 2) = (png_byte)((background->green >> 8) & 0xff);
+ *(sp + 3) = (png_byte)(background->green & 0xff);
+ *(sp + 4) = (png_byte)((background->blue >> 8) & 0xff);
+ *(sp + 5) = (png_byte)(background->blue & 0xff);
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ case PNG_COLOR_TYPE_GRAY_ALPHA:
+ {
+ if (row_info->bit_depth == 8)
+ {
+#ifdef PNG_READ_GAMMA_SUPPORTED
+ if (gamma_to_1 != NULL && gamma_from_1 != NULL &&
+ gamma_table != NULL)
+ {
+ sp = row;
+ dp = row;
+ for (i = 0; i < row_width; i++, sp += 2, dp++)
+ {
+ png_uint_16 a = *(sp + 1);
+
+ if (a == 0xff)
+ {
+ *dp = gamma_table[*sp];
+ }
+ else if (a == 0)
+ {
+ /* Background is already in screen gamma */
+ *dp = (png_byte)background->gray;
+ }
+ else
+ {
+ png_byte v, w;
+
+ v = gamma_to_1[*sp];
+ png_composite(w, v, a, background_1->gray);
+ *dp = gamma_from_1[w];
+ }
+ }
+ }
+ else
+#endif
+ {
+ sp = row;
+ dp = row;
+ for (i = 0; i < row_width; i++, sp += 2, dp++)
+ {
+ png_byte a = *(sp + 1);
+
+ if (a == 0xff)
+ {
+ *dp = *sp;
+ }
+#ifdef PNG_READ_GAMMA_SUPPORTED
+ else if (a == 0)
+ {
+ *dp = (png_byte)background->gray;
+ }
+ else
+ {
+ png_composite(*dp, *sp, a, background_1->gray);
+ }
+#else
+ *dp = (png_byte)background->gray;
+#endif
+ }
+ }
+ }
+ else /* if (png_ptr->bit_depth == 16) */
+ {
+#ifdef PNG_READ_GAMMA_SUPPORTED
+ if (gamma_16 != NULL && gamma_16_from_1 != NULL &&
+ gamma_16_to_1 != NULL)
+ {
+ sp = row;
+ dp = row;
+ for (i = 0; i < row_width; i++, sp += 4, dp += 2)
+ {
+ png_uint_16 a = (png_uint_16)(((*(sp+2)) << 8) + *(sp+3));
+
+ if (a == (png_uint_16)0xffff)
+ {
+ png_uint_16 v;
+
+ v = gamma_16[*(sp + 1) >> gamma_shift][*sp];
+ *dp = (png_byte)((v >> 8) & 0xff);
+ *(dp + 1) = (png_byte)(v & 0xff);
+ }
+#ifdef PNG_READ_GAMMA_SUPPORTED
+ else if (a == 0)
+#else
+ else
+#endif
+ {
+ /* Background is already in screen gamma */
+ *dp = (png_byte)((background->gray >> 8) & 0xff);
+ *(dp + 1) = (png_byte)(background->gray & 0xff);
+ }
+#ifdef PNG_READ_GAMMA_SUPPORTED
+ else
+ {
+ png_uint_16 g, v, w;
+
+ g = gamma_16_to_1[*(sp + 1) >> gamma_shift][*sp];
+ png_composite_16(v, g, a, background_1->gray);
+ w = gamma_16_from_1[(v&0xff) >> gamma_shift][v >> 8];
+ *dp = (png_byte)((w >> 8) & 0xff);
+ *(dp + 1) = (png_byte)(w & 0xff);
+ }
+#endif
+ }
+ }
+ else
+#endif
+ {
+ sp = row;
+ dp = row;
+ for (i = 0; i < row_width; i++, sp += 4, dp += 2)
+ {
+ png_uint_16 a = (png_uint_16)(((*(sp+2)) << 8) + *(sp+3));
+ if (a == (png_uint_16)0xffff)
+ {
+ png_memcpy(dp, sp, 2);
+ }
+#ifdef PNG_READ_GAMMA_SUPPORTED
+ else if (a == 0)
+#else
+ else
+#endif
+ {
+ *dp = (png_byte)((background->gray >> 8) & 0xff);
+ *(dp + 1) = (png_byte)(background->gray & 0xff);
+ }
+#ifdef PNG_READ_GAMMA_SUPPORTED
+ else
+ {
+ png_uint_16 g, v;
+
+ g = (png_uint_16)(((*sp) << 8) + *(sp + 1));
+ png_composite_16(v, g, a, background_1->gray);
+ *dp = (png_byte)((v >> 8) & 0xff);
+ *(dp + 1) = (png_byte)(v & 0xff);
+ }
+#endif
+ }
+ }
+ }
+ break;
+ }
+
+ case PNG_COLOR_TYPE_RGB_ALPHA:
+ {
+ if (row_info->bit_depth == 8)
+ {
+#ifdef PNG_READ_GAMMA_SUPPORTED
+ if (gamma_to_1 != NULL && gamma_from_1 != NULL &&
+ gamma_table != NULL)
+ {
+ sp = row;
+ dp = row;
+ for (i = 0; i < row_width; i++, sp += 4, dp += 3)
+ {
+ png_byte a = *(sp + 3);
+
+ if (a == 0xff)
+ {
+ *dp = gamma_table[*sp];
+ *(dp + 1) = gamma_table[*(sp + 1)];
+ *(dp + 2) = gamma_table[*(sp + 2)];
+ }
+ else if (a == 0)
+ {
+ /* Background is already in screen gamma */
+ *dp = (png_byte)background->red;
+ *(dp + 1) = (png_byte)background->green;
+ *(dp + 2) = (png_byte)background->blue;
+ }
+ else
+ {
+ png_byte v, w;
+
+ v = gamma_to_1[*sp];
+ png_composite(w, v, a, background_1->red);
+ *dp = gamma_from_1[w];
+ v = gamma_to_1[*(sp + 1)];
+ png_composite(w, v, a, background_1->green);
+ *(dp + 1) = gamma_from_1[w];
+ v = gamma_to_1[*(sp + 2)];
+ png_composite(w, v, a, background_1->blue);
+ *(dp + 2) = gamma_from_1[w];
+ }
+ }
+ }
+ else
+#endif
+ {
+ sp = row;
+ dp = row;
+ for (i = 0; i < row_width; i++, sp += 4, dp += 3)
+ {
+ png_byte a = *(sp + 3);
+
+ if (a == 0xff)
+ {
+ *dp = *sp;
+ *(dp + 1) = *(sp + 1);
+ *(dp + 2) = *(sp + 2);
+ }
+ else if (a == 0)
+ {
+ *dp = (png_byte)background->red;
+ *(dp + 1) = (png_byte)background->green;
+ *(dp + 2) = (png_byte)background->blue;
+ }
+ else
+ {
+ png_composite(*dp, *sp, a, background->red);
+ png_composite(*(dp + 1), *(sp + 1), a,
+ background->green);
+ png_composite(*(dp + 2), *(sp + 2), a,
+ background->blue);
+ }
+ }
+ }
+ }
+ else /* if (row_info->bit_depth == 16) */
+ {
+#ifdef PNG_READ_GAMMA_SUPPORTED
+ if (gamma_16 != NULL && gamma_16_from_1 != NULL &&
+ gamma_16_to_1 != NULL)
+ {
+ sp = row;
+ dp = row;
+ for (i = 0; i < row_width; i++, sp += 8, dp += 6)
+ {
+ png_uint_16 a = (png_uint_16)(((png_uint_16)(*(sp + 6))
+ << 8) + (png_uint_16)(*(sp + 7)));
+ if (a == (png_uint_16)0xffff)
+ {
+ png_uint_16 v;
+
+ v = gamma_16[*(sp + 1) >> gamma_shift][*sp];
+ *dp = (png_byte)((v >> 8) & 0xff);
+ *(dp + 1) = (png_byte)(v & 0xff);
+ v = gamma_16[*(sp + 3) >> gamma_shift][*(sp + 2)];
+ *(dp + 2) = (png_byte)((v >> 8) & 0xff);
+ *(dp + 3) = (png_byte)(v & 0xff);
+ v = gamma_16[*(sp + 5) >> gamma_shift][*(sp + 4)];
+ *(dp + 4) = (png_byte)((v >> 8) & 0xff);
+ *(dp + 5) = (png_byte)(v & 0xff);
+ }
+ else if (a == 0)
+ {
+ /* Background is already in screen gamma */
+ *dp = (png_byte)((background->red >> 8) & 0xff);
+ *(dp + 1) = (png_byte)(background->red & 0xff);
+ *(dp + 2) = (png_byte)((background->green >> 8) & 0xff);
+ *(dp + 3) = (png_byte)(background->green & 0xff);
+ *(dp + 4) = (png_byte)((background->blue >> 8) & 0xff);
+ *(dp + 5) = (png_byte)(background->blue & 0xff);
+ }
+ else
+ {
+ png_uint_16 v, w, x;
+
+ v = gamma_16_to_1[*(sp + 1) >> gamma_shift][*sp];
+ png_composite_16(w, v, a, background_1->red);
+ x = gamma_16_from_1[((w&0xff) >> gamma_shift)][w >> 8];
+ *dp = (png_byte)((x >> 8) & 0xff);
+ *(dp + 1) = (png_byte)(x & 0xff);
+ v = gamma_16_to_1[*(sp + 3) >> gamma_shift][*(sp + 2)];
+ png_composite_16(w, v, a, background_1->green);
+ x = gamma_16_from_1[((w&0xff) >> gamma_shift)][w >> 8];
+ *(dp + 2) = (png_byte)((x >> 8) & 0xff);
+ *(dp + 3) = (png_byte)(x & 0xff);
+ v = gamma_16_to_1[*(sp + 5) >> gamma_shift][*(sp + 4)];
+ png_composite_16(w, v, a, background_1->blue);
+ x = gamma_16_from_1[(w & 0xff) >> gamma_shift][w >> 8];
+ *(dp + 4) = (png_byte)((x >> 8) & 0xff);
+ *(dp + 5) = (png_byte)(x & 0xff);
+ }
+ }
+ }
+ else
+#endif
+ {
+ sp = row;
+ dp = row;
+ for (i = 0; i < row_width; i++, sp += 8, dp += 6)
+ {
+ png_uint_16 a = (png_uint_16)(((png_uint_16)(*(sp + 6))
+ << 8) + (png_uint_16)(*(sp + 7)));
+ if (a == (png_uint_16)0xffff)
+ {
+ png_memcpy(dp, sp, 6);
+ }
+ else if (a == 0)
+ {
+ *dp = (png_byte)((background->red >> 8) & 0xff);
+ *(dp + 1) = (png_byte)(background->red & 0xff);
+ *(dp + 2) = (png_byte)((background->green >> 8) & 0xff);
+ *(dp + 3) = (png_byte)(background->green & 0xff);
+ *(dp + 4) = (png_byte)((background->blue >> 8) & 0xff);
+ *(dp + 5) = (png_byte)(background->blue & 0xff);
+ }
+ else
+ {
+ png_uint_16 v;
+
+ png_uint_16 r = (png_uint_16)(((*sp) << 8) + *(sp + 1));
+ png_uint_16 g = (png_uint_16)(((*(sp + 2)) << 8)
+ + *(sp + 3));
+ png_uint_16 b = (png_uint_16)(((*(sp + 4)) << 8)
+ + *(sp + 5));
+
+ png_composite_16(v, r, a, background->red);
+ *dp = (png_byte)((v >> 8) & 0xff);
+ *(dp + 1) = (png_byte)(v & 0xff);
+ png_composite_16(v, g, a, background->green);
+ *(dp + 2) = (png_byte)((v >> 8) & 0xff);
+ *(dp + 3) = (png_byte)(v & 0xff);
+ png_composite_16(v, b, a, background->blue);
+ *(dp + 4) = (png_byte)((v >> 8) & 0xff);
+ *(dp + 5) = (png_byte)(v & 0xff);
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ if (row_info->color_type & PNG_COLOR_MASK_ALPHA)
+ {
+ row_info->color_type &= ~PNG_COLOR_MASK_ALPHA;
+ row_info->channels--;
+ row_info->pixel_depth = (png_byte)(row_info->channels *
+ row_info->bit_depth);
+ row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, row_width);
+ }
+ }
+}
+#endif
+
+#ifdef PNG_READ_GAMMA_SUPPORTED
+/* Gamma correct the image, avoiding the alpha channel. Make sure
+ * you do this after you deal with the transparency issue on grayscale
+ * or RGB images. If your bit depth is 8, use gamma_table, if it
+ * is 16, use gamma_16_table and gamma_shift. Build these with
+ * build_gamma_table().
+ */
+void /* PRIVATE */
+png_do_gamma(png_row_infop row_info, png_bytep row,
+ png_bytep gamma_table, png_uint_16pp gamma_16_table,
+ int gamma_shift)
+{
+ png_bytep sp;
+ png_uint_32 i;
+ png_uint_32 row_width=row_info->width;
+
+ png_debug(1, "in png_do_gamma");
+
+ if (
+#ifdef PNG_USELESS_TESTS_SUPPORTED
+ row != NULL && row_info != NULL &&
+#endif
+ ((row_info->bit_depth <= 8 && gamma_table != NULL) ||
+ (row_info->bit_depth == 16 && gamma_16_table != NULL)))
+ {
+ switch (row_info->color_type)
+ {
+ case PNG_COLOR_TYPE_RGB:
+ {
+ if (row_info->bit_depth == 8)
+ {
+ sp = row;
+ for (i = 0; i < row_width; i++)
+ {
+ *sp = gamma_table[*sp];
+ sp++;
+ *sp = gamma_table[*sp];
+ sp++;
+ *sp = gamma_table[*sp];
+ sp++;
+ }
+ }
+ else /* if (row_info->bit_depth == 16) */
+ {
+ sp = row;
+ for (i = 0; i < row_width; i++)
+ {
+ png_uint_16 v;
+
+ v = gamma_16_table[*(sp + 1) >> gamma_shift][*sp];
+ *sp = (png_byte)((v >> 8) & 0xff);
+ *(sp + 1) = (png_byte)(v & 0xff);
+ sp += 2;
+ v = gamma_16_table[*(sp + 1) >> gamma_shift][*sp];
+ *sp = (png_byte)((v >> 8) & 0xff);
+ *(sp + 1) = (png_byte)(v & 0xff);
+ sp += 2;
+ v = gamma_16_table[*(sp + 1) >> gamma_shift][*sp];
+ *sp = (png_byte)((v >> 8) & 0xff);
+ *(sp + 1) = (png_byte)(v & 0xff);
+ sp += 2;
+ }
+ }
+ break;
+ }
+
+ case PNG_COLOR_TYPE_RGB_ALPHA:
+ {
+ if (row_info->bit_depth == 8)
+ {
+ sp = row;
+ for (i = 0; i < row_width; i++)
+ {
+ *sp = gamma_table[*sp];
+ sp++;
+ *sp = gamma_table[*sp];
+ sp++;
+ *sp = gamma_table[*sp];
+ sp++;
+ sp++;
+ }
+ }
+ else /* if (row_info->bit_depth == 16) */
+ {
+ sp = row;
+ for (i = 0; i < row_width; i++)
+ {
+ png_uint_16 v = gamma_16_table[*(sp + 1) >> gamma_shift][*sp];
+ *sp = (png_byte)((v >> 8) & 0xff);
+ *(sp + 1) = (png_byte)(v & 0xff);
+ sp += 2;
+ v = gamma_16_table[*(sp + 1) >> gamma_shift][*sp];
+ *sp = (png_byte)((v >> 8) & 0xff);
+ *(sp + 1) = (png_byte)(v & 0xff);
+ sp += 2;
+ v = gamma_16_table[*(sp + 1) >> gamma_shift][*sp];
+ *sp = (png_byte)((v >> 8) & 0xff);
+ *(sp + 1) = (png_byte)(v & 0xff);
+ sp += 4;
+ }
+ }
+ break;
+ }
+
+ case PNG_COLOR_TYPE_GRAY_ALPHA:
+ {
+ if (row_info->bit_depth == 8)
+ {
+ sp = row;
+ for (i = 0; i < row_width; i++)
+ {
+ *sp = gamma_table[*sp];
+ sp += 2;
+ }
+ }
+ else /* if (row_info->bit_depth == 16) */
+ {
+ sp = row;
+ for (i = 0; i < row_width; i++)
+ {
+ png_uint_16 v = gamma_16_table[*(sp + 1) >> gamma_shift][*sp];
+ *sp = (png_byte)((v >> 8) & 0xff);
+ *(sp + 1) = (png_byte)(v & 0xff);
+ sp += 4;
+ }
+ }
+ break;
+ }
+
+ case PNG_COLOR_TYPE_GRAY:
+ {
+ if (row_info->bit_depth == 2)
+ {
+ sp = row;
+ for (i = 0; i < row_width; i += 4)
+ {
+ int a = *sp & 0xc0;
+ int b = *sp & 0x30;
+ int c = *sp & 0x0c;
+ int d = *sp & 0x03;
+
+ *sp = (png_byte)(
+ ((((int)gamma_table[a|(a>>2)|(a>>4)|(a>>6)]) ) & 0xc0)|
+ ((((int)gamma_table[(b<<2)|b|(b>>2)|(b>>4)])>>2) & 0x30)|
+ ((((int)gamma_table[(c<<4)|(c<<2)|c|(c>>2)])>>4) & 0x0c)|
+ ((((int)gamma_table[(d<<6)|(d<<4)|(d<<2)|d])>>6) ));
+ sp++;
+ }
+ }
+
+ if (row_info->bit_depth == 4)
+ {
+ sp = row;
+ for (i = 0; i < row_width; i += 2)
+ {
+ int msb = *sp & 0xf0;
+ int lsb = *sp & 0x0f;
+
+ *sp = (png_byte)((((int)gamma_table[msb | (msb >> 4)]) & 0xf0)
+ | (((int)gamma_table[(lsb << 4) | lsb]) >> 4));
+ sp++;
+ }
+ }
+
+ else if (row_info->bit_depth == 8)
+ {
+ sp = row;
+ for (i = 0; i < row_width; i++)
+ {
+ *sp = gamma_table[*sp];
+ sp++;
+ }
+ }
+
+ else if (row_info->bit_depth == 16)
+ {
+ sp = row;
+ for (i = 0; i < row_width; i++)
+ {
+ png_uint_16 v = gamma_16_table[*(sp + 1) >> gamma_shift][*sp];
+ *sp = (png_byte)((v >> 8) & 0xff);
+ *(sp + 1) = (png_byte)(v & 0xff);
+ sp += 2;
+ }
+ }
+ break;
+ }
+ }
+ }
+}
+#endif
+
+#ifdef PNG_READ_EXPAND_SUPPORTED
+/* Expands a palette row to an RGB or RGBA row depending
+ * upon whether you supply trans and num_trans.
+ */
+void /* PRIVATE */
+png_do_expand_palette(png_row_infop row_info, png_bytep row,
+ png_colorp palette, png_bytep trans, int num_trans)
+{
+ int shift, value;
+ png_bytep sp, dp;
+ png_uint_32 i;
+ png_uint_32 row_width=row_info->width;
+
+ png_debug(1, "in png_do_expand_palette");
+
+ if (
+#ifdef PNG_USELESS_TESTS_SUPPORTED
+ row != NULL && row_info != NULL &&
+#endif
+ row_info->color_type == PNG_COLOR_TYPE_PALETTE)
+ {
+ if (row_info->bit_depth < 8)
+ {
+ switch (row_info->bit_depth)
+ {
+ case 1:
+ {
+ sp = row + (png_size_t)((row_width - 1) >> 3);
+ dp = row + (png_size_t)row_width - 1;
+ shift = 7 - (int)((row_width + 7) & 0x07);
+ for (i = 0; i < row_width; i++)
+ {
+ if ((*sp >> shift) & 0x01)
+ *dp = 1;
+ else
+ *dp = 0;
+ if (shift == 7)
+ {
+ shift = 0;
+ sp--;
+ }
+ else
+ shift++;
+
+ dp--;
+ }
+ break;
+ }
+
+ case 2:
+ {
+ sp = row + (png_size_t)((row_width - 1) >> 2);
+ dp = row + (png_size_t)row_width - 1;
+ shift = (int)((3 - ((row_width + 3) & 0x03)) << 1);
+ for (i = 0; i < row_width; i++)
+ {
+ value = (*sp >> shift) & 0x03;
+ *dp = (png_byte)value;
+ if (shift == 6)
+ {
+ shift = 0;
+ sp--;
+ }
+ else
+ shift += 2;
+
+ dp--;
+ }
+ break;
+ }
+
+ case 4:
+ {
+ sp = row + (png_size_t)((row_width - 1) >> 1);
+ dp = row + (png_size_t)row_width - 1;
+ shift = (int)((row_width & 0x01) << 2);
+ for (i = 0; i < row_width; i++)
+ {
+ value = (*sp >> shift) & 0x0f;
+ *dp = (png_byte)value;
+ if (shift == 4)
+ {
+ shift = 0;
+ sp--;
+ }
+ else
+ shift += 4;
+
+ dp--;
+ }
+ break;
+ }
+ }
+ row_info->bit_depth = 8;
+ row_info->pixel_depth = 8;
+ row_info->rowbytes = row_width;
+ }
+ switch (row_info->bit_depth)
+ {
+ case 8:
+ {
+ if (trans != NULL)
+ {
+ sp = row + (png_size_t)row_width - 1;
+ dp = row + (png_size_t)(row_width << 2) - 1;
+
+ for (i = 0; i < row_width; i++)
+ {
+ if ((int)(*sp) >= num_trans)
+ *dp-- = 0xff;
+ else
+ *dp-- = trans[*sp];
+ *dp-- = palette[*sp].blue;
+ *dp-- = palette[*sp].green;
+ *dp-- = palette[*sp].red;
+ sp--;
+ }
+ row_info->bit_depth = 8;
+ row_info->pixel_depth = 32;
+ row_info->rowbytes = row_width * 4;
+ row_info->color_type = 6;
+ row_info->channels = 4;
+ }
+ else
+ {
+ sp = row + (png_size_t)row_width - 1;
+ dp = row + (png_size_t)(row_width * 3) - 1;
+
+ for (i = 0; i < row_width; i++)
+ {
+ *dp-- = palette[*sp].blue;
+ *dp-- = palette[*sp].green;
+ *dp-- = palette[*sp].red;
+ sp--;
+ }
+
+ row_info->bit_depth = 8;
+ row_info->pixel_depth = 24;
+ row_info->rowbytes = row_width * 3;
+ row_info->color_type = 2;
+ row_info->channels = 3;
+ }
+ break;
+ }
+ }
+ }
+}
+
+/* If the bit depth < 8, it is expanded to 8. Also, if the already
+ * expanded transparency value is supplied, an alpha channel is built.
+ */
+void /* PRIVATE */
+png_do_expand(png_row_infop row_info, png_bytep row,
+ png_color_16p trans_value)
+{
+ int shift, value;
+ png_bytep sp, dp;
+ png_uint_32 i;
+ png_uint_32 row_width=row_info->width;
+
+ png_debug(1, "in png_do_expand");
+
+#ifdef PNG_USELESS_TESTS_SUPPORTED
+ if (row != NULL && row_info != NULL)
+#endif
+ {
+ if (row_info->color_type == PNG_COLOR_TYPE_GRAY)
+ {
+ png_uint_16 gray = (png_uint_16)(trans_value ? trans_value->gray : 0);
+
+ if (row_info->bit_depth < 8)
+ {
+ switch (row_info->bit_depth)
+ {
+ case 1:
+ {
+ gray = (png_uint_16)((gray&0x01)*0xff);
+ sp = row + (png_size_t)((row_width - 1) >> 3);
+ dp = row + (png_size_t)row_width - 1;
+ shift = 7 - (int)((row_width + 7) & 0x07);
+ for (i = 0; i < row_width; i++)
+ {
+ if ((*sp >> shift) & 0x01)
+ *dp = 0xff;
+ else
+ *dp = 0;
+ if (shift == 7)
+ {
+ shift = 0;
+ sp--;
+ }
+ else
+ shift++;
+
+ dp--;
+ }
+ break;
+ }
+
+ case 2:
+ {
+ gray = (png_uint_16)((gray&0x03)*0x55);
+ sp = row + (png_size_t)((row_width - 1) >> 2);
+ dp = row + (png_size_t)row_width - 1;
+ shift = (int)((3 - ((row_width + 3) & 0x03)) << 1);
+ for (i = 0; i < row_width; i++)
+ {
+ value = (*sp >> shift) & 0x03;
+ *dp = (png_byte)(value | (value << 2) | (value << 4) |
+ (value << 6));
+ if (shift == 6)
+ {
+ shift = 0;
+ sp--;
+ }
+ else
+ shift += 2;
+
+ dp--;
+ }
+ break;
+ }
+
+ case 4:
+ {
+ gray = (png_uint_16)((gray&0x0f)*0x11);
+ sp = row + (png_size_t)((row_width - 1) >> 1);
+ dp = row + (png_size_t)row_width - 1;
+ shift = (int)((1 - ((row_width + 1) & 0x01)) << 2);
+ for (i = 0; i < row_width; i++)
+ {
+ value = (*sp >> shift) & 0x0f;
+ *dp = (png_byte)(value | (value << 4));
+ if (shift == 4)
+ {
+ shift = 0;
+ sp--;
+ }
+ else
+ shift = 4;
+
+ dp--;
+ }
+ break;
+ }
+ }
+
+ row_info->bit_depth = 8;
+ row_info->pixel_depth = 8;
+ row_info->rowbytes = row_width;
+ }
+
+ if (trans_value != NULL)
+ {
+ if (row_info->bit_depth == 8)
+ {
+ gray = gray & 0xff;
+ sp = row + (png_size_t)row_width - 1;
+ dp = row + (png_size_t)(row_width << 1) - 1;
+ for (i = 0; i < row_width; i++)
+ {
+ if (*sp == gray)
+ *dp-- = 0;
+ else
+ *dp-- = 0xff;
+ *dp-- = *sp--;
+ }
+ }
+
+ else if (row_info->bit_depth == 16)
+ {
+ png_byte gray_high = (gray >> 8) & 0xff;
+ png_byte gray_low = gray & 0xff;
+ sp = row + row_info->rowbytes - 1;
+ dp = row + (row_info->rowbytes << 1) - 1;
+ for (i = 0; i < row_width; i++)
+ {
+ if (*(sp - 1) == gray_high && *(sp) == gray_low)
+ {
+ *dp-- = 0;
+ *dp-- = 0;
+ }
+ else
+ {
+ *dp-- = 0xff;
+ *dp-- = 0xff;
+ }
+ *dp-- = *sp--;
+ *dp-- = *sp--;
+ }
+ }
+
+ row_info->color_type = PNG_COLOR_TYPE_GRAY_ALPHA;
+ row_info->channels = 2;
+ row_info->pixel_depth = (png_byte)(row_info->bit_depth << 1);
+ row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth,
+ row_width);
+ }
+ }
+ else if (row_info->color_type == PNG_COLOR_TYPE_RGB && trans_value)
+ {
+ if (row_info->bit_depth == 8)
+ {
+ png_byte red = trans_value->red & 0xff;
+ png_byte green = trans_value->green & 0xff;
+ png_byte blue = trans_value->blue & 0xff;
+ sp = row + (png_size_t)row_info->rowbytes - 1;
+ dp = row + (png_size_t)(row_width << 2) - 1;
+ for (i = 0; i < row_width; i++)
+ {
+ if (*(sp - 2) == red && *(sp - 1) == green && *(sp) == blue)
+ *dp-- = 0;
+ else
+ *dp-- = 0xff;
+ *dp-- = *sp--;
+ *dp-- = *sp--;
+ *dp-- = *sp--;
+ }
+ }
+ else if (row_info->bit_depth == 16)
+ {
+ png_byte red_high = (trans_value->red >> 8) & 0xff;
+ png_byte green_high = (trans_value->green >> 8) & 0xff;
+ png_byte blue_high = (trans_value->blue >> 8) & 0xff;
+ png_byte red_low = trans_value->red & 0xff;
+ png_byte green_low = trans_value->green & 0xff;
+ png_byte blue_low = trans_value->blue & 0xff;
+ sp = row + row_info->rowbytes - 1;
+ dp = row + (png_size_t)(row_width << 3) - 1;
+ for (i = 0; i < row_width; i++)
+ {
+ if (*(sp - 5) == red_high &&
+ *(sp - 4) == red_low &&
+ *(sp - 3) == green_high &&
+ *(sp - 2) == green_low &&
+ *(sp - 1) == blue_high &&
+ *(sp ) == blue_low)
+ {
+ *dp-- = 0;
+ *dp-- = 0;
+ }
+ else
+ {
+ *dp-- = 0xff;
+ *dp-- = 0xff;
+ }
+ *dp-- = *sp--;
+ *dp-- = *sp--;
+ *dp-- = *sp--;
+ *dp-- = *sp--;
+ *dp-- = *sp--;
+ *dp-- = *sp--;
+ }
+ }
+ row_info->color_type = PNG_COLOR_TYPE_RGB_ALPHA;
+ row_info->channels = 4;
+ row_info->pixel_depth = (png_byte)(row_info->bit_depth << 2);
+ row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, row_width);
+ }
+ }
+}
+#endif
+
+#ifdef PNG_READ_DITHER_SUPPORTED
+void /* PRIVATE */
+png_do_dither(png_row_infop row_info, png_bytep row,
+ png_bytep palette_lookup, png_bytep dither_lookup)
+{
+ png_bytep sp, dp;
+ png_uint_32 i;
+ png_uint_32 row_width=row_info->width;
+
+ png_debug(1, "in png_do_dither");
+
+#ifdef PNG_USELESS_TESTS_SUPPORTED
+ if (row != NULL && row_info != NULL)
+#endif
+ {
+ if (row_info->color_type == PNG_COLOR_TYPE_RGB &&
+ palette_lookup && row_info->bit_depth == 8)
+ {
+ int r, g, b, p;
+ sp = row;
+ dp = row;
+ for (i = 0; i < row_width; i++)
+ {
+ r = *sp++;
+ g = *sp++;
+ b = *sp++;
+
+ /* This looks real messy, but the compiler will reduce
+ * it down to a reasonable formula. For example, with
+ * 5 bits per color, we get:
+ * p = (((r >> 3) & 0x1f) << 10) |
+ * (((g >> 3) & 0x1f) << 5) |
+ * ((b >> 3) & 0x1f);
+ */
+ p = (((r >> (8 - PNG_DITHER_RED_BITS)) &
+ ((1 << PNG_DITHER_RED_BITS) - 1)) <<
+ (PNG_DITHER_GREEN_BITS + PNG_DITHER_BLUE_BITS)) |
+ (((g >> (8 - PNG_DITHER_GREEN_BITS)) &
+ ((1 << PNG_DITHER_GREEN_BITS) - 1)) <<
+ (PNG_DITHER_BLUE_BITS)) |
+ ((b >> (8 - PNG_DITHER_BLUE_BITS)) &
+ ((1 << PNG_DITHER_BLUE_BITS) - 1));
+
+ *dp++ = palette_lookup[p];
+ }
+ row_info->color_type = PNG_COLOR_TYPE_PALETTE;
+ row_info->channels = 1;
+ row_info->pixel_depth = row_info->bit_depth;
+ row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, row_width);
+ }
+ else if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA &&
+ palette_lookup != NULL && row_info->bit_depth == 8)
+ {
+ int r, g, b, p;
+ sp = row;
+ dp = row;
+ for (i = 0; i < row_width; i++)
+ {
+ r = *sp++;
+ g = *sp++;
+ b = *sp++;
+ sp++;
+
+ p = (((r >> (8 - PNG_DITHER_RED_BITS)) &
+ ((1 << PNG_DITHER_RED_BITS) - 1)) <<
+ (PNG_DITHER_GREEN_BITS + PNG_DITHER_BLUE_BITS)) |
+ (((g >> (8 - PNG_DITHER_GREEN_BITS)) &
+ ((1 << PNG_DITHER_GREEN_BITS) - 1)) <<
+ (PNG_DITHER_BLUE_BITS)) |
+ ((b >> (8 - PNG_DITHER_BLUE_BITS)) &
+ ((1 << PNG_DITHER_BLUE_BITS) - 1));
+
+ *dp++ = palette_lookup[p];
+ }
+ row_info->color_type = PNG_COLOR_TYPE_PALETTE;
+ row_info->channels = 1;
+ row_info->pixel_depth = row_info->bit_depth;
+ row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, row_width);
+ }
+ else if (row_info->color_type == PNG_COLOR_TYPE_PALETTE &&
+ dither_lookup && row_info->bit_depth == 8)
+ {
+ sp = row;
+ for (i = 0; i < row_width; i++, sp++)
+ {
+ *sp = dither_lookup[*sp];
+ }
+ }
+ }
+}
+#endif
+
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+#ifdef PNG_READ_GAMMA_SUPPORTED
+static PNG_CONST int png_gamma_shift[] =
+ {0x10, 0x21, 0x42, 0x84, 0x110, 0x248, 0x550, 0xff0, 0x00};
+
+/* We build the 8- or 16-bit gamma tables here. Note that for 16-bit
+ * tables, we don't make a full table if we are reducing to 8-bit in
+ * the future. Note also how the gamma_16 tables are segmented so that
+ * we don't need to allocate > 64K chunks for a full 16-bit table.
+ *
+ * See the PNG extensions document for an integer algorithm for creating
+ * the gamma tables. Maybe we will implement that here someday.
+ *
+ * We should only reach this point if
+ *
+ * the file_gamma is known (i.e., the gAMA or sRGB chunk is present,
+ * or the application has provided a file_gamma)
+ *
+ * AND
+ * {
+ * the screen_gamma is known
+ * OR
+ *
+ * RGB_to_gray transformation is being performed
+ * }
+ *
+ * AND
+ * {
+ * the screen_gamma is different from the reciprocal of the
+ * file_gamma by more than the specified threshold
+ *
+ * OR
+ *
+ * a background color has been specified and the file_gamma
+ * and screen_gamma are not 1.0, within the specified threshold.
+ * }
+ */
+
+void /* PRIVATE */
+png_build_gamma_table(png_structp png_ptr)
+{
+ png_debug(1, "in png_build_gamma_table");
+
+ if (png_ptr->bit_depth <= 8)
+ {
+ int i;
+ double g;
+
+ if (png_ptr->screen_gamma > .000001)
+ g = 1.0 / (png_ptr->gamma * png_ptr->screen_gamma);
+
+ else
+ g = 1.0;
+
+ png_ptr->gamma_table = (png_bytep)png_malloc(png_ptr,
+ (png_uint_32)256);
+
+ for (i = 0; i < 256; i++)
+ {
+ png_ptr->gamma_table[i] = (png_byte)(pow((double)i / 255.0,
+ g) * 255.0 + .5);
+ }
+
+#if defined(PNG_READ_BACKGROUND_SUPPORTED) || \
+ defined(PNG_READ_RGB_TO_GRAY_SUPPORTED)
+ if (png_ptr->transformations & ((PNG_BACKGROUND) | PNG_RGB_TO_GRAY))
+ {
+
+ g = 1.0 / (png_ptr->gamma);
+
+ png_ptr->gamma_to_1 = (png_bytep)png_malloc(png_ptr,
+ (png_uint_32)256);
+
+ for (i = 0; i < 256; i++)
+ {
+ png_ptr->gamma_to_1[i] = (png_byte)(pow((double)i / 255.0,
+ g) * 255.0 + .5);
+ }
+
+
+ png_ptr->gamma_from_1 = (png_bytep)png_malloc(png_ptr,
+ (png_uint_32)256);
+
+ if (png_ptr->screen_gamma > 0.000001)
+ g = 1.0 / png_ptr->screen_gamma;
+
+ else
+ g = png_ptr->gamma; /* Probably doing rgb_to_gray */
+
+ for (i = 0; i < 256; i++)
+ {
+ png_ptr->gamma_from_1[i] = (png_byte)(pow((double)i / 255.0,
+ g) * 255.0 + .5);
+
+ }
+ }
+#endif /* PNG_READ_BACKGROUND_SUPPORTED || PNG_RGB_TO_GRAY_SUPPORTED */
+ }
+ else
+ {
+ double g;
+ int i, j, shift, num;
+ int sig_bit;
+ png_uint_32 ig;
+
+ if (png_ptr->color_type & PNG_COLOR_MASK_COLOR)
+ {
+ sig_bit = (int)png_ptr->sig_bit.red;
+
+ if ((int)png_ptr->sig_bit.green > sig_bit)
+ sig_bit = png_ptr->sig_bit.green;
+
+ if ((int)png_ptr->sig_bit.blue > sig_bit)
+ sig_bit = png_ptr->sig_bit.blue;
+ }
+ else
+ {
+ sig_bit = (int)png_ptr->sig_bit.gray;
+ }
+
+ if (sig_bit > 0)
+ shift = 16 - sig_bit;
+
+ else
+ shift = 0;
+
+ if (png_ptr->transformations & PNG_16_TO_8)
+ {
+ if (shift < (16 - PNG_MAX_GAMMA_8))
+ shift = (16 - PNG_MAX_GAMMA_8);
+ }
+
+ if (shift > 8)
+ shift = 8;
+
+ if (shift < 0)
+ shift = 0;
+
+ png_ptr->gamma_shift = (png_byte)shift;
+
+ num = (1 << (8 - shift));
+
+ if (png_ptr->screen_gamma > .000001)
+ g = 1.0 / (png_ptr->gamma * png_ptr->screen_gamma);
+ else
+ g = 1.0;
+
+ png_ptr->gamma_16_table = (png_uint_16pp)png_calloc(png_ptr,
+ (png_uint_32)(num * png_sizeof(png_uint_16p)));
+
+ if (png_ptr->transformations & (PNG_16_TO_8 | PNG_BACKGROUND))
+ {
+ double fin, fout;
+ png_uint_32 last, max;
+
+ for (i = 0; i < num; i++)
+ {
+ png_ptr->gamma_16_table[i] = (png_uint_16p)png_malloc(png_ptr,
+ (png_uint_32)(256 * png_sizeof(png_uint_16)));
+ }
+
+ g = 1.0 / g;
+ last = 0;
+ for (i = 0; i < 256; i++)
+ {
+ fout = ((double)i + 0.5) / 256.0;
+ fin = pow(fout, g);
+ max = (png_uint_32)(fin * (double)((png_uint_32)num << 8));
+ while (last <= max)
+ {
+ png_ptr->gamma_16_table[(int)(last & (0xff >> shift))]
+ [(int)(last >> (8 - shift))] = (png_uint_16)(
+ (png_uint_16)i | ((png_uint_16)i << 8));
+ last++;
+ }
+ }
+ while (last < ((png_uint_32)num << 8))
+ {
+ png_ptr->gamma_16_table[(int)(last & (0xff >> shift))]
+ [(int)(last >> (8 - shift))] = (png_uint_16)65535L;
+ last++;
+ }
+ }
+ else
+ {
+ for (i = 0; i < num; i++)
+ {
+ png_ptr->gamma_16_table[i] = (png_uint_16p)png_malloc(png_ptr,
+ (png_uint_32)(256 * png_sizeof(png_uint_16)));
+
+ ig = (((png_uint_32)i * (png_uint_32)png_gamma_shift[shift]) >> 4);
+
+ for (j = 0; j < 256; j++)
+ {
+ png_ptr->gamma_16_table[i][j] =
+ (png_uint_16)(pow((double)(ig + ((png_uint_32)j << 8)) /
+ 65535.0, g) * 65535.0 + .5);
+ }
+ }
+ }
+
+#if defined(PNG_READ_BACKGROUND_SUPPORTED) || \
+ defined(PNG_READ_RGB_TO_GRAY_SUPPORTED)
+ if (png_ptr->transformations & (PNG_BACKGROUND | PNG_RGB_TO_GRAY))
+ {
+
+ g = 1.0 / (png_ptr->gamma);
+
+ png_ptr->gamma_16_to_1 = (png_uint_16pp)png_calloc(png_ptr,
+ (png_uint_32)(num * png_sizeof(png_uint_16p )));
+
+ for (i = 0; i < num; i++)
+ {
+ png_ptr->gamma_16_to_1[i] = (png_uint_16p)png_malloc(png_ptr,
+ (png_uint_32)(256 * png_sizeof(png_uint_16)));
+
+ ig = (((png_uint_32)i *
+ (png_uint_32)png_gamma_shift[shift]) >> 4);
+ for (j = 0; j < 256; j++)
+ {
+ png_ptr->gamma_16_to_1[i][j] =
+ (png_uint_16)(pow((double)(ig + ((png_uint_32)j << 8)) /
+ 65535.0, g) * 65535.0 + .5);
+ }
+ }
+
+ if (png_ptr->screen_gamma > 0.000001)
+ g = 1.0 / png_ptr->screen_gamma;
+
+ else
+ g = png_ptr->gamma; /* Probably doing rgb_to_gray */
+
+ png_ptr->gamma_16_from_1 = (png_uint_16pp)png_calloc(png_ptr,
+ (png_uint_32)(num * png_sizeof(png_uint_16p)));
+
+ for (i = 0; i < num; i++)
+ {
+ png_ptr->gamma_16_from_1[i] = (png_uint_16p)png_malloc(png_ptr,
+ (png_uint_32)(256 * png_sizeof(png_uint_16)));
+
+ ig = (((png_uint_32)i *
+ (png_uint_32)png_gamma_shift[shift]) >> 4);
+
+ for (j = 0; j < 256; j++)
+ {
+ png_ptr->gamma_16_from_1[i][j] =
+ (png_uint_16)(pow((double)(ig + ((png_uint_32)j << 8)) /
+ 65535.0, g) * 65535.0 + .5);
+ }
+ }
+ }
+#endif /* PNG_READ_BACKGROUND_SUPPORTED || PNG_RGB_TO_GRAY_SUPPORTED */
+ }
+}
+#endif
+/* To do: install integer version of png_build_gamma_table here */
+#endif
+
+#ifdef PNG_MNG_FEATURES_SUPPORTED
+/* Undoes intrapixel differencing */
+void /* PRIVATE */
+png_do_read_intrapixel(png_row_infop row_info, png_bytep row)
+{
+ png_debug(1, "in png_do_read_intrapixel");
+
+ if (
+#ifdef PNG_USELESS_TESTS_SUPPORTED
+ row != NULL && row_info != NULL &&
+#endif
+ (row_info->color_type & PNG_COLOR_MASK_COLOR))
+ {
+ int bytes_per_pixel;
+ png_uint_32 row_width = row_info->width;
+ if (row_info->bit_depth == 8)
+ {
+ png_bytep rp;
+ png_uint_32 i;
+
+ if (row_info->color_type == PNG_COLOR_TYPE_RGB)
+ bytes_per_pixel = 3;
+
+ else if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA)
+ bytes_per_pixel = 4;
+
+ else
+ return;
+
+ for (i = 0, rp = row; i < row_width; i++, rp += bytes_per_pixel)
+ {
+ *(rp) = (png_byte)((256 + *rp + *(rp+1))&0xff);
+ *(rp+2) = (png_byte)((256 + *(rp+2) + *(rp+1))&0xff);
+ }
+ }
+ else if (row_info->bit_depth == 16)
+ {
+ png_bytep rp;
+ png_uint_32 i;
+
+ if (row_info->color_type == PNG_COLOR_TYPE_RGB)
+ bytes_per_pixel = 6;
+
+ else if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA)
+ bytes_per_pixel = 8;
+
+ else
+ return;
+
+ for (i = 0, rp = row; i < row_width; i++, rp += bytes_per_pixel)
+ {
+ png_uint_32 s0 = (*(rp ) << 8) | *(rp + 1);
+ png_uint_32 s1 = (*(rp + 2) << 8) | *(rp + 3);
+ png_uint_32 s2 = (*(rp + 4) << 8) | *(rp + 5);
+ png_uint_32 red = (png_uint_32)((s0 + s1 + 65536L) & 0xffffL);
+ png_uint_32 blue = (png_uint_32)((s2 + s1 + 65536L) & 0xffffL);
+ *(rp ) = (png_byte)((red >> 8) & 0xff);
+ *(rp+1) = (png_byte)(red & 0xff);
+ *(rp+4) = (png_byte)((blue >> 8) & 0xff);
+ *(rp+5) = (png_byte)(blue & 0xff);
+ }
+ }
+ }
+}
+#endif /* PNG_MNG_FEATURES_SUPPORTED */
+#endif /* PNG_READ_SUPPORTED */
diff --git a/trunk/src/third_party/libpng/pngrutil.c b/trunk/src/third_party/libpng/pngrutil.c
new file mode 100644
index 0000000..1e2db31
--- /dev/null
+++ b/trunk/src/third_party/libpng/pngrutil.c
@@ -0,0 +1,3382 @@
+
+/* pngrutil.c - utilities to read a PNG file
+ *
+ * Last changed in libpng 1.2.44 [June 26, 2010]
+ * Copyright (c) 1998-2010 Glenn Randers-Pehrson
+ * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger)
+ * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.)
+ *
+ * This code is released under the libpng license.
+ * For conditions of distribution and use, see the disclaimer
+ * and license in png.h
+ *
+ * This file contains routines that are only called from within
+ * libpng itself during the course of reading an image.
+ */
+
+#define PNG_INTERNAL
+#define PNG_NO_PEDANTIC_WARNINGS
+#include "png.h"
+#ifdef PNG_READ_SUPPORTED
+
+#if defined(_WIN32_WCE) && (_WIN32_WCE<0x500)
+# define WIN32_WCE_OLD
+#endif
+
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+# ifdef WIN32_WCE_OLD
+/* The strtod() function is not supported on WindowsCE */
+__inline double png_strtod(png_structp png_ptr, PNG_CONST char *nptr,
+ char **endptr)
+{
+ double result = 0;
+ int len;
+ wchar_t *str, *end;
+
+ len = MultiByteToWideChar(CP_ACP, 0, nptr, -1, NULL, 0);
+ str = (wchar_t *)png_malloc(png_ptr, len * png_sizeof(wchar_t));
+ if ( NULL != str )
+ {
+ MultiByteToWideChar(CP_ACP, 0, nptr, -1, str, len);
+ result = wcstod(str, &end);
+ len = WideCharToMultiByte(CP_ACP, 0, end, -1, NULL, 0, NULL, NULL);
+ *endptr = (char *)nptr + (png_strlen(nptr) - len + 1);
+ png_free(png_ptr, str);
+ }
+ return result;
+}
+# else
+# define png_strtod(p,a,b) strtod(a,b)
+# endif
+#endif
+
+png_uint_32 PNGAPI
+png_get_uint_31(png_structp png_ptr, png_bytep buf)
+{
+#ifdef PNG_READ_BIG_ENDIAN_SUPPORTED
+ png_uint_32 i = png_get_uint_32(buf);
+#else
+ /* Avoid an extra function call by inlining the result. */
+ png_uint_32 i = ((png_uint_32)(*buf) << 24) +
+ ((png_uint_32)(*(buf + 1)) << 16) +
+ ((png_uint_32)(*(buf + 2)) << 8) +
+ (png_uint_32)(*(buf + 3));
+#endif
+ if (i > PNG_UINT_31_MAX)
+ png_error(png_ptr, "PNG unsigned integer out of range.");
+ return (i);
+}
+#ifndef PNG_READ_BIG_ENDIAN_SUPPORTED
+/* Grab an unsigned 32-bit integer from a buffer in big-endian format. */
+png_uint_32 PNGAPI
+png_get_uint_32(png_bytep buf)
+{
+ png_uint_32 i = ((png_uint_32)(*buf) << 24) +
+ ((png_uint_32)(*(buf + 1)) << 16) +
+ ((png_uint_32)(*(buf + 2)) << 8) +
+ (png_uint_32)(*(buf + 3));
+
+ return (i);
+}
+
+/* Grab a signed 32-bit integer from a buffer in big-endian format. The
+ * data is stored in the PNG file in two's complement format, and it is
+ * assumed that the machine format for signed integers is the same.
+ */
+png_int_32 PNGAPI
+png_get_int_32(png_bytep buf)
+{
+ png_int_32 i = ((png_int_32)(*buf) << 24) +
+ ((png_int_32)(*(buf + 1)) << 16) +
+ ((png_int_32)(*(buf + 2)) << 8) +
+ (png_int_32)(*(buf + 3));
+
+ return (i);
+}
+
+/* Grab an unsigned 16-bit integer from a buffer in big-endian format. */
+png_uint_16 PNGAPI
+png_get_uint_16(png_bytep buf)
+{
+ png_uint_16 i = (png_uint_16)(((png_uint_16)(*buf) << 8) +
+ (png_uint_16)(*(buf + 1)));
+
+ return (i);
+}
+#endif /* PNG_READ_BIG_ENDIAN_SUPPORTED */
+
+/* Read the chunk header (length + type name).
+ * Put the type name into png_ptr->chunk_name, and return the length.
+ */
+png_uint_32 /* PRIVATE */
+png_read_chunk_header(png_structp png_ptr)
+{
+ png_byte buf[8];
+ png_uint_32 length;
+
+ /* Read the length and the chunk name */
+ png_read_data(png_ptr, buf, 8);
+ length = png_get_uint_31(png_ptr, buf);
+
+ /* Put the chunk name into png_ptr->chunk_name */
+ png_memcpy(png_ptr->chunk_name, buf + 4, 4);
+
+ png_debug2(0, "Reading %s chunk, length = %lu",
+ png_ptr->chunk_name, length);
+
+ /* Reset the crc and run it over the chunk name */
+ png_reset_crc(png_ptr);
+ png_calculate_crc(png_ptr, png_ptr->chunk_name, 4);
+
+ /* Check to see if chunk name is valid */
+ png_check_chunk_name(png_ptr, png_ptr->chunk_name);
+
+ return length;
+}
+
+/* Read data, and (optionally) run it through the CRC. */
+void /* PRIVATE */
+png_crc_read(png_structp png_ptr, png_bytep buf, png_size_t length)
+{
+ if (png_ptr == NULL)
+ return;
+ png_read_data(png_ptr, buf, length);
+ png_calculate_crc(png_ptr, buf, length);
+}
+
+/* Optionally skip data and then check the CRC. Depending on whether we
+ * are reading a ancillary or critical chunk, and how the program has set
+ * things up, we may calculate the CRC on the data and print a message.
+ * Returns '1' if there was a CRC error, '0' otherwise.
+ */
+int /* PRIVATE */
+png_crc_finish(png_structp png_ptr, png_uint_32 skip)
+{
+ png_size_t i;
+ png_size_t istop = png_ptr->zbuf_size;
+
+ for (i = (png_size_t)skip; i > istop; i -= istop)
+ {
+ png_crc_read(png_ptr, png_ptr->zbuf, png_ptr->zbuf_size);
+ }
+ if (i)
+ {
+ png_crc_read(png_ptr, png_ptr->zbuf, i);
+ }
+
+ if (png_crc_error(png_ptr))
+ {
+ if (((png_ptr->chunk_name[0] & 0x20) && /* Ancillary */
+ !(png_ptr->flags & PNG_FLAG_CRC_ANCILLARY_NOWARN)) ||
+ (!(png_ptr->chunk_name[0] & 0x20) && /* Critical */
+ (png_ptr->flags & PNG_FLAG_CRC_CRITICAL_USE)))
+ {
+ png_chunk_warning(png_ptr, "CRC error");
+ }
+ else
+ {
+ png_chunk_error(png_ptr, "CRC error");
+ }
+ return (1);
+ }
+
+ return (0);
+}
+
+/* Compare the CRC stored in the PNG file with that calculated by libpng from
+ * the data it has read thus far.
+ */
+int /* PRIVATE */
+png_crc_error(png_structp png_ptr)
+{
+ png_byte crc_bytes[4];
+ png_uint_32 crc;
+ int need_crc = 1;
+
+ if (png_ptr->chunk_name[0] & 0x20) /* ancillary */
+ {
+ if ((png_ptr->flags & PNG_FLAG_CRC_ANCILLARY_MASK) ==
+ (PNG_FLAG_CRC_ANCILLARY_USE | PNG_FLAG_CRC_ANCILLARY_NOWARN))
+ need_crc = 0;
+ }
+ else /* critical */
+ {
+ if (png_ptr->flags & PNG_FLAG_CRC_CRITICAL_IGNORE)
+ need_crc = 0;
+ }
+
+ png_read_data(png_ptr, crc_bytes, 4);
+
+ if (need_crc)
+ {
+ crc = png_get_uint_32(crc_bytes);
+ return ((int)(crc != png_ptr->crc));
+ }
+ else
+ return (0);
+}
+
+#if defined(PNG_READ_zTXt_SUPPORTED) || defined(PNG_READ_iTXt_SUPPORTED) || \
+ defined(PNG_READ_iCCP_SUPPORTED)
+static png_size_t
+png_inflate(png_structp png_ptr, const png_byte *data, png_size_t size,
+ png_bytep output, png_size_t output_size)
+{
+ png_size_t count = 0;
+
+ png_ptr->zstream.next_in = (png_bytep)data; /* const_cast: VALID */
+ png_ptr->zstream.avail_in = size;
+
+ while (1)
+ {
+ int ret, avail;
+
+ /* Reset the output buffer each time round - we empty it
+ * after every inflate call.
+ */
+ png_ptr->zstream.next_out = png_ptr->zbuf;
+ png_ptr->zstream.avail_out = png_ptr->zbuf_size;
+
+ ret = inflate(&png_ptr->zstream, Z_NO_FLUSH);
+ avail = png_ptr->zbuf_size - png_ptr->zstream.avail_out;
+
+ /* First copy/count any new output - but only if we didn't
+ * get an error code.
+ */
+ if ((ret == Z_OK || ret == Z_STREAM_END) && avail > 0)
+ {
+ if (output != 0 && output_size > count)
+ {
+ int copy = output_size - count;
+ if (avail < copy) copy = avail;
+ png_memcpy(output + count, png_ptr->zbuf, copy);
+ }
+ count += avail;
+ }
+
+ if (ret == Z_OK)
+ continue;
+
+ /* Termination conditions - always reset the zstream, it
+ * must be left in inflateInit state.
+ */
+ png_ptr->zstream.avail_in = 0;
+ inflateReset(&png_ptr->zstream);
+
+ if (ret == Z_STREAM_END)
+ return count; /* NOTE: may be zero. */
+
+ /* Now handle the error codes - the API always returns 0
+ * and the error message is dumped into the uncompressed
+ * buffer if available.
+ */
+ {
+ PNG_CONST char *msg;
+ if (png_ptr->zstream.msg != 0)
+ msg = png_ptr->zstream.msg;
+ else
+ {
+#if defined(PNG_STDIO_SUPPORTED) && !defined(_WIN32_WCE)
+ char umsg[52];
+
+ switch (ret)
+ {
+ case Z_BUF_ERROR:
+ msg = "Buffer error in compressed datastream in %s chunk";
+ break;
+ case Z_DATA_ERROR:
+ msg = "Data error in compressed datastream in %s chunk";
+ break;
+ default:
+ msg = "Incomplete compressed datastream in %s chunk";
+ break;
+ }
+
+ png_snprintf(umsg, sizeof umsg, msg, png_ptr->chunk_name);
+ msg = umsg;
+#else
+ msg = "Damaged compressed datastream in chunk other than IDAT";
+#endif
+ }
+
+ png_warning(png_ptr, msg);
+ }
+
+ /* 0 means an error - notice that this code simple ignores
+ * zero length compressed chunks as a result.
+ */
+ return 0;
+ }
+}
+
+/*
+ * Decompress trailing data in a chunk. The assumption is that chunkdata
+ * points at an allocated area holding the contents of a chunk with a
+ * trailing compressed part. What we get back is an allocated area
+ * holding the original prefix part and an uncompressed version of the
+ * trailing part (the malloc area passed in is freed).
+ */
+void /* PRIVATE */
+png_decompress_chunk(png_structp png_ptr, int comp_type,
+ png_size_t chunklength,
+ png_size_t prefix_size, png_size_t *newlength)
+{
+ /* The caller should guarantee this */
+ if (prefix_size > chunklength)
+ {
+ /* The recovery is to delete the chunk. */
+ png_warning(png_ptr, "invalid chunklength");
+ prefix_size = 0; /* To delete everything */
+ }
+
+ else if (comp_type == PNG_COMPRESSION_TYPE_BASE)
+ {
+ png_size_t expanded_size = png_inflate(png_ptr,
+ (png_bytep)(png_ptr->chunkdata + prefix_size),
+ chunklength - prefix_size,
+ 0/*output*/, 0/*output size*/);
+
+ /* Now check the limits on this chunk - if the limit fails the
+ * compressed data will be removed, the prefix will remain.
+ */
+#ifdef PNG_SET_CHUNK_MALLOC_LIMIT_SUPPORTED
+ if (png_ptr->user_chunk_malloc_max &&
+ (prefix_size + expanded_size >= png_ptr->user_chunk_malloc_max - 1))
+#else
+# ifdef PNG_USER_CHUNK_MALLOC_MAX
+ if ((PNG_USER_CHUNK_MALLOC_MAX > 0) &&
+ prefix_size + expanded_size >= PNG_USER_CHUNK_MALLOC_MAX - 1)
+# endif
+#endif
+ png_warning(png_ptr, "Exceeded size limit while expanding chunk");
+
+ /* If the size is zero either there was an error and a message
+ * has already been output (warning) or the size really is zero
+ * and we have nothing to do - the code will exit through the
+ * error case below.
+ */
+#if defined(PNG_SET_CHUNK_MALLOC_LIMIT_SUPPORTED) || \
+ defined(PNG_USER_CHUNK_MALLOC_MAX)
+ else
+#endif
+ if (expanded_size > 0)
+ {
+ /* Success (maybe) - really uncompress the chunk. */
+ png_size_t new_size = 0;
+ png_charp text = png_malloc_warn(png_ptr,
+ prefix_size + expanded_size + 1);
+
+ if (text != NULL)
+ {
+ png_memcpy(text, png_ptr->chunkdata, prefix_size);
+ new_size = png_inflate(png_ptr,
+ (png_bytep)(png_ptr->chunkdata + prefix_size),
+ chunklength - prefix_size,
+ (png_bytep)(text + prefix_size), expanded_size);
+ text[prefix_size + expanded_size] = 0; /* just in case */
+
+ if (new_size == expanded_size)
+ {
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = text;
+ *newlength = prefix_size + expanded_size;
+ return; /* The success return! */
+ }
+
+ png_warning(png_ptr, "png_inflate logic error");
+ png_free(png_ptr, text);
+ }
+ else
+ png_warning(png_ptr, "Not enough memory to decompress chunk.");
+ }
+ }
+
+ else /* if (comp_type != PNG_COMPRESSION_TYPE_BASE) */
+ {
+#if defined(PNG_STDIO_SUPPORTED) && !defined(_WIN32_WCE)
+ char umsg[50];
+
+ png_snprintf(umsg, sizeof umsg, "Unknown zTXt compression type %d",
+ comp_type);
+ png_warning(png_ptr, umsg);
+#else
+ png_warning(png_ptr, "Unknown zTXt compression type");
+#endif
+
+ /* The recovery is to simply drop the data. */
+ }
+
+ /* Generic error return - leave the prefix, delete the compressed
+ * data, reallocate the chunkdata to remove the potentially large
+ * amount of compressed data.
+ */
+ {
+ png_charp text = png_malloc_warn(png_ptr, prefix_size + 1);
+ if (text != NULL)
+ {
+ if (prefix_size > 0)
+ png_memcpy(text, png_ptr->chunkdata, prefix_size);
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = text;
+
+ /* This is an extra zero in the 'uncompressed' part. */
+ *(png_ptr->chunkdata + prefix_size) = 0x00;
+ }
+ /* Ignore a malloc error here - it is safe. */
+ }
+
+ *newlength = prefix_size;
+}
+#endif
+
+/* Read and check the IDHR chunk */
+void /* PRIVATE */
+png_handle_IHDR(png_structp png_ptr, png_infop info_ptr, png_uint_32 length)
+{
+ png_byte buf[13];
+ png_uint_32 width, height;
+ int bit_depth, color_type, compression_type, filter_type;
+ int interlace_type;
+
+ png_debug(1, "in png_handle_IHDR");
+
+ if (png_ptr->mode & PNG_HAVE_IHDR)
+ png_error(png_ptr, "Out of place IHDR");
+
+ /* Check the length */
+ if (length != 13)
+ png_error(png_ptr, "Invalid IHDR chunk");
+
+ png_ptr->mode |= PNG_HAVE_IHDR;
+
+ png_crc_read(png_ptr, buf, 13);
+ png_crc_finish(png_ptr, 0);
+
+ width = png_get_uint_31(png_ptr, buf);
+ height = png_get_uint_31(png_ptr, buf + 4);
+ bit_depth = buf[8];
+ color_type = buf[9];
+ compression_type = buf[10];
+ filter_type = buf[11];
+ interlace_type = buf[12];
+
+ /* Set internal variables */
+ png_ptr->width = width;
+ png_ptr->height = height;
+ png_ptr->bit_depth = (png_byte)bit_depth;
+ png_ptr->interlaced = (png_byte)interlace_type;
+ png_ptr->color_type = (png_byte)color_type;
+#ifdef PNG_MNG_FEATURES_SUPPORTED
+ png_ptr->filter_type = (png_byte)filter_type;
+#endif
+ png_ptr->compression_type = (png_byte)compression_type;
+
+ /* Find number of channels */
+ switch (png_ptr->color_type)
+ {
+ case PNG_COLOR_TYPE_GRAY:
+ case PNG_COLOR_TYPE_PALETTE:
+ png_ptr->channels = 1;
+ break;
+
+ case PNG_COLOR_TYPE_RGB:
+ png_ptr->channels = 3;
+ break;
+
+ case PNG_COLOR_TYPE_GRAY_ALPHA:
+ png_ptr->channels = 2;
+ break;
+
+ case PNG_COLOR_TYPE_RGB_ALPHA:
+ png_ptr->channels = 4;
+ break;
+ }
+
+ /* Set up other useful info */
+ png_ptr->pixel_depth = (png_byte)(png_ptr->bit_depth *
+ png_ptr->channels);
+ png_ptr->rowbytes = PNG_ROWBYTES(png_ptr->pixel_depth, png_ptr->width);
+ png_debug1(3, "bit_depth = %d", png_ptr->bit_depth);
+ png_debug1(3, "channels = %d", png_ptr->channels);
+ png_debug1(3, "rowbytes = %lu", png_ptr->rowbytes);
+ png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth,
+ color_type, interlace_type, compression_type, filter_type);
+}
+
+/* Read and check the palette */
+void /* PRIVATE */
+png_handle_PLTE(png_structp png_ptr, png_infop info_ptr, png_uint_32 length)
+{
+ png_color palette[PNG_MAX_PALETTE_LENGTH];
+ int num, i;
+#ifdef PNG_POINTER_INDEXING_SUPPORTED
+ png_colorp pal_ptr;
+#endif
+
+ png_debug(1, "in png_handle_PLTE");
+
+ if (!(png_ptr->mode & PNG_HAVE_IHDR))
+ png_error(png_ptr, "Missing IHDR before PLTE");
+
+ else if (png_ptr->mode & PNG_HAVE_IDAT)
+ {
+ png_warning(png_ptr, "Invalid PLTE after IDAT");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+ else if (png_ptr->mode & PNG_HAVE_PLTE)
+ png_error(png_ptr, "Duplicate PLTE chunk");
+
+ png_ptr->mode |= PNG_HAVE_PLTE;
+
+ if (!(png_ptr->color_type&PNG_COLOR_MASK_COLOR))
+ {
+ png_warning(png_ptr,
+ "Ignoring PLTE chunk in grayscale PNG");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+#ifndef PNG_READ_OPT_PLTE_SUPPORTED
+ if (png_ptr->color_type != PNG_COLOR_TYPE_PALETTE)
+ {
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+#endif
+
+ if (length > 3*PNG_MAX_PALETTE_LENGTH || length % 3)
+ {
+ if (png_ptr->color_type != PNG_COLOR_TYPE_PALETTE)
+ {
+ png_warning(png_ptr, "Invalid palette chunk");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+ else
+ {
+ png_error(png_ptr, "Invalid palette chunk");
+ }
+ }
+
+ num = (int)length / 3;
+
+#ifdef PNG_POINTER_INDEXING_SUPPORTED
+ for (i = 0, pal_ptr = palette; i < num; i++, pal_ptr++)
+ {
+ png_byte buf[3];
+
+ png_crc_read(png_ptr, buf, 3);
+ pal_ptr->red = buf[0];
+ pal_ptr->green = buf[1];
+ pal_ptr->blue = buf[2];
+ }
+#else
+ for (i = 0; i < num; i++)
+ {
+ png_byte buf[3];
+
+ png_crc_read(png_ptr, buf, 3);
+ /* Don't depend upon png_color being any order */
+ palette[i].red = buf[0];
+ palette[i].green = buf[1];
+ palette[i].blue = buf[2];
+ }
+#endif
+
+ /* If we actually NEED the PLTE chunk (ie for a paletted image), we do
+ * whatever the normal CRC configuration tells us. However, if we
+ * have an RGB image, the PLTE can be considered ancillary, so
+ * we will act as though it is.
+ */
+#ifndef PNG_READ_OPT_PLTE_SUPPORTED
+ if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE)
+#endif
+ {
+ png_crc_finish(png_ptr, 0);
+ }
+#ifndef PNG_READ_OPT_PLTE_SUPPORTED
+ else if (png_crc_error(png_ptr)) /* Only if we have a CRC error */
+ {
+ /* If we don't want to use the data from an ancillary chunk,
+ we have two options: an error abort, or a warning and we
+ ignore the data in this chunk (which should be OK, since
+ it's considered ancillary for a RGB or RGBA image). */
+ if (!(png_ptr->flags & PNG_FLAG_CRC_ANCILLARY_USE))
+ {
+ if (png_ptr->flags & PNG_FLAG_CRC_ANCILLARY_NOWARN)
+ {
+ png_chunk_error(png_ptr, "CRC error");
+ }
+ else
+ {
+ png_chunk_warning(png_ptr, "CRC error");
+ return;
+ }
+ }
+ /* Otherwise, we (optionally) emit a warning and use the chunk. */
+ else if (!(png_ptr->flags & PNG_FLAG_CRC_ANCILLARY_NOWARN))
+ {
+ png_chunk_warning(png_ptr, "CRC error");
+ }
+ }
+#endif
+
+ png_set_PLTE(png_ptr, info_ptr, palette, num);
+
+#ifdef PNG_READ_tRNS_SUPPORTED
+ if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE)
+ {
+ if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_tRNS))
+ {
+ if (png_ptr->num_trans > (png_uint_16)num)
+ {
+ png_warning(png_ptr, "Truncating incorrect tRNS chunk length");
+ png_ptr->num_trans = (png_uint_16)num;
+ }
+ if (info_ptr->num_trans > (png_uint_16)num)
+ {
+ png_warning(png_ptr, "Truncating incorrect info tRNS chunk length");
+ info_ptr->num_trans = (png_uint_16)num;
+ }
+ }
+ }
+#endif
+
+}
+
+void /* PRIVATE */
+png_handle_IEND(png_structp png_ptr, png_infop info_ptr, png_uint_32 length)
+{
+ png_debug(1, "in png_handle_IEND");
+
+ if (!(png_ptr->mode & PNG_HAVE_IHDR) || !(png_ptr->mode & PNG_HAVE_IDAT))
+ {
+ png_error(png_ptr, "No image in file");
+ }
+
+ png_ptr->mode |= (PNG_AFTER_IDAT | PNG_HAVE_IEND);
+
+ if (length != 0)
+ {
+ png_warning(png_ptr, "Incorrect IEND chunk length");
+ }
+ png_crc_finish(png_ptr, length);
+
+ info_ptr = info_ptr; /* Quiet compiler warnings about unused info_ptr */
+}
+
+#ifdef PNG_READ_gAMA_SUPPORTED
+void /* PRIVATE */
+png_handle_gAMA(png_structp png_ptr, png_infop info_ptr, png_uint_32 length)
+{
+ png_fixed_point igamma;
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+ float file_gamma;
+#endif
+ png_byte buf[4];
+
+ png_debug(1, "in png_handle_gAMA");
+
+ if (!(png_ptr->mode & PNG_HAVE_IHDR))
+ png_error(png_ptr, "Missing IHDR before gAMA");
+ else if (png_ptr->mode & PNG_HAVE_IDAT)
+ {
+ png_warning(png_ptr, "Invalid gAMA after IDAT");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ else if (png_ptr->mode & PNG_HAVE_PLTE)
+ /* Should be an error, but we can cope with it */
+ png_warning(png_ptr, "Out of place gAMA chunk");
+
+ if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_gAMA)
+#ifdef PNG_READ_sRGB_SUPPORTED
+ && !(info_ptr->valid & PNG_INFO_sRGB)
+#endif
+ )
+ {
+ png_warning(png_ptr, "Duplicate gAMA chunk");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+ if (length != 4)
+ {
+ png_warning(png_ptr, "Incorrect gAMA chunk length");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+ png_crc_read(png_ptr, buf, 4);
+ if (png_crc_finish(png_ptr, 0))
+ return;
+
+ igamma = (png_fixed_point)png_get_uint_32(buf);
+ /* Check for zero gamma */
+ if (igamma == 0)
+ {
+ png_warning(png_ptr,
+ "Ignoring gAMA chunk with gamma=0");
+ return;
+ }
+
+#ifdef PNG_READ_sRGB_SUPPORTED
+ if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_sRGB))
+ if (PNG_OUT_OF_RANGE(igamma, 45500L, 500))
+ {
+ png_warning(png_ptr,
+ "Ignoring incorrect gAMA value when sRGB is also present");
+#ifdef PNG_CONSOLE_IO_SUPPORTED
+ fprintf(stderr, "gamma = (%d/100000)", (int)igamma);
+#endif
+ return;
+ }
+#endif /* PNG_READ_sRGB_SUPPORTED */
+
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+ file_gamma = (float)igamma / (float)100000.0;
+# ifdef PNG_READ_GAMMA_SUPPORTED
+ png_ptr->gamma = file_gamma;
+# endif
+ png_set_gAMA(png_ptr, info_ptr, file_gamma);
+#endif
+#ifdef PNG_FIXED_POINT_SUPPORTED
+ png_set_gAMA_fixed(png_ptr, info_ptr, igamma);
+#endif
+}
+#endif
+
+#ifdef PNG_READ_sBIT_SUPPORTED
+void /* PRIVATE */
+png_handle_sBIT(png_structp png_ptr, png_infop info_ptr, png_uint_32 length)
+{
+ png_size_t truelen;
+ png_byte buf[4];
+
+ png_debug(1, "in png_handle_sBIT");
+
+ buf[0] = buf[1] = buf[2] = buf[3] = 0;
+
+ if (!(png_ptr->mode & PNG_HAVE_IHDR))
+ png_error(png_ptr, "Missing IHDR before sBIT");
+ else if (png_ptr->mode & PNG_HAVE_IDAT)
+ {
+ png_warning(png_ptr, "Invalid sBIT after IDAT");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ else if (png_ptr->mode & PNG_HAVE_PLTE)
+ {
+ /* Should be an error, but we can cope with it */
+ png_warning(png_ptr, "Out of place sBIT chunk");
+ }
+ if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_sBIT))
+ {
+ png_warning(png_ptr, "Duplicate sBIT chunk");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+ if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE)
+ truelen = 3;
+ else
+ truelen = (png_size_t)png_ptr->channels;
+
+ if (length != truelen || length > 4)
+ {
+ png_warning(png_ptr, "Incorrect sBIT chunk length");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+ png_crc_read(png_ptr, buf, truelen);
+ if (png_crc_finish(png_ptr, 0))
+ return;
+
+ if (png_ptr->color_type & PNG_COLOR_MASK_COLOR)
+ {
+ png_ptr->sig_bit.red = buf[0];
+ png_ptr->sig_bit.green = buf[1];
+ png_ptr->sig_bit.blue = buf[2];
+ png_ptr->sig_bit.alpha = buf[3];
+ }
+ else
+ {
+ png_ptr->sig_bit.gray = buf[0];
+ png_ptr->sig_bit.red = buf[0];
+ png_ptr->sig_bit.green = buf[0];
+ png_ptr->sig_bit.blue = buf[0];
+ png_ptr->sig_bit.alpha = buf[1];
+ }
+ png_set_sBIT(png_ptr, info_ptr, &(png_ptr->sig_bit));
+}
+#endif
+
+#ifdef PNG_READ_cHRM_SUPPORTED
+void /* PRIVATE */
+png_handle_cHRM(png_structp png_ptr, png_infop info_ptr, png_uint_32 length)
+{
+ png_byte buf[32];
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+ float white_x, white_y, red_x, red_y, green_x, green_y, blue_x, blue_y;
+#endif
+ png_fixed_point int_x_white, int_y_white, int_x_red, int_y_red, int_x_green,
+ int_y_green, int_x_blue, int_y_blue;
+
+ png_uint_32 uint_x, uint_y;
+
+ png_debug(1, "in png_handle_cHRM");
+
+ if (!(png_ptr->mode & PNG_HAVE_IHDR))
+ png_error(png_ptr, "Missing IHDR before cHRM");
+ else if (png_ptr->mode & PNG_HAVE_IDAT)
+ {
+ png_warning(png_ptr, "Invalid cHRM after IDAT");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ else if (png_ptr->mode & PNG_HAVE_PLTE)
+ /* Should be an error, but we can cope with it */
+ png_warning(png_ptr, "Missing PLTE before cHRM");
+
+ if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_cHRM)
+#ifdef PNG_READ_sRGB_SUPPORTED
+ && !(info_ptr->valid & PNG_INFO_sRGB)
+#endif
+ )
+ {
+ png_warning(png_ptr, "Duplicate cHRM chunk");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+ if (length != 32)
+ {
+ png_warning(png_ptr, "Incorrect cHRM chunk length");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+ png_crc_read(png_ptr, buf, 32);
+ if (png_crc_finish(png_ptr, 0))
+ return;
+
+ uint_x = png_get_uint_32(buf);
+ uint_y = png_get_uint_32(buf + 4);
+ int_x_white = (png_fixed_point)uint_x;
+ int_y_white = (png_fixed_point)uint_y;
+
+ uint_x = png_get_uint_32(buf + 8);
+ uint_y = png_get_uint_32(buf + 12);
+ int_x_red = (png_fixed_point)uint_x;
+ int_y_red = (png_fixed_point)uint_y;
+
+ uint_x = png_get_uint_32(buf + 16);
+ uint_y = png_get_uint_32(buf + 20);
+ int_x_green = (png_fixed_point)uint_x;
+ int_y_green = (png_fixed_point)uint_y;
+
+ uint_x = png_get_uint_32(buf + 24);
+ uint_y = png_get_uint_32(buf + 28);
+ int_x_blue = (png_fixed_point)uint_x;
+ int_y_blue = (png_fixed_point)uint_y;
+
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+ white_x = (float)int_x_white / (float)100000.0;
+ white_y = (float)int_y_white / (float)100000.0;
+ red_x = (float)int_x_red / (float)100000.0;
+ red_y = (float)int_y_red / (float)100000.0;
+ green_x = (float)int_x_green / (float)100000.0;
+ green_y = (float)int_y_green / (float)100000.0;
+ blue_x = (float)int_x_blue / (float)100000.0;
+ blue_y = (float)int_y_blue / (float)100000.0;
+#endif
+
+#ifdef PNG_READ_sRGB_SUPPORTED
+ if ((info_ptr != NULL) && (info_ptr->valid & PNG_INFO_sRGB))
+ {
+ if (PNG_OUT_OF_RANGE(int_x_white, 31270, 1000) ||
+ PNG_OUT_OF_RANGE(int_y_white, 32900, 1000) ||
+ PNG_OUT_OF_RANGE(int_x_red, 64000L, 1000) ||
+ PNG_OUT_OF_RANGE(int_y_red, 33000, 1000) ||
+ PNG_OUT_OF_RANGE(int_x_green, 30000, 1000) ||
+ PNG_OUT_OF_RANGE(int_y_green, 60000L, 1000) ||
+ PNG_OUT_OF_RANGE(int_x_blue, 15000, 1000) ||
+ PNG_OUT_OF_RANGE(int_y_blue, 6000, 1000))
+ {
+ png_warning(png_ptr,
+ "Ignoring incorrect cHRM value when sRGB is also present");
+#ifdef PNG_CONSOLE_IO_SUPPORTED
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+ fprintf(stderr, "wx=%f, wy=%f, rx=%f, ry=%f\n",
+ white_x, white_y, red_x, red_y);
+ fprintf(stderr, "gx=%f, gy=%f, bx=%f, by=%f\n",
+ green_x, green_y, blue_x, blue_y);
+#else
+ fprintf(stderr, "wx=%ld, wy=%ld, rx=%ld, ry=%ld\n",
+ (long)int_x_white, (long)int_y_white,
+ (long)int_x_red, (long)int_y_red);
+ fprintf(stderr, "gx=%ld, gy=%ld, bx=%ld, by=%ld\n",
+ (long)int_x_green, (long)int_y_green,
+ (long)int_x_blue, (long)int_y_blue);
+#endif
+#endif /* PNG_CONSOLE_IO_SUPPORTED */
+ }
+ return;
+ }
+#endif /* PNG_READ_sRGB_SUPPORTED */
+
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+ png_set_cHRM(png_ptr, info_ptr,
+ white_x, white_y, red_x, red_y, green_x, green_y, blue_x, blue_y);
+#endif
+#ifdef PNG_FIXED_POINT_SUPPORTED
+ png_set_cHRM_fixed(png_ptr, info_ptr,
+ int_x_white, int_y_white, int_x_red, int_y_red, int_x_green,
+ int_y_green, int_x_blue, int_y_blue);
+#endif
+}
+#endif
+
+#ifdef PNG_READ_sRGB_SUPPORTED
+void /* PRIVATE */
+png_handle_sRGB(png_structp png_ptr, png_infop info_ptr, png_uint_32 length)
+{
+ int intent;
+ png_byte buf[1];
+
+ png_debug(1, "in png_handle_sRGB");
+
+ if (!(png_ptr->mode & PNG_HAVE_IHDR))
+ png_error(png_ptr, "Missing IHDR before sRGB");
+ else if (png_ptr->mode & PNG_HAVE_IDAT)
+ {
+ png_warning(png_ptr, "Invalid sRGB after IDAT");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ else if (png_ptr->mode & PNG_HAVE_PLTE)
+ /* Should be an error, but we can cope with it */
+ png_warning(png_ptr, "Out of place sRGB chunk");
+
+ if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_sRGB))
+ {
+ png_warning(png_ptr, "Duplicate sRGB chunk");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+ if (length != 1)
+ {
+ png_warning(png_ptr, "Incorrect sRGB chunk length");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+ png_crc_read(png_ptr, buf, 1);
+ if (png_crc_finish(png_ptr, 0))
+ return;
+
+ intent = buf[0];
+ /* Check for bad intent */
+ if (intent >= PNG_sRGB_INTENT_LAST)
+ {
+ png_warning(png_ptr, "Unknown sRGB intent");
+ return;
+ }
+
+#if defined(PNG_READ_gAMA_SUPPORTED) && defined(PNG_READ_GAMMA_SUPPORTED)
+ if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_gAMA))
+ {
+ png_fixed_point igamma;
+#ifdef PNG_FIXED_POINT_SUPPORTED
+ igamma=info_ptr->int_gamma;
+#else
+# ifdef PNG_FLOATING_POINT_SUPPORTED
+ igamma=(png_fixed_point)(info_ptr->gamma * 100000.);
+# endif
+#endif
+ if (PNG_OUT_OF_RANGE(igamma, 45500L, 500))
+ {
+ png_warning(png_ptr,
+ "Ignoring incorrect gAMA value when sRGB is also present");
+#ifdef PNG_CONSOLE_IO_SUPPORTED
+# ifdef PNG_FIXED_POINT_SUPPORTED
+ fprintf(stderr, "incorrect gamma=(%d/100000)\n",
+ (int)png_ptr->int_gamma);
+# else
+# ifdef PNG_FLOATING_POINT_SUPPORTED
+ fprintf(stderr, "incorrect gamma=%f\n", png_ptr->gamma);
+# endif
+# endif
+#endif
+ }
+ }
+#endif /* PNG_READ_gAMA_SUPPORTED */
+
+#ifdef PNG_READ_cHRM_SUPPORTED
+#ifdef PNG_FIXED_POINT_SUPPORTED
+ if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_cHRM))
+ if (PNG_OUT_OF_RANGE(info_ptr->int_x_white, 31270, 1000) ||
+ PNG_OUT_OF_RANGE(info_ptr->int_y_white, 32900, 1000) ||
+ PNG_OUT_OF_RANGE(info_ptr->int_x_red, 64000L, 1000) ||
+ PNG_OUT_OF_RANGE(info_ptr->int_y_red, 33000, 1000) ||
+ PNG_OUT_OF_RANGE(info_ptr->int_x_green, 30000, 1000) ||
+ PNG_OUT_OF_RANGE(info_ptr->int_y_green, 60000L, 1000) ||
+ PNG_OUT_OF_RANGE(info_ptr->int_x_blue, 15000, 1000) ||
+ PNG_OUT_OF_RANGE(info_ptr->int_y_blue, 6000, 1000))
+ {
+ png_warning(png_ptr,
+ "Ignoring incorrect cHRM value when sRGB is also present");
+ }
+#endif /* PNG_FIXED_POINT_SUPPORTED */
+#endif /* PNG_READ_cHRM_SUPPORTED */
+
+ png_set_sRGB_gAMA_and_cHRM(png_ptr, info_ptr, intent);
+}
+#endif /* PNG_READ_sRGB_SUPPORTED */
+
+#ifdef PNG_READ_iCCP_SUPPORTED
+void /* PRIVATE */
+png_handle_iCCP(png_structp png_ptr, png_infop info_ptr, png_uint_32 length)
+/* Note: this does not properly handle chunks that are > 64K under DOS */
+{
+ png_byte compression_type;
+ png_bytep pC;
+ png_charp profile;
+ png_uint_32 skip = 0;
+ png_uint_32 profile_size, profile_length;
+ png_size_t slength, prefix_length, data_length;
+
+ png_debug(1, "in png_handle_iCCP");
+
+ if (!(png_ptr->mode & PNG_HAVE_IHDR))
+ png_error(png_ptr, "Missing IHDR before iCCP");
+ else if (png_ptr->mode & PNG_HAVE_IDAT)
+ {
+ png_warning(png_ptr, "Invalid iCCP after IDAT");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ else if (png_ptr->mode & PNG_HAVE_PLTE)
+ /* Should be an error, but we can cope with it */
+ png_warning(png_ptr, "Out of place iCCP chunk");
+
+ if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_iCCP))
+ {
+ png_warning(png_ptr, "Duplicate iCCP chunk");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+#ifdef PNG_MAX_MALLOC_64K
+ if (length > (png_uint_32)65535L)
+ {
+ png_warning(png_ptr, "iCCP chunk too large to fit in memory");
+ skip = length - (png_uint_32)65535L;
+ length = (png_uint_32)65535L;
+ }
+#endif
+
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = (png_charp)png_malloc(png_ptr, length + 1);
+ slength = (png_size_t)length;
+ png_crc_read(png_ptr, (png_bytep)png_ptr->chunkdata, slength);
+
+ if (png_crc_finish(png_ptr, skip))
+ {
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ return;
+ }
+
+ png_ptr->chunkdata[slength] = 0x00;
+
+ for (profile = png_ptr->chunkdata; *profile; profile++)
+ /* Empty loop to find end of name */ ;
+
+ ++profile;
+
+ /* There should be at least one zero (the compression type byte)
+ * following the separator, and we should be on it
+ */
+ if ( profile >= png_ptr->chunkdata + slength - 1)
+ {
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ png_warning(png_ptr, "Malformed iCCP chunk");
+ return;
+ }
+
+ /* Compression_type should always be zero */
+ compression_type = *profile++;
+ if (compression_type)
+ {
+ png_warning(png_ptr, "Ignoring nonzero compression type in iCCP chunk");
+ compression_type = 0x00; /* Reset it to zero (libpng-1.0.6 through 1.0.8
+ wrote nonzero) */
+ }
+
+ prefix_length = profile - png_ptr->chunkdata;
+ png_decompress_chunk(png_ptr, compression_type,
+ slength, prefix_length, &data_length);
+
+ profile_length = data_length - prefix_length;
+
+ if ( prefix_length > data_length || profile_length < 4)
+ {
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ png_warning(png_ptr, "Profile size field missing from iCCP chunk");
+ return;
+ }
+
+ /* Check the profile_size recorded in the first 32 bits of the ICC profile */
+ pC = (png_bytep)(png_ptr->chunkdata + prefix_length);
+ profile_size = ((*(pC ))<<24) |
+ ((*(pC + 1))<<16) |
+ ((*(pC + 2))<< 8) |
+ ((*(pC + 3)) );
+
+ if (profile_size < profile_length)
+ profile_length = profile_size;
+
+ if (profile_size > profile_length)
+ {
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ png_warning(png_ptr, "Ignoring truncated iCCP profile.");
+ return;
+ }
+
+ png_set_iCCP(png_ptr, info_ptr, png_ptr->chunkdata,
+ compression_type, png_ptr->chunkdata + prefix_length, profile_length);
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+}
+#endif /* PNG_READ_iCCP_SUPPORTED */
+
+#ifdef PNG_READ_sPLT_SUPPORTED
+void /* PRIVATE */
+png_handle_sPLT(png_structp png_ptr, png_infop info_ptr, png_uint_32 length)
+/* Note: this does not properly handle chunks that are > 64K under DOS */
+{
+ png_bytep entry_start;
+ png_sPLT_t new_palette;
+#ifdef PNG_POINTER_INDEXING_SUPPORTED
+ png_sPLT_entryp pp;
+#endif
+ int data_length, entry_size, i;
+ png_uint_32 skip = 0;
+ png_size_t slength;
+
+ png_debug(1, "in png_handle_sPLT");
+
+#ifdef PNG_USER_LIMITS_SUPPORTED
+
+ if (png_ptr->user_chunk_cache_max != 0)
+ {
+ if (png_ptr->user_chunk_cache_max == 1)
+ {
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ if (--png_ptr->user_chunk_cache_max == 1)
+ {
+ png_warning(png_ptr, "No space in chunk cache for sPLT");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ }
+#endif
+
+ if (!(png_ptr->mode & PNG_HAVE_IHDR))
+ png_error(png_ptr, "Missing IHDR before sPLT");
+ else if (png_ptr->mode & PNG_HAVE_IDAT)
+ {
+ png_warning(png_ptr, "Invalid sPLT after IDAT");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+#ifdef PNG_MAX_MALLOC_64K
+ if (length > (png_uint_32)65535L)
+ {
+ png_warning(png_ptr, "sPLT chunk too large to fit in memory");
+ skip = length - (png_uint_32)65535L;
+ length = (png_uint_32)65535L;
+ }
+#endif
+
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = (png_charp)png_malloc(png_ptr, length + 1);
+ slength = (png_size_t)length;
+ png_crc_read(png_ptr, (png_bytep)png_ptr->chunkdata, slength);
+
+ if (png_crc_finish(png_ptr, skip))
+ {
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ return;
+ }
+
+ png_ptr->chunkdata[slength] = 0x00;
+
+ for (entry_start = (png_bytep)png_ptr->chunkdata; *entry_start;
+ entry_start++)
+ /* Empty loop to find end of name */ ;
+ ++entry_start;
+
+ /* A sample depth should follow the separator, and we should be on it */
+ if (entry_start > (png_bytep)png_ptr->chunkdata + slength - 2)
+ {
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ png_warning(png_ptr, "malformed sPLT chunk");
+ return;
+ }
+
+ new_palette.depth = *entry_start++;
+ entry_size = (new_palette.depth == 8 ? 6 : 10);
+ data_length = (slength - (entry_start - (png_bytep)png_ptr->chunkdata));
+
+ /* Integrity-check the data length */
+ if (data_length % entry_size)
+ {
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ png_warning(png_ptr, "sPLT chunk has bad length");
+ return;
+ }
+
+ new_palette.nentries = (png_int_32) ( data_length / entry_size);
+ if ((png_uint_32) new_palette.nentries >
+ (png_uint_32) (PNG_SIZE_MAX / png_sizeof(png_sPLT_entry)))
+ {
+ png_warning(png_ptr, "sPLT chunk too long");
+ return;
+ }
+ new_palette.entries = (png_sPLT_entryp)png_malloc_warn(
+ png_ptr, new_palette.nentries * png_sizeof(png_sPLT_entry));
+ if (new_palette.entries == NULL)
+ {
+ png_warning(png_ptr, "sPLT chunk requires too much memory");
+ return;
+ }
+
+#ifdef PNG_POINTER_INDEXING_SUPPORTED
+ for (i = 0; i < new_palette.nentries; i++)
+ {
+ pp = new_palette.entries + i;
+
+ if (new_palette.depth == 8)
+ {
+ pp->red = *entry_start++;
+ pp->green = *entry_start++;
+ pp->blue = *entry_start++;
+ pp->alpha = *entry_start++;
+ }
+ else
+ {
+ pp->red = png_get_uint_16(entry_start); entry_start += 2;
+ pp->green = png_get_uint_16(entry_start); entry_start += 2;
+ pp->blue = png_get_uint_16(entry_start); entry_start += 2;
+ pp->alpha = png_get_uint_16(entry_start); entry_start += 2;
+ }
+ pp->frequency = png_get_uint_16(entry_start); entry_start += 2;
+ }
+#else
+ pp = new_palette.entries;
+ for (i = 0; i < new_palette.nentries; i++)
+ {
+
+ if (new_palette.depth == 8)
+ {
+ pp[i].red = *entry_start++;
+ pp[i].green = *entry_start++;
+ pp[i].blue = *entry_start++;
+ pp[i].alpha = *entry_start++;
+ }
+ else
+ {
+ pp[i].red = png_get_uint_16(entry_start); entry_start += 2;
+ pp[i].green = png_get_uint_16(entry_start); entry_start += 2;
+ pp[i].blue = png_get_uint_16(entry_start); entry_start += 2;
+ pp[i].alpha = png_get_uint_16(entry_start); entry_start += 2;
+ }
+ pp->frequency = png_get_uint_16(entry_start); entry_start += 2;
+ }
+#endif
+
+ /* Discard all chunk data except the name and stash that */
+ new_palette.name = png_ptr->chunkdata;
+
+ png_set_sPLT(png_ptr, info_ptr, &new_palette, 1);
+
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ png_free(png_ptr, new_palette.entries);
+}
+#endif /* PNG_READ_sPLT_SUPPORTED */
+
+#ifdef PNG_READ_tRNS_SUPPORTED
+void /* PRIVATE */
+png_handle_tRNS(png_structp png_ptr, png_infop info_ptr, png_uint_32 length)
+{
+ png_byte readbuf[PNG_MAX_PALETTE_LENGTH];
+
+ png_debug(1, "in png_handle_tRNS");
+
+ if (!(png_ptr->mode & PNG_HAVE_IHDR))
+ png_error(png_ptr, "Missing IHDR before tRNS");
+ else if (png_ptr->mode & PNG_HAVE_IDAT)
+ {
+ png_warning(png_ptr, "Invalid tRNS after IDAT");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_tRNS))
+ {
+ png_warning(png_ptr, "Duplicate tRNS chunk");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+ if (png_ptr->color_type == PNG_COLOR_TYPE_GRAY)
+ {
+ png_byte buf[2];
+
+ if (length != 2)
+ {
+ png_warning(png_ptr, "Incorrect tRNS chunk length");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+ png_crc_read(png_ptr, buf, 2);
+ png_ptr->num_trans = 1;
+ png_ptr->trans_values.gray = png_get_uint_16(buf);
+ }
+ else if (png_ptr->color_type == PNG_COLOR_TYPE_RGB)
+ {
+ png_byte buf[6];
+
+ if (length != 6)
+ {
+ png_warning(png_ptr, "Incorrect tRNS chunk length");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ png_crc_read(png_ptr, buf, (png_size_t)length);
+ png_ptr->num_trans = 1;
+ png_ptr->trans_values.red = png_get_uint_16(buf);
+ png_ptr->trans_values.green = png_get_uint_16(buf + 2);
+ png_ptr->trans_values.blue = png_get_uint_16(buf + 4);
+ }
+ else if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE)
+ {
+ if (!(png_ptr->mode & PNG_HAVE_PLTE))
+ {
+ /* Should be an error, but we can cope with it. */
+ png_warning(png_ptr, "Missing PLTE before tRNS");
+ }
+ if (length > (png_uint_32)png_ptr->num_palette ||
+ length > PNG_MAX_PALETTE_LENGTH)
+ {
+ png_warning(png_ptr, "Incorrect tRNS chunk length");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ if (length == 0)
+ {
+ png_warning(png_ptr, "Zero length tRNS chunk");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ png_crc_read(png_ptr, readbuf, (png_size_t)length);
+ png_ptr->num_trans = (png_uint_16)length;
+ }
+ else
+ {
+ png_warning(png_ptr, "tRNS chunk not allowed with alpha channel");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+ if (png_crc_finish(png_ptr, 0))
+ {
+ png_ptr->num_trans = 0;
+ return;
+ }
+
+ png_set_tRNS(png_ptr, info_ptr, readbuf, png_ptr->num_trans,
+ &(png_ptr->trans_values));
+}
+#endif
+
+#ifdef PNG_READ_bKGD_SUPPORTED
+void /* PRIVATE */
+png_handle_bKGD(png_structp png_ptr, png_infop info_ptr, png_uint_32 length)
+{
+ png_size_t truelen;
+ png_byte buf[6];
+
+ png_debug(1, "in png_handle_bKGD");
+
+ if (!(png_ptr->mode & PNG_HAVE_IHDR))
+ png_error(png_ptr, "Missing IHDR before bKGD");
+ else if (png_ptr->mode & PNG_HAVE_IDAT)
+ {
+ png_warning(png_ptr, "Invalid bKGD after IDAT");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ else if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE &&
+ !(png_ptr->mode & PNG_HAVE_PLTE))
+ {
+ png_warning(png_ptr, "Missing PLTE before bKGD");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_bKGD))
+ {
+ png_warning(png_ptr, "Duplicate bKGD chunk");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+ if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE)
+ truelen = 1;
+ else if (png_ptr->color_type & PNG_COLOR_MASK_COLOR)
+ truelen = 6;
+ else
+ truelen = 2;
+
+ if (length != truelen)
+ {
+ png_warning(png_ptr, "Incorrect bKGD chunk length");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+ png_crc_read(png_ptr, buf, truelen);
+ if (png_crc_finish(png_ptr, 0))
+ return;
+
+ /* We convert the index value into RGB components so that we can allow
+ * arbitrary RGB values for background when we have transparency, and
+ * so it is easy to determine the RGB values of the background color
+ * from the info_ptr struct. */
+ if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE)
+ {
+ png_ptr->background.index = buf[0];
+ if (info_ptr && info_ptr->num_palette)
+ {
+ if (buf[0] >= info_ptr->num_palette)
+ {
+ png_warning(png_ptr, "Incorrect bKGD chunk index value");
+ return;
+ }
+ png_ptr->background.red =
+ (png_uint_16)png_ptr->palette[buf[0]].red;
+ png_ptr->background.green =
+ (png_uint_16)png_ptr->palette[buf[0]].green;
+ png_ptr->background.blue =
+ (png_uint_16)png_ptr->palette[buf[0]].blue;
+ }
+ }
+ else if (!(png_ptr->color_type & PNG_COLOR_MASK_COLOR)) /* GRAY */
+ {
+ png_ptr->background.red =
+ png_ptr->background.green =
+ png_ptr->background.blue =
+ png_ptr->background.gray = png_get_uint_16(buf);
+ }
+ else
+ {
+ png_ptr->background.red = png_get_uint_16(buf);
+ png_ptr->background.green = png_get_uint_16(buf + 2);
+ png_ptr->background.blue = png_get_uint_16(buf + 4);
+ }
+
+ png_set_bKGD(png_ptr, info_ptr, &(png_ptr->background));
+}
+#endif
+
+#ifdef PNG_READ_hIST_SUPPORTED
+void /* PRIVATE */
+png_handle_hIST(png_structp png_ptr, png_infop info_ptr, png_uint_32 length)
+{
+ unsigned int num, i;
+ png_uint_16 readbuf[PNG_MAX_PALETTE_LENGTH];
+
+ png_debug(1, "in png_handle_hIST");
+
+ if (!(png_ptr->mode & PNG_HAVE_IHDR))
+ png_error(png_ptr, "Missing IHDR before hIST");
+ else if (png_ptr->mode & PNG_HAVE_IDAT)
+ {
+ png_warning(png_ptr, "Invalid hIST after IDAT");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ else if (!(png_ptr->mode & PNG_HAVE_PLTE))
+ {
+ png_warning(png_ptr, "Missing PLTE before hIST");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_hIST))
+ {
+ png_warning(png_ptr, "Duplicate hIST chunk");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+ num = length / 2 ;
+ if (num != (unsigned int) png_ptr->num_palette || num >
+ (unsigned int) PNG_MAX_PALETTE_LENGTH)
+ {
+ png_warning(png_ptr, "Incorrect hIST chunk length");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+ for (i = 0; i < num; i++)
+ {
+ png_byte buf[2];
+
+ png_crc_read(png_ptr, buf, 2);
+ readbuf[i] = png_get_uint_16(buf);
+ }
+
+ if (png_crc_finish(png_ptr, 0))
+ return;
+
+ png_set_hIST(png_ptr, info_ptr, readbuf);
+}
+#endif
+
+#ifdef PNG_READ_pHYs_SUPPORTED
+void /* PRIVATE */
+png_handle_pHYs(png_structp png_ptr, png_infop info_ptr, png_uint_32 length)
+{
+ png_byte buf[9];
+ png_uint_32 res_x, res_y;
+ int unit_type;
+
+ png_debug(1, "in png_handle_pHYs");
+
+ if (!(png_ptr->mode & PNG_HAVE_IHDR))
+ png_error(png_ptr, "Missing IHDR before pHYs");
+ else if (png_ptr->mode & PNG_HAVE_IDAT)
+ {
+ png_warning(png_ptr, "Invalid pHYs after IDAT");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_pHYs))
+ {
+ png_warning(png_ptr, "Duplicate pHYs chunk");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+ if (length != 9)
+ {
+ png_warning(png_ptr, "Incorrect pHYs chunk length");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+ png_crc_read(png_ptr, buf, 9);
+ if (png_crc_finish(png_ptr, 0))
+ return;
+
+ res_x = png_get_uint_32(buf);
+ res_y = png_get_uint_32(buf + 4);
+ unit_type = buf[8];
+ png_set_pHYs(png_ptr, info_ptr, res_x, res_y, unit_type);
+}
+#endif
+
+#ifdef PNG_READ_oFFs_SUPPORTED
+void /* PRIVATE */
+png_handle_oFFs(png_structp png_ptr, png_infop info_ptr, png_uint_32 length)
+{
+ png_byte buf[9];
+ png_int_32 offset_x, offset_y;
+ int unit_type;
+
+ png_debug(1, "in png_handle_oFFs");
+
+ if (!(png_ptr->mode & PNG_HAVE_IHDR))
+ png_error(png_ptr, "Missing IHDR before oFFs");
+ else if (png_ptr->mode & PNG_HAVE_IDAT)
+ {
+ png_warning(png_ptr, "Invalid oFFs after IDAT");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_oFFs))
+ {
+ png_warning(png_ptr, "Duplicate oFFs chunk");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+ if (length != 9)
+ {
+ png_warning(png_ptr, "Incorrect oFFs chunk length");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+ png_crc_read(png_ptr, buf, 9);
+ if (png_crc_finish(png_ptr, 0))
+ return;
+
+ offset_x = png_get_int_32(buf);
+ offset_y = png_get_int_32(buf + 4);
+ unit_type = buf[8];
+ png_set_oFFs(png_ptr, info_ptr, offset_x, offset_y, unit_type);
+}
+#endif
+
+#ifdef PNG_READ_pCAL_SUPPORTED
+/* Read the pCAL chunk (described in the PNG Extensions document) */
+void /* PRIVATE */
+png_handle_pCAL(png_structp png_ptr, png_infop info_ptr, png_uint_32 length)
+{
+ png_int_32 X0, X1;
+ png_byte type, nparams;
+ png_charp buf, units, endptr;
+ png_charpp params;
+ png_size_t slength;
+ int i;
+
+ png_debug(1, "in png_handle_pCAL");
+
+ if (!(png_ptr->mode & PNG_HAVE_IHDR))
+ png_error(png_ptr, "Missing IHDR before pCAL");
+ else if (png_ptr->mode & PNG_HAVE_IDAT)
+ {
+ png_warning(png_ptr, "Invalid pCAL after IDAT");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_pCAL))
+ {
+ png_warning(png_ptr, "Duplicate pCAL chunk");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+ png_debug1(2, "Allocating and reading pCAL chunk data (%lu bytes)",
+ length + 1);
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = (png_charp)png_malloc_warn(png_ptr, length + 1);
+ if (png_ptr->chunkdata == NULL)
+ {
+ png_warning(png_ptr, "No memory for pCAL purpose.");
+ return;
+ }
+ slength = (png_size_t)length;
+ png_crc_read(png_ptr, (png_bytep)png_ptr->chunkdata, slength);
+
+ if (png_crc_finish(png_ptr, 0))
+ {
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ return;
+ }
+
+ png_ptr->chunkdata[slength] = 0x00; /* Null terminate the last string */
+
+ png_debug(3, "Finding end of pCAL purpose string");
+ for (buf = png_ptr->chunkdata; *buf; buf++)
+ /* Empty loop */ ;
+
+ endptr = png_ptr->chunkdata + slength;
+
+ /* We need to have at least 12 bytes after the purpose string
+ in order to get the parameter information. */
+ if (endptr <= buf + 12)
+ {
+ png_warning(png_ptr, "Invalid pCAL data");
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ return;
+ }
+
+ png_debug(3, "Reading pCAL X0, X1, type, nparams, and units");
+ X0 = png_get_int_32((png_bytep)buf+1);
+ X1 = png_get_int_32((png_bytep)buf+5);
+ type = buf[9];
+ nparams = buf[10];
+ units = buf + 11;
+
+ png_debug(3, "Checking pCAL equation type and number of parameters");
+ /* Check that we have the right number of parameters for known
+ equation types. */
+ if ((type == PNG_EQUATION_LINEAR && nparams != 2) ||
+ (type == PNG_EQUATION_BASE_E && nparams != 3) ||
+ (type == PNG_EQUATION_ARBITRARY && nparams != 3) ||
+ (type == PNG_EQUATION_HYPERBOLIC && nparams != 4))
+ {
+ png_warning(png_ptr, "Invalid pCAL parameters for equation type");
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ return;
+ }
+ else if (type >= PNG_EQUATION_LAST)
+ {
+ png_warning(png_ptr, "Unrecognized equation type for pCAL chunk");
+ }
+
+ for (buf = units; *buf; buf++)
+ /* Empty loop to move past the units string. */ ;
+
+ png_debug(3, "Allocating pCAL parameters array");
+ params = (png_charpp)png_malloc_warn(png_ptr,
+ (png_uint_32)(nparams * png_sizeof(png_charp))) ;
+ if (params == NULL)
+ {
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ png_warning(png_ptr, "No memory for pCAL params.");
+ return;
+ }
+
+ /* Get pointers to the start of each parameter string. */
+ for (i = 0; i < (int)nparams; i++)
+ {
+ buf++; /* Skip the null string terminator from previous parameter. */
+
+ png_debug1(3, "Reading pCAL parameter %d", i);
+ for (params[i] = buf; buf <= endptr && *buf != 0x00; buf++)
+ /* Empty loop to move past each parameter string */ ;
+
+ /* Make sure we haven't run out of data yet */
+ if (buf > endptr)
+ {
+ png_warning(png_ptr, "Invalid pCAL data");
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ png_free(png_ptr, params);
+ return;
+ }
+ }
+
+ png_set_pCAL(png_ptr, info_ptr, png_ptr->chunkdata, X0, X1, type, nparams,
+ units, params);
+
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ png_free(png_ptr, params);
+}
+#endif
+
+#ifdef PNG_READ_sCAL_SUPPORTED
+/* Read the sCAL chunk */
+void /* PRIVATE */
+png_handle_sCAL(png_structp png_ptr, png_infop info_ptr, png_uint_32 length)
+{
+ png_charp ep;
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+ double width, height;
+ png_charp vp;
+#else
+#ifdef PNG_FIXED_POINT_SUPPORTED
+ png_charp swidth, sheight;
+#endif
+#endif
+ png_size_t slength;
+
+ png_debug(1, "in png_handle_sCAL");
+
+ if (!(png_ptr->mode & PNG_HAVE_IHDR))
+ png_error(png_ptr, "Missing IHDR before sCAL");
+ else if (png_ptr->mode & PNG_HAVE_IDAT)
+ {
+ png_warning(png_ptr, "Invalid sCAL after IDAT");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_sCAL))
+ {
+ png_warning(png_ptr, "Duplicate sCAL chunk");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+ png_debug1(2, "Allocating and reading sCAL chunk data (%lu bytes)",
+ length + 1);
+ png_ptr->chunkdata = (png_charp)png_malloc_warn(png_ptr, length + 1);
+ if (png_ptr->chunkdata == NULL)
+ {
+ png_warning(png_ptr, "Out of memory while processing sCAL chunk");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ slength = (png_size_t)length;
+ png_crc_read(png_ptr, (png_bytep)png_ptr->chunkdata, slength);
+
+ if (png_crc_finish(png_ptr, 0))
+ {
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ return;
+ }
+
+ png_ptr->chunkdata[slength] = 0x00; /* Null terminate the last string */
+
+ ep = png_ptr->chunkdata + 1; /* Skip unit byte */
+
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+ width = png_strtod(png_ptr, ep, &vp);
+ if (*vp)
+ {
+ png_warning(png_ptr, "malformed width string in sCAL chunk");
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ return;
+ }
+#else
+#ifdef PNG_FIXED_POINT_SUPPORTED
+ swidth = (png_charp)png_malloc_warn(png_ptr, png_strlen(ep) + 1);
+ if (swidth == NULL)
+ {
+ png_warning(png_ptr, "Out of memory while processing sCAL chunk width");
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ return;
+ }
+ png_memcpy(swidth, ep, (png_size_t)png_strlen(ep));
+#endif
+#endif
+
+ for (ep = png_ptr->chunkdata; *ep; ep++)
+ /* Empty loop */ ;
+ ep++;
+
+ if (png_ptr->chunkdata + slength < ep)
+ {
+ png_warning(png_ptr, "Truncated sCAL chunk");
+#if defined(PNG_FIXED_POINT_SUPPORTED) && !defined(PNG_FLOATING_POINT_SUPPORTED)
+ png_free(png_ptr, swidth);
+#endif
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ return;
+ }
+
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+ height = png_strtod(png_ptr, ep, &vp);
+ if (*vp)
+ {
+ png_warning(png_ptr, "malformed height string in sCAL chunk");
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+#if defined(PNG_FIXED_POINT_SUPPORTED) && !defined(PNG_FLOATING_POINT_SUPPORTED)
+ png_free(png_ptr, swidth);
+#endif
+ return;
+ }
+#else
+#ifdef PNG_FIXED_POINT_SUPPORTED
+ sheight = (png_charp)png_malloc_warn(png_ptr, png_strlen(ep) + 1);
+ if (sheight == NULL)
+ {
+ png_warning(png_ptr, "Out of memory while processing sCAL chunk height");
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+#if defined(PNG_FIXED_POINT_SUPPORTED) && !defined(PNG_FLOATING_POINT_SUPPORTED)
+ png_free(png_ptr, swidth);
+#endif
+ return;
+ }
+ png_memcpy(sheight, ep, (png_size_t)png_strlen(ep));
+#endif
+#endif
+
+ if (png_ptr->chunkdata + slength < ep
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+ || width <= 0. || height <= 0.
+#endif
+ )
+ {
+ png_warning(png_ptr, "Invalid sCAL data");
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+#if defined(PNG_FIXED_POINT_SUPPORTED) && !defined(PNG_FLOATING_POINT_SUPPORTED)
+ png_free(png_ptr, swidth);
+ png_free(png_ptr, sheight);
+#endif
+ return;
+ }
+
+
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+ png_set_sCAL(png_ptr, info_ptr, png_ptr->chunkdata[0], width, height);
+#else
+#ifdef PNG_FIXED_POINT_SUPPORTED
+ png_set_sCAL_s(png_ptr, info_ptr, png_ptr->chunkdata[0], swidth, sheight);
+#endif
+#endif
+
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+#if defined(PNG_FIXED_POINT_SUPPORTED) && !defined(PNG_FLOATING_POINT_SUPPORTED)
+ png_free(png_ptr, swidth);
+ png_free(png_ptr, sheight);
+#endif
+}
+#endif
+
+#ifdef PNG_READ_tIME_SUPPORTED
+void /* PRIVATE */
+png_handle_tIME(png_structp png_ptr, png_infop info_ptr, png_uint_32 length)
+{
+ png_byte buf[7];
+ png_time mod_time;
+
+ png_debug(1, "in png_handle_tIME");
+
+ if (!(png_ptr->mode & PNG_HAVE_IHDR))
+ png_error(png_ptr, "Out of place tIME chunk");
+ else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_tIME))
+ {
+ png_warning(png_ptr, "Duplicate tIME chunk");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+ if (png_ptr->mode & PNG_HAVE_IDAT)
+ png_ptr->mode |= PNG_AFTER_IDAT;
+
+ if (length != 7)
+ {
+ png_warning(png_ptr, "Incorrect tIME chunk length");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+
+ png_crc_read(png_ptr, buf, 7);
+ if (png_crc_finish(png_ptr, 0))
+ return;
+
+ mod_time.second = buf[6];
+ mod_time.minute = buf[5];
+ mod_time.hour = buf[4];
+ mod_time.day = buf[3];
+ mod_time.month = buf[2];
+ mod_time.year = png_get_uint_16(buf);
+
+ png_set_tIME(png_ptr, info_ptr, &mod_time);
+}
+#endif
+
+#ifdef PNG_READ_tEXt_SUPPORTED
+/* Note: this does not properly handle chunks that are > 64K under DOS */
+void /* PRIVATE */
+png_handle_tEXt(png_structp png_ptr, png_infop info_ptr, png_uint_32 length)
+{
+ png_textp text_ptr;
+ png_charp key;
+ png_charp text;
+ png_uint_32 skip = 0;
+ png_size_t slength;
+ int ret;
+
+ png_debug(1, "in png_handle_tEXt");
+
+#ifdef PNG_USER_LIMITS_SUPPORTED
+ if (png_ptr->user_chunk_cache_max != 0)
+ {
+ if (png_ptr->user_chunk_cache_max == 1)
+ {
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ if (--png_ptr->user_chunk_cache_max == 1)
+ {
+ png_warning(png_ptr, "No space in chunk cache for tEXt");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ }
+#endif
+
+ if (!(png_ptr->mode & PNG_HAVE_IHDR))
+ png_error(png_ptr, "Missing IHDR before tEXt");
+
+ if (png_ptr->mode & PNG_HAVE_IDAT)
+ png_ptr->mode |= PNG_AFTER_IDAT;
+
+#ifdef PNG_MAX_MALLOC_64K
+ if (length > (png_uint_32)65535L)
+ {
+ png_warning(png_ptr, "tEXt chunk too large to fit in memory");
+ skip = length - (png_uint_32)65535L;
+ length = (png_uint_32)65535L;
+ }
+#endif
+
+ png_free(png_ptr, png_ptr->chunkdata);
+
+ png_ptr->chunkdata = (png_charp)png_malloc_warn(png_ptr, length + 1);
+ if (png_ptr->chunkdata == NULL)
+ {
+ png_warning(png_ptr, "No memory to process text chunk.");
+ return;
+ }
+ slength = (png_size_t)length;
+ png_crc_read(png_ptr, (png_bytep)png_ptr->chunkdata, slength);
+
+ if (png_crc_finish(png_ptr, skip))
+ {
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ return;
+ }
+
+ key = png_ptr->chunkdata;
+
+ key[slength] = 0x00;
+
+ for (text = key; *text; text++)
+ /* Empty loop to find end of key */ ;
+
+ if (text != key + slength)
+ text++;
+
+ text_ptr = (png_textp)png_malloc_warn(png_ptr,
+ (png_uint_32)png_sizeof(png_text));
+ if (text_ptr == NULL)
+ {
+ png_warning(png_ptr, "Not enough memory to process text chunk.");
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ return;
+ }
+ text_ptr->compression = PNG_TEXT_COMPRESSION_NONE;
+ text_ptr->key = key;
+#ifdef PNG_iTXt_SUPPORTED
+ text_ptr->lang = NULL;
+ text_ptr->lang_key = NULL;
+ text_ptr->itxt_length = 0;
+#endif
+ text_ptr->text = text;
+ text_ptr->text_length = png_strlen(text);
+
+ ret = png_set_text_2(png_ptr, info_ptr, text_ptr, 1);
+
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ png_free(png_ptr, text_ptr);
+ if (ret)
+ png_warning(png_ptr, "Insufficient memory to process text chunk.");
+}
+#endif
+
+#ifdef PNG_READ_zTXt_SUPPORTED
+/* Note: this does not correctly handle chunks that are > 64K under DOS */
+void /* PRIVATE */
+png_handle_zTXt(png_structp png_ptr, png_infop info_ptr, png_uint_32 length)
+{
+ png_textp text_ptr;
+ png_charp text;
+ int comp_type;
+ int ret;
+ png_size_t slength, prefix_len, data_len;
+
+ png_debug(1, "in png_handle_zTXt");
+
+#ifdef PNG_USER_LIMITS_SUPPORTED
+ if (png_ptr->user_chunk_cache_max != 0)
+ {
+ if (png_ptr->user_chunk_cache_max == 1)
+ {
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ if (--png_ptr->user_chunk_cache_max == 1)
+ {
+ png_warning(png_ptr, "No space in chunk cache for zTXt");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ }
+#endif
+
+ if (!(png_ptr->mode & PNG_HAVE_IHDR))
+ png_error(png_ptr, "Missing IHDR before zTXt");
+
+ if (png_ptr->mode & PNG_HAVE_IDAT)
+ png_ptr->mode |= PNG_AFTER_IDAT;
+
+#ifdef PNG_MAX_MALLOC_64K
+ /* We will no doubt have problems with chunks even half this size, but
+ there is no hard and fast rule to tell us where to stop. */
+ if (length > (png_uint_32)65535L)
+ {
+ png_warning(png_ptr, "zTXt chunk too large to fit in memory");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+#endif
+
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = (png_charp)png_malloc_warn(png_ptr, length + 1);
+ if (png_ptr->chunkdata == NULL)
+ {
+ png_warning(png_ptr, "Out of memory processing zTXt chunk.");
+ return;
+ }
+ slength = (png_size_t)length;
+ png_crc_read(png_ptr, (png_bytep)png_ptr->chunkdata, slength);
+ if (png_crc_finish(png_ptr, 0))
+ {
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ return;
+ }
+
+ png_ptr->chunkdata[slength] = 0x00;
+
+ for (text = png_ptr->chunkdata; *text; text++)
+ /* Empty loop */ ;
+
+ /* zTXt must have some text after the chunkdataword */
+ if (text >= png_ptr->chunkdata + slength - 2)
+ {
+ png_warning(png_ptr, "Truncated zTXt chunk");
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ return;
+ }
+ else
+ {
+ comp_type = *(++text);
+ if (comp_type != PNG_TEXT_COMPRESSION_zTXt)
+ {
+ png_warning(png_ptr, "Unknown compression type in zTXt chunk");
+ comp_type = PNG_TEXT_COMPRESSION_zTXt;
+ }
+ text++; /* Skip the compression_method byte */
+ }
+ prefix_len = text - png_ptr->chunkdata;
+
+ png_decompress_chunk(png_ptr, comp_type,
+ (png_size_t)length, prefix_len, &data_len);
+
+ text_ptr = (png_textp)png_malloc_warn(png_ptr,
+ (png_uint_32)png_sizeof(png_text));
+ if (text_ptr == NULL)
+ {
+ png_warning(png_ptr, "Not enough memory to process zTXt chunk.");
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ return;
+ }
+ text_ptr->compression = comp_type;
+ text_ptr->key = png_ptr->chunkdata;
+#ifdef PNG_iTXt_SUPPORTED
+ text_ptr->lang = NULL;
+ text_ptr->lang_key = NULL;
+ text_ptr->itxt_length = 0;
+#endif
+ text_ptr->text = png_ptr->chunkdata + prefix_len;
+ text_ptr->text_length = data_len;
+
+ ret = png_set_text_2(png_ptr, info_ptr, text_ptr, 1);
+
+ png_free(png_ptr, text_ptr);
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ if (ret)
+ png_error(png_ptr, "Insufficient memory to store zTXt chunk.");
+}
+#endif
+
+#ifdef PNG_READ_iTXt_SUPPORTED
+/* Note: this does not correctly handle chunks that are > 64K under DOS */
+void /* PRIVATE */
+png_handle_iTXt(png_structp png_ptr, png_infop info_ptr, png_uint_32 length)
+{
+ png_textp text_ptr;
+ png_charp key, lang, text, lang_key;
+ int comp_flag;
+ int comp_type = 0;
+ int ret;
+ png_size_t slength, prefix_len, data_len;
+
+ png_debug(1, "in png_handle_iTXt");
+
+#ifdef PNG_USER_LIMITS_SUPPORTED
+ if (png_ptr->user_chunk_cache_max != 0)
+ {
+ if (png_ptr->user_chunk_cache_max == 1)
+ {
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ if (--png_ptr->user_chunk_cache_max == 1)
+ {
+ png_warning(png_ptr, "No space in chunk cache for iTXt");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ }
+#endif
+
+ if (!(png_ptr->mode & PNG_HAVE_IHDR))
+ png_error(png_ptr, "Missing IHDR before iTXt");
+
+ if (png_ptr->mode & PNG_HAVE_IDAT)
+ png_ptr->mode |= PNG_AFTER_IDAT;
+
+#ifdef PNG_MAX_MALLOC_64K
+ /* We will no doubt have problems with chunks even half this size, but
+ there is no hard and fast rule to tell us where to stop. */
+ if (length > (png_uint_32)65535L)
+ {
+ png_warning(png_ptr, "iTXt chunk too large to fit in memory");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+#endif
+
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = (png_charp)png_malloc_warn(png_ptr, length + 1);
+ if (png_ptr->chunkdata == NULL)
+ {
+ png_warning(png_ptr, "No memory to process iTXt chunk.");
+ return;
+ }
+ slength = (png_size_t)length;
+ png_crc_read(png_ptr, (png_bytep)png_ptr->chunkdata, slength);
+ if (png_crc_finish(png_ptr, 0))
+ {
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ return;
+ }
+
+ png_ptr->chunkdata[slength] = 0x00;
+
+ for (lang = png_ptr->chunkdata; *lang; lang++)
+ /* Empty loop */ ;
+ lang++; /* Skip NUL separator */
+
+ /* iTXt must have a language tag (possibly empty), two compression bytes,
+ * translated keyword (possibly empty), and possibly some text after the
+ * keyword
+ */
+
+ if (lang >= png_ptr->chunkdata + slength - 3)
+ {
+ png_warning(png_ptr, "Truncated iTXt chunk");
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ return;
+ }
+ else
+ {
+ comp_flag = *lang++;
+ comp_type = *lang++;
+ }
+
+ for (lang_key = lang; *lang_key; lang_key++)
+ /* Empty loop */ ;
+ lang_key++; /* Skip NUL separator */
+
+ if (lang_key >= png_ptr->chunkdata + slength)
+ {
+ png_warning(png_ptr, "Truncated iTXt chunk");
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ return;
+ }
+
+ for (text = lang_key; *text; text++)
+ /* Empty loop */ ;
+ text++; /* Skip NUL separator */
+ if (text >= png_ptr->chunkdata + slength)
+ {
+ png_warning(png_ptr, "Malformed iTXt chunk");
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ return;
+ }
+
+ prefix_len = text - png_ptr->chunkdata;
+
+ key=png_ptr->chunkdata;
+ if (comp_flag)
+ png_decompress_chunk(png_ptr, comp_type,
+ (size_t)length, prefix_len, &data_len);
+ else
+ data_len = png_strlen(png_ptr->chunkdata + prefix_len);
+ text_ptr = (png_textp)png_malloc_warn(png_ptr,
+ (png_uint_32)png_sizeof(png_text));
+ if (text_ptr == NULL)
+ {
+ png_warning(png_ptr, "Not enough memory to process iTXt chunk.");
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ return;
+ }
+ text_ptr->compression = (int)comp_flag + 1;
+ text_ptr->lang_key = png_ptr->chunkdata + (lang_key - key);
+ text_ptr->lang = png_ptr->chunkdata + (lang - key);
+ text_ptr->itxt_length = data_len;
+ text_ptr->text_length = 0;
+ text_ptr->key = png_ptr->chunkdata;
+ text_ptr->text = png_ptr->chunkdata + prefix_len;
+
+ ret = png_set_text_2(png_ptr, info_ptr, text_ptr, 1);
+
+ png_free(png_ptr, text_ptr);
+ png_free(png_ptr, png_ptr->chunkdata);
+ png_ptr->chunkdata = NULL;
+ if (ret)
+ png_error(png_ptr, "Insufficient memory to store iTXt chunk.");
+}
+#endif
+
+/* This function is called when we haven't found a handler for a
+ chunk. If there isn't a problem with the chunk itself (ie bad
+ chunk name, CRC, or a critical chunk), the chunk is silently ignored
+ -- unless the PNG_FLAG_UNKNOWN_CHUNKS_SUPPORTED flag is on in which
+ case it will be saved away to be written out later. */
+void /* PRIVATE */
+png_handle_unknown(png_structp png_ptr, png_infop info_ptr, png_uint_32 length)
+{
+ png_uint_32 skip = 0;
+
+ png_debug(1, "in png_handle_unknown");
+
+#ifdef PNG_USER_LIMITS_SUPPORTED
+ if (png_ptr->user_chunk_cache_max != 0)
+ {
+ if (png_ptr->user_chunk_cache_max == 1)
+ {
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ if (--png_ptr->user_chunk_cache_max == 1)
+ {
+ png_warning(png_ptr, "No space in chunk cache for unknown chunk");
+ png_crc_finish(png_ptr, length);
+ return;
+ }
+ }
+#endif
+
+ if (png_ptr->mode & PNG_HAVE_IDAT)
+ {
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_CONST PNG_IDAT;
+#endif
+ if (png_memcmp(png_ptr->chunk_name, png_IDAT, 4)) /* Not an IDAT */
+ png_ptr->mode |= PNG_AFTER_IDAT;
+ }
+
+ if (!(png_ptr->chunk_name[0] & 0x20))
+ {
+#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
+ if (png_handle_as_unknown(png_ptr, png_ptr->chunk_name) !=
+ PNG_HANDLE_CHUNK_ALWAYS
+#ifdef PNG_READ_USER_CHUNKS_SUPPORTED
+ && png_ptr->read_user_chunk_fn == NULL
+#endif
+ )
+#endif
+ png_chunk_error(png_ptr, "unknown critical chunk");
+ }
+
+#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
+ if ((png_ptr->flags & PNG_FLAG_KEEP_UNKNOWN_CHUNKS)
+#ifdef PNG_READ_USER_CHUNKS_SUPPORTED
+ || (png_ptr->read_user_chunk_fn != NULL)
+#endif
+ )
+ {
+#ifdef PNG_MAX_MALLOC_64K
+ if (length > (png_uint_32)65535L)
+ {
+ png_warning(png_ptr, "unknown chunk too large to fit in memory");
+ skip = length - (png_uint_32)65535L;
+ length = (png_uint_32)65535L;
+ }
+#endif
+ png_memcpy((png_charp)png_ptr->unknown_chunk.name,
+ (png_charp)png_ptr->chunk_name,
+ png_sizeof(png_ptr->unknown_chunk.name));
+ png_ptr->unknown_chunk.name[png_sizeof(png_ptr->unknown_chunk.name)-1]
+ = '\0';
+ png_ptr->unknown_chunk.size = (png_size_t)length;
+ if (length == 0)
+ png_ptr->unknown_chunk.data = NULL;
+ else
+ {
+ png_ptr->unknown_chunk.data = (png_bytep)png_malloc(png_ptr, length);
+ png_crc_read(png_ptr, (png_bytep)png_ptr->unknown_chunk.data, length);
+ }
+#ifdef PNG_READ_USER_CHUNKS_SUPPORTED
+ if (png_ptr->read_user_chunk_fn != NULL)
+ {
+ /* Callback to user unknown chunk handler */
+ int ret;
+ ret = (*(png_ptr->read_user_chunk_fn))
+ (png_ptr, &png_ptr->unknown_chunk);
+ if (ret < 0)
+ png_chunk_error(png_ptr, "error in user chunk");
+ if (ret == 0)
+ {
+ if (!(png_ptr->chunk_name[0] & 0x20))
+#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
+ if (png_handle_as_unknown(png_ptr, png_ptr->chunk_name) !=
+ PNG_HANDLE_CHUNK_ALWAYS)
+#endif
+ png_chunk_error(png_ptr, "unknown critical chunk");
+ png_set_unknown_chunks(png_ptr, info_ptr,
+ &png_ptr->unknown_chunk, 1);
+ }
+ }
+ else
+#endif
+ png_set_unknown_chunks(png_ptr, info_ptr, &png_ptr->unknown_chunk, 1);
+ png_free(png_ptr, png_ptr->unknown_chunk.data);
+ png_ptr->unknown_chunk.data = NULL;
+ }
+ else
+#endif
+ skip = length;
+
+ png_crc_finish(png_ptr, skip);
+
+#ifndef PNG_READ_USER_CHUNKS_SUPPORTED
+ info_ptr = info_ptr; /* Quiet compiler warnings about unused info_ptr */
+#endif
+}
+
+/* This function is called to verify that a chunk name is valid.
+ This function can't have the "critical chunk check" incorporated
+ into it, since in the future we will need to be able to call user
+ functions to handle unknown critical chunks after we check that
+ the chunk name itself is valid. */
+
+#define isnonalpha(c) ((c) < 65 || (c) > 122 || ((c) > 90 && (c) < 97))
+
+void /* PRIVATE */
+png_check_chunk_name(png_structp png_ptr, png_bytep chunk_name)
+{
+ png_debug(1, "in png_check_chunk_name");
+ if (isnonalpha(chunk_name[0]) || isnonalpha(chunk_name[1]) ||
+ isnonalpha(chunk_name[2]) || isnonalpha(chunk_name[3]))
+ {
+ png_chunk_error(png_ptr, "invalid chunk type");
+ }
+}
+
+/* Combines the row recently read in with the existing pixels in the
+ row. This routine takes care of alpha and transparency if requested.
+ This routine also handles the two methods of progressive display
+ of interlaced images, depending on the mask value.
+ The mask value describes which pixels are to be combined with
+ the row. The pattern always repeats every 8 pixels, so just 8
+ bits are needed. A one indicates the pixel is to be combined,
+ a zero indicates the pixel is to be skipped. This is in addition
+ to any alpha or transparency value associated with the pixel. If
+ you want all pixels to be combined, pass 0xff (255) in mask. */
+
+void /* PRIVATE */
+png_combine_row(png_structp png_ptr, png_bytep row, int mask)
+{
+ png_debug(1, "in png_combine_row");
+ if (mask == 0xff)
+ {
+ png_memcpy(row, png_ptr->row_buf + 1,
+ PNG_ROWBYTES(png_ptr->row_info.pixel_depth, png_ptr->width));
+ }
+ else
+ {
+ switch (png_ptr->row_info.pixel_depth)
+ {
+ case 1:
+ {
+ png_bytep sp = png_ptr->row_buf + 1;
+ png_bytep dp = row;
+ int s_inc, s_start, s_end;
+ int m = 0x80;
+ int shift;
+ png_uint_32 i;
+ png_uint_32 row_width = png_ptr->width;
+
+#ifdef PNG_READ_PACKSWAP_SUPPORTED
+ if (png_ptr->transformations & PNG_PACKSWAP)
+ {
+ s_start = 0;
+ s_end = 7;
+ s_inc = 1;
+ }
+ else
+#endif
+ {
+ s_start = 7;
+ s_end = 0;
+ s_inc = -1;
+ }
+
+ shift = s_start;
+
+ for (i = 0; i < row_width; i++)
+ {
+ if (m & mask)
+ {
+ int value;
+
+ value = (*sp >> shift) & 0x01;
+ *dp &= (png_byte)((0x7f7f >> (7 - shift)) & 0xff);
+ *dp |= (png_byte)(value << shift);
+ }
+
+ if (shift == s_end)
+ {
+ shift = s_start;
+ sp++;
+ dp++;
+ }
+ else
+ shift += s_inc;
+
+ if (m == 1)
+ m = 0x80;
+ else
+ m >>= 1;
+ }
+ break;
+ }
+ case 2:
+ {
+ png_bytep sp = png_ptr->row_buf + 1;
+ png_bytep dp = row;
+ int s_start, s_end, s_inc;
+ int m = 0x80;
+ int shift;
+ png_uint_32 i;
+ png_uint_32 row_width = png_ptr->width;
+ int value;
+
+#ifdef PNG_READ_PACKSWAP_SUPPORTED
+ if (png_ptr->transformations & PNG_PACKSWAP)
+ {
+ s_start = 0;
+ s_end = 6;
+ s_inc = 2;
+ }
+ else
+#endif
+ {
+ s_start = 6;
+ s_end = 0;
+ s_inc = -2;
+ }
+
+ shift = s_start;
+
+ for (i = 0; i < row_width; i++)
+ {
+ if (m & mask)
+ {
+ value = (*sp >> shift) & 0x03;
+ *dp &= (png_byte)((0x3f3f >> (6 - shift)) & 0xff);
+ *dp |= (png_byte)(value << shift);
+ }
+
+ if (shift == s_end)
+ {
+ shift = s_start;
+ sp++;
+ dp++;
+ }
+ else
+ shift += s_inc;
+ if (m == 1)
+ m = 0x80;
+ else
+ m >>= 1;
+ }
+ break;
+ }
+ case 4:
+ {
+ png_bytep sp = png_ptr->row_buf + 1;
+ png_bytep dp = row;
+ int s_start, s_end, s_inc;
+ int m = 0x80;
+ int shift;
+ png_uint_32 i;
+ png_uint_32 row_width = png_ptr->width;
+ int value;
+
+#ifdef PNG_READ_PACKSWAP_SUPPORTED
+ if (png_ptr->transformations & PNG_PACKSWAP)
+ {
+ s_start = 0;
+ s_end = 4;
+ s_inc = 4;
+ }
+ else
+#endif
+ {
+ s_start = 4;
+ s_end = 0;
+ s_inc = -4;
+ }
+ shift = s_start;
+
+ for (i = 0; i < row_width; i++)
+ {
+ if (m & mask)
+ {
+ value = (*sp >> shift) & 0xf;
+ *dp &= (png_byte)((0xf0f >> (4 - shift)) & 0xff);
+ *dp |= (png_byte)(value << shift);
+ }
+
+ if (shift == s_end)
+ {
+ shift = s_start;
+ sp++;
+ dp++;
+ }
+ else
+ shift += s_inc;
+ if (m == 1)
+ m = 0x80;
+ else
+ m >>= 1;
+ }
+ break;
+ }
+ default:
+ {
+ png_bytep sp = png_ptr->row_buf + 1;
+ png_bytep dp = row;
+ png_size_t pixel_bytes = (png_ptr->row_info.pixel_depth >> 3);
+ png_uint_32 i;
+ png_uint_32 row_width = png_ptr->width;
+ png_byte m = 0x80;
+
+
+ for (i = 0; i < row_width; i++)
+ {
+ if (m & mask)
+ {
+ png_memcpy(dp, sp, pixel_bytes);
+ }
+
+ sp += pixel_bytes;
+ dp += pixel_bytes;
+
+ if (m == 1)
+ m = 0x80;
+ else
+ m >>= 1;
+ }
+ break;
+ }
+ }
+ }
+}
+
+#ifdef PNG_READ_INTERLACING_SUPPORTED
+/* OLD pre-1.0.9 interface:
+void png_do_read_interlace(png_row_infop row_info, png_bytep row, int pass,
+ png_uint_32 transformations)
+ */
+void /* PRIVATE */
+png_do_read_interlace(png_structp png_ptr)
+{
+ png_row_infop row_info = &(png_ptr->row_info);
+ png_bytep row = png_ptr->row_buf + 1;
+ int pass = png_ptr->pass;
+ png_uint_32 transformations = png_ptr->transformations;
+ /* Arrays to facilitate easy interlacing - use pass (0 - 6) as index */
+ /* Offset to next interlace block */
+ PNG_CONST int png_pass_inc[7] = {8, 8, 4, 4, 2, 2, 1};
+
+ png_debug(1, "in png_do_read_interlace");
+ if (row != NULL && row_info != NULL)
+ {
+ png_uint_32 final_width;
+
+ final_width = row_info->width * png_pass_inc[pass];
+
+ switch (row_info->pixel_depth)
+ {
+ case 1:
+ {
+ png_bytep sp = row + (png_size_t)((row_info->width - 1) >> 3);
+ png_bytep dp = row + (png_size_t)((final_width - 1) >> 3);
+ int sshift, dshift;
+ int s_start, s_end, s_inc;
+ int jstop = png_pass_inc[pass];
+ png_byte v;
+ png_uint_32 i;
+ int j;
+
+#ifdef PNG_READ_PACKSWAP_SUPPORTED
+ if (transformations & PNG_PACKSWAP)
+ {
+ sshift = (int)((row_info->width + 7) & 0x07);
+ dshift = (int)((final_width + 7) & 0x07);
+ s_start = 7;
+ s_end = 0;
+ s_inc = -1;
+ }
+ else
+#endif
+ {
+ sshift = 7 - (int)((row_info->width + 7) & 0x07);
+ dshift = 7 - (int)((final_width + 7) & 0x07);
+ s_start = 0;
+ s_end = 7;
+ s_inc = 1;
+ }
+
+ for (i = 0; i < row_info->width; i++)
+ {
+ v = (png_byte)((*sp >> sshift) & 0x01);
+ for (j = 0; j < jstop; j++)
+ {
+ *dp &= (png_byte)((0x7f7f >> (7 - dshift)) & 0xff);
+ *dp |= (png_byte)(v << dshift);
+ if (dshift == s_end)
+ {
+ dshift = s_start;
+ dp--;
+ }
+ else
+ dshift += s_inc;
+ }
+ if (sshift == s_end)
+ {
+ sshift = s_start;
+ sp--;
+ }
+ else
+ sshift += s_inc;
+ }
+ break;
+ }
+ case 2:
+ {
+ png_bytep sp = row + (png_uint_32)((row_info->width - 1) >> 2);
+ png_bytep dp = row + (png_uint_32)((final_width - 1) >> 2);
+ int sshift, dshift;
+ int s_start, s_end, s_inc;
+ int jstop = png_pass_inc[pass];
+ png_uint_32 i;
+
+#ifdef PNG_READ_PACKSWAP_SUPPORTED
+ if (transformations & PNG_PACKSWAP)
+ {
+ sshift = (int)(((row_info->width + 3) & 0x03) << 1);
+ dshift = (int)(((final_width + 3) & 0x03) << 1);
+ s_start = 6;
+ s_end = 0;
+ s_inc = -2;
+ }
+ else
+#endif
+ {
+ sshift = (int)((3 - ((row_info->width + 3) & 0x03)) << 1);
+ dshift = (int)((3 - ((final_width + 3) & 0x03)) << 1);
+ s_start = 0;
+ s_end = 6;
+ s_inc = 2;
+ }
+
+ for (i = 0; i < row_info->width; i++)
+ {
+ png_byte v;
+ int j;
+
+ v = (png_byte)((*sp >> sshift) & 0x03);
+ for (j = 0; j < jstop; j++)
+ {
+ *dp &= (png_byte)((0x3f3f >> (6 - dshift)) & 0xff);
+ *dp |= (png_byte)(v << dshift);
+ if (dshift == s_end)
+ {
+ dshift = s_start;
+ dp--;
+ }
+ else
+ dshift += s_inc;
+ }
+ if (sshift == s_end)
+ {
+ sshift = s_start;
+ sp--;
+ }
+ else
+ sshift += s_inc;
+ }
+ break;
+ }
+ case 4:
+ {
+ png_bytep sp = row + (png_size_t)((row_info->width - 1) >> 1);
+ png_bytep dp = row + (png_size_t)((final_width - 1) >> 1);
+ int sshift, dshift;
+ int s_start, s_end, s_inc;
+ png_uint_32 i;
+ int jstop = png_pass_inc[pass];
+
+#ifdef PNG_READ_PACKSWAP_SUPPORTED
+ if (transformations & PNG_PACKSWAP)
+ {
+ sshift = (int)(((row_info->width + 1) & 0x01) << 2);
+ dshift = (int)(((final_width + 1) & 0x01) << 2);
+ s_start = 4;
+ s_end = 0;
+ s_inc = -4;
+ }
+ else
+#endif
+ {
+ sshift = (int)((1 - ((row_info->width + 1) & 0x01)) << 2);
+ dshift = (int)((1 - ((final_width + 1) & 0x01)) << 2);
+ s_start = 0;
+ s_end = 4;
+ s_inc = 4;
+ }
+
+ for (i = 0; i < row_info->width; i++)
+ {
+ png_byte v = (png_byte)((*sp >> sshift) & 0xf);
+ int j;
+
+ for (j = 0; j < jstop; j++)
+ {
+ *dp &= (png_byte)((0xf0f >> (4 - dshift)) & 0xff);
+ *dp |= (png_byte)(v << dshift);
+ if (dshift == s_end)
+ {
+ dshift = s_start;
+ dp--;
+ }
+ else
+ dshift += s_inc;
+ }
+ if (sshift == s_end)
+ {
+ sshift = s_start;
+ sp--;
+ }
+ else
+ sshift += s_inc;
+ }
+ break;
+ }
+ default:
+ {
+ png_size_t pixel_bytes = (row_info->pixel_depth >> 3);
+ png_bytep sp = row + (png_size_t)(row_info->width - 1)
+ * pixel_bytes;
+ png_bytep dp = row + (png_size_t)(final_width - 1) * pixel_bytes;
+
+ int jstop = png_pass_inc[pass];
+ png_uint_32 i;
+
+ for (i = 0; i < row_info->width; i++)
+ {
+ png_byte v[8];
+ int j;
+
+ png_memcpy(v, sp, pixel_bytes);
+ for (j = 0; j < jstop; j++)
+ {
+ png_memcpy(dp, v, pixel_bytes);
+ dp -= pixel_bytes;
+ }
+ sp -= pixel_bytes;
+ }
+ break;
+ }
+ }
+ row_info->width = final_width;
+ row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, final_width);
+ }
+#ifndef PNG_READ_PACKSWAP_SUPPORTED
+ transformations = transformations; /* Silence compiler warning */
+#endif
+}
+#endif /* PNG_READ_INTERLACING_SUPPORTED */
+
+void /* PRIVATE */
+png_read_filter_row(png_structp png_ptr, png_row_infop row_info, png_bytep row,
+ png_bytep prev_row, int filter)
+{
+ png_debug(1, "in png_read_filter_row");
+ png_debug2(2, "row = %lu, filter = %d", png_ptr->row_number, filter);
+ switch (filter)
+ {
+ case PNG_FILTER_VALUE_NONE:
+ break;
+ case PNG_FILTER_VALUE_SUB:
+ {
+ png_uint_32 i;
+ png_uint_32 istop = row_info->rowbytes;
+ png_uint_32 bpp = (row_info->pixel_depth + 7) >> 3;
+ png_bytep rp = row + bpp;
+ png_bytep lp = row;
+
+ for (i = bpp; i < istop; i++)
+ {
+ *rp = (png_byte)(((int)(*rp) + (int)(*lp++)) & 0xff);
+ rp++;
+ }
+ break;
+ }
+ case PNG_FILTER_VALUE_UP:
+ {
+ png_uint_32 i;
+ png_uint_32 istop = row_info->rowbytes;
+ png_bytep rp = row;
+ png_bytep pp = prev_row;
+
+ for (i = 0; i < istop; i++)
+ {
+ *rp = (png_byte)(((int)(*rp) + (int)(*pp++)) & 0xff);
+ rp++;
+ }
+ break;
+ }
+ case PNG_FILTER_VALUE_AVG:
+ {
+ png_uint_32 i;
+ png_bytep rp = row;
+ png_bytep pp = prev_row;
+ png_bytep lp = row;
+ png_uint_32 bpp = (row_info->pixel_depth + 7) >> 3;
+ png_uint_32 istop = row_info->rowbytes - bpp;
+
+ for (i = 0; i < bpp; i++)
+ {
+ *rp = (png_byte)(((int)(*rp) +
+ ((int)(*pp++) / 2 )) & 0xff);
+ rp++;
+ }
+
+ for (i = 0; i < istop; i++)
+ {
+ *rp = (png_byte)(((int)(*rp) +
+ (int)(*pp++ + *lp++) / 2 ) & 0xff);
+ rp++;
+ }
+ break;
+ }
+ case PNG_FILTER_VALUE_PAETH:
+ {
+ png_uint_32 i;
+ png_bytep rp = row;
+ png_bytep pp = prev_row;
+ png_bytep lp = row;
+ png_bytep cp = prev_row;
+ png_uint_32 bpp = (row_info->pixel_depth + 7) >> 3;
+ png_uint_32 istop=row_info->rowbytes - bpp;
+
+ for (i = 0; i < bpp; i++)
+ {
+ *rp = (png_byte)(((int)(*rp) + (int)(*pp++)) & 0xff);
+ rp++;
+ }
+
+ for (i = 0; i < istop; i++) /* Use leftover rp,pp */
+ {
+ int a, b, c, pa, pb, pc, p;
+
+ a = *lp++;
+ b = *pp++;
+ c = *cp++;
+
+ p = b - c;
+ pc = a - c;
+
+#ifdef PNG_USE_ABS
+ pa = abs(p);
+ pb = abs(pc);
+ pc = abs(p + pc);
+#else
+ pa = p < 0 ? -p : p;
+ pb = pc < 0 ? -pc : pc;
+ pc = (p + pc) < 0 ? -(p + pc) : p + pc;
+#endif
+
+ /*
+ if (pa <= pb && pa <= pc)
+ p = a;
+ else if (pb <= pc)
+ p = b;
+ else
+ p = c;
+ */
+
+ p = (pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c;
+
+ *rp = (png_byte)(((int)(*rp) + p) & 0xff);
+ rp++;
+ }
+ break;
+ }
+ default:
+ png_warning(png_ptr, "Ignoring bad adaptive filter type");
+ *row = 0;
+ break;
+ }
+}
+
+#ifdef PNG_SEQUENTIAL_READ_SUPPORTED
+void /* PRIVATE */
+png_read_finish_row(png_structp png_ptr)
+{
+#ifdef PNG_READ_INTERLACING_SUPPORTED
+ /* Arrays to facilitate easy interlacing - use pass (0 - 6) as index */
+
+ /* Start of interlace block */
+ PNG_CONST int png_pass_start[7] = {0, 4, 0, 2, 0, 1, 0};
+
+ /* Offset to next interlace block */
+ PNG_CONST int png_pass_inc[7] = {8, 8, 4, 4, 2, 2, 1};
+
+ /* Start of interlace block in the y direction */
+ PNG_CONST int png_pass_ystart[7] = {0, 0, 4, 0, 2, 0, 1};
+
+ /* Offset to next interlace block in the y direction */
+ PNG_CONST int png_pass_yinc[7] = {8, 8, 8, 4, 4, 2, 2};
+#endif /* PNG_READ_INTERLACING_SUPPORTED */
+
+ png_debug(1, "in png_read_finish_row");
+ png_ptr->row_number++;
+ if (png_ptr->row_number < png_ptr->num_rows)
+ return;
+
+#ifdef PNG_READ_INTERLACING_SUPPORTED
+ if (png_ptr->interlaced)
+ {
+ png_ptr->row_number = 0;
+ png_memset_check(png_ptr, png_ptr->prev_row, 0,
+ png_ptr->rowbytes + 1);
+ do
+ {
+ png_ptr->pass++;
+ if (png_ptr->pass >= 7)
+ break;
+ png_ptr->iwidth = (png_ptr->width +
+ png_pass_inc[png_ptr->pass] - 1 -
+ png_pass_start[png_ptr->pass]) /
+ png_pass_inc[png_ptr->pass];
+
+ if (!(png_ptr->transformations & PNG_INTERLACE))
+ {
+ png_ptr->num_rows = (png_ptr->height +
+ png_pass_yinc[png_ptr->pass] - 1 -
+ png_pass_ystart[png_ptr->pass]) /
+ png_pass_yinc[png_ptr->pass];
+ if (!(png_ptr->num_rows))
+ continue;
+ }
+ else /* if (png_ptr->transformations & PNG_INTERLACE) */
+ break;
+ } while (png_ptr->iwidth == 0);
+
+ if (png_ptr->pass < 7)
+ return;
+ }
+#endif /* PNG_READ_INTERLACING_SUPPORTED */
+
+ if (!(png_ptr->flags & PNG_FLAG_ZLIB_FINISHED))
+ {
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_CONST PNG_IDAT;
+#endif
+ char extra;
+ int ret;
+
+ png_ptr->zstream.next_out = (Byte *)&extra;
+ png_ptr->zstream.avail_out = (uInt)1;
+ for (;;)
+ {
+ if (!(png_ptr->zstream.avail_in))
+ {
+ while (!png_ptr->idat_size)
+ {
+ png_byte chunk_length[4];
+
+ png_crc_finish(png_ptr, 0);
+
+ png_read_data(png_ptr, chunk_length, 4);
+ png_ptr->idat_size = png_get_uint_31(png_ptr, chunk_length);
+ png_reset_crc(png_ptr);
+ png_crc_read(png_ptr, png_ptr->chunk_name, 4);
+ if (png_memcmp(png_ptr->chunk_name, png_IDAT, 4))
+ png_error(png_ptr, "Not enough image data");
+
+ }
+ png_ptr->zstream.avail_in = (uInt)png_ptr->zbuf_size;
+ png_ptr->zstream.next_in = png_ptr->zbuf;
+ if (png_ptr->zbuf_size > png_ptr->idat_size)
+ png_ptr->zstream.avail_in = (uInt)png_ptr->idat_size;
+ png_crc_read(png_ptr, png_ptr->zbuf, png_ptr->zstream.avail_in);
+ png_ptr->idat_size -= png_ptr->zstream.avail_in;
+ }
+ ret = inflate(&png_ptr->zstream, Z_PARTIAL_FLUSH);
+ if (ret == Z_STREAM_END)
+ {
+ if (!(png_ptr->zstream.avail_out) || png_ptr->zstream.avail_in ||
+ png_ptr->idat_size)
+ png_warning(png_ptr, "Extra compressed data.");
+ png_ptr->mode |= PNG_AFTER_IDAT;
+ png_ptr->flags |= PNG_FLAG_ZLIB_FINISHED;
+ break;
+ }
+ if (ret != Z_OK)
+ png_error(png_ptr, png_ptr->zstream.msg ? png_ptr->zstream.msg :
+ "Decompression Error");
+
+ if (!(png_ptr->zstream.avail_out))
+ {
+ png_warning(png_ptr, "Extra compressed data.");
+ png_ptr->mode |= PNG_AFTER_IDAT;
+ png_ptr->flags |= PNG_FLAG_ZLIB_FINISHED;
+ break;
+ }
+
+ }
+ png_ptr->zstream.avail_out = 0;
+ }
+
+ if (png_ptr->idat_size || png_ptr->zstream.avail_in)
+ png_warning(png_ptr, "Extra compression data.");
+
+ inflateReset(&png_ptr->zstream);
+
+ png_ptr->mode |= PNG_AFTER_IDAT;
+}
+#endif /* PNG_SEQUENTIAL_READ_SUPPORTED */
+
+void /* PRIVATE */
+png_read_start_row(png_structp png_ptr)
+{
+#ifdef PNG_READ_INTERLACING_SUPPORTED
+ /* Arrays to facilitate easy interlacing - use pass (0 - 6) as index */
+
+ /* Start of interlace block */
+ PNG_CONST int png_pass_start[7] = {0, 4, 0, 2, 0, 1, 0};
+
+ /* Offset to next interlace block */
+ PNG_CONST int png_pass_inc[7] = {8, 8, 4, 4, 2, 2, 1};
+
+ /* Start of interlace block in the y direction */
+ PNG_CONST int png_pass_ystart[7] = {0, 0, 4, 0, 2, 0, 1};
+
+ /* Offset to next interlace block in the y direction */
+ PNG_CONST int png_pass_yinc[7] = {8, 8, 8, 4, 4, 2, 2};
+#endif
+
+ int max_pixel_depth;
+ png_size_t row_bytes;
+
+ png_debug(1, "in png_read_start_row");
+ png_ptr->zstream.avail_in = 0;
+ png_init_read_transformations(png_ptr);
+#ifdef PNG_READ_INTERLACING_SUPPORTED
+ if (png_ptr->interlaced)
+ {
+ if (!(png_ptr->transformations & PNG_INTERLACE))
+ png_ptr->num_rows = (png_ptr->height + png_pass_yinc[0] - 1 -
+ png_pass_ystart[0]) / png_pass_yinc[0];
+ else
+ png_ptr->num_rows = png_ptr->height;
+
+ png_ptr->iwidth = (png_ptr->width +
+ png_pass_inc[png_ptr->pass] - 1 -
+ png_pass_start[png_ptr->pass]) /
+ png_pass_inc[png_ptr->pass];
+ }
+ else
+#endif /* PNG_READ_INTERLACING_SUPPORTED */
+ {
+ png_ptr->num_rows = png_ptr->height;
+ png_ptr->iwidth = png_ptr->width;
+ }
+ max_pixel_depth = png_ptr->pixel_depth;
+
+#ifdef PNG_READ_PACK_SUPPORTED
+ if ((png_ptr->transformations & PNG_PACK) && png_ptr->bit_depth < 8)
+ max_pixel_depth = 8;
+#endif
+
+#ifdef PNG_READ_EXPAND_SUPPORTED
+ if (png_ptr->transformations & PNG_EXPAND)
+ {
+ if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE)
+ {
+ if (png_ptr->num_trans)
+ max_pixel_depth = 32;
+ else
+ max_pixel_depth = 24;
+ }
+ else if (png_ptr->color_type == PNG_COLOR_TYPE_GRAY)
+ {
+ if (max_pixel_depth < 8)
+ max_pixel_depth = 8;
+ if (png_ptr->num_trans)
+ max_pixel_depth *= 2;
+ }
+ else if (png_ptr->color_type == PNG_COLOR_TYPE_RGB)
+ {
+ if (png_ptr->num_trans)
+ {
+ max_pixel_depth *= 4;
+ max_pixel_depth /= 3;
+ }
+ }
+ }
+#endif
+
+#ifdef PNG_READ_FILLER_SUPPORTED
+ if (png_ptr->transformations & (PNG_FILLER))
+ {
+ if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE)
+ max_pixel_depth = 32;
+ else if (png_ptr->color_type == PNG_COLOR_TYPE_GRAY)
+ {
+ if (max_pixel_depth <= 8)
+ max_pixel_depth = 16;
+ else
+ max_pixel_depth = 32;
+ }
+ else if (png_ptr->color_type == PNG_COLOR_TYPE_RGB)
+ {
+ if (max_pixel_depth <= 32)
+ max_pixel_depth = 32;
+ else
+ max_pixel_depth = 64;
+ }
+ }
+#endif
+
+#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED
+ if (png_ptr->transformations & PNG_GRAY_TO_RGB)
+ {
+ if (
+#ifdef PNG_READ_EXPAND_SUPPORTED
+ (png_ptr->num_trans && (png_ptr->transformations & PNG_EXPAND)) ||
+#endif
+#ifdef PNG_READ_FILLER_SUPPORTED
+ (png_ptr->transformations & (PNG_FILLER)) ||
+#endif
+ png_ptr->color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
+ {
+ if (max_pixel_depth <= 16)
+ max_pixel_depth = 32;
+ else
+ max_pixel_depth = 64;
+ }
+ else
+ {
+ if (max_pixel_depth <= 8)
+ {
+ if (png_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA)
+ max_pixel_depth = 32;
+ else
+ max_pixel_depth = 24;
+ }
+ else if (png_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA)
+ max_pixel_depth = 64;
+ else
+ max_pixel_depth = 48;
+ }
+ }
+#endif
+
+#if defined(PNG_READ_USER_TRANSFORM_SUPPORTED) && \
+defined(PNG_USER_TRANSFORM_PTR_SUPPORTED)
+ if (png_ptr->transformations & PNG_USER_TRANSFORM)
+ {
+ int user_pixel_depth = png_ptr->user_transform_depth*
+ png_ptr->user_transform_channels;
+ if (user_pixel_depth > max_pixel_depth)
+ max_pixel_depth=user_pixel_depth;
+ }
+#endif
+
+ /* Align the width on the next larger 8 pixels. Mainly used
+ * for interlacing
+ */
+ row_bytes = ((png_ptr->width + 7) & ~((png_uint_32)7));
+ /* Calculate the maximum bytes needed, adding a byte and a pixel
+ * for safety's sake
+ */
+ row_bytes = PNG_ROWBYTES(max_pixel_depth, row_bytes) +
+ 1 + ((max_pixel_depth + 7) >> 3);
+#ifdef PNG_MAX_MALLOC_64K
+ if (row_bytes > (png_uint_32)65536L)
+ png_error(png_ptr, "This image requires a row greater than 64KB");
+#endif
+
+ if (row_bytes + 64 > png_ptr->old_big_row_buf_size)
+ {
+ png_free(png_ptr, png_ptr->big_row_buf);
+ if (png_ptr->interlaced)
+ png_ptr->big_row_buf = (png_bytep)png_calloc(png_ptr,
+ row_bytes + 64);
+ else
+ png_ptr->big_row_buf = (png_bytep)png_malloc(png_ptr,
+ row_bytes + 64);
+ png_ptr->old_big_row_buf_size = row_bytes + 64;
+
+ /* Use 32 bytes of padding before and after row_buf. */
+ png_ptr->row_buf = png_ptr->big_row_buf + 32;
+ png_ptr->old_big_row_buf_size = row_bytes + 64;
+ }
+
+#ifdef PNG_MAX_MALLOC_64K
+ if ((png_uint_32)row_bytes + 1 > (png_uint_32)65536L)
+ png_error(png_ptr, "This image requires a row greater than 64KB");
+#endif
+ if ((png_uint_32)row_bytes > (png_uint_32)(PNG_SIZE_MAX - 1))
+ png_error(png_ptr, "Row has too many bytes to allocate in memory.");
+
+ if (row_bytes + 1 > png_ptr->old_prev_row_size)
+ {
+ png_free(png_ptr, png_ptr->prev_row);
+ png_ptr->prev_row = (png_bytep)png_malloc(png_ptr, (png_uint_32)(
+ row_bytes + 1));
+ png_memset_check(png_ptr, png_ptr->prev_row, 0, row_bytes + 1);
+ png_ptr->old_prev_row_size = row_bytes + 1;
+ }
+
+ png_ptr->rowbytes = row_bytes;
+
+ png_debug1(3, "width = %lu,", png_ptr->width);
+ png_debug1(3, "height = %lu,", png_ptr->height);
+ png_debug1(3, "iwidth = %lu,", png_ptr->iwidth);
+ png_debug1(3, "num_rows = %lu,", png_ptr->num_rows);
+ png_debug1(3, "rowbytes = %lu,", png_ptr->rowbytes);
+ png_debug1(3, "irowbytes = %lu",
+ PNG_ROWBYTES(png_ptr->pixel_depth, png_ptr->iwidth) + 1);
+
+ png_ptr->flags |= PNG_FLAG_ROW_INIT;
+}
+#endif /* PNG_READ_SUPPORTED */
diff --git a/trunk/src/third_party/libpng/pngset.c b/trunk/src/third_party/libpng/pngset.c
new file mode 100644
index 0000000..717757f
--- /dev/null
+++ b/trunk/src/third_party/libpng/pngset.c
@@ -0,0 +1,1226 @@
+
+/* pngset.c - storage of image information into info struct
+ *
+ * Last changed in libpng 1.2.43 [February 25, 2010]
+ * Copyright (c) 1998-2010 Glenn Randers-Pehrson
+ * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger)
+ * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.)
+ *
+ * This code is released under the libpng license.
+ * For conditions of distribution and use, see the disclaimer
+ * and license in png.h
+ *
+ * The functions here are used during reads to store data from the file
+ * into the info struct, and during writes to store application data
+ * into the info struct for writing into the file. This abstracts the
+ * info struct and allows us to change the structure in the future.
+ */
+
+#define PNG_INTERNAL
+#define PNG_NO_PEDANTIC_WARNINGS
+#include "png.h"
+#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED)
+
+#ifdef PNG_bKGD_SUPPORTED
+void PNGAPI
+png_set_bKGD(png_structp png_ptr, png_infop info_ptr, png_color_16p background)
+{
+ png_debug1(1, "in %s storage function", "bKGD");
+
+ if (png_ptr == NULL || info_ptr == NULL)
+ return;
+
+ png_memcpy(&(info_ptr->background), background, png_sizeof(png_color_16));
+ info_ptr->valid |= PNG_INFO_bKGD;
+}
+#endif
+
+#ifdef PNG_cHRM_SUPPORTED
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+void PNGAPI
+png_set_cHRM(png_structp png_ptr, png_infop info_ptr,
+ double white_x, double white_y, double red_x, double red_y,
+ double green_x, double green_y, double blue_x, double blue_y)
+{
+ png_debug1(1, "in %s storage function", "cHRM");
+
+ if (png_ptr == NULL || info_ptr == NULL)
+ return;
+
+ info_ptr->x_white = (float)white_x;
+ info_ptr->y_white = (float)white_y;
+ info_ptr->x_red = (float)red_x;
+ info_ptr->y_red = (float)red_y;
+ info_ptr->x_green = (float)green_x;
+ info_ptr->y_green = (float)green_y;
+ info_ptr->x_blue = (float)blue_x;
+ info_ptr->y_blue = (float)blue_y;
+#ifdef PNG_FIXED_POINT_SUPPORTED
+ info_ptr->int_x_white = (png_fixed_point)(white_x*100000.+0.5);
+ info_ptr->int_y_white = (png_fixed_point)(white_y*100000.+0.5);
+ info_ptr->int_x_red = (png_fixed_point)( red_x*100000.+0.5);
+ info_ptr->int_y_red = (png_fixed_point)( red_y*100000.+0.5);
+ info_ptr->int_x_green = (png_fixed_point)(green_x*100000.+0.5);
+ info_ptr->int_y_green = (png_fixed_point)(green_y*100000.+0.5);
+ info_ptr->int_x_blue = (png_fixed_point)( blue_x*100000.+0.5);
+ info_ptr->int_y_blue = (png_fixed_point)( blue_y*100000.+0.5);
+#endif
+ info_ptr->valid |= PNG_INFO_cHRM;
+}
+#endif /* PNG_FLOATING_POINT_SUPPORTED */
+
+#ifdef PNG_FIXED_POINT_SUPPORTED
+void PNGAPI
+png_set_cHRM_fixed(png_structp png_ptr, png_infop info_ptr,
+ png_fixed_point white_x, png_fixed_point white_y, png_fixed_point red_x,
+ png_fixed_point red_y, png_fixed_point green_x, png_fixed_point green_y,
+ png_fixed_point blue_x, png_fixed_point blue_y)
+{
+ png_debug1(1, "in %s storage function", "cHRM fixed");
+
+ if (png_ptr == NULL || info_ptr == NULL)
+ return;
+
+#ifdef PNG_CHECK_cHRM_SUPPORTED
+ if (png_check_cHRM_fixed(png_ptr,
+ white_x, white_y, red_x, red_y, green_x, green_y, blue_x, blue_y))
+#endif
+ {
+ info_ptr->int_x_white = white_x;
+ info_ptr->int_y_white = white_y;
+ info_ptr->int_x_red = red_x;
+ info_ptr->int_y_red = red_y;
+ info_ptr->int_x_green = green_x;
+ info_ptr->int_y_green = green_y;
+ info_ptr->int_x_blue = blue_x;
+ info_ptr->int_y_blue = blue_y;
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+ info_ptr->x_white = (float)(white_x/100000.);
+ info_ptr->y_white = (float)(white_y/100000.);
+ info_ptr->x_red = (float)( red_x/100000.);
+ info_ptr->y_red = (float)( red_y/100000.);
+ info_ptr->x_green = (float)(green_x/100000.);
+ info_ptr->y_green = (float)(green_y/100000.);
+ info_ptr->x_blue = (float)( blue_x/100000.);
+ info_ptr->y_blue = (float)( blue_y/100000.);
+#endif
+ info_ptr->valid |= PNG_INFO_cHRM;
+ }
+}
+#endif /* PNG_FIXED_POINT_SUPPORTED */
+#endif /* PNG_cHRM_SUPPORTED */
+
+#ifdef PNG_gAMA_SUPPORTED
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+void PNGAPI
+png_set_gAMA(png_structp png_ptr, png_infop info_ptr, double file_gamma)
+{
+ double png_gamma;
+
+ png_debug1(1, "in %s storage function", "gAMA");
+
+ if (png_ptr == NULL || info_ptr == NULL)
+ return;
+
+ /* Check for overflow */
+ if (file_gamma > 21474.83)
+ {
+ png_warning(png_ptr, "Limiting gamma to 21474.83");
+ png_gamma=21474.83;
+ }
+ else
+ png_gamma = file_gamma;
+ info_ptr->gamma = (float)png_gamma;
+#ifdef PNG_FIXED_POINT_SUPPORTED
+ info_ptr->int_gamma = (int)(png_gamma*100000.+.5);
+#endif
+ info_ptr->valid |= PNG_INFO_gAMA;
+ if (png_gamma == 0.0)
+ png_warning(png_ptr, "Setting gamma=0");
+}
+#endif
+void PNGAPI
+png_set_gAMA_fixed(png_structp png_ptr, png_infop info_ptr, png_fixed_point
+ int_gamma)
+{
+ png_fixed_point png_gamma;
+
+ png_debug1(1, "in %s storage function", "gAMA");
+
+ if (png_ptr == NULL || info_ptr == NULL)
+ return;
+
+ if (int_gamma > (png_fixed_point)PNG_UINT_31_MAX)
+ {
+ png_warning(png_ptr, "Limiting gamma to 21474.83");
+ png_gamma=PNG_UINT_31_MAX;
+ }
+ else
+ {
+ if (int_gamma < 0)
+ {
+ png_warning(png_ptr, "Setting negative gamma to zero");
+ png_gamma = 0;
+ }
+ else
+ png_gamma = int_gamma;
+ }
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+ info_ptr->gamma = (float)(png_gamma/100000.);
+#endif
+#ifdef PNG_FIXED_POINT_SUPPORTED
+ info_ptr->int_gamma = png_gamma;
+#endif
+ info_ptr->valid |= PNG_INFO_gAMA;
+ if (png_gamma == 0)
+ png_warning(png_ptr, "Setting gamma=0");
+}
+#endif
+
+#ifdef PNG_hIST_SUPPORTED
+void PNGAPI
+png_set_hIST(png_structp png_ptr, png_infop info_ptr, png_uint_16p hist)
+{
+ int i;
+
+ png_debug1(1, "in %s storage function", "hIST");
+
+ if (png_ptr == NULL || info_ptr == NULL)
+ return;
+
+ if (info_ptr->num_palette == 0 || info_ptr->num_palette
+ > PNG_MAX_PALETTE_LENGTH)
+ {
+ png_warning(png_ptr,
+ "Invalid palette size, hIST allocation skipped.");
+ return;
+ }
+
+#ifdef PNG_FREE_ME_SUPPORTED
+ png_free_data(png_ptr, info_ptr, PNG_FREE_HIST, 0);
+#endif
+ /* Changed from info->num_palette to PNG_MAX_PALETTE_LENGTH in
+ * version 1.2.1
+ */
+ png_ptr->hist = (png_uint_16p)png_malloc_warn(png_ptr,
+ (png_uint_32)(PNG_MAX_PALETTE_LENGTH * png_sizeof(png_uint_16)));
+ if (png_ptr->hist == NULL)
+ {
+ png_warning(png_ptr, "Insufficient memory for hIST chunk data.");
+ return;
+ }
+
+ for (i = 0; i < info_ptr->num_palette; i++)
+ png_ptr->hist[i] = hist[i];
+ info_ptr->hist = png_ptr->hist;
+ info_ptr->valid |= PNG_INFO_hIST;
+
+#ifdef PNG_FREE_ME_SUPPORTED
+ info_ptr->free_me |= PNG_FREE_HIST;
+#else
+ png_ptr->flags |= PNG_FLAG_FREE_HIST;
+#endif
+}
+#endif
+
+void PNGAPI
+png_set_IHDR(png_structp png_ptr, png_infop info_ptr,
+ png_uint_32 width, png_uint_32 height, int bit_depth,
+ int color_type, int interlace_type, int compression_type,
+ int filter_type)
+{
+ png_debug1(1, "in %s storage function", "IHDR");
+
+ if (png_ptr == NULL || info_ptr == NULL)
+ return;
+
+ info_ptr->width = width;
+ info_ptr->height = height;
+ info_ptr->bit_depth = (png_byte)bit_depth;
+ info_ptr->color_type = (png_byte)color_type;
+ info_ptr->compression_type = (png_byte)compression_type;
+ info_ptr->filter_type = (png_byte)filter_type;
+ info_ptr->interlace_type = (png_byte)interlace_type;
+
+ png_check_IHDR (png_ptr, info_ptr->width, info_ptr->height,
+ info_ptr->bit_depth, info_ptr->color_type, info_ptr->interlace_type,
+ info_ptr->compression_type, info_ptr->filter_type);
+
+ if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE)
+ info_ptr->channels = 1;
+ else if (info_ptr->color_type & PNG_COLOR_MASK_COLOR)
+ info_ptr->channels = 3;
+ else
+ info_ptr->channels = 1;
+ if (info_ptr->color_type & PNG_COLOR_MASK_ALPHA)
+ info_ptr->channels++;
+ info_ptr->pixel_depth = (png_byte)(info_ptr->channels * info_ptr->bit_depth);
+
+ /* Check for potential overflow */
+ if (width > (PNG_UINT_32_MAX
+ >> 3) /* 8-byte RGBA pixels */
+ - 64 /* bigrowbuf hack */
+ - 1 /* filter byte */
+ - 7*8 /* rounding of width to multiple of 8 pixels */
+ - 8) /* extra max_pixel_depth pad */
+ info_ptr->rowbytes = (png_size_t)0;
+ else
+ info_ptr->rowbytes = PNG_ROWBYTES(info_ptr->pixel_depth, width);
+}
+
+#ifdef PNG_oFFs_SUPPORTED
+void PNGAPI
+png_set_oFFs(png_structp png_ptr, png_infop info_ptr,
+ png_int_32 offset_x, png_int_32 offset_y, int unit_type)
+{
+ png_debug1(1, "in %s storage function", "oFFs");
+
+ if (png_ptr == NULL || info_ptr == NULL)
+ return;
+
+ info_ptr->x_offset = offset_x;
+ info_ptr->y_offset = offset_y;
+ info_ptr->offset_unit_type = (png_byte)unit_type;
+ info_ptr->valid |= PNG_INFO_oFFs;
+}
+#endif
+
+#ifdef PNG_pCAL_SUPPORTED
+void PNGAPI
+png_set_pCAL(png_structp png_ptr, png_infop info_ptr,
+ png_charp purpose, png_int_32 X0, png_int_32 X1, int type, int nparams,
+ png_charp units, png_charpp params)
+{
+ png_uint_32 length;
+ int i;
+
+ png_debug1(1, "in %s storage function", "pCAL");
+
+ if (png_ptr == NULL || info_ptr == NULL)
+ return;
+
+ length = png_strlen(purpose) + 1;
+ png_debug1(3, "allocating purpose for info (%lu bytes)",
+ (unsigned long)length);
+ info_ptr->pcal_purpose = (png_charp)png_malloc_warn(png_ptr, length);
+ if (info_ptr->pcal_purpose == NULL)
+ {
+ png_warning(png_ptr, "Insufficient memory for pCAL purpose.");
+ return;
+ }
+ png_memcpy(info_ptr->pcal_purpose, purpose, (png_size_t)length);
+
+ png_debug(3, "storing X0, X1, type, and nparams in info");
+ info_ptr->pcal_X0 = X0;
+ info_ptr->pcal_X1 = X1;
+ info_ptr->pcal_type = (png_byte)type;
+ info_ptr->pcal_nparams = (png_byte)nparams;
+
+ length = png_strlen(units) + 1;
+ png_debug1(3, "allocating units for info (%lu bytes)",
+ (unsigned long)length);
+ info_ptr->pcal_units = (png_charp)png_malloc_warn(png_ptr, length);
+ if (info_ptr->pcal_units == NULL)
+ {
+ png_warning(png_ptr, "Insufficient memory for pCAL units.");
+ return;
+ }
+ png_memcpy(info_ptr->pcal_units, units, (png_size_t)length);
+
+ info_ptr->pcal_params = (png_charpp)png_malloc_warn(png_ptr,
+ (png_uint_32)((nparams + 1) * png_sizeof(png_charp)));
+ if (info_ptr->pcal_params == NULL)
+ {
+ png_warning(png_ptr, "Insufficient memory for pCAL params.");
+ return;
+ }
+
+ png_memset(info_ptr->pcal_params, 0, (nparams + 1) * png_sizeof(png_charp));
+
+ for (i = 0; i < nparams; i++)
+ {
+ length = png_strlen(params[i]) + 1;
+ png_debug2(3, "allocating parameter %d for info (%lu bytes)", i,
+ (unsigned long)length);
+ info_ptr->pcal_params[i] = (png_charp)png_malloc_warn(png_ptr, length);
+ if (info_ptr->pcal_params[i] == NULL)
+ {
+ png_warning(png_ptr, "Insufficient memory for pCAL parameter.");
+ return;
+ }
+ png_memcpy(info_ptr->pcal_params[i], params[i], (png_size_t)length);
+ }
+
+ info_ptr->valid |= PNG_INFO_pCAL;
+#ifdef PNG_FREE_ME_SUPPORTED
+ info_ptr->free_me |= PNG_FREE_PCAL;
+#endif
+}
+#endif
+
+#if defined(PNG_READ_sCAL_SUPPORTED) || defined(PNG_WRITE_sCAL_SUPPORTED)
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+void PNGAPI
+png_set_sCAL(png_structp png_ptr, png_infop info_ptr,
+ int unit, double width, double height)
+{
+ png_debug1(1, "in %s storage function", "sCAL");
+
+ if (png_ptr == NULL || info_ptr == NULL)
+ return;
+
+ info_ptr->scal_unit = (png_byte)unit;
+ info_ptr->scal_pixel_width = width;
+ info_ptr->scal_pixel_height = height;
+
+ info_ptr->valid |= PNG_INFO_sCAL;
+}
+#else
+#ifdef PNG_FIXED_POINT_SUPPORTED
+void PNGAPI
+png_set_sCAL_s(png_structp png_ptr, png_infop info_ptr,
+ int unit, png_charp swidth, png_charp sheight)
+{
+ png_uint_32 length;
+
+ png_debug1(1, "in %s storage function", "sCAL");
+
+ if (png_ptr == NULL || info_ptr == NULL)
+ return;
+
+ info_ptr->scal_unit = (png_byte)unit;
+
+ length = png_strlen(swidth) + 1;
+ png_debug1(3, "allocating unit for info (%u bytes)",
+ (unsigned int)length);
+ info_ptr->scal_s_width = (png_charp)png_malloc_warn(png_ptr, length);
+ if (info_ptr->scal_s_width == NULL)
+ {
+ png_warning(png_ptr,
+ "Memory allocation failed while processing sCAL.");
+ return;
+ }
+ png_memcpy(info_ptr->scal_s_width, swidth, (png_size_t)length);
+
+ length = png_strlen(sheight) + 1;
+ png_debug1(3, "allocating unit for info (%u bytes)",
+ (unsigned int)length);
+ info_ptr->scal_s_height = (png_charp)png_malloc_warn(png_ptr, length);
+ if (info_ptr->scal_s_height == NULL)
+ {
+ png_free (png_ptr, info_ptr->scal_s_width);
+ info_ptr->scal_s_width = NULL;
+ png_warning(png_ptr,
+ "Memory allocation failed while processing sCAL.");
+ return;
+ }
+ png_memcpy(info_ptr->scal_s_height, sheight, (png_size_t)length);
+ info_ptr->valid |= PNG_INFO_sCAL;
+#ifdef PNG_FREE_ME_SUPPORTED
+ info_ptr->free_me |= PNG_FREE_SCAL;
+#endif
+}
+#endif
+#endif
+#endif
+
+#ifdef PNG_pHYs_SUPPORTED
+void PNGAPI
+png_set_pHYs(png_structp png_ptr, png_infop info_ptr,
+ png_uint_32 res_x, png_uint_32 res_y, int unit_type)
+{
+ png_debug1(1, "in %s storage function", "pHYs");
+
+ if (png_ptr == NULL || info_ptr == NULL)
+ return;
+
+ info_ptr->x_pixels_per_unit = res_x;
+ info_ptr->y_pixels_per_unit = res_y;
+ info_ptr->phys_unit_type = (png_byte)unit_type;
+ info_ptr->valid |= PNG_INFO_pHYs;
+}
+#endif
+
+void PNGAPI
+png_set_PLTE(png_structp png_ptr, png_infop info_ptr,
+ png_colorp palette, int num_palette)
+{
+
+ png_debug1(1, "in %s storage function", "PLTE");
+
+ if (png_ptr == NULL || info_ptr == NULL)
+ return;
+
+ if (num_palette < 0 || num_palette > PNG_MAX_PALETTE_LENGTH)
+ {
+ if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE)
+ png_error(png_ptr, "Invalid palette length");
+ else
+ {
+ png_warning(png_ptr, "Invalid palette length");
+ return;
+ }
+ }
+
+ /* It may not actually be necessary to set png_ptr->palette here;
+ * we do it for backward compatibility with the way the png_handle_tRNS
+ * function used to do the allocation.
+ */
+#ifdef PNG_FREE_ME_SUPPORTED
+ png_free_data(png_ptr, info_ptr, PNG_FREE_PLTE, 0);
+#endif
+
+ /* Changed in libpng-1.2.1 to allocate PNG_MAX_PALETTE_LENGTH instead
+ * of num_palette entries, in case of an invalid PNG file that has
+ * too-large sample values.
+ */
+ png_ptr->palette = (png_colorp)png_calloc(png_ptr,
+ PNG_MAX_PALETTE_LENGTH * png_sizeof(png_color));
+ png_memcpy(png_ptr->palette, palette, num_palette * png_sizeof(png_color));
+ info_ptr->palette = png_ptr->palette;
+ info_ptr->num_palette = png_ptr->num_palette = (png_uint_16)num_palette;
+
+#ifdef PNG_FREE_ME_SUPPORTED
+ info_ptr->free_me |= PNG_FREE_PLTE;
+#else
+ png_ptr->flags |= PNG_FLAG_FREE_PLTE;
+#endif
+
+ info_ptr->valid |= PNG_INFO_PLTE;
+}
+
+#ifdef PNG_sBIT_SUPPORTED
+void PNGAPI
+png_set_sBIT(png_structp png_ptr, png_infop info_ptr,
+ png_color_8p sig_bit)
+{
+ png_debug1(1, "in %s storage function", "sBIT");
+
+ if (png_ptr == NULL || info_ptr == NULL)
+ return;
+
+ png_memcpy(&(info_ptr->sig_bit), sig_bit, png_sizeof(png_color_8));
+ info_ptr->valid |= PNG_INFO_sBIT;
+}
+#endif
+
+#ifdef PNG_sRGB_SUPPORTED
+void PNGAPI
+png_set_sRGB(png_structp png_ptr, png_infop info_ptr, int intent)
+{
+ png_debug1(1, "in %s storage function", "sRGB");
+
+ if (png_ptr == NULL || info_ptr == NULL)
+ return;
+
+ info_ptr->srgb_intent = (png_byte)intent;
+ info_ptr->valid |= PNG_INFO_sRGB;
+}
+
+void PNGAPI
+png_set_sRGB_gAMA_and_cHRM(png_structp png_ptr, png_infop info_ptr,
+ int intent)
+{
+#ifdef PNG_gAMA_SUPPORTED
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+ float file_gamma;
+#endif
+#ifdef PNG_FIXED_POINT_SUPPORTED
+ png_fixed_point int_file_gamma;
+#endif
+#endif
+#ifdef PNG_cHRM_SUPPORTED
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+ float white_x, white_y, red_x, red_y, green_x, green_y, blue_x, blue_y;
+#endif
+ png_fixed_point int_white_x, int_white_y, int_red_x, int_red_y, int_green_x,
+ int_green_y, int_blue_x, int_blue_y;
+#endif
+ png_debug1(1, "in %s storage function", "sRGB_gAMA_and_cHRM");
+
+ if (png_ptr == NULL || info_ptr == NULL)
+ return;
+
+ png_set_sRGB(png_ptr, info_ptr, intent);
+
+#ifdef PNG_gAMA_SUPPORTED
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+ file_gamma = (float).45455;
+ png_set_gAMA(png_ptr, info_ptr, file_gamma);
+#endif
+#ifdef PNG_FIXED_POINT_SUPPORTED
+ int_file_gamma = 45455L;
+ png_set_gAMA_fixed(png_ptr, info_ptr, int_file_gamma);
+#endif
+#endif
+
+#ifdef PNG_cHRM_SUPPORTED
+ int_white_x = 31270L;
+ int_white_y = 32900L;
+ int_red_x = 64000L;
+ int_red_y = 33000L;
+ int_green_x = 30000L;
+ int_green_y = 60000L;
+ int_blue_x = 15000L;
+ int_blue_y = 6000L;
+
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+ white_x = (float).3127;
+ white_y = (float).3290;
+ red_x = (float).64;
+ red_y = (float).33;
+ green_x = (float).30;
+ green_y = (float).60;
+ blue_x = (float).15;
+ blue_y = (float).06;
+#endif
+
+#ifdef PNG_FIXED_POINT_SUPPORTED
+ png_set_cHRM_fixed(png_ptr, info_ptr,
+ int_white_x, int_white_y, int_red_x, int_red_y, int_green_x,
+ int_green_y, int_blue_x, int_blue_y);
+#endif
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+ png_set_cHRM(png_ptr, info_ptr,
+ white_x, white_y, red_x, red_y, green_x, green_y, blue_x, blue_y);
+#endif
+#endif /* cHRM */
+}
+#endif /* sRGB */
+
+
+#ifdef PNG_iCCP_SUPPORTED
+void PNGAPI
+png_set_iCCP(png_structp png_ptr, png_infop info_ptr,
+ png_charp name, int compression_type,
+ png_charp profile, png_uint_32 proflen)
+{
+ png_charp new_iccp_name;
+ png_charp new_iccp_profile;
+ png_uint_32 length;
+
+ png_debug1(1, "in %s storage function", "iCCP");
+
+ if (png_ptr == NULL || info_ptr == NULL || name == NULL || profile == NULL)
+ return;
+
+ length = png_strlen(name)+1;
+ new_iccp_name = (png_charp)png_malloc_warn(png_ptr, length);
+ if (new_iccp_name == NULL)
+ {
+ png_warning(png_ptr, "Insufficient memory to process iCCP chunk.");
+ return;
+ }
+ png_memcpy(new_iccp_name, name, length);
+ new_iccp_profile = (png_charp)png_malloc_warn(png_ptr, proflen);
+ if (new_iccp_profile == NULL)
+ {
+ png_free (png_ptr, new_iccp_name);
+ png_warning(png_ptr,
+ "Insufficient memory to process iCCP profile.");
+ return;
+ }
+ png_memcpy(new_iccp_profile, profile, (png_size_t)proflen);
+
+ png_free_data(png_ptr, info_ptr, PNG_FREE_ICCP, 0);
+
+ info_ptr->iccp_proflen = proflen;
+ info_ptr->iccp_name = new_iccp_name;
+ info_ptr->iccp_profile = new_iccp_profile;
+ /* Compression is always zero but is here so the API and info structure
+ * does not have to change if we introduce multiple compression types
+ */
+ info_ptr->iccp_compression = (png_byte)compression_type;
+#ifdef PNG_FREE_ME_SUPPORTED
+ info_ptr->free_me |= PNG_FREE_ICCP;
+#endif
+ info_ptr->valid |= PNG_INFO_iCCP;
+}
+#endif
+
+#ifdef PNG_TEXT_SUPPORTED
+void PNGAPI
+png_set_text(png_structp png_ptr, png_infop info_ptr, png_textp text_ptr,
+ int num_text)
+{
+ int ret;
+ ret = png_set_text_2(png_ptr, info_ptr, text_ptr, num_text);
+ if (ret)
+ png_error(png_ptr, "Insufficient memory to store text");
+}
+
+int /* PRIVATE */
+png_set_text_2(png_structp png_ptr, png_infop info_ptr, png_textp text_ptr,
+ int num_text)
+{
+ int i;
+
+ png_debug1(1, "in %s storage function", ((png_ptr == NULL ||
+ png_ptr->chunk_name[0] == '\0') ?
+ "text" : (png_const_charp)png_ptr->chunk_name));
+
+ if (png_ptr == NULL || info_ptr == NULL || num_text == 0)
+ return(0);
+
+ /* Make sure we have enough space in the "text" array in info_struct
+ * to hold all of the incoming text_ptr objects.
+ */
+ if (info_ptr->num_text + num_text > info_ptr->max_text)
+ {
+ if (info_ptr->text != NULL)
+ {
+ png_textp old_text;
+ int old_max;
+
+ old_max = info_ptr->max_text;
+ info_ptr->max_text = info_ptr->num_text + num_text + 8;
+ old_text = info_ptr->text;
+ info_ptr->text = (png_textp)png_malloc_warn(png_ptr,
+ (png_uint_32)(info_ptr->max_text * png_sizeof(png_text)));
+ if (info_ptr->text == NULL)
+ {
+ png_free(png_ptr, old_text);
+ return(1);
+ }
+ png_memcpy(info_ptr->text, old_text, (png_size_t)(old_max *
+ png_sizeof(png_text)));
+ png_free(png_ptr, old_text);
+ }
+ else
+ {
+ info_ptr->max_text = num_text + 8;
+ info_ptr->num_text = 0;
+ info_ptr->text = (png_textp)png_malloc_warn(png_ptr,
+ (png_uint_32)(info_ptr->max_text * png_sizeof(png_text)));
+ if (info_ptr->text == NULL)
+ return(1);
+#ifdef PNG_FREE_ME_SUPPORTED
+ info_ptr->free_me |= PNG_FREE_TEXT;
+#endif
+ }
+ png_debug1(3, "allocated %d entries for info_ptr->text",
+ info_ptr->max_text);
+ }
+ for (i = 0; i < num_text; i++)
+ {
+ png_size_t text_length, key_len;
+ png_size_t lang_len, lang_key_len;
+ png_textp textp = &(info_ptr->text[info_ptr->num_text]);
+
+ if (text_ptr[i].key == NULL)
+ continue;
+
+ key_len = png_strlen(text_ptr[i].key);
+
+ if (text_ptr[i].compression <= 0)
+ {
+ lang_len = 0;
+ lang_key_len = 0;
+ }
+
+ else
+#ifdef PNG_iTXt_SUPPORTED
+ {
+ /* Set iTXt data */
+
+ if (text_ptr[i].lang != NULL)
+ lang_len = png_strlen(text_ptr[i].lang);
+ else
+ lang_len = 0;
+ if (text_ptr[i].lang_key != NULL)
+ lang_key_len = png_strlen(text_ptr[i].lang_key);
+ else
+ lang_key_len = 0;
+ }
+#else /* PNG_iTXt_SUPPORTED */
+ {
+ png_warning(png_ptr, "iTXt chunk not supported.");
+ continue;
+ }
+#endif
+
+ if (text_ptr[i].text == NULL || text_ptr[i].text[0] == '\0')
+ {
+ text_length = 0;
+#ifdef PNG_iTXt_SUPPORTED
+ if (text_ptr[i].compression > 0)
+ textp->compression = PNG_ITXT_COMPRESSION_NONE;
+ else
+#endif
+ textp->compression = PNG_TEXT_COMPRESSION_NONE;
+ }
+
+ else
+ {
+ text_length = png_strlen(text_ptr[i].text);
+ textp->compression = text_ptr[i].compression;
+ }
+
+ textp->key = (png_charp)png_malloc_warn(png_ptr,
+ (png_uint_32)
+ (key_len + text_length + lang_len + lang_key_len + 4));
+ if (textp->key == NULL)
+ return(1);
+ png_debug2(2, "Allocated %lu bytes at %x in png_set_text",
+ (png_uint_32)
+ (key_len + lang_len + lang_key_len + text_length + 4),
+ (int)textp->key);
+
+ png_memcpy(textp->key, text_ptr[i].key,(png_size_t)(key_len));
+ *(textp->key + key_len) = '\0';
+#ifdef PNG_iTXt_SUPPORTED
+ if (text_ptr[i].compression > 0)
+ {
+ textp->lang = textp->key + key_len + 1;
+ png_memcpy(textp->lang, text_ptr[i].lang, lang_len);
+ *(textp->lang + lang_len) = '\0';
+ textp->lang_key = textp->lang + lang_len + 1;
+ png_memcpy(textp->lang_key, text_ptr[i].lang_key, lang_key_len);
+ *(textp->lang_key + lang_key_len) = '\0';
+ textp->text = textp->lang_key + lang_key_len + 1;
+ }
+ else
+#endif
+ {
+#ifdef PNG_iTXt_SUPPORTED
+ textp->lang=NULL;
+ textp->lang_key=NULL;
+#endif
+ textp->text = textp->key + key_len + 1;
+ }
+ if (text_length)
+ png_memcpy(textp->text, text_ptr[i].text,
+ (png_size_t)(text_length));
+ *(textp->text + text_length) = '\0';
+
+#ifdef PNG_iTXt_SUPPORTED
+ if (textp->compression > 0)
+ {
+ textp->text_length = 0;
+ textp->itxt_length = text_length;
+ }
+ else
+#endif
+
+ {
+ textp->text_length = text_length;
+#ifdef PNG_iTXt_SUPPORTED
+ textp->itxt_length = 0;
+#endif
+ }
+ info_ptr->num_text++;
+ png_debug1(3, "transferred text chunk %d", info_ptr->num_text);
+ }
+ return(0);
+}
+#endif
+
+#ifdef PNG_tIME_SUPPORTED
+void PNGAPI
+png_set_tIME(png_structp png_ptr, png_infop info_ptr, png_timep mod_time)
+{
+ png_debug1(1, "in %s storage function", "tIME");
+
+ if (png_ptr == NULL || info_ptr == NULL ||
+ (png_ptr->mode & PNG_WROTE_tIME))
+ return;
+
+ png_memcpy(&(info_ptr->mod_time), mod_time, png_sizeof(png_time));
+ info_ptr->valid |= PNG_INFO_tIME;
+}
+#endif
+
+#ifdef PNG_tRNS_SUPPORTED
+void PNGAPI
+png_set_tRNS(png_structp png_ptr, png_infop info_ptr,
+ png_bytep trans, int num_trans, png_color_16p trans_values)
+{
+ png_debug1(1, "in %s storage function", "tRNS");
+
+ if (png_ptr == NULL || info_ptr == NULL)
+ return;
+
+ if (trans != NULL)
+ {
+ /* It may not actually be necessary to set png_ptr->trans here;
+ * we do it for backward compatibility with the way the png_handle_tRNS
+ * function used to do the allocation.
+ */
+
+#ifdef PNG_FREE_ME_SUPPORTED
+ png_free_data(png_ptr, info_ptr, PNG_FREE_TRNS, 0);
+#endif
+
+ /* Changed from num_trans to PNG_MAX_PALETTE_LENGTH in version 1.2.1 */
+ png_ptr->trans = info_ptr->trans = (png_bytep)png_malloc(png_ptr,
+ (png_uint_32)PNG_MAX_PALETTE_LENGTH);
+ if (num_trans > 0 && num_trans <= PNG_MAX_PALETTE_LENGTH)
+ png_memcpy(info_ptr->trans, trans, (png_size_t)num_trans);
+ }
+
+ if (trans_values != NULL)
+ {
+ int sample_max = (1 << info_ptr->bit_depth);
+ if ((info_ptr->color_type == PNG_COLOR_TYPE_GRAY &&
+ (int)trans_values->gray > sample_max) ||
+ (info_ptr->color_type == PNG_COLOR_TYPE_RGB &&
+ ((int)trans_values->red > sample_max ||
+ (int)trans_values->green > sample_max ||
+ (int)trans_values->blue > sample_max)))
+ png_warning(png_ptr,
+ "tRNS chunk has out-of-range samples for bit_depth");
+ png_memcpy(&(info_ptr->trans_values), trans_values,
+ png_sizeof(png_color_16));
+ if (num_trans == 0)
+ num_trans = 1;
+ }
+
+ info_ptr->num_trans = (png_uint_16)num_trans;
+ if (num_trans != 0)
+ {
+ info_ptr->valid |= PNG_INFO_tRNS;
+#ifdef PNG_FREE_ME_SUPPORTED
+ info_ptr->free_me |= PNG_FREE_TRNS;
+#else
+ png_ptr->flags |= PNG_FLAG_FREE_TRNS;
+#endif
+ }
+}
+#endif
+
+#ifdef PNG_sPLT_SUPPORTED
+void PNGAPI
+png_set_sPLT(png_structp png_ptr,
+ png_infop info_ptr, png_sPLT_tp entries, int nentries)
+/*
+ * entries - array of png_sPLT_t structures
+ * to be added to the list of palettes
+ * in the info structure.
+ * nentries - number of palette structures to be
+ * added.
+ */
+{
+ png_sPLT_tp np;
+ int i;
+
+ if (png_ptr == NULL || info_ptr == NULL)
+ return;
+
+ np = (png_sPLT_tp)png_malloc_warn(png_ptr,
+ (info_ptr->splt_palettes_num + nentries) *
+ (png_uint_32)png_sizeof(png_sPLT_t));
+ if (np == NULL)
+ {
+ png_warning(png_ptr, "No memory for sPLT palettes.");
+ return;
+ }
+
+ png_memcpy(np, info_ptr->splt_palettes,
+ info_ptr->splt_palettes_num * png_sizeof(png_sPLT_t));
+ png_free(png_ptr, info_ptr->splt_palettes);
+ info_ptr->splt_palettes=NULL;
+
+ for (i = 0; i < nentries; i++)
+ {
+ png_sPLT_tp to = np + info_ptr->splt_palettes_num + i;
+ png_sPLT_tp from = entries + i;
+ png_uint_32 length;
+
+ length = png_strlen(from->name) + 1;
+ to->name = (png_charp)png_malloc_warn(png_ptr, length);
+ if (to->name == NULL)
+ {
+ png_warning(png_ptr,
+ "Out of memory while processing sPLT chunk");
+ continue;
+ }
+ png_memcpy(to->name, from->name, length);
+ to->entries = (png_sPLT_entryp)png_malloc_warn(png_ptr,
+ (png_uint_32)(from->nentries * png_sizeof(png_sPLT_entry)));
+ if (to->entries == NULL)
+ {
+ png_warning(png_ptr,
+ "Out of memory while processing sPLT chunk");
+ png_free(png_ptr, to->name);
+ to->name = NULL;
+ continue;
+ }
+ png_memcpy(to->entries, from->entries,
+ from->nentries * png_sizeof(png_sPLT_entry));
+ to->nentries = from->nentries;
+ to->depth = from->depth;
+ }
+
+ info_ptr->splt_palettes = np;
+ info_ptr->splt_palettes_num += nentries;
+ info_ptr->valid |= PNG_INFO_sPLT;
+#ifdef PNG_FREE_ME_SUPPORTED
+ info_ptr->free_me |= PNG_FREE_SPLT;
+#endif
+}
+#endif /* PNG_sPLT_SUPPORTED */
+
+#ifdef PNG_UNKNOWN_CHUNKS_SUPPORTED
+void PNGAPI
+png_set_unknown_chunks(png_structp png_ptr,
+ png_infop info_ptr, png_unknown_chunkp unknowns, int num_unknowns)
+{
+ png_unknown_chunkp np;
+ int i;
+
+ if (png_ptr == NULL || info_ptr == NULL || num_unknowns == 0)
+ return;
+
+ np = (png_unknown_chunkp)png_malloc_warn(png_ptr,
+ (png_uint_32)((info_ptr->unknown_chunks_num + num_unknowns) *
+ png_sizeof(png_unknown_chunk)));
+ if (np == NULL)
+ {
+ png_warning(png_ptr,
+ "Out of memory while processing unknown chunk.");
+ return;
+ }
+
+ png_memcpy(np, info_ptr->unknown_chunks,
+ info_ptr->unknown_chunks_num * png_sizeof(png_unknown_chunk));
+ png_free(png_ptr, info_ptr->unknown_chunks);
+ info_ptr->unknown_chunks = NULL;
+
+ for (i = 0; i < num_unknowns; i++)
+ {
+ png_unknown_chunkp to = np + info_ptr->unknown_chunks_num + i;
+ png_unknown_chunkp from = unknowns + i;
+
+ png_memcpy((png_charp)to->name, (png_charp)from->name,
+ png_sizeof(from->name));
+ to->name[png_sizeof(to->name)-1] = '\0';
+ to->size = from->size;
+ /* Note our location in the read or write sequence */
+ to->location = (png_byte)(png_ptr->mode & 0xff);
+
+ if (from->size == 0)
+ to->data=NULL;
+ else
+ {
+ to->data = (png_bytep)png_malloc_warn(png_ptr,
+ (png_uint_32)from->size);
+ if (to->data == NULL)
+ {
+ png_warning(png_ptr,
+ "Out of memory while processing unknown chunk.");
+ to->size = 0;
+ }
+ else
+ png_memcpy(to->data, from->data, from->size);
+ }
+ }
+
+ info_ptr->unknown_chunks = np;
+ info_ptr->unknown_chunks_num += num_unknowns;
+#ifdef PNG_FREE_ME_SUPPORTED
+ info_ptr->free_me |= PNG_FREE_UNKN;
+#endif
+}
+void PNGAPI
+png_set_unknown_chunk_location(png_structp png_ptr, png_infop info_ptr,
+ int chunk, int location)
+{
+ if (png_ptr != NULL && info_ptr != NULL && chunk >= 0 && chunk <
+ (int)info_ptr->unknown_chunks_num)
+ info_ptr->unknown_chunks[chunk].location = (png_byte)location;
+}
+#endif
+
+#if defined(PNG_1_0_X) || defined(PNG_1_2_X)
+#if defined(PNG_READ_EMPTY_PLTE_SUPPORTED) || \
+ defined(PNG_WRITE_EMPTY_PLTE_SUPPORTED)
+void PNGAPI
+png_permit_empty_plte (png_structp png_ptr, int empty_plte_permitted)
+{
+ /* This function is deprecated in favor of png_permit_mng_features()
+ and will be removed from libpng-1.3.0 */
+
+ png_debug(1, "in png_permit_empty_plte, DEPRECATED.");
+
+ if (png_ptr == NULL)
+ return;
+ png_ptr->mng_features_permitted = (png_byte)
+ ((png_ptr->mng_features_permitted & (~PNG_FLAG_MNG_EMPTY_PLTE)) |
+ ((empty_plte_permitted & PNG_FLAG_MNG_EMPTY_PLTE)));
+}
+#endif
+#endif
+
+#ifdef PNG_MNG_FEATURES_SUPPORTED
+png_uint_32 PNGAPI
+png_permit_mng_features (png_structp png_ptr, png_uint_32 mng_features)
+{
+ png_debug(1, "in png_permit_mng_features");
+
+ if (png_ptr == NULL)
+ return (png_uint_32)0;
+ png_ptr->mng_features_permitted =
+ (png_byte)(mng_features & PNG_ALL_MNG_FEATURES);
+ return (png_uint_32)png_ptr->mng_features_permitted;
+}
+#endif
+
+#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
+void PNGAPI
+png_set_keep_unknown_chunks(png_structp png_ptr, int keep, png_bytep
+ chunk_list, int num_chunks)
+{
+ png_bytep new_list, p;
+ int i, old_num_chunks;
+ if (png_ptr == NULL)
+ return;
+ if (num_chunks == 0)
+ {
+ if (keep == PNG_HANDLE_CHUNK_ALWAYS || keep == PNG_HANDLE_CHUNK_IF_SAFE)
+ png_ptr->flags |= PNG_FLAG_KEEP_UNKNOWN_CHUNKS;
+ else
+ png_ptr->flags &= ~PNG_FLAG_KEEP_UNKNOWN_CHUNKS;
+
+ if (keep == PNG_HANDLE_CHUNK_ALWAYS)
+ png_ptr->flags |= PNG_FLAG_KEEP_UNSAFE_CHUNKS;
+ else
+ png_ptr->flags &= ~PNG_FLAG_KEEP_UNSAFE_CHUNKS;
+ return;
+ }
+ if (chunk_list == NULL)
+ return;
+ old_num_chunks = png_ptr->num_chunk_list;
+ new_list=(png_bytep)png_malloc(png_ptr,
+ (png_uint_32)
+ (5*(num_chunks + old_num_chunks)));
+ if (png_ptr->chunk_list != NULL)
+ {
+ png_memcpy(new_list, png_ptr->chunk_list,
+ (png_size_t)(5*old_num_chunks));
+ png_free(png_ptr, png_ptr->chunk_list);
+ png_ptr->chunk_list=NULL;
+ }
+ png_memcpy(new_list + 5*old_num_chunks, chunk_list,
+ (png_size_t)(5*num_chunks));
+ for (p = new_list + 5*old_num_chunks + 4, i = 0; i<num_chunks; i++, p += 5)
+ *p=(png_byte)keep;
+ png_ptr->num_chunk_list = old_num_chunks + num_chunks;
+ png_ptr->chunk_list = new_list;
+#ifdef PNG_FREE_ME_SUPPORTED
+ png_ptr->free_me |= PNG_FREE_LIST;
+#endif
+}
+#endif
+
+#ifdef PNG_READ_USER_CHUNKS_SUPPORTED
+void PNGAPI
+png_set_read_user_chunk_fn(png_structp png_ptr, png_voidp user_chunk_ptr,
+ png_user_chunk_ptr read_user_chunk_fn)
+{
+ png_debug(1, "in png_set_read_user_chunk_fn");
+
+ if (png_ptr == NULL)
+ return;
+
+ png_ptr->read_user_chunk_fn = read_user_chunk_fn;
+ png_ptr->user_chunk_ptr = user_chunk_ptr;
+}
+#endif
+
+#ifdef PNG_INFO_IMAGE_SUPPORTED
+void PNGAPI
+png_set_rows(png_structp png_ptr, png_infop info_ptr, png_bytepp row_pointers)
+{
+ png_debug1(1, "in %s storage function", "rows");
+
+ if (png_ptr == NULL || info_ptr == NULL)
+ return;
+
+ if (info_ptr->row_pointers && (info_ptr->row_pointers != row_pointers))
+ png_free_data(png_ptr, info_ptr, PNG_FREE_ROWS, 0);
+ info_ptr->row_pointers = row_pointers;
+ if (row_pointers)
+ info_ptr->valid |= PNG_INFO_IDAT;
+}
+#endif
+
+void PNGAPI
+png_set_compression_buffer_size(png_structp png_ptr,
+ png_uint_32 size)
+{
+ if (png_ptr == NULL)
+ return;
+ png_free(png_ptr, png_ptr->zbuf);
+ png_ptr->zbuf_size = (png_size_t)size;
+ png_ptr->zbuf = (png_bytep)png_malloc(png_ptr, size);
+ png_ptr->zstream.next_out = png_ptr->zbuf;
+ png_ptr->zstream.avail_out = (uInt)png_ptr->zbuf_size;
+}
+
+void PNGAPI
+png_set_invalid(png_structp png_ptr, png_infop info_ptr, int mask)
+{
+ if (png_ptr && info_ptr)
+ info_ptr->valid &= ~mask;
+}
+
+
+#ifndef PNG_1_0_X
+#ifdef PNG_ASSEMBLER_CODE_SUPPORTED
+/* Function was added to libpng 1.2.0 and should always exist by default */
+void PNGAPI
+png_set_asm_flags (png_structp png_ptr, png_uint_32 asm_flags)
+{
+/* Obsolete as of libpng-1.2.20 and will be removed from libpng-1.4.0 */
+ if (png_ptr != NULL)
+ png_ptr->asm_flags = 0;
+ asm_flags = asm_flags; /* Quiet the compiler */
+}
+
+/* This function was added to libpng 1.2.0 */
+void PNGAPI
+png_set_mmx_thresholds (png_structp png_ptr,
+ png_byte mmx_bitdepth_threshold,
+ png_uint_32 mmx_rowbytes_threshold)
+{
+/* Obsolete as of libpng-1.2.20 and will be removed from libpng-1.4.0 */
+ if (png_ptr == NULL)
+ return;
+ /* Quiet the compiler */
+ mmx_bitdepth_threshold = mmx_bitdepth_threshold;
+ mmx_rowbytes_threshold = mmx_rowbytes_threshold;
+}
+#endif /* ?PNG_ASSEMBLER_CODE_SUPPORTED */
+
+#ifdef PNG_SET_USER_LIMITS_SUPPORTED
+/* This function was added to libpng 1.2.6 */
+void PNGAPI
+png_set_user_limits (png_structp png_ptr, png_uint_32 user_width_max,
+ png_uint_32 user_height_max)
+{
+ /* Images with dimensions larger than these limits will be
+ * rejected by png_set_IHDR(). To accept any PNG datastream
+ * regardless of dimensions, set both limits to 0x7ffffffL.
+ */
+ if (png_ptr == NULL)
+ return;
+ png_ptr->user_width_max = user_width_max;
+ png_ptr->user_height_max = user_height_max;
+}
+#endif /* ?PNG_SET_USER_LIMITS_SUPPORTED */
+
+
+#ifdef PNG_BENIGN_ERRORS_SUPPORTED
+void PNGAPI
+png_set_benign_errors(png_structp png_ptr, int allowed)
+{
+ png_debug(1, "in png_set_benign_errors");
+
+ if (allowed)
+ png_ptr->flags |= PNG_FLAG_BENIGN_ERRORS_WARN;
+ else
+ png_ptr->flags &= ~PNG_FLAG_BENIGN_ERRORS_WARN;
+}
+#endif /* PNG_BENIGN_ERRORS_SUPPORTED */
+#endif /* ?PNG_1_0_X */
+#endif /* PNG_READ_SUPPORTED || PNG_WRITE_SUPPORTED */
diff --git a/trunk/src/third_party/libpng/pngtrans.c b/trunk/src/third_party/libpng/pngtrans.c
new file mode 100644
index 0000000..6ad9dcf
--- /dev/null
+++ b/trunk/src/third_party/libpng/pngtrans.c
@@ -0,0 +1,699 @@
+
+/* pngtrans.c - transforms the data in a row (used by both readers and writers)
+ *
+ * Last changed in libpng 1.2.41 [December 3, 2009]
+ * Copyright (c) 1998-2009 Glenn Randers-Pehrson
+ * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger)
+ * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.)
+ *
+ * This code is released under the libpng license.
+ * For conditions of distribution and use, see the disclaimer
+ * and license in png.h
+ */
+
+#define PNG_INTERNAL
+#define PNG_NO_PEDANTIC_WARNINGS
+#include "png.h"
+#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED)
+
+#if defined(PNG_READ_BGR_SUPPORTED) || defined(PNG_WRITE_BGR_SUPPORTED)
+/* Turn on BGR-to-RGB mapping */
+void PNGAPI
+png_set_bgr(png_structp png_ptr)
+{
+ png_debug(1, "in png_set_bgr");
+
+ if (png_ptr == NULL)
+ return;
+ png_ptr->transformations |= PNG_BGR;
+}
+#endif
+
+#if defined(PNG_READ_SWAP_SUPPORTED) || defined(PNG_WRITE_SWAP_SUPPORTED)
+/* Turn on 16 bit byte swapping */
+void PNGAPI
+png_set_swap(png_structp png_ptr)
+{
+ png_debug(1, "in png_set_swap");
+
+ if (png_ptr == NULL)
+ return;
+ if (png_ptr->bit_depth == 16)
+ png_ptr->transformations |= PNG_SWAP_BYTES;
+}
+#endif
+
+#if defined(PNG_READ_PACK_SUPPORTED) || defined(PNG_WRITE_PACK_SUPPORTED)
+/* Turn on pixel packing */
+void PNGAPI
+png_set_packing(png_structp png_ptr)
+{
+ png_debug(1, "in png_set_packing");
+
+ if (png_ptr == NULL)
+ return;
+ if (png_ptr->bit_depth < 8)
+ {
+ png_ptr->transformations |= PNG_PACK;
+ png_ptr->usr_bit_depth = 8;
+ }
+}
+#endif
+
+#if defined(PNG_READ_PACKSWAP_SUPPORTED)||defined(PNG_WRITE_PACKSWAP_SUPPORTED)
+/* Turn on packed pixel swapping */
+void PNGAPI
+png_set_packswap(png_structp png_ptr)
+{
+ png_debug(1, "in png_set_packswap");
+
+ if (png_ptr == NULL)
+ return;
+ if (png_ptr->bit_depth < 8)
+ png_ptr->transformations |= PNG_PACKSWAP;
+}
+#endif
+
+#if defined(PNG_READ_SHIFT_SUPPORTED) || defined(PNG_WRITE_SHIFT_SUPPORTED)
+void PNGAPI
+png_set_shift(png_structp png_ptr, png_color_8p true_bits)
+{
+ png_debug(1, "in png_set_shift");
+
+ if (png_ptr == NULL)
+ return;
+ png_ptr->transformations |= PNG_SHIFT;
+ png_ptr->shift = *true_bits;
+}
+#endif
+
+#if defined(PNG_READ_INTERLACING_SUPPORTED) || \
+ defined(PNG_WRITE_INTERLACING_SUPPORTED)
+int PNGAPI
+png_set_interlace_handling(png_structp png_ptr)
+{
+ png_debug(1, "in png_set_interlace handling");
+
+ if (png_ptr && png_ptr->interlaced)
+ {
+ png_ptr->transformations |= PNG_INTERLACE;
+ return (7);
+ }
+
+ return (1);
+}
+#endif
+
+#if defined(PNG_READ_FILLER_SUPPORTED) || defined(PNG_WRITE_FILLER_SUPPORTED)
+/* Add a filler byte on read, or remove a filler or alpha byte on write.
+ * The filler type has changed in v0.95 to allow future 2-byte fillers
+ * for 48-bit input data, as well as to avoid problems with some compilers
+ * that don't like bytes as parameters.
+ */
+void PNGAPI
+png_set_filler(png_structp png_ptr, png_uint_32 filler, int filler_loc)
+{
+ png_debug(1, "in png_set_filler");
+
+ if (png_ptr == NULL)
+ return;
+ png_ptr->transformations |= PNG_FILLER;
+#ifdef PNG_LEGACY_SUPPORTED
+ png_ptr->filler = (png_byte)filler;
+#else
+ png_ptr->filler = (png_uint_16)filler;
+#endif
+ if (filler_loc == PNG_FILLER_AFTER)
+ png_ptr->flags |= PNG_FLAG_FILLER_AFTER;
+ else
+ png_ptr->flags &= ~PNG_FLAG_FILLER_AFTER;
+
+ /* This should probably go in the "do_read_filler" routine.
+ * I attempted to do that in libpng-1.0.1a but that caused problems
+ * so I restored it in libpng-1.0.2a
+ */
+
+ if (png_ptr->color_type == PNG_COLOR_TYPE_RGB)
+ {
+ png_ptr->usr_channels = 4;
+ }
+
+ /* Also I added this in libpng-1.0.2a (what happens when we expand
+ * a less-than-8-bit grayscale to GA? */
+
+ if (png_ptr->color_type == PNG_COLOR_TYPE_GRAY && png_ptr->bit_depth >= 8)
+ {
+ png_ptr->usr_channels = 2;
+ }
+}
+
+#ifndef PNG_1_0_X
+/* Added to libpng-1.2.7 */
+void PNGAPI
+png_set_add_alpha(png_structp png_ptr, png_uint_32 filler, int filler_loc)
+{
+ png_debug(1, "in png_set_add_alpha");
+
+ if (png_ptr == NULL)
+ return;
+ png_set_filler(png_ptr, filler, filler_loc);
+ png_ptr->transformations |= PNG_ADD_ALPHA;
+}
+#endif
+
+#endif
+
+#if defined(PNG_READ_SWAP_ALPHA_SUPPORTED) || \
+ defined(PNG_WRITE_SWAP_ALPHA_SUPPORTED)
+void PNGAPI
+png_set_swap_alpha(png_structp png_ptr)
+{
+ png_debug(1, "in png_set_swap_alpha");
+
+ if (png_ptr == NULL)
+ return;
+ png_ptr->transformations |= PNG_SWAP_ALPHA;
+}
+#endif
+
+#if defined(PNG_READ_INVERT_ALPHA_SUPPORTED) || \
+ defined(PNG_WRITE_INVERT_ALPHA_SUPPORTED)
+void PNGAPI
+png_set_invert_alpha(png_structp png_ptr)
+{
+ png_debug(1, "in png_set_invert_alpha");
+
+ if (png_ptr == NULL)
+ return;
+ png_ptr->transformations |= PNG_INVERT_ALPHA;
+}
+#endif
+
+#if defined(PNG_READ_INVERT_SUPPORTED) || defined(PNG_WRITE_INVERT_SUPPORTED)
+void PNGAPI
+png_set_invert_mono(png_structp png_ptr)
+{
+ png_debug(1, "in png_set_invert_mono");
+
+ if (png_ptr == NULL)
+ return;
+ png_ptr->transformations |= PNG_INVERT_MONO;
+}
+
+/* Invert monochrome grayscale data */
+void /* PRIVATE */
+png_do_invert(png_row_infop row_info, png_bytep row)
+{
+ png_debug(1, "in png_do_invert");
+
+ /* This test removed from libpng version 1.0.13 and 1.2.0:
+ * if (row_info->bit_depth == 1 &&
+ */
+#ifdef PNG_USELESS_TESTS_SUPPORTED
+ if (row == NULL || row_info == NULL)
+ return;
+#endif
+ if (row_info->color_type == PNG_COLOR_TYPE_GRAY)
+ {
+ png_bytep rp = row;
+ png_uint_32 i;
+ png_uint_32 istop = row_info->rowbytes;
+
+ for (i = 0; i < istop; i++)
+ {
+ *rp = (png_byte)(~(*rp));
+ rp++;
+ }
+ }
+ else if (row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA &&
+ row_info->bit_depth == 8)
+ {
+ png_bytep rp = row;
+ png_uint_32 i;
+ png_uint_32 istop = row_info->rowbytes;
+
+ for (i = 0; i < istop; i+=2)
+ {
+ *rp = (png_byte)(~(*rp));
+ rp+=2;
+ }
+ }
+ else if (row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA &&
+ row_info->bit_depth == 16)
+ {
+ png_bytep rp = row;
+ png_uint_32 i;
+ png_uint_32 istop = row_info->rowbytes;
+
+ for (i = 0; i < istop; i+=4)
+ {
+ *rp = (png_byte)(~(*rp));
+ *(rp+1) = (png_byte)(~(*(rp+1)));
+ rp+=4;
+ }
+ }
+}
+#endif
+
+#if defined(PNG_READ_SWAP_SUPPORTED) || defined(PNG_WRITE_SWAP_SUPPORTED)
+/* Swaps byte order on 16 bit depth images */
+void /* PRIVATE */
+png_do_swap(png_row_infop row_info, png_bytep row)
+{
+ png_debug(1, "in png_do_swap");
+
+ if (
+#ifdef PNG_USELESS_TESTS_SUPPORTED
+ row != NULL && row_info != NULL &&
+#endif
+ row_info->bit_depth == 16)
+ {
+ png_bytep rp = row;
+ png_uint_32 i;
+ png_uint_32 istop= row_info->width * row_info->channels;
+
+ for (i = 0; i < istop; i++, rp += 2)
+ {
+ png_byte t = *rp;
+ *rp = *(rp + 1);
+ *(rp + 1) = t;
+ }
+ }
+}
+#endif
+
+#if defined(PNG_READ_PACKSWAP_SUPPORTED)||defined(PNG_WRITE_PACKSWAP_SUPPORTED)
+static PNG_CONST png_byte onebppswaptable[256] = {
+ 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0,
+ 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
+ 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8,
+ 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
+ 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4,
+ 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4,
+ 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC,
+ 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC,
+ 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2,
+ 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2,
+ 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA,
+ 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
+ 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6,
+ 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6,
+ 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE,
+ 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
+ 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1,
+ 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
+ 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9,
+ 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9,
+ 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5,
+ 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
+ 0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED,
+ 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
+ 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3,
+ 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3,
+ 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB,
+ 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
+ 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7,
+ 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
+ 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF,
+ 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
+};
+
+static PNG_CONST png_byte twobppswaptable[256] = {
+ 0x00, 0x40, 0x80, 0xC0, 0x10, 0x50, 0x90, 0xD0,
+ 0x20, 0x60, 0xA0, 0xE0, 0x30, 0x70, 0xB0, 0xF0,
+ 0x04, 0x44, 0x84, 0xC4, 0x14, 0x54, 0x94, 0xD4,
+ 0x24, 0x64, 0xA4, 0xE4, 0x34, 0x74, 0xB4, 0xF4,
+ 0x08, 0x48, 0x88, 0xC8, 0x18, 0x58, 0x98, 0xD8,
+ 0x28, 0x68, 0xA8, 0xE8, 0x38, 0x78, 0xB8, 0xF8,
+ 0x0C, 0x4C, 0x8C, 0xCC, 0x1C, 0x5C, 0x9C, 0xDC,
+ 0x2C, 0x6C, 0xAC, 0xEC, 0x3C, 0x7C, 0xBC, 0xFC,
+ 0x01, 0x41, 0x81, 0xC1, 0x11, 0x51, 0x91, 0xD1,
+ 0x21, 0x61, 0xA1, 0xE1, 0x31, 0x71, 0xB1, 0xF1,
+ 0x05, 0x45, 0x85, 0xC5, 0x15, 0x55, 0x95, 0xD5,
+ 0x25, 0x65, 0xA5, 0xE5, 0x35, 0x75, 0xB5, 0xF5,
+ 0x09, 0x49, 0x89, 0xC9, 0x19, 0x59, 0x99, 0xD9,
+ 0x29, 0x69, 0xA9, 0xE9, 0x39, 0x79, 0xB9, 0xF9,
+ 0x0D, 0x4D, 0x8D, 0xCD, 0x1D, 0x5D, 0x9D, 0xDD,
+ 0x2D, 0x6D, 0xAD, 0xED, 0x3D, 0x7D, 0xBD, 0xFD,
+ 0x02, 0x42, 0x82, 0xC2, 0x12, 0x52, 0x92, 0xD2,
+ 0x22, 0x62, 0xA2, 0xE2, 0x32, 0x72, 0xB2, 0xF2,
+ 0x06, 0x46, 0x86, 0xC6, 0x16, 0x56, 0x96, 0xD6,
+ 0x26, 0x66, 0xA6, 0xE6, 0x36, 0x76, 0xB6, 0xF6,
+ 0x0A, 0x4A, 0x8A, 0xCA, 0x1A, 0x5A, 0x9A, 0xDA,
+ 0x2A, 0x6A, 0xAA, 0xEA, 0x3A, 0x7A, 0xBA, 0xFA,
+ 0x0E, 0x4E, 0x8E, 0xCE, 0x1E, 0x5E, 0x9E, 0xDE,
+ 0x2E, 0x6E, 0xAE, 0xEE, 0x3E, 0x7E, 0xBE, 0xFE,
+ 0x03, 0x43, 0x83, 0xC3, 0x13, 0x53, 0x93, 0xD3,
+ 0x23, 0x63, 0xA3, 0xE3, 0x33, 0x73, 0xB3, 0xF3,
+ 0x07, 0x47, 0x87, 0xC7, 0x17, 0x57, 0x97, 0xD7,
+ 0x27, 0x67, 0xA7, 0xE7, 0x37, 0x77, 0xB7, 0xF7,
+ 0x0B, 0x4B, 0x8B, 0xCB, 0x1B, 0x5B, 0x9B, 0xDB,
+ 0x2B, 0x6B, 0xAB, 0xEB, 0x3B, 0x7B, 0xBB, 0xFB,
+ 0x0F, 0x4F, 0x8F, 0xCF, 0x1F, 0x5F, 0x9F, 0xDF,
+ 0x2F, 0x6F, 0xAF, 0xEF, 0x3F, 0x7F, 0xBF, 0xFF
+};
+
+static PNG_CONST png_byte fourbppswaptable[256] = {
+ 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70,
+ 0x80, 0x90, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0,
+ 0x01, 0x11, 0x21, 0x31, 0x41, 0x51, 0x61, 0x71,
+ 0x81, 0x91, 0xA1, 0xB1, 0xC1, 0xD1, 0xE1, 0xF1,
+ 0x02, 0x12, 0x22, 0x32, 0x42, 0x52, 0x62, 0x72,
+ 0x82, 0x92, 0xA2, 0xB2, 0xC2, 0xD2, 0xE2, 0xF2,
+ 0x03, 0x13, 0x23, 0x33, 0x43, 0x53, 0x63, 0x73,
+ 0x83, 0x93, 0xA3, 0xB3, 0xC3, 0xD3, 0xE3, 0xF3,
+ 0x04, 0x14, 0x24, 0x34, 0x44, 0x54, 0x64, 0x74,
+ 0x84, 0x94, 0xA4, 0xB4, 0xC4, 0xD4, 0xE4, 0xF4,
+ 0x05, 0x15, 0x25, 0x35, 0x45, 0x55, 0x65, 0x75,
+ 0x85, 0x95, 0xA5, 0xB5, 0xC5, 0xD5, 0xE5, 0xF5,
+ 0x06, 0x16, 0x26, 0x36, 0x46, 0x56, 0x66, 0x76,
+ 0x86, 0x96, 0xA6, 0xB6, 0xC6, 0xD6, 0xE6, 0xF6,
+ 0x07, 0x17, 0x27, 0x37, 0x47, 0x57, 0x67, 0x77,
+ 0x87, 0x97, 0xA7, 0xB7, 0xC7, 0xD7, 0xE7, 0xF7,
+ 0x08, 0x18, 0x28, 0x38, 0x48, 0x58, 0x68, 0x78,
+ 0x88, 0x98, 0xA8, 0xB8, 0xC8, 0xD8, 0xE8, 0xF8,
+ 0x09, 0x19, 0x29, 0x39, 0x49, 0x59, 0x69, 0x79,
+ 0x89, 0x99, 0xA9, 0xB9, 0xC9, 0xD9, 0xE9, 0xF9,
+ 0x0A, 0x1A, 0x2A, 0x3A, 0x4A, 0x5A, 0x6A, 0x7A,
+ 0x8A, 0x9A, 0xAA, 0xBA, 0xCA, 0xDA, 0xEA, 0xFA,
+ 0x0B, 0x1B, 0x2B, 0x3B, 0x4B, 0x5B, 0x6B, 0x7B,
+ 0x8B, 0x9B, 0xAB, 0xBB, 0xCB, 0xDB, 0xEB, 0xFB,
+ 0x0C, 0x1C, 0x2C, 0x3C, 0x4C, 0x5C, 0x6C, 0x7C,
+ 0x8C, 0x9C, 0xAC, 0xBC, 0xCC, 0xDC, 0xEC, 0xFC,
+ 0x0D, 0x1D, 0x2D, 0x3D, 0x4D, 0x5D, 0x6D, 0x7D,
+ 0x8D, 0x9D, 0xAD, 0xBD, 0xCD, 0xDD, 0xED, 0xFD,
+ 0x0E, 0x1E, 0x2E, 0x3E, 0x4E, 0x5E, 0x6E, 0x7E,
+ 0x8E, 0x9E, 0xAE, 0xBE, 0xCE, 0xDE, 0xEE, 0xFE,
+ 0x0F, 0x1F, 0x2F, 0x3F, 0x4F, 0x5F, 0x6F, 0x7F,
+ 0x8F, 0x9F, 0xAF, 0xBF, 0xCF, 0xDF, 0xEF, 0xFF
+};
+
+/* Swaps pixel packing order within bytes */
+void /* PRIVATE */
+png_do_packswap(png_row_infop row_info, png_bytep row)
+{
+ png_debug(1, "in png_do_packswap");
+
+ if (
+#ifdef PNG_USELESS_TESTS_SUPPORTED
+ row != NULL && row_info != NULL &&
+#endif
+ row_info->bit_depth < 8)
+ {
+ png_bytep rp, end, table;
+
+ end = row + row_info->rowbytes;
+
+ if (row_info->bit_depth == 1)
+ table = (png_bytep)onebppswaptable;
+ else if (row_info->bit_depth == 2)
+ table = (png_bytep)twobppswaptable;
+ else if (row_info->bit_depth == 4)
+ table = (png_bytep)fourbppswaptable;
+ else
+ return;
+
+ for (rp = row; rp < end; rp++)
+ *rp = table[*rp];
+ }
+}
+#endif /* PNG_READ_PACKSWAP_SUPPORTED or PNG_WRITE_PACKSWAP_SUPPORTED */
+
+#if defined(PNG_WRITE_FILLER_SUPPORTED) || \
+ defined(PNG_READ_STRIP_ALPHA_SUPPORTED)
+/* Remove filler or alpha byte(s) */
+void /* PRIVATE */
+png_do_strip_filler(png_row_infop row_info, png_bytep row, png_uint_32 flags)
+{
+ png_debug(1, "in png_do_strip_filler");
+
+#ifdef PNG_USELESS_TESTS_SUPPORTED
+ if (row != NULL && row_info != NULL)
+#endif
+ {
+ png_bytep sp=row;
+ png_bytep dp=row;
+ png_uint_32 row_width=row_info->width;
+ png_uint_32 i;
+
+ if ((row_info->color_type == PNG_COLOR_TYPE_RGB ||
+ (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA &&
+ (flags & PNG_FLAG_STRIP_ALPHA))) &&
+ row_info->channels == 4)
+ {
+ if (row_info->bit_depth == 8)
+ {
+ /* This converts from RGBX or RGBA to RGB */
+ if (flags & PNG_FLAG_FILLER_AFTER)
+ {
+ dp+=3; sp+=4;
+ for (i = 1; i < row_width; i++)
+ {
+ *dp++ = *sp++;
+ *dp++ = *sp++;
+ *dp++ = *sp++;
+ sp++;
+ }
+ }
+ /* This converts from XRGB or ARGB to RGB */
+ else
+ {
+ for (i = 0; i < row_width; i++)
+ {
+ sp++;
+ *dp++ = *sp++;
+ *dp++ = *sp++;
+ *dp++ = *sp++;
+ }
+ }
+ row_info->pixel_depth = 24;
+ row_info->rowbytes = row_width * 3;
+ }
+ else /* if (row_info->bit_depth == 16) */
+ {
+ if (flags & PNG_FLAG_FILLER_AFTER)
+ {
+ /* This converts from RRGGBBXX or RRGGBBAA to RRGGBB */
+ sp += 8; dp += 6;
+ for (i = 1; i < row_width; i++)
+ {
+ /* This could be (although png_memcpy is probably slower):
+ png_memcpy(dp, sp, 6);
+ sp += 8;
+ dp += 6;
+ */
+
+ *dp++ = *sp++;
+ *dp++ = *sp++;
+ *dp++ = *sp++;
+ *dp++ = *sp++;
+ *dp++ = *sp++;
+ *dp++ = *sp++;
+ sp += 2;
+ }
+ }
+ else
+ {
+ /* This converts from XXRRGGBB or AARRGGBB to RRGGBB */
+ for (i = 0; i < row_width; i++)
+ {
+ /* This could be (although png_memcpy is probably slower):
+ png_memcpy(dp, sp, 6);
+ sp += 8;
+ dp += 6;
+ */
+
+ sp+=2;
+ *dp++ = *sp++;
+ *dp++ = *sp++;
+ *dp++ = *sp++;
+ *dp++ = *sp++;
+ *dp++ = *sp++;
+ *dp++ = *sp++;
+ }
+ }
+ row_info->pixel_depth = 48;
+ row_info->rowbytes = row_width * 6;
+ }
+ row_info->channels = 3;
+ }
+ else if ((row_info->color_type == PNG_COLOR_TYPE_GRAY ||
+ (row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA &&
+ (flags & PNG_FLAG_STRIP_ALPHA))) &&
+ row_info->channels == 2)
+ {
+ if (row_info->bit_depth == 8)
+ {
+ /* This converts from GX or GA to G */
+ if (flags & PNG_FLAG_FILLER_AFTER)
+ {
+ for (i = 0; i < row_width; i++)
+ {
+ *dp++ = *sp++;
+ sp++;
+ }
+ }
+ /* This converts from XG or AG to G */
+ else
+ {
+ for (i = 0; i < row_width; i++)
+ {
+ sp++;
+ *dp++ = *sp++;
+ }
+ }
+ row_info->pixel_depth = 8;
+ row_info->rowbytes = row_width;
+ }
+ else /* if (row_info->bit_depth == 16) */
+ {
+ if (flags & PNG_FLAG_FILLER_AFTER)
+ {
+ /* This converts from GGXX or GGAA to GG */
+ sp += 4; dp += 2;
+ for (i = 1; i < row_width; i++)
+ {
+ *dp++ = *sp++;
+ *dp++ = *sp++;
+ sp += 2;
+ }
+ }
+ else
+ {
+ /* This converts from XXGG or AAGG to GG */
+ for (i = 0; i < row_width; i++)
+ {
+ sp += 2;
+ *dp++ = *sp++;
+ *dp++ = *sp++;
+ }
+ }
+ row_info->pixel_depth = 16;
+ row_info->rowbytes = row_width * 2;
+ }
+ row_info->channels = 1;
+ }
+ if (flags & PNG_FLAG_STRIP_ALPHA)
+ row_info->color_type &= ~PNG_COLOR_MASK_ALPHA;
+ }
+}
+#endif
+
+#if defined(PNG_READ_BGR_SUPPORTED) || defined(PNG_WRITE_BGR_SUPPORTED)
+/* Swaps red and blue bytes within a pixel */
+void /* PRIVATE */
+png_do_bgr(png_row_infop row_info, png_bytep row)
+{
+ png_debug(1, "in png_do_bgr");
+
+ if (
+#ifdef PNG_USELESS_TESTS_SUPPORTED
+ row != NULL && row_info != NULL &&
+#endif
+ (row_info->color_type & PNG_COLOR_MASK_COLOR))
+ {
+ png_uint_32 row_width = row_info->width;
+ if (row_info->bit_depth == 8)
+ {
+ if (row_info->color_type == PNG_COLOR_TYPE_RGB)
+ {
+ png_bytep rp;
+ png_uint_32 i;
+
+ for (i = 0, rp = row; i < row_width; i++, rp += 3)
+ {
+ png_byte save = *rp;
+ *rp = *(rp + 2);
+ *(rp + 2) = save;
+ }
+ }
+ else if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA)
+ {
+ png_bytep rp;
+ png_uint_32 i;
+
+ for (i = 0, rp = row; i < row_width; i++, rp += 4)
+ {
+ png_byte save = *rp;
+ *rp = *(rp + 2);
+ *(rp + 2) = save;
+ }
+ }
+ }
+ else if (row_info->bit_depth == 16)
+ {
+ if (row_info->color_type == PNG_COLOR_TYPE_RGB)
+ {
+ png_bytep rp;
+ png_uint_32 i;
+
+ for (i = 0, rp = row; i < row_width; i++, rp += 6)
+ {
+ png_byte save = *rp;
+ *rp = *(rp + 4);
+ *(rp + 4) = save;
+ save = *(rp + 1);
+ *(rp + 1) = *(rp + 5);
+ *(rp + 5) = save;
+ }
+ }
+ else if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA)
+ {
+ png_bytep rp;
+ png_uint_32 i;
+
+ for (i = 0, rp = row; i < row_width; i++, rp += 8)
+ {
+ png_byte save = *rp;
+ *rp = *(rp + 4);
+ *(rp + 4) = save;
+ save = *(rp + 1);
+ *(rp + 1) = *(rp + 5);
+ *(rp + 5) = save;
+ }
+ }
+ }
+ }
+}
+#endif /* PNG_READ_BGR_SUPPORTED or PNG_WRITE_BGR_SUPPORTED */
+
+#if defined(PNG_READ_USER_TRANSFORM_SUPPORTED) || \
+ defined(PNG_LEGACY_SUPPORTED) || \
+ defined(PNG_WRITE_USER_TRANSFORM_SUPPORTED)
+void PNGAPI
+png_set_user_transform_info(png_structp png_ptr, png_voidp
+ user_transform_ptr, int user_transform_depth, int user_transform_channels)
+{
+ png_debug(1, "in png_set_user_transform_info");
+
+ if (png_ptr == NULL)
+ return;
+#ifdef PNG_USER_TRANSFORM_PTR_SUPPORTED
+ png_ptr->user_transform_ptr = user_transform_ptr;
+ png_ptr->user_transform_depth = (png_byte)user_transform_depth;
+ png_ptr->user_transform_channels = (png_byte)user_transform_channels;
+#else
+ if (user_transform_ptr || user_transform_depth || user_transform_channels)
+ png_warning(png_ptr,
+ "This version of libpng does not support user transform info");
+#endif
+}
+#endif
+
+/* This function returns a pointer to the user_transform_ptr associated with
+ * the user transform functions. The application should free any memory
+ * associated with this pointer before png_write_destroy and png_read_destroy
+ * are called.
+ */
+png_voidp PNGAPI
+png_get_user_transform_ptr(png_structp png_ptr)
+{
+ if (png_ptr == NULL)
+ return (NULL);
+#ifdef PNG_USER_TRANSFORM_PTR_SUPPORTED
+ return ((png_voidp)png_ptr->user_transform_ptr);
+#else
+ return (NULL);
+#endif
+}
+#endif /* PNG_READ_SUPPORTED || PNG_WRITE_SUPPORTED */
diff --git a/trunk/src/third_party/libpng/pngvcrd.c b/trunk/src/third_party/libpng/pngvcrd.c
new file mode 100644
index 0000000..ce4233e
--- /dev/null
+++ b/trunk/src/third_party/libpng/pngvcrd.c
@@ -0,0 +1 @@
+/* pnggvrd.c was removed from libpng-1.2.20. */
diff --git a/trunk/src/third_party/libpng/pngwio.c b/trunk/src/third_party/libpng/pngwio.c
new file mode 100644
index 0000000..44e5ea9
--- /dev/null
+++ b/trunk/src/third_party/libpng/pngwio.c
@@ -0,0 +1,260 @@
+
+/* pngwio.c - functions for data output
+ *
+ * Last changed in libpng 1.2.41 [December 3, 2009]
+ * Copyright (c) 1998-2009 Glenn Randers-Pehrson
+ * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger)
+ * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.)
+ *
+ * This code is released under the libpng license.
+ * For conditions of distribution and use, see the disclaimer
+ * and license in png.h
+ *
+ * This file provides a location for all output. Users who need
+ * special handling are expected to write functions that have the same
+ * arguments as these and perform similar functions, but that possibly
+ * use different output methods. Note that you shouldn't change these
+ * functions, but rather write replacement functions and then change
+ * them at run time with png_set_write_fn(...).
+ */
+
+#define PNG_INTERNAL
+#define PNG_NO_PEDANTIC_WARNINGS
+#include "png.h"
+#ifdef PNG_WRITE_SUPPORTED
+
+/* Write the data to whatever output you are using. The default routine
+ * writes to a file pointer. Note that this routine sometimes gets called
+ * with very small lengths, so you should implement some kind of simple
+ * buffering if you are using unbuffered writes. This should never be asked
+ * to write more than 64K on a 16 bit machine.
+ */
+
+void /* PRIVATE */
+png_write_data(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+ if (png_ptr->write_data_fn != NULL )
+ (*(png_ptr->write_data_fn))(png_ptr, data, length);
+ else
+ png_error(png_ptr, "Call to NULL write function");
+}
+
+#ifdef PNG_STDIO_SUPPORTED
+/* This is the function that does the actual writing of data. If you are
+ * not writing to a standard C stream, you should create a replacement
+ * write_data function and use it at run time with png_set_write_fn(), rather
+ * than changing the library.
+ */
+#ifndef USE_FAR_KEYWORD
+void PNGAPI
+png_default_write_data(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+ png_uint_32 check;
+
+ if (png_ptr == NULL)
+ return;
+#ifdef _WIN32_WCE
+ if ( !WriteFile((HANDLE)(png_ptr->io_ptr), data, length, &check, NULL) )
+ check = 0;
+#else
+ check = fwrite(data, 1, length, (png_FILE_p)(png_ptr->io_ptr));
+#endif
+ if (check != length)
+ png_error(png_ptr, "Write Error");
+}
+#else
+/* This is the model-independent version. Since the standard I/O library
+ * can't handle far buffers in the medium and small models, we have to copy
+ * the data.
+ */
+
+#define NEAR_BUF_SIZE 1024
+#define MIN(a,b) (a <= b ? a : b)
+
+void PNGAPI
+png_default_write_data(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+ png_uint_32 check;
+ png_byte *near_data; /* Needs to be "png_byte *" instead of "png_bytep" */
+ png_FILE_p io_ptr;
+
+ if (png_ptr == NULL)
+ return;
+ /* Check if data really is near. If so, use usual code. */
+ near_data = (png_byte *)CVT_PTR_NOCHECK(data);
+ io_ptr = (png_FILE_p)CVT_PTR(png_ptr->io_ptr);
+ if ((png_bytep)near_data == data)
+ {
+#ifdef _WIN32_WCE
+ if ( !WriteFile(io_ptr, near_data, length, &check, NULL) )
+ check = 0;
+#else
+ check = fwrite(near_data, 1, length, io_ptr);
+#endif
+ }
+ else
+ {
+ png_byte buf[NEAR_BUF_SIZE];
+ png_size_t written, remaining, err;
+ check = 0;
+ remaining = length;
+ do
+ {
+ written = MIN(NEAR_BUF_SIZE, remaining);
+ png_memcpy(buf, data, written); /* Copy far buffer to near buffer */
+#ifdef _WIN32_WCE
+ if ( !WriteFile(io_ptr, buf, written, &err, NULL) )
+ err = 0;
+#else
+ err = fwrite(buf, 1, written, io_ptr);
+#endif
+ if (err != written)
+ break;
+
+ else
+ check += err;
+
+ data += written;
+ remaining -= written;
+ }
+ while (remaining != 0);
+ }
+ if (check != length)
+ png_error(png_ptr, "Write Error");
+}
+
+#endif
+#endif
+
+/* This function is called to output any data pending writing (normally
+ * to disk). After png_flush is called, there should be no data pending
+ * writing in any buffers.
+ */
+#ifdef PNG_WRITE_FLUSH_SUPPORTED
+void /* PRIVATE */
+png_flush(png_structp png_ptr)
+{
+ if (png_ptr->output_flush_fn != NULL)
+ (*(png_ptr->output_flush_fn))(png_ptr);
+}
+
+#ifdef PNG_STDIO_SUPPORTED
+void PNGAPI
+png_default_flush(png_structp png_ptr)
+{
+#ifndef _WIN32_WCE
+ png_FILE_p io_ptr;
+#endif
+ if (png_ptr == NULL)
+ return;
+#ifndef _WIN32_WCE
+ io_ptr = (png_FILE_p)CVT_PTR((png_ptr->io_ptr));
+ fflush(io_ptr);
+#endif
+}
+#endif
+#endif
+
+/* This function allows the application to supply new output functions for
+ * libpng if standard C streams aren't being used.
+ *
+ * This function takes as its arguments:
+ * png_ptr - pointer to a png output data structure
+ * io_ptr - pointer to user supplied structure containing info about
+ * the output functions. May be NULL.
+ * write_data_fn - pointer to a new output function that takes as its
+ * arguments a pointer to a png_struct, a pointer to
+ * data to be written, and a 32-bit unsigned int that is
+ * the number of bytes to be written. The new write
+ * function should call png_error(png_ptr, "Error msg")
+ * to exit and output any fatal error messages. May be
+ * NULL, in which case libpng's default function will
+ * be used.
+ * flush_data_fn - pointer to a new flush function that takes as its
+ * arguments a pointer to a png_struct. After a call to
+ * the flush function, there should be no data in any buffers
+ * or pending transmission. If the output method doesn't do
+ * any buffering of output, a function prototype must still be
+ * supplied although it doesn't have to do anything. If
+ * PNG_WRITE_FLUSH_SUPPORTED is not defined at libpng compile
+ * time, output_flush_fn will be ignored, although it must be
+ * supplied for compatibility. May be NULL, in which case
+ * libpng's default function will be used, if
+ * PNG_WRITE_FLUSH_SUPPORTED is defined. This is not
+ * a good idea if io_ptr does not point to a standard
+ * *FILE structure.
+ */
+void PNGAPI
+png_set_write_fn(png_structp png_ptr, png_voidp io_ptr,
+ png_rw_ptr write_data_fn, png_flush_ptr output_flush_fn)
+{
+ if (png_ptr == NULL)
+ return;
+
+ png_ptr->io_ptr = io_ptr;
+
+#ifdef PNG_STDIO_SUPPORTED
+ if (write_data_fn != NULL)
+ png_ptr->write_data_fn = write_data_fn;
+
+ else
+ png_ptr->write_data_fn = png_default_write_data;
+#else
+ png_ptr->write_data_fn = write_data_fn;
+#endif
+
+#ifdef PNG_WRITE_FLUSH_SUPPORTED
+#ifdef PNG_STDIO_SUPPORTED
+ if (output_flush_fn != NULL)
+ png_ptr->output_flush_fn = output_flush_fn;
+
+ else
+ png_ptr->output_flush_fn = png_default_flush;
+#else
+ png_ptr->output_flush_fn = output_flush_fn;
+#endif
+#endif /* PNG_WRITE_FLUSH_SUPPORTED */
+
+ /* It is an error to read while writing a png file */
+ if (png_ptr->read_data_fn != NULL)
+ {
+ png_ptr->read_data_fn = NULL;
+ png_warning(png_ptr,
+ "Attempted to set both read_data_fn and write_data_fn in");
+ png_warning(png_ptr,
+ "the same structure. Resetting read_data_fn to NULL.");
+ }
+}
+
+#ifdef USE_FAR_KEYWORD
+#ifdef _MSC_VER
+void *png_far_to_near(png_structp png_ptr, png_voidp ptr, int check)
+{
+ void *near_ptr;
+ void FAR *far_ptr;
+ FP_OFF(near_ptr) = FP_OFF(ptr);
+ far_ptr = (void FAR *)near_ptr;
+
+ if (check != 0)
+ if (FP_SEG(ptr) != FP_SEG(far_ptr))
+ png_error(png_ptr, "segment lost in conversion");
+
+ return(near_ptr);
+}
+# else
+void *png_far_to_near(png_structp png_ptr, png_voidp ptr, int check)
+{
+ void *near_ptr;
+ void FAR *far_ptr;
+ near_ptr = (void FAR *)ptr;
+ far_ptr = (void FAR *)near_ptr;
+
+ if (check != 0)
+ if (far_ptr != ptr)
+ png_error(png_ptr, "segment lost in conversion");
+
+ return(near_ptr);
+}
+# endif
+# endif
+#endif /* PNG_WRITE_SUPPORTED */
diff --git a/trunk/src/third_party/libpng/pngwrite.c b/trunk/src/third_party/libpng/pngwrite.c
new file mode 100644
index 0000000..e411e81
--- /dev/null
+++ b/trunk/src/third_party/libpng/pngwrite.c
@@ -0,0 +1,1592 @@
+
+/* pngwrite.c - general routines to write a PNG file
+ *
+ * Last changed in libpng 1.2.42 [January 3, 2010]
+ * Copyright (c) 1998-2010 Glenn Randers-Pehrson
+ * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger)
+ * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.)
+ *
+ * This code is released under the libpng license.
+ * For conditions of distribution and use, see the disclaimer
+ * and license in png.h
+ */
+
+/* Get internal access to png.h */
+#define PNG_INTERNAL
+#define PNG_NO_PEDANTIC_WARNINGS
+#include "png.h"
+#ifdef PNG_WRITE_SUPPORTED
+
+/* Writes all the PNG information. This is the suggested way to use the
+ * library. If you have a new chunk to add, make a function to write it,
+ * and put it in the correct location here. If you want the chunk written
+ * after the image data, put it in png_write_end(). I strongly encourage
+ * you to supply a PNG_INFO_ flag, and check info_ptr->valid before writing
+ * the chunk, as that will keep the code from breaking if you want to just
+ * write a plain PNG file. If you have long comments, I suggest writing
+ * them in png_write_end(), and compressing them.
+ */
+void PNGAPI
+png_write_info_before_PLTE(png_structp png_ptr, png_infop info_ptr)
+{
+ png_debug(1, "in png_write_info_before_PLTE");
+
+ if (png_ptr == NULL || info_ptr == NULL)
+ return;
+ if (!(png_ptr->mode & PNG_WROTE_INFO_BEFORE_PLTE))
+ {
+ /* Write PNG signature */
+ png_write_sig(png_ptr);
+#ifdef PNG_MNG_FEATURES_SUPPORTED
+ if ((png_ptr->mode&PNG_HAVE_PNG_SIGNATURE) && \
+ (png_ptr->mng_features_permitted))
+ {
+ png_warning(png_ptr, "MNG features are not allowed in a PNG datastream");
+ png_ptr->mng_features_permitted = 0;
+ }
+#endif
+ /* Write IHDR information. */
+ png_write_IHDR(png_ptr, info_ptr->width, info_ptr->height,
+ info_ptr->bit_depth, info_ptr->color_type, info_ptr->compression_type,
+ info_ptr->filter_type,
+#ifdef PNG_WRITE_INTERLACING_SUPPORTED
+ info_ptr->interlace_type);
+#else
+ 0);
+#endif
+ /* The rest of these check to see if the valid field has the appropriate
+ * flag set, and if it does, writes the chunk.
+ */
+#ifdef PNG_WRITE_gAMA_SUPPORTED
+ if (info_ptr->valid & PNG_INFO_gAMA)
+ {
+# ifdef PNG_FLOATING_POINT_SUPPORTED
+ png_write_gAMA(png_ptr, info_ptr->gamma);
+#else
+#ifdef PNG_FIXED_POINT_SUPPORTED
+ png_write_gAMA_fixed(png_ptr, info_ptr->int_gamma);
+# endif
+#endif
+ }
+#endif
+#ifdef PNG_WRITE_sRGB_SUPPORTED
+ if (info_ptr->valid & PNG_INFO_sRGB)
+ png_write_sRGB(png_ptr, (int)info_ptr->srgb_intent);
+#endif
+#ifdef PNG_WRITE_iCCP_SUPPORTED
+ if (info_ptr->valid & PNG_INFO_iCCP)
+ png_write_iCCP(png_ptr, info_ptr->iccp_name, PNG_COMPRESSION_TYPE_BASE,
+ info_ptr->iccp_profile, (int)info_ptr->iccp_proflen);
+#endif
+#ifdef PNG_WRITE_sBIT_SUPPORTED
+ if (info_ptr->valid & PNG_INFO_sBIT)
+ png_write_sBIT(png_ptr, &(info_ptr->sig_bit), info_ptr->color_type);
+#endif
+#ifdef PNG_WRITE_cHRM_SUPPORTED
+ if (info_ptr->valid & PNG_INFO_cHRM)
+ {
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+ png_write_cHRM(png_ptr,
+ info_ptr->x_white, info_ptr->y_white,
+ info_ptr->x_red, info_ptr->y_red,
+ info_ptr->x_green, info_ptr->y_green,
+ info_ptr->x_blue, info_ptr->y_blue);
+#else
+# ifdef PNG_FIXED_POINT_SUPPORTED
+ png_write_cHRM_fixed(png_ptr,
+ info_ptr->int_x_white, info_ptr->int_y_white,
+ info_ptr->int_x_red, info_ptr->int_y_red,
+ info_ptr->int_x_green, info_ptr->int_y_green,
+ info_ptr->int_x_blue, info_ptr->int_y_blue);
+# endif
+#endif
+ }
+#endif
+#ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED
+ if (info_ptr->unknown_chunks_num)
+ {
+ png_unknown_chunk *up;
+
+ png_debug(5, "writing extra chunks");
+
+ for (up = info_ptr->unknown_chunks;
+ up < info_ptr->unknown_chunks + info_ptr->unknown_chunks_num;
+ up++)
+ {
+ int keep = png_handle_as_unknown(png_ptr, up->name);
+ if (keep != PNG_HANDLE_CHUNK_NEVER &&
+ up->location && !(up->location & PNG_HAVE_PLTE) &&
+ !(up->location & PNG_HAVE_IDAT) &&
+ ((up->name[3] & 0x20) || keep == PNG_HANDLE_CHUNK_ALWAYS ||
+ (png_ptr->flags & PNG_FLAG_KEEP_UNSAFE_CHUNKS)))
+ {
+ if (up->size == 0)
+ png_warning(png_ptr, "Writing zero-length unknown chunk");
+ png_write_chunk(png_ptr, up->name, up->data, up->size);
+ }
+ }
+ }
+#endif
+ png_ptr->mode |= PNG_WROTE_INFO_BEFORE_PLTE;
+ }
+}
+
+void PNGAPI
+png_write_info(png_structp png_ptr, png_infop info_ptr)
+{
+#if defined(PNG_WRITE_TEXT_SUPPORTED) || defined(PNG_WRITE_sPLT_SUPPORTED)
+ int i;
+#endif
+
+ png_debug(1, "in png_write_info");
+
+ if (png_ptr == NULL || info_ptr == NULL)
+ return;
+
+ png_write_info_before_PLTE(png_ptr, info_ptr);
+
+ if (info_ptr->valid & PNG_INFO_PLTE)
+ png_write_PLTE(png_ptr, info_ptr->palette,
+ (png_uint_32)info_ptr->num_palette);
+ else if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE)
+ png_error(png_ptr, "Valid palette required for paletted images");
+
+#ifdef PNG_WRITE_tRNS_SUPPORTED
+ if (info_ptr->valid & PNG_INFO_tRNS)
+ {
+#ifdef PNG_WRITE_INVERT_ALPHA_SUPPORTED
+ /* Invert the alpha channel (in tRNS) */
+ if ((png_ptr->transformations & PNG_INVERT_ALPHA) &&
+ info_ptr->color_type == PNG_COLOR_TYPE_PALETTE)
+ {
+ int j;
+ for (j = 0; j<(int)info_ptr->num_trans; j++)
+ info_ptr->trans[j] = (png_byte)(255 - info_ptr->trans[j]);
+ }
+#endif
+ png_write_tRNS(png_ptr, info_ptr->trans, &(info_ptr->trans_values),
+ info_ptr->num_trans, info_ptr->color_type);
+ }
+#endif
+#ifdef PNG_WRITE_bKGD_SUPPORTED
+ if (info_ptr->valid & PNG_INFO_bKGD)
+ png_write_bKGD(png_ptr, &(info_ptr->background), info_ptr->color_type);
+#endif
+#ifdef PNG_WRITE_hIST_SUPPORTED
+ if (info_ptr->valid & PNG_INFO_hIST)
+ png_write_hIST(png_ptr, info_ptr->hist, info_ptr->num_palette);
+#endif
+#ifdef PNG_WRITE_oFFs_SUPPORTED
+ if (info_ptr->valid & PNG_INFO_oFFs)
+ png_write_oFFs(png_ptr, info_ptr->x_offset, info_ptr->y_offset,
+ info_ptr->offset_unit_type);
+#endif
+#ifdef PNG_WRITE_pCAL_SUPPORTED
+ if (info_ptr->valid & PNG_INFO_pCAL)
+ png_write_pCAL(png_ptr, info_ptr->pcal_purpose, info_ptr->pcal_X0,
+ info_ptr->pcal_X1, info_ptr->pcal_type, info_ptr->pcal_nparams,
+ info_ptr->pcal_units, info_ptr->pcal_params);
+#endif
+
+#ifdef PNG_sCAL_SUPPORTED
+ if (info_ptr->valid & PNG_INFO_sCAL)
+#ifdef PNG_WRITE_sCAL_SUPPORTED
+#if defined(PNG_FLOATING_POINT_SUPPORTED) && defined(PNG_STDIO_SUPPORTED)
+ png_write_sCAL(png_ptr, (int)info_ptr->scal_unit,
+ info_ptr->scal_pixel_width, info_ptr->scal_pixel_height);
+#else /* !FLOATING_POINT */
+#ifdef PNG_FIXED_POINT_SUPPORTED
+ png_write_sCAL_s(png_ptr, (int)info_ptr->scal_unit,
+ info_ptr->scal_s_width, info_ptr->scal_s_height);
+#endif /* FIXED_POINT */
+#endif /* FLOATING_POINT */
+#else /* !WRITE_sCAL */
+ png_warning(png_ptr,
+ "png_write_sCAL not supported; sCAL chunk not written.");
+#endif /* WRITE_sCAL */
+#endif /* sCAL */
+
+#ifdef PNG_WRITE_pHYs_SUPPORTED
+ if (info_ptr->valid & PNG_INFO_pHYs)
+ png_write_pHYs(png_ptr, info_ptr->x_pixels_per_unit,
+ info_ptr->y_pixels_per_unit, info_ptr->phys_unit_type);
+#endif /* pHYs */
+
+#ifdef PNG_WRITE_tIME_SUPPORTED
+ if (info_ptr->valid & PNG_INFO_tIME)
+ {
+ png_write_tIME(png_ptr, &(info_ptr->mod_time));
+ png_ptr->mode |= PNG_WROTE_tIME;
+ }
+#endif /* tIME */
+
+#ifdef PNG_WRITE_sPLT_SUPPORTED
+ if (info_ptr->valid & PNG_INFO_sPLT)
+ for (i = 0; i < (int)info_ptr->splt_palettes_num; i++)
+ png_write_sPLT(png_ptr, info_ptr->splt_palettes + i);
+#endif /* sPLT */
+
+#ifdef PNG_WRITE_TEXT_SUPPORTED
+ /* Check to see if we need to write text chunks */
+ for (i = 0; i < info_ptr->num_text; i++)
+ {
+ png_debug2(2, "Writing header text chunk %d, type %d", i,
+ info_ptr->text[i].compression);
+ /* An internationalized chunk? */
+ if (info_ptr->text[i].compression > 0)
+ {
+#ifdef PNG_WRITE_iTXt_SUPPORTED
+ /* Write international chunk */
+ png_write_iTXt(png_ptr,
+ info_ptr->text[i].compression,
+ info_ptr->text[i].key,
+ info_ptr->text[i].lang,
+ info_ptr->text[i].lang_key,
+ info_ptr->text[i].text);
+#else
+ png_warning(png_ptr, "Unable to write international text");
+#endif
+ /* Mark this chunk as written */
+ info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_NONE_WR;
+ }
+ /* If we want a compressed text chunk */
+ else if (info_ptr->text[i].compression == PNG_TEXT_COMPRESSION_zTXt)
+ {
+#ifdef PNG_WRITE_zTXt_SUPPORTED
+ /* Write compressed chunk */
+ png_write_zTXt(png_ptr, info_ptr->text[i].key,
+ info_ptr->text[i].text, 0,
+ info_ptr->text[i].compression);
+#else
+ png_warning(png_ptr, "Unable to write compressed text");
+#endif
+ /* Mark this chunk as written */
+ info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_zTXt_WR;
+ }
+ else if (info_ptr->text[i].compression == PNG_TEXT_COMPRESSION_NONE)
+ {
+#ifdef PNG_WRITE_tEXt_SUPPORTED
+ /* Write uncompressed chunk */
+ png_write_tEXt(png_ptr, info_ptr->text[i].key,
+ info_ptr->text[i].text,
+ 0);
+ /* Mark this chunk as written */
+ info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_NONE_WR;
+#else
+ /* Can't get here */
+ png_warning(png_ptr, "Unable to write uncompressed text");
+#endif
+ }
+ }
+#endif /* tEXt */
+
+#ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED
+ if (info_ptr->unknown_chunks_num)
+ {
+ png_unknown_chunk *up;
+
+ png_debug(5, "writing extra chunks");
+
+ for (up = info_ptr->unknown_chunks;
+ up < info_ptr->unknown_chunks + info_ptr->unknown_chunks_num;
+ up++)
+ {
+ int keep = png_handle_as_unknown(png_ptr, up->name);
+ if (keep != PNG_HANDLE_CHUNK_NEVER &&
+ up->location && (up->location & PNG_HAVE_PLTE) &&
+ !(up->location & PNG_HAVE_IDAT) &&
+ ((up->name[3] & 0x20) || keep == PNG_HANDLE_CHUNK_ALWAYS ||
+ (png_ptr->flags & PNG_FLAG_KEEP_UNSAFE_CHUNKS)))
+ {
+ png_write_chunk(png_ptr, up->name, up->data, up->size);
+ }
+ }
+ }
+#endif
+}
+
+/* Writes the end of the PNG file. If you don't want to write comments or
+ * time information, you can pass NULL for info. If you already wrote these
+ * in png_write_info(), do not write them again here. If you have long
+ * comments, I suggest writing them here, and compressing them.
+ */
+void PNGAPI
+png_write_end(png_structp png_ptr, png_infop info_ptr)
+{
+ png_debug(1, "in png_write_end");
+
+ if (png_ptr == NULL)
+ return;
+ if (!(png_ptr->mode & PNG_HAVE_IDAT))
+ png_error(png_ptr, "No IDATs written into file");
+
+ /* See if user wants us to write information chunks */
+ if (info_ptr != NULL)
+ {
+#ifdef PNG_WRITE_TEXT_SUPPORTED
+ int i; /* local index variable */
+#endif
+#ifdef PNG_WRITE_tIME_SUPPORTED
+ /* Check to see if user has supplied a time chunk */
+ if ((info_ptr->valid & PNG_INFO_tIME) &&
+ !(png_ptr->mode & PNG_WROTE_tIME))
+ png_write_tIME(png_ptr, &(info_ptr->mod_time));
+#endif
+#ifdef PNG_WRITE_TEXT_SUPPORTED
+ /* Loop through comment chunks */
+ for (i = 0; i < info_ptr->num_text; i++)
+ {
+ png_debug2(2, "Writing trailer text chunk %d, type %d", i,
+ info_ptr->text[i].compression);
+ /* An internationalized chunk? */
+ if (info_ptr->text[i].compression > 0)
+ {
+#ifdef PNG_WRITE_iTXt_SUPPORTED
+ /* Write international chunk */
+ png_write_iTXt(png_ptr,
+ info_ptr->text[i].compression,
+ info_ptr->text[i].key,
+ info_ptr->text[i].lang,
+ info_ptr->text[i].lang_key,
+ info_ptr->text[i].text);
+#else
+ png_warning(png_ptr, "Unable to write international text");
+#endif
+ /* Mark this chunk as written */
+ info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_NONE_WR;
+ }
+ else if (info_ptr->text[i].compression >= PNG_TEXT_COMPRESSION_zTXt)
+ {
+#ifdef PNG_WRITE_zTXt_SUPPORTED
+ /* Write compressed chunk */
+ png_write_zTXt(png_ptr, info_ptr->text[i].key,
+ info_ptr->text[i].text, 0,
+ info_ptr->text[i].compression);
+#else
+ png_warning(png_ptr, "Unable to write compressed text");
+#endif
+ /* Mark this chunk as written */
+ info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_zTXt_WR;
+ }
+ else if (info_ptr->text[i].compression == PNG_TEXT_COMPRESSION_NONE)
+ {
+#ifdef PNG_WRITE_tEXt_SUPPORTED
+ /* Write uncompressed chunk */
+ png_write_tEXt(png_ptr, info_ptr->text[i].key,
+ info_ptr->text[i].text, 0);
+#else
+ png_warning(png_ptr, "Unable to write uncompressed text");
+#endif
+
+ /* Mark this chunk as written */
+ info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_NONE_WR;
+ }
+ }
+#endif
+#ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED
+ if (info_ptr->unknown_chunks_num)
+ {
+ png_unknown_chunk *up;
+
+ png_debug(5, "writing extra chunks");
+
+ for (up = info_ptr->unknown_chunks;
+ up < info_ptr->unknown_chunks + info_ptr->unknown_chunks_num;
+ up++)
+ {
+ int keep = png_handle_as_unknown(png_ptr, up->name);
+ if (keep != PNG_HANDLE_CHUNK_NEVER &&
+ up->location && (up->location & PNG_AFTER_IDAT) &&
+ ((up->name[3] & 0x20) || keep == PNG_HANDLE_CHUNK_ALWAYS ||
+ (png_ptr->flags & PNG_FLAG_KEEP_UNSAFE_CHUNKS)))
+ {
+ png_write_chunk(png_ptr, up->name, up->data, up->size);
+ }
+ }
+ }
+#endif
+ }
+
+ png_ptr->mode |= PNG_AFTER_IDAT;
+
+ /* Write end of PNG file */
+ png_write_IEND(png_ptr);
+ /* This flush, added in libpng-1.0.8, removed from libpng-1.0.9beta03,
+ * and restored again in libpng-1.2.30, may cause some applications that
+ * do not set png_ptr->output_flush_fn to crash. If your application
+ * experiences a problem, please try building libpng with
+ * PNG_WRITE_FLUSH_AFTER_IEND_SUPPORTED defined, and report the event to
+ * png-mng-implement at lists.sf.net .
+ */
+#ifdef PNG_WRITE_FLUSH_SUPPORTED
+# ifdef PNG_WRITE_FLUSH_AFTER_IEND_SUPPORTED
+ png_flush(png_ptr);
+# endif
+#endif
+}
+
+#ifdef PNG_CONVERT_tIME_SUPPORTED
+/* "tm" structure is not supported on WindowsCE */
+void PNGAPI
+png_convert_from_struct_tm(png_timep ptime, struct tm FAR * ttime)
+{
+ png_debug(1, "in png_convert_from_struct_tm");
+
+ ptime->year = (png_uint_16)(1900 + ttime->tm_year);
+ ptime->month = (png_byte)(ttime->tm_mon + 1);
+ ptime->day = (png_byte)ttime->tm_mday;
+ ptime->hour = (png_byte)ttime->tm_hour;
+ ptime->minute = (png_byte)ttime->tm_min;
+ ptime->second = (png_byte)ttime->tm_sec;
+}
+
+void PNGAPI
+png_convert_from_time_t(png_timep ptime, time_t ttime)
+{
+ struct tm *tbuf;
+
+ png_debug(1, "in png_convert_from_time_t");
+
+ tbuf = gmtime(&ttime);
+ png_convert_from_struct_tm(ptime, tbuf);
+}
+#endif
+
+/* Initialize png_ptr structure, and allocate any memory needed */
+png_structp PNGAPI
+png_create_write_struct(png_const_charp user_png_ver, png_voidp error_ptr,
+ png_error_ptr error_fn, png_error_ptr warn_fn)
+{
+#ifdef PNG_USER_MEM_SUPPORTED
+ return (png_create_write_struct_2(user_png_ver, error_ptr, error_fn,
+ warn_fn, png_voidp_NULL, png_malloc_ptr_NULL, png_free_ptr_NULL));
+}
+
+/* Alternate initialize png_ptr structure, and allocate any memory needed */
+png_structp PNGAPI
+png_create_write_struct_2(png_const_charp user_png_ver, png_voidp error_ptr,
+ png_error_ptr error_fn, png_error_ptr warn_fn, png_voidp mem_ptr,
+ png_malloc_ptr malloc_fn, png_free_ptr free_fn)
+{
+#endif /* PNG_USER_MEM_SUPPORTED */
+#ifdef PNG_SETJMP_SUPPORTED
+ volatile
+#endif
+ png_structp png_ptr;
+#ifdef PNG_SETJMP_SUPPORTED
+#ifdef USE_FAR_KEYWORD
+ jmp_buf jmpbuf;
+#endif
+#endif
+ int i;
+
+ png_debug(1, "in png_create_write_struct");
+
+#ifdef PNG_USER_MEM_SUPPORTED
+ png_ptr = (png_structp)png_create_struct_2(PNG_STRUCT_PNG,
+ (png_malloc_ptr)malloc_fn, (png_voidp)mem_ptr);
+#else
+ png_ptr = (png_structp)png_create_struct(PNG_STRUCT_PNG);
+#endif /* PNG_USER_MEM_SUPPORTED */
+ if (png_ptr == NULL)
+ return (NULL);
+
+ /* Added at libpng-1.2.6 */
+#ifdef PNG_SET_USER_LIMITS_SUPPORTED
+ png_ptr->user_width_max = PNG_USER_WIDTH_MAX;
+ png_ptr->user_height_max = PNG_USER_HEIGHT_MAX;
+#endif
+
+#ifdef PNG_SETJMP_SUPPORTED
+#ifdef USE_FAR_KEYWORD
+ if (setjmp(jmpbuf))
+#else
+ if (setjmp(png_ptr->jmpbuf))
+#endif
+ {
+ png_free(png_ptr, png_ptr->zbuf);
+ png_ptr->zbuf = NULL;
+#ifdef PNG_USER_MEM_SUPPORTED
+ png_destroy_struct_2((png_voidp)png_ptr,
+ (png_free_ptr)free_fn, (png_voidp)mem_ptr);
+#else
+ png_destroy_struct((png_voidp)png_ptr);
+#endif
+ return (NULL);
+ }
+#ifdef USE_FAR_KEYWORD
+ png_memcpy(png_ptr->jmpbuf, jmpbuf, png_sizeof(jmp_buf));
+#endif
+#endif
+
+#ifdef PNG_USER_MEM_SUPPORTED
+ png_set_mem_fn(png_ptr, mem_ptr, malloc_fn, free_fn);
+#endif /* PNG_USER_MEM_SUPPORTED */
+ png_set_error_fn(png_ptr, error_ptr, error_fn, warn_fn);
+
+ if (user_png_ver)
+ {
+ i = 0;
+ do
+ {
+ if (user_png_ver[i] != png_libpng_ver[i])
+ png_ptr->flags |= PNG_FLAG_LIBRARY_MISMATCH;
+ } while (png_libpng_ver[i++]);
+ }
+
+ if (png_ptr->flags & PNG_FLAG_LIBRARY_MISMATCH)
+ {
+ /* Libpng 0.90 and later are binary incompatible with libpng 0.89, so
+ * we must recompile any applications that use any older library version.
+ * For versions after libpng 1.0, we will be compatible, so we need
+ * only check the first digit.
+ */
+ if (user_png_ver == NULL || user_png_ver[0] != png_libpng_ver[0] ||
+ (user_png_ver[0] == '1' && user_png_ver[2] != png_libpng_ver[2]) ||
+ (user_png_ver[0] == '0' && user_png_ver[2] < '9'))
+ {
+#if defined(PNG_STDIO_SUPPORTED) && !defined(_WIN32_WCE)
+ char msg[80];
+ if (user_png_ver)
+ {
+ png_snprintf(msg, 80,
+ "Application was compiled with png.h from libpng-%.20s",
+ user_png_ver);
+ png_warning(png_ptr, msg);
+ }
+ png_snprintf(msg, 80,
+ "Application is running with png.c from libpng-%.20s",
+ png_libpng_ver);
+ png_warning(png_ptr, msg);
+#endif
+#ifdef PNG_ERROR_NUMBERS_SUPPORTED
+ png_ptr->flags = 0;
+#endif
+ png_error(png_ptr,
+ "Incompatible libpng version in application and library");
+ }
+ }
+
+ /* Initialize zbuf - compression buffer */
+ png_ptr->zbuf_size = PNG_ZBUF_SIZE;
+ png_ptr->zbuf = (png_bytep)png_malloc(png_ptr,
+ (png_uint_32)png_ptr->zbuf_size);
+
+ png_set_write_fn(png_ptr, png_voidp_NULL, png_rw_ptr_NULL,
+ png_flush_ptr_NULL);
+
+#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED
+ png_set_filter_heuristics(png_ptr, PNG_FILTER_HEURISTIC_DEFAULT,
+ 1, png_doublep_NULL, png_doublep_NULL);
+#endif
+
+#ifdef PNG_SETJMP_SUPPORTED
+ /* Applications that neglect to set up their own setjmp() and then
+ * encounter a png_error() will longjmp here. Since the jmpbuf is
+ * then meaningless we abort instead of returning.
+ */
+#ifdef USE_FAR_KEYWORD
+ if (setjmp(jmpbuf))
+ PNG_ABORT();
+ png_memcpy(png_ptr->jmpbuf, jmpbuf, png_sizeof(jmp_buf));
+#else
+ if (setjmp(png_ptr->jmpbuf))
+ PNG_ABORT();
+#endif
+#endif
+ return (png_ptr);
+}
+
+/* Initialize png_ptr structure, and allocate any memory needed */
+#if defined(PNG_1_0_X) || defined(PNG_1_2_X)
+/* Deprecated. */
+#undef png_write_init
+void PNGAPI
+png_write_init(png_structp png_ptr)
+{
+ /* We only come here via pre-1.0.7-compiled applications */
+ png_write_init_2(png_ptr, "1.0.6 or earlier", 0, 0);
+}
+
+void PNGAPI
+png_write_init_2(png_structp png_ptr, png_const_charp user_png_ver,
+ png_size_t png_struct_size, png_size_t png_info_size)
+{
+ /* We only come here via pre-1.0.12-compiled applications */
+ if (png_ptr == NULL) return;
+#if defined(PNG_STDIO_SUPPORTED) && !defined(_WIN32_WCE)
+ if (png_sizeof(png_struct) > png_struct_size ||
+ png_sizeof(png_info) > png_info_size)
+ {
+ char msg[80];
+ png_ptr->warning_fn = NULL;
+ if (user_png_ver)
+ {
+ png_snprintf(msg, 80,
+ "Application was compiled with png.h from libpng-%.20s",
+ user_png_ver);
+ png_warning(png_ptr, msg);
+ }
+ png_snprintf(msg, 80,
+ "Application is running with png.c from libpng-%.20s",
+ png_libpng_ver);
+ png_warning(png_ptr, msg);
+ }
+#endif
+ if (png_sizeof(png_struct) > png_struct_size)
+ {
+ png_ptr->error_fn = NULL;
+#ifdef PNG_ERROR_NUMBERS_SUPPORTED
+ png_ptr->flags = 0;
+#endif
+ png_error(png_ptr,
+ "The png struct allocated by the application for writing is"
+ " too small.");
+ }
+ if (png_sizeof(png_info) > png_info_size)
+ {
+ png_ptr->error_fn = NULL;
+#ifdef PNG_ERROR_NUMBERS_SUPPORTED
+ png_ptr->flags = 0;
+#endif
+ png_error(png_ptr,
+ "The info struct allocated by the application for writing is"
+ " too small.");
+ }
+ png_write_init_3(&png_ptr, user_png_ver, png_struct_size);
+}
+#endif /* PNG_1_0_X || PNG_1_2_X */
+
+
+void PNGAPI
+png_write_init_3(png_structpp ptr_ptr, png_const_charp user_png_ver,
+ png_size_t png_struct_size)
+{
+ png_structp png_ptr = *ptr_ptr;
+#ifdef PNG_SETJMP_SUPPORTED
+ jmp_buf tmp_jmp; /* to save current jump buffer */
+#endif
+
+ int i = 0;
+
+ if (png_ptr == NULL)
+ return;
+
+ do
+ {
+ if (user_png_ver[i] != png_libpng_ver[i])
+ {
+#ifdef PNG_LEGACY_SUPPORTED
+ png_ptr->flags |= PNG_FLAG_LIBRARY_MISMATCH;
+#else
+ png_ptr->warning_fn = NULL;
+ png_warning(png_ptr,
+ "Application uses deprecated png_write_init() and should be recompiled.");
+#endif
+ }
+ } while (png_libpng_ver[i++]);
+
+ png_debug(1, "in png_write_init_3");
+
+#ifdef PNG_SETJMP_SUPPORTED
+ /* Save jump buffer and error functions */
+ png_memcpy(tmp_jmp, png_ptr->jmpbuf, png_sizeof(jmp_buf));
+#endif
+
+ if (png_sizeof(png_struct) > png_struct_size)
+ {
+ png_destroy_struct(png_ptr);
+ png_ptr = (png_structp)png_create_struct(PNG_STRUCT_PNG);
+ *ptr_ptr = png_ptr;
+ }
+
+ /* Reset all variables to 0 */
+ png_memset(png_ptr, 0, png_sizeof(png_struct));
+
+ /* Added at libpng-1.2.6 */
+#ifdef PNG_SET_USER_LIMITS_SUPPORTED
+ png_ptr->user_width_max = PNG_USER_WIDTH_MAX;
+ png_ptr->user_height_max = PNG_USER_HEIGHT_MAX;
+#endif
+
+#ifdef PNG_SETJMP_SUPPORTED
+ /* Restore jump buffer */
+ png_memcpy(png_ptr->jmpbuf, tmp_jmp, png_sizeof(jmp_buf));
+#endif
+
+ png_set_write_fn(png_ptr, png_voidp_NULL, png_rw_ptr_NULL,
+ png_flush_ptr_NULL);
+
+ /* Initialize zbuf - compression buffer */
+ png_ptr->zbuf_size = PNG_ZBUF_SIZE;
+ png_ptr->zbuf = (png_bytep)png_malloc(png_ptr,
+ (png_uint_32)png_ptr->zbuf_size);
+#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED
+ png_set_filter_heuristics(png_ptr, PNG_FILTER_HEURISTIC_DEFAULT,
+ 1, png_doublep_NULL, png_doublep_NULL);
+#endif
+}
+
+/* Write a few rows of image data. If the image is interlaced,
+ * either you will have to write the 7 sub images, or, if you
+ * have called png_set_interlace_handling(), you will have to
+ * "write" the image seven times.
+ */
+void PNGAPI
+png_write_rows(png_structp png_ptr, png_bytepp row,
+ png_uint_32 num_rows)
+{
+ png_uint_32 i; /* row counter */
+ png_bytepp rp; /* row pointer */
+
+ png_debug(1, "in png_write_rows");
+
+ if (png_ptr == NULL)
+ return;
+
+ /* Loop through the rows */
+ for (i = 0, rp = row; i < num_rows; i++, rp++)
+ {
+ png_write_row(png_ptr, *rp);
+ }
+}
+
+/* Write the image. You only need to call this function once, even
+ * if you are writing an interlaced image.
+ */
+void PNGAPI
+png_write_image(png_structp png_ptr, png_bytepp image)
+{
+ png_uint_32 i; /* row index */
+ int pass, num_pass; /* pass variables */
+ png_bytepp rp; /* points to current row */
+
+ if (png_ptr == NULL)
+ return;
+
+ png_debug(1, "in png_write_image");
+
+#ifdef PNG_WRITE_INTERLACING_SUPPORTED
+ /* Initialize interlace handling. If image is not interlaced,
+ * this will set pass to 1
+ */
+ num_pass = png_set_interlace_handling(png_ptr);
+#else
+ num_pass = 1;
+#endif
+ /* Loop through passes */
+ for (pass = 0; pass < num_pass; pass++)
+ {
+ /* Loop through image */
+ for (i = 0, rp = image; i < png_ptr->height; i++, rp++)
+ {
+ png_write_row(png_ptr, *rp);
+ }
+ }
+}
+
+/* Called by user to write a row of image data */
+void PNGAPI
+png_write_row(png_structp png_ptr, png_bytep row)
+{
+ if (png_ptr == NULL)
+ return;
+
+ png_debug2(1, "in png_write_row (row %ld, pass %d)",
+ png_ptr->row_number, png_ptr->pass);
+
+ /* Initialize transformations and other stuff if first time */
+ if (png_ptr->row_number == 0 && png_ptr->pass == 0)
+ {
+ /* Make sure we wrote the header info */
+ if (!(png_ptr->mode & PNG_WROTE_INFO_BEFORE_PLTE))
+ png_error(png_ptr,
+ "png_write_info was never called before png_write_row.");
+
+ /* Check for transforms that have been set but were defined out */
+#if !defined(PNG_WRITE_INVERT_SUPPORTED) && defined(PNG_READ_INVERT_SUPPORTED)
+ if (png_ptr->transformations & PNG_INVERT_MONO)
+ png_warning(png_ptr,
+ "PNG_WRITE_INVERT_SUPPORTED is not defined.");
+#endif
+#if !defined(PNG_WRITE_FILLER_SUPPORTED) && defined(PNG_READ_FILLER_SUPPORTED)
+ if (png_ptr->transformations & PNG_FILLER)
+ png_warning(png_ptr,
+ "PNG_WRITE_FILLER_SUPPORTED is not defined.");
+#endif
+#if !defined(PNG_WRITE_PACKSWAP_SUPPORTED) && \
+ defined(PNG_READ_PACKSWAP_SUPPORTED)
+ if (png_ptr->transformations & PNG_PACKSWAP)
+ png_warning(png_ptr,
+ "PNG_WRITE_PACKSWAP_SUPPORTED is not defined.");
+#endif
+#if !defined(PNG_WRITE_PACK_SUPPORTED) && defined(PNG_READ_PACK_SUPPORTED)
+ if (png_ptr->transformations & PNG_PACK)
+ png_warning(png_ptr, "PNG_WRITE_PACK_SUPPORTED is not defined.");
+#endif
+#if !defined(PNG_WRITE_SHIFT_SUPPORTED) && defined(PNG_READ_SHIFT_SUPPORTED)
+ if (png_ptr->transformations & PNG_SHIFT)
+ png_warning(png_ptr, "PNG_WRITE_SHIFT_SUPPORTED is not defined.");
+#endif
+#if !defined(PNG_WRITE_BGR_SUPPORTED) && defined(PNG_READ_BGR_SUPPORTED)
+ if (png_ptr->transformations & PNG_BGR)
+ png_warning(png_ptr, "PNG_WRITE_BGR_SUPPORTED is not defined.");
+#endif
+#if !defined(PNG_WRITE_SWAP_SUPPORTED) && defined(PNG_READ_SWAP_SUPPORTED)
+ if (png_ptr->transformations & PNG_SWAP_BYTES)
+ png_warning(png_ptr, "PNG_WRITE_SWAP_SUPPORTED is not defined.");
+#endif
+
+ png_write_start_row(png_ptr);
+ }
+
+#ifdef PNG_WRITE_INTERLACING_SUPPORTED
+ /* If interlaced and not interested in row, return */
+ if (png_ptr->interlaced && (png_ptr->transformations & PNG_INTERLACE))
+ {
+ switch (png_ptr->pass)
+ {
+ case 0:
+ if (png_ptr->row_number & 0x07)
+ {
+ png_write_finish_row(png_ptr);
+ return;
+ }
+ break;
+ case 1:
+ if ((png_ptr->row_number & 0x07) || png_ptr->width < 5)
+ {
+ png_write_finish_row(png_ptr);
+ return;
+ }
+ break;
+ case 2:
+ if ((png_ptr->row_number & 0x07) != 4)
+ {
+ png_write_finish_row(png_ptr);
+ return;
+ }
+ break;
+ case 3:
+ if ((png_ptr->row_number & 0x03) || png_ptr->width < 3)
+ {
+ png_write_finish_row(png_ptr);
+ return;
+ }
+ break;
+ case 4:
+ if ((png_ptr->row_number & 0x03) != 2)
+ {
+ png_write_finish_row(png_ptr);
+ return;
+ }
+ break;
+ case 5:
+ if ((png_ptr->row_number & 0x01) || png_ptr->width < 2)
+ {
+ png_write_finish_row(png_ptr);
+ return;
+ }
+ break;
+ case 6:
+ if (!(png_ptr->row_number & 0x01))
+ {
+ png_write_finish_row(png_ptr);
+ return;
+ }
+ break;
+ }
+ }
+#endif
+
+ /* Set up row info for transformations */
+ png_ptr->row_info.color_type = png_ptr->color_type;
+ png_ptr->row_info.width = png_ptr->usr_width;
+ png_ptr->row_info.channels = png_ptr->usr_channels;
+ png_ptr->row_info.bit_depth = png_ptr->usr_bit_depth;
+ png_ptr->row_info.pixel_depth = (png_byte)(png_ptr->row_info.bit_depth *
+ png_ptr->row_info.channels);
+
+ png_ptr->row_info.rowbytes = PNG_ROWBYTES(png_ptr->row_info.pixel_depth,
+ png_ptr->row_info.width);
+
+ png_debug1(3, "row_info->color_type = %d", png_ptr->row_info.color_type);
+ png_debug1(3, "row_info->width = %lu", png_ptr->row_info.width);
+ png_debug1(3, "row_info->channels = %d", png_ptr->row_info.channels);
+ png_debug1(3, "row_info->bit_depth = %d", png_ptr->row_info.bit_depth);
+ png_debug1(3, "row_info->pixel_depth = %d", png_ptr->row_info.pixel_depth);
+ png_debug1(3, "row_info->rowbytes = %lu", png_ptr->row_info.rowbytes);
+
+ /* Copy user's row into buffer, leaving room for filter byte. */
+ png_memcpy_check(png_ptr, png_ptr->row_buf + 1, row,
+ png_ptr->row_info.rowbytes);
+
+#ifdef PNG_WRITE_INTERLACING_SUPPORTED
+ /* Handle interlacing */
+ if (png_ptr->interlaced && png_ptr->pass < 6 &&
+ (png_ptr->transformations & PNG_INTERLACE))
+ {
+ png_do_write_interlace(&(png_ptr->row_info),
+ png_ptr->row_buf + 1, png_ptr->pass);
+ /* This should always get caught above, but still ... */
+ if (!(png_ptr->row_info.width))
+ {
+ png_write_finish_row(png_ptr);
+ return;
+ }
+ }
+#endif
+
+ /* Handle other transformations */
+ if (png_ptr->transformations)
+ png_do_write_transformations(png_ptr);
+
+#ifdef PNG_MNG_FEATURES_SUPPORTED
+ /* Write filter_method 64 (intrapixel differencing) only if
+ * 1. Libpng was compiled with PNG_MNG_FEATURES_SUPPORTED and
+ * 2. Libpng did not write a PNG signature (this filter_method is only
+ * used in PNG datastreams that are embedded in MNG datastreams) and
+ * 3. The application called png_permit_mng_features with a mask that
+ * included PNG_FLAG_MNG_FILTER_64 and
+ * 4. The filter_method is 64 and
+ * 5. The color_type is RGB or RGBA
+ */
+ if ((png_ptr->mng_features_permitted & PNG_FLAG_MNG_FILTER_64) &&
+ (png_ptr->filter_type == PNG_INTRAPIXEL_DIFFERENCING))
+ {
+ /* Intrapixel differencing */
+ png_do_write_intrapixel(&(png_ptr->row_info), png_ptr->row_buf + 1);
+ }
+#endif
+
+ /* Find a filter if necessary, filter the row and write it out. */
+ png_write_find_filter(png_ptr, &(png_ptr->row_info));
+
+ if (png_ptr->write_row_fn != NULL)
+ (*(png_ptr->write_row_fn))(png_ptr, png_ptr->row_number, png_ptr->pass);
+}
+
+#ifdef PNG_WRITE_FLUSH_SUPPORTED
+/* Set the automatic flush interval or 0 to turn flushing off */
+void PNGAPI
+png_set_flush(png_structp png_ptr, int nrows)
+{
+ png_debug(1, "in png_set_flush");
+
+ if (png_ptr == NULL)
+ return;
+ png_ptr->flush_dist = (nrows < 0 ? 0 : nrows);
+}
+
+/* Flush the current output buffers now */
+void PNGAPI
+png_write_flush(png_structp png_ptr)
+{
+ int wrote_IDAT;
+
+ png_debug(1, "in png_write_flush");
+
+ if (png_ptr == NULL)
+ return;
+ /* We have already written out all of the data */
+ if (png_ptr->row_number >= png_ptr->num_rows)
+ return;
+
+ do
+ {
+ int ret;
+
+ /* Compress the data */
+ ret = deflate(&png_ptr->zstream, Z_SYNC_FLUSH);
+ wrote_IDAT = 0;
+
+ /* Check for compression errors */
+ if (ret != Z_OK)
+ {
+ if (png_ptr->zstream.msg != NULL)
+ png_error(png_ptr, png_ptr->zstream.msg);
+ else
+ png_error(png_ptr, "zlib error");
+ }
+
+ if (!(png_ptr->zstream.avail_out))
+ {
+ /* Write the IDAT and reset the zlib output buffer */
+ png_write_IDAT(png_ptr, png_ptr->zbuf,
+ png_ptr->zbuf_size);
+ png_ptr->zstream.next_out = png_ptr->zbuf;
+ png_ptr->zstream.avail_out = (uInt)png_ptr->zbuf_size;
+ wrote_IDAT = 1;
+ }
+ } while(wrote_IDAT == 1);
+
+ /* If there is any data left to be output, write it into a new IDAT */
+ if (png_ptr->zbuf_size != png_ptr->zstream.avail_out)
+ {
+ /* Write the IDAT and reset the zlib output buffer */
+ png_write_IDAT(png_ptr, png_ptr->zbuf,
+ png_ptr->zbuf_size - png_ptr->zstream.avail_out);
+ png_ptr->zstream.next_out = png_ptr->zbuf;
+ png_ptr->zstream.avail_out = (uInt)png_ptr->zbuf_size;
+ }
+ png_ptr->flush_rows = 0;
+ png_flush(png_ptr);
+}
+#endif /* PNG_WRITE_FLUSH_SUPPORTED */
+
+/* Free all memory used by the write */
+void PNGAPI
+png_destroy_write_struct(png_structpp png_ptr_ptr, png_infopp info_ptr_ptr)
+{
+ png_structp png_ptr = NULL;
+ png_infop info_ptr = NULL;
+#ifdef PNG_USER_MEM_SUPPORTED
+ png_free_ptr free_fn = NULL;
+ png_voidp mem_ptr = NULL;
+#endif
+
+ png_debug(1, "in png_destroy_write_struct");
+
+ if (png_ptr_ptr != NULL)
+ {
+ png_ptr = *png_ptr_ptr;
+#ifdef PNG_USER_MEM_SUPPORTED
+ free_fn = png_ptr->free_fn;
+ mem_ptr = png_ptr->mem_ptr;
+#endif
+ }
+
+#ifdef PNG_USER_MEM_SUPPORTED
+ if (png_ptr != NULL)
+ {
+ free_fn = png_ptr->free_fn;
+ mem_ptr = png_ptr->mem_ptr;
+ }
+#endif
+
+ if (info_ptr_ptr != NULL)
+ info_ptr = *info_ptr_ptr;
+
+ if (info_ptr != NULL)
+ {
+ if (png_ptr != NULL)
+ {
+ png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1);
+
+#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
+ if (png_ptr->num_chunk_list)
+ {
+ png_free(png_ptr, png_ptr->chunk_list);
+ png_ptr->chunk_list = NULL;
+ png_ptr->num_chunk_list = 0;
+ }
+#endif
+ }
+
+#ifdef PNG_USER_MEM_SUPPORTED
+ png_destroy_struct_2((png_voidp)info_ptr, (png_free_ptr)free_fn,
+ (png_voidp)mem_ptr);
+#else
+ png_destroy_struct((png_voidp)info_ptr);
+#endif
+ *info_ptr_ptr = NULL;
+ }
+
+ if (png_ptr != NULL)
+ {
+ png_write_destroy(png_ptr);
+#ifdef PNG_USER_MEM_SUPPORTED
+ png_destroy_struct_2((png_voidp)png_ptr, (png_free_ptr)free_fn,
+ (png_voidp)mem_ptr);
+#else
+ png_destroy_struct((png_voidp)png_ptr);
+#endif
+ *png_ptr_ptr = NULL;
+ }
+}
+
+
+/* Free any memory used in png_ptr struct (old method) */
+void /* PRIVATE */
+png_write_destroy(png_structp png_ptr)
+{
+#ifdef PNG_SETJMP_SUPPORTED
+ jmp_buf tmp_jmp; /* Save jump buffer */
+#endif
+ png_error_ptr error_fn;
+ png_error_ptr warning_fn;
+ png_voidp error_ptr;
+#ifdef PNG_USER_MEM_SUPPORTED
+ png_free_ptr free_fn;
+#endif
+
+ png_debug(1, "in png_write_destroy");
+
+ /* Free any memory zlib uses */
+ deflateEnd(&png_ptr->zstream);
+
+ /* Free our memory. png_free checks NULL for us. */
+ png_free(png_ptr, png_ptr->zbuf);
+ png_free(png_ptr, png_ptr->row_buf);
+#ifdef PNG_WRITE_FILTER_SUPPORTED
+ png_free(png_ptr, png_ptr->prev_row);
+ png_free(png_ptr, png_ptr->sub_row);
+ png_free(png_ptr, png_ptr->up_row);
+ png_free(png_ptr, png_ptr->avg_row);
+ png_free(png_ptr, png_ptr->paeth_row);
+#endif
+
+#ifdef PNG_TIME_RFC1123_SUPPORTED
+ png_free(png_ptr, png_ptr->time_buffer);
+#endif
+
+#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED
+ png_free(png_ptr, png_ptr->prev_filters);
+ png_free(png_ptr, png_ptr->filter_weights);
+ png_free(png_ptr, png_ptr->inv_filter_weights);
+ png_free(png_ptr, png_ptr->filter_costs);
+ png_free(png_ptr, png_ptr->inv_filter_costs);
+#endif
+
+#ifdef PNG_SETJMP_SUPPORTED
+ /* Reset structure */
+ png_memcpy(tmp_jmp, png_ptr->jmpbuf, png_sizeof(jmp_buf));
+#endif
+
+ error_fn = png_ptr->error_fn;
+ warning_fn = png_ptr->warning_fn;
+ error_ptr = png_ptr->error_ptr;
+#ifdef PNG_USER_MEM_SUPPORTED
+ free_fn = png_ptr->free_fn;
+#endif
+
+ png_memset(png_ptr, 0, png_sizeof(png_struct));
+
+ png_ptr->error_fn = error_fn;
+ png_ptr->warning_fn = warning_fn;
+ png_ptr->error_ptr = error_ptr;
+#ifdef PNG_USER_MEM_SUPPORTED
+ png_ptr->free_fn = free_fn;
+#endif
+
+#ifdef PNG_SETJMP_SUPPORTED
+ png_memcpy(png_ptr->jmpbuf, tmp_jmp, png_sizeof(jmp_buf));
+#endif
+}
+
+/* Allow the application to select one or more row filters to use. */
+void PNGAPI
+png_set_filter(png_structp png_ptr, int method, int filters)
+{
+ png_debug(1, "in png_set_filter");
+
+ if (png_ptr == NULL)
+ return;
+#ifdef PNG_MNG_FEATURES_SUPPORTED
+ if ((png_ptr->mng_features_permitted & PNG_FLAG_MNG_FILTER_64) &&
+ (method == PNG_INTRAPIXEL_DIFFERENCING))
+ method = PNG_FILTER_TYPE_BASE;
+#endif
+ if (method == PNG_FILTER_TYPE_BASE)
+ {
+ switch (filters & (PNG_ALL_FILTERS | 0x07))
+ {
+#ifdef PNG_WRITE_FILTER_SUPPORTED
+ case 5:
+ case 6:
+ case 7: png_warning(png_ptr, "Unknown row filter for method 0");
+#endif /* PNG_WRITE_FILTER_SUPPORTED */
+ case PNG_FILTER_VALUE_NONE:
+ png_ptr->do_filter = PNG_FILTER_NONE; break;
+#ifdef PNG_WRITE_FILTER_SUPPORTED
+ case PNG_FILTER_VALUE_SUB:
+ png_ptr->do_filter = PNG_FILTER_SUB; break;
+ case PNG_FILTER_VALUE_UP:
+ png_ptr->do_filter = PNG_FILTER_UP; break;
+ case PNG_FILTER_VALUE_AVG:
+ png_ptr->do_filter = PNG_FILTER_AVG; break;
+ case PNG_FILTER_VALUE_PAETH:
+ png_ptr->do_filter = PNG_FILTER_PAETH; break;
+ default: png_ptr->do_filter = (png_byte)filters; break;
+#else
+ default: png_warning(png_ptr, "Unknown row filter for method 0");
+#endif /* PNG_WRITE_FILTER_SUPPORTED */
+ }
+
+ /* If we have allocated the row_buf, this means we have already started
+ * with the image and we should have allocated all of the filter buffers
+ * that have been selected. If prev_row isn't already allocated, then
+ * it is too late to start using the filters that need it, since we
+ * will be missing the data in the previous row. If an application
+ * wants to start and stop using particular filters during compression,
+ * it should start out with all of the filters, and then add and
+ * remove them after the start of compression.
+ */
+ if (png_ptr->row_buf != NULL)
+ {
+#ifdef PNG_WRITE_FILTER_SUPPORTED
+ if ((png_ptr->do_filter & PNG_FILTER_SUB) && png_ptr->sub_row == NULL)
+ {
+ png_ptr->sub_row = (png_bytep)png_malloc(png_ptr,
+ (png_ptr->rowbytes + 1));
+ png_ptr->sub_row[0] = PNG_FILTER_VALUE_SUB;
+ }
+
+ if ((png_ptr->do_filter & PNG_FILTER_UP) && png_ptr->up_row == NULL)
+ {
+ if (png_ptr->prev_row == NULL)
+ {
+ png_warning(png_ptr, "Can't add Up filter after starting");
+ png_ptr->do_filter &= ~PNG_FILTER_UP;
+ }
+ else
+ {
+ png_ptr->up_row = (png_bytep)png_malloc(png_ptr,
+ (png_ptr->rowbytes + 1));
+ png_ptr->up_row[0] = PNG_FILTER_VALUE_UP;
+ }
+ }
+
+ if ((png_ptr->do_filter & PNG_FILTER_AVG) && png_ptr->avg_row == NULL)
+ {
+ if (png_ptr->prev_row == NULL)
+ {
+ png_warning(png_ptr, "Can't add Average filter after starting");
+ png_ptr->do_filter &= ~PNG_FILTER_AVG;
+ }
+ else
+ {
+ png_ptr->avg_row = (png_bytep)png_malloc(png_ptr,
+ (png_ptr->rowbytes + 1));
+ png_ptr->avg_row[0] = PNG_FILTER_VALUE_AVG;
+ }
+ }
+
+ if ((png_ptr->do_filter & PNG_FILTER_PAETH) &&
+ png_ptr->paeth_row == NULL)
+ {
+ if (png_ptr->prev_row == NULL)
+ {
+ png_warning(png_ptr, "Can't add Paeth filter after starting");
+ png_ptr->do_filter &= (png_byte)(~PNG_FILTER_PAETH);
+ }
+ else
+ {
+ png_ptr->paeth_row = (png_bytep)png_malloc(png_ptr,
+ (png_ptr->rowbytes + 1));
+ png_ptr->paeth_row[0] = PNG_FILTER_VALUE_PAETH;
+ }
+ }
+
+ if (png_ptr->do_filter == PNG_NO_FILTERS)
+#endif /* PNG_WRITE_FILTER_SUPPORTED */
+ png_ptr->do_filter = PNG_FILTER_NONE;
+ }
+ }
+ else
+ png_error(png_ptr, "Unknown custom filter method");
+}
+
+/* This allows us to influence the way in which libpng chooses the "best"
+ * filter for the current scanline. While the "minimum-sum-of-absolute-
+ * differences metric is relatively fast and effective, there is some
+ * question as to whether it can be improved upon by trying to keep the
+ * filtered data going to zlib more consistent, hopefully resulting in
+ * better compression.
+ */
+#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED /* GRR 970116 */
+void PNGAPI
+png_set_filter_heuristics(png_structp png_ptr, int heuristic_method,
+ int num_weights, png_doublep filter_weights,
+ png_doublep filter_costs)
+{
+ int i;
+
+ png_debug(1, "in png_set_filter_heuristics");
+
+ if (png_ptr == NULL)
+ return;
+ if (heuristic_method >= PNG_FILTER_HEURISTIC_LAST)
+ {
+ png_warning(png_ptr, "Unknown filter heuristic method");
+ return;
+ }
+
+ if (heuristic_method == PNG_FILTER_HEURISTIC_DEFAULT)
+ {
+ heuristic_method = PNG_FILTER_HEURISTIC_UNWEIGHTED;
+ }
+
+ if (num_weights < 0 || filter_weights == NULL ||
+ heuristic_method == PNG_FILTER_HEURISTIC_UNWEIGHTED)
+ {
+ num_weights = 0;
+ }
+
+ png_ptr->num_prev_filters = (png_byte)num_weights;
+ png_ptr->heuristic_method = (png_byte)heuristic_method;
+
+ if (num_weights > 0)
+ {
+ if (png_ptr->prev_filters == NULL)
+ {
+ png_ptr->prev_filters = (png_bytep)png_malloc(png_ptr,
+ (png_uint_32)(png_sizeof(png_byte) * num_weights));
+
+ /* To make sure that the weighting starts out fairly */
+ for (i = 0; i < num_weights; i++)
+ {
+ png_ptr->prev_filters[i] = 255;
+ }
+ }
+
+ if (png_ptr->filter_weights == NULL)
+ {
+ png_ptr->filter_weights = (png_uint_16p)png_malloc(png_ptr,
+ (png_uint_32)(png_sizeof(png_uint_16) * num_weights));
+
+ png_ptr->inv_filter_weights = (png_uint_16p)png_malloc(png_ptr,
+ (png_uint_32)(png_sizeof(png_uint_16) * num_weights));
+ for (i = 0; i < num_weights; i++)
+ {
+ png_ptr->inv_filter_weights[i] =
+ png_ptr->filter_weights[i] = PNG_WEIGHT_FACTOR;
+ }
+ }
+
+ for (i = 0; i < num_weights; i++)
+ {
+ if (filter_weights[i] < 0.0)
+ {
+ png_ptr->inv_filter_weights[i] =
+ png_ptr->filter_weights[i] = PNG_WEIGHT_FACTOR;
+ }
+ else
+ {
+ png_ptr->inv_filter_weights[i] =
+ (png_uint_16)((double)PNG_WEIGHT_FACTOR*filter_weights[i]+0.5);
+ png_ptr->filter_weights[i] =
+ (png_uint_16)((double)PNG_WEIGHT_FACTOR/filter_weights[i]+0.5);
+ }
+ }
+ }
+
+ /* If, in the future, there are other filter methods, this would
+ * need to be based on png_ptr->filter.
+ */
+ if (png_ptr->filter_costs == NULL)
+ {
+ png_ptr->filter_costs = (png_uint_16p)png_malloc(png_ptr,
+ (png_uint_32)(png_sizeof(png_uint_16) * PNG_FILTER_VALUE_LAST));
+
+ png_ptr->inv_filter_costs = (png_uint_16p)png_malloc(png_ptr,
+ (png_uint_32)(png_sizeof(png_uint_16) * PNG_FILTER_VALUE_LAST));
+
+ for (i = 0; i < PNG_FILTER_VALUE_LAST; i++)
+ {
+ png_ptr->inv_filter_costs[i] =
+ png_ptr->filter_costs[i] = PNG_COST_FACTOR;
+ }
+ }
+
+ /* Here is where we set the relative costs of the different filters. We
+ * should take the desired compression level into account when setting
+ * the costs, so that Paeth, for instance, has a high relative cost at low
+ * compression levels, while it has a lower relative cost at higher
+ * compression settings. The filter types are in order of increasing
+ * relative cost, so it would be possible to do this with an algorithm.
+ */
+ for (i = 0; i < PNG_FILTER_VALUE_LAST; i++)
+ {
+ if (filter_costs == NULL || filter_costs[i] < 0.0)
+ {
+ png_ptr->inv_filter_costs[i] =
+ png_ptr->filter_costs[i] = PNG_COST_FACTOR;
+ }
+ else if (filter_costs[i] >= 1.0)
+ {
+ png_ptr->inv_filter_costs[i] =
+ (png_uint_16)((double)PNG_COST_FACTOR / filter_costs[i] + 0.5);
+ png_ptr->filter_costs[i] =
+ (png_uint_16)((double)PNG_COST_FACTOR * filter_costs[i] + 0.5);
+ }
+ }
+}
+#endif /* PNG_WRITE_WEIGHTED_FILTER_SUPPORTED */
+
+void PNGAPI
+png_set_compression_level(png_structp png_ptr, int level)
+{
+ png_debug(1, "in png_set_compression_level");
+
+ if (png_ptr == NULL)
+ return;
+ png_ptr->flags |= PNG_FLAG_ZLIB_CUSTOM_LEVEL;
+ png_ptr->zlib_level = level;
+}
+
+void PNGAPI
+png_set_compression_mem_level(png_structp png_ptr, int mem_level)
+{
+ png_debug(1, "in png_set_compression_mem_level");
+
+ if (png_ptr == NULL)
+ return;
+ png_ptr->flags |= PNG_FLAG_ZLIB_CUSTOM_MEM_LEVEL;
+ png_ptr->zlib_mem_level = mem_level;
+}
+
+void PNGAPI
+png_set_compression_strategy(png_structp png_ptr, int strategy)
+{
+ png_debug(1, "in png_set_compression_strategy");
+
+ if (png_ptr == NULL)
+ return;
+ png_ptr->flags |= PNG_FLAG_ZLIB_CUSTOM_STRATEGY;
+ png_ptr->zlib_strategy = strategy;
+}
+
+void PNGAPI
+png_set_compression_window_bits(png_structp png_ptr, int window_bits)
+{
+ if (png_ptr == NULL)
+ return;
+ if (window_bits > 15)
+ png_warning(png_ptr, "Only compression windows <= 32k supported by PNG");
+ else if (window_bits < 8)
+ png_warning(png_ptr, "Only compression windows >= 256 supported by PNG");
+#ifndef WBITS_8_OK
+ /* Avoid libpng bug with 256-byte windows */
+ if (window_bits == 8)
+ {
+ png_warning(png_ptr, "Compression window is being reset to 512");
+ window_bits = 9;
+ }
+#endif
+ png_ptr->flags |= PNG_FLAG_ZLIB_CUSTOM_WINDOW_BITS;
+ png_ptr->zlib_window_bits = window_bits;
+}
+
+void PNGAPI
+png_set_compression_method(png_structp png_ptr, int method)
+{
+ png_debug(1, "in png_set_compression_method");
+
+ if (png_ptr == NULL)
+ return;
+ if (method != 8)
+ png_warning(png_ptr, "Only compression method 8 is supported by PNG");
+ png_ptr->flags |= PNG_FLAG_ZLIB_CUSTOM_METHOD;
+ png_ptr->zlib_method = method;
+}
+
+void PNGAPI
+png_set_write_status_fn(png_structp png_ptr, png_write_status_ptr write_row_fn)
+{
+ if (png_ptr == NULL)
+ return;
+ png_ptr->write_row_fn = write_row_fn;
+}
+
+#ifdef PNG_WRITE_USER_TRANSFORM_SUPPORTED
+void PNGAPI
+png_set_write_user_transform_fn(png_structp png_ptr, png_user_transform_ptr
+ write_user_transform_fn)
+{
+ png_debug(1, "in png_set_write_user_transform_fn");
+
+ if (png_ptr == NULL)
+ return;
+ png_ptr->transformations |= PNG_USER_TRANSFORM;
+ png_ptr->write_user_transform_fn = write_user_transform_fn;
+}
+#endif
+
+
+#ifdef PNG_INFO_IMAGE_SUPPORTED
+void PNGAPI
+png_write_png(png_structp png_ptr, png_infop info_ptr,
+ int transforms, voidp params)
+{
+ if (png_ptr == NULL || info_ptr == NULL)
+ return;
+
+ /* Write the file header information. */
+ png_write_info(png_ptr, info_ptr);
+
+ /* ------ these transformations don't touch the info structure ------- */
+
+#ifdef PNG_WRITE_INVERT_SUPPORTED
+ /* Invert monochrome pixels */
+ if (transforms & PNG_TRANSFORM_INVERT_MONO)
+ png_set_invert_mono(png_ptr);
+#endif
+
+#ifdef PNG_WRITE_SHIFT_SUPPORTED
+ /* Shift the pixels up to a legal bit depth and fill in
+ * as appropriate to correctly scale the image.
+ */
+ if ((transforms & PNG_TRANSFORM_SHIFT)
+ && (info_ptr->valid & PNG_INFO_sBIT))
+ png_set_shift(png_ptr, &info_ptr->sig_bit);
+#endif
+
+#ifdef PNG_WRITE_PACK_SUPPORTED
+ /* Pack pixels into bytes */
+ if (transforms & PNG_TRANSFORM_PACKING)
+ png_set_packing(png_ptr);
+#endif
+
+#ifdef PNG_WRITE_SWAP_ALPHA_SUPPORTED
+ /* Swap location of alpha bytes from ARGB to RGBA */
+ if (transforms & PNG_TRANSFORM_SWAP_ALPHA)
+ png_set_swap_alpha(png_ptr);
+#endif
+
+#ifdef PNG_WRITE_FILLER_SUPPORTED
+ /* Pack XRGB/RGBX/ARGB/RGBA into * RGB (4 channels -> 3 channels) */
+ if (transforms & PNG_TRANSFORM_STRIP_FILLER_AFTER)
+ png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);
+ else if (transforms & PNG_TRANSFORM_STRIP_FILLER_BEFORE)
+ png_set_filler(png_ptr, 0, PNG_FILLER_BEFORE);
+#endif
+
+#ifdef PNG_WRITE_BGR_SUPPORTED
+ /* Flip BGR pixels to RGB */
+ if (transforms & PNG_TRANSFORM_BGR)
+ png_set_bgr(png_ptr);
+#endif
+
+#ifdef PNG_WRITE_SWAP_SUPPORTED
+ /* Swap bytes of 16-bit files to most significant byte first */
+ if (transforms & PNG_TRANSFORM_SWAP_ENDIAN)
+ png_set_swap(png_ptr);
+#endif
+
+#ifdef PNG_WRITE_PACKSWAP_SUPPORTED
+ /* Swap bits of 1, 2, 4 bit packed pixel formats */
+ if (transforms & PNG_TRANSFORM_PACKSWAP)
+ png_set_packswap(png_ptr);
+#endif
+
+#ifdef PNG_WRITE_INVERT_ALPHA_SUPPORTED
+ /* Invert the alpha channel from opacity to transparency */
+ if (transforms & PNG_TRANSFORM_INVERT_ALPHA)
+ png_set_invert_alpha(png_ptr);
+#endif
+
+ /* ----------------------- end of transformations ------------------- */
+
+ /* Write the bits */
+ if (info_ptr->valid & PNG_INFO_IDAT)
+ png_write_image(png_ptr, info_ptr->row_pointers);
+
+ /* It is REQUIRED to call this to finish writing the rest of the file */
+ png_write_end(png_ptr, info_ptr);
+
+ transforms = transforms; /* Quiet compiler warnings */
+ params = params;
+}
+#endif
+#endif /* PNG_WRITE_SUPPORTED */
diff --git a/trunk/src/third_party/libpng/pngwtran.c b/trunk/src/third_party/libpng/pngwtran.c
new file mode 100644
index 0000000..0ce9b9b
--- /dev/null
+++ b/trunk/src/third_party/libpng/pngwtran.c
@@ -0,0 +1,582 @@
+
+/* pngwtran.c - transforms the data in a row for PNG writers
+ *
+ * Last changed in libpng 1.2.43 [February 25, 2010]
+ * Copyright (c) 1998-2010 Glenn Randers-Pehrson
+ * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger)
+ * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.)
+ *
+ * This code is released under the libpng license.
+ * For conditions of distribution and use, see the disclaimer
+ * and license in png.h
+ */
+
+#define PNG_INTERNAL
+#define PNG_NO_PEDANTIC_WARNINGS
+#include "png.h"
+#ifdef PNG_WRITE_SUPPORTED
+
+/* Transform the data according to the user's wishes. The order of
+ * transformations is significant.
+ */
+void /* PRIVATE */
+png_do_write_transformations(png_structp png_ptr)
+{
+ png_debug(1, "in png_do_write_transformations");
+
+ if (png_ptr == NULL)
+ return;
+
+#ifdef PNG_WRITE_USER_TRANSFORM_SUPPORTED
+ if (png_ptr->transformations & PNG_USER_TRANSFORM)
+ if (png_ptr->write_user_transform_fn != NULL)
+ (*(png_ptr->write_user_transform_fn)) /* User write transform
+ function */
+ (png_ptr, /* png_ptr */
+ &(png_ptr->row_info), /* row_info: */
+ /* png_uint_32 width; width of row */
+ /* png_uint_32 rowbytes; number of bytes in row */
+ /* png_byte color_type; color type of pixels */
+ /* png_byte bit_depth; bit depth of samples */
+ /* png_byte channels; number of channels (1-4) */
+ /* png_byte pixel_depth; bits per pixel (depth*channels) */
+ png_ptr->row_buf + 1); /* start of pixel data for row */
+#endif
+#ifdef PNG_WRITE_FILLER_SUPPORTED
+ if (png_ptr->transformations & PNG_FILLER)
+ png_do_strip_filler(&(png_ptr->row_info), png_ptr->row_buf + 1,
+ png_ptr->flags);
+#endif
+#ifdef PNG_WRITE_PACKSWAP_SUPPORTED
+ if (png_ptr->transformations & PNG_PACKSWAP)
+ png_do_packswap(&(png_ptr->row_info), png_ptr->row_buf + 1);
+#endif
+#ifdef PNG_WRITE_PACK_SUPPORTED
+ if (png_ptr->transformations & PNG_PACK)
+ png_do_pack(&(png_ptr->row_info), png_ptr->row_buf + 1,
+ (png_uint_32)png_ptr->bit_depth);
+#endif
+#ifdef PNG_WRITE_SWAP_SUPPORTED
+ if (png_ptr->transformations & PNG_SWAP_BYTES)
+ png_do_swap(&(png_ptr->row_info), png_ptr->row_buf + 1);
+#endif
+#ifdef PNG_WRITE_SHIFT_SUPPORTED
+ if (png_ptr->transformations & PNG_SHIFT)
+ png_do_shift(&(png_ptr->row_info), png_ptr->row_buf + 1,
+ &(png_ptr->shift));
+#endif
+#ifdef PNG_WRITE_SWAP_ALPHA_SUPPORTED
+ if (png_ptr->transformations & PNG_SWAP_ALPHA)
+ png_do_write_swap_alpha(&(png_ptr->row_info), png_ptr->row_buf + 1);
+#endif
+#ifdef PNG_WRITE_INVERT_ALPHA_SUPPORTED
+ if (png_ptr->transformations & PNG_INVERT_ALPHA)
+ png_do_write_invert_alpha(&(png_ptr->row_info), png_ptr->row_buf + 1);
+#endif
+#ifdef PNG_WRITE_BGR_SUPPORTED
+ if (png_ptr->transformations & PNG_BGR)
+ png_do_bgr(&(png_ptr->row_info), png_ptr->row_buf + 1);
+#endif
+#ifdef PNG_WRITE_INVERT_SUPPORTED
+ if (png_ptr->transformations & PNG_INVERT_MONO)
+ png_do_invert(&(png_ptr->row_info), png_ptr->row_buf + 1);
+#endif
+}
+
+#ifdef PNG_WRITE_PACK_SUPPORTED
+/* Pack pixels into bytes. Pass the true bit depth in bit_depth. The
+ * row_info bit depth should be 8 (one pixel per byte). The channels
+ * should be 1 (this only happens on grayscale and paletted images).
+ */
+void /* PRIVATE */
+png_do_pack(png_row_infop row_info, png_bytep row, png_uint_32 bit_depth)
+{
+ png_debug(1, "in png_do_pack");
+
+ if (row_info->bit_depth == 8 &&
+#ifdef PNG_USELESS_TESTS_SUPPORTED
+ row != NULL && row_info != NULL &&
+#endif
+ row_info->channels == 1)
+ {
+ switch ((int)bit_depth)
+ {
+ case 1:
+ {
+ png_bytep sp, dp;
+ int mask, v;
+ png_uint_32 i;
+ png_uint_32 row_width = row_info->width;
+
+ sp = row;
+ dp = row;
+ mask = 0x80;
+ v = 0;
+
+ for (i = 0; i < row_width; i++)
+ {
+ if (*sp != 0)
+ v |= mask;
+ sp++;
+ if (mask > 1)
+ mask >>= 1;
+ else
+ {
+ mask = 0x80;
+ *dp = (png_byte)v;
+ dp++;
+ v = 0;
+ }
+ }
+ if (mask != 0x80)
+ *dp = (png_byte)v;
+ break;
+ }
+ case 2:
+ {
+ png_bytep sp, dp;
+ int shift, v;
+ png_uint_32 i;
+ png_uint_32 row_width = row_info->width;
+
+ sp = row;
+ dp = row;
+ shift = 6;
+ v = 0;
+ for (i = 0; i < row_width; i++)
+ {
+ png_byte value;
+
+ value = (png_byte)(*sp & 0x03);
+ v |= (value << shift);
+ if (shift == 0)
+ {
+ shift = 6;
+ *dp = (png_byte)v;
+ dp++;
+ v = 0;
+ }
+ else
+ shift -= 2;
+ sp++;
+ }
+ if (shift != 6)
+ *dp = (png_byte)v;
+ break;
+ }
+ case 4:
+ {
+ png_bytep sp, dp;
+ int shift, v;
+ png_uint_32 i;
+ png_uint_32 row_width = row_info->width;
+
+ sp = row;
+ dp = row;
+ shift = 4;
+ v = 0;
+ for (i = 0; i < row_width; i++)
+ {
+ png_byte value;
+
+ value = (png_byte)(*sp & 0x0f);
+ v |= (value << shift);
+
+ if (shift == 0)
+ {
+ shift = 4;
+ *dp = (png_byte)v;
+ dp++;
+ v = 0;
+ }
+ else
+ shift -= 4;
+
+ sp++;
+ }
+ if (shift != 4)
+ *dp = (png_byte)v;
+ break;
+ }
+ }
+ row_info->bit_depth = (png_byte)bit_depth;
+ row_info->pixel_depth = (png_byte)(bit_depth * row_info->channels);
+ row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth,
+ row_info->width);
+ }
+}
+#endif
+
+#ifdef PNG_WRITE_SHIFT_SUPPORTED
+/* Shift pixel values to take advantage of whole range. Pass the
+ * true number of bits in bit_depth. The row should be packed
+ * according to row_info->bit_depth. Thus, if you had a row of
+ * bit depth 4, but the pixels only had values from 0 to 7, you
+ * would pass 3 as bit_depth, and this routine would translate the
+ * data to 0 to 15.
+ */
+void /* PRIVATE */
+png_do_shift(png_row_infop row_info, png_bytep row, png_color_8p bit_depth)
+{
+ png_debug(1, "in png_do_shift");
+
+#ifdef PNG_USELESS_TESTS_SUPPORTED
+ if (row != NULL && row_info != NULL &&
+#else
+ if (
+#endif
+ row_info->color_type != PNG_COLOR_TYPE_PALETTE)
+ {
+ int shift_start[4], shift_dec[4];
+ int channels = 0;
+
+ if (row_info->color_type & PNG_COLOR_MASK_COLOR)
+ {
+ shift_start[channels] = row_info->bit_depth - bit_depth->red;
+ shift_dec[channels] = bit_depth->red;
+ channels++;
+ shift_start[channels] = row_info->bit_depth - bit_depth->green;
+ shift_dec[channels] = bit_depth->green;
+ channels++;
+ shift_start[channels] = row_info->bit_depth - bit_depth->blue;
+ shift_dec[channels] = bit_depth->blue;
+ channels++;
+ }
+ else
+ {
+ shift_start[channels] = row_info->bit_depth - bit_depth->gray;
+ shift_dec[channels] = bit_depth->gray;
+ channels++;
+ }
+ if (row_info->color_type & PNG_COLOR_MASK_ALPHA)
+ {
+ shift_start[channels] = row_info->bit_depth - bit_depth->alpha;
+ shift_dec[channels] = bit_depth->alpha;
+ channels++;
+ }
+
+ /* With low row depths, could only be grayscale, so one channel */
+ if (row_info->bit_depth < 8)
+ {
+ png_bytep bp = row;
+ png_uint_32 i;
+ png_byte mask;
+ png_uint_32 row_bytes = row_info->rowbytes;
+
+ if (bit_depth->gray == 1 && row_info->bit_depth == 2)
+ mask = 0x55;
+ else if (row_info->bit_depth == 4 && bit_depth->gray == 3)
+ mask = 0x11;
+ else
+ mask = 0xff;
+
+ for (i = 0; i < row_bytes; i++, bp++)
+ {
+ png_uint_16 v;
+ int j;
+
+ v = *bp;
+ *bp = 0;
+ for (j = shift_start[0]; j > -shift_dec[0]; j -= shift_dec[0])
+ {
+ if (j > 0)
+ *bp |= (png_byte)((v << j) & 0xff);
+ else
+ *bp |= (png_byte)((v >> (-j)) & mask);
+ }
+ }
+ }
+ else if (row_info->bit_depth == 8)
+ {
+ png_bytep bp = row;
+ png_uint_32 i;
+ png_uint_32 istop = channels * row_info->width;
+
+ for (i = 0; i < istop; i++, bp++)
+ {
+
+ png_uint_16 v;
+ int j;
+ int c = (int)(i%channels);
+
+ v = *bp;
+ *bp = 0;
+ for (j = shift_start[c]; j > -shift_dec[c]; j -= shift_dec[c])
+ {
+ if (j > 0)
+ *bp |= (png_byte)((v << j) & 0xff);
+ else
+ *bp |= (png_byte)((v >> (-j)) & 0xff);
+ }
+ }
+ }
+ else
+ {
+ png_bytep bp;
+ png_uint_32 i;
+ png_uint_32 istop = channels * row_info->width;
+
+ for (bp = row, i = 0; i < istop; i++)
+ {
+ int c = (int)(i%channels);
+ png_uint_16 value, v;
+ int j;
+
+ v = (png_uint_16)(((png_uint_16)(*bp) << 8) + *(bp + 1));
+ value = 0;
+ for (j = shift_start[c]; j > -shift_dec[c]; j -= shift_dec[c])
+ {
+ if (j > 0)
+ value |= (png_uint_16)((v << j) & (png_uint_16)0xffff);
+ else
+ value |= (png_uint_16)((v >> (-j)) & (png_uint_16)0xffff);
+ }
+ *bp++ = (png_byte)(value >> 8);
+ *bp++ = (png_byte)(value & 0xff);
+ }
+ }
+ }
+}
+#endif
+
+#ifdef PNG_WRITE_SWAP_ALPHA_SUPPORTED
+void /* PRIVATE */
+png_do_write_swap_alpha(png_row_infop row_info, png_bytep row)
+{
+ png_debug(1, "in png_do_write_swap_alpha");
+
+#ifdef PNG_USELESS_TESTS_SUPPORTED
+ if (row != NULL && row_info != NULL)
+#endif
+ {
+ if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA)
+ {
+ /* This converts from ARGB to RGBA */
+ if (row_info->bit_depth == 8)
+ {
+ png_bytep sp, dp;
+ png_uint_32 i;
+ png_uint_32 row_width = row_info->width;
+ for (i = 0, sp = dp = row; i < row_width; i++)
+ {
+ png_byte save = *(sp++);
+ *(dp++) = *(sp++);
+ *(dp++) = *(sp++);
+ *(dp++) = *(sp++);
+ *(dp++) = save;
+ }
+ }
+ /* This converts from AARRGGBB to RRGGBBAA */
+ else
+ {
+ png_bytep sp, dp;
+ png_uint_32 i;
+ png_uint_32 row_width = row_info->width;
+
+ for (i = 0, sp = dp = row; i < row_width; i++)
+ {
+ png_byte save[2];
+ save[0] = *(sp++);
+ save[1] = *(sp++);
+ *(dp++) = *(sp++);
+ *(dp++) = *(sp++);
+ *(dp++) = *(sp++);
+ *(dp++) = *(sp++);
+ *(dp++) = *(sp++);
+ *(dp++) = *(sp++);
+ *(dp++) = save[0];
+ *(dp++) = save[1];
+ }
+ }
+ }
+ else if (row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
+ {
+ /* This converts from AG to GA */
+ if (row_info->bit_depth == 8)
+ {
+ png_bytep sp, dp;
+ png_uint_32 i;
+ png_uint_32 row_width = row_info->width;
+
+ for (i = 0, sp = dp = row; i < row_width; i++)
+ {
+ png_byte save = *(sp++);
+ *(dp++) = *(sp++);
+ *(dp++) = save;
+ }
+ }
+ /* This converts from AAGG to GGAA */
+ else
+ {
+ png_bytep sp, dp;
+ png_uint_32 i;
+ png_uint_32 row_width = row_info->width;
+
+ for (i = 0, sp = dp = row; i < row_width; i++)
+ {
+ png_byte save[2];
+ save[0] = *(sp++);
+ save[1] = *(sp++);
+ *(dp++) = *(sp++);
+ *(dp++) = *(sp++);
+ *(dp++) = save[0];
+ *(dp++) = save[1];
+ }
+ }
+ }
+ }
+}
+#endif
+
+#ifdef PNG_WRITE_INVERT_ALPHA_SUPPORTED
+void /* PRIVATE */
+png_do_write_invert_alpha(png_row_infop row_info, png_bytep row)
+{
+ png_debug(1, "in png_do_write_invert_alpha");
+
+#ifdef PNG_USELESS_TESTS_SUPPORTED
+ if (row != NULL && row_info != NULL)
+#endif
+ {
+ if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA)
+ {
+ /* This inverts the alpha channel in RGBA */
+ if (row_info->bit_depth == 8)
+ {
+ png_bytep sp, dp;
+ png_uint_32 i;
+ png_uint_32 row_width = row_info->width;
+ for (i = 0, sp = dp = row; i < row_width; i++)
+ {
+ /* Does nothing
+ *(dp++) = *(sp++);
+ *(dp++) = *(sp++);
+ *(dp++) = *(sp++);
+ */
+ sp+=3; dp = sp;
+ *(dp++) = (png_byte)(255 - *(sp++));
+ }
+ }
+ /* This inverts the alpha channel in RRGGBBAA */
+ else
+ {
+ png_bytep sp, dp;
+ png_uint_32 i;
+ png_uint_32 row_width = row_info->width;
+
+ for (i = 0, sp = dp = row; i < row_width; i++)
+ {
+ /* Does nothing
+ *(dp++) = *(sp++);
+ *(dp++) = *(sp++);
+ *(dp++) = *(sp++);
+ *(dp++) = *(sp++);
+ *(dp++) = *(sp++);
+ *(dp++) = *(sp++);
+ */
+ sp+=6; dp = sp;
+ *(dp++) = (png_byte)(255 - *(sp++));
+ *(dp++) = (png_byte)(255 - *(sp++));
+ }
+ }
+ }
+ else if (row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
+ {
+ /* This inverts the alpha channel in GA */
+ if (row_info->bit_depth == 8)
+ {
+ png_bytep sp, dp;
+ png_uint_32 i;
+ png_uint_32 row_width = row_info->width;
+
+ for (i = 0, sp = dp = row; i < row_width; i++)
+ {
+ *(dp++) = *(sp++);
+ *(dp++) = (png_byte)(255 - *(sp++));
+ }
+ }
+ /* This inverts the alpha channel in GGAA */
+ else
+ {
+ png_bytep sp, dp;
+ png_uint_32 i;
+ png_uint_32 row_width = row_info->width;
+
+ for (i = 0, sp = dp = row; i < row_width; i++)
+ {
+ /* Does nothing
+ *(dp++) = *(sp++);
+ *(dp++) = *(sp++);
+ */
+ sp+=2; dp = sp;
+ *(dp++) = (png_byte)(255 - *(sp++));
+ *(dp++) = (png_byte)(255 - *(sp++));
+ }
+ }
+ }
+ }
+}
+#endif
+
+#ifdef PNG_MNG_FEATURES_SUPPORTED
+/* Undoes intrapixel differencing */
+void /* PRIVATE */
+png_do_write_intrapixel(png_row_infop row_info, png_bytep row)
+{
+ png_debug(1, "in png_do_write_intrapixel");
+
+ if (
+#ifdef PNG_USELESS_TESTS_SUPPORTED
+ row != NULL && row_info != NULL &&
+#endif
+ (row_info->color_type & PNG_COLOR_MASK_COLOR))
+ {
+ int bytes_per_pixel;
+ png_uint_32 row_width = row_info->width;
+ if (row_info->bit_depth == 8)
+ {
+ png_bytep rp;
+ png_uint_32 i;
+
+ if (row_info->color_type == PNG_COLOR_TYPE_RGB)
+ bytes_per_pixel = 3;
+ else if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA)
+ bytes_per_pixel = 4;
+ else
+ return;
+
+ for (i = 0, rp = row; i < row_width; i++, rp += bytes_per_pixel)
+ {
+ *(rp) = (png_byte)((*rp - *(rp+1))&0xff);
+ *(rp+2) = (png_byte)((*(rp+2) - *(rp+1))&0xff);
+ }
+ }
+ else if (row_info->bit_depth == 16)
+ {
+ png_bytep rp;
+ png_uint_32 i;
+
+ if (row_info->color_type == PNG_COLOR_TYPE_RGB)
+ bytes_per_pixel = 6;
+ else if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA)
+ bytes_per_pixel = 8;
+ else
+ return;
+
+ for (i = 0, rp = row; i < row_width; i++, rp += bytes_per_pixel)
+ {
+ png_uint_32 s0 = (*(rp ) << 8) | *(rp+1);
+ png_uint_32 s1 = (*(rp+2) << 8) | *(rp+3);
+ png_uint_32 s2 = (*(rp+4) << 8) | *(rp+5);
+ png_uint_32 red = (png_uint_32)((s0 - s1) & 0xffffL);
+ png_uint_32 blue = (png_uint_32)((s2 - s1) & 0xffffL);
+ *(rp ) = (png_byte)((red >> 8) & 0xff);
+ *(rp+1) = (png_byte)(red & 0xff);
+ *(rp+4) = (png_byte)((blue >> 8) & 0xff);
+ *(rp+5) = (png_byte)(blue & 0xff);
+ }
+ }
+ }
+}
+#endif /* PNG_MNG_FEATURES_SUPPORTED */
+#endif /* PNG_WRITE_SUPPORTED */
diff --git a/trunk/src/third_party/libpng/pngwutil.c b/trunk/src/third_party/libpng/pngwutil.c
new file mode 100644
index 0000000..c75f53e
--- /dev/null
+++ b/trunk/src/third_party/libpng/pngwutil.c
@@ -0,0 +1,2832 @@
+
+/* pngwutil.c - utilities to write a PNG file
+ *
+ * Last changed in libpng 1.2.43 [February 25, 2010]
+ * Copyright (c) 1998-2010 Glenn Randers-Pehrson
+ * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger)
+ * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.)
+ *
+ * This code is released under the libpng license.
+ * For conditions of distribution and use, see the disclaimer
+ * and license in png.h
+ */
+
+#define PNG_INTERNAL
+#define PNG_NO_PEDANTIC_WARNINGS
+#include "png.h"
+#ifdef PNG_WRITE_SUPPORTED
+
+/* Place a 32-bit number into a buffer in PNG byte order. We work
+ * with unsigned numbers for convenience, although one supported
+ * ancillary chunk uses signed (two's complement) numbers.
+ */
+void PNGAPI
+png_save_uint_32(png_bytep buf, png_uint_32 i)
+{
+ buf[0] = (png_byte)((i >> 24) & 0xff);
+ buf[1] = (png_byte)((i >> 16) & 0xff);
+ buf[2] = (png_byte)((i >> 8) & 0xff);
+ buf[3] = (png_byte)(i & 0xff);
+}
+
+/* The png_save_int_32 function assumes integers are stored in two's
+ * complement format. If this isn't the case, then this routine needs to
+ * be modified to write data in two's complement format.
+ */
+void PNGAPI
+png_save_int_32(png_bytep buf, png_int_32 i)
+{
+ buf[0] = (png_byte)((i >> 24) & 0xff);
+ buf[1] = (png_byte)((i >> 16) & 0xff);
+ buf[2] = (png_byte)((i >> 8) & 0xff);
+ buf[3] = (png_byte)(i & 0xff);
+}
+
+/* Place a 16-bit number into a buffer in PNG byte order.
+ * The parameter is declared unsigned int, not png_uint_16,
+ * just to avoid potential problems on pre-ANSI C compilers.
+ */
+void PNGAPI
+png_save_uint_16(png_bytep buf, unsigned int i)
+{
+ buf[0] = (png_byte)((i >> 8) & 0xff);
+ buf[1] = (png_byte)(i & 0xff);
+}
+
+/* Simple function to write the signature. If we have already written
+ * the magic bytes of the signature, or more likely, the PNG stream is
+ * being embedded into another stream and doesn't need its own signature,
+ * we should call png_set_sig_bytes() to tell libpng how many of the
+ * bytes have already been written.
+ */
+void /* PRIVATE */
+png_write_sig(png_structp png_ptr)
+{
+ png_byte png_signature[8] = {137, 80, 78, 71, 13, 10, 26, 10};
+
+ /* Write the rest of the 8 byte signature */
+ png_write_data(png_ptr, &png_signature[png_ptr->sig_bytes],
+ (png_size_t)(8 - png_ptr->sig_bytes));
+ if (png_ptr->sig_bytes < 3)
+ png_ptr->mode |= PNG_HAVE_PNG_SIGNATURE;
+}
+
+/* Write a PNG chunk all at once. The type is an array of ASCII characters
+ * representing the chunk name. The array must be at least 4 bytes in
+ * length, and does not need to be null terminated. To be safe, pass the
+ * pre-defined chunk names here, and if you need a new one, define it
+ * where the others are defined. The length is the length of the data.
+ * All the data must be present. If that is not possible, use the
+ * png_write_chunk_start(), png_write_chunk_data(), and png_write_chunk_end()
+ * functions instead.
+ */
+void PNGAPI
+png_write_chunk(png_structp png_ptr, png_bytep chunk_name,
+ png_bytep data, png_size_t length)
+{
+ if (png_ptr == NULL)
+ return;
+ png_write_chunk_start(png_ptr, chunk_name, (png_uint_32)length);
+ png_write_chunk_data(png_ptr, data, (png_size_t)length);
+ png_write_chunk_end(png_ptr);
+}
+
+/* Write the start of a PNG chunk. The type is the chunk type.
+ * The total_length is the sum of the lengths of all the data you will be
+ * passing in png_write_chunk_data().
+ */
+void PNGAPI
+png_write_chunk_start(png_structp png_ptr, png_bytep chunk_name,
+ png_uint_32 length)
+{
+ png_byte buf[8];
+
+ png_debug2(0, "Writing %s chunk, length = %lu", chunk_name,
+ (unsigned long)length);
+
+ if (png_ptr == NULL)
+ return;
+
+
+ /* Write the length and the chunk name */
+ png_save_uint_32(buf, length);
+ png_memcpy(buf + 4, chunk_name, 4);
+ png_write_data(png_ptr, buf, (png_size_t)8);
+ /* Put the chunk name into png_ptr->chunk_name */
+ png_memcpy(png_ptr->chunk_name, chunk_name, 4);
+ /* Reset the crc and run it over the chunk name */
+ png_reset_crc(png_ptr);
+ png_calculate_crc(png_ptr, chunk_name, (png_size_t)4);
+}
+
+/* Write the data of a PNG chunk started with png_write_chunk_start().
+ * Note that multiple calls to this function are allowed, and that the
+ * sum of the lengths from these calls *must* add up to the total_length
+ * given to png_write_chunk_start().
+ */
+void PNGAPI
+png_write_chunk_data(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+ /* Write the data, and run the CRC over it */
+ if (png_ptr == NULL)
+ return;
+ if (data != NULL && length > 0)
+ {
+ png_write_data(png_ptr, data, length);
+ /* Update the CRC after writing the data,
+ * in case that the user I/O routine alters it.
+ */
+ png_calculate_crc(png_ptr, data, length);
+ }
+}
+
+/* Finish a chunk started with png_write_chunk_start(). */
+void PNGAPI
+png_write_chunk_end(png_structp png_ptr)
+{
+ png_byte buf[4];
+
+ if (png_ptr == NULL) return;
+
+ /* Write the crc in a single operation */
+ png_save_uint_32(buf, png_ptr->crc);
+
+ png_write_data(png_ptr, buf, (png_size_t)4);
+}
+
+#if defined(PNG_WRITE_TEXT_SUPPORTED) || defined(PNG_WRITE_iCCP_SUPPORTED)
+/* This pair of functions encapsulates the operation of (a) compressing a
+ * text string, and (b) issuing it later as a series of chunk data writes.
+ * The compression_state structure is shared context for these functions
+ * set up by the caller in order to make the whole mess thread-safe.
+ */
+
+typedef struct
+{
+ char *input; /* The uncompressed input data */
+ int input_len; /* Its length */
+ int num_output_ptr; /* Number of output pointers used */
+ int max_output_ptr; /* Size of output_ptr */
+ png_charpp output_ptr; /* Array of pointers to output */
+} compression_state;
+
+/* Compress given text into storage in the png_ptr structure */
+static int /* PRIVATE */
+png_text_compress(png_structp png_ptr,
+ png_charp text, png_size_t text_len, int compression,
+ compression_state *comp)
+{
+ int ret;
+
+ comp->num_output_ptr = 0;
+ comp->max_output_ptr = 0;
+ comp->output_ptr = NULL;
+ comp->input = NULL;
+ comp->input_len = 0;
+
+ /* We may just want to pass the text right through */
+ if (compression == PNG_TEXT_COMPRESSION_NONE)
+ {
+ comp->input = text;
+ comp->input_len = text_len;
+ return((int)text_len);
+ }
+
+ if (compression >= PNG_TEXT_COMPRESSION_LAST)
+ {
+#if defined(PNG_STDIO_SUPPORTED) && !defined(_WIN32_WCE)
+ char msg[50];
+ png_snprintf(msg, 50, "Unknown compression type %d", compression);
+ png_warning(png_ptr, msg);
+#else
+ png_warning(png_ptr, "Unknown compression type");
+#endif
+ }
+
+ /* We can't write the chunk until we find out how much data we have,
+ * which means we need to run the compressor first and save the
+ * output. This shouldn't be a problem, as the vast majority of
+ * comments should be reasonable, but we will set up an array of
+ * malloc'd pointers to be sure.
+ *
+ * If we knew the application was well behaved, we could simplify this
+ * greatly by assuming we can always malloc an output buffer large
+ * enough to hold the compressed text ((1001 * text_len / 1000) + 12)
+ * and malloc this directly. The only time this would be a bad idea is
+ * if we can't malloc more than 64K and we have 64K of random input
+ * data, or if the input string is incredibly large (although this
+ * wouldn't cause a failure, just a slowdown due to swapping).
+ */
+
+ /* Set up the compression buffers */
+ png_ptr->zstream.avail_in = (uInt)text_len;
+ png_ptr->zstream.next_in = (Bytef *)text;
+ png_ptr->zstream.avail_out = (uInt)png_ptr->zbuf_size;
+ png_ptr->zstream.next_out = (Bytef *)png_ptr->zbuf;
+
+ /* This is the same compression loop as in png_write_row() */
+ do
+ {
+ /* Compress the data */
+ ret = deflate(&png_ptr->zstream, Z_NO_FLUSH);
+ if (ret != Z_OK)
+ {
+ /* Error */
+ if (png_ptr->zstream.msg != NULL)
+ png_error(png_ptr, png_ptr->zstream.msg);
+ else
+ png_error(png_ptr, "zlib error");
+ }
+ /* Check to see if we need more room */
+ if (!(png_ptr->zstream.avail_out))
+ {
+ /* Make sure the output array has room */
+ if (comp->num_output_ptr >= comp->max_output_ptr)
+ {
+ int old_max;
+
+ old_max = comp->max_output_ptr;
+ comp->max_output_ptr = comp->num_output_ptr + 4;
+ if (comp->output_ptr != NULL)
+ {
+ png_charpp old_ptr;
+
+ old_ptr = comp->output_ptr;
+ comp->output_ptr = (png_charpp)png_malloc(png_ptr,
+ (png_uint_32)
+ (comp->max_output_ptr * png_sizeof(png_charpp)));
+ png_memcpy(comp->output_ptr, old_ptr, old_max
+ * png_sizeof(png_charp));
+ png_free(png_ptr, old_ptr);
+ }
+ else
+ comp->output_ptr = (png_charpp)png_malloc(png_ptr,
+ (png_uint_32)
+ (comp->max_output_ptr * png_sizeof(png_charp)));
+ }
+
+ /* Save the data */
+ comp->output_ptr[comp->num_output_ptr] =
+ (png_charp)png_malloc(png_ptr,
+ (png_uint_32)png_ptr->zbuf_size);
+ png_memcpy(comp->output_ptr[comp->num_output_ptr], png_ptr->zbuf,
+ png_ptr->zbuf_size);
+ comp->num_output_ptr++;
+
+ /* and reset the buffer */
+ png_ptr->zstream.avail_out = (uInt)png_ptr->zbuf_size;
+ png_ptr->zstream.next_out = png_ptr->zbuf;
+ }
+ /* Continue until we don't have any more to compress */
+ } while (png_ptr->zstream.avail_in);
+
+ /* Finish the compression */
+ do
+ {
+ /* Tell zlib we are finished */
+ ret = deflate(&png_ptr->zstream, Z_FINISH);
+
+ if (ret == Z_OK)
+ {
+ /* Check to see if we need more room */
+ if (!(png_ptr->zstream.avail_out))
+ {
+ /* Check to make sure our output array has room */
+ if (comp->num_output_ptr >= comp->max_output_ptr)
+ {
+ int old_max;
+
+ old_max = comp->max_output_ptr;
+ comp->max_output_ptr = comp->num_output_ptr + 4;
+ if (comp->output_ptr != NULL)
+ {
+ png_charpp old_ptr;
+
+ old_ptr = comp->output_ptr;
+ /* This could be optimized to realloc() */
+ comp->output_ptr = (png_charpp)png_malloc(png_ptr,
+ (png_uint_32)(comp->max_output_ptr *
+ png_sizeof(png_charp)));
+ png_memcpy(comp->output_ptr, old_ptr,
+ old_max * png_sizeof(png_charp));
+ png_free(png_ptr, old_ptr);
+ }
+ else
+ comp->output_ptr = (png_charpp)png_malloc(png_ptr,
+ (png_uint_32)(comp->max_output_ptr *
+ png_sizeof(png_charp)));
+ }
+
+ /* Save the data */
+ comp->output_ptr[comp->num_output_ptr] =
+ (png_charp)png_malloc(png_ptr,
+ (png_uint_32)png_ptr->zbuf_size);
+ png_memcpy(comp->output_ptr[comp->num_output_ptr], png_ptr->zbuf,
+ png_ptr->zbuf_size);
+ comp->num_output_ptr++;
+
+ /* and reset the buffer pointers */
+ png_ptr->zstream.avail_out = (uInt)png_ptr->zbuf_size;
+ png_ptr->zstream.next_out = png_ptr->zbuf;
+ }
+ }
+ else if (ret != Z_STREAM_END)
+ {
+ /* We got an error */
+ if (png_ptr->zstream.msg != NULL)
+ png_error(png_ptr, png_ptr->zstream.msg);
+ else
+ png_error(png_ptr, "zlib error");
+ }
+ } while (ret != Z_STREAM_END);
+
+ /* Text length is number of buffers plus last buffer */
+ text_len = png_ptr->zbuf_size * comp->num_output_ptr;
+ if (png_ptr->zstream.avail_out < png_ptr->zbuf_size)
+ text_len += png_ptr->zbuf_size - (png_size_t)png_ptr->zstream.avail_out;
+
+ return((int)text_len);
+}
+
+/* Ship the compressed text out via chunk writes */
+static void /* PRIVATE */
+png_write_compressed_data_out(png_structp png_ptr, compression_state *comp)
+{
+ int i;
+
+ /* Handle the no-compression case */
+ if (comp->input)
+ {
+ png_write_chunk_data(png_ptr, (png_bytep)comp->input,
+ (png_size_t)comp->input_len);
+ return;
+ }
+
+ /* Write saved output buffers, if any */
+ for (i = 0; i < comp->num_output_ptr; i++)
+ {
+ png_write_chunk_data(png_ptr, (png_bytep)comp->output_ptr[i],
+ (png_size_t)png_ptr->zbuf_size);
+ png_free(png_ptr, comp->output_ptr[i]);
+ comp->output_ptr[i]=NULL;
+ }
+ if (comp->max_output_ptr != 0)
+ png_free(png_ptr, comp->output_ptr);
+ comp->output_ptr=NULL;
+ /* Write anything left in zbuf */
+ if (png_ptr->zstream.avail_out < (png_uint_32)png_ptr->zbuf_size)
+ png_write_chunk_data(png_ptr, png_ptr->zbuf,
+ (png_size_t)(png_ptr->zbuf_size - png_ptr->zstream.avail_out));
+
+ /* Reset zlib for another zTXt/iTXt or image data */
+ deflateReset(&png_ptr->zstream);
+ png_ptr->zstream.data_type = Z_BINARY;
+}
+#endif
+
+/* Write the IHDR chunk, and update the png_struct with the necessary
+ * information. Note that the rest of this code depends upon this
+ * information being correct.
+ */
+void /* PRIVATE */
+png_write_IHDR(png_structp png_ptr, png_uint_32 width, png_uint_32 height,
+ int bit_depth, int color_type, int compression_type, int filter_type,
+ int interlace_type)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_IHDR;
+#endif
+ int ret;
+
+ png_byte buf[13]; /* Buffer to store the IHDR info */
+
+ png_debug(1, "in png_write_IHDR");
+
+ /* Check that we have valid input data from the application info */
+ switch (color_type)
+ {
+ case PNG_COLOR_TYPE_GRAY:
+ switch (bit_depth)
+ {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ case 16: png_ptr->channels = 1; break;
+ default: png_error(png_ptr,
+ "Invalid bit depth for grayscale image");
+ }
+ break;
+ case PNG_COLOR_TYPE_RGB:
+ if (bit_depth != 8 && bit_depth != 16)
+ png_error(png_ptr, "Invalid bit depth for RGB image");
+ png_ptr->channels = 3;
+ break;
+ case PNG_COLOR_TYPE_PALETTE:
+ switch (bit_depth)
+ {
+ case 1:
+ case 2:
+ case 4:
+ case 8: png_ptr->channels = 1; break;
+ default: png_error(png_ptr, "Invalid bit depth for paletted image");
+ }
+ break;
+ case PNG_COLOR_TYPE_GRAY_ALPHA:
+ if (bit_depth != 8 && bit_depth != 16)
+ png_error(png_ptr, "Invalid bit depth for grayscale+alpha image");
+ png_ptr->channels = 2;
+ break;
+ case PNG_COLOR_TYPE_RGB_ALPHA:
+ if (bit_depth != 8 && bit_depth != 16)
+ png_error(png_ptr, "Invalid bit depth for RGBA image");
+ png_ptr->channels = 4;
+ break;
+ default:
+ png_error(png_ptr, "Invalid image color type specified");
+ }
+
+ if (compression_type != PNG_COMPRESSION_TYPE_BASE)
+ {
+ png_warning(png_ptr, "Invalid compression type specified");
+ compression_type = PNG_COMPRESSION_TYPE_BASE;
+ }
+
+ /* Write filter_method 64 (intrapixel differencing) only if
+ * 1. Libpng was compiled with PNG_MNG_FEATURES_SUPPORTED and
+ * 2. Libpng did not write a PNG signature (this filter_method is only
+ * used in PNG datastreams that are embedded in MNG datastreams) and
+ * 3. The application called png_permit_mng_features with a mask that
+ * included PNG_FLAG_MNG_FILTER_64 and
+ * 4. The filter_method is 64 and
+ * 5. The color_type is RGB or RGBA
+ */
+ if (
+#ifdef PNG_MNG_FEATURES_SUPPORTED
+ !((png_ptr->mng_features_permitted & PNG_FLAG_MNG_FILTER_64) &&
+ ((png_ptr->mode&PNG_HAVE_PNG_SIGNATURE) == 0) &&
+ (color_type == PNG_COLOR_TYPE_RGB ||
+ color_type == PNG_COLOR_TYPE_RGB_ALPHA) &&
+ (filter_type == PNG_INTRAPIXEL_DIFFERENCING)) &&
+#endif
+ filter_type != PNG_FILTER_TYPE_BASE)
+ {
+ png_warning(png_ptr, "Invalid filter type specified");
+ filter_type = PNG_FILTER_TYPE_BASE;
+ }
+
+#ifdef PNG_WRITE_INTERLACING_SUPPORTED
+ if (interlace_type != PNG_INTERLACE_NONE &&
+ interlace_type != PNG_INTERLACE_ADAM7)
+ {
+ png_warning(png_ptr, "Invalid interlace type specified");
+ interlace_type = PNG_INTERLACE_ADAM7;
+ }
+#else
+ interlace_type=PNG_INTERLACE_NONE;
+#endif
+
+ /* Save the relevent information */
+ png_ptr->bit_depth = (png_byte)bit_depth;
+ png_ptr->color_type = (png_byte)color_type;
+ png_ptr->interlaced = (png_byte)interlace_type;
+#ifdef PNG_MNG_FEATURES_SUPPORTED
+ png_ptr->filter_type = (png_byte)filter_type;
+#endif
+ png_ptr->compression_type = (png_byte)compression_type;
+ png_ptr->width = width;
+ png_ptr->height = height;
+
+ png_ptr->pixel_depth = (png_byte)(bit_depth * png_ptr->channels);
+ png_ptr->rowbytes = PNG_ROWBYTES(png_ptr->pixel_depth, width);
+ /* Set the usr info, so any transformations can modify it */
+ png_ptr->usr_width = png_ptr->width;
+ png_ptr->usr_bit_depth = png_ptr->bit_depth;
+ png_ptr->usr_channels = png_ptr->channels;
+
+ /* Pack the header information into the buffer */
+ png_save_uint_32(buf, width);
+ png_save_uint_32(buf + 4, height);
+ buf[8] = (png_byte)bit_depth;
+ buf[9] = (png_byte)color_type;
+ buf[10] = (png_byte)compression_type;
+ buf[11] = (png_byte)filter_type;
+ buf[12] = (png_byte)interlace_type;
+
+ /* Write the chunk */
+ png_write_chunk(png_ptr, (png_bytep)png_IHDR, buf, (png_size_t)13);
+
+ /* Initialize zlib with PNG info */
+ png_ptr->zstream.zalloc = png_zalloc;
+ png_ptr->zstream.zfree = png_zfree;
+ png_ptr->zstream.opaque = (voidpf)png_ptr;
+ if (!(png_ptr->do_filter))
+ {
+ if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE ||
+ png_ptr->bit_depth < 8)
+ png_ptr->do_filter = PNG_FILTER_NONE;
+ else
+ png_ptr->do_filter = PNG_ALL_FILTERS;
+ }
+ if (!(png_ptr->flags & PNG_FLAG_ZLIB_CUSTOM_STRATEGY))
+ {
+ if (png_ptr->do_filter != PNG_FILTER_NONE)
+ png_ptr->zlib_strategy = Z_FILTERED;
+ else
+ png_ptr->zlib_strategy = Z_DEFAULT_STRATEGY;
+ }
+ if (!(png_ptr->flags & PNG_FLAG_ZLIB_CUSTOM_LEVEL))
+ png_ptr->zlib_level = Z_DEFAULT_COMPRESSION;
+ if (!(png_ptr->flags & PNG_FLAG_ZLIB_CUSTOM_MEM_LEVEL))
+ png_ptr->zlib_mem_level = 8;
+ if (!(png_ptr->flags & PNG_FLAG_ZLIB_CUSTOM_WINDOW_BITS))
+ png_ptr->zlib_window_bits = 15;
+ if (!(png_ptr->flags & PNG_FLAG_ZLIB_CUSTOM_METHOD))
+ png_ptr->zlib_method = 8;
+ ret = deflateInit2(&png_ptr->zstream, png_ptr->zlib_level,
+ png_ptr->zlib_method, png_ptr->zlib_window_bits,
+ png_ptr->zlib_mem_level, png_ptr->zlib_strategy);
+ if (ret != Z_OK)
+ {
+ if (ret == Z_VERSION_ERROR) png_error(png_ptr,
+ "zlib failed to initialize compressor -- version error");
+ if (ret == Z_STREAM_ERROR) png_error(png_ptr,
+ "zlib failed to initialize compressor -- stream error");
+ if (ret == Z_MEM_ERROR) png_error(png_ptr,
+ "zlib failed to initialize compressor -- mem error");
+ png_error(png_ptr, "zlib failed to initialize compressor");
+ }
+ png_ptr->zstream.next_out = png_ptr->zbuf;
+ png_ptr->zstream.avail_out = (uInt)png_ptr->zbuf_size;
+ /* libpng is not interested in zstream.data_type */
+ /* Set it to a predefined value, to avoid its evaluation inside zlib */
+ png_ptr->zstream.data_type = Z_BINARY;
+
+ png_ptr->mode = PNG_HAVE_IHDR;
+}
+
+/* Write the palette. We are careful not to trust png_color to be in the
+ * correct order for PNG, so people can redefine it to any convenient
+ * structure.
+ */
+void /* PRIVATE */
+png_write_PLTE(png_structp png_ptr, png_colorp palette, png_uint_32 num_pal)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_PLTE;
+#endif
+ png_uint_32 i;
+ png_colorp pal_ptr;
+ png_byte buf[3];
+
+ png_debug(1, "in png_write_PLTE");
+
+ if ((
+#ifdef PNG_MNG_FEATURES_SUPPORTED
+ !(png_ptr->mng_features_permitted & PNG_FLAG_MNG_EMPTY_PLTE) &&
+#endif
+ num_pal == 0) || num_pal > 256)
+ {
+ if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE)
+ {
+ png_error(png_ptr, "Invalid number of colors in palette");
+ }
+ else
+ {
+ png_warning(png_ptr, "Invalid number of colors in palette");
+ return;
+ }
+ }
+
+ if (!(png_ptr->color_type&PNG_COLOR_MASK_COLOR))
+ {
+ png_warning(png_ptr,
+ "Ignoring request to write a PLTE chunk in grayscale PNG");
+ return;
+ }
+
+ png_ptr->num_palette = (png_uint_16)num_pal;
+ png_debug1(3, "num_palette = %d", png_ptr->num_palette);
+
+ png_write_chunk_start(png_ptr, (png_bytep)png_PLTE,
+ (png_uint_32)(num_pal * 3));
+#ifdef PNG_POINTER_INDEXING_SUPPORTED
+ for (i = 0, pal_ptr = palette; i < num_pal; i++, pal_ptr++)
+ {
+ buf[0] = pal_ptr->red;
+ buf[1] = pal_ptr->green;
+ buf[2] = pal_ptr->blue;
+ png_write_chunk_data(png_ptr, buf, (png_size_t)3);
+ }
+#else
+ /* This is a little slower but some buggy compilers need to do this
+ * instead
+ */
+ pal_ptr=palette;
+ for (i = 0; i < num_pal; i++)
+ {
+ buf[0] = pal_ptr[i].red;
+ buf[1] = pal_ptr[i].green;
+ buf[2] = pal_ptr[i].blue;
+ png_write_chunk_data(png_ptr, buf, (png_size_t)3);
+ }
+#endif
+ png_write_chunk_end(png_ptr);
+ png_ptr->mode |= PNG_HAVE_PLTE;
+}
+
+/* Write an IDAT chunk */
+void /* PRIVATE */
+png_write_IDAT(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_IDAT;
+#endif
+
+ png_debug(1, "in png_write_IDAT");
+
+ /* Optimize the CMF field in the zlib stream. */
+ /* This hack of the zlib stream is compliant to the stream specification. */
+ if (!(png_ptr->mode & PNG_HAVE_IDAT) &&
+ png_ptr->compression_type == PNG_COMPRESSION_TYPE_BASE)
+ {
+ unsigned int z_cmf = data[0]; /* zlib compression method and flags */
+ if ((z_cmf & 0x0f) == 8 && (z_cmf & 0xf0) <= 0x70)
+ {
+ /* Avoid memory underflows and multiplication overflows.
+ *
+ * The conditions below are practically always satisfied;
+ * however, they still must be checked.
+ */
+ if (length >= 2 &&
+ png_ptr->height < 16384 && png_ptr->width < 16384)
+ {
+ png_uint_32 uncompressed_idat_size = png_ptr->height *
+ ((png_ptr->width *
+ png_ptr->channels * png_ptr->bit_depth + 15) >> 3);
+ unsigned int z_cinfo = z_cmf >> 4;
+ unsigned int half_z_window_size = 1 << (z_cinfo + 7);
+ while (uncompressed_idat_size <= half_z_window_size &&
+ half_z_window_size >= 256)
+ {
+ z_cinfo--;
+ half_z_window_size >>= 1;
+ }
+ z_cmf = (z_cmf & 0x0f) | (z_cinfo << 4);
+ if (data[0] != (png_byte)z_cmf)
+ {
+ data[0] = (png_byte)z_cmf;
+ data[1] &= 0xe0;
+ data[1] += (png_byte)(0x1f - ((z_cmf << 8) + data[1]) % 0x1f);
+ }
+ }
+ }
+ else
+ png_error(png_ptr,
+ "Invalid zlib compression method or flags in IDAT");
+ }
+
+ png_write_chunk(png_ptr, (png_bytep)png_IDAT, data, length);
+ png_ptr->mode |= PNG_HAVE_IDAT;
+}
+
+/* Write an IEND chunk */
+void /* PRIVATE */
+png_write_IEND(png_structp png_ptr)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_IEND;
+#endif
+
+ png_debug(1, "in png_write_IEND");
+
+ png_write_chunk(png_ptr, (png_bytep)png_IEND, png_bytep_NULL,
+ (png_size_t)0);
+ png_ptr->mode |= PNG_HAVE_IEND;
+}
+
+#ifdef PNG_WRITE_gAMA_SUPPORTED
+/* Write a gAMA chunk */
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+void /* PRIVATE */
+png_write_gAMA(png_structp png_ptr, double file_gamma)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_gAMA;
+#endif
+ png_uint_32 igamma;
+ png_byte buf[4];
+
+ png_debug(1, "in png_write_gAMA");
+
+ /* file_gamma is saved in 1/100,000ths */
+ igamma = (png_uint_32)(file_gamma * 100000.0 + 0.5);
+ png_save_uint_32(buf, igamma);
+ png_write_chunk(png_ptr, (png_bytep)png_gAMA, buf, (png_size_t)4);
+}
+#endif
+#ifdef PNG_FIXED_POINT_SUPPORTED
+void /* PRIVATE */
+png_write_gAMA_fixed(png_structp png_ptr, png_fixed_point file_gamma)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_gAMA;
+#endif
+ png_byte buf[4];
+
+ png_debug(1, "in png_write_gAMA");
+
+ /* file_gamma is saved in 1/100,000ths */
+ png_save_uint_32(buf, (png_uint_32)file_gamma);
+ png_write_chunk(png_ptr, (png_bytep)png_gAMA, buf, (png_size_t)4);
+}
+#endif
+#endif
+
+#ifdef PNG_WRITE_sRGB_SUPPORTED
+/* Write a sRGB chunk */
+void /* PRIVATE */
+png_write_sRGB(png_structp png_ptr, int srgb_intent)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_sRGB;
+#endif
+ png_byte buf[1];
+
+ png_debug(1, "in png_write_sRGB");
+
+ if (srgb_intent >= PNG_sRGB_INTENT_LAST)
+ png_warning(png_ptr,
+ "Invalid sRGB rendering intent specified");
+ buf[0]=(png_byte)srgb_intent;
+ png_write_chunk(png_ptr, (png_bytep)png_sRGB, buf, (png_size_t)1);
+}
+#endif
+
+#ifdef PNG_WRITE_iCCP_SUPPORTED
+/* Write an iCCP chunk */
+void /* PRIVATE */
+png_write_iCCP(png_structp png_ptr, png_charp name, int compression_type,
+ png_charp profile, int profile_len)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_iCCP;
+#endif
+ png_size_t name_len;
+ png_charp new_name;
+ compression_state comp;
+ int embedded_profile_len = 0;
+
+ png_debug(1, "in png_write_iCCP");
+
+ comp.num_output_ptr = 0;
+ comp.max_output_ptr = 0;
+ comp.output_ptr = NULL;
+ comp.input = NULL;
+ comp.input_len = 0;
+
+ if ((name_len = png_check_keyword(png_ptr, name,
+ &new_name)) == 0)
+ return;
+
+ if (compression_type != PNG_COMPRESSION_TYPE_BASE)
+ png_warning(png_ptr, "Unknown compression type in iCCP chunk");
+
+ if (profile == NULL)
+ profile_len = 0;
+
+ if (profile_len > 3)
+ embedded_profile_len =
+ ((*( (png_bytep)profile ))<<24) |
+ ((*( (png_bytep)profile + 1))<<16) |
+ ((*( (png_bytep)profile + 2))<< 8) |
+ ((*( (png_bytep)profile + 3)) );
+
+ if (embedded_profile_len < 0)
+ {
+ png_warning(png_ptr,
+ "Embedded profile length in iCCP chunk is negative");
+ png_free(png_ptr, new_name);
+ return;
+ }
+
+ if (profile_len < embedded_profile_len)
+ {
+ png_warning(png_ptr,
+ "Embedded profile length too large in iCCP chunk");
+ png_free(png_ptr, new_name);
+ return;
+ }
+
+ if (profile_len > embedded_profile_len)
+ {
+ png_warning(png_ptr,
+ "Truncating profile to actual length in iCCP chunk");
+ profile_len = embedded_profile_len;
+ }
+
+ if (profile_len)
+ profile_len = png_text_compress(png_ptr, profile,
+ (png_size_t)profile_len, PNG_COMPRESSION_TYPE_BASE, &comp);
+
+ /* Make sure we include the NULL after the name and the compression type */
+ png_write_chunk_start(png_ptr, (png_bytep)png_iCCP,
+ (png_uint_32)(name_len + profile_len + 2));
+ new_name[name_len + 1] = 0x00;
+ png_write_chunk_data(png_ptr, (png_bytep)new_name,
+ (png_size_t)(name_len + 2));
+
+ if (profile_len)
+ png_write_compressed_data_out(png_ptr, &comp);
+
+ png_write_chunk_end(png_ptr);
+ png_free(png_ptr, new_name);
+}
+#endif
+
+#ifdef PNG_WRITE_sPLT_SUPPORTED
+/* Write a sPLT chunk */
+void /* PRIVATE */
+png_write_sPLT(png_structp png_ptr, png_sPLT_tp spalette)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_sPLT;
+#endif
+ png_size_t name_len;
+ png_charp new_name;
+ png_byte entrybuf[10];
+ int entry_size = (spalette->depth == 8 ? 6 : 10);
+ int palette_size = entry_size * spalette->nentries;
+ png_sPLT_entryp ep;
+#ifndef PNG_POINTER_INDEXING_SUPPORTED
+ int i;
+#endif
+
+ png_debug(1, "in png_write_sPLT");
+
+ if ((name_len = png_check_keyword(png_ptr,spalette->name, &new_name))==0)
+ return;
+
+ /* Make sure we include the NULL after the name */
+ png_write_chunk_start(png_ptr, (png_bytep)png_sPLT,
+ (png_uint_32)(name_len + 2 + palette_size));
+ png_write_chunk_data(png_ptr, (png_bytep)new_name,
+ (png_size_t)(name_len + 1));
+ png_write_chunk_data(png_ptr, (png_bytep)&spalette->depth, (png_size_t)1);
+
+ /* Loop through each palette entry, writing appropriately */
+#ifdef PNG_POINTER_INDEXING_SUPPORTED
+ for (ep = spalette->entries; ep<spalette->entries + spalette->nentries; ep++)
+ {
+ if (spalette->depth == 8)
+ {
+ entrybuf[0] = (png_byte)ep->red;
+ entrybuf[1] = (png_byte)ep->green;
+ entrybuf[2] = (png_byte)ep->blue;
+ entrybuf[3] = (png_byte)ep->alpha;
+ png_save_uint_16(entrybuf + 4, ep->frequency);
+ }
+ else
+ {
+ png_save_uint_16(entrybuf + 0, ep->red);
+ png_save_uint_16(entrybuf + 2, ep->green);
+ png_save_uint_16(entrybuf + 4, ep->blue);
+ png_save_uint_16(entrybuf + 6, ep->alpha);
+ png_save_uint_16(entrybuf + 8, ep->frequency);
+ }
+ png_write_chunk_data(png_ptr, entrybuf, (png_size_t)entry_size);
+ }
+#else
+ ep=spalette->entries;
+ for (i=0; i>spalette->nentries; i++)
+ {
+ if (spalette->depth == 8)
+ {
+ entrybuf[0] = (png_byte)ep[i].red;
+ entrybuf[1] = (png_byte)ep[i].green;
+ entrybuf[2] = (png_byte)ep[i].blue;
+ entrybuf[3] = (png_byte)ep[i].alpha;
+ png_save_uint_16(entrybuf + 4, ep[i].frequency);
+ }
+ else
+ {
+ png_save_uint_16(entrybuf + 0, ep[i].red);
+ png_save_uint_16(entrybuf + 2, ep[i].green);
+ png_save_uint_16(entrybuf + 4, ep[i].blue);
+ png_save_uint_16(entrybuf + 6, ep[i].alpha);
+ png_save_uint_16(entrybuf + 8, ep[i].frequency);
+ }
+ png_write_chunk_data(png_ptr, entrybuf, (png_size_t)entry_size);
+ }
+#endif
+
+ png_write_chunk_end(png_ptr);
+ png_free(png_ptr, new_name);
+}
+#endif
+
+#ifdef PNG_WRITE_sBIT_SUPPORTED
+/* Write the sBIT chunk */
+void /* PRIVATE */
+png_write_sBIT(png_structp png_ptr, png_color_8p sbit, int color_type)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_sBIT;
+#endif
+ png_byte buf[4];
+ png_size_t size;
+
+ png_debug(1, "in png_write_sBIT");
+
+ /* Make sure we don't depend upon the order of PNG_COLOR_8 */
+ if (color_type & PNG_COLOR_MASK_COLOR)
+ {
+ png_byte maxbits;
+
+ maxbits = (png_byte)(color_type==PNG_COLOR_TYPE_PALETTE ? 8 :
+ png_ptr->usr_bit_depth);
+ if (sbit->red == 0 || sbit->red > maxbits ||
+ sbit->green == 0 || sbit->green > maxbits ||
+ sbit->blue == 0 || sbit->blue > maxbits)
+ {
+ png_warning(png_ptr, "Invalid sBIT depth specified");
+ return;
+ }
+ buf[0] = sbit->red;
+ buf[1] = sbit->green;
+ buf[2] = sbit->blue;
+ size = 3;
+ }
+ else
+ {
+ if (sbit->gray == 0 || sbit->gray > png_ptr->usr_bit_depth)
+ {
+ png_warning(png_ptr, "Invalid sBIT depth specified");
+ return;
+ }
+ buf[0] = sbit->gray;
+ size = 1;
+ }
+
+ if (color_type & PNG_COLOR_MASK_ALPHA)
+ {
+ if (sbit->alpha == 0 || sbit->alpha > png_ptr->usr_bit_depth)
+ {
+ png_warning(png_ptr, "Invalid sBIT depth specified");
+ return;
+ }
+ buf[size++] = sbit->alpha;
+ }
+
+ png_write_chunk(png_ptr, (png_bytep)png_sBIT, buf, size);
+}
+#endif
+
+#ifdef PNG_WRITE_cHRM_SUPPORTED
+/* Write the cHRM chunk */
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+void /* PRIVATE */
+png_write_cHRM(png_structp png_ptr, double white_x, double white_y,
+ double red_x, double red_y, double green_x, double green_y,
+ double blue_x, double blue_y)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_cHRM;
+#endif
+ png_byte buf[32];
+
+ png_fixed_point int_white_x, int_white_y, int_red_x, int_red_y,
+ int_green_x, int_green_y, int_blue_x, int_blue_y;
+
+ png_debug(1, "in png_write_cHRM");
+
+ int_white_x = (png_uint_32)(white_x * 100000.0 + 0.5);
+ int_white_y = (png_uint_32)(white_y * 100000.0 + 0.5);
+ int_red_x = (png_uint_32)(red_x * 100000.0 + 0.5);
+ int_red_y = (png_uint_32)(red_y * 100000.0 + 0.5);
+ int_green_x = (png_uint_32)(green_x * 100000.0 + 0.5);
+ int_green_y = (png_uint_32)(green_y * 100000.0 + 0.5);
+ int_blue_x = (png_uint_32)(blue_x * 100000.0 + 0.5);
+ int_blue_y = (png_uint_32)(blue_y * 100000.0 + 0.5);
+
+#ifdef PNG_CHECK_cHRM_SUPPORTED
+ if (png_check_cHRM_fixed(png_ptr, int_white_x, int_white_y,
+ int_red_x, int_red_y, int_green_x, int_green_y, int_blue_x, int_blue_y))
+#endif
+ {
+ /* Each value is saved in 1/100,000ths */
+
+ png_save_uint_32(buf, int_white_x);
+ png_save_uint_32(buf + 4, int_white_y);
+
+ png_save_uint_32(buf + 8, int_red_x);
+ png_save_uint_32(buf + 12, int_red_y);
+
+ png_save_uint_32(buf + 16, int_green_x);
+ png_save_uint_32(buf + 20, int_green_y);
+
+ png_save_uint_32(buf + 24, int_blue_x);
+ png_save_uint_32(buf + 28, int_blue_y);
+
+ png_write_chunk(png_ptr, (png_bytep)png_cHRM, buf, (png_size_t)32);
+ }
+}
+#endif
+#ifdef PNG_FIXED_POINT_SUPPORTED
+void /* PRIVATE */
+png_write_cHRM_fixed(png_structp png_ptr, png_fixed_point white_x,
+ png_fixed_point white_y, png_fixed_point red_x, png_fixed_point red_y,
+ png_fixed_point green_x, png_fixed_point green_y, png_fixed_point blue_x,
+ png_fixed_point blue_y)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_cHRM;
+#endif
+ png_byte buf[32];
+
+ png_debug(1, "in png_write_cHRM");
+
+ /* Each value is saved in 1/100,000ths */
+#ifdef PNG_CHECK_cHRM_SUPPORTED
+ if (png_check_cHRM_fixed(png_ptr, white_x, white_y, red_x, red_y,
+ green_x, green_y, blue_x, blue_y))
+#endif
+ {
+ png_save_uint_32(buf, (png_uint_32)white_x);
+ png_save_uint_32(buf + 4, (png_uint_32)white_y);
+
+ png_save_uint_32(buf + 8, (png_uint_32)red_x);
+ png_save_uint_32(buf + 12, (png_uint_32)red_y);
+
+ png_save_uint_32(buf + 16, (png_uint_32)green_x);
+ png_save_uint_32(buf + 20, (png_uint_32)green_y);
+
+ png_save_uint_32(buf + 24, (png_uint_32)blue_x);
+ png_save_uint_32(buf + 28, (png_uint_32)blue_y);
+
+ png_write_chunk(png_ptr, (png_bytep)png_cHRM, buf, (png_size_t)32);
+ }
+}
+#endif
+#endif
+
+#ifdef PNG_WRITE_tRNS_SUPPORTED
+/* Write the tRNS chunk */
+void /* PRIVATE */
+png_write_tRNS(png_structp png_ptr, png_bytep trans, png_color_16p tran,
+ int num_trans, int color_type)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_tRNS;
+#endif
+ png_byte buf[6];
+
+ png_debug(1, "in png_write_tRNS");
+
+ if (color_type == PNG_COLOR_TYPE_PALETTE)
+ {
+ if (num_trans <= 0 || num_trans > (int)png_ptr->num_palette)
+ {
+ png_warning(png_ptr, "Invalid number of transparent colors specified");
+ return;
+ }
+ /* Write the chunk out as it is */
+ png_write_chunk(png_ptr, (png_bytep)png_tRNS, trans,
+ (png_size_t)num_trans);
+ }
+ else if (color_type == PNG_COLOR_TYPE_GRAY)
+ {
+ /* One 16 bit value */
+ if (tran->gray >= (1 << png_ptr->bit_depth))
+ {
+ png_warning(png_ptr,
+ "Ignoring attempt to write tRNS chunk out-of-range for bit_depth");
+ return;
+ }
+ png_save_uint_16(buf, tran->gray);
+ png_write_chunk(png_ptr, (png_bytep)png_tRNS, buf, (png_size_t)2);
+ }
+ else if (color_type == PNG_COLOR_TYPE_RGB)
+ {
+ /* Three 16 bit values */
+ png_save_uint_16(buf, tran->red);
+ png_save_uint_16(buf + 2, tran->green);
+ png_save_uint_16(buf + 4, tran->blue);
+ if (png_ptr->bit_depth == 8 && (buf[0] | buf[2] | buf[4]))
+ {
+ png_warning(png_ptr,
+ "Ignoring attempt to write 16-bit tRNS chunk when bit_depth is 8");
+ return;
+ }
+ png_write_chunk(png_ptr, (png_bytep)png_tRNS, buf, (png_size_t)6);
+ }
+ else
+ {
+ png_warning(png_ptr, "Can't write tRNS with an alpha channel");
+ }
+}
+#endif
+
+#ifdef PNG_WRITE_bKGD_SUPPORTED
+/* Write the background chunk */
+void /* PRIVATE */
+png_write_bKGD(png_structp png_ptr, png_color_16p back, int color_type)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_bKGD;
+#endif
+ png_byte buf[6];
+
+ png_debug(1, "in png_write_bKGD");
+
+ if (color_type == PNG_COLOR_TYPE_PALETTE)
+ {
+ if (
+#ifdef PNG_MNG_FEATURES_SUPPORTED
+ (png_ptr->num_palette ||
+ (!(png_ptr->mng_features_permitted & PNG_FLAG_MNG_EMPTY_PLTE))) &&
+#endif
+ back->index >= png_ptr->num_palette)
+ {
+ png_warning(png_ptr, "Invalid background palette index");
+ return;
+ }
+ buf[0] = back->index;
+ png_write_chunk(png_ptr, (png_bytep)png_bKGD, buf, (png_size_t)1);
+ }
+ else if (color_type & PNG_COLOR_MASK_COLOR)
+ {
+ png_save_uint_16(buf, back->red);
+ png_save_uint_16(buf + 2, back->green);
+ png_save_uint_16(buf + 4, back->blue);
+ if (png_ptr->bit_depth == 8 && (buf[0] | buf[2] | buf[4]))
+ {
+ png_warning(png_ptr,
+ "Ignoring attempt to write 16-bit bKGD chunk when bit_depth is 8");
+ return;
+ }
+ png_write_chunk(png_ptr, (png_bytep)png_bKGD, buf, (png_size_t)6);
+ }
+ else
+ {
+ if (back->gray >= (1 << png_ptr->bit_depth))
+ {
+ png_warning(png_ptr,
+ "Ignoring attempt to write bKGD chunk out-of-range for bit_depth");
+ return;
+ }
+ png_save_uint_16(buf, back->gray);
+ png_write_chunk(png_ptr, (png_bytep)png_bKGD, buf, (png_size_t)2);
+ }
+}
+#endif
+
+#ifdef PNG_WRITE_hIST_SUPPORTED
+/* Write the histogram */
+void /* PRIVATE */
+png_write_hIST(png_structp png_ptr, png_uint_16p hist, int num_hist)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_hIST;
+#endif
+ int i;
+ png_byte buf[3];
+
+ png_debug(1, "in png_write_hIST");
+
+ if (num_hist > (int)png_ptr->num_palette)
+ {
+ png_debug2(3, "num_hist = %d, num_palette = %d", num_hist,
+ png_ptr->num_palette);
+ png_warning(png_ptr, "Invalid number of histogram entries specified");
+ return;
+ }
+
+ png_write_chunk_start(png_ptr, (png_bytep)png_hIST,
+ (png_uint_32)(num_hist * 2));
+ for (i = 0; i < num_hist; i++)
+ {
+ png_save_uint_16(buf, hist[i]);
+ png_write_chunk_data(png_ptr, buf, (png_size_t)2);
+ }
+ png_write_chunk_end(png_ptr);
+}
+#endif
+
+#if defined(PNG_WRITE_TEXT_SUPPORTED) || defined(PNG_WRITE_pCAL_SUPPORTED) || \
+ defined(PNG_WRITE_iCCP_SUPPORTED) || defined(PNG_WRITE_sPLT_SUPPORTED)
+/* Check that the tEXt or zTXt keyword is valid per PNG 1.0 specification,
+ * and if invalid, correct the keyword rather than discarding the entire
+ * chunk. The PNG 1.0 specification requires keywords 1-79 characters in
+ * length, forbids leading or trailing whitespace, multiple internal spaces,
+ * and the non-break space (0x80) from ISO 8859-1. Returns keyword length.
+ *
+ * The new_key is allocated to hold the corrected keyword and must be freed
+ * by the calling routine. This avoids problems with trying to write to
+ * static keywords without having to have duplicate copies of the strings.
+ */
+png_size_t /* PRIVATE */
+png_check_keyword(png_structp png_ptr, png_charp key, png_charpp new_key)
+{
+ png_size_t key_len;
+ png_charp kp, dp;
+ int kflag;
+ int kwarn=0;
+
+ png_debug(1, "in png_check_keyword");
+
+ *new_key = NULL;
+
+ if (key == NULL || (key_len = png_strlen(key)) == 0)
+ {
+ png_warning(png_ptr, "zero length keyword");
+ return ((png_size_t)0);
+ }
+
+ png_debug1(2, "Keyword to be checked is '%s'", key);
+
+ *new_key = (png_charp)png_malloc_warn(png_ptr, (png_uint_32)(key_len + 2));
+ if (*new_key == NULL)
+ {
+ png_warning(png_ptr, "Out of memory while procesing keyword");
+ return ((png_size_t)0);
+ }
+
+ /* Replace non-printing characters with a blank and print a warning */
+ for (kp = key, dp = *new_key; *kp != '\0'; kp++, dp++)
+ {
+ if ((png_byte)*kp < 0x20 ||
+ ((png_byte)*kp > 0x7E && (png_byte)*kp < 0xA1))
+ {
+#if defined(PNG_STDIO_SUPPORTED) && !defined(_WIN32_WCE)
+ char msg[40];
+
+ png_snprintf(msg, 40,
+ "invalid keyword character 0x%02X", (png_byte)*kp);
+ png_warning(png_ptr, msg);
+#else
+ png_warning(png_ptr, "invalid character in keyword");
+#endif
+ *dp = ' ';
+ }
+ else
+ {
+ *dp = *kp;
+ }
+ }
+ *dp = '\0';
+
+ /* Remove any trailing white space. */
+ kp = *new_key + key_len - 1;
+ if (*kp == ' ')
+ {
+ png_warning(png_ptr, "trailing spaces removed from keyword");
+
+ while (*kp == ' ')
+ {
+ *(kp--) = '\0';
+ key_len--;
+ }
+ }
+
+ /* Remove any leading white space. */
+ kp = *new_key;
+ if (*kp == ' ')
+ {
+ png_warning(png_ptr, "leading spaces removed from keyword");
+
+ while (*kp == ' ')
+ {
+ kp++;
+ key_len--;
+ }
+ }
+
+ png_debug1(2, "Checking for multiple internal spaces in '%s'", kp);
+
+ /* Remove multiple internal spaces. */
+ for (kflag = 0, dp = *new_key; *kp != '\0'; kp++)
+ {
+ if (*kp == ' ' && kflag == 0)
+ {
+ *(dp++) = *kp;
+ kflag = 1;
+ }
+ else if (*kp == ' ')
+ {
+ key_len--;
+ kwarn=1;
+ }
+ else
+ {
+ *(dp++) = *kp;
+ kflag = 0;
+ }
+ }
+ *dp = '\0';
+ if (kwarn)
+ png_warning(png_ptr, "extra interior spaces removed from keyword");
+
+ if (key_len == 0)
+ {
+ png_free(png_ptr, *new_key);
+ *new_key=NULL;
+ png_warning(png_ptr, "Zero length keyword");
+ }
+
+ if (key_len > 79)
+ {
+ png_warning(png_ptr, "keyword length must be 1 - 79 characters");
+ (*new_key)[79] = '\0';
+ key_len = 79;
+ }
+
+ return (key_len);
+}
+#endif
+
+#ifdef PNG_WRITE_tEXt_SUPPORTED
+/* Write a tEXt chunk */
+void /* PRIVATE */
+png_write_tEXt(png_structp png_ptr, png_charp key, png_charp text,
+ png_size_t text_len)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_tEXt;
+#endif
+ png_size_t key_len;
+ png_charp new_key;
+
+ png_debug(1, "in png_write_tEXt");
+
+ if ((key_len = png_check_keyword(png_ptr, key, &new_key))==0)
+ return;
+
+ if (text == NULL || *text == '\0')
+ text_len = 0;
+ else
+ text_len = png_strlen(text);
+
+ /* Make sure we include the 0 after the key */
+ png_write_chunk_start(png_ptr, (png_bytep)png_tEXt,
+ (png_uint_32)(key_len + text_len + 1));
+ /*
+ * We leave it to the application to meet PNG-1.0 requirements on the
+ * contents of the text. PNG-1.0 through PNG-1.2 discourage the use of
+ * any non-Latin-1 characters except for NEWLINE. ISO PNG will forbid them.
+ * The NUL character is forbidden by PNG-1.0 through PNG-1.2 and ISO PNG.
+ */
+ png_write_chunk_data(png_ptr, (png_bytep)new_key,
+ (png_size_t)(key_len + 1));
+ if (text_len)
+ png_write_chunk_data(png_ptr, (png_bytep)text, (png_size_t)text_len);
+
+ png_write_chunk_end(png_ptr);
+ png_free(png_ptr, new_key);
+}
+#endif
+
+#ifdef PNG_WRITE_zTXt_SUPPORTED
+/* Write a compressed text chunk */
+void /* PRIVATE */
+png_write_zTXt(png_structp png_ptr, png_charp key, png_charp text,
+ png_size_t text_len, int compression)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_zTXt;
+#endif
+ png_size_t key_len;
+ char buf[1];
+ png_charp new_key;
+ compression_state comp;
+
+ png_debug(1, "in png_write_zTXt");
+
+ comp.num_output_ptr = 0;
+ comp.max_output_ptr = 0;
+ comp.output_ptr = NULL;
+ comp.input = NULL;
+ comp.input_len = 0;
+
+ if ((key_len = png_check_keyword(png_ptr, key, &new_key))==0)
+ {
+ png_free(png_ptr, new_key);
+ return;
+ }
+
+ if (text == NULL || *text == '\0' || compression==PNG_TEXT_COMPRESSION_NONE)
+ {
+ png_write_tEXt(png_ptr, new_key, text, (png_size_t)0);
+ png_free(png_ptr, new_key);
+ return;
+ }
+
+ text_len = png_strlen(text);
+
+ /* Compute the compressed data; do it now for the length */
+ text_len = png_text_compress(png_ptr, text, text_len, compression,
+ &comp);
+
+ /* Write start of chunk */
+ png_write_chunk_start(png_ptr, (png_bytep)png_zTXt,
+ (png_uint_32)(key_len+text_len + 2));
+ /* Write key */
+ png_write_chunk_data(png_ptr, (png_bytep)new_key,
+ (png_size_t)(key_len + 1));
+ png_free(png_ptr, new_key);
+
+ buf[0] = (png_byte)compression;
+ /* Write compression */
+ png_write_chunk_data(png_ptr, (png_bytep)buf, (png_size_t)1);
+ /* Write the compressed data */
+ png_write_compressed_data_out(png_ptr, &comp);
+
+ /* Close the chunk */
+ png_write_chunk_end(png_ptr);
+}
+#endif
+
+#ifdef PNG_WRITE_iTXt_SUPPORTED
+/* Write an iTXt chunk */
+void /* PRIVATE */
+png_write_iTXt(png_structp png_ptr, int compression, png_charp key,
+ png_charp lang, png_charp lang_key, png_charp text)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_iTXt;
+#endif
+ png_size_t lang_len, key_len, lang_key_len, text_len;
+ png_charp new_lang;
+ png_charp new_key = NULL;
+ png_byte cbuf[2];
+ compression_state comp;
+
+ png_debug(1, "in png_write_iTXt");
+
+ comp.num_output_ptr = 0;
+ comp.max_output_ptr = 0;
+ comp.output_ptr = NULL;
+ comp.input = NULL;
+
+ if ((key_len = png_check_keyword(png_ptr, key, &new_key))==0)
+ return;
+
+ if ((lang_len = png_check_keyword(png_ptr, lang, &new_lang))==0)
+ {
+ png_warning(png_ptr, "Empty language field in iTXt chunk");
+ new_lang = NULL;
+ lang_len = 0;
+ }
+
+ if (lang_key == NULL)
+ lang_key_len = 0;
+ else
+ lang_key_len = png_strlen(lang_key);
+
+ if (text == NULL)
+ text_len = 0;
+ else
+ text_len = png_strlen(text);
+
+ /* Compute the compressed data; do it now for the length */
+ text_len = png_text_compress(png_ptr, text, text_len, compression-2,
+ &comp);
+
+
+ /* Make sure we include the compression flag, the compression byte,
+ * and the NULs after the key, lang, and lang_key parts */
+
+ png_write_chunk_start(png_ptr, (png_bytep)png_iTXt,
+ (png_uint_32)(
+ 5 /* comp byte, comp flag, terminators for key, lang and lang_key */
+ + key_len
+ + lang_len
+ + lang_key_len
+ + text_len));
+
+ /* We leave it to the application to meet PNG-1.0 requirements on the
+ * contents of the text. PNG-1.0 through PNG-1.2 discourage the use of
+ * any non-Latin-1 characters except for NEWLINE. ISO PNG will forbid them.
+ * The NUL character is forbidden by PNG-1.0 through PNG-1.2 and ISO PNG.
+ */
+ png_write_chunk_data(png_ptr, (png_bytep)new_key,
+ (png_size_t)(key_len + 1));
+
+ /* Set the compression flag */
+ if (compression == PNG_ITXT_COMPRESSION_NONE || \
+ compression == PNG_TEXT_COMPRESSION_NONE)
+ cbuf[0] = 0;
+ else /* compression == PNG_ITXT_COMPRESSION_zTXt */
+ cbuf[0] = 1;
+ /* Set the compression method */
+ cbuf[1] = 0;
+ png_write_chunk_data(png_ptr, cbuf, (png_size_t)2);
+
+ cbuf[0] = 0;
+ png_write_chunk_data(png_ptr, (new_lang ? (png_bytep)new_lang : cbuf),
+ (png_size_t)(lang_len + 1));
+ png_write_chunk_data(png_ptr, (lang_key ? (png_bytep)lang_key : cbuf),
+ (png_size_t)(lang_key_len + 1));
+ png_write_compressed_data_out(png_ptr, &comp);
+
+ png_write_chunk_end(png_ptr);
+ png_free(png_ptr, new_key);
+ png_free(png_ptr, new_lang);
+}
+#endif
+
+#ifdef PNG_WRITE_oFFs_SUPPORTED
+/* Write the oFFs chunk */
+void /* PRIVATE */
+png_write_oFFs(png_structp png_ptr, png_int_32 x_offset, png_int_32 y_offset,
+ int unit_type)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_oFFs;
+#endif
+ png_byte buf[9];
+
+ png_debug(1, "in png_write_oFFs");
+
+ if (unit_type >= PNG_OFFSET_LAST)
+ png_warning(png_ptr, "Unrecognized unit type for oFFs chunk");
+
+ png_save_int_32(buf, x_offset);
+ png_save_int_32(buf + 4, y_offset);
+ buf[8] = (png_byte)unit_type;
+
+ png_write_chunk(png_ptr, (png_bytep)png_oFFs, buf, (png_size_t)9);
+}
+#endif
+#ifdef PNG_WRITE_pCAL_SUPPORTED
+/* Write the pCAL chunk (described in the PNG extensions document) */
+void /* PRIVATE */
+png_write_pCAL(png_structp png_ptr, png_charp purpose, png_int_32 X0,
+ png_int_32 X1, int type, int nparams, png_charp units, png_charpp params)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_pCAL;
+#endif
+ png_size_t purpose_len, units_len, total_len;
+ png_uint_32p params_len;
+ png_byte buf[10];
+ png_charp new_purpose;
+ int i;
+
+ png_debug1(1, "in png_write_pCAL (%d parameters)", nparams);
+
+ if (type >= PNG_EQUATION_LAST)
+ png_warning(png_ptr, "Unrecognized equation type for pCAL chunk");
+
+ purpose_len = png_check_keyword(png_ptr, purpose, &new_purpose) + 1;
+ png_debug1(3, "pCAL purpose length = %d", (int)purpose_len);
+ units_len = png_strlen(units) + (nparams == 0 ? 0 : 1);
+ png_debug1(3, "pCAL units length = %d", (int)units_len);
+ total_len = purpose_len + units_len + 10;
+
+ params_len = (png_uint_32p)png_malloc(png_ptr,
+ (png_uint_32)(nparams * png_sizeof(png_uint_32)));
+
+ /* Find the length of each parameter, making sure we don't count the
+ null terminator for the last parameter. */
+ for (i = 0; i < nparams; i++)
+ {
+ params_len[i] = png_strlen(params[i]) + (i == nparams - 1 ? 0 : 1);
+ png_debug2(3, "pCAL parameter %d length = %lu", i,
+ (unsigned long) params_len[i]);
+ total_len += (png_size_t)params_len[i];
+ }
+
+ png_debug1(3, "pCAL total length = %d", (int)total_len);
+ png_write_chunk_start(png_ptr, (png_bytep)png_pCAL, (png_uint_32)total_len);
+ png_write_chunk_data(png_ptr, (png_bytep)new_purpose,
+ (png_size_t)purpose_len);
+ png_save_int_32(buf, X0);
+ png_save_int_32(buf + 4, X1);
+ buf[8] = (png_byte)type;
+ buf[9] = (png_byte)nparams;
+ png_write_chunk_data(png_ptr, buf, (png_size_t)10);
+ png_write_chunk_data(png_ptr, (png_bytep)units, (png_size_t)units_len);
+
+ png_free(png_ptr, new_purpose);
+
+ for (i = 0; i < nparams; i++)
+ {
+ png_write_chunk_data(png_ptr, (png_bytep)params[i],
+ (png_size_t)params_len[i]);
+ }
+
+ png_free(png_ptr, params_len);
+ png_write_chunk_end(png_ptr);
+}
+#endif
+
+#ifdef PNG_WRITE_sCAL_SUPPORTED
+/* Write the sCAL chunk */
+#if defined(PNG_FLOATING_POINT_SUPPORTED) && defined(PNG_STDIO_SUPPORTED)
+void /* PRIVATE */
+png_write_sCAL(png_structp png_ptr, int unit, double width, double height)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_sCAL;
+#endif
+ char buf[64];
+ png_size_t total_len;
+
+ png_debug(1, "in png_write_sCAL");
+
+ buf[0] = (char)unit;
+#ifdef _WIN32_WCE
+/* sprintf() function is not supported on WindowsCE */
+ {
+ wchar_t wc_buf[32];
+ size_t wc_len;
+ swprintf(wc_buf, TEXT("%12.12e"), width);
+ wc_len = wcslen(wc_buf);
+ WideCharToMultiByte(CP_ACP, 0, wc_buf, -1, buf + 1, wc_len, NULL,
+ NULL);
+ total_len = wc_len + 2;
+ swprintf(wc_buf, TEXT("%12.12e"), height);
+ wc_len = wcslen(wc_buf);
+ WideCharToMultiByte(CP_ACP, 0, wc_buf, -1, buf + total_len, wc_len,
+ NULL, NULL);
+ total_len += wc_len;
+ }
+#else
+ png_snprintf(buf + 1, 63, "%12.12e", width);
+ total_len = 1 + png_strlen(buf + 1) + 1;
+ png_snprintf(buf + total_len, 64-total_len, "%12.12e", height);
+ total_len += png_strlen(buf + total_len);
+#endif
+
+ png_debug1(3, "sCAL total length = %u", (unsigned int)total_len);
+ png_write_chunk(png_ptr, (png_bytep)png_sCAL, (png_bytep)buf, total_len);
+}
+#else
+#ifdef PNG_FIXED_POINT_SUPPORTED
+void /* PRIVATE */
+png_write_sCAL_s(png_structp png_ptr, int unit, png_charp width,
+ png_charp height)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_sCAL;
+#endif
+ png_byte buf[64];
+ png_size_t wlen, hlen, total_len;
+
+ png_debug(1, "in png_write_sCAL_s");
+
+ wlen = png_strlen(width);
+ hlen = png_strlen(height);
+ total_len = wlen + hlen + 2;
+ if (total_len > 64)
+ {
+ png_warning(png_ptr, "Can't write sCAL (buffer too small)");
+ return;
+ }
+
+ buf[0] = (png_byte)unit;
+ png_memcpy(buf + 1, width, wlen + 1); /* Append the '\0' here */
+ png_memcpy(buf + wlen + 2, height, hlen); /* Do NOT append the '\0' here */
+
+ png_debug1(3, "sCAL total length = %u", (unsigned int)total_len);
+ png_write_chunk(png_ptr, (png_bytep)png_sCAL, buf, total_len);
+}
+#endif
+#endif
+#endif
+
+#ifdef PNG_WRITE_pHYs_SUPPORTED
+/* Write the pHYs chunk */
+void /* PRIVATE */
+png_write_pHYs(png_structp png_ptr, png_uint_32 x_pixels_per_unit,
+ png_uint_32 y_pixels_per_unit,
+ int unit_type)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_pHYs;
+#endif
+ png_byte buf[9];
+
+ png_debug(1, "in png_write_pHYs");
+
+ if (unit_type >= PNG_RESOLUTION_LAST)
+ png_warning(png_ptr, "Unrecognized unit type for pHYs chunk");
+
+ png_save_uint_32(buf, x_pixels_per_unit);
+ png_save_uint_32(buf + 4, y_pixels_per_unit);
+ buf[8] = (png_byte)unit_type;
+
+ png_write_chunk(png_ptr, (png_bytep)png_pHYs, buf, (png_size_t)9);
+}
+#endif
+
+#ifdef PNG_WRITE_tIME_SUPPORTED
+/* Write the tIME chunk. Use either png_convert_from_struct_tm()
+ * or png_convert_from_time_t(), or fill in the structure yourself.
+ */
+void /* PRIVATE */
+png_write_tIME(png_structp png_ptr, png_timep mod_time)
+{
+#ifdef PNG_USE_LOCAL_ARRAYS
+ PNG_tIME;
+#endif
+ png_byte buf[7];
+
+ png_debug(1, "in png_write_tIME");
+
+ if (mod_time->month > 12 || mod_time->month < 1 ||
+ mod_time->day > 31 || mod_time->day < 1 ||
+ mod_time->hour > 23 || mod_time->second > 60)
+ {
+ png_warning(png_ptr, "Invalid time specified for tIME chunk");
+ return;
+ }
+
+ png_save_uint_16(buf, mod_time->year);
+ buf[2] = mod_time->month;
+ buf[3] = mod_time->day;
+ buf[4] = mod_time->hour;
+ buf[5] = mod_time->minute;
+ buf[6] = mod_time->second;
+
+ png_write_chunk(png_ptr, (png_bytep)png_tIME, buf, (png_size_t)7);
+}
+#endif
+
+/* Initializes the row writing capability of libpng */
+void /* PRIVATE */
+png_write_start_row(png_structp png_ptr)
+{
+#ifdef PNG_WRITE_INTERLACING_SUPPORTED
+ /* Arrays to facilitate easy interlacing - use pass (0 - 6) as index */
+
+ /* Start of interlace block */
+ int png_pass_start[7] = {0, 4, 0, 2, 0, 1, 0};
+
+ /* Offset to next interlace block */
+ int png_pass_inc[7] = {8, 8, 4, 4, 2, 2, 1};
+
+ /* Start of interlace block in the y direction */
+ int png_pass_ystart[7] = {0, 0, 4, 0, 2, 0, 1};
+
+ /* Offset to next interlace block in the y direction */
+ int png_pass_yinc[7] = {8, 8, 8, 4, 4, 2, 2};
+#endif
+
+ png_size_t buf_size;
+
+ png_debug(1, "in png_write_start_row");
+
+ buf_size = (png_size_t)(PNG_ROWBYTES(
+ png_ptr->usr_channels*png_ptr->usr_bit_depth, png_ptr->width) + 1);
+
+ /* Set up row buffer */
+ png_ptr->row_buf = (png_bytep)png_malloc(png_ptr,
+ (png_uint_32)buf_size);
+ png_ptr->row_buf[0] = PNG_FILTER_VALUE_NONE;
+
+#ifdef PNG_WRITE_FILTER_SUPPORTED
+ /* Set up filtering buffer, if using this filter */
+ if (png_ptr->do_filter & PNG_FILTER_SUB)
+ {
+ png_ptr->sub_row = (png_bytep)png_malloc(png_ptr,
+ (png_uint_32)(png_ptr->rowbytes + 1));
+ png_ptr->sub_row[0] = PNG_FILTER_VALUE_SUB;
+ }
+
+ /* We only need to keep the previous row if we are using one of these. */
+ if (png_ptr->do_filter & (PNG_FILTER_AVG | PNG_FILTER_UP | PNG_FILTER_PAETH))
+ {
+ /* Set up previous row buffer */
+ png_ptr->prev_row = (png_bytep)png_calloc(png_ptr,
+ (png_uint_32)buf_size);
+
+ if (png_ptr->do_filter & PNG_FILTER_UP)
+ {
+ png_ptr->up_row = (png_bytep)png_malloc(png_ptr,
+ (png_uint_32)(png_ptr->rowbytes + 1));
+ png_ptr->up_row[0] = PNG_FILTER_VALUE_UP;
+ }
+
+ if (png_ptr->do_filter & PNG_FILTER_AVG)
+ {
+ png_ptr->avg_row = (png_bytep)png_malloc(png_ptr,
+ (png_uint_32)(png_ptr->rowbytes + 1));
+ png_ptr->avg_row[0] = PNG_FILTER_VALUE_AVG;
+ }
+
+ if (png_ptr->do_filter & PNG_FILTER_PAETH)
+ {
+ png_ptr->paeth_row = (png_bytep)png_malloc(png_ptr,
+ (png_uint_32)(png_ptr->rowbytes + 1));
+ png_ptr->paeth_row[0] = PNG_FILTER_VALUE_PAETH;
+ }
+ }
+#endif /* PNG_WRITE_FILTER_SUPPORTED */
+
+#ifdef PNG_WRITE_INTERLACING_SUPPORTED
+ /* If interlaced, we need to set up width and height of pass */
+ if (png_ptr->interlaced)
+ {
+ if (!(png_ptr->transformations & PNG_INTERLACE))
+ {
+ png_ptr->num_rows = (png_ptr->height + png_pass_yinc[0] - 1 -
+ png_pass_ystart[0]) / png_pass_yinc[0];
+ png_ptr->usr_width = (png_ptr->width + png_pass_inc[0] - 1 -
+ png_pass_start[0]) / png_pass_inc[0];
+ }
+ else
+ {
+ png_ptr->num_rows = png_ptr->height;
+ png_ptr->usr_width = png_ptr->width;
+ }
+ }
+ else
+#endif
+ {
+ png_ptr->num_rows = png_ptr->height;
+ png_ptr->usr_width = png_ptr->width;
+ }
+ png_ptr->zstream.avail_out = (uInt)png_ptr->zbuf_size;
+ png_ptr->zstream.next_out = png_ptr->zbuf;
+}
+
+/* Internal use only. Called when finished processing a row of data. */
+void /* PRIVATE */
+png_write_finish_row(png_structp png_ptr)
+{
+#ifdef PNG_WRITE_INTERLACING_SUPPORTED
+ /* Arrays to facilitate easy interlacing - use pass (0 - 6) as index */
+
+ /* Start of interlace block */
+ int png_pass_start[7] = {0, 4, 0, 2, 0, 1, 0};
+
+ /* Offset to next interlace block */
+ int png_pass_inc[7] = {8, 8, 4, 4, 2, 2, 1};
+
+ /* Start of interlace block in the y direction */
+ int png_pass_ystart[7] = {0, 0, 4, 0, 2, 0, 1};
+
+ /* Offset to next interlace block in the y direction */
+ int png_pass_yinc[7] = {8, 8, 8, 4, 4, 2, 2};
+#endif
+
+ int ret;
+
+ png_debug(1, "in png_write_finish_row");
+
+ /* Next row */
+ png_ptr->row_number++;
+
+ /* See if we are done */
+ if (png_ptr->row_number < png_ptr->num_rows)
+ return;
+
+#ifdef PNG_WRITE_INTERLACING_SUPPORTED
+ /* If interlaced, go to next pass */
+ if (png_ptr->interlaced)
+ {
+ png_ptr->row_number = 0;
+ if (png_ptr->transformations & PNG_INTERLACE)
+ {
+ png_ptr->pass++;
+ }
+ else
+ {
+ /* Loop until we find a non-zero width or height pass */
+ do
+ {
+ png_ptr->pass++;
+ if (png_ptr->pass >= 7)
+ break;
+ png_ptr->usr_width = (png_ptr->width +
+ png_pass_inc[png_ptr->pass] - 1 -
+ png_pass_start[png_ptr->pass]) /
+ png_pass_inc[png_ptr->pass];
+ png_ptr->num_rows = (png_ptr->height +
+ png_pass_yinc[png_ptr->pass] - 1 -
+ png_pass_ystart[png_ptr->pass]) /
+ png_pass_yinc[png_ptr->pass];
+ if (png_ptr->transformations & PNG_INTERLACE)
+ break;
+ } while (png_ptr->usr_width == 0 || png_ptr->num_rows == 0);
+
+ }
+
+ /* Reset the row above the image for the next pass */
+ if (png_ptr->pass < 7)
+ {
+ if (png_ptr->prev_row != NULL)
+ png_memset(png_ptr->prev_row, 0,
+ (png_size_t)(PNG_ROWBYTES(png_ptr->usr_channels*
+ png_ptr->usr_bit_depth, png_ptr->width)) + 1);
+ return;
+ }
+ }
+#endif
+
+ /* If we get here, we've just written the last row, so we need
+ to flush the compressor */
+ do
+ {
+ /* Tell the compressor we are done */
+ ret = deflate(&png_ptr->zstream, Z_FINISH);
+ /* Check for an error */
+ if (ret == Z_OK)
+ {
+ /* Check to see if we need more room */
+ if (!(png_ptr->zstream.avail_out))
+ {
+ png_write_IDAT(png_ptr, png_ptr->zbuf, png_ptr->zbuf_size);
+ png_ptr->zstream.next_out = png_ptr->zbuf;
+ png_ptr->zstream.avail_out = (uInt)png_ptr->zbuf_size;
+ }
+ }
+ else if (ret != Z_STREAM_END)
+ {
+ if (png_ptr->zstream.msg != NULL)
+ png_error(png_ptr, png_ptr->zstream.msg);
+ else
+ png_error(png_ptr, "zlib error");
+ }
+ } while (ret != Z_STREAM_END);
+
+ /* Write any extra space */
+ if (png_ptr->zstream.avail_out < png_ptr->zbuf_size)
+ {
+ png_write_IDAT(png_ptr, png_ptr->zbuf, png_ptr->zbuf_size -
+ png_ptr->zstream.avail_out);
+ }
+
+ deflateReset(&png_ptr->zstream);
+ png_ptr->zstream.data_type = Z_BINARY;
+}
+
+#ifdef PNG_WRITE_INTERLACING_SUPPORTED
+/* Pick out the correct pixels for the interlace pass.
+ * The basic idea here is to go through the row with a source
+ * pointer and a destination pointer (sp and dp), and copy the
+ * correct pixels for the pass. As the row gets compacted,
+ * sp will always be >= dp, so we should never overwrite anything.
+ * See the default: case for the easiest code to understand.
+ */
+void /* PRIVATE */
+png_do_write_interlace(png_row_infop row_info, png_bytep row, int pass)
+{
+ /* Arrays to facilitate easy interlacing - use pass (0 - 6) as index */
+
+ /* Start of interlace block */
+ int png_pass_start[7] = {0, 4, 0, 2, 0, 1, 0};
+
+ /* Offset to next interlace block */
+ int png_pass_inc[7] = {8, 8, 4, 4, 2, 2, 1};
+
+ png_debug(1, "in png_do_write_interlace");
+
+ /* We don't have to do anything on the last pass (6) */
+#ifdef PNG_USELESS_TESTS_SUPPORTED
+ if (row != NULL && row_info != NULL && pass < 6)
+#else
+ if (pass < 6)
+#endif
+ {
+ /* Each pixel depth is handled separately */
+ switch (row_info->pixel_depth)
+ {
+ case 1:
+ {
+ png_bytep sp;
+ png_bytep dp;
+ int shift;
+ int d;
+ int value;
+ png_uint_32 i;
+ png_uint_32 row_width = row_info->width;
+
+ dp = row;
+ d = 0;
+ shift = 7;
+ for (i = png_pass_start[pass]; i < row_width;
+ i += png_pass_inc[pass])
+ {
+ sp = row + (png_size_t)(i >> 3);
+ value = (int)(*sp >> (7 - (int)(i & 0x07))) & 0x01;
+ d |= (value << shift);
+
+ if (shift == 0)
+ {
+ shift = 7;
+ *dp++ = (png_byte)d;
+ d = 0;
+ }
+ else
+ shift--;
+
+ }
+ if (shift != 7)
+ *dp = (png_byte)d;
+ break;
+ }
+ case 2:
+ {
+ png_bytep sp;
+ png_bytep dp;
+ int shift;
+ int d;
+ int value;
+ png_uint_32 i;
+ png_uint_32 row_width = row_info->width;
+
+ dp = row;
+ shift = 6;
+ d = 0;
+ for (i = png_pass_start[pass]; i < row_width;
+ i += png_pass_inc[pass])
+ {
+ sp = row + (png_size_t)(i >> 2);
+ value = (*sp >> ((3 - (int)(i & 0x03)) << 1)) & 0x03;
+ d |= (value << shift);
+
+ if (shift == 0)
+ {
+ shift = 6;
+ *dp++ = (png_byte)d;
+ d = 0;
+ }
+ else
+ shift -= 2;
+ }
+ if (shift != 6)
+ *dp = (png_byte)d;
+ break;
+ }
+ case 4:
+ {
+ png_bytep sp;
+ png_bytep dp;
+ int shift;
+ int d;
+ int value;
+ png_uint_32 i;
+ png_uint_32 row_width = row_info->width;
+
+ dp = row;
+ shift = 4;
+ d = 0;
+ for (i = png_pass_start[pass]; i < row_width;
+ i += png_pass_inc[pass])
+ {
+ sp = row + (png_size_t)(i >> 1);
+ value = (*sp >> ((1 - (int)(i & 0x01)) << 2)) & 0x0f;
+ d |= (value << shift);
+
+ if (shift == 0)
+ {
+ shift = 4;
+ *dp++ = (png_byte)d;
+ d = 0;
+ }
+ else
+ shift -= 4;
+ }
+ if (shift != 4)
+ *dp = (png_byte)d;
+ break;
+ }
+ default:
+ {
+ png_bytep sp;
+ png_bytep dp;
+ png_uint_32 i;
+ png_uint_32 row_width = row_info->width;
+ png_size_t pixel_bytes;
+
+ /* Start at the beginning */
+ dp = row;
+ /* Find out how many bytes each pixel takes up */
+ pixel_bytes = (row_info->pixel_depth >> 3);
+ /* Loop through the row, only looking at the pixels that
+ matter */
+ for (i = png_pass_start[pass]; i < row_width;
+ i += png_pass_inc[pass])
+ {
+ /* Find out where the original pixel is */
+ sp = row + (png_size_t)i * pixel_bytes;
+ /* Move the pixel */
+ if (dp != sp)
+ png_memcpy(dp, sp, pixel_bytes);
+ /* Next pixel */
+ dp += pixel_bytes;
+ }
+ break;
+ }
+ }
+ /* Set new row width */
+ row_info->width = (row_info->width +
+ png_pass_inc[pass] - 1 -
+ png_pass_start[pass]) /
+ png_pass_inc[pass];
+ row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth,
+ row_info->width);
+ }
+}
+#endif
+
+/* This filters the row, chooses which filter to use, if it has not already
+ * been specified by the application, and then writes the row out with the
+ * chosen filter.
+ */
+#define PNG_MAXSUM (((png_uint_32)(-1)) >> 1)
+#define PNG_HISHIFT 10
+#define PNG_LOMASK ((png_uint_32)0xffffL)
+#define PNG_HIMASK ((png_uint_32)(~PNG_LOMASK >> PNG_HISHIFT))
+void /* PRIVATE */
+png_write_find_filter(png_structp png_ptr, png_row_infop row_info)
+{
+ png_bytep best_row;
+#ifdef PNG_WRITE_FILTER_SUPPORTED
+ png_bytep prev_row, row_buf;
+ png_uint_32 mins, bpp;
+ png_byte filter_to_do = png_ptr->do_filter;
+ png_uint_32 row_bytes = row_info->rowbytes;
+#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED
+ int num_p_filters = (int)png_ptr->num_prev_filters;
+#endif
+
+ png_debug(1, "in png_write_find_filter");
+
+#ifndef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED
+ if (png_ptr->row_number == 0 && filter_to_do == PNG_ALL_FILTERS)
+ {
+ /* These will never be selected so we need not test them. */
+ filter_to_do &= ~(PNG_FILTER_UP | PNG_FILTER_PAETH);
+ }
+#endif
+
+ /* Find out how many bytes offset each pixel is */
+ bpp = (row_info->pixel_depth + 7) >> 3;
+
+ prev_row = png_ptr->prev_row;
+#endif
+ best_row = png_ptr->row_buf;
+#ifdef PNG_WRITE_FILTER_SUPPORTED
+ row_buf = best_row;
+ mins = PNG_MAXSUM;
+
+ /* The prediction method we use is to find which method provides the
+ * smallest value when summing the absolute values of the distances
+ * from zero, using anything >= 128 as negative numbers. This is known
+ * as the "minimum sum of absolute differences" heuristic. Other
+ * heuristics are the "weighted minimum sum of absolute differences"
+ * (experimental and can in theory improve compression), and the "zlib
+ * predictive" method (not implemented yet), which does test compressions
+ * of lines using different filter methods, and then chooses the
+ * (series of) filter(s) that give minimum compressed data size (VERY
+ * computationally expensive).
+ *
+ * GRR 980525: consider also
+ * (1) minimum sum of absolute differences from running average (i.e.,
+ * keep running sum of non-absolute differences & count of bytes)
+ * [track dispersion, too? restart average if dispersion too large?]
+ * (1b) minimum sum of absolute differences from sliding average, probably
+ * with window size <= deflate window (usually 32K)
+ * (2) minimum sum of squared differences from zero or running average
+ * (i.e., ~ root-mean-square approach)
+ */
+
+
+ /* We don't need to test the 'no filter' case if this is the only filter
+ * that has been chosen, as it doesn't actually do anything to the data.
+ */
+ if ((filter_to_do & PNG_FILTER_NONE) &&
+ filter_to_do != PNG_FILTER_NONE)
+ {
+ png_bytep rp;
+ png_uint_32 sum = 0;
+ png_uint_32 i;
+ int v;
+
+ for (i = 0, rp = row_buf + 1; i < row_bytes; i++, rp++)
+ {
+ v = *rp;
+ sum += (v < 128) ? v : 256 - v;
+ }
+
+#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED
+ if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED)
+ {
+ png_uint_32 sumhi, sumlo;
+ int j;
+ sumlo = sum & PNG_LOMASK;
+ sumhi = (sum >> PNG_HISHIFT) & PNG_HIMASK; /* Gives us some footroom */
+
+ /* Reduce the sum if we match any of the previous rows */
+ for (j = 0; j < num_p_filters; j++)
+ {
+ if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_NONE)
+ {
+ sumlo = (sumlo * png_ptr->filter_weights[j]) >>
+ PNG_WEIGHT_SHIFT;
+ sumhi = (sumhi * png_ptr->filter_weights[j]) >>
+ PNG_WEIGHT_SHIFT;
+ }
+ }
+
+ /* Factor in the cost of this filter (this is here for completeness,
+ * but it makes no sense to have a "cost" for the NONE filter, as
+ * it has the minimum possible computational cost - none).
+ */
+ sumlo = (sumlo * png_ptr->filter_costs[PNG_FILTER_VALUE_NONE]) >>
+ PNG_COST_SHIFT;
+ sumhi = (sumhi * png_ptr->filter_costs[PNG_FILTER_VALUE_NONE]) >>
+ PNG_COST_SHIFT;
+
+ if (sumhi > PNG_HIMASK)
+ sum = PNG_MAXSUM;
+ else
+ sum = (sumhi << PNG_HISHIFT) + sumlo;
+ }
+#endif
+ mins = sum;
+ }
+
+ /* Sub filter */
+ if (filter_to_do == PNG_FILTER_SUB)
+ /* It's the only filter so no testing is needed */
+ {
+ png_bytep rp, lp, dp;
+ png_uint_32 i;
+ for (i = 0, rp = row_buf + 1, dp = png_ptr->sub_row + 1; i < bpp;
+ i++, rp++, dp++)
+ {
+ *dp = *rp;
+ }
+ for (lp = row_buf + 1; i < row_bytes;
+ i++, rp++, lp++, dp++)
+ {
+ *dp = (png_byte)(((int)*rp - (int)*lp) & 0xff);
+ }
+ best_row = png_ptr->sub_row;
+ }
+
+ else if (filter_to_do & PNG_FILTER_SUB)
+ {
+ png_bytep rp, dp, lp;
+ png_uint_32 sum = 0, lmins = mins;
+ png_uint_32 i;
+ int v;
+
+#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED
+ /* We temporarily increase the "minimum sum" by the factor we
+ * would reduce the sum of this filter, so that we can do the
+ * early exit comparison without scaling the sum each time.
+ */
+ if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED)
+ {
+ int j;
+ png_uint_32 lmhi, lmlo;
+ lmlo = lmins & PNG_LOMASK;
+ lmhi = (lmins >> PNG_HISHIFT) & PNG_HIMASK;
+
+ for (j = 0; j < num_p_filters; j++)
+ {
+ if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_SUB)
+ {
+ lmlo = (lmlo * png_ptr->inv_filter_weights[j]) >>
+ PNG_WEIGHT_SHIFT;
+ lmhi = (lmhi * png_ptr->inv_filter_weights[j]) >>
+ PNG_WEIGHT_SHIFT;
+ }
+ }
+
+ lmlo = (lmlo * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_SUB]) >>
+ PNG_COST_SHIFT;
+ lmhi = (lmhi * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_SUB]) >>
+ PNG_COST_SHIFT;
+
+ if (lmhi > PNG_HIMASK)
+ lmins = PNG_MAXSUM;
+ else
+ lmins = (lmhi << PNG_HISHIFT) + lmlo;
+ }
+#endif
+
+ for (i = 0, rp = row_buf + 1, dp = png_ptr->sub_row + 1; i < bpp;
+ i++, rp++, dp++)
+ {
+ v = *dp = *rp;
+
+ sum += (v < 128) ? v : 256 - v;
+ }
+ for (lp = row_buf + 1; i < row_bytes;
+ i++, rp++, lp++, dp++)
+ {
+ v = *dp = (png_byte)(((int)*rp - (int)*lp) & 0xff);
+
+ sum += (v < 128) ? v : 256 - v;
+
+ if (sum > lmins) /* We are already worse, don't continue. */
+ break;
+ }
+
+#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED
+ if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED)
+ {
+ int j;
+ png_uint_32 sumhi, sumlo;
+ sumlo = sum & PNG_LOMASK;
+ sumhi = (sum >> PNG_HISHIFT) & PNG_HIMASK;
+
+ for (j = 0; j < num_p_filters; j++)
+ {
+ if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_SUB)
+ {
+ sumlo = (sumlo * png_ptr->inv_filter_weights[j]) >>
+ PNG_WEIGHT_SHIFT;
+ sumhi = (sumhi * png_ptr->inv_filter_weights[j]) >>
+ PNG_WEIGHT_SHIFT;
+ }
+ }
+
+ sumlo = (sumlo * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_SUB]) >>
+ PNG_COST_SHIFT;
+ sumhi = (sumhi * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_SUB]) >>
+ PNG_COST_SHIFT;
+
+ if (sumhi > PNG_HIMASK)
+ sum = PNG_MAXSUM;
+ else
+ sum = (sumhi << PNG_HISHIFT) + sumlo;
+ }
+#endif
+
+ if (sum < mins)
+ {
+ mins = sum;
+ best_row = png_ptr->sub_row;
+ }
+ }
+
+ /* Up filter */
+ if (filter_to_do == PNG_FILTER_UP)
+ {
+ png_bytep rp, dp, pp;
+ png_uint_32 i;
+
+ for (i = 0, rp = row_buf + 1, dp = png_ptr->up_row + 1,
+ pp = prev_row + 1; i < row_bytes;
+ i++, rp++, pp++, dp++)
+ {
+ *dp = (png_byte)(((int)*rp - (int)*pp) & 0xff);
+ }
+ best_row = png_ptr->up_row;
+ }
+
+ else if (filter_to_do & PNG_FILTER_UP)
+ {
+ png_bytep rp, dp, pp;
+ png_uint_32 sum = 0, lmins = mins;
+ png_uint_32 i;
+ int v;
+
+
+#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED
+ if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED)
+ {
+ int j;
+ png_uint_32 lmhi, lmlo;
+ lmlo = lmins & PNG_LOMASK;
+ lmhi = (lmins >> PNG_HISHIFT) & PNG_HIMASK;
+
+ for (j = 0; j < num_p_filters; j++)
+ {
+ if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_UP)
+ {
+ lmlo = (lmlo * png_ptr->inv_filter_weights[j]) >>
+ PNG_WEIGHT_SHIFT;
+ lmhi = (lmhi * png_ptr->inv_filter_weights[j]) >>
+ PNG_WEIGHT_SHIFT;
+ }
+ }
+
+ lmlo = (lmlo * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_UP]) >>
+ PNG_COST_SHIFT;
+ lmhi = (lmhi * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_UP]) >>
+ PNG_COST_SHIFT;
+
+ if (lmhi > PNG_HIMASK)
+ lmins = PNG_MAXSUM;
+ else
+ lmins = (lmhi << PNG_HISHIFT) + lmlo;
+ }
+#endif
+
+ for (i = 0, rp = row_buf + 1, dp = png_ptr->up_row + 1,
+ pp = prev_row + 1; i < row_bytes; i++)
+ {
+ v = *dp++ = (png_byte)(((int)*rp++ - (int)*pp++) & 0xff);
+
+ sum += (v < 128) ? v : 256 - v;
+
+ if (sum > lmins) /* We are already worse, don't continue. */
+ break;
+ }
+
+#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED
+ if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED)
+ {
+ int j;
+ png_uint_32 sumhi, sumlo;
+ sumlo = sum & PNG_LOMASK;
+ sumhi = (sum >> PNG_HISHIFT) & PNG_HIMASK;
+
+ for (j = 0; j < num_p_filters; j++)
+ {
+ if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_UP)
+ {
+ sumlo = (sumlo * png_ptr->filter_weights[j]) >>
+ PNG_WEIGHT_SHIFT;
+ sumhi = (sumhi * png_ptr->filter_weights[j]) >>
+ PNG_WEIGHT_SHIFT;
+ }
+ }
+
+ sumlo = (sumlo * png_ptr->filter_costs[PNG_FILTER_VALUE_UP]) >>
+ PNG_COST_SHIFT;
+ sumhi = (sumhi * png_ptr->filter_costs[PNG_FILTER_VALUE_UP]) >>
+ PNG_COST_SHIFT;
+
+ if (sumhi > PNG_HIMASK)
+ sum = PNG_MAXSUM;
+ else
+ sum = (sumhi << PNG_HISHIFT) + sumlo;
+ }
+#endif
+
+ if (sum < mins)
+ {
+ mins = sum;
+ best_row = png_ptr->up_row;
+ }
+ }
+
+ /* Avg filter */
+ if (filter_to_do == PNG_FILTER_AVG)
+ {
+ png_bytep rp, dp, pp, lp;
+ png_uint_32 i;
+ for (i = 0, rp = row_buf + 1, dp = png_ptr->avg_row + 1,
+ pp = prev_row + 1; i < bpp; i++)
+ {
+ *dp++ = (png_byte)(((int)*rp++ - ((int)*pp++ / 2)) & 0xff);
+ }
+ for (lp = row_buf + 1; i < row_bytes; i++)
+ {
+ *dp++ = (png_byte)(((int)*rp++ - (((int)*pp++ + (int)*lp++) / 2))
+ & 0xff);
+ }
+ best_row = png_ptr->avg_row;
+ }
+
+ else if (filter_to_do & PNG_FILTER_AVG)
+ {
+ png_bytep rp, dp, pp, lp;
+ png_uint_32 sum = 0, lmins = mins;
+ png_uint_32 i;
+ int v;
+
+#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED
+ if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED)
+ {
+ int j;
+ png_uint_32 lmhi, lmlo;
+ lmlo = lmins & PNG_LOMASK;
+ lmhi = (lmins >> PNG_HISHIFT) & PNG_HIMASK;
+
+ for (j = 0; j < num_p_filters; j++)
+ {
+ if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_AVG)
+ {
+ lmlo = (lmlo * png_ptr->inv_filter_weights[j]) >>
+ PNG_WEIGHT_SHIFT;
+ lmhi = (lmhi * png_ptr->inv_filter_weights[j]) >>
+ PNG_WEIGHT_SHIFT;
+ }
+ }
+
+ lmlo = (lmlo * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_AVG]) >>
+ PNG_COST_SHIFT;
+ lmhi = (lmhi * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_AVG]) >>
+ PNG_COST_SHIFT;
+
+ if (lmhi > PNG_HIMASK)
+ lmins = PNG_MAXSUM;
+ else
+ lmins = (lmhi << PNG_HISHIFT) + lmlo;
+ }
+#endif
+
+ for (i = 0, rp = row_buf + 1, dp = png_ptr->avg_row + 1,
+ pp = prev_row + 1; i < bpp; i++)
+ {
+ v = *dp++ = (png_byte)(((int)*rp++ - ((int)*pp++ / 2)) & 0xff);
+
+ sum += (v < 128) ? v : 256 - v;
+ }
+ for (lp = row_buf + 1; i < row_bytes; i++)
+ {
+ v = *dp++ =
+ (png_byte)(((int)*rp++ - (((int)*pp++ + (int)*lp++) / 2)) & 0xff);
+
+ sum += (v < 128) ? v : 256 - v;
+
+ if (sum > lmins) /* We are already worse, don't continue. */
+ break;
+ }
+
+#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED
+ if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED)
+ {
+ int j;
+ png_uint_32 sumhi, sumlo;
+ sumlo = sum & PNG_LOMASK;
+ sumhi = (sum >> PNG_HISHIFT) & PNG_HIMASK;
+
+ for (j = 0; j < num_p_filters; j++)
+ {
+ if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_NONE)
+ {
+ sumlo = (sumlo * png_ptr->filter_weights[j]) >>
+ PNG_WEIGHT_SHIFT;
+ sumhi = (sumhi * png_ptr->filter_weights[j]) >>
+ PNG_WEIGHT_SHIFT;
+ }
+ }
+
+ sumlo = (sumlo * png_ptr->filter_costs[PNG_FILTER_VALUE_AVG]) >>
+ PNG_COST_SHIFT;
+ sumhi = (sumhi * png_ptr->filter_costs[PNG_FILTER_VALUE_AVG]) >>
+ PNG_COST_SHIFT;
+
+ if (sumhi > PNG_HIMASK)
+ sum = PNG_MAXSUM;
+ else
+ sum = (sumhi << PNG_HISHIFT) + sumlo;
+ }
+#endif
+
+ if (sum < mins)
+ {
+ mins = sum;
+ best_row = png_ptr->avg_row;
+ }
+ }
+
+ /* Paeth filter */
+ if (filter_to_do == PNG_FILTER_PAETH)
+ {
+ png_bytep rp, dp, pp, cp, lp;
+ png_uint_32 i;
+ for (i = 0, rp = row_buf + 1, dp = png_ptr->paeth_row + 1,
+ pp = prev_row + 1; i < bpp; i++)
+ {
+ *dp++ = (png_byte)(((int)*rp++ - (int)*pp++) & 0xff);
+ }
+
+ for (lp = row_buf + 1, cp = prev_row + 1; i < row_bytes; i++)
+ {
+ int a, b, c, pa, pb, pc, p;
+
+ b = *pp++;
+ c = *cp++;
+ a = *lp++;
+
+ p = b - c;
+ pc = a - c;
+
+#ifdef PNG_USE_ABS
+ pa = abs(p);
+ pb = abs(pc);
+ pc = abs(p + pc);
+#else
+ pa = p < 0 ? -p : p;
+ pb = pc < 0 ? -pc : pc;
+ pc = (p + pc) < 0 ? -(p + pc) : p + pc;
+#endif
+
+ p = (pa <= pb && pa <=pc) ? a : (pb <= pc) ? b : c;
+
+ *dp++ = (png_byte)(((int)*rp++ - p) & 0xff);
+ }
+ best_row = png_ptr->paeth_row;
+ }
+
+ else if (filter_to_do & PNG_FILTER_PAETH)
+ {
+ png_bytep rp, dp, pp, cp, lp;
+ png_uint_32 sum = 0, lmins = mins;
+ png_uint_32 i;
+ int v;
+
+#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED
+ if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED)
+ {
+ int j;
+ png_uint_32 lmhi, lmlo;
+ lmlo = lmins & PNG_LOMASK;
+ lmhi = (lmins >> PNG_HISHIFT) & PNG_HIMASK;
+
+ for (j = 0; j < num_p_filters; j++)
+ {
+ if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_PAETH)
+ {
+ lmlo = (lmlo * png_ptr->inv_filter_weights[j]) >>
+ PNG_WEIGHT_SHIFT;
+ lmhi = (lmhi * png_ptr->inv_filter_weights[j]) >>
+ PNG_WEIGHT_SHIFT;
+ }
+ }
+
+ lmlo = (lmlo * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_PAETH]) >>
+ PNG_COST_SHIFT;
+ lmhi = (lmhi * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_PAETH]) >>
+ PNG_COST_SHIFT;
+
+ if (lmhi > PNG_HIMASK)
+ lmins = PNG_MAXSUM;
+ else
+ lmins = (lmhi << PNG_HISHIFT) + lmlo;
+ }
+#endif
+
+ for (i = 0, rp = row_buf + 1, dp = png_ptr->paeth_row + 1,
+ pp = prev_row + 1; i < bpp; i++)
+ {
+ v = *dp++ = (png_byte)(((int)*rp++ - (int)*pp++) & 0xff);
+
+ sum += (v < 128) ? v : 256 - v;
+ }
+
+ for (lp = row_buf + 1, cp = prev_row + 1; i < row_bytes; i++)
+ {
+ int a, b, c, pa, pb, pc, p;
+
+ b = *pp++;
+ c = *cp++;
+ a = *lp++;
+
+#ifndef PNG_SLOW_PAETH
+ p = b - c;
+ pc = a - c;
+#ifdef PNG_USE_ABS
+ pa = abs(p);
+ pb = abs(pc);
+ pc = abs(p + pc);
+#else
+ pa = p < 0 ? -p : p;
+ pb = pc < 0 ? -pc : pc;
+ pc = (p + pc) < 0 ? -(p + pc) : p + pc;
+#endif
+ p = (pa <= pb && pa <=pc) ? a : (pb <= pc) ? b : c;
+#else /* PNG_SLOW_PAETH */
+ p = a + b - c;
+ pa = abs(p - a);
+ pb = abs(p - b);
+ pc = abs(p - c);
+ if (pa <= pb && pa <= pc)
+ p = a;
+ else if (pb <= pc)
+ p = b;
+ else
+ p = c;
+#endif /* PNG_SLOW_PAETH */
+
+ v = *dp++ = (png_byte)(((int)*rp++ - p) & 0xff);
+
+ sum += (v < 128) ? v : 256 - v;
+
+ if (sum > lmins) /* We are already worse, don't continue. */
+ break;
+ }
+
+#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED
+ if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED)
+ {
+ int j;
+ png_uint_32 sumhi, sumlo;
+ sumlo = sum & PNG_LOMASK;
+ sumhi = (sum >> PNG_HISHIFT) & PNG_HIMASK;
+
+ for (j = 0; j < num_p_filters; j++)
+ {
+ if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_PAETH)
+ {
+ sumlo = (sumlo * png_ptr->filter_weights[j]) >>
+ PNG_WEIGHT_SHIFT;
+ sumhi = (sumhi * png_ptr->filter_weights[j]) >>
+ PNG_WEIGHT_SHIFT;
+ }
+ }
+
+ sumlo = (sumlo * png_ptr->filter_costs[PNG_FILTER_VALUE_PAETH]) >>
+ PNG_COST_SHIFT;
+ sumhi = (sumhi * png_ptr->filter_costs[PNG_FILTER_VALUE_PAETH]) >>
+ PNG_COST_SHIFT;
+
+ if (sumhi > PNG_HIMASK)
+ sum = PNG_MAXSUM;
+ else
+ sum = (sumhi << PNG_HISHIFT) + sumlo;
+ }
+#endif
+
+ if (sum < mins)
+ {
+ best_row = png_ptr->paeth_row;
+ }
+ }
+#endif /* PNG_WRITE_FILTER_SUPPORTED */
+ /* Do the actual writing of the filtered row data from the chosen filter. */
+
+ png_write_filtered_row(png_ptr, best_row);
+
+#ifdef PNG_WRITE_FILTER_SUPPORTED
+#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED
+ /* Save the type of filter we picked this time for future calculations */
+ if (png_ptr->num_prev_filters > 0)
+ {
+ int j;
+ for (j = 1; j < num_p_filters; j++)
+ {
+ png_ptr->prev_filters[j] = png_ptr->prev_filters[j - 1];
+ }
+ png_ptr->prev_filters[j] = best_row[0];
+ }
+#endif
+#endif /* PNG_WRITE_FILTER_SUPPORTED */
+}
+
+
+/* Do the actual writing of a previously filtered row. */
+void /* PRIVATE */
+png_write_filtered_row(png_structp png_ptr, png_bytep filtered_row)
+{
+ png_debug(1, "in png_write_filtered_row");
+
+ png_debug1(2, "filter = %d", filtered_row[0]);
+ /* Set up the zlib input buffer */
+
+ png_ptr->zstream.next_in = filtered_row;
+ png_ptr->zstream.avail_in = (uInt)png_ptr->row_info.rowbytes + 1;
+ /* Repeat until we have compressed all the data */
+ do
+ {
+ int ret; /* Return of zlib */
+
+ /* Compress the data */
+ ret = deflate(&png_ptr->zstream, Z_NO_FLUSH);
+ /* Check for compression errors */
+ if (ret != Z_OK)
+ {
+ if (png_ptr->zstream.msg != NULL)
+ png_error(png_ptr, png_ptr->zstream.msg);
+ else
+ png_error(png_ptr, "zlib error");
+ }
+
+ /* See if it is time to write another IDAT */
+ if (!(png_ptr->zstream.avail_out))
+ {
+ /* Write the IDAT and reset the zlib output buffer */
+ png_write_IDAT(png_ptr, png_ptr->zbuf, png_ptr->zbuf_size);
+ png_ptr->zstream.next_out = png_ptr->zbuf;
+ png_ptr->zstream.avail_out = (uInt)png_ptr->zbuf_size;
+ }
+ /* Repeat until all data has been compressed */
+ } while (png_ptr->zstream.avail_in);
+
+ /* Swap the current and previous rows */
+ if (png_ptr->prev_row != NULL)
+ {
+ png_bytep tptr;
+
+ tptr = png_ptr->prev_row;
+ png_ptr->prev_row = png_ptr->row_buf;
+ png_ptr->row_buf = tptr;
+ }
+
+ /* Finish row - updates counters and flushes zlib if last row */
+ png_write_finish_row(png_ptr);
+
+#ifdef PNG_WRITE_FLUSH_SUPPORTED
+ png_ptr->flush_rows++;
+
+ if (png_ptr->flush_dist > 0 &&
+ png_ptr->flush_rows >= png_ptr->flush_dist)
+ {
+ png_write_flush(png_ptr);
+ }
+#endif
+}
+#endif /* PNG_WRITE_SUPPORTED */
diff --git a/trunk/src/third_party/opencv/gen/arch/linux/ia32/include/cvconfig.h b/trunk/src/third_party/opencv/gen/arch/linux/ia32/include/cvconfig.h
new file mode 100644
index 0000000..0daa41e
--- /dev/null
+++ b/trunk/src/third_party/opencv/gen/arch/linux/ia32/include/cvconfig.h
@@ -0,0 +1,158 @@
+/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP
+ systems. This function is required for `alloca.c' support on those systems.
+ */
+/* #undef CRAY_STACKSEG_END */
+
+/* Define to 1 if using `alloca.c'. */
+/* #undef C_ALLOCA */
+
+/* Define to 1 if you have `alloca', as a function or macro. */
+/* #undef HAVE_ALLOCA 1 */
+
+/* Define to 1 if you have <alloca.h> and it should be used (not on Ultrix).
+ */
+#define HAVE_ALLOCA_H 1
+
+/* V4L capturing support */
+#define HAVE_CAMV4L
+
+/* V4L2 capturing support */
+#define HAVE_CAMV4L2
+
+/* Carbon windowing environment */
+/* #undef HAVE_CARBON */
+
+/* IEEE1394 capturing support */
+/* #undef HAVE_DC1394 */
+
+/* libdc1394 0.9.4 or 0.9.5 */
+/* #undef HAVE_DC1394_095 */
+
+/* IEEE1394 capturing support - libdc1394 v2.x */
+/* #undef HAVE_DC1394_2 */
+
+/* ffmpeg in Gentoo */
+/* #undef HAVE_GENTOO_FFMPEG */
+
+/* FFMpeg video library */
+/* #undef HAVE_FFMPEG */
+
+/* ffmpeg's libswscale */
+/* #undef HAVE_FFMPEG_SWSCALE */
+
+/* GStreamer multimedia framework */
+/* #undef HAVE_GSTREAMER */
+
+/* GStreamer with gstappsink & gstappsrc */
+/* #undef HAVE_GSTREAMER_APP */
+
+/* GTK+ 2.0 Thread support */
+/* #undef HAVE_GTHREAD */
+
+/* GTK+ 2.x toolkit */
+/* #undef HAVE_GTK */
+
+/* OpenEXR codec */
+/* #undef HAVE_ILMIMF */
+
+/* Apple ImageIO Framework */
+/* #undef HAVE_IMAGEIO */
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+/* #undef HAVE_INTTYPES_H 1 */
+
+/* JPEG-2000 codec */
+/* #undef HAVE_JASPER */
+
+/* IJG JPEG codec */
+#define HAVE_JPEG
+
+/* Define to 1 if you have the `dl' library (-ldl). */
+/* #undef HAVE_LIBDL 1 */
+
+/* Define to 1 if you have the `gomp' library (-lgomp). */
+/* #undef HAVE_LIBGOMP 1 */
+
+/* Define to 1 if you have the `m' library (-lm). */
+#define HAVE_LIBM 1
+
+/* libpng/png.h needs to be included */
+/* #undef HAVE_LIBPNG_PNG_H */
+
+/* Define to 1 if you have the `pthread' library (-lpthread). */
+#define HAVE_LIBPTHREAD 1
+
+/* Define to 1 if you have the `lrint' function. */
+/* #undef HAVE_LRINT 1 */
+
+/* PNG codec */
+#define HAVE_PNG
+
+/* Define to 1 if you have the `png_get_valid' function. */
+#define HAVE_PNG_GET_VALID 1
+
+/* png.h needs to be included */
+#define HAVE_PNG_H
+
+/* Define to 1 if you have the `png_set_tRNS_to_alpha' function. */
+#define HAVE_PNG_SET_TRNS_TO_ALPHA 1
+
+/* QuickTime video libraries */
+/* #undef HAVE_QUICKTIME */
+
+/* TIFF codec */
+/* #undef HAVE_TIFF */
+
+/* Unicap video capture library */
+/* #undef HAVE_UNICAP */
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Xine video library */
+/* #undef HAVE_XINE */
+
+/* LZ77 compression/decompression library (used for PNG) */
+#define HAVE_ZLIB
+
+/* Intel Integrated Performance Primitives */
+/* #undef HAVE_IPP */
+
+/* OpenCV compiled as static or dynamic libs */
+#define OPENCV_BUILD_SHARED_LIB
+
+/* Name of package */
+#define PACKAGE "opencv"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "opencvlibrary-devel@lists.sourceforge.net"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "opencv"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "opencv 2.1.0"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "opencv"
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "2.1.0"
+
+/* If using the C implementation of alloca, define if you know the
+ direction of stack growth for your system; otherwise it will be
+ automatically deduced at runtime.
+ STACK_DIRECTION > 0 => grows toward higher addresses
+ STACK_DIRECTION < 0 => grows toward lower addresses
+ STACK_DIRECTION = 0 => direction of growth unknown */
+/* #undef STACK_DIRECTION */
+
+/* Version number of package */
+#define VERSION "2.1.0"
+
+/* Define to 1 if your processor stores words with the most significant byte
+ first (like Motorola and SPARC, unlike Intel and VAX). */
+/* #undef WORDS_BIGENDIAN */
+
+/* Intel Threading Building Blocks */
+/* #undef HAVE_TBB */
diff --git a/trunk/src/third_party/opencv/gen/arch/linux/x64/include/cvconfig.h b/trunk/src/third_party/opencv/gen/arch/linux/x64/include/cvconfig.h
new file mode 100644
index 0000000..a9be56a
--- /dev/null
+++ b/trunk/src/third_party/opencv/gen/arch/linux/x64/include/cvconfig.h
@@ -0,0 +1,158 @@
+/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP
+ systems. This function is required for `alloca.c' support on those systems.
+ */
+/* #undef CRAY_STACKSEG_END */
+
+/* Define to 1 if using `alloca.c'. */
+/* #undef C_ALLOCA */
+
+/* Define to 1 if you have `alloca', as a function or macro. */
+/* #undef HAVE_ALLOCA */
+
+/* Define to 1 if you have <alloca.h> and it should be used (not on Ultrix).
+ */
+#define HAVE_ALLOCA_H 1
+
+/* V4L capturing support */
+#define HAVE_CAMV4L
+
+/* V4L2 capturing support */
+#define HAVE_CAMV4L2
+
+/* Carbon windowing environment */
+/* #undef HAVE_CARBON */
+
+/* IEEE1394 capturing support */
+/* #undef HAVE_DC1394 */
+
+/* libdc1394 0.9.4 or 0.9.5 */
+/* #undef HAVE_DC1394_095 */
+
+/* IEEE1394 capturing support - libdc1394 v2.x */
+/* #undef HAVE_DC1394_2 */
+
+/* ffmpeg in Gentoo */
+/* #undef HAVE_GENTOO_FFMPEG */
+
+/* FFMpeg video library */
+/* #undef HAVE_FFMPEG */
+
+/* ffmpeg's libswscale */
+/* #undef HAVE_FFMPEG_SWSCALE */
+
+/* GStreamer multimedia framework */
+/* #undef HAVE_GSTREAMER */
+
+/* GStreamer with gstappsink & gstappsrc */
+/* #undef HAVE_GSTREAMER_APP */
+
+/* GTK+ 2.0 Thread support */
+/* #undef HAVE_GTHREAD */
+
+/* GTK+ 2.x toolkit */
+/* #undef HAVE_GTK */
+
+/* OpenEXR codec */
+/* #undef HAVE_ILMIMF */
+
+/* Apple ImageIO Framework */
+/* #undef HAVE_IMAGEIO */
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+/* #undef HAVE_INTTYPES_H */
+
+/* JPEG-2000 codec */
+/* #undef HAVE_JASPER */
+
+/* IJG JPEG codec */
+#define HAVE_JPEG
+
+/* Define to 1 if you have the `dl' library (-ldl). */
+/* #undef HAVE_LIBDL */
+
+/* Define to 1 if you have the `gomp' library (-lgomp). */
+/* #undef HAVE_LIBGOMP */
+
+/* Define to 1 if you have the `m' library (-lm). */
+#define HAVE_LIBM 1
+
+/* libpng/png.h needs to be included */
+/* #undef HAVE_LIBPNG_PNG_H */
+
+/* Define to 1 if you have the `pthread' library (-lpthread). */
+#define HAVE_LIBPTHREAD 1
+
+/* Define to 1 if you have the `lrint' function. */
+/* #undef HAVE_LRINT */
+
+/* PNG codec */
+#define HAVE_PNG
+
+/* Define to 1 if you have the `png_get_valid' function. */
+#define HAVE_PNG_GET_VALID 1
+
+/* png.h needs to be included */
+#define HAVE_PNG_H
+
+/* Define to 1 if you have the `png_set_tRNS_to_alpha' function. */
+#define HAVE_PNG_SET_TRNS_TO_ALPHA 1
+
+/* QuickTime video libraries */
+/* #undef HAVE_QUICKTIME */
+
+/* TIFF codec */
+/* #undef HAVE_TIFF */
+
+/* Unicap video capture library */
+/* #undef HAVE_UNICAP */
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Xine video library */
+/* #undef HAVE_XINE */
+
+/* LZ77 compression/decompression library (used for PNG) */
+#define HAVE_ZLIB
+
+/* Intel Integrated Performance Primitives */
+/* #undef HAVE_IPP */
+
+/* OpenCV compiled as static or dynamic libs */
+#define OPENCV_BUILD_SHARED_LIB
+
+/* Name of package */
+#define PACKAGE "opencv"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "opencvlibrary-devel@lists.sourceforge.net"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "opencv"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "opencv 2.1.0"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "opencv"
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "2.1.0"
+
+/* If using the C implementation of alloca, define if you know the
+ direction of stack growth for your system; otherwise it will be
+ automatically deduced at runtime.
+ STACK_DIRECTION > 0 => grows toward higher addresses
+ STACK_DIRECTION < 0 => grows toward lower addresses
+ STACK_DIRECTION = 0 => direction of growth unknown */
+/* #undef STACK_DIRECTION */
+
+/* Version number of package */
+#define VERSION "2.1.0"
+
+/* Define to 1 if your processor stores words with the most significant byte
+ first (like Motorola and SPARC, unlike Intel and VAX). */
+/* #undef WORDS_BIGENDIAN */
+
+/* Intel Threading Building Blocks */
+/* #undef HAVE_TBB */
diff --git a/trunk/src/third_party/opencv/gen/arch/mac/ia32/include/cvconfig.h b/trunk/src/third_party/opencv/gen/arch/mac/ia32/include/cvconfig.h
new file mode 100644
index 0000000..ab7c7a6
--- /dev/null
+++ b/trunk/src/third_party/opencv/gen/arch/mac/ia32/include/cvconfig.h
@@ -0,0 +1,158 @@
+/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP
+ systems. This function is required for `alloca.c' support on those systems.
+ */
+/* #undef CRAY_STACKSEG_END */
+
+/* Define to 1 if using `alloca.c'. */
+/* #undef C_ALLOCA */
+
+/* Define to 1 if you have `alloca', as a function or macro. */
+/* #undef HAVE_ALLOCA */
+
+/* Define to 1 if you have <alloca.h> and it should be used (not on Ultrix).
+ */
+/* #undef HAVE_ALLOCA_H */
+
+/* V4L capturing support */
+/* #undef HAVE_CAMV4L */
+
+/* V4L2 capturing support */
+/* #undef HAVE_CAMV4L2 */
+
+/* Carbon windowing environment */
+/* #undef HAVE_CARBON */
+
+/* IEEE1394 capturing support */
+/* #undef HAVE_DC1394 */
+
+/* libdc1394 0.9.4 or 0.9.5 */
+/* #undef HAVE_DC1394_095 */
+
+/* IEEE1394 capturing support - libdc1394 v2.x */
+/* #undef HAVE_DC1394_2 */
+
+/* ffmpeg in Gentoo */
+/* #undef HAVE_GENTOO_FFMPEG */
+
+/* FFMpeg video library */
+/* #undef HAVE_FFMPEG */
+
+/* ffmpeg's libswscale */
+/* #undef HAVE_FFMPEG_SWSCALE */
+
+/* GStreamer multimedia framework */
+/* #undef HAVE_GSTREAMER */
+
+/* GStreamer with gstappsink & gstappsrc */
+/* #undef HAVE_GSTREAMER_APP */
+
+/* GTK+ 2.0 Thread support */
+/* #undef HAVE_GTHREAD */
+
+/* GTK+ 2.x toolkit */
+/* #undef HAVE_GTK */
+
+/* OpenEXR codec */
+/* #undef HAVE_ILMIMF */
+
+/* Apple ImageIO Framework */
+/* #undef HAVE_IMAGEIO */
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+/* #undef HAVE_INTTYPES_H */
+
+/* JPEG-2000 codec */
+/* #undef HAVE_JASPER */
+
+/* IJG JPEG codec */
+/* #undef HAVE_JPEG */
+
+/* Define to 1 if you have the `dl' library (-ldl). */
+/* #undef HAVE_LIBDL */
+
+/* Define to 1 if you have the `gomp' library (-lgomp). */
+/* #undef HAVE_LIBGOMP */
+
+/* Define to 1 if you have the `m' library (-lm). */
+/* #undef HAVE_LIBM */
+
+/* libpng/png.h needs to be included */
+/* #undef HAVE_LIBPNG_PNG_H */
+
+/* Define to 1 if you have the `pthread' library (-lpthread). */
+/* #undef HAVE_LIBPTHREAD */
+
+/* Define to 1 if you have the `lrint' function. */
+/* #undef HAVE_LRINT */
+
+/* PNG codec */
+/* #undef HAVE_PNG */
+
+/* Define to 1 if you have the `png_get_valid' function. */
+/* #undef HAVE_PNG_GET_VALID */
+
+/* png.h needs to be included */
+/* #undef HAVE_PNG_H */
+
+/* Define to 1 if you have the `png_set_tRNS_to_alpha' function. */
+/* #undef HAVE_PNG_SET_TRNS_TO_ALPHA */
+
+/* QuickTime video libraries */
+/* #undef HAVE_QUICKTIME */
+
+/* TIFF codec */
+/* #undef HAVE_TIFF */
+
+/* Unicap video capture library */
+/* #undef HAVE_UNICAP */
+
+/* Define to 1 if you have the <unistd.h> header file. */
+/* #undef HAVE_UNISTD_H */
+
+/* Xine video library */
+/* #undef HAVE_XINE */
+
+/* LZ77 compression/decompression library (used for PNG) */
+/* #undef HAVE_ZLIB */
+
+/* Intel Integrated Performance Primitives */
+/* #undef HAVE_IPP */
+
+/* OpenCV compiled as static or dynamic libs */
+#define OPENCV_BUILD_SHARED_LIB
+
+/* Name of package */
+#define PACKAGE "opencv"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "opencvlibrary-devel@lists.sourceforge.net"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "opencv"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "opencv 2.1.0"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "opencv"
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "2.1.0"
+
+/* If using the C implementation of alloca, define if you know the
+ direction of stack growth for your system; otherwise it will be
+ automatically deduced at runtime.
+ STACK_DIRECTION > 0 => grows toward higher addresses
+ STACK_DIRECTION < 0 => grows toward lower addresses
+ STACK_DIRECTION = 0 => direction of growth unknown */
+/* #undef STACK_DIRECTION */
+
+/* Version number of package */
+#define VERSION "2.1.0"
+
+/* Define to 1 if your processor stores words with the most significant byte
+ first (like Motorola and SPARC, unlike Intel and VAX). */
+/* #undef WORDS_BIGENDIAN */
+
+/* Intel Threading Building Blocks */
+/* #undef HAVE_TBB */
diff --git a/trunk/src/third_party/opencv/opencv.gyp b/trunk/src/third_party/opencv/opencv.gyp
new file mode 100644
index 0000000..dcbe0ad
--- /dev/null
+++ b/trunk/src/third_party/opencv/opencv.gyp
@@ -0,0 +1,528 @@
+# Copyright 2009 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.
+
+{
+ 'variables': {
+ 'opencv_root': '<(DEPTH)/third_party/opencv',
+ 'opencv_src': '<(opencv_root)/src',
+ 'opencv_gen': '<(DEPTH)/third_party/opencv/gen/arch/<(OS)/<(target_arch)',
+ 'opencv_system_include%': '/usr/include/opencv',
+ 'use_system_opencv%': 0,
+ },
+ 'conditions': [
+ ['use_system_opencv==0', {
+ 'targets': [
+ {
+ 'target_name': 'flann',
+ 'type': '<(library)',
+ 'sources': [
+ 'src/opencv/3rdparty/flann/algorithms/dist.cpp',
+ 'src/opencv/3rdparty/flann/flann.cpp',
+ 'src/opencv/3rdparty/flann/nn/index_testing.cpp',
+ 'src/opencv/3rdparty/flann/util/logger.cpp',
+ 'src/opencv/3rdparty/flann/util/random.cpp',
+ 'src/opencv/3rdparty/flann/util/saving.cpp',
+ ],
+ 'include_dirs': [
+ '<(opencv_src)/opencv/3rdparty/flann/algorithms',
+ '<(opencv_src)/opencv/3rdparty/flann/nn',
+ '<(opencv_src)/opencv/3rdparty/flann/util',
+ '<(opencv_src)/opencv/3rdparty/include',
+ '<(opencv_src)/opencv/3rdparty/include/flann',
+ ],
+ },
+ {
+ 'target_name': 'lapack',
+ 'type': '<(library)',
+ 'sources': [
+ 'src/opencv/3rdparty/lapack/dsyevr.c',
+ 'src/opencv/3rdparty/lapack/slalsa.c',
+ 'src/opencv/3rdparty/lapack/sorml2.c',
+ 'src/opencv/3rdparty/lapack/scopy.c',
+ 'src/opencv/3rdparty/lapack/slassq.c',
+ 'src/opencv/3rdparty/lapack/dlarre.c',
+ 'src/opencv/3rdparty/lapack/dlasq2.c',
+ 'src/opencv/3rdparty/lapack/slarfg.c',
+ 'src/opencv/3rdparty/lapack/slaeda.c',
+ 'src/opencv/3rdparty/lapack/ddot.c',
+ 'src/opencv/3rdparty/lapack/dgetri.c',
+ 'src/opencv/3rdparty/lapack/slarft.c',
+ 'src/opencv/3rdparty/lapack/sgemm.c',
+ 'src/opencv/3rdparty/lapack/slarre.c',
+ 'src/opencv/3rdparty/lapack/slasq5.c',
+ 'src/opencv/3rdparty/lapack/strtrs.c',
+ 'src/opencv/3rdparty/lapack/dgetrf.c',
+ 'src/opencv/3rdparty/lapack/spotrf.c',
+ 'src/opencv/3rdparty/lapack/slapy2.c',
+ 'src/opencv/3rdparty/lapack/sormlq.c',
+ 'src/opencv/3rdparty/lapack/dsytri.c',
+ 'src/opencv/3rdparty/lapack/slaed1.c',
+ 'src/opencv/3rdparty/lapack/slaed5.c',
+ 'src/opencv/3rdparty/lapack/dlasda.c',
+ 'src/opencv/3rdparty/lapack/sgemv.c',
+ 'src/opencv/3rdparty/lapack/strti2.c',
+ 'src/opencv/3rdparty/lapack/dsyrk.c',
+ 'src/opencv/3rdparty/lapack/dlarrk.c',
+ 'src/opencv/3rdparty/lapack/sormbr.c',
+ 'src/opencv/3rdparty/lapack/sbdsdc.c',
+ 'src/opencv/3rdparty/lapack/dlascl.c',
+ 'src/opencv/3rdparty/lapack/slanst.c',
+ 'src/opencv/3rdparty/lapack/dlanst.c',
+ 'src/opencv/3rdparty/lapack/dorml2.c',
+ 'src/opencv/3rdparty/lapack/slaset.c',
+ 'src/opencv/3rdparty/lapack/dorm2r.c',
+ 'src/opencv/3rdparty/lapack/dorgbr.c',
+ 'src/opencv/3rdparty/lapack/dstemr.c',
+ 'src/opencv/3rdparty/lapack/dlasdq.c',
+ 'src/opencv/3rdparty/lapack/dlansy.c',
+ 'src/opencv/3rdparty/lapack/dlalsd.c',
+ 'src/opencv/3rdparty/lapack/sdot.c',
+ 'src/opencv/3rdparty/lapack/dsytf2.c',
+ 'src/opencv/3rdparty/lapack/slabrd.c',
+ 'src/opencv/3rdparty/lapack/dormql.c',
+ 'src/opencv/3rdparty/lapack/ilaenv.c',
+ 'src/opencv/3rdparty/lapack/dlaed9.c',
+ 'src/opencv/3rdparty/lapack/sormqr.c',
+ 'src/opencv/3rdparty/lapack/dlange.c',
+ 'src/opencv/3rdparty/lapack/dlabrd.c',
+ 'src/opencv/3rdparty/lapack/dlasd6.c',
+ 'src/opencv/3rdparty/lapack/dpotrs.c',
+ 'src/opencv/3rdparty/lapack/dlazq3.c',
+ 'src/opencv/3rdparty/lapack/idamax.c',
+ 'src/opencv/3rdparty/lapack/dsterf.c',
+ 'src/opencv/3rdparty/lapack/slasd7.c',
+ 'src/opencv/3rdparty/lapack/dlacpy.c',
+ 'src/opencv/3rdparty/lapack/dlasd8.c',
+ 'src/opencv/3rdparty/lapack/ssyr2k.c',
+ 'src/opencv/3rdparty/lapack/slasda.c',
+ 'src/opencv/3rdparty/lapack/dormtr.c',
+ 'src/opencv/3rdparty/lapack/slalsd.c',
+ 'src/opencv/3rdparty/lapack/dlarrj.c',
+ 'src/opencv/3rdparty/lapack/dlarra.c',
+ 'src/opencv/3rdparty/lapack/dlar1v.c',
+ 'src/opencv/3rdparty/lapack/dtrtrs.c',
+ 'src/opencv/3rdparty/lapack/slasd5.c',
+ 'src/opencv/3rdparty/lapack/slaruv.c',
+ 'src/opencv/3rdparty/lapack/dlamrg.c',
+ 'src/opencv/3rdparty/lapack/slaebz.c',
+ 'src/opencv/3rdparty/lapack/dgetf2.c',
+ 'src/opencv/3rdparty/lapack/sgelqf.c',
+ 'src/opencv/3rdparty/lapack/sgebrd.c',
+ 'src/opencv/3rdparty/lapack/slartg.c',
+ 'src/opencv/3rdparty/lapack/sorglq.c',
+ 'src/opencv/3rdparty/lapack/dormlq.c',
+ 'src/opencv/3rdparty/lapack/slaed2.c',
+ 'src/opencv/3rdparty/lapack/ssytrd.c',
+ 'src/opencv/3rdparty/lapack/dlaed5.c',
+ 'src/opencv/3rdparty/lapack/dlagts.c',
+ 'src/opencv/3rdparty/lapack/slarrf.c',
+ 'src/opencv/3rdparty/lapack/sorm2l.c',
+ 'src/opencv/3rdparty/lapack/sorgqr.c',
+ 'src/opencv/3rdparty/lapack/dlasq3.c',
+ 'src/opencv/3rdparty/lapack/slaed6.c',
+ 'src/opencv/3rdparty/lapack/dgesv.c',
+ 'src/opencv/3rdparty/lapack/dlasr.c',
+ 'src/opencv/3rdparty/lapack/s_cat.c',
+ 'src/opencv/3rdparty/lapack/dlasq5.c',
+ 'src/opencv/3rdparty/lapack/dbdsqr.c',
+ 'src/opencv/3rdparty/lapack/slae2.c',
+ 'src/opencv/3rdparty/lapack/srot.c',
+ 'src/opencv/3rdparty/lapack/sormql.c',
+ 'src/opencv/3rdparty/lapack/dlasq4.c',
+ 'src/opencv/3rdparty/lapack/sgeqrf.c',
+ 'src/opencv/3rdparty/lapack/sormtr.c',
+ 'src/opencv/3rdparty/lapack/slarrb.c',
+ 'src/opencv/3rdparty/lapack/slarrd.c',
+ 'src/opencv/3rdparty/lapack/sgetri.c',
+ 'src/opencv/3rdparty/lapack/dlartg.c',
+ 'src/opencv/3rdparty/lapack/dpotf2.c',
+ 'src/opencv/3rdparty/lapack/sorg2r.c',
+ 'src/opencv/3rdparty/lapack/dsytrd.c',
+ 'src/opencv/3rdparty/lapack/dgels.c',
+ 'src/opencv/3rdparty/lapack/slasd4.c',
+ 'src/opencv/3rdparty/lapack/slatrd.c',
+ 'src/opencv/3rdparty/lapack/ieeeck.c',
+ 'src/opencv/3rdparty/lapack/slasq4.c',
+ 'src/opencv/3rdparty/lapack/slarrv.c',
+ 'src/opencv/3rdparty/lapack/dtrmv.c',
+ 'src/opencv/3rdparty/lapack/dlaed1.c',
+ 'src/opencv/3rdparty/lapack/s_cmp.c',
+ 'src/opencv/3rdparty/lapack/ssterf.c',
+ 'src/opencv/3rdparty/lapack/dgelq2.c',
+ 'src/opencv/3rdparty/lapack/dlarrd.c',
+ 'src/opencv/3rdparty/lapack/ssyrk.c',
+ 'src/opencv/3rdparty/lapack/slamrg.c',
+ 'src/opencv/3rdparty/lapack/dlasrt.c',
+ 'src/opencv/3rdparty/lapack/slaed7.c',
+ 'src/opencv/3rdparty/lapack/dlarrv.c',
+ 'src/opencv/3rdparty/lapack/dlae2.c',
+ 'src/opencv/3rdparty/lapack/sgelsd.c',
+ 'src/opencv/3rdparty/lapack/dscal.c',
+ 'src/opencv/3rdparty/lapack/dlabad.c',
+ 'src/opencv/3rdparty/lapack/slaisnan.c',
+ 'src/opencv/3rdparty/lapack/dsytd2.c',
+ 'src/opencv/3rdparty/lapack/dlaed2.c',
+ 'src/opencv/3rdparty/lapack/slarnv.c',
+ 'src/opencv/3rdparty/lapack/dlaneg.c',
+ 'src/opencv/3rdparty/lapack/dorm2l.c',
+ 'src/opencv/3rdparty/lapack/f77_aloc.c',
+ 'src/opencv/3rdparty/lapack/strmm.c',
+ 'src/opencv/3rdparty/lapack/dlaisnan.c',
+ 'src/opencv/3rdparty/lapack/slals0.c',
+ 'src/opencv/3rdparty/lapack/dger.c',
+ 'src/opencv/3rdparty/lapack/slar1v.c',
+ 'src/opencv/3rdparty/lapack/slaed0.c',
+ 'src/opencv/3rdparty/lapack/dlaeda.c',
+ 'src/opencv/3rdparty/lapack/sger.c',
+ 'src/opencv/3rdparty/lapack/dcopy.c',
+ 'src/opencv/3rdparty/lapack/dlazq4.c',
+ 'src/opencv/3rdparty/lapack/daxpy.c',
+ 'src/opencv/3rdparty/lapack/ssyevr.c',
+ 'src/opencv/3rdparty/lapack/sgels.c',
+ 'src/opencv/3rdparty/lapack/dorglq.c',
+ 'src/opencv/3rdparty/lapack/dgelqf.c',
+ 'src/opencv/3rdparty/lapack/slarrc.c',
+ 'src/opencv/3rdparty/lapack/dlaebz.c',
+ 'src/opencv/3rdparty/lapack/sgetrs.c',
+ 'src/opencv/3rdparty/lapack/slasr.c',
+ 'src/opencv/3rdparty/lapack/dsteqr.c',
+ 'src/opencv/3rdparty/lapack/dtrtri.c',
+ 'src/opencv/3rdparty/lapack/dtrmm.c',
+ 'src/opencv/3rdparty/lapack/dstein.c',
+ 'src/opencv/3rdparty/lapack/dlaruv.c',
+ 'src/opencv/3rdparty/lapack/slaneg.c',
+ 'src/opencv/3rdparty/lapack/slarfb.c',
+ 'src/opencv/3rdparty/lapack/dorgqr.c',
+ 'src/opencv/3rdparty/lapack/dlasd3.c',
+ 'src/opencv/3rdparty/lapack/dlarnv.c',
+ 'src/opencv/3rdparty/lapack/precomp.c',
+ 'src/opencv/3rdparty/lapack/dsytrs.c',
+ 'src/opencv/3rdparty/lapack/dlarfg.c',
+ 'src/opencv/3rdparty/lapack/slaed9.c',
+ 'src/opencv/3rdparty/lapack/dgemm.c',
+ 'src/opencv/3rdparty/lapack/dgebrd.c',
+ 'src/opencv/3rdparty/lapack/slas2.c',
+ 'src/opencv/3rdparty/lapack/dlasq1.c',
+ 'src/opencv/3rdparty/lapack/dlarrr.c',
+ 'src/opencv/3rdparty/lapack/dpotri.c',
+ 'src/opencv/3rdparty/lapack/slasrt.c',
+ 'src/opencv/3rdparty/lapack/slagts.c',
+ 'src/opencv/3rdparty/lapack/dlasd4.c',
+ 'src/opencv/3rdparty/lapack/sbdsqr.c',
+ 'src/opencv/3rdparty/lapack/slarrk.c',
+ 'src/opencv/3rdparty/lapack/slaed8.c',
+ 'src/opencv/3rdparty/lapack/sstebz.c',
+ 'src/opencv/3rdparty/lapack/dlarrb.c',
+ 'src/opencv/3rdparty/lapack/saxpy.c',
+ 'src/opencv/3rdparty/lapack/dlauu2.c',
+ 'src/opencv/3rdparty/lapack/dasum.c',
+ 'src/opencv/3rdparty/lapack/pow_ri.c',
+ 'src/opencv/3rdparty/lapack/slange.c',
+ 'src/opencv/3rdparty/lapack/dpotrf.c',
+ 'src/opencv/3rdparty/lapack/dbdsdc.c',
+ 'src/opencv/3rdparty/lapack/sgesv.c',
+ 'src/opencv/3rdparty/lapack/drot.c',
+ 'src/opencv/3rdparty/lapack/sorgbr.c',
+ 'src/opencv/3rdparty/lapack/spotf2.c',
+ 'src/opencv/3rdparty/lapack/ssteqr.c',
+ 'src/opencv/3rdparty/lapack/dstebz.c',
+ 'src/opencv/3rdparty/lapack/dsyr2.c',
+ 'src/opencv/3rdparty/lapack/dlalsa.c',
+ 'src/opencv/3rdparty/lapack/sgebd2.c',
+ 'src/opencv/3rdparty/lapack/dgeqr2.c',
+ 'src/opencv/3rdparty/lapack/ssymv.c',
+ 'src/opencv/3rdparty/lapack/dlasdt.c',
+ 'src/opencv/3rdparty/lapack/dtrti2.c',
+ 'src/opencv/3rdparty/lapack/slasdt.c',
+ 'src/opencv/3rdparty/lapack/sorgl2.c',
+ 'src/opencv/3rdparty/lapack/slazq4.c',
+ 'src/opencv/3rdparty/lapack/dtrsm.c',
+ 'src/opencv/3rdparty/lapack/slascl.c',
+ 'src/opencv/3rdparty/lapack/iparmq.c',
+ 'src/opencv/3rdparty/lapack/sswap.c',
+ 'src/opencv/3rdparty/lapack/dlarrc.c',
+ 'src/opencv/3rdparty/lapack/dlassq.c',
+ 'src/opencv/3rdparty/lapack/dlasd5.c',
+ 'src/opencv/3rdparty/lapack/dlaswp.c',
+ 'src/opencv/3rdparty/lapack/slagtf.c',
+ 'src/opencv/3rdparty/lapack/dormbr.c',
+ 'src/opencv/3rdparty/lapack/dgesdd.c',
+ 'src/opencv/3rdparty/lapack/dlaed8.c',
+ 'src/opencv/3rdparty/lapack/dswap.c',
+ 'src/opencv/3rdparty/lapack/dlaed7.c',
+ 'src/opencv/3rdparty/lapack/sgesdd.c',
+ 'src/opencv/3rdparty/lapack/slasdq.c',
+ 'src/opencv/3rdparty/lapack/slaed3.c',
+ 'src/opencv/3rdparty/lapack/dlapy2.c',
+ 'src/opencv/3rdparty/lapack/dlaed4.c',
+ 'src/opencv/3rdparty/lapack/dsytrf.c',
+ 'src/opencv/3rdparty/lapack/dgeqrf.c',
+ 'src/opencv/3rdparty/lapack/slasd0.c',
+ 'src/opencv/3rdparty/lapack/slasd3.c',
+ 'src/opencv/3rdparty/lapack/dgemv.c',
+ 'src/opencv/3rdparty/lapack/slasd6.c',
+ 'src/opencv/3rdparty/lapack/dlamch.c',
+ 'src/opencv/3rdparty/lapack/dlasd0.c',
+ 'src/opencv/3rdparty/lapack/s_copy.c',
+ 'src/opencv/3rdparty/lapack/snrm2.c',
+ 'src/opencv/3rdparty/lapack/dsymv.c',
+ 'src/opencv/3rdparty/lapack/dgelsd.c',
+ 'src/opencv/3rdparty/lapack/dlarf.c',
+ 'src/opencv/3rdparty/lapack/strsm.c',
+ 'src/opencv/3rdparty/lapack/xerbla.c',
+ 'src/opencv/3rdparty/lapack/slasq1.c',
+ 'src/opencv/3rdparty/lapack/dlasd1.c',
+ 'src/opencv/3rdparty/lapack/slazq3.c',
+ 'src/opencv/3rdparty/lapack/spotri.c',
+ 'src/opencv/3rdparty/lapack/dlarfb.c',
+ 'src/opencv/3rdparty/lapack/sstein.c',
+ 'src/opencv/3rdparty/lapack/strmv.c',
+ 'src/opencv/3rdparty/lapack/pow_ii.c',
+ 'src/opencv/3rdparty/lapack/slarrr.c',
+ 'src/opencv/3rdparty/lapack/slauum.c',
+ 'src/opencv/3rdparty/lapack/dlals0.c',
+ 'src/opencv/3rdparty/lapack/slasq2.c',
+ 'src/opencv/3rdparty/lapack/dgetrs.c',
+ 'src/opencv/3rdparty/lapack/dlatrd.c',
+ 'src/opencv/3rdparty/lapack/slauu2.c',
+ 'src/opencv/3rdparty/lapack/slaev2.c',
+ 'src/opencv/3rdparty/lapack/slacpy.c',
+ 'src/opencv/3rdparty/lapack/dlaed0.c',
+ 'src/opencv/3rdparty/lapack/slasq6.c',
+ 'src/opencv/3rdparty/lapack/dlasd2.c',
+ 'src/opencv/3rdparty/lapack/slarrj.c',
+ 'src/opencv/3rdparty/lapack/sasum.c',
+ 'src/opencv/3rdparty/lapack/dsyr.c',
+ 'src/opencv/3rdparty/lapack/slasd2.c',
+ 'src/opencv/3rdparty/lapack/dlaed3.c',
+ 'src/opencv/3rdparty/lapack/slasd8.c',
+ 'src/opencv/3rdparty/lapack/sstemr.c',
+ 'src/opencv/3rdparty/lapack/slabad.c',
+ 'src/opencv/3rdparty/lapack/slansy.c',
+ 'src/opencv/3rdparty/lapack/spotrs.c',
+ 'src/opencv/3rdparty/lapack/dnrm2.c',
+ 'src/opencv/3rdparty/lapack/sgeqr2.c',
+ 'src/opencv/3rdparty/lapack/slamch.c',
+ 'src/opencv/3rdparty/lapack/sgetf2.c',
+ 'src/opencv/3rdparty/lapack/dlaev2.c',
+ 'src/opencv/3rdparty/lapack/pow_di.c',
+ 'src/opencv/3rdparty/lapack/slarf.c',
+ 'src/opencv/3rdparty/lapack/dlagtf.c',
+ 'src/opencv/3rdparty/lapack/slarra.c',
+ 'src/opencv/3rdparty/lapack/dlaed6.c',
+ 'src/opencv/3rdparty/lapack/dlarrf.c',
+ 'src/opencv/3rdparty/lapack/ssytd2.c',
+ 'src/opencv/3rdparty/lapack/slasv2.c',
+ 'src/opencv/3rdparty/lapack/dsyr2k.c',
+ 'src/opencv/3rdparty/lapack/ssyr2.c',
+ 'src/opencv/3rdparty/lapack/slaswp.c',
+ 'src/opencv/3rdparty/lapack/dormqr.c',
+ 'src/opencv/3rdparty/lapack/isamax.c',
+ 'src/opencv/3rdparty/lapack/dlasq6.c',
+ 'src/opencv/3rdparty/lapack/strtri.c',
+ 'src/opencv/3rdparty/lapack/slasq3.c',
+ 'src/opencv/3rdparty/lapack/dorg2r.c',
+ 'src/opencv/3rdparty/lapack/dlarft.c',
+ 'src/opencv/3rdparty/lapack/dlauum.c',
+ 'src/opencv/3rdparty/lapack/sgetrf.c',
+ 'src/opencv/3rdparty/lapack/dlaset.c',
+ 'src/opencv/3rdparty/lapack/dlas2.c',
+ 'src/opencv/3rdparty/lapack/dlasv2.c',
+ 'src/opencv/3rdparty/lapack/sgelq2.c',
+ 'src/opencv/3rdparty/lapack/dlasd7.c',
+ 'src/opencv/3rdparty/lapack/sscal.c',
+ 'src/opencv/3rdparty/lapack/dgebd2.c',
+ 'src/opencv/3rdparty/lapack/slaed4.c',
+ 'src/opencv/3rdparty/lapack/slasd1.c',
+ 'src/opencv/3rdparty/lapack/sorm2r.c',
+ 'src/opencv/3rdparty/lapack/dorgl2.c',
+ 'src/opencv/3rdparty/lapack/dlasyf.c',
+ ],
+ 'include_dirs': [
+ '<(opencv_src)/opencv/3rdparty/include',
+ ],
+ },
+ {
+ 'target_name': 'cxcore',
+ 'type': '<(library)',
+ 'dependencies': [
+ 'flann',
+ 'lapack',
+ '<(DEPTH)/third_party/zlib/zlib.gyp:zlib',
+ ],
+ 'sources': [
+ 'src/opencv/src/cxcore/cxalloc.cpp',
+ 'src/opencv/src/cxcore/cxarithm.cpp',
+ 'src/opencv/src/cxcore/cxarray.cpp',
+ 'src/opencv/src/cxcore/cxconvert.cpp',
+ 'src/opencv/src/cxcore/cxcopy.cpp',
+ 'src/opencv/src/cxcore/_cxcore.h',
+ 'src/opencv/src/cxcore/cxdatastructs.cpp',
+ 'src/opencv/src/cxcore/cxdrawing.cpp',
+ 'src/opencv/src/cxcore/cxdxt.cpp',
+ 'src/opencv/src/cxcore/cxflann.cpp',
+ 'src/opencv/src/cxcore/cxlapack.cpp',
+ 'src/opencv/src/cxcore/cxmathfuncs.cpp',
+ 'src/opencv/src/cxcore/cxmatmul.cpp',
+ 'src/opencv/src/cxcore/cxmatrix.cpp',
+ 'src/opencv/src/cxcore/cxstat.cpp',
+ 'src/opencv/src/cxcore/cxpersistence.cpp',
+ 'src/opencv/src/cxcore/cxprecomp.cpp',
+ 'src/opencv/src/cxcore/cxrand.cpp',
+ 'src/opencv/src/cxcore/cxsystem.cpp',
+ 'src/opencv/src/cxcore/cxtables.cpp',
+ ],
+ 'include_dirs': [
+ # opencv provides its own copy of zlib.h, but we want to use
+ # our version in third_party/zlib, so third_party/zlib must
+ # appear early in the list of include dirs.
+ '<(DEPTH)/third_party/zlib',
+ '<(opencv_src)/opencv/include/opencv',
+ '<(opencv_src)/opencv/3rdparty/include',
+ ],
+ },
+ {
+ 'target_name': 'cv',
+ 'type': '<(library)',
+ 'dependencies': [
+ 'cxcore',
+ ],
+ 'sources': [
+ 'src/opencv/src/cv/cvaccum.cpp',
+ 'src/opencv/src/cv/cvapprox.cpp',
+ 'src/opencv/src/cv/cvcalibinit.cpp',
+ 'src/opencv/src/cv/cvcalibration.cpp',
+ 'src/opencv/src/cv/cvcamshift.cpp',
+ 'src/opencv/src/cv/cvcanny.cpp',
+ 'src/opencv/src/cv/cvcascadedetect.cpp',
+ 'src/opencv/src/cv/cvcheckchessboard.cpp',
+ 'src/opencv/src/cv/cvcolor.cpp',
+ 'src/opencv/src/cv/cvcontours.cpp',
+ 'src/opencv/src/cv/cvcontourtree.cpp',
+ 'src/opencv/src/cv/cvconvhull.cpp',
+ 'src/opencv/src/cv/cvcorner.cpp',
+ 'src/opencv/src/cv/cvcornersubpix.cpp',
+ 'src/opencv/src/cv/cvderiv.cpp',
+ 'src/opencv/src/cv/cvdistransform.cpp',
+ 'src/opencv/src/cv/cvemd.cpp',
+ 'src/opencv/src/cv/cvfeatureselect.cpp',
+ 'src/opencv/src/cv/cvfloodfill.cpp',
+ 'src/opencv/src/cv/cvfundam.cpp',
+ 'src/opencv/src/cv/cvgeometry.cpp',
+ 'src/opencv/src/cv/cvhaar.cpp',
+ 'src/opencv/src/cv/cvhistogram.cpp',
+ 'src/opencv/src/cv/cvhough.cpp',
+ 'src/opencv/src/cv/cvimgwarp.cpp',
+ 'src/opencv/src/cv/cvkalman.cpp',
+ 'src/opencv/src/cv/cvlinefit.cpp',
+ 'src/opencv/src/cv/cvlkpyramid.cpp',
+ 'src/opencv/src/cv/cvmatchcontours.cpp',
+ 'src/opencv/src/cv/cvmodelest.cpp',
+ 'src/opencv/src/cv/cvmoments.cpp',
+ 'src/opencv/src/cv/cvmorph.cpp',
+ 'src/opencv/src/cv/cvmotempl.cpp',
+ 'src/opencv/src/cv/cvoptflowbm.cpp',
+ 'src/opencv/src/cv/cvoptflowhs.cpp',
+ 'src/opencv/src/cv/cvoptflowlk.cpp',
+ 'src/opencv/src/cv/cvposit.cpp',
+ 'src/opencv/src/cv/cvprecomp.cpp',
+ 'src/opencv/src/cv/cvpyramids.cpp',
+ 'src/opencv/src/cv/cvpyrsegmentation.cpp',
+ 'src/opencv/src/cv/cvrotcalipers.cpp',
+ 'src/opencv/src/cv/cvsamplers.cpp',
+ 'src/opencv/src/cv/cvshapedescr.cpp',
+ 'src/opencv/src/cv/cvsmooth.cpp',
+ 'src/opencv/src/cv/cvsnakes.cpp',
+ 'src/opencv/src/cv/cvsubdivision2d.cpp',
+ 'src/opencv/src/cv/cvsumpixels.cpp',
+ 'src/opencv/src/cv/cvtables.cpp',
+ 'src/opencv/src/cv/cvtemplmatch.cpp',
+ 'src/opencv/src/cv/cvthresh.cpp',
+ 'src/opencv/src/cv/cvundistort.cpp',
+ 'src/opencv/src/cv/cvutils.cpp',
+ 'src/opencv/src/cv/cvfilter.cpp',
+ 'src/opencv/src/cv/cvinpaint.cpp',
+ 'src/opencv/src/cv/cvsegmentation.cpp',
+ ],
+ 'include_dirs': [
+ '<(opencv_src)/opencv/include/opencv',
+ ],
+ },
+ {
+ 'target_name': 'highgui',
+ 'type': '<(library)',
+ 'dependencies': [
+ 'cv',
+ '<(DEPTH)/third_party/zlib/zlib.gyp:zlib',
+ '<(DEPTH)/third_party/libjpeg/libjpeg.gyp:libjpeg',
+ '<(DEPTH)/third_party/libpng/libpng.gyp:libpng',
+ ],
+ 'sources': [
+ 'src/opencv/src/highgui/bitstrm.cpp',
+ 'src/opencv/src/highgui/cvcap.cpp',
+ 'src/opencv/src/highgui/cvcap_dc1394.cpp',
+ 'src/opencv/src/highgui/cvcap_images.cpp',
+ 'src/opencv/src/highgui/cvcap_v4l.cpp',
+ 'src/opencv/src/highgui/grfmt_base.cpp',
+ 'src/opencv/src/highgui/grfmt_bmp.cpp',
+ 'src/opencv/src/highgui/grfmt_jpeg.cpp',
+ 'src/opencv/src/highgui/grfmt_png.cpp',
+ 'src/opencv/src/highgui/grfmt_pxm.cpp',
+ 'src/opencv/src/highgui/grfmt_sunras.cpp',
+ 'src/opencv/src/highgui/grfmt_tiff.cpp',
+ 'src/opencv/src/highgui/image.cpp',
+ 'src/opencv/src/highgui/loadsave.cpp',
+ 'src/opencv/src/highgui/precomp.cpp',
+ 'src/opencv/src/highgui/utils.cpp',
+ 'src/opencv/src/highgui/window.cpp',
+ ],
+ 'include_dirs': [
+ '<(opencv_gen)/include',
+ '<(opencv_src)/opencv/include/opencv',
+ ],
+ },
+ ],
+ },
+ { # use_system_opencv
+ 'targets': [
+ {
+ 'target_name': 'highgui',
+ 'type': 'settings',
+ 'include_dirs': [
+ '<(opencv_system_include)',
+ ],
+ 'direct_dependent_settings': {
+ 'defines': [
+ 'USE_SYSTEM_OPENCV',
+ ],
+ 'include_dirs': [
+ '<(opencv_system_include)',
+ ],
+ },
+ 'defines': [
+ 'USE_SYSTEM_OPENCV',
+ ],
+ 'link_settings': {
+ 'libraries': [
+ '-lcv',
+ '-lcxcore',
+ '-lhighgui',
+ ],
+ },
+ },
+ ],
+ }],
+ ],
+}
+
diff --git a/trunk/src/third_party/serf/instaweb_allocator.c b/trunk/src/third_party/serf/instaweb_allocator.c
new file mode 100644
index 0000000..5064b0d
--- /dev/null
+++ b/trunk/src/third_party/serf/instaweb_allocator.c
@@ -0,0 +1,415 @@
+/* Copyright 2002-2004 Justin Erenkrantz and Greg Stein
+ *
+ * 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.
+ */
+
+#include <stdlib.h>
+
+#include <apr_pools.h>
+
+#include "serf.h"
+#include "serf_bucket_util.h"
+
+
+typedef struct node_header_t {
+ apr_size_t size;
+ union {
+ struct node_header_t *next; /* if size == 0 (freed/inactive) */
+ /* no data if size == STANDARD_NODE_SIZE */
+ apr_memnode_t *memnode; /* if size > STANDARD_NODE_SIZE */
+ } u;
+} node_header_t;
+
+/* The size of a node_header_t, properly aligned. Note that (normally)
+ * this macro will round the size to a multiple of 8 bytes. Keep this in
+ * mind when altering the node_header_t structure. Also, keep in mind that
+ * node_header_t is an overhead for every allocation performed through
+ * the serf_bucket_mem_alloc() function.
+ */
+#define SIZEOF_NODE_HEADER_T APR_ALIGN_DEFAULT(sizeof(node_header_t))
+
+
+/* STANDARD_NODE_SIZE is manually set to an allocation size that will
+ * capture most allocators performed via this API. It must be "large
+ * enough" to avoid lots of spillage to allocating directly from the
+ * apr_allocator associated with the bucket allocator. The apr_allocator
+ * has a minimum size of 8k, which can be expensive if you missed the
+ * STANDARD_NODE_SIZE by just a few bytes.
+ */
+/* ### we should define some rules or ways to determine how to derive
+ * ### a "good" value for this. probably log some stats on allocs, then
+ * ### analyze them for size "misses". then find the balance point between
+ * ### wasted space due to min-size allocator, and wasted-space due to
+ * ### size-spill to the 8k minimum.
+ */
+#define STANDARD_NODE_SIZE 128
+
+/* When allocating a block of memory from the allocator, we should go for
+ * an 8k block, minus the overhead that the allocator needs.
+ */
+#define ALLOC_AMT (8192 - APR_MEMNODE_T_SIZE)
+
+/* Define DEBUG_DOUBLE_FREE if you're interested in debugging double-free
+ * calls to serf_bucket_mem_free().
+ */
+#define DEBUG_DOUBLE_FREE
+
+
+typedef struct {
+ const serf_bucket_t *bucket;
+ apr_status_t last;
+} read_status_t;
+
+#define TRACK_BUCKET_COUNT 100 /* track N buckets' status */
+
+typedef struct {
+ int next_index; /* info[] is a ring. next bucket goes at this idx. */
+ int num_used;
+
+ read_status_t info[TRACK_BUCKET_COUNT];
+} track_state_t;
+
+
+struct serf_bucket_alloc_t {
+ apr_pool_t *pool;
+ apr_allocator_t *allocator;
+ int own_allocator;
+
+ serf_unfreed_func_t unfreed;
+ void *unfreed_baton;
+
+ apr_uint32_t num_alloc;
+
+ node_header_t *freelist; /* free STANDARD_NODE_SIZE blocks */
+ apr_memnode_t *blocks; /* blocks we allocated for subdividing */
+
+ track_state_t *track;
+};
+
+/* ==================================================================== */
+
+
+static apr_status_t allocator_cleanup(void *data)
+{
+ serf_bucket_alloc_t *allocator = data;
+
+ /* If we allocated anything, give it back. */
+ if (allocator->blocks) {
+ apr_allocator_free(allocator->allocator, allocator->blocks);
+ }
+ if (allocator->own_allocator == 1) {
+ apr_allocator_destroy(allocator->allocator);
+ }
+ return APR_SUCCESS;
+}
+
+SERF_DECLARE(serf_bucket_alloc_t *) serf_bucket_allocator_create(
+ apr_pool_t *pool,
+ serf_unfreed_func_t unfreed,
+ void *unfreed_baton)
+{
+ serf_bucket_alloc_t *allocator = apr_pcalloc(pool, sizeof(*allocator));
+
+ allocator->pool = pool;
+ allocator->allocator = apr_pool_allocator_get(pool);
+ allocator->own_allocator = 0;
+ if (allocator->allocator == NULL) {
+ /* This most likely means pools are running in debug mode, create our
+ * own allocator to deal with memory ourselves */
+ apr_allocator_create(&allocator->allocator);
+ allocator->own_allocator = 1;
+ }
+ allocator->unfreed = unfreed;
+ allocator->unfreed_baton = unfreed_baton;
+
+#ifdef SERF_DEBUG_BUCKET_USE
+ {
+ track_state_t *track;
+
+ track = allocator->track = apr_palloc(pool, sizeof(*allocator->track));
+ track->next_index = 0;
+ track->num_used = 0;
+ }
+#endif
+
+ /* ### this implies buckets cannot cross a fork/exec. desirable?
+ *
+ * ### hmm. it probably also means that buckets cannot be AROUND
+ * ### during a fork/exec. the new process will try to clean them
+ * ### up and figure out there are unfreed blocks...
+ */
+ apr_pool_cleanup_register(pool, allocator,
+ allocator_cleanup, allocator_cleanup);
+
+ return allocator;
+}
+
+SERF_DECLARE(apr_pool_t *) serf_bucket_allocator_get_pool(
+ const serf_bucket_alloc_t *allocator)
+{
+ return allocator->pool;
+}
+
+SERF_DECLARE(void *) serf_bucket_mem_alloc(
+ serf_bucket_alloc_t *allocator,
+ apr_size_t size)
+{
+ node_header_t *node;
+
+ ++allocator->num_alloc;
+
+ size += SIZEOF_NODE_HEADER_T;
+ if (size <= STANDARD_NODE_SIZE) {
+ if (allocator->freelist) {
+ /* just pull a node off our freelist */
+ node = allocator->freelist;
+ allocator->freelist = node->u.next;
+#ifdef DEBUG_DOUBLE_FREE
+ /* When we free an item, we set its size to zero. Thus, when
+ * we return it to the caller, we must ensure the size is set
+ * properly.
+ */
+ node->size = STANDARD_NODE_SIZE;
+#endif
+ }
+ else {
+ apr_memnode_t *active = allocator->blocks;
+
+ if (active == NULL
+ || active->first_avail + STANDARD_NODE_SIZE >= active->endp) {
+ apr_memnode_t *head = allocator->blocks;
+
+ /* ran out of room. grab another block. */
+ active = apr_allocator_alloc(allocator->allocator, ALLOC_AMT);
+
+ /* link the block into our tracking list */
+ allocator->blocks = active;
+ active->next = head;
+ }
+
+ node = (node_header_t *)active->first_avail;
+ node->size = STANDARD_NODE_SIZE;
+ active->first_avail += STANDARD_NODE_SIZE;
+ }
+ }
+ else {
+ apr_memnode_t *memnode = apr_allocator_alloc(allocator->allocator,
+ size);
+
+ node = (node_header_t *)memnode->first_avail;
+ node->u.memnode = memnode;
+ node->size = size;
+ }
+
+ return ((char *)node) + SIZEOF_NODE_HEADER_T;
+}
+
+SERF_DECLARE(void *) serf_bucket_mem_calloc(
+ serf_bucket_alloc_t *allocator,
+ apr_size_t size)
+{
+ void *mem;
+ mem = serf_bucket_mem_alloc(allocator, size);
+ memset(mem, 0, size);
+ return mem;
+}
+
+SERF_DECLARE(void) serf_bucket_mem_free(
+ serf_bucket_alloc_t *allocator,
+ void *block)
+{
+ node_header_t *node;
+
+ --allocator->num_alloc;
+
+ node = (node_header_t *)((char *)block - SIZEOF_NODE_HEADER_T);
+
+ if (node->size == STANDARD_NODE_SIZE) {
+ /* put the node onto our free list */
+ node->u.next = allocator->freelist;
+ allocator->freelist = node;
+
+#ifdef DEBUG_DOUBLE_FREE
+ /* note that this thing was freed. */
+ node->size = 0;
+ }
+ else if (node->size == 0) {
+ /* damn thing was freed already. */
+ abort();
+#endif
+ }
+ else {
+#ifdef DEBUG_DOUBLE_FREE
+ /* note that this thing was freed. */
+ node->size = 0;
+#endif
+
+ /* now free it */
+ apr_allocator_free(allocator->allocator, node->u.memnode);
+ }
+}
+
+
+/* ==================================================================== */
+
+
+#ifdef SERF_DEBUG_BUCKET_USE
+
+static read_status_t *find_read_status(
+ track_state_t *track,
+ const serf_bucket_t *bucket,
+ int create_rs)
+{
+ read_status_t *rs;
+
+ if (track->num_used) {
+ int count = track->num_used;
+ int idx = track->next_index;
+
+ /* Search backwards. In all likelihood, the bucket which just got
+ * read was read very recently.
+ */
+ while (count-- > 0) {
+ if (!idx--) {
+ /* assert: track->num_used == TRACK_BUCKET_COUNT */
+ idx = track->num_used - 1;
+ }
+ if ((rs = &track->info[idx])->bucket == bucket) {
+ return rs;
+ }
+ }
+ }
+
+ /* Only create a new read_status_t when asked. */
+ if (!create_rs)
+ return NULL;
+
+ if (track->num_used < TRACK_BUCKET_COUNT) {
+ /* We're still filling up the ring. */
+ ++track->num_used;
+ }
+
+ rs = &track->info[track->next_index];
+ rs->bucket = bucket;
+ rs->last = APR_SUCCESS; /* ### the right initial value? */
+
+ if (++track->next_index == TRACK_BUCKET_COUNT)
+ track->next_index = 0;
+
+ return rs;
+}
+
+#endif /* SERF_DEBUG_BUCKET_USE */
+
+
+SERF_DECLARE(apr_status_t) serf_debug__record_read(
+ const serf_bucket_t *bucket,
+ apr_status_t status)
+{
+#ifndef SERF_DEBUG_BUCKET_USE
+ return status;
+#else
+
+ track_state_t *track = bucket->allocator->track;
+ read_status_t *rs = find_read_status(track, bucket, 1);
+
+ /* Validate that the previous status value allowed for another read. */
+ if (APR_STATUS_IS_EAGAIN(rs->last) /* ### or APR_EOF? */) {
+ /* Somebody read when they weren't supposed to. Bail. */
+ abort();
+ }
+
+ /* Save the current status for later. */
+ rs->last = status;
+
+ return status;
+#endif
+}
+
+SERF_DECLARE(void) serf_debug__entered_loop(serf_bucket_alloc_t *allocator)
+{
+#ifdef SERF_DEBUG_BUCKET_USE
+
+ track_state_t *track = allocator->track;
+ read_status_t *rs = &track->info[0];
+
+ for ( ; track->num_used; --track->num_used, ++rs ) {
+ if (rs->last == APR_SUCCESS) {
+ /* Somebody should have read this bucket again. */
+ abort();
+ }
+
+ /* ### other status values? */
+ }
+
+ /* num_used was reset. also need to reset the next index. */
+ track->next_index = 0;
+
+#endif
+}
+
+SERF_DECLARE(void) serf_debug__closed_conn(serf_bucket_alloc_t *allocator)
+{
+#ifdef SERF_DEBUG_BUCKET_USE
+
+ /* Just reset the number used so that we don't examine the info[] */
+ allocator->track->num_used = 0;
+ allocator->track->next_index = 0;
+
+#endif
+}
+
+SERF_DECLARE(void) serf_debug__bucket_destroy(const serf_bucket_t *bucket)
+{
+#ifdef SERF_DEBUG_BUCKET_USE
+
+ track_state_t *track = bucket->allocator->track;
+ read_status_t *rs = find_read_status(track, bucket, 0);
+
+ if (rs != NULL && rs->last != APR_EOF) {
+ /* The bucket was destroyed before it was read to completion. */
+
+ /* Special exception for socket buckets. If a connection remains
+ * open, they are not read to completion.
+ */
+ if (SERF_BUCKET_IS_SOCKET(bucket))
+ return;
+
+ /* Ditto for SSL Decrypt buckets. */
+ if (SERF_BUCKET_IS_SSL_DECRYPT(bucket))
+ return;
+
+ /* Ditto for SSL Encrypt buckets. */
+ if (SERF_BUCKET_IS_SSL_ENCRYPT(bucket))
+ return;
+
+ /* Ditto for barrier buckets. */
+ if (SERF_BUCKET_IS_BARRIER(bucket))
+ return;
+
+
+ abort();
+ }
+
+#endif
+}
+
+SERF_DECLARE(void) serf_debug__bucket_alloc_check(
+ serf_bucket_alloc_t *allocator)
+{
+#ifdef SERF_DEBUG_BUCKET_USE
+ if (allocator->num_alloc != 0) {
+ abort();
+ }
+#endif
+}
diff --git a/trunk/src/third_party/serf/instaweb_context.c b/trunk/src/third_party/serf/instaweb_context.c
new file mode 100644
index 0000000..a2b2ca2
--- /dev/null
+++ b/trunk/src/third_party/serf/instaweb_context.c
@@ -0,0 +1,1467 @@
+/* Copyright 2002-2004 Justin Erenkrantz and Greg Stein
+ *
+ * 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.
+ */
+
+#include <apr_pools.h>
+#include <apr_poll.h>
+#include <apr_version.h>
+
+#include "serf.h"
+#include "serf_bucket_util.h"
+
+
+/* ### what the hell? why does the APR interface have a "size" ??
+ ### the implication is that, if we bust this limit, we'd need to
+ ### stop, rebuild a pollset, and repopulate it. what suckage. */
+#define MAX_CONN 16
+
+/* Windows does not define IOV_MAX, so we need to ensure it is defined. */
+#ifndef IOV_MAX
+#define IOV_MAX 16
+#endif
+
+/* Holds all the information corresponding to a request/response pair. */
+struct serf_request_t {
+ serf_connection_t *conn;
+
+ apr_pool_t *respool;
+ serf_bucket_alloc_t *allocator;
+
+ /* The bucket corresponding to the request. Will be NULL once the
+ * bucket has been emptied (for delivery into the socket).
+ */
+ serf_bucket_t *req_bkt;
+
+ serf_request_setup_t setup;
+ void *setup_baton;
+
+ serf_response_acceptor_t acceptor;
+ void *acceptor_baton;
+
+ serf_response_handler_t handler;
+ void *handler_baton;
+
+ serf_bucket_t *resp_bkt;
+
+ struct serf_request_t *next;
+};
+
+typedef struct serf_pollset_t {
+ /* the set of connections to poll */
+ apr_pollset_t *pollset;
+} serf_pollset_t;
+
+struct serf_context_t {
+ /* the pool used for self and for other allocations */
+ apr_pool_t *pool;
+
+ void *pollset_baton;
+ serf_socket_add_t pollset_add;
+ serf_socket_remove_t pollset_rm;
+
+ /* one of our connections has a dirty pollset state. */
+ int dirty_pollset;
+
+ /* the list of active connections */
+ apr_array_header_t *conns;
+#define GET_CONN(ctx, i) (((serf_connection_t **)(ctx)->conns->elts)[i])
+
+ /* Proxy server address */
+ apr_sockaddr_t *proxy_address;
+
+ /* Progress callback */
+ serf_progress_t progress_func;
+ void *progress_baton;
+ apr_off_t progress_read;
+ apr_off_t progress_written;
+};
+
+struct serf_connection_t {
+ serf_context_t *ctx;
+
+ apr_pool_t *pool;
+ serf_bucket_alloc_t *allocator;
+
+ apr_sockaddr_t *address;
+
+ apr_socket_t *skt;
+ apr_pool_t *skt_pool;
+
+ /* the last reqevents we gave to pollset_add */
+ apr_int16_t reqevents;
+
+ /* the events we've seen for this connection in our returned pollset */
+ apr_int16_t seen_in_pollset;
+
+ /* are we a dirty connection that needs its poll status updated? */
+ int dirty_conn;
+
+ /* number of completed requests we've sent */
+ unsigned int completed_requests;
+
+ /* number of completed responses we've got */
+ unsigned int completed_responses;
+
+ /* keepalive */
+ unsigned int probable_keepalive_limit;
+
+ /* someone has told us that the connection is closing
+ * so, let's start a new socket.
+ */
+ int closing;
+
+ /* A bucket wrapped around our socket (for reading responses). */
+ serf_bucket_t *stream;
+
+ /* The list of active requests. */
+ serf_request_t *requests;
+ serf_request_t *requests_tail;
+
+ /* The list of requests we're holding on to because we're going to
+ * reset the connection soon.
+ */
+ serf_request_t *hold_requests;
+ serf_request_t *hold_requests_tail;
+
+ struct iovec vec[IOV_MAX];
+ int vec_len;
+
+ serf_connection_setup_t setup;
+ void *setup_baton;
+ serf_connection_closed_t closed;
+ void *closed_baton;
+
+ /* Max. number of outstanding requests. */
+ unsigned int max_outstanding_requests;
+
+ /* Host info. */
+ const char *host_url;
+ apr_uri_t host_info;
+};
+
+/* cleanup for sockets */
+static apr_status_t clean_skt(void *data)
+{
+ serf_connection_t *conn = data;
+ apr_status_t status = APR_SUCCESS;
+
+ if (conn->skt) {
+ status = apr_socket_close(conn->skt);
+ conn->skt = NULL;
+ }
+
+ return status;
+}
+
+static apr_status_t clean_resp(void *data)
+{
+ serf_request_t *req = data;
+
+ /* This pool just got cleared/destroyed. Don't try to destroy the pool
+ * (again) when the request is canceled.
+ */
+ req->respool = NULL;
+
+ return APR_SUCCESS;
+}
+
+/* cleanup for conns */
+static apr_status_t clean_conn(void *data)
+{
+ serf_connection_t *conn = data;
+
+ serf_connection_close(conn);
+
+ return APR_SUCCESS;
+}
+
+/* Update the pollset for this connection. We tweak the pollset based on
+ * whether we want to read and/or write, given conditions within the
+ * connection. If the connection is not (yet) in the pollset, then it
+ * will be added.
+ */
+static apr_status_t update_pollset(serf_connection_t *conn)
+{
+ serf_context_t *ctx = conn->ctx;
+ apr_status_t status;
+ apr_pollfd_t desc = { 0 };
+
+ if (!conn->skt) {
+ return APR_SUCCESS;
+ }
+
+ /* Remove the socket from the poll set. */
+ desc.desc_type = APR_POLL_SOCKET;
+ desc.desc.s = conn->skt;
+ desc.reqevents = conn->reqevents;
+
+ status = ctx->pollset_rm(ctx->pollset_baton,
+ &desc, conn);
+ if (status && !APR_STATUS_IS_NOTFOUND(status))
+ return status;
+
+ /* Now put it back in with the correct read/write values. */
+ desc.reqevents = APR_POLLHUP | APR_POLLERR;
+ if (conn->requests) {
+ /* If there are any outstanding events, then we want to read. */
+ /* ### not true. we only want to read IF we have sent some data */
+ desc.reqevents |= APR_POLLIN;
+
+ /* If the connection has unwritten data, or there are any requests
+ * that still have buckets to write out, then we want to write.
+ */
+ if (conn->vec_len)
+ desc.reqevents |= APR_POLLOUT;
+ else {
+ serf_request_t *request = conn->requests;
+
+ if ((conn->probable_keepalive_limit &&
+ conn->completed_requests > conn->probable_keepalive_limit) ||
+ (conn->max_outstanding_requests &&
+ conn->completed_requests - conn->completed_responses >=
+ conn->max_outstanding_requests)) {
+ /* we wouldn't try to write any way right now. */
+ }
+ else {
+ while (request != NULL && request->req_bkt == NULL &&
+ request->setup == NULL)
+ request = request->next;
+ if (request != NULL)
+ desc.reqevents |= APR_POLLOUT;
+ }
+ }
+ }
+
+ /* save our reqevents, so we can pass it in to remove later. */
+ conn->reqevents = desc.reqevents;
+
+ /* Note: even if we don't want to read/write this socket, we still
+ * want to poll it for hangups and errors.
+ */
+ return ctx->pollset_add(ctx->pollset_baton,
+ &desc, conn);
+}
+
+#ifdef SERF_DEBUG_BUCKET_USE
+
+/* Make sure all response buckets were drained. */
+static void check_buckets_drained(serf_connection_t *conn)
+{
+ serf_request_t *request = conn->requests;
+
+ for ( ; request ; request = request->next ) {
+ if (request->resp_bkt != NULL) {
+ /* ### crap. can't do this. this allocator may have un-drained
+ * ### REQUEST buckets.
+ */
+ /* serf_debug__entered_loop(request->resp_bkt->allocator); */
+ /* ### for now, pretend we closed the conn (resets the tracking) */
+ serf_debug__closed_conn(request->resp_bkt->allocator);
+ }
+ }
+}
+
+#endif
+
+/* Create and connect sockets for any connections which don't have them
+ * yet. This is the core of our lazy-connect behavior.
+ */
+static apr_status_t open_connections(serf_context_t *ctx)
+{
+ int i;
+
+ for (i = ctx->conns->nelts; i--; ) {
+ serf_connection_t *conn = GET_CONN(ctx, i);
+ apr_status_t status;
+ apr_socket_t *skt;
+ apr_sockaddr_t *serv_addr;
+
+ conn->seen_in_pollset = 0;
+
+ if (conn->skt != NULL) {
+#ifdef SERF_DEBUG_BUCKET_USE
+ check_buckets_drained(conn);
+#endif
+ continue;
+ }
+
+ /* Delay opening until we have something to deliver! */
+ if (conn->requests == NULL) {
+ continue;
+ }
+
+ apr_pool_clear(conn->skt_pool);
+ apr_pool_cleanup_register(conn->skt_pool, conn, clean_skt, clean_skt);
+
+ /* Do we have to connect to a proxy server? */
+ if (ctx->proxy_address)
+ serv_addr = ctx->proxy_address;
+ else
+ serv_addr = conn->address;
+
+ if ((status = apr_socket_create(&skt, serv_addr->family,
+ SOCK_STREAM,
+#if APR_MAJOR_VERSION > 0
+ APR_PROTO_TCP,
+#endif
+ conn->skt_pool)) != APR_SUCCESS)
+ return status;
+
+ /* Set the socket to be non-blocking */
+ if ((status = apr_socket_timeout_set(skt, 0)) != APR_SUCCESS)
+ return status;
+
+ /* Disable Nagle's algorithm */
+ if ((status = apr_socket_opt_set(skt,
+ APR_TCP_NODELAY, 0)) != APR_SUCCESS)
+ return status;
+
+ /* Configured. Store it into the connection now. */
+ conn->skt = skt;
+
+ /* Now that the socket is set up, let's connect it. This should
+ * return immediately.
+ */
+ if ((status = apr_socket_connect(skt,
+ serv_addr)) != APR_SUCCESS) {
+ if (!APR_STATUS_IS_EINPROGRESS(status))
+ return status;
+ }
+
+ /* Flag our pollset as dirty now that we have a new socket. */
+ conn->dirty_conn = 1;
+ ctx->dirty_pollset = 1;
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t no_more_writes(serf_connection_t *conn,
+ serf_request_t *request)
+{
+ /* Note that we should hold new requests until we open our new socket. */
+ conn->closing = 1;
+
+ /* We can take the *next* request in our list and assume it hasn't
+ * been written yet and 'save' it for the new socket.
+ */
+ conn->hold_requests = request->next;
+ conn->hold_requests_tail = conn->requests_tail;
+ request->next = NULL;
+ conn->requests_tail = request;
+
+ /* Clear our iovec. */
+ conn->vec_len = 0;
+
+ /* Update the pollset to know we don't want to write on this socket any
+ * more.
+ */
+ conn->dirty_conn = 1;
+ conn->ctx->dirty_pollset = 1;
+ return APR_SUCCESS;
+}
+
+/* Read the 'Connection' header from the response. Return SERF_ERROR_CLOSING if
+ * the header contains value 'close' indicating the server is closing the
+ * connection right after this response.
+ * Otherwise returns APR_SUCCESS.
+ */
+static apr_status_t is_conn_closing(serf_bucket_t *response)
+{
+ serf_bucket_t *hdrs;
+ const char *val;
+
+ hdrs = serf_bucket_response_get_headers(response);
+ val = serf_bucket_headers_get(hdrs, "Connection");
+ if (val && strcasecmp("close", val) == 0)
+ {
+ return SERF_ERROR_CLOSING;
+ }
+
+ return APR_SUCCESS;
+}
+
+static void link_requests(serf_request_t **list, serf_request_t **tail,
+ serf_request_t *request)
+{
+ if (*list == NULL) {
+ *list = request;
+ *tail = request;
+ }
+ else {
+ (*tail)->next = request;
+ *tail = request;
+ }
+}
+
+static apr_status_t cancel_request(serf_request_t *request,
+ serf_request_t **list,
+ int notify_request)
+{
+ /* If we haven't run setup, then we won't have a handler to call. */
+ if (request->handler && notify_request) {
+ /* We actually don't care what the handler returns.
+ * We have bigger matters at hand.
+ */
+ (*request->handler)(request, NULL, request->handler_baton,
+ request->respool);
+ }
+
+ if (*list == request) {
+ *list = request->next;
+ }
+ else {
+ serf_request_t *scan = *list;
+
+ while (scan->next && scan->next != request)
+ scan = scan->next;
+
+ if (scan->next) {
+ scan->next = scan->next->next;
+ }
+ }
+
+ if (request->resp_bkt) {
+ serf_debug__closed_conn(request->resp_bkt->allocator);
+ serf_bucket_destroy(request->resp_bkt);
+ }
+ if (request->req_bkt) {
+ serf_debug__closed_conn(request->req_bkt->allocator);
+ serf_bucket_destroy(request->req_bkt);
+ }
+
+ if (request->respool) {
+ apr_pool_destroy(request->respool);
+ }
+
+ serf_bucket_mem_free(request->conn->allocator, request);
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t remove_connection(serf_context_t *ctx,
+ serf_connection_t *conn)
+{
+ apr_pollfd_t desc = { 0 };
+
+ desc.desc_type = APR_POLL_SOCKET;
+ desc.desc.s = conn->skt;
+ desc.reqevents = conn->reqevents;
+
+ return ctx->pollset_rm(ctx->pollset_baton,
+ &desc, conn);
+}
+
+static apr_status_t reset_connection(serf_connection_t *conn,
+ int requeue_requests)
+{
+ serf_context_t *ctx = conn->ctx;
+ apr_status_t status;
+ serf_request_t *old_reqs, *held_reqs, *held_reqs_tail;
+
+ conn->probable_keepalive_limit = conn->completed_responses;
+ conn->completed_requests = 0;
+ conn->completed_responses = 0;
+
+ old_reqs = conn->requests;
+ held_reqs = conn->hold_requests;
+ held_reqs_tail = conn->hold_requests_tail;
+
+ if (conn->closing) {
+ conn->hold_requests = NULL;
+ conn->hold_requests_tail = NULL;
+ conn->closing = 0;
+ }
+
+ conn->requests = NULL;
+ conn->requests_tail = NULL;
+
+ while (old_reqs) {
+ /* If we haven't started to write the connection, bring it over
+ * unchanged to our new socket. Otherwise, call the cancel function.
+ */
+ if (requeue_requests && old_reqs->setup) {
+ serf_request_t *req = old_reqs;
+ old_reqs = old_reqs->next;
+ req->next = NULL;
+ link_requests(&conn->requests, &conn->requests_tail, req);
+ }
+ else {
+ cancel_request(old_reqs, &old_reqs, requeue_requests);
+ }
+ }
+
+ if (conn->requests_tail) {
+ conn->requests_tail->next = held_reqs;
+ }
+ else {
+ conn->requests = held_reqs;
+ }
+ if (held_reqs_tail) {
+ conn->requests_tail = held_reqs_tail;
+ }
+
+ if (conn->skt != NULL) {
+ remove_connection(ctx, conn);
+ status = apr_socket_close(conn->skt);
+ if (conn->closed != NULL) {
+ (*conn->closed)(conn, conn->closed_baton, status,
+ conn->pool);
+ }
+ conn->skt = NULL;
+ }
+
+ if (conn->stream != NULL) {
+ serf_bucket_destroy(conn->stream);
+ conn->stream = NULL;
+ }
+
+ /* Don't try to resume any writes */
+ conn->vec_len = 0;
+
+ conn->dirty_conn = 1;
+ conn->ctx->dirty_pollset = 1;
+
+ /* Let our context know that we've 'reset' the socket already. */
+ conn->seen_in_pollset |= APR_POLLHUP;
+
+ /* Found the connection. Closed it. All done. */
+ return APR_SUCCESS;
+}
+
+/**
+ * Callback function (implements serf_progress_t). Takes a number of bytes
+ * read @a read and bytes written @a written, adds those to the total for this
+ * context and notifies an interested party (if any).
+ */
+static void serf_context_progress_delta(
+ void *progress_baton,
+ apr_off_t read,
+ apr_off_t written)
+{
+ serf_context_t *ctx = progress_baton;
+
+ ctx->progress_read += read;
+ ctx->progress_written += written;
+
+ if (ctx->progress_func)
+ ctx->progress_func(ctx->progress_baton,
+ ctx->progress_read,
+ ctx->progress_written);
+}
+
+static apr_status_t socket_writev(serf_connection_t *conn)
+{
+ apr_size_t written;
+ apr_status_t status;
+
+ status = apr_socket_sendv(conn->skt, conn->vec,
+ conn->vec_len, &written);
+
+ /* did we write everything? */
+ if (written) {
+ apr_size_t len = 0;
+ int i;
+
+ for (i = 0; i < conn->vec_len; i++) {
+ len += conn->vec[i].iov_len;
+ if (written < len) {
+ if (i) {
+ memmove(conn->vec, &conn->vec[i],
+ sizeof(struct iovec) * (conn->vec_len - i));
+ conn->vec_len -= i;
+ }
+ conn->vec[0].iov_base += conn->vec[0].iov_len - (len - written);
+ conn->vec[0].iov_len = len - written;
+ break;
+ }
+ }
+ if (len == written) {
+ conn->vec_len = 0;
+ }
+
+ /* Log progress information */
+ serf_context_progress_delta(conn->ctx, 0, written);
+ }
+
+ return status;
+}
+
+/* write data out to the connection */
+static apr_status_t write_to_connection(serf_connection_t *conn)
+{
+ serf_request_t *request = conn->requests;
+
+ if (conn->probable_keepalive_limit &&
+ conn->completed_requests > conn->probable_keepalive_limit) {
+ /* backoff for now. */
+ return APR_SUCCESS;
+ }
+
+ /* Find a request that has data which needs to be delivered. */
+ while (request != NULL &&
+ request->req_bkt == NULL && request->setup == NULL)
+ request = request->next;
+
+ /* assert: request != NULL || conn->vec_len */
+
+ /* Keep reading and sending until we run out of stuff to read, or
+ * writing would block.
+ */
+ while (1) {
+ int stop_reading = 0;
+ apr_status_t status;
+ apr_status_t read_status;
+
+ if (conn->max_outstanding_requests &&
+ conn->completed_requests -
+ conn->completed_responses >= conn->max_outstanding_requests) {
+ /* backoff for now. */
+ return APR_SUCCESS;
+ }
+
+ /* If we have unwritten data, then write what we can. */
+ while (conn->vec_len) {
+ status = socket_writev(conn);
+
+ /* If the write would have blocked, then we're done. Don't try
+ * to write anything else to the socket.
+ */
+ if (APR_STATUS_IS_EAGAIN(status))
+ return APR_SUCCESS;
+ if (APR_STATUS_IS_EPIPE(status))
+ return no_more_writes(conn, request);
+ if (status)
+ return status;
+ }
+ /* ### can we have a short write, yet no EAGAIN? a short write
+ ### would imply unwritten_len > 0 ... */
+ /* assert: unwritten_len == 0. */
+
+ /* We may need to move forward to a request which has something
+ * to write.
+ */
+ while (request != NULL &&
+ request->req_bkt == NULL && request->setup == NULL)
+ request = request->next;
+
+ if (request == NULL) {
+ /* No more requests (with data) are registered with the
+ * connection. Let's update the pollset so that we don't
+ * try to write to this socket again.
+ */
+ conn->dirty_conn = 1;
+ conn->ctx->dirty_pollset = 1;
+ return APR_SUCCESS;
+ }
+
+ /* If the connection does not have an associated bucket, then
+ * call the setup callback to get one.
+ */
+ if (conn->stream == NULL) {
+ conn->stream = (*conn->setup)(conn->skt,
+ conn->setup_baton,
+ conn->pool);
+ }
+
+ if (request->req_bkt == NULL) {
+ /* Now that we are about to serve the request, allocate a pool. */
+ apr_pool_create(&request->respool, conn->pool);
+ request->allocator = serf_bucket_allocator_create(request->respool,
+ NULL, NULL);
+ apr_pool_cleanup_register(request->respool, request,
+ clean_resp, clean_resp);
+
+ /* Fill in the rest of the values for the request. */
+ read_status = request->setup(request, request->setup_baton,
+ &request->req_bkt,
+ &request->acceptor,
+ &request->acceptor_baton,
+ &request->handler,
+ &request->handler_baton,
+ request->respool);
+
+ if (read_status) {
+ /* Something bad happened. Propagate any errors. */
+ return read_status;
+ }
+
+ request->setup = NULL;
+ }
+
+ /* ### optimize at some point by using read_for_sendfile */
+ read_status = serf_bucket_read_iovec(request->req_bkt,
+ SERF_READ_ALL_AVAIL,
+ IOV_MAX,
+ conn->vec,
+ &conn->vec_len);
+
+ if (APR_STATUS_IS_EAGAIN(read_status)) {
+ /* We read some stuff, but should not try to read again. */
+ stop_reading = 1;
+
+ /* ### we should avoid looking for writability for a while so
+ ### that (hopefully) something will appear in the bucket so
+ ### we can actually write something. otherwise, we could
+ ### end up in a CPU spin: socket wants something, but we
+ ### don't have anything (and keep returning EAGAIN)
+ */
+ }
+ else if (read_status && !APR_STATUS_IS_EOF(read_status)) {
+ /* Something bad happened. Propagate any errors. */
+ return read_status;
+ }
+
+ /* If we got some data, then deliver it. */
+ /* ### what to do if we got no data?? is that a problem? */
+ if (conn->vec_len > 0) {
+ status = socket_writev(conn);
+
+ /* If we can't write any more, or an error occurred, then
+ * we're done here.
+ */
+ if (APR_STATUS_IS_EAGAIN(status))
+ return APR_SUCCESS;
+ if (APR_STATUS_IS_EPIPE(status))
+ return no_more_writes(conn, request);
+ if (APR_STATUS_IS_ECONNRESET(status)) {
+ return no_more_writes(conn, request);
+ }
+ if (status)
+ return status;
+ }
+
+ if (APR_STATUS_IS_EOF(read_status) &&
+ conn->vec_len == 0) {
+ /* If we hit the end of the request bucket and all of its data has
+ * been written, then clear it out to signify that we're done
+ * sending the request. On the next iteration through this loop:
+ * - if there are remaining bytes they will be written, and as the
+ * request bucket will be completely read it will be destroyed then.
+ * - we'll see if there are other requests that need to be sent
+ * ("pipelining").
+ */
+ serf_bucket_destroy(request->req_bkt);
+ request->req_bkt = NULL;
+
+ conn->completed_requests++;
+
+ if (conn->probable_keepalive_limit &&
+ conn->completed_requests > conn->probable_keepalive_limit) {
+ /* backoff for now. */
+ stop_reading = 1;
+ }
+ }
+
+ if (stop_reading) {
+ return APR_SUCCESS;
+ }
+ }
+ /* NOTREACHED */
+}
+
+/* read data from the connection */
+static apr_status_t read_from_connection(serf_connection_t *conn)
+{
+ apr_status_t status;
+ apr_pool_t *tmppool;
+ int close_connection = FALSE;
+
+ /* Whatever is coming in on the socket corresponds to the first request
+ * on our chain.
+ */
+ serf_request_t *request = conn->requests;
+
+ /* assert: request != NULL */
+
+ if ((status = apr_pool_create(&tmppool, conn->pool)) != APR_SUCCESS)
+ goto error;
+
+ /* Invoke response handlers until we have no more work. */
+ while (1) {
+ apr_pool_clear(tmppool);
+
+ /* If the connection does not have an associated bucket, then
+ * call the setup callback to get one.
+ */
+ if (conn->stream == NULL) {
+ conn->stream = (*conn->setup)(conn->skt,
+ conn->setup_baton,
+ conn->pool);
+ }
+
+ /* We are reading a response for a request we haven't
+ * written yet!
+ *
+ * This shouldn't normally happen EXCEPT:
+ *
+ * 1) when the other end has closed the socket and we're
+ * pending an EOF return.
+ * 2) Doing the initial SSL handshake - we'll get EAGAIN
+ * as the SSL buckets will hide the handshake from us
+ * but not return any data.
+ *
+ * In these cases, we should not receive any actual user data.
+ *
+ * If we see an EOF (due to an expired timeout), we'll reset the
+ * connection and open a new one.
+ */
+ if (request->req_bkt || request->setup) {
+ const char *data;
+ apr_size_t len;
+
+ status = serf_bucket_read(conn->stream, SERF_READ_ALL_AVAIL,
+ &data, &len);
+
+ if (!status && len) {
+ status = APR_EGENERAL;
+ }
+ else if (APR_STATUS_IS_EOF(status)) {
+ reset_connection(conn, 1);
+ status = APR_SUCCESS;
+ }
+ else if (APR_STATUS_IS_EAGAIN(status)) {
+ status = APR_SUCCESS;
+ }
+
+ goto error;
+ }
+
+ /* If the request doesn't have a response bucket, then call the
+ * acceptor to get one created.
+ */
+ if (request->resp_bkt == NULL) {
+ request->resp_bkt = (*request->acceptor)(request, conn->stream,
+ request->acceptor_baton,
+ tmppool);
+ apr_pool_clear(tmppool);
+ }
+
+ status = (*request->handler)(request,
+ request->resp_bkt,
+ request->handler_baton,
+ tmppool);
+
+ /* Some systems will not generate a HUP poll event so we have to
+ * handle the ECONNRESET issue here.
+ */
+ if (APR_STATUS_IS_ECONNRESET(status) ||
+ status == SERF_ERROR_REQUEST_LOST) {
+ reset_connection(conn, 1);
+ status = APR_SUCCESS;
+ goto error;
+ }
+
+ /* If our response handler says it can't do anything more, we now
+ * treat that as a success.
+ */
+ if (APR_STATUS_IS_EAGAIN(status)) {
+ status = APR_SUCCESS;
+ goto error;
+ }
+
+ /* If we received APR_SUCCESS, run this loop again. */
+ if (!status) {
+ continue;
+ }
+
+ close_connection = is_conn_closing(request->resp_bkt);
+
+ if (!APR_STATUS_IS_EOF(status) &&
+ close_connection != SERF_ERROR_CLOSING) {
+ /* Whether success, or an error, there is no more to do unless
+ * this request has been completed.
+ */
+ goto error;
+ }
+
+ /* The request has been fully-delivered, and the response has
+ * been fully-read. Remove it from our queue and loop to read
+ * another response.
+ */
+ conn->requests = request->next;
+
+ /* The bucket is no longer needed, nor is the request's pool. */
+ serf_bucket_destroy(request->resp_bkt);
+ if (request->req_bkt) {
+ serf_bucket_destroy(request->req_bkt);
+ }
+
+ serf_debug__bucket_alloc_check(request->allocator);
+ apr_pool_destroy(request->respool);
+ serf_bucket_mem_free(conn->allocator, request);
+
+ request = conn->requests;
+
+ /* If we're truly empty, update our tail. */
+ if (request == NULL) {
+ conn->requests_tail = NULL;
+ }
+
+ conn->completed_responses++;
+
+ /* This means that we're being advised that the connection is done. */
+ if (close_connection == SERF_ERROR_CLOSING) {
+ reset_connection(conn, 1);
+ if (APR_STATUS_IS_EOF(status))
+ status = APR_SUCCESS;
+ goto error;
+ }
+
+ /* The server is suddenly deciding to serve more responses than we've
+ * seen before.
+ *
+ * Let our requests go.
+ */
+ if (conn->probable_keepalive_limit &&
+ conn->completed_responses > conn->probable_keepalive_limit) {
+ conn->probable_keepalive_limit = 0;
+ }
+
+ /* If we just ran out of requests or have unwritten requests, then
+ * update the pollset. We don't want to read from this socket any
+ * more. We are definitely done with this loop, too.
+ */
+ if (request == NULL || request->setup) {
+ conn->dirty_conn = 1;
+ conn->ctx->dirty_pollset = 1;
+ status = APR_SUCCESS;
+ goto error;
+ }
+ }
+
+ error:
+ apr_pool_destroy(tmppool);
+ return status;
+}
+
+/* process all events on the connection */
+static apr_status_t process_connection(serf_connection_t *conn,
+ apr_int16_t events)
+{
+ apr_status_t status;
+
+ /* POLLHUP/ERR should come after POLLIN so if there's an error message or
+ * the like sitting on the connection, we give the app a chance to read
+ * it before we trigger a reset condition.
+ */
+ if ((events & APR_POLLIN) != 0) {
+ if ((status = read_from_connection(conn)) != APR_SUCCESS)
+ return status;
+
+ /* If we decided to reset our connection, return now as we don't
+ * want to write.
+ */
+ if ((conn->seen_in_pollset & APR_POLLHUP) != 0) {
+ return APR_SUCCESS;
+ }
+ }
+ if ((events & APR_POLLHUP) != 0) {
+ return APR_ECONNRESET;
+ }
+ if ((events & APR_POLLERR) != 0) {
+ /* We might be talking to a buggy HTTP server that doesn't
+ * do lingering-close. (httpd < 2.1.8 does this.)
+ *
+ * See:
+ *
+ * http://issues.apache.org/bugzilla/show_bug.cgi?id=35292
+ */
+ if (!conn->probable_keepalive_limit) {
+ return reset_connection(conn, 1);
+ }
+ return APR_EGENERAL;
+ }
+ if ((events & APR_POLLOUT) != 0) {
+ if ((status = write_to_connection(conn)) != APR_SUCCESS)
+ return status;
+ }
+ return APR_SUCCESS;
+}
+
+/* Check for dirty connections and update their pollsets accordingly. */
+static apr_status_t check_dirty_pollsets(serf_context_t *ctx)
+{
+ int i;
+
+ /* if we're not dirty, return now. */
+ if (!ctx->dirty_pollset) {
+ return APR_SUCCESS;
+ }
+
+ for (i = ctx->conns->nelts; i--; ) {
+ serf_connection_t *conn = GET_CONN(ctx, i);
+ apr_status_t status;
+
+ /* if this connection isn't dirty, skip it. */
+ if (!conn->dirty_conn) {
+ continue;
+ }
+
+ /* reset this connection's flag before we update. */
+ conn->dirty_conn = 0;
+
+ if ((status = update_pollset(conn)) != APR_SUCCESS)
+ return status;
+ }
+
+ /* reset our context flag now */
+ ctx->dirty_pollset = 0;
+
+ return APR_SUCCESS;
+}
+
+
+static apr_status_t pollset_add(void *user_baton,
+ apr_pollfd_t *pfd,
+ void *serf_baton)
+{
+ serf_pollset_t *s = (serf_pollset_t*)user_baton;
+ pfd->client_data = serf_baton;
+ return apr_pollset_add(s->pollset, pfd);
+}
+
+static apr_status_t pollset_rm(void *user_baton,
+ apr_pollfd_t *pfd,
+ void *serf_baton)
+{
+ serf_pollset_t *s = (serf_pollset_t*)user_baton;
+ pfd->client_data = serf_baton;
+ return apr_pollset_remove(s->pollset, pfd);
+}
+
+
+SERF_DECLARE(void) serf_config_proxy(serf_context_t *ctx,
+ apr_sockaddr_t *address)
+{
+ ctx->proxy_address = address;
+}
+
+SERF_DECLARE(serf_context_t *) serf_context_create_ex(void *user_baton,
+ serf_socket_add_t addf,
+ serf_socket_remove_t rmf,
+ apr_pool_t *pool)
+{
+ serf_context_t *ctx = apr_pcalloc(pool, sizeof(*ctx));
+
+ ctx->pool = pool;
+
+ if (user_baton != NULL) {
+ ctx->pollset_baton = user_baton;
+ ctx->pollset_add = addf;
+ ctx->pollset_rm = rmf;
+ }
+ else {
+ /* build the pollset with a (default) number of connections */
+ serf_pollset_t *ps = apr_pcalloc(pool, sizeof(*ps));
+ (void) apr_pollset_create(&ps->pollset, MAX_CONN, pool, 0);
+ ctx->pollset_baton = ps;
+ ctx->pollset_add = pollset_add;
+ ctx->pollset_rm = pollset_rm;
+ }
+
+ /* default to a single connection since that is the typical case */
+ ctx->conns = apr_array_make(pool, 1, sizeof(serf_connection_t *));
+
+ /* Initialize progress status */
+ ctx->progress_read = 0;
+ ctx->progress_written = 0;
+
+ return ctx;
+}
+
+SERF_DECLARE(serf_context_t *) serf_context_create(apr_pool_t *pool)
+{
+ return serf_context_create_ex(NULL, NULL, NULL, pool);
+}
+
+SERF_DECLARE(apr_status_t) serf_context_prerun(serf_context_t *ctx)
+{
+ apr_status_t status = APR_SUCCESS;
+ if ((status = open_connections(ctx)) != APR_SUCCESS)
+ return status;
+
+ if ((status = check_dirty_pollsets(ctx)) != APR_SUCCESS)
+ return status;
+ return status;
+}
+
+SERF_DECLARE(apr_status_t) serf_event_trigger(serf_context_t *s,
+ void *serf_baton,
+ const apr_pollfd_t *desc)
+{
+ apr_status_t status = APR_SUCCESS;
+
+ serf_connection_t *conn = serf_baton;
+
+ /* apr_pollset_poll() can return a conn multiple times... */
+ if ((conn->seen_in_pollset & desc->rtnevents) != 0 ||
+ (conn->seen_in_pollset & APR_POLLHUP) != 0) {
+ return APR_SUCCESS;
+ }
+
+ conn->seen_in_pollset |= desc->rtnevents;
+
+ if ((status = process_connection(conn,
+ desc->rtnevents)) != APR_SUCCESS) {
+ return status;
+ }
+
+ return status;
+}
+
+SERF_DECLARE(apr_status_t) serf_context_run(serf_context_t *ctx,
+ apr_short_interval_time_t duration,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+ apr_int32_t num;
+ const apr_pollfd_t *desc;
+ serf_pollset_t *ps = (serf_pollset_t*)ctx->pollset_baton;
+
+ if ((status = serf_context_prerun(ctx)) != APR_SUCCESS) {
+ return status;
+ }
+
+ if ((status = apr_pollset_poll(ps->pollset, duration, &num,
+ &desc)) != APR_SUCCESS) {
+ /* ### do we still need to dispatch stuff here?
+ ### look at the potential return codes. map to our defined
+ ### return values? ...
+ */
+ return status;
+ }
+
+ while (num--) {
+ serf_connection_t *conn = desc->client_data;
+
+ status = serf_event_trigger(ctx, conn, desc);
+ if (status) {
+ return status;
+ }
+
+ desc++;
+ }
+
+ return APR_SUCCESS;
+}
+
+SERF_DECLARE(void) serf_context_set_progress_cb(
+ serf_context_t *ctx,
+ const serf_progress_t progress_func,
+ void *progress_baton)
+{
+ ctx->progress_func = progress_func;
+ ctx->progress_baton = progress_baton;
+}
+
+SERF_DECLARE(serf_connection_t *) serf_connection_create(
+ serf_context_t *ctx,
+ apr_sockaddr_t *address,
+ serf_connection_setup_t setup,
+ void *setup_baton,
+ serf_connection_closed_t closed,
+ void *closed_baton,
+ apr_pool_t *pool)
+{
+ serf_connection_t *conn = apr_pcalloc(pool, sizeof(*conn));
+
+ conn->ctx = ctx;
+ conn->address = address;
+ conn->setup = setup;
+ conn->setup_baton = setup_baton;
+ conn->closed = closed;
+ conn->closed_baton = closed_baton;
+ conn->pool = pool;
+ conn->allocator = serf_bucket_allocator_create(pool, NULL, NULL);
+ conn->stream = NULL;
+
+ /* Create a subpool for our connection. */
+ apr_pool_create(&conn->skt_pool, conn->pool);
+
+ /* register a cleanup */
+ apr_pool_cleanup_register(conn->pool, conn, clean_conn, apr_pool_cleanup_null);
+
+ /* Add the connection to the context. */
+ *(serf_connection_t **)apr_array_push(ctx->conns) = conn;
+
+ return conn;
+}
+
+SERF_DECLARE(apr_status_t) serf_connection_create2(
+ serf_connection_t **conn,
+ serf_context_t *ctx,
+ apr_uri_t host_info,
+ serf_connection_setup_t setup,
+ void *setup_baton,
+ serf_connection_closed_t closed,
+ void *closed_baton,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+ serf_connection_t *c;
+ apr_sockaddr_t *host_address;
+
+ /* Parse the url, store the address of the server. */
+ status = apr_sockaddr_info_get(&host_address,
+ host_info.hostname,
+ APR_UNSPEC, host_info.port, 0, pool);
+ if (status)
+ return status;
+
+ c = serf_connection_create(ctx, host_address, setup, setup_baton,
+ closed, closed_baton, pool);
+
+ /* We're not interested in the path following the hostname. */
+ c->host_url = apr_uri_unparse(c->pool,
+ &host_info,
+ APR_URI_UNP_OMITPATHINFO);
+ c->host_info = host_info;
+
+ *conn = c;
+
+ return status;
+}
+
+SERF_DECLARE(apr_status_t) serf_connection_reset(
+ serf_connection_t *conn)
+{
+ return reset_connection(conn, 0);
+}
+
+SERF_DECLARE(apr_status_t) serf_connection_close(
+ serf_connection_t *conn)
+{
+ int i;
+ serf_context_t *ctx = conn->ctx;
+ apr_status_t status;
+
+ for (i = ctx->conns->nelts; i--; ) {
+ serf_connection_t *conn_seq = GET_CONN(ctx, i);
+
+ if (conn_seq == conn) {
+ while (conn->requests) {
+ serf_request_cancel(conn->requests);
+ }
+ if (conn->skt != NULL) {
+ remove_connection(ctx, conn);
+ status = apr_socket_close(conn->skt);
+ if (conn->closed != NULL) {
+ (*conn->closed)(conn, conn->closed_baton, status,
+ conn->pool);
+ }
+ conn->skt = NULL;
+ }
+ if (conn->stream != NULL) {
+ serf_bucket_destroy(conn->stream);
+ conn->stream = NULL;
+ }
+
+ /* Remove the connection from the context. We don't want to
+ * deal with it any more.
+ */
+ if (i < ctx->conns->nelts - 1) {
+ /* move later connections over this one. */
+ memmove(
+ &GET_CONN(ctx, i),
+ &GET_CONN(ctx, i + 1),
+ (ctx->conns->nelts - i - 1) * sizeof(serf_connection_t *));
+ }
+ --ctx->conns->nelts;
+
+ /* Found the connection. Closed it. All done. */
+ return APR_SUCCESS;
+ }
+ }
+
+ /* We didn't find the specified connection. */
+ /* ### doc talks about this w.r.t poll structures. use something else? */
+ return APR_NOTFOUND;
+}
+
+SERF_DECLARE(void)
+serf_connection_set_max_outstanding_requests(serf_connection_t *conn,
+ unsigned int max_requests)
+{
+ conn->max_outstanding_requests = max_requests;
+}
+
+SERF_DECLARE(serf_request_t *) serf_connection_request_create(
+ serf_connection_t *conn,
+ serf_request_setup_t setup,
+ void *setup_baton)
+{
+ serf_request_t *request;
+
+ request = serf_bucket_mem_alloc(conn->allocator, sizeof(*request));
+ request->conn = conn;
+ request->setup = setup;
+ request->setup_baton = setup_baton;
+ request->handler = NULL;
+ request->respool = NULL;
+ request->req_bkt = NULL;
+ request->resp_bkt = NULL;
+ request->next = NULL;
+
+ /* Link the request to the end of the request chain. */
+ if (conn->closing) {
+ link_requests(&conn->hold_requests, &conn->hold_requests_tail, request);
+ }
+ else {
+ link_requests(&conn->requests, &conn->requests_tail, request);
+
+ /* Ensure our pollset becomes writable in context run */
+ conn->ctx->dirty_pollset = 1;
+ conn->dirty_conn = 1;
+ }
+
+ return request;
+}
+
+SERF_DECLARE(serf_request_t *) serf_connection_priority_request_create(
+ serf_connection_t *conn,
+ serf_request_setup_t setup,
+ void *setup_baton)
+{
+ serf_request_t *request;
+ serf_request_t *iter, *prev;
+
+ request = serf_bucket_mem_alloc(conn->allocator, sizeof(*request));
+ request->conn = conn;
+ request->setup = setup;
+ request->setup_baton = setup_baton;
+ request->handler = NULL;
+ request->respool = NULL;
+ request->req_bkt = NULL;
+ request->resp_bkt = NULL;
+ request->next = NULL;
+
+ /* Link the new request after the last written request, but before all
+ upcoming requests. */
+ if (conn->closing) {
+ iter = conn->hold_requests;
+ }
+ else {
+ iter = conn->requests;
+ }
+ prev = NULL;
+
+ /* Find a request that has data which needs to be delivered. */
+ while (iter != NULL && iter->req_bkt == NULL && iter->setup == NULL) {
+ prev = iter;
+ iter = iter->next;
+ }
+
+ if (prev) {
+ request->next = iter;
+ prev->next = request;
+ } else {
+ request->next = iter;
+ if (conn->closing) {
+ conn->hold_requests = request;
+ }
+ else {
+ conn->requests = request;
+ }
+ }
+
+ if (! conn->closing) {
+ /* Ensure our pollset becomes writable in context run */
+ conn->ctx->dirty_pollset = 1;
+ conn->dirty_conn = 1;
+ }
+
+ return request;
+}
+
+SERF_DECLARE(apr_status_t) serf_request_cancel(serf_request_t *request)
+{
+ return cancel_request(request, &request->conn->requests, 0);
+}
+
+SERF_DECLARE(apr_pool_t *) serf_request_get_pool(const serf_request_t *request)
+{
+ return request->respool;
+}
+
+SERF_DECLARE(serf_bucket_alloc_t *) serf_request_get_alloc(
+ const serf_request_t *request)
+{
+ return request->allocator;
+}
+
+SERF_DECLARE(serf_connection_t *) serf_request_get_conn(
+ const serf_request_t *request)
+{
+ return request->conn;
+}
+
+SERF_DECLARE(void) serf_request_set_handler(
+ serf_request_t *request,
+ const serf_response_handler_t handler,
+ const void **handler_baton)
+{
+ request->handler = handler;
+ request->handler_baton = handler_baton;
+}
+
+SERF_DECLARE(serf_bucket_t *) serf_context_bucket_socket_create(
+ serf_context_t *ctx,
+ apr_socket_t *skt,
+ serf_bucket_alloc_t *allocator)
+{
+ serf_bucket_t *bucket = serf_bucket_socket_create(skt, allocator);
+
+ /* Use serf's default bytes read/written callback */
+ serf_bucket_socket_set_read_progress_cb(bucket,
+ serf_context_progress_delta,
+ ctx);
+
+ return bucket;
+}
+
+SERF_DECLARE(serf_bucket_t *) serf_request_bucket_request_create_for_host(
+ serf_request_t *request,
+ const char *method,
+ const char *uri,
+ serf_bucket_t *body,
+ serf_bucket_alloc_t *allocator, const char* host)
+{
+ serf_bucket_t *req_bkt, *hdrs_bkt;
+
+ req_bkt = serf_bucket_request_create(method, uri, body, allocator);
+ hdrs_bkt = serf_bucket_request_get_headers(req_bkt);
+
+ /* Proxy? */
+ if (request->conn->ctx->proxy_address && request->conn->host_url)
+ serf_bucket_request_set_root(req_bkt, request->conn->host_url);
+
+ if (host == NULL)
+ host = request->conn->host_info.hostname;
+ if (host)
+ serf_bucket_headers_setn(hdrs_bkt, "Host", host);
+
+ return req_bkt;
+}
+
+SERF_DECLARE(serf_bucket_t *) serf_request_bucket_request_create(
+ serf_request_t *request,
+ const char *method,
+ const char *uri,
+ serf_bucket_t *body,
+ serf_bucket_alloc_t *allocator)
+{
+ return serf_request_bucket_request_create_for_host(
+ request, method, uri, body, allocator, NULL);
+}
diff --git a/trunk/src/third_party/serf/instaweb_headers_buckets.c b/trunk/src/third_party/serf/instaweb_headers_buckets.c
new file mode 100644
index 0000000..be041a9
--- /dev/null
+++ b/trunk/src/third_party/serf/instaweb_headers_buckets.c
@@ -0,0 +1,431 @@
+/* Copyright 2004 Justin Erenkrantz and Greg Stein
+ *
+ * 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.
+ */
+
+#include <stdlib.h>
+
+#include <apr_general.h> /* for strcasecmp() */
+
+#include "serf.h"
+#include "serf_bucket_util.h"
+
+
+typedef struct header_list {
+ const char *header;
+ const char *value;
+
+ apr_size_t header_size;
+ apr_size_t value_size;
+
+ int alloc_flags;
+#define ALLOC_HEADER 0x0001 /* header lives in our allocator */
+#define ALLOC_VALUE 0x0002 /* value lives in our allocator */
+
+ struct header_list *next;
+} header_list_t;
+
+typedef struct {
+ header_list_t *list;
+
+ header_list_t *cur_read;
+ enum {
+ READ_START, /* haven't started reading yet */
+ READ_HEADER, /* reading cur_read->header */
+ READ_SEP, /* reading ": " */
+ READ_VALUE, /* reading cur_read->value */
+ READ_CRLF, /* reading "\r\n" */
+ READ_TERM, /* reading the final "\r\n" */
+ READ_DONE /* no more data to read */
+ } state;
+ apr_size_t amt_read; /* how much of the current state we've read */
+
+} headers_context_t;
+
+
+SERF_DECLARE(serf_bucket_t *) serf_bucket_headers_create(
+ serf_bucket_alloc_t *allocator)
+{
+ headers_context_t *ctx;
+
+ ctx = serf_bucket_mem_alloc(allocator, sizeof(*ctx));
+ ctx->list = NULL;
+ ctx->state = READ_START;
+
+ return serf_bucket_create(&serf_bucket_type_headers, allocator, ctx);
+}
+
+SERF_DECLARE(void) serf_bucket_headers_setx(
+ serf_bucket_t *bkt,
+ const char *header, apr_size_t header_size, int header_copy,
+ const char *value, apr_size_t value_size, int value_copy)
+{
+ headers_context_t *ctx = bkt->data;
+ header_list_t *found = ctx->list;
+ header_list_t *hdr;
+
+ /* Check to see if this header is already present. */
+ while (found) {
+ if (strncasecmp(found->header, header, header_size) == 0)
+ break;
+ found = found->next;
+ }
+
+ if (found) {
+
+ /* The header is already present. RFC 2616, section 4.2
+ indicates that we should append the new value, separated by
+ a comma. Reasoning: for headers whose values are known to
+ be comma-separated, that is clearly the correct behavior;
+ for others, the correct behavior is undefined anyway. */
+
+ /* The "+1" is for the comma; serf_bstrmemdup() will also add
+ one slot for the terminating '\0'. */
+ apr_size_t new_size = found->value_size + value_size + 1;
+ char *new_val = serf_bucket_mem_alloc(bkt->allocator, new_size);
+ memcpy(new_val, found->value, found->value_size);
+ new_val[found->value_size] = ',';
+ memcpy(new_val + found->value_size + 1, value, value_size);
+ new_val[new_size] = '\0';
+
+ // If the previous value was added, we must free it before overwriting
+ if (found->alloc_flags & ALLOC_VALUE) {
+ serf_bucket_mem_free(bkt->allocator, (void *)found->value);
+ }
+
+ found->value = new_val;
+ found->value_size = new_size;
+ found->alloc_flags |= ALLOC_VALUE;
+ return;
+ }
+
+ /* Else the header is not already present. Add it to the bucket. */
+
+#if 0
+ /* ### include this? */
+ if (ctx->cur_read) {
+ /* we started reading. can't change now. */
+ abort();
+ }
+#endif
+
+ hdr = serf_bucket_mem_alloc(bkt->allocator, sizeof(*hdr));
+ hdr->header_size = header_size;
+ hdr->value_size = value_size;
+ hdr->alloc_flags = 0;
+
+ if (header_copy) {
+ hdr->header = serf_bstrmemdup(bkt->allocator, header, header_size);
+ hdr->alloc_flags |= ALLOC_HEADER;
+ }
+ else {
+ hdr->header = header;
+ }
+
+ if (value_copy) {
+ hdr->value = serf_bstrmemdup(bkt->allocator, value, value_size);
+ hdr->alloc_flags |= ALLOC_VALUE;
+ }
+ else {
+ hdr->value = value;
+ }
+
+ hdr->next = ctx->list;
+ ctx->list = hdr;
+}
+
+SERF_DECLARE(void) serf_bucket_headers_set(
+ serf_bucket_t *headers_bucket,
+ const char *header,
+ const char *value)
+{
+ serf_bucket_headers_setx(headers_bucket,
+ header, strlen(header), 0,
+ value, strlen(value), 1);
+}
+
+SERF_DECLARE(void) serf_bucket_headers_setc(
+ serf_bucket_t *headers_bucket,
+ const char *header,
+ const char *value)
+{
+ serf_bucket_headers_setx(headers_bucket,
+ header, strlen(header), 1,
+ value, strlen(value), 1);
+}
+
+SERF_DECLARE(void) serf_bucket_headers_setn(
+ serf_bucket_t *headers_bucket,
+ const char *header,
+ const char *value)
+{
+ serf_bucket_headers_setx(headers_bucket,
+ header, strlen(header), 0,
+ value, strlen(value), 0);
+}
+
+SERF_DECLARE(const char *) serf_bucket_headers_get(
+ serf_bucket_t *headers_bucket,
+ const char *header)
+{
+ headers_context_t *ctx = headers_bucket->data;
+ header_list_t *scan = ctx->list;
+
+ while (scan) {
+ if (strcasecmp(scan->header, header) == 0)
+ return scan->value;
+ scan = scan->next;
+ }
+
+ return NULL;
+}
+
+SERF_DECLARE(void) serf_bucket_headers_do(
+ serf_bucket_t *headers_bucket,
+ serf_bucket_headers_do_callback_fn_t func,
+ void *baton)
+{
+ headers_context_t *ctx = headers_bucket->data;
+ header_list_t *scan = ctx->list;
+
+ while (scan) {
+ if (func(baton, scan->header, scan->value) != 0) {
+ break;
+ }
+ scan = scan->next;
+ }
+}
+
+static void serf_headers_destroy_and_data(serf_bucket_t *bucket)
+{
+ headers_context_t *ctx = bucket->data;
+ header_list_t *scan = ctx->list;
+
+ while (scan) {
+ header_list_t *next_hdr = scan->next;
+
+ if (scan->alloc_flags & ALLOC_HEADER)
+ serf_bucket_mem_free(bucket->allocator, (void *)scan->header);
+ if (scan->alloc_flags & ALLOC_VALUE)
+ serf_bucket_mem_free(bucket->allocator, (void *)scan->value);
+ serf_bucket_mem_free(bucket->allocator, scan);
+
+ scan = next_hdr;
+ }
+
+ serf_default_destroy_and_data(bucket);
+}
+
+static void select_value(
+ headers_context_t *ctx,
+ const char **value,
+ apr_size_t *len)
+{
+ const char *v;
+ apr_size_t l;
+
+ if (ctx->state == READ_START) {
+ if (ctx->list == NULL) {
+ /* No headers. Move straight to the TERM state. */
+ ctx->state = READ_TERM;
+ }
+ else {
+ ctx->state = READ_HEADER;
+ ctx->cur_read = ctx->list;
+ }
+ ctx->amt_read = 0;
+ }
+
+ switch (ctx->state) {
+ case READ_HEADER:
+ v = ctx->cur_read->header;
+ l = ctx->cur_read->header_size;
+ break;
+ case READ_SEP:
+ v = ": ";
+ l = 2;
+ break;
+ case READ_VALUE:
+ v = ctx->cur_read->value;
+ l = ctx->cur_read->value_size;
+ break;
+ case READ_CRLF:
+ case READ_TERM:
+ v = "\r\n";
+ l = 2;
+ break;
+ case READ_DONE:
+ *len = 0;
+ return;
+ default:
+ /* Not reachable */
+ return;
+ }
+
+ *value = v + ctx->amt_read;
+ *len = l - ctx->amt_read;
+}
+
+/* the current data chunk has been read/consumed. move our internal state. */
+static apr_status_t consume_chunk(headers_context_t *ctx)
+{
+ /* move to the next state, resetting the amount read. */
+ ++ctx->state;
+ ctx->amt_read = 0;
+
+ /* just sent the terminator and moved to DONE. signal completion. */
+ if (ctx->state == READ_DONE)
+ return APR_EOF;
+
+ /* end of this header. move to the next one. */
+ if (ctx->state == READ_TERM) {
+ ctx->cur_read = ctx->cur_read->next;
+ if (ctx->cur_read != NULL) {
+ /* We've got another head to send. Reset the read state. */
+ ctx->state = READ_HEADER;
+ }
+ /* else leave in READ_TERM */
+ }
+
+ /* there is more data which can be read immediately. */
+ return APR_SUCCESS;
+}
+
+static apr_status_t serf_headers_peek(serf_bucket_t *bucket,
+ const char **data,
+ apr_size_t *len)
+{
+ headers_context_t *ctx = bucket->data;
+
+ select_value(ctx, data, len);
+
+ /* already done or returning the CRLF terminator? return EOF */
+ if (ctx->state == READ_DONE || ctx->state == READ_TERM)
+ return APR_EOF;
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t serf_headers_read(serf_bucket_t *bucket,
+ apr_size_t requested,
+ const char **data, apr_size_t *len)
+{
+ headers_context_t *ctx = bucket->data;
+ apr_size_t avail;
+
+ select_value(ctx, data, &avail);
+ if (ctx->state == READ_DONE)
+ return APR_EOF;
+
+ if (requested >= avail) {
+ /* return everything from this chunk */
+ *len = avail;
+
+ /* we consumed this chunk. advance the state. */
+ return consume_chunk(ctx);
+ }
+
+ /* return just the amount requested, and advance our pointer */
+ *len = requested;
+ ctx->amt_read += requested;
+
+ /* there is more that can be read immediately */
+ return APR_SUCCESS;
+}
+
+static apr_status_t serf_headers_readline(serf_bucket_t *bucket,
+ int acceptable, int *found,
+ const char **data, apr_size_t *len)
+{
+ headers_context_t *ctx = bucket->data;
+ apr_status_t status;
+
+ /* ### what behavior should we use here? APR_EGENERAL for now */
+ if ((acceptable & SERF_NEWLINE_CRLF) == 0)
+ return APR_EGENERAL;
+
+ /* get whatever is in this chunk */
+ select_value(ctx, data, len);
+ if (ctx->state == READ_DONE)
+ return APR_EOF;
+
+ /* we consumed this chunk. advance the state. */
+ status = consume_chunk(ctx);
+
+ /* the type of newline found is easy... */
+ *found = (ctx->state == READ_CRLF || ctx->state == READ_TERM)
+ ? SERF_NEWLINE_CRLF : SERF_NEWLINE_NONE;
+
+ return status;
+}
+
+static apr_status_t serf_headers_read_iovec(serf_bucket_t *bucket,
+ apr_size_t requested,
+ int vecs_size,
+ struct iovec *vecs,
+ int *vecs_used)
+{
+ apr_size_t avail = requested;
+ int i;
+
+ *vecs_used = 0;
+
+ for (i = 0; i < vecs_size; i++) {
+ const char *data;
+ apr_size_t len;
+ apr_status_t status;
+
+ /* Calling read() would not be a safe opt in the general case, but it
+ * is here for the header bucket as it only frees all of the header
+ * keys and values when the entire bucket goes away - not on a
+ * per-read() basis as is normally the case.
+ */
+ status = serf_headers_read(bucket, avail, &data, &len);
+
+ if (len) {
+ vecs[*vecs_used].iov_base = (char*)data;
+ vecs[*vecs_used].iov_len = len;
+
+ (*vecs_used)++;
+
+ if (avail != SERF_READ_ALL_AVAIL) {
+ avail -= len;
+
+ /* If we reach 0, then read()'s status will suffice. */
+ if (avail == 0) {
+ return status;
+ }
+ }
+ }
+
+ if (status) {
+ return status;
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+SERF_DECLARE_DATA const serf_bucket_type_t serf_bucket_type_headers = {
+ "HEADERS",
+ serf_headers_read,
+ serf_headers_readline,
+ serf_headers_read_iovec,
+ serf_default_read_for_sendfile,
+ serf_default_read_bucket,
+ serf_headers_peek,
+ serf_headers_destroy_and_data,
+ serf_default_snapshot,
+ serf_default_restore_snapshot,
+ serf_default_is_snapshot_set,
+};
diff --git a/trunk/src/third_party/serf/instaweb_response_buckets.c b/trunk/src/third_party/serf/instaweb_response_buckets.c
new file mode 100644
index 0000000..1c9a2c3
--- /dev/null
+++ b/trunk/src/third_party/serf/instaweb_response_buckets.c
@@ -0,0 +1,440 @@
+/* Copyright 2002-2004 Justin Erenkrantz and Greg Stein
+ *
+ * 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.
+ *
+ * This is an Instaweb-specific modification of
+ * src/third_party/serf/src/buckets/response_buckets.c
+ *
+ * 1. Fix a buffer overrun in fetch_headers() by guarding the call to
+ * serf_bucket_headers_setx.
+ * 2. Removing the block of code responsible for gunzipping zipped content.
+ * If the caller as specified Accept-Encoding:gzip then serf should not
+ * unzip it.
+ */
+
+#include <apr_lib.h>
+#include <apr_strings.h>
+#include <apr_date.h>
+
+#include "serf.h"
+#include "serf_bucket_util.h"
+
+
+typedef struct {
+ serf_bucket_t *stream;
+ serf_bucket_t *body; /* Pointer to the stream wrapping the body. */
+ serf_bucket_t *headers; /* holds parsed headers */
+
+ enum {
+ STATE_STATUS_LINE, /* reading status line */
+ STATE_HEADERS, /* reading headers */
+ STATE_BODY, /* reading body */
+ STATE_TRAILERS, /* reading trailers */
+ STATE_DONE /* we've sent EOF */
+ } state;
+
+ /* Buffer for accumulating a line from the response. */
+ serf_linebuf_t linebuf;
+
+ serf_status_line sl;
+
+ int chunked; /* Do we need to read trailers? */
+ int head_req; /* Was this a HEAD request? */
+} response_context_t;
+
+
+SERF_DECLARE(serf_bucket_t *) serf_bucket_response_create(
+ serf_bucket_t *stream,
+ serf_bucket_alloc_t *allocator)
+{
+ response_context_t *ctx;
+
+ ctx = serf_bucket_mem_alloc(allocator, sizeof(*ctx));
+ ctx->stream = stream;
+ ctx->body = NULL;
+ ctx->headers = serf_bucket_headers_create(allocator);
+ ctx->state = STATE_STATUS_LINE;
+ ctx->chunked = 0;
+ ctx->head_req = 0;
+
+ serf_linebuf_init(&ctx->linebuf);
+
+ return serf_bucket_create(&serf_bucket_type_response, allocator, ctx);
+}
+
+SERF_DECLARE(void) serf_bucket_response_set_head(
+ serf_bucket_t *bucket)
+{
+ response_context_t *ctx = bucket->data;
+
+ ctx->head_req = 1;
+}
+
+SERF_DECLARE(serf_bucket_t *) serf_bucket_response_get_headers(
+ serf_bucket_t *bucket)
+{
+ return ((response_context_t *)bucket->data)->headers;
+}
+
+
+static void serf_response_destroy_and_data(serf_bucket_t *bucket)
+{
+ response_context_t *ctx = bucket->data;
+
+ if (ctx->state != STATE_STATUS_LINE) {
+ serf_bucket_mem_free(bucket->allocator, (void*)ctx->sl.reason);
+ }
+
+ serf_bucket_destroy(ctx->stream);
+ if (ctx->body != NULL)
+ serf_bucket_destroy(ctx->body);
+ serf_bucket_destroy(ctx->headers);
+
+ serf_default_destroy_and_data(bucket);
+}
+
+static apr_status_t fetch_line(response_context_t *ctx, int acceptable)
+{
+ return serf_linebuf_fetch(&ctx->linebuf, ctx->stream, acceptable);
+}
+
+static apr_status_t parse_status_line(response_context_t *ctx,
+ serf_bucket_alloc_t *allocator)
+{
+ int res;
+ char *reason; /* ### stupid APR interface makes this non-const */
+
+ /* ctx->linebuf.line should be of form: HTTP/1.1 200 OK */
+ res = apr_date_checkmask(ctx->linebuf.line, "HTTP/#.# ###*");
+ if (!res) {
+ /* Not an HTTP response? Well, at least we won't understand it. */
+ return APR_EGENERAL;
+ }
+
+ ctx->sl.version = SERF_HTTP_VERSION(ctx->linebuf.line[5] - '0',
+ ctx->linebuf.line[7] - '0');
+ ctx->sl.code = apr_strtoi64(ctx->linebuf.line + 8, &reason, 10);
+
+ /* Skip leading spaces for the reason string. */
+ if (apr_isspace(*reason)) {
+ reason++;
+ }
+
+ /* Copy the reason value out of the line buffer. */
+ ctx->sl.reason = serf_bstrmemdup(allocator, reason,
+ ctx->linebuf.used
+ - (reason - ctx->linebuf.line));
+
+ return APR_SUCCESS;
+}
+
+/* This code should be replaced with header buckets. */
+static apr_status_t fetch_headers(serf_bucket_t *bkt, response_context_t *ctx)
+{
+ apr_status_t status;
+
+ /* RFC 2616 says that CRLF is the only line ending, but we can easily
+ * accept any kind of line ending.
+ */
+ status = fetch_line(ctx, SERF_NEWLINE_ANY);
+ if (SERF_BUCKET_READ_ERROR(status)) {
+ return status;
+ }
+ /* Something was read. Process it. */
+
+ if (ctx->linebuf.state == SERF_LINEBUF_READY && ctx->linebuf.used) {
+ const char *end_key;
+ const char *c;
+
+ end_key = c = memchr(ctx->linebuf.line, ':', ctx->linebuf.used);
+ if (!c) {
+ /* Bad headers? */
+ return APR_EGENERAL;
+ }
+
+ /* Skip over initial : and spaces. */
+ while (apr_isspace(*++c))
+ continue;
+
+ /* Always copy the headers (from the linebuf into new mem). */
+ /* ### we should be able to optimize some mem copies */
+
+ /*
+ * In Instaweb, we noticed that this code needs to be fixed to
+ * avoid risking a buffer overrun.
+ */
+ if (ctx->linebuf.line + ctx->linebuf.used > c) {
+ apr_size_t value_size = ctx->linebuf.line + ctx->linebuf.used - c;
+ serf_bucket_headers_setx(
+ ctx->headers,
+ ctx->linebuf.line, end_key - ctx->linebuf.line, 1,
+ c, value_size, 1);
+ }
+ }
+
+ return status;
+}
+
+/* Perform one iteration of the state machine.
+ *
+ * Will return when one the following conditions occurred:
+ * 1) a state change
+ * 2) an error
+ * 3) the stream is not ready or at EOF
+ * 4) APR_SUCCESS, meaning the machine can be run again immediately
+ */
+static apr_status_t run_machine(serf_bucket_t *bkt, response_context_t *ctx)
+{
+ apr_status_t status = APR_SUCCESS; /* initialize to avoid gcc warnings */
+
+ switch (ctx->state) {
+ case STATE_STATUS_LINE:
+ /* RFC 2616 says that CRLF is the only line ending, but we can easily
+ * accept any kind of line ending.
+ */
+ status = fetch_line(ctx, SERF_NEWLINE_ANY);
+ if (SERF_BUCKET_READ_ERROR(status))
+ return status;
+
+ if (ctx->linebuf.state == SERF_LINEBUF_READY) {
+ /* The Status-Line is in the line buffer. Process it. */
+ status = parse_status_line(ctx, bkt->allocator);
+ if (status)
+ return status;
+
+ /* Okay... move on to reading the headers. */
+ ctx->state = STATE_HEADERS;
+ }
+ else {
+ /* The connection closed before we could get the next
+ * response. Treat the request as lost so that our upper
+ * end knows the server never tried to give us a response.
+ */
+ if (APR_STATUS_IS_EOF(status)) {
+ return SERF_ERROR_REQUEST_LOST;
+ }
+ }
+ break;
+ case STATE_HEADERS:
+ status = fetch_headers(bkt, ctx);
+ if (SERF_BUCKET_READ_ERROR(status))
+ return status;
+
+ /* If an empty line was read, then we hit the end of the headers.
+ * Move on to the body.
+ */
+ if (ctx->linebuf.state == SERF_LINEBUF_READY && !ctx->linebuf.used) {
+ const void *v;
+
+ /* Advance the state. */
+ ctx->state = STATE_BODY;
+
+ ctx->body =
+ serf_bucket_barrier_create(ctx->stream, bkt->allocator);
+
+ /* Are we C-L, chunked, or conn close? */
+ v = serf_bucket_headers_get(ctx->headers, "Content-Length");
+ if (v) {
+ apr_uint64_t length;
+ length = apr_strtoi64(v, NULL, 10);
+ if (errno == ERANGE) {
+ return APR_FROM_OS_ERROR(ERANGE);
+ }
+ ctx->body = serf_bucket_limit_create(ctx->body, length,
+ bkt->allocator);
+ }
+ else {
+ v = serf_bucket_headers_get(ctx->headers, "Transfer-Encoding");
+
+ /* Need to handle multiple transfer-encoding. */
+ if (v && strcasecmp("chunked", v) == 0) {
+ ctx->chunked = 1;
+ ctx->body = serf_bucket_dechunk_create(ctx->body,
+ bkt->allocator);
+ }
+
+ if (!v && (ctx->sl.code == 204 || ctx->sl.code == 304)) {
+ ctx->state = STATE_DONE;
+ }
+ }
+
+ /*
+ * Instaweb would prefer to receive gzipped output if that's what
+ * was asked for.
+ *
+ * v = serf_bucket_headers_get(ctx->headers, "Content-Encoding");
+ * if (v) {
+ * * Need to handle multiple content-encoding. *
+ * if (v && strcasecmp("gzip", v) == 0) {
+ * ctx->body =
+ * serf_bucket_deflate_create(ctx->body, bkt->allocator,
+ * SERF_DEFLATE_GZIP);
+ * }
+ * else if (v && strcasecmp("deflate", v) == 0) {
+ * ctx->body =
+ * serf_bucket_deflate_create(ctx->body, bkt->allocator,
+ * SERF_DEFLATE_DEFLATE);
+ * }
+ * }
+ */
+
+ /* If we're a HEAD request, we don't receive a body. */
+ if (ctx->head_req) {
+ ctx->state = STATE_DONE;
+ }
+ }
+ break;
+ case STATE_BODY:
+ /* Don't do anything. */
+ break;
+ case STATE_TRAILERS:
+ status = fetch_headers(bkt, ctx);
+ if (SERF_BUCKET_READ_ERROR(status))
+ return status;
+
+ /* If an empty line was read, then we're done. */
+ if (ctx->linebuf.state == SERF_LINEBUF_READY && !ctx->linebuf.used) {
+ ctx->state = STATE_DONE;
+ return APR_EOF;
+ }
+ break;
+ case STATE_DONE:
+ return APR_EOF;
+ default:
+ /* Not reachable */
+ return APR_EGENERAL;
+ }
+
+ return status;
+}
+
+static apr_status_t wait_for_body(serf_bucket_t *bkt, response_context_t *ctx)
+{
+ apr_status_t status;
+
+ /* Keep reading and moving through states if we aren't at the BODY */
+ while (ctx->state != STATE_BODY) {
+ status = run_machine(bkt, ctx);
+
+ /* Anything other than APR_SUCCESS means that we cannot immediately
+ * read again (for now).
+ */
+ if (status)
+ return status;
+ }
+ /* in STATE_BODY */
+
+ return APR_SUCCESS;
+}
+
+SERF_DECLARE(apr_status_t) serf_bucket_response_wait_for_headers(
+ serf_bucket_t *bucket)
+{
+ response_context_t *ctx = bucket->data;
+
+ return wait_for_body(bucket, ctx);
+}
+
+SERF_DECLARE(apr_status_t) serf_bucket_response_status(
+ serf_bucket_t *bkt,
+ serf_status_line *sline)
+{
+ response_context_t *ctx = bkt->data;
+ apr_status_t status;
+
+ if (ctx->state != STATE_STATUS_LINE) {
+ /* We already read it and moved on. Just return it. */
+ *sline = ctx->sl;
+ return APR_SUCCESS;
+ }
+
+ /* Running the state machine once will advance the machine, or state
+ * that the stream isn't ready with enough data. There isn't ever a
+ * need to run the machine more than once to try and satisfy this. We
+ * have to look at the state to tell whether it advanced, though, as
+ * it is quite possible to advance *and* to return APR_EAGAIN.
+ */
+ status = run_machine(bkt, ctx);
+ if (ctx->state == STATE_HEADERS) {
+ *sline = ctx->sl;
+ }
+ else {
+ /* Indicate that we don't have the information yet. */
+ sline->version = 0;
+ }
+
+ return status;
+}
+
+static apr_status_t serf_response_read(serf_bucket_t *bucket,
+ apr_size_t requested,
+ const char **data, apr_size_t *len)
+{
+ response_context_t *ctx = bucket->data;
+ apr_status_t rv;
+
+ rv = wait_for_body(bucket, ctx);
+ if (rv) {
+ /* It's not possible to have read anything yet! */
+ if (APR_STATUS_IS_EOF(rv) || APR_STATUS_IS_EAGAIN(rv)) {
+ *len = 0;
+ }
+ return rv;
+ }
+
+ rv = serf_bucket_read(ctx->body, requested, data, len);
+ if (APR_STATUS_IS_EOF(rv)) {
+ if (ctx->chunked) {
+ ctx->state = STATE_TRAILERS;
+ /* Mask the result. */
+ rv = APR_SUCCESS;
+ }
+ else {
+ ctx->state = STATE_DONE;
+ }
+ }
+ return rv;
+}
+
+static apr_status_t serf_response_readline(serf_bucket_t *bucket,
+ int acceptable, int *found,
+ const char **data, apr_size_t *len)
+{
+ response_context_t *ctx = bucket->data;
+ apr_status_t rv;
+
+ rv = wait_for_body(bucket, ctx);
+ if (rv) {
+ return rv;
+ }
+
+ /* Delegate to the stream bucket to do the readline. */
+ return serf_bucket_readline(ctx->body, acceptable, found, data, len);
+}
+
+/* ### need to implement */
+#define serf_response_peek NULL
+
+SERF_DECLARE_DATA const serf_bucket_type_t serf_bucket_type_response = {
+ "RESPONSE",
+ serf_response_read,
+ serf_response_readline,
+ serf_default_read_iovec,
+ serf_default_read_for_sendfile,
+ serf_default_read_bucket,
+ serf_response_peek,
+ serf_response_destroy_and_data,
+ serf_default_snapshot,
+ serf_default_restore_snapshot,
+ serf_default_is_snapshot_set,
+};
diff --git a/trunk/src/third_party/serf/serf.diff b/trunk/src/third_party/serf/serf.diff
new file mode 100644
index 0000000..ca65c7d
--- /dev/null
+++ b/trunk/src/third_party/serf/serf.diff
@@ -0,0 +1,58 @@
+Index: buckets/allocator.c
+===================================================================
+--- buckets/allocator.c (revision 1421)
++++ buckets/allocator.c (working copy)
+@@ -83,6 +83,7 @@
+ struct serf_bucket_alloc_t {
+ apr_pool_t *pool;
+ apr_allocator_t *allocator;
++ int own_allocator;
+
+ serf_unfreed_func_t unfreed;
+ void *unfreed_baton;
+@@ -106,7 +107,9 @@
+ if (allocator->blocks) {
+ apr_allocator_free(allocator->allocator, allocator->blocks);
+ }
+-
++ if (allocator->own_allocator == 1) {
++ apr_allocator_destroy(allocator->allocator);
++ }
+ return APR_SUCCESS;
+ }
+
+@@ -119,10 +122,12 @@
+
+ allocator->pool = pool;
+ allocator->allocator = apr_pool_allocator_get(pool);
++ allocator->own_allocator = 0;
+ if (allocator->allocator == NULL) {
+ /* This most likely means pools are running in debug mode, create our
+ * own allocator to deal with memory ourselves */
+ apr_allocator_create(&allocator->allocator);
++ allocator->own_allocator = 1;
+ }
+ allocator->unfreed = unfreed;
+ allocator->unfreed_baton = unfreed_baton;
+@@ -408,4 +413,3 @@
+ }
+ #endif
+ }
+-
+Index: buckets/headers_buckets.c
+===================================================================
+--- buckets/headers_buckets.c (revision 1421)
++++ buckets/headers_buckets.c (working copy)
+@@ -97,6 +97,12 @@
+ new_val[found->value_size] = ',';
+ memcpy(new_val + found->value_size + 1, value, value_size);
+ new_val[new_size] = '\0';
++
++ // If the previous value was added, we must free it before overwriting
++ if (found->alloc_flags & ALLOC_VALUE) {
++ serf_bucket_mem_free(bkt->allocator, (void *)found->value);
++ }
++
+ found->value = new_val;
+ found->value_size = new_size;
+ found->alloc_flags |= ALLOC_VALUE;
diff --git a/trunk/src/third_party/serf/serf.gyp b/trunk/src/third_party/serf/serf.gyp
new file mode 100644
index 0000000..fbbe74d
--- /dev/null
+++ b/trunk/src/third_party/serf/serf.gyp
@@ -0,0 +1,69 @@
+# Copyright 2009 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.
+
+{
+ 'variables': {
+ 'serf_root': '<(DEPTH)/third_party/serf',
+ 'serf_src': '<(serf_root)/src',
+ },
+ 'targets': [
+ {
+ 'target_name': 'serf',
+ 'type': '<(library)',
+ 'dependencies': [
+ '<(DEPTH)/third_party/apache/apr/apr.gyp:include',
+ '<(DEPTH)/third_party/apache/aprutil/aprutil.gyp:include',
+ '<(DEPTH)/third_party/zlib/zlib.gyp:zlib',
+ ],
+ 'sources': [
+ '<(serf_root)/instaweb_context.c',
+ '<(serf_src)/context.c',
+ '<(serf_src)/buckets/aggregate_buckets.c',
+ '<(serf_root)/instaweb_allocator.c',
+ '<(serf_src)/buckets/barrier_buckets.c',
+ '<(serf_src)/buckets/buckets.c',
+ '<(serf_src)/buckets/chunk_buckets.c',
+ '<(serf_src)/buckets/dechunk_buckets.c',
+ '<(serf_src)/buckets/deflate_buckets.c',
+ '<(serf_src)/buckets/file_buckets.c',
+ '<(serf_root)/instaweb_headers_buckets.c',
+ '<(serf_src)/buckets/limit_buckets.c',
+ '<(serf_src)/buckets/mmap_buckets.c',
+ '<(serf_src)/buckets/request_buckets.c',
+# There are two bugs in serf 0.3.1.
+# 1. There is a buffer-overrun risk in fetch_headers
+# 2. Serf always unzips zipped content, which is not what we want, and
+# then it leaves the 'gzip' headers in. This is in contrast to the
+# behavior of curl and wget, which provide gzipped output if that's
+# what was requested.
+#
+# We will try to pursue fixes to these issues with the Serf community but
+# in the meantime we will override our own version of response_buckets.c,
+# which will be placed in the root directory.
+ '<(serf_root)/instaweb_response_buckets.c',
+ '<(serf_src)/buckets/simple_buckets.c',
+ '<(serf_src)/buckets/socket_buckets.c',
+ ],
+ 'include_dirs': [
+ '<(serf_src)',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(serf_src)',
+ ],
+ },
+ }
+ ],
+}
+