Import fixes from kschiess

This commit is contained in:
Rory OConnell 2010-02-10 12:13:59 -06:00
parent d37c3b3ae6
commit 4c24cf239a
6 changed files with 249 additions and 248 deletions

View file

@ -7,12 +7,18 @@ require 'hoe'
$LOAD_PATH.unshift( "#{File.dirname(__FILE__)}/lib" ) $LOAD_PATH.unshift( "#{File.dirname(__FILE__)}/lib" )
# Pull in local 'net/ldap' as opposed to an installed version. # Pull in local 'net/ldap' as opposed to an installed version.
require 'net/ldap' require 'net'
Hoe.new('net-ldap', Net::LDAP::VERSION) do |p| Hoe.spec "net-ldap" do
p.rubyforge_name = 'net-ldap' developer 'Francis Cianfrocca', 'garbagecat10@gmail.com'
p.developer('Francis Cianfrocca', 'garbagecat10@gmail.com') developer 'Emiel van de Laar', 'gemiel@gmail.com'
p.developer('Emiel van de Laar', 'gemiel@gmail.com') developer "Rory O'Connell", 'rory.ocon@gmail.com'
end end
# Hoe.new('net-ldap', Net::LDAP::VERSION) do |p|
# p.rubyforge_name = 'net-ldap'
# p.developer('Francis Cianfrocca', 'garbagecat10@gmail.com')
# p.developer('Emiel van de Laar', 'gemiel@gmail.com')
# end
# vim: syntax=Ruby # vim: syntax=Ruby

View file

@ -40,100 +40,97 @@ module Net
# of this one. # of this one.
# #
def read_ber syntax=nil def read_ber syntax=nil
# don't bother with this line, since IO#getc by definition returns nil on eof. # don't bother with this line, since IO#getc by definition returns nil on eof.
#return nil if eof? #return nil if eof?
# here we'll create two different procs, one for 1.8 and one for 1.9 # here we'll create two different procs, one for 1.8 and one for 1.9
# the reason being getc doesn't return a byte value in 1.9, so we need to # the reason being getc doesn't return a byte value in 1.9, so we need to
# get the byte code out of the 1.9 encoded string # get the byte code out of the 1.9 encoded string
if RUBY_VERSION =~ /^1\.9/ if RUBY_VERSION =~ /^1\.9/
fetch_byte = Proc.new { getc.bytes.first } fetch_byte = Proc.new { getc.bytes.first }
elsif RUBY_VERSION =~ /^1\.8/ elsif RUBY_VERSION =~ /^1\.8/
fetch_byte = Proc.new { getc } fetch_byte = Proc.new { getc }
end
id = fetch_byte.call or return nil # don't trash this value, we'll use it later
#tag = id & 31
#tag < 31 or raise BerError.new( "unsupported tag encoding: #{id}" )
#tagclass = TagClasses[ id >> 6 ]
#encoding = (id & 0x20 != 0) ? :constructed : :primitive
n = fetch_byte.call
lengthlength,contentlength = if n <= 127
[1,n]
else
# Replaced the inject because it profiles hot.
#j = (0...(n & 127)).inject(0) {|mem,x| mem = (mem << 8) + getc}
j = 0
read( n & 127 ).each_byte {|n1| j = (j << 8) + n1}
[1 + (n & 127), j]
end
newobj = read contentlength
# This exceptionally clever and clear bit of code is verrrry slow.
objtype = (syntax && syntax[id]) || BuiltinSyntax[id]
# == is expensive so sort this if/else so the common cases are at the top.
obj = if objtype == :string
#(newobj || "").dup
s = BerIdentifiedString.new( newobj || "" )
s.ber_identifier = id
s
elsif objtype == :integer
j = 0
newobj.each_byte {|b| j = (j << 8) + b}
j
elsif objtype == :oid
# cf X.690 pgh 8.19 for an explanation of this algorithm.
# Potentially not good enough. We may need a BerIdentifiedOid
# as a subclass of BerIdentifiedArray, to get the ber identifier
# and also a to_s method that produces the familiar dotted notation.
oid = newobj.unpack("w*")
f = oid.shift
g = if f < 40
[0, f]
elsif f < 80
[1, f-40]
else
[2, f-80] # f-80 can easily be > 80. What a weird optimization.
end end
oid.unshift g.last
oid.unshift g.first
oid
elsif objtype == :array
#seq = []
seq = BerIdentifiedArray.new
seq.ber_identifier = id
sio = StringIO.new( newobj || "" )
# Interpret the subobject, but note how the loop
# is built: nil ends the loop, but false (a valid
# BER value) does not!
while (e = sio.read_ber(syntax)) != nil
seq << e
end
seq
elsif objtype == :boolean
newobj != "\000"
elsif objtype == :null
n = BerIdentifiedNull.new
n.ber_identifier = id
n
else
#raise BerError.new( "unsupported object type: class=#{tagclass}, encoding=#{encoding}, tag=#{tag}" )
raise BerError.new( "unsupported object type: id=#{id}" )
end
# Add the identifier bits into the object if it's a String or an Array. id = fetch_byte.call or return nil # don't trash this value, we'll use it later
# We can't add extra stuff to Fixnums and booleans, not that it makes much sense anyway. #tag = id & 31
# Replaced this mechanism with subclasses because the instance_eval profiled too hot. #tag < 31 or raise BerError.new( "unsupported tag encoding: #{id}" )
#obj and ([String,Array].include? obj.class) and obj.instance_eval "def ber_identifier; #{id}; end" #tagclass = TagClasses[ id >> 6 ]
#obj.ber_identifier = id if obj.respond_to?(:ber_identifier) #encoding = (id & 0x20 != 0) ? :constructed : :primitive
obj
end n = fetch_byte.call
lengthlength,contentlength = if n <= 127
[1,n]
else
# Replaced the inject because it profiles hot.
#j = (0...(n & 127)).inject(0) {|mem,x| mem = (mem << 8) + getc}
j = 0
read( n & 127 ).each_byte {|n1| j = (j << 8) + n1}
[1 + (n & 127), j]
end
newobj = read contentlength
# This exceptionally clever and clear bit of code is verrrry slow.
objtype = (syntax && syntax[id]) || BuiltinSyntax[id]
# == is expensive so sort this if/else so the common cases are at the top.
obj = if objtype == :string
#(newobj || "").dup
s = BerIdentifiedString.new( newobj || "" )
s.ber_identifier = id
s
elsif objtype == :integer
j = 0
newobj.each_byte {|b| j = (j << 8) + b}
j
elsif objtype == :oid
# cf X.690 pgh 8.19 for an explanation of this algorithm.
# Potentially not good enough. We may need a BerIdentifiedOid
# as a subclass of BerIdentifiedArray, to get the ber identifier
# and also a to_s method that produces the familiar dotted notation.
oid = newobj.unpack("w*")
f = oid.shift
g = if f < 40
[0, f]
elsif f < 80
[1, f-40]
else
[2, f-80] # f-80 can easily be > 80. What a weird optimization.
end
oid.unshift g.last
oid.unshift g.first
oid
elsif objtype == :array
#seq = []
seq = BerIdentifiedArray.new
seq.ber_identifier = id
sio = StringIO.new( newobj || "" )
# Interpret the subobject, but note how the loop
# is built: nil ends the loop, but false (a valid
# BER value) does not!
while (e = sio.read_ber(syntax)) != nil
seq << e
end
seq
elsif objtype == :boolean
newobj != "\000"
elsif objtype == :null
n = BerIdentifiedNull.new
n.ber_identifier = id
n
else
#raise BerError.new( "unsupported object type: class=#{tagclass}, encoding=#{encoding}, tag=#{tag}" )
raise BerError.new( "unsupported object type: id=#{id}" )
end
# Add the identifier bits into the object if it's a String or an Array.
# We can't add extra stuff to Fixnums and booleans, not that it makes much sense anyway.
# Replaced this mechanism with subclasses because the instance_eval profiled too hot.
#obj and ([String,Array].include? obj.class) and obj.instance_eval "def ber_identifier; #{id}; end"
#obj.ber_identifier = id if obj.respond_to?(:ber_identifier)
obj
end
#-- #--
# Violates DRY! This replicates the functionality of #read_ber. # Violates DRY! This replicates the functionality of #read_ber.
@ -145,24 +142,24 @@ module Net
# #
# Observe that weirdly we recursively call the original #read_ber in here. # Observe that weirdly we recursively call the original #read_ber in here.
# That needs to be fixed if we ever obsolete the original method in favor of this one. # That needs to be fixed if we ever obsolete the original method in favor of this one.
def read_ber_from_string str, syntax=nil def read_ber_from_string str, syntax=nil
id = str[0] or return nil id = str[0].ord or return nil
n = str[1] or return nil n = str[1].ord or return nil
n_consumed = 2 n_consumed = 2
lengthlength,contentlength = if n <= 127 lengthlength,contentlength = if n <= 127
[1,n] [1,n]
else else
n1 = n & 127 n1 = n & 127
return nil unless str.length >= (n_consumed + n1) return nil unless str.length >= (n_consumed + n1)
j = 0 j = 0
n1.times { n1.times {
j = (j << 8) + str[n_consumed] j = (j << 8) + str[n_consumed]
n_consumed += 1 n_consumed += 1
} }
[1 + (n1), j] [1 + (n1), j]
end end
return nil unless str.length >= (n_consumed + contentlength) return nil unless str.length >= (n_consumed + contentlength)
newobj = str[n_consumed...(n_consumed + contentlength)] newobj = str[n_consumed...(n_consumed + contentlength)]
n_consumed += contentlength n_consumed += contentlength
@ -170,52 +167,52 @@ module Net
# == is expensive so sort this if/else so the common cases are at the top. # == is expensive so sort this if/else so the common cases are at the top.
obj = if objtype == :array obj = if objtype == :array
seq = BerIdentifiedArray.new seq = BerIdentifiedArray.new
seq.ber_identifier = id seq.ber_identifier = id
sio = StringIO.new( newobj || "" ) sio = StringIO.new( newobj || "" )
# Interpret the subobject, but note how the loop # Interpret the subobject, but note how the loop
# is built: nil ends the loop, but false (a valid # is built: nil ends the loop, but false (a valid
# BER value) does not! # BER value) does not!
# Also, we can use the standard read_ber method because # Also, we can use the standard read_ber method because
# we know for sure we have enough data. (Although this # we know for sure we have enough data. (Although this
# might be faster than the standard method.) # might be faster than the standard method.)
while (e = sio.read_ber(syntax)) != nil while (e = sio.read_ber(syntax)) != nil
seq << e seq << e
end end
seq seq
elsif objtype == :string elsif objtype == :string
s = BerIdentifiedString.new( newobj || "" ) s = BerIdentifiedString.new( newobj || "" )
s.ber_identifier = id s.ber_identifier = id
s s
elsif objtype == :integer elsif objtype == :integer
j = 0 j = 0
newobj.each_byte {|b| j = (j << 8) + b} newobj.each_byte {|b| j = (j << 8) + b}
j j
elsif objtype == :oid elsif objtype == :oid
# cf X.690 pgh 8.19 for an explanation of this algorithm. # cf X.690 pgh 8.19 for an explanation of this algorithm.
# Potentially not good enough. We may need a BerIdentifiedOid # Potentially not good enough. We may need a BerIdentifiedOid
# as a subclass of BerIdentifiedArray, to get the ber identifier # as a subclass of BerIdentifiedArray, to get the ber identifier
# and also a to_s method that produces the familiar dotted notation. # and also a to_s method that produces the familiar dotted notation.
oid = newobj.unpack("w*") oid = newobj.unpack("w*")
f = oid.shift f = oid.shift
g = if f < 40 g = if f < 40
[0,f] [0,f]
elsif f < 80 elsif f < 80
[1, f-40] [1, f-40]
else else
[2, f-80] # f-80 can easily be > 80. What a weird optimization. [2, f-80] # f-80 can easily be > 80. What a weird optimization.
end end
oid.unshift g.last oid.unshift g.last
oid.unshift g.first oid.unshift g.first
oid oid
elsif objtype == :boolean elsif objtype == :boolean
newobj != "\000" newobj != "\000"
elsif objtype == :null elsif objtype == :null
n = BerIdentifiedNull.new n = BerIdentifiedNull.new
n.ber_identifier = id n.ber_identifier = id
n n
else else
raise BerError.new( "unsupported object type: id=#{id}" ) raise BerError.new( "unsupported object type: id=#{id}" )
end end
[obj, n_consumed] [obj, n_consumed]

View file

@ -29,80 +29,78 @@
module Net module Net
class LDAP class LDAP
class Dataset < Hash
class Dataset < Hash attr_reader :comments
class IOFilter
attr_reader :comments def initialize(io)
@io = io
end
def Dataset::read_ldif io
ds = Dataset.new def gets
s = @io.gets
line = io.gets && chomp s.chomp if s
dn = nil
while line
io.gets and chomp
if $_ =~ /^[\s]+/
line << " " << $'
else
nextline = $_
if line =~ /^\#/
ds.comments << line
elsif line =~ /^dn:[\s]*/i
dn = $'
ds[dn] = Hash.new {|k,v| k[v] = []}
elsif line.length == 0
dn = nil
elsif line =~ /^([^:]+):([\:]?)[\s]*/
# $1 is the attribute name
# $2 is a colon iff the attr-value is base-64 encoded
# $' is the attr-value
# Avoid the Base64 class because not all Ruby versions have it.
attrvalue = ($2 == ":") ? $'.unpack('m').shift : $'
ds[dn][$1.downcase.intern] << attrvalue
end end
line = nextline
end end
end
def self.read_ldif io
ds = Dataset.new
line = io.gets
dn = nil
while line
io.gets and chomp
if new_line =~ /^[\s]+/
line << " " << $'
else
nextline = new_line
if line =~ /^\#/
ds.comments << line
elsif line =~ /^dn:[\s]*/i
dn = $'
ds[dn] = Hash.new {|k,v| k[v] = []}
elsif line.length == 0
dn = nil
elsif line =~ /^([^:]+):([\:]?)[\s]*/
# $1 is the attribute name
# $2 is a colon iff the attr-value is base-64 encoded
# $' is the attr-value
# Avoid the Base64 class because not all Ruby versions have it.
attrvalue = ($2 == ":") ? $'.unpack('m').shift : $'
ds[dn][$1.downcase.intern] << attrvalue
end
line = nextline
end
end
ds ds
end end
def initialize def initialize
@comments = [] @comments = []
end end
def to_ldif def to_ldif
ary = [] ary = []
ary += (@comments || []) ary += (@comments || [])
keys.sort.each do |dn|
ary << "dn: #{dn}"
keys.sort.each {|dn| self[dn].keys.map {|sym| sym.to_s}.sort.each do |attr|
ary << "dn: #{dn}" self[dn][attr.intern].each {|val| ary << "#{attr}: #{val}" }
end
self[dn].keys.map {|sym| sym.to_s}.sort.each {|attr|
self[dn][attr.intern].each {|val|
ary << "#{attr}: #{val}"
}
}
ary << ""
}
block_given? and ary.each {|line| yield line}
ary
end
end # Dataset
end # LDAP
end # Net
ary << ""
end
block_given? and ary.each {|line| yield line}
ary
end
end
end
end

View file

@ -27,36 +27,36 @@
module Net module Net
class LDAP class LDAP
class Password
class << self
# Generate a password-hash suitable for inclusion in an LDAP attribute.
class Password # Pass a hash type (currently supported: :md5 and :sha) and a plaintext
class << self # password. This function will return a hashed representation.
# STUB: This is here to fulfill the requirements of an RFC, which one?
# Generate a password-hash suitable for inclusion in an LDAP attribute. # TODO, gotta do salted-sha and (maybe) salted-md5.
# Pass a hash type (currently supported: :md5 and :sha) and a plaintext # Should we provide sha1 as a synonym for sha1? I vote no because then
# password. This function will return a hashed representation. # should you also provide ssha1 for symmetry?
# STUB: This is here to fulfill the requirements of an RFC, which one? def generate( type, str )
# TODO, gotta do salted-sha and (maybe) salted-md5. digest, digest_name = case type
# Should we provide sha1 as a synonym for sha1? I vote no because then when :md5
# should you also provide ssha1 for symmetry? [Digest::MD5.new, 'MD5']
def generate( type, str ) when :sha
case type [Digest::SHA1.new, 'sha']
when :md5 # when ssha
"{MD5}#{ [MD5.new( str.to_s ).digest].pack("m").chomp }" else
when :sha raise Net::LDAP::LdapError.new( "unsupported password-hash type (#{type})" )
"{SHA}#{ [SHA1.new( str.to_s ).digest].pack("m").chomp }" end
# when ssha
else digest << str.to_s
raise Net::LDAP::LdapError.new( "unsupported password-hash type (#{type})" ) return "{#{digest_name}}#{[digest.digest].pack('m').chomp }"
end
end
end end
end end
end
end end
end # class LDAP
end # module Net

View file

@ -11,7 +11,7 @@ class TestLdif < Test::Unit::TestCase
TestLdifFilename = "#{File.dirname(__FILE__)}/testdata.ldif" TestLdifFilename = "#{File.dirname(__FILE__)}/testdata.ldif"
def test_empty_ldif def test_empty_ldif
ds = Net::LDAP::Dataset::read_ldif( StringIO.new ) ds = Net::LDAP::Dataset.read_ldif( StringIO.new )
assert_equal( true, ds.empty? ) assert_equal( true, ds.empty? )
end end

View file

@ -32,7 +32,7 @@ class TestSnmp < Test::Unit::TestCase
# partially-received data streams, such as from network connections. # partially-received data streams, such as from network connections.
def test_consume_string def test_consume_string
data = "xxx" data = "xxx"
assert_equal( nil, data.read_ber! ) assert_equal( data.read_ber!, nil )
assert_equal( "xxx", data ) assert_equal( "xxx", data )
data = SnmpGetRequest + "!!!" data = SnmpGetRequest + "!!!"