From 3651736b41117903510dbac7cd2b6876e33b9f10 Mon Sep 17 00:00:00 2001 From: brent s Date: Fri, 11 Sep 2020 00:22:24 -0400 Subject: [PATCH 1/2] adding test data for https://github.com/lestrrat-go/libxml2/issues/67 --- test/go_libxml2_local.xml | 18 ++ test/go_libxml2_remote.xml | 18 ++ test/schema/lib/types/cksum.xsd | 76 +++++++++ test/schema/lib/types/net.xsd | 197 ++++++++++++++++++++++ test/schema/lib/types/std.xsd | 62 +++++++ test/schema/lib/types/unix.xsd | 178 +++++++++++++++++++ test/schema/projects/go_libxml2_local.xsd | 31 ++++ 7 files changed, 580 insertions(+) create mode 100644 test/go_libxml2_local.xml create mode 100644 test/go_libxml2_remote.xml create mode 100644 test/schema/lib/types/cksum.xsd create mode 100644 test/schema/lib/types/net.xsd create mode 100644 test/schema/lib/types/std.xsd create mode 100644 test/schema/lib/types/unix.xsd create mode 100644 test/schema/projects/go_libxml2_local.xsd diff --git a/test/go_libxml2_local.xml b/test/go_libxml2_local.xml new file mode 100644 index 0000000..ae8b50e --- /dev/null +++ b/test/go_libxml2_local.xml @@ -0,0 +1,18 @@ + + + + + 4cfcf843c66979eb1df2bd0c52817edb753a52ba + + + this is a test string only. + be218408a748759fb98363593b8f544eb054171bced856ca98bd972823dec0b07b205453fc3c46f23c934d0959f1e05b609c011b6ada84a7050ad7c910b24bf1 + + + foobar + f7b34871a562283ee92bbda00485eb45 + + + diff --git a/test/go_libxml2_remote.xml b/test/go_libxml2_remote.xml new file mode 100644 index 0000000..0067fbd --- /dev/null +++ b/test/go_libxml2_remote.xml @@ -0,0 +1,18 @@ + + + + + 4cfcf843c66979eb1df2bd0c52817edb753a52ba + + + this is a test string only. + be218408a748759fb98363593b8f544eb054171bced856ca98bd972823dec0b07b205453fc3c46f23c934d0959f1e05b609c011b6ada84a7050ad7c910b24bf1 + + + foobar + f7b34871a562283ee92bbda00485eb45 + + + diff --git a/test/schema/lib/types/cksum.xsd b/test/schema/lib/types/cksum.xsd new file mode 100644 index 0000000..59f637f --- /dev/null +++ b/test/schema/lib/types/cksum.xsd @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/schema/lib/types/net.xsd b/test/schema/lib/types/net.xsd new file mode 100644 index 0000000..68a7c94 --- /dev/null +++ b/test/schema/lib/types/net.xsd @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/schema/lib/types/std.xsd b/test/schema/lib/types/std.xsd new file mode 100644 index 0000000..22e315f --- /dev/null +++ b/test/schema/lib/types/std.xsd @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/schema/lib/types/unix.xsd b/test/schema/lib/types/unix.xsd new file mode 100644 index 0000000..b2dcf2d --- /dev/null +++ b/test/schema/lib/types/unix.xsd @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/schema/projects/go_libxml2_local.xsd b/test/schema/projects/go_libxml2_local.xsd new file mode 100644 index 0000000..43178f4 --- /dev/null +++ b/test/schema/projects/go_libxml2_local.xsd @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + From 4681c5b37077d3c002e226412862cfc85b135ae9 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Tue, 15 Sep 2020 12:55:12 +0900 Subject: [PATCH 2/2] First pass at solving #67 --- clib/clib.go | 51 ++++++++++++-- internal/option/interface.go | 5 ++ internal/option/option.go | 25 +++++++ test/schema/projects/go_libxml2_remote.xsd | 31 +++++++++ xsd/interface.go | 4 ++ xsd/option.go | 41 +++++++++++ xsd/xsd.go | 7 +- xsd_test.go | 81 ++++++++++++++++++++++ 8 files changed, 237 insertions(+), 8 deletions(-) create mode 100644 internal/option/interface.go create mode 100644 internal/option/option.go create mode 100644 test/schema/projects/go_libxml2_remote.xsd create mode 100644 xsd/option.go diff --git a/clib/clib.go b/clib/clib.go index d6a1ff3..ee62213 100644 --- a/clib/clib.go +++ b/clib/clib.go @@ -36,6 +36,7 @@ package clib #include #include #include +#include static inline void MY_nilErrorHandler(void *ctx, const char *msg, ...) {} @@ -343,6 +344,7 @@ import ( "unsafe" "github.com/lestrrat-go/libxml2/internal/debug" + "github.com/lestrrat-go/libxml2/internal/option" "github.com/pkg/errors" ) @@ -465,7 +467,14 @@ func ReportErrors(b bool) { } func xmlCtxtLastError(ctx PtrSource) error { - e := C.MY_xmlCtxtLastError(unsafe.Pointer(ctx.Pointer())) + return xmlCtxtLastErrorRaw(ctx.Pointer()) +} + +func xmlCtxtLastErrorRaw(ctx uintptr) error { + e := C.MY_xmlCtxtLastError(unsafe.Pointer(ctx)) + if e == nil { + return errors.New(`unknown error`) + } msg := strings.TrimSuffix(C.GoString(e.message), "\n") return errors.Errorf("Entity: line %v: parser error : %v", e.line, msg) } @@ -2116,11 +2125,41 @@ func XMLTextData(n PtrSource) string { return xmlCharToString(nptr.content) } -func XMLSchemaParse(buf []byte) (uintptr, error) { - parserCtx := C.xmlSchemaNewMemParserCtxt( - (*C.char)(unsafe.Pointer(&buf[0])), - C.int(len(buf)), - ) +func XMLSchemaParse(buf []byte, options ...option.Interface) (uintptr, error) { + var uri string + var encoding string + var coptions int + for _, opt := range options { + switch opt.Name() { + case option.OptKeyWithURI: + uri = opt.Value().(string) + } + } + + docctx := C.xmlCreateMemoryParserCtxt((*C.char)(unsafe.Pointer(&buf[0])), C.int(len(buf))) + if docctx == nil { + return 0, errors.New("error creating doc parser") + } + + var curi *C.char + if uri != "" { + curi = C.CString(uri) + defer C.free(unsafe.Pointer(curi)) + } + + var cencoding *C.char + if encoding != "" { + cencoding = C.CString(encoding) + defer C.free(unsafe.Pointer(cencoding)) + } + + doc := C.xmlCtxtReadMemory(docctx, (*C.char)(unsafe.Pointer(&buf[0])), C.int(len(buf)), curi, cencoding, C.int(coptions)) + if doc == nil { + return 0, errors.Errorf("failed to read schema from memory: %v", + xmlCtxtLastErrorRaw(uintptr(unsafe.Pointer(docctx)))) + } + + parserCtx := C.xmlSchemaNewDocParserCtxt((*C.xmlDoc)(unsafe.Pointer(doc))) if parserCtx == nil { return 0, errors.New("failed to create parser") } diff --git a/internal/option/interface.go b/internal/option/interface.go new file mode 100644 index 0000000..3108ed9 --- /dev/null +++ b/internal/option/interface.go @@ -0,0 +1,5 @@ +package option + +const ( + OptKeyWithURI = `with-uri` +) diff --git a/internal/option/option.go b/internal/option/option.go new file mode 100644 index 0000000..9259dc5 --- /dev/null +++ b/internal/option/option.go @@ -0,0 +1,25 @@ +package option + +type Interface interface { + Name() string + Value() interface{} +} + +type Option struct { + name string + value interface{} +} + +func New(name string, value interface{}) *Option { + return &Option{ + name: name, + value: value, + } +} + +func (o *Option) Name() string { + return o.name +} +func (o *Option) Value() interface{} { + return o.value +} diff --git a/test/schema/projects/go_libxml2_remote.xsd b/test/schema/projects/go_libxml2_remote.xsd new file mode 100644 index 0000000..43178f4 --- /dev/null +++ b/test/schema/projects/go_libxml2_remote.xsd @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xsd/interface.go b/xsd/interface.go index 3218b09..6177f41 100644 --- a/xsd/interface.go +++ b/xsd/interface.go @@ -1,5 +1,7 @@ package xsd +import "github.com/lestrrat-go/libxml2/internal/option" + // Schema represents an XML schema. type Schema struct { ptr uintptr // *C.xmlSchema @@ -11,3 +13,5 @@ type Schema struct { type SchemaValidationError struct { errors []error } + +type Option = option.Interface diff --git a/xsd/option.go b/xsd/option.go new file mode 100644 index 0000000..0a356b2 --- /dev/null +++ b/xsd/option.go @@ -0,0 +1,41 @@ +package xsd + +import ( + "net/url" + "os" + "path/filepath" + + "github.com/lestrrat-go/libxml2/internal/option" +) + +// WithPath provides a hint to the XSD parser as to where the +// document being parsed is located at. +// +// This is useful when you must resolve relative paths inside a +// document, because to use relative paths the parser needs to +// know the reference location (i.e. location of the document +// being parsed). In case where you are parsing using `ParseFromFile()` +// this is handled automatically by the `ParseFromFile` method, +// but if you are using `Parse` method this is required +// +// If the path is provided as a relative path, the current directory +// should be obtainable via `os.Getwd` when this call is made, otherwise +// path resolution may fail in weird ways. +func WithPath(path string) Option { + if !filepath.IsAbs(path) { + if curdir, err := os.Getwd(); err == nil { + path = filepath.Join(curdir, path) + } + } + + return WithURI( + (&url.URL{ + Scheme: `file`, + Path: path, + }).String(), + ) +} + +func WithURI(v string) Option { + return option.New(option.OptKeyWithURI, v) +} diff --git a/xsd/xsd.go b/xsd/xsd.go index 121b86d..bf3e46c 100644 --- a/xsd/xsd.go +++ b/xsd/xsd.go @@ -27,8 +27,11 @@ const ValueVCCreate = 1 // Parse is used to parse an XML Schema Document to produce a // Schema instance. Make sure to call Free() on the instance // when you are done with it. -func Parse(buf []byte) (*Schema, error) { - sptr, err := clib.XMLSchemaParse(buf) + + +func Parse(buf []byte, options ...Option) (*Schema, error) { + // xsd.WithURI(...) + sptr, err := clib.XMLSchemaParse(buf, options...) if err != nil { return nil, errors.Wrap(err, "failed to parse input") } diff --git a/xsd_test.go b/xsd_test.go index 8c3b666..2be5dc8 100644 --- a/xsd_test.go +++ b/xsd_test.go @@ -2,6 +2,8 @@ package libxml2_test import ( "io/ioutil" + "net/http" + "net/http/httptest" "os" "path/filepath" "testing" @@ -162,3 +164,82 @@ elementFormDefault="qualified"> t.Logf("%s", doc.String()) } + +func TestGHIssue67(t *testing.T) { + t.Run("Local validation", func(t *testing.T) { + const schemafile = "test/schema/projects/go_libxml2_local.xsd" + const docfile = "test/go_libxml2_local.xml" + + schemasrc, err := ioutil.ReadFile(schemafile) + if !assert.NoError(t, err, `failed to read xsd file`) { + return + } + + docsrc, err := ioutil.ReadFile(docfile) + if !assert.NoError(t, err, `failed to read xml file`) { + return + } + + schema, err := xsd.Parse(schemasrc, xsd.WithPath(schemafile)) + if !assert.NoError(t, err, `xsd.Parse should succeed`) { + return + } + defer schema.Free() + + doc, err := libxml2.Parse(docsrc) + if !assert.NoError(t, err, "parsing XML") { + return + } + defer doc.Free() + if !assert.NoError(t, schema.Validate(doc, xsd.ValueVCCreate), `schema.Validate should succeed`) { + return + } + + t.Logf("%s", doc.String()) + }) + t.Run("Remote validation", func(t *testing.T) { + curdir, err := os.Getwd() + if !assert.NoError(t, err, `os.Getwd failed`) { + return + } + + srv := httptest.NewServer(http.FileServer(http.Dir(curdir))) + defer srv.Close() + + var schemafile = srv.URL + "/test/schema/projects/go_libxml2_remote.xsd" + const docfile = "test/go_libxml2_remote.xml" + + res, err := http.Get(schemafile) + if !assert.NoError(t, err, `failed to fetch xsd file`) { + return + } + + schemasrc, err := ioutil.ReadAll(res.Body) + defer res.Body.Close() + if !assert.NoError(t, err, `failed to read xsd file`) { + return + } + + docsrc, err := ioutil.ReadFile(docfile) + if !assert.NoError(t, err, `failed to read xml file`) { + return + } + + schema, err := xsd.Parse(schemasrc, xsd.WithURI(schemafile)) + if !assert.NoError(t, err, `xsd.Parse should succeed`) { + return + } + defer schema.Free() + + doc, err := libxml2.Parse(docsrc) + if !assert.NoError(t, err, "parsing XML") { + return + } + defer doc.Free() + if !assert.NoError(t, schema.Validate(doc, xsd.ValueVCCreate), `schema.Validate should succeed`) { + return + } + + t.Logf("%s", doc.String()) + }) +}