1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package nginx.unit.websocket.server; 18 19 import java.util.ArrayList; 20 import java.util.HashMap; 21 import java.util.HashSet; 22 import java.util.Iterator; 23 import java.util.List; 24 import java.util.Map; 25 import java.util.Set; 26 27 import javax.websocket.DeploymentException; 28 29 import org.apache.tomcat.util.res.StringManager; 30 31 /** 32 * Extracts path parameters from URIs used to create web socket connections 33 * using the URI template defined for the associated Endpoint. 34 */ 35 public class UriTemplate { 36 37 private static final StringManager sm = StringManager.getManager(UriTemplate.class); 38 39 private final String normalized; 40 private final List<Segment> segments = new ArrayList<>(); 41 private final boolean hasParameters; 42 43 UriTemplate(String path)44 public UriTemplate(String path) throws DeploymentException { 45 46 if (path == null || path.length() ==0 || !path.startsWith("/")) { 47 throw new DeploymentException( 48 sm.getString("uriTemplate.invalidPath", path)); 49 } 50 51 StringBuilder normalized = new StringBuilder(path.length()); 52 Set<String> paramNames = new HashSet<>(); 53 54 // Include empty segments. 55 String[] segments = path.split("/", -1); 56 int paramCount = 0; 57 int segmentCount = 0; 58 59 for (int i = 0; i < segments.length; i++) { 60 String segment = segments[i]; 61 if (segment.length() == 0) { 62 if (i == 0 || (i == segments.length - 1 && paramCount == 0)) { 63 // Ignore the first empty segment as the path must always 64 // start with '/' 65 // Ending with a '/' is also OK for instances used for 66 // matches but not for parameterised templates. 67 continue; 68 } else { 69 // As per EG discussion, all other empty segments are 70 // invalid 71 throw new IllegalArgumentException(sm.getString( 72 "uriTemplate.emptySegment", path)); 73 } 74 } 75 normalized.append('/'); 76 int index = -1; 77 if (segment.startsWith("{") && segment.endsWith("}")) { 78 index = segmentCount; 79 segment = segment.substring(1, segment.length() - 1); 80 normalized.append('{'); 81 normalized.append(paramCount++); 82 normalized.append('}'); 83 if (!paramNames.add(segment)) { 84 throw new IllegalArgumentException(sm.getString( 85 "uriTemplate.duplicateParameter", segment)); 86 } 87 } else { 88 if (segment.contains("{") || segment.contains("}")) { 89 throw new IllegalArgumentException(sm.getString( 90 "uriTemplate.invalidSegment", segment, path)); 91 } 92 normalized.append(segment); 93 } 94 this.segments.add(new Segment(index, segment)); 95 segmentCount++; 96 } 97 98 this.normalized = normalized.toString(); 99 this.hasParameters = paramCount > 0; 100 } 101 102 match(UriTemplate candidate)103 public Map<String,String> match(UriTemplate candidate) { 104 105 Map<String,String> result = new HashMap<>(); 106 107 // Should not happen but for safety 108 if (candidate.getSegmentCount() != getSegmentCount()) { 109 return null; 110 } 111 112 Iterator<Segment> candidateSegments = 113 candidate.getSegments().iterator(); 114 Iterator<Segment> targetSegments = segments.iterator(); 115 116 while (candidateSegments.hasNext()) { 117 Segment candidateSegment = candidateSegments.next(); 118 Segment targetSegment = targetSegments.next(); 119 120 if (targetSegment.getParameterIndex() == -1) { 121 // Not a parameter - values must match 122 if (!targetSegment.getValue().equals( 123 candidateSegment.getValue())) { 124 // Not a match. Stop here 125 return null; 126 } 127 } else { 128 // Parameter 129 result.put(targetSegment.getValue(), 130 candidateSegment.getValue()); 131 } 132 } 133 134 return result; 135 } 136 137 hasParameters()138 public boolean hasParameters() { 139 return hasParameters; 140 } 141 142 getSegmentCount()143 public int getSegmentCount() { 144 return segments.size(); 145 } 146 147 getNormalizedPath()148 public String getNormalizedPath() { 149 return normalized; 150 } 151 152 getSegments()153 private List<Segment> getSegments() { 154 return segments; 155 } 156 157 158 private static class Segment { 159 private final int parameterIndex; 160 private final String value; 161 Segment(int parameterIndex, String value)162 public Segment(int parameterIndex, String value) { 163 this.parameterIndex = parameterIndex; 164 this.value = value; 165 } 166 167 getParameterIndex()168 public int getParameterIndex() { 169 return parameterIndex; 170 } 171 172 getValue()173 public String getValue() { 174 return value; 175 } 176 } 177 } 178