Skip to content

Commit 91a201c

Browse files
author
Jamstah
committed
DN Class to escape and parse DNs.
1 parent 2b3e277 commit 91a201c

File tree

1 file changed

+221
-0
lines changed

1 file changed

+221
-0
lines changed

lib/net/ldap/dn.rb

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
# LDAP DN support classes
2+
#
3+
4+
##
5+
# Objects of this class represent an LDAP DN.
6+
#
7+
# In LDAP-land, a DN ("Distinguished Name") is a unique identifier for an
8+
# entry within an LDAP directory. It is made up of a number of other
9+
# attributes strung together, to identify the entry in the tree.
10+
#
11+
# Each attribute that makes up a DN needs to have its value escaped so that
12+
# the DN is valid. This class helps take care of that.
13+
#
14+
# A fully escaped DN needs to be unescaped when analysing its contents. This
15+
# class also helps take care of that.
16+
class Net::LDAP::DN
17+
##
18+
# Initialize a DN, escaping as required. Pass in attributes in name/value
19+
# pairs. If there is a left over argument, it will be appended to the dn
20+
# without escaping (useful for a base string).
21+
#
22+
# Most uses of this class will be to escape a DN, rather than to parse it,
23+
# so storing the dn as an escaped String and parsing parts as required with
24+
# a state machine seems sensible.
25+
def initialize(*args)
26+
buffer = StringIO.new
27+
28+
args.each_index do |index|
29+
buffer << "=" if index % 2 == 1
30+
buffer << "," if index % 2 == 0 && index != 0
31+
32+
if index < args.length - 1 || index % 2 == 1
33+
buffer << Net::LDAP::DN.escape(args[index])
34+
else
35+
buffer << args[index]
36+
end
37+
end
38+
39+
@dn = buffer.string
40+
end
41+
42+
##
43+
# Parse a DN into key value pairs using ASN from
44+
# http://tools.ietf.org/html/rfc2253 section 3.
45+
#
46+
def each_pair
47+
state = :key
48+
key = StringIO.new
49+
value = StringIO.new
50+
hex_buffer = ""
51+
52+
@dn.each_char do |char|
53+
case state
54+
55+
when :key then case char
56+
when 'a'..'z','A'..'Z' then
57+
state = :key_normal
58+
key << char
59+
when '0'..'9' then
60+
state = :key_oid
61+
key << char
62+
when ' ' then state = :key
63+
else raise "DN badly formed"
64+
end
65+
when :key_normal then case char
66+
when '=' then state = :value
67+
when 'a'..'z','A'..'Z','0'..'9','-',' ' then key << char
68+
else raise "DN badly formed"
69+
end
70+
when :key_oid then case char
71+
when '=' then state = :value
72+
when '0'..'9','.',' ' then key << char
73+
else raise "DN badly formed"
74+
end
75+
76+
when :value then case char
77+
when '\\' then state = :value_normal_escape
78+
when '"' then state = :value_quoted
79+
when ' ' then state = :value
80+
when '#' then
81+
state = :value_hexstring
82+
value << char
83+
when ',' then
84+
state = :key
85+
yield key.string.strip, value.string.rstrip
86+
key = StringIO.new
87+
value = StringIO.new;
88+
else
89+
state = :value_normal
90+
value << char
91+
end
92+
93+
when :value_normal then case char
94+
when '\\' then state = :value_normal_escape
95+
when ',' then
96+
state = :key
97+
yield key.string.strip, value.string.rstrip
98+
key = StringIO.new
99+
value = StringIO.new;
100+
else value << char
101+
end
102+
when :value_normal_escape then case char
103+
when '0'..'9', 'a'..'f', 'A'..'F' then
104+
state = :value_normal_escape_hex
105+
hex_buffer = char
106+
else state = :value_normal; value << char
107+
end
108+
when :value_normal_escape_hex then case char
109+
when '0'..'9', 'a'..'f', 'A'..'F' then
110+
state = :value_normal
111+
value << "#{hex_buffer}#{char}".to_i(16).chr
112+
else raise "DN badly formed"
113+
end
114+
115+
when :value_quoted then case char
116+
when '\\' then state = :value_quoted_escape
117+
when '"' then state = :value_end
118+
else value << char
119+
end
120+
when :value_quoted_escape then case char
121+
when '0'..'9', 'a'..'f', 'A'..'F' then
122+
state = :value_quoted_escape_hex
123+
hex_buffer = char
124+
else state = :value_quoted; value << char
125+
end
126+
when :value_quoted_escape_hex then case char
127+
when '0'..'9', 'a'..'f', 'A'..'F' then
128+
state = :value_quoted
129+
value << "#{hex_buffer}#{char}".to_i(16).chr
130+
else raise "DN badly formed"
131+
end
132+
133+
when :value_hexstring then case char
134+
when '0'..'9', 'a'..'f', 'A'..'F' then
135+
state = :value_hexstring_hex
136+
value << char
137+
when ' ' then state = :value_end
138+
when ',' then
139+
state = :key
140+
yield key.string.strip, value.string.rstrip
141+
key = StringIO.new
142+
value = StringIO.new;
143+
else raise "DN badly formed"
144+
end
145+
when :value_hexstring_hex then case char
146+
when '0'..'9', 'a'..'f', 'A'..'F' then
147+
state = :value_hexstring
148+
value << char
149+
else raise "DN badly formed"
150+
end
151+
152+
when :value_end then case char
153+
when ' ' then state = :value_end
154+
when ',' then
155+
state = :key
156+
yield key.string.strip, value.string.rstrip
157+
key = StringIO.new
158+
value = StringIO.new;
159+
else raise "DN badly formed"
160+
end
161+
162+
else raise "Fell out of state machine"
163+
end
164+
end
165+
166+
# Last pair
167+
if [:value, :value_normal, :value_hexstring, :value_end].include? state
168+
yield key.string.strip, value.string.rstrip
169+
else
170+
raise "DN badly formed"
171+
end
172+
end
173+
174+
##
175+
# Returns the DN as an array in the form expected by the constructor.
176+
def to_a
177+
a = []
178+
self.each_pair { |key, value| a << key << value }
179+
a
180+
end
181+
182+
##
183+
# Return the DN as an escaped string.
184+
def to_s
185+
@dn
186+
end
187+
188+
# http://tools.ietf.org/html/rfc2253 section 2.4 lists these exceptions
189+
# for dn values. All of the following must be escaped in any normal
190+
# string using a single backslash ('\') as escape.
191+
#
192+
ESCAPES = {
193+
',' => ',',
194+
'+' => '+',
195+
'"' => '"',
196+
'\\' => '\\',
197+
'<' => '<',
198+
'>' => '>',
199+
';' => ';',
200+
}
201+
# Compiled character class regexp using the keys from the above hash, and
202+
# checking for a space or # at the start, or space at the end, of the
203+
# string.
204+
ESCAPE_RE = Regexp.new(
205+
"(^ |^#| $|[" +
206+
ESCAPES.keys.map { |e| Regexp.escape(e) }.join +
207+
"])")
208+
209+
##
210+
# Escape a string for use in a DN value
211+
def self.escape(string)
212+
string.gsub(ESCAPE_RE) { |char| "\\" + ESCAPES[char] }
213+
end
214+
215+
##
216+
# Proxy all other requests to the string object, because a DN is mainly
217+
# used within the library as a string
218+
def method_missing(method, *args, &block)
219+
@dn.send(method, *args, &block)
220+
end
221+
end

0 commit comments

Comments
 (0)