Ejemplo n.º 1
0
    def parse_unix_mode(self, mode_string):
        """
        Return an integer from the `mode_string`, compatible with
        the `st_mode` value in stat results. Such a mode string
        may look like "drwxr-xr-x".

        If the mode string can't be parsed, raise an
        `ftp_error.ParserError`.
        """
        st_mode = 0
        if len(mode_string) != 10:
            raise ftp_error.ParserError("invalid mode string '%s'" %
                                        mode_string)
        for bit in mode_string[1:10]:
            bit = (bit != '-')
            st_mode = (st_mode << 1) + bit
        if mode_string[3] == 's':
            st_mode = st_mode | stat.S_ISUID
        if mode_string[6] == 's':
            st_mode = st_mode | stat.S_ISGID
        file_type_to_mode = {'d': stat.S_IFDIR, 'l': stat.S_IFLNK,
                             'c': stat.S_IFCHR, '-': stat.S_IFREG}
        file_type = mode_string[0]
        if file_type in file_type_to_mode:
            st_mode = st_mode | file_type_to_mode[file_type]
        else:
            raise ftp_error.ParserError(
                  "unknown file type character '%s'" % file_type)
        return st_mode
Ejemplo n.º 2
0
    def parse_line(self, line, time_shift=0.0):
        """
        Return a `StatResult` instance corresponding to the given
        text line from a FTP server which emits "Microsoft format"
        (see end of file).

        If the line can't be parsed, raise a `ParserError`.

        The parameter `time_shift` isn't used in this method but is
        listed for compatibilty with the base class.
        """
        try:
            date, time_, dir_or_size, name = line.split(None, 3)
        except ValueError:
            # "unpack list of wrong size"
            raise ftp_error.ParserError("line '%s' can't be parsed" % line)
        # st_mode
        #  Default to read access only; in fact, we can't tell.
        st_mode = 0400
        if dir_or_size == "<DIR>":
            st_mode = st_mode | stat.S_IFDIR
        else:
            st_mode = st_mode | stat.S_IFREG
        # st_ino, st_dev, st_nlink, st_uid, st_gid
        st_ino = None
        st_dev = None
        st_nlink = None
        st_uid = None
        st_gid = None
        # st_size
        if dir_or_size != "<DIR>":
            try:
                st_size = int(dir_or_size)
            except ValueError:
                raise ftp_error.ParserError("invalid size %s" % dir_or_size)
        else:
            st_size = None
        # st_atime
        st_atime = None
        # st_mtime
        st_mtime = self.parse_ms_time(date, time_, time_shift)
        # st_ctime
        st_ctime = None
        stat_result = StatResult(
            (st_mode, st_ino, st_dev, st_nlink, st_uid, st_gid, st_size,
             st_atime, st_mtime, st_ctime))
        # _st_name and _st_target
        stat_result._st_name = name
        stat_result._st_target = None
        # mtime precision in seconds
        stat_result._st_mtime_precision = 60
        return stat_result
Ejemplo n.º 3
0
 def _split_line(self, line):
     """
     Split a line in metadata, nlink, user, group, size, month,
     day, year_or_time and name and return the result as an
     nine-element list of these values. If the name is a link,
     it will be encoded as a string "link_name -> link_target".
     """
     # This method encapsulates the recognition of an unusual
     #  Unix format variant (see ticket
     #  http://ftputil.sschwarzer.net/trac/ticket/12 ).
     line_parts = line.split()
     FIELD_COUNT_WITHOUT_USERID = 8
     FIELD_COUNT_WITH_USERID = FIELD_COUNT_WITHOUT_USERID + 1
     if len(line_parts) < FIELD_COUNT_WITHOUT_USERID:
         # No known Unix-style format
         raise ftp_error.ParserError("line '%s' can't be parsed" % line)
     # If we have a valid format (either with or without user id field),
     #  the field with index 5 is either the month abbreviation or a day.
     try:
         int(line_parts[5])
     except ValueError:
         # Month abbreviation, "invalid literal for int"
         line_parts = line.split(None, FIELD_COUNT_WITH_USERID - 1)
     else:
         # Day
         line_parts = line.split(None, FIELD_COUNT_WITHOUT_USERID - 1)
         USER_FIELD_INDEX = 2
         line_parts.insert(USER_FIELD_INDEX, None)
     return line_parts
Ejemplo n.º 4
0
 def _split_line(self, line):
     """
     Split a line in metadata, nlink, user, group, size, month,
     day, year_or_time and name and return the result as an
     nine-element list of these values.
     """
     # This method encapsulates the recognition of an unusual
     #  Unix format variant (see ticket
     #  http://ftputil.sschwarzer.net/trac/ticket/12 )
     parts = line.split(None, 8)
     if len(parts) == 9:
         if parts[-1].startswith("-> "):
             # for the alternative format, the last part will not be
             #  "link_name -> link_target" but "-> link_target" and the
             #  link name will be in the previous field;
             # this heuristic will fail for names starting with "-> "
             #  which should be _quite_ rare
             # insert `None` for the user field
             parts.insert(2, None)
             parts[-2] = "%s %s" % tuple(parts[-2:])
             del parts[-1]
         return parts
     elif len(parts) == 8:
         # alternative unusual format, insert `None` for the user field
         parts.insert(2, None)
         return parts
     else:
         # no known Unix-style format
         raise ftp_error.ParserError("line '%s' can't be parsed" % line)
Ejemplo n.º 5
0
    def parse_ms_time(self, date, time_, time_shift):
        """
        Return a floating point number, like from `time.mktime`, by
        parsing the string arguments `date` and `time_`. The parameter
        `time_shift` is the difference
        "time on server" - "time on client"
        and is available as the `time_shift` parameter in the
        `parse_line` interface.

        Times in MS-style directory listings typically have the
        format "10-23-01 03:25PM" (month-day_of_month-two_digit_year,
        hour:minute, am/pm).

        If this method can not make sense of the given arguments, it
        raises an `ftp_error.ParserError`.
        """
        try:
            month, day, year = map(int, date.split('-'))
            if year >= 70:
                year = 1900 + year
            else:
                year = 2000 + year
            hour, minute, am_pm = time_[0:2], time_[3:5], time_[5]
            hour, minute = int(hour), int(minute)
        except (ValueError, IndexError):
            raise ftp_error.ParserError("invalid time string '%s'" % time_)
        if am_pm == 'P':
            hour = hour + 12
        st_mtime = time.mktime( (year, month, day,
                                 hour, minute, 0, 0, 0, -1) )
        return st_mtime
Ejemplo n.º 6
0
    def parse_unix_mode(self, mode_string):
        """
        Return an integer from the `mode_string`, compatible with
        the `st_mode` value in stat results. Such a mode string
        may look like "drwxr-xr-x".

        If the mode string can't be parsed, raise an
        `ftp_error.ParserError`.
        """
        if len(mode_string) != 10:
            raise ftp_error.ParserError("invalid mode string '%s'" %
                                        mode_string)
        st_mode = 0
        #TODO Add support for "S" and sticky bit ("t", "T").
        for bit in mode_string[1:10]:
            bit = (bit != '-')
            st_mode = (st_mode << 1) + bit
        if mode_string[3] == 's':
            st_mode = st_mode | stat.S_ISUID
        if mode_string[6] == 's':
            st_mode = st_mode | stat.S_ISGID
        file_type_to_mode = {
            'b': stat.S_IFBLK,
            'c': stat.S_IFCHR,
            'd': stat.S_IFDIR,
            'l': stat.S_IFLNK,
            'p': stat.S_IFIFO,
            's': stat.S_IFSOCK,
            '-': stat.S_IFREG,
            # Ignore types which `ls` can't make sense of
            #  (assuming the FTP server returns listings
            #  like `ls` does).
            '?': 0,
        }
        file_type = mode_string[0]
        if file_type in file_type_to_mode:
            st_mode = st_mode | file_type_to_mode[file_type]
        else:
            raise ftp_error.ParserError("unknown file type character '%s'" %
                                        file_type)
        return st_mode
Ejemplo n.º 7
0
    def parse_unix_time(self, month_abbreviation, day, year_or_time,
                        time_shift):
        """
        Return a floating point number, like from `time.mktime`, by
        parsing the string arguments `month_abbreviation`, `day` and
        `year_or_time`. The parameter `time_shift` is the difference
        "time on server" - "time on client" and is available as the
        `time_shift` parameter in the `parse_line` interface.

        Times in Unix-style directory listings typically have one of
        these formats:

        - "Nov 23 02:33" (month name, day of month, time)

        - "May 26  2005" (month name, day of month, year)

        If this method can not make sense of the given arguments, it
        raises an `ftp_error.ParserError`.
        """
        try:
            month = self._month_numbers[month_abbreviation.lower()]
        except KeyError:
            raise ftp_error.ParserError("invalid month name '%s'" % month)
        day = int(day)
        if ":" not in year_or_time:
            # `year_or_time` is really a year
            year, hour, minute = int(year_or_time), 0, 0
            st_mtime = time.mktime( (year, month, day,
                                     hour, minute, 0, 0, 0, -1) )
        else:
            # `year_or_time` is a time hh:mm
            hour, minute = year_or_time.split(':')
            year, hour, minute = None, int(hour), int(minute)
            # try the current year
            year = time.localtime()[0]
            st_mtime = time.mktime( (year, month, day,
                                     hour, minute, 0, 0, 0, -1) )
            # rhs of comparison: transform client time to server time
            #  (as on the lhs), so both can be compared with respect
            #  to the set time shift (see the definition of the time
            #  shift in `FTPHost.set_time_shift`'s docstring); the
            #  last addend allows for small deviations between the
            #  supposed (rounded) and the actual time shift
            # #XXX the downside of this "correction" is that there is
            #  a one-minute time interval exactly one year ago that
            #  may cause that datetime to be recognized as the current
            #  datetime, but after all the datetime from the server
            #  can only be exact up to a minute
            if st_mtime > time.time() + time_shift + 60.0:
                # if it's in the future, use previous year
                st_mtime = time.mktime( (year-1, month, day,
                                         hour, minute, 0, 0, 0, -1) )
        return st_mtime
Ejemplo n.º 8
0
    def parse_line(self, line, time_shift=0.0):
        """
        Return a `StatResult` instance corresponding to the given
        text line. The `time_shift` value is needed to determine
        to which year a datetime without an explicit year belongs.

        If the line can't be parsed, raise a `ParserError`.
        """
        mode_string, nlink, user, group, size, month, day, \
          year_or_time, name = self._split_line(line)
        # st_mode
        st_mode = self.parse_unix_mode(mode_string)
        # st_ino, st_dev, st_nlink, st_uid, st_gid, st_size, st_atime
        st_ino = None
        st_dev = None
        st_nlink = int(nlink)
        st_uid = user
        st_gid = group
        st_size = int(size)
        st_atime = None
        # st_mtime
        st_mtime, st_mtime_precision = \
          self.parse_unix_time(month, day, year_or_time, time_shift,
                               with_precision=True)
        # st_ctime
        st_ctime = None
        # st_name
        if name.count(" -> ") > 1:
            # If we have more than one arrow we can't tell where the link
            #  name ends and the target name starts.
            raise ftp_error.ParserError(
                'name "%s" contains more than one "->"' % name)
        elif name.count(" -> ") == 1:
            st_name, st_target = name.split(' -> ')
        else:
            st_name, st_target = name, None
        stat_result = StatResult(
            (st_mode, st_ino, st_dev, st_nlink, st_uid, st_gid, st_size,
             st_atime, st_mtime, st_ctime))
        stat_result._st_mtime_precision = st_mtime_precision
        stat_result._st_name = st_name
        stat_result._st_target = st_target
        return stat_result
Ejemplo n.º 9
0
    def parse_ms_time(self, date, time_, time_shift):
        """
        Return a floating point number, like from `time.mktime`, by
        parsing the string arguments `date` and `time_`. The parameter
        `time_shift` is the difference

            "time on server" - "time on client"

        and can be set as the `time_shift` parameter in the
        `parse_line` interface.

        Times in MS-style directory listings typically have the
        format "10-23-01 03:25PM" (month-day_of_month-two_digit_year,
        hour:minute, am/pm).

        If this method can not make sense of the given arguments, it
        raises an `ftp_error.ParserError`.
        """
        # Don't complain about unused `time_shift` argument
        # pylint: disable=W0613
        # For the time being, I don't add a `with_precision`
        #  parameter as in the Unix parser because the precision for
        #  the DOS format is always a minute and can be set in
        #  `MSParser.parse_line`. Should you find yourself needing
        #  support for `with_precision` for a derived class, please
        #  send a mail (see ftputil.txt/html).
        try:
            month, day, year = [int(part) for part in date.split('-')]
            if year >= 70:
                year = 1900 + year
            else:
                year = 2000 + year
            hour, minute, am_pm = time_[0:2], time_[3:5], time_[5]
            hour, minute = int(hour), int(minute)
        except (ValueError, IndexError):
            raise ftp_error.ParserError("invalid time string '%s'" % time_)
        if hour == 12 and am_pm == 'A':
            hour = 0
        if hour != 12 and am_pm == 'P':
            hour = hour + 12
        st_mtime = time.mktime((year, month, day, hour, minute, 0, 0, 0, -1))
        return st_mtime
Ejemplo n.º 10
0
    def parse_unix_time(self,
                        month_abbreviation,
                        day,
                        year_or_time,
                        time_shift,
                        with_precision=False):
        """
        Return a floating point number, like from `time.mktime`, by
        parsing the string arguments `month_abbreviation`, `day` and
        `year_or_time`. The parameter `time_shift` is the difference
        "time on server" - "time on client" and is available as the
        `time_shift` parameter in the `parse_line` interface.

        If `with_precision` is true (default: false), return a
        two-element tuple consisting of the floating point number as
        described in the previous paragraph and the precision of the
        time in seconds. This takes into account that, for example, a
        time string like "May 26  2005" has only a precision of one
        day. This information is important for the `upload_if_newer`
        and `download_if_newer` methods in the `FTPHost` class.

        Times in Unix-style directory listings typically have one of
        these formats:

        - "Nov 23 02:33" (month name, day of month, time)

        - "May 26  2005" (month name, day of month, year)

        If this method can not make sense of the given arguments, it
        raises an `ftp_error.ParserError`.
        """
        try:
            month = self._month_numbers[month_abbreviation.lower()]
        except KeyError:
            raise ftp_error.ParserError("invalid month name '%s'" % month)
        day = int(day)
        if ":" not in year_or_time:
            # `year_or_time` is really a year.
            year, hour, minute = int(year_or_time), 0, 0
            st_mtime = time.mktime(
                (year, month, day, hour, minute, 0, 0, 0, -1))
            # Precise up to a day.
            st_mtime_precision = 24 * 60 * 60
        else:
            # `year_or_time` is a time hh:mm.
            hour, minute = year_or_time.split(':')
            year, hour, minute = None, int(hour), int(minute)
            # Try the current year
            year = time.localtime()[0]
            st_mtime = time.mktime(
                (year, month, day, hour, minute, 0, 0, 0, -1))
            # Times are precise up to a minute.
            st_mtime_precision = 60
            # Rhs of comparison: Transform client time to server time
            #  (as on the lhs), so both can be compared with respect
            #  to the set time shift (see the definition of the time
            #  shift in `FTPHost.set_time_shift`'s docstring). The
            #  last addend allows for small deviations between the
            #  supposed (rounded) and the actual time shift.
            # #XXX The downside of this "correction" is that there is
            #  a one-minute time interval exactly one year ago that
            #  may cause that datetime to be recognized as the current
            #  datetime, but after all the datetime from the server
            #  can only be exact up to a minute.
            if st_mtime > time.time() + time_shift + st_mtime_precision:
                # If it's in the future, use previous year.
                st_mtime = time.mktime(
                    (year - 1, month, day, hour, minute, 0, 0, 0, -1))
        if with_precision:
            return (st_mtime, st_mtime_precision)
        else:
            return st_mtime