Skip to content

Commit 971f8e6

Browse files
committed
Merge branch 'develop'
2 parents 3e5980a + 7b8e84c commit 971f8e6

File tree

15 files changed

+187
-82
lines changed

15 files changed

+187
-82
lines changed

.github/workflows/check-links.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ jobs:
2626
fetch-depth: 0
2727

2828
- name: Restore lychee cache
29-
uses: actions/cache@v3
29+
uses: actions/cache@v4
3030
with:
3131
path: .lycheecache
3232
key: cache-lychee-${{ github.sha }}
3333
restore-keys: cache-lychee-
3434

3535
- name: Check links
3636
id: lychee
37-
uses: lycheeverse/[email protected].1
37+
uses: lycheeverse/[email protected].3
3838
with:
3939
fail: true
4040
args: --max-concurrency 1 --cache --no-progress --exclude-all-private './**/*.md'

.github/workflows/java-ea-maven.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,4 @@ jobs:
3434
- name: Build and (headless) test with Maven
3535
uses: smithki/[email protected]
3636
with:
37-
run: mvn -U -B -ntp package
37+
run: mvn -U -B -ntp verify

.github/workflows/java8-maven.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
strategy:
99
fail-fast: false
1010
matrix:
11-
java: [ 8, 17 ]
11+
java: [ 8, 21 ]
1212
os: [ ubuntu-latest, macOS-latest, windows-latest ]
1313

1414
name: JDK${{ matrix.java }} on ${{ matrix.os }}
@@ -34,7 +34,7 @@ jobs:
3434
- name: Build and (headless) test with Maven
3535
uses: smithki/[email protected]
3636
with:
37-
run: mvn -U -B -ntp package
37+
run: mvn -U -B -ntp verify
3838

3939
auto-merge-job:
4040
needs: build-and-test-job
@@ -58,7 +58,7 @@ jobs:
5858
if: startsWith(github.repository, 'nbbrd/') && startsWith(github.ref, 'refs/heads/develop')
5959
strategy:
6060
matrix:
61-
java: [ 17 ]
61+
java: [ 21 ]
6262
os: [ ubuntu-latest ]
6363

6464
name: Snapshot on develop
@@ -108,7 +108,7 @@ jobs:
108108
if: startsWith(github.repository, 'nbbrd/') && startsWith(github.ref, 'refs/tags/v')
109109
strategy:
110110
matrix:
111-
java: [ 17 ]
111+
java: [ 21 ]
112112
os: [ ubuntu-latest ]
113113

114114
name: Release on tag

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## [Unreleased]
99

10+
## [0.0.28] - 2024-03-21
11+
12+
### Fixed
13+
14+
- Fix encoding of space characters in URL [#301](https://github.com/nbbrd/java-io-util/issues/301)
15+
1016
## [0.0.27] - 2024-01-16
1117

1218
### Fixed
@@ -246,7 +252,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
246252

247253
- Initial release
248254

249-
[Unreleased]: https://github.com/nbbrd/java-io-util/compare/v0.0.27...HEAD
255+
[Unreleased]: https://github.com/nbbrd/java-io-util/compare/v0.0.28...HEAD
256+
[0.0.28]: https://github.com/nbbrd/java-io-util/compare/v0.0.27...v0.0.28
250257
[0.0.27]: https://github.com/nbbrd/java-io-util/compare/v0.0.26...v0.0.27
251258
[0.0.26]: https://github.com/nbbrd/java-io-util/compare/v0.0.25...v0.0.26
252259
[0.0.25]: https://github.com/nbbrd/java-io-util/compare/v0.0.24...v0.0.25

java-io-base/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<groupId>com.github.nbbrd.java-io-util</groupId>
77
<artifactId>java-io-parent</artifactId>
8-
<version>0.0.27</version>
8+
<version>0.0.28</version>
99
</parent>
1010

1111
<artifactId>java-io-base</artifactId>

java-io-bom/pom.xml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<parent>
88
<artifactId>java-io-parent</artifactId>
99
<groupId>com.github.nbbrd.java-io-util</groupId>
10-
<version>0.0.27</version>
10+
<version>0.0.28</version>
1111
</parent>
1212

1313
<artifactId>java-io-bom</artifactId>
@@ -62,7 +62,7 @@
6262
<plugin>
6363
<groupId>org.codehaus.mojo</groupId>
6464
<artifactId>flatten-maven-plugin</artifactId>
65-
<version>1.5.0</version>
65+
<version>1.6.0</version>
6666
<configuration>
6767
<flattenMode>bom</flattenMode>
6868
<outputDirectory>${project.build.directory}</outputDirectory>
@@ -106,7 +106,6 @@
106106
<plugin>
107107
<groupId>org.jreleaser</groupId>
108108
<artifactId>jreleaser-maven-plugin</artifactId>
109-
<version>1.10.0</version>
110109
<executions>
111110
<execution>
112111
<phase>install</phase>

java-io-curl/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<parent>
88
<groupId>com.github.nbbrd.java-io-util</groupId>
99
<artifactId>java-io-parent</artifactId>
10-
<version>0.0.27</version>
10+
<version>0.0.28</version>
1111
</parent>
1212

1313
<artifactId>java-io-curl</artifactId>

java-io-http/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<parent>
88
<groupId>com.github.nbbrd.java-io-util</groupId>
99
<artifactId>java-io-parent</artifactId>
10-
<version>0.0.27</version>
10+
<version>0.0.28</version>
1111
</parent>
1212

1313
<artifactId>java-io-http</artifactId>

java-io-http/src/main/java/nbbrd/io/http/URLQueryBuilder.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import java.net.MalformedURLException;
2626
import java.net.URL;
2727
import java.net.URLEncoder;
28-
import java.nio.charset.Charset;
2928
import java.nio.charset.StandardCharsets;
3029
import java.util.*;
3130

@@ -45,7 +44,6 @@ public final class URLQueryBuilder {
4544

4645
private boolean trailingSlash = false;
4746

48-
private final Charset encoding = StandardCharsets.UTF_8;
4947
private final List<String> paths = new ArrayList<>();
5048
private final Map<String, String> params = new LinkedHashMap<>();
5149

@@ -127,7 +125,7 @@ public String toString() {
127125
}
128126

129127
for (String path : paths) {
130-
result.append('/').append(encode(path, encoding));
128+
result.append('/').append(encode(path));
131129
}
132130

133131
if (trailingSlash) {
@@ -148,10 +146,10 @@ public String toString() {
148146
}
149147

150148
private void appendParam(StringBuilder result, Map.Entry<String, String> o) {
151-
result.append(encode(o.getKey(), encoding));
149+
result.append(encode(o.getKey()));
152150
String value = o.getValue();
153151
if (value != null) {
154-
result.append('=').append(encode(value, encoding));
152+
result.append('=').append(encode(value));
155153
}
156154
}
157155

@@ -167,9 +165,9 @@ public URL build() throws MalformedURLException {
167165
return new URL(toString());
168166
}
169167

170-
private static String encode(String s, Charset charset) {
168+
private static String encode(String s) {
171169
try {
172-
return URLEncoder.encode(s, charset.name());
170+
return URLEncoder.encode(s, StandardCharsets.UTF_8.name()).replace("+", "%20");
173171
} catch (UnsupportedEncodingException ex) {
174172
throw new UncheckedIOException(ex);
175173
}

java-io-http/src/test/java/nbbrd/io/http/URLQueryBuilderTest.java

Lines changed: 87 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,14 @@
1818

1919
import nbbrd.io.text.Parser;
2020
import org.junit.jupiter.api.Test;
21+
import wiremock.org.apache.hc.core5.net.URIBuilder;
2122

2223
import java.io.IOException;
24+
import java.net.MalformedURLException;
25+
import java.net.URISyntaxException;
2326
import java.net.URL;
2427
import java.util.List;
28+
import java.util.function.Consumer;
2529

2630
import static java.util.Arrays.asList;
2731
import static nbbrd.io.http.URLQueryBuilder.of;
@@ -39,87 +43,141 @@ public void testFactory() throws IOException {
3943
assertThatNullPointerException()
4044
.isThrownBy(() -> of(null));
4145

42-
assertThat(of(withoutTrailingSlash))
46+
assertThat(of(urlWithoutSlash).build())
47+
.isEqualTo(uriBuilderAsURL(urlWithoutSlash, DO_NOTHING))
4348
.hasToString("http://localhost");
4449

45-
assertThat(of(withTrailingSlash))
50+
assertThat(of(urlWithSlash).build())
51+
.isEqualTo(uriBuilderAsURL(urlWithSlash, DO_NOTHING))
4652
.hasToString("http://localhost/");
4753
}
4854

4955
@SuppressWarnings("DataFlowIssue")
5056
@Test
5157
public void testPath() throws IOException {
5258
assertThatNullPointerException()
53-
.isThrownBy(() -> of(withoutTrailingSlash).path((String) null));
59+
.isThrownBy(() -> of(urlWithoutSlash).path((String) null));
5460

55-
assertThatNullPointerException()
56-
.isThrownBy(() -> of(withoutTrailingSlash).path((List<String>) null));
57-
58-
assertThat(of(withoutTrailingSlash).path("hello").path("").path("worl/d").build())
61+
assertThat(of(urlWithoutSlash).path("hello").path("").path("worl/d").build())
62+
.isEqualTo(uriBuilderAsURL(urlWithoutSlash, o -> o.appendPathSegments("hello", "", "worl/d")))
5963
.hasToString("http://localhost/hello//worl%2Fd");
6064

61-
assertThat(of(withTrailingSlash).path("hello").path("").path("worl/d").build())
65+
assertThat(of(urlWithSlash).path("hello").path("").path("worl/d").build())
66+
.isNotEqualTo(uriBuilderAsURL(urlWithSlash, o -> o.appendPathSegments("hello", "", "worl/d")))
6267
.hasToString("http://localhost/hello//worl%2Fd");
68+
}
69+
70+
@SuppressWarnings("DataFlowIssue")
71+
@Test
72+
public void testPathList() throws IOException {
73+
assertThatNullPointerException()
74+
.isThrownBy(() -> of(urlWithoutSlash).path((List<String>) null));
6375

64-
assertThat(of(withoutTrailingSlash).path(asList("hello", "", "worl/d")).build())
76+
assertThat(of(urlWithoutSlash).path(asList("hello", "", "worl/d")).build())
77+
.isEqualTo(uriBuilderAsURL(urlWithoutSlash, o -> o.appendPathSegments("hello", "", "worl/d")))
6578
.hasToString("http://localhost/hello//worl%2Fd");
6679

67-
assertThat(of(withTrailingSlash).path(asList("hello", "", "worl/d")).build())
80+
assertThat(of(urlWithSlash).path(asList("hello", "", "worl/d")).build())
81+
.isNotEqualTo(uriBuilderAsURL(urlWithSlash, o -> o.appendPathSegments("hello", "", "worl/d")))
6882
.hasToString("http://localhost/hello//worl%2Fd");
6983
}
7084

7185
@Test
7286
@SuppressWarnings({"DataFlowIssue"})
7387
public void testParam() throws IOException {
7488
assertThatNullPointerException()
75-
.isThrownBy(() -> of(withoutTrailingSlash).param(null, ""));
89+
.isThrownBy(() -> of(urlWithoutSlash).param(null, ""));
7690

7791
assertThatNullPointerException()
78-
.isThrownBy(() -> of(withoutTrailingSlash).param("", null));
92+
.isThrownBy(() -> of(urlWithoutSlash).param("", null));
7993

80-
assertThatNullPointerException()
81-
.isThrownBy(() -> of(withoutTrailingSlash).param(null));
82-
83-
assertThat(of(withoutTrailingSlash).param("p1", "v1").param("p&=2", "v&=2").build())
94+
assertThat(of(urlWithoutSlash).param("p1", "v1").param("p&=2", "v&=2").build())
95+
.isEqualTo(uriBuilderAsURL(urlWithoutSlash, o -> o.addParameter("p1", "v1").addParameter("p&=2", "v&=2")))
8496
.hasToString("http://localhost?p1=v1&p%26%3D2=v%26%3D2");
8597

86-
assertThat(of(withTrailingSlash).param("p1", "v1").param("p&=2", "v&=2").build())
98+
assertThat(of(urlWithSlash).param("p1", "v1").param("p&=2", "v&=2").build())
99+
.isEqualTo(uriBuilderAsURL(urlWithSlash, o -> o.addParameter("p1", "v1").addParameter("p&=2", "v&=2")))
87100
.hasToString("http://localhost/?p1=v1&p%26%3D2=v%26%3D2");
88101

89-
assertThat(of(withoutTrailingSlash).path("hello").path("worl/d").param("p1", "v1").param("p&=2", "v&=2").build())
102+
assertThat(of(urlWithoutSlash).path("hello").path("worl/d").param("p1", "v1").param("p&=2", "v&=2").build())
103+
.isEqualTo(uriBuilderAsURL(urlWithoutSlash, o -> o.appendPathSegments("hello", "worl/d").addParameter("p1", "v1").addParameter("p&=2", "v&=2")))
90104
.hasToString("http://localhost/hello/worl%2Fd?p1=v1&p%26%3D2=v%26%3D2");
91105

92-
assertThat(of(withTrailingSlash).path("hello").path("worl/d").param("p1", "v1").param("p&=2", "v&=2").build())
106+
assertThat(of(urlWithSlash).path("hello").path("worl/d").param("p1", "v1").param("p&=2", "v&=2").build())
107+
.isNotEqualTo(uriBuilderAsURL(urlWithSlash, o -> o.appendPathSegments("hello", "worl/d").addParameter("p1", "v1").addParameter("p&=2", "v&=2")))
93108
.hasToString("http://localhost/hello/worl%2Fd?p1=v1&p%26%3D2=v%26%3D2");
94109

95-
assertThat(of(withoutTrailingSlash).param("b", "2").param("a", "1").build())
110+
assertThat(of(urlWithoutSlash).param("b", "2").param("a", "1").build())
111+
.isEqualTo(uriBuilderAsURL(urlWithoutSlash, o -> o.addParameter("b", "2").addParameter("a", "1")))
96112
.hasToString("http://localhost?b=2&a=1");
97113

98-
assertThat(of(withTrailingSlash).param("b", "2").param("a", "1").build())
114+
assertThat(of(urlWithSlash).param("b", "2").param("a", "1").build())
115+
.isEqualTo(uriBuilderAsURL(urlWithSlash, o -> o.addParameter("b", "2").addParameter("a", "1")))
99116
.hasToString("http://localhost/?b=2&a=1");
117+
}
100118

101-
assertThat(of(withoutTrailingSlash).param("b").param("a").build())
119+
@Test
120+
@SuppressWarnings({"DataFlowIssue"})
121+
public void testParamWithoutValue() throws IOException {
122+
assertThatNullPointerException()
123+
.isThrownBy(() -> of(urlWithoutSlash).param(null));
124+
125+
assertThat(of(urlWithoutSlash).param("b").param("a").build())
126+
.isEqualTo(uriBuilderAsURL(urlWithoutSlash, o -> o.addParameter("b", null).addParameter("a", null)))
102127
.hasToString("http://localhost?b&a");
103128

104-
assertThat(of(withTrailingSlash).param("b").param("a").build())
129+
assertThat(of(urlWithSlash).param("b").param("a").build())
130+
.isEqualTo(uriBuilderAsURL(urlWithSlash, o -> o.addParameter("b", null).addParameter("a", null)))
105131
.hasToString("http://localhost/?b&a");
106132
}
107133

108134
@Test
109135
public void testTrailingSlash() throws IOException {
110-
assertThat(of(withoutTrailingSlash).trailingSlash(true).build())
136+
assertThat(of(urlWithoutSlash).trailingSlash(true).build())
111137
.hasToString("http://localhost/");
112138

113-
assertThat(of(withoutTrailingSlash).trailingSlash(true).param("p1", "v1").param("p&=2", "v&=2").build())
139+
assertThat(of(urlWithoutSlash).trailingSlash(true).param("p1", "v1").param("p&=2", "v&=2").build())
114140
.hasToString("http://localhost/?p1=v1&p%26%3D2=v%26%3D2");
115141

116-
assertThat(of(withoutTrailingSlash).trailingSlash(true).path("hello").path("worl/d").build())
142+
assertThat(of(urlWithoutSlash).trailingSlash(true).path("hello").path("worl/d").build())
117143
.hasToString("http://localhost/hello/worl%2Fd/");
118144

119-
assertThat(of(withoutTrailingSlash).trailingSlash(true).path("hello").path("worl/d").param("p1", "v1").param("p&=2", "v&=2").build())
145+
assertThat(of(urlWithoutSlash).trailingSlash(true).path("hello").path("worl/d").param("p1", "v1").param("p&=2", "v&=2").build())
120146
.hasToString("http://localhost/hello/worl%2Fd/?p1=v1&p%26%3D2=v%26%3D2");
121147
}
122148

123-
private final URL withoutTrailingSlash = Parser.onURL().parseValue("http://localhost").orElseThrow(RuntimeException::new);
124-
private final URL withTrailingSlash = Parser.onURL().parseValue("http://localhost/").orElseThrow(RuntimeException::new);
149+
@Test
150+
public void testEncodingOfSpaceCharacter() throws IOException {
151+
assertThat(of(urlWithoutSlash).path("a b+").build())
152+
.isEqualTo(uriBuilderAsURL(urlWithoutSlash, o -> o.appendPathSegments("a b+")))
153+
.hasToString("http://localhost/a%20b%2B");
154+
155+
assertThat(of(urlWithSlash).path("a b+").build())
156+
.isNotEqualTo(uriBuilderAsURL(urlWithSlash, o -> o.appendPathSegments("a b+")))
157+
.hasToString("http://localhost/a%20b%2B");
158+
159+
assertThat(of(urlWithoutSlash).param("x y+", "a b+").build())
160+
.isEqualTo(uriBuilderAsURL(urlWithoutSlash, o -> o.addParameter("x y+","a b+")))
161+
.hasToString("http://localhost?x%20y%2B=a%20b%2B");
162+
163+
assertThat(of(urlWithSlash).param("x y+", "a b+").build())
164+
.isEqualTo(uriBuilderAsURL(urlWithSlash, o -> o.addParameter("x y+","a b+")))
165+
.hasToString("http://localhost/?x%20y%2B=a%20b%2B");
166+
}
167+
168+
private final URL urlWithoutSlash = Parser.onURL().parseValue("http://localhost").orElseThrow(RuntimeException::new);
169+
private final URL urlWithSlash = Parser.onURL().parseValue("http://localhost/").orElseThrow(RuntimeException::new);
170+
171+
private static URL uriBuilderAsURL(URL base, Consumer<? super URIBuilder> consumer) {
172+
try {
173+
URIBuilder builder = new URIBuilder(base.toURI());
174+
consumer.accept(builder);
175+
return builder.build().toURL();
176+
} catch (URISyntaxException | MalformedURLException ex) {
177+
throw new RuntimeException(ex);
178+
}
179+
}
180+
181+
private static final Consumer<Object> DO_NOTHING = ignore -> {
182+
};
125183
}

0 commit comments

Comments
 (0)