[前][次][番号順一覧][スレッド一覧]

ruby-changes:39754

From: shugo <ko1@a...>
Date: Sat, 12 Sep 2015 17:57:34 +0900 (JST)
Subject: [ruby-changes:39754] shugo:r51835 (trunk): * lib/net/ftp.rb (mlst, mlsd): support new commands MLST and MLSD

shugo	2015-09-12 17:57:17 +0900 (Sat, 12 Sep 2015)

  New Revision: 51835

  http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=51835

  Log:
    * lib/net/ftp.rb (mlst, mlsd): support new commands MLST and MLSD
      specified in RFC 3659.

  Modified files:
    trunk/ChangeLog
    trunk/lib/net/ftp.rb
    trunk/test/net/ftp/test_ftp.rb
Index: ChangeLog
===================================================================
--- ChangeLog	(revision 51834)
+++ ChangeLog	(revision 51835)
@@ -1,3 +1,8 @@ https://github.com/ruby/ruby/blob/trunk/ChangeLog#L1
+Sat Sep 12 17:55:24 2015  Shugo Maeda  <shugo@r...>
+
+	* lib/net/ftp.rb (mlst, mlsd): support new commands MLST and MLSD
+	  specified in RFC 3659.
+
 Sat Sep 12 16:14:31 2015  SHIBATA Hiroshi  <hsbt@r...>
 
 	* file.c: access()/eaccess() wrapping methods check more than just uid.
Index: lib/net/ftp.rb
===================================================================
--- lib/net/ftp.rb	(revision 51834)
+++ lib/net/ftp.rb	(revision 51835)
@@ -17,6 +17,7 @@ https://github.com/ruby/ruby/blob/trunk/lib/net/ftp.rb#L17
 require "socket"
 require "monitor"
 require "net/protocol"
+require "time"
 
 module Net
 
@@ -767,6 +768,79 @@ module Net https://github.com/ruby/ruby/blob/trunk/lib/net/ftp.rb#L768
     alias ls list
     alias dir list
 
+    MLSxEntry = Struct.new(:facts, :pathname)
+    
+    CASE_DEPENDENT_PARSER = ->(value) { value }
+    CASE_INDEPENDENT_PARSER = ->(value) { value.downcase }
+    INTEGER_PARSER = ->(value) { value.to_i }
+    TIME_PARSER = ->(value) {
+      t = Time.strptime(value.sub(/\.\d+\z/, "") + "+00:00",
+                        "%Y%m%d%H%M%S%z").utc
+      fractions = value.slice(/\.(\d+)\z/, 1)
+      if fractions
+        t + fractions.to_i.quo(10 ** fractions.size)
+      else
+        t
+      end
+    }
+    FACT_PARSERS = Hash.new(CASE_DEPENDENT_PARSER)
+    FACT_PARSERS["size"] = INTEGER_PARSER
+    FACT_PARSERS["modify"] = TIME_PARSER
+    FACT_PARSERS["create"] = TIME_PARSER
+    FACT_PARSERS["type"] = CASE_INDEPENDENT_PARSER
+    FACT_PARSERS["unique"] = CASE_DEPENDENT_PARSER
+    FACT_PARSERS["perm"] = CASE_INDEPENDENT_PARSER
+    FACT_PARSERS["lang"] = CASE_INDEPENDENT_PARSER
+    FACT_PARSERS["media-type"] = CASE_INDEPENDENT_PARSER
+    FACT_PARSERS["charset"] = CASE_INDEPENDENT_PARSER
+
+    def parse_mlsx_entry(entry)
+      facts, pathname = entry.split(" ")
+      return MLSxEntry.new(
+        facts.scan(/(.*?)=(.*?);/).each_with_object({}) {
+          |(factname, value), h|
+          name = factname.downcase
+          h[name] = FACT_PARSERS[name].(value)
+        },
+        pathname)
+    end
+    private :parse_mlsx_entry
+
+    #
+    # Returns data (e.g., size, last modification time, entry type, etc.)
+    # about the file or directory specified by +pathname+.
+    # If +pathname+ is omitted, the current directory is assumed.
+    #
+    def mlst(pathname = nil)
+      cmd = pathname ? "MLST #{pathname}" : "MLST"
+      resp = sendcmd(cmd)
+      if !resp.start_with?("250")
+        raise FTPReplyError, resp
+      end
+      entry = resp.lines[1].sub(/\A(250-| *)/, "")
+      return parse_mlsx_entry(entry)
+    end
+
+    #
+    # Returns an array of the entries of the directory specified by
+    # +pathname+.
+    # Each entry has the facts (e.g., size, last modification time, etc.)
+    # and the pathname.
+    # If a block is given, it iterates through the listing.
+    # If +pathname+ is omitted, the current directory is assumed.
+    #
+    def mlsd(pathname = nil, &block) # :yield: entry
+      cmd = pathname ? "MLSD #{pathname}" : "MLSD"
+      entries = []
+      retrlines(cmd) do |line|
+        entries << parse_mlsx_entry(line)
+      end
+      if block
+        entries.each(&block)
+      end
+      return entries
+    end
+
     #
     # Renames a file on the server.
     #
Index: test/net/ftp/test_ftp.rb
===================================================================
--- test/net/ftp/test_ftp.rb	(revision 51834)
+++ test/net/ftp/test_ftp.rb	(revision 51835)
@@ -1119,6 +1119,117 @@ EOF https://github.com/ruby/ruby/blob/trunk/test/net/ftp/test_ftp.rb#L1119
     end
   end
 
+  def test_mlst
+    commands = []
+    server = create_ftp_server { |sock|
+      sock.print("220 (test_ftp).\r\n")
+      commands.push(sock.gets)
+      sock.print("250- Listing foo\r\n")
+      sock.print(" Type=file;Unique=FC00U1E554A;Size=1234567;Modify=20131220035929;Perm=r; /foo\r\n")
+      sock.print("250 End\r\n")
+    }
+    begin
+      begin
+        ftp = Net::FTP.new
+        ftp.connect(SERVER_ADDR, server.port)
+        entry = ftp.mlst("foo")
+        assert_equal("file", entry.facts["type"])
+        assert_equal("FC00U1E554A", entry.facts["unique"])
+        assert_equal(1234567, entry.facts["size"])
+        assert_equal("r", entry.facts["perm"])
+        modify = entry.facts["modify"]
+        assert_equal(2013, modify.year)
+        assert_equal(12, modify.month)
+        assert_equal(20, modify.day)
+        assert_equal(3, modify.hour)
+        assert_equal(59, modify.min)
+        assert_equal(29, modify.sec)
+        assert_equal(true, modify.utc?)
+        assert_match("MLST foo\r\n", commands.shift)
+        assert_equal(nil, commands.shift)
+      ensure
+        ftp.close if ftp
+      end
+    ensure
+      server.close
+    end
+  end
+
+  def test_mlsd
+    commands = []
+    entry_lines = [
+      "Type=file;Unique=FC00U1E554A;Size=1234567;Modify=20131220035929.123456;Perm=r; foo",
+      "Type=cdir;Unique=FC00U1E554B;Modify=20131220035929;Perm=flcdmpe; .",
+      "Type=pdir;Unique=FC00U1E554C;Modify=20131220035929;Perm=flcdmpe; ..",
+    ]
+    server = create_ftp_server { |sock|
+      sock.print("220 (test_ftp).\r\n")
+      commands.push(sock.gets)
+      sock.print("331 Please specify the password.\r\n")
+      commands.push(sock.gets)
+      sock.print("230 Login successful.\r\n")
+      commands.push(sock.gets)
+      sock.print("200 Switching to Binary mode.\r\n")
+      commands.push(sock.gets)
+      sock.print("200 Switching to ASCII mode.\r\n")
+      line = sock.gets
+      commands.push(line)
+      port_args = line.slice(/\APORT (.*)/, 1).split(/,/)
+      host = port_args[0, 4].join(".")
+      port = port_args[4, 2].map(&:to_i).inject {|x, y| (x << 8) + y}
+      sock.print("200 PORT command successful.\r\n")
+      commands.push(sock.gets)
+      sock.print("150 Here comes the directory listing.\r\n")
+      begin
+        conn = TCPSocket.new(host, port)
+        entry_lines.each do |line|
+          conn.print(line, "\r\n")
+        end
+      rescue Errno::EPIPE
+      ensure
+        assert_nil($!)
+        conn.close
+      end
+      sock.print("226 Directory send OK.\r\n")
+      commands.push(sock.gets)
+      sock.print("200 Switching to Binary mode.\r\n")
+    }
+    begin
+      begin
+        ftp = Net::FTP.new
+        ftp.connect(SERVER_ADDR, server.port)
+        ftp.login
+        assert_match(/\AUSER /, commands.shift)
+        assert_match(/\APASS /, commands.shift)
+        assert_equal("TYPE I\r\n", commands.shift)
+        entries = ftp.mlsd("/")
+        assert_equal(3, entries.size)
+        assert_equal("file", entries[0].facts["type"])
+        assert_equal("cdir", entries[1].facts["type"])
+        assert_equal("pdir", entries[2].facts["type"])
+        assert_equal("flcdmpe", entries[1].facts["perm"])
+        modify = entries[0].facts["modify"]
+        assert_equal(2013, modify.year)
+        assert_equal(12, modify.month)
+        assert_equal(20, modify.day)
+        assert_equal(3, modify.hour)
+        assert_equal(59, modify.min)
+        assert_equal(29, modify.sec)
+        assert_equal(123456, modify.usec)
+        assert_equal(true, modify.utc?)
+        assert_equal("TYPE A\r\n", commands.shift)
+        assert_match(/\APORT /, commands.shift)
+        assert_match("MLSD /\r\n", commands.shift)
+        assert_equal("TYPE I\r\n", commands.shift)
+        assert_equal(nil, commands.shift)
+      ensure
+        ftp.close if ftp
+      end
+    ensure
+      server.close
+    end
+  end
+
   private
 
 

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]