Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix DidUrl and Did implementation #77

Merged
merged 17 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<groupId>com.apicatalog</groupId>
<artifactId>carbon-did</artifactId>

<version>0.5.0</version>
<version>0.6.0</version>
<packaging>jar</packaging>

<url>https://github.com/filip26/carbon-decentralized-identifiers</url>
Expand Down
54 changes: 36 additions & 18 deletions src/main/java/com/apicatalog/did/Did.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,20 @@

public class Did implements Serializable {

private static final long serialVersionUID = -3127318269811273712L;
private static final long serialVersionUID = 8800667526627004412L;

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

/*
* idchar = ALPHA / DIGIT / "." / "-" / "_" / pct-encoded
*/
static final IntPredicate ID_CHAR = ch -> Character.isLetter(ch) || Character.isDigit(ch) || ch == '.' || ch == '-' || ch == '_';

public static final String SCHEME = "did";

protected final String method;
protected final String specificId;

Expand All @@ -43,7 +48,9 @@ public static boolean isDid(final URI uri) {
&& parts[0].length() > 0
&& parts[1].length() > 0
&& parts[0].codePoints().allMatch(METHOD_CHAR)
;
// FIXME does not validate pct-encoded correctly
&& parts[1].codePoints().allMatch(ID_CHAR.or(ch -> ch == ':' || ch == '%'));

}

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

/**
Expand All @@ -81,8 +89,18 @@ public static Did from(final URI uri) {
if (!Did.SCHEME.equalsIgnoreCase(uri.getScheme())) {
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, must start with 'did:' prefix.");
}

if (uri.getFragment() != null) {
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, must be in form 'did:method:method-specific-id'.");
}

final String[] parts = uri.getSchemeSpecificPart().split(":", 2);

if (parts.length != 2) {
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, must be in form 'did:method:method-specific-id'.");
}

return from(uri, uri.getSchemeSpecificPart().split(":", 2));
return from(uri, parts[0], parts[1]);
}

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

return from(uri, new String[] { parts[1], parts[2] });
return from(uri, parts[1], parts[2]);
}

protected static Did from(final Object uri, final String[] parts) {
if (parts.length != 2) {
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, must be in form 'did:method:method-specific-id'.");
}

protected static Did from(final Object uri, final String method, final String specificId) {
// check method
if (parts[0].length() == 0
|| !parts[0].codePoints().allMatch(METHOD_CHAR)) {
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, method [" + parts[0] + "] syntax is blank or invalid.");
if (method == null
|| method.length() == 0
|| !method.codePoints().allMatch(METHOD_CHAR)) {
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, method [" + method + "] syntax is blank or invalid.");
}

// check method specific id
if (parts[1].length() == 0) {
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, method specific id [" + parts[1] + "] is blank.");
if (specificId == null
|| specificId.length() == 0
// FIXME does not validate pct-encoded correctly
|| !specificId.codePoints().allMatch(ID_CHAR.or(ch -> ch == ':' || ch == '%'))) {
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, method specific id [" + specificId + "] is blank.");
}

return new Did(parts[0], parts[1]);
return new Did(method, specificId);
}

public String getMethod() {
Expand Down
151 changes: 115 additions & 36 deletions src/main/java/com/apicatalog/did/DidUrl.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package com.apicatalog.did;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Objects;

public class DidUrl extends Did {

private static final long serialVersionUID = 5752880077497569763L;

protected final String path;
protected final String query;
protected final String fragment;
Expand All @@ -27,49 +25,119 @@ public static DidUrl from(Did did, String path, String query, String fragment) {

public static DidUrl from(final URI uri) {

if (!isDidUrl(uri)) {
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID URL, does not start with 'did:'.");
if (uri == null) {
throw new IllegalArgumentException("The DID URL must not be null.");
}

if (!Did.SCHEME.equalsIgnoreCase(uri.getScheme())) {
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID URL, must start with 'did:' prefix.");
}

final String[] didParts = uri.getSchemeSpecificPart().split(":", 2);

if (didParts.length != 2) {
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, must be in form 'did:method:method-specific-id'.");
}

return from(uri, didParts[0], didParts[1], uri.getFragment());
}

public static DidUrl from(final String uri) {

if (uri == null || uri.length() == 0) {
throw new IllegalArgumentException("The DID must not be null or blank string.");
}

final String[] parts = uri.split(":", 3);

if (parts.length != 3) {
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, must be in form 'did:method:method-specific-id'.");
}

if (!Did.SCHEME.equalsIgnoreCase(parts[0])) {
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, must start with 'did:' prefix.");
}

Did did = from(uri, uri.getSchemeSpecificPart().split(":", 2));

return new DidUrl(did, uri.getPath(), uri.getQuery(), uri.getFragment());
String rest = parts[2];
String fragment = null;

int fragmentIndex = rest.indexOf('#');
if (fragmentIndex != -1) {
fragment = rest.substring(fragmentIndex + 1);
rest = rest.substring(0, fragmentIndex);
}

return from(uri, parts[1], rest, fragment);
}

protected static DidUrl from(Object uri, final String method, final String rest, final String fragment) {
String specificId = rest;

String path = null;
String query = null;

int urlPartIndex = specificId.indexOf('?');
if (urlPartIndex != -1) {
query = specificId.substring(urlPartIndex + 1);
specificId = specificId.substring(0, urlPartIndex);
}

urlPartIndex = specificId.indexOf('/');
if (urlPartIndex != -1) {
path = specificId.substring(urlPartIndex);
specificId = specificId.substring(0, urlPartIndex);
}

Did did = from(uri, method, specificId);

return new DidUrl(did, path, query, fragment);
}

public static boolean isDidUrl(final URI uri) {
return Did.SCHEME.equals(uri.getScheme());
if (!Did.SCHEME.equalsIgnoreCase(uri.getScheme())
|| isBlank(uri.getSchemeSpecificPart())
|| isNotBlank(uri.getAuthority())
|| isNotBlank(uri.getUserInfo())
|| isNotBlank(uri.getHost())) {
return false;
}

final String[] parts = uri.getSchemeSpecificPart().split(":", 2);

return parts.length == 2
&& parts[0].length() > 0
&& parts[1].length() > 0
&& parts[0].codePoints().allMatch(METHOD_CHAR);
}

public static boolean isDidUrl(final String uri) {
if (Did.isBlank(uri)) {
if (uri == null) {
return false;
}

final String[] parts = uri.split(":");
final String[] parts = uri.split(":", 3);

return (parts.length == 3 || parts.length == 4)
return parts.length == 3
&& Did.SCHEME.equalsIgnoreCase(parts[0])
;
&& parts[1].length() > 0
&& parts[2].length() > 0
&& parts[1].codePoints().allMatch(METHOD_CHAR);
}

@Override
public URI toUri() {
try {
return new URI(SCHEME, method + ":" + specificId, path, query, fragment);
return new URI(SCHEME,
appendPathQuery(new StringBuilder()
.append(method)
.append(':')
.append(specificId)).toString(),
fragment);
} catch (URISyntaxException e) {
throw new IllegalStateException(e);
}
}

public URL toUrl() {
try {
return toUri().toURL();
} catch (MalformedURLException e) {
throw new IllegalStateException(e);
}
}

@Override
public boolean isDidUrl() {
return true;
Expand All @@ -84,28 +152,39 @@ public DidUrl asDidUrl() {
public String toString() {
final StringBuilder builder = new StringBuilder(super.toString());

if (Did.isNotBlank(path)) {
if (path.charAt(0) != '/') {
builder.append('/');
appendPathQuery(builder);

if (fragment != null) {
if (fragment.length() == 0 || fragment.charAt(0) != '#') {
builder.append('#');
}
if (fragment.length() > 0) {
builder.append(fragment);
}
builder.append(path);
}

if (Did.isNotBlank(query)) {
if (query.charAt(0) != '?') {
builder.append('?');
return builder.toString();
}

protected StringBuilder appendPathQuery(final StringBuilder builder) {
if (path != null) {
if (path.length() == 0 || path.charAt(0) != '/') {
builder.append('/');
}
if (path.length() > 0) {
builder.append(path);
}
builder.append(query);
}

if (Did.isNotBlank(fragment)) {
if (fragment.charAt(0) != '#') {
builder.append('#');
if (query != null) {
if (query.length() == 0 || query.charAt(0) != '?') {
builder.append('?');
}
if (query.length() > 0) {
builder.append(query);
}
builder.append(fragment);
}

return builder.toString();
return builder;
}

@Override
Expand Down
Loading
Loading