mirror of
https://codeberg.org/shroff/phylum.git
synced 2026-01-07 12:10:26 -06:00
[server] Serve public resources
This commit is contained in:
31
server/internal/api/public/public.go
Normal file
31
server/internal/api/public/public.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package public
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/shroff/phylum/server/internal/api/auth"
|
||||
"github.com/shroff/phylum/server/internal/api/serve"
|
||||
"github.com/shroff/phylum/server/internal/core/fs/public"
|
||||
)
|
||||
|
||||
func requirePassword(c *gin.Context, id string) string {
|
||||
if _, pass, ok := c.Request.BasicAuth(); ok {
|
||||
return pass
|
||||
} else {
|
||||
c.Header("WWW-Authenticate", "Basic realm=\""+id+"\"")
|
||||
panic(auth.ErrRequired)
|
||||
}
|
||||
}
|
||||
|
||||
func Setup(r *gin.RouterGroup) {
|
||||
// r.Handle("OPTIONS", "/:id/*path", handler.HandleRequest)
|
||||
r.Handle("GET", "/:id/*path", func(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
path := c.Param("path")
|
||||
|
||||
r, err := public.OpenResource(c.Request.Context(), id, requirePassword(c, id), path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
serve.Serve(c.Writer, c.Request, r)
|
||||
})
|
||||
}
|
||||
@@ -32,15 +32,19 @@ type Resource interface {
|
||||
FSContentSize() int64
|
||||
FSContentSHA256() string
|
||||
FSContentType() string
|
||||
|
||||
ReadDir(recursive bool) ([]Resource, error)
|
||||
OpenRead(start, length int64) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
type FileSystem interface {
|
||||
func Serve(w http.ResponseWriter, r *http.Request, res Resource) {
|
||||
if res.FSDir() {
|
||||
serveCollection(w, r, res)
|
||||
} else {
|
||||
serveResource(w, r, res)
|
||||
}
|
||||
}
|
||||
|
||||
func ServeCollection(w http.ResponseWriter, r *http.Request, file Resource) {
|
||||
func serveCollection(w http.ResponseWriter, r *http.Request, file Resource) {
|
||||
if !strings.HasSuffix(r.URL.Path, "/") {
|
||||
http.Redirect(w, r, r.URL.String()+"/", http.StatusMovedPermanently)
|
||||
return
|
||||
@@ -84,7 +88,7 @@ func ServeCollection(w http.ResponseWriter, r *http.Request, file Resource) {
|
||||
fmt.Fprintf(w, "</pre>\n")
|
||||
}
|
||||
|
||||
func ServeResource(w http.ResponseWriter, r *http.Request, file Resource) {
|
||||
func serveResource(w http.ResponseWriter, r *http.Request, file Resource) {
|
||||
w.Header().Set("Etag", file.FSContentSHA256())
|
||||
w.Header().Set("Last-Modified", file.FSModified().Format(http.TimeFormat))
|
||||
w.Header().Set("Content-Type", file.FSContentType())
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/shroff/phylum/server/internal/api/auth"
|
||||
"github.com/shroff/phylum/server/internal/api/serve"
|
||||
"github.com/shroff/phylum/server/internal/core/fs"
|
||||
)
|
||||
|
||||
func handleCatRequest(c *gin.Context) {
|
||||
@@ -13,10 +14,13 @@ func handleCatRequest(c *gin.Context) {
|
||||
panic(errResourceIDInvalid)
|
||||
}
|
||||
|
||||
fs := auth.GetFileSystem(c)
|
||||
r, err := fs.ResourceByID(resourceID)
|
||||
f := auth.GetFileSystem(c)
|
||||
r, err := f.ResourceByID(resourceID)
|
||||
if r.Dir {
|
||||
err = fs.ErrResourceCollection
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
serve.ServeResource(c.Writer, c.Request, r)
|
||||
serve.Serve(c.Writer, c.Request, r)
|
||||
}
|
||||
|
||||
@@ -212,15 +212,11 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (sta
|
||||
return status, err
|
||||
}
|
||||
// TODO: check locks for read-only access??
|
||||
file, err := h.FileSystem.ResourceByPath(reqPath)
|
||||
res, err := h.FileSystem.ResourceByPath(reqPath)
|
||||
if err != nil {
|
||||
return http.StatusNotFound, err
|
||||
}
|
||||
if file.Dir {
|
||||
serve.ServeCollection(w, r, file)
|
||||
} else {
|
||||
serve.ServeResource(w, r, file)
|
||||
}
|
||||
serve.Serve(w, r, res)
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package serve
|
||||
|
||||
import (
|
||||
"github.com/fvbock/endless"
|
||||
"github.com/shroff/phylum/server/internal/api/public"
|
||||
apiv1 "github.com/shroff/phylum/server/internal/api/v1"
|
||||
"github.com/shroff/phylum/server/internal/api/webdav"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -19,6 +20,7 @@ func SetupCommand() *cobra.Command {
|
||||
|
||||
webdav.SetupHandler(engine.Group(config.GetString("webdav_prefix")))
|
||||
apiv1.Setup(engine.Group("/api/v1"))
|
||||
public.Setup(engine.Group("/public"))
|
||||
|
||||
server := endless.NewServer(config.GetString("listen"), engine)
|
||||
server.BeforeBegin = func(addr string) {
|
||||
|
||||
@@ -2,7 +2,6 @@ package public
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
@@ -11,12 +10,6 @@ import (
|
||||
"github.com/shroff/phylum/server/internal/core/storage"
|
||||
)
|
||||
|
||||
type FileSystem interface {
|
||||
ResourceByPath(path string) (Resource, error)
|
||||
OpenRead(r Resource, start, length int64) (io.ReadCloser, error)
|
||||
ReadDir(r Resource, recursive bool) ([]Resource, error)
|
||||
}
|
||||
|
||||
type filesystem struct {
|
||||
ctx context.Context
|
||||
db *db.DbHandler
|
||||
@@ -45,38 +38,3 @@ func (f filesystem) ResourceByPath(path string) (Resource, error) {
|
||||
contentSHA256: r.ContentSha256,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f filesystem) OpenRead(r Resource, start, length int64) (io.ReadCloser, error) {
|
||||
return f.cs.OpenRead(r.id, start, length)
|
||||
}
|
||||
|
||||
func (f filesystem) ReadDir(r Resource, recursive bool) ([]Resource, error) {
|
||||
if !r.dir {
|
||||
return nil, fs.ErrResourceNotCollection
|
||||
}
|
||||
children, err := f.db.ReadDir(f.ctx, db.ReadDirParams{
|
||||
ID: r.id,
|
||||
IncludeRoot: false,
|
||||
Recursive: recursive,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]Resource, len(children))
|
||||
|
||||
for i, c := range children {
|
||||
result[i] = Resource{
|
||||
f: f,
|
||||
id: c.ID,
|
||||
name: c.Name,
|
||||
dir: c.Dir,
|
||||
created: c.Created.Time,
|
||||
modified: c.Modified.Time,
|
||||
contentSize: c.ContentSize,
|
||||
contentType: c.ContentType,
|
||||
contentSHA256: c.ContentSha256,
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -14,45 +14,45 @@ import (
|
||||
|
||||
var ErrPublicShareNotFound = errors.NewError(http.StatusUnauthorized, "public_share_not_found", "public share not found")
|
||||
|
||||
|
||||
func OpenPublic(ctx context.Context, id string, password string) (FileSystem, error) {
|
||||
func OpenResource(ctx context.Context, id string, password, path string) (Resource, error) {
|
||||
// Check exists
|
||||
s, err := db.Get().PublicShare(ctx, id)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
err = ErrPublicShareNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return Resource{}, err
|
||||
}
|
||||
|
||||
// check password
|
||||
if s.PasswordHash.Valid {
|
||||
if ok, err := crypt.VerifyPassword(password, s.PasswordHash.String); err != nil {
|
||||
return nil, err
|
||||
return Resource{}, err
|
||||
} else if !ok {
|
||||
return nil, ErrPublicShareNotFound
|
||||
return Resource{}, ErrPublicShareNotFound
|
||||
}
|
||||
}
|
||||
|
||||
// check expiration
|
||||
if s.Expires.Valid {
|
||||
if s.Expires.Time.Before(time.Now()) {
|
||||
return nil, ErrPublicShareNotFound
|
||||
return Resource{}, ErrPublicShareNotFound
|
||||
}
|
||||
}
|
||||
|
||||
// check max accesses
|
||||
if s.AccessesLeft.Valid {
|
||||
if s.AccessesLeft.Int32 <= 0 {
|
||||
return nil, ErrPublicShareNotFound
|
||||
return Resource{}, ErrPublicShareNotFound
|
||||
}
|
||||
db.Get().AccessPublicShare(ctx, id)
|
||||
}
|
||||
|
||||
return filesystem{
|
||||
f := filesystem{
|
||||
ctx: ctx,
|
||||
db: db.Get(),
|
||||
cs: storage.Get(),
|
||||
rootID: s.Root,
|
||||
}, nil
|
||||
}
|
||||
return f.ResourceByPath(path)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package public
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/shroff/phylum/server/internal/api/serve"
|
||||
"github.com/shroff/phylum/server/internal/core/db"
|
||||
"github.com/shroff/phylum/server/internal/core/fs"
|
||||
)
|
||||
|
||||
type Resource struct {
|
||||
@@ -11,6 +15,7 @@ type Resource struct {
|
||||
id uuid.UUID
|
||||
name string
|
||||
dir bool
|
||||
path string
|
||||
created time.Time
|
||||
modified time.Time
|
||||
contentSize int64
|
||||
@@ -18,11 +23,52 @@ type Resource struct {
|
||||
contentSHA256 string
|
||||
}
|
||||
|
||||
func (r Resource) MID() uuid.UUID { return r.id }
|
||||
func (r Resource) MName() string { return r.name }
|
||||
func (r Resource) MDir() bool { return r.dir }
|
||||
func (r Resource) MCreated() time.Time { return r.created }
|
||||
func (r Resource) MModified() time.Time { return r.modified }
|
||||
func (r Resource) MContentSize() int64 { return r.contentSize }
|
||||
func (r Resource) MContentSHA256() string { return r.contentSHA256 }
|
||||
func (r Resource) MContentType() string { return r.contentType }
|
||||
func (r Resource) FSID() uuid.UUID { return r.id }
|
||||
func (r Resource) FSName() string { return r.name }
|
||||
func (r Resource) FSPath() string { return r.name }
|
||||
func (r Resource) FSDir() bool { return r.dir }
|
||||
func (r Resource) FSCreated() time.Time { return r.created }
|
||||
func (r Resource) FSModified() time.Time { return r.modified }
|
||||
func (r Resource) FSContentSize() int64 { return r.contentSize }
|
||||
func (r Resource) FSContentSHA256() string { return r.contentSHA256 }
|
||||
func (r Resource) FSContentType() string { return r.contentType }
|
||||
|
||||
func (r Resource) OpenRead(start, length int64) (io.ReadCloser, error) {
|
||||
return r.f.cs.OpenRead(r.id, start, length)
|
||||
}
|
||||
|
||||
func (r Resource) ReadDir(recursive bool) ([]serve.Resource, error) {
|
||||
if !r.dir {
|
||||
return nil, fs.ErrResourceNotCollection
|
||||
}
|
||||
children, err := r.f.db.ReadDir(r.f.ctx, db.ReadDirParams{
|
||||
ID: r.id,
|
||||
IncludeRoot: false,
|
||||
Recursive: recursive,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]serve.Resource, len(children))
|
||||
|
||||
for i, c := range children {
|
||||
path := r.path + c.Path
|
||||
if r.path == "/" {
|
||||
path = c.Path
|
||||
}
|
||||
result[i] = Resource{
|
||||
f: r.f,
|
||||
id: c.ID,
|
||||
name: c.Name,
|
||||
dir: c.Dir,
|
||||
path: path,
|
||||
created: c.Created.Time,
|
||||
modified: c.Modified.Time,
|
||||
contentSize: c.ContentSize,
|
||||
contentType: c.ContentType,
|
||||
contentSHA256: c.ContentSha256,
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user