Skip to content

Commit 671f5a4

Browse files
authored
Merge pull request #77 from filip26/issue/didurl
Fix DidUrl and Did implementation
2 parents 4afe1c1 + 50cd639 commit 671f5a4

5 files changed

Lines changed: 441 additions & 75 deletions

File tree

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<groupId>com.apicatalog</groupId>
88
<artifactId>carbon-did</artifactId>
99

10-
<version>0.5.0</version>
10+
<version>0.6.0</version>
1111
<packaging>jar</packaging>
1212

1313
<url>https://github.com/filip26/carbon-decentralized-identifiers</url>

src/main/java/com/apicatalog/did/Did.java

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,20 @@
88

99
public class Did implements Serializable {
1010

11-
private static final long serialVersionUID = -3127318269811273712L;
11+
private static final long serialVersionUID = 8800667526627004412L;
1212

1313
/*
1414
* method-char = %x61-7A / DIGIT
1515
*/
1616
static final IntPredicate METHOD_CHAR = ch -> (0x61 <= ch && ch <= 0x7A) || ('0' <= ch && ch <= '9');
1717

18+
/*
19+
* idchar = ALPHA / DIGIT / "." / "-" / "_" / pct-encoded
20+
*/
21+
static final IntPredicate ID_CHAR = ch -> Character.isLetter(ch) || Character.isDigit(ch) || ch == '.' || ch == '-' || ch == '_';
22+
1823
public static final String SCHEME = "did";
19-
24+
2025
protected final String method;
2126
protected final String specificId;
2227

@@ -43,7 +48,9 @@ public static boolean isDid(final URI uri) {
4348
&& parts[0].length() > 0
4449
&& parts[1].length() > 0
4550
&& parts[0].codePoints().allMatch(METHOD_CHAR)
46-
;
51+
// FIXME does not validate pct-encoded correctly
52+
&& parts[1].codePoints().allMatch(ID_CHAR.or(ch -> ch == ':' || ch == '%'));
53+
4754
}
4855

4956
public static boolean isDid(final String uri) {
@@ -59,7 +66,8 @@ public static boolean isDid(final String uri) {
5966
&& parts[1].length() > 0
6067
&& parts[2].length() > 0
6168
&& parts[1].codePoints().allMatch(METHOD_CHAR)
62-
;
69+
// FIXME does not validate pct-encoded correctly
70+
&& parts[2].codePoints().allMatch(ID_CHAR.or(ch -> ch == ':' || ch == '%'));
6371
}
6472

6573
/**
@@ -81,8 +89,18 @@ public static Did from(final URI uri) {
8189
if (!Did.SCHEME.equalsIgnoreCase(uri.getScheme())) {
8290
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, must start with 'did:' prefix.");
8391
}
92+
93+
if (uri.getFragment() != null) {
94+
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, must be in form 'did:method:method-specific-id'.");
95+
}
96+
97+
final String[] parts = uri.getSchemeSpecificPart().split(":", 2);
98+
99+
if (parts.length != 2) {
100+
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, must be in form 'did:method:method-specific-id'.");
101+
}
84102

85-
return from(uri, uri.getSchemeSpecificPart().split(":", 2));
103+
return from(uri, parts[0], parts[1]);
86104
}
87105

88106
/**
@@ -110,26 +128,26 @@ public static Did from(final String uri) {
110128
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, must start with 'did:' prefix.");
111129
}
112130

113-
return from(uri, new String[] { parts[1], parts[2] });
131+
return from(uri, parts[1], parts[2]);
114132
}
115133

116-
protected static Did from(final Object uri, final String[] parts) {
117-
if (parts.length != 2) {
118-
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, must be in form 'did:method:method-specific-id'.");
119-
}
120-
134+
protected static Did from(final Object uri, final String method, final String specificId) {
121135
// check method
122-
if (parts[0].length() == 0
123-
|| !parts[0].codePoints().allMatch(METHOD_CHAR)) {
124-
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, method [" + parts[0] + "] syntax is blank or invalid.");
136+
if (method == null
137+
|| method.length() == 0
138+
|| !method.codePoints().allMatch(METHOD_CHAR)) {
139+
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, method [" + method + "] syntax is blank or invalid.");
125140
}
126-
141+
127142
// check method specific id
128-
if (parts[1].length() == 0) {
129-
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, method specific id [" + parts[1] + "] is blank.");
143+
if (specificId == null
144+
|| specificId.length() == 0
145+
// FIXME does not validate pct-encoded correctly
146+
|| !specificId.codePoints().allMatch(ID_CHAR.or(ch -> ch == ':' || ch == '%'))) {
147+
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, method specific id [" + specificId + "] is blank.");
130148
}
131149

132-
return new Did(parts[0], parts[1]);
150+
return new Did(method, specificId);
133151
}
134152

135153
public String getMethod() {

src/main/java/com/apicatalog/did/DidUrl.java

Lines changed: 115 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
package com.apicatalog.did;
22

3-
import java.net.MalformedURLException;
43
import java.net.URI;
54
import java.net.URISyntaxException;
6-
import java.net.URL;
75
import java.util.Objects;
86

97
public class DidUrl extends Did {
108

119
private static final long serialVersionUID = 5752880077497569763L;
12-
10+
1311
protected final String path;
1412
protected final String query;
1513
protected final String fragment;
@@ -27,49 +25,119 @@ public static DidUrl from(Did did, String path, String query, String fragment) {
2725

2826
public static DidUrl from(final URI uri) {
2927

30-
if (!isDidUrl(uri)) {
31-
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID URL, does not start with 'did:'.");
28+
if (uri == null) {
29+
throw new IllegalArgumentException("The DID URL must not be null.");
30+
}
31+
32+
if (!Did.SCHEME.equalsIgnoreCase(uri.getScheme())) {
33+
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID URL, must start with 'did:' prefix.");
34+
}
35+
36+
final String[] didParts = uri.getSchemeSpecificPart().split(":", 2);
37+
38+
if (didParts.length != 2) {
39+
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, must be in form 'did:method:method-specific-id'.");
40+
}
41+
42+
return from(uri, didParts[0], didParts[1], uri.getFragment());
43+
}
44+
45+
public static DidUrl from(final String uri) {
46+
47+
if (uri == null || uri.length() == 0) {
48+
throw new IllegalArgumentException("The DID must not be null or blank string.");
49+
}
50+
51+
final String[] parts = uri.split(":", 3);
52+
53+
if (parts.length != 3) {
54+
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, must be in form 'did:method:method-specific-id'.");
55+
}
56+
57+
if (!Did.SCHEME.equalsIgnoreCase(parts[0])) {
58+
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, must start with 'did:' prefix.");
3259
}
33-
34-
Did did = from(uri, uri.getSchemeSpecificPart().split(":", 2));
3560

36-
return new DidUrl(did, uri.getPath(), uri.getQuery(), uri.getFragment());
61+
String rest = parts[2];
62+
String fragment = null;
63+
64+
int fragmentIndex = rest.indexOf('#');
65+
if (fragmentIndex != -1) {
66+
fragment = rest.substring(fragmentIndex + 1);
67+
rest = rest.substring(0, fragmentIndex);
68+
}
69+
70+
return from(uri, parts[1], rest, fragment);
3771
}
3872

73+
protected static DidUrl from(Object uri, final String method, final String rest, final String fragment) {
74+
String specificId = rest;
75+
76+
String path = null;
77+
String query = null;
78+
79+
int urlPartIndex = specificId.indexOf('?');
80+
if (urlPartIndex != -1) {
81+
query = specificId.substring(urlPartIndex + 1);
82+
specificId = specificId.substring(0, urlPartIndex);
83+
}
84+
85+
urlPartIndex = specificId.indexOf('/');
86+
if (urlPartIndex != -1) {
87+
path = specificId.substring(urlPartIndex);
88+
specificId = specificId.substring(0, urlPartIndex);
89+
}
90+
91+
Did did = from(uri, method, specificId);
92+
93+
return new DidUrl(did, path, query, fragment);
94+
}
3995

4096
public static boolean isDidUrl(final URI uri) {
41-
return Did.SCHEME.equals(uri.getScheme());
97+
if (!Did.SCHEME.equalsIgnoreCase(uri.getScheme())
98+
|| isBlank(uri.getSchemeSpecificPart())
99+
|| isNotBlank(uri.getAuthority())
100+
|| isNotBlank(uri.getUserInfo())
101+
|| isNotBlank(uri.getHost())) {
102+
return false;
103+
}
104+
105+
final String[] parts = uri.getSchemeSpecificPart().split(":", 2);
106+
107+
return parts.length == 2
108+
&& parts[0].length() > 0
109+
&& parts[1].length() > 0
110+
&& parts[0].codePoints().allMatch(METHOD_CHAR);
42111
}
43112

44113
public static boolean isDidUrl(final String uri) {
45-
if (Did.isBlank(uri)) {
114+
if (uri == null) {
46115
return false;
47116
}
48117

49-
final String[] parts = uri.split(":");
118+
final String[] parts = uri.split(":", 3);
50119

51-
return (parts.length == 3 || parts.length == 4)
120+
return parts.length == 3
52121
&& Did.SCHEME.equalsIgnoreCase(parts[0])
53-
;
122+
&& parts[1].length() > 0
123+
&& parts[2].length() > 0
124+
&& parts[1].codePoints().allMatch(METHOD_CHAR);
54125
}
55126

56127
@Override
57128
public URI toUri() {
58129
try {
59-
return new URI(SCHEME, method + ":" + specificId, path, query, fragment);
130+
return new URI(SCHEME,
131+
appendPathQuery(new StringBuilder()
132+
.append(method)
133+
.append(':')
134+
.append(specificId)).toString(),
135+
fragment);
60136
} catch (URISyntaxException e) {
61137
throw new IllegalStateException(e);
62138
}
63139
}
64140

65-
public URL toUrl() {
66-
try {
67-
return toUri().toURL();
68-
} catch (MalformedURLException e) {
69-
throw new IllegalStateException(e);
70-
}
71-
}
72-
73141
@Override
74142
public boolean isDidUrl() {
75143
return true;
@@ -84,28 +152,39 @@ public DidUrl asDidUrl() {
84152
public String toString() {
85153
final StringBuilder builder = new StringBuilder(super.toString());
86154

87-
if (Did.isNotBlank(path)) {
88-
if (path.charAt(0) != '/') {
89-
builder.append('/');
155+
appendPathQuery(builder);
156+
157+
if (fragment != null) {
158+
if (fragment.length() == 0 || fragment.charAt(0) != '#') {
159+
builder.append('#');
160+
}
161+
if (fragment.length() > 0) {
162+
builder.append(fragment);
90163
}
91-
builder.append(path);
92164
}
93165

94-
if (Did.isNotBlank(query)) {
95-
if (query.charAt(0) != '?') {
96-
builder.append('?');
166+
return builder.toString();
167+
}
168+
169+
protected StringBuilder appendPathQuery(final StringBuilder builder) {
170+
if (path != null) {
171+
if (path.length() == 0 || path.charAt(0) != '/') {
172+
builder.append('/');
173+
}
174+
if (path.length() > 0) {
175+
builder.append(path);
97176
}
98-
builder.append(query);
99177
}
100178

101-
if (Did.isNotBlank(fragment)) {
102-
if (fragment.charAt(0) != '#') {
103-
builder.append('#');
179+
if (query != null) {
180+
if (query.length() == 0 || query.charAt(0) != '?') {
181+
builder.append('?');
182+
}
183+
if (query.length() > 0) {
184+
builder.append(query);
104185
}
105-
builder.append(fragment);
106186
}
107-
108-
return builder.toString();
187+
return builder;
109188
}
110189

111190
@Override

0 commit comments

Comments
 (0)