{"record":{"id":"bbda76958c6ee1c6","repo":"dutchcoders/transfer.sh","slug":"maxdownloads-expired","errorCode":null,"errorMessage":"maxDownloads expired","messagePattern":"maxDownloads expired","errorType":"http","errorClass":null,"httpStatus":404,"severity":"warning","filePath":"server/handlers.go","lineNumber":886,"sourceCode":"}\n\nfunc (s *Server) checkMetadata(ctx context.Context, token, filename string, increaseDownload bool) (metadata, error) {\n\ts.lock(token, filename)\n\tdefer s.unlock(token, filename)\n\n\tvar metadata metadata\n\n\tr, _, err := s.storage.Get(ctx, token, fmt.Sprintf(\"%s.metadata\", filename), nil)\n\tdefer storage.CloseCheck(r)\n\n\tif err != nil {\n\t\treturn metadata, err\n\t}\n\n\tif err := json.NewDecoder(r).Decode(&metadata); err != nil {\n\t\treturn metadata, err\n\t} else if metadata.MaxDownloads != -1 && metadata.Downloads >= metadata.MaxDownloads {\n\t\treturn metadata, errors.New(\"maxDownloads expired\")\n\t} else if !metadata.MaxDate.IsZero() && time.Now().After(metadata.MaxDate) {\n\t\treturn metadata, errors.New(\"maxDate expired\")\n\t} else if metadata.MaxDownloads != -1 && increaseDownload {\n\t\t// todo(nl5887): mutex?\n\n\t\t// update number of downloads\n\t\tmetadata.Downloads++\n\n\t\tbuffer := &bytes.Buffer{}\n\t\tif err := json.NewEncoder(buffer).Encode(metadata); err != nil {\n\t\t\treturn metadata, errors.New(\"could not encode metadata\")\n\t\t} else if err := s.storage.Put(ctx, token, fmt.Sprintf(\"%s.metadata\", filename), buffer, \"text/json\", uint64(buffer.Len())); err != nil {\n\t\t\treturn metadata, errors.New(\"could not save metadata\")\n\t\t}\n\t}\n\n\treturn metadata, nil\n}","sourceCodeStart":868,"sourceCodeEnd":904,"githubUrl":"https://github.com/dutchcoders/transfer.sh/blob/c37bfd95797fd6da8a6da53fc13d191994b3f687/server/handlers.go#L868-L904","documentation":"checkMetadata validates a file's stored .metadata JSON before serving it. Each upload carries an optional MaxDownloads limit; when the recorded Downloads count has already reached MaxDownloads (MaxDownloads != -1), the server refuses to serve the file and returns this error, then typically purges the file. It is the per-file download quota being exhausted.","triggerScenarios":"GET/HEAD/preview/zip/tar/tar.gz download of a token whose metadata has MaxDownloads >= 1 and Downloads >= MaxDownloads, i.e. the file was configured with a limited number of downloads and all of them have been consumed.","commonSituations":"Sender shared a one-time (X-Url-Delete style, MaxDownloads=1) link and the recipient retries after the first fetch; a download manager or preview bot fetching the URL multiple times exhausts the quota before the human opens it; load balancer health checks hitting the URL.","solutions":["Ask the sender to re-upload and share a fresh link (the expired file is consumed by design).","Re-upload with a higher MaxDownloads value (or omit the limit) if repeated access is expected.","Disable prefetching/AV scanning on the download client so it does not burn the quota before the real download.","If this is your own deployment and behavior is undesired, adjust or remove the MaxDownloads enforcement in server/handlers.go checkMetadata."],"exampleFix":"// before (upload with 1 download allowed)\ncurl --upload-file ./f -H 'Max-Downloads: 1' https://host/f\n// after (unlimited downloads)\ncurl --upload-file ./f -H 'Max-Downloads: -1' https://host/f","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"resp, err := http.Get(url)\nif err == nil {\n\tbody, _ := io.ReadAll(resp.Body)\n\tif strings.Contains(string(body), \"maxDownloads expired\") {\n\t\t// request a fresh link from the sender\n\t}\n}","preventionTips":["Set Max-Downloads generously (or -1) when the link may be fetched by scanners or download managers.","Never preview/prefetch one-time links; one fetch consumes the quota.","Communicate the link's single-use nature to recipients up front."],"tags":["metadata","download-limit","expiration","file-transfer"],"backgroundTag":"download-limit-exceeded","analyzedSha":"c37bfd95797fd6da8a6da53fc13d191994b3f687","analyzedAt":"2026-09-05T10:21:07.548Z","contentChangedAt":"2026-09-05T10:21:07.548Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}