code refactoring, added file download (#118)
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
|||||||
DialogContent,
|
DialogContent,
|
||||||
DialogContentText,
|
DialogContentText,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
|
IconButton,
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
ListItemButton,
|
ListItemButton,
|
||||||
@@ -39,6 +40,8 @@ import { useI18n } from '../hooks/useI18n'
|
|||||||
import { ffetch } from '../lib/httpClient'
|
import { ffetch } from '../lib/httpClient'
|
||||||
import { DeleteRequest, DirectoryEntry } from '../types'
|
import { DeleteRequest, DirectoryEntry } from '../types'
|
||||||
import { base64URLEncode, roundMiB } from '../utils'
|
import { base64URLEncode, roundMiB } from '../utils'
|
||||||
|
import DownloadIcon from '@mui/icons-material/Download'
|
||||||
|
|
||||||
|
|
||||||
export default function Downloaded() {
|
export default function Downloaded() {
|
||||||
const serverAddr = useRecoilValue(serverURL)
|
const serverAddr = useRecoilValue(serverURL)
|
||||||
@@ -119,7 +122,7 @@ export default function Downloaded() {
|
|||||||
combineLatestWith(selected$),
|
combineLatestWith(selected$),
|
||||||
map(([data, selected]) => data.map(x => ({
|
map(([data, selected]) => data.map(x => ({
|
||||||
...x,
|
...x,
|
||||||
selected: selected.includes(x.name)
|
selected: selected.includes(x.path)
|
||||||
}))),
|
}))),
|
||||||
share()
|
share()
|
||||||
), [])
|
), [])
|
||||||
@@ -155,7 +158,13 @@ export default function Downloaded() {
|
|||||||
const onFileClick = (path: string) => startTransition(() => {
|
const onFileClick = (path: string) => startTransition(() => {
|
||||||
const encoded = base64URLEncode(path)
|
const encoded = base64URLEncode(path)
|
||||||
|
|
||||||
window.open(`${serverAddr}/archive/d/${encoded}`)
|
window.open(`${serverAddr}/archive/v/${encoded}?token=${localStorage.getItem('token')}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
const downloadFile = (path: string) => startTransition(() => {
|
||||||
|
const encoded = base64URLEncode(path)
|
||||||
|
|
||||||
|
window.open(`${serverAddr}/archive/d/${encoded}?token=${localStorage.getItem('token')}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
const onFolderClick = (path: string) => startTransition(() => {
|
const onFolderClick = (path: string) => startTransition(() => {
|
||||||
@@ -192,11 +201,20 @@ export default function Downloaded() {
|
|||||||
{roundMiB(file.size)}
|
{roundMiB(file.size)}
|
||||||
</Typography>
|
</Typography>
|
||||||
}
|
}
|
||||||
{!file.isDirectory && <Checkbox
|
{!file.isDirectory && <>
|
||||||
|
<IconButton
|
||||||
|
size='small'
|
||||||
|
onClick={() => downloadFile(file.path)}
|
||||||
|
sx={{ marginLeft: 1.5 }}
|
||||||
|
>
|
||||||
|
<DownloadIcon />
|
||||||
|
</IconButton>
|
||||||
|
<Checkbox
|
||||||
edge="end"
|
edge="end"
|
||||||
checked={file.selected}
|
checked={file.selected}
|
||||||
onChange={() => addSelected(file.name)}
|
onChange={() => addSelected(file.path)}
|
||||||
/>}
|
/>
|
||||||
|
</>}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
disablePadding
|
disablePadding
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package handlers
|
|||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
@@ -136,14 +137,55 @@ func SendFile(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// TODO: further path / file validations
|
// TODO: further path / file validations
|
||||||
if strings.Contains(filepath.Dir(filename), root) {
|
if strings.Contains(filepath.Dir(filename), root) {
|
||||||
w.Header().Add(
|
|
||||||
"Content-Disposition",
|
|
||||||
"inline; filename="+filepath.Base(filename),
|
|
||||||
)
|
|
||||||
|
|
||||||
http.ServeFile(w, r, filename)
|
http.ServeFile(w, r, filename)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DownloadFile(w http.ResponseWriter, r *http.Request) {
|
||||||
|
path := chi.URLParam(r, "id")
|
||||||
|
|
||||||
|
if path == "" {
|
||||||
|
http.Error(w, "inexistent path", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
path, err := url.QueryUnescape(path)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(path)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := string(decoded)
|
||||||
|
|
||||||
|
root := config.Instance().DownloadPath
|
||||||
|
|
||||||
|
if strings.Contains(filepath.Dir(filename), root) {
|
||||||
|
w.Header().Add(
|
||||||
|
"Content-Disposition",
|
||||||
|
"inline; filename="+filepath.Base(filename),
|
||||||
|
)
|
||||||
|
w.Header().Set(
|
||||||
|
"Content-Type",
|
||||||
|
"application/octet-stream",
|
||||||
|
)
|
||||||
|
|
||||||
|
fd, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
io.Copy(w, fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|||||||
@@ -42,19 +42,9 @@ func validateToken(tokenValue string) error {
|
|||||||
func Authenticated(next http.Handler) http.Handler {
|
func Authenticated(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
token := r.Header.Get("X-Authentication")
|
token := r.Header.Get("X-Authentication")
|
||||||
|
if token == "" {
|
||||||
if err := validateToken(token); err != nil {
|
token = r.URL.Query().Get("token")
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
}
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func WebSocketAuthentication(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
token := r.URL.Query().Get("token")
|
|
||||||
|
|
||||||
if err := validateToken(token); err != nil {
|
if err := validateToken(token); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
|||||||
@@ -18,18 +18,10 @@ func Container(db *internal.MemoryDB, mq *internal.MessageQueue) *Service {
|
|||||||
// RPC service must be registered before applying this router!
|
// RPC service must be registered before applying this router!
|
||||||
func ApplyRouter() func(chi.Router) {
|
func ApplyRouter() func(chi.Router) {
|
||||||
return func(r chi.Router) {
|
return func(r chi.Router) {
|
||||||
r.Route("/ws", func(r chi.Router) {
|
|
||||||
if config.Instance().RequireAuth {
|
|
||||||
r.Use(middlewares.WebSocketAuthentication)
|
|
||||||
}
|
|
||||||
r.Get("/", WebSocket)
|
|
||||||
})
|
|
||||||
|
|
||||||
r.Route("/http", func(r chi.Router) {
|
|
||||||
if config.Instance().RequireAuth {
|
if config.Instance().RequireAuth {
|
||||||
r.Use(middlewares.Authenticated)
|
r.Use(middlewares.Authenticated)
|
||||||
}
|
}
|
||||||
r.Post("/", Post)
|
r.Get("/ws", WebSocket)
|
||||||
})
|
r.Post("/http", Post)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,7 +102,8 @@ func newServer(c serverConfig) *http.Server {
|
|||||||
}
|
}
|
||||||
r.Post("/downloaded", handlers.ListDownloaded)
|
r.Post("/downloaded", handlers.ListDownloaded)
|
||||||
r.Post("/delete", handlers.DeleteFile)
|
r.Post("/delete", handlers.DeleteFile)
|
||||||
r.Get("/d/{id}", handlers.SendFile)
|
r.Get("/d/{id}", handlers.DownloadFile)
|
||||||
|
r.Get("/v/{id}", handlers.SendFile)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Authentication routes
|
// Authentication routes
|
||||||
|
|||||||
Reference in New Issue
Block a user