From 3b9e4352a7b7c992855bd8301dbe7056b511160b Mon Sep 17 00:00:00 2001 From: Kapeel Sable Date: Tue, 1 Nov 2016 12:46:15 -0700 Subject: [PATCH 1/2] Adds support for sha512 password hashing --- README.md | 20 + .../V11__sha512_pw_hashing_support.sql | 9 + .../apache/commons/codec/BinaryDecoder.java | 38 + .../apache/commons/codec/BinaryEncoder.java | 38 + .../apache/commons/codec/CharEncoding.java | 113 +++ src/org/apache/commons/codec/Charsets.java | 156 ++++ src/org/apache/commons/codec/Decoder.java | 47 + .../commons/codec/DecoderException.java | 86 ++ src/org/apache/commons/codec/Encoder.java | 44 + .../commons/codec/EncoderException.java | 89 ++ .../apache/commons/codec/StringDecoder.java | 38 + .../apache/commons/codec/StringEncoder.java | 38 + .../codec/StringEncoderComparator.java | 91 ++ .../apache/commons/codec/binary/Base32.java | 539 ++++++++++++ .../codec/binary/Base32InputStream.java | 85 ++ .../codec/binary/Base32OutputStream.java | 89 ++ .../apache/commons/codec/binary/Base64.java | 784 +++++++++++++++++ .../codec/binary/Base64InputStream.java | 88 ++ .../codec/binary/Base64OutputStream.java | 92 ++ .../commons/codec/binary/BaseNCodec.java | 525 +++++++++++ .../codec/binary/BaseNCodecInputStream.java | 211 +++++ .../codec/binary/BaseNCodecOutputStream.java | 153 ++++ .../commons/codec/binary/BinaryCodec.java | 301 +++++++ .../codec/binary/CharSequenceUtils.java | 79 ++ src/org/apache/commons/codec/binary/Hex.java | 334 +++++++ .../commons/codec/binary/StringUtils.java | 386 +++++++++ .../apache/commons/codec/binary/package.html | 21 + src/org/apache/commons/codec/digest/B64.java | 79 ++ .../apache/commons/codec/digest/Crypt.java | 151 ++++ .../commons/codec/digest/DigestUtils.java | 819 ++++++++++++++++++ .../commons/codec/digest/HmacAlgorithms.java | 94 ++ .../commons/codec/digest/HmacUtils.java | 794 +++++++++++++++++ .../apache/commons/codec/digest/Md5Crypt.java | 302 +++++++ .../codec/digest/MessageDigestAlgorithms.java | 71 ++ .../commons/codec/digest/Sha2Crypt.java | 545 ++++++++++++ .../commons/codec/digest/UnixCrypt.java | 413 +++++++++ .../apache/commons/codec/digest/package.html | 24 + src/org/apache/commons/codec/overview.html | 29 + src/org/apache/commons/codec/package.html | 100 +++ src/org/ohmage/cache/PreferenceCache.java | 5 + src/org/ohmage/domain/ServerConfig.java | 15 + .../query/impl/AuthenticationQuery.java | 28 +- src/org/ohmage/service/ConfigServices.java | 20 + src/org/ohmage/service/UserServices.java | 77 +- 44 files changed, 8034 insertions(+), 26 deletions(-) create mode 100644 db/migration/V11__sha512_pw_hashing_support.sql create mode 100644 src/org/apache/commons/codec/BinaryDecoder.java create mode 100644 src/org/apache/commons/codec/BinaryEncoder.java create mode 100644 src/org/apache/commons/codec/CharEncoding.java create mode 100644 src/org/apache/commons/codec/Charsets.java create mode 100644 src/org/apache/commons/codec/Decoder.java create mode 100644 src/org/apache/commons/codec/DecoderException.java create mode 100644 src/org/apache/commons/codec/Encoder.java create mode 100644 src/org/apache/commons/codec/EncoderException.java create mode 100644 src/org/apache/commons/codec/StringDecoder.java create mode 100644 src/org/apache/commons/codec/StringEncoder.java create mode 100644 src/org/apache/commons/codec/StringEncoderComparator.java create mode 100644 src/org/apache/commons/codec/binary/Base32.java create mode 100644 src/org/apache/commons/codec/binary/Base32InputStream.java create mode 100644 src/org/apache/commons/codec/binary/Base32OutputStream.java create mode 100644 src/org/apache/commons/codec/binary/Base64.java create mode 100644 src/org/apache/commons/codec/binary/Base64InputStream.java create mode 100644 src/org/apache/commons/codec/binary/Base64OutputStream.java create mode 100644 src/org/apache/commons/codec/binary/BaseNCodec.java create mode 100644 src/org/apache/commons/codec/binary/BaseNCodecInputStream.java create mode 100644 src/org/apache/commons/codec/binary/BaseNCodecOutputStream.java create mode 100644 src/org/apache/commons/codec/binary/BinaryCodec.java create mode 100644 src/org/apache/commons/codec/binary/CharSequenceUtils.java create mode 100644 src/org/apache/commons/codec/binary/Hex.java create mode 100644 src/org/apache/commons/codec/binary/StringUtils.java create mode 100644 src/org/apache/commons/codec/binary/package.html create mode 100644 src/org/apache/commons/codec/digest/B64.java create mode 100644 src/org/apache/commons/codec/digest/Crypt.java create mode 100644 src/org/apache/commons/codec/digest/DigestUtils.java create mode 100644 src/org/apache/commons/codec/digest/HmacAlgorithms.java create mode 100644 src/org/apache/commons/codec/digest/HmacUtils.java create mode 100644 src/org/apache/commons/codec/digest/Md5Crypt.java create mode 100644 src/org/apache/commons/codec/digest/MessageDigestAlgorithms.java create mode 100644 src/org/apache/commons/codec/digest/Sha2Crypt.java create mode 100644 src/org/apache/commons/codec/digest/UnixCrypt.java create mode 100644 src/org/apache/commons/codec/digest/package.html create mode 100644 src/org/apache/commons/codec/overview.html create mode 100644 src/org/apache/commons/codec/package.html diff --git a/README.md b/README.md index 7850f8cd..cf40d899 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,26 @@ ohmage depends on a set of directories to store log files and user data. By defa Any Servlet 3.0 compliant container should work. Internally, we use Tomcat. To build the WAR file, use `ant clean dist`, which will produce an ssl-disabled container. It should be noted that we do not recommend having the servlet itself handle SSL, and instead suggest you use a web server like nginx or apache to do SSL termination. +## Using SHA-512 for Password Hashing + +The Blowfish algorithm is used by default for password hashing. Ohmage supports SHA512 algorithm for password hashing. + +To use SHA-512, run the following commands in MySQL console as soon as ohmage is up and running: +``` +--Enables SHA-512 password hashing +UPDATE `ohmage`.`preference` SET `p_value` = 'true' WHERE `p_key` = 'sha512_password_hash_enabled'; +--Updates the default SHA512 password hash for `ohmage.admin` user to `ohmage.passwd` +UPDATE `ohmage`.`user` SET `password` = '$6$Afmg23YTsd$113jh7VsD6q6wDnDWD9SqJUzobqjFIuGMhpgpuXM49acjyjFfWOGAhzT7W7zRleIhN2Xe.xH7ki2bk8nBlsX4/' WHERE `username` = 'ohmage.admin' +``` + +To use blowfish, run the following commands: +``` +--Disables SHA-512 password hashing (thus enabling default blowfish) +UPDATE `ohmage`.`preference` SET `p_value` = 'false' WHERE `p_key` = 'sha512_password_hash_enabled'; +--Updates the default blowfish password hash for `ohmage.admin` user to `ohmage.passwd` +UPDATE `ohmage`.`user` SET `password` = '$2a$13$yxus2tQ3/QiOwWcELImOQuy9d5PXWbByQ6Bhp52b1se7fNYGFxN5i' WHERE `username` = 'ohmage.admin' +``` + # Collaboration The coding rules are loose, and the best reference would be other parts of the code. A few rules we do have are: diff --git a/db/migration/V11__sha512_pw_hashing_support.sql b/db/migration/V11__sha512_pw_hashing_support.sql new file mode 100644 index 00000000..5b9f8df0 --- /dev/null +++ b/db/migration/V11__sha512_pw_hashing_support.sql @@ -0,0 +1,9 @@ +-- Adds configuration option for enabling sha512 password hashing +-- with default value of false + +INSERT INTO preference (p_key, p_value) VALUES + ('sha512_password_hash_enabled', 'false') + ON DUPLICATE KEY UPDATE p_value=VALUES(p_value); + +-- Changes password column length to 120 chars to accomadate sha512 password hashes +ALTER TABLE `user` MODIFY `password` VARCHAR(120); diff --git a/src/org/apache/commons/codec/BinaryDecoder.java b/src/org/apache/commons/codec/BinaryDecoder.java new file mode 100644 index 00000000..6f7dcd63 --- /dev/null +++ b/src/org/apache/commons/codec/BinaryDecoder.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec; + +/** + * Defines common decoding methods for byte array decoders. + * + * @version $Id: BinaryDecoder.java 1379145 2012-08-30 21:02:52Z tn $ + */ +public interface BinaryDecoder extends Decoder { + + /** + * Decodes a byte array and returns the results as a byte array. + * + * @param source + * A byte array which has been encoded with the appropriate encoder + * @return a byte array that contains decoded content + * @throws DecoderException + * A decoder exception is thrown if a Decoder encounters a failure condition during the decode process. + */ + byte[] decode(byte[] source) throws DecoderException; +} + diff --git a/src/org/apache/commons/codec/BinaryEncoder.java b/src/org/apache/commons/codec/BinaryEncoder.java new file mode 100644 index 00000000..75049022 --- /dev/null +++ b/src/org/apache/commons/codec/BinaryEncoder.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec; + +/** + * Defines common encoding methods for byte array encoders. + * + * @version $Id: BinaryEncoder.java 1379145 2012-08-30 21:02:52Z tn $ + */ +public interface BinaryEncoder extends Encoder { + + /** + * Encodes a byte array and return the encoded data as a byte array. + * + * @param source + * Data to be encoded + * @return A byte array containing the encoded data + * @throws EncoderException + * thrown if the Encoder encounters a failure condition during the encoding process. + */ + byte[] encode(byte[] source) throws EncoderException; +} + diff --git a/src/org/apache/commons/codec/CharEncoding.java b/src/org/apache/commons/codec/CharEncoding.java new file mode 100644 index 00000000..f7b75341 --- /dev/null +++ b/src/org/apache/commons/codec/CharEncoding.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec; + +/** + * Character encoding names required of every implementation of the Java platform. + * + * From the Java documentation Standard charsets: + *

+ * Every implementation of the Java platform is required to support the following character encodings. Consult the + * release documentation for your implementation to see if any other encodings are supported. Consult the release + * documentation for your implementation to see if any other encodings are supported. + *

+ * + * + * + * This perhaps would best belong in the [lang] project. Even if a similar interface is defined in [lang], it is not + * foreseen that [codec] would be made to depend on [lang]. + * + *

+ * This class is immutable and thread-safe. + *

+ * + * @see Standard charsets + * @since 1.4 + * @version $Id: CharEncoding.java 1563226 2014-01-31 19:38:06Z ggregory $ + */ +public class CharEncoding { + /** + * CharEncodingISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1. + *

+ * Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard charsets + */ + public static final String ISO_8859_1 = "ISO-8859-1"; + + /** + * Seven-bit ASCII, also known as ISO646-US, also known as the Basic Latin block of the Unicode character set. + *

+ * Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard charsets + */ + public static final String US_ASCII = "US-ASCII"; + + /** + * Sixteen-bit Unicode Transformation Format, The byte order specified by a mandatory initial byte-order mark + * (either order accepted on input, big-endian used on output) + *

+ * Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard charsets + */ + public static final String UTF_16 = "UTF-16"; + + /** + * Sixteen-bit Unicode Transformation Format, big-endian byte order. + *

+ * Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard charsets + */ + public static final String UTF_16BE = "UTF-16BE"; + + /** + * Sixteen-bit Unicode Transformation Format, little-endian byte order. + *

+ * Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard charsets + */ + public static final String UTF_16LE = "UTF-16LE"; + + /** + * Eight-bit Unicode Transformation Format. + *

+ * Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard charsets + */ + public static final String UTF_8 = "UTF-8"; +} diff --git a/src/org/apache/commons/codec/Charsets.java b/src/org/apache/commons/codec/Charsets.java new file mode 100644 index 00000000..359f49ee --- /dev/null +++ b/src/org/apache/commons/codec/Charsets.java @@ -0,0 +1,156 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.codec; + +import java.nio.charset.Charset; + +/** + * Charsets required of every implementation of the Java platform. + * + * From the Java documentation Standard + * charsets: + *

+ * Every implementation of the Java platform is required to support the following character encodings. Consult the + * release documentation for your implementation to see if any other encodings are supported. Consult the release + * documentation for your implementation to see if any other encodings are supported. + *

+ * + * + * + * This perhaps would best belong in the Commons Lang project. Even if a similar class is defined in Commons Lang, it is + * not foreseen that Commons Codec would be made to depend on Commons Lang. + * + *

+ * This class is immutable and thread-safe. + *

+ * + * @see Standard charsets + * @since 1.7 + * @version $Id: CharEncoding.java 1173287 2011-09-20 18:16:19Z ggregory $ + */ +public class Charsets { + + // + // This class should only contain Charset instances for required encodings. This guarantees that it will load + // correctly and without delay on all Java platforms. + // + + /** + * Returns the given Charset or the default Charset if the given Charset is null. + * + * @param charset + * A charset or null. + * @return the given Charset or the default Charset if the given Charset is null + */ + public static Charset toCharset(final Charset charset) { + return charset == null ? Charset.defaultCharset() : charset; + } + + /** + * Returns a Charset for the named charset. If the name is null, return the default Charset. + * + * @param charset + * The name of the requested charset, may be null. + * @return a Charset for the named charset + * @throws java.nio.charset.UnsupportedCharsetException + * If the named charset is unavailable + */ + public static Charset toCharset(final String charset) { + return charset == null ? Charset.defaultCharset() : Charset.forName(charset); + } + + /** + * CharEncodingISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1. + *

+ * Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard charsets + * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} + */ + @Deprecated + public static final Charset ISO_8859_1 = Charset.forName(CharEncoding.ISO_8859_1); + + /** + * Seven-bit ASCII, also known as ISO646-US, also known as the Basic Latin block of the Unicode character set. + *

+ * Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard charsets + * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} + */ + @Deprecated + public static final Charset US_ASCII = Charset.forName(CharEncoding.US_ASCII); + + /** + * Sixteen-bit Unicode Transformation Format, The byte order specified by a mandatory initial byte-order mark + * (either order accepted on input, big-endian used on output) + *

+ * Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard charsets + * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} + */ + @Deprecated + public static final Charset UTF_16 = Charset.forName(CharEncoding.UTF_16); + + /** + * Sixteen-bit Unicode Transformation Format, big-endian byte order. + *

+ * Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard charsets + * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} + */ + @Deprecated + public static final Charset UTF_16BE = Charset.forName(CharEncoding.UTF_16BE); + + /** + * Sixteen-bit Unicode Transformation Format, little-endian byte order. + *

+ * Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard charsets + * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} + */ + @Deprecated + public static final Charset UTF_16LE = Charset.forName(CharEncoding.UTF_16LE); + + /** + * Eight-bit Unicode Transformation Format. + *

+ * Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard charsets + * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} + */ + @Deprecated + public static final Charset UTF_8 = Charset.forName(CharEncoding.UTF_8); +} diff --git a/src/org/apache/commons/codec/Decoder.java b/src/org/apache/commons/codec/Decoder.java new file mode 100644 index 00000000..5bf611e4 --- /dev/null +++ b/src/org/apache/commons/codec/Decoder.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec; + +/** + * Provides the highest level of abstraction for Decoders. + *

+ * This is the sister interface of {@link Encoder}. All Decoders implement this common generic interface. + * Allows a user to pass a generic Object to any Decoder implementation in the codec package. + *

+ * One of the two interfaces at the center of the codec package. + * + * @version $Id: Decoder.java 1379145 2012-08-30 21:02:52Z tn $ + */ +public interface Decoder { + + /** + * Decodes an "encoded" Object and returns a "decoded" Object. Note that the implementation of this interface will + * try to cast the Object parameter to the specific type expected by a particular Decoder implementation. If a + * {@link ClassCastException} occurs this decode method will throw a DecoderException. + * + * @param source + * the object to decode + * @return a 'decoded" object + * @throws DecoderException + * a decoder exception can be thrown for any number of reasons. Some good candidates are that the + * parameter passed to this method is null, a param cannot be cast to the appropriate type for a + * specific encoder. + */ + Object decode(Object source) throws DecoderException; +} + diff --git a/src/org/apache/commons/codec/DecoderException.java b/src/org/apache/commons/codec/DecoderException.java new file mode 100644 index 00000000..a35fbed2 --- /dev/null +++ b/src/org/apache/commons/codec/DecoderException.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec; + +/** + * Thrown when there is a failure condition during the decoding process. This exception is thrown when a {@link Decoder} + * encounters a decoding specific exception such as invalid data, or characters outside of the expected range. + * + * @version $Id: DecoderException.java 1619948 2014-08-22 22:53:55Z ggregory $ + */ +public class DecoderException extends Exception { + + /** + * Declares the Serial Version Uid. + * + * @see Always Declare Serial Version Uid + */ + private static final long serialVersionUID = 1L; + + /** + * Constructs a new exception with null as its detail message. The cause is not initialized, and may + * subsequently be initialized by a call to {@link #initCause}. + * + * @since 1.4 + */ + public DecoderException() { + super(); + } + + /** + * Constructs a new exception with the specified detail message. The cause is not initialized, and may subsequently + * be initialized by a call to {@link #initCause}. + * + * @param message + * The detail message which is saved for later retrieval by the {@link #getMessage()} method. + */ + public DecoderException(final String message) { + super(message); + } + + /** + * Constructs a new exception with the specified detail message and cause. + *

+ * Note that the detail message associated with cause is not automatically incorporated into this + * exception's detail message. + * + * @param message + * The detail message which is saved for later retrieval by the {@link #getMessage()} method. + * @param cause + * The cause which is saved for later retrieval by the {@link #getCause()} method. A null + * value is permitted, and indicates that the cause is nonexistent or unknown. + * @since 1.4 + */ + public DecoderException(final String message, final Throwable cause) { + super(message, cause); + } + + /** + * Constructs a new exception with the specified cause and a detail message of (cause==null ? + * null : cause.toString()) (which typically contains the class and detail message of cause). + * This constructor is useful for exceptions that are little more than wrappers for other throwables. + * + * @param cause + * The cause which is saved for later retrieval by the {@link #getCause()} method. A null + * value is permitted, and indicates that the cause is nonexistent or unknown. + * @since 1.4 + */ + public DecoderException(final Throwable cause) { + super(cause); + } +} diff --git a/src/org/apache/commons/codec/Encoder.java b/src/org/apache/commons/codec/Encoder.java new file mode 100644 index 00000000..1605a374 --- /dev/null +++ b/src/org/apache/commons/codec/Encoder.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec; + +/** + * Provides the highest level of abstraction for Encoders. + *

+ * This is the sister interface of {@link Decoder}. Every implementation of Encoder provides this + * common generic interface which allows a user to pass a generic Object to any Encoder implementation + * in the codec package. + * + * @version $Id: Encoder.java 1379145 2012-08-30 21:02:52Z tn $ + */ +public interface Encoder { + + /** + * Encodes an "Object" and returns the encoded content as an Object. The Objects here may just be + * byte[] or Strings depending on the implementation used. + * + * @param source + * An object to encode + * @return An "encoded" Object + * @throws EncoderException + * An encoder exception is thrown if the encoder experiences a failure condition during the encoding + * process. + */ + Object encode(Object source) throws EncoderException; +} + diff --git a/src/org/apache/commons/codec/EncoderException.java b/src/org/apache/commons/codec/EncoderException.java new file mode 100644 index 00000000..b05ac09b --- /dev/null +++ b/src/org/apache/commons/codec/EncoderException.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec; + +/** + * Thrown when there is a failure condition during the encoding process. This exception is thrown when an + * {@link Encoder} encounters a encoding specific exception such as invalid data, inability to calculate a checksum, + * characters outside of the expected range. + * + * @version $Id: EncoderException.java 1619948 2014-08-22 22:53:55Z ggregory $ + */ +public class EncoderException extends Exception { + + /** + * Declares the Serial Version Uid. + * + * @see Always Declare Serial Version Uid + */ + private static final long serialVersionUID = 1L; + + /** + * Constructs a new exception with null as its detail message. The cause is not initialized, and may + * subsequently be initialized by a call to {@link #initCause}. + * + * @since 1.4 + */ + public EncoderException() { + super(); + } + + /** + * Constructs a new exception with the specified detail message. The cause is not initialized, and may subsequently + * be initialized by a call to {@link #initCause}. + * + * @param message + * a useful message relating to the encoder specific error. + */ + public EncoderException(final String message) { + super(message); + } + + /** + * Constructs a new exception with the specified detail message and cause. + * + *

+ * Note that the detail message associated with cause is not automatically incorporated into this + * exception's detail message. + *

+ * + * @param message + * The detail message which is saved for later retrieval by the {@link #getMessage()} method. + * @param cause + * The cause which is saved for later retrieval by the {@link #getCause()} method. A null + * value is permitted, and indicates that the cause is nonexistent or unknown. + * @since 1.4 + */ + public EncoderException(final String message, final Throwable cause) { + super(message, cause); + } + + /** + * Constructs a new exception with the specified cause and a detail message of (cause==null ? + * null : cause.toString()) (which typically contains the class and detail message of cause). + * This constructor is useful for exceptions that are little more than wrappers for other throwables. + * + * @param cause + * The cause which is saved for later retrieval by the {@link #getCause()} method. A null + * value is permitted, and indicates that the cause is nonexistent or unknown. + * @since 1.4 + */ + public EncoderException(final Throwable cause) { + super(cause); + } +} diff --git a/src/org/apache/commons/codec/StringDecoder.java b/src/org/apache/commons/codec/StringDecoder.java new file mode 100644 index 00000000..a42ce3a6 --- /dev/null +++ b/src/org/apache/commons/codec/StringDecoder.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec; + +/** + * Defines common decoding methods for String decoders. + * + * @version $Id: StringDecoder.java 1379145 2012-08-30 21:02:52Z tn $ + */ +public interface StringDecoder extends Decoder { + + /** + * Decodes a String and returns a String. + * + * @param source + * the String to decode + * @return the encoded String + * @throws DecoderException + * thrown if there is an error condition during the Encoding process. + */ + String decode(String source) throws DecoderException; +} + diff --git a/src/org/apache/commons/codec/StringEncoder.java b/src/org/apache/commons/codec/StringEncoder.java new file mode 100644 index 00000000..9450083c --- /dev/null +++ b/src/org/apache/commons/codec/StringEncoder.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec; + +/** + * Defines common encoding methods for String encoders. + * + * @version $Id: StringEncoder.java 1379145 2012-08-30 21:02:52Z tn $ + */ +public interface StringEncoder extends Encoder { + + /** + * Encodes a String and returns a String. + * + * @param source + * the String to encode + * @return the encoded String + * @throws EncoderException + * thrown if there is an error condition during the encoding process. + */ + String encode(String source) throws EncoderException; +} + diff --git a/src/org/apache/commons/codec/StringEncoderComparator.java b/src/org/apache/commons/codec/StringEncoderComparator.java new file mode 100644 index 00000000..ddad57fd --- /dev/null +++ b/src/org/apache/commons/codec/StringEncoderComparator.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec; + +import java.util.Comparator; + +/** + * Compares Strings using a {@link StringEncoder}. This comparator is used to sort Strings by an encoding scheme such as + * Soundex, Metaphone, etc. This class can come in handy if one need to sort Strings by an encoded form of a name such + * as Soundex. + * + *

This class is immutable and thread-safe.

+ * + * @version $Id: StringEncoderComparator.java 1468177 2013-04-15 18:35:15Z ggregory $ + */ +@SuppressWarnings("rawtypes") +// TODO ought to implement Comparator but that's not possible whilst maintaining binary compatibility. +public class StringEncoderComparator implements Comparator { + + /** + * Internal encoder instance. + */ + private final StringEncoder stringEncoder; + + /** + * Constructs a new instance. + * + * @deprecated Creating an instance without a {@link StringEncoder} leads to a {@link NullPointerException}. Will be + * removed in 2.0. + */ + @Deprecated + public StringEncoderComparator() { + this.stringEncoder = null; // Trying to use this will cause things to break + } + + /** + * Constructs a new instance with the given algorithm. + * + * @param stringEncoder + * the StringEncoder used for comparisons. + */ + public StringEncoderComparator(final StringEncoder stringEncoder) { + this.stringEncoder = stringEncoder; + } + + /** + * Compares two strings based not on the strings themselves, but on an encoding of the two strings using the + * StringEncoder this Comparator was created with. + * + * If an {@link EncoderException} is encountered, return 0. + * + * @param o1 + * the object to compare + * @param o2 + * the object to compare to + * @return the Comparable.compareTo() return code or 0 if an encoding error was caught. + * @see Comparable + */ + @Override + public int compare(final Object o1, final Object o2) { + + int compareCode = 0; + + try { + @SuppressWarnings("unchecked") // May fail with CCE if encode returns something that is not Comparable + // However this was always the case. + final Comparable> s1 = (Comparable>) this.stringEncoder.encode(o1); + final Comparable s2 = (Comparable) this.stringEncoder.encode(o2); + compareCode = s1.compareTo(s2); + } catch (final EncoderException ee) { + compareCode = 0; + } + return compareCode; + } + +} diff --git a/src/org/apache/commons/codec/binary/Base32.java b/src/org/apache/commons/codec/binary/Base32.java new file mode 100644 index 00000000..ff04d1c2 --- /dev/null +++ b/src/org/apache/commons/codec/binary/Base32.java @@ -0,0 +1,539 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.binary; + +/** + * Provides Base32 encoding and decoding as defined by RFC 4648. + * + *

+ * The class can be parameterized in the following manner with various constructors: + *

+ *
    + *
  • Whether to use the "base32hex" variant instead of the default "base32"
  • + *
  • Line length: Default 76. Line length that aren't multiples of 8 will still essentially end up being multiples of + * 8 in the encoded data. + *
  • Line separator: Default is CRLF ("\r\n")
  • + *
+ *

+ * This class operates directly on byte streams, and not character streams. + *

+ *

+ * This class is thread-safe. + *

+ * + * @see RFC 4648 + * + * @since 1.5 + * @version $Id: Base32.java 1619949 2014-08-22 22:56:08Z ggregory $ + */ +public class Base32 extends BaseNCodec { + + /** + * BASE32 characters are 5 bits in length. + * They are formed by taking a block of five octets to form a 40-bit string, + * which is converted into eight BASE32 characters. + */ + private static final int BITS_PER_ENCODED_BYTE = 5; + private static final int BYTES_PER_ENCODED_BLOCK = 8; + private static final int BYTES_PER_UNENCODED_BLOCK = 5; + + /** + * Chunk separator per RFC 2045 section 2.1. + * + * @see RFC 2045 section 2.1 + */ + private static final byte[] CHUNK_SEPARATOR = {'\r', '\n'}; + + /** + * This array is a lookup table that translates Unicode characters drawn from the "Base32 Alphabet" (as specified + * in Table 3 of RFC 4648) into their 5-bit positive integer equivalents. Characters that are not in the Base32 + * alphabet but fall within the bounds of the array are translated to -1. + */ + private static final byte[] DECODE_TABLE = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20-2f + -1, -1, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, // 30-3f 2-7 + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 40-4f A-N + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // 50-5a O-Z + }; + + /** + * This array is a lookup table that translates 5-bit positive integer index values into their "Base32 Alphabet" + * equivalents as specified in Table 3 of RFC 4648. + */ + private static final byte[] ENCODE_TABLE = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '2', '3', '4', '5', '6', '7', + }; + + /** + * This array is a lookup table that translates Unicode characters drawn from the "Base32 |Hex Alphabet" (as + * specified in Table 3 of RFC 4648) into their 5-bit positive integer equivalents. Characters that are not in the + * Base32 Hex alphabet but fall within the bounds of the array are translated to -1. + */ + private static final byte[] HEX_DECODE_TABLE = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20-2f + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, // 30-3f 2-7 + -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 40-4f A-N + 25, 26, 27, 28, 29, 30, 31, 32, // 50-57 O-V + }; + + /** + * This array is a lookup table that translates 5-bit positive integer index values into their + * "Base32 Hex Alphabet" equivalents as specified in Table 3 of RFC 4648. + */ + private static final byte[] HEX_ENCODE_TABLE = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + }; + + /** Mask used to extract 5 bits, used when encoding Base32 bytes */ + private static final int MASK_5BITS = 0x1f; + + // The static final fields above are used for the original static byte[] methods on Base32. + // The private member fields below are used with the new streaming approach, which requires + // some state be preserved between calls of encode() and decode(). + + /** + * Place holder for the bytes we're dealing with for our based logic. + * Bitwise operations store and extract the encoding or decoding from this variable. + */ + + /** + * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. + * decodeSize = {@link #BYTES_PER_ENCODED_BLOCK} - 1 + lineSeparator.length; + */ + private final int decodeSize; + + /** + * Decode table to use. + */ + private final byte[] decodeTable; + + /** + * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. + * encodeSize = {@link #BYTES_PER_ENCODED_BLOCK} + lineSeparator.length; + */ + private final int encodeSize; + + /** + * Encode table to use. + */ + private final byte[] encodeTable; + + /** + * Line separator for encoding. Not used when decoding. Only used if lineLength > 0. + */ + private final byte[] lineSeparator; + + /** + * Creates a Base32 codec used for decoding and encoding. + *

+ * When encoding the line length is 0 (no chunking). + *

+ * + */ + public Base32() { + this(false); + } + + /** + * Creates a Base32 codec used for decoding and encoding. + *

+ * When encoding the line length is 0 (no chunking). + *

+ * @param pad byte used as padding byte. + */ + public Base32(final byte pad) { + this(false, pad); + } + + /** + * Creates a Base32 codec used for decoding and encoding. + *

+ * When encoding the line length is 0 (no chunking). + *

+ * @param useHex if {@code true} then use Base32 Hex alphabet + */ + public Base32(final boolean useHex) { + this(0, null, useHex, PAD_DEFAULT); + } + + /** + * Creates a Base32 codec used for decoding and encoding. + *

+ * When encoding the line length is 0 (no chunking). + *

+ * @param useHex if {@code true} then use Base32 Hex alphabet + * @param pad byte used as padding byte. + */ + public Base32(final boolean useHex, final byte pad) { + this(0, null, useHex, pad); + } + + /** + * Creates a Base32 codec used for decoding and encoding. + *

+ * When encoding the line length is given in the constructor, the line separator is CRLF. + *

+ * + * @param lineLength + * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of + * 8). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when + * decoding. + */ + public Base32(final int lineLength) { + this(lineLength, CHUNK_SEPARATOR); + } + + /** + * Creates a Base32 codec used for decoding and encoding. + *

+ * When encoding the line length and line separator are given in the constructor. + *

+ *

+ * Line lengths that aren't multiples of 8 will still essentially end up being multiples of 8 in the encoded data. + *

+ * + * @param lineLength + * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of + * 8). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when + * decoding. + * @param lineSeparator + * Each line of encoded data will end with this sequence of bytes. + * @throws IllegalArgumentException + * The provided lineSeparator included some Base32 characters. That's not going to work! + */ + public Base32(final int lineLength, final byte[] lineSeparator) { + this(lineLength, lineSeparator, false, PAD_DEFAULT); + } + + /** + * Creates a Base32 / Base32 Hex codec used for decoding and encoding. + *

+ * When encoding the line length and line separator are given in the constructor. + *

+ *

+ * Line lengths that aren't multiples of 8 will still essentially end up being multiples of 8 in the encoded data. + *

+ * + * @param lineLength + * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of + * 8). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when + * decoding. + * @param lineSeparator + * Each line of encoded data will end with this sequence of bytes. + * @param useHex + * if {@code true}, then use Base32 Hex alphabet, otherwise use Base32 alphabet + * @throws IllegalArgumentException + * The provided lineSeparator included some Base32 characters. That's not going to work! Or the + * lineLength > 0 and lineSeparator is null. + */ + public Base32(final int lineLength, final byte[] lineSeparator, final boolean useHex) { + this(lineLength, lineSeparator, useHex, PAD_DEFAULT); + } + + /** + * Creates a Base32 / Base32 Hex codec used for decoding and encoding. + *

+ * When encoding the line length and line separator are given in the constructor. + *

+ *

+ * Line lengths that aren't multiples of 8 will still essentially end up being multiples of 8 in the encoded data. + *

+ * + * @param lineLength + * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of + * 8). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when + * decoding. + * @param lineSeparator + * Each line of encoded data will end with this sequence of bytes. + * @param useHex + * if {@code true}, then use Base32 Hex alphabet, otherwise use Base32 alphabet + * @param pad byte used as padding byte. + * @throws IllegalArgumentException + * The provided lineSeparator included some Base32 characters. That's not going to work! Or the + * lineLength > 0 and lineSeparator is null. + */ + public Base32(final int lineLength, final byte[] lineSeparator, final boolean useHex, final byte pad) { + super(BYTES_PER_UNENCODED_BLOCK, BYTES_PER_ENCODED_BLOCK, lineLength, + lineSeparator == null ? 0 : lineSeparator.length, pad); + if (useHex) { + this.encodeTable = HEX_ENCODE_TABLE; + this.decodeTable = HEX_DECODE_TABLE; + } else { + this.encodeTable = ENCODE_TABLE; + this.decodeTable = DECODE_TABLE; + } + if (lineLength > 0) { + if (lineSeparator == null) { + throw new IllegalArgumentException("lineLength " + lineLength + " > 0, but lineSeparator is null"); + } + // Must be done after initializing the tables + if (containsAlphabetOrPad(lineSeparator)) { + final String sep = StringUtils.newStringUtf8(lineSeparator); + throw new IllegalArgumentException("lineSeparator must not contain Base32 characters: [" + sep + "]"); + } + this.encodeSize = BYTES_PER_ENCODED_BLOCK + lineSeparator.length; + this.lineSeparator = new byte[lineSeparator.length]; + System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length); + } else { + this.encodeSize = BYTES_PER_ENCODED_BLOCK; + this.lineSeparator = null; + } + this.decodeSize = this.encodeSize - 1; + + if (isInAlphabet(pad) || isWhiteSpace(pad)) { + throw new IllegalArgumentException("pad must not be in alphabet or whitespace"); + } + } + + /** + *

+ * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once + * with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1" + * call is not necessary when decoding, but it doesn't hurt, either. + *

+ *

+ * Ignores all non-Base32 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are + * silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in, + * garbage-out philosophy: it will not check the provided data for validity. + *

+ * + * @param in + * byte[] array of ascii data to Base32 decode. + * @param inPos + * Position to start reading data from. + * @param inAvail + * Amount of bytes available from input for encoding. + * @param context the context to be used + * + * Output is written to {@link Context#buffer} as 8-bit octets, using {@link Context#pos} as the buffer position + */ + @Override + void decode(final byte[] in, int inPos, final int inAvail, final Context context) { + // package protected for access from I/O streams + + if (context.eof) { + return; + } + if (inAvail < 0) { + context.eof = true; + } + for (int i = 0; i < inAvail; i++) { + final byte b = in[inPos++]; + if (b == pad) { + // We're done. + context.eof = true; + break; + } else { + final byte[] buffer = ensureBufferSize(decodeSize, context); + if (b >= 0 && b < this.decodeTable.length) { + final int result = this.decodeTable[b]; + if (result >= 0) { + context.modulus = (context.modulus+1) % BYTES_PER_ENCODED_BLOCK; + // collect decoded bytes + context.lbitWorkArea = (context.lbitWorkArea << BITS_PER_ENCODED_BYTE) + result; + if (context.modulus == 0) { // we can output the 5 bytes + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 32) & MASK_8BITS); + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 24) & MASK_8BITS); + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 16) & MASK_8BITS); + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 8) & MASK_8BITS); + buffer[context.pos++] = (byte) (context.lbitWorkArea & MASK_8BITS); + } + } + } + } + } + + // Two forms of EOF as far as Base32 decoder is concerned: actual + // EOF (-1) and first time '=' character is encountered in stream. + // This approach makes the '=' padding characters completely optional. + if (context.eof && context.modulus >= 2) { // if modulus < 2, nothing to do + final byte[] buffer = ensureBufferSize(decodeSize, context); + + // we ignore partial bytes, i.e. only multiples of 8 count + switch (context.modulus) { + case 2 : // 10 bits, drop 2 and output one byte + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 2) & MASK_8BITS); + break; + case 3 : // 15 bits, drop 7 and output 1 byte + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 7) & MASK_8BITS); + break; + case 4 : // 20 bits = 2*8 + 4 + context.lbitWorkArea = context.lbitWorkArea >> 4; // drop 4 bits + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 8) & MASK_8BITS); + buffer[context.pos++] = (byte) ((context.lbitWorkArea) & MASK_8BITS); + break; + case 5 : // 25bits = 3*8 + 1 + context.lbitWorkArea = context.lbitWorkArea >> 1; + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 16) & MASK_8BITS); + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 8) & MASK_8BITS); + buffer[context.pos++] = (byte) ((context.lbitWorkArea) & MASK_8BITS); + break; + case 6 : // 30bits = 3*8 + 6 + context.lbitWorkArea = context.lbitWorkArea >> 6; + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 16) & MASK_8BITS); + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 8) & MASK_8BITS); + buffer[context.pos++] = (byte) ((context.lbitWorkArea) & MASK_8BITS); + break; + case 7 : // 35 = 4*8 +3 + context.lbitWorkArea = context.lbitWorkArea >> 3; + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 24) & MASK_8BITS); + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 16) & MASK_8BITS); + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 8) & MASK_8BITS); + buffer[context.pos++] = (byte) ((context.lbitWorkArea) & MASK_8BITS); + break; + default: + // modulus can be 0-7, and we excluded 0,1 already + throw new IllegalStateException("Impossible modulus "+context.modulus); + } + } + } + + /** + *

+ * Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least twice: once with + * the data to encode, and once with inAvail set to "-1" to alert encoder that EOF has been reached, so flush last + * remaining bytes (if not multiple of 5). + *

+ * + * @param in + * byte[] array of binary data to Base32 encode. + * @param inPos + * Position to start reading data from. + * @param inAvail + * Amount of bytes available from input for encoding. + * @param context the context to be used + */ + @Override + void encode(final byte[] in, int inPos, final int inAvail, final Context context) { + // package protected for access from I/O streams + + if (context.eof) { + return; + } + // inAvail < 0 is how we're informed of EOF in the underlying data we're + // encoding. + if (inAvail < 0) { + context.eof = true; + if (0 == context.modulus && lineLength == 0) { + return; // no leftovers to process and not using chunking + } + final byte[] buffer = ensureBufferSize(encodeSize, context); + final int savedPos = context.pos; + switch (context.modulus) { // % 5 + case 0 : + break; + case 1 : // Only 1 octet; take top 5 bits then remainder + buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 3) & MASK_5BITS]; // 8-1*5 = 3 + buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea << 2) & MASK_5BITS]; // 5-3=2 + buffer[context.pos++] = pad; + buffer[context.pos++] = pad; + buffer[context.pos++] = pad; + buffer[context.pos++] = pad; + buffer[context.pos++] = pad; + buffer[context.pos++] = pad; + break; + case 2 : // 2 octets = 16 bits to use + buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 11) & MASK_5BITS]; // 16-1*5 = 11 + buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 6) & MASK_5BITS]; // 16-2*5 = 6 + buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 1) & MASK_5BITS]; // 16-3*5 = 1 + buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea << 4) & MASK_5BITS]; // 5-1 = 4 + buffer[context.pos++] = pad; + buffer[context.pos++] = pad; + buffer[context.pos++] = pad; + buffer[context.pos++] = pad; + break; + case 3 : // 3 octets = 24 bits to use + buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 19) & MASK_5BITS]; // 24-1*5 = 19 + buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 14) & MASK_5BITS]; // 24-2*5 = 14 + buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 9) & MASK_5BITS]; // 24-3*5 = 9 + buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 4) & MASK_5BITS]; // 24-4*5 = 4 + buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea << 1) & MASK_5BITS]; // 5-4 = 1 + buffer[context.pos++] = pad; + buffer[context.pos++] = pad; + buffer[context.pos++] = pad; + break; + case 4 : // 4 octets = 32 bits to use + buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 27) & MASK_5BITS]; // 32-1*5 = 27 + buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 22) & MASK_5BITS]; // 32-2*5 = 22 + buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 17) & MASK_5BITS]; // 32-3*5 = 17 + buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 12) & MASK_5BITS]; // 32-4*5 = 12 + buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 7) & MASK_5BITS]; // 32-5*5 = 7 + buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 2) & MASK_5BITS]; // 32-6*5 = 2 + buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea << 3) & MASK_5BITS]; // 5-2 = 3 + buffer[context.pos++] = pad; + break; + default: + throw new IllegalStateException("Impossible modulus "+context.modulus); + } + context.currentLinePos += context.pos - savedPos; // keep track of current line position + // if currentPos == 0 we are at the start of a line, so don't add CRLF + if (lineLength > 0 && context.currentLinePos > 0){ // add chunk separator if required + System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length); + context.pos += lineSeparator.length; + } + } else { + for (int i = 0; i < inAvail; i++) { + final byte[] buffer = ensureBufferSize(encodeSize, context); + context.modulus = (context.modulus+1) % BYTES_PER_UNENCODED_BLOCK; + int b = in[inPos++]; + if (b < 0) { + b += 256; + } + context.lbitWorkArea = (context.lbitWorkArea << 8) + b; // BITS_PER_BYTE + if (0 == context.modulus) { // we have enough bytes to create our output + buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 35) & MASK_5BITS]; + buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 30) & MASK_5BITS]; + buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 25) & MASK_5BITS]; + buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 20) & MASK_5BITS]; + buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 15) & MASK_5BITS]; + buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 10) & MASK_5BITS]; + buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 5) & MASK_5BITS]; + buffer[context.pos++] = encodeTable[(int)context.lbitWorkArea & MASK_5BITS]; + context.currentLinePos += BYTES_PER_ENCODED_BLOCK; + if (lineLength > 0 && lineLength <= context.currentLinePos) { + System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length); + context.pos += lineSeparator.length; + context.currentLinePos = 0; + } + } + } + } + } + + /** + * Returns whether or not the {@code octet} is in the Base32 alphabet. + * + * @param octet + * The value to test + * @return {@code true} if the value is defined in the the Base32 alphabet {@code false} otherwise. + */ + @Override + public boolean isInAlphabet(final byte octet) { + return octet >= 0 && octet < decodeTable.length && decodeTable[octet] != -1; + } +} diff --git a/src/org/apache/commons/codec/binary/Base32InputStream.java b/src/org/apache/commons/codec/binary/Base32InputStream.java new file mode 100644 index 00000000..04369890 --- /dev/null +++ b/src/org/apache/commons/codec/binary/Base32InputStream.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.binary; + +import java.io.InputStream; + +/** + * Provides Base32 encoding and decoding in a streaming fashion (unlimited size). When encoding the default lineLength + * is 76 characters and the default lineEnding is CRLF, but these can be overridden by using the appropriate + * constructor. + *

+ * The default behaviour of the Base32InputStream is to DECODE, whereas the default behaviour of the Base32OutputStream + * is to ENCODE, but this behaviour can be overridden by using a different constructor. + *

+ *

+ * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only encode/decode + * character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, UTF-8, etc). + *

+ * + * @version $Id: Base32InputStream.java 1586299 2014-04-10 13:50:21Z ggregory $ + * @see RFC 4648 + * @since 1.5 + */ +public class Base32InputStream extends BaseNCodecInputStream { + + /** + * Creates a Base32InputStream such that all data read is Base32-decoded from the original provided InputStream. + * + * @param in + * InputStream to wrap. + */ + public Base32InputStream(final InputStream in) { + this(in, false); + } + + /** + * Creates a Base32InputStream such that all data read is either Base32-encoded or Base32-decoded from the original + * provided InputStream. + * + * @param in + * InputStream to wrap. + * @param doEncode + * true if we should encode all data read from us, false if we should decode. + */ + public Base32InputStream(final InputStream in, final boolean doEncode) { + super(in, new Base32(false), doEncode); + } + + /** + * Creates a Base32InputStream such that all data read is either Base32-encoded or Base32-decoded from the original + * provided InputStream. + * + * @param in + * InputStream to wrap. + * @param doEncode + * true if we should encode all data read from us, false if we should decode. + * @param lineLength + * If doEncode is true, each line of encoded data will contain lineLength characters (rounded down to + * nearest multiple of 4). If lineLength <= 0, the encoded data is not divided into lines. If doEncode + * is false, lineLength is ignored. + * @param lineSeparator + * If doEncode is true, each line of encoded data will be terminated with this byte sequence (e.g. \r\n). + * If lineLength <= 0, the lineSeparator is not used. If doEncode is false lineSeparator is ignored. + */ + public Base32InputStream(final InputStream in, final boolean doEncode, + final int lineLength, final byte[] lineSeparator) { + super(in, new Base32(lineLength, lineSeparator), doEncode); + } + +} diff --git a/src/org/apache/commons/codec/binary/Base32OutputStream.java b/src/org/apache/commons/codec/binary/Base32OutputStream.java new file mode 100644 index 00000000..2c5318cb --- /dev/null +++ b/src/org/apache/commons/codec/binary/Base32OutputStream.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.binary; + +import java.io.OutputStream; + +/** + * Provides Base32 encoding and decoding in a streaming fashion (unlimited size). When encoding the default lineLength + * is 76 characters and the default lineEnding is CRLF, but these can be overridden by using the appropriate + * constructor. + *

+ * The default behaviour of the Base32OutputStream is to ENCODE, whereas the default behaviour of the Base32InputStream + * is to DECODE. But this behaviour can be overridden by using a different constructor. + *

+ *

+ * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only encode/decode + * character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, UTF-8, etc). + *

+ *

+ * Note: It is mandatory to close the stream after the last byte has been written to it, otherwise the + * final padding will be omitted and the resulting data will be incomplete/inconsistent. + *

+ * + * @version $Id: Base32OutputStream.java 1635952 2014-11-01 14:19:04Z tn $ + * @see RFC 4648 + * @since 1.5 + */ +public class Base32OutputStream extends BaseNCodecOutputStream { + + /** + * Creates a Base32OutputStream such that all data written is Base32-encoded to the original provided OutputStream. + * + * @param out + * OutputStream to wrap. + */ + public Base32OutputStream(final OutputStream out) { + this(out, true); + } + + /** + * Creates a Base32OutputStream such that all data written is either Base32-encoded or Base32-decoded to the + * original provided OutputStream. + * + * @param out + * OutputStream to wrap. + * @param doEncode + * true if we should encode all data written to us, false if we should decode. + */ + public Base32OutputStream(final OutputStream out, final boolean doEncode) { + super(out, new Base32(false), doEncode); + } + + /** + * Creates a Base32OutputStream such that all data written is either Base32-encoded or Base32-decoded to the + * original provided OutputStream. + * + * @param out + * OutputStream to wrap. + * @param doEncode + * true if we should encode all data written to us, false if we should decode. + * @param lineLength + * If doEncode is true, each line of encoded data will contain lineLength characters (rounded down to + * nearest multiple of 4). If lineLength <= 0, the encoded data is not divided into lines. If doEncode + * is false, lineLength is ignored. + * @param lineSeparator + * If doEncode is true, each line of encoded data will be terminated with this byte sequence (e.g. \r\n). + * If lineLength <= 0, the lineSeparator is not used. If doEncode is false lineSeparator is ignored. + */ + public Base32OutputStream(final OutputStream out, final boolean doEncode, + final int lineLength, final byte[] lineSeparator) { + super(out, new Base32(lineLength, lineSeparator), doEncode); + } + +} diff --git a/src/org/apache/commons/codec/binary/Base64.java b/src/org/apache/commons/codec/binary/Base64.java new file mode 100644 index 00000000..6545c2ff --- /dev/null +++ b/src/org/apache/commons/codec/binary/Base64.java @@ -0,0 +1,784 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.binary; + +import java.math.BigInteger; + +/** + * Provides Base64 encoding and decoding as defined by RFC 2045. + * + *

+ * This class implements section 6.8. Base64 Content-Transfer-Encoding from RFC 2045 Multipurpose + * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies by Freed and Borenstein. + *

+ *

+ * The class can be parameterized in the following manner with various constructors: + *

+ *
    + *
  • URL-safe mode: Default off.
  • + *
  • Line length: Default 76. Line length that aren't multiples of 4 will still essentially end up being multiples of + * 4 in the encoded data. + *
  • Line separator: Default is CRLF ("\r\n")
  • + *
+ *

+ * The URL-safe parameter is only applied to encode operations. Decoding seamlessly handles both modes. + *

+ *

+ * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only + * encode/decode character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, + * UTF-8, etc). + *

+ *

+ * This class is thread-safe. + *

+ * + * @see RFC 2045 + * @since 1.0 + * @version $Id: Base64.java 1635986 2014-11-01 16:27:52Z tn $ + */ +public class Base64 extends BaseNCodec { + + /** + * BASE32 characters are 6 bits in length. + * They are formed by taking a block of 3 octets to form a 24-bit string, + * which is converted into 4 BASE64 characters. + */ + private static final int BITS_PER_ENCODED_BYTE = 6; + private static final int BYTES_PER_UNENCODED_BLOCK = 3; + private static final int BYTES_PER_ENCODED_BLOCK = 4; + + /** + * Chunk separator per RFC 2045 section 2.1. + * + *

+ * N.B. The next major release may break compatibility and make this field private. + *

+ * + * @see RFC 2045 section 2.1 + */ + static final byte[] CHUNK_SEPARATOR = {'\r', '\n'}; + + /** + * This array is a lookup table that translates 6-bit positive integer index values into their "Base64 Alphabet" + * equivalents as specified in Table 1 of RFC 2045. + * + * Thanks to "commons" project in ws.apache.org for this code. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + */ + private static final byte[] STANDARD_ENCODE_TABLE = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' + }; + + /** + * This is a copy of the STANDARD_ENCODE_TABLE above, but with + and / + * changed to - and _ to make the encoded Base64 results more URL-SAFE. + * This table is only used when the Base64's mode is set to URL-SAFE. + */ + private static final byte[] URL_SAFE_ENCODE_TABLE = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' + }; + + /** + * This array is a lookup table that translates Unicode characters drawn from the "Base64 Alphabet" (as specified + * in Table 1 of RFC 2045) into their 6-bit positive integer equivalents. Characters that are not in the Base64 + * alphabet but fall within the bounds of the array are translated to -1. + * + * Note: '+' and '-' both decode to 62. '/' and '_' both decode to 63. This means decoder seamlessly handles both + * URL_SAFE and STANDARD base64. (The encoder, on the other hand, needs to know ahead of time what to emit). + * + * Thanks to "commons" project in ws.apache.org for this code. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + */ + private static final byte[] DECODE_TABLE = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 + }; + + /** + * Base64 uses 6-bit fields. + */ + /** Mask used to extract 6 bits, used when encoding */ + private static final int MASK_6BITS = 0x3f; + + // The static final fields above are used for the original static byte[] methods on Base64. + // The private member fields below are used with the new streaming approach, which requires + // some state be preserved between calls of encode() and decode(). + + /** + * Encode table to use: either STANDARD or URL_SAFE. Note: the DECODE_TABLE above remains static because it is able + * to decode both STANDARD and URL_SAFE streams, but the encodeTable must be a member variable so we can switch + * between the two modes. + */ + private final byte[] encodeTable; + + // Only one decode table currently; keep for consistency with Base32 code + private final byte[] decodeTable = DECODE_TABLE; + + /** + * Line separator for encoding. Not used when decoding. Only used if lineLength > 0. + */ + private final byte[] lineSeparator; + + /** + * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. + * decodeSize = 3 + lineSeparator.length; + */ + private final int decodeSize; + + /** + * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. + * encodeSize = 4 + lineSeparator.length; + */ + private final int encodeSize; + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + *

+ * When encoding the line length is 0 (no chunking), and the encoding table is STANDARD_ENCODE_TABLE. + *

+ * + *

+ * When decoding all variants are supported. + *

+ */ + public Base64() { + this(0); + } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in the given URL-safe mode. + *

+ * When encoding the line length is 76, the line separator is CRLF, and the encoding table is STANDARD_ENCODE_TABLE. + *

+ * + *

+ * When decoding all variants are supported. + *

+ * + * @param urlSafe + * if true, URL-safe encoding is used. In most cases this should be set to + * false. + * @since 1.4 + */ + public Base64(final boolean urlSafe) { + this(MIME_CHUNK_SIZE, CHUNK_SEPARATOR, urlSafe); + } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + *

+ * When encoding the line length is given in the constructor, the line separator is CRLF, and the encoding table is + * STANDARD_ENCODE_TABLE. + *

+ *

+ * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. + *

+ *

+ * When decoding all variants are supported. + *

+ * + * @param lineLength + * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of + * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when + * decoding. + * @since 1.4 + */ + public Base64(final int lineLength) { + this(lineLength, CHUNK_SEPARATOR); + } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + *

+ * When encoding the line length and line separator are given in the constructor, and the encoding table is + * STANDARD_ENCODE_TABLE. + *

+ *

+ * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. + *

+ *

+ * When decoding all variants are supported. + *

+ * + * @param lineLength + * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of + * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when + * decoding. + * @param lineSeparator + * Each line of encoded data will end with this sequence of bytes. + * @throws IllegalArgumentException + * Thrown when the provided lineSeparator included some base64 characters. + * @since 1.4 + */ + public Base64(final int lineLength, final byte[] lineSeparator) { + this(lineLength, lineSeparator, false); + } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + *

+ * When encoding the line length and line separator are given in the constructor, and the encoding table is + * STANDARD_ENCODE_TABLE. + *

+ *

+ * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. + *

+ *

+ * When decoding all variants are supported. + *

+ * + * @param lineLength + * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of + * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when + * decoding. + * @param lineSeparator + * Each line of encoded data will end with this sequence of bytes. + * @param urlSafe + * Instead of emitting '+' and '/' we emit '-' and '_' respectively. urlSafe is only applied to encode + * operations. Decoding seamlessly handles both modes. + * Note: no padding is added when using the URL-safe alphabet. + * @throws IllegalArgumentException + * The provided lineSeparator included some base64 characters. That's not going to work! + * @since 1.4 + */ + public Base64(final int lineLength, final byte[] lineSeparator, final boolean urlSafe) { + super(BYTES_PER_UNENCODED_BLOCK, BYTES_PER_ENCODED_BLOCK, + lineLength, + lineSeparator == null ? 0 : lineSeparator.length); + // TODO could be simplified if there is no requirement to reject invalid line sep when length <=0 + // @see test case Base64Test.testConstructors() + if (lineSeparator != null) { + if (containsAlphabetOrPad(lineSeparator)) { + final String sep = StringUtils.newStringUtf8(lineSeparator); + throw new IllegalArgumentException("lineSeparator must not contain base64 characters: [" + sep + "]"); + } + if (lineLength > 0){ // null line-sep forces no chunking rather than throwing IAE + this.encodeSize = BYTES_PER_ENCODED_BLOCK + lineSeparator.length; + this.lineSeparator = new byte[lineSeparator.length]; + System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length); + } else { + this.encodeSize = BYTES_PER_ENCODED_BLOCK; + this.lineSeparator = null; + } + } else { + this.encodeSize = BYTES_PER_ENCODED_BLOCK; + this.lineSeparator = null; + } + this.decodeSize = this.encodeSize - 1; + this.encodeTable = urlSafe ? URL_SAFE_ENCODE_TABLE : STANDARD_ENCODE_TABLE; + } + + /** + * Returns our current encode mode. True if we're URL-SAFE, false otherwise. + * + * @return true if we're in URL-SAFE mode, false otherwise. + * @since 1.4 + */ + public boolean isUrlSafe() { + return this.encodeTable == URL_SAFE_ENCODE_TABLE; + } + + /** + *

+ * Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least twice: once with + * the data to encode, and once with inAvail set to "-1" to alert encoder that EOF has been reached, to flush last + * remaining bytes (if not multiple of 3). + *

+ *

Note: no padding is added when encoding using the URL-safe alphabet.

+ *

+ * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + *

+ * + * @param in + * byte[] array of binary data to base64 encode. + * @param inPos + * Position to start reading data from. + * @param inAvail + * Amount of bytes available from input for encoding. + * @param context + * the context to be used + */ + @Override + void encode(final byte[] in, int inPos, final int inAvail, final Context context) { + if (context.eof) { + return; + } + // inAvail < 0 is how we're informed of EOF in the underlying data we're + // encoding. + if (inAvail < 0) { + context.eof = true; + if (0 == context.modulus && lineLength == 0) { + return; // no leftovers to process and not using chunking + } + final byte[] buffer = ensureBufferSize(encodeSize, context); + final int savedPos = context.pos; + switch (context.modulus) { // 0-2 + case 0 : // nothing to do here + break; + case 1 : // 8 bits = 6 + 2 + // top 6 bits: + buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 2) & MASK_6BITS]; + // remaining 2: + buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 4) & MASK_6BITS]; + // URL-SAFE skips the padding to further reduce size. + if (encodeTable == STANDARD_ENCODE_TABLE) { + buffer[context.pos++] = pad; + buffer[context.pos++] = pad; + } + break; + + case 2 : // 16 bits = 6 + 6 + 4 + buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 10) & MASK_6BITS]; + buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 4) & MASK_6BITS]; + buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 2) & MASK_6BITS]; + // URL-SAFE skips the padding to further reduce size. + if (encodeTable == STANDARD_ENCODE_TABLE) { + buffer[context.pos++] = pad; + } + break; + default: + throw new IllegalStateException("Impossible modulus "+context.modulus); + } + context.currentLinePos += context.pos - savedPos; // keep track of current line position + // if currentPos == 0 we are at the start of a line, so don't add CRLF + if (lineLength > 0 && context.currentLinePos > 0) { + System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length); + context.pos += lineSeparator.length; + } + } else { + for (int i = 0; i < inAvail; i++) { + final byte[] buffer = ensureBufferSize(encodeSize, context); + context.modulus = (context.modulus+1) % BYTES_PER_UNENCODED_BLOCK; + int b = in[inPos++]; + if (b < 0) { + b += 256; + } + context.ibitWorkArea = (context.ibitWorkArea << 8) + b; // BITS_PER_BYTE + if (0 == context.modulus) { // 3 bytes = 24 bits = 4 * 6 bits to extract + buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 18) & MASK_6BITS]; + buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 12) & MASK_6BITS]; + buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 6) & MASK_6BITS]; + buffer[context.pos++] = encodeTable[context.ibitWorkArea & MASK_6BITS]; + context.currentLinePos += BYTES_PER_ENCODED_BLOCK; + if (lineLength > 0 && lineLength <= context.currentLinePos) { + System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length); + context.pos += lineSeparator.length; + context.currentLinePos = 0; + } + } + } + } + } + + /** + *

+ * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once + * with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1" + * call is not necessary when decoding, but it doesn't hurt, either. + *

+ *

+ * Ignores all non-base64 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are + * silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in, + * garbage-out philosophy: it will not check the provided data for validity. + *

+ *

+ * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + *

+ * + * @param in + * byte[] array of ascii data to base64 decode. + * @param inPos + * Position to start reading data from. + * @param inAvail + * Amount of bytes available from input for encoding. + * @param context + * the context to be used + */ + @Override + void decode(final byte[] in, int inPos, final int inAvail, final Context context) { + if (context.eof) { + return; + } + if (inAvail < 0) { + context.eof = true; + } + for (int i = 0; i < inAvail; i++) { + final byte[] buffer = ensureBufferSize(decodeSize, context); + final byte b = in[inPos++]; + if (b == pad) { + // We're done. + context.eof = true; + break; + } else { + if (b >= 0 && b < DECODE_TABLE.length) { + final int result = DECODE_TABLE[b]; + if (result >= 0) { + context.modulus = (context.modulus+1) % BYTES_PER_ENCODED_BLOCK; + context.ibitWorkArea = (context.ibitWorkArea << BITS_PER_ENCODED_BYTE) + result; + if (context.modulus == 0) { + buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 16) & MASK_8BITS); + buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS); + buffer[context.pos++] = (byte) (context.ibitWorkArea & MASK_8BITS); + } + } + } + } + } + + // Two forms of EOF as far as base64 decoder is concerned: actual + // EOF (-1) and first time '=' character is encountered in stream. + // This approach makes the '=' padding characters completely optional. + if (context.eof && context.modulus != 0) { + final byte[] buffer = ensureBufferSize(decodeSize, context); + + // We have some spare bits remaining + // Output all whole multiples of 8 bits and ignore the rest + switch (context.modulus) { +// case 0 : // impossible, as excluded above + case 1 : // 6 bits - ignore entirely + // TODO not currently tested; perhaps it is impossible? + break; + case 2 : // 12 bits = 8 + 4 + context.ibitWorkArea = context.ibitWorkArea >> 4; // dump the extra 4 bits + buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS); + break; + case 3 : // 18 bits = 8 + 8 + 2 + context.ibitWorkArea = context.ibitWorkArea >> 2; // dump 2 bits + buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS); + buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS); + break; + default: + throw new IllegalStateException("Impossible modulus "+context.modulus); + } + } + } + + /** + * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. Currently the + * method treats whitespace as valid. + * + * @param arrayOctet + * byte array to test + * @return true if all bytes are valid characters in the Base64 alphabet or if the byte array is empty; + * false, otherwise + * @deprecated 1.5 Use {@link #isBase64(byte[])}, will be removed in 2.0. + */ + @Deprecated + public static boolean isArrayByteBase64(final byte[] arrayOctet) { + return isBase64(arrayOctet); + } + + /** + * Returns whether or not the octet is in the base 64 alphabet. + * + * @param octet + * The value to test + * @return true if the value is defined in the the base 64 alphabet, false otherwise. + * @since 1.4 + */ + public static boolean isBase64(final byte octet) { + return octet == PAD_DEFAULT || (octet >= 0 && octet < DECODE_TABLE.length && DECODE_TABLE[octet] != -1); + } + + /** + * Tests a given String to see if it contains only valid characters within the Base64 alphabet. Currently the + * method treats whitespace as valid. + * + * @param base64 + * String to test + * @return true if all characters in the String are valid characters in the Base64 alphabet or if + * the String is empty; false, otherwise + * @since 1.5 + */ + public static boolean isBase64(final String base64) { + return isBase64(StringUtils.getBytesUtf8(base64)); + } + + /** + * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. Currently the + * method treats whitespace as valid. + * + * @param arrayOctet + * byte array to test + * @return true if all bytes are valid characters in the Base64 alphabet or if the byte array is empty; + * false, otherwise + * @since 1.5 + */ + public static boolean isBase64(final byte[] arrayOctet) { + for (int i = 0; i < arrayOctet.length; i++) { + if (!isBase64(arrayOctet[i]) && !isWhiteSpace(arrayOctet[i])) { + return false; + } + } + return true; + } + + /** + * Encodes binary data using the base64 algorithm but does not chunk the output. + * + * @param binaryData + * binary data to encode + * @return byte[] containing Base64 characters in their UTF-8 representation. + */ + public static byte[] encodeBase64(final byte[] binaryData) { + return encodeBase64(binaryData, false); + } + + /** + * Encodes binary data using the base64 algorithm but does not chunk the output. + * + * NOTE: We changed the behaviour of this method from multi-line chunking (commons-codec-1.4) to + * single-line non-chunking (commons-codec-1.5). + * + * @param binaryData + * binary data to encode + * @return String containing Base64 characters. + * @since 1.4 (NOTE: 1.4 chunked the output, whereas 1.5 does not). + */ + public static String encodeBase64String(final byte[] binaryData) { + return StringUtils.newStringUtf8(encodeBase64(binaryData, false)); + } + + /** + * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The + * url-safe variation emits - and _ instead of + and / characters. + * Note: no padding is added. + * @param binaryData + * binary data to encode + * @return byte[] containing Base64 characters in their UTF-8 representation. + * @since 1.4 + */ + public static byte[] encodeBase64URLSafe(final byte[] binaryData) { + return encodeBase64(binaryData, false, true); + } + + /** + * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The + * url-safe variation emits - and _ instead of + and / characters. + * Note: no padding is added. + * @param binaryData + * binary data to encode + * @return String containing Base64 characters + * @since 1.4 + */ + public static String encodeBase64URLSafeString(final byte[] binaryData) { + return StringUtils.newStringUtf8(encodeBase64(binaryData, false, true)); + } + + /** + * Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks + * + * @param binaryData + * binary data to encode + * @return Base64 characters chunked in 76 character blocks + */ + public static byte[] encodeBase64Chunked(final byte[] binaryData) { + return encodeBase64(binaryData, true); + } + + /** + * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. + * + * @param binaryData + * Array containing binary data to encode. + * @param isChunked + * if true this encoder will chunk the base64 output into 76 character blocks + * @return Base64-encoded data. + * @throws IllegalArgumentException + * Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} + */ + public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked) { + return encodeBase64(binaryData, isChunked, false); + } + + /** + * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. + * + * @param binaryData + * Array containing binary data to encode. + * @param isChunked + * if true this encoder will chunk the base64 output into 76 character blocks + * @param urlSafe + * if true this encoder will emit - and _ instead of the usual + and / characters. + * Note: no padding is added when encoding using the URL-safe alphabet. + * @return Base64-encoded data. + * @throws IllegalArgumentException + * Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} + * @since 1.4 + */ + public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked, final boolean urlSafe) { + return encodeBase64(binaryData, isChunked, urlSafe, Integer.MAX_VALUE); + } + + /** + * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. + * + * @param binaryData + * Array containing binary data to encode. + * @param isChunked + * if true this encoder will chunk the base64 output into 76 character blocks + * @param urlSafe + * if true this encoder will emit - and _ instead of the usual + and / characters. + * Note: no padding is added when encoding using the URL-safe alphabet. + * @param maxResultSize + * The maximum result size to accept. + * @return Base64-encoded data. + * @throws IllegalArgumentException + * Thrown when the input array needs an output array bigger than maxResultSize + * @since 1.4 + */ + public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked, + final boolean urlSafe, final int maxResultSize) { + if (binaryData == null || binaryData.length == 0) { + return binaryData; + } + + // Create this so can use the super-class method + // Also ensures that the same roundings are performed by the ctor and the code + final Base64 b64 = isChunked ? new Base64(urlSafe) : new Base64(0, CHUNK_SEPARATOR, urlSafe); + final long len = b64.getEncodedLength(binaryData); + if (len > maxResultSize) { + throw new IllegalArgumentException("Input array too big, the output array would be bigger (" + + len + + ") than the specified maximum size of " + + maxResultSize); + } + + return b64.encode(binaryData); + } + + /** + * Decodes a Base64 String into octets. + *

+ * Note: this method seamlessly handles data encoded in URL-safe or normal mode. + *

+ * + * @param base64String + * String containing Base64 data + * @return Array containing decoded data. + * @since 1.4 + */ + public static byte[] decodeBase64(final String base64String) { + return new Base64().decode(base64String); + } + + /** + * Decodes Base64 data into octets. + *

+ * Note: this method seamlessly handles data encoded in URL-safe or normal mode. + *

+ * + * @param base64Data + * Byte array containing Base64 data + * @return Array containing decoded data. + */ + public static byte[] decodeBase64(final byte[] base64Data) { + return new Base64().decode(base64Data); + } + + // Implementation of the Encoder Interface + + // Implementation of integer encoding used for crypto + /** + * Decodes a byte64-encoded integer according to crypto standards such as W3C's XML-Signature. + * + * @param pArray + * a byte array containing base64 character data + * @return A BigInteger + * @since 1.4 + */ + public static BigInteger decodeInteger(final byte[] pArray) { + return new BigInteger(1, decodeBase64(pArray)); + } + + /** + * Encodes to a byte64-encoded integer according to crypto standards such as W3C's XML-Signature. + * + * @param bigInt + * a BigInteger + * @return A byte array containing base64 character data + * @throws NullPointerException + * if null is passed in + * @since 1.4 + */ + public static byte[] encodeInteger(final BigInteger bigInt) { + if (bigInt == null) { + throw new NullPointerException("encodeInteger called with null parameter"); + } + return encodeBase64(toIntegerBytes(bigInt), false); + } + + /** + * Returns a byte-array representation of a BigInteger without sign bit. + * + * @param bigInt + * BigInteger to be converted + * @return a byte array representation of the BigInteger parameter + */ + static byte[] toIntegerBytes(final BigInteger bigInt) { + int bitlen = bigInt.bitLength(); + // round bitlen + bitlen = ((bitlen + 7) >> 3) << 3; + final byte[] bigBytes = bigInt.toByteArray(); + + if (((bigInt.bitLength() % 8) != 0) && (((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) { + return bigBytes; + } + // set up params for copying everything but sign bit + int startSrc = 0; + int len = bigBytes.length; + + // if bigInt is exactly byte-aligned, just skip signbit in copy + if ((bigInt.bitLength() % 8) == 0) { + startSrc = 1; + len--; + } + final int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec + final byte[] resizedBytes = new byte[bitlen / 8]; + System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len); + return resizedBytes; + } + + /** + * Returns whether or not the octet is in the Base64 alphabet. + * + * @param octet + * The value to test + * @return true if the value is defined in the the Base64 alphabet false otherwise. + */ + @Override + protected boolean isInAlphabet(final byte octet) { + return octet >= 0 && octet < decodeTable.length && decodeTable[octet] != -1; + } + +} diff --git a/src/org/apache/commons/codec/binary/Base64InputStream.java b/src/org/apache/commons/codec/binary/Base64InputStream.java new file mode 100644 index 00000000..3cdfa88c --- /dev/null +++ b/src/org/apache/commons/codec/binary/Base64InputStream.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.binary; + +import java.io.InputStream; + +/** + * Provides Base64 encoding and decoding in a streaming fashion (unlimited size). When encoding the default lineLength + * is 76 characters and the default lineEnding is CRLF, but these can be overridden by using the appropriate + * constructor. + *

+ * The default behaviour of the Base64InputStream is to DECODE, whereas the default behaviour of the Base64OutputStream + * is to ENCODE, but this behaviour can be overridden by using a different constructor. + *

+ *

+ * This class implements section 6.8. Base64 Content-Transfer-Encoding from RFC 2045 Multipurpose + * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies by Freed and Borenstein. + *

+ *

+ * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only encode/decode + * character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, UTF-8, etc). + *

+ * + * @version $Id: Base64InputStream.java 1634429 2014-10-27 01:08:36Z ggregory $ + * @see RFC 2045 + * @since 1.4 + */ +public class Base64InputStream extends BaseNCodecInputStream { + + /** + * Creates a Base64InputStream such that all data read is Base64-decoded from the original provided InputStream. + * + * @param in + * InputStream to wrap. + */ + public Base64InputStream(final InputStream in) { + this(in, false); + } + + /** + * Creates a Base64InputStream such that all data read is either Base64-encoded or Base64-decoded from the original + * provided InputStream. + * + * @param in + * InputStream to wrap. + * @param doEncode + * true if we should encode all data read from us, false if we should decode. + */ + public Base64InputStream(final InputStream in, final boolean doEncode) { + super(in, new Base64(false), doEncode); + } + + /** + * Creates a Base64InputStream such that all data read is either Base64-encoded or Base64-decoded from the original + * provided InputStream. + * + * @param in + * InputStream to wrap. + * @param doEncode + * true if we should encode all data read from us, false if we should decode. + * @param lineLength + * If doEncode is true, each line of encoded data will contain lineLength characters (rounded down to + * nearest multiple of 4). If lineLength <= 0, the encoded data is not divided into lines. If doEncode + * is false, lineLength is ignored. + * @param lineSeparator + * If doEncode is true, each line of encoded data will be terminated with this byte sequence (e.g. \r\n). + * If lineLength <= 0, the lineSeparator is not used. If doEncode is false lineSeparator is ignored. + */ + public Base64InputStream(final InputStream in, final boolean doEncode, + final int lineLength, final byte[] lineSeparator) { + super(in, new Base64(lineLength, lineSeparator), doEncode); + } +} diff --git a/src/org/apache/commons/codec/binary/Base64OutputStream.java b/src/org/apache/commons/codec/binary/Base64OutputStream.java new file mode 100644 index 00000000..2845ec37 --- /dev/null +++ b/src/org/apache/commons/codec/binary/Base64OutputStream.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.binary; + +import java.io.OutputStream; + +/** + * Provides Base64 encoding and decoding in a streaming fashion (unlimited size). When encoding the default lineLength + * is 76 characters and the default lineEnding is CRLF, but these can be overridden by using the appropriate + * constructor. + *

+ * The default behaviour of the Base64OutputStream is to ENCODE, whereas the default behaviour of the Base64InputStream + * is to DECODE. But this behaviour can be overridden by using a different constructor. + *

+ *

+ * This class implements section 6.8. Base64 Content-Transfer-Encoding from RFC 2045 Multipurpose + * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies by Freed and Borenstein. + *

+ *

+ * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only encode/decode + * character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, UTF-8, etc). + *

+ *

+ * Note: It is mandatory to close the stream after the last byte has been written to it, otherwise the + * final padding will be omitted and the resulting data will be incomplete/inconsistent. + *

+ * + * @version $Id: Base64OutputStream.java 1635952 2014-11-01 14:19:04Z tn $ + * @see RFC 2045 + * @since 1.4 + */ +public class Base64OutputStream extends BaseNCodecOutputStream { + + /** + * Creates a Base64OutputStream such that all data written is Base64-encoded to the original provided OutputStream. + * + * @param out + * OutputStream to wrap. + */ + public Base64OutputStream(final OutputStream out) { + this(out, true); + } + + /** + * Creates a Base64OutputStream such that all data written is either Base64-encoded or Base64-decoded to the + * original provided OutputStream. + * + * @param out + * OutputStream to wrap. + * @param doEncode + * true if we should encode all data written to us, false if we should decode. + */ + public Base64OutputStream(final OutputStream out, final boolean doEncode) { + super(out,new Base64(false), doEncode); + } + + /** + * Creates a Base64OutputStream such that all data written is either Base64-encoded or Base64-decoded to the + * original provided OutputStream. + * + * @param out + * OutputStream to wrap. + * @param doEncode + * true if we should encode all data written to us, false if we should decode. + * @param lineLength + * If doEncode is true, each line of encoded data will contain lineLength characters (rounded down to + * nearest multiple of 4). If lineLength <= 0, the encoded data is not divided into lines. If doEncode + * is false, lineLength is ignored. + * @param lineSeparator + * If doEncode is true, each line of encoded data will be terminated with this byte sequence (e.g. \r\n). + * If lineLength <= 0, the lineSeparator is not used. If doEncode is false lineSeparator is ignored. + */ + public Base64OutputStream(final OutputStream out, final boolean doEncode, + final int lineLength, final byte[] lineSeparator) { + super(out, new Base64(lineLength, lineSeparator), doEncode); + } +} diff --git a/src/org/apache/commons/codec/binary/BaseNCodec.java b/src/org/apache/commons/codec/binary/BaseNCodec.java new file mode 100644 index 00000000..46fc4f0d --- /dev/null +++ b/src/org/apache/commons/codec/binary/BaseNCodec.java @@ -0,0 +1,525 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.binary; + +import java.util.Arrays; + +import org.apache.commons.codec.BinaryDecoder; +import org.apache.commons.codec.BinaryEncoder; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.EncoderException; + +/** + * Abstract superclass for Base-N encoders and decoders. + * + *

+ * This class is thread-safe. + *

+ * + * @version $Id: BaseNCodec.java 1634404 2014-10-26 23:06:10Z ggregory $ + */ +public abstract class BaseNCodec implements BinaryEncoder, BinaryDecoder { + + /** + * Holds thread context so classes can be thread-safe. + * + * This class is not itself thread-safe; each thread must allocate its own copy. + * + * @since 1.7 + */ + static class Context { + + /** + * Place holder for the bytes we're dealing with for our based logic. + * Bitwise operations store and extract the encoding or decoding from this variable. + */ + int ibitWorkArea; + + /** + * Place holder for the bytes we're dealing with for our based logic. + * Bitwise operations store and extract the encoding or decoding from this variable. + */ + long lbitWorkArea; + + /** + * Buffer for streaming. + */ + byte[] buffer; + + /** + * Position where next character should be written in the buffer. + */ + int pos; + + /** + * Position where next character should be read from the buffer. + */ + int readPos; + + /** + * Boolean flag to indicate the EOF has been reached. Once EOF has been reached, this object becomes useless, + * and must be thrown away. + */ + boolean eof; + + /** + * Variable tracks how many characters have been written to the current line. Only used when encoding. We use + * it to make sure each encoded line never goes beyond lineLength (if lineLength > 0). + */ + int currentLinePos; + + /** + * Writes to the buffer only occur after every 3/5 reads when encoding, and every 4/8 reads when decoding. This + * variable helps track that. + */ + int modulus; + + Context() { + } + + /** + * Returns a String useful for debugging (especially within a debugger.) + * + * @return a String useful for debugging. + */ + @SuppressWarnings("boxing") // OK to ignore boxing here + @Override + public String toString() { + return String.format("%s[buffer=%s, currentLinePos=%s, eof=%s, ibitWorkArea=%s, lbitWorkArea=%s, " + + "modulus=%s, pos=%s, readPos=%s]", this.getClass().getSimpleName(), Arrays.toString(buffer), + currentLinePos, eof, ibitWorkArea, lbitWorkArea, modulus, pos, readPos); + } + } + + /** + * EOF + * + * @since 1.7 + */ + static final int EOF = -1; + + /** + * MIME chunk size per RFC 2045 section 6.8. + * + *

+ * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any + * equal signs. + *

+ * + * @see RFC 2045 section 6.8 + */ + public static final int MIME_CHUNK_SIZE = 76; + + /** + * PEM chunk size per RFC 1421 section 4.3.2.4. + * + *

+ * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any + * equal signs. + *

+ * + * @see RFC 1421 section 4.3.2.4 + */ + public static final int PEM_CHUNK_SIZE = 64; + + private static final int DEFAULT_BUFFER_RESIZE_FACTOR = 2; + + /** + * Defines the default buffer size - currently {@value} + * - must be large enough for at least one encoded block+separator + */ + private static final int DEFAULT_BUFFER_SIZE = 8192; + + /** Mask used to extract 8 bits, used in decoding bytes */ + protected static final int MASK_8BITS = 0xff; + + /** + * Byte used to pad output. + */ + protected static final byte PAD_DEFAULT = '='; // Allow static access to default + + /** + * @deprecated Use {@link #pad}. Will be removed in 2.0. + */ + @Deprecated + protected final byte PAD = PAD_DEFAULT; // instance variable just in case it needs to vary later + + protected final byte pad; // instance variable just in case it needs to vary later + + /** Number of bytes in each full block of unencoded data, e.g. 4 for Base64 and 5 for Base32 */ + private final int unencodedBlockSize; + + /** Number of bytes in each full block of encoded data, e.g. 3 for Base64 and 8 for Base32 */ + private final int encodedBlockSize; + + /** + * Chunksize for encoding. Not used when decoding. + * A value of zero or less implies no chunking of the encoded data. + * Rounded down to nearest multiple of encodedBlockSize. + */ + protected final int lineLength; + + /** + * Size of chunk separator. Not used unless {@link #lineLength} > 0. + */ + private final int chunkSeparatorLength; + + /** + * Note lineLength is rounded down to the nearest multiple of {@link #encodedBlockSize} + * If chunkSeparatorLength is zero, then chunking is disabled. + * @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3) + * @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4) + * @param lineLength if > 0, use chunking with a length lineLength + * @param chunkSeparatorLength the chunk separator length, if relevant + */ + protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize, + final int lineLength, final int chunkSeparatorLength) { + this(unencodedBlockSize, encodedBlockSize, lineLength, chunkSeparatorLength, PAD_DEFAULT); + } + + /** + * Note lineLength is rounded down to the nearest multiple of {@link #encodedBlockSize} + * If chunkSeparatorLength is zero, then chunking is disabled. + * @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3) + * @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4) + * @param lineLength if > 0, use chunking with a length lineLength + * @param chunkSeparatorLength the chunk separator length, if relevant + * @param pad byte used as padding byte. + */ + protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize, + final int lineLength, final int chunkSeparatorLength, final byte pad) { + this.unencodedBlockSize = unencodedBlockSize; + this.encodedBlockSize = encodedBlockSize; + final boolean useChunking = lineLength > 0 && chunkSeparatorLength > 0; + this.lineLength = useChunking ? (lineLength / encodedBlockSize) * encodedBlockSize : 0; + this.chunkSeparatorLength = chunkSeparatorLength; + + this.pad = pad; + } + + /** + * Returns true if this object has buffered data for reading. + * + * @param context the context to be used + * @return true if there is data still available for reading. + */ + boolean hasData(final Context context) { // package protected for access from I/O streams + return context.buffer != null; + } + + /** + * Returns the amount of buffered data available for reading. + * + * @param context the context to be used + * @return The amount of buffered data available for reading. + */ + int available(final Context context) { // package protected for access from I/O streams + return context.buffer != null ? context.pos - context.readPos : 0; + } + + /** + * Get the default buffer size. Can be overridden. + * + * @return {@link #DEFAULT_BUFFER_SIZE} + */ + protected int getDefaultBufferSize() { + return DEFAULT_BUFFER_SIZE; + } + + /** + * Increases our buffer by the {@link #DEFAULT_BUFFER_RESIZE_FACTOR}. + * @param context the context to be used + */ + private byte[] resizeBuffer(final Context context) { + if (context.buffer == null) { + context.buffer = new byte[getDefaultBufferSize()]; + context.pos = 0; + context.readPos = 0; + } else { + final byte[] b = new byte[context.buffer.length * DEFAULT_BUFFER_RESIZE_FACTOR]; + System.arraycopy(context.buffer, 0, b, 0, context.buffer.length); + context.buffer = b; + } + return context.buffer; + } + + /** + * Ensure that the buffer has room for size bytes + * + * @param size minimum spare space required + * @param context the context to be used + * @return the buffer + */ + protected byte[] ensureBufferSize(final int size, final Context context){ + if ((context.buffer == null) || (context.buffer.length < context.pos + size)){ + return resizeBuffer(context); + } + return context.buffer; + } + + /** + * Extracts buffered data into the provided byte[] array, starting at position bPos, up to a maximum of bAvail + * bytes. Returns how many bytes were actually extracted. + *

+ * Package protected for access from I/O streams. + * + * @param b + * byte[] array to extract the buffered data into. + * @param bPos + * position in byte[] array to start extraction at. + * @param bAvail + * amount of bytes we're allowed to extract. We may extract fewer (if fewer are available). + * @param context + * the context to be used + * @return The number of bytes successfully extracted into the provided byte[] array. + */ + int readResults(final byte[] b, final int bPos, final int bAvail, final Context context) { + if (context.buffer != null) { + final int len = Math.min(available(context), bAvail); + System.arraycopy(context.buffer, context.readPos, b, bPos, len); + context.readPos += len; + if (context.readPos >= context.pos) { + context.buffer = null; // so hasData() will return false, and this method can return -1 + } + return len; + } + return context.eof ? EOF : 0; + } + + /** + * Checks if a byte value is whitespace or not. + * Whitespace is taken to mean: space, tab, CR, LF + * @param byteToCheck + * the byte to check + * @return true if byte is whitespace, false otherwise + */ + protected static boolean isWhiteSpace(final byte byteToCheck) { + switch (byteToCheck) { + case ' ' : + case '\n' : + case '\r' : + case '\t' : + return true; + default : + return false; + } + } + + /** + * Encodes an Object using the Base-N algorithm. This method is provided in order to satisfy the requirements of + * the Encoder interface, and will throw an EncoderException if the supplied object is not of type byte[]. + * + * @param obj + * Object to encode + * @return An object (of type byte[]) containing the Base-N encoded data which corresponds to the byte[] supplied. + * @throws EncoderException + * if the parameter supplied is not of type byte[] + */ + @Override + public Object encode(final Object obj) throws EncoderException { + if (!(obj instanceof byte[])) { + throw new EncoderException("Parameter supplied to Base-N encode is not a byte[]"); + } + return encode((byte[]) obj); + } + + /** + * Encodes a byte[] containing binary data, into a String containing characters in the Base-N alphabet. + * Uses UTF8 encoding. + * + * @param pArray + * a byte array containing binary data + * @return A String containing only Base-N character data + */ + public String encodeToString(final byte[] pArray) { + return StringUtils.newStringUtf8(encode(pArray)); + } + + /** + * Encodes a byte[] containing binary data, into a String containing characters in the appropriate alphabet. + * Uses UTF8 encoding. + * + * @param pArray a byte array containing binary data + * @return String containing only character data in the appropriate alphabet. + */ + public String encodeAsString(final byte[] pArray){ + return StringUtils.newStringUtf8(encode(pArray)); + } + + /** + * Decodes an Object using the Base-N algorithm. This method is provided in order to satisfy the requirements of + * the Decoder interface, and will throw a DecoderException if the supplied object is not of type byte[] or String. + * + * @param obj + * Object to decode + * @return An object (of type byte[]) containing the binary data which corresponds to the byte[] or String + * supplied. + * @throws DecoderException + * if the parameter supplied is not of type byte[] + */ + @Override + public Object decode(final Object obj) throws DecoderException { + if (obj instanceof byte[]) { + return decode((byte[]) obj); + } else if (obj instanceof String) { + return decode((String) obj); + } else { + throw new DecoderException("Parameter supplied to Base-N decode is not a byte[] or a String"); + } + } + + /** + * Decodes a String containing characters in the Base-N alphabet. + * + * @param pArray + * A String containing Base-N character data + * @return a byte array containing binary data + */ + public byte[] decode(final String pArray) { + return decode(StringUtils.getBytesUtf8(pArray)); + } + + /** + * Decodes a byte[] containing characters in the Base-N alphabet. + * + * @param pArray + * A byte array containing Base-N character data + * @return a byte array containing binary data + */ + @Override + public byte[] decode(final byte[] pArray) { + if (pArray == null || pArray.length == 0) { + return pArray; + } + final Context context = new Context(); + decode(pArray, 0, pArray.length, context); + decode(pArray, 0, EOF, context); // Notify decoder of EOF. + final byte[] result = new byte[context.pos]; + readResults(result, 0, result.length, context); + return result; + } + + /** + * Encodes a byte[] containing binary data, into a byte[] containing characters in the alphabet. + * + * @param pArray + * a byte array containing binary data + * @return A byte array containing only the basen alphabetic character data + */ + @Override + public byte[] encode(final byte[] pArray) { + if (pArray == null || pArray.length == 0) { + return pArray; + } + final Context context = new Context(); + encode(pArray, 0, pArray.length, context); + encode(pArray, 0, EOF, context); // Notify encoder of EOF. + final byte[] buf = new byte[context.pos - context.readPos]; + readResults(buf, 0, buf.length, context); + return buf; + } + + // package protected for access from I/O streams + abstract void encode(byte[] pArray, int i, int length, Context context); + + // package protected for access from I/O streams + abstract void decode(byte[] pArray, int i, int length, Context context); + + /** + * Returns whether or not the octet is in the current alphabet. + * Does not allow whitespace or pad. + * + * @param value The value to test + * + * @return true if the value is defined in the current alphabet, false otherwise. + */ + protected abstract boolean isInAlphabet(byte value); + + /** + * Tests a given byte array to see if it contains only valid characters within the alphabet. + * The method optionally treats whitespace and pad as valid. + * + * @param arrayOctet byte array to test + * @param allowWSPad if true, then whitespace and PAD are also allowed + * + * @return true if all bytes are valid characters in the alphabet or if the byte array is empty; + * false, otherwise + */ + public boolean isInAlphabet(final byte[] arrayOctet, final boolean allowWSPad) { + for (int i = 0; i < arrayOctet.length; i++) { + if (!isInAlphabet(arrayOctet[i]) && + (!allowWSPad || (arrayOctet[i] != pad) && !isWhiteSpace(arrayOctet[i]))) { + return false; + } + } + return true; + } + + /** + * Tests a given String to see if it contains only valid characters within the alphabet. + * The method treats whitespace and PAD as valid. + * + * @param basen String to test + * @return true if all characters in the String are valid characters in the alphabet or if + * the String is empty; false, otherwise + * @see #isInAlphabet(byte[], boolean) + */ + public boolean isInAlphabet(final String basen) { + return isInAlphabet(StringUtils.getBytesUtf8(basen), true); + } + + /** + * Tests a given byte array to see if it contains any characters within the alphabet or PAD. + * + * Intended for use in checking line-ending arrays + * + * @param arrayOctet + * byte array to test + * @return true if any byte is a valid character in the alphabet or PAD; false otherwise + */ + protected boolean containsAlphabetOrPad(final byte[] arrayOctet) { + if (arrayOctet == null) { + return false; + } + for (final byte element : arrayOctet) { + if (pad == element || isInAlphabet(element)) { + return true; + } + } + return false; + } + + /** + * Calculates the amount of space needed to encode the supplied array. + * + * @param pArray byte[] array which will later be encoded + * + * @return amount of space needed to encoded the supplied array. + * Returns a long since a max-len array will require > Integer.MAX_VALUE + */ + public long getEncodedLength(final byte[] pArray) { + // Calculate non-chunked size - rounded up to allow for padding + // cast to long is needed to avoid possibility of overflow + long len = ((pArray.length + unencodedBlockSize-1) / unencodedBlockSize) * (long) encodedBlockSize; + if (lineLength > 0) { // We're using chunking + // Round up to nearest multiple + len += ((len + lineLength-1) / lineLength) * chunkSeparatorLength; + } + return len; + } +} diff --git a/src/org/apache/commons/codec/binary/BaseNCodecInputStream.java b/src/org/apache/commons/codec/binary/BaseNCodecInputStream.java new file mode 100644 index 00000000..777eb6d2 --- /dev/null +++ b/src/org/apache/commons/codec/binary/BaseNCodecInputStream.java @@ -0,0 +1,211 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.binary; + +import static org.apache.commons.codec.binary.BaseNCodec.EOF; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.commons.codec.binary.BaseNCodec.Context; + +/** + * Abstract superclass for Base-N input streams. + * + * @since 1.5 + * @version $Id: BaseNCodecInputStream.java 1429868 2013-01-07 16:08:05Z ggregory $ + */ +public class BaseNCodecInputStream extends FilterInputStream { + + private final BaseNCodec baseNCodec; + + private final boolean doEncode; + + private final byte[] singleByte = new byte[1]; + + private final Context context = new Context(); + + protected BaseNCodecInputStream(final InputStream in, final BaseNCodec baseNCodec, final boolean doEncode) { + super(in); + this.doEncode = doEncode; + this.baseNCodec = baseNCodec; + } + + /** + * {@inheritDoc} + * + * @return 0 if the {@link InputStream} has reached EOF, + * 1 otherwise + * @since 1.7 + */ + @Override + public int available() throws IOException { + // Note: the logic is similar to the InflaterInputStream: + // as long as we have not reached EOF, indicate that there is more + // data available. As we do not know for sure how much data is left, + // just return 1 as a safe guess. + + return context.eof ? 0 : 1; + } + + /** + * Marks the current position in this input stream. + *

The {@link #mark} method of {@link BaseNCodecInputStream} does nothing.

+ * + * @param readLimit the maximum limit of bytes that can be read before the mark position becomes invalid. + * @since 1.7 + */ + @Override + public synchronized void mark(final int readLimit) { + } + + /** + * {@inheritDoc} + * + * @return always returns false + */ + @Override + public boolean markSupported() { + return false; // not an easy job to support marks + } + + /** + * Reads one byte from this input stream. + * + * @return the byte as an integer in the range 0 to 255. Returns -1 if EOF has been reached. + * @throws IOException + * if an I/O error occurs. + */ + @Override + public int read() throws IOException { + int r = read(singleByte, 0, 1); + while (r == 0) { + r = read(singleByte, 0, 1); + } + if (r > 0) { + final byte b = singleByte[0]; + return b < 0 ? 256 + b : b; + } + return EOF; + } + + /** + * Attempts to read len bytes into the specified b array starting at offset + * from this InputStream. + * + * @param b + * destination byte array + * @param offset + * where to start writing the bytes + * @param len + * maximum number of bytes to read + * + * @return number of bytes read + * @throws IOException + * if an I/O error occurs. + * @throws NullPointerException + * if the byte array parameter is null + * @throws IndexOutOfBoundsException + * if offset, len or buffer size are invalid + */ + @Override + public int read(final byte b[], final int offset, final int len) throws IOException { + if (b == null) { + throw new NullPointerException(); + } else if (offset < 0 || len < 0) { + throw new IndexOutOfBoundsException(); + } else if (offset > b.length || offset + len > b.length) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; + } else { + int readLen = 0; + /* + Rationale for while-loop on (readLen == 0): + ----- + Base32.readResults() usually returns > 0 or EOF (-1). In the + rare case where it returns 0, we just keep trying. + + This is essentially an undocumented contract for InputStream + implementors that want their code to work properly with + java.io.InputStreamReader, since the latter hates it when + InputStream.read(byte[]) returns a zero. Unfortunately our + readResults() call must return 0 if a large amount of the data + being decoded was non-base32, so this while-loop enables proper + interop with InputStreamReader for that scenario. + ----- + This is a fix for CODEC-101 + */ + while (readLen == 0) { + if (!baseNCodec.hasData(context)) { + final byte[] buf = new byte[doEncode ? 4096 : 8192]; + final int c = in.read(buf); + if (doEncode) { + baseNCodec.encode(buf, 0, c, context); + } else { + baseNCodec.decode(buf, 0, c, context); + } + } + readLen = baseNCodec.readResults(b, offset, len, context); + } + return readLen; + } + } + + /** + * Repositions this stream to the position at the time the mark method was last called on this input stream. + *

+ * The {@link #reset} method of {@link BaseNCodecInputStream} does nothing except throw an {@link IOException}. + * + * @throws IOException if this method is invoked + * @since 1.7 + */ + @Override + public synchronized void reset() throws IOException { + throw new IOException("mark/reset not supported"); + } + + /** + * {@inheritDoc} + * + * @throws IllegalArgumentException if the provided skip length is negative + * @since 1.7 + */ + @Override + public long skip(final long n) throws IOException { + if (n < 0) { + throw new IllegalArgumentException("Negative skip length: " + n); + } + + // skip in chunks of 512 bytes + final byte[] b = new byte[512]; + long todo = n; + + while (todo > 0) { + int len = (int) Math.min(b.length, todo); + len = this.read(b, 0, len); + if (len == EOF) { + break; + } + todo -= len; + } + + return n - todo; + } +} diff --git a/src/org/apache/commons/codec/binary/BaseNCodecOutputStream.java b/src/org/apache/commons/codec/binary/BaseNCodecOutputStream.java new file mode 100644 index 00000000..72ce81a6 --- /dev/null +++ b/src/org/apache/commons/codec/binary/BaseNCodecOutputStream.java @@ -0,0 +1,153 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.binary; + +import static org.apache.commons.codec.binary.BaseNCodec.EOF; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.commons.codec.binary.BaseNCodec.Context; + +/** + * Abstract superclass for Base-N output streams. + * + * @since 1.5 + * @version $Id: BaseNCodecOutputStream.java 1544347 2013-11-21 22:30:31Z ggregory $ + */ +public class BaseNCodecOutputStream extends FilterOutputStream { + + private final boolean doEncode; + + private final BaseNCodec baseNCodec; + + private final byte[] singleByte = new byte[1]; + + private final Context context = new Context(); + + // TODO should this be protected? + public BaseNCodecOutputStream(final OutputStream out, final BaseNCodec basedCodec, final boolean doEncode) { + super(out); + this.baseNCodec = basedCodec; + this.doEncode = doEncode; + } + + /** + * Writes the specified byte to this output stream. + * + * @param i + * source byte + * @throws IOException + * if an I/O error occurs. + */ + @Override + public void write(final int i) throws IOException { + singleByte[0] = (byte) i; + write(singleByte, 0, 1); + } + + /** + * Writes len bytes from the specified b array starting at offset to this + * output stream. + * + * @param b + * source byte array + * @param offset + * where to start reading the bytes + * @param len + * maximum number of bytes to write + * + * @throws IOException + * if an I/O error occurs. + * @throws NullPointerException + * if the byte array parameter is null + * @throws IndexOutOfBoundsException + * if offset, len or buffer size are invalid + */ + @Override + public void write(final byte b[], final int offset, final int len) throws IOException { + if (b == null) { + throw new NullPointerException(); + } else if (offset < 0 || len < 0) { + throw new IndexOutOfBoundsException(); + } else if (offset > b.length || offset + len > b.length) { + throw new IndexOutOfBoundsException(); + } else if (len > 0) { + if (doEncode) { + baseNCodec.encode(b, offset, len, context); + } else { + baseNCodec.decode(b, offset, len, context); + } + flush(false); + } + } + + /** + * Flushes this output stream and forces any buffered output bytes to be written out to the stream. If propagate is + * true, the wrapped stream will also be flushed. + * + * @param propagate + * boolean flag to indicate whether the wrapped OutputStream should also be flushed. + * @throws IOException + * if an I/O error occurs. + */ + private void flush(final boolean propagate) throws IOException { + final int avail = baseNCodec.available(context); + if (avail > 0) { + final byte[] buf = new byte[avail]; + final int c = baseNCodec.readResults(buf, 0, avail, context); + if (c > 0) { + out.write(buf, 0, c); + } + } + if (propagate) { + out.flush(); + } + } + + /** + * Flushes this output stream and forces any buffered output bytes to be written out to the stream. + * + * @throws IOException + * if an I/O error occurs. + */ + @Override + public void flush() throws IOException { + flush(true); + } + + /** + * Closes this output stream and releases any system resources associated with the stream. + * + * @throws IOException + * if an I/O error occurs. + */ + @Override + public void close() throws IOException { + // Notify encoder of EOF (-1). + if (doEncode) { + baseNCodec.encode(singleByte, 0, EOF, context); + } else { + baseNCodec.decode(singleByte, 0, EOF, context); + } + flush(); + out.close(); + } + +} diff --git a/src/org/apache/commons/codec/binary/BinaryCodec.java b/src/org/apache/commons/codec/binary/BinaryCodec.java new file mode 100644 index 00000000..c813e3f8 --- /dev/null +++ b/src/org/apache/commons/codec/binary/BinaryCodec.java @@ -0,0 +1,301 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.binary; + +import org.apache.commons.codec.BinaryDecoder; +import org.apache.commons.codec.BinaryEncoder; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.EncoderException; + +/** + * Converts between byte arrays and strings of "0"s and "1"s. + * + *

This class is immutable and thread-safe.

+ * + * TODO: may want to add more bit vector functions like and/or/xor/nand + * TODO: also might be good to generate boolean[] from byte[] et cetera. + * + * @since 1.3 + * @version $Id: BinaryCodec.java 1619948 2014-08-22 22:53:55Z ggregory $ + */ +public class BinaryCodec implements BinaryDecoder, BinaryEncoder { + /* + * tried to avoid using ArrayUtils to minimize dependencies while using these empty arrays - dep is just not worth + * it. + */ + /** Empty char array. */ + private static final char[] EMPTY_CHAR_ARRAY = new char[0]; + + /** Empty byte array. */ + private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + + /** Mask for bit 0 of a byte. */ + private static final int BIT_0 = 1; + + /** Mask for bit 1 of a byte. */ + private static final int BIT_1 = 0x02; + + /** Mask for bit 2 of a byte. */ + private static final int BIT_2 = 0x04; + + /** Mask for bit 3 of a byte. */ + private static final int BIT_3 = 0x08; + + /** Mask for bit 4 of a byte. */ + private static final int BIT_4 = 0x10; + + /** Mask for bit 5 of a byte. */ + private static final int BIT_5 = 0x20; + + /** Mask for bit 6 of a byte. */ + private static final int BIT_6 = 0x40; + + /** Mask for bit 7 of a byte. */ + private static final int BIT_7 = 0x80; + + private static final int[] BITS = {BIT_0, BIT_1, BIT_2, BIT_3, BIT_4, BIT_5, BIT_6, BIT_7}; + + /** + * Converts an array of raw binary data into an array of ASCII 0 and 1 characters. + * + * @param raw + * the raw binary data to convert + * @return 0 and 1 ASCII character bytes one for each bit of the argument + * @see org.apache.commons.codec.BinaryEncoder#encode(byte[]) + */ + @Override + public byte[] encode(final byte[] raw) { + return toAsciiBytes(raw); + } + + /** + * Converts an array of raw binary data into an array of ASCII 0 and 1 chars. + * + * @param raw + * the raw binary data to convert + * @return 0 and 1 ASCII character chars one for each bit of the argument + * @throws EncoderException + * if the argument is not a byte[] + * @see org.apache.commons.codec.Encoder#encode(Object) + */ + @Override + public Object encode(final Object raw) throws EncoderException { + if (!(raw instanceof byte[])) { + throw new EncoderException("argument not a byte array"); + } + return toAsciiChars((byte[]) raw); + } + + /** + * Decodes a byte array where each byte represents an ASCII '0' or '1'. + * + * @param ascii + * each byte represents an ASCII '0' or '1' + * @return the raw encoded binary where each bit corresponds to a byte in the byte array argument + * @throws DecoderException + * if argument is not a byte[], char[] or String + * @see org.apache.commons.codec.Decoder#decode(Object) + */ + @Override + public Object decode(final Object ascii) throws DecoderException { + if (ascii == null) { + return EMPTY_BYTE_ARRAY; + } + if (ascii instanceof byte[]) { + return fromAscii((byte[]) ascii); + } + if (ascii instanceof char[]) { + return fromAscii((char[]) ascii); + } + if (ascii instanceof String) { + return fromAscii(((String) ascii).toCharArray()); + } + throw new DecoderException("argument not a byte array"); + } + + /** + * Decodes a byte array where each byte represents an ASCII '0' or '1'. + * + * @param ascii + * each byte represents an ASCII '0' or '1' + * @return the raw encoded binary where each bit corresponds to a byte in the byte array argument + * @see org.apache.commons.codec.Decoder#decode(Object) + */ + @Override + public byte[] decode(final byte[] ascii) { + return fromAscii(ascii); + } + + /** + * Decodes a String where each char of the String represents an ASCII '0' or '1'. + * + * @param ascii + * String of '0' and '1' characters + * @return the raw encoded binary where each bit corresponds to a byte in the byte array argument + * @see org.apache.commons.codec.Decoder#decode(Object) + */ + public byte[] toByteArray(final String ascii) { + if (ascii == null) { + return EMPTY_BYTE_ARRAY; + } + return fromAscii(ascii.toCharArray()); + } + + // ------------------------------------------------------------------------ + // + // static codec operations + // + // ------------------------------------------------------------------------ + /** + * Decodes a char array where each char represents an ASCII '0' or '1'. + * + * @param ascii + * each char represents an ASCII '0' or '1' + * @return the raw encoded binary where each bit corresponds to a char in the char array argument + */ + public static byte[] fromAscii(final char[] ascii) { + if (ascii == null || ascii.length == 0) { + return EMPTY_BYTE_ARRAY; + } + // get length/8 times bytes with 3 bit shifts to the right of the length + final byte[] l_raw = new byte[ascii.length >> 3]; + /* + * We decr index jj by 8 as we go along to not recompute indices using multiplication every time inside the + * loop. + */ + for (int ii = 0, jj = ascii.length - 1; ii < l_raw.length; ii++, jj -= 8) { + for (int bits = 0; bits < BITS.length; ++bits) { + if (ascii[jj - bits] == '1') { + l_raw[ii] |= BITS[bits]; + } + } + } + return l_raw; + } + + /** + * Decodes a byte array where each byte represents an ASCII '0' or '1'. + * + * @param ascii + * each byte represents an ASCII '0' or '1' + * @return the raw encoded binary where each bit corresponds to a byte in the byte array argument + */ + public static byte[] fromAscii(final byte[] ascii) { + if (isEmpty(ascii)) { + return EMPTY_BYTE_ARRAY; + } + // get length/8 times bytes with 3 bit shifts to the right of the length + final byte[] l_raw = new byte[ascii.length >> 3]; + /* + * We decr index jj by 8 as we go along to not recompute indices using multiplication every time inside the + * loop. + */ + for (int ii = 0, jj = ascii.length - 1; ii < l_raw.length; ii++, jj -= 8) { + for (int bits = 0; bits < BITS.length; ++bits) { + if (ascii[jj - bits] == '1') { + l_raw[ii] |= BITS[bits]; + } + } + } + return l_raw; + } + + /** + * Returns true if the given array is null or empty (size 0.) + * + * @param array + * the source array + * @return true if the given array is null or empty (size 0.) + */ + private static boolean isEmpty(final byte[] array) { + return array == null || array.length == 0; + } + + /** + * Converts an array of raw binary data into an array of ASCII 0 and 1 character bytes - each byte is a truncated + * char. + * + * @param raw + * the raw binary data to convert + * @return an array of 0 and 1 character bytes for each bit of the argument + * @see org.apache.commons.codec.BinaryEncoder#encode(byte[]) + */ + public static byte[] toAsciiBytes(final byte[] raw) { + if (isEmpty(raw)) { + return EMPTY_BYTE_ARRAY; + } + // get 8 times the bytes with 3 bit shifts to the left of the length + final byte[] l_ascii = new byte[raw.length << 3]; + /* + * We decr index jj by 8 as we go along to not recompute indices using multiplication every time inside the + * loop. + */ + for (int ii = 0, jj = l_ascii.length - 1; ii < raw.length; ii++, jj -= 8) { + for (int bits = 0; bits < BITS.length; ++bits) { + if ((raw[ii] & BITS[bits]) == 0) { + l_ascii[jj - bits] = '0'; + } else { + l_ascii[jj - bits] = '1'; + } + } + } + return l_ascii; + } + + /** + * Converts an array of raw binary data into an array of ASCII 0 and 1 characters. + * + * @param raw + * the raw binary data to convert + * @return an array of 0 and 1 characters for each bit of the argument + * @see org.apache.commons.codec.BinaryEncoder#encode(byte[]) + */ + public static char[] toAsciiChars(final byte[] raw) { + if (isEmpty(raw)) { + return EMPTY_CHAR_ARRAY; + } + // get 8 times the bytes with 3 bit shifts to the left of the length + final char[] l_ascii = new char[raw.length << 3]; + /* + * We decr index jj by 8 as we go along to not recompute indices using multiplication every time inside the + * loop. + */ + for (int ii = 0, jj = l_ascii.length - 1; ii < raw.length; ii++, jj -= 8) { + for (int bits = 0; bits < BITS.length; ++bits) { + if ((raw[ii] & BITS[bits]) == 0) { + l_ascii[jj - bits] = '0'; + } else { + l_ascii[jj - bits] = '1'; + } + } + } + return l_ascii; + } + + /** + * Converts an array of raw binary data into a String of ASCII 0 and 1 characters. + * + * @param raw + * the raw binary data to convert + * @return a String of 0 and 1 characters representing the binary data + * @see org.apache.commons.codec.BinaryEncoder#encode(byte[]) + */ + public static String toAsciiString(final byte[] raw) { + return new String(toAsciiChars(raw)); + } +} diff --git a/src/org/apache/commons/codec/binary/CharSequenceUtils.java b/src/org/apache/commons/codec/binary/CharSequenceUtils.java new file mode 100644 index 00000000..7e4f9974 --- /dev/null +++ b/src/org/apache/commons/codec/binary/CharSequenceUtils.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.codec.binary; + +/** + *

+ * Operations on {@link CharSequence} that are null safe. + *

+ *

+ * Copied from Apache Commons Lang r1586295 on April 10, 2014 (day of 3.3.2 release). + *

+ * + * @see CharSequence + * @since 1.10 + */ +public class CharSequenceUtils { + + /** + * Green implementation of regionMatches. + * + * @param cs + * the CharSequence to be processed + * @param ignoreCase + * whether or not to be case insensitive + * @param thisStart + * the index to start on the cs CharSequence + * @param substring + * the CharSequence to be looked for + * @param start + * the index to start on the substring CharSequence + * @param length + * character length of the region + * @return whether the region matched + */ + static boolean regionMatches(final CharSequence cs, final boolean ignoreCase, final int thisStart, + final CharSequence substring, final int start, final int length) { + if (cs instanceof String && substring instanceof String) { + return ((String) cs).regionMatches(ignoreCase, thisStart, (String) substring, start, length); + } + int index1 = thisStart; + int index2 = start; + int tmpLen = length; + + while (tmpLen-- > 0) { + char c1 = cs.charAt(index1++); + char c2 = substring.charAt(index2++); + + if (c1 == c2) { + continue; + } + + if (!ignoreCase) { + return false; + } + + // The same check as in String.regionMatches(): + if (Character.toUpperCase(c1) != Character.toUpperCase(c2) && + Character.toLowerCase(c1) != Character.toLowerCase(c2)) { + return false; + } + } + + return true; + } +} diff --git a/src/org/apache/commons/codec/binary/Hex.java b/src/org/apache/commons/codec/binary/Hex.java new file mode 100644 index 00000000..8ae0dd7b --- /dev/null +++ b/src/org/apache/commons/codec/binary/Hex.java @@ -0,0 +1,334 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.binary; + +import java.nio.charset.Charset; + +import org.apache.commons.codec.BinaryDecoder; +import org.apache.commons.codec.BinaryEncoder; +import org.apache.commons.codec.CharEncoding; +import org.apache.commons.codec.Charsets; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.EncoderException; + +/** + * Converts hexadecimal Strings. The charset used for certain operation can be set, the default is set in + * {@link #DEFAULT_CHARSET_NAME} + * + * This class is thread-safe. + * + * @since 1.1 + * @version $Id: Hex.java 1619948 2014-08-22 22:53:55Z ggregory $ + */ +public class Hex implements BinaryEncoder, BinaryDecoder { + + /** + * Default charset name is {@link Charsets#UTF_8} + * + * @since 1.7 + */ + public static final Charset DEFAULT_CHARSET = Charsets.UTF_8; + + /** + * Default charset name is {@link CharEncoding#UTF_8} + * + * @since 1.4 + */ + public static final String DEFAULT_CHARSET_NAME = CharEncoding.UTF_8; + + /** + * Used to build output as Hex + */ + private static final char[] DIGITS_LOWER = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + /** + * Used to build output as Hex + */ + private static final char[] DIGITS_UPPER = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + /** + * Converts an array of characters representing hexadecimal values into an array of bytes of those same values. The + * returned array will be half the length of the passed array, as it takes two characters to represent any given + * byte. An exception is thrown if the passed char array has an odd number of elements. + * + * @param data + * An array of characters containing hexadecimal digits + * @return A byte array containing binary data decoded from the supplied char array. + * @throws DecoderException + * Thrown if an odd number or illegal of characters is supplied + */ + public static byte[] decodeHex(final char[] data) throws DecoderException { + + final int len = data.length; + + if ((len & 0x01) != 0) { + throw new DecoderException("Odd number of characters."); + } + + final byte[] out = new byte[len >> 1]; + + // two characters form the hex value. + for (int i = 0, j = 0; j < len; i++) { + int f = toDigit(data[j], j) << 4; + j++; + f = f | toDigit(data[j], j); + j++; + out[i] = (byte) (f & 0xFF); + } + + return out; + } + + /** + * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. + * The returned array will be double the length of the passed array, as it takes two characters to represent any + * given byte. + * + * @param data + * a byte[] to convert to Hex characters + * @return A char[] containing hexadecimal characters + */ + public static char[] encodeHex(final byte[] data) { + return encodeHex(data, true); + } + + /** + * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. + * The returned array will be double the length of the passed array, as it takes two characters to represent any + * given byte. + * + * @param data + * a byte[] to convert to Hex characters + * @param toLowerCase + * true converts to lowercase, false to uppercase + * @return A char[] containing hexadecimal characters + * @since 1.4 + */ + public static char[] encodeHex(final byte[] data, final boolean toLowerCase) { + return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER); + } + + /** + * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. + * The returned array will be double the length of the passed array, as it takes two characters to represent any + * given byte. + * + * @param data + * a byte[] to convert to Hex characters + * @param toDigits + * the output alphabet + * @return A char[] containing hexadecimal characters + * @since 1.4 + */ + protected static char[] encodeHex(final byte[] data, final char[] toDigits) { + final int l = data.length; + final char[] out = new char[l << 1]; + // two characters form the hex value. + for (int i = 0, j = 0; i < l; i++) { + out[j++] = toDigits[(0xF0 & data[i]) >>> 4]; + out[j++] = toDigits[0x0F & data[i]]; + } + return out; + } + + /** + * Converts an array of bytes into a String representing the hexadecimal values of each byte in order. The returned + * String will be double the length of the passed array, as it takes two characters to represent any given byte. + * + * @param data + * a byte[] to convert to Hex characters + * @return A String containing hexadecimal characters + * @since 1.4 + */ + public static String encodeHexString(final byte[] data) { + return new String(encodeHex(data)); + } + + /** + * Converts a hexadecimal character to an integer. + * + * @param ch + * A character to convert to an integer digit + * @param index + * The index of the character in the source + * @return An integer + * @throws DecoderException + * Thrown if ch is an illegal hex character + */ + protected static int toDigit(final char ch, final int index) throws DecoderException { + final int digit = Character.digit(ch, 16); + if (digit == -1) { + throw new DecoderException("Illegal hexadecimal character " + ch + " at index " + index); + } + return digit; + } + + private final Charset charset; + + /** + * Creates a new codec with the default charset name {@link #DEFAULT_CHARSET} + */ + public Hex() { + // use default encoding + this.charset = DEFAULT_CHARSET; + } + + /** + * Creates a new codec with the given Charset. + * + * @param charset + * the charset. + * @since 1.7 + */ + public Hex(final Charset charset) { + this.charset = charset; + } + + /** + * Creates a new codec with the given charset name. + * + * @param charsetName + * the charset name. + * @throws java.nio.charset.UnsupportedCharsetException + * If the named charset is unavailable + * @since 1.4 + * @since 1.7 throws UnsupportedCharsetException if the named charset is unavailable + */ + public Hex(final String charsetName) { + this(Charset.forName(charsetName)); + } + + /** + * Converts an array of character bytes representing hexadecimal values into an array of bytes of those same values. + * The returned array will be half the length of the passed array, as it takes two characters to represent any given + * byte. An exception is thrown if the passed char array has an odd number of elements. + * + * @param array + * An array of character bytes containing hexadecimal digits + * @return A byte array containing binary data decoded from the supplied byte array (representing characters). + * @throws DecoderException + * Thrown if an odd number of characters is supplied to this function + * @see #decodeHex(char[]) + */ + @Override + public byte[] decode(final byte[] array) throws DecoderException { + return decodeHex(new String(array, getCharset()).toCharArray()); + } + + /** + * Converts a String or an array of character bytes representing hexadecimal values into an array of bytes of those + * same values. The returned array will be half the length of the passed String or array, as it takes two characters + * to represent any given byte. An exception is thrown if the passed char array has an odd number of elements. + * + * @param object + * A String or, an array of character bytes containing hexadecimal digits + * @return A byte array containing binary data decoded from the supplied byte array (representing characters). + * @throws DecoderException + * Thrown if an odd number of characters is supplied to this function or the object is not a String or + * char[] + * @see #decodeHex(char[]) + */ + @Override + public Object decode(final Object object) throws DecoderException { + try { + final char[] charArray = object instanceof String ? ((String) object).toCharArray() : (char[]) object; + return decodeHex(charArray); + } catch (final ClassCastException e) { + throw new DecoderException(e.getMessage(), e); + } + } + + /** + * Converts an array of bytes into an array of bytes for the characters representing the hexadecimal values of each + * byte in order. The returned array will be double the length of the passed array, as it takes two characters to + * represent any given byte. + *

+ * The conversion from hexadecimal characters to the returned bytes is performed with the charset named by + * {@link #getCharset()}. + *

+ * + * @param array + * a byte[] to convert to Hex characters + * @return A byte[] containing the bytes of the hexadecimal characters + * @since 1.7 No longer throws IllegalStateException if the charsetName is invalid. + * @see #encodeHex(byte[]) + */ + @Override + public byte[] encode(final byte[] array) { + return encodeHexString(array).getBytes(this.getCharset()); + } + + /** + * Converts a String or an array of bytes into an array of characters representing the hexadecimal values of each + * byte in order. The returned array will be double the length of the passed String or array, as it takes two + * characters to represent any given byte. + *

+ * The conversion from hexadecimal characters to bytes to be encoded to performed with the charset named by + * {@link #getCharset()}. + *

+ * + * @param object + * a String, or byte[] to convert to Hex characters + * @return A char[] containing hexadecimal characters + * @throws EncoderException + * Thrown if the given object is not a String or byte[] + * @see #encodeHex(byte[]) + */ + @Override + public Object encode(final Object object) throws EncoderException { + try { + final byte[] byteArray = object instanceof String ? + ((String) object).getBytes(this.getCharset()) : (byte[]) object; + return encodeHex(byteArray); + } catch (final ClassCastException e) { + throw new EncoderException(e.getMessage(), e); + } + } + + /** + * Gets the charset. + * + * @return the charset. + * @since 1.7 + */ + public Charset getCharset() { + return this.charset; + } + + /** + * Gets the charset name. + * + * @return the charset name. + * @since 1.4 + */ + public String getCharsetName() { + return this.charset.name(); + } + + /** + * Returns a string representation of the object, which includes the charset name. + * + * @return a string representation of the object. + */ + @Override + public String toString() { + return super.toString() + "[charsetName=" + this.charset + "]"; + } +} diff --git a/src/org/apache/commons/codec/binary/StringUtils.java b/src/org/apache/commons/codec/binary/StringUtils.java new file mode 100644 index 00000000..1954103b --- /dev/null +++ b/src/org/apache/commons/codec/binary/StringUtils.java @@ -0,0 +1,386 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.binary; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; + +import org.apache.commons.codec.CharEncoding; +import org.apache.commons.codec.Charsets; + +/** + * Converts String to and from bytes using the encodings required by the Java specification. These encodings are + * specified in + * Standard charsets. + * + *

This class is immutable and thread-safe.

+ * + * @see CharEncoding + * @see Standard charsets + * @version $Id: StringUtils.java 1634456 2014-10-27 05:26:56Z ggregory $ + * @since 1.4 + */ +public class StringUtils { + + /** + *

+ * Compares two CharSequences, returning true if they represent equal sequences of characters. + *

+ * + *

+ * nulls are handled without exceptions. Two null references are considered to be equal. + * The comparison is case sensitive. + *

+ * + *
+     * StringUtils.equals(null, null)   = true
+     * StringUtils.equals(null, "abc")  = false
+     * StringUtils.equals("abc", null)  = false
+     * StringUtils.equals("abc", "abc") = true
+     * StringUtils.equals("abc", "ABC") = false
+     * 
+ * + *

+ * Copied from Apache Commons Lang r1583482 on April 10, 2014 (day of 3.3.2 release). + *

+ * + * @see Object#equals(Object) + * @param cs1 + * the first CharSequence, may be null + * @param cs2 + * the second CharSequence, may be null + * @return true if the CharSequences are equal (case-sensitive), or both null + * @since 1.10 + */ + public static boolean equals(final CharSequence cs1, final CharSequence cs2) { + if (cs1 == cs2) { + return true; + } + if (cs1 == null || cs2 == null) { + return false; + } + if (cs1 instanceof String && cs2 instanceof String) { + return cs1.equals(cs2); + } + return CharSequenceUtils.regionMatches(cs1, false, 0, cs2, 0, Math.max(cs1.length(), cs2.length())); + } + + /** + * Calls {@link String#getBytes(Charset)} + * + * @param string + * The string to encode (if null, return null). + * @param charset + * The {@link Charset} to encode the String + * @return the encoded bytes + */ + private static byte[] getBytes(final String string, final Charset charset) { + if (string == null) { + return null; + } + return string.getBytes(charset); + } + + /** + * Encodes the given string into a sequence of bytes using the ISO-8859-1 charset, storing the result into a new + * byte array. + * + * @param string + * the String to encode, may be null + * @return encoded bytes, or null if the input string was null + * @throws NullPointerException + * Thrown if {@link Charsets#ISO_8859_1} is not initialized, which should never happen since it is + * required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + * @see Standard charsets + * @see #getBytesUnchecked(String, String) + */ + public static byte[] getBytesIso8859_1(final String string) { + return getBytes(string, Charsets.ISO_8859_1); + } + + + /** + * Encodes the given string into a sequence of bytes using the named charset, storing the result into a new byte + * array. + *

+ * This method catches {@link UnsupportedEncodingException} and rethrows it as {@link IllegalStateException}, which + * should never happen for a required charset name. Use this method when the encoding is required to be in the JRE. + *

+ * + * @param string + * the String to encode, may be null + * @param charsetName + * The name of a required {@link java.nio.charset.Charset} + * @return encoded bytes, or null if the input string was null + * @throws IllegalStateException + * Thrown when a {@link UnsupportedEncodingException} is caught, which should never happen for a + * required charset name. + * @see CharEncoding + * @see String#getBytes(String) + */ + public static byte[] getBytesUnchecked(final String string, final String charsetName) { + if (string == null) { + return null; + } + try { + return string.getBytes(charsetName); + } catch (final UnsupportedEncodingException e) { + throw StringUtils.newIllegalStateException(charsetName, e); + } + } + + /** + * Encodes the given string into a sequence of bytes using the US-ASCII charset, storing the result into a new byte + * array. + * + * @param string + * the String to encode, may be null + * @return encoded bytes, or null if the input string was null + * @throws NullPointerException + * Thrown if {@link Charsets#US_ASCII} is not initialized, which should never happen since it is + * required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + * @see Standard charsets + * @see #getBytesUnchecked(String, String) + */ + public static byte[] getBytesUsAscii(final String string) { + return getBytes(string, Charsets.US_ASCII); + } + + /** + * Encodes the given string into a sequence of bytes using the UTF-16 charset, storing the result into a new byte + * array. + * + * @param string + * the String to encode, may be null + * @return encoded bytes, or null if the input string was null + * @throws NullPointerException + * Thrown if {@link Charsets#UTF_16} is not initialized, which should never happen since it is + * required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + * @see Standard charsets + * @see #getBytesUnchecked(String, String) + */ + public static byte[] getBytesUtf16(final String string) { + return getBytes(string, Charsets.UTF_16); + } + + /** + * Encodes the given string into a sequence of bytes using the UTF-16BE charset, storing the result into a new byte + * array. + * + * @param string + * the String to encode, may be null + * @return encoded bytes, or null if the input string was null + * @throws NullPointerException + * Thrown if {@link Charsets#UTF_16BE} is not initialized, which should never happen since it is + * required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + * @see Standard charsets + * @see #getBytesUnchecked(String, String) + */ + public static byte[] getBytesUtf16Be(final String string) { + return getBytes(string, Charsets.UTF_16BE); + } + + /** + * Encodes the given string into a sequence of bytes using the UTF-16LE charset, storing the result into a new byte + * array. + * + * @param string + * the String to encode, may be null + * @return encoded bytes, or null if the input string was null + * @throws NullPointerException + * Thrown if {@link Charsets#UTF_16LE} is not initialized, which should never happen since it is + * required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + * @see Standard charsets + * @see #getBytesUnchecked(String, String) + */ + public static byte[] getBytesUtf16Le(final String string) { + return getBytes(string, Charsets.UTF_16LE); + } + + /** + * Encodes the given string into a sequence of bytes using the UTF-8 charset, storing the result into a new byte + * array. + * + * @param string + * the String to encode, may be null + * @return encoded bytes, or null if the input string was null + * @throws NullPointerException + * Thrown if {@link Charsets#UTF_8} is not initialized, which should never happen since it is + * required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + * @see Standard charsets + * @see #getBytesUnchecked(String, String) + */ + public static byte[] getBytesUtf8(final String string) { + return getBytes(string, Charsets.UTF_8); + } + + private static IllegalStateException newIllegalStateException(final String charsetName, + final UnsupportedEncodingException e) { + return new IllegalStateException(charsetName + ": " + e); + } + + /** + * Constructs a new String by decoding the specified array of bytes using the given charset. + * + * @param bytes + * The bytes to be decoded into characters + * @param charset + * The {@link Charset} to encode the String + * @return A new String decoded from the specified array of bytes using the given charset, + * or null if the input byte array was null. + * @throws NullPointerException + * Thrown if {@link Charsets#UTF_8} is not initialized, which should never happen since it is + * required by the Java platform specification. + */ + private static String newString(final byte[] bytes, final Charset charset) { + return bytes == null ? null : new String(bytes, charset); + } + + /** + * Constructs a new String by decoding the specified array of bytes using the given charset. + *

+ * This method catches {@link UnsupportedEncodingException} and re-throws it as {@link IllegalStateException}, which + * should never happen for a required charset name. Use this method when the encoding is required to be in the JRE. + *

+ * + * @param bytes + * The bytes to be decoded into characters, may be null + * @param charsetName + * The name of a required {@link java.nio.charset.Charset} + * @return A new String decoded from the specified array of bytes using the given charset, + * or null if the input byte array was null. + * @throws IllegalStateException + * Thrown when a {@link UnsupportedEncodingException} is caught, which should never happen for a + * required charset name. + * @see CharEncoding + * @see String#String(byte[], String) + */ + public static String newString(final byte[] bytes, final String charsetName) { + if (bytes == null) { + return null; + } + try { + return new String(bytes, charsetName); + } catch (final UnsupportedEncodingException e) { + throw StringUtils.newIllegalStateException(charsetName, e); + } + } + + /** + * Constructs a new String by decoding the specified array of bytes using the ISO-8859-1 charset. + * + * @param bytes + * The bytes to be decoded into characters, may be null + * @return A new String decoded from the specified array of bytes using the ISO-8859-1 charset, or + * null if the input byte array was null. + * @throws NullPointerException + * Thrown if {@link Charsets#ISO_8859_1} is not initialized, which should never happen since it is + * required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + */ + public static String newStringIso8859_1(final byte[] bytes) { + return new String(bytes, Charsets.ISO_8859_1); + } + + /** + * Constructs a new String by decoding the specified array of bytes using the US-ASCII charset. + * + * @param bytes + * The bytes to be decoded into characters + * @return A new String decoded from the specified array of bytes using the US-ASCII charset, + * or null if the input byte array was null. + * @throws NullPointerException + * Thrown if {@link Charsets#US_ASCII} is not initialized, which should never happen since it is + * required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + */ + public static String newStringUsAscii(final byte[] bytes) { + return new String(bytes, Charsets.US_ASCII); + } + + /** + * Constructs a new String by decoding the specified array of bytes using the UTF-16 charset. + * + * @param bytes + * The bytes to be decoded into characters + * @return A new String decoded from the specified array of bytes using the UTF-16 charset + * or null if the input byte array was null. + * @throws NullPointerException + * Thrown if {@link Charsets#UTF_16} is not initialized, which should never happen since it is + * required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + */ + public static String newStringUtf16(final byte[] bytes) { + return new String(bytes, Charsets.UTF_16); + } + + /** + * Constructs a new String by decoding the specified array of bytes using the UTF-16BE charset. + * + * @param bytes + * The bytes to be decoded into characters + * @return A new String decoded from the specified array of bytes using the UTF-16BE charset, + * or null if the input byte array was null. + * @throws NullPointerException + * Thrown if {@link Charsets#UTF_16BE} is not initialized, which should never happen since it is + * required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + */ + public static String newStringUtf16Be(final byte[] bytes) { + return new String(bytes, Charsets.UTF_16BE); + } + + /** + * Constructs a new String by decoding the specified array of bytes using the UTF-16LE charset. + * + * @param bytes + * The bytes to be decoded into characters + * @return A new String decoded from the specified array of bytes using the UTF-16LE charset, + * or null if the input byte array was null. + * @throws NullPointerException + * Thrown if {@link Charsets#UTF_16LE} is not initialized, which should never happen since it is + * required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + */ + public static String newStringUtf16Le(final byte[] bytes) { + return new String(bytes, Charsets.UTF_16LE); + } + + /** + * Constructs a new String by decoding the specified array of bytes using the UTF-8 charset. + * + * @param bytes + * The bytes to be decoded into characters + * @return A new String decoded from the specified array of bytes using the UTF-8 charset, + * or null if the input byte array was null. + * @throws NullPointerException + * Thrown if {@link Charsets#UTF_8} is not initialized, which should never happen since it is + * required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + */ + public static String newStringUtf8(final byte[] bytes) { + return newString(bytes, Charsets.UTF_8); + } + +} diff --git a/src/org/apache/commons/codec/binary/package.html b/src/org/apache/commons/codec/binary/package.html new file mode 100644 index 00000000..ee97d94d --- /dev/null +++ b/src/org/apache/commons/codec/binary/package.html @@ -0,0 +1,21 @@ + + + + Base64, Base32, Binary, and Hexadecimal String encoding and decoding. + + diff --git a/src/org/apache/commons/codec/digest/B64.java b/src/org/apache/commons/codec/digest/B64.java new file mode 100644 index 00000000..951fc2b7 --- /dev/null +++ b/src/org/apache/commons/codec/digest/B64.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.codec.digest; + +import java.util.Random; + +/** + * Base64 like method to convert binary bytes into ASCII chars. + * + * TODO: Can Base64 be reused? + * + *

+ * This class is immutable and thread-safe. + *

+ * + * @version $Id: B64.java 1435550 2013-01-19 14:09:52Z tn $ + * @since 1.7 + */ +class B64 { + + /** + * Table with characters for Base64 transformation. + */ + static final String B64T = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + /** + * Base64 like conversion of bytes to ASCII chars. + * + * @param b2 + * A byte from the result. + * @param b1 + * A byte from the result. + * @param b0 + * A byte from the result. + * @param outLen + * The number of expected output chars. + * @param buffer + * Where the output chars is appended to. + */ + static void b64from24bit(final byte b2, final byte b1, final byte b0, final int outLen, + final StringBuilder buffer) { + // The bit masking is necessary because the JVM byte type is signed! + int w = ((b2 << 16) & 0x00ffffff) | ((b1 << 8) & 0x00ffff) | (b0 & 0xff); + // It's effectively a "for" loop but kept to resemble the original C code. + int n = outLen; + while (n-- > 0) { + buffer.append(B64T.charAt(w & 0x3f)); + w >>= 6; + } + } + + /** + * Generates a string of random chars from the B64T set. + * + * @param num + * Number of chars to generate. + */ + static String getRandomSalt(final int num) { + final StringBuilder saltString = new StringBuilder(); + for (int i = 1; i <= num; i++) { + saltString.append(B64T.charAt(new Random().nextInt(B64T.length()))); + } + return saltString.toString(); + } +} diff --git a/src/org/apache/commons/codec/digest/Crypt.java b/src/org/apache/commons/codec/digest/Crypt.java new file mode 100644 index 00000000..b7c41b56 --- /dev/null +++ b/src/org/apache/commons/codec/digest/Crypt.java @@ -0,0 +1,151 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.codec.digest; + +import org.apache.commons.codec.Charsets; + +/** + * GNU libc crypt(3) compatible hash method. + *

+ * See {@link #crypt(String, String)} for further details. + *

+ * This class is immutable and thread-safe. + * + * @version $Id: Crypt.java 1635205 2014-10-29 17:14:16Z ggregory $ + * @since 1.7 + */ +public class Crypt { + + /** + * Encrypts a password in a crypt(3) compatible way. + *

+ * A random salt and the default algorithm (currently SHA-512) are used. See {@link #crypt(String, String)} for + * details. + * + * @param keyBytes + * plaintext password + * @return hash value + * @throws RuntimeException + * when a {@link java.security.NoSuchAlgorithmException} is caught. + */ + public static String crypt(final byte[] keyBytes) { + return crypt(keyBytes, null); + } + + /** + * Encrypts a password in a crypt(3) compatible way. + *

+ * If no salt is provided, a random salt and the default algorithm (currently SHA-512) will be used. See + * {@link #crypt(String, String)} for details. + * + * @param keyBytes + * plaintext password + * @param salt + * salt value + * @return hash value + * @throws IllegalArgumentException + * if the salt does not match the allowed pattern + * @throws RuntimeException + * when a {@link java.security.NoSuchAlgorithmException} is caught. + */ + public static String crypt(final byte[] keyBytes, final String salt) { + if (salt == null) { + return Sha2Crypt.sha512Crypt(keyBytes); + } else if (salt.startsWith(Sha2Crypt.SHA512_PREFIX)) { + return Sha2Crypt.sha512Crypt(keyBytes, salt); + } else if (salt.startsWith(Sha2Crypt.SHA256_PREFIX)) { + return Sha2Crypt.sha256Crypt(keyBytes, salt); + } else if (salt.startsWith(Md5Crypt.MD5_PREFIX)) { + return Md5Crypt.md5Crypt(keyBytes, salt); + } else { + return UnixCrypt.crypt(keyBytes, salt); + } + } + + /** + * Calculates the digest using the strongest crypt(3) algorithm. + *

+ * A random salt and the default algorithm (currently SHA-512) are used. + * + * @see #crypt(String, String) + * @param key + * plaintext password + * @return hash value + * @throws RuntimeException + * when a {@link java.security.NoSuchAlgorithmException} is caught. + */ + public static String crypt(final String key) { + return crypt(key, null); + } + + /** + * Encrypts a password in a crypt(3) compatible way. + *

+ * The exact algorithm depends on the format of the salt string: + *

    + *
  • SHA-512 salts start with {@code $6$} and are up to 16 chars long. + *
  • SHA-256 salts start with {@code $5$} and are up to 16 chars long + *
  • MD5 salts start with {@code $1$} and are up to 8 chars long + *
  • DES, the traditional UnixCrypt algorithm is used with only 2 chars + *
  • Only the first 8 chars of the passwords are used in the DES algorithm! + *
+ * The magic strings {@code "$apr1$"} and {@code "$2a$"} are not recognized by this method as its output should be + * identical with that of the libc implementation. + *

+ * The rest of the salt string is drawn from the set {@code [a-zA-Z0-9./]} and is cut at the maximum length of if a + * {@code "$"} sign is encountered. It is therefore valid to enter a complete hash value as salt to e.g. verify a + * password with: + * + *

+     * storedPwd.equals(crypt(enteredPwd, storedPwd))
+     * 
+ *

+ * The resulting string starts with the marker string ({@code $6$}), continues with the salt value and ends with a + * {@code "$"} sign followed by the actual hash value. For DES the string only contains the salt and actual hash. + * It's total length is dependent on the algorithm used: + *

    + *
  • SHA-512: 106 chars + *
  • SHA-256: 63 chars + *
  • MD5: 34 chars + *
  • DES: 13 chars + *
+ *

+ * Example: + * + *

+     *      crypt("secret", "$1$xxxx") => "$1$xxxx$aMkevjfEIpa35Bh3G4bAc."
+     *      crypt("secret", "xx") => "xxWAum7tHdIUw"
+     * 
+ *

+ * This method comes in a variation that accepts a byte[] array to support input strings that are not encoded in + * UTF-8 but e.g. in ISO-8859-1 where equal characters result in different byte values. + * + * @see "The man page of the libc crypt (3) function." + * @param key + * plaintext password as entered by the used + * @param salt + * salt value + * @return hash value, i.e. encrypted password including the salt string + * @throws IllegalArgumentException + * if the salt does not match the allowed pattern + * @throws RuntimeException + * when a {@link java.security.NoSuchAlgorithmException} is caught. * + */ + public static String crypt(final String key, final String salt) { + return crypt(key.getBytes(Charsets.UTF_8), salt); + } +} diff --git a/src/org/apache/commons/codec/digest/DigestUtils.java b/src/org/apache/commons/codec/digest/DigestUtils.java new file mode 100644 index 00000000..e1d9e2ac --- /dev/null +++ b/src/org/apache/commons/codec/digest/DigestUtils.java @@ -0,0 +1,819 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.digest; + +import java.io.IOException; +import java.io.InputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.codec.binary.StringUtils; + +/** + * Operations to simplify common {@link java.security.MessageDigest} tasks. + * This class is immutable and thread-safe. + * + * @version $Id: DigestUtils.java 1634433 2014-10-27 01:10:47Z ggregory $ + */ +public class DigestUtils { + + private static final int STREAM_BUFFER_LENGTH = 1024; + + /** + * Read through an InputStream and returns the digest for the data + * + * @param digest + * The MessageDigest to use (e.g. MD5) + * @param data + * Data to digest + * @return the digest + * @throws IOException + * On error reading from the stream + */ + private static byte[] digest(final MessageDigest digest, final InputStream data) throws IOException { + return updateDigest(digest, data).digest(); + } + + /** + * Returns a MessageDigest for the given algorithm. + * + * @param algorithm + * the name of the algorithm requested. See Appendix A in the Java Cryptography Architecture Reference Guide for information about standard + * algorithm names. + * @return A digest instance. + * @see MessageDigest#getInstance(String) + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught. + */ + public static MessageDigest getDigest(final String algorithm) { + try { + return MessageDigest.getInstance(algorithm); + } catch (final NoSuchAlgorithmException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Returns an MD2 MessageDigest. + * + * @return An MD2 digest instance. + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught, which should never happen because MD2 is a + * built-in algorithm + * @see MessageDigestAlgorithms#MD2 + * @since 1.7 + */ + public static MessageDigest getMd2Digest() { + return getDigest(MessageDigestAlgorithms.MD2); + } + + /** + * Returns an MD5 MessageDigest. + * + * @return An MD5 digest instance. + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught, which should never happen because MD5 is a + * built-in algorithm + * @see MessageDigestAlgorithms#MD5 + */ + public static MessageDigest getMd5Digest() { + return getDigest(MessageDigestAlgorithms.MD5); + } + + /** + * Returns an SHA-1 digest. + * + * @return An SHA-1 digest instance. + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught, which should never happen because SHA-1 is a + * built-in algorithm + * @see MessageDigestAlgorithms#SHA_1 + * @since 1.7 + */ + public static MessageDigest getSha1Digest() { + return getDigest(MessageDigestAlgorithms.SHA_1); + } + + /** + * Returns an SHA-256 digest. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @return An SHA-256 digest instance. + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught, which should never happen because SHA-256 is a + * built-in algorithm + * @see MessageDigestAlgorithms#SHA_256 + */ + public static MessageDigest getSha256Digest() { + return getDigest(MessageDigestAlgorithms.SHA_256); + } + + /** + * Returns an SHA-384 digest. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @return An SHA-384 digest instance. + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught, which should never happen because SHA-384 is a + * built-in algorithm + * @see MessageDigestAlgorithms#SHA_384 + */ + public static MessageDigest getSha384Digest() { + return getDigest(MessageDigestAlgorithms.SHA_384); + } + + /** + * Returns an SHA-512 digest. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @return An SHA-512 digest instance. + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught, which should never happen because SHA-512 is a + * built-in algorithm + * @see MessageDigestAlgorithms#SHA_512 + */ + public static MessageDigest getSha512Digest() { + return getDigest(MessageDigestAlgorithms.SHA_512); + } + + /** + * Returns an SHA-1 digest. + * + * @return An SHA-1 digest instance. + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught + * @deprecated Use {@link #getSha1Digest()} + */ + @Deprecated + public static MessageDigest getShaDigest() { + return getSha1Digest(); + } + + /** + * Calculates the MD2 digest and returns the value as a 16 element byte[]. + * + * @param data + * Data to digest + * @return MD2 digest + * @since 1.7 + */ + public static byte[] md2(final byte[] data) { + return getMd2Digest().digest(data); + } + + /** + * Calculates the MD2 digest and returns the value as a 16 element byte[]. + * + * @param data + * Data to digest + * @return MD2 digest + * @throws IOException + * On error reading from the stream + * @since 1.7 + */ + public static byte[] md2(final InputStream data) throws IOException { + return digest(getMd2Digest(), data); + } + + /** + * Calculates the MD2 digest and returns the value as a 16 element byte[]. + * + * @param data + * Data to digest; converted to bytes using {@link StringUtils#getBytesUtf8(String)} + * @return MD2 digest + * @since 1.7 + */ + public static byte[] md2(final String data) { + return md2(StringUtils.getBytesUtf8(data)); + } + + /** + * Calculates the MD2 digest and returns the value as a 32 character hex string. + * + * @param data + * Data to digest + * @return MD2 digest as a hex string + * @since 1.7 + */ + public static String md2Hex(final byte[] data) { + return Hex.encodeHexString(md2(data)); + } + + /** + * Calculates the MD2 digest and returns the value as a 32 character hex string. + * + * @param data + * Data to digest + * @return MD2 digest as a hex string + * @throws IOException + * On error reading from the stream + * @since 1.7 + */ + public static String md2Hex(final InputStream data) throws IOException { + return Hex.encodeHexString(md2(data)); + } + + /** + * Calculates the MD2 digest and returns the value as a 32 character hex string. + * + * @param data + * Data to digest + * @return MD2 digest as a hex string + * @since 1.7 + */ + public static String md2Hex(final String data) { + return Hex.encodeHexString(md2(data)); + } + + /** + * Calculates the MD5 digest and returns the value as a 16 element byte[]. + * + * @param data + * Data to digest + * @return MD5 digest + */ + public static byte[] md5(final byte[] data) { + return getMd5Digest().digest(data); + } + + /** + * Calculates the MD5 digest and returns the value as a 16 element byte[]. + * + * @param data + * Data to digest + * @return MD5 digest + * @throws IOException + * On error reading from the stream + * @since 1.4 + */ + public static byte[] md5(final InputStream data) throws IOException { + return digest(getMd5Digest(), data); + } + + /** + * Calculates the MD5 digest and returns the value as a 16 element byte[]. + * + * @param data + * Data to digest; converted to bytes using {@link StringUtils#getBytesUtf8(String)} + * @return MD5 digest + */ + public static byte[] md5(final String data) { + return md5(StringUtils.getBytesUtf8(data)); + } + + /** + * Calculates the MD5 digest and returns the value as a 32 character hex string. + * + * @param data + * Data to digest + * @return MD5 digest as a hex string + */ + public static String md5Hex(final byte[] data) { + return Hex.encodeHexString(md5(data)); + } + + /** + * Calculates the MD5 digest and returns the value as a 32 character hex string. + * + * @param data + * Data to digest + * @return MD5 digest as a hex string + * @throws IOException + * On error reading from the stream + * @since 1.4 + */ + public static String md5Hex(final InputStream data) throws IOException { + return Hex.encodeHexString(md5(data)); + } + + /** + * Calculates the MD5 digest and returns the value as a 32 character hex string. + * + * @param data + * Data to digest + * @return MD5 digest as a hex string + */ + public static String md5Hex(final String data) { + return Hex.encodeHexString(md5(data)); + } + + /** + * Calculates the SHA-1 digest and returns the value as a byte[]. + * + * @param data + * Data to digest + * @return SHA-1 digest + * @deprecated Use {@link #sha1(byte[])} + */ + @Deprecated + public static byte[] sha(final byte[] data) { + return sha1(data); + } + + /** + * Calculates the SHA-1 digest and returns the value as a byte[]. + * + * @param data + * Data to digest + * @return SHA-1 digest + * @throws IOException + * On error reading from the stream + * @since 1.4 + * @deprecated Use {@link #sha1(InputStream)} + */ + @Deprecated + public static byte[] sha(final InputStream data) throws IOException { + return sha1(data); + } + + /** + * Calculates the SHA-1 digest and returns the value as a byte[]. + * + * @param data + * Data to digest + * @return SHA-1 digest + * @deprecated Use {@link #sha1(String)} + */ + @Deprecated + public static byte[] sha(final String data) { + return sha1(data); + } + + /** + * Calculates the SHA-1 digest and returns the value as a byte[]. + * + * @param data + * Data to digest + * @return SHA-1 digest + * @since 1.7 + */ + public static byte[] sha1(final byte[] data) { + return getSha1Digest().digest(data); + } + + /** + * Calculates the SHA-1 digest and returns the value as a byte[]. + * + * @param data + * Data to digest + * @return SHA-1 digest + * @throws IOException + * On error reading from the stream + * @since 1.7 + */ + public static byte[] sha1(final InputStream data) throws IOException { + return digest(getSha1Digest(), data); + } + + /** + * Calculates the SHA-1 digest and returns the value as a byte[]. + * + * @param data + * Data to digest; converted to bytes using {@link StringUtils#getBytesUtf8(String)} + * @return SHA-1 digest + */ + public static byte[] sha1(final String data) { + return sha1(StringUtils.getBytesUtf8(data)); + } + + /** + * Calculates the SHA-1 digest and returns the value as a hex string. + * + * @param data + * Data to digest + * @return SHA-1 digest as a hex string + * @since 1.7 + */ + public static String sha1Hex(final byte[] data) { + return Hex.encodeHexString(sha1(data)); + } + + /** + * Calculates the SHA-1 digest and returns the value as a hex string. + * + * @param data + * Data to digest + * @return SHA-1 digest as a hex string + * @throws IOException + * On error reading from the stream + * @since 1.7 + */ + public static String sha1Hex(final InputStream data) throws IOException { + return Hex.encodeHexString(sha1(data)); + } + + /** + * Calculates the SHA-1 digest and returns the value as a hex string. + * + * @param data + * Data to digest + * @return SHA-1 digest as a hex string + * @since 1.7 + */ + public static String sha1Hex(final String data) { + return Hex.encodeHexString(sha1(data)); + } + + /** + * Calculates the SHA-256 digest and returns the value as a byte[]. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-256 digest + * @since 1.4 + */ + public static byte[] sha256(final byte[] data) { + return getSha256Digest().digest(data); + } + + /** + * Calculates the SHA-256 digest and returns the value as a byte[]. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-256 digest + * @throws IOException + * On error reading from the stream + * @since 1.4 + */ + public static byte[] sha256(final InputStream data) throws IOException { + return digest(getSha256Digest(), data); + } + + /** + * Calculates the SHA-256 digest and returns the value as a byte[]. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest; converted to bytes using {@link StringUtils#getBytesUtf8(String)} + * @return SHA-256 digest + * @since 1.4 + */ + public static byte[] sha256(final String data) { + return sha256(StringUtils.getBytesUtf8(data)); + } + + /** + * Calculates the SHA-256 digest and returns the value as a hex string. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-256 digest as a hex string + * @since 1.4 + */ + public static String sha256Hex(final byte[] data) { + return Hex.encodeHexString(sha256(data)); + } + + /** + * Calculates the SHA-256 digest and returns the value as a hex string. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-256 digest as a hex string + * @throws IOException + * On error reading from the stream + * @since 1.4 + */ + public static String sha256Hex(final InputStream data) throws IOException { + return Hex.encodeHexString(sha256(data)); + } + + /** + * Calculates the SHA-256 digest and returns the value as a hex string. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-256 digest as a hex string + * @since 1.4 + */ + public static String sha256Hex(final String data) { + return Hex.encodeHexString(sha256(data)); + } + + /** + * Calculates the SHA-384 digest and returns the value as a byte[]. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-384 digest + * @since 1.4 + */ + public static byte[] sha384(final byte[] data) { + return getSha384Digest().digest(data); + } + + /** + * Calculates the SHA-384 digest and returns the value as a byte[]. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-384 digest + * @throws IOException + * On error reading from the stream + * @since 1.4 + */ + public static byte[] sha384(final InputStream data) throws IOException { + return digest(getSha384Digest(), data); + } + + /** + * Calculates the SHA-384 digest and returns the value as a byte[]. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest; converted to bytes using {@link StringUtils#getBytesUtf8(String)} + * @return SHA-384 digest + * @since 1.4 + */ + public static byte[] sha384(final String data) { + return sha384(StringUtils.getBytesUtf8(data)); + } + + /** + * Calculates the SHA-384 digest and returns the value as a hex string. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-384 digest as a hex string + * @since 1.4 + */ + public static String sha384Hex(final byte[] data) { + return Hex.encodeHexString(sha384(data)); + } + + /** + * Calculates the SHA-384 digest and returns the value as a hex string. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-384 digest as a hex string + * @throws IOException + * On error reading from the stream + * @since 1.4 + */ + public static String sha384Hex(final InputStream data) throws IOException { + return Hex.encodeHexString(sha384(data)); + } + + /** + * Calculates the SHA-384 digest and returns the value as a hex string. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-384 digest as a hex string + * @since 1.4 + */ + public static String sha384Hex(final String data) { + return Hex.encodeHexString(sha384(data)); + } + + /** + * Calculates the SHA-512 digest and returns the value as a byte[]. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-512 digest + * @since 1.4 + */ + public static byte[] sha512(final byte[] data) { + return getSha512Digest().digest(data); + } + + /** + * Calculates the SHA-512 digest and returns the value as a byte[]. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-512 digest + * @throws IOException + * On error reading from the stream + * @since 1.4 + */ + public static byte[] sha512(final InputStream data) throws IOException { + return digest(getSha512Digest(), data); + } + + /** + * Calculates the SHA-512 digest and returns the value as a byte[]. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest; converted to bytes using {@link StringUtils#getBytesUtf8(String)} + * @return SHA-512 digest + * @since 1.4 + */ + public static byte[] sha512(final String data) { + return sha512(StringUtils.getBytesUtf8(data)); + } + + /** + * Calculates the SHA-512 digest and returns the value as a hex string. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-512 digest as a hex string + * @since 1.4 + */ + public static String sha512Hex(final byte[] data) { + return Hex.encodeHexString(sha512(data)); + } + + /** + * Calculates the SHA-512 digest and returns the value as a hex string. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-512 digest as a hex string + * @throws IOException + * On error reading from the stream + * @since 1.4 + */ + public static String sha512Hex(final InputStream data) throws IOException { + return Hex.encodeHexString(sha512(data)); + } + + /** + * Calculates the SHA-512 digest and returns the value as a hex string. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-512 digest as a hex string + * @since 1.4 + */ + public static String sha512Hex(final String data) { + return Hex.encodeHexString(sha512(data)); + } + + /** + * Calculates the SHA-1 digest and returns the value as a hex string. + * + * @param data + * Data to digest + * @return SHA-1 digest as a hex string + * @deprecated Use {@link #sha1Hex(byte[])} + */ + @Deprecated + public static String shaHex(final byte[] data) { + return sha1Hex(data); + } + + /** + * Calculates the SHA-1 digest and returns the value as a hex string. + * + * @param data + * Data to digest + * @return SHA-1 digest as a hex string + * @throws IOException + * On error reading from the stream + * @since 1.4 + * @deprecated Use {@link #sha1Hex(InputStream)} + */ + @Deprecated + public static String shaHex(final InputStream data) throws IOException { + return sha1Hex(data); + } + + /** + * Calculates the SHA-1 digest and returns the value as a hex string. + * + * @param data + * Data to digest + * @return SHA-1 digest as a hex string + * @deprecated Use {@link #sha1Hex(String)} + */ + @Deprecated + public static String shaHex(final String data) { + return sha1Hex(data); + } + + /** + * Updates the given {@link MessageDigest}. + * + * @param messageDigest + * the {@link MessageDigest} to update + * @param valueToDigest + * the value to update the {@link MessageDigest} with + * @return the updated {@link MessageDigest} + * @since 1.7 + */ + public static MessageDigest updateDigest(final MessageDigest messageDigest, final byte[] valueToDigest) { + messageDigest.update(valueToDigest); + return messageDigest; + } + + /** + * Reads through an InputStream and updates the digest for the data + * + * @param digest + * The MessageDigest to use (e.g. MD5) + * @param data + * Data to digest + * @return the digest + * @throws IOException + * On error reading from the stream + * @since 1.8 + */ + public static MessageDigest updateDigest(final MessageDigest digest, final InputStream data) throws IOException { + final byte[] buffer = new byte[STREAM_BUFFER_LENGTH]; + int read = data.read(buffer, 0, STREAM_BUFFER_LENGTH); + + while (read > -1) { + digest.update(buffer, 0, read); + read = data.read(buffer, 0, STREAM_BUFFER_LENGTH); + } + + return digest; + } + + /** + * Updates the given {@link MessageDigest}. + * + * @param messageDigest + * the {@link MessageDigest} to update + * @param valueToDigest + * the value to update the {@link MessageDigest} with; + * converted to bytes using {@link StringUtils#getBytesUtf8(String)} + * @return the updated {@link MessageDigest} + * @since 1.7 + */ + public static MessageDigest updateDigest(final MessageDigest messageDigest, final String valueToDigest) { + messageDigest.update(StringUtils.getBytesUtf8(valueToDigest)); + return messageDigest; + } +} diff --git a/src/org/apache/commons/codec/digest/HmacAlgorithms.java b/src/org/apache/commons/codec/digest/HmacAlgorithms.java new file mode 100644 index 00000000..fb84a3f9 --- /dev/null +++ b/src/org/apache/commons/codec/digest/HmacAlgorithms.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.digest; + +/** + * Standard {@link HmacUtils} algorithm names from the Java Cryptography Architecture Standard Algorithm Name + * Documentation. + * + *

+ * Note: Not all JCE implementations supports all algorithms in this enum. + *

+ * + * @see Java Cryptography + * Architecture Standard Algorithm Name Documentation + * @since 1.10 + * @version $Id: HmacAlgorithms.java 1634405 2014-10-26 23:07:26Z ggregory $ + */ +public enum HmacAlgorithms { + + /** + * The HmacMD5 Message Authentication Code (MAC) algorithm specified in RFC 2104 and RFC 1321. + *

+ * Every implementation of the Java platform is required to support this standard Mac algorithm. + *

+ */ + HMAC_MD5("HmacMD5"), + + /** + * The HmacSHA1 Message Authentication Code (MAC) algorithm specified in RFC 2104 and FIPS PUB 180-2. + *

+ * Every implementation of the Java platform is required to support this standard Mac algorithm. + *

+ */ + HMAC_SHA_1("HmacSHA1"), + + /** + * The HmacSHA256 Message Authentication Code (MAC) algorithm specified in RFC 2104 and FIPS PUB 180-2. + *

+ * Every implementation of the Java platform is required to support this standard Mac algorithm. + *

+ */ + HMAC_SHA_256("HmacSHA256"), + + /** + * The HmacSHA384 Message Authentication Code (MAC) algorithm specified in RFC 2104 and FIPS PUB 180-2. + *

+ * Every implementation of the Java platform is not required to support this Mac algorithm. + *

+ */ + HMAC_SHA_384("HmacSHA384"), + + /** + * The HmacSHA512 Message Authentication Code (MAC) algorithm specified in RFC 2104 and FIPS PUB 180-2. + *

+ * Every implementation of the Java platform is not required to support this Mac algorithm. + *

+ */ + HMAC_SHA_512("HmacSHA512"); + + private final String algorithm; + + private HmacAlgorithms(final String algorithm) { + this.algorithm = algorithm; + } + + /** + * The algorithm name + * + * @see Java + * Cryptography Architecture Sun Providers Documentation + * @return The algorithm name ("HmacSHA512" for example) + */ + @Override + public String toString() { + return algorithm; + } + +} diff --git a/src/org/apache/commons/codec/digest/HmacUtils.java b/src/org/apache/commons/codec/digest/HmacUtils.java new file mode 100644 index 00000000..5cd1e514 --- /dev/null +++ b/src/org/apache/commons/codec/digest/HmacUtils.java @@ -0,0 +1,794 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.digest; + +import java.io.IOException; +import java.io.InputStream; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.codec.binary.StringUtils; + +/** + * Simplifies common {@link javax.crypto.Mac} tasks. This class is immutable and thread-safe. + * + * + *

+ * Note: Not all JCE implementations supports all algorithms. If not supported, an IllegalArgumentException is + * thrown. + *

+ * + * @since 1.10 + * @version $Id: HmacUtils.java 1634411 2014-10-27 00:35:43Z ggregory $ + */ +public final class HmacUtils { + + private static final int STREAM_BUFFER_LENGTH = 1024; + + /** + * Returns an initialized Mac for the HmacMD5 algorithm. + *

+ * Every implementation of the Java platform is required to support this standard Mac algorithm. + *

+ * + * @param key + * They key for the keyed digest (must not be null) + * @return A Mac instance initialized with the given key. + * @see Mac#getInstance(String) + * @see Mac#init(Key) + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static Mac getHmacMd5(final byte[] key) { + return getInitializedMac(HmacAlgorithms.HMAC_MD5, key); + } + + /** + * Returns an initialized Mac for the HmacSHA1 algorithm. + *

+ * Every implementation of the Java platform is required to support this standard Mac algorithm. + *

+ * + * @param key + * They key for the keyed digest (must not be null) + * @return A Mac instance initialized with the given key. + * @see Mac#getInstance(String) + * @see Mac#init(Key) + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static Mac getHmacSha1(final byte[] key) { + return getInitializedMac(HmacAlgorithms.HMAC_SHA_1, key); + } + + /** + * Returns an initialized Mac for the HmacSHA256 algorithm. + *

+ * Every implementation of the Java platform is required to support this standard Mac algorithm. + *

+ * + * @param key + * They key for the keyed digest (must not be null) + * @return A Mac instance initialized with the given key. + * @see Mac#getInstance(String) + * @see Mac#init(Key) + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static Mac getHmacSha256(final byte[] key) { + return getInitializedMac(HmacAlgorithms.HMAC_SHA_256, key); + } + + /** + * Returns an initialized Mac for the HmacSHA384 algorithm. + *

+ * Every implementation of the Java platform is not required to support this Mac algorithm. + *

+ * + * @param key + * They key for the keyed digest (must not be null) + * @return A Mac instance initialized with the given key. + * @see Mac#getInstance(String) + * @see Mac#init(Key) + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static Mac getHmacSha384(final byte[] key) { + return getInitializedMac(HmacAlgorithms.HMAC_SHA_384, key); + } + + /** + * Returns an initialized Mac for the HmacSHA512 algorithm. + *

+ * Every implementation of the Java platform is not required to support this Mac algorithm. + *

+ * + * @param key + * They key for the keyed digest (must not be null) + * @return A Mac instance initialized with the given key. + * @see Mac#getInstance(String) + * @see Mac#init(Key) + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static Mac getHmacSha512(final byte[] key) { + return getInitializedMac(HmacAlgorithms.HMAC_SHA_512, key); + } + + /** + * Returns an initialized Mac for the given algorithm. + * + * @param algorithm + * the name of the algorithm requested. See Appendix + * A in the Java Cryptography Architecture Reference Guide for information about standard algorithm + * names. + * @param key + * They key for the keyed digest (must not be null) + * @return A Mac instance initialized with the given key. + * @see Mac#getInstance(String) + * @see Mac#init(Key) + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static Mac getInitializedMac(final HmacAlgorithms algorithm, final byte[] key) { + return getInitializedMac(algorithm.toString(), key); + } + + /** + * Returns an initialized Mac for the given algorithm. + * + * @param algorithm + * the name of the algorithm requested. See Appendix + * A in the Java Cryptography Architecture Reference Guide for information about standard algorithm + * names. + * @param key + * They key for the keyed digest (must not be null) + * @return A Mac instance initialized with the given key. + * @see Mac#getInstance(String) + * @see Mac#init(Key) + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static Mac getInitializedMac(final String algorithm, final byte[] key) { + + if (key == null) { + throw new IllegalArgumentException("Null key"); + } + + try { + final SecretKeySpec keySpec = new SecretKeySpec(key, algorithm); + final Mac mac = Mac.getInstance(algorithm); + mac.init(keySpec); + return mac; + } catch (final NoSuchAlgorithmException e) { + throw new IllegalArgumentException(e); + } catch (final InvalidKeyException e) { + throw new IllegalArgumentException(e); + } + } + + // hmacMd5 + + /** + * Returns a HmacMD5 Message Authentication Code (MAC) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest (maybe empty or null) + * @return HmacMD5 MAC for the given key and value + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static byte[] hmacMd5(final byte[] key, final byte[] valueToDigest) { + try { + return getHmacMd5(key).doFinal(valueToDigest); + } catch (final IllegalStateException e) { + // cannot happen + throw new IllegalArgumentException(e); + } + } + + /** + * Returns a HmacMD5 Message Authentication Code (MAC) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest + *

+ * The InputStream must not be null and will not be closed + *

+ * @return HmacMD5 MAC for the given key and value + * @throws IOException + * If an I/O error occurs. + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static byte[] hmacMd5(final byte[] key, final InputStream valueToDigest) throws IOException { + return updateHmac(getHmacMd5(key), valueToDigest).doFinal(); + } + + /** + * Returns a HmacMD5 Message Authentication Code (MAC) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest (maybe empty or null) + * @return HmacMD5 MAC for the given key and value + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static byte[] hmacMd5(final String key, final String valueToDigest) { + return hmacMd5(StringUtils.getBytesUtf8(key), StringUtils.getBytesUtf8(valueToDigest)); + } + + /** + * Returns a HmacMD5 Message Authentication Code (MAC) as a hex string (lowercase) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest (maybe empty or null) + * @return HmacMD5 MAC for the given key and value as a hex string (lowercase) + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static String hmacMd5Hex(final byte[] key, final byte[] valueToDigest) { + return Hex.encodeHexString(hmacMd5(key, valueToDigest)); + } + + /** + * Returns a HmacMD5 Message Authentication Code (MAC) as a hex string (lowercase) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest + *

+ * The InputStream must not be null and will not be closed + *

+ * @return HmacMD5 MAC for the given key and value as a hex string (lowercase) + * @throws IOException + * If an I/O error occurs. + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static String hmacMd5Hex(final byte[] key, final InputStream valueToDigest) throws IOException { + return Hex.encodeHexString(hmacMd5(key, valueToDigest)); + } + + /** + * Returns a HmacMD5 Message Authentication Code (MAC) as a hex string (lowercase) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest (maybe empty or null) + * @return HmacMD5 MAC for the given key and value as a hex string (lowercase) + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static String hmacMd5Hex(final String key, final String valueToDigest) { + return Hex.encodeHexString(hmacMd5(key, valueToDigest)); + } + + // hmacSha1 + + /** + * Returns a HmacSHA1 Message Authentication Code (MAC) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest (maybe empty or null) + * @return HmacSHA1 MAC for the given key and value + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static byte[] hmacSha1(final byte[] key, final byte[] valueToDigest) { + try { + return getHmacSha1(key).doFinal(valueToDigest); + } catch (final IllegalStateException e) { + // cannot happen + throw new IllegalArgumentException(e); + } + } + + /** + * Returns a HmacSHA1 Message Authentication Code (MAC) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest + *

+ * The InputStream must not be null and will not be closed + *

+ * @return HmacSHA1 MAC for the given key and value + * @throws IOException + * If an I/O error occurs. + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static byte[] hmacSha1(final byte[] key, final InputStream valueToDigest) throws IOException { + return updateHmac(getHmacSha1(key), valueToDigest).doFinal(); + } + + /** + * Returns a HmacSHA1 Message Authentication Code (MAC) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest (maybe empty or null) + * @return HmacSHA1 MAC for the given key and value + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static byte[] hmacSha1(final String key, final String valueToDigest) { + return hmacSha1(StringUtils.getBytesUtf8(key), StringUtils.getBytesUtf8(valueToDigest)); + } + + /** + * Returns a HmacSHA1 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest (maybe empty or null) + * @return HmacSHA1 MAC for the given key and value as hex string (lowercase) + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static String hmacSha1Hex(final byte[] key, final byte[] valueToDigest) { + return Hex.encodeHexString(hmacSha1(key, valueToDigest)); + } + + /** + * Returns a HmacSHA1 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest + *

+ * The InputStream must not be null and will not be closed + *

+ * @return HmacSHA1 MAC for the given key and value as hex string (lowercase) + * @throws IOException + * If an I/O error occurs. + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static String hmacSha1Hex(final byte[] key, final InputStream valueToDigest) throws IOException { + return Hex.encodeHexString(hmacSha1(key, valueToDigest)); + } + + /** + * Returns a HmacSHA1 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest (maybe empty or null) + * @return HmacSHA1 MAC for the given key and value as hex string (lowercase) + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static String hmacSha1Hex(final String key, final String valueToDigest) { + return Hex.encodeHexString(hmacSha1(key, valueToDigest)); + } + + // hmacSha256 + + /** + * Returns a HmacSHA256 Message Authentication Code (MAC) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest (maybe empty or null) + * @return HmacSHA256 MAC for the given key and value + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static byte[] hmacSha256(final byte[] key, final byte[] valueToDigest) { + try { + return getHmacSha256(key).doFinal(valueToDigest); + } catch (final IllegalStateException e) { + // cannot happen + throw new IllegalArgumentException(e); + } + } + + /** + * Returns a HmacSHA256 Message Authentication Code (MAC) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest + *

+ * The InputStream must not be null and will not be closed + *

+ * @return HmacSHA256 MAC for the given key and value + * @throws IOException + * If an I/O error occurs. +s * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static byte[] hmacSha256(final byte[] key, final InputStream valueToDigest) throws IOException { + return updateHmac(getHmacSha256(key), valueToDigest).doFinal(); + } + + /** + * Returns a HmacSHA256 Message Authentication Code (MAC) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest (maybe empty or null) + * @return HmacSHA256 MAC for the given key and value + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static byte[] hmacSha256(final String key, final String valueToDigest) { + return hmacSha256(StringUtils.getBytesUtf8(key), StringUtils.getBytesUtf8(valueToDigest)); + } + + /** + * Returns a HmacSHA256 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest (maybe empty or null) + * @return HmacSHA256 MAC for the given key and value as hex string (lowercase) + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static String hmacSha256Hex(final byte[] key, final byte[] valueToDigest) { + return Hex.encodeHexString(hmacSha256(key, valueToDigest)); + } + + /** + * Returns a HmacSHA256 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest + *

+ * The InputStream must not be null and will not be closed + *

+ * @return HmacSHA256 MAC for the given key and value as hex string (lowercase) + * @throws IOException + * If an I/O error occurs. + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static String hmacSha256Hex(final byte[] key, final InputStream valueToDigest) throws IOException { + return Hex.encodeHexString(hmacSha256(key, valueToDigest)); + } + + /** + * Returns a HmacSHA256 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest (maybe empty or null) + * @return HmacSHA256 MAC for the given key and value as hex string (lowercase) + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static String hmacSha256Hex(final String key, final String valueToDigest) { + return Hex.encodeHexString(hmacSha256(key, valueToDigest)); + } + + // hmacSha384 + + /** + * Returns a HmacSHA384 Message Authentication Code (MAC) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest (maybe empty or null) + * @return HmacSHA384 MAC for the given key and value + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static byte[] hmacSha384(final byte[] key, final byte[] valueToDigest) { + try { + return getHmacSha384(key).doFinal(valueToDigest); + } catch (final IllegalStateException e) { + // cannot happen + throw new IllegalArgumentException(e); + } + } + + /** + * Returns a HmacSHA384 Message Authentication Code (MAC) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest + *

+ * The InputStream must not be null and will not be closed + *

+ * @return HmacSHA384 MAC for the given key and value + * @throws IOException + * If an I/O error occurs. + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static byte[] hmacSha384(final byte[] key, final InputStream valueToDigest) throws IOException { + return updateHmac(getHmacSha384(key), valueToDigest).doFinal(); + } + + /** + * Returns a HmacSHA384 Message Authentication Code (MAC) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest (maybe empty or null) + * @return HmacSHA384 MAC for the given key and value + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static byte[] hmacSha384(final String key, final String valueToDigest) { + return hmacSha384(StringUtils.getBytesUtf8(key), StringUtils.getBytesUtf8(valueToDigest)); + } + + /** + * Returns a HmacSHA384 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest (maybe empty or null) + * @return HmacSHA384 MAC for the given key and value as hex string (lowercase) + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static String hmacSha384Hex(final byte[] key, final byte[] valueToDigest) { + return Hex.encodeHexString(hmacSha384(key, valueToDigest)); + } + + /** + * Returns a HmacSHA384 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest + *

+ * The InputStream must not be null and will not be closed + *

+ * @return HmacSHA384 MAC for the given key and value as hex string (lowercase) + * @throws IOException + * If an I/O error occurs. + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static String hmacSha384Hex(final byte[] key, final InputStream valueToDigest) throws IOException { + return Hex.encodeHexString(hmacSha384(key, valueToDigest)); + } + + /** + * Returns a HmacSHA384 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest (maybe empty or null) + * @return HmacSHA384 MAC for the given key and value as hex string (lowercase) + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static String hmacSha384Hex(final String key, final String valueToDigest) { + return Hex.encodeHexString(hmacSha384(key, valueToDigest)); + } + + // hmacSha512 + + /** + * Returns a HmacSHA512 Message Authentication Code (MAC) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest (maybe empty or null) + * @return HmacSHA512 MAC for the given key and value + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static byte[] hmacSha512(final byte[] key, final byte[] valueToDigest) { + try { + return getHmacSha512(key).doFinal(valueToDigest); + } catch (final IllegalStateException e) { + // cannot happen + throw new IllegalArgumentException(e); + } + } + + /** + * Returns a HmacSHA512 Message Authentication Code (MAC) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest + *

+ * The InputStream must not be null and will not be closed + *

+ * @return HmacSHA512 MAC for the given key and value + * @throws IOException + * If an I/O error occurs. + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static byte[] hmacSha512(final byte[] key, final InputStream valueToDigest) throws IOException { + return updateHmac(getHmacSha512(key), valueToDigest).doFinal(); + } + + /** + * Returns a HmacSHA512 Message Authentication Code (MAC) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest (maybe empty or null) + * @return HmacSHA512 MAC for the given key and value + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static byte[] hmacSha512(final String key, final String valueToDigest) { + return hmacSha512(StringUtils.getBytesUtf8(key), StringUtils.getBytesUtf8(valueToDigest)); + } + + /** + * Returns a HmacSHA512 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest (maybe empty or null) + * @return HmacSHA512 MAC for the given key and value as hex string (lowercase) + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static String hmacSha512Hex(final byte[] key, final byte[] valueToDigest) { + return Hex.encodeHexString(hmacSha512(key, valueToDigest)); + } + + /** + * Returns a HmacSHA512 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest + *

+ * The InputStream must not be null and will not be closed + *

+ * @return HmacSHA512 MAC for the given key and value as hex string (lowercase) + * @throws IOException + * If an I/O error occurs. + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static String hmacSha512Hex(final byte[] key, final InputStream valueToDigest) throws IOException { + return Hex.encodeHexString(hmacSha512(key, valueToDigest)); + } + + /** + * Returns a HmacSHA512 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value. + * + * @param key + * They key for the keyed digest (must not be null) + * @param valueToDigest + * The value (data) which should to digest (maybe empty or null) + * @return HmacSHA512 MAC for the given key and value as hex string (lowercase) + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. + */ + public static String hmacSha512Hex(final String key, final String valueToDigest) { + return Hex.encodeHexString(hmacSha512(key, valueToDigest)); + } + + // update + + /** + * Updates the given {@link Mac}. This generates a digest for valueToDigest and the key the Mac was initialized + * + * @param mac + * the initialized {@link Mac} to update + * @param valueToDigest + * the value to update the {@link Mac} with (maybe null or empty) + * @return the updated {@link Mac} + * @throws IllegalStateException + * if the Mac was not initialized + * @since 1.x + */ + public static Mac updateHmac(final Mac mac, final byte[] valueToDigest) { + mac.reset(); + mac.update(valueToDigest); + return mac; + } + + /** + * Updates the given {@link Mac}. This generates a digest for valueToDigest and the key the Mac was initialized + * + * @param mac + * the initialized {@link Mac} to update + * @param valueToDigest + * the value to update the {@link Mac} with + *

+ * The InputStream must not be null and will not be closed + *

+ * @return the updated {@link Mac} + * @throws IOException + * If an I/O error occurs. + * @throws IllegalStateException + * If the Mac was not initialized + * @since 1.x + */ + public static Mac updateHmac(final Mac mac, final InputStream valueToDigest) throws IOException { + mac.reset(); + final byte[] buffer = new byte[STREAM_BUFFER_LENGTH]; + int read = valueToDigest.read(buffer, 0, STREAM_BUFFER_LENGTH); + + while (read > -1) { + mac.update(buffer, 0, read); + read = valueToDigest.read(buffer, 0, STREAM_BUFFER_LENGTH); + } + + return mac; + } + + /** + * Updates the given {@link Mac}. This generates a digest for valueToDigest and the key the Mac was initialized + * + * @param mac + * the initialized {@link Mac} to update + * @param valueToDigest + * the value to update the {@link Mac} with (maybe null or empty) + * @return the updated {@link Mac} + * @throws IllegalStateException + * if the Mac was not initialized + * @since 1.x + */ + public static Mac updateHmac(final Mac mac, final String valueToDigest) { + mac.reset(); + mac.update(StringUtils.getBytesUtf8(valueToDigest)); + return mac; + } +} diff --git a/src/org/apache/commons/codec/digest/Md5Crypt.java b/src/org/apache/commons/codec/digest/Md5Crypt.java new file mode 100644 index 00000000..83c1bce8 --- /dev/null +++ b/src/org/apache/commons/codec/digest/Md5Crypt.java @@ -0,0 +1,302 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.codec.digest; + +import java.security.MessageDigest; +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.codec.Charsets; + +/** + * The libc crypt() "$1$" and Apache "$apr1$" MD5-based hash algorithm. + *

+ * Based on the public domain ("beer-ware") C implementation from Poul-Henning Kamp which was found at: + * crypt-md5.c @ freebsd.org
+ *

+ * Source: + * + *

+ * $FreeBSD: src/lib/libcrypt/crypt-md5.c,v 1.1 1999/01/21 13:50:09 brandon Exp $
+ * 
+ *

+ * Conversion to Kotlin and from there to Java in 2012. + *

+ * The C style comments are from the original C code, the ones with "//" from the port. + *

+ * This class is immutable and thread-safe. + * + * @version $Id: Md5Crypt.java 1563226 2014-01-31 19:38:06Z ggregory $ + * @since 1.7 + */ +public class Md5Crypt { + + /** The Identifier of the Apache variant. */ + static final String APR1_PREFIX = "$apr1$"; + + /** The number of bytes of the final hash. */ + private static final int BLOCKSIZE = 16; + + /** The Identifier of this crypt() variant. */ + static final String MD5_PREFIX = "$1$"; + + /** The number of rounds of the big loop. */ + private static final int ROUNDS = 1000; + + /** + * See {@link #apr1Crypt(String, String)} for details. + * + * @param keyBytes + * plaintext string to hash. + * @return the hash value + * @throws RuntimeException + * when a {@link java.security.NoSuchAlgorithmException} is caught. * + */ + public static String apr1Crypt(final byte[] keyBytes) { + return apr1Crypt(keyBytes, APR1_PREFIX + B64.getRandomSalt(8)); + } + + /** + * See {@link #apr1Crypt(String, String)} for details. + * + * @param keyBytes + * plaintext string to hash. + * @param salt An APR1 salt. + * @return the hash value + * @throws IllegalArgumentException + * if the salt does not match the allowed pattern + * @throws RuntimeException + * when a {@link java.security.NoSuchAlgorithmException} is caught. + */ + public static String apr1Crypt(final byte[] keyBytes, String salt) { + // to make the md5Crypt regex happy + if (salt != null && !salt.startsWith(APR1_PREFIX)) { + salt = APR1_PREFIX + salt; + } + return Md5Crypt.md5Crypt(keyBytes, salt, APR1_PREFIX); + } + + /** + * See {@link #apr1Crypt(String, String)} for details. + * + * @param keyBytes + * plaintext string to hash. + * @return the hash value + * @throws RuntimeException + * when a {@link java.security.NoSuchAlgorithmException} is caught. + */ + public static String apr1Crypt(final String keyBytes) { + return apr1Crypt(keyBytes.getBytes(Charsets.UTF_8)); + } + + /** + * Generates an Apache htpasswd compatible "$apr1$" MD5 based hash value. + *

+ * The algorithm is identical to the crypt(3) "$1$" one but produces different outputs due to the different salt + * prefix. + * + * @param keyBytes + * plaintext string to hash. + * @param salt + * salt string including the prefix and optionally garbage at the end. Will be generated randomly if + * null. + * @return the hash value + * @throws IllegalArgumentException + * if the salt does not match the allowed pattern + * @throws RuntimeException + * when a {@link java.security.NoSuchAlgorithmException} is caught. + */ + public static String apr1Crypt(final String keyBytes, final String salt) { + return apr1Crypt(keyBytes.getBytes(Charsets.UTF_8), salt); + } + + /** + * Generates a libc6 crypt() compatible "$1$" hash value. + *

+ * See {@link Crypt#crypt(String, String)} for details. + * + * @param keyBytes + * plaintext string to hash. + * @return the hash value + * @throws RuntimeException + * when a {@link java.security.NoSuchAlgorithmException} is caught. + */ + public static String md5Crypt(final byte[] keyBytes) { + return md5Crypt(keyBytes, MD5_PREFIX + B64.getRandomSalt(8)); + } + + /** + * Generates a libc crypt() compatible "$1$" MD5 based hash value. + *

+ * See {@link Crypt#crypt(String, String)} for details. + * + * @param keyBytes + * plaintext string to hash. + * @param salt + * salt string including the prefix and optionally garbage at the end. Will be generated randomly if + * null. + * @return the hash value + * @throws IllegalArgumentException + * if the salt does not match the allowed pattern + * @throws RuntimeException + * when a {@link java.security.NoSuchAlgorithmException} is caught. + */ + public static String md5Crypt(final byte[] keyBytes, final String salt) { + return md5Crypt(keyBytes, salt, MD5_PREFIX); + } + + /** + * Generates a libc6 crypt() "$1$" or Apache htpasswd "$apr1$" hash value. + *

+ * See {@link Crypt#crypt(String, String)} or {@link #apr1Crypt(String, String)} for details. + * + * @param keyBytes + * plaintext string to hash. + * @param salt May be null. + * @param prefix salt prefix + * @return the hash value + * @throws IllegalArgumentException + * if the salt does not match the allowed pattern + * @throws RuntimeException + * when a {@link java.security.NoSuchAlgorithmException} is caught. + */ + public static String md5Crypt(final byte[] keyBytes, final String salt, final String prefix) { + final int keyLen = keyBytes.length; + + // Extract the real salt from the given string which can be a complete hash string. + String saltString; + if (salt == null) { + saltString = B64.getRandomSalt(8); + } else { + final Pattern p = Pattern.compile("^" + prefix.replace("$", "\\$") + "([\\.\\/a-zA-Z0-9]{1,8}).*"); + final Matcher m = p.matcher(salt); + if (m == null || !m.find()) { + throw new IllegalArgumentException("Invalid salt value: " + salt); + } + saltString = m.group(1); + } + final byte[] saltBytes = saltString.getBytes(Charsets.UTF_8); + + final MessageDigest ctx = DigestUtils.getMd5Digest(); + + /* + * The password first, since that is what is most unknown + */ + ctx.update(keyBytes); + + /* + * Then our magic string + */ + ctx.update(prefix.getBytes(Charsets.UTF_8)); + + /* + * Then the raw salt + */ + ctx.update(saltBytes); + + /* + * Then just as many characters of the MD5(pw,salt,pw) + */ + MessageDigest ctx1 = DigestUtils.getMd5Digest(); + ctx1.update(keyBytes); + ctx1.update(saltBytes); + ctx1.update(keyBytes); + byte[] finalb = ctx1.digest(); + int ii = keyLen; + while (ii > 0) { + ctx.update(finalb, 0, ii > 16 ? 16 : ii); + ii -= 16; + } + + /* + * Don't leave anything around in vm they could use. + */ + Arrays.fill(finalb, (byte) 0); + + /* + * Then something really weird... + */ + ii = keyLen; + final int j = 0; + while (ii > 0) { + if ((ii & 1) == 1) { + ctx.update(finalb[j]); + } else { + ctx.update(keyBytes[j]); + } + ii >>= 1; + } + + /* + * Now make the output string + */ + final StringBuilder passwd = new StringBuilder(prefix + saltString + "$"); + finalb = ctx.digest(); + + /* + * and now, just to make sure things don't run too fast On a 60 Mhz Pentium this takes 34 msec, so you would + * need 30 seconds to build a 1000 entry dictionary... + */ + for (int i = 0; i < ROUNDS; i++) { + ctx1 = DigestUtils.getMd5Digest(); + if ((i & 1) != 0) { + ctx1.update(keyBytes); + } else { + ctx1.update(finalb, 0, BLOCKSIZE); + } + + if (i % 3 != 0) { + ctx1.update(saltBytes); + } + + if (i % 7 != 0) { + ctx1.update(keyBytes); + } + + if ((i & 1) != 0) { + ctx1.update(finalb, 0, BLOCKSIZE); + } else { + ctx1.update(keyBytes); + } + finalb = ctx1.digest(); + } + + // The following was nearly identical to the Sha2Crypt code. + // Again, the buflen is not really needed. + // int buflen = MD5_PREFIX.length() - 1 + salt_string.length() + 1 + BLOCKSIZE + 1; + B64.b64from24bit(finalb[0], finalb[6], finalb[12], 4, passwd); + B64.b64from24bit(finalb[1], finalb[7], finalb[13], 4, passwd); + B64.b64from24bit(finalb[2], finalb[8], finalb[14], 4, passwd); + B64.b64from24bit(finalb[3], finalb[9], finalb[15], 4, passwd); + B64.b64from24bit(finalb[4], finalb[10], finalb[5], 4, passwd); + B64.b64from24bit((byte) 0, (byte) 0, finalb[11], 2, passwd); + + /* + * Don't leave anything around in vm they could use. + */ + // Is there a better way to do this with the JVM? + ctx.reset(); + ctx1.reset(); + Arrays.fill(keyBytes, (byte) 0); + Arrays.fill(saltBytes, (byte) 0); + Arrays.fill(finalb, (byte) 0); + + return passwd.toString(); + } +} diff --git a/src/org/apache/commons/codec/digest/MessageDigestAlgorithms.java b/src/org/apache/commons/codec/digest/MessageDigestAlgorithms.java new file mode 100644 index 00000000..05bfab68 --- /dev/null +++ b/src/org/apache/commons/codec/digest/MessageDigestAlgorithms.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.digest; + +import java.security.MessageDigest; + +/** + * Standard {@link MessageDigest} algorithm names from the Java Cryptography Architecture Standard Algorithm Name + * Documentation. + *

+ * This class is immutable and thread-safe. + *

+ * TODO This should be an enum. + * + * @see Java Cryptography + * Architecture Standard Algorithm Name Documentation + * @since 1.7 + * @version $Id: MessageDigestAlgorithms.java 1585867 2014-04-09 00:12:36Z ggregory $ + */ +public class MessageDigestAlgorithms { + + private MessageDigestAlgorithms() { + // cannot be instantiated. + } + + /** + * The MD2 message digest algorithm defined in RFC 1319. + */ + public static final String MD2 = "MD2"; + + /** + * The MD5 message digest algorithm defined in RFC 1321. + */ + public static final String MD5 = "MD5"; + + /** + * The SHA-1 hash algorithm defined in the FIPS PUB 180-2. + */ + public static final String SHA_1 = "SHA-1"; + + /** + * The SHA-256 hash algorithm defined in the FIPS PUB 180-2. + */ + public static final String SHA_256 = "SHA-256"; + + /** + * The SHA-384 hash algorithm defined in the FIPS PUB 180-2. + */ + public static final String SHA_384 = "SHA-384"; + + /** + * The SHA-512 hash algorithm defined in the FIPS PUB 180-2. + */ + public static final String SHA_512 = "SHA-512"; + +} diff --git a/src/org/apache/commons/codec/digest/Sha2Crypt.java b/src/org/apache/commons/codec/digest/Sha2Crypt.java new file mode 100644 index 00000000..0643dd87 --- /dev/null +++ b/src/org/apache/commons/codec/digest/Sha2Crypt.java @@ -0,0 +1,545 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.codec.digest; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.codec.Charsets; + +/** + * SHA2-based Unix crypt implementation. + *

+ * Based on the C implementation released into the Public Domain by Ulrich Drepper <drepper@redhat.com> + * http://www.akkadia.org/drepper/SHA-crypt.txt + *

+ * Conversion to Kotlin and from there to Java in 2012 by Christian Hammers <ch@lathspell.de> and likewise put + * into the Public Domain. + *

+ * This class is immutable and thread-safe. + * + * @version $Id: Sha2Crypt.java 1619948 2014-08-22 22:53:55Z ggregory $ + * @since 1.7 + */ +public class Sha2Crypt { + + /** Default number of rounds if not explicitly specified. */ + private static final int ROUNDS_DEFAULT = 5000; + + /** Maximum number of rounds. */ + private static final int ROUNDS_MAX = 999999999; + + /** Minimum number of rounds. */ + private static final int ROUNDS_MIN = 1000; + + /** Prefix for optional rounds specification. */ + private static final String ROUNDS_PREFIX = "rounds="; + + /** The number of bytes the final hash value will have (SHA-256 variant). */ + private static final int SHA256_BLOCKSIZE = 32; + + /** The prefixes that can be used to identify this crypt() variant (SHA-256). */ + static final String SHA256_PREFIX = "$5$"; + + /** The number of bytes the final hash value will have (SHA-512 variant). */ + private static final int SHA512_BLOCKSIZE = 64; + + /** The prefixes that can be used to identify this crypt() variant (SHA-512). */ + static final String SHA512_PREFIX = "$6$"; + + /** The pattern to match valid salt values. */ + private static final Pattern SALT_PATTERN = Pattern + .compile("^\\$([56])\\$(rounds=(\\d+)\\$)?([\\.\\/a-zA-Z0-9]{1,16}).*"); + + /** + * Generates a libc crypt() compatible "$5$" hash value with random salt. + *

+ * See {@link Crypt#crypt(String, String)} for details. + * + * @param keyBytes + * plaintext to hash + * @return complete hash value + * @throws RuntimeException + * when a {@link java.security.NoSuchAlgorithmException} is caught. + */ + public static String sha256Crypt(final byte[] keyBytes) { + return sha256Crypt(keyBytes, null); + } + + /** + * Generates a libc6 crypt() compatible "$5$" hash value. + *

+ * See {@link Crypt#crypt(String, String)} for details. + * + * @param keyBytes + * plaintext to hash + * @param salt + * real salt value without prefix or "rounds=" + * @return complete hash value including salt + * @throws IllegalArgumentException + * if the salt does not match the allowed pattern + * @throws RuntimeException + * when a {@link java.security.NoSuchAlgorithmException} is caught. + */ + public static String sha256Crypt(final byte[] keyBytes, String salt) { + if (salt == null) { + salt = SHA256_PREFIX + B64.getRandomSalt(8); + } + return sha2Crypt(keyBytes, salt, SHA256_PREFIX, SHA256_BLOCKSIZE, MessageDigestAlgorithms.SHA_256); + } + + /** + * Generates a libc6 crypt() compatible "$5$" or "$6$" SHA2 based hash value. + *

+ * This is a nearly line by line conversion of the original C function. The numbered comments are from the algorithm + * description, the short C-style ones from the original C code and the ones with "Remark" from me. + *

+ * See {@link Crypt#crypt(String, String)} for details. + * + * @param keyBytes + * plaintext to hash + * @param salt + * real salt value without prefix or "rounds=" + * @param saltPrefix + * either $5$ or $6$ + * @param blocksize + * a value that differs between $5$ and $6$ + * @param algorithm + * {@link MessageDigest} algorithm identifier string + * @return complete hash value including prefix and salt + * @throws IllegalArgumentException + * if the given salt is null or does not match the allowed pattern + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught + * @see MessageDigestAlgorithms + */ + private static String sha2Crypt(final byte[] keyBytes, final String salt, final String saltPrefix, + final int blocksize, final String algorithm) { + + final int keyLen = keyBytes.length; + + // Extracts effective salt and the number of rounds from the given salt. + int rounds = ROUNDS_DEFAULT; + boolean roundsCustom = false; + if (salt == null) { + throw new IllegalArgumentException("Salt must not be null"); + } + + final Matcher m = SALT_PATTERN.matcher(salt); + if (m == null || !m.find()) { + throw new IllegalArgumentException("Invalid salt value: " + salt); + } + if (m.group(3) != null) { + rounds = Integer.parseInt(m.group(3)); + rounds = Math.max(ROUNDS_MIN, Math.min(ROUNDS_MAX, rounds)); + roundsCustom = true; + } + final String saltString = m.group(4); + final byte[] saltBytes = saltString.getBytes(Charsets.UTF_8); + final int saltLen = saltBytes.length; + + // 1. start digest A + // Prepare for the real work. + MessageDigest ctx = DigestUtils.getDigest(algorithm); + + // 2. the password string is added to digest A + /* + * Add the key string. + */ + ctx.update(keyBytes); + + // 3. the salt string is added to digest A. This is just the salt string + // itself without the enclosing '$', without the magic salt_prefix $5$ and + // $6$ respectively and without the rounds= specification. + // + // NB: the MD5 algorithm did add the $1$ salt_prefix. This is not deemed + // necessary since it is a constant string and does not add security + // and /possibly/ allows a plain text attack. Since the rounds= + // specification should never be added this would also create an + // inconsistency. + /* + * The last part is the salt string. This must be at most 16 characters and it ends at the first `$' character + * (for compatibility with existing implementations). + */ + ctx.update(saltBytes); + + // 4. start digest B + /* + * Compute alternate sha512 sum with input KEY, SALT, and KEY. The final result will be added to the first + * context. + */ + MessageDigest altCtx = DigestUtils.getDigest(algorithm); + + // 5. add the password to digest B + /* + * Add key. + */ + altCtx.update(keyBytes); + + // 6. add the salt string to digest B + /* + * Add salt. + */ + altCtx.update(saltBytes); + + // 7. add the password again to digest B + /* + * Add key again. + */ + altCtx.update(keyBytes); + + // 8. finish digest B + /* + * Now get result of this (32 bytes) and add it to the other context. + */ + byte[] altResult = altCtx.digest(); + + // 9. For each block of 32 or 64 bytes in the password string (excluding + // the terminating NUL in the C representation), add digest B to digest A + /* + * Add for any character in the key one byte of the alternate sum. + */ + /* + * (Remark: the C code comment seems wrong for key length > 32!) + */ + int cnt = keyBytes.length; + while (cnt > blocksize) { + ctx.update(altResult, 0, blocksize); + cnt -= blocksize; + } + + // 10. For the remaining N bytes of the password string add the first + // N bytes of digest B to digest A + ctx.update(altResult, 0, cnt); + + // 11. For each bit of the binary representation of the length of the + // password string up to and including the highest 1-digit, starting + // from to lowest bit position (numeric value 1): + // + // a) for a 1-digit add digest B to digest A + // + // b) for a 0-digit add the password string + // + // NB: this step differs significantly from the MD5 algorithm. It + // adds more randomness. + /* + * Take the binary representation of the length of the key and for every 1 add the alternate sum, for every 0 + * the key. + */ + cnt = keyBytes.length; + while (cnt > 0) { + if ((cnt & 1) != 0) { + ctx.update(altResult, 0, blocksize); + } else { + ctx.update(keyBytes); + } + cnt >>= 1; + } + + // 12. finish digest A + /* + * Create intermediate result. + */ + altResult = ctx.digest(); + + // 13. start digest DP + /* + * Start computation of P byte sequence. + */ + altCtx = DigestUtils.getDigest(algorithm); + + // 14. for every byte in the password (excluding the terminating NUL byte + // in the C representation of the string) + // + // add the password to digest DP + /* + * For every character in the password add the entire password. + */ + for (int i = 1; i <= keyLen; i++) { + altCtx.update(keyBytes); + } + + // 15. finish digest DP + /* + * Finish the digest. + */ + byte[] tempResult = altCtx.digest(); + + // 16. produce byte sequence P of the same length as the password where + // + // a) for each block of 32 or 64 bytes of length of the password string + // the entire digest DP is used + // + // b) for the remaining N (up to 31 or 63) bytes use the first N + // bytes of digest DP + /* + * Create byte sequence P. + */ + final byte[] pBytes = new byte[keyLen]; + int cp = 0; + while (cp < keyLen - blocksize) { + System.arraycopy(tempResult, 0, pBytes, cp, blocksize); + cp += blocksize; + } + System.arraycopy(tempResult, 0, pBytes, cp, keyLen - cp); + + // 17. start digest DS + /* + * Start computation of S byte sequence. + */ + altCtx = DigestUtils.getDigest(algorithm); + + // 18. repeast the following 16+A[0] times, where A[0] represents the first + // byte in digest A interpreted as an 8-bit unsigned value + // + // add the salt to digest DS + /* + * For every character in the password add the entire password. + */ + for (int i = 1; i <= 16 + (altResult[0] & 0xff); i++) { + altCtx.update(saltBytes); + } + + // 19. finish digest DS + /* + * Finish the digest. + */ + tempResult = altCtx.digest(); + + // 20. produce byte sequence S of the same length as the salt string where + // + // a) for each block of 32 or 64 bytes of length of the salt string + // the entire digest DS is used + // + // b) for the remaining N (up to 31 or 63) bytes use the first N + // bytes of digest DS + /* + * Create byte sequence S. + */ + // Remark: The salt is limited to 16 chars, how does this make sense? + final byte[] sBytes = new byte[saltLen]; + cp = 0; + while (cp < saltLen - blocksize) { + System.arraycopy(tempResult, 0, sBytes, cp, blocksize); + cp += blocksize; + } + System.arraycopy(tempResult, 0, sBytes, cp, saltLen - cp); + + // 21. repeat a loop according to the number specified in the rounds= + // specification in the salt (or the default value if none is + // present). Each round is numbered, starting with 0 and up to N-1. + // + // The loop uses a digest as input. In the first round it is the + // digest produced in step 12. In the latter steps it is the digest + // produced in step 21.h. The following text uses the notation + // "digest A/C" to describe this behavior. + /* + * Repeatedly run the collected hash value through sha512 to burn CPU cycles. + */ + for (int i = 0; i <= rounds - 1; i++) { + // a) start digest C + /* + * New context. + */ + ctx = DigestUtils.getDigest(algorithm); + + // b) for odd round numbers add the byte sequense P to digest C + // c) for even round numbers add digest A/C + /* + * Add key or last result. + */ + if ((i & 1) != 0) { + ctx.update(pBytes, 0, keyLen); + } else { + ctx.update(altResult, 0, blocksize); + } + + // d) for all round numbers not divisible by 3 add the byte sequence S + /* + * Add salt for numbers not divisible by 3. + */ + if (i % 3 != 0) { + ctx.update(sBytes, 0, saltLen); + } + + // e) for all round numbers not divisible by 7 add the byte sequence P + /* + * Add key for numbers not divisible by 7. + */ + if (i % 7 != 0) { + ctx.update(pBytes, 0, keyLen); + } + + // f) for odd round numbers add digest A/C + // g) for even round numbers add the byte sequence P + /* + * Add key or last result. + */ + if ((i & 1) != 0) { + ctx.update(altResult, 0, blocksize); + } else { + ctx.update(pBytes, 0, keyLen); + } + + // h) finish digest C. + /* + * Create intermediate result. + */ + altResult = ctx.digest(); + } + + // 22. Produce the output string. This is an ASCII string of the maximum + // size specified above, consisting of multiple pieces: + // + // a) the salt salt_prefix, $5$ or $6$ respectively + // + // b) the rounds= specification, if one was present in the input + // salt string. A trailing '$' is added in this case to separate + // the rounds specification from the following text. + // + // c) the salt string truncated to 16 characters + // + // d) a '$' character + /* + * Now we can construct the result string. It consists of three parts. + */ + final StringBuilder buffer = new StringBuilder(saltPrefix); + if (roundsCustom) { + buffer.append(ROUNDS_PREFIX); + buffer.append(rounds); + buffer.append("$"); + } + buffer.append(saltString); + buffer.append("$"); + + // e) the base-64 encoded final C digest. The encoding used is as + // follows: + // [...] + // + // Each group of three bytes from the digest produces four + // characters as output: + // + // 1. character: the six low bits of the first byte + // 2. character: the two high bits of the first byte and the + // four low bytes from the second byte + // 3. character: the four high bytes from the second byte and + // the two low bits from the third byte + // 4. character: the six high bits from the third byte + // + // The groups of three bytes are as follows (in this sequence). + // These are the indices into the byte array containing the + // digest, starting with index 0. For the last group there are + // not enough bytes left in the digest and the value zero is used + // in its place. This group also produces only three or two + // characters as output for SHA-512 and SHA-512 respectively. + + // This was just a safeguard in the C implementation: + // int buflen = salt_prefix.length() - 1 + ROUNDS_PREFIX.length() + 9 + 1 + salt_string.length() + 1 + 86 + 1; + + if (blocksize == 32) { + B64.b64from24bit(altResult[0], altResult[10], altResult[20], 4, buffer); + B64.b64from24bit(altResult[21], altResult[1], altResult[11], 4, buffer); + B64.b64from24bit(altResult[12], altResult[22], altResult[2], 4, buffer); + B64.b64from24bit(altResult[3], altResult[13], altResult[23], 4, buffer); + B64.b64from24bit(altResult[24], altResult[4], altResult[14], 4, buffer); + B64.b64from24bit(altResult[15], altResult[25], altResult[5], 4, buffer); + B64.b64from24bit(altResult[6], altResult[16], altResult[26], 4, buffer); + B64.b64from24bit(altResult[27], altResult[7], altResult[17], 4, buffer); + B64.b64from24bit(altResult[18], altResult[28], altResult[8], 4, buffer); + B64.b64from24bit(altResult[9], altResult[19], altResult[29], 4, buffer); + B64.b64from24bit((byte) 0, altResult[31], altResult[30], 3, buffer); + } else { + B64.b64from24bit(altResult[0], altResult[21], altResult[42], 4, buffer); + B64.b64from24bit(altResult[22], altResult[43], altResult[1], 4, buffer); + B64.b64from24bit(altResult[44], altResult[2], altResult[23], 4, buffer); + B64.b64from24bit(altResult[3], altResult[24], altResult[45], 4, buffer); + B64.b64from24bit(altResult[25], altResult[46], altResult[4], 4, buffer); + B64.b64from24bit(altResult[47], altResult[5], altResult[26], 4, buffer); + B64.b64from24bit(altResult[6], altResult[27], altResult[48], 4, buffer); + B64.b64from24bit(altResult[28], altResult[49], altResult[7], 4, buffer); + B64.b64from24bit(altResult[50], altResult[8], altResult[29], 4, buffer); + B64.b64from24bit(altResult[9], altResult[30], altResult[51], 4, buffer); + B64.b64from24bit(altResult[31], altResult[52], altResult[10], 4, buffer); + B64.b64from24bit(altResult[53], altResult[11], altResult[32], 4, buffer); + B64.b64from24bit(altResult[12], altResult[33], altResult[54], 4, buffer); + B64.b64from24bit(altResult[34], altResult[55], altResult[13], 4, buffer); + B64.b64from24bit(altResult[56], altResult[14], altResult[35], 4, buffer); + B64.b64from24bit(altResult[15], altResult[36], altResult[57], 4, buffer); + B64.b64from24bit(altResult[37], altResult[58], altResult[16], 4, buffer); + B64.b64from24bit(altResult[59], altResult[17], altResult[38], 4, buffer); + B64.b64from24bit(altResult[18], altResult[39], altResult[60], 4, buffer); + B64.b64from24bit(altResult[40], altResult[61], altResult[19], 4, buffer); + B64.b64from24bit(altResult[62], altResult[20], altResult[41], 4, buffer); + B64.b64from24bit((byte) 0, (byte) 0, altResult[63], 2, buffer); + } + + /* + * Clear the buffer for the intermediate result so that people attaching to processes or reading core dumps + * cannot get any information. + */ + // Is there a better way to do this with the JVM? + Arrays.fill(tempResult, (byte) 0); + Arrays.fill(pBytes, (byte) 0); + Arrays.fill(sBytes, (byte) 0); + ctx.reset(); + altCtx.reset(); + Arrays.fill(keyBytes, (byte) 0); + Arrays.fill(saltBytes, (byte) 0); + + return buffer.toString(); + } + + /** + * Generates a libc crypt() compatible "$6$" hash value with random salt. + *

+ * See {@link Crypt#crypt(String, String)} for details. + * + * @param keyBytes + * plaintext to hash + * @return complete hash value + * @throws RuntimeException + * when a {@link java.security.NoSuchAlgorithmException} is caught. + */ + public static String sha512Crypt(final byte[] keyBytes) { + return sha512Crypt(keyBytes, null); + } + + /** + * Generates a libc6 crypt() compatible "$6$" hash value. + *

+ * See {@link Crypt#crypt(String, String)} for details. + * + * @param keyBytes + * plaintext to hash + * @param salt + * real salt value without prefix or "rounds=" + * @return complete hash value including salt + * @throws IllegalArgumentException + * if the salt does not match the allowed pattern + * @throws RuntimeException + * when a {@link java.security.NoSuchAlgorithmException} is caught. + */ + public static String sha512Crypt(final byte[] keyBytes, String salt) { + if (salt == null) { + salt = SHA512_PREFIX + B64.getRandomSalt(8); + } + return sha2Crypt(keyBytes, salt, SHA512_PREFIX, SHA512_BLOCKSIZE, MessageDigestAlgorithms.SHA_512); + } +} diff --git a/src/org/apache/commons/codec/digest/UnixCrypt.java b/src/org/apache/commons/codec/digest/UnixCrypt.java new file mode 100644 index 00000000..16ebc35c --- /dev/null +++ b/src/org/apache/commons/codec/digest/UnixCrypt.java @@ -0,0 +1,413 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.codec.digest; + +import java.util.Random; + +import org.apache.commons.codec.Charsets; + +/** + * Unix crypt(3) algorithm implementation. + *

+ * This class only implements the traditional 56 bit DES based algorithm. Please use DigestUtils.crypt() for a method + * that distinguishes between all the algorithms supported in the current glibc's crypt(). + *

+ * The Java implementation was taken from the JetSpeed Portal project (see + * org.apache.jetspeed.services.security.ldap.UnixCrypt). + *

+ * This class is slightly incompatible if the given salt contains characters that are not part of the allowed range + * [a-zA-Z0-9./]. + *

+ * This class is immutable and thread-safe. + * + * @version $Id: UnixCrypt.java 1429868 2013-01-07 16:08:05Z ggregory $ + * @since 1.7 + */ +public class UnixCrypt { + + private static final int CON_SALT[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, + 34, 35, 36, 37, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 0, 0, 0, 0, 0 }; + + private static final int COV2CHAR[] = { 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, + 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, + 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122 }; + + private static final char SALT_CHARS[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./" + .toCharArray(); + + private static final boolean SHIFT2[] = { false, false, true, true, true, true, true, true, false, true, true, + true, true, true, true, false }; + + private static final int SKB[][] = { + { 0, 16, 0x20000000, 0x20000010, 0x10000, 0x10010, 0x20010000, 0x20010010, 2048, 2064, 0x20000800, + 0x20000810, 0x10800, 0x10810, 0x20010800, 0x20010810, 32, 48, 0x20000020, 0x20000030, 0x10020, + 0x10030, 0x20010020, 0x20010030, 2080, 2096, 0x20000820, 0x20000830, 0x10820, 0x10830, 0x20010820, + 0x20010830, 0x80000, 0x80010, 0x20080000, 0x20080010, 0x90000, 0x90010, 0x20090000, 0x20090010, + 0x80800, 0x80810, 0x20080800, 0x20080810, 0x90800, 0x90810, 0x20090800, 0x20090810, 0x80020, + 0x80030, 0x20080020, 0x20080030, 0x90020, 0x90030, 0x20090020, 0x20090030, 0x80820, 0x80830, + 0x20080820, 0x20080830, 0x90820, 0x90830, 0x20090820, 0x20090830 }, + { 0, 0x2000000, 8192, 0x2002000, 0x200000, 0x2200000, 0x202000, 0x2202000, 4, 0x2000004, 8196, 0x2002004, + 0x200004, 0x2200004, 0x202004, 0x2202004, 1024, 0x2000400, 9216, 0x2002400, 0x200400, 0x2200400, + 0x202400, 0x2202400, 1028, 0x2000404, 9220, 0x2002404, 0x200404, 0x2200404, 0x202404, 0x2202404, + 0x10000000, 0x12000000, 0x10002000, 0x12002000, 0x10200000, 0x12200000, 0x10202000, 0x12202000, + 0x10000004, 0x12000004, 0x10002004, 0x12002004, 0x10200004, 0x12200004, 0x10202004, 0x12202004, + 0x10000400, 0x12000400, 0x10002400, 0x12002400, 0x10200400, 0x12200400, 0x10202400, 0x12202400, + 0x10000404, 0x12000404, 0x10002404, 0x12002404, 0x10200404, 0x12200404, 0x10202404, 0x12202404 }, + { 0, 1, 0x40000, 0x40001, 0x1000000, 0x1000001, 0x1040000, 0x1040001, 2, 3, 0x40002, 0x40003, 0x1000002, + 0x1000003, 0x1040002, 0x1040003, 512, 513, 0x40200, 0x40201, 0x1000200, 0x1000201, 0x1040200, + 0x1040201, 514, 515, 0x40202, 0x40203, 0x1000202, 0x1000203, 0x1040202, 0x1040203, 0x8000000, + 0x8000001, 0x8040000, 0x8040001, 0x9000000, 0x9000001, 0x9040000, 0x9040001, 0x8000002, 0x8000003, + 0x8040002, 0x8040003, 0x9000002, 0x9000003, 0x9040002, 0x9040003, 0x8000200, 0x8000201, 0x8040200, + 0x8040201, 0x9000200, 0x9000201, 0x9040200, 0x9040201, 0x8000202, 0x8000203, 0x8040202, 0x8040203, + 0x9000202, 0x9000203, 0x9040202, 0x9040203 }, + { 0, 0x100000, 256, 0x100100, 8, 0x100008, 264, 0x100108, 4096, 0x101000, 4352, 0x101100, 4104, 0x101008, + 4360, 0x101108, 0x4000000, 0x4100000, 0x4000100, 0x4100100, 0x4000008, 0x4100008, 0x4000108, + 0x4100108, 0x4001000, 0x4101000, 0x4001100, 0x4101100, 0x4001008, 0x4101008, 0x4001108, 0x4101108, + 0x20000, 0x120000, 0x20100, 0x120100, 0x20008, 0x120008, 0x20108, 0x120108, 0x21000, 0x121000, + 0x21100, 0x121100, 0x21008, 0x121008, 0x21108, 0x121108, 0x4020000, 0x4120000, 0x4020100, + 0x4120100, 0x4020008, 0x4120008, 0x4020108, 0x4120108, 0x4021000, 0x4121000, 0x4021100, 0x4121100, + 0x4021008, 0x4121008, 0x4021108, 0x4121108 }, + { 0, 0x10000000, 0x10000, 0x10010000, 4, 0x10000004, 0x10004, 0x10010004, 0x20000000, 0x30000000, + 0x20010000, 0x30010000, 0x20000004, 0x30000004, 0x20010004, 0x30010004, 0x100000, 0x10100000, + 0x110000, 0x10110000, 0x100004, 0x10100004, 0x110004, 0x10110004, 0x20100000, 0x30100000, + 0x20110000, 0x30110000, 0x20100004, 0x30100004, 0x20110004, 0x30110004, 4096, 0x10001000, 0x11000, + 0x10011000, 4100, 0x10001004, 0x11004, 0x10011004, 0x20001000, 0x30001000, 0x20011000, 0x30011000, + 0x20001004, 0x30001004, 0x20011004, 0x30011004, 0x101000, 0x10101000, 0x111000, 0x10111000, + 0x101004, 0x10101004, 0x111004, 0x10111004, 0x20101000, 0x30101000, 0x20111000, 0x30111000, + 0x20101004, 0x30101004, 0x20111004, 0x30111004 }, + { 0, 0x8000000, 8, 0x8000008, 1024, 0x8000400, 1032, 0x8000408, 0x20000, 0x8020000, 0x20008, 0x8020008, + 0x20400, 0x8020400, 0x20408, 0x8020408, 1, 0x8000001, 9, 0x8000009, 1025, 0x8000401, 1033, + 0x8000409, 0x20001, 0x8020001, 0x20009, 0x8020009, 0x20401, 0x8020401, 0x20409, 0x8020409, + 0x2000000, 0xa000000, 0x2000008, 0xa000008, 0x2000400, 0xa000400, 0x2000408, 0xa000408, 0x2020000, + 0xa020000, 0x2020008, 0xa020008, 0x2020400, 0xa020400, 0x2020408, 0xa020408, 0x2000001, 0xa000001, + 0x2000009, 0xa000009, 0x2000401, 0xa000401, 0x2000409, 0xa000409, 0x2020001, 0xa020001, 0x2020009, + 0xa020009, 0x2020401, 0xa020401, 0x2020409, 0xa020409 }, + { 0, 256, 0x80000, 0x80100, 0x1000000, 0x1000100, 0x1080000, 0x1080100, 16, 272, 0x80010, 0x80110, + 0x1000010, 0x1000110, 0x1080010, 0x1080110, 0x200000, 0x200100, 0x280000, 0x280100, 0x1200000, + 0x1200100, 0x1280000, 0x1280100, 0x200010, 0x200110, 0x280010, 0x280110, 0x1200010, 0x1200110, + 0x1280010, 0x1280110, 512, 768, 0x80200, 0x80300, 0x1000200, 0x1000300, 0x1080200, 0x1080300, 528, + 784, 0x80210, 0x80310, 0x1000210, 0x1000310, 0x1080210, 0x1080310, 0x200200, 0x200300, 0x280200, + 0x280300, 0x1200200, 0x1200300, 0x1280200, 0x1280300, 0x200210, 0x200310, 0x280210, 0x280310, + 0x1200210, 0x1200310, 0x1280210, 0x1280310 }, + { 0, 0x4000000, 0x40000, 0x4040000, 2, 0x4000002, 0x40002, 0x4040002, 8192, 0x4002000, 0x42000, 0x4042000, + 8194, 0x4002002, 0x42002, 0x4042002, 32, 0x4000020, 0x40020, 0x4040020, 34, 0x4000022, 0x40022, + 0x4040022, 8224, 0x4002020, 0x42020, 0x4042020, 8226, 0x4002022, 0x42022, 0x4042022, 2048, + 0x4000800, 0x40800, 0x4040800, 2050, 0x4000802, 0x40802, 0x4040802, 10240, 0x4002800, 0x42800, + 0x4042800, 10242, 0x4002802, 0x42802, 0x4042802, 2080, 0x4000820, 0x40820, 0x4040820, 2082, + 0x4000822, 0x40822, 0x4040822, 10272, 0x4002820, 0x42820, 0x4042820, 10274, 0x4002822, 0x42822, + 0x4042822 } }; + + private static final int SPTRANS[][] = { + { 0x820200, 0x20000, 0x80800000, 0x80820200, 0x800000, 0x80020200, 0x80020000, 0x80800000, 0x80020200, + 0x820200, 0x820000, 0x80000200, 0x80800200, 0x800000, 0, 0x80020000, 0x20000, 0x80000000, + 0x800200, 0x20200, 0x80820200, 0x820000, 0x80000200, 0x800200, 0x80000000, 512, 0x20200, + 0x80820000, 512, 0x80800200, 0x80820000, 0, 0, 0x80820200, 0x800200, 0x80020000, 0x820200, + 0x20000, 0x80000200, 0x800200, 0x80820000, 512, 0x20200, 0x80800000, 0x80020200, 0x80000000, + 0x80800000, 0x820000, 0x80820200, 0x20200, 0x820000, 0x80800200, 0x800000, 0x80000200, 0x80020000, + 0, 0x20000, 0x800000, 0x80800200, 0x820200, 0x80000000, 0x80820000, 512, 0x80020200 }, + { 0x10042004, 0, 0x42000, 0x10040000, 0x10000004, 8196, 0x10002000, 0x42000, 8192, 0x10040004, 4, + 0x10002000, 0x40004, 0x10042000, 0x10040000, 4, 0x40000, 0x10002004, 0x10040004, 8192, 0x42004, + 0x10000000, 0, 0x40004, 0x10002004, 0x42004, 0x10042000, 0x10000004, 0x10000000, 0x40000, 8196, + 0x10042004, 0x40004, 0x10042000, 0x10002000, 0x42004, 0x10042004, 0x40004, 0x10000004, 0, + 0x10000000, 8196, 0x40000, 0x10040004, 8192, 0x10000000, 0x42004, 0x10002004, 0x10042000, 8192, 0, + 0x10000004, 4, 0x10042004, 0x42000, 0x10040000, 0x10040004, 0x40000, 8196, 0x10002000, 0x10002004, + 4, 0x10040000, 0x42000 }, + { 0x41000000, 0x1010040, 64, 0x41000040, 0x40010000, 0x1000000, 0x41000040, 0x10040, 0x1000040, 0x10000, + 0x1010000, 0x40000000, 0x41010040, 0x40000040, 0x40000000, 0x41010000, 0, 0x40010000, 0x1010040, + 64, 0x40000040, 0x41010040, 0x10000, 0x41000000, 0x41010000, 0x1000040, 0x40010040, 0x1010000, + 0x10040, 0, 0x1000000, 0x40010040, 0x1010040, 64, 0x40000000, 0x10000, 0x40000040, 0x40010000, + 0x1010000, 0x41000040, 0, 0x1010040, 0x10040, 0x41010000, 0x40010000, 0x1000000, 0x41010040, + 0x40000000, 0x40010040, 0x41000000, 0x1000000, 0x41010040, 0x10000, 0x1000040, 0x41000040, + 0x10040, 0x1000040, 0, 0x41010000, 0x40000040, 0x41000000, 0x40010040, 64, 0x1010000 }, + { 0x100402, 0x4000400, 2, 0x4100402, 0, 0x4100000, 0x4000402, 0x100002, 0x4100400, 0x4000002, 0x4000000, + 1026, 0x4000002, 0x100402, 0x100000, 0x4000000, 0x4100002, 0x100400, 1024, 2, 0x100400, 0x4000402, + 0x4100000, 1024, 1026, 0, 0x100002, 0x4100400, 0x4000400, 0x4100002, 0x4100402, 0x100000, + 0x4100002, 1026, 0x100000, 0x4000002, 0x100400, 0x4000400, 2, 0x4100000, 0x4000402, 0, 1024, + 0x100002, 0, 0x4100002, 0x4100400, 1024, 0x4000000, 0x4100402, 0x100402, 0x100000, 0x4100402, 2, + 0x4000400, 0x100402, 0x100002, 0x100400, 0x4100000, 0x4000402, 1026, 0x4000000, 0x4000002, + 0x4100400 }, + { 0x2000000, 16384, 256, 0x2004108, 0x2004008, 0x2000100, 16648, 0x2004000, 16384, 8, 0x2000008, 16640, + 0x2000108, 0x2004008, 0x2004100, 0, 16640, 0x2000000, 16392, 264, 0x2000100, 16648, 0, 0x2000008, + 8, 0x2000108, 0x2004108, 16392, 0x2004000, 256, 264, 0x2004100, 0x2004100, 0x2000108, 16392, + 0x2004000, 16384, 8, 0x2000008, 0x2000100, 0x2000000, 16640, 0x2004108, 0, 16648, 0x2000000, 256, + 16392, 0x2000108, 256, 0, 0x2004108, 0x2004008, 0x2004100, 264, 16384, 16640, 0x2004008, + 0x2000100, 264, 8, 16648, 0x2004000, 0x2000008 }, + { 0x20000010, 0x80010, 0, 0x20080800, 0x80010, 2048, 0x20000810, 0x80000, 2064, 0x20080810, 0x80800, + 0x20000000, 0x20000800, 0x20000010, 0x20080000, 0x80810, 0x80000, 0x20000810, 0x20080010, 0, 2048, + 16, 0x20080800, 0x20080010, 0x20080810, 0x20080000, 0x20000000, 2064, 16, 0x80800, 0x80810, + 0x20000800, 2064, 0x20000000, 0x20000800, 0x80810, 0x20080800, 0x80010, 0, 0x20000800, 0x20000000, + 2048, 0x20080010, 0x80000, 0x80010, 0x20080810, 0x80800, 16, 0x20080810, 0x80800, 0x80000, + 0x20000810, 0x20000010, 0x20080000, 0x80810, 0, 2048, 0x20000010, 0x20000810, 0x20080800, + 0x20080000, 2064, 16, 0x20080010 }, + { 4096, 128, 0x400080, 0x400001, 0x401081, 4097, 4224, 0, 0x400000, 0x400081, 129, 0x401000, 1, 0x401080, + 0x401000, 129, 0x400081, 4096, 4097, 0x401081, 0, 0x400080, 0x400001, 4224, 0x401001, 4225, + 0x401080, 1, 4225, 0x401001, 128, 0x400000, 4225, 0x401000, 0x401001, 129, 4096, 128, 0x400000, + 0x401001, 0x400081, 4225, 4224, 0, 128, 0x400001, 1, 0x400080, 0, 0x400081, 0x400080, 4224, 129, + 4096, 0x401081, 0x400000, 0x401080, 1, 4097, 0x401081, 0x400001, 0x401080, 0x401000, 4097 }, + { 0x8200020, 0x8208000, 32800, 0, 0x8008000, 0x200020, 0x8200000, 0x8208020, 32, 0x8000000, 0x208000, + 32800, 0x208020, 0x8008020, 0x8000020, 0x8200000, 32768, 0x208020, 0x200020, 0x8008000, 0x8208020, + 0x8000020, 0, 0x208000, 0x8000000, 0x200000, 0x8008020, 0x8200020, 0x200000, 32768, 0x8208000, 32, + 0x200000, 32768, 0x8000020, 0x8208020, 32800, 0x8000000, 0, 0x208000, 0x8200020, 0x8008020, + 0x8008000, 0x200020, 0x8208000, 32, 0x200020, 0x8008000, 0x8208020, 0x200000, 0x8200000, + 0x8000020, 0x208000, 32800, 0x8008020, 0x8200000, 32, 0x8208000, 0x208020, 0, 0x8000000, + 0x8200020, 32768, 0x208020 } }; + + /** + * Generates a crypt(3) compatible hash using the DES algorithm. + *

+ * As no salt is given, a random one will be used. + * + * @param original + * plaintext password + * @return a 13 character string starting with the salt string + */ + public static String crypt(final byte[] original) { + return crypt(original, null); + } + + /** + * Generates a crypt(3) compatible hash using the DES algorithm. + *

+ * Using unspecified characters as salt results incompatible hash values. + * + * @param original + * plaintext password + * @param salt + * a two character string drawn from [a-zA-Z0-9./] or null for a random one + * @return a 13 character string starting with the salt string + * @throws IllegalArgumentException + * if the salt does not match the allowed pattern + */ + public static String crypt(final byte[] original, String salt) { + if (salt == null) { + final Random randomGenerator = new Random(); + final int numSaltChars = SALT_CHARS.length; + salt = "" + SALT_CHARS[randomGenerator.nextInt(numSaltChars)] + + SALT_CHARS[randomGenerator.nextInt(numSaltChars)]; + } else if (!salt.matches("^[" + B64.B64T + "]{2,}$")) { + throw new IllegalArgumentException("Invalid salt value: " + salt); + } + + final StringBuilder buffer = new StringBuilder(" "); + final char charZero = salt.charAt(0); + final char charOne = salt.charAt(1); + buffer.setCharAt(0, charZero); + buffer.setCharAt(1, charOne); + final int eSwap0 = CON_SALT[charZero]; + final int eSwap1 = CON_SALT[charOne] << 4; + final byte key[] = new byte[8]; + for (int i = 0; i < key.length; i++) { + key[i] = 0; + } + + for (int i = 0; i < key.length && i < original.length; i++) { + final int iChar = original[i]; + key[i] = (byte) (iChar << 1); + } + + final int schedule[] = desSetKey(key); + final int out[] = body(schedule, eSwap0, eSwap1); + final byte b[] = new byte[9]; + intToFourBytes(out[0], b, 0); + intToFourBytes(out[1], b, 4); + b[8] = 0; + int i = 2; + int y = 0; + int u = 128; + for (; i < 13; i++) { + int j = 0; + int c = 0; + for (; j < 6; j++) { + c <<= 1; + if ((b[y] & u) != 0) { + c |= 0x1; + } + u >>>= 1; + if (u == 0) { + y++; + u = 128; + } + buffer.setCharAt(i, (char) COV2CHAR[c]); + } + } + return buffer.toString(); + } + + /** + * Generates a crypt(3) compatible hash using the DES algorithm. + *

+ * As no salt is given, a random one is used. + * + * @param original + * plaintext password + * @return a 13 character string starting with the salt string + */ + public static String crypt(final String original) { + return crypt(original.getBytes(Charsets.UTF_8)); + } + + /** + * Generates a crypt(3) compatible hash using the DES algorithm. + * + * @param original + * plaintext password + * @param salt + * a two character string drawn from [a-zA-Z0-9./] or null for a random one + * @return a 13 character string starting with the salt string + * @throws IllegalArgumentException + * if the salt does not match the allowed pattern + */ + public static String crypt(final String original, final String salt) { + return crypt(original.getBytes(Charsets.UTF_8), salt); + } + + private static int[] body(final int schedule[], final int eSwap0, final int eSwap1) { + int left = 0; + int right = 0; + int t = 0; + for (int j = 0; j < 25; j++) { + for (int i = 0; i < 32; i += 4) { + left = dEncrypt(left, right, i, eSwap0, eSwap1, schedule); + right = dEncrypt(right, left, i + 2, eSwap0, eSwap1, schedule); + } + t = left; + left = right; + right = t; + } + + t = right; + right = left >>> 1 | left << 31; + left = t >>> 1 | t << 31; + final int results[] = new int[2]; + permOp(right, left, 1, 0x55555555, results); + right = results[0]; + left = results[1]; + permOp(left, right, 8, 0xff00ff, results); + left = results[0]; + right = results[1]; + permOp(right, left, 2, 0x33333333, results); + right = results[0]; + left = results[1]; + permOp(left, right, 16, 65535, results); + left = results[0]; + right = results[1]; + permOp(right, left, 4, 0xf0f0f0f, results); + right = results[0]; + left = results[1]; + final int out[] = new int[2]; + out[0] = left; + out[1] = right; + return out; + } + + private static int byteToUnsigned(final byte b) { + final int value = b; + return value < 0 ? value + 256 : value; + } + + private static int dEncrypt(int el, final int r, final int s, final int e0, final int e1, final int sArr[]) { + int v = r ^ r >>> 16; + int u = v & e0; + v &= e1; + u = u ^ u << 16 ^ r ^ sArr[s]; + int t = v ^ v << 16 ^ r ^ sArr[s + 1]; + t = t >>> 4 | t << 28; + el ^= SPTRANS[1][t & 0x3f] | SPTRANS[3][t >>> 8 & 0x3f] | SPTRANS[5][t >>> 16 & 0x3f] | + SPTRANS[7][t >>> 24 & 0x3f] | SPTRANS[0][u & 0x3f] | SPTRANS[2][u >>> 8 & 0x3f] | + SPTRANS[4][u >>> 16 & 0x3f] | SPTRANS[6][u >>> 24 & 0x3f]; + return el; + } + + private static int[] desSetKey(final byte key[]) { + final int schedule[] = new int[32]; + int c = fourBytesToInt(key, 0); + int d = fourBytesToInt(key, 4); + final int results[] = new int[2]; + permOp(d, c, 4, 0xf0f0f0f, results); + d = results[0]; + c = results[1]; + c = hPermOp(c, -2, 0xcccc0000); + d = hPermOp(d, -2, 0xcccc0000); + permOp(d, c, 1, 0x55555555, results); + d = results[0]; + c = results[1]; + permOp(c, d, 8, 0xff00ff, results); + c = results[0]; + d = results[1]; + permOp(d, c, 1, 0x55555555, results); + d = results[0]; + c = results[1]; + d = (d & 0xff) << 16 | d & 0xff00 | (d & 0xff0000) >>> 16 | (c & 0xf0000000) >>> 4; + c &= 0xfffffff; + int j = 0; + for (int i = 0; i < 16; i++) { + if (SHIFT2[i]) { + c = c >>> 2 | c << 26; + d = d >>> 2 | d << 26; + } else { + c = c >>> 1 | c << 27; + d = d >>> 1 | d << 27; + } + c &= 0xfffffff; + d &= 0xfffffff; + int s = SKB[0][c & 0x3f] | SKB[1][c >>> 6 & 0x3 | c >>> 7 & 0x3c] | + SKB[2][c >>> 13 & 0xf | c >>> 14 & 0x30] | + SKB[3][c >>> 20 & 0x1 | c >>> 21 & 0x6 | c >>> 22 & 0x38]; + final int t = SKB[4][d & 0x3f] | SKB[5][d >>> 7 & 0x3 | d >>> 8 & 0x3c] | SKB[6][d >>> 15 & 0x3f] | + SKB[7][d >>> 21 & 0xf | d >>> 22 & 0x30]; + schedule[j++] = (t << 16 | s & 0xffff); + s = s >>> 16 | t & 0xffff0000; + s = s << 4 | s >>> 28; + schedule[j++] = s; + } + + return schedule; + } + + private static int fourBytesToInt(final byte b[], int offset) { + int value = byteToUnsigned(b[offset++]); + value |= byteToUnsigned(b[offset++]) << 8; + value |= byteToUnsigned(b[offset++]) << 16; + value |= byteToUnsigned(b[offset++]) << 24; + return value; + } + + private static int hPermOp(int a, final int n, final int m) { + final int t = (a << 16 - n ^ a) & m; + a = a ^ t ^ t >>> 16 - n; + return a; + } + + private static void intToFourBytes(final int iValue, final byte b[], int offset) { + b[offset++] = (byte) (iValue & 0xff); + b[offset++] = (byte) (iValue >>> 8 & 0xff); + b[offset++] = (byte) (iValue >>> 16 & 0xff); + b[offset++] = (byte) (iValue >>> 24 & 0xff); + } + + private static void permOp(int a, int b, final int n, final int m, final int results[]) { + final int t = (a >>> n ^ b) & m; + a ^= t << n; + b ^= t; + results[0] = a; + results[1] = b; + } + +} diff --git a/src/org/apache/commons/codec/digest/package.html b/src/org/apache/commons/codec/digest/package.html new file mode 100644 index 00000000..7ffeacd4 --- /dev/null +++ b/src/org/apache/commons/codec/digest/package.html @@ -0,0 +1,24 @@ + + + + Simplifies common {@link java.security.MessageDigest} tasks and + includes a libc crypt(3) compatible crypt method that supports DES, + MD5, SHA-256 and SHA-512 based algorithms as well as the Apache + specific "$apr1$" variant. + + diff --git a/src/org/apache/commons/codec/overview.html b/src/org/apache/commons/codec/overview.html new file mode 100644 index 00000000..fb10bca1 --- /dev/null +++ b/src/org/apache/commons/codec/overview.html @@ -0,0 +1,29 @@ + + + + +

+This document is the API specification for the Apache Commons Codec Library, version 1.3. +

+

+This library requires a JRE version of 1.2.2 or greater. +The hypertext links originating from this document point to Sun's version 1.3 API as the 1.2.2 API documentation +is no longer on-line. +

+ + diff --git a/src/org/apache/commons/codec/package.html b/src/org/apache/commons/codec/package.html new file mode 100644 index 00000000..4d04c845 --- /dev/null +++ b/src/org/apache/commons/codec/package.html @@ -0,0 +1,100 @@ + + + + + + +

Interfaces and classes used by + the various implementations in the sub-packages.

+ +

Definitive implementations of commonly used encoders and decoders.

+ +

Codec is currently comprised of a modest set of utilities and a + simple framework for String encoding and decoding in three categories: + Binary Encoders, Language Encoders, and Network Encoders.

+ +

Binary Encoders

+ + + + + + + + + + + + + + +
+ + org.apache.commons.codec.binary.Base64 + + Provides Base64 content-transfer-encoding as defined in + RFC 2045 + Production
+ + org.apache.commons.codec.binary.Hex + + Converts an array of bytes into an array of characters + representing the hexadecimal values of each byte in order + Production
+

+ Language Encoders +

+

+ Codec contains a number of commonly used language and phonetic + encoders +

+ + + + + + + + + + + + + +
+ org.apache.commons.codec.language.Soundex + Implementation of the Soundex algorithm.Production
+ org.apache.commons.codec.language.Metaphone + Implementation of the Metaphone algorithm.Production
+

Network Encoders

+

+

Codec contains network related encoders

+ + + + + + + + +
+ org.apache.commons.codec.net.URLCodec + Implements the 'www-form-urlencoded' encoding scheme.Production
+
+ + diff --git a/src/org/ohmage/cache/PreferenceCache.java b/src/org/ohmage/cache/PreferenceCache.java index 28e7eaa7..09a6a61a 100644 --- a/src/org/ohmage/cache/PreferenceCache.java +++ b/src/org/ohmage/cache/PreferenceCache.java @@ -157,6 +157,11 @@ public final class PreferenceCache extends KeyValueCache { // Local auth enabling. public static final String KEY_LOCAL_AUTH_ENABLED = "local_auth_enabled"; + + // SHA512 hash enabling (over the default blowfish option - + // useful for redhat based distros). + public static final String KEY_SHA512_PASSWORD_HASH_ENABLED = + "sha512_password_hash_enabled"; // The reference to one's self to return to requesters. private static PreferenceCache instance; diff --git a/src/org/ohmage/domain/ServerConfig.java b/src/org/ohmage/domain/ServerConfig.java index d7775028..2b761c74 100644 --- a/src/org/ohmage/domain/ServerConfig.java +++ b/src/org/ohmage/domain/ServerConfig.java @@ -103,6 +103,7 @@ public class ServerConfig { public static final boolean DEFAULT_SELF_REGISTRATION_ALLOWED = true; public static final boolean DEFAULT_KEYCLOAK_AUTH_ENABLED = false; public static final boolean DEFAULT_LOCAL_AUTH_ENABLED = true; + public static final boolean DEFAULT_SHA512_PASSWORD_HASH_ENABLED = false; private final String appName; @@ -119,6 +120,7 @@ public class ServerConfig { private final boolean userSetupEnabled; private final boolean keycloakAuthEnabled; private final boolean localAuthEnabled; + private final boolean sha512PasswordHashEnabled; private final String publicClassId; /** @@ -154,6 +156,7 @@ public ServerConfig( final boolean userSetupEnabled, final boolean keycloakAuthEnabled, final boolean localAuthEnabled, + final boolean sha512PasswordHashEnabled, final String publicClassId) throws DomainException { @@ -203,6 +206,7 @@ else if(StringUtils.isEmptyOrWhitespaceOnly(publicClassId)){ this.keycloakAuthEnabled = keycloakAuthEnabled; this.localAuthEnabled = localAuthEnabled; + this.sha512PasswordHashEnabled = sha512PasswordHashEnabled; this.publicClassId = publicClassId; } @@ -392,6 +396,8 @@ public ServerConfig( "The public class id was missing from the JSON.", e); } + + sha512PasswordHashEnabled = DEFAULT_SHA512_PASSWORD_HASH_ENABLED; } @@ -522,6 +528,15 @@ public final boolean getKeycloakAuthEnabled() { public final boolean getLocalAuthEnabled() { return localAuthEnabled; } + + /** + * Returns whether or not sha512 password hashing is enabled. + * + * @return Whether or not sha512 password hashing is enabled. + */ + public final boolean getSha512PasswordHashingEnabled() { + return sha512PasswordHashEnabled; + } /** * Returns the public class id of the server. diff --git a/src/org/ohmage/query/impl/AuthenticationQuery.java b/src/org/ohmage/query/impl/AuthenticationQuery.java index ff24c13c..707f9bdf 100644 --- a/src/org/ohmage/query/impl/AuthenticationQuery.java +++ b/src/org/ohmage/query/impl/AuthenticationQuery.java @@ -17,17 +17,22 @@ import java.sql.ResultSet; import java.sql.SQLException; +import org.apache.log4j.Logger; import javax.sql.DataSource; import jbcrypt.BCrypt; +import org.apache.commons.codec.digest.Crypt; import org.ohmage.annotator.Annotator.ErrorCode; import org.ohmage.domain.KeycloakUser; import org.ohmage.domain.User; import org.ohmage.exception.DataAccessException; +import org.ohmage.exception.ServiceException; import org.ohmage.query.IAuthenticationQuery; import org.ohmage.request.UserRequest; +import org.ohmage.service.ConfigServices; +import org.ohmage.service.UserServices; import org.springframework.jdbc.core.RowMapper; /** @@ -36,6 +41,8 @@ * @author John Jenkins */ public final class AuthenticationQuery extends Query implements IAuthenticationQuery{ + private static final Logger LOGGER = Logger.getLogger("UserServices"); + // Gets the user's hashed password from the database. private static final String SQL_GET_PASSWORD = "SELECT password " + @@ -105,7 +112,7 @@ public boolean getNewAccount() { private AuthenticationQuery(DataSource dataSource) { super(dataSource); - instance = this; + instance = this; } /* (non-Javadoc) @@ -115,7 +122,7 @@ private AuthenticationQuery(DataSource dataSource) { public UserInformation execute(UserRequest userRequest) throws DataAccessException { User user = userRequest.getUser(); String hashedPassword; - + // Hash the password if necessary. // SN: set password to fixed string if user is a keycloak user if(user instanceof KeycloakUser){ @@ -137,10 +144,21 @@ else if(user.hashPassword()) { if(actualPassword.equals(KeycloakUser.KEYCLOAK_USER_PASSWORD)){ userRequest.setFailed(ErrorCode.AUTHENTICATION_FAILED, "Unknown user or incorrect password."); return null; - } - hashedPassword = BCrypt.hashpw(user.getPassword(), actualPassword); + } + + if( ConfigServices.readServerConfiguration().getSha512PasswordHashingEnabled() ) { + hashedPassword = Crypt.crypt(user.getPassword(), actualPassword); + } + else { + hashedPassword = BCrypt.hashpw(user.getPassword(), actualPassword); + } + userRequest.getUser().setHashedPassword(hashedPassword); } + catch (ServiceException e) { + userRequest.setFailed(ErrorCode.AUTHENTICATION_FAILED, "Unexpected error occurred."); + return null; + } catch(org.springframework.dao.IncorrectResultSizeDataAccessException e) { // If there were multiple users with the same username, if(e.getActualSize() > 1) { @@ -160,7 +178,7 @@ else if(user.hashPassword()) { else { hashedPassword = user.getPassword(); } - + // Get the user's information from the database. try { UserInformation userInformation = instance.getJdbcTemplate().queryForObject( diff --git a/src/org/ohmage/service/ConfigServices.java b/src/org/ohmage/service/ConfigServices.java index 5256115f..f9ff2281 100644 --- a/src/org/ohmage/service/ConfigServices.java +++ b/src/org/ohmage/service/ConfigServices.java @@ -174,6 +174,25 @@ public static ServerConfig readServerConfiguration() catch(IllegalArgumentException e) { throw new ServiceException("The local auth config was not a valid boolean.", e); } + + boolean sha512PasswordHashEnabled; + try { + sha512PasswordHashEnabled = + StringUtils.decodeBoolean( + PreferenceCache.instance().lookup( + PreferenceCache.KEY_SHA512_PASSWORD_HASH_ENABLED)); + } + catch(CacheMissException e) { + sha512PasswordHashEnabled = + ServerConfig.DEFAULT_SHA512_PASSWORD_HASH_ENABLED; + LOGGER.warn("sha512_password_hash_enabled config is " + + "missing from the DB. Will default to " + + sha512PasswordHashEnabled); + } + catch(IllegalArgumentException e) { + throw new ServiceException("The sha512_password_hash_enabled " + + " was not a valid boolean.", e); + } String publicClassId; try { @@ -200,6 +219,7 @@ public static ServerConfig readServerConfiguration() userSetupEnabled, keycloakAuthEnabled, localAuthEnabled, + sha512PasswordHashEnabled, publicClassId); } catch(DomainException e) { diff --git a/src/org/ohmage/service/UserServices.java b/src/org/ohmage/service/UserServices.java index 01229ab4..b3fe33ed 100644 --- a/src/org/ohmage/service/UserServices.java +++ b/src/org/ohmage/service/UserServices.java @@ -48,6 +48,9 @@ import javax.mail.internet.MimeMessage; import com.sun.mail.smtp.SMTPTransport; +import org.apache.commons.codec.digest.Crypt; +import org.ohmage.service.ConfigServices; + import jbcrypt.BCrypt; import net.tanesha.recaptcha.ReCaptchaImpl; import net.tanesha.recaptcha.ReCaptchaResponse; @@ -121,7 +124,7 @@ public final class UserServices { private IUserClassQueries userClassQueries; private IUserImageQueries userImageQueries; private IImageQueries imageQueries; - + /** * Default constructor. Privately instantiated via dependency injection * (reflection). @@ -161,7 +164,7 @@ private UserServices(IUserQueries iUserQueries, userCampaignQueries = iUserCampaignQueries; userClassQueries = iUserClassQueries; userImageQueries = iUserImageQueries; - imageQueries = iImageQueries; + imageQueries = iImageQueries; } /** @@ -170,7 +173,7 @@ private UserServices(IUserQueries iUserQueries, public static UserServices instance() { return instance; } - + /** * Creates a new user. * @@ -211,8 +214,15 @@ public void createUser( hashedPassword = KeycloakUser.KEYCLOAK_USER_PASSWORD; } else { - hashedPassword = - BCrypt.hashpw(password, BCrypt.gensalt(User.BCRYPT_COMPLEXITY)); + if( ConfigServices.readServerConfiguration().getSha512PasswordHashingEnabled() ) { + hashedPassword = + Crypt.crypt(password); + } + else { + hashedPassword = + BCrypt.hashpw(password, BCrypt.gensalt(User.BCRYPT_COMPLEXITY)); + } + } userQueries @@ -276,8 +286,14 @@ public boolean createUser( hashedPassword = KeycloakUser.KEYCLOAK_USER_PASSWORD; } else { - hashedPassword = - BCrypt.hashpw(password, BCrypt.gensalt(User.BCRYPT_COMPLEXITY)); + if(ConfigServices.readServerConfiguration().getSha512PasswordHashingEnabled()) { + hashedPassword = + Crypt.crypt(password); + } + else { + hashedPassword = + BCrypt.hashpw(password, BCrypt.gensalt(User.BCRYPT_COMPLEXITY)); + } } return @@ -338,11 +354,17 @@ public void createUserRegistration( } String registrationId = buffer.toString(); + // Hash the password. - String hashedPassword = - BCrypt.hashpw( - password, - BCrypt.gensalt(User.BCRYPT_COMPLEXITY)); + String hashedPassword; + if(ConfigServices.readServerConfiguration().getSha512PasswordHashingEnabled()) { + hashedPassword = + Crypt.crypt(password); + } + else { + hashedPassword = + BCrypt.hashpw(password, BCrypt.gensalt(User.BCRYPT_COMPLEXITY)); + } // Create the user in the database with all of its connections. userQueries.createUserRegistration( @@ -2039,9 +2061,18 @@ public void resetPassword(final String username) } try { + String hashedPassword; + if(ConfigServices.readServerConfiguration().getSha512PasswordHashingEnabled()) { + hashedPassword = + Crypt.crypt(newPassword); + } + else { + hashedPassword = + BCrypt.hashpw(newPassword, BCrypt.gensalt(User.BCRYPT_COMPLEXITY)); + } userQueries.updateUserPassword( username, - BCrypt.hashpw(newPassword, BCrypt.gensalt(13)), + hashedPassword, true); } catch(DataAccessException e) { @@ -2194,15 +2225,19 @@ public String updatePassword(final String username, final String plaintextPassword) throws ServiceException { try { - String hashedPassword = - BCrypt - .hashpw( - plaintextPassword, - BCrypt.gensalt(User.BCRYPT_COMPLEXITY)); - - userQueries.updateUserPassword(username, hashedPassword, false); - - return hashedPassword; + String hashedPassword; + if(ConfigServices.readServerConfiguration().getSha512PasswordHashingEnabled()) { + hashedPassword = + Crypt.crypt(plaintextPassword); + } + else { + hashedPassword = + BCrypt.hashpw(plaintextPassword, BCrypt.gensalt(User.BCRYPT_COMPLEXITY)); + } + + userQueries.updateUserPassword(username, hashedPassword, false); + + return hashedPassword; } catch(DataAccessException e) { throw new ServiceException(e); From 01589b9085a7fbcbba750db27a0f44fa373f673a Mon Sep 17 00:00:00 2001 From: Kapeel Sable Date: Mon, 14 Nov 2016 12:16:24 -0800 Subject: [PATCH 2/2] Added commons-codec.jar as dependency, removed the commons-codec source files. Minor change to README SQL statements. --- README.md | 4 +- build.xml | 4 +- lib/commons-codec-1.10.jar | Bin 0 -> 284184 bytes .../apache/commons/codec/BinaryDecoder.java | 38 - .../apache/commons/codec/BinaryEncoder.java | 38 - .../apache/commons/codec/CharEncoding.java | 113 --- src/org/apache/commons/codec/Charsets.java | 156 ---- src/org/apache/commons/codec/Decoder.java | 47 - .../commons/codec/DecoderException.java | 86 -- src/org/apache/commons/codec/Encoder.java | 44 - .../commons/codec/EncoderException.java | 89 -- .../apache/commons/codec/StringDecoder.java | 38 - .../apache/commons/codec/StringEncoder.java | 38 - .../codec/StringEncoderComparator.java | 91 -- .../apache/commons/codec/binary/Base32.java | 539 ------------ .../codec/binary/Base32InputStream.java | 85 -- .../codec/binary/Base32OutputStream.java | 89 -- .../apache/commons/codec/binary/Base64.java | 784 ----------------- .../codec/binary/Base64InputStream.java | 88 -- .../codec/binary/Base64OutputStream.java | 92 -- .../commons/codec/binary/BaseNCodec.java | 525 ----------- .../codec/binary/BaseNCodecInputStream.java | 211 ----- .../codec/binary/BaseNCodecOutputStream.java | 153 ---- .../commons/codec/binary/BinaryCodec.java | 301 ------- .../codec/binary/CharSequenceUtils.java | 79 -- src/org/apache/commons/codec/binary/Hex.java | 334 ------- .../commons/codec/binary/StringUtils.java | 386 --------- .../apache/commons/codec/binary/package.html | 21 - src/org/apache/commons/codec/digest/B64.java | 79 -- .../apache/commons/codec/digest/Crypt.java | 151 ---- .../commons/codec/digest/DigestUtils.java | 819 ------------------ .../commons/codec/digest/HmacAlgorithms.java | 94 -- .../commons/codec/digest/HmacUtils.java | 794 ----------------- .../apache/commons/codec/digest/Md5Crypt.java | 302 ------- .../codec/digest/MessageDigestAlgorithms.java | 71 -- .../commons/codec/digest/Sha2Crypt.java | 545 ------------ .../commons/codec/digest/UnixCrypt.java | 413 --------- .../apache/commons/codec/digest/package.html | 24 - src/org/apache/commons/codec/overview.html | 29 - src/org/apache/commons/codec/package.html | 100 --- 40 files changed, 5 insertions(+), 7889 deletions(-) create mode 100644 lib/commons-codec-1.10.jar delete mode 100644 src/org/apache/commons/codec/BinaryDecoder.java delete mode 100644 src/org/apache/commons/codec/BinaryEncoder.java delete mode 100644 src/org/apache/commons/codec/CharEncoding.java delete mode 100644 src/org/apache/commons/codec/Charsets.java delete mode 100644 src/org/apache/commons/codec/Decoder.java delete mode 100644 src/org/apache/commons/codec/DecoderException.java delete mode 100644 src/org/apache/commons/codec/Encoder.java delete mode 100644 src/org/apache/commons/codec/EncoderException.java delete mode 100644 src/org/apache/commons/codec/StringDecoder.java delete mode 100644 src/org/apache/commons/codec/StringEncoder.java delete mode 100644 src/org/apache/commons/codec/StringEncoderComparator.java delete mode 100644 src/org/apache/commons/codec/binary/Base32.java delete mode 100644 src/org/apache/commons/codec/binary/Base32InputStream.java delete mode 100644 src/org/apache/commons/codec/binary/Base32OutputStream.java delete mode 100644 src/org/apache/commons/codec/binary/Base64.java delete mode 100644 src/org/apache/commons/codec/binary/Base64InputStream.java delete mode 100644 src/org/apache/commons/codec/binary/Base64OutputStream.java delete mode 100644 src/org/apache/commons/codec/binary/BaseNCodec.java delete mode 100644 src/org/apache/commons/codec/binary/BaseNCodecInputStream.java delete mode 100644 src/org/apache/commons/codec/binary/BaseNCodecOutputStream.java delete mode 100644 src/org/apache/commons/codec/binary/BinaryCodec.java delete mode 100644 src/org/apache/commons/codec/binary/CharSequenceUtils.java delete mode 100644 src/org/apache/commons/codec/binary/Hex.java delete mode 100644 src/org/apache/commons/codec/binary/StringUtils.java delete mode 100644 src/org/apache/commons/codec/binary/package.html delete mode 100644 src/org/apache/commons/codec/digest/B64.java delete mode 100644 src/org/apache/commons/codec/digest/Crypt.java delete mode 100644 src/org/apache/commons/codec/digest/DigestUtils.java delete mode 100644 src/org/apache/commons/codec/digest/HmacAlgorithms.java delete mode 100644 src/org/apache/commons/codec/digest/HmacUtils.java delete mode 100644 src/org/apache/commons/codec/digest/Md5Crypt.java delete mode 100644 src/org/apache/commons/codec/digest/MessageDigestAlgorithms.java delete mode 100644 src/org/apache/commons/codec/digest/Sha2Crypt.java delete mode 100644 src/org/apache/commons/codec/digest/UnixCrypt.java delete mode 100644 src/org/apache/commons/codec/digest/package.html delete mode 100644 src/org/apache/commons/codec/overview.html delete mode 100644 src/org/apache/commons/codec/package.html diff --git a/README.md b/README.md index cf40d899..486aea49 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ To use SHA-512, run the following commands in MySQL console as soon as ohmage is --Enables SHA-512 password hashing UPDATE `ohmage`.`preference` SET `p_value` = 'true' WHERE `p_key` = 'sha512_password_hash_enabled'; --Updates the default SHA512 password hash for `ohmage.admin` user to `ohmage.passwd` -UPDATE `ohmage`.`user` SET `password` = '$6$Afmg23YTsd$113jh7VsD6q6wDnDWD9SqJUzobqjFIuGMhpgpuXM49acjyjFfWOGAhzT7W7zRleIhN2Xe.xH7ki2bk8nBlsX4/' WHERE `username` = 'ohmage.admin' +UPDATE `ohmage`.`user` SET `password` = '$6$Afmg23YTsd$113jh7VsD6q6wDnDWD9SqJUzobqjFIuGMhpgpuXM49acjyjFfWOGAhzT7W7zRleIhN2Xe.xH7ki2bk8nBlsX4/' WHERE `username` = 'ohmage.admin'; ``` To use blowfish, run the following commands: @@ -59,7 +59,7 @@ To use blowfish, run the following commands: --Disables SHA-512 password hashing (thus enabling default blowfish) UPDATE `ohmage`.`preference` SET `p_value` = 'false' WHERE `p_key` = 'sha512_password_hash_enabled'; --Updates the default blowfish password hash for `ohmage.admin` user to `ohmage.passwd` -UPDATE `ohmage`.`user` SET `password` = '$2a$13$yxus2tQ3/QiOwWcELImOQuy9d5PXWbByQ6Bhp52b1se7fNYGFxN5i' WHERE `username` = 'ohmage.admin' +UPDATE `ohmage`.`user` SET `password` = '$2a$13$yxus2tQ3/QiOwWcELImOQuy9d5PXWbByQ6Bhp52b1se7fNYGFxN5i' WHERE `username` = 'ohmage.admin'; ``` # Collaboration diff --git a/build.xml b/build.xml index 143c82a8..6fd575e5 100644 --- a/build.xml +++ b/build.xml @@ -13,6 +13,7 @@ + @@ -45,6 +46,7 @@ + @@ -125,7 +127,7 @@ - + 4NbW-x-42u5&g9KXo2ZpIKRCDvs z^$j^7Ak_a8CMP5>B`&6-N-rl~t*c|V!Hw+8vHK(VXf$VRvL?UKB#C6Wk%RlYL$1}L z0pdfQ64}I>L`GYB{OI$2Am27glnOUND1f}>c%gL9zNV@6leDAIqy9apfmb?_rQwNv zVrWPurNQ`V0O9%zkB@|E!^8I&!s{IDY%h+S>qnrdZzN3m@^fifsaA(x(yiPL zhm7AVCpC^9p%CwcjQ)g^8@WFBQd-{a9v#@)8xyFbFP zSoG+>JNI8^i0K^;8u94V4g5YZR^Q9_=hma^wqOaQH1yayBa*d$8DKuaU_+CX)wbOe zSO`;V-ILI}DG{^u$jhKB{_*g^@KIL6t?3SZA^hIn`1I^aW-KZNM*wntotlZPWIc>k zcoVi$Xt{|I=;Gt|d3hd?qH$N_i9_FQ^SR*2fS?_RzH1PzDTPc>;|~R;G6)Fo$&#oe z49-N6GJL|kON_gf*?Y;_ERO1(37RQrLiHo~42(?f%X#H`yF7V(6dnCcp+rlsu z!X9Bo)Y=83%5$7j>##P{;l}IcaqzVST2t35_bo0!;DH^qJCK zww_e}jJSO#V$_{fNyZ)ubn$`=bjk*{!JeH{Ooa&jv1zQiUhuD_xgF-Iu|u#H*!j6< znch1~&E<944!#+9$O5x=|7NS4yJv%sK2Y_?y@He}>VOoT;fi6y018f1Guc1mZKOP& zF1uG8)aSxYIFKx}kY-&QQtE#3kBvO?+~>qA`<2f#{1RZo4_ERl8}DcvL>=dHZ79$o z3mrwlT)_^@$Js)F_{5U&&UxH)WQ&$JYT_xu5Ka<6g-I}Sr^`tgvtW!pXGCRiIT~hh z6o@zyuQT?>>5v7_D#U<20~rXs9}V;Ii}4oc^;K;l zt{@HNW3Mjm3iaSMycm3D2}}b*Ts?%32F^pH~yv8g#19+g0yMvJl+-jV5e0gDR(5K>Vz*idRVnkG@`}(c}t~Y0O$Wh%%xB`^#0BM@~ zz22tN0EJAkPXHOo2=9h>w-~6`FvPre7mP>DYZ7AWol#Rl3h*}9uI9oo!BLdD3AHQg z_V;n3SmsiaU&5P0RuJF#9{jyR4HovEZnpcoS%{z6J-c2Ze#=I)f#qnu4yp-NQI^|> zMBF%7pTnrp_^U7%Kp)nWRAK6RrNEv!#{~pcT+^Dm5!JS@rx4c3z8$+2H;zOKX~scN z%ebWsn_wDAKM(@7q(QLDy`?DE`)ur(LR);~jS?%}vq#p!NA6eQ2p#uV7p0Lawy(Pv z`ceXa-J{`R#(k_5pM-gBhKk*;XFhc%l;M|U_UD0(&ji1gCQM+@!YxBA*XZW39=-6+APg>Al0^OZUlX;_Q$hIQ z@U;aHAtw4$U`HP%o-(aZ#;itcU9QY5>AZ`@-J@1t9&iC#iz%3J^nAiV&zKs@rYRF{>Me zLBkA{REh9q9#;ju%S4w6StPAsnN=&6$|4FESG)L7|G^;Z96>?;DuWV6-j576<@p^Z zL4R2aQPMNA?$P%O2^tX>K0z%!irP}CLJAx7>ASh3xR-xv>5)Wgu=syGsIvbMmZm z5h8b`^P>`(Y4+?WAoMG&LV;#&mb#Xpx>HDdXW9wwJ>=oSHfgc-fg#a1pBvuwK^SGL z*B;>>F41?dh=4`v)LKg8&Aam&{H#k<%yjGe4U_a?{xklG^lIoKeT?{pnVb4tC91T*Dhc1P+BJpH3Pl@R1JL#|l^zjOw7IBkH z)>}NVivh4-kzfTwRSYWSJCwKtbcVY&3mr0tBH7?c6nkQ)HpTIhoFfa~pd2 zx3m~R`NlOTuDYEkI{|TPe4n@Zb`>e-8a4WRZdwd6mq%rO2C122)^(Q%k#*92uGSkT%m^39PioCl&(OUU;}F4^+6`pFBkJO}teWv!F_(Si{`&h?y9K z`uB8fg6`h}o(CfC^eDn2nlXhZAv~~T6!8Sc7_vnDlukVm-^{**r~r<#>cN+e&Dj!N15IuMN6)>zlIN;Son0u`~Bns2#C zhJQ^96g~=^Yr!feTkSd5eOkmVYQQSKJmQejTnV5^=Lpu)&ts!|AMxlxc$&@$p5B%4 zP=5cEB?og4HINbA8upln(*3z+A`Ulbk@x~N)bPu{_=%Ax-%qs}mj{0kc4zoHpi+8Q}}{QqT}TABl#oc{%z z_+LHyw~?*6i;?;N`HlYXS0?*!@r`Z%MHle?)h%oR|Ji%U|2f#{Lk3b_2tYuTbpMwd zkd+bqg6|5TUn41~I76CB6)QK#r7p|@^yLcZaCw=aC?#go~s;gF{yXKnDv)}j2!G08% zEf*qd>Gf6j?HUM*w<2rxyI$W9DwhZ9eBM6aY+F0yyk|q|D^IcoCi#B$-MSvX-=9l) z9NOEEYi-dr9zNct_T{^}+tF^WYiJz2zMh|t&$nnib?u6I zw=RAmcSPQz3N+d`rrTSuxfmCkGJtQoE4m&8U*afbcHdoyT32CaR|Ko+SZCCijHqo! z>f1KqRO!b&LJ(zJoT@H`AL(zFuixC0!PlATRu8rIH@%7-@TtV&EFp{(V0n@1*tB?6 zYFr4V>D5;Gt7=w+{f#@QV^{ylREWaK)@H6cBDB9Am#(_h_*9-95(RHg$Zk8{iao^i z>L4=^g3@~6S3*1mz8Us`-G&slI{G}Dy)cy1fU;EkEH>$*=|JMc`v9M?YFta>w?*4V z<>9OP68|2Bxm(vvG38mW4)`%WB441;nd)a}>5fJ*f8VTS;{nj5&Gl=OhF*X5&@f>t z$!O+oM{d0K)T39ej_TK-YQ@XDn*4zUCWas9$pftFVRNo4HUx*@izyW51T{wMV2;d^2vT#X5XA;YLQ2Wc zS`>>z=+Ywt4-Z3i?2jZ9LI;*52aUAAAD%J^$tt`t;5CkQE>}NSE&}A=*(myMVIbCn zr295br_MT++5kHC-JX`JR|MzLVOH#yL|nkwd`E3(JX&BG>R&9;QjQEzf1zy4Fg%Zp z0~DhyaPjcIQJx{6PaL)2VD(z@35H(Y>EJbUMKX%Q> zZr|3NIDZ;P#hfzi$JOKYP4t&NC@K%HX*pKT=#8V~DZf4$M6iZo+lDy1U@Am8Px~b? zIjX%H(sTmHGILwQ%KNCHG~WsX@0n`;5H;^Pj>e2P`<#w6Q>cKO8Bbq z@&n>=oYT&@B$}lf9V1L5Edt`_paxf<$kYyH{dq$7mxjXI0;Z^y?8+zpYC4u{07(w- z#OQ{K4HC+CgsLvG%~0HuR2&!l=-L4JmSi@HxC+?tEQDNHGO~Q4w&RDfr&MTYoBU`B$>|d&U?!Q?jG-xB3Q7BD zwF(hVZIHN{QB;|=dXXzyy36!}C?CdvJpVKceE=Z720RrcEPA(sVnH>u@gDUvwHR3X zJVxD(FmJVH)cg-q`(K>JVYxg!DG${oT`T$gJ+vFJBn-Y%siLYyZ?*pMK`iLx#OvNa z7jsMqyCfV@`2kMd=CHI4DV|XgY5E-8GkvYJyHYfg-h%0`R)io z61uJ5(B`K!3z8zO8Is#TCK7d3#8X`RVGtZ%?c1Or63JT%;Ag%snuy2lD&KE+?7S+| zWaEpPsUYWt>0ioTRP69;?IKVe>rA4Sw^%31$WByus%t^_M};9OR?(-8-<`}ZjA8!^ zy@s3yIiD~Nr0>>7xHYCRN`!F$o&zgC1Y>?ViqC|gAMPPz8z(~v@ksn5UxM{G4nw4o z@IBd!VfE9PHNlM1I^1qv#nvHZBW|qq?Z)Ykn!VYsgy6g(U+wMczg+MNX3pt9UMd)y z7=p1CqWyQZY?^$1BvY*NciMt6!OThteY%AjEkI;(aGrETM=~tlDQnINsX~YC(=+&~L2$R8AYv+j$X zUhEfALK+W8dfKy-)0PC55u>}iY=y?NRe(d)4G7>|?g@kKmv`)F0#E+oEZ*nG82@2VAt~{t-qN^);<+o zrnvI8?n4yW6PC;)Ge6!cFR*~`9Sr}fkcOIs7pF=j->Cx){F&vNXUMGHq)DkkZb^bm zEn5Rwk~%_GmpUg+{Qzw;>AH2>(^1VW+;Yrah~O{RbLIIxPjyy?m>C7ypMl$JsL1&Y zKvfl|YV2=JDfvFiN!ss3KBc&3|MO@mA#=)Jh;X#1HXSztfvJzg_F|OCrxzcqL>l-RM=D*9Bh5<8VFI zeKJ6hXdZLuIT1@xnySI_bSDM6c%E&-rr=RXCKm-YnIj;GjeKQaIjVzHuJGM#Q#66+ zE-Re1+dWF0FOX~5@iIgZjRGXn%Yd?lSEKGAp2YLW+gsdGS*ab}C}l$A*9T>^M zzDNhz>!eErDf2lznss9Wn-H|4U)dh3X!nEmTCS}>l@;`i%;-(GxIJN|9da2y?{Mg5 zd1{urgX!j$-h2{9UHkKK_b@Og;zdxU*hdlys3^sNex)4$*JT>jbgOc-b0PRZpKx^g zeluN;G!V$nC_=mRc=oa|w! zr^BBYMH0C+hz#6d10wZX^B+g&T}o%iblqd^r;W0Kt_bArAWO=lM$10f$x!ekQUqr> zGv$MXKEig*eEM6Z8;)UhR)bL#3P%yu6$ARgWLI6+xy?4Tn?CjYET4#Q(ksUs8pE3H za$Xw$fT=Nn!gZC}n*A?0-IV93!Q$lsrD&aQ`RKVn$us3Dp{9(TT__Ko>961Sw!W#V zpT5372UDG1ci;EV&yEfPJ>4iAtw$Wd9JND7`{s8qZ~M?$k{?))By9S64p>u~3#fFf zxC)y=$w-~gl!;n;y3%VXqcG9iGdDZB8j9chYuQUm(IJSMP97tsiu936yCqnPmo!;P9GD}2_jnyy2bJ121Ov=ar^I*zMZSwWwQ^_ ztZqpsTry(eqruqgH%FJ8l?5jbR-oQ^z*^N<4;UbJ!|Ce`1sVu54tR!HL18<^cT3pu zNlQc9o)W#r@3~SJR^RIs&5~oFT*QAwU;2u`R^{K}%@yE}3w50zrrlTcuofO&FAis?47;F=; zpBp@8d2?(AUP!}3^TJ=t#a0Ex2U;=q8%j)tD#G1m!wil^^+88J*c`qrAMxR+a&hm( z`n7AL@4nP*^W`PJH)p{5tkFndcVhubKGmOME!}LOW$b)D*UFEpU5=fU-Ha3T zP0eW=ycC4Q8J8#AZOB9NloQT@OR7nq7OtuSZ`7<*(aqYIS+%1(Yma(d?oz0HvW9IU zxcDd?X1@V{(&3H*!ka{5BKJ3AF{F}ma293k<~%|6NV!i zFGeOXcrBUS`Kwx8?*P;hVt=PJh)hlrZPWRAqP8(AY;zZK%$@)*a*jPIN{r5zXu3xB zXXNon7^}(QV<#7;=Cys&C6CY^S`|zxK~V|S<8Kz6%4Hj0+&5FSQ9IMF7?86e;KYLb zcn4l?HTdSU;f1IT_fE&4j)`Jx*c6YVQzluUaPRGR!qD6}*5Sc1XTU?${iyRbcK0Ze z&|V0)A3EY9#z}YiLSiP_{IYdMhD7bC)1<-k>zmqVe}l+!5YtVqkRF!2lgWCD)8<2*!^K3&-5SMIy% zGaTm6{`EW7Abcc$*ot3NQE=^ndv1Y>+b10Q6y-Puzx2kB#5Dd`$I5oLG73?_0t-kW z$e!z$Rq#guH=*@om;v$dvW(f=^{W-- z2TbI!t}9+Y@+0uWF%mqCoVCw6tXTAHx_*qhFmnk$VZVBVP>b-vVYf$XGt&IQrSCp0 zd&?!CFX776B)5@OO%vyMNinb+c&DNOHom;M5bZ6y2;yk-or}w>dHh)R#5)6>%8vGs z=T?E8{F_z9NH2C-$7UH+nJnNLGZYG9K6ii7!;^T})FiL5OL^xm)W*cv&^e)Q4vV|P?zu9~Rki9i6>sAA#f{PM=& zS*IOkkrfqlA1ru8tsscg_Vn6*5m>^;UX{YKUQhznpCxoZzT7S|1Z0DH4;*TjXpJ;7 ztD#(;F96XQKcwo?kwje+op)GwS&go14GV)_Neh3&%=I^s622al%gE3A?w~p-==I9g zY3l{v^MS$c6!145F5g%0tV(F?p_K>NjO>_nogH*eO8)vW#(xgn#6Pi?HScGBI6M$g z4gnAl?Y|0W{v)3fHgW>6Fw>j-Hga-0()4mqT1xlZxnXXV?I$t?PLvvjgd9VZlqCEW zXh9B&iJTxPOeh4KI3|IaJz3-=q(rl{U(BIHvqVf+(2}iJtsMtcFn_VU)uLn5`f+(q z4`{tQe_kt{S+BmT5eMwNOr|ml(l_~hRzC3V-0+-q{j=YHWWQwD`QG-Ou>dK;&F~m9 z4Pqg~O7RFqsCeNjLUA}fqlf;5ZDrmZw&xj;sJ0J7d*&iU;5Q56dGo;-7_q6#-Erd~ z#JDyxf8pNce+`D?w+vEnm{4Hx9{%+*V9xc#Rr3HeaAI?thv(Y+i~bupm3MG046A$8 zUNLO)rV9p3NSdL zBaX?OC^M#dWZ zM9|&eO6^et40fi(f5n)lyM&z6nBAdqg3KUfV>Y;M5dh%sK zM_K2l?vqfJoEPS0=4~-XWCPHeF^ng`WC4 zJ{~o(ivRV0<=(U85i_Khs3XYzv*KIJtK*L-tcu!wa$M|P0B?=IM2t7_;E6JvN z;37%lnBGMp{vxH*m3@#T|B>r9(O+(sb=ORW6*C(%5sl3ei+)*vr#PxVj1x0Ty^$%2 zU4-w&h>vg$Hx&AeOtn^MbI~~mNp=BpfI6lrwc$-eQyG*jb~hV-RRAAlL5#-n$^2;w zAA$z()8;)mn6$Er@{D{8Nz^g9%f#Lnk7FMOkGaIPxfd>X&w6GjAVKxrrRGkdIa?hM zA}$Lg6Fkq)0%l7x$VfI2e_NNiL^Y$-8#EH_*2uW=eE2)=ZIHNR%Xat>TI9?RLHuUR z_~6U3L%AbwCDIpSPj{4}tW(rFRGI_IOYKul;yjD9t2Da@S;e8FLkN=R{8wd8zU3g4bGP+B0N>x8^0^92`#CvW=Z z6!qpLh|`q$&7S_9F-EY1e|cH!j_bAqj^i!S@nA+B)83eQ%{c_3R&+2`asckM?3#Om zX+a^>vv^l|_)4L5FzLEU^vA?4yQCA{DGNTgR2a`~+fK2QqY*rV?o@ppOz?O;6 zE#7?UQJ1;qJ$j zBO-YwxPwS3=XvZw2nmg-nF%3)_?1&q#6diH?gVI}87feogGrO!xTp?Ve< z*pTHY2@g&@Ek84=<=24XzLO<|;WJ6x!MwcCnb7Ew?fu`&SPc1Om=!Skj20Z|S&+uM zH2P99#h7Bn5dE?NF2z`k<*xivb zd**GhzA-Kd!Ang^L8t;P{p`CiKkZE1)6sJdwox%o?RO zzO>=)ITh05Y3RB~BXJdMP5e6;=D351ih) zJM=Pg)l&c-(9%t8H%5gGs^205j0}l?*IQNqojjbd8ZrkYxBeUF%N?-;pZL*f*$fwu zH#T5(hC4n8&N1d$Z0Sm}W=(R&taKkPvVQjbjm*-?xP{+o4TRS79kkxc$l-Pq)g$=` zc~SVw^FVwGiE^`v&KIdqMX@o&(X~+~=*uwA=2K#g+ru#I@>?)09h;oTWWPP<$nS~C z?;HVonv?XwM_#7W;b&*<$E_Wv{0w-XU1d$5-F$%Px5f;qcbdwR}0wpR0bwSY)2zGiq?^fIy zzVUBV?(a~f$$XWec1x4cKcnWOUue8;8^?oQxsEKo1>eB=%eulLl2&eE#tCkTT)_FO z==&Ke)Z8U)1_qcTt_p$*Aml~jh*P3to^pi=#As&+Ymoqox^qV!&gzgZE7$rprufpT&Sx#uKUrKtL zfz8c*IMouIxe&F=c>c(?bU8K7k7LLzpZX9z!(+=vNqhQ>l6K;~DrR|93^h#ZNKVk0 zW-PycXm%0Cc*NSxHfLxq%aq$Pj$IaTE|mbr@-Xm z%?k=M=S^k3)n>obP< ztfRV)9J+2Cnk`ULA+^Am(w?M}9zvCkA{wVhA-0w%>v-%$MB!z(rFGu|te@aTrnf;t0R$wZZg2#P03P9({b=GIzt)9_;kn-QJv< zTtaYy_=ShOzGy}Y4AXmqsk~C-_vRX6`$d%A5scomd~$qBe3E@97VHbqINh-oBN=GC z-x?(G+!-W;xT{MY%p@im=y65%C~l7!RFJ*WJYjz#xIlF0Z^zhGgxv}(ncuFQ zD}6FNseMMj!+hF3wZ5j59N0Aje`d3z>@0V^dQ}7e_cc{&sa0icCf_eb{%H?EuRZs@^g9^l8`vrfd#MwR>XJR8g~N3LuM{t@-w1ucHy~O#>|8HeVGkvRuT=m zX;aGn3x`IQ9zy~D~ zr4q#&VFwweO~=hJ2Ic$r4JnQ?CK{m!W#)(X4Jb}3_A_SYC-qI(qYdDOw!=PP)|jQu z)cNN})AtCA42rq-D21Qyekhp?z zLK5qe&yZ}0W9#%}Nj6DA_sp2G>qHJwTqCf8bwqy|rP?FeqJ;!+49W^?>-XdDk%wmY z(@@zNj(-i-TaYFUVH<0)B25|7JV->EYNAE+szFJ zN4lEn{x%*TGCS6FW;`oM)WXn7bDbONimaCHS{B+G5$l&w>gt>cw9D2tb=i4>9|EP3QjZ>o^A)^D>A{#=*+LOfEWg{BYBO2r*9NSTkpsB{esib>Q0fneX z*i_@>RO8LcNf$+wD>6#pMU)(pO6)R9Es{#MNd>=?3w|dS{7x;{Bo%Adif9mtXi$l8 zj6?-s$n|=veoJ)8iyMC`92Y=Caup=o1Zw$# z_)kasZ>tuJVgo{l|A`{}<~BJ&PXR>;HFRgqw$lad zynt{hkkEpfw8me5OhOq%DiIE9IZm>+WJ8LlWXrNe2gNT%Lyq+h;9gILQ0xp zI*ZF^#);qTNB8Fw&X9mX5A}9ZXecCv4Fi6;`DL>&G)OMAKR?RUak5SsuKV6@Gj+UH zB1mao(zx$~K;M9Ql|W60F$oy!&x2AA+5Sw|QsJm^p73_(Vg2F@tp$Z1mnkQt>gL^> zf0*80SjCRUT-VtYeovB5>a0KN01zX3?r}Y!M<3%T1oSs$P-qvR2W4341(?vN`e$L$%3QO!o`W-o%mvsP3uc zve52{!20TfUNWqD)9woOz?t6+IQ!>t|5Y04L7PXT|45_qA8By@x22)r;{5+;!%ksA z5p_6s!<7cz9u*Cf886RF*f^2P2V{;D1WF2oiG`)Nvh&)ka?LZfhV9Pe&I*d$8;C#Y zZgay7YCNGG<0QxHIcw+5$?N0e9b6EP0T1P^TPq`Kz9@Mx5CK8I6{~8dfd*6B7xT}8^D5Gv#x|Cs=~C)1f+i2#3NQSo z1Yb@wi&y4r5AVUC?pt6k+;Iz8a}7{^ihsnhV0~ZjuG6Cu-Irw9U+3z_0-MRQ-lgb6 z?|h_$daAz$CIU!#K5QsIoc2Gy6I+ydPqOc?s{*ED+GkQQoix;Cms(1�@`CoND>D zo3T@kCZp{IxU=nyO8+gKtYK!rJpbvT4t;qb&8{egxhB_U`&)g0b&-Ct#cbR1Y%Irs zy75@PrYL~&i0Ll4oWCK_f#baH^|1_k8KDajX^MwutGLcX8{e8TRu>h9ZBiJF7D$sS$!MpE<wRnF* zng#(vSOJdkAIOtxVXq331XR?rSc>%tVZD6&FeUUDH`o3O0Iz6U6&o@P*>jRwNP_s# z=Fln9>g2%bnP?MJ6CUxP8GY4(FanL9-Qc`q=NI#E8SVxjEcXcoal%qt##m7kBBV)osR)_o#e4plL(p+|6`GqkP zi4q_sww02IhFktMc_k=1H*L-83AW zew6);=DnnV;T-Hj8lzwlszSKyow%4{;_Ts2NoHUj^XRZ6q~;y~zz-vYA`&~3zH z-$<9g*!)Rs$EOi=JM^u{w5y->ouuA7d9;MK8StI@+45B(BXhP!lYnj7&J?#9nLF89 zQqO*ch$KBnc9coDNN~3#%H>0>K4@aOE0-(|6|2`vFPA&Zo0oM8Y3*!kwy5XC&{-Ld z4M|Tf=oWr^pu++%QVwkXS|s4JSZ5H8Bq@ct*qLL{VAlznQ!Xi~qgl3|uTpg^o)@Na zwO8qjxG4?r=%@*qRWl@KW=7~>xmFdOCnzsC=hvzo3v0?1VWaGQo603{U~i%fTQ*{Q zkc@R`0-7pG7RPJ$wUJ}A`$iWq6)5_LBf_C=N~X*j`1Q3F==3WJle4SNQu0h8Q3s$9CNhs~M? z;|k%~&kjuu?jw!4q1=d(FZ(~reiZ|^BMapL8C!SGO;bBo_H4Lg1pa2iD6AgX>!HRK zA`KIe5-k!@>;kUpMJ!uq<~w&IttQw!r}7LgjrUv5rFlvs?G>r`yB+l)DcXrK;wwWW zW%8z2)b?}Fw+Bg)#*-UFuRniE&R1_B17mVKq{?$0==gh{4EGA|4&QkHs zG@QGo`(H>_s)nP4yr#P&qNeI+Ii};MqL^y(NHK0inqZBc)izs3!77m#p_Izv^;maT zj0s2EaW;bqXTeIv@`fz146vUL6kH?1IvToSRD-S2cZ1X8!ASeU>|RVYv>J>Gww&3L zgoC$i(s<9?t|Y|;IhJms-O+gQNpq0Zc}1c&)%X1^-ygBuWg7Q_G_|a8I2})JeP|GF z>qh0A2&O~6UO+7QcWoF9Bh^e{5oHM#l8S7O*h_>4naKtLpG#VA))uf~+C`C^yybR( zpY&{eR!o}>WIS;D;-(KItGeNqF#_a$G8b3E_#vxz#Sf1QYIir>W|t-QwaBbdPWw_% zEgffB7Ne3@ev4&V!gSN_amxs^t*LDo32FV{T6Xg{z@a;#?_eaS0XLSDg{tVBc|sZo z(53WBHE@W7+dg82xWVT=U_PDJVIX&=-TUbNbq6ZkGesWgFG)TZN)3>GCVs|sWD+~ zSVZTQCb20lRhTW)r-nY)rBIxc;Q~n~#6DWvh*9#bGZkphEezCmHV;XqRca9${_#4go+x{B>iepO<59M5Uf3@8383gHss!+BwL^}WZI!8Tl^vF zkB_#HRDW!IFVyCiwr*&cMr6`5fYpfCGC548Z;pc{Sxd44_L{gwm=bVZRc`j6KBY@YskK@#}XeB5V#z{rvgT4`v?glAS zhUtUR=^E{GAw!d2I;*+*o<1vywdzYUe5N4c6l5HaHMkR zm8jpSHy_JN2>i1qbl!HeSZ{>wX?`FTiIPtP|0be2MrfTD)A1CFVk~zJ^k9kH-z8=N zv>A*%M@f&IYT}4zW7Yv#=NWP8us|xzB4Qmbq{N16>Z2c5;>f*BpJ`ZqZ`3V`mps(G z8UkGy&TQxSl3QGCA@aqlpi6jWg@E{<>`rP#WXLw7Zz;Q0WOns?$)|mmtLg7OZKQ6= zy4R7_NphuHHgg!iVHnt_Qj2?*w44rSt!boiC!Egi)-$)4bdRz3NCjA-DoL(joeI^wyw{A=^8&dI@j2$MC{^aR>EH(rv_C6uXGbA)bBoJCr+*yYAOO zP72!v=plWZfHu7i%In#LefcXhhtw8@&1ycP>$#W+1}lbe{Hv-qgR8YG$jwGkjv*u+ z+}V5ZbKx$Bh{m=er*xGrshc@C%7q~o;#g!!F87aT3Cf}b3kp^05N)~QdC4_ZD~u~~ z*#is2JY2Uq59$;9LLXVCE}&sw(WP~YqCXF;VUYp_t<&W}d7T1=7CXZDIC{J}XpEya zV6Y2JXJW3C-V;m^y76vfg@5ctyR-dut}U+I9`NL*XOU;HXTl!qCD(kAbgSKBA8Mk%f1>lV@(vRtQ{-p%8cY5jw7hIEIVjmog# zHuqw4C-)xW9>kBjgH*WtS~#R*T=3ehNxY3dF^DioeJ3(U?@MZnb`!BtM}&`Z1JLzG z*%I~IO`zx8QzxA-Qo%1_vNy#cjF-u6G_eQA6J-N1WBqy z4gfi<<7?J1H2<9B&od8*;1N5V_f{=JQ(>A%X>v%ftPG_w@9&_IYls4rMV3cn6y0V; zkhsler=T~2(_c_ntj7yELFzjz^~gv$Eo?c*E_H66U#b6fAi|?HG)e(-6Ys@f8JHPv zYLiD;zoek#Z2O3)y7nyj$Tc0#wRU+)+X_?6W0DD^AlXw0OY89oe=`s`{JhEYs@{&Q zb_>COTnkm~VYv{StK~1fRzW~TckUrOwYm@sfRJ|Bene5_)?BRCnt^f?Puf5pqUwgT zmOkZ|IjyZ*=I*xX)7UF6-Bau%>HbRcN-$%^c$u5j$DWqL$q^l0M<-$hk5yUomIx?3 zd}hEa5PUu`MhDglIN+R`&J0qO@@vC!5O<*#NVuD==Ph{#YHxk{hnXXM=63L7XIFBOF8@9il?po7YC^>EK^J_bT&)1yq zG?!dCE!iCCHCPLj=w6j-8A~jXJeHxs5Yl20o!R$z^X|w>K$(Qfq`nq)PputX>IZH% zEa{T!>3!TmtCpAfWFmN@A}o-!6c?A$T&hQc95yg)S@-ldzdg4=Lr}=KqjSl~KxeHrSS^XZNSR`u2KM*L7 z5fssq=TQKBg7YdAg6hXCW^&2bG!nb~xgkV*v!|Ius*$)|aa$`KOi65hkzqzyLv|?( z|2EG@Fpr4rp8u&u8R0qKl8NCTq0(rSJ7#6uC@wwom_Kw>Qyc?RlbeFq+FHC!Vw0Qd zsIswg9Yd3wny0D-ckzHbO>oK6sR8A4MB|iQ4$^POo{YEd)on+|rC$k9)`y9SgEP$e z4Jt8CZl9!!a6AEL7_bQ$lakrX)PO~sl-Wm&1?aYLx*H^j&U zynSGlKgtojeZnS`e2S8N#3g9yI3{MCgMG#&aL_%teTS@nF*DS39F={EY!JW^gHz@j z+*US6ep}6K8-*9FOSRflO(WsZxnlv6p z9#)Ggjsip-MM>i*0w5~z1fx;PmIBb|cdf?WtVuBzrRcS&wsxsOi_Ekb!U8IbF?0tU zq-&Dfce%N6yVRxYgkpej0Qjq<5`h;-l=eZB5FF#c_KCP~{o}i4T9CFzOEo6H0lG65 zO2@0iV{x>=I>iXE6UKFNDgL3jFt(`&y{=~vJcsU##1#HKhobhSS)k%argbXvzf2sd zF;jol$;gA)#R)OyxA-cj1X3M^SaP9kh8WJ$y3yobtMmVrcymVZe)n*^pHL`cAyfdn zPi`w>jqu@YxW9FsKdoJC|4}>XGO#vGp;%39S8qg{LIGk z|G>|GXo(vgAL;%d?iJ#3`vO6oOaH{r zZuq6GySG3H9lZ#uziaXdv#gO`XtA!JR=@D`lfrw;-|%zNU--GW#VyY>)3Rchg2lTY zCin(#_QhDXVgnF`99o*-B>BTsW8N<-U_QtmkqgbkIuR$y=MKP@cNy~#H4XAlUg24g$(N+;EJ;6LKAVH3gK{#1 zm7Dh#{$!`zG6rjG5t`eNd<3UA{;IVr-4)Rr`d{K_HkSVvezvy! z!p}pW?2;K+>>XC^D{Uv)$7~vUiFp*zNoe5uL`glX>Mh4b6K0Om>5x8^T~r*)5Ld!s zhns=DPC=`G(dQTb-?w(Rr;k&f-%6HU75b@hGT>oVaF^YxI@#Hpt*n?)`2!VGy^JW- z-*G;&JoD~;6^>BIrc2U1UA=Fk1>9@8fC-Nv5ZkB0a}#|Ga)X3dJ~3?tPy<3vvLUJp z@uRUUenol$@UL^}I|blFn6T$o(EOI5hykBhCtfs@76|2xsZ6q%ivZhTeM7Mf+J!?Q z^=xs!i=-w@s)k04^Y(adkn|x8MfzjqUa-D+XqJJ{A)YwKfSKxqa>_Xl#Hhd1XY%qv zBJ?dL+;R4LrAvy2ig7^S@n=o=wpDha^u1G0pGZY{%R*{hcH1WHu8AeR+PzSnY)(4d zJ+okpAGr};yLWzvV2QVrm29`o2$!Ujg>ka(Xs$dhc+&hRNKj3L)!~E22s3^9;Xwk}c(Ap?OXAHntA(KaA<{Y&A zzve6@am{wZQDgv~>D`xZ4Dpr-YTbpnkahfdeppMGxRCRGdwap_CfPiK+}1)x!B#^4 zzGM%QfOpg*i`QbOozKm6v6C&L%ynU?m5*g^;yk|XCxTFvmO@r#2o%FY!-T2UbF-v- zxzS0^0WyYY+@d568z)qyf(#JW=9@rVb*_NL7H#=1fr@Ms=7a;QJCJ?|H8iCatw30{ zW3zCEoi`l0uknFfhGJ+5yA9?l&Cp-y(56NSX`6&$WzgPW6Sro$-mu3>(D%_yYO8IB z_oMT%Y%p>c(zfSdp***{s`4~R*((L3MYQ}#jjCFi^fzgEng70>Eq~I1Y8}lV%}!Ju zrU}G*-ML4alJV&b%(06r-t77|WjR(2rK4i)+i-)Kc-8)By0lm=5Mqw0T4t>Y_7`Su zEj)a4Yrp(b{!#WvEu;r~BD0j<4ndb`)7}i&>jENmXzu(Y95??GY$ovJSQa~MQx+a( zhTuv+!aH*}8$B#)@X4LbV2qoqSpoVN*t^}tVD)%LI9O>Dfpo#NCW|fBMV3ltnGavv zvL@s0ife|u#zwWc`ITkH@Vo>inTmSL2;=WaAvs#NwA`$h?p`@0?_pjqHI7{F{l=yK z_3NtEF>9=KO7Z4t2bBUd>B%m-+IHgM?CAqQ@;lYUOsAt+$C5Ox)TEpnOnH6#;fXpm zy=BzOVf+zOrZF*2FEu%-Z;y=A-yfzwK@p2W#NeI_%&FyZ^4|oy5x}z_grD=acq+Yc zcpLZiS_O{C+{J$JK7*YlK>r}ApkC1qtP7J4j0}b>kcc&Hj2zkWM+hxlc5&72+{F=U zQ@HuI4bqs=W_QC(yf|Zgu)~C_Y@{=|3r7J) zPFh*;x_gO@b4km-(1JI=6kIVHy&L|5j!gsDWy=5k?Ozv=>BBo@*O%wi723CN4F6$r z@9zuf?+d3$-O@>A0qtXQJS%OS7>Xp#OadH2RK^6^9~@XJbaF3mrZXJ_jB0ywFIXpa zT*lN!Vft4Qg7#}QTd6`_vRZ+dC2~27g|>55^@5uAny%&sIt_RY*TgqwZvz#L7s{xwa6Ci;~7RVM`h{T3L)0Vt1kT4UaZH6&>JtU2vc zveKhYfp%`; z?*h}6Gg@HQbG)p&?TfsbCp%Nqq3$CA2>NK3!PVdwaJLxYT(1?tsT(Hnuk7V5yaMeV zUEzSvw9404!}EegAYiP+W9z`QVsp=qq`Bd>3R#=40)HV?C5P{5%Iut0OY)|r z45SJPdGYKkL{nwDa+HjX`RYb~iE}~HQ}ucNkB!iuvtk~h!!}$S+^lKS4#HG~Bpn-R zh3VLej$AQSk^`yJP!8U%WGyX<6XhdNRg7tAnIz)aSPJ!wc_j<$J%g?3OCFle@wk-n zi#t}6{7Flzm>Jod(uwDVo_H+tdmjK+YFPyh#C9mwKh@af-3O~NdgfJVBHE`CRpf1` zia7M*K|fRIMCH?LevrY2uZDM}&s0ZMC{q&ivGP^#kB>Pl-WU*+?3L0Yx(PesmM_`IvZLq8K0`&CW;73y(mm>QmXBr=Sjq3abRMg9vKjdI^e-`G&O9?^qA&D9;3&zPbfgvH9mia zAWbvIP#I2kTgXayLq6)CfBF=?2`y}(#*;QCpAR%i6?0+A6C$5deaNLZzY3R^En;%D z#y>k$Lg_?w;M$jpMQd*)I7}=rxs^_Tb-2ll@ZQRtQ(7%dCToIT(U6AQU}c5R>W3Z; zgm18*O(J%`a1j#q{nazBLRaQ|l4)CHx}h}{3fzj;MRNdRCMx~SS_5BzI|3d(i)N>@ z_U8sr=HB*{##y% zLKCw#J0GeH+2d7><>5dq8)4gJ*s$7c1Z8)G3jOF!U00?Hb&%x{5WpMJ&mTlM0sx56 zxP7DBPJ2p$^Hm1{_8-WxSn*>(LCl;d^l5Ks8J*h&Ls$X4m;%6(4^bW=3`pJI(L?NF z1orwWctW6=g(*kqJ^1LRv{P<-aA;?ZX7IM-4U@D@lc{4vq%$f-gO=pG zP3jY`;$Tp-s$pbCYAi-po&ib18=Pe&oLlB;mbuATza`pdwvQkfO)v1+?ofN^NDt<A_5>-4-(qFV9?0Jg$lrqr1aPbOEF=r4|Q)tF6ana%Ub_EKZby3J^r%MR<5 zYt*?4*c)Oc?PiI&w{#XDaVO~t3YvA+Ks%h~yDss37J_Z(Llvz79yk+SM`AIJ^kWd_ zI{C_r1Ku8 zE6GuIFuzJddD>PO=X4|!ggnVnx-e3olT_;I7U?q%lT#$YCwkEnUfVm_;tp;8%cIEH zn#d%65llFHHJ7GkX|?I1e|@_=MF(PT4zPi`qM!AXax6F(U<_syI)xnq3;9If zlxkX;cr(PPzj_3Q5nL0x(t+7G65PJ@Gxl5A?He6sFgcrMMEgaI z20rdAofp85zqmp!L3oR6=wz#>%EA=WU6#-3R^YV3hyH=0(N_^s9i}YGyY9Hiz27gH zUyDXMq~`hM;@vsS)-W&t6i8eW`qr-DA=_LxhX0lKQZul)4_aG5aU=+_tj& z2Uzq1VSK@|q2S~!XE1JcN?`S#NnBu!$CSMr@jVPhS`|NpBGe_u0Ax6_N(5T7bFwsB zpF$M_tn_I#>8W7K_-vu^dyPnH#_BeeXnKrzd7^t_u$+NtgY;zGmAIupU4DtwieNyl zCBh8Fq2?1{SJpoirrpHNV4sC2+ObbCo?8)UK;kw4zkFBXmb?hG4^t zlmSiUWDt@E1g{wnJGmcvWv;Rld$2W0y>C`hlnd*pLwe#b)4}SuE6vH`%Hh@86Uwp% zK_%d{b4AVU@#bZVzI7^QFrUf@_Mo$Gud)2 z+dR29KMLQ3^2HphBX|$K4NKKrvb8WWuhDW9*(Vn7m5JHY!}2}E(^9%NlG!N@3GFJC ztC7}%oerB7arw6@f-A3PEPOs{5c!_ND|hK7%f^rjcgH(87m8Ko-^(XfYW`A392;5+ z<`yn>Yl2IRqcyn^PpQGuX>Ia^L!UYd2WqzkVs9YfZV)!tbo)#EWqa=tQkxyi6d%?p zC#ouyzX@^}MN5agZ^jNwI=E>hDf2?+F{%#b8lpN@@aG+-+9zQn=izsyI>uuQuF+T$XSc`BcpAM-X}MLsNx2l5zCGs}t~BQ0CI8d7`D^Ri#x$ zsud~%L9+GM>hT+?R)}8WqFDUQyP=`c^mAAtK6ZoXLtMG|Uan5KzG2KhGd(GXn}yc> z(Zn2S>s^oQ4S%a+>Uy2P$N%xI7K|LOvQGkUr}f9scM!qI(SZPmW%7`B=SDmR4o``p zK5*E`6Sw}KVTvFKTe)|fM%_YMbj%LJqdkj^=sm4|XsOfNM{e^1Fk-|UG&!7{d;441 z8evc%0BA12oFXd2tl9|RQXyIXpp3QV zKQC|K7zx~O>H*u)ZuN7XO`TUx~uQT_fOM*w+)_Jr0mcnnXpmiFyrY}v-3;K+G-71%;J62{pQ^JVd z*88KD%Xdp*y~g`d+-Ccg9o)X0LTPd?&+KHc&{rZ7cW5s>d^%Z{(4k&6{q9*u963GL zM>o`P=?|@!YM|;Jdcx+fhwqcKbQVn@3WU7|T%(umS0XuZSTE$yLNnaphxV^J!!WM$ zqfyAd_l^OtRZDdNR~&>J3Xg35dC)@Vk5M8T`OJ+rK{7 z8Q;b)F6VQr>v!Y&H0Iv%L`&!n@8`!mo~hMaGkiv|QvUwkD6jup3@^Wp{Ra@7?9ICU zA_+pC$Bj${xjb18(u-3r3UEf7NAM5csHyvrhg(F+^Mqj*;1NyG$E*;xD1~}w>}{hV zmOF$aG=cD4i$G2nqW(~___u0nu}JPyJ6_*hQPF<5w+o>OH=Q*3Ns{KD>5@)@S}l1I z=wZg}>a&)bH^PB@B73YyJ(6atoU8s+jvD8eN}A*0wye_ZpM;>;E>KqLEV1Ii?NHq1 zK{m=)Ky?b@q^r5%=v7vehQ&V91y}2l+U0~?9yq0nWbtat8hf|i5`Jz}hot!S<_fe=5gp2sx7#ezdv`*3JCvk&HM$^z4 z#?f^syW)*ObznAVK~4ACX<~UH6eX89MZBn3lh<;7<&zLRRTJJM^r1qZF=mYNag~<%p+d zomN(`STY9*Ot+U5mReyGAlV16C5^;Gn~N%1e^*lLtVEBxss;H^Y>!4nC207hl}T2w z8CE4N+|XIyOes&W#$)AP`R&r2xes%@fqgI3kXI=d+^ zB8p+(-lg^Ej8eY_Y?8}KUg(k#%d&1v3Zvz#$Jd|d!Mfy!a&sii=A@_0pc>k!)Tj1l zt;3bB=w^bcqNw$;ID3aps)34V*F51t;l1yu(^$V`L_^rQs!C}uPoOrQ!FeInisOT0-72$+HBEjtv1Sw7nn8X*aDVewWbWu2L`|4 zu*@%3i8i$>o6gL*Z;PnBLRyorl*v8#Ltj9%hk)1;@`MGKF5>d(yEdTlH;J-(TKvM& zhJ<-a6yP^h%t=V6m)KGVeP*S64hHp+cKf4``Nt5IUyt8MWx$Gb8c?a&Khkly#nS_^ zQmc>N6h|sm9uV0A_c&cl>5r3F^@@iSU#{B=KDu7IP4v?f&Gqq`bOh~E1+Xt0`Udu| zIL8(-mLT=ZW7JGie|8%l18p^iq_hyO$h#)5@_O$ zFrpK+u0>v>fF(CKEJOQvC3i>ii=NlCD^5Z2%vFBa6d4%uT6aO1Z>Vn&qmQeLMzq`tbPMX9Ij1PI-VbQmp{Y5PDE`I2AF%wg0LbbCxOu2YORkF$xMS zfy#m*Z^wEaRaneQ9_lz5a}z_{rXqC^j&VG61B-A&k)dL%I`Al;`y%xspHwhCCD#OQ zYBbW`TIwj(31*Cv*fy0~ZPZC!U3W+tN}D;U39sOagjK5!u%$WWlI*y6lt7dfgf2!( zQGAM8)kIWOrzny zJ~!ZRTnUd0_VTu`3AN404@FL9Jv5rA)}W?8IDAlQp6Vn^%x;NlRR^EAy%R2b_bv|U z_$?cBg_aR#BBTHTx(*YS=%!UKqA)nY97whPIj4ByDcyWOf-Qimb%=x3$h^D z{_!?I(@CQ?RqA3O?OQ*B=`yLCIY$g9oHa+m&xr-^J0%zdVeIccoN)FeESX{z!E!nY zc7szolXl6Y)3X)-gx1QkUrZZZ^on=LeEqgQ`Xw+~ptu@28+gf14<(pHm zhQi;NvK8_-RB@>Lb@{{K{@6WX{?(>Gd3g6gE8+2E>;yvi4z|}u8_140!_B5p)M730 z9Hd65v$rgTFMvTLA;cmFqZH{Bgjv0V?0Q!(e(@dLD{NxGP3R=ocsK=ADs5HH1NvE` z*iG(?^<)YueplYq=?A1OQ9DLwbjVLZ5k?WIKA-M`n>wj(G2nKgCH42fE3y~;YB2o| z0y`jvwGSMN@44wR3t9b-3adVdr`Xj^St73wF{P4?ycT~x%;ndbqv;HkvIz&v%6Vr7 zDeJ>lbL?+^=F6qD*a_t(d(UskpxTAln^JCw!ak;ka*~;3d%_$IcsqFfgas%uJ(*re z8#njTL;%p0Z;F;i826;R5{>Q@)sPlYi`2rFf*Y}B? zQf4Bjokzp!UyN*9DLdt;re>xo+3R4Vu1ysN98l_bZ7`nA!F6s%3jp{D+J5KzNcM4N z_{Zg~?#oqGYIw#2l%7mOMWiDqq?Ofg{ zvitKamqo>1%D;%7N;;!{G`#*M%(Im|>hLyu0b%jxOIPUp5I7d|qO-krfns~(xDJ`r z6!`@Ir{ew9xYQ9V5l8wJwKsl6?Tr74;{DAr_HQYmzZ2{%HF*UTbb|`94;oiWakK@L^9!mt?(Yxv6Ai!CqZ*i{*_!?j} zy31@W?YqZG9y4zf^Y{7Y)ys3bF@J?2uBXjc@7K-8_g{ZrAJ*30-v)SoW?|kJAb0sx z3M%!2L)Ca*2jdedt15ztDEdT_4d7_6TKq5EfHg`FD(TcC4 zg%PsJS<(ZQ7S;mRSa+q<#lV223Jt}AOFTKeT1aeybip9maE#)lZ^b_NTsLS(K?F~k zxlfBk856I_$qX>h6z9ZNgHA8P6&dzA*{C2xo5R@aW+Cr7Z_gT-?-8x9ZM_uw5{*4l z_M9id!<%n>m)u?=n>h|WC=gR^G6_&0qH*|GevV`-HVuOn%^wz%XyM8pF~O3$6B+{3 zwWLR1fPqz=9Om_4%)qx`3{~Z^HoeNCL>F6=-~w~7FR(NUB-#g$tSmfp zvvQItp|Hue!9Ua!OS<>p4noN*P_pSJYPlldsze{cFv>T8)^JIHDv?D{GdQxI*sJus ze?%n<$2wO?w#?_C%g@e9r~`pxYOf8+vsuuN#ClY11T!vGix5UB(E5&W0OO$mS4^(z^*04s8X$>AX#wsRHS80zKFeaa=@C{Z}P=(?ed^46@^w6{qKHN zA5vjE!m;^pi*6#-hS^Q~v7>D6bkieko^;crY#wyiAXYnXarF{Hz@zvq__d&r9h>73 z;4HvKaYKoknXq&SGwUryRz$g`CeeU9tGXAtor4%(TfV)LQqV#&V02F?+fMkVM1ts>A&5D+6hxk zNEqN?7EQtGl87BXE5xW3rCf>o>UKy9^7+RE;$t3q9qR*q*4eGb0XRj`BIMpa;!oq# zu`N33I#CAtGa2x@156S4pZYbn5$mg&Qsqu4H+y(v4tWf_xaw!!_T)#isn>qPS;1d( zWPifm+1wFCY~7!UK9vGKrCwf>Pd|I8UM^ei_L$=2+P)fv|!YZpHo=QD^Nd+l9S zE@z|Ntk;#crJ&{(>jyn$0e6_o1^@D6XW=&`y;U^Rp)cl*H0QJ%m2I|AEAp3kl}Ab`A&lEpEF2}`pQgo zS8*rgBbWZC@Fs_F!33$`p0S>k!IWf1q_TN-47EUAv@mhiE>w*!-i#i8F1#eGu>UEd zGkErtrGJ;QI%p3CKujR0cV_z%4}stFj%cz9gK?LUDEI&jR3&^EY_sEoRg$8{8Yl4!eI)A!xARZ z%@7`JSBN6rcvZfucaTlO3o}nl9RpXM%t7CKTO~B*4TF3Wboh|0cqQopiN7Trc@D*V z7j^g$_QKymU3onU*~~9Q(w-G=Eo-UHn5YnsvTb2*D;r%JW_*-05@PTxXMA^~a+PETX$Msrq5u>!qcl>g&t{vL(#qeeB=@sB_d!(kUt%}&;B2`FPW z^2T+dc6bR-ID2+HDrxo&Rgat0H3fuO;wU55Z=n73YcL(FFv9Sztb9B&%a|VW>;L1= z4f*Y_J!c?6VXpV9>cHeH(*94HWrfZ39h8mjosDe_ja8k@tsMVRbf7FPkD`eBVZ;69 ze|&?83#x@CQbe1m)txVdXq|6v<*e8Zx?{k#-WYH$lO-pL+!xND2V*}J97X3v8)@?n z@_~-we<)`uQ$UkBz`x!1!Etayo;Z*5|h-wY{k*z3RwZP77RDdog!$w{^*$+BkTh)uziR zWmh#0S~k;})u~SlD2V+P_Vi6jp^|p@cV=fE#4_wt^mD7zq8VeQ)vA$em>>Zi^ZjU34ansHK-on0Fa2C&e) za@Wt@pb#V-kVO*Avwr=awd~(HWRxB<52t0B)};gxJR~8)N|mb&ij}iDjM%Tyeqn@? z@fMN(>c$8oleCCOcZ2xUXCby!5}N@|%SB&@0sI!?%Ny}zdW9Sbk)bG~^pwIW)sj&D zAi$+LnV-!#9u|LBL0{v#E_;`b-xg>3@7y%jL7kBSv(nC;VR znc5F`L#KxoL7@jl-5M5>*yTq9*rsca>w~}8<`f+mbY8WoDfL}LqrXs4RaErsdh-zv4FqTNCwA-{bC{b~oL{0~Ag31hc^6cMPsDytk~ z`M`T}c8m!k5PWZ?2*=hCK!h^r649WG|B0y|C4uar5nXGtLOO=bC@ZtN%x0dzQhYqn zY>whtm(qA2|4kXs+Siily=*S+LEg;wR6c8D-XiC5t*@RScGAh@k@t`L)Fa=YSH8>r zO!k9hawz!@qgmegMC?Hlkzbn;~Lja%fIiw^3{lFv(i^63`>|JAPUo5E0xQ)kJpH(m;BrM2ay8I1#Wo{zkxV1 z@dJ{lRzQmbg9__%c@JKGxQ(m$?!!#kwLbTXE!>cmJt0z^0ePj7$^~ALOZq3>N5NZ8 z-hF6NJxi;BOC}Rz5;A^h`cg_g(bSy=O0;gSH!u8nVjOo5ZrDhO3%qHc$XKd=K zO0BB(C7LtYkhW@v6(h!pTc~@y(Edy(&=+Lok7bLbTigvg&(FQ+nB7kTOxMNvPzOPKBPyTKR zI5^^Bt^RTGVwqVaV7OVZIF}l|LA*-iIhEIXrss%b2@8J|?hRI5hx7&sV^9dK*>20>V=F0CIzqKB&z2^IBx9ycS$gBO&HVxVh@M`FFN*!i9TyOB| z-9ZbP<{=Db^DSYS+8Kz3@`qfG7o>=DN<3z8lbPJS9bdC_(e(!|)>cQ+J~iVbj=L3{ zENnfHpjU2-jG|9YnPs~m&A0KApR6{rGRxOUa7|8WJ)|rv9zDb{siI9=K%m#@dw+kLu6-y z#6Rv39J2z*P^C*8b!jFhE&c8=YM$Rf^@#1K1UKJdY~0+viQHZV!d~A<_cj9A6k?fS zma>o^Z(F>0tA(q{T;n?Sn6LvxF9wMoao1m*5)S>fsc(!}=&40zHRH(fAE$nt{ob0b zUjI~{P!;O1lki5zO6=scg`er# zSdQ#7)2}6k%}=7nVD&`W8Ks>pkPSN8NwqAAzKwOkJ2HwDR~fGEpqRfP%r)#N_Lg#K zFLiMw4*50C{XHnk1KX_ABOr3bQI(N$((}vknA51x^=eq|`#}3zU9_ERGy@(@8|dd( zVBPJB_k!yM!%`UcT}9$_t^8 ziC2KD?dTFoxE)fGTgKV1fx}B9CIg0?s_^$Fn!eakxcA{e2h1U}K6$v8XO5f$L~DBH zS(&K779`F`^AlN!qLG|VuhIJC)g#9p%N?7%u~w*00Xuj<%{@BV(N|4L#?0G=(@DxJ z_2UBRlbU6gLp@xY33mhp$*4afo82^vsFhiMnmhq-{=jrnCJGz@&Pwk^_(mF(3aTzi z>pB6QN+-|BvC3m}!yG-(fm&HA3nc~VsvA2h`0t$&9<&vEEbi7^i~t9zNyy233OBq{ z{@!5AG|Z(^6q}g~HV1oC#Y*{l<=onAms+k&U|5RE6XVzqiNctXYjRC6OuJYkQevX> zKd}KTJ9;kx3y#Yi5wb9`K=)fUSKGFlb|0{rIkfAgTQsoT$47-A^%V+AT6md)*EA9y z$GD@(CJ&dIxj|UdLd}}w=ndxj{eHjqj5>;65Uw0kRX+hf`9B1IR!qFz&ksxTDt%*R zrl?69SmZuwWLo@47JdF;FB#Ug_!NHU-ItDik*##faLyFA2FMwzJgX;M*MJ*_a>-`G zsQW-zcIXbJiNf;>^UdA?T#i_WFWB-7!O^5cr=c4{8JHlj;F0{<@IN}_p6-vYZb`i$ zSY&%#J_7xJD%2Wg`)8R$Hyf-OKsQn)nlCIDFXReUOeiRbK^s`Ksu?LNb(*{yIf7lbBFkIp@D8e`Gmqr8`@4%xyu*tp`_YH;@}$QU`}o26+KjGKKzP&Z@_v5 zl)O`A+ILTu?cq|C=1T~J!<4rjC}==$WP#~!3%tgTJ3a8LpaJ3k5TAgnWS`K^21?Py z2iRqF^nm~x2zj2)Z?Y}d{1YQzpfOr;?&Y+u0>7W5Z-XS zuIcS~TwOah0&&XSIUY9~4&Np(vmGx+@IT+)V1D}Wgy!Oyl4kCnT2@hPd4cYxhHg1` zA_=09^|k&igKXZC_35P8M&_4syf>ypr88+&XiH7Xm25>81U7A>2xb^k&x)u(>bH;D zO>9j)ln4e@7D^*@(_nWxr4S}q;qIB{v6{4iJ$K)jo}?L{GE2H0l?h|H?kuA!f-1_t zZK$wnKDeXmtM1gKG)b(_pESo>$jo7nqN-|0PfweHD{|mguBB>M#BI4#a*j2ySi(p| z=Dd(SsG(v{l#yf%Puu1!Qp%-I&$9@Ky6eISEz>$TT%I28XFD)j3H4gkd+s!j*w#+~qwdbkDJeKD3VAX$v`LlDAlu=z?{c{c5o}m9%ZLx;|Jw*(uRd zD@I%Kp0Z}1^LWF<>}GSh!wsN9^{RiV%oRCkZ9&ERaQ{*5ocIuUf*M1XPDDCEq2>6{IH72-elCWl&0Djc?<0NS@aFhO z>+NleZp=)STFC=4y7R~@cRREz*77bWe~z?0!7{Sn8t%Bw1eGtII#Rag&fBb$fma1{ za%RDQ_b^8O)&-t3<1DmgVy-#Jw#+&}s;v@><;f-N>kp!80RcZfQ>VIxeT_A)h7EO= z++Ne6HxT$t<(@V4y6E`Sx#Y{RYMK^(SeWp%kVrXd-O_ec-iDzv3Cmty=fP zkQj{yYTgyq>MQgfasGftUI7tTI<}peePg)iF1$l&1O*iP2+u8r7M+u3O7R091N%7< z+@qu)vo1Se(T+j#k^KCL*O;;O69x5zaN8-$m2NT6B* z<6L}C-6tQWgB7SE9|1G0?SXb!H7_jKwpjhnNbl-TxcUQB7Z4__`#k{KAbG5Vp_2A$~ugj?qm1Bx(+#Z324^l1^b2 zyAazzS4`BG7N|F0)E9j8mll|}9LV<=$ah2FPyV0}@t_Yp;LqQnA4nf5J1-ce1U&n^ zF-SRHk^Otfi$wr^@6H5Jw8m2;HX40H-m^cyY)?i)l@lb+xQAnA zSkk2E_2(?e*HR;&8Fn!TE7(ldmwM)b@)D56C_sccF2Gxbf!r`$ICWcrKXqiun~Bxd z!mL1eccLKcCqnCJkMuUale;`|)s5}Ws4j(ZmY~Q_JG#1^kpp;GC&7+I+tlxhqKv&3 zVs(!g9N-x9fg=rtB9-A7S3xnq9_GFtz%b{8BE5toQ@{108bo?vI&XBfZGPIwx^jO+ zg44BNzy6z}HxUUMW#!kI751ysh~|Gfy9)g+e;{IP_|-(>@b}|uPz*10uK;4upZ?T) z{A*A(18fW)Aq8!iF_lx3Fepj3e*8NNbU?Ro7)r#?APt@a-UDxjTdxiLZ^@WYcc3r8 zApRIw*2H}#0B|2nM32)7wtdRF0Y$xGchjjPwhWTpMG^^U)eSZLQmw+1d`M>P0i`4< z{K$E>skFrV?_)J=`Z|xs#o-z@k7YCDy?5km3Ln3v9Bai1I4aRUCFy!ZGZKbI_9->+ z9^l(;|Cv1bt9=Hvs-dOoKYwdcoB#IK-1oLieBj{V#Nd1`;B+qFHZI@*G4SU3&b_xp z6#`-6RY~U6usFZ5CL+$JToPvfn$#>mjA4XHdRi$t=dp31a%6|macP=zCooH@*)b^^ zX(JKPW|E=l4mggSB>}#N9 zz8D_$|1{WtfxExG^w*~T_py$OTaoKkKn(uu(?T+`|1&^JCuz+Pw}p&C8XL>vptzNc zI|`L3U@3Lo)yZ_-3vrbf>8KUZb$2lC)nI(U|KksS&bMm2Od-@Y$up9=u~-Yt37aAZ z?>SmV)H@{K{ngYpkclguF~B;bQF%1)ee1dNlN%Z886$;t!K;}H4jkAkY?w>MCT$WL z?r`KK{{kxNVIc}0Q$MLHDz4&Km$CLHi16K6XCGn(mv>L>0272 z5Z;AWyl()bt(h#AobwQ#*;`PCa9-M^k&tElIgD}ze4BQ|2m+l1*5o*==tlg;v)zr- zW`-Eh7%cfG_E7BWTQlA2!{Cm092x)^u7S3>L`=zy%s^JmLL++3lf2! zY(FBzJ(6fFiJDNSKqmAxkv&&G)=m+kX_zD7WWvy1)m^oM$SqdrkIsOuXVQufY$mq| zR*yMBpv5}Dr+)y}AZ2M~6hXufGEf&3 z4B*oUP%i>LA(l$PFhK*dF{8ZAH}k6Y6yzVQmP zrjV4SeTB=fi{otD_TQWD_ph%A-5m_|5nSg*Yley9#gHUB zjZ1n}8h+pL4<^UXW+GXt$Y@eBcd1s>8&-jT*0yPjHeG>Ow+-v2a<$85$_XR>vCM<$ z9DFy{jKd*Yx7(Px5v<{&OTXo}+eRBKjh77W`dBr`+@EW!ZaKDa^TO5F{jjK2rI6@3 zs-bzFj>hy_q?z9cHslOU)j-eP)Q3p#>D&`r&QZN=#_k&Qd*IjwykTdhv#!ru{AJsO zhr_Nkb0qxmQu{4*4WhTc%OoM3q!Q+}unm178M1TQkiw+EIh9MAXo4>fvl4vW=hh#; zOzW#DO~%yIsSNzr?nTao$w!7f5$$r6LW0qP*e2Dgctnmgqg_z?$0m9Z)Ui$RbRVG+Y8 z-`i-#LBxcOB`Ge(6c<+)Dv=p$BJ$PZK_eu#obw9)5EO}79W>I-Q$1(lgk0K%69^@< zW8&6|3t)5LkK+(KL)mGY+IzgWwwbFSOxBsT6Gt);?rn-?SRkEu7&>~* zxK;gXoaZR<;24Wn7O!60woC#?9p_N=7X8hctHA6W1jSr-m1$0a%MyZGT!<~Rk)G!X zlwqceea($M?^(VDlJp+ObV0Rn@&4aV7k|kw#w{a6&ewE)`udXnPt*Eewwr$nFTe#{ z?n`^WR5x+{FVz(TXNAkD?5`Jj&+PAX^8Wm~?`{v-QdWjg44iH$d~e=Ae*CS|;7h6r z_&I(zQgANNGDDa_$G|MN67j~0iGc^bR($Y1%HiZ^Uxu=VZVq&6V^)-LBuYT3Tb zBkBKxR}^(KG`4dxx3&2%xgGnv+{Q^KZNZ6N!&ld0W|%n@=gwPko@Uj5nRg6OXww&Ve&Gt*77x4&+vU>#x@F~(1#(2WrVOfqb!dV8{i5sP z^yF7xn$AQ6Rt4tdD@Pjanw&uCJkM|Avh4b`)%q@*6i<6Ai#4t}-vvnB7cwaU=k+Q}KQRBiq=#ZNDq@-?ok#rsn7Yxkzqz6T-OAL4=xy2&X@ z)2_K(d+Ch>IS@bmlTc4npNg)DsEMpec=q;+3<>jR<99s;;Q;+ejlU{p>K?~H2b59l z9&3wH*7i7%_)aPlL~hD3(^&d8WU5uQUbOm39@Ooyr~-f;TxErFvr!XE&mhrmm{lA} z-~1>Xs$NTk&tkG9T2o!(3IWr^*!5>W#hHHu9kGVJ*{eSmqm0@ZMXh!%^{$vqw9+Zh z&IUd)bVNRPVtSMOKUMm#!*V2wd);5-{PL9+`ycuLKknTX`Y#{IC}nBeIRV5EHv2O~ z9Y{YVY%L1fm_(tCVnT(NT-n^%_!}w8n6<$bGP-CyTpZ|-aFEZR+!*jW+sGubt@hjU z1RvnYfpsVXkg*DhS+>8kvYt%_U$58J9>2|MgzJ+xGOV7vU^a>|BK48XGE$@}#a>qx zZ>hkpZ;%7Y^GeZ}nWg5pitc+6$=ePFY+fD4LsZzH^>#Le#dfYTGIJNCp+s~oE z-c;&iUGNBVu(pe--?{`XXNmS9t9N>6a#VC$LG!0pIXXsh2mii|kJn~ShZPTRR$g!m zIAzT{cIlF%999dgR*ft-YsZGlU$@}Q&~!cX;8JGO3C>fYOQC)vWIDt$5#?b1m8rlv z(e1#OdY1x)Y~^XAs3C%3ru?wD6x28UImNK|6#LOl_Qns z%lMrEo$p8Q?0|I8{@6egwR(;2Z$+)HHkZ?ftd7n~^?r{g6RUOR)8viTOBUVBp1uk3 z7_{-y)Pc-!_e{g1SMea&>#crAIOWs%0t283Du>|;ju;qN;!nn0e0zUb^)VbFSKk6I z$+AO_DDDU+GR5CFjfYXewN$VmWp6p}udLuJsA>yUU_Y><?Dik^7Mi&J97lH;OqeL)_$>{g^JaAQNJ#Vc0ra|Paw z3M34CA8#O_OJcL62X`~VrWZfloS2a|J(y}?&a22*hV|4gl5$4R4>8YDkd*d@PDuK`f%|>KBj^WqidCQx z5GLWq&DD>L?dHQB(BC0Kbp#cM9O-`w3j9ShvP>v66nm0x&B;K$QiZ)0S=sWd|>hJ(Sd;dwhSVF`_30fkt&p3${!1r#Hl<7DW28$tD&o zAnC{83EBw$c)!+G{T%GpV5i= zgd&iAK;8j`A^b{zDu{G-QCFn!;~RfZb-dtpHEHJe^?rpX2tkADN{yWkNDP#uU@Rmx zibd-=$Gm{#Mhl?Q$7V)GQUdm5kM2n&JNc< zp?}cQZf_cFNl!ekXk^`7-%2Jl;Ky>n4xV7pG}m@cx^$?@tI+CLu*hIrNv|$rE%++Y z)T)1Q&EgEMCBqE{GHyt%wz_rIv36*vR>K=(I0X$oKYAVT7B4CHn;;4h>Yi(jo{yc# zh(=u1ujx3kvo=k-eCbsl1&aWeMCh}EcmjiwSa`@TIyM$=Oj>tLMP|TCnI_-G4lZyb z*d+un)2xq;G>{wQ0ff!hO6(^vq4T%~yWo#$pk|W$a|1*b#h|2_k`+x-Tc>#$?f@{} zZB12{iBdV@RcvmPC9W%Q**PQsA7k$TWLeW@jTXCX+qP}nwr$%yW!tu0)#dK8ZFJf0 z;;nb)o0xCzjr;!-aq>jFoPcE)~k<$9i#OMfIM=duppj{BCn$BOyNlly2>FHLW7 zQ%!mNXi)pd&$8xmg-_$L%c|$O(Zs@72F`x1OT>Y`r%&Bq#$Y6YVvTjZ>d5XBIEq7i z1@9|1j?w~jAzLhFPGSY-7A7PLr~Hvwj9QDlDYM=%e++6Dx)zCpE81V%3?UF$mkg(T z1OH)cAr##w5tc`IcNfg(jKCdCq~oeN!taSf7~=P|MF3$Mm+%eY1_rn23(0=w1rjl; zc>EA%*zJKLH=<4Y36`r*Sp~bIXr#Uot_Tw9BUgh&(o;Pp{E}<-Nk*SsfRln`N8q?OW>P|G2_v9|5g1l6tL|@ z&=0f?lh}bMEI=QAW91t@V!`GsKXfbtxeFu2JBUC4w+|5VrHW*LxYa~7Fxonx%`=Rs zzq_#crj?g_K0PA#1v%VZAb-2KduENW(B_%l+M(|l-QohSn_ln%*Uc}$fMc!qgpqj7 z_lyx3T7A=76m$WzTVz0f;|mucovY9w^hTy)a8Q?GPrHIM6sN-ZrPJ}rVyOL&&rc<@)$=||l zm11b`x)X`0Qdb#`2C0|=LRNnwD_U*xKb3nN(CaMWY)40n*GDM}FqxQM8DW!(4uDJ4 zsxXXkS0;Taa|!26V!GzP>v5*rb@i5=b#UECDo!#A^s=V__uNW3v{0&%%Ef-^G#=tw zXJ_0W8o0}I%y~<18q0Xo-iD4pC0ve!{P3p?1MD^$inX-DM`K2k5*O)6N(*etlp7{{ zm@Lxd#=T1vDYd)wOu)0x73*FV)2#W~1mTdl*sSl*Oy8ci6e_VLp!fu&xAxYvR*xuP=jA+AdvEh-RMxwUS~r!_LS%p()14DpRpS#DO# zrCJwd;q60|2YC8D)dkns+rXmZ*5L8v zN3o=A8{yge5j2GUv92+a;w*Eq)2_GpEuLyG9&lC0QlSUTCXIi=7Mzk zF1RD|vG%PKu?b3o_IU`@3;RE|`E&5a{nA-HjTHyEDl(h*ORO64u8*6DZx61s)PilKQ;a!JNjD`@a7nq3$6u>_0!Ajo#h|>g7O!JEQ-B`I{ z)*Bw8)i(@seSrR`zKL1>2GCckcy{Jpy1H_t!%gvNU}u9hYQep$5u=ShP!vw#13!7` z18nkBDm(;_=F2uTJ{^LZBEK>ERqNMZV^0-^ILNjrranIneR2)ECEI?1Z8R9ZL9R_h zww;4*$e7|_*w%l?=Ox_GYErY?uwPj=5Q|v3Q>y~~I?|mgw}n2XcY4Mu$ZEz@YhdY$ z!Re8^ao58Dk9tk6`=V+GEWc96aU7IP;8aS8CLwa2TGxakBPEiG%C|v(zUdH3MdsL|5XVMJCJjfOby`=SJaH@3 zcUt3N&Ns>+i+zSW@@+d>@a`=aC4mZ19(Hcwcn+YvNG9{DDATx0)_UrbGLp3#M=VCi zw-Pcg)u7>Uc90@lM%J#$riqbSMJ3}QQ~X3pEFqJ3 zm&|$Z1W$5hk+;7Sf6b00C2@96R{Zv5kz^*O;KBLuv!hEe6_s~~N@S0gY(l2!2AB8k z79-V)O5OsY==1xo%yx2a=9m{kilTCMZdQ!K`i~4?H{UjB7?rD2Em9RZaZ)B{lica9 zBGOcU|ByW@xm<@Z(_vI<80Sh<)mHxD0?<8B+8KM<`jz-~0$OZK+PKC`v<67^jR@`Puhb$(Zmo%ue%o59GX z`yv!=bH`u&(H{AryE`X~hEzJsEwcD%7;hB-zTurr9MTsL!iuMr0feR92*^*Q>}Z#^ znZN&UXIx3|tHhUYbC&I!+Ryrb!VUiQs+Y7gG7+-1aB#MAwY2*e{Fke`ro3Q?`sv?A zN|#7>Q5{Taha??DEGaf_o(O`WWX`S2NHmOGCf5q%aKIy}a%n!OJA z%DKMK`HuC~o)2W+fD?$J5Te;%6siO@6MQm)EC994n?6Tj!XIEguE;Suo*v;8)N$RIU-5(Ud zI=rR&^72F~aoA~<236+xpFuX{6WFlxOF?-|u68hmNIi9%8uklm{yEE`313$2LS@!T zIe^Y(haKa{En3V?Z9KJSm|alis52#|g!TZKJL={y&~>cy(?P8#Q6YlYzZX*Czc5u8 z`#DdHyL7=rT9LW+!9`k;y9~gGXstT=;DGZJX;zQ6ysq$av&1vG<5%pB>B`fkWgg>B z+*R++>)O9=KBZ$mM|&S~6VzWj=mJjyWgzsp59f`_6un3Vbp+xw8W4bahcUtMk+8|e ze;T6g!(`R%{*{#6W$N{p;1GT~fb4v7V&$4Yg7+jb*y|2X$IG(k3O!Y;n?l?>EGZY8 zogh-aQA9CC%Easm6=430HUC#G27Mlc2h}FM=Q&4ntK=XuSzct)Nd{k*OJ8Z6H8w+s zkYxG-;2Z4P-V%~E?`zMQ+1J@zcMp+tcm3hv$x?Np1ey-1U$7PwtYM5T)&eI_ADYU90)|_^$K38PstJY$J6@ZqjEnZb5(H+S76U$p56`Hy^ zl^c7M7%fv2L}#_Elf4sfW)3@J`rlX5KSpzY2-+6QH!JfK^Z$at{o6|VA11SwCe8}l zr$iDK<_}xYwnn`kBpwhW)D0(L;R`TE%r(e%^jS`KnIBdOKUxs24l_vQ+FMThI(^p5 z82;ajylJKXlyr1`daDD`xK(HIlC78#bb$NRplE`sm&D+-+11m5zFpI=m)Y6f z)3L;XxdW3S=#;?V@E}!Y6C5d@!AJ`^j@_W(tnQi;GXk~UCwE!O8`u)!vfxJ&^?BSW z^0LW?3??)RxdlmT+hK~BmhWBYX`_j{c#s2WYTRoq7YQ!Q$DS&&(ETijarg%kjkUS$ zm*XI0{DwO3LbV_ui~TtyaR{2-XXeQxW)kx_7Ut8@+kaaAZ-{tC&1Uma56H@zKlo#u z2&Fl00WEZINQ48;DZyE1Ye~RMzYsW1B|!ly9f>`ksW_zGKtg*}Uxt&l5K{y39DM8+QK-mC7Ey= zQXU+(OE6tafaSR&apRxX`qI{NTdyb&jK2{3PAt~LV{7byV@uAN7Cad|l#!C$azh@X zL*80cG4}LUE9V8t2k2S3c_Ly%e755KUw3Mnl}HI$DDMqkLP9q5{#s{!YM{wl&#D-K zldbG&JD86Xa!5JoG}|O$@?(bfL#W(jA;!RJu?D>V*#V!>+L) zsa`9xX65BmtazNJC`xnr^_gIfW>e#{EsE6H@Yy2h1V*uciz~C`+#Yt}(FEq4peS5% ze>KESYUl8 zF`RiqX}Ic+Oh7-WwOgz`qOB{Twb#aVJH^M@yo905U~ec$fKau4ltvqzzOeZhA8d7K zj8t>`16Dd*;zmBWe_$>S_tH)`5^gaqi)mq)>7}@Z)HG{aG)^0DA)cFSaZGe+Eq}m! zoV@VqYurNMA?(6ky(rQN>Chk?5r-ZGyVR0L~^({Gz>*oX*$ zjJN*zu*z*Zyzl(GfUXN-Lh7hYZ9ved@#$iS?>c~pEeX3RNj!(WVTJ32PyhC|R{Mm? zS#pL4iR-*vv=`pA)jn@gkb;(;f8jR%-@snXxjlr;f7j>6Y zSca>bs85QqLurWwBIzW11?C9V;JVChUvk=_SlT8xUDH`%jWBUFK1DT?^~%r|XPq%` zfpKum>9EJVEF$8EU_s`*)h(T&iW0qi=6QvF=Wm>_(|oCWjPbpC;x4_Ch2eLL;rFTO zCdc!>_jgsltk;^1SKPJR#b-`HDWEdF!3?P)f$icPP)ry7k?)G(w zYD{;kV>`W(4%bz{grkVpX2NbzP$gOlafO}Cs{W7C8k)jI=SVmCJ6JS#WcncQ2r%pO zD#br**pa{YZIT1MCj>>jW_7E|Qeb$h_Jf@%&QkO(#}je(zrzd5d2Kp%zWlT#U4*!?uuT0cahFsm?|UZn~b~9XGU2dw+5E zg;mGH_^KWE{#11$>H%Ob|8zL0?h4CJhq+7DekldPFmYknCkcL(Thk98>aNlN_v#11 zq@}>TeGkM2^h_70urtdc@;^!rbsfE`kAhp#G3@Do_4a~bm>=qPc$wAeiF^-*inyoc-H&YKg^PjB3E!+>Q(A^>(XL27GJj(?zL-kQL~>;VnbKKM8+b$+YT54di{9& zwLODVq6_aqF#J4#_12AeOUKfQX7$Y7D19UL;|d!|(!xI4m(ai56q~Q=n35Djy8#bj z)*(-4R>`Cv9nGq=wWJ2hG)Ftf(RwbU8oW_b8UESab5p0FTL&;cml0nySJ?=n^J4ae z@IQg+@lCaDvTyri6aff`{(oV~%bBwMJ2tK15JevqRqwh;Yq2 z{tQY<%v7iOh(DMp?;Qx`j`axTPU5r{BX>juS3?otZa+CaWd%J28;(hRMF%CI)Gi@j z+41>i+uN((;DzmGoZM7n%NAvs*B!9E$vZ=KuP4p#%CeXsmQ;mEJbApIJhqp2_*&8h zx8&nE1#cd&*0_&H7uo|1ck=wvxeUzg%k$=@^uR1Syb^ZLCdpSPitzc0FKJ35*bWM~ zbLNNl);hAVRpIn*p2{Y0=k7x#6_zE%mvXQP=8A4@xu+6dGH3VaLh}~y%v(4bTNf~A zGGSM%k78ocMKrPbH|KPzsQ-qtZ!_6jUu-&cgdm!a*gR5ab=_G<>%G0nvH2HiOsy%d zggeao>wu5ou%<4!XM}2s^>dv3q7bHZ_Ko5auBcFBd{kYWOvlZBLk(&B=0U!=ae!m@)Q$ovDnzl5GFV8nIL z=SMDIWWbyBI}{s$eHWX+H*XW=iXyX6eUbB%9Q6mjL5tvsTP*Nv@cgHO;F*0FX!l~h z!DBRE;CRXx;y%*h4bEM1qi!5x9*^7lsPAnz4+f)Rl_| z;$5x8oSWD-Tq4GJ$FCbFI{P$bA3Hv^gw@msc#hI_U9a4vJso8iF1n$x#;b7edR5xc z>}|TXxHdXn7OANV6_zO5uAQ?N!;WrejjT1~ZL&S^*C|yvmVj>iI7kk(et0Bx2FNNG zQMS9RvD`y^&Q-S*7+bM9G4UDk12H2hsa>N@_v2bb zXVey1-Hpas{}Dy9@VsX2EADh4%rATyk zgI4bB(G+VMf%zjBDjdZ-H)Y&b6ubU_TV$3e$W)M14AAKd`qohxZFCKe^xXSHxii<$ccyW(ED5S?#;6ebfxU zfz<4G)!=8oS{{-=MaNLkD8~69Z>ZJzl~#0}2PtE*ivqz}3tC0QZw5aQPc*%PbY4&@ zX7MBxyl!Q7j6M;qu48fCG_yTE4x*uU$fYVLlo|A8{o;-FPW^=4w)b^e{8MxJMY`M7?4+Mx5}!0hC(vuDig zWF3&Fd)!I+ZrelVY^E^*xY6k_u9L#0D}-YlUtZ5zU#FrqZ*rG{a$`J#j{@jo--q%t z6i^S}(q{KpO=$X8nBr#rJh!TOQTjcU{*(C#x-&X6w$nn9VvMig(2b5~ob64N zDx5@?T9b>+5~jw~**Asrz#sl&IW1Snf$)a(P|y(hs=xyp?zrbGH|GOJ#(eg7)--#* zB0NNIU2`2k(-g}tZ}3bCn9n$omS^cGH^)zEx820j?1f0Fxs|O{aCx}K5G$xz4vLy> zT7yBTx#0)e${LJfG$qBJ#bOgnKb0*cb>1fU%LQYww4m{_jw-D-_)#i_3^LEG3$)y{ zKBA|Hb3X~XSu<)?)_@$*8kwdP?Pn)nyP;X5>dd8Ud^4M{q`nAj9h0#ypjWd5`=A{la024k0s~U- zDPNa=v95kG{8hPHdGCF~k=-TJ9d=-f+S#Le&+B~$UU(tV`59xdr%-dNar7A^aFFi_ z{x!IBxn^g%PL{hm18W0l)+50Q0Z>L3VIe) zRRm$d*gG`%fpUXah{h`G>;ywqfnkB#I~N-l-`bV7tTpbBYjbhlU>w`x1*FPwW1e>H zbXRB~TP1f#zAo-5yQ?vR$2=Xc{j>8gA4{INgAd+jv^kwpc+;xYpx~}lDu|r8*b{R; zius44%WKl8nvvBVtE>ULlfAmU0;mUOvhU+Ngf~rUldNe&fer6lXuN*lGGEj`IlhQ~ zyeq@5zSuI`qHw)aKx7XxoE^q#Q0<|>P$Az+;-KbOLDzUb=q}xl9D>3`o_gTra+A)M zD+?}bU}FwN(P=VwI=l6kJQdUD&kXKAjo^AjmML*w1^(OM_=i>XUcYT;{!Ka3`i2kr z{x=4PnTv~&h1q`|7XE)Q#6%f|K|$288NtHVV8T8$f(%RoDE7dY$k9;27?@e`@LH_# z5?HEQE5O%8Y7|UBpuo5%jNHX&Yq?OEaD5&LA=$~mQ=!KJ;E1+z=pOIGe0ZV=6+Um# zVcnf)c7tILA7<2bz(gm>jabH%kah!FOyZlbQ^ZypZr7Oy8*Y-XU+l3JWf9GTHBIxG z$OC8dyC9G(91yTQlJG@$-zb1=&Ujv}o2jN;)8rYxSt?FHeJEv|l2oeToeGESsEW@b)Q zW0adRH_O=h_utr_{~%Ue@~BN2FhD>O-!aMmkDRf8Ayz7uM$G@iWQ6kkCnWidj{|e^ zm<)5KX<*Q|qHGRns32ho7-DimY4`{!ZBt}&W;!DyPP1aLn$7?^KzgYtSe>fEsdBkA zlrmaP2W_*qt(Cq)v$b_+#lH@<@?rDYoEa4C&s&nq^*3PkTywq61M$<(RUi<%$6Sm| zSY4ISR(u#5iwI^pn#o!W)`>|*vq4_+eKzJ1-Bl|w1a588z;-0wF$yhxpBcuVb~Inp ziHm()#T}Q^euP|?=(j@iEsJg2zV!QMG+oDdbhT3~p$_=pFtn9@ZX>f0@D;#qI=U@N znM1?A_$H}o8_WR@ZO2-$4(&^7gLgGnduC&D!U}W1UJq6+jm2b++Ruj0SV6ha%~3~6 z^ykoh0B=wzIcQim)N;w;<|`SN>;kWTnt)L*X;XpOQ4M)o14sQ+&AloFZ{hUWvgabf zS&nc+>vUy(rCYtNW!{dbzRkjXW=)K;y8J$s8IcB0-;rpi3mjKa`QhU7>rq=O%-P1e zdD|ojCtP+^hkzp95_jw45)AKc2RdBiX-(y4f`C7~kb5cvF=eb#zW8s4r-&A>D8p`a zyZaUh);*8ab12$7(T`w-U}@h}0r3i3cnO|rKBRb$RP{?Zh>#p|e7~&r_2cVBfqC8I8skXbX;U(O4NGn4Z`w7oJ*nnb8|g5m+8oC!>1QSEGD6XrH`Q9e-5657q}G zAAZOM1umaLElTj92+^VDcWzgU?SC>J&&-WuQNcMYNsH`xT6L>qOM`(7#`~zr4ydyD z7w_|L&fHeM(BTuJvV|>sf%qF#RtTMPJBco)R(T?NWZou3N8IwTJXM?w=%e+n+KiG= zA-qZ;|4;sRKyI%&wYDcXMooFT*A#bSJF0XvV!{ulBXCs!iK`yKL6(8Yimx)NszK z)8eYLdiM8kO-8#Js0efl_tAQ!DFqH@vg0dUosZk=mwRqi2juMzQ9@O=cn^87)gNp~ zi^6F;My)JAVySIel=6!%m39mJkuDQ%MSp& zj)~oo9%+NCXk{i;Zgh;J*exxSwU4$HI|k%O>WNPRSfTvXV9d#4;sI&E}o-;;S%Z5W53R^&b=+a&R8IbkBXc@ zSrI+-m2vvp&$ zuuoYur4Q1owLb|fLuyAkDhng(kUEU$vdrw8yAzd&KbRX#9x2#fZ@+6Xz zI>{^2J6^t~00R{4@h?>}0f{CiOcO=$LCqslRTcsI5C&s^-ya4;?_jbQ2N6sL*^O7D zgtFtB9JjA)Sm()Jz4Ru2to8SQCtX-feC-%>r*+xlhl?wX8BT=s9(2pJxHaRv%c_V1 zf3ThfP4-Wg+;OI%+i0OJ!kh*rx9G66G&909C$GCCtqX0d?9ZwFr47Fb@g2~obXP%W zvQ5*^@VW>p?JSzNZ%SQn_Ax?xn|S^7LS*QHr#um5dhf~w9@UrPFr;4 z7hpB9>5R!0pA{omSp7FApPWUNPfoCK&9; zhplSnu6^6$k}t)W=3c5)e=uxNanl|tFzKAu#hYMjAYBL{a`U?I?IZbl=+3E0qCkHt z#MI31;s1QA;5M8%lR)G~BhGb~D$`E#Q=tuKEu2q(l`v)7z3R13YAAZ+sDVqiTT86H zYfOCW*O$qv`8$)0UeHAw&jq|nlmo^OZo8)Dej|ocF{lN==F`;y0U1-on9(!ScDqwy z@vONC2RZ8QODV=>I33n@nJ}`p6bhGZ(JW))f<;jS0ZN$gAR|#X05`|!8=t&2RmKw| zw#WIUN5~`Vy=%>FB&L;k`P-~d(f#lwa4JVMX+PZGe480PnQB9IIL?5I+hkH7wwA^@ zqunI3`8R*en@M9b-kA_p{L|i#>q&O;5QfIot6`%=c#FI#YRNgU=H?0v63;V3eI!kJ zY!z^Uz^F=u31r1OLeK^0kPHqOT87v^Fxz9pNu#c2*@PhryNCnl>)=+i^e@Ij%!|$7 z29%Og$SGd@vOppfrl*1&e7P|S!zR^|Gyq21RPO}8RZPAX6yrHP)Z5jnd1qqS4Nbn^ z{59GLm&1y#W_)>0R*Lb)JxHbmnkJ!XNYd1Og~76tA>N)SvC)q~t>T3lDS4{3d%+zG zyO0A4%+bpbd3KA@W51K3)yaP!vT{n_xQVa5_EU*BX2kGU8e2dyv%z(N2~1Jy5O~JT zdC88en4!Am@DG?ut{&>g9QI+fD3))&neCgL2Hixn)6q+-|PjT z89-wQZIFSfUCh@IKkLl4XbaCA^T+bz;@rF zetCPrBkYoWPTg?h|LT4>*+u+9^o%$7bA2G^8#?p?O>kiI4kK~H^$9q53735#_>(9M z-=xfhHg8E?#KgNiiKbZ{JojTv7(>RrOBY)fr<8e}SZ!Wy zp0rE6s@!fK_fG1*LPuNJLyG3aUaHuqgm8<7?1caH`^>j1I4`+v7WkB%o03mF$Er}Qm&F#WzVPxz4bN42q|=~nrl)7)IKNI<3g{JT%rUt2D21pwyzOZ zlphPKQu&#q3&&+r4`Ei=epc26^|H;UHYzN5Os%s@AFm&?Ar0je(I9- zCL#PZ$nAD#&PSO}T!1y+JgHY({)w?yTVNAtR27_Qi^)*i1>cq%T9~TJOagSf_h>Au zsu5w@U~s*0B3koUTU*ube&jZz5%WK2y8YN(t5vxD%Cra58?_e$&Ut_Ko&K2D7A z=Kc|ZwZ5zLiwiaz3(ws3jvy+)xa!mwVn+Qm01x-Qe=~@Df{EP|^~oWzp2kh_mP!7P z(hVUZKD1@Yh`QknKlCG}c=q1c4+DP@1;>u+w}7Zv?o%>yv*$NtosTVs#<}voVeyUW z)_k`!8CmLZ;?x37Yc1@3_|l;(4AS%A+*SKNbS<6>h%%dzbc) zk&p0u_xUd&5YPbs|IWx)v$y)*r2jW(SqsKDWhwRN^E3y`j%@hh^fOb!FIeADpk%Nl zq98_aGaRdmd zvjVbjgyIhHe7B*>mVS`M(Bsq($b2#thcCAWv)$m1HYYDmbKO&H?L#YgN-TV+f{Fd% zDP`zEaNw5Uo67X!dNEy?ZcNZv?J3YeT(U6#N<$R@C5&QvaUu}_EDeyB5RIVB2q8cy zTs<(&d2c0*qXncz6}Ab})Dqk>u@WdDw0mty50Yzq);G$`diG=U6!mME4GsscPS zJmv>1VJI*tq)|VRFsmWsiecS=ZVTZ;!W_^M_9R;d0NoUu8Dm@5(`9cetR2&o>9=vh z2%*a!1{OFPD5TH`kgy^X&?-xVg&_i+C}Ifh53PSDOmFOj1ORC24kQ0p$+)pz%@0Zp zx}?AgHnf_+hd~e1SnO2!@D3 z5RBh5!u*~Q?Dvd*D0u;as^G^A=7IXaUvM=X_y7N9EMU5$L()*j_ikOZpK!0ZSLtwn zONL=EMTsihIY&dtbSX{nDW8! z0l^BE{l{er;4&PYGgQhHp9ijsHE<3w2M0*|yP0aB74Soor5O_jGhAl4tcbX`;%E%5 zww7j)tjIq-x!^H)>@eWn2_kl=;Dd$)&1Xj_f$CL@z8}Rvd$O+76Ln#%VqkG#>VC^W zB_RD}QC3>e6Q;Q^Gax#2w`NRm#n}^M)WCE$sv%~<&W4LYWz>1HOtJIW%i%3TnbW`s3>kIbS3oT zW-JiKQ`GvQ1z|d&8BVXpk~cN*gPJZxZ=_&igRx7ObRyvy4=C0iLa?|yrpK)U3Mf@O zq8FPktuRckaL9O3neB&YCXQ@^tem2ZoMV%4xG{ocnI~(463_{GZ^d87#m&mp?xOpg ziLSKT%d*MMmhd+mZ1oy__da!}NCuH^m=KzlXxCc(=BjSsUI67;U2{5${a}mbO6S%w z15X4hN$p3-2gxOCLhZJo&qZ`$UynMO%^Sr1am>|v62aBg8nYm>!=<9bOn-RZp8o@k zu!X757k;hv3Ot-c_4V4;LN}km4o{2C%Iq%w>sI=8?e+Oi+765IPydDh+mV~kEfSd) z=;xxsrId@ZKT9?q4;Pn}1SeRV>N{-An*trNsGEg*pLQQ1e4vmy!qZ9nJLkQpi%ENV z1AT>h7Do{GLwqD6Ly_4_;u5bAXEVn_->QglS7&Efm|Bg!78(01EyP{<#OD!#>pUmr z>G%!^r#Y;*-U^9y#X9XrQk@sq;_2~E2oHZEZb^GhuZ!Mq5Z6ffi%#6(=Lf~^^xnMP zkL6j97VfSmugjr)ETP`S8`}!M!PMIJSp>KfrCM^g*oTKJ-IeBS4FPNHq9YR5^I2>6 zCj#BPBMzl^h;nY7@K;1b_k}Dro2o>SX+eJ)F--ENHWKC6DEQ;&82FgkBy@*0fuw7E zZGn#RfDOaG_?G60>c+`o3(xPrl#;J-quse70p|7s`+cM32RFIGq|HqOL@ur&|EeZC z?4EwDvEN@ly}yagjiyK?Nc(e#rJm=rCpXT{z2!cZQ@fEdO>@6zmur3QBAwnk|L*DK zb~A-b3{2v`VwL3UnHB~(miSmML9u*afTF14B!0pndzyL-2-E6BW{GbtZwCoejdWXj zJNT;!c+&D`zPV{GhmK{Pg;sLJx(0ZgSd&k}s#Gp5w*RJFZ$myRKB^1lvb_>NH-Wl$ za*0>3X4q2I$j{!H?JzW2C^{OMNUP^G`BTNBQO|XL(Rx08%KO}N-`%z1(6Kuhi}7fC zdO6{+)z%yEYdT0TM5LR6V4H=x%VTHtlZL*t$>>T=Eky~+rM^?4dR&F61`G4I!#S2F zdyk!~hQFifA591UXUQ)_y46FrkYTd`gFs=ZjO8>Sg%_yt<=uw>sC%jV%FxaoUv8ta z%u}cZi#sTnJT3vF@X4!CsL6+lzp{hA%f9qR_>2+_!kwXOru6a|M$HsPM;U8S%~Q4t z8F=OvgOjXkP%JP_Fq2^|$v+=MdLNB*v>Uo!L%n8w^{dYUJV|&4@aq+i)A0UouK+?h z$5ZpRy6;0$vhF_%iTPSRL?P}?NOFec)0$ax^=`jTyGcYSeC4P6te_-39%E)hl;q2| zY107P!f}(oMq6tTq3ukWs!Gtnl{mZ2!ClRJ{>$De|8w}aE>Jce)4iOCk$dqZJRFtowdJ=%OPBUtB($BcCf9jf#m&KGdFhJJq05PXsanZQ zj{dgop+&Ev0Yt=AHZ^64)KY3Ybuqg~WJe3jMU`Y(W~ER!CBs+ODXKVCRFbOMl0>LU zR1IU-N{Y_42;@I%NUH zh(&}Yw=;>ekTZeB%d=l+_>U-V0w4TEd8!6#0_AH8iOb1n+rr!%R?`LEVJhbk)M89CEHIP8(!->sHpyC|5EDF0rVFxk=|xf5Ba6|M z5sS2miR~#`ptajgx0q9WO5j$Jp|A8=J$|C0y5zJ}6}?k| zq!#;O6wf=Jl^Z~VvN$dfdeKv_K?qG{@SvJdj_kQ_WQVi6UdO-va{$6ZrN zAS-%(x1nmhBuBM{SkFaP&PIVNQ4S!QMGgd~+zmeXQE9fGwYEvZP$lLMKBTR0(_GXG>nL&WWs5G|DX#ouZ5%C#fx8*y2|N zRBV;es72&eswgq8bcKzd z*>4r_7vd~gJb*lk^(y*Faa0D(0Tw;nnqL$3Yy8V}78kssQmtrGO>yqq)J7_Yn{sND z)JoD^_C))U%iH(S#|2$r%mQ;f2V9+q!k6yLvb8&szt(Gxo!2Vsm~brZMfqx@J6Vfz z?yUacQeY^J>rH*3USctg-*MF|28}v_P#b zT@>P9Zwx_$m>4|m3Zae9LFLSr61|Q%@f>fp8s}+$kIeeRs_1YU#BD7;>-uXhyPytG z$G6t1^dO_~Bm#F>GYenrXKjwUwO}qj%P`5?QS$zoJfayjKVMB*IbC9*SF7wDn^ilb z40v3#F4eEbpUdr0#F!Atma+6gZKtP8c+2RF856T%Nd~ARBmG%veeK|Sp)J~%W^EkVF`|;CihXfY;zNp@bGq7E z{HC&9WX(31!^C~aWAFSz)WZSKp$%|@9BPxKOShn++N0I{jET3_6@2`OPu2*czbMuh zwl_iTC^(3GG<_r%dsItq)zz}#%!gE*rHw>=#{^$7-L# zw2hv3Y#~xshX3wt*QU0ds>`!YtAA=EABo`ZPO=v{J|z>Pypgrm{rPA zh3$v5^gzB;#h_mOKz3&9SIMnmAl|)f-m3Ln!P~nU;aZP9=V$$Mx;pz&-K~4O?5ZUF z!>`MLufStAu2scP?pk@LF#Rk3(f-lJ?^_been!q))u8nG^!zcSPvm;#`IP1X)VteX z)4+|m&?>(>DVd|HUKs|u(i5XMGs}!&!C*I?J=gK^*h`}rwEYv70QN+&@R~)3YpeXE z+iS;}vJQG1VyGuT)o`Z^@I>c-KaYc1psIF<#tk*%lu2W&v$B6r^P#V+;v{)KYC^q| z#=q7=|7Y4t@9ou|s;hk53mPqcUSmONzkqsSW%g|kv6o7HG|!WBfwv*nP}>L6`ceW4#a&LoY$Jy-H^%y|?0adEQ+bb_MF7 z2{=E^a0#iJ*nebkaj<)yjf8^UdV$80rZiwdzhvQ~_%^ms@mM^r40uQ& z7NFF?%s}dX+J4)9TtRe2xC-D!kRy;I;4`2{cp-XOMgiejL^4ZPD0IyS3~k6PT2g>* zOvBnJ6vhVeAbMnagE0+FTca@#4ZYb2yt=MAO6bHT=^$EL#tp2FaL2s%pc=cBXW1j9 zhEBd?Y_6k&8H}+=v_*}V;k!hlfR;@vP9l4Bl zwon~Ax;=KDbjR*oiB*SlNRL(T!CZC;PQzoAt0uV6khVm}-}Ho=b{NR|_SujTm&Aj7 zZJCb4^yIj$bfG?%$b)`usg9FvNLTHbp&F~Dp&G%XPW@_upn>gS+ytsBwh;4}g`AP3 zF@nUAB+-nzam0g4qFm7F#gR^^PH{z%#geOLzpc7)rR+2at1q!6ZMu=Y>@-pq?9ZP)wlyb;UVex7*j z4vM^`A1K?7LA-P+e%RFuIcD#ZaOn;+zx71CV@H6zeUy0XPNTeSFEYJxDe1itU(?Q$ z@Z?T$w+&Btdr5EFhj+M0u)nx7RT;ol{>fPWi7)?wRMLm{XLDzw^JlzEK#WU(!0{)V zs9-0@SB15wVpy%;V&O+g_EYjztWmj-NvMC(!@+#K*sr39dX$z=%MoC)Krln(0!&5Z zGk?XY<|~t`#EMJKMuoz-L3|~w+sLsB3DGWm<~Lrn4Xkj#FyH99JbbCxlM5L!x$M zd}o`ah)wFJ1Rb(%Wphr$@Wg?<72t5b!$DJo&V;)J7!}^bK~;oK#Vl8*LfTVf@y>|J zikk#1D}>C%+XSoyUei%mgiIvQ-!WMD%*FE}=93QzSZespCTS6q8J9#XN8dFy-(8*( zu-LvEEPpq6Ou)M5H5JeOE`4SXGPyQ({FaZ4>y;U3^uXeLVh=GvSX($G@>82cMs6ygy0X+W?XnuI;D`4dbx@t*{H4~2T6 zLb+ifCUn+D-BdR@(d1vn3@6Nnc_n+O7s-&MvDydY}Ps8cc zr}6SIUT`SO%=Gu6npc)NgqxpnLd(c|hNvqRmse)iy`!B~I%^%{w0>CUriJGWO%N|Y zbYqkBl+SWe#K62UY~lV!*FfPAqenCBNbvSHbA- z=OsU~%4uQi@0YPoE^}DRKCvQb`xi~a>F>{|o?2&hnEk{VuBj**M47&2jnQxPiFTg_ z>=gq_d0>?@SnL_vEgf3+Sklt}gtnFu)FLXR-8j>d276*@SS_ldO;XhiV{O}~)mhh~ zqH%__lmcx%9^8!Hw}#!fCS_=wT<2C&*OH;(EN^_Fn6;r}7Oj_~=!@Uh!xu?A<`~@8 znBK+@?P#6e_6^GMEU3*ns7;ReQimAfle%6d*%c2#?s_20vi=alQfJUc+#yVA%E?Jc=$`Zp72rkkb1M(aL`W(YP4le2x8>~5R-zvs#l{&{6 z>eOxDs@kqirbCM^#~J37n?(5q+8jhsldjNOUK|@)(FMjF1Lj;va1*c4T38$#P0ZtBzful3i%g#umdsxA5477V%}alHC=`-WL6UF6BVCc$il$(i0!?r6=(v z1o5R9@g)TVyD#LFL`V|jlds_==)?xH;bmao#$+Gf*41j!cV$&dVYReQ(X>w0v`*Qy zPTjOl;k4H}M)y+2C%`8Q`!;m@HVh3f?F=uC`!+^Lg!o?OJnziS$q4B7h+C(IMG#0JnM12siJNAw4}H6C;@Wi!dGRVK;G^z25T#9|%I{r7_N|ll;mSc2j}jqm5uk1nU~V~xNl*q#?Ou%c zt*ZyMel^3iD9V6AeW=K3 ziI;aJje%WJzM~P7aa?-G=PHM1+TH7AC2xLAl&{2Oe57O_QW1!#V?`-rrXcapxD@fh z*f*TAL8U%t$c@QH79~N7P$Vmgas%+tx{Ih3;t;dOc-Q{lNU zO!2!!jfmJQ|EX`upNcL+%2E#iI%`0BiRR=d|6(f1fQ*VUR>4(&zH5VIqk1uj)9D>;~c}>}`q1_t->ax_J9DWJGT{8Us&!`Xg zl;x%Tu!6rx!Mf?d-Zg1pj?rEO#?%Cn5l;Cw{PRXeY0b4&_gC=qhBM;p>n z)RWsX$P>-`U-4?|&c3&MUt?@H@5%FdMvijb=zfCX^sRZ>>k!Ooi|?jKTJ5WIxc}BS zOWTJW#gM|Gweo~=vq^K)QoraH?oN%D(-po8bxy{;Fsj`U8w;*TxDBJTeI1gjLow_L zy3Ihlj;>#;ZYZ zifL-pl(pZ0&cse%Z^B`+_PjFZ;)pd;i_KK4L@|*Bt%~wS*TSQ{F%b{cfT!IIrbW7S z@CglNm4~1fHTJ=Xm6Go$QsI#=y+zr~3 zdXMJ|bU&mjw?NRhejfXIf>1K;?omoRArH$hPA?9^< zFP30W?#;TvuZ8 zwE=>|BBB(`I}2EAhFu9Q%|_WiTl0D8(f4v`^zEnYqKsb7@7Jy?18sJDJAGC)xRJ1l zZ-E`4L{6DHEspGhn1y|<39pLh!tdbqJaE(h2PvA#k_znWEC2g_?V zO(w$_oAzPx@h6X%7{Tf1Clu_38|HQ!lxC7-844Me8uu5U7e{@XJv3?w*e|}@%tL~x zoagZ5&L)NRFbRoLiI_~^%a_{A8dNLJ8jx3uQj3hlfv!{w$f6xOuD{`mUyKx!o0u|> zg}=><-gETF(xW2C9JnS?v}|z-H|nHsAdwc*(T#=CR$J*qr5ohf%ro1Kz4*C6xUL%g z5;DeVy#R8|(>%girIqkhsW^vq$Uk=V{tsMtgjoo!e@a30FDl0WDOmmgrXafT7v1JR zsUS-G|D%E^0qG-d{y(E2`X7Js|Dhl%3n1_#g>3IuN-3y_+~D#OBV}3?K*D44qrlx1 z6f76pycWkcmnww#Traz|n%#9baenzae1_|%Yf~KGS227gfuv?V7oEXS>{9lZ$kc#prs@<(rq4SKi^1J#bAb^kc;BJWnhvwIwrXpx&z# zdNt(YB+|FGxSMwIhm%>&u2z&P+_6uR3>f}8r3y~XGTUNc1J_#9hF+|fW7mAjmmd=M za=G;HNY?_PLEhz#wFJqls&eaHa6Idlgi_Xpr9S%2{d(Wvf^O3q2-7QLj`>Dah-od$ zd!~!tG$8X8em1iBoIdhO=dL>SzMZ^rE2RiRD3fr;gU6HiJ%0P-ANP9skvs@kfu8V2 zqY>f{9h2A4E24~Kg& zlSUmPEE?sp{fLN+4o(+YQ0;Ccbn;~ZxiW_1m!D#d3S?Mp-wz!Zg1!jhb=tX~f7jn-ckq*@^&c2cCJat%mL-z)+-+dyW`5nu%-+oX#`hJx zE?ir4VmAXpc=!k1QiH_4Cq`piyeJI2Ym!UV85Ofh3*K@(BKDlAbE@F_BUgZ7&uQD< zslyD$ci62<<)W2)o2~6b|H4N}g+yLTleXpi0K4edjmz+i3wVee+=L6L&ZT2J_mR$m z$1?mezjdcf!ie6hFEwST?G6`VwJLT5*TMwJ%Q?-6LG))wCSh_xTa0z2yvhwPDF=^d z_?MJF8=_lVu8V~|sZN8U!)k8KMqc60emS+6?6htEG#S_^Wt04Q;W$Qyd(0MIj(+NS zPPLXiYF7Ef0>M1aSc3z)-yp}+zy82r0jfXom}du+Skxd$c>9XGz*u7XdE9iM)rup3 z*K_V#5BoOz6WpMdYSm(zYWZhps=l%O0Qiv3U(AE`ZhL|=9P*+O<%uO&DJ2sEy!~@c z&rN63P^=TzU~{>cO|{i?nYo~op}wLSbnt#lJQtU7rY7c9#&z-sMGASU?})uY5U_U# zhkGP+2Jv*fAX&vQ;MWO2suzP}h(%kZzrats3*4g8?bQysmBdH9qa@I+{OKGBxd@P^ z)PlP3a^>QZ-4Ub*>hPp>2VgjHz>_ZT+Tv!|ENgpV)Pkw1;I(4%zGAV)g@av*po!^l z7EmFL7E|f>n%*iIsp#2iRN_y7q^1eV|HrDw|FTCT+fRnh7W#$` z4o>zS`i6F27+gGDlGWDj*2R!~met`fzSM!P(fv#-0zV0hS#2}IllqMJ9awEHVl85? z;Xo-u`Oy9r{>zxQZxwS_7-l#f&2vK*v)2({!HYN0^sd^JXj&M6*X2px8}W?y>x)&n zI}e6K^_;`^W)W$U@2y~oEJnXaf(+p;K+te|cKAueRg9}jVD&}X zqi-Ge11x_oj-o(}o^M*TXjb*!8WlVt)LAj|+yM0MMj@^&{itlZQ9^+J%&M1Fuc&j? zyx8@=j|H&ihJ0G5N2T6A1v#TNZ8_dt|G;j(S`sUa?7Pi-_868kj~56*zP2-;usLW? zA&i;oSVgSexsi-}*9VH-F_z{_1Pqgwi(k zY}wSp(>pN>_F0jO2>#~5WQM*UVo-c<%zMr9P23XOyv9_cV7Ce`YP_=RWM1=LqM7hA z6t|*%5xk6{_X1>&!C($NjG^}u!~}E41?b`$^Mh-4{Zp~j%EWhSm4^bmPZ^H|iFzvc z1#X{cycA0jC%>seR3*zbt7lpkSrqeJ9_~uGl#Vzy}gYf1&{|)ow;q?Jir7Ea^Te!bI;b+l(C3TR0WYf5fg+ zGcv7~w(gcLDy1^57PrpUE>pjtIcxS@wlHay$4z#T;5rnlSZK(uw03uTy4Khy4xWgW z51v|V-j^c)Z+B*YXdH|!Y$TKu{m_ls0`j456bK1Ghd6oCjQ*S|!(UBEd&-3hezPz5 zB2)9^el|DqB3N#uT}bEXG8dOau0;8IAV(QpNl(8}{+gg$1)fA{*$cu=yKHn?%WjB9 zNwBm>ioTC+O4J(xb}>Tlt%!ad@e!F9x|?P|$fLrbuEU&TC&kB=>P2N5+FG=mV;*c; zL0nZK%e=0V`cjsIM6-Gj_hOKPR04090ZUmLR6v~cGSV#CUN|=vtQkW7v#0^NiRMD4 zEfw+Mr`D#Al-Ebz`nihL0@QZ9s1HS%!L5J@OYtU-Y~&$9rw@e@;Z)1u=*I20+j&kM zoi~Jg33*QbU%78PyOA1(#MH574Jh!qtIlRc5n^~lw(STF2n*|0=LltTF{ZMkE+fe@ z^vjmDVSq{L6m36vqTC~&Zi7&j*6d2}M)_wm!WXlrpJwhaX1_x12WMP*z}vDdRjWcU zYS>L=M^&KHm%8lAFt}ei9M#nKU?^#W`vOT{>YFY`XOopyt|1|N1k)RYnD8qgHi0e9 zKV&?Oo=Pt^`7JL$Tx838ANV=7ew~ItY%fVQKJZ(Lq+DKEsegd~Exq|2PKv=)6;c7= z#2WRVO>h1_oEY2N+5)uk0THEH!`2>09O*;Hckl#9mBUqLF;-Id)aKJ8O^`FE#DR)p zQ}0iW*OlTYl)AHz%#Xy6`l${7$>^&(>vE{f?AGJ-bf4XnsWXhOkeqaj%pcSy2o32F zU-K!Rkvqn1%2D8TQ`gJKq8eu&`!5Od%1|BD0;LTuQDmu5%^L@z?%-Sn~j}RnjMi__~HTn8{=tfwQVnO6_(QyFs?He*<3z zUc#zqEW@TIo$=i$C1Q1$B?Y==UFg z&)++tztMmtNdCiM=P6Vv2nlY7<0N+^AMPiW>-Q`DUawXDRmf;wVS;WgG8Nzw%d%Wg zEtyL*dRwKuWFe=H8B}LW3XP0tCv?lO4fbpeEh^aT30bMT45I+$fbhu;lfeKf!evpB zY_&5lV@gN9!Ey<6{Rb)8hT4_Bf$E+;xan-$W-ffJuz7zd;TOf74A?`+)w^;E8faL! zKt2q-7M?So|?H30|sz>A{;a%6ES}Jq15@Vcri~ozv*K)$Rqo%?sPTfE>7K zA`bYUd%Pov$G}R24ZS%+WQNmX8;YcbH%gP3qQK)w1FbiWOT;G_pxhV#?y* znF*djf%}1Cz`zI(9H`_RR^Sy@5k7hH{*oZL@((5?@`W|08RGo>SckXbJ|mmdz4RF` zrO<=0BLvN|#LI{aFXA*cNH;NC{H&l`vL? zSM3GQH@`k6(ouVbitQB67mUZo(W~l^y*$(UZK#(tyl48FQEEK6Ix@LiTxO71@QVV@ z86Dez`^_(5X>RP;n!B9yvHiVgPuPoHft=jk_Dz0x$N8KMSg-3N^xTPu?L?<2{+aBo(Zj-U$Q(d_Xh}rys=Fk6^lixs`{!wD=KPfyrr-6aFCLK1zCxx zzUwY7C->C+IBV%`UTd)sz3kgwg8nw6`X;hlO6$9(AGr0cu7XzCGN&U!W=RHPYyXG| zDcAoQZlD&_5pLi!(y)LIzC^D82m}gaTpof5Gt#603+SvXQGh=b1PNAH8R#gs#FT&& zM8dLv6xK-C?}wAm63`}CafSjR!08<}WT5eIB1}k{xs!#M^s=hqD(ZZR_=%tt$1((q z^ba)Q(-Pidd(0@g@z1VgF@U`(dsyLK(2~6pAYv$t2|MIu8Sxfk1=!)f(BTGT0r5~` zBzi&#%n4cX2T^3duE;Vkw8(Kj7a)`3bSI54lJtkckurU<%rqfOh?fu}p(W#Xdu1N4 z3h(N8V&;a*pnpCOQ0MFTjt|27kQDg!gF)tnbG#SGd-eN0LrzDtiZa)2y3MXo|Hb3UU4{n^Xb5qb%Fr z*S4F!+}7uX=NJ529+mwnv|izGA%e z;plc$uiP4UIJwI9hq?&6Wck*N$!y%(g}PHGd%sGklGN(nWZUi5+^wekqr2CCi~ZOH zU>E#=tr$H2R5kk#?*5CDrHzfH@o$H3sw8cnDS*`Tsvfl=jnc00u##h8R9S|c0w;N( z+!O*V7OdORiT1kf9wHv8!eC)|kar;FC7rr;*J>sNrm8uB(d-T^5W&$$IZWrfAuI_mBxt8) z^yu$RFVI5)8CcJsZ$hPtMz&A^JolEL9i&l}L@%K}vA#*C+6>8T(Nt5yfRrYaRTL^3 zndu;|_V|VE-NyLP1HlNgHj0!kH&7nLm6Cb=_pq-Qt&bcPai?XT#LaFFNroM%XPKsPj^qi-Q^}Rv{ zI98*vm}Bo((qVm*N1&%&r?54Bi&uC#tjq4}%rSFc|FAUYca{+ClGAkn06}H}nF9Bp zFdI`la~n%%i{Dn`sno5-;!+5vk;wGegKKf0yzH=x z=g-m9M^O)>QgpL3pGr^PX@;!bO+lc7r!YWYLG9~8gZzkGoLv$k-6)fy=u1doOdn=f zHb7;i3eRIflPVPEq_SkapdIh&5(bO68UQI~W%k3W57w_?n>|K~Ng^dP=^(k(OXTGw z@*)9kp2Nn&mLh|0Um}4_IH%$w!BNgfO`v|F&t)y9gsn&^n+zXI4_xISznSbEbZN6y zUusuX{bXF!MmT`(ZH~8mcyS&bn9+v!E%xX9)Aht+um?mroeG5b%(F}H9FyviY7!^( zm)`gWXT+T07Dz=eS`PgtDFOUpOCmbVjJf1<1`;Rj^D$`eDj55)|)rA0u^84 zliO*BhW_9Qp?ch%=ZV>)g>)jc?WyXq-SPExXVm^{QX)$b z%Hd$SWW;7&a?##@`^Mvf|1|n}5=PSsb$yth-%Z$T>tNwPTh5*7*juO#Qx%wS1K0WK z(csymr;I@tBie#ST&&xnVv732l%hHPo~hAQ?NgTOS(l5;Ic67D5hE93D!Q9jyLf`v zg7@N$q=bunTG}pl8tTW79Obs252)%3nd#wQ`<}Y@4a0TY`=7@{k#jKDhO4l-2{42d zHL6=XzX}HZ*dWb4>%u=hI@JX!P{ezN{reQ#lgb>&0&sXn{%Hj9?|rVBlc}BYU(SxH zlA`^Z0G#i!y2dO)o)P1Pqt}?$Oo1v9P6v!?whI}BhU8^Kr?fJe@0)ijSU`xt3Ez4y z|JTPiJ23Za323Q6Y7OwmFeEjS-ESKBHl!?lN{dYBDM>+&Sf40+(^8h*4>{c{E{CvWHn2YjZ%^!Z zcxU@Ksu-uDy388f;>+piMxaWV=Cg6+4aI`djsqO8COVl(K;S3??@Fw$c)_EZL(g@h z9~a!bfCht0^M`X&%TKmkc3Eary<3@6n=N^cuO|BHH+#;s>#DrtAkl`cA4^56`|r>q zgAFM4=JmlA8xZd)Z;zT{g{yYyvfo|K+*$frb#>2JZ_oR|9%Ut<`mhQ({0I&429?Ec zQEb1~2`iO>Ds93k?QxLdEu)0%GS#?%PE|I(OcTz3xNW?Hxt#eS8kHd9TQlPJTVH^^}=$ zv^+rwhlA}A=X9nd5S{XZ`t>f|RCA>&d!Ch`U~BpT^C{bW-~+sdS6#P=S$oKUCMP_w z9#IW$vt{H|s<2*C!yHA+6=q;Z8msV*DpdtLsJRzJJ`b0l&Q&(*!yoh#e>?w3==x7j z0Q=F>8!SKF73TiiCLu3J>)@QkV*bHDN1pH8WPU z*hT@OMx{M0ohXFc^@&KrfdV7uW>Z=1ll(C|q&y9vhY@CZ;3ufTbtI5)(gTXiB1#(2 z{mcdFJB$d4i(uE1g5>&xODThRDDK_>jMxy;tWT{ChN!F~O5>&4DXCAn`bh{%k zFxbknNM^O^e3~RaMle>y(+(mu38oVz6yS_80;^E*ER;;fJi}d!ohC1mfn}V@KL#q& zi&xjA5>!h?S-Ti$m)cQF==&?%YzNSMiDM^kO&?d4qO0X+v;s%jJ%&#bxp37}x4|}4D3J`26v#6Hg(6ck+4StAc$VERr?)4}^14O3z#ACijVH2g zp+|Hrde9mEThvk{wZb{}QNDe06MCME*{dWo`-WXA7qpx5xwh(kZ_K%0@y$kDmJq{- z(&$Bjib{&iYC8>Dnr22ovP@^N(ywJx)JgF|A{e6HE6BI{ye~aSV|}J*rF}MTwb9*; zns--sXN&JP{z5%1XvS_mHdBdHbI7^K3h`>ZENU{9k!u!*Sw5&ZKTVcgoedL##N0&1 z1k)Zv27`5?qA7(9EJAof1sp*mAfo1h72y1D6gUi9!_yL3z+l{PFMBap<5jvG;(`uK*!VwE_Jm=#( z!!YU-fDjojm^1Uftd%e&>OpgDaI-;f5@w#p&_BdXwi!#?$4+_-yBRZGu5?wirA9Bl z{KIjQrU9Lr2BhG0fE4`C948A?BPUb$zb4?5c3F%_Jy*(6k4YlhWu7+jEUT&tBymec zvjp9YeyVl4>q*y-+5W|%G?*q{N3WUTgl^YBwG|>4^#KNejql;f;u-6r5S8~*sNUJG`OE-zRL|* zF79D&q$hjpO*k~xJ9@+J0r5kHYW9faV*iM-er#QSe`ilC>r6$pKsC5-^B;;uu_Y=m zs%*_p=opA@{Dzk_dU8ziOuSO-$P;5sW( zcH2Xrw^0=0Lqde^^_&x?f`@g$OP_Ps3bK`W$2l5tPesJvZ=AzhESh2N!X=oXyF)&i zgW#8heX|qldhTaJGlg3DrR|KN8pT8T`J6tbLXBK=ev$|`Xy{F>^ot^0hu)W*N=kq9 zewPY$Lp`cr-#$@9xOZ|uoZF^-{6oqOTCJM30WcFjfSLSXMdSXPK97Z~ow=ct4|9mqJQjmQK|n@5G7H6VDu1jar^X1es5SeXM?^#&O-1k- zy&&vl>pqWh4Lh!VN9u|ZUn4#EVjy=x&!t>@L&}H2RIPdTqz$G3@tfrE)~)OHpp>g^ zv~s=N6*u0AOCJJJk-yAc2+LPk533QH7pXa{bSRI)#(Dgt;Rcj=-2Sp0^lZNkwsAjf zE{kc5>8Z5)b%(U$7eDkANF~w!j{aTJL#~I->0(>2ADRO?)>3EoArVYlcz(quv7p!6zyc+$Sj6t&E5p%A3-$$O_deFbzVDOtw z!+K!z@BmNz9pdJa7f!l3Nf-XOAQVySvgU`A8)6OH1){LtUN3Frsj)KqP3 zTXy$JKA>se^dDbA$C49s#{g7BJzrHl!U|mg9^D~0AGshy{dg8MD`wJ-dYQaaTJU_| z{Xo-H3n>%oyv{7~@?UR6J0Nf|qdY!QGF^~y{-YTHV3og`is`28su;it@F{{p4 z)OZ?Oxp>v6*(<-JW2*@=vpcc*W`acUHNUFJVphoPKgh-WK4>Q@pZ0$vX}|sRpFz@` z>;Xyff4S#LyT2f5)MA(9;W~c_+TTe809)MeG^Ze zH~laKJLysj1hh~PLXmMAs<*W4u2{*!E9rvjG;y;IsRx~2JEUBUINv!=qJ;&iqY+}{ zgm>&Wqo67r!+3w}kow^{224MS{%i}*;0lU(>l!#G4Oa6Lk|q$__Z6B{sHl)AQ-v&g z5$1<=T%l5rw@X{r!(+7tVzzSWA;*Q5A(p${l#R@Fqy!*oEd7f^eK)LO@LfzXb>1<2 z&hyx;X_IYp7Dn z*03oRvvjbsw8}M&irHV9gm`QA_19dw|J0l*_Sl2Bvyl|ynjoA}yQ`g0`XVN5@pq zE;_YkB*~g=Bg-CWg@kj5o1GGAH2ZK+tUe7w>wj8S%BcH0k`qU$S0WaT+m*G!Z1*^O zOl56XpN_QtBa$Zi(|)rEP)sHMNhHn5)!F$!tj6^>8o-DIKm)Q0cm)Ox*7hHh&;e+G zAd3rn1=lDUMOMN}`UQ*WSnT$yBvDm1c*NVL&$f^I@yjHX-422=1B-mG1fw{D@2JsF zky`_+5{@bpauhQK7_8S>D-f4Mn4%pbbTlQ$N)%~Bv`boHjV&4wY4l@oOtEPolBsO( zwk?QOX%2%aV!_RXGSBEEi{J@l)&(U~2O{beG7ITOJOw0$CNVVmbSZ@*l~x{VnOF!D z`nEZ7I&(EMYwmEhZp_Ue9Tf}5a%#G`fiazeGP{(gD>S+0xm61*M;^Khncl&u^{{Rr z#PBK^h(#~f6Vk$Jqw0!BBO()!+1b`JJZ{-x8@OUG6Rse)j545RVHWL|c+u3~CXN9} zT3RDdB2D%a{?wI4jbYQ<%34bmWV~~=rPeoE6&3&>pdr~})I!UXDpqZ= zL6RMCxnyq|V%;HA{Up(ih-zUc<995MBtqbQS4fnh6+dk7>hoR~EdJ|b3$gV(Q@{i7 zMoqtH9sCmUrrJ&@_cL-(B(*~Z_Tp}BaRYvwY}4IAxVGJ|6i?X4;+0N@FHw+D<*e~) zbcb|lGuqG#`sOQ-ThAgL_Fx_y?`!AwvaKErpMS z2!_4;8m7V0Kz{oaMuoGAl4>8`$?X0@cv(~0&mCIgU>VKY;OZDsR~YX}={s^Olq4m7 zblCNa!R#K%LyIwd(X6gnFF-OBfk@liycDMJ+utK$f6QtB42RVu5)B@5;EM+W(Q&9yEzeep(%3D9`;|gQf$`N(JI`c!k0XOC|b>yyqCmPC&G9dfj zWw1;H=a0nnIYbL+w~}RWPg9l++lEbE2MLTQ3PubqV@U<4!fw^l-`y(_uqrx5>9tOD z0WmZ{H{STC8*H%^6wKie$t53^K5SfVa^UwB{yY;ZIvDcmdf?(0VQ!Rqm3;c7nl^j) zmWm4AG{HpI<{xSx&PkFDn-hc4ZTYm*FE&3dqawp8iBX4|9rQ53YE(i8=Lw5 zdDG0nBd9g>z4Jbe;1Tb_pz&*n{``9OdV31So0X9aQm+*^5`YSCcVhyw$KYr={Q(qvIEKb zdu8bLW!vj6ufuW%KyaQf8O`?Tc`|qv{b2ip$j->?5YJ+3&Kx1o%aO!ns5>>n(`{V-AyR~w ztliIGL((ll%!yv*IBicVe5#SONO-uh2QUuD!Sy<#6!Q=~B~liY)Hh$Dz9@^d?a_3b zmT|&etcY`>v*gH-4bN46w_8dnv7B%ybRlL|tyh0Fqk)MPnrS1!r*P0AbA7X!NpoiE z=_#3-dJ9pQ!sUf_S!8e9fV!zNm>utD1)_cD9$#SxZyG3`DumY;U5kHcCB55qCtj^* zj)>HeG~cc>mdSbQ;!tfkpL~;uZU~znXxnwA z_KW=)`r`#xufOr_IP&c$ZA5_wsxZ$-KS#v2j+kPXuFziprYYLsDGCiA4_*B3Nx$lS zlSnt(M?;V-p3gt%`|Heu(O|<=;Bn@xhQOhanHz~u; z>)Fi}&GcbJ2bYJ)UNlKkI0i`XnY0ko=lZ(W-zlJrlDPBzy8Mcm-ksUYtuZP>MXFSX zzS8YicLe=WPtyp$43e^_SiCVN_1$zNMcuDtMu8eQIiz7OfJ(BsDy6efRDUYaix-A622cr{ZTU#oSh74Li@Qa5CuJrttsc3h((X_8a zC|2akv-!p>SX#6Xmkr*Yp_vKhXMTgudsLOLnY(v*Clqmh#f^C!| zMa-DK&#CwxwEJ~&L04NSTIEYyTGT**mjqFpP}_DY9NFq7S+J!~m?34n3sU)}j0Tl3 zLO%k7J?qR6g1_k)9|Jg9E%jdq8&2*na!R?Ik#>py0=DZq7rz8y6`r>flO*FroPwM5s z_k4@Yaq%4zvPJN!2_Xx?fUBw71X2!CAD1&7Y$!7pVMlgc?8|gvR|+EqR#r$h;jgX^ zZa3Z@*Lrq_Cm#@c(8!)&8lvMTY(>o_)t9NjcIw(Knj6e$??qDL-;c}T)OPqSxaPIC-!f2gz<;Fjhd?^z3<_Ym!D&-Oa~<4Vro#jL2V z0f#)GDiH^?C^`T2Q}?&y=Bl)9pZU8cspY8WZBoT4;kA>ml2mDeDdMn9R?$7^12$3f4t&)x%u zn1EM>uq={&L-7S~=uqugJ_$;!m3fljIM*-}J7;JUHjluv z15KyIqxgIq?$(&qudMn|Wty8j&u!VXm!7p_>yo;v`Wv|5dW-RvA^~kSqd3;FFU>^A5ps2$FH zDhdb0agS&6Y`Sns_Cg5Z;@WTLnXfi?u1CIdJ;$|PNz0y3b{)!Yt8YAXs|t;i^19m@ zk8-If!f+=yZ`}^6JSxy~q6>7}KQnlfuL@&hulEa2#l^q=!|22Nr`zoRgv9@)^5L(U zpz*)COaFtpOCGB2Ut4+msoo*~zUKU+(fmt*_+O3Y56q>1mz@98O3r`AX8uGt{3BuK z-xSIJSrp0t&SYQ$#WmLe?Eo%7?eZr~=5HJsQ>DM_1t|X)tG0_QUVav5h>_-eZnHS8 z^$Kq1KYIbQf9VA{u@JyQVF(2ywQ?P&Hl)s2ht{=bQ@apZ?=Yyn{_X|TL44=3s1g<& zt%i=7(%rFt2!iTx34_nrX$69D43bCGylo4FU4#OEu?%1Jf=LDnj^MdpdJlpXJ}n^1 zUt*2hMVurXFf7*vScRiY*cLgYW7@S4V+2(gIKvp}Ejgukk{cwjzjya4Ma`Mj8 z1>~^-4;0@+(QUXCCjT8p)MwlNf$EF3F7_s#*mRb4g&QH|YQuw()qT$V&&BxtsA6GqmX(t*a(#;^sLl!Ob-1zb)9|tiC$=InP5Z)dz@F?`l_}G$Y5g zlD+zRjPB6$5$v-_yTKrcd+-#=OQzr{@m&zh$zPL7=Cb{jRqOg+tlA5PO|lW$_(uEB zrLc!KlS$bO$WpJk+3}Hc1=b_dwK?DzAj-3pJ}&){2J$Bi=HD!k{}n8dKV~ogQkU_s*~`C4ApfKJ`u|-pnjPStIs^nG>pvHa z{>B0MLoo7^B3V}9ZJ|t9p|J|06~=jK3;7%c)9K<)eFNxAM@g#&1(~J4e3%u6qVx2^ zqFeB!AkhWk5MR1{&z;EW&%^JVdMsJ+uk!EYSlm&ZA99`x z549b?KrnOc9>AmEXS{R-_PzXd4a%!3FL35cdV?ErA&(uq0#=k~HfqHw4W`H0X8a*d z-MoJwbxbR=!1as8Ca^E5&Ym-R?&{3jyc#CZE7z6?UU63I^Kwo-y8v(2R|(~6*c3vp z?z9yx;I)=7?bM!OrU4-G5;kj-t8s`aSzHB22+5M2&r=CD^hEZOAFjVL`nA0| z1}t-Im{$mQEOYrNzck3VuL))m0NU4lwZNRrN8g{O{@R&N*>^hbWRZ@`Bx$~?%VS2j znN;bnL8AFfBV%FM5R@|Pzb`>4S2d#1BjK|C#!CBydRr8 zRZ3LdEza#tg7PmWI0yv!CV`YD$$o>PV;SU}sD`6zKV)#)6 zRa-P#fT<`x)*8z|Wu>@NI()l_*#M=8gp6f;>WLkd_k0I9r(EQs4#d8<73wBW>f@l~ zF|h$kocV{;7bcv`y*Cd;odWPeXd;*>n(asl zQ80jUOdwRJxBH&0xq7@B#g2s?s4&f{U1x_WEcLewv`$S6^mOb|BC zv~b1Zb*{1WeQp{_slnXL2Mm?v#{V8F3&7D(D+dgfDJRM}Fu&gKnT!>I9aqC^xb0z*Y^MN&9BeOgHA&LP_cD zN>_)H7UOw)C)EDS2U@Xd9Xu2$;Be3Ylk5r|{MSEFTmR@{{)Dysn+5Q{5XM_e0%`^W zbSFvyYncBfoB3;@r2n@TMYfu@^Bx=8Z`zdNE6_Ta`|FAfArUP6CS1{Hvf^x8v6Y5u z1G-u==h5qZFOxue;M3?EnOh&OV{Uc*E=2lgpeG)KbR%L5U@pU%aZA6i-pHr$$#5YB zBSIDJBYB&5q4)wLWMNVMadjvO*vSGWTDPM4@zS)SdG#hXgG|*OKzzo9Ulw>u1m|EH$CSM%X|rW@n#MzKNW@iBGTFu<2@* zY@jne#-MvXtt{8`x7EzC={3@sN*T8HVCWddVshtkmY~moQ)JFXryFn5;N*- ze1=&td=ncYP@Zo#j?w6}_46sjj>|zL%@z1|9Q#H~ddrisC2OYaammZ~I5a=o8#+q5 zAvcsOHa}|;WdYTFgU1&;lw0FxIL(N#el!Qzt>rW?kCD>PdpY+RypbBBG7Fq%4>%=d z5Ih=tz-ZFM41?UPs~ z1+1J0d4pP{g{Ys(GClA#?fmCX|0fZZq|AHf7P0Sy{DZ-2`Z6B;KqKBJwU~J;K^)KJM#* zO^1bKz~hSR;i~)nDwF|_^F*CJD!L-WVm>&QA_?}D!Gr;?&Ecf49W>Lz!^c{*(O^4x zA{Z1CL6^hbfVt63OtuT=-nQ3dAU2^g1 zM&0#7aLfG|TF{NxdcmJZoprAKxqAMEtOkGW=Hsbfi|E(VwLKdln|B*xEac%q=@O~D zdSDmQdmmWo(!BILp?G5SR^`;!+iG1_YH85P``7Nixm4M@LtkyxnES^hid<_mO|zx} zdAd80BW&N(9rMEkF-);1?ThB>E|iJkHc=k0>2j;L>}JCCA8y|o?c7r_<_@y+i>v?oxbmh^JjIaP;RUNPckIGKB z0H9GaWMI<&!`V57R~BH~I_I=ym zdfscUImeviA3_Q7aWu^P;R2Fdv8evOb_ojVOl(F0(5X--Oew2ZRrZbQYGZM4QLyf5 z)B$g4)btHSJopJ3qqIz5+Rh9+Yn%^I2tb&J`4UG;oI3$9F(VNNHKpOrEz`MiAHG<(M9*5ELh|*u60D!I<+h~VQj4D=e z4wh_~@zFJr8(~LpblF5Wb#prG{b*D=?u`OFM=;`#biCZy)^RiHM&$_|*1vy`{$|Lg zl{@`a=dPYvrv`Vh!s>kO4sWvk7#sb@R+B@z{MMXJ8E1fwRxYXp4K9C7SK5IKEuIPD z@qcNH5FhUUo4*`~1SS;}3d1;n0HXtkLPo+cWFZoo04@imQyh5n(wk>)ZlJyuL!ORJO$&4e*-3(0Aoy)dsHmC zZMY;DfR{l`)IBhMP>iP9WZ}0l5nXgYw#{u1!)_(lHKv#(9nMnzh}Djs85Y*%VT!fs z0M~sQTn7#2bNd#0H)jU$vVW&u%JLXFp?26(l97escwsjuc4C?%M)Z-I3whezmCCk< z$16+cPya5HQwOS%wY^qF7aF!Gl8xwR8ZjgMM|c+W%nQ!^5IAx+w(^DQ__Lwzm(>R& z%@P%r0iHv`=|Q#&a&<}+2iTP;VaRMHU${y`d}wj0G7?e=myq0Pc8IheBZUcE4isUY z>lwMy1nMo3t8tq+&ufwd8F*5;X@yCA8PFr0z-~QkXPs}+lBM#p3ms30Umkt-leckv z!hKx=_qohL(q9>hfnD@dKOeh=6uX2ld|q1~1P%1<$7|tcq+ZvsbGv@Df_ptFiu7p9 zuBD&s5Ht{b)zXBWbfk-}d4=&@r8zUoB0#?C8N90*GRuxacN%+Zj6dv~3-CZslm|Y< zg;IIhs-D{-4dA zKq>GOyQdHD4m*Ae|3sVXA$lb$ARu~WEXXB%jkVNT{!5AM9(w#Y+}Cj4W2mw{H|HfI z-X@WgwKm<`lfHMIjDv^A9M+L8S)Sp!Q1uH(R7UtTjEkBvalj1lEF%}l`6pR7LWDWl zC2u`!{YCDzM9bU>sjP38s=cmD0QdRgtmS7X=`MnMVn1{Ds1}$n2=ML@M@z5_84M{j z$?4mwSGHnnLSsAnSO<_QUL?aCLIU%(3IQW{hjO@1e@R}Khjs~b7p6nwYEr2e5g~Dr zAp4hg0A3%4_;n%4v-aRQ4oeA-iF5=QFb(nX9zCX`hoPFfcAM)ej-+K)gHk!q8%&JX zXy1`kJbj5B{C(?)-|^2YvQ_|DKKAzh~qB=zjITjD1f3<@H== zfbD*x49{94jts+VNn9o`o9hQQaubdDB|Fh?BUyOR=J6IU_ZusX{@XY5MzRnFhQIS? z(E^kW5pNKl=&s^O|74AWQfQ@Zj*SZ2e3$NfB<&R^bwKY!f{js7uren}U%-5+=^+f9 z59Y{=HUw25p1vOH9w%s-&A`uR+?^jhPmIV08kz=6!~FtT&K*0Z3(TZZI*!_glp&KF-;|YZ-I=aBzdIhTj1JkiMzntJG^tLW64QS7XlVwH_X&vX0f zMtxdm$SpJ~HXKBgWN+b?)Mi;L+VSg}NH1qzFoMaQEn9>$^JNQqq^MS+L|*(MMae2< zk7^O@za9~qZ;wcbs+y2q&5oDa(V>>W*4?Scms+2fD~E|_vc2@T z66dJTbEw))|J)c<#nCUV$5Z`JB@RLUdoacxL&@}LZP9I)jFXLby4J{CTC|xuRkQ@V z@FmgIM2%%hBXokJsY6QJf+e4ZLHm3n6R}MA=_O~iRhOeIJ$t$pOlJvYyGfQo^c`lx zgCr-(uB9i1@3FV3BO0TBC_{30%kR#2_wS3Ng+|di#P2ESjPyVJKLK29Or8Gg9y(c5 z-kzidt>;V`F{t3iEZb$y7%Hi@l1E%_>CjIuHg7Q_zg9CP@e$WX1bR zQC!obGPjjrkx&#!l&ApW!@I zC>Fq@l3SGZ1<6Z5ZBT0^h)5_Mz($z0prlAKpq*}sDKpi{o!|_T!@X0Svq?lsgU(0{e4`9w>yXR?1Bo<$7DubV=g&)i`)6ozJvAr z+)l~@-avkj9Tk0|HIDanTu!^cYs>kZ+# zVw+Kp% zK2W3qga9YREO>R>ki|F7?fzD;9on6>ris4>N2{{0nE2=gH`I7L%}2FAm2X?=h8Agi z{)Uz@cc!EjG12j!>qH~TUsC34(_Uu29%A-QR>B(xDDvdyNOlBF9*+pIs02KtW2Lxs za=&|M5eh4ZK!23p6vPYmWR;2{=L(O_u@Fh;B=}{2pC-d(>g`+pin-@po^J zWY!G*KZbbYh~T-{?nQ87S-;Nk2?hDbwYFDET3C(l297R{vYEbFVYak^Omxt;<$s>y zu&z2Q`h)`pu+a#MfEjyJ*HWL7HNu21m$ro|XYKtrhn!v~^y|7%wmBf&8lb%T6{W=MwH8roj_7q)+ ze3L;?Gu`{3)XOBlaBZ9@j=j*LzWWZ5MQvj(3Wv|kM1Ptf(Dtw-5v@X$sfx-%UlKru zmBb#+ZBM{V8C2t|3NVnYig%V@%p!b_2(vpdbPT2AnW*0l)idx6I1bTd<5+_?nZ_fu z*(P{>F~i|x$9Ayj3F|Rjy$Znf=#4>#G0^b8LbWl!SkEdU)(2I0X{QghW_3*yn>_)CwU;T8y9;%%@FuW!WC}+-PNWCc$!&$wX;A-y$+uyr9JPr*F>* z?bF^wwtF|mjN9%IY=@m03U9wsftpC*8}jCeW3E*r$eQ;oj`-l|D1=4VPFa+b1cq0| zensP-`V@vT`&HE(A@}m|+?Z8W<*GK*O^NkB?kaoEqeCxuMd+aJY_8LnUc7{OQMHzZ zHf4EU0%G6GyHqw?BI};*_g0UF7mqu;yT{A*j$E108_)LEjF!yMkw5btCS(q~xByX6 z#f;$S#s=-|0*i JiTc_d8>l(pWkxHal{Z==e>~eIZk;#MntTD-k;5 z1**%(SJ?vQj((|sJmt}p1-V|3M-MmcecC6_~N9#hV<*O^(skUO{ zEZ72CuyG2ls+j(09I6@{t3<1oU{^L*)H7BLQ#IcTLiiWeiH&br*_>C@Lw7Ohb`(aG zIhaBmF!GVQ#DO3{MUN^dPDjHWH1)^1g}MxJYrS`-I$?LDC92TdBX9naTt{-p5UbHi zea(?5z+rS=1)F3iHnJF6nDQ+7+^gTtOY1H-=iaxqll{55nc^E0v(`b2I^d-7w&rp} zq(wD*4CK_F%`^V4%mjQ^`)3)Qg9~Y>h|jpllHBg)mz3CISLzsUh@bDLkpnsvwe;n| z9eRFkI*3j}U3(k0ZB2{s=9Y9q6@z~Dq-530T46WeWb3QQ1CqRkeB^wSJc-Sj(XYZF z<`H%`0jj5jaW9EC@RZ1p_Q!;%bX3*P*Degm#o}$+#VJ}^tAI$30E>dM$QbS@AkwFu z$aanAbtcipRJL|Xp+71Fe}?4pdIj4=^>Th6pWiH|F{ER4(CIUSmJ4MnR!J40~!gil_RGo;d z{M07A!n50vZKa}?U_1}eRAH;Jt1bs)1hk;p7d$uVHagL4X@s$}9>nEy9&%wNo}C?d zc(Cvj_ZL1zzn%dONP(<9-utBEh!>dI2vlzRnmQZ#-nNvIe@Q0gkW zw>IcKFF$Pr^W!&Pp)LFxSX>A%5Vtk=T8%Gf9)x>tIbh>Ru}8yOifacf?_4 zVGfS2TBFH6j1lscq3dh(b&v+zeP2?^PPkL2dblrInm?38XIjcuZx)Z>}2qEz@JxJzC9Rw@2zy$z@MT- zp?sHpcK@iWZY?r5ee;)1w_k^ULr*$=Pj#Ok8#m2ACU(Q`XL52L2@vS2ZLjYIZ<}m& zD{)Nz>OLexW->Gtl{4e!26^) zTbpWATaYy;bA0tW!PB~BW(l1&Q7|`DPi``vN?w9rQfXGOE;r|}HWho=-=cT!e%D+JS zPwKGYe4_!>Xg)T%Ng`q)5Xc5pa9LqM)|R5Q!j75%tW{uwJuX@LHX^@0J(UTUy-$!A z@ode6j>EMH9(3)4gRJhJp`6ECx&OR2PFJ5$QP(F$}xk9ca}60 z%6@*crPZviY%t0VW3pd!;If0L|FNA9Z~F544P4S?bi}%9>vGv4a8BA4q+TV?d?Ws7 zL^)=%ZM{?yIM`RMEY`^ z<+us&5r0QV{B!57-R74krZ~@pgVy$6;vp=8Y#`~nDoXBtBa-F#RtaN_rp-!7x_7kW61{s5cWY^ z+3`0C_q2E@+#`W{VN0k@?V^7>Abj z2+e`R1o-wXWx^7!ohg~ttV6-4CCHJb^X~;s+20C8y0YB_MFY zxv1%_?#!QC3_Omoq+L8K58FE{wp40yGow_jzsa(PBLLkl2jKxSdEK|>>f~y@_JljO z-ehRpY)yW^r&8{?{#+LK;4vkAhRq_VkLA`q0 zdtnE(`KfY$mt|)RD{uJOw4-!`uQ%dC%kee^uZag^Hb8aH(kP;NO$_^l?ysHeU5Q*C z+i!=T2Z<_LY^}UCjlzH&Qi@u3FW6SwwNr5Yn6JZkt|0jC&M8k8gq}+S z8k#R(u@K)UdPl(M&m(0VjCbPlY{j3RS_0&u^j1+wpXYkX`s5*o!7g4T1HUlSW@+>> z(~xa^iBIh#Z^j@$4f2RB{PqEX^l(`y?{mGzAI8?kO-u6VZ^3?S8Uy+?RTn+jLb=;Z zofYibVjWr;op-~b?Q(BgVXX)MZbKpLB+`5NUPWg7r>n?+6^H-fD9BdRw_j&N^jj_^ z3B28!+5>!C^V7MY!0JikomfddHRac z><(XnaV0@fKvz0qaRRSXrA6j3%5GD`G+1s|EO$_%X44*8b_zv=U4TwwtD1+9kS<7O zzMbVLO70RP2Y@OB03LtUWA;{VUp{6oTc?a6Yf#zxK~=ZRqZN&!&Hq-*{E?DR667z4DT_^)N5<-MV z-BVdu)w}xxXn96FEJ3UHCpGu}g;ZAhd<-i(dBrYcz*>IgvASxiQW+R|JUC8T?3-W& zA4kA#zj(hh8=7(du4wz&7xdp#{gjDj+uC5a7pY8dj|c~kAdk)Av(&D2=d*08KCQ{6 zR59$yo83u~Xgjh!|LK&1V(3?@m(xO=>OSX6@4j;dUnOSRg|w!~Sj;ySpgVHZ{mL|3 zqiJ95p-gu{sY|xEmJT;B*OvA!UE8Njmr2)_^9Q>(vxe>S=J1+TE3cW)6`O4uOoQoJ zc%G$DVgYutLlh^wE3`2NvwpBL`N2$znSq4s+Yp zvq?-*3WT2-`o)7EAen<*Gxl2>5Q_R@Sg3@xj~QZ-4F$gj~G`>&qfE3yBefW_DN!xqOO5!I5jQ}kmB%!iOQQMyj z&J{$uyI=~JD<~b3oZ#(LRXnht-Th@~i zh(G9iAp9VpeNuk)b!+MFgW`=16zA)Y?Y8T7;nj>WLaw(Uw7Bmfsi7#sn&%>&j37D) z_C3!-n4U$yN<(83jlV0qIKJYsCBVdVuN~F}w;@Mf4*$)=3@^@yfH4Z8bvfv5!5+qj zj5=6|4v`6*<4NMxhh9Wr0SYyW(u|+=C0pgsEZfCEw(|&N7>KBvEfvaQ6qRZUi5dI{ z^>?9uG!cpMut|@8ZxawS=gj^U8I^+p$hMn93x0}ngm8nv)jVz>nD0`M)ntRW9U=kL zFZECJerC?yee(r#A+E<;vNvU%voHFaT4K7tuz8KJ>CLmU>3cy!``vwym`(+meCk?x z(w>V|UP0otjG&&lX?i>h8G|FcnXD^DsgjHXV%;`J!BvINFS%<7*wm%D;@ljj&d>q! zV#9j#+sHRFNcK56>3ErT4xD|<1Rs-9JV8|_Z%@do#*6G}IzL!_01!QA!w<%IC8@)H zejOe?nwui;h#Wt%&RFtL710&gx-B;Nlac&hEmDD#!z=vXyWu3}CFR20H+h(@7Vh{rWT zbEwAOVs-%O=Av;(Vy18}6}C)}=Wx^4A;6(e+JG84Wd39YVTGOqjuSD?1T3_zYhAGl zF)Z0Pp<6;U)JJsCfK=wEs))-%VZ5S^AO^H7`h@eirP@Y>->Sn{MW?Ci>7CqP7o4sb zYa+Ux&|8P(?UEiq1>+mXLpk#cXbfr>hPc9-P>vxzD<628*0&q=(ULd>x^7uhJLtZ( zGVwSX?SOtjBCJ1s zrHc+fM3jH$V5SV%x|ZknX7+PPGsbCSDOgjDiv}Z}fMO3tCKRkrME%V`>Wf<-1<>2J zDXgA#x6azIWK8u=?cq_~&;%hPoBFlop})sBMB+JAT#W&9k&^!;VFk=znw&+sdy@Qp z*SDU?LK0uJ7-2Xj_743s!#ckIvi@@R^5%nw?ra!L{HMR3OVp~rnJy`ak6VR%y#Dv6 z5`@su&+)Jt)G-}%X2U3s7(AlHqT3yd8->R*i$OnQq6YV!^(S14(y%dU^S_Rx0dPTj z_&wy$cdx3N^@g!2fv}^^(ciAH=7+MF9BU>6;-{b3$u(9w{TtdSBeVASPl);Kb z$AdiSgx7{CkB^K(DCNmX5}$&)X{Y~?T+(q9<~&X`n`yzVw4Rc7Y3R(>pA(7@BTmWm zi^WCFmTHGR9bU+edXPM*CByhFtuAXH{p4->LvAG3fy>W2J8q5BkD$L+__=su^m-e3 z@$Z**){}^h!HDLvGvMYI!l#V+vzhq-_~pT1PS&WhS2?-4_F1Q1!;if+Z42mK&Iz;W zWCCtHZItfDIf`sggo?TT)3uJ9jmkBVz=z`XMhoGeHi|2b`SyBD%T%0?1oWQap7Kg} zQb{e{U6}Vnx+wP^_N8lZHCFQl!L!|>UP7C1MefaAswLvTMJb6Eh?VvIN`0tpBW>JL zPklHW!|BhQB9ASf|IO@+l!Mhy`Q2shL;rsjh5t5G`KV6V?X#h7o>9{U)13=iVdyqf zPemSY^*5SF^R4X3GG6g_Miv^%X->b_sWUdb+ubp%h* zD4guD59!rl2mR`xX0vfN7xe5U@0J`J7p|aY%J>q=aO==v%PY&0Jg?nynN@a?Xv^3g z4~vLciN$2;B(b@g37^~pNX72~X1c_%_ncpa4YJ+VJU_;aa z4)|SL)YyF(0DwJ5SJvL#0b_GRByrUpb>JRiOP1nlq~XRn@EEyZjQ5=@r?UqrhIfGD zy-npim4>n`m#1mK)~#Ij8w^oL=Ru54+&BNrW`GGkeFLC6Tactv9a7R}CBs0*PF;^a zOqb-Vivf-e(dPgeKTY7#2Txp&s2o#@LPG@KAG8vTxD-403@<=>C$W?;Pf|=4q;N)h zb<6?VPx_Q@UchlFFYX)W=S>X@bxvzNJPQw%d)w5&WlxsPR>%Qw%tha0sV_DT%0fGWq+L#MQYc2bw`+xT_hC?=Hw;p6n zZ@WupkEv($h{&Jkv@Wa>5TZS4typT;k{Y>ToKf%3c3jWA!la&XUSO^!!fJ=O^Gu;UsrH^% z8+t>~=nfLDKfyXs7L6yQ=*Gh8OqUD$!tNhyDR%)l>|!}e)+)-Khk7ih08eJ@FQ$OA zIUBb2xwQA5?1}!Ax0Z>NYd=3%4XyKgcC{}8dm6+Q4%DCRpBGe~+!;;mnQv4gX-A#H zPHtqO58&ypKa1^LEP_XMm0?6Q!+x9Lol&@TS#mXv z87a5paf@I)EWqSe2#;q6dO8Waea(9~O*lxi`)fywHZ`eBj_lY*ZYR?Ha$SJ8Ts=dm zzKg#7cO=Gy;z3KXCI++naxk#DwLmVP20BHq?N1>_NRJ0 z&kzhl=QuM1RM1O8*^6EzCt)IsjdVTYp~#K5jwcU$J#eQ*7e|@y%~dJ#hGp3e&_OAi zUuCOk@t=J)Q}EQ1Hx#)ZFz{YGIF|JqeM;#v3m#2}qx&p*v!SDz@u&RJG}I==G;D^Y z)(o18t==@m${jbDY=hEkb4@a=wd8CSNglVHd)pYH^biFxJ?fA|3>V4c4LRHww+VU~ zR@PxWgB-X6Kk;oIy|i};&!6Jj5%1v*Db8)10`?~v5cnxsT>MR;GbrRM4(aSH6+v{N z>nYu7hKYxlOt?%b!&G?pwdV6tb^i9x>cOF>#mSHZ3@d|}{vq}FRvx|~rj0SCo~sO2 zr?|Pex|{^4^Y7;Gp2-e`FyrF7t4>ygq(mIuRr*bPi}=!GhaQwYX1?XLA#@zPZ0v?# zZy0Kw(5UMCPL+_i+UK>Oezwy!_Kb{+G+Wg()oA9zRZAg5=?1tVgE8kxMbz!ZeYof86i5Xlt`Jo%p8xWTDKeP%=$C=9GR8W8TM(_#6@Sf-Y zoE4tSjC)$c8Y?j=5`YCAj(s(Io+9vgHv*zJpfaL^lkcUfqlWkGGkT9$oL7@^;!rP^ zNkfzBk08iK0FJj-NC=v*Q_N6K(U7bhDrj_1X~7C8&bWh1U5ffS2gwp&MPRd-s+Z%m$OS2&K)1dIrqw^sKFO4)J4>--O)etqbL>^3k;nm^&L zijUkF>1nh?^)rbvWQIG{(>p-l9J}NT&{OhrSd;y=9!+~`zLVkZG&Qw5@nb71uNXC{-w3_2Y zsx3OwT1kOt(F`o=K@ccXiILHDQ$p#bE_0&$eFi+ktnKdXP#^Mrnn4zv86g@%EZ37= zKlI*Mw|YC+;rWS%@zzFh1fH+4`DQYg z;nCL3QEiEpd};_5d#YImw1*Z*rnyOI)^7|G`d_{ylA529gPu5Gos^Fr3DggiLymLbn|B4kJqx% zYChOapA2*_ms*mX{@}k0@_$4SK*V-Eb97|LIC`9h*>PZM`nilkeL4Mv?BP}k=Ev0| z;qyHq(yH|1qLcjIY48_)E{&9uXD^iVBM4oWUeUj^Cvw*8#qWj`g424noQ2Ym#-Oa> ziK7%17m~sKQIrax4y*jYZ{7wf_d>H^vKF^4+E9#XbZpE&;ZBe^ElUn51UWhSS?XF8 z-@g1px|U9q-lY5I=P`OZh%pZ;THp*mt`O1PSyH zs1ug2IB?PT*GnAn|LY6(-!9M)wLZYNOxblt&5(tE9%b$F9W($SX3gSK8cs@#VB!@( zCha4Jf&ik898f0D_7hXjebt1uxx{^uRpADx2(o4BZHs9+i7cxGbKv0#%GQz8KEOXs40aWl!c zvr%IhP)!FViOGC1l4$wvEuqF^YBH8g8+gK9!@eS6-%89))M5f&eGC|LWRZoM0UoVF z99vSM^XLw38`8Ud5JRb-NLRtp(=p1VtRcn1Yy7#sEED3=jZ&;FR%c{B2<`#u<1jvx z%5PZ7d>}%Hll>+2sE|F!I~GD-Y$Ry?SZ5FdQJzOltt58`4x_d-3BwqZq;-Y^g);@o;O`69{hk6{)dVMT4nZ|~nk!u0ntrw!mkM^FQ$ z<9?V(H?)N{ILG-@fE%mUf$QIZ)6nXtI!}#iZ7`?r3sR;{w)Xp|5;{cKD&;uZ!Y6U* zVZfh0d4BL=l})7_HtxZo1Rk&tWLolkMn{F#?m2#+j-Lc~Vw29;*Ag9zT1k^2_dS5V zSJ1{9TzSqBi@1Q68e#01m$#O>P^r0SHQa9~HQSD(Q)=hgSU0;Qv4KLxjbvfPxse|* z1g7`6hGWIMnF+iy0KNUa0FHAhT=4t6goR9Ua|$?+8xJH(UD{Cc8eGef8Fm05*Zp48 z#^*}EhD0(Oc)A+`bkQblsm5-9_OfvqHiiC0|IM*}Bl>4C$8zYhO^(o>FTSuHSNtZb zVxo-3c!|LzjOa=RI$FL4sp^Fq7Oj+;b3LOHTgH zQ`OqVG)7*y0SX%G6nXoLhvlbE`Y0hSQwLF4qx50gxbA7K9oc|XO49;eNb;oW=ZGdI zuTw;#Nrzzjk&)nS+TS;wyBa-TE!a(0t? zNO#Z${?%Gc4flfTg!BD|W=#x@_(=9{g(=7D#kpGP1w;#)&ZMQ0VON;w&k|R?%313= zt<7Y31Oq+oLV_MJgJ`yWUC;S}RaM=-!_EpJ1ap?&4*coffpJfbCZ4Q-u;hn2u3HBP zd^H+t3R{qhv`Vg%T^kkkLiCX(q4GpZA)>s2z5u@lWHnVT9)Azrp{aE5cULXYN^`M+ zFGqnS<#mSL3`VeS{0Z?4zl53Ad>1f)^c{^bG>6L{8o`Vf|3`C~45Hswu_+EyI%Wju z*IEDsFC^re^oihiEjEopF_0#tq|nSnX!o;>Ki)RIr~~q{X~NHa_8aK-^l!Bvb+$Jy z&t|u-?%Ayv<++MW{X8CP6zV47O*3Q@Aoze=|Dcx*8W25Px2-PjjWHjLoz9745bteM zXCU@5%Ax|12sg78#^D!~P6~!@zd*OB)j|}BlvaVe3cI}G5N9AunCwm`aYJ-Cmqd#b z#&?2vAP1Twd(dC)tKBHuO-5cD7Ny~?zmAnfHSV@D4Bvq){9t)aUx1DBlS$w4-Au;O zr1;~xah4vZXt4XAW&|V&_|0DSDa!GC2%joVdtBW>V+QC?mTUFlYeE;ySZa_$r!p;K z^x@twDIrsR{krH{LlLVpEWqd5BbE?f=%vgvUxDp8?7d;*Td;iA*pRknEKzp5a#@kK zVmwfGr;Z8mKne;XpuHs;J*E7dKTt^e!^Ezx>-x!r59G0N8;`pqE{WEf3V!elzD&8o z+{<@ToqqgVWkQGs<)Iwjr;;X8k#vjx zgJhu^RF{&)l{%7fy{a|Q!nb0Gt=mQITTM;1{Z>=g1*3LwUK~;o_MdEAhRp*Xtr_0I ze*Bsq0#0&O&^p)L=Ns-TmWJN6O7AbLq^@j#oKiSPiIaZ~s4^S5Ne8My_L@{9R95F){qr`QG2UCxR!1a@d z2j_Zdb0nJDU8A;ESoK|iAWPWE9(;FbfyOnwGL9rBbj;PoAQ;A~Z`|W2d?_IJr&Ym`?kv&}xg1W5JjKA`dts%Zv{L5GEL- z=a4979ATHk5wVB!T-+a965c%TqU9QjxdsFSrrmbMl3(4=2p!~-Yj-l2hm39JGxz)x z2RX&l%(g=HFlh45fj5H3xAk*!O4UXr4UbRe%PFRxyPU-5^H~?M>qMn{CNrkBw`!h+ zDtaE4$v$qw&GUWIzZJ1xo@#UCrl4Zw1D|J}YUp^N@e_V_fZu#AmBAdeHkGm}?osaI z=9r1#$YTRhBLrT>o1Jlcf$4>czLX7=`d3x=H`<`|;lp{%AGDZ#U^bAp90Q*-#rr`3oaw zD6%qVY(Lo3NE7vHnL&c%853qwh|J#iI}&U(SM~r{OwZr#(E1-KJ;qN40fJc)U6hnW zIAq)iJi$cN+Fj}cZO48rL1n!Y z9g>g$gfa(ikRic8Usk=bHr_0xQ3BammIdQJM26&`Bk-o?BFw!S!6`de71FUO!kGx+ z+vB1{IMx-=1-BX}EgDh6b1}zm@no-xCY5SPv)GyrwA4+%`=Q$cz0jt;cBGbLEN7Dx z{VU&iCE=6(KWg^(I8;Qh{?dc30-|y>mbcC1<^D?fHNqJRlS_kI>0`hj?$x5tQU=!} zR%GG!w(P;+VB;DOEhxkv9Qe6%rQB6g-TRs>c4{2H!QInBU*@{aQJzLnV@}6QErDjU z`>GKR(5BTl(7$A_B`@>V$eP|1hAD$w8;;QrA0DN#yfW8nSXukmY=0c%n2lxz|Aged zetj}7p_e%}GH-1%lS*4TWim_C9+sdc#yA*IOSrT(2;BW&R(#$4sdB#l!~53_ zegrZ>UaQFtd_mQ)KXOJhfu6&0rQvpJ>Ui3a*k(}XFl%nu z_q@q^#}c0fn7A8;*8pSIDS8j3a$mOB-k@TajX3`YWx>eb< zR#wJrBdFrGF;;X;UbvL72>M1c9JJ%Lx0<}ZO;Z@>3>B`ox(g%(JdfNY&qr{bE-+!l zRa?CnVyID&uH>6q=5bIbw0;&e}nf%0642m&DAj>U^#BA9p`BE_5Zkebn=PZ5P!) zq;h0>=a8X&2n;RAmnLNvf67!vr1uuKQ_y957LzujEIfs8Cx&k) zTZm)nh^y{ZemkYAPD--ECnC7APE!_Ir$Ax_oE=xm+VM@*3vd)4!(&90rS7x<4@$#S z9YKXYHO_-6F)P0btYae0TPTmv=J?4o{MUL)uz{x@9%B?pLb4xk@w~hZ!}{NvTw2=B zRVvcy(|l{^Jr5i4s1_CFz8TWzhhC3v_n8+9kB>hWH83b=CjDe#Bo4nu5u1&S3PKs` zx2Ry5?!!!~2tfo9Ip(dh3-f#djb1ptTCGh^S?VFyN?Vv1SYNfv{hk-C7SwSwrxdpU zB%U9M;+A&NagL_5(6hsN(WNj;1O&57dDZ4MqP5**>yhSvxsWj;ZDK3As=Yv;3}j%X zfbd|sfQ%p*SE++I@)q+@70eWl($@zPF(5ZQAfN;;3}}Jy;JC)m7V{d|pZ|?u_Upyt zdf;0k!un66rv6n#{|CmwMOD`RdvDuyrw)tGD-l42#>;jb@V&S#`{&{o-cuui8bAw3 z-O`mx>Ell{M&Lzl18+NwbqP&MQOSk|i)zV=ecGCwkNoF~CLP*T6hbbD8Z4Mq{zks4 z<+3;0pL(+_in|<5rvKRr8Zrd98q5MTnJuuQNQ;vsad=mARUIq`hlnaaD;soB$oWwH zO!nRmN?Q|pwCEzS?oF^g5V}oBi^HNJh&E+Ulf0ajUXY6C8zd}tuwkNfxYELhosI#2 zF9>A}qd8MP8JaYxZcf)+yY3>I{nXAmhDphX#TcYrC9Lb4^CePDjyfP`zj{xL%h<8^ zy}K1O9x7{emlM5+qq?m)RRSdcif7se{b=P!n~T`T{0B+6WuIXO9)+^y4Q={Q4-I{|6L*2=CJNmF9Pa(0}+|koR7~Dn(JNvE`3azom zc5lbSsZV3X619jrV$J1lti^*b1PJc{-(DMA5s;PT$bK!bY!S#<9FXPBiAG@-8gl8M|!;VSWVEP@>g;lYV zDINSXCus+;>di@Z@Y;pclRFVIZyMgd9ASp6L{4b_U*P9+4en-;xx5>iY|zvb0k*3- zt)KjVu;4p+VtAle`Y={v$7YMCZ5W)_5TX-YV_oYH{z(2Ah?VTxPC!VkPz|atO%?rkR#x~}}hwp5P zY@o;9NPf2*tI|x~cu}0ilfkG~o7c4TPT78oeLFS;FAQ~+uldFU!|3-J@UcFqUJC?2 zAe0Zk-cASDkPMb?Y+YTOkz7XAZrN?Zx}8;Dd_C|wF)V9GA0Il<&+8|Y@1p|B`<~$d z9nEQm((;t7Gfv2|dBl5x=8C`3p=00v${OZ{hJ zdf#f)|8sbukg27KDL~d9;AARlXKerdo6gwA(8;MubyFEd9rbGyu|ZM;k^sC)MK*rk z8?_*>prBa_v>1-yRy8j-(-h8n%+Acs@%+9Ef$weS`6$Rfo6@&0YTDv$G5u2!Z>CE# z1(8jzUxom1wdwTe=KDA6jeFJa%j*H6m!Uhh0A-oX8@~^u6{$})N*t*-70H>9+EA$9 z1XEuWX;>vmM&GGaqkC66hJL|2eRrZNm=mgnbdoukr7#Z%dmtps5{*epQjxte$_hQQ z#BiazraWDC=%)=OIBg=m*%6zJu#+<@wSh6}PKDj^v`c7-2q2w^Lk=-iBc_vCd#KZl zmg+_JxATFF>Z*pC_Hcv&ETFutR3Llume zIkTs*d|KnSE&{Q+&9t1DjKF>(u&}I0hMJ6h$l@`lA{jSZsbWvVfoAh3?v8`(4`Hw2 zqrjudpQ5%m%gEq-sS-=-kNAVp3_VS&7QwubqL62gt(IlKNa$>$iHxkLbI8&(_T4a* zHqFI8%{rJ)c140X@7xUs2F$VPS8Hh6^n7cL`s_WPxn8KTm!#eo#(DGK3e4`vWpDDP zhNMU`iHm6V>JM43k2Dr}D*3$Y7pH*H`h zuu^V#^B67w;ZL*a=5@tU-Ix<(syp4_# z4+5a>uO2>8h&(O{B`aC>N~WBCWuruc@|DEOQDE}r>HAA3bZaScC9YV0#O3^koP#=+ zya+7TBdL3Zm7PI^z1&-o4$euS_D2Ch&tnZEwEI4Q1z?57IwZJ?DRIl%U&rz1OLlq@ zf3O#cTX;lYTZx-s%(ax?>&LmycFJy%BQ+(v8bBe-%5kfS99%N3MNi5*QU&MB=V@?H z&k{+5*5zHeSz=iJ?bUcCz%^bk$ABz$BUE?#5SS$>KhC9YHNsbqYUNw~3yrr^APDGS zli{Z4pVV}n{e96?z9HLHAZl1E$`bVXykymR7+HGrFf#I4*8g=B`2`z9=NpYZ4ReLU zeu=(&G_Sq2?mBl#;=g@Db*bK1Gh?kw5E_T%kv+tzpVK(8L;j(c^JDO?4q}Eu3~a_j z$6cTI#M2!gWhN1u2ZNEyoo*L^=i{{M`Qh{y~aT1S_du7`s&|!PrnlRPWQf* zh>7oMi~awVuVHR#r*HVLG!H{NkAGO8lhqXeaV_inRLcSB;{Z*D|JZ5=nxthJZR(1= z3dKYMYLS_uIZ+^{_QS0|e$u?E*)Iny`XmDmwzk!jcgcQb0> zHs=9dww*Li8rx_Z+qP{q>VI`+ z_Paate&2QNnfGk=oFwaHo!#rF=SKBg2O8Q)2Q-I}-K5V60daQqNNIbeu^WE6$kj-F z&=UCbBBAb?GL;ZEU-VqrU8RS+f+vD~5s2}vJyU$N2YJ-HKG+#^Sf+l7#XbeO1I9?& z^uG{zjG2$kPo+vLpdjq{EQ|C?`wXnZ*F)`^-~0aYI`l*@WVj&5V#5xhg(ni_lj=-z z!PcRtlX2==G~SovVj+$0O!N;r9uxR0ZM|a%>%#nv>iI#Rzd=9slta?>Tnb^p>pinQ zXFlrBhBa~}E)JC$Vp&Y>Sd;EiAC1+55!y+*Gqq^1`@@+m@Y@iUqO7TpzIoN_c-D=^ zFjI@;!QVxfv=n!bdhF)B?xjZF#Ku823%SjQaQIN&Ja~GDIi@iP=c>bZ*Wr0}V9sQ4 zDZZtdv|%75(MQHf5-|QQgQg>KrHE_LGbiR_V%I(w_fco!2i>cv##FbB#7+QPDMk7) zs~lxU^U-nd(>sUf#cF))=KCT$q{e65^BPB)WDAr-a_Hf(T_qD55i?!L6QLogr_=EG z>U-1=4Z)gdi-R#nU@qrfxV_qV3*%ws+soO+^JvG;A6NjHa;ho|H_BBU^aVD%;>!_~ zii^nBwuLR*n86X0g}e1thsDumr(xI?^~W0QAJp?yE8fc|3%)9ODEEBMbZFTm3{vb1 z9PzhhNSpX}m_6#{&&j$Ogsr!>2}bSP>GYv7s#9KpeClD>ueQycPBDzv#VT^$&(VV3 zWcm{Pta8$5j@6>VS{maZ~K&De77+NM8McLt-2{CNN*0!{%>bFVt#DtB*joQ}o zQ>S7VUtiwOhFKFq$JT|zUOXjl8$@OE2++Qe`w4>X63F$P%czk=2XDbQo6xLxjT?Du zpah>6%27bnBKw!;grNntKV{%bz4*<-2_X!pXJv%N!R66Jw(0H6sp*9nIMQ<%CFDX` zM&>E@wdsvWZto<-vPaCl#Y>gQC9-`>9ur@;S~BWkLAX-k-aUVgAk^`+um_h5ld$=n zcg0O47K^gkt7upp*H(i8!4=NrL+`|@%ICGDc4}13R%w}1J4ai+Gm8B!xEEfy&cjs(C6N9V)ykKi&R40-Az*DOC5M;X04HoG&v91qe z{8I$15^vpju3sX1g28yYYDeTbX}T;fPz4QzWj>bhJ!z{2w4W&vw#`5%$+zeL;ZFLg zKrRV>re%_-NNl{CaVup-AqtryYA9}7ev=}fx+RfL;i9V&w=(Qr$iR)puq+4mQFJ}fRn8RUvx|B zQVQn}C)@0R9-3_SYz*^3-tJpAHLpMz>BcQ!36BGuv7AfZ_uJxBhvw46fkSqCC-@M% z04Ljnc=Q>1QSzMeYc|9za5v{?rL}Z=y@mT1(TF(qrKHVKy$7jgvct1&lJp{NW&bgG zjZSVe%I#^e07CUXw;Gm`Ub@F8gm$lSME zEPzlatRf3=xA5BY;Ca}!+~j16!7VN5V02e5l4Xu?1gOkAMAG*UNaV|`1V*Af57so? z1gUY?U-dtnozkw7Tu5%#GN%mJjtr(zamy1WJR)fHBJ}216>PV2A*(S+5o;dyL+rlZ z_X=<7Bk_d2@jGl?f!9ZUGrQA929Z&^+O`DeUN#qfZ%!M86|8~a#6I)cNA%1^pI>l_ zr5~M{g38WFloOc_m>Kuo#P~||if#}J>+>H(e~<7!b1i@|<;~l_hPQuP4A!gts`;B& zgNeA*-v2=7u0wPdbu+m9*Kmj3Op7a<4ia@GdqA3S;utax(V}0=r2vkG{qFV>U^AIpa#M`%0@; zGA5Z`IkZsXIvXP7D$5bIyPubhqqO|ZmHM;G1=bm*d*NC40_@dWmz+!Mi0cT2f;Qu8 zHEQO*%po?Mndo8E5L~0MXJvV)h0j{-a+qK(3tOqii_gkMwQ4j`O0ie5(z#(RrKjh5)lP4%S|kLKn-kk4#S@)mOzu&NMCu&KbX`}fkI*2h+NN^kBdSRW_1Wh4*`WPCoZ$EEgmg%QSDI>GbF{kP-Rgg-diX>=g!^mj; zX@oKLaAwp(0!ju)s?3Co8cpf3iSGT3Sv?o7E3tbny{u;Wc^zi|w*a|G&u(tku?{(I z6p#BrV-I8LO)oZ_j4YwyR^>jBbA-q5ENLGLqwmVoXc5}NN|e$CPyL>@q;dD5F+ z23?i6U1&Lcuqjs5o5&jrq#aNAM7@Ct)^lV>aQ^l1Lh#Xxv&qqSZN^S!IA*JIH`6Bn zZZ&8{Xxs^$=hSx{odI$~nEyfr9(*ezXw@g-^x|sk%e&Yj2;B{c5S{Mku#(0loMez| ztd>wFc8nf7&M<^epEHR@W0Dr^2owGC3XDKHj=jU+t8u2NB#PLg$?M+NT-NQzPAX@N zNS&C!t`)rMCLzq>xLO=*WTzRmOog}0=dwXNXC+yL-5(mVG>2JlrKXabrL6LKI+|wd zUoVOv$9advyN1PkhE1Q#+O>H4O*amGZuG3(=&O36HHBSXPXK(dG6#HUDy&LE4|i-W z7gOs)sH^u$YLX46i&EnA*IB{X)?Yy*3v=QM#P^LdSaPNQW@K(6wod72+HAwaRWW5h ztXpjYkE2-qq=iQpX@v%M$@Kcsbk(?k`EWJxWN3QpgFD!%fKCT@QGEM7S^lU;0}}kk zZYAQ)*TnNgWMG7A{@#ry6pcE}Q2^0Xhd%-nkb&Vfc?HVnmKk<;hdT!_Q`G$7TmU*~*5VM%lefIy&*_+xwtKm@@3oCy4UA;F0-LW)5K zF~!FC?ZM;bePJ;NVt)L&U=)MO$BZ%NPXjt^u_OVBg%xH(RL>sG>!+1g4po}xi>HkT zA={V2o1?v@3LO{o6y0J*&W?R>B1wu>6V=BI^@11g5(5@RW{BJ1BpDEki6tQJ-j9J4 zCBg{RCE4O6;Upf!6dx12`YuzmR-v3rNQ}jlyXg?hLOc~A#!U2*(I75Lgc)i`QX^Pn z()g}=ByHe0GJ8#E!t#>~ddu4u?ls1y#w`1b@%7%2wPLzaVG>eb4_0R;va?bp(OHe> zK8#LAQPw_PgNOz6X+%1xs!- z5|U-Sj(A>A@=?!9FyaN~w^ml_ZXm zG`{S~0}xv<7LJD%`u&xq{)_#;+2;!}WuoBp%(%0S3gl9; z@4&yGKqMg`P}YC5wf>JukUvczKkLAa;$&?A8SlO4uWyO()y6#29x>LHo@BXX9=uarhVe5v&ffe6m!AY(ZGy_|x-TugeJ z>mz>$T1@3&x9*871!h5*pJAQ356eeWazZp}BbX`F@l)%h(j|XP77bNJbVxE3@9 zt}i#|H_T`;6aIvm&3FT}cW0mS!9NkOvobybFhigd4@fD_mSakwf<`u8@L_4KAZ+E!MH`veb2uRYNG`ovlsAQd2kTAu2bM;3?q9 z0NJf5U)tV6yoS$C&IuB)6ibkOiHl=M?PpiiMPa1~&0%>fktf7SVa9q&J>1bI1R87p z6&T?D>w{4ps8hi1H_%d@-Vn^%(?;eX@8|erHkzTJUU`Bj?lz}+qPD0Z?D4HS$~vqz zyuDr6b*yt4f@X0&wQ12O$uwveTYSIS!|3GgZb8SG^m$#B%*X&uld#K5E$zZLvdGyc zZ&@mC`9?YFnlY6NhsBh>pP9my2yG$QzB5nJ-O{zE8fbtpdY#?H0?R@Nz3_6vU+c@l z|F&^`zgYxHOrm@mQ$E=8$(Zrh7sX*{0&eo%6!GJ}pXzhonkR;NE8;ie1xuH)kqqMb z)(&?;_1dvQA;Yeb1wA4Qx{6Rj3#{6PLTS8U4qLN1eKw22)8?M5XC6n~`$5K0XmxXx z<^Ff?oP~_nc4l@oWn7r{Jq4T4m4WbAupRF2bRS$his`h_B2B5@i?-XAj!_;Mk=3W( zFx5J#&}S$gw>dhVptoV}xtJ$FldkxS_oaX9&W#khf83Ya_o+oQ;*_yU@s?3mNvMv(#0Zy_qfk=1iggv2 z6C&&I*Q>>yL&IWX@R@DaJhmV7z!5IKf>BD6a==xPiE+{F#2?pUPas-4RZ2LPp^n6R zw!Yzv11g8C^HssVq6t(PyC`w-V=5mz+@>4j9xV(x=IVH`G8&@*$s~r3Xvnnpb;mmANPUi3}{xRI>IqGIy5K(YUXI zO17?xpFT&10{mGeA4qw;hmvPaWLfW7CvR=)9>CELtuKKkxDRNhix2B_#zK`QXPx2l zs7$v`yn=S@+Tsr4g-ZH2KyTbMzz^~#?}Lh*E1t0WdlkQ+;rbSsbVS*BUN@|~wC){4O5 zu&F}gaHe?t&c(C#BxSGS@Vrm55H8|Q?!}$O*f~$`E}Dkt4hdI{?3V=Na5qiT%9Huy zQb#_`kGVn+#d~Eb1?{e#O-cK8QodSpQ$36XswpZ*qY4y!kCT7Rbu`py@U8@C8T=G?P7SiK?(6lY;5*1y_S547 z+)dNFKky29USvie0QHy`pqlzCC(D1UmaP<*qX4<}UI0kRidc47j>|-A_G7bk0|8NKVV`!meD-=E^33Arv_v<%s2<^041=B2;y1ttNgsM zf*Mp0b8hMe1AP1}=!G~bq>O+VMPPo?Eg69 z<^VJ_rlK^Wab+r>I+2$?v^n9ptuR%b(MTZ~7(*2QB4jKJIg~21@6Y+E5}XJ;-fG4y$xKKOO5+>NATN(z zQx-cvC-vbguK!4_KjYS2hYTi=B9H)nz$24qswWwrynCNvy~*k64n4nYn#%tSig4+U zWR>R{S&15OjQboh7fva6g1Ps}Cb14RN6Pp~oSA*aI++W~S@A?u`KBxC#HZkLB_>0V z?p0y%G*?L}Noui~>TQa8nqQ(+YoNmSc_Wn3_j&S&V2B&{Ag^i@JUWm@n@nMfdTg!p zeQS$l-|oNN9(gzL6Y6M&F>>p$?TecnhKxfNi+#q!qAr{jv8uBl>wtpuVmfYVt`dJk z+)A8BAm!1sB}muT7f;B*B9tYR(*z^}(s%fVBB;xiBBN?qctjiv7_1e}MVALO4SRw` zm+Ot-6DHV=xnV_Mp6;T|r7N9;ws=8hk0q_fD$wh-k$^Bp(TOuNmyCr4ES`Db=_3-C zy4^U9-|@9fyonh=A2}SaARDl6J{>w-aS9Bd8$2i?wL}-NM%7>&yjo&9qH2-%6+#}a z{cu1_I9yHK2O{|{ReFNQN-wJHKZSV_bEcG1CtbcntvId-O zIsoErcky2n5dS}}Hr`KTZ>(d(VkYRA*VmnKX^_{Au({^_RR=Gv-I zOwEgQc)A_A%AFfikG{dKE0jJ+<&Ol!#%$0d8S^i@Hm=`MD^^xZs7<%^Y@7W0Bz@aW z011f4nhGsliM5?L8U1EJj3$*$+R`#zO2Iqk1kahD*h`HOaDQCW^CW-fmcubsh1xk_s};o>gSw?Kpn-Oyw2DLoY5`QlAw zsqksFpDsZiWN0GkOF&5v1@4zY*p@f}OgNWNj#UucN#9AxJ^>pXCJ8e~+aoT}7C@L? zOr+3>JOn-vHJ%(o%Ywcn1z|1rn$ zr``K^5_uzqf9`yL(>veV^BrVf`=D!6t<18pWAb^=B z_|M2KeuB-v_55B6TG4>r(sR60O~lIwC9pL56vYLU^$2Px@ED4a3RC|}*jE{=eS6i* z?ba~}&?p}w!f{s<6BnK)hYff##pSbsb8 zg8@{rZUHK>{4EtD;z0SPF<3pj5=a__4Dt^cs}PVJ^gHC!&EGe=(G1`Tk7`0%_D!8t z22SLJ=w|U6NP52@T3MihKm*53g=`@y5{8957M?@rWdfzbRNTN%cO<4Ri~HG~iX^v) zsHQm()|yB3eVrw8zesnqOs;&PS1C0(Z$dqG?(E@KCO^RqV2LqXi`lcm&cdpZ=!jzx zC1Y!2T&#vTHINS-sMmAMMveDbVXFjURx-5+Umr`kS=No&cgsdidQuSacmL8&eCmYk znk~89aHRhAgT>O^IxOm`>kB*$sU~&PJjewI`dP3q{!Q6H_`C0nURgcE7l&pXsZZK(rpaAXWJF(AvSkiff;J6- z-n|SKLu5FMdOc{Co`4#nobtJ;1!#5_Ectm+-jykKbkka!vLG4JY#|P+Rs?uaP^vgGB2u& zz1fH;-LF?Szjv!69qzD3~|e;M#)THm#)JL?+Pc)%lhlm{b?LRvxzZJUZ{ zU5*$wi(4t=;u%q9h2n6 zd6NhJS(sWMtQCZnMw248CtC5Jm^5d#quGYsj0+T#GP@>y7lA0InB|fPdal#efYh&-|Wa4DxVD!(f`Cqkbn(C@f|LdLiuMh-&C$v`>{TE5`(8wiG zaMr*hw>31Zg-AiyC2@Ml7f2Lrxn5d)t%EtHPHflDa@_MH^JNb--bSj#3?4NkxL|mU zn(`GryAwvD&_b$!Dpky+ZSqblfF8gFpa*aqL<$K|M#4wQ2yL`li-OK_e8qFRK;n<$ z=r(yG@>&s;#uXItw${Ik6{P0>M2e7S_wJK1g|afqOex5e=j1qGTNqN#$KI<)e=m*Q z6@is$GbjqI7!vR4GQDN85}N3RH)>m7ZR-j5+oqW(3+zPBdVGBR_)7husZ|wyf~;%9 z4k&UT*-96%X*T|dzW%bP13$Ly3dUHJ^UJ0=Gt4CC3)nOtmk>{qIq>=!72>PgC_fP}9Z3Y3gO7U^uGh^XzkQSfJ{XW&|# z>CPEzK=gHukAouCoH)E$N01HRgp3X3CK-PIlnlHU*F!iM=9u2|`X>76gpzj3tFH)` zWD&_be{mW-hS>m*N0S_0jZ8dCO}eN36mUVd49jTmjWefOOtS``C1BZO=b{bQpS;~M zs>lLU0iKqZ)zhH(-bqgwrurEnf7rSh1L~GLcz@*EYFd=%(;v=S<~lGF<$yDB=3jia z`zbHoNkQHQ(93_M8RznLeI2OlKc%G35&6u|Qioc~HAq615x0`|0I??j}etXtitM5J_9dSO#OxgeszX=YWXRN#+|m~=i^d^Y;JWYD_E{G zaK=E0scPkp5kAe5>e0)VV)k#~lP0Ucs-lqfV%_eDw{q$J#p|EYjxA zp;1R&KLV@7m8YBzT8^2w3N(^7dez8NV>OBu<+R>vrf#OuDIW7Goc93ErYIX=NCB}B z>;4oGg(^FYE3aznHk*u1tuQLfwn*KN@YZvm-J0O$sat3X)lN4|n$l!_r$>t6Khh?i z`dH>>lFP2xKQ+#1R?+vaISm@{=t`A`*69Q6Jm$G{@VyLWjmsJPe&BU6GZ;yeW;-P2*HLE~heXWxLUxXwHU$AUcs5+g~bayH-_LGj#=>Ks#{3g`!2K zZNgR60!$5|U_KeCucKjOpw6PE#+>z5i$t8+O8d1=Z!*VL?-c6%R>-SaWNO z*3M;*?zLz2*3or;;w@cywP*v~7@{+a!ueMOb?p#^!-2Mp`}N)kJYZP|vg#$+hB--w z!wDe6@beQTy&Lr36CTikH8U;%3$!6%sQoK@&`$zlBfz2MUst+nV)P#maN?GztiXdY z!h9kY7`jRe1+9`*1$>?+-F3wxVy4Nd=Qd<**BkIz#r$Sfpr51yD#_e79;8B?) zNiaE})J7X(Ll|N+F;KmLPz50yiAQPF@yF4(sE71v9K;!pw!J7TuByr930q+ufk4D_ zlP~H`>Kp$ko>?=1Q;WSPj6MW?#I_nAL^jS$-|RzU=j$Eyhvrrk)tI3#y0%~d<*CQ8 zg1YK_BZ;9ewaG^gj_7GP;mq<4cScSi;EHEYNA>1nm)k5D~!I3SGT@Cg+?y?PH{ zo5>bmw`rE0c!$v z%)>?RI_Nf50zlsl5sY~@MFV`5IAi8Hz9;eRUtr-z%g6OU!A=qL6TYn{6GQW!gAC^4N{9-6>)SMQa{J6OlzYkZeGTHHH~YTMx#s${&TIQO zxZeZm=ke{Y0LjtG-onh`AHLOp7vGAn*ZU~~(1eEoJ~01rF6)nd+Q?PU!0}%>nnr;B z3wZkf(SOU#&8g7}d99Z~tOkQr1W@CYChH&|;H#k7YNgFyU&cU93kfA3#$49D?pMJY z@m9X%4aj5B0DLT21s5(eyi=)8yI8+y+}3QUP(aP=HFN+m{aSoJ1B%ZFfQ@B!eJp%*93oxm`WiDa`iw(b+eVqvr z7Ig&-A$93;W?iv4jCoA4G;K?w|6+?w&Fx5UhFyQ@%)Y>_A~rg%tl&cxIgaY?^-;Wk zi!-C8yAiw2rtl)|JAj?5yFdEGLz00>DAlETtEg@2wup%&+|)LMZ*ku4+wp!G{={Ux zE?Ux-mCMiNryZc(46xLE3fh!fU=G3E#D{5yll{zA$@TGYSVHD6hk;~~(c~MrlSAKP z2iQDqQE-Y_01or8X*)@Y2*y~Q{>?bkt>(<{k6xI39Ji6xQUo#>n_y}=u5kw(+FoMM z8ZL0G+4O26^I)goR*D9N+9Ia|%2)#ifxP<${Sm~{yCX)p^JVH_4c*WD4V41 z8V(uI3kFJ9)oysR4pTOqF!$-wIAqmVap zoK9(d-h8b0E#~3tg*3Yziv(IhWaV?xB zZ5K(3Q?I;(yz~@~QI>9n*F$Zczpsk;yRJACfYy2lNVezyGw)!(;rmzZ?Y~7WMvBWa z%lwGH62#sJDBQ+MB*K!+m>(%Ef`XzZvRy8z>sPL>N=rRFj=6e;hY&3~aPN=wZc8H2 z>5;JuVj}s|c3FdglCE#~qlT6@p)t_jQSGUqL63p3wZCBr3=)K*VA%;o3#g$?n6+%I+&{46F|@Z>mw9vW6P4F7Pd#;1)v+>#rs;&+=6| z7|2AVRj4p1QP`o4)+p435h(~@(^QXwG#1aSGW*=iv66=RA%^zro_B}Ne@=$cwO5(<(6_{;nH#enz%@Hl!T^#n$?RFFpfgDncU=%p$5U zPv9{A^(=ChUoTNe(q=%nCy>ka0pEw9Yshj+&`IWJ*t{odL=NU1r1OG8c)7xSx3DH{ zYY)kC(3ZC~(v)mWvDlW<1s++@V&VQ1VekcSxsrqK%;j{Y7*am{3Y`V@??56bc1^b$=CpX1rknZoAXLM)Y9W9yC8;_6Ne!>W~P=(?V-<`~mQ`f(& z!Ev!T_$B@+Ciri^!k;vZk~Y7* zC01hCN#xSj{g4lwlolgVmy74}y6e69tlQc^x%hD%6OUE_3ArZqn>O=c4?Nz6dSWrJ z3L@f+U!AEbbW@(8-FL1J%Xq&R@@ErT;tcgtHRrFqGQ*^bx&6f=b zJQ~~7lYSCBPNKzqnX*K3h2v>h!0iJXq61vq(e4hYR68vQW^t@XQ4w6Xw|JZ#jsd55 zRHQ}RtlpJW^WN5lUec%*eT&dJWx0;OeZu7}_*7h(g0{FN`TML5mIAX!5(CG#jn;P? z$(PI{&QjkMG+E>Bt$=pPScOY`q=YYMQBxO~2Q*bY-?hKJNXi;F(>8V$CVu#$G{01c;`*G zQJ`s6@{G^McZKP3&3h}E)775W(_D|A)VMGKljQjWE835 zlzA*$(%n!NlItfV>V&xnAPoKY_~hXSdeEBkL_1$@L}uDio%cgdWH3w6yj-QO27YRg3E3hig zDAC7PPS3sas~i#UG9DY(H^pXA-}xz_Vfm}hxDHe`kR&=PJ5e@`TnsIC;wAK(6UdkB z2JB~|$Sqr7*h&UgGT2lSC)d`PKGpgxW_g-gG3NmL}B*8&H(5M@}Tz zxr1uQmqiqjH0+e8>#`SEKa%9lzHOut)eXAjPCI7QQsA~AIiG(s6e)Qj1depQ`F?8y5VicNHS5<>_Kz6q?=6PHt)#j?Z!n3N z`uK<1_A_oc{`??n@IP9pe-w0oI|beUFKB)yVf_Wr{3M3@hk``_!fJp1Tu-=AX~z>~ zKw>f&YbV{t6`?~pFNO1twx;=5+U0<^;MPJeim5|}fOe2-H}R{p{SMTaVp@?bljwW9 z9LX>bYc6e#Ct{Z{Z;;%Ue!)_=D-t`z{ z@fL`rnK*I@#A$7ij%LC8Bw$`{1UsGh7*dU)0M6ydbmhpdiVr8WNu?^J>eHhH*a3ad zqD5}))+ZR=grq)Ri4D6iiCZ9fws9zrT%`B$K`U8KZ zkb_t6=QL|a%XDcc8RdYmOPoI_6V~bE7N0+rb5OSdN)h)|CQa3mX>>g?ITyPpG**EqtVMgo$t9z@N;rTG|m^8LpKQwZE zkWID3j41zF+{x1>sej{xYs56oA|(gT=4A{Ysk*(`x4K{s-tK4;ucbG?Xt?J>|E1x! zX?2VzNGCE`*Ox&bT1=zl)*?!{W9K3cpUSlu82-QuLjzh^81$t{?)9UN;M;N){DB=O zRR-iVm$oCGHVfuI50xzIE$7)tpMDh>2nw?O5T{&K`9ruWqnbyvf`WV!5-Tq| zAeKTQJ4B2!o~ef&GHsIVy~H~Rfd`0F;K8?JIdUt245AH8Uuw!HQHxk@k_ydlDlFz9>j)E;Ok!PLVi7gOrU{=5p zcjlVX9c&OO;P!>2ad1l7hoyJvaUweh?tJrIHsZZ z=3*!8H@hRNF-ui~qwWP>eSgx=HEayOg3R5Wo&1Br`p@;`FU%?b=urMEbSOWgCtFvXg&c^0$SFCZB}z4q!kr|7&~Czhx%>BIP|%ih4-= zDxHn{VR%L@g!Qc{h%o}X)zO9Wa@hquLQ**(z&Q2Wt8rd1Do+;-swqzrB254m;hAH1 z_DEJw4(^w+yTTdYGT&B?r@2$6K;nTmmdthxno!I+xTqeps&J|B%kI;e#E?xX;{HwtNRh&$7M+i-4Q( z_`tvI;j8t7(0qzDRoE3+iw&_4uv}eGoLH%RPp&&Y%r@Z*VdU(2gxE4J&jQ;c8Xd}0sqca2vyf@5|vk4UiMAwKa0KmLD}|mh4~A! z?H^?n{}h?OZISsIDSzQD@ejI*e?-=Q*9*Vrd+YrSm z2J$`gk%Su)*4fsxE5e6duso!Qz%{kE2~(yvhfMB#04=^;q{xZ92RiUh2(xDz>fknz zIY5hVFNsnsyQ2woMa!8I@dC#xR8mUxNd-4%&)*wKzgoV5XqDmIwtl7#ZzO-RB`HWw z7uqfkUzOGxuqiM8v?+(%1?);bsOOyR$k;w%uYB~UcDX--UUJ32wa&RL|Jq%vG&hUz zLtWFi$PH*l9lj~ys-8NdGF>yv7CAhCBiwV{8iG!qX{uET&t^X?HI))Nng+#qDch_m9y17s@R@&b44Wq6+=`BN|kwh(atfc}UF%@Mp#% z+%BxDel_2m<@tp_Q$wlH3M5%tVrbEVsq8xGvw1iPVV4DQ-z!o<$iT7*!iUPHmnn=gz%ZVq6c=)=;UA%3z8gU!`rc&K4>FfAgqO+$#0SvQ)@|viw~hOM;yp7-LMC!dsFC6cpKC&=SXp8K%h!QFZ))Y5cD6V~G{k z;L|JcF4@}$d2qYE9^zd_Xv};QSF!khfT$s$6h(LtENgq` z^35-Y75E_B7^y)weO3gQD*Y6h zyQxtaqwfH(_)q)a?)e^uH6OCR-zj@Nto@1XMUi(jA(+xz;ZM|Ma##hTEUwo3)TjrYJ(aa zNp%h?&!$NxB1WdkFLETQik}ioWF?@GdILDLUYf*!ag>NoKO+5%36C6WN3=?cl;;#H zHME4JiF+@%%o-E*IAicxE=ck66B7DG1Q*#*<*H7E((*z~57ps^9{BcoM+ial0aS{m~xR(eT~l~8bzJA?&+h2lEVqD zOyM5=&>jd2^YRQ$b!OO=C2EWXRhsMeS$fg?+&Pqms8gI|t-09D-S!f@H;j?eL8oY! zV8uNTkrCX5Ef)QN+@kq6PoiKk-fwz*x}@J>fTN4<7;!Y?%aUJAeRMyHChHM+vrXQF z8_kD6-g5~#iF@;&`GE-Z_MS$STaGRKBmMt&_a_H^?V}?*e{iag1Gag zrd)KgL>%ODvb64xf(Ok33D?;fC4@#)J4uo>?iGfk$s#%)cfiI}Pl5 zTQG~ddq~97qt?<~Yi!YDU$1Qgf7IQOG!RFJBYjbKvnp5hgTiEgDl~JfA>Ug|J(ziJ86>E zCyh10wtnP4WKGP{@4TZQJXwRVF%eIM#3vcEF*Y$aF)@DEbR*F&s!h1x)aw_rNPt&25dvH)X?vbz^92!m0X3=!NbAB;TZ{;>QfC`Mtry z@>8e7->J69h<{4W;AQ^Kb=YH@eJ4(!A17Rvn|`WMhVEaxk-C1(0Y{^f`N0fK-=1)u zXo)cpNJ%;jd(A>p(kzWmVfQWu1^1jz{a2bH?I! zXJK}dM`zj5ApIwN2^*GSc5%Ao>3W7vEEB`)s4`+Wi4TkWY1TH;M(Avjj~KQ>aM8e- zjNEB%+(^(lfYGfA`nUO_m90hgW)eyY+{?8!HHd5~$|^E!vIa|3(Yy2aLzSqKZjs5d zMpy-k3y=j$DLn_?w@GMBaf_Kn_cEsVNts;})M{5+g?@i|1lRN3z8oO6OJN8d)%)7W zFtvI4Mh`6y>M($&YDJX76N{g3k7khZ)8YpWLeQ04kILA+75Ml>ifIsJEvPdYa&Z*; zIg(>mJwezKkN|_Pm!PiX@pZ=n#n9RD0Sn?EBzjw`ZWxM~fJ`kFHS4(+*1)=i$!B7U_QnoYKp*vZp23oX?w#*2 z5sZGQ>bsf%pflTWY+b`wo$v(0L>7~BhSynCQc+rQ|dJSNJBC*c=A`_HPgLV8VxN*R9kNyk$OG|>w>!* zYf=EKbi(4`;n*e}eVGU&z?%SVmyk$7#D#!@wUX<8FlK;ahZ6w@?a(XkNi`YIMMLk6 z$m3{=c&a7)DhS#~ccTl3nIFN;RVR=t#-@dfv)0pP0L$~fD+t}Ph2gt&s+?X12UI)+ zL}aq1imM3lu3OGkp&lH@^C-6fvCJ21qh>7%DHb6Rpf`=3HE@YFAqGz5`6bFK#)}p! zBtChm&q>Xttr)17TyfM&0#9ETl29>Icp0j@o2m_V8Wd#5zm>DlJW~#rA)kJLZZTVD z*|@14!RWydFuFyzFuOU-k8Wr-1fj}5xb5X2HsFUll(e!g<;89;KX$dcHu63Tqq_#NyqiCqG)J&G zeBr-inrQ~a(pg9e*S4^sxw)FKq>8J1*g}nOZ$O6~^c%b2tg%-6W1Fs+h`?`v8*)9q zDGKixhQ&WZb+c83aT;VRVt;-v5;=pxY5sDBH`Kp5FZ%Q~KBaGMJ2ySLj(~ezDr?)@ zDf~;j&4coyot)kYrkDN0Ns-8L^Wn?)@~2HjFKL9%nfd?6**OJPwqRSkQn8&>Tov24 z?Nn^rwr$(lv2Ckj+qTU=r@Qa{Z=bjBx3%B*oNKN*#{9uqiSf&? z$E^*@O%jV8`DUI$o6vl@a$G_2hbYlh5osJQ_D-*Q@4NNs_+5!1JX~$AkE5p*&La*t z!E$~^Yqz0A*rOv3# z&t7=boA@ted7t^SpB;Wjn`@D+m(IC+w2MuPxQHOSofRjLiXr9!UdL{eKBH!<>w8Wd9Y1e#iG{}mu=mp zD#PbCd)}wd5fA>0OvndA-lyMD7ygTNNT=^nddLSy-ly+TG@>Wgv@?Py{B*XGDMrq# z=oU~OkHDQkW+lk`U4QUYj3om!Pr%9R30uQ%(%~B=Dm%_cby%stVx1K2l#OO#fMfSsFEGvBeZ zF$8EA%ja+*q@^nWL5_(@*2Yc$cU49$)P?!%Ut$TelL|biv`~+GZ~gJ;ICC;Gf+x=P zi(-TxX!dw!3Qek2sxuj@^A&&4(W5r;Zx#6%lC9TQL|c2BJpyR?u_2a3-K@^Sx8VTa32(=)po@7#VB_`&WKOn7N^E-@E zd9w`1;{8p8hy&yD`T2E9)%3Ll`j76q{~Dtlb&U<}|HXa%hiUIiyka}AoRPg>$~v;j z%c~5juh!f*(V5r~;JyBY9=tw$*QmT>LLaB;=(>oa<)FcZZGqmyoF6N zi)^ud$y>$1ezEMB4n+L)u$aqGLz9<1PH{A>|G{qOU(Ru28C$G|KpPyMzJDaIq<=Zb zslS}#zEWzuN)}t@Y6b&Id>geltJe(yO?Uf5!TA*!n0DK5gnMC`Z1tCS92a|`b)HF;!7KraZ&~{YmL^Pq=cwBrmatkkMGKyN z0vh$NyueU|8I6Fu+L?x8)9IJ2&$q!=~Nk#~7mSiCIa!%`CoZSUet{9&DmE`uTYwuPaNp>9;_ zwF1pOoGT=V6VappCSf0Wf<0UZ!u!o3qslL$F1}t=m=6~K$#L{V;`{@~b#Nx2j#2-X zad+4-h1eBM6P2w!990A@0OQa;Yj1~IpZhTG{digEvUzk+ZfuzpDGZ{ZD$m0&;zo>c ziG1A10;b$=nv{Qf5uAMGz>sl2`&cG!%>x|H= z<1nl6-IYaNk3-^?1#TJr8W7bB;6e$$kyQ(nDn4p`TTls+{-|!UZenhDb1H^97lfR8 zc}_gFxmNT_<_-ZZ3Lw?$F|<{?by=4+&g@>3Jp9C$_tY2JT%9_XjuVp{Md%%slM@rm z*UQIk9pM|xWD(LThj(%BdCo5vh`TxrF@06&7!%*-BBruD2s2mv<@3J_Lq!eveWA7( z14@W=5d$&;kK>FX!AAUyZ$)#oK}lKpnQa-P?J#8ZoV7@c*Ujh_tQb&Tjb*)V@^(>ql|++Y{r)}%z+x6nU1diDQKIoa0xEv-*hg9L|*sT9~7!`rpl|z84o=f zpta*-rT~O?{s&z|aQ29)=`gG=uT>+ULQgyx<$eO@Lte@+#-R0dO5}q@uo1!)Db<1u zQEahs)HGVPVe3v!ZvDN$%%i26p}i2a$#*-gxJ~}a%_RZWC|ZRZrLy(TMXvL#$Ea&W z<>mN`XQ9hGT#n<9TIUsx^V)ERhtVkSwaf|G9+2DP9vpa5t>Bz6@V+>B5*dmOPG1!;KVxPrY$b*fqe3!c zW`Id*D6#5pqCgztChjC?{!>d4)V zbUWkrBJeSxKUJup5DQva84&iiSV?isy-49;vF?(iFl~x}16QD^G08rHSYb;#6tr)_X?Ly(N#u zMw}{OeYK^oIO;Z^QM=m4AHTt;Y~GSFFC+4W&`bDHWgg4t8&D&T_qqWP-enDBtx*?P^6>%Nrbp*9zuKiwGO%84*z1E0F*$CM z-AdY|EU7rS0=}j~gYS}D+dix@A!ez@*xC_r!c5K{_f`V2OOF}6VgLaP>MeYoLr5T6 z7-9mc^^UGgEew4Kf-luI>KxR(H4_gNP5O^h!=Ltog9JacW3LkzOH0s@Yr%+{zw7kG zIWJ26#J(XcXIK=h^lW*9G~$d64IVTbm4M)P$workdMRnI3C!TYY^@wX0~a}HH3tX5;-MY|HSMBIl;L#2ba(5aOA z!xB|!MG?R3(~L_prb?EY>%*G`zY-T?*=Pzot$ec-QA3RcdQ6J>ED3SQSObNN|Jk!Yb{OC@ zX^meTOXl3!m#N;2;%FRJ0$`kX1#ieH2IZrod`^)$)slRG^J1LB;NR;tl`oUy!Q2yK zR%rKtwdw`Zh8yD*bS!OBPPhKEgvdv928BQ_CI|2)_#H?&Wp|JTLfTc+sjq@(onili!mDB{*&J_FXsRN(1T!M<0$>bjW zdF=jFbAC%aN)Isg$a|6Di~sRQ*N;90eAoPWVv)@J)(>JVNy@E&^7R8M>h3O%5ja?%u4Af!>0i*|ZU9RfQFH0he z6pu)E>5X{i8;m~_!=X&J={l2Ww{mdHC05~Qk#(BY6a}Vb8&wmm*B|H8W|1t{@>RSn zaK|#fY_++olJ79G;A^4YPZ_MoSIrDm9ZLMWa`}T=?nH!E=s=J-98I~3`Lo45&c}n56 zvEZWEaG5MgiWw$sG1-|7bZeGP%g*Hwuh&GV`C?3|3h7B%CYv@y z3*9VdhF&$p^J<{Qs&ox4DJLGyGpb(-lY;7kYE{KMvxWCzqNk|$=?o@Kz#IFVRhLtoWA%LR7INhNP7R^8_a{9K^5mD{TL`5SvQ`I0VC7YHsGc#p7rje zYES8l<3797I2>Getx$1JQCf`A2wLrcd5nh7!{%P~+MTQ&0lh2v904BL-$h%Tw2<2| z1iahudxV>=qD;*JXxpYF5?gZov5tJ}1R7v#OPfd|DheW$sFvs7;NS7g*f0g`xsbRI zLF5K9i(}#doe)1i>2HRa(uYN)O8<_iPN`52)Xrdr zE`Q#DMQm>FY%ByyuaeL1*b$X>?d4r*4IR;d_2U!^IRB!L_MPwc2|JqMB+DZ#;-QOV zQ>=OV$wyh?FrIt;{i<|zo-5#1RsI%u9M?hmlIvpal>SrqvDtZs!)WM{<1lkK{GtV( zGL!jdraEde$~&X2m9vMi5^p9|+A4O+wgWGpq7UbV<%ggy?MEHMM*<-jNoL0rlNjqB zHoL6oI5h8Dh49#XZULt`Soctnz(suAaJ++PM>4$_wCQSt=j0U&X$exTD)qj>eEFper z6D}OD1KK`U-oowpm1@FvBgk(mHx52@qQ(cWK(89Z ziQ}ZHBR{psj->i*d<6DeCeFsJV)k*zz1?N#%5P%SiYV56G9pZM&YRFzS$>KQ2@haV zj2?6`#vK(zeD|clZWP}0UCv`t$K5i4A>(}MxfgM=IUReau;%$jp;t#7Ov4@Y&forS zlV!m((*J00ETYhBP|4NkLpG|jPRk6zo4 zCBWi+76-&Iv*Z#zG*?3%F^1Y$Lkahir(_}+Q2o4ck4$5_&4B#$NwAe@@2XoS1kwA# z;U*=`qq^&SqWp?`p8B$Jd-v=^G70*TUH5Hrtu}2tQ8`ZgZkz+2X0D-KFd9k59nEV} z$ACMK>Iy%5fMGU&Z1(UmYwY5JyI?;RUZ3p&&+TqW;c;p15?N3aLweK<0A1r0N`AOV zNGyn&^ni-sHYt5j@F1BI=i1*Z|8cxeDYe6t4mr=qcq(;S&)K3+fW_bHn$JN)SKS4= zm#1mHCmyeUzi{gP@1zphKzrrjuk}yPmzwq;9nSpgbNqjyOO^?vQLD@-L65g6;QZ4Q z1yP#D%as`h#B`WT-NUA|br8Jj3H>U0C~;APz%+I;Q$pJLLKlw)H-z_%GI0X(NT?-) zKY($)K3it}?Ky1VDr-RZ^^>lHZtld z=ZKOaMr7Ei@ZA>tU4FjC(Py6Kx!E9vbw|ghPUJ~Kxvw#lQXI3!Y&_^VYGIfwz_k}b zdrH{Q5$0@2?wBy0!jF?Kkr$1b?k7~BnmKok{BxB*pB##(p8UlZbnrJST&1oy$;XFl zdZUu&A!H&_o1^;x$b(BKSe(8ZH2shPviuQb8d>VGK4xII(`_+$Eq0gFEOz{&U^24ThH}@%cQ8*911Xs45d&e0+h zjtcu!^k*A!m#O)N1Ea0T-9l&pVn>BgZN`02E7*r@pBLm%)W?$3d(CdEj9=Kt+{H|hbTa1M;e(s!MY*#6p!Q1S^Ngva zKesu}ysCT2#D{gs@JiyxJQ;relNtp+7Ht^T+e6l_z_>D)XQPM8VN=KD7Qpv%@oJ{G zNlc{&e&x!ax!vJP0GtHOOQkO~5Lv#XStl zNd?dk0sC)>x%Z_M?+U?}mCVj#(PD;_mVknvLX+Gf*`we!@3&oT-b{BE_M<169gNA; z3-daie_QbG55EkLepUC|S9SkK-r;{%_y6S(IsRU?S{43_X$TX*E$T~w>S>WZCukLj ziKIggyB7SN!c?e|KlaQHV;Kl_KUFVK(Slc(aL$$vgI+t)dzdYdV5z+ZvUerjV0}@E|RDy4QQObsm zANoZZaff^hRg4YUqmH*nY<977pbk7GgIEMgt|7^z{v%n8bXYt>MD1{2qH<`Ol3=KU znjFa*a48!s{oMYFA=dDxNKs8alV%$`U-=B%PnHkKULq}f+@*k4am*a`*$M4> z_nnnWsJBG#XwrcQf!cya2MU->>5~(XN8{FU@3wBpzHddkuBE7Z`Qw${4lEnZRAKJD z`_MLG{e=z@$(@r698Fh^fqB5dv#}8JgzWgM%FRR1;noB~Mz7-|xQJ&P$VJN6ow_?~ zzNvYF2T4PBo^S{p#t7oVkZeX=%53p{?iKK)!GOa>$ZTD>I9Rh{M2Un%;Zy!`6-0rH6} z@s)d0{FWZ1=@j2JlC4@er3|o)*EGhfmHOycS;f_^Y?kB_u8P`s9u;v`TMOitH@P5{C3JbxSV;jOzRT6O*VB(G)iY&1uQd<({QZlN86PheKI$# z&z&B5ZK#y(%!2*YXC9D&I?x=i_sn|9rG6=-s|wQ9pZ50?_6p}>OakD(JTS+1tv&Kv z@m#EwS}89yP=bC88RQiYt4GZ5A;-SOMv-c^mPoM@;ToVQtIaY^Dt57PLtp`i8>MSwp(MF0%`F zPnPkHInVO#m>e>R#b;+&yc0*-Q^6|%SY;5N1zGEMRnqs&r=lQ4!3aNof9#k48)`6HQ+QeDRU`E`~i z%aU8hN83>UkH4|Sr-E|ZkH179j4$}@E;QhW>NJWj80}+|!ID7_VAA@zD zV{di-0`PF#8vPoWSw4z7=-7UK15fBBEfpyTwDJJSlrO*@A3~&?&oC3|A3oFJ#DfAP z3e$#^L^OTXdVv=mrJAbtVUT#bOUVPX9K1z! zM5a2JTU9Q|VpkT}lmUN-#t*7;sw{XgMa6s7bvx~2hw5}2o2bsH{O$NcOLj*x1}m6W zM+K-z+{uM*C<_WB1`B3=RLklO`3_Hu{27xb$>?s2MH%{ariRLMxBlfe$Rn)yE$}bq zKKu|Lim(Ww9jf#sak4@)6g9QqH6yk#ZHrI*pC`tQpm@3&2)}Elc9@|ek?v`R^1e$b zDP)~=zqdWHjQrwWkScZYkRC&toK3@z=gVL z5ZmJ>&J4;U4>nY6fA44IWCT}WtO0E{!EU>Zjn0U%1gzzF~zF12Ew>?0FW?;r@R#-{emFp9olO)iS? zu`%zVoE#g~A@ajJydK(Il0M2rWpyHIPD_CPV_}*~em+LrsF)?Q8e##k`%^!m9{_1+=RCR5qC%v>KJ@}heUZb zY!Kq1RVOFDg2q0FxQ$)4S0 z$+4Tcm&!cwc4E`{6=ys%Ak$&ddatV3t9G`qD2#b_eF4)htctOjyA;;M*kdKWekUhv zUcFx049pRcZPH5@so7>Gc&YpSA(BGJ7D{Yn%QJ)c7$=sKC_Vag63ZnF4{vv#Shb+q zD**m<-m_M0fkF@-p{nspq_(vS*q5u1edjlpoew?Hy~5tQO;wvi;@4|*X#qdpJvEm} zk+B^?_DES5g-V=iOinGWOvsmQ`u7c~W|QoLi1u$2T%lVwQ^`e&xeVd46=2{69x?$B z=(?FcpT9tk0xgo{=lMbGYxmO9#Wtru1SB#8g2h5tP zanYx+CAo=B&+hd#r@r&Urc3M3_V>}iIasmu=5UIl&-54I;D(QGJvwpsixBqsI|s|x zS}!%q8pC!(?k@M}6e~Xn(7hD;XNOpYFea%V=7pY@{V8NYqA^V!7VsV6uY-m}7G%D+ zHEP~M=6KTr1_hQcA=o4oa3*!Jx~T`ep6Gm%*wk-@jjV#h^!4eFmc)6tOoW3eFm`^7 z*|S5%Wuq?fJiop8?wan78m^jbvNj^W)fYHY6`Lc*riY}urO z`5tBdv@@_LC(HCX3}(d!Ca2FNilFA@i--u05CoEDz)`o0Fjc(37F`cd4Y|}MQ>$!- zfJ)4`v{Z46YD;kx)+a05d0>Vw+uA*e3T|?~4Cej))`d6;O@Z+%KW5-Rb*2CF4flW8 zXPHXle-&xxD;XHSuM4oT=AA6c`5&?N(CG@C@(d8Mm~Zuram`7hfdxvZyj4nfu`f`W zxdw9SLexwk3b%dHw=~q*6I)~&qacX`C>dbNKg-Qxr;tv^8hU?f918<_#VT|Xu# zME`O8B5Ud&5ofX=!O0lqVi*m9&k}-4ZcGEDCkWrQe$GO(5c3D=S7PI)U{TWyD$E{~ zY3}?11O{IyW1>erDIdAs?~Y$!_PX*VcI-{u7^&!R4rNnI{0qr2b;dF~Wpz}IL-$2- z*?0~N&2-l;5dGgb5aF_KM)xOSn2a+`VuaDXyF;D6l)JAgrIgOO=I&XEEiq75OqMSO zA<2p~mgVGz`T4R1atwV5d1O%v);le{aq1mte}6e@xZ0T9o9N; zVX<+qlxRXFbZSljJMd)lT`pcVlr^RXV8Z6lEy4Y2AZ$*$My~CHGc!p$%NSUkX-T+Q z1ckzq`%`V8I+VbrGmPchk8i{Jp~~6%@yg55zHa4$<@ARW%avY}XUnp+lX_mIPQ&{* zZ>vTcbRQH3cJNE{KHQkpxZThanTwXB31S-nt);o70Wq?j)PCeJqMc{3ux;*~3Arg- zRT7LOMqxstLubyJe%DjZzD1)(Z`r+*XI<;VrGa((YTWxZ<;!Ds7eUAbJfw5~avtTV z>fK&&9Qm(FY)O(g-TEK}BbNCHDYplDfmdPntkB;ny zp{DybAb4;O)Zw>5q&9qeTw|I^nRLzbb6-3p(fgAB-VxfJ@-!Y3TGyxswlo|YU2J;B zoB5;q;SY;m2SHq5R~ZAG;RsQVNTao70)nOPU7w~vxGeO@Ll9vsIg21zo=kh~se+x0 zkpl4+rYkL3GJ7ECG!!MS>sx*wB-E%j9#2G4$J^&me;DEpqxdLt+Jqqv@~qU~k-<8a zLL0@LQ@55yc2Rs>%iLALobyg2^DA$USpvXgx}+Ij6kCZo_NF7 zv6pSmZI7qsCoKvwYr?*7d-?m&gLQ_o@q*!CL%{E^lrzW+87??<)G9ys0=!p$ad|#t z;LhTx{3<~nIOeGEy6oG#hIBb)S!9eYw#U!NL1wf}d+<)rt2bz=iJhVteuFHGAm2R@Z;ZKmcEUByUBNi`}bX=@1Tt8z+4P%v1ym z)0BKeH_%K&ZuE$dV#w%-kFF8ID;aaI^1p9A?wIl0sT6o~sPZl&S}qk3x<5%>D91gq z-H;7)C(zUlVspq63AJ+RJehqw0+*s&h>paGpl2u2PanE@ww#46BDzy8AgGO7kXwAJ z&r;OTN8xk_^ETCguNd}@H)S;r0e~KNqgnj&oN$3C_mt46dPt`#uiR|c;?lkx+Bx-g zBndLhedhfLtt|kc1-X6DlH>Lk97dgw1EL9@GosEIF%_gH-G&K-D6mphQ&wX?e@56x z;JW2+rr5bLm)l^$Jq_o(>J3toiY#F|3c8@J7a^k<0?WO)+p< zor$VuNfQiYM}UJeZY{zJU0b27U4uZISvosYGX!wxKB*6gd&upCLuty7#90w0U50ot z{E4XCLe-QBQ0trq)C=(df!m+c9!_{NhWFz57ViFS-$)59;u-mMA+!SZU+o+JKTt4I zaoh@v<*&eKm=N5(?uIbj)p(21C^ZrBc5#>?Ss=4yH956dtT=O3U);hYfY?Tkafga= z(yMt34;ZL<(B_!OZLGk(Hs}?Ul>uP_Djh4V6`4-VHNXYP;mjHyDnHjVm0|^Pf$5iE5le`1Ycx zu9mgG?NCFNj2&U;b~`2W4xUFzuz~n8in;fWLOEx#HnvC0XJPM^|s@Tsq zb1HXjo;r%8nH)=ggAZoHtJ{a06eT-Y&#Xj=lq|2)|-;z$je zX!huMz;{IrCKvsbWhf`eq8GsVDJA9>*37zgLam5$RQP2PMf?*OQO%UN$B7toB%+I= znIM@GS?^GZ2055OoQQQC25lJqn#ceC^XPHgjTLtT&MNG3m?a}3*84~G9GwoES7)CL zjpHja7(@$d*kK8@N`RH3W!30<$uxPdq=m0mms_~qB|d7n&%lPdpduTMy+DTQjr@qY z1+$JS1*LhIVUbPEUOJ@?n6qFYEzwR!dJ77%&P8CUb#-Z<%iNG{AdaV%FQxDI0(13= zC3n>YC!@xb=nxeHJ&xZaKNh2G0khmyqIwWSR7zfEqg|Cw;KIjmWLLTH8rYq*j6pqPuotG_vDro4;F3fw6&94LBFkyL3;^S#KKuUnx~ zNfSzzRcQrrKgumfJ^Z~Y{zPCp48Psu!|`8m2B*UJspqA&0Ukoxbw3UgOnN;DZR0f< zZz@%<2V04A!--9ZP%9qpvb-yxtv`jvouxDgWZ&#WB}pwBc?oUkDdXpeF)@n+r$x z`X>l(=K`;<8E&WJIF07*V<9;G=TXYbm9phwIbtcj`r5tO=dgryvVtWi5be<*T52)h zvX!@x^i%19`bPPK;JzwYwehmNBMWu(mXG_M#AyR(yR;Sha$zIt5Mf7j#wKMvFLtH*uqTsu$6zy#st zx738bZmVsNQi^#S^050LG<1k6h7*aX>)H}1{b^!Jj&gzAE{KZO0y>qAlXxWlM8RMl zcre_K(+u^oaJc{z&>f~8K+_^gj`U0}V}JJBnL0_@HYW)+>Ie_Rym)zVeM4#ao@yHd z#Vl{DmpVE|N=E^scN&JMlE=DvHFhN7lVUG{7g@Be_)VhbpcH@H(I*a2P2f{W4Kh9@s9~7N8KTjWnJ>9{MHaI}DiT>t#OH z7LOkN)?nHB&ixZ!==u4EB=00XQrWztr>a`y5_VO97#1bXx%hcD;}A??pq=?yt=eLO zBe8+58t;)QGy@~(VNB_}-7*uf(NwI*xr~|Q$EZ#7>N$0))U$l4!P0M5Y+dbq)~6cr z^meN{rbW!Q`w|2wcg|JdQMwAYblqC>#(c;XvgNN`D@Wk@L>%bq@plguI!Ra;|%qW6{%AV+p?Iv0` zXCUk^Ips;AviQ5Cf-KK-FI>|yfEaWICC+i;P><|DMJ)-1ayw?2N*RMnd}3bMB&}=^ z1YNKW4NLco|2WxEE=RYD1-tk4Clv8^qUVsuu5G~c5LfiB&OPUSci$oS+rGN4Ue)0r zg}`!DgR9n|lDunH!_sYqTg(Nj5O|>5`fyIwbp80ON6IiX+SV%wU7P?_K_>(;E75iS z=#@b`Ur`SSGo-S`XpJ*GN=VxyZ0*l-`#)rCUwTjlskP~gt=5Uh*1Q)D@9A1j;hfPF zZI<8jjPG;p*eF0~7A{R`iDWT-hzcMn;O!K7xf+|~6lD;7V!v|kdU(!e?25E}AxXyZ zKfe#-yOoX|HWe)=n=QL87g!tA_2e6!2Ejmk*YZ+?3*Xs2*gUvA^fiY49?Gr7N<#RXlOqq1+8_7vhsnRM&jeLNy{yf3jlumo2kU7JtV{$x(gOjpxh3*rpU=j|sC zRBEPtux5a7M9!f}q(l`7bLq-lCFX+bNs1J5B2m`ot%*kp%k?v0fU{zvi_+-hZ;eRn zJ2D~}rHKjZFQsf*95r{QEzg@X!559Xs#fO%CWxHHPgQx5dFv(}W~0$GJ)i&HbD*iH zNKC(aj?vdQ?tig{`d^@GqLP&LSIg;mRsI^Q%6Y%Ws`dD^rIIOLVJd(xV!=0}%HqU7 zL=u&f97_T-OJZwP+A}mmwm5Clo=2H)5}B%DYl&ygmA?eimQz`*H^$`6)>ZvDu6*EA zMN`4)zq1)lMXmqpyw8grTMOuMV?@oOa3$1fSy#9ImPY9-N*jU%ksp9KLW@Md?O!6O zEsO7WN)iz$IqU$MGn5i^X?4fpyr|O;42CN+N?4v{%SQZ@?SRZct`?Rcz>X_g!Ib42 z^%P=~Akslw=ulv^<8DX5t3IS0u}WN&Ydb%!=Se}6maUkCh9>IkT3BHnb9t08KjXG} ze=d1;r3b(4D9Y}bbmpLOb?|a-8#sO(tyC)LxKl7?($}07XxptxXYh|XOWzy}3AP~o zy7B@fFBS)^vR`my-dmq+cV3BNv=&uaK`RugOVM%3&8OAL-5f8up6oOiLM1IvZByFS z4-@{OJ2G}*3g_o?_qd6lo4DJHD1n5m)i%qo%*bq=GdhfU@~EXY9G}*f?-@?;>|Eg7 z){pER#J-#-cU?;^OTi-N zw)|hCmb4qa41CiHAndy()UxSVkVk*;QrcBfSx=tchyhimmLml_p;Gx`1BV8j}BPIyt+U({Sh&UzSXbj2_2dI?Nxe1 zkf>d{BZ~?S`^t-@mq!d+G@-_s&H4#jtn0Bq*|#;i=!t!J`1G7UqUip?({l@t+>U7D zyY-CfXN-E~u>rGc(s^b-U`$HFa*(0ICMl&NAalU^IXm|yZVYtpU)LWB1-=UJ50QJ9 z-9RUHDG%85dzb1`->`sjV;B1tI>>P{u){|j607o4REifg*oh!z8&@tdpu_EMpuZOS z9nNK?2R37<7b5nBa}bSwY^$NGle3QSuGOz z&w~*yoh_~6pOr1a-2Rv`%%v-&`;B!;S&2XFznP4A4H|G)jrX_GKjp3Mw7Jffr%>YM zDHl3whO3RfK2^x@h4azQ9?xh|DnBf4!cl}6^ScmMpG z2vv{hxm*6#zP7*G*MEdV`{#=8-%-DwN)pz)e{Jc$q-xwaAxv~GD_M{Ws~Jq#1go=i ze5`_RhSQ8xp>y&HI9M+XTn|$U)gkCLh9&yfV3T@cKHT!1TV;`QYr=gl9UWm3Pu<+dNvBo0oV1O~g5DaoR zgAQyY7wfRTIkNJ0@6x_*HJJ+itbs*mg&1W`TeW?5a>{No<5nfFg_2Ss17J+qH36p_ zsz#&i)lp~!K(&&$oap~h_dizJfe9;oGZ>^%$ZlX3?!SQ6*+}uzq4z(g^tmLdJ3fnu z@JN~PZujH~hL3M+6JyQR8sB<=+gJ;-F1kIo44Gq+>~()K#q8t)j$#wwc_U*9_|}(@ zAcNbs)7UW=#GA?Q5PKV_F#^i3kZ0iMKZRw5!X!Q+MGReP?h7<%K%AIbUzr;wps{Dd zi3wT`^RxSq;>~z8$C#7QRX4e8DA^;AMbusRg+hhgZb(R5VxonWD|&Ytc!kTms0iLP zS|Y3E<%tNZK4g}cC5E6k5R9fbbFI=MJf$n5%1vN*oUV>|Ip$Y@I5PaA?@a!x ztRMOt1Js%VFKO)BH7=*bAM$fHP(?+}lV6G-YL z53du%*sfc5HAfSLk*`o|{S|{k(z&g!hcX^0E)5Nh+${p<-CFXN=h>dFX^KuLgM`Rs z`P%b$a9k1))$;dTZR`p-uqu%NJ8Dm194NPSoY*y-WKJUo7Lz%->LciffW<#C5$^QD z>5Mzj>a~mm@0z?i*O<=tiAW`sN`ed$YNuMK`4VI8jF4hmr;LN+F^@*3mrGfM_XeAf zQ(2Yn>>CO@;b6*yJCYdujU=S1bIN4lrNxymkrBv%Zmi8o$Uf_wt-B{!E-U64Mok>Uk)O9 zv*M_cVlsB|U-z3`TKhH{>|yNJ@y2shIB!>p{E^>hHB)l1kUXH$ep&a@68Urg(9Kn| z6WCiI@r0M@IK@9wnvON%BY2Md#rRtk zg7EN0C^{R+on9{AdGgqk`rHoQl_e}=MdQVCmXtd(@myZEY?&rbN$ucN$J&}z6Tn{j z*mkym*Mc9@5-MFXs7yC0tv(#zgx*`&+;D=V{gRSGDlI`mK;q98El|NqJ~24)K0#Ay zRXw6ew7wEPbtOZPB(n(;jkG*q1Y4_FXZLp-eQ06)-I$O(sNNF*t-iRyV4jcsvHlNT z@+3k(@j93Q!M*~dJl7Eie1kB@+f=mUB_DF4#Q~K)9P!yA>Ads6q`VRHGQI3zGj{9z zg=-H4OV)1A-}};sXaKcec-9|PO>Xc;Ke%)mVn({_w;JZ&RLAW7<>eL$C%=^VD#rt} zEl#*YvAHSQJL5)V+5A>4T7O>@a~^&Do3>8vB*S3UKR5sd@9EsGaQky%bryn?bI9c!AShrS93H}~r4Q>&o&>P4ZIpd5Dy4qfLuSZw1U z$EP|C1#nV@DNa|t0w1IRhv;UILu`5LC zgRE4Er7$BhOJ3GYOUg|iCTNz7#fON&7ZrKxpcfFAfa3m12Qu8X?eTW#kI8etMJ6dY z0@%j*N`3tEi&QmCJ5+5c%@hv3G!CPVUyKzpE?j|k4>MsehtVS8Ac076#*D6tawUwK zpjyMeb9*KYk+;`1ojYqH?-l_qZz~3J7F73MZHb7eRzf#N$qg1-a-ddH)>O$S#$?HX zpK&sEB4Dh>s%0KuTTQlMZ-r8bEsv4mgs_Fxgidp#@oQ0BQR?}X&im-k>fe+ST>8fL z;xC^W6W!%~lKlaz9S=e4rit1=N!cUue!)IIxHnHoLEjUgk;Jx7V z-Hgrw*oZDhZ?y~1u&=#qtGP(9AdX9x84ZsUMZ&ts3YqO#OLh`T*-Xb;0iQUQwDOhDtG3&0=mlof>uu*Lf2OR<)a!pV{3yWLZ5 zTkE63A)Eprq{kKM{gStkIzSy|+dB#Rss?$x4#a;X^a`6o)gU%wM>hF8Gc^VfI#SO zqIu4)-r3Y)tf4sWd0X8?SLDPEWuUJ-P4Iy_@RDaXxRfQElOxT{eDMib9@c5Me zn5B~Q82RhP!uNvr!1Z(=dpgfc>j;HcZ#m8{j!H+Rcza~ZQHhO+jdg1ZM))%ZQD*NuH-v! z_rJgH{#|wV#W`c&?lIwUW8+cc%g!!oaRcm4RaSbZI+)H@Y^Yehd$xdS6xD~*DgS@zSgMj$iw-#` zW5geopEp_BtMS-Q&-Os?qokhxP(o(|X#^1afziBRF`I9HLGVAjc>>y?Mv10w_q_SL zq0t9?!!1F+u8{n}nzMl&pI|@k+JzUvC{+^kdFwKKd}Sn{pOm4pgC&f~h;ik}$z(W5 zttVScB9t9-e~9?7Q8Jb>{~QaeB}nDUkHL=f=5t8#-eWtR4w;Y4a!Qiyk!Ie%p>N_F z?2D+e&9oR*dlS}hW?20YWzWb(rAjQtJsOE9d5tPc0i6_^%vCA5^TMKdF71@D&Xtm` zhQ9JpJR<8;taZL)(vr^HS51`U&X)v zMei*Ium7A4s2vdz|4;Sfe;U$!KdCW~m6X*j^e5*LNB^XD{4 z)ET@6Lt6)gZ{EdGYp1eRa18W~)2JHBvGLle*EVH?+`BOgbR?RMEC<0oVf>uOr<3^& z%UI6D8F4cKWRMjzXF*58DawrnY~2P8!Xc`2X=qgC0JS%bjd>u!tsV-~D(dS}O=zT= zB#EjB(MnbTPHl|A``^F*0a9^CGDNF?4`Dty_AWWGzAVj5ma}=yq=R$_r0E9^--6~q zOzF;Whs#_Ocr4w=ulUE-WEyx33=ogo2sT4EkMww~Lt7)j?&3~Ju}MkgiJjRb4~{^x zdt%BNC>+X;w)dXh8S_SmLS^m8|CoOA=8dK>iIOm5{-XFba(Idn8f3v3v5kSO0mRJp zKAvvwl3;O839JA!T4ng7@8E}~b|3wDR#Iz+3G?p|WyYUQVINh3d&rw5yoVdOlz%++ zI4gE9|9l*cIP&(y)lc6RyjD z{!Oyhq2B_fwB2912KlN*ePfv$t(Ln&%h#T&m)#^X0Z-YTb&Xp(EeLYNWC3xM3(Xa4 zcv6mMEPj-WmBdtd6y$hYm#{OX$#ATctzGrhn;C1UQmTLa!qCdn zbdW?e6D}a1A^lRX@Tt%H^%>HA9=e)sOKa@Fb{0cT#itCd`;a@R=S?*J!WnmjrHa0| zDOPJJ5ifiz3-!I{swADil!xkCB{z1&XMVB5vT(4F$UMn0HHt&dm*CGFo%hCKwRSbl zB^BJKEal!(P6hb)*J)#)qZ)BY%b4vp)3q{JnRCW_wa$1$ve|Wuj1iej+P4#|xF8Of zRn0QcrJisLYawX{<&Y?J)B>4s4k&(c;%&YN=A4VGDwP8l_YRx_4!l2&XWNQ4nm2s9 zPmc{3_YJHD-O#*)MZ32tN7wa~I$iU;jYTWB9UEuuo{>Dtk4rvUVW!qctuX$OX=@#B z-as!qIsaGWz}D6d?(6-ls=7TVeYL^}7VJGe1dBZ*Grk&a{F(lt32#ll2S*5eby^z= zJCLf(YVPxWTU8Abipa1L#Y;mZYG5LGc-Wk@hMI+a;HgV$c6&Zw9=gC~78k280!T2d zN??>cERnrSlXJ<(_Lo>3Z3*0V)>-C~Kot$~KGycW=LnfB;3n&Yw7V(Y6GZeoZD#}@ zVC1}?lcAs5?3%@*Aj^?bLvT{yz5`imzPA0SLke=4BCi7uEAYEiAJu;ucfB1bZo@gE zKDwT+C$%$m%KVhY!Zkl|n`rllC=fU^XO(z%y??iLxJ1zRdu7anzZOoO0dbC66&Hy* z>9CwLjlQAuP%!#?2ESmk7>vcCM#-=k48ygwB*GKg6{awpQBD;laE7Pj_wOP>1C=la zEKyCEMacKqjm#CQ2=aip==4t?{M74Z0!qct1Ic%OgKF&WRpGcmW9RbXg0=Aphzh^^ z%c8gtk)ZW;Csh@}F!tAay?)#47gqo7vZ>?a`!QfC*iQWLKybS!>g3hzmP1CU<7pF% zt!WQSxW&2%P4tpnTjc2bf@^c>SDbC|8nBX zeU?{90*F&`z^wWIK%D;f9En`D0eb*(+WSERF^HcdW{trI@Dn13D>!hqxnxE1_%?RtHuf{{kK9) z98ieG`@3`H=j90z|5J$lA&b?A?j|MfMung*)kq{n0~BI~fI_T@s2*jW*NhvTd8A`R zJjT^ZNG;i>8FeJm@V(pQbCLqz*ltp`-h>>N)y4N@3hq!RsPOJzsJDb%dD@g9cVT{2zulvx^ z*6|g<*HB|Kraw+vIPa)+^bP8o8EkgAXgOcu`RQb$SR0{ix#ZahtZt}{q+{5GRb3HM zCsX&4DPF2%{}VsNYD0nbFS}b+E0^)LALLS;6cjm;>=2MV`93QzdGgER+vcQ)i>E+m zSJu?YVlyIr{oa%FvpILZ{tRO7kf}SIfV_o6d{D7DW`qEG7Ii->`1!cW>ee$|E7hC~ zku3~Bx$q_x9jqjbcOllny8=Jjm9ZaO2)=m!HYxx8a*?8fv zg_v{SX9DTQ@0w?W*RR8`#gm)e*v%7&+}~E|GQI9c9Nl+gYThEfeDk(6%(1PtdIrn% zWp2*a!jcK)PIi0_7)~3^_I!sf?Stag!*4`Zmv1@lh$W>MzsD0HSkd@+RAzeYuOYQ} z3_B6N4K>;F5F%i!!;KJACp;`V)OlKIlwSIMR;SM5>a<>PnV*vIQePr7;)tK?T20da ztrV03f@Kal)?|SmOUOYUfQ^xxdL?eZe ztoIyd#DxOu=VRM|QclTF+LlH)Konn%$f3DThg{4LT#sz}W^d`o#!&Eb@2$}k2n~Qy zna9)u`oQ(@Pj<%`69T99z16A%tet_dXdPYVA?C*8``p=s9h%RF3if@ci1g_U_3Js>`uzK;EqTwtj}uCB9a?A?vgL%H{P7%?97CWt zHz#L${=6VQOd`f!t_ukttWkogC~wr4f--wpC%L~Kyj}3KX5V7{e0$07b{R z+?C;$OM)}@m;E!+AAR{|K^O-rOPW8PmwqL^VjeQw6#*7C5^aDqh#8xNmB=dmDldX= z*}+^L6w~deX!r11jy;EemBa9a&sxp_NX=tFVfueqLj3OmCIj-Df{4ARG~)b7qA7r6 z3R6O;YRXDSN|0eA6NMbI=el*hL$Ko&g!96eFVW5K3&3iT9FCUr&f`orhl9H>Z=gv> zGBkAz6+rG6hL}!Vs8N$qhYqnXtU-+?Zw)SC+u=p+z=7fz?@o>*Q4p zMa&WjPHiCp#AHk&kUL%63{7hmpD4YFuJoeah4!N}twqtMIh;%iAimzxJ|aXz60;dL zB$uUD6Ze~}OPCkw_JK>e`b-FXw*)F4*_A0Obi`g6XB9zmwv9M21AbzWL z5jhL?Q9p2pVq4jTsH33zEw&b0!n;6<*F60*XqVe6;4`lYwC&%8Jt_Uy~9}w2m2t;mf53V2iA42_E0-`3rbLjuL7RvxqZrkum-N1-&!wB%?W- zYb$(pb>9_V&Nx$VXx-Kuf4X=*bq+jqk>?dxZdL(^isd3Cd*KeuRK+ zk1EzKDoS@ER9r=ij5$>HSAmkRk~C{rOOJ*Ogi89j8Ihh#;nlZ`Q}MkaA}OZe96$YN zAk=vj3C3h@OvF^3_66`UN$(0@be1kQ1cvv(oX(>TRx@PmpPR}@bdjhw-B}Jm|IvjFUMf!g@L?rtd9qvwUk*ohMZ&Xl zuQtkn;zKH=NZ_3Rlbr|HXDA#j4o-ZKL|VYN5-yb>`~BvS6?7k4c)LQf6LzUCQn4v2 zb~aLsA?$|G+Lc|GvaPbYv!Q*m%u+wF_b;y2#p&}QI)JYV=zmpQzKW?`g;Zv+4%7%uctOEFo5QH$6M#DY0*<839Z^T{vA%29em_ z7=sZQk7>wJbwbh$+F^TA>+UF$ar}y~LL`31ur2GtVEW7-!V#KcNe&2_J0@bWP}5Mk zj%IX<(^sMfjbfQVlDBk$Mq)NU?As!3eKhPthp=g+p@<7O^|A?jPom|Rwne~6&;QBF zo?`{3PvlgMGNmnB&x*l3EE5|&tcCDGrd83&+rj!7R!Qn@>#USk7Z>f;H_<~sRM}xa zOvhUsAN8=gT`z8JYb)6+WB(Nh(u>dnM8tL!&9!jlH=-O@X{;Z?y02Q3siK7g`f;xv zGgew=sbEJ&$no#?`Kw=ct?~i+vhwANc&${sShzSy4o#P>sn*M>W&5Kx)yO#bFEd&AFvd<@|yA8xb+W+>om-25KR>*pjV0)bcFfe`(%S*Id+P6L|3ETQGc~O0YPQvowNcaJu~5Tj-0) z5PzM>ropcti4Z96bb$5dUq$aGkh=1xoQH$$doVlL#r&BU$0D*435RtLOu}o{cS*d5 z9K}!nF1lNECiBD>O;bc+v|tILw86BsJ_d^W>m(X@*SNpE<5^6ooQ_AU=_1%3)}}j) z*&)gdy9v{o-g^cur0;XEvwCETubn9xUr86qFQe=^`s`iPr01{Gy+H~w&EJcZJy|rd zwyxcui`f^3Qx+psN)ycO=>qLJLf^^o#-dps7v^?5f|=g0rC<*4f0|$B4Kfp?EJCap zbXrKSW8!Cb&gykK$ko!94@ov!EJZdpW0x{?JZ5ckRYoWTH}yDz%w2AWhucfVpo>Ie z;3Gw2z>4blkd-!yQ0F(z@U(ugEa)o_Z>e}5wn~y**oWcHH;PG!Qa~1-`Cy1}Eoxw_ zZx>rIzZ3j-+V|nqK8hc3WXVJSpJ?Cz5+mdX5T5h}?6wrp7=Iu+nX)>gpjCD~0x+?@mg&%HZ{UFnG{12e6X2!r9? zny5?4n@}pvXzxB{*3?*lAbn&`(u_1;Lz%L7P%^UAqoz9bD2^eAog14i47(2lXgw5) z38Jh8*=yF#EFAVDcX<54O6gF=T`NXZNSR{*Q%iaoGL57&(-UJuK6`9rJni`Bo(Zt0 zE*prgB9b@t{J7Tj*~9yNN6dTa2g%d%*VKK_csD(;MFQ*fqd!WegP1idCPXeZPM%(d# zx-6fp(iE^P)IoBTB;83U6&0xg6yT_x7~S~-Q{r7I5kCM8OrE8BQ&I~t@yWpe^KX+H z|DW__8DBksc|=i5q*IQt9S5SRu2gf-KqwB4%Eb$oF9M1^7j*?LJa8-zLlhBRupa^fx%dIH}BB)mOHHa!$Mt=dDh2 z2&VbEgdg6Qh5Fw&I6kk#?e%TR>?+x*_jYu&eb>-2?0D6CX=!}soaI)_IsyuJ=tm3q9T>YL>6j=mM2KG?NL!fJ6#{sAU*xfpD3eZ1Fv>dp_hAb1^s<;eHqV(4k&TL zT{O?y_*392qlRbyF53cseGd72`*u|^m?o_8B#T7MY@)BW z8aw%DB1Y-k+eEC`)oU(ea^$_*I;uQZ&E*N&T6ys5DR1UQy632zn4{$5ZHvNFiQ1V| z_WTsVD*BCYaibVN#%YSE6+!EwGXG?GJBL;zVu8vX(U2#S(8{r7Xftut343AM9^Pbk zvrn`)2l{vRV8Ov*HHfzQ$4jT47ror{pq$%-aO28rUJV3j0*Jb&~RXs8Zd@lED-22aKga|x1W!*0yIT2 zNV@&46N)@j12}hNgDpu|ex&AyJXT02Wy_sU6PBFWGW( z^Q6Ke+C_}dS01m^PzF>_sAaIRhK;m@J3%F=9b6QQ>lwFeYSSUuI9gl$t`Zd*EXX%bcJjp z()lSnZcE0qWB9Ao|HHJn8MU2$bPpcTaqMTwSK{$4Tn&MyW-k$zK@Ud-zATc;kJ38h z)brB{)bF`_yRhih9B6?|;&kZJ*Bz+!=ps%Vb!iU$_H<%{D4D82;Ac;#Ak2<_9PTl= zxu?gYyOT3=hcmY>X2zDosHH`OX%?$~D`K^@IrglNhdrTHsnmY^0z~AU9*ECdlI8^f zVusokh9y7W@HGWdo$Tva@zSIbj@+wdFAZ(DYdEte&WuUF@}e~l-#q#iVt;|ubq>u2 zTD+Fz&3y1L1PKoCWSti&*MTXGqtXcBn^7jJ=0iwFH3`0H41R`+VnRi+2eFHa6$Dqn zsv=hkMxW6mdUPPTDyV`JmlwgSqMQq6je#+X$`yp485N8h%?&D{WqngPb0D+xwz;l> zJBxm`@E5Tw%Cx|>kX=AL38(x{fqYqb)bsS0Fx~~3Z=V;d@1+RwSr=3R;NAk|| z2fhtJF~}Snf!$Z|pe(zWe?37ojZleUkx&j&x|hS-y~aZ=>+QNIpY%{hacrbdU9#~z z#<$3Z6B#|+s-D!cW7C|}j%zS_8QrTZ(nAx*y019*(zm``K}$`3nJ?e# zHdsM5|4JGc4aEB_qxs|F%>{y@^H$-LBMdiP&=(0|@rjaj~WAMnYq{BK-L{_Va0 z`BMgXi~sxU=c?)`peP}J$uj`ffGWqtRC~adb__Rzi_np3vocYM)RiI`Hjmfnt=iqX z*W&MIiD&I9WbiZq?#mr-dpp zrVz&?ZpJr2gF3pzNsh`&_g$_^EBPzVpmuKFpvdubQDvWP+s*(gKT^^r41Vz1HNtTr zGnwq(SH+RNb!S^>pTX8!h=W#%^+&tbp6z@qF?ctePudJnu1FLg81gtC7)m8xNz5fTJBixYZ!UD3))o4uYbiCtw& zbQ^snGjv(7d$Zc+20(CHtJb~d*P2@T?U(X%iWKD+?B934alKidmz;0fmo2Y@siEJ% zs?l-onT}&o-Hz?S^)#>HpypeL{kyG{F>P2cgsx74qBpearXa0!!NAMBs~;S@)AQjp~P zLXdfb2zIYYa02Q1ytR82F#Z?)EU!O7%{*6t5&Un4T|D(+?t()eew_)XpuUs!&@Ow4 zvHwcMspobi(NCFXu=CuGH`s?8f0TpV8LTHu*yX&}`IKjOkm>uCF->6lC_(WRDlmD% z@bLI+<85U3h)H2`ei!XiWIzDVvi}#ZX1s^(YkP2t^DBW@Kb2R%71aP1^tG0lU|@i? z31dZs4X8G0kp!5noOS~s^tIrJ=24=INw&LXH>&~d1;T4q>&*zrvwDDqV$A4Z$;Qu4 zoo!SYkM|b)TEFfa8w5AEuwbNTJnwD>L3gHFl^46~hgM6y_pILS_k;N)87Xvzso`Mq51JLi*4fsQO?!=V+)i>iCZ!$hB#g8pW(yTsqW1b5}1?D{Yw41lXzB|0g4V$ zZgzl`u9n(`qq;K1ZgkD^u)ogy4VFLPbBOmQm1sfGp{(%!>?^KX^C3rvl{yp6I))ru zC*l6FyNzaF*nM+2Ul~-wZZcG1uUG17wNyob1mCh!hr7&u$KS0QPInC#BxbD9_UYci zBRh=yWUaYQ_k3sxoR52fynGh@V;HfcBm?}15PDl`zhs_7N|IDJl*^Eoy$uGH9R4N3 zH*VlYGxgwHI|G$rp>Eu=^wsL1l^ng(Ak~Ur+ps_MtyZblk78&slSY`5IH6Moi5y%= zEECPBkbC*7K%h&tV1;FA(uIANdk}H^hC=d~OUwFpu6ULyYesDEb0!l%igXps-1L62 z$yVPa{=&y{t4B~2NgdcCS*&_y85fmNluGWyyK%rlHe5HTTr4Ubb~3-!y$r@oj~KFrerBgKpKfI-n4nT<+zZ`daTg!sUIBSRO9mmT zIUVlFx(oYpPUaLapJvrwL16K6L2(SG{nEIKl!M)*rKBaJW-PVfowIM`wr#l|d0 z%}p9Stq}<)L8L0`hY?J%ShW#OCVj(+f`z!Cz@18P%qpc``-CalFGQC4tX3hc7P5Io zsZ3~+1{sS0vnhfVo>*`ypXG|sZ2WcN^5$Zn64t>HTsGR6IYXP(B@NnCFz~8OLKrnA z(r7D_`h;^~v{Psf3jTU(a&mEgHCf~oHK)D~K{OT7Mr!F{)eTFR5?!>#;V_WJ@N-R+ zo7nN#%rf(P#Y+S1M@a4x!NaW9{x-#qnD>b(ZDtj;Qsb_EFBX~4f#i~4Dq3mt1vW_* z5mcI{mq_O5&Q9G?E6%d&K^o4wsqJ}|J?%QNwnZy6CK)s|{f#A3Nm-e4h2u3WqFJnG6GZgad3=1HI9j713fv(r7VaY%WdI>5-*X zn&k9GU=>B;$OhEbYEC~LtBd59s*8kZEdncgR_QfHVAffq%+*@<%++d!r?Eg*!AMz|aG!w7g(8--f#%P@PT zPMB%TcTmO)CVN;|z$81;`meT_&61l3Os1b5Ej&ieYvecCZk(qA|uod@Ya75v=h0D=Sm|Ax7og0+d-9HZ2;E0CECa1~Bkw&?! zk7XP;Fqbjn(4lm#ru+%H8lJhvE70W!Mm07( zK%7Hn94vX??7!LUya{zgOL?8NwlWrIf#!vcBT<1k=pSr)oV!T1uB@vSBi;q-ukC8i zzM^%hQoXV*2ZuRaTqOsgT(QK1E!`Dx4;4n2R*K_+)4tc;Fbf!{dJ$O#jUlEc&vY_j zg@VjI+|oezk+dlZO1D05N@;@x;UfN=K7nYK8Q0{{D9uV++iK;!ve$-?VVS% z)XK`r)<&<;>$&JoR%}^8)sn5?MVGAJQ?_X8CYMH0cHDKJ*JKoyHJvXlC5E+WWtPW` z82|LAVBQSEkeZ50Pdjz&ku<`WPmH+K<#Y;3lW_vl!Ti`N^b#mrdc5prYYldB4S%IL zOF$C^swZaCj`gfdxZ5>k1|Y@J4KklawEs3St81&-O8%S=Q4nb@E2U%0phTG74WHEZ z7&qX9axN{WeOb^flp)n0{iUSZ6Eq-(|2H;M|Rr%W+^oq1j75#{EsNwOM+`s%i9Fy~U+8gyu&F z`PwN#&>g;rK0bcfd4o_@b1QbXk*!HJIn;dA_0@F~uQcO04-B{okkSf*iw*GqCkr`Yp>$0h;RACten5}3V zUED*TIfWt-&WTQ%_+b3CX=i`PQki6B}DI{GL8aGt?az&GCiA2$u-CxkPDMevw%x zI3ynYtsJe>)VE=`jQh2FHr0s*XiXFJthCg?zp%-~ZHCw=4Wsm9FD)N0m4XzE;rYYD zI)En~Nvxev(ht#V%zOKQ^NYnCO0~Ql$?S*duYRj>1}5ygE8~%ls_ryVlf+9ru4u}2dO(mu;E0|?`Ze@K+J8DM(o@gQ17Krh}^%xR@*H;GH zC(WmFO@hKR#mW;+)weA5O&}Rf60S5T6h+c32{GhYHHDu z39u-%T#T9U8@5Io*#P-AL1tSSDIy4SO6ELiZBcpNlTRYQzzn6a!$mOM*ue`IgJU+@ zL>~e7>VzP8q;=0x8W9p&sG^qjv;b&?3OM?lk{L~t>Hx083y2-ftDI42fR1fAwA=TL z3?3csb4NPb?^vRNFM|KipdAwbTb;t+(N_8-u84wuWwQYi3-ZEpdhNb((+RhUN- zOKTRj;rXqN2y~E^CWPwveHfG#gKP@;TtD+UxENE)`GE(nY~f=DBZJw!UGskPlMPkh zFc*|sfdr~O!c~d_WCJtGW?5swoe8Buiiac*@#t?hqMe|G7!OQ;FN&3hd`a}`Qk%Hu zfgyi74f|o8j&a-Xf1%*^b^Y9?fq{Tn0Ge>R|7l}k`%hy5Nar15dpVCleGxciI!?r+-8pG7*0EF49^POBZ_kelwdwk9cn|c9u6W?na1LbnY-`GTrx3E=t_wT2N-HN zF#lNW-7_$YJ2d)gYxoJJlbx_LomO_EM`T*FscI}`5?=CNOhATdO;RPvI@7ZQ+gcma zTiMc9Mg-Ln#z7ocgUuZRzQ^`Y&F_)%(Wm4Qe1GP|@;78ZcukK!%XZ$|n_mNIn?7FP zrFgOE9BZ#i2YZ^dG?x+aCDcwfHw-^4ob?r-Mx^W$2Vl>a1|C& z#|e2B%Azzf?theLM-7#Wd7t^H;b~76{>2`5T*;sr3u;Y!7>wrOdQ%Fw3S%>*O4(mh zYliH;66#-{xis*sR+Kl?>A#l(?-muLcE9680*EzJ+gm(m41RdZ$d{ASEQACC#)n?d zspwXD_vcVj?%@Ph&n=KgXPHm}5QlsXiVuy(Yzkg_)oP);SOIOI>8dK@H>xZX5QX!< z<|%(MgJ$K`5Hy%Ysr8>%P++)EemT%7j;ODXGk}`?A4d-U z({TP<+~9wZRHZ614gjy&FLwDuRyb7Q$q%D~xsrqq)&3xLNj0=8X(C)@>H%=OZ98Mr zTlHrMC4Ur1PBjqd8{MQ1$Nj;u9~d){jfdf5R$@WJ$YQrW}VAe$>py5Olv(o2eoFj~Cbk}Dcx{RiG zS9ltA4{)4@$h~+A(@ZJBh)X0T>ZzQs$yQOb_SN1RqTly^q!kBZg{Rub1RC8(vi3n0 zO&qsgpogMzs8Ki$gK~6DAMzQpQRBG@$#LhwP(2jR*|l03mqqf1W7TxD;az*Xi?H#? zcb`BTNb32VN7`MBkMuN89hbDVg}QH`XYj1(N3W08qD>Sp^@4$Bf1D$AZb(#~&M7QxFAi97cF(Kq(kk*UuAhdk`l zDeyt212^B@EYXB}`u>psSH0f5ISQnhHI@8rnPNav5>_{^xNe`jR!eq{SGriVO?Ls0 zT#Lap;=h_n{sUm-HnMj;0Dv3+ZvY15x&Fhlma6C}pa`MzEz)h5qRzt{KweZ)(h=wd zk3tfW!&MHKJe9728B8_6UT1A%HSw?=I6MgDOR~;;d?!QS-?&>zhe0)sPv!Q$PSAy4b#0)*qPQtUi*u|Y z?W6DBkE_(sWPH%!d*Az9OkKodFa;LgX-gy3eegzud8uDe+w%D4cjN7MP(!80j@`hl z-gnx1cmUu|dz!}2kjx%WPDrk+FPhRtDObLSL z+)l%%Z&7i&ZnN@@7*TnZzsuOb$B8As&V{S|ny+v>e)>4bPq(NSqpF`=jG{MbE-lk9 zD!g)I&0nLQoqb1@!y0xHBbO{3<9nAfd}5^ELe}QTA=`0 z(V7OgDWkX#z0;R?(6cZD@L@;vtGAvA6Sx3Hb`;b4tjJg~m{&1|T+;nHH+c$zyi(2Y zdsy+{u>cA4ZcAL^k4I7r7Ko~`m+H*X=~N@8v_z)xD#H~Z=0%AeZb;`()g!wpSQ>*@ z=1|(a!cY1eGK1~_`1tbx$F;vVbfqw{2V;!mQ-NS4WK4I2jbRTVj~}Z zD=6{)yFf#%md?aOZzKj`l*oCjZ^;q{5Qss;_GI_^=MFoWExHuTKI$I=M%$9`|i@pMK;4K zJBKV90%rf+)6p+hYlL)~hbm2MHtC-d$@C;|C6sU%Ftu38u)-6tQ42(C81IopGB!=s zzOZrXwWoo&FrijCfFQK-J&0}d1$LeHaLgfzl3&DM&>IMp_0m^}QM0D15K#HBh%*PY zWl-f2tSq6YXKF~30vb+vsm$eEVf!HT$T&k}x;Mmesu-sqo^U^$IcTLzGB3CPKsQ5Q|-gw?_A>j{x8~+e}MZr$NAI=04)_95RmZy9=IwFc4q$pUYGv|T@4)# z6b;lbc}eC@*ht_)OFHfO6A-YW^r}(uXWVWbZvO&~;!pwo&u5-Mqb=wHGy;eQ ztO!ua{mA^bbHexoOoDhwrU(=e`G;vL{+#DS*qMiY65!;(Hz4LQh&hOLbUevEzmNtn3Ua7pl*@#t}6C*=Lm6CCYRjawQ z1T(G0g;GC)p3g997ud(m>D*bp^K4vc9w6~55IQyVV8*JJftuE{j%be_okN(E#lX8l z4t+pJad~(_EMmd=(BzYJgNIvFFU5s#<8!$Fi#Mr2wJe$}*S0mZ(?+N?xu`q1#)jHL zCxiLaqBL1dxPVqelB1YlUb5u`q>zO+Ohcwu#E-NI-Ao#uet1v$J(5LY3tEp}rc0e1 zNuyLyB_*|f5DYfjP&%_s2YRkzShR^)19970!MRpgHj$^*71Wm94ZN2y`rbV_lg45zTd zUZxPyo;VLt-4P0qx~PtLXrz4O;0iq%G-cuXId(d=2hrTXSyA73hK_oJ=-24%zgax5 zaQ1i24p?0{y_*_pFj`-8z&PBr2juq99dyRnL$I`i4IkeYz-W@&pekG(r}#s2)&|p* z;@)p?G?pmZg@hKaVuyZadTMmmTE{u_#jxEvS=hEJ5*0S$kf#_>)3)N#*OnT3i;k!X zI%y2&ao1ym1)I@Lq&WN(m=GEmv~0~s1I;Xl9NwLOtarTUes$!bk}cTHux zuBK$Cl6q=CV~r{Nnm9r{5)tdgv)boo!fK)BB++C{2y$~u*2P?GT(Rmbub8y`!#;@c zS`2yO>{vtmK{}jZiQ`Qmue)Tydx9tpYh*#0v}0JaDRk^o{;2 zq`2#F+(kS*=?QoAY?hhZ{T4_&g~_4jO+u#O`kxGl#{D^i(M)~;J7 z6tMIV0a3^8z_#0K0q4%k=p?R)!UnyzU5eqVVp>yELgMdjyTMdrSr|i~}G8_YTLa zo!tOona4f8^Wuz+BFQJ@X#mK#YA(*utj z^6lcH52Z)=nJL1)+ac?-@)-TXR*cicFLcoEbXx9bqrs$b<3lQo#xsk^~8e1^gm5 zoI)r&rS`XrV0z=EwgzbXhKPI}7MY}y$?-x|f3tCq(sH*c*)5$(Zn~D>oH3SKySnyI zpRbuN-fvaxSanY!tE)fri>+=kcZt;V3L_wVBe5?~w?u_Yc(yj$`a7Ch)}PDpjGZFT zu1O`I_H4Pae{ z*34Odu=pAqJWJhSAO<(%b=ok~SL|Axs^p3gn?(;i@<_I^ysr+lpx)T2b=7p{hpJHB~-NE=v2m%-D%yBkNwS%vD3EG{1I zRmREHQI$BAfVEk2x5~s{@Kbv)sn#xs>ImqTOVGbHCXD;fP$nMnki2X)y9&4-ru zI9Rkwi%W)Bft=l++qaDGClF~8O07u1CSguDl*3*YL1mFSrS7C#(203SBF>#86$S?_ z)Q@B`$LH*lYGgAb5=9PDY|d@-Um zrb1&2xP=eq`BWx521(G5P1c^^N$jREDbyckchOuo{M>!NED}sRiuS3|**>wk%4)(j zl8RS9f#FsibBhBFh8!IAh8*s(Q*?%I@4CWwWgfo@_TfI2sNYii>1-afE#6J<^PaVv zd%AJbr}=o@HlP+g0+Q!;hjp;|qc#5U4Omox_P#`KJs3wt%sR*Rx2V+4kphIl8O6R z`I4t`YETB4ZRbRYmMq|vvI!8ZHc_i<8iQ)i`BB?f(v+?hMV7$!My-BxB8{!}^w}4!NzJl4 zb9P?9WD~cpm!$UCVY@g2j=^Qsm_c5WAXfVmCr6qnZ8knt&G4bYS^OvvM#5}VR-ryo z7lxc{dtZ~-juw1CK4>58Qd<+9`E6+;^M2U%DHX7LtZSpQT&3O$8oqfxLBsU?)>U=OAP*s7r~uwxLv`60cl%KLx$9Vr)^3bI zu-Wc(A}GK*93-sDsp7;XTL*D9Yq--fM2{J9~Y9dJB`=3yZFWo>Kly{ zw`OZ5rtM6Ry?M(QzWCGH3lQKPQldPrsUMYjNRTktYJstzH8sGu0%d`YAAmklnCoCT zN!*hXQ?Z}Ep_m;u3P%^yg}b6>F#JBA3y0%efxzw2?cHRVIRqtTf-d*>#(WhQTW1?? zTB~JhKq21C)v=E$24Sjjla}V=erpiEk%1dnmxCc|jAn_%^xP``L*^`6mZ8+?K5nftuM&-IXRElwmhJ3C7pY1k-FD#FPR zp%O!3&%}KRJL(Kdn0*v0_K}u?9A;+L1#SOWJQYNa1LF&zb)FkLHbyvoc zP}RGO<%@^G=rdqhVFP<5Nw{TztgTSIWiw0rVQY>Fe683wS{_v)iAH3k1kQz3?&U<` zha{&>R+@%gvT}mGTQU|keIVfTTu&m9(p0#JrT&is}Q+9 z9J^1jZLb_J;9hXv-spQef~#ocxbb{@h>y2_ZIHI7c5*Z3nj`h&K{6U_)=%WXKq1QE z74DPyd0=)K*-OgXcC&Af?IU%l+OOiqJ%pd`sXhA171QyGjAuH=QeM7DQj1LV0uX^mDMs!(xH$qD51CEpqAnuC$7EC4L!JC!!vcI-rzZN{!aq3K@Kayjx?&hTEk3R59pB zRig=RYNs-*2F!|!+K3~GuDdo9@MPDOo7Q3P&*-1;;T}skBsUc()WvxEtd`lJ=bx+A5IuUYbOI>0A4rvcv;rO<@ z2rP(L>Y8iadBcWw6HS`@9!H%?e-oCBiLLD~VWo3PXOEz>@pG;Z70i#k`E&9Dh*)W0 zmN9B0Gn`+xk!1sv2t&+~g1t`R#iR9P%YL^gc}Ri0^2ERVr@?2U6hDZqfuAJ7?>4&i zLep$wW$^2RXM=(>9Lr0LN(Lxo1?I=_mk~(kmEzJcctwa4J^_ip& zH6GFHh#v3cP@24Phuzrl%>uZv!~YbrZSv>T5jn%NIv2tQM-qXDm?)Tg9*9H7!XgcF zN>T;^eloU>SUvxbR@q(fJp`AJS5M z!g;*i7(|nq^j)w%)NAK#ljrQTwY4@p-X0tvwSc4%<;jcDZ=tp+|J((pHMzYa4GByO zQz~?f)JYRog!eaD)19Yz_6qB!6Fz>>p~qiI=(%&9M!CwSE=w5f>hy^e52N1jn zxmxcI%VNwYo%0A5%OD1)6=RJ)kKT-$#<+0ZPqGO1b$Q{oh3Bv--op$CJjCMJNgCCy z=P@FFz^J-wbB6by(hoEuIoyRqN-;S?26rMw6B4j$p%_?~;DItY2jM)DpOr(3fUNr6 z_UB3{e<_ewl&K)H~`?O5mKpwX-RtRI5;e zOD{oOkUov>YEa+sS}tW&D^an~TXX`tCP@$|C|JpyqUq4~5LffCZn@eL>5|J&TQUn) z&nvYSC%8vXCH2T&=8Al%2Ur*L@Gy^B7GzpbJ7|_ zk;)4drjDaofNPLpiNb*VP9fI7kik<%Rf#3R0`?1nMOlg_J znYkb-qkCJY8`eis*P-_3hYHUxBO;(!Stx7Fnp22TfxMV85HHA(5~P{(_u{;up!A`j zT!4bj%snej-@UHk?dsmLw&L@+Foqr(wWIl%?Z zM#@t>kF9*nU+Gc6irLBa<-sTGsfCLIx+?MDB?QMz%poaomF-ga@zG{AJx1!ltF3ZY za+eN8H0uq&J9TK|ui6w+PthJb0T_9V$?;v)QFHGYnFskhUQ|0@oK77p+R>Dd%)4{N zO&Qj!_{|h%hi0TFQJPb5^CX7dkf*yBVAXdew0XLdn%pSfHuANuFdMS@fmAOQ=j)L^ zb@m?Ay%mv*QZUIxtZXy!nvj)Q5wmDg43sOKFZbGaX*6Hq!n~_PjECe@Le?h`aR8L00FfR&r z>NA>>Nm`bI+UZt9AJWwM1XwWy=}sIuQc=`y!cbftXq(aO)ZEaVtK@@*vhWg zz_v(VQfi)DTw13Wz_1d!X?X}9>U=sXokmP-Y`jAb2F`EjPCT1nDcf}e0Dqi3)RW#Y z@CsupP?f0j-nN|y?_YZ|iFga_9*S-3sa^k-RjD)ts#UWK;wSy(Nkru;|LbE%2TXlH zQbCJmXaZ2On9k4ydoH2HJcwyd$qoZ^3<0HPR~e;d51iFMeB~}UoYhWA7;HP9AiC^y z#16!!!WHbM+!gMo;+0RssuuzUZ=VPS@4yI!>`t70KlbrFh%O?!k3S^=Pwpt54xa&}Uq%ISir6&aydXYzx>7VTy# zpymk=lsU&{Cw&P$tCEpJ`GNebPO)@S@(We-EF>LZR-M~%_Qf##_yt!%WFU7e%IcSo zR`zkZ5ItnZ5smDwAx%ok*QAj(raRQI_tqZl45x1Y_m28b4&nsX3{tq{GZPWjWu`Gg zA`AAz$Bg!*dN()Is41RQ;~Zs0v~mzdvQyuIB_rj_G`o(~TeQe5PSM`!WVmT3A2rczO+qTS4i3fFHXIcDL7=bBWqD6+VTCf0JWdV`hqD_gl zkf7D!9rlf^TbLsFR?ro|X;Y$V1HiOXcZoDpw48a$Lt=A1NQ{>0unu3@UtkB%4(UJ( z`oWa+g0%QnNoQixm4Nb`qByO6S<6t1qZ%vYZc+k9FKzmWXyDj_Kwwb7-J;sM787i! zu+dpxX61?V$Txiwn-jM$d-j_*i3?I668h_7VN3K+kei_ynTRVY5?T=%bzvZmcG$ru ztbxdmUjj_PEDi*jsX~FIpQSs@ zYb2u8VTeyr2mPf2^wGbY`IzXUBccx|#iL4pmJ0zPnVLd1%jk#ohQlFPMS)62T$Ic7 z@&@m&zQv48<54a(DDlC*a-ZAxUi?@bO9B&VmBH}WkAqR~PZC7*kmth%qktPYRxS)B zS{;EJ^+xsZZHWkA3lfA)^$6tysF)R`3wYcS^Nr95YUWz7L(XWy2y()Ls6{2ip( z%C9iVb?^<*w8ky#E`ar$e(RLRr&7+4Q2!3_Q5!X3LvImR1oRgj7uv1IWRIntO$LCw zC1@dQ+xlusbtRS2neRHM=Zk|fp6zenV^>(aiBU?g$JK!<)T38Ajm~?x3}TbUM9tE0>R(yOEUvKM>8Aif4r=! z8xBZD$X{MrLhJs(6}wT=?Zi-H)?&_eq2a89V7l1Z zw|KilV3~PC{wo-wCZ2cRdYN+=UbjB87`HE00vm;TpimrX+&fpEC(oQGm&X$mXgq+m zur{D`eh46(8MRd4aJW{(G^;e?67k&eT%=>A#4#baz|X<8aI%!Hl!2&RIQ@*SD)AqX zjrgbpI6|NX_QjI+_@Q+5kkBAtApT&`iW4`^_wMSy_GbC7LQ!2TiSY22!@B^dD2Ob~ zn-%9lo>#J;m6P43CWjTB$LJcm8qk9y>$g}9lj`lNN{#H@xeQPslhu#eicK~(qraW) zX98N3H&-XJy*8=BBdDjTBp&!BNeaL9L|GX*)KQTglFQH@tD@uZ79=5I?QB%1vVGH; zq`R16SH_LbR+=vu2b1)%()bxIQr%UnF^ctG zppc22@;Z|RSqJJ388|3+v38lm2-rn31vGwM&CR=AH^Qi6(D0TgLAO;~|Gv!RKhV@Z zY(b82BPv)6Hi9HRHd5%q4z@6>+AbKXXxCI|PG%o0ZM9%UdVqwaP0SJbyr6!^iyb{@ zSs26$i>KYs=ix?*JDnOE_0_|zgQYLrIH9Nmu#}K*u<9}#A525?;83+E4&M4AJO{)r zko+}9y&J!nyO#WzS5$5z#@gT<{iIaRRItpEIoAFboNE%G#MgpckXf2Yji5jGJ!3_2 z{1ig)cT+11pGBbnio6;il}SGR{^3Ih^+4fPzyU^=T3P03b|!B2S&3d%q7!7hSqPNO z8f-=~j?9H#f@)E3ez**^q8-+?k{y*w`Dwp4rHZ&cD`6!VN=Vi#~2 z-v#^ZmvZ#c1%PuGup!|mc2a#%lsbbVC_ndF!&C0k!&mGC`mik1Scx*g_oXJCi{Z{O ze%Cz>GvO#UP{%%;se)1q+7!jXQ_Wf zKN?0&>ge0i+z=+A&f!H9lVCkG`N#x9hEaE}Z`&z0!lGmIGyZIz`J(*KkJ(i2$;0hY zF5y-f*AFfQsb2$~O!_pu101hssC*ffrr^{k;{hmI4b@xh5t}&b``8ouFCN>!_T%LF%?PR+#RoQ&_%Zvad%hwrvij`Zue#bAa z10lHc%BG~;BPek6;#M~c{S@mC04)cEPTsytud#5mx^sg&8ogP{T(OL19V1!d7hTs< z>o*Q2^FX}Udsht?_n-qQ_xnpU;W>$6lQ6HG_TGeW!7HOl};zfCo!9TqyfTj?Q z3m!Fj`=ki$jLbaGBcK`J&M{~-frJ7UQIXZl>op^0GehChZX8=VGpJ;J|+(W z(Zu}1L43s>cm<&NGg|YW^BVq|cH=9_7OcBEc07BAM1!#wVI*aO(;2~$;c_G2rs21-%8iCf68flf@*&wnF!Vnr|_Si~Pc zwy6G#!K(k7F#Uta)ge3+mzutEetydi86Obh{c;2H<$;Vz{KK8qETJI^1AQSP08q%r zOc?0W!WwE@7pt0@mNc4F>~!JJHH5@N<;`f!l{YUwe{1OKdfk-GtgNq_AQYZ#rcK5} zdjZ^i#BY9l`{f)yUp9Zv_`Izq{3xE^;P~7J+^ezn4DM;W>v`1*@3~kd8PFB0+X#ZH z8ws*GsFCpu>*DCc-^ZBOqNA7zgBl|Z(uNF)aAYVvO6)I6*uY;1vP(QEmQSo%FeQsF z1VkHF%!5)KJrDulLpO5n9v;DCL6mYT$|L-Ni4=cAX7hetZN5y82YZ$0j`dc?lU(X??Ni ziodES3|^3Ke!NnpwyLGNW9zOBrRR7c@n!Dg#Dxm3GV}5xMaHHHm_^`2javtPwbsCh zIS@B(?i16~HBoL2=i(9~C|}K4`Fx(syNL1PqOoMPwIuy%Z86u5YvB&+6oHZvcRD_P zo~%X;f1b$`;R9}9va+;7ZLQU)?>%)!g*vT0tHi3SWbmM`L=zGECRj)E`D=6hAQxS- z76M9OXtdGnkOhifK)eck>AekxG`iO4C8CvGymv7A**0`0fVDAl_xia;IvXM<)yjyi zx8#s&icVT^GW>PJKVW%+Np7ycm}A$qjEAHr(@HKXz|zg932G_pqbNmhXsKo^r4EkG50_7bG4W zf-0HIqEX5B;!HbfKt3DbqRTe9!{UUOHKHlHOyquf&%{m)JIM_1VR2Kea(bLLecPZJ zP?kxFf2r1l3D%o>0s3tnW_!unOWh&S!_8h|$nPeit@w{Ppd~RW!WBo_n^K)zGJ=jKgfkd;n>_@6?ee#L9 zb@yR!tO_AM@^2f(@FE(F;5Bfe<@G^mSrXLj27MOofg`OgmyRRU<=^E-iX!qK%7`u| zR&Fcl5HZDr!rpPVqYY6y29 zWH#?&3$u3`2~2|C$nnvaE@slX0XcNO+^oqRmQiL(hevZAEov~7XIt4iMTwfdW901E z7KXQHj>J36Ojg`VjE?RiRG!)Pmlo@KzlSp9Tx!xAO@sY<* zK%g^;Z!!;eOP@z~B@;oIJhW;!4|gUZzfYOqEu?z!Xu#9=A)=5_oo6B49__6^|MaMs zWleV`;@Pjd+SeK{-1S7dCB&NH7?C+w<(MJfvVf!C$0^+om6PhoYDIU3yKXv9`a4bh zW!@`Lv4=w<-H}(D29oR7e86LDSeENnVa|f_lN@~VyoXu|4Jo%kOnPIIyYzX173ot$ z!>oa4EfZhN!^gH=rMPTpGSDOOLF6y#vRjY1thUPX#113U?tq*0#w-^5`q(GT19j(A z-PxaRcNlY!3EBC&XN?1eC1)7Ve;(J<#Rhw1YWoXK&Q?gdLu8Tp;3a*sS#0}rbTqMy zQ>;`;i_uI=#DC7q^v4Zor6)Y3Chb{d59NgpsU$gOX3?0xg-dJ2OqbKb*uOwpg{V3 zNSE=n?#W}7a#wsc^&WPc+9#|TsF5(+Xf<^A2vN?g3{ZG5RB8n+1efHk{GG{+=ktR$ zIp`Tryu8WddZHY)srG3 zrqgcw@ql>$lM?A9*PN&s`~G!WpTZ#y*6FbJL-7Oqnc8=-DmjzNh*?FB!>+EM#$bXU z!JzC`qWFy*o#9ETobw59JvqCVQBPr_u6pZyWg+Gt0FaoUOvfkA>!Bej_rcFre+DF8 zm`iw`YqK-++BXLB(7y-MJXAUf;{SqYNG=NzaB+Q_4N--a65rFz`|ViE1b>0)DA3>b zg$K)Zh$NEj{+X32c!gj+SU0Xwavd5xPf%*hC(0p=!P~=VEnI+sUq^RBbDQ)_kg$m$ zY?v~v?aShm#|2)e6XT1l_3&cZS+~VH7#0M>%+FINS=QW1+5rIx@4`SFtee@r z$_EZk*bF(e5p&;eZRW3AyEtDiohsC|e7}do4U-n1q@?LihCJm@{^I_1^#ukO9c{&_ z`e2{_j1`olbiISf=#cgS96yJ}FFNE?WH^wly|S=|BI~`)^y9UCq=c8Bz;?*AgDn83 z=s$n5!_%H*2jAFzy|?m!1Tj%&j8tjsW57hRzys1v=+b4H)+AP=T+o(1XBBHE&sHE8 zLO7SZKWw<-l|303gPWk)9imoJ8v)`REa$6C1ZKrK^fexKcq#R3B(+p-)%W4!5FiHc zyPj%Qh*sMDX{7z)2@FgQd){2ROb!$c@#bNA%u9$1#}pWFq(+^?j?UE=6Ysm_n}9DO z$Z)-u((5aX;Z{QhU85!jPkN8f_u*CKdlWUR!StH+&7GsoD8l)=+4DAt znUy8h6;#PDFZbk^W%*n+$CAAStC zSoi69v2})J84B=5@3!PjSxB}|d(10XI~2}bH@)P$VnyGx7dkoeM(;JbZ!;B)-kx`n zw?R38($zK1S9stEi@X((pAQu3U|%@6XLXTh9mkhXo{-yb1=8z;sQ%QK*jVc#zYz|a zRF+|6x!O8+DnzF56j6u+nU|p4ue|4YmoK`rZ8MiyUTmGHAVz-4Uq7k7Z|}PH#!d+- z%7~qrFw8r@^0Hmn{{H)-BqDuEzu&f=9Iz_(rT)itvm%FzNU(VzmKDZ{v-&)BBx-z; zb(tD-!V|U#S$-2|Qd+`Pwlr^cXimF2^|wE&1|=IIZ{*vX5z7s8BPbvDf5;K+_r$=S$M6DNh@!U0=C&Ynu zldA>G!POm2%OkDM3!I=fLKG?Y7OC<6Z|SUZYe$zTjtmPpB5cQ>#^`Z=Sqj1z+zkxsZEehD+$ff zWjw3}Fe7jJ(uD8AHQ82)Bp=@rCR0?o^O3X7wUhL*Gv0~Id^Qqwlqy=;5-sCsf%*;TUk`(9^ZFqB*hSX?3+V=(QV!6rcr z6;ygE+l!n!1IrN&tg4WYm~IQ1!qbpJ%}4&t$|yPoqiF!mv;Lsv{;$5Sa)|9 z>9$&qu3Sp;M|Cr5kZa6{mIfA3 zYb1MTf=+Lb{X{Us z6~v!tP3Puot5@!AlMk7K3yjH~km{-tPmtYUKnrnVL@wt-*GsXx)wun;5abx%+isBY zlfDNM7U=#P#tx80RxiH@=WnU*!rjfE4lGc(>;AB9gGq=Wq~~0^xXvqqHCndk(aVO$ zh#QQ4ytiqI-lj+5!7IQOvqtk`O(FIw7DUz#uX@TaK?F6ju;Yk|wN0w^t=u%_=)QCOfP~Zj+DekT@I`ver7RR zlAn+m;QKZhnp_Snmp65XOVXVBLQ#x=(?oXkWzVRk~Zl zL$YE+k;OwkqbF7u!*aN+SNVGd>%Yk7$bA)7I3Z)pjl`p$u?lMU5eQz7J73+VgW}{w zVlb4mCP624UrEqtpwF)a?TxPbnLwIQdvXr*8j@zN0SI>7yrH7W_GgM)yGMY$<#Qz& z@3SE$9}fG&8SM&pQ=~KlxeMwg%5igcSAUAyq4IVFIw_&kezFGU{qE@Sqfap&jor^A zc+Y*2*#OqyGS*-d2v>nKoXt%Du?mrcd_}J%ddP}sYT)HAxxB~xFRM@22 zA0D_l_2G5;ZQ5XZ1&yWI#jH%neBz zbiU^VC?!^518MWhK^MaZwpc(E1o{`bmIcj6@)2aUuC%if_rpI7FYB>`*!JD_N_Y!F>#9mEEjOlW<@3PqjGOl zVFWu5gu0_tMxc{vup^`?&h3l+Pk=?DI2at;vjR=NObpN=U+Bqst)^a_=#u;gOvyiI zyAJfoy{2NdQ=f|7*!cZv#dKv)q-G01MjJ-!#Tf&zJN>%`_o&CDpuO=$>**xMLJ;SIJ1o=jt@g`(byPgzXv7-uaGpQ#f>NCnK<0p(yzizb(-;{!O?*B+?~r;~ zbkJw*cq5>34dX@xjd&DFM%0nz7Uk#Vm*w?lL%5)Y@iCS$MgOE~%AdNc;Jfc9EMbQ@ zV?-KU9~qDh%%h*!tu?Ic?T+ID%>UFr3$8&=hB_G-0s^hNb^#gCA4t;Y#dH+9&356+ zJM|3l0OPVv;GiV+E6cz&TeTC(43=LN@F|PUYEWu znv5em-8J|GI%#5ZDmFvruG)bZJKgeqAG+!|jhtb@;I97-+Qy`87qP-Zx+sO3{H=0w zj?^lsH2BWQW}2Eqcaie}-ikgXAc-Y!IJ9)~qwR{~0< z40uMMR5_whL6U4-JynUA&u2)Pu+&wmnL%VWUD*Pr^>9^*n173Y^M>6s&Sp`M2*yh( z134$ZLHSR?Qy9zQDBl0a@qZ!kCwRjR&9~4G>)XHb-!aEOoa0@}7P1S!kiEC9Dz{4M z$k0lBOGib(kmdP>ZyESYe8Iu*3@Bh09jz9ufDQg2eIiO@i%9<|Pjy}CG2klEOa!Lu z2NP$TaiDeEdD&ck;nnQ|pbH&BPq)h+0zpMnkF0G)7KIW?i>hm--jl$l*GjUBq9;4x zIB1Q87{+3-9v_`(9a^`7NI)zG&JPMnNH%~B?c`f^hVeT*1oDzzvD{LhJC^7^s*D65 zTz2f3C=I5Zq)_^)UQL8wcYa0!z;1iEiY~{SJ13cJkTgsz1+_R+*&eM*zN=94VqqQRHFOTEpKDzvHd!nakv!{jW7ES0dx)%Y`8Xb8X;a@CtbXm%-FB zjp;}CBYSUop!l>Y+m^6_(;2uqpSYE@5XGz>w5T+n>&HX~Pd4jqQjt(kP!2eMv!_bX zU!yl`7Vc6sG@jFbs~a#tMD5i0v7*UMP3HRc(IaFwm1oDdk)y#IukA^|rWHWmNI@J@ zO8`6!6_-TSJ46pwH^dv*(~X@7fPIQnbu?+7?d<4?Z@hy*ac6j*34jAgrv`KnMdAxu!bVwr)wk6vrRZAn07cjf4%2^ zV3agVecoJeUR$3VF@BH&BF1xx55{vz@e%epp#9R6wyDt*g^8gj6#B|K%^%_tzJxf3 zC*!JNSy9GcRBH5(O^>Gpub^o24^0nOmMby#cvpx+3oSo)2!$jbmIJwO*d4TvhXG$` z?h)4L3?OBRBi&Hv$eI=brG&4g?&1foOi?5Mf|Az%C5c}YE13^UR?+oZZ<=rOOnblPC&yPhoLR!m>dIB zyTrtYCsXX1Do-fI!X>HyNC=lQOE$4KQduMb^k?=m!_^?y1hLV!V*(TOYifiTC?Q4^ zVpbfE1j3+J2s@k+9Xbe@8?~y-I&}89`(|jUo>76vpwBHJsv}|c^U;fzIl)Gi4K$-f zjKe_fBQLyQmrX0%_1#_Jhz9SNA^|TYmeN&(jTOz*D}MyrSR$;)ywKR}fCJvo+j&up z>=0ERmAc_tw-9mk<6d0`1}Dvy>JskgnhKt+O@p06CgTpL z5ZyNK9G3Rm8|?&V{W3u$l=Za%RBS}_4}`KRp^#=V`Jhf;>M~%)8reRX@C$Q*+W9uV zD94<9ikR(nOi-$`5K&Lp1>!FcAZ}%! z+3rID`2iuopv;}2B#UDf3eyzfl)&194faon{|buGU_tlMZ&2v}*PvkfCn&xr27iZg z!PXsH_2yOi_}f*pl8!KYFn2|BrV5F20|7P*XW}lS&c)Y3x{^`2Cx_&k znr1V7#JQr|i(j(m7f4B^e|c);W9Qc$$kZ$2V`O@1^A!ZnSObhZfD9GcZ-tl7{djJ&E)(3xpJvta>D3}44kXCtGcwMaBFQ0zv z5_R=rM;|!ANz$}Hp;(to)iX}mD-p{A&A1{>aJ@qcbzP^ALbdmj@kJFyM6$fy5I4!Z zPzjxIqVmjG3UseU=wYOwhrl+h7-|;sfOv5Xg0J9$Z#eieC zN0Th4s2yC|=%?wzQMCqf6o5{2rWqxA&9T7Mys=nZF|Z{~8v|{|po- ztQY=Dz}W0AF~tO-XpW?Pxqx9oR6tk|zzZ95L7n=n8QQ|N*$;ljyenJA^5LWY6(zPJ zDWCwvtp{RBFzH>gkUWf1@Q_a36$M`@_rYM|T>C7H)`0iMaO7M88YS7|2~WR;6*volGwJsd0CI84UyC^?Oq$a#yI8e|D7Z&bqzyOq0ZX zCe;=JQV|gvE6}C@k#B_g54aYf%|30vX$pI^QXG?ecUQjfh7At(zHKZsXd{hWcW6zr z=aXsam`^y*#cgI@h-jtG=huZn{|nR{oMeHV?q{GP&R z1G>N!jQ&s$y*&`r8bg>Dl9hGOXm!&i1G49c=8@;r4#vsK$7{V&;V)4CN*ZnN^YF9( zD|E2@f6xI=5fq#(O3>&Ajtw$c+)E1Rmx#KUOWU3yRjsc&&GuBFI|K>=tltNAD<8^9 zpo{=troHNL%rosZ9h0-u4Ro5n*W2G7LCB)DXge`%h2>(oiK506C$BxkL6ild^e%cY z^gC|Rz$ca@5BCNtXYA zv+b;G<>D*q5zv7xYKEJdX_B^8fkI-zy=4KxwbPb1;v@+6#yqa209d{(mE{5Q=iN!K z(HTPlnI|h^%hD(G+;9J)7)Gp^ugjs@V97}CfVB&T>i$bj6e@?{69>?et>3_RC_!*O z2$qxJ)iXd}q;#j4D(bqwqUw75n0aWt6ks&VKPWg{w>f*38mK%`A#zl zzthbBF3@28Cp>n(-xT=IXZOc-d%htx`SMcatET=WJPGJwQ_L`FJ{V@M+vY86_mGt? zcivYI*M}iAq_F(}A77tDyXIs*LgL$6PR0(#9nS{F_xG6@-5)Bo*&#vi?jCY{unz~N zzxZw7pi&2jpp>DagD?ZUdIKa%Fv^9>1NGc3a`0?fcgs(j6Gm0LV1g;GkUaD-9b4Q8 zxj>soV!f`Zc-{Ks%0KZMPc9i!r)sGvJSSA|t=4ZwPl0ZL6-*#N;GEqKEj%1eNwe`7 zec754Jm_*-g9 ztsB0JrlE%Nz6WLve>Z`Gbd;srhYN75ZFF{JZ)msUK% z`;~hW-zt5&Qco^gB3qC<n5|P{Vz}w{MVpl`~QKmsxDtizH;3nsABggAbq+B z5kzTTa7edROUCw|Q_VKqC-ye+nWSnt%}r{6ePxowX7uw(7u&9jMDnzN(SJe)gsThuTIXS_Qh51s#zdOEQv0W#pMmx1*rPBHD{RT zlCDAO98VouF~3a&S--(AobSnIHGd8%1lyi@OAjvTQmbmoAiB?}-Z*d^jUJ<2fXW&I z{lPf7?wh!In2=^-F!(ZAzClU#cTh4${-(vV{|l4>{NJGDrV#lCWs0@Su62oDB@aP+ zGG2_?G>*ygz3XB!oh$7cW1{ReAdCjSGVh&; zH)1r(qtou6nqFPn@(Gq7kS@lQD#0N{PY^4YE_=#JrOi2ICPuV30Y#iYTB| zXgv8cA0P`uSUSC3uzEv`oL^}-k;||A- z`q}Wh{M=HdtLA={TSR~Q`0_a5MY#c>@A$j0qcy9_WA6lg$MbGTDF1aA?p73IrjjHU z9D-{yb0TxX3@VPWs%>bZT`kV2P>r}4F8HnL_+x1lE+ z{~la(c;PskG{?COQ#g$XiEH^Vx^gdO_BW!WtD+()aPB=&i#OII`WH;*b5qnOQrt+k zaN4%a^DV1@S09ZKr817hHF4uQ9fl}T5~$)({+d)BNHovpnF`g*Pq4l7PJk0dm&~Gz z3iLd)$SY-}HPi^)9eC{;Bkay z)-7!c@UQtbAj_^JaYCXYIX3+)#`lFCShy~otXZcM>@1E+*`Cab3$wE9O%TjRzH;w< zBj*cZNO$dm_Nm|P3&q*C1culv$(l65Yu}i3cAm`-BA@j|(Nm)Q#-zMCEKL-@38ImE z6?}_;eY9!Y+$NaCF@0Pfm za7?O=ClHpn@y(CFm{L~a0Ezm6adH!lVFpL?j!RPh1I;*5z(LhEY1z%W*5;;v9N)m1Q3NM#JKzQ-=6Ql;``L*Z%|f!J2}|@t19t7Jdyvb@@FYY z|23x6D`V9T3tK}%G8cYaG*>)*S-dA#Lb*&xxLiz9I2TYT?yqt&oAVJ{C|+;QE;t%( zn=M5s(Eg@wuMIzVsuPSrW0EOs2V?e{oA*QKK^I;Q4^Zl`0Jc{vZb0O$h;B zNU%7JA|^BpNbqX@k=iGsAXCDYq1?E&HXXDQwP>Y_2J_z533CRaAj-<&GwBQU8#X9P zk69B&V^r})C@RRK@(6;?+VNF$E-?BrW%UZ(D=ce9Ki!tfgwwj=ML(0}H4{fCL8Qh+ z>q?z4N1!;GWT3c-ORH$A%JD0F5w1ji1@vSgN!1X%T?8~E_m8o8vtXg6lj8^GZlrML z>@v#-G_F#{>*eO4vlLhn;)u}+f8;-WGRC2bK1=3on>S*1&C&grugO-4Krn4L?h)LN zbwWQQRkxx|!Z(sHb9IYLh`F|6QAQfKnva;419u!RIQH4{3PR0b7&(mTOAK&XnKli{ zao@K%g6Rd$Uu<2_LBEm$^ zz8nh0=5nI~<6Mm!#FZC~Qk`s_+pFE*b3|z`@ii zrCr=ldn4B+Bz8#J%{R5ME?ntRs+95!_vxk#h(?ckh?Iy`011*3FizEqxyn=>rwoW8 zJBOK{8V&)CC{ELR`VR81sSaGa#qPy7J2%4q_`&+WxeR~j=`K|ZMXYaUFf1|gS|v>s z2~*Xw2E1Tvpjr)xe0pLT2<0(smb&u7eNTsHQ~$)PH<`j z4O2Y(2+L%DRJ#27@q93c470D5!ldg2HrWu;{EAa~0nK_KH=RjpFyl-uf=wO1_TnY> zPKBM~NyCf)UH9~^WCGp5TBP26l7?HvhMQG|5NBLukwuZy6wBcH#v{9M0HR}6NGXHX z=`m0M!VN$j7|Naq|g!q{~`EC7}bPMO9jy+hmlVm&@j? zuT)D>0yR|tl+deRbX1^I+FPh3=9)^F8m`Z9%|G^jCXH&ov5Knkuo39OrAnDMxJozNH?L-IX; zwIJpT9ta{`X8z=dUli#c289}l1CAD7RS@+ueuQ4QvD9?2*>OAxcRf!0v-k?|+P7r! zN)fz=paoZ=IIv=EUnPs;oPG)C^8c{+j?tB{+16;qHY&DVF)Oxh+qP}nw(X>1+qUhb zl2pFb`}R5gcHcYhINjs^`cD7aW9*-M&u2Yr%{iYni#O24e8y6XkZe&=6_ET$Z`A!o zVuiO5XPo1RPQ#c*dhd;S`lGB!H}Aj1+rd0TOm78X#H4SDqb;CZ>y*52TgI_hy^;D| zjB-23IGG<}ueyJ|a;))b^nY8P@jFW?YymfvYVRGHN8n4(YlF=tG63xblOe^Z2SWeW zT@nJ+!Bc8R`m>Z+uT-dCE+yX7RlDkt>jbU<0avf*PG^KcPX%qKR=f!)fc<-h3CJdL#^JFzcGiE-5JB-iXd_(y=DUbe$qyXI0*{DWw2LTP`wI69 z%{PT*Wmckl>?P?tdYc}Uoo5S1%{1lwP6#P!vnY}$PLhK9K&T=s0!nCbGX}n45`3GE zhoUmIAAQ+AXQt?x%#`{^6_%>Vh9yK@HJVRMgSC@T@9Qq#H)4iZ>Ka)n(@kEyb`0fL zlcv;LdJ%Ta%=b6eC`S9b=eLiRhdf4_NHi>9NFsi5#lwSRHwbC=!nWl(3p&PxJuRx@ zx+4b15fA6s5OATx#@%E7<0ZtJEWUa9`2)s$UPS-ak0@bk|F`wUnST@T&oneRtT5!j zWiDm#AP~z5@Nv`>`4oM{P!*6BJFLb>S4NYEzXE$J3PAaRefNpx8(+05FXEi$beTGL zIm}31+4y6Ey#@xtgjsKp8KO>4S|hnkYcbYK)wQCJB19ie$lbdPjrKcAg={IGwMgt) zmGvU3DVV0Exg1>M+{^}gNBP|Fux0?<}<@A$w2PVHt2dMHcS9tl#%vF|Ev0>9dK zrPC)IOxHDC6$I7L1mRoV@5r+f+zL{8ohsaa`ka$Gyh%HtlpSxJJE0baW8Ap`?s` zN3fS<=e_9RpBj6PEX#@?pJx&N6H2WAQZFMXqwC;cX!p0r*pX4SR-YM}0>+=4WQSsB z=j677;!Vh7V+cTq{i3Ycl$v5agE%;FlL>HRqWNk!S|(5#Tj?Uw&x-9Ihcs6J~Z(0sVvdVdFaQ7 zGdn6SEINB3Fsz%gA$e9gbl0Ii7zZ1Mu&OnH%iCGXNKk)>Svh z_6>FU0QLQ#&ErJ*RDgme;Qa~$YJmHFLi+Xr{Nq#U!hO_~HB2_t)R{~)W5m>D5ttx^ zY?sUyl+-O7FV!m5DjR7M30|gJuOf(eEc|=!dkHRTEKuwV~m7tZg8_20u zu&adAJrrF?;gvlUj?&Q&kmzEHVd9b+mV7scTk_10QY~{M9Px8VVmOgPtFRXaQtQHp zZMkfl8Ejk-_s$-MyGrqfltQa`_aR~%6vcYqwmppHriFhXyUc|_f30+P%H@p>=_eG5 zwXE$|@e?H!Yxx_eUk#sG_hcbs)-#E#;!rXlE3)|Mm9dW@XV_J zOw66NJroc5OKhMR%LcZ}kl!VZ<$FAA<+C}6_nr^b%ds(v$0UIIO|u1VxBRs$bL>|v zqRFkhJ-Cx+f+o7#WQ8|<3{Uq|7S8-#N>~pRZTV|QkS!jWueA3<`EINsISZivZY}KF zkplPNd_Q{~b8|K;mR<;DLAK7lWa~?bBXUwGfqBqW6qTFHbVqhqf9_!@QD;sfu)6Iz1>)ANx84_;Z*QTONTb0H#N^ zdDFd2*^0_KB{pO-B@l`bddsTvJh9 zTeI1Lnc6Zud2xXe&j5W{Pu4rfqpLYkrjY4*3AA%YMMb#b{*ArT+F{8({ed`L{k~Kf zctWE%;CsNuFAE*Z2Y;VZ21M^+R6wGQk}h(wRm)KIz*)qlyL~Sch7kocTUK9{hnev+ zxHw-FWMW&n&PhzGX_o%`9O-N|%?_*{HOUfH+g~|axaL}w!O1SlU=GX?F;keVo0yMx zs~(|VKb6IQJ|YnCeSfxTF%9QlMkJK0NjopaKYhsLlZ)JY40NVZ z5yQlPotEz9j=SZZeN_u1+%yRhG~4R~uZRYqU}7t(8Zj0X-sI7#@N}S42wGg!~1uGw?yca9oWw^{%8JWSZIReI{_PY>_JJ%fI?Y{XxvEr=SY< z%e~}%%AL8FZL6#koxO2P|Tnw%hEsA zLjVc|cu^)?N?Z8;EzM7wgoz_nA>TFaDI0%@s=CtQ%L2TTHLFA`i?LwY?hMAX|NYEt zU6fs+qhpCF1K&ifIX*s}nD#Y@AOEdM(Qa5CGKiu!)xs&~} zB_#wWiT8bKM5c9#sl`ZRB4k1&v2vK;$L5{BW!I}J^*9lW7PlwVvfd%>GMM6tfd}SM zi*_?R8zwP2FvGl|{pC9u^AGwykcfi=j&Qs-;uqn8eGU=6Lnm;P(IDUpzPYQYh{cjQdd+jm^wII5xhU|{$TFy-)(LVaQqDVg1?{+*UKgA z#_@bk$|;6Z1&^qaxC48iK?kQM-~M@jIQ-V}>_Xpp%Zw2f{M$Qwsd0>6+E-E&91C_s#hMWh(#EQfF>_zF%QAY2VRo*Tqy>wcW|?6d z7tilpi0Euq=PHmh>HGQ`PiHr8>|H58pd8a^bxvpG`tz`zOcYE);sQy6fuU^(B^krM za1B1zKjH!FE8n*RH4ozj#C8FZI=&yha9*ctYbA^x(9}`xoz^!vyy+Kz8Ntv_BieZ1p`M3_hHk%f>3+UZs$^i#xFh zLn&1+dCJ0Y|FDDk@WVB$fx8q&o8y*OxF&@82*ag(W`W`E=_Z8!cw$4)m9ZX~40{2R zbW`cm%ZS=m!`M$+!-i=E!ncm+u-ME~Y-4&u=AOH@Cuu_~)k3oo5z43QbQsB~Bk$P3 zP1t)SBSDP;z53Cf5Y%p~6xg>pQDFwt-Tw~xOTG{28KrR<1E-%x(ue1?!c8rZB%tO# z$D)d*PHJ<~;OS5uK}m8sATtCdj6@-}sHgs#Ly{HEoxN6+s8e|I49044`{)`<(uBa@ zPZ>^JkhSgmev&fzB~z|SO=yCGzT==aH!M}hLBviZQ3+6B2c6- z26&Q(bPWE!fnLXuRgtzd*099b=mX?xt~au$>wFC)kT)spo&}={QR7x6eh~7v*pwz7fXXpqI9pmvciA{3zaY zKS(n1_o<FF}o(Bzru@1iW3c^yV-A?)W z1w8|In>L>O&&F4@+k~zw9N|F7I;9&j7^}bwjP;)AHC>j6EYY7Z%qQun{drJ}~ z5mtup6v`_1#K_fxrrP*>D1bnTvR_d38D(h(DfF=1kMt7|5K(bpZBK@TCU>57%XVQJ z!qBQiA@m4D%cg`0BCfn!J&ATES}2D_Z|p(&4q~_5Wsh8sFE|7J=M`B(Dd1@v!^6x~ z?d}`HFz@qLcg;@gOKMoW{A=x}_ZHt%kTYd(D+6Tv3#{tu;)b`g^CB^2*edh${GdRT zqm_tHkbIAd-gxBpANwkV%Lr`AQE|H-&K_P7Y>zBWbBTloSBkx6s3M;U@G$7L5FAT+ zN?)##3pp7dj9yFM)>u9URTI|ll!M8}U&o~kb;_Ou!n)NrxP0Qh3S<{u2_Nc22|e$w zYIs|ZS8&rX3`!Z96hQzh66PD|!p^l4(VuoV!Hum0WCeqpBlgo@`*x3-TqIcqRx`Ff zlMS`5y(+g-XWwH%5#4yA>(0*&colmlh>{x5`MIoM7nN{Vn4udBWlzqowKgh`A4TNH?)Us^_i$Ia&DPyH~%>({)W zeqC+e&ExEtlIsTw{*n$RCs&vjK6`jGKTNKG3TC#OBb6Q#uXSymr%<(X<|98g9P9k$ z9h6z5)uki*hHyt?qXTzgr;&UVNlS70ktwp}8)v24@%|1|*qs~fJ-e;m`J6^kh6At~ zB7)AP)nBh!9V1esTB~DUGSV&d3zf$F+Y^4!mN`t{wwcTqJQxj)__=WqmL6E1XYvV< zb!g$9$%2QIhF_|)Zupg@bA=DocAZce8uhd5BbK39rFMyX*d$(H+KKS}nhosXI#i6X z^%At#=OC1`S)9^6S~U>E-2fJgn(Dxp=WauF;moSrR&IyBky*I@px*wx*AVz`XhFTk z(?Zr84Z=U*O@d)DAgIp+-M{QtAYB&nU*)#~QGxO3Y#mOLPnxa3=pi zsp}+8jTnxRo0u2}UgvkP2IgThbN_L%^0A++SQ`+kk(n_Y%dkKhlgz`p(rlJ=HQ07n zR{n1Vp7t#N=;$Ok+Y} zZE`{R>41uKoZ?){i95ff(uvNB9{{)zb>^jSl0&#<2N_GatwF4d=}U$-(Dh9^`ApP} zrKqJBC8xxr^H=%i7QH7z$M!vR*oGgk>Yc2vJR~))!ABcS_4vjFw$gn85jgclsW+9TfRU&?aSw^xCt={wih3ddx4oc+j%jjv$*d z+Y|3}>k-t}qOKj5qxrG>eRktd2@bs>LiGKyk*H2HvKMr1K#&|cCCSyhwW5Ow**VoHlDo}s;{tL)9Ynf_E@y@#t~@a6E{@SMi(N zKHVueFSY0Z%B~rEt2%EDZ{|8#Lu|P)Bv$^=BfUgL^ZFy!{$Vd!9%m!{V+Q6(m8Ys} zhLhx$HW9gJV&t(1@}%VZRyHcTy&8~5JJN7JOKU%$M_;csAwUW!@2}?kogAp z-%5HSr+w0q5bTj7eBp@mkwpA)M~+qaV~+%%%aJE2^mK#g*B}yt*h4B?&>}RmQwAm6 zxOl)Mi7r22voY2QkIYoO5|jP{3mt)E7?$#qX26UHXiVVJ#!HO|L_ywk8jkR6 zC0}k~jnw>L?90bI2hmIzyPzNK#?J;L+d;U^2V-S3V1L$Iyv98HA_f5z5^c<(OEXgE z)MXe{=r{0^skGia;K!IggkL|KWo{XWmAs)k^I%v2{^r>8C_*zabN?2U|9^z;O z4JKEHxclLVT=FWwQ2N?)r#-s0Vk9=UO|O;-vO>0}zz`Mb*f!83`BGni!8s+-a2GS{ zOe?+Zv?v#xU1`z{c1;=mM4{S2|BjrcUEHPI1?P!RJY^$3ib2k%kaF7Cj+nD)D?0ID z1GbW~Q5*dgVD`W$!3c$;VXCaDieVGwt1TL`wDQXRNMh&LG^(2M#EVhJaU0SEg(644 zEIc=u8Rh^{skjizO6qh{5a_0BtZ77eeBe3*>VzNT_Qg6+ez0q7D#;4wSSTW|JO{ZN?6yrd>@N zkWCouWq|5ku4~XusH;8CYnFB4V(tJO4>ZzkLRJ{uf!1fV$-W`kA?|HZ4}_`yuN)A^c#Y?T$cpKbx2v=@l?b;#W1*&AR zSR;=S8O4WM6Yjnjsj2n3+Hk53htiW-Nb^%Fx}_pd4d!J&8pf_g#XxYRyI@8_+F{>( z?+|TV__~h=b=|I@>ll*{+q3p9^9(O;l3Ky`B4*%6eTr`?)}3h*aXA%Ab`JabSB-we zU?79{I9?BqZ$lF2t_qTv#a&_}>&qW$sVg*~7kCCT1UzMvVFg3qQyqPf8MeP*3E#cT zID9dpV6B1DqvPq(Xbp72R1f{-I~ehyhf@iLJ_$#}2s10k_k|18nHPUK?mItcBjFQX z^Ny?#D{6a4xWg{X)}Ds27RSWiy*ygV<%l)NKpRp;lAqFa1YQRca zw@!L$RmtkG+zlmKnHnRZ+w+0p6&{~D2Ho!03ewDh^E20ftPzDObYZ%gd>K@HHg zNI(ad_lva1M)V8Mhg`A4z|5~#b10dCpe;9L}=o7kUNqJb!__wH;13m zQU0lZ`H0vO><1e_$k|W^&J)}gY5i5C==4DOT$6K=4X3*NnWV``tp!a)6q&ABT9LWa z_>vX)w{LVR6~VE2s&NJAXtg3{x8GH}L#XV$sfu*WGRBxFmeT4+Vy3PW)?vO!ry7cV za-4f3_wCMosW-Ie(l=G=UTkA~b(=88Td~Jv21i6qK8hLA37TIB`vd7TH3U{H%;na& zFvX`y1R2$}f^#x9%of?}cVJERIrqd7s3#`)og zbX*~Iw5_)-$$f|9rku&HS2}&I#UMDp6R#LQfNmxtx&ST=fnFOMY1@ePG-^O)&mD6% zV^6kcr{c|}$kOc_z_t)?IBChnm~TL-Q|VZ+d8ePBp6tgxGru^}YT)7hx)?_k=Y3kU z!bR&-<#C7;y9$)B<=zhk!_(#j&L1F@Aw2W zcq4*K+9gV5xYNb|>>-w00_u#sKxq1XUsfojq@Ep!dunW#xrl)q%8S3~)^ajUE&Iw# zeAMrK^Pt!%m53C+|2j3_52MMF2Y#jve(SQJGhYo- z6Z7GZr`4TTXFgRh4bG{0YYdR8w~LxBdAhF6XAcO^s)Ef+xvKdm#bs&@@1xb`K7ShZ zD*7%trtsHMMm}3~l&UD^77yCDFTQ3=4tLD^z*!?}F|mml3l(aq;*O@+*ZlSp$&HLl z1su68^n|U5oqmMAtz#AZ3bB4sv4X`bv zDps;&&yzV?T@mh!&*D+Tqu4|h_S@_&2oCi%n$SQeFW{+T^$9Ln#W=4ZqoTCEd@iA* zJ;|jAo`HB;gefc#r;aV5koiM9)GmmdMH}9ELibY%ZdwKBHvoq{cL~JY2^8%K3ARWN z4v>>R(7kkE#&N=?22JJr*wXB}ALuyZGYpZN7DE&nf{auJAp%46D*UNw8N@FOVa_O%i!>_u3Y@SYw6y z?_Ow)xB|LgA-0klvcEwmpwig*de8)zn{YLwlS-q1t}ZBHWoPGn z&BuBQ8kRW`zq5I|+}Nl$mcD#{KYFG8B0emPr}rHa1`06_5j9_?wm%t+^HyfCCgOn1 z64&;lHlihh)ZSZN_B8@D5#bIuc0fM_B>`|le#JyIE1+z9dLdZpF>Y8R>=-~u1cjdwW2{13 z`n8=@vj=Doa`mwGh+4CHqeirS6{1Olf;C&)&FntX;|J7)y$(qJn-B>KC)mUIBXE@t zA}z9nu4E6)I?;nl{?2^#%Zw&SA6o935f;OSBK#9pFhx2f;(DkftdQ(R+1gUUN(B~1 zo47oc_CkYBt&x4<8q={rVVgiRcPdRa^=!V}iD%HF5SyZp#dWQ1#}*C2eOE_?^r*ss9skeAbC z8U>n+#jK&Cl))~Mc(Fwh^Ry)z(Y#(|?{@%5UpzJ*h z^KRWYhW)see@}A;NURuMil$JZGj!-1kDL^IC{-!)^~=;wmvQ}H z4aj-zhJuqAJZo#8a`5-hcV7Qvdz-zXjkd0hjh(fNwyu>cjf0DWp`3VhIvsr19i>o) zlQOviR}*PkFsh?qS;_^4Fc^srip3JM;oTJnP=O!?A0IQrmN|)M*fxZgLg1=COb?F< z0zD<=PjGvfdczQWr>#GM}O1CKjug3`+nK1oSq`n_iq z?Xi;B#bux^6*vcs5DRHxx|00>P55 z;X-Zi7>@?YgpENleop$@4ufYX#QAK6&4HI1cGvHQap{?SiRdE#d`_)&T1HV$-hyL<~{eNm9_zPb2f7d9h-;jw^(zM!RhWA<`2Q`!J4`-zL*}{w4U^|PJ zzRG*NVOeJaB$h{QL@Y;Kx-7bokHRHaJ&it6SITIvUW0FQm~Q>61V7js&;>}D9$_>L zIRi8qB19}}JN%?o-HvdZPnqIC$_zvpp&jiDO&~xeunABgv!4oLWk#&+flN|!#E-8+ zA$(-U(qB^GVcrecF|#xKG!6mfOw|eRyAo&!36e#j#4WpiV3oS0iP=rq$3TJzVum0< zS;>{69yfc?+r_d?ixWpo7`XB=GK>ajI@B&FHQ$7}Zl0QD1|{FL!4F*{xDJm+lXAl^ zAn5isGGk@*BB_0SgGg|aQQeUZS0oS%&ehWpOck#}+%asr@qK8~j6Yts?Y|>InzqLU zUC+f|VU|ABcyVIYph+g}fOru|AE+6#aS3$Hthu{ds~I`<9}R3&spO_Gtr@&Qx<>31 zbBX9V^bp0iXq$Jy?Nh;^YF5MY*KE!K+ zr76WdzAf)yk0fcdXAe5}d5cR4?X9@5_bh-2#H+h-XGKKu>P$>Wt0V@||DYpYkS~z5 zZ||HBgy^yp)^AT!}sPL z#KfG6>E8B0x#rVueiNa!%houYVw&RMkFCdU%YtNbJgXRL9=O9vro9SXD+unRy8ufX zoy=RPRdy^mp*)ouf3?%HZoM}zqq|Ub;G5~Q68%}b${u+xM6TUD!-W~eoT&Mvai=7a zCaiVm7SIXVcwJDJcvESCEVmN`zv}c(y@MfyEHo+VWdKIKYoQ;YA{Q(gGFLPz|KmdA zhSS>*jdZ>H<4w|CpmBN1_64&G647u?=CmTKS&AwXiidsO0JZq-3*y_Y{)}fMD>M#q z@Q#@awDC{hAx0gQk;De{H{dJBpgj4OeQg8ZGblH(9kV@3bGd+-=bwneL;J>{GZSnA zOG_+s+C$v}^>^j!IU|q5_V-Z>uGi_TyN&y3CrdcJukYBpkuwI}vFDg0TsGd_k!6Fz zja#A%&2bAfoaio?`e3!J@WpWbieS4gjyfJ-yZ5@R4(C&tKiSi!`nDA$WX`P7Mx5F1 zo5R*2?q55*=e+U_8yOOVFH)9qPw`_%fo$FU*HCoQN4>BB;cmsVzi+}WJNPw&fO>!L zbG8ZoxW7cE>RQp}X`XweOLCz_W*scB>m@?Z`sG)}%FBYyoz8c_IGJS|R;O&R_|>JQ zD zK<2kBnCkLFpPSVMh4xmi9YM@9<7=IqvzQ=-7y(Q|#3f`j8bOPG#H%Wr5x?t=AmSpz zyFH_qWt@w2pV14U=)Ud0tRVSa!o(?nCVxL|ruw|{{+$wLWM^olZ}R7@H({0WdFx%t zM?5A7sF%1|NHeY|$`HgX7R=yu()lRXXs#t(KW6$C3Q~MEa6f!a55;x5{!(2ga8c{0 zgVz8ann?T}P})a^-8%x^7RQSY;~dJm0far{I|Dw*YmG%OX6j&b$`0IihAI#f$$u^j zh6@1RkN$lu{Qd^K@f4ooAelrDSJ4EWtYNgzWL>%hLQE-9&uqna)?tR04~uzLQH-VE zoU}NN*$178eTxF~n?#RPzv;s6*Y(sy53O;V`dV8LsNFBz;K8b00@>)NQRa`$OYiS2 zDJAX6NG3=+*Dbz-(TJ9Kr3K~9nQ?9Xk@ekJ2}1{=D}JOA=Vz)OD?AlFr9mg%9R3QS@C>c+?{l2Z{YocKZ;M@@d7= z^g_b6XNO;YF?$8AM4f1xl?&Z=QRl1|1bGtRp}0S1g(zU5?Q>CQ-8BPl#^14xgx!e(4O{|6@j zm%{`q$540UZw>VS*qZy7hRD=G*TPiykB-PtZX)V;6MaN3bYke7$hV;Hp4T24)I)zQveqamy4l4*)F5);XEEjB&2ZBPiL;$5HjhII4$2oEqoR0A%i@TZ$iJM* zt4t9yW0Sn!?!HaPPKVLMdK}5mOCAXuCB?tpei;T{W*fqFx=rYVW$QQfDEPA_FpWJR z?5#0$MHsU3OJEXb#Nc~CTCRd(tV}tq$eo}!%5IHp6~P8wbtkLY>KCKWpdfK(#1y`a z{dU#x?p>*h^E;EE^vJ+Hb$Cn{LoD4FEKkG|j#AYvt)(aJo!*VkHlN>JG?WQvvI&s5 zp=RfW$O+=t$zOIhI765rY#;*tvmCOF=r_{Wq*pv1vrYgG@K)bY6!V z^&YP`6zXkS4#AuDYTclA88;_@qjeQ>nJN~=LaEz~g{D8L^c}F8t(dp1_r9har2Hym zHaEB$1DupJRUu_7c){o*c1^Pe-lwMSy3&Sp5r+*XqMWsaeFfsYtS@d5c|y;2iZKv= zjy00TM@P9!H-Ld;zS^z2*@V$^tcXBW1s&9&deXc zft<$?{{)Udyt?8wErTLj0{?QlR`PG)d^LXkZ{S3H0w)4CYSMGN-H8uak)t2h@mk#n z+BQHM&h@s%KTItk?rpyR94Ju8hmRizbkB=#K&FgJv_uiA;3Y3TfVCo#QgmtDygKTX z#0Q;`eJf@IQi4XR?p-oG+eTRF(Utz)LVw2vRu8B;f{-e^IUD#?%53rO?B_2^jn;N~ zhUXtLC;>a+4 zHH!KBm9~x!_Gn!8Ri;g^tRqQPUpMleG8U_|u#7#r{OYL6WsFhR%6LgwrA#xvkD*}= zRi42-yS@R|W6gEXTVbrc*7?}WV=UT&iZ%sV+a?)c0=SCbUIiy(ZSL-Ule#lV?%4s4 z4!$QJiRT|J=ITFz@FF^y1C3$O$LUZZg;gOD%~FZ#>%5qi{1Z5S6co#8Rql#}^M3=5 z;~&6D0-Tc**46kEI7*+u32&lB{}VU|opvSRMh|~k;rGwT`FHA#y^XGwsr`SoFaLqf z{}R}wC!J^g-q`Z(v*0lQD+R~UMOWY9?=;VUH@vLGGpJ=aYe2o_At zp=P|tKWHBDx76Ir7LvC2$jk1Y#5OQj5KF)a9G2;4*<2W+Lu+0K zD0jHHGG{x+VFAOCI8k5Zm5c5|?2#kf#;QnLB zTR8Pg?cLO2@bD=VX=UUDD*tiGDq)_q8(g-|EMc!w;y<2(BG!A5KvVUF zcqxYntT0zDjYF)gV1a;5r6Dm3yka^-SwoEhG$*6^*`+bc$bMLG1RrUTL-$^2ITL6l zlWv-4sw`F`J+ov8w%#dmS}-~MW}TU0 z@udJZH-dK{7h&nz2kYy>+;cv^W*Y}lMqD}Vb=W3~J4h)N7Bp_oi}vW_#)H63Q5-rhi@ko8C12^-!iwJ z8#0%KlGf4A7rXtZm-qHjR3c3asd@~=7h52jxGOiDJkDLl9dsXG63&hV>hMb*)zH*t&UhM=s4|^q%4vl|R#{374ZS02bXKy@l+y zn{O{OMD%H!T~PdB3_K&9THSGKpD)~I9x#=^Q`jdMm^HA~0SV^AZcVb1HLIf3UDtpi zNnw*9r_fysq`x||^CiijG4Pu?&0K48U?kuG1m1#rGKIrcT7KYY@s-5#swHX*s}*1; z2<`sGv88U7byH13s=qPE7synw5s7i5BKRyFh%bXzy6{x z{l2$jBbNKzKM$AUCwclmUaIi-!}XU__4$DBlXUPrq7<^ijWelN@U+MtHbj{RFK~$` z3tb&fQLO=FZj@Y}=oWl0_{BFw)vj6!4ly{M!FDtbpDkbmxFnY6q~gc2Z5~j76o<23 z@rFm$+k4-Jtc4CE455t$6ll_80<53QgbEbKxT>yrpEa3AO%O6l&{Wcy4rJ4h@O>=# z+AAQXAEIF?pZSc>ti!Zbs7gfubz)VCb#rZ58CME# zzGLI*A%GEj!u3E@_}Xrirs$FX2`M^Q7%8EVjWBEO*wRb#7e{2f<@-cz?_e0kW<8+K z$9LE`JbP+-N1+rqX6-@{)HggUU0^RabiOIQP@`Y-5$VeN7~!)Z`7c}+Hl4S2UC@GB z(a5JFD|a({pVK@ho)1ovoJ!gjVAzGN-Y*l0qr+iBqD(|(;a7y9b^Z1d22YS8d*U|Q z(tOhgadlva?srzB2u-;q@U{X#lWauT{ObdXJy3Zr=MEIT+5=wq4@pLnPKblwKqv3c z2{embg=PTb&H=m%RtBJBIP<@^Tw2KRFe+XnjoG%eYp%+Irz7eu6h%$>J*< zqEy0$&*oYtLfJbC)IifknkC|X9euB+WDLtcj-RbN5$_$mL|@|jXS_^`R8+7HwOWe^9BbxO;CEKIops0mbY0vu2w}{8wiL9VEycfq z=zpZq*65%==iZQvRf76p?B@6-l9HKT00L*hsIZCUehbm~{Z`l4Sf@Jl6xtx7+gHN5 zU=+?Dl!_*Ri}2L_sQC76yV-R|tY*#8;#3nT-fjJ?@dqxVqyv+@qzxYz*PKgZsv zKgQm{_AY zHfghro=M-_qShEO!Iy5J#_c3N4YRRU97~|6 zGfd=s@@T(=tPNzberEZNGu8Z1(_ud)mx4n1^+YUiVwPAmeyFA?|IZW4U-{O511JCC z(f&iX{?-itznjm0q|yGv%KvX*<+=X#8Rh@MoBanV{~M6?rhjkb;{gqxm}}L35Lw-04w=QA{2qZohaw% zsoO@<$rnbne3Bk3j47EwsU^Ikz=inA7}(+^1q*tbYQ1$q2rhXD_6ds;UjX~lylOQj zs;lr((fNoZkOWpJb>)Q(Lu^`;`%y z#fRzbnkZGp={U{nVQjKfF1(bCHhq#FmV`9hSuPq$+N9mAWei7stLjd_Fwe=^kKAKM z`AltiBHQ7i#N|YlwG8~m;K33d*-CJusYBJ&rG9l(0V{Ai09lbw4=i$*HV%MoRhOCd znvWnAB7LajdW(B%0xw-*tk9g+k58kWE^ZZ0&+k3pxbW8Tb&=GfSFZz~kuS`Vq9+zP zjqL+UaCiy-L3-p1m3bcXc@9xT-2=gUh`8OZnyvzjBiG>mRu=5OUYHNuOg_mB5=4NCpGXoP zM+zRgn)gY2K+d<)SycrA3q_G2wbnfFrQ)91g?dc66B?u>BG~HR1aRQ2lA7fzwUegV zR23Z6nuDIZ{CF}&T5p?V9Hj#ZItO|nT5G=qRVlEt<`1hYf@;@uL_^tO4%3p;SbTi~ zW19c5v=u*_W-ZmgkAe3l1lHTW~&pfz6#(r`! z_`=oS?L5`F1+T~$EA`FQgQMZNY3er1%GRQ(pBu)?y}sS4xV-`A!AeV08N>zFkW6}Ua_%6-HCh-*A<4cy)q49DV zb-&s{j=0+-c@>iqPCNFa9hMx&$MMF)Xv?}w9@3);OQCG3GKcKyMcvpY&B#$h&{kYppG?5bH;J+?<1W19h0X!tfVXX_3 zlC>a{T+nO&e8Ooz4{0W<(w=H8|1hFU(5$~>@z2ENhuQUUczc`ciT`VQ{cr9CE=Ciu z=JSjnL;MT-sDE=WzlqM@0-L9jr1hE*d?(bjH`;RooT7z?g>)HpA0JF9T_WiBsuF}& z*v7F-8vZkF#&;SuWKlInQyu4@nf!;2lbc8OJuN9Vkfv$ zLliKBPcF&00O}9n_2x{CiVCWUVe8dcWe=HEo;BnhF`VTqoX}T^NlKY2l%}zZ2@5f_ z9MGW{*CXf@D78Wg4sWKsURYlH*h)WIVoa zIbSa<@iL;JXLjP*lsmo(Dk?q*)@&@1L^3N18P9Lna(Wck;_Tv-VO16udCH0LcV<=c zAn#KnFxX{;kK!7#R?>kQ6w=Q-CO-aPwug6EJ#yUWh?G6$aR+LYCSyhGrwfDI_UGjc zbLTynF>zyeWepVezq{*kVaFJxV++n7d3IsMmK+R_(xL0}ffL@_!@+BeLflc*^?)%@ z_}R4E@%>;z#Zuxq^=270WjIixggr5dKv3iXiE%Vq!qJacSpF`|88$a)*ttt+%)TKRQ#26FrhV@8HNBNe>;$wCM^UE-rbvVo{e32 z#6-zRZsDaJ8=062x{L_OSPXI&x>yWw+;NKOubxqbz$Rhl@V9fB&c`;rGn6~%6uIVnt6in>%BNs; zmhqMjL*6h+`zGkiZ*I}#=s=ay_Ey0U8Z)iLsdNOn`SlOCKb;Rf-t!bUt9l;p9ucA3 zo3;`kqFHRJ)8+Ss1;o3%1b;Pe^l5!KwvTVI7`*FqI7vKhHSfO#AG}>Xc2-(t?~dyT zc5it=s=l{x-W`(nWq*5P>(i!tBkt4w_Quq=MfXPCxApCftB;%REn?>gx?AU&G|1aZ ze=8^1qK`7jTle{aW)uCkPraJKKRM<4%mS08V9gQ4*deg=EV%C@dI}%OVT!%&G{A9Z zAb0aYKsn{rq_(ymzV!jbR0@xjh*T+qjIFlx=H}6J7dJs6*~g zhx0K!VmXBuN)bf7-KWF(R1Jh4<1%j72F~M8hqJ#e*>8vQj4Sk8<)`#z_J{O^&Ewnh z!`LZimWy8$6VE)~`%8Q#53^Xbhn7%Y!W+%tz)f9_%Jy)}GN&ABu%vVU)H{QzaKhBE{G zEqy8fA$?ILI>I!O7XIOI?s3hi!aKx5o+=-JNHO99rMS*Jr;+~Z_l_|%$9OSM;A*Ua zxrH@YonfwY&aG)MwwQi7<1Tf!Bof8&eJY1)tZY7Y;L$7&G&w2hQFU;i)oR14NBit+ zilgM8B-I(fr#EAn2&7;Dw2M?w%>v}p?3I1jjJO%U_ibdaX0*};d1 zqencA%MyT%;jjnptRG;EXawo3aD7d)P4qLL*Vwu?FNmV4Xic^eyzgzo?qpeLf7a|1Z@vQ+dN`jTyOXNglqRZdno)uKm1fosl;Z#0jC58iNqX zBsB?rtbjt!$>Lad@j=hh7k0T#%VIfG+afqYKrSp^m*H0b7R&31gPT40lnZj2(4mK5 zlsFTgI7UroURo6RgYTO%W}K`8joo*`Fo-Dj_v?;u9Z?1%RzU)O%2?S*M2ISS<_VN@ zVqyXUTcMLMJ7Q^gNOGp@z8M!Ax1d9+T$4cqSMUn!6g3mPMjb>;fhGZaAA)-Hxgt?N zwBSfZQKJ0+L)trqXTmmH!X4YTZQEwYwrzLNNuJmp+qRPqx|1iiZQJIbcfLK>e0%>V zd(Rx+=T)m})vCJ|%dkcCpb^))1-$_^IcJuIOV11%0^33k;E)4Ldk432>(Dkq)4_{S zGmu-72}DZw`C?BOVSbvo?bpu5qZM4Krdd>*5_Rfaa7inQ0)`TVK_$B}Kx*!GY~`m{ zXN_>58p;&a02=v0H!dkG;z{E{qY|y(+NnKfCYlL#ZaDqLJmCW)aSib5Eb~{3;C&F4 znr!$&W|lef?lEQpx|M_(tQlIvG!DBq>oOE>CVcPst|@utoh`PI0;3Iw)YZD?&o-1; zCn51*)pZPzXXGnv=jmR;K)5lg%Zp?(yp>@1c@7uO_K%=KS zY~^sArTkXw5`k|S@`H&J@-C-36*Nb()3)g|;p5uiHP_r!EE4rvv(cxbGS)37Du?Qz zJ?Wt`OOZ6HN;frzD@shzJ_6`D0s#wue0jP3q9K8^)BCs6<^k|`xl!`_&WeG5QE;gX zW)U`flbk5AvI|u~e*ip!}A0bfprO>lHO2d+;-d##z`hbBd(rcX|E~xUiDpFFZ1YjX@aS4sg{>Nu#6l)kO zSFH1()FMRLaGA9trtz(I;;W&ZU`0^;QYmS1f70JS8cg@TGa#{;3Z`U ztRjeVy8tBXAe)W?-Vq*cX^owBVY^47hs#HzODo2G{a;cFDxZug(5F{AO=FTTX zH%%lhPw0ghAsgQqa;aw@Wr|$dEXdVp(!djj(6G~(pWH2YQL^j7ElPeyfT-3~%6*G| zJ3^(7Ie%ZxIv0v~w}g%LT{GBhT~1)U>HzEJur0r_!=$3isUCmU-xe}ZAMn@!BZZ?t zT9m;6rO5wnH1*iKk$miWrEdt*dxw2)WTM?R?1s50X~`G1O!+OXF}tVph5d%I)c=>@?^@(QzoB=755RU{Mx(V~ts@iB>O;1t!~H#|vu~xmlBqHcV4myNJ<& zyOL!!-ck2@`g4Z+eQ7StY^(^FLGbUJUe|B*Q?u><)V#Ah=8cb40^7g6X6(3-Yf=j}EwS<= zXeinh>X_>RsIYZIry9~}d7&387Bq%O^aN971zDotHH@tUKRKB3UP0xCN4o5B%!2W45gS?E>=z3`pBuc?a99Y3(0#xT+20S9^ zmK-b@R=Yise|Bq@5H&}AcrlB=!+AA&yKvSxX!XJUc&1*Z@)qD>ud8ldvPR{JXwja21&s#shR|&!~}@qtCc|J zV(yY^z^Fhs1w`*+AI97JvR8xuv@W$m0A1kX6Wa7;>lo!i`{zTCaIz`1MsN(unmqX3 z4qpb~4#0;%3OIYMh4Op2F2% z=6!a%x_)-aBa5alil}uZaUO_srtl=K@c+$GiC|9B(;+9uz?sPi?~zVZst7-^kRmHw z3Sz{_;7?f&nzd^~`)t_%wdh@0F?X2Fn8682z$H4i9eU%GoQoyoTtV~7JzmcCiV=&R z9Z5!QpFiXrkDCiNMz2g^V4WhJ4~p-$Fi$LSkwNZb3Gp0Nfj0G;j|VLmh_CzJzhLo) zKGj^CQCz}1U!lp4PtPEshVFA`FoRR7OB3dRr~F<^S`7CxmYB5wBb*>XL3|jkszgm8 z*D5J=Ph%SY#^No@g1f|^Ifd+A(2aN<06KWzb4&0velHL(A>+_BetG(c*XCYZyFNeA zab@g>;J8@nxiO$y3#}j2@jF>B|pN>V}ww{}2-Cneh9+xIY6%iawHA0zC$jW6TeL(Ki_x97LxxBYGy zxEE&8gp)0`Y*G$WU7`^YB79=qz~=if+BzMImRNn7Un{Nv`Cl(TgZuJr{!`J%RdbVvjJinQ84^!9kb*~Ey zc2Cu4_H}nFDe5z&B~>en#>=Vh0Jw@T+5!EtlVdghlPILePo6gkS(Yye^&}U+e;415 zq05_K1Uelt{UDZa{nnDt>=kadYDhKgNz(redHvJzKQDk)uU&FheC?<-{!ffQ{+W~f z3k&{DsZCn_G73fh+WbN%tHk;_({^tDBNt9Sc}SDb!&myL8y5Xd%53U2J^I=+L9vGAI@#*h51+G?JA zBj0HkDKDcNRqZkBv(0YD<&FbNpmK1+zo(yr&lO!jju{d6uPn}`y>(xvnlBq`xJVuM z8G0OD?&!U4Kb*^QN}P`-VL>RK9?M&0U3($YOKGFv6)q(uH;HsmboeDH+u*m#uPD(k ze78Qa)lsl7NBL39x3!iAUW+G+>~mFz2Wg!7D>`Vqu;&B$>-%b#Ljbm281qu_xJUpU zHe|4%3exX)u_c%nUHdI=wkqIv7X^Ymd6C<_di{%=loZq>mGG!6DHBpsI|WL%uPvB4 zwi*fo*$H<_*cIDAP*d5&NO|lZO8w*AW$~Lf&w?7GrEd1hmHYbH2Kqi*>Ty?7SNA2` zy*P-7{CKlXdB<-jxoiXplTjHYf(?SD6LoTgmWYFZLqs&ZKanjxOxLrCBTkFD%z~{o zECPG}%Be?z%~dbBG1O-J#@ETfLqIKoAf@lFleCw&pX}n5J=@E=?EA#8%EPp)E@0AC zFfjVc6itXH36Pul)NgE@x|sFM6^7P(sqnV#lz3%{7QoZQ@a9nl^T~etQ5^#paO~#p zHu}^m)!Nnhx9a}CObR3;81t#ViXr!xJN5raGx}Eo{F?~vr`qGNB8=R1i$Rtvn)LOR z!_@^(U_E! z5h1RJwvGnsj}eN0h>bN}2~V^-jj2V*FQ@IWT|weK+@!rc_B~lwlZd1y9eUZQ`Y{$z zIrc-I)CKr&q7$|+$99Bku|_nki1o~jFqN9UCF1|m0ubbiOq}V<*hht>;rA5(ci=O4 z=#ZB~sLRwS&_;qxkCi7n`OcZ(JL+S+`fBXw$0!a$k=1Nr-6$nH2G@$ai#T=ys~7)- z5?CDCL8)j{BU~!o+V{alsVM)W1q_FK^4i+u5lup&HrK&#HvUFa8ogjM!bGEK{U}<% z+zbO&R6tgx@GJ4+5c1(GPAyt*jEgj##aP_>#|yB#7@4a#4?i3Xt1LspND z(()w)P~f9BZ|lqypz9s>PU#`QMo0XI5OCXFHdVKHyHF05!>5ZBDn>gXL3&L)Ebi)= zaE|gI5G{-4ZLXK>=tlAA()_)seEJbNV7A|~lqJ=NkTuq@ z4fMPg=|tZxYFFEvaREWq&hw#$FZShyecw&z(tP)ArSOmHB%L~6bj%7}hSKuX+zr=~ z`Q+>Cq37){)y+=?;#2RUMKKPv07Chm+_%n0M@d(zAdXt>6hn5aX&rBD;k7($luPI% z?@>sGRf=-Yp7St1Yg-1wK&yG)tA;qkhsuxg%2PB5?O;}M?_d}41`$-{(<)Il_A=o+ ze@^Ii@jGMPHI{I@U^_lSeu@HxV9xLgMA8ihqSclXo{lwl`e)zAXTL&;R9b zgW=0Oo#@woBm(TWZ~r54|6l3%pT+%b^`3wJmv_C=LG}`6N!Vb0-6WvOG>U;#MDgyB z4nohPkhP#sfR)j%-;O)x3^T!6n)P6kaQvvBP(r2PqETXJM*g#XH9m$n;q~V8t#(^f zTo0EpSluWUpAsVK%CHPomt}lgFPYUsgBk`XcjQ@~J( z6Ih&anC)){G~{h%yz)*M<}=AgM*0 z<~QfXWbJS3myB|=7E%9Y+lbyDZgrq0MVq<`XaoVY2t}0o(BN=Y`gyT&;;#uez zVIwUl;);v^{td4)yAeU!JfFQ&YMhA7KuaFub|gxk8Ry!-ToCfac|E=?#QbPdk0S3^ zHy(2o)^W|Nu8yv+5TT|zhY90;!pw17wUck)?}^?<$CIYx1-^7=Q>E%K6{|U~dJqjG z9TZ)o2As;W;2OD_*EGpuWruXaVC$9d?C+fJ70o;*=YCLgF*4AUB=W#-ij>>z{FJ{Q zXP;L`Jzc$o+J9$^9n3Z&GuCe1xjb0#<{C^OX9LYV5QG#h9b*FvEwICcFf+c^k|Ble`VEAaIc& zdu(uol>!LCu;Xa3vC8qml|4}*ga2Y9A@r1e_>cDM+pc_WETz%vn%dfKHR@ucbT+qh z>76Uafy&2~VEm4XOKxbqP${ z(XmE1SL++jGnTl!*oaEUl{(uCIAS@kusR6ry0-R8R`xxUSf4&;c_0@RV+{_+L$YHC z@TpF8I-Eo4Y#6m8eHv+T%S9Oo*+h{bAbqIZIEE3`tn?awX+EsHz=NEA*b0G!-uzZ8IP-R(ja~oY11*H# zTWUbs)g+apC+@V0bYJ8GhRzQMx1d5%!1s;NE8E*`YehfC{}ke4pFZ3`oh|P*{H?T;FP% zQaOScM=areq%Os|l*CuQ+-Lt$aFr62@;$EAfNw?Dtu-q6<*?7ktRrUjsPn-prtBm? z_U%5upE>;wG`;XgOWeObw9+F#&Q_a!3!U-Sn5wol@xF6a1#Ox>rN z@Zkk@ST*{0OIe-E;2gM1!g~tw;55QAO?_IV7zsCIQ*p~VD}+8)Kju~KzjdE_0Kc9R|A3Kqq%H=Sezu5Bh+8=x{QRSZ+ zpl%@PjNtVEon=wT;{L`t7SK3264v~xbkHZ!xx3MMA7_6MymZ*`B4N-1`)mFthuP;8LZb#-*uvQs|!tX zJb&BG9GS17n}Bi+i79ty`pc=2H5iXG;N|w9bg}t^7c(SD-j(%}ptno>ul{&go!Te8 znWc`<6CvWO&HZ6u3Ff9ccGZF_4P2G86L?NxRXh|m=l+iqqxIt7uhgc7M(yhSAhS)mRxkD^6&^F;?T-j` z4tgM&uXR^Aow=`Y0ixusQTIrdakg#QhK7@=r~M&FmA2f53_|n^QS+$N6l1BLSGy+9 z#-LvZQk?rpn5e;$Xv%X)D>8p%6eogbp!ZT@<*yAPtExH=n}XDDM2Jr3?G+3a<#*$v zW1}e0vRR)7vKk&p1X|>9Wj>RBw^|Yfty41Ak>vg!*aGv!qd7y)%zHR6l7uT5TDzU4 z8;fcKH8y3)z+$>-S{uM@bb(hcD-e@DI9D&A`W=3#@YvqlBHx)&%jQb00?L_CCd5}3HE4>au>%64yNJ-Dl1|DIR$1_OSmA;dp>?Wc=h|Co*N`aKnq_tvw`b z%aH*cJisxZ{7DmzqvwyV7V6tvVbHeCYqJzz=X0YJj}z(i>1qGCiU0lwk&pQ5EUx=0 zbOJW?W|P&dd-gnoM@O$8w_2K(Q2%*e?YgdF$6=76WM-!Xtn#9Bh5SYVzVdVdXo7Wu zh?%aLHXm_K-oKyf`RAgUBL$1x*cY4c|6=p3|E*O?F=G#N7bh!62lM|hmsML;L{moB zFJ&OW)@4*zI6!}kfnry#qSToSw+3Z8aN|zu@hF*)kNAk(+zO_IjUYELc&FYK zWE0c1@y?T(w7=|HF!+(n7VzQ!36d91fg!VpF;)>y{N>IA>rzaYrphc7UH}WRsLuN* zpENcT)`6z2dJ8d_lpeT&j2LXtlASS;xweG*+n5WtW&dQx;>g`$UZ<1t&b*r}>mbEG zVuw`BceqLZQc%o|Qq?$|A(0Izz1-Ayt5Haswb-dsny9wS>DARspn3rWrnQ>?h7U2V zM=iH$@`P7q+qdrq`f2*jyUJI#q&=){yKlSp2;maTw>j=2$;grErJlGin=>t0omTT%+;I;*7F2l&*DW1E8SrJsfbpN# zd{|1-BO_P}il(?Fn|1^A2-Np5KUbK0TZpO2FBYv4YNecCGdD&sHYkpnfSO9Pj1itX zkTVBOgHIU$GH%iY6FZQuQ*pvs#W3}{`Nh{s!oO|hP`O-{X(D!OS1nco(KRctNyd@U zRqjocA(~hPF{}fvRUF#^lhtDe3YW*ZA6Ix3ADuVrrX4E|O)Uq-WZSvPP(5TzrF;3n zjjz|?u2%q+2@Yg_S8XV|TY|DuAjrQaKPM!Wr88phfJ#}HGP6gTb`1B+Z-iEy=9VY? z8H>#dU=DL$p$Nj3-(nw^$E4`%kWxm;2x~Az89g`Ia>b**fMR(1h8Cy_O{{61MZs5? zl1r5d@Db~qq04@ad6FW3%&#hjd}O|3%X|%aVpNMDIG4Sl@hi$nXiH{{b;~4+foqe7 z&mig_DB~qozk|57Mnb0t!0s&TNl#~g=wP{ZA`lGjGrxbCckU!bh*NT$?V8=PpY8OO z5_T#OeTU;;^#ZmX><-VPSCg9MK9E_K!QG@Hpt$@M`ai||pWM(Lkey!o$_vpiror|< z%ng?RNRDI}H&qGr&`+7Xb_NkAaVN2U2)HHyW)dxQifn$ohFxAUI%sWet;`BL3=;?4&94%v8)N420$hFs@LZQHl>s*%W=krE3pJxi!<9_+(qQJL2 z?A2T3L1%AnLMmj%v3?nDZGkLN?qsKo-dPw+5;>q)d_I3aB7^tuC1VYrYT$k)WRMB37w;I=h-?E%Sv zRTxiX4HONu1>_6zHHo&&DH$!fwlF6qV*+D=ZNc7m02DM5Dj_MqyceJ@@QQkyrB||d z7eEF59W{rPU%@N%ihi4?_ZEN!oe9l^YCzg4za;M!az(q%(R&JD0B}J&qHd6ODtLum zF>VX=z5=QNNYF$tSIUGLbITmtrWCnlvoCXDlfF-Sd7fn^4{xL(ICh*X(ZXxe;-Dmh zu@t1c;4di@Xx4+$+#HhprZ9WaaTF#K;2@44b+~~B=EIT?4{!sh8tNs@Jq?z)2+nd8 zav74H19he+?W4=15d$-zfkrd2;L%COlGQ)c?d>OsvVGtEm2h5_n* zl|%KYC4AFUZvl)hTHjA6WT@C$hRHf7NW)rDT;LmvXQbQduD9U=JLB<`303J%H|@I< zXtRlE==ZpVy8ASz6Ke)dprK%QkDW22{kwR;S7)Y2pq=7NJ&mX?(dPaUKwgNkS$3W z;@&fzr1fbrn^YmE2y`i9S0dpm8`Fm03!k&&V#U`>Bsd70mA}{HR%yf&_8^-$p^vG} z(xmlF6&U@_&=qOt1&2t6hk;OsKc_XV{^im9s?+@H!4cECF#li`Hd=ykz6l4BLRSGlHRps`-(3Gq{|woKz_0K3@<2^4gr46w!~zuo{V z4p(6Br~5j@z}pXBYq6DjgxP0X{LSq@wPCetYqH{5WxCZE%i+;zQBZ3)dm`P#>Z(B) z`Q-sA0j&m%g@TH#X?Cq=tYqueFTV%xwEVnVk|Xg-)Nv0J>5lU)K5a4Z%9E0}Dz%<+ zPr6Sbu7q8FEwf?AZc!7x#>L zjE{8Bq9UA5tF>aH2zHg-HMo&#Xm6<>9gE-6cC#Bis`!OnSCKtOLQG*5P7#^b!42GS z;Dv3kap8tFDE>8*7ssH?Pq79WK>?z7JE%IN(>k%17{iq8nt(fF(AEc$Y3@Q-0pacb zCU`;4FGVA749NRNYrn;52HU4G@Ry-CO3-kV2{{EL9ryJO(CUBDb&`B!>mVjo{rGCCPv+XA@8Rw>cgZ**ENn}iboMD zP(%FHg9E6Aw4(ad*FMrJgs~mh5%*Zxi8v3I)F& z#_8Qxp>Y4dC={&!Q7BSi+|VVELqBs6mSvZyTfFiL^1?w-F;Uf$MzMK7WyDdzwzbma zDKjZD*UddRNvLzY@^<`os>{5|IA%G}oEHp+EJ=wNsC!THCUX|WtKqa&$S`CSwTwAP@z&2!W%vb%$gyuqXAfcyP zlWmJ};@d{-%?5Npd!lI|YoKbNT#&5EwZ%;-YALkkUVSr0*cRy(1BAiY!kj|KArn&Y zOL}Epfp5$7`h0l^mBLIS=TPuVdF5PzewkCs0a9V`plgv0C^{vVq`k7QAh#8IbpZ;1 zR+vlV4T?@FuiPt;ZLwY{KpadivLM1vqaJ(N3dfc!MYq!Y3u}07(34J{?)}opvKWNf zvtDCA{NT7ayWT!;y|^1d*ua`K!DAetYdXNzBK$ClXM&}a1~$a1WV zfRRJjj%q$len}!rq9wboQ~3S%Gkfo6&CP@&6)a8=b(Pn^%FHx_>8**>lQv}#}cv=XY1q?ADw_f6J z-D^Fk2?g7o!RRhHikg8sOtYRt=ahDO11znpg&sQiSvuMnL7F-g-A(Z9z-=sXa&1o@)Iv zUrBTryGtuBJM59*P&pg0QtCeODpEr(mT{Fd0YUO#L{B$fsW%ISBbR-O_|V34$nE~8RmA!z21fRQ@qgLiJG)g-M^a}G5L6=?Yr zBxPZ8{bkFtjrE4x>$rs5d-)`mcNL)T7ij!xZ>W;$K``s`$us!q{p$M!+kY-TXMe}r zI@UVWR`0cBt1gI+uglZjS!L6^nH!|ov8J-VZsy%E?;$(xR;qYrH)~FH!0SQlpyMHv z6~)mR^|3x3%c)Ak*P~hM6xIzw!1NR8SLU7V6x$!UMV{m6ihRq4AO70fSdvFklppUP z;18l~sQgsk=EQ%+%Hkm8!Z?g`o=tx`fN6?V-O10;Hwu^=wkZ+8PJY8_@ zBl;Y7NgdWp`WN}e-BI8IJ zDP)hnXTzr&v`r%(WtUG%WKSA}MM%D}LP*?a!7Ul0MVP z!(_AoF7zT0jOJ0c&g?0stQtGmRXynZv9$bgHg~<2>%*^_3gYeU6 zY!Z%RsP8B!#$%X37OAAa=xEAR6-E*1Wmuec3Btiv5^7lh$$D%SXOe?Rx@qzh62=Sh zMk^`NDlrxQn53nj-X>`H*DNzTcx@(dtR{E|cp^` z4mfIz@q-}bA%!U7!FIpzK{$7w?1mTsbFTi)clVznk|>K%#q-6)eZIzr68}B6;b`Y* z>0ti9EU?|IP07Xn^%ixvurU7*H|$~+U0XB}^bZS9S6wd7@_HTBW}CTjPLD2rYBls2 z=)gSDB4P;hhHQrxJC9KdvLnYEksd9jV2IYqGlMpA=4+imSIlFJG~(*+br)0wiMdTEFYV<+*@(Ke^#KnMV#7{=DM}{-Akd9l*5KFSuEZ0y)0ROhnu` zg!j>(K|0OnM9Gu+U{yjjeRqeme zDxv#dEZA4kF&l}QqbV!5-mn@ZgBL`Q^--d+P)BUTrCv0|xG@~_CVlonDG0}~ynXvj z$jCeNFyFd^>tpYF%yOOdo^Z|C@aKDX0X2ezb9N&sC5`QDa%+z+Z;Gx=`&-lm%OXF> z3`D-KXdXC12igrg(?N6(j#p5pDRx~fA*_t{(rc~NuzO3ON5<}}+0|>f+ilN}NzfWr zEOT7407Wlg?=$<~AcIwk=tDPD#xX0$Us{|DukE8q-zf(779c%sXOMy-$>l9UQj9M- zx=P2h3AS0SLha#Nt*#}-lqN>@X<|MQ0Eb4;q=CDP`>er1DRp#&6oYoxEy<~6(2nYzw~^|KcffZ3+`l?lPMY!dNABi2lxwc9PQ+fHiKn8f*FIPyQe)IrysWBlE$J#(P zHX#fBCsGJ@jvmede0~S;`-sTfN$-mY5Y0W5+f(14Si)k;wfKvsuXgm_Va5ai;GaWE zn8p#w?&A86Elq}t&=aeu)6|j{2yb#oIkO?wgR-v&$C{1YWg4&%=?a`>KBhx zdIyuVsbUGP7+MTm3&0Y9MmClHi2o1?w#;&OjG*OJ+PFuJ$k5gt;wiGT5v14mXFT`e z8qBmS2q}s%^O|j<7h&0|6u5~TL&PY#^Ff{Ifxr0DHy`bjpawq>BLHqw62f-3;o~>3 zrpLtj5SoCvS;0zx|G(Ys`3Jl6cKZhZ!EU=R?3Vj~$&c^uZvVdo8FiQyMi)rrc1F|7 zXo0B(s1OV9#Q**qB!MCgNG&dd08K{5qnw!Y*k4L84er$p6o(KEN7@qm2BY=uM;}2c zI=cI8WXNIngn;YC{1dRY^%6v{&jnFCYfP-4<~Tb=iX)DVyRx99U?M&S-&0H9qHPXQ zbae~Nfv2}F$Y1Cx%HAxhD9HDhg5sbNq4HGeqgFrTIIdsjuN7l{+*|qQir*-N8 zOQ*x)DMRu7pf^XtB*CCt_7j{AeUElnE_nEB-%i;rY=>Q2!O<@8IA+AQ$Ur5lmpvOQ zaYf4PDuP!~6&}9;cbg@chxqzHxd>k@!p3yr#~hV1#XH8#9nF5&3mNN3A_H~eouTxE zX2H_~lAm>0Hvq^j+5{WBv&1PtB?7y=Jkl{%(OIx2uem`y>&qhYo^2!)eqt$F9hd1* zW~p3atG%QC3kUr~AT~t&Jpy2xU_)t zf}tw9mkjHf z^~%#s?zHq@#seFTmXjAb>jFQzUGFe#YsSNlb*`u9fU~V2!Yx1R;c}`x;Vu zw&iU>iAsj7(d^ZTE_Cb4U!YtVkUYY)8xEzF@e{8FixQ&Vh>;*gZAt<7D|V!4{i}9Z zuu@14`*v9cw|f)4Mj}cn3UaxxqoH%%jcIZcyZmYQyd#dKN&G{x06&!9y2;m+F+ebO zX$>@-qwBuvMG2Iuj;;-P>E#dscb+-)MT?s3D0pJMoyI7ttU`88mIF=s6Hoq-URtv% zUgoNYyF>vX$WJ7Q0so9@>9NV}@<7J+2hwcbRPR9cd45gK#*o1R{A1MiM2++hr0@2A z3i4PUkDQ*59uS$pZCzg64(-nG__Vrc<$&&((q!A^Vu3WmDd_%uaoN)`gv&)%zxsEX z6nfa=-TJ)?ElLtsRjb7?)U2@qC0;fyEWC*Ru%_Q$K3?3}BRAZ6{ina`=ZqLR%jVOq z-I0n_GjhVYoJ#8DDLuH;6p!o0BfF@RUxRv@QQK4ILYtEmi4mtJz65(|8ydp0m{~by zQ&W~%u@&P7^aayvF@ky&wyK?{wQ=Z5Ix#5I*8XUxlU^Un1YA3!Y(GU9Y9`CGXnxURudU%~SJoU%w{aFw&A7}+ zMA$WMcRw%a(aUJDO&*WA8m!?W@`T1YYL^$p1Ait9o>AB{mxudInx|%TSSKl^q{)_)i?kF_49aA;U!mEr!cP_zn9*k4gRsZ|N-@{n_Re0N>bQgeS zE)f}Au7$sL1HkcQ&7XhlgC5}Snv6sXzp-p)jx^tp&KX8@_=#K}j#vSYSDO^U=yZePnlR=KyIk_>>l@k6NmLNXHrcI8TweJ96pXP%f9xyv5>WOb(AtK zh4__9&xt*VvJKDHqxFEg*QI8;(ZEex{G!#9OV&(0CnBhejsApYTP_^LvXHs!e$dvB zk2mIA$z$LcyE>L|2@|LIFnTn^KGYb)j~W+SVLi1vhMpHu9wl`p(6pOtWigShDGH|( zqaI2<+vB{T=B1}Tzb(`;j#>BP9AmJq@3Pqumb)|7eAj8|d-SsPPVZq0T9qZ+8Dpuu{gnIr)5Mr6UI4hzaIXSZ)bq1oUojE>}3v2bi)d+g!lvsV|bmBye9F zONpv%@$~MDKN6J?*Dy3H>|>YumNw_NGc_8PVgripvvxOG?YzK5u)dvM&V9pyvGJ@j zu*ngBLx%ot#XF3wvpaC{dY?S9T7WBZZ;hNk#Vk=;(0sRRx6S95A0Pa#igwvGptR1a zDxUI+*A{TwaTnL9-IXQ)Y~143x!q>W6)&%E-seo!lv8k#^lpWt+HUS;!~Ahm(Lq_i z6gjL_W_j4vFmI8reoar_Xlve*Wl8XC#Vdi0oPXjDsm7H0O#NoiUn$$AHu>9GkK#I= zt-SuETqsubk7jl+YKybi{7+Wr?q#8nN<|%euh>g;E55Ti=#Y%95={C+4K)?MRz>L3 z@XRQmX<=h_74Y+>Od*;F6Q*;qEQdak-!PuD<6iL&JwS9IS`$&N55$H`p;tTl!{{AW1W#Hj}aBR{jx+!>z{~?4+QjG zQ5QuW*3lRFQpW~9kr#!B$NspM!E?I2an)hHgdSLg)n9uE{vp*s< z*!5lj(h+t0I<}D7E&`wKWCO-Q+!LJ*As6#afqj3l^e?j-IHP86lL4}XO1^1on+E-2 zq_-O&nA_|H=9-cdX8cOE|IzIpeIcuDY_l>{$k1T+rWIKRnCk=_lMydphPH;08m|u4 zx+kYCt>Ywb+b~|$*cSQ$&+rkc5HxU~Shixq)S8RHZ3iMKRJ)6IX$7x~e;{&;`{K;Oyw9twP=e5hVRn)4~H(8O&*BR8&mb1}= z=!0d?9)dji!`q%|99!nzCI0^H>U`%=zF)R#_ZKs(F+xR*cj;t_eYe8H|(Tod8BTH##Pi4(Ziv*#RK2ZoP#fCj0dkdw%D@q zS?q`vf$@Yg;4EeYZH|6I*~xLFcy^97`H3?B^bk!Oh8P=thRF1p<#uo9Ga*V#c%AN; z=dG->L6^G5ig|HZNKPj+x0KcnO(!-`F`*?YF&;xK4n>Sfbv`_7lY^7aT`yWnl%|6n z1E`SN77?EL5arM}mx)yPojp@r6%aL#v=z4kUrX`ST*V>TtK32J6a`I6@01oK6poN^D1y8(;7c1*|d4d2`)djlhy1=Z<+ zO5=Dj9Q4@*n;4KX1jZ=;)kEOCYD*YYcnJFZw=Zb_P%PIHNk^D3iZ%U3v4s9x6ieLL z+RfBT!O_;y&C$X_)zRI-%-oCoKZN<>L^($^7UaRt3ENfk!cY~1ZS}kS8^0b8*F9*)fo$ev?oz z-YKuNTf>CkO&fE$;dmnl;f=0*R3e))udcP?OXoGMk8Z^X#e0$Ftcv#1?v%j&d(G3X zm7XTOs)DVg{FHRgFE11#A7b?ttuvs1e9Gcqj!hW5OHPO8gvCTT~c-m{W zYgqs2u02w+Pvy5JHt0L}RGUQbHx<56%dKp!X>&?3v0rq$OnB zK?NqJ8{&zX|2BuGJG_tHzK&Kelcc`Wt!b7vHyl!u@>ug(Dl(>@zT;*u^F}>^J=bgS zonhCa=IjpgBOwceYf7oQ1t+ZDu>Lku_BWc?-Q?HXuL6XLb#BQT8P+#`i6?WI%2H*t zpn46b<(!T-QH`3Lq%uvaIZbr?oza*Bq&+U_SmiXx+&3`BPl!}!gvE1j9Q6hl>aEc6 z&InzN%>jZLh!OA<>6Bs3rgpeKonpWHf3sZpC(1~-bngYfBJA$#qoer$LzIcS82@Ac zV`^vY>RPO}sfwqL{^8(%&S^&?2_0nAtbq_g(PTrXAmKkp3#$fVe5Q5U&`zFj%te`r zi1Yyx8ym~^qI5UzdC*{hdS{osTx4~ku{3OGo51X z!_Zyc%1SszAmdPss=sh;Sq8g)I~a31A+OKak}NUCPYR*rjm@IU z3Iu{?kJecAQj6gT8}$_kke)shi@Irj%hK)jJqts2gho(gY&oKgRyOZsWh+y0;s5|K zsI!CC;C_Qi<)`o;WA&ebgHu#fukp%USJb}|?nuq^plA?W=`XFsjIS=bjke%xth`qL z((s#Z5tt>LZwO0T!)dW!F{bcq2KH?PB4Z1%bAcWrd24Llfsi!?AbJ=^^lcd8mqa#2 zF+zC~3)}}txhv}VwO8@~KonJX)sDwFZs-a=K0Xc`?1kR^OP)K%Owl33P~>x5-L+An zx{K}P_S&vnM0JS$p8g7LPDuQ=;U|7Ul7aGwtSY+tFd?XzE}G;*i1JVsMb)v~B1mui zQv(O2wETLZq8zqv0jvrw{R}BwvZdjV@F(Q-RZ8#Z2mE^Q`O*#a3WIfW z12lrUn+XC;=rRO9$~W3`ZGNdd_Iqg23`M^(i9L58IHZhy4r6kY&97D}B7riN>4)0J z$Km8TW|Lv5+yO4&_wWekO4?c=!rwNuli%P*mR-MxgCAer=o#-*OXV zeFpPT-`Pff7$&I6wM5h?Sb@EfwxYnqAvo-a>aqZ3Co4*tqc;mzE15C1GNhkEEE>*^ z9m=WF!LMKvUy@{eLsPGKD6b%-j^*fg={n3$1brOlvSGXAO>S)GJSZg6dX<5{o+wEWi189z7p_ z{+B_#x)jqo-4{Mtf8mqp{{ud$e4QToFX+@TOnD4S6Cx~A{zv#GnA5s{K}XoC4i|4l z*2YH1ka3j44$mWfJ!U8RGk8*P`>tQP9E?rW_Wgbu}@8 z#N!t*H9nu_QvCtpj_Yv7A1ns>sFAKDuZFPh!UL*f zTvA*Kj@-NjM#2XF?$Os=q7)0*C;Uvp%2=~Quc)z4iy3vZj*6jN?ElTY?%jIgvTDxl z+?r`y0lbgYYPM5u6hs>bV#Apvw`KrPvN`2}+2(C2&rD0V>t?p_vcozk@O~IT^hMp% z(cbR<)J#zg%i~V%-4>F`p5mPv97KCL2Je}X5}GrWM84R()#XTYE?D2-Q`>?(-P6;Q zq;=9mu-CQDcB|1yu}-1vlm_fKN1*{mbgJ7V1{IwZ2T%?+I0FS?DSBb2 zSEM>C4^evQa;x+~EciBu*hL(7Gv`vx@ZKJ<$JARaf<4K#In5- zQ`_~62zR)Y_>SzE5G}ri+RmK_vdvg<1}bI1ba&H<3C(kt{sff6P4>eg`6Z_qGsErH z*OSF>N_Xlo;je=XIfLH);8qXhd0FPUaQTISf5ro+Z$wAVt*99)+{j1s73uFJ7}u;f zS+!o9`nIwl-_Z@#d#qu}+1c4M=@8@s-YoAB1^Qmd)T~Ry8{su!Qg`zD!j2>qr6 zKEjTNX6bK@Yw>#D!PbpU#T&nsgE^nPfbc!GXz;r(K}Y4Xo_j`O?hfBUbMc3erysr@ zAE$hPIEPCd%01Ze{C#gxcMC;#O8QI39P+UM-tm~AJCv~xu!9#Yu8*LfemCW=mAV(G zo-ZwOd#*AH%fFk=%f}giKPp~=?H!%{hLrlC3R}bVjx>G@9Yn@NVp9<NKi zWP9E#`fwV#zn5D}=b!R10iLD;N|B-?M2y9Q30%O$9r`BNL!!3d(De#=1?@*1W4w6o z8UD`~Wj8B4Z}oq#$>|jTTOZ#4^+Nt%D)d_e+9&xa{AY=%?#8<9%2kDG*M^(B z_SRKHi>`LLXw&ye)|jLM#^;l`NAF*s?UNV&5$+w&iz@LSw5g!@Izj3!uM8Yt8^I+n zEjYb%H}+dEIey>bSv%Gbne6@!2XSwo`nGx;w-liM&-p*Zw>V38dMzHpaQs~l+TC^@ z2?^{wm}*&bsv&jhD%T8;W3TA2o_OOdHVryNOis8BMb=v5?!uB*`WM!8(eT+uC0rFdP^ z+NPe~%GgdXFJWKh6v3WYH7&;HLuNgdn&NBHpLCdpichDd*lC(d)M%eu05of4maOe# zV4MPptbapE2NPj0qtMuLfsx z>q3rgF`YI~inSsxHCo#$)MbjFI1=p3L1B%AD9n<55pfeCft-&qN3 zsSyP``0&M`2|3m|FmPGSR9kY0bIP1bX6!iDziu}Xq#HuqsA!@wKnKcJuH@)*PVNkx zs7skRMygS1G48HmInFVY%n#W$J}kZHT!qYyeGw_MKp@VR9MK6Hf z-8QXp#Z0HS?xB@ZHA&T2G&L-_L4bE?;WQqJfG{ok8QhQ>8Mk*z$y-%_Laqxd@`;*w zJNK5rSYiQw$N8`^J?_gi!Yc?F$(AnD&&tk$AIV6Zrm@ zq}>u_!IPbF!JS~~$^j2$lOA(pd*Yhi41KRc4bvpSooVCfnu(%23or2SlGKNiu0qnr zN0m^ej}$v)DKSoT=pk=!Fn0K@SOsRTw9hM7m{AFxcSZJTi|(?th=vk3s1J%;%?+Fw zR|Ta~%wr2s`S=3~9vjgJ$HdwlM^Dmhdw&Xj)G(9`Oem>bWd9?b^hI zFlFw>ijkbN^02YS7PVi%(zX;?Y%`v_QPs0O+SdDI4R=w*sAk1P&U6VhUPWUPkTr>uhk6r)GDVh&#}3KPDZJ)~gbXvofpbd@ zXLW3etwBw{S>ZM9M1 zkHicEG>RFANt@Y}bZL!~;zSu!0e>ly9SEJw_H zuXq6TNzW6wVWUe@!9Rn+9uoEWRH!5$T_kvcHf#Wcp|sX~Jc*~l!ZQKZTsbhAv-k*h zZp)ScRd6uS5H9tOWl4m){8>Efomf3p!mMKDKm}$%1ce8<`yg@PZM~($QgXSD-8emv z=^}leF1g>&u`-CuJGs4Zk2HJ#<}SeC)tjjTCW)~`yq1v+NTCT80}0pkbvjsS!tByA zotX%rpZe8DTQh!wnI*FoFT>d7R)wz$0wVi25ZomQJeAMWn<0i*{Hj+Q{wV_ZkMLQl zL#3YK)S_cN-qJWSh_7Qi?#kRh?U9^y5AvFyc_~aAL|!PM;HCt=q)kP{6}FcOU%ZA# zG$&Bm7T#G3UVbi#hB<1IEWm1t#o53TZ#D!_fIz1Pg=K-$@L+NTLri~x=&{{bY*}w^ z@QS^Obg70f>BJ1wAna*FDqhi@AvD>i*0CLhFWrpbmRY0WV4}@%YI-}MbE=|h8$(y- ziw>80h3ZI*8^0>`k>Rr&r#MV1-6Xmy-K>}@bCE9da@eDs8K)DD+s#%!T#lE|y2pC0 z&Ogtga&ggSax{FaY$nQMDUrY4pDE)2qXx-{_Js5KiWimn;06BJZn zo3TnzoQ!FzFn-w&8jV?os*p#Fd89YfI^O|>6`{A>Rap@+ih>rTC=v`@)^gc;3oGB{2cTCB;?ErRiwqmBYA^m$2 z)NJl^g`#lnB{wys86Pq86&`KjzkZQhIB7^^{{FZ!wYrcZuU&jGYPigrbm$(OTCoY2|mZC6=p^7g}oxs^O~LB4hF%WcN5Giw>% zm{U!(s5@7#XN~|xI!!`q!Ur7Y6g^}1H%_HFYYYArIkhjU*oTpq;xFbgKva0O9mzr} zgxxo%Zj7cDyG;QQHhLp7BMFMPZY9kYz*jtk?k; zz43| zfZMdfdC4|?4^of%x}_IhZw}k}QJcT#{A?JxL)nPgQqJIRXus_!+(As9TrId4YXU;6g9@Mq^Tu7FKG+|kGTAg-@$l4)Pg3@ ze6hY|K~!`!_}l4h5>QI|d&`xfQVZ%Vm@k4q&4M!i^LfVlxQ{Q+xKY7+z2O*~&jXL% zElr3e#ucyfVuj;31sY_KOanJlZU}p<+^vrX>L&KbjNZdw&vO20mw1EKaN+BBZ_e3j zBabKT)pFN{V6AVwesw#KCkRGvj5B%eNY2SgnIBU6Jwwx2>^z|zeLwyCnkc!VJxvV! z>Is|98Aj_b7;aDS`hR9I>tQ|Oik(4KnI4XwD9BBSzdm?FDBhWTYm3GSyySnj`e3{| z?27FlRB4Ppb0WWxH!y|hfWU+pirP>jLz)3mO!tLaJQ`GFvKd}AXuvHJg=~-s=u<3L zsTPn^uq4K0E)y0UizfSxnIXdu;d(}o_aK=G{8OFA(xj*oLz#suO=D?^DWqR)er4U{ zV)x+9By2S%r})N~yfbqVPL$|RCxx4a;wGdVdbaqetBpA;(bP?7aZ$ssN>BU45g-_{ z*cG;U+>)~Inmu+KI;6h;!I2sGbQx@>N&O$6UQYd6!;_!Sapk)ouo_ys-YBm$m(b%OgU=aD8Uxo5(HD z3!5p3q)A zZBV0S4v*(M%FvcpCGO6cT}|Vk&V7Y0Q>H`}FP7N^G+PTeYeaS~nFL0LOgNi? zG;`*RE1;b+(@{KpmrBmA+{M zY)OTi5#z-#@^6oia^s1mkf@5sXEP{hPuFya=PiR_C;md?wD1hbTDwCkik)I!#N`b4 z2-xMa2|Tfe=&9XRgk$gntu-zt<`n8}y~nU`(zc?j@W7V7`3Y*}U_I9;NbBF&y-!7p z_s6p+(KOl#5v~Ix)M*E^C^_0eRS1oD3g@~>j;Z*wC=_3Cw$lx>#dgj5fhLzs37@{M z9?>Y&_inHYpXdjE2@47D)kwZa8chC>rxnyt=mIoKp z*)wTlnEhhzRdv$VI0 zn|UllG6>^XLdSUCOXIn?^F}blt1^;%%XaT`cpT^>-NBrEI>C3`L}cJFCAYr+h!vhJ zfjWMFXbP!Y$Q5l*RTZ$SpVjv$E*;fl8R8gHclojL8QMb?6+G0_&FAq64r*~7zMzlv z_?ivA^}ct91%BsCc}Uqq;EC(V?7gPaJEMPTyw&){T64$ar(YVe${iZe(d&O2oK3G5 z#vbn(F4d4w#g~@nsvT8y(1;yIp#H5b%vUtru|oD~zg3f^4X!^jpTF;{f!Vf`gJxFy z+}N+s3u^JuS?4Mp>e3ajJeQRyQBxH}&|T{c`A@PSGn~(H%y1 z4_Wx+T5UGo9boD3`vaWFGZ^H&E@FP;dDW(U!)ye@7QtujcyNFnbY^w)A8V)Yg1zo= z&B-*iM!?Eev~gOE_QIBLZT!_u8+citL(Xl>QnKOJsbdK{8q>Pw)N}>cF~u%#m$q^! zr(JQ`lrjdQ-N9p96>q;reI(vW?b)YRqc@Of0WpzF_yxT~G}97t>5g?%CWXJemv@FS z#2F?xkp!b@byd$(A;DT)K_T0BAv3vixc2iS`VrzT8;voyulH7jKGz)o4gc(@0qO<5 zXNGxbP?mPaB`@vQngzH?Ft%nOfrcMWqEwa3n4zRv1VQ6T!HB!2-f3dlS%Q~#ERTS` zI<=i^gQ33JO@>Y1J-~0!+qQYgSq-X9xzoP>(kqvp#&py>@b52E0`Q{ZLs6&1f;%O0{86n*7kESZXc>XgMGOXWKp$Kxds7YYYg3Ee-im47ez$By zaT;5+DrpmXggNJHlKmRQ@Rn>EKB2&U(|YDIC481+QtjG1Ob$I$75bQOap zx`9cisf0UdN-Habi`$P7cyuJ2qDi2ykl}jg;JifS5`iXD^9Og&lpC?I$^SG|Tr9gg z%mKnr4(S6jXJ0*Jl!o=Q$|w8-TN?V>>jNGBg;#oL<>mMjbf;GQ3~8i#u{q@D)BHX^ zNjBalQ%vnGtd$uoXn4@7m=)fOyAp(DP1Cu3LqPc&r?b=d=-Bt<^6XGO!H5uOZ*!zY zY6Cy-Fqk=sz1Hq;p^-(YA;EQwI}>QD*O)t;hWT6^!=4ns>+#qB=4by8ifcf;2HD-e zW}QRdCFG=s%?kz&|POUEsC4xf&2K!37jhM9qFkq$jgxs90c1y8Z-R97+_?IG_bp zButRkQS=#AA&w-%rXP`lQwDWXaVSw%v`fe(!I8N!IF8AsB=5pJZ>TO!patWd8iL>v z6kVRYs>v$0o|3bY(dSs!4`-sES;K^}%%ArWxv3 z@EvdfDKZd1KraJfaU-iK^{-$$`ZRN9#kOT~@|LbNbVfIQKsIYz*x1SLl)J#kz*@mh zdaee*IByYv(#*A`Kg}IP$JkI+Xe~mVj!PIWN9{qJS`x(P4tf2S+MZOL{Id@xLqL;1 zcP`kq5&&7F*VQJdmD?J(eagodR8})EpmtLJdnfps=+c!NaYOvd4zdK)!Zmhp%I#zi z!a)WPd*8gIktx5Zzchs+iF#(;>Nw;Ii{>J6#E8%+x)xK-X4H^UzJuCkB3b81Xyd1~ z3lkA<#%O54_Pn2kNo7Z8W9PiheptP%L|jZFRiiQ=Fz8yI5C7!?#Wu%!rVbnsJc+3lj>5g%& zIKMrj?tTIaeesd=Dqyrbjk!NC?L(MAkEF)uNU@r(pM^;3_T;)(Pt7Md|0 z^L%AZ({|Xqe>oh_ab{Yc%1$psJnkJmp~G)){KK(%ZDy2Tp`50XL#vV4%P*z4`)58v zFe^J%T)Sp2g5(&An{Vbo%dC?A3HxBJL~!`E+u7S1 z``O}pS;IDr$cV~n)^9r{hBqpVr-4(pe)GP%g0ZrVFP+RmqxeO8wI<{9gkq03zJPYC z4>_R`K5Afj@dHb$i=gVxn9Z+R11Im^^|c|)`aFddRpqLy&nyib&pg#*gKU_dsY~!$ z4!CL`DbVuC>=o_K>Ab>y?KPr%Xr6}1hxx^hbtzzP3Xt`m7{8uwU&_Ha#XUh)reK*SEfPyB^<4-wgqME?U5ErMSZaA)4H59Lqx5p(O_&tC|0 zN4A$2;xB%{^@Z|;{M|B>cBAQEyA5O>JYi3*>Mg{+D;fIl0#g(z~pbYXL zfD<{U4~VbwOdMOpPcpHOyxK-iLF@629Gf7)#;iVEmaKUT4n$!y|I}KdPEC0V`;$8< z>MBVlJw8d1W>aNx9Jdlxl5IEz>r#60*{IdU4Ro?|gL!JF+I#~DLOd9xbRV6{=DQ;`YI2Mv!jo8u#PV0`}ZzK}HW z^9z*>`q3=1)Ui@ekFEM4vJtF5LrN_rHAVHX1?Q1PKX4Si+*zzfuMzyQ*%Di=ve=3( zcz*t`ywxU3MqFj3>cAv&s79oxyN*)HFD3xhy)hL9y47L!nhII7N!6uwRONZ&jcqso z@;nJ(gM5>RB(yCY{MnP#+x|wSL#HyI_FRzBepOcJvq6%r9xZtz$77VuD3*-1+Loues?LG3G4Os2P;E%V2~0 z2y2#eHgh0ZEE#9&tV1pd!7rS9{z1UU3Xj+iwC9B)9%J7c3Fmf4 zVC2`&4Pq^x&e&;0%PhpWL_7gV!X5E6AIO1wUD89y^rRKW{q6@Q9%Nid+_Db20BJ$RP}=+-~p>3Q9;2pwVhI`*Jfc0q2&1V&)P`APJrn&Sy{v*nPd;$Uw~ zf!i(u;@D@l`N)vTzXk8pox8_gRns1j*vRVzRhMU9(OvWud3{5z$6B@W3dOhk^Sig) z0t(D(i2nXio~CD#xNbJ5y3#LUol$ZxUo=-afB4q2`U$j^lCdQmEH$@Y=*chL`qw$C zqX@uK3mCJN;>=&b&(}vjT>*QWij|sVO-Xl5Sv~wHh?2K5!uOQ z1DM{LlD;e^vyrhXFLG&4{0xiyo)Yo-Y%ut|z%LuTnoWYl-i6k)tU~g~@wH1?!2Ylu z{FGq8x%ar?8*%0{?f}~TJFMp8XSz)B9ZLIfdp{0Fizi)Rv0EBvWB@;jr$o+%L_@nqm3P(xG`W)YQF7?C8skY7ouA;joYyZHsv_ zCkD)F-=S^Dd2b@iC20sw;3jjEygXi_1I(nXk~F9r+-p0Dp!%M8G#? zLi1bLAO~3w*_}M#oA%BYcq8!?IM4MygQF7fYlWRipY* z4NE5Mp=IbXZPSnwny9(iZ_Ogt#rz#=wYk2ITp96hb9*FuA=cHs%Z-fHu($8V=xZvi zWzsiSGU)%uCkY{Hnz@=FG|R~9S=*j5H9Zl zG2A>67tT~zP}*s1ENLz2F6lJYoXAWaPCczu5XB_o$F+s7vw}`*3$fPfm!(U%PnD3B zHZheZ-2}uohg=}fTWMiysWpqx{hWdt%W1$v+p@jO^uYKj7I8#&rF-RT3MDod%?3rk zZ8G>=`!ZgWgR{>I&jiROJGYB*tp+|dKWu8bJlWb&G@Pus{I$u%+W=T^;pT0vZ>>o> zDea)!tM=Q(*hbFa7YHQM*4VxP`kM<;*cgpjOWNBqu(SDKgdWND2uk8{rfZ}A^b5&~IU(=_S5Ymw$~YT^i(;Nh_ZWC+ zH&&|S_~dR|VNC}$z5|1;siPuWIMevo+FmqV-9>CYC3QLNBA7ed+(9X~9ooitMHkkn zDM@}jQUlIceLNwB{;+Ks3Ciq4?^bwzVzp?7DTw;6XrikJK3T@Pws?06oJwt-&;_lh zy`?WudFO0n>V(bFDscm}<6kZLcn)(40rH#F5$h^@32{BuspvZ_l{~%A9ist&Ta( zTUe#7Z(mFFZ*&)a^KSR)srSJay0UnZ?zOoOcD+&Zc~c($zKivskcFmR zUSJ9RwO%aOEWg+FL$8R_`YK|ilUP@~^|_+Bz)Sw(SVO-2#-y>48)?V4!3|-0f6joo z=+F->Y!DM11#ii^@suc*{mIrWlJ_ckZrzwwXMWv~?C#Lw?{e<)OQYNLG#1IGF*?Cs zFvqSe!3VP5-#-0aU4rT0-Zuwqxxa=;Wh5$}Y2CBoc8{}$MuqYwKpO)3LMnPBE^;k#D#Xrkp@28ks>&J(|Zf*<5vfsIP%+d5xYju%mGN4;VX4%Dg`bxAEi>+7by2mFh_tHMQkX z;WfEI+SL*F9pFAU>f!x-v?jLlX0f7lNmV9NF7Xxy?9@2z6^{jZOL`$J>HShc8e=M@ zbF`E7Y?B8tJQB?I$pr2sv?7^=d9I0*G!(& z#&VO4L#u`W5lQ+ZG~~W1n}{FAoYx)c>Y@^(Ybg!ib`gE}#C$nJMNk$*1I&`oLzFaY ztc3U!2QS1(rDSkqH#>`}s)R%oM9u`%T{)E|bag#K&#fjkWm%evWspppI?7F5XX)9Q z={1?8HJJvfE0ksNUB&ta=$IXTpa&%VcA-Je))%qMb!AepRDkj3Men$%)pS;JMrFW`h? zg3NU~r%lQLb$2c2Q!#u-Cbm&oX&|b~vg%MxU!qOx3SyU-%I)+LA{CI2fJlI_ChH$2 zF(__wY(exJId&4YvO1rP#e!a1tj`ge^ur@)>1^GkFpDs<3zAV>Lg0I$>pn&yOCBGT zNjO1k@-b>nKR|IF;>k;AKz9(JC&gB+{#30bvTBe1Qp5{-wSnd%nfyYgKPe644gI$d zjj|c3EUx{tNyAIXnyiZYZ;DMOvyI3oLX^J{MdyrEb;;WPs=2t^okckBNxNz^X}Txu z@ifqB@9Wwg$BepSZn6AxT4XD-TH)o$C{%f^a~t!X{A*W6rqq|j+*R;9iji)0$@dEH z!y?kzaeM}fq64N=si;|ajicwqr+kUwfEW=2uc1VKd1Yz&@Ih#V;oQm2Z zY2v(BV`r*2&c>^h+niUK?P(3^rp=R?k=I^|DjTvKncCg;Dp^VF*@d1g?W4+|7v2mA z#pJ1d=5`H^R}GGxk0L2EwZgGQT$CAQK-wOffCm~+JbZ7E{=y5#55isOOl873&a5KE zr{10Tk%_4(u5nF1^#nj-a{KWj9UotFt z(D3#H+>0bT+2|!hV07hw(II1q7_XO2cwkDz3gS2fXo^JwueN3vk2kK1urcUz2~RAry7o zgJtG*8-X;8=HWIWrvK3vm)kvyM!9=$!A0@(>c};_d-TaNV4wLVKf9iZ0 zGUMc_R#*Y)fvq4TKYA6;ba|5mA?TDh5^LztZrJE_DF#!}f?6hv+F%|;u&j7(5x9m_ zv<2V(W4!@xdKk&YfX&5-#$`{86N9?|vQ2>DxnHgON9&$wJ!oCP!>tu3L0zb>2jb=M z`<{>wZcl)GKgOMBJEs57wB5R0=@;#G)SZBdkbsF$oLZzahZ0Ok^(dQmvB}W7Nbpsm=syHlZ*Go>wARh}y;DQy{3|Gq`W^TOpT1?AFh)i`73e&HS6fu)KQ z5KQX5Wt1t!^ufIkH;81fNQuZRvGIf(?th4t$*f_qWcT@*HxErfNs9}cH}^*r7W`a; zTbhBGRqPQJvW6sE@(m5rGLHPJA*uP*U(_6H&aBkLu|8z}b5WRt@^=B{Q5IunqKM&n zt{6m22=Qs7MJ-t-{rN&vFi59hq;$J4QfW-_XKtacO~2LpS*Ba3XqNkT?PcUgb&Fg^ zO)bt3_q$I*vBa-I$FYtpX@uXDbcf6}J=uoprp$GPI4|jQs-tNPO9da%trj&Tm}>G? z*Lo5#rQwv)hn>p=*mkZm;25QX%vh`ivnsM2s$5i+$lSH{SMqRoC|f*II-<40>-|LY zmxk%vh?Zp3Q&&ubgxi2s*w8+K&XFg|Lk_I3!qZ?z;Xo1Fc-UZ&wia!hYb7BXBBj1Y z!Oi$pS|*ONd8w=wt*kL?Z$sJyX?+;-93XA+ds0}}s3^6pn710#MZ>%{^m&O-epc(fV4kD zs)pHZ{YHLC4rHzQks$~x@< z@Nf02j)%1-%@tnZBcNd@b^fd-N?GpvRUIDFo=HC@)`^*Q%4|cvRO?)vxAhC)zG0Q0 zPy&HXwipkWd!WFj*twNBDBm(vCcrkdgVqiW69mF-Rf&RM-5poVUmV0Sk4Qzn<7N3; zPgcl6VzFAOWjImy3`rDxc(O-Z12a4~pC*WXw+L~-gpgEDN;xQtH0YKdWj-Lp136wU zQa(suUdmhmF%#NU56NywU}?^6dBPKJ=4Lr{IiI;!!qzlVfCZf^)a$R*nDv-gy0z~s z&9DK;$RAEVTAafxQM0z`u-zRZ^Q(NQR3V)rr=4Ir$23oYu&*~>H;>C9;)t^Y%@-Pt z!x+Hb1TFD^rtOn1$hP{n`haOx2t*yB*~6tyoF1~yN9;YG?PsqEwbg>%Xhv`4YTgA| zNn{GW#5qg8NI{4uoe#{K|7-H>sLG>;xSH`dx>0a%;X>u8)rs{GBMa-Eaq;g!yUdE- zxc*mp-k=sQR8%%o^Kf*5k8Gi3o|wh0IGT_WG-4%G)cgluN(Jqun6mc)mZ1&Mzor+6 zq++%s*d_@~I@JhuXJDm?Zn&JCxUx0~O*%f2jh#ms1+C|^DBh@em0{ob#r~wJ_Tk*z zMOzf+@z=#!xCAV&Wv(CR;)yJo6ED}<%nWwNt@yVY{hgMGQFm7#YA1um>O@WVrdhx8 z>>7sFrcTCgvfV+10Ikz@K-S~2QwvuZ1ho`eo^p#=B?Mtistq=ulPb-v_%^bN;{FpO zvVA~>2MO1utU~B}`Fw9izYD)kK%3%{ANOWRumd-KfUpPT7QFl1)~&4vcyBPW&ySfu zL^gv19%y{gGyBLs$OHo%caA<7d4CY^-e0iz$b4Qv6T;-9fF3G`yDWu2;;2sa4LWPC zK3E&G^Tbjg7DiqC@ExD7{$XzLKTyo49KB0rrN;OX%v0$TI`saEAi(y@Wdy6SB}rA- zDK+GLj~)F*=(_tB%N?zBrdbk&0G!J@3GkvgY>fj~w#>Zy&DgEDyoI_fN5%uR13lyr zl#p&W$K5;B-;2PFFn+qsdy%8euPmK|XyjW+iCa3bFW5w~dw!gVWOaWbTwzS09;}hx zdB_qq&JY0yK*uYt!F@o6_oCuM83gga;K&bVsC<9JmF-tKV1EJnAI-Wz^@DGAZ}#YX z@V;X0^97$}`}qqHcic7~J6DSnd>6{jv-p-0`sWSccxSglLqRyU9uHnK3CbQ`TMhx) z$S06*eK)LHy`}?C&&f6#w%X<*pDekbFyDeJ^Y02F-yxbW$hxuw+<^n z%~u4wEraL@GklgypD2FD2fAaj>pi)Jo($B7WvMjFB!@pTs(MGdukVC^^mDr3#Xz1i zerg)(*7Rp)zZe$A`}a#$z@aE%JPr*ku_h(iCQO5?T$3u9ZUq1}YLL<(Cm9Tuje4|n z${V+S&(_3d)H;4WTuA89cnH5{yPe6**iKopuqlUniLLn|fLtBe7S7Y$4QYsmy zNdz-Qxp)Y?>^U+awL%#P98x$zEY-EP9a-8>ENrKmpTJ?0 zIf-Ex%Sg^7>bZYS9!7WgwOW=wcWu;&n^vHgn*D%L*>b66mYqU+EKgP_y7-ZzW*t_h zs)`YL7Ec^$(47wANerbtBm)(>#wa}*2yT=+059EwlWN65yJV;F>tlO{#k&74^Q5bK zy=>m|rYu8Jz)?e~knfTzO6#>W&9;4QoCD;m`>5gGeM>3P2m=XvAa!EFl%e zN{(8<%T~9VreQlt$$XH4JQ*}zPIi(z{~)OwdW)Mx3W8q;pIEi+Ftcu|>*xEFdTd=| zTT?Uh&pPupNq!?*t&LW?aT$063Acdm`KWx$bNA(cdB%zs+n) z>lu!}7){A~bseoy!0Y3P?f`~hSXW};I>xHjyDcA(9yuz7fpJ;XZFoN8D2m)ouXIAt zNM@f`F6EwP@SgQZ4jmMOr2@hi=gLuql|Jf{jC>7{HB0G@AdVYS1D(*zdP&lsX6an> zjPZ2$$1O@aBW5Cd-hSoAq3mxJoZEf@!p#nprb(aJ{TuoHo0$gXbV!L2o=QZML5oEo z9x((4P)$nHv0cd~gs1@vANlIm*AW31CFfRoE|jVek!47Y7Hy+m%VwmU6R{2Wa3|D& z!+?yj58{|Wl7hEbahWe?3h7GCU9P+)7=9od65Tx}y>(rPTIapq9^}O86nrgd7H@j? zvwV3RQQXy4_;ru^KKSX{h2~!^Aj~Vz#tn^4FHL)9JM%i}r+S7|W>Nl@+mY-a{f1um zm%3;DqVex%0KSUjkF9|^R5p3pT@#CTm0MZKdK+NxVF`s#zOO-K(l=)4{S8RyPTKv6^NaY(>pP45Bja4q z47oEE+IIqs4(k(`z_kyYv**`T<(%2uqkZ?Yz(gj(lPUYu7ab**AY0mj@N?vrAtg>4 z1c^-K9)*J>BE1f2=L8Ghw`@Rb{wopNIxv)qw=G$I_p9d6>mC1tiR;+O{HeUzhU75|iX|*9?XaGxM{?2zEA_$B+T)TCtSclFT4T88~@ednEt8xzCDRisnA&X}z~EZJk$<@=xB z8UFm-sag~m8rA-&kuh}nc)DCtZ648#bIWoJal;%=h?;#*3k?3Cfqiffm2N2c$h2M6 zJLi`i53Fs}y8gVOI$U;7@r^?~+a&j9*=@(wL2>pBA-0b}aN|-3yp$^I#1*<=8^lXl zA8hh7cI)4Chbz%|=p0SOZRzJ!E@qFP-HuhdW|ehLavZ94`BmR3S#rc;0v{kc`*HEK zdf_g2Gm8WsN7B4v4uB!g?9p^gQ56}k!U#OKRtEoWp2DAOdDsJ4_+__JptIuV zfD=xs=d|^fAq3l}LmfwZ$}OYIh$+~RZVK|`1YaBuc{(SOe%88xYXZM*naJUPAYY!J zt^r1Z_92FCf_WBi8Xbx|fH*YFNy)^#>q3Gu9s^z9pI)wO0G@2Xhpy2dTbev*PhLUD z!gH2EAPixKRpz{FbMHOhtda}M>)5gzCV3c~pY)ZQiAD2SaGljEbXy2a2GQ_I| zFE~ICchiDuiR%>OU|$qq!T=V>lwpzylQPVzVJt)8Jmc^Ivsg`LwG3e6CM|@lH9zA7 zm^7jv@=C{p7VR*o=iEYDr&$tA4BQy6zH633PF}g%%o}n{qF*FIcLnH5Bis!eih0MY zA~OqicX$^F?CroJR-~{h<>knTW$J(Om`}P!_zOh+NBkk`soY~|vEdS}+69*e=XQRj zM8spu@O;uD=9}#1>+kkc@A8Z%LczLJgy1rQNx)#mYIQXLRx&oB>PqK9TMb9dU=0H( zna4-tts733E zlX}8Ab8EkO;*H5wjq$f5 z6h6lL7{QA(dK@{}Cz6=<}s%9O(=!>G>_6bv(#3`waf|~ zQhLT&ZrdozWZ}=Z_fJAy=`;M*gWGECQ^E^`Z(A2X;7#lJnDj|=l8LC;-@ zc>~-ydMo)4Lq6CSc(|T3n{@}LzV;M&f6`C-FKg$EOZE%Mhv>};!ZB+}RnzX(tBQB+LHYwB6$;a)VubR0sT=t4esoXS{P_asQ2h^?&31nEW92v1X z{4t5Bm^)wt?dZeUJB9Dn1n8LF1(WUgLc{RMf8Px|nDRwdC8B8wM~tm068w`Xt-TGH zWz?6S0WDPH>i{PMcE8x&)$0~1l`nm|+{!PAK=G*LSstxwpW%R`ag0eL!qFaq$p#Wi z`TW2mq`;YoN^Sce-((|z&y^5@N-dCGvWu)|mTZKgY}ve{Nq@Xvp*Yc-V2Xt#*#L3~ z&TtTH%v~dh)ybK~dkf{xk#F1 z2I|W+)aMygx4ix@u9mUnt?S@pm$TjcV9R8t7&@7vKueu!3mY|7dZK8%FKa{@ABRHD z@v@V1O_LPu{Nx}LDu*)_h+dT=)I?FeF#R82|HD*EQhIEL|F1l+4D)~M@0T_F2f0x) zbg{HE7j$rNvNtrg5VbS5H~Iew(CV-r$ww)Ey^~inlksr;GK{2w(-m-P~ zzOiH2xYj~Dd-wgGWHeYEInm5@_;Q|h_wM~SOlPcY^LpV5M5P}LA9-}*>JPEltumeU zBg4;FdB_bY!jG`>)a`+DIc>(^cbD&xVdyR2vT;xwNG+`#?Lx!Xjowr#J#Zv`)R5Lm z-n%>P;?Vmn#H@K}CHAHr5^TPRe+3gYB=;G9_zJ!LG0L)XZw&Dm|^=68em}ikQ%6C`w$!Ow|ZfK z?_Ij(hVNaug@pgE*^9&eD&F&F{iqA$VEfP<(5COc>gT4RO__rbC;86HP_|CwGnT7ak6J)r&-j*vp2sF`u)(LA}v5o2kCG zw@Vv_9ppW88~!t~O}fWi;S4XE;A)Ljvy7+5yPlgEJZ-bb{Hha#%HrqrwSwWOu%&^q z`{?=~n%y&4*GG|7SCK9+FEAY(VqV?fRlw1!Z4#aYq6xs*#f%jr zN*^UQrpme;iF3c7GRBZn!aVSF%dZ7_CI^JLu5f6C?s>AF3Zbj3E)M6Q0}P&6fywA_ znUr~LGf^Qrq=>til`)fvbkn4IwCf07NX&{mO?4@%A+eRH`K+|)6J-wvB>B0 z(Lt2hdadPs9r*uH_D)TjMcuY;veGu*w4GIH+qP{Rm8!ID+qP}nw(+K2r&jD0C)Rg! z_Bt0cV*Y|Y`e>uI_Gl!OGNcXUC~J893Y5#~;P`91&z_Sb0W_--abx--CZ*oXonY5F zm%(k%611O^#^rVbKmQ&Yqq;RJqN4y5v@j;d;H_7Iz40*vGf4U`sdaQ8i2z;2w6zxX z=qm;+K*Pek)>lg@!ilHFRUEwm<+x7e!MFL8Z=Et1l?kh*nX!vHzHElA(Q_PQI-l%$ zlvuoSd|R4>PkEviAPc#@%z5;F^f|nGs%a~nO7AIGHd=TQ#x=<>8MT6`Z3h=`KT*;- zd3HqkcK3pWXTB-p3W3<>3se>xN`?gPhUXKzI$25heSJ9 z>hxlf`@)%fH2OD;M?6H=7}%6HOehv%f3x2HkS>c~w|ZO9$uNhihfabxdAsHe6_%a9 z=ILFwx<}9|ZAmz6YS|+!q20s5VZv76tdqVbG&Wm7>R)z`VV8WbVR&X`usbi&-s!jr%gxRdD<--pDL z!jtTi!;|h4^=aOddbC8!n6%b3q|Dr3xy-k0xh(GKP*dDfwQ0G`xlFgeD5s&OPUU&5 zdn|UW_>6YU2zVS`f0cJ?sF})b!ZxYi=^~5So;RZMQ@eWtA zRDC(8VGOc-WJwMg)bO~+r}!#;`GsA1)aV5GI=5tm2DW6|Er|w8v~gr<`*kz3oaevt z$DE{HaGu`ZobF9V`&G?o?II1D%VIrD>h31CWt1H?8VFKiZ+E=yk3TdMJB-#7aFw~t zCxlLCdcR`Gl;Or#jv0zfK^VVHVWx0HA8)$375XIFp6k>kLXg*$ zFG5h%RsS#FMvwIyRUTjoKwR!7X(pHX*}& zRH``IAo3fb*vvQBCL1qSfTz5K!t|pm=?Obgww@J~x`_BM^Q3m9D5M{|#gYviPjG|e z+Q0@}9n!jsxXV}#ccC!iHU_}yLd#{WI8~ff*7n)z1E>~Sy?pNe4){>iwSD2%-q7i{ zO1;SmqU@TaM$CEFG6uheIxI}MEh#{r{JaC>rn6I&ELLMhb@4DBp zJz$nW1~8RlAAlAZQ1xDVo^5anx$k$W$#CkFfcCGx}+W0Q^$iR z!D{p5+@UD)cVpAv;Uj@}zZQG2lQcvjDPf*^oqI-@Sf3sgI| zrO0G{VVfZV1Z5Y=tST+3Xf6>`^D?TlT2eo*LZOg^PivYzAg~Vegj`_6U5t8yIc)IL zC65e^_RB?+=slAVcTUwp1k(p5%lx3QCN0d-62zy*)OIXe>1!Q%;bpabkt3~}e9Fpz zlIx;xpSZcjcMAJO1L}tlH^C=FmvxM_yedOIVPXEKZKuo|&z?QiSWIxE;rF5}d*lV4 z=__BjDF>GpgOwM2Pb+VtSJ#gL-mj)vz;F^bcKq8q&8o-t(#iaMaS#=vC-_z3@8Wj5zXJg#%yA*etB}JlWgB zfD>c;>|JBp;g8l&1Lsd117oM5GnzaH{UD>wm7MnU<)6EXlZzfj?cWVtu_X&>L(1Jz(V&Spi ziFXWFWZ{jKy6YPm=2b}T_$_eR-hfqsuzI6&L zK|-{-Tja3%`3`egF9E#lku#ZU@D|zA-k&k!XE1Jt6DWQGD8YOf4#^+f`Ji?q6b5uG zV}FgPKN>`A|G%JTMBo#Vtl*%t8C+S`|>_2I<=(ui8m`~(RjA#1a?>V=`n+QHZb1BWu zBkyUw1&4JI4t-i_%$$LwTeT58!QNP2W0q4JwTGZi$IyG)9&uTCwR_-EJ526gfx|#z zCp`=r>3tqig`EVj)>JX!3^Z{9Tx zf4nb!y|#Uuetb?{!)XJ47w$5L|G9#hKomf_fJaA^DX=^gYzuU%vpi(r-u~87PzxxS z)e6Ho_!oPCmt&5CHJr02EyE~GqeQhpRW}ZA`;lz~&%5*zVV%6ain7VG^PAr~bn0(& z72$%LYL(?n#VVb4Q~=`-4b6dyshWQM#(}YwI&giZTffrZ9*C!%WiI=ZLRltJVf=${ zDHV>fn3X$>oa$^0l@|V@`=xXEmRvhlt(um0{J1!$^tLkwthYWuyVuHWw$9%QODg}$ zg*mz=`!D^tJ=9Dw+2=dh3+1)~-i)v^hO0HCJRzNZ^I@z?R(lH$@?#X8!m<)Gq*b|1 zjKMUjHi)^sUEYHt$P4Ipd{pbD7>q;UPHANkY)aeuG&dl|5iR;Nt=PCkur6N_WIA7EM(8nVG`b}BBJjnm_KM+L! zWg>pWZcvwk0|9}<00CkCUxgh0-+06Su@5J!!#Sud<9yF_pWf_j+vvebHxLuU7)Uh4 zVMi%?O6mxO1(BHw|JftWoMxq?=rnW(2PLEe8y0U4Q-!G}Qe6R4^0w8wHgFx`I^>vf z$%;ATaJ4FSvl$)L*o+a%{yyEw?99YIOLCa`n)Ul0b^m_I{hQ@;*_k>ErV++WX@%@+ z02-22Hl&8Z40Rw8)?atlvR=OpgF#xbN!*cL&?)Ppon7SKnRkP{lpU}^Ue>9yMW5te z*(M(K5@uI>_FVp&|F0bRM%6Jl;zN}u?;kCIKm`~VK?ny2CGt`i5qR(s#`J*3P0)PE zj1kMN7%V(6Qw9jVek}*^8`rru*V)hm^aDYKMUP*wlvtNH@WbOF@^9pAD0+c_!0w=3`MPk~sOf$U zLgtQec<~lAYTE)yQ-MEbcfPZGu7msjeDq>&o)Y@ZyE|CBl9*I<>;584E|(d5aXnJp z8PxRHSNk67E}dd4QawnXTRPGh#C5~21no%T?@cHnSq@#KCAM(lp@+U;6HJ$=n4bd> zaPfov1_5AFK-Z8%+YO0T*VmhwzVd>mU6(N+_F`qr8j(Oa`9GN8+%atq=<*#7@ci!k)I8G)4)I}d*PvK2q|_beID^6gjT@Vp}3qnObZe4!FH!3%d(v17a?nb%M~Ydr(RJ(=K|R&f1Hx8|Z#{g^lSR z{;FgE3(M6Hu?y~LJqNBY7FyRDr~N39d;A>A>5-BYx1a4WjBR%$dIf#*A+rju^ADRv zl!8+md?CqzevLT9I!xL~BqRicpyr+ik)|nFlk|9Y(|r$3vJ9%o9i@~t=AvYnQq}>> z=zSFk+T($rI+J@J{1c-PpJKj>5qe91_NR0p$)Emc3c`~PhTjQP>#dE|beC7RH;Y27 z$?QjIO6^yPxNOhGVLm_|;)IeYn@u!`S{>-Hju`a=NT4c3bQ>VYm4Yf%ERQMp0j<(W zrKkyQOI?8tI_`0Eu{HWdZS*ETMJ5tE4<@8D>Ml(yzA%M*hU+->td>Pwc%)0z<=O{u zl9j6s>dS}@G#33|WK;z^D1oE$?pPD&GnJsrY!2xAPD?1=adr+csq?Q9Q9u^lRr4rw zrNTk!rbrK$l=9vXG3hY*P@I_y<~pkn>A0N5V=Tp7@bq`L`(-ew?5^PwHp#>trT3rl z#UbRZAi=K+&*RcQy@8Xd0bP#kR5o_LB|(IuDYu?f)N3@XETckGuG4uc=DBFi5&T>29U_ z+xfx+1LB1+<3C*UOQ?fLEc4?Sn%rRXF@4OI718ChfHY1!@8~*`Q_`8#B~M3?E5$gP zhefGi@!JNecq)v;q@7&BS;kwpioDOp-r?%NGU_8X4xSjEzbI2>nDDX2@SdT2YlRJD z<^-cKyfXLfW>FpG(F&XL*DSWP+ansdr0)ptF-4;bmnW+>Tz~CNXg9GZgO^rtyx%mf zdJCTY-@djAVy)4?PW%%iq%q|=3X0grig<@7yTKntvWh#^9)X#X1*L#mfU z=GJ9=5$ItWdE7P=smOYMh77o#GhDqI5nLjI8FHIyu(Z5X zsHt_u3*`n!R4wsBnds3H>8UYHxu_z%h)wJ`!Qj3odZ81Y?4$Cp}RK{Zw=K)R3W zuafi(KCRvoTtKFtPGZcY>)qT-&8!!l!k%q8Vqib&sP+-_!hMQxS*jfmLpdo`SLW-< zV5xKQ6)piYSdJ>)yWfC_AILg-WA4xO5=(+q9A2kf6MT$TS+o~pP*r3Xq7opKnnxn+ z?#HbQwi*Sw89=@nR*PCd>NUn5SzME2HG!x#qTCs}_JDIg(7hTEC-eO0*TA{y_q;M> zyE5ino4t1=r0x5vLPx4w_lQ$H1gRVxtRjf5Gvk@yy5qPUByNEWUFYN(=No2zW{r_% z;2}Le2vYfzsDvUX?aM=1Jp`u&!YGw_m*9dnQk4DL+=Ry{?FGa;hn|s=?4rr;vsNk9 zOWhg4Tk!Hwe*d*o@cz{Dh_jQw@#NJGc00lP8mJTca`fPY z!03ePmn^@p{KoRl&>J{=`~ocgMCnPG-M4;&`&9mh{d+>wv#-f93+L!RYY6+oj8|(& z(;L9~>U45csmX>^r?WGB?-?e0h;p*OsR6p%-}PyyHuO?wx+v`rudh_NiZ`7QcQhg{WONW$+Y{%1Y(D&N_kGqG4&aN^@<~-jKsevprS& z*nkl+qLnml)0|*XJprhaZI5ieeAz>8I15Rs&vH{W19*d8U%9%tFIL;^tOeX(-hxT4 z2Vz(hbUDz%2!r#PqNy#=PtNDL7+`FFz~UxT=*TBUF9Tg&9@zS3F5a}7P>YQa(zn8f zvEn8f1dM8K=ZwWO0oRYamqoo!<~d*d4on$63E87DTO_5mc}6$hr4Lr-j;fw~m2PHO zm>iMWkaFh1i7ZaxY1eL2yG9)%duRRje5!Ute+~qHPWz!d7@@L&rP}grl0!= zf_$_{!Z|pkrL>~24m?-+GbhdU8&c`p2{!Z<#ppMeKm@GM=>$U8D@+plqA|q>HcyXH zcULk#>o%JMl6<`D@*cYaONZi&-JIbPUJD(zGd;GmEOx*}K{VxBRUAb)Zf7nPRz?sC6ot>71w?~SR z=r&LX{fgBit3AN+Ot$5mS&@faEMJ-GnkeWpQgQPNBlq5ti2zfLzhza4A^E^iv2|G- z+x~`$_4X zP-9W~oX$`b#R`~WvTc;vJPScq4GFs+k?GP%Fxh7`$TVy zy>v|a7xF*5R>$rJ4+{S!k1+3V|l&nl0CW<~ZU7a*{IczUZD zE>=zzt}PAH5Bn9G149Vm9QewR=oy&gn13M zDsFa6uUoW6;Ko&?X9${KnjW=+1G1%CVP1a#E;ly_0T(eNY)|tsX9+fhUng+w zCItdBi0_got)?~2%3wS165u#H5@th~8VlJnoAtnYL+({`gwin>H*-`qtIT9+sSVQz z2%k|nwSx-+TJBPfG0k)D0JD}2W3`{zGO<6+pHRN7I`BBq2?Y?KIt!O?TJV8kScdevD@7m1Gl`n82EezG||VjB5-0UDHAt zfzZN9Q2CGF@A{T{wz+slc(SwdeS(Nhi&4J<|<*dEd2VlOkl6u5tb#ZsZ3F| zLx=esEMAY+JbE2JyW`V?EmLZP)HPTp8_hUd`GU*nTqd8vSyk7N#ClaSc6!0M@lY}3 z!3XVd?80Mw2q}&gDBNXJfTqjkYjQC93e&iZ7Jl|x9-b@tR(y6)5;iPjBbFL&C+g;s zb{3x&dT2*Np~!4t6e+X;DaR?PXad2{4utz$7^3x99;&^|2&XkX_t+k)1>&Lg4&Tar z68JI2iwlkt%6`K5u>r9@le2lpij58sx+GTvom`XPo$Rp#pSrUXy8#GF!q@PHe7M}%bw!fzln35$cH%#qqi zaWrbh&ybZA?%hvt2wPVqMv_>pc1UtrWsQ;oJB44bjGqyPr$;~d@y8)b$jpu{b(yha zp60|;CcJ7$BjIi3;yea}dO8;AR2OcN+@2Ya%Q{Du*6ey5>zss_O&?I9lG~~ zUy=i*KE|%?V|-}LBYWxvzrf8@RFt#Glj`6($$RjMqCPOmzvm!J#1(zN(Z{C63+q^9 zhDrV+x#V0#;gD|hJN#N9O{_}Xkk2Tyw7!WAS-HvghzkDxV6m(ULBzsIG*4MlYW!N2 z0e?e2L&H^q^L`Z-Um*Pj$F>jcqSWStB_O71Or@^muLQ6skR$k%V8;~<)H`d5QXvpe zYl1TsG@X8EG(I7isD33v*r)ZU1~}S15ky&d?{5iQ9e_9+0{3hii`;-<3#$g4Wux)>&T3(i1yD;JU^G*~DqJiomy>R}7GE*6z2U zrq+!`X9^ibXS6+1jaWp(q^i9~l3LlhrwYEn+oHx#WO)3+1#`U29N}U3fCn6R?=CPv ztr4}J_8zIqBgz2w!Xr~r52|#!9uw8Kii%z1jp|CLI*^sEok0qd1(*pZ^ASFJLMCcov-pe>vsy21{s)J1am!vhRsxwnBfFg!vx}YI*pN<=(I=os( z)Z6ne2`6jA2YopLno}g2#0p>(Od{tTmK;&$9Htt+kMrtz@-hALvf9yBHn_XWQhHZl`Tx3;zza+#@Rn^~}QyB~uf? ztgC7djSpt@Wwh0xaK{D*UnB*!gGz}(O?=X0yEXK-Qf=iE72MG5D1U=p*dV zh^muoxMI|0!~#7a%kYEh8`!?33UJWhQU#rSdUG6R)=@|KmxX36>&)KOM_rfJ1BCZ7 z?gMkAvzY_Kt#b7=`!+AW*dzi9gZP`d6Nf4GOf*w}0hj7&pzX)Kb;3`Oi-o>$`4E>3O3^$B5 z!g)x`G2a4H(Xk~g5f{CB023>$$^xx(_{Xx=YJEGwu`-dmKN6$U@|bF$z9k2sv)uuH zYuKwPQC{VK6UHdRrn~)y;>6|UDe9C_qq9E1 znqkyFbO~McbwXZYy!0@Ui?8_bpT`e*YNp%}h)5%<(G|VYn%^DhJe1K|{|Klm($YAM zU}+=SUKZikV>OhoAjgA#;YW-;DFELQfU?{VBN0ImUt*@b=Pr;H=|;pQy;#&q(n?qX zC}x$NI4eeYSVU;z^4Mcu5{Rqbc|%qPR>Q79Fe*5R-VMUm!0VOmRqXwSob`~AZMI2{ z_J-<9?TF&;31NKFq?4u9KVy3FVb?#QeWi)L7tJ~P=uE8z+ImO7Fjphpk@JmhduZ5> z;w@vgpZEUfy0s3v$_p@*ZPtGlx|6{mxNtFgUT52we^tD)pcZwcsEtX<(_ZL^ z#Yr8ru>LB}2;tyPwX>e5lDKP{Aq#5k>KdnA>iDH-xG#_Q@X%3i&ib3M+DK)Xe*6e7 z+vuF6?~Yz}R{MuCsr$j2ORNe>^fdi>hHHiGDMsSdG`(%D(3`5kfKoG_%F6h)L^FCt z@~mo|Op2LcYgR)NnM*6hhQ?3H!&v!sYJj`ed=VJtEy8zNPA8K}migh803i)-dXwGH zFz!N3x|YoC)>ZOXIcuC!2Vcy^rAOxwgYCNRJ^+|v|E7m{|0Oi72e={511x4;Szlj5 z6dr60F=_ty@*e7j+jXg-wnw85JWT3{sewr$lj|kYc~Acg0?i zLwhg=3_Wn>rpH5Fo@Zxq+waq(tMMg>W7O-g8};C?yA^s@cxwxcnia|ORE+|8LX6Gk zupYI>dqa@cGRraseJbUyM6$1(TDw$4dPSpCxacxI`c1+0#!za(VQ->SLwZvbJS4z+ z`|Gkk+B+Ggny(2#yEP!ULX*TfS5e|Tr5gjyY5IgeefH8!h`!FBS4&KNU4ADjN4F01 z`WCgnD??%)RZfywO)n-RM^>6cB+f4#FE@Gf>g20hbtDyheuy6y25MjI7vetCOD&MF z=tp$d>68N@y78cSBNb0>bJMh%Gr^@|dwiJfXq^bj=6bfMSi;-oZP={A>YY zpan(>xmVM=1sLstf$X3#zgR1>i3-tR>W4iY zD94@=7P;p*;^Iw^m-3pLwftYYqMb#8>C4w>^$=~&G08H3==)8z1dO|NDm3ZBB3wz@wGa#X*2b?Y3u71|s8!Vp0G!hY8px;4OeMDV&ry6y93{(&o`9g$l&(?A6rLwiC;lDenow5uK+a14>;wB)ptPYZ7}y6MAxfZXVci{zjA>JV;UpI zH%PF1wqYMVrGpCOOeUA$MkO48+(1;!V~a@03!O|M|FMqeF~0sh;UPa#7v)@_4ouL) zP)Du3u=*xxi%nvq8>Wd0>#TGQT0nm zUB0&>K8?fH7f!&BG#6I7=bh8tkH6oWFSoMauf4lApxAw}g%W*s0#KO8aq)vn=!hwW zVFu(8s%;cC86FG)Hs}arqMjs->~K!1NrPB2Nis==79jwC%F_t^oYX-$MHWL@Fx^Bg z{UliZgi1#7I}9|&3H#$dX^I}J18cDA#$RxU=9DEA<}-9MrL(!YJlyq6&gUeQ{%cFB zAD*3z)~D+4W?c!H`kcgb9BN~!yxh0b>+2DN!d{>dLh2(B1)JPp<>f+_#j^|>>8hoS z%45f21D@mKRG$&u2anU}G;ieQ(PFqx&l^dJ`am_vA+mfq&f$c_dS3Hz2K}ldUiQGnOftBudf~O(PWGu5KTT*_6>W+V&t87IFWKo{aZkCM7 z@S*PtgY!?_lXR0pZ7VaV^6xA3u&gN)a4^wxQR>4iH`C{;zGA&}a5$f{*171S;Ml@N zAx17)@9hD0hku{r%up*GPdFZ!Y^O8D+*e-=j`}g9DmAggMVD@abIfTH>akRPnrn7( zkh%oK44L;osJc@?cLEh1WtX)mC9&+YRAP6J3w^1f7$HqtHyu*cx#Id>*w|^nr*ZtE^kAVSqU;}=S`TobN z;NA#QF&_UwQ?N32Y6G!<%XcBT{9S9WpMimcMOdZb+cy6k|DE95hR>je2fQw~(~Ud$ z_+J5d{sN*0?n~F|D^AY)b%x|C>vSPthCqXyvD^v@HSWy=Y{IE!Im6sX{$SeE7M*>d zvo2DPw!4S!X+pW*HFkrFoA{-mzRfK* zYXZnz`q@z73}o)ng@K;o(gHFO?7u7W)etuo%06SSRyTA=jf@Iaq*p5iA) zkG03|UMa+gzp?f=1~@xAfsJPV5c9l(XIT@3GAfKJ^do8)&>wPW5A?qNV3kG7zl^ z!3j}#j)8n;xR2t&sH|^AER;iE?-Pq*yJvl^r}2$?jivge1j5`%emVxdfv1hD%mUI{ zEAm35`duND?qXH^W$@JnoLmDck>#AK6&Lz;OPNsrFe=D*%%%LsidUa+3lDkIZ~_0g zAy>X7YCybXb03`8@ijf96#1kE_b8_L(^9d`=8Y=9D5PZ>exhq89+(9{axxoJp+;*a z?p6f1Rmn$)DvuMocY}5KmL#P(T{o%l0Yu}i@h{YTfuR9pzu4pDnysai5Upj+4>n{) zG8smjSNjS(P%p>Zj&GAT&ClkL<+CU{U2j?xehQw(DsYV_lMUFgmaP*}wZA7_WK;1?=fY^E zaPs`Q^EyL0;N_ti@_`V({K@+a@x@J80 zvM>0^8H1{Ks*jBK2NHX6YL3-koGQ2HLI3L9!!e4C6ZUzYUVn|Ne`!X5p13!F9?#<} zhi8GWE!(13{#y#p@9_QB1xU#HNKjQ`8>h=IILVd$yqKbG;(K!w@GPnsH`v^_ZL$2j7TrtDZIn}H4xiZ5~mmPa6b(pJq z?SE6-C?o-q)?}5-XzVq3BsJ3`&GGCve|^#7-*OP}ckEUacao3|ErbGz;jhprl+pT$ zQOI-Y;KRi5v7q8GW!w7re8h(8s1OT?5DjIU_C)8kiXVP;eV&3{?37WQi3w~I==7Vm zgjMxhdW$Kx#y;>YmM*4hdZ*9|tDae&i5>b>q*2tQz2eu8b2?q{G4|l5-lIY6b`9J*r^BSi2X4ugW4gxa! z0|bQS|IcBq>SAg0-`ab!s;u*(8anT6LiLauDr!dDN?O9g55J%^?2H7iASL5~$iz9W zahb_Fg(Gpgi!lOCv$Guj`5J7tZ4<#x=<`vRl1#S}zk}c<(rw5$Cc`83SQ&o6kAqy`dP&n-qIWPteCzx ztI5q7ZQDt^v99fnb<1|ZjjQEpW=iLFbBk%u%0-9ko57%aMs|U~lAY8fMw*=Zt>#NL z5rh5%@8~vznVuUAbf|hT6L(MP+#}3<#)4`(UW&Lv{3f~UU_(!z!gT8uqk5GaNLB*2 z1kBLo%y#EOVvQ_=^|^W@v~-fL>nVnQ&2}hwKJHH*qPvZ9n9g6NDPZ%-gli4g+)=Co z427WwNc5P=%7oX|%tP6w7RT>vy#3y*vBaDyHbh2s~T5Zpv0-2yEA?q%4iF zNhxabZ4S6n5Vn{U`ze3eWt15W57DeZ#B^)u0{#ip%4oTU10tB5;H+quYshI|e$EfM zSJv7uMbj&0dg+Cf7yeQB&v711S4(6iV#hSjXXjEOu!Q_mdIxBe&#DhUvgB0X{mViwn$KoeB z12V~zcqqp0V4WY)>jXYv`NLM3W{JDek=rEVB)cNfqExPK_hh?@B-;&RZ#%fLv*lh`J5bp`(7$#ZENc912XI`Qo@fLKpA*cA7E zffQv27bqRH5c;^NDQpgx zLS0z8fS_qMT%LT~1YIr-9qk_kx zCl2E{M`>T|1FVXU!J#Mt1JFS;q=YC8?x)cwAT?fLE?imfjAxcs!bngMR=uljB88=p zuAz26c)UI`i;wis6h{+>6UP(A$y;#p#6`jiECqV?U*hzE~X^s=k&83`Zay^PyrL3el=`-faVhar)!4F9-7R+y{XzgTA zpJ{@e=gI>iIXhx zMcxE^(rP?VFVqg7vy8CBdfMMz&lUqJUV<@-`Ea=9Dn}_3Uo17_u1DMzv(~|nQei)B zF&i9xc!paKTBWTRjmN0U+Cm60y4Oa9HHHt*;vIE|QBx$-%c9g(w)zY)YVRG;IFET!4|;!45ZSsPp@=T{22)m9(s3M*5|qq?)>kj;ux)=m$JB` z=_A;7f(MXmtAr~8Zp?Cn80OerO*d&|<(rsp&yCD2`mIO-gAiLzl$Z>w@9A1wKlIe(EuyiZzq z34jv7!XbNQN>XmI?3DB&S)P(^yrr%A7%{^pHb>J@A~(0Wa&L72?6!xl|Ljo|4*y~> zE{fSL+hdm|qB9f^)^7`XM&-INeTy>Ur~Ij}GQP84bc0-u7r}f&)+x#M7!c7V&HZ8#s-x&;M@iIVAf$_!Uqd5Q)u+)6CMfOiC>UXS zvS+C1ylXgEsxauYYa|SV7wN71GS8_#QnGh~Nyym<+F zi#z)kIPxXioK>wr;3f+VKUxeRbG&O>9dDrs*^H>tM+?b^9lU;m_amOt58t;Rx^r*% z0`lWDggnlQbZv%NP~F_6IxM#)@mFd^Y+9zxa&B7)KzMPs1&v-D{qF#)G@y0PG(34Wq(3Y2oN#xpeCdT?GuCktDXh- zPXO`E6CmD_u}knbp_q};vXIVIv2ISOs9un=?8i*T1gA$s$EuzrtSPo`cR6SLIyQcL zFYc|8hsBw-a@TO#Qt^)O1n>$@IW>!}=GI(d#q zr*6N7;O)8`M*ZRbX+xG^CY9&3s(2_!iXS;LD1~L$Y?>&ro>K$J=GE4AYkq0A+uc~E zSFp>BEiEA3PqC=j{OR0swXxyZ`^N%>$})O1ftoaB!#pY~6VV9+`R>`RrPayJ4*Bb+-x!og*&C_6ckk5nFUkU7S&;Cn$7EP1M-z^wpv_{fzltTn2`i zfLZJrC%nvCU5cI+-!$t&7dWJ_hW(3V(2N6>5I~+*w?%~-12pk6oq|SDUzYQ;X>Zr} zq3p_caHm0uM$ml6nP^+YW^FV%!ksr6lWCqD1gaXi+3FIL-U@uSL{~l=NN)=|Q5Jk| z3i5r(7s=$5NYFHclA6Y?-Xx)Lt{6sXLb(~2ViZkJL7;rGu%d>WqLZ3M6;MqK$KwLf zr~qsU-Swcq;-iMp%o3UfZl4cPoar#gg@X)Ag$<PDb~hqb^;?})aUbzdD$jS17pOh78PTEu=f-n(&>@*dbw5!tVKI> z>5bxX%@}9Kz`a)KKpU=iD=;dsU#m6(DH~e2dZO82PGtco@Mo|8>IdlYd$f@&o16on zU+l%~choHdA(TvRmMn%0xqi-)>2^iXeEivwM6v)sbQ5tkP zu#cIb9~AHqBZ*PyYj|~YXKgmQ29Ys*?dCP1x~lFT-uhzC$F0&hCPJeaGSGD0p=Pe^ z5k;!2Ei0a}Vutcx2PQ)N@1_QJzGeFv5Zs^pDA+1O^2lFvE!l*t12 zLF(SNw$7{r$#-0op)6u8UI-ev~XSy|(h4-6wzQFXdYiW)sBB1ra;`6RuP10v~skvVBA$s`T~Y%#zk5__{=Bg3^=B5OTL5 zz+SaKGBE$r;a~G%AH^f=GqeP-;g z3p?|@g7KEJaDBDxb9JE+x0@jKB#MH#c_O5G+_|SUPHK$h=tRTNnSUVM`;hM#PwN@m zmZaB965Mk=??J7_;$7WarnQ9^zV?xr$F1v+nOvJicfRTfPiAjbqD$AM23b};h@KrJ zCOXp4aZ~bM(s8lg)WdG4KuM0H;;9e)W(p$M!OUF4pmOsW;MK8vlyW;Jj5N6kb?}YC zxxdE5gjTZqTCJOCs9s6?~ z7pyKsqgC}zS8o@AFbldrar2KN<|%OfM(5gKa(a4IF7L7_GJ#96@0E+lfT}s* z$Cdz#3i;_D<9C$GulyG)9PLfSDXRgMEhya)e2v;tKiS~hRp0DxgV8NHYp=j;W2n=-MLjs z%kl&E&dh^Gq@i$%@Ul!_1rQy3=c1Zy+~g6FB$c4%AgU)I3Ghv)KMmA}Q6MikFN&NS z5ojKbRkHjE;C=o?u$;(@$!zy+eI=%;>!q6!ENaAwVdPF=IY}DNlY!luuQ%*7kZp+u z;Sntjmmy0#Ygo?&*o64|LMCAAn0hvCqYVtBCG=V%GmI1IWW?6RL$9Lj|0dZI2$KCv zDKt*(*IqG#ug1GQu68wbd12%wr|xbRnwk!Pu1sze=PIqI+x4^W-)A5D;dpwovFSMj z1#8C_Rpzu)6p~5%4>tMBz9Ag+-u8s)aN_5o5x=(iQ+Z*!q}~#lb*`SaUEc4%GX3A0 z4-7n6N<9L&lf^n!N2uhfH0?6uR^--6IyENM39eENC`2<6;%?qA>RPApZW zcrs7X~Bvh#w^g(*V9po!#BSq12yguN zc*~RhqvH&|;Rmw_=ld|vgeW%hq+tZp56{4mAu~1Q5}fQ2I_nP5-Z35+-pa&Q3q+k( zhm69CPs{+$fMDk-nx6Q-VE?(AAsPF{(*L`5qvQWy2p9i#^;UGXcQG|laI&;>G5km2 z`JaoL`hO8FFureX?0I}4~Pm+s-C2uOD$pd!+p64D^uARyh{ zjS>=)0!m9LEg=n3(j_3064Idvf{3L0&3W(jUXO&H`(NujE*G+vXaAnrd(WOdd*+!D zmGa{frfL+jR{0Rp!IFThWy3LaNxkX`NCEZA&xr}z9xOR*xgO;{c#ew0@ z@VF(nmDQ^H-EYhF@m(ykhWkEU^~-PF`tI}z>@c{c@_(CAg4=pfcCKm$S1=_&+eb5G zLRxByspW#;!T0sHLpNP@Z@Pj6dmz@MTX2~{#eqQS4DemDCA|7^=mcjd9h#kMN(saVI*kLMTM#Cvz{?t>FkLQ1Yb zn3W{CT`)_Fetlp=1^xQqh7$r~C1Vmf3mXdOqlqW+lBBVls!!@i$4q;}@c7{qo%to0 zDsE<2_l`%Zt!ioUO;nW^n%|trQZ(o?W#1Gtvf>{!>6W$pXlZrRXjAc-T1GkJL$Xe> z*hKUJu@U=E>Ph%1VmPH@O;Z)wS%tRY_6T!^ozf7fjt zhDr1Cqiogz$&;@GV_Z*ITy>?JJbHPH)bgEr8OGjZbZD?%j~qf<8B?{-(*w1SndV}=~?dL zpNYsM($lYOPRzhSy&G4)-bKXt&e^&Dy><5W)k?4P~DKXcB`@Xtb?Zwolaa2jN6ZhumB~Oz_vSUj|q-W6Dy9|{l4+!pkiTS34 zsYYlrVf`tO$Ee`C_!G78>H%j@>YTgL3kxCr#?kuC1AAzZhJ8DCt$pZvzMuPEL)=%Z zx}|erj-1@q#mJa z?cLqy%5R)sYLassWoVA~W5c004s2>O43}WUcRthYs42A;ei}6WZdV|6WD|-1$!yfM zZ+FdH_=B&nvb>^i(Y;`!+1|`*tGYVoml@fpA0K(f*ja~ZNN>eK1bJf?J zbqUKXEhhiMy}JX1a^(?~)pasOqykyJqcQ6}>nt&e{Mjn3Z=6fNe$C_fYC6AE(ofb^ z$zSBkm%0^j=jG)$_Y1zSZ?DGL-F%VFrhaB_FrWKj<^GqH5_*@-*Nbl-Mw)%-Yts-~ z%q`y#d!zVHk$E$|?O;xI5yQoKsO&(#J9A`bhxA?LiZAUHb#k9zNl=<2r-WdlPp<9c zhIW6#qEZF95?j@c%$JDd=&8>Mok zp&@!XnLD<0W2Gyzg57a)Gc9Dv(;EUxb@QaeFK40x0__d337!zkDEgyV zD0#DKNX5_TT$bxhnJ6JcS&nwqM;N`}vAR{$KxsGCwcTB}O;@C`TsUg)b8%#8Q!86s<#uGFO-hDzOx|OwIQtt>Za~ZIkov9f^w73{zbZ#O1 z5~T*t0v&ET{8^F`{U%tQcj!$gI35 zKXpc;$5lt-!8P4G{tseDK3y=L7`EaYrN~R1*72g3?%uwdgSreCuY9Nz0z*5 zcDi?~h=Nx83B}j38>)ocA@~@GbY984Qa8t}_T#(P_ zJ0I_)n$@x3F37AzXU#dnoATU=Htkv6;1t*U$8|Im?U|xAJ*DZ9X?U|7y zS{PqeM^c!iN|L>@ZvVojTjShh_Mx%mE!Qga%UtIs&qNj8PwT5C^;2=UEIB10r{%kK zhGEVb=aa>mt%E6=GPQ+gHVY949OIw216INroYiV0xoQ?KU#ED{Y)O8*L5SM}{;f9a zJ#4ZKTboZo@VdAKn0dUp7?H-WxJClvaX_xT8*U=XZ}ruJ^Jsm6|#)#Ym>M7Hu)=*U2@Uidsm3N zlVqnR>~Q(DR&G>74-`l|2G2_u#xv>}&HZT)V4_hWK^k3r_PaPP#FzPvQ*Y zYL?Z)ZZa1cM-2=yL~5{7-YRcI_=ttf89cT2C>tYaL24$Z^sENWQ&080u-;6kBw|N+ zOa5_(2UhO!%2%=cvuCGB-an{mqWD}I)_m(~a7=}>NHn-n3w^k_$p?)cFZLr*-%_J? zpj4WEy+cGP`&MMjRn+Zk*|cik==h5_Hikcib<=I+huKD7dd_Wan)v9}qrRna7Nb%= z9lkqVs&}>?nInI6${pYP_L^^&H^gL;qTK>4Mrug8QO&$|MVizu=5EQi%~(C3&!r}t z=ZZf2IXGuu=HrWnyxY?kHj>m*9DzHKv$pg$S%p2zLefmvNGij>y<9dXB<_JeRhLU1 zlq$UQh3)e`mg)qyQoKRrxF7XHu`p?aENP>N_Z?cg?OB#^6KVvs-NwVV?5gT4L`f_N zA2|au0uk1HOj!!9Y~TK(M@SlqFC~BNHA{=+`yt(}n5Ii<&qzabY&f+CR%xQGc-x8Q zi@}Xt4T#t6+CR86m8xx~UBAB8T|HD3eyvB7>hMan0%O#Bc#lP;ei!AbNVY52?brIe zF*KU5a27wuT8iT*a>Ho1X-Tnu%gHqpGoVgEeonieI52l7Vqod|1FJ26&p3;v3NB0L zolHKtdt;kwwKr06(EKdQf?0gZM`Q`Owpo}DuywyY@rbaImLJB`eyI4#vWt_e|AyIf z5{lIOU-UD+P^cWJ41F6!HlNnYox0o=wJQ77Xx~{PLm}EK!iAtzRGT@Rbrq)RVJWkh3bvEzgyMsenS+@ETppK7`!hl ztFO^ya?Nz9K6bK(0*}IMy5#Qin`T{X^42<$y&!%MJ9)t#_6hG0Dif6(YMyE?lkv{C}Yax1`l9E6=BV$Ul>=@r6j{Z%A_i7D%R_n~=;>9NeATHNK$-ZSo~^*u1NpH+E#2UOQ~)+3WkZ{r;UD zT;+TQAt`n|>x?-5961^GRyt8otNl~to=+mpO(U(;i<2e^ zPn0-ws5{&_ER4(U1~1_$NVwRFAMhDYxY|xLF=c}NJb|AVn@Q~)YsaUL0 ze&SWslGk-P16}xCrHoxB20@&tq?C7X1D%tS(fm9r$5FJX+vIR!Q7&tuaD$PR~@v9Vmot9bl%^@e16gNiX9rZKM9^@09Ye_ zf@0{z^3?QoP~u{qex8I%Q)Y&?PSssfZhY?mJACh8ZPicm?-z9t8I4j_g{PkRTRch6 z!SjxqKvUd+U&rchQw>xW?1j_hM-rMtxxjTwUfV#R`q#lPybGUnYgvLvT{n5;4bW|Gz@wCI&GGvBaN9Km$r_mT+SGE=+GZO`!yQQ;b z{bmd(YJerXa_6+2HJ+BqvY2D1H ze}13{C8FF;q`FkYywPJs*I$;sLin+m1%6iI^D{bfvHFc;m$WUMCt9hk16J@V;d}z? zWXh=a$!;Jo-g)Ov*t@xrW?yo(Uy+ml-uu)LJ-TL2yf->`y=dmF7)8iKIq+vo^0ItX z5~^iFH@IY?eK`WNxcZC7RIqze#~N%ZuHVKZnRqvY;lF(`?g=jE?s+SRkOraWomI9m z?}Sl3H_vQGl9N>$e54VkZy|Vk%ayG4y}$=*!noMsodMb9@^k{PXYqaJCH*G0AIf}8 z%qs-m>V4VeVQu4%C|+RoU=3UpFG%pKom`5Fj+3zOu%T&+kw*wEKHC-yU!I@jRgiqX z(EKtkD;dLum*27<1-mm#BL$0x_(Y}O74K9RY%ew2z&w~vXw9E)5g)2nW-RhC(6bW7 zuBgo<$@`X5+G?r&I3s9iHR;xw`0I+TNs=Z@T1^GfnYfYp+huA~m(tF-VWUTt2tVuT z+G)H&jOQHxosonlcFQKtM)u+u12@9Lr4i1a{%5y&D3Nqx-My&ekcS4`!9mLk!Zkv* zVGlmZs4m3QT58ZRYtZ_P(nxXVp0_m&c`NoXwUxuRDc!BecOugxm}>p?NAlZk@ceC} zhklaoksZ58*T?tXF$Y%0q!BJ2a>O|EJ-mg#mYrLD?ffXlp{)3raKdwkG!$A{6Ib-h zst*jBcgdcDSFc}R!6hyX_@G;}fIq_8uKQTGJtHW7?1svf-rMCjVs=~84P#r85Fcsf z@jw3Do3Z=u&RD~fuRq_d{Su5TSGM&Zw1Gue53uM;|GP~!YRa-djVW0g2O6rQ_}?&I zzP!*bL-gVdxZQs93_FeBB_;1nG<_;;R!S^RY-V=Cx@#|IUS4REB&S|d+v2b&v>Dg3 z;kdE&BEc8EkosKOaDOV&64UWu1Kd&U7%hv!2R4*x|BNq%=6g|69LZ}0H{vyB4( zWjy|m?NtIaFIe9#BkF`a z^?~|y{8I{=m6$D3l-ny!{A8~4l8)a>&UthPq|KC*nq@BJyzaS}caTkLb|q94rSHZo zyRKOqgpV}4iT-Z*bvZLW2o&deCf<`KQ{Z6rp4~&=)OGrx$)#`{%D7xg93eQ&7jElZyUUUHC*2x+$A^c9>l-QxvudB%7`bjeun@Bn33^kM z%;8eVY8X@_Pgzx(ukvC5D{AE|0%=4-1MTqildF~HcP~cF8!(RNT|wKkM!m4a6i`iI z8-8`)viuBVolG%u$HBq9I43d8(N^cOpk$e1gY~7SbO^~<-JyZKQbBm{Emh3nejDCetp8eAA!3~|~BMRkvYuN;E2Opzr&az=9w6=CDvT5FW$i)^t zM}gqahtS?;USks}{#kh6b`8Bvr34(&%2I&Y$-YS5tl!6uRLX|T+KWi|uJD|?eDegA ztbE+9nj5iAObOw;T|ld^~n6>ie1g&FQ5b>=_q0RdgqtMk1lym5V(X}GI(a|VS8Ze7 zkn!uzBd-)1mg~tJxVGHNGZnKmCdgbM~viJ`t{`SX-L$1Fr=P zT(#GnxLq&EMkT1KQR%*gqn_Q$87eIbWBR?~dgcH3UKO+595udr|MPcoD+^u`Nx8Haa(l_^p zh`b}vWxL59%i_-+IADZrOF!XW$LiC_C^d4mCFr6+S(<{Uq|*+8K;qE;!dik~Oa*<` z)B<<1%;f-9)A`4=F8a0C)N|%-LM(5bWo#&-&=R4;lf{bGPqKc!fA5t|``|a@@1t@r zXOViR?DY{W9Wd1$IpLowBKZv;s&+P!fSp`5&j~+GSM4s(O?dV#p3k`W^i8MP7x^Mo z8l&rrV=r^?y|41n`(0x7PuYt5^p#8?#gAo5d;?CyQVX4Qp!w$WG2>cI1-ZOcN$sIwS6|eg2q9_jq zQJ~f8`3G?`+2Zk*7iucx(*~D$B6csOIe8TJEa$6vcatWlcQO;0m=)q|(6-p8iRCG5 z?kfmyB3eDPr0wAg#vgvWr?ZQ!!O-guvv3&9?D;-3iu@U#E@;C7s1)^f+bBO24sNVAi9rBa6vRb!< zjopiXM#wyme0xtYKx6Z)P2{{wSf;M$co<*a1R^)5YPr)x$vc=vsO&-+JC9aokZTig zkp}KDU83s{Jx@E;fpop20ub3gejCI zy0I>$jk-pOK&-a);%A>_Jn3RJ2yGV5O!Wxpl*^qffG@=~HCiA4^df6;jDSY3p{SXZ zAAxgaTibh7!+kkT#Ae1kl1A?~_FhF~Nf!Hx6HS2Na^HNM<&8&1cnI2IbtaM6c)A$s zQtM8gU*Dv+)eE6}yfM5o`c?fbfg``1m|4g>x$b_pW~W2(4T3{=QZ@Hrf{!r~JDTtx z2fy$aUZ*q@4PQRHpF1685VnJET#1n->)mCD>k*~N;OdyQrAqdB-0+bQ6SCLcN*DEO zR~bxAnk5-b1KX^L@kU2GFK5h=n1)QDxGw4i&eL5W@m@<$VvxLzG*(gGUUY@KOu!vm zT=b2!TzWYxdcO4 z%@|lzMK#!G@g`JaQ^$Dc`JJ@)dtc!<1Dkn;WRogd>Nl$lL~X8H;|6a~HC&h3tdkFi>b1*uwQpwL zAZ^60DSGUW+;L6qbJ^8&Jr1gO4aoa##Gg5F2dZ$snm)dBe`O-1;n7MJme2C{P+iA< zJxo{Byb_W#56@CGv1V)pa;-DDnST7_!_ik?@9>?fo%^<1b;;`xa=5D)g z%X+xKsWzUD=QcB~pspP^PG?3yF88xaN$MM^7t z6=M~MvBSrY^S+LS+C(p$s8Y*dsBG^(pHIJQa}y<+#NDFhF~6&8muU|N&IBxlrrxg* z=q`J|NDaRlGj!t|@}u~P7(P3_joHU_-#!d&1}Q7@UMp{LSa9zb|8UQ0J!J!#`&l?W z2^}K2esad42AzUH;6QN_+A7`j7mrtE6Kpub#exKafh_#Ch`8&hqx_yzZ1ui4UA1r`Sg=VBY9zNV%rChk%z zSS(dd?nXb0Xp0kYm@@39iH0zKwJVPDO$hJwJ~H$c0zE9k2PMFz7|wvdr@5AjGuSy` zCT3#v?>#Df+jgUZh=_=n5d~clnOqTVT@lG85Gx-HEgU{jl|bY~Pb+9G75kdfTIc+A zCg$TOqZ(c8>r87@7`sh0F;}AFBI5 zHJ$$dN7EqzMMYtLw-^MJR`A}&si$fDW%a+CE^23EZ{TR)Z0Gp*3mgyD6b2RX+S4ZR zQ6}yadN`o?=RPA$x890~U|>BTZEW6bzhChHF;u(O`O!dQC-ZIA0ir|w!w(b-lG`=6 z5-BeyPK>{GTCZu`N^!E=nI9dsgL9;jVD-%sHrI?aKzJTBOr&bexxlf&IXP&rr=R=I ztJ10ZUBbHGA~ID^ky|b)dJBK4^I+lVt(?3bUX|jM2nLkT#S@Wh@#ijesj3N0rR(We zTl0;SsryWS`V#(?M8L>ol*3Z3r7Y{hHMQ@ZwkQjpvQ@Jqq(Nr8MY3c*hMfF89Nw!! zlqD06-nF)BYuR$2j5-{ATB^FR6BsB+?a=B?d4l)BU}o&eq@tdkDD0ZAE1P;zaQb?s zY@_=?>^Om9(wnWl78IHi8Z)Q5n^dMuggDY;-d0-ZH}C5#o0?o_Hh#cP)h?2;_6Tc2 zxvA!DqgBe6>l13@o^E!}RT)?%=U(4TeG+PyQ0x{i@#4CG{t#YaHDiByrFEjqevI6$ z=oy|lqR`8s2|-8>{H>S$Hf@RLHARIfhn<$ssej)|Kn_2gSXAL;oGyB2cA3yKU@M}; zA6vtjS2E9b;u_KVN-90efXx7x?It<}oTx`c*ZSsKl>JN7Pac6I*YL)LIKCoWViXkJ zlags-`KUnzzjA1#dF5glw$`RAheQ{Ak;v_KkL|(hO*zI2pP1fgFVI|Y%|`W#_;B`3 zo3Fr|NQ^g*RQOxZ3{gf!mK54QZzV=Oc%7DRERUtMalumf;gWL42btV^4Y;3aY3hU3 z6)$HcVv5m7870DTVzKrw!SQ`wXR|PBR%ZO{a(4Qmsi#`Y;~=U# z1-hcr>o>2h+*-o?5%}QXMnD$;vF@J_Cis_}xT-MY4S5M>8v|Dp+kft$I{Ke49R1Hm zb~ZM4woZ(Y*l2XhS0R<0UpUZTh5qfw1D}Tzsx=Xhmwj&U29_CaGQz>J!6wMw&W6d| z#=4Ef%4ve@Q?1z{g?(xtN0(;i8vjyKe*T5)S~(?)(F-z8+}syJbd;Xm@I~^Cc_{zw zfKllFoQ!cMQ-AE?i7p2e@-XerPO^S>ykjT3~M@qHz~hjt${q#4MAq>XQT-m6-PMPjJ$Yy1x!!P)rgS z=F=*xJcY+*SH5^z)y~ew4tvY2$Ypj`rEZaEAjwk z^NjB%vX9kr=tWfUuBNNtv1QB5v#t`Z+TiKa4z%)*U+J##@_PC{t|ANNjp97rnekA^ zQYQ}T<@pPrmdw2c-%h>tyd${h;Vy9TW0b^`Cp}$z?-I}?7u?-F90hqPbms<%d7mE& z-R5oNe{;pxRfNetUiz{?XlFS3MKLo12bE{s4hE@eC})yO;2zE?NnECkezChm&7gcA zS*1M+B@nf$$}o<_CP1=+Q-r!+HxbSFk`$eILdJKhOcBe9RqtDH45+l>({6?Q_L54v z?s72*SL>g0s$s#CNU}z#GYa%S+6iUd^b-LR;NM32sK-z4WSm zk}~T)k!-aU)3s11ct7fdMV+tD=KZNNP-ljp)sX1!GS>;b{Fd@*`;p<53gnVSCmFYA zRaGCqdv4l>qvDPm=~&>t;-jW^=&~}I{V1kyvyX7tNsWTK!l-*#xigXKmec&mZ+1X| z6|07Pf%jEZX6NfIdtcPhiUbp6&$5q%Jd)A_8|)D8 zK`c*h76^P645~BLd`}an5HiotKu5Rkb>M}5g;!30=KZt-6UH~Ywh|hd*6KW~>LqwZ z-WSz8D+ewKS5So!Vs56hGbI*0wX$*Zf2JK^ghce+l}UTwHoj&lvdd%_u!v0iY~ z?}8QU4l%v~Q8mTdgWL67_6;oMWc9v=Q#LrRn)_*YT3^T3NZG5`o+IdU7+K5rnKH3+ zYO``I=3jo4u2&OF<0XnPg0<<+RMHbNlp+>9?HXsUu}CClAL5j7-k*UpXGmS^Vj-Rb zZ|_6jCbISYhOfh|RI@$kM05eXmCZtCI3!eiMtLdIBkSaW%r7$n=2=@*sTsA#U9DKo zI!SS(lLjO3i+wRB;m=mJGvIVAz zQL)<@tY!&~s4hLvJsv9$7*KC^QkOAv4C>9=7jy{8V|8%tUMrRf))a3-WxQpE{C38i z!0+{_JBPPyVw7REXqZfXDbECDMfKEmcllgu6*B*aou3cSELx?=;b!+%Op~_v@?*8C zE?za`j}XlN%ADJNc^5}vQ>hkXp_y8$8Erhb?6K2D^81!nYVv*kee#ky@f|tttsQ;= z?>r2Px7(NAk}_Hjk@_Vrh#+|m`CnxTXJIXgwq|1Bj$3I^p9TvEWUNTipD4VDKGyHA zH%4g@8Av}geGtq2*$Az0;1#|^f;E}d)33on`sj1*d`^-{ z(=_=torX>x@n@^-s0d8`LzG5{x|3g#g^94{PU7*5Su);mF!G9Z$WGqr$#lK9Dy2o( zW9;*BzjT8K+A#{Ecm4|jmG4+cU{6Fs(K%OTB)u(Q#D$} zGdMOXyuRQ4c9<0{2ET^9ehcNJMh(*9$dmc6DAVa(r7sdtEZEU@ z4sXm^h%4yFY%n0`Ky!YO{+Q%rfXvyb~CrKQvdLO^v4i2JO6|86- zLNUb17m`}G%I6Q8+;?TQAb4{UKOK(sz6 ziAE0Oj_MV%W9Brq*~$|dp4M08j}~T&(9pAf6aOf#{_5i}jf7RN3XCD4t{2?JGG9d~ zr&FRo#<+jQC}(TE-Q{_AvbG`AoCjq&-pyaqYz&pjUXfUg46{>iCP3whZt?!N=V_mN zY`bau> zMs^OTJ${Y;2r~|{q!ZcbN~DT0ZW0WvvV{9lg@u1_1ABJ>#P3$K)zx5 z_csje!M(^Pr+yJ^KKSX2vCv=qd#(y408(4}@fFBV;(vaDkXw%z@MG5eZv%c6LkT73 z;50FY7PbbC9;ZGOv@~9z9f$(|R>zN4{q<1B7G@?+&ZkPd1odogr%AImur+frFoV|l zuWh{oC9mp_D}-GB@!A?cz||aPEd~c z$LnQph>FXrh%-66JO3)dofKa*1QeLpK*{l6pMrFNIntjb$SbG<8NUjElxnT`4>Ll* z`H-m;e^se}v?tRq88q%h^Zo4%A_h(->}-G4EPsA9i0TcZ{DRuSqh)}l749Em!Ou)T z8&D4aGs!n>?OmKfrPRd0=6FbAz~o{lfII~d9w-l;RrZ&K>fk5~Fd@}t^q+y{P zFP(HaXw(X9I_3u5kMAGZ5TCVIgGKspO=5?2pE3cAH)3G$;)eop=)eLx-7Ju9SbJY8 z`U+@LIcO3q6p7m44@mzD`v0@bQH?>wx5X+BHUq1hg2q7i@+7n0ix)LFa8xmIa51qp zGWqEoyPrb|#OTtI?!n>b;o#1JK1d7Ih7!x)L!?aHkAL>>qN1xwz*{9i{DW4_U64cl zu3!AMfOE=IU!uaNEdWo82D%=Ic>jDr0Q5LTyvYAl4z(Qkt3J*ZmQ@uVE7{2Nx00b& z+kTa7$G=Tz4xU#Qn3VpXCx&zYEw57~L+toJxnLplsIDO<)IV&ri2*_{0)0brvmc*K zKUAOJ2zD}Y{`-8xU!Rt6ixnviNZJK|seecWKf&mC=MNX&0%RUWgfD4Q$kVBJy zd@}voe19)f+}+5;-r2&=_NW#hy(JviqXZ&&AM}=A9NGUA;eRJfA}5U^36bVO$w0ZT zK)KLXV+#7cSW!n0`(vUlqw^xAhc96n>Y~!C#re8`}tOH3|MI{B7y) zg{#?GxSt>#q9lv~M6KidKh@AF!cUEWJ${`!KpEUr4y~C()Q@|qELEwbW zm0A}61INSB!rI!x=r~e@b86iJFwX#J6?FQyzYIIc#MaE(!pZzNkSC29i8%mu9`OV2 zRoFqMjwZH7$8y4uOPF^6habdnXcO(M!Ok%=akMe8J+3`6bnt!sz(fqdM9>_GP1rf+ zCWekCZpU%p3)NC40Y?yUprfMA)_>r**qRwQS{&abrB`KH*Z@-w0z7n+GPhwz+1s7u z3M;D2^APWbGzr?f(f45IINAXVozNojeY>el;0&=xyj$$xKagCUoK67!Y$fP!B>W9_ zkdr-V(ed5LqG3EZ3Fs{yxHn|8)sIi6pDWy%lN0!V!s*{(&cq#r)RUrIU^Bcv5nyzJ z97X5{0Q}7KGd>49b504Q;}75}&Vi7V1EvAc9B)b(IDfgC-pO$% z-s~wCCJ3ZsAZ3KExQtQ3z=4E^%rPx;iD?CG5CqjhXg_|>12ejbCJ!9207}aR*$s4f{vZcC=Ctrkd|UENCTNT#)~^jJR)U#ya(Ir( zbGh3A^!DXQ0-CP@Gv>7LY@vgVw+8$m9291t!&$c$?4XmwbLgJOMhmbI@sT+KrasJ^ z6T&m3&SBlXGE@dQy1=iYr#*DWf5;Iru`o7ql(TbmGC8sQ0h!c?#tOcl1NP|ws)MfQ z^MJbWA0=#NCbqw+97N;g%n|`dmxqs;AD)h7wi~g7w6+K zC)ZnId|=0%GXBtnj@&;01}Zw@aQ(ipgH9fR_`k~cLJHafN9rw?0$}HyJOZ6;AIG5o zz;QG&u{ti-$r)UXhNSIsKy9FY`_CuSFB@z+fHfGHK`_<^&K5A1kd{MXXPrE%-rwt% zc>8x>g^r~d5&wa6+OYaF3ObFGjDj6y;cQ@iLcyh<1Gfbp*eLkOICMK2cF@TK_|I+! z?GJAr{1=X+vx}LFiId53zGtqlG590WLB~{_c-UE|Web*isILk^r+suJExD2SA4n&s z3xsF2Q#OF>83R*6`-4IX?4T3!1;`-&vz?$p38}DyoLwBPPAI?$w%tAm2de7^$rkj4 zt1le}(5VGDibZ!a8lbUvM+$I7Phe;Kt^g-oPj~SZun`L&LJzPUIk3}C%uJ$W9S`Jy z;>3aCpfTEcFk?I*vIn11sbCQs^Y@a584;-you2;g0IXNZyxi!$X zp{#)&b5cg4yBdlH>7J~>p`k}_@!J1J`J;pc+C294Fr!Y+M}F2D^f(vS2s`GqbmV6e zv|l$i!Av?O8_^b&R$BlzvOE$(ms()voScmO+#G0ExCnMs{&8~p*VtokcUms;GYUG4 z%7D$MFrrRNMNY0OJ9WbjIyn<5*zLM73QQyfOa$!;882bxoRWzAtT<@St3H@Hr{p2E z3{8qyU?{zCq>i}*qQxH@=(qK2j-rmHU65oCkpEi0)*FU_b5i{}22sG55@ZEcz%0<| zR5qBh{BbjWtezcPzYau%kXFix#sD+W4g1cNrDA1U|dZNSbrF^O92 z-0$oGm_vYp?v}xyVaA+RCVv%_QTGnmg&3ru&|}-vUD!b z+QlDR|1Zm&nB7pv5vXv0nNHi0t`hMbX38nq4FjsAPXy?a<40=o#0Y0$ZOzHq&4{(J z;{|{zK7#p*3^V5B>;{#Q@u~&Dm;$Xq+r|L{cFalHO*yvDd;y@$0t)ny#)A9bC?{q& zTCDjlkV$VPa0Te>h8t97VRW{Wvl}rT&1N0o(|SkNF!)Ge$DEek%r}i1FaeU%kBX^jSD+OGE`bf#&g62P9PRnlig8I#00asW& zQvX0>gc)^OcJm}|AV3?mY3@kq;begwbaHm{Y1#IkAmGS@@`(@SKgZ~dXvS7uWiuLGbQHXdoHuLjNcTvX3oH6=ued7O9vV^Wp%6cKC|ta}6NT zqQk+7K+(iBV5X_q*_iww9<$FM!b5+iJrx2Xe+r1o&~Fml0*ZyrN=ID`LQvrWH&g@t z*??Ao)cK!JrXRP-9}?sZY)n)=>`&+*7nI5!NC2P!_%L)f%K)|t|4|bZAv?mIEsVr% z%`9w9j`KE1f5#5FJn{+j^*!)5mDE4&fOtZh<$nYHk85Kh;$mS97MYLg4?fu$G>v z2qF;VFj43-4rk~eVw6ps>|7j;!2OZ7PR<6lC%yC}Q5=bD0Z4ZN33?CJbl9JgfaZ?# z4v3%Y!asPz21NUV0Tgl=+K*4BUwi~i;#rO=3L>BReA!1v;Nqm9w?aQY@qJk2pAhC; z_fTCy`3H^WO#&fKH0D<8Jq@(=^5a4H~A4A2Vn`f(1{!%-eMUxervb}GQc)}z2pZ>3}d+PcU$P zy8?%K|JK=OfU^Q>d(b&%y+iF-^LS3zrdj@0(6P+-QK(l7P)hv`K?z(hY%bmzi=nR9x-Y)yHHdlR6Z z9jQ?d;=qhLpLc1%hGB1IJ+JJ?ir}K9S|AceeNdpl1+U?Q7tgc^|Y(`UemE%=F8r{XZc8ck0w>-1Pa@4oFk2 zK~rU*xZU8*{r}w5zYd`~4cuysq-_lh7zEz?gPuBAgUkVD@bMEMr)&v3u;vRl09tn) zs&$ND@7wPoVg?rAJq0;CD?4X9Q&SZ?7h7W!_v7Z-5U;s3qrDATWBLpNtQZt5mjA!Q zL>vtuCpjD`Jwb?SlFU=|fM@}TB2Ywj;r~vA6kLwyxmsTE(g5~Kxccj2bc6VR<{d>r zMqS_gnpZYK(=tHO0($wz{^p;cjBWlmQ2n}%95UtB9>JX91fqvQoRx#pT7mALh!(SR zF|;<3GjTSshZNVTe)%2LY+bBRs3~{}n({kdfP+f}Qx|Bbmo@qi=%cEGc*W)HB~x;s zwj82gy#m+oPatvzz~_}AFJ+j4Qo5rZIJxx1_roCi{7h7;Ar01VQNdwB(D_$%)F1JF zX!7{xbZ)fFJprb|$Nu$5CA#E45ijrIWMOg48WBWQuUoHn27!os2LvMMyo)&NPh=~b zn1WRm&AbEsdrS}8?UR3-uyDS$;9TBzPqR(T2ma<-|+DTf5k3=4Uw0;?*4Z~`qhw(d7#l}-_h zX>=!w5j-g#*{>apsr`3iUF@7qj1|GfTh0ccf1dz>SoGI>=znM>)9=~fZy-l+rT=+3 z0bgDL^)nIGo-ybZv@82y~;r~6y z_s9K5{~pKu>(SR>wD{@E@khV>S0(1(`;bl+1(rL167~9bqD~Tg{@5Fmeu&QWBmHos zfzYC$fBXBsq<w+plUe-AQOltBQY5)N)1{4v!5y94$>^o09=0L_lo literal 0 HcmV?d00001 diff --git a/src/org/apache/commons/codec/BinaryDecoder.java b/src/org/apache/commons/codec/BinaryDecoder.java deleted file mode 100644 index 6f7dcd63..00000000 --- a/src/org/apache/commons/codec/BinaryDecoder.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.codec; - -/** - * Defines common decoding methods for byte array decoders. - * - * @version $Id: BinaryDecoder.java 1379145 2012-08-30 21:02:52Z tn $ - */ -public interface BinaryDecoder extends Decoder { - - /** - * Decodes a byte array and returns the results as a byte array. - * - * @param source - * A byte array which has been encoded with the appropriate encoder - * @return a byte array that contains decoded content - * @throws DecoderException - * A decoder exception is thrown if a Decoder encounters a failure condition during the decode process. - */ - byte[] decode(byte[] source) throws DecoderException; -} - diff --git a/src/org/apache/commons/codec/BinaryEncoder.java b/src/org/apache/commons/codec/BinaryEncoder.java deleted file mode 100644 index 75049022..00000000 --- a/src/org/apache/commons/codec/BinaryEncoder.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.codec; - -/** - * Defines common encoding methods for byte array encoders. - * - * @version $Id: BinaryEncoder.java 1379145 2012-08-30 21:02:52Z tn $ - */ -public interface BinaryEncoder extends Encoder { - - /** - * Encodes a byte array and return the encoded data as a byte array. - * - * @param source - * Data to be encoded - * @return A byte array containing the encoded data - * @throws EncoderException - * thrown if the Encoder encounters a failure condition during the encoding process. - */ - byte[] encode(byte[] source) throws EncoderException; -} - diff --git a/src/org/apache/commons/codec/CharEncoding.java b/src/org/apache/commons/codec/CharEncoding.java deleted file mode 100644 index f7b75341..00000000 --- a/src/org/apache/commons/codec/CharEncoding.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.codec; - -/** - * Character encoding names required of every implementation of the Java platform. - * - * From the Java documentation Standard charsets: - *

- * Every implementation of the Java platform is required to support the following character encodings. Consult the - * release documentation for your implementation to see if any other encodings are supported. Consult the release - * documentation for your implementation to see if any other encodings are supported. - *

- * - *
    - *
  • US-ASCII
    - * Seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the Unicode character set.
  • - *
  • ISO-8859-1
    - * ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1.
  • - *
  • UTF-8
    - * Eight-bit Unicode Transformation Format.
  • - *
  • UTF-16BE
    - * Sixteen-bit Unicode Transformation Format, big-endian byte order.
  • - *
  • UTF-16LE
    - * Sixteen-bit Unicode Transformation Format, little-endian byte order.
  • - *
  • UTF-16
    - * Sixteen-bit Unicode Transformation Format, byte order specified by a mandatory initial byte-order mark (either order - * accepted on input, big-endian used on output.)
  • - *
- * - * This perhaps would best belong in the [lang] project. Even if a similar interface is defined in [lang], it is not - * foreseen that [codec] would be made to depend on [lang]. - * - *

- * This class is immutable and thread-safe. - *

- * - * @see Standard charsets - * @since 1.4 - * @version $Id: CharEncoding.java 1563226 2014-01-31 19:38:06Z ggregory $ - */ -public class CharEncoding { - /** - * CharEncodingISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1. - *

- * Every implementation of the Java platform is required to support this character encoding. - * - * @see Standard charsets - */ - public static final String ISO_8859_1 = "ISO-8859-1"; - - /** - * Seven-bit ASCII, also known as ISO646-US, also known as the Basic Latin block of the Unicode character set. - *

- * Every implementation of the Java platform is required to support this character encoding. - * - * @see Standard charsets - */ - public static final String US_ASCII = "US-ASCII"; - - /** - * Sixteen-bit Unicode Transformation Format, The byte order specified by a mandatory initial byte-order mark - * (either order accepted on input, big-endian used on output) - *

- * Every implementation of the Java platform is required to support this character encoding. - * - * @see Standard charsets - */ - public static final String UTF_16 = "UTF-16"; - - /** - * Sixteen-bit Unicode Transformation Format, big-endian byte order. - *

- * Every implementation of the Java platform is required to support this character encoding. - * - * @see Standard charsets - */ - public static final String UTF_16BE = "UTF-16BE"; - - /** - * Sixteen-bit Unicode Transformation Format, little-endian byte order. - *

- * Every implementation of the Java platform is required to support this character encoding. - * - * @see Standard charsets - */ - public static final String UTF_16LE = "UTF-16LE"; - - /** - * Eight-bit Unicode Transformation Format. - *

- * Every implementation of the Java platform is required to support this character encoding. - * - * @see Standard charsets - */ - public static final String UTF_8 = "UTF-8"; -} diff --git a/src/org/apache/commons/codec/Charsets.java b/src/org/apache/commons/codec/Charsets.java deleted file mode 100644 index 359f49ee..00000000 --- a/src/org/apache/commons/codec/Charsets.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.codec; - -import java.nio.charset.Charset; - -/** - * Charsets required of every implementation of the Java platform. - * - * From the Java documentation Standard - * charsets: - *

- * Every implementation of the Java platform is required to support the following character encodings. Consult the - * release documentation for your implementation to see if any other encodings are supported. Consult the release - * documentation for your implementation to see if any other encodings are supported. - *

- * - *
    - *
  • US-ASCII
    - * Seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the Unicode character set.
  • - *
  • ISO-8859-1
    - * ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1.
  • - *
  • UTF-8
    - * Eight-bit Unicode Transformation Format.
  • - *
  • UTF-16BE
    - * Sixteen-bit Unicode Transformation Format, big-endian byte order.
  • - *
  • UTF-16LE
    - * Sixteen-bit Unicode Transformation Format, little-endian byte order.
  • - *
  • UTF-16
    - * Sixteen-bit Unicode Transformation Format, byte order specified by a mandatory initial byte-order mark (either order - * accepted on input, big-endian used on output.)
  • - *
- * - * This perhaps would best belong in the Commons Lang project. Even if a similar class is defined in Commons Lang, it is - * not foreseen that Commons Codec would be made to depend on Commons Lang. - * - *

- * This class is immutable and thread-safe. - *

- * - * @see Standard charsets - * @since 1.7 - * @version $Id: CharEncoding.java 1173287 2011-09-20 18:16:19Z ggregory $ - */ -public class Charsets { - - // - // This class should only contain Charset instances for required encodings. This guarantees that it will load - // correctly and without delay on all Java platforms. - // - - /** - * Returns the given Charset or the default Charset if the given Charset is null. - * - * @param charset - * A charset or null. - * @return the given Charset or the default Charset if the given Charset is null - */ - public static Charset toCharset(final Charset charset) { - return charset == null ? Charset.defaultCharset() : charset; - } - - /** - * Returns a Charset for the named charset. If the name is null, return the default Charset. - * - * @param charset - * The name of the requested charset, may be null. - * @return a Charset for the named charset - * @throws java.nio.charset.UnsupportedCharsetException - * If the named charset is unavailable - */ - public static Charset toCharset(final String charset) { - return charset == null ? Charset.defaultCharset() : Charset.forName(charset); - } - - /** - * CharEncodingISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1. - *

- * Every implementation of the Java platform is required to support this character encoding. - * - * @see Standard charsets - * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} - */ - @Deprecated - public static final Charset ISO_8859_1 = Charset.forName(CharEncoding.ISO_8859_1); - - /** - * Seven-bit ASCII, also known as ISO646-US, also known as the Basic Latin block of the Unicode character set. - *

- * Every implementation of the Java platform is required to support this character encoding. - * - * @see Standard charsets - * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} - */ - @Deprecated - public static final Charset US_ASCII = Charset.forName(CharEncoding.US_ASCII); - - /** - * Sixteen-bit Unicode Transformation Format, The byte order specified by a mandatory initial byte-order mark - * (either order accepted on input, big-endian used on output) - *

- * Every implementation of the Java platform is required to support this character encoding. - * - * @see Standard charsets - * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} - */ - @Deprecated - public static final Charset UTF_16 = Charset.forName(CharEncoding.UTF_16); - - /** - * Sixteen-bit Unicode Transformation Format, big-endian byte order. - *

- * Every implementation of the Java platform is required to support this character encoding. - * - * @see Standard charsets - * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} - */ - @Deprecated - public static final Charset UTF_16BE = Charset.forName(CharEncoding.UTF_16BE); - - /** - * Sixteen-bit Unicode Transformation Format, little-endian byte order. - *

- * Every implementation of the Java platform is required to support this character encoding. - * - * @see Standard charsets - * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} - */ - @Deprecated - public static final Charset UTF_16LE = Charset.forName(CharEncoding.UTF_16LE); - - /** - * Eight-bit Unicode Transformation Format. - *

- * Every implementation of the Java platform is required to support this character encoding. - * - * @see Standard charsets - * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} - */ - @Deprecated - public static final Charset UTF_8 = Charset.forName(CharEncoding.UTF_8); -} diff --git a/src/org/apache/commons/codec/Decoder.java b/src/org/apache/commons/codec/Decoder.java deleted file mode 100644 index 5bf611e4..00000000 --- a/src/org/apache/commons/codec/Decoder.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.codec; - -/** - * Provides the highest level of abstraction for Decoders. - *

- * This is the sister interface of {@link Encoder}. All Decoders implement this common generic interface. - * Allows a user to pass a generic Object to any Decoder implementation in the codec package. - *

- * One of the two interfaces at the center of the codec package. - * - * @version $Id: Decoder.java 1379145 2012-08-30 21:02:52Z tn $ - */ -public interface Decoder { - - /** - * Decodes an "encoded" Object and returns a "decoded" Object. Note that the implementation of this interface will - * try to cast the Object parameter to the specific type expected by a particular Decoder implementation. If a - * {@link ClassCastException} occurs this decode method will throw a DecoderException. - * - * @param source - * the object to decode - * @return a 'decoded" object - * @throws DecoderException - * a decoder exception can be thrown for any number of reasons. Some good candidates are that the - * parameter passed to this method is null, a param cannot be cast to the appropriate type for a - * specific encoder. - */ - Object decode(Object source) throws DecoderException; -} - diff --git a/src/org/apache/commons/codec/DecoderException.java b/src/org/apache/commons/codec/DecoderException.java deleted file mode 100644 index a35fbed2..00000000 --- a/src/org/apache/commons/codec/DecoderException.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.codec; - -/** - * Thrown when there is a failure condition during the decoding process. This exception is thrown when a {@link Decoder} - * encounters a decoding specific exception such as invalid data, or characters outside of the expected range. - * - * @version $Id: DecoderException.java 1619948 2014-08-22 22:53:55Z ggregory $ - */ -public class DecoderException extends Exception { - - /** - * Declares the Serial Version Uid. - * - * @see Always Declare Serial Version Uid - */ - private static final long serialVersionUID = 1L; - - /** - * Constructs a new exception with null as its detail message. The cause is not initialized, and may - * subsequently be initialized by a call to {@link #initCause}. - * - * @since 1.4 - */ - public DecoderException() { - super(); - } - - /** - * Constructs a new exception with the specified detail message. The cause is not initialized, and may subsequently - * be initialized by a call to {@link #initCause}. - * - * @param message - * The detail message which is saved for later retrieval by the {@link #getMessage()} method. - */ - public DecoderException(final String message) { - super(message); - } - - /** - * Constructs a new exception with the specified detail message and cause. - *

- * Note that the detail message associated with cause is not automatically incorporated into this - * exception's detail message. - * - * @param message - * The detail message which is saved for later retrieval by the {@link #getMessage()} method. - * @param cause - * The cause which is saved for later retrieval by the {@link #getCause()} method. A null - * value is permitted, and indicates that the cause is nonexistent or unknown. - * @since 1.4 - */ - public DecoderException(final String message, final Throwable cause) { - super(message, cause); - } - - /** - * Constructs a new exception with the specified cause and a detail message of (cause==null ? - * null : cause.toString()) (which typically contains the class and detail message of cause). - * This constructor is useful for exceptions that are little more than wrappers for other throwables. - * - * @param cause - * The cause which is saved for later retrieval by the {@link #getCause()} method. A null - * value is permitted, and indicates that the cause is nonexistent or unknown. - * @since 1.4 - */ - public DecoderException(final Throwable cause) { - super(cause); - } -} diff --git a/src/org/apache/commons/codec/Encoder.java b/src/org/apache/commons/codec/Encoder.java deleted file mode 100644 index 1605a374..00000000 --- a/src/org/apache/commons/codec/Encoder.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.codec; - -/** - * Provides the highest level of abstraction for Encoders. - *

- * This is the sister interface of {@link Decoder}. Every implementation of Encoder provides this - * common generic interface which allows a user to pass a generic Object to any Encoder implementation - * in the codec package. - * - * @version $Id: Encoder.java 1379145 2012-08-30 21:02:52Z tn $ - */ -public interface Encoder { - - /** - * Encodes an "Object" and returns the encoded content as an Object. The Objects here may just be - * byte[] or Strings depending on the implementation used. - * - * @param source - * An object to encode - * @return An "encoded" Object - * @throws EncoderException - * An encoder exception is thrown if the encoder experiences a failure condition during the encoding - * process. - */ - Object encode(Object source) throws EncoderException; -} - diff --git a/src/org/apache/commons/codec/EncoderException.java b/src/org/apache/commons/codec/EncoderException.java deleted file mode 100644 index b05ac09b..00000000 --- a/src/org/apache/commons/codec/EncoderException.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.codec; - -/** - * Thrown when there is a failure condition during the encoding process. This exception is thrown when an - * {@link Encoder} encounters a encoding specific exception such as invalid data, inability to calculate a checksum, - * characters outside of the expected range. - * - * @version $Id: EncoderException.java 1619948 2014-08-22 22:53:55Z ggregory $ - */ -public class EncoderException extends Exception { - - /** - * Declares the Serial Version Uid. - * - * @see Always Declare Serial Version Uid - */ - private static final long serialVersionUID = 1L; - - /** - * Constructs a new exception with null as its detail message. The cause is not initialized, and may - * subsequently be initialized by a call to {@link #initCause}. - * - * @since 1.4 - */ - public EncoderException() { - super(); - } - - /** - * Constructs a new exception with the specified detail message. The cause is not initialized, and may subsequently - * be initialized by a call to {@link #initCause}. - * - * @param message - * a useful message relating to the encoder specific error. - */ - public EncoderException(final String message) { - super(message); - } - - /** - * Constructs a new exception with the specified detail message and cause. - * - *

- * Note that the detail message associated with cause is not automatically incorporated into this - * exception's detail message. - *

- * - * @param message - * The detail message which is saved for later retrieval by the {@link #getMessage()} method. - * @param cause - * The cause which is saved for later retrieval by the {@link #getCause()} method. A null - * value is permitted, and indicates that the cause is nonexistent or unknown. - * @since 1.4 - */ - public EncoderException(final String message, final Throwable cause) { - super(message, cause); - } - - /** - * Constructs a new exception with the specified cause and a detail message of (cause==null ? - * null : cause.toString()) (which typically contains the class and detail message of cause). - * This constructor is useful for exceptions that are little more than wrappers for other throwables. - * - * @param cause - * The cause which is saved for later retrieval by the {@link #getCause()} method. A null - * value is permitted, and indicates that the cause is nonexistent or unknown. - * @since 1.4 - */ - public EncoderException(final Throwable cause) { - super(cause); - } -} diff --git a/src/org/apache/commons/codec/StringDecoder.java b/src/org/apache/commons/codec/StringDecoder.java deleted file mode 100644 index a42ce3a6..00000000 --- a/src/org/apache/commons/codec/StringDecoder.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.codec; - -/** - * Defines common decoding methods for String decoders. - * - * @version $Id: StringDecoder.java 1379145 2012-08-30 21:02:52Z tn $ - */ -public interface StringDecoder extends Decoder { - - /** - * Decodes a String and returns a String. - * - * @param source - * the String to decode - * @return the encoded String - * @throws DecoderException - * thrown if there is an error condition during the Encoding process. - */ - String decode(String source) throws DecoderException; -} - diff --git a/src/org/apache/commons/codec/StringEncoder.java b/src/org/apache/commons/codec/StringEncoder.java deleted file mode 100644 index 9450083c..00000000 --- a/src/org/apache/commons/codec/StringEncoder.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.codec; - -/** - * Defines common encoding methods for String encoders. - * - * @version $Id: StringEncoder.java 1379145 2012-08-30 21:02:52Z tn $ - */ -public interface StringEncoder extends Encoder { - - /** - * Encodes a String and returns a String. - * - * @param source - * the String to encode - * @return the encoded String - * @throws EncoderException - * thrown if there is an error condition during the encoding process. - */ - String encode(String source) throws EncoderException; -} - diff --git a/src/org/apache/commons/codec/StringEncoderComparator.java b/src/org/apache/commons/codec/StringEncoderComparator.java deleted file mode 100644 index ddad57fd..00000000 --- a/src/org/apache/commons/codec/StringEncoderComparator.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.codec; - -import java.util.Comparator; - -/** - * Compares Strings using a {@link StringEncoder}. This comparator is used to sort Strings by an encoding scheme such as - * Soundex, Metaphone, etc. This class can come in handy if one need to sort Strings by an encoded form of a name such - * as Soundex. - * - *

This class is immutable and thread-safe.

- * - * @version $Id: StringEncoderComparator.java 1468177 2013-04-15 18:35:15Z ggregory $ - */ -@SuppressWarnings("rawtypes") -// TODO ought to implement Comparator but that's not possible whilst maintaining binary compatibility. -public class StringEncoderComparator implements Comparator { - - /** - * Internal encoder instance. - */ - private final StringEncoder stringEncoder; - - /** - * Constructs a new instance. - * - * @deprecated Creating an instance without a {@link StringEncoder} leads to a {@link NullPointerException}. Will be - * removed in 2.0. - */ - @Deprecated - public StringEncoderComparator() { - this.stringEncoder = null; // Trying to use this will cause things to break - } - - /** - * Constructs a new instance with the given algorithm. - * - * @param stringEncoder - * the StringEncoder used for comparisons. - */ - public StringEncoderComparator(final StringEncoder stringEncoder) { - this.stringEncoder = stringEncoder; - } - - /** - * Compares two strings based not on the strings themselves, but on an encoding of the two strings using the - * StringEncoder this Comparator was created with. - * - * If an {@link EncoderException} is encountered, return 0. - * - * @param o1 - * the object to compare - * @param o2 - * the object to compare to - * @return the Comparable.compareTo() return code or 0 if an encoding error was caught. - * @see Comparable - */ - @Override - public int compare(final Object o1, final Object o2) { - - int compareCode = 0; - - try { - @SuppressWarnings("unchecked") // May fail with CCE if encode returns something that is not Comparable - // However this was always the case. - final Comparable> s1 = (Comparable>) this.stringEncoder.encode(o1); - final Comparable s2 = (Comparable) this.stringEncoder.encode(o2); - compareCode = s1.compareTo(s2); - } catch (final EncoderException ee) { - compareCode = 0; - } - return compareCode; - } - -} diff --git a/src/org/apache/commons/codec/binary/Base32.java b/src/org/apache/commons/codec/binary/Base32.java deleted file mode 100644 index ff04d1c2..00000000 --- a/src/org/apache/commons/codec/binary/Base32.java +++ /dev/null @@ -1,539 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.codec.binary; - -/** - * Provides Base32 encoding and decoding as defined by RFC 4648. - * - *

- * The class can be parameterized in the following manner with various constructors: - *

- *
    - *
  • Whether to use the "base32hex" variant instead of the default "base32"
  • - *
  • Line length: Default 76. Line length that aren't multiples of 8 will still essentially end up being multiples of - * 8 in the encoded data. - *
  • Line separator: Default is CRLF ("\r\n")
  • - *
- *

- * This class operates directly on byte streams, and not character streams. - *

- *

- * This class is thread-safe. - *

- * - * @see RFC 4648 - * - * @since 1.5 - * @version $Id: Base32.java 1619949 2014-08-22 22:56:08Z ggregory $ - */ -public class Base32 extends BaseNCodec { - - /** - * BASE32 characters are 5 bits in length. - * They are formed by taking a block of five octets to form a 40-bit string, - * which is converted into eight BASE32 characters. - */ - private static final int BITS_PER_ENCODED_BYTE = 5; - private static final int BYTES_PER_ENCODED_BLOCK = 8; - private static final int BYTES_PER_UNENCODED_BLOCK = 5; - - /** - * Chunk separator per RFC 2045 section 2.1. - * - * @see RFC 2045 section 2.1 - */ - private static final byte[] CHUNK_SEPARATOR = {'\r', '\n'}; - - /** - * This array is a lookup table that translates Unicode characters drawn from the "Base32 Alphabet" (as specified - * in Table 3 of RFC 4648) into their 5-bit positive integer equivalents. Characters that are not in the Base32 - * alphabet but fall within the bounds of the array are translated to -1. - */ - private static final byte[] DECODE_TABLE = { - // 0 1 2 3 4 5 6 7 8 9 A B C D E F - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20-2f - -1, -1, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, // 30-3f 2-7 - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 40-4f A-N - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // 50-5a O-Z - }; - - /** - * This array is a lookup table that translates 5-bit positive integer index values into their "Base32 Alphabet" - * equivalents as specified in Table 3 of RFC 4648. - */ - private static final byte[] ENCODE_TABLE = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - '2', '3', '4', '5', '6', '7', - }; - - /** - * This array is a lookup table that translates Unicode characters drawn from the "Base32 |Hex Alphabet" (as - * specified in Table 3 of RFC 4648) into their 5-bit positive integer equivalents. Characters that are not in the - * Base32 Hex alphabet but fall within the bounds of the array are translated to -1. - */ - private static final byte[] HEX_DECODE_TABLE = { - // 0 1 2 3 4 5 6 7 8 9 A B C D E F - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20-2f - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, // 30-3f 2-7 - -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 40-4f A-N - 25, 26, 27, 28, 29, 30, 31, 32, // 50-57 O-V - }; - - /** - * This array is a lookup table that translates 5-bit positive integer index values into their - * "Base32 Hex Alphabet" equivalents as specified in Table 3 of RFC 4648. - */ - private static final byte[] HEX_ENCODE_TABLE = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', - }; - - /** Mask used to extract 5 bits, used when encoding Base32 bytes */ - private static final int MASK_5BITS = 0x1f; - - // The static final fields above are used for the original static byte[] methods on Base32. - // The private member fields below are used with the new streaming approach, which requires - // some state be preserved between calls of encode() and decode(). - - /** - * Place holder for the bytes we're dealing with for our based logic. - * Bitwise operations store and extract the encoding or decoding from this variable. - */ - - /** - * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. - * decodeSize = {@link #BYTES_PER_ENCODED_BLOCK} - 1 + lineSeparator.length; - */ - private final int decodeSize; - - /** - * Decode table to use. - */ - private final byte[] decodeTable; - - /** - * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. - * encodeSize = {@link #BYTES_PER_ENCODED_BLOCK} + lineSeparator.length; - */ - private final int encodeSize; - - /** - * Encode table to use. - */ - private final byte[] encodeTable; - - /** - * Line separator for encoding. Not used when decoding. Only used if lineLength > 0. - */ - private final byte[] lineSeparator; - - /** - * Creates a Base32 codec used for decoding and encoding. - *

- * When encoding the line length is 0 (no chunking). - *

- * - */ - public Base32() { - this(false); - } - - /** - * Creates a Base32 codec used for decoding and encoding. - *

- * When encoding the line length is 0 (no chunking). - *

- * @param pad byte used as padding byte. - */ - public Base32(final byte pad) { - this(false, pad); - } - - /** - * Creates a Base32 codec used for decoding and encoding. - *

- * When encoding the line length is 0 (no chunking). - *

- * @param useHex if {@code true} then use Base32 Hex alphabet - */ - public Base32(final boolean useHex) { - this(0, null, useHex, PAD_DEFAULT); - } - - /** - * Creates a Base32 codec used for decoding and encoding. - *

- * When encoding the line length is 0 (no chunking). - *

- * @param useHex if {@code true} then use Base32 Hex alphabet - * @param pad byte used as padding byte. - */ - public Base32(final boolean useHex, final byte pad) { - this(0, null, useHex, pad); - } - - /** - * Creates a Base32 codec used for decoding and encoding. - *

- * When encoding the line length is given in the constructor, the line separator is CRLF. - *

- * - * @param lineLength - * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of - * 8). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when - * decoding. - */ - public Base32(final int lineLength) { - this(lineLength, CHUNK_SEPARATOR); - } - - /** - * Creates a Base32 codec used for decoding and encoding. - *

- * When encoding the line length and line separator are given in the constructor. - *

- *

- * Line lengths that aren't multiples of 8 will still essentially end up being multiples of 8 in the encoded data. - *

- * - * @param lineLength - * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of - * 8). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when - * decoding. - * @param lineSeparator - * Each line of encoded data will end with this sequence of bytes. - * @throws IllegalArgumentException - * The provided lineSeparator included some Base32 characters. That's not going to work! - */ - public Base32(final int lineLength, final byte[] lineSeparator) { - this(lineLength, lineSeparator, false, PAD_DEFAULT); - } - - /** - * Creates a Base32 / Base32 Hex codec used for decoding and encoding. - *

- * When encoding the line length and line separator are given in the constructor. - *

- *

- * Line lengths that aren't multiples of 8 will still essentially end up being multiples of 8 in the encoded data. - *

- * - * @param lineLength - * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of - * 8). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when - * decoding. - * @param lineSeparator - * Each line of encoded data will end with this sequence of bytes. - * @param useHex - * if {@code true}, then use Base32 Hex alphabet, otherwise use Base32 alphabet - * @throws IllegalArgumentException - * The provided lineSeparator included some Base32 characters. That's not going to work! Or the - * lineLength > 0 and lineSeparator is null. - */ - public Base32(final int lineLength, final byte[] lineSeparator, final boolean useHex) { - this(lineLength, lineSeparator, useHex, PAD_DEFAULT); - } - - /** - * Creates a Base32 / Base32 Hex codec used for decoding and encoding. - *

- * When encoding the line length and line separator are given in the constructor. - *

- *

- * Line lengths that aren't multiples of 8 will still essentially end up being multiples of 8 in the encoded data. - *

- * - * @param lineLength - * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of - * 8). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when - * decoding. - * @param lineSeparator - * Each line of encoded data will end with this sequence of bytes. - * @param useHex - * if {@code true}, then use Base32 Hex alphabet, otherwise use Base32 alphabet - * @param pad byte used as padding byte. - * @throws IllegalArgumentException - * The provided lineSeparator included some Base32 characters. That's not going to work! Or the - * lineLength > 0 and lineSeparator is null. - */ - public Base32(final int lineLength, final byte[] lineSeparator, final boolean useHex, final byte pad) { - super(BYTES_PER_UNENCODED_BLOCK, BYTES_PER_ENCODED_BLOCK, lineLength, - lineSeparator == null ? 0 : lineSeparator.length, pad); - if (useHex) { - this.encodeTable = HEX_ENCODE_TABLE; - this.decodeTable = HEX_DECODE_TABLE; - } else { - this.encodeTable = ENCODE_TABLE; - this.decodeTable = DECODE_TABLE; - } - if (lineLength > 0) { - if (lineSeparator == null) { - throw new IllegalArgumentException("lineLength " + lineLength + " > 0, but lineSeparator is null"); - } - // Must be done after initializing the tables - if (containsAlphabetOrPad(lineSeparator)) { - final String sep = StringUtils.newStringUtf8(lineSeparator); - throw new IllegalArgumentException("lineSeparator must not contain Base32 characters: [" + sep + "]"); - } - this.encodeSize = BYTES_PER_ENCODED_BLOCK + lineSeparator.length; - this.lineSeparator = new byte[lineSeparator.length]; - System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length); - } else { - this.encodeSize = BYTES_PER_ENCODED_BLOCK; - this.lineSeparator = null; - } - this.decodeSize = this.encodeSize - 1; - - if (isInAlphabet(pad) || isWhiteSpace(pad)) { - throw new IllegalArgumentException("pad must not be in alphabet or whitespace"); - } - } - - /** - *

- * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once - * with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1" - * call is not necessary when decoding, but it doesn't hurt, either. - *

- *

- * Ignores all non-Base32 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are - * silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in, - * garbage-out philosophy: it will not check the provided data for validity. - *

- * - * @param in - * byte[] array of ascii data to Base32 decode. - * @param inPos - * Position to start reading data from. - * @param inAvail - * Amount of bytes available from input for encoding. - * @param context the context to be used - * - * Output is written to {@link Context#buffer} as 8-bit octets, using {@link Context#pos} as the buffer position - */ - @Override - void decode(final byte[] in, int inPos, final int inAvail, final Context context) { - // package protected for access from I/O streams - - if (context.eof) { - return; - } - if (inAvail < 0) { - context.eof = true; - } - for (int i = 0; i < inAvail; i++) { - final byte b = in[inPos++]; - if (b == pad) { - // We're done. - context.eof = true; - break; - } else { - final byte[] buffer = ensureBufferSize(decodeSize, context); - if (b >= 0 && b < this.decodeTable.length) { - final int result = this.decodeTable[b]; - if (result >= 0) { - context.modulus = (context.modulus+1) % BYTES_PER_ENCODED_BLOCK; - // collect decoded bytes - context.lbitWorkArea = (context.lbitWorkArea << BITS_PER_ENCODED_BYTE) + result; - if (context.modulus == 0) { // we can output the 5 bytes - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 32) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 24) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 16) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 8) & MASK_8BITS); - buffer[context.pos++] = (byte) (context.lbitWorkArea & MASK_8BITS); - } - } - } - } - } - - // Two forms of EOF as far as Base32 decoder is concerned: actual - // EOF (-1) and first time '=' character is encountered in stream. - // This approach makes the '=' padding characters completely optional. - if (context.eof && context.modulus >= 2) { // if modulus < 2, nothing to do - final byte[] buffer = ensureBufferSize(decodeSize, context); - - // we ignore partial bytes, i.e. only multiples of 8 count - switch (context.modulus) { - case 2 : // 10 bits, drop 2 and output one byte - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 2) & MASK_8BITS); - break; - case 3 : // 15 bits, drop 7 and output 1 byte - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 7) & MASK_8BITS); - break; - case 4 : // 20 bits = 2*8 + 4 - context.lbitWorkArea = context.lbitWorkArea >> 4; // drop 4 bits - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 8) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.lbitWorkArea) & MASK_8BITS); - break; - case 5 : // 25bits = 3*8 + 1 - context.lbitWorkArea = context.lbitWorkArea >> 1; - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 16) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 8) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.lbitWorkArea) & MASK_8BITS); - break; - case 6 : // 30bits = 3*8 + 6 - context.lbitWorkArea = context.lbitWorkArea >> 6; - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 16) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 8) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.lbitWorkArea) & MASK_8BITS); - break; - case 7 : // 35 = 4*8 +3 - context.lbitWorkArea = context.lbitWorkArea >> 3; - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 24) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 16) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 8) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.lbitWorkArea) & MASK_8BITS); - break; - default: - // modulus can be 0-7, and we excluded 0,1 already - throw new IllegalStateException("Impossible modulus "+context.modulus); - } - } - } - - /** - *

- * Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least twice: once with - * the data to encode, and once with inAvail set to "-1" to alert encoder that EOF has been reached, so flush last - * remaining bytes (if not multiple of 5). - *

- * - * @param in - * byte[] array of binary data to Base32 encode. - * @param inPos - * Position to start reading data from. - * @param inAvail - * Amount of bytes available from input for encoding. - * @param context the context to be used - */ - @Override - void encode(final byte[] in, int inPos, final int inAvail, final Context context) { - // package protected for access from I/O streams - - if (context.eof) { - return; - } - // inAvail < 0 is how we're informed of EOF in the underlying data we're - // encoding. - if (inAvail < 0) { - context.eof = true; - if (0 == context.modulus && lineLength == 0) { - return; // no leftovers to process and not using chunking - } - final byte[] buffer = ensureBufferSize(encodeSize, context); - final int savedPos = context.pos; - switch (context.modulus) { // % 5 - case 0 : - break; - case 1 : // Only 1 octet; take top 5 bits then remainder - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 3) & MASK_5BITS]; // 8-1*5 = 3 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea << 2) & MASK_5BITS]; // 5-3=2 - buffer[context.pos++] = pad; - buffer[context.pos++] = pad; - buffer[context.pos++] = pad; - buffer[context.pos++] = pad; - buffer[context.pos++] = pad; - buffer[context.pos++] = pad; - break; - case 2 : // 2 octets = 16 bits to use - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 11) & MASK_5BITS]; // 16-1*5 = 11 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 6) & MASK_5BITS]; // 16-2*5 = 6 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 1) & MASK_5BITS]; // 16-3*5 = 1 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea << 4) & MASK_5BITS]; // 5-1 = 4 - buffer[context.pos++] = pad; - buffer[context.pos++] = pad; - buffer[context.pos++] = pad; - buffer[context.pos++] = pad; - break; - case 3 : // 3 octets = 24 bits to use - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 19) & MASK_5BITS]; // 24-1*5 = 19 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 14) & MASK_5BITS]; // 24-2*5 = 14 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 9) & MASK_5BITS]; // 24-3*5 = 9 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 4) & MASK_5BITS]; // 24-4*5 = 4 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea << 1) & MASK_5BITS]; // 5-4 = 1 - buffer[context.pos++] = pad; - buffer[context.pos++] = pad; - buffer[context.pos++] = pad; - break; - case 4 : // 4 octets = 32 bits to use - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 27) & MASK_5BITS]; // 32-1*5 = 27 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 22) & MASK_5BITS]; // 32-2*5 = 22 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 17) & MASK_5BITS]; // 32-3*5 = 17 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 12) & MASK_5BITS]; // 32-4*5 = 12 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 7) & MASK_5BITS]; // 32-5*5 = 7 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 2) & MASK_5BITS]; // 32-6*5 = 2 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea << 3) & MASK_5BITS]; // 5-2 = 3 - buffer[context.pos++] = pad; - break; - default: - throw new IllegalStateException("Impossible modulus "+context.modulus); - } - context.currentLinePos += context.pos - savedPos; // keep track of current line position - // if currentPos == 0 we are at the start of a line, so don't add CRLF - if (lineLength > 0 && context.currentLinePos > 0){ // add chunk separator if required - System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length); - context.pos += lineSeparator.length; - } - } else { - for (int i = 0; i < inAvail; i++) { - final byte[] buffer = ensureBufferSize(encodeSize, context); - context.modulus = (context.modulus+1) % BYTES_PER_UNENCODED_BLOCK; - int b = in[inPos++]; - if (b < 0) { - b += 256; - } - context.lbitWorkArea = (context.lbitWorkArea << 8) + b; // BITS_PER_BYTE - if (0 == context.modulus) { // we have enough bytes to create our output - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 35) & MASK_5BITS]; - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 30) & MASK_5BITS]; - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 25) & MASK_5BITS]; - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 20) & MASK_5BITS]; - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 15) & MASK_5BITS]; - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 10) & MASK_5BITS]; - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 5) & MASK_5BITS]; - buffer[context.pos++] = encodeTable[(int)context.lbitWorkArea & MASK_5BITS]; - context.currentLinePos += BYTES_PER_ENCODED_BLOCK; - if (lineLength > 0 && lineLength <= context.currentLinePos) { - System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length); - context.pos += lineSeparator.length; - context.currentLinePos = 0; - } - } - } - } - } - - /** - * Returns whether or not the {@code octet} is in the Base32 alphabet. - * - * @param octet - * The value to test - * @return {@code true} if the value is defined in the the Base32 alphabet {@code false} otherwise. - */ - @Override - public boolean isInAlphabet(final byte octet) { - return octet >= 0 && octet < decodeTable.length && decodeTable[octet] != -1; - } -} diff --git a/src/org/apache/commons/codec/binary/Base32InputStream.java b/src/org/apache/commons/codec/binary/Base32InputStream.java deleted file mode 100644 index 04369890..00000000 --- a/src/org/apache/commons/codec/binary/Base32InputStream.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.codec.binary; - -import java.io.InputStream; - -/** - * Provides Base32 encoding and decoding in a streaming fashion (unlimited size). When encoding the default lineLength - * is 76 characters and the default lineEnding is CRLF, but these can be overridden by using the appropriate - * constructor. - *

- * The default behaviour of the Base32InputStream is to DECODE, whereas the default behaviour of the Base32OutputStream - * is to ENCODE, but this behaviour can be overridden by using a different constructor. - *

- *

- * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only encode/decode - * character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, UTF-8, etc). - *

- * - * @version $Id: Base32InputStream.java 1586299 2014-04-10 13:50:21Z ggregory $ - * @see RFC 4648 - * @since 1.5 - */ -public class Base32InputStream extends BaseNCodecInputStream { - - /** - * Creates a Base32InputStream such that all data read is Base32-decoded from the original provided InputStream. - * - * @param in - * InputStream to wrap. - */ - public Base32InputStream(final InputStream in) { - this(in, false); - } - - /** - * Creates a Base32InputStream such that all data read is either Base32-encoded or Base32-decoded from the original - * provided InputStream. - * - * @param in - * InputStream to wrap. - * @param doEncode - * true if we should encode all data read from us, false if we should decode. - */ - public Base32InputStream(final InputStream in, final boolean doEncode) { - super(in, new Base32(false), doEncode); - } - - /** - * Creates a Base32InputStream such that all data read is either Base32-encoded or Base32-decoded from the original - * provided InputStream. - * - * @param in - * InputStream to wrap. - * @param doEncode - * true if we should encode all data read from us, false if we should decode. - * @param lineLength - * If doEncode is true, each line of encoded data will contain lineLength characters (rounded down to - * nearest multiple of 4). If lineLength <= 0, the encoded data is not divided into lines. If doEncode - * is false, lineLength is ignored. - * @param lineSeparator - * If doEncode is true, each line of encoded data will be terminated with this byte sequence (e.g. \r\n). - * If lineLength <= 0, the lineSeparator is not used. If doEncode is false lineSeparator is ignored. - */ - public Base32InputStream(final InputStream in, final boolean doEncode, - final int lineLength, final byte[] lineSeparator) { - super(in, new Base32(lineLength, lineSeparator), doEncode); - } - -} diff --git a/src/org/apache/commons/codec/binary/Base32OutputStream.java b/src/org/apache/commons/codec/binary/Base32OutputStream.java deleted file mode 100644 index 2c5318cb..00000000 --- a/src/org/apache/commons/codec/binary/Base32OutputStream.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.codec.binary; - -import java.io.OutputStream; - -/** - * Provides Base32 encoding and decoding in a streaming fashion (unlimited size). When encoding the default lineLength - * is 76 characters and the default lineEnding is CRLF, but these can be overridden by using the appropriate - * constructor. - *

- * The default behaviour of the Base32OutputStream is to ENCODE, whereas the default behaviour of the Base32InputStream - * is to DECODE. But this behaviour can be overridden by using a different constructor. - *

- *

- * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only encode/decode - * character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, UTF-8, etc). - *

- *

- * Note: It is mandatory to close the stream after the last byte has been written to it, otherwise the - * final padding will be omitted and the resulting data will be incomplete/inconsistent. - *

- * - * @version $Id: Base32OutputStream.java 1635952 2014-11-01 14:19:04Z tn $ - * @see RFC 4648 - * @since 1.5 - */ -public class Base32OutputStream extends BaseNCodecOutputStream { - - /** - * Creates a Base32OutputStream such that all data written is Base32-encoded to the original provided OutputStream. - * - * @param out - * OutputStream to wrap. - */ - public Base32OutputStream(final OutputStream out) { - this(out, true); - } - - /** - * Creates a Base32OutputStream such that all data written is either Base32-encoded or Base32-decoded to the - * original provided OutputStream. - * - * @param out - * OutputStream to wrap. - * @param doEncode - * true if we should encode all data written to us, false if we should decode. - */ - public Base32OutputStream(final OutputStream out, final boolean doEncode) { - super(out, new Base32(false), doEncode); - } - - /** - * Creates a Base32OutputStream such that all data written is either Base32-encoded or Base32-decoded to the - * original provided OutputStream. - * - * @param out - * OutputStream to wrap. - * @param doEncode - * true if we should encode all data written to us, false if we should decode. - * @param lineLength - * If doEncode is true, each line of encoded data will contain lineLength characters (rounded down to - * nearest multiple of 4). If lineLength <= 0, the encoded data is not divided into lines. If doEncode - * is false, lineLength is ignored. - * @param lineSeparator - * If doEncode is true, each line of encoded data will be terminated with this byte sequence (e.g. \r\n). - * If lineLength <= 0, the lineSeparator is not used. If doEncode is false lineSeparator is ignored. - */ - public Base32OutputStream(final OutputStream out, final boolean doEncode, - final int lineLength, final byte[] lineSeparator) { - super(out, new Base32(lineLength, lineSeparator), doEncode); - } - -} diff --git a/src/org/apache/commons/codec/binary/Base64.java b/src/org/apache/commons/codec/binary/Base64.java deleted file mode 100644 index 6545c2ff..00000000 --- a/src/org/apache/commons/codec/binary/Base64.java +++ /dev/null @@ -1,784 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.codec.binary; - -import java.math.BigInteger; - -/** - * Provides Base64 encoding and decoding as defined by RFC 2045. - * - *

- * This class implements section 6.8. Base64 Content-Transfer-Encoding from RFC 2045 Multipurpose - * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies by Freed and Borenstein. - *

- *

- * The class can be parameterized in the following manner with various constructors: - *

- *
    - *
  • URL-safe mode: Default off.
  • - *
  • Line length: Default 76. Line length that aren't multiples of 4 will still essentially end up being multiples of - * 4 in the encoded data. - *
  • Line separator: Default is CRLF ("\r\n")
  • - *
- *

- * The URL-safe parameter is only applied to encode operations. Decoding seamlessly handles both modes. - *

- *

- * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only - * encode/decode character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, - * UTF-8, etc). - *

- *

- * This class is thread-safe. - *

- * - * @see RFC 2045 - * @since 1.0 - * @version $Id: Base64.java 1635986 2014-11-01 16:27:52Z tn $ - */ -public class Base64 extends BaseNCodec { - - /** - * BASE32 characters are 6 bits in length. - * They are formed by taking a block of 3 octets to form a 24-bit string, - * which is converted into 4 BASE64 characters. - */ - private static final int BITS_PER_ENCODED_BYTE = 6; - private static final int BYTES_PER_UNENCODED_BLOCK = 3; - private static final int BYTES_PER_ENCODED_BLOCK = 4; - - /** - * Chunk separator per RFC 2045 section 2.1. - * - *

- * N.B. The next major release may break compatibility and make this field private. - *

- * - * @see RFC 2045 section 2.1 - */ - static final byte[] CHUNK_SEPARATOR = {'\r', '\n'}; - - /** - * This array is a lookup table that translates 6-bit positive integer index values into their "Base64 Alphabet" - * equivalents as specified in Table 1 of RFC 2045. - * - * Thanks to "commons" project in ws.apache.org for this code. - * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ - */ - private static final byte[] STANDARD_ENCODE_TABLE = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' - }; - - /** - * This is a copy of the STANDARD_ENCODE_TABLE above, but with + and / - * changed to - and _ to make the encoded Base64 results more URL-SAFE. - * This table is only used when the Base64's mode is set to URL-SAFE. - */ - private static final byte[] URL_SAFE_ENCODE_TABLE = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' - }; - - /** - * This array is a lookup table that translates Unicode characters drawn from the "Base64 Alphabet" (as specified - * in Table 1 of RFC 2045) into their 6-bit positive integer equivalents. Characters that are not in the Base64 - * alphabet but fall within the bounds of the array are translated to -1. - * - * Note: '+' and '-' both decode to 62. '/' and '_' both decode to 63. This means decoder seamlessly handles both - * URL_SAFE and STANDARD base64. (The encoder, on the other hand, needs to know ahead of time what to emit). - * - * Thanks to "commons" project in ws.apache.org for this code. - * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ - */ - private static final byte[] DECODE_TABLE = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, 52, 53, 54, - 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, - 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, - 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, - 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 - }; - - /** - * Base64 uses 6-bit fields. - */ - /** Mask used to extract 6 bits, used when encoding */ - private static final int MASK_6BITS = 0x3f; - - // The static final fields above are used for the original static byte[] methods on Base64. - // The private member fields below are used with the new streaming approach, which requires - // some state be preserved between calls of encode() and decode(). - - /** - * Encode table to use: either STANDARD or URL_SAFE. Note: the DECODE_TABLE above remains static because it is able - * to decode both STANDARD and URL_SAFE streams, but the encodeTable must be a member variable so we can switch - * between the two modes. - */ - private final byte[] encodeTable; - - // Only one decode table currently; keep for consistency with Base32 code - private final byte[] decodeTable = DECODE_TABLE; - - /** - * Line separator for encoding. Not used when decoding. Only used if lineLength > 0. - */ - private final byte[] lineSeparator; - - /** - * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. - * decodeSize = 3 + lineSeparator.length; - */ - private final int decodeSize; - - /** - * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. - * encodeSize = 4 + lineSeparator.length; - */ - private final int encodeSize; - - /** - * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. - *

- * When encoding the line length is 0 (no chunking), and the encoding table is STANDARD_ENCODE_TABLE. - *

- * - *

- * When decoding all variants are supported. - *

- */ - public Base64() { - this(0); - } - - /** - * Creates a Base64 codec used for decoding (all modes) and encoding in the given URL-safe mode. - *

- * When encoding the line length is 76, the line separator is CRLF, and the encoding table is STANDARD_ENCODE_TABLE. - *

- * - *

- * When decoding all variants are supported. - *

- * - * @param urlSafe - * if true, URL-safe encoding is used. In most cases this should be set to - * false. - * @since 1.4 - */ - public Base64(final boolean urlSafe) { - this(MIME_CHUNK_SIZE, CHUNK_SEPARATOR, urlSafe); - } - - /** - * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. - *

- * When encoding the line length is given in the constructor, the line separator is CRLF, and the encoding table is - * STANDARD_ENCODE_TABLE. - *

- *

- * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. - *

- *

- * When decoding all variants are supported. - *

- * - * @param lineLength - * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of - * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when - * decoding. - * @since 1.4 - */ - public Base64(final int lineLength) { - this(lineLength, CHUNK_SEPARATOR); - } - - /** - * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. - *

- * When encoding the line length and line separator are given in the constructor, and the encoding table is - * STANDARD_ENCODE_TABLE. - *

- *

- * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. - *

- *

- * When decoding all variants are supported. - *

- * - * @param lineLength - * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of - * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when - * decoding. - * @param lineSeparator - * Each line of encoded data will end with this sequence of bytes. - * @throws IllegalArgumentException - * Thrown when the provided lineSeparator included some base64 characters. - * @since 1.4 - */ - public Base64(final int lineLength, final byte[] lineSeparator) { - this(lineLength, lineSeparator, false); - } - - /** - * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. - *

- * When encoding the line length and line separator are given in the constructor, and the encoding table is - * STANDARD_ENCODE_TABLE. - *

- *

- * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. - *

- *

- * When decoding all variants are supported. - *

- * - * @param lineLength - * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of - * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when - * decoding. - * @param lineSeparator - * Each line of encoded data will end with this sequence of bytes. - * @param urlSafe - * Instead of emitting '+' and '/' we emit '-' and '_' respectively. urlSafe is only applied to encode - * operations. Decoding seamlessly handles both modes. - * Note: no padding is added when using the URL-safe alphabet. - * @throws IllegalArgumentException - * The provided lineSeparator included some base64 characters. That's not going to work! - * @since 1.4 - */ - public Base64(final int lineLength, final byte[] lineSeparator, final boolean urlSafe) { - super(BYTES_PER_UNENCODED_BLOCK, BYTES_PER_ENCODED_BLOCK, - lineLength, - lineSeparator == null ? 0 : lineSeparator.length); - // TODO could be simplified if there is no requirement to reject invalid line sep when length <=0 - // @see test case Base64Test.testConstructors() - if (lineSeparator != null) { - if (containsAlphabetOrPad(lineSeparator)) { - final String sep = StringUtils.newStringUtf8(lineSeparator); - throw new IllegalArgumentException("lineSeparator must not contain base64 characters: [" + sep + "]"); - } - if (lineLength > 0){ // null line-sep forces no chunking rather than throwing IAE - this.encodeSize = BYTES_PER_ENCODED_BLOCK + lineSeparator.length; - this.lineSeparator = new byte[lineSeparator.length]; - System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length); - } else { - this.encodeSize = BYTES_PER_ENCODED_BLOCK; - this.lineSeparator = null; - } - } else { - this.encodeSize = BYTES_PER_ENCODED_BLOCK; - this.lineSeparator = null; - } - this.decodeSize = this.encodeSize - 1; - this.encodeTable = urlSafe ? URL_SAFE_ENCODE_TABLE : STANDARD_ENCODE_TABLE; - } - - /** - * Returns our current encode mode. True if we're URL-SAFE, false otherwise. - * - * @return true if we're in URL-SAFE mode, false otherwise. - * @since 1.4 - */ - public boolean isUrlSafe() { - return this.encodeTable == URL_SAFE_ENCODE_TABLE; - } - - /** - *

- * Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least twice: once with - * the data to encode, and once with inAvail set to "-1" to alert encoder that EOF has been reached, to flush last - * remaining bytes (if not multiple of 3). - *

- *

Note: no padding is added when encoding using the URL-safe alphabet.

- *

- * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach. - * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ - *

- * - * @param in - * byte[] array of binary data to base64 encode. - * @param inPos - * Position to start reading data from. - * @param inAvail - * Amount of bytes available from input for encoding. - * @param context - * the context to be used - */ - @Override - void encode(final byte[] in, int inPos, final int inAvail, final Context context) { - if (context.eof) { - return; - } - // inAvail < 0 is how we're informed of EOF in the underlying data we're - // encoding. - if (inAvail < 0) { - context.eof = true; - if (0 == context.modulus && lineLength == 0) { - return; // no leftovers to process and not using chunking - } - final byte[] buffer = ensureBufferSize(encodeSize, context); - final int savedPos = context.pos; - switch (context.modulus) { // 0-2 - case 0 : // nothing to do here - break; - case 1 : // 8 bits = 6 + 2 - // top 6 bits: - buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 2) & MASK_6BITS]; - // remaining 2: - buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 4) & MASK_6BITS]; - // URL-SAFE skips the padding to further reduce size. - if (encodeTable == STANDARD_ENCODE_TABLE) { - buffer[context.pos++] = pad; - buffer[context.pos++] = pad; - } - break; - - case 2 : // 16 bits = 6 + 6 + 4 - buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 10) & MASK_6BITS]; - buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 4) & MASK_6BITS]; - buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 2) & MASK_6BITS]; - // URL-SAFE skips the padding to further reduce size. - if (encodeTable == STANDARD_ENCODE_TABLE) { - buffer[context.pos++] = pad; - } - break; - default: - throw new IllegalStateException("Impossible modulus "+context.modulus); - } - context.currentLinePos += context.pos - savedPos; // keep track of current line position - // if currentPos == 0 we are at the start of a line, so don't add CRLF - if (lineLength > 0 && context.currentLinePos > 0) { - System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length); - context.pos += lineSeparator.length; - } - } else { - for (int i = 0; i < inAvail; i++) { - final byte[] buffer = ensureBufferSize(encodeSize, context); - context.modulus = (context.modulus+1) % BYTES_PER_UNENCODED_BLOCK; - int b = in[inPos++]; - if (b < 0) { - b += 256; - } - context.ibitWorkArea = (context.ibitWorkArea << 8) + b; // BITS_PER_BYTE - if (0 == context.modulus) { // 3 bytes = 24 bits = 4 * 6 bits to extract - buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 18) & MASK_6BITS]; - buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 12) & MASK_6BITS]; - buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 6) & MASK_6BITS]; - buffer[context.pos++] = encodeTable[context.ibitWorkArea & MASK_6BITS]; - context.currentLinePos += BYTES_PER_ENCODED_BLOCK; - if (lineLength > 0 && lineLength <= context.currentLinePos) { - System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length); - context.pos += lineSeparator.length; - context.currentLinePos = 0; - } - } - } - } - } - - /** - *

- * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once - * with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1" - * call is not necessary when decoding, but it doesn't hurt, either. - *

- *

- * Ignores all non-base64 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are - * silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in, - * garbage-out philosophy: it will not check the provided data for validity. - *

- *

- * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach. - * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ - *

- * - * @param in - * byte[] array of ascii data to base64 decode. - * @param inPos - * Position to start reading data from. - * @param inAvail - * Amount of bytes available from input for encoding. - * @param context - * the context to be used - */ - @Override - void decode(final byte[] in, int inPos, final int inAvail, final Context context) { - if (context.eof) { - return; - } - if (inAvail < 0) { - context.eof = true; - } - for (int i = 0; i < inAvail; i++) { - final byte[] buffer = ensureBufferSize(decodeSize, context); - final byte b = in[inPos++]; - if (b == pad) { - // We're done. - context.eof = true; - break; - } else { - if (b >= 0 && b < DECODE_TABLE.length) { - final int result = DECODE_TABLE[b]; - if (result >= 0) { - context.modulus = (context.modulus+1) % BYTES_PER_ENCODED_BLOCK; - context.ibitWorkArea = (context.ibitWorkArea << BITS_PER_ENCODED_BYTE) + result; - if (context.modulus == 0) { - buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 16) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS); - buffer[context.pos++] = (byte) (context.ibitWorkArea & MASK_8BITS); - } - } - } - } - } - - // Two forms of EOF as far as base64 decoder is concerned: actual - // EOF (-1) and first time '=' character is encountered in stream. - // This approach makes the '=' padding characters completely optional. - if (context.eof && context.modulus != 0) { - final byte[] buffer = ensureBufferSize(decodeSize, context); - - // We have some spare bits remaining - // Output all whole multiples of 8 bits and ignore the rest - switch (context.modulus) { -// case 0 : // impossible, as excluded above - case 1 : // 6 bits - ignore entirely - // TODO not currently tested; perhaps it is impossible? - break; - case 2 : // 12 bits = 8 + 4 - context.ibitWorkArea = context.ibitWorkArea >> 4; // dump the extra 4 bits - buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS); - break; - case 3 : // 18 bits = 8 + 8 + 2 - context.ibitWorkArea = context.ibitWorkArea >> 2; // dump 2 bits - buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS); - break; - default: - throw new IllegalStateException("Impossible modulus "+context.modulus); - } - } - } - - /** - * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. Currently the - * method treats whitespace as valid. - * - * @param arrayOctet - * byte array to test - * @return true if all bytes are valid characters in the Base64 alphabet or if the byte array is empty; - * false, otherwise - * @deprecated 1.5 Use {@link #isBase64(byte[])}, will be removed in 2.0. - */ - @Deprecated - public static boolean isArrayByteBase64(final byte[] arrayOctet) { - return isBase64(arrayOctet); - } - - /** - * Returns whether or not the octet is in the base 64 alphabet. - * - * @param octet - * The value to test - * @return true if the value is defined in the the base 64 alphabet, false otherwise. - * @since 1.4 - */ - public static boolean isBase64(final byte octet) { - return octet == PAD_DEFAULT || (octet >= 0 && octet < DECODE_TABLE.length && DECODE_TABLE[octet] != -1); - } - - /** - * Tests a given String to see if it contains only valid characters within the Base64 alphabet. Currently the - * method treats whitespace as valid. - * - * @param base64 - * String to test - * @return true if all characters in the String are valid characters in the Base64 alphabet or if - * the String is empty; false, otherwise - * @since 1.5 - */ - public static boolean isBase64(final String base64) { - return isBase64(StringUtils.getBytesUtf8(base64)); - } - - /** - * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. Currently the - * method treats whitespace as valid. - * - * @param arrayOctet - * byte array to test - * @return true if all bytes are valid characters in the Base64 alphabet or if the byte array is empty; - * false, otherwise - * @since 1.5 - */ - public static boolean isBase64(final byte[] arrayOctet) { - for (int i = 0; i < arrayOctet.length; i++) { - if (!isBase64(arrayOctet[i]) && !isWhiteSpace(arrayOctet[i])) { - return false; - } - } - return true; - } - - /** - * Encodes binary data using the base64 algorithm but does not chunk the output. - * - * @param binaryData - * binary data to encode - * @return byte[] containing Base64 characters in their UTF-8 representation. - */ - public static byte[] encodeBase64(final byte[] binaryData) { - return encodeBase64(binaryData, false); - } - - /** - * Encodes binary data using the base64 algorithm but does not chunk the output. - * - * NOTE: We changed the behaviour of this method from multi-line chunking (commons-codec-1.4) to - * single-line non-chunking (commons-codec-1.5). - * - * @param binaryData - * binary data to encode - * @return String containing Base64 characters. - * @since 1.4 (NOTE: 1.4 chunked the output, whereas 1.5 does not). - */ - public static String encodeBase64String(final byte[] binaryData) { - return StringUtils.newStringUtf8(encodeBase64(binaryData, false)); - } - - /** - * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The - * url-safe variation emits - and _ instead of + and / characters. - * Note: no padding is added. - * @param binaryData - * binary data to encode - * @return byte[] containing Base64 characters in their UTF-8 representation. - * @since 1.4 - */ - public static byte[] encodeBase64URLSafe(final byte[] binaryData) { - return encodeBase64(binaryData, false, true); - } - - /** - * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The - * url-safe variation emits - and _ instead of + and / characters. - * Note: no padding is added. - * @param binaryData - * binary data to encode - * @return String containing Base64 characters - * @since 1.4 - */ - public static String encodeBase64URLSafeString(final byte[] binaryData) { - return StringUtils.newStringUtf8(encodeBase64(binaryData, false, true)); - } - - /** - * Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks - * - * @param binaryData - * binary data to encode - * @return Base64 characters chunked in 76 character blocks - */ - public static byte[] encodeBase64Chunked(final byte[] binaryData) { - return encodeBase64(binaryData, true); - } - - /** - * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. - * - * @param binaryData - * Array containing binary data to encode. - * @param isChunked - * if true this encoder will chunk the base64 output into 76 character blocks - * @return Base64-encoded data. - * @throws IllegalArgumentException - * Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} - */ - public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked) { - return encodeBase64(binaryData, isChunked, false); - } - - /** - * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. - * - * @param binaryData - * Array containing binary data to encode. - * @param isChunked - * if true this encoder will chunk the base64 output into 76 character blocks - * @param urlSafe - * if true this encoder will emit - and _ instead of the usual + and / characters. - * Note: no padding is added when encoding using the URL-safe alphabet. - * @return Base64-encoded data. - * @throws IllegalArgumentException - * Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} - * @since 1.4 - */ - public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked, final boolean urlSafe) { - return encodeBase64(binaryData, isChunked, urlSafe, Integer.MAX_VALUE); - } - - /** - * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. - * - * @param binaryData - * Array containing binary data to encode. - * @param isChunked - * if true this encoder will chunk the base64 output into 76 character blocks - * @param urlSafe - * if true this encoder will emit - and _ instead of the usual + and / characters. - * Note: no padding is added when encoding using the URL-safe alphabet. - * @param maxResultSize - * The maximum result size to accept. - * @return Base64-encoded data. - * @throws IllegalArgumentException - * Thrown when the input array needs an output array bigger than maxResultSize - * @since 1.4 - */ - public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked, - final boolean urlSafe, final int maxResultSize) { - if (binaryData == null || binaryData.length == 0) { - return binaryData; - } - - // Create this so can use the super-class method - // Also ensures that the same roundings are performed by the ctor and the code - final Base64 b64 = isChunked ? new Base64(urlSafe) : new Base64(0, CHUNK_SEPARATOR, urlSafe); - final long len = b64.getEncodedLength(binaryData); - if (len > maxResultSize) { - throw new IllegalArgumentException("Input array too big, the output array would be bigger (" + - len + - ") than the specified maximum size of " + - maxResultSize); - } - - return b64.encode(binaryData); - } - - /** - * Decodes a Base64 String into octets. - *

- * Note: this method seamlessly handles data encoded in URL-safe or normal mode. - *

- * - * @param base64String - * String containing Base64 data - * @return Array containing decoded data. - * @since 1.4 - */ - public static byte[] decodeBase64(final String base64String) { - return new Base64().decode(base64String); - } - - /** - * Decodes Base64 data into octets. - *

- * Note: this method seamlessly handles data encoded in URL-safe or normal mode. - *

- * - * @param base64Data - * Byte array containing Base64 data - * @return Array containing decoded data. - */ - public static byte[] decodeBase64(final byte[] base64Data) { - return new Base64().decode(base64Data); - } - - // Implementation of the Encoder Interface - - // Implementation of integer encoding used for crypto - /** - * Decodes a byte64-encoded integer according to crypto standards such as W3C's XML-Signature. - * - * @param pArray - * a byte array containing base64 character data - * @return A BigInteger - * @since 1.4 - */ - public static BigInteger decodeInteger(final byte[] pArray) { - return new BigInteger(1, decodeBase64(pArray)); - } - - /** - * Encodes to a byte64-encoded integer according to crypto standards such as W3C's XML-Signature. - * - * @param bigInt - * a BigInteger - * @return A byte array containing base64 character data - * @throws NullPointerException - * if null is passed in - * @since 1.4 - */ - public static byte[] encodeInteger(final BigInteger bigInt) { - if (bigInt == null) { - throw new NullPointerException("encodeInteger called with null parameter"); - } - return encodeBase64(toIntegerBytes(bigInt), false); - } - - /** - * Returns a byte-array representation of a BigInteger without sign bit. - * - * @param bigInt - * BigInteger to be converted - * @return a byte array representation of the BigInteger parameter - */ - static byte[] toIntegerBytes(final BigInteger bigInt) { - int bitlen = bigInt.bitLength(); - // round bitlen - bitlen = ((bitlen + 7) >> 3) << 3; - final byte[] bigBytes = bigInt.toByteArray(); - - if (((bigInt.bitLength() % 8) != 0) && (((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) { - return bigBytes; - } - // set up params for copying everything but sign bit - int startSrc = 0; - int len = bigBytes.length; - - // if bigInt is exactly byte-aligned, just skip signbit in copy - if ((bigInt.bitLength() % 8) == 0) { - startSrc = 1; - len--; - } - final int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec - final byte[] resizedBytes = new byte[bitlen / 8]; - System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len); - return resizedBytes; - } - - /** - * Returns whether or not the octet is in the Base64 alphabet. - * - * @param octet - * The value to test - * @return true if the value is defined in the the Base64 alphabet false otherwise. - */ - @Override - protected boolean isInAlphabet(final byte octet) { - return octet >= 0 && octet < decodeTable.length && decodeTable[octet] != -1; - } - -} diff --git a/src/org/apache/commons/codec/binary/Base64InputStream.java b/src/org/apache/commons/codec/binary/Base64InputStream.java deleted file mode 100644 index 3cdfa88c..00000000 --- a/src/org/apache/commons/codec/binary/Base64InputStream.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.codec.binary; - -import java.io.InputStream; - -/** - * Provides Base64 encoding and decoding in a streaming fashion (unlimited size). When encoding the default lineLength - * is 76 characters and the default lineEnding is CRLF, but these can be overridden by using the appropriate - * constructor. - *

- * The default behaviour of the Base64InputStream is to DECODE, whereas the default behaviour of the Base64OutputStream - * is to ENCODE, but this behaviour can be overridden by using a different constructor. - *

- *

- * This class implements section 6.8. Base64 Content-Transfer-Encoding from RFC 2045 Multipurpose - * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies by Freed and Borenstein. - *

- *

- * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only encode/decode - * character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, UTF-8, etc). - *

- * - * @version $Id: Base64InputStream.java 1634429 2014-10-27 01:08:36Z ggregory $ - * @see RFC 2045 - * @since 1.4 - */ -public class Base64InputStream extends BaseNCodecInputStream { - - /** - * Creates a Base64InputStream such that all data read is Base64-decoded from the original provided InputStream. - * - * @param in - * InputStream to wrap. - */ - public Base64InputStream(final InputStream in) { - this(in, false); - } - - /** - * Creates a Base64InputStream such that all data read is either Base64-encoded or Base64-decoded from the original - * provided InputStream. - * - * @param in - * InputStream to wrap. - * @param doEncode - * true if we should encode all data read from us, false if we should decode. - */ - public Base64InputStream(final InputStream in, final boolean doEncode) { - super(in, new Base64(false), doEncode); - } - - /** - * Creates a Base64InputStream such that all data read is either Base64-encoded or Base64-decoded from the original - * provided InputStream. - * - * @param in - * InputStream to wrap. - * @param doEncode - * true if we should encode all data read from us, false if we should decode. - * @param lineLength - * If doEncode is true, each line of encoded data will contain lineLength characters (rounded down to - * nearest multiple of 4). If lineLength <= 0, the encoded data is not divided into lines. If doEncode - * is false, lineLength is ignored. - * @param lineSeparator - * If doEncode is true, each line of encoded data will be terminated with this byte sequence (e.g. \r\n). - * If lineLength <= 0, the lineSeparator is not used. If doEncode is false lineSeparator is ignored. - */ - public Base64InputStream(final InputStream in, final boolean doEncode, - final int lineLength, final byte[] lineSeparator) { - super(in, new Base64(lineLength, lineSeparator), doEncode); - } -} diff --git a/src/org/apache/commons/codec/binary/Base64OutputStream.java b/src/org/apache/commons/codec/binary/Base64OutputStream.java deleted file mode 100644 index 2845ec37..00000000 --- a/src/org/apache/commons/codec/binary/Base64OutputStream.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.codec.binary; - -import java.io.OutputStream; - -/** - * Provides Base64 encoding and decoding in a streaming fashion (unlimited size). When encoding the default lineLength - * is 76 characters and the default lineEnding is CRLF, but these can be overridden by using the appropriate - * constructor. - *

- * The default behaviour of the Base64OutputStream is to ENCODE, whereas the default behaviour of the Base64InputStream - * is to DECODE. But this behaviour can be overridden by using a different constructor. - *

- *

- * This class implements section 6.8. Base64 Content-Transfer-Encoding from RFC 2045 Multipurpose - * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies by Freed and Borenstein. - *

- *

- * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only encode/decode - * character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, UTF-8, etc). - *

- *

- * Note: It is mandatory to close the stream after the last byte has been written to it, otherwise the - * final padding will be omitted and the resulting data will be incomplete/inconsistent. - *

- * - * @version $Id: Base64OutputStream.java 1635952 2014-11-01 14:19:04Z tn $ - * @see RFC 2045 - * @since 1.4 - */ -public class Base64OutputStream extends BaseNCodecOutputStream { - - /** - * Creates a Base64OutputStream such that all data written is Base64-encoded to the original provided OutputStream. - * - * @param out - * OutputStream to wrap. - */ - public Base64OutputStream(final OutputStream out) { - this(out, true); - } - - /** - * Creates a Base64OutputStream such that all data written is either Base64-encoded or Base64-decoded to the - * original provided OutputStream. - * - * @param out - * OutputStream to wrap. - * @param doEncode - * true if we should encode all data written to us, false if we should decode. - */ - public Base64OutputStream(final OutputStream out, final boolean doEncode) { - super(out,new Base64(false), doEncode); - } - - /** - * Creates a Base64OutputStream such that all data written is either Base64-encoded or Base64-decoded to the - * original provided OutputStream. - * - * @param out - * OutputStream to wrap. - * @param doEncode - * true if we should encode all data written to us, false if we should decode. - * @param lineLength - * If doEncode is true, each line of encoded data will contain lineLength characters (rounded down to - * nearest multiple of 4). If lineLength <= 0, the encoded data is not divided into lines. If doEncode - * is false, lineLength is ignored. - * @param lineSeparator - * If doEncode is true, each line of encoded data will be terminated with this byte sequence (e.g. \r\n). - * If lineLength <= 0, the lineSeparator is not used. If doEncode is false lineSeparator is ignored. - */ - public Base64OutputStream(final OutputStream out, final boolean doEncode, - final int lineLength, final byte[] lineSeparator) { - super(out, new Base64(lineLength, lineSeparator), doEncode); - } -} diff --git a/src/org/apache/commons/codec/binary/BaseNCodec.java b/src/org/apache/commons/codec/binary/BaseNCodec.java deleted file mode 100644 index 46fc4f0d..00000000 --- a/src/org/apache/commons/codec/binary/BaseNCodec.java +++ /dev/null @@ -1,525 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.codec.binary; - -import java.util.Arrays; - -import org.apache.commons.codec.BinaryDecoder; -import org.apache.commons.codec.BinaryEncoder; -import org.apache.commons.codec.DecoderException; -import org.apache.commons.codec.EncoderException; - -/** - * Abstract superclass for Base-N encoders and decoders. - * - *

- * This class is thread-safe. - *

- * - * @version $Id: BaseNCodec.java 1634404 2014-10-26 23:06:10Z ggregory $ - */ -public abstract class BaseNCodec implements BinaryEncoder, BinaryDecoder { - - /** - * Holds thread context so classes can be thread-safe. - * - * This class is not itself thread-safe; each thread must allocate its own copy. - * - * @since 1.7 - */ - static class Context { - - /** - * Place holder for the bytes we're dealing with for our based logic. - * Bitwise operations store and extract the encoding or decoding from this variable. - */ - int ibitWorkArea; - - /** - * Place holder for the bytes we're dealing with for our based logic. - * Bitwise operations store and extract the encoding or decoding from this variable. - */ - long lbitWorkArea; - - /** - * Buffer for streaming. - */ - byte[] buffer; - - /** - * Position where next character should be written in the buffer. - */ - int pos; - - /** - * Position where next character should be read from the buffer. - */ - int readPos; - - /** - * Boolean flag to indicate the EOF has been reached. Once EOF has been reached, this object becomes useless, - * and must be thrown away. - */ - boolean eof; - - /** - * Variable tracks how many characters have been written to the current line. Only used when encoding. We use - * it to make sure each encoded line never goes beyond lineLength (if lineLength > 0). - */ - int currentLinePos; - - /** - * Writes to the buffer only occur after every 3/5 reads when encoding, and every 4/8 reads when decoding. This - * variable helps track that. - */ - int modulus; - - Context() { - } - - /** - * Returns a String useful for debugging (especially within a debugger.) - * - * @return a String useful for debugging. - */ - @SuppressWarnings("boxing") // OK to ignore boxing here - @Override - public String toString() { - return String.format("%s[buffer=%s, currentLinePos=%s, eof=%s, ibitWorkArea=%s, lbitWorkArea=%s, " + - "modulus=%s, pos=%s, readPos=%s]", this.getClass().getSimpleName(), Arrays.toString(buffer), - currentLinePos, eof, ibitWorkArea, lbitWorkArea, modulus, pos, readPos); - } - } - - /** - * EOF - * - * @since 1.7 - */ - static final int EOF = -1; - - /** - * MIME chunk size per RFC 2045 section 6.8. - * - *

- * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any - * equal signs. - *

- * - * @see RFC 2045 section 6.8 - */ - public static final int MIME_CHUNK_SIZE = 76; - - /** - * PEM chunk size per RFC 1421 section 4.3.2.4. - * - *

- * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any - * equal signs. - *

- * - * @see RFC 1421 section 4.3.2.4 - */ - public static final int PEM_CHUNK_SIZE = 64; - - private static final int DEFAULT_BUFFER_RESIZE_FACTOR = 2; - - /** - * Defines the default buffer size - currently {@value} - * - must be large enough for at least one encoded block+separator - */ - private static final int DEFAULT_BUFFER_SIZE = 8192; - - /** Mask used to extract 8 bits, used in decoding bytes */ - protected static final int MASK_8BITS = 0xff; - - /** - * Byte used to pad output. - */ - protected static final byte PAD_DEFAULT = '='; // Allow static access to default - - /** - * @deprecated Use {@link #pad}. Will be removed in 2.0. - */ - @Deprecated - protected final byte PAD = PAD_DEFAULT; // instance variable just in case it needs to vary later - - protected final byte pad; // instance variable just in case it needs to vary later - - /** Number of bytes in each full block of unencoded data, e.g. 4 for Base64 and 5 for Base32 */ - private final int unencodedBlockSize; - - /** Number of bytes in each full block of encoded data, e.g. 3 for Base64 and 8 for Base32 */ - private final int encodedBlockSize; - - /** - * Chunksize for encoding. Not used when decoding. - * A value of zero or less implies no chunking of the encoded data. - * Rounded down to nearest multiple of encodedBlockSize. - */ - protected final int lineLength; - - /** - * Size of chunk separator. Not used unless {@link #lineLength} > 0. - */ - private final int chunkSeparatorLength; - - /** - * Note lineLength is rounded down to the nearest multiple of {@link #encodedBlockSize} - * If chunkSeparatorLength is zero, then chunking is disabled. - * @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3) - * @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4) - * @param lineLength if > 0, use chunking with a length lineLength - * @param chunkSeparatorLength the chunk separator length, if relevant - */ - protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize, - final int lineLength, final int chunkSeparatorLength) { - this(unencodedBlockSize, encodedBlockSize, lineLength, chunkSeparatorLength, PAD_DEFAULT); - } - - /** - * Note lineLength is rounded down to the nearest multiple of {@link #encodedBlockSize} - * If chunkSeparatorLength is zero, then chunking is disabled. - * @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3) - * @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4) - * @param lineLength if > 0, use chunking with a length lineLength - * @param chunkSeparatorLength the chunk separator length, if relevant - * @param pad byte used as padding byte. - */ - protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize, - final int lineLength, final int chunkSeparatorLength, final byte pad) { - this.unencodedBlockSize = unencodedBlockSize; - this.encodedBlockSize = encodedBlockSize; - final boolean useChunking = lineLength > 0 && chunkSeparatorLength > 0; - this.lineLength = useChunking ? (lineLength / encodedBlockSize) * encodedBlockSize : 0; - this.chunkSeparatorLength = chunkSeparatorLength; - - this.pad = pad; - } - - /** - * Returns true if this object has buffered data for reading. - * - * @param context the context to be used - * @return true if there is data still available for reading. - */ - boolean hasData(final Context context) { // package protected for access from I/O streams - return context.buffer != null; - } - - /** - * Returns the amount of buffered data available for reading. - * - * @param context the context to be used - * @return The amount of buffered data available for reading. - */ - int available(final Context context) { // package protected for access from I/O streams - return context.buffer != null ? context.pos - context.readPos : 0; - } - - /** - * Get the default buffer size. Can be overridden. - * - * @return {@link #DEFAULT_BUFFER_SIZE} - */ - protected int getDefaultBufferSize() { - return DEFAULT_BUFFER_SIZE; - } - - /** - * Increases our buffer by the {@link #DEFAULT_BUFFER_RESIZE_FACTOR}. - * @param context the context to be used - */ - private byte[] resizeBuffer(final Context context) { - if (context.buffer == null) { - context.buffer = new byte[getDefaultBufferSize()]; - context.pos = 0; - context.readPos = 0; - } else { - final byte[] b = new byte[context.buffer.length * DEFAULT_BUFFER_RESIZE_FACTOR]; - System.arraycopy(context.buffer, 0, b, 0, context.buffer.length); - context.buffer = b; - } - return context.buffer; - } - - /** - * Ensure that the buffer has room for size bytes - * - * @param size minimum spare space required - * @param context the context to be used - * @return the buffer - */ - protected byte[] ensureBufferSize(final int size, final Context context){ - if ((context.buffer == null) || (context.buffer.length < context.pos + size)){ - return resizeBuffer(context); - } - return context.buffer; - } - - /** - * Extracts buffered data into the provided byte[] array, starting at position bPos, up to a maximum of bAvail - * bytes. Returns how many bytes were actually extracted. - *

- * Package protected for access from I/O streams. - * - * @param b - * byte[] array to extract the buffered data into. - * @param bPos - * position in byte[] array to start extraction at. - * @param bAvail - * amount of bytes we're allowed to extract. We may extract fewer (if fewer are available). - * @param context - * the context to be used - * @return The number of bytes successfully extracted into the provided byte[] array. - */ - int readResults(final byte[] b, final int bPos, final int bAvail, final Context context) { - if (context.buffer != null) { - final int len = Math.min(available(context), bAvail); - System.arraycopy(context.buffer, context.readPos, b, bPos, len); - context.readPos += len; - if (context.readPos >= context.pos) { - context.buffer = null; // so hasData() will return false, and this method can return -1 - } - return len; - } - return context.eof ? EOF : 0; - } - - /** - * Checks if a byte value is whitespace or not. - * Whitespace is taken to mean: space, tab, CR, LF - * @param byteToCheck - * the byte to check - * @return true if byte is whitespace, false otherwise - */ - protected static boolean isWhiteSpace(final byte byteToCheck) { - switch (byteToCheck) { - case ' ' : - case '\n' : - case '\r' : - case '\t' : - return true; - default : - return false; - } - } - - /** - * Encodes an Object using the Base-N algorithm. This method is provided in order to satisfy the requirements of - * the Encoder interface, and will throw an EncoderException if the supplied object is not of type byte[]. - * - * @param obj - * Object to encode - * @return An object (of type byte[]) containing the Base-N encoded data which corresponds to the byte[] supplied. - * @throws EncoderException - * if the parameter supplied is not of type byte[] - */ - @Override - public Object encode(final Object obj) throws EncoderException { - if (!(obj instanceof byte[])) { - throw new EncoderException("Parameter supplied to Base-N encode is not a byte[]"); - } - return encode((byte[]) obj); - } - - /** - * Encodes a byte[] containing binary data, into a String containing characters in the Base-N alphabet. - * Uses UTF8 encoding. - * - * @param pArray - * a byte array containing binary data - * @return A String containing only Base-N character data - */ - public String encodeToString(final byte[] pArray) { - return StringUtils.newStringUtf8(encode(pArray)); - } - - /** - * Encodes a byte[] containing binary data, into a String containing characters in the appropriate alphabet. - * Uses UTF8 encoding. - * - * @param pArray a byte array containing binary data - * @return String containing only character data in the appropriate alphabet. - */ - public String encodeAsString(final byte[] pArray){ - return StringUtils.newStringUtf8(encode(pArray)); - } - - /** - * Decodes an Object using the Base-N algorithm. This method is provided in order to satisfy the requirements of - * the Decoder interface, and will throw a DecoderException if the supplied object is not of type byte[] or String. - * - * @param obj - * Object to decode - * @return An object (of type byte[]) containing the binary data which corresponds to the byte[] or String - * supplied. - * @throws DecoderException - * if the parameter supplied is not of type byte[] - */ - @Override - public Object decode(final Object obj) throws DecoderException { - if (obj instanceof byte[]) { - return decode((byte[]) obj); - } else if (obj instanceof String) { - return decode((String) obj); - } else { - throw new DecoderException("Parameter supplied to Base-N decode is not a byte[] or a String"); - } - } - - /** - * Decodes a String containing characters in the Base-N alphabet. - * - * @param pArray - * A String containing Base-N character data - * @return a byte array containing binary data - */ - public byte[] decode(final String pArray) { - return decode(StringUtils.getBytesUtf8(pArray)); - } - - /** - * Decodes a byte[] containing characters in the Base-N alphabet. - * - * @param pArray - * A byte array containing Base-N character data - * @return a byte array containing binary data - */ - @Override - public byte[] decode(final byte[] pArray) { - if (pArray == null || pArray.length == 0) { - return pArray; - } - final Context context = new Context(); - decode(pArray, 0, pArray.length, context); - decode(pArray, 0, EOF, context); // Notify decoder of EOF. - final byte[] result = new byte[context.pos]; - readResults(result, 0, result.length, context); - return result; - } - - /** - * Encodes a byte[] containing binary data, into a byte[] containing characters in the alphabet. - * - * @param pArray - * a byte array containing binary data - * @return A byte array containing only the basen alphabetic character data - */ - @Override - public byte[] encode(final byte[] pArray) { - if (pArray == null || pArray.length == 0) { - return pArray; - } - final Context context = new Context(); - encode(pArray, 0, pArray.length, context); - encode(pArray, 0, EOF, context); // Notify encoder of EOF. - final byte[] buf = new byte[context.pos - context.readPos]; - readResults(buf, 0, buf.length, context); - return buf; - } - - // package protected for access from I/O streams - abstract void encode(byte[] pArray, int i, int length, Context context); - - // package protected for access from I/O streams - abstract void decode(byte[] pArray, int i, int length, Context context); - - /** - * Returns whether or not the octet is in the current alphabet. - * Does not allow whitespace or pad. - * - * @param value The value to test - * - * @return true if the value is defined in the current alphabet, false otherwise. - */ - protected abstract boolean isInAlphabet(byte value); - - /** - * Tests a given byte array to see if it contains only valid characters within the alphabet. - * The method optionally treats whitespace and pad as valid. - * - * @param arrayOctet byte array to test - * @param allowWSPad if true, then whitespace and PAD are also allowed - * - * @return true if all bytes are valid characters in the alphabet or if the byte array is empty; - * false, otherwise - */ - public boolean isInAlphabet(final byte[] arrayOctet, final boolean allowWSPad) { - for (int i = 0; i < arrayOctet.length; i++) { - if (!isInAlphabet(arrayOctet[i]) && - (!allowWSPad || (arrayOctet[i] != pad) && !isWhiteSpace(arrayOctet[i]))) { - return false; - } - } - return true; - } - - /** - * Tests a given String to see if it contains only valid characters within the alphabet. - * The method treats whitespace and PAD as valid. - * - * @param basen String to test - * @return true if all characters in the String are valid characters in the alphabet or if - * the String is empty; false, otherwise - * @see #isInAlphabet(byte[], boolean) - */ - public boolean isInAlphabet(final String basen) { - return isInAlphabet(StringUtils.getBytesUtf8(basen), true); - } - - /** - * Tests a given byte array to see if it contains any characters within the alphabet or PAD. - * - * Intended for use in checking line-ending arrays - * - * @param arrayOctet - * byte array to test - * @return true if any byte is a valid character in the alphabet or PAD; false otherwise - */ - protected boolean containsAlphabetOrPad(final byte[] arrayOctet) { - if (arrayOctet == null) { - return false; - } - for (final byte element : arrayOctet) { - if (pad == element || isInAlphabet(element)) { - return true; - } - } - return false; - } - - /** - * Calculates the amount of space needed to encode the supplied array. - * - * @param pArray byte[] array which will later be encoded - * - * @return amount of space needed to encoded the supplied array. - * Returns a long since a max-len array will require > Integer.MAX_VALUE - */ - public long getEncodedLength(final byte[] pArray) { - // Calculate non-chunked size - rounded up to allow for padding - // cast to long is needed to avoid possibility of overflow - long len = ((pArray.length + unencodedBlockSize-1) / unencodedBlockSize) * (long) encodedBlockSize; - if (lineLength > 0) { // We're using chunking - // Round up to nearest multiple - len += ((len + lineLength-1) / lineLength) * chunkSeparatorLength; - } - return len; - } -} diff --git a/src/org/apache/commons/codec/binary/BaseNCodecInputStream.java b/src/org/apache/commons/codec/binary/BaseNCodecInputStream.java deleted file mode 100644 index 777eb6d2..00000000 --- a/src/org/apache/commons/codec/binary/BaseNCodecInputStream.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.codec.binary; - -import static org.apache.commons.codec.binary.BaseNCodec.EOF; - -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; - -import org.apache.commons.codec.binary.BaseNCodec.Context; - -/** - * Abstract superclass for Base-N input streams. - * - * @since 1.5 - * @version $Id: BaseNCodecInputStream.java 1429868 2013-01-07 16:08:05Z ggregory $ - */ -public class BaseNCodecInputStream extends FilterInputStream { - - private final BaseNCodec baseNCodec; - - private final boolean doEncode; - - private final byte[] singleByte = new byte[1]; - - private final Context context = new Context(); - - protected BaseNCodecInputStream(final InputStream in, final BaseNCodec baseNCodec, final boolean doEncode) { - super(in); - this.doEncode = doEncode; - this.baseNCodec = baseNCodec; - } - - /** - * {@inheritDoc} - * - * @return 0 if the {@link InputStream} has reached EOF, - * 1 otherwise - * @since 1.7 - */ - @Override - public int available() throws IOException { - // Note: the logic is similar to the InflaterInputStream: - // as long as we have not reached EOF, indicate that there is more - // data available. As we do not know for sure how much data is left, - // just return 1 as a safe guess. - - return context.eof ? 0 : 1; - } - - /** - * Marks the current position in this input stream. - *

The {@link #mark} method of {@link BaseNCodecInputStream} does nothing.

- * - * @param readLimit the maximum limit of bytes that can be read before the mark position becomes invalid. - * @since 1.7 - */ - @Override - public synchronized void mark(final int readLimit) { - } - - /** - * {@inheritDoc} - * - * @return always returns false - */ - @Override - public boolean markSupported() { - return false; // not an easy job to support marks - } - - /** - * Reads one byte from this input stream. - * - * @return the byte as an integer in the range 0 to 255. Returns -1 if EOF has been reached. - * @throws IOException - * if an I/O error occurs. - */ - @Override - public int read() throws IOException { - int r = read(singleByte, 0, 1); - while (r == 0) { - r = read(singleByte, 0, 1); - } - if (r > 0) { - final byte b = singleByte[0]; - return b < 0 ? 256 + b : b; - } - return EOF; - } - - /** - * Attempts to read len bytes into the specified b array starting at offset - * from this InputStream. - * - * @param b - * destination byte array - * @param offset - * where to start writing the bytes - * @param len - * maximum number of bytes to read - * - * @return number of bytes read - * @throws IOException - * if an I/O error occurs. - * @throws NullPointerException - * if the byte array parameter is null - * @throws IndexOutOfBoundsException - * if offset, len or buffer size are invalid - */ - @Override - public int read(final byte b[], final int offset, final int len) throws IOException { - if (b == null) { - throw new NullPointerException(); - } else if (offset < 0 || len < 0) { - throw new IndexOutOfBoundsException(); - } else if (offset > b.length || offset + len > b.length) { - throw new IndexOutOfBoundsException(); - } else if (len == 0) { - return 0; - } else { - int readLen = 0; - /* - Rationale for while-loop on (readLen == 0): - ----- - Base32.readResults() usually returns > 0 or EOF (-1). In the - rare case where it returns 0, we just keep trying. - - This is essentially an undocumented contract for InputStream - implementors that want their code to work properly with - java.io.InputStreamReader, since the latter hates it when - InputStream.read(byte[]) returns a zero. Unfortunately our - readResults() call must return 0 if a large amount of the data - being decoded was non-base32, so this while-loop enables proper - interop with InputStreamReader for that scenario. - ----- - This is a fix for CODEC-101 - */ - while (readLen == 0) { - if (!baseNCodec.hasData(context)) { - final byte[] buf = new byte[doEncode ? 4096 : 8192]; - final int c = in.read(buf); - if (doEncode) { - baseNCodec.encode(buf, 0, c, context); - } else { - baseNCodec.decode(buf, 0, c, context); - } - } - readLen = baseNCodec.readResults(b, offset, len, context); - } - return readLen; - } - } - - /** - * Repositions this stream to the position at the time the mark method was last called on this input stream. - *

- * The {@link #reset} method of {@link BaseNCodecInputStream} does nothing except throw an {@link IOException}. - * - * @throws IOException if this method is invoked - * @since 1.7 - */ - @Override - public synchronized void reset() throws IOException { - throw new IOException("mark/reset not supported"); - } - - /** - * {@inheritDoc} - * - * @throws IllegalArgumentException if the provided skip length is negative - * @since 1.7 - */ - @Override - public long skip(final long n) throws IOException { - if (n < 0) { - throw new IllegalArgumentException("Negative skip length: " + n); - } - - // skip in chunks of 512 bytes - final byte[] b = new byte[512]; - long todo = n; - - while (todo > 0) { - int len = (int) Math.min(b.length, todo); - len = this.read(b, 0, len); - if (len == EOF) { - break; - } - todo -= len; - } - - return n - todo; - } -} diff --git a/src/org/apache/commons/codec/binary/BaseNCodecOutputStream.java b/src/org/apache/commons/codec/binary/BaseNCodecOutputStream.java deleted file mode 100644 index 72ce81a6..00000000 --- a/src/org/apache/commons/codec/binary/BaseNCodecOutputStream.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.codec.binary; - -import static org.apache.commons.codec.binary.BaseNCodec.EOF; - -import java.io.FilterOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -import org.apache.commons.codec.binary.BaseNCodec.Context; - -/** - * Abstract superclass for Base-N output streams. - * - * @since 1.5 - * @version $Id: BaseNCodecOutputStream.java 1544347 2013-11-21 22:30:31Z ggregory $ - */ -public class BaseNCodecOutputStream extends FilterOutputStream { - - private final boolean doEncode; - - private final BaseNCodec baseNCodec; - - private final byte[] singleByte = new byte[1]; - - private final Context context = new Context(); - - // TODO should this be protected? - public BaseNCodecOutputStream(final OutputStream out, final BaseNCodec basedCodec, final boolean doEncode) { - super(out); - this.baseNCodec = basedCodec; - this.doEncode = doEncode; - } - - /** - * Writes the specified byte to this output stream. - * - * @param i - * source byte - * @throws IOException - * if an I/O error occurs. - */ - @Override - public void write(final int i) throws IOException { - singleByte[0] = (byte) i; - write(singleByte, 0, 1); - } - - /** - * Writes len bytes from the specified b array starting at offset to this - * output stream. - * - * @param b - * source byte array - * @param offset - * where to start reading the bytes - * @param len - * maximum number of bytes to write - * - * @throws IOException - * if an I/O error occurs. - * @throws NullPointerException - * if the byte array parameter is null - * @throws IndexOutOfBoundsException - * if offset, len or buffer size are invalid - */ - @Override - public void write(final byte b[], final int offset, final int len) throws IOException { - if (b == null) { - throw new NullPointerException(); - } else if (offset < 0 || len < 0) { - throw new IndexOutOfBoundsException(); - } else if (offset > b.length || offset + len > b.length) { - throw new IndexOutOfBoundsException(); - } else if (len > 0) { - if (doEncode) { - baseNCodec.encode(b, offset, len, context); - } else { - baseNCodec.decode(b, offset, len, context); - } - flush(false); - } - } - - /** - * Flushes this output stream and forces any buffered output bytes to be written out to the stream. If propagate is - * true, the wrapped stream will also be flushed. - * - * @param propagate - * boolean flag to indicate whether the wrapped OutputStream should also be flushed. - * @throws IOException - * if an I/O error occurs. - */ - private void flush(final boolean propagate) throws IOException { - final int avail = baseNCodec.available(context); - if (avail > 0) { - final byte[] buf = new byte[avail]; - final int c = baseNCodec.readResults(buf, 0, avail, context); - if (c > 0) { - out.write(buf, 0, c); - } - } - if (propagate) { - out.flush(); - } - } - - /** - * Flushes this output stream and forces any buffered output bytes to be written out to the stream. - * - * @throws IOException - * if an I/O error occurs. - */ - @Override - public void flush() throws IOException { - flush(true); - } - - /** - * Closes this output stream and releases any system resources associated with the stream. - * - * @throws IOException - * if an I/O error occurs. - */ - @Override - public void close() throws IOException { - // Notify encoder of EOF (-1). - if (doEncode) { - baseNCodec.encode(singleByte, 0, EOF, context); - } else { - baseNCodec.decode(singleByte, 0, EOF, context); - } - flush(); - out.close(); - } - -} diff --git a/src/org/apache/commons/codec/binary/BinaryCodec.java b/src/org/apache/commons/codec/binary/BinaryCodec.java deleted file mode 100644 index c813e3f8..00000000 --- a/src/org/apache/commons/codec/binary/BinaryCodec.java +++ /dev/null @@ -1,301 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.codec.binary; - -import org.apache.commons.codec.BinaryDecoder; -import org.apache.commons.codec.BinaryEncoder; -import org.apache.commons.codec.DecoderException; -import org.apache.commons.codec.EncoderException; - -/** - * Converts between byte arrays and strings of "0"s and "1"s. - * - *

This class is immutable and thread-safe.

- * - * TODO: may want to add more bit vector functions like and/or/xor/nand - * TODO: also might be good to generate boolean[] from byte[] et cetera. - * - * @since 1.3 - * @version $Id: BinaryCodec.java 1619948 2014-08-22 22:53:55Z ggregory $ - */ -public class BinaryCodec implements BinaryDecoder, BinaryEncoder { - /* - * tried to avoid using ArrayUtils to minimize dependencies while using these empty arrays - dep is just not worth - * it. - */ - /** Empty char array. */ - private static final char[] EMPTY_CHAR_ARRAY = new char[0]; - - /** Empty byte array. */ - private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; - - /** Mask for bit 0 of a byte. */ - private static final int BIT_0 = 1; - - /** Mask for bit 1 of a byte. */ - private static final int BIT_1 = 0x02; - - /** Mask for bit 2 of a byte. */ - private static final int BIT_2 = 0x04; - - /** Mask for bit 3 of a byte. */ - private static final int BIT_3 = 0x08; - - /** Mask for bit 4 of a byte. */ - private static final int BIT_4 = 0x10; - - /** Mask for bit 5 of a byte. */ - private static final int BIT_5 = 0x20; - - /** Mask for bit 6 of a byte. */ - private static final int BIT_6 = 0x40; - - /** Mask for bit 7 of a byte. */ - private static final int BIT_7 = 0x80; - - private static final int[] BITS = {BIT_0, BIT_1, BIT_2, BIT_3, BIT_4, BIT_5, BIT_6, BIT_7}; - - /** - * Converts an array of raw binary data into an array of ASCII 0 and 1 characters. - * - * @param raw - * the raw binary data to convert - * @return 0 and 1 ASCII character bytes one for each bit of the argument - * @see org.apache.commons.codec.BinaryEncoder#encode(byte[]) - */ - @Override - public byte[] encode(final byte[] raw) { - return toAsciiBytes(raw); - } - - /** - * Converts an array of raw binary data into an array of ASCII 0 and 1 chars. - * - * @param raw - * the raw binary data to convert - * @return 0 and 1 ASCII character chars one for each bit of the argument - * @throws EncoderException - * if the argument is not a byte[] - * @see org.apache.commons.codec.Encoder#encode(Object) - */ - @Override - public Object encode(final Object raw) throws EncoderException { - if (!(raw instanceof byte[])) { - throw new EncoderException("argument not a byte array"); - } - return toAsciiChars((byte[]) raw); - } - - /** - * Decodes a byte array where each byte represents an ASCII '0' or '1'. - * - * @param ascii - * each byte represents an ASCII '0' or '1' - * @return the raw encoded binary where each bit corresponds to a byte in the byte array argument - * @throws DecoderException - * if argument is not a byte[], char[] or String - * @see org.apache.commons.codec.Decoder#decode(Object) - */ - @Override - public Object decode(final Object ascii) throws DecoderException { - if (ascii == null) { - return EMPTY_BYTE_ARRAY; - } - if (ascii instanceof byte[]) { - return fromAscii((byte[]) ascii); - } - if (ascii instanceof char[]) { - return fromAscii((char[]) ascii); - } - if (ascii instanceof String) { - return fromAscii(((String) ascii).toCharArray()); - } - throw new DecoderException("argument not a byte array"); - } - - /** - * Decodes a byte array where each byte represents an ASCII '0' or '1'. - * - * @param ascii - * each byte represents an ASCII '0' or '1' - * @return the raw encoded binary where each bit corresponds to a byte in the byte array argument - * @see org.apache.commons.codec.Decoder#decode(Object) - */ - @Override - public byte[] decode(final byte[] ascii) { - return fromAscii(ascii); - } - - /** - * Decodes a String where each char of the String represents an ASCII '0' or '1'. - * - * @param ascii - * String of '0' and '1' characters - * @return the raw encoded binary where each bit corresponds to a byte in the byte array argument - * @see org.apache.commons.codec.Decoder#decode(Object) - */ - public byte[] toByteArray(final String ascii) { - if (ascii == null) { - return EMPTY_BYTE_ARRAY; - } - return fromAscii(ascii.toCharArray()); - } - - // ------------------------------------------------------------------------ - // - // static codec operations - // - // ------------------------------------------------------------------------ - /** - * Decodes a char array where each char represents an ASCII '0' or '1'. - * - * @param ascii - * each char represents an ASCII '0' or '1' - * @return the raw encoded binary where each bit corresponds to a char in the char array argument - */ - public static byte[] fromAscii(final char[] ascii) { - if (ascii == null || ascii.length == 0) { - return EMPTY_BYTE_ARRAY; - } - // get length/8 times bytes with 3 bit shifts to the right of the length - final byte[] l_raw = new byte[ascii.length >> 3]; - /* - * We decr index jj by 8 as we go along to not recompute indices using multiplication every time inside the - * loop. - */ - for (int ii = 0, jj = ascii.length - 1; ii < l_raw.length; ii++, jj -= 8) { - for (int bits = 0; bits < BITS.length; ++bits) { - if (ascii[jj - bits] == '1') { - l_raw[ii] |= BITS[bits]; - } - } - } - return l_raw; - } - - /** - * Decodes a byte array where each byte represents an ASCII '0' or '1'. - * - * @param ascii - * each byte represents an ASCII '0' or '1' - * @return the raw encoded binary where each bit corresponds to a byte in the byte array argument - */ - public static byte[] fromAscii(final byte[] ascii) { - if (isEmpty(ascii)) { - return EMPTY_BYTE_ARRAY; - } - // get length/8 times bytes with 3 bit shifts to the right of the length - final byte[] l_raw = new byte[ascii.length >> 3]; - /* - * We decr index jj by 8 as we go along to not recompute indices using multiplication every time inside the - * loop. - */ - for (int ii = 0, jj = ascii.length - 1; ii < l_raw.length; ii++, jj -= 8) { - for (int bits = 0; bits < BITS.length; ++bits) { - if (ascii[jj - bits] == '1') { - l_raw[ii] |= BITS[bits]; - } - } - } - return l_raw; - } - - /** - * Returns true if the given array is null or empty (size 0.) - * - * @param array - * the source array - * @return true if the given array is null or empty (size 0.) - */ - private static boolean isEmpty(final byte[] array) { - return array == null || array.length == 0; - } - - /** - * Converts an array of raw binary data into an array of ASCII 0 and 1 character bytes - each byte is a truncated - * char. - * - * @param raw - * the raw binary data to convert - * @return an array of 0 and 1 character bytes for each bit of the argument - * @see org.apache.commons.codec.BinaryEncoder#encode(byte[]) - */ - public static byte[] toAsciiBytes(final byte[] raw) { - if (isEmpty(raw)) { - return EMPTY_BYTE_ARRAY; - } - // get 8 times the bytes with 3 bit shifts to the left of the length - final byte[] l_ascii = new byte[raw.length << 3]; - /* - * We decr index jj by 8 as we go along to not recompute indices using multiplication every time inside the - * loop. - */ - for (int ii = 0, jj = l_ascii.length - 1; ii < raw.length; ii++, jj -= 8) { - for (int bits = 0; bits < BITS.length; ++bits) { - if ((raw[ii] & BITS[bits]) == 0) { - l_ascii[jj - bits] = '0'; - } else { - l_ascii[jj - bits] = '1'; - } - } - } - return l_ascii; - } - - /** - * Converts an array of raw binary data into an array of ASCII 0 and 1 characters. - * - * @param raw - * the raw binary data to convert - * @return an array of 0 and 1 characters for each bit of the argument - * @see org.apache.commons.codec.BinaryEncoder#encode(byte[]) - */ - public static char[] toAsciiChars(final byte[] raw) { - if (isEmpty(raw)) { - return EMPTY_CHAR_ARRAY; - } - // get 8 times the bytes with 3 bit shifts to the left of the length - final char[] l_ascii = new char[raw.length << 3]; - /* - * We decr index jj by 8 as we go along to not recompute indices using multiplication every time inside the - * loop. - */ - for (int ii = 0, jj = l_ascii.length - 1; ii < raw.length; ii++, jj -= 8) { - for (int bits = 0; bits < BITS.length; ++bits) { - if ((raw[ii] & BITS[bits]) == 0) { - l_ascii[jj - bits] = '0'; - } else { - l_ascii[jj - bits] = '1'; - } - } - } - return l_ascii; - } - - /** - * Converts an array of raw binary data into a String of ASCII 0 and 1 characters. - * - * @param raw - * the raw binary data to convert - * @return a String of 0 and 1 characters representing the binary data - * @see org.apache.commons.codec.BinaryEncoder#encode(byte[]) - */ - public static String toAsciiString(final byte[] raw) { - return new String(toAsciiChars(raw)); - } -} diff --git a/src/org/apache/commons/codec/binary/CharSequenceUtils.java b/src/org/apache/commons/codec/binary/CharSequenceUtils.java deleted file mode 100644 index 7e4f9974..00000000 --- a/src/org/apache/commons/codec/binary/CharSequenceUtils.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.codec.binary; - -/** - *

- * Operations on {@link CharSequence} that are null safe. - *

- *

- * Copied from Apache Commons Lang r1586295 on April 10, 2014 (day of 3.3.2 release). - *

- * - * @see CharSequence - * @since 1.10 - */ -public class CharSequenceUtils { - - /** - * Green implementation of regionMatches. - * - * @param cs - * the CharSequence to be processed - * @param ignoreCase - * whether or not to be case insensitive - * @param thisStart - * the index to start on the cs CharSequence - * @param substring - * the CharSequence to be looked for - * @param start - * the index to start on the substring CharSequence - * @param length - * character length of the region - * @return whether the region matched - */ - static boolean regionMatches(final CharSequence cs, final boolean ignoreCase, final int thisStart, - final CharSequence substring, final int start, final int length) { - if (cs instanceof String && substring instanceof String) { - return ((String) cs).regionMatches(ignoreCase, thisStart, (String) substring, start, length); - } - int index1 = thisStart; - int index2 = start; - int tmpLen = length; - - while (tmpLen-- > 0) { - char c1 = cs.charAt(index1++); - char c2 = substring.charAt(index2++); - - if (c1 == c2) { - continue; - } - - if (!ignoreCase) { - return false; - } - - // The same check as in String.regionMatches(): - if (Character.toUpperCase(c1) != Character.toUpperCase(c2) && - Character.toLowerCase(c1) != Character.toLowerCase(c2)) { - return false; - } - } - - return true; - } -} diff --git a/src/org/apache/commons/codec/binary/Hex.java b/src/org/apache/commons/codec/binary/Hex.java deleted file mode 100644 index 8ae0dd7b..00000000 --- a/src/org/apache/commons/codec/binary/Hex.java +++ /dev/null @@ -1,334 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.codec.binary; - -import java.nio.charset.Charset; - -import org.apache.commons.codec.BinaryDecoder; -import org.apache.commons.codec.BinaryEncoder; -import org.apache.commons.codec.CharEncoding; -import org.apache.commons.codec.Charsets; -import org.apache.commons.codec.DecoderException; -import org.apache.commons.codec.EncoderException; - -/** - * Converts hexadecimal Strings. The charset used for certain operation can be set, the default is set in - * {@link #DEFAULT_CHARSET_NAME} - * - * This class is thread-safe. - * - * @since 1.1 - * @version $Id: Hex.java 1619948 2014-08-22 22:53:55Z ggregory $ - */ -public class Hex implements BinaryEncoder, BinaryDecoder { - - /** - * Default charset name is {@link Charsets#UTF_8} - * - * @since 1.7 - */ - public static final Charset DEFAULT_CHARSET = Charsets.UTF_8; - - /** - * Default charset name is {@link CharEncoding#UTF_8} - * - * @since 1.4 - */ - public static final String DEFAULT_CHARSET_NAME = CharEncoding.UTF_8; - - /** - * Used to build output as Hex - */ - private static final char[] DIGITS_LOWER = - {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - - /** - * Used to build output as Hex - */ - private static final char[] DIGITS_UPPER = - {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; - - /** - * Converts an array of characters representing hexadecimal values into an array of bytes of those same values. The - * returned array will be half the length of the passed array, as it takes two characters to represent any given - * byte. An exception is thrown if the passed char array has an odd number of elements. - * - * @param data - * An array of characters containing hexadecimal digits - * @return A byte array containing binary data decoded from the supplied char array. - * @throws DecoderException - * Thrown if an odd number or illegal of characters is supplied - */ - public static byte[] decodeHex(final char[] data) throws DecoderException { - - final int len = data.length; - - if ((len & 0x01) != 0) { - throw new DecoderException("Odd number of characters."); - } - - final byte[] out = new byte[len >> 1]; - - // two characters form the hex value. - for (int i = 0, j = 0; j < len; i++) { - int f = toDigit(data[j], j) << 4; - j++; - f = f | toDigit(data[j], j); - j++; - out[i] = (byte) (f & 0xFF); - } - - return out; - } - - /** - * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. - * The returned array will be double the length of the passed array, as it takes two characters to represent any - * given byte. - * - * @param data - * a byte[] to convert to Hex characters - * @return A char[] containing hexadecimal characters - */ - public static char[] encodeHex(final byte[] data) { - return encodeHex(data, true); - } - - /** - * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. - * The returned array will be double the length of the passed array, as it takes two characters to represent any - * given byte. - * - * @param data - * a byte[] to convert to Hex characters - * @param toLowerCase - * true converts to lowercase, false to uppercase - * @return A char[] containing hexadecimal characters - * @since 1.4 - */ - public static char[] encodeHex(final byte[] data, final boolean toLowerCase) { - return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER); - } - - /** - * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. - * The returned array will be double the length of the passed array, as it takes two characters to represent any - * given byte. - * - * @param data - * a byte[] to convert to Hex characters - * @param toDigits - * the output alphabet - * @return A char[] containing hexadecimal characters - * @since 1.4 - */ - protected static char[] encodeHex(final byte[] data, final char[] toDigits) { - final int l = data.length; - final char[] out = new char[l << 1]; - // two characters form the hex value. - for (int i = 0, j = 0; i < l; i++) { - out[j++] = toDigits[(0xF0 & data[i]) >>> 4]; - out[j++] = toDigits[0x0F & data[i]]; - } - return out; - } - - /** - * Converts an array of bytes into a String representing the hexadecimal values of each byte in order. The returned - * String will be double the length of the passed array, as it takes two characters to represent any given byte. - * - * @param data - * a byte[] to convert to Hex characters - * @return A String containing hexadecimal characters - * @since 1.4 - */ - public static String encodeHexString(final byte[] data) { - return new String(encodeHex(data)); - } - - /** - * Converts a hexadecimal character to an integer. - * - * @param ch - * A character to convert to an integer digit - * @param index - * The index of the character in the source - * @return An integer - * @throws DecoderException - * Thrown if ch is an illegal hex character - */ - protected static int toDigit(final char ch, final int index) throws DecoderException { - final int digit = Character.digit(ch, 16); - if (digit == -1) { - throw new DecoderException("Illegal hexadecimal character " + ch + " at index " + index); - } - return digit; - } - - private final Charset charset; - - /** - * Creates a new codec with the default charset name {@link #DEFAULT_CHARSET} - */ - public Hex() { - // use default encoding - this.charset = DEFAULT_CHARSET; - } - - /** - * Creates a new codec with the given Charset. - * - * @param charset - * the charset. - * @since 1.7 - */ - public Hex(final Charset charset) { - this.charset = charset; - } - - /** - * Creates a new codec with the given charset name. - * - * @param charsetName - * the charset name. - * @throws java.nio.charset.UnsupportedCharsetException - * If the named charset is unavailable - * @since 1.4 - * @since 1.7 throws UnsupportedCharsetException if the named charset is unavailable - */ - public Hex(final String charsetName) { - this(Charset.forName(charsetName)); - } - - /** - * Converts an array of character bytes representing hexadecimal values into an array of bytes of those same values. - * The returned array will be half the length of the passed array, as it takes two characters to represent any given - * byte. An exception is thrown if the passed char array has an odd number of elements. - * - * @param array - * An array of character bytes containing hexadecimal digits - * @return A byte array containing binary data decoded from the supplied byte array (representing characters). - * @throws DecoderException - * Thrown if an odd number of characters is supplied to this function - * @see #decodeHex(char[]) - */ - @Override - public byte[] decode(final byte[] array) throws DecoderException { - return decodeHex(new String(array, getCharset()).toCharArray()); - } - - /** - * Converts a String or an array of character bytes representing hexadecimal values into an array of bytes of those - * same values. The returned array will be half the length of the passed String or array, as it takes two characters - * to represent any given byte. An exception is thrown if the passed char array has an odd number of elements. - * - * @param object - * A String or, an array of character bytes containing hexadecimal digits - * @return A byte array containing binary data decoded from the supplied byte array (representing characters). - * @throws DecoderException - * Thrown if an odd number of characters is supplied to this function or the object is not a String or - * char[] - * @see #decodeHex(char[]) - */ - @Override - public Object decode(final Object object) throws DecoderException { - try { - final char[] charArray = object instanceof String ? ((String) object).toCharArray() : (char[]) object; - return decodeHex(charArray); - } catch (final ClassCastException e) { - throw new DecoderException(e.getMessage(), e); - } - } - - /** - * Converts an array of bytes into an array of bytes for the characters representing the hexadecimal values of each - * byte in order. The returned array will be double the length of the passed array, as it takes two characters to - * represent any given byte. - *

- * The conversion from hexadecimal characters to the returned bytes is performed with the charset named by - * {@link #getCharset()}. - *

- * - * @param array - * a byte[] to convert to Hex characters - * @return A byte[] containing the bytes of the hexadecimal characters - * @since 1.7 No longer throws IllegalStateException if the charsetName is invalid. - * @see #encodeHex(byte[]) - */ - @Override - public byte[] encode(final byte[] array) { - return encodeHexString(array).getBytes(this.getCharset()); - } - - /** - * Converts a String or an array of bytes into an array of characters representing the hexadecimal values of each - * byte in order. The returned array will be double the length of the passed String or array, as it takes two - * characters to represent any given byte. - *

- * The conversion from hexadecimal characters to bytes to be encoded to performed with the charset named by - * {@link #getCharset()}. - *

- * - * @param object - * a String, or byte[] to convert to Hex characters - * @return A char[] containing hexadecimal characters - * @throws EncoderException - * Thrown if the given object is not a String or byte[] - * @see #encodeHex(byte[]) - */ - @Override - public Object encode(final Object object) throws EncoderException { - try { - final byte[] byteArray = object instanceof String ? - ((String) object).getBytes(this.getCharset()) : (byte[]) object; - return encodeHex(byteArray); - } catch (final ClassCastException e) { - throw new EncoderException(e.getMessage(), e); - } - } - - /** - * Gets the charset. - * - * @return the charset. - * @since 1.7 - */ - public Charset getCharset() { - return this.charset; - } - - /** - * Gets the charset name. - * - * @return the charset name. - * @since 1.4 - */ - public String getCharsetName() { - return this.charset.name(); - } - - /** - * Returns a string representation of the object, which includes the charset name. - * - * @return a string representation of the object. - */ - @Override - public String toString() { - return super.toString() + "[charsetName=" + this.charset + "]"; - } -} diff --git a/src/org/apache/commons/codec/binary/StringUtils.java b/src/org/apache/commons/codec/binary/StringUtils.java deleted file mode 100644 index 1954103b..00000000 --- a/src/org/apache/commons/codec/binary/StringUtils.java +++ /dev/null @@ -1,386 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.codec.binary; - -import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; - -import org.apache.commons.codec.CharEncoding; -import org.apache.commons.codec.Charsets; - -/** - * Converts String to and from bytes using the encodings required by the Java specification. These encodings are - * specified in - * Standard charsets. - * - *

This class is immutable and thread-safe.

- * - * @see CharEncoding - * @see Standard charsets - * @version $Id: StringUtils.java 1634456 2014-10-27 05:26:56Z ggregory $ - * @since 1.4 - */ -public class StringUtils { - - /** - *

- * Compares two CharSequences, returning true if they represent equal sequences of characters. - *

- * - *

- * nulls are handled without exceptions. Two null references are considered to be equal. - * The comparison is case sensitive. - *

- * - *
-     * StringUtils.equals(null, null)   = true
-     * StringUtils.equals(null, "abc")  = false
-     * StringUtils.equals("abc", null)  = false
-     * StringUtils.equals("abc", "abc") = true
-     * StringUtils.equals("abc", "ABC") = false
-     * 
- * - *

- * Copied from Apache Commons Lang r1583482 on April 10, 2014 (day of 3.3.2 release). - *

- * - * @see Object#equals(Object) - * @param cs1 - * the first CharSequence, may be null - * @param cs2 - * the second CharSequence, may be null - * @return true if the CharSequences are equal (case-sensitive), or both null - * @since 1.10 - */ - public static boolean equals(final CharSequence cs1, final CharSequence cs2) { - if (cs1 == cs2) { - return true; - } - if (cs1 == null || cs2 == null) { - return false; - } - if (cs1 instanceof String && cs2 instanceof String) { - return cs1.equals(cs2); - } - return CharSequenceUtils.regionMatches(cs1, false, 0, cs2, 0, Math.max(cs1.length(), cs2.length())); - } - - /** - * Calls {@link String#getBytes(Charset)} - * - * @param string - * The string to encode (if null, return null). - * @param charset - * The {@link Charset} to encode the String - * @return the encoded bytes - */ - private static byte[] getBytes(final String string, final Charset charset) { - if (string == null) { - return null; - } - return string.getBytes(charset); - } - - /** - * Encodes the given string into a sequence of bytes using the ISO-8859-1 charset, storing the result into a new - * byte array. - * - * @param string - * the String to encode, may be null - * @return encoded bytes, or null if the input string was null - * @throws NullPointerException - * Thrown if {@link Charsets#ISO_8859_1} is not initialized, which should never happen since it is - * required by the Java platform specification. - * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException - * @see Standard charsets - * @see #getBytesUnchecked(String, String) - */ - public static byte[] getBytesIso8859_1(final String string) { - return getBytes(string, Charsets.ISO_8859_1); - } - - - /** - * Encodes the given string into a sequence of bytes using the named charset, storing the result into a new byte - * array. - *

- * This method catches {@link UnsupportedEncodingException} and rethrows it as {@link IllegalStateException}, which - * should never happen for a required charset name. Use this method when the encoding is required to be in the JRE. - *

- * - * @param string - * the String to encode, may be null - * @param charsetName - * The name of a required {@link java.nio.charset.Charset} - * @return encoded bytes, or null if the input string was null - * @throws IllegalStateException - * Thrown when a {@link UnsupportedEncodingException} is caught, which should never happen for a - * required charset name. - * @see CharEncoding - * @see String#getBytes(String) - */ - public static byte[] getBytesUnchecked(final String string, final String charsetName) { - if (string == null) { - return null; - } - try { - return string.getBytes(charsetName); - } catch (final UnsupportedEncodingException e) { - throw StringUtils.newIllegalStateException(charsetName, e); - } - } - - /** - * Encodes the given string into a sequence of bytes using the US-ASCII charset, storing the result into a new byte - * array. - * - * @param string - * the String to encode, may be null - * @return encoded bytes, or null if the input string was null - * @throws NullPointerException - * Thrown if {@link Charsets#US_ASCII} is not initialized, which should never happen since it is - * required by the Java platform specification. - * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException - * @see Standard charsets - * @see #getBytesUnchecked(String, String) - */ - public static byte[] getBytesUsAscii(final String string) { - return getBytes(string, Charsets.US_ASCII); - } - - /** - * Encodes the given string into a sequence of bytes using the UTF-16 charset, storing the result into a new byte - * array. - * - * @param string - * the String to encode, may be null - * @return encoded bytes, or null if the input string was null - * @throws NullPointerException - * Thrown if {@link Charsets#UTF_16} is not initialized, which should never happen since it is - * required by the Java platform specification. - * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException - * @see Standard charsets - * @see #getBytesUnchecked(String, String) - */ - public static byte[] getBytesUtf16(final String string) { - return getBytes(string, Charsets.UTF_16); - } - - /** - * Encodes the given string into a sequence of bytes using the UTF-16BE charset, storing the result into a new byte - * array. - * - * @param string - * the String to encode, may be null - * @return encoded bytes, or null if the input string was null - * @throws NullPointerException - * Thrown if {@link Charsets#UTF_16BE} is not initialized, which should never happen since it is - * required by the Java platform specification. - * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException - * @see Standard charsets - * @see #getBytesUnchecked(String, String) - */ - public static byte[] getBytesUtf16Be(final String string) { - return getBytes(string, Charsets.UTF_16BE); - } - - /** - * Encodes the given string into a sequence of bytes using the UTF-16LE charset, storing the result into a new byte - * array. - * - * @param string - * the String to encode, may be null - * @return encoded bytes, or null if the input string was null - * @throws NullPointerException - * Thrown if {@link Charsets#UTF_16LE} is not initialized, which should never happen since it is - * required by the Java platform specification. - * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException - * @see Standard charsets - * @see #getBytesUnchecked(String, String) - */ - public static byte[] getBytesUtf16Le(final String string) { - return getBytes(string, Charsets.UTF_16LE); - } - - /** - * Encodes the given string into a sequence of bytes using the UTF-8 charset, storing the result into a new byte - * array. - * - * @param string - * the String to encode, may be null - * @return encoded bytes, or null if the input string was null - * @throws NullPointerException - * Thrown if {@link Charsets#UTF_8} is not initialized, which should never happen since it is - * required by the Java platform specification. - * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException - * @see Standard charsets - * @see #getBytesUnchecked(String, String) - */ - public static byte[] getBytesUtf8(final String string) { - return getBytes(string, Charsets.UTF_8); - } - - private static IllegalStateException newIllegalStateException(final String charsetName, - final UnsupportedEncodingException e) { - return new IllegalStateException(charsetName + ": " + e); - } - - /** - * Constructs a new String by decoding the specified array of bytes using the given charset. - * - * @param bytes - * The bytes to be decoded into characters - * @param charset - * The {@link Charset} to encode the String - * @return A new String decoded from the specified array of bytes using the given charset, - * or null if the input byte array was null. - * @throws NullPointerException - * Thrown if {@link Charsets#UTF_8} is not initialized, which should never happen since it is - * required by the Java platform specification. - */ - private static String newString(final byte[] bytes, final Charset charset) { - return bytes == null ? null : new String(bytes, charset); - } - - /** - * Constructs a new String by decoding the specified array of bytes using the given charset. - *

- * This method catches {@link UnsupportedEncodingException} and re-throws it as {@link IllegalStateException}, which - * should never happen for a required charset name. Use this method when the encoding is required to be in the JRE. - *

- * - * @param bytes - * The bytes to be decoded into characters, may be null - * @param charsetName - * The name of a required {@link java.nio.charset.Charset} - * @return A new String decoded from the specified array of bytes using the given charset, - * or null if the input byte array was null. - * @throws IllegalStateException - * Thrown when a {@link UnsupportedEncodingException} is caught, which should never happen for a - * required charset name. - * @see CharEncoding - * @see String#String(byte[], String) - */ - public static String newString(final byte[] bytes, final String charsetName) { - if (bytes == null) { - return null; - } - try { - return new String(bytes, charsetName); - } catch (final UnsupportedEncodingException e) { - throw StringUtils.newIllegalStateException(charsetName, e); - } - } - - /** - * Constructs a new String by decoding the specified array of bytes using the ISO-8859-1 charset. - * - * @param bytes - * The bytes to be decoded into characters, may be null - * @return A new String decoded from the specified array of bytes using the ISO-8859-1 charset, or - * null if the input byte array was null. - * @throws NullPointerException - * Thrown if {@link Charsets#ISO_8859_1} is not initialized, which should never happen since it is - * required by the Java platform specification. - * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException - */ - public static String newStringIso8859_1(final byte[] bytes) { - return new String(bytes, Charsets.ISO_8859_1); - } - - /** - * Constructs a new String by decoding the specified array of bytes using the US-ASCII charset. - * - * @param bytes - * The bytes to be decoded into characters - * @return A new String decoded from the specified array of bytes using the US-ASCII charset, - * or null if the input byte array was null. - * @throws NullPointerException - * Thrown if {@link Charsets#US_ASCII} is not initialized, which should never happen since it is - * required by the Java platform specification. - * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException - */ - public static String newStringUsAscii(final byte[] bytes) { - return new String(bytes, Charsets.US_ASCII); - } - - /** - * Constructs a new String by decoding the specified array of bytes using the UTF-16 charset. - * - * @param bytes - * The bytes to be decoded into characters - * @return A new String decoded from the specified array of bytes using the UTF-16 charset - * or null if the input byte array was null. - * @throws NullPointerException - * Thrown if {@link Charsets#UTF_16} is not initialized, which should never happen since it is - * required by the Java platform specification. - * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException - */ - public static String newStringUtf16(final byte[] bytes) { - return new String(bytes, Charsets.UTF_16); - } - - /** - * Constructs a new String by decoding the specified array of bytes using the UTF-16BE charset. - * - * @param bytes - * The bytes to be decoded into characters - * @return A new String decoded from the specified array of bytes using the UTF-16BE charset, - * or null if the input byte array was null. - * @throws NullPointerException - * Thrown if {@link Charsets#UTF_16BE} is not initialized, which should never happen since it is - * required by the Java platform specification. - * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException - */ - public static String newStringUtf16Be(final byte[] bytes) { - return new String(bytes, Charsets.UTF_16BE); - } - - /** - * Constructs a new String by decoding the specified array of bytes using the UTF-16LE charset. - * - * @param bytes - * The bytes to be decoded into characters - * @return A new String decoded from the specified array of bytes using the UTF-16LE charset, - * or null if the input byte array was null. - * @throws NullPointerException - * Thrown if {@link Charsets#UTF_16LE} is not initialized, which should never happen since it is - * required by the Java platform specification. - * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException - */ - public static String newStringUtf16Le(final byte[] bytes) { - return new String(bytes, Charsets.UTF_16LE); - } - - /** - * Constructs a new String by decoding the specified array of bytes using the UTF-8 charset. - * - * @param bytes - * The bytes to be decoded into characters - * @return A new String decoded from the specified array of bytes using the UTF-8 charset, - * or null if the input byte array was null. - * @throws NullPointerException - * Thrown if {@link Charsets#UTF_8} is not initialized, which should never happen since it is - * required by the Java platform specification. - * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException - */ - public static String newStringUtf8(final byte[] bytes) { - return newString(bytes, Charsets.UTF_8); - } - -} diff --git a/src/org/apache/commons/codec/binary/package.html b/src/org/apache/commons/codec/binary/package.html deleted file mode 100644 index ee97d94d..00000000 --- a/src/org/apache/commons/codec/binary/package.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - Base64, Base32, Binary, and Hexadecimal String encoding and decoding. - - diff --git a/src/org/apache/commons/codec/digest/B64.java b/src/org/apache/commons/codec/digest/B64.java deleted file mode 100644 index 951fc2b7..00000000 --- a/src/org/apache/commons/codec/digest/B64.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.codec.digest; - -import java.util.Random; - -/** - * Base64 like method to convert binary bytes into ASCII chars. - * - * TODO: Can Base64 be reused? - * - *

- * This class is immutable and thread-safe. - *

- * - * @version $Id: B64.java 1435550 2013-01-19 14:09:52Z tn $ - * @since 1.7 - */ -class B64 { - - /** - * Table with characters for Base64 transformation. - */ - static final String B64T = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - - /** - * Base64 like conversion of bytes to ASCII chars. - * - * @param b2 - * A byte from the result. - * @param b1 - * A byte from the result. - * @param b0 - * A byte from the result. - * @param outLen - * The number of expected output chars. - * @param buffer - * Where the output chars is appended to. - */ - static void b64from24bit(final byte b2, final byte b1, final byte b0, final int outLen, - final StringBuilder buffer) { - // The bit masking is necessary because the JVM byte type is signed! - int w = ((b2 << 16) & 0x00ffffff) | ((b1 << 8) & 0x00ffff) | (b0 & 0xff); - // It's effectively a "for" loop but kept to resemble the original C code. - int n = outLen; - while (n-- > 0) { - buffer.append(B64T.charAt(w & 0x3f)); - w >>= 6; - } - } - - /** - * Generates a string of random chars from the B64T set. - * - * @param num - * Number of chars to generate. - */ - static String getRandomSalt(final int num) { - final StringBuilder saltString = new StringBuilder(); - for (int i = 1; i <= num; i++) { - saltString.append(B64T.charAt(new Random().nextInt(B64T.length()))); - } - return saltString.toString(); - } -} diff --git a/src/org/apache/commons/codec/digest/Crypt.java b/src/org/apache/commons/codec/digest/Crypt.java deleted file mode 100644 index b7c41b56..00000000 --- a/src/org/apache/commons/codec/digest/Crypt.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.codec.digest; - -import org.apache.commons.codec.Charsets; - -/** - * GNU libc crypt(3) compatible hash method. - *

- * See {@link #crypt(String, String)} for further details. - *

- * This class is immutable and thread-safe. - * - * @version $Id: Crypt.java 1635205 2014-10-29 17:14:16Z ggregory $ - * @since 1.7 - */ -public class Crypt { - - /** - * Encrypts a password in a crypt(3) compatible way. - *

- * A random salt and the default algorithm (currently SHA-512) are used. See {@link #crypt(String, String)} for - * details. - * - * @param keyBytes - * plaintext password - * @return hash value - * @throws RuntimeException - * when a {@link java.security.NoSuchAlgorithmException} is caught. - */ - public static String crypt(final byte[] keyBytes) { - return crypt(keyBytes, null); - } - - /** - * Encrypts a password in a crypt(3) compatible way. - *

- * If no salt is provided, a random salt and the default algorithm (currently SHA-512) will be used. See - * {@link #crypt(String, String)} for details. - * - * @param keyBytes - * plaintext password - * @param salt - * salt value - * @return hash value - * @throws IllegalArgumentException - * if the salt does not match the allowed pattern - * @throws RuntimeException - * when a {@link java.security.NoSuchAlgorithmException} is caught. - */ - public static String crypt(final byte[] keyBytes, final String salt) { - if (salt == null) { - return Sha2Crypt.sha512Crypt(keyBytes); - } else if (salt.startsWith(Sha2Crypt.SHA512_PREFIX)) { - return Sha2Crypt.sha512Crypt(keyBytes, salt); - } else if (salt.startsWith(Sha2Crypt.SHA256_PREFIX)) { - return Sha2Crypt.sha256Crypt(keyBytes, salt); - } else if (salt.startsWith(Md5Crypt.MD5_PREFIX)) { - return Md5Crypt.md5Crypt(keyBytes, salt); - } else { - return UnixCrypt.crypt(keyBytes, salt); - } - } - - /** - * Calculates the digest using the strongest crypt(3) algorithm. - *

- * A random salt and the default algorithm (currently SHA-512) are used. - * - * @see #crypt(String, String) - * @param key - * plaintext password - * @return hash value - * @throws RuntimeException - * when a {@link java.security.NoSuchAlgorithmException} is caught. - */ - public static String crypt(final String key) { - return crypt(key, null); - } - - /** - * Encrypts a password in a crypt(3) compatible way. - *

- * The exact algorithm depends on the format of the salt string: - *

    - *
  • SHA-512 salts start with {@code $6$} and are up to 16 chars long. - *
  • SHA-256 salts start with {@code $5$} and are up to 16 chars long - *
  • MD5 salts start with {@code $1$} and are up to 8 chars long - *
  • DES, the traditional UnixCrypt algorithm is used with only 2 chars - *
  • Only the first 8 chars of the passwords are used in the DES algorithm! - *
- * The magic strings {@code "$apr1$"} and {@code "$2a$"} are not recognized by this method as its output should be - * identical with that of the libc implementation. - *

- * The rest of the salt string is drawn from the set {@code [a-zA-Z0-9./]} and is cut at the maximum length of if a - * {@code "$"} sign is encountered. It is therefore valid to enter a complete hash value as salt to e.g. verify a - * password with: - * - *

-     * storedPwd.equals(crypt(enteredPwd, storedPwd))
-     * 
- *

- * The resulting string starts with the marker string ({@code $6$}), continues with the salt value and ends with a - * {@code "$"} sign followed by the actual hash value. For DES the string only contains the salt and actual hash. - * It's total length is dependent on the algorithm used: - *

    - *
  • SHA-512: 106 chars - *
  • SHA-256: 63 chars - *
  • MD5: 34 chars - *
  • DES: 13 chars - *
- *

- * Example: - * - *

-     *      crypt("secret", "$1$xxxx") => "$1$xxxx$aMkevjfEIpa35Bh3G4bAc."
-     *      crypt("secret", "xx") => "xxWAum7tHdIUw"
-     * 
- *

- * This method comes in a variation that accepts a byte[] array to support input strings that are not encoded in - * UTF-8 but e.g. in ISO-8859-1 where equal characters result in different byte values. - * - * @see "The man page of the libc crypt (3) function." - * @param key - * plaintext password as entered by the used - * @param salt - * salt value - * @return hash value, i.e. encrypted password including the salt string - * @throws IllegalArgumentException - * if the salt does not match the allowed pattern - * @throws RuntimeException - * when a {@link java.security.NoSuchAlgorithmException} is caught. * - */ - public static String crypt(final String key, final String salt) { - return crypt(key.getBytes(Charsets.UTF_8), salt); - } -} diff --git a/src/org/apache/commons/codec/digest/DigestUtils.java b/src/org/apache/commons/codec/digest/DigestUtils.java deleted file mode 100644 index e1d9e2ac..00000000 --- a/src/org/apache/commons/codec/digest/DigestUtils.java +++ /dev/null @@ -1,819 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.codec.digest; - -import java.io.IOException; -import java.io.InputStream; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -import org.apache.commons.codec.binary.Hex; -import org.apache.commons.codec.binary.StringUtils; - -/** - * Operations to simplify common {@link java.security.MessageDigest} tasks. - * This class is immutable and thread-safe. - * - * @version $Id: DigestUtils.java 1634433 2014-10-27 01:10:47Z ggregory $ - */ -public class DigestUtils { - - private static final int STREAM_BUFFER_LENGTH = 1024; - - /** - * Read through an InputStream and returns the digest for the data - * - * @param digest - * The MessageDigest to use (e.g. MD5) - * @param data - * Data to digest - * @return the digest - * @throws IOException - * On error reading from the stream - */ - private static byte[] digest(final MessageDigest digest, final InputStream data) throws IOException { - return updateDigest(digest, data).digest(); - } - - /** - * Returns a MessageDigest for the given algorithm. - * - * @param algorithm - * the name of the algorithm requested. See Appendix A in the Java Cryptography Architecture Reference Guide for information about standard - * algorithm names. - * @return A digest instance. - * @see MessageDigest#getInstance(String) - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught. - */ - public static MessageDigest getDigest(final String algorithm) { - try { - return MessageDigest.getInstance(algorithm); - } catch (final NoSuchAlgorithmException e) { - throw new IllegalArgumentException(e); - } - } - - /** - * Returns an MD2 MessageDigest. - * - * @return An MD2 digest instance. - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught, which should never happen because MD2 is a - * built-in algorithm - * @see MessageDigestAlgorithms#MD2 - * @since 1.7 - */ - public static MessageDigest getMd2Digest() { - return getDigest(MessageDigestAlgorithms.MD2); - } - - /** - * Returns an MD5 MessageDigest. - * - * @return An MD5 digest instance. - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught, which should never happen because MD5 is a - * built-in algorithm - * @see MessageDigestAlgorithms#MD5 - */ - public static MessageDigest getMd5Digest() { - return getDigest(MessageDigestAlgorithms.MD5); - } - - /** - * Returns an SHA-1 digest. - * - * @return An SHA-1 digest instance. - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught, which should never happen because SHA-1 is a - * built-in algorithm - * @see MessageDigestAlgorithms#SHA_1 - * @since 1.7 - */ - public static MessageDigest getSha1Digest() { - return getDigest(MessageDigestAlgorithms.SHA_1); - } - - /** - * Returns an SHA-256 digest. - *

- * Throws a RuntimeException on JRE versions prior to 1.4.0. - *

- * - * @return An SHA-256 digest instance. - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught, which should never happen because SHA-256 is a - * built-in algorithm - * @see MessageDigestAlgorithms#SHA_256 - */ - public static MessageDigest getSha256Digest() { - return getDigest(MessageDigestAlgorithms.SHA_256); - } - - /** - * Returns an SHA-384 digest. - *

- * Throws a RuntimeException on JRE versions prior to 1.4.0. - *

- * - * @return An SHA-384 digest instance. - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught, which should never happen because SHA-384 is a - * built-in algorithm - * @see MessageDigestAlgorithms#SHA_384 - */ - public static MessageDigest getSha384Digest() { - return getDigest(MessageDigestAlgorithms.SHA_384); - } - - /** - * Returns an SHA-512 digest. - *

- * Throws a RuntimeException on JRE versions prior to 1.4.0. - *

- * - * @return An SHA-512 digest instance. - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught, which should never happen because SHA-512 is a - * built-in algorithm - * @see MessageDigestAlgorithms#SHA_512 - */ - public static MessageDigest getSha512Digest() { - return getDigest(MessageDigestAlgorithms.SHA_512); - } - - /** - * Returns an SHA-1 digest. - * - * @return An SHA-1 digest instance. - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught - * @deprecated Use {@link #getSha1Digest()} - */ - @Deprecated - public static MessageDigest getShaDigest() { - return getSha1Digest(); - } - - /** - * Calculates the MD2 digest and returns the value as a 16 element byte[]. - * - * @param data - * Data to digest - * @return MD2 digest - * @since 1.7 - */ - public static byte[] md2(final byte[] data) { - return getMd2Digest().digest(data); - } - - /** - * Calculates the MD2 digest and returns the value as a 16 element byte[]. - * - * @param data - * Data to digest - * @return MD2 digest - * @throws IOException - * On error reading from the stream - * @since 1.7 - */ - public static byte[] md2(final InputStream data) throws IOException { - return digest(getMd2Digest(), data); - } - - /** - * Calculates the MD2 digest and returns the value as a 16 element byte[]. - * - * @param data - * Data to digest; converted to bytes using {@link StringUtils#getBytesUtf8(String)} - * @return MD2 digest - * @since 1.7 - */ - public static byte[] md2(final String data) { - return md2(StringUtils.getBytesUtf8(data)); - } - - /** - * Calculates the MD2 digest and returns the value as a 32 character hex string. - * - * @param data - * Data to digest - * @return MD2 digest as a hex string - * @since 1.7 - */ - public static String md2Hex(final byte[] data) { - return Hex.encodeHexString(md2(data)); - } - - /** - * Calculates the MD2 digest and returns the value as a 32 character hex string. - * - * @param data - * Data to digest - * @return MD2 digest as a hex string - * @throws IOException - * On error reading from the stream - * @since 1.7 - */ - public static String md2Hex(final InputStream data) throws IOException { - return Hex.encodeHexString(md2(data)); - } - - /** - * Calculates the MD2 digest and returns the value as a 32 character hex string. - * - * @param data - * Data to digest - * @return MD2 digest as a hex string - * @since 1.7 - */ - public static String md2Hex(final String data) { - return Hex.encodeHexString(md2(data)); - } - - /** - * Calculates the MD5 digest and returns the value as a 16 element byte[]. - * - * @param data - * Data to digest - * @return MD5 digest - */ - public static byte[] md5(final byte[] data) { - return getMd5Digest().digest(data); - } - - /** - * Calculates the MD5 digest and returns the value as a 16 element byte[]. - * - * @param data - * Data to digest - * @return MD5 digest - * @throws IOException - * On error reading from the stream - * @since 1.4 - */ - public static byte[] md5(final InputStream data) throws IOException { - return digest(getMd5Digest(), data); - } - - /** - * Calculates the MD5 digest and returns the value as a 16 element byte[]. - * - * @param data - * Data to digest; converted to bytes using {@link StringUtils#getBytesUtf8(String)} - * @return MD5 digest - */ - public static byte[] md5(final String data) { - return md5(StringUtils.getBytesUtf8(data)); - } - - /** - * Calculates the MD5 digest and returns the value as a 32 character hex string. - * - * @param data - * Data to digest - * @return MD5 digest as a hex string - */ - public static String md5Hex(final byte[] data) { - return Hex.encodeHexString(md5(data)); - } - - /** - * Calculates the MD5 digest and returns the value as a 32 character hex string. - * - * @param data - * Data to digest - * @return MD5 digest as a hex string - * @throws IOException - * On error reading from the stream - * @since 1.4 - */ - public static String md5Hex(final InputStream data) throws IOException { - return Hex.encodeHexString(md5(data)); - } - - /** - * Calculates the MD5 digest and returns the value as a 32 character hex string. - * - * @param data - * Data to digest - * @return MD5 digest as a hex string - */ - public static String md5Hex(final String data) { - return Hex.encodeHexString(md5(data)); - } - - /** - * Calculates the SHA-1 digest and returns the value as a byte[]. - * - * @param data - * Data to digest - * @return SHA-1 digest - * @deprecated Use {@link #sha1(byte[])} - */ - @Deprecated - public static byte[] sha(final byte[] data) { - return sha1(data); - } - - /** - * Calculates the SHA-1 digest and returns the value as a byte[]. - * - * @param data - * Data to digest - * @return SHA-1 digest - * @throws IOException - * On error reading from the stream - * @since 1.4 - * @deprecated Use {@link #sha1(InputStream)} - */ - @Deprecated - public static byte[] sha(final InputStream data) throws IOException { - return sha1(data); - } - - /** - * Calculates the SHA-1 digest and returns the value as a byte[]. - * - * @param data - * Data to digest - * @return SHA-1 digest - * @deprecated Use {@link #sha1(String)} - */ - @Deprecated - public static byte[] sha(final String data) { - return sha1(data); - } - - /** - * Calculates the SHA-1 digest and returns the value as a byte[]. - * - * @param data - * Data to digest - * @return SHA-1 digest - * @since 1.7 - */ - public static byte[] sha1(final byte[] data) { - return getSha1Digest().digest(data); - } - - /** - * Calculates the SHA-1 digest and returns the value as a byte[]. - * - * @param data - * Data to digest - * @return SHA-1 digest - * @throws IOException - * On error reading from the stream - * @since 1.7 - */ - public static byte[] sha1(final InputStream data) throws IOException { - return digest(getSha1Digest(), data); - } - - /** - * Calculates the SHA-1 digest and returns the value as a byte[]. - * - * @param data - * Data to digest; converted to bytes using {@link StringUtils#getBytesUtf8(String)} - * @return SHA-1 digest - */ - public static byte[] sha1(final String data) { - return sha1(StringUtils.getBytesUtf8(data)); - } - - /** - * Calculates the SHA-1 digest and returns the value as a hex string. - * - * @param data - * Data to digest - * @return SHA-1 digest as a hex string - * @since 1.7 - */ - public static String sha1Hex(final byte[] data) { - return Hex.encodeHexString(sha1(data)); - } - - /** - * Calculates the SHA-1 digest and returns the value as a hex string. - * - * @param data - * Data to digest - * @return SHA-1 digest as a hex string - * @throws IOException - * On error reading from the stream - * @since 1.7 - */ - public static String sha1Hex(final InputStream data) throws IOException { - return Hex.encodeHexString(sha1(data)); - } - - /** - * Calculates the SHA-1 digest and returns the value as a hex string. - * - * @param data - * Data to digest - * @return SHA-1 digest as a hex string - * @since 1.7 - */ - public static String sha1Hex(final String data) { - return Hex.encodeHexString(sha1(data)); - } - - /** - * Calculates the SHA-256 digest and returns the value as a byte[]. - *

- * Throws a RuntimeException on JRE versions prior to 1.4.0. - *

- * - * @param data - * Data to digest - * @return SHA-256 digest - * @since 1.4 - */ - public static byte[] sha256(final byte[] data) { - return getSha256Digest().digest(data); - } - - /** - * Calculates the SHA-256 digest and returns the value as a byte[]. - *

- * Throws a RuntimeException on JRE versions prior to 1.4.0. - *

- * - * @param data - * Data to digest - * @return SHA-256 digest - * @throws IOException - * On error reading from the stream - * @since 1.4 - */ - public static byte[] sha256(final InputStream data) throws IOException { - return digest(getSha256Digest(), data); - } - - /** - * Calculates the SHA-256 digest and returns the value as a byte[]. - *

- * Throws a RuntimeException on JRE versions prior to 1.4.0. - *

- * - * @param data - * Data to digest; converted to bytes using {@link StringUtils#getBytesUtf8(String)} - * @return SHA-256 digest - * @since 1.4 - */ - public static byte[] sha256(final String data) { - return sha256(StringUtils.getBytesUtf8(data)); - } - - /** - * Calculates the SHA-256 digest and returns the value as a hex string. - *

- * Throws a RuntimeException on JRE versions prior to 1.4.0. - *

- * - * @param data - * Data to digest - * @return SHA-256 digest as a hex string - * @since 1.4 - */ - public static String sha256Hex(final byte[] data) { - return Hex.encodeHexString(sha256(data)); - } - - /** - * Calculates the SHA-256 digest and returns the value as a hex string. - *

- * Throws a RuntimeException on JRE versions prior to 1.4.0. - *

- * - * @param data - * Data to digest - * @return SHA-256 digest as a hex string - * @throws IOException - * On error reading from the stream - * @since 1.4 - */ - public static String sha256Hex(final InputStream data) throws IOException { - return Hex.encodeHexString(sha256(data)); - } - - /** - * Calculates the SHA-256 digest and returns the value as a hex string. - *

- * Throws a RuntimeException on JRE versions prior to 1.4.0. - *

- * - * @param data - * Data to digest - * @return SHA-256 digest as a hex string - * @since 1.4 - */ - public static String sha256Hex(final String data) { - return Hex.encodeHexString(sha256(data)); - } - - /** - * Calculates the SHA-384 digest and returns the value as a byte[]. - *

- * Throws a RuntimeException on JRE versions prior to 1.4.0. - *

- * - * @param data - * Data to digest - * @return SHA-384 digest - * @since 1.4 - */ - public static byte[] sha384(final byte[] data) { - return getSha384Digest().digest(data); - } - - /** - * Calculates the SHA-384 digest and returns the value as a byte[]. - *

- * Throws a RuntimeException on JRE versions prior to 1.4.0. - *

- * - * @param data - * Data to digest - * @return SHA-384 digest - * @throws IOException - * On error reading from the stream - * @since 1.4 - */ - public static byte[] sha384(final InputStream data) throws IOException { - return digest(getSha384Digest(), data); - } - - /** - * Calculates the SHA-384 digest and returns the value as a byte[]. - *

- * Throws a RuntimeException on JRE versions prior to 1.4.0. - *

- * - * @param data - * Data to digest; converted to bytes using {@link StringUtils#getBytesUtf8(String)} - * @return SHA-384 digest - * @since 1.4 - */ - public static byte[] sha384(final String data) { - return sha384(StringUtils.getBytesUtf8(data)); - } - - /** - * Calculates the SHA-384 digest and returns the value as a hex string. - *

- * Throws a RuntimeException on JRE versions prior to 1.4.0. - *

- * - * @param data - * Data to digest - * @return SHA-384 digest as a hex string - * @since 1.4 - */ - public static String sha384Hex(final byte[] data) { - return Hex.encodeHexString(sha384(data)); - } - - /** - * Calculates the SHA-384 digest and returns the value as a hex string. - *

- * Throws a RuntimeException on JRE versions prior to 1.4.0. - *

- * - * @param data - * Data to digest - * @return SHA-384 digest as a hex string - * @throws IOException - * On error reading from the stream - * @since 1.4 - */ - public static String sha384Hex(final InputStream data) throws IOException { - return Hex.encodeHexString(sha384(data)); - } - - /** - * Calculates the SHA-384 digest and returns the value as a hex string. - *

- * Throws a RuntimeException on JRE versions prior to 1.4.0. - *

- * - * @param data - * Data to digest - * @return SHA-384 digest as a hex string - * @since 1.4 - */ - public static String sha384Hex(final String data) { - return Hex.encodeHexString(sha384(data)); - } - - /** - * Calculates the SHA-512 digest and returns the value as a byte[]. - *

- * Throws a RuntimeException on JRE versions prior to 1.4.0. - *

- * - * @param data - * Data to digest - * @return SHA-512 digest - * @since 1.4 - */ - public static byte[] sha512(final byte[] data) { - return getSha512Digest().digest(data); - } - - /** - * Calculates the SHA-512 digest and returns the value as a byte[]. - *

- * Throws a RuntimeException on JRE versions prior to 1.4.0. - *

- * - * @param data - * Data to digest - * @return SHA-512 digest - * @throws IOException - * On error reading from the stream - * @since 1.4 - */ - public static byte[] sha512(final InputStream data) throws IOException { - return digest(getSha512Digest(), data); - } - - /** - * Calculates the SHA-512 digest and returns the value as a byte[]. - *

- * Throws a RuntimeException on JRE versions prior to 1.4.0. - *

- * - * @param data - * Data to digest; converted to bytes using {@link StringUtils#getBytesUtf8(String)} - * @return SHA-512 digest - * @since 1.4 - */ - public static byte[] sha512(final String data) { - return sha512(StringUtils.getBytesUtf8(data)); - } - - /** - * Calculates the SHA-512 digest and returns the value as a hex string. - *

- * Throws a RuntimeException on JRE versions prior to 1.4.0. - *

- * - * @param data - * Data to digest - * @return SHA-512 digest as a hex string - * @since 1.4 - */ - public static String sha512Hex(final byte[] data) { - return Hex.encodeHexString(sha512(data)); - } - - /** - * Calculates the SHA-512 digest and returns the value as a hex string. - *

- * Throws a RuntimeException on JRE versions prior to 1.4.0. - *

- * - * @param data - * Data to digest - * @return SHA-512 digest as a hex string - * @throws IOException - * On error reading from the stream - * @since 1.4 - */ - public static String sha512Hex(final InputStream data) throws IOException { - return Hex.encodeHexString(sha512(data)); - } - - /** - * Calculates the SHA-512 digest and returns the value as a hex string. - *

- * Throws a RuntimeException on JRE versions prior to 1.4.0. - *

- * - * @param data - * Data to digest - * @return SHA-512 digest as a hex string - * @since 1.4 - */ - public static String sha512Hex(final String data) { - return Hex.encodeHexString(sha512(data)); - } - - /** - * Calculates the SHA-1 digest and returns the value as a hex string. - * - * @param data - * Data to digest - * @return SHA-1 digest as a hex string - * @deprecated Use {@link #sha1Hex(byte[])} - */ - @Deprecated - public static String shaHex(final byte[] data) { - return sha1Hex(data); - } - - /** - * Calculates the SHA-1 digest and returns the value as a hex string. - * - * @param data - * Data to digest - * @return SHA-1 digest as a hex string - * @throws IOException - * On error reading from the stream - * @since 1.4 - * @deprecated Use {@link #sha1Hex(InputStream)} - */ - @Deprecated - public static String shaHex(final InputStream data) throws IOException { - return sha1Hex(data); - } - - /** - * Calculates the SHA-1 digest and returns the value as a hex string. - * - * @param data - * Data to digest - * @return SHA-1 digest as a hex string - * @deprecated Use {@link #sha1Hex(String)} - */ - @Deprecated - public static String shaHex(final String data) { - return sha1Hex(data); - } - - /** - * Updates the given {@link MessageDigest}. - * - * @param messageDigest - * the {@link MessageDigest} to update - * @param valueToDigest - * the value to update the {@link MessageDigest} with - * @return the updated {@link MessageDigest} - * @since 1.7 - */ - public static MessageDigest updateDigest(final MessageDigest messageDigest, final byte[] valueToDigest) { - messageDigest.update(valueToDigest); - return messageDigest; - } - - /** - * Reads through an InputStream and updates the digest for the data - * - * @param digest - * The MessageDigest to use (e.g. MD5) - * @param data - * Data to digest - * @return the digest - * @throws IOException - * On error reading from the stream - * @since 1.8 - */ - public static MessageDigest updateDigest(final MessageDigest digest, final InputStream data) throws IOException { - final byte[] buffer = new byte[STREAM_BUFFER_LENGTH]; - int read = data.read(buffer, 0, STREAM_BUFFER_LENGTH); - - while (read > -1) { - digest.update(buffer, 0, read); - read = data.read(buffer, 0, STREAM_BUFFER_LENGTH); - } - - return digest; - } - - /** - * Updates the given {@link MessageDigest}. - * - * @param messageDigest - * the {@link MessageDigest} to update - * @param valueToDigest - * the value to update the {@link MessageDigest} with; - * converted to bytes using {@link StringUtils#getBytesUtf8(String)} - * @return the updated {@link MessageDigest} - * @since 1.7 - */ - public static MessageDigest updateDigest(final MessageDigest messageDigest, final String valueToDigest) { - messageDigest.update(StringUtils.getBytesUtf8(valueToDigest)); - return messageDigest; - } -} diff --git a/src/org/apache/commons/codec/digest/HmacAlgorithms.java b/src/org/apache/commons/codec/digest/HmacAlgorithms.java deleted file mode 100644 index fb84a3f9..00000000 --- a/src/org/apache/commons/codec/digest/HmacAlgorithms.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.codec.digest; - -/** - * Standard {@link HmacUtils} algorithm names from the Java Cryptography Architecture Standard Algorithm Name - * Documentation. - * - *

- * Note: Not all JCE implementations supports all algorithms in this enum. - *

- * - * @see Java Cryptography - * Architecture Standard Algorithm Name Documentation - * @since 1.10 - * @version $Id: HmacAlgorithms.java 1634405 2014-10-26 23:07:26Z ggregory $ - */ -public enum HmacAlgorithms { - - /** - * The HmacMD5 Message Authentication Code (MAC) algorithm specified in RFC 2104 and RFC 1321. - *

- * Every implementation of the Java platform is required to support this standard Mac algorithm. - *

- */ - HMAC_MD5("HmacMD5"), - - /** - * The HmacSHA1 Message Authentication Code (MAC) algorithm specified in RFC 2104 and FIPS PUB 180-2. - *

- * Every implementation of the Java platform is required to support this standard Mac algorithm. - *

- */ - HMAC_SHA_1("HmacSHA1"), - - /** - * The HmacSHA256 Message Authentication Code (MAC) algorithm specified in RFC 2104 and FIPS PUB 180-2. - *

- * Every implementation of the Java platform is required to support this standard Mac algorithm. - *

- */ - HMAC_SHA_256("HmacSHA256"), - - /** - * The HmacSHA384 Message Authentication Code (MAC) algorithm specified in RFC 2104 and FIPS PUB 180-2. - *

- * Every implementation of the Java platform is not required to support this Mac algorithm. - *

- */ - HMAC_SHA_384("HmacSHA384"), - - /** - * The HmacSHA512 Message Authentication Code (MAC) algorithm specified in RFC 2104 and FIPS PUB 180-2. - *

- * Every implementation of the Java platform is not required to support this Mac algorithm. - *

- */ - HMAC_SHA_512("HmacSHA512"); - - private final String algorithm; - - private HmacAlgorithms(final String algorithm) { - this.algorithm = algorithm; - } - - /** - * The algorithm name - * - * @see Java - * Cryptography Architecture Sun Providers Documentation - * @return The algorithm name ("HmacSHA512" for example) - */ - @Override - public String toString() { - return algorithm; - } - -} diff --git a/src/org/apache/commons/codec/digest/HmacUtils.java b/src/org/apache/commons/codec/digest/HmacUtils.java deleted file mode 100644 index 5cd1e514..00000000 --- a/src/org/apache/commons/codec/digest/HmacUtils.java +++ /dev/null @@ -1,794 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.codec.digest; - -import java.io.IOException; -import java.io.InputStream; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.NoSuchAlgorithmException; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - -import org.apache.commons.codec.binary.Hex; -import org.apache.commons.codec.binary.StringUtils; - -/** - * Simplifies common {@link javax.crypto.Mac} tasks. This class is immutable and thread-safe. - * - * - *

- * Note: Not all JCE implementations supports all algorithms. If not supported, an IllegalArgumentException is - * thrown. - *

- * - * @since 1.10 - * @version $Id: HmacUtils.java 1634411 2014-10-27 00:35:43Z ggregory $ - */ -public final class HmacUtils { - - private static final int STREAM_BUFFER_LENGTH = 1024; - - /** - * Returns an initialized Mac for the HmacMD5 algorithm. - *

- * Every implementation of the Java platform is required to support this standard Mac algorithm. - *

- * - * @param key - * They key for the keyed digest (must not be null) - * @return A Mac instance initialized with the given key. - * @see Mac#getInstance(String) - * @see Mac#init(Key) - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static Mac getHmacMd5(final byte[] key) { - return getInitializedMac(HmacAlgorithms.HMAC_MD5, key); - } - - /** - * Returns an initialized Mac for the HmacSHA1 algorithm. - *

- * Every implementation of the Java platform is required to support this standard Mac algorithm. - *

- * - * @param key - * They key for the keyed digest (must not be null) - * @return A Mac instance initialized with the given key. - * @see Mac#getInstance(String) - * @see Mac#init(Key) - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static Mac getHmacSha1(final byte[] key) { - return getInitializedMac(HmacAlgorithms.HMAC_SHA_1, key); - } - - /** - * Returns an initialized Mac for the HmacSHA256 algorithm. - *

- * Every implementation of the Java platform is required to support this standard Mac algorithm. - *

- * - * @param key - * They key for the keyed digest (must not be null) - * @return A Mac instance initialized with the given key. - * @see Mac#getInstance(String) - * @see Mac#init(Key) - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static Mac getHmacSha256(final byte[] key) { - return getInitializedMac(HmacAlgorithms.HMAC_SHA_256, key); - } - - /** - * Returns an initialized Mac for the HmacSHA384 algorithm. - *

- * Every implementation of the Java platform is not required to support this Mac algorithm. - *

- * - * @param key - * They key for the keyed digest (must not be null) - * @return A Mac instance initialized with the given key. - * @see Mac#getInstance(String) - * @see Mac#init(Key) - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static Mac getHmacSha384(final byte[] key) { - return getInitializedMac(HmacAlgorithms.HMAC_SHA_384, key); - } - - /** - * Returns an initialized Mac for the HmacSHA512 algorithm. - *

- * Every implementation of the Java platform is not required to support this Mac algorithm. - *

- * - * @param key - * They key for the keyed digest (must not be null) - * @return A Mac instance initialized with the given key. - * @see Mac#getInstance(String) - * @see Mac#init(Key) - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static Mac getHmacSha512(final byte[] key) { - return getInitializedMac(HmacAlgorithms.HMAC_SHA_512, key); - } - - /** - * Returns an initialized Mac for the given algorithm. - * - * @param algorithm - * the name of the algorithm requested. See Appendix - * A in the Java Cryptography Architecture Reference Guide for information about standard algorithm - * names. - * @param key - * They key for the keyed digest (must not be null) - * @return A Mac instance initialized with the given key. - * @see Mac#getInstance(String) - * @see Mac#init(Key) - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static Mac getInitializedMac(final HmacAlgorithms algorithm, final byte[] key) { - return getInitializedMac(algorithm.toString(), key); - } - - /** - * Returns an initialized Mac for the given algorithm. - * - * @param algorithm - * the name of the algorithm requested. See Appendix - * A in the Java Cryptography Architecture Reference Guide for information about standard algorithm - * names. - * @param key - * They key for the keyed digest (must not be null) - * @return A Mac instance initialized with the given key. - * @see Mac#getInstance(String) - * @see Mac#init(Key) - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static Mac getInitializedMac(final String algorithm, final byte[] key) { - - if (key == null) { - throw new IllegalArgumentException("Null key"); - } - - try { - final SecretKeySpec keySpec = new SecretKeySpec(key, algorithm); - final Mac mac = Mac.getInstance(algorithm); - mac.init(keySpec); - return mac; - } catch (final NoSuchAlgorithmException e) { - throw new IllegalArgumentException(e); - } catch (final InvalidKeyException e) { - throw new IllegalArgumentException(e); - } - } - - // hmacMd5 - - /** - * Returns a HmacMD5 Message Authentication Code (MAC) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest (maybe empty or null) - * @return HmacMD5 MAC for the given key and value - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static byte[] hmacMd5(final byte[] key, final byte[] valueToDigest) { - try { - return getHmacMd5(key).doFinal(valueToDigest); - } catch (final IllegalStateException e) { - // cannot happen - throw new IllegalArgumentException(e); - } - } - - /** - * Returns a HmacMD5 Message Authentication Code (MAC) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest - *

- * The InputStream must not be null and will not be closed - *

- * @return HmacMD5 MAC for the given key and value - * @throws IOException - * If an I/O error occurs. - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static byte[] hmacMd5(final byte[] key, final InputStream valueToDigest) throws IOException { - return updateHmac(getHmacMd5(key), valueToDigest).doFinal(); - } - - /** - * Returns a HmacMD5 Message Authentication Code (MAC) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest (maybe empty or null) - * @return HmacMD5 MAC for the given key and value - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static byte[] hmacMd5(final String key, final String valueToDigest) { - return hmacMd5(StringUtils.getBytesUtf8(key), StringUtils.getBytesUtf8(valueToDigest)); - } - - /** - * Returns a HmacMD5 Message Authentication Code (MAC) as a hex string (lowercase) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest (maybe empty or null) - * @return HmacMD5 MAC for the given key and value as a hex string (lowercase) - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static String hmacMd5Hex(final byte[] key, final byte[] valueToDigest) { - return Hex.encodeHexString(hmacMd5(key, valueToDigest)); - } - - /** - * Returns a HmacMD5 Message Authentication Code (MAC) as a hex string (lowercase) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest - *

- * The InputStream must not be null and will not be closed - *

- * @return HmacMD5 MAC for the given key and value as a hex string (lowercase) - * @throws IOException - * If an I/O error occurs. - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static String hmacMd5Hex(final byte[] key, final InputStream valueToDigest) throws IOException { - return Hex.encodeHexString(hmacMd5(key, valueToDigest)); - } - - /** - * Returns a HmacMD5 Message Authentication Code (MAC) as a hex string (lowercase) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest (maybe empty or null) - * @return HmacMD5 MAC for the given key and value as a hex string (lowercase) - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static String hmacMd5Hex(final String key, final String valueToDigest) { - return Hex.encodeHexString(hmacMd5(key, valueToDigest)); - } - - // hmacSha1 - - /** - * Returns a HmacSHA1 Message Authentication Code (MAC) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest (maybe empty or null) - * @return HmacSHA1 MAC for the given key and value - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static byte[] hmacSha1(final byte[] key, final byte[] valueToDigest) { - try { - return getHmacSha1(key).doFinal(valueToDigest); - } catch (final IllegalStateException e) { - // cannot happen - throw new IllegalArgumentException(e); - } - } - - /** - * Returns a HmacSHA1 Message Authentication Code (MAC) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest - *

- * The InputStream must not be null and will not be closed - *

- * @return HmacSHA1 MAC for the given key and value - * @throws IOException - * If an I/O error occurs. - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static byte[] hmacSha1(final byte[] key, final InputStream valueToDigest) throws IOException { - return updateHmac(getHmacSha1(key), valueToDigest).doFinal(); - } - - /** - * Returns a HmacSHA1 Message Authentication Code (MAC) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest (maybe empty or null) - * @return HmacSHA1 MAC for the given key and value - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static byte[] hmacSha1(final String key, final String valueToDigest) { - return hmacSha1(StringUtils.getBytesUtf8(key), StringUtils.getBytesUtf8(valueToDigest)); - } - - /** - * Returns a HmacSHA1 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest (maybe empty or null) - * @return HmacSHA1 MAC for the given key and value as hex string (lowercase) - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static String hmacSha1Hex(final byte[] key, final byte[] valueToDigest) { - return Hex.encodeHexString(hmacSha1(key, valueToDigest)); - } - - /** - * Returns a HmacSHA1 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest - *

- * The InputStream must not be null and will not be closed - *

- * @return HmacSHA1 MAC for the given key and value as hex string (lowercase) - * @throws IOException - * If an I/O error occurs. - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static String hmacSha1Hex(final byte[] key, final InputStream valueToDigest) throws IOException { - return Hex.encodeHexString(hmacSha1(key, valueToDigest)); - } - - /** - * Returns a HmacSHA1 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest (maybe empty or null) - * @return HmacSHA1 MAC for the given key and value as hex string (lowercase) - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static String hmacSha1Hex(final String key, final String valueToDigest) { - return Hex.encodeHexString(hmacSha1(key, valueToDigest)); - } - - // hmacSha256 - - /** - * Returns a HmacSHA256 Message Authentication Code (MAC) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest (maybe empty or null) - * @return HmacSHA256 MAC for the given key and value - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static byte[] hmacSha256(final byte[] key, final byte[] valueToDigest) { - try { - return getHmacSha256(key).doFinal(valueToDigest); - } catch (final IllegalStateException e) { - // cannot happen - throw new IllegalArgumentException(e); - } - } - - /** - * Returns a HmacSHA256 Message Authentication Code (MAC) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest - *

- * The InputStream must not be null and will not be closed - *

- * @return HmacSHA256 MAC for the given key and value - * @throws IOException - * If an I/O error occurs. -s * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static byte[] hmacSha256(final byte[] key, final InputStream valueToDigest) throws IOException { - return updateHmac(getHmacSha256(key), valueToDigest).doFinal(); - } - - /** - * Returns a HmacSHA256 Message Authentication Code (MAC) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest (maybe empty or null) - * @return HmacSHA256 MAC for the given key and value - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static byte[] hmacSha256(final String key, final String valueToDigest) { - return hmacSha256(StringUtils.getBytesUtf8(key), StringUtils.getBytesUtf8(valueToDigest)); - } - - /** - * Returns a HmacSHA256 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest (maybe empty or null) - * @return HmacSHA256 MAC for the given key and value as hex string (lowercase) - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static String hmacSha256Hex(final byte[] key, final byte[] valueToDigest) { - return Hex.encodeHexString(hmacSha256(key, valueToDigest)); - } - - /** - * Returns a HmacSHA256 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest - *

- * The InputStream must not be null and will not be closed - *

- * @return HmacSHA256 MAC for the given key and value as hex string (lowercase) - * @throws IOException - * If an I/O error occurs. - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static String hmacSha256Hex(final byte[] key, final InputStream valueToDigest) throws IOException { - return Hex.encodeHexString(hmacSha256(key, valueToDigest)); - } - - /** - * Returns a HmacSHA256 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest (maybe empty or null) - * @return HmacSHA256 MAC for the given key and value as hex string (lowercase) - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static String hmacSha256Hex(final String key, final String valueToDigest) { - return Hex.encodeHexString(hmacSha256(key, valueToDigest)); - } - - // hmacSha384 - - /** - * Returns a HmacSHA384 Message Authentication Code (MAC) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest (maybe empty or null) - * @return HmacSHA384 MAC for the given key and value - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static byte[] hmacSha384(final byte[] key, final byte[] valueToDigest) { - try { - return getHmacSha384(key).doFinal(valueToDigest); - } catch (final IllegalStateException e) { - // cannot happen - throw new IllegalArgumentException(e); - } - } - - /** - * Returns a HmacSHA384 Message Authentication Code (MAC) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest - *

- * The InputStream must not be null and will not be closed - *

- * @return HmacSHA384 MAC for the given key and value - * @throws IOException - * If an I/O error occurs. - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static byte[] hmacSha384(final byte[] key, final InputStream valueToDigest) throws IOException { - return updateHmac(getHmacSha384(key), valueToDigest).doFinal(); - } - - /** - * Returns a HmacSHA384 Message Authentication Code (MAC) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest (maybe empty or null) - * @return HmacSHA384 MAC for the given key and value - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static byte[] hmacSha384(final String key, final String valueToDigest) { - return hmacSha384(StringUtils.getBytesUtf8(key), StringUtils.getBytesUtf8(valueToDigest)); - } - - /** - * Returns a HmacSHA384 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest (maybe empty or null) - * @return HmacSHA384 MAC for the given key and value as hex string (lowercase) - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static String hmacSha384Hex(final byte[] key, final byte[] valueToDigest) { - return Hex.encodeHexString(hmacSha384(key, valueToDigest)); - } - - /** - * Returns a HmacSHA384 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest - *

- * The InputStream must not be null and will not be closed - *

- * @return HmacSHA384 MAC for the given key and value as hex string (lowercase) - * @throws IOException - * If an I/O error occurs. - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static String hmacSha384Hex(final byte[] key, final InputStream valueToDigest) throws IOException { - return Hex.encodeHexString(hmacSha384(key, valueToDigest)); - } - - /** - * Returns a HmacSHA384 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest (maybe empty or null) - * @return HmacSHA384 MAC for the given key and value as hex string (lowercase) - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static String hmacSha384Hex(final String key, final String valueToDigest) { - return Hex.encodeHexString(hmacSha384(key, valueToDigest)); - } - - // hmacSha512 - - /** - * Returns a HmacSHA512 Message Authentication Code (MAC) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest (maybe empty or null) - * @return HmacSHA512 MAC for the given key and value - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static byte[] hmacSha512(final byte[] key, final byte[] valueToDigest) { - try { - return getHmacSha512(key).doFinal(valueToDigest); - } catch (final IllegalStateException e) { - // cannot happen - throw new IllegalArgumentException(e); - } - } - - /** - * Returns a HmacSHA512 Message Authentication Code (MAC) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest - *

- * The InputStream must not be null and will not be closed - *

- * @return HmacSHA512 MAC for the given key and value - * @throws IOException - * If an I/O error occurs. - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static byte[] hmacSha512(final byte[] key, final InputStream valueToDigest) throws IOException { - return updateHmac(getHmacSha512(key), valueToDigest).doFinal(); - } - - /** - * Returns a HmacSHA512 Message Authentication Code (MAC) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest (maybe empty or null) - * @return HmacSHA512 MAC for the given key and value - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static byte[] hmacSha512(final String key, final String valueToDigest) { - return hmacSha512(StringUtils.getBytesUtf8(key), StringUtils.getBytesUtf8(valueToDigest)); - } - - /** - * Returns a HmacSHA512 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest (maybe empty or null) - * @return HmacSHA512 MAC for the given key and value as hex string (lowercase) - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static String hmacSha512Hex(final byte[] key, final byte[] valueToDigest) { - return Hex.encodeHexString(hmacSha512(key, valueToDigest)); - } - - /** - * Returns a HmacSHA512 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest - *

- * The InputStream must not be null and will not be closed - *

- * @return HmacSHA512 MAC for the given key and value as hex string (lowercase) - * @throws IOException - * If an I/O error occurs. - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static String hmacSha512Hex(final byte[] key, final InputStream valueToDigest) throws IOException { - return Hex.encodeHexString(hmacSha512(key, valueToDigest)); - } - - /** - * Returns a HmacSHA512 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value. - * - * @param key - * They key for the keyed digest (must not be null) - * @param valueToDigest - * The value (data) which should to digest (maybe empty or null) - * @return HmacSHA512 MAC for the given key and value as hex string (lowercase) - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid. - */ - public static String hmacSha512Hex(final String key, final String valueToDigest) { - return Hex.encodeHexString(hmacSha512(key, valueToDigest)); - } - - // update - - /** - * Updates the given {@link Mac}. This generates a digest for valueToDigest and the key the Mac was initialized - * - * @param mac - * the initialized {@link Mac} to update - * @param valueToDigest - * the value to update the {@link Mac} with (maybe null or empty) - * @return the updated {@link Mac} - * @throws IllegalStateException - * if the Mac was not initialized - * @since 1.x - */ - public static Mac updateHmac(final Mac mac, final byte[] valueToDigest) { - mac.reset(); - mac.update(valueToDigest); - return mac; - } - - /** - * Updates the given {@link Mac}. This generates a digest for valueToDigest and the key the Mac was initialized - * - * @param mac - * the initialized {@link Mac} to update - * @param valueToDigest - * the value to update the {@link Mac} with - *

- * The InputStream must not be null and will not be closed - *

- * @return the updated {@link Mac} - * @throws IOException - * If an I/O error occurs. - * @throws IllegalStateException - * If the Mac was not initialized - * @since 1.x - */ - public static Mac updateHmac(final Mac mac, final InputStream valueToDigest) throws IOException { - mac.reset(); - final byte[] buffer = new byte[STREAM_BUFFER_LENGTH]; - int read = valueToDigest.read(buffer, 0, STREAM_BUFFER_LENGTH); - - while (read > -1) { - mac.update(buffer, 0, read); - read = valueToDigest.read(buffer, 0, STREAM_BUFFER_LENGTH); - } - - return mac; - } - - /** - * Updates the given {@link Mac}. This generates a digest for valueToDigest and the key the Mac was initialized - * - * @param mac - * the initialized {@link Mac} to update - * @param valueToDigest - * the value to update the {@link Mac} with (maybe null or empty) - * @return the updated {@link Mac} - * @throws IllegalStateException - * if the Mac was not initialized - * @since 1.x - */ - public static Mac updateHmac(final Mac mac, final String valueToDigest) { - mac.reset(); - mac.update(StringUtils.getBytesUtf8(valueToDigest)); - return mac; - } -} diff --git a/src/org/apache/commons/codec/digest/Md5Crypt.java b/src/org/apache/commons/codec/digest/Md5Crypt.java deleted file mode 100644 index 83c1bce8..00000000 --- a/src/org/apache/commons/codec/digest/Md5Crypt.java +++ /dev/null @@ -1,302 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.codec.digest; - -import java.security.MessageDigest; -import java.util.Arrays; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.commons.codec.Charsets; - -/** - * The libc crypt() "$1$" and Apache "$apr1$" MD5-based hash algorithm. - *

- * Based on the public domain ("beer-ware") C implementation from Poul-Henning Kamp which was found at: - * crypt-md5.c @ freebsd.org
- *

- * Source: - * - *

- * $FreeBSD: src/lib/libcrypt/crypt-md5.c,v 1.1 1999/01/21 13:50:09 brandon Exp $
- * 
- *

- * Conversion to Kotlin and from there to Java in 2012. - *

- * The C style comments are from the original C code, the ones with "//" from the port. - *

- * This class is immutable and thread-safe. - * - * @version $Id: Md5Crypt.java 1563226 2014-01-31 19:38:06Z ggregory $ - * @since 1.7 - */ -public class Md5Crypt { - - /** The Identifier of the Apache variant. */ - static final String APR1_PREFIX = "$apr1$"; - - /** The number of bytes of the final hash. */ - private static final int BLOCKSIZE = 16; - - /** The Identifier of this crypt() variant. */ - static final String MD5_PREFIX = "$1$"; - - /** The number of rounds of the big loop. */ - private static final int ROUNDS = 1000; - - /** - * See {@link #apr1Crypt(String, String)} for details. - * - * @param keyBytes - * plaintext string to hash. - * @return the hash value - * @throws RuntimeException - * when a {@link java.security.NoSuchAlgorithmException} is caught. * - */ - public static String apr1Crypt(final byte[] keyBytes) { - return apr1Crypt(keyBytes, APR1_PREFIX + B64.getRandomSalt(8)); - } - - /** - * See {@link #apr1Crypt(String, String)} for details. - * - * @param keyBytes - * plaintext string to hash. - * @param salt An APR1 salt. - * @return the hash value - * @throws IllegalArgumentException - * if the salt does not match the allowed pattern - * @throws RuntimeException - * when a {@link java.security.NoSuchAlgorithmException} is caught. - */ - public static String apr1Crypt(final byte[] keyBytes, String salt) { - // to make the md5Crypt regex happy - if (salt != null && !salt.startsWith(APR1_PREFIX)) { - salt = APR1_PREFIX + salt; - } - return Md5Crypt.md5Crypt(keyBytes, salt, APR1_PREFIX); - } - - /** - * See {@link #apr1Crypt(String, String)} for details. - * - * @param keyBytes - * plaintext string to hash. - * @return the hash value - * @throws RuntimeException - * when a {@link java.security.NoSuchAlgorithmException} is caught. - */ - public static String apr1Crypt(final String keyBytes) { - return apr1Crypt(keyBytes.getBytes(Charsets.UTF_8)); - } - - /** - * Generates an Apache htpasswd compatible "$apr1$" MD5 based hash value. - *

- * The algorithm is identical to the crypt(3) "$1$" one but produces different outputs due to the different salt - * prefix. - * - * @param keyBytes - * plaintext string to hash. - * @param salt - * salt string including the prefix and optionally garbage at the end. Will be generated randomly if - * null. - * @return the hash value - * @throws IllegalArgumentException - * if the salt does not match the allowed pattern - * @throws RuntimeException - * when a {@link java.security.NoSuchAlgorithmException} is caught. - */ - public static String apr1Crypt(final String keyBytes, final String salt) { - return apr1Crypt(keyBytes.getBytes(Charsets.UTF_8), salt); - } - - /** - * Generates a libc6 crypt() compatible "$1$" hash value. - *

- * See {@link Crypt#crypt(String, String)} for details. - * - * @param keyBytes - * plaintext string to hash. - * @return the hash value - * @throws RuntimeException - * when a {@link java.security.NoSuchAlgorithmException} is caught. - */ - public static String md5Crypt(final byte[] keyBytes) { - return md5Crypt(keyBytes, MD5_PREFIX + B64.getRandomSalt(8)); - } - - /** - * Generates a libc crypt() compatible "$1$" MD5 based hash value. - *

- * See {@link Crypt#crypt(String, String)} for details. - * - * @param keyBytes - * plaintext string to hash. - * @param salt - * salt string including the prefix and optionally garbage at the end. Will be generated randomly if - * null. - * @return the hash value - * @throws IllegalArgumentException - * if the salt does not match the allowed pattern - * @throws RuntimeException - * when a {@link java.security.NoSuchAlgorithmException} is caught. - */ - public static String md5Crypt(final byte[] keyBytes, final String salt) { - return md5Crypt(keyBytes, salt, MD5_PREFIX); - } - - /** - * Generates a libc6 crypt() "$1$" or Apache htpasswd "$apr1$" hash value. - *

- * See {@link Crypt#crypt(String, String)} or {@link #apr1Crypt(String, String)} for details. - * - * @param keyBytes - * plaintext string to hash. - * @param salt May be null. - * @param prefix salt prefix - * @return the hash value - * @throws IllegalArgumentException - * if the salt does not match the allowed pattern - * @throws RuntimeException - * when a {@link java.security.NoSuchAlgorithmException} is caught. - */ - public static String md5Crypt(final byte[] keyBytes, final String salt, final String prefix) { - final int keyLen = keyBytes.length; - - // Extract the real salt from the given string which can be a complete hash string. - String saltString; - if (salt == null) { - saltString = B64.getRandomSalt(8); - } else { - final Pattern p = Pattern.compile("^" + prefix.replace("$", "\\$") + "([\\.\\/a-zA-Z0-9]{1,8}).*"); - final Matcher m = p.matcher(salt); - if (m == null || !m.find()) { - throw new IllegalArgumentException("Invalid salt value: " + salt); - } - saltString = m.group(1); - } - final byte[] saltBytes = saltString.getBytes(Charsets.UTF_8); - - final MessageDigest ctx = DigestUtils.getMd5Digest(); - - /* - * The password first, since that is what is most unknown - */ - ctx.update(keyBytes); - - /* - * Then our magic string - */ - ctx.update(prefix.getBytes(Charsets.UTF_8)); - - /* - * Then the raw salt - */ - ctx.update(saltBytes); - - /* - * Then just as many characters of the MD5(pw,salt,pw) - */ - MessageDigest ctx1 = DigestUtils.getMd5Digest(); - ctx1.update(keyBytes); - ctx1.update(saltBytes); - ctx1.update(keyBytes); - byte[] finalb = ctx1.digest(); - int ii = keyLen; - while (ii > 0) { - ctx.update(finalb, 0, ii > 16 ? 16 : ii); - ii -= 16; - } - - /* - * Don't leave anything around in vm they could use. - */ - Arrays.fill(finalb, (byte) 0); - - /* - * Then something really weird... - */ - ii = keyLen; - final int j = 0; - while (ii > 0) { - if ((ii & 1) == 1) { - ctx.update(finalb[j]); - } else { - ctx.update(keyBytes[j]); - } - ii >>= 1; - } - - /* - * Now make the output string - */ - final StringBuilder passwd = new StringBuilder(prefix + saltString + "$"); - finalb = ctx.digest(); - - /* - * and now, just to make sure things don't run too fast On a 60 Mhz Pentium this takes 34 msec, so you would - * need 30 seconds to build a 1000 entry dictionary... - */ - for (int i = 0; i < ROUNDS; i++) { - ctx1 = DigestUtils.getMd5Digest(); - if ((i & 1) != 0) { - ctx1.update(keyBytes); - } else { - ctx1.update(finalb, 0, BLOCKSIZE); - } - - if (i % 3 != 0) { - ctx1.update(saltBytes); - } - - if (i % 7 != 0) { - ctx1.update(keyBytes); - } - - if ((i & 1) != 0) { - ctx1.update(finalb, 0, BLOCKSIZE); - } else { - ctx1.update(keyBytes); - } - finalb = ctx1.digest(); - } - - // The following was nearly identical to the Sha2Crypt code. - // Again, the buflen is not really needed. - // int buflen = MD5_PREFIX.length() - 1 + salt_string.length() + 1 + BLOCKSIZE + 1; - B64.b64from24bit(finalb[0], finalb[6], finalb[12], 4, passwd); - B64.b64from24bit(finalb[1], finalb[7], finalb[13], 4, passwd); - B64.b64from24bit(finalb[2], finalb[8], finalb[14], 4, passwd); - B64.b64from24bit(finalb[3], finalb[9], finalb[15], 4, passwd); - B64.b64from24bit(finalb[4], finalb[10], finalb[5], 4, passwd); - B64.b64from24bit((byte) 0, (byte) 0, finalb[11], 2, passwd); - - /* - * Don't leave anything around in vm they could use. - */ - // Is there a better way to do this with the JVM? - ctx.reset(); - ctx1.reset(); - Arrays.fill(keyBytes, (byte) 0); - Arrays.fill(saltBytes, (byte) 0); - Arrays.fill(finalb, (byte) 0); - - return passwd.toString(); - } -} diff --git a/src/org/apache/commons/codec/digest/MessageDigestAlgorithms.java b/src/org/apache/commons/codec/digest/MessageDigestAlgorithms.java deleted file mode 100644 index 05bfab68..00000000 --- a/src/org/apache/commons/codec/digest/MessageDigestAlgorithms.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.codec.digest; - -import java.security.MessageDigest; - -/** - * Standard {@link MessageDigest} algorithm names from the Java Cryptography Architecture Standard Algorithm Name - * Documentation. - *

- * This class is immutable and thread-safe. - *

- * TODO This should be an enum. - * - * @see Java Cryptography - * Architecture Standard Algorithm Name Documentation - * @since 1.7 - * @version $Id: MessageDigestAlgorithms.java 1585867 2014-04-09 00:12:36Z ggregory $ - */ -public class MessageDigestAlgorithms { - - private MessageDigestAlgorithms() { - // cannot be instantiated. - } - - /** - * The MD2 message digest algorithm defined in RFC 1319. - */ - public static final String MD2 = "MD2"; - - /** - * The MD5 message digest algorithm defined in RFC 1321. - */ - public static final String MD5 = "MD5"; - - /** - * The SHA-1 hash algorithm defined in the FIPS PUB 180-2. - */ - public static final String SHA_1 = "SHA-1"; - - /** - * The SHA-256 hash algorithm defined in the FIPS PUB 180-2. - */ - public static final String SHA_256 = "SHA-256"; - - /** - * The SHA-384 hash algorithm defined in the FIPS PUB 180-2. - */ - public static final String SHA_384 = "SHA-384"; - - /** - * The SHA-512 hash algorithm defined in the FIPS PUB 180-2. - */ - public static final String SHA_512 = "SHA-512"; - -} diff --git a/src/org/apache/commons/codec/digest/Sha2Crypt.java b/src/org/apache/commons/codec/digest/Sha2Crypt.java deleted file mode 100644 index 0643dd87..00000000 --- a/src/org/apache/commons/codec/digest/Sha2Crypt.java +++ /dev/null @@ -1,545 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.codec.digest; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.commons.codec.Charsets; - -/** - * SHA2-based Unix crypt implementation. - *

- * Based on the C implementation released into the Public Domain by Ulrich Drepper <drepper@redhat.com> - * http://www.akkadia.org/drepper/SHA-crypt.txt - *

- * Conversion to Kotlin and from there to Java in 2012 by Christian Hammers <ch@lathspell.de> and likewise put - * into the Public Domain. - *

- * This class is immutable and thread-safe. - * - * @version $Id: Sha2Crypt.java 1619948 2014-08-22 22:53:55Z ggregory $ - * @since 1.7 - */ -public class Sha2Crypt { - - /** Default number of rounds if not explicitly specified. */ - private static final int ROUNDS_DEFAULT = 5000; - - /** Maximum number of rounds. */ - private static final int ROUNDS_MAX = 999999999; - - /** Minimum number of rounds. */ - private static final int ROUNDS_MIN = 1000; - - /** Prefix for optional rounds specification. */ - private static final String ROUNDS_PREFIX = "rounds="; - - /** The number of bytes the final hash value will have (SHA-256 variant). */ - private static final int SHA256_BLOCKSIZE = 32; - - /** The prefixes that can be used to identify this crypt() variant (SHA-256). */ - static final String SHA256_PREFIX = "$5$"; - - /** The number of bytes the final hash value will have (SHA-512 variant). */ - private static final int SHA512_BLOCKSIZE = 64; - - /** The prefixes that can be used to identify this crypt() variant (SHA-512). */ - static final String SHA512_PREFIX = "$6$"; - - /** The pattern to match valid salt values. */ - private static final Pattern SALT_PATTERN = Pattern - .compile("^\\$([56])\\$(rounds=(\\d+)\\$)?([\\.\\/a-zA-Z0-9]{1,16}).*"); - - /** - * Generates a libc crypt() compatible "$5$" hash value with random salt. - *

- * See {@link Crypt#crypt(String, String)} for details. - * - * @param keyBytes - * plaintext to hash - * @return complete hash value - * @throws RuntimeException - * when a {@link java.security.NoSuchAlgorithmException} is caught. - */ - public static String sha256Crypt(final byte[] keyBytes) { - return sha256Crypt(keyBytes, null); - } - - /** - * Generates a libc6 crypt() compatible "$5$" hash value. - *

- * See {@link Crypt#crypt(String, String)} for details. - * - * @param keyBytes - * plaintext to hash - * @param salt - * real salt value without prefix or "rounds=" - * @return complete hash value including salt - * @throws IllegalArgumentException - * if the salt does not match the allowed pattern - * @throws RuntimeException - * when a {@link java.security.NoSuchAlgorithmException} is caught. - */ - public static String sha256Crypt(final byte[] keyBytes, String salt) { - if (salt == null) { - salt = SHA256_PREFIX + B64.getRandomSalt(8); - } - return sha2Crypt(keyBytes, salt, SHA256_PREFIX, SHA256_BLOCKSIZE, MessageDigestAlgorithms.SHA_256); - } - - /** - * Generates a libc6 crypt() compatible "$5$" or "$6$" SHA2 based hash value. - *

- * This is a nearly line by line conversion of the original C function. The numbered comments are from the algorithm - * description, the short C-style ones from the original C code and the ones with "Remark" from me. - *

- * See {@link Crypt#crypt(String, String)} for details. - * - * @param keyBytes - * plaintext to hash - * @param salt - * real salt value without prefix or "rounds=" - * @param saltPrefix - * either $5$ or $6$ - * @param blocksize - * a value that differs between $5$ and $6$ - * @param algorithm - * {@link MessageDigest} algorithm identifier string - * @return complete hash value including prefix and salt - * @throws IllegalArgumentException - * if the given salt is null or does not match the allowed pattern - * @throws IllegalArgumentException - * when a {@link NoSuchAlgorithmException} is caught - * @see MessageDigestAlgorithms - */ - private static String sha2Crypt(final byte[] keyBytes, final String salt, final String saltPrefix, - final int blocksize, final String algorithm) { - - final int keyLen = keyBytes.length; - - // Extracts effective salt and the number of rounds from the given salt. - int rounds = ROUNDS_DEFAULT; - boolean roundsCustom = false; - if (salt == null) { - throw new IllegalArgumentException("Salt must not be null"); - } - - final Matcher m = SALT_PATTERN.matcher(salt); - if (m == null || !m.find()) { - throw new IllegalArgumentException("Invalid salt value: " + salt); - } - if (m.group(3) != null) { - rounds = Integer.parseInt(m.group(3)); - rounds = Math.max(ROUNDS_MIN, Math.min(ROUNDS_MAX, rounds)); - roundsCustom = true; - } - final String saltString = m.group(4); - final byte[] saltBytes = saltString.getBytes(Charsets.UTF_8); - final int saltLen = saltBytes.length; - - // 1. start digest A - // Prepare for the real work. - MessageDigest ctx = DigestUtils.getDigest(algorithm); - - // 2. the password string is added to digest A - /* - * Add the key string. - */ - ctx.update(keyBytes); - - // 3. the salt string is added to digest A. This is just the salt string - // itself without the enclosing '$', without the magic salt_prefix $5$ and - // $6$ respectively and without the rounds= specification. - // - // NB: the MD5 algorithm did add the $1$ salt_prefix. This is not deemed - // necessary since it is a constant string and does not add security - // and /possibly/ allows a plain text attack. Since the rounds= - // specification should never be added this would also create an - // inconsistency. - /* - * The last part is the salt string. This must be at most 16 characters and it ends at the first `$' character - * (for compatibility with existing implementations). - */ - ctx.update(saltBytes); - - // 4. start digest B - /* - * Compute alternate sha512 sum with input KEY, SALT, and KEY. The final result will be added to the first - * context. - */ - MessageDigest altCtx = DigestUtils.getDigest(algorithm); - - // 5. add the password to digest B - /* - * Add key. - */ - altCtx.update(keyBytes); - - // 6. add the salt string to digest B - /* - * Add salt. - */ - altCtx.update(saltBytes); - - // 7. add the password again to digest B - /* - * Add key again. - */ - altCtx.update(keyBytes); - - // 8. finish digest B - /* - * Now get result of this (32 bytes) and add it to the other context. - */ - byte[] altResult = altCtx.digest(); - - // 9. For each block of 32 or 64 bytes in the password string (excluding - // the terminating NUL in the C representation), add digest B to digest A - /* - * Add for any character in the key one byte of the alternate sum. - */ - /* - * (Remark: the C code comment seems wrong for key length > 32!) - */ - int cnt = keyBytes.length; - while (cnt > blocksize) { - ctx.update(altResult, 0, blocksize); - cnt -= blocksize; - } - - // 10. For the remaining N bytes of the password string add the first - // N bytes of digest B to digest A - ctx.update(altResult, 0, cnt); - - // 11. For each bit of the binary representation of the length of the - // password string up to and including the highest 1-digit, starting - // from to lowest bit position (numeric value 1): - // - // a) for a 1-digit add digest B to digest A - // - // b) for a 0-digit add the password string - // - // NB: this step differs significantly from the MD5 algorithm. It - // adds more randomness. - /* - * Take the binary representation of the length of the key and for every 1 add the alternate sum, for every 0 - * the key. - */ - cnt = keyBytes.length; - while (cnt > 0) { - if ((cnt & 1) != 0) { - ctx.update(altResult, 0, blocksize); - } else { - ctx.update(keyBytes); - } - cnt >>= 1; - } - - // 12. finish digest A - /* - * Create intermediate result. - */ - altResult = ctx.digest(); - - // 13. start digest DP - /* - * Start computation of P byte sequence. - */ - altCtx = DigestUtils.getDigest(algorithm); - - // 14. for every byte in the password (excluding the terminating NUL byte - // in the C representation of the string) - // - // add the password to digest DP - /* - * For every character in the password add the entire password. - */ - for (int i = 1; i <= keyLen; i++) { - altCtx.update(keyBytes); - } - - // 15. finish digest DP - /* - * Finish the digest. - */ - byte[] tempResult = altCtx.digest(); - - // 16. produce byte sequence P of the same length as the password where - // - // a) for each block of 32 or 64 bytes of length of the password string - // the entire digest DP is used - // - // b) for the remaining N (up to 31 or 63) bytes use the first N - // bytes of digest DP - /* - * Create byte sequence P. - */ - final byte[] pBytes = new byte[keyLen]; - int cp = 0; - while (cp < keyLen - blocksize) { - System.arraycopy(tempResult, 0, pBytes, cp, blocksize); - cp += blocksize; - } - System.arraycopy(tempResult, 0, pBytes, cp, keyLen - cp); - - // 17. start digest DS - /* - * Start computation of S byte sequence. - */ - altCtx = DigestUtils.getDigest(algorithm); - - // 18. repeast the following 16+A[0] times, where A[0] represents the first - // byte in digest A interpreted as an 8-bit unsigned value - // - // add the salt to digest DS - /* - * For every character in the password add the entire password. - */ - for (int i = 1; i <= 16 + (altResult[0] & 0xff); i++) { - altCtx.update(saltBytes); - } - - // 19. finish digest DS - /* - * Finish the digest. - */ - tempResult = altCtx.digest(); - - // 20. produce byte sequence S of the same length as the salt string where - // - // a) for each block of 32 or 64 bytes of length of the salt string - // the entire digest DS is used - // - // b) for the remaining N (up to 31 or 63) bytes use the first N - // bytes of digest DS - /* - * Create byte sequence S. - */ - // Remark: The salt is limited to 16 chars, how does this make sense? - final byte[] sBytes = new byte[saltLen]; - cp = 0; - while (cp < saltLen - blocksize) { - System.arraycopy(tempResult, 0, sBytes, cp, blocksize); - cp += blocksize; - } - System.arraycopy(tempResult, 0, sBytes, cp, saltLen - cp); - - // 21. repeat a loop according to the number specified in the rounds= - // specification in the salt (or the default value if none is - // present). Each round is numbered, starting with 0 and up to N-1. - // - // The loop uses a digest as input. In the first round it is the - // digest produced in step 12. In the latter steps it is the digest - // produced in step 21.h. The following text uses the notation - // "digest A/C" to describe this behavior. - /* - * Repeatedly run the collected hash value through sha512 to burn CPU cycles. - */ - for (int i = 0; i <= rounds - 1; i++) { - // a) start digest C - /* - * New context. - */ - ctx = DigestUtils.getDigest(algorithm); - - // b) for odd round numbers add the byte sequense P to digest C - // c) for even round numbers add digest A/C - /* - * Add key or last result. - */ - if ((i & 1) != 0) { - ctx.update(pBytes, 0, keyLen); - } else { - ctx.update(altResult, 0, blocksize); - } - - // d) for all round numbers not divisible by 3 add the byte sequence S - /* - * Add salt for numbers not divisible by 3. - */ - if (i % 3 != 0) { - ctx.update(sBytes, 0, saltLen); - } - - // e) for all round numbers not divisible by 7 add the byte sequence P - /* - * Add key for numbers not divisible by 7. - */ - if (i % 7 != 0) { - ctx.update(pBytes, 0, keyLen); - } - - // f) for odd round numbers add digest A/C - // g) for even round numbers add the byte sequence P - /* - * Add key or last result. - */ - if ((i & 1) != 0) { - ctx.update(altResult, 0, blocksize); - } else { - ctx.update(pBytes, 0, keyLen); - } - - // h) finish digest C. - /* - * Create intermediate result. - */ - altResult = ctx.digest(); - } - - // 22. Produce the output string. This is an ASCII string of the maximum - // size specified above, consisting of multiple pieces: - // - // a) the salt salt_prefix, $5$ or $6$ respectively - // - // b) the rounds= specification, if one was present in the input - // salt string. A trailing '$' is added in this case to separate - // the rounds specification from the following text. - // - // c) the salt string truncated to 16 characters - // - // d) a '$' character - /* - * Now we can construct the result string. It consists of three parts. - */ - final StringBuilder buffer = new StringBuilder(saltPrefix); - if (roundsCustom) { - buffer.append(ROUNDS_PREFIX); - buffer.append(rounds); - buffer.append("$"); - } - buffer.append(saltString); - buffer.append("$"); - - // e) the base-64 encoded final C digest. The encoding used is as - // follows: - // [...] - // - // Each group of three bytes from the digest produces four - // characters as output: - // - // 1. character: the six low bits of the first byte - // 2. character: the two high bits of the first byte and the - // four low bytes from the second byte - // 3. character: the four high bytes from the second byte and - // the two low bits from the third byte - // 4. character: the six high bits from the third byte - // - // The groups of three bytes are as follows (in this sequence). - // These are the indices into the byte array containing the - // digest, starting with index 0. For the last group there are - // not enough bytes left in the digest and the value zero is used - // in its place. This group also produces only three or two - // characters as output for SHA-512 and SHA-512 respectively. - - // This was just a safeguard in the C implementation: - // int buflen = salt_prefix.length() - 1 + ROUNDS_PREFIX.length() + 9 + 1 + salt_string.length() + 1 + 86 + 1; - - if (blocksize == 32) { - B64.b64from24bit(altResult[0], altResult[10], altResult[20], 4, buffer); - B64.b64from24bit(altResult[21], altResult[1], altResult[11], 4, buffer); - B64.b64from24bit(altResult[12], altResult[22], altResult[2], 4, buffer); - B64.b64from24bit(altResult[3], altResult[13], altResult[23], 4, buffer); - B64.b64from24bit(altResult[24], altResult[4], altResult[14], 4, buffer); - B64.b64from24bit(altResult[15], altResult[25], altResult[5], 4, buffer); - B64.b64from24bit(altResult[6], altResult[16], altResult[26], 4, buffer); - B64.b64from24bit(altResult[27], altResult[7], altResult[17], 4, buffer); - B64.b64from24bit(altResult[18], altResult[28], altResult[8], 4, buffer); - B64.b64from24bit(altResult[9], altResult[19], altResult[29], 4, buffer); - B64.b64from24bit((byte) 0, altResult[31], altResult[30], 3, buffer); - } else { - B64.b64from24bit(altResult[0], altResult[21], altResult[42], 4, buffer); - B64.b64from24bit(altResult[22], altResult[43], altResult[1], 4, buffer); - B64.b64from24bit(altResult[44], altResult[2], altResult[23], 4, buffer); - B64.b64from24bit(altResult[3], altResult[24], altResult[45], 4, buffer); - B64.b64from24bit(altResult[25], altResult[46], altResult[4], 4, buffer); - B64.b64from24bit(altResult[47], altResult[5], altResult[26], 4, buffer); - B64.b64from24bit(altResult[6], altResult[27], altResult[48], 4, buffer); - B64.b64from24bit(altResult[28], altResult[49], altResult[7], 4, buffer); - B64.b64from24bit(altResult[50], altResult[8], altResult[29], 4, buffer); - B64.b64from24bit(altResult[9], altResult[30], altResult[51], 4, buffer); - B64.b64from24bit(altResult[31], altResult[52], altResult[10], 4, buffer); - B64.b64from24bit(altResult[53], altResult[11], altResult[32], 4, buffer); - B64.b64from24bit(altResult[12], altResult[33], altResult[54], 4, buffer); - B64.b64from24bit(altResult[34], altResult[55], altResult[13], 4, buffer); - B64.b64from24bit(altResult[56], altResult[14], altResult[35], 4, buffer); - B64.b64from24bit(altResult[15], altResult[36], altResult[57], 4, buffer); - B64.b64from24bit(altResult[37], altResult[58], altResult[16], 4, buffer); - B64.b64from24bit(altResult[59], altResult[17], altResult[38], 4, buffer); - B64.b64from24bit(altResult[18], altResult[39], altResult[60], 4, buffer); - B64.b64from24bit(altResult[40], altResult[61], altResult[19], 4, buffer); - B64.b64from24bit(altResult[62], altResult[20], altResult[41], 4, buffer); - B64.b64from24bit((byte) 0, (byte) 0, altResult[63], 2, buffer); - } - - /* - * Clear the buffer for the intermediate result so that people attaching to processes or reading core dumps - * cannot get any information. - */ - // Is there a better way to do this with the JVM? - Arrays.fill(tempResult, (byte) 0); - Arrays.fill(pBytes, (byte) 0); - Arrays.fill(sBytes, (byte) 0); - ctx.reset(); - altCtx.reset(); - Arrays.fill(keyBytes, (byte) 0); - Arrays.fill(saltBytes, (byte) 0); - - return buffer.toString(); - } - - /** - * Generates a libc crypt() compatible "$6$" hash value with random salt. - *

- * See {@link Crypt#crypt(String, String)} for details. - * - * @param keyBytes - * plaintext to hash - * @return complete hash value - * @throws RuntimeException - * when a {@link java.security.NoSuchAlgorithmException} is caught. - */ - public static String sha512Crypt(final byte[] keyBytes) { - return sha512Crypt(keyBytes, null); - } - - /** - * Generates a libc6 crypt() compatible "$6$" hash value. - *

- * See {@link Crypt#crypt(String, String)} for details. - * - * @param keyBytes - * plaintext to hash - * @param salt - * real salt value without prefix or "rounds=" - * @return complete hash value including salt - * @throws IllegalArgumentException - * if the salt does not match the allowed pattern - * @throws RuntimeException - * when a {@link java.security.NoSuchAlgorithmException} is caught. - */ - public static String sha512Crypt(final byte[] keyBytes, String salt) { - if (salt == null) { - salt = SHA512_PREFIX + B64.getRandomSalt(8); - } - return sha2Crypt(keyBytes, salt, SHA512_PREFIX, SHA512_BLOCKSIZE, MessageDigestAlgorithms.SHA_512); - } -} diff --git a/src/org/apache/commons/codec/digest/UnixCrypt.java b/src/org/apache/commons/codec/digest/UnixCrypt.java deleted file mode 100644 index 16ebc35c..00000000 --- a/src/org/apache/commons/codec/digest/UnixCrypt.java +++ /dev/null @@ -1,413 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.codec.digest; - -import java.util.Random; - -import org.apache.commons.codec.Charsets; - -/** - * Unix crypt(3) algorithm implementation. - *

- * This class only implements the traditional 56 bit DES based algorithm. Please use DigestUtils.crypt() for a method - * that distinguishes between all the algorithms supported in the current glibc's crypt(). - *

- * The Java implementation was taken from the JetSpeed Portal project (see - * org.apache.jetspeed.services.security.ldap.UnixCrypt). - *

- * This class is slightly incompatible if the given salt contains characters that are not part of the allowed range - * [a-zA-Z0-9./]. - *

- * This class is immutable and thread-safe. - * - * @version $Id: UnixCrypt.java 1429868 2013-01-07 16:08:05Z ggregory $ - * @since 1.7 - */ -public class UnixCrypt { - - private static final int CON_SALT[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 5, 6, - 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, - 34, 35, 36, 37, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, - 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 0, 0, 0, 0, 0 }; - - private static final int COV2CHAR[] = { 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, - 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, - 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122 }; - - private static final char SALT_CHARS[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./" - .toCharArray(); - - private static final boolean SHIFT2[] = { false, false, true, true, true, true, true, true, false, true, true, - true, true, true, true, false }; - - private static final int SKB[][] = { - { 0, 16, 0x20000000, 0x20000010, 0x10000, 0x10010, 0x20010000, 0x20010010, 2048, 2064, 0x20000800, - 0x20000810, 0x10800, 0x10810, 0x20010800, 0x20010810, 32, 48, 0x20000020, 0x20000030, 0x10020, - 0x10030, 0x20010020, 0x20010030, 2080, 2096, 0x20000820, 0x20000830, 0x10820, 0x10830, 0x20010820, - 0x20010830, 0x80000, 0x80010, 0x20080000, 0x20080010, 0x90000, 0x90010, 0x20090000, 0x20090010, - 0x80800, 0x80810, 0x20080800, 0x20080810, 0x90800, 0x90810, 0x20090800, 0x20090810, 0x80020, - 0x80030, 0x20080020, 0x20080030, 0x90020, 0x90030, 0x20090020, 0x20090030, 0x80820, 0x80830, - 0x20080820, 0x20080830, 0x90820, 0x90830, 0x20090820, 0x20090830 }, - { 0, 0x2000000, 8192, 0x2002000, 0x200000, 0x2200000, 0x202000, 0x2202000, 4, 0x2000004, 8196, 0x2002004, - 0x200004, 0x2200004, 0x202004, 0x2202004, 1024, 0x2000400, 9216, 0x2002400, 0x200400, 0x2200400, - 0x202400, 0x2202400, 1028, 0x2000404, 9220, 0x2002404, 0x200404, 0x2200404, 0x202404, 0x2202404, - 0x10000000, 0x12000000, 0x10002000, 0x12002000, 0x10200000, 0x12200000, 0x10202000, 0x12202000, - 0x10000004, 0x12000004, 0x10002004, 0x12002004, 0x10200004, 0x12200004, 0x10202004, 0x12202004, - 0x10000400, 0x12000400, 0x10002400, 0x12002400, 0x10200400, 0x12200400, 0x10202400, 0x12202400, - 0x10000404, 0x12000404, 0x10002404, 0x12002404, 0x10200404, 0x12200404, 0x10202404, 0x12202404 }, - { 0, 1, 0x40000, 0x40001, 0x1000000, 0x1000001, 0x1040000, 0x1040001, 2, 3, 0x40002, 0x40003, 0x1000002, - 0x1000003, 0x1040002, 0x1040003, 512, 513, 0x40200, 0x40201, 0x1000200, 0x1000201, 0x1040200, - 0x1040201, 514, 515, 0x40202, 0x40203, 0x1000202, 0x1000203, 0x1040202, 0x1040203, 0x8000000, - 0x8000001, 0x8040000, 0x8040001, 0x9000000, 0x9000001, 0x9040000, 0x9040001, 0x8000002, 0x8000003, - 0x8040002, 0x8040003, 0x9000002, 0x9000003, 0x9040002, 0x9040003, 0x8000200, 0x8000201, 0x8040200, - 0x8040201, 0x9000200, 0x9000201, 0x9040200, 0x9040201, 0x8000202, 0x8000203, 0x8040202, 0x8040203, - 0x9000202, 0x9000203, 0x9040202, 0x9040203 }, - { 0, 0x100000, 256, 0x100100, 8, 0x100008, 264, 0x100108, 4096, 0x101000, 4352, 0x101100, 4104, 0x101008, - 4360, 0x101108, 0x4000000, 0x4100000, 0x4000100, 0x4100100, 0x4000008, 0x4100008, 0x4000108, - 0x4100108, 0x4001000, 0x4101000, 0x4001100, 0x4101100, 0x4001008, 0x4101008, 0x4001108, 0x4101108, - 0x20000, 0x120000, 0x20100, 0x120100, 0x20008, 0x120008, 0x20108, 0x120108, 0x21000, 0x121000, - 0x21100, 0x121100, 0x21008, 0x121008, 0x21108, 0x121108, 0x4020000, 0x4120000, 0x4020100, - 0x4120100, 0x4020008, 0x4120008, 0x4020108, 0x4120108, 0x4021000, 0x4121000, 0x4021100, 0x4121100, - 0x4021008, 0x4121008, 0x4021108, 0x4121108 }, - { 0, 0x10000000, 0x10000, 0x10010000, 4, 0x10000004, 0x10004, 0x10010004, 0x20000000, 0x30000000, - 0x20010000, 0x30010000, 0x20000004, 0x30000004, 0x20010004, 0x30010004, 0x100000, 0x10100000, - 0x110000, 0x10110000, 0x100004, 0x10100004, 0x110004, 0x10110004, 0x20100000, 0x30100000, - 0x20110000, 0x30110000, 0x20100004, 0x30100004, 0x20110004, 0x30110004, 4096, 0x10001000, 0x11000, - 0x10011000, 4100, 0x10001004, 0x11004, 0x10011004, 0x20001000, 0x30001000, 0x20011000, 0x30011000, - 0x20001004, 0x30001004, 0x20011004, 0x30011004, 0x101000, 0x10101000, 0x111000, 0x10111000, - 0x101004, 0x10101004, 0x111004, 0x10111004, 0x20101000, 0x30101000, 0x20111000, 0x30111000, - 0x20101004, 0x30101004, 0x20111004, 0x30111004 }, - { 0, 0x8000000, 8, 0x8000008, 1024, 0x8000400, 1032, 0x8000408, 0x20000, 0x8020000, 0x20008, 0x8020008, - 0x20400, 0x8020400, 0x20408, 0x8020408, 1, 0x8000001, 9, 0x8000009, 1025, 0x8000401, 1033, - 0x8000409, 0x20001, 0x8020001, 0x20009, 0x8020009, 0x20401, 0x8020401, 0x20409, 0x8020409, - 0x2000000, 0xa000000, 0x2000008, 0xa000008, 0x2000400, 0xa000400, 0x2000408, 0xa000408, 0x2020000, - 0xa020000, 0x2020008, 0xa020008, 0x2020400, 0xa020400, 0x2020408, 0xa020408, 0x2000001, 0xa000001, - 0x2000009, 0xa000009, 0x2000401, 0xa000401, 0x2000409, 0xa000409, 0x2020001, 0xa020001, 0x2020009, - 0xa020009, 0x2020401, 0xa020401, 0x2020409, 0xa020409 }, - { 0, 256, 0x80000, 0x80100, 0x1000000, 0x1000100, 0x1080000, 0x1080100, 16, 272, 0x80010, 0x80110, - 0x1000010, 0x1000110, 0x1080010, 0x1080110, 0x200000, 0x200100, 0x280000, 0x280100, 0x1200000, - 0x1200100, 0x1280000, 0x1280100, 0x200010, 0x200110, 0x280010, 0x280110, 0x1200010, 0x1200110, - 0x1280010, 0x1280110, 512, 768, 0x80200, 0x80300, 0x1000200, 0x1000300, 0x1080200, 0x1080300, 528, - 784, 0x80210, 0x80310, 0x1000210, 0x1000310, 0x1080210, 0x1080310, 0x200200, 0x200300, 0x280200, - 0x280300, 0x1200200, 0x1200300, 0x1280200, 0x1280300, 0x200210, 0x200310, 0x280210, 0x280310, - 0x1200210, 0x1200310, 0x1280210, 0x1280310 }, - { 0, 0x4000000, 0x40000, 0x4040000, 2, 0x4000002, 0x40002, 0x4040002, 8192, 0x4002000, 0x42000, 0x4042000, - 8194, 0x4002002, 0x42002, 0x4042002, 32, 0x4000020, 0x40020, 0x4040020, 34, 0x4000022, 0x40022, - 0x4040022, 8224, 0x4002020, 0x42020, 0x4042020, 8226, 0x4002022, 0x42022, 0x4042022, 2048, - 0x4000800, 0x40800, 0x4040800, 2050, 0x4000802, 0x40802, 0x4040802, 10240, 0x4002800, 0x42800, - 0x4042800, 10242, 0x4002802, 0x42802, 0x4042802, 2080, 0x4000820, 0x40820, 0x4040820, 2082, - 0x4000822, 0x40822, 0x4040822, 10272, 0x4002820, 0x42820, 0x4042820, 10274, 0x4002822, 0x42822, - 0x4042822 } }; - - private static final int SPTRANS[][] = { - { 0x820200, 0x20000, 0x80800000, 0x80820200, 0x800000, 0x80020200, 0x80020000, 0x80800000, 0x80020200, - 0x820200, 0x820000, 0x80000200, 0x80800200, 0x800000, 0, 0x80020000, 0x20000, 0x80000000, - 0x800200, 0x20200, 0x80820200, 0x820000, 0x80000200, 0x800200, 0x80000000, 512, 0x20200, - 0x80820000, 512, 0x80800200, 0x80820000, 0, 0, 0x80820200, 0x800200, 0x80020000, 0x820200, - 0x20000, 0x80000200, 0x800200, 0x80820000, 512, 0x20200, 0x80800000, 0x80020200, 0x80000000, - 0x80800000, 0x820000, 0x80820200, 0x20200, 0x820000, 0x80800200, 0x800000, 0x80000200, 0x80020000, - 0, 0x20000, 0x800000, 0x80800200, 0x820200, 0x80000000, 0x80820000, 512, 0x80020200 }, - { 0x10042004, 0, 0x42000, 0x10040000, 0x10000004, 8196, 0x10002000, 0x42000, 8192, 0x10040004, 4, - 0x10002000, 0x40004, 0x10042000, 0x10040000, 4, 0x40000, 0x10002004, 0x10040004, 8192, 0x42004, - 0x10000000, 0, 0x40004, 0x10002004, 0x42004, 0x10042000, 0x10000004, 0x10000000, 0x40000, 8196, - 0x10042004, 0x40004, 0x10042000, 0x10002000, 0x42004, 0x10042004, 0x40004, 0x10000004, 0, - 0x10000000, 8196, 0x40000, 0x10040004, 8192, 0x10000000, 0x42004, 0x10002004, 0x10042000, 8192, 0, - 0x10000004, 4, 0x10042004, 0x42000, 0x10040000, 0x10040004, 0x40000, 8196, 0x10002000, 0x10002004, - 4, 0x10040000, 0x42000 }, - { 0x41000000, 0x1010040, 64, 0x41000040, 0x40010000, 0x1000000, 0x41000040, 0x10040, 0x1000040, 0x10000, - 0x1010000, 0x40000000, 0x41010040, 0x40000040, 0x40000000, 0x41010000, 0, 0x40010000, 0x1010040, - 64, 0x40000040, 0x41010040, 0x10000, 0x41000000, 0x41010000, 0x1000040, 0x40010040, 0x1010000, - 0x10040, 0, 0x1000000, 0x40010040, 0x1010040, 64, 0x40000000, 0x10000, 0x40000040, 0x40010000, - 0x1010000, 0x41000040, 0, 0x1010040, 0x10040, 0x41010000, 0x40010000, 0x1000000, 0x41010040, - 0x40000000, 0x40010040, 0x41000000, 0x1000000, 0x41010040, 0x10000, 0x1000040, 0x41000040, - 0x10040, 0x1000040, 0, 0x41010000, 0x40000040, 0x41000000, 0x40010040, 64, 0x1010000 }, - { 0x100402, 0x4000400, 2, 0x4100402, 0, 0x4100000, 0x4000402, 0x100002, 0x4100400, 0x4000002, 0x4000000, - 1026, 0x4000002, 0x100402, 0x100000, 0x4000000, 0x4100002, 0x100400, 1024, 2, 0x100400, 0x4000402, - 0x4100000, 1024, 1026, 0, 0x100002, 0x4100400, 0x4000400, 0x4100002, 0x4100402, 0x100000, - 0x4100002, 1026, 0x100000, 0x4000002, 0x100400, 0x4000400, 2, 0x4100000, 0x4000402, 0, 1024, - 0x100002, 0, 0x4100002, 0x4100400, 1024, 0x4000000, 0x4100402, 0x100402, 0x100000, 0x4100402, 2, - 0x4000400, 0x100402, 0x100002, 0x100400, 0x4100000, 0x4000402, 1026, 0x4000000, 0x4000002, - 0x4100400 }, - { 0x2000000, 16384, 256, 0x2004108, 0x2004008, 0x2000100, 16648, 0x2004000, 16384, 8, 0x2000008, 16640, - 0x2000108, 0x2004008, 0x2004100, 0, 16640, 0x2000000, 16392, 264, 0x2000100, 16648, 0, 0x2000008, - 8, 0x2000108, 0x2004108, 16392, 0x2004000, 256, 264, 0x2004100, 0x2004100, 0x2000108, 16392, - 0x2004000, 16384, 8, 0x2000008, 0x2000100, 0x2000000, 16640, 0x2004108, 0, 16648, 0x2000000, 256, - 16392, 0x2000108, 256, 0, 0x2004108, 0x2004008, 0x2004100, 264, 16384, 16640, 0x2004008, - 0x2000100, 264, 8, 16648, 0x2004000, 0x2000008 }, - { 0x20000010, 0x80010, 0, 0x20080800, 0x80010, 2048, 0x20000810, 0x80000, 2064, 0x20080810, 0x80800, - 0x20000000, 0x20000800, 0x20000010, 0x20080000, 0x80810, 0x80000, 0x20000810, 0x20080010, 0, 2048, - 16, 0x20080800, 0x20080010, 0x20080810, 0x20080000, 0x20000000, 2064, 16, 0x80800, 0x80810, - 0x20000800, 2064, 0x20000000, 0x20000800, 0x80810, 0x20080800, 0x80010, 0, 0x20000800, 0x20000000, - 2048, 0x20080010, 0x80000, 0x80010, 0x20080810, 0x80800, 16, 0x20080810, 0x80800, 0x80000, - 0x20000810, 0x20000010, 0x20080000, 0x80810, 0, 2048, 0x20000010, 0x20000810, 0x20080800, - 0x20080000, 2064, 16, 0x20080010 }, - { 4096, 128, 0x400080, 0x400001, 0x401081, 4097, 4224, 0, 0x400000, 0x400081, 129, 0x401000, 1, 0x401080, - 0x401000, 129, 0x400081, 4096, 4097, 0x401081, 0, 0x400080, 0x400001, 4224, 0x401001, 4225, - 0x401080, 1, 4225, 0x401001, 128, 0x400000, 4225, 0x401000, 0x401001, 129, 4096, 128, 0x400000, - 0x401001, 0x400081, 4225, 4224, 0, 128, 0x400001, 1, 0x400080, 0, 0x400081, 0x400080, 4224, 129, - 4096, 0x401081, 0x400000, 0x401080, 1, 4097, 0x401081, 0x400001, 0x401080, 0x401000, 4097 }, - { 0x8200020, 0x8208000, 32800, 0, 0x8008000, 0x200020, 0x8200000, 0x8208020, 32, 0x8000000, 0x208000, - 32800, 0x208020, 0x8008020, 0x8000020, 0x8200000, 32768, 0x208020, 0x200020, 0x8008000, 0x8208020, - 0x8000020, 0, 0x208000, 0x8000000, 0x200000, 0x8008020, 0x8200020, 0x200000, 32768, 0x8208000, 32, - 0x200000, 32768, 0x8000020, 0x8208020, 32800, 0x8000000, 0, 0x208000, 0x8200020, 0x8008020, - 0x8008000, 0x200020, 0x8208000, 32, 0x200020, 0x8008000, 0x8208020, 0x200000, 0x8200000, - 0x8000020, 0x208000, 32800, 0x8008020, 0x8200000, 32, 0x8208000, 0x208020, 0, 0x8000000, - 0x8200020, 32768, 0x208020 } }; - - /** - * Generates a crypt(3) compatible hash using the DES algorithm. - *

- * As no salt is given, a random one will be used. - * - * @param original - * plaintext password - * @return a 13 character string starting with the salt string - */ - public static String crypt(final byte[] original) { - return crypt(original, null); - } - - /** - * Generates a crypt(3) compatible hash using the DES algorithm. - *

- * Using unspecified characters as salt results incompatible hash values. - * - * @param original - * plaintext password - * @param salt - * a two character string drawn from [a-zA-Z0-9./] or null for a random one - * @return a 13 character string starting with the salt string - * @throws IllegalArgumentException - * if the salt does not match the allowed pattern - */ - public static String crypt(final byte[] original, String salt) { - if (salt == null) { - final Random randomGenerator = new Random(); - final int numSaltChars = SALT_CHARS.length; - salt = "" + SALT_CHARS[randomGenerator.nextInt(numSaltChars)] + - SALT_CHARS[randomGenerator.nextInt(numSaltChars)]; - } else if (!salt.matches("^[" + B64.B64T + "]{2,}$")) { - throw new IllegalArgumentException("Invalid salt value: " + salt); - } - - final StringBuilder buffer = new StringBuilder(" "); - final char charZero = salt.charAt(0); - final char charOne = salt.charAt(1); - buffer.setCharAt(0, charZero); - buffer.setCharAt(1, charOne); - final int eSwap0 = CON_SALT[charZero]; - final int eSwap1 = CON_SALT[charOne] << 4; - final byte key[] = new byte[8]; - for (int i = 0; i < key.length; i++) { - key[i] = 0; - } - - for (int i = 0; i < key.length && i < original.length; i++) { - final int iChar = original[i]; - key[i] = (byte) (iChar << 1); - } - - final int schedule[] = desSetKey(key); - final int out[] = body(schedule, eSwap0, eSwap1); - final byte b[] = new byte[9]; - intToFourBytes(out[0], b, 0); - intToFourBytes(out[1], b, 4); - b[8] = 0; - int i = 2; - int y = 0; - int u = 128; - for (; i < 13; i++) { - int j = 0; - int c = 0; - for (; j < 6; j++) { - c <<= 1; - if ((b[y] & u) != 0) { - c |= 0x1; - } - u >>>= 1; - if (u == 0) { - y++; - u = 128; - } - buffer.setCharAt(i, (char) COV2CHAR[c]); - } - } - return buffer.toString(); - } - - /** - * Generates a crypt(3) compatible hash using the DES algorithm. - *

- * As no salt is given, a random one is used. - * - * @param original - * plaintext password - * @return a 13 character string starting with the salt string - */ - public static String crypt(final String original) { - return crypt(original.getBytes(Charsets.UTF_8)); - } - - /** - * Generates a crypt(3) compatible hash using the DES algorithm. - * - * @param original - * plaintext password - * @param salt - * a two character string drawn from [a-zA-Z0-9./] or null for a random one - * @return a 13 character string starting with the salt string - * @throws IllegalArgumentException - * if the salt does not match the allowed pattern - */ - public static String crypt(final String original, final String salt) { - return crypt(original.getBytes(Charsets.UTF_8), salt); - } - - private static int[] body(final int schedule[], final int eSwap0, final int eSwap1) { - int left = 0; - int right = 0; - int t = 0; - for (int j = 0; j < 25; j++) { - for (int i = 0; i < 32; i += 4) { - left = dEncrypt(left, right, i, eSwap0, eSwap1, schedule); - right = dEncrypt(right, left, i + 2, eSwap0, eSwap1, schedule); - } - t = left; - left = right; - right = t; - } - - t = right; - right = left >>> 1 | left << 31; - left = t >>> 1 | t << 31; - final int results[] = new int[2]; - permOp(right, left, 1, 0x55555555, results); - right = results[0]; - left = results[1]; - permOp(left, right, 8, 0xff00ff, results); - left = results[0]; - right = results[1]; - permOp(right, left, 2, 0x33333333, results); - right = results[0]; - left = results[1]; - permOp(left, right, 16, 65535, results); - left = results[0]; - right = results[1]; - permOp(right, left, 4, 0xf0f0f0f, results); - right = results[0]; - left = results[1]; - final int out[] = new int[2]; - out[0] = left; - out[1] = right; - return out; - } - - private static int byteToUnsigned(final byte b) { - final int value = b; - return value < 0 ? value + 256 : value; - } - - private static int dEncrypt(int el, final int r, final int s, final int e0, final int e1, final int sArr[]) { - int v = r ^ r >>> 16; - int u = v & e0; - v &= e1; - u = u ^ u << 16 ^ r ^ sArr[s]; - int t = v ^ v << 16 ^ r ^ sArr[s + 1]; - t = t >>> 4 | t << 28; - el ^= SPTRANS[1][t & 0x3f] | SPTRANS[3][t >>> 8 & 0x3f] | SPTRANS[5][t >>> 16 & 0x3f] | - SPTRANS[7][t >>> 24 & 0x3f] | SPTRANS[0][u & 0x3f] | SPTRANS[2][u >>> 8 & 0x3f] | - SPTRANS[4][u >>> 16 & 0x3f] | SPTRANS[6][u >>> 24 & 0x3f]; - return el; - } - - private static int[] desSetKey(final byte key[]) { - final int schedule[] = new int[32]; - int c = fourBytesToInt(key, 0); - int d = fourBytesToInt(key, 4); - final int results[] = new int[2]; - permOp(d, c, 4, 0xf0f0f0f, results); - d = results[0]; - c = results[1]; - c = hPermOp(c, -2, 0xcccc0000); - d = hPermOp(d, -2, 0xcccc0000); - permOp(d, c, 1, 0x55555555, results); - d = results[0]; - c = results[1]; - permOp(c, d, 8, 0xff00ff, results); - c = results[0]; - d = results[1]; - permOp(d, c, 1, 0x55555555, results); - d = results[0]; - c = results[1]; - d = (d & 0xff) << 16 | d & 0xff00 | (d & 0xff0000) >>> 16 | (c & 0xf0000000) >>> 4; - c &= 0xfffffff; - int j = 0; - for (int i = 0; i < 16; i++) { - if (SHIFT2[i]) { - c = c >>> 2 | c << 26; - d = d >>> 2 | d << 26; - } else { - c = c >>> 1 | c << 27; - d = d >>> 1 | d << 27; - } - c &= 0xfffffff; - d &= 0xfffffff; - int s = SKB[0][c & 0x3f] | SKB[1][c >>> 6 & 0x3 | c >>> 7 & 0x3c] | - SKB[2][c >>> 13 & 0xf | c >>> 14 & 0x30] | - SKB[3][c >>> 20 & 0x1 | c >>> 21 & 0x6 | c >>> 22 & 0x38]; - final int t = SKB[4][d & 0x3f] | SKB[5][d >>> 7 & 0x3 | d >>> 8 & 0x3c] | SKB[6][d >>> 15 & 0x3f] | - SKB[7][d >>> 21 & 0xf | d >>> 22 & 0x30]; - schedule[j++] = (t << 16 | s & 0xffff); - s = s >>> 16 | t & 0xffff0000; - s = s << 4 | s >>> 28; - schedule[j++] = s; - } - - return schedule; - } - - private static int fourBytesToInt(final byte b[], int offset) { - int value = byteToUnsigned(b[offset++]); - value |= byteToUnsigned(b[offset++]) << 8; - value |= byteToUnsigned(b[offset++]) << 16; - value |= byteToUnsigned(b[offset++]) << 24; - return value; - } - - private static int hPermOp(int a, final int n, final int m) { - final int t = (a << 16 - n ^ a) & m; - a = a ^ t ^ t >>> 16 - n; - return a; - } - - private static void intToFourBytes(final int iValue, final byte b[], int offset) { - b[offset++] = (byte) (iValue & 0xff); - b[offset++] = (byte) (iValue >>> 8 & 0xff); - b[offset++] = (byte) (iValue >>> 16 & 0xff); - b[offset++] = (byte) (iValue >>> 24 & 0xff); - } - - private static void permOp(int a, int b, final int n, final int m, final int results[]) { - final int t = (a >>> n ^ b) & m; - a ^= t << n; - b ^= t; - results[0] = a; - results[1] = b; - } - -} diff --git a/src/org/apache/commons/codec/digest/package.html b/src/org/apache/commons/codec/digest/package.html deleted file mode 100644 index 7ffeacd4..00000000 --- a/src/org/apache/commons/codec/digest/package.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - Simplifies common {@link java.security.MessageDigest} tasks and - includes a libc crypt(3) compatible crypt method that supports DES, - MD5, SHA-256 and SHA-512 based algorithms as well as the Apache - specific "$apr1$" variant. - - diff --git a/src/org/apache/commons/codec/overview.html b/src/org/apache/commons/codec/overview.html deleted file mode 100644 index fb10bca1..00000000 --- a/src/org/apache/commons/codec/overview.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - -

-This document is the API specification for the Apache Commons Codec Library, version 1.3. -

-

-This library requires a JRE version of 1.2.2 or greater. -The hypertext links originating from this document point to Sun's version 1.3 API as the 1.2.2 API documentation -is no longer on-line. -

- - diff --git a/src/org/apache/commons/codec/package.html b/src/org/apache/commons/codec/package.html deleted file mode 100644 index 4d04c845..00000000 --- a/src/org/apache/commons/codec/package.html +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - -

Interfaces and classes used by - the various implementations in the sub-packages.

- -

Definitive implementations of commonly used encoders and decoders.

- -

Codec is currently comprised of a modest set of utilities and a - simple framework for String encoding and decoding in three categories: - Binary Encoders, Language Encoders, and Network Encoders.

- -

Binary Encoders

- - - - - - - - - - - - - - -
- - org.apache.commons.codec.binary.Base64 - - Provides Base64 content-transfer-encoding as defined in - RFC 2045 - Production
- - org.apache.commons.codec.binary.Hex - - Converts an array of bytes into an array of characters - representing the hexadecimal values of each byte in order - Production
-

- Language Encoders -

-

- Codec contains a number of commonly used language and phonetic - encoders -

- - - - - - - - - - - - - -
- org.apache.commons.codec.language.Soundex - Implementation of the Soundex algorithm.Production
- org.apache.commons.codec.language.Metaphone - Implementation of the Metaphone algorithm.Production
-

Network Encoders

-

-

Codec contains network related encoders

- - - - - - - - -
- org.apache.commons.codec.net.URLCodec - Implements the 'www-form-urlencoded' encoding scheme.Production
-
- -