feat: initial goget release — modern IPv6-first download utility
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
//go:build linux || freebsd
|
||||
// +build linux freebsd
|
||||
|
||||
// Package linkrewrite rewrites HTML links for local offline viewing.
|
||||
package linkrewrite
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
// Rewriter converts absolute URLs in HTML to local relative paths.
|
||||
type Rewriter struct {
|
||||
baseURL *url.URL
|
||||
outputDir string
|
||||
visited map[string]string
|
||||
visitedMu sync.RWMutex
|
||||
}
|
||||
|
||||
// New creates a link rewriter for a mirror or recursive download.
|
||||
func New(baseURL *url.URL, outputDir string) *Rewriter {
|
||||
return &Rewriter{
|
||||
baseURL: baseURL,
|
||||
outputDir: outputDir,
|
||||
visited: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// Register records a downloaded URL and its local path.
|
||||
func (lr *Rewriter) Register(u *url.URL, localPath string) {
|
||||
lr.visitedMu.Lock()
|
||||
defer lr.visitedMu.Unlock()
|
||||
lr.visited[u.String()] = localPath
|
||||
}
|
||||
|
||||
// RewriteLinks converts URLs in an HTML document to local paths.
|
||||
func (lr *Rewriter) RewriteLinks(doc *html.Node, filePath string) int {
|
||||
converted := 0
|
||||
dir := filepath.Dir(filePath)
|
||||
|
||||
var traverse func(*html.Node)
|
||||
traverse = func(n *html.Node) {
|
||||
if n.Type == html.ElementNode {
|
||||
switch n.Data {
|
||||
case "a":
|
||||
converted += lr.rewriteAttr(n, "href", dir)
|
||||
case "img", "script", "iframe", "video", "audio", "source", "track", "embed", "object":
|
||||
converted += lr.rewriteAttr(n, "src", dir)
|
||||
case "link":
|
||||
converted += lr.rewriteAttr(n, "href", dir)
|
||||
case "form":
|
||||
converted += lr.rewriteAttr(n, "action", dir)
|
||||
case "area":
|
||||
converted += lr.rewriteAttr(n, "href", dir)
|
||||
case "meta":
|
||||
for _, attr := range n.Attr {
|
||||
if attr.Key == "content" && strings.Contains(attr.Val, "url=") {
|
||||
converted += lr.rewriteMetaRefresh(n, dir)
|
||||
}
|
||||
if attr.Key == "property" && strings.Contains(attr.Val, "image") {
|
||||
converted += lr.rewriteAttr(n, "content", dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
traverse(c)
|
||||
}
|
||||
}
|
||||
|
||||
traverse(doc)
|
||||
return converted
|
||||
}
|
||||
|
||||
func (lr *Rewriter) rewriteMetaRefresh(n *html.Node, baseDir string) int {
|
||||
for i, attr := range n.Attr {
|
||||
if attr.Key == "content" {
|
||||
content := attr.Val
|
||||
idx := strings.Index(strings.ToLower(content), "url=")
|
||||
if idx == -1 {
|
||||
return 0
|
||||
}
|
||||
urlStr := strings.TrimSpace(content[idx+4:])
|
||||
parsed, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
resolved := lr.baseURL.ResolveReference(parsed)
|
||||
lr.visitedMu.RLock()
|
||||
localPath, exists := lr.visited[resolved.String()]
|
||||
lr.visitedMu.RUnlock()
|
||||
if exists {
|
||||
relPath, err := filepath.Rel(baseDir, localPath)
|
||||
if err != nil {
|
||||
relPath = localPath
|
||||
}
|
||||
n.Attr[i].Val = content[:idx+4] + filepath.ToSlash(relPath)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (lr *Rewriter) rewriteAttr(n *html.Node, attrName, baseDir string) int {
|
||||
for i, attr := range n.Attr {
|
||||
if attr.Key != attrName {
|
||||
continue
|
||||
}
|
||||
value := attr.Val
|
||||
if strings.HasPrefix(value, "#") ||
|
||||
strings.HasPrefix(value, "javascript:") ||
|
||||
strings.HasPrefix(value, "mailto:") ||
|
||||
strings.HasPrefix(value, "tel:") ||
|
||||
strings.HasPrefix(value, "data:") ||
|
||||
strings.HasPrefix(value, "about:") {
|
||||
return 0
|
||||
}
|
||||
parsed, err := url.Parse(value)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
resolved := lr.baseURL.ResolveReference(parsed)
|
||||
lr.visitedMu.RLock()
|
||||
localPath, exists := lr.visited[resolved.String()]
|
||||
lr.visitedMu.RUnlock()
|
||||
if !exists {
|
||||
return 0
|
||||
}
|
||||
relPath, err := filepath.Rel(baseDir, localPath)
|
||||
if err != nil {
|
||||
relPath = localPath
|
||||
}
|
||||
n.Attr[i].Val = filepath.ToSlash(relPath)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
Reference in New Issue
Block a user