From c97e35377aea70cb293cabdd205bcc5da64b95c6 Mon Sep 17 00:00:00 2001
From: Sven Gothel
- * This constructor first creates a temporary Uri string from the given components. This
+ * This constructor first creates a temporary Uri string from the given unencoded components. This
* string will be parsed later on to create the Uri instance.
*
* {@code [scheme:]scheme-specific-part[#fragment]}
*
+ * {@code host} and {@code port} may be undefined or invalid within {@code scheme-specific-part}.
+ *
- * This constructor first creates a temporary Uri string from the given components. This
+ * This constructor first creates a temporary Uri string from the given encoded components. This
+ * string will be parsed later on to create the Uri instance.
+ *
+ * The given encoded components are taken as-is, i.e. no re-encoding will be performed!
+ * However, Uri parsing will re-evaluate encoding of the resulting components.
+ *
+ * {@code [scheme:]scheme-specific-part[#fragment]}
+ *
+ * {@code host} and {@code port} may be undefined or invalid within {@code scheme-specific-part}.
+ *
+ * This constructor first creates a temporary Uri string from the given unencoded components. This
* string will be parsed later on to create the Uri instance.
*
* {@code [scheme:][user-info@]host[:port][path][?query][#fragment]}
*
+ * {@code host} and {@code port} must be defined and valid, if any {@code authority} components are defined,
+ * i.e. {@code user-info}, {@code host} or {@code port}.
+ *
- * This constructor first creates a temporary Uri string from the given components. This
+ * This constructor first creates a temporary Uri string from the given encoded components. This
+ * string will be parsed later on to create the Uri instance.
+ *
+ * The given encoded components are taken as-is, i.e. no re-encoding will be performed!
+ * However, Uri parsing will re-evaluate encoding of the resulting components.
+ *
+ * {@code [scheme:][user-info@]host[:port][path][?query][#fragment]}
+ *
+ * {@code host} and {@code port} must be defined and valid, if any {@code authority} components are defined,
+ * i.e. {@code user-info}, {@code host} or {@code port}.
+ *
+ * This constructor first creates a temporary Uri string from the given unencoded components. This
* string will be parsed later on to create the Uri instance.
*
* {@code [scheme:]host[path][#fragment]}
*
+ * {@code host} must be valid, if defined.
+ *
- * This constructor first creates a temporary Uri string from the given components. This
+ * This constructor first creates a temporary Uri string from the given encoded components. This
+ * string will be parsed later on to create the Uri instance.
+ *
+ * The given encoded components are taken as-is, i.e. no re-encoding will be performed!
+ * However, Uri parsing will re-evaluate encoding of the resulting components.
+ *
+ * {@code [scheme:]host[path][#fragment]}
+ *
+ * {@code host} must be valid, if defined.
+ *
+ * This constructor first creates a temporary Uri string from the given unencoded components. This
* string will be parsed later on to create the Uri instance.
*
* {@code [scheme:][//authority][path][?query][#fragment]}
*
+ * {@code host} and {@code port} may be undefined or invalid, in the optional {@code authority}.
+ *
+ * This constructor first creates a temporary Uri string from the given encoded encoded components. This
+ * string will be parsed later on to create the Uri instance.
+ *
+ * The given encoded components are taken as-is, i.e. no re-encoding will be performed!
+ * However, Uri parsing will re-evaluate encoding of the resulting components.
+ *
+ * {@code [scheme:][//authority][path][?query][#fragment]}
+ *
+ * {@code host} and {@code port} may be undefined or invalid, in the optional {@code authority}.
+ *
scheme2:/some/path/gluegen-rt.jar#fragment
*
* Example 3:
- * This instance: scheme1:scheme2:/some/path/gluegen-rt.jar?lala=01#fragment
+ * This instance: scheme1:scheme2:/some/path/gluegen-rt.jar!/?lala=01#fragment
* Returned Uri: scheme2:/some/path/gluegen-rt.jar?lala=01#fragment
*
- * @throws URISyntaxException
+ * @throws URISyntaxException if this Uri is a container Uri and does not comply with the container spec, i.e. a JAR Uri
*/
public final Uri getContainedUri() throws URISyntaxException {
if( !emptyString(schemeSpecificPart) ) {
@@ -1185,6 +1408,7 @@ public class Uri {
} catch(final URISyntaxException e) {
// OK, does not contain uri
if( DEBUG ) {
+ System.err.println("Caught "+e.getClass().getSimpleName()+": "+e.getMessage());
e.printStackTrace();
}
}
@@ -1192,184 +1416,283 @@ public class Uri {
return null;
}
+ private static final boolean cutoffLastPathSegementImpl(final StringBuilder pathBuf,
+ final boolean cutoffFile,
+ final boolean cutoffDir,
+ final Encoded appendPath) throws URISyntaxException {
+ final boolean cleaned;
+ {// clean-up existing path
+ final String pathS = pathBuf.toString();
+ if( 0 > pathS.indexOf("/") && emptyString(appendPath) ) {
+ return false; // nothing to cut-off
+ }
+ pathBuf.setLength(0);
+ pathBuf.append( IOUtil.cleanPathString( pathS ) );
+ cleaned = pathBuf.length() != pathS.length();
+ }
+
+ {// cut-off file or last dir-segment
+ final String pathS = pathBuf.toString();
+ final int jarSepIdx = pathS.lastIndexOf(JAR_SCHEME_SEPARATOR);
+ final int e = pathS.lastIndexOf("/");
+ if( 0 > jarSepIdx || e - 1 > jarSepIdx ) { // stop at jar-separator '!/', if exist
+ if( cutoffFile && e < pathS.length() - 1 ) {
+ // cut-off file
+ pathBuf.setLength(0);
+ pathBuf.append( pathS.substring(0, e+1) );
+ } else if( cutoffDir ) {
+ // cut-off dir-segment
+ final int p = pathS.lastIndexOf("/", e-1);
+ if( p >= 0 ) {
+ pathBuf.setLength(0);
+ pathBuf.append( pathS.substring(0, p+1) );
+ } // else keep
+ } // else keep
+ }
+ final boolean cutoff = pathBuf.length() != pathS.length();
+ if( !cutoff && ( cutoffDir || !cleaned ) && emptyString(appendPath) ) {
+ return false; // no modifications!
+ }
+ }
+ if( !emptyString(appendPath) ) {
+ pathBuf.append(appendPath.get());
+ // 2nd round of cleaning!
+ final String pathS = pathBuf.toString();
+ pathBuf.setLength(0);
+ pathBuf.append( IOUtil.cleanPathString( pathS ) );
+ }
+ return true; // continue processing w/ buffer
+ }
+ private final Uri cutoffLastPathSegementImpl(final boolean cutoffFile, final boolean cutoffDir, final Encoded appendPath) throws URISyntaxException {
+ if( opaque ) {
+ if( emptyString(schemeSpecificPart) ) {
+ // nothing to cut-off
+ if( !emptyString(appendPath) ) {
+ return Uri.create(scheme, appendPath, fragment);
+ } else {
+ return null;
+ }
+ }
+ final StringBuilder sspBuf = new StringBuilder(); // without path!
+
+ // save optional query in scheme-specific-part
+ final Encoded queryTemp;
+ final int queryI = schemeSpecificPart.lastIndexOf(QUERY_SEPARATOR);
+ if( queryI >= 0 ) {
+ queryTemp = schemeSpecificPart.substring(queryI+1);
+ sspBuf.append( schemeSpecificPart.substring(0, queryI).get() );
+ } else {
+ queryTemp = null;
+ sspBuf.append( schemeSpecificPart.get() );
+ }
+
+ if( !cutoffLastPathSegementImpl(sspBuf, cutoffFile, cutoffDir, appendPath) ) {
+ return null; // no modifications
+ }
+
+ if ( !emptyString(queryTemp) ) {
+ sspBuf.append(QUERY_SEPARATOR);
+ sspBuf.append( queryTemp.get() );
+ }
+
+ // without host validation if authority is defined
+ return Uri.create(scheme, new Encoded(sspBuf.toString()), fragment);
+ } else {
+ if( emptyString(path) ) {
+ return null; // nothing to cut-off
+ }
+ final StringBuilder pathBuf = new StringBuilder();
+ pathBuf.append( path.get() );
+
+ if( !cutoffLastPathSegementImpl(pathBuf, cutoffFile, cutoffDir, appendPath) ) {
+ return null; // no modifications
+ }
+
+ // with host validation if authority is defined
+ return Uri.create(scheme, userInfo, host, port, new Encoded(pathBuf.toString()), query, fragment);
+ }
+ }
/**
- * Return a new Uri instance representing the parent path of this Uri,
- * while cutting of optional {@code query} and {@code fragment} parts.
- * - * Method is {@code jar-file-entry} aware, i.e. will return the parent entry if exists. - *
+ * {@link IOUtil#cleanPathString(String) Normalizes} this Uri's path and return the + * {@link IOUtil#cleanPathString(String) normalized} form if it differs, otherwise {@code this} instance. *- * If this Uri does not contain any path separator, or a parent folder Uri cannot be found, method returns {@code null}. - *
** Example-1: - * This instance :+ * */ - public final Uri getParent() { - final int pl = null!=schemeSpecificPart? schemeSpecificPart.length() : 0; - if(pl != 0) { - final int e = schemeSpecificPart.lastIndexOf("/"); - if( e > 0 ) { // 0 == e: no path - if( e < pl - 1 ) { - // path is file or has a query - try { - return new Uri( new Encoded( scheme.get()+SCHEME_SEPARATOR+schemeSpecificPart.get().substring(0, e+1) ) ); - } catch (final URISyntaxException ue) { - // not complete, hence removed authority, or even root folder -> return null - } - } - // path is a directory .. - final int p = schemeSpecificPart.lastIndexOf("/", e-1); - if( p > 0 ) { - try { - return new Uri( new Encoded( scheme.get()+SCHEME_SEPARATOR+schemeSpecificPart.get().substring(0, p+1) ) ); - } catch (final URISyntaxException ue) { - // not complete, hence removed authority, or even root folder -> return null - } - } + public final Uri getNormalized() { + try { + final Uri res = cutoffLastPathSegementImpl(false, false, null); + return null != res ? res : this; + } catch (final URISyntaxException e) { + if( DEBUG ) { + System.err.println("Caught "+e.getClass().getSimpleName()+": "+e.getMessage()); + e.printStackTrace(); } - } - return null; - } - - /** - * Concatenates the given encoded string to the {@link #getEncoded() encoded uri} - * of this instance and returns {@link #Uri(Encoded) a new Uri instance} with the result. - * - * @throws URISyntaxException - * if the concatenated string {@code uri} doesn't fit to the - * specification RFC2396 and RFC3986 or could not be parsed correctly. - */ - public final Uri concat(final Uri.Encoded suffix) throws URISyntaxException { - if( null == suffix ) { return this; - } else { - return new Uri( input.concat(suffix) ); } } /** - * Returns a new Uri instance w/ the given new query {@code newQuery}. + * Returns this Uri's directory Uri. + *jar:http://some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class
- * Returned Uri #1:jar:http://some/path/gluegen-rt.jar!/com/jogamp/common/
- * Returned Uri #2:jar:http://some/path/gluegen-rt.jar!/com/jogamp/
+ * This instance :jar:http://some/path/../gluegen-rt.jar!/com/Test.class?arg=1#frag
+ * Normalized :jar:http://some/gluegen-rt.jar!/com/Test.class?arg=1#frag
* * Example-2: - * This instance :http://some/path/gluegen-rt.jar
- * Returned Uri #1:http://some/path/
- * Returned Uri #2:http://some/
+ * This instance :http://some/path/../gluegen-rt.jar?arg=1#frag
+ * Normalized :http://some/gluegen-rt.jar?arg=1#frag
*
+ * This Uri path will be {@link IOUtil#cleanPathString(String) normalized} before returning the directory. + *
+ *+ * If this Uri's directory cannot be found, or already denotes a directory, method returns {@code this} instance. + *
+ *+ *
+ * Example-1: + * this-uri: http:/some/path/gluegen-rt.jar?arg=1#frag + * result: http:/some/path/?arg=1#frag * - * @throws URISyntaxException if this Uri is {@link #opaque} - * or if the new string {@code uri} doesn't fit to the - * specification RFC2396 and RFC3986 or could not be parsed correctly. + * Example-2: + * this-uri: file:/some/path/ + * result: file:/some/path/ + * + * Example-3: + * this-uri: file:/some/path/lala/lili/../../hello.txt + * result: file:/some/path/ + *+ * + * @throws URISyntaxException if the new string {@code uri} doesn't fit to the + * specification RFC2396 and RFC3986 or could not be parsed correctly. */ - public final Uri getNewQuery(final String newQuery) throws URISyntaxException { - if( opaque ) { - throw new URISyntaxException(input.decode(), "Opaque Uri cannot permute by query"); - } else if( null != host ) { - // with host validation - return Uri.create(decode(scheme), decode(userInfo), decode(host), port, - decode(path), newQuery, decode(fragment)); - } else { - // without host validation - return Uri.create(decode(scheme), decode(authority), - decode(path), newQuery, decode(fragment)); + public Uri getDirectory() { + try { + final Uri res = cutoffLastPathSegementImpl(true, false, null); + return null != res ? res : this; + } catch (final URISyntaxException e) { + if( DEBUG ) { + System.err.println("Caught "+e.getClass().getSimpleName()+": "+e.getMessage()); + e.printStackTrace(); + } + return this; } } - /// NEW START - /** - * The URI's
protocol:/some/path/gluegen-rt.jar
- * parent dirname URI protocol:/some/path/
will be returned,
- * or {@code null} if not applicable.
+ * Returns this Uri's parent directory Uri..
* - * protocol may be "file", "http", etc.. + * This Uri path will be {@link IOUtil#cleanPathString(String) normalized} before traversing up one directory. *
+ *+ * If a parent folder cannot be found, method returns {@code null}. + *
+ *+ *
+ * Example-1: + * This instance :+ * */ - public Uri getDirectory() throws URISyntaxException { - final String uriS = input.get(); - - // from - // file:/some/path/gluegen-rt.jar _or_ rsrc:gluegen-rt.jar - // to - // file:/some/path/ _or_ rsrc: - int idx = uriS.lastIndexOf('/'); - if(0 > idx) { - // no abs-path, check for protocol terminator ':' - idx = uriS.lastIndexOf(':'); - if(0 > idx) { - throw new URISyntaxException(input.get(), "no scheme terminator ':'"); - } - } + public final Uri getParent() { try { - return Uri.cast(uriS.substring(0, idx+1)); // exclude jar name, include terminal '/' or ':' - } catch (final URISyntaxException ue) { + return cutoffLastPathSegementImpl(true, true, null); + } catch (final URISyntaxException e) { if( DEBUG ) { - ue.printStackTrace(); + System.err.println("Caught "+e.getClass().getSimpleName()+": "+e.getMessage()); + e.printStackTrace(); } + return null; } - return null; } /** - * Generates a URI for the relativePath relative to the baseURI, - * hence the result is a absolute location. + * Returns a new Uri appending the given {@code appendPath} + * to this instance's {@link #getDirectory() directory}. *jar:http://some/path/gluegen-rt.jar!/com/Test.class?arg=1#frag
+ * Returned Uri #1:jar:http://some/path/gluegen-rt.jar!/com/?arg=1#frag
+ * Returned Uri #2:jar:http://some/path/gluegen-rt.jar!/?arg=1#frag
+ * Returned Uri #3:null
* - * @return "protocol:/some/path/" - * @throws IllegalArgumentException if the URI doesn't match the expected formatting, or is null - * @throws URISyntaxException + * Example-2: + * This instance :http://some/path/gluegen-rt.jar?arg=1#frag
+ * Returned Uri #1:http://some/path/?arg=1#frag
+ * Returned Uri #2:http://some/?arg=1#frag
+ * Returned Uri #2:null
+ * + * Example-3: + * This instance :http://some/path/../gluegen-rt.jar?arg=1#frag
+ * Returned Uri #1:http://some/?arg=1#frag
+ * Returned Uri #2:null
+ *
- * Impl. operates on the scheme-specific-part, and hence is sub-protocol savvy. + * If {@code appendPath} is empty, method behaves like {@link #getNormalized()}. *
*- * In case baseURI is not a path ending w/ '/', it's a assumed to be a file and it's parent is being used. + * This resulting path will be {@link IOUtil#cleanPathString(String) normalized}. *
+ *+ *
+ * Example-1:
+ * append: null
+ * this-uri: http:/some/path/gluegen-rt.jar
+ * result: http:/some/path/gluegen-rt.jar
*
- * @param baseURI denotes a URI to a directory ending w/ '/', or a file. In the latter case the file's directory is being used.
- * @param relativePath denotes a relative file to the baseLocation's parent directory (URI encoded)
- * @throws URISyntaxException if path is empty or has no parent directory available while resolving ../
+ * Example-2:
+ * append: test.txt
+ * this-uri: file:/some/path/gluegen-rt.jar
+ * result: file:/some/path/test.txt
+ *
+ * Example-3:
+ * append: test.txt
+ * this-uri: file:/some/path/lala/lili/../../hello.txt
+ * result: file:/some/path/test.txt
+ *
+ *
+ *
+ * @param appendPath denotes a relative path to be appended to this Uri's directory
+ * @throws URISyntaxException
+ * if the resulting {@code uri} doesn't fit to the
+ * specification RFC2396 and RFC3986 or could not be parsed correctly.
*/
- public Uri getRelativeOf(final Encoded relativePath) throws URISyntaxException {
- return compose(scheme, schemeSpecificPart, relativePath, fragment);
+ public Uri getRelativeOf(final Encoded appendPath) throws URISyntaxException {
+ if( emptyString(appendPath) ) {
+ return getNormalized();
+ } else {
+ return cutoffLastPathSegementImpl(true, false, appendPath);
+ }
}
- static Uri compose(final Encoded scheme, final Encoded schemeSpecificPart, final Encoded relativePath, final Encoded fragment) throws URISyntaxException {
- String schemeSpecificPartS = schemeSpecificPart.get();
-
- // cut off optional query in scheme-specific-part
- final String query;
- final int queryI = schemeSpecificPartS.lastIndexOf(QUERY_SEPARATOR);
- if( queryI >= 0 ) {
- query = schemeSpecificPartS.substring(queryI+1);
- schemeSpecificPartS = schemeSpecificPartS.substring(0, queryI);
+ /**
+ * Concatenates the given encoded string to the {@link #getEncoded() encoded uri}
+ * of this instance and returns {@link #Uri(Encoded) a new Uri instance} with the result.
+ *
+ * @throws URISyntaxException
+ * if the concatenated string {@code uri} doesn't fit to the
+ * specification RFC2396 and RFC3986 or could not be parsed correctly.
+ */
+ public final Uri concat(final Encoded suffix) throws URISyntaxException {
+ if( null == suffix ) {
+ return this;
} else {
- query = null;
- }
- if( null != relativePath ) {
- if( !schemeSpecificPartS.endsWith("/") ) {
- schemeSpecificPartS = IOUtil.getParentOf(schemeSpecificPartS);
- }
- schemeSpecificPartS = schemeSpecificPartS + relativePath.get();
- }
- schemeSpecificPartS = IOUtil.cleanPathString( schemeSpecificPartS );
- final StringBuilder uri = new StringBuilder();
- uri.append(scheme.get());
- uri.append(':');
- uri.append(schemeSpecificPartS);
- if ( null != query ) {
- uri.append(QUERY_SEPARATOR);
- uri.append(query);
- }
- if ( null != fragment ) {
- uri.append(FRAGMENT_SEPARATOR);
- uri.append(fragment.get());
+ return new Uri( input.concat(suffix) );
}
- return Uri.cast(uri.toString());
}
- /// NEW END
+ /**
+ * Returns a new Uri instance w/ the given new query {@code newQuery}.
+ *
+ * @throws URISyntaxException if this Uri is {@link #opaque}
+ * or if the new string {@code uri} doesn't fit to the
+ * specification RFC2396 and RFC3986 or could not be parsed correctly.
+ */
+ public final Uri getNewQuery(final Encoded newQuery) throws URISyntaxException {
+ if( opaque ) {
+ throw new URISyntaxException(input.decode(), "Opaque Uri cannot permute by query");
+ } else {
+ // with host validation if authority is defined
+ return Uri.create(scheme, userInfo, host, port, path, newQuery, fragment);
+ }
+ }
/**
* {@inheritDoc}
diff --git a/src/java/com/jogamp/common/net/UriQueryProps.java b/src/java/com/jogamp/common/net/UriQueryProps.java
index 8d9bcb4..a93a1eb 100644
--- a/src/java/com/jogamp/common/net/UriQueryProps.java
+++ b/src/java/com/jogamp/common/net/UriQueryProps.java
@@ -63,14 +63,14 @@ public class UriQueryProps {
public final Map