Skip to content

Commit 003dded

Browse files
committed
Updates to handle more use cases cleanly
1 parent f8b043c commit 003dded

File tree

4 files changed

+100
-39
lines changed

4 files changed

+100
-39
lines changed

build.savant

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ logbackVersion = "1.4.14"
2929
slf4jVersion = "2.0.13"
3030
testngVersion = "7.8.0"
3131

32-
project(group: "org.primeframework", name: "prime-mvc", version: "4.25.0", licenses: ["ApacheV2_0"]) {
32+
project(group: "org.primeframework", name: "prime-mvc", version: "4.25.1", licenses: ["ApacheV2_0"]) {
3333
workflow {
3434
fetch {
3535
// Dependency resolution order:

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<groupId>org.primeframework</groupId>
77
<artifactId>prime-mvc</artifactId>
8-
<version>4.25.0</version>
8+
<version>4.25.1</version>
99
<packaging>jar</packaging>
1010

1111
<name>FusionAuth App</name>

src/main/java/org/primeframework/mvc/util/QueryStringBuilder.java

Lines changed: 55 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019-2023, Inversoft Inc., All Rights Reserved
2+
* Copyright (c) 2019-2024, Inversoft Inc., All Rights Reserved
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -49,51 +49,54 @@ public static QueryStringBuilder builder(String uri) {
4949
}
5050

5151
public QueryStringBuilder beginFragment() {
52-
sb.append("#");
53-
addSeparator = false;
52+
// If query string contains no terms, remove the question mark
53+
if (sb.toString().endsWith("?")) {
54+
sb.setLength(sb.length() - 1);
55+
}
56+
57+
if (sb.indexOf("#") == -1) {
58+
sb.append("#");
59+
addSeparator = false;
60+
} else {
61+
char lastChar = sb.charAt(sb.length() - 1);
62+
addSeparator = lastChar != '#' && lastChar != '&';
63+
}
5464
return this;
5565
}
5666

5767
public QueryStringBuilder beginQuery() {
58-
sb.append("?");
59-
addSeparator = false;
68+
if (sb.indexOf("#") != -1) {
69+
throw new IllegalStateException("You cannot add a query after a fragment");
70+
}
71+
72+
if (sb.indexOf("?") == -1) {
73+
sb.append("?");
74+
addSeparator = false;
75+
} else {
76+
char lastChar = sb.charAt(sb.length() - 1);
77+
addSeparator = lastChar != '?' && lastChar != '&';
78+
}
6079
return this;
6180
}
6281

6382
public String build() {
64-
if (uri.length() == 0) {
83+
if (uri.isEmpty()) {
6584
return sb.toString();
6685
}
6786

68-
// URL provided contains a ?, perhaps other parameters as well
69-
if (uri.indexOf("?") != -1) {
70-
if (sb.indexOf("?") == 0) {
71-
return uri + sb.substring(1);
72-
}
73-
return uri.append(sb).toString();
74-
} else {
75-
if (segments.size() > 0) {
76-
if (uri.lastIndexOf("/") != uri.length() - 1) {
77-
uri.append("/");
78-
}
79-
80-
uri.append(String.join("/", segments));
87+
if (segments.size() > 0) {
88+
if (uri.lastIndexOf("/") != uri.length() - 1) {
89+
uri.append("/");
8190
}
82-
}
8391

84-
if (sb.indexOf("?") == 0) {
85-
return uri.append(sb).toString();
92+
uri.append(String.join("/", segments));
8693
}
8794

88-
if (sb.indexOf("#") == 0) {
95+
if ((sb.indexOf("?") == 0 || sb.indexOf("#") == 0) && sb.length() > 1) {
8996
return uri.append(sb).toString();
9097
}
9198

92-
if (uri.indexOf("#") != -1 && sb.length() > 0) {
93-
return uri.append("&").append(sb).toString();
94-
}
95-
96-
if (sb.length() == 0) {
99+
if (sb.isEmpty() || sb.toString().equals("?") || sb.toString().equals("#")) {
97100
return uri.toString();
98101
}
99102

@@ -134,13 +137,26 @@ public QueryStringBuilder ifTrue(boolean test, Consumer<QueryStringBuilder> cons
134137
}
135138

136139
public QueryStringBuilder uri(String uri) {
137-
if (this.uri.length() > 0) {
140+
if (!this.uri.isEmpty()) {
138141
throw new IllegalStateException("Object has already been initialized with a URL");
139142
}
140143

141144
if (uri != null) {
142-
this.uri.append(uri);
143-
if (uri.contains("?") && !uri.endsWith("&")) {
145+
String del = null;
146+
if (uri.contains("?")) {
147+
del = "?";
148+
} else if (uri.contains("#")) {
149+
del = "#";
150+
}
151+
152+
if (del != null) {
153+
this.uri.append(uri, 0, uri.indexOf(del));
154+
this.sb.append(uri, uri.indexOf(del), uri.length());
155+
} else {
156+
this.uri.append(uri);
157+
}
158+
159+
if ((uri.contains("?") || uri.contains("#")) && !List.of('#', '?', '&').contains(uri.charAt(uri.length() - 1))) {
144160
addSeparator = true;
145161
}
146162
}
@@ -162,9 +178,7 @@ public QueryStringBuilder with(String name, Object value) {
162178
sb.append("&");
163179
}
164180

165-
sb.append(URLEncoder.encode(name, StandardCharsets.UTF_8))
166-
.append("=")
167-
.append(URLEncoder.encode(value.toString(), StandardCharsets.UTF_8));
181+
sb.append(URLEncoder.encode(name, StandardCharsets.UTF_8)).append("=").append(URLEncoder.encode(value.toString(), StandardCharsets.UTF_8));
168182

169183
addSeparator = true;
170184
return this;
@@ -175,8 +189,13 @@ public QueryStringBuilder withActual(String name) {
175189
}
176190

177191
public QueryStringBuilder withSegment(Object segment) {
178-
if (uri.length() > 0 && (uri.indexOf("?") == uri.length() - 1)) {
179-
throw new IllegalStateException("You cannot add a URL segment after you have appended a ? to the end of the URL");
192+
String message = "You cannot add a URL segment after you have appended a %s to the end of the URL";
193+
194+
if (!sb.isEmpty() && (sb.indexOf("?") != -1)) {
195+
throw new IllegalStateException(String.format(message, "?"));
196+
}
197+
if (!sb.isEmpty() && (sb.indexOf("#") != -1)) {
198+
throw new IllegalStateException(String.format(message, "#"));
180199
}
181200

182201
if (segment != null) {

src/test/java/org/primeframework/mvc/util/QueryStringBuilderTest.java

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019, Inversoft Inc., All Rights Reserved
2+
* Copyright (c) 2019-2024, Inversoft Inc., All Rights Reserved
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -73,6 +73,48 @@ public void testBuilder() {
7373
test("http://acme.com/", b -> b.withSegment("bar").beginQuery().with("foo", "bar").beginFragment().with("bing", "baz"), "http://acme.com/bar?foo=bar#bing=baz");
7474
test("http://acme.com/", b -> b.withSegment("bar").withSegment("baz").beginQuery().with("foo", "bar").beginFragment().with("bing", "baz"), "http://acme.com/bar/baz?foo=bar#bing=baz");
7575

76+
// URL with query term, adding another term
77+
test("http://acme.com?test=data", b -> b.with("foo", "bar"), "http://acme.com?test=data&foo=bar");
78+
test("http://acme.com?test=data", b -> b.beginQuery().with("foo", "bar"), "http://acme.com?test=data&foo=bar");
79+
80+
// URL with fragment, adding more fragment terms
81+
test("http://acme.com#frag", b -> b.with("foo", "bar"), "http://acme.com#frag&foo=bar");
82+
test("http://acme.com#frag", b -> b.with("foo", "bar").with("bing", "baz"), "http://acme.com#frag&foo=bar&bing=baz");
83+
test("http://acme.com#frag", b -> b.beginFragment().with("foo", "bar").with("bing", "baz"), "http://acme.com#frag&foo=bar&bing=baz");
84+
85+
// Should not include query since no terms were added before beginFragment
86+
test("http://acme.com", b -> b.beginQuery().beginFragment().with("foo", "bar"), "http://acme.com#foo=bar");
87+
88+
// URLs contain trailing control characters
89+
test("http://acme.com?", b -> b.with("foo", "bar"), "http://acme.com?foo=bar");
90+
test("http://acme.com#test&", b -> b.beginFragment().with("foo", "bar"), "http://acme.com#test&foo=bar");
91+
test("http://acme.com?test=data&", b -> b.beginQuery().with("foo", "bar"), "http://acme.com?test=data&foo=bar");
92+
93+
// Begin query and fragement but add no terms
94+
test("http://acme.com", QueryStringBuilder::beginQuery, "http://acme.com");
95+
test("http://acme.com", QueryStringBuilder::beginFragment, "http://acme.com");
96+
97+
try {
98+
test("http://acme.com?test=data", b -> b.withSegment("bar").with("foo", "bar"), "http://acme.com/bar?test=data&foo=bar");
99+
fail("Expected this to fail");
100+
} catch (Exception e) {
101+
assertEquals(e.getMessage(), "You cannot add a URL segment after you have appended a ? to the end of the URL");
102+
}
103+
104+
try {
105+
test("http://acme.com#frag", b -> b.withSegment("bar").with("foo", "bar"), "http://acme.com/bar#frag");
106+
fail("Expected this to fail");
107+
} catch (Exception e) {
108+
assertEquals(e.getMessage(), "You cannot add a URL segment after you have appended a # to the end of the URL");
109+
}
110+
111+
try {
112+
test("http://acme.com#frag", b -> b.beginQuery().with("foo", "bar"), "http://acme.com/bar");
113+
fail("Expected this to fail");
114+
} catch (Exception e) {
115+
assertEquals(e.getMessage(), "You cannot add a query after a fragment");
116+
}
117+
76118
// Expect to explode, if you leave a ? at the end of the initial URL you can't have any segments
77119
try {
78120
test("http://acme.com?", b -> b.withSegment("bar").with("foo", "bar"), "http://acme.com?foo=bar");

0 commit comments

Comments
 (0)