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