2019-05-30 01:53:05 +00:00
|
|
|
//
|
|
|
|
// WRP - Web Rendering Proxy
|
|
|
|
//
|
|
|
|
// Copyright (c) 2013-2018 Antoni Sawicki
|
2022-11-06 09:41:40 +00:00
|
|
|
// Copyright (c) 2019-2022 Google LLC
|
2019-05-30 01:53:05 +00:00
|
|
|
//
|
|
|
|
|
2019-05-29 08:29:01 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2019-05-30 06:49:39 +00:00
|
|
|
"bytes"
|
2019-05-29 08:52:28 +00:00
|
|
|
"context"
|
2022-03-17 02:27:34 +00:00
|
|
|
"embed"
|
2019-05-29 08:52:28 +00:00
|
|
|
"flag"
|
|
|
|
"fmt"
|
2020-10-27 11:35:57 +00:00
|
|
|
"html/template"
|
2019-11-13 08:22:07 +00:00
|
|
|
"image"
|
2022-11-10 05:29:58 +00:00
|
|
|
"image/color/palette"
|
2019-05-30 06:49:39 +00:00
|
|
|
"image/gif"
|
|
|
|
"image/png"
|
2020-10-29 14:16:14 +00:00
|
|
|
"io/ioutil"
|
2019-05-29 08:52:28 +00:00
|
|
|
"log"
|
2019-11-03 23:01:05 +00:00
|
|
|
"math"
|
2019-05-30 09:03:17 +00:00
|
|
|
"math/rand"
|
2019-05-29 08:52:28 +00:00
|
|
|
"net/http"
|
2019-05-30 06:49:39 +00:00
|
|
|
"net/url"
|
2019-07-17 05:29:35 +00:00
|
|
|
"os"
|
|
|
|
"os/signal"
|
2019-05-29 08:52:28 +00:00
|
|
|
"strconv"
|
2019-05-31 07:36:53 +00:00
|
|
|
"strings"
|
2019-07-17 05:29:35 +00:00
|
|
|
"syscall"
|
2019-05-29 08:52:28 +00:00
|
|
|
"time"
|
2019-05-29 08:29:01 +00:00
|
|
|
|
2020-09-28 19:19:27 +00:00
|
|
|
"github.com/MaxHalford/halfgone"
|
2019-08-13 06:35:29 +00:00
|
|
|
"github.com/chromedp/cdproto/css"
|
2019-05-29 09:39:06 +00:00
|
|
|
"github.com/chromedp/cdproto/emulation"
|
2019-11-03 23:01:05 +00:00
|
|
|
"github.com/chromedp/cdproto/page"
|
2019-05-29 08:52:28 +00:00
|
|
|
"github.com/chromedp/chromedp"
|
2022-11-10 04:48:18 +00:00
|
|
|
"github.com/soniakeys/quant/median"
|
2019-05-29 08:29:01 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2022-11-06 09:39:03 +00:00
|
|
|
version = "4.5.3"
|
2020-10-27 11:35:57 +00:00
|
|
|
srv http.Server
|
|
|
|
ctx context.Context
|
|
|
|
cancel context.CancelFunc
|
|
|
|
img = make(map[string]bytes.Buffer)
|
|
|
|
ismap = make(map[string]wrpReq)
|
2020-10-31 15:51:20 +00:00
|
|
|
noDel bool
|
|
|
|
defType string
|
|
|
|
defGeom geom
|
2020-10-27 11:35:57 +00:00
|
|
|
htmlTmpl *template.Template
|
2019-05-29 08:29:01 +00:00
|
|
|
)
|
|
|
|
|
2022-03-17 02:27:34 +00:00
|
|
|
// go:embed *.html
|
|
|
|
var fs embed.FS
|
|
|
|
|
2019-11-03 23:38:26 +00:00
|
|
|
type geom struct {
|
|
|
|
w int64
|
|
|
|
h int64
|
|
|
|
c int64
|
|
|
|
}
|
|
|
|
|
2020-10-27 11:35:57 +00:00
|
|
|
// Data for html template
|
|
|
|
type uiData struct {
|
|
|
|
Version string
|
|
|
|
URL string
|
2020-10-31 15:51:20 +00:00
|
|
|
BgColor string
|
2020-10-27 11:35:57 +00:00
|
|
|
NColors int64
|
|
|
|
Width int64
|
|
|
|
Height int64
|
2021-03-08 13:02:22 +00:00
|
|
|
Zoom float64
|
2020-10-27 11:35:57 +00:00
|
|
|
ImgType string
|
2020-10-31 15:51:20 +00:00
|
|
|
ImgURL string
|
2020-10-27 11:35:57 +00:00
|
|
|
ImgSize string
|
|
|
|
ImgWidth int
|
|
|
|
ImgHeight int
|
2020-10-31 15:51:20 +00:00
|
|
|
MapURL string
|
2020-10-27 11:35:57 +00:00
|
|
|
PageHeight string
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parameters for HTML print function
|
|
|
|
type printParams struct {
|
2020-10-31 15:51:20 +00:00
|
|
|
bgColor string
|
|
|
|
pageHeight string
|
|
|
|
imgSize string
|
|
|
|
imgURL string
|
|
|
|
mapURL string
|
|
|
|
imgWidth int
|
|
|
|
imgHeight int
|
2020-10-27 11:35:57 +00:00
|
|
|
}
|
|
|
|
|
2020-04-24 10:06:21 +00:00
|
|
|
// WRP Request
|
2019-07-11 06:58:40 +00:00
|
|
|
type wrpReq struct {
|
2020-04-26 06:56:14 +00:00
|
|
|
url string // url
|
|
|
|
width int64 // width
|
|
|
|
height int64 // height
|
2021-03-08 13:02:22 +00:00
|
|
|
zoom float64 // zoom/scale
|
2020-04-26 06:56:14 +00:00
|
|
|
colors int64 // #colors
|
|
|
|
mouseX int64 // mouseX
|
|
|
|
mouseY int64 // mouseY
|
|
|
|
keys string // keys to send
|
|
|
|
buttons string // Fn buttons
|
|
|
|
imgType string // imgtype
|
2022-11-06 09:11:49 +00:00
|
|
|
w http.ResponseWriter
|
|
|
|
r *http.Request
|
2019-06-26 00:07:43 +00:00
|
|
|
}
|
2019-06-24 07:40:34 +00:00
|
|
|
|
2020-04-24 10:06:21 +00:00
|
|
|
// Parse HTML Form, Process Input Boxes, Etc.
|
2022-11-06 09:07:21 +00:00
|
|
|
func (rq *wrpReq) parseForm() {
|
2022-11-06 09:11:49 +00:00
|
|
|
rq.r.ParseForm()
|
|
|
|
rq.url = rq.r.FormValue("url")
|
2022-11-06 09:07:21 +00:00
|
|
|
if len(rq.url) > 1 && !strings.HasPrefix(rq.url, "http") {
|
|
|
|
rq.url = fmt.Sprintf("http://www.google.com/search?q=%s", url.QueryEscape(rq.url))
|
2019-06-26 00:07:43 +00:00
|
|
|
}
|
2022-11-06 09:11:49 +00:00
|
|
|
rq.width, _ = strconv.ParseInt(rq.r.FormValue("w"), 10, 64)
|
|
|
|
rq.height, _ = strconv.ParseInt(rq.r.FormValue("h"), 10, 64)
|
2022-11-06 09:07:21 +00:00
|
|
|
if rq.width < 10 && rq.height < 10 {
|
|
|
|
rq.width = defGeom.w
|
|
|
|
rq.height = defGeom.h
|
2019-05-29 08:52:28 +00:00
|
|
|
}
|
2022-11-06 09:11:49 +00:00
|
|
|
rq.zoom, _ = strconv.ParseFloat(rq.r.FormValue("z"), 64)
|
2022-11-06 09:07:21 +00:00
|
|
|
if rq.zoom < 0.1 {
|
|
|
|
rq.zoom = 1.0
|
2019-05-31 01:08:48 +00:00
|
|
|
}
|
2022-11-06 09:11:49 +00:00
|
|
|
rq.colors, _ = strconv.ParseInt(rq.r.FormValue("c"), 10, 64)
|
2022-11-06 09:07:21 +00:00
|
|
|
if rq.colors < 2 || rq.colors > 256 {
|
|
|
|
rq.colors = defGeom.c
|
2019-06-03 00:06:41 +00:00
|
|
|
}
|
2022-11-06 09:11:49 +00:00
|
|
|
rq.keys = rq.r.FormValue("k")
|
|
|
|
rq.buttons = rq.r.FormValue("Fn")
|
|
|
|
rq.imgType = rq.r.FormValue("t")
|
2022-11-29 13:50:17 +00:00
|
|
|
if rq.imgType != "gif" && rq.imgType != "png" {
|
2022-11-06 09:07:21 +00:00
|
|
|
rq.imgType = defType
|
2019-11-04 00:58:38 +00:00
|
|
|
}
|
2022-11-06 09:11:49 +00:00
|
|
|
log.Printf("%s WrpReq from UI Form: %+v\n", rq.r.RemoteAddr, rq)
|
2019-06-26 00:07:43 +00:00
|
|
|
}
|
|
|
|
|
2020-04-24 10:06:21 +00:00
|
|
|
// Display WP UI
|
2022-11-06 09:07:21 +00:00
|
|
|
func (rq *wrpReq) printHTML(p printParams) {
|
2022-11-06 09:11:49 +00:00
|
|
|
rq.w.Header().Set("Cache-Control", "max-age=0")
|
|
|
|
rq.w.Header().Set("Expires", "-1")
|
|
|
|
rq.w.Header().Set("Pragma", "no-cache")
|
|
|
|
rq.w.Header().Set("Content-Type", "text/html")
|
2020-10-27 11:35:57 +00:00
|
|
|
data := uiData{
|
|
|
|
Version: version,
|
2022-11-06 09:07:21 +00:00
|
|
|
URL: rq.url,
|
2020-10-31 15:51:20 +00:00
|
|
|
BgColor: p.bgColor,
|
2022-11-06 09:07:21 +00:00
|
|
|
Width: rq.width,
|
|
|
|
Height: rq.height,
|
|
|
|
NColors: rq.colors,
|
|
|
|
Zoom: rq.zoom,
|
|
|
|
ImgType: rq.imgType,
|
2020-10-31 15:51:20 +00:00
|
|
|
ImgSize: p.imgSize,
|
|
|
|
ImgWidth: p.imgWidth,
|
|
|
|
ImgHeight: p.imgHeight,
|
|
|
|
ImgURL: p.imgURL,
|
|
|
|
MapURL: p.mapURL,
|
|
|
|
PageHeight: p.pageHeight,
|
2019-11-04 01:53:20 +00:00
|
|
|
}
|
2022-11-06 09:11:49 +00:00
|
|
|
err := htmlTmpl.Execute(rq.w, data)
|
2020-10-27 11:35:57 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
2019-11-04 00:58:38 +00:00
|
|
|
}
|
2019-06-26 08:07:13 +00:00
|
|
|
}
|
|
|
|
|
2020-10-26 08:21:42 +00:00
|
|
|
// Determine what action to take
|
2022-11-06 09:07:21 +00:00
|
|
|
func (rq *wrpReq) action() chromedp.Action {
|
2020-04-24 10:09:20 +00:00
|
|
|
// Mouse Click
|
2022-11-06 09:07:21 +00:00
|
|
|
if rq.mouseX > 0 && rq.mouseY > 0 {
|
2022-11-06 09:11:49 +00:00
|
|
|
log.Printf("%s Mouse Click %d,%d\n", rq.r.RemoteAddr, rq.mouseX, rq.mouseY)
|
2022-11-06 09:07:21 +00:00
|
|
|
return chromedp.MouseClickXY(float64(rq.mouseX)/float64(rq.zoom), float64(rq.mouseY)/float64(rq.zoom))
|
2020-10-26 08:21:42 +00:00
|
|
|
}
|
|
|
|
// Buttons
|
2022-11-06 09:07:21 +00:00
|
|
|
if len(rq.buttons) > 0 {
|
2022-11-06 09:11:49 +00:00
|
|
|
log.Printf("%s Button %v\n", rq.r.RemoteAddr, rq.buttons)
|
2022-11-06 09:07:21 +00:00
|
|
|
switch rq.buttons {
|
2019-07-13 07:40:56 +00:00
|
|
|
case "Bk":
|
2020-10-26 08:21:42 +00:00
|
|
|
return chromedp.NavigateBack()
|
2021-03-08 12:41:13 +00:00
|
|
|
case "St":
|
|
|
|
return chromedp.Stop()
|
|
|
|
case "Re":
|
|
|
|
return chromedp.Reload()
|
2019-07-13 07:40:56 +00:00
|
|
|
case "Bs":
|
2020-10-26 08:21:42 +00:00
|
|
|
return chromedp.KeyEvent("\b")
|
2019-07-13 07:40:56 +00:00
|
|
|
case "Rt":
|
2020-10-26 08:21:42 +00:00
|
|
|
return chromedp.KeyEvent("\r")
|
2019-07-13 07:40:56 +00:00
|
|
|
case "<":
|
2020-10-26 08:21:42 +00:00
|
|
|
return chromedp.KeyEvent("\u0302")
|
2019-07-13 07:40:56 +00:00
|
|
|
case "^":
|
2020-10-26 08:21:42 +00:00
|
|
|
return chromedp.KeyEvent("\u0304")
|
2019-07-13 07:40:56 +00:00
|
|
|
case "v":
|
2020-10-26 08:21:42 +00:00
|
|
|
return chromedp.KeyEvent("\u0301")
|
2019-07-13 07:40:56 +00:00
|
|
|
case ">":
|
2020-10-26 08:21:42 +00:00
|
|
|
return chromedp.KeyEvent("\u0303")
|
2019-07-13 07:40:56 +00:00
|
|
|
}
|
2020-10-26 08:21:42 +00:00
|
|
|
}
|
|
|
|
// Keys
|
2022-11-06 09:07:21 +00:00
|
|
|
if len(rq.keys) > 0 {
|
2022-11-06 09:11:49 +00:00
|
|
|
log.Printf("%s Sending Keys: %#v\n", rq.r.RemoteAddr, rq.keys)
|
2022-11-06 09:07:21 +00:00
|
|
|
return chromedp.KeyEvent(rq.keys)
|
2019-07-10 08:01:40 +00:00
|
|
|
}
|
2020-10-26 08:21:42 +00:00
|
|
|
// Navigate to URL
|
2022-11-06 09:11:49 +00:00
|
|
|
log.Printf("%s Processing Capture Request for %s\n", rq.r.RemoteAddr, rq.url)
|
2022-11-06 09:07:21 +00:00
|
|
|
return chromedp.Navigate(rq.url)
|
2020-10-26 08:21:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Process Keyboard and Mouse events or Navigate to the desired URL.
|
2022-11-06 09:07:21 +00:00
|
|
|
func (rq *wrpReq) navigate() {
|
|
|
|
err := chromedp.Run(ctx, rq.action())
|
2019-05-31 07:41:46 +00:00
|
|
|
if err != nil {
|
2019-06-09 00:32:35 +00:00
|
|
|
if err.Error() == "context canceled" {
|
2022-11-06 09:11:49 +00:00
|
|
|
log.Printf("%s Contex cancelled, try again", rq.r.RemoteAddr)
|
|
|
|
fmt.Fprintf(rq.w, "<BR>%s<BR> -- restarting, try again", err)
|
2019-06-09 00:32:35 +00:00
|
|
|
ctx, cancel = chromedp.NewContext(context.Background())
|
2020-10-26 08:21:42 +00:00
|
|
|
return
|
2019-06-09 00:32:35 +00:00
|
|
|
}
|
2022-11-06 09:11:49 +00:00
|
|
|
log.Printf("%s %s", rq.r.RemoteAddr, err)
|
|
|
|
fmt.Fprintf(rq.w, "<BR>%s<BR>", err)
|
2019-05-31 07:41:46 +00:00
|
|
|
}
|
2020-04-24 09:45:34 +00:00
|
|
|
}
|
|
|
|
|
2022-11-06 08:47:59 +00:00
|
|
|
// https://github.com/chromedp/chromedp/issues/979
|
|
|
|
func chromedpCaptureScreenshot(res *[]byte, h int64) chromedp.Action {
|
|
|
|
if res == nil {
|
|
|
|
panic("res cannot be nil")
|
|
|
|
}
|
|
|
|
if h == 0 {
|
|
|
|
return chromedp.CaptureScreenshot(res)
|
|
|
|
}
|
|
|
|
|
|
|
|
return chromedp.ActionFunc(func(ctx context.Context) error {
|
|
|
|
var err error
|
|
|
|
*res, err = page.CaptureScreenshot().Do(ctx)
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-11-29 13:50:17 +00:00
|
|
|
func gifPalette(i image.Image, n int64) image.Image {
|
|
|
|
switch n {
|
|
|
|
case 2:
|
|
|
|
i = halfgone.FloydSteinbergDitherer{}.Apply(halfgone.ImageToGray(i))
|
|
|
|
case 216:
|
|
|
|
var FastGifLut = [256]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5}
|
|
|
|
r := i.Bounds()
|
|
|
|
// NOTE: the color index computation below works only for palette.WebSafe!
|
|
|
|
p := image.NewPaletted(r, palette.WebSafe)
|
|
|
|
if i64, ok := i.(image.RGBA64Image); ok {
|
|
|
|
for y := r.Min.Y; y < r.Max.Y; y++ {
|
|
|
|
for x := r.Min.X; x < r.Max.X; x++ {
|
|
|
|
c := i64.RGBA64At(x, y)
|
|
|
|
r6 := FastGifLut[c.R>>8]
|
|
|
|
g6 := FastGifLut[c.G>>8]
|
|
|
|
b6 := FastGifLut[c.B>>8]
|
|
|
|
p.SetColorIndex(x, y, uint8(36*r6+6*g6+b6))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for y := r.Min.Y; y < r.Max.Y; y++ {
|
|
|
|
for x := r.Min.X; x < r.Max.X; x++ {
|
|
|
|
c := i.At(x, y)
|
|
|
|
r, g, b, _ := c.RGBA()
|
|
|
|
r6 := FastGifLut[r&0xff]
|
|
|
|
g6 := FastGifLut[g&0xff]
|
|
|
|
b6 := FastGifLut[b&0xff]
|
|
|
|
p.SetColorIndex(x, y, uint8(36*r6+6*g6+b6))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
i = p
|
|
|
|
default:
|
|
|
|
q := median.Quantizer(n)
|
|
|
|
i = q.Paletted(i)
|
|
|
|
}
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
2020-04-24 10:06:21 +00:00
|
|
|
// Capture currently rendered web page to an image and fake ISMAP
|
2022-11-06 09:07:21 +00:00
|
|
|
func (rq *wrpReq) capture() {
|
2020-04-24 09:45:34 +00:00
|
|
|
var err error
|
2020-04-23 10:25:39 +00:00
|
|
|
var styles []*css.ComputedStyleProperty
|
2019-08-22 07:38:36 +00:00
|
|
|
var r, g, b int
|
2019-11-03 23:01:05 +00:00
|
|
|
var h int64
|
|
|
|
var pngcap []byte
|
2019-08-13 06:35:29 +00:00
|
|
|
chromedp.Run(ctx,
|
2022-11-06 09:07:21 +00:00
|
|
|
emulation.SetDeviceMetricsOverride(int64(float64(rq.width)/rq.zoom), 10, rq.zoom, false),
|
|
|
|
chromedp.Location(&rq.url),
|
2019-11-03 23:01:05 +00:00
|
|
|
chromedp.ComputedStyle("body", &styles, chromedp.ByQuery),
|
|
|
|
chromedp.ActionFunc(func(ctx context.Context) error {
|
2022-11-06 07:51:44 +00:00
|
|
|
_, _, _, _, _, s, err := page.GetLayoutMetrics().Do(ctx)
|
2019-11-03 23:01:05 +00:00
|
|
|
if err == nil {
|
|
|
|
h = int64(math.Ceil(s.Height))
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}),
|
2019-07-12 08:51:23 +00:00
|
|
|
)
|
2019-08-13 06:35:29 +00:00
|
|
|
for _, style := range styles {
|
|
|
|
if style.Name == "background-color" {
|
|
|
|
fmt.Sscanf(style.Value, "rgb(%d,%d,%d)", &r, &g, &b)
|
|
|
|
}
|
|
|
|
}
|
2022-11-06 09:11:49 +00:00
|
|
|
log.Printf("%s Landed on: %s, Height: %v\n", rq.r.RemoteAddr, rq.url, h)
|
2022-11-06 09:07:21 +00:00
|
|
|
height := int64(float64(rq.height) / rq.zoom)
|
|
|
|
if rq.height == 0 && h > 0 {
|
2020-10-26 08:49:03 +00:00
|
|
|
height = h + 30
|
2019-11-03 23:01:05 +00:00
|
|
|
}
|
2022-11-06 08:47:59 +00:00
|
|
|
chromedp.Run(
|
2022-11-06 09:07:21 +00:00
|
|
|
ctx, emulation.SetDeviceMetricsOverride(int64(float64(rq.width)/rq.zoom), height, rq.zoom, false),
|
2022-11-06 09:38:31 +00:00
|
|
|
chromedp.Sleep(time.Second*2), // TODO(tenox): totally lame, find a better way to determine if page is rendered
|
2020-10-27 11:05:17 +00:00
|
|
|
)
|
2022-11-06 08:47:59 +00:00
|
|
|
// Capture screenshot...
|
2022-11-06 09:07:21 +00:00
|
|
|
err = chromedp.Run(ctx, chromedpCaptureScreenshot(&pngcap, rq.height))
|
2019-06-24 07:40:34 +00:00
|
|
|
if err != nil {
|
2020-04-26 07:23:31 +00:00
|
|
|
if err.Error() == "context canceled" {
|
2022-11-06 09:11:49 +00:00
|
|
|
log.Printf("%s Contex cancelled, try again", rq.r.RemoteAddr)
|
|
|
|
fmt.Fprintf(rq.w, "<BR>%s<BR> -- restarting, try again", err)
|
2020-04-26 07:23:31 +00:00
|
|
|
ctx, cancel = chromedp.NewContext(context.Background())
|
2020-10-26 08:21:42 +00:00
|
|
|
return
|
2020-04-26 07:23:31 +00:00
|
|
|
}
|
2022-11-06 09:11:49 +00:00
|
|
|
log.Printf("%s Failed to capture screenshot: %s\n", rq.r.RemoteAddr, err)
|
|
|
|
fmt.Fprintf(rq.w, "<BR>Unable to capture screenshot:<BR>%s<BR>\n", err)
|
2019-06-24 07:40:34 +00:00
|
|
|
return
|
|
|
|
}
|
2019-06-26 08:07:13 +00:00
|
|
|
seq := rand.Intn(9999)
|
2022-11-29 13:50:17 +00:00
|
|
|
imgpath := fmt.Sprintf("/img/%04d.%s", seq, rq.imgType)
|
2019-06-26 08:07:13 +00:00
|
|
|
mappath := fmt.Sprintf("/map/%04d.map", seq)
|
2022-11-06 09:07:21 +00:00
|
|
|
ismap[mappath] = *rq
|
2019-11-04 01:24:18 +00:00
|
|
|
var ssize string
|
2020-10-27 11:35:57 +00:00
|
|
|
var iw, ih int
|
2022-11-06 09:07:21 +00:00
|
|
|
switch rq.imgType {
|
2020-10-26 08:32:09 +00:00
|
|
|
case "gif":
|
2019-11-03 07:56:04 +00:00
|
|
|
i, err := png.Decode(bytes.NewReader(pngcap))
|
2019-08-22 07:38:36 +00:00
|
|
|
if err != nil {
|
2022-11-29 13:50:17 +00:00
|
|
|
log.Printf("%s Failed to decode PNG screenshot: %s\n", rq.r.RemoteAddr, err)
|
|
|
|
fmt.Fprintf(rq.w, "<BR>Unable to decode page PNG screenshot:<BR>%s<BR>\n", err)
|
2019-08-22 07:38:36 +00:00
|
|
|
return
|
|
|
|
}
|
2022-11-06 07:27:10 +00:00
|
|
|
st := time.Now()
|
2022-11-29 13:50:17 +00:00
|
|
|
var gifbuf bytes.Buffer
|
|
|
|
err = gif.Encode(&gifbuf, gifPalette(i, rq.colors), &gif.Options{})
|
2019-08-22 07:38:36 +00:00
|
|
|
if err != nil {
|
2022-11-06 09:11:49 +00:00
|
|
|
log.Printf("%s Failed to encode GIF: %s\n", rq.r.RemoteAddr, err)
|
|
|
|
fmt.Fprintf(rq.w, "<BR>Unable to encode GIF:<BR>%s<BR>\n", err)
|
2019-08-22 07:38:36 +00:00
|
|
|
return
|
|
|
|
}
|
2019-11-03 07:56:04 +00:00
|
|
|
img[imgpath] = gifbuf
|
2020-04-26 08:25:28 +00:00
|
|
|
ssize = fmt.Sprintf("%.0f KB", float32(len(gifbuf.Bytes()))/1024.0)
|
2020-10-27 11:35:57 +00:00
|
|
|
iw = i.Bounds().Max.X
|
|
|
|
ih = i.Bounds().Max.Y
|
2022-11-29 13:50:17 +00:00
|
|
|
log.Printf("%s Encoded GIF image: %s, Size: %s, Colors: %d, Res: %dx%d, Time: %vms\n", rq.r.RemoteAddr, imgpath, ssize, rq.colors, iw, ih, time.Since(st).Milliseconds())
|
2020-10-26 08:32:09 +00:00
|
|
|
case "png":
|
2019-08-22 07:38:36 +00:00
|
|
|
pngbuf := bytes.NewBuffer(pngcap)
|
2019-11-03 07:56:04 +00:00
|
|
|
img[imgpath] = *pngbuf
|
2019-11-13 08:22:07 +00:00
|
|
|
cfg, _, _ := image.DecodeConfig(pngbuf)
|
2020-04-26 08:25:28 +00:00
|
|
|
ssize = fmt.Sprintf("%.0f KB", float32(len(pngbuf.Bytes()))/1024.0)
|
2020-10-27 11:35:57 +00:00
|
|
|
iw = cfg.Width
|
|
|
|
ih = cfg.Height
|
2022-11-29 13:50:17 +00:00
|
|
|
log.Printf("%s Got PNG image: %s, Size: %s, Res: %dx%d\n", rq.r.RemoteAddr, imgpath, ssize, iw, ih)
|
2019-08-22 07:38:36 +00:00
|
|
|
}
|
2022-11-06 09:07:21 +00:00
|
|
|
rq.printHTML(printParams{
|
2020-10-31 15:51:20 +00:00
|
|
|
bgColor: fmt.Sprintf("#%02X%02X%02X", r, g, b),
|
|
|
|
pageHeight: fmt.Sprintf("%d PX", h),
|
|
|
|
imgSize: ssize,
|
|
|
|
imgURL: imgpath,
|
|
|
|
mapURL: mappath,
|
|
|
|
imgWidth: iw,
|
|
|
|
imgHeight: ih,
|
2020-10-27 11:35:57 +00:00
|
|
|
})
|
2022-11-06 09:11:49 +00:00
|
|
|
log.Printf("%s Done with capture for %s\n", rq.r.RemoteAddr, rq.url)
|
2019-05-29 08:29:01 +00:00
|
|
|
}
|
|
|
|
|
2020-11-05 16:05:50 +00:00
|
|
|
// Process HTTP requests to WRP '/' url
|
2022-11-06 09:11:49 +00:00
|
|
|
func pageServer(w http.ResponseWriter, r *http.Request) {
|
|
|
|
log.Printf("%s Page Request for %s [%+v]\n", r.RemoteAddr, r.URL.Path, r.URL.RawQuery)
|
2022-11-06 09:07:21 +00:00
|
|
|
rq := wrpReq{
|
2022-11-06 09:11:49 +00:00
|
|
|
r: r,
|
|
|
|
w: w,
|
2022-11-06 09:04:00 +00:00
|
|
|
}
|
2022-11-06 09:07:21 +00:00
|
|
|
rq.parseForm()
|
|
|
|
if len(rq.url) < 4 {
|
|
|
|
rq.printHTML(printParams{bgColor: "#FFFFFF"})
|
2020-11-05 16:05:50 +00:00
|
|
|
return
|
|
|
|
}
|
2022-11-06 09:07:21 +00:00
|
|
|
rq.navigate()
|
|
|
|
rq.capture()
|
2020-11-05 16:05:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Process HTTP requests to ISMAP '/map/' url
|
2022-11-06 09:11:49 +00:00
|
|
|
func mapServer(w http.ResponseWriter, r *http.Request) {
|
|
|
|
log.Printf("%s ISMAP Request for %s [%+v]\n", r.RemoteAddr, r.URL.Path, r.URL.RawQuery)
|
|
|
|
rq, ok := ismap[r.URL.Path]
|
|
|
|
rq.r = r
|
|
|
|
rq.w = w
|
2020-11-05 16:05:50 +00:00
|
|
|
if !ok {
|
2022-11-06 09:11:49 +00:00
|
|
|
fmt.Fprintf(w, "Unable to find map %s\n", r.URL.Path)
|
|
|
|
log.Printf("Unable to find map %s\n", r.URL.Path)
|
2020-11-05 16:05:50 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if !noDel {
|
2022-11-06 09:11:49 +00:00
|
|
|
defer delete(ismap, r.URL.Path)
|
2020-11-05 16:05:50 +00:00
|
|
|
}
|
2022-11-06 09:11:49 +00:00
|
|
|
n, err := fmt.Sscanf(r.URL.RawQuery, "%d,%d", &rq.mouseX, &rq.mouseY)
|
2020-11-05 16:05:50 +00:00
|
|
|
if err != nil || n != 2 {
|
2022-11-06 09:11:49 +00:00
|
|
|
fmt.Fprintf(w, "n=%d, err=%s\n", n, err)
|
|
|
|
log.Printf("%s ISMAP n=%d, err=%s\n", r.RemoteAddr, n, err)
|
2020-11-05 16:05:50 +00:00
|
|
|
return
|
|
|
|
}
|
2022-11-06 09:11:49 +00:00
|
|
|
log.Printf("%s WrpReq from ISMAP: %+v\n", r.RemoteAddr, rq)
|
2022-11-06 09:07:21 +00:00
|
|
|
if len(rq.url) < 4 {
|
|
|
|
rq.printHTML(printParams{bgColor: "#FFFFFF"})
|
2020-11-05 16:05:50 +00:00
|
|
|
return
|
|
|
|
}
|
2022-11-06 09:07:21 +00:00
|
|
|
rq.navigate()
|
|
|
|
rq.capture()
|
2020-11-05 16:05:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Process HTTP requests for images '/img/' url
|
2022-11-06 09:11:49 +00:00
|
|
|
func imgServer(w http.ResponseWriter, r *http.Request) {
|
|
|
|
log.Printf("%s IMG Request for %s\n", r.RemoteAddr, r.URL.Path)
|
|
|
|
imgbuf, ok := img[r.URL.Path]
|
2020-11-05 16:05:50 +00:00
|
|
|
if !ok || imgbuf.Bytes() == nil {
|
2022-11-06 09:11:49 +00:00
|
|
|
fmt.Fprintf(w, "Unable to find image %s\n", r.URL.Path)
|
|
|
|
log.Printf("%s Unable to find image %s\n", r.RemoteAddr, r.URL.Path)
|
2020-11-05 16:05:50 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if !noDel {
|
2022-11-06 09:11:49 +00:00
|
|
|
defer delete(img, r.URL.Path)
|
2020-11-05 16:05:50 +00:00
|
|
|
}
|
|
|
|
switch {
|
2022-11-06 09:11:49 +00:00
|
|
|
case strings.HasPrefix(r.URL.Path, ".gif"):
|
|
|
|
w.Header().Set("Content-Type", "image/gif")
|
|
|
|
case strings.HasPrefix(r.URL.Path, ".png"):
|
|
|
|
w.Header().Set("Content-Type", "image/png")
|
2020-11-05 16:05:50 +00:00
|
|
|
}
|
2022-11-06 09:11:49 +00:00
|
|
|
w.Header().Set("Content-Length", strconv.Itoa(len(imgbuf.Bytes())))
|
|
|
|
w.Header().Set("Cache-Control", "max-age=0")
|
|
|
|
w.Header().Set("Expires", "-1")
|
|
|
|
w.Header().Set("Pragma", "no-cache")
|
|
|
|
w.Write(imgbuf.Bytes())
|
|
|
|
w.(http.Flusher).Flush()
|
2020-11-05 16:05:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Process HTTP requests for Shutdown via '/shutdown/' url
|
2022-11-06 09:11:49 +00:00
|
|
|
func haltServer(w http.ResponseWriter, r *http.Request) {
|
|
|
|
log.Printf("%s Shutdown Request for %s\n", r.RemoteAddr, r.URL.Path)
|
|
|
|
w.Header().Set("Cache-Control", "max-age=0")
|
|
|
|
w.Header().Set("Expires", "-1")
|
|
|
|
w.Header().Set("Pragma", "no-cache")
|
|
|
|
w.Header().Set("Content-Type", "text/plain")
|
|
|
|
fmt.Fprintf(w, "Shutting down WRP...\n")
|
|
|
|
w.(http.Flusher).Flush()
|
2020-11-05 16:05:50 +00:00
|
|
|
time.Sleep(time.Second * 2)
|
|
|
|
cancel()
|
|
|
|
srv.Shutdown(context.Background())
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
2020-11-05 15:43:06 +00:00
|
|
|
// returns html template, either from html file or built-in
|
2020-10-29 14:16:14 +00:00
|
|
|
func tmpl(t string) string {
|
|
|
|
var tmpl []byte
|
|
|
|
fh, err := os.Open(t)
|
|
|
|
if err != nil {
|
2022-11-06 09:12:26 +00:00
|
|
|
goto builtin
|
2020-10-29 14:16:14 +00:00
|
|
|
}
|
|
|
|
tmpl, err = ioutil.ReadAll(fh)
|
|
|
|
if err != nil {
|
2022-11-06 09:12:26 +00:00
|
|
|
goto builtin
|
2020-10-29 14:16:14 +00:00
|
|
|
}
|
|
|
|
log.Printf("Got UI template from %v file\n", t)
|
|
|
|
return string(tmpl)
|
|
|
|
|
2022-11-06 09:12:26 +00:00
|
|
|
builtin:
|
2022-03-17 02:27:34 +00:00
|
|
|
fhs, err := fs.Open("/wrp.html")
|
2020-10-29 14:16:14 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
tmpl, err = ioutil.ReadAll(fhs)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
log.Printf("Got UI template from built-in\n")
|
|
|
|
return string(tmpl)
|
|
|
|
}
|
|
|
|
|
2020-04-24 10:06:21 +00:00
|
|
|
// Main...
|
2019-05-29 08:29:01 +00:00
|
|
|
func main() {
|
2021-03-08 12:26:09 +00:00
|
|
|
var addr, fgeom, tHTML string
|
2020-10-26 08:37:25 +00:00
|
|
|
var headless bool
|
2019-06-04 00:50:16 +00:00
|
|
|
var debug bool
|
2019-11-03 23:38:26 +00:00
|
|
|
var err error
|
2019-05-29 08:52:28 +00:00
|
|
|
flag.StringVar(&addr, "l", ":8080", "Listen address:port, default :8080")
|
2020-10-26 08:37:25 +00:00
|
|
|
flag.BoolVar(&headless, "h", true, "Headless mode - hide browser window")
|
2019-06-04 00:50:16 +00:00
|
|
|
flag.BoolVar(&debug, "d", false, "Debug ChromeDP")
|
2020-10-31 15:51:20 +00:00
|
|
|
flag.BoolVar(&noDel, "n", false, "Do not free maps and images after use")
|
2022-11-29 13:50:17 +00:00
|
|
|
flag.StringVar(&defType, "t", "gif", "Image type: gif|png")
|
|
|
|
flag.StringVar(&fgeom, "g", "1152x600x216", "Geometry: width x height x colors, height can be 0 for unlimited")
|
2021-03-08 12:26:09 +00:00
|
|
|
flag.StringVar(&tHTML, "ui", "wrp.html", "HTML template file for the UI")
|
2019-05-29 08:52:28 +00:00
|
|
|
flag.Parse()
|
2020-04-27 08:05:02 +00:00
|
|
|
if len(os.Getenv("PORT")) > 0 {
|
|
|
|
addr = ":" + os.Getenv(("PORT"))
|
|
|
|
}
|
2020-10-31 15:51:20 +00:00
|
|
|
n, err := fmt.Sscanf(fgeom, "%dx%dx%d", &defGeom.w, &defGeom.h, &defGeom.c)
|
2019-11-03 23:38:26 +00:00
|
|
|
if err != nil || n != 3 {
|
|
|
|
log.Fatalf("Unable to parse -g geometry flag / %s", err)
|
|
|
|
}
|
2020-11-05 16:05:50 +00:00
|
|
|
|
2019-06-04 00:50:16 +00:00
|
|
|
opts := append(chromedp.DefaultExecAllocatorOptions[:],
|
2019-06-04 07:58:45 +00:00
|
|
|
chromedp.Flag("headless", headless),
|
2019-07-11 06:58:40 +00:00
|
|
|
chromedp.Flag("hide-scrollbars", false),
|
2019-06-04 00:50:16 +00:00
|
|
|
)
|
2019-07-17 05:29:35 +00:00
|
|
|
actx, acancel := chromedp.NewExecAllocator(context.Background(), opts...)
|
|
|
|
defer acancel()
|
2022-11-06 09:20:18 +00:00
|
|
|
switch debug {
|
|
|
|
case true:
|
2019-06-04 08:23:46 +00:00
|
|
|
ctx, cancel = chromedp.NewContext(actx, chromedp.WithDebugf(log.Printf))
|
2022-11-06 09:20:18 +00:00
|
|
|
default:
|
2019-06-04 08:23:46 +00:00
|
|
|
ctx, cancel = chromedp.NewContext(actx)
|
2019-06-04 00:50:16 +00:00
|
|
|
}
|
|
|
|
defer cancel()
|
2020-11-05 16:05:50 +00:00
|
|
|
|
2019-05-30 09:03:17 +00:00
|
|
|
rand.Seed(time.Now().UnixNano())
|
2020-11-05 16:05:50 +00:00
|
|
|
|
2019-07-17 05:29:35 +00:00
|
|
|
c := make(chan os.Signal)
|
|
|
|
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
|
|
|
go func() {
|
|
|
|
<-c
|
|
|
|
log.Printf("Interrupt - shutting down.")
|
|
|
|
cancel()
|
|
|
|
srv.Shutdown(context.Background())
|
|
|
|
os.Exit(1)
|
|
|
|
}()
|
2020-11-05 16:05:50 +00:00
|
|
|
|
2019-05-29 08:52:28 +00:00
|
|
|
http.HandleFunc("/", pageServer)
|
2019-06-26 08:07:13 +00:00
|
|
|
http.HandleFunc("/map/", mapServer)
|
2019-05-30 09:03:17 +00:00
|
|
|
http.HandleFunc("/img/", imgServer)
|
2019-06-18 06:53:22 +00:00
|
|
|
http.HandleFunc("/shutdown/", haltServer)
|
2019-05-30 01:47:03 +00:00
|
|
|
http.HandleFunc("/favicon.ico", http.NotFound)
|
2020-11-05 16:05:50 +00:00
|
|
|
|
2019-06-02 23:24:46 +00:00
|
|
|
log.Printf("Web Rendering Proxy Version %s\n", version)
|
2020-04-27 19:29:18 +00:00
|
|
|
log.Printf("Args: %q", os.Args)
|
2020-10-31 15:51:20 +00:00
|
|
|
log.Printf("Default Img Type: %v, Geometry: %+v", defType, defGeom)
|
2020-11-05 16:05:50 +00:00
|
|
|
|
2021-03-08 12:26:09 +00:00
|
|
|
htmlTmpl, err = template.New("wrp.html").Parse(tmpl(tHTML))
|
2020-10-29 14:16:14 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2020-11-05 16:05:50 +00:00
|
|
|
|
2019-05-31 06:40:43 +00:00
|
|
|
log.Printf("Starting WRP http server on %s\n", addr)
|
2019-06-18 06:53:22 +00:00
|
|
|
srv.Addr = addr
|
2019-11-03 23:38:26 +00:00
|
|
|
err = srv.ListenAndServe()
|
2019-06-18 06:53:22 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2019-05-29 08:29:01 +00:00
|
|
|
}
|