fix encoding url in archive
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
"@fontsource/roboto": "^5.0.6",
|
"@fontsource/roboto": "^5.0.6",
|
||||||
"@mui/icons-material": "^5.11.16",
|
"@mui/icons-material": "^5.11.16",
|
||||||
"@mui/material": "^5.13.5",
|
"@mui/material": "^5.13.5",
|
||||||
|
"fp-ts": "^2.16.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
|
|||||||
@@ -1,13 +1,24 @@
|
|||||||
export async function ffetch<T>(
|
import { tryCatch } from 'fp-ts/TaskEither'
|
||||||
url: string,
|
import { flow } from 'fp-ts/lib/function'
|
||||||
onSuccess: (res: T) => void,
|
|
||||||
onError: (err: string) => void,
|
export const ffetch = <T>(url: string, opt?: RequestInit) => flow(
|
||||||
opt?: RequestInit,
|
tryCatch(
|
||||||
) {
|
() => fetcher<T>(url, opt),
|
||||||
|
(e) => `error while fetching: ${e}`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
const fetcher = async <T>(url: string, opt?: RequestInit) => {
|
||||||
const res = await fetch(url, opt)
|
const res = await fetch(url, opt)
|
||||||
|
|
||||||
|
if (opt && !opt.headers) {
|
||||||
|
opt.headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
onError(await res.text())
|
throw await res.text()
|
||||||
return
|
|
||||||
}
|
}
|
||||||
onSuccess(await res.json() as T)
|
return res.json() as T
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { pipe } from 'fp-ts/lib/function'
|
||||||
import type { RPCResponse } from "./types"
|
import type { RPCResponse } from "./types"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -10,15 +11,6 @@ export function validateIP(ipAddr: string): boolean {
|
|||||||
return ipRegex.test(ipAddr)
|
return ipRegex.test(ipAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate a domain via regex.
|
|
||||||
* The validation pass if the domain respects the following formats:
|
|
||||||
* - localhost
|
|
||||||
* - domain.tld
|
|
||||||
* - dir.domain.tld
|
|
||||||
* @param domainName
|
|
||||||
* @returns domain validity test
|
|
||||||
*/
|
|
||||||
export function validateDomain(url: string): boolean {
|
export function validateDomain(url: string): boolean {
|
||||||
const urlRegex = /(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)/
|
const urlRegex = /(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)/
|
||||||
const slugRegex = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
|
const slugRegex = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
|
||||||
@@ -28,17 +20,6 @@ export function validateDomain(url: string): boolean {
|
|||||||
return urlRegex.test(url) || name === 'localhost' && slugRegex.test(slug)
|
return urlRegex.test(url) || name === 'localhost' && slugRegex.test(slug)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate a domain via regex.
|
|
||||||
* Exapmples
|
|
||||||
* - http://example.com
|
|
||||||
* - https://example.com
|
|
||||||
* - http://www.example.com
|
|
||||||
* - https://www.example.com
|
|
||||||
* - http://10.0.0.1/[something]/[something-else]
|
|
||||||
* @param url
|
|
||||||
* @returns url validity test
|
|
||||||
*/
|
|
||||||
export function isValidURL(url: string): boolean {
|
export function isValidURL(url: string): boolean {
|
||||||
let urlRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)/
|
let urlRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)/
|
||||||
return urlRegex.test(url)
|
return urlRegex.test(url)
|
||||||
@@ -94,3 +75,10 @@ export function mapProcessStatus(status: number) {
|
|||||||
|
|
||||||
export const prefersDarkMode = () =>
|
export const prefersDarkMode = () =>
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').matches
|
window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
|
|
||||||
|
export const base64URLEncode = (s: string) => pipe(
|
||||||
|
s,
|
||||||
|
s => String.fromCodePoint(...new TextEncoder().encode(s)),
|
||||||
|
btoa,
|
||||||
|
encodeURIComponent
|
||||||
|
)
|
||||||
@@ -26,22 +26,26 @@ import FolderIcon from '@mui/icons-material/Folder'
|
|||||||
import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile'
|
import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile'
|
||||||
import VideoFileIcon from '@mui/icons-material/VideoFile'
|
import VideoFileIcon from '@mui/icons-material/VideoFile'
|
||||||
|
|
||||||
|
import { matchW } from 'fp-ts/lib/TaskEither'
|
||||||
|
import { pipe } from 'fp-ts/lib/function'
|
||||||
import { useEffect, useMemo, useState, useTransition } from 'react'
|
import { useEffect, useMemo, useState, useTransition } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { useRecoilValue } from 'recoil'
|
import { useRecoilValue } from 'recoil'
|
||||||
import { BehaviorSubject, Subject, combineLatestWith, map, share } from 'rxjs'
|
import { BehaviorSubject, Subject, combineLatestWith, map, share } from 'rxjs'
|
||||||
import { serverURL } from '../atoms/settings'
|
import { serverURL } from '../atoms/settings'
|
||||||
import { useObservable } from '../hooks/observable'
|
import { useObservable } from '../hooks/observable'
|
||||||
|
import { useToast } from '../hooks/toast'
|
||||||
import { useI18n } from '../hooks/useI18n'
|
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 { roundMiB } from '../utils'
|
import { base64URLEncode, roundMiB } from '../utils'
|
||||||
|
|
||||||
export default function Downloaded() {
|
export default function Downloaded() {
|
||||||
const serverAddr = useRecoilValue(serverURL)
|
const serverAddr = useRecoilValue(serverURL)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const { i18n } = useI18n()
|
const { i18n } = useI18n()
|
||||||
|
const { pushMessage } = useToast()
|
||||||
|
|
||||||
const [openDialog, setOpenDialog] = useState(false)
|
const [openDialog, setOpenDialog] = useState(false)
|
||||||
|
|
||||||
@@ -50,20 +54,24 @@ export default function Downloaded() {
|
|||||||
|
|
||||||
const [isPending, startTransition] = useTransition()
|
const [isPending, startTransition] = useTransition()
|
||||||
|
|
||||||
const fetcher = () => ffetch<DirectoryEntry[]>(
|
const fetcher = () => pipe(
|
||||||
|
ffetch<DirectoryEntry[]>(
|
||||||
`${serverAddr}/archive/downloaded`,
|
`${serverAddr}/archive/downloaded`,
|
||||||
(d) => files$.next(d),
|
|
||||||
() => navigate('/login'),
|
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
subdir: '',
|
subdir: '',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
),
|
||||||
|
matchW(
|
||||||
|
(e) => {
|
||||||
|
pushMessage(e)
|
||||||
|
navigate('/login')
|
||||||
|
},
|
||||||
|
(d) => files$.next(d),
|
||||||
)
|
)
|
||||||
|
)()
|
||||||
|
|
||||||
const fetcherSubfolder = (sub: string) => {
|
const fetcherSubfolder = (sub: string) => {
|
||||||
const folders = sub.startsWith('/')
|
const folders = sub.startsWith('/')
|
||||||
@@ -137,9 +145,7 @@ export default function Downloaded() {
|
|||||||
}, [serverAddr])
|
}, [serverAddr])
|
||||||
|
|
||||||
const onFileClick = (path: string) => startTransition(() => {
|
const onFileClick = (path: string) => startTransition(() => {
|
||||||
const encoded = btoa(
|
const encoded = base64URLEncode(path)
|
||||||
String.fromCodePoint(...new TextEncoder().encode(path))
|
|
||||||
)
|
|
||||||
|
|
||||||
window.open(`${serverAddr}/archive/d/${encoded}`)
|
window.open(`${serverAddr}/archive/d/${encoded}`)
|
||||||
})
|
})
|
||||||
|
|||||||
3
go.mod
3
go.mod
@@ -4,11 +4,12 @@ go 1.20
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-chi/chi/v5 v5.0.10
|
github.com/go-chi/chi/v5 v5.0.10
|
||||||
|
github.com/go-chi/cors v1.2.1
|
||||||
github.com/goccy/go-json v0.10.2
|
github.com/goccy/go-json v0.10.2
|
||||||
github.com/golang-jwt/jwt/v5 v5.0.0
|
github.com/golang-jwt/jwt/v5 v5.0.0
|
||||||
github.com/google/uuid v1.3.1
|
github.com/google/uuid v1.3.1
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/marcopeocchi/fazzoletti v0.0.0-20230308161120-c545580f79fa
|
github.com/marcopeocchi/fazzoletti v0.0.0-20230308161120-c545580f79fa
|
||||||
golang.org/x/sys v0.12.0
|
golang.org/x/sys v0.13.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|||||||
8
go.sum
8
go.sum
@@ -1,21 +1,21 @@
|
|||||||
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
|
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
|
||||||
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
|
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||||
|
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
|
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
|
||||||
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/marcopeocchi/fazzoletti v0.0.0-20230308161120-c545580f79fa h1:uaAQLGhN4SesB9inOQ1Q6EH+BwTWHQOvwhR0TIJvnYc=
|
github.com/marcopeocchi/fazzoletti v0.0.0-20230308161120-c545580f79fa h1:uaAQLGhN4SesB9inOQ1Q6EH+BwTWHQOvwhR0TIJvnYc=
|
||||||
github.com/marcopeocchi/fazzoletti v0.0.0-20230308161120-c545580f79fa/go.mod h1:RvfVo/6Sbnfra9kkvIxDW8NYOOaYsHjF0DdtMCs9cdo=
|
github.com/marcopeocchi/fazzoletti v0.0.0-20230308161120-c545580f79fa/go.mod h1:RvfVo/6Sbnfra9kkvIxDW8NYOOaYsHjF0DdtMCs9cdo=
|
||||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
|
||||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||||
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -130,6 +131,12 @@ func SendFile(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
path, err := url.QueryUnescape(path)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
decoded, err := base64.StdEncoding.DecodeString(path)
|
decoded, err := base64.StdEncoding.DecodeString(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
@@ -137,7 +144,7 @@ func SendFile(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
decodedStr := string(decoded)
|
decodedStr := string(decoded)
|
||||||
fmt.Println(decodedStr)
|
fmt.Println("decoded", decodedStr)
|
||||||
|
|
||||||
root := config.Instance().GetConfig().DownloadPath
|
root := config.Instance().GetConfig().DownloadPath
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
"github.com/go-chi/cors"
|
||||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/handlers"
|
"github.com/marcopeocchi/yt-dlp-web-ui/server/handlers"
|
||||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/internal"
|
"github.com/marcopeocchi/yt-dlp-web-ui/server/internal"
|
||||||
middlewares "github.com/marcopeocchi/yt-dlp-web-ui/server/middleware"
|
middlewares "github.com/marcopeocchi/yt-dlp-web-ui/server/middleware"
|
||||||
@@ -54,7 +55,7 @@ func newServer(c serverConfig) *http.Server {
|
|||||||
|
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
|
|
||||||
r.Use(middlewares.CORS)
|
r.Use(cors.AllowAll().Handler)
|
||||||
r.Use(middleware.Logger)
|
r.Use(middleware.Logger)
|
||||||
|
|
||||||
app := http.FileServer(http.FS(c.frontend))
|
app := http.FileServer(http.FS(c.frontend))
|
||||||
|
|||||||
Reference in New Issue
Block a user