Net::Netrc request for comments

Discussion in 'Ruby' started by Bob Showalter, Sep 22, 2005.

  1. Hi,

    I'm a Perl-er learning ruby. I'm creating a ruby port of Perl's Net::Netrc
    module for accessing ftp(1)'s .netrc file. I couldn't find an existing ruby
    version anywhere.

    My current code is below. I've essentially ported the Perl module's usage
    semantics, although my internal implementation is quite different. I'd sure
    appreciate any feedback, especially related to doing things the "ruby way",
    with a view toward submitting this code to the appropriate archive site(s)
    at some point.

    Example usage:

    require 'net/netrc'

    n = Net::Netrc.locate('example.com')
    if (n.nil?)
    puts "No entry found"
    else
    puts "login = #{n.login}, password = #{n.password}"
    end

    Questions:

    1. Should I use locate() instead of new()? locate() is how the Perl
    module works.

    2. Should I return nil if no entry is found, or should I return an
    object with the accessors all nil?

    3. Should I be returning a Net::Netrc object at all, or just a simple
    Hash? Or should Net::Netrc sublcass Hash?

    4. Is rasing a SecurityError appropriate? Should I create a specific
    exception class instead?

    ------- BEGIN CODE --------

    module Net

    class Netrc

    attr_accessor :machine, :login, :password, :account

    # returns name of .netrc file
    def Netrc.rcname
    # TODO: cross platform? getpwuid() for home dir?
    home = ENV['HOME']
    home ||= ENV['HOMEDRIVE'] + (ENV['HOMEPATH'] || '') if
    ENV['HOMEDRIVE']
    File.join(home, '.netrc')
    end

    # opens .netrc file, returning File object if successful.
    # returns nil if .netrc not found.
    # raises SecurityError if .netrc is not owned by the current.
    # user or if it is readable or writable by other than the
    # current user.
    def Netrc.open
    name = rcname
    return nil unless File.exist?(name)
    # TODO: this stat code not applicable to Win32 (and others?)
    s = File.stat(name)
    raise SecurityError, "Not owner: #{name}" unless s.owned?
    raise SecurityError, "Bad permissions: #{name}" if s.mode & 077 != 0
    File.open(name, 'r')
    end

    # given a machine name, returns a Net::Netrc object containing
    # the matching entry for that name, or the default entry. If
    # no match is found an no default entry exists, nil is returned.
    def Netrc.locate(mach)
    f = open or return nil
    entry = nil
    key = nil
    inmacdef = false
    while line = f.gets
    if inmacdef
    inmacdef = false if line.strip.empty?
    next
    end
    toks = line.scan(/"((?:\\.|[^"])*)"|((?:\\.|\S)+)/).flatten.compact
    toks.each { |t| t.gsub!(/\\(.)/, '\1') }
    while toks.length > 0
    tok = toks.shift
    if key
    entry = new if key == 'machine' && tok == mach
    entry.send "#{key}=", tok if entry
    key = nil
    end
    case tok
    when 'default'
    return entry if entry
    entry = new
    when 'machine'
    return entry if entry
    key = 'machine'
    when 'login', 'password', 'account'
    key = tok
    end
    end
    end
    entry
    end

    end

    end

    ------- END CODE --------

    TIA for any feedback,

    Bob
     
    Bob Showalter, Sep 22, 2005
    #1
    1. Advertising

  2. Bob Showalter

    Guest

    Hi,

    At Fri, 23 Sep 2005 00:54:51 +0900,
    Bob Showalter wrote in [ruby-talk:157128]:
    > I'm a Perl-er learning ruby. I'm creating a ruby port of Perl's Net::Netrc
    > module for accessing ftp(1)'s .netrc file. I couldn't find an existing ruby
    > version anywhere.


    I'm using <http://www.rubyist.net/~nobu/ruby/netrc.rb>.

    --
    Nobu Nakada
     
    , Sep 22, 2005
    #2
    1. Advertising

  3. Bob Showalter <> wrote:
    > Hi,
    >
    > I'm a Perl-er learning ruby. I'm creating a ruby port of Perl's
    > Net::Netrc module for accessing ftp(1)'s .netrc file. I couldn't find
    > an existing ruby version anywhere.
    >
    > My current code is below. I've essentially ported the Perl module's
    > usage semantics, although my internal implementation is quite
    > different. I'd sure appreciate any feedback, especially related to
    > doing things the "ruby way", with a view toward submitting this code
    > to the appropriate archive site(s) at some point.
    >
    > Example usage:
    >
    > require 'net/netrc'
    >
    > n = Net::Netrc.locate('example.com')
    > if (n.nil?)
    > puts "No entry found"
    > else
    > puts "login = #{n.login}, password = #{n.password}"
    > end
    >
    > Questions:
    >
    > 1. Should I use locate() instead of new()? locate() is how the Perl
    > module works.


    I'd use locate only and probably make new private. Reason is that locate
    will not always return a valid object.

    > 2. Should I return nil if no entry is found, or should I return an
    > object with the accessors all nil?


    Return nil

    > 3. Should I be returning a Net::Netrc object at all, or just a simple
    > Hash? Or should Net::Netrc sublcass Hash?


    Definitely not the latter. I'd probably use an instance of Net::Netrc and
    add some methods (who says the Ruby version must not be better than the Perl
    version)? For example, I'd add a method that opens a connection with the
    info you have:

    class Netrc
    def open(do_init = true)
    conn = Ftp.open machine
    begin
    conn.login login, password
    # add code that executes all default actions if defined, maybe
    argument flag controlled
    rescue Net::FTPPermError
    conn.close
    raise
    end

    if block_given?
    begin
    yield conn
    ensure
    conn.close
    end

    nil
    else
    conn
    end
    end
    end

    Then you can do

    n = Net::Netrc.locate('example.com')

    if n
    n.open do |ftp|
    files = ftp.chdir('pub/lang/ruby/contrib')
    files = ftp.list('n*')
    ftp.getbinaryfile('nif.rb-0.91.gz', 'nif.gz', 1024)
    end
    else
    puts "not found"
    end

    You could even make this more convenient by adding a class method open that
    does this

    class Netrc
    def self.open(machine, do_init = true, &b)
    n = locate machine
    raise "error" unless n
    n.open(do_init, &b)
    end
    end

    Then you can do

    Net::Netrc.open('ftp.foo.bar') do |ftp|
    ...
    end

    Uh, just detected a name clash. I'd rename your open as this seems to be a
    rather internal method.

    > 4. Is rasing a SecurityError appropriate? Should I create a specific
    > exception class instead?


    I think it's appropriate. However, it seems to me that if you want to mimic
    FTP's behavior then you should raise the exception only if you find a
    password:
    http://www.die.net/doc/linux/man/man5/netrc.5.html

    > ------- BEGIN CODE --------
    >
    > module Net
    >
    > class Netrc
    >
    > attr_accessor :machine, :login, :password, :account
    >
    > # returns name of .netrc file
    > def Netrc.rcname
    > # TODO: cross platform? getpwuid() for home dir?
    > home = ENV['HOME']
    > home ||= ENV['HOMEDRIVE'] + (ENV['HOMEPATH'] || '') if
    > ENV['HOMEDRIVE']
    > File.join(home, '.netrc')
    > end
    >
    > # opens .netrc file, returning File object if successful.
    > # returns nil if .netrc not found.
    > # raises SecurityError if .netrc is not owned by the current.
    > # user or if it is readable or writable by other than the
    > # current user.
    > def Netrc.open
    > name = rcname
    > return nil unless File.exist?(name)
    > # TODO: this stat code not applicable to Win32 (and others?)
    > s = File.stat(name)
    > raise SecurityError, "Not owner: #{name}" unless s.owned?
    > raise SecurityError, "Bad permissions: #{name}" if s.mode & 077
    > != 0 File.open(name, 'r')
    > end
    >
    > # given a machine name, returns a Net::Netrc object containing
    > # the matching entry for that name, or the default entry. If
    > # no match is found an no default entry exists, nil is returned.
    > def Netrc.locate(mach)
    > f = open or return nil
    > entry = nil
    > key = nil
    > inmacdef = false
    > while line = f.gets
    > if inmacdef
    > inmacdef = false if line.strip.empty?
    > next
    > end
    > toks =
    > line.scan(/"((?:\\.|[^"])*)"|((?:\\.|\S)+)/).flatten.compact
    > toks.each { |t| t.gsub!(/\\(.)/, '\1') } while toks.length > 0


    # what do you need that "while toks.length > 0" for? Seems completely
    superfluous to me. Or is this an indentation problem and the "while" should
    have been on the next line? Hmm, probably...

    I'd probably do the parsing a bit different: I would have to think about
    this a bit more but I'd keep a set of settings for default and a set for the
    machine and directly return if I found the machine. Just a rough idea...

    > tok = toks.shift
    > if key
    > entry = new if key == 'machine' && tok == mach
    > entry.send "#{key}=", tok if entry
    > key = nil
    > end
    > case tok
    > when 'default'
    > return entry if entry
    > entry = new
    > when 'machine'
    > return entry if entry
    > key = 'machine'
    > when 'login', 'password', 'account'
    > key = tok
    > end
    > end
    > end
    > entry
    > end
    >
    > end
    >
    > end
    >
    > ------- END CODE --------
    >
    > TIA for any feedback,
    >
    > Bob


    You're welcome!

    Kind regards

    robert
     
    Robert Klemme, Sep 22, 2005
    #3
  4. wrote:
    > Hi,
    >
    > At Fri, 23 Sep 2005 00:54:51 +0900,
    > Bob Showalter wrote in [ruby-talk:157128]:
    >> I'm a Perl-er learning ruby. I'm creating a ruby port of Perl's
    >> Net::Netrc module for accessing ftp(1)'s .netrc file. I couldn't
    >> find an existing ruby version anywhere.

    >
    > I'm using <http://www.rubyist.net/~nobu/ruby/netrc.rb>.


    "Internal Server Error" - Hm...

    robert
     
    Robert Klemme, Sep 22, 2005
    #4
  5. Bob Showalter

    Guest

    Hi,

    At Fri, 23 Sep 2005 01:46:40 +0900,
    Robert Klemme wrote in [ruby-talk:157135]:
    > > I'm using <http://www.rubyist.net/~nobu/ruby/netrc.rb>.

    >
    > "Internal Server Error" - Hm...


    Hmmm, I can't stop it from seeing .rb as CGI. Try
    <http://www.rubyist.net/~nobu/ruby/netrc_rb.txt>

    --
    Nobu Nakada
     
    , Sep 23, 2005
    #5
  6. wrote:
    > Hi,
    >
    > At Fri, 23 Sep 2005 01:46:40 +0900,
    > Robert Klemme wrote in [ruby-talk:157135]:
    >>> I'm using <http://www.rubyist.net/~nobu/ruby/netrc.rb>.

    >>
    >> "Internal Server Error" - Hm...

    >
    > Hmmm, I can't stop it from seeing .rb as CGI. Try
    > http://www.rubyist.net/~nobu/ruby/netrc_rb.txt


    Thanks, I can see it now. Looks like you subclassed Hash. I will study your
    code more. Your parsing logic seems much more complex than mine; perhaps I'm
    missing something.

    I note that your technique is similar to the Perl module in that you load
    the entire file into a hash. But this conflicts with the documentation in
    that once a matching "machine" entry is found or the "default" entry is
    found, parsing stops when the next "machine" or "default" entry (or eof) is
    found.

    So if I have:

    machine foo ...
    default ...
    machine bar ...

    ftp(1) will not see the "bar" entry (but instead would return the default
    login), while your code (and the Perl code) will return the "bar" entry.
     
    Bob Showalter, Sep 23, 2005
    #6
  7. Hi,

    At Fri, 23 Sep 2005 09:52:06 +0900,
    Bob Showalter wrote in [ruby-talk:157201]:
    > I note that your technique is similar to the Perl module in that you load
    > the entire file into a hash. But this conflicts with the documentation in
    > that once a matching "machine" entry is found or the "default" entry is
    > found, parsing stops when the next "machine" or "default" entry (or eof) is
    > found.


    Thank you, I haven't noticed it. But is that behaviors better?

    --
    Nobu Nakada
     
    nobuyoshi nakada, Sep 26, 2005
    #7
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Replies:
    0
    Views:
    1,167
  2. Nikolai Weibull
    Replies:
    0
    Views:
    89
    Nikolai Weibull
    Oct 18, 2005
  3. Bob Showalter

    [ANN] Net::Netrc 0.1.0

    Bob Showalter, Dec 15, 2005, in forum: Ruby
    Replies:
    1
    Views:
    91
    Nikolai Weibull
    Dec 17, 2005
  4. loial
    Replies:
    0
    Views:
    113
    loial
    Sep 5, 2013
  5. pratibha natani

    httplib with NETRC authentication

    pratibha natani, May 14, 2014, in forum: Python
    Replies:
    4
    Views:
    107
    Chris Angelico
    May 14, 2014
Loading...

Share This Page