2020-08-17 23:23:45 -05:00
// Copyright 2020 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2020-08-17 23:23:45 -05:00
package storage
import (
2020-10-12 22:58:34 -05:00
"context"
2023-03-21 15:02:49 -05:00
"fmt"
2020-08-17 23:23:45 -05:00
"io"
"net/url"
"os"
"path/filepath"
2020-10-15 22:51:06 -05:00
"code.gitea.io/gitea/modules/log"
2023-06-13 22:42:38 -05:00
"code.gitea.io/gitea/modules/setting"
2020-08-17 23:23:45 -05:00
"code.gitea.io/gitea/modules/util"
)
2022-01-20 12:46:10 -05:00
var _ ObjectStorage = & LocalStorage { }
2020-08-17 23:23:45 -05:00
// LocalStorage represents a local files storage
type LocalStorage struct {
2021-03-05 08:19:17 -05:00
ctx context . Context
dir string
tmpdir string
2020-08-17 23:23:45 -05:00
}
// NewLocalStorage returns a local files
2023-06-13 22:42:38 -05:00
func NewLocalStorage ( ctx context . Context , config * setting . Storage ) ( ObjectStorage , error ) {
2023-03-21 15:02:49 -05:00
if ! filepath . IsAbs ( config . Path ) {
return nil , fmt . Errorf ( "LocalStorageConfig.Path should have been prepared by setting/storage.go and should be an absolute path, but not: %q" , config . Path )
}
2020-10-15 22:51:06 -05:00
log . Info ( "Creating new Local Storage at %s" , config . Path )
2020-10-12 22:58:34 -05:00
if err := os . MkdirAll ( config . Path , os . ModePerm ) ; err != nil {
2020-08-17 23:23:45 -05:00
return nil , err
}
2021-03-05 08:19:17 -05:00
if config . TemporaryPath == "" {
2023-03-21 15:02:49 -05:00
config . TemporaryPath = filepath . Join ( config . Path , "tmp" )
}
if ! filepath . IsAbs ( config . TemporaryPath ) {
return nil , fmt . Errorf ( "LocalStorageConfig.TemporaryPath should be an absolute path, but not: %q" , config . TemporaryPath )
2021-03-05 08:19:17 -05:00
}
2020-08-17 23:23:45 -05:00
return & LocalStorage {
2021-03-05 08:19:17 -05:00
ctx : ctx ,
dir : config . Path ,
tmpdir : config . TemporaryPath ,
2020-08-17 23:23:45 -05:00
} , nil
}
2022-03-22 16:02:26 -05:00
func ( l * LocalStorage ) buildLocalPath ( p string ) string {
2023-03-21 15:02:49 -05:00
return util . FilePathJoinAbs ( l . dir , p )
2022-03-22 16:02:26 -05:00
}
2020-08-17 23:23:45 -05:00
// Open a file
2020-09-08 10:45:10 -05:00
func ( l * LocalStorage ) Open ( path string ) ( Object , error ) {
2022-03-22 16:02:26 -05:00
return os . Open ( l . buildLocalPath ( path ) )
2020-08-17 23:23:45 -05:00
}
// Save a file
2021-04-03 11:19:59 -05:00
func ( l * LocalStorage ) Save ( path string , r io . Reader , size int64 ) ( int64 , error ) {
2022-03-22 16:02:26 -05:00
p := l . buildLocalPath ( path )
2020-08-17 23:23:45 -05:00
if err := os . MkdirAll ( filepath . Dir ( p ) , os . ModePerm ) ; err != nil {
return 0 , err
}
2021-03-05 08:19:17 -05:00
// Create a temporary file to save to
if err := os . MkdirAll ( l . tmpdir , os . ModePerm ) ; err != nil {
2020-08-17 23:23:45 -05:00
return 0 , err
}
2021-09-22 00:38:34 -05:00
tmp , err := os . CreateTemp ( l . tmpdir , "upload-*" )
2021-03-05 08:19:17 -05:00
if err != nil {
return 0 , err
}
tmpRemoved := false
defer func ( ) {
if ! tmpRemoved {
_ = util . Remove ( tmp . Name ( ) )
}
} ( )
2020-08-17 23:23:45 -05:00
2021-03-05 08:19:17 -05:00
n , err := io . Copy ( tmp , r )
2020-08-17 23:23:45 -05:00
if err != nil {
return 0 , err
}
2021-03-05 08:19:17 -05:00
if err := tmp . Close ( ) ; err != nil {
return 0 , err
}
2021-07-15 10:46:07 -05:00
if err := util . Rename ( tmp . Name ( ) , p ) ; err != nil {
2021-03-05 08:19:17 -05:00
return 0 , err
}
2022-09-24 08:04:14 -05:00
// Golang's tmp file (os.CreateTemp) always have 0o600 mode, so we need to change the file to follow the umask (as what Create/MkDir does)
2022-12-18 19:50:36 -05:00
// but we don't want to make these files executable - so ensure that we mask out the executable bits
if err := util . ApplyUmask ( p , os . ModePerm & 0 o666 ) ; err != nil {
2022-09-24 08:04:14 -05:00
return 0 , err
}
2021-03-05 08:19:17 -05:00
tmpRemoved = true
return n , nil
2020-08-17 23:23:45 -05:00
}
2020-09-08 10:45:10 -05:00
// Stat returns the info of the file
2020-09-29 04:05:13 -05:00
func ( l * LocalStorage ) Stat ( path string ) ( os . FileInfo , error ) {
2022-03-22 16:02:26 -05:00
return os . Stat ( l . buildLocalPath ( path ) )
2022-03-14 10:18:27 -05:00
}
2020-08-17 23:23:45 -05:00
// Delete delete a file
func ( l * LocalStorage ) Delete ( path string ) error {
2022-03-22 16:02:26 -05:00
return util . Remove ( l . buildLocalPath ( path ) )
2020-08-17 23:23:45 -05:00
}
// URL gets the redirect URL to a file
Fix `missing signature key` error when pulling Docker images with `SERVE_DIRECT` enabled (#32365)
Fix #28121
I did some tests and found that the `missing signature key` error is
caused by an incorrect `Content-Type` header. Gitea correctly sets the
`Content-Type` header when serving files.
https://github.com/go-gitea/gitea/blob/348d1d0f322ca57c459acd902f54821d687ca804/routers/api/packages/container/container.go#L712-L717
However, when `SERVE_DIRECT` is enabled, the `Content-Type` header may
be set to an incorrect value by the storage service. To fix this issue,
we can use query parameters to override response header values.
https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html
<img width="600px"
src="https://github.com/user-attachments/assets/f2ff90f0-f1df-46f9-9680-b8120222c555"
/>
In this PR, I introduced a new parameter to the `URL` method to support
additional parameters.
```
URL(path, name string, reqParams url.Values) (*url.URL, error)
```
---
Most S3-like services support specifying the content type when storing
objects. However, Gitea always use `application/octet-stream`.
Therefore, I believe we also need to improve the `Save` method to
support storing objects with the correct content type.
https://github.com/go-gitea/gitea/blob/b7fb20e73e63b8edc9b90c52073e248bef428fcc/modules/storage/minio.go#L214-L221
(cherry picked from commit 0690cb076bf63f71988a709f62a9c04660b51a4f)
Conflicts:
- modules/storage/azureblob.go
Dropped the change, as we do not support Azure blob storage.
- modules/storage/helper.go
Resolved by adjusting their `discardStorage` to our
`DiscardStorage`
- routers/api/actions/artifacts.go
routers/api/actions/artifactsv4.go
routers/web/repo/actions/view.go
routers/web/repo/download.go
Resolved the conflicts by manually adding the new `nil`
parameter to the `storage.Attachments.URL()` calls.
Originally conflicted due to differences in the if expression
above these calls.
2024-10-31 10:28:25 -05:00
func ( l * LocalStorage ) URL ( path , name string , reqParams url . Values ) ( * url . URL , error ) {
2020-08-17 23:23:45 -05:00
return nil , ErrURLNotSupported
}
2020-09-29 04:05:13 -05:00
// IterateObjects iterates across the objects in the local storage
2023-05-13 17:33:25 -05:00
func ( l * LocalStorage ) IterateObjects ( dirName string , fn func ( path string , obj Object ) error ) error {
dir := l . buildLocalPath ( dirName )
2023-03-13 05:23:51 -05:00
return filepath . WalkDir ( dir , func ( path string , d os . DirEntry , err error ) error {
2020-09-29 04:05:13 -05:00
if err != nil {
return err
}
2020-10-12 22:58:34 -05:00
select {
case <- l . ctx . Done ( ) :
return l . ctx . Err ( )
default :
}
2020-09-29 04:05:13 -05:00
if path == l . dir {
return nil
}
2023-01-16 11:21:44 -05:00
if d . IsDir ( ) {
2020-09-29 04:05:13 -05:00
return nil
}
relPath , err := filepath . Rel ( l . dir , path )
if err != nil {
return err
}
obj , err := os . Open ( path )
if err != nil {
return err
}
defer obj . Close ( )
return fn ( relPath , obj )
} )
}
2020-10-12 22:58:34 -05:00
func init ( ) {
2023-06-13 22:42:38 -05:00
RegisterStorageType ( setting . LocalStorageType , NewLocalStorage )
2020-10-12 22:58:34 -05:00
}