diff --git a/api/admin_inspect.go b/api/admin_inspect.go index c59eb5c0ed..6774cd6553 100644 --- a/api/admin_inspect.go +++ b/api/admin_inspect.go @@ -120,7 +120,7 @@ func processInspectResponse(params *inspectApi.InspectParams, k []byte, r io.Rea _, err := io.Copy(w, r) if err != nil { - LogError("Unable to write all the data: %v", err) + LogError("unable to write all the data: %v", err) } } } diff --git a/api/user_objects.go b/api/user_objects.go index b3f225dab3..b78a315044 100644 --- a/api/user_objects.go +++ b/api/user_objects.go @@ -448,6 +448,8 @@ func getDownloadObjectResponse(session *models.Principal, params objectApi.Downl // override filename is set decodeOverride, err := base64.StdEncoding.DecodeString(*params.OverrideFileName) if err != nil { + fmtError := ErrorWithContext(ctx, fmt.Errorf("unable to decode OverrideFileName: %v", err)) + http.Error(rw, fmtError.APIError.DetailedMessage, http.StatusBadRequest) return } @@ -472,16 +474,16 @@ func getDownloadObjectResponse(session *models.Principal, params objectApi.Downl stat, err := resp.Stat() if err != nil { minErr := minio.ToErrorResponse(err) - rw.WriteHeader(minErr.StatusCode) - ErrorWithContext(ctx, fmt.Errorf("Failed to get Stat() response from server for %s (version %s): %v", prefix, opts.VersionID, minErr.Error())) + fmtError := ErrorWithContext(ctx, fmt.Errorf("failed to get Stat() response from server for %s (version %s): %v", prefix, opts.VersionID, minErr.Error())) + http.Error(rw, fmtError.APIError.DetailedMessage, http.StatusInternalServerError) return } // if we are getting a Range Request (video) handle that specially ranges, err := parseRange(params.HTTPRequest.Header.Get("Range"), stat.Size) if err != nil { - ErrorWithContext(ctx, fmt.Errorf("Unable to parse range header input %s: %v", params.HTTPRequest.Header.Get("Range"), err)) - rw.WriteHeader(400) + fmtError := ErrorWithContext(ctx, fmt.Errorf("unable to parse range header input %s: %v", params.HTTPRequest.Header.Get("Range"), err)) + http.Error(rw, fmtError.APIError.DetailedMessage, http.StatusInternalServerError) return } contentType := stat.ContentType @@ -510,8 +512,8 @@ func getDownloadObjectResponse(session *models.Principal, params objectApi.Downl _, err = resp.Seek(start, io.SeekStart) if err != nil { - ErrorWithContext(ctx, fmt.Errorf("Unable to seek at offset %d: %v", start, err)) - rw.WriteHeader(400) + fmtError := ErrorWithContext(ctx, fmt.Errorf("unable to seek at offset %d: %v", start, err)) + http.Error(rw, fmtError.APIError.DetailedMessage, http.StatusInternalServerError) return } @@ -524,7 +526,9 @@ func getDownloadObjectResponse(session *models.Principal, params objectApi.Downl rw.Header().Set("Content-Length", fmt.Sprintf("%d", length)) _, err = io.Copy(rw, io.LimitReader(resp, length)) if err != nil { - ErrorWithContext(ctx, fmt.Errorf("Unable to write all data to client: %v", err)) + ErrorWithContext(ctx, fmt.Errorf("unable to write all data to client: %v", err)) + // You can't change headers after you already started writing the body. + // Handle incomplete write in client. return } }), nil @@ -610,7 +614,8 @@ func getDownloadFolderResponse(session *models.Principal, params objectApi.Downl encodedPrefix := SanitizeEncodedPrefix(params.Prefix) decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix) if err != nil { - ErrorWithContext(ctx, fmt.Errorf("Unable to parse encoded prefix %s: %v", encodedPrefix, err)) + fmtError := ErrorWithContext(ctx, fmt.Errorf("unable to parse encoded prefix %s: %v", encodedPrefix, err)) + http.Error(rw, fmtError.APIError.DetailedMessage, http.StatusInternalServerError) return } @@ -632,7 +637,10 @@ func getDownloadFolderResponse(session *models.Principal, params objectApi.Downl // Copy the stream _, err := io.Copy(rw, resp) if err != nil { - ErrorWithContext(ctx, fmt.Errorf("Unable to write all the requested data: %v", err)) + ErrorWithContext(ctx, fmt.Errorf("unable to write all the requested data: %v", err)) + // You can't change headers after you already started writing the body. + // Handle incomplete write in client. + return } }), nil } @@ -754,7 +762,10 @@ func getMultipleFilesDownloadResponse(session *models.Principal, params objectAp // Copy the stream _, err := io.Copy(rw, resp) if err != nil { - ErrorWithContext(ctx, fmt.Errorf("Unable to write all the requested data: %v", err)) + ErrorWithContext(ctx, fmt.Errorf("unable to write all the requested data: %v", err)) + // You can't change headers after you already started writing the body. + // Handle incomplete write in client. + return } }), nil } diff --git a/web-app/src/screens/Console/Buckets/ListBuckets/Objects/utils.ts b/web-app/src/screens/Console/Buckets/ListBuckets/Objects/utils.ts index 6cb778e563..626bea53b4 100644 --- a/web-app/src/screens/Console/Buckets/ListBuckets/Objects/utils.ts +++ b/web-app/src/screens/Console/Buckets/ListBuckets/Objects/utils.ts @@ -21,7 +21,7 @@ import store from "../../../../../store"; import { ContentType, PermissionResource } from "api/consoleApi"; import { api } from "../../../../../api"; import { setErrorSnackMessage } from "../../../../../systemSlice"; - +import { StatusCodes } from "http-status-codes"; const downloadWithLink = (href: string, downloadFileName: string) => { const link = document.createElement("a"); link.href = href; @@ -78,6 +78,7 @@ export const download = ( toastCallback: () => void, ) => { let basename = document.baseURI.replace(window.location.origin, ""); + let bytesLoaded = 0; const state = store.getState(); const anonymousMode = state.system.anonymousMode; @@ -106,7 +107,7 @@ export const download = ( "progress", function (evt) { let percentComplete = Math.round((evt.loaded / fileSize) * 100); - + bytesLoaded = evt.loaded; if (progressCallback) { progressCallback(percentComplete); } @@ -116,8 +117,10 @@ export const download = ( req.responseType = "blob"; req.onreadystatechange = () => { - if (req.readyState === 4) { - if (req.status === 200) { + if (req.readyState === XMLHttpRequest.DONE) { + let completeDownload = bytesLoaded === fileSize; + + if (req.status === StatusCodes.OK && completeDownload) { const rspHeader = req.getResponseHeader("Content-Disposition"); let filename = "download"; @@ -143,7 +146,7 @@ export const download = ( return; } } - errorCallback(`Unexpected response status code (${req.status}).`); + errorCallback(`Unexpected response, download incomplete.`); } } };