001/*******************************************************************************
002 * Copyright 2014 The MITRE Corporation
003 *   and the MIT Kerberos and Internet Trust Consortium
004 * 
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 * 
009 *   http://www.apache.org/licenses/LICENSE-2.0
010 * 
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 ******************************************************************************/
017package org.mitre.discovery.util;
018
019import java.util.regex.Matcher;
020import java.util.regex.Pattern;
021
022import org.slf4j.Logger;
023import org.slf4j.LoggerFactory;
024import org.springframework.util.StringUtils;
025import org.springframework.web.util.UriComponents;
026import org.springframework.web.util.UriComponentsBuilder;
027
028import com.google.common.base.Strings;
029
030/**
031 * Provides utility methods for normalizing and parsing URIs for use with Webfinger Discovery.
032 * 
033 * @author wkim
034 *
035 */
036public class WebfingerURLNormalizer {
037
038        private static Logger logger = LoggerFactory.getLogger(WebfingerURLNormalizer.class);
039
040        // pattern used to parse user input; we can't use the built-in java URI parser
041        private static final Pattern pattern = Pattern.compile("^" +
042                        "((https|acct|http|mailto|tel|device):(//)?)?" + // scheme
043                        "(" +
044                        "(([^@]+)@)?" + // userinfo
045                        "(([^\\?#:/]+)" + // host
046                        "(:(\\d*))?)" + // port
047                        ")" +
048                        "([^\\?#]+)?" + // path
049                        "(\\?([^#]+))?" + // query
050                        "(#(.*))?" +  // fragment
051                        "$"
052                        );
053
054
055
056        /**
057         * Private constructor to prevent instantiation.
058         */
059        private WebfingerURLNormalizer() {
060                // intentionally blank
061        }
062
063        /**
064         * Normalize the resource string as per OIDC Discovery.
065         * @param identifier
066         * @return the normalized string, or null if the string can't be normalized
067         */
068        public static UriComponents normalizeResource(String identifier) {
069                // try to parse the URI
070                // NOTE: we can't use the Java built-in URI class because it doesn't split the parts appropriately
071
072                if (Strings.isNullOrEmpty(identifier)) {
073                        logger.warn("Can't normalize null or empty URI: " + identifier);
074                        return null; // nothing we can do
075                } else {
076
077                        //UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(identifier);
078                        UriComponentsBuilder builder = UriComponentsBuilder.newInstance();
079
080                        Matcher m = pattern.matcher(identifier);
081                        if (m.matches()) {
082                                builder.scheme(m.group(2));
083                                builder.userInfo(m.group(6));
084                                builder.host(m.group(8));
085                                String port = m.group(10);
086                                if (!Strings.isNullOrEmpty(port)) {
087                                        builder.port(Integer.parseInt(port));
088                                }
089                                builder.path(m.group(11));
090                                builder.query(m.group(13));
091                                builder.fragment(m.group(15)); // we throw away the hash, but this is the group it would be if we kept it
092                        } else {
093                                // doesn't match the pattern, throw it out
094                                logger.warn("Parser couldn't match input: " + identifier);
095                                return null;
096                        }
097
098                        UriComponents n = builder.build();
099
100                        if (Strings.isNullOrEmpty(n.getScheme())) {
101                                if (!Strings.isNullOrEmpty(n.getUserInfo())
102                                                && Strings.isNullOrEmpty(n.getPath())
103                                                && Strings.isNullOrEmpty(n.getQuery())
104                                                && n.getPort() < 0) {
105
106                                        // scheme empty, userinfo is not empty, path/query/port are empty
107                                        // set to "acct" (rule 2)
108                                        builder.scheme("acct");
109
110                                } else {
111                                        // scheme is empty, but rule 2 doesn't apply
112                                        // set scheme to "https" (rule 3)
113                                        builder.scheme("https");
114                                }
115                        }
116
117                        // fragment must be stripped (rule 4)
118                        builder.fragment(null);
119
120                        return builder.build();
121                }
122
123
124        }
125
126
127        public static String serializeURL(UriComponents uri) {
128                if (uri.getScheme() != null &&
129                                (uri.getScheme().equals("acct") ||
130                                                uri.getScheme().equals("mailto") ||
131                                                uri.getScheme().equals("tel") ||
132                                                uri.getScheme().equals("device")
133                                                )) {
134
135                        // serializer copied from HierarchicalUriComponents but with "//" removed
136
137                        StringBuilder uriBuilder = new StringBuilder();
138
139                        if (uri.getScheme() != null) {
140                                uriBuilder.append(uri.getScheme());
141                                uriBuilder.append(':');
142                        }
143
144                        if (uri.getUserInfo() != null || uri.getHost() != null) {
145                                if (uri.getUserInfo() != null) {
146                                        uriBuilder.append(uri.getUserInfo());
147                                        uriBuilder.append('@');
148                                }
149                                if (uri.getHost() != null) {
150                                        uriBuilder.append(uri.getHost());
151                                }
152                                if (uri.getPort() != -1) {
153                                        uriBuilder.append(':');
154                                        uriBuilder.append(uri.getPort());
155                                }
156                        }
157
158                        String path = uri.getPath();
159                        if (StringUtils.hasLength(path)) {
160                                if (uriBuilder.length() != 0 && path.charAt(0) != '/') {
161                                        uriBuilder.append('/');
162                                }
163                                uriBuilder.append(path);
164                        }
165
166                        String query = uri.getQuery();
167                        if (query != null) {
168                                uriBuilder.append('?');
169                                uriBuilder.append(query);
170                        }
171
172                        if (uri.getFragment() != null) {
173                                uriBuilder.append('#');
174                                uriBuilder.append(uri.getFragment());
175                        }
176
177                        return uriBuilder.toString();
178                } else {
179                        return uri.toUriString();
180                }
181
182        }
183
184
185}