From 2251705b7fcf470a83bc5873524d56da277aa605 Mon Sep 17 00:00:00 2001 From: Hanno Hecker Date: Sat, 19 Nov 2016 09:15:07 +0100 Subject: [PATCH 1/3] Add callback this adds a callback function which is called on every entry found in the LDIF file, useful for huge LDIF files or parsing an incoming stream of LDIF entries. Other changes: * fix changetype not being reset when being run with a mix of normal enries and change records * add option to not error out when an empty value was found (then simply return the attribute with a value of "") --- ldif.go | 26 +++++++++++++++++++------- ldif_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/ldif.go b/ldif.go index fd8fc6b..bcf9049 100644 --- a/ldif.go +++ b/ldif.go @@ -29,12 +29,14 @@ type Entry struct { // when parsing (default: false to ignore the controls). // FoldWidth is used for the line lenght when marshalling. type LDIF struct { - Entries []*Entry - Version int - changeType string - FoldWidth int - Controls bool - firstEntry bool + Entries []*Entry + Version int + changeType string + FoldWidth int + Controls bool + firstEntry bool + Callback func(*Entry) + IgnoreEmptyValues bool } // The ParseError holds the error message and the line in the ldif @@ -111,7 +113,11 @@ func Unmarshal(r io.Reader, l *LDIF) (err error) { if perr != nil { return &ParseError{Line: curLine, Message: perr.Error()} } - l.Entries = append(l.Entries, entry) + if l.Callback != nil { + l.Callback(entry) + } else { + l.Entries = append(l.Entries, entry) + } line = "" lines = []string{} if err == io.EOF { @@ -193,6 +199,7 @@ func (l *LDIF) parseEntry(lines []string) (entry *Entry, err error) { return nil, err } + l.changeType = "" // reset from previous if strings.HasPrefix(lines[0], "changetype:") { _, val, err := l.parseLine(lines[0]) if err != nil { @@ -310,6 +317,11 @@ func (l *LDIF) parseLine(line string) (attr, val string, err error) { } if off > len(line)-2 { + if l.IgnoreEmptyValues { + attr = strings.Split(line, ":")[0] + val = "" + return + } err = errors.New("empty value") // FIXME: this is allowed for some attributes, e.g. seeAlso return diff --git a/ldif_test.go b/ldif_test.go index 08c7957..1e37632 100644 --- a/ldif_test.go +++ b/ldif_test.go @@ -1,8 +1,10 @@ package ldif_test import ( + "bytes" "io/ioutil" "os" + "strings" "testing" "github.com/go-ldap/ldif" @@ -257,3 +259,25 @@ func TestLDIFVersionOnSecond(t *testing.T) { t.Errorf("did not fail to parse LDIF") } } + +func TestLDIFCallback(t *testing.T) { + src := bytes.NewBuffer([]byte(ldifRFC2849Example)) + dst := bytes.NewBuffer(nil) + ld := &ldif.LDIF{Callback: func(e *ldif.Entry) { + if e.Entry.GetAttributeValue("uid") == "bjensen" { + ldif.Dump(dst, 0, e.Entry) + } + }} + err := ldif.Unmarshal(src, ld) + if err != nil { + t.Errorf("failed to parse LDIF: %s", err) + } + ret := dst.String() + out := strings.Split(ret, "\n") + if out[0] != `dn: cn=Barbara Jensen, ou=Product Development, dc=airius, dc=com` { + t.Errorf("wrong dn line") + } + if len(out) != 13 { // 13: trailing empty line + t.Errorf("output not as expected: >>%#v<<", out) + } +} From 51769e5f28bbbff7d9caab5b6a3139fe856fe656 Mon Sep 17 00:00:00 2001 From: Hanno Hecker Date: Sat, 19 Nov 2016 11:44:46 +0100 Subject: [PATCH 2/3] fix golint errors --- ldif.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ldif.go b/ldif.go index bcf9049..0987216 100644 --- a/ldif.go +++ b/ldif.go @@ -164,7 +164,7 @@ func (l *LDIF) parseEntry(lines []string) (entry *Entry, err error) { } if l.Version != 1 { - return nil, errors.New("Invalid version spec " + string(line)) + return nil, errors.New("invalid version spec " + string(line)) } l.Version = 1 @@ -180,7 +180,7 @@ func (l *LDIF) parseEntry(lines []string) (entry *Entry, err error) { } if !strings.HasPrefix(lines[0], "dn:") { - return nil, errors.New("Missing dn:") + return nil, errors.New("missing dn: line") } _, val, err := l.parseLine(lines[0]) if err != nil { @@ -492,7 +492,7 @@ func validOID(oid string) error { case c >= '0' && c <= '9': lastDot = false default: - return errors.New("Invalid character in OID") + return errors.New("invalid character in OID") } } return nil From 5115067bc196f6910c792f412effdfdc9d9a0233 Mon Sep 17 00:00:00 2001 From: Hanno Hecker Date: Sat, 19 Nov 2016 12:15:52 +0100 Subject: [PATCH 3/3] add docs for Callback and example wrapper how to use it --- ldif.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/ldif.go b/ldif.go index 0987216..729ed0c 100644 --- a/ldif.go +++ b/ldif.go @@ -26,8 +26,17 @@ type Entry struct { // The LDIF struct is used for parsing an LDIF. The Controls // is used to tell the parser to ignore any controls found -// when parsing (default: false to ignore the controls). +// when parsing (default: false to ignore the controls). Note +// that the support for controls is quite limited currently, +// the only one is ManageDsaIt. +// // FoldWidth is used for the line lenght when marshalling. +// +// When the Callback func is non nil, it is called for each +// parsed entry. +// +// IgnoreEmptyValues can be set return the attribute with an +// empty value instead of raising an error. type LDIF struct { Entries []*Entry Version int @@ -75,6 +84,13 @@ func ParseWithControls(str string) (l *LDIF, err error) { return } +// ParseWithCallback wraps Unmarshal to parse an LDIF from the +// given io.Reader and calls cb for every entry found. +func ParseWithCallback(r io.Reader, cb func(*Entry)) error { + l := &LDIF{Callback: cb} + return Unmarshal(r, l) +} + // Unmarshal parses the LDIF from the given io.Reader into the LDIF struct. // The caller is responsible for closing the io.Reader if that is // needed.