Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/proxy/plugins/workspacedownload/workspace_download.go
2500 views
1
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package workspacedownload
6
7
import (
8
"fmt"
9
"io"
10
"net/http"
11
"time"
12
13
"github.com/caddyserver/caddy/v2"
14
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
15
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
16
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
17
)
18
19
const (
20
workspaceDownloadModule = "gitpod.workspace_download"
21
)
22
23
func init() {
24
caddy.RegisterModule(Download{})
25
httpcaddyfile.RegisterHandlerDirective(workspaceDownloadModule, parseCaddyfile)
26
}
27
28
// Download implements an HTTP handler that extracts gitpod headers
29
type Download struct {
30
Service string `json:"service,omitempty"`
31
}
32
33
// CaddyModule returns the Caddy module information.
34
func (Download) CaddyModule() caddy.ModuleInfo {
35
return caddy.ModuleInfo{
36
ID: "http.handlers.gitpod_workspace_download",
37
New: func() caddy.Module { return new(Download) },
38
}
39
}
40
41
// ServeHTTP implements caddyhttp.MiddlewareHandler.
42
func (m Download) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
43
origReq := r.Context().Value(caddyhttp.OriginalRequestCtxKey).(http.Request)
44
45
query := r.URL.RawQuery
46
if query != "" {
47
query = "?" + query
48
}
49
50
url := fmt.Sprintf("%v%v%v", m.Service, origReq.URL.Path, query)
51
client := http.Client{Timeout: 5 * time.Second}
52
req, err := http.NewRequest("GET", url, nil)
53
if err != nil {
54
return fmt.Errorf("Server Error: cannot download token OTS")
55
}
56
57
// pass browser headers
58
// TODO (aledbf): check if it's possible to narrow the list
59
for k, vv := range r.Header {
60
for _, v := range vv {
61
req.Header.Add(k, v)
62
}
63
}
64
65
// override content-type
66
req.Header.Set("Content-Type", "*/*")
67
68
resp, err := client.Do(req)
69
if err != nil {
70
return fmt.Errorf("Server Error: cannot download token OTS")
71
}
72
defer resp.Body.Close()
73
74
if resp.StatusCode != http.StatusOK {
75
return fmt.Errorf("Bad Request: /workspace-download/get returned with code %v", resp.StatusCode)
76
}
77
78
upstreamURLBytes, err := io.ReadAll(resp.Body)
79
if err != nil {
80
return fmt.Errorf("server error: cannot obtain workspace download URL")
81
}
82
upstreamURL := string(upstreamURLBytes)
83
84
// perform the upstream request here
85
resp, err = http.Get(upstreamURL)
86
if err != nil {
87
caddy.Log().Sugar().Errorf("error starting download of workspace for %v: %v", upstreamURL, err)
88
return caddyhttp.Error(http.StatusInternalServerError, fmt.Errorf("unexpected error downloading workspace"))
89
}
90
defer resp.Body.Close()
91
92
if resp.StatusCode != http.StatusOK {
93
caddy.Log().Sugar().Errorf("invalid status code downloading workspace for %v: %v", upstreamURL, resp.StatusCode)
94
return caddyhttp.Error(http.StatusInternalServerError, fmt.Errorf("unexpected error downloading workspace"))
95
}
96
97
brw := newNoBufferResponseWriter(w)
98
_, err = io.Copy(brw, resp.Body)
99
if err != nil {
100
caddy.Log().Sugar().Errorf("error proxying workspace download for %v: %v", upstreamURL, err)
101
return caddyhttp.Error(http.StatusInternalServerError, fmt.Errorf("unexpected error downloading workspace"))
102
}
103
104
return next.ServeHTTP(w, r)
105
}
106
107
// UnmarshalCaddyfile implements Caddyfile.Unmarshaler.
108
func (m *Download) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
109
if !d.Next() {
110
return d.Err("expected token following filter")
111
}
112
113
for d.NextBlock(0) {
114
key := d.Val()
115
var value string
116
d.Args(&value)
117
if d.NextArg() {
118
return d.ArgErr()
119
}
120
121
switch key {
122
case "service":
123
m.Service = value
124
default:
125
return d.Errf("unrecognized subdirective '%s'", d.Val())
126
}
127
}
128
129
if m.Service == "" {
130
return fmt.Errorf("Please configure the service subdirective")
131
}
132
133
return nil
134
}
135
136
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
137
m := new(Download)
138
err := m.UnmarshalCaddyfile(h.Dispenser)
139
if err != nil {
140
return nil, err
141
}
142
143
return m, nil
144
}
145
146
// Interface guards
147
var (
148
_ caddyhttp.MiddlewareHandler = (*Download)(nil)
149
_ caddyfile.Unmarshaler = (*Download)(nil)
150
)
151
152
type noBufferWriter struct {
153
w http.ResponseWriter
154
flusher http.Flusher
155
}
156
157
func newNoBufferResponseWriter(w http.ResponseWriter) *noBufferWriter {
158
writer := &noBufferWriter{
159
w: w,
160
}
161
if flusher, ok := w.(http.Flusher); ok {
162
writer.flusher = flusher
163
}
164
return writer
165
}
166
167
func (n *noBufferWriter) Write(p []byte) (written int, err error) {
168
written, err = n.w.Write(p)
169
if n.flusher != nil {
170
n.flusher.Flush()
171
}
172
173
return
174
}
175
176