package main
import (
"golang.org/x/net/html"
"golang.org/x/net/html/atom"
"bytes"
"errors"
"fmt"
"io"
"log"
"os"
"strings"
"net/http"
"crypto/sha512"
"image"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
)
func getattr(node *html.Node, attr string) string {
for _, a := range node.Attr {
if a.Key == attr {
return a.Val
}
}
return ""
}
func setattr(node *html.Node, attr string, val string) {
for _, a := range node.Attr {
if a.Key == attr {
a.Val = val
return
}
}
a := html.Attribute{
"",
attr,
val,
}
node.Attr = append(node.Attr, a)
}
func removecomments(node *html.Node) {
if node.Type == html.ElementNode && node.Data == "div" {
if getattr(node, "id") == "comments" {
node.FirstChild = nil
node.LastChild = nil
}
}
for c := node.FirstChild; c != nil; c = c.NextSibling {
removecomments(c)
}
}
type imagereq struct {
src string
srchash string
data []byte
x, y int
reply chan<- *imagereq
}
func hashsrc(src string) string {
return fmt.Sprintf("/cfc-images/%x", sha512.Sum512_256([]byte(src)))
}
func fetchimg(src string) (image.Image, []byte, error) {
if src == "" {
return nil, nil, errors.New("missing src")
}
log.Println("fetch img", src)
resp, err := http.Get(src)
if err != nil {
return nil, nil, err
}
var buf bytes.Buffer
defer resp.Body.Close()
io.Copy(&buf, resp.Body)
data := buf.Bytes()
img, _, err := image.Decode(bytes.NewReader(data))
if err != nil {
return nil, nil, err
}
return img, data, nil
}
var imagereqchan = make(chan *imagereq)
func imagemonitor() {
var cache = make(map[string]*imagereq)
for req := range imagereqchan {
reply := cache[req.srchash]
if reply == nil {
img, data, err := fetchimg(req.src)
if err != nil {
req.reply <- nil
continue
}
reply = &imagereq{
req.src,
req.srchash,
data,
img.Bounds().Max.X,
img.Bounds().Max.Y,
nil,
}
log.Println("img dimensions", reply.x, reply.y)
cache[req.srchash] = reply
}
req.reply <- reply
}
}
func cacheimages(node *html.Node) {
if node.Type == html.ElementNode && node.Data == "img" {
ch := make(chan *imagereq)
req := &imagereq{
getattr(node, "src"),
hashsrc(getattr(node, "src")),
nil, 0, 0, ch,
}
imagereqchan <- req
reply := <-ch
if reply != nil {
fmt.Println("replacing img")
node.Data = "div"
node.DataAtom = atom.Div
node.Attr = nil
node.FirstChild = nil
node.LastChild = nil
innerDiv := &html.Node{
nil, nil, nil, nil, nil,
html.ElementNode, atom.Div,
"div",
"", nil,
}
innerText := &html.Node{
nil, nil, nil, nil, nil,
html.TextNode, 0,
fmt.Sprintf("image: %d x %d %d bytes",
reply.x, reply.y, len(reply.data)),
"", nil,
}
innerDiv.AppendChild(innerText)
setattr(innerDiv, "style",
fmt.Sprintf("border: 4px solid black; width: %dpx; height: %dpx",
reply.x, reply.y))
setattr(innerDiv, "data-cfcimg", fmt.Sprintf(``, reply.srchash))
node.AppendChild(innerDiv)
setattr(node, "onclick", "cfcimgclick(this)")
}
}
if node.Type == html.ElementNode && node.Data == "body" {
script := &html.Node{
nil, nil, nil, nil, nil,
html.ElementNode, atom.Script,
"script",
"", nil,
}
js := &html.Node{
nil, nil, nil, nil, nil,
html.TextNode, 0,
`function cfcimgclick(elem) {
elem.innerHTML = elem.children[0].dataset.cfcimg
elem.onclick = undefined
}`,
"", nil,
}
script.AppendChild(js)
node.AppendChild(script)
}
for c := node.FirstChild; c != nil; c = c.NextSibling {
cacheimages(c)
}
}
func rerender(w io.Writer, root *html.Node) {
removecomments(root)
cacheimages(root)
html.Render(w, root)
}
func refetch(w http.ResponseWriter, r *http.Request) {
targurl := "https://" + r.Host + r.URL.Path
log.Println("fetching", targurl)
resp, err := http.Get(targurl)
if err != nil {
log.Println("err", err)
return
}
defer resp.Body.Close()
if strings.HasPrefix(resp.Header.Get("Content-Type"), "text/html") {
doc, err := html.Parse(resp.Body)
if err != nil {
return
}
rerender(w, doc)
} else {
w.Header().Set("Cache-Control", "max-age=360000")
io.Copy(w, resp.Body)
}
}
func serveimage(w http.ResponseWriter, r *http.Request) {
log.Println("img request", r.URL.Path)
ch := make(chan *imagereq)
req := &imagereq{
"",
r.URL.Path,
nil, 0, 0, ch,
}
imagereqchan <- req
reply := <-ch
if reply != nil {
w.Write(reply.data)
}
}
func testmode() {
fd, _ := os.Open("cost.html")
root, _ := html.Parse(fd)
rerender(os.Stdout, root)
fmt.Println("done")
os.Exit(0)
}
func main() {
go imagemonitor()
mux := http.NewServeMux()
mux.HandleFunc("/cfc-images/", serveimage)
mux.HandleFunc("/", refetch)
http.ListenAndServe(":8090", mux)
}