修复: 网站预览资源路径+七牛目录层级
This commit is contained in:
@@ -155,7 +155,6 @@ const _restoreLastPlayed = () => {
|
|||||||
if (!track) return
|
if (!track) return
|
||||||
lastPlayedPath.value = saved
|
lastPlayedPath.value = saved
|
||||||
playingPath.value = saved
|
playingPath.value = saved
|
||||||
emit('switch', track)
|
|
||||||
}
|
}
|
||||||
restorePlaylist().then(_restoreLastPlayed)
|
restorePlaylist().then(_restoreLastPlayed)
|
||||||
|
|
||||||
|
|||||||
@@ -898,7 +898,10 @@ var sa=Element.prototype.setAttribute;
|
|||||||
Element.prototype.setAttribute=function(n,v){if((n==='src'||n==='href'||n==='data'||n==='poster')&&typeof v==='string')v=rw(v);return sa.call(this,n,v);};
|
Element.prototype.setAttribute=function(n,v){if((n==='src'||n==='href'||n==='data'||n==='poster')&&typeof v==='string')v=rw(v);return sa.call(this,n,v);};
|
||||||
try{var d=Object.getOwnPropertyDescriptor(HTMLScriptElement.prototype,'src');Object.defineProperty(HTMLScriptElement.prototype,'src',{set:function(v){d.set.call(this,rw(v))},get:d.get,configurable:true});}catch(e){}
|
try{var d=Object.getOwnPropertyDescriptor(HTMLScriptElement.prototype,'src');Object.defineProperty(HTMLScriptElement.prototype,'src',{set:function(v){d.set.call(this,rw(v))},get:d.get,configurable:true});}catch(e){}
|
||||||
try{var d2=Object.getOwnPropertyDescriptor(HTMLLinkElement.prototype,'href');Object.defineProperty(HTMLLinkElement.prototype,'href',{set:function(v){d2.set.call(this,rw(v))},get:d2.get,configurable:true});}catch(e){}
|
try{var d2=Object.getOwnPropertyDescriptor(HTMLLinkElement.prototype,'href');Object.defineProperty(HTMLLinkElement.prototype,'href',{set:function(v){d2.set.call(this,rw(v))},get:d2.get,configurable:true});}catch(e){}
|
||||||
})();
|
try{var d3=Object.getOwnPropertyDescriptor(HTMLImageElement.prototype,'src');Object.defineProperty(HTMLImageElement.prototype,'src',{set:function(v){d3.set.call(this,rw(v))},get:d3.get,configurable:true});}catch(e){}
|
||||||
|
try{var d4=Object.getOwnPropertyDescriptor(HTMLSourceElement.prototype,'src');Object.defineProperty(HTMLSourceElement.prototype,'src',{set:function(v){d4.set.call(this,rw(v))},get:d4.get,configurable:true});}catch(e){}
|
||||||
|
try{var d5=Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype,'src');Object.defineProperty(HTMLIFrameElement.prototype,'src',{set:function(v){d5.set.call(this,rw(v))},get:d5.get,configurable:true});}catch(e){}
|
||||||
|
})();
|
||||||
</script>`
|
</script>`
|
||||||
|
|
||||||
// 在 <head> 后立即插入(确保在任何其他脚本之前执行)
|
// 在 <head> 后立即插入(确保在任何其他脚本之前执行)
|
||||||
|
|||||||
47
internal/filesystem/site_resource.go
Normal file
47
internal/filesystem/site_resource.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package filesystem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
htmlResRegex = regexp.MustCompile(`(?:src|href|data|poster)=["']([^"']+)["']`)
|
||||||
|
htmlCssUrlRe = regexp.MustCompile(`url\(\s*["']?([^"')]+)["']?\s*\)`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExtractHtmlResources 从 HTML 内容提取资源路径
|
||||||
|
func ExtractHtmlResources(html string) []string {
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
var resources []string
|
||||||
|
add := func(v string) {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v != "" && !seen[v] {
|
||||||
|
seen[v] = true
|
||||||
|
resources = append(resources, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, m := range htmlResRegex.FindAllStringSubmatch(html, -1) {
|
||||||
|
if len(m) > 1 {
|
||||||
|
add(m[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, m := range htmlCssUrlRe.FindAllStringSubmatch(html, -1) {
|
||||||
|
if len(m) > 1 {
|
||||||
|
add(m[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resources
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShouldSkipResource 判断资源路径是否应跳过
|
||||||
|
func ShouldSkipResource(p string) bool {
|
||||||
|
return strings.HasPrefix(p, "data:") ||
|
||||||
|
strings.HasPrefix(p, "http://") ||
|
||||||
|
strings.HasPrefix(p, "https://") ||
|
||||||
|
strings.HasPrefix(p, "//") ||
|
||||||
|
strings.HasPrefix(p, "#") ||
|
||||||
|
strings.HasPrefix(p, "javascript:") ||
|
||||||
|
strings.HasPrefix(p, "mailto:") ||
|
||||||
|
strings.HasPrefix(p, "blob:")
|
||||||
|
}
|
||||||
@@ -480,6 +480,9 @@ func (c *Client) ListFiles(ctx context.Context, options *oss.ListOptions) (*oss.
|
|||||||
if options.Marker != "" {
|
if options.Marker != "" {
|
||||||
path += "&marker=" + options.Marker
|
path += "&marker=" + options.Marker
|
||||||
}
|
}
|
||||||
|
if options.Delimiter != "" {
|
||||||
|
path += "&delimiter=" + options.Delimiter
|
||||||
|
}
|
||||||
|
|
||||||
// 使用 GET 方法和 RSF API
|
// 使用 GET 方法和 RSF API
|
||||||
resp, err := c.doRSFRequest("GET", path)
|
resp, err := c.doRSFRequest("GET", path)
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -640,9 +639,7 @@ func (s *Service) RenamePath(connID string, oldPath string, newPath string) (*fi
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// htmlResourceRegex 提取 HTML 资源引用的正则
|
|
||||||
var htmlResourceRegex = regexp.MustCompile(`(?:src|href|data|poster)=["']([^"']+)["']`)
|
|
||||||
var htmlCssUrlRegex = regexp.MustCompile(`url\(\s*["']?([^"')]+)["']?\s*\)`)
|
|
||||||
|
|
||||||
// DownloadSiteForPreview 下载 HTML 及其引用的资源到临时目录
|
// DownloadSiteForPreview 下载 HTML 及其引用的资源到临时目录
|
||||||
// 对绝对路径(/开头)从 HTML 目录逐级向上嗅探网站根目录
|
// 对绝对路径(/开头)从 HTML 目录逐级向上嗅探网站根目录
|
||||||
@@ -695,7 +692,7 @@ func (s *Service) DownloadSiteForPreview(connID string, rawPath string) (string,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return htmlLocalPath, nil // HTML 已下载,资源解析失败不影响
|
return htmlLocalPath, nil // HTML 已下载,资源解析失败不影响
|
||||||
}
|
}
|
||||||
resources := extractHtmlResources(string(htmlContent))
|
resources := filesystem.ExtractHtmlResources(string(htmlContent))
|
||||||
|
|
||||||
// 4. 下载资源
|
// 4. 下载资源
|
||||||
htmlOssDir := keyDir
|
htmlOssDir := keyDir
|
||||||
@@ -716,7 +713,7 @@ func (s *Service) DownloadSiteForPreview(connID string, rawPath string) (string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, resPath := range resources {
|
for _, resPath := range resources {
|
||||||
if shouldSkipResource(resPath) {
|
if filesystem.ShouldSkipResource(resPath) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -817,57 +814,19 @@ func supplementDir(c oss.OSSProvider, ctx context.Context, remoteDir string, tmp
|
|||||||
prefix := remoteDir + "/"
|
prefix := remoteDir + "/"
|
||||||
result, err := c.ListFiles(ctx, &oss.ListOptions{Prefix: prefix, MaxKeys: 200})
|
result, err := c.ListFiles(ctx, &oss.ListOptions{Prefix: prefix, MaxKeys: 200})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, f := range result.Files {
|
for _, f := range result.Files {
|
||||||
if strings.HasSuffix(f.Key, "/") || f.Size == 0 {
|
if strings.HasSuffix(f.Key, "/") || f.Size == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
relPath := strings.TrimPrefix(f.Key, siteRoot)
|
localPath := filepath.Join(tmpDir, filepath.FromSlash(f.Key))
|
||||||
localPath := filepath.Join(tmpDir, filepath.FromSlash(relPath))
|
|
||||||
if _, err := os.Stat(localPath); err == nil {
|
if _, err := os.Stat(localPath); err == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
downloadResource(c, ctx, f.Key, localPath)
|
downloadResource(c, ctx, f.Key, localPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func extractHtmlResources(html string) []string {
|
|
||||||
seen := make(map[string]bool)
|
|
||||||
var resources []string
|
|
||||||
|
|
||||||
add := func(v string) {
|
|
||||||
v = strings.TrimSpace(v)
|
|
||||||
if v != "" && !seen[v] {
|
|
||||||
seen[v] = true
|
|
||||||
resources = append(resources, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, m := range htmlResourceRegex.FindAllStringSubmatch(html, -1) {
|
|
||||||
if len(m) > 1 {
|
|
||||||
add(m[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, m := range htmlCssUrlRegex.FindAllStringSubmatch(html, -1) {
|
|
||||||
if len(m) > 1 {
|
|
||||||
add(m[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return resources
|
|
||||||
}
|
|
||||||
|
|
||||||
// shouldSkipResource 判断资源路径是否应跳过
|
|
||||||
func shouldSkipResource(p string) bool {
|
|
||||||
return strings.HasPrefix(p, "data:") ||
|
|
||||||
strings.HasPrefix(p, "http://") ||
|
|
||||||
strings.HasPrefix(p, "https://") ||
|
|
||||||
strings.HasPrefix(p, "//") ||
|
|
||||||
strings.HasPrefix(p, "#") ||
|
|
||||||
strings.HasPrefix(p, "javascript:") ||
|
|
||||||
strings.HasPrefix(p, "mailto:") ||
|
|
||||||
strings.HasPrefix(p, "blob:")
|
|
||||||
}
|
|
||||||
|
|
||||||
// DownloadToTemp 下载文件到本地临时目录(带 SQLite 缓存)
|
// DownloadToTemp 下载文件到本地临时目录(带 SQLite 缓存)
|
||||||
func (s *Service) DownloadToTemp(connID string, rawPath string) (string, error) {
|
func (s *Service) DownloadToTemp(connID string, rawPath string) (string, error) {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -19,10 +18,7 @@ import (
|
|||||||
sftpclient "github.com/pkg/sftp"
|
sftpclient "github.com/pkg/sftp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
sftpResRegex = regexp.MustCompile(`(?:src|href|data|poster)=["']([^"']+)["']`)
|
|
||||||
sftpCssUrlRe = regexp.MustCompile(`url\(\s*["']?([^"')]+)["']?\s*\)`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Service SFTP 文件操作服务
|
// Service SFTP 文件操作服务
|
||||||
type Service struct {
|
type Service struct {
|
||||||
@@ -383,7 +379,7 @@ func (s *Service) DownloadSiteForPreview(connID string, remotePath string) (stri
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return htmlLocalPath, nil
|
return htmlLocalPath, nil
|
||||||
}
|
}
|
||||||
resources := sftpExtractResources(string(htmlContent))
|
resources := filesystem.ExtractHtmlResources(string(htmlContent))
|
||||||
|
|
||||||
// 5. 下载静态引用资源(嗅探网站根)
|
// 5. 下载静态引用资源(嗅探网站根)
|
||||||
htmlRemoteDir := keyDir
|
htmlRemoteDir := keyDir
|
||||||
@@ -404,7 +400,7 @@ func (s *Service) DownloadSiteForPreview(connID string, remotePath string) (stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, resPath := range resources {
|
for _, resPath := range resources {
|
||||||
if sftpShouldSkip(resPath) {
|
if filesystem.ShouldSkipResource(resPath) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
isAbsolute := strings.HasPrefix(resPath, "/")
|
isAbsolute := strings.HasPrefix(resPath, "/")
|
||||||
@@ -513,9 +509,7 @@ func sftpSupplementDir(s *Service, c *Client, remoteDir string, tmpDir string, s
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fullPath := path.Join(remoteDir, entry.Name())
|
fullPath := path.Join(remoteDir, entry.Name())
|
||||||
relPath := strings.TrimPrefix(fullPath, siteRoot)
|
localPath := filepath.Join(tmpDir, filepath.FromSlash(fullPath))
|
||||||
relPath = strings.TrimPrefix(relPath, "/")
|
|
||||||
localPath := filepath.Join(tmpDir, filepath.FromSlash(relPath))
|
|
||||||
if _, err := os.Stat(localPath); err == nil {
|
if _, err := os.Stat(localPath); err == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -707,38 +701,3 @@ func toFileOperationResult(m map[string]interface{}, isDir bool) *filesystem.Fil
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sftpExtractResources 从 HTML 内容提取资源路径
|
|
||||||
func sftpExtractResources(html string) []string {
|
|
||||||
seen := make(map[string]bool)
|
|
||||||
var resources []string
|
|
||||||
add := func(v string) {
|
|
||||||
v = strings.TrimSpace(v)
|
|
||||||
if v != "" && !seen[v] {
|
|
||||||
seen[v] = true
|
|
||||||
resources = append(resources, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, m := range sftpResRegex.FindAllStringSubmatch(html, -1) {
|
|
||||||
if len(m) > 1 {
|
|
||||||
add(m[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, m := range sftpCssUrlRe.FindAllStringSubmatch(html, -1) {
|
|
||||||
if len(m) > 1 {
|
|
||||||
add(m[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resources
|
|
||||||
}
|
|
||||||
|
|
||||||
// sftpShouldSkip 判断资源路径是否应跳过
|
|
||||||
func sftpShouldSkip(p string) bool {
|
|
||||||
return strings.HasPrefix(p, "data:") ||
|
|
||||||
strings.HasPrefix(p, "http://") ||
|
|
||||||
strings.HasPrefix(p, "https://") ||
|
|
||||||
strings.HasPrefix(p, "//") ||
|
|
||||||
strings.HasPrefix(p, "#") ||
|
|
||||||
strings.HasPrefix(p, "javascript:") ||
|
|
||||||
strings.HasPrefix(p, "mailto:") ||
|
|
||||||
strings.HasPrefix(p, "blob:")
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user