| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.knox.gateway.util.urltemplate; |
| |
| import org.apache.knox.gateway.i18n.resources.ResourcesFactory; |
| |
| import java.net.URISyntaxException; |
| import java.util.StringTokenizer; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| //NOTE: Instances Not thread safe but reusable. Static parse method is thread safe. |
| //NOTE: Ignores matrix parameters at this point. |
| public class Parser { |
| |
| /* |
| ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? |
| 12 3 4 5 6 7 8 9 |
| |
| The numbers in the second line above are only to assist readability; |
| they indicate the reference points for each subexpression (i.e., each |
| paired parenthesis). We refer to the value matched for subexpression |
| <n> as $<n>. For example, matching the above expression to |
| |
| http://www.ics.uci.edu/pub/ietf/uri/#Related |
| |
| results in the following subexpression matches: |
| |
| $1 = http: |
| $2 = http |
| $3 = //www.ics.uci.edu |
| $4 = www.ics.uci.edu |
| $5 = /pub/ietf/uri/ |
| $6 = <undefined> |
| $7 = <undefined> |
| $8 = #Related |
| $9 = Related |
| |
| where <undefined> indicates that the component is not present, as is |
| the case for the query component in the above example. Therefore, we |
| can determine the value of the five components as |
| |
| scheme = $2 |
| authority = $4 |
| path = $5 |
| query = $7 |
| fragment = $9 |
| */ |
| |
| private static final Resources RES = ResourcesFactory.get( Resources.class ); |
| |
| public static final char TEMPLATE_OPEN_MARKUP = '{'; |
| public static final char TEMPLATE_CLOSE_MARKUP = '}'; |
| public static final char NAME_PATTERN_SEPARATOR = '='; |
| |
| private static final int MATCH_GROUP_SCHEME = 1; |
| private static final int MATCH_GROUP_SCHEME_NAKED = 2; |
| private static final int MATCH_GROUP_AUTHORITY = 3; |
| private static final int MATCH_GROUP_AUTHORITY_NAKED = 4; |
| private static final int MATCH_GROUP_PATH = 5; |
| private static final int MATCH_GROUP_QUERY = 6; |
| private static final int MATCH_GROUP_QUERY_NAKED = 7; |
| private static final int MATCH_GROUP_FRAGMENT = 8; |
| private static final int MATCH_GROUP_FRAGMENT_NAKED = 9; |
| |
| private static Pattern PATTERN = Pattern.compile( "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?" ); |
| |
| @Deprecated |
| public static Template parse( String template ) throws URISyntaxException { |
| return Parser.parseTemplate( template ); |
| } |
| |
| public static Template parseTemplate( final String template ) throws URISyntaxException { |
| Builder builder = new Builder( template ); |
| return parseInternal( builder ); |
| } |
| |
| public static Template parseLiteral( final String literal ) throws URISyntaxException { |
| Builder builder = new Builder( literal ); |
| builder.setLiteral( true ); |
| return parseInternal( builder ); |
| } |
| |
| private static Template parseInternal( final Builder builder ) throws URISyntaxException { |
| String original = builder.getOriginal(); |
| builder.setHasScheme( false ); |
| builder.setHasAuthority( false ); // Assume no until found otherwise. If true, will cause // in output URL. |
| builder.setIsAuthorityOnly( false ); |
| builder.setIsAbsolute( false ); // Assume relative until found otherwise. If true, will cause leading / in output URL. |
| builder.setIsDirectory( false ); // Assume a file path until found otherwise. If true, will cause trailing / in output URL. |
| builder.setHasQuery( false ); // Assume no ? until found otherwise. If true, will cause ? in output URL. |
| builder.setHasFragment( false ); // Assume no # until found otherwise. If true, will cause # in output URL. |
| Matcher match = PATTERN.matcher( original ); |
| if( match.matches() ) { |
| consumeSchemeMatch( builder, match ); |
| consumeAuthorityMatch( builder, match ); |
| consumePathMatch( builder, match ); |
| consumeQueryMatch( builder, match ); |
| consumeFragmentMatch( builder, match ); |
| fixNakedAuthority( builder ); |
| } else { |
| throw new URISyntaxException( original, RES.parseTemplateFailureReason( original ) ); |
| } |
| return builder.build(); |
| } |
| |
| private static void fixNakedAuthority( final Builder builder ) { |
| if( builder.getHasScheme() && |
| !builder.getHasAuthority() && |
| !builder.getIsAbsolute() && |
| !builder.getIsDirectory() && |
| ( builder.getPath().size() == 1 ) && |
| !builder.getHasQuery() && |
| !builder.getHasFragment() ) { |
| final Scheme scheme = builder.getScheme(); |
| builder.setHasScheme( false ); |
| builder.setHost( makeTokenSingular( scheme.getToken() ) ); |
| Path path = builder.getPath().remove( 0 ); |
| builder.setPort( makeTokenSingular( path.getToken() ) ); |
| builder.setIsAuthorityOnly( true ); |
| } |
| } |
| |
| private static Token makeTokenSingular( Token token ) { |
| final String effectivePattern = token.getEffectivePattern(); |
| if( Segment.GLOB_PATTERN.equals( effectivePattern ) ) { |
| token = new Token( token.getParameterName(), token.getOriginalPattern(), Segment.STAR_PATTERN, token.isLiteral() ); |
| } |
| return token; |
| } |
| |
| // private String makePatternSingular( String pattern ) { |
| // if( Segment.GLOB_PATTERN.equals( pattern ) ) { |
| // pattern = Segment.STAR_PATTERN; |
| // } |
| // return pattern; |
| // } |
| |
| private static void consumeSchemeMatch( final Builder builder, final Matcher match ) { |
| if( match.group( MATCH_GROUP_SCHEME ) != null ) { |
| builder.setHasScheme( true ); |
| consumeSchemeToken( builder, match.group( MATCH_GROUP_SCHEME_NAKED ) ); |
| } |
| } |
| |
| private static void consumeSchemeToken( final Builder builder, final String token ) { |
| if( token != null ) { |
| Token t = parseTemplateToken( builder, token, Segment.STAR_PATTERN ); |
| builder.setScheme( t ); |
| } |
| } |
| |
| private static void consumeAuthorityMatch( final Builder builder, final Matcher match ) { |
| if( match.group( MATCH_GROUP_AUTHORITY ) != null ) { |
| builder.setHasAuthority( true ); |
| consumeAuthorityToken( builder, match.group( MATCH_GROUP_AUTHORITY_NAKED ) ); |
| } |
| } |
| |
| private static void consumeAuthorityToken( final Builder builder, final String token ) { |
| if( token != null ) { |
| Token paramPattern; |
| String[] usernamePassword=null, hostPort; |
| String[] userAddr = split( token, '@' ); |
| if( userAddr.length == 1 ) { |
| hostPort = split( userAddr[ 0 ], ':' ); |
| } else { |
| usernamePassword = split( userAddr[ 0 ], ':' ); |
| hostPort = split( userAddr[ 1 ], ':' ); |
| } |
| if( usernamePassword != null ) { |
| if(!usernamePassword[0].isEmpty()) { |
| paramPattern = makeTokenSingular( parseTemplateToken( builder, usernamePassword[ 0 ], Segment.STAR_PATTERN ) ); |
| builder.setUsername( paramPattern ); |
| } |
| if( usernamePassword.length > 1 && !usernamePassword[1].isEmpty()) { |
| paramPattern = makeTokenSingular( parseTemplateToken( builder, usernamePassword[ 1 ], Segment.STAR_PATTERN ) ); |
| builder.setPassword( paramPattern ); |
| } |
| } |
| if(!hostPort[0].isEmpty()) { |
| paramPattern = makeTokenSingular( parseTemplateToken( builder, hostPort[ 0 ], Segment.STAR_PATTERN ) ); |
| builder.setHost( paramPattern ); |
| } |
| if( hostPort.length > 1 && !hostPort[1].isEmpty()) { |
| paramPattern = makeTokenSingular( parseTemplateToken( builder, hostPort[ 1 ], Segment.STAR_PATTERN ) ); |
| builder.setPort( paramPattern ); |
| } |
| } |
| } |
| |
| private static void consumePathMatch( final Builder builder, final Matcher match ) { |
| String path = match.group( MATCH_GROUP_PATH ); |
| if( path != null ) { |
| builder.setIsAbsolute( path.startsWith( "/" ) ); |
| builder.setIsDirectory( path.endsWith( "/" ) ); |
| consumePathToken( builder, path ); |
| } |
| } |
| |
| private static void consumePathToken( final Builder builder, final String token ) { |
| if( token != null ) { |
| final StringTokenizer tokenizer = new StringTokenizer( token, "/" ); |
| while( tokenizer.hasMoreTokens() ) { |
| consumePathSegment( builder, tokenizer.nextToken() ); |
| } |
| } |
| } |
| |
| private static void consumePathSegment( final Builder builder, final String token ) { |
| if( token != null ) { |
| final Token t = parseTemplateToken( builder, token, Segment.GLOB_PATTERN ); |
| builder.addPath( t ); |
| } |
| } |
| |
| private static void consumeQueryMatch( final Builder builder, Matcher match ) { |
| if( match.group( MATCH_GROUP_QUERY ) != null ) { |
| builder.setHasQuery( true ); |
| consumeQueryToken( builder, match.group( MATCH_GROUP_QUERY_NAKED ) ); |
| } |
| } |
| |
| private static void consumeQueryToken( final Builder builder, String token ) { |
| if( token != null ) { |
| //add "&" as a delimiter |
| String[] tokens = token.split("(&|\\?|&)"); |
| if (tokens != null){ |
| for (String nextToken : tokens){ |
| consumeQuerySegment(builder,nextToken); |
| } |
| } |
| |
| } |
| } |
| |
| private static void consumeQuerySegment( final Builder builder, String token ) { |
| if( token != null && !token.isEmpty()) { |
| // Shorthand format {queryParam} == queryParam={queryParam=*} |
| if( TEMPLATE_OPEN_MARKUP == token.charAt( 0 ) ) { |
| Token paramPattern = parseTemplateToken( builder, token, Segment.GLOB_PATTERN ); |
| String paramName = paramPattern.parameterName; |
| if( paramPattern.originalPattern == null ) { |
| builder.addQuery( paramName, new Token( paramName, null, Segment.GLOB_PATTERN, builder.isLiteral() ) ); |
| // if( Segment.STAR_PATTERN.equals( paramName ) || Segment.GLOB_PATTERN.equals( paramName ) ) { |
| // builder.addQuery( paramName, new Token( paramName, null, Segment.GLOB_PATTERN ) ); |
| // } else { |
| // builder.addQuery( paramName, new Token( paramName, null, Segment.GLOB_PATTERN ) ); |
| // } |
| } else { |
| builder.addQuery( paramName, new Token( paramName, paramPattern.originalPattern, builder.isLiteral() ) ); |
| } |
| } else { |
| String[] nameValue = split( token, '=' ); |
| if( nameValue.length == 1 ) { |
| String queryName = nameValue[ 0 ]; |
| builder.addQuery( queryName, new Token( Segment.ANONYMOUS_PARAM, null, builder.isLiteral() ) ); |
| } else { |
| String queryName = nameValue[ 0 ]; |
| Token paramPattern = parseTemplateToken( builder, nameValue[ 1 ], Segment.GLOB_PATTERN ); |
| builder.addQuery( queryName, paramPattern ); |
| } |
| } |
| } |
| } |
| |
| private static void consumeFragmentMatch( final Builder builder, Matcher match ) { |
| if( match.group( MATCH_GROUP_FRAGMENT ) != null ) { |
| builder.setHasFragment( true ); |
| consumeFragmentToken( builder, match.group( MATCH_GROUP_FRAGMENT_NAKED ) ); |
| } |
| } |
| |
| private static void consumeFragmentToken( final Builder builder, String token ) { |
| if( token != null && !token.isEmpty()) { |
| Token t = parseTemplateToken( builder, token, Segment.STAR_PATTERN ); |
| builder.setFragment( t ); |
| } |
| } |
| |
| static Token parseTemplateToken(final Builder builder, final String s, final String defaultEffectivePattern ) { |
| String paramName, actualPattern, effectivePattern; |
| final int l = s.length(); |
| // If the token isn't the empty string, then |
| if( l > 0 && !builder.isLiteral() ) { |
| final int b = ( s.charAt( 0 ) == TEMPLATE_OPEN_MARKUP ? 1 : -1 ); |
| final int e = ( s.charAt( l-1 ) == TEMPLATE_CLOSE_MARKUP ? l-1 : -1 ); |
| // If this is a parameter template, ie {...} |
| if( ( b > 0 ) && ( e > 0 ) && ( e > b ) ) { |
| final int i = s.indexOf( NAME_PATTERN_SEPARATOR, b ); |
| // If this is an anonymous template |
| if( i < 0 ) { |
| paramName = s.substring( b, e ); |
| actualPattern = null; |
| if( Segment.GLOB_PATTERN.equals( paramName ) ) { |
| effectivePattern = Segment.GLOB_PATTERN; |
| } else { |
| effectivePattern = defaultEffectivePattern; |
| } |
| // Otherwise populate the NVP. |
| } else { |
| paramName = s.substring( b, i ); |
| actualPattern = s.substring( i+1, e ); |
| effectivePattern = actualPattern; |
| } |
| // Otherwise it is just a pattern. |
| } else { |
| paramName = Segment.ANONYMOUS_PARAM; |
| actualPattern = s; |
| effectivePattern = actualPattern; |
| } |
| // Otherwise the token has no value. |
| } else { |
| paramName = Segment.ANONYMOUS_PARAM; |
| actualPattern = s; |
| effectivePattern = actualPattern; |
| } |
| return new Token( paramName, actualPattern, effectivePattern, builder.isLiteral() ); |
| } |
| |
| // Using this because String.split is very inefficient. |
| private static String[] split( String s, char d ) { |
| String[] a; |
| int i = s.indexOf( d ); |
| if( i < 0 ) { |
| a = new String[]{ s }; |
| } else { |
| a = new String[]{ s.substring( 0, i ), s.substring( i + 1 ) }; |
| } |
| return a; |
| } |
| |
| } |