2019-06-11 16:23:20 +00:00
package handler
2015-11-29 01:33:55 +00:00
import (
2019-06-11 14:16:02 +00:00
"context"
2015-11-29 01:33:55 +00:00
"encoding/base64"
"io"
"log"
2018-06-03 16:37:45 +00:00
"math"
2017-03-01 18:43:37 +00:00
"net"
2015-11-29 01:33:55 +00:00
"net/http"
"regexp"
"strconv"
"strings"
2017-01-19 20:02:48 +00:00
"time"
2015-11-29 01:33:55 +00:00
)
2018-04-23 21:10:23 +00:00
const UploadLengthDeferred = "1"
2016-01-16 14:27:35 +00:00
var (
reExtractFileID = regexp . MustCompile ( ` ([^/]+)\/?$ ` )
2022-10-04 22:06:17 +00:00
reForwardedHost = regexp . MustCompile ( ` host="?([^;"]+) ` )
2016-01-16 14:27:35 +00:00
reForwardedProto = regexp . MustCompile ( ` proto=(https?) ` )
2019-10-07 09:28:13 +00:00
reMimeType = regexp . MustCompile ( ` ^[a-z]+\/[a-z0-9\-\+\.]+$ ` )
2016-01-16 14:27:35 +00:00
)
2015-11-29 01:33:55 +00:00
2017-01-26 22:15:05 +00:00
var (
2022-03-01 23:36:49 +00:00
ErrUnsupportedVersion = NewError ( "ERR_UNSUPPORTED_VERSION" , "missing, invalid or unsupported Tus-Resumable header" , http . StatusPreconditionFailed )
ErrMaxSizeExceeded = NewError ( "ERR_MAX_SIZE_EXCEEDED" , "maximum size exceeded" , http . StatusRequestEntityTooLarge )
ErrInvalidContentType = NewError ( "ERR_INVALID_CONTENT_TYPE" , "missing or invalid Content-Type header" , http . StatusBadRequest )
ErrInvalidUploadLength = NewError ( "ERR_INVALID_UPLOAD_LENGTH" , "missing or invalid Upload-Length header" , http . StatusBadRequest )
ErrInvalidOffset = NewError ( "ERR_INVALID_OFFSET" , "missing or invalid Upload-Offset header" , http . StatusBadRequest )
ErrNotFound = NewError ( "ERR_UPLOAD_NOT_FOUND" , "upload not found" , http . StatusNotFound )
ErrFileLocked = NewError ( "ERR_UPLOAD_LOCKED" , "file currently locked" , http . StatusLocked )
2022-03-19 22:21:17 +00:00
ErrLockTimeout = NewError ( "ERR_LOCK_TIMEOUT" , "failed to acquire lock before timeout" , http . StatusInternalServerError )
2022-03-01 23:36:49 +00:00
ErrMismatchOffset = NewError ( "ERR_MISMATCHED_OFFSET" , "mismatched offset" , http . StatusConflict )
ErrSizeExceeded = NewError ( "ERR_UPLOAD_SIZE_EXCEEDED" , "upload's size exceeded" , http . StatusRequestEntityTooLarge )
ErrNotImplemented = NewError ( "ERR_NOT_IMPLEMENTED" , "feature not implemented" , http . StatusNotImplemented )
ErrUploadNotFinished = NewError ( "ERR_UPLOAD_NOT_FINISHED" , "one of the partial uploads is not finished" , http . StatusBadRequest )
ErrInvalidConcat = NewError ( "ERR_INVALID_CONCAT" , "invalid Upload-Concat header" , http . StatusBadRequest )
ErrModifyFinal = NewError ( "ERR_MODIFY_FINAL" , "modifying a final upload is not allowed" , http . StatusForbidden )
ErrUploadLengthAndUploadDeferLength = NewError ( "ERR_AMBIGUOUS_UPLOAD_LENGTH" , "provided both Upload-Length and Upload-Defer-Length" , http . StatusBadRequest )
ErrInvalidUploadDeferLength = NewError ( "ERR_INVALID_UPLOAD_LENGTH_DEFER" , "invalid Upload-Defer-Length header" , http . StatusBadRequest )
ErrUploadStoppedByServer = NewError ( "ERR_UPLOAD_STOPPED" , "upload has been stopped by server" , http . StatusBadRequest )
ErrUploadRejectedByServer = NewError ( "ERR_UPLOAD_REJECTED" , "upload creation has been rejected by server" , http . StatusBadRequest )
2023-06-20 09:36:15 +00:00
ErrUploadInterrupted = NewError ( "ERR_UPLOAD_INTERRUPTED" , "upload has been interrupted by another request for this upload resource" , http . StatusBadRequest )
2023-06-13 14:17:46 +00:00
ErrServerShutdown = NewError ( "ERR_SERVER_SHUTDOWN" , "request has been interrupted because the server is shutting down" , http . StatusInternalServerError )
2021-10-25 09:54:02 +00:00
// TODO: These two responses are 500 for backwards compatability. We should discuss
// whether it is better to more them to 4XX status codes.
2022-03-01 23:36:49 +00:00
ErrReadTimeout = NewError ( "ERR_READ_TIMEOUT" , "timeout while reading request body" , http . StatusInternalServerError )
ErrConnectionReset = NewError ( "ERR_CONNECTION_RESET" , "TCP connection reset by peer" , http . StatusInternalServerError )
2017-01-26 22:15:05 +00:00
)
2015-12-07 20:09:47 +00:00
// UnroutedHandler exposes methods to handle requests as part of the tus protocol,
// such as PostFile, HeadFile, PatchFile and DelFile. In addition the GetFile method
// is provided which is, however, not part of the specification.
2015-11-29 01:33:55 +00:00
type UnroutedHandler struct {
config Config
2016-02-21 22:25:35 +00:00
composer * StoreComposer
2015-11-29 01:33:55 +00:00
isBasePathAbs bool
basePath string
logger * log . Logger
2015-12-26 23:44:02 +00:00
extensions string
2023-06-13 14:17:46 +00:00
serverCtx chan struct { }
2015-11-29 01:33:55 +00:00
2016-03-12 21:28:24 +00:00
// CompleteUploads is used to send notifications whenever an upload is
2019-09-19 09:15:48 +00:00
// completed by a user. The HookEvent will contain information about this
2016-03-12 21:28:24 +00:00
// upload after it is completed. Sending to this channel will only
// happen if the NotifyCompleteUploads field is set to true in the Config
// structure. Notifications will also be sent for completions using the
// Concatenation extension.
2019-09-19 09:15:48 +00:00
CompleteUploads chan HookEvent
2016-03-12 21:24:57 +00:00
// TerminatedUploads is used to send notifications whenever an upload is
2019-09-19 09:15:48 +00:00
// terminated by a user. The HookEvent will contain information about this
2016-03-12 21:24:57 +00:00
// upload gathered before the termination. Sending to this channel will only
// happen if the NotifyTerminatedUploads field is set to true in the Config
// structure.
2019-09-19 09:15:48 +00:00
TerminatedUploads chan HookEvent
2017-02-21 22:17:07 +00:00
// UploadProgress is used to send notifications about the progress of the
// currently running uploads. For each open PATCH request, every second
2019-09-19 09:15:48 +00:00
// a HookEvent instance will be send over this channel with the Offset field
2017-02-21 22:17:07 +00:00
// being set to the number of bytes which have been transfered to the server.
// Please be aware that this number may be higher than the number of bytes
// which have been stored by the data store! Sending to this channel will only
// happen if the NotifyUploadProgress field is set to true in the Config
// structure.
2019-09-19 09:15:48 +00:00
UploadProgress chan HookEvent
2017-07-19 15:45:16 +00:00
// CreatedUploads is used to send notifications about the uploads having been
2019-09-19 09:15:48 +00:00
// created. It triggers post creation and therefore has all the HookEvent incl.
2017-07-19 15:45:16 +00:00
// the ID available already. It facilitates the post-create hook. Sending to
// this channel will only happen if the NotifyCreatedUploads field is set to
// true in the Config structure.
2019-09-19 09:15:48 +00:00
CreatedUploads chan HookEvent
2016-05-24 15:04:28 +00:00
// Metrics provides numbers of the usage for this handler.
Metrics Metrics
2015-11-29 01:33:55 +00:00
}
// NewUnroutedHandler creates a new handler without routing using the given
// configuration. It exposes the http handlers which need to be combined with
// a router (aka mux) of your choice. If you are looking for preconfigured
// handler see NewHandler.
func NewUnroutedHandler ( config Config ) ( * UnroutedHandler , error ) {
2016-02-21 22:25:35 +00:00
if err := config . validate ( ) ; err != nil {
2015-11-29 01:33:55 +00:00
return nil , err
}
2015-12-26 23:44:02 +00:00
// Only promote extesions using the Tus-Extension header which are implemented
2018-06-03 16:15:50 +00:00
extensions := "creation,creation-with-upload"
2016-02-21 22:25:35 +00:00
if config . StoreComposer . UsesTerminater {
2015-12-26 23:44:02 +00:00
extensions += ",termination"
}
2016-02-21 22:25:35 +00:00
if config . StoreComposer . UsesConcater {
2016-01-20 14:33:17 +00:00
extensions += ",concatenation"
}
2018-06-03 16:15:50 +00:00
if config . StoreComposer . UsesLengthDeferrer {
extensions += ",creation-defer-length"
}
2015-12-26 23:44:02 +00:00
2015-11-29 01:33:55 +00:00
handler := & UnroutedHandler {
2016-03-12 21:24:57 +00:00
config : config ,
composer : config . StoreComposer ,
basePath : config . BasePath ,
isBasePathAbs : config . isAbs ,
2019-09-19 09:15:48 +00:00
CompleteUploads : make ( chan HookEvent ) ,
TerminatedUploads : make ( chan HookEvent ) ,
UploadProgress : make ( chan HookEvent ) ,
CreatedUploads : make ( chan HookEvent ) ,
2016-03-12 21:24:57 +00:00
logger : config . Logger ,
extensions : extensions ,
2016-05-24 15:04:28 +00:00
Metrics : newMetrics ( ) ,
2023-06-13 14:17:46 +00:00
serverCtx : make ( chan struct { } ) ,
2015-11-29 01:33:55 +00:00
}
return handler , nil
}
2023-06-13 14:17:46 +00:00
// InterruptRequestHandling attempts to interrupt long running requests, so
// the server can shutdown gracefully. This function should not be used on
// its own, but as part of http.Server.Shutdown. For example:
//
// server := &http.Server{
// Handler: handler,
// }
// server.RegisterOnShutdown(handler.InterruptRequestHandling)
// server.Shutdown(ctx)
//
// Note: currently, this function only interrupts POST and PATCH requests
// with a request body. In the future, this might be extended to HEAD, DELETE
// and GET requests.
func ( handler UnroutedHandler ) InterruptRequestHandling ( ) {
close ( handler . serverCtx )
}
2019-09-10 13:26:37 +00:00
// SupportedExtensions returns a comma-separated list of the supported tus extensions.
// The availability of an extension usually depends on whether the provided data store
// implements some additional interfaces.
func ( handler * UnroutedHandler ) SupportedExtensions ( ) string {
return handler . extensions
}
2015-12-07 20:10:02 +00:00
// Middleware checks various aspects of the request and ensures that it
2015-11-29 01:33:55 +00:00
// conforms with the spec. Also handles method overriding for clients which
// cannot make PATCH AND DELETE requests. If you are using the tusd handlers
// directly you will need to wrap at least the POST and PATCH endpoints in
// this middleware.
2015-12-07 20:10:02 +00:00
func ( handler * UnroutedHandler ) Middleware ( h http . Handler ) http . Handler {
2015-11-29 01:33:55 +00:00
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
// Allow overriding the HTTP method. The reason for this is
// that some libraries/environments to not support PATCH and
// DELETE requests, e.g. Flash in a browser and parts of Java
if newMethod := r . Header . Get ( "X-HTTP-Method-Override" ) ; newMethod != "" {
r . Method = newMethod
}
2020-04-06 10:20:57 +00:00
handler . log ( "RequestIncoming" , "method" , r . Method , "path" , r . URL . Path , "requestId" , getRequestId ( r ) )
2016-09-23 19:21:38 +00:00
2019-03-21 19:04:01 +00:00
handler . Metrics . incRequestsTotal ( r . Method )
2015-11-29 01:33:55 +00:00
header := w . Header ( )
2023-03-26 22:11:41 +00:00
if origin := r . Header . Get ( "Origin" ) ; ! handler . config . DisableCors && origin != "" {
2015-11-29 01:33:55 +00:00
header . Set ( "Access-Control-Allow-Origin" , origin )
if r . Method == "OPTIONS" {
2022-03-28 21:43:35 +00:00
allowedMethods := "POST, HEAD, PATCH, OPTIONS"
if ! handler . config . DisableDownload {
allowedMethods += ", GET"
}
if ! handler . config . DisableTermination {
allowedMethods += ", DELETE"
}
2015-11-29 01:33:55 +00:00
// Preflight request
2022-03-28 21:43:35 +00:00
header . Add ( "Access-Control-Allow-Methods" , allowedMethods )
2020-04-06 10:24:05 +00:00
header . Add ( "Access-Control-Allow-Headers" , "Authorization, Origin, X-Requested-With, X-Request-ID, X-HTTP-Method-Override, Content-Type, Upload-Length, Upload-Offset, Tus-Resumable, Upload-Metadata, Upload-Defer-Length, Upload-Concat" )
2015-11-29 01:33:55 +00:00
header . Set ( "Access-Control-Max-Age" , "86400" )
} else {
// Actual request
2018-11-10 20:10:38 +00:00
header . Add ( "Access-Control-Expose-Headers" , "Upload-Offset, Location, Upload-Length, Tus-Version, Tus-Resumable, Tus-Max-Size, Tus-Extension, Upload-Metadata, Upload-Defer-Length, Upload-Concat" )
2015-11-29 01:33:55 +00:00
}
}
// Set current version used by the server
header . Set ( "Tus-Resumable" , "1.0.0" )
// Add nosniff to all responses https://golang.org/src/net/http/server.go#L1429
header . Set ( "X-Content-Type-Options" , "nosniff" )
// Set appropriated headers in case of OPTIONS method allowing protocol
// discovery and end with an 204 No Content
if r . Method == "OPTIONS" {
if handler . config . MaxSize > 0 {
header . Set ( "Tus-Max-Size" , strconv . FormatInt ( handler . config . MaxSize , 10 ) )
}
header . Set ( "Tus-Version" , "1.0.0" )
2015-12-26 23:44:02 +00:00
header . Set ( "Tus-Extension" , handler . extensions )
2015-11-29 01:33:55 +00:00
2016-05-10 09:58:43 +00:00
// Although the 204 No Content status code is a better fit in this case,
// since we do not have a response body included, we cannot use it here
// as some browsers only accept 200 OK as successful response to a
// preflight request. If we send them the 204 No Content the response
// will be ignored or interpreted as a rejection.
// For example, the Presto engine, which is used in older versions of
// Opera, Opera Mobile and Opera Mini, handles CORS this way.
2022-03-19 22:21:17 +00:00
c := newContext ( w , r )
handler . sendResp ( c , HTTPResponse {
2022-03-01 23:36:49 +00:00
StatusCode : http . StatusOK ,
} )
2015-11-29 01:33:55 +00:00
return
}
// Test if the version sent by the client is supported
2021-10-17 22:29:13 +00:00
// GET and HEAD methods are not checked since a browser may visit this URL and does
// not include this header. GET requests are not part of the specification.
if r . Method != "GET" && r . Method != "HEAD" && r . Header . Get ( "Tus-Resumable" ) != "1.0.0" {
2022-03-19 22:21:17 +00:00
c := newContext ( w , r )
handler . sendError ( c , ErrUnsupportedVersion )
2015-11-29 01:33:55 +00:00
return
}
// Proceed with routing the request
h . ServeHTTP ( w , r )
} )
}
// PostFile creates a new file upload using the datastore after validating the
// length and parsing the metadata.
func ( handler * UnroutedHandler ) PostFile ( w http . ResponseWriter , r * http . Request ) {
2022-03-19 22:21:17 +00:00
c := newContext ( w , r )
2019-09-15 11:43:59 +00:00
2016-09-29 19:20:51 +00:00
// Check for presence of application/offset+octet-stream. If another content
// type is defined, it will be ignored and treated as none was set because
// some HTTP clients may enforce a default value for this header.
containsChunk := r . Header . Get ( "Content-Type" ) == "application/offset+octet-stream"
2016-08-28 20:06:37 +00:00
2016-01-20 14:33:17 +00:00
// Only use the proper Upload-Concat header if the concatenation extension
// is even supported by the data store.
var concatHeader string
2016-02-21 22:25:35 +00:00
if handler . composer . UsesConcater {
2016-01-20 14:33:17 +00:00
concatHeader = r . Header . Get ( "Upload-Concat" )
}
2015-11-29 01:33:55 +00:00
// Parse Upload-Concat header
2019-09-19 10:14:25 +00:00
isPartial , isFinal , partialUploadIDs , err := parseConcat ( concatHeader )
2015-11-29 01:33:55 +00:00
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2015-11-29 01:33:55 +00:00
return
}
// If the upload is a final upload created by concatenation multiple partial
// uploads the size is sum of all sizes of these files (no need for
// Upload-Length header)
var size int64
2018-04-23 21:10:23 +00:00
var sizeIsDeferred bool
2019-09-19 10:14:25 +00:00
var partialUploads [ ] Upload
2015-11-29 01:33:55 +00:00
if isFinal {
2016-08-28 20:06:37 +00:00
// A final upload must not contain a chunk within the creation request
if containsChunk {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , ErrModifyFinal )
2016-08-28 20:06:37 +00:00
return
}
2022-03-19 22:21:17 +00:00
partialUploads , size , err = handler . sizeOfUploads ( c , partialUploadIDs )
2015-11-29 01:33:55 +00:00
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2015-11-29 01:33:55 +00:00
return
}
} else {
2018-04-23 21:10:23 +00:00
uploadLengthHeader := r . Header . Get ( "Upload-Length" )
uploadDeferLengthHeader := r . Header . Get ( "Upload-Defer-Length" )
2018-05-05 19:20:26 +00:00
size , sizeIsDeferred , err = handler . validateNewUploadLengthHeaders ( uploadLengthHeader , uploadDeferLengthHeader )
2018-04-23 21:10:23 +00:00
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2015-11-29 01:33:55 +00:00
return
}
}
// Test whether the size is still allowed
if handler . config . MaxSize > 0 && size > handler . config . MaxSize {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , ErrMaxSizeExceeded )
2015-11-29 01:33:55 +00:00
return
}
// Parse metadata
2018-05-22 16:46:18 +00:00
meta := ParseMetadataHeader ( r . Header . Get ( "Upload-Metadata" ) )
2015-11-29 01:33:55 +00:00
info := FileInfo {
Size : size ,
2018-04-23 21:10:23 +00:00
SizeIsDeferred : sizeIsDeferred ,
2015-11-29 01:33:55 +00:00
MetaData : meta ,
IsPartial : isPartial ,
IsFinal : isFinal ,
2019-09-19 10:14:25 +00:00
PartialUploads : partialUploadIDs ,
2015-11-29 01:33:55 +00:00
}
2022-03-01 23:36:49 +00:00
resp := HTTPResponse {
StatusCode : http . StatusCreated ,
Headers : HTTPHeaders { } ,
}
2019-09-19 09:15:48 +00:00
if handler . config . PreUploadCreateCallback != nil {
2022-03-01 23:36:49 +00:00
resp2 , err := handler . config . PreUploadCreateCallback ( newHookEvent ( info , r ) )
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2019-09-19 09:15:48 +00:00
return
}
2022-03-01 23:36:49 +00:00
resp = resp . MergeWith ( resp2 )
2019-09-19 09:15:48 +00:00
}
2022-03-19 22:21:17 +00:00
upload , err := handler . composer . Core . NewUpload ( c , info )
2015-11-29 01:33:55 +00:00
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2015-11-29 01:33:55 +00:00
return
}
2022-03-19 22:21:17 +00:00
info , err = upload . GetInfo ( c )
2019-08-24 13:14:51 +00:00
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2019-08-24 13:14:51 +00:00
return
}
id := info . ID
2018-03-29 12:40:43 +00:00
2016-08-28 20:06:37 +00:00
// Add the Location header directly after creating the new resource to even
// include it in cases of failure when an error is returned
url := handler . absFileURL ( r , id )
2022-03-01 23:36:49 +00:00
resp . Headers [ "Location" ] = url
2016-08-28 20:06:37 +00:00
2019-03-21 19:04:01 +00:00
handler . Metrics . incUploadsCreated ( )
2016-09-23 19:21:38 +00:00
handler . log ( "UploadCreated" , "id" , id , "size" , i64toa ( size ) , "url" , url )
2016-08-28 20:06:37 +00:00
2017-07-19 15:45:16 +00:00
if handler . config . NotifyCreatedUploads {
2019-09-19 09:15:48 +00:00
handler . CreatedUploads <- newHookEvent ( info , r )
2017-07-19 15:45:16 +00:00
}
2015-11-29 01:33:55 +00:00
if isFinal {
2019-09-19 10:14:25 +00:00
concatableUpload := handler . composer . Concater . AsConcatableUpload ( upload )
2022-03-19 22:21:17 +00:00
if err := concatableUpload . ConcatUploads ( c , partialUploads ) ; err != nil {
handler . sendError ( c , err )
2015-11-29 01:33:55 +00:00
return
}
2016-03-12 21:52:31 +00:00
info . Offset = size
2016-02-21 17:38:43 +00:00
if handler . config . NotifyCompleteUploads {
2019-09-19 09:15:48 +00:00
handler . CompleteUploads <- newHookEvent ( info , r )
2016-02-21 17:38:43 +00:00
}
2016-08-28 20:06:37 +00:00
}
2016-05-24 15:04:28 +00:00
2016-08-28 20:06:37 +00:00
if containsChunk {
if handler . composer . UsesLocker {
2022-03-19 22:21:17 +00:00
lock , err := handler . lockUpload ( c , id )
2019-09-12 10:37:43 +00:00
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2016-08-28 20:06:37 +00:00
return
}
2019-09-12 10:37:43 +00:00
defer lock . Unlock ( )
2016-08-28 20:06:37 +00:00
}
2022-03-19 22:21:17 +00:00
resp , err = handler . writeChunk ( c , resp , upload , info )
2022-03-01 23:36:49 +00:00
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2016-08-28 20:06:37 +00:00
return
}
2018-04-23 21:16:28 +00:00
} else if ! sizeIsDeferred && size == 0 {
2018-03-29 12:40:43 +00:00
// Directly finish the upload if the upload is empty (i.e. has a size of 0).
// This statement is in an else-if block to avoid causing duplicate calls
// to finishUploadIfComplete if an upload is empty and contains a chunk.
2022-03-19 22:21:17 +00:00
resp , err = handler . finishUploadIfComplete ( c , resp , upload , info )
2022-03-01 23:36:49 +00:00
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2020-02-23 19:26:00 +00:00
return
}
2015-11-29 01:33:55 +00:00
}
2022-03-19 22:21:17 +00:00
handler . sendResp ( c , resp )
2015-11-29 01:33:55 +00:00
}
// HeadFile returns the length and offset for the HEAD request
func ( handler * UnroutedHandler ) HeadFile ( w http . ResponseWriter , r * http . Request ) {
2022-03-19 22:21:17 +00:00
c := newContext ( w , r )
2015-11-29 01:33:55 +00:00
2015-12-07 20:37:34 +00:00
id , err := extractIDFromPath ( r . URL . Path )
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2015-12-07 20:37:34 +00:00
return
}
2015-12-26 20:23:09 +00:00
2016-02-21 22:25:35 +00:00
if handler . composer . UsesLocker {
2022-03-19 22:21:17 +00:00
lock , err := handler . lockUpload ( c , id )
2019-09-12 10:37:43 +00:00
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2015-12-26 20:23:09 +00:00
return
}
2019-09-12 10:37:43 +00:00
defer lock . Unlock ( )
2015-12-26 20:23:09 +00:00
}
2022-03-19 22:21:17 +00:00
upload , err := handler . composer . Core . GetUpload ( c , id )
2019-08-24 13:14:51 +00:00
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2019-08-24 13:14:51 +00:00
return
}
2022-03-19 22:21:17 +00:00
info , err := upload . GetInfo ( c )
2015-11-29 01:33:55 +00:00
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2015-11-29 01:33:55 +00:00
return
}
2022-03-01 23:36:49 +00:00
resp := HTTPResponse {
StatusCode : http . StatusOK ,
Headers : make ( HTTPHeaders ) ,
}
2015-11-29 01:33:55 +00:00
// Add Upload-Concat header if possible
if info . IsPartial {
2022-03-01 23:36:49 +00:00
resp . Headers [ "Upload-Concat" ] = "partial"
2015-11-29 01:33:55 +00:00
}
if info . IsFinal {
v := "final;"
for _ , uploadID := range info . PartialUploads {
2017-01-31 15:58:31 +00:00
v += handler . absFileURL ( r , uploadID ) + " "
2015-11-29 01:33:55 +00:00
}
2017-01-31 15:58:31 +00:00
// Remove trailing space
v = v [ : len ( v ) - 1 ]
2022-03-01 23:36:49 +00:00
resp . Headers [ "Upload-Concat" ] = v
2015-11-29 01:33:55 +00:00
}
if len ( info . MetaData ) != 0 {
2022-03-01 23:36:49 +00:00
resp . Headers [ "Upload-Metadata" ] = SerializeMetadataHeader ( info . MetaData )
2015-11-29 01:33:55 +00:00
}
2018-04-23 21:10:23 +00:00
if info . SizeIsDeferred {
2022-03-01 23:36:49 +00:00
resp . Headers [ "Upload-Defer-Length" ] = UploadLengthDeferred
2018-04-23 21:10:23 +00:00
} else {
2022-03-01 23:36:49 +00:00
resp . Headers [ "Upload-Length" ] = strconv . FormatInt ( info . Size , 10 )
resp . Headers [ "Content-Length" ] = strconv . FormatInt ( info . Size , 10 )
2018-04-23 21:10:23 +00:00
}
2022-03-01 23:36:49 +00:00
resp . Headers [ "Cache-Control" ] = "no-store"
resp . Headers [ "Upload-Offset" ] = strconv . FormatInt ( info . Offset , 10 )
2022-03-19 22:21:17 +00:00
handler . sendResp ( c , resp )
2015-11-29 01:33:55 +00:00
}
2018-03-29 12:40:43 +00:00
// PatchFile adds a chunk to an upload. This operation is only allowed
// if enough space in the upload is left.
2015-11-29 01:33:55 +00:00
func ( handler * UnroutedHandler ) PatchFile ( w http . ResponseWriter , r * http . Request ) {
2022-03-19 22:21:17 +00:00
c := newContext ( w , r )
2015-11-29 01:33:55 +00:00
2015-12-09 19:25:08 +00:00
// Check for presence of application/offset+octet-stream
2015-11-29 01:33:55 +00:00
if r . Header . Get ( "Content-Type" ) != "application/offset+octet-stream" {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , ErrInvalidContentType )
2015-11-29 01:33:55 +00:00
return
}
2015-12-09 19:25:08 +00:00
// Check for presence of a valid Upload-Offset Header
2015-11-29 01:33:55 +00:00
offset , err := strconv . ParseInt ( r . Header . Get ( "Upload-Offset" ) , 10 , 64 )
if err != nil || offset < 0 {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , ErrInvalidOffset )
2015-11-29 01:33:55 +00:00
return
}
2015-12-07 20:37:34 +00:00
id , err := extractIDFromPath ( r . URL . Path )
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2015-12-07 20:37:34 +00:00
return
}
2015-11-29 01:33:55 +00:00
2016-02-21 22:25:35 +00:00
if handler . composer . UsesLocker {
2022-03-19 22:21:17 +00:00
lock , err := handler . lockUpload ( c , id )
2019-09-12 10:37:43 +00:00
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2015-12-26 20:23:09 +00:00
return
}
2015-11-29 01:33:55 +00:00
2019-09-12 10:37:43 +00:00
defer lock . Unlock ( )
2015-12-26 20:23:09 +00:00
}
2015-11-29 01:33:55 +00:00
2022-03-19 22:21:17 +00:00
upload , err := handler . composer . Core . GetUpload ( c , id )
2019-08-24 13:14:51 +00:00
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2019-08-24 13:14:51 +00:00
return
}
2022-03-19 22:21:17 +00:00
info , err := upload . GetInfo ( c )
2015-11-29 01:33:55 +00:00
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2015-11-29 01:33:55 +00:00
return
}
// Modifying a final upload is not allowed
if info . IsFinal {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , ErrModifyFinal )
2015-11-29 01:33:55 +00:00
return
}
if offset != info . Offset {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , ErrMismatchOffset )
2015-11-29 01:33:55 +00:00
return
}
2022-03-01 23:36:49 +00:00
resp := HTTPResponse {
StatusCode : http . StatusNoContent ,
Headers : make ( HTTPHeaders , 1 ) , // Initialize map, so writeChunk can set the Upload-Offset header.
}
2016-04-09 20:09:22 +00:00
// Do not proxy the call to the data store if the upload is already completed
2018-04-23 21:16:28 +00:00
if ! info . SizeIsDeferred && info . Offset == info . Size {
2022-03-01 23:36:49 +00:00
resp . Headers [ "Upload-Offset" ] = strconv . FormatInt ( offset , 10 )
2022-03-19 22:21:17 +00:00
handler . sendResp ( c , resp )
2016-04-09 20:09:22 +00:00
return
}
2018-06-03 16:24:51 +00:00
if r . Header . Get ( "Upload-Length" ) != "" {
2018-06-11 21:59:54 +00:00
if ! handler . composer . UsesLengthDeferrer {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , ErrNotImplemented )
2018-06-11 21:59:54 +00:00
return
2018-05-13 13:52:27 +00:00
}
2018-06-11 21:59:54 +00:00
if ! info . SizeIsDeferred {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , ErrInvalidUploadLength )
2018-06-11 21:59:54 +00:00
return
}
uploadLength , err := strconv . ParseInt ( r . Header . Get ( "Upload-Length" ) , 10 , 64 )
2018-06-22 15:35:57 +00:00
if err != nil || uploadLength < 0 || uploadLength < info . Offset || ( handler . config . MaxSize > 0 && uploadLength > handler . config . MaxSize ) {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , ErrInvalidUploadLength )
2018-06-11 21:59:54 +00:00
return
}
2019-08-24 13:14:51 +00:00
lengthDeclarableUpload := handler . composer . LengthDeferrer . AsLengthDeclarableUpload ( upload )
2022-03-19 22:21:17 +00:00
if err := lengthDeclarableUpload . DeclareLength ( c , uploadLength ) ; err != nil {
handler . sendError ( c , err )
2018-06-11 21:59:54 +00:00
return
}
info . Size = uploadLength
info . SizeIsDeferred = false
2018-04-23 21:16:20 +00:00
}
2022-03-19 22:21:17 +00:00
resp , err = handler . writeChunk ( c , resp , upload , info )
2022-03-01 23:36:49 +00:00
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2016-08-28 20:06:37 +00:00
return
}
2022-03-19 22:21:17 +00:00
handler . sendResp ( c , resp )
2016-08-28 20:06:37 +00:00
}
2018-03-29 12:40:43 +00:00
// writeChunk reads the body from the requests r and appends it to the upload
// with the corresponding id. Afterwards, it will set the necessary response
// headers but will not send the response.
2022-03-19 22:21:17 +00:00
func ( handler * UnroutedHandler ) writeChunk ( c * httpContext , resp HTTPResponse , upload Upload , info FileInfo ) ( HTTPResponse , error ) {
2015-11-29 01:33:55 +00:00
// Get Content-Length if possible
2022-03-19 22:21:17 +00:00
r := c . req
2015-11-29 01:33:55 +00:00
length := r . ContentLength
2016-08-28 20:06:37 +00:00
offset := info . Offset
2019-08-24 13:14:51 +00:00
id := info . ID
2015-11-29 01:33:55 +00:00
// Test if this upload fits into the file's size
2018-04-23 21:16:28 +00:00
if ! info . SizeIsDeferred && offset + length > info . Size {
2022-03-01 23:36:49 +00:00
return resp , ErrSizeExceeded
2015-11-29 01:33:55 +00:00
}
maxSize := info . Size - offset
2018-06-03 16:37:45 +00:00
// If the upload's length is deferred and the PATCH request does not contain the Content-Length
// header (which is allowed if 'Transfer-Encoding: chunked' is used), we still need to set limits for
// the body size.
if info . SizeIsDeferred {
if handler . config . MaxSize > 0 {
// Ensure that the upload does not exceed the maximum upload size
maxSize = handler . config . MaxSize - offset
} else {
// If no upload limit is given, we allow arbitrary sizes
maxSize = math . MaxInt64
}
}
2015-11-29 01:33:55 +00:00
if length > 0 {
maxSize = length
}
2016-09-23 19:21:38 +00:00
handler . log ( "ChunkWriteStart" , "id" , id , "maxSize" , i64toa ( maxSize ) , "offset" , i64toa ( offset ) )
2016-08-28 20:06:37 +00:00
var bytesWritten int64
2021-04-26 08:08:37 +00:00
var err error
2017-01-19 20:02:48 +00:00
// Prevent a nil pointer dereference when accessing the body which may not be
2016-08-28 20:06:37 +00:00
// available in the case of a malicious request.
2022-09-09 16:21:09 +00:00
if r . Body != nil {
2017-01-19 20:02:48 +00:00
// Limit the data read from the request's body to the allowed maximum
2022-03-19 22:21:17 +00:00
c . body = newBodyReader ( r . Body , maxSize )
2015-11-29 01:33:55 +00:00
2019-05-26 19:56:51 +00:00
// We use a context object to allow the hook system to cancel an upload
uploadCtx , stopUpload := context . WithCancel ( context . Background ( ) )
info . stopUpload = stopUpload
2023-06-13 14:17:46 +00:00
2019-05-26 19:56:51 +00:00
// terminateUpload specifies whether the upload should be deleted after
// the write has finished
terminateUpload := false
2023-06-13 14:17:46 +00:00
serverShutDown := false
2019-05-26 19:56:51 +00:00
// Cancel the context when the function exits to ensure that the goroutine
// is properly cleaned up
defer stopUpload ( )
go func ( ) {
2023-06-13 14:17:46 +00:00
select {
case <- uploadCtx . Done ( ) :
// uploadCtx is done if the upload is stopped by a post-receive hook
terminateUpload = true
case <- handler . serverCtx :
// serverCtx is closed if the server is being shut down
serverShutDown = true
}
// interrupt the Read() calls from the request body
2019-05-26 19:56:51 +00:00
r . Body . Close ( )
} ( )
2017-01-19 20:02:48 +00:00
if handler . config . NotifyUploadProgress {
2022-03-19 22:21:17 +00:00
stopProgressEvents := handler . sendProgressMessages ( newHookEvent ( info , r ) , c . body )
2019-05-26 19:56:51 +00:00
defer close ( stopProgressEvents )
2017-01-19 20:02:48 +00:00
}
2022-03-19 22:21:17 +00:00
bytesWritten , err = upload . WriteChunk ( c , offset , c . body )
2019-05-26 19:56:51 +00:00
if terminateUpload && handler . composer . UsesTerminater {
2022-03-19 22:21:17 +00:00
if terminateErr := handler . terminateUpload ( c , upload , info ) ; terminateErr != nil {
2019-05-26 19:56:51 +00:00
// We only log this error and not show it to the user since this
// termination error is not relevant to the uploading client
handler . log ( "UploadStopTerminateError" , "id" , id , "error" , terminateErr . Error ( ) )
}
}
2021-04-26 08:08:37 +00:00
// If we encountered an error while reading the body from the HTTP request, log it, but only include
// it in the response, if the store did not also return an error.
2022-03-19 22:21:17 +00:00
if bodyErr := c . body . hasError ( ) ; bodyErr != nil {
2021-04-26 08:08:37 +00:00
handler . log ( "BodyReadError" , "id" , id , "error" , bodyErr . Error ( ) )
if err == nil {
err = bodyErr
}
2019-05-26 19:56:51 +00:00
}
2021-04-26 08:08:37 +00:00
// If the upload was stopped by the server, send an error response indicating this.
// TODO: Include a custom reason for the end user why the upload was stopped.
if terminateUpload {
err = ErrUploadStoppedByServer
2016-08-28 20:06:37 +00:00
}
2023-06-13 14:17:46 +00:00
// If the server is closing down, send an error response indicating this.
if serverShutDown {
err = ErrServerShutdown
}
2015-11-29 01:33:55 +00:00
}
2016-09-23 19:21:38 +00:00
handler . log ( "ChunkWriteComplete" , "id" , id , "bytesWritten" , i64toa ( bytesWritten ) )
2021-04-26 08:08:37 +00:00
if err != nil {
2022-03-01 23:36:49 +00:00
return resp , err
2021-04-26 08:08:37 +00:00
}
2015-11-29 01:33:55 +00:00
// Send new offset to client
newOffset := offset + bytesWritten
2022-03-01 23:36:49 +00:00
resp . Headers [ "Upload-Offset" ] = strconv . FormatInt ( newOffset , 10 )
2019-03-21 19:04:01 +00:00
handler . Metrics . incBytesReceived ( uint64 ( bytesWritten ) )
2018-03-29 12:40:43 +00:00
info . Offset = newOffset
2022-03-19 22:21:17 +00:00
return handler . finishUploadIfComplete ( c , resp , upload , info )
2018-03-29 12:40:43 +00:00
}
2015-11-29 01:33:55 +00:00
2018-03-29 12:40:43 +00:00
// finishUploadIfComplete checks whether an upload is completed (i.e. upload offset
// matches upload size) and if so, it will call the data store's FinishUpload
// function and send the necessary message on the CompleteUpload channel.
2022-03-19 22:21:17 +00:00
func ( handler * UnroutedHandler ) finishUploadIfComplete ( c * httpContext , resp HTTPResponse , upload Upload , info FileInfo ) ( HTTPResponse , error ) {
r := c . req
2015-12-08 21:08:54 +00:00
// If the upload is completed, ...
2018-04-23 21:16:28 +00:00
if ! info . SizeIsDeferred && info . Offset == info . Size {
2022-06-17 11:02:36 +00:00
// ... allow the data storage to finish and cleanup the upload
2022-03-19 22:21:17 +00:00
if err := upload . FinishUpload ( c ) ; err != nil {
2022-03-01 23:36:49 +00:00
return resp , err
2015-12-08 21:08:54 +00:00
}
2022-06-17 11:02:36 +00:00
// ... allow the hook callback to run before sending the response
2020-05-15 15:27:09 +00:00
if handler . config . PreFinishResponseCallback != nil {
2022-03-01 23:36:49 +00:00
resp2 , err := handler . config . PreFinishResponseCallback ( newHookEvent ( info , r ) )
if err != nil {
return resp , err
2020-05-15 15:27:09 +00:00
}
2022-03-01 23:36:49 +00:00
resp = resp . MergeWith ( resp2 )
2020-05-15 15:27:09 +00:00
}
2022-06-17 11:02:36 +00:00
2023-06-09 06:30:42 +00:00
handler . log ( "UploadFinished" , "id" , info . ID , "size" , strconv . FormatInt ( info . Size , 10 ) )
2022-06-17 11:02:36 +00:00
handler . Metrics . incUploadsFinished ( )
// ... send the info out to the channel
if handler . config . NotifyCompleteUploads {
handler . CompleteUploads <- newHookEvent ( info , r )
}
2015-11-29 01:33:55 +00:00
}
2022-03-01 23:36:49 +00:00
return resp , nil
2015-11-29 01:33:55 +00:00
}
// GetFile handles requests to download a file using a GET request. This is not
// part of the specification.
func ( handler * UnroutedHandler ) GetFile ( w http . ResponseWriter , r * http . Request ) {
2022-03-19 22:21:17 +00:00
c := newContext ( w , r )
2019-09-15 11:43:59 +00:00
2015-12-07 20:37:34 +00:00
id , err := extractIDFromPath ( r . URL . Path )
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2015-12-07 20:37:34 +00:00
return
}
2015-11-29 01:33:55 +00:00
2016-02-21 22:25:35 +00:00
if handler . composer . UsesLocker {
2022-03-19 22:21:17 +00:00
lock , err := handler . lockUpload ( c , id )
2019-09-12 10:37:43 +00:00
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2015-12-26 20:23:09 +00:00
return
}
2015-11-29 01:33:55 +00:00
2019-09-12 10:37:43 +00:00
defer lock . Unlock ( )
2015-12-26 20:23:09 +00:00
}
2015-11-29 01:33:55 +00:00
2022-03-19 22:21:17 +00:00
upload , err := handler . composer . Core . GetUpload ( c , id )
2019-08-24 13:14:51 +00:00
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2019-08-24 13:14:51 +00:00
return
}
2022-03-19 22:21:17 +00:00
info , err := upload . GetInfo ( c )
2015-11-29 01:33:55 +00:00
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2015-11-29 01:33:55 +00:00
return
}
2018-02-28 21:55:14 +00:00
contentType , contentDisposition := filterContentType ( info )
2022-03-01 23:36:49 +00:00
resp := HTTPResponse {
StatusCode : http . StatusOK ,
Headers : HTTPHeaders {
"Content-Length" : strconv . FormatInt ( info . Offset , 10 ) ,
"Content-Type" : contentType ,
"Content-Disposition" : contentDisposition ,
} ,
Body : "" , // Body is intentionally left empty, and we copy it manually in later.
}
2016-10-13 16:38:43 +00:00
2018-02-19 17:28:42 +00:00
// If no data has been uploaded yet, respond with an empty "204 No Content" status.
2015-11-29 01:33:55 +00:00
if info . Offset == 0 {
2022-03-01 23:36:49 +00:00
resp . StatusCode = http . StatusNoContent
2022-03-19 22:21:17 +00:00
handler . sendResp ( c , resp )
2015-11-29 01:33:55 +00:00
return
}
2022-03-19 22:21:17 +00:00
src , err := upload . GetReader ( c )
2015-11-29 01:33:55 +00:00
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2015-11-29 01:33:55 +00:00
return
}
2022-03-19 22:21:17 +00:00
handler . sendResp ( c , resp )
2015-11-29 01:33:55 +00:00
io . Copy ( w , src )
2022-06-19 10:18:02 +00:00
src . Close ( )
2015-11-29 01:33:55 +00:00
}
2018-02-28 21:55:14 +00:00
// mimeInlineBrowserWhitelist is a map containing MIME types which should be
// allowed to be rendered by browser inline, instead of being forced to be
2022-07-01 21:29:20 +00:00
// downloaded. For example, HTML or SVG files are not allowed, since they may
2018-02-28 21:55:14 +00:00
// contain malicious JavaScript. In a similiar fashion PDF is not on this list
// as their parsers commonly contain vulnerabilities which can be exploited.
2022-07-01 21:29:20 +00:00
// The values of this map does not convey any meaning and are therefore just
2018-02-28 21:55:14 +00:00
// empty structs.
var mimeInlineBrowserWhitelist = map [ string ] struct { } {
2023-06-09 09:07:05 +00:00
"text/plain" : { } ,
"image/png" : { } ,
"image/jpeg" : { } ,
"image/gif" : { } ,
"image/bmp" : { } ,
"image/webp" : { } ,
"audio/wave" : { } ,
"audio/wav" : { } ,
"audio/x-wav" : { } ,
"audio/x-pn-wav" : { } ,
"audio/webm" : { } ,
"video/webm" : { } ,
"audio/ogg" : { } ,
"video/ogg" : { } ,
"application/ogg" : { } ,
2018-02-28 21:55:14 +00:00
}
// filterContentType returns the values for the Content-Type and
// Content-Disposition headers for a given upload. These values should be used
// in responses for GET requests to ensure that only non-malicious file types
// are shown directly in the browser. It will extract the file name and type
// from the "fileame" and "filetype".
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
func filterContentType ( info FileInfo ) ( contentType string , contentDisposition string ) {
filetype := info . MetaData [ "filetype" ]
if reMimeType . MatchString ( filetype ) {
// If the filetype from metadata is well formed, we forward use this
// for the Content-Type header. However, only whitelisted mime types
// will be allowed to be shown inline in the browser
contentType = filetype
if _ , isWhitelisted := mimeInlineBrowserWhitelist [ filetype ] ; isWhitelisted {
contentDisposition = "inline"
} else {
contentDisposition = "attachment"
}
} else {
// If the filetype from the metadata is not well formed, we use a
// default type and force the browser to download the content.
contentType = "application/octet-stream"
contentDisposition = "attachment"
}
// Add a filename to Content-Disposition if one is available in the metadata
if filename , ok := info . MetaData [ "filename" ] ; ok {
contentDisposition += ";filename=" + strconv . Quote ( filename )
}
return contentType , contentDisposition
}
2015-11-29 01:33:55 +00:00
// DelFile terminates an upload permanently.
func ( handler * UnroutedHandler ) DelFile ( w http . ResponseWriter , r * http . Request ) {
2022-03-19 22:21:17 +00:00
c := newContext ( w , r )
2019-09-15 11:43:59 +00:00
2015-12-26 23:44:02 +00:00
// Abort the request handling if the required interface is not implemented
2016-02-21 22:25:35 +00:00
if ! handler . composer . UsesTerminater {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , ErrNotImplemented )
2015-12-07 20:37:34 +00:00
return
}
2015-11-29 01:33:55 +00:00
2015-12-07 20:37:34 +00:00
id , err := extractIDFromPath ( r . URL . Path )
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2015-11-29 01:33:55 +00:00
return
}
2016-02-21 22:25:35 +00:00
if handler . composer . UsesLocker {
2022-03-19 22:21:17 +00:00
lock , err := handler . lockUpload ( c , id )
2019-09-12 10:37:43 +00:00
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2015-12-26 20:23:09 +00:00
return
}
2015-11-29 01:33:55 +00:00
2019-09-12 10:37:43 +00:00
defer lock . Unlock ( )
2015-12-26 20:23:09 +00:00
}
2015-11-29 01:33:55 +00:00
2022-03-19 22:21:17 +00:00
upload , err := handler . composer . Core . GetUpload ( c , id )
2019-08-24 13:14:51 +00:00
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2019-08-24 13:14:51 +00:00
return
}
2016-03-12 21:24:57 +00:00
var info FileInfo
if handler . config . NotifyTerminatedUploads {
2022-03-19 22:21:17 +00:00
info , err = upload . GetInfo ( c )
2016-03-12 21:24:57 +00:00
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2016-03-12 21:24:57 +00:00
return
}
}
2022-03-19 22:21:17 +00:00
err = handler . terminateUpload ( c , upload , info )
2015-11-29 01:33:55 +00:00
if err != nil {
2022-03-19 22:21:17 +00:00
handler . sendError ( c , err )
2015-11-29 01:33:55 +00:00
return
}
2022-03-19 22:21:17 +00:00
handler . sendResp ( c , HTTPResponse {
2022-03-01 23:36:49 +00:00
StatusCode : http . StatusNoContent ,
} )
2019-05-26 19:56:51 +00:00
}
// terminateUpload passes a given upload to the DataStore's Terminater,
// send the corresponding upload info on the TerminatedUploads channnel
// and updates the statistics.
// Note the the info argument is only needed if the terminated uploads
// notifications are enabled.
2022-03-19 22:21:17 +00:00
func ( handler * UnroutedHandler ) terminateUpload ( c * httpContext , upload Upload , info FileInfo ) error {
2019-08-24 13:14:51 +00:00
terminatableUpload := handler . composer . Terminater . AsTerminatableUpload ( upload )
2022-03-19 22:21:17 +00:00
err := terminatableUpload . Terminate ( c )
2019-05-26 19:56:51 +00:00
if err != nil {
return err
}
2016-03-12 21:24:57 +00:00
if handler . config . NotifyTerminatedUploads {
2022-03-19 22:21:17 +00:00
handler . TerminatedUploads <- newHookEvent ( info , c . req )
2016-03-12 21:24:57 +00:00
}
2016-05-24 15:04:28 +00:00
2023-06-09 06:30:42 +00:00
handler . log ( "UploadTerminated" , "id" , info . ID )
2019-03-21 19:04:01 +00:00
handler . Metrics . incUploadsTerminated ( )
2019-05-26 19:56:51 +00:00
return nil
2015-11-29 01:33:55 +00:00
}
// Send the error in the response body. The status code will be looked up in
// ErrStatusCodes. If none is found 500 Internal Error will be used.
2022-03-19 22:21:17 +00:00
func ( handler * UnroutedHandler ) sendError ( c * httpContext , err error ) {
2017-03-01 18:43:37 +00:00
// Errors for read timeouts contain too much information which is not
// necessary for us and makes grouping for the metrics harder. The error
// message looks like: read tcp 127.0.0.1:1080->127.0.0.1:53673: i/o timeout
// Therefore, we use a common error message for all of them.
if netErr , ok := err . ( net . Error ) ; ok && netErr . Timeout ( ) {
2021-10-25 09:54:02 +00:00
err = ErrReadTimeout
2017-03-01 18:43:37 +00:00
}
2019-07-21 20:40:41 +00:00
// Errors for connnection resets also contain TCP details, we don't need, e.g:
// read tcp 127.0.0.1:1080->127.0.0.1:10023: read: connection reset by peer
// Therefore, we also trim those down.
if strings . HasSuffix ( err . Error ( ) , "read: connection reset by peer" ) {
2021-10-25 09:54:02 +00:00
err = ErrConnectionReset
2021-04-26 08:08:37 +00:00
}
// TODO: Decide if we should handle this in here, in body_reader or not at all.
// If the HTTP PATCH request gets interrupted in the middle (e.g. because
// the user wants to pause the upload), Go's net/http returns an io.ErrUnexpectedEOF.
// However, for the handler it's not important whether the stream has ended
// on purpose or accidentally.
//if err == io.ErrUnexpectedEOF {
// err = nil
//}
// TODO: Decide if we want to ignore connection reset errors all together.
// In some cases, the HTTP connection gets reset by the other peer. This is not
// necessarily the tus client but can also be a proxy in front of tusd, e.g. HAProxy 2
// is known to reset the connection to tusd, when the tus client closes the connection.
// To avoid erroring out in this case and loosing the uploaded data, we can ignore
// the error here without causing harm.
//if strings.Contains(err.Error(), "read: connection reset by peer") {
// err = nil
//}
2019-07-21 20:40:41 +00:00
2022-03-19 22:21:17 +00:00
r := c . req
2022-03-01 23:36:49 +00:00
detailedErr , ok := err . ( Error )
2017-02-28 19:39:25 +00:00
if ! ok {
2021-10-25 09:54:02 +00:00
handler . log ( "InternalServerError" , "message" , err . Error ( ) , "method" , r . Method , "path" , r . URL . Path , "requestId" , getRequestId ( r ) )
2022-03-01 23:36:49 +00:00
detailedErr = NewError ( "ERR_INTERNAL_SERVER_ERROR" , err . Error ( ) , http . StatusInternalServerError )
2015-11-29 01:33:55 +00:00
}
2022-03-01 23:36:49 +00:00
// If we are sending the response for a HEAD request, ensure that we are not including
// any response body.
2015-11-29 01:33:55 +00:00
if r . Method == "HEAD" {
2022-03-01 23:36:49 +00:00
detailedErr . HTTPResponse . Body = ""
2015-11-29 01:33:55 +00:00
}
2022-03-19 22:21:17 +00:00
handler . sendResp ( c , detailedErr . HTTPResponse )
2022-03-01 23:36:49 +00:00
handler . Metrics . incErrorsTotal ( detailedErr )
2015-11-29 01:33:55 +00:00
}
2016-09-23 19:21:38 +00:00
// sendResp writes the header to w with the specified status code.
2022-03-19 22:21:17 +00:00
func ( handler * UnroutedHandler ) sendResp ( c * httpContext , resp HTTPResponse ) {
resp . writeTo ( c . res )
2016-09-23 19:21:38 +00:00
2022-03-19 22:21:17 +00:00
handler . log ( "ResponseOutgoing" , "status" , strconv . Itoa ( resp . StatusCode ) , "method" , c . req . Method , "path" , c . req . URL . Path , "requestId" , getRequestId ( c . req ) , "body" , resp . Body )
2016-09-23 19:21:38 +00:00
}
2015-11-29 01:33:55 +00:00
// Make an absolute URLs to the given upload id. If the base path is absolute
// it will be prepended else the host and protocol from the request is used.
func ( handler * UnroutedHandler ) absFileURL ( r * http . Request , id string ) string {
if handler . isBasePathAbs {
return handler . basePath + id
}
// Read origin and protocol from request
2016-01-16 14:27:35 +00:00
host , proto := getHostAndProtocol ( r , handler . config . RespectForwardedHeaders )
url := proto + "://" + host + handler . basePath + id
return url
}
2017-02-21 22:17:07 +00:00
// sendProgressMessage will send a notification over the UploadProgress channel
// every second, indicating how much data has been transfered to the server.
// It will stop sending these instances once the returned channel has been
2021-04-26 08:08:37 +00:00
// closed.
func ( handler * UnroutedHandler ) sendProgressMessages ( hook HookEvent , reader * bodyReader ) chan <- struct { } {
2019-05-15 21:57:20 +00:00
previousOffset := int64 ( 0 )
2021-10-13 19:08:09 +00:00
originalOffset := hook . Upload . Offset
2017-01-19 20:02:48 +00:00
stop := make ( chan struct { } , 1 )
go func ( ) {
for {
select {
case <- stop :
2021-10-13 19:08:09 +00:00
hook . Upload . Offset = originalOffset + reader . bytesRead ( )
2019-09-19 09:15:48 +00:00
if hook . Upload . Offset != previousOffset {
handler . UploadProgress <- hook
previousOffset = hook . Upload . Offset
2019-05-15 21:57:20 +00:00
}
2017-01-19 20:02:48 +00:00
return
2022-08-15 21:26:58 +00:00
case <- time . After ( handler . config . UploadProgressInterval ) :
2021-10-13 19:08:09 +00:00
hook . Upload . Offset = originalOffset + reader . bytesRead ( )
2019-09-19 09:15:48 +00:00
if hook . Upload . Offset != previousOffset {
handler . UploadProgress <- hook
previousOffset = hook . Upload . Offset
2019-05-15 21:57:20 +00:00
}
2017-01-19 20:02:48 +00:00
}
}
} ( )
2021-04-26 08:08:37 +00:00
return stop
2017-01-19 20:02:48 +00:00
}
2016-01-16 14:27:35 +00:00
// getHostAndProtocol extracts the host and used protocol (either HTTP or HTTPS)
// from the given request. If `allowForwarded` is set, the X-Forwarded-Host,
// X-Forwarded-Proto and Forwarded headers will also be checked to
// support proxies.
func getHostAndProtocol ( r * http . Request , allowForwarded bool ) ( host , proto string ) {
2015-11-29 01:33:55 +00:00
if r . TLS != nil {
2016-01-16 14:27:35 +00:00
proto = "https"
} else {
proto = "http"
2015-11-29 01:33:55 +00:00
}
2016-01-16 14:27:35 +00:00
host = r . Host
2015-11-29 01:33:55 +00:00
2016-01-16 14:27:35 +00:00
if ! allowForwarded {
return
}
if h := r . Header . Get ( "X-Forwarded-Host" ) ; h != "" {
host = h
}
if h := r . Header . Get ( "X-Forwarded-Proto" ) ; h == "http" || h == "https" {
proto = h
}
if h := r . Header . Get ( "Forwarded" ) ; h != "" {
if r := reForwardedHost . FindStringSubmatch ( h ) ; len ( r ) == 2 {
host = r [ 1 ]
}
if r := reForwardedProto . FindStringSubmatch ( h ) ; len ( r ) == 2 {
proto = r [ 1 ]
}
}
return
2015-11-29 01:33:55 +00:00
}
// The get sum of all sizes for a list of upload ids while checking whether
// all of these uploads are finished yet. This is used to calculate the size
// of a final resource.
2019-09-19 10:14:25 +00:00
func ( handler * UnroutedHandler ) sizeOfUploads ( ctx context . Context , ids [ ] string ) ( partialUploads [ ] Upload , size int64 , err error ) {
partialUploads = make ( [ ] Upload , len ( ids ) )
for i , id := range ids {
2019-09-15 11:43:59 +00:00
upload , err := handler . composer . Core . GetUpload ( ctx , id )
2019-08-24 13:14:51 +00:00
if err != nil {
2019-09-19 10:14:25 +00:00
return nil , 0 , err
2019-08-24 13:14:51 +00:00
}
2019-09-15 11:43:59 +00:00
info , err := upload . GetInfo ( ctx )
2015-11-29 01:33:55 +00:00
if err != nil {
2019-09-19 10:14:25 +00:00
return nil , 0 , err
2015-11-29 01:33:55 +00:00
}
2018-06-03 17:07:07 +00:00
if info . SizeIsDeferred || info . Offset != info . Size {
2015-11-29 01:33:55 +00:00
err = ErrUploadNotFinished
2019-09-19 10:14:25 +00:00
return nil , 0 , err
2015-11-29 01:33:55 +00:00
}
size += info . Size
2019-09-19 10:14:25 +00:00
partialUploads [ i ] = upload
2015-11-29 01:33:55 +00:00
}
return
}
2018-04-23 21:10:23 +00:00
// Verify that the Upload-Length and Upload-Defer-Length headers are acceptable for creating a
// new upload
2018-05-05 19:20:26 +00:00
func ( handler * UnroutedHandler ) validateNewUploadLengthHeaders ( uploadLengthHeader string , uploadDeferLengthHeader string ) ( uploadLength int64 , uploadLengthDeferred bool , err error ) {
2018-04-23 21:10:23 +00:00
haveBothLengthHeaders := uploadLengthHeader != "" && uploadDeferLengthHeader != ""
haveInvalidDeferHeader := uploadDeferLengthHeader != "" && uploadDeferLengthHeader != UploadLengthDeferred
lengthIsDeferred := uploadDeferLengthHeader == UploadLengthDeferred
2018-05-05 19:20:26 +00:00
if lengthIsDeferred && ! handler . composer . UsesLengthDeferrer {
err = ErrNotImplemented
} else if haveBothLengthHeaders {
2018-04-23 21:10:23 +00:00
err = ErrUploadLengthAndUploadDeferLength
} else if haveInvalidDeferHeader {
err = ErrInvalidUploadDeferLength
} else if lengthIsDeferred {
uploadLengthDeferred = true
} else {
uploadLength , err = strconv . ParseInt ( uploadLengthHeader , 10 , 64 )
if err != nil || uploadLength < 0 {
err = ErrInvalidUploadLength
}
}
return
}
2019-09-12 10:37:43 +00:00
// lockUpload creates a new lock for the given upload ID and attempts to lock it.
// The created lock is returned if it was aquired successfully.
2022-03-19 22:21:17 +00:00
func ( handler * UnroutedHandler ) lockUpload ( c * httpContext , id string ) ( Lock , error ) {
2019-09-12 10:37:43 +00:00
lock , err := handler . composer . Locker . NewLock ( id )
if err != nil {
return nil , err
}
2022-03-19 22:21:17 +00:00
// TODO: Make lock timeout configurable
ctx , cancelContext := context . WithTimeout ( context . Background ( ) , 3 * time . Second )
defer cancelContext ( )
releaseLock := func ( ) {
if c . body != nil {
handler . log ( "UploadInterrupted" , "id" , id , "requestId" , getRequestId ( c . req ) )
2023-06-13 14:17:46 +00:00
// TODO: Consider replacing this with a channel or a context
2022-03-19 22:21:17 +00:00
c . body . closeWithError ( ErrUploadInterrupted )
}
}
if err := lock . Lock ( ctx , releaseLock ) ; err != nil {
2019-09-12 10:37:43 +00:00
return nil , err
}
return lock , nil
}
2018-05-22 16:46:18 +00:00
// ParseMetadataHeader parses the Upload-Metadata header as defined in the
// File Creation extension.
2015-11-29 01:33:55 +00:00
// e.g. Upload-Metadata: name bHVucmpzLnBuZw==,type aW1hZ2UvcG5n
2018-05-22 16:46:18 +00:00
func ParseMetadataHeader ( header string ) map [ string ] string {
2015-11-29 01:33:55 +00:00
meta := make ( map [ string ] string )
for _ , element := range strings . Split ( header , "," ) {
element := strings . TrimSpace ( element )
parts := strings . Split ( element , " " )
2020-06-20 08:32:06 +00:00
if len ( parts ) > 2 {
2015-11-29 01:33:55 +00:00
continue
}
key := parts [ 0 ]
2020-06-20 08:32:06 +00:00
if key == "" {
2015-11-29 01:33:55 +00:00
continue
}
2020-06-20 08:32:06 +00:00
value := ""
if len ( parts ) == 2 {
// Ignore current element if the value is no valid base64
dec , err := base64 . StdEncoding . DecodeString ( parts [ 1 ] )
if err != nil {
continue
}
value = string ( dec )
}
meta [ key ] = value
2015-11-29 01:33:55 +00:00
}
return meta
}
2018-05-22 16:46:18 +00:00
// SerializeMetadataHeader serializes a map of strings into the Upload-Metadata
// header format used in the response for HEAD requests.
2015-11-29 01:33:55 +00:00
// e.g. Upload-Metadata: name bHVucmpzLnBuZw==,type aW1hZ2UvcG5n
2018-05-22 16:46:18 +00:00
func SerializeMetadataHeader ( meta map [ string ] string ) string {
2015-11-29 01:33:55 +00:00
header := ""
for key , value := range meta {
valueBase64 := base64 . StdEncoding . EncodeToString ( [ ] byte ( value ) )
header += key + " " + valueBase64 + ","
}
// Remove trailing comma
if len ( header ) > 0 {
header = header [ : len ( header ) - 1 ]
}
return header
}
// Parse the Upload-Concat header, e.g.
// Upload-Concat: partial
2017-01-31 15:58:31 +00:00
// Upload-Concat: final;http://tus.io/files/a /files/b/
2015-11-29 01:33:55 +00:00
func parseConcat ( header string ) ( isPartial bool , isFinal bool , partialUploads [ ] string , err error ) {
if len ( header ) == 0 {
return
}
if header == "partial" {
isPartial = true
return
}
2017-01-31 15:58:31 +00:00
l := len ( "final;" )
if strings . HasPrefix ( header , "final;" ) && len ( header ) > l {
2015-11-29 01:33:55 +00:00
isFinal = true
list := strings . Split ( header [ l : ] , " " )
for _ , value := range list {
value := strings . TrimSpace ( value )
if value == "" {
continue
}
2015-12-07 20:37:34 +00:00
id , extractErr := extractIDFromPath ( value )
if extractErr != nil {
err = extractErr
2015-11-29 01:33:55 +00:00
return
}
partialUploads = append ( partialUploads , id )
}
}
// If no valid partial upload ids are extracted this is not a final upload.
if len ( partialUploads ) == 0 {
isFinal = false
err = ErrInvalidConcat
}
return
}
// extractIDFromPath pulls the last segment from the url provided
2015-12-07 20:37:34 +00:00
func extractIDFromPath ( url string ) ( string , error ) {
2015-11-29 01:33:55 +00:00
result := reExtractFileID . FindStringSubmatch ( url )
if len ( result ) != 2 {
2015-12-07 20:37:34 +00:00
return "" , ErrNotFound
2015-11-29 01:33:55 +00:00
}
2015-12-07 20:37:34 +00:00
return result [ 1 ] , nil
2015-11-29 01:33:55 +00:00
}
2016-09-23 19:21:38 +00:00
func i64toa ( num int64 ) string {
return strconv . FormatInt ( num , 10 )
}
2020-04-06 10:20:57 +00:00
// getRequestId returns the value of the X-Request-ID header, if available,
// and also takes care of truncating the input.
func getRequestId ( r * http . Request ) string {
reqId := r . Header . Get ( "X-Request-ID" )
if reqId == "" {
return ""
}
// Limit the length of the request ID to 36 characters, which is enough
// to fit a UUID.
if len ( reqId ) > 36 {
reqId = reqId [ : 36 ]
}
return reqId
}