    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 *      https://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
    018package org.apache.commons.net.ftp.parser;
    019
    020import java.text.ParseException;
    021
    022import org.apache.commons.net.ftp.FTPClientConfig;
    023import org.apache.commons.net.ftp.FTPFile;
    024
    025/**
    026 * Implementation FTPFileEntryParser and FTPFileListParser for pre MacOS-X Systems.
    027 *
    028 * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions)
    029 * @since 3.1
    030 */
    031public class MacOsPeterFTPEntryParser extends ConfigurableFTPFileEntryParserImpl {
    032
    033    static final String DEFAULT_DATE_FORMAT = "MMM d yyyy"; // Nov 9 2001
    034
    035    static final String DEFAULT_RECENT_DATE_FORMAT = "MMM d HH:mm"; // Nov 9 20:06
    036
    037    /**
    038     * this is the regular expression used by this parser.
    039     *
    040     * Permissions: r the file is readable w the file is writable x the file is executable - the indicated permission is not granted L mandatory locking occurs
    041     * during access (the set-group-ID bit is on and the group execution bit is off) s the set-user-ID or set-group-ID bit is on, and the corresponding user or
    042     * group execution bit is also on S undefined bit-state (the set-user-ID bit is on and the user execution bit is off) t the 1000 (octal) bit, or sticky bit,
    043     * is on [see chmod(1)], and execution is on T the 1000 bit is turned on, and execution is off (undefined bit-state) e z/OS external link bit
    044     */
    045    private static final String REGEX = "([bcdelfmpSs-])" // type (1)
    046            + "(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\\+?\\s+" // permission
    047            + "(" + "(folder\\s+)" + "|" + "((\\d+)\\s+(\\d+)\\s+)" // resource size & data size
    048            + ")" + "(\\d+)\\s+" // size
    049            /*
    050             * numeric or standard format date: yyyy-mm-dd (expecting hh:mm to follow) MMM [d]d [d]d MMM Use non-space for MMM to allow for languages such
    051             * as German which use diacritics (e.g. umlaut) in some abbreviations.
    052             */
    053            + "((?:\\d+[-/]\\d+[-/]\\d+)|(?:\\S{3}\\s+\\d{1,2})|(?:\\d{1,2}\\s+\\S{3}))\\s+"
    054            /*
    055             * year (for non-recent standard format) - yyyy or time (for numeric or recent standard format) [h]h:mm
    056             */
    057            + "(\\d+(?::\\d+)?)\\s+"
    058
    059            + "(\\S*)(\\s*.*)"; // the rest
    060
    061    /**
    062     * The default constructor for a UnixFTPEntryParser object.
    063     *
    064     * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. If it is seen, this is a
    065     *                                  sign that {@code REGEX} is not a valid regular expression.
    066     */
    067    public MacOsPeterFTPEntryParser() {
    068        this(null);
    069    }
    070
    071    /**
    072     * This constructor allows the creation of a UnixFTPEntryParser object with something other than the default configuration.
    073     *
    074     * @param config The {@link FTPClientConfig configuration} object used to configure this parser.
    075     * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. If it is seen, this is a
    076     *                                  sign that {@code REGEX} is not a valid regular expression.
    077     * @since 1.4
    078     */
    079    public MacOsPeterFTPEntryParser(final FTPClientConfig config) {
    080        super(REGEX);
    081        configure(config);
    082    }
    083
    084    /**
    085     * Gets a new default configuration to be used when this class is instantiated without a {@link FTPClientConfig FTPClientConfig} parameter being specified.
    086     *
    087     * @return the default configuration for this parser.
    088     */
    089    @Override
    090    protected FTPClientConfig getDefaultConfiguration() {
    091        return new FTPClientConfig(FTPClientConfig.SYST_UNIX, DEFAULT_DATE_FORMAT, DEFAULT_RECENT_DATE_FORMAT);
    092    }
    093
    094    /**
    095     * Parses a line of a Unix (standard) FTP server file listing and converts it into a usable format in the form of an {@code FTPFile} instance. If the
    096     * file listing line doesn't describe a file, {@code null} is returned, otherwise a {@code FTPFile} instance representing the files in the
    097     * directory is returned.
    098     *
    099     * @param entry A line of text from the file listing
    100     * @return An FTPFile instance corresponding to the supplied entry
    101     */
    102    @Override
    103    public FTPFile parseFTPEntry(final String entry) {
    104        final FTPFile file = new FTPFile();
    105        file.setRawListing(entry);
    106        final int type;
    107        boolean isDevice = false;
    108
    109        if (matches(entry)) {
    110            final String typeStr = group(1);
    111            final String hardLinkCount = "0";
    112            final String fileSize = group(20);
    113            final String datestr = group(21) + " " + group(22);
    114            String name = group(23);
    115            final String endtoken = group(24);
    116
    117            try {
    118                file.setTimestamp(super.parseTimestamp(datestr));
    119            } catch (final ParseException e) {
    120                // intentionally do nothing
    121            }
    122
    123            // A 'whiteout' file is an ARTIFICIAL entry in any of several types of
    124            // 'translucent' filesystems, of which a 'union' filesystem is one.
    125
    126            // bcdelfmpSs-
    127            switch (typeStr.charAt(0)) {
    128            case 'd':
    129                type = FTPFile.DIRECTORY_TYPE;
    130                break;
    131            case 'e': // NET-39 => z/OS external link
    132                type = FTPFile.SYMBOLIC_LINK_TYPE;
    133                break;
    134            case 'l':
    135                type = FTPFile.SYMBOLIC_LINK_TYPE;
    136                break;
    137            case 'b':
    138            case 'c':
    139                isDevice = true;
    140                type = FTPFile.FILE_TYPE; // TODO change this if DEVICE_TYPE implemented
    141                break;
    142            case 'f':
    143            case '-':
    144                type = FTPFile.FILE_TYPE;
    145                break;
    146            default: // e.g. ? and w = whiteout
    147                type = FTPFile.UNKNOWN_TYPE;
    148            }
    149
    150            file.setType(type);
    151
    152            int g = 4;
    153            for (int access = 0; access < 3; access++, g += 4) {
    154                // Use != '-' to avoid having to check for suid and sticky bits
    155                file.setPermission(access, FTPFile.READ_PERMISSION, !group(g).equals("-"));
    156                file.setPermission(access, FTPFile.WRITE_PERMISSION, !group(g + 1).equals("-"));
    157
    158                final String execPerm = group(g + 2);
    159                file.setPermission(access, FTPFile.EXECUTE_PERMISSION, !execPerm.equals("-") && !Character.isUpperCase(execPerm.charAt(0)));
    160            }
    161
    162            if (!isDevice) {
    163                try {
    164                    file.setHardLinkCount(Integer.parseInt(hardLinkCount));
    165                } catch (final NumberFormatException e) {
    166                    // intentionally do nothing
    167                }
    168            }
    169
    170            file.setUser(null);
    171            file.setGroup(null);
    172
    173            try {
    174                file.setSize(Long.parseLong(fileSize));
    175            } catch (final NumberFormatException e) {
    176                // intentionally do nothing
    177            }
    178
    179            if (null == endtoken) {
    180                file.setName(name);
    181            } else {
    182                // oddball cases like symbolic links, file names
    183                // with spaces in them.
    184                name += endtoken;
    185                if (type == FTPFile.SYMBOLIC_LINK_TYPE) {
    186
    187                    final int end = name.indexOf(" -> ");
    188                    // Give up if no link indicator is present
    189                    if (end == -1) {
    190                        file.setName(name);
    191                    } else {
    192                        file.setName(name.substring(0, end));
    193                        file.setLink(name.substring(end + 4));
    194                    }
    195
    196                } else {
    197                    file.setName(name);
    198                }
    199            }
    200            return file;
    201        }
    202        return null;
    203    }
    204
    205}