[server] Serve public resources

This commit is contained in:
Abhishek Shroff
2024-10-25 16:14:08 +05:30
parent 4670da07c1
commit dcbb581bc1
8 changed files with 113 additions and 72 deletions

View 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)
})
}

View File

@@ -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())

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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
}