Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions dn.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import (
enchex "encoding/hex"
"errors"
"fmt"
"regexp"
"strings"

"gopkg.in/asn1-ber.v1"
Expand Down Expand Up @@ -212,6 +213,89 @@ func (d *DN) AncestorOf(other *DN) bool {
return true
}

// String returns string representation of the RDN Object with required
// escaping of spaces and formatting applied
func (r RelativeDN) String() string {
var buffer bytes.Buffer
for j := range r.Attributes {
if j > 0 {
buffer.WriteString("+")
}
buffer.WriteString(r.Attributes[j].Type)
buffer.WriteString("=")
//Escape the value before building DN string
val := EscapeAttrValue(r.Attributes[j].Value)
buffer.WriteString(val)
}
return buffer.String()
}

// String returns string representation of the DN Object with required
// escaping of spaces and formatting applied
func (d DN) String() string {
var buffer bytes.Buffer

for i := range d.RDNs {
if i > 0 {
buffer.WriteString(",")
}
buffer.WriteString(d.RDNs[i].String())
}
return buffer.String()
}

//EscapeAttrValue function escapes the LDAP special characters from the value part
//of the RDN
/**
distinguishedName = [ relativeDistinguishedName
*( COMMA relativeDistinguishedName ) ]
relativeDistinguishedName = attributeTypeAndValue
*( PLUS attributeTypeAndValue )
attributeTypeAndValue = attributeType EQUALS attributeValue
attributeType = descr / numericoid
attributeValue = string / hexstring

; The following characters are to be escaped when they appear
; in the value to be encoded: ESC, one of <escaped>, leading
; SHARP or SPACE, trailing SPACE, and NULL.
string = [ ( leadchar / pair ) [ *( stringchar / pair )
( trailchar / pair ) ] ]

leadchar = LUTF1 / UTFMB
LUTF1 = %x01-1F / %x21 / %x24-2A / %x2D-3A /
%x3D / %x3F-5B / %x5D-7F

trailchar = TUTF1 / UTFMB
TUTF1 = %x01-1F / %x21 / %x23-2A / %x2D-3A /
%x3D / %x3F-5B / %x5D-7F

stringchar = SUTF1 / UTFMB
SUTF1 = %x01-21 / %x23-2A / %x2D-3A /
%x3D / %x3F-5B / %x5D-7F

pair = ESC ( ESC / special / hexpair )
special = escaped / SPACE / SHARP / EQUALS
escaped = DQUOTE / PLUS / COMMA / SEMI / LANGLE / RANGLE
hexstring = SHARP 1*hexpair
hexpair = HEX HEX

where the productions <descr>, <numericoid>, <COMMA>, <DQUOTE>,
<EQUALS>, <ESC>, <HEX>, <LANGLE>, <NULL>, <PLUS>, <RANGLE>, <SEMI>,
<SPACE>, <SHARP>, and <UTFMB> are defined in [RFC4512].
*/
func EscapeAttrValue(in string) string {

escape := regexp.MustCompile("[,\\#+<>;\"=]")
out := escape.ReplaceAllString(in, "\\$0")
if strings.HasPrefix(out, " ") {
out = "\\" + out
}
if strings.HasSuffix(out, " ") {
out = out[:len(out)-1] + "\\ "
}
return out
}

// Equal returns true if the RelativeDNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch).
// Relative distinguished names are the same if and only if they have the same number of AttributeTypeAndValues
// and each attribute of the first RDN is the same as the attribute of the second RDN with the same attribute type.
Expand Down
84 changes: 84 additions & 0 deletions dn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,90 @@ func TestDNEqual(t *testing.T) {
}
}

func TestEscapeAttrValue(t *testing.T) {
testcases := []struct {
A string
B string
Equal bool
}{
// Exact match
{"", "", true},
{"o=A", "o\\=A", true},
{"o,A", "o\\,A", true},
{"o#A", "o\\#A", true},
{"o<A", "o\\<A", true},
{"o>A", "o\\>A", true},
{"o;A", "o\\;A", true},
{"o\"A", "o\\\"A", true},
{"o+A", "o\\+A", true},
{" o=A", "\\ o\\=A", true},
{"o=A ", "o\\=A\\ ", true},
{" o=A ", "\\ o\\=A\\ ", true},
}
for i, tc := range testcases {
a := EscapeAttrValue(tc.A)
if tc.B != a {
t.Errorf("%d: when escaping string '%s', got '%s' expected and '%s'", i, tc.A, a, tc.B)
continue
}
}
}

func TestDNString(t *testing.T) {
testcases := []struct {
A string
B string
Equal bool
}{
// Exact match
{"", "", true},
{"o=A", "o=A", true},
{"o=A", "o=B", false},

{"o= A,o=B", "o=A,o=B", true},
{"o=A,o=B", "o=A,o=C", false},

{"o=A+o=B", "o=A+o=B", true},
{"o=A + o=B", "o=A+o=B", true},
{"o= A +o= B", "o=A+o=B", true},

// Number of RDN attributes is significant
{"o=A+o=B", "O=B+o=A+O=B", false},

// Missing values are significant
{"o=A+o=B", "O=B+o=A+O=C", false}, // missing values matter
{"o=A+o=B+o=C", "O=B+o=A", false}, // missing values matter

// Real examples
// Difference in leading/trailing chars is ignored
{
"cn=John Doe, ou=People, dc=sun.com",
"cn=John Doe,ou=People,dc=sun.com",
true,
},
// Difference in values is significant
{
"cn=John Doe, ou=People, dc=sun.com",
"cn=John Doe, ou=People, dc=sun.com",
false,
},
}

for i, tc := range testcases {
a, err := ParseDN(tc.A)
if err != nil {
t.Errorf("%d: %v", i, err)
continue
}
dnstr := a.String()

if tc.Equal == (tc.B != dnstr) {
t.Errorf("%d: when converting to string '%s', got '%s' expected and '%s'", i, tc.A, dnstr, tc.B)
continue
}
}
}

func TestDNAncestor(t *testing.T) {
testcases := []struct {
A string
Expand Down