view pkg/soap/validate.go @ 5415:4ad68ab239b7 marking-single-beam

Factored creation of default class breaks in SR import to be reused with markings, too.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Wed, 07 Jul 2021 12:01:28 +0200
parents bd09d6ad4c14
children 31973f6f5cca
line wrap: on
line source

// 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)
}