comparison pkg/controllers/proxy.go @ 414:c1047fd04a3a

Moved project specific Go packages to new pkg folder.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Wed, 15 Aug 2018 17:30:50 +0200
parents controllers/proxy.go@cdd63547930a
children 6627c48363a0
comparison
equal deleted inserted replaced
413:a9440a4826aa 414:c1047fd04a3a
1 package controllers
2
3 import (
4 "compress/flate"
5 "compress/gzip"
6 "crypto/hmac"
7 "crypto/sha256"
8 "encoding/base64"
9 "encoding/xml"
10 "io"
11 "io/ioutil"
12 "log"
13 "net/http"
14 "net/url"
15 "regexp"
16 "strings"
17 "time"
18
19 "github.com/gorilla/mux"
20 "golang.org/x/net/html/charset"
21
22 "gemma.intevation.de/gemma/pkg/config"
23 )
24
25 // proxyBlackList is a set of URLs that should not be rewritten by the proxy.
26 var proxyBlackList = map[string]struct{}{
27 "http://www.w3.org/2001/XMLSchema-instance": struct{}{},
28 "http://www.w3.org/1999/xlink": struct{}{},
29 "http://www.w3.org/2001/XMLSchema": struct{}{},
30 "http://www.w3.org/XML/1998/namespace": struct{}{},
31 "http://www.opengis.net/wfs/2.0": struct{}{},
32 "http://www.opengis.net/ows/1.1": struct{}{},
33 "http://www.opengis.net/gml/3.2": struct{}{},
34 "http://www.opengis.net/fes/2.0": struct{}{},
35 "http://schemas.opengis.net/gml": struct{}{},
36 }
37
38 func findEntry(entry string) (string, bool) {
39 external := config.ExternalWFSs()
40 if external == nil || len(external) == 0 {
41 return "", false
42 }
43 alias, found := external[entry]
44 if !found {
45 return "", false
46 }
47 data, ok := alias.(map[string]interface{})
48 if !ok {
49 return "", false
50 }
51 urlS, found := data["url"]
52 if !found {
53 return "", false
54 }
55 url, ok := urlS.(string)
56 return url, ok
57 }
58
59 func proxyDirector(req *http.Request) {
60
61 log.Printf("proxyDirector: %s\n", req.RequestURI)
62
63 abort := func(format string, args ...interface{}) {
64 log.Printf(format, args...)
65 panic(http.ErrAbortHandler)
66 }
67
68 vars := mux.Vars(req)
69
70 var s string
71
72 if entry, found := vars["entry"]; found {
73 if s, found = findEntry(entry); !found {
74 abort("Cannot find entry '%s'\n", entry)
75 }
76 } else {
77 expectedMAC, err := base64.URLEncoding.DecodeString(vars["hash"])
78 if err != nil {
79 abort("Cannot base64 decode hash: %v\n", err)
80 }
81 url, err := base64.URLEncoding.DecodeString(vars["url"])
82 if err != nil {
83 abort("Cannot base64 decode url: %v\n", err)
84 }
85
86 mac := hmac.New(sha256.New, config.ProxyKey())
87 mac.Write(url)
88 messageMAC := mac.Sum(nil)
89
90 s = string(url)
91
92 if !hmac.Equal(messageMAC, expectedMAC) {
93 abort("HMAC of URL %s failed.\n", s)
94 }
95 }
96
97 nURL := s + "?" + req.URL.RawQuery
98 //log.Printf("%v\n", nURL)
99
100 u, err := url.Parse(nURL)
101 if err != nil {
102 abort("Invalid url: %v\n", err)
103 }
104 req.URL = u
105
106 req.Host = u.Host
107 //req.Header.Del("If-None-Match")
108 //log.Printf("headers: %v\n", req.Header)
109 }
110
111 type nopCloser struct {
112 io.Writer
113 }
114
115 func (nopCloser) Close() error { return nil }
116
117 func encoding(h http.Header) (
118 func(io.Reader) (io.ReadCloser, error),
119 func(io.Writer) (io.WriteCloser, error),
120 ) {
121 switch enc := h.Get("Content-Encoding"); {
122 case strings.Contains(enc, "gzip"):
123 log.Println("gzip compression")
124 return func(r io.Reader) (io.ReadCloser, error) {
125 return gzip.NewReader(r)
126 },
127 func(w io.Writer) (io.WriteCloser, error) {
128 return gzip.NewWriter(w), nil
129 }
130 case strings.Contains(enc, "deflate"):
131 log.Println("Deflate compression")
132 return func(r io.Reader) (io.ReadCloser, error) {
133 return flate.NewReader(r), nil
134 },
135 func(w io.Writer) (io.WriteCloser, error) {
136 return flate.NewWriter(w, flate.DefaultCompression)
137 }
138 default:
139 log.Println("No content compression")
140 return func(r io.Reader) (io.ReadCloser, error) {
141 if r2, ok := r.(io.ReadCloser); ok {
142 return r2, nil
143 }
144 return ioutil.NopCloser(r), nil
145 },
146 func(w io.Writer) (io.WriteCloser, error) {
147 if w2, ok := w.(io.WriteCloser); ok {
148 return w2, nil
149 }
150 return nopCloser{w}, nil
151 }
152 }
153 }
154
155 func proxyModifyResponse(resp *http.Response) error {
156
157 if !isXML(resp.Header) {
158 return nil
159 }
160
161 pr, pw := io.Pipe()
162
163 var (
164 r io.ReadCloser
165 w io.WriteCloser
166 err error
167 )
168
169 reader, writer := encoding(resp.Header)
170
171 if r, err = reader(resp.Body); err != nil {
172 return err
173 }
174
175 if w, err = writer(pw); err != nil {
176 return err
177 }
178
179 go func(force io.ReadCloser) {
180 start := time.Now()
181 defer func() {
182 //r.Close()
183 w.Close()
184 pw.Close()
185 force.Close()
186 log.Printf("rewrite took %s\n", time.Since(start))
187 }()
188 if err := rewrite(w, r); err != nil {
189 log.Printf("rewrite failed: %v\n", err)
190 return
191 }
192 log.Println("rewrite successful")
193 }(resp.Body)
194
195 resp.Body = pr
196
197 return nil
198 }
199
200 var xmlContentTypes = []string{
201 "application/xml",
202 "text/xml",
203 "application/gml+xml",
204 }
205
206 func isXML(h http.Header) bool {
207 for _, t := range h["Content-Type"] {
208 t = strings.ToLower(t)
209 for _, ct := range xmlContentTypes {
210 if strings.Contains(t, ct) {
211 return true
212 }
213 }
214 }
215 return false
216 }
217
218 var replaceRe = regexp.MustCompile(`\b(https?://[^\s\?]*)`)
219
220 func replace(s string) string {
221
222 proxyKey := config.ProxyKey()
223 proxyPrefix := config.ProxyPrefix() + "/api/proxy/"
224
225 return replaceRe.ReplaceAllStringFunc(s, func(s string) string {
226 if _, found := proxyBlackList[s]; found {
227 return s
228 }
229 mac := hmac.New(sha256.New, proxyKey)
230 b := []byte(s)
231 mac.Write(b)
232 expectedMAC := mac.Sum(nil)
233
234 hash := base64.URLEncoding.EncodeToString(expectedMAC)
235 enc := base64.URLEncoding.EncodeToString(b)
236 return proxyPrefix + hash + "/" + enc
237 })
238 }
239
240 func rewrite(w io.Writer, r io.Reader) error {
241
242 decoder := xml.NewDecoder(r)
243 decoder.CharsetReader = charset.NewReaderLabel
244
245 encoder := xml.NewEncoder(w)
246
247 var n nsdef
248
249 tokens:
250 for {
251 tok, err := decoder.Token()
252 switch {
253 case tok == nil && err == io.EOF:
254 break tokens
255 case err != nil:
256 return err
257 }
258
259 switch t := tok.(type) {
260 case xml.StartElement:
261 t = t.Copy()
262
263 isDef := n.isDef(t.Name.Space)
264 n = n.push()
265
266 for i := range t.Attr {
267 t.Attr[i].Value = replace(t.Attr[i].Value)
268 n.checkDef(&t.Attr[i])
269 }
270
271 for i := range t.Attr {
272 n.adjust(&t.Attr[i])
273 }
274
275 switch {
276 case isDef:
277 t.Name.Space = ""
278 default:
279 if s := n.lookup(t.Name.Space); s != "" {
280 t.Name.Space = ""
281 t.Name.Local = s + ":" + t.Name.Local
282 }
283 }
284 tok = t
285
286 case xml.CharData:
287 tok = xml.CharData(replace(string(t)))
288
289 case xml.EndElement:
290 s := n.lookup(t.Name.Space)
291
292 n = n.pop()
293
294 if n.isDef(t.Name.Space) {
295 t.Name.Space = ""
296 } else if s != "" {
297 t.Name.Space = ""
298 t.Name.Local = s + ":" + t.Name.Local
299 }
300 tok = t
301 }
302
303 if err := encoder.EncodeToken(tok); err != nil {
304 return err
305 }
306 }
307
308 return encoder.Flush()
309 }
310
311 type nsframe struct {
312 def string
313 ns map[string]string
314 }
315
316 type nsdef []nsframe
317
318 func (n nsdef) setDef(def string) {
319 if l := len(n); l > 0 {
320 n[l-1].def = def
321 }
322 }
323
324 func (n nsdef) isDef(s string) bool {
325 for i := len(n) - 1; i >= 0; i-- {
326 if x := n[i].def; x != "" {
327 return s == x
328 }
329 }
330 return false
331 }
332
333 func (n nsdef) define(ns, s string) {
334 if l := len(n); l > 0 {
335 n[l-1].ns[ns] = s
336 }
337 }
338
339 func (n nsdef) lookup(ns string) string {
340 for i := len(n) - 1; i >= 0; i-- {
341 if s := n[i].ns[ns]; s != "" {
342 return s
343 }
344 }
345 return ""
346 }
347
348 func (n nsdef) checkDef(at *xml.Attr) {
349 if at.Name.Space == "" && at.Name.Local == "xmlns" {
350 n.setDef(at.Value)
351 }
352 }
353
354 func (n nsdef) adjust(at *xml.Attr) {
355 switch {
356 case at.Name.Space == "xmlns":
357 n.define(at.Value, at.Name.Local)
358 at.Name.Local = "xmlns:" + at.Name.Local
359 at.Name.Space = ""
360
361 case at.Name.Space != "":
362 if n.isDef(at.Name.Space) {
363 at.Name.Space = ""
364 } else if s := n.lookup(at.Name.Space); s != "" {
365 at.Name.Local = s + ":" + at.Name.Local
366 at.Name.Space = ""
367 }
368 }
369 }
370
371 func (n nsdef) push() nsdef {
372 return append(n, nsframe{ns: make(map[string]string)})
373 }
374
375 func (n nsdef) pop() nsdef {
376 if l := len(n); l > 0 {
377 n[l-1] = nsframe{}
378 n = n[:l-1]
379 }
380 return n
381 }