├── .gitignore ├── .travis.yml ├── LICENSE ├── src ├── main │ └── java │ │ └── io │ │ └── mola │ │ └── galimatias │ │ ├── canonicalize │ │ ├── CharacterPredicate.java │ │ ├── URLCanonicalizer.java │ │ ├── CombinedCanonicalizer.java │ │ ├── BaseURLCanonicalizer.java │ │ ├── StripPartCanonicalizer.java │ │ ├── RegexCanonicalizer.java │ │ ├── DecodeUnreservedCanonicalizer.java │ │ ├── RFC3986Canonicalizer.java │ │ └── RFC2396Canonicalizer.java │ │ ├── ErrorHandler.java │ │ ├── ParseIssue.java │ │ ├── DefaultErrorHandler.java │ │ ├── StrictErrorHandler.java │ │ ├── URLParsingSettings.java │ │ ├── Host.java │ │ ├── NameValue.java │ │ ├── GalimatiasParseException.java │ │ ├── IPv4Address.java │ │ ├── URLSearchParameters.java │ │ ├── cli │ │ └── CLI.java │ │ ├── Domain.java │ │ ├── FormURLEncodedParser.java │ │ └── IPv6Address.java └── test │ ├── java │ └── io │ │ └── mola │ │ └── galimatias │ │ ├── theories │ │ ├── AnyNameValue.java │ │ ├── AnyUrlyString.java │ │ ├── FooOrNullString.java │ │ ├── FooOrEmptyOrNullString.java │ │ ├── FooOrNullStringSupplier.java │ │ ├── FooOrEmptyOrNullStringSupplier.java │ │ ├── AnyUrlyStringSupplier.java │ │ └── AnyNameValueSupplier.java │ │ ├── cli │ │ └── CLITest.java │ │ ├── canonicalize │ │ ├── RegexCanonicalizerTest.java │ │ ├── RFC3986CanonicalizerTest.java │ │ ├── RFC2396CanonicalizerTest.java │ │ └── DecodeUnreservedCanonicalizerTest.java │ │ ├── HostTest.java │ │ ├── DomainTest.java │ │ ├── TestURL.java │ │ ├── NameValueTest.java │ │ ├── GalimatiasParseExceptionTest.java │ │ ├── ParseIssueTest.java │ │ ├── FormURLEncodedParserTest.java │ │ ├── IPv4AddressTest.java │ │ ├── TestURLLoader.java │ │ ├── BadURLTest.java │ │ ├── IPv6AddressTest.java │ │ └── URLSearchParamsTest.java │ └── resources │ └── data │ └── urltestdata_host_whatwg.txt ├── galimatias.iml ├── README.md └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | 3 | # IntelliJ IDEA 4 | #.idea/workspace.xml 5 | .idea 6 | 7 | # VIM 8 | .*.swp 9 | 10 | # Others 11 | *~ 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | - oraclejdk7 5 | - openjdk7 6 | - openjdk6 7 | after_success: 8 | - mvn clean cobertura:cobertura coveralls:cobertura 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2014 Santiago M. Mola 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 14 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/main/java/io/mola/galimatias/canonicalize/CharacterPredicate.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias.canonicalize; 23 | 24 | interface CharacterPredicate { 25 | boolean test(int c); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/io/mola/galimatias/canonicalize/URLCanonicalizer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias.canonicalize; 23 | 24 | import io.mola.galimatias.GalimatiasParseException; 25 | import io.mola.galimatias.URL; 26 | 27 | public interface URLCanonicalizer { 28 | 29 | public URL canonicalize(URL url) throws GalimatiasParseException; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/io/mola/galimatias/ErrorHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias; 23 | 24 | /** 25 | * Handler for parse errors. 26 | */ 27 | public interface ErrorHandler { 28 | 29 | public void error(GalimatiasParseException error) throws GalimatiasParseException; 30 | 31 | public void fatalError(GalimatiasParseException error); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/io/mola/galimatias/theories/AnyNameValue.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias.theories; 23 | 24 | import org.junit.experimental.theories.ParametersSuppliedBy; 25 | 26 | import java.lang.annotation.Retention; 27 | import java.lang.annotation.RetentionPolicy; 28 | 29 | @Retention(RetentionPolicy.RUNTIME) 30 | @ParametersSuppliedBy(AnyNameValueSupplier.class) 31 | public @interface AnyNameValue { 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/io/mola/galimatias/theories/AnyUrlyString.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias.theories; 23 | 24 | import org.junit.experimental.theories.ParametersSuppliedBy; 25 | 26 | import java.lang.annotation.Retention; 27 | import java.lang.annotation.RetentionPolicy; 28 | 29 | @Retention(RetentionPolicy.RUNTIME) 30 | @ParametersSuppliedBy(AnyUrlyStringSupplier.class) 31 | public @interface AnyUrlyString { 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/io/mola/galimatias/theories/FooOrNullString.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias.theories; 23 | 24 | import org.junit.experimental.theories.ParametersSuppliedBy; 25 | 26 | import java.lang.annotation.Retention; 27 | import java.lang.annotation.RetentionPolicy; 28 | 29 | @Retention(RetentionPolicy.RUNTIME) 30 | @ParametersSuppliedBy(FooOrNullStringSupplier.class) 31 | public @interface FooOrNullString { 32 | public String foo() default "foo"; 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/io/mola/galimatias/theories/FooOrEmptyOrNullString.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias.theories; 23 | 24 | import org.junit.experimental.theories.ParametersSuppliedBy; 25 | 26 | import java.lang.annotation.Retention; 27 | import java.lang.annotation.RetentionPolicy; 28 | 29 | @Retention(RetentionPolicy.RUNTIME) 30 | @ParametersSuppliedBy(FooOrEmptyOrNullStringSupplier.class) 31 | public @interface FooOrEmptyOrNullString { 32 | public String foo() default "foo"; 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/io/mola/galimatias/ParseIssue.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias; 23 | 24 | /** 25 | * Represents a parsing error. 26 | * 27 | * 28 | * This API is considered experimental and will change in 29 | * coming versions. 30 | * 31 | */ 32 | public enum ParseIssue { 33 | UNSPECIFIED, 34 | MISSING_SCHEME, 35 | INVALID_PERCENT_ENCODING, 36 | BACKSLASH_AS_DELIMITER, 37 | ILLEGAL_WHITESPACE, 38 | ILLEGAL_CHARACTER, 39 | INVALID_HOST 40 | } 41 | -------------------------------------------------------------------------------- /galimatias.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/test/java/io/mola/galimatias/cli/CLITest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias.cli; 23 | 24 | import io.mola.galimatias.GalimatiasParseException; 25 | import io.mola.galimatias.TestURL; 26 | import org.junit.experimental.theories.Theories; 27 | import org.junit.experimental.theories.Theory; 28 | import org.junit.runner.RunWith; 29 | 30 | @RunWith(Theories.class) 31 | public class CLITest { 32 | 33 | @Theory 34 | public void parse_no_exceptions(final @TestURL.TestURLs(dataset = TestURL.DATASETS.WHATWG) 35 | TestURL testURL) throws GalimatiasParseException { 36 | CLI.main(new String[]{ testURL.rawURL }); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/io/mola/galimatias/DefaultErrorHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias; 23 | 24 | /** 25 | * Default {@link io.mola.galimatias.ErrorHandler}. It 26 | * does nothing with errors. 27 | */ 28 | public final class DefaultErrorHandler implements ErrorHandler { 29 | 30 | private final static DefaultErrorHandler instance = new DefaultErrorHandler(); 31 | 32 | private DefaultErrorHandler() { 33 | 34 | } 35 | 36 | public static DefaultErrorHandler getInstance() { 37 | return instance; 38 | } 39 | 40 | @Override 41 | public void error(GalimatiasParseException error) throws GalimatiasParseException { 42 | 43 | } 44 | 45 | @Override 46 | public void fatalError(GalimatiasParseException error) { 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/io/mola/galimatias/StrictErrorHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias; 23 | 24 | /** 25 | * Strict {@link io.mola.galimatias.ErrorHandler}. 26 | * It throws an exception on any parse error, even 27 | * recoverable ones. 28 | */ 29 | public final class StrictErrorHandler implements ErrorHandler { 30 | 31 | private final static StrictErrorHandler instance = new StrictErrorHandler(); 32 | 33 | private StrictErrorHandler() { 34 | 35 | } 36 | 37 | public static StrictErrorHandler getInstance() { 38 | return instance; 39 | } 40 | 41 | @Override 42 | public void error(GalimatiasParseException error) throws GalimatiasParseException { 43 | throw error; 44 | } 45 | 46 | @Override 47 | public void fatalError(GalimatiasParseException error) { 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/io/mola/galimatias/theories/FooOrNullStringSupplier.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias.theories; 23 | 24 | import org.junit.experimental.theories.ParameterSignature; 25 | import org.junit.experimental.theories.ParameterSupplier; 26 | import org.junit.experimental.theories.PotentialAssignment; 27 | 28 | import java.util.ArrayList; 29 | import java.util.List; 30 | 31 | public class FooOrNullStringSupplier extends ParameterSupplier { 32 | 33 | @Override 34 | public List getValueSources(ParameterSignature sig) { 35 | FooOrNullString annotation = sig.getAnnotation(FooOrNullString.class); 36 | String foo = annotation.foo(); 37 | List res = new ArrayList(); 38 | for (final String value : new String[]{ foo, null }) { 39 | res.add(PotentialAssignment.forValue(value, value)); 40 | } 41 | return res; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/io/mola/galimatias/canonicalize/RegexCanonicalizerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias.canonicalize; 23 | 24 | import io.mola.galimatias.GalimatiasParseException; 25 | import io.mola.galimatias.URL; 26 | import org.junit.Test; 27 | import org.junit.experimental.theories.Theories; 28 | import org.junit.runner.RunWith; 29 | 30 | import java.util.regex.Pattern; 31 | 32 | import static org.fest.assertions.Assertions.assertThat; 33 | 34 | @RunWith(Theories.class) 35 | public class RegexCanonicalizerTest { 36 | 37 | @Test 38 | public void test() throws GalimatiasParseException { 39 | URLCanonicalizer canon = new RegexCanonicalizer(RegexCanonicalizer.Scope.HOST, Pattern.compile("^www\\."), ""); 40 | assertThat(canon.canonicalize(URL.parse("http://www.example.com/"))).isEqualTo(URL.parse("http://example.com")); 41 | assertThat(canon.canonicalize(URL.parse("http://example.com/"))).isEqualTo(URL.parse("http://example.com")); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/io/mola/galimatias/theories/FooOrEmptyOrNullStringSupplier.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias.theories; 23 | 24 | import org.junit.experimental.theories.ParameterSignature; 25 | import org.junit.experimental.theories.ParameterSupplier; 26 | import org.junit.experimental.theories.PotentialAssignment; 27 | 28 | import java.util.ArrayList; 29 | import java.util.List; 30 | 31 | public class FooOrEmptyOrNullStringSupplier extends ParameterSupplier { 32 | 33 | @Override 34 | public List getValueSources(ParameterSignature sig) { 35 | FooOrEmptyOrNullString annotation = sig.getAnnotation(FooOrEmptyOrNullString.class); 36 | String foo = annotation.foo(); 37 | List res = new ArrayList(); 38 | for (final String value : new String[]{ foo, "", null }) { 39 | res.add(PotentialAssignment.forValue(value, value)); 40 | } 41 | return res; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/io/mola/galimatias/URLParsingSettings.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias; 23 | 24 | /** 25 | * Provides settings for URL parsing. 26 | * 27 | * This class is immutable and all its attributes are immutable 28 | * by default too. 29 | */ 30 | public final class URLParsingSettings { 31 | 32 | private static URLParsingSettings DEFAULT = new URLParsingSettings(); 33 | 34 | 35 | private ErrorHandler errorHandler; 36 | 37 | private URLParsingSettings() { 38 | this(DefaultErrorHandler.getInstance()); 39 | } 40 | 41 | private URLParsingSettings(final ErrorHandler errorHandler) { 42 | this.errorHandler = errorHandler; 43 | } 44 | 45 | public ErrorHandler errorHandler() { 46 | return this.errorHandler; 47 | } 48 | 49 | public static URLParsingSettings create() { 50 | return DEFAULT; 51 | } 52 | 53 | public URLParsingSettings withErrorHandler(final ErrorHandler handler) { 54 | return new URLParsingSettings(handler); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/io/mola/galimatias/theories/AnyUrlyStringSupplier.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias.theories; 23 | 24 | import org.junit.experimental.theories.ParameterSignature; 25 | import org.junit.experimental.theories.ParameterSupplier; 26 | import org.junit.experimental.theories.PotentialAssignment; 27 | 28 | import java.util.ArrayList; 29 | import java.util.List; 30 | 31 | public class AnyUrlyStringSupplier extends ParameterSupplier { 32 | 33 | final static String[] values = new String[]{ 34 | "foo", "f", "", 35 | "foo=", "=foo", "=", 36 | "foo&", "&foo", "&", 37 | "foo?", "?foo", "?", 38 | "foo#", "#foo", "#" 39 | }; 40 | 41 | @Override 42 | public List getValueSources(ParameterSignature sig) { 43 | AnyUrlyString annotation = sig.getAnnotation(AnyUrlyString.class); 44 | List res = new ArrayList(); 45 | for (final String value : values) { 46 | res.add(PotentialAssignment.forValue(value, value)); 47 | } 48 | return res; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/io/mola/galimatias/HostTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias; 23 | 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | import org.junit.runners.JUnit4; 27 | 28 | import static org.fest.assertions.Assertions.assertThat; 29 | 30 | @RunWith(JUnit4.class) 31 | public class HostTest { 32 | 33 | @Test 34 | public void parseTest() throws GalimatiasParseException { 35 | assertThat(Host.parseHost("example.com")).isInstanceOf(Domain.class); 36 | assertThat(Host.parseHost("[2001:0db8:85a3:08d3:1319:8a2e:0370:7334]")).isInstanceOf(IPv6Address.class); 37 | } 38 | 39 | @Test(expected = GalimatiasParseException.class) 40 | public void parseHostWithUnmatchedBracket() throws GalimatiasParseException { 41 | Host.parseHost("[2001:0db8:85a3:08d3:1319:8a2e:0370:7334"); 42 | } 43 | 44 | @Test(expected = NullPointerException.class) 45 | public void parseNullHost() throws GalimatiasParseException { 46 | Host.parseHost(null); 47 | } 48 | 49 | @Test(expected = GalimatiasParseException.class) 50 | public void parseEmptyHost() throws GalimatiasParseException { 51 | Host.parseHost(""); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/io/mola/galimatias/canonicalize/CombinedCanonicalizer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias.canonicalize; 23 | 24 | import io.mola.galimatias.GalimatiasParseException; 25 | import io.mola.galimatias.URL; 26 | 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | 30 | public class CombinedCanonicalizer implements URLCanonicalizer { 31 | 32 | private final List canonicalizers; 33 | 34 | public CombinedCanonicalizer(final URLCanonicalizer ... canons) { 35 | canonicalizers = new ArrayList(); 36 | for (final URLCanonicalizer canon : canons) { 37 | if (canon instanceof CombinedCanonicalizer) { 38 | canonicalizers.addAll(((CombinedCanonicalizer) canon).canonicalizers); 39 | } else { 40 | canonicalizers.add(canon); 41 | } 42 | } 43 | } 44 | 45 | @Override 46 | public URL canonicalize(final URL input) throws GalimatiasParseException { 47 | URL result = input; 48 | for (final URLCanonicalizer canon : canonicalizers) { 49 | result = canon.canonicalize(result); 50 | } 51 | return result; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/io/mola/galimatias/theories/AnyNameValueSupplier.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias.theories; 23 | 24 | import io.mola.galimatias.NameValue; 25 | import org.junit.experimental.theories.ParameterSignature; 26 | import org.junit.experimental.theories.ParameterSupplier; 27 | import org.junit.experimental.theories.PotentialAssignment; 28 | 29 | import java.util.ArrayList; 30 | import java.util.List; 31 | 32 | public class AnyNameValueSupplier extends ParameterSupplier { 33 | 34 | final static String[] values = new String[]{ 35 | "foo", "f", "", 36 | "foo=", "=foo", "=", 37 | "foo&", "&foo", "&", 38 | "foo?", "?foo", "?", 39 | "foo#", "#foo", "#" 40 | }; 41 | 42 | @Override 43 | public List getValueSources(ParameterSignature sig) { 44 | List res = new ArrayList(); 45 | for (final String name : values) { 46 | for (final String value : values) { 47 | final NameValue nameValue = new NameValue(name, value); 48 | res.add(PotentialAssignment.forValue(nameValue.toString(), nameValue)); 49 | } 50 | } 51 | return res; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/io/mola/galimatias/canonicalize/BaseURLCanonicalizer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias.canonicalize; 23 | 24 | import static io.mola.galimatias.URLUtils.UTF_8; 25 | import static io.mola.galimatias.URLUtils.isASCIIHexDigit; 26 | import static io.mola.galimatias.URLUtils.percentEncode; 27 | 28 | abstract class BaseURLCanonicalizer implements URLCanonicalizer { 29 | 30 | protected static String canonicalize(String input, CharacterPredicate unencodedPredicate) { 31 | StringBuilder result = new StringBuilder(); 32 | final int length = input.length(); 33 | for (int offset = 0; offset < length; ) { 34 | final int c = input.codePointAt(offset); 35 | 36 | if ((c == '%' && input.length() > offset + 2 && 37 | isASCIIHexDigit(input.charAt(offset + 1)) && isASCIIHexDigit(input.charAt(offset + 2))) || 38 | unencodedPredicate.test(c)) { 39 | result.append((char) c); 40 | } else { 41 | final byte[] bytes = new String(Character.toChars(c)).getBytes(UTF_8); 42 | for (final byte b : bytes) { 43 | percentEncode(b, result); 44 | } 45 | } 46 | 47 | offset += Character.charCount(c); 48 | } 49 | return result.toString(); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/io/mola/galimatias/canonicalize/StripPartCanonicalizer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias.canonicalize; 23 | 24 | import io.mola.galimatias.GalimatiasParseException; 25 | import io.mola.galimatias.URL; 26 | 27 | public class StripPartCanonicalizer implements URLCanonicalizer { 28 | 29 | public static enum Part { 30 | USERNAME, 31 | PASSWORD, 32 | PORT, 33 | PATH, 34 | QUERY, 35 | FRAGMENT 36 | } 37 | 38 | private final Part part; 39 | 40 | public StripPartCanonicalizer(final Part part) { 41 | this.part = part; 42 | } 43 | 44 | @Override 45 | public URL canonicalize(final URL input) throws GalimatiasParseException { 46 | switch (part) { 47 | case USERNAME: 48 | return input.withUsername(null); 49 | case PASSWORD: 50 | return input.withPassword(null); 51 | case PORT: 52 | return input.withPort(-1); 53 | case PATH: 54 | return input.withPath("/"); 55 | case QUERY: 56 | return input.withQuery(null); 57 | case FRAGMENT: 58 | return input.withFragment(null); 59 | default: 60 | return input; 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/io/mola/galimatias/Host.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias; 23 | 24 | import java.io.Serializable; 25 | 26 | public abstract class Host implements Serializable { 27 | 28 | @Override 29 | public abstract String toString(); 30 | 31 | public abstract String toHumanString(); 32 | 33 | /** 34 | * Parses a host as found in URLs. IPv6 literals are expected 35 | * enclosed in square brackets (i.e. [ipv6-literal]). 36 | * 37 | * @param input 38 | * @return 39 | * @throws GalimatiasParseException 40 | */ 41 | public static Host parseHost(final String input) throws GalimatiasParseException { 42 | if (input == null) { 43 | throw new NullPointerException("null host"); 44 | } 45 | if (input.isEmpty()) { 46 | throw new GalimatiasParseException("empty host", -1); 47 | } 48 | if (input.charAt(0) == '[') { 49 | if (input.charAt(input.length() - 1) != ']') { 50 | throw new GalimatiasParseException("Unmatched '['", -1); 51 | } 52 | return IPv6Address.parseIPv6Address(input.substring(1, input.length() - 1)); 53 | } 54 | final Domain domain = Domain.parseDomain(input); 55 | try { 56 | return IPv4Address.parseIPv4Address(domain.toString()); 57 | } catch (GalimatiasParseException e) { 58 | return domain; 59 | } 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/io/mola/galimatias/NameValue.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias; 23 | 24 | 25 | /** 26 | * A name-value pair. Used for query parameters APIs. 27 | */ 28 | public class NameValue { 29 | 30 | private final String name; 31 | private final String value; 32 | 33 | public NameValue(final String name, final String value) { 34 | if (name == null) { 35 | throw new NullPointerException("name"); 36 | } 37 | if (value == null) { 38 | throw new NullPointerException("value"); 39 | } 40 | this.name = name; 41 | this.value = value; 42 | } 43 | 44 | public String name() { 45 | return name; 46 | } 47 | 48 | public String value() { 49 | return value; 50 | } 51 | 52 | @Override 53 | public boolean equals(Object o) { 54 | if (this == o) return true; 55 | if (o == null || getClass() != o.getClass()) return false; 56 | NameValue that = (NameValue) o; 57 | return name.equals(that.name) && value.equals(that.value); 58 | } 59 | 60 | @Override 61 | public int hashCode() { 62 | int result = 1; 63 | result = 31 * result + name.hashCode(); 64 | result = 31 * result + value.hashCode(); 65 | return result; 66 | } 67 | 68 | @Override 69 | public String toString() { 70 | return "NameValuePair{" + 71 | "name='" + name + '\'' + 72 | ", value='" + value + '\'' + 73 | '}'; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/io/mola/galimatias/canonicalize/RegexCanonicalizer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias.canonicalize; 23 | 24 | import io.mola.galimatias.GalimatiasParseException; 25 | import io.mola.galimatias.URL; 26 | 27 | import java.util.regex.Pattern; 28 | 29 | public class RegexCanonicalizer implements URLCanonicalizer { 30 | 31 | public static enum Scope { 32 | HOST, 33 | PATH, 34 | QUERY, 35 | FRAGMENT, 36 | FULL 37 | } 38 | 39 | private final Scope scope; 40 | private final Pattern pattern; 41 | private final String substitution; 42 | 43 | public RegexCanonicalizer(final Scope scope, final Pattern pattern, final String substitution) { 44 | this.scope = scope; 45 | this.pattern = pattern; 46 | this.substitution = substitution; 47 | } 48 | 49 | @Override 50 | public URL canonicalize(final URL input) throws GalimatiasParseException { 51 | switch (scope) { 52 | case HOST: 53 | return input.withHost(pattern.matcher(input.host().toString()).replaceAll(substitution)); 54 | case PATH: 55 | return input.withPath(pattern.matcher(input.path()).replaceAll(substitution)); 56 | case QUERY: 57 | return input.withQuery(pattern.matcher(input.query()).replaceAll(substitution)); 58 | case FRAGMENT: 59 | return input.withFragment(pattern.matcher(input.fragment()).replaceAll(substitution)); 60 | case FULL: 61 | return URL.parse(pattern.matcher(input.toString()).replaceAll(substitution)); 62 | default: 63 | return input; 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/io/mola/galimatias/DomainTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias; 23 | 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | import org.junit.runners.JUnit4; 27 | 28 | import static org.fest.assertions.Assertions.assertThat; 29 | 30 | @RunWith(JUnit4.class) 31 | public class DomainTest { 32 | 33 | @Test 34 | public void equals() throws GalimatiasParseException { 35 | final Domain sameDomain = Domain.parseDomain("example.com"); 36 | assertThat(sameDomain).isEqualTo(sameDomain); 37 | assertThat(sameDomain).isEqualTo(Domain.parseDomain("example.com")); 38 | assertThat(sameDomain).isEqualTo(Domain.parseDomain("EXAMPLE.COM")); 39 | assertThat(sameDomain).isNotEqualTo(Domain.parseDomain("other.com")); 40 | assertThat(sameDomain).isNotEqualTo(Domain.parseDomain("other.example.com")); 41 | assertThat(sameDomain).isNotEqualTo("foo"); 42 | assertThat(sameDomain).isNotEqualTo(null); 43 | } 44 | 45 | @Test 46 | public void parseDomainIDNA() throws GalimatiasParseException { 47 | assertThat(Domain.parseDomain("ジェーピーニック.jp").toString()).isEqualTo("xn--hckqz9bzb1cyrb.jp"); 48 | } 49 | 50 | @Test(expected = GalimatiasParseException.class) 51 | public void parseDomainEmpty() throws GalimatiasParseException { 52 | Domain.parseDomain(""); 53 | } 54 | 55 | @Test(expected = NullPointerException.class) 56 | public void parseDomainNull() throws GalimatiasParseException { 57 | Domain.parseDomain(null); 58 | } 59 | 60 | @Test(expected = GalimatiasParseException.class) 61 | public void parseDomainDot() throws GalimatiasParseException { 62 | Domain.parseDomain("."); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/io/mola/galimatias/canonicalize/RFC3986CanonicalizerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias.canonicalize; 23 | 24 | import io.mola.galimatias.GalimatiasParseException; 25 | import io.mola.galimatias.TestURL; 26 | import io.mola.galimatias.URL; 27 | import org.junit.Test; 28 | import org.junit.experimental.theories.Theories; 29 | import org.junit.experimental.theories.Theory; 30 | import org.junit.runner.RunWith; 31 | 32 | import static org.fest.assertions.Assertions.assertThat; 33 | import static org.junit.Assume.assumeNotNull; 34 | 35 | @RunWith(Theories.class) 36 | public class RFC3986CanonicalizerTest { 37 | 38 | @Test 39 | public void test() throws GalimatiasParseException { 40 | final URLCanonicalizer canon = new RFC3986Canonicalizer(); 41 | for (final String[] pair : new String[][] { 42 | new String[]{ "http://example.com/^{}|[]`~", "http://example.com/%5E%7B%7D%7C%5B%5D%60~" }, 43 | new String[]{ "http://example.com/?^{}|[]`~", "http://example.com/?%5E%7B%7D%7C%5B%5D%60~" }, 44 | new String[]{ "http://example.com/#^{}|[]`~", "http://example.com/#%5E%7B%7D%7C%5B%5D%60~" } 45 | }) { 46 | assertThat(canon.canonicalize(URL.parse(pair[0])).toString()) 47 | .isEqualTo(URL.parse(pair[1]).toString()); 48 | } 49 | } 50 | 51 | @Theory 52 | public void idempotence(final @TestURL.TestURLs(dataset = TestURL.DATASETS.WHATWG) TestURL testURL) throws GalimatiasParseException { 53 | assumeNotNull(testURL.parsedURL); 54 | final URLCanonicalizer canon = new RFC2396Canonicalizer(); 55 | final URL roundOne = canon.canonicalize(testURL.parsedURL); 56 | final URL roundTwo = canon.canonicalize(roundOne); 57 | assertThat(roundOne).isEqualTo(roundTwo); 58 | final URL reparse = URL.parse(roundTwo.toString()); 59 | assertThat(reparse).isEqualTo(roundTwo); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/io/mola/galimatias/canonicalize/RFC2396CanonicalizerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias.canonicalize; 23 | 24 | import io.mola.galimatias.GalimatiasParseException; 25 | import io.mola.galimatias.TestURL; 26 | import io.mola.galimatias.URL; 27 | import org.junit.Test; 28 | import org.junit.experimental.theories.Theories; 29 | import org.junit.experimental.theories.Theory; 30 | import org.junit.runner.RunWith; 31 | 32 | import static org.fest.assertions.Assertions.assertThat; 33 | import static org.junit.Assume.assumeNotNull; 34 | 35 | @RunWith(Theories.class) 36 | public class RFC2396CanonicalizerTest { 37 | 38 | @Test 39 | public void test() throws GalimatiasParseException { 40 | final URLCanonicalizer canon = new RFC2396Canonicalizer(); 41 | for (final String[] pair : new String[][] { 42 | new String[]{ "http://example.com/^{}|[]`~", "http://example.com/%5E%7B%7D%7C%5B%5D%60%7E" }, 43 | new String[]{ "http://example.com/?^{}|[]`~", "http://example.com/?%5E%7B%7D%7C%5B%5D%60%7E" }, 44 | new String[]{ "http://example.com/#^{}|[]`~", "http://example.com/#%5E%7B%7D%7C%5B%5D%60%7E" } 45 | }) { 46 | assertThat(canon.canonicalize(URL.parse(pair[0])).toString()) 47 | .isEqualTo(URL.parse(pair[1]).toString()); 48 | } 49 | } 50 | 51 | @Theory 52 | public void idempotence(final @TestURL.TestURLs(dataset = TestURL.DATASETS.WHATWG) TestURL testURL) throws GalimatiasParseException { 53 | assumeNotNull(testURL.parsedURL); 54 | final URLCanonicalizer canon = new RFC2396Canonicalizer(); 55 | final URL roundOne = canon.canonicalize(testURL.parsedURL); 56 | final URL roundTwo = canon.canonicalize(roundOne); 57 | assertThat(roundOne).isEqualTo(roundTwo); 58 | final URL reparse = URL.parse(roundTwo.toString()); 59 | assertThat(reparse).isEqualTo(roundTwo); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/io/mola/galimatias/canonicalize/DecodeUnreservedCanonicalizer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias.canonicalize; 23 | 24 | import io.mola.galimatias.GalimatiasParseException; 25 | import io.mola.galimatias.URL; 26 | import io.mola.galimatias.URLUtils; 27 | 28 | public class DecodeUnreservedCanonicalizer implements URLCanonicalizer { 29 | 30 | @Override 31 | public URL canonicalize(final URL input) throws GalimatiasParseException { 32 | if (input == null) { 33 | return input; 34 | } 35 | URL output = input; 36 | if (output.isHierarchical()) { 37 | output = output 38 | .withUsername(decodeUnreserved(output.username())) 39 | .withPassword(decodeUnreserved(output.password())) 40 | .withPath(decodeUnreserved(output.path())); 41 | } 42 | return output 43 | .withQuery(decodeUnreserved(output.query())) 44 | .withFragment(decodeUnreserved(output.fragment())); 45 | } 46 | 47 | private static String decodeUnreserved(final String input) { 48 | if (input == null || input.isEmpty()) { 49 | return input; 50 | } 51 | final StringBuilder output = new StringBuilder(); 52 | for (int i = 0; i < input.length(); i++) { 53 | final char c = input.charAt(i); 54 | if (c == '%' && input.length() > i + 2 && 55 | URLUtils.isASCIIHexDigit(input.charAt(i + 1)) && 56 | URLUtils.isASCIIHexDigit(input.charAt(i + 2))) { 57 | final int d = URLUtils.hexToInt(input.charAt(i + 1), input.charAt(i + 2)); 58 | if (URLUtils.isASCIIAlphanumeric(d) || d == 0x2D || d == 0x2E || d == 0x5F || d == 0x7E) { 59 | output.appendCodePoint(d); 60 | } else { 61 | output.append(input.substring(i, i + 3)); 62 | } 63 | i += 2; 64 | } else { 65 | output.append(c); 66 | } 67 | } 68 | return output.toString(); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/io/mola/galimatias/canonicalize/DecodeUnreservedCanonicalizerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias.canonicalize; 23 | 24 | import io.mola.galimatias.GalimatiasParseException; 25 | import io.mola.galimatias.TestURL; 26 | import io.mola.galimatias.URL; 27 | import org.junit.Test; 28 | import org.junit.experimental.theories.Theories; 29 | import org.junit.experimental.theories.Theory; 30 | import org.junit.runner.RunWith; 31 | 32 | import static org.fest.assertions.Assertions.assertThat; 33 | import static org.junit.Assume.assumeNotNull; 34 | 35 | @RunWith(Theories.class) 36 | public class DecodeUnreservedCanonicalizerTest { 37 | 38 | @Test 39 | public void test() throws GalimatiasParseException { 40 | final URLCanonicalizer canon = new DecodeUnreservedCanonicalizer(); 41 | for (final String[] pair : new String[][] { 42 | new String[]{ "http://%41%5A%61%7A%30%39%2D%2E%5F%7E@example.com/", "http://AZaz09-._~@example.com/"}, 43 | new String[]{ "http://:%41%5A%61%7A%30%39%2D%2E%5F%7E@example.com/", "http://:AZaz09-._~@example.com/"}, 44 | new String[]{ "http://example.com/%41%5A%61%7A%30%39%2D%2E%5F%7E", "http://example.com/AZaz09-._~" }, 45 | new String[]{ "http://example.com/?%41%5A%61%7A%30%39%2D%2E%5F%7E", "http://example.com/?AZaz09-._~" }, 46 | new String[]{ "http://example.com/#%41%5A%61%7A%30%39%2D%2E%5F%7E", "http://example.com/#AZaz09-._~" } 47 | }) { 48 | assertThat(canon.canonicalize(URL.parse(pair[0])).toString()) 49 | .isEqualTo(URL.parse(pair[1]).toString()); 50 | } 51 | } 52 | 53 | @Theory 54 | public void idempotence(final @TestURL.TestURLs(dataset = TestURL.DATASETS.WHATWG) TestURL testURL) throws GalimatiasParseException { 55 | assumeNotNull(testURL.parsedURL); 56 | final URLCanonicalizer canon = new DecodeUnreservedCanonicalizer(); 57 | final URL roundOne = canon.canonicalize(testURL.parsedURL); 58 | final URL roundTwo = canon.canonicalize(roundOne); 59 | assertThat(roundOne).isEqualTo(roundTwo); 60 | final URL reparse = URL.parse(roundTwo.toString()); 61 | assertThat(reparse).isEqualTo(roundTwo); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/io/mola/galimatias/TestURL.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias; 23 | 24 | import org.junit.experimental.theories.ParameterSignature; 25 | import org.junit.experimental.theories.ParameterSupplier; 26 | import org.junit.experimental.theories.ParametersSuppliedBy; 27 | import org.junit.experimental.theories.PotentialAssignment; 28 | 29 | import java.lang.annotation.Retention; 30 | import java.lang.annotation.RetentionPolicy; 31 | import java.util.ArrayList; 32 | import java.util.HashMap; 33 | import java.util.List; 34 | import java.util.Map; 35 | 36 | public class TestURL { 37 | 38 | public String rawURL; 39 | public String rawBaseURL; 40 | public URL parsedURL; 41 | public URL parsedBaseURL; 42 | 43 | @Override 44 | public String toString() { 45 | return String.format("TestURL(rawURL=%s, rawBaseURL=%s, parsedURL=%s, parsedBaseURL=%s)", 46 | rawURL, rawBaseURL, parsedURL, parsedBaseURL); 47 | } 48 | 49 | public abstract class DATASETS { 50 | public static final String WHATWG = "/data/urltestdata_whatwg.txt"; 51 | public static final String HOST_WHATWG = "/data/urltestdata_host_whatwg.txt"; 52 | } 53 | 54 | @Retention(RetentionPolicy.RUNTIME) 55 | @ParametersSuppliedBy(TestURLSupplier.class) 56 | public static @interface TestURLs { 57 | String dataset() default DATASETS.WHATWG; 58 | } 59 | 60 | public static class TestURLSupplier extends ParameterSupplier { 61 | 62 | private static final Map> datasetMap = new HashMap>(); 63 | 64 | @Override 65 | public List getValueSources(final ParameterSignature sig) { 66 | final TestURLs ref = sig.getAnnotation(TestURLs.class); 67 | final String dataset = ref.dataset(); 68 | if (!datasetMap.containsKey(dataset)) { 69 | datasetMap.put(dataset, TestURLLoader.loadTestURLs(dataset)); 70 | } 71 | final List values = new ArrayList(); 72 | for (final TestURL testURL : datasetMap.get(dataset)) { 73 | values.add(PotentialAssignment.forValue(testURL.toString(), testURL)); 74 | } 75 | return values; 76 | } 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/io/mola/galimatias/GalimatiasParseException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias; 23 | 24 | /** 25 | * Exception thrown by parsers. 26 | */ 27 | public class GalimatiasParseException extends Exception { 28 | 29 | private ParseIssue parseIssue = ParseIssue.UNSPECIFIED; 30 | private int position = -1; 31 | 32 | private GalimatiasParseException() {} 33 | 34 | static Builder builder() { 35 | return new Builder(); 36 | } 37 | 38 | GalimatiasParseException(final String message) { 39 | super(message); 40 | } 41 | 42 | GalimatiasParseException(final String message, final int position) { 43 | super(message); 44 | this.position = position; 45 | } 46 | 47 | GalimatiasParseException(final String message, final ParseIssue parseIssue, final int position, final Throwable exception) { 48 | super(message, exception); 49 | 50 | if (parseIssue != null) { 51 | this.parseIssue = parseIssue; 52 | } 53 | 54 | this.position = position; 55 | } 56 | 57 | public int getPosition() { 58 | return position; 59 | } 60 | 61 | /** 62 | * Gets the @{link ParseIssue}. 63 | * 64 | * 65 | * This API is considered experimental and will change in 66 | * coming versions. 67 | * 68 | */ 69 | public ParseIssue getParseIssue() { 70 | return parseIssue; 71 | } 72 | 73 | static class Builder { 74 | private String message; 75 | private ParseIssue parseIssue; 76 | private int position; 77 | private Throwable cause; 78 | 79 | private Builder() {} 80 | 81 | public Builder withMessage(final String msg) { 82 | this.message = msg; 83 | return this; 84 | } 85 | 86 | public Builder withPosition(final int pos) { 87 | this.position = pos; 88 | return this; 89 | } 90 | 91 | public Builder withParseIssue(final ParseIssue issue) { 92 | this.parseIssue = issue; 93 | return this; 94 | } 95 | 96 | public Builder withCause(final Throwable throwableCause) { 97 | this.cause = throwableCause; 98 | return this; 99 | } 100 | 101 | public GalimatiasParseException build() { 102 | return new GalimatiasParseException(message, parseIssue, position, cause); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/test/java/io/mola/galimatias/NameValueTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias; 23 | 24 | import io.mola.galimatias.theories.AnyUrlyString; 25 | import io.mola.galimatias.theories.FooOrNullString; 26 | import org.junit.experimental.theories.Theories; 27 | import org.junit.experimental.theories.Theory; 28 | import org.junit.runner.RunWith; 29 | import org.slf4j.Logger; 30 | import org.slf4j.LoggerFactory; 31 | 32 | import static org.fest.assertions.Assertions.*; 33 | import static org.junit.Assume.*; 34 | import static org.junit.Assert.*; 35 | 36 | @RunWith(Theories.class) 37 | public class NameValueTest { 38 | 39 | private static final Logger log = LoggerFactory.getLogger(NameValueTest.class); 40 | 41 | @Theory 42 | public void nullsThrow(@FooOrNullString final String name, @FooOrNullString final String value) { 43 | assumeTrue(name == null || value == null); 44 | try { 45 | new NameValue(name, value); 46 | fail("null was not thrown"); 47 | } catch (NullPointerException ex) { } 48 | } 49 | 50 | @Theory 51 | public void getters(@AnyUrlyString final String name, @AnyUrlyString final String value) { 52 | final NameValue nameValue = new NameValue(name, value); 53 | assertThat(nameValue.name()).isEqualTo(name); 54 | assertThat(nameValue.value()).isEqualTo(value); 55 | } 56 | 57 | @Theory 58 | public void equalsForUnequals(@AnyUrlyString final String name1, @AnyUrlyString final String value1, 59 | @AnyUrlyString final String name2, @AnyUrlyString final String value2) { 60 | assumeTrue(!name1.equals(name2) || !value1.equals(value2)); 61 | final NameValue nameValue1 = new NameValue(name1, value1); 62 | final NameValue nameValue2 = new NameValue(name2, value2); 63 | assertThat(nameValue1).isNotEqualTo(nameValue2); 64 | if (nameValue1.hashCode() == nameValue2.hashCode()) { 65 | log.warn("hashCode collision for {} and {}", nameValue1, nameValue2); 66 | } 67 | assertThat(nameValue1.toString()).isNotEqualTo(nameValue2.toString()); 68 | } 69 | 70 | @Theory 71 | public void equalsForEquals(@AnyUrlyString final String name, @AnyUrlyString final String value) { 72 | final NameValue nameValue1 = new NameValue(name, value); 73 | final NameValue nameValue2 = new NameValue(name, value); 74 | assertThat(nameValue1).isEqualTo(nameValue2); 75 | assertThat(nameValue1.hashCode()).isEqualTo(nameValue2.hashCode()); 76 | assertThat(nameValue1.toString()).isEqualTo(nameValue2.toString()); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/io/mola/galimatias/GalimatiasParseExceptionTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias; 23 | 24 | import org.junit.Test; 25 | 26 | import static org.junit.Assert.*; 27 | 28 | public class GalimatiasParseExceptionTest { 29 | 30 | @Test 31 | public void defaultsParseIssueToUnspecified() { 32 | GalimatiasParseException exception = new GalimatiasParseException("message"); 33 | assertEquals(ParseIssue.UNSPECIFIED, exception.getParseIssue()); 34 | } 35 | 36 | @Test 37 | public void defaultsParseIssueToUnspecifiedWhenPositionIsProvided() { 38 | GalimatiasParseException exception = new GalimatiasParseException("message", 1); 39 | assertEquals(ParseIssue.UNSPECIFIED, exception.getParseIssue()); 40 | } 41 | 42 | 43 | @Test 44 | public void defaultsPositionToNegativeOne() { 45 | GalimatiasParseException exception = new GalimatiasParseException("message"); 46 | assertEquals(-1, exception.getPosition()); 47 | } 48 | 49 | @Test 50 | public void setsParseIssueToUnspecifiedWhenNullIsProvided() { 51 | GalimatiasParseException exception = new GalimatiasParseException("message", null, 1, new RuntimeException()); 52 | assertEquals(ParseIssue.UNSPECIFIED, exception.getParseIssue()); 53 | } 54 | 55 | @Test 56 | public void setsAllFieldsCorrectlyWhenFullConstructorIsUsed() { 57 | final String message = "message"; 58 | final ParseIssue parseIssue = ParseIssue.INVALID_PERCENT_ENCODING; 59 | final int position = 1; 60 | final Throwable cause = new RuntimeException(); 61 | 62 | GalimatiasParseException exception = new GalimatiasParseException(message, parseIssue, position, cause); 63 | 64 | assertEquals(message, exception.getMessage()); 65 | assertEquals(parseIssue, exception.getParseIssue()); 66 | assertEquals(position, exception.getPosition()); 67 | assertEquals(cause, exception.getCause()); 68 | } 69 | 70 | @Test 71 | public void setsAllFieldsCorrectlyWhenBuilderIsUsed() { 72 | final String message = "message"; 73 | final ParseIssue parseIssue = ParseIssue.INVALID_PERCENT_ENCODING; 74 | final int position = 1; 75 | final Throwable cause = new RuntimeException(); 76 | 77 | GalimatiasParseException exception = GalimatiasParseException.builder() 78 | .withMessage(message) 79 | .withParseIssue(parseIssue) 80 | .withPosition(position) 81 | .withCause(cause) 82 | .build(); 83 | 84 | assertEquals(message, exception.getMessage()); 85 | assertEquals(parseIssue, exception.getParseIssue()); 86 | assertEquals(position, exception.getPosition()); 87 | assertEquals(cause, exception.getCause()); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/test/java/io/mola/galimatias/ParseIssueTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias; 23 | 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | import org.junit.runners.Parameterized; 27 | import org.junit.runners.Parameterized.Parameters; 28 | 29 | import java.util.Arrays; 30 | import java.util.Collection; 31 | 32 | import static org.junit.Assert.assertEquals; 33 | import static org.junit.Assert.assertNotNull; 34 | 35 | @RunWith(Parameterized.class) 36 | public class ParseIssueTest { 37 | 38 | @Parameters(name = "{0}: {1}, fatal: {2}") 39 | public static Collection data() { 40 | return Arrays.asList(new Object[][] { 41 | { "www.example.com", ParseIssue.MISSING_SCHEME, true }, 42 | { "http://www.example.com/%", ParseIssue.INVALID_PERCENT_ENCODING, false }, 43 | { "http://www.example.com\\path", ParseIssue.BACKSLASH_AS_DELIMITER, false }, 44 | { "http://us`er:pass@www.example.com/path", ParseIssue.ILLEGAL_CHARACTER, false }, 45 | { "http://www.exam\tple.com/path", ParseIssue.ILLEGAL_WHITESPACE, false }, 46 | { "http://user:pass@/path", ParseIssue.INVALID_HOST, true } 47 | }); 48 | } 49 | 50 | private ParseIssue parseIssue; 51 | private boolean errorIsFatal; 52 | private URLParser parser; 53 | private GalimatiasParseException errorException; 54 | private GalimatiasParseException fatalErrorException; 55 | 56 | public ParseIssueTest(String url, ParseIssue parseIssue, boolean errorIsFatal) { 57 | this.parseIssue = parseIssue; 58 | this.errorIsFatal = errorIsFatal; 59 | 60 | this.parser = new URLParser(url); 61 | this.parser.settings(URLParsingSettings.create().withErrorHandler(new ErrorHandler() { 62 | @Override 63 | public void error(GalimatiasParseException error) throws GalimatiasParseException { 64 | errorException = error; 65 | throw error; 66 | } 67 | 68 | @Override 69 | public void fatalError(GalimatiasParseException error) { 70 | fatalErrorException = error; 71 | } 72 | })); 73 | } 74 | 75 | @Test 76 | public void handlesGalimiatiasParseExceptionWithCorrectParseIssue() throws GalimatiasParseException { 77 | try { 78 | this.parser.parse(); 79 | } catch (GalimatiasParseException ignored) {} 80 | 81 | if (errorIsFatal) { 82 | assertNotNull("Fatal error expected", fatalErrorException); 83 | assertEquals(parseIssue, fatalErrorException.getParseIssue()); 84 | } else { 85 | assertNotNull("Non-fatal error expected", errorException); 86 | assertEquals(parseIssue, errorException.getParseIssue()); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/io/mola/galimatias/canonicalize/RFC3986Canonicalizer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias.canonicalize; 23 | 24 | import io.mola.galimatias.GalimatiasParseException; 25 | import io.mola.galimatias.URL; 26 | import static io.mola.galimatias.URLUtils.*; 27 | 28 | public class RFC3986Canonicalizer extends BaseURLCanonicalizer { 29 | 30 | @Override 31 | public URL canonicalize(URL url) throws GalimatiasParseException { 32 | // User 33 | if (url.username() != null && !url.username().isEmpty()) { 34 | url = url.withUsername(canonicalize(url.username(), USERINFO_PREDICATE)); 35 | } 36 | 37 | // Pass 38 | if (url.password() != null && !url.password().isEmpty()) { 39 | url = url.withPassword(canonicalize(url.password(), USERINFO_PREDICATE)); 40 | } 41 | 42 | // Path 43 | if (url.path() != null) { 44 | url = url.withPath(canonicalize(url.path(), PATH_PREDICATE)); 45 | } 46 | 47 | // Query 48 | if (url.query() != null) { 49 | url = url.withQuery(canonicalize(url.query(), QUERY_OR_FRAGMENT_PREDICATE)); 50 | } 51 | 52 | // Fragment 53 | if (url.fragment() != null) { 54 | url = url.withFragment(canonicalize(url.fragment(), QUERY_OR_FRAGMENT_PREDICATE)); 55 | } 56 | 57 | return url; 58 | } 59 | 60 | private static boolean isUnreserved(final int c) { 61 | return isASCIIAlphanumeric(c) || c == '-' || c == '.' || c == '_' || c == '~'; 62 | } 63 | 64 | private static boolean isSubdelim(final int c) { 65 | return c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' || c == ')' || c == '*' || c == '+' || c == ',' || c == ';' || c == '='; 66 | } 67 | 68 | private static boolean isPChar(final int c) { 69 | //XXX: "pct-encoded" is pchar, but we check for it before calling this. 70 | return isUnreserved(c) || isSubdelim(c) || c == ':' || c == '@'; 71 | } 72 | 73 | private static boolean isUserInfo(final int c) { 74 | //XXX: ':' excluded here since we work directly with user/pass 75 | return isUnreserved(c) || isSubdelim(c); 76 | } 77 | 78 | private static final CharacterPredicate USERINFO_PREDICATE = new CharacterPredicate() { 79 | @Override 80 | public boolean test(int c) { 81 | return isUserInfo(c); 82 | } 83 | }; 84 | 85 | private static final CharacterPredicate PATH_PREDICATE = new CharacterPredicate() { 86 | @Override 87 | public boolean test(int c) { 88 | return isPChar(c) || c == '/'; 89 | } 90 | }; 91 | 92 | private static final CharacterPredicate QUERY_OR_FRAGMENT_PREDICATE = new CharacterPredicate() { 93 | @Override 94 | public boolean test(int c) { 95 | return isPChar(c) || c == '/' || c == '?'; 96 | } 97 | }; 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/io/mola/galimatias/canonicalize/RFC2396Canonicalizer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias.canonicalize; 23 | 24 | import io.mola.galimatias.GalimatiasParseException; 25 | import io.mola.galimatias.URL; 26 | 27 | import static io.mola.galimatias.URLUtils.*; 28 | 29 | public class RFC2396Canonicalizer extends BaseURLCanonicalizer { 30 | 31 | @Override 32 | public URL canonicalize(URL url) throws GalimatiasParseException { 33 | // User 34 | if (url.username() != null && !url.username().isEmpty()) { 35 | url = url.withUsername(canonicalize(url.username(), USERINFO_PREDICATE)); 36 | } 37 | 38 | // Pass 39 | if (url.password() != null && !url.password().isEmpty()) { 40 | url = url.withPassword(canonicalize(url.password(), USERINFO_PREDICATE)); 41 | } 42 | 43 | // Path 44 | if (url.path() != null) { 45 | url = url.withPath(canonicalize(url.path(), PATH_PREDICATE)); 46 | } 47 | 48 | // Query 49 | if (url.query() != null) { 50 | url = url.withQuery(canonicalize(url.query(), URIC_PREDICATE)); 51 | } 52 | 53 | // Fragment 54 | if (url.fragment() != null) { 55 | url = url.withFragment(canonicalize(url.fragment(), URIC_PREDICATE)); 56 | } 57 | 58 | return url; 59 | } 60 | 61 | private static boolean isMark(final int c) { 62 | return c == '-' || c == '_' || c == '.' || c == '!' || c == '*' || c == '\'' || c == '(' || c == ')'; 63 | } 64 | 65 | private static boolean isUnreserved(final int c) { 66 | return isASCIIAlphanumeric(c) || isMark(c); 67 | } 68 | 69 | private static boolean isReserved(final int c) { 70 | return c == ';' || c == '/' || c == '?' || c == ':' || c == '@' || c == '&' || c == '=' || c == '+' || c == '$' || c == ','; 71 | } 72 | 73 | private static boolean isPChar(final int c) { 74 | //XXX: "pct-encoded" is pchar, but we check for it before calling this. 75 | return isUnreserved(c) || c == ':' || c == '@' || c == '&' || c == '=' || c == '+' || c == '$' || c == ','; 76 | } 77 | 78 | private static boolean isUric(final int c) { 79 | return isReserved(c) || isUnreserved(c); 80 | } 81 | 82 | private static boolean isUserInfo(final int c) { 83 | //XXX: ':' is excluded here, since we work with user/pass, not userInfo 84 | return isUnreserved(c) || c == ';' || c == ':' || c == '&' || c == '=' || c == '+' || c == '$' || c == ','; 85 | } 86 | 87 | private static final CharacterPredicate URIC_PREDICATE = new CharacterPredicate() { 88 | @Override 89 | public boolean test(int c) { 90 | return isUric(c); 91 | } 92 | }; 93 | 94 | private static final CharacterPredicate PATH_PREDICATE = new CharacterPredicate() { 95 | @Override 96 | public boolean test(int c) { 97 | return isPChar(c) || c == '/'; 98 | } 99 | }; 100 | 101 | private static final CharacterPredicate USERINFO_PREDICATE = new CharacterPredicate() { 102 | @Override 103 | public boolean test(int c) { 104 | return isUserInfo(c); 105 | } 106 | }; 107 | 108 | } 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | galimatias 2 | ========== 3 | 4 | [![Build Status](https://travis-ci.org/smola/galimatias.png?branch=master)](https://travis-ci.org/smola/galimatias) 5 | [![Coverage Status](https://coveralls.io/repos/smola/galimatias/badge.png?branch=master)](https://coveralls.io/r/smola/galimatias?branch=master) 6 | 7 | galimatias is a URL parsing and normalization library written in Java. 8 | 9 | ### Design goals 10 | 11 | - Parse URLs as browsers do, optionally enforcing compliance with old standards (i.e. RFC 3986, RFC 2396). 12 | - Stay as close as possible to WHATWG's [URL Standard](http://url.spec.whatwg.org/). 13 | - Convenient fluent API with immutable URL objects. 14 | - Interoperable with java.net.URL and java.net.URI. 15 | - Minimal dependencies. 16 | 17 | ### Gotchas 18 | 19 | galimatias is not a generic URI parser. It can parse any URI, but only schemes defined in the URL Standard (i.e. http, https, ftp, ws, wss, gopher, file) will be parsed as hierarchical URIs. For example, in `git://github.com/smola/galimatias.git` you'll be able to extract scheme (i.e. `git`) and scheme data (i.e. `//github.com/smola/galimatias.git`), but not host (i.e. `github.com`). **This is intended.** We cannot guarantee that applying a set of generic rules won't break certain kind of URIs, so we do not try with them. **I will consider adding further support for other schemes if enough people provides solid use cases and testing. [You can check this issue](https://github.com/smola/galimatias/issues/8) if you are interested.** 20 | 21 | But, why? 22 | --------- 23 | 24 | galimatias started out of frustration with java.net.URL and java.net.URI. Both of them are good for basic use cases, but severely broken for others: 25 | 26 | - **[java.net.URL.equals() is broken.](http://stackoverflow.com/a/3771123/205607)** 27 | 28 | - **java.net.URI can pase only RFC 2396 URI syntax.** `java.net.URI` will only parse a URI if it's strictly compliant with RFC 2396. Most URLs found in the wild do not comply with any syntax standard, and RFC 2396 is outdated anyway. 29 | 30 | - **java.net.URI is not protocol-aware.** `http://example.com`, `http://example.com/` and `http://example.com:80` are different entities. 31 | 32 | - **Manipulation is a pain.** I haven't seen any URL manipulation code using `java.net.URL` or `java.net.URI` that is simple and concise. 33 | 34 | - **Not IDN ready.** Java has IDN support with `java.net.IDN`, but this does not apply to `java.net.URL` or `java.net.URI`. 35 | 36 | Setup with Maven 37 | ---------------- 38 | 39 | galimatias is available at Maven Central. Just add to your pom.xml `` section: 40 | 41 | ```xml 42 | 43 | io.mola.galimatias 44 | galimatias 45 | 0.2.0 46 | 47 | ``` 48 | 49 | Development snapshots are also available at Sonatype OSS Snapshots repository. 50 | 51 | Getting started 52 | --------------- 53 | 54 | ### Parse a URL 55 | 56 | ```java 57 | // Parse 58 | String urlString = //... 59 | URL url; 60 | try { 61 | url = URL.parse(urlString); 62 | } catch (GalimatiasParseException ex) { 63 | // Do something with non-recoverable parsing error 64 | } 65 | ``` 66 | 67 | ### Convert to java.net.URL 68 | 69 | ```java 70 | URL url = //... 71 | java.net.URL javaURL; 72 | try { 73 | javaURL = url.toJavaURL(); 74 | } catch (MalformedURLException ex) { 75 | // This can happen if scheme is not http, https, ftp, file or jar. 76 | } 77 | ``` 78 | 79 | ### Convert to java.net.URI 80 | 81 | ```java 82 | URL url = //... 83 | java.net.URI javaURI; 84 | try { 85 | javaURI = url.toJavaURI(); 86 | } catch (URISyntaxException ex) { 87 | // This will happen in rare cases such as "foo://" 88 | } 89 | ``` 90 | 91 | ### Parse a URL with strict error handling 92 | 93 | You can use a strict error handler that will throw an exception 94 | on any invalid URL, even if it's a recovarable error. 95 | 96 | ```java 97 | URLParsingSettings settings = URLParsingSettings.create() 98 | .withErrorHandler(StrictErrorHandler.getInstance()); 99 | URL url = URL.parse(settings, urlString); 100 | ``` 101 | 102 | Documentation 103 | ------------- 104 | 105 | Check out the [Javadoc](http://galimatias.mola.io/apidocs/0.2.0/). 106 | 107 | Contribute 108 | ---------- 109 | 110 | Did you find a bug? [Report it on GitHub](https://github.com/smola/galimatias/issues). 111 | 112 | Did you write a patch? Send a pull request. 113 | 114 | Something else? Email me at santi@mola.io. 115 | 116 | License 117 | ------- 118 | 119 | Copyright (c) 2013-2014 Santiago M. Mola 120 | 121 | galimatias is released under the terms of the MIT License. 122 | -------------------------------------------------------------------------------- /src/test/java/io/mola/galimatias/FormURLEncodedParserTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias; 23 | 24 | import static org.fest.assertions.Assertions.*; 25 | 26 | import org.junit.Test; 27 | import org.junit.runner.RunWith; 28 | import org.junit.runners.JUnit4; 29 | 30 | import java.util.Arrays; 31 | import java.util.List; 32 | 33 | @RunWith(JUnit4.class) 34 | public class FormURLEncodedParserTest { 35 | 36 | //TODO: add encode-parse-encode tests 37 | 38 | @Test 39 | public void parse() { 40 | List result; 41 | 42 | result = FormURLEncodedParser.parse("foo=123"); 43 | assertThat(result).isEqualTo(Arrays.asList(new NameValue("foo", "123"))); 44 | 45 | result = FormURLEncodedParser.parse("foo=123&bar=456"); 46 | assertThat(result).isEqualTo(Arrays.asList(new NameValue("foo", "123"), new NameValue("bar", "456"))); 47 | 48 | result = FormURLEncodedParser.parse("=123"); 49 | assertThat(result).isEqualTo(Arrays.asList(new NameValue("", "123"))); 50 | 51 | result = FormURLEncodedParser.parse("foo"); 52 | assertThat(result).isEqualTo(Arrays.asList(new NameValue("foo", ""))); 53 | 54 | result = FormURLEncodedParser.parse("foo="); 55 | assertThat(result).isEqualTo(Arrays.asList(new NameValue("foo", ""))); 56 | 57 | result = FormURLEncodedParser.parse("=123"); 58 | assertThat(result).isEqualTo(Arrays.asList(new NameValue("", "123"))); 59 | 60 | result = FormURLEncodedParser.parse("foo"); 61 | assertThat(result).isEqualTo(Arrays.asList(new NameValue("foo", ""))); 62 | 63 | result = FormURLEncodedParser.parse("foo="); 64 | assertThat(result).isEqualTo(Arrays.asList(new NameValue("foo", ""))); 65 | 66 | result = FormURLEncodedParser.parse("a+=b+"); 67 | assertThat(result).isEqualTo(Arrays.asList(new NameValue("a ", "b "))); 68 | 69 | result = FormURLEncodedParser.parse("a%20=b%20"); 70 | assertThat(result).isEqualTo(Arrays.asList(new NameValue("a%20", "b%20"))); 71 | 72 | result = FormURLEncodedParser.parse("©=ß"); 73 | assertThat(result).isEqualTo(Arrays.asList(new NameValue("©", "ß"))); 74 | } 75 | 76 | @Test 77 | public void encode() { 78 | String result; 79 | 80 | result = FormURLEncodedParser.encode(Arrays.asList(new NameValue("foo", "123"))); 81 | assertThat(result).isEqualTo("foo=123"); 82 | 83 | result = FormURLEncodedParser.encode(Arrays.asList(new NameValue("foo", "123"), new NameValue("bar", "456"))); 84 | assertThat(result).isEqualTo("foo=123&bar=456"); 85 | 86 | result = FormURLEncodedParser.encode(Arrays.asList(new NameValue("", "123"))); 87 | assertThat(result).isEqualTo("=123"); 88 | 89 | result = FormURLEncodedParser.encode(Arrays.asList(new NameValue("foo", ""))); 90 | assertThat(result).isEqualTo("foo="); 91 | 92 | result = FormURLEncodedParser.encode(Arrays.asList(new NameValue("", "123"))); 93 | assertThat(result).isEqualTo("=123"); 94 | 95 | result = FormURLEncodedParser.encode(Arrays.asList(new NameValue("a ", "b "))); 96 | assertThat(result).isEqualTo("a =b "); 97 | 98 | result = FormURLEncodedParser.encode(Arrays.asList(new NameValue("a%20", "b%20"))); 99 | assertThat(result).isEqualTo("a%2520=b%2520"); 100 | 101 | result = FormURLEncodedParser.encode(Arrays.asList(new NameValue("©", "ß"))); 102 | assertThat(result).isEqualTo("%C2%A9=%C3%9F"); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/test/java/io/mola/galimatias/IPv4AddressTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias; 23 | 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | import org.junit.runners.JUnit4; 27 | import org.slf4j.Logger; 28 | import org.slf4j.LoggerFactory; 29 | 30 | import java.net.Inet4Address; 31 | import java.net.InetAddress; 32 | import java.net.UnknownHostException; 33 | 34 | import static org.fest.assertions.Assertions.assertThat; 35 | 36 | @RunWith(JUnit4.class) 37 | public class IPv4AddressTest { 38 | 39 | private static Logger log = LoggerFactory.getLogger(IPv4AddressTest.class); 40 | 41 | private static final String[] TEST_ADDRESSES = new String[] { 42 | "0.0.0.0", 43 | "255.255.255.255", 44 | "127.0.0.1" 45 | }; 46 | 47 | @Test 48 | public void parseIPv4Address() throws GalimatiasParseException { 49 | for (final String testAddress : TEST_ADDRESSES) { 50 | log.debug("TESTING: {}", testAddress); 51 | assertThat(IPv4Address.parseIPv4Address(testAddress).toString()).isEqualTo(testAddress); 52 | } 53 | } 54 | 55 | @Test 56 | public void equals() throws GalimatiasParseException { 57 | final IPv4Address ip = IPv4Address.parseIPv4Address("127.0.0.1"); 58 | assertThat(ip).isEqualTo(ip); 59 | assertThat(ip).isEqualTo(IPv4Address.parseIPv4Address("127.0.0.1")); 60 | assertThat(ip).isNotEqualTo(IPv4Address.parseIPv4Address("127.0.0.2")); 61 | assertThat(ip).isNotEqualTo("foo"); 62 | assertThat(ip).isNotEqualTo(null); 63 | assertThat(ip.toHumanString()).isEqualTo(ip.toString()); 64 | } 65 | 66 | @Test(expected = NullPointerException.class) 67 | public void parseNullAddress() throws GalimatiasParseException { 68 | IPv4Address.parseIPv4Address(null); 69 | } 70 | 71 | @Test(expected = GalimatiasParseException.class) 72 | public void parseEmptyAddress() throws GalimatiasParseException { 73 | IPv4Address.parseIPv4Address(""); 74 | } 75 | 76 | @Test(expected = GalimatiasParseException.class) 77 | public void parseIllegalCharacter() throws GalimatiasParseException { 78 | IPv4Address.parseIPv4Address("1.1.x.1"); 79 | } 80 | 81 | 82 | @Test(expected = GalimatiasParseException.class) 83 | public void parseTooLongAddress() throws GalimatiasParseException { 84 | IPv4Address.parseIPv4Address("1.1.1.1.2"); 85 | } 86 | 87 | @Test(expected = GalimatiasParseException.class) 88 | public void parseAddressWithFinalDot() throws GalimatiasParseException { 89 | IPv4Address.parseIPv4Address("1.1.1.1."); 90 | } 91 | 92 | @Test(expected = GalimatiasParseException.class) 93 | public void parseWithLeadingZero1() throws GalimatiasParseException { 94 | IPv6Address.parseIPv6Address("192.168.1.1.05"); 95 | } 96 | 97 | @Test(expected = GalimatiasParseException.class) 98 | public void parseWithLeadingZero2() throws GalimatiasParseException { 99 | IPv6Address.parseIPv6Address("192.168.1.1.00"); 100 | } 101 | 102 | @Test(expected = GalimatiasParseException.class) 103 | public void parseTooShortAddress() throws GalimatiasParseException { 104 | IPv4Address.parseIPv4Address("1.1.1"); 105 | } 106 | 107 | @Test(expected = GalimatiasParseException.class) 108 | public void parseHighValueIPv4Mapped() throws GalimatiasParseException { 109 | IPv4Address.parseIPv4Address("192.168.1.256"); 110 | } 111 | 112 | @Test 113 | public void toFromInetAddress() throws UnknownHostException, GalimatiasParseException { 114 | for (final String testAddress : TEST_ADDRESSES) { 115 | log.debug("TESTING: {}", testAddress); 116 | final InetAddress target = InetAddress.getByName(testAddress); 117 | final IPv4Address address = IPv4Address.parseIPv4Address(testAddress); 118 | assertThat(address.toInetAddress()).isEqualTo(target); 119 | assertThat(address.toInetAddress().getHostAddress()).isEqualToIgnoringCase(testAddress); 120 | assertThat(IPv4Address.fromInet4Adress((Inet4Address)target)).isEqualTo(address); 121 | } 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/test/resources/data/urltestdata_host_whatwg.txt: -------------------------------------------------------------------------------- 1 | -- Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/host.html 2 | 3 | -- Basic canonicalization, uppercase should be converted to lowercase 4 | http://ExAmPlE.CoM http://other.com/ s:http p:/ h:example.com 5 | 6 | -- US: Spaces should fail 7 | http://example\sexample.com 8 | 9 | -- WEBKIT: Spaces and some other characters should be escaped. 10 | -- US: This should fail 11 | http://Goo%20\sgoo%7C|.com 12 | 13 | -- WEBKIT: Exciting different types of spaces! 14 | -- US: This should fail 15 | http://GOO\u00a0\u3000goo.com 16 | 17 | -- Other types of space (no-break, zero-width, zero-width-no-break) are 18 | -- name-prepped away to nothing. 19 | http://GOO\u200b\u2060\ufeffgoo.com s:http p:/ h:googoo.com 20 | 21 | -- Ideographic full stop (full-width period for Chinese, etc.) should be 22 | -- treated as a dot. 23 | http://www.foo\u3002bar.com s:http p:/ h:www.foo.bar.com 24 | 25 | -- Invalid unicode characters should fail... 26 | http://\ufdd0zyx.com 27 | 28 | -- ...This is the same as previous but with with escaped. 29 | http://%ef%b7%90zyx.com 30 | 31 | -- Test name prepping, fullwidth input should be converted to ASCII and NOT 32 | -- IDN-ized. This is "Go" in fullwidth UTF-8/UTF-16. 33 | http://\uff27\uff4f.com s:http p:/ h:go.com 34 | 35 | -- WEBKIT: Test that fullwidth escaped values are properly name-prepped, 36 | -- WEBKIT: then converted or rejected. 37 | -- WEBKIT: ...%41 in fullwidth = 'A' (also as escaped UTF-8 input) 38 | -- 39 | -- WHATWG: URL spec forbids this: 40 | -- WHATWG: https://www.w3.org/Bugs/Public/show_bug.cgi?id=24257 41 | http://\uff05\uff14\uff11.com 42 | http://%ef%bc%85%ef%bc%94%ef%bc%91.com 43 | 44 | -- ...%00 in fullwidth should fail (also as escaped UTF-8 input) 45 | -- TODO 46 | -- http://\uff05\uff10\uff10.com 47 | -- http://%ef%bc%85%ef%bc%90%ef%bc%90.com 48 | 49 | -- Basic IDN support, UTF-8 and UTF-16 input should be converted to IDN 50 | http://\u4f60\u597d\u4f60\u597d s:http p:/ h:xn--6qqa088eba 51 | 52 | -- Invalid escaped characters should fail and the percents should be 53 | -- escaped. 54 | -- TODO: https://www.w3.org/Bugs/Public/show_bug.cgi?id=24191 55 | -- http://%zz%66%a.com 56 | 57 | -- If we get an invalid character that has been escaped. 58 | -- TODO 59 | -- http://%25 60 | -- http://hello%00 61 | 62 | -- Escaped numbers should be treated like IP addresses if they are. 63 | -- TODO: Right now galimatias treats IPv4 addresses as domains in this case 64 | -- Check this case later on 65 | -- http://%30%78%63%30%2e%30%32%35%30.01 s:http p:/ h:127.0.0.1 66 | -- http://%30%78%63%30%2e%30%32%35%30.01%2e 67 | 68 | -- Invalid escaping should trigger the regular host error handling. 69 | -- TODO 70 | -- http://%3g%78%63%30%2e%30%32%35%30%2E.01 71 | 72 | -- Something that isn't exactly an IP should get treated as a host and 73 | -- spaces escaped. 74 | http://192.168.0.1\shello 75 | 76 | -- Fullwidth and escaped UTF-8 fullwidth should still be treated as IP. 77 | -- These are "0Xc0.0250.01" in fullwidth. 78 | -- TODO 79 | -- http://\uff10\uff38\uff43\uff10\uff0e\uff10\uff12\uff15\uff10\uff0e\uff10\uff11 s:http p:/ h:192.168.0.1 80 | 81 | -- Broken IP addresses. 82 | -- TODO 83 | -- http://192.168.0.257 84 | http://[google.com] 85 | 86 | -- Cyrillic letter followed buy ( should return punicode for ( escaped before punicode string was created. I.e. 87 | -- if ( is escaped after punicode is created we would get xn--%28-8tb (incorrect). 88 | -- TODO 89 | -- http://\u0442( 90 | 91 | -- IDN misc 92 | http://ジェーピーニック.jp s:http h:xn--hckqz9bzb1cyrb.jp p:/ 93 | http://ß.com/ s:http h:ss.com p:/ 94 | http://☃.com/ s:http h:xn--n3h.com p:/ 95 | http://☃☃☃.com/ s:http h:xn--n3haa.com p:/ 96 | http://\uD83D\uDC35.com/ s:http h:xn--9o8h.com p:/ 97 | 98 | -- Percent-encoded ß 99 | http://%C3%9F.com/ s:http h:ss.com p:/ 100 | 101 | -- GALIMATIAS: DNS length checks 102 | http://000000000000000000000000000000000000000000000000000000000000000 s:http p:/ h:000000000000000000000000000000000000000000000000000000000000000 103 | http://0000000000000000000000000000000000000000000000000000000000000000 104 | http://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.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.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 s:http p:/ h: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.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.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 105 | http://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.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.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 106 | http://000000000000000000000000000000000000000000000000000000000000000.000000000000000000000000000000000000000000000000000000000000000.000000000000000000000000000000000000000000000000000000000000000.0000000000000000000000000000000000000000000000000000000000000 s:http p:/ h:000000000000000000000000000000000000000000000000000000000000000.000000000000000000000000000000000000000000000000000000000000000.000000000000000000000000000000000000000000000000000000000000000.0000000000000000000000000000000000000000000000000000000000000 107 | http://000000000000000000000000000000000000000000000000000000000000000.000000000000000000000000000000000000000000000000000000000000000.000000000000000000000000000000000000000000000000000000000000000.000000000000000000000000000000000000000000000000000000000000000 108 | http://0..0 109 | -------------------------------------------------------------------------------- /src/main/java/io/mola/galimatias/IPv4Address.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias; 23 | 24 | import java.net.Inet4Address; 25 | import java.net.UnknownHostException; 26 | 27 | import static io.mola.galimatias.URLUtils.isASCIIDigit; 28 | 29 | public class IPv4Address extends Host { 30 | 31 | private static final long serialVersionUID = 1L; 32 | 33 | private final int address; 34 | 35 | private IPv4Address(final byte[] addrBytes) { 36 | int addr = 0; 37 | addr = addrBytes[3] & 0xFF; 38 | addr |= ((addrBytes[2] << 8) & 0xFF00); 39 | addr |= ((addrBytes[1] << 16) & 0xFF0000); 40 | addr |= ((addrBytes[0] << 24) & 0xFF000000); 41 | this.address = addr; 42 | } 43 | 44 | public static IPv4Address parseIPv4Address(final String input) throws GalimatiasParseException{ 45 | if (input == null) { 46 | throw new NullPointerException("null input"); 47 | } 48 | if (input.isEmpty()) { 49 | throw new GalimatiasParseException("empty input"); 50 | } 51 | if (input.charAt(input.length() - 1) == '.') { //XXX: This case is not covered by the IPv6-mapped IPv4 case in the spec 52 | throw new GalimatiasParseException("IPv4 address has trailing dot"); 53 | } 54 | byte[] addr = new byte[4]; 55 | int dotsSeen = 0; 56 | int addrIdx = 0; 57 | int idx = 0; 58 | boolean isEOF = false; 59 | while (!isEOF) { 60 | char c = input.charAt(idx); 61 | Integer value = null; 62 | if (!isASCIIDigit(c)) { 63 | throw new GalimatiasParseException("Non-digit character in IPv4 address"); 64 | } 65 | while (isASCIIDigit(c)) { 66 | final int number = c - 0x30; // 10.3.1 67 | if (value == null) { // 10.3.2 68 | value = number; 69 | } else if (value == 0) { 70 | throw new GalimatiasParseException("IPv4 address contains a leading zero"); 71 | } else { 72 | value = value * 10 + number; 73 | } 74 | idx++; // 10.3.3 75 | isEOF = idx >= input.length(); 76 | c = (isEOF)? 0x00 : input.charAt(idx); 77 | if (value > 255) { // 10.3.4 78 | throw new GalimatiasParseException("Invalid value for IPv4 address"); 79 | } 80 | } 81 | if (dotsSeen < 3 && c != '.') { 82 | throw new GalimatiasParseException("Illegal character in IPv4 address", idx); 83 | } 84 | idx++; 85 | isEOF = idx >= input.length(); 86 | c = (isEOF)? 0x00 : input.charAt(idx); 87 | if (dotsSeen == 3 && idx < input.length()) { 88 | throw new GalimatiasParseException("IPv4 address is too long", idx); 89 | } 90 | addr[addrIdx] = (byte) (int) value; 91 | addrIdx++; 92 | dotsSeen++; 93 | } 94 | if (dotsSeen != 4) { 95 | throw new GalimatiasParseException("Malformed IPv4 address"); 96 | } 97 | return new IPv4Address(addr); 98 | } 99 | 100 | /** 101 | * Convert to @{java.net.InetAddress}. 102 | * 103 | * @return The IPv4 address as a @{java.net.InetAddress}. 104 | */ 105 | public Inet4Address toInetAddress() throws UnknownHostException { 106 | return (Inet4Address) Inet4Address.getByAddress(getBytes()); 107 | } 108 | 109 | /** 110 | * Convert from @{java.net.Inet4Address}. 111 | * 112 | * @param inet4Address The IPv4 address as a @{java.net.Inet4Address}. 113 | * @return The IPv4 address as a @{IPv4Address}. 114 | */ 115 | public static IPv4Address fromInet4Adress(final Inet4Address inet4Address) { 116 | return new IPv4Address(inet4Address.getAddress()); 117 | } 118 | 119 | private byte[] getBytes() { 120 | return new byte[] { 121 | (byte) (address >> 24 & 0x00FF), 122 | (byte) (address >> 16 & 0x00FF), 123 | (byte) (address >> 8 & 0x00FF), 124 | (byte) (address & 0x00FF) 125 | }; 126 | } 127 | 128 | @Override 129 | public String toString() { 130 | byte[] bytes = getBytes(); 131 | return String.format("%d.%d.%d.%d", bytes[0] & 0x00FF, bytes[1] & 0x00FF, bytes[2] & 0x00FF, bytes[3] & 0x00FF); 132 | } 133 | 134 | @Override 135 | public String toHumanString() { 136 | return toString(); 137 | } 138 | 139 | @Override 140 | public boolean equals(Object obj) { 141 | if (this == obj) { 142 | return true; 143 | } 144 | if (obj == null) { 145 | return false; 146 | } 147 | if (!(obj instanceof IPv4Address)) { 148 | return false; 149 | } 150 | return this.address == ((IPv4Address) obj).address; 151 | } 152 | 153 | @Override 154 | public int hashCode() { 155 | return address; 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/io/mola/galimatias/URLSearchParameters.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias; 23 | 24 | import java.util.ArrayList; 25 | import java.util.Collections; 26 | import java.util.Iterator; 27 | import java.util.List; 28 | 29 | public final class URLSearchParameters implements Iterable { 30 | 31 | private static final List EMPTY_NAME_VALUES = Collections.unmodifiableList(new ArrayList(0)); 32 | 33 | private final List nameValues; 34 | 35 | URLSearchParameters(final String query) { 36 | if (query != null && !query.isEmpty()) { 37 | nameValues = Collections.unmodifiableList(FormURLEncodedParser.parse(query)); 38 | } else { 39 | nameValues = EMPTY_NAME_VALUES; 40 | } 41 | } 42 | 43 | URLSearchParameters(final List nameValues) { 44 | if (nameValues == null) { 45 | throw new NullPointerException("nameValues"); 46 | } 47 | this.nameValues = Collections.unmodifiableList(nameValues); 48 | } 49 | 50 | public URLSearchParameters withAppended(final String name, final String value) { 51 | if (name == null) { 52 | throw new NullPointerException("name"); 53 | } 54 | if (value == null) { 55 | throw new NullPointerException("value"); 56 | } 57 | return withAppended(new NameValue(name, value)); 58 | } 59 | 60 | public URLSearchParameters withAppended(final NameValue nameValue) { 61 | if (nameValue == null) { 62 | throw new NullPointerException("nameValue"); 63 | } 64 | final List newNameValuesList = new ArrayList(this.nameValues.size() + 1); 65 | for (final NameValue nv : nameValues) { 66 | newNameValuesList.add(nv); 67 | } 68 | newNameValuesList.add(nameValue); 69 | return new URLSearchParameters(newNameValuesList); 70 | } 71 | 72 | public URLSearchParameters with(final String name, final String value) { 73 | if (name == null) { 74 | throw new NullPointerException("name"); 75 | } 76 | if (value == null) { 77 | throw new NullPointerException("value"); 78 | } 79 | return with(new NameValue(name, value)); 80 | } 81 | 82 | public URLSearchParameters with(final NameValue nameValue) { 83 | if (nameValue == null) { 84 | throw new NullPointerException("nameValue"); 85 | } 86 | final List newNameValuesList = new ArrayList(this.nameValues.size() + 1); 87 | final String name = nameValue.name(); 88 | for (final NameValue nv : nameValues) { 89 | if (!nv.name().equals(name)) { 90 | newNameValuesList.add(nv); 91 | } 92 | } 93 | newNameValuesList.add(nameValue); 94 | return new URLSearchParameters(newNameValuesList); 95 | } 96 | 97 | public URLSearchParameters without(final String name) { 98 | if (name == null) { 99 | throw new NullPointerException("name"); 100 | } 101 | final List newNameValuesList = new ArrayList(this.nameValues.size()); 102 | for (final NameValue nv : nameValues) { 103 | if (!nv.name().equals(name)) { 104 | newNameValuesList.add(nv); 105 | } 106 | } 107 | return new URLSearchParameters(newNameValuesList); 108 | } 109 | 110 | public String get(final String name) { 111 | if (name == null) { 112 | throw new NullPointerException("name"); 113 | } 114 | for (final NameValue nv : nameValues) { 115 | if (name.equals(nv.name())) { 116 | return nv.value(); 117 | } 118 | } 119 | return null; 120 | } 121 | 122 | public List getAll(final String name) { 123 | if (name == null) { 124 | throw new NullPointerException("name"); 125 | } 126 | final List result = new ArrayList(); 127 | for (final NameValue nv : nameValues) { 128 | if (name.equals(nv.name())) { 129 | result.add(nv.value()); 130 | } 131 | } 132 | return result; 133 | } 134 | 135 | public boolean has(final String name) { 136 | if (name == null) { 137 | throw new NullPointerException("name"); 138 | } 139 | for (final NameValue nv : nameValues) { 140 | if (name.equals(nv.name())) { 141 | return true; 142 | } 143 | } 144 | return false; 145 | } 146 | 147 | @Override 148 | public Iterator iterator() { 149 | return nameValues.iterator(); 150 | } 151 | 152 | @Override 153 | public boolean equals(Object o) { 154 | if (this == o) return true; 155 | if (o == null || getClass() != o.getClass()) return false; 156 | 157 | URLSearchParameters that = (URLSearchParameters) o; 158 | 159 | if (!nameValues.equals(that.nameValues)) return false; 160 | 161 | return true; 162 | } 163 | 164 | @Override 165 | public int hashCode() { 166 | return nameValues.hashCode(); 167 | } 168 | } -------------------------------------------------------------------------------- /src/main/java/io/mola/galimatias/cli/CLI.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias.cli; 23 | 24 | import io.mola.galimatias.ErrorHandler; 25 | import io.mola.galimatias.GalimatiasParseException; 26 | import io.mola.galimatias.URL; 27 | import io.mola.galimatias.URLParsingSettings; 28 | import io.mola.galimatias.canonicalize.RFC2396Canonicalizer; 29 | import io.mola.galimatias.canonicalize.RFC3986Canonicalizer; 30 | 31 | /** 32 | * A command line interface to Galimatias URL parser. 33 | */ 34 | public class CLI { 35 | 36 | private static void printError(GalimatiasParseException error) { 37 | System.out.println("\t\tError: " + error.getMessage()); 38 | if (error.getPosition() != -1) { 39 | System.out.println("\t\tPosition: " + error.getPosition()); 40 | } 41 | } 42 | 43 | private static void printResult(URL url) { 44 | System.out.println("\tResult:"); 45 | System.out.println("\t\tURL: " + url.toString()); 46 | System.out.println("\t\tURL type: " + ((url.isHierarchical())? "hierarchical" : "opaque")); 47 | System.out.println("\t\tScheme: " + url.scheme()); 48 | if (url.schemeData() != null) { 49 | System.out.println("\t\tScheme data: " + url.schemeData()); 50 | } 51 | if (url.username() != null) { 52 | System.out.println("\t\tUsername: " + url.username()); 53 | } 54 | if (url.password() != null) { 55 | System.out.println("\t\tPassword: " + url.password()); 56 | } 57 | if (url.host() != null) { 58 | System.out.println("\t\tHost: " + url.host()); 59 | } 60 | if (url.port() != -1) { 61 | System.out.println("\t\tPort: " + url.port()); 62 | } 63 | if (url.path() != null) { 64 | System.out.println("\t\tPath: " + url.path()); 65 | } 66 | if (url.query() != null) { 67 | System.out.println("\t\tQuery: " + url.query()); 68 | } 69 | if (url.fragment() != null) { 70 | System.out.println("\t\tFragment: " + url.fragment()); 71 | } 72 | } 73 | 74 | private static class PrintErrorHandler implements ErrorHandler { 75 | 76 | @Override 77 | public void error(GalimatiasParseException error) throws GalimatiasParseException { 78 | System.out.println("\tRecoverable error found;"); 79 | System.out.println("\t\tError: " + error.getMessage()); 80 | if (error.getPosition() != -1) { 81 | System.out.println("\t\tPosition: " + error.getPosition()); 82 | } 83 | } 84 | 85 | @Override 86 | public void fatalError(GalimatiasParseException error) { 87 | 88 | } 89 | } 90 | 91 | private static ErrorHandler errorHandler = new PrintErrorHandler(); 92 | 93 | public static void main(String[] args) { 94 | 95 | URLParsingSettings settings = URLParsingSettings.create().withErrorHandler(errorHandler); 96 | URL url = null; 97 | String whatwgUrlSerialized = ""; 98 | String rfc3986UrlSerialized = ""; 99 | String rfc2396UrlSerialized = ""; 100 | 101 | boolean parseErrors; 102 | 103 | URL base = null; 104 | String input = ""; 105 | if (args.length < 1) { 106 | System.err.println("Need a URL as input"); 107 | System.exit(1); 108 | } else if (args.length == 1) { 109 | input = args[0]; 110 | } else if (args.length == 2) { 111 | try { 112 | base = URL.parse(args[0]); 113 | } catch (GalimatiasParseException ex) { 114 | assert false; // shouldn't get in here unless serious bug 115 | } 116 | input = args[1]; 117 | } else { 118 | System.err.println("Too many args"); 119 | System.exit(1); 120 | } 121 | 122 | if (base == null) { 123 | try { 124 | base = URL.parse("http://example.org/foo/bar"); 125 | } catch (GalimatiasParseException ex) { 126 | assert false; // shouldn't get in here unless serious bug 127 | } 128 | } 129 | 130 | System.out.println("Base: " + base.toString()); 131 | System.out.println("Analyzing URL: " + input); 132 | 133 | try { 134 | System.out.println("Parsing..."); 135 | url = URL.parse(settings, base, input); 136 | whatwgUrlSerialized = url.toString(); 137 | printResult(url); 138 | } catch (GalimatiasParseException ex) { 139 | System.out.println("Parsing with WHATWG rules resulted in fatal error"); 140 | printError(ex); 141 | return; 142 | } 143 | 144 | try { 145 | System.out.println("Canonicalizing with RFC 3986 rules..."); 146 | rfc3986UrlSerialized = new RFC3986Canonicalizer().canonicalize(url).toString(); 147 | if (whatwgUrlSerialized.equals(rfc3986UrlSerialized)) { 148 | System.out.println("\tResult identical to WHATWG rules"); 149 | } else { 150 | printResult(url); 151 | } 152 | } catch (GalimatiasParseException ex) { 153 | System.out.println("Canonicalizing with RFC 3986 rules resulted in fatal error"); 154 | printError(ex); 155 | } 156 | 157 | try { 158 | System.out.println("Canonicalizing with RFC 2396 rules..."); 159 | rfc2396UrlSerialized = new RFC2396Canonicalizer().canonicalize(url).toString(); 160 | if (rfc3986UrlSerialized.equals(rfc2396UrlSerialized)) { 161 | System.out.println("\tResult identical to RFC 3986 rules"); 162 | } else { 163 | printResult(url); 164 | } 165 | } catch (GalimatiasParseException ex) { 166 | System.out.println("Canonicalizing with RFC 2396 rules resulted in fatal error"); 167 | printError(ex); 168 | } 169 | 170 | } 171 | 172 | } 173 | -------------------------------------------------------------------------------- /src/test/java/io/mola/galimatias/TestURLLoader.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias; 23 | 24 | import java.io.BufferedReader; 25 | import java.io.IOException; 26 | import java.io.InputStreamReader; 27 | import java.util.ArrayList; 28 | import java.util.HashMap; 29 | import java.util.List; 30 | import java.util.Map; 31 | 32 | /** 33 | * Loads URL tests in the W3C format. 34 | * 35 | * See https://raw.github.com/w3c/web-platform-tests/master/url/urltestdata.txt 36 | */ 37 | public class TestURLLoader { 38 | 39 | private static Map tokenMap = new HashMap() {{ 40 | put('\\', '\\'); 41 | put('n', '\n'); 42 | put('r', '\r'); 43 | put('s', ' '); 44 | put('t', '\t'); 45 | put('f', '\f'); 46 | }}; 47 | 48 | private static String normalize(final String input) { 49 | // Borrowed from https://github.com/w3c/web-platform-tests/blob/master/url/urltestparser.js 50 | final StringBuilder output = new StringBuilder(input.length()); 51 | for (int i = 0; i < input.length(); i++) { 52 | final char c = input.charAt(i); 53 | if (c == '\\') { 54 | i++; 55 | final char nextC = i >= input.length()? 0x00 : input.charAt(i); 56 | 57 | if (tokenMap.containsKey(nextC)) { 58 | output.append(tokenMap.get(nextC)); 59 | } else if (nextC == 'u') { 60 | char uChar = (char)Integer.parseInt(input.substring(i + 1, i + 5), 16); 61 | output.append(uChar); 62 | i += 4; 63 | } else { 64 | throw new RuntimeException("Token error"); 65 | } 66 | } else { 67 | output.append(c); 68 | } 69 | } 70 | return output.toString(); 71 | } 72 | 73 | public static List loadTestURLs(final String resource) { 74 | try { 75 | final List results = new ArrayList(); 76 | final BufferedReader br; 77 | br = new BufferedReader(new InputStreamReader(TestURLLoader.class.getResourceAsStream(resource))); 78 | String line; 79 | while ((line = br.readLine()) != null) { 80 | if (line.isEmpty() || line.startsWith("--")) { 81 | continue; 82 | } 83 | String[] fields = line.split(" "); 84 | 85 | TestURL testURL = new TestURL(); 86 | results.add(testURL); 87 | testURL.rawURL = normalize(fields[0]); 88 | 89 | if (fields.length > 1 && !fields[1].isEmpty()) { 90 | testURL.rawBaseURL = normalize(fields[1]); 91 | try { 92 | testURL.parsedBaseURL = URL.parse(testURL.rawBaseURL); 93 | } catch (GalimatiasParseException ex) { 94 | throw new RuntimeException("Exception while parsing test line: " + line, ex); 95 | } 96 | } else { 97 | //FIXME: This implies there are no tests with null base 98 | testURL.rawBaseURL = results.get(results.size() - 2).rawBaseURL; 99 | testURL.parsedBaseURL = results.get(results.size() - 2).parsedBaseURL; 100 | } 101 | 102 | if (fields.length < 2) { 103 | continue; 104 | } 105 | 106 | String scheme = ""; 107 | String schemeData = ""; 108 | String username = ""; 109 | String password = null; 110 | Host host = null; 111 | int port = -1; 112 | String path = null; 113 | String query = null; 114 | String fragment = null; 115 | boolean isHierarchical = false; 116 | 117 | for (int i = 2; i < fields.length; i++) { 118 | final String field = normalize(fields[i]); 119 | if (field.length() < 2) { 120 | throw new RuntimeException("Malformed test: " + line); 121 | } 122 | final String value = field.substring(2); 123 | if (field.startsWith("s:")) { 124 | scheme = value; 125 | isHierarchical = URLUtils.isRelativeScheme(scheme); 126 | } else if (field.startsWith("u:")) { 127 | username = value; 128 | } else if (field.startsWith("pass:")) { 129 | password = field.substring(5); 130 | } else if (field.startsWith("h:")) { 131 | try { 132 | host = Host.parseHost(value); 133 | } catch (GalimatiasParseException ex) { 134 | throw new RuntimeException(ex); 135 | } 136 | } else if (field.startsWith("port:")) { 137 | port = Integer.parseInt(field.substring(5)); 138 | } else if (field.startsWith("p:")) { 139 | path = value; 140 | } else if (field.startsWith("q:")) { 141 | query = value; 142 | //FIXME: Workaround for bad test data 143 | //TODO: https://github.com/w3c/web-platform-tests/issues/499 144 | if (query.startsWith("?")) { 145 | query = query.substring(1); 146 | } 147 | } else if (field.startsWith("f:")) { 148 | fragment = value; 149 | //FIXME: Workaround for bad test data 150 | if (fragment.startsWith("#")) { 151 | fragment = fragment.substring(1); 152 | } 153 | } 154 | } 155 | 156 | if (!isHierarchical) { 157 | schemeData = path; 158 | path = null; 159 | } else { 160 | final String defaultPortString = URLUtils.getDefaultPortForScheme(scheme); 161 | if (defaultPortString != null && port == Integer.parseInt(defaultPortString)) { 162 | port = -1; 163 | } 164 | } 165 | 166 | testURL.parsedURL = new URL(scheme, schemeData, username, password, 167 | host, port, path, query, fragment, isHierarchical); 168 | 169 | } 170 | return results; 171 | } catch (IOException ex) { 172 | throw new RuntimeException(ex); 173 | } 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /src/test/java/io/mola/galimatias/BadURLTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias; 23 | 24 | import org.junit.Rule; 25 | import org.junit.Test; 26 | import org.junit.experimental.theories.Theories; 27 | import org.junit.experimental.theories.Theory; 28 | import org.junit.rules.ExpectedException; 29 | import org.junit.runner.RunWith; 30 | 31 | import static org.fest.assertions.Assertions.assertThat; 32 | import static org.junit.Assume.*; 33 | 34 | @RunWith(Theories.class) 35 | public class BadURLTest { 36 | 37 | @Rule 38 | public ExpectedException thrown = ExpectedException.none(); 39 | 40 | @Test 41 | public void parseNullURL() throws GalimatiasParseException { 42 | thrown.expect(NullPointerException.class); 43 | thrown.expectMessage("null input"); 44 | URL.parse(null); 45 | } 46 | 47 | @Test 48 | public void parseEmptyURL() throws GalimatiasParseException { 49 | thrown.expect(GalimatiasParseException.class); 50 | thrown.expectMessage("Missing scheme"); 51 | URL.parse(""); 52 | } 53 | 54 | @Test(expected = GalimatiasParseException.class) 55 | public void parseURLwithoutScheme() throws GalimatiasParseException { 56 | URL.parse("//scheme-relative-stuff"); 57 | } 58 | 59 | @Test(expected = GalimatiasParseException.class) 60 | public void parseOneToken() throws GalimatiasParseException { 61 | URL.parse("http"); 62 | } 63 | 64 | @Test(expected = GalimatiasParseException.class) 65 | public void parseURLWithBadBase() throws GalimatiasParseException { 66 | URL.parse(URL.parse("mailto:user@example.com"), "/relative"); 67 | } 68 | 69 | @Test(expected = GalimatiasParseException.class) 70 | public void parseURLWithMalformedScheme() throws GalimatiasParseException { 71 | URL.parse("+http://example.com"); 72 | } 73 | 74 | @Test 75 | public void parseURLWithErrors() throws GalimatiasParseException { 76 | assertThat(URL.parse("http://example.com\\foo\\bar").toString()).isEqualTo("http://example.com/foo/bar"); 77 | } 78 | 79 | @Test(expected = GalimatiasParseException.class) 80 | public void parseURLWithErrorsStrict() throws GalimatiasParseException { 81 | final URLParsingSettings settings = URLParsingSettings.create() 82 | .withErrorHandler(StrictErrorHandler.getInstance()); 83 | assertThat(URL.parse(settings, "http://example.com\\foo\\bar").toString()).isEqualTo("http://example.com/foo/bar"); 84 | } 85 | 86 | @Theory 87 | public void withSchemeInvalidCharacter(final @TestURL.TestURLs(dataset = TestURL.DATASETS.WHATWG) 88 | TestURL testURL) throws GalimatiasParseException { 89 | assumeNotNull(testURL.parsedURL); 90 | thrown.expect(GalimatiasParseException.class); 91 | testURL.parsedURL.withScheme("http%%"); 92 | } 93 | 94 | @Theory 95 | public void withSchemeStartingNotAlpha(final @TestURL.TestURLs(dataset = TestURL.DATASETS.WHATWG) 96 | TestURL testURL) throws GalimatiasParseException { 97 | assumeNotNull(testURL.parsedURL); 98 | thrown.expect(GalimatiasParseException.class); 99 | testURL.parsedURL.withScheme("1foo"); 100 | } 101 | 102 | @Test 103 | public void strictTabsInUser() throws GalimatiasParseException { 104 | assertThat(URL.parse("http://a\tb@example.com")).isEqualTo(URL.parse("http://ab@example.com")); 105 | thrown.expect(GalimatiasParseException.class); 106 | URL.parse( 107 | URLParsingSettings.create().withErrorHandler(StrictErrorHandler.getInstance()), 108 | "http://a\tb@example.com" 109 | ); 110 | } 111 | 112 | @Test 113 | public void strictCarriageInUser() throws GalimatiasParseException { 114 | assertThat(URL.parse("http://a\rb@example.com")).isEqualTo(URL.parse("http://ab@example.com")); 115 | thrown.expect(GalimatiasParseException.class); 116 | URL.parse( 117 | URLParsingSettings.create().withErrorHandler(StrictErrorHandler.getInstance()), 118 | "http://a\rb@example.com" 119 | ); 120 | } 121 | 122 | @Test 123 | public void strictNewlineInUser() throws GalimatiasParseException { 124 | assertThat(URL.parse("http://a\nb@example.com")).isEqualTo(URL.parse("http://ab@example.com")); 125 | thrown.expect(GalimatiasParseException.class); 126 | URL.parse( 127 | URLParsingSettings.create().withErrorHandler(StrictErrorHandler.getInstance()), 128 | "http://a\nb@example.com" 129 | ); 130 | } 131 | 132 | @Test 133 | public void strictTabsNewlines() throws GalimatiasParseException { 134 | URLParsingSettings strictSettings = URLParsingSettings.create().withErrorHandler(StrictErrorHandler.getInstance()); 135 | for (String replacement : new String[] { "\t", "\n", "\r" }) { 136 | for (String url : new String[] { 137 | "http://a%sb@example.com", "http://a%sb:foo@example.com", "http://foo:a%sb@example.com", 138 | "http://a%sb.com", "http://ab.com:2%s2", "http://example.com/a%sb", 139 | "http://example.com/?a%sb", "http://exaple.com/#a%sb", 140 | "file://host%sname/path" 141 | }) { 142 | final String urlString = String.format(url, replacement); 143 | assertThat(URL.parse(urlString)).isEqualTo(URL.parse(String.format(url, ""))); 144 | try { 145 | URL.parse(strictSettings, urlString); 146 | assertThat(false); 147 | } catch (GalimatiasParseException ex) { 148 | assertThat(true); 149 | } 150 | } 151 | } 152 | } 153 | 154 | @Test 155 | public void strictUnencodedPercentage() throws GalimatiasParseException { 156 | URLParsingSettings strictSettings = URLParsingSettings.create().withErrorHandler(StrictErrorHandler.getInstance()); 157 | for (String replacement : new String[] { "%%", "%1Z", "%Z1", "%ZZ" }) { 158 | for (String url : new String[] { 159 | "data:%s", "http://%s@example.com", "http://%s:foo@example.com", "http://foo:%s@example.com", 160 | "http://example.com/%s", "http://example.com/?%s", "http://exaple.com/#%s" 161 | }) { 162 | final String urlString = String.format(url, replacement); 163 | URL.parse(urlString); 164 | try { 165 | URL.parse(strictSettings, urlString); 166 | assertThat(false); 167 | } catch (GalimatiasParseException ex) { 168 | assertThat(true); 169 | } 170 | } 171 | } 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /src/test/java/io/mola/galimatias/IPv6AddressTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias; 23 | 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | import org.junit.runners.JUnit4; 27 | import org.slf4j.Logger; 28 | import org.slf4j.LoggerFactory; 29 | 30 | import java.net.Inet6Address; 31 | import java.net.InetAddress; 32 | import java.net.UnknownHostException; 33 | 34 | import static org.fest.assertions.Assertions.assertThat; 35 | 36 | @RunWith(JUnit4.class) 37 | public class IPv6AddressTest { 38 | 39 | private static Logger log = LoggerFactory.getLogger(IPv6AddressTest.class); 40 | 41 | private static final String[][] TEST_ADDRESSES = new String[][] { 42 | new String[] { "fedc:ba98:7654:3210:fedc:ba98:7654:3210" }, 43 | new String[] { "FEDC:BA98:7654:3210:FEDC:BA98:7654:3211", "fedc:ba98:7654:3210:fedc:ba98:7654:3211" }, 44 | new String[] { "2001:0db8:85a3:0000:0000:8a2e:0370:7334", "2001:db8:85a3::8a2e:370:7334" }, 45 | new String[] { "2001:db8:85a3::8a2e:370:7334", "2001:db8:85a3::8a2e:370:7334" }, 46 | new String[] { "0:0:0:0:0:0:0:1", "::1" }, 47 | new String[] { "0:0:0:0:0:0:0:0", "::" }, 48 | new String[] { "::1" }, 49 | new String[] { "::" }, 50 | new String[] { "::ffff:192.0.2.128", "::ffff:c000:280" }, //XXX: "::ffff:192.0.2.128" }, 51 | new String[] { "::192.0.2.128", "::c000:280" } //XXX: Are we serializing IPv4-mapped addresses? "::192.0.2.128" } 52 | }; 53 | 54 | @Test 55 | public void parseIPv6Address() throws GalimatiasParseException { 56 | for (final String[] testAddress : TEST_ADDRESSES) { 57 | final String origin = testAddress[0]; 58 | log.debug("TESTING: {}", origin); 59 | final String target = (testAddress.length > 1)? testAddress[1] : testAddress[0]; 60 | final IPv6Address address = IPv6Address.parseIPv6Address(origin); 61 | assertThat(address.toString()).isEqualTo(target); 62 | } 63 | } 64 | 65 | @Test 66 | public void equals() throws GalimatiasParseException { 67 | for (final String[] testAddress : TEST_ADDRESSES) { 68 | final IPv6Address original = IPv6Address.parseIPv6Address(testAddress[0]); 69 | final IPv6Address result = IPv6Address.parseIPv6Address(testAddress[testAddress.length > 1? 1 : 0]); 70 | assertThat(original).isEqualTo(original); 71 | assertThat(original).isEqualTo(result); 72 | assertThat(original.hashCode()).isEqualTo(result.hashCode()); 73 | assertThat(original).isNotEqualTo(null); 74 | assertThat(original).isNotEqualTo("foo"); 75 | assertThat(original.toHumanString()).isEqualTo(original.toString()); 76 | } 77 | assertThat(IPv6Address.parseIPv6Address(TEST_ADDRESSES[0][0])) 78 | .isNotEqualTo(IPv6Address.parseIPv6Address(TEST_ADDRESSES[1][0])); 79 | 80 | } 81 | 82 | @Test(expected = GalimatiasParseException.class) 83 | public void parseInvalidPrefix1() throws GalimatiasParseException { 84 | IPv6Address.parseIPv6Address(":1"); 85 | } 86 | 87 | @Test(expected = GalimatiasParseException.class) 88 | public void parseInvalidPrefix2() throws GalimatiasParseException { 89 | IPv6Address.parseIPv6Address(":"); 90 | } 91 | 92 | @Test(expected = NullPointerException.class) 93 | public void parseNullAddress() throws GalimatiasParseException { 94 | IPv6Address.parseIPv6Address(null); 95 | } 96 | 97 | @Test(expected = GalimatiasParseException.class) 98 | public void parseEmptyAddress() throws GalimatiasParseException { 99 | IPv6Address.parseIPv6Address(""); 100 | } 101 | 102 | @Test(expected = GalimatiasParseException.class) 103 | public void parseIllegalCharacter() throws GalimatiasParseException { 104 | IPv6Address.parseIPv6Address("1::x:1"); 105 | } 106 | 107 | 108 | @Test(expected = GalimatiasParseException.class) 109 | public void parseTooLongAddress() throws GalimatiasParseException { 110 | IPv6Address.parseIPv6Address("0:0:0:0:0:0:0:1:2"); 111 | } 112 | 113 | @Test(expected = GalimatiasParseException.class) 114 | public void parseAddressWithFinalColon() throws GalimatiasParseException { 115 | IPv6Address.parseIPv6Address("0:0:0:0:0:0:0:1:"); 116 | } 117 | 118 | @Test(expected = GalimatiasParseException.class) 119 | public void parseTooLongIPv4MappedAddress() throws GalimatiasParseException { 120 | IPv6Address.parseIPv6Address("0:0:0:0:0:0:0:192.168.1.1"); 121 | } 122 | 123 | @Test(expected = GalimatiasParseException.class) 124 | public void parseTooShortAddress() throws GalimatiasParseException { 125 | IPv6Address.parseIPv6Address("0:0:0:0:0:1"); 126 | } 127 | 128 | @Test(expected = GalimatiasParseException.class) 129 | public void parseDoubleCompressedAddress() throws GalimatiasParseException { 130 | IPv6Address.parseIPv6Address("1::2::3"); 131 | } 132 | 133 | @Test(expected = GalimatiasParseException.class) 134 | public void parseTooLongIPv4Mapped() throws GalimatiasParseException { 135 | IPv6Address.parseIPv6Address("::192.168.1.1.5"); 136 | } 137 | 138 | @Test(expected = GalimatiasParseException.class) 139 | public void parseIPv4MappedWithLeadingZero1() throws GalimatiasParseException { 140 | IPv6Address.parseIPv6Address("::192.168.1.1.05"); 141 | } 142 | 143 | @Test(expected = GalimatiasParseException.class) 144 | public void parseIPv4MappedWithLeadingZero2() throws GalimatiasParseException { 145 | IPv6Address.parseIPv6Address("::192.168.1.1.00"); 146 | } 147 | 148 | @Test(expected = GalimatiasParseException.class) 149 | public void parseMalformedIPv4Mapped() throws GalimatiasParseException { 150 | IPv6Address.parseIPv6Address("::192.168.1a.1"); 151 | } 152 | 153 | @Test(expected = GalimatiasParseException.class) 154 | public void parseMalformedIPv4Mapped2() throws GalimatiasParseException { 155 | IPv6Address.parseIPv6Address("::192.168.a1.1"); 156 | } 157 | 158 | @Test(expected = GalimatiasParseException.class) 159 | public void parseMalformedIPv4Mapped3() throws GalimatiasParseException { 160 | IPv6Address.parseIPv6Address("::.192.168.1.1"); 161 | } 162 | 163 | @Test(expected = GalimatiasParseException.class) 164 | public void parseHighValueIPv4Mapped() throws GalimatiasParseException { 165 | IPv6Address.parseIPv6Address("::192.168.1.256"); 166 | } 167 | 168 | @Test 169 | public void toFromInetAddress() throws UnknownHostException, GalimatiasParseException { 170 | for (final String[] testAddress : TEST_ADDRESSES) { 171 | final String origin = testAddress[0]; 172 | log.debug("TESTING: {}", origin); 173 | final InetAddress target = InetAddress.getByName(origin); 174 | final IPv6Address address = IPv6Address.parseIPv6Address(origin); 175 | assertThat(address.toInetAddress()).isEqualTo(target); 176 | 177 | //FIXME: We currently do not support getting Inet4Address here 178 | // (such as an IPv6-mapped IPv4 address). 179 | if (target instanceof Inet6Address) { 180 | assertThat(IPv6Address.fromInet6Address((Inet6Address) target)).isEqualTo(address); 181 | } 182 | } 183 | } 184 | 185 | } 186 | -------------------------------------------------------------------------------- /src/main/java/io/mola/galimatias/Domain.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias; 23 | 24 | import com.ibm.icu.text.IDNA; 25 | 26 | import java.util.ArrayList; 27 | import java.util.Arrays; 28 | import java.util.List; 29 | 30 | public class Domain extends Host { 31 | 32 | private static final long serialVersionUID = 2L; 33 | 34 | private final String domain; 35 | private final boolean unicode; 36 | 37 | private Domain(final String domain, final boolean unicode) { 38 | this.domain = domain; 39 | this.unicode = unicode; 40 | } 41 | 42 | public static Domain parseDomain(final String input) throws GalimatiasParseException { 43 | return parseDomain(input, false); 44 | } 45 | 46 | public List labels() { 47 | return Arrays.asList(splitWorker(domain, "\u002E\u3002\uFF0E\uFF61", -1, true)); 48 | } 49 | 50 | public static Domain parseDomain(final String input, final boolean unicode) throws GalimatiasParseException { 51 | return parseDomain(URLParsingSettings.create(), input, unicode); 52 | } 53 | 54 | public static Domain parseDomain(final URLParsingSettings settings, final String input, final boolean unicode) throws GalimatiasParseException { 55 | if (input == null) { 56 | throw new NullPointerException(); 57 | } 58 | if (input.isEmpty()) { 59 | throw new GalimatiasParseException("input is empty"); 60 | } 61 | 62 | final ErrorHandler errorHandler = settings.errorHandler(); 63 | 64 | // WHATWG says: Let host be the result of running utf-8's decoder on the percent decoding of running utf-8 encode on input. 65 | String domain = URLUtils.percentDecode(input); 66 | 67 | String asciiDomain = URLUtils.domainToASCII(domain, errorHandler); 68 | 69 | for (int i = 0; i < asciiDomain.length(); i++) { 70 | switch (asciiDomain.charAt(i)) { 71 | case 0x0000: 72 | case 0x0009: 73 | case 0x000A: 74 | case 0x000D: 75 | case 0x0020: 76 | case '#': 77 | case '%': 78 | case '/': 79 | case ':': 80 | case '?': 81 | case '@': 82 | case '[': 83 | case '\\': 84 | case ']': 85 | final GalimatiasParseException exception = 86 | new GalimatiasParseException("Domain contains invalid character: " + asciiDomain.charAt(i)); 87 | errorHandler.fatalError(exception); 88 | throw exception; 89 | } 90 | } 91 | 92 | if (!unicode) { 93 | return new Domain(asciiDomain, unicode); 94 | } 95 | 96 | return new Domain(URLUtils.domainToUnicode(asciiDomain, errorHandler), unicode); 97 | } 98 | 99 | @Override 100 | public String toString() { 101 | return domain; 102 | } 103 | 104 | @Override 105 | public String toHumanString() { 106 | if (unicode) { 107 | return domain; 108 | } 109 | final IDNA.Info idnaInfo = new IDNA.Info(); 110 | final StringBuilder idnaOutput = new StringBuilder(); 111 | IDNA.getUTS46Instance(IDNA.DEFAULT).nameToUnicode(domain, idnaOutput, idnaInfo); 112 | return idnaOutput.toString(); 113 | } 114 | 115 | @Override 116 | public boolean equals(Object o) { 117 | if (this == o) return true; 118 | if (o == null || getClass() != o.getClass()) return false; 119 | 120 | Domain domain1 = (Domain) o; 121 | 122 | //if (unicode != domain1.unicode) return false; 123 | if (domain != null ? !domain.equals(domain1.domain) : domain1.domain != null) return false; 124 | 125 | return true; 126 | } 127 | 128 | @Override 129 | public int hashCode() { 130 | int result = domain != null ? domain.hashCode() : 0; 131 | //result = 31 * result + (unicode ? 1 : 0); 132 | return result; 133 | } 134 | 135 | /** 136 | * Imported from 137 | * https://github.com/apache/commons-lang/blob/690dc3c9c4cf8a1875d882ae09741c2e6342ad6b/src/main/java/org/apache/commons/lang3/StringUtils.java 138 | * 139 | * Performs the logic for the {@code split} and 140 | * {@code splitPreserveAllTokens} methods that return a maximum array 141 | * length. 142 | * 143 | * @param str the String to parse, may be {@code null} 144 | * @param separatorChars the separate character 145 | * @param max the maximum number of elements to include in the 146 | * array. A zero or negative value implies no limit. 147 | * @param preserveAllTokens if {@code true}, adjacent separators are 148 | * treated as empty token separators; if {@code false}, adjacent 149 | * separators are treated as one separator. 150 | * @return an array of parsed Strings, {@code null} if null String input 151 | */ 152 | private static String[] splitWorker(final String str, final String separatorChars, final int max, final boolean preserveAllTokens) { 153 | // Performance tuned for 2.0 (JDK1.4) 154 | // Direct code is quicker than StringTokenizer. 155 | // Also, StringTokenizer uses isSpace() not isWhitespace() 156 | 157 | //XXX: This never happens in Domain.parseDomain 158 | //if (str == null) { 159 | // return null; 160 | //} 161 | final int len = str.length(); 162 | //if (len == 0) { 163 | // return EMPTY_STRING_ARRAY; 164 | //} 165 | 166 | final List list = new ArrayList(); 167 | int sizePlus1 = 1; 168 | int i = 0, start = 0; 169 | boolean match = false; 170 | boolean lastMatch = false; 171 | if (separatorChars == null) { 172 | // Null separator means use whitespace 173 | while (i < len) { 174 | if (Character.isWhitespace(str.charAt(i))) { 175 | if (match || preserveAllTokens) { 176 | lastMatch = true; 177 | if (sizePlus1++ == max) { 178 | i = len; 179 | lastMatch = false; 180 | } 181 | list.add(str.substring(start, i)); 182 | match = false; 183 | } 184 | start = ++i; 185 | continue; 186 | } 187 | lastMatch = false; 188 | match = true; 189 | i++; 190 | } 191 | } else if (separatorChars.length() == 1) { 192 | // Optimise 1 character case 193 | final char sep = separatorChars.charAt(0); 194 | while (i < len) { 195 | if (str.charAt(i) == sep) { 196 | if (match || preserveAllTokens) { 197 | lastMatch = true; 198 | if (sizePlus1++ == max) { 199 | i = len; 200 | lastMatch = false; 201 | } 202 | list.add(str.substring(start, i)); 203 | match = false; 204 | } 205 | start = ++i; 206 | continue; 207 | } 208 | lastMatch = false; 209 | match = true; 210 | i++; 211 | } 212 | } else { 213 | // standard case 214 | while (i < len) { 215 | if (separatorChars.indexOf(str.charAt(i)) >= 0) { 216 | if (match || preserveAllTokens) { 217 | lastMatch = true; 218 | if (sizePlus1++ == max) { 219 | i = len; 220 | lastMatch = false; 221 | } 222 | list.add(str.substring(start, i)); 223 | match = false; 224 | } 225 | start = ++i; 226 | continue; 227 | } 228 | lastMatch = false; 229 | match = true; 230 | i++; 231 | } 232 | } 233 | if (match || preserveAllTokens && lastMatch) { 234 | list.add(str.substring(start, i)); 235 | } 236 | return list.toArray(new String[list.size()]); 237 | } 238 | 239 | } 240 | -------------------------------------------------------------------------------- /src/main/java/io/mola/galimatias/FormURLEncodedParser.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias; 23 | 24 | import java.nio.ByteBuffer; 25 | import java.nio.charset.Charset; 26 | import java.util.ArrayList; 27 | import java.util.List; 28 | 29 | /** 30 | * Parses query strings with application/x-www-form-urlencoded rules. 31 | * 32 | * This class supersedes @{link java.net.URLEncoder}. 33 | * 34 | * @see https://url.spec.whatwg.org/#application/x-www-form-urlencoded-0 35 | */ 36 | public final class FormURLEncodedParser { 37 | 38 | private FormURLEncodedParser() { 39 | 40 | } 41 | 42 | private static final FormURLEncodedParser INSTANCE = new FormURLEncodedParser(); 43 | 44 | @Deprecated 45 | public static FormURLEncodedParser getInstance() { 46 | return INSTANCE; 47 | } 48 | 49 | /** 50 | * Charset defaults to UTF-8. 51 | * 52 | * @see {@link #parse(String, java.nio.charset.Charset)} 53 | * @see application/x-www-form-urlencoded parser 54 | * 55 | * @param input 56 | */ 57 | public static List parse(final String input) { 58 | return parse(input, Charset.forName("UTF-8")); 59 | } 60 | 61 | /** 62 | * 63 | * @see application/x-www-form-urlencoded parser 64 | * 65 | * @param input 66 | * @param charset Any charset. Note that the 67 | * WHATWG Encoding Standard 68 | * imposes restrictions on what encodings can and cannot be used. This method does not enforce 69 | * such restrictions. 70 | */ 71 | public static List parse(final String input, final Charset charset) { 72 | final boolean isIndex = false; 73 | 74 | if (input == null) { 75 | throw new NullPointerException("input"); 76 | } 77 | if (charset == null) { 78 | throw new NullPointerException("charset"); 79 | } 80 | 81 | //TODO: Encoding stuff is not here because we get Strings instead of bytes 82 | 83 | // 3. Let sequences be the result of splitting input on `&`. 84 | final String[] sequences = input.split("&"); 85 | 86 | // 4. If the isindex flag is set and the first byte sequence in sequences does not contain 87 | // a `=`, prepend `=` to the first byte sequence in sequences. 88 | if (isIndex) { 89 | if (sequences[0].isEmpty() || sequences[0].charAt(0) != '=') { 90 | sequences[0] = "=" + sequences[0]; 91 | } 92 | } 93 | 94 | // 5. Let pairs be an empty list of name-value pairs where both name and value hold a byte sequence. 95 | final List pairs = new ArrayList(); 96 | 97 | // 6. For each byte sequence bytes in sequences, run these substeps: 98 | for (final String bytes : sequences) { 99 | 100 | // 1. If bytes is the empty byte sequence, run these substeps for the next byte sequence. 101 | if (bytes.isEmpty()) { 102 | continue; 103 | } 104 | 105 | String name; 106 | String value; 107 | 108 | // 2. If bytes contains a `=`, then let name be the bytes from the start of bytes up to but excluding its 109 | // first `=`, and let value be the bytes, if any, after the first `=` up to the end of bytes. If `=` 110 | // is the first byte, then name will be the empty byte sequence. If it is the last, then value will be 111 | // the empty byte sequence. 112 | final int equalsIndex = bytes.indexOf("="); 113 | if (equalsIndex != -1) { 114 | if (equalsIndex == 0) { 115 | name = ""; 116 | } else { 117 | name = bytes.substring(0, equalsIndex); 118 | } 119 | if (equalsIndex == bytes.length() - 1) { 120 | value = ""; 121 | } else { 122 | value = bytes.substring(equalsIndex + 1); 123 | } 124 | } 125 | 126 | // 3. Otherwise, let name have the value of bytes and let value be the empty byte sequence. 127 | else { 128 | name = bytes; 129 | value = ""; 130 | } 131 | 132 | // 4. Replace any `+` in name and value with 0x20. 133 | name = name.replace('+', ' '); 134 | value = value.replace('+', ' '); 135 | 136 | // 5. If use _charset_ flag is set, name is `_charset_`, run these substeps: 137 | // TODO 138 | 139 | // 6. Add a pair consisting of name and value to pairs. 140 | pairs.add(new NameValue(name, value)); 141 | 142 | } 143 | 144 | // 7. Let output be an empty list of name-value pairs where both name and value hold a string. 145 | // 8. For each name-value pair in pairs, append a name-value pair to output where the new name 146 | // and value appended to output are the result of running encoding override's decoder on the percent 147 | // decoding of the name and value from pairs, respectively. 148 | // TODO 149 | 150 | // 9. Return output. 151 | return pairs; 152 | } 153 | 154 | private static String serialize(ByteBuffer bytes) { 155 | if (bytes == null) { 156 | throw new NullPointerException("bytes"); 157 | } 158 | return serialize(bytes.array(), bytes.arrayOffset(), bytes.limit()); 159 | } 160 | 161 | /** 162 | * Implements the application/x-www-form-urlencoded byte serializer. 163 | */ 164 | private static String serialize(final byte[] bytes, final int offset, final int length) { 165 | if (bytes == null) { 166 | throw new NullPointerException("bytes"); 167 | } 168 | final StringBuilder sb = new StringBuilder(length); 169 | for (int i = offset; i < offset + length; i++) { 170 | final byte b = bytes[i]; 171 | if (0x20 == b) { 172 | sb.append((char)0x20); 173 | } else if (0x2A == b || 0x2D == b || 0x2E == b || (0x30 <= b && b <= 0x39) 174 | || (0x41 <= b && b <= 0x5A) || 0x5F == b || (0x61 <= b && b <= 0x7A)) { 175 | sb.appendCodePoint(b); 176 | } else { 177 | URLUtils.percentEncode(b, sb); 178 | } 179 | } 180 | return sb.toString(); 181 | } 182 | 183 | /** 184 | * Replacement for {@link java.net.URLEncoder#encode(String, String)}. 185 | * 186 | * Uses UTF-8 as default charset. 187 | * 188 | * @see {@link #encode(java.util.List, java.nio.charset.Charset)} 189 | * 190 | * @param input 191 | * @return 192 | */ 193 | public static String encode(final String input) { 194 | // 1. If encoding override is not given, set it to utf-8. 195 | return encode(input, Charset.forName("UTF-8")); 196 | } 197 | 198 | /** 199 | * Replacement for {@link java.net.URLEncoder#encode(String, String)}. 200 | * 201 | * @see {@link #encode(java.util.List, java.nio.charset.Charset)} 202 | * 203 | * @param input 204 | * @param charset 205 | * @return 206 | */ 207 | public static String encode(final String input, final Charset charset) { 208 | return encode(parse(input), charset); 209 | } 210 | 211 | /** 212 | * Implements the application/x-www-form-urlencoded serializer. 213 | * 214 | * Uses UTF-8 as default charset. 215 | * 216 | * @see {@link #encode(java.util.List, java.nio.charset.Charset)} 217 | * 218 | * @param input 219 | * @return 220 | */ 221 | public static String encode(final List input) { 222 | // 1. If encoding override is not given, set it to utf-8. 223 | return encode(input, Charset.forName("UTF-8")); 224 | } 225 | 226 | /** 227 | * Implements the application/x-www-form-urlencoded serializer. 228 | * 229 | * @param input 230 | * @param charset 231 | * @return 232 | */ 233 | public static String encode(final List input, final Charset charset) { 234 | if (input == null) { 235 | throw new NullPointerException("input"); 236 | } 237 | if (charset == null) { 238 | throw new NullPointerException("charset"); 239 | } 240 | 241 | // 2. Let output be the empty string. 242 | final StringBuilder sb = new StringBuilder(); 243 | 244 | // 3. For each pair in pairs, run these substeps: 245 | for (int i = 0; i < input.size(); i++) { 246 | final NameValue pair = input.get(i); 247 | 248 | // 1. Let outputPair be a copy of pair. 249 | // N/A 250 | 251 | // 2. Replace outputPair's name and value with the result of running encode on them using 252 | // encoding override, respectively. 253 | // 3. Replace outputPair's name and value with their serialization. 254 | final String outputName = serialize(charset.encode(pair.name())); 255 | final String outputValue = serialize(charset.encode(pair.value())); 256 | 257 | // 4. If pair is not the first pair in pairs, append "&" to output. 258 | if (i != 0) { 259 | sb.append('&'); 260 | } 261 | 262 | // 5. Append outputPair's name, followed by "=", followed by outputPair's value to output. 263 | sb.append(outputName).append('=').append(outputValue); 264 | } 265 | 266 | // 4. Return output. 267 | return sb.toString(); 268 | } 269 | 270 | } -------------------------------------------------------------------------------- /src/test/java/io/mola/galimatias/URLSearchParamsTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias; 23 | 24 | import io.mola.galimatias.theories.FooOrEmptyOrNullString; 25 | import org.junit.Rule; 26 | import org.junit.Test; 27 | import org.junit.experimental.theories.Theories; 28 | import org.junit.experimental.theories.Theory; 29 | import org.junit.rules.ExpectedException; 30 | import org.junit.runner.RunWith; 31 | 32 | import java.lang.reflect.Field; 33 | import java.lang.reflect.InvocationTargetException; 34 | import java.util.ArrayList; 35 | import java.util.Arrays; 36 | import java.util.Iterator; 37 | import java.util.List; 38 | 39 | import static org.fest.assertions.Assertions.assertThat; 40 | import static org.junit.Assert.fail; 41 | import static org.junit.Assume.*; 42 | 43 | @RunWith(Theories.class) 44 | public class URLSearchParamsTest { 45 | 46 | @Rule 47 | public ExpectedException thrown = ExpectedException.none(); 48 | 49 | @Test 50 | public void queryParameters() throws GalimatiasParseException { 51 | URL url = URL.parse("http://example.com/?a=1"); 52 | assertThat(url.queryParameter("a")).isEqualTo("1"); 53 | assertThat(url.queryParameter("b")).isNull(); 54 | assertThat(url.queryParameters("a")).isEqualTo(Arrays.asList("1")); 55 | assertThat(url.queryParameters("b")).isEqualTo(Arrays.asList()); 56 | 57 | url = URL.parse("http://example.com/?a=1&foo=bar&a=2"); 58 | assertThat(url.queryParameter("a")).isEqualTo("1"); 59 | assertThat(url.queryParameter("foo")).isEqualTo("bar"); 60 | assertThat(url.queryParameter("b")).isNull(); 61 | assertThat(url.queryParameters("a")).isEqualTo(Arrays.asList("1", "2")); 62 | assertThat(url.queryParameters("foo")).isEqualTo(Arrays.asList("bar")); 63 | assertThat(url.queryParameters("b")).isEqualTo(Arrays.asList()); 64 | 65 | url = URL.parse("http://example.com/?a"); 66 | assertThat(url.queryParameter("a")).isEmpty(); 67 | assertThat(url.queryParameter("b")).isNull(); 68 | assertThat(url.queryParameters("a")).isEqualTo(Arrays.asList("")); 69 | assertThat(url.queryParameters("b")).isEqualTo(Arrays.asList()); 70 | 71 | url = URL.parse("http://example.com/?a="); 72 | assertThat(url.queryParameter("a")).isEqualTo(""); 73 | assertThat(url.queryParameter("b")).isNull(); 74 | assertThat(url.queryParameters("a")).isEqualTo(Arrays.asList("")); 75 | assertThat(url.queryParameters("b")).isEqualTo(Arrays.asList()); 76 | 77 | try { 78 | url.queryParameter(null); 79 | fail("Did not throw NPE"); 80 | } catch (NullPointerException ex) { } 81 | try { 82 | url.queryParameters(null); 83 | fail("Did not throw NPE"); 84 | } catch (NullPointerException ex) { } 85 | } 86 | 87 | @Test 88 | public void throwsNullPointerExceptions() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { 89 | try { 90 | new URLSearchParameters((List)null); 91 | fail("Did not throw NPE"); 92 | } catch (NullPointerException ex) { } 93 | 94 | URLSearchParameters params = new URLSearchParameters((String)null); 95 | 96 | try { 97 | params.get(null); 98 | fail("Did not throw NPE"); 99 | } catch (NullPointerException ex) { } 100 | 101 | try { 102 | params.getAll(null); 103 | fail("Did not throw NPE"); 104 | } catch (NullPointerException ex) { } 105 | 106 | try { 107 | params.has(null); 108 | fail("Did not throw NPE"); 109 | } catch (NullPointerException ex) { } 110 | 111 | try { 112 | params.with(null); 113 | fail("Did not throw NPE"); 114 | } catch (NullPointerException ex) { } 115 | 116 | try { 117 | params.withAppended(null); 118 | fail("Did not throw NPE"); 119 | } catch (NullPointerException ex) { } 120 | 121 | try { 122 | params.with("foo", null); 123 | fail("Did not throw NPE"); 124 | } catch (NullPointerException ex) { } 125 | 126 | try { 127 | params.with(null, "foo"); 128 | fail("Did not throw NPE"); 129 | } catch (NullPointerException ex) { } 130 | 131 | try { 132 | params.withAppended("foo", null); 133 | fail("Did not throw NPE"); 134 | } catch (NullPointerException ex) { } 135 | 136 | try { 137 | params.withAppended(null, "foo"); 138 | fail("Did not throw NPE"); 139 | } catch (NullPointerException ex) { } 140 | 141 | try { 142 | params.without(null); 143 | fail("Did not throw NPE"); 144 | } catch (NullPointerException ex) { } 145 | } 146 | 147 | @Theory 148 | public void validConstructorString(@FooOrEmptyOrNullString final String query) throws NoSuchFieldException, IllegalAccessException { 149 | final NameValue nameValue = new NameValue("foo", "bar"); 150 | 151 | Field field = URLSearchParameters.class.getDeclaredField("nameValues"); 152 | field.setAccessible(true); 153 | 154 | try { 155 | URLSearchParameters params = new URLSearchParameters(query); 156 | ((List) field.get(params)).add(nameValue); 157 | fail("Internal list is not immutable"); 158 | } catch (UnsupportedOperationException ex) { } 159 | 160 | field.setAccessible(false); 161 | } 162 | 163 | @Test 164 | public void validConstructorList() throws NoSuchFieldException, IllegalAccessException { 165 | final NameValue nameValue = new NameValue("foo", "bar"); 166 | 167 | Field field = URLSearchParameters.class.getDeclaredField("nameValues"); 168 | field.setAccessible(true); 169 | 170 | try { 171 | URLSearchParameters params = new URLSearchParameters(new ArrayList()); 172 | ((List) field.get(params)).add(nameValue); 173 | fail("Internal list is not immutable"); 174 | } catch (UnsupportedOperationException ex) { } 175 | 176 | field.setAccessible(false); 177 | } 178 | 179 | @Theory 180 | public void withNameValues(@TestURL.TestURLs final TestURL testURL) throws NoSuchFieldException, IllegalAccessException { 181 | assumeNotNull(testURL.parsedURL); 182 | final URLSearchParameters params = testURL.parsedURL.searchParameters(); 183 | final String key = "__FOO__"; 184 | final NameValue nameValue1 = new NameValue(key, "bar"); 185 | final NameValue nameValue2 = new NameValue(key, "baz"); 186 | assertThat(params.with(nameValue1).getAll(key)) 187 | .isEqualTo(Arrays.asList("bar")); 188 | assertThat(params.with(nameValue1).with(nameValue2).getAll(key)) 189 | .isEqualTo(Arrays.asList("baz")); 190 | assertThat(params.with(nameValue1.name(), nameValue1.value()).getAll(key)) 191 | .isEqualTo(Arrays.asList("bar")); 192 | assertThat(params.with(nameValue1.name(), nameValue1.value()).with(nameValue2.name(), nameValue2.value()).getAll(key)) 193 | .isEqualTo(Arrays.asList("baz")); 194 | } 195 | 196 | @Theory 197 | public void withAppendedNameValues(@TestURL.TestURLs final TestURL testURL) throws NoSuchFieldException, IllegalAccessException { 198 | assumeNotNull(testURL.parsedURL); 199 | final URLSearchParameters params = testURL.parsedURL.searchParameters(); 200 | final String key = "__FOO__"; 201 | final NameValue nameValue1 = new NameValue(key, "bar"); 202 | final NameValue nameValue2 = new NameValue(key, "baz"); 203 | assertThat(params.withAppended(nameValue1).getAll(key)) 204 | .isEqualTo(Arrays.asList("bar")); 205 | assertThat(params.withAppended(nameValue1).withAppended(nameValue2).getAll(key)) 206 | .isEqualTo(Arrays.asList("bar", "baz")); 207 | assertThat(params.withAppended(nameValue1.name(), nameValue1.value()).getAll(key)) 208 | .isEqualTo(Arrays.asList("bar")); 209 | assertThat(params.withAppended(nameValue1.name(), nameValue1.value()).withAppended(nameValue2.name(), nameValue2.value()).getAll(key)) 210 | .isEqualTo(Arrays.asList("bar", "baz")); 211 | } 212 | 213 | @Theory 214 | public void get(@TestURL.TestURLs final TestURL testURL) throws NoSuchFieldException, IllegalAccessException { 215 | assumeNotNull(testURL.parsedURL); 216 | final URLSearchParameters params = testURL.parsedURL.searchParameters(); 217 | final String key = "__FOO__"; 218 | final NameValue nameValue1 = new NameValue(key, "bar"); 219 | final NameValue nameValue2 = new NameValue(key, "baz"); 220 | assertThat(params.withAppended(nameValue1).get(key)) 221 | .isEqualTo("bar"); 222 | assertThat(params.withAppended(nameValue1).withAppended(nameValue2).get(key)) 223 | .isEqualTo("bar"); 224 | assertThat(params.withAppended(nameValue1).get("__BAR__")) 225 | .isNull(); 226 | assertThat(params.withAppended(nameValue1).withAppended(nameValue2).get("__BAR__")) 227 | .isNull(); 228 | } 229 | 230 | @Theory 231 | public void has(@TestURL.TestURLs final TestURL testURL) { 232 | assumeNotNull(testURL.parsedURL); 233 | final URLSearchParameters params = testURL.parsedURL.searchParameters(); 234 | assertThat(params.has("__FOO__")).isFalse(); 235 | for (final NameValue nv : params) { 236 | assertThat(params.has(nv.name())).isTrue(); 237 | } 238 | } 239 | 240 | @Theory 241 | public void without(@TestURL.TestURLs final TestURL testURL) { 242 | assumeNotNull(testURL.parsedURL); 243 | final URLSearchParameters params = testURL.parsedURL.searchParameters(); 244 | assertThat((Object)params.without("__FOO__")).isEqualTo(params); 245 | for (final NameValue nv : params) { 246 | assertThat((Object)params.without(nv.name()).get(nv.name())).isNull(); 247 | } 248 | } 249 | 250 | @Test 251 | public void iterator() { 252 | final URLSearchParameters params = new URLSearchParameters("foo=1&bar=2&foo=3"); 253 | final Iterator it = params.iterator(); 254 | assertThat(it.next()).isEqualTo(new NameValue("foo", "1")); 255 | assertThat(it.next()).isEqualTo(new NameValue("bar", "2")); 256 | assertThat(it.next()).isEqualTo(new NameValue("foo", "3")); 257 | assertThat(it.hasNext()).isFalse(); 258 | } 259 | 260 | } 261 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 4.0.0 27 | 28 | io.mola.galimatias 29 | galimatias 30 | 0.2.1-SNAPSHOT 31 | jar 32 | 33 | galimatias 34 | 35 | galimatias is a library for URL parsing, canonicalization and manipulation. 36 | 37 | http://github.io/smola/galimatias 38 | 2013 39 | 40 | 41 | UTF-8 42 | UTF-8 43 | github 44 | 45 | 46 | 47 | 48 | MIT License 49 | https://raw.github.com/smola/galimatias/master/LICENSE 50 | repo 51 | 52 | 53 | 54 | 55 | scm:git:git@github.com:smola/galimatias.git 56 | https://github.com/smola/galimatias.git 57 | scm:git:git://github.com/smola/galimatias.git 58 | 59 | 60 | 61 | GitHub 62 | http://github.com/smola/galimatias/issues 63 | 64 | 65 | 66 | 67 | Santiago M. Mola 68 | santi@mola.io 69 | http://mola.io/ 70 | 71 | 72 | 73 | 74 | org.sonatype.oss 75 | oss-parent 76 | 7 77 | 78 | 79 | 80 | 81 | com.ibm.icu 82 | icu4j 83 | 53.1 84 | 85 | 86 | ch.qos.logback 87 | logback-classic 88 | 1.1.2 89 | test 90 | 91 | 92 | org.slf4j 93 | slf4j-api 94 | 1.7.7 95 | test 96 | 97 | 98 | junit 99 | junit 100 | 4.11 101 | test 102 | 103 | 104 | org.easytesting 105 | fest-assert 106 | 1.4 107 | test 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | org.apache.maven.plugins 116 | maven-javadoc-plugin 117 | 2.9.1 118 | 119 | ${project.version} 120 | 121 | 122 | 123 | 124 | 125 | 126 | org.apache.maven.plugins 127 | maven-compiler-plugin 128 | 3.1 129 | 130 | UTF-8 131 | 1.6 132 | 1.6 133 | true 134 | true 135 | 136 | 137 | 138 | org.codehaus.mojo 139 | cobertura-maven-plugin 140 | 2.6 141 | 142 | 143 | html 144 | xml 145 | 146 | 256m 147 | 148 | 149 | 150 | org.eluder.coveralls 151 | coveralls-maven-plugin 152 | 2.1.0 153 | 154 | 155 | com.mycila 156 | license-maven-plugin 157 | 2.6 158 | 159 |
LICENSE
160 | 161 | LICENSE 162 | **/README 163 | src/test/resources/** 164 | src/main/resources/** 165 | 166 |
167 | 168 | 169 | 170 | check 171 | 172 | 173 | 174 |
175 | 176 | maven-enforcer-plugin 177 | 1.3.1 178 | 179 | 180 | org.semver 181 | enforcer-rule 182 | 0.9.26 183 | 184 | 185 | 186 | 187 | check 188 | verify 189 | 190 | enforce 191 | 192 | 193 | 194 | 195 | 196 | true 197 | BACKWARD_COMPATIBLE_USER 198 | true 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | org.apache.maven.plugins 207 | maven-javadoc-plugin 208 | 209 | 210 | 211 | javadoc 212 | 213 | site 214 | 215 | 216 | 217 | 218 | org.apache.maven.plugins 219 | maven-release-plugin 220 | 221 | v@{project.version} 222 | clean verify 223 | 224 | 225 | 226 | org.codehaus.mojo 227 | findbugs-maven-plugin 228 | 2.5.4 229 | 230 | Max 231 | Low 232 | true 233 | 234 | 235 | 236 | org.codehaus.mojo 237 | versions-maven-plugin 238 | 2.1 239 | 240 |
241 |
242 | 243 | 244 | 245 | 246 | org.apache.maven.plugins 247 | maven-project-info-reports-plugin 248 | 2.7 249 | 250 | 251 | 252 | license 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | release 263 | 264 | 265 | 266 | org.apache.maven.plugins 267 | maven-javadoc-plugin 268 | 269 | 270 | attach-javadocs 271 | 272 | jar 273 | 274 | 275 | 276 | 277 | 278 | com.github.github 279 | site-maven-plugin 280 | 0.9 281 | 282 | Creating site for ${project.version} 283 | true 284 | false 285 | smola 286 | galimatias 287 | 288 | 289 | 290 | 291 | site 292 | 293 | site 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 |
303 | -------------------------------------------------------------------------------- /src/main/java/io/mola/galimatias/IPv6Address.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-2014 Santiago M. Mola 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | package io.mola.galimatias; 23 | 24 | import java.net.Inet6Address; 25 | import java.net.InetAddress; 26 | import java.net.UnknownHostException; 27 | import java.util.Arrays; 28 | 29 | import static io.mola.galimatias.URLUtils.isASCIIDigit; 30 | 31 | public class IPv6Address extends Host { 32 | 33 | private static final long serialVersionUID = 1L; 34 | 35 | private final short[] pieces; 36 | 37 | IPv6Address(short[] pieces) { 38 | this.pieces = Arrays.copyOf(pieces, pieces.length); 39 | } 40 | 41 | public static IPv6Address parseIPv6Address(final String ipString) throws GalimatiasParseException { 42 | // See also Mozilla's IPv6 parser: 43 | // http://bonsai.mozilla.org/cvsblame.cgi?file=/mozilla/nsprpub/pr/src/misc/prnetdb.c&rev=3.54&mark=1561#1561 44 | 45 | if (ipString == null) { 46 | throw new NullPointerException("Argument is null"); 47 | } 48 | if (ipString.isEmpty()) { 49 | throw new GalimatiasParseException("empty string"); 50 | } 51 | 52 | final short[] address = new short[8]; 53 | 54 | int piecePointer = 0; 55 | Integer compressPointer = null; 56 | int idx = 0; 57 | final char[] input = ipString.toCharArray(); 58 | boolean isEOF = idx >= input.length; 59 | char c = (isEOF)? 0x00 : input[idx]; 60 | 61 | if (c == ':') { 62 | if (idx + 1 >= input.length || input[idx+1] != ':') { 63 | throw new GalimatiasParseException("IPv6 address starting with ':' is not followed by a second ':'."); 64 | } 65 | idx += 2; 66 | piecePointer = 1; 67 | compressPointer = piecePointer; 68 | } 69 | 70 | boolean jumpToIpV4 = false; 71 | 72 | while (!isEOF) { // MAIN 73 | 74 | isEOF = idx >= input.length; 75 | c = (isEOF)? 0x00 : input[idx]; 76 | 77 | if (piecePointer == 8) { 78 | throw new GalimatiasParseException("Address too long"); 79 | } 80 | if (c == ':') { 81 | if (compressPointer != null) { 82 | throw new GalimatiasParseException("Zero-compression can be used only once."); 83 | } 84 | idx++; 85 | isEOF = idx >= input.length; 86 | c = (isEOF)? 0x00 : input[idx]; 87 | piecePointer++; 88 | compressPointer = piecePointer; 89 | continue; 90 | } 91 | 92 | int value = 0; 93 | int length = 0; 94 | 95 | while (length < 4 && URLUtils.isASCIIHexDigit(c)) { 96 | value = value * 0x10 + Integer.parseInt("" + c, 16); 97 | idx++; 98 | isEOF = idx >= input.length; 99 | c = (isEOF)? 0x00 : input[idx]; 100 | length++; 101 | } 102 | 103 | if (c == '.') { 104 | if (length == 0) { 105 | throw new GalimatiasParseException("':' cannot be followed by '.'"); 106 | } 107 | idx -= length; 108 | isEOF = idx >= input.length; 109 | c = (isEOF)? 0x00 : input[idx]; 110 | jumpToIpV4 = true; 111 | break; 112 | } else if (c == ':') { 113 | idx++; 114 | isEOF = idx >= input.length; 115 | if (isEOF) { 116 | throw new GalimatiasParseException("Cannot end with ':'"); 117 | } 118 | } else if (!isEOF) { 119 | throw new GalimatiasParseException("Illegal character"); 120 | } 121 | 122 | address[piecePointer] = (short)value; 123 | piecePointer++; 124 | 125 | } // end while MAIN 126 | 127 | boolean jumpToFinale = false; 128 | 129 | // Step 7 130 | if (!jumpToIpV4 && isEOF) { 131 | jumpToFinale = true; 132 | } 133 | 134 | if (!jumpToFinale) { 135 | // Step 8 IPv4 136 | if (piecePointer > 6) { 137 | throw new GalimatiasParseException("Not enough room for a IPv4-mapped address"); 138 | } 139 | } 140 | 141 | // Step 9 142 | int dotsSeen = 0; 143 | 144 | if (!jumpToFinale) { 145 | // Step 10: IPv4-mapped address. 146 | while (!isEOF) { 147 | // Step 10.1 148 | Integer value = null; 149 | 150 | // Step 10.2 151 | if (!isASCIIDigit(c)) { 152 | throw new GalimatiasParseException("Non-digit character in IPv4-mapped address"); 153 | } 154 | 155 | // Step 10.3 156 | while (isASCIIDigit(c)) { 157 | final int number = c - 0x30; // 10.3.1 158 | if (value == null) { // 10.3.2 159 | value = number; 160 | } else if (value == 0) { 161 | throw new GalimatiasParseException("IPv4 mapped address contains a leading zero"); 162 | } else { 163 | value = value * 10 + number; 164 | } 165 | idx++; // 10.3.3 166 | isEOF = idx >= input.length; 167 | c = (isEOF)? 0x00 : input[idx]; 168 | if (value > 255) { // 10.3.4 169 | throw new GalimatiasParseException("Invalid value for IPv4-mapped address"); 170 | } 171 | } 172 | 173 | // Step 10.4 174 | if (dotsSeen < 3 && c != '.') { 175 | throw new GalimatiasParseException("Illegal character in IPv4-mapped address"); 176 | } 177 | 178 | // Step 10.5 179 | address[piecePointer] = (short) ((address[piecePointer] << 8) + value); 180 | 181 | // Step 10.6 182 | if (dotsSeen == 1 || dotsSeen == 3) { 183 | piecePointer++; 184 | } 185 | 186 | // Step 10.7 187 | idx++; 188 | isEOF = idx >= input.length; 189 | c = (isEOF)? 0x00 : input[idx]; 190 | 191 | // Step 10.8 192 | if (dotsSeen == 3 && !isEOF) { 193 | throw new GalimatiasParseException("Too long IPv4-mapped address"); 194 | } 195 | 196 | // Step 10.9 197 | dotsSeen++; 198 | } 199 | } 200 | 201 | // Step 11 Finale 202 | if (compressPointer != null) { 203 | // Step 11.1 204 | int swaps = piecePointer - compressPointer; 205 | // Step 11.2 206 | piecePointer = 7; 207 | // Step 11.3 208 | while (piecePointer != 0 && swaps > 0) { 209 | short swappedPiece = address[piecePointer]; 210 | address[piecePointer] = address[compressPointer + swaps - 1]; 211 | address[compressPointer + swaps - 1] = swappedPiece; 212 | piecePointer--; 213 | swaps--; 214 | } 215 | } 216 | // Step 12 217 | else if (compressPointer == null && piecePointer != 8) { 218 | throw new GalimatiasParseException("Address too short"); 219 | } 220 | 221 | return new IPv6Address(address); 222 | } 223 | 224 | /** 225 | * Convert to @{java.net.InetAddress}. 226 | * 227 | * @return The IPv6 address as a @{java.net.InetAddress}. 228 | */ 229 | public InetAddress toInetAddress() { 230 | final byte[] bytes = new byte[16]; 231 | for (int i = 0; i < pieces.length; i++) { 232 | bytes[i*2] = (byte)((pieces[i] >> 8) & 0xFF); 233 | bytes[i*2+1] = (byte)(pieces[i] & 0xFF); 234 | } 235 | 236 | try { 237 | return InetAddress.getByAddress(bytes); 238 | } catch (UnknownHostException e) { 239 | // Can't happen if we pass the right amount of bytes 240 | throw new RuntimeException("BUG", e); 241 | } 242 | } 243 | 244 | /** 245 | * Convert from @{java.net.Inet6Address}. 246 | * 247 | * @param inet6Address The IPv6 address as a @{java.net.Inet6Address}. 248 | * @return The IPv6 address as a @{IPv6Address}. 249 | */ 250 | public static IPv6Address fromInet6Address(final Inet6Address inet6Address) { 251 | final byte[] bytes = inet6Address.getAddress(); 252 | final short[] pieces = new short[8]; 253 | for (int i = 0; i < pieces.length; i++) { 254 | pieces[i] = (short) (((bytes[i*2] & 0xFF) << 8) | (bytes[i*2+1] & 0x00FF)); 255 | } 256 | return new IPv6Address(pieces); 257 | } 258 | 259 | @Override 260 | public String toString() { 261 | // IPv6 serialization as specified in the WHATWG URL standard. 262 | // http://url.spec.whatwg.org/#host-serializing 263 | 264 | // Step 1 265 | final StringBuilder output = new StringBuilder(40); 266 | 267 | // Step 2: Let compress pointer be a pointer to the first 16-bit piece in 268 | // the first longest sequences of address's 16-bit pieces that are 0. 269 | int compressPointer = -1; 270 | int maxConsecutiveZeroes = 0; 271 | for (int i = 0; i < pieces.length; i++) { 272 | if (pieces[i] != 0) { 273 | continue; 274 | } 275 | int consecutiveZeroes = 0; 276 | for (int j = i; j < pieces.length; j++) { 277 | if (pieces[j] == 0) { 278 | consecutiveZeroes++; 279 | } else { 280 | break; 281 | } 282 | } 283 | if (consecutiveZeroes > maxConsecutiveZeroes) { 284 | compressPointer = i; 285 | maxConsecutiveZeroes = consecutiveZeroes; 286 | } 287 | } 288 | 289 | // Step 3: If there is no sequence of address's 16-bit pieces that are 0 longer than one, 290 | // set compress pointer to null. 291 | // 292 | // NOTE: Here null is -1, and it was already initialized. 293 | 294 | // Step 4: For each piece in address's pieces, run these substeps: 295 | for (int i = 0; i < pieces.length; i++) { 296 | 297 | // Step 4.1: If compress pointer points to piece, append "::" to output if piece is address's 298 | // first piece and append ":" otherwise, and then run these substeps again with all 299 | // subsequent pieces in address's pieces that are 0 skipped or go the next step in the 300 | // overall set of steps if that leaves no pieces. 301 | if (compressPointer == i) { 302 | if (i == 0) { 303 | output.append("::"); 304 | } else { 305 | output.append(':'); 306 | } 307 | while (i < pieces.length && pieces[i] == 0) { 308 | i++; 309 | } 310 | } 311 | 312 | if (i >= pieces.length) { 313 | break; 314 | } 315 | 316 | // Step 4.2: Append piece, represented as the shortest possible lowercase hexadecimal number, to output. 317 | output.append(Integer.toHexString(pieces[i] & 0xFFFF)); 318 | 319 | // Step 4.3: If piece is not address's last piece, append ":" to output. 320 | if (i < pieces.length - 1) { 321 | output.append(':'); 322 | } 323 | } 324 | 325 | return output.toString(); 326 | } 327 | 328 | @Override 329 | public String toHumanString() { 330 | return toString(); 331 | } 332 | 333 | @Override 334 | public boolean equals(Object o) { 335 | if (this == o) return true; 336 | if (o == null || getClass() != o.getClass()) return false; 337 | 338 | IPv6Address that = (IPv6Address) o; 339 | 340 | return Arrays.equals(pieces, that.pieces); 341 | } 342 | 343 | @Override 344 | public int hashCode() { 345 | return Arrays.hashCode(pieces); 346 | } 347 | } 348 | --------------------------------------------------------------------------------