Repository: go-gitea/gitea
File: modules/storage/minio.go (Go)

Code before:
	}
	return &minioFileInfo{info}, nil
}

// Delete delete a file
func (m *MinioStorage) Delete(path string) error {
	err := m.client.RemoveObject(m.ctx, m.bucket, m.buildMinioPath(path), minio.RemoveObjectOptions{})

	return convertMinioErr(err)
}

// URL gets the redirect URL to a file. The presigned link is valid for 5 minutes.
func (m *MinioStorage) URL(path, name, method string, serveDirectReqParams url.Values) (*url.URL, error) {
	// copy serveDirectReqParams
	reqParams, err := url.ParseQuery(serveDirectReqParams.Encode())
	if err != nil {
		return nil, err
	}
	// Detect content type by extension name
	contentDisposition := "attachment; filename=\"" + quoteEscaper.Replace(name) + "\""
	ext := filepath.Ext(name)
	mimetype := mime.TypeByExtension(ext)
	reqParams.Set("response-content-type", mimetype)
	if mimetype == "application/pdf" || strings.HasPrefix(mimetype, "image/") {
		contentDisposition = "inline"
	}

	reqParams.Set("response-content-disposition", contentDisposition)
	expires := 5 * time.Minute
	if method == http.MethodHead {
		u, err := m.client.PresignedHeadObject(m.ctx, m.bucket, m.buildMinioPath(path), expires, reqParams)
		return u, convertMinioErr(err)
	}
	u, err := m.client.PresignedGetObject(m.ctx, m.bucket, m.buildMinioPath(path), expires, reqParams)
	return u, convertMinioErr(err)
}

// IterateObjects iterates across the objects in the miniostorage
func (m *MinioStorage) IterateObjects(dirName string, fn func(path string, obj Object) error) error {
	opts := minio.GetObjectOptions{}
	for mObjInfo := range m.client.ListObjects(m.ctx, m.bucket, minio.ListObjectsOptions{
		Prefix:    m.buildMinioDirPrefix(dirName),
		Recursive: true,
	}) {
		object, err := m.client.GetObject(m.ctx, m.bucket, mObjInfo.Key, opts)
		if err != nil {
			return convertMinioErr(err)
		}
		if err := func(object *minio.Object, fn func(path string, obj Object) error) error {
			defer object.Close()
			return fn(strings.TrimPrefix(mObjInfo.Key, m.basePath), &minioObject{object})

Review comment (security):
For `image/svg`, we can't render it directly in browser, otherwise there will be XSS without proper CSP

Code after:
}

// Delete delete a file
func (m *MinioStorage) Delete(path string) error {
	err := m.client.RemoveObject(m.ctx, m.bucket, m.buildMinioPath(path), minio.RemoveObjectOptions{})

	return convertMinioErr(err)
}

// URL gets the redirect URL to a file. The presigned link is valid for 5 minutes.
func (m *MinioStorage) URL(storePath, name, method string, serveDirectReqParams url.Values) (*url.URL, error) {
	// copy serveDirectReqParams
	reqParams, err := url.ParseQuery(serveDirectReqParams.Encode())
	if err != nil {
		return nil, err
	}

	// Here we might not know the real filename, and it's quite inefficient to detect the mine type by pre-fetching the object head.
	// So we just do a quick detection by extension name, at least if works for the "View Raw File" for an LFS file on the Web UI.
	// Detect content type by extension name, only support the well-known safe types for inline rendering.
	// TODO: OBJECT-STORAGE-CONTENT-TYPE: need a complete solution and refactor for Azure in the future
	ext := path.Ext(name)
	inlineExtMimeTypes := map[string]string{
		".png":  "image/png",
		".jpg":  "image/jpeg",
		".jpeg": "image/jpeg",
		".gif":  "image/gif",
		".webp": "image/webp",
		".avif": "image/avif",
		// ATTENTION! Don't support unsafe types like HTML/SVG due to security concerns: they can contain JS code, and maybe they need proper Content-Security-Policy
		// HINT: PDF-RENDER-SANDBOX: PDF won't render in sandboxed context, it seems fine to render it inline
		".pdf": "application/pdf",

		// TODO: refactor with "modules/public/mime_types.go", for example: "DetectWellKnownSafeInlineMimeType"
	}
	if mimeType, ok := inlineExtMimeTypes[ext]; ok {
		reqParams.Set("response-content-type", mimeType)
		reqParams.Set("response-content-disposition", "inline")
	} else {
		reqParams.Set("response-content-disposition", fmt.Sprintf(`attachment; filename="%s"`, quoteEscaper.Replace(name)))
	}

	expires := 5 * time.Minute
	if method == http.MethodHead {
		u, err := m.client.PresignedHeadObject(m.ctx, m.bucket, m.buildMinioPath(storePath), expires, reqParams)
		return u, convertMinioErr(err)
	}
	u, err := m.client.PresignedGetObject(m.ctx, m.bucket, m.buildMinioPath(storePath), expires, reqParams)
	return u, convertMinioErr(err)
}