From 86410f276f4c511cd077db6f9711f81ba421c67c Mon Sep 17 00:00:00 2001 From: Kyungwook Tak Date: Mon, 30 Nov 2015 13:51:38 +0900 Subject: [PATCH] Initial commit Change-Id: Iaf7f316deb8bdb8e705f6f74676e2228ac32c3ee Signed-off-by: Kyungwook Tak --- .gitignore | 10 + AUTHORS | 1 + CMakeLists.txt | 69 + LICENSE | 203 ++ LICENSE.BSD-3-Clause | 27 + LICENSE.MPL-1.1 | 65 + packaging/pubkey-pinning.manifest | 5 + packaging/pubkey-pinning.spec | 95 + src/CMakeLists.txt | 21 + src/common/CMakeLists.txt | 52 + src/common/base/logging.h | 44 + src/common/base/strings/string16.h | 36 + src/common/include/tpkp_common.h | 115 ++ src/common/include/tpkp_error.h | 46 + src/common/include/tpkp_logger.h | 29 + src/common/net/http/transport_security_state.cpp | 370 ++++ src/common/net/http/transport_security_state.h | 58 + .../net/http/transport_security_state_static.h | 2074 ++++++++++++++++++++ src/common/src/tpkp_common.cpp | 259 +++ src/common/url/third_party/mozilla/url_parse.h | 393 ++++ src/common/url/url_constants.cc | 53 + src/common/url/url_constants.h | 57 + src/common/url/url_export.h | 55 + src/common/url/url_file.h | 105 + src/common/url/url_parse.cc | 932 +++++++++ src/common/url/url_parse_file.cc | 247 +++ src/common/url/url_parse_internal.h | 113 ++ src/common/url/url_util.cc | 187 ++ src/common/url/url_util.h | 72 + src/common/url/url_util_internal.h | 48 + src/curl/CMakeLists.txt | 53 + src/curl/include/tpkp_curl.h | 141 ++ src/curl/tpkp_curl.cpp | 188 ++ src/gnutls/CMakeLists.txt | 52 + src/gnutls/include/tpkp_gnutls.h | 51 + src/gnutls/tpkp_gnutls.cpp | 229 +++ test/CMakeLists.txt | 53 + test/colors.cpp | 50 + test/colors.h | 49 + test/colour_log_formatter.cpp | 244 +++ test/colour_log_formatter.h | 68 + test/curl_test.cpp | 435 ++++ test/gnutls_test.cpp | 188 ++ test/main.cpp | 38 + tpkp-curl.pc.in | 9 + tpkp-gnutls.pc.in | 9 + 46 files changed, 7698 insertions(+) create mode 100644 .gitignore create mode 100644 AUTHORS create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 LICENSE.BSD-3-Clause create mode 100644 LICENSE.MPL-1.1 create mode 100644 packaging/pubkey-pinning.manifest create mode 100644 packaging/pubkey-pinning.spec create mode 100644 src/CMakeLists.txt create mode 100644 src/common/CMakeLists.txt create mode 100644 src/common/base/logging.h create mode 100644 src/common/base/strings/string16.h create mode 100644 src/common/include/tpkp_common.h create mode 100644 src/common/include/tpkp_error.h create mode 100644 src/common/include/tpkp_logger.h create mode 100644 src/common/net/http/transport_security_state.cpp create mode 100644 src/common/net/http/transport_security_state.h create mode 100644 src/common/net/http/transport_security_state_static.h create mode 100644 src/common/src/tpkp_common.cpp create mode 100644 src/common/url/third_party/mozilla/url_parse.h create mode 100644 src/common/url/url_constants.cc create mode 100644 src/common/url/url_constants.h create mode 100644 src/common/url/url_export.h create mode 100644 src/common/url/url_file.h create mode 100644 src/common/url/url_parse.cc create mode 100644 src/common/url/url_parse_file.cc create mode 100644 src/common/url/url_parse_internal.h create mode 100644 src/common/url/url_util.cc create mode 100644 src/common/url/url_util.h create mode 100644 src/common/url/url_util_internal.h create mode 100644 src/curl/CMakeLists.txt create mode 100644 src/curl/include/tpkp_curl.h create mode 100644 src/curl/tpkp_curl.cpp create mode 100644 src/gnutls/CMakeLists.txt create mode 100644 src/gnutls/include/tpkp_gnutls.h create mode 100644 src/gnutls/tpkp_gnutls.cpp create mode 100644 test/CMakeLists.txt create mode 100644 test/colors.cpp create mode 100644 test/colors.h create mode 100644 test/colour_log_formatter.cpp create mode 100644 test/colour_log_formatter.h create mode 100644 test/curl_test.cpp create mode 100644 test/gnutls_test.cpp create mode 100644 test/main.cpp create mode 100644 tpkp-curl.pc.in create mode 100644 tpkp-gnutls.pc.in diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..44d6a2d --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# cscope/ctag data # +#################### +/cscope.files +/cscope.out +/tags + +# Temporary files # +################### +*.swp +*~ diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..f129c9d --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Kyungwook Tak diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..18937c3 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,69 @@ +# Copyright (c) 2015 Samsung Electronics Co., Ltd 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. +# +# @file CMakeLists.txt +# @author Kyungwook Tak (k.tak@samsung.com) +# @brief TPKP Project makefile on top +# +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) +PROJECT(pubkey-pinning) + +INCLUDE(FindPkgConfig) + +STRING(REGEX MATCH "([^.]*)" SO_VERSION "${VERSION}") + +# compiler options +SET(GC_SECTIONS_FLAGS "-fdata-sections -ffunction-sections -Wl,--gc-sections") +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${GC_SECTIONS_FLAGS}") +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GC_SECTIONS_FLAGS}") +SET(CMAKE_C_FLAGS_RELEASE "-g -O2") +SET(CMAKE_CXX_FLAGS_RELEASE "-g -std=c++0x -O2") +SET(CMAKE_C_FLAGS_DEBUG "-g -O0 -Wp,-U_FORTIFY_SOURCE") +SET(CMAKE_CXX_FLAGS_DEBUG "-g -std=c++0x -O0 -Wp,-U_FORTIFY_SOURCE") +SET(CMAKE_C_FLAGS_CCOV "-g -O2 --coverage") +SET(CMAKE_CXX_FLAGS_CCOV "-g -std=c++0x -O2 --coverage") +SET(CMAKE_SHARED_LINKER_FLAGS "-Wl,--as-needed") +SET(CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed") +SET(CMAKE_SKIP_RPATH "TRUE") + +IF (CMAKE_BUILD_TYPE MATCHES "DEBUG") +ADD_DEFINITIONS("-DBUILD_TYPE_DEBUG") +ADD_DEFINITIONS("-DDPL_LOGS_ENABLED") +ENDIF (CMAKE_BUILD_TYPE MATCHES "DEBUG") + +# compiler warning flags +ADD_DEFINITIONS("-Wall") +ADD_DEFINITIONS("-Wextra") +ADD_DEFINITIONS("-Werror") + +SET(TARGET_TPKP_COMMON_LIB "tpkp-common") +SET(TARGET_TPKP_CURL_LIB "tpkp-curl") +SET(TARGET_TPKP_GNUTLS_LIB "tpkp-gnutls") + +CONFIGURE_FILE(tpkp-curl.pc.in tpkp-curl.pc @ONLY) +CONFIGURE_FILE(tpkp-gnutls.pc.in tpkp-gnutls.pc @ONLY) +INSTALL( + FILES + tpkp-curl.pc + tpkp-gnutls.pc + DESTINATION + ${LIB_INSTALL_DIR}/pkgconfig + ) + +ADD_SUBDIRECTORY(src) + +IF (DEFINED PUBKEY_PINNING_TEST_BUILD) +SET(TARGET_TPKP_TEST "tpkp-internal-test") +ADD_SUBDIRECTORY(test) +ENDIF (DEFINED PUBKEY_PINNING_TEST_BUILD) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..181359e --- /dev/null +++ b/LICENSE @@ -0,0 +1,203 @@ +Copyright (c) 2000 - 2012 Samsung Electronics Co., Ltd. All rights reserved. + + 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 [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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/LICENSE.BSD-3-Clause b/LICENSE.BSD-3-Clause new file mode 100644 index 0000000..80341c4 --- /dev/null +++ b/LICENSE.BSD-3-Clause @@ -0,0 +1,27 @@ +Copyright 2014 The Chromium Authors. 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. diff --git a/LICENSE.MPL-1.1 b/LICENSE.MPL-1.1 new file mode 100644 index 0000000..9b62f1c --- /dev/null +++ b/LICENSE.MPL-1.1 @@ -0,0 +1,65 @@ +Copyright 2007, 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. + +------------------------------------------------------------------------------- + +The file url_parse.cc is based on nsURLParsers.cc from Mozilla. This file is +licensed separately as follows: + +The contents of this file are subject to the Mozilla Public License Version +1.1 (the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at +http://www.mozilla.org/MPL/ + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +for the specific language governing rights and limitations under the +License. + +The Original Code is mozilla.org code. + +The Initial Developer of the Original Code is +Netscape Communications Corporation. +Portions created by the Initial Developer are Copyright (C) 1998 +the Initial Developer. All Rights Reserved. + +Contributor(s): + Darin Fisher (original author) + +Alternatively, the contents of this file may be used under the terms of +either the GNU General Public License Version 2 or later (the "GPL"), or +the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +in which case the provisions of the GPL or the LGPL are applicable instead +of those above. If you wish to allow use of your version of this file only +under the terms of either the GPL or the LGPL, and not to allow others to +use your version of this file under the terms of the MPL, indicate your +decision by deleting the provisions above and replace them with the notice +and other provisions required by the GPL or the LGPL. If you do not delete +the provisions above, a recipient may use your version of this file under +the terms of any one of the MPL, the GPL or the LGPL. \ No newline at end of file diff --git a/packaging/pubkey-pinning.manifest b/packaging/pubkey-pinning.manifest new file mode 100644 index 0000000..a76fdba --- /dev/null +++ b/packaging/pubkey-pinning.manifest @@ -0,0 +1,5 @@ + + + + + diff --git a/packaging/pubkey-pinning.spec b/packaging/pubkey-pinning.spec new file mode 100644 index 0000000..2a7451d --- /dev/null +++ b/packaging/pubkey-pinning.spec @@ -0,0 +1,95 @@ +%define pubkey_pinning_test_build 0 + +Name: pubkey-pinning +Summary: Https Public Key Pinning for Tizen platform +Version: 0.0.1 +Release: 0 +Group: Security/Libraries +License: Apache-2.0 and BSD-3-Clause and MPL-1.1 +Source0: %name-%version.tar.gz +Source1: %name.manifest +BuildRequires: cmake +BuildRequires: pkgconfig(glib-2.0) +BuildRequires: pkgconfig(dlog) +BuildRequires: pkgconfig(libxml-2.0) +BuildRequires: pkgconfig(libiri) +BuildRequires: pkgconfig(libcurl) +BuildRequires: pkgconfig(gnutls) +BuildRequires: pkgconfig(openssl) + +%description +Https Public Key Pinning for Tizen platform system framework. + +%package devel +Summary: Tizen HPKP library development files +Group: Development/Libraries +Requires: %name = %version-%release + +%description devel +Tizen HPKP library development files including headers and +pkgconfig. + +%if 0%{?pubkey_pinning_test_build} +%package test +Summary: Tizen HPKP library internal test +Group: Security/Testing +BuildRequires: boost-devel +Requires: %name = %version-%release + +%description test +Tizen HPKP library internal test with boost test framework. +%endif + +%prep +%setup -q +cp %SOURCE1 . + +%build +export CFLAGS="$CFLAGS -DTIZEN_DEBUG_ENABLE" +export CXXFLAGS="$CXXFLAGS -DTIZEN_DEBUG_ENABLE" +export FFLAGS="$FFLAGS -DTIZEN_DEBUG_ENABLE" + +export LDFLAGS+="-Wl,--rpath=%_prefix/lib" + + +%{!?build_type:%define build_type "Release"} +%cmake . -DCMAKE_INSTALL_PREFIX=%_prefix \ + -DVERSION=%version \ + -DINCLUDEDIR=%_includedir \ + -DCMAKE_BUILD_TYPE=%build_type \ +%if 0%{?pubkey_pinning_test_build} + -DPUBKEY_PINNING_TEST_BUILD=1 \ +%endif + -DCMAKE_VERBOSE_MAKEFILE=ON + +make %{?_smp_mflags} + +%install +%make_install + +%post -p /sbin/ldconfig +%postun -p /sbin/ldconfig + +%files +%manifest %name.manifest +%license LICENSE +%license LICENSE.BSD-3-Clause +%license LICENSE.MPL-1.1 +%_libdir/libtpkp-common.so.* +%_libdir/libtpkp-curl.so.* +%_libdir/libtpkp-gnutls.so.* + +%files devel +%_includedir/tpkp/common/tpkp_error.h +%_includedir/tpkp/curl/tpkp_curl.h +%_includedir/tpkp/gnutls/tpkp_gnutls.h +%_libdir/pkgconfig/tpkp-curl.pc +%_libdir/pkgconfig/tpkp-gnutls.pc +%_libdir/libtpkp-common.so +%_libdir/libtpkp-curl.so +%_libdir/libtpkp-gnutls.so + +%if 0%{?pubkey_pinning_test_build} +%files test +%_bindir/tpkp-internal-test +%endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..ce746d3 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,21 @@ +# Copyright (c) 2015 Samsung Electronics Co., Ltd 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. +# +# @file CMakeLists.txt +# @author Kyungwook Tak (k.tak@samsung.com) +# @brief TPKP lib makefile +# +ADD_SUBDIRECTORY(common) +ADD_SUBDIRECTORY(curl) +ADD_SUBDIRECTORY(gnutls) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt new file mode 100644 index 0000000..6933a6d --- /dev/null +++ b/src/common/CMakeLists.txt @@ -0,0 +1,52 @@ +# Copyright (c) 2015 Samsung Electronics Co., Ltd 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. +# +# @file CMakeLists.txt +# @author Kyungwook Tak (k.tak@samsung.com) +# @brief TPKP common lib makefile +# +PKG_CHECK_MODULES(TPKP_COMMON_DEP + REQUIRED + dlog + ) + +INCLUDE_DIRECTORIES( + SYSTEM + ${TPKP_COMMON_DEP_INCLUDE_DIRS} + . # common internal headers + include # common interface headers + ) + +SET(TPKP_COMMON_SRCS + url/url_constants.cc + url/url_util.cc + url/url_parse.cc + url/url_parse_file.cc + net/http/transport_security_state.cpp + src/tpkp_common.cpp + ) + +ADD_LIBRARY(${TARGET_TPKP_COMMON_LIB} SHARED ${TPKP_COMMON_SRCS}) + +SET_TARGET_PROPERTIES(${TARGET_TPKP_COMMON_LIB} + PROPERTIES + COMPILE_FLAGS "-D_GNU_SOURCE -fPIC -fvisibility=hidden" + SOVERSION ${SO_VERSION} + VERSION ${VERSION} + ) + +TARGET_LINK_LIBRARIES(${TARGET_TPKP_COMMON_LIB} ${TPKP_COMMON_DEP_LIBRARIES}) + +INSTALL(TARGETS ${TARGET_TPKP_COMMON_LIB} DESTINATION ${LIB_INSTALL_DIR}) +INSTALL(FILES include/tpkp_error.h DESTINATION ${INCLUDEDIR}/tpkp/common) diff --git a/src/common/base/logging.h b/src/common/base/logging.h new file mode 100644 index 0000000..c44e291 --- /dev/null +++ b/src/common/base/logging.h @@ -0,0 +1,44 @@ +/* + * Copyright 2014 The Chromium Authors. 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. + */ +#pragma once + +#include "tpkp_logger.h" + +#define DCHECK(condition, format, arg...) \ + if(!(condition)) \ + SLOGE("Check failed: " #condition ". " format, ##arg) + +#define DCHECK_LE(arg1, arg2) \ + if(!((arg1)<=(arg2))) \ + SLOGE("Check failed: " #arg1 "<=" #arg2 ".") + +#define DCHECK_LT(arg1, arg2) \ + if(!((arg1)<(arg2))) \ + SLOGE("Check failed: " #arg1 "<" #arg2 ".") diff --git a/src/common/base/strings/string16.h b/src/common/base/strings/string16.h new file mode 100644 index 0000000..a24994c --- /dev/null +++ b/src/common/base/strings/string16.h @@ -0,0 +1,36 @@ +/* + * Copyright 2014 The Chromium Authors. 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. + */ +#pragma once + +namespace base { + +typedef wchar_t char16; + +} diff --git a/src/common/include/tpkp_common.h b/src/common/include/tpkp_common.h new file mode 100644 index 0000000..a525b49 --- /dev/null +++ b/src/common/include/tpkp_common.h @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd 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. + */ +/* + * @file tpkp_common.h + * @author Kyungwook Tak (k.tak@samsung.com) + * @version 1.0 + * @brief Tizen Https Public Key Pinning common library interface. + */ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "tpkp_error.h" +#include "tpkp_logger.h" + +#define EXPORT_API __attribute__((visibility("default"))) + +namespace TPKP { + +enum class HashAlgo : int { + SHA1, + SHA256, // currently not supported. preloaded public keys were hashed with SHA1 + COUNT, + DEFAULT = SHA1 +}; + +/* hash size as a byte */ +enum class HashSize : size_t { + SHA1 = 20, + SHA256 = 32, + DEFAULT = SHA1 +}; + +template +constexpr auto typeCast(E e) -> typename std::underlying_type::type +{ + return static_cast::type>(e); +} + +using RawBuffer = std::vector; + +struct HashValue { + HashAlgo algo; + RawBuffer hash; + + HashValue() = delete; + explicit HashValue(HashAlgo _algo, const RawBuffer &_hash) + : algo(_algo) + , hash(_hash) + {} +}; + +using HashValueVector = std::vector; + +class EXPORT_API Exception : public std::exception { +public: + explicit Exception(tpkp_e code, const std::string &message); + virtual const char *what(void) const noexcept; + tpkp_e code(void) const noexcept; + +private: + tpkp_e m_code; + std::string m_message; +}; + +EXPORT_API +tpkp_e ExceptionSafe(const std::function &func) noexcept; + +class EXPORT_API Context { +public: + Context() = delete; + virtual ~Context(); + explicit Context(const std::string &url); + + void addPubkeyHash(HashAlgo algo, const RawBuffer &hashBuf); + bool checkPubkeyPins(void); + bool hasPins(void); + +private: + class Impl; + std::unique_ptr pImpl; +}; + +EXPORT_API +pid_t getThreadId(void); + +} + +#define TPKP_THROW_EXCEPTION(code, message) \ +do { \ + std::ostringstream log; \ + log << message; \ + throw TPKP::Exception(code, log.str()); \ +} while(false) + +#define TPKP_CHECK_THROW_EXCEPTION(cond, code, message) \ + if (!(cond)) TPKP_THROW_EXCEPTION(code, message) diff --git a/src/common/include/tpkp_error.h b/src/common/include/tpkp_error.h new file mode 100644 index 0000000..a7149e1 --- /dev/null +++ b/src/common/include/tpkp_error.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd 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. + */ +/* + * @file tpkp_error.h + * @author Kyungwook Tak (k.tak@samsung.com) + * @version 1.0 + * @brief Tizen Https Public Key Pinning error codes. + */ +#ifndef TPKP_ERROR_H_ +#define TPKP_ERROR_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + TPKP_E_NONE = 0, + TPKP_E_MEMORY = -1, + TPKP_E_INVALID_URL = -2, + TPKP_E_INVALID_CERT = -3, + TPKP_E_FAILED_GET_PUBKEY_HASH = -4, + TPKP_E_NO_URL_DATA = -5, + TPKP_E_INVALID_PEER_CERT_CHAIN = -6, + TPKP_E_PUBKEY_MISMATCH = -7, + TPKP_E_STD_EXCEPTION = -99, + TPKP_E_INTERNAL = -100 +} tpkp_e; + +#ifdef __cplusplus +} +#endif + +#endif /* TPKP_ERROR_H_ */ diff --git a/src/common/include/tpkp_logger.h b/src/common/include/tpkp_logger.h new file mode 100644 index 0000000..0a148c9 --- /dev/null +++ b/src/common/include/tpkp_logger.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd 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. + */ +/* + * @file tpkp_logger.h + * @author Kyungwook Tak (k.tak@samsung.com) + * @version 1.0 + * @brief Tizen Https Public Key Pinning dlog wrapper. + */ +#pragma once + +#ifdef LOG_TAG +#undef LOG_TAG +#endif +#define LOG_TAG "TPKP" + +#include diff --git a/src/common/net/http/transport_security_state.cpp b/src/common/net/http/transport_security_state.cpp new file mode 100644 index 0000000..605b274 --- /dev/null +++ b/src/common/net/http/transport_security_state.cpp @@ -0,0 +1,370 @@ +/* + * Copyright 2014 The Chromium Authors. 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. + */ + +// This file is subset of chromium-efl/net/http/transport_security_state.cc + +#include "net/http/transport_security_state.h" +#include "net/http/transport_security_state_static.h" +#include "base/logging.h" + +namespace { + +// BitReader is a class that allows a bytestring to be read bit-by-bit. +class BitReader { + public: + BitReader(const uint8* bytes, size_t num_bits) + : bytes_(bytes), + num_bits_(num_bits), + num_bytes_((num_bits + 7) / 8), + current_byte_index_(0), + num_bits_used_(8) {} + + // Next sets |*out| to the next bit from the input. It returns false if no + // more bits are available or true otherwise. + bool Next(bool* out) { + if (num_bits_used_ == 8) { + if (current_byte_index_ >= num_bytes_) { + return false; + } + current_byte_ = bytes_[current_byte_index_++]; + num_bits_used_ = 0; + } + + *out = 1 & (current_byte_ >> (7 - num_bits_used_)); + num_bits_used_++; + return true; + } + + // Read sets the |num_bits| least-significant bits of |*out| to the value of + // the next |num_bits| bits from the input. It returns false if there are + // insufficient bits in the input or true otherwise. + bool Read(unsigned num_bits, uint32* out) { + DCHECK_LE(num_bits, 32u); + + uint32 ret = 0; + for (unsigned i = 0; i < num_bits; ++i) { + bool bit; + if (!Next(&bit)) { + return false; + } + ret |= static_cast(bit) << (num_bits - 1 - i); + } + + *out = ret; + return true; + } + + // Unary sets |*out| to the result of decoding a unary value from the input. + // It returns false if there were insufficient bits in the input and true + // otherwise. + bool Unary(size_t* out) { + size_t ret = 0; + + for (;;) { + bool bit; + if (!Next(&bit)) { + return false; + } + if (!bit) { + break; + } + ret++; + } + + *out = ret; + return true; + } + + // Seek sets the current offest in the input to bit number |offset|. It + // returns true if |offset| is within the range of the input and false + // otherwise. + bool Seek(size_t offset) { + if (offset >= num_bits_) { + return false; + } + current_byte_index_ = offset / 8; + current_byte_ = bytes_[current_byte_index_++]; + num_bits_used_ = offset % 8; + return true; + } + + private: + const uint8* const bytes_; + const size_t num_bits_; + const size_t num_bytes_; + // current_byte_index_ contains the current byte offset in |bytes_|. + size_t current_byte_index_; + // current_byte_ contains the current byte of the input. + uint8 current_byte_; + // num_bits_used_ contains the number of bits of |current_byte_| that have + // been read. + unsigned num_bits_used_; +}; + +// HuffmanDecoder is a very simple Huffman reader. The input Huffman tree is +// simply encoded as a series of two-byte structures. The first byte determines +// the "0" pointer for that node and the second the "1" pointer. Each byte +// either has the MSB set, in which case the bottom 7 bits are the value for +// that position, or else the bottom seven bits contain the index of a node. +// +// The tree is decoded by walking rather than a table-driven approach. +class HuffmanDecoder { + public: + HuffmanDecoder(const uint8* tree, size_t tree_bytes) + : tree_(tree), + tree_bytes_(tree_bytes) {} + + bool Decode(BitReader* reader, char* out) { + const uint8* current = &tree_[tree_bytes_-2]; + + for (;;) { + bool bit; + if (!reader->Next(&bit)) { + return false; + } + + uint8 b = current[bit]; + if (b & 0x80) { + *out = static_cast(b & 0x7f); + return true; + } + + unsigned offset = static_cast(b) * 2; + DCHECK_LT(offset, tree_bytes_); + if (offset >= tree_bytes_) { + return false; + } + + current = &tree_[offset]; + } + } + + private: + const uint8* const tree_; + const size_t tree_bytes_; +}; + +} // namespace anonymous + +namespace net { + +// DecodeHSTSPreloadRaw resolves |hostname| in the preloaded data. It returns +// false on internal error and true otherwise. After a successful return, +// |*out_found| is true iff a relevant entry has been found. If so, |*out| +// contains the details. +// +// Don't call this function, call DecodeHSTSPreload, below. +// +// Although this code should be robust, it never processes attacker-controlled +// data -- it only operates on the preloaded data built into the binary. +// +// The preloaded data is represented as a trie and matches the hostname +// backwards. Each node in the trie starts with a number of characters, which +// must match exactly. After that is a dispatch table which maps the next +// character in the hostname to another node in the trie. +// +// In the dispatch table, the zero character represents the "end of string" +// (which is the *beginning* of a hostname since we process it backwards). The +// value in that case is special -- rather than an offset to another trie node, +// it contains the HSTS information: whether subdomains are included, pinsets +// etc. If an "end of string" matches a period in the hostname then the +// information is remembered because, if no more specific node is found, then +// that information applies to the hostname. +// +// Dispatch tables are always given in order, but the "end of string" (zero) +// value always comes before an entry for '.'. +bool DecodeHSTSPreloadRaw(const std::string& hostname, + bool* out_found, + PreloadResult* out) { + HuffmanDecoder huffman(kHSTSHuffmanTree, sizeof(kHSTSHuffmanTree)); + BitReader reader(kPreloadedHSTSData, kPreloadedHSTSBits); + size_t bit_offset = kHSTSRootPosition; + static const char kEndOfString = 0; + static const char kEndOfTable = 127; + + *out_found = false; + + if (hostname.empty()) { + return true; + } + // hostname_offset contains one more than the index of the current character + // in the hostname that is being considered. It's one greater so that we can + // represent the position just before the beginning (with zero). + size_t hostname_offset = hostname.size(); + + for (;;) { + // Seek to the desired location. + if (!reader.Seek(bit_offset)) { + return false; + } + + // Decode the unary length of the common prefix. + size_t prefix_length; + if (!reader.Unary(&prefix_length)) { + return false; + } + + // Match each character in the prefix. + for (size_t i = 0; i < prefix_length; ++i) { + if (hostname_offset == 0) { + // We can't match the terminator with a prefix string. + return true; + } + + char c; + if (!huffman.Decode(&reader, &c)) { + return false; + } + if (hostname[hostname_offset - 1] != c) { + return true; + } + hostname_offset--; + } + + bool is_first_offset = true; + size_t current_offset = 0; + + // Next is the dispatch table. + for (;;) { + char c; + if (!huffman.Decode(&reader, &c)) { + return false; + } + if (c == kEndOfTable) { + // No exact match. + return true; + } + + if (c == kEndOfString) { + PreloadResult tmp; + if (!reader.Next(&tmp.sts_include_subdomains) || + !reader.Next(&tmp.force_https) || + !reader.Next(&tmp.has_pins)) { + return false; + } + + tmp.pkp_include_subdomains = tmp.sts_include_subdomains; + + if (tmp.has_pins) { + if (!reader.Read(4, &tmp.pinset_id) || + !reader.Read(9, &tmp.domain_id) || + (!tmp.sts_include_subdomains && + !reader.Next(&tmp.pkp_include_subdomains))) { + return false; + } + } + + tmp.hostname_offset = hostname_offset; + + if (hostname_offset == 0 || hostname[hostname_offset - 1] == '.') { + *out_found = + tmp.sts_include_subdomains || tmp.pkp_include_subdomains; + *out = tmp; + + if (hostname_offset > 0) { + out->force_https &= tmp.sts_include_subdomains; + } else { + *out_found = true; + return true; + } + } + + continue; + } + + // The entries in a dispatch table are in order thus we can tell if there + // will be no match if the current character past the one that we want. + if (hostname_offset == 0 || hostname[hostname_offset-1] < c) { + return true; + } + + if (is_first_offset) { + // The first offset is backwards from the current position. + uint32 jump_delta_bits; + uint32 jump_delta; + if (!reader.Read(5, &jump_delta_bits) || + !reader.Read(jump_delta_bits, &jump_delta)) { + return false; + } + + if (bit_offset < jump_delta) { + return false; + } + + current_offset = bit_offset - jump_delta; + is_first_offset = false; + } else { + // Subsequent offsets are forward from the target of the first offset. + uint32 is_long_jump; + if (!reader.Read(1, &is_long_jump)) { + return false; + } + + uint32 jump_delta; + if (!is_long_jump) { + if (!reader.Read(7, &jump_delta)) { + return false; + } + } else { + uint32 jump_delta_bits; + if (!reader.Read(4, &jump_delta_bits) || + !reader.Read(jump_delta_bits + 8, &jump_delta)) { + return false; + } + } + + current_offset += jump_delta; + if (current_offset >= bit_offset) { + return false; + } + } + + DCHECK_LT(0u, hostname_offset); + if (hostname[hostname_offset - 1] == c) { + bit_offset = current_offset; + hostname_offset--; + break; + } + } + } +} + +bool DecodeHSTSPreload(const std::string& hostname, PreloadResult* out) +{ + bool found; + if (!DecodeHSTSPreloadRaw(hostname, &found, out)) { + DCHECK(false, "Internal error in DecodeHSTSPreloadRaw for hostname %s", hostname.c_str()); + return false; + } + + return found; +} + +} // namespace TPKP diff --git a/src/common/net/http/transport_security_state.h b/src/common/net/http/transport_security_state.h new file mode 100644 index 0000000..4344f9c --- /dev/null +++ b/src/common/net/http/transport_security_state.h @@ -0,0 +1,58 @@ +/* + * Copyright 2014 The Chromium Authors. 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. + */ + +// This file is subset of chromium-efl/net/http/transport_security_state.cc +#pragma once + +#include +#include + +#define uint8 unsigned char +#define uint32 unsigned int + +namespace net { + +// PreloadResult is the result of resolving a specific name in the preloaded +// data. +struct PreloadResult { + uint32 pinset_id; + uint32 domain_id; + // hostname_offset contains the number of bytes from the start of the given + // hostname where the name of the matching entry starts. + size_t hostname_offset; + bool sts_include_subdomains; + bool pkp_include_subdomains; + bool force_https; + bool has_pins; +}; + +bool DecodeHSTSPreload(const std::string &hostname, PreloadResult *out); + +} diff --git a/src/common/net/http/transport_security_state_static.h b/src/common/net/http/transport_security_state_static.h new file mode 100644 index 0000000..f4fbbfc --- /dev/null +++ b/src/common/net/http/transport_security_state_static.h @@ -0,0 +1,2074 @@ +/* + * Copyright 2014 The Chromium Authors. 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. + */ + +// This file is automatically generated by transport_security_state_static_generate.go. +// You can find it at https://github.com/agl/transport-security-state-generate. + +#pragma once + +enum SecondLevelDomainName { + DOMAIN_NOT_PINNED, + DOMAIN_GOOGLE_COM, + DOMAIN_ANDROID_COM, + DOMAIN_GOOGLE_ANALYTICS_COM, + DOMAIN_GOOGLEPLEX_COM, + DOMAIN_YTIMG_COM, + DOMAIN_GOOGLEUSERCONTENT_COM, + DOMAIN_YOUTUBE_COM, + DOMAIN_GOOGLEAPIS_COM, + DOMAIN_GOOGLEADSERVICES_COM, + DOMAIN_GOOGLECODE_COM, + DOMAIN_APPSPOT_COM, + DOMAIN_GOOGLESYNDICATION_COM, + DOMAIN_DOUBLECLICK_NET, + DOMAIN_GSTATIC_COM, + DOMAIN_GMAIL_COM, + DOMAIN_GOOGLEMAIL_COM, + DOMAIN_GOOGLEGROUPS_COM, + DOMAIN_TORPROJECT_ORG, + DOMAIN_TWITTER_COM, + DOMAIN_TWIMG_COM, + DOMAIN_AKAMAIHD_NET, + DOMAIN_TOR2WEB_ORG, + DOMAIN_YOUTU_BE, + DOMAIN_GOOGLECOMMERCE_COM, + DOMAIN_URCHIN_COM, + DOMAIN_GOO_GL, + DOMAIN_G_CO, + DOMAIN_GOOGLE_AC, + DOMAIN_GOOGLE_AD, + DOMAIN_GOOGLE_AE, + DOMAIN_GOOGLE_AF, + DOMAIN_GOOGLE_AG, + DOMAIN_GOOGLE_AM, + DOMAIN_GOOGLE_AS, + DOMAIN_GOOGLE_AT, + DOMAIN_GOOGLE_AZ, + DOMAIN_GOOGLE_BA, + DOMAIN_GOOGLE_BE, + DOMAIN_GOOGLE_BF, + DOMAIN_GOOGLE_BG, + DOMAIN_GOOGLE_BI, + DOMAIN_GOOGLE_BJ, + DOMAIN_GOOGLE_BS, + DOMAIN_GOOGLE_BY, + DOMAIN_GOOGLE_CA, + DOMAIN_GOOGLE_CAT, + DOMAIN_GOOGLE_CC, + DOMAIN_GOOGLE_CD, + DOMAIN_GOOGLE_CF, + DOMAIN_GOOGLE_CG, + DOMAIN_GOOGLE_CH, + DOMAIN_GOOGLE_CI, + DOMAIN_GOOGLE_CL, + DOMAIN_GOOGLE_CM, + DOMAIN_GOOGLE_CN, + DOMAIN_CO_AO, + DOMAIN_CO_BW, + DOMAIN_CO_CK, + DOMAIN_CO_CR, + DOMAIN_CO_HU, + DOMAIN_CO_ID, + DOMAIN_CO_IL, + DOMAIN_CO_IM, + DOMAIN_CO_IN, + DOMAIN_CO_JE, + DOMAIN_CO_JP, + DOMAIN_CO_KE, + DOMAIN_CO_KR, + DOMAIN_CO_LS, + DOMAIN_CO_MA, + DOMAIN_CO_MZ, + DOMAIN_CO_NZ, + DOMAIN_CO_TH, + DOMAIN_CO_TZ, + DOMAIN_CO_UG, + DOMAIN_CO_UK, + DOMAIN_CO_UZ, + DOMAIN_CO_VE, + DOMAIN_CO_VI, + DOMAIN_CO_ZA, + DOMAIN_CO_ZM, + DOMAIN_CO_ZW, + DOMAIN_COM_AF, + DOMAIN_COM_AG, + DOMAIN_COM_AI, + DOMAIN_COM_AR, + DOMAIN_COM_AU, + DOMAIN_COM_BD, + DOMAIN_COM_BH, + DOMAIN_COM_BN, + DOMAIN_COM_BO, + DOMAIN_COM_BR, + DOMAIN_COM_BY, + DOMAIN_COM_BZ, + DOMAIN_COM_CN, + DOMAIN_COM_CO, + DOMAIN_COM_CU, + DOMAIN_COM_CY, + DOMAIN_COM_DO, + DOMAIN_COM_EC, + DOMAIN_COM_EG, + DOMAIN_COM_ET, + DOMAIN_COM_FJ, + DOMAIN_COM_GE, + DOMAIN_COM_GH, + DOMAIN_COM_GI, + DOMAIN_COM_GR, + DOMAIN_COM_GT, + DOMAIN_COM_HK, + DOMAIN_COM_IQ, + DOMAIN_COM_JM, + DOMAIN_COM_JO, + DOMAIN_COM_KH, + DOMAIN_COM_KW, + DOMAIN_COM_LB, + DOMAIN_COM_LY, + DOMAIN_COM_MT, + DOMAIN_COM_MX, + DOMAIN_COM_MY, + DOMAIN_COM_NA, + DOMAIN_COM_NF, + DOMAIN_COM_NG, + DOMAIN_COM_NI, + DOMAIN_COM_NP, + DOMAIN_COM_NR, + DOMAIN_COM_OM, + DOMAIN_COM_PA, + DOMAIN_COM_PE, + DOMAIN_COM_PH, + DOMAIN_COM_PK, + DOMAIN_COM_PL, + DOMAIN_COM_PR, + DOMAIN_COM_PY, + DOMAIN_COM_QA, + DOMAIN_COM_RU, + DOMAIN_COM_SA, + DOMAIN_COM_SB, + DOMAIN_COM_SG, + DOMAIN_COM_SL, + DOMAIN_COM_SV, + DOMAIN_COM_TJ, + DOMAIN_COM_TN, + DOMAIN_COM_TR, + DOMAIN_COM_TW, + DOMAIN_COM_UA, + DOMAIN_COM_UY, + DOMAIN_COM_VC, + DOMAIN_COM_VE, + DOMAIN_COM_VN, + DOMAIN_GOOGLE_CV, + DOMAIN_GOOGLE_CZ, + DOMAIN_GOOGLE_DE, + DOMAIN_GOOGLE_DJ, + DOMAIN_GOOGLE_DK, + DOMAIN_GOOGLE_DM, + DOMAIN_GOOGLE_DZ, + DOMAIN_GOOGLE_EE, + DOMAIN_GOOGLE_ES, + DOMAIN_GOOGLE_FI, + DOMAIN_GOOGLE_FM, + DOMAIN_GOOGLE_FR, + DOMAIN_GOOGLE_GA, + DOMAIN_GOOGLE_GE, + DOMAIN_GOOGLE_GG, + DOMAIN_GOOGLE_GL, + DOMAIN_GOOGLE_GM, + DOMAIN_GOOGLE_GP, + DOMAIN_GOOGLE_GR, + DOMAIN_GOOGLE_GY, + DOMAIN_GOOGLE_HK, + DOMAIN_GOOGLE_HN, + DOMAIN_GOOGLE_HR, + DOMAIN_GOOGLE_HT, + DOMAIN_GOOGLE_HU, + DOMAIN_GOOGLE_IE, + DOMAIN_GOOGLE_IM, + DOMAIN_GOOGLE_INFO, + DOMAIN_GOOGLE_IQ, + DOMAIN_GOOGLE_IS, + DOMAIN_GOOGLE_IT, + DOMAIN_IT_AO, + DOMAIN_GOOGLE_JE, + DOMAIN_GOOGLE_JO, + DOMAIN_GOOGLE_JOBS, + DOMAIN_GOOGLE_JP, + DOMAIN_GOOGLE_KG, + DOMAIN_GOOGLE_KI, + DOMAIN_GOOGLE_KZ, + DOMAIN_GOOGLE_LA, + DOMAIN_GOOGLE_LI, + DOMAIN_GOOGLE_LK, + DOMAIN_GOOGLE_LT, + DOMAIN_GOOGLE_LU, + DOMAIN_GOOGLE_LV, + DOMAIN_GOOGLE_MD, + DOMAIN_GOOGLE_ME, + DOMAIN_GOOGLE_MG, + DOMAIN_GOOGLE_MK, + DOMAIN_GOOGLE_ML, + DOMAIN_GOOGLE_MN, + DOMAIN_GOOGLE_MS, + DOMAIN_GOOGLE_MU, + DOMAIN_GOOGLE_MV, + DOMAIN_GOOGLE_MW, + DOMAIN_GOOGLE_NE, + DOMAIN_NE_JP, + DOMAIN_GOOGLE_NET, + DOMAIN_GOOGLE_NL, + DOMAIN_GOOGLE_NO, + DOMAIN_GOOGLE_NR, + DOMAIN_GOOGLE_NU, + DOMAIN_OFF_AI, + DOMAIN_GOOGLE_PK, + DOMAIN_GOOGLE_PL, + DOMAIN_GOOGLE_PN, + DOMAIN_GOOGLE_PS, + DOMAIN_GOOGLE_PT, + DOMAIN_GOOGLE_RO, + DOMAIN_GOOGLE_RS, + DOMAIN_GOOGLE_RU, + DOMAIN_GOOGLE_RW, + DOMAIN_GOOGLE_SC, + DOMAIN_GOOGLE_SE, + DOMAIN_GOOGLE_SH, + DOMAIN_GOOGLE_SI, + DOMAIN_GOOGLE_SK, + DOMAIN_GOOGLE_SM, + DOMAIN_GOOGLE_SN, + DOMAIN_GOOGLE_SO, + DOMAIN_GOOGLE_ST, + DOMAIN_GOOGLE_TD, + DOMAIN_GOOGLE_TG, + DOMAIN_GOOGLE_TK, + DOMAIN_GOOGLE_TL, + DOMAIN_GOOGLE_TM, + DOMAIN_GOOGLE_TN, + DOMAIN_GOOGLE_TO, + DOMAIN_GOOGLE_TP, + DOMAIN_GOOGLE_TT, + DOMAIN_GOOGLE_US, + DOMAIN_GOOGLE_UZ, + DOMAIN_GOOGLE_VG, + DOMAIN_GOOGLE_VU, + DOMAIN_GOOGLE_WS, + DOMAIN_CHROMIUM_ORG, + DOMAIN_CRYPTO_CAT, + DOMAIN_LAVABIT_COM, + DOMAIN_GOOGLETAGMANAGER_COM, + DOMAIN_GOOGLETAGSERVICES_COM, + DOMAIN_DROPBOX_COM, + DOMAIN_YOUTUBE_NOCOOKIE_COM, + DOMAIN_2MDN_NET, + DOMAIN_FACEBOOK_COM, + DOMAIN_SPIDEROAK_COM, + DOMAIN_BLOGGER_COM, + DOMAIN_CHROME_COM, + // Boundary value for UMA_HISTOGRAM_ENUMERATION. + DOMAIN_NUM_EVENTS, +}; + +// These are SubjectPublicKeyInfo hashes for public key pinning. The +// hashes are SHA1 digests. + +static const char kSPKIHash_TestSPKI[] = + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; + +static const char kSPKIHash_VeriSignClass3[] = + "\xe2\x7f\x7b\xd8\x77\xd5\xdf\x9e\x0a\x3f" + "\x9e\xb4\xcb\x0e\x2e\xa9\xef\xdb\x69\x77"; + +static const char kSPKIHash_VeriSignClass3_G3[] = + "\x22\xf1\x9e\x2e\xc6\xea\xcc\xfc\x5d\x23" + "\x46\xf4\xc2\xe8\xf6\xc5\x54\xdd\x5e\x07"; + +static const char kSPKIHash_GoogleBackup2048[] = + "\xbe\xae\xce\xca\x34\xa7\xa8\xe7\x28\xf6" + "\x7c\x8c\x08\x31\x9d\xcb\xbe\xde\x8a\x33"; + +static const char kSPKIHash_GoogleG2[] = + "\x43\xda\xd6\x30\xee\x53\xf8\xa9\x80\xca" + "\x6e\xfd\x85\xf4\x6a\xa3\x79\x90\xe0\xea"; + +static const char kSPKIHash_GeoTrustGlobal[] = + "\xc0\x7a\x98\x68\x8d\x89\xfb\xab\x05\x64" + "\x0c\x11\x7d\xaa\x7d\x65\xb8\xca\xcc\x4e"; + +static const char kSPKIHash_GeoTrustPrimary[] = + "\xb0\x19\x89\xe7\xef\xfb\x4a\xaf\xcb\x14" + "\x8f\x58\x46\x39\x76\x22\x41\x50\xe1\xba"; + +static const char kSPKIHash_RapidSSL[] = + "\xa3\x93\x99\xc4\x04\xc3\xb2\x09\xb0\x81" + "\xc2\x1f\x21\x62\x27\x78\xc2\x74\x8e\x4c"; + +static const char kSPKIHash_DigiCertEVRoot[] = + "\x83\x31\x7e\x62\x85\x42\x53\xd6\xd7\x78" + "\x31\x90\xec\x91\x90\x56\xe9\x91\xb9\xe3"; + +static const char kSPKIHash_DigiCertAssuredIDRoot[] = + "\x68\x33\x0e\x61\x35\x85\x21\x59\x29\x83" + "\xa3\xc8\xd2\xd2\xe1\x40\x6e\x7a\xb3\xc1"; + +static const char kSPKIHash_DigiCertGlobalRoot[] = + "\xd5\x2e\x13\xc1\xab\xe3\x49\xda\xe8\xb4" + "\x95\x94\xef\x7c\x38\x43\x60\x64\x66\xbd"; + +static const char kSPKIHash_Tor1[] = + "\x8e\xe3\x71\x49\x3b\xfd\x50\x03\x66\xa4" + "\x2f\x64\x17\x91\x8a\xa6\x65\x8d\xc7\x76"; + +static const char kSPKIHash_Tor2[] = + "\x96\x26\xb8\xde\x53\xe8\x97\x34\x8f\x54" + "\x8a\xb7\xe0\x3c\x39\xee\xe6\x1c\x2c\x3f"; + +static const char kSPKIHash_Tor3[] = + "\xaf\x31\x32\x40\x82\x8e\x87\xbe\xe3\xf3" + "\xb9\xf9\x6e\x35\x94\x36\x0b\x97\x17\xc6"; + +static const char kSPKIHash_VeriSignClass1[] = + "\x23\x43\xd1\x48\xa2\x55\x89\x9b\x94\x7d" + "\x46\x1a\x79\x7e\xc0\x4c\xfe\xd1\x70\xb7"; + +static const char kSPKIHash_VeriSignClass3_G4[] = + "\xed\x66\x31\x35\xd3\x1b\xd4\xec\xa6\x14" + "\xc4\x29\xe3\x19\x06\x9f\x94\xc1\x26\x50"; + +static const char kSPKIHash_VeriSignClass4_G3[] = + "\x3c\x03\x43\x68\x68\x95\x1c\xf3\x69\x2a" + "\xb8\xb4\x26\xda\xba\x8f\xe9\x22\xe5\xbd"; + +static const char kSPKIHash_VeriSignClass1_G3[] = + "\x55\x19\xb2\x78\xac\xb2\x81\xd7\xed\xa7" + "\xab\xc1\x83\x99\xc3\xbb\x69\x04\x24\xb5"; + +static const char kSPKIHash_VeriSignClass2_G3[] = + "\x5a\xbe\xc5\x75\xdc\xae\xf3\xb0\x8e\x27" + "\x19\x43\xfc\x7f\x25\x0c\x3d\xf6\x61\xe3"; + +static const char kSPKIHash_VeriSignClass3_G2[] = + "\x1a\x21\xb4\x95\x2b\x62\x93\xce\x18\xb3" + "\x65\xec\x9c\x0e\x93\x4c\xb3\x81\xe6\xd4"; + +static const char kSPKIHash_VeriSignClass2_G2[] = + "\x12\x37\xba\x45\x17\xee\xad\x29\x26\xfd" + "\xc1\xcd\xfe\xbe\xed\xf2\xde\xd9\x14\x5c"; + +static const char kSPKIHash_VeriSignClass3_G5[] = + "\xb1\x81\x08\x1a\x19\xa4\xc0\x94\x1f\xfa" + "\xe8\x95\x28\xc1\x24\xc9\x9b\x34\xac\xc7"; + +static const char kSPKIHash_VeriSignUniversal[] = + "\xbb\xc2\x3e\x29\x0b\xb3\x28\x77\x1d\xad" + "\x3e\xa2\x4d\xbd\xf4\x23\xbd\x06\xb0\x3d"; + +static const char kSPKIHash_Twitter1[] = + "\x56\xfe\xf3\xc2\x14\x7d\x4e\xd3\x88\x37" + "\xfd\xbd\x30\x52\x38\x72\x01\xe5\x77\x8d"; + +static const char kSPKIHash_GeoTrustGlobal2[] = + "\x71\x38\x36\xf2\x02\x31\x53\x47\x2b\x6e" + "\xba\x65\x46\xa9\x10\x15\x58\x20\x05\x09"; + +static const char kSPKIHash_GeoTrustUniversal[] = + "\x87\xe8\x5b\x63\x53\xc6\x23\xa3\x12\x8c" + "\xb0\xff\xbb\xf5\x51\xfe\x59\x80\x0e\x22"; + +static const char kSPKIHash_GeoTrustUniversal2[] = + "\x5e\x4f\x53\x86\x85\xdd\x4f\x9e\xca\x5f" + "\xdc\x0d\x45\x6f\x7d\x51\xb1\xdc\x9b\x7b"; + +static const char kSPKIHash_GeoTrustPrimary_G2[] = + "\xbd\xbe\xa7\x1b\xab\x71\x57\xf9\xe4\x75" + "\xd9\x54\xd2\xb7\x27\x80\x1a\x82\x26\x82"; + +static const char kSPKIHash_GeoTrustPrimary_G3[] = + "\x9c\xa9\x8d\x00\xaf\x74\x0d\xdd\x81\x80" + "\xd2\x13\x45\xa5\x8b\x8f\x2e\x94\x38\xd6"; + +static const char kSPKIHash_Entrust_2048[] = + "\x55\xe4\x81\xd1\x11\x80\xbe\xd8\x89\xb9" + "\x08\xa3\x31\xf9\xa1\x24\x09\x16\xb9\x70"; + +static const char kSPKIHash_Entrust_EV[] = + "\xba\x42\xb0\x81\x88\x53\x88\x1d\x86\x63" + "\xbd\x4c\xc0\x5e\x08\xfe\xea\x6e\xbb\x77"; + +static const char kSPKIHash_Entrust_G2[] = + "\xab\x30\xd3\xaf\x4b\xd8\xf1\x6b\x58\x69" + "\xee\x45\x69\x29\xda\x84\xb8\x73\x94\x88"; + +static const char kSPKIHash_Entrust_SSL[] = + "\xf0\x17\x62\x13\x55\x3d\xb3\xff\x0a\x00" + "\x6b\xfb\x50\x84\x97\xf3\xed\x62\xd0\x1a"; + +static const char kSPKIHash_AAACertificateServices[] = + "\xc4\x30\x28\xc5\xd3\xe3\x08\x0c\x10\x44" + "\x8b\x2c\x77\xba\x24\x53\x97\x60\xbb\xf9"; + +static const char kSPKIHash_AddTrustClass1CARoot[] = + "\x8b\xdb\xd7\xcc\xa0\x68\x53\x42\x16\xf4" + "\xc1\x2b\x25\x44\xfc\x02\x9c\xa5\x8b\x47"; + +static const char kSPKIHash_AddTrustExternalCARoot[] = + "\x4f\x9c\x7d\x21\x79\x9c\xad\x0e\xd8\xb9" + "\x0c\x57\x9f\x1a\x02\x99\xe7\x90\xf3\x87"; + +static const char kSPKIHash_AddTrustPublicCARoot[] = + "\xa8\x57\x65\xd6\xe8\x32\xc8\xc5\x19\x63" + "\x73\x5a\x9a\x17\x74\x3a\x81\xdf\xee\x2e"; + +static const char kSPKIHash_AddTrustQualifiedCARoot[] = + "\xbc\xe4\xb7\x23\x12\x55\x98\xe5\x63\x41" + "\x19\x1c\x50\xe4\xb6\x47\xc2\x76\x05\xd7"; + +static const char kSPKIHash_COMODOCertificationAuthority[] = + "\x11\xe4\x91\xd1\xc9\xe4\xc0\xeb\x9a\xce" + "\xcf\x73\x54\x5d\xe1\xf1\xa8\x30\x3e\xc3"; + +static const char kSPKIHash_SecureCertificateServices[] = + "\x3c\xb4\x1a\x84\x2e\xf5\x5c\xf2\x1a\x3d" + "\xa5\x4a\xc8\xd1\xbe\x39\x08\x76\x37\xbc"; + +static const char kSPKIHash_TrustedCertificateServices[] = + "\xfe\x72\xc8\xeb\xbf\x0c\x2f\xbb\x0e\x26" + "\x13\x93\x93\x3c\x2c\xa9\x8d\xdc\x24\x94"; + +static const char kSPKIHash_UTNDATACorpSGC[] = + "\x53\x32\xd1\xb3\xcf\x7f\xfa\xe0\xf1\xa0" + "\x5d\x85\x4e\x92\xd2\x9e\x45\x1d\xb4\x4f"; + +static const char kSPKIHash_UTNUSERFirstClientAuthenticationandEmail[] = + "\x89\x82\x67\x7d\xc4\x9d\x26\x70\x00\x4b" + "\xb4\x50\x48\x7c\xde\x3d\xae\x04\x6e\x7d"; + +static const char kSPKIHash_UTNUSERFirstHardware[] = + "\xa1\x72\x5f\x26\x1b\x28\x98\x43\x95\x5d" + "\x07\x37\xd5\x85\x96\x9d\x4b\xd2\xc3\x45"; + +static const char kSPKIHash_UTNUSERFirstObject[] = + "\xda\xed\x64\x74\x14\x9c\x14\x3c\xab\xdd" + "\x99\xa9\xbd\x5b\x28\x4d\x8b\x3c\xc9\xd8"; + +static const char kSPKIHash_GTECyberTrustGlobalRoot[] = + "\x59\x79\x12\xde\x61\x75\xd6\x6f\xc4\x23" + "\xb7\x77\x13\x74\xc7\x96\xde\x6f\x88\x72"; + +static const char kSPKIHash_BaltimoreCyberTrustRoot[] = + "\x30\xa4\xe6\x4f\xde\x76\x8a\xfc\xed\x5a" + "\x90\x84\x28\x30\x46\x79\x2c\x29\x15\x70"; + +static const char kSPKIHash_GlobalSignRootCA[] = + "\x87\xdb\xd4\x5f\xb0\x92\x8d\x4e\x1d\xf8" + "\x15\x67\xe7\xf2\xab\xaf\xd6\x2b\x67\x75"; + +static const char kSPKIHash_GlobalSignRootCA_R2[] = + "\xa5\x06\x8a\x78\xcf\x84\xbd\x74\x32\xdd" + "\x58\xf9\x65\xeb\x3a\x55\xe7\xc7\x80\xdc"; + +static const char kSPKIHash_GlobalSignRootCA_R3[] = + "\xf7\x93\x19\xef\xdf\xc1\xf5\x20\xfb\xac" + "\x85\x55\x2c\xf2\xd2\x8f\x5a\xb9\xca\x0b"; + +static const char kSPKIHash_EntrustRootEC1[] = + "\x07\x23\x2d\x45\x65\x87\xb9\xd7\xb1\xd9" + "\x7d\xd1\xc5\xfb\x65\xc5\x89\xbf\x92\x96"; + +static const char kSPKIHash_TheGoDaddyGroupClass2[] = + "\xee\xe5\x9f\x1e\x2a\xa5\x44\xc3\xcb\x25" + "\x43\xa6\x9a\x5b\xd4\x6a\x25\xbc\xbb\x8e"; + +static const char kSPKIHash_GoDaddyRoot_G2[] = + "\x21\x0f\x2c\x89\xf7\xc4\xcd\x5d\x1b\x82" + "\x5e\x38\xd6\xc6\x59\x3b\xa6\x93\x75\xae"; + +static const char kSPKIHash_GoDaddySecure[] = + "\xba\x2e\xb5\xa8\x3e\x13\x23\xd9\x53\x4b" + "\x5e\x65\xbc\xe7\xa3\x13\x5d\xd0\xa9\x96"; + +static const char kSPKIHash_ThawtePremiumServer[] = + "\x5f\xf3\x24\x6c\x8f\x91\x24\xaf\x9b\x5f" + "\x3e\xb0\x34\x6a\xf4\x2d\x5c\xa8\x5d\xcc"; + +static const char kSPKIHash_ThawtePrimaryRootCA_G2[] = + "\x6a\x25\x23\x9d\x62\x75\xcd\x52\x21\x69" + "\x5c\x31\xe9\x89\xc4\xd5\x38\xb8\xc4\xea"; + +static const char kSPKIHash_ThawtePrimaryRootCA_G3[] = + "\xab\x76\x88\xf4\xe5\xe1\x38\xc9\xe9\x50" + "\x17\xcd\xcd\xb3\x18\x17\xb3\x3e\x8c\xf5"; + +static const char kSPKIHash_ThawtePrimaryRootCA[] = + "\x6c\xca\xbd\x7d\xb4\x7e\x94\xa5\x75\x99" + "\x01\xb6\xa7\xdf\xd4\x5d\x1c\x09\x1c\xcc"; + +static const char kSPKIHash_SymantecClass3EVG3[] = + "\x47\x49\xdf\x16\x57\xf4\x6c\x8b\xd2\x8c" + "\x79\x1b\x99\xfb\x9f\x28\x81\x2a\x60\xe0"; + +static const char kSPKIHash_DigiCertECCSecureServerCA[] = + "\xc3\x02\xb1\x73\x62\x0b\x47\x78\x5d\x21" + "\xe2\x4d\xb3\xac\x46\xb1\xfc\xb8\xc5\xa9"; + +static const char kSPKIHash_FacebookBackup[] = + "\xd7\x0c\x3c\x13\x40\x18\xb1\x1d\xa8\x5f" + "\x99\x67\x76\x4d\xa1\xc2\x9d\x94\xa2\xc9"; + +static const char kSPKIHash_SpiderOak1[] = + "\x50\xfa\xef\x15\x44\xab\xa7\xd6\x9a\x97" + "\x9b\xfa\x46\x7d\x09\xbf\x76\x09\xff\x05"; + +static const char kSPKIHash_SpiderOak2[] = + "\x0f\x47\xd2\xfe\x1a\xae\x03\xa4\x29\xae" + "\x5b\x9c\x8b\x23\xb5\x86\x51\x54\x03\x18"; + +// The following is static data describing the hosts that are hardcoded with +// certificate pins or HSTS information. + +// kNoRejectedPublicKeys is a placeholder for when no public keys are rejected. +static const char* const kNoRejectedPublicKeys[] = { + NULL, +}; + +static const char* const kTestAcceptableCerts[] = { + kSPKIHash_TestSPKI, + NULL, +}; +static const char* const kGoogleAcceptableCerts[] = { + kSPKIHash_GoogleBackup2048, + kSPKIHash_GoogleG2, + NULL, +}; +static const char* const kTorAcceptableCerts[] = { + kSPKIHash_RapidSSL, + kSPKIHash_DigiCertEVRoot, + kSPKIHash_Tor1, + kSPKIHash_Tor2, + kSPKIHash_Tor3, + NULL, +}; +static const char* const kTwitterComAcceptableCerts[] = { + kSPKIHash_VeriSignClass1, + kSPKIHash_VeriSignClass3, + kSPKIHash_VeriSignClass3_G4, + kSPKIHash_VeriSignClass4_G3, + kSPKIHash_VeriSignClass3_G3, + kSPKIHash_VeriSignClass1_G3, + kSPKIHash_VeriSignClass2_G3, + kSPKIHash_VeriSignClass3_G2, + kSPKIHash_VeriSignClass2_G2, + kSPKIHash_VeriSignClass3_G5, + kSPKIHash_VeriSignUniversal, + kSPKIHash_GeoTrustGlobal, + kSPKIHash_GeoTrustGlobal2, + kSPKIHash_GeoTrustUniversal, + kSPKIHash_GeoTrustUniversal2, + kSPKIHash_GeoTrustPrimary, + kSPKIHash_GeoTrustPrimary_G2, + kSPKIHash_GeoTrustPrimary_G3, + kSPKIHash_DigiCertGlobalRoot, + kSPKIHash_DigiCertEVRoot, + kSPKIHash_DigiCertAssuredIDRoot, + kSPKIHash_Twitter1, + NULL, +}; +static const char* const kTwitterCDNAcceptableCerts[] = { + kSPKIHash_VeriSignClass1, + kSPKIHash_VeriSignClass3, + kSPKIHash_VeriSignClass3_G4, + kSPKIHash_VeriSignClass4_G3, + kSPKIHash_VeriSignClass3_G3, + kSPKIHash_VeriSignClass1_G3, + kSPKIHash_VeriSignClass2_G3, + kSPKIHash_VeriSignClass3_G2, + kSPKIHash_VeriSignClass2_G2, + kSPKIHash_VeriSignClass3_G5, + kSPKIHash_VeriSignUniversal, + kSPKIHash_GeoTrustGlobal, + kSPKIHash_GeoTrustGlobal2, + kSPKIHash_GeoTrustUniversal, + kSPKIHash_GeoTrustUniversal2, + kSPKIHash_GeoTrustPrimary, + kSPKIHash_GeoTrustPrimary_G2, + kSPKIHash_GeoTrustPrimary_G3, + kSPKIHash_DigiCertGlobalRoot, + kSPKIHash_DigiCertEVRoot, + kSPKIHash_DigiCertAssuredIDRoot, + kSPKIHash_Twitter1, + kSPKIHash_Entrust_2048, + kSPKIHash_Entrust_EV, + kSPKIHash_Entrust_G2, + kSPKIHash_Entrust_SSL, + kSPKIHash_AAACertificateServices, + kSPKIHash_AddTrustClass1CARoot, + kSPKIHash_AddTrustExternalCARoot, + kSPKIHash_AddTrustPublicCARoot, + kSPKIHash_AddTrustQualifiedCARoot, + kSPKIHash_COMODOCertificationAuthority, + kSPKIHash_SecureCertificateServices, + kSPKIHash_TrustedCertificateServices, + kSPKIHash_UTNDATACorpSGC, + kSPKIHash_UTNUSERFirstClientAuthenticationandEmail, + kSPKIHash_UTNUSERFirstHardware, + kSPKIHash_UTNUSERFirstObject, + kSPKIHash_GTECyberTrustGlobalRoot, + kSPKIHash_BaltimoreCyberTrustRoot, + kSPKIHash_GlobalSignRootCA, + kSPKIHash_GlobalSignRootCA_R2, + kSPKIHash_GlobalSignRootCA_R3, + NULL, +}; +static const char* const kDropboxAcceptableCerts[] = { + kSPKIHash_DigiCertAssuredIDRoot, + kSPKIHash_DigiCertGlobalRoot, + kSPKIHash_DigiCertEVRoot, + kSPKIHash_EntrustRootEC1, + kSPKIHash_Entrust_G2, + kSPKIHash_Entrust_EV, + kSPKIHash_Entrust_2048, + kSPKIHash_GeoTrustGlobal, + kSPKIHash_GeoTrustPrimary_G2, + kSPKIHash_GeoTrustPrimary_G3, + kSPKIHash_GeoTrustPrimary, + kSPKIHash_TheGoDaddyGroupClass2, + kSPKIHash_GoDaddyRoot_G2, + kSPKIHash_GoDaddySecure, + kSPKIHash_ThawtePremiumServer, + kSPKIHash_ThawtePrimaryRootCA_G2, + kSPKIHash_ThawtePrimaryRootCA_G3, + kSPKIHash_ThawtePrimaryRootCA, + NULL, +}; +static const char* const kFacebookAcceptableCerts[] = { + kSPKIHash_SymantecClass3EVG3, + kSPKIHash_DigiCertECCSecureServerCA, + kSPKIHash_DigiCertEVRoot, + kSPKIHash_FacebookBackup, + NULL, +}; +static const char* const kSpideroakAcceptableCerts[] = { + kSPKIHash_RapidSSL, + kSPKIHash_SpiderOak1, + kSPKIHash_SpiderOak2, + NULL, +}; + +struct Pinset { + const char *const *const accepted_pins; + const char *const *const rejected_pins; +}; + +static const struct Pinset kPinsets[] = { + {kTestAcceptableCerts, kNoRejectedPublicKeys}, + {kGoogleAcceptableCerts, kNoRejectedPublicKeys}, + {kTorAcceptableCerts, kNoRejectedPublicKeys}, + {kTwitterComAcceptableCerts, kNoRejectedPublicKeys}, + {kTwitterCDNAcceptableCerts, kNoRejectedPublicKeys}, + {kDropboxAcceptableCerts, kNoRejectedPublicKeys}, + {kFacebookAcceptableCerts, kNoRejectedPublicKeys}, + {kSpideroakAcceptableCerts, kNoRejectedPublicKeys}, +}; + +// kHSTSHuffmanTree describes a Huffman tree. The nodes of the tree are pairs +// of uint8s. The last node in the array is the root of the tree. Each pair is +// two uint8 values, the first is "left" and the second is "right". If a uint8 +// value has the MSB set then it represents a literal leaf value. Otherwise +// it's a pointer to the n'th element of the array. +static const uint8_t kHSTSHuffmanTree[] = { + 0xf9, 0xe6, 0xf0, 0x00, 0x01, 0xf2, 0xae, 0xed, 0xe9, 0x03, 0x02, 0x04, + 0xf1, 0xb2, 0xb1, 0xb5, 0xb3, 0x07, 0x06, 0x08, 0x09, 0xfa, 0x0a, 0xf6, + 0xeb, 0x0b, 0x0c, 0xe3, 0xe1, 0x0d, 0x80, 0x0e, 0x05, 0x0f, 0xe2, 0xf7, + 0x11, 0xf3, 0xef, 0x12, 0xff, 0x13, 0xe8, 0xf5, 0xee, 0x15, 0xe5, 0x16, + 0xf4, 0xe7, 0xb7, 0xb6, 0xb9, 0xb8, 0x19, 0x1a, 0x1b, 0xb0, 0xb4, 0x1c, + 0x1d, 0xea, 0xf8, 0x1e, 0xad, 0x1f, 0x20, 0xe4, 0x21, 0xec, 0x18, 0x22, + 0x17, 0x23, 0x14, 0x24, 0x10, 0x25, +}; + +static const uint8_t kPreloadedHSTSData[] = { + 0xfe, 0x36, 0x7f, 0xb5, 0x5d, 0x54, 0x44, 0xb3, 0xef, 0xf6, 0xab, 0xaa, + 0x88, 0xb6, 0x6c, 0xd9, 0x29, 0x32, 0x52, 0x07, 0xcd, 0x28, 0x05, 0xa7, + 0xf7, 0x33, 0xe0, 0x39, 0x5d, 0x23, 0x1a, 0x79, 0xf9, 0xfa, 0x27, 0x78, + 0xe2, 0xd0, 0xc7, 0xe3, 0x48, 0x53, 0xfc, 0x2d, 0xa2, 0xeb, 0x1c, 0x96, + 0x91, 0xb1, 0x34, 0x5f, 0x28, 0x78, 0x04, 0x41, 0x3f, 0x8d, 0x9f, 0xed, + 0x57, 0x55, 0x15, 0x14, 0xff, 0xf6, 0x8b, 0x46, 0x0f, 0x32, 0xcf, 0x5b, + 0x3d, 0x5a, 0x7d, 0xfe, 0xd5, 0x75, 0x51, 0x5e, 0xcf, 0x89, 0xd7, 0xbb, + 0xd8, 0xb4, 0x8d, 0x87, 0xbf, 0xf3, 0x39, 0xff, 0x8d, 0x4f, 0x36, 0x7f, + 0xb5, 0x5d, 0x54, 0x48, 0xd3, 0xff, 0x8c, 0x7a, 0x79, 0xb3, 0xfd, 0xaa, + 0xea, 0xa2, 0x78, 0x9f, 0xfc, 0x63, 0xd3, 0xcd, 0x9f, 0xed, 0x57, 0x55, + 0x13, 0xfc, 0xff, 0xe3, 0x1e, 0x9e, 0x6c, 0xff, 0x6a, 0xba, 0xa8, 0xa1, + 0xa7, 0xff, 0x18, 0xf4, 0xf3, 0x67, 0xfb, 0x55, 0xd5, 0x45, 0x11, 0x3f, + 0xea, 0x79, 0xb3, 0xfd, 0xaa, 0xea, 0xa2, 0x91, 0x9f, 0xff, 0xb3, 0xdd, + 0x30, 0x46, 0xe0, 0xd0, 0x2f, 0x76, 0x02, 0x52, 0x31, 0xd1, 0x49, 0xe4, + 0x93, 0x3f, 0xf1, 0xa9, 0xe6, 0xcf, 0xf6, 0xab, 0xaa, 0x89, 0x42, 0x2c, + 0x5d, 0x99, 0x7c, 0x70, 0x7d, 0x3f, 0xf3, 0x9f, 0x7e, 0x16, 0x07, 0x27, + 0xd2, 0x78, 0x15, 0x5d, 0x05, 0x5b, 0xaa, 0xef, 0x0f, 0x97, 0x52, 0xc9, + 0xff, 0xc6, 0x3d, 0x3c, 0xd9, 0xfe, 0xd5, 0x75, 0x51, 0x39, 0xcf, 0xfe, + 0x31, 0xe9, 0xe6, 0xcf, 0xf6, 0xab, 0xaa, 0x8a, 0x26, 0x7f, 0xef, 0xbc, + 0xda, 0x2b, 0xb8, 0xf2, 0x02, 0xd1, 0xf4, 0x77, 0xdd, 0x57, 0x75, 0x59, + 0xfc, 0x6c, 0xff, 0x6a, 0xba, 0xa8, 0x87, 0x27, 0xf9, 0xbf, 0x9d, 0xd3, + 0x6c, 0xf5, 0xa7, 0xdf, 0xed, 0x57, 0x55, 0x11, 0x7c, 0xfe, 0xbe, 0x7b, + 0xac, 0x05, 0x2d, 0x35, 0xf6, 0x5a, 0x7b, 0x8f, 0x60, 0xd2, 0x83, 0x9b, + 0x9a, 0x17, 0x91, 0xac, 0x47, 0xb6, 0x1c, 0x38, 0x66, 0x2d, 0x73, 0xff, + 0x8c, 0x7a, 0x79, 0xb3, 0xfd, 0xaa, 0xea, 0xa2, 0x64, 0x9f, 0xc6, 0xcf, + 0xf6, 0xab, 0xaa, 0x8b, 0x7a, 0x7f, 0xf1, 0x8f, 0x4f, 0x36, 0x7f, 0xb5, + 0x5d, 0x54, 0x52, 0x73, 0x79, 0xb7, 0x49, 0x69, 0xfb, 0x60, 0x05, 0xfa, + 0x02, 0xd3, 0x68, 0xce, 0x89, 0xe8, 0x51, 0x0c, 0x32, 0xae, 0x17, 0xc6, + 0x69, 0x88, 0x77, 0x55, 0x76, 0x7c, 0x14, 0x29, 0x67, 0xf1, 0xb3, 0xfd, + 0xaa, 0xea, 0xa2, 0x1d, 0x9f, 0xfc, 0x63, 0xd3, 0xcd, 0x9f, 0xed, 0x57, + 0x55, 0x12, 0xc4, 0xfe, 0x36, 0x7f, 0xb5, 0x5d, 0x54, 0x46, 0x13, 0xef, + 0xf6, 0xab, 0xaa, 0x88, 0xf6, 0x7a, 0xf5, 0xb3, 0xb5, 0xa7, 0x8d, 0x4f, + 0x36, 0x1e, 0xbf, 0x0c, 0xe7, 0xf1, 0xb3, 0xfd, 0xaa, 0xea, 0xa2, 0xc3, + 0x9f, 0xc6, 0xcf, 0xf6, 0xab, 0xaa, 0x8b, 0x9e, 0x19, 0x3d, 0xcb, 0x0f, + 0x9e, 0xaa, 0x50, 0x95, 0x39, 0xc7, 0x0f, 0xa7, 0xf1, 0xb3, 0xfd, 0xaa, + 0xea, 0xa2, 0x1e, 0x9f, 0x7f, 0xb5, 0x5d, 0x54, 0x44, 0xd3, 0xff, 0xb3, + 0x95, 0xad, 0x33, 0xb7, 0xe7, 0xd9, 0x69, 0xb8, 0x75, 0xa7, 0xff, 0x66, + 0x82, 0xb2, 0xec, 0x5b, 0x71, 0x8e, 0xb4, 0x6c, 0x7c, 0x3a, 0x16, 0x9e, + 0xe6, 0xf4, 0x12, 0xa2, 0x17, 0x91, 0xb1, 0x33, 0x0d, 0x19, 0xde, 0x15, + 0x5b, 0x91, 0xce, 0xfd, 0x75, 0x69, 0x69, 0x68, 0x0c, 0xd5, 0xee, 0x37, + 0x3d, 0xcd, 0x17, 0x16, 0x9f, 0xff, 0xfb, 0x5b, 0xfa, 0x59, 0xf7, 0x07, + 0x1d, 0xeb, 0x3d, 0x6b, 0xdd, 0x83, 0x5a, 0x7f, 0xf1, 0x38, 0xfc, 0xd9, + 0xfe, 0x0f, 0x54, 0x2b, 0x4d, 0x4e, 0xab, 0x46, 0x23, 0xeb, 0xa4, 0x3a, + 0x76, 0xa4, 0xb9, 0xdf, 0xae, 0xad, 0x0e, 0x89, 0xeb, 0x06, 0x77, 0x3e, + 0xd5, 0x72, 0x9e, 0xb4, 0xfe, 0x7d, 0xda, 0xfe, 0x68, 0xf3, 0x7e, 0x6d, + 0x69, 0xe2, 0xb9, 0x01, 0x69, 0xd6, 0xdb, 0x6a, 0x53, 0xea, 0x1e, 0x6b, + 0x12, 0x31, 0x7f, 0x3e, 0xae, 0x39, 0x9c, 0x5a, 0x3d, 0x44, 0xd0, 0x0f, + 0xc4, 0xd2, 0x7b, 0x55, 0x54, 0xb4, 0xff, 0xc0, 0x27, 0x3c, 0x69, 0x8b, + 0xfb, 0xf9, 0xd6, 0x9f, 0xf1, 0x06, 0xc2, 0xd7, 0x62, 0x3a, 0xd3, 0xf6, + 0x3b, 0xdd, 0xf9, 0x75, 0xa7, 0xb3, 0xdc, 0xb1, 0x68, 0xc5, 0x45, 0xbb, + 0x13, 0x14, 0x38, 0x7c, 0xe6, 0x0e, 0x0f, 0xe9, 0x2c, 0x4e, 0xf8, 0x5f, + 0x3f, 0x65, 0xad, 0x9e, 0x92, 0xd3, 0x9b, 0x70, 0x2d, 0x3f, 0xff, 0xf8, + 0x47, 0x66, 0x0c, 0x73, 0xdd, 0x13, 0x9e, 0x2f, 0x9b, 0x3f, 0xf9, 0x6a, + 0xd3, 0xe2, 0xe6, 0xed, 0x62, 0xd3, 0xff, 0xfb, 0x36, 0x7d, 0xcf, 0x83, + 0xe1, 0xd4, 0xb5, 0x61, 0x67, 0xd6, 0x9f, 0xff, 0xf5, 0xbe, 0x07, 0x3b, + 0xa2, 0x1c, 0xdf, 0x6e, 0x78, 0xab, 0x70, 0x35, 0xa7, 0xf6, 0xcf, 0xb8, + 0x7b, 0xb6, 0xcb, 0x4f, 0xea, 0xb7, 0x77, 0x1b, 0x94, 0xb4, 0x32, 0x37, + 0x49, 0xc8, 0x4d, 0xe7, 0x81, 0xc2, 0xb1, 0x69, 0xff, 0x35, 0x58, 0xc7, + 0x16, 0xd8, 0x0b, 0x43, 0x2a, 0x72, 0xc7, 0xc2, 0x28, 0xfc, 0x68, 0x54, + 0x5a, 0x24, 0x53, 0x6e, 0xf5, 0xa7, 0xcc, 0x0b, 0xd9, 0xc5, 0xa7, 0x57, + 0x2e, 0xb4, 0xdb, 0xd8, 0xb4, 0xe1, 0xe3, 0x8f, 0x36, 0x7f, 0x1b, 0x9f, + 0xb7, 0xef, 0x8f, 0x76, 0xb5, 0x68, 0x63, 0xe7, 0x23, 0x39, 0xff, 0xf6, + 0xed, 0xcf, 0x03, 0xbb, 0xaf, 0x8c, 0xe7, 0x96, 0x71, 0x69, 0xff, 0xb4, + 0xc1, 0xf8, 0xe0, 0xd7, 0x1b, 0x8b, 0x4f, 0xef, 0x1b, 0x3c, 0xb5, 0xb9, + 0xd6, 0x80, 0x1f, 0xe0, 0xa2, 0xcf, 0xfb, 0xee, 0x78, 0xbb, 0x15, 0x95, + 0xc5, 0xa1, 0x8f, 0x8b, 0x72, 0x19, 0xff, 0xff, 0xed, 0xc6, 0xff, 0x16, + 0x73, 0xc0, 0x5c, 0x27, 0xe6, 0xdc, 0xc7, 0x63, 0x97, 0x5a, 0x4c, 0xb4, + 0xfd, 0x5f, 0x2b, 0xb6, 0xcb, 0x4f, 0xfa, 0xad, 0xfb, 0x0f, 0x35, 0x8e, + 0xab, 0x43, 0xcf, 0xb3, 0xe5, 0x93, 0xfd, 0xa2, 0xfb, 0x9a, 0xac, 0xfa, + 0xd3, 0xff, 0xff, 0xf0, 0x2e, 0x39, 0x5b, 0x78, 0xf7, 0x5b, 0xf8, 0x1c, + 0xd9, 0xf9, 0xbd, 0x83, 0x95, 0xb2, 0xd1, 0xd4, 0x64, 0x11, 0xc4, 0xea, + 0xae, 0xaa, 0x29, 0x88, 0xc3, 0xc9, 0xe9, 0x14, 0xff, 0xb0, 0x76, 0x7d, + 0xf8, 0xdf, 0x71, 0x69, 0xd8, 0x40, 0x5a, 0x58, 0xb4, 0xed, 0x87, 0x00, + 0x6a, 0x5d, 0x8d, 0x41, 0x22, 0x72, 0x9a, 0x27, 0x3d, 0xb6, 0x5a, 0x7f, + 0x1f, 0xdd, 0x35, 0x99, 0x6a, 0xd1, 0xb1, 0xe8, 0xd0, 0xe4, 0xff, 0xfe, + 0x1c, 0xef, 0x38, 0xdc, 0xbb, 0x83, 0x96, 0xf8, 0x67, 0x4c, 0xb4, 0xff, + 0xba, 0xc7, 0xd6, 0x5f, 0x08, 0x56, 0x86, 0x45, 0x1e, 0x9a, 0x27, 0xdf, + 0x67, 0x6d, 0xf5, 0xa7, 0xea, 0x1f, 0x1e, 0xb0, 0xad, 0x18, 0x7e, 0xe4, + 0x45, 0xb9, 0x44, 0x58, 0xbe, 0x44, 0xf1, 0x7f, 0x61, 0x84, 0x19, 0x07, + 0x63, 0x3e, 0xd8, 0x88, 0xa1, 0x04, 0xe3, 0xdf, 0xe3, 0x33, 0x3c, 0x2f, + 0x45, 0xcb, 0x91, 0x9e, 0x4f, 0xf8, 0x3f, 0xd5, 0xbe, 0x2b, 0xda, 0xb1, + 0x69, 0xec, 0x10, 0xfc, 0xeb, 0x4f, 0xff, 0xff, 0xb4, 0x5c, 0xd6, 0x7d, + 0x9d, 0xf8, 0x3e, 0xee, 0xbe, 0x33, 0x67, 0xe6, 0xe2, 0x0b, 0xad, 0x2e, + 0x12, 0x2c, 0xee, 0x4b, 0x3f, 0xfe, 0xcc, 0xd1, 0x77, 0x59, 0xe2, 0xfb, + 0xe9, 0xbc, 0xeb, 0x4f, 0xff, 0x73, 0x58, 0x0f, 0x07, 0x6c, 0xe7, 0xb8, + 0x2b, 0x4f, 0xf6, 0xde, 0x0e, 0xd8, 0x3c, 0xf2, 0x5a, 0x57, 0xc4, 0x46, + 0xd2, 0x84, 0x88, 0x53, 0x0e, 0xe4, 0x39, 0xe7, 0xcf, 0x6d, 0x9f, 0x75, + 0xa7, 0xff, 0xff, 0xfd, 0x82, 0x1f, 0x82, 0xd6, 0xe6, 0x70, 0xb8, 0xc7, + 0x1a, 0x0f, 0x44, 0x6c, 0xd6, 0xc0, 0x6f, 0x52, 0x9f, 0xff, 0xfb, 0xa5, + 0x6f, 0xbb, 0xff, 0xc6, 0xed, 0x65, 0xfb, 0xad, 0xf5, 0xb5, 0x5a, 0xb4, + 0xdb, 0xd9, 0xb2, 0x68, 0x64, 0x51, 0x78, 0x4f, 0x43, 0x2a, 0xea, 0xd4, + 0x66, 0xc3, 0x1b, 0x84, 0xff, 0xe3, 0xf7, 0xc6, 0xa9, 0xf8, 0x7a, 0x10, + 0x2d, 0x3f, 0xfe, 0x21, 0xe6, 0xb1, 0xd7, 0x8e, 0x67, 0x9e, 0x84, 0x0b, + 0x4f, 0xfc, 0xc3, 0x60, 0xe3, 0xbb, 0xee, 0xdb, 0x2d, 0x17, 0x45, 0x06, + 0xeb, 0x53, 0xd9, 0xcc, 0x74, 0x16, 0x9d, 0xe5, 0x9a, 0x5a, 0x6c, 0xf5, + 0x68, 0xb1, 0x36, 0x77, 0xc3, 0xaa, 0xe4, 0x9c, 0x25, 0x76, 0x3d, 0x3e, + 0x2e, 0x5f, 0x00, 0xb4, 0xfd, 0xeb, 0x0e, 0xa9, 0xeb, 0x4e, 0x6f, 0x59, + 0x69, 0xf9, 0xf9, 0xcb, 0xd3, 0x9e, 0x0f, 0x1c, 0xe5, 0x90, 0xf4, 0x5a, + 0x38, 0xdb, 0x3f, 0x3a, 0xea, 0xbc, 0xec, 0x05, 0xa7, 0xf7, 0xdc, 0x1c, + 0xd5, 0x71, 0x68, 0xc3, 0xe5, 0x23, 0x39, 0xf9, 0x81, 0x9c, 0x0a, 0xc5, + 0xa1, 0xe7, 0x9c, 0x24, 0x13, 0xfd, 0xcd, 0x63, 0xe8, 0x73, 0x65, 0xa7, + 0xff, 0xf8, 0x68, 0x17, 0xb3, 0x08, 0x78, 0x38, 0xef, 0xc5, 0xed, 0x15, + 0xa0, 0x51, 0x3f, 0xc3, 0x79, 0xff, 0x16, 0x39, 0xe2, 0xed, 0xaa, 0xf3, + 0xad, 0x3f, 0xab, 0x6b, 0xeb, 0x2b, 0xeb, 0x47, 0xa7, 0xeb, 0xc4, 0x39, + 0xfe, 0xcf, 0xd8, 0xc5, 0xfc, 0x15, 0xa4, 0x75, 0xbc, 0x1b, 0x69, 0xc3, + 0x9e, 0xad, 0x0c, 0x6f, 0x76, 0x22, 0x9f, 0xb6, 0xbe, 0x60, 0x86, 0xb4, + 0xf1, 0xc2, 0x60, 0x2d, 0x18, 0x79, 0xe4, 0x5b, 0x3b, 0x7d, 0x80, 0xb4, + 0x32, 0x7b, 0x79, 0x09, 0x72, 0x84, 0xb7, 0xdc, 0x04, 0x82, 0x7f, 0x70, + 0xac, 0xcd, 0x6f, 0xc5, 0xa4, 0xe2, 0xd3, 0xf6, 0x6f, 0x9a, 0xed, 0x8b, + 0x4d, 0x9b, 0x31, 0xbf, 0xb8, 0x8c, 0xfd, 0xaa, 0x79, 0xfe, 0x4b, 0x4f, + 0x03, 0xf9, 0xb2, 0xd3, 0xe2, 0xbd, 0xd8, 0xeb, 0x41, 0x1e, 0x43, 0xb2, + 0x28, 0x0d, 0x33, 0x62, 0x73, 0x01, 0x66, 0xee, 0x93, 0xff, 0xf7, 0xdf, + 0xac, 0x73, 0x4c, 0x21, 0x8e, 0x73, 0x5b, 0xad, 0x3f, 0xff, 0x8b, 0x36, + 0x7d, 0xf9, 0xa6, 0x3b, 0x17, 0xbe, 0x19, 0xd3, 0x2d, 0x3f, 0xff, 0xfe, + 0xdc, 0x7c, 0x5b, 0x5b, 0x3f, 0xf4, 0x0f, 0x17, 0xdd, 0xb6, 0xd6, 0x10, + 0xe0, 0x6b, 0x4f, 0xff, 0xb7, 0xff, 0xf3, 0x97, 0xd1, 0x67, 0x8d, 0x01, + 0x96, 0x8c, 0x47, 0x0e, 0xa1, 0x15, 0x3f, 0xfc, 0xf7, 0x5d, 0x63, 0x0b, + 0x83, 0x9c, 0xbd, 0x2d, 0x3f, 0xfe, 0xf7, 0x0f, 0xe1, 0xd7, 0x8d, 0x77, + 0x1f, 0x5f, 0xb1, 0x69, 0xff, 0xfb, 0x85, 0x66, 0x7b, 0xe0, 0xf4, 0xff, + 0x95, 0x94, 0x75, 0xa7, 0xf5, 0x94, 0x71, 0x6f, 0xb8, 0xb4, 0xff, 0x6d, + 0x81, 0x80, 0xf9, 0xe3, 0xf8, 0x89, 0x12, 0x5e, 0x9f, 0xec, 0x1b, 0x3c, + 0x02, 0xa9, 0xc5, 0xa7, 0xfb, 0xf6, 0x35, 0x96, 0x6f, 0xfb, 0x16, 0x9f, + 0xed, 0xc7, 0xc5, 0xf7, 0xf2, 0xdf, 0xeb, 0x43, 0x1f, 0xfd, 0xcf, 0xa7, + 0xff, 0x59, 0xe2, 0xfa, 0xdf, 0xc7, 0x2f, 0x76, 0xfa, 0xd3, 0xff, 0xfe, + 0xcd, 0xae, 0x59, 0x6f, 0x8d, 0x67, 0x2f, 0x96, 0x78, 0xc2, 0xf5, 0x68, + 0xc4, 0x61, 0x12, 0x8c, 0x58, 0xb9, 0x7d, 0xd8, 0xd2, 0x76, 0x27, 0x25, + 0x0f, 0xc3, 0xb8, 0xea, 0x77, 0x85, 0xcf, 0x21, 0xc7, 0x3f, 0xb8, 0x39, + 0xcd, 0x65, 0x8b, 0x4f, 0xfa, 0xbf, 0xac, 0xe3, 0x09, 0x01, 0x69, 0xfe, + 0xcb, 0x76, 0x7d, 0xfc, 0x70, 0x96, 0x9f, 0xff, 0xf3, 0x75, 0x8f, 0xe1, + 0xcd, 0x10, 0xfb, 0xe0, 0x2e, 0x8e, 0x6b, 0x16, 0x86, 0x4c, 0x67, 0xa6, + 0x42, 0x76, 0xea, 0x79, 0x3c, 0x5c, 0x26, 0x5a, 0x7f, 0xff, 0xf0, 0x90, + 0x7a, 0xa7, 0x3c, 0x0e, 0xee, 0xbe, 0x33, 0x67, 0xe6, 0xe2, 0x0b, 0xad, + 0x3e, 0xd1, 0x30, 0xd8, 0xb4, 0xdc, 0x63, 0xa2, 0x9d, 0xdc, 0x20, 0x61, + 0xe8, 0xfb, 0x28, 0x63, 0x43, 0x32, 0x07, 0xb2, 0x74, 0x68, 0xa3, 0xa3, + 0xa8, 0xce, 0xa7, 0xcc, 0x3c, 0x27, 0x6b, 0x4f, 0x72, 0xf4, 0x05, 0xa7, + 0xfe, 0xd1, 0x78, 0x2f, 0xef, 0x65, 0x7f, 0xab, 0x4f, 0x16, 0xaa, 0xc5, + 0xa7, 0x9d, 0xb7, 0x19, 0x69, 0xfb, 0x55, 0x67, 0x87, 0xf1, 0x69, 0xf5, + 0x7c, 0x76, 0x25, 0xa0, 0x8f, 0x5b, 0x86, 0x11, 0xea, 0x69, 0x38, 0x4e, + 0x19, 0x0f, 0x51, 0xc8, 0x87, 0x8f, 0x13, 0xc1, 0x6f, 0x57, 0x5a, 0x7f, + 0xfe, 0x2f, 0xf8, 0xc2, 0xc1, 0x0f, 0x45, 0x87, 0xce, 0xad, 0x3f, 0xff, + 0xc5, 0x67, 0xd8, 0xf7, 0xdd, 0xbc, 0x37, 0xdb, 0xdd, 0x31, 0xd6, 0x8c, + 0x46, 0x17, 0xd6, 0x67, 0xff, 0xff, 0x7f, 0xe5, 0x67, 0x8d, 0x67, 0x05, + 0x9c, 0xd6, 0x58, 0xdb, 0x73, 0x7e, 0xad, 0x3f, 0xff, 0xf3, 0x07, 0xa6, + 0xf0, 0x39, 0xb8, 0x7e, 0x36, 0x7d, 0xc8, 0x42, 0x6b, 0x16, 0x9f, 0x6d, + 0x72, 0xc1, 0x5a, 0x31, 0x13, 0xfb, 0xbc, 0xc1, 0x26, 0x7f, 0xa8, 0xc7, + 0x27, 0xfe, 0x7d, 0xc8, 0x3d, 0x31, 0x58, 0xc1, 0xad, 0x3f, 0xc3, 0xbb, + 0x83, 0x9a, 0xae, 0x2d, 0x3f, 0xde, 0xe9, 0xad, 0xf7, 0x54, 0x75, 0xa7, + 0xff, 0xfa, 0xbf, 0xac, 0xe1, 0x60, 0xd9, 0x76, 0xc1, 0xf7, 0x4c, 0xb4, + 0xf7, 0x3c, 0x59, 0xf5, 0xa3, 0x64, 0x44, 0x53, 0x1c, 0xff, 0xeb, 0x68, + 0x6c, 0x3e, 0xe5, 0xe9, 0x09, 0x2d, 0x3f, 0x39, 0xb3, 0xf7, 0x6e, 0x2d, + 0x3f, 0xda, 0xa3, 0x8e, 0x6a, 0xb8, 0xb4, 0xf6, 0x7c, 0x16, 0xad, 0x0c, + 0x88, 0x8b, 0x98, 0x70, 0xda, 0x7e, 0xce, 0x7f, 0xf8, 0xf5, 0xa7, 0xe1, + 0xcd, 0x0e, 0x75, 0x69, 0xe1, 0xcb, 0xf8, 0x70, 0xf5, 0xb8, 0x59, 0x1e, + 0xae, 0x3f, 0xf6, 0x36, 0x1d, 0x8a, 0x49, 0x17, 0xe7, 0x1a, 0x86, 0x1d, + 0xc8, 0xf9, 0x0d, 0x27, 0x70, 0x85, 0x9f, 0xf8, 0x47, 0x3c, 0x7f, 0x34, + 0x38, 0xe2, 0xd0, 0xcb, 0xc8, 0x19, 0x3a, 0xc9, 0x50, 0x99, 0x9f, 0x0b, + 0x1e, 0xbd, 0x5a, 0x7f, 0x63, 0x9a, 0xce, 0x37, 0xab, 0x4f, 0xff, 0xff, + 0x6b, 0x39, 0x7a, 0x73, 0xc0, 0xe6, 0xcf, 0xcd, 0xec, 0x1c, 0xf7, 0x44, + 0xe2, 0xd3, 0x6e, 0x1a, 0xd1, 0xf4, 0x4d, 0x72, 0x10, 0x53, 0xf6, 0x58, + 0x39, 0xb5, 0xd6, 0x9f, 0xfd, 0xfd, 0x9f, 0x7c, 0x3e, 0xcd, 0xad, 0x32, + 0xd2, 0xba, 0xd1, 0xb9, 0xed, 0x84, 0x97, 0x3f, 0x98, 0xe3, 0x9a, 0xae, + 0x2d, 0x3d, 0xe3, 0xb6, 0x5a, 0xb4, 0xff, 0xff, 0xb8, 0x2c, 0x0d, 0x9f, + 0x7d, 0xdb, 0x83, 0x9e, 0x96, 0x7d, 0xc5, 0xa0, 0x95, 0x45, 0x38, 0x4d, + 0x50, 0xcd, 0x12, 0x8b, 0xc2, 0x23, 0x72, 0x47, 0x66, 0x01, 0x13, 0x4f, + 0x9e, 0x5f, 0xcd, 0x96, 0x9f, 0x65, 0xec, 0xcb, 0x56, 0x8d, 0x8f, 0x3f, + 0x44, 0xf3, 0xff, 0xe2, 0xb3, 0x3b, 0xee, 0x8b, 0x03, 0xf0, 0x1e, 0xec, + 0xb4, 0xf3, 0xaf, 0x33, 0x4b, 0x4c, 0x47, 0x5a, 0x7f, 0xff, 0xdb, 0x3e, + 0xb6, 0xf0, 0x3b, 0xba, 0xf8, 0xcd, 0x9f, 0x9b, 0x88, 0x2e, 0xb4, 0x1d, + 0x11, 0x8e, 0xa2, 0xb1, 0x62, 0x35, 0xb2, 0x16, 0x13, 0xff, 0xff, 0xe6, + 0xda, 0xfb, 0xb6, 0xde, 0x35, 0x9e, 0xf8, 0x1c, 0xdc, 0x3f, 0x17, 0x00, + 0x0b, 0x8b, 0x4f, 0x98, 0x76, 0xab, 0x16, 0x9f, 0xff, 0xff, 0xfd, 0x87, + 0xce, 0xb6, 0x98, 0xfc, 0xcb, 0xdd, 0xbf, 0xf6, 0x1c, 0xb3, 0x37, 0xbe, + 0xee, 0x0b, 0x1d, 0x69, 0xfe, 0x06, 0x7a, 0x39, 0xaa, 0xe2, 0xd3, 0xfa, + 0xcd, 0xdb, 0x87, 0x2e, 0x2d, 0x3f, 0xe6, 0x0d, 0xd7, 0x7c, 0x17, 0x02, + 0x3a, 0xd1, 0x87, 0xf1, 0xb1, 0xa4, 0xff, 0xb3, 0x01, 0x7f, 0x1f, 0xbf, + 0xb7, 0x5a, 0x7f, 0x7f, 0x3b, 0xa2, 0xfb, 0x8b, 0x4d, 0x9c, 0xf4, 0xfd, + 0x31, 0x06, 0x7a, 0xc2, 0x1e, 0x2d, 0x0c, 0xb8, 0xcf, 0x84, 0x65, 0x19, + 0x87, 0xca, 0x0f, 0x08, 0xed, 0x13, 0xd4, 0x29, 0x06, 0x15, 0xf7, 0x84, + 0xb6, 0xe5, 0xb3, 0xbd, 0xf0, 0xe2, 0xd3, 0xff, 0xdb, 0x3c, 0x5b, 0xc1, + 0xc5, 0x83, 0xe1, 0x58, 0xb4, 0x61, 0xf9, 0x11, 0x04, 0xfd, 0x63, 0x8f, + 0xf3, 0x3f, 0x62, 0xd3, 0xe3, 0x97, 0xb9, 0xea, 0xd2, 0x71, 0x69, 0x98, + 0xeb, 0x4b, 0x8b, 0x47, 0xcd, 0x2d, 0x0a, 0xc7, 0xa7, 0xad, 0xa3, 0x69, + 0xee, 0x5e, 0xb6, 0x5a, 0x67, 0x29, 0x69, 0xfd, 0xa2, 0xfe, 0xcf, 0x16, + 0x5a, 0x3c, 0xda, 0x68, 0x2c, 0x6d, 0x8f, 0xc4, 0x46, 0x72, 0x2d, 0xc5, + 0xa7, 0xf5, 0xcb, 0x6b, 0x68, 0x40, 0xb4, 0xff, 0xf9, 0xbe, 0xdd, 0xc3, + 0xb7, 0x5e, 0x57, 0xdc, 0xeb, 0x43, 0x88, 0x87, 0xf2, 0x33, 0x9f, 0xbf, + 0x8c, 0x1b, 0x0a, 0xd3, 0x86, 0x8e, 0xb4, 0xc0, 0x65, 0xa1, 0xe7, 0xb4, + 0x45, 0x62, 0x35, 0x0e, 0x93, 0xb7, 0x5b, 0xf3, 0x23, 0x73, 0x69, 0x4a, + 0xb6, 0x3d, 0xbc, 0xb3, 0xd9, 0x51, 0xd9, 0x3e, 0xf8, 0x1c, 0x33, 0x3b, + 0x2f, 0x83, 0x68, 0xe8, 0xca, 0x30, 0x77, 0x21, 0xaf, 0xf9, 0x53, 0x67, + 0x8e, 0x1f, 0x54, 0x85, 0xda, 0x8e, 0x68, 0x67, 0xa6, 0x2f, 0x2b, 0x1f, + 0x93, 0xa6, 0xae, 0xe3, 0x16, 0xf2, 0x8d, 0x84, 0x28, 0x71, 0xba, 0xc2, + 0x06, 0x7f, 0xfe, 0xdf, 0xa6, 0xdb, 0x9f, 0x6c, 0xdb, 0xcc, 0xf6, 0xa8, + 0x35, 0xa0, 0xca, 0x8c, 0xca, 0x3c, 0xf9, 0xfc, 0x6c, 0xff, 0x6a, 0xba, + 0xa8, 0xa7, 0x67, 0xdf, 0xed, 0x57, 0x55, 0x15, 0x1c, 0xff, 0xa9, 0xe6, + 0xcf, 0xf6, 0xab, 0xaa, 0x89, 0xa2, 0x30, 0xff, 0x0e, 0x67, 0x3f, 0x82, + 0xa7, 0x66, 0xe1, 0x58, 0xb4, 0x18, 0xf5, 0xd8, 0x86, 0x7f, 0x1b, 0x3f, + 0xda, 0xae, 0xaa, 0x2a, 0xf9, 0xf7, 0xfb, 0x55, 0xd5, 0x45, 0x6d, 0x3f, + 0xcf, 0x36, 0x7f, 0xb5, 0x5d, 0x54, 0x48, 0x32, 0x36, 0x1f, 0xbd, 0x19, + 0xcf, 0xfc, 0x6a, 0x79, 0xb3, 0xfd, 0xaa, 0xea, 0xa2, 0x43, 0x9f, 0xf3, + 0xf4, 0xdc, 0xbb, 0xa6, 0xbb, 0xa4, 0x2b, 0x4b, 0x65, 0xa6, 0x21, 0x5a, + 0x3d, 0x34, 0xae, 0x08, 0xcf, 0xb3, 0x04, 0x17, 0x5a, 0x7d, 0xfe, 0xd5, + 0x75, 0x51, 0x62, 0x4f, 0xb3, 0xec, 0x21, 0xad, 0x3e, 0x0b, 0xcb, 0x3e, + 0xcb, 0x4e, 0xbb, 0x0a, 0xd2, 0xd3, 0x1e, 0x21, 0x15, 0x4f, 0xfc, 0xd9, + 0x7c, 0x1f, 0x48, 0x0c, 0x2b, 0x4e, 0x16, 0xb1, 0x69, 0x3c, 0x4f, 0x6f, + 0x74, 0x09, 0xde, 0x44, 0x05, 0xa7, 0xc4, 0xef, 0x7a, 0xba, 0xd3, 0xff, + 0x3b, 0xdf, 0x36, 0xf5, 0xde, 0xb2, 0x9c, 0x5a, 0x66, 0x7a, 0xd1, 0x87, + 0xc1, 0xf4, 0xb9, 0xfd, 0x9f, 0x78, 0x33, 0x1c, 0x5a, 0x7d, 0x4f, 0x0b, + 0x4c, 0xb4, 0xfd, 0x7b, 0xfc, 0xad, 0xf3, 0xad, 0x0c, 0x88, 0xb2, 0x32, + 0xa2, 0x79, 0xff, 0xe7, 0xb1, 0xd9, 0xfe, 0x8b, 0x6a, 0xb3, 0xeb, 0x4f, + 0xee, 0x8e, 0x3b, 0x1c, 0xba, 0xd1, 0xb1, 0xff, 0xe9, 0x36, 0x7f, 0x01, + 0xb3, 0x84, 0x36, 0xa5, 0x3f, 0xf6, 0xa8, 0xb8, 0xfc, 0x0c, 0x70, 0x0b, + 0x4f, 0xb3, 0x5b, 0x57, 0x9d, 0x69, 0xe0, 0x5d, 0x85, 0x69, 0xfd, 0xfd, + 0x51, 0x5c, 0x9d, 0xad, 0x0c, 0x7a, 0x78, 0x41, 0x04, 0x8e, 0x01, 0x42, + 0xbb, 0xf4, 0xfd, 0x56, 0x71, 0xb6, 0x7a, 0xd3, 0xfe, 0x16, 0x2f, 0x78, + 0x35, 0xb5, 0xd6, 0x87, 0x49, 0x77, 0x21, 0x9b, 0xbd, 0x23, 0xc2, 0x50, + 0xcc, 0xfa, 0xd5, 0xb4, 0x20, 0x48, 0xa1, 0xc1, 0xef, 0xc2, 0x27, 0x50, + 0xac, 0xa8, 0x56, 0x00, 0x8e, 0xf1, 0x88, 0xf0, 0xb8, 0x22, 0xe9, 0xff, + 0xb4, 0x4d, 0xcd, 0x50, 0x78, 0x20, 0x5a, 0x7f, 0xfb, 0x36, 0x7f, 0x37, + 0xb0, 0xda, 0xa3, 0x95, 0xd6, 0x9d, 0xc1, 0x65, 0xa6, 0xd1, 0xb1, 0x15, + 0xc2, 0x84, 0x12, 0x94, 0x19, 0x3e, 0x56, 0x8e, 0xaa, 0x7f, 0x1b, 0x3f, + 0xda, 0xae, 0xaa, 0x2c, 0xd9, 0xb3, 0xd5, 0xa7, 0xfd, 0x4f, 0x36, 0x7f, + 0xb5, 0x5d, 0x54, 0x50, 0x13, 0xf8, 0x8e, 0xde, 0x7d, 0x65, 0x8b, 0x48, + 0xd8, 0x89, 0x53, 0x8b, 0x69, 0x26, 0x6d, 0xf4, 0xb4, 0xfd, 0x76, 0x38, + 0xb3, 0x8b, 0x4b, 0x4b, 0x4e, 0x08, 0x5c, 0x5a, 0x6c, 0xe6, 0x1a, 0xd0, + 0x08, 0x40, 0x11, 0x08, 0x2b, 0x33, 0x78, 0xba, 0xd3, 0xfd, 0xac, 0xff, + 0x05, 0x8f, 0x75, 0xa7, 0xbb, 0x55, 0xd5, 0x45, 0xbf, 0x3f, 0x05, 0xca, + 0xed, 0x7d, 0x68, 0xf9, 0xed, 0x00, 0xb6, 0x7b, 0x04, 0x9c, 0x5a, 0x7d, + 0xc7, 0xdc, 0x2b, 0xad, 0x3f, 0x3b, 0x0b, 0x82, 0xc0, 0x5a, 0x7d, 0xeb, + 0x79, 0xf7, 0x3a, 0xd0, 0xc7, 0xb8, 0x45, 0xf3, 0xd4, 0x43, 0xc5, 0xa7, + 0xf6, 0xfb, 0x5d, 0xbb, 0x87, 0x5a, 0x7b, 0x41, 0xfa, 0x75, 0xa6, 0xc7, + 0xad, 0x18, 0x6e, 0x44, 0x92, 0x46, 0x74, 0x95, 0x7c, 0x34, 0x26, 0xde, + 0x45, 0xe8, 0xc6, 0x42, 0x44, 0x88, 0xbe, 0x41, 0xa8, 0x42, 0x80, 0x80, + 0x47, 0xf8, 0xdb, 0x3f, 0xea, 0x79, 0xb3, 0xfd, 0xaa, 0xea, 0xa2, 0x94, + 0x9f, 0xe7, 0x9b, 0x3f, 0xda, 0xae, 0xaa, 0x24, 0xe9, 0x18, 0xe8, 0x8d, + 0xa4, 0x98, 0x67, 0xcb, 0x32, 0xb2, 0x36, 0xef, 0x6d, 0xd0, 0x16, 0x15, + 0x76, 0x31, 0x12, 0x21, 0xf3, 0xc2, 0xd5, 0xc2, 0x63, 0xcf, 0xfe, 0x68, + 0xa4, 0x10, 0xd3, 0xbc, 0xb2, 0xd7, 0x71, 0x9f, 0x4f, 0xbf, 0xda, 0xae, + 0xaa, 0x21, 0xf9, 0xff, 0x53, 0xcd, 0x9f, 0xed, 0x57, 0x55, 0x12, 0x9c, + 0x8d, 0x87, 0xf8, 0x73, 0x39, 0xfc, 0x6c, 0xff, 0x6a, 0xba, 0xa8, 0x89, + 0xe7, 0xf1, 0xb3, 0xfd, 0xaa, 0xea, 0xa2, 0x31, 0x9f, 0xfc, 0x63, 0xd3, + 0xcd, 0x9f, 0xed, 0x57, 0x55, 0x13, 0xcc, 0x32, 0x3e, 0x2c, 0x27, 0x79, + 0xf6, 0x8f, 0xa7, 0xdf, 0xed, 0x57, 0x55, 0x11, 0x04, 0xff, 0xa9, 0xe6, + 0xcf, 0xf6, 0xab, 0xaa, 0x89, 0x52, 0x46, 0xc3, 0xfc, 0x39, 0x9c, 0xfe, + 0x36, 0x7f, 0xb5, 0x5d, 0x54, 0x45, 0x13, 0xf8, 0xd9, 0xfe, 0xd5, 0x75, + 0x51, 0x19, 0x4f, 0xfe, 0x31, 0xe9, 0xe6, 0xcf, 0xf6, 0xab, 0xaa, 0x89, + 0x96, 0x7f, 0x1b, 0x3f, 0xda, 0xae, 0xaa, 0x2a, 0x49, 0xfc, 0x6c, 0xff, + 0x6a, 0xba, 0xa8, 0xae, 0xa7, 0xf1, 0xb3, 0xfd, 0xaa, 0xea, 0xa2, 0xc5, + 0x9f, 0xf8, 0xf4, 0xf3, 0x67, 0xfb, 0x55, 0xd5, 0x44, 0xf5, 0x3f, 0xd6, + 0x9a, 0x98, 0xbc, 0x89, 0xd5, 0x68, 0x32, 0x23, 0x19, 0x3e, 0x7e, 0xf3, + 0x46, 0x70, 0xb7, 0xba, 0xd3, 0xdf, 0x3f, 0x36, 0x5a, 0x77, 0x73, 0x49, + 0x4e, 0x3b, 0x5d, 0x69, 0xff, 0xfb, 0x43, 0x41, 0x97, 0xd9, 0xef, 0xcb, + 0x80, 0x0c, 0xb4, 0xff, 0xfd, 0xf2, 0x75, 0xa3, 0x99, 0xbf, 0xf2, 0x75, + 0xee, 0xf6, 0x2d, 0x3c, 0x5a, 0x08, 0xeb, 0x4f, 0xfe, 0x20, 0xfc, 0x60, + 0xd6, 0xd7, 0x0c, 0x3a, 0x5a, 0x3a, 0x7d, 0xe2, 0x45, 0x3f, 0xfa, 0xae, + 0x38, 0x03, 0x68, 0xbb, 0x5f, 0x5a, 0x7e, 0xf7, 0x03, 0xe6, 0x75, 0x69, + 0xed, 0x31, 0xfa, 0xb4, 0x51, 0xe7, 0xf0, 0xba, 0x7f, 0xf0, 0xf8, 0x66, + 0x16, 0x3f, 0x8f, 0x1a, 0xf2, 0x5a, 0x6a, 0xd9, 0x69, 0xfd, 0x57, 0xad, + 0x6e, 0x36, 0x2d, 0x02, 0x79, 0x3b, 0x8b, 0x45, 0x8a, 0xb6, 0x08, 0x6f, + 0xeb, 0x27, 0x86, 0x8e, 0x88, 0x46, 0x12, 0xdc, 0x21, 0x75, 0x84, 0xcc, + 0xe1, 0xae, 0x2d, 0x38, 0x4a, 0xeb, 0x4a, 0xd7, 0x44, 0xda, 0x30, 0xd4, + 0xf7, 0xdd, 0xd1, 0xd6, 0x8c, 0x3c, 0xf2, 0x2c, 0x9f, 0xf1, 0x6a, 0xb8, + 0xce, 0x5c, 0x8e, 0xb4, 0xf8, 0x43, 0x2c, 0xba, 0xd3, 0xff, 0x65, 0xb4, + 0x0c, 0xee, 0xa8, 0x17, 0x5a, 0x7f, 0xd7, 0xad, 0x98, 0x6b, 0xdc, 0x0d, + 0x68, 0x24, 0x53, 0x89, 0x37, 0x10, 0xe7, 0x1d, 0xb4, 0xb4, 0xf0, 0xfb, + 0xe0, 0x35, 0xa7, 0xec, 0xbd, 0xb4, 0x36, 0x2d, 0x18, 0x7d, 0xb4, 0x36, + 0x24, 0xb3, 0xed, 0xc3, 0xbf, 0xac, 0xb4, 0x62, 0x3c, 0x4a, 0x13, 0x7a, + 0x2c, 0x9f, 0xff, 0xfd, 0xa6, 0x2f, 0xfd, 0x82, 0xe1, 0x0e, 0xef, 0xcb, + 0xfb, 0xbd, 0x7d, 0xeb, 0x4f, 0xac, 0xf7, 0xe5, 0x6a, 0xd3, 0xf7, 0xb5, + 0xf0, 0x79, 0x62, 0xd3, 0xfe, 0x12, 0x7f, 0x35, 0x9f, 0x2b, 0xad, 0x3f, + 0xea, 0xc0, 0x30, 0xd7, 0xb8, 0x1a, 0xd0, 0xf3, 0xf8, 0x01, 0xe4, 0xfe, + 0xad, 0xaf, 0xce, 0x67, 0x9d, 0x69, 0xff, 0xcd, 0x9b, 0x73, 0x4d, 0xc1, + 0x60, 0x5d, 0x69, 0xd7, 0xd1, 0xd6, 0x8e, 0x1f, 0x26, 0xe9, 0x13, 0xf6, + 0xf7, 0xe5, 0xf3, 0x8b, 0x4e, 0xb6, 0xdb, 0x52, 0x9f, 0xfd, 0xc2, 0xe5, + 0xf4, 0x5e, 0x41, 0x7d, 0x9e, 0x91, 0x8b, 0xf8, 0x24, 0x54, 0xee, 0x9b, + 0x0f, 0x55, 0xef, 0x8f, 0x24, 0x55, 0xf8, 0x54, 0x1c, 0x88, 0x61, 0x37, + 0xc8, 0x66, 0x4f, 0xf6, 0xcf, 0xbd, 0xf3, 0xda, 0xe2, 0xd2, 0xc5, 0xa1, + 0x8f, 0x24, 0x33, 0xa9, 0xff, 0xfe, 0x21, 0xae, 0x16, 0xa8, 0xfe, 0x32, + 0xed, 0x9f, 0xc1, 0x5a, 0x7b, 0x8d, 0xb5, 0xab, 0x46, 0x91, 0x0b, 0x6b, + 0x14, 0xee, 0xdb, 0xa5, 0xa7, 0xf3, 0x57, 0xf5, 0x6d, 0x7a, 0x94, 0xd6, + 0xda, 0x94, 0x7a, 0x79, 0x76, 0x99, 0xce, 0xe1, 0x79, 0xd2, 0x31, 0xa4, + 0x9e, 0xdd, 0xfb, 0xf1, 0x68, 0xb9, 0xe9, 0xf9, 0x17, 0xcd, 0x6d, 0xd6, + 0x96, 0x2d, 0x2d, 0xb0, 0xd3, 0x70, 0x5e, 0x7f, 0xeb, 0xdf, 0xd1, 0x6f, + 0x74, 0xdc, 0xba, 0xd0, 0xc7, 0xd9, 0xb9, 0x3c, 0xff, 0xb8, 0x5f, 0xa0, + 0x3a, 0xf3, 0x34, 0xb4, 0xff, 0x15, 0xa3, 0x9c, 0x2d, 0xc0, 0xb4, 0xfe, + 0xaf, 0xf2, 0xe2, 0x41, 0xad, 0x0f, 0x46, 0x61, 0x11, 0x79, 0xd0, 0x28, + 0xe2, 0x76, 0x17, 0xd6, 0x9e, 0xaf, 0xb3, 0xd6, 0x8b, 0x0d, 0xd1, 0xc6, + 0xa7, 0xfc, 0xdb, 0x69, 0x8f, 0xdd, 0x13, 0x8b, 0x46, 0x1f, 0x0f, 0xc8, + 0xa7, 0xee, 0x37, 0xfd, 0xc1, 0x5a, 0x7f, 0x7b, 0xa6, 0x1b, 0x6b, 0x65, + 0xa7, 0xfe, 0x2f, 0x74, 0xdd, 0x60, 0x50, 0x81, 0x69, 0xff, 0xff, 0x11, + 0xe8, 0x76, 0x79, 0xad, 0xc2, 0x77, 0x83, 0x9e, 0xd3, 0xd7, 0x17, 0xac, + 0x32, 0x63, 0x74, 0x59, 0x73, 0x4d, 0xd0, 0xa7, 0xff, 0xb3, 0x97, 0xc7, + 0x34, 0xce, 0xf3, 0x5a, 0x65, 0xa7, 0xff, 0xff, 0xc5, 0x9b, 0x5c, 0xb2, + 0xdf, 0x19, 0xb3, 0xee, 0x5c, 0xb9, 0x6d, 0xef, 0xbb, 0xd8, 0xb4, 0xff, + 0xf8, 0x87, 0xdf, 0x01, 0x70, 0xaf, 0x44, 0x3b, 0xbd, 0x68, 0x14, 0xcc, + 0xb8, 0xa0, 0xee, 0x11, 0x73, 0xfa, 0xca, 0xe0, 0x02, 0x17, 0xad, 0x35, + 0x7a, 0xb4, 0xfd, 0xfa, 0xbe, 0xa9, 0xeb, 0x4b, 0x65, 0xa6, 0x2b, 0x56, + 0x98, 0x20, 0x2d, 0x0e, 0x1a, 0xd0, 0x0b, 0x4e, 0x7d, 0x7a, 0xb4, 0xd6, + 0xda, 0xb4, 0x3d, 0x1a, 0xd8, 0x2d, 0xd2, 0xc2, 0x3e, 0xb9, 0x15, 0xa3, + 0x93, 0xb8, 0x10, 0x12, 0x31, 0xeb, 0x4f, 0xff, 0xfe, 0xe3, 0x7b, 0xad, + 0xe8, 0x33, 0xd7, 0xb9, 0x82, 0x1d, 0xef, 0x82, 0x05, 0xa3, 0x64, 0x53, + 0x91, 0x74, 0xff, 0x67, 0x34, 0xdf, 0xe6, 0x75, 0x69, 0xcd, 0xf7, 0x16, + 0x8c, 0x55, 0x68, 0x46, 0xf5, 0x28, 0x1c, 0x48, 0xfc, 0x8d, 0xe7, 0x5e, + 0x9c, 0x5a, 0x7b, 0x39, 0x4e, 0xab, 0x46, 0xc6, 0xfc, 0x47, 0x27, 0xfe, + 0xcf, 0xff, 0x1c, 0xd6, 0x5e, 0x85, 0x69, 0xfc, 0x2d, 0xfd, 0xfb, 0xa6, + 0x5a, 0x7c, 0x41, 0xea, 0x9e, 0xb4, 0xfc, 0x4c, 0x7b, 0x32, 0xd5, 0xa6, + 0xb6, 0xd5, 0xa3, 0xa7, 0xd9, 0xf2, 0x7b, 0x4b, 0x67, 0xfb, 0x1c, 0xa7, + 0x2b, 0x67, 0xdd, 0x23, 0x1b, 0x09, 0xde, 0xb1, 0xd6, 0x9f, 0xd8, 0x0c, + 0x71, 0xb6, 0xba, 0xd0, 0xc7, 0x9f, 0x83, 0x93, 0xff, 0xb0, 0x41, 0x9a, + 0x2d, 0xaf, 0xad, 0xee, 0xb4, 0xfc, 0x34, 0x0f, 0x70, 0x56, 0x9f, 0x8e, + 0x5e, 0x59, 0xf0, 0xd6, 0x9d, 0x6d, 0xb6, 0xa5, 0x3f, 0xf3, 0x7c, 0x77, + 0xa0, 0xb1, 0xc6, 0x3a, 0xc6, 0x2f, 0xe3, 0xce, 0xaa, 0x79, 0xc8, 0x67, + 0x0c, 0x26, 0x6e, 0x41, 0xc4, 0xad, 0xca, 0xed, 0x4f, 0x93, 0xa0, 0xb4, + 0x89, 0x69, 0xfe, 0xe3, 0x7e, 0xfa, 0x61, 0xe2, 0xd2, 0x74, 0x16, 0x9f, + 0xee, 0x37, 0xef, 0xa6, 0x1e, 0x2d, 0x35, 0xd9, 0x69, 0xc7, 0xf5, 0x96, + 0x9f, 0xc3, 0xca, 0x72, 0xed, 0xc5, 0xa0, 0x8f, 0x3a, 0x87, 0x27, 0x8a, + 0xb3, 0xab, 0x4f, 0xfb, 0x1d, 0x8e, 0x5d, 0xc1, 0x6f, 0x56, 0x9e, 0xef, + 0xad, 0x62, 0xd3, 0x01, 0x96, 0x9d, 0x72, 0xfa, 0xd1, 0x87, 0xa6, 0x02, + 0x3e, 0x0a, 0xcf, 0xe7, 0xe0, 0x97, 0xb8, 0x2b, 0x4e, 0xe5, 0xcc, 0xc9, + 0xef, 0x60, 0xab, 0x86, 0xfa, 0x63, 0x01, 0x00, 0x8f, 0xde, 0x12, 0x5c, + 0x2e, 0x9a, 0xec, 0xb4, 0xe3, 0xfa, 0xcb, 0x4f, 0xe1, 0xe5, 0x39, 0x76, + 0xe2, 0xd0, 0x47, 0x9d, 0x43, 0x93, 0xc5, 0x59, 0xd5, 0xa7, 0xfd, 0x8e, + 0xc7, 0x2e, 0xe0, 0xb7, 0xab, 0x4f, 0x77, 0xd6, 0xb1, 0x69, 0xf9, 0xc0, + 0x17, 0x9e, 0x9c, 0x5a, 0x60, 0x32, 0xd3, 0xae, 0x5f, 0x5a, 0x31, 0x10, + 0xbd, 0x23, 0x01, 0x97, 0x05, 0x67, 0xf3, 0xf0, 0x4b, 0xdc, 0x15, 0xa7, + 0xe1, 0xaf, 0x70, 0x33, 0x32, 0xe4, 0x97, 0xa2, 0xb8, 0x1f, 0xd9, 0x42, + 0xae, 0x42, 0x1f, 0x4c, 0x60, 0x20, 0x11, 0xfb, 0xc3, 0x13, 0x87, 0x93, + 0xff, 0xea, 0xf7, 0x30, 0x43, 0x08, 0x58, 0x6c, 0x2f, 0xad, 0x3a, 0xbf, + 0x62, 0xe4, 0x12, 0x9e, 0x7e, 0x6c, 0xf5, 0xc8, 0x25, 0x3a, 0xe5, 0xea, + 0xe4, 0x12, 0x9a, 0xdb, 0x57, 0x20, 0x94, 0x75, 0x14, 0xce, 0x14, 0xf0, + 0xbe, 0xd2, 0x99, 0xab, 0x89, 0x90, 0x48, 0x63, 0x7f, 0x3f, 0x66, 0xb3, + 0x04, 0x35, 0xa7, 0x50, 0x81, 0x95, 0x2c, 0x1e, 0x18, 0x23, 0x19, 0xcf, + 0x0c, 0xe7, 0xbc, 0xb2, 0xb4, 0xb4, 0xb3, 0xce, 0x7f, 0x60, 0x58, 0x9f, + 0xce, 0x3f, 0x7b, 0x38, 0x56, 0x25, 0x3f, 0x89, 0xfb, 0xd5, 0xf4, 0x4b, + 0x49, 0xe9, 0x4e, 0xaf, 0xd8, 0x94, 0x25, 0x0c, 0x6d, 0xba, 0x20, 0x71, + 0xc9, 0xe1, 0x60, 0x75, 0x23, 0x1a, 0xd8, 0x64, 0x62, 0xe4, 0x25, 0x67, + 0xb7, 0xaf, 0xbd, 0x69, 0x1d, 0x69, 0xb1, 0xfe, 0x9b, 0x27, 0x08, 0xa7, + 0xd6, 0xe3, 0x84, 0x75, 0xa7, 0xee, 0xb3, 0x8f, 0x60, 0x25, 0x22, 0x5a, + 0x7f, 0xac, 0xdd, 0xfe, 0xeb, 0x7a, 0x15, 0xa7, 0xe2, 0x63, 0xd9, 0x96, + 0xad, 0x3e, 0xcb, 0x0f, 0x87, 0x5a, 0x75, 0xcb, 0xeb, 0x40, 0x9e, 0x17, + 0x09, 0xe7, 0xe1, 0xa0, 0x03, 0x7b, 0xad, 0x3f, 0x10, 0x61, 0x31, 0xcd, + 0xea, 0x61, 0x18, 0x1f, 0xf3, 0xcb, 0xb7, 0xf0, 0x86, 0x31, 0x3c, 0xc2, + 0x28, 0x18, 0xcd, 0x67, 0x87, 0x2c, 0xdd, 0x68, 0x7a, 0xbc, 0xfc, 0x2b, + 0xd4, 0x3f, 0x05, 0x62, 0xf1, 0xfd, 0x70, 0xd2, 0x7f, 0xfd, 0x67, 0xff, + 0x9e, 0xd1, 0xf5, 0x5c, 0x16, 0x71, 0x69, 0xff, 0xff, 0xdb, 0xff, 0x77, + 0x06, 0x81, 0x7b, 0xe0, 0x79, 0xbf, 0xeb, 0xd6, 0xd3, 0x2d, 0x3a, 0xdb, + 0x6d, 0x4a, 0x67, 0xb2, 0x46, 0x2f, 0xe1, 0x93, 0x14, 0xb1, 0x5b, 0xf0, + 0x9a, 0x9f, 0xac, 0x71, 0xfe, 0x67, 0xec, 0x5a, 0x7b, 0x55, 0xb5, 0xab, + 0x4f, 0xac, 0xf9, 0x31, 0xd6, 0x9f, 0xfe, 0x60, 0xb8, 0x2c, 0x0a, 0xe0, + 0x02, 0x17, 0xad, 0x1e, 0x6d, 0x19, 0xe4, 0x6d, 0x44, 0x7c, 0x27, 0x9c, + 0x17, 0xae, 0x2d, 0x39, 0xe4, 0x2b, 0x4f, 0xdc, 0x67, 0x07, 0x7b, 0xad, + 0x16, 0x1f, 0x3b, 0xc7, 0xf8, 0x35, 0x35, 0x5d, 0x69, 0xb7, 0x74, 0x16, + 0x81, 0x36, 0x3b, 0x8a, 0xcd, 0x46, 0x74, 0x9d, 0x33, 0x9b, 0xa7, 0x35, + 0xf3, 0x51, 0x1b, 0x4a, 0xf3, 0xb2, 0x30, 0x77, 0x90, 0x7b, 0x1e, 0xae, + 0x4b, 0x2a, 0x0e, 0x17, 0x7d, 0x85, 0x3e, 0xc4, 0x85, 0x0d, 0xf7, 0x23, + 0x83, 0xfc, 0x36, 0x8f, 0x19, 0xe6, 0xa7, 0x08, 0xea, 0x12, 0x22, 0x43, + 0x79, 0x5c, 0x1c, 0xa5, 0xd5, 0x6f, 0x1b, 0xb7, 0x94, 0x60, 0xa1, 0x42, + 0xa1, 0xd5, 0x86, 0x7f, 0xf1, 0x8f, 0x4f, 0x36, 0x7f, 0xb5, 0x5d, 0x54, + 0x51, 0x53, 0xf8, 0xd9, 0xfe, 0xd5, 0x75, 0x51, 0x74, 0x4f, 0xfc, 0x6a, + 0x79, 0xb3, 0xfd, 0xaa, 0xea, 0xa2, 0x4b, 0x9f, 0xc6, 0xcf, 0xf6, 0xab, + 0xaa, 0x8b, 0xca, 0x19, 0xd5, 0xe4, 0x58, 0x4e, 0xf3, 0xec, 0x3e, 0xea, + 0xab, 0x87, 0xc7, 0x3e, 0xd4, 0x32, 0x86, 0xd3, 0x4a, 0xde, 0x3d, 0x5e, + 0x2a, 0xee, 0x7c, 0xed, 0x3e, 0x7f, 0xf1, 0x8f, 0x4f, 0x36, 0x7f, 0xb5, + 0x5d, 0x54, 0x4b, 0x33, 0x6f, 0xa5, 0xa7, 0xbb, 0x55, 0xd5, 0x44, 0x67, + 0x3f, 0x13, 0x1e, 0xcc, 0xb5, 0x69, 0x85, 0xeb, 0x48, 0xeb, 0x4f, 0x8b, + 0xfa, 0xa3, 0x7c, 0xf4, 0x8e, 0x5a, 0x10, 0xac, 0xfd, 0x4f, 0x2f, 0xd7, + 0xd6, 0x9f, 0xfa, 0xe5, 0xbd, 0xf0, 0x77, 0x7e, 0x5d, 0x69, 0xd7, 0x6d, + 0x96, 0x8f, 0xa6, 0xac, 0x77, 0xf1, 0x4c, 0xb9, 0x5f, 0x11, 0x27, 0xf9, + 0xf7, 0xc6, 0xf0, 0xd7, 0x3a, 0xd3, 0xff, 0x70, 0xbf, 0x40, 0xf1, 0xb3, + 0xd8, 0xeb, 0x4f, 0x85, 0x9f, 0x7a, 0x5a, 0x08, 0xfa, 0xdd, 0xa3, 0x4e, + 0xa0, 0x9e, 0xb4, 0xff, 0xf6, 0xfc, 0xbe, 0xcf, 0x61, 0x05, 0xfc, 0x6e, + 0x2b, 0x4e, 0x6e, 0x32, 0xd2, 0x2f, 0x4f, 0xb9, 0xda, 0xa4, 0xda, 0x71, + 0x69, 0xfd, 0xb5, 0xc7, 0x3f, 0xeb, 0x2d, 0x04, 0x79, 0x54, 0x2f, 0x3d, + 0xcd, 0x6f, 0xe4, 0xb4, 0xff, 0x70, 0xad, 0xbb, 0x6a, 0xbc, 0xeb, 0x4b, + 0x08, 0xf8, 0x8e, 0x4d, 0x3f, 0xed, 0x31, 0x68, 0x98, 0x5c, 0xdd, 0x69, + 0xfd, 0x97, 0x6e, 0x5f, 0x44, 0xb4, 0x8c, 0xe9, 0x2e, 0x49, 0x64, 0x6d, + 0xbb, 0x25, 0x39, 0x0a, 0x4d, 0x11, 0xd4, 0x22, 0x80, 0xe8, 0x30, 0x82, + 0xb9, 0x37, 0x91, 0xec, 0xff, 0xe3, 0x1e, 0x9e, 0x6c, 0xff, 0x6a, 0xba, + 0xa8, 0x9a, 0x67, 0xff, 0x18, 0xf4, 0xf3, 0x67, 0xfb, 0x55, 0xd5, 0x44, + 0xe3, 0x3f, 0xf8, 0xc7, 0xa7, 0x9b, 0x3f, 0xda, 0xae, 0xaa, 0x28, 0x19, + 0xf7, 0xfb, 0x55, 0xd5, 0x45, 0xc1, 0x33, 0x12, 0xd3, 0xf8, 0x5e, 0xc5, + 0xee, 0x1d, 0x69, 0x1b, 0x0f, 0xe3, 0x46, 0x7b, 0x8a, 0x4f, 0xe2, 0x30, + 0x4c, 0x0c, 0xb5, 0x69, 0xfe, 0x79, 0xb3, 0xfd, 0xaa, 0xea, 0xa2, 0x49, + 0x91, 0xb4, 0x7e, 0xf4, 0x67, 0x3e, 0xa3, 0x7b, 0x68, 0x16, 0x8b, 0x19, + 0x01, 0x4f, 0x9c, 0xe7, 0xec, 0x2c, 0x1c, 0x55, 0x02, 0xad, 0xe1, 0x9b, + 0xc8, 0x56, 0x3b, 0x27, 0x9f, 0xf0, 0x74, 0x6c, 0xff, 0x6a, 0xba, 0xa8, + 0xb5, 0x27, 0xfd, 0x4f, 0x36, 0x7f, 0xb5, 0x5d, 0x54, 0x4a, 0xb0, 0xb4, + 0x8c, 0x1a, 0x27, 0x4e, 0x94, 0xea, 0x93, 0x3f, 0x8d, 0x9f, 0xed, 0x57, + 0x55, 0x11, 0x4c, 0xfe, 0x36, 0x7f, 0xb5, 0x5d, 0x54, 0x46, 0x93, 0x6f, + 0xa5, 0xa7, 0xbb, 0x55, 0xd5, 0x45, 0x3f, 0x37, 0x19, 0x68, 0xf9, 0xe1, + 0x70, 0xb6, 0x7f, 0xdc, 0x71, 0xb9, 0xaa, 0x73, 0xc6, 0x2d, 0x3f, 0xf6, + 0xfc, 0xfe, 0xee, 0x5f, 0xb4, 0xe5, 0xd6, 0x9f, 0xfb, 0x1c, 0xdc, 0x6c, + 0xbf, 0x69, 0xcb, 0xad, 0x23, 0x3a, 0x49, 0xa6, 0x62, 0xc9, 0x11, 0x0a, + 0x0f, 0x12, 0x67, 0xff, 0x18, 0xf4, 0xf3, 0x67, 0xfb, 0x55, 0xd5, 0x44, + 0xd5, 0x3f, 0x8d, 0x9f, 0xed, 0x57, 0x55, 0x15, 0xdc, 0xf7, 0x6a, 0xba, + 0xa8, 0xaf, 0xa7, 0x5b, 0x6d, 0xa9, 0x48, 0x52, 0x31, 0x7f, 0x1f, 0x3e, + 0xa3, 0xa6, 0x4e, 0x7b, 0x6c, 0xb4, 0xe7, 0x33, 0x4b, 0x4f, 0xfb, 0x35, + 0x6d, 0x72, 0xed, 0x96, 0x2d, 0x2e, 0x2d, 0x3f, 0xc5, 0xfc, 0xb0, 0x9f, + 0x9e, 0xad, 0x1f, 0x3c, 0x8a, 0x10, 0x91, 0xb1, 0x30, 0x17, 0x08, 0xa8, + 0x70, 0x47, 0x2f, 0x08, 0x69, 0xff, 0xc6, 0x3d, 0x3c, 0xd9, 0xfe, 0xd5, + 0x75, 0x51, 0x3d, 0xcf, 0xe3, 0x67, 0xfb, 0x55, 0xd5, 0x45, 0xc3, 0x3f, + 0xf1, 0xa9, 0xe6, 0xcf, 0xf6, 0xab, 0xaa, 0x89, 0x3e, 0x19, 0x76, 0x26, + 0xc3, 0xa7, 0x9f, 0x07, 0x1c, 0x57, 0x54, 0xdc, 0x55, 0xfc, 0x75, 0x3a, + 0x54, 0xba, 0xab, 0xb3, 0xe9, 0xfc, 0x6c, 0xff, 0x6a, 0xba, 0xa8, 0x8a, + 0xa7, 0xdf, 0xed, 0x57, 0x55, 0x14, 0xcc, 0xfc, 0x5a, 0xca, 0xd8, 0x0b, + 0x48, 0xd8, 0x7b, 0xfe, 0x46, 0x73, 0xff, 0x8c, 0x7a, 0x79, 0xb3, 0xfd, + 0xaa, 0xea, 0xa2, 0x67, 0x9f, 0xfc, 0x63, 0xd3, 0xcd, 0x9f, 0xed, 0x57, + 0x55, 0x14, 0x6c, 0x58, 0x9b, 0x8f, 0xb0, 0x9d, 0x0c, 0xab, 0x8a, 0xb3, + 0xff, 0x1a, 0x9e, 0x6c, 0xff, 0x6a, 0xba, 0xa8, 0x8e, 0xa6, 0xdf, 0x4b, + 0x4f, 0xc5, 0x83, 0xee, 0x58, 0xb4, 0xf7, 0x6a, 0xba, 0xa8, 0xa6, 0xa7, + 0xd9, 0xb5, 0xb5, 0xa5, 0xa3, 0xe7, 0xaa, 0x25, 0xb3, 0xff, 0xb4, 0x5c, + 0xbd, 0x6d, 0x8e, 0x36, 0xae, 0xb4, 0xff, 0xcf, 0xd1, 0x0d, 0x97, 0xdd, + 0xbe, 0xe2, 0xd3, 0x30, 0x6b, 0x4f, 0xc4, 0xc7, 0xb3, 0x2d, 0x5a, 0x7f, + 0xfb, 0x39, 0xac, 0x7f, 0xe8, 0x79, 0xaa, 0x7a, 0xd3, 0x30, 0x16, 0x9f, + 0xd5, 0xb3, 0x74, 0xba, 0x6f, 0xa2, 0x3c, 0x4b, 0x42, 0x4d, 0x8d, 0x23, + 0xf6, 0xf0, 0xa5, 0x91, 0x9d, 0x25, 0x4d, 0xde, 0x8b, 0xe4, 0x20, 0x7a, + 0x43, 0xb2, 0x57, 0x23, 0x43, 0x9f, 0x7f, 0xb5, 0x5d, 0x54, 0x55, 0x53, + 0xfe, 0xa7, 0x9b, 0x3f, 0xda, 0xae, 0xaa, 0x26, 0xd9, 0x1b, 0x0f, 0xf0, + 0xe6, 0x73, 0xf8, 0xd9, 0xfe, 0xd5, 0x75, 0x51, 0x5f, 0xcf, 0xe3, 0x67, + 0xfb, 0x55, 0xd5, 0x45, 0x8d, 0x3f, 0xd9, 0xce, 0x67, 0xfd, 0xc1, 0x5a, + 0x70, 0x6f, 0xc5, 0xa7, 0x17, 0xcc, 0x27, 0xa7, 0x73, 0x79, 0xf7, 0xfb, + 0x55, 0xd5, 0x45, 0xab, 0x3f, 0xea, 0x79, 0xb3, 0xfd, 0xaa, 0xea, 0xa2, + 0x82, 0x91, 0xb0, 0xff, 0x0e, 0x67, 0x3e, 0xff, 0x6a, 0xba, 0xa8, 0xb8, + 0xa7, 0xa9, 0xd4, 0xac, 0x5a, 0x79, 0xd5, 0xd4, 0xba, 0xb4, 0xfe, 0x63, + 0xd3, 0xb6, 0xfb, 0xaa, 0xd2, 0x36, 0x22, 0xd3, 0x46, 0x74, 0x49, 0xc2, + 0x79, 0xf7, 0xfb, 0x55, 0xd5, 0x45, 0xd3, 0x3e, 0x61, 0xaf, 0x59, 0x69, + 0x1b, 0x0f, 0x60, 0x8c, 0xe7, 0xff, 0xb0, 0x78, 0x47, 0xd3, 0x10, 0xd7, + 0xc3, 0x5a, 0x7f, 0xfe, 0x39, 0xb1, 0xed, 0xf0, 0x78, 0xbe, 0x98, 0xf4, + 0x2b, 0x4f, 0x65, 0xbe, 0x6c, 0xeb, 0x4f, 0xff, 0xda, 0x1d, 0xeb, 0xcf, + 0x8f, 0xd6, 0x07, 0x9e, 0xb9, 0xba, 0xd3, 0x77, 0x16, 0x86, 0x3f, 0x70, + 0xd8, 0xa7, 0xfc, 0xfc, 0xff, 0xe9, 0xf6, 0x65, 0xab, 0x4e, 0x1a, 0x37, + 0xcf, 0x87, 0x84, 0x53, 0xfe, 0xfb, 0x6d, 0xaa, 0xbf, 0x1a, 0xd5, 0xa6, + 0xdf, 0x4b, 0x4f, 0x87, 0x35, 0x5c, 0x5a, 0x7e, 0x7d, 0x63, 0xb6, 0xf5, + 0x68, 0x74, 0x4f, 0x4f, 0xcd, 0x24, 0xb3, 0xf0, 0x29, 0xc7, 0x97, 0x16, + 0x9f, 0xff, 0xe6, 0xf7, 0x59, 0xb7, 0x06, 0xb4, 0x0a, 0xbd, 0xeb, 0xfd, + 0x5a, 0x7f, 0x66, 0x60, 0x61, 0xd3, 0xd6, 0x80, 0xd1, 0x31, 0xc6, 0x69, + 0xff, 0xfe, 0xd3, 0x0e, 0xf7, 0xd1, 0x5f, 0x9a, 0xcf, 0x4a, 0xf8, 0x2b, + 0x4f, 0x76, 0xab, 0xaa, 0x89, 0x32, 0x7d, 0xdf, 0x35, 0xf9, 0x9e, 0x6d, + 0xd1, 0x5a, 0x7f, 0x66, 0xdc, 0xe5, 0x3e, 0xeb, 0x43, 0x1f, 0x9f, 0xd0, + 0x21, 0xe9, 0x93, 0x7d, 0x94, 0xf0, 0xa0, 0x9f, 0xbd, 0x03, 0xa3, 0x70, + 0x79, 0x2d, 0x3f, 0xfb, 0x45, 0xcd, 0xba, 0x5f, 0xbb, 0x8d, 0x4b, 0x4f, + 0xb3, 0x84, 0x7a, 0x5a, 0x7f, 0x9b, 0x5c, 0x2b, 0x0e, 0xdd, 0x5a, 0x7f, + 0xfd, 0x5d, 0xfb, 0x68, 0x73, 0x98, 0x6b, 0x6d, 0xb5, 0x28, 0x7a, 0x2c, + 0xee, 0x4b, 0xc3, 0x89, 0xfb, 0xbd, 0xdf, 0xa2, 0xcb, 0x4f, 0xfe, 0xdc, + 0x6b, 0x8d, 0xfb, 0xe9, 0x87, 0x8b, 0x4e, 0xb6, 0xdb, 0x52, 0x9f, 0xbf, + 0x56, 0x1c, 0xae, 0x91, 0x8b, 0xf9, 0xf6, 0x1f, 0xf9, 0xe7, 0x5a, 0x7c, + 0x5c, 0xb3, 0x2d, 0x5a, 0x7f, 0x9b, 0xba, 0x1d, 0xf8, 0xd7, 0x5a, 0x7f, + 0xfd, 0xcd, 0x63, 0xfc, 0x77, 0x45, 0xcd, 0xba, 0x5f, 0x5a, 0x3e, 0x8b, + 0x83, 0x94, 0x09, 0xc4, 0xff, 0x65, 0x03, 0xc6, 0x7b, 0x4f, 0x5a, 0x7f, + 0xbc, 0x0f, 0x8b, 0xd9, 0x55, 0xa5, 0xa7, 0xff, 0x55, 0x9a, 0x26, 0x1c, + 0x70, 0xbe, 0x4b, 0x46, 0x23, 0xaf, 0x85, 0xfb, 0x9c, 0xda, 0x79, 0x3e, + 0x1c, 0xd5, 0x71, 0x69, 0xff, 0x3d, 0x86, 0xc0, 0xbe, 0x3b, 0xbd, 0x68, + 0xf3, 0x9f, 0x2b, 0x84, 0xb3, 0xdc, 0xb3, 0x3d, 0x5a, 0x7f, 0x88, 0x30, + 0xbe, 0x03, 0x95, 0xd6, 0x87, 0x0f, 0x77, 0xc8, 0x8e, 0x75, 0xb6, 0xda, + 0xb4, 0xff, 0xfb, 0x0f, 0xc1, 0x60, 0x67, 0xb7, 0xc3, 0xb7, 0x52, 0x31, + 0x7f, 0x18, 0x99, 0x59, 0x42, 0x13, 0x48, 0xb2, 0x25, 0xa7, 0xed, 0x37, + 0x8e, 0x95, 0x8b, 0x4f, 0xdf, 0xaf, 0x8b, 0x3d, 0x68, 0x74, 0xe7, 0xd3, + 0xe8, 0x7f, 0xcb, 0xe6, 0x79, 0x9d, 0x26, 0x4c, 0x07, 0x98, 0xd4, 0xc5, + 0xef, 0x86, 0x2e, 0x46, 0x94, 0x19, 0xb7, 0x4d, 0xf6, 0x87, 0xe9, 0x18, + 0x38, 0x5b, 0x4c, 0xe2, 0x79, 0x78, 0xf3, 0x79, 0x1e, 0x70, 0x50, 0x9d, + 0x9f, 0xfd, 0xfc, 0xb7, 0xdd, 0x63, 0x83, 0x9f, 0x7a, 0xd3, 0xbf, 0x9e, + 0x75, 0xa5, 0x9f, 0x3e, 0xa3, 0xa5, 0xcf, 0xfd, 0x9a, 0x16, 0xb0, 0x2d, + 0x6a, 0xbc, 0xeb, 0x4f, 0xd5, 0xd3, 0x5b, 0x6d, 0xab, 0x48, 0xcc, 0xcd, + 0xa7, 0xc4, 0xc0, 0xd6, 0xfb, 0x18, 0x07, 0xcc, 0xaa, 0x93, 0x7e, 0x31, + 0xb8, 0x5c, 0x9d, 0xda, 0x44, 0x3d, 0xb5, 0x2a, 0xf6, 0x55, 0xee, 0xd0, + 0xec, 0xf9, 0x39, 0xcf, 0xb5, 0x09, 0x10, 0x42, 0xba, 0xf0, 0xdb, 0xe4, + 0x26, 0x77, 0xa6, 0xe6, 0xcf, 0xe3, 0x67, 0xfb, 0x55, 0xd5, 0x44, 0x6b, + 0x3e, 0xff, 0x6a, 0xba, 0xa8, 0xa9, 0x66, 0xae, 0xaa, 0x21, 0xa9, 0x1b, + 0x0f, 0x56, 0x8c, 0xe7, 0xfe, 0x35, 0x3c, 0xd9, 0xfe, 0xd5, 0x75, 0x51, + 0x1f, 0x4f, 0xbf, 0xda, 0xae, 0xaa, 0x2c, 0x79, 0xfe, 0x1a, 0x0f, 0x44, + 0xc3, 0x62, 0xd2, 0x36, 0x1f, 0x53, 0x86, 0x73, 0xbc, 0xd6, 0x70, 0xd6, + 0x9f, 0x9d, 0x11, 0xcd, 0x57, 0x16, 0x9f, 0xe1, 0xa0, 0x5e, 0xb9, 0xca, + 0x5a, 0x78, 0x07, 0x2f, 0xad, 0x3f, 0xff, 0xcd, 0xf1, 0xcf, 0x73, 0x5a, + 0x2e, 0xe8, 0xb9, 0x7a, 0xd9, 0x68, 0x24, 0x43, 0x68, 0x86, 0x75, 0x57, + 0x55, 0x16, 0x84, 0xff, 0xaa, 0xd7, 0x5e, 0xd7, 0xec, 0xcb, 0x56, 0x8e, + 0x9f, 0x57, 0xc9, 0xe7, 0xff, 0xff, 0x8b, 0xfa, 0xa1, 0x26, 0xe1, 0x69, + 0xaf, 0xac, 0xbf, 0xf3, 0xb9, 0xc5, 0xa3, 0xe8, 0x9a, 0xd1, 0x14, 0xfa, + 0x9e, 0x34, 0x12, 0xd3, 0xf3, 0xb0, 0xb8, 0x2c, 0x05, 0xa7, 0xcd, 0xcc, + 0xd3, 0x2d, 0x3f, 0xfd, 0x9c, 0xbe, 0x39, 0xa6, 0x77, 0x9a, 0xd3, 0x2d, + 0x16, 0x1f, 0xa8, 0x92, 0xc3, 0x23, 0x1b, 0x21, 0x49, 0x3f, 0xe0, 0x37, + 0x74, 0x4e, 0x55, 0x58, 0xb4, 0xff, 0x16, 0xe2, 0xff, 0xb6, 0x7a, 0xb4, + 0xff, 0xff, 0x6b, 0x1f, 0xdd, 0x16, 0x9b, 0x3e, 0xfe, 0x0b, 0x1e, 0xeb, + 0x4b, 0x2e, 0x89, 0xce, 0x1c, 0x4f, 0xdf, 0x6e, 0x59, 0x96, 0xad, 0x3e, + 0xe6, 0xa8, 0x43, 0x5a, 0x7f, 0x87, 0x7b, 0x3d, 0xd6, 0x0b, 0x2d, 0x2c, + 0xc3, 0xe0, 0xe9, 0x3c, 0x12, 0x2d, 0x06, 0x11, 0xd3, 0xfb, 0x34, 0xe1, + 0x68, 0x8e, 0xb4, 0xfe, 0x7e, 0x7e, 0xe2, 0xd6, 0x2d, 0x3b, 0x35, 0x4b, + 0x4f, 0x9c, 0xf7, 0x7d, 0xc5, 0x68, 0xf4, 0xf1, 0x68, 0x6a, 0x7f, 0xb3, + 0x2d, 0xce, 0xf7, 0x2d, 0x5a, 0x31, 0x30, 0x3d, 0x8c, 0xa9, 0xde, 0xe4, + 0x53, 0xd9, 0xaa, 0xe2, 0xd3, 0xf1, 0x0e, 0xef, 0xcb, 0xad, 0x3f, 0xff, + 0xc4, 0x3b, 0xbf, 0x2f, 0xe3, 0x38, 0xdb, 0x02, 0xb5, 0xa2, 0x5a, 0x03, + 0x44, 0x9f, 0x0b, 0x24, 0x67, 0x49, 0x7d, 0x7f, 0xcc, 0x25, 0x63, 0x0f, + 0x61, 0x79, 0x91, 0x93, 0x1c, 0x8f, 0x50, 0xec, 0x01, 0x30, 0xc3, 0x6a, + 0xf0, 0xdc, 0xe4, 0x65, 0x3e, 0x47, 0xa1, 0x42, 0xce, 0x7e, 0x75, 0x2f, + 0x45, 0x9c, 0x5a, 0x7b, 0x35, 0x5c, 0x5a, 0x4e, 0x8e, 0x1e, 0x80, 0x98, + 0x4f, 0xbf, 0xda, 0xae, 0xaa, 0x2d, 0x69, 0xff, 0x53, 0xcd, 0x9f, 0xed, + 0x57, 0x55, 0x14, 0x1c, 0x8d, 0xe6, 0x22, 0x9f, 0x0a, 0xce, 0x67, 0x3f, + 0xf8, 0xc7, 0xa7, 0x9b, 0x3f, 0xda, 0xae, 0xaa, 0x28, 0xb9, 0xfc, 0x6c, + 0xff, 0x6a, 0xba, 0xa8, 0xba, 0xa1, 0xec, 0xba, 0x9e, 0xc2, 0x4c, 0x89, + 0x8f, 0x0b, 0x9d, 0x4f, 0xc3, 0x82, 0x3e, 0x3b, 0x9d, 0x71, 0x56, 0x7d, + 0xfe, 0xd5, 0x75, 0x51, 0x10, 0xce, 0xd6, 0x7a, 0xb4, 0xf6, 0x3b, 0x2f, + 0xad, 0x23, 0x61, 0xf8, 0xec, 0x67, 0xe7, 0x1c, 0x9f, 0xc6, 0xcf, 0xf6, + 0xab, 0xaa, 0x88, 0xda, 0x7f, 0x1b, 0x3f, 0xda, 0xae, 0xaa, 0x29, 0xb9, + 0xfc, 0x6c, 0xff, 0x6a, 0xba, 0xa8, 0xa8, 0x27, 0xf1, 0xb3, 0xfd, 0xaa, + 0xea, 0xa2, 0xa6, 0x9f, 0x7f, 0xb5, 0x5d, 0x54, 0x56, 0x13, 0xe2, 0xb7, + 0xdd, 0xfe, 0xb4, 0xff, 0x3c, 0xd9, 0xfe, 0xd5, 0x75, 0x51, 0x1f, 0xce, + 0xc6, 0xb1, 0x69, 0x1b, 0x11, 0x79, 0xd3, 0x3a, 0x2a, 0x14, 0x49, 0xff, + 0xc6, 0x3d, 0x3c, 0xd9, 0xfe, 0xd5, 0x75, 0x51, 0x37, 0xcf, 0xfc, 0x7a, + 0x79, 0xb3, 0xfd, 0xaa, 0xea, 0xa2, 0x7e, 0x9f, 0x79, 0xb7, 0x47, 0xcb, + 0x36, 0x5a, 0x5a, 0x5a, 0x1d, 0x31, 0xe3, 0x6e, 0x6d, 0x3f, 0xde, 0x6a, + 0xd5, 0x73, 0x95, 0x56, 0xad, 0x3e, 0xf0, 0x1f, 0xeb, 0xab, 0x4f, 0xbc, + 0xc7, 0x4e, 0x00, 0xd9, 0x69, 0xff, 0xe6, 0x66, 0x66, 0x66, 0x66, 0x6d, + 0xae, 0xb4, 0xfb, 0xe5, 0x65, 0x1d, 0x29, 0xad, 0xb5, 0x28, 0xc3, 0x7f, + 0x69, 0x3c, 0xb7, 0x48, 0xc6, 0x86, 0x19, 0x19, 0x57, 0x85, 0x4c, 0xf7, + 0xc4, 0x99, 0x69, 0xff, 0xfb, 0x0f, 0x7e, 0x7e, 0xac, 0xf7, 0x4d, 0x7e, + 0x6f, 0xa5, 0xa7, 0xd5, 0xdd, 0x17, 0xd6, 0x9f, 0xff, 0xfc, 0x34, 0x19, + 0x7d, 0x9f, 0xe3, 0x44, 0x7f, 0x5b, 0xc7, 0x96, 0xfa, 0x2f, 0xad, 0x3d, + 0x4f, 0x2d, 0x96, 0x8f, 0x53, 0x24, 0xfa, 0xf6, 0x89, 0x85, 0xfe, 0x7f, + 0xfc, 0xea, 0xdb, 0x3e, 0x84, 0x1e, 0x00, 0x5f, 0x20, 0xd6, 0x9e, 0xb0, + 0xe4, 0xf5, 0xa7, 0xd7, 0x1d, 0xe9, 0xeb, 0x4f, 0xed, 0x31, 0xcc, 0x00, + 0x32, 0xd2, 0xfb, 0x1f, 0xde, 0x11, 0x11, 0x3c, 0xff, 0x9b, 0x6f, 0x42, + 0x6f, 0x3e, 0xb7, 0xba, 0xd3, 0xb3, 0x1c, 0x5a, 0x5e, 0x75, 0xa6, 0xbe, + 0x2d, 0x04, 0x6a, 0x40, 0x29, 0x3f, 0xf7, 0xad, 0xca, 0xbe, 0x6b, 0x44, + 0x75, 0xa7, 0x70, 0x9c, 0x5a, 0x18, 0xf7, 0xee, 0x87, 0x0c, 0xaf, 0x67, + 0x09, 0xca, 0x34, 0x37, 0x0f, 0xbf, 0x0d, 0x53, 0x99, 0x01, 0x18, 0x51, + 0x38, 0xff, 0x39, 0xc0, 0x8e, 0xb4, 0xff, 0x67, 0x2f, 0x9c, 0xd6, 0x01, + 0x68, 0xa3, 0xd5, 0x11, 0xe9, 0xa8, 0x56, 0x8f, 0x9b, 0x43, 0x90, 0x4e, + 0xb6, 0xdb, 0x56, 0x9e, 0xb0, 0x49, 0x92, 0x31, 0x7f, 0x36, 0xcf, 0x5a, + 0x7f, 0x06, 0xdc, 0xbe, 0xab, 0x75, 0xa7, 0xc5, 0x66, 0x6d, 0xc5, 0xa6, + 0x2e, 0xad, 0x2d, 0x99, 0x11, 0x01, 0x8b, 0x9c, 0xd3, 0x85, 0x10, 0xc9, + 0x9d, 0xe9, 0x03, 0x78, 0x5b, 0xcf, 0xfd, 0x66, 0xe0, 0xc1, 0xdd, 0xc6, + 0xe5, 0x2d, 0x3f, 0xf3, 0x68, 0x2f, 0x78, 0x58, 0xe3, 0x1d, 0x69, 0xe6, + 0xe6, 0xe7, 0x5a, 0x6b, 0xf5, 0x51, 0x07, 0x49, 0x8e, 0x78, 0xbc, 0x22, + 0x9f, 0xab, 0x4d, 0xe7, 0xdd, 0xeb, 0x47, 0xd3, 0x28, 0x3a, 0x37, 0x21, + 0x14, 0xec, 0x9e, 0x6f, 0xfa, 0xb4, 0xd6, 0xda, 0xb4, 0x3c, 0xd7, 0xda, + 0x2f, 0x3d, 0x76, 0x27, 0x69, 0x18, 0xd1, 0x4f, 0xae, 0x17, 0xa0, 0xba, + 0xd0, 0x47, 0xb8, 0x11, 0x94, 0xeb, 0x6d, 0xb5, 0x28, 0x48, 0xc5, 0xfc, + 0xf1, 0x5a, 0x4e, 0x25, 0x1d, 0x37, 0x64, 0x33, 0x04, 0x9c, 0x76, 0xa1, + 0xe4, 0x2f, 0x93, 0x84, 0xae, 0xb4, 0xf7, 0xb4, 0x20, 0x5a, 0x7f, 0xe6, + 0x10, 0x0b, 0x6c, 0xe3, 0x08, 0x16, 0x82, 0x3f, 0xd0, 0x0d, 0x5c, 0x86, + 0x7c, 0xd9, 0xfd, 0x12, 0xd3, 0xb0, 0xfe, 0x75, 0xa7, 0xf6, 0x5c, 0x73, + 0x9a, 0x25, 0xa3, 0xd3, 0xcf, 0x11, 0xf8, 0x71, 0x12, 0xce, 0xdd, 0x27, + 0xee, 0x63, 0x82, 0xc7, 0x5c, 0x40, 0x53, 0xea, 0x1f, 0x74, 0xca, 0x88, + 0x08, 0xc6, 0xe6, 0x7e, 0xfb, 0x3e, 0xb6, 0xb5, 0x69, 0xfe, 0x6f, 0xd9, + 0xe3, 0x1c, 0x23, 0xad, 0x3c, 0x76, 0xc3, 0xad, 0x3e, 0x60, 0x16, 0xd7, + 0x5a, 0x7f, 0x7d, 0xcb, 0x90, 0xf0, 0x96, 0x9e, 0xd8, 0x1f, 0x64, 0xa6, + 0xb6, 0xd4, 0xa1, 0x8d, 0xd5, 0xa4, 0x53, 0xf5, 0x7d, 0xef, 0x12, 0x48, + 0xc6, 0x86, 0x19, 0x56, 0xf6, 0x43, 0x10, 0x97, 0xfe, 0x87, 0xa2, 0xea, + 0x3b, 0x12, 0x0e, 0x13, 0xef, 0x08, 0xd9, 0xf6, 0xfc, 0xde, 0x82, 0x54, + 0x40, 0xf3, 0x36, 0xeb, 0x4f, 0xb7, 0x10, 0x04, 0xf5, 0xa7, 0xff, 0xce, + 0xc7, 0x2f, 0xe0, 0x2d, 0x30, 0x0f, 0x4f, 0xf1, 0x86, 0xfe, 0xe2, 0xd3, + 0xfe, 0xd6, 0xf6, 0x37, 0x86, 0xe3, 0x7a, 0xb4, 0xea, 0xfd, 0x8b, 0x4d, + 0x6d, 0xab, 0x4f, 0xef, 0x96, 0x3b, 0x09, 0xe6, 0xe9, 0xb4, 0xb4, 0x72, + 0x3d, 0x46, 0x1f, 0xdd, 0xa7, 0xfe, 0x1c, 0x0f, 0x54, 0xf0, 0xab, 0x66, + 0x5a, 0x18, 0xfa, 0x30, 0x92, 0x7f, 0xf6, 0x1c, 0xf4, 0xfc, 0xff, 0x6a, + 0xba, 0xa8, 0x86, 0x20, 0x95, 0x08, 0x6a, 0x39, 0xc1, 0x20, 0x9f, 0x7f, + 0xb5, 0x5d, 0x54, 0x41, 0x53, 0xb4, 0xc7, 0x5a, 0x30, 0xf3, 0x88, 0xce, + 0x7b, 0x97, 0x60, 0x25, 0x3a, 0xbf, 0x62, 0x53, 0xdb, 0x8d, 0x06, 0x94, + 0xfe, 0x2b, 0x33, 0x97, 0x60, 0x25, 0x09, 0x4f, 0xd9, 0xd6, 0xd3, 0x1d, + 0x29, 0xad, 0xb5, 0x29, 0xf8, 0x58, 0x6c, 0x2f, 0xa5, 0x18, 0x98, 0x4f, + 0x48, 0x4e, 0x39, 0xa2, 0x20, 0x19, 0x08, 0x55, 0xa5, 0x61, 0x0c, 0x4c, + 0x5f, 0x48, 0xc7, 0xe5, 0x2a, 0x7a, 0x78, 0x3a, 0x8e, 0x6e, 0x7f, 0xcc, + 0xfa, 0x1c, 0xbf, 0xda, 0xeb, 0x4f, 0xf7, 0x34, 0xce, 0xfd, 0x60, 0x32, + 0xd3, 0xfc, 0xdb, 0x3f, 0xc9, 0xbb, 0x87, 0x5a, 0x18, 0xfd, 0x74, 0x75, + 0x3f, 0xff, 0x39, 0x54, 0xfa, 0xd7, 0x8c, 0xb3, 0x7e, 0x6f, 0x41, 0x2a, + 0x2f, 0xb9, 0xdc, 0xce, 0xad, 0x3d, 0xc1, 0xc7, 0xad, 0x0c, 0x6e, 0xc8, + 0x6e, 0x7f, 0xc2, 0x4f, 0xe6, 0xb3, 0xe5, 0x75, 0xa7, 0xf7, 0x00, 0x10, + 0xbf, 0x58, 0xb8, 0x80, 0x67, 0x57, 0xde, 0xb8, 0x80, 0x63, 0x0f, 0xa3, + 0x74, 0x09, 0xa9, 0xeb, 0x88, 0x06, 0x7a, 0x87, 0x67, 0xae, 0x20, 0x19, + 0xfc, 0x5d, 0xd6, 0x00, 0x0c, 0xb8, 0x80, 0x67, 0x10, 0xfa, 0xb8, 0x80, + 0x63, 0xd4, 0x5b, 0x1c, 0x8b, 0x45, 0xce, 0xcf, 0xa7, 0x7d, 0xb8, 0xb8, + 0x80, 0x61, 0x71, 0x00, 0xcc, 0xc7, 0x5c, 0x40, 0x31, 0xe9, 0xb9, 0x21, + 0x79, 0xe2, 0x3f, 0xac, 0xb8, 0x80, 0x67, 0x72, 0xba, 0xb8, 0x80, 0x67, + 0xfc, 0x34, 0xf3, 0x68, 0xbb, 0x5f, 0x5c, 0x40, 0x33, 0x57, 0xab, 0x88, + 0x06, 0x7f, 0x0d, 0x5b, 0x7b, 0xb0, 0x17, 0x10, 0x0c, 0xf8, 0x83, 0xa1, + 0x02, 0xe2, 0x01, 0x98, 0xae, 0xb8, 0x80, 0x63, 0xe7, 0xab, 0xc3, 0x39, + 0xf7, 0x05, 0xb6, 0x7a, 0xa2, 0x01, 0x98, 0x0c, 0xb8, 0x80, 0x4c, 0x6d, + 0x27, 0xc4, 0xc7, 0xaf, 0x57, 0x10, 0x0c, 0xf6, 0xf4, 0x3d, 0x5c, 0x40, + 0x33, 0x9b, 0xfd, 0x5c, 0x40, 0x33, 0xfe, 0xcd, 0xaf, 0xeb, 0x60, 0x82, + 0xeb, 0x88, 0x06, 0x7d, 0xbd, 0x3d, 0xec, 0xb8, 0x80, 0x63, 0x0f, 0xff, + 0x49, 0x73, 0x7c, 0x0b, 0x88, 0x06, 0x1e, 0xaa, 0x2f, 0xd2, 0x3c, 0x84, + 0xa1, 0x2b, 0x68, 0xc8, 0x06, 0x77, 0x2e, 0xe4, 0x29, 0xf7, 0x21, 0x9f, + 0x67, 0x2f, 0x5b, 0x2e, 0x20, 0x19, 0xfd, 0xeb, 0x75, 0xcf, 0xb7, 0x17, + 0x10, 0x0f, 0xa6, 0xd2, 0x77, 0xda, 0xd5, 0xc4, 0x03, 0x1d, 0x3f, 0xbf, + 0xa9, 0x4f, 0x10, 0xbe, 0xeb, 0x88, 0x06, 0x7e, 0xa7, 0x1f, 0x9b, 0x3d, + 0x71, 0x00, 0xc6, 0x22, 0x28, 0x04, 0x5b, 0x96, 0xcf, 0xf6, 0x9a, 0xe6, + 0xbe, 0xff, 0x02, 0xe2, 0x01, 0x91, 0x2e, 0x20, 0x19, 0xaa, 0xcf, 0x4f, + 0x8f, 0x48, 0xf3, 0x7c, 0x0b, 0x88, 0x06, 0x7d, 0x5c, 0xb9, 0x6c, 0xb8, + 0x80, 0x67, 0xe2, 0x1d, 0xdf, 0x97, 0x5c, 0x40, 0x30, 0xc8, 0x90, 0x12, + 0x3e, 0x1a, 0x47, 0xac, 0x80, 0x7c, 0x86, 0xc3, 0x88, 0x1f, 0x61, 0xd1, + 0x0d, 0x17, 0x00, 0xa2, 0xf2, 0xb5, 0x39, 0x1e, 0x1f, 0x94, 0x24, 0xc2, + 0x85, 0xfc, 0xea, 0xae, 0xaa, 0x20, 0x13, 0x22, 0xf2, 0x78, 0x37, 0x4f, + 0xe9, 0xd6, 0x9f, 0xf9, 0xe3, 0x9d, 0xd1, 0x00, 0x1b, 0xb8, 0xb4, 0xc0, + 0x64, 0xa4, 0x1a, 0x53, 0xb4, 0xd6, 0x2d, 0x35, 0xb6, 0xa5, 0x04, 0x7b, + 0x5e, 0x71, 0x57, 0x04, 0x6d, 0x1c, 0x9c, 0x72, 0xba, 0x46, 0x3c, 0x19, + 0xff, 0xdc, 0xbe, 0x06, 0x5f, 0x08, 0x00, 0x6d, 0x96, 0x8b, 0x19, 0x68, + 0x2f, 0x1f, 0xed, 0x25, 0x9c, 0x8e, 0x68, 0xa8, 0x10, 0xe0, 0x08, 0xb2, + 0x7e, 0xe0, 0x4e, 0xdd, 0xb5, 0xd6, 0x9c, 0x3b, 0x3d, 0x71, 0x80, 0x41, + 0x1e, 0xdd, 0x19, 0x4f, 0xdf, 0x71, 0xed, 0xfb, 0x16, 0x9f, 0xf3, 0xef, + 0xe0, 0x70, 0xed, 0xf0, 0xd6, 0x9f, 0xfb, 0xfa, 0xac, 0x7b, 0x7c, 0x01, + 0x1d, 0x69, 0xf8, 0x2b, 0x7e, 0x59, 0xa4, 0xa7, 0x9c, 0xd5, 0x38, 0xb4, + 0xfb, 0x1c, 0x7b, 0x6c, 0xb4, 0xf6, 0x0b, 0x3d, 0x28, 0xf9, 0xf5, 0x09, + 0x1b, 0xa9, 0x44, 0x32, 0x6f, 0x98, 0x5e, 0x48, 0x1a, 0x44, 0xa8, 0x4c, + 0x4f, 0xdb, 0x5f, 0xff, 0x6e, 0x2d, 0x3c, 0x43, 0xcb, 0xad, 0x3b, 0x77, + 0x42, 0xeb, 0x4f, 0xfc, 0x03, 0xe7, 0x74, 0x5e, 0xeb, 0x70, 0xd6, 0x9f, + 0xfa, 0xe1, 0x87, 0x5e, 0x18, 0xe2, 0x41, 0xad, 0x3f, 0xd9, 0xce, 0x10, + 0x7c, 0xce, 0xad, 0x3f, 0xbd, 0x6d, 0xaf, 0x9b, 0x71, 0x69, 0xfe, 0x6e, + 0x17, 0x4b, 0xd6, 0xe2, 0xd2, 0xbf, 0xa8, 0xa4, 0xc3, 0x8f, 0x9a, 0xc6, + 0xc9, 0x90, 0xf2, 0x1d, 0x73, 0xf9, 0xf9, 0x70, 0xaf, 0xcc, 0x5a, 0x19, + 0x3e, 0x72, 0x22, 0xa8, 0xda, 0x37, 0x2a, 0x9f, 0xf9, 0xb6, 0x78, 0xee, + 0x0f, 0x1d, 0x38, 0x16, 0x9f, 0x53, 0xbf, 0x7d, 0x65, 0xa6, 0xbd, 0xd6, + 0x9d, 0x6d, 0xb6, 0xad, 0x30, 0x29, 0x23, 0x17, 0xf1, 0xf3, 0xd9, 0xb9, + 0xac, 0xff, 0xb2, 0xe3, 0x41, 0xb8, 0x24, 0xe2, 0xd3, 0xa8, 0x18, 0x94, + 0x3d, 0x1f, 0xa7, 0x84, 0x1e, 0x88, 0xbc, 0x8f, 0xa7, 0xf7, 0x07, 0x36, + 0x00, 0x4f, 0x5a, 0x6a, 0x3a, 0xd3, 0xfc, 0x38, 0xef, 0x0d, 0x6d, 0xb6, + 0xa5, 0x38, 0xf5, 0xc5, 0xa1, 0xc3, 0xec, 0xd0, 0xab, 0xb3, 0xc9, 0xf6, + 0x38, 0xf6, 0x7a, 0xd3, 0xf5, 0x9e, 0xb6, 0x0f, 0xab, 0x43, 0x26, 0xd8, + 0x48, 0xb5, 0x0a, 0x0e, 0x19, 0x04, 0x4f, 0x3f, 0x3a, 0x05, 0xf6, 0xf8, + 0xad, 0x3f, 0xfb, 0x3d, 0xa3, 0xe6, 0xcf, 0xc0, 0x67, 0xab, 0x4d, 0xaa, + 0x5a, 0x67, 0x2e, 0xb4, 0xfb, 0x1d, 0x90, 0xfb, 0x86, 0xb0, 0x21, 0x58, + 0xf0, 0x8b, 0x53, 0x3b, 0x4f, 0x7b, 0x9a, 0xa5, 0xa7, 0xfe, 0x6c, 0x17, + 0xeb, 0x04, 0x5a, 0xd5, 0xa4, 0x5f, 0x44, 0x30, 0x92, 0xf0, 0x86, 0x2c, + 0x6e, 0xe0, 0x9f, 0x2a, 0x1b, 0xd9, 0x48, 0xa1, 0x97, 0xf6, 0x16, 0x04, + 0x43, 0xfa, 0x5f, 0x49, 0xe1, 0xa3, 0xa8, 0xe7, 0x29, 0x30, 0x05, 0xc3, + 0x28, 0x1e, 0xe8, 0x5c, 0x95, 0x63, 0xba, 0x73, 0xb8, 0xda, 0xe7, 0xfb, + 0x3e, 0x2d, 0xc7, 0xe6, 0x96, 0x9f, 0x84, 0x19, 0xed, 0x3d, 0x69, 0xf5, + 0x3f, 0xc7, 0xa4, 0xb4, 0xf8, 0x27, 0x1e, 0xde, 0x74, 0xa1, 0x91, 0x71, + 0x86, 0xda, 0x2c, 0x08, 0xa6, 0x7f, 0xe0, 0xc7, 0x7a, 0x0b, 0x2e, 0x4c, + 0x2b, 0x4e, 0xf2, 0x2f, 0x56, 0x8f, 0x9f, 0x11, 0xd0, 0xe4, 0x12, 0xa2, + 0x05, 0x97, 0x15, 0x40, 0xa4, 0x8b, 0x86, 0xee, 0xd1, 0xe9, 0xff, 0x17, + 0x96, 0x88, 0x23, 0x00, 0x0c, 0x94, 0xff, 0x6b, 0x7a, 0xbe, 0x60, 0x86, + 0xb4, 0xff, 0x85, 0xb6, 0x3d, 0x73, 0x36, 0xe2, 0xd3, 0xff, 0xfb, 0x4d, + 0xf0, 0x63, 0xb2, 0xbd, 0xf0, 0x7b, 0x42, 0x05, 0xa7, 0xcd, 0x83, 0xcf, + 0x25, 0xa4, 0x75, 0xa7, 0xaf, 0x5b, 0x78, 0x39, 0xb8, 0x12, 0x88, 0xf5, + 0x34, 0x7f, 0x9c, 0x68, 0xfb, 0x90, 0x97, 0x86, 0x4f, 0x30, 0xa3, 0x88, + 0x9f, 0xff, 0xef, 0xd9, 0x84, 0xe5, 0x53, 0xee, 0xc7, 0xf7, 0x4c, 0x3d, + 0x5a, 0x75, 0x82, 0xf5, 0xa1, 0x95, 0x7d, 0x1d, 0x77, 0x51, 0xf9, 0xd1, + 0x46, 0xec, 0xd3, 0xf9, 0xee, 0x60, 0xd6, 0xb1, 0x69, 0xff, 0xdf, 0xe8, + 0xe3, 0xb1, 0xc6, 0xfb, 0x5d, 0x69, 0xfd, 0x4e, 0x6b, 0x0f, 0x97, 0x5a, + 0x30, 0xfe, 0x9d, 0xa4, 0x4f, 0xc4, 0x1f, 0x33, 0x0e, 0xb4, 0xfb, 0x3d, + 0x2f, 0xdd, 0x69, 0xdc, 0xe6, 0xcb, 0x4f, 0xfd, 0xb7, 0x4b, 0x5e, 0x1d, + 0x73, 0x44, 0x75, 0xa5, 0x70, 0x1f, 0x37, 0x07, 0x64, 0xd6, 0x22, 0xd6, + 0xd8, 0x46, 0xc1, 0x26, 0x67, 0xa2, 0x3e, 0x43, 0x86, 0x7f, 0xc2, 0x41, + 0x6d, 0x8e, 0xf6, 0x60, 0x96, 0x9d, 0xed, 0x7d, 0x69, 0xf0, 0x28, 0x71, + 0xd5, 0x68, 0x63, 0xc4, 0xdc, 0x72, 0x71, 0xf6, 0x71, 0x69, 0xff, 0x8f, + 0x73, 0xb7, 0xad, 0xfd, 0xd8, 0x0b, 0x4f, 0xf9, 0xa8, 0x73, 0x9c, 0x2b, + 0x78, 0xb4, 0x32, 0x27, 0xc8, 0x7b, 0x74, 0x58, 0x65, 0x71, 0x2f, 0x8f, + 0x7b, 0x0d, 0x6f, 0x08, 0x6e, 0x43, 0x06, 0x7f, 0xf3, 0xcb, 0x58, 0xe1, + 0x39, 0xbb, 0xae, 0xf7, 0x5a, 0x7f, 0x16, 0xbd, 0xa1, 0x6d, 0x2d, 0x3e, + 0x6b, 0x19, 0xd5, 0x96, 0x9f, 0xb7, 0x0b, 0x59, 0xb5, 0xd6, 0x9f, 0xff, + 0xff, 0x7d, 0xb8, 0x35, 0xd2, 0xbb, 0x85, 0xfe, 0xb5, 0xf8, 0xdb, 0x30, + 0x59, 0x75, 0xa7, 0xff, 0xff, 0xfd, 0x7b, 0xe7, 0x35, 0x47, 0xbf, 0xfe, + 0x5b, 0x72, 0xa8, 0x3c, 0xc7, 0x37, 0xe3, 0x5b, 0xb3, 0x2d, 0x0c, 0x98, + 0xfd, 0x42, 0x0a, 0x78, 0x15, 0xb5, 0xd6, 0x9a, 0xdb, 0x56, 0x8b, 0x9b, + 0xab, 0x48, 0xa7, 0xc5, 0xaa, 0xcd, 0x24, 0x63, 0x45, 0x1b, 0x2a, 0xb1, + 0x71, 0x43, 0x46, 0x2e, 0x81, 0x48, 0xc6, 0x9f, 0x78, 0x41, 0xce, 0xb6, + 0xdb, 0x52, 0x9e, 0x7f, 0x33, 0xa9, 0x18, 0xbf, 0x9f, 0x67, 0xa4, 0x0b, + 0xae, 0xef, 0xe9, 0x57, 0xcf, 0x90, 0x4c, 0x26, 0xfd, 0xd6, 0x9c, 0xed, + 0xba, 0xb4, 0x31, 0xb3, 0x21, 0x69, 0xf8, 0x76, 0xbb, 0x99, 0xc5, 0xa7, + 0xff, 0x87, 0x8e, 0x67, 0xf2, 0x8e, 0xda, 0x2f, 0x56, 0x9f, 0x59, 0xe7, + 0x2c, 0xd9, 0x69, 0xf1, 0x5d, 0xac, 0x3a, 0xec, 0xfd, 0x9f, 0x35, 0x7c, + 0x1b, 0xae, 0xcf, 0xd9, 0xa9, 0xeb, 0xb3, 0xf6, 0x7b, 0x77, 0xe5, 0xd7, + 0x67, 0xec, 0x7a, 0x7a, 0x22, 0x45, 0x3e, 0x6c, 0xbe, 0x0a, 0xec, 0xfd, + 0x85, 0xd9, 0xfb, 0x35, 0x71, 0x76, 0x7e, 0x9c, 0xb7, 0x93, 0xc4, 0xfe, + 0x77, 0x48, 0x9e, 0xcf, 0x22, 0x02, 0xec, 0xfd, 0x85, 0xd9, 0xfb, 0x30, + 0x19, 0x76, 0x7e, 0xcf, 0xf6, 0x02, 0xbf, 0x8e, 0xf3, 0xd5, 0xd9, 0xfb, + 0x3f, 0x66, 0x8a, 0xfb, 0xd8, 0xbb, 0x3f, 0x60, 0x08, 0xa3, 0x12, 0x2b, + 0xa3, 0x4f, 0x7e, 0xc6, 0xe2, 0xec, 0xfd, 0x85, 0xd9, 0xfb, 0x86, 0xbe, + 0x6b, 0x6d, 0x5d, 0x9f, 0xb0, 0xf5, 0x61, 0xfe, 0x9a, 0x64, 0x21, 0x76, + 0x84, 0xdf, 0xca, 0x0e, 0x61, 0x78, 0x5d, 0x71, 0x7a, 0xd2, 0x69, 0xec, + 0x7b, 0x06, 0x9b, 0x3f, 0x46, 0x44, 0x8c, 0xff, 0xb3, 0xbe, 0xe7, 0xed, + 0xad, 0x38, 0xb4, 0xcf, 0xb1, 0x28, 0xb1, 0x13, 0x1b, 0x20, 0x8a, 0x04, + 0x7d, 0x72, 0x56, 0xa7, 0x19, 0x27, 0xff, 0xcf, 0xfb, 0x3b, 0xa7, 0x1b, + 0xdd, 0x31, 0xe9, 0xc5, 0xa7, 0xf3, 0xad, 0x5c, 0xae, 0xe6, 0x2d, 0x1b, + 0x22, 0x37, 0xeb, 0x10, 0xcb, 0xcc, 0xef, 0x59, 0xc1, 0xf2, 0x2e, 0xa9, + 0xce, 0x3d, 0xe1, 0x75, 0x3f, 0xff, 0xc5, 0xaa, 0xd3, 0x3c, 0xd8, 0x06, + 0x7d, 0xfd, 0xd3, 0x7d, 0x69, 0xff, 0x06, 0xdb, 0x0b, 0x6b, 0x5b, 0xf5, + 0x69, 0xf8, 0x68, 0x3d, 0x53, 0xd6, 0x9d, 0x6d, 0xb6, 0xa5, 0x38, 0x26, + 0x02, 0x46, 0x2f, 0xe7, 0xfc, 0x34, 0xf0, 0xbf, 0x8e, 0xeb, 0xeb, 0x4f, + 0xf0, 0xd0, 0x3c, 0x3d, 0x81, 0xb2, 0xd0, 0xf4, 0xe5, 0x3d, 0x65, 0x3a, + 0x00, 0x12, 0xc4, 0xaf, 0x87, 0xf3, 0xfe, 0x11, 0x2d, 0xdd, 0x0b, 0xf9, + 0x37, 0x16, 0x9d, 0x6d, 0xb6, 0xa6, 0x21, 0x04, 0xfb, 0xfd, 0xaa, 0xea, + 0x62, 0x10, 0x18, 0xd6, 0xce, 0xb6, 0xdb, 0x53, 0x10, 0x7a, 0x13, 0x10, + 0x78, 0xc6, 0xb6, 0x47, 0xc4, 0x4c, 0xf5, 0xc6, 0x7f, 0xed, 0x33, 0xcb, + 0x99, 0xae, 0xb1, 0xd6, 0x86, 0x3e, 0xa7, 0x65, 0x13, 0xee, 0x37, 0x18, + 0xeb, 0x4f, 0x09, 0x30, 0x6b, 0x4e, 0x09, 0x80, 0xb4, 0x58, 0x6f, 0x0e, + 0x41, 0x3e, 0xeb, 0x69, 0x8e, 0x94, 0xf8, 0x68, 0x47, 0x12, 0x9b, 0x3a, + 0x94, 0xd6, 0xda, 0x94, 0x61, 0xfb, 0x5c, 0x9b, 0x84, 0x76, 0x8a, 0xcf, + 0xe1, 0x09, 0x81, 0xac, 0x75, 0x48, 0xc6, 0xf2, 0x19, 0x37, 0xb2, 0x62, + 0xa8, 0x6a, 0x4f, 0xfc, 0xc0, 0xbe, 0x67, 0xb4, 0x34, 0x1a, 0xd3, 0xff, + 0xed, 0x9f, 0x7b, 0x5b, 0xfa, 0x62, 0xd3, 0x09, 0x2d, 0x1a, 0x44, 0xb5, + 0x21, 0x4f, 0xeb, 0x84, 0x7b, 0xdb, 0x5b, 0x2d, 0x0c, 0xb9, 0x19, 0x8b, + 0x05, 0x1b, 0xaf, 0xe3, 0x9e, 0xa8, 0x64, 0xdc, 0x8a, 0x75, 0xb6, 0xda, + 0x94, 0xf0, 0x28, 0x7d, 0x48, 0xc5, 0xfc, 0xfc, 0x16, 0x7f, 0x31, 0xc5, + 0xa1, 0xe7, 0xbb, 0x73, 0x09, 0xff, 0xe1, 0xdb, 0xc6, 0xc0, 0x61, 0xed, + 0x58, 0x34, 0xb4, 0xff, 0xff, 0xff, 0x72, 0xe1, 0x5f, 0xc6, 0xa8, 0xb8, + 0x5a, 0xe9, 0x78, 0x79, 0x6a, 0x87, 0x8f, 0xcf, 0xe2, 0xd3, 0xff, 0xd8, + 0xc6, 0xf4, 0x5b, 0x7e, 0xe0, 0x65, 0xf5, 0xa7, 0x9d, 0x74, 0x07, 0x6b, + 0x43, 0xcf, 0xda, 0x93, 0xe7, 0xfe, 0xcf, 0x80, 0xe5, 0x70, 0xae, 0xd8, + 0xb4, 0xea, 0xf3, 0xdd, 0x68, 0x79, 0xf1, 0x52, 0x1c, 0xf1, 0x7b, 0x87, + 0x5a, 0x19, 0x52, 0x23, 0xc8, 0xb1, 0x46, 0xa3, 0x0c, 0x18, 0x41, 0xee, + 0x43, 0x3f, 0x79, 0xee, 0x43, 0xb3, 0xd6, 0x9f, 0xf5, 0x7f, 0xc8, 0xb5, + 0x95, 0xb0, 0x16, 0x9f, 0xff, 0x17, 0x18, 0xf9, 0xb7, 0x18, 0xe1, 0x7e, + 0x80, 0xb4, 0xff, 0x5d, 0xbf, 0xc6, 0x37, 0xa4, 0xb4, 0x3d, 0x11, 0xb7, + 0x55, 0x9f, 0xc4, 0x1f, 0x1b, 0x4d, 0x75, 0xa7, 0xfc, 0x34, 0xe6, 0xab, + 0xff, 0x2f, 0x56, 0x8c, 0x4e, 0x47, 0xa6, 0x25, 0x0c, 0xdf, 0x91, 0x9c, + 0xca, 0x79, 0xe1, 0x63, 0x8b, 0x4f, 0xbf, 0xee, 0x60, 0xad, 0x3f, 0xf7, + 0x9c, 0xb3, 0x4c, 0xee, 0xfa, 0x6d, 0x96, 0x8a, 0x3e, 0xfb, 0x93, 0x4f, + 0xf6, 0x0f, 0xf2, 0xf8, 0x5a, 0x5a, 0x7f, 0xf7, 0x2f, 0x8e, 0x69, 0x9d, + 0xe6, 0xb4, 0xcb, 0x4f, 0xda, 0xf4, 0x9d, 0xb7, 0xab, 0x46, 0x1f, 0xdd, + 0x25, 0xcf, 0x67, 0x0a, 0xeb, 0x4f, 0xce, 0x99, 0xc1, 0x74, 0xac, 0xdd, + 0x68, 0xb9, 0xed, 0xf0, 0x82, 0x3d, 0x4d, 0xbb, 0xa4, 0x23, 0x0b, 0x0e, + 0x3d, 0xcf, 0xff, 0x15, 0xf3, 0xde, 0x5c, 0x83, 0xd5, 0x09, 0x2d, 0x3f, + 0xff, 0x87, 0x99, 0xa2, 0x6e, 0x10, 0x19, 0xe6, 0xb6, 0xdb, 0x52, 0x9e, + 0xf7, 0x30, 0x34, 0xa7, 0xa8, 0x15, 0xc5, 0xa7, 0x3c, 0x77, 0x54, 0x43, + 0x33, 0xad, 0xb6, 0xd4, 0xa7, 0x60, 0xf5, 0x23, 0x17, 0xf3, 0xfe, 0xcb, + 0x33, 0xda, 0x78, 0xb5, 0x8b, 0x40, 0x0f, 0x9c, 0x4a, 0x67, 0xf3, 0xc7, + 0x39, 0xcd, 0xec, 0x5a, 0x75, 0x79, 0xd9, 0x68, 0x64, 0xf1, 0x6c, 0x61, + 0xf4, 0x8b, 0x62, 0x0f, 0xc2, 0xbe, 0x88, 0x77, 0x34, 0x9e, 0x2e, 0x13, + 0x2d, 0x3f, 0xb7, 0xcf, 0x80, 0x04, 0x2b, 0x4f, 0xff, 0x3c, 0xbd, 0xd0, + 0x57, 0xcf, 0xf6, 0xab, 0xaa, 0x88, 0x32, 0x7f, 0xf7, 0xe9, 0xde, 0x0f, + 0x74, 0x40, 0x0b, 0x8b, 0x43, 0x22, 0xa3, 0x75, 0xd8, 0xd2, 0x3f, 0x79, + 0x0d, 0xa9, 0xb5, 0xba, 0xd3, 0xdf, 0x2b, 0x7c, 0xeb, 0x41, 0x1b, 0xdb, + 0x8b, 0xcf, 0xef, 0x2c, 0xbf, 0x96, 0x7d, 0x96, 0x87, 0xa7, 0xb0, 0x51, + 0x8e, 0x5d, 0x97, 0x84, 0x13, 0xff, 0xcc, 0x36, 0x78, 0x70, 0x87, 0x3d, + 0xc1, 0x0d, 0x69, 0xcd, 0xf7, 0x16, 0x86, 0x5f, 0x98, 0xf5, 0x3b, 0x25, + 0x00, 0x75, 0x0c, 0xa5, 0x09, 0xd4, 0xa4, 0x4d, 0xd0, 0xfc, 0x94, 0x67, + 0xd4, 0x7a, 0xcb, 0xad, 0x3f, 0xfd, 0xd6, 0xbd, 0xd8, 0xf9, 0xc2, 0xbe, + 0xa9, 0x69, 0xfc, 0x22, 0xcf, 0x3b, 0x12, 0xd3, 0xfb, 0x84, 0xec, 0xb8, + 0xf6, 0x4a, 0x47, 0x5a, 0x7e, 0xc1, 0x0f, 0xec, 0x62, 0x3c, 0x30, 0x8c, + 0xe3, 0x13, 0x03, 0x24, 0xdb, 0xb9, 0x4f, 0x7f, 0x7d, 0x9e, 0xb4, 0xfe, + 0x17, 0x8e, 0x1d, 0xba, 0xb4, 0xe3, 0xfb, 0x8b, 0x43, 0x1f, 0x86, 0x12, + 0x51, 0x84, 0xee, 0x15, 0x8b, 0x4f, 0xad, 0x2f, 0x5d, 0x49, 0x69, 0xff, + 0x6f, 0xcb, 0x85, 0xf0, 0x1c, 0xae, 0xb4, 0xfe, 0xbb, 0x59, 0x82, 0x41, + 0xad, 0x3f, 0xf3, 0x0d, 0x69, 0x80, 0x6f, 0xde, 0xe9, 0x47, 0xa8, 0xb5, + 0xc4, 0x13, 0x99, 0x4f, 0xed, 0x9f, 0xb5, 0xcb, 0x8c, 0xb4, 0xe2, 0xda, + 0xeb, 0x4b, 0x58, 0x7a, 0x04, 0x69, 0x3f, 0xfe, 0xcf, 0x7c, 0x51, 0x65, + 0xda, 0xcc, 0x12, 0x0d, 0x69, 0xb2, 0xd5, 0xa1, 0x29, 0xfa, 0xe1, 0x37, + 0xd8, 0xe9, 0x42, 0x50, 0x94, 0x25, 0x09, 0x43, 0xcf, 0x7c, 0x82, 0x80, + 0x5b, 0xb8, 0x57, 0x90, 0x53, 0xa8, 0x54, 0xda, 0xc4, 0xa7, 0xea, 0xae, + 0x38, 0xc7, 0x4b, 0xc1, 0x6b, 0x27, 0x41, 0x28, 0x4a, 0x12, 0x87, 0x96, + 0x84, 0x15, 0x09, 0x42, 0x50, 0x94, 0x25, 0x09, 0x42, 0x51, 0x61, 0xbc, + 0xf4, 0x28, 0x82, 0x80, 0x15, 0x70, 0xa7, 0x61, 0x50, 0x94, 0x25, 0x0f, + 0x2d, 0x2e, 0x15, 0x09, 0x42, 0x50, 0x94, 0x25, 0x0f, 0x35, 0x00, 0x0a, + 0xe0, 0x53, 0xa8, 0x54, 0x25, 0x09, 0x42, 0x50, 0x94, 0x58, 0x6a, 0x03, + 0x0a, 0xf8, 0x56, 0x82, 0xa4, 0x1a, 0x50, 0x94, 0x25, 0x09, 0x42, 0x51, + 0xe9, 0xa8, 0xd8, 0x28, 0x01, 0x5b, 0x85, 0x42, 0x50, 0x94, 0x25, 0x3e, + 0xd3, 0x02, 0xf8, 0x94, 0x25, 0x0f, 0x3c, 0xee, 0x85, 0x68, 0x2a, 0x82, + 0x80, 0x4d, 0x2e, 0xa5, 0x09, 0x42, 0x50, 0x94, 0x25, 0x0f, 0x35, 0x1b, + 0x05, 0x10, 0x53, 0xb0, 0xa8, 0x4a, 0x12, 0x84, 0xa1, 0x28, 0x79, 0xa8, + 0xf4, 0x2b, 0x41, 0x42, 0x15, 0x2d, 0x25, 0x09, 0x42, 0x52, 0x7a, 0x50, + 0x96, 0xc5, 0x84, 0x25, 0x09, 0x42, 0x50, 0x94, 0x58, 0x7c, 0xcf, 0x0a, + 0x0c, 0x6b, 0xa3, 0x4e, 0x05, 0x00, 0x2b, 0x81, 0x52, 0xc4, 0xa1, 0x28, + 0x4a, 0x4f, 0x4a, 0x12, 0xd8, 0xb0, 0x84, 0xa1, 0x28, 0x63, 0xd2, 0x78, + 0x51, 0x0d, 0x7c, 0x68, 0xe1, 0x50, 0x94, 0x25, 0x09, 0x42, 0x50, 0x94, + 0x31, 0xb2, 0xd8, 0x2b, 0xe1, 0x47, 0x0a, 0x10, 0xa8, 0x4a, 0x12, 0x84, + 0xa3, 0xe5, 0xf5, 0xc2, 0xb8, 0x15, 0x09, 0x42, 0x50, 0x94, 0x1c, 0xbe, + 0x10, 0xae, 0x05, 0x48, 0xe9, 0x42, 0x50, 0x94, 0x00, 0xb4, 0xdc, 0x2a, + 0x12, 0x84, 0xa1, 0x28, 0x4a, 0x18, 0xd4, 0x38, 0x15, 0xa0, 0xad, 0xc2, + 0xa1, 0x97, 0xea, 0x6c, 0x71, 0x79, 0xef, 0xa5, 0x38, 0xb7, 0xd6, 0xcd, + 0x92, 0x09, 0x9b, 0xce, 0x7a, 0xe1, 0xcf, 0xe1, 0x18, 0x74, 0x8d, 0x1c, + 0x53, 0x28, 0x0e, 0xc5, 0xda, 0xec, 0xdc, 0x61, 0xdd, 0xe5, 0xdb, 0x2d, + 0xa9, 0x7e, 0x45, 0x81, 0x12, 0x3a, 0xa4, 0xcf, 0x9e, 0x2d, 0x97, 0x48, + 0xc9, 0xab, 0xce, 0x26, 0x3a, 0x53, 0xc5, 0xda, 0xfa, 0xd3, 0x8b, 0xdc, + 0x5a, 0x46, 0xd9, 0x11, 0xbf, 0x38, 0xd0, 0xd7, 0x08, 0x24, 0xce, 0xd9, + 0x48, 0xc1, 0x52, 0x80, 0xa7, 0xf6, 0x8a, 0xdf, 0x7e, 0xd6, 0x2d, 0x1b, + 0x1f, 0x6b, 0x87, 0x13, 0x01, 0x96, 0x9f, 0xbf, 0x42, 0x13, 0x01, 0x69, + 0xab, 0xeb, 0x48, 0x96, 0xe9, 0x6b, 0x2e, 0xad, 0x23, 0xad, 0x37, 0x91, + 0x89, 0x12, 0x9f, 0x15, 0xd2, 0x0d, 0x0f, 0x04, 0x21, 0x3f, 0xda, 0x63, + 0xe3, 0x8c, 0x36, 0x2d, 0x18, 0x89, 0x00, 0x2c, 0x4f, 0xc7, 0x2f, 0x2a, + 0x10, 0x2d, 0x3f, 0xb6, 0x06, 0x0e, 0x6d, 0x6a, 0xd3, 0xff, 0xcd, 0xe2, + 0xbf, 0xfa, 0x03, 0x1a, 0xdb, 0x6d, 0x5a, 0x19, 0x16, 0xb4, 0x5d, 0xb9, + 0xa4, 0x58, 0xd8, 0x75, 0xbe, 0x32, 0x8f, 0x61, 0x3f, 0xd2, 0xcd, 0x87, + 0x0a, 0x1c, 0x0e, 0x42, 0x00, 0xe4, 0xd5, 0x4b, 0x08, 0x18, 0xd2, 0x39, + 0x0d, 0x79, 0xf1, 0xc8, 0x7a, 0xcb, 0x4f, 0xe6, 0x1e, 0x50, 0xef, 0x8b, + 0x4f, 0xcf, 0x12, 0x7e, 0xb1, 0x69, 0xf5, 0x85, 0xf6, 0x7a, 0xd1, 0xf3, + 0xd2, 0x12, 0xb9, 0xff, 0x69, 0xbe, 0x01, 0xcb, 0x68, 0x0b, 0x4e, 0xcc, + 0x71, 0x69, 0x66, 0x8f, 0x60, 0x07, 0xb3, 0xf6, 0xff, 0xdc, 0xf4, 0x2b, + 0x4f, 0x1d, 0xb2, 0xc5, 0xa7, 0xd8, 0xec, 0x5b, 0x65, 0xa0, 0x8f, 0x26, + 0xe4, 0x33, 0xcc, 0x3c, 0xea, 0xd2, 0x6c, 0x4d, 0x60, 0x5e, 0x6e, 0x4f, + 0xc7, 0x7f, 0x22, 0x19, 0xed, 0xeb, 0xef, 0x5a, 0x79, 0xb7, 0x74, 0x2e, + 0x94, 0xf9, 0xe6, 0xb6, 0xdb, 0x56, 0x81, 0x3d, 0x2d, 0xc9, 0xe7, 0xab, + 0x87, 0xe2, 0xd1, 0xea, 0x2d, 0xf1, 0xd0, 0x04, 0x50, 0xca, 0xcd, 0xce, + 0x4a, 0x08, 0xf6, 0xb7, 0x8c, 0x22, 0x7d, 0xb8, 0x30, 0x7a, 0xb4, 0xfe, + 0xe6, 0x04, 0xce, 0x15, 0xd6, 0x8f, 0x9e, 0xd3, 0xb2, 0x79, 0xfe, 0xb0, + 0x71, 0xfa, 0xc0, 0x5d, 0x69, 0xef, 0x7d, 0x67, 0x6b, 0x4f, 0xff, 0x69, + 0x8f, 0xd6, 0xe6, 0x7f, 0xb5, 0x5d, 0x54, 0x5f, 0x13, 0xab, 0xf6, 0x2a, + 0x2f, 0xf8, 0x64, 0x41, 0xf5, 0x6a, 0x7f, 0xce, 0xa5, 0x83, 0xe8, 0x65, + 0x9f, 0x5a, 0x75, 0x6c, 0xea, 0xb4, 0xfd, 0x5c, 0xbb, 0x65, 0x8b, 0x43, + 0xb3, 0xcb, 0xb4, 0x7e, 0x71, 0x68, 0x96, 0x9f, 0xf9, 0xdf, 0x03, 0x61, + 0x7e, 0x68, 0x8e, 0xb4, 0xfd, 0x96, 0x85, 0xaa, 0xe2, 0xd3, 0xff, 0xee, + 0x04, 0xf6, 0x76, 0x42, 0x0f, 0x93, 0x1f, 0x12, 0x9e, 0xd0, 0x5b, 0x71, + 0x68, 0xb1, 0x14, 0xf8, 0x5d, 0xd5, 0x59, 0xb9, 0x8b, 0x4f, 0xbe, 0x56, + 0x51, 0xd5, 0x30, 0x9c, 0xf7, 0x37, 0x6a, 0x54, 0xc2, 0x73, 0x01, 0x95, + 0x40, 0x9c, 0xfe, 0x1a, 0x0f, 0x8d, 0xf0, 0x2a, 0x81, 0x39, 0xfd, 0x7c, + 0xd1, 0x5f, 0x7b, 0x15, 0x30, 0x9c, 0xd9, 0xea, 0xa6, 0x13, 0x9a, 0xdb, + 0x57, 0x30, 0x9c, 0x62, 0x69, 0x9b, 0x1a, 0x11, 0x71, 0xc8, 0xae, 0x7e, + 0xed, 0x06, 0xd2, 0x39, 0x71, 0x33, 0x09, 0x8c, 0x7c, 0xf2, 0x2d, 0x93, + 0xf7, 0xb6, 0x3c, 0x88, 0x25, 0x6b, 0xd4, 0x35, 0x78, 0x76, 0x72, 0x51, + 0x3c, 0xe1, 0x01, 0xd6, 0x9f, 0xe6, 0xdf, 0x82, 0x4e, 0xc2, 0x3a, 0xd3, + 0x87, 0x2f, 0x87, 0xad, 0xf1, 0xb8, 0x65, 0xde, 0x0f, 0x4e, 0x3b, 0x0a, + 0x8f, 0x91, 0x6a, 0x11, 0x7c, 0x97, 0x06, 0xee, 0x13, 0xf3, 0xef, 0x73, + 0xe4, 0x1a, 0xd3, 0xf6, 0x98, 0x21, 0xd8, 0x0b, 0x4f, 0xcd, 0xcc, 0x70, + 0x8e, 0xb4, 0xf8, 0x70, 0x2c, 0x71, 0x69, 0xfe, 0xd5, 0x1f, 0x44, 0xdb, + 0x3d, 0x69, 0xf8, 0x21, 0x7f, 0x95, 0x58, 0xb4, 0x11, 0xf4, 0x80, 0xe2, + 0x2c, 0x46, 0xe8, 0x96, 0x72, 0x12, 0x13, 0xfe, 0xca, 0xd3, 0x84, 0x3a, + 0xcd, 0x96, 0x86, 0x64, 0x14, 0x64, 0xf0, 0x21, 0x3f, 0xf9, 0xca, 0x2a, + 0x30, 0xb1, 0x35, 0x9f, 0xfc, 0x7e, 0x5c, 0x87, 0x67, 0xf0, 0xae, 0x4b, + 0x4f, 0xff, 0xe1, 0xcd, 0xb2, 0xf4, 0x3a, 0x26, 0xe6, 0xb7, 0xa3, 0xad, + 0x3f, 0x0e, 0xe7, 0x2d, 0x32, 0xd3, 0xff, 0xea, 0x1e, 0x55, 0x68, 0x5b, + 0x97, 0xb7, 0x34, 0xb4, 0xff, 0xac, 0xa1, 0xbf, 0x34, 0x4d, 0x75, 0xa7, + 0x7e, 0xf7, 0x5c, 0x40, 0x73, 0xff, 0xbe, 0xda, 0x6f, 0x19, 0xfe, 0xd5, + 0x75, 0x51, 0x01, 0x98, 0xd4, 0xc5, 0x23, 0x30, 0x26, 0x68, 0x02, 0x68, + 0x3c, 0x8c, 0x7a, 0x31, 0x39, 0x72, 0x8d, 0xb6, 0x7d, 0xc6, 0x07, 0x96, + 0x2d, 0x3c, 0x3c, 0x06, 0xeb, 0x4f, 0xff, 0xcf, 0xcb, 0x97, 0xfe, 0x47, + 0x0b, 0x9a, 0xcb, 0x78, 0xb4, 0x61, 0xff, 0x51, 0x14, 0xff, 0xff, 0x53, + 0xf0, 0x5c, 0x7e, 0x6c, 0xf0, 0x98, 0x06, 0xb6, 0xdb, 0x52, 0x86, 0x4c, + 0xcb, 0x50, 0xa7, 0x12, 0x09, 0xfc, 0xd9, 0xfe, 0xd5, 0x75, 0x51, 0x04, + 0xcf, 0xf3, 0x73, 0x3f, 0xda, 0xae, 0xaa, 0x2f, 0x99, 0xf1, 0x3b, 0x1c, + 0xbf, 0xa8, 0x80, 0xe9, 0xec, 0xfe, 0xdf, 0x5e, 0x5a, 0x2e, 0xe9, 0x69, + 0xff, 0x59, 0x5b, 0x79, 0x67, 0x06, 0x9d, 0xad, 0x3f, 0xec, 0xf5, 0x9e, + 0xda, 0xc0, 0x52, 0xd3, 0xad, 0xb6, 0xd4, 0xa7, 0xda, 0xce, 0xd7, 0xd2, + 0x31, 0x7f, 0x3f, 0xff, 0x9c, 0xfb, 0x3b, 0xf0, 0x1d, 0x78, 0x09, 0xcb, + 0xf8, 0xcd, 0xb8, 0xb4, 0x71, 0x14, 0x61, 0x1a, 0xc5, 0x89, 0xb2, 0x9d, + 0x0c, 0x61, 0xfd, 0x3f, 0xf5, 0x87, 0x6d, 0xae, 0x39, 0x60, 0x4f, 0x5a, + 0x7e, 0xf3, 0xe3, 0xf4, 0xdf, 0x5a, 0x7f, 0xf6, 0xc3, 0xe3, 0xba, 0x20, + 0x01, 0xbe, 0xf5, 0xa1, 0x8f, 0xf0, 0x23, 0x09, 0xff, 0x6f, 0x76, 0xc1, + 0xe3, 0xa3, 0x6f, 0x56, 0x9d, 0x6d, 0xb6, 0xa5, 0x3f, 0x82, 0x03, 0x0e, + 0x6d, 0xc4, 0x8c, 0x5f, 0xcf, 0xff, 0xce, 0x87, 0x8e, 0xe8, 0xbd, 0xf7, + 0x2d, 0xf1, 0xcb, 0xe5, 0x8b, 0x4a, 0xb4, 0x8a, 0xad, 0xd0, 0xa1, 0xeb, + 0x85, 0xde, 0xa0, 0x94, 0x73, 0x9f, 0x36, 0x3c, 0x30, 0xc4, 0x8b, 0x91, + 0x86, 0xcf, 0xfb, 0x45, 0x6f, 0xba, 0xce, 0x79, 0x62, 0xd3, 0xed, 0x67, + 0x1c, 0x7a, 0xd3, 0xfe, 0xce, 0x37, 0xef, 0xa6, 0x1e, 0x2e, 0x20, 0x89, + 0xfc, 0xd9, 0xfe, 0xd5, 0x75, 0x51, 0x04, 0x18, 0xf2, 0x67, 0xea, 0xd7, + 0xfe, 0x56, 0xad, 0x3f, 0x79, 0xfd, 0xb6, 0x85, 0xeb, 0x40, 0x9e, 0xfe, + 0xe5, 0xb1, 0xa4, 0xd0, 0x00, 0xe2, 0x30, 0xad, 0x9f, 0xe6, 0x1e, 0x05, + 0x9a, 0xad, 0x96, 0x9f, 0xfe, 0xab, 0x79, 0x9a, 0x62, 0xe0, 0xe6, 0x89, + 0x68, 0x7a, 0x21, 0x04, 0xe2, 0x7f, 0xb4, 0xdf, 0xe0, 0xb6, 0x6c, 0xb4, + 0xff, 0xff, 0x15, 0x8c, 0xfb, 0xff, 0xe5, 0xb3, 0x8f, 0xaf, 0x9f, 0x36, + 0x5a, 0x7b, 0x9a, 0xc1, 0xf5, 0x14, 0x1a, 0x37, 0x9f, 0x82, 0x3d, 0xed, + 0xad, 0x96, 0x86, 0x3e, 0xae, 0x1d, 0x4f, 0x13, 0x85, 0x6a, 0xd3, 0xff, + 0xed, 0xec, 0x16, 0xcd, 0x19, 0xb9, 0x9a, 0xdf, 0xeb, 0x4f, 0xb3, 0x97, + 0x0a, 0xfe, 0x9f, 0xc0, 0x44, 0x53, 0xf7, 0x74, 0x5e, 0x44, 0x1a, 0xd3, + 0xf1, 0xdb, 0xcf, 0xac, 0xb1, 0x69, 0xff, 0xff, 0xe2, 0xe5, 0x1e, 0x84, + 0x0f, 0x2b, 0xee, 0x7d, 0x13, 0x73, 0x5b, 0xd1, 0xd6, 0x8f, 0x51, 0xba, + 0x46, 0x14, 0x63, 0x3f, 0x08, 0x30, 0xed, 0xa5, 0xa7, 0x7c, 0x2b, 0xad, + 0x3f, 0xea, 0x1e, 0xe7, 0xfb, 0x55, 0xd5, 0x44, 0x23, 0x0c, 0x7c, 0xbb, + 0x8e, 0xcf, 0xfe, 0x6c, 0xd8, 0x2e, 0x10, 0xee, 0xfc, 0xba, 0xd3, 0xff, + 0xd5, 0x56, 0x05, 0x9a, 0xa3, 0x9a, 0xdb, 0x6d, 0x5a, 0x3d, 0x44, 0xeb, + 0x89, 0x53, 0xfd, 0xeb, 0x5e, 0xed, 0x66, 0x5a, 0xb4, 0x61, 0xef, 0xd1, + 0x2c, 0xeb, 0x6d, 0xb5, 0x29, 0xfe, 0xc0, 0x57, 0xf1, 0xde, 0x7a, 0x91, + 0x8b, 0xf9, 0xad, 0xb5, 0x29, 0xd6, 0xdb, 0x6a, 0x53, 0xf5, 0x59, 0xee, + 0x9a, 0xe9, 0x18, 0xbf, 0x81, 0x45, 0xe5, 0xa9, 0x1e, 0x46, 0xf3, 0xee, + 0x97, 0xa1, 0x1d, 0x23, 0x1b, 0x39, 0xd6, 0xdb, 0x6a, 0x53, 0xae, 0xdf, + 0x48, 0xc5, 0xfc, 0x80, 0x48, 0x82, 0xe2, 0xc4, 0xfd, 0xe8, 0xb3, 0xeb, + 0xab, 0x4f, 0xe0, 0xbf, 0xe9, 0x3b, 0x6f, 0x56, 0x9f, 0xfb, 0x54, 0xf1, + 0xa0, 0xdc, 0x12, 0x71, 0x69, 0xfb, 0x58, 0xef, 0x39, 0x75, 0xa3, 0xa7, + 0xe5, 0xc4, 0x48, 0xfa, 0x3d, 0xf4, 0x58, 0x30, 0xa9, 0x86, 0x4d, 0x4f, + 0x23, 0x1f, 0x9d, 0xb7, 0x9a, 0x1d, 0x32, 0xd3, 0xff, 0xb3, 0xcf, 0x42, + 0x0c, 0xeb, 0x69, 0x8e, 0xb4, 0xfd, 0xd2, 0xbe, 0x89, 0xc4, 0xa7, 0xf0, + 0xe6, 0xcf, 0xb9, 0x06, 0xb4, 0xf5, 0x57, 0xc3, 0x5a, 0x3c, 0x1e, 0xa5, + 0x86, 0x73, 0xcd, 0x47, 0x76, 0x94, 0xeb, 0x6d, 0xb5, 0x29, 0xff, 0xe0, + 0x98, 0x19, 0x83, 0xcd, 0x13, 0x0d, 0x89, 0x18, 0xbf, 0x96, 0x75, 0x13, + 0xa7, 0x41, 0x86, 0x4f, 0x81, 0xe5, 0x7b, 0x25, 0x14, 0x20, 0x75, 0x0c, + 0x29, 0xff, 0xf1, 0xf3, 0xac, 0x35, 0xcb, 0xfb, 0xbd, 0x7d, 0xeb, 0x4f, + 0x9b, 0x97, 0x77, 0x8b, 0x43, 0x1f, 0xef, 0x15, 0x67, 0xff, 0x89, 0xde, + 0x0f, 0xa3, 0x56, 0xe0, 0xd0, 0x6b, 0x4f, 0xff, 0xf7, 0xba, 0x2b, 0x71, + 0xd4, 0x8f, 0x5c, 0xbe, 0xef, 0xcd, 0xb8, 0xb4, 0x62, 0x2e, 0xe9, 0x3e, + 0x19, 0xb6, 0xc6, 0xb1, 0x25, 0xf1, 0xfa, 0xfb, 0x1a, 0xd6, 0x4e, 0x71, + 0x75, 0xdc, 0xa3, 0x82, 0x72, 0x17, 0x3f, 0x8c, 0xb0, 0xf0, 0x99, 0xd4, + 0x63, 0xf4, 0x5e, 0x08, 0x48, 0x0c, 0x63, 0xd7, 0x96, 0x07, 0xc9, 0x41, + 0x1b, 0xc3, 0x46, 0xd8, 0x6e, 0x4e, 0x76, 0xc2, 0xb4, 0xff, 0xf6, 0x09, + 0xb8, 0x19, 0x6d, 0x7d, 0x60, 0x29, 0x69, 0x6c, 0xf3, 0xeb, 0xf4, 0x72, + 0x7e, 0xd3, 0x85, 0xa2, 0x3a, 0xd3, 0xfe, 0x16, 0xe6, 0xa8, 0x15, 0xb5, + 0xd6, 0x9c, 0xe6, 0xff, 0x5a, 0x7f, 0xd4, 0x5f, 0xcd, 0x8d, 0x6d, 0xb6, + 0xad, 0x14, 0x7b, 0xf7, 0x1e, 0x9f, 0xfe, 0x16, 0x7f, 0x80, 0xb8, 0x43, + 0xbb, 0xf2, 0xeb, 0x46, 0x26, 0x77, 0xd2, 0xdd, 0x42, 0x70, 0x04, 0x33, + 0xf9, 0x85, 0xe3, 0x98, 0x05, 0xa7, 0xf3, 0xf3, 0xf7, 0x16, 0xb1, 0x69, + 0xff, 0xec, 0xe3, 0x0f, 0x73, 0x9a, 0x2f, 0x83, 0xab, 0x4f, 0xe6, 0x3d, + 0xef, 0x43, 0x62, 0xd3, 0xea, 0xbe, 0x0f, 0x16, 0x9d, 0xca, 0xf3, 0xad, + 0x3f, 0xb9, 0xa2, 0x30, 0x28, 0x96, 0x8f, 0x51, 0xe9, 0xa4, 0xd1, 0x32, + 0xe1, 0x2e, 0xe3, 0xf3, 0xfb, 0x70, 0x36, 0x8a, 0x9e, 0xb4, 0xff, 0x77, + 0x38, 0xce, 0xc8, 0x40, 0xb4, 0xff, 0xfd, 0xbb, 0x72, 0xf8, 0x3e, 0x3e, + 0xc5, 0xe8, 0xd3, 0xd6, 0x82, 0x44, 0x98, 0x9c, 0x4f, 0xff, 0xc3, 0x97, + 0xdf, 0xc6, 0x6a, 0x87, 0x1c, 0x7b, 0x6c, 0xb4, 0xff, 0xb2, 0xfb, 0xe7, + 0xfb, 0x55, 0xd5, 0x44, 0x0d, 0x3d, 0xcd, 0x53, 0xfc, 0x22, 0x98, 0x57, + 0x21, 0x93, 0x02, 0xc8, 0x63, 0xcf, 0xfa, 0xbf, 0x82, 0x0b, 0xf2, 0xfb, + 0x2d, 0x3f, 0xff, 0xff, 0x67, 0x35, 0x42, 0x1f, 0x8b, 0xfe, 0xab, 0x8e, + 0xf3, 0xdf, 0x18, 0x7a, 0x1d, 0x9e, 0xb8, 0x82, 0xe7, 0xfd, 0x54, 0x1f, + 0x6b, 0xdf, 0x01, 0xda, 0xb8, 0x82, 0xe7, 0xfe, 0xd1, 0x68, 0x98, 0x79, + 0xe0, 0x3b, 0x57, 0x10, 0x5c, 0xfe, 0x62, 0x1e, 0x78, 0x0e, 0xd5, 0xc4, + 0x17, 0x3f, 0x1f, 0x3d, 0xf0, 0x1d, 0xab, 0x88, 0x2e, 0x7f, 0xff, 0xa8, + 0x44, 0x8f, 0xe2, 0xfd, 0xd1, 0x7c, 0x83, 0xb3, 0x2d, 0x5c, 0x41, 0x73, + 0x6d, 0xe3, 0xd4, 0xe6, 0x7a, 0xa1, 0xf5, 0x3a, 0x42, 0x13, 0xf8, 0x65, + 0x59, 0x7a, 0x3e, 0x19, 0x46, 0x93, 0xf8, 0x98, 0x3b, 0xf2, 0xfb, 0x2d, + 0x3d, 0x5f, 0x05, 0x2d, 0x3f, 0xf6, 0x8b, 0x44, 0xc3, 0xcf, 0x01, 0xda, + 0xb8, 0x82, 0xe7, 0xf9, 0xcb, 0xf4, 0xac, 0xf0, 0x1d, 0xab, 0x88, 0x2e, + 0x7d, 0xcb, 0xb1, 0xfc, 0x75, 0x13, 0xe1, 0x29, 0xcf, 0xfe, 0xf1, 0xdd, + 0x17, 0x5b, 0x97, 0xf0, 0x1d, 0xab, 0x88, 0x2e, 0x7f, 0xff, 0xe1, 0x12, + 0x3f, 0x8d, 0xf3, 0xc5, 0xfb, 0xa2, 0xf9, 0x07, 0x66, 0x5a, 0xb8, 0x82, + 0xe3, 0x13, 0x24, 0xd9, 0x0e, 0x97, 0x67, 0xfb, 0x45, 0xf2, 0x0e, 0xcc, + 0xb5, 0x71, 0x05, 0xcf, 0xff, 0x55, 0x6c, 0xfb, 0xe8, 0xbd, 0xf5, 0xbe, + 0xc9, 0x4f, 0xfb, 0x1e, 0xfd, 0xef, 0xf1, 0xb3, 0xc9, 0x71, 0x05, 0xc7, + 0x51, 0xcf, 0xf4, 0x7b, 0xa8, 0x4f, 0xfb, 0xa5, 0xfe, 0x50, 0x2f, 0xe2, + 0xd5, 0xc4, 0x17, 0x3f, 0x68, 0xb5, 0xa2, 0x02, 0xa0, 0x0b, 0x9f, 0x60, + 0x3c, 0x07, 0x6a, 0xe2, 0x0b, 0x9b, 0x39, 0xd3, 0xf1, 0xd1, 0xcc, 0x6c, + 0x8e, 0xcb, 0xc2, 0xfe, 0x7e, 0x3e, 0x7b, 0xe0, 0x3b, 0x57, 0x10, 0x5c, + 0xff, 0xbb, 0xa2, 0xf9, 0x07, 0x66, 0x5a, 0xb8, 0x82, 0xe6, 0xcf, 0x14, + 0x88, 0xcb, 0x9f, 0xcf, 0xed, 0xc8, 0xf4, 0x3b, 0x3d, 0x71, 0x05, 0xcf, + 0xfb, 0x09, 0xde, 0x0e, 0x7b, 0x4f, 0x5c, 0x41, 0x67, 0x3c, 0x08, 0xf5, + 0x78, 0x03, 0xe6, 0xe0, 0x34, 0x18, 0xf8, 0xef, 0x18, 0xbf, 0x23, 0x1c, + 0xde, 0x16, 0xb6, 0xb7, 0xcf, 0x81, 0x70, 0x01, 0x95, 0x10, 0x59, 0x91, + 0x1b, 0x3f, 0xec, 0x7e, 0xa9, 0xee, 0xae, 0xbb, 0xd8, 0xb4, 0xe3, 0xb3, + 0xd6, 0x9f, 0x67, 0xba, 0xde, 0xd5, 0xa5, 0xfa, 0x3c, 0x51, 0x1b, 0x9c, + 0x5e, 0x58, 0xb4, 0xea, 0xf8, 0x16, 0x96, 0xcc, 0x6e, 0x38, 0x3b, 0x3f, + 0x65, 0x95, 0xa6, 0xf3, 0xad, 0x04, 0x8b, 0x5a, 0x5e, 0x12, 0x79, 0xd5, + 0xfb, 0x16, 0x98, 0x0c, 0xb4, 0xf1, 0x31, 0xf1, 0x68, 0x5a, 0x7e, 0xa0, + 0xf8, 0xdf, 0x02, 0xd1, 0xe9, 0xb7, 0x10, 0xa9, 0xff, 0xfc, 0xc5, 0xe8, + 0xb6, 0xfd, 0xe6, 0x77, 0xd6, 0xe8, 0x47, 0x5a, 0x60, 0x32, 0xd3, 0x30, + 0x6b, 0x4f, 0xf6, 0x72, 0xec, 0x70, 0xb1, 0xc5, 0xa7, 0xf5, 0xf3, 0x45, + 0x7d, 0xec, 0x5a, 0x6b, 0x6d, 0x4a, 0x7f, 0x87, 0x1d, 0xd3, 0xee, 0x5e, + 0xad, 0x1d, 0x4f, 0xcc, 0x87, 0x3e, 0x2a, 0x75, 0x6d, 0x10, 0x81, 0x89, + 0xd0, 0x15, 0x11, 0x6b, 0x9c, 0xda, 0x68, 0x10, 0xc4, 0xeb, 0x6d, 0xb5, + 0x29, 0x1d, 0x23, 0x17, 0xf3, 0xea, 0x72, 0xab, 0xe9, 0x19, 0x1b, 0x81, + 0x42, 0xf6, 0x7f, 0x6d, 0xc6, 0xcd, 0x65, 0x8b, 0x43, 0x36, 0x71, 0xb9, + 0x1b, 0x28, 0x68, 0x5b, 0x17, 0x94, 0x64, 0xdf, 0x4b, 0xd4, 0x78, 0xf5, + 0x49, 0x7f, 0x14, 0x6b, 0xc6, 0x81, 0xbc, 0xaf, 0xc7, 0x69, 0x73, 0xd9, + 0xd6, 0xe2, 0xd3, 0xd8, 0x7c, 0xe2, 0xd3, 0xff, 0x6b, 0x7b, 0x37, 0x36, + 0x8b, 0xb5, 0xf5, 0xa7, 0xaa, 0xc0, 0x9e, 0xb4, 0x32, 0x2a, 0xb6, 0x20, + 0xe1, 0x03, 0xaa, 0x3c, 0xfc, 0x3b, 0xfa, 0x7a, 0x7a, 0xd3, 0xff, 0xdb, + 0x3c, 0x5b, 0xc1, 0xc5, 0x83, 0xe1, 0x58, 0xb4, 0xfe, 0xbf, 0x1d, 0xe0, + 0xe7, 0xab, 0x43, 0x22, 0xd0, 0x8b, 0xa9, 0x4a, 0x75, 0x78, 0x09, 0x69, + 0xff, 0xe1, 0x7f, 0x8c, 0xf4, 0xaf, 0xb7, 0x37, 0xab, 0xad, 0x3e, 0xf1, + 0xa6, 0xfb, 0xd6, 0x8f, 0x08, 0x9a, 0xc1, 0xeb, 0x54, 0xe7, 0xeb, 0x1c, + 0x7f, 0x99, 0xfb, 0x16, 0x99, 0xf6, 0x2d, 0x3e, 0xd6, 0x13, 0xee, 0xb4, + 0xff, 0xf6, 0x98, 0x23, 0x70, 0x68, 0x17, 0xbb, 0x01, 0x29, 0xfc, 0x0c, + 0xff, 0x6a, 0xba, 0xb8, 0x81, 0x27, 0x70, 0xac, 0x5a, 0x1e, 0x8c, 0xff, + 0x49, 0xbe, 0x9e, 0x73, 0xe9, 0x9d, 0x5e, 0xb4, 0xf7, 0x2c, 0xa1, 0x5a, + 0x7b, 0x6b, 0xd7, 0x16, 0x8b, 0x0f, 0x73, 0x06, 0x74, 0x43, 0x3f, 0x86, + 0xad, 0xbd, 0xd8, 0x0b, 0x4e, 0xb6, 0xdb, 0x57, 0x57, 0xd4, 0xea, 0x1f, + 0x53, 0x57, 0xd0, 0xc6, 0xb2, 0x3d, 0x44, 0xb8, 0x17, 0xa7, 0xff, 0xd8, + 0x24, 0x19, 0xaf, 0xcd, 0x6f, 0x4f, 0x7b, 0x2d, 0x16, 0x1f, 0xc8, 0x64, + 0x93, 0xd9, 0xed, 0x3d, 0x69, 0xf5, 0x08, 0x91, 0xd6, 0x8f, 0x36, 0xad, + 0x79, 0xe6, 0xd9, 0x0e, 0x62, 0x84, 0xd5, 0x46, 0x7e, 0x02, 0x41, 0x21, + 0x9f, 0x17, 0x1b, 0x97, 0x4a, 0x7b, 0xad, 0xcb, 0xa5, 0x35, 0xb6, 0xa5, + 0x0f, 0x3d, 0xfc, 0x26, 0xb4, 0x86, 0x6c, 0xb5, 0x23, 0x1a, 0xf9, 0xff, + 0xf6, 0x71, 0xbb, 0x98, 0x3c, 0xd1, 0x30, 0xd8, 0xb4, 0x00, 0xfd, 0xed, + 0x26, 0x9f, 0xff, 0xcc, 0xec, 0x84, 0x1e, 0x39, 0x7c, 0xee, 0xa8, 0x79, + 0x75, 0xa7, 0xcf, 0xd6, 0xed, 0xf5, 0xa7, 0xfe, 0xa3, 0xfd, 0x9d, 0x93, + 0xb1, 0xde, 0xeb, 0x46, 0xc7, 0xdf, 0xa2, 0x89, 0xff, 0x66, 0x39, 0xfc, + 0xdc, 0xed, 0x75, 0xa1, 0xc3, 0xe1, 0xf9, 0x1c, 0xe7, 0xd7, 0xd6, 0x9f, + 0x3f, 0x39, 0x9e, 0xa5, 0x22, 0x70, 0xf0, 0xb8, 0x37, 0x3d, 0xc0, 0xf3, + 0x4b, 0x4c, 0x06, 0x5a, 0x60, 0x32, 0xd3, 0xed, 0xdf, 0x82, 0x62, 0x35, + 0x40, 0x15, 0x86, 0x44, 0x48, 0xa6, 0x4f, 0xff, 0x60, 0xd6, 0xd7, 0x0c, + 0x3a, 0xf1, 0xd3, 0x81, 0x69, 0xff, 0xb9, 0xac, 0x38, 0x4c, 0x0c, 0xdb, + 0x8b, 0x4f, 0xed, 0xba, 0x5f, 0x09, 0xbd, 0x5a, 0x78, 0x07, 0xa7, 0xf8, + 0x46, 0x6d, 0xd4, 0xf8, 0x8b, 0x3b, 0xcb, 0x36, 0x5a, 0x78, 0x1f, 0x2f, + 0xad, 0x0c, 0x88, 0x9a, 0x49, 0xb4, 0x7e, 0x7a, 0xfb, 0xed, 0xc5, 0xa7, + 0x5b, 0x6d, 0xa9, 0x4f, 0xb0, 0x31, 0xde, 0xe9, 0x18, 0xbf, 0x9f, 0x61, + 0xad, 0xb6, 0xd5, 0xa1, 0x8f, 0x87, 0x86, 0xf3, 0xff, 0x60, 0xe5, 0xfb, + 0xa2, 0xeb, 0x0a, 0xd3, 0xd6, 0xd0, 0xbd, 0x69, 0xfe, 0xe3, 0x6c, 0x0a, + 0xd6, 0x89, 0x69, 0x69, 0x68, 0xc3, 0xc8, 0xe9, 0xc4, 0xeb, 0x6d, 0xb5, + 0x29, 0xf8, 0xa8, 0x3d, 0x17, 0x12, 0x31, 0x7f, 0x3e, 0xc0, 0x66, 0x38, + 0xb4, 0xaf, 0xe1, 0x14, 0x3f, 0x40, 0x13, 0xb9, 0xb7, 0x7f, 0xa9, 0xa0, + 0xe4, 0x61, 0x52, 0x2d, 0x27, 0x70, 0x31, 0xc0, 0xcf, 0x57, 0xcb, 0x4b, + 0x43, 0x2f, 0x6e, 0x3c, 0x8f, 0x23, 0x32, 0x0d, 0x97, 0xf0, 0xb1, 0xd4, + 0x6e, 0xf4, 0x5f, 0x78, 0x52, 0x72, 0x3d, 0x9f, 0x22, 0xe9, 0xf9, 0x8f, + 0xff, 0xd7, 0x16, 0x9f, 0xfe, 0x23, 0xec, 0xfb, 0xf8, 0xc1, 0x1c, 0x20, + 0x2d, 0x1a, 0x3f, 0xbe, 0x16, 0x48, 0xce, 0x94, 0x2b, 0x0a, 0xdd, 0x39, + 0x4f, 0x9a, 0x50, 0x3c, 0xd6, 0x54, 0xd3, 0x9a, 0xd6, 0x4a, 0x4c, 0x7c, + 0xae, 0xcf, 0x65, 0xc7, 0xe5, 0x78, 0x10, 0x1c, 0x7a, 0x9d, 0x97, 0x31, + 0xb4, 0xbd, 0xa2, 0x96, 0x16, 0xe4, 0xfa, 0xa7, 0xe7, 0xa1, 0x0f, 0x2c, + 0x9f, 0x54, 0x98, 0xaa, 0xac, 0x5d, 0x41, 0x2f, 0xf8, 0x69, 0x19, 0xb7, + 0xad, 0xcb, 0xb9, 0x5a, 0xa1, 0xef, 0x1b, 0xe3, 0xb8, 0x7a, 0x5b, 0x0c, + 0x7f, 0x29, 0x72, 0x01, 0x52, 0x16, 0x5d, 0x63, 0x26, 0x83, 0x42, 0xbc, + 0x7d, 0xf8, 0xac, 0xc0, 0x9f, 0xc6, 0xcf, 0xf6, 0xab, 0xaa, 0x8b, 0x8e, + 0x7f, 0x1b, 0x3f, 0xda, 0xae, 0xaa, 0x2e, 0xb9, 0xff, 0x8d, 0x4f, 0x36, + 0x7f, 0xb5, 0x5d, 0x54, 0x4a, 0x30, 0xd0, 0xb2, 0xa4, 0x79, 0xcf, 0xa7, + 0xc1, 0x9f, 0x74, 0xf8, 0xa1, 0xf5, 0xe7, 0x40, 0xac, 0x57, 0xba, 0x5c, + 0xe3, 0x87, 0xce, 0xa7, 0xd3, 0xff, 0x8c, 0x7a, 0x79, 0xb3, 0xfd, 0xaa, + 0xea, 0xa2, 0x5a, 0x9f, 0x7f, 0xb5, 0x5d, 0x54, 0x46, 0xf3, 0xfe, 0xa7, + 0x9b, 0x3f, 0xda, 0xae, 0xaa, 0x25, 0xf9, 0x1b, 0x0f, 0xf0, 0xe6, 0x73, + 0xf8, 0xd9, 0xfe, 0xd5, 0x75, 0x51, 0x55, 0xce, 0x17, 0x5b, 0x56, 0x9f, + 0xe0, 0xf0, 0x73, 0x6e, 0x51, 0xd6, 0x9b, 0xd2, 0x5a, 0x7e, 0xcf, 0xf6, + 0xab, 0xaa, 0x89, 0x02, 0x3c, 0x1e, 0x71, 0x85, 0xa7, 0xd7, 0xa1, 0xad, + 0x96, 0x87, 0x9e, 0x56, 0xc4, 0x93, 0xfd, 0x9f, 0xb0, 0xec, 0x2f, 0xba, + 0xd3, 0xff, 0xb1, 0xcd, 0x51, 0xf9, 0xa6, 0xe6, 0xe7, 0x5a, 0x3d, 0x4e, + 0x54, 0x31, 0xfa, 0x86, 0x80, 0x91, 0x84, 0x71, 0x3f, 0xf0, 0xe5, 0x86, + 0xf2, 0x61, 0x1c, 0xe2, 0xd0, 0x64, 0x4a, 0xfd, 0x46, 0x7f, 0x1b, 0x3f, + 0xda, 0xae, 0xaa, 0x2c, 0x89, 0xfc, 0x6c, 0xff, 0x6a, 0xba, 0xa8, 0xb5, + 0xe7, 0xf1, 0xb3, 0xfd, 0xaa, 0xea, 0xa2, 0xe4, 0x9f, 0x7f, 0xb5, 0x5d, + 0x54, 0x5d, 0x93, 0xfe, 0xa7, 0x9b, 0x3f, 0xda, 0xae, 0xaa, 0x28, 0xe9, + 0x1b, 0x0f, 0xf0, 0xe6, 0x72, 0x7a, 0xd3, 0xef, 0xf6, 0xab, 0xaa, 0x8a, + 0x56, 0x7f, 0xff, 0xfb, 0x4d, 0x63, 0x68, 0x9d, 0xd7, 0xba, 0x23, 0x77, + 0x44, 0xe6, 0x9a, 0xcc, 0x5a, 0x67, 0x9b, 0x11, 0x66, 0xe1, 0x9c, 0xc6, + 0x3e, 0xc8, 0xf2, 0xa8, 0x61, 0xc5, 0x8b, 0xc0, 0xcf, 0x86, 0x8e, 0xc4, + 0xe5, 0x29, 0xa0, 0xe4, 0x60, 0x3e, 0xb9, 0xf7, 0x21, 0x74, 0xee, 0x31, + 0x99, 0xfe, 0x79, 0xb3, 0xfd, 0xaa, 0xea, 0xa2, 0x38, 0x9f, 0xe2, 0x36, + 0x7f, 0xb5, 0x5d, 0x54, 0x56, 0xb2, 0x35, 0x22, 0x1f, 0x88, 0x93, 0xff, + 0x8c, 0x7a, 0x79, 0xb3, 0xfd, 0xaa, 0xea, 0xa2, 0x5b, 0x9b, 0x36, 0x5a, + 0x7f, 0x7b, 0x87, 0x3b, 0x15, 0xd6, 0x8d, 0x8f, 0x20, 0x85, 0xa7, 0x6b, + 0xd7, 0xad, 0x39, 0xe7, 0xea, 0xd3, 0xff, 0xf6, 0xab, 0x60, 0x7b, 0xaa, + 0x7e, 0x5c, 0x77, 0xa0, 0x96, 0x85, 0x44, 0x37, 0x3f, 0xea, 0x79, 0xb3, + 0xfd, 0xaa, 0xea, 0xa2, 0x60, 0x9b, 0xb8, 0xb4, 0xdb, 0x81, 0x28, 0x23, + 0x59, 0xf1, 0x59, 0xfc, 0x39, 0xce, 0x37, 0xee, 0xb4, 0x8c, 0xc9, 0xca, + 0xd8, 0x45, 0xe8, 0xee, 0x0e, 0x75, 0x74, 0xe2, 0xfc, 0x75, 0x08, 0x82, + 0x71, 0xf7, 0xfa, 0x53, 0xfe, 0xa7, 0x9b, 0x3f, 0xda, 0xae, 0xaa, 0x26, + 0x39, 0x18, 0x8f, 0x98, 0xe3, 0xb3, 0xf3, 0x5f, 0xc7, 0x33, 0x4b, 0x4e, + 0x61, 0xb1, 0x69, 0xff, 0xff, 0xe7, 0xdf, 0x59, 0xbd, 0xb7, 0xd6, 0x77, + 0x2f, 0xe3, 0x36, 0x7d, 0xc8, 0x49, 0x69, 0xee, 0xd5, 0x75, 0x51, 0x58, + 0xcf, 0xfb, 0xcb, 0x38, 0x39, 0xbb, 0x0f, 0x16, 0x80, 0xd3, 0x35, 0xe9, + 0x76, 0xc3, 0x7f, 0x84, 0x07, 0x0b, 0x67, 0xff, 0x30, 0xf0, 0x3d, 0xd8, + 0x73, 0xad, 0xf5, 0xa7, 0xfb, 0xdd, 0x65, 0xf6, 0x7b, 0x69, 0x69, 0xfe, + 0x6d, 0x9e, 0xe3, 0xeb, 0xf6, 0x2d, 0x18, 0x7e, 0xa4, 0x75, 0x3f, 0xec, + 0xf7, 0xe5, 0xbd, 0x80, 0xbb, 0xd6, 0x9f, 0xff, 0xf8, 0x77, 0xb3, 0x3b, + 0xe0, 0x77, 0x75, 0xf1, 0x9b, 0x3f, 0x37, 0x10, 0x5d, 0x69, 0xfd, 0xe6, + 0xec, 0x71, 0xfe, 0x67, 0xec, 0x5a, 0x7f, 0xf6, 0x39, 0xaa, 0x3f, 0x34, + 0xdc, 0xdc, 0xeb, 0x4e, 0xd1, 0x1b, 0x15, 0x3c, 0xf5, 0x4b, 0x50, 0xbd, + 0x12, 0x0e, 0x20, 0xf9, 0x3e, 0x04, 0x85, 0x30, 0x40, 0x5a, 0x6d, 0xdc, + 0x5a, 0x7d, 0x43, 0x66, 0xf7, 0x5a, 0x00, 0x7a, 0xe2, 0x2f, 0x71, 0x89, + 0xd7, 0x0a, 0xeb, 0x4f, 0x6d, 0x63, 0x06, 0xb4, 0x7a, 0x78, 0x3b, 0x8e, + 0xcf, 0x8f, 0x82, 0xcf, 0x5a, 0x79, 0xbf, 0x42, 0xb4, 0xf5, 0xee, 0xc0, + 0x5a, 0x18, 0xf9, 0x74, 0x4b, 0x68, 0xfc, 0xf9, 0x9e, 0x0d, 0xde, 0xb4, + 0xe6, 0x17, 0xad, 0x0e, 0x1e, 0x17, 0x0a, 0x27, 0x68, 0x23, 0xad, 0x3f, + 0x31, 0xf0, 0x48, 0x35, 0xa5, 0x75, 0xa0, 0x8d, 0xdd, 0x15, 0xcc, 0x06, + 0x4a, 0x6b, 0x6d, 0x4a, 0x08, 0xd6, 0xda, 0x2b, 0x3f, 0xa9, 0xe3, 0x9c, + 0xd1, 0x24, 0x63, 0x43, 0x3d, 0xed, 0x6d, 0x75, 0xa7, 0x30, 0xb8, 0xb4, + 0xc2, 0xcb, 0x43, 0x86, 0xbf, 0xe3, 0x73, 0xff, 0x06, 0x43, 0xdb, 0xb1, + 0xeb, 0x6e, 0x2d, 0x38, 0x18, 0xea, 0xb4, 0x06, 0x7c, 0x5f, 0x44, 0x90, + 0x6b, 0x4d, 0xf0, 0xd6, 0x82, 0x35, 0x01, 0x08, 0xcf, 0xfe, 0xe3, 0x3c, + 0xb9, 0xac, 0xdb, 0x9b, 0xb2, 0xd2, 0xc5, 0xa0, 0x8f, 0x67, 0xc9, 0x2a, + 0x19, 0x3b, 0xfc, 0x52, 0x28, 0x42, 0x69, 0x2e, 0x9e, 0xa1, 0x69, 0x1d, + 0x69, 0x6b, 0x85, 0xd3, 0xb0, 0xa9, 0xf8, 0x0d, 0xac, 0x77, 0x4b, 0x4f, + 0xc5, 0xcc, 0xd5, 0x06, 0xb4, 0xeb, 0x6d, 0xb5, 0x29, 0xff, 0xb3, 0xbe, + 0xb5, 0x81, 0x66, 0xab, 0x64, 0x8c, 0x5f, 0xcf, 0x17, 0xdd, 0x7d, 0x5a, + 0x5f, 0x5a, 0x7c, 0x72, 0xe0, 0x29, 0x68, 0xb0, 0xf6, 0xfc, 0xe4, 0xbb, + 0x88, 0x4f, 0xee, 0x95, 0xc7, 0x1d, 0xd2, 0xd3, 0x67, 0x16, 0x8d, 0x8f, + 0x1f, 0x46, 0x53, 0xdc, 0x1c, 0x7a, 0xd3, 0xff, 0x8a, 0xfa, 0xcf, 0x47, + 0x1d, 0x8e, 0x5d, 0x68, 0xf9, 0xf4, 0xf0, 0x82, 0x46, 0x65, 0xf3, 0x0b, + 0x1b, 0xfd, 0x84, 0x4e, 0x38, 0x91, 0x1f, 0xd6, 0x0e, 0xdb, 0xa8, 0xff, + 0xe9, 0x98, 0x05, 0x0e, 0x81, 0x68, 0xa5, 0xde, 0x16, 0x1c, 0x7c, 0x0a, + 0x11, 0x93, 0xef, 0xf6, 0xab, 0xaa, 0x8a, 0xde, 0x7f, 0xd4, 0xf3, 0x67, + 0xfb, 0x55, 0xd5, 0x44, 0xe1, 0x23, 0x61, 0xfe, 0x1c, 0xce, 0x6d, 0xf4, + 0xb4, 0xfb, 0xfd, 0xaa, 0xea, 0xa2, 0xd1, 0x9f, 0x9a, 0x87, 0x7f, 0x71, + 0x69, 0x88, 0x35, 0xa7, 0xff, 0xd6, 0x37, 0x1b, 0xdf, 0xb6, 0xaa, 0xe3, + 0x80, 0x5a, 0x7f, 0xcd, 0xb5, 0x79, 0xf4, 0x35, 0xe7, 0xb1, 0x68, 0x64, + 0x4d, 0x69, 0x52, 0x58, 0xe2, 0x34, 0xaf, 0x0b, 0x09, 0x19, 0xd2, 0x4d, + 0xdb, 0x05, 0xce, 0x67, 0xa8, 0x7f, 0x4f, 0x76, 0xab, 0xaa, 0x8b, 0x6a, + 0x7e, 0xd3, 0x1d, 0x8b, 0xd5, 0xa3, 0xe7, 0xb3, 0xc2, 0xd9, 0xfe, 0x6f, + 0xf8, 0xbb, 0x6a, 0x89, 0x68, 0xc3, 0xda, 0xb4, 0x8a, 0x7e, 0x0e, 0xbf, + 0xee, 0xe2, 0xb4, 0xfa, 0xb6, 0x78, 0x57, 0x5a, 0x46, 0x0c, 0xf6, 0x1d, + 0x97, 0xc1, 0x91, 0x4a, 0x07, 0xa9, 0xfc, 0x6c, 0xff, 0x6a, 0xba, 0xa8, + 0xb9, 0x67, 0xf1, 0xb3, 0xfd, 0xaa, 0xea, 0xa2, 0xed, 0x86, 0x6d, 0x17, + 0x6c, 0x28, 0x7c, 0xa3, 0x2f, 0x61, 0xbe, 0x1c, 0xbd, 0x52, 0xa4, 0x02, + 0xf9, 0xe3, 0x69, 0xd4, 0x73, 0xa3, 0x1d, 0x2d, 0xc8, 0xf8, 0x7d, 0x3f, + 0x8d, 0x9f, 0xed, 0x57, 0x55, 0x15, 0x3c, 0xc5, 0xc5, 0xa7, 0xbb, 0x55, + 0xd5, 0x45, 0x73, 0x3f, 0x8d, 0x9f, 0xed, 0x57, 0x55, 0x16, 0x74, 0x7c, + 0xfa, 0xf4, 0x5b, 0x3f, 0xfd, 0x9c, 0x6b, 0xf8, 0xde, 0xfc, 0x67, 0x1c, + 0x25, 0xa7, 0xfd, 0xa2, 0xff, 0xba, 0xc2, 0x10, 0xd6, 0x9e, 0xa1, 0x6f, + 0x56, 0x9f, 0x7f, 0xb5, 0x5d, 0x54, 0x48, 0x53, 0xfc, 0x5b, 0x5f, 0x82, + 0xc7, 0xba, 0xd3, 0x3c, 0xd8, 0x7d, 0x60, 0x33, 0x9e, 0x74, 0xb4, 0xc7, + 0x5a, 0x7f, 0x66, 0xaf, 0x7c, 0xfd, 0x8b, 0x48, 0xde, 0xaa, 0x44, 0xc8, + 0x40, 0x11, 0x13, 0x8a, 0x67, 0x3e, 0xa8, 0x45, 0xdc, 0xb7, 0x84, 0xd3, + 0xff, 0x8c, 0x7a, 0x79, 0xb3, 0xfd, 0xaa, 0xea, 0xa2, 0x7c, 0x8e, 0xab, + 0x59, 0xf3, 0xca, 0x82, 0xd4, 0x20, 0xa7, 0xdf, 0xed, 0x57, 0x55, 0x15, + 0x94, 0xff, 0xa9, 0xe6, 0xcf, 0xf6, 0xab, 0xaa, 0x89, 0xba, 0x62, 0x36, + 0x1f, 0xe1, 0xcc, 0xe7, 0xff, 0x18, 0xf4, 0xf3, 0x67, 0xfb, 0x55, 0xd5, + 0x44, 0xad, 0x3f, 0xe2, 0xbe, 0xe7, 0x12, 0x77, 0xbd, 0x2d, 0x3f, 0x85, + 0xbf, 0x8e, 0xe8, 0xeb, 0x43, 0xcf, 0xc8, 0x90, 0x27, 0xdf, 0xed, 0x57, + 0x55, 0x12, 0xe4, 0xff, 0x15, 0x3f, 0x82, 0xc7, 0xba, 0xd3, 0xce, 0xc8, + 0x59, 0x69, 0xf1, 0x8f, 0x4f, 0x33, 0x22, 0xaf, 0x08, 0x74, 0x67, 0x46, + 0xd3, 0xff, 0x1a, 0x9e, 0x6c, 0xff, 0x6a, 0xba, 0xa8, 0x8e, 0xe7, 0xfb, + 0x9a, 0xc7, 0x9a, 0x86, 0xeb, 0x4f, 0xde, 0x60, 0xb7, 0x70, 0x56, 0x9b, + 0x7d, 0x2d, 0x3f, 0x3a, 0x23, 0x9a, 0xae, 0x2d, 0x3f, 0xb2, 0xdb, 0xb0, + 0xf2, 0xeb, 0x4f, 0xbf, 0xda, 0xae, 0xaa, 0x2a, 0x19, 0x9f, 0x8b, 0x4f, + 0x6b, 0x99, 0x62, 0xd3, 0xec, 0xb2, 0x85, 0xda, 0xd3, 0xff, 0xff, 0xf9, + 0x9f, 0xe3, 0x9a, 0x26, 0xbf, 0x8c, 0xdf, 0xac, 0xef, 0xc6, 0x7f, 0xf8, + 0xef, 0x7a, 0xd2, 0xd1, 0x88, 0xe2, 0x12, 0x2b, 0x94, 0x4f, 0xff, 0xf6, + 0xf5, 0xe7, 0x2b, 0x35, 0x45, 0xc6, 0xd1, 0x72, 0xf9, 0xea, 0xd2, 0x33, + 0xa2, 0xa9, 0xa9, 0xd2, 0x2f, 0xf3, 0x05, 0xec, 0x30, 0xc3, 0x2e, 0x99, + 0xf2, 0x1e, 0xfe, 0x45, 0xd3, 0xef, 0xf6, 0xab, 0xaa, 0x8a, 0xa2, 0x7f, + 0xd4, 0xf3, 0x67, 0xfb, 0x55, 0xd5, 0x44, 0xd7, 0x23, 0x61, 0xfe, 0x1c, + 0xce, 0x7f, 0x1b, 0x3f, 0xda, 0xae, 0xaa, 0x2a, 0xc9, 0xff, 0x1b, 0x4d, + 0xb7, 0x1b, 0x55, 0xe7, 0x5a, 0x7f, 0xe3, 0x53, 0xcd, 0x9f, 0xed, 0x57, + 0x55, 0x12, 0x24, 0xfb, 0xfd, 0xaa, 0xea, 0xa2, 0xd2, 0x9f, 0xf5, 0x3c, + 0xd9, 0xfe, 0xd5, 0x75, 0x51, 0x3e, 0xc8, 0xd8, 0x7f, 0x87, 0x33, 0x9f, + 0xfc, 0x63, 0xd3, 0xcd, 0x9f, 0xed, 0x57, 0x55, 0x14, 0x24, 0xfb, 0x45, + 0x63, 0x7d, 0x69, 0xf7, 0xfb, 0x55, 0xd5, 0x45, 0x1f, 0x3f, 0xe1, 0x67, + 0x18, 0x59, 0xcd, 0x32, 0xd3, 0xff, 0xf6, 0x7b, 0xa6, 0x08, 0xdc, 0x1a, + 0x05, 0xee, 0xc0, 0x4a, 0x7c, 0x63, 0xd3, 0xcc, 0xc8, 0xf1, 0xc2, 0x7e, + 0x19, 0xf9, 0x1e, 0x43, 0x32, 0x3f, 0xec, 0x8d, 0xc9, 0xea, 0xf8, 0x9e, + 0x1c, 0xaa, 0x1e, 0xc6, 0x19, 0xb1, 0x39, 0x1f, 0x38, 0x85, 0xa8, 0x65, + 0x80, 0x9f, 0x91, 0xa3, 0x4f, 0xe3, 0x67, 0xfb, 0x55, 0xd5, 0x44, 0x45, + 0x3f, 0x67, 0xfb, 0x55, 0xd5, 0x44, 0x57, 0x3f, 0xde, 0x73, 0x67, 0xfb, + 0x55, 0xd5, 0x45, 0x71, 0x06, 0x44, 0x05, 0x1c, 0x4f, 0x66, 0xab, 0x8b, + 0x4f, 0xec, 0x2f, 0x80, 0x1b, 0xdd, 0x69, 0x3a, 0x22, 0x7a, 0x77, 0x20, + 0x9f, 0xff, 0xb9, 0x72, 0xd5, 0x13, 0xc8, 0x79, 0xbe, 0xbf, 0x8b, 0x4f, + 0xbf, 0xda, 0xae, 0xaa, 0x29, 0xe9, 0xef, 0x03, 0x4f, 0x5a, 0x7d, 0x59, + 0xa2, 0x3a, 0xd2, 0xcb, 0x9e, 0x3b, 0xb2, 0x28, 0xf3, 0x13, 0x28, 0x62, + 0xcc, 0x59, 0x17, 0xa9, 0xff, 0xd4, 0xf3, 0x7a, 0x35, 0x6d, 0xee, 0xc0, + 0x5a, 0x0c, 0x88, 0xaf, 0x50, 0x27, 0x1b, 0xdc, 0x5a, 0x7f, 0xfd, 0xa6, + 0xfe, 0x3c, 0xab, 0x99, 0xe9, 0x6f, 0xd5, 0xa7, 0xcc, 0xf0, 0x6e, 0xf5, + 0xa7, 0xbb, 0x55, 0xd5, 0x45, 0x67, 0x0e, 0x1e, 0xaf, 0xca, 0x27, 0xeb, + 0x0e, 0xc2, 0xfb, 0xad, 0x39, 0xac, 0x25, 0xa7, 0xdc, 0x00, 0x42, 0xf5, + 0xa7, 0x6f, 0x57, 0x5a, 0x7d, 0x8e, 0x59, 0xb8, 0x16, 0x91, 0xbd, 0x4e, + 0x0b, 0x21, 0x53, 0xf2, 0x3d, 0x16, 0xd0, 0xd8, 0x94, 0x04, 0x39, 0x3f, + 0xf1, 0xa9, 0xe6, 0xcf, 0xf6, 0xab, 0xaa, 0x89, 0x16, 0x7e, 0xcf, 0xf6, + 0xab, 0xaa, 0x8b, 0x26, 0x7f, 0xf7, 0x2e, 0x15, 0xcd, 0x96, 0x10, 0xbe, + 0xeb, 0x41, 0x91, 0x07, 0x87, 0x13, 0xf8, 0xd9, 0xfe, 0xd5, 0x75, 0x51, + 0x6c, 0x4e, 0xcb, 0xe2, 0xd3, 0xdd, 0xaa, 0xea, 0xa2, 0xdb, 0x9e, 0xbd, + 0x95, 0xf5, 0xa3, 0xe7, 0x9f, 0xc2, 0xd9, 0x19, 0xe8, 0x8b, 0xc6, 0x99, + 0xd6, 0x68, 0x96, 0x9f, 0xf7, 0x99, 0xe6, 0x3a, 0x7e, 0x6b, 0x05, 0x80, + 0xb4, 0xfc, 0xc2, 0xff, 0x77, 0x3a, 0xd3, 0xef, 0xf6, 0xab, 0xaa, 0x8b, + 0xc2, 0x7b, 0x9e, 0x59, 0xa5, 0xa7, 0xfe, 0x60, 0x8f, 0xb3, 0xf9, 0x83, + 0xcb, 0xad, 0x3e, 0xc1, 0x03, 0xab, 0x2d, 0x3e, 0x60, 0xb7, 0xab, 0xad, + 0x3b, 0x8c, 0x05, 0xa4, 0x6f, 0x31, 0x39, 0xef, 0x34, 0x8e, 0x58, 0x9b, + 0x85, 0xdb, 0x19, 0xe8, 0x90, 0x08, 0xa2, 0x51, 0xc2, 0x89, 0xc5, 0x9b, + 0x2d, 0x3e, 0xff, 0x6a, 0xba, 0xa8, 0xbd, 0x27, 0xfc, 0x35, 0xc7, 0xb0, + 0x7c, 0x2b, 0x16, 0x9f, 0x61, 0xf5, 0x5e, 0xad, 0x23, 0x7a, 0x8b, 0x3c, + 0x1b, 0x08, 0xcd, 0xd4, 0xfa, 0x19, 0x94, 0x4b, 0x64, 0x2f, 0xb2, 0x51, + 0x3f, 0x49, 0x0a, 0x39, 0xef, 0xb7, 0x9e, 0x1a, 0x40, 0x22, 0x18, 0x64, + 0xef, 0x1e, 0x35, 0xb1, 0xa2, 0xcd, 0xbe, 0x96, 0x9e, 0xcd, 0x57, 0x16, + 0x9f, 0xd8, 0x5f, 0x00, 0x37, 0xba, 0xd2, 0x74, 0x44, 0xf4, 0xee, 0x41, + 0x3e, 0x2e, 0x36, 0xd6, 0x2d, 0x3e, 0xff, 0x6a, 0xba, 0xa8, 0x88, 0xe7, + 0xff, 0xb5, 0xbd, 0xef, 0x87, 0x1c, 0x77, 0xe1, 0x9d, 0x32, 0xd3, 0xfe, + 0x7b, 0x59, 0xfc, 0xe7, 0x0b, 0x8b, 0x4f, 0xf6, 0x7f, 0x2f, 0x94, 0x36, + 0x2d, 0x3f, 0xff, 0xcd, 0xaa, 0x2e, 0x36, 0x87, 0x39, 0xa2, 0xd6, 0x15, + 0xab, 0x4d, 0x61, 0xd2, 0x9a, 0xdb, 0x52, 0x9f, 0xf1, 0xbb, 0xa2, 0x73, + 0x4d, 0x61, 0x80, 0x6b, 0xed, 0x17, 0x9e, 0x76, 0xdc, 0x65, 0xa1, 0xe7, + 0xf4, 0x4b, 0x13, 0xf7, 0x9c, 0x73, 0x55, 0xc5, 0xa7, 0xe6, 0x0f, 0x04, + 0x83, 0x5a, 0x7e, 0xce, 0x3a, 0xf3, 0x34, 0xb4, 0x62, 0x22, 0x84, 0xbe, + 0xe5, 0x93, 0xff, 0xf7, 0x4d, 0xfa, 0x12, 0xe1, 0xbe, 0xdc, 0x1a, 0x02, + 0xd0, 0xe9, 0x2b, 0xfa, 0xf3, 0x1a, 0x98, 0xb3, 0x0a, 0x3a, 0x67, 0xb2, + 0xc9, 0x1f, 0x7c, 0xda, 0xa3, 0x0b, 0xe4, 0x2a, 0xdd, 0x97, 0x4f, 0xbf, + 0xda, 0xae, 0xaa, 0x22, 0xe9, 0xf7, 0x00, 0x10, 0xbd, 0x29, 0x1b, 0x0f, + 0x6e, 0x8c, 0xe0, 0xc9, 0x97, 0xbe, 0x30, 0x99, 0xff, 0x8f, 0x4f, 0x36, + 0x7f, 0xb5, 0x5d, 0x54, 0x4c, 0xd3, 0x5d, 0xeb, 0x4f, 0x9d, 0x3b, 0xa3, + 0x76, 0xe2, 0xd0, 0xe9, 0xcf, 0x23, 0xcd, 0x62, 0xd3, 0xb6, 0x6b, 0xad, + 0x3b, 0xa4, 0x75, 0xa6, 0xf2, 0xf3, 0x6b, 0x4f, 0xfe, 0xd3, 0x1e, 0xb9, + 0x9b, 0x00, 0xe1, 0x69, 0x69, 0xfb, 0x99, 0x66, 0x01, 0xc5, 0xa7, 0xfb, + 0xdf, 0x18, 0x35, 0xf0, 0x79, 0x2d, 0x1e, 0x6d, 0x1a, 0xa4, 0x3e, 0xe2, + 0x5e, 0x8b, 0xa7, 0xdd, 0xad, 0x13, 0x8b, 0x4f, 0xff, 0x81, 0xb3, 0xf5, + 0xbb, 0x7c, 0xda, 0x2e, 0xd7, 0xd6, 0x9f, 0xf9, 0xd4, 0xb8, 0x5d, 0x2f, + 0x79, 0x9d, 0x5a, 0x7f, 0xff, 0xb7, 0xa1, 0xe8, 0xe7, 0xba, 0xdf, 0xc6, + 0x99, 0xff, 0x6b, 0x16, 0x86, 0x4c, 0x1f, 0x15, 0xf4, 0x8d, 0x3f, 0xfe, + 0xf4, 0x86, 0xcc, 0xb9, 0x56, 0x8d, 0x6d, 0xb6, 0xa5, 0x3f, 0x6c, 0xfb, + 0x83, 0x77, 0x16, 0x9e, 0xed, 0x57, 0x55, 0x16, 0x7c, 0xff, 0x8b, 0x4c, + 0xf2, 0xe3, 0x72, 0xeb, 0x4f, 0xff, 0x17, 0xf5, 0x5e, 0x31, 0xd8, 0xe5, + 0xf0, 0x56, 0x9d, 0x6d, 0xb6, 0xa5, 0x3f, 0xea, 0x7d, 0xc7, 0x3b, 0xee, + 0x7d, 0x23, 0x17, 0xf3, 0xff, 0x36, 0xcf, 0x1d, 0xc1, 0xe3, 0xa7, 0x02, + 0xd3, 0xfc, 0xda, 0x2f, 0x46, 0xa9, 0xc5, 0xa7, 0xf8, 0x87, 0xd0, 0xbd, + 0xdf, 0x97, 0x5a, 0x3a, 0xa9, 0x9d, 0xc5, 0x8f, 0x98, 0x9c, 0xb7, 0x47, + 0x80, 0x6e, 0xba, 0x67, 0x12, 0x1d, 0x9b, 0xcf, 0xf7, 0xea, 0xdd, 0x13, + 0x7d, 0x96, 0x9f, 0xff, 0xff, 0x60, 0x2f, 0x98, 0x21, 0x85, 0xf1, 0x6c, + 0xd1, 0xae, 0x39, 0x61, 0xf0, 0xeb, 0x4f, 0x9d, 0xd6, 0x98, 0x25, 0xa3, + 0x11, 0x54, 0x50, 0x81, 0x9d, 0x76, 0xb1, 0x69, 0xde, 0x59, 0xa5, 0xa1, + 0xe7, 0xc1, 0x72, 0x6e, 0x0e, 0x4e, 0xd6, 0xf6, 0xad, 0x38, 0x1b, 0xf5, + 0x69, 0xdc, 0xc0, 0x96, 0x8f, 0x4f, 0x5f, 0x61, 0xe2, 0x1d, 0x9f, 0xbe, + 0x4e, 0xf0, 0x6e, 0xb4, 0xfb, 0x42, 0xd9, 0xf5, 0x27, 0xfc, 0x5f, 0x7e, + 0x7e, 0xcd, 0xeb, 0xd5, 0x44, 0x1a, 0x63, 0x4b, 0x3e, 0xae, 0x91, 0xd9, + 0x69, 0xf9, 0xfa, 0x1a, 0xdb, 0x8b, 0x4b, 0x3a, 0x7a, 0x42, 0x4b, 0x3f, + 0xf6, 0x0e, 0xdc, 0xcf, 0x95, 0x94, 0x75, 0xa6, 0x21, 0x5a, 0x7e, 0xab, + 0x79, 0x9a, 0xe3, 0x1e, 0xb8, 0x68, 0x70, 0xc9, 0xe1, 0xbd, 0x4b, 0x21, + 0x5e, 0x2f, 0xb3, 0xf7, 0xf3, 0xdb, 0xe9, 0x96, 0x9f, 0xff, 0xdb, 0x8d, + 0x07, 0xe0, 0x21, 0xae, 0x5c, 0xb6, 0xd1, 0x5a, 0xb4, 0xff, 0x8f, 0x9c, + 0xb8, 0x57, 0xa7, 0xe2, 0xd3, 0xf5, 0xee, 0xdf, 0xe3, 0x2c, 0x63, 0x7d, + 0x3d, 0xac, 0x27, 0x6b, 0x4f, 0xd7, 0x2f, 0xe6, 0xff, 0x5a, 0x09, 0x11, + 0x7a, 0x3c, 0xe1, 0x14, 0xc7, 0x74, 0x55, 0x17, 0xe4, 0xff, 0xcf, 0xe6, + 0xef, 0x79, 0xf9, 0xc6, 0x3a, 0xd3, 0xff, 0x16, 0x0e, 0x65, 0xfc, 0x3e, + 0xe7, 0x5a, 0x36, 0x44, 0x4d, 0xd1, 0xa7, 0x06, 0xdf, 0x5a, 0x7e, 0xf5, + 0x81, 0xfc, 0xd9, 0x68, 0x31, 0xe5, 0x1c, 0x72, 0x62, 0x71, 0x69, 0xdc, + 0x2b, 0x16, 0x9c, 0x2d, 0xd5, 0xa3, 0xc1, 0xe6, 0x1c, 0x58, 0x47, 0x21, + 0x93, 0xec, 0xf4, 0xbf, 0x21, 0x62, 0x4d, 0xd4, 0xdd, 0x3d, 0x5c, 0x6d, + 0x96, 0x9f, 0xff, 0xf3, 0x68, 0x8e, 0x67, 0x1f, 0x5f, 0x0b, 0x45, 0xb5, + 0xfa, 0x56, 0x2d, 0x0e, 0x22, 0x41, 0xd4, 0x86, 0x7e, 0xaf, 0xb6, 0x82, + 0xf5, 0x69, 0xeb, 0x6b, 0x2e, 0xb4, 0xff, 0x3c, 0xf4, 0xfe, 0x5d, 0x83, + 0x5a, 0x18, 0xf6, 0xa8, 0x86, 0x7b, 0x2e, 0x42, 0xb4, 0x52, 0x31, 0xc6, + 0x11, 0x1b, 0x90, 0x4f, 0x05, 0xbd, 0x5d, 0x69, 0xe0, 0x03, 0x36, 0x5a, + 0x7e, 0x75, 0xe6, 0x50, 0xf5, 0x69, 0xfe, 0xb8, 0x0b, 0x6d, 0x9f, 0x9c, + 0x5a, 0x7e, 0x6d, 0xae, 0x72, 0xf3, 0xad, 0x1f, 0x45, 0xde, 0x88, 0xb8, + 0x5c, 0xec, 0xea, 0x19, 0x31, 0xdc, 0x86, 0xf4, 0xce, 0x6e, 0xb4, 0x8e, + 0xb4, 0x6c, 0x6a, 0x1d, 0x01, 0x89, 0xfd, 0x55, 0xb3, 0xc5, 0xbd, 0x5a, + 0x7f, 0xf0, 0x93, 0x5c, 0x69, 0xfe, 0x2f, 0x9f, 0x5a, 0x7f, 0xff, 0xda, + 0x2d, 0xb9, 0x9b, 0x73, 0xff, 0x6e, 0x00, 0x21, 0x7e, 0xb1, 0x69, 0xef, + 0x7d, 0xa1, 0x5a, 0x7f, 0xd5, 0xae, 0x95, 0x78, 0xbd, 0x69, 0x69, 0xfe, + 0xd1, 0x50, 0x33, 0xda, 0x7a, 0xd0, 0xc9, 0xd3, 0x7a, 0x4f, 0x86, 0x5d, + 0x48, 0x26, 0xed, 0x11, 0x70, 0xfa, 0x7f, 0xec, 0x1d, 0xb9, 0x9f, 0x2b, + 0x28, 0xeb, 0x4f, 0xb3, 0x8f, 0x3e, 0x96, 0x9b, 0xc6, 0x2d, 0x3d, 0x71, + 0x63, 0xad, 0x0c, 0x6d, 0xf6, 0x17, 0x86, 0x46, 0x15, 0xd0, 0xf7, 0x5e, + 0x96, 0x96, 0x9a, 0xef, 0x5a, 0x00, 0x69, 0xb7, 0x10, 0x9f, 0xac, 0x71, + 0xfe, 0x67, 0xec, 0x5a, 0x7f, 0x07, 0xac, 0xe6, 0x8b, 0x8b, 0x4c, 0x5f, + 0x5a, 0x3c, 0xd9, 0xff, 0x11, 0xb6, 0x8c, 0xe7, 0xfe, 0xf5, 0xbd, 0xfb, + 0x70, 0xba, 0x5e, 0xad, 0x3f, 0x6b, 0x7b, 0x1c, 0xd1, 0x2d, 0x3c, 0x23, + 0x9c, 0x5a, 0x3d, 0x44, 0xa6, 0x91, 0x78, 0x61, 0x23, 0x79, 0xb6, 0xd4, + 0xb1, 0xd2, 0x30, 0x74, 0xe3, 0x8d, 0x0f, 0x0b, 0x10, 0xfd, 0x8c, 0xbb, + 0x25, 0x4b, 0x07, 0x08, 0x3d, 0xa3, 0x41, 0x28, 0x43, 0x39, 0x1f, 0x9f, + 0xd0, 0x0e, 0x5b, 0xa9, 0x63, 0x55, 0x0d, 0xd0, 0x43, 0xb8, 0x63, 0x51, + 0xbc, 0x7e, 0xbc, 0x8c, 0x96, 0xd5, 0x6f, 0x28, 0x50, 0x05, 0x0c, 0x88, + 0x33, 0x6e, 0xc1, 0xaa, 0xd7, 0x0e, 0x7f, 0xf1, 0x8f, 0x4f, 0x36, 0x7f, + 0xb5, 0x5d, 0x54, 0x4d, 0x93, 0xf8, 0xd9, 0xfe, 0xd5, 0x75, 0x51, 0x56, + 0xcf, 0xe7, 0xbf, 0x7e, 0x0b, 0x3d, 0x69, 0xec, 0xd5, 0x71, 0x69, 0x3a, + 0x38, 0x7a, 0x62, 0x67, 0x3e, 0xff, 0x6a, 0xba, 0xa8, 0xad, 0x27, 0xff, + 0xba, 0xdf, 0xc0, 0x51, 0xe9, 0xec, 0x5d, 0x5a, 0x7f, 0xf9, 0xf7, 0xc1, + 0x63, 0xd6, 0x73, 0x8c, 0x75, 0xa6, 0xe7, 0x19, 0x13, 0x14, 0x99, 0x3f, + 0x99, 0xd8, 0x5c, 0x16, 0x02, 0xd3, 0xf5, 0x99, 0xa2, 0xc3, 0xad, 0x3e, + 0x73, 0x3d, 0x3f, 0x9d, 0x69, 0xf8, 0x5a, 0xdd, 0x9f, 0x75, 0xa5, 0x8e, + 0x1e, 0xdb, 0xa9, 0x6c, 0xfe, 0x7e, 0x72, 0x84, 0x1b, 0xad, 0x23, 0x79, + 0x8a, 0x8e, 0xf0, 0xac, 0xa1, 0x97, 0xa2, 0xda, 0x34, 0x18, 0x43, 0xf0, + 0xae, 0x7f, 0x1b, 0x3f, 0xda, 0xae, 0xaa, 0x2c, 0x09, 0xf7, 0xfb, 0x55, + 0xd5, 0x44, 0xeb, 0x3f, 0xff, 0xb4, 0xd6, 0x7b, 0x87, 0x35, 0xef, 0x9a, + 0x2b, 0xef, 0x62, 0xd3, 0xe3, 0x1e, 0x9e, 0x6c, 0x44, 0xbb, 0x86, 0x73, + 0xf9, 0xf7, 0x30, 0x61, 0xb1, 0xd6, 0x9f, 0x7f, 0xb5, 0x5d, 0x54, 0x5b, + 0x33, 0xfe, 0x03, 0x5c, 0xda, 0x2e, 0xd7, 0xd6, 0x91, 0xb0, 0xfc, 0x28, + 0xce, 0x7b, 0xb5, 0x5d, 0x54, 0x5c, 0xd2, 0x3a, 0xd1, 0xf3, 0x7b, 0x69, + 0x6c, 0xce, 0x1d, 0x69, 0x1b, 0x0d, 0xcd, 0xa4, 0x53, 0xef, 0xf6, 0xab, + 0xaa, 0x8b, 0xbe, 0x7f, 0x3c, 0xd7, 0xff, 0xca, 0xd5, 0xa4, 0x6c, 0x3e, + 0x7a, 0x33, 0x9e, 0x30, 0x7e, 0x81, 0x68, 0x66, 0xfa, 0x0b, 0x2b, 0x78, + 0xae, 0x99, 0x6c, 0xaa, 0x52, 0xb3, 0x3e, 0xee, 0x78, 0x6a, 0xd2, 0x00, + 0x21, 0x4b, 0x78, 0x4e, 0x72, 0x11, 0xc1, 0x13, 0x4f, 0xaf, 0x73, 0xd3, + 0xd6, 0x9f, 0xfb, 0x6e, 0xef, 0x5b, 0x73, 0x30, 0x43, 0x5a, 0x70, 0xd1, + 0x98, 0xfb, 0x78, 0x4f, 0x3f, 0x86, 0xcc, 0x75, 0xe3, 0x06, 0xb4, 0xfb, + 0xfd, 0xaa, 0xea, 0xa2, 0x57, 0x9f, 0xf0, 0xd5, 0xbe, 0xe9, 0xac, 0xcb, + 0x56, 0x9f, 0xff, 0x9f, 0x84, 0x0c, 0x39, 0x72, 0xec, 0x7a, 0xdb, 0x8b, + 0x4f, 0x6f, 0x5f, 0x7a, 0xd3, 0xff, 0xf8, 0xb9, 0x47, 0xa1, 0x06, 0xad, + 0xa1, 0xb3, 0xa5, 0x62, 0xd1, 0xea, 0x20, 0xa8, 0x8a, 0x3d, 0x4c, 0xfb, + 0x10, 0x35, 0x0d, 0xb9, 0xff, 0x72, 0x9f, 0x7d, 0x37, 0xc2, 0xf5, 0x69, + 0xff, 0xb8, 0x4e, 0xcb, 0x8e, 0x3d, 0x87, 0x8b, 0x4e, 0xa7, 0x99, 0x95, + 0x31, 0x61, 0xb5, 0xe3, 0x71, 0xe1, 0xb0, 0x48, 0x13, 0xff, 0xb3, 0x46, + 0x1c, 0xf4, 0x87, 0x99, 0xa5, 0xa7, 0x71, 0xbd, 0x4a, 0x7f, 0xf6, 0xa8, + 0xb8, 0xcf, 0x2f, 0xd9, 0xb8, 0x12, 0x9f, 0xd5, 0xd3, 0x7b, 0xf7, 0x40, + 0xcc, 0x7c, 0xd7, 0x1c, 0x91, 0xba, 0xaf, 0xc8, 0xf2, 0xa4, 0xf8, 0xc4, + 0xee, 0x15, 0xb3, 0xff, 0x8c, 0x7a, 0x79, 0xb3, 0xfd, 0xaa, 0xea, 0xa2, + 0x61, 0x9f, 0xfc, 0x2c, 0xec, 0x5b, 0x63, 0x79, 0xb7, 0x4f, 0x77, 0xad, + 0x3f, 0xfb, 0xf4, 0x03, 0x07, 0xc7, 0xda, 0xd7, 0x7a, 0xd3, 0x61, 0xbd, + 0x45, 0x18, 0x4a, 0xf3, 0xff, 0x3a, 0x39, 0xb3, 0xf7, 0xae, 0x0b, 0x3d, + 0x69, 0xfc, 0xd5, 0x67, 0x03, 0xaf, 0xad, 0x3e, 0xaf, 0xbe, 0x8e, 0xb4, + 0x09, 0xec, 0xee, 0x67, 0x3f, 0x1d, 0xb0, 0x79, 0xe4, 0xb4, 0xae, 0xb4, + 0xf9, 0xb0, 0x79, 0xe4, 0xb4, 0xfd, 0xa2, 0xe5, 0xeb, 0x6f, 0x07, 0xcc, + 0xe1, 0x71, 0xc4, 0x27, 0xff, 0xfc, 0x39, 0x6b, 0xaf, 0x33, 0x5e, 0x07, + 0x2c, 0x2c, 0x1d, 0x9f, 0x75, 0xa7, 0xff, 0x9b, 0x83, 0x40, 0xd6, 0x17, + 0xbb, 0xf2, 0xeb, 0x4f, 0xce, 0xc2, 0xe0, 0xb0, 0x16, 0x9f, 0xfc, 0xdc, + 0x03, 0x3f, 0x36, 0xe5, 0x9b, 0xda, 0xb4, 0x31, 0xfe, 0x11, 0x7c, 0xfd, + 0x72, 0x0c, 0xf5, 0xc5, 0xa7, 0xff, 0xfd, 0xfc, 0x76, 0xc3, 0xcf, 0x01, + 0x0b, 0x6a, 0x8b, 0x8d, 0xcb, 0xad, 0x1b, 0x22, 0x6b, 0xe5, 0xb3, 0xde, + 0x1d, 0xba, 0xd8, 0xb4, 0x61, 0xe7, 0x06, 0x49, 0x3f, 0xfe, 0xd0, 0xe7, + 0x34, 0x59, 0xb7, 0x31, 0xdb, 0x5d, 0x69, 0xcc, 0x00, 0x96, 0x8c, 0x3f, + 0x11, 0x55, 0x9e, 0xa0, 0xbd, 0xc5, 0xa4, 0x6f, 0x31, 0x72, 0x5f, 0xd8, + 0x51, 0x76, 0x11, 0x0e, 0x2a, 0xfd, 0xc3, 0x50, 0xcb, 0x18, 0xc2, 0x39, + 0x09, 0x3d, 0xc8, 0x27, 0xff, 0xbe, 0x5b, 0x3e, 0xe5, 0xcd, 0x37, 0xf8, + 0xcb, 0x4f, 0xbf, 0xda, 0xae, 0xaa, 0x2a, 0xe9, 0xfe, 0x79, 0xb3, 0xfd, + 0xaa, 0xea, 0xa2, 0x3c, 0x91, 0xbd, 0x46, 0x36, 0x27, 0xd1, 0x9c, 0xfe, + 0x36, 0x7f, 0xb5, 0x5d, 0x54, 0x58, 0x33, 0xf8, 0xd9, 0xfe, 0xd5, 0x75, + 0x51, 0x65, 0x4f, 0xe3, 0x67, 0xfb, 0x55, 0xd5, 0x45, 0xa7, 0x3f, 0xb2, + 0xfe, 0x3f, 0xfd, 0xf4, 0xb4, 0xf7, 0x6a, 0xba, 0xa8, 0xb7, 0x27, 0xfe, + 0x6c, 0xfe, 0x0f, 0x8d, 0x66, 0xd6, 0xad, 0x1f, 0x3f, 0x2b, 0x96, 0xc8, + 0x56, 0x9f, 0xf8, 0xbd, 0xd6, 0xf5, 0x7c, 0xc1, 0x0d, 0x68, 0xc3, 0xd3, + 0xd0, 0x7c, 0xff, 0x30, 0xee, 0x76, 0xbf, 0x09, 0x69, 0xfd, 0x55, 0x67, + 0xad, 0x9f, 0x5a, 0x16, 0x9d, 0xbd, 0x71, 0x69, 0xb7, 0x65, 0xa1, 0xe6, + 0xcb, 0x83, 0x90, 0xb4, 0xf6, 0xdd, 0x2f, 0xad, 0x3c, 0x4c, 0x73, 0x62, + 0x22, 0x76, 0x3c, 0x39, 0x0f, 0x02, 0xa7, 0xfd, 0x4f, 0x36, 0x7f, 0xb5, + 0x5d, 0x54, 0x50, 0xf3, 0xf9, 0xbe, 0x06, 0xee, 0x1d, 0x69, 0xff, 0x8f, + 0x5e, 0xb7, 0xba, 0x21, 0x23, 0xad, 0x3f, 0xf6, 0xef, 0xd5, 0x7f, 0x6e, + 0x31, 0x58, 0xb4, 0xff, 0xb7, 0xf8, 0x3f, 0x43, 0xcc, 0x02, 0xd3, 0xfd, + 0x82, 0x35, 0xc0, 0x9b, 0xab, 0x4f, 0xab, 0x6b, 0xf0, 0x96, 0x8d, 0x1e, + 0xed, 0xcd, 0xa7, 0xe1, 0xf3, 0x33, 0xed, 0x75, 0xa7, 0xff, 0xec, 0xf7, + 0x4c, 0x11, 0xb8, 0x34, 0x0b, 0xdd, 0x80, 0x94, 0xfe, 0xd5, 0x01, 0xc1, + 0x6f, 0x56, 0x91, 0x9e, 0xba, 0x03, 0x90, 0xa8, 0xeb, 0xa9, 0x10, 0xb8, + 0x6f, 0xf8, 0x5d, 0x9d, 0x4f, 0x49, 0x34, 0x62, 0x04, 0x3b, 0xa3, 0x72, + 0x13, 0x7b, 0x91, 0x79, 0x18, 0x04, 0xb7, 0x3f, 0x8d, 0x9f, 0xed, 0x57, + 0x55, 0x17, 0x9c, 0x33, 0x3a, 0x55, 0xea, 0x7e, 0xc3, 0xe3, 0x27, 0x31, + 0xf6, 0x8d, 0xbb, 0xe6, 0xa7, 0x3e, 0xd1, 0xf0, 0xce, 0x9f, 0xbb, 0x87, + 0x3c, 0xfe, 0x36, 0x7f, 0xb5, 0x5d, 0x54, 0x52, 0xd3, 0xef, 0xf6, 0xab, + 0xaa, 0x8b, 0x0a, 0x7f, 0xfc, 0xd8, 0x3e, 0xb6, 0x9b, 0x6e, 0x60, 0xd1, + 0xd6, 0x91, 0xb1, 0x10, 0xa7, 0x33, 0x9f, 0xc6, 0xcf, 0xf6, 0xab, 0xaa, + 0x8b, 0x2e, 0x78, 0xc1, 0xf9, 0xad, 0xd3, 0x2d, 0x38, 0xc1, 0x1d, 0x69, + 0x5f, 0xa7, 0x9d, 0xb9, 0x8c, 0xfa, 0xbf, 0x81, 0xd8, 0xb4, 0xfe, 0xb3, + 0x6b, 0xb7, 0xbc, 0x25, 0xa7, 0xcd, 0x8e, 0xda, 0xeb, 0x4c, 0x2d, 0x47, + 0xb9, 0x69, 0xac, 0xf5, 0x3d, 0xfe, 0xad, 0x3f, 0x57, 0xdb, 0x95, 0xa5, + 0xa6, 0x6b, 0xad, 0x1e, 0x9f, 0x16, 0x88, 0x9d, 0x95, 0xcf, 0xc5, 0x57, + 0x05, 0xf7, 0x5a, 0x7f, 0xbe, 0xcf, 0xe5, 0x6a, 0xbd, 0x5a, 0x4f, 0x5a, + 0x4f, 0x5a, 0x4f, 0x5a, 0x18, 0xd8, 0x06, 0x20, 0x42, 0x13, 0xff, 0xee, + 0x5c, 0xbb, 0x83, 0xf6, 0x1c, 0xf7, 0x03, 0x5a, 0x5b, 0x2d, 0x35, 0xf7, + 0x5a, 0x30, 0xfe, 0xee, 0xa7, 0xc1, 0x19, 0x89, 0x96, 0x9f, 0x85, 0x8e, + 0x2d, 0x8b, 0x4c, 0xf6, 0x5a, 0x3d, 0x3d, 0x27, 0x05, 0x37, 0x29, 0x9f, + 0xff, 0xfe, 0xd3, 0x72, 0xe5, 0x7b, 0xb7, 0x34, 0x4d, 0xf0, 0x1e, 0x9e, + 0xfe, 0x06, 0xb4, 0xcc, 0x75, 0xa7, 0xff, 0xe7, 0xb0, 0xee, 0xf7, 0xb7, + 0x35, 0x87, 0x09, 0x80, 0xb4, 0xdd, 0x33, 0x2e, 0x24, 0xd8, 0x53, 0xec, + 0x21, 0x32, 0x11, 0xdd, 0x32, 0xf9, 0x6d, 0x34, 0x0c, 0x2a, 0x2f, 0x08, + 0x0e, 0x19, 0x79, 0x3e, 0x84, 0x2b, 0x3f, 0xf8, 0xc7, 0xa7, 0x9b, 0x3f, + 0xda, 0xae, 0xaa, 0x28, 0xc9, 0xf3, 0xb6, 0xfb, 0x01, 0x69, 0xf0, 0xd0, + 0x18, 0x25, 0xa4, 0x66, 0x3c, 0xed, 0x14, 0x43, 0xd7, 0xd5, 0x3f, 0x0c, + 0x33, 0x94, 0x54, 0xe6, 0xad, 0xe1, 0x95, 0xc8, 0x51, 0xcf, 0xfc, 0x6a, + 0x79, 0xb3, 0xfd, 0xaa, 0xea, 0xa2, 0x39, 0x9f, 0xfc, 0x63, 0xd3, 0xcd, + 0x9f, 0xed, 0x57, 0x55, 0x13, 0x94, 0xfe, 0x36, 0x7f, 0xb5, 0x5d, 0x54, + 0x59, 0x93, 0xfd, 0xe6, 0xaf, 0x70, 0xed, 0xb3, 0x1d, 0x69, 0xfe, 0x07, + 0xdb, 0x9a, 0xad, 0x9e, 0xb4, 0xfe, 0xcb, 0xff, 0xf9, 0xb5, 0xd6, 0x91, + 0x9d, 0x32, 0x2b, 0x59, 0x05, 0xe7, 0x33, 0xf8, 0xd9, 0xfe, 0xd5, 0x75, + 0x51, 0x6e, 0xcf, 0xfe, 0x31, 0xe9, 0xe6, 0xcf, 0xf6, 0xab, 0xaa, 0x8a, + 0x42, 0x7f, 0xe3, 0x53, 0xcd, 0x9f, 0xed, 0x57, 0x55, 0x12, 0x94, 0x58, + 0xaa, 0xb1, 0xc4, 0xf3, 0xaa, 0x82, 0x1d, 0x62, 0x77, 0xc3, 0xe7, 0x55, + 0x59, 0xff, 0x53, 0xcd, 0x9f, 0xed, 0x57, 0x55, 0x13, 0xb4, 0xff, 0xf8, + 0xbd, 0xdc, 0xec, 0xeb, 0x5e, 0x8b, 0x0e, 0x75, 0x69, 0x18, 0xe8, 0xa0, + 0xa4, 0x99, 0xff, 0xa8, 0xfa, 0xce, 0xd6, 0x98, 0x27, 0xad, 0x3f, 0xf0, + 0xe6, 0xaa, 0xcc, 0xb8, 0xd6, 0xcb, 0x4d, 0xe4, 0x6f, 0x51, 0x0b, 0x74, + 0x38, 0x3a, 0x38, 0x3c, 0xa1, 0x5b, 0x3e, 0xff, 0x6a, 0xba, 0xa8, 0x8b, + 0x27, 0xfd, 0x4f, 0x36, 0x7f, 0xb5, 0x5d, 0x54, 0x4b, 0xb3, 0xff, 0xf6, + 0x7b, 0xa6, 0x08, 0xdc, 0x1a, 0x05, 0xee, 0xc0, 0x4a, 0x46, 0xc4, 0x6b, + 0x9c, 0xcf, 0xc9, 0x26, 0x7f, 0xf1, 0x8f, 0x4f, 0x36, 0x7f, 0xb5, 0x5d, + 0x54, 0x4c, 0x53, 0xef, 0xf6, 0xab, 0xaa, 0x8a, 0xa6, 0x58, 0xb4, 0x61, + 0xe1, 0x06, 0x67, 0x3f, 0xff, 0xfb, 0xf5, 0xad, 0x9f, 0x9c, 0x36, 0xaa, + 0xff, 0x2b, 0x79, 0xa2, 0xfb, 0xd6, 0x83, 0x22, 0x6e, 0x88, 0xa7, 0xff, + 0x18, 0xf4, 0xf3, 0x67, 0xfb, 0x55, 0xd5, 0x44, 0xe9, 0x3f, 0x8a, 0xbe, + 0xcf, 0xa7, 0x6b, 0x4f, 0x9c, 0x67, 0x1a, 0xeb, 0x4f, 0xd6, 0xe1, 0x3b, + 0xc1, 0x5a, 0x08, 0xf5, 0x6e, 0x51, 0x38, 0xb4, 0xcb, 0x18, 0xd0, 0xcf, + 0xfd, 0xbd, 0xf8, 0xdb, 0x1b, 0x77, 0x08, 0xeb, 0x41, 0xcf, 0xcb, 0x45, + 0x53, 0xfe, 0xa7, 0x9b, 0x3f, 0xda, 0xae, 0xaa, 0x27, 0x79, 0xf7, 0x37, + 0xa3, 0x92, 0x52, 0x37, 0xa9, 0xd6, 0x64, 0x63, 0x47, 0x22, 0xa4, 0x99, + 0xff, 0xc6, 0x3d, 0x3c, 0xd9, 0xfe, 0xd5, 0x75, 0x51, 0x42, 0xcf, 0xfe, + 0x31, 0xe9, 0xe6, 0xcf, 0xf6, 0xab, 0xaa, 0x8a, 0x4a, 0x7f, 0xff, 0x65, + 0xcc, 0x16, 0x39, 0xaa, 0x3f, 0x34, 0xdc, 0xdc, 0xeb, 0x45, 0x8b, 0x9c, + 0x6f, 0x38, 0xec, 0x66, 0x5f, 0x22, 0x3c, 0xa0, 0x10, 0x24, 0xee, 0xab, + 0xe4, 0xab, 0x3f, 0x8d, 0x9f, 0xed, 0x57, 0x55, 0x11, 0x24, 0xff, 0xe3, + 0x1e, 0x9e, 0x6c, 0xff, 0x6a, 0xba, 0xa8, 0x97, 0xa7, 0xbf, 0xbf, 0x2e, + 0xb4, 0xfb, 0xe0, 0x39, 0x5d, 0x69, 0xc0, 0x6e, 0x2d, 0x3f, 0xf5, 0x39, + 0x76, 0x77, 0x5a, 0x6d, 0x9c, 0x5a, 0x3d, 0x45, 0x61, 0x11, 0xb8, 0x4f, + 0xf1, 0xb9, 0xfe, 0x2a, 0x1d, 0xf8, 0xe3, 0x06, 0xb4, 0xfb, 0xfd, 0xaa, + 0xea, 0xa2, 0x97, 0x9f, 0x0f, 0x2c, 0xcb, 0x56, 0x9f, 0xe0, 0x5f, 0xed, + 0xb3, 0xc8, 0xeb, 0x4f, 0xf5, 0x59, 0xe2, 0xcd, 0xdc, 0x6f, 0x3a, 0xd3, + 0x63, 0xd8, 0xfe, 0xf7, 0x39, 0x9f, 0xbe, 0x06, 0xee, 0x1d, 0x69, 0x9f, + 0x8b, 0x4e, 0x60, 0x5d, 0x68, 0x63, 0xdc, 0xf4, 0xb2, 0xd1, 0x59, 0xff, + 0x88, 0xf5, 0xe8, 0x4e, 0x3d, 0x87, 0x65, 0xa7, 0xae, 0x56, 0xf1, 0x68, + 0xd1, 0xf4, 0x71, 0x1a, 0x7f, 0xab, 0x54, 0xe1, 0x7d, 0xac, 0x5a, 0x7c, + 0xea, 0x47, 0x6b, 0xad, 0x3e, 0xa7, 0x79, 0xfb, 0xad, 0x26, 0xf4, 0xf4, + 0x4e, 0x53, 0x3e, 0x20, 0xee, 0x0e, 0xad, 0x23, 0x32, 0xb6, 0x9f, 0x4f, + 0xb0, 0xe7, 0x63, 0x37, 0x21, 0x47, 0xa8, 0x44, 0xd4, 0x26, 0x2e, 0x45, + 0xc8, 0x44, 0xf9, 0x14, 0x4f, 0xe3, 0x67, 0xfb, 0x55, 0xd5, 0x45, 0x39, + 0x3f, 0xe2, 0xfd, 0x5f, 0x59, 0x83, 0xd5, 0xa7, 0xfe, 0x0c, 0xb6, 0xb8, + 0xe7, 0xba, 0xab, 0x56, 0x9f, 0xf6, 0x69, 0xbf, 0x86, 0xb6, 0xdb, 0x52, + 0x98, 0xad, 0x5a, 0x6b, 0x0d, 0xea, 0x35, 0xf8, 0x77, 0xe4, 0x8c, 0xea, + 0x81, 0x3e, 0xff, 0x6a, 0xba, 0xa8, 0xaf, 0x27, 0xff, 0xec, 0xf7, 0x4c, + 0x11, 0xb8, 0x34, 0x0b, 0xdd, 0x80, 0x94, 0x8d, 0x88, 0x8e, 0xf2, 0x33, + 0x9f, 0xf8, 0xd4, 0xf3, 0x67, 0xfb, 0x55, 0xd5, 0x44, 0x8f, 0x38, 0x80, + 0xcb, 0x4e, 0xee, 0x1d, 0x23, 0x17, 0x53, 0xef, 0xf6, 0xab, 0xaa, 0x89, + 0x22, 0x78, 0xd4, 0xf3, 0x31, 0xee, 0xe1, 0x54, 0xff, 0xc6, 0xa7, 0x9b, + 0x3f, 0xda, 0xae, 0xaa, 0x24, 0xa9, 0xf7, 0xfb, 0x55, 0xd5, 0x45, 0xe3, + 0x3e, 0xbd, 0xf9, 0x9d, 0x5a, 0x7f, 0x9e, 0x6c, 0xff, 0x6a, 0xba, 0xa8, + 0x93, 0x64, 0x6c, 0x44, 0xf7, 0xcc, 0xe8, 0x9e, 0x19, 0x93, 0x1f, 0x61, + 0xf3, 0xe7, 0x1a, 0xbd, 0x85, 0x51, 0x46, 0x2a, 0xe4, 0x37, 0xce, 0x57, + 0xa8, 0x63, 0x70, 0xdf, 0x78, 0x76, 0xc3, 0x47, 0xe7, 0x77, 0x64, 0x76, + 0xaf, 0x96, 0x25, 0xec, 0xa4, 0x1c, 0xbc, 0xb8, 0xe0, 0xe5, 0x34, 0xf6, + 0xd6, 0x40, 0xed, 0x48, 0xda, 0x29, 0xeb, 0x6f, 0x3c, 0xa0, 0xf7, 0x2b, + 0x57, 0x0f, 0xd3, 0x19, 0x4f, 0x8b, 0x3a, 0x1d, 0x4f, 0x5f, 0x55, 0x6a, + 0xfe, 0x09, 0xc2, 0x87, 0x42, 0x17, 0xc3, 0x49, 0x2b, 0xbd, 0x2b, 0xb7, + 0x96, 0x91, 0xbb, 0x7a, 0xcf, 0x05, 0xdd, 0x20, 0x12, 0xd9, 0x63, 0x9e, + 0x51, 0xf7, 0x85, 0x3b, 0xca, 0xeb, 0x4a, 0x15, 0x80, +}; + +static const unsigned kPreloadedHSTSBits = 129955; + +static const unsigned kHSTSRootPosition = 129364; diff --git a/src/common/src/tpkp_common.cpp b/src/common/src/tpkp_common.cpp new file mode 100644 index 0000000..5c0d9ac --- /dev/null +++ b/src/common/src/tpkp_common.cpp @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd 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. + */ +/* + * @file tpkp_common.cpp + * @author Kyungwook Tak (k.tak@samsung.com) + * @version 1.0 + * @brief Https Public Key Pinning common implementation. + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "net/http/transport_security_state.h" +#include "net/http/transport_security_state_static.h" +#include "url/third_party/mozilla/url_parse.h" + +#include "tpkp_common.h" + +namespace { + +template +inline size_t _arraySize(const T &t) +{ + return sizeof(t) / sizeof(*t); +} + +inline std::string _toLower(const std::string &host) +{ + std::string lowerHost; + std::for_each( + host.begin(), + host.end(), + [&lowerHost](const std::string::value_type &ch) + { + lowerHost.push_back(std::tolower(ch)); + }); + + return lowerHost; +} + +} // anonymous namespace + +namespace TPKP { + +pid_t getThreadId() +{ + return syscall(SYS_gettid); +} + +Exception::Exception(tpkp_e code, const std::string &message) + : m_code(code) + , m_message(message) +{} + +const char *Exception::what(void) const noexcept +{ + return m_message.c_str(); +} + +tpkp_e Exception::code(void) const noexcept +{ + return m_code; +} + +tpkp_e ExceptionSafe(const std::function &func) +{ + try { + func(); + return TPKP_E_NONE; + } catch (const Exception &e) { + SLOGE("Exception: %s", e.what()); + return e.code(); + } catch (const std::bad_alloc &e) { + SLOGE("bad_alloc std exception: %s", e.what()); + return TPKP_E_MEMORY; + } catch (const std::exception &e) { + SLOGE("std exception: %s", e.what()); + return TPKP_E_STD_EXCEPTION; + } catch (...) { + SLOGE("Unhandled exception occured!"); + return TPKP_E_INTERNAL; + } +} + +class Context::Impl { +public: + Impl() = delete; + virtual ~Impl(); + explicit Impl(const std::string &url); + + void addPubkeyHash(HashAlgo algo, const RawBuffer &hashBuf); + bool checkPubkeyPins(void); + bool hasPins(void); + +private: + std::string m_url; + std::string m_host; + net::PreloadResult m_preloaded; + HashValueVector m_hashes; + + bool LoadPreloadedPins(void); + bool HashesIntersect(const char *const *hashesArr); + + class HashValuesEqual { + public: + explicit HashValuesEqual(const char *chash); + bool operator()(const HashValue &other) const; + private: + const char *m_chash; + }; +}; + +Context::Impl::Impl(const std::string &url) : m_url(url) +{ + url::Parsed parsed; + url::ParseStandardURL(m_url.c_str(), m_url.length(), &parsed); + TPKP_CHECK_THROW_EXCEPTION(parsed.host.is_valid(), + TPKP_E_INVALID_URL, "Failed to parse url: " << url); + + m_host = _toLower(m_url.substr( + static_cast(parsed.host.begin), + static_cast(parsed.host.len))); + + SLOGD("HPKP ready to check on host[%s]", m_host.c_str()); + + LoadPreloadedPins(); + if (!m_preloaded.has_pins) { + SLOGD("no pins on static pubkey list."); + return; + } +} + +Context::Impl::~Impl() {} + +void Context::Impl::addPubkeyHash(HashAlgo algo, const RawBuffer &hashBuf) +{ + m_hashes.emplace_back(algo, hashBuf); +} + +bool Context::Impl::checkPubkeyPins(void) +{ + if (!hasPins()) { + SLOGD("no pins on static pubkey list."); + return true; + } + + const Pinset &pinset = kPinsets[m_preloaded.pinset_id]; + + if (HashesIntersect(pinset.rejected_pins)) { + SLOGE("pubkey is in rejected pin!"); + return false; + } + + if (!HashesIntersect(pinset.accepted_pins)) { + SLOGE("pubkey cannot be found in accepted pins!"); + return false; + } + + SLOGD("pubkey is pinned one!"); + + return true; +} + +bool Context::Impl::hasPins(void) +{ + return m_preloaded.has_pins; +} + +bool Context::Impl::LoadPreloadedPins(void) +{ + m_preloaded.has_pins = false; + if (!net::DecodeHSTSPreload(m_host, &m_preloaded)) + return false; + + size_t arrsize = _arraySize(kPinsets); + if (m_preloaded.pinset_id >= static_cast(arrsize)) + return false; + + return true; +} + +bool Context::Impl::HashesIntersect(const char *const *hashesArr) +{ + if (!hashesArr) + return false; + + for (; *hashesArr != nullptr; hashesArr++) { + if (std::find_if( + m_hashes.begin(), + m_hashes.end(), + HashValuesEqual{*hashesArr}) != m_hashes.end()) { + SLOGD("hash intersect found!"); + return true; + } + } + + return false; +} + +Context::Impl::HashValuesEqual::HashValuesEqual(const char *chash) : m_chash(chash) {} + +bool Context::Impl::HashValuesEqual::operator()(const HashValue &other) const +{ + size_t len = other.hash.size(); + + /* + * hash from decode preloaded value is base64 encoded, + * so it can be get length by strlen. + */ + if (strlen(m_chash) != len) + return false; + + for (size_t i = 0; i < len; i++) { + if (m_chash[i] != other.hash[i]) + return false; + } + + return true; +} + +Context::Context(const std::string &url) : pImpl(new Impl{url}) {} +Context::~Context() {} + +void Context::addPubkeyHash(HashAlgo algo, const RawBuffer &hashBuf) +{ + SLOGD("add public key hash of algo[%d]", algo); + pImpl->addPubkeyHash(algo, hashBuf); +} + +bool Context::checkPubkeyPins(void) +{ + return pImpl->checkPubkeyPins(); +} + +bool Context::hasPins(void) +{ + return pImpl->hasPins(); +} + +} diff --git a/src/common/url/third_party/mozilla/url_parse.h b/src/common/url/third_party/mozilla/url_parse.h new file mode 100644 index 0000000..22747db --- /dev/null +++ b/src/common/url/third_party/mozilla/url_parse.h @@ -0,0 +1,393 @@ +/* + * Copyright 2014 The Chromium Authors. 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. + */ +#pragma once + +#include + +#include "base/strings/string16.h" +#include "url/url_export.h" + +namespace url { + +// Deprecated, but WebKit/WebCore/platform/KURLGooglePrivate.h and +// KURLGoogle.cpp still rely on this type. +typedef base::char16 UTF16Char; + +// Component ------------------------------------------------------------------ + +// Represents a substring for URL parsing. +struct Component { + Component() : begin(0), len(-1) {} + + // Normal constructor: takes an offset and a length. + Component(int b, int l) : begin(b), len(l) {} + + int end() const { + return begin + len; + } + + // Returns true if this component is valid, meaning the length is given. Even + // valid components may be empty to record the fact that they exist. + bool is_valid() const { + return (len != -1); + } + + // Returns true if the given component is specified on false, the component + // is either empty or invalid. + bool is_nonempty() const { + return (len > 0); + } + + void reset() { + begin = 0; + len = -1; + } + + bool operator==(const Component& other) const { + return begin == other.begin && len == other.len; + } + + int begin; // Byte offset in the string of this component. + int len; // Will be -1 if the component is unspecified. +}; + +// Helper that returns a component created with the given begin and ending +// points. The ending point is non-inclusive. +inline Component MakeRange(int begin, int end) { + return Component(begin, end - begin); +} + +// Parsed --------------------------------------------------------------------- + +// A structure that holds the identified parts of an input URL. This structure +// does NOT store the URL itself. The caller will have to store the URL text +// and its corresponding Parsed structure separately. +// +// Typical usage would be: +// +// Parsed parsed; +// Component scheme; +// if (!ExtractScheme(url, url_len, &scheme)) +// return I_CAN_NOT_FIND_THE_SCHEME_DUDE; +// +// if (IsStandardScheme(url, scheme)) // Not provided by this component +// ParseStandardURL(url, url_len, &parsed); +// else if (IsFileURL(url, scheme)) // Not provided by this component +// ParseFileURL(url, url_len, &parsed); +// else +// ParsePathURL(url, url_len, &parsed); +// +struct URL_EXPORT Parsed { + // Identifies different components. + enum ComponentType { + SCHEME, + USERNAME, + PASSWORD, + HOST, + PORT, + PATH, + QUERY, + REF, + }; + + // The default constructor is sufficient for the components, but inner_parsed_ + // requires special handling. + Parsed(); + Parsed(const Parsed&); + Parsed& operator=(const Parsed&); + ~Parsed(); + + // Returns the length of the URL (the end of the last component). + // + // Note that for some invalid, non-canonical URLs, this may not be the length + // of the string. For example "http://": the parsed structure will only + // contain an entry for the four-character scheme, and it doesn't know about + // the "://". For all other last-components, it will return the real length. + int Length() const; + + // Returns the number of characters before the given component if it exists, + // or where the component would be if it did exist. This will return the + // string length if the component would be appended to the end. + // + // Note that this can get a little funny for the port, query, and ref + // components which have a delimiter that is not counted as part of the + // component. The |include_delimiter| flag controls if you want this counted + // as part of the component or not when the component exists. + // + // This example shows the difference between the two flags for two of these + // delimited components that is present (the port and query) and one that + // isn't (the reference). The components that this flag affects are marked + // with a *. + // 0 1 2 + // 012345678901234567890 + // Example input: http://foo:80/?query + // include_delim=true, ...=false ("<-" indicates different) + // SCHEME: 0 0 + // USERNAME: 5 5 + // PASSWORD: 5 5 + // HOST: 7 7 + // *PORT: 10 11 <- + // PATH: 13 13 + // *QUERY: 14 15 <- + // *REF: 20 20 + // + int CountCharactersBefore(ComponentType type, bool include_delimiter) const; + + // Scheme without the colon: "http://foo"/ would have a scheme of "http". + // The length will be -1 if no scheme is specified ("foo.com"), or 0 if there + // is a colon but no scheme (":foo"). Note that the scheme is not guaranteed + // to start at the beginning of the string if there are preceeding whitespace + // or control characters. + Component scheme; + + // Username. Specified in URLs with an @ sign before the host. See |password| + Component username; + + // Password. The length will be -1 if unspecified, 0 if specified but empty. + // Not all URLs with a username have a password, as in "http://me@host/". + // The password is separated form the username with a colon, as in + // "http://me:secret@host/" + Component password; + + // Host name. + Component host; + + // Port number. + Component port; + + // Path, this is everything following the host name, stopping at the query of + // ref delimiter (if any). Length will be -1 if unspecified. This includes + // the preceeding slash, so the path on http://www.google.com/asdf" is + // "/asdf". As a result, it is impossible to have a 0 length path, it will + // be -1 in cases like "http://host?foo". + // Note that we treat backslashes the same as slashes. + Component path; + + // Stuff between the ? and the # after the path. This does not include the + // preceeding ? character. Length will be -1 if unspecified, 0 if there is + // a question mark but no query string. + Component query; + + // Indicated by a #, this is everything following the hash sign (not + // including it). If there are multiple hash signs, we'll use the last one. + // Length will be -1 if there is no hash sign, or 0 if there is one but + // nothing follows it. + Component ref; + + // The URL spec from the character after the scheme: until the end of the + // URL, regardless of the scheme. This is mostly useful for 'opaque' non- + // hierarchical schemes like data: and javascript: as a convient way to get + // the string with the scheme stripped off. + Component GetContent() const; + + // This is used for nested URL types, currently only filesystem. If you + // parse a filesystem URL, the resulting Parsed will have a nested + // inner_parsed_ to hold the parsed inner URL's component information. + // For all other url types [including the inner URL], it will be NULL. + Parsed* inner_parsed() const { + return inner_parsed_; + } + + void set_inner_parsed(const Parsed& inner_parsed) { + if (!inner_parsed_) + inner_parsed_ = new Parsed(inner_parsed); + else + *inner_parsed_ = inner_parsed; + } + + void clear_inner_parsed() { + if (inner_parsed_) { + delete inner_parsed_; + inner_parsed_ = NULL; + } + } + + private: + Parsed* inner_parsed_; // This object is owned and managed by this struct. +}; + +// Initialization functions --------------------------------------------------- +// +// These functions parse the given URL, filling in all of the structure's +// components. These functions can not fail, they will always do their best +// at interpreting the input given. +// +// The string length of the URL MUST be specified, we do not check for NULLs +// at any point in the process, and will actually handle embedded NULLs. +// +// IMPORTANT: These functions do NOT hang on to the given pointer or copy it +// in any way. See the comment above the struct. +// +// The 8-bit versions require UTF-8 encoding. + +// StandardURL is for when the scheme is known to be one that has an +// authority (host) like "http". This function will not handle weird ones +// like "about:" and "javascript:", or do the right thing for "file:" URLs. +URL_EXPORT void ParseStandardURL(const char* url, + int url_len, + Parsed* parsed); +URL_EXPORT void ParseStandardURL(const base::char16* url, + int url_len, + Parsed* parsed); + +// PathURL is for when the scheme is known not to have an authority (host) +// section but that aren't file URLs either. The scheme is parsed, and +// everything after the scheme is considered as the path. This is used for +// things like "about:" and "javascript:" +URL_EXPORT void ParsePathURL(const char* url, + int url_len, + bool trim_path_end, + Parsed* parsed); +URL_EXPORT void ParsePathURL(const base::char16* url, + int url_len, + bool trim_path_end, + Parsed* parsed); + +// FileURL is for file URLs. There are some special rules for interpreting +// these. +URL_EXPORT void ParseFileURL(const char* url, int url_len, Parsed* parsed); +URL_EXPORT void ParseFileURL(const base::char16* url, + int url_len, + Parsed* parsed); + +// Filesystem URLs are structured differently than other URLs. +URL_EXPORT void ParseFileSystemURL(const char* url, + int url_len, + Parsed* parsed); +URL_EXPORT void ParseFileSystemURL(const base::char16* url, + int url_len, + Parsed* parsed); + +// MailtoURL is for mailto: urls. They are made up scheme,path,query +URL_EXPORT void ParseMailtoURL(const char* url, int url_len, Parsed* parsed); +URL_EXPORT void ParseMailtoURL(const base::char16* url, + int url_len, + Parsed* parsed); + +// Helper functions ----------------------------------------------------------- + +// Locates the scheme according to the URL parser's rules. This function is +// designed so the caller can find the scheme and call the correct Init* +// function according to their known scheme types. +// +// It also does not perform any validation on the scheme. +// +// This function will return true if the scheme is found and will put the +// scheme's range into *scheme. False means no scheme could be found. Note +// that a URL beginning with a colon has a scheme, but it is empty, so this +// function will return true but *scheme will = (0,0). +// +// The scheme is found by skipping spaces and control characters at the +// beginning, and taking everything from there to the first colon to be the +// scheme. The character at scheme.end() will be the colon (we may enhance +// this to handle full width colons or something, so don't count on the +// actual character value). The character at scheme.end()+1 will be the +// beginning of the rest of the URL, be it the authority or the path (or the +// end of the string). +// +// The 8-bit version requires UTF-8 encoding. +URL_EXPORT bool ExtractScheme(const char* url, + int url_len, + Component* scheme); +URL_EXPORT bool ExtractScheme(const base::char16* url, + int url_len, + Component* scheme); + +// Returns true if ch is a character that terminates the authority segment +// of a URL. +URL_EXPORT bool IsAuthorityTerminator(base::char16 ch); + +// Does a best effort parse of input |spec|, in range |auth|. If a particular +// component is not found, it will be set to invalid. +URL_EXPORT void ParseAuthority(const char* spec, + const Component& auth, + Component* username, + Component* password, + Component* hostname, + Component* port_num); +URL_EXPORT void ParseAuthority(const base::char16* spec, + const Component& auth, + Component* username, + Component* password, + Component* hostname, + Component* port_num); + +// Computes the integer port value from the given port component. The port +// component should have been identified by one of the init functions on +// |Parsed| for the given input url. +// +// The return value will be a positive integer between 0 and 64K, or one of +// the two special values below. +enum SpecialPort { PORT_UNSPECIFIED = -1, PORT_INVALID = -2 }; +URL_EXPORT int ParsePort(const char* url, const Component& port); +URL_EXPORT int ParsePort(const base::char16* url, const Component& port); + +// Extracts the range of the file name in the given url. The path must +// already have been computed by the parse function, and the matching URL +// and extracted path are provided to this function. The filename is +// defined as being everything from the last slash/backslash of the path +// to the end of the path. +// +// The file name will be empty if the path is empty or there is nothing +// following the last slash. +// +// The 8-bit version requires UTF-8 encoding. +URL_EXPORT void ExtractFileName(const char* url, + const Component& path, + Component* file_name); +URL_EXPORT void ExtractFileName(const base::char16* url, + const Component& path, + Component* file_name); + +// Extract the first key/value from the range defined by |*query|. Updates +// |*query| to start at the end of the extracted key/value pair. This is +// designed for use in a loop: you can keep calling it with the same query +// object and it will iterate over all items in the query. +// +// Some key/value pairs may have the key, the value, or both be empty (for +// example, the query string "?&"). These will be returned. Note that an empty +// last parameter "foo.com?" or foo.com?a&" will not be returned, this case +// is the same as "done." +// +// The initial query component should not include the '?' (this is the default +// for parsed URLs). +// +// If no key/value are found |*key| and |*value| will be unchanged and it will +// return false. +URL_EXPORT bool ExtractQueryKeyValue(const char* url, + Component* query, + Component* key, + Component* value); +URL_EXPORT bool ExtractQueryKeyValue(const base::char16* url, + Component* query, + Component* key, + Component* value); + +} // namespace url diff --git a/src/common/url/url_constants.cc b/src/common/url/url_constants.cc new file mode 100644 index 0000000..fbbb72f --- /dev/null +++ b/src/common/url/url_constants.cc @@ -0,0 +1,53 @@ +/* + * Copyright 2014 The Chromium Authors. 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. + */ +#include "url/url_constants.h" + +namespace url { + +const char kAboutBlankURL[] = "about:blank"; + +const char kAboutScheme[] = "about"; +const char kBlobScheme[] = "blob"; +const char kContentScheme[] = "content"; +const char kDataScheme[] = "data"; +const char kFileScheme[] = "file"; +const char kFileSystemScheme[] = "filesystem"; +const char kFtpScheme[] = "ftp"; +const char kGopherScheme[] = "gopher"; +const char kHttpScheme[] = "http"; +const char kHttpsScheme[] = "https"; +const char kJavaScriptScheme[] = "javascript"; +const char kMailToScheme[] = "mailto"; +const char kWsScheme[] = "ws"; +const char kWssScheme[] = "wss"; + +const char kStandardSchemeSeparator[] = "://"; + +} // namespace url diff --git a/src/common/url/url_constants.h b/src/common/url/url_constants.h new file mode 100644 index 0000000..242601e --- /dev/null +++ b/src/common/url/url_constants.h @@ -0,0 +1,57 @@ +/* + * Copyright 2014 The Chromium Authors. 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. + */ +#pragma once + +#include "url/url_export.h" + +namespace url { + +URL_EXPORT extern const char kAboutBlankURL[]; + +URL_EXPORT extern const char kAboutScheme[]; +URL_EXPORT extern const char kBlobScheme[]; +// The content scheme is specific to Android for identifying a stored file. +URL_EXPORT extern const char kContentScheme[]; +URL_EXPORT extern const char kDataScheme[]; +URL_EXPORT extern const char kFileScheme[]; +URL_EXPORT extern const char kFileSystemScheme[]; +URL_EXPORT extern const char kFtpScheme[]; +URL_EXPORT extern const char kGopherScheme[]; +URL_EXPORT extern const char kHttpScheme[]; +URL_EXPORT extern const char kHttpsScheme[]; +URL_EXPORT extern const char kJavaScriptScheme[]; +URL_EXPORT extern const char kMailToScheme[]; +URL_EXPORT extern const char kWsScheme[]; +URL_EXPORT extern const char kWssScheme[]; + +// Used to separate a standard scheme and the hostname: "://". +URL_EXPORT extern const char kStandardSchemeSeparator[]; + +} // namespace url diff --git a/src/common/url/url_export.h b/src/common/url/url_export.h new file mode 100644 index 0000000..36b1955 --- /dev/null +++ b/src/common/url/url_export.h @@ -0,0 +1,55 @@ +/* + * Copyright 2014 The Chromium Authors. 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. + */ +#pragma once + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(URL_IMPLEMENTATION) +#define URL_EXPORT __declspec(dllexport) +#else +#define URL_EXPORT __declspec(dllimport) +#endif // defined(URL_IMPLEMENTATION) + +#else // !defined(WIN32) + +#if defined(URL_IMPLEMENTATION) +#define URL_EXPORT __attribute__((visibility("default"))) +#else +#define URL_EXPORT +#endif // defined(URL_IMPLEMENTATION) + +#endif // defined(WIN32) + +#else // !defined(COMPONENT_BUILD) + +#define URL_EXPORT + +#endif // define(COMPONENT_BUILD) diff --git a/src/common/url/url_file.h b/src/common/url/url_file.h new file mode 100644 index 0000000..cc42d6e --- /dev/null +++ b/src/common/url/url_file.h @@ -0,0 +1,105 @@ +/* + * Copyright 2014 The Chromium Authors. 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. + */ + +// Provides shared functions used by the internals of the parser and +// canonicalizer for file URLs. Do not use outside of these modules. +#pragma once + +#include "url/url_parse_internal.h" + +namespace url { + +#ifdef WIN32 + +// We allow both "c:" and "c|" as drive identifiers. +inline bool IsWindowsDriveSeparator(base::char16 ch) { + return ch == ':' || ch == '|'; +} +inline bool IsWindowsDriveLetter(base::char16 ch) { + return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'); +} + +#endif // WIN32 + +// Returns the index of the next slash in the input after the given index, or +// spec_len if the end of the input is reached. +template +inline int FindNextSlash(const CHAR* spec, int begin_index, int spec_len) { + int idx = begin_index; + while (idx < spec_len && !IsURLSlash(spec[idx])) + idx++; + return idx; +} + +#ifdef WIN32 + +// Returns true if the start_offset in the given spec looks like it begins a +// drive spec, for example "c:". This function explicitly handles start_offset +// values that are equal to or larger than the spec_len to simplify callers. +// +// If this returns true, the spec is guaranteed to have a valid drive letter +// plus a colon starting at |start_offset|. +template +inline bool DoesBeginWindowsDriveSpec(const CHAR* spec, int start_offset, + int spec_len) { + int remaining_len = spec_len - start_offset; + if (remaining_len < 2) + return false; // Not enough room. + if (!IsWindowsDriveLetter(spec[start_offset])) + return false; // Doesn't start with a valid drive letter. + if (!IsWindowsDriveSeparator(spec[start_offset + 1])) + return false; // Isn't followed with a drive separator. + return true; +} + +// Returns true if the start_offset in the given text looks like it begins a +// UNC path, for example "\\". This function explicitly handles start_offset +// values that are equal to or larger than the spec_len to simplify callers. +// +// When strict_slashes is set, this function will only accept backslashes as is +// standard for Windows. Otherwise, it will accept forward slashes as well +// which we use for a lot of URL handling. +template +inline bool DoesBeginUNCPath(const CHAR* text, + int start_offset, + int len, + bool strict_slashes) { + int remaining_len = len - start_offset; + if (remaining_len < 2) + return false; + + if (strict_slashes) + return text[start_offset] == '\\' && text[start_offset + 1] == '\\'; + return IsURLSlash(text[start_offset]) && IsURLSlash(text[start_offset + 1]); +} + +#endif // WIN32 + +} // namespace url diff --git a/src/common/url/url_parse.cc b/src/common/url/url_parse.cc new file mode 100644 index 0000000..d1861a5 --- /dev/null +++ b/src/common/url/url_parse.cc @@ -0,0 +1,932 @@ +/* Based on nsURLParsers.cc from Mozilla + * ------------------------------------- + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Darin Fisher (original author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include + +#include "base/logging.h" +#include "url/url_parse_internal.h" +#include "url/url_util.h" +#include "url/url_util_internal.h" +#include "url/third_party/mozilla/url_parse.h" + + +namespace url { + +namespace { + +// Returns true if the given character is a valid digit to use in a port. +inline bool IsPortDigit(base::char16 ch) { + return ch >= '0' && ch <= '9'; +} + +// Returns the offset of the next authority terminator in the input starting +// from start_offset. If no terminator is found, the return value will be equal +// to spec_len. +template +int FindNextAuthorityTerminator(const CHAR* spec, + int start_offset, + int spec_len) { + for (int i = start_offset; i < spec_len; i++) { + if (IsAuthorityTerminator(spec[i])) + return i; + } + return spec_len; // Not found. +} + +template +void ParseUserInfo(const CHAR* spec, + const Component& user, + Component* username, + Component* password) { + // Find the first colon in the user section, which separates the username and + // password. + int colon_offset = 0; + while (colon_offset < user.len && spec[user.begin + colon_offset] != ':') + colon_offset++; + + if (colon_offset < user.len) { + // Found separator: : + *username = Component(user.begin, colon_offset); + *password = MakeRange(user.begin + colon_offset + 1, + user.begin + user.len); + } else { + // No separator, treat everything as the username + *username = user; + *password = Component(); + } +} + +template +void ParseServerInfo(const CHAR* spec, + const Component& serverinfo, + Component* hostname, + Component* port_num) { + if (serverinfo.len == 0) { + // No server info, host name is empty. + hostname->reset(); + port_num->reset(); + return; + } + + // If the host starts with a left-bracket, assume the entire host is an + // IPv6 literal. Otherwise, assume none of the host is an IPv6 literal. + // This assumption will be overridden if we find a right-bracket. + // + // Our IPv6 address canonicalization code requires both brackets to exist, + // but the ability to locate an incomplete address can still be useful. + int ipv6_terminator = spec[serverinfo.begin] == '[' ? serverinfo.end() : -1; + int colon = -1; + + // Find the last right-bracket, and the last colon. + for (int i = serverinfo.begin; i < serverinfo.end(); i++) { + switch (spec[i]) { + case ']': + ipv6_terminator = i; + break; + case ':': + colon = i; + break; + } + } + + if (colon > ipv6_terminator) { + // Found a port number: : + *hostname = MakeRange(serverinfo.begin, colon); + if (hostname->len == 0) + hostname->reset(); + *port_num = MakeRange(colon + 1, serverinfo.end()); + } else { + // No port: + *hostname = serverinfo; + port_num->reset(); + } +} + +// Given an already-identified auth section, breaks it into its consituent +// parts. The port number will be parsed and the resulting integer will be +// filled into the given *port variable, or -1 if there is no port number or it +// is invalid. +template +void DoParseAuthority(const CHAR* spec, + const Component& auth, + Component* username, + Component* password, + Component* hostname, + Component* port_num) { + DCHECK(auth.is_valid(), "We should always get an authority"); + if (auth.len == 0) { + username->reset(); + password->reset(); + hostname->reset(); + port_num->reset(); + return; + } + + // Search backwards for @, which is the separator between the user info and + // the server info. + int i = auth.begin + auth.len - 1; + while (i > auth.begin && spec[i] != '@') + i--; + + if (spec[i] == '@') { + // Found user info: @ + ParseUserInfo(spec, Component(auth.begin, i - auth.begin), + username, password); + ParseServerInfo(spec, MakeRange(i + 1, auth.begin + auth.len), + hostname, port_num); + } else { + // No user info, everything is server info. + username->reset(); + password->reset(); + ParseServerInfo(spec, auth, hostname, port_num); + } +} + +template +void ParsePath(const CHAR* spec, + const Component& path, + Component* filepath, + Component* query, + Component* ref) { + // path = [/]//<...>/;?# + + // Special case when there is no path. + if (path.len == -1) { + filepath->reset(); + query->reset(); + ref->reset(); + return; + } + DCHECK(path.len > 0, "We should never have 0 length paths"); + + // Search for first occurrence of either ? or #. + int path_end = path.begin + path.len; + + int query_separator = -1; // Index of the '?' + int ref_separator = -1; // Index of the '#' + for (int i = path.begin; i < path_end; i++) { + switch (spec[i]) { + case '?': + // Only match the query string if it precedes the reference fragment + // and when we haven't found one already. + if (ref_separator < 0 && query_separator < 0) + query_separator = i; + break; + case '#': + // Record the first # sign only. + if (ref_separator < 0) + ref_separator = i; + break; + } + } + + // Markers pointing to the character after each of these corresponding + // components. The code below words from the end back to the beginning, + // and will update these indices as it finds components that exist. + int file_end, query_end; + + // Ref fragment: from the # to the end of the path. + if (ref_separator >= 0) { + file_end = query_end = ref_separator; + *ref = MakeRange(ref_separator + 1, path_end); + } else { + file_end = query_end = path_end; + ref->reset(); + } + + // Query fragment: everything from the ? to the next boundary (either the end + // of the path or the ref fragment). + if (query_separator >= 0) { + file_end = query_separator; + *query = MakeRange(query_separator + 1, query_end); + } else { + query->reset(); + } + + // File path: treat an empty file path as no file path. + if (file_end != path.begin) + *filepath = MakeRange(path.begin, file_end); + else + filepath->reset(); +} + +template +bool DoExtractScheme(const CHAR* url, + int url_len, + Component* scheme) { + // Skip leading whitespace and control characters. + int begin = 0; + while (begin < url_len && ShouldTrimFromURL(url[begin])) + begin++; + if (begin == url_len) + return false; // Input is empty or all whitespace. + + // Find the first colon character. + for (int i = begin; i < url_len; i++) { + if (url[i] == ':') { + *scheme = MakeRange(begin, i); + return true; + } + } + return false; // No colon found: no scheme +} + +// Fills in all members of the Parsed structure except for the scheme. +// +// |spec| is the full spec being parsed, of length |spec_len|. +// |after_scheme| is the character immediately following the scheme (after the +// colon) where we'll begin parsing. +// +// Compatability data points. I list "host", "path" extracted: +// Input IE6 Firefox Us +// ----- -------------- -------------- -------------- +// http://foo.com/ "foo.com", "/" "foo.com", "/" "foo.com", "/" +// http:foo.com/ "foo.com", "/" "foo.com", "/" "foo.com", "/" +// http:/foo.com/ fail(*) "foo.com", "/" "foo.com", "/" +// http:\foo.com/ fail(*) "\foo.com", "/"(fail) "foo.com", "/" +// http:////foo.com/ "foo.com", "/" "foo.com", "/" "foo.com", "/" +// +// (*) Interestingly, although IE fails to load these URLs, its history +// canonicalizer handles them, meaning if you've been to the corresponding +// "http://foo.com/" link, it will be colored. +template +void DoParseAfterScheme(const CHAR* spec, + int spec_len, + int after_scheme, + Parsed* parsed) { + int num_slashes = CountConsecutiveSlashes(spec, after_scheme, spec_len); + int after_slashes = after_scheme + num_slashes; + + // First split into two main parts, the authority (username, password, host, + // and port) and the full path (path, query, and reference). + Component authority; + Component full_path; + + // Found "//", looks like an authority section. Treat everything + // from there to the next slash (or end of spec) to be the authority. Note + // that we ignore the number of slashes and treat it as the authority. + int end_auth = FindNextAuthorityTerminator(spec, after_slashes, spec_len); + authority = Component(after_slashes, end_auth - after_slashes); + + if (end_auth == spec_len) // No beginning of path found. + full_path = Component(); + else // Everything starting from the slash to the end is the path. + full_path = Component(end_auth, spec_len - end_auth); + + // Now parse those two sub-parts. + DoParseAuthority(spec, authority, &parsed->username, &parsed->password, + &parsed->host, &parsed->port); + ParsePath(spec, full_path, &parsed->path, &parsed->query, &parsed->ref); +} + +// The main parsing function for standard URLs. Standard URLs have a scheme, +// host, path, etc. +template +void DoParseStandardURL(const CHAR* spec, int spec_len, Parsed* parsed) { + DCHECK(spec_len >= 0, ""); + + // Strip leading & trailing spaces and control characters. + int begin = 0; + + int after_scheme; + if (DoExtractScheme(spec, spec_len, &parsed->scheme)) { + after_scheme = parsed->scheme.end() + 1; // Skip past the colon. + } else { + // Say there's no scheme when there is no colon. We could also say that + // everything is the scheme. Both would produce an invalid URL, but this way + // seems less wrong in more cases. + parsed->scheme.reset(); + after_scheme = begin; + } + DoParseAfterScheme(spec, spec_len, after_scheme, parsed); +} + +template +void DoParseFileSystemURL(const CHAR* spec, int spec_len, Parsed* parsed) { + DCHECK(spec_len >= 0, ""); + + // Get the unused parts of the URL out of the way. + parsed->username.reset(); + parsed->password.reset(); + parsed->host.reset(); + parsed->port.reset(); + parsed->path.reset(); // May use this; reset for convenience. + parsed->ref.reset(); // May use this; reset for convenience. + parsed->query.reset(); // May use this; reset for convenience. + parsed->clear_inner_parsed(); // May use this; reset for convenience. + + // Strip leading & trailing spaces and control characters. + int begin = 0; + TrimURL(spec, &begin, &spec_len); + + // Handle empty specs or ones that contain only whitespace or control chars. + if (begin == spec_len) { + parsed->scheme.reset(); + return; + } + + int inner_start = -1; + + // Extract the scheme. We also handle the case where there is no scheme. + if (DoExtractScheme(&spec[begin], spec_len - begin, &parsed->scheme)) { + // Offset the results since we gave ExtractScheme a substring. + parsed->scheme.begin += begin; + + if (parsed->scheme.end() == spec_len - 1) + return; + + inner_start = parsed->scheme.end() + 1; + } else { + // No scheme found; that's not valid for filesystem URLs. + parsed->scheme.reset(); + return; + } + + Component inner_scheme; + const CHAR* inner_spec = &spec[inner_start]; + int inner_spec_len = spec_len - inner_start; + + if (DoExtractScheme(inner_spec, inner_spec_len, &inner_scheme)) { + // Offset the results since we gave ExtractScheme a substring. + inner_scheme.begin += inner_start; + + if (inner_scheme.end() == spec_len - 1) + return; + } else { + // No scheme found; that's not valid for filesystem URLs. + // The best we can do is return "filesystem://". + return; + } + + Parsed inner_parsed; + + if (CompareSchemeComponent(spec, inner_scheme, kFileScheme)) { + // File URLs are special. + ParseFileURL(inner_spec, inner_spec_len, &inner_parsed); + } else if (CompareSchemeComponent(spec, inner_scheme, kFileSystemScheme)) { + // Filesystem URLs don't nest. + return; + } else if (IsStandard(spec, inner_scheme)) { + // All "normal" URLs. + DoParseStandardURL(inner_spec, inner_spec_len, &inner_parsed); + } else { + return; + } + + // All members of inner_parsed need to be offset by inner_start. + // If we had any scheme that supported nesting more than one level deep, + // we'd have to recurse into the inner_parsed's inner_parsed when + // adjusting by inner_start. + inner_parsed.scheme.begin += inner_start; + inner_parsed.username.begin += inner_start; + inner_parsed.password.begin += inner_start; + inner_parsed.host.begin += inner_start; + inner_parsed.port.begin += inner_start; + inner_parsed.query.begin += inner_start; + inner_parsed.ref.begin += inner_start; + inner_parsed.path.begin += inner_start; + + // Query and ref move from inner_parsed to parsed. + parsed->query = inner_parsed.query; + inner_parsed.query.reset(); + parsed->ref = inner_parsed.ref; + inner_parsed.ref.reset(); + + parsed->set_inner_parsed(inner_parsed); + if (!inner_parsed.scheme.is_valid() || !inner_parsed.path.is_valid() || + inner_parsed.inner_parsed()) { + return; + } + + // The path in inner_parsed should start with a slash, then have a filesystem + // type followed by a slash. From the first slash up to but excluding the + // second should be what it keeps; the rest goes to parsed. If the path ends + // before the second slash, it's still pretty clear what the user meant, so + // we'll let that through. + if (!IsURLSlash(spec[inner_parsed.path.begin])) { + return; + } + int inner_path_end = inner_parsed.path.begin + 1; // skip the leading slash + while (inner_path_end < spec_len && + !IsURLSlash(spec[inner_path_end])) + ++inner_path_end; + parsed->path.begin = inner_path_end; + int new_inner_path_length = inner_path_end - inner_parsed.path.begin; + parsed->path.len = inner_parsed.path.len - new_inner_path_length; + parsed->inner_parsed()->path.len = new_inner_path_length; +} + +// Initializes a path URL which is merely a scheme followed by a path. Examples +// include "about:foo" and "javascript:alert('bar');" +template +void DoParsePathURL(const CHAR* spec, int spec_len, + bool trim_path_end, + Parsed* parsed) { + // Get the non-path and non-scheme parts of the URL out of the way, we never + // use them. + parsed->username.reset(); + parsed->password.reset(); + parsed->host.reset(); + parsed->port.reset(); + parsed->path.reset(); + parsed->query.reset(); + parsed->ref.reset(); + + // Strip leading & trailing spaces and control characters. + int scheme_begin = 0; + TrimURL(spec, &scheme_begin, &spec_len, trim_path_end); + + // Handle empty specs or ones that contain only whitespace or control chars. + if (scheme_begin == spec_len) { + parsed->scheme.reset(); + parsed->path.reset(); + return; + } + + int path_begin; + // Extract the scheme, with the path being everything following. We also + // handle the case where there is no scheme. + if (ExtractScheme(&spec[scheme_begin], spec_len - scheme_begin, + &parsed->scheme)) { + // Offset the results since we gave ExtractScheme a substring. + parsed->scheme.begin += scheme_begin; + path_begin = parsed->scheme.end() + 1; + } else { + // No scheme case. + parsed->scheme.reset(); + path_begin = scheme_begin; + } + + if (path_begin == spec_len) + return; + + ParsePath(spec, + MakeRange(path_begin, spec_len), + &parsed->path, + &parsed->query, + &parsed->ref); +} + +template +void DoParseMailtoURL(const CHAR* spec, int spec_len, Parsed* parsed) { + DCHECK(spec_len >= 0, ""); + + // Get the non-path and non-scheme parts of the URL out of the way, we never + // use them. + parsed->username.reset(); + parsed->password.reset(); + parsed->host.reset(); + parsed->port.reset(); + parsed->ref.reset(); + parsed->query.reset(); // May use this; reset for convenience. + + // Strip leading & trailing spaces and control characters. + int begin = 0; + TrimURL(spec, &begin, &spec_len); + + // Handle empty specs or ones that contain only whitespace or control chars. + if (begin == spec_len) { + parsed->scheme.reset(); + parsed->path.reset(); + return; + } + + int path_begin = -1; + int path_end = -1; + + // Extract the scheme, with the path being everything following. We also + // handle the case where there is no scheme. + if (ExtractScheme(&spec[begin], spec_len - begin, &parsed->scheme)) { + // Offset the results since we gave ExtractScheme a substring. + parsed->scheme.begin += begin; + + if (parsed->scheme.end() != spec_len - 1) { + path_begin = parsed->scheme.end() + 1; + path_end = spec_len; + } + } else { + // No scheme found, just path. + parsed->scheme.reset(); + path_begin = begin; + path_end = spec_len; + } + + // Split [path_begin, path_end) into a path + query. + for (int i = path_begin; i < path_end; ++i) { + if (spec[i] == '?') { + parsed->query = MakeRange(i + 1, path_end); + path_end = i; + break; + } + } + + // For compatability with the standard URL parser, treat no path as + // -1, rather than having a length of 0 + if (path_begin == path_end) { + parsed->path.reset(); + } else { + parsed->path = MakeRange(path_begin, path_end); + } +} + +// Converts a port number in a string to an integer. We'd like to just call +// sscanf but our input is not NULL-terminated, which sscanf requires. Instead, +// we copy the digits to a small stack buffer (since we know the maximum number +// of digits in a valid port number) that we can NULL terminate. +template +int DoParsePort(const CHAR* spec, const Component& component) { + // Easy success case when there is no port. + const int kMaxDigits = 5; + if (!component.is_nonempty()) + return PORT_UNSPECIFIED; + + // Skip over any leading 0s. + Component digits_comp(component.end(), 0); + for (int i = 0; i < component.len; i++) { + if (spec[component.begin + i] != '0') { + digits_comp = MakeRange(component.begin + i, component.end()); + break; + } + } + if (digits_comp.len == 0) + return 0; // All digits were 0. + + // Verify we don't have too many digits (we'll be copying to our buffer so + // we need to double-check). + if (digits_comp.len > kMaxDigits) + return PORT_INVALID; + + // Copy valid digits to the buffer. + char digits[kMaxDigits + 1]; // +1 for null terminator + for (int i = 0; i < digits_comp.len; i++) { + CHAR ch = spec[digits_comp.begin + i]; + if (!IsPortDigit(ch)) { + // Invalid port digit, fail. + return PORT_INVALID; + } + digits[i] = static_cast(ch); + } + + // Null-terminate the string and convert to integer. Since we guarantee + // only digits, atoi's lack of error handling is OK. + digits[digits_comp.len] = 0; + int port = atoi(digits); + if (port > 65535) + return PORT_INVALID; // Out of range. + return port; +} + +template +void DoExtractFileName(const CHAR* spec, + const Component& path, + Component* file_name) { + // Handle empty paths: they have no file names. + if (!path.is_nonempty()) { + file_name->reset(); + return; + } + + // Extract the filename range from the path which is between + // the last slash and the following semicolon. + int file_end = path.end(); + for (int i = path.end() - 1; i >= path.begin; i--) { + if (spec[i] == ';') { + file_end = i; + } else if (IsURLSlash(spec[i])) { + // File name is everything following this character to the end + *file_name = MakeRange(i + 1, file_end); + return; + } + } + + // No slash found, this means the input was degenerate (generally paths + // will start with a slash). Let's call everything the file name. + *file_name = MakeRange(path.begin, file_end); + return; +} + +template +bool DoExtractQueryKeyValue(const CHAR* spec, + Component* query, + Component* key, + Component* value) { + if (!query->is_nonempty()) + return false; + + int start = query->begin; + int cur = start; + int end = query->end(); + + // We assume the beginning of the input is the beginning of the "key" and we + // skip to the end of it. + key->begin = cur; + while (cur < end && spec[cur] != '&' && spec[cur] != '=') + cur++; + key->len = cur - key->begin; + + // Skip the separator after the key (if any). + if (cur < end && spec[cur] == '=') + cur++; + + // Find the value part. + value->begin = cur; + while (cur < end && spec[cur] != '&') + cur++; + value->len = cur - value->begin; + + // Finally skip the next separator if any + if (cur < end && spec[cur] == '&') + cur++; + + // Save the new query + *query = MakeRange(cur, end); + return true; +} + +} // namespace + +Parsed::Parsed() : inner_parsed_(NULL) { +} + +Parsed::Parsed(const Parsed& other) : + scheme(other.scheme), + username(other.username), + password(other.password), + host(other.host), + port(other.port), + path(other.path), + query(other.query), + ref(other.ref), + inner_parsed_(NULL) { + if (other.inner_parsed_) + set_inner_parsed(*other.inner_parsed_); +} + +Parsed& Parsed::operator=(const Parsed& other) { + if (this != &other) { + scheme = other.scheme; + username = other.username; + password = other.password; + host = other.host; + port = other.port; + path = other.path; + query = other.query; + ref = other.ref; + if (other.inner_parsed_) + set_inner_parsed(*other.inner_parsed_); + else + clear_inner_parsed(); + } + return *this; +} + +Parsed::~Parsed() { + delete inner_parsed_; +} + +int Parsed::Length() const { + if (ref.is_valid()) + return ref.end(); + return CountCharactersBefore(REF, false); +} + +int Parsed::CountCharactersBefore(ComponentType type, + bool include_delimiter) const { + if (type == SCHEME) + return scheme.begin; + + // There will be some characters after the scheme like "://" and we don't + // know how many. Search forwards for the next thing until we find one. + int cur = 0; + if (scheme.is_valid()) + cur = scheme.end() + 1; // Advance over the ':' at the end of the scheme. + + if (username.is_valid()) { + if (type <= USERNAME) + return username.begin; + cur = username.end() + 1; // Advance over the '@' or ':' at the end. + } + + if (password.is_valid()) { + if (type <= PASSWORD) + return password.begin; + cur = password.end() + 1; // Advance over the '@' at the end. + } + + if (host.is_valid()) { + if (type <= HOST) + return host.begin; + cur = host.end(); + } + + if (port.is_valid()) { + if (type < PORT || (type == PORT && include_delimiter)) + return port.begin - 1; // Back over delimiter. + if (type == PORT) + return port.begin; // Don't want delimiter counted. + cur = port.end(); + } + + if (path.is_valid()) { + if (type <= PATH) + return path.begin; + cur = path.end(); + } + + if (query.is_valid()) { + if (type < QUERY || (type == QUERY && include_delimiter)) + return query.begin - 1; // Back over delimiter. + if (type == QUERY) + return query.begin; // Don't want delimiter counted. + cur = query.end(); + } + + if (ref.is_valid()) { + if (type == REF && !include_delimiter) + return ref.begin; // Back over delimiter. + + // When there is a ref and we get here, the component we wanted was before + // this and not found, so we always know the beginning of the ref is right. + return ref.begin - 1; // Don't want delimiter counted. + } + + return cur; +} + +Component Parsed::GetContent() const { + const int begin = CountCharactersBefore(USERNAME, false); + const int len = Length() - begin; + // For compatability with the standard URL parser, we treat no content as + // -1, rather than having a length of 0 (we normally wouldn't care so + // much for these non-standard URLs). + return len ? Component(begin, len) : Component(); +} + +bool ExtractScheme(const char* url, int url_len, Component* scheme) { + return DoExtractScheme(url, url_len, scheme); +} + +bool ExtractScheme(const base::char16* url, int url_len, Component* scheme) { + return DoExtractScheme(url, url_len, scheme); +} + +// This handles everything that may be an authority terminator, including +// backslash. For special backslash handling see DoParseAfterScheme. +bool IsAuthorityTerminator(base::char16 ch) { + return IsURLSlash(ch) || ch == '?' || ch == '#'; +} + +void ExtractFileName(const char* url, + const Component& path, + Component* file_name) { + DoExtractFileName(url, path, file_name); +} + +void ExtractFileName(const base::char16* url, + const Component& path, + Component* file_name) { + DoExtractFileName(url, path, file_name); +} + +bool ExtractQueryKeyValue(const char* url, + Component* query, + Component* key, + Component* value) { + return DoExtractQueryKeyValue(url, query, key, value); +} + +bool ExtractQueryKeyValue(const base::char16* url, + Component* query, + Component* key, + Component* value) { + return DoExtractQueryKeyValue(url, query, key, value); +} + +void ParseAuthority(const char* spec, + const Component& auth, + Component* username, + Component* password, + Component* hostname, + Component* port_num) { + DoParseAuthority(spec, auth, username, password, hostname, port_num); +} + +void ParseAuthority(const base::char16* spec, + const Component& auth, + Component* username, + Component* password, + Component* hostname, + Component* port_num) { + DoParseAuthority(spec, auth, username, password, hostname, port_num); +} + +int ParsePort(const char* url, const Component& port) { + return DoParsePort(url, port); +} + +int ParsePort(const base::char16* url, const Component& port) { + return DoParsePort(url, port); +} + +void ParseStandardURL(const char* url, int url_len, Parsed* parsed) { + DoParseStandardURL(url, url_len, parsed); +} + +void ParseStandardURL(const base::char16* url, int url_len, Parsed* parsed) { + DoParseStandardURL(url, url_len, parsed); +} + +void ParsePathURL(const char* url, + int url_len, + bool trim_path_end, + Parsed* parsed) { + DoParsePathURL(url, url_len, trim_path_end, parsed); +} + +void ParsePathURL(const base::char16* url, + int url_len, + bool trim_path_end, + Parsed* parsed) { + DoParsePathURL(url, url_len, trim_path_end, parsed); +} + +void ParseFileSystemURL(const char* url, int url_len, Parsed* parsed) { + DoParseFileSystemURL(url, url_len, parsed); +} + +void ParseFileSystemURL(const base::char16* url, int url_len, Parsed* parsed) { + DoParseFileSystemURL(url, url_len, parsed); +} + +void ParseMailtoURL(const char* url, int url_len, Parsed* parsed) { + DoParseMailtoURL(url, url_len, parsed); +} + +void ParseMailtoURL(const base::char16* url, int url_len, Parsed* parsed) { + DoParseMailtoURL(url, url_len, parsed); +} + +void ParsePathInternal(const char* spec, + const Component& path, + Component* filepath, + Component* query, + Component* ref) { + ParsePath(spec, path, filepath, query, ref); +} + +void ParsePathInternal(const base::char16* spec, + const Component& path, + Component* filepath, + Component* query, + Component* ref) { + ParsePath(spec, path, filepath, query, ref); +} + +void ParseAfterScheme(const char* spec, + int spec_len, + int after_scheme, + Parsed* parsed) { + DoParseAfterScheme(spec, spec_len, after_scheme, parsed); +} + +void ParseAfterScheme(const base::char16* spec, + int spec_len, + int after_scheme, + Parsed* parsed) { + DoParseAfterScheme(spec, spec_len, after_scheme, parsed); +} + +} // namespace url diff --git a/src/common/url/url_parse_file.cc b/src/common/url/url_parse_file.cc new file mode 100644 index 0000000..09ef8a0 --- /dev/null +++ b/src/common/url/url_parse_file.cc @@ -0,0 +1,247 @@ +/* + * Copyright 2014 The Chromium Authors. 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. + */ +#include "base/logging.h" +#include "url/third_party/mozilla/url_parse.h" +#include "url/url_file.h" +#include "url/url_parse_internal.h" + +// Interesting IE file:isms... +// +// INPUT OUTPUT +// ========================= ============================== +// file:/foo/bar file:///foo/bar +// The result here seems totally invalid!?!? This isn't UNC. +// +// file:/ +// file:// or any other number of slashes +// IE6 doesn't do anything at all if you click on this link. No error: +// nothing. IE6's history system seems to always color this link, so I'm +// guessing that it maps internally to the empty URL. +// +// C:\ file:///C:/ +// When on a file: URL source page, this link will work. When over HTTP, +// the file: URL will appear in the status bar but the link will not work +// (security restriction for all file URLs). +// +// file:foo/ file:foo/ (invalid?!?!?) +// file:/foo/ file:///foo/ (invalid?!?!?) +// file://foo/ file://foo/ (UNC to server "foo") +// file:///foo/ file:///foo/ (invalid, seems to be a file) +// file:////foo/ file://foo/ (UNC to server "foo") +// Any more than four slashes is also treated as UNC. +// +// file:C:/ file://C:/ +// file:/C:/ file://C:/ +// The number of slashes after "file:" don't matter if the thing following +// it looks like an absolute drive path. Also, slashes and backslashes are +// equally valid here. + +namespace url { + +namespace { + +// A subcomponent of DoInitFileURL, the input of this function should be a UNC +// path name, with the index of the first character after the slashes following +// the scheme given in |after_slashes|. This will initialize the host, path, +// query, and ref, and leave the other output components untouched +// (DoInitFileURL handles these for us). +template +void DoParseUNC(const CHAR* spec, + int after_slashes, + int spec_len, + Parsed* parsed) { + int next_slash = FindNextSlash(spec, after_slashes, spec_len); + if (next_slash == spec_len) { + // No additional slash found, as in "file://foo", treat the text as the + // host with no path (this will end up being UNC to server "foo"). + int host_len = spec_len - after_slashes; + if (host_len) + parsed->host = Component(after_slashes, host_len); + else + parsed->host.reset(); + parsed->path.reset(); + return; + } + +#ifdef WIN32 + // See if we have something that looks like a path following the first + // component. As in "file://localhost/c:/", we get "c:/" out. We want to + // treat this as a having no host but the path given. Works on Windows only. + if (DoesBeginWindowsDriveSpec(spec, next_slash + 1, spec_len)) { + parsed->host.reset(); + ParsePathInternal(spec, MakeRange(next_slash, spec_len), + &parsed->path, &parsed->query, &parsed->ref); + return; + } +#endif + + // Otherwise, everything up until that first slash we found is the host name, + // which will end up being the UNC host. For example "file://foo/bar.txt" + // will get a server name of "foo" and a path of "/bar". Later, on Windows, + // this should be treated as the filename "\\foo\bar.txt" in proper UNC + // notation. + int host_len = next_slash - after_slashes; + if (host_len) + parsed->host = MakeRange(after_slashes, next_slash); + else + parsed->host.reset(); + if (next_slash < spec_len) { + ParsePathInternal(spec, MakeRange(next_slash, spec_len), + &parsed->path, &parsed->query, &parsed->ref); + } else { + parsed->path.reset(); + } +} + +// A subcomponent of DoParseFileURL, the input should be a local file, with the +// beginning of the path indicated by the index in |path_begin|. This will +// initialize the host, path, query, and ref, and leave the other output +// components untouched (DoInitFileURL handles these for us). +template +void DoParseLocalFile(const CHAR* spec, + int path_begin, + int spec_len, + Parsed* parsed) { + parsed->host.reset(); + ParsePathInternal(spec, MakeRange(path_begin, spec_len), + &parsed->path, &parsed->query, &parsed->ref); +} + +// Backend for the external functions that operates on either char type. +// Handles cases where there is a scheme, but also when handed the first +// character following the "file:" at the beginning of the spec. If so, +// this is usually a slash, but needn't be; we allow paths like "file:c:\foo". +template +void DoParseFileURL(const CHAR* spec, int spec_len, Parsed* parsed) { + DCHECK(spec_len >= 0, ""); + + // Get the parts we never use for file URLs out of the way. + parsed->username.reset(); + parsed->password.reset(); + parsed->port.reset(); + + // Many of the code paths don't set these, so it's convenient to just clear + // them. We'll write them in those cases we need them. + parsed->query.reset(); + parsed->ref.reset(); + + // Strip leading & trailing spaces and control characters. + int begin = 0; + TrimURL(spec, &begin, &spec_len); + + // Find the scheme, if any. + int num_slashes = CountConsecutiveSlashes(spec, begin, spec_len); + int after_scheme; + int after_slashes; +#ifdef WIN32 + // See how many slashes there are. We want to handle cases like UNC but also + // "/c:/foo". This is when there is no scheme, so we can allow pages to do + // links like "c:/foo/bar" or "//foo/bar". This is also called by the + // relative URL resolver when it determines there is an absolute URL, which + // may give us input like "/c:/foo". + after_slashes = begin + num_slashes; + if (DoesBeginWindowsDriveSpec(spec, after_slashes, spec_len)) { + // Windows path, don't try to extract the scheme (for example, "c:\foo"). + parsed->scheme.reset(); + after_scheme = after_slashes; + } else if (DoesBeginUNCPath(spec, begin, spec_len, false)) { + // Windows UNC path: don't try to extract the scheme, but keep the slashes. + parsed->scheme.reset(); + after_scheme = begin; + } else +#endif + { + // ExtractScheme doesn't understand the possibility of filenames with + // colons in them, in which case it returns the entire spec up to the + // colon as the scheme. So handle /foo.c:5 as a file but foo.c:5 as + // the foo.c: scheme. + if (!num_slashes && + ExtractScheme(&spec[begin], spec_len - begin, &parsed->scheme)) { + // Offset the results since we gave ExtractScheme a substring. + parsed->scheme.begin += begin; + after_scheme = parsed->scheme.end() + 1; + } else { + // No scheme found, remember that. + parsed->scheme.reset(); + after_scheme = begin; + } + } + + // Handle empty specs ones that contain only whitespace or control chars, + // or that are just the scheme (for example "file:"). + if (after_scheme == spec_len) { + parsed->host.reset(); + parsed->path.reset(); + return; + } + + num_slashes = CountConsecutiveSlashes(spec, after_scheme, spec_len); + after_slashes = after_scheme + num_slashes; +#ifdef WIN32 + // Check whether the input is a drive again. We checked above for windows + // drive specs, but that's only at the very beginning to see if we have a + // scheme at all. This test will be duplicated in that case, but will + // additionally handle all cases with a real scheme such as "file:///C:/". + if (!DoesBeginWindowsDriveSpec(spec, after_slashes, spec_len) && + num_slashes != 3) { + // Anything not beginning with a drive spec ("c:\") on Windows is treated + // as UNC, with the exception of three slashes which always means a file. + // Even IE7 treats file:///foo/bar as "/foo/bar", which then fails. + DoParseUNC(spec, after_slashes, spec_len, parsed); + return; + } +#else + // file: URL with exactly 2 slashes is considered to have a host component. + if (num_slashes == 2) { + DoParseUNC(spec, after_slashes, spec_len, parsed); + return; + } +#endif // WIN32 + + // Easy and common case, the full path immediately follows the scheme + // (modulo slashes), as in "file://c:/foo". Just treat everything from + // there to the end as the path. Empty hosts have 0 length instead of -1. + // We include the last slash as part of the path if there is one. + DoParseLocalFile(spec, + num_slashes > 0 ? after_scheme + num_slashes - 1 : after_scheme, + spec_len, parsed); +} + +} // namespace + +void ParseFileURL(const char* url, int url_len, Parsed* parsed) { + DoParseFileURL(url, url_len, parsed); +} + +void ParseFileURL(const base::char16* url, int url_len, Parsed* parsed) { + DoParseFileURL(url, url_len, parsed); +} + +} // namespace url diff --git a/src/common/url/url_parse_internal.h b/src/common/url/url_parse_internal.h new file mode 100644 index 0000000..8445870 --- /dev/null +++ b/src/common/url/url_parse_internal.h @@ -0,0 +1,113 @@ +/* + * Copyright 2014 The Chromium Authors. 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. + */ +#pragma once + +// Contains common inline helper functions used by the URL parsing routines. + +#include "url/third_party/mozilla/url_parse.h" + +namespace url { + +// We treat slashes and backslashes the same for IE compatability. +inline bool IsURLSlash(base::char16 ch) { + return ch == '/' || ch == '\\'; +} + +// Returns true if we should trim this character from the URL because it is a +// space or a control character. +inline bool ShouldTrimFromURL(base::char16 ch) { + return ch <= ' '; +} + +// Given an already-initialized begin index and length, this shrinks the range +// to eliminate "should-be-trimmed" characters. Note that the length does *not* +// indicate the length of untrimmed data from |*begin|, but rather the position +// in the input string (so the string starts at character |*begin| in the spec, +// and goes until |*len|). +template +inline void TrimURL(const CHAR* spec, int* begin, int* len, + bool trim_path_end = true) { + // Strip leading whitespace and control characters. + while (*begin < *len && ShouldTrimFromURL(spec[*begin])) + (*begin)++; + + if (trim_path_end) { + // Strip trailing whitespace and control characters. We need the >i test + // for when the input string is all blanks; we don't want to back past the + // input. + while (*len > *begin && ShouldTrimFromURL(spec[*len - 1])) + (*len)--; + } +} + +// Counts the number of consecutive slashes starting at the given offset +// in the given string of the given length. +template +inline int CountConsecutiveSlashes(const CHAR *str, + int begin_offset, int str_len) { + int count = 0; + while (begin_offset + count < str_len && + IsURLSlash(str[begin_offset + count])) + ++count; + return count; +} + +// Internal functions in url_parse.cc that parse the path, that is, everything +// following the authority section. The input is the range of everything +// following the authority section, and the output is the identified ranges. +// +// This is designed for the file URL parser or other consumers who may do +// special stuff at the beginning, but want regular path parsing, it just +// maps to the internal parsing function for paths. +void ParsePathInternal(const char* spec, + const Component& path, + Component* filepath, + Component* query, + Component* ref); +void ParsePathInternal(const base::char16* spec, + const Component& path, + Component* filepath, + Component* query, + Component* ref); + + +// Given a spec and a pointer to the character after the colon following the +// scheme, this parses it and fills in the structure, Every item in the parsed +// structure is filled EXCEPT for the scheme, which is untouched. +void ParseAfterScheme(const char* spec, + int spec_len, + int after_scheme, + Parsed* parsed); +void ParseAfterScheme(const base::char16* spec, + int spec_len, + int after_scheme, + Parsed* parsed); + +} // namespace url diff --git a/src/common/url/url_util.cc b/src/common/url/url_util.cc new file mode 100644 index 0000000..510cd24 --- /dev/null +++ b/src/common/url/url_util.cc @@ -0,0 +1,187 @@ +/* + * Copyright 2014 The Chromium Authors. 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. + */ +#include "url/url_util.h" + +#include +#include + +#include "base/logging.h" +#include "url/url_util_internal.h" + +namespace url { + +namespace { + +// ASCII-specific tolower. The standard library's tolower is locale sensitive, +// so we don't want to use it here. +template +inline Char ToLowerASCII(Char c) { + return (c >= 'A' && c <= 'Z') ? (c + ('a' - 'A')) : c; +} + +// Backend for LowerCaseEqualsASCII. +template +inline bool DoLowerCaseEqualsASCII(Iter a_begin, Iter a_end, const char* b) { + for (Iter it = a_begin; it != a_end; ++it, ++b) { + if (!*b || ToLowerASCII(*it) != *b) + return false; + } + return *b == 0; +} + +const int kNumStandardURLSchemes = 9; +const char* kStandardURLSchemes[kNumStandardURLSchemes] = { + kHttpScheme, + kHttpsScheme, + kFileScheme, // Yes, file urls can have a hostname! + kFtpScheme, + kGopherScheme, + kWsScheme, // WebSocket. + kWssScheme, // WebSocket secure. + kFileSystemScheme, + "rtsp", +}; + +// List of the currently installed standard schemes. This list is lazily +// initialized by InitStandardSchemes and is leaked on shutdown to prevent +// any destructors from being called that will slow us down or cause problems. +std::vector* standard_schemes = NULL; + +// See the LockStandardSchemes declaration in the header. +bool standard_schemes_locked = false; + +// Ensures that the standard_schemes list is initialized, does nothing if it +// already has values. +void InitStandardSchemes() { + if (standard_schemes) + return; + standard_schemes = new std::vector; + for (int i = 0; i < kNumStandardURLSchemes; i++) + standard_schemes->push_back(kStandardURLSchemes[i]); +} + +// Given a string and a range inside the string, compares it to the given +// lower-case |compare_to| buffer. +template +inline bool DoCompareSchemeComponent(const CHAR* spec, + const Component& component, + const char* compare_to) { + if (!component.is_nonempty()) + return compare_to[0] == 0; // When component is empty, match empty scheme. + return LowerCaseEqualsASCII(&spec[component.begin], + &spec[component.end()], + compare_to); +} + +// Returns true if the given scheme identified by |scheme| within |spec| is one +// of the registered "standard" schemes. +template +bool DoIsStandard(const CHAR* spec, const Component& scheme) { + if (!scheme.is_nonempty()) + return false; // Empty or invalid schemes are non-standard. + + InitStandardSchemes(); + for (size_t i = 0; i < standard_schemes->size(); i++) { + if (LowerCaseEqualsASCII(&spec[scheme.begin], &spec[scheme.end()], + standard_schemes->at(i))) + return true; + } + return false; +} + + +} // namespace + +void Initialize() { + InitStandardSchemes(); +} + +void Shutdown() { + if (standard_schemes) { + delete standard_schemes; + standard_schemes = NULL; + } +} + +void LockStandardSchemes() { + standard_schemes_locked = true; +} + + + +// Front-ends for LowerCaseEqualsASCII. +bool LowerCaseEqualsASCII(const char* a_begin, + const char* a_end, + const char* b) { + return DoLowerCaseEqualsASCII(a_begin, a_end, b); +} + +bool LowerCaseEqualsASCII(const char* a_begin, + const char* a_end, + const char* b_begin, + const char* b_end) { + while (a_begin != a_end && b_begin != b_end && + ToLowerASCII(*a_begin) == *b_begin) { + a_begin++; + b_begin++; + } + return a_begin == a_end && b_begin == b_end; +} + +bool LowerCaseEqualsASCII(const base::char16* a_begin, + const base::char16* a_end, + const char* b) { + return DoLowerCaseEqualsASCII(a_begin, a_end, b); +} + + +//========================================= + +bool IsStandard(const char* spec, const Component& scheme) { + return DoIsStandard(spec, scheme); +} + +bool IsStandard(const base::char16* spec, const Component& scheme) { + return DoIsStandard(spec, scheme); +} + +bool CompareSchemeComponent(const char* spec, + const Component& component, + const char* compare_to) { + return DoCompareSchemeComponent(spec, component, compare_to); +} + +bool CompareSchemeComponent(const base::char16* spec, + const Component& component, + const char* compare_to) { + return DoCompareSchemeComponent(spec, component, compare_to); +} + +} // namespace url diff --git a/src/common/url/url_util.h b/src/common/url/url_util.h new file mode 100644 index 0000000..8d43d73 --- /dev/null +++ b/src/common/url/url_util.h @@ -0,0 +1,72 @@ +/* + * Copyright 2014 The Chromium Authors. 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. + */ +#pragma once + +#include + +#include "base/strings/string16.h" +#include "url/url_constants.h" +#include "url/url_export.h" +#include "url/third_party/mozilla/url_parse.h" + +namespace url { + +// Returns true if the given string represents a standard URL. This means that +// either the scheme is in the list of known standard schemes. +URL_EXPORT bool IsStandard(const char* spec, const Component& scheme); +URL_EXPORT bool IsStandard(const base::char16* spec, const Component& scheme); + +// TODO(brettw) remove this. This is a temporary compatibility hack to avoid +// breaking the WebKit build when this version is synced via Chrome. +inline bool IsStandard(const char* spec, + int spec_len, + const Component& scheme) { + return IsStandard(spec, scheme); +} + +// Compare the lower-case form of the given string against the given ASCII +// string. This is useful for doing checking if an input string matches some +// token, and it is optimized to avoid intermediate string copies. +// +// The versions of this function that don't take a b_end assume that the b +// string is NULL terminated. +URL_EXPORT bool LowerCaseEqualsASCII(const char* a_begin, + const char* a_end, + const char* b); +URL_EXPORT bool LowerCaseEqualsASCII(const char* a_begin, + const char* a_end, + const char* b_begin, + const char* b_end); +URL_EXPORT bool LowerCaseEqualsASCII(const base::char16* a_begin, + const base::char16* a_end, + const char* b); + + +} // namespace url diff --git a/src/common/url/url_util_internal.h b/src/common/url/url_util_internal.h new file mode 100644 index 0000000..fe12d3e --- /dev/null +++ b/src/common/url/url_util_internal.h @@ -0,0 +1,48 @@ +/* + * Copyright 2014 The Chromium Authors. 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. + */ +#pragma once + +#include + +#include "base/strings/string16.h" +#include "url/third_party/mozilla/url_parse.h" + +namespace url { + +// Given a string and a range inside the string, compares it to the given +// lower-case |compare_to| buffer. +bool CompareSchemeComponent(const char* spec, + const Component& component, + const char* compare_to); +bool CompareSchemeComponent(const base::char16* spec, + const Component& component, + const char* compare_to); + +} // namespace url diff --git a/src/curl/CMakeLists.txt b/src/curl/CMakeLists.txt new file mode 100644 index 0000000..f26aa2a --- /dev/null +++ b/src/curl/CMakeLists.txt @@ -0,0 +1,53 @@ +# Copyright (c) 2015 Samsung Electronics Co., Ltd 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. +# +# @file CMakeLists.txt +# @author Kyungwook Tak (k.tak@samsung.com) +# @brief TPKP curl lib makefile +# +PKG_CHECK_MODULES(TPKP_CURL_DEP + REQUIRED + openssl + libcurl + dlog + ) + +INCLUDE_DIRECTORIES( + SYSTEM + include + ${PROJECT_SOURCE_DIR}/src/common/include # common library interface header + ${TPKP_CURL_DEP_INCLUDE_DIRS} + ) + +SET(TPKP_CURL_SRCS + tpkp_curl.cpp + ) + +ADD_LIBRARY(${TARGET_TPKP_CURL_LIB} SHARED ${TPKP_CURL_SRCS}) + +SET_TARGET_PROPERTIES(${TARGET_TPKP_CURL_LIB} + PROPERTIES + COMPILE_FLAGS "-D_GNU_SOURCE -fPIC -fvisibility=hidden" + SOVERSION ${SO_VERSION} + VERSION ${VERSION} + ) + +TARGET_LINK_LIBRARIES(${TARGET_TPKP_CURL_LIB} + ${TARGET_TPKP_COMMON_LIB} + ${TPKP_CURL_DEP_LIBRARIES} + ) + +INSTALL(TARGETS ${TARGET_TPKP_CURL_LIB} DESTINATION ${LIB_INSTALL_DIR}) + +INSTALL(FILES include/tpkp_curl.h DESTINATION ${INCLUDEDIR}/tpkp/curl) diff --git a/src/curl/include/tpkp_curl.h b/src/curl/include/tpkp_curl.h new file mode 100644 index 0000000..8b0126b --- /dev/null +++ b/src/curl/include/tpkp_curl.h @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd 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. + */ +/* + * @file tpkp_curl.h + * @author Kyungwook Tak (k.tak@samsung.com) + * @version 1.0 + * @brief Tizen Https Public Key Pinning interface for libcurl. + */ +#ifndef TPKP_CURL_H_ +#define TPKP_CURL_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +/* + * @brief verify_callback of OpenSSL SSL_CTX_set_verify. + * + * @remarks verify_callback which verifies HPKP with url set by + * tpkp_curl_set_url_data() in ssl_ctx_callback set by + * CURLOPT_SSL_CTX_FUNCTION curl option. + * @remarks tpkp_curl_set_url_data() should be used to set url data + * on ssl_ctx_callback. + * + * + * @param[in] preverify_ok Whether the verification of the certificate in + * question was passed(1) or not(0) + * @param[in] x509_ctx pointer to the complete context used for + * certificate chain verification + * + * @return return 1 on success, otherwise return 0. + * + * @see tpkp_curl_set_url_data() + */ +int tpkp_curl_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx); + +/* + * @brief Sets current url to check pinned info later (in tpkp_curl_verify_callback()) + * + * @remarks Url data is saved thread-specifically. + * @remarks tpkp_curl_cleanup() should be called before current thread ended + * or tpkp_curl_cleanup_all() should be called on thread globally + * before the process ended to use libcurl. + * + * @param[in] curl cURL handle + * + * @return #TPKP_E_NONE on success. + * + * @see tpkp_curl_cleanup() + * @see tpkp_curl_cleanup_all() + */ +tpkp_e tpkp_curl_set_url_data(CURL *curl); + +/* + * @brief Sets verify_callback with SSL_CTX_set_verify. + * + * @remarks For clients who doesn't use SSL_CTX_set_verify in ssl_ctx_callback. + * @remarks url data is set internall, so client doesn't needed to call + * tpkp_curl_set_url_data(). + * @remarks tpkp_curl_cleanup() should be called before current thread ended + * or tpkp_curl_cleanup_all() should be called on thread globally + * before the process ended to use libcurl. + * + * @param[in] curl cURL handle + * @param[in/out] ssl_ctx OpenSSL SSL_CTX + * + * @return #TPKP_E_NONE on success. + * + * @see tpkp_curl_cleanup() + * @see tpkp_curl_cleanup_all() + */ +tpkp_e tpkp_curl_set_verify(CURL *curl, SSL_CTX *ssl_ctx); + +/* + * @brief SSL context callback for OpenSSL. callback type is defined by libcurl. + * + * @remarks Refer CURLOPT_SSL_CTX_FUNCTION and ssl_ctx_callback defined in libcurl. + * @remarks tpkp_curl_cleanup() should be called before current thread ended + * or tpkp_curl_cleanup_all() should be called on thread globally + * before the process ended to use libcurl. + * + * @param[in] curl cURL handle + * @param[in/out] ssl_ctx OpenSSL SSL_CTX + * @param[in] userptr Not used + * + * @return TPKP_E_NONE on success. + * + * @retval #TPKP_E_NONE + * + * @see tpkp_curl_cleanup() + * @see tpkp_curl_cleanup_all() + */ +CURLcode tpkp_curl_ssl_ctx_callback(CURL *curl, void *ssl_ctx, void *userptr); + +/* + * @brief Cleans up memory of current thread. + * + * @remarks Only cleans up current thread's specific memory. It should be called + * inside of thread before end. + * @remarks Call beside of curl_[easy|multi]_cleanup(). + * + * @see tpkp_curl_ssl_ctx_callback() + * @see tpkp_curl_set_verify() + * @see tpkp_curl_set_url_data() + */ +void tpkp_curl_cleanup(void); + +/* + * @brief Cleans up all memory used by tpkp_curl API. + * + * @remarks Should be called thread-globally, after all jobs done by worker threads. + * @remarks Call beside of curl_global_cleanup(). + * + * @see tpkp_curl_ssl_ctx_callback() + * @see tpkp_curl_set_verify() + * @see tpkp_curl_set_url_data() + */ +void tpkp_curl_cleanup_all(void); + +#ifdef __cplusplus +} +#endif + +#endif /* TPKP_CURL_H_ */ diff --git a/src/curl/tpkp_curl.cpp b/src/curl/tpkp_curl.cpp new file mode 100644 index 0000000..2ba5cfe --- /dev/null +++ b/src/curl/tpkp_curl.cpp @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd 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. + */ +/* + * @file tpkp_curl.cpp + * @author Kyungwook Tak (k.tak@samsung.com) + * @version 1.0 + * @brief Tizen Https Public Key Pinning implementation for libcurl. + */ +#include +#include +#include +#include + +#include + +#include "tpkp_common.h" +#include "tpkp_curl.h" + +namespace { + +std::map s_urlmap; +std::mutex s_mutex; + +inline CURLcode err_tpkp_to_curle(tpkp_e err) noexcept +{ + switch (err) { + case TPKP_E_NONE: return CURLE_OK; + case TPKP_E_MEMORY: return CURLE_OUT_OF_MEMORY; + case TPKP_E_INVALID_URL: return CURLE_URL_MALFORMAT; + case TPKP_E_NO_URL_DATA: return CURLE_SSL_CERTPROBLEM; + case TPKP_E_PUBKEY_MISMATCH: return CURLE_SSL_PINNEDPUBKEYNOTMATCH; + case TPKP_E_INVALID_CERT: + case TPKP_E_INVALID_PEER_CERT_CHAIN: + case TPKP_E_FAILED_GET_PUBKEY_HASH: return CURLE_PEER_FAILED_VERIFICATION; + case TPKP_E_STD_EXCEPTION: + case TPKP_E_INTERNAL: + default: return CURLE_UNKNOWN_OPTION; + } +} + +TPKP::RawBuffer getPubkeyHash(X509 *cert, TPKP::HashAlgo algo) +{ + std::unique_ptr + pubkeyPtr(X509_get_pubkey(cert), EVP_PKEY_free); + + TPKP_CHECK_THROW_EXCEPTION(pubkeyPtr, + TPKP_E_INVALID_CERT, "Failed to get pubkey from cert."); + + unsigned char *der = nullptr; + auto len = i2d_PUBKEY(pubkeyPtr.get(), &der); + TPKP_CHECK_THROW_EXCEPTION(len > 0, + TPKP_E_INVALID_CERT, "Failed to convert pem pubkey to der."); + + TPKP::RawBuffer pubkeyder(der, der + len); + free(der); + unsigned char *hashResult = nullptr; + TPKP::RawBuffer out; + switch (algo) { + case TPKP::HashAlgo::SHA1: + out.resize(SHA_DIGEST_LENGTH); + hashResult = SHA1(pubkeyder.data(), pubkeyder.size(), out.data()); + + break; + + default: + TPKP_CHECK_THROW_EXCEPTION(false, + TPKP_E_INTERNAL, "Invalid hash algo type in get_pubkey_hash"); + } + + TPKP_CHECK_THROW_EXCEPTION(hashResult, + TPKP_E_FAILED_GET_PUBKEY_HASH, "Failed to get pubkey haso by openssl SHA1."); + + return out; +} + +} // anonymous namespace + + +EXPORT_API +int tpkp_curl_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx) +{ + tpkp_e res = TPKP::ExceptionSafe([&]{ + TPKP_CHECK_THROW_EXCEPTION(preverify_ok != 0, + TPKP_E_INTERNAL, "verify callback already failed before enter tpkp_curl callback"); + + auto tid = TPKP::getThreadId(); + std::string url; + + { + std::lock_guard lock(s_mutex); + url = s_urlmap[tid]; + } + + TPKP_CHECK_THROW_EXCEPTION(!url.empty(), + TPKP_E_NO_URL_DATA, "No url for thread id[" << tid << "] in map"); + + SLOGD("get url[%s] of thread id[%u]", url.c_str(), tid); + + TPKP::Context ctx(url); + if (!ctx.hasPins()) { + SLOGI("Skip. No static pin data for url: %s", url.c_str()); + return; + } + + auto chain = X509_STORE_CTX_get1_chain(x509_ctx); + int num = sk_X509_num(chain); + TPKP_CHECK_THROW_EXCEPTION(num != -1, + TPKP_E_INVALID_PEER_CERT_CHAIN, + "Invalid cert chain from x509_ctx in verify callback."); + + for (int i = 0; i < num; i++) + ctx.addPubkeyHash( + TPKP::HashAlgo::SHA1, + getPubkeyHash(sk_X509_value(chain, i), TPKP::HashAlgo::SHA1)); + + TPKP_CHECK_THROW_EXCEPTION(ctx.checkPubkeyPins(), + TPKP_E_PUBKEY_MISMATCH, "The pubkey mismatched with pinned data!"); + }); + + return (res == TPKP_E_NONE) ? 1 : 0; +} + +EXPORT_API +tpkp_e tpkp_curl_set_url_data(CURL *curl) +{ + return TPKP::ExceptionSafe([&]{ + char *url = nullptr; + curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url); + + auto tid = TPKP::getThreadId(); + + { + std::lock_guard lock(s_mutex); + s_urlmap[tid] = url; + } + + SLOGD("set url[%s] of thread id[%u]", url, tid); + }); +} + +EXPORT_API +tpkp_e tpkp_curl_set_verify(CURL *curl, SSL_CTX *ssl_ctx) +{ + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, tpkp_curl_verify_callback); + return tpkp_curl_set_url_data(curl); +} + +EXPORT_API +CURLcode tpkp_curl_ssl_ctx_callback(CURL *curl, void *ssl_ctx, void *) +{ + return err_tpkp_to_curle(tpkp_curl_set_verify(curl, (SSL_CTX *)ssl_ctx)); +} + +EXPORT_API +void tpkp_curl_cleanup(void) +{ + tpkp_e res = TPKP::ExceptionSafe([&]{ + auto tid = TPKP::getThreadId(); + + { + std::lock_guard lock(s_mutex); + s_urlmap.erase(tid); + } + + SLOGD("cleanup url data for thread id[%u]", tid); + }); + + (void) res; +} + +EXPORT_API +void tpkp_curl_cleanup_all(void) +{ + s_urlmap.clear(); +} diff --git a/src/gnutls/CMakeLists.txt b/src/gnutls/CMakeLists.txt new file mode 100644 index 0000000..adf0e92 --- /dev/null +++ b/src/gnutls/CMakeLists.txt @@ -0,0 +1,52 @@ +# Copyright (c) 2015 Samsung Electronics Co., Ltd 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. +# +# @file CMakeLists.txt +# @author Kyungwook Tak (k.tak@samsung.com) +# @brief TPKP gnutls lib makefile +# +PKG_CHECK_MODULES(TPKP_GNUTLS_DEP + REQUIRED + gnutls + dlog + ) + +INCLUDE_DIRECTORIES( + SYSTEM + include + ${PROJECT_SOURCE_DIR}/src/common/include # common library interface header + ${TPKP_GNUTLS_DEP_INCLUDE_DIRS} + ) + +SET(TPKP_GNUTLS_SRCS + tpkp_gnutls.cpp + ) + +ADD_LIBRARY(${TARGET_TPKP_GNUTLS_LIB} SHARED ${TPKP_GNUTLS_SRCS}) + +SET_TARGET_PROPERTIES(${TARGET_TPKP_GNUTLS_LIB} + PROPERTIES + COMPILE_FLAGS "-D_GNU_SOURCE -fPIC -fvisibility=hidden" + SOVERSION ${SO_VERSION} + VERSION ${VERSION} + ) + +TARGET_LINK_LIBRARIES(${TARGET_TPKP_GNUTLS_LIB} + ${TARGET_TPKP_COMMON_LIB} + ${TPKP_GNUTLS_DEP_LIBRARIES} + ) + +INSTALL(TARGETS ${TARGET_TPKP_GNUTLS_LIB} DESTINATION ${LIB_INSTALL_DIR}) + +INSTALL(FILES include/tpkp_gnutls.h DESTINATION ${INCLUDEDIR}/tpkp/gnutls) diff --git a/src/gnutls/include/tpkp_gnutls.h b/src/gnutls/include/tpkp_gnutls.h new file mode 100644 index 0000000..fd412d2 --- /dev/null +++ b/src/gnutls/include/tpkp_gnutls.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd 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. + */ +/* + * @file tpkp_gnutls.h + * @author Kyungwook Tak (k.tak@samsung.com) + * @version 1.0 + * @brief Tizen Https Public Key Pinning interface for gnutls. + */ +#ifndef TPKP_GNUTLS_H_ +#define TPKP_GNUTLS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/* + * @brief gnutls_certificate_verify_function of verifying pubkey pinning + * + * @remarks set by gnutls_certificate_verify_function(). + * + * + */ +int tpkp_gnutls_verify_callback(gnutls_session_t session); + +tpkp_e tpkp_gnutls_set_url_data(const char *url); + +void tpkp_gnutls_cleanup(void); + +void tpkp_gnutls_cleanup_all(void); + +#ifdef __cplusplus +} +#endif + +#endif /* TPKP_GNUTLS_H_ */ diff --git a/src/gnutls/tpkp_gnutls.cpp b/src/gnutls/tpkp_gnutls.cpp new file mode 100644 index 0000000..b4ef9f4 --- /dev/null +++ b/src/gnutls/tpkp_gnutls.cpp @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd 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. + */ +/* + * @file tpkp_gnutls.cpp + * @author Kyungwook Tak (k.tak@samsung.com) + * @version 1.0 + * @brief Tizen Https Public Key Pinning implementation for gnutls. + */ +#include +#include +#include +#include + +#include +#include +#include + +#include "tpkp_common.h" +#include "tpkp_gnutls.h" + +namespace { + +std::map s_urlmap; +std::mutex s_mutex; + +inline int err_tpkp_to_gnutlse(tpkp_e err) noexcept +{ + switch (err) { + case TPKP_E_NONE: return GNUTLS_E_SUCCESS; + case TPKP_E_MEMORY: return GNUTLS_E_MEMORY_ERROR; + case TPKP_E_INVALID_URL: return GNUTLS_E_INVALID_SESSION; + case TPKP_E_NO_URL_DATA: return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; + case TPKP_E_PUBKEY_MISMATCH: return GNUTLS_E_CERTIFICATE_KEY_MISMATCH; + case TPKP_E_INVALID_CERT: + case TPKP_E_INVALID_PEER_CERT_CHAIN: + case TPKP_E_FAILED_GET_PUBKEY_HASH: return GNUTLS_E_PK_SIG_VERIFY_FAILED; + case TPKP_E_STD_EXCEPTION: + case TPKP_E_INTERNAL: + default: return GNUTLS_E_INTERNAL_ERROR; + } +} + +TPKP::RawBuffer getPubkeyHash(gnutls_x509_crt_t cert, TPKP::HashAlgo algo) +{ + std::unique_ptr + pubkeyPtr(new gnutls_pubkey_t, [](gnutls_pubkey_t *ptr)->void + { + if (ptr != nullptr) + gnutls_pubkey_deinit(*ptr); + }); + + int ret = gnutls_pubkey_init(pubkeyPtr.get()); + TPKP_CHECK_THROW_EXCEPTION(ret == GNUTLS_E_SUCCESS, + TPKP_E_INTERNAL, + "Failed to gnutls_pubkey_init. gnutls ret: " << ret); + + ret = gnutls_pubkey_import_x509(*pubkeyPtr, cert, 0); + TPKP_CHECK_THROW_EXCEPTION(ret == GNUTLS_E_SUCCESS, + TPKP_E_INVALID_CERT, + "Failed to gnutls_pubkey_import_x509. gnutls ret: " << ret); + + size_t len = 0; + ret = gnutls_pubkey_export(*pubkeyPtr, GNUTLS_X509_FMT_DER, nullptr, &len); + TPKP_CHECK_THROW_EXCEPTION( + (ret == GNUTLS_E_SHORT_MEMORY_BUFFER || ret == GNUTLS_E_SUCCESS) && len != 0, + TPKP_E_INVALID_CERT, + "Failed to gnutls_pubkey_export for getting size. gnutls ret: " + << ret << " desc: " << gnutls_strerror(ret) << " size: " << len); + + TPKP::RawBuffer derbuf(len, 0x00); + ret = gnutls_pubkey_export(*pubkeyPtr, GNUTLS_X509_FMT_DER, derbuf.data(), &len); + TPKP_CHECK_THROW_EXCEPTION(ret == GNUTLS_E_SUCCESS && len == derbuf.size(), + TPKP_E_INVALID_CERT, + "Failed to gnutls_pubkey_export. gnutls ret: " + << ret << " desc: " << gnutls_strerror(ret)); + + gnutls_datum_t pubkeyder = { + derbuf.data(), + static_cast(derbuf.size()) + }; + + auto gnutlsHashAlgo = GNUTLS_DIG_SHA1; /* default hash alog */ + TPKP::RawBuffer out; + switch (algo) { + case TPKP::HashAlgo::SHA1: + out.resize(TPKP::typeCast(TPKP::HashSize::SHA1), 0x00); + len = out.size(); + gnutlsHashAlgo = GNUTLS_DIG_SHA1; + break; + + default: + TPKP_CHECK_THROW_EXCEPTION( + false, + TPKP_E_INTERNAL, + "Invalid hash algo type in getPubkeyHash."); + } + + ret = gnutls_fingerprint(gnutlsHashAlgo, &pubkeyder, out.data(), &len); + TPKP_CHECK_THROW_EXCEPTION(ret == GNUTLS_E_SUCCESS && len == out.size(), + TPKP_E_FAILED_GET_PUBKEY_HASH, + "Failed to gnutls_fingerprint. gnutls ret: " + << ret << " desc: " << gnutls_strerror(ret)); + + return out; +} + +} + +EXPORT_API +int tpkp_gnutls_verify_callback(gnutls_session_t session) +{ + tpkp_e res = TPKP::ExceptionSafe([&]{ + gnutls_certificate_type_t type = gnutls_certificate_type_get(session); + if (type != GNUTLS_CRT_X509) { + // + // TODO: what should we do if it's not x509 type cert? + // for now, just return 0 which means verification success + // + SLOGW("Certificate type of session isn't X509. skipt for now..."); + return; + } + + unsigned int listSize = 0; + const gnutls_datum_t *certChain = gnutls_certificate_get_peers(session, &listSize); + TPKP_CHECK_THROW_EXCEPTION(certChain != nullptr && listSize != 0, + TPKP_E_INVALID_PEER_CERT_CHAIN, + "no certificate from peer!"); + + auto tid = TPKP::getThreadId(); + std::string url; + + { + std::lock_guard lock(s_mutex); + url = s_urlmap[tid]; + } + + TPKP_CHECK_THROW_EXCEPTION( + !url.empty(), + TPKP_E_NO_URL_DATA, + "No url of thread id[" << tid << "]"); + + SLOGD("get url[%s] of thread id[%u]", url.c_str(), tid); + + TPKP::Context ctx(url); + if (!ctx.hasPins()) { + SLOGI("Skip. No static pin data for url: %s", url.c_str()); + return; + } + + for (unsigned int i = 0; i < listSize; i++) { + std::unique_ptr + crtPtr(new gnutls_x509_crt_t, [](gnutls_x509_crt_t *ptr)->void + { + if (ptr != nullptr) + gnutls_x509_crt_deinit(*ptr); + }); + + TPKP_CHECK_THROW_EXCEPTION( + gnutls_x509_crt_init(crtPtr.get()) == GNUTLS_E_SUCCESS, + TPKP_E_INTERNAL, + "Failed to gnutls_x509_crt_init."); + + TPKP_CHECK_THROW_EXCEPTION( + gnutls_x509_crt_import(*crtPtr, certChain++, GNUTLS_X509_FMT_DER) >= 0, + TPKP_E_INVALID_CERT, + "Failed to import DER cert to gnutls crt"); + + ctx.addPubkeyHash( + TPKP::HashAlgo::SHA1, + getPubkeyHash(*crtPtr, TPKP::HashAlgo::SHA1)); + } + + TPKP_CHECK_THROW_EXCEPTION(ctx.checkPubkeyPins(), + TPKP_E_PUBKEY_MISMATCH, "THe pubkey mismatched with pinned data!"); + }); + + return err_tpkp_to_gnutlse(res); +} + +EXPORT_API +tpkp_e tpkp_gnutls_set_url_data(const char *url) +{ + return TPKP::ExceptionSafe([&]{ + pid_t tid = TPKP::getThreadId(); + + { + std::lock_guard lock(s_mutex); + s_urlmap[tid] = url; + } + + SLOGD("set url[%s] of thread id[%u]", url, tid); + }); +} + +EXPORT_API +void tpkp_gnutls_cleanup(void) +{ + tpkp_e res = TPKP::ExceptionSafe([&]{ + auto tid = TPKP::getThreadId(); + + { + std::lock_guard lock(s_mutex); + s_urlmap.erase(tid); + } + + SLOGD("cleanup url data from thread id[%u]", tid); + }); + + (void) res; +} + +EXPORT_API +void tpkp_gnutls_cleanup_all(void) +{ + s_urlmap.clear(); +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..a9c32d3 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,53 @@ +# Copyright (c) 2015 Samsung Electronics Co., Ltd 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. +# +# @file CMakeLists.txt +# @author Kyungwook Tak (k.tak@samsung.com) +# @brief TPKP test makefile +# +ADD_DEFINITIONS("-DBOOST_TEST_DYN_LINK") + +PKG_CHECK_MODULES(TEST_TPKP_DEP + REQUIRED + openssl + libcurl + gnutls + ) + +INCLUDE_DIRECTORIES( + SYSTEM + ${TEST_TPKP_DEP_INCLUDE_DIRS} + ${PROJECT_SOURCE_DIR}/src/curl/include # tpkp API header + ${PROJECT_SOURCE_DIR}/src/gnutls/include # tpkp API header + ${PROJECT_SOURCE_DIR}/src/common/include # tpkp error header in common + ) + +SET(TEST_SRCS + colour_log_formatter.cpp + colors.cpp + main.cpp + gnutls_test.cpp + curl_test.cpp + ) + +ADD_EXECUTABLE(${TARGET_TPKP_TEST} ${TEST_SRCS}) + +TARGET_LINK_LIBRARIES(${TARGET_TPKP_TEST} + ${TEST_TPKP_DEP_LIBRARIES} + ${TARGET_TPKP_GNUTLS_LIB} + ${TARGET_TPKP_CURL_LIB} + boost_unit_test_framework + ) + +INSTALL(TARGETS ${TARGET_TPKP_TEST} DESTINATION bin) diff --git a/test/colors.cpp b/test/colors.cpp new file mode 100644 index 0000000..9e06956 --- /dev/null +++ b/test/colors.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2011 Samsung Electronics Co., Ltd 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. + */ +/* + * @file colors.cpp + * @author Lukasz Wrzosek (l.wrzosek@samsung.com) + * @version 1.0 + * @brief Some constants with definition of colors for Console + * and html output + */ +#include +#include "colors.h" + +namespace TPKP { +namespace Colors { +namespace Text { +const char* BOLD_GREEN_BEGIN = "\033[1;32m"; +const char* BOLD_GREEN_END = "\033[m"; +const char* RED_BEGIN = "\033[0;31m"; +const char* RED_END = "\033[m"; +const char* PURPLE_BEGIN = "\033[0;35m"; +const char* PURPLE_END = "\033[m"; +const char* GREEN_BEGIN = "\033[0;32m"; +const char* GREEN_END = "\033[m"; +const char* CYAN_BEGIN = "\033[0;36m"; +const char* CYAN_END = "\033[m"; +const char* BOLD_RED_BEGIN = "\033[1;31m"; +const char* BOLD_RED_END = "\033[m"; +const char* BOLD_YELLOW_BEGIN = "\033[1;33m"; +const char* BOLD_YELLOW_END = "\033[m"; +const char* BOLD_GOLD_BEGIN = "\033[0;33m"; +const char* BOLD_GOLD_END = "\033[m"; +const char* BOLD_WHITE_BEGIN = "\033[1;37m"; +const char* BOLD_WHITE_END = "\033[m"; +const char* COLOR_END = "\033[m"; +} //namespace Text +} //namespace Colors +} //namespace TPKP diff --git a/test/colors.h b/test/colors.h new file mode 100644 index 0000000..437e4a6 --- /dev/null +++ b/test/colors.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2011 Samsung Electronics Co., Ltd 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. + */ +/* + * @file colors.h + * @author Lukasz Wrzosek (l.wrzosek@samsung.com) + * @version 1.0 + * @brief Some constants with definition of colors for Console + * and html output + */ +#pragma once + +namespace TPKP { +namespace Colors { +namespace Text { +extern const char* BOLD_GREEN_BEGIN; +extern const char* BOLD_GREEN_END; +extern const char* PURPLE_BEGIN; +extern const char* PURPLE_END; +extern const char* RED_BEGIN; +extern const char* RED_END; +extern const char* GREEN_BEGIN; +extern const char* GREEN_END; +extern const char* CYAN_BEGIN; +extern const char* CYAN_END; +extern const char* BOLD_RED_BEGIN; +extern const char* BOLD_RED_END; +extern const char* BOLD_YELLOW_BEGIN; +extern const char* BOLD_YELLOW_END; +extern const char* BOLD_GOLD_BEGIN; +extern const char* BOLD_GOLD_END; +extern const char* BOLD_WHITE_BEGIN; +extern const char* BOLD_WHITE_END; +extern const char* COLOR_END; +} //namespace Text +} //namespace Colors +} //namespace TPKP diff --git a/test/colour_log_formatter.cpp b/test/colour_log_formatter.cpp new file mode 100644 index 0000000..08ef10d --- /dev/null +++ b/test/colour_log_formatter.cpp @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd 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. + */ +/* + * @file colour_log_formatter.cpp + * @author Zofia Abramowska (z.abramowska@samsung.com) + * @version 1.0 + * @brief color formatter for test output. + */ +#include +#include +#include +#include +#include + +#include +#include + +#include "colors.h" +#include "colour_log_formatter.h" + +using namespace boost::unit_test; +namespace TPKP { + +namespace { + +const_string +test_phase_identifier() +{ + return framework::is_initialized() + ? const_string( framework::current_test_case().p_name.get() ) + : BOOST_TEST_L( "Test setup" ); +} + +const_string +get_basename(const const_string &file_name) { + return basename(file_name.begin()); +} + +std::string +get_basename(const std::string &file_name) { + return basename(file_name.c_str()); +} + +} // local namespace + +//____________________________________________________________________________// + +void +colour_log_formatter::log_start( + std::ostream& output, + counter_t test_cases_amount ) +{ + if( test_cases_amount > 0 ) + output << "Running " << test_cases_amount << " test " + << (test_cases_amount > 1 ? "cases" : "case") << "...\n"; +} + +//____________________________________________________________________________// + +void +colour_log_formatter::log_finish( std::ostream& ostr ) +{ + ostr.flush(); +} + +//____________________________________________________________________________// + +void +colour_log_formatter::log_build_info( std::ostream& output ) +{ + output << "Platform: " << BOOST_PLATFORM << '\n' + << "Compiler: " << BOOST_COMPILER << '\n' + << "STL : " << BOOST_STDLIB << '\n' + << "Boost : " << BOOST_VERSION/100000 << "." + << BOOST_VERSION/100 % 1000 << "." + << BOOST_VERSION % 100 << std::endl; +} + +//____________________________________________________________________________// + +void +colour_log_formatter::test_unit_start( + std::ostream& output, + test_unit const& tu ) +{ + if (tu.p_type_name->find(const_string("suite")) == 0) { + output << "Starting test " << tu.p_type_name << " \"" << tu.p_name << "\"" << std::endl; + } else { + output << "Running test " << tu.p_type_name << " \"" << tu.p_name << "\"" << std::endl; + } +} + +//____________________________________________________________________________// + +void +colour_log_formatter::test_unit_finish( + std::ostream& output, + test_unit const& tu, + unsigned long elapsed ) +{ + if (tu.p_type_name->find(const_string("suite")) == 0) { + output << "Finished test " << tu.p_type_name << " \"" << tu.p_name << "\""<< std::endl; + return; + } + std::string color = TPKP::Colors::Text::GREEN_BEGIN; + std::string status = "OK"; + if (m_isTestCaseFailed) { + color = TPKP::Colors::Text::RED_BEGIN; + status = "FAIL"; + } + output << "\t" << "[ " << color << status << TPKP::Colors::Text::COLOR_END << " ]"; + + + output << ", " << TPKP::Colors::Text::CYAN_BEGIN << "time: "; + if( elapsed > 0 ) { + if( elapsed % 1000 == 0 ) + output << elapsed/1000 << "ms"; + else + output << elapsed << "mks"; + } else { + output << "N/A"; + } + + output << TPKP::Colors::Text::COLOR_END << std::endl; + m_isTestCaseFailed = false; +} + +//____________________________________________________________________________// + +void +colour_log_formatter::test_unit_skipped( + std::ostream& output, + test_unit const& tu ) +{ + output << "Test " << tu.p_type_name << " \"" << tu.p_name << "\"" << "is skipped" << std::endl; +} + +//____________________________________________________________________________// + +void +colour_log_formatter::log_exception( + std::ostream& output, + log_checkpoint_data const& checkpoint_data, + boost::execution_exception const& ex ) +{ + boost::execution_exception::location const& loc = ex.where(); + output << '\t' << TPKP::Colors::Text::BOLD_YELLOW_BEGIN << get_basename(loc.m_file_name) + << '(' << loc.m_line_num << "), "; + + output << "fatal error in \"" + << (loc.m_function.is_empty() ? test_phase_identifier() : loc.m_function ) << "\": "; + + output << TPKP::Colors::Text::COLOR_END << ex.what(); + + if( !checkpoint_data.m_file_name.is_empty() ) { + output << '\n'; + output << "\tlast checkpoint : " << get_basename(checkpoint_data.m_file_name) + << '(' << checkpoint_data.m_line_num << ")"; + if( !checkpoint_data.m_message.empty() ) + output << ": " << checkpoint_data.m_message; + } + + output << std::endl; + m_isTestCaseFailed = true; +} + +//____________________________________________________________________________// + +void +colour_log_formatter::log_entry_start( + std::ostream& output, + log_entry_data const& entry_data, + log_entry_types let ) +{ + switch( let ) { + case BOOST_UTL_ET_INFO: + output << '\t' << entry_data.m_file_name << '(' << entry_data.m_line_num << "), "; + output << "info: "; + break; + case BOOST_UTL_ET_MESSAGE: + break; + case BOOST_UTL_ET_WARNING: + output << '\t' << get_basename(entry_data.m_file_name) << '(' << entry_data.m_line_num << "), "; + output << "warning in \"" << test_phase_identifier() << "\": "; + break; + case BOOST_UTL_ET_ERROR: + output << '\t' << TPKP::Colors::Text::BOLD_YELLOW_BEGIN << get_basename(entry_data.m_file_name) + << '(' << entry_data.m_line_num << "), "; + output << "error in \"" << test_phase_identifier() << "\": "; + m_isTestCaseFailed = true; + break; + case BOOST_UTL_ET_FATAL_ERROR: + output << '\t' << TPKP::Colors::Text::BOLD_YELLOW_BEGIN << get_basename(entry_data.m_file_name) + << '(' << entry_data.m_line_num << "), "; + output << " fatal error in \"" << test_phase_identifier() << "\": "; + m_isTestCaseFailed = true; + break; + } + output << TPKP::Colors::Text::COLOR_END; +} + +//____________________________________________________________________________// + +void +colour_log_formatter::log_entry_value( + std::ostream& output, + const_string value ) +{ + output << value; +} + +//____________________________________________________________________________// + +void +colour_log_formatter::log_entry_value( + std::ostream& output, + lazy_ostream const& value ) +{ + output << value; +} + +//____________________________________________________________________________// + +void +colour_log_formatter::log_entry_finish( + std::ostream& output ) +{ + output << std::endl; +} + +} // namespace TPKP diff --git a/test/colour_log_formatter.h b/test/colour_log_formatter.h new file mode 100644 index 0000000..b36838a --- /dev/null +++ b/test/colour_log_formatter.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd 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. + */ +/* + * @file colour_log_formatter.h + * @author Zofia Abramowska (z.abramowska@samsung.com) + * @version 1.0 + * @brief color formatter for test output. + */ +#pragma once + +#include + +namespace TPKP { +class colour_log_formatter : public boost::unit_test::unit_test_log_formatter { +public: + // Formatter interface + colour_log_formatter() : m_isTestCaseFailed(false) {} + void log_start( + std::ostream&, + boost::unit_test::counter_t test_cases_amount ); + void log_finish( std::ostream& ); + void log_build_info( std::ostream& ); + + void test_unit_start( + std::ostream&, + boost::unit_test::test_unit const& tu ); + void test_unit_finish( + std::ostream&, + boost::unit_test::test_unit const& tu, + unsigned long elapsed ); + void test_unit_skipped( + std::ostream&, + boost::unit_test::test_unit const& tu ); + + void log_exception( + std::ostream&, + boost::unit_test::log_checkpoint_data const&, + boost::execution_exception const& ex ); + + void log_entry_start( + std::ostream&, + boost::unit_test::log_entry_data const&, + log_entry_types let ); + void log_entry_value( + std::ostream&, + boost::unit_test::const_string value ); + void log_entry_value( + std::ostream&, + boost::unit_test::lazy_ostream const& value ); + void log_entry_finish( std::ostream& ); +private: + bool m_isTestCaseFailed; +}; + +} diff --git a/test/curl_test.cpp b/test/curl_test.cpp new file mode 100644 index 0000000..2c52c49 --- /dev/null +++ b/test/curl_test.cpp @@ -0,0 +1,435 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd 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. + */ +/* + * @file libcurl_sample.cpp + * @author Kyungwook Tak (k.tak@samsung.com) + * @version 1.0 + * @brief tpkp_curl unit test. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +static std::vector UrlList = { + "https://www.google.com", + "https://www.facebook.com", + "https://www.twitter.com", + "https://www.dropbox.com", + "https://www.spideroak.com", + "https://www.youtube.com", + "https://thehackernews.com" /* no static pinned data */ +}; + +const std::string targetUrl = "https://WwW.GooGle.cO.Kr"; +const std::string targetInvalidUrl = "https://WwW.GooGle.cO.Kr11143343jiuj::"; + +int verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx) +{ + if (preverify_ok == 0) + return 0; + + /* + * Do something which isn't related with HPKP here + * And update value to preverify_ok of validation result + */ + + /* call tpkp_verify_callback as additional step */ + return tpkp_curl_verify_callback(preverify_ok, x509_ctx); +} + +static CURLcode ssl_ctx_callback_set_verify(CURL *curl, void *ssl_ctx, void *userptr) +{ + (void)userptr; + + SSL_CTX_set_verify((SSL_CTX *)ssl_ctx, SSL_VERIFY_PEER, verify_callback); + tpkp_e res = tpkp_curl_set_url_data(curl); + if (res != TPKP_E_NONE) + return CURLE_FAILED_INIT; + + return CURLE_OK; +} + +static CURLcode ssl_ctx_callback_not_set_verify(CURL *curl, void *ssl_ctx, void *userptr) +{ + (void)userptr; + + tpkp_e res = tpkp_curl_set_verify(curl, (SSL_CTX *)ssl_ctx); + if (res != TPKP_E_NONE) + return CURLE_FAILED_INIT; + + return CURLE_OK; +} + +static CURL *makeLocalDefaultHandle(std::string url) +{ + CURL *handle = curl_easy_init(); + + BOOST_REQUIRE_MESSAGE( + curl_easy_setopt(handle, CURLOPT_URL, url.c_str()) == CURLE_OK, + "Failed to set opt url : " << targetUrl); + + BOOST_REQUIRE_MESSAGE( + curl_easy_setopt(handle, CURLOPT_VERBOSE, 0L) == CURLE_OK, + "Failed to set opt verbose"); + + BOOST_REQUIRE_MESSAGE( + curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 1L) == CURLE_OK, + "Failed to set opt verify peer"); + + BOOST_REQUIRE_MESSAGE( + curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 2L) == CURLE_OK, + "Failed to set opt verify host"); + + BOOST_REQUIRE_MESSAGE( + curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L) == CURLE_OK, + "Failed to set opt follow location"); + + BOOST_REQUIRE_MESSAGE( + curl_easy_setopt(handle, CURLOPT_NOBODY, 1L) == CURLE_OK, + "Failed to set opt no body"); + + return handle; +} + +static CURL *makeDefaultHandle(std::string url) +{ + curl_global_init(CURL_GLOBAL_DEFAULT); + + return makeLocalDefaultHandle(url); +} + +static void performWithUrl(std::string url) +{ + CURL *curl = makeLocalDefaultHandle(url); + CURLcode res; + + res = curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, tpkp_curl_ssl_ctx_callback); + BOOST_REQUIRE_MESSAGE( + res == CURLE_OK, + "Failed to set opt ssl ctx function. code: " << curl_easy_strerror(res)); + + res = curl_easy_perform(curl); + BOOST_REQUIRE_MESSAGE( + res == CURLE_OK, + "Failed to perform curl: " << curl_easy_strerror(res)); + + tpkp_curl_cleanup(); + curl_easy_cleanup(curl); +} + +BOOST_AUTO_TEST_SUITE(TPKP_CURL_TEST) + +BOOST_AUTO_TEST_CASE(T00101_posivite_notusing_ssl_ctx_func_opt) +{ + CURL *curl = makeDefaultHandle(targetUrl); + CURLcode res; + + res = curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, tpkp_curl_ssl_ctx_callback); + BOOST_REQUIRE_MESSAGE( + res == CURLE_OK, + "Failed to set opt ssl ctx function. code: " << curl_easy_strerror(res)); + + res = curl_easy_perform(curl); + BOOST_REQUIRE_MESSAGE( + res == CURLE_OK, + "Failed to perform curl: " << curl_easy_strerror(res)); + + tpkp_curl_cleanup(); + curl_easy_cleanup(curl); + curl_global_cleanup(); +} + +BOOST_AUTO_TEST_CASE(T00102_posivite_using_ssl_ctx_func_opt_notusing_ssl_ctx_set_verify) +{ + CURL *curl = makeDefaultHandle(targetUrl); + CURLcode res; + + res = curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, ssl_ctx_callback_not_set_verify); + BOOST_REQUIRE_MESSAGE( + res == CURLE_OK, + "Failed to set opt ssl ctx function. code: " << curl_easy_strerror(res)); + + res = curl_easy_perform(curl); + BOOST_REQUIRE_MESSAGE( + res == CURLE_OK, + "Failed to perform curl: " << curl_easy_strerror(res)); + + tpkp_curl_cleanup(); + curl_easy_cleanup(curl); + curl_global_cleanup(); +} + +BOOST_AUTO_TEST_CASE(T00103_posivite_using_ssl_ctx_func_opt_using_ssl_ctx_set_verify) +{ + CURL *curl = makeDefaultHandle(targetUrl); + CURLcode res; + + res = curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, ssl_ctx_callback_set_verify); + BOOST_REQUIRE_MESSAGE( + res == CURLE_OK, + "Failed to set opt ssl ctx function. code: " << curl_easy_strerror(res)); + + res = curl_easy_perform(curl); + BOOST_REQUIRE_MESSAGE( + res == CURLE_OK, + "Failed to perform curl: " << curl_easy_strerror(res)); + + tpkp_curl_cleanup(); + curl_easy_cleanup(curl); + curl_global_cleanup(); +} + +BOOST_AUTO_TEST_CASE(T00104_negative_invalid_url) +{ + CURL *curl = makeDefaultHandle(targetInvalidUrl); + CURLcode res; + + res = curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, ssl_ctx_callback_set_verify); + BOOST_REQUIRE_MESSAGE( + res == CURLE_OK, + "Failed to set opt ssl ctx function. code: " << curl_easy_strerror(res)); + + res = curl_easy_perform(curl); + BOOST_REQUIRE_MESSAGE( + res != CURLE_OK, + "Shouldnot success perform curl: " << curl_easy_strerror(res)); + std::cout << "code: " << res << " description: " << curl_easy_strerror(res) << std::endl; + + tpkp_curl_cleanup(); + curl_easy_cleanup(curl); + curl_global_cleanup(); +} + +BOOST_AUTO_TEST_CASE(T00105_positive_facebook_with_https) +{ + CURL *curl = makeDefaultHandle("https://www.facebook.com"); + CURLcode res; + + res = curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, tpkp_curl_ssl_ctx_callback); + BOOST_REQUIRE_MESSAGE( + res == CURLE_OK, + "Failed to set opt ssl ctx function. code: " << curl_easy_strerror(res)); + + res = curl_easy_perform(curl); + BOOST_REQUIRE_MESSAGE( + res == CURLE_OK, + "Failed to perform curl: " << curl_easy_strerror(res)); + + tpkp_curl_cleanup(); + curl_easy_cleanup(curl); + curl_global_cleanup(); +} + +BOOST_AUTO_TEST_CASE(T00106_positive_facebook_with_http) +{ + CURL *curl = makeDefaultHandle("http://www.facebook.com"); + CURLcode res; + + res = curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, tpkp_curl_ssl_ctx_callback); + BOOST_REQUIRE_MESSAGE( + res == CURLE_OK, + "Failed to set opt ssl ctx function. code: " << curl_easy_strerror(res)); + + res = curl_easy_perform(curl); + BOOST_REQUIRE_MESSAGE( + res == CURLE_OK, + "Failed to perform curl: " << curl_easy_strerror(res)); + + tpkp_curl_cleanup(); + curl_easy_cleanup(curl); + curl_global_cleanup(); +} + +BOOST_AUTO_TEST_CASE(T00107_positive_facebook_with_hostname) +{ + CURL *curl = makeDefaultHandle("www.facebook.com"); + CURLcode res; + + res = curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, tpkp_curl_ssl_ctx_callback); + BOOST_REQUIRE_MESSAGE( + res == CURLE_OK, + "Failed to set opt ssl ctx function. code: " << curl_easy_strerror(res)); + + res = curl_easy_perform(curl); + BOOST_REQUIRE_MESSAGE( + res == CURLE_OK, + "Failed to perform curl: " << curl_easy_strerror(res)); + + tpkp_curl_cleanup(); + curl_easy_cleanup(curl); + curl_global_cleanup(); +} + +BOOST_AUTO_TEST_CASE(T00108_positive_twitter_with_https) +{ + CURL *curl = makeDefaultHandle("https://www.twitter.com"); + CURLcode res; + + res = curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, tpkp_curl_ssl_ctx_callback); + BOOST_REQUIRE_MESSAGE( + res == CURLE_OK, + "Failed to set opt ssl ctx function. code: " << curl_easy_strerror(res)); + + res = curl_easy_perform(curl); + BOOST_REQUIRE_MESSAGE( + res == CURLE_OK, + "Failed to perform curl: " << curl_easy_strerror(res)); + + tpkp_curl_cleanup(); + curl_easy_cleanup(curl); + curl_global_cleanup(); +} + +BOOST_AUTO_TEST_CASE(T00109_positive_dropbox_with_https) +{ + CURL *curl = makeDefaultHandle("https://www.dropbox.com"); + CURLcode res; + + res = curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, tpkp_curl_ssl_ctx_callback); + BOOST_REQUIRE_MESSAGE( + res == CURLE_OK, + "Failed to set opt ssl ctx function. code: " << curl_easy_strerror(res)); + + res = curl_easy_perform(curl); + BOOST_REQUIRE_MESSAGE( + res == CURLE_OK, + "Failed to perform curl: " << curl_easy_strerror(res)); + + tpkp_curl_cleanup(); + curl_easy_cleanup(curl); + curl_global_cleanup(); +} + +BOOST_AUTO_TEST_CASE(T00110_positive_spideroak_with_https) +{ + CURL *curl = makeDefaultHandle("https://www.spideroak.com"); + CURLcode res; + + res = curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, tpkp_curl_ssl_ctx_callback); + BOOST_REQUIRE_MESSAGE( + res == CURLE_OK, + "Failed to set opt ssl ctx function. code: " << curl_easy_strerror(res)); + + res = curl_easy_perform(curl); + BOOST_REQUIRE_MESSAGE( + res == CURLE_OK, + "Failed to perform curl: " << curl_easy_strerror(res)); + + tpkp_curl_cleanup(); + curl_easy_cleanup(curl); + curl_global_cleanup(); +} + +BOOST_AUTO_TEST_CASE(T00111_positive_https_but_no_pinned_data_youtube) +{ + CURL *curl = makeDefaultHandle("https://www.youtube.com"); + CURLcode res; + + res = curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, tpkp_curl_ssl_ctx_callback); + BOOST_REQUIRE_MESSAGE( + res == CURLE_OK, + "Failed to set opt ssl ctx function. code: " << curl_easy_strerror(res)); + + res = curl_easy_perform(curl); + BOOST_REQUIRE_MESSAGE( + res == CURLE_OK, + "Failed to perform curl: " << curl_easy_strerror(res)); + + tpkp_curl_cleanup(); + curl_easy_cleanup(curl); + curl_global_cleanup(); +} + +BOOST_AUTO_TEST_CASE(T00112_positive_https_but_no_pinned_data_hackernews) +{ + CURL *curl = makeDefaultHandle("https://thehackernews.com"); + CURLcode res; + + res = curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, tpkp_curl_ssl_ctx_callback); + BOOST_REQUIRE_MESSAGE( + res == CURLE_OK, + "Failed to set opt ssl ctx function. code: " << curl_easy_strerror(res)); + + res = curl_easy_perform(curl); + BOOST_REQUIRE_MESSAGE( + res == CURLE_OK, + "Failed to perform curl: " << curl_easy_strerror(res)); + + tpkp_curl_cleanup(); + curl_easy_cleanup(curl); + curl_global_cleanup(); + +} + +BOOST_AUTO_TEST_CASE(T00113_positive_threads) +{ + curl_global_init(CURL_GLOBAL_DEFAULT); + + std::vector threads; + + for (const auto &url : UrlList) + threads.emplace_back(performWithUrl, url); + + for (auto &t : threads) + t.join(); + + curl_global_cleanup(); +} + +BOOST_AUTO_TEST_CASE(T00114_positive_threads_2times) +{ + curl_global_init(CURL_GLOBAL_DEFAULT); + + std::vector threads; + + for (int i = 0; i < 2; i++) { + for (const auto &url : UrlList) + threads.emplace_back(performWithUrl, url); + } + + for (auto &t : threads) + t.join(); + + curl_global_cleanup(); +} + +BOOST_AUTO_TEST_CASE(T00113_positive_threads_3times) +{ + curl_global_init(CURL_GLOBAL_DEFAULT); + + std::vector threads; + + for (int i = 0; i < 3; i++) { + for (const auto &url : UrlList) + threads.emplace_back(performWithUrl, url); + } + + for (auto &t : threads) + t.join(); + + curl_global_cleanup(); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/gnutls_test.cpp b/test/gnutls_test.cpp new file mode 100644 index 0000000..3c3399f --- /dev/null +++ b/test/gnutls_test.cpp @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd 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. + */ +/* + * @file gnutls_sample.cpp + * @author Kyungwook Tak (k.tak@samsung.com) + * @version 1.0 + * @brief tpkp_gnutls unit test. + */ +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#include + +#include + +#define SHA1_LENGTH 20 + +namespace { +const std::string targetUrl = "https://WwW.GooGle.cO.Kr"; + +const char certStr[] = + "-----BEGIN CERTIFICATE-----\n" + "MIIDfTCCAuagAwIBAgIDErvmMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT\n" + "MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0\n" + "aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDIwNTIxMDQwMDAwWhcNMTgwODIxMDQwMDAw\n" + "WjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UE\n" + "AxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n" + "CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9m\n" + "OSm9BXiLnTjoBbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIu\n" + "T8rxh0PBFpVXLVDviS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6c\n" + "JmTM386DGXHKTubU1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmR\n" + "Cw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5asz\n" + "PeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo4HwMIHtMB8GA1UdIwQYMBaAFEjm\n" + "aPkr0rKV10fYIyAQTzOYkJ/UMB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrM\n" + "TjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjA6BgNVHR8EMzAxMC+g\n" + "LaArhilodHRwOi8vY3JsLmdlb3RydXN0LmNvbS9jcmxzL3NlY3VyZWNhLmNybDBO\n" + "BgNVHSAERzBFMEMGBFUdIAAwOzA5BggrBgEFBQcCARYtaHR0cHM6Ly93d3cuZ2Vv\n" + "dHJ1c3QuY29tL3Jlc291cmNlcy9yZXBvc2l0b3J5MA0GCSqGSIb3DQEBBQUAA4GB\n" + "AHbhEm5OSxYShjAGsoEIz/AIx8dxfmbuwu3UOx//8PDITtZDOLC5MH0Y0FWDomrL\n" + "NhGc6Ehmo21/uBPUR/6LWlxz/K7ZGzIZOKuXNBSqltLroxwUCEm2u+WR74M26x1W\n" + "b8ravHNjkOR/ez4iyz0H7V84dJzjA1BOoa+Y7mHyhD8S\n" + "-----END CERTIFICATE-----\n"; + +const char sha1hash[] = // kSPKIHash_GeoTrustGlobal + "\xc0\x7a\x98\x68\x8d\x89\xfb\xab\x05\x64" + "\x0c\x11\x7d\xaa\x7d\x65\xb8\xca\xcc\x4e"; + +int tcp_connect(const char *ip, int port) +{ + int sock; + struct sockaddr_in server_addr; + + BOOST_REQUIRE_MESSAGE( + (sock = socket(PF_INET, SOCK_STREAM, 0)) >= 0, + "Cannot create socket!"); + + memset(&server_addr, 0x00, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = inet_addr(ip); + server_addr.sin_port = htons(port); + + BOOST_REQUIRE_MESSAGE( + (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr))) >= 0, + "Cannot connect to sock:" << sock); + + return sock; +} + +void tcp_close(int sd) +{ + close(sd); +} + +typedef struct _GIOGnuTLSChannel GIOGnuTLSChannel; + +struct _GIOGnuTLSChannel { + int fd; +}; + +gnutls_session_t makeDefaultSession(void) +{ + int ret = gnutls_global_init(); + BOOST_REQUIRE_MESSAGE( + ret == GNUTLS_E_SUCCESS, + "Failed to gnutls global init: " << gnutls_strerror(ret)); + + gnutls_session_t session; + ret = gnutls_init(&session, GNUTLS_CLIENT); + BOOST_REQUIRE_MESSAGE( + ret == GNUTLS_E_SUCCESS, + "Failed to gnutls init session: " << gnutls_strerror(ret)); + + ret = gnutls_set_default_priority(session); + BOOST_REQUIRE_MESSAGE( + ret == GNUTLS_E_SUCCESS, + "Failed to set default priority on session: " << gnutls_strerror(ret)); + + return session; +} + +inline gnutls_certificate_credentials_t makeDefaultCred(gnutls_certificate_verify_function *verify_callback) +{ + gnutls_certificate_credentials_t cred; + + int ret = gnutls_certificate_allocate_credentials(&cred); + BOOST_REQUIRE_MESSAGE( + ret == GNUTLS_E_SUCCESS, + "Failed to gnutls_certificate_allocate_credentials: " << gnutls_strerror(ret)); + + ret = gnutls_certificate_set_x509_trust_file(cred, "/etc/ssl/ca-bundle.pem", GNUTLS_X509_FMT_PEM); + BOOST_REQUIRE_MESSAGE( + ret > 0, + "Failed to gnutls_certificate_set_x509_trust_file ret: " << ret); + std::cout << "x509 trust file loaded. cert num: " << ret << std::endl; + + gnutls_certificate_set_verify_function(cred, verify_callback); + + return cred; +} + +} + +BOOST_AUTO_TEST_SUITE(TPKP_GNUTLS_TEST) + +#define MAX_BUF 1024 +#define MSG "GET / HTTP/1.0\r\n\r\n" +BOOST_AUTO_TEST_CASE(T00101_positive) +{ + gnutls_certificate_credentials_t cred = makeDefaultCred(&tpkp_gnutls_verify_callback); + gnutls_session_t session = makeDefaultSession(); + + int ret = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, cred); + BOOST_REQUIRE_MESSAGE( + ret == GNUTLS_E_SUCCESS, + "Failed to gnutls_credentials_set: " << gnutls_strerror(ret)); + + int sd = tcp_connect("216.58.221.36", 443); + + BOOST_REQUIRE_MESSAGE( + tpkp_gnutls_set_url_data(targetUrl.c_str()) == TPKP_E_NONE, + "Failed to tpkp_gnutls_set_url_data."); + + gnutls_transport_set_int(session, sd); + gnutls_handshake_set_timeout(session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); + + do { + ret = gnutls_handshake(session); + } while (ret != GNUTLS_E_SUCCESS && gnutls_error_is_fatal(ret) == 0); + + BOOST_REQUIRE_MESSAGE( + ret == GNUTLS_E_SUCCESS, + "Handshake failed: " << gnutls_strerror(ret)); + + gnutls_bye(session, GNUTLS_SHUT_RDWR); + + tcp_close(sd); + + gnutls_certificate_free_credentials(cred); + + gnutls_deinit(session); + gnutls_global_deinit(); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/main.cpp b/test/main.cpp new file mode 100644 index 0000000..cb9670f --- /dev/null +++ b/test/main.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2000 - 2015 Samsung Electronics Co., Ltd 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 + */ +/* + * @file main.cpp + * @author Kyungwook Tak (k.tak@samsung.com) + * @version 1.0 + * @brief pubkey-pinning internal unit test main of boost test framework. + */ +#define BOOST_TEST_MODULE TPKP_INTERNAL_TEST +#include +#include +#include + +#include "colour_log_formatter.h" + +struct TestConfig { + TestConfig() + { + boost::unit_test::unit_test_log.set_threshold_level( boost::unit_test::log_test_units); + boost::unit_test::results_reporter::set_level(boost::unit_test::SHORT_REPORT); + boost::unit_test::unit_test_log.set_formatter(new TPKP::colour_log_formatter); + } +}; + +BOOST_GLOBAL_FIXTURE(TestConfig) diff --git a/tpkp-curl.pc.in b/tpkp-curl.pc.in new file mode 100644 index 0000000..abe4669 --- /dev/null +++ b/tpkp-curl.pc.in @@ -0,0 +1,9 @@ +libdir=@LIB_INSTALL_DIR@ +includedir=@INCLUDEDIR@ + +Name: tpkp-curl +Description: Tizen HPKP for curl +Version: @VERSION@ +Requires: openssl curl +Libs: -L${libdir} -ltpkp-curl -ltpkp-common +Cflags: -I${includedir}/tpkp/curl -I${includedir}/tpkp/common diff --git a/tpkp-gnutls.pc.in b/tpkp-gnutls.pc.in new file mode 100644 index 0000000..86cb9d9 --- /dev/null +++ b/tpkp-gnutls.pc.in @@ -0,0 +1,9 @@ +libdir=@LIB_INSTALL_DIR@ +includedir=@INCLUDEDIR@ + +Name: tpkp-gnutls +Description: Tizen HPKP for gnutls +Version: @VERSION@ +Requires: gnutls +Libs: -L${libdir} -ltpkp-gnutls -ltpkp-common +Cflags: -I${includedir}/tpkp/gnutls -I${includedir}/tpkp/common -- 2.7.4