From 2a65ae256ec2170edaa50c9360a0afbfea15319c Mon Sep 17 00:00:00 2001 From: Dmitry Spikhalskiy Date: Sun, 8 Nov 2015 20:04:37 +0300 Subject: [PATCH] [#4331] Helper methods to get charset from Content-Type header of HttpMessage Motivation: HttpHeaders already has specific methods for such popular and simple headers like "Host", but if I need to convert POST raw body to string I need to parse complex ContentType header in my code. Modifications: Add getCharset and getCharsetAsString methods to parse charset from Content-Length header. Result: Easy to use utility method. --- NOTICE.txt | 8 + .../io/netty/handler/codec/http/HttpUtil.java | 56 +++++ .../handler/codec/http/HttpUtilTest.java | 44 +++- .../main/java/io/netty/util/AsciiString.java | 219 +++++++++++++++++- .../netty/util/AsciiStringCharacterTest.java | 41 ++++ license/LICENSE.commons-lang.txt | 177 ++++++++++++++ 6 files changed, 533 insertions(+), 12 deletions(-) create mode 100644 license/LICENSE.commons-lang.txt diff --git a/NOTICE.txt b/NOTICE.txt index c051eacb54..952a6e418a 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -219,3 +219,11 @@ the HTTP/2 HPACK algorithm written by Twitter. It can be obtained at: * HOMEPAGE: * https://github.com/twitter/hpack +This product contains a modified portion of 'Apache Commons Lang', a Java library +provides utilities for the java.lang API, which can be obtained at: + + * LICENSE: + * license/LICENSE.commons-lang.txt (Apache License 2.0) + * HOMEPAGE: + * https://commons.apache.org/proper/commons-lang/ + diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpUtil.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpUtil.java index 1bdf7e11bb..1755b25bbe 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpUtil.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpUtil.java @@ -16,9 +16,13 @@ package io.netty.handler.codec.http; import io.netty.buffer.ByteBuf; +import io.netty.util.AsciiString; +import io.netty.util.CharsetUtil; import java.net.URI; import java.util.ArrayList; +import java.nio.charset.Charset; +import java.nio.charset.UnsupportedCharsetException; import java.util.Iterator; import java.util.List; @@ -35,6 +39,7 @@ public final class HttpUtil { */ @Deprecated static final EmptyHttpHeaders EMPTY_HEADERS = new EmptyHttpHeaders(); + private static final AsciiString CHARSET_EQUALS = AsciiString.of(HttpHeaderValues.CHARSET + "="); private HttpUtil() { } @@ -325,6 +330,57 @@ public final class HttpUtil { } } + /** + * Fetch charset from message's Content-Type header. + * + * @return the charset from message's Content-Type header or {@link io.netty.util.CharsetUtil#ISO_8859_1} + * if charset is not presented or unparsable + */ + public static Charset getCharset(HttpMessage message) { + return getCharset(message, CharsetUtil.ISO_8859_1); + } + + /** + * Fetch charset from message's Content-Type header. + * + * @return the charset from message's Content-Type header or {@code defaultCharset} + * if charset is not presented or unparsable + */ + public static Charset getCharset(HttpMessage message, Charset defaultCharset) { + CharSequence charsetCharSequence = getCharsetAsString(message); + if (charsetCharSequence != null) { + try { + return Charset.forName(charsetCharSequence.toString()); + } catch (UnsupportedCharsetException unsupportedException) { + return defaultCharset; + } + } else { + return defaultCharset; + } + } + + /** + * Fetch charset string from message's Content-Type header. + * + * A lot of sites/possibly clients have charset="CHARSET", for example charset="utf-8". Or "utf8" instead of "utf-8" + * This is not according to standard, but this method provide an ability to catch desired mistakes manually in code + * + * @return the charset string from message's Content-Type header or {@code null} if charset is not presented + */ + public static CharSequence getCharsetAsString(HttpMessage message) { + CharSequence contentTypeValue = message.headers().get(HttpHeaderNames.CONTENT_TYPE); + if (contentTypeValue != null) { + int indexOfCharset = AsciiString.indexOfIgnoreCaseAscii(contentTypeValue, CHARSET_EQUALS, 0); + if (indexOfCharset != AsciiString.INDEX_NOT_FOUND) { + int indexOfEncoding = indexOfCharset + CHARSET_EQUALS.length(); + if (indexOfEncoding < contentTypeValue.length()) { + return contentTypeValue.subSequence(indexOfEncoding, contentTypeValue.length()); + } + } + } + return null; + } + static void encodeAscii0(CharSequence seq, ByteBuf buf) { int length = seq.length(); for (int i = 0 ; i < length; i++) { diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpUtilTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpUtilTest.java index ec6552a3e4..ce47340277 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpUtilTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpUtilTest.java @@ -15,16 +15,16 @@ */ package io.netty.handler.codec.http; -import io.netty.util.AsciiString; +import io.netty.util.CharsetUtil; import org.junit.Test; +import java.nio.charset.StandardCharsets; import java.util.List; import static io.netty.handler.codec.http.HttpHeadersTestUtils.of; -import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; public class HttpUtilTest { @@ -54,10 +54,38 @@ public class HttpUtilTest { } @Test - public void testEquansIgnoreCase() { - assertThat(AsciiString.contentEqualsIgnoreCase(null, null), is(true)); - assertThat(AsciiString.contentEqualsIgnoreCase(null, "foo"), is(false)); - assertThat(AsciiString.contentEqualsIgnoreCase("bar", null), is(false)); - assertThat(AsciiString.contentEqualsIgnoreCase("FoO", "fOo"), is(true)); + public void testGetCharsetAsRawString() { + HttpMessage message = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); + message.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=\"utf8\""); + assertEquals("\"utf8\"", HttpUtil.getCharsetAsString(message)); + + message.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html"); + assertNull(HttpUtil.getCharsetAsString(message)); + } + + @Test + public void testGetCharset() { + HttpMessage message = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); + message.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=utf-8"); + assertEquals(CharsetUtil.UTF_8, HttpUtil.getCharset(message)); + + message.headers().set(HttpHeaderNames.CONTENT_TYPE, "TEXT/HTML; CHARSET=UTF-8"); + assertEquals(CharsetUtil.UTF_8, HttpUtil.getCharset(message)); + } + + @Test + public void testGetCharset_defaultValue() { + HttpMessage message = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); + message.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html"); + assertEquals(CharsetUtil.ISO_8859_1, HttpUtil.getCharset(message)); + + message.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html"); + assertEquals(CharsetUtil.UTF_8, HttpUtil.getCharset(message, StandardCharsets.UTF_8)); + + message.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTFFF"); + assertEquals(CharsetUtil.ISO_8859_1, HttpUtil.getCharset(message)); + + message.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTFFF"); + assertEquals(CharsetUtil.UTF_8, HttpUtil.getCharset(message, StandardCharsets.UTF_8)); } } diff --git a/common/src/main/java/io/netty/util/AsciiString.java b/common/src/main/java/io/netty/util/AsciiString.java index 7a3689783b..38ce3243e8 100644 --- a/common/src/main/java/io/netty/util/AsciiString.java +++ b/common/src/main/java/io/netty/util/AsciiString.java @@ -48,6 +48,8 @@ public final class AsciiString implements CharSequence, Comparable public static final AsciiString EMPTY_STRING = new AsciiString(""); private static final char MAX_CHAR_VALUE = 255; + public static final int INDEX_NOT_FOUND = -1; + /** * If this value is modified outside the constructor then call {@link #arrayChanged()}. */ @@ -1382,7 +1384,7 @@ public final class AsciiString implements CharSequence, Comparable * Determine if {@code a} contains {@code b} in a case insensitive manner. */ public static boolean containsIgnoreCase(CharSequence a, CharSequence b) { - return contains(a, b, CaseInsensativeCharEqualityComparator.INSTANCE); + return contains(a, b, AsciiCaseInsensitiveCharEqualityComparator.INSTANCE); } /** @@ -1497,9 +1499,10 @@ public final class AsciiString implements CharSequence, Comparable } } - private static final class CaseInsensativeCharEqualityComparator implements CharEqualityComparator { - static final CaseInsensativeCharEqualityComparator INSTANCE = new CaseInsensativeCharEqualityComparator(); - private CaseInsensativeCharEqualityComparator() { } + private static final class AsciiCaseInsensitiveCharEqualityComparator implements CharEqualityComparator { + static final AsciiCaseInsensitiveCharEqualityComparator + INSTANCE = new AsciiCaseInsensitiveCharEqualityComparator(); + private AsciiCaseInsensitiveCharEqualityComparator() { } @Override public boolean equals(char a, char b) { @@ -1507,6 +1510,19 @@ public final class AsciiString implements CharSequence, Comparable } } + private static final class GeneralCaseInsensitiveCharEqualityComparator implements CharEqualityComparator { + static final GeneralCaseInsensitiveCharEqualityComparator + INSTANCE = new GeneralCaseInsensitiveCharEqualityComparator(); + private GeneralCaseInsensitiveCharEqualityComparator() { } + + @Override + public boolean equals(char a, char b) { + //For motivation, why we need two checks, see comment in String#regionMatches + return Character.toUpperCase(a) == Character.toUpperCase(b) || + Character.toLowerCase(a) == Character.toLowerCase(b); + } + } + private static boolean contains(CharSequence a, CharSequence b, CharEqualityComparator cmp) { if (a == null || b == null || a.length() < b.length()) { return false; @@ -1531,6 +1547,201 @@ public final class AsciiString implements CharSequence, Comparable return false; } + private static boolean regionMatchesCharSequences(final CharSequence cs, final int csStart, + final CharSequence string, final int start, final int length, + CharEqualityComparator charEqualityComparator) { + //general purpose implementation for CharSequences + if (csStart < 0 || length > cs.length() - csStart) { + return false; + } + if (start < 0 || length > string.length() - start) { + return false; + } + + int csIndex = csStart; + int csEnd = csIndex + length; + int stringIndex = start; + + while (csIndex < csEnd) { + char c1 = cs.charAt(csIndex++); + char c2 = string.charAt(stringIndex++); + + if (!charEqualityComparator.equals(c1, c2)) { + return false; + } + } + return true; + } + + /** + * This methods make regionMatches operation correctly for any chars in strings + * @param cs the {@code CharSequence} to be processed + * @param ignoreCase specifies if case should be ignored. + * @param csStart the starting offset in the {@code cs} CharSequence + * @param string the {@code CharSequence} to compare. + * @param start the starting offset in the specified {@code string}. + * @param length the number of characters to compare. + * @return {@code true} if the ranges of characters are equal, {@code false} otherwise. + */ + public static boolean regionMatches(final CharSequence cs, final boolean ignoreCase, final int csStart, + final CharSequence string, final int start, final int length) { + if (cs == null || string == null) { + return false; + } + + if (cs instanceof String && string instanceof String) { + return ((String) cs).regionMatches(ignoreCase, csStart, (String) string, start, length); + } + + if (cs instanceof AsciiString) { + return ((AsciiString) cs).regionMatches(ignoreCase, csStart, string, start, length); + } + + return regionMatchesCharSequences(cs, csStart, string, start, length, + ignoreCase ? GeneralCaseInsensitiveCharEqualityComparator.INSTANCE : + DefaultCharEqualityComparator.INSTANCE); + } + + /** + * This is optimized version of regionMatches for string with ASCII chars only + * @param cs the {@code CharSequence} to be processed + * @param ignoreCase specifies if case should be ignored. + * @param csStart the starting offset in the {@code cs} CharSequence + * @param string the {@code CharSequence} to compare. + * @param start the starting offset in the specified {@code string}. + * @param length the number of characters to compare. + * @return {@code true} if the ranges of characters are equal, {@code false} otherwise. + */ + public static boolean regionMatchesAscii(final CharSequence cs, final boolean ignoreCase, final int csStart, + final CharSequence string, final int start, final int length) { + if (cs == null || string == null) { + return false; + } + + if (!ignoreCase && cs instanceof String && string instanceof String) { + //we don't call regionMatches from String for ignoreCase==true. It's a general purpose method, + //which make complex comparison in case of ignoreCase==true, which is useless for ASCII-only strings. + //To avoid applying this complex ignore-case comparison, we will use regionMatchesCharSequences + return ((String) cs).regionMatches(false, csStart, (String) string, start, length); + } + + if (cs instanceof AsciiString) { + return ((AsciiString) cs).regionMatches(ignoreCase, csStart, string, start, length); + } + + return regionMatchesCharSequences(cs, csStart, string, start, length, + ignoreCase ? AsciiCaseInsensitiveCharEqualityComparator.INSTANCE : + DefaultCharEqualityComparator.INSTANCE); + } + + /** + *

Case in-sensitive find of the first index within a CharSequence + * from the specified position.

+ * + *

A {@code null} CharSequence will return {@code -1}. + * A negative start position is treated as zero. + * An empty ("") search CharSequence always matches. + * A start position greater than the string length only matches + * an empty search CharSequence.

+ * + *
+     * AsciiString.indexOfIgnoreCase(null, *, *)          = -1
+     * AsciiString.indexOfIgnoreCase(*, null, *)          = -1
+     * AsciiString.indexOfIgnoreCase("", "", 0)           = 0
+     * AsciiString.indexOfIgnoreCase("aabaabaa", "A", 0)  = 0
+     * AsciiString.indexOfIgnoreCase("aabaabaa", "B", 0)  = 2
+     * AsciiString.indexOfIgnoreCase("aabaabaa", "AB", 0) = 1
+     * AsciiString.indexOfIgnoreCase("aabaabaa", "B", 3)  = 5
+     * AsciiString.indexOfIgnoreCase("aabaabaa", "B", 9)  = -1
+     * AsciiString.indexOfIgnoreCase("aabaabaa", "B", -1) = 2
+     * AsciiString.indexOfIgnoreCase("aabaabaa", "", 2)   = 2
+     * AsciiString.indexOfIgnoreCase("abc", "", 9)        = -1
+     * 
+ * + * @param str the CharSequence to check, may be null + * @param searchStr the CharSequence to find, may be null + * @param startPos the start position, negative treated as zero + * @return the first index of the search CharSequence (always ≥ startPos), + * -1 if no match or {@code null} string input + * @throws NullPointerException if {@code cs} or {@code string} is {@code null}. + */ + public static int indexOfIgnoreCase(final CharSequence str, final CharSequence searchStr, int startPos) { + if (str == null || searchStr == null) { + return INDEX_NOT_FOUND; + } + if (startPos < 0) { + startPos = 0; + } + int searchStrLen = searchStr.length(); + final int endLimit = str.length() - searchStrLen + 1; + if (startPos > endLimit) { + return INDEX_NOT_FOUND; + } + if (searchStrLen == 0) { + return startPos; + } + for (int i = startPos; i < endLimit; i++) { + if (regionMatches(str, true, i, searchStr, 0, searchStrLen)) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + *

Case in-sensitive find of the first index within a CharSequence + * from the specified position. This method optimized and works correctly for ASCII CharSequences only

+ * + *

A {@code null} CharSequence will return {@code -1}. + * A negative start position is treated as zero. + * An empty ("") search CharSequence always matches. + * A start position greater than the string length only matches + * an empty search CharSequence.

+ * + *
+     * AsciiString.indexOfIgnoreCase(null, *, *)          = -1
+     * AsciiString.indexOfIgnoreCase(*, null, *)          = -1
+     * AsciiString.indexOfIgnoreCase("", "", 0)           = 0
+     * AsciiString.indexOfIgnoreCase("aabaabaa", "A", 0)  = 0
+     * AsciiString.indexOfIgnoreCase("aabaabaa", "B", 0)  = 2
+     * AsciiString.indexOfIgnoreCase("aabaabaa", "AB", 0) = 1
+     * AsciiString.indexOfIgnoreCase("aabaabaa", "B", 3)  = 5
+     * AsciiString.indexOfIgnoreCase("aabaabaa", "B", 9)  = -1
+     * AsciiString.indexOfIgnoreCase("aabaabaa", "B", -1) = 2
+     * AsciiString.indexOfIgnoreCase("aabaabaa", "", 2)   = 2
+     * AsciiString.indexOfIgnoreCase("abc", "", 9)        = -1
+     * 
+ * + * @param str the CharSequence to check, may be null + * @param searchStr the CharSequence to find, may be null + * @param startPos the start position, negative treated as zero + * @return the first index of the search CharSequence (always ≥ startPos), + * -1 if no match or {@code null} string input + * @throws NullPointerException if {@code cs} or {@code string} is {@code null}. + */ + public static int indexOfIgnoreCaseAscii(final CharSequence str, final CharSequence searchStr, int startPos) { + if (str == null || searchStr == null) { + return INDEX_NOT_FOUND; + } + if (startPos < 0) { + startPos = 0; + } + int searchStrLen = searchStr.length(); + final int endLimit = str.length() - searchStrLen + 1; + if (startPos > endLimit) { + return INDEX_NOT_FOUND; + } + if (searchStrLen == 0) { + return startPos; + } + for (int i = startPos; i < endLimit; i++) { + if (regionMatchesAscii(str, true, i, searchStr, 0, searchStrLen)) { + return i; + } + } + return INDEX_NOT_FOUND; + } + private static boolean equalsIgnoreCase(byte a, byte b) { return a == b || toLowerCase(a) == toLowerCase(b); } diff --git a/common/src/test/java/io/netty/util/AsciiStringCharacterTest.java b/common/src/test/java/io/netty/util/AsciiStringCharacterTest.java index 91253c8034..cbec28140d 100644 --- a/common/src/test/java/io/netty/util/AsciiStringCharacterTest.java +++ b/common/src/test/java/io/netty/util/AsciiStringCharacterTest.java @@ -24,9 +24,11 @@ import java.util.Random; import static io.netty.util.AsciiString.contains; import static io.netty.util.AsciiString.containsIgnoreCase; +import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; /** @@ -231,4 +233,43 @@ public class AsciiStringCharacterTest { assertTrue(new AsciiString(new byte[] { 5 }).parseBoolean()); assertTrue(new AsciiString(new byte[] { 2, 0 }).parseBoolean()); } + + @Test + public void testEqualsIgnoreCase() { + assertThat(AsciiString.contentEqualsIgnoreCase(null, null), is(true)); + assertThat(AsciiString.contentEqualsIgnoreCase(null, "foo"), is(false)); + assertThat(AsciiString.contentEqualsIgnoreCase("bar", null), is(false)); + assertThat(AsciiString.contentEqualsIgnoreCase("FoO", "fOo"), is(true)); + } + + @Test + public void testIndexOfIgnoreCase() { + assertEquals(-1, AsciiString.indexOfIgnoreCase(null, "abc", 1)); + assertEquals(-1, AsciiString.indexOfIgnoreCase("abc", null, 1)); + assertEquals(0, AsciiString.indexOfIgnoreCase("", "", 0)); + assertEquals(0, AsciiString.indexOfIgnoreCase("aabaabaa", "A", 0)); + assertEquals(2, AsciiString.indexOfIgnoreCase("aabaabaa", "B", 0)); + assertEquals(1, AsciiString.indexOfIgnoreCase("aabaabaa", "AB", 0)); + assertEquals(5, AsciiString.indexOfIgnoreCase("aabaabaa", "B", 3)); + assertEquals(-1, AsciiString.indexOfIgnoreCase("aabaabaa", "B", 9)); + assertEquals(2, AsciiString.indexOfIgnoreCase("aabaabaa", "B", -1)); + assertEquals(2, AsciiString.indexOfIgnoreCase("aabaabaa", "", 2)); + assertEquals(-1, AsciiString.indexOfIgnoreCase("abc", "", 9)); + assertEquals(0, AsciiString.indexOfIgnoreCase("ãabaabaa", "Ã", 0)); + } + + @Test + public void testIndexOfIgnoreCaseAscii() { + assertEquals(-1, AsciiString.indexOfIgnoreCaseAscii(null, "abc", 1)); + assertEquals(-1, AsciiString.indexOfIgnoreCaseAscii("abc", null, 1)); + assertEquals(0, AsciiString.indexOfIgnoreCaseAscii("", "", 0)); + assertEquals(0, AsciiString.indexOfIgnoreCaseAscii("aabaabaa", "A", 0)); + assertEquals(2, AsciiString.indexOfIgnoreCaseAscii("aabaabaa", "B", 0)); + assertEquals(1, AsciiString.indexOfIgnoreCaseAscii("aabaabaa", "AB", 0)); + assertEquals(5, AsciiString.indexOfIgnoreCaseAscii("aabaabaa", "B", 3)); + assertEquals(-1, AsciiString.indexOfIgnoreCaseAscii("aabaabaa", "B", 9)); + assertEquals(2, AsciiString.indexOfIgnoreCaseAscii("aabaabaa", "B", -1)); + assertEquals(2, AsciiString.indexOfIgnoreCaseAscii("aabaabaa", "", 2)); + assertEquals(-1, AsciiString.indexOfIgnoreCaseAscii("abc", "", 9)); + } } diff --git a/license/LICENSE.commons-lang.txt b/license/LICENSE.commons-lang.txt new file mode 100644 index 0000000000..66a27ec5ff --- /dev/null +++ b/license/LICENSE.commons-lang.txt @@ -0,0 +1,177 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS +