-
Notifications
You must be signed in to change notification settings - Fork 209
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix support resumable + fix incorrect handling of paths which start with a / #918
Changes from all commits
0b0b083
85740a1
be16d56
434ddfe
0e5e65f
41e8db7
c62771b
2792636
6412da7
a5991c7
07a0d59
71bb72e
8695101
d18156b
ed3cf5d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ import ( | |
"net/http/httptest" | ||
"net/http/httputil" | ||
"net/textproto" | ||
"net/url" | ||
"strings" | ||
"sync" | ||
|
||
|
@@ -211,9 +212,22 @@ func newServer(options Options) (*Server, error) { | |
return &s, nil | ||
} | ||
|
||
func unescapeMuxVars(vars map[string]string) map[string]string { | ||
m := make(map[string]string) | ||
for k, v := range vars { | ||
r, err := url.PathUnescape(v) | ||
if err == nil { | ||
m[k] = r | ||
} else { | ||
m[k] = v | ||
} | ||
} | ||
return m | ||
} | ||
|
||
func (s *Server) buildMuxer() { | ||
const apiPrefix = "/storage/v1" | ||
s.mux = mux.NewRouter() | ||
s.mux = mux.NewRouter().SkipClean(true).UseEncodedPath() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reason for this.
This fix issue, that if we use names like
We do not fix urls, what defined by sdk or users |
||
|
||
// healthcheck | ||
s.mux.Path("/_internal/healthcheck").Methods(http.MethodGet).HandlerFunc(s.healthcheck) | ||
|
@@ -251,6 +265,7 @@ func (s *Server) buildMuxer() { | |
s.mux.Host(bucketHost).Path("/{objectName:.+}").Methods(http.MethodGet, http.MethodHead).HandlerFunc(s.downloadObject) | ||
s.mux.Path("/download/storage/v1/b/{bucketName}/o/{objectName:.+}").Methods(http.MethodGet).HandlerFunc(s.downloadObject) | ||
s.mux.Path("/upload/storage/v1/b/{bucketName}/o").Methods(http.MethodPost).HandlerFunc(jsonToHTTPHandler(s.insertObject)) | ||
s.mux.Path("/upload/storage/v1/b/{bucketName}/o").Methods(http.MethodPut).HandlerFunc(jsonToHTTPHandler(s.uploadFileContent)) | ||
s.mux.Path("/upload/resumable/{uploadId}").Methods(http.MethodPut, http.MethodPost).HandlerFunc(jsonToHTTPHandler(s.uploadFileContent)) | ||
|
||
// Batch endpoint | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ import ( | |
"mime" | ||
"mime/multipart" | ||
"net/http" | ||
"net/url" | ||
"strconv" | ||
"strings" | ||
|
||
|
@@ -46,7 +47,7 @@ type contentRange struct { | |
} | ||
|
||
func (s *Server) insertObject(r *http.Request) jsonResponse { | ||
bucketName := mux.Vars(r)["bucketName"] | ||
bucketName := unescapeMuxVars(mux.Vars(r))["bucketName"] | ||
|
||
if _, err := s.backend.GetBucket(bucketName); err != nil { | ||
return jsonResponse{status: http.StatusNotFound} | ||
|
@@ -78,7 +79,7 @@ func (s *Server) insertObject(r *http.Request) jsonResponse { | |
} | ||
|
||
func (s *Server) insertFormObject(r *http.Request) xmlResponse { | ||
bucketName := mux.Vars(r)["bucketName"] | ||
bucketName := unescapeMuxVars(mux.Vars(r))["bucketName"] | ||
|
||
if err := r.ParseMultipartForm(32 << 20); nil != err { | ||
return xmlResponse{errorMessage: "invalid form", status: http.StatusBadRequest} | ||
|
@@ -242,7 +243,7 @@ func (s notImplementedSeeker) Seek(offset int64, whence int) (int64, error) { | |
|
||
func (s *Server) signedUpload(bucketName string, r *http.Request) jsonResponse { | ||
defer r.Body.Close() | ||
name := mux.Vars(r)["objectName"] | ||
name := unescapeMuxVars(mux.Vars(r))["objectName"] | ||
predefinedACL := r.URL.Query().Get("predefinedAcl") | ||
contentEncoding := r.URL.Query().Get("contentEncoding") | ||
|
||
|
@@ -362,6 +363,9 @@ func (s *Server) multipartUpload(bucketName string, r *http.Request) jsonRespons | |
} | ||
|
||
func (s *Server) resumableUpload(bucketName string, r *http.Request) jsonResponse { | ||
if r.URL.Query().Has("upload_id") { | ||
return s.uploadFileContent(r) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if |
||
} | ||
predefinedACL := r.URL.Query().Get("predefinedAcl") | ||
contentEncoding := r.URL.Query().Get("contentEncoding") | ||
metadata := new(multipartMetadata) | ||
|
@@ -391,9 +395,16 @@ func (s *Server) resumableUpload(bucketName string, r *http.Request) jsonRespons | |
} | ||
s.uploads.Store(uploadID, obj) | ||
header := make(http.Header) | ||
header.Set("Location", s.URL()+"/upload/resumable/"+uploadID) | ||
location := fmt.Sprintf( | ||
"%s/upload/storage/v1/b/%s/o?uploadType=resumable&name=%s&upload_id=%s", | ||
s.URL(), | ||
bucketName, | ||
url.PathEscape(objName), | ||
uploadID, | ||
) | ||
header.Set("Location", location) | ||
if r.Header.Get("X-Goog-Upload-Command") == "start" { | ||
header.Set("X-Goog-Upload-URL", s.URL()+"/upload/resumable/"+uploadID) | ||
header.Set("X-Goog-Upload-URL", location) | ||
header.Set("X-Goog-Upload-Status", "active") | ||
} | ||
return jsonResponse{ | ||
|
@@ -438,7 +449,7 @@ func (s *Server) resumableUpload(bucketName string, r *http.Request) jsonRespons | |
// then has a status of "200 OK", with a header "X-Http-Status-Code-Override" | ||
// set to "308". | ||
func (s *Server) uploadFileContent(r *http.Request) jsonResponse { | ||
uploadID := mux.Vars(r)["uploadId"] | ||
uploadID := r.URL.Query().Get("upload_id") | ||
rawObj, ok := s.uploads.Load(uploadID) | ||
if !ok { | ||
return jsonResponse{status: http.StatusNotFound} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In some places we use
r.URL.Query()
, which provide already decoded values. Butmux.Vars(r)
provide values "as is" (without decoding). That is why we need decodeobjectName
andbucketName
ourself. This is important, because before accessing to storage we encode values and without this this lead to double encoding.