dutchcoders/transfer.sh · warning

maxDownloads expired

Error message

maxDownloads expired

What it means

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.

Source

Thrown at server/handlers.go:886

}

func (s *Server) checkMetadata(ctx context.Context, token, filename string, increaseDownload bool) (metadata, error) {
	s.lock(token, filename)
	defer s.unlock(token, filename)

	var metadata metadata

	r, _, err := s.storage.Get(ctx, token, fmt.Sprintf("%s.metadata", filename), nil)
	defer storage.CloseCheck(r)

	if err != nil {
		return metadata, err
	}

	if err := json.NewDecoder(r).Decode(&metadata); err != nil {
		return metadata, err
	} else if metadata.MaxDownloads != -1 && metadata.Downloads >= metadata.MaxDownloads {
		return metadata, errors.New("maxDownloads expired")
	} else if !metadata.MaxDate.IsZero() && time.Now().After(metadata.MaxDate) {
		return metadata, errors.New("maxDate expired")
	} else if metadata.MaxDownloads != -1 && increaseDownload {
		// todo(nl5887): mutex?

		// update number of downloads
		metadata.Downloads++

		buffer := &bytes.Buffer{}
		if err := json.NewEncoder(buffer).Encode(metadata); err != nil {
			return metadata, errors.New("could not encode metadata")
		} else if err := s.storage.Put(ctx, token, fmt.Sprintf("%s.metadata", filename), buffer, "text/json", uint64(buffer.Len())); err != nil {
			return metadata, errors.New("could not save metadata")
		}
	}

	return metadata, nil
}

View on GitHub (pinned to c37bfd9579)

Solutions

  1. Ask the sender to re-upload and share a fresh link (the expired file is consumed by design).
  2. Re-upload with a higher MaxDownloads value (or omit the limit) if repeated access is expected.
  3. Disable prefetching/AV scanning on the download client so it does not burn the quota before the real download.
  4. If this is your own deployment and behavior is undesired, adjust or remove the MaxDownloads enforcement in server/handlers.go checkMetadata.

Example fix

// before (upload with 1 download allowed)
curl --upload-file ./f -H 'Max-Downloads: 1' https://host/f
// after (unlimited downloads)
curl --upload-file ./f -H 'Max-Downloads: -1' https://host/f
Defensive patterns

Strategy: try-catch

Try / catch

resp, err := http.Get(url)
if err == nil {
	body, _ := io.ReadAll(resp.Body)
	if strings.Contains(string(body), "maxDownloads expired") {
		// request a fresh link from the sender
	}
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of dutchcoders/transfer.sh@c37bfd9579 (2026-09-05). Data as JSON: /api/errors/bbda76958c6ee1c6. Report an issue: GitHub.