changeset 2181:bd09d6ad4c14

SOAP: Add validating parser (uses 'xmllint') for manual uploads.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Mon, 11 Feb 2019 15:56:59 +0100
parents 20d9b71f4125
children 7ae0b6be0203
files pkg/config/config.go pkg/soap/validate.go
diffstat 2 files changed, 156 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/pkg/config/config.go	Mon Feb 11 13:04:36 2019 +0100
+++ b/pkg/config/config.go	Mon Feb 11 15:56:59 2019 +0100
@@ -166,6 +166,9 @@
 	return externalURL
 }
 
+// The root directories where to find schema files.
+func SchemaDirs() string { return viper.GetString("schema-dirs") }
+
 // RootCmd is cobra command to be bound th the cobra/viper infrastructure.
 var RootCmd = &cobra.Command{
 	Use:   "gemma",
@@ -246,6 +249,8 @@
 
 	str("tmp-dir", "", "Temp directory of gemma server.\n"+
 		"Defaults to system temp directory.")
+
+	str("schema-dirs", ".", "Directories to find XSD schema files in (recursive).")
 }
 
 var (
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/soap/validate.go	Mon Feb 11 15:56:59 2019 +0100
@@ -0,0 +1,151 @@
+// This is Free Software under GNU Affero General Public License v >= 3.0
+// without warranty, see README.md and license for details.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// License-Filename: LICENSES/AGPL-3.0.txt
+//
+// Copyright (C) 2018 by via donau
+//   – Österreichische Wasserstraßen-Gesellschaft mbH
+// Software engineering by Intevation GmbH
+//
+// Author(s):
+//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+package soap
+
+import (
+	"bytes"
+	"encoding/xml"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strings"
+
+	"golang.org/x/net/html/charset"
+
+	"gemma.intevation.de/gemma/pkg/config"
+)
+
+const linter = "xmllint"
+
+type (
+	ValidationError string
+	foundError      string
+)
+
+func (ef foundError) Error() string {
+	return string(ef)
+}
+
+func (ve ValidationError) Error() string {
+	return string(ve)
+}
+
+func FindSchema(name string) (string, error) {
+	name = strings.ToLower(name)
+	config.WaitReady()
+	for _, root := range filepath.SplitList(config.SchemaDirs()) {
+		err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
+			if err != nil {
+				return err
+			}
+			if info == nil {
+				return nil
+			}
+			if info.Mode().IsRegular() && strings.ToLower(info.Name()) == name {
+				return foundError(path)
+			}
+			return nil
+		})
+		if path, ok := err.(foundError); ok {
+			return string(path), nil
+		}
+		if err != nil {
+			return "", err
+		}
+	}
+	return "", nil
+}
+
+func ValidateFile(fname, schema string, dst interface{}) error {
+	f, err := os.Open(fname)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	return Validate(f, schema, dst)
+}
+
+func Validate(r io.Reader, schema string, dst interface{}) error {
+	schemaPath, err := FindSchema(schema)
+	if err != nil {
+		return err
+	}
+	if schemaPath == "" {
+		return fmt.Errorf("no schema file '%s' found", schema)
+	}
+	linterPath, err := exec.LookPath(linter)
+	if err != nil {
+		return err
+	}
+
+	type envelope struct {
+		_    xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
+		Body *struct {
+			Inner []byte `xml:",innerxml"`
+		} `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
+	}
+
+	content, err := ioutil.ReadAll(r)
+	if err != nil {
+		return err
+	}
+
+	dec := xml.NewDecoder(bytes.NewReader(content))
+	dec.CharsetReader = charset.NewReaderLabel
+
+	var env envelope
+	if err := dec.Decode(&env); err != nil {
+		return err
+	}
+
+	// It has a body -> throw envelope away.
+	if env.Body != nil && len(env.Body.Inner) > 0 {
+		content = env.Body.Inner
+	}
+
+	cmd := exec.Command(
+		linterPath,
+		"--schema", schemaPath,
+		"--noout",
+		"-")
+
+	var stderr bytes.Buffer
+
+	cmd.Stdin = bytes.NewReader(content)
+	cmd.Stderr = &stderr
+	cmd.Stdout = ioutil.Discard
+
+	if err := cmd.Start(); err != nil {
+		return err
+	}
+
+	if err := cmd.Wait(); err != nil {
+		if err2, ok := err.(*exec.ExitError); ok {
+			if !err2.Success() {
+				return ValidationError(stderr.String())
+			}
+		}
+		return err
+	}
+
+	// Validation successful -> Deserialize.
+
+	dec = xml.NewDecoder(bytes.NewReader(content))
+	dec.CharsetReader = charset.NewReaderLabel
+
+	return dec.Decode(dst)
+}