Mercurial > gemma
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 } |