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 018 package org.apache.commons.net.ftp.parser; 019 import java.text.ParseException; 020 import java.text.SimpleDateFormat; 021 import java.util.GregorianCalendar; 022 import java.util.HashMap; 023 import java.util.Locale; 024 import java.util.TimeZone; 025 026 import org.apache.commons.net.ftp.FTPFile; 027 import org.apache.commons.net.ftp.FTPFileEntryParserImpl; 028 029 /** 030 * Parser class for MSLT and MLSD replies. See RFC 3659. 031 * <p> 032 * Format is as follows: 033 * <pre> 034 * entry = [ facts ] SP pathname 035 * facts = 1*( fact ";" ) 036 * fact = factname "=" value 037 * factname = "Size" / "Modify" / "Create" / 038 * "Type" / "Unique" / "Perm" / 039 * "Lang" / "Media-Type" / "CharSet" / 040 * os-depend-fact / local-fact 041 * os-depend-fact = <IANA assigned OS name> "." token 042 * local-fact = "X." token 043 * value = *SCHAR 044 * 045 * Sample os-depend-fact: 046 * UNIX.group=0;UNIX.mode=0755;UNIX.owner=0; 047 * </pre> 048 * A single control response entry (MLST) is returned with a leading space; 049 * multiple (data) entries are returned without any leading spaces. 050 * The parser requires that the leading space from the MLST entry is removed. 051 * MLSD entries can begin with a single space if there are no facts. 052 * 053 * @since 3.0 054 */ 055 public class MLSxEntryParser extends FTPFileEntryParserImpl 056 { 057 // This class is immutable, so a single instance can be shared. 058 private static final MLSxEntryParser PARSER = new MLSxEntryParser(); 059 060 private static final HashMap<String, Integer> TYPE_TO_INT = new HashMap<String, Integer>(); 061 static { 062 TYPE_TO_INT.put("file", Integer.valueOf(FTPFile.FILE_TYPE)); 063 TYPE_TO_INT.put("cdir", Integer.valueOf(FTPFile.DIRECTORY_TYPE)); // listed directory 064 TYPE_TO_INT.put("pdir", Integer.valueOf(FTPFile.DIRECTORY_TYPE)); // a parent dir 065 TYPE_TO_INT.put("dir", Integer.valueOf(FTPFile.DIRECTORY_TYPE)); // dir or sub-dir 066 } 067 068 private static int UNIX_GROUPS[] = { // Groups in order of mode digits 069 FTPFile.USER_ACCESS, 070 FTPFile.GROUP_ACCESS, 071 FTPFile.WORLD_ACCESS, 072 }; 073 074 private static int UNIX_PERMS[][] = { // perm bits, broken down by octal int value 075 /* 0 */ {}, 076 /* 1 */ {FTPFile.EXECUTE_PERMISSION}, 077 /* 2 */ {FTPFile.WRITE_PERMISSION}, 078 /* 3 */ {FTPFile.EXECUTE_PERMISSION, FTPFile.WRITE_PERMISSION}, 079 /* 4 */ {FTPFile.READ_PERMISSION}, 080 /* 5 */ {FTPFile.READ_PERMISSION, FTPFile.EXECUTE_PERMISSION}, 081 /* 6 */ {FTPFile.READ_PERMISSION, FTPFile.WRITE_PERMISSION}, 082 /* 7 */ {FTPFile.READ_PERMISSION, FTPFile.WRITE_PERMISSION, FTPFile.EXECUTE_PERMISSION}, 083 }; 084 085 /** 086 * Create the parser for MSLT and MSLD listing entries 087 * This class is immutable, so one can use {@link #getInstance()} instead. 088 */ 089 public MLSxEntryParser() 090 { 091 super(); 092 } 093 094 public FTPFile parseFTPEntry(String entry) { 095 String parts[] = entry.split(" ",2); // Path may contain space 096 if (parts.length != 2) { 097 return null; 098 } 099 FTPFile file = new FTPFile(); 100 file.setRawListing(entry); 101 file.setName(parts[1]); 102 String[] facts = parts[0].split(";"); 103 boolean hasUnixMode = parts[0].toLowerCase(Locale.ENGLISH).contains("unix.mode="); 104 for(String fact : facts) { 105 String []factparts = fact.split("="); 106 // Sample missing permission 107 // drwx------ 2 mirror mirror 4096 Mar 13 2010 subversion 108 // modify=20100313224553;perm=;type=dir;unique=811U282598;UNIX.group=500;UNIX.mode=0700;UNIX.owner=500; subversion 109 if (factparts.length != 2) { 110 continue; // nothing to do here 111 } 112 String factname = factparts[0].toLowerCase(Locale.ENGLISH); 113 String factvalue = factparts[1]; 114 String valueLowerCase = factvalue.toLowerCase(Locale.ENGLISH); 115 if ("size".equals(factname)) { 116 file.setSize(Long.parseLong(factvalue)); 117 } 118 else if ("sizd".equals(factname)) { // Directory size 119 file.setSize(Long.parseLong(factvalue)); 120 } 121 else if ("modify".equals(factname)) { 122 // YYYYMMDDHHMMSS[.sss] 123 SimpleDateFormat sdf; // Not thread-safe 124 if (factvalue.contains(".")){ 125 sdf = new SimpleDateFormat("yyyyMMddHHmmss.SSS"); 126 } else { 127 sdf = new SimpleDateFormat("yyyyMMddHHmmss"); 128 } 129 TimeZone GMT = TimeZone.getTimeZone("GMT"); // both need to be set for the parse to work OK 130 sdf.setTimeZone(GMT); 131 GregorianCalendar gc = new GregorianCalendar(GMT); 132 try { 133 gc.setTime(sdf.parse(factvalue)); 134 } catch (ParseException e) { 135 // TODO ?? 136 } 137 file.setTimestamp(gc); 138 } 139 else if ("type".equals(factname)) { 140 Integer intType = TYPE_TO_INT.get(valueLowerCase); 141 if (intType == null) { 142 file.setType(FTPFile.UNKNOWN_TYPE); 143 } else { 144 file.setType(intType.intValue()); 145 } 146 } 147 else if (factname.startsWith("unix.")) { 148 String unixfact = factname.substring("unix.".length()).toLowerCase(Locale.ENGLISH); 149 if ("group".equals(unixfact)){ 150 file.setGroup(factvalue); 151 } else if ("owner".equals(unixfact)){ 152 file.setUser(factvalue); 153 } else if ("mode".equals(unixfact)){ // e.g. 0[1]755 154 int off = factvalue.length()-3; // only parse last 3 digits 155 for(int i=0; i < 3; i++){ 156 int ch = factvalue.charAt(off+i)-'0'; 157 if (ch >= 0 && ch <= 7) { // Check it's valid octal 158 for(int p : UNIX_PERMS[ch]) { 159 file.setPermission(UNIX_GROUPS[i], p, true); 160 } 161 } else { 162 // TODO should this cause failure, or can it be reported somehow? 163 } 164 } // digits 165 } // mode 166 } // unix. 167 else if (!hasUnixMode && "perm".equals(factname)) { // skip if we have the UNIX.mode 168 doUnixPerms(file, valueLowerCase); 169 } // process "perm" 170 } // each fact 171 return file; 172 } 173 174 // perm-fact = "Perm" "=" *pvals 175 // pvals = "a" / "c" / "d" / "e" / "f" / 176 // "l" / "m" / "p" / "r" / "w" 177 private void doUnixPerms(FTPFile file, String valueLowerCase) { 178 for(char c : valueLowerCase.toCharArray()) { 179 // TODO these are mostly just guesses at present 180 switch (c) { 181 case 'a': // (file) may APPEnd 182 file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); 183 break; 184 case 'c': // (dir) files may be created in the dir 185 file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); 186 break; 187 case 'd': // deletable 188 file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); 189 break; 190 case 'e': // (dir) can change to this dir 191 file.setPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION, true); 192 break; 193 case 'f': // (file) renamable 194 // ?? file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); 195 break; 196 case 'l': // (dir) can be listed 197 file.setPermission(FTPFile.USER_ACCESS, FTPFile.EXECUTE_PERMISSION, true); 198 break; 199 case 'm': // (dir) can create directory here 200 file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); 201 break; 202 case 'p': // (dir) entries may be deleted 203 file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); 204 break; 205 case 'r': // (files) file may be RETRieved 206 file.setPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION, true); 207 break; 208 case 'w': // (files) file may be STORed 209 file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); 210 break; 211 default: 212 break; 213 // ignore unexpected flag for now. 214 } // switch 215 } // each char 216 } 217 218 public static FTPFile parseEntry(String entry) { 219 return PARSER.parseFTPEntry(entry); 220 } 221 222 public static MLSxEntryParser getInstance() { 223 return PARSER; 224 } 225 }