16535: Refactor test suite to accommodate "project bucket" tests.
[arvados.git] / services / keep-web / s3.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package main
6
7 import (
8         "fmt"
9         "io"
10         "net/http"
11         "os"
12         "strings"
13 )
14
15 // serveS3 handles r and returns true if r is a request from an S3
16 // client, otherwise it returns false.
17 func (h *handler) serveS3(w http.ResponseWriter, r *http.Request) bool {
18         var token string
19         if auth := r.Header.Get("Authorization"); strings.HasPrefix(auth, "AWS ") {
20                 split := strings.SplitN(auth[4:], ":", 2)
21                 if len(split) < 2 {
22                         w.WriteHeader(http.StatusUnauthorized)
23                         return true
24                 }
25                 token = split[0]
26         } else if strings.HasPrefix(auth, "AWS4-HMAC-SHA256 ") {
27                 w.WriteHeader(http.StatusBadRequest)
28                 fmt.Println(w, "V4 signature is not supported")
29                 return true
30         } else {
31                 return false
32         }
33
34         _, kc, client, release, err := h.getClients(r.Header.Get("X-Request-Id"), token)
35         if err != nil {
36                 http.Error(w, "Pool failed: "+h.clientPool.Err().Error(), http.StatusInternalServerError)
37                 return true
38         }
39         defer release()
40
41         r.URL.Path = "/by_id" + r.URL.Path
42
43         fs := client.SiteFileSystem(kc)
44         fs.ForwardSlashNameSubstitution(h.Config.cluster.Collections.ForwardSlashNameSubstitution)
45
46         switch r.Method {
47         case "GET":
48                 fi, err := fs.Stat(r.URL.Path)
49                 if os.IsNotExist(err) {
50                         http.Error(w, err.Error(), http.StatusNotFound)
51                         return true
52                 } else if err != nil {
53                         http.Error(w, err.Error(), http.StatusInternalServerError)
54                         return true
55                 } else if fi.IsDir() {
56                         http.Error(w, "not found", http.StatusNotFound)
57                 }
58                 http.FileServer(fs).ServeHTTP(w, r)
59                 return true
60         case "PUT":
61                 f, err := fs.OpenFile(r.URL.Path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
62                 if os.IsNotExist(err) {
63                         // create missing intermediate directories, then try again
64                         for i, c := range r.URL.Path {
65                                 if i > 0 && c == '/' {
66                                         dir := r.URL.Path[:i]
67                                         err := fs.Mkdir(dir, 0755)
68                                         if err != nil && err != os.ErrExist {
69                                                 err = fmt.Errorf("mkdir %q failed: %w", dir, err)
70                                                 http.Error(w, err.Error(), http.StatusInternalServerError)
71                                                 return true
72                                         }
73                                 }
74                         }
75                         f, err = fs.OpenFile(r.URL.Path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
76                 }
77                 if err != nil {
78                         err = fmt.Errorf("open %q failed: %w", r.URL.Path, err)
79                         http.Error(w, err.Error(), http.StatusBadRequest)
80                         return true
81                 }
82                 defer f.Close()
83                 _, err = io.Copy(f, r.Body)
84                 if err != nil {
85                         err = fmt.Errorf("write to %q failed: %w", r.URL.Path, err)
86                         http.Error(w, err.Error(), http.StatusBadGateway)
87                         return true
88                 }
89                 err = f.Close()
90                 if err != nil {
91                         err = fmt.Errorf("write to %q failed: %w", r.URL.Path, err)
92                         http.Error(w, err.Error(), http.StatusBadGateway)
93                         return true
94                 }
95                 err = fs.Sync()
96                 if err != nil {
97                         err = fmt.Errorf("sync failed: %w", err)
98                         http.Error(w, err.Error(), http.StatusInternalServerError)
99                         return true
100                 }
101                 w.WriteHeader(http.StatusOK)
102                 return true
103         default:
104                 http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
105                 return true
106         }
107 }