2019-05-30 01:53:05 +00:00
|
|
|
//
|
|
|
|
// WRP - Web Rendering Proxy
|
|
|
|
//
|
2024-05-23 07:34:00 +00:00
|
|
|
// Copyright (c) 2013-2024 Antoni Sawicki
|
2024-01-02 10:09:38 +00:00
|
|
|
// Copyright (c) 2019-2024 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"
|
2022-12-08 08:18:13 +00:00
|
|
|
"io"
|
2019-05-29 08:52:28 +00:00
|
|
|
"log"
|
|
|
|
"net/http"
|
2019-05-30 06:49:39 +00:00
|
|
|
"net/url"
|
2019-07-17 05:29:35 +00:00
|
|
|
"os"
|
|
|
|
"os/signal"
|
2024-05-23 07:34:00 +00:00
|
|
|
"runtime"
|
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"
|
2024-06-20 06:37:44 +00:00
|
|
|
"text/template"
|
2019-05-29 08:52:28 +00:00
|
|
|
"time"
|
2024-07-09 09:09:18 +00:00
|
|
|
|
|
|
|
_ "github.com/breml/rootcerts"
|
2019-05-29 08:29:01 +00:00
|
|
|
)
|
|
|
|
|
2024-07-03 12:24:56 +00:00
|
|
|
const version = "4.8.0"
|
|
|
|
|
|
|
|
var (
|
|
|
|
addr = flag.String("l", ":8080", "Listen address:port, default :8080")
|
|
|
|
headless = flag.Bool("h", true, "Headless mode / hide browser window (default true)")
|
|
|
|
noDel = flag.Bool("n", false, "Do not free maps and images after use")
|
|
|
|
defType = flag.String("t", "gif", "Image type: png|gif|jpg")
|
|
|
|
wrpMode = flag.String("m", "ismap", "WRP Mode: ismap|html")
|
|
|
|
defImgSize = flag.Int64("is", 200, "html mode default image size")
|
2024-07-09 08:45:03 +00:00
|
|
|
defJpgQual = flag.Int64("q", 75, "Jpeg image quality, default 75%") // TODO: this should be form dropdown when jpeg is selected as image type
|
2024-07-03 12:24:56 +00:00
|
|
|
fgeom = flag.String("g", "1152x600x216", "Geometry: width x height x colors, height can be 0 for unlimited")
|
|
|
|
htmFnam = flag.String("ui", "wrp.html", "HTML template file for the UI")
|
|
|
|
delay = flag.Duration("s", 2*time.Second, "Delay/sleep after page is rendered and before screenshot is taken")
|
|
|
|
userAgent = flag.String("ua", "", "override chrome user agent")
|
|
|
|
)
|
2022-12-08 07:44:09 +00:00
|
|
|
|
2019-05-29 08:29:01 +00:00
|
|
|
var (
|
2022-12-08 08:18:13 +00:00
|
|
|
srv http.Server
|
|
|
|
actx, ctx context.Context
|
|
|
|
acncl, cncl context.CancelFunc
|
|
|
|
img = make(map[string]bytes.Buffer)
|
|
|
|
ismap = make(map[string]wrpReq)
|
|
|
|
defGeom geom
|
|
|
|
htmlTmpl *template.Template
|
2019-05-29 08:29:01 +00:00
|
|
|
)
|
|
|
|
|
2022-11-30 10:00:53 +00:00
|
|
|
//go:embed *.html
|
2022-03-17 02:27:34 +00:00
|
|
|
var fs embed.FS
|
|
|
|
|
2019-11-03 23:38:26 +00:00
|
|
|
type geom struct {
|
|
|
|
w int64
|
|
|
|
h int64
|
|
|
|
c int64
|
|
|
|
}
|
|
|
|
|
2024-07-09 04:27:51 +00:00
|
|
|
// TODO: there is a major overlap/duplication/triplication
|
|
|
|
// between the 3 data structs, perhps we could reduce to just one?
|
|
|
|
|
2020-10-27 11:35:57 +00:00
|
|
|
// Data for html template
|
|
|
|
type uiData struct {
|
|
|
|
Version string
|
2024-07-03 12:24:56 +00:00
|
|
|
WrpMode string
|
2020-10-27 11:35:57 +00:00
|
|
|
URL string
|
2020-10-31 15:51:20 +00:00
|
|
|
BgColor string
|
2020-10-27 11:35:57 +00:00
|
|
|
NColors int64
|
2024-07-09 08:45:03 +00:00
|
|
|
JQual int64
|
2020-10-27 11:35:57 +00:00
|
|
|
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
|
2024-07-03 12:24:56 +00:00
|
|
|
MaxSize int64
|
2020-10-31 15:51:20 +00:00
|
|
|
MapURL string
|
2020-10-27 11:35:57 +00:00
|
|
|
PageHeight string
|
2024-06-20 06:37:44 +00:00
|
|
|
TeXT string
|
2020-10-27 11:35:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Parameters for HTML print function
|
2024-07-09 04:36:32 +00:00
|
|
|
type uiParams struct {
|
2020-10-31 15:51:20 +00:00
|
|
|
bgColor string
|
|
|
|
pageHeight string
|
|
|
|
imgSize string
|
|
|
|
imgURL string
|
|
|
|
mapURL string
|
|
|
|
imgWidth int
|
|
|
|
imgHeight int
|
2024-06-20 06:37:44 +00:00
|
|
|
text string
|
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 {
|
2024-07-09 08:45:03 +00:00
|
|
|
url string
|
|
|
|
width int64
|
|
|
|
height int64
|
|
|
|
zoom float64
|
|
|
|
nColors int64
|
|
|
|
jQual int64
|
|
|
|
mouseX int64
|
|
|
|
mouseY int64
|
|
|
|
keys string
|
|
|
|
buttons string
|
|
|
|
imgType string
|
|
|
|
wrpMode string
|
|
|
|
maxSize int64
|
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
|
|
|
|
2022-11-06 09:07:21 +00:00
|
|
|
func (rq *wrpReq) parseForm() {
|
2022-11-06 09:11:49 +00:00
|
|
|
rq.r.ParseForm()
|
2024-07-03 12:24:56 +00:00
|
|
|
rq.wrpMode = rq.r.FormValue("m")
|
|
|
|
if rq.wrpMode == "" {
|
|
|
|
rq.wrpMode = *wrpMode
|
|
|
|
}
|
2022-11-06 09:11:49 +00:00
|
|
|
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
|
|
|
}
|
2024-07-03 12:24:56 +00:00
|
|
|
// TODO: implement atoiOrZero
|
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
|
|
|
}
|
2024-07-09 08:45:03 +00:00
|
|
|
rq.imgType = rq.r.FormValue("t")
|
|
|
|
switch rq.imgType {
|
|
|
|
case "png", "gif", "jpg":
|
|
|
|
default:
|
|
|
|
rq.imgType = *defType
|
|
|
|
}
|
|
|
|
rq.nColors, _ = strconv.ParseInt(rq.r.FormValue("c"), 10, 64)
|
|
|
|
if rq.nColors < 2 || rq.nColors > 256 {
|
|
|
|
rq.nColors = defGeom.c
|
|
|
|
}
|
|
|
|
rq.jQual, _ = strconv.ParseInt(rq.r.FormValue("q"), 10, 64)
|
|
|
|
if rq.jQual < 1 || rq.jQual > 100 {
|
|
|
|
rq.jQual = *defJpgQual
|
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")
|
2024-07-03 12:24:56 +00:00
|
|
|
rq.maxSize, _ = strconv.ParseInt(rq.r.FormValue("s"), 10, 64)
|
|
|
|
if rq.maxSize == 0 {
|
|
|
|
rq.maxSize = *defImgSize
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
2024-07-09 04:36:32 +00:00
|
|
|
func (rq *wrpReq) printUI(p uiParams) {
|
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")
|
2024-07-02 09:04:36 +00:00
|
|
|
if p.bgColor == "" {
|
|
|
|
p.bgColor = "#FFFFFF"
|
|
|
|
}
|
2020-10-27 11:35:57 +00:00
|
|
|
data := uiData{
|
|
|
|
Version: version,
|
2024-07-03 12:24:56 +00:00
|
|
|
WrpMode: rq.wrpMode,
|
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,
|
2024-07-09 08:45:03 +00:00
|
|
|
NColors: rq.nColors,
|
|
|
|
JQual: rq.jQual,
|
2022-11-06 09:07:21 +00:00
|
|
|
Zoom: rq.zoom,
|
2024-07-03 12:24:56 +00:00
|
|
|
MaxSize: rq.maxSize,
|
2022-11-06 09:07:21 +00:00
|
|
|
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,
|
2024-06-20 06:37:44 +00:00
|
|
|
TeXT: p.text,
|
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 {
|
2024-06-22 22:40:37 +00:00
|
|
|
fmt.Fprintf(rq.w, "Error: %v", err)
|
2019-11-04 00:58:38 +00:00
|
|
|
}
|
2019-06-26 08:07:13 +00:00
|
|
|
}
|
|
|
|
|
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 {
|
2024-07-09 04:36:32 +00:00
|
|
|
rq.printUI(uiParams{bgColor: "#FFFFFF"})
|
2020-11-05 16:05:50 +00:00
|
|
|
return
|
|
|
|
}
|
2022-12-08 08:28:28 +00:00
|
|
|
rq.navigate() // TODO: if error from navigate do not capture
|
2024-07-03 12:24:56 +00:00
|
|
|
if rq.wrpMode == "html" {
|
2024-06-23 02:43:53 +00:00
|
|
|
rq.captureMarkdown()
|
2024-06-20 06:37:44 +00:00
|
|
|
return
|
|
|
|
}
|
2024-07-03 12:24:56 +00:00
|
|
|
rq.captureScreenshot()
|
2020-11-05 16:05:50 +00:00
|
|
|
}
|
|
|
|
|
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("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)
|
2022-12-08 08:18:13 +00:00
|
|
|
cncl()
|
|
|
|
acncl()
|
2020-11-05 16:05:50 +00:00
|
|
|
srv.Shutdown(context.Background())
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
2024-07-09 04:36:32 +00:00
|
|
|
func wrpTemplate(t string) string {
|
2020-10-29 14:16:14 +00:00
|
|
|
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
|
|
|
}
|
2022-12-08 07:50:58 +00:00
|
|
|
defer fh.Close()
|
|
|
|
|
2024-07-03 12:24:56 +00:00
|
|
|
tmpl, err = io.ReadAll(fh)
|
2020-10-29 14:16:14 +00:00
|
|
|
if err != nil {
|
2022-11-06 09:12:26 +00:00
|
|
|
goto builtin
|
2020-10-29 14:16:14 +00:00
|
|
|
}
|
2022-12-08 07:50:58 +00:00
|
|
|
log.Printf("Got HTML UI template from %v file, size %v \n", t, len(tmpl))
|
2020-10-29 14:16:14 +00:00
|
|
|
return string(tmpl)
|
|
|
|
|
2022-11-06 09:12:26 +00:00
|
|
|
builtin:
|
2022-11-30 10:00:53 +00:00
|
|
|
fhs, err := fs.Open("wrp.html")
|
2020-10-29 14:16:14 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2022-12-08 07:50:58 +00:00
|
|
|
defer fhs.Close()
|
2020-10-29 14:16:14 +00:00
|
|
|
|
2024-07-03 12:24:56 +00:00
|
|
|
tmpl, err = io.ReadAll(fhs)
|
2020-10-29 14:16:14 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2022-12-08 07:50:58 +00:00
|
|
|
log.Printf("Got HTML UI template from embed\n")
|
2020-10-29 14:16:14 +00:00
|
|
|
return string(tmpl)
|
|
|
|
}
|
|
|
|
|
2019-05-29 08:29:01 +00:00
|
|
|
func main() {
|
2019-11-03 23:38:26 +00:00
|
|
|
var err error
|
2024-05-23 07:34:00 +00:00
|
|
|
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
2019-05-29 08:52:28 +00:00
|
|
|
flag.Parse()
|
2024-05-23 07:34:00 +00:00
|
|
|
log.Printf("Web Rendering Proxy Version %s (%v)\n", version, runtime.GOARCH)
|
2022-12-08 09:49:46 +00:00
|
|
|
log.Printf("Args: %q", os.Args)
|
2020-04-27 08:05:02 +00:00
|
|
|
if len(os.Getenv("PORT")) > 0 {
|
2022-12-08 07:44:09 +00:00
|
|
|
*addr = ":" + os.Getenv(("PORT"))
|
2020-04-27 08:05:02 +00:00
|
|
|
}
|
2024-07-09 04:36:32 +00:00
|
|
|
printMyIPs(*addr)
|
2022-12-08 07:44:09 +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
|
|
|
|
2024-07-03 12:24:56 +00:00
|
|
|
cncl, acncl = chromedpStart()
|
2022-12-08 08:18:13 +00:00
|
|
|
defer cncl()
|
2024-07-03 12:24:56 +00:00
|
|
|
defer acncl()
|
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.")
|
2022-12-08 08:18:13 +00:00
|
|
|
cncl()
|
|
|
|
acncl()
|
2019-07-17 05:29:35 +00:00
|
|
|
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)
|
2024-07-09 04:36:32 +00:00
|
|
|
http.HandleFunc("/img/", imgServerMap)
|
|
|
|
http.HandleFunc(imgZpfx, imgServerTxt)
|
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
|
|
|
|
2022-12-08 07:44:09 +00:00
|
|
|
log.Printf("Default Img Type: %v, Geometry: %+v", *defType, defGeom)
|
2020-11-05 16:05:50 +00:00
|
|
|
|
2024-07-09 04:36:32 +00:00
|
|
|
htmlTmpl, err = template.New("wrp.html").Parse(wrpTemplate(*htmFnam))
|
2020-10-29 14:16:14 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2020-11-05 16:05:50 +00:00
|
|
|
|
2022-12-08 09:49:46 +00:00
|
|
|
log.Print("Starting WRP http server")
|
2022-12-08 07:44:09 +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
|
|
|
}
|