Path: blob/main/components/proxy/plugins/workspacedownload/workspace_download.go
2500 views
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34package workspacedownload56import (7"fmt"8"io"9"net/http"10"time"1112"github.com/caddyserver/caddy/v2"13"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"14"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"15"github.com/caddyserver/caddy/v2/modules/caddyhttp"16)1718const (19workspaceDownloadModule = "gitpod.workspace_download"20)2122func init() {23caddy.RegisterModule(Download{})24httpcaddyfile.RegisterHandlerDirective(workspaceDownloadModule, parseCaddyfile)25}2627// Download implements an HTTP handler that extracts gitpod headers28type Download struct {29Service string `json:"service,omitempty"`30}3132// CaddyModule returns the Caddy module information.33func (Download) CaddyModule() caddy.ModuleInfo {34return caddy.ModuleInfo{35ID: "http.handlers.gitpod_workspace_download",36New: func() caddy.Module { return new(Download) },37}38}3940// ServeHTTP implements caddyhttp.MiddlewareHandler.41func (m Download) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {42origReq := r.Context().Value(caddyhttp.OriginalRequestCtxKey).(http.Request)4344query := r.URL.RawQuery45if query != "" {46query = "?" + query47}4849url := fmt.Sprintf("%v%v%v", m.Service, origReq.URL.Path, query)50client := http.Client{Timeout: 5 * time.Second}51req, err := http.NewRequest("GET", url, nil)52if err != nil {53return fmt.Errorf("Server Error: cannot download token OTS")54}5556// pass browser headers57// TODO (aledbf): check if it's possible to narrow the list58for k, vv := range r.Header {59for _, v := range vv {60req.Header.Add(k, v)61}62}6364// override content-type65req.Header.Set("Content-Type", "*/*")6667resp, err := client.Do(req)68if err != nil {69return fmt.Errorf("Server Error: cannot download token OTS")70}71defer resp.Body.Close()7273if resp.StatusCode != http.StatusOK {74return fmt.Errorf("Bad Request: /workspace-download/get returned with code %v", resp.StatusCode)75}7677upstreamURLBytes, err := io.ReadAll(resp.Body)78if err != nil {79return fmt.Errorf("server error: cannot obtain workspace download URL")80}81upstreamURL := string(upstreamURLBytes)8283// perform the upstream request here84resp, err = http.Get(upstreamURL)85if err != nil {86caddy.Log().Sugar().Errorf("error starting download of workspace for %v: %v", upstreamURL, err)87return caddyhttp.Error(http.StatusInternalServerError, fmt.Errorf("unexpected error downloading workspace"))88}89defer resp.Body.Close()9091if resp.StatusCode != http.StatusOK {92caddy.Log().Sugar().Errorf("invalid status code downloading workspace for %v: %v", upstreamURL, resp.StatusCode)93return caddyhttp.Error(http.StatusInternalServerError, fmt.Errorf("unexpected error downloading workspace"))94}9596brw := newNoBufferResponseWriter(w)97_, err = io.Copy(brw, resp.Body)98if err != nil {99caddy.Log().Sugar().Errorf("error proxying workspace download for %v: %v", upstreamURL, err)100return caddyhttp.Error(http.StatusInternalServerError, fmt.Errorf("unexpected error downloading workspace"))101}102103return next.ServeHTTP(w, r)104}105106// UnmarshalCaddyfile implements Caddyfile.Unmarshaler.107func (m *Download) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {108if !d.Next() {109return d.Err("expected token following filter")110}111112for d.NextBlock(0) {113key := d.Val()114var value string115d.Args(&value)116if d.NextArg() {117return d.ArgErr()118}119120switch key {121case "service":122m.Service = value123default:124return d.Errf("unrecognized subdirective '%s'", d.Val())125}126}127128if m.Service == "" {129return fmt.Errorf("Please configure the service subdirective")130}131132return nil133}134135func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {136m := new(Download)137err := m.UnmarshalCaddyfile(h.Dispenser)138if err != nil {139return nil, err140}141142return m, nil143}144145// Interface guards146var (147_ caddyhttp.MiddlewareHandler = (*Download)(nil)148_ caddyfile.Unmarshaler = (*Download)(nil)149)150151type noBufferWriter struct {152w http.ResponseWriter153flusher http.Flusher154}155156func newNoBufferResponseWriter(w http.ResponseWriter) *noBufferWriter {157writer := &noBufferWriter{158w: w,159}160if flusher, ok := w.(http.Flusher); ok {161writer.flusher = flusher162}163return writer164}165166func (n *noBufferWriter) Write(p []byte) (written int, err error) {167written, err = n.w.Write(p)168if n.flusher != nil {169n.flusher.Flush()170}171172return173}174175176