001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  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.apache.commons.vfs2.provider;
018
019import org.apache.commons.vfs2.FileName;
020import org.apache.commons.vfs2.FileSystemException;
021import org.apache.commons.vfs2.FileType;
022import org.apache.commons.vfs2.util.Cryptor;
023import org.apache.commons.vfs2.util.CryptorFactory;
024
025/**
026 * Implementation for any url based filesystem.<br />
027 * Parses the url into user/password/host/port/path<br />
028 * Does not handle a query string (after ?)
029 *
030 * @see URLFileNameParser URLFileNameParser for the implementation which also handles the query string too
031 */
032public class HostFileNameParser extends AbstractFileNameParser
033{
034    private final int defaultPort;
035
036    public HostFileNameParser(final int defaultPort)
037    {
038        this.defaultPort = defaultPort;
039    }
040
041    public int getDefaultPort()
042    {
043        return defaultPort;
044    }
045
046    @Override
047    public FileName parseUri(final VfsComponentContext context, final FileName base, final String filename)
048            throws FileSystemException
049    {
050        // FTP URI are generic URI (as per RFC 2396)
051        final StringBuilder name = new StringBuilder();
052
053        // Extract the scheme and authority parts
054        final Authority auth = extractToPath(filename, name);
055
056        // Decode and normalise the file name
057        UriParser.canonicalizePath(name, 0, name.length(), this);
058        UriParser.fixSeparators(name);
059        final FileType fileType = UriParser.normalisePath(name);
060        final String path = name.toString();
061
062        return new GenericFileName(
063            auth.scheme,
064            auth.hostName,
065            auth.port,
066            defaultPort,
067            auth.userName,
068            auth.password,
069            path,
070            fileType);
071    }
072
073    /**
074     * Extracts the scheme, userinfo, hostname and port components of a
075     * generic URI.
076     *
077     * @param uri  The absolute URI to parse.
078     * @param name Used to return the remainder of the URI.
079     */
080    protected Authority extractToPath(final String uri,
081                                      final StringBuilder name)
082        throws FileSystemException
083    {
084        final Authority auth = new Authority();
085
086        // Extract the scheme
087        auth.scheme = UriParser.extractScheme(uri, name);
088
089        // Expecting "//"
090        if (name.length() < 2 || name.charAt(0) != '/' || name.charAt(1) != '/')
091        {
092            throw new FileSystemException("vfs.provider/missing-double-slashes.error", uri);
093        }
094        name.delete(0, 2);
095
096        // Extract userinfo, and split into username and password
097        final String userInfo = extractUserInfo(name);
098        final String userName;
099        final String password;
100        if (userInfo != null)
101        {
102            final int idx = userInfo.indexOf(':');
103            if (idx == -1)
104            {
105                userName = userInfo;
106                password = null;
107            }
108            else
109            {
110                userName = userInfo.substring(0, idx);
111                password = userInfo.substring(idx + 1);
112            }
113        }
114        else
115        {
116            userName = null;
117            password = null;
118        }
119        auth.userName = UriParser.decode(userName);
120        auth.password = UriParser.decode(password);
121
122        if (auth.password != null && auth.password.startsWith("{") && auth.password.endsWith("}"))
123        {
124            try
125            {
126                final Cryptor cryptor = CryptorFactory.getCryptor();
127                auth.password = cryptor.decrypt(auth.password.substring(1, auth.password.length() - 1));
128            }
129            catch (final Exception ex)
130            {
131                throw new FileSystemException("Unable to decrypt password", ex);
132            }
133        }
134
135        // Extract hostname, and normalise (lowercase)
136        final String hostName = extractHostName(name);
137        if (hostName == null)
138        {
139            throw new FileSystemException("vfs.provider/missing-hostname.error", uri);
140        }
141        auth.hostName = hostName.toLowerCase();
142
143        // Extract port
144        auth.port = extractPort(name, uri);
145
146        // Expecting '/' or empty name
147        if (name.length() > 0 && name.charAt(0) != '/')
148        {
149            throw new FileSystemException("vfs.provider/missing-hostname-path-sep.error", uri);
150        }
151
152        return auth;
153    }
154
155    /**
156     * Extracts the user info from a URI.  The scheme:// part has been removed
157     * already.
158     */
159    protected String extractUserInfo(final StringBuilder name)
160    {
161        final int maxlen = name.length();
162        for (int pos = 0; pos < maxlen; pos++)
163        {
164            final char ch = name.charAt(pos);
165            if (ch == '@')
166            {
167                // Found the end of the user info
168                final String userInfo = name.substring(0, pos);
169                name.delete(0, pos + 1);
170                return userInfo;
171            }
172            if (ch == '/' || ch == '?')
173            {
174                // Not allowed in user info
175                break;
176            }
177        }
178
179        // Not found
180        return null;
181    }
182
183    /**
184     * Extracts the hostname from a URI.  The scheme://userinfo@ part has
185     * been removed.
186     */
187    protected String extractHostName(final StringBuilder name)
188    {
189        final int maxlen = name.length();
190        int pos = 0;
191        for (; pos < maxlen; pos++)
192        {
193            final char ch = name.charAt(pos);
194            if (ch == '/' || ch == ';' || ch == '?' || ch == ':'
195                || ch == '@' || ch == '&' || ch == '=' || ch == '+'
196                || ch == '$' || ch == ',')
197            {
198                break;
199            }
200        }
201        if (pos == 0)
202        {
203            return null;
204        }
205
206        final String hostname = name.substring(0, pos);
207        name.delete(0, pos);
208        return hostname;
209    }
210
211    /**
212     * Extracts the port from a URI.  The scheme://userinfo@hostname
213     * part has been removed.
214     *
215     * @return The port, or -1 if the URI does not contain a port.
216     */
217    protected int extractPort(final StringBuilder name, final String uri) throws FileSystemException
218    {
219        if (name.length() < 1 || name.charAt(0) != ':')
220        {
221            return -1;
222        }
223
224        final int maxlen = name.length();
225        int pos = 1;
226        for (; pos < maxlen; pos++)
227        {
228            final char ch = name.charAt(pos);
229            if (ch < '0' || ch > '9')
230            {
231                break;
232            }
233        }
234
235        final String port = name.substring(1, pos);
236        name.delete(0, pos);
237        if (port.length() == 0)
238        {
239            throw new FileSystemException("vfs.provider/missing-port.error", uri);
240        }
241
242        return Integer.parseInt(port);
243    }
244
245    /**
246     * Parsed authority info (scheme, hostname, userinfo, port)
247     */
248    protected static class Authority
249    {
250        private String scheme;
251        private String hostName;
252        private String userName;
253        private String password;
254        private int port;
255
256        /** @since 2.0 */
257        public String getScheme()
258        {
259            return scheme;
260        }
261
262        /** @since 2.0 */
263        public void setScheme(final String scheme)
264        {
265            this.scheme = scheme;
266        }
267
268        /** @since 2.0 */
269        public String getHostName()
270        {
271            return hostName;
272        }
273
274        /** @since 2.0 */
275        public void setHostName(final String hostName)
276        {
277            this.hostName = hostName;
278        }
279
280        /** @since 2.0 */
281        public String getUserName()
282        {
283            return userName;
284        }
285
286        /** @since 2.0 */
287        public void setUserName(final String userName)
288        {
289            this.userName = userName;
290        }
291
292        /** @since 2.0 */
293        public String getPassword()
294        {
295            return password;
296        }
297
298        /** @since 2.0 */
299        public void setPassword(final String password)
300        {
301            this.password = password;
302        }
303
304        /** @since 2.0 */
305        public int getPort()
306        {
307            return port;
308        }
309
310        /** @since 2.0 */
311        public void setPort(final int port)
312        {
313            this.port = port;
314        }
315    }
316}