comparison pkg/soap/validate.go @ 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
children 31973f6f5cca
comparison
equal deleted inserted replaced
2179:20d9b71f4125 2181:bd09d6ad4c14
1 // This is Free Software under GNU Affero General Public License v >= 3.0
2 // without warranty, see README.md and license for details.
3 //
4 // SPDX-License-Identifier: AGPL-3.0-or-later
5 // License-Filename: LICENSES/AGPL-3.0.txt
6 //
7 // Copyright (C) 2018 by via donau
8 // – Österreichische Wasserstraßen-Gesellschaft mbH
9 // Software engineering by Intevation GmbH
10 //
11 // Author(s):
12 // * Sascha L. Teichmann <sascha.teichmann@intevation.de>
13
14 package soap
15
16 import (
17 "bytes"
18 "encoding/xml"
19 "fmt"
20 "io"
21 "io/ioutil"
22 "os"
23 "os/exec"
24 "path/filepath"
25 "strings"
26
27 "golang.org/x/net/html/charset"
28
29 "gemma.intevation.de/gemma/pkg/config"
30 )
31
32 const linter = "xmllint"
33
34 type (
35 ValidationError string
36 foundError string
37 )
38
39 func (ef foundError) Error() string {
40 return string(ef)
41 }
42
43 func (ve ValidationError) Error() string {
44 return string(ve)
45 }
46
47 func FindSchema(name string) (string, error) {
48 name = strings.ToLower(name)
49 config.WaitReady()
50 for _, root := range filepath.SplitList(config.SchemaDirs()) {
51 err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
52 if err != nil {
53 return err
54 }
55 if info == nil {
56 return nil
57 }
58 if info.Mode().IsRegular() && strings.ToLower(info.Name()) == name {
59 return foundError(path)
60 }
61 return nil
62 })
63 if path, ok := err.(foundError); ok {
64 return string(path), nil
65 }
66 if err != nil {
67 return "", err
68 }
69 }
70 return "", nil
71 }
72
73 func ValidateFile(fname, schema string, dst interface{}) error {
74 f, err := os.Open(fname)
75 if err != nil {
76 return err
77 }
78 defer f.Close()
79 return Validate(f, schema, dst)
80 }
81
82 func Validate(r io.Reader, schema string, dst interface{}) error {
83 schemaPath, err := FindSchema(schema)
84 if err != nil {
85 return err
86 }
87 if schemaPath == "" {
88 return fmt.Errorf("no schema file '%s' found", schema)
89 }
90 linterPath, err := exec.LookPath(linter)
91 if err != nil {
92 return err
93 }
94
95 type envelope struct {
96 _ xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
97 Body *struct {
98 Inner []byte `xml:",innerxml"`
99 } `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
100 }
101
102 content, err := ioutil.ReadAll(r)
103 if err != nil {
104 return err
105 }
106
107 dec := xml.NewDecoder(bytes.NewReader(content))
108 dec.CharsetReader = charset.NewReaderLabel
109
110 var env envelope
111 if err := dec.Decode(&env); err != nil {
112 return err
113 }
114
115 // It has a body -> throw envelope away.
116 if env.Body != nil && len(env.Body.Inner) > 0 {
117 content = env.Body.Inner
118 }
119
120 cmd := exec.Command(
121 linterPath,
122 "--schema", schemaPath,
123 "--noout",
124 "-")
125
126 var stderr bytes.Buffer
127
128 cmd.Stdin = bytes.NewReader(content)
129 cmd.Stderr = &stderr
130 cmd.Stdout = ioutil.Discard
131
132 if err := cmd.Start(); err != nil {
133 return err
134 }
135
136 if err := cmd.Wait(); err != nil {
137 if err2, ok := err.(*exec.ExitError); ok {
138 if !err2.Success() {
139 return ValidationError(stderr.String())
140 }
141 }
142 return err
143 }
144
145 // Validation successful -> Deserialize.
146
147 dec = xml.NewDecoder(bytes.NewReader(content))
148 dec.CharsetReader = charset.NewReaderLabel
149
150 return dec.Decode(dst)
151 }