View Javadoc

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  
18  package org.apache.commons.net.ftp.parser;
19  import java.text.ParseException;
20  import java.text.SimpleDateFormat;
21  import java.util.GregorianCalendar;
22  import java.util.HashMap;
23  import java.util.Locale;
24  import java.util.TimeZone;
25  
26  import org.apache.commons.net.ftp.FTPFile;
27  import org.apache.commons.net.ftp.FTPFileEntryParserImpl;
28  
29  /**
30   * Parser class for MSLT and MLSD replies. See RFC 3659.
31   * <p>
32   * Format is as follows:
33   * <pre>
34   * entry            = [ facts ] SP pathname
35   * facts            = 1*( fact ";" )
36   * fact             = factname "=" value
37   * factname         = "Size" / "Modify" / "Create" /
38   *                    "Type" / "Unique" / "Perm" /
39   *                    "Lang" / "Media-Type" / "CharSet" /
40   * os-depend-fact / local-fact
41   * os-depend-fact   = <IANA assigned OS name> "." token
42   * local-fact       = "X." token
43   * value            = *SCHAR
44   *
45   * Sample os-depend-fact:
46   * UNIX.group=0;UNIX.mode=0755;UNIX.owner=0;
47   * </pre>
48   * A single control response entry (MLST) is returned with a leading space;
49   * multiple (data) entries are returned without any leading spaces.
50   * The parser requires that the leading space from the MLST entry is removed.
51   * MLSD entries can begin with a single space if there are no facts.
52   *
53   * @since 3.0
54   */
55  public class MLSxEntryParser extends FTPFileEntryParserImpl
56  {
57      // This class is immutable, so a single instance can be shared.
58      private static final MLSxEntryParser PARSER = new MLSxEntryParser();
59  
60      private static final HashMap<String, Integer> TYPE_TO_INT = new HashMap<String, Integer>();
61      static {
62          TYPE_TO_INT.put("file", Integer.valueOf(FTPFile.FILE_TYPE));
63          TYPE_TO_INT.put("cdir", Integer.valueOf(FTPFile.DIRECTORY_TYPE)); // listed directory
64          TYPE_TO_INT.put("pdir", Integer.valueOf(FTPFile.DIRECTORY_TYPE)); // a parent dir
65          TYPE_TO_INT.put("dir", Integer.valueOf(FTPFile.DIRECTORY_TYPE)); // dir or sub-dir
66      }
67  
68      private static int UNIX_GROUPS[] = { // Groups in order of mode digits
69          FTPFile.USER_ACCESS,
70          FTPFile.GROUP_ACCESS,
71          FTPFile.WORLD_ACCESS,
72      };
73  
74      private static int UNIX_PERMS[][] = { // perm bits, broken down by octal int value
75  /* 0 */  {},
76  /* 1 */  {FTPFile.EXECUTE_PERMISSION},
77  /* 2 */  {FTPFile.WRITE_PERMISSION},
78  /* 3 */  {FTPFile.EXECUTE_PERMISSION, FTPFile.WRITE_PERMISSION},
79  /* 4 */  {FTPFile.READ_PERMISSION},
80  /* 5 */  {FTPFile.READ_PERMISSION, FTPFile.EXECUTE_PERMISSION},
81  /* 6 */  {FTPFile.READ_PERMISSION, FTPFile.WRITE_PERMISSION},
82  /* 7 */  {FTPFile.READ_PERMISSION, FTPFile.WRITE_PERMISSION, FTPFile.EXECUTE_PERMISSION},
83      };
84  
85      /**
86       * Create the parser for MSLT and MSLD listing entries
87       * This class is immutable, so one can use {@link #getInstance()} instead.
88       */
89      public MLSxEntryParser()
90      {
91          super();
92      }
93  
94  //    @Override
95      public FTPFile parseFTPEntry(String entry) {
96          String parts[] = entry.split(" ",2); // Path may contain space
97          if (parts.length != 2) {
98              return null;
99          }
100         FTPFile file = new FTPFile();
101         file.setRawListing(entry);
102         file.setName(parts[1]);
103         String[] facts = parts[0].split(";");
104         boolean hasUnixMode = parts[0].toLowerCase(Locale.ENGLISH).contains("unix.mode=");
105         for(String fact : facts) {
106             String []factparts = fact.split("=");
107 // Sample missing permission
108 // drwx------   2 mirror   mirror       4096 Mar 13  2010 subversion
109 // modify=20100313224553;perm=;type=dir;unique=811U282598;UNIX.group=500;UNIX.mode=0700;UNIX.owner=500; subversion
110             if (factparts.length != 2) {
111                 continue; // nothing to do here
112             }
113             String factname = factparts[0].toLowerCase(Locale.ENGLISH);
114             String factvalue = factparts[1];
115             String valueLowerCase = factvalue.toLowerCase(Locale.ENGLISH);
116             if ("size".equals(factname)) {
117                 file.setSize(Long.parseLong(factvalue));
118             }
119             else if ("sizd".equals(factname)) { // Directory size
120                 file.setSize(Long.parseLong(factvalue));
121             }
122             else if ("modify".equals(factname)) {
123                 // YYYYMMDDHHMMSS[.sss]
124                 SimpleDateFormat sdf; // Not thread-safe
125                 if (factvalue.contains(".")){
126                     sdf = new SimpleDateFormat("yyyyMMddHHmmss.SSS");
127                 } else {
128                     sdf = new SimpleDateFormat("yyyyMMddHHmmss");
129                 }
130                 TimeZone GMT = TimeZone.getTimeZone("GMT"); // both need to be set for the parse to work OK
131                 sdf.setTimeZone(GMT);
132                 GregorianCalendar gc = new GregorianCalendar(GMT);
133                 try {
134                     gc.setTime(sdf.parse(factvalue));
135                 } catch (ParseException e) {
136                     // TODO ??
137                 }
138                 file.setTimestamp(gc);
139             }
140             else if ("type".equals(factname)) {
141                     Integer intType = TYPE_TO_INT.get(valueLowerCase);
142                     if (intType == null) {
143                         file.setType(FTPFile.UNKNOWN_TYPE);
144                     } else {
145                         file.setType(intType.intValue());
146                     }
147             }
148             else if (factname.startsWith("unix.")) {
149                 String unixfact = factname.substring("unix.".length()).toLowerCase(Locale.ENGLISH);
150                 if ("group".equals(unixfact)){
151                     file.setGroup(factvalue);
152                 } else if ("owner".equals(unixfact)){
153                     file.setUser(factvalue);
154                 } else if ("mode".equals(unixfact)){ // e.g. 0[1]755
155                     int off = factvalue.length()-3; // only parse last 3 digits
156                     for(int i=0; i < 3; i++){
157                         int ch = factvalue.charAt(off+i)-'0';
158                         if (ch >= 0 && ch <= 7) { // Check it's valid octal
159                             for(int p : UNIX_PERMS[ch]) {
160                                 file.setPermission(UNIX_GROUPS[i], p, true);
161                             }
162                         } else {
163                             // TODO should this cause failure, or can it be reported somehow?
164                         }
165                     } // digits
166                 } // mode
167             } // unix.
168             else if (!hasUnixMode && "perm".equals(factname)) { // skip if we have the UNIX.mode
169                 doUnixPerms(file, valueLowerCase);
170             } // process "perm"
171         } // each fact
172         return file;
173     }
174 
175     //              perm-fact    = "Perm" "=" *pvals
176     //              pvals        = "a" / "c" / "d" / "e" / "f" /
177     //                             "l" / "m" / "p" / "r" / "w"
178     private void doUnixPerms(FTPFile file, String valueLowerCase) {
179         for(char c : valueLowerCase.toCharArray()) {
180             // TODO these are mostly just guesses at present
181             switch (c) {
182                 case 'a':     // (file) may APPEnd
183                     file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true);
184                     break;
185                 case 'c':     // (dir) files may be created in the dir
186                     file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true);
187                     break;
188                 case 'd':     // deletable
189                     file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true);
190                     break;
191                 case 'e':     // (dir) can change to this dir
192                     file.setPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION, true);
193                     break;
194                 case 'f':     // (file) renamable
195                     // ?? file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true);
196                     break;
197                 case 'l':     // (dir) can be listed
198                     file.setPermission(FTPFile.USER_ACCESS, FTPFile.EXECUTE_PERMISSION, true);
199                     break;
200                 case 'm':     // (dir) can create directory here
201                     file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true);
202                     break;
203                 case 'p':     // (dir) entries may be deleted
204                     file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true);
205                     break;
206                 case 'r':     // (files) file may be RETRieved
207                     file.setPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION, true);
208                     break;
209                 case 'w':     // (files) file may be STORed
210                     file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true);
211                     break;
212                 default:
213                     break;
214                     // ignore unexpected flag for now.
215             } // switch
216         } // each char
217     }
218 
219     public static FTPFile parseEntry(String entry) {
220         return PARSER.parseFTPEntry(entry);
221     }
222 
223     public static  MLSxEntryParser getInstance() {
224         return PARSER;
225     }
226 }