69 Commits
4.6.3 ... 4.7.2

Author SHA1 Message Date
Antoni Sawicki
b6e402029a ver bump to 4.7.2 2024-07-09 02:00:45 -07:00
Antoni Sawicki
9f7107c00b add docker clean target 2024-07-09 02:00:34 -07:00
Antoni Sawicki
500ad0d19a use local binaries for local dockerfile 2024-07-09 01:57:51 -07:00
Antoni Sawicki
b5747f52e7 use embedded certs 2024-07-09 01:57:03 -07:00
Antoni Sawicki
9f21d8d06e makefile fixes 2024-07-07 23:38:33 -07:00
Antoni Sawicki
b0f4170c6c minor release with bug fixes 2024-07-07 23:34:12 -07:00
Antoni Sawicki
407907bece add / path prefix 2024-07-02 17:15:46 -07:00
Antoni Sawicki
f838caa328 bugs update 2024-07-02 12:18:51 -07:00
Antoni Sawicki
a8b8a557f7 add error to http status? 2024-07-02 02:57:00 -07:00
Antoni Sawicki
d036914fcb fetch is more fortunate than grab 2024-07-02 02:54:55 -07:00
Antoni Sawicki
701240eb7c process http status code 2024-07-02 02:54:33 -07:00
Antoni Sawicki
1d9af26604 add main todo 2024-07-02 02:38:32 -07:00
Antoni Sawicki
5ed55c387c todo image type based on form value 2024-07-02 02:35:48 -07:00
Antoni Sawicki
0e3192b69f dynamically present content type 2024-07-02 02:32:59 -07:00
Antoni Sawicki
91870f5724 add todo 2024-07-02 02:25:47 -07:00
Antoni Sawicki
c1ffd71e25 use resize.Thumbnail mode instead of calculating size 2024-07-02 02:24:01 -07:00
Antoni Sawicki
26b3c21aa7 remove cache control 2024-07-02 02:23:40 -07:00
Antoni Sawicki
8cec22eb90 add some defensive code for background setting 2024-07-02 02:04:36 -07:00
Antoni Sawicki
e64f413c76 add bgcolor for markdown 2024-07-02 01:42:53 -07:00
Antoni Sawicki
94d0e0d128 add comments todo 2024-07-02 01:23:14 -07:00
Antoni Sawicki
b546b5cbae use flag for image size 2024-07-02 01:21:22 -07:00
Antoni Sawicki
a2a4152b86 fix destination bug 2024-07-02 01:19:19 -07:00
Antoni Sawicki
db77479bad grab image now returns error; delete img src on error 2024-07-02 01:14:24 -07:00
Antoni Sawicki
33436333cf better log entry and todo 2024-07-02 01:06:58 -07:00
Antoni Sawicki
866750700b only resize larger images 2024-07-02 01:05:07 -07:00
Antoni Sawicki
3f445abd14 remove debug print 2024-07-02 00:58:26 -07:00
Antoni Sawicki
d7eb89e135 add embed image support 2024-07-02 00:58:14 -07:00
Antoni Sawicki
bf946f0f63 use prefix const instead of hardcoding 2024-07-02 00:46:24 -07:00
Antoni Sawicki
7673e15b9e add dependencies and goldmark cleanup 2024-07-02 00:36:45 -07:00
Antoni Sawicki
d00b1d5d7f add imgz procesing 2024-07-02 00:36:18 -07:00
Antoni Sawicki
3e8f109edd use HasSuffix to determine content type 2024-07-01 23:15:18 -07:00
Antoni Sawicki
9a0e5809c1 remove seed for random number generator, deprecated 2024-07-01 19:47:52 -07:00
Antoni Sawicki
96acf94521 ver bump 2024-06-24 00:15:30 -07:00
Antoni Sawicki
807373d668 add link to docker 2024-06-22 19:59:11 -07:00
Antoni Sawicki
703e9d0452 fix dockerfile casing 2024-06-22 19:57:11 -07:00
Antoni Sawicki
f7799ecba5 rename funcs for readability 2024-06-22 19:43:53 -07:00
Antoni Sawicki
37562cce23 add txt/reader mode to readme 2024-06-22 19:43:33 -07:00
Antoni Sawicki
631971b0bb printf instead of fata error 2024-06-22 15:40:37 -07:00
Antoni Sawicki
23a7ba5cbf readme update 2024-06-22 15:38:47 -07:00
Antoni Sawicki
2733a84d2d do not display image properties when there is no image 2024-06-22 15:33:27 -07:00
Antoni Sawicki
abea41f498 flag for txt by default 2024-06-22 15:18:03 -07:00
Antoni Sawicki
da524ff275 use goldmark instead of gomarkdown 2024-06-22 15:17:26 -07:00
Antoni Sawicki
b92478fd6a add todo for goldmark 2024-06-22 12:14:39 -07:00
Antoni Sawicki
ee0e72f246 use plugins for markdown 2024-06-22 11:53:20 -07:00
Antoni Sawicki
a3c06d346c use html-to-markdown module instead of api 2024-06-22 02:42:53 -07:00
Antoni Sawicki
0065fdc2c7 asciify utf8 2024-06-22 01:48:46 -07:00
Antoni Sawicki
22efadf9cf remove commented out code 2024-06-22 01:22:39 -07:00
Antoni Sawicki
4070fa7a53 add background 2024-06-22 01:21:58 -07:00
Antoni Sawicki
41a14ad590 properly delete img nodes 2024-06-22 01:21:44 -07:00
Antoni Sawicki
a212a14bd8 no extensions 2024-06-21 01:31:04 -07:00
Antoni Sawicki
5a86c32d9a tidy 2024-06-21 01:30:48 -07:00
Antoni Sawicki
55f3c852f9 prefix links and remove images 2024-06-21 01:04:30 -07:00
Antoni Sawicki
79c86a7056 initial markdown support 2024-06-19 23:37:44 -07:00
Antoni Sawicki
19f4be3ac1 format fixes 2024-06-19 00:35:12 -07:00
Antoni Sawicki
6ffdf1fd7f lend at eof 2024-06-16 16:25:43 -07:00
Antoni Sawicki
0796c86e73 add browsh 2024-06-16 16:24:41 -07:00
Antoni Sawicki
d8b4f160ac use golang builder 2024-06-10 01:58:38 -07:00
Antoni Sawicki
93e9fddca7 add info about proxy 2024-06-10 01:39:37 -07:00
Antoni Sawicki
e13a06610b readme update 2024-06-10 01:25:50 -07:00
Antoni Sawicki
5b28f1d913 add aws apprunner cli example 2024-06-10 01:21:41 -07:00
Antoni Sawicki
da5246b9df add info on aws apprunner 2024-06-10 01:16:04 -07:00
Antoni Sawicki
8a484813c3 add related section 2024-06-10 00:24:56 -07:00
Antoni Sawicki
75233ccb97 Merge pull request #113 from luRaichu/master
Add PgUp, PgDn, and Select All buttons
2024-05-24 01:19:23 -07:00
Antoni Sawicki
04954f8be6 latest 2024-05-23 13:25:59 -07:00
Antoni Sawicki
884374edcd remove gcr.io 2024-05-23 01:05:26 -07:00
Antoni Sawicki
ba522e3a86 readme update 2024-05-23 00:55:41 -07:00
Antoni Sawicki
e05135e433 fix makefile 2024-05-23 00:39:06 -07:00
Antoni Sawicki
67d550672b legit update 2024-05-23 00:36:58 -07:00
luRaichu
b937bea370 Add PgUp, PgDn, and Select All buttons 2023-10-18 18:30:40 -04:00
9 changed files with 504 additions and 72 deletions

View File

@@ -1,6 +1,14 @@
FROM golang AS builder
WORKDIR /src
RUN git clone https://github.com/tenox7/wrp.git
WORKDIR /src/wrp
RUN go mod download
ARG TARGETARCH
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} go build -o /wrp-${TARGETARCH}
FROM chromedp/headless-shell
ARG TARGETARCH
ADD wrp-${TARGETARCH}-linux /wrp
COPY --from=builder /wrp-${TARGETARCH} /wrp
ENTRYPOINT ["/wrp"]
ENV PATH="/headless-shell:${PATH}"
LABEL maintainer="as@tenoware.com"

6
Dockerfile.local Normal file
View File

@@ -0,0 +1,6 @@
FROM chromedp/headless-shell
ARG TARGETARCH
ADD wrp-${TARGETARCH}-linux /wrp
ENTRYPOINT ["/wrp"]
ENV PATH="/headless-shell:${PATH}"
LABEL maintainer="as@tenoware.com"

View File

@@ -1,27 +1,28 @@
all: wrp
wrp: wrp.go
go build wrp.go
go build -a
cross:
GOOS=linux GOARCH=amd64 go build -a -o wrp-amd64-linux wrp.go
GOOS=linux GOARCH=arm64 go build -a -o wrp-arm64-linux wrp.go
GOOS=freebsd GOARCH=amd64 go build -a -o wrp-amd64-freebsd wrp.go
GOOS=openbsd GOARCH=amd64 go build -a -o wrp-amd64-openbsd wrp.go
GOOS=darwin GOARCH=amd64 go build -a -o wrp-amd64-macos wrp.go
GOOS=darwin GOARCH=arm64 go build -a -o wrp-arm64-macos wrp.go
GOOS=windows GOARCH=amd64 go build -a -o wrp-amd64-windows.exe wrp.go
GOOS=linux GOARCH=arm go build -a -o wrp-arm-linux wrp.go
GOOS=linux GOARCH=arm64 go build -a -o wrp-arm64-linux wrp.go
GOOS=linux GOARCH=amd64 go build -a -o wrp-amd64-linux
GOOS=freebsd GOARCH=amd64 go build -a -o wrp-amd64-freebsd
GOOS=openbsd GOARCH=amd64 go build -a -o wrp-amd64-openbsd
GOOS=darwin GOARCH=amd64 go build -a -o wrp-amd64-macos
GOOS=darwin GOARCH=arm64 go build -a -o wrp-arm64-macos
GOOS=windows GOARCH=amd64 go build -a -o wrp-amd64-windows.exe
GOOS=linux GOARCH=arm go build -a -o wrp-arm-linux
GOOS=linux GOARCH=arm64 go build -a -o wrp-arm64-linux
docker: wrp
GOOS=linux GOARCH=amd64 go build -a -o wrp-amd64-linux wrp.go
GOOS=linux GOARCH=arm64 go build -a -o wrp-arm64-linux wrp.go
docker-local:
GOOS=linux GOARCH=amd64 go build -a -o wrp-amd64-linux
GOOS=linux GOARCH=arm64 go build -a -o wrp-arm64-linux
docker buildx build --platform linux/amd64,linux/arm64 -t tenox7/wrp:latest -f Dockerfile.local --load .
docker-push:
docker buildx build --platform linux/amd64,linux/arm64 -t tenox7/wrp:latest --push .
gcrio:
docker tag tenox7/wrp:latest gcr.io/tenox7/wrp
docker push gcr.io/tenox7/wrp
docker-clean:
docker buildx prune -a -f
clean:
rm -rf wrp-* wrp

102
README.md
View File

@@ -1,12 +1,14 @@
# WRP - Web Rendering Proxy
A browser-in-browser "proxy" server that allows to use historical / vintage web browsers on the modern web. It works by rendering a web page in to a GIF or PNG image with clickable imagemap.
A browser-in-browser "proxy" server that allows to use historical / vintage web browsers on the modern web. It works by rendering a web page in to a GIF or PNG image with clickable imagemap. Optionally by converting modern HTML in to Markdown and back to a simple HTML.
![Internet Explorer 1.5 doing Gmail](wrp.png)
## Usage Instructions
* [Download a WRP binary](https://github.com/tenox7/wrp/releases/) and run it on a machine that will become your WRP gateway/server. This should be modern hardware, OS and Google Chrome / Chromium Browser is required to be preinstalled. Do not try to run WRP on an old machine like Windows XP or 98.
### Image Map Mode
* [Download a WRP binary](https://github.com/tenox7/wrp/releases/) run it on a machine that will become your WRP gateway/server. This should be modern hardware and OS. Google Chrome / Chromium Browser is required to be preinstalled. Do not try to run WRP on an old machine like Windows XP or 98.
* Make sure you have disabled firewall or open port WRP is listening on (by default 8080).
* Point your legacy browser to `http://address:port` of the WRP server. Do not set or use it as a "proxy server".
* Type a search string or a full http/https URL and click **Go**.
@@ -20,6 +22,13 @@ A browser-in-browser "proxy" server that allows to use historical / vintage web
* Prefer PNG over GIF if your browser supports it. PNG is much faster, whereas GIF requires a lot of additional processing on both client and server to encode/decode. Jpeg encoding is also quite fast.
* GIF images are by default encoded with 216 colors, "web safe" palette. This uses an ultra fast but not very accurate color mapping algorithm. If you want better color representation switch to 256 color mode.
### Simple HTML mode
Also known as **Reader** mode. **NEW!**
* Select image type to `TXT`. WRP will convert the image in to Markdown, then render it back in to a simplified HTML.
* If you want to always start in txt mode specify `-t txt` flag.
## UI explanation
The first unnamed input box is either search (google) or URL starting with http/https
@@ -52,41 +61,67 @@ used with PNG and lots of memory on a client side.
**< ^ v >** are arrow keys, typically for navigating a map, buggy.
### UI Customization
### UI Customization
WRP supports customizing it's own UI using HTML Template file. Download [wrp.html](wrp.html) place in the same directory with wrp binary customize it to your liking.
## Docker
https://hub.docker.com/r/tenox7/wrp
```shell
$ docker run -d -p 80:8080 tenox7/wrp
$ docker run -d --rm -p 8080:8080 tenox7/wrp:latest
```
## AWS
It's possible to run WRP on AWS App Runner.
First you need to upload the Docker image to ECR - [Instructions](https://docs.aws.amazon.com/AmazonECR/latest/userguide/docker-push-ecr-image.html).
Create App Runner service using the uploaded image using the AWS Console or CLI.
[AWS Console](https://console.aws.amazon.com/apprunner/home#/create)
```shell
aws apprunner create-service --service-name my-app-runner-service --source-configuration '{
"ImageRepository": {
"ImageIdentifier": "<account_id>.dkr.ecr.<region>.amazonaws.com/wrp:latest",
"ImageRepositoryType": "ECR",
"ImageConfiguration": {"Port": "8000"},
"AutoDeploymentsEnabled": true
}
}' --instance-configuration '{
"Cpu": "1024",
"Memory": "2048",
"InstanceRoleArn": "arn:aws:iam::<account_id>:role/AppRunnerECRAccessRole"
}'
```
## Azure Container Instances
[Azure Console](https://portal.azure.com/#create/Microsoft.ContainerInstances)
CLI:
```shell
$ az container create --resource-group wrp --name wrp --image tenox7/wrp:latest --cpu 1 --memory 2 --ports 80 --protocol tcp --os-type Linux --ip-address Public --command-line '/wrp -l :80 -t png -g 1280x0x256'
```
## Google Cloud Run
```shell
$ gcloud run deploy --platform managed --image=gcr.io/tenox7/wrp:latest --memory=2Gi --args='-t=png','-g=1280x0x256'
$ gcloud run deploy --platform managed --image=tenox7/wrp:latest --memory=2Gi --args='-t=png','-g=1280x0x256'
```
Or from [Gcloud Console](https://console.cloud.google.com/run). Use `gcr.io/tenox7/wrp:latest` as container image URL.
Unfortunately Google Cloud Run forces you to use HTTPS, which likely won't work with old browsers.
Note that unfortunately GCR forces https. Your browser support of encryption protocols and certification authorities will vary.
## Azure Container Instances
```shell
$ az container create --resource-group wrp --name wrp --image gcr.io/tenox7/wrp:latest --cpu 1 --memory 2 --ports 80 --protocol tcp --os-type Linux --ip-address Public --command-line '/wrp -l :80 -t png -g 1280x0x256'
```
Or from the [Azure Console](https://portal.azure.com/#create/Microsoft.ContainerInstances). Use `gcr.io/tenox7/wrp:latest` or `tenox7/wrp:latest` for image name.
Fortunately ACI allows port 80 without encryption.
## Flags
```text
-l listen address:port (default :8080)
-t image type gif, png or jpg (default gif)
-t image type gif, png or jpg (default gif) also txt for simple html/reader mode
-g image geometry, WxHxC, height can be 0 for unlimited (default 1152x600x216)
C (number of colors) is only used for GIF
-q Jpeg image quality, default 80%
@@ -103,7 +138,7 @@ Fortunately ACI allows port 80 without encryption.
* Server/Gateway requires modern hardware and operating system that is supported by [Go language](https://github.com/golang/go/wiki/MinimumRequirements) and Chrome/Chromium Browser, which must be installed.
* Client Browser needs to support `HTML FORMs` and `ISMAP`. Typically [Mosaic 2.0](http://www.ncsa.illinois.edu/enabling/mosaic/versions) would be minimum version for forms. However ISMAP was supported since 0.6B, so if you manually enter url using `?url=...`, you can use the earlier version.
## Troubleshooting
## FAQ
### I can't get it to run
@@ -125,16 +160,26 @@ Obtain your regular desktop browser user agent and specify it as the flag. For e
$ wrp -ua="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
```
### Why is WRP called "proxy" when it's not
WRP originally started as true http proxy. However this stopped working because the whole internet is now encrypted thanks to [Let's Encrypt](https://en.wikipedia.org/wiki/Let%27s_Encrypt). Legacy browsers do not support modern SSL/TLS certs as well as [HTTP CONNECT](https://en.wikipedia.org/wiki/HTTP_tunnel#HTTP_CONNECT_method) so this mode had to be disabled.
### Will you support http proxy mode in future?
Some efforts are under way but it's very [difficult](https://en.wikipedia.org/wiki/HTTP_tunnel#HTTP_CONNECT_method) to do it correctly and the priority is rather low.
## History
* Version 1.0 (2014) started as a *cgi-bin* script, adaptation of `webkit2png.py` and `pcidade.py`, [blog post](https://virtuallyfun.com/2014/03/03/surfing-modern-web-with-ancient-browsers/).
* Version 2.0 became a stand alone http-proxy server, supporting both Linux and MacOS, [another post](https://virtuallyfun.com/wordpress/2014/03/11/web-rendering-proxy-update//).
* In 2016 thanks to EFF/Certbot the whole internet migrated to HTTPS/SSL/TLS and WRP largely stopped working. Python code became unmaintainable and there was no easy way to make it work on Windows, even under WSL.
* In 2016 thanks to [Let's Encrypt](https://en.wikipedia.org/wiki/Let%27s_Encrypt) the whole internet migrated to HTTPS/SSL/TLS and WRP largely stopped working. Python code became unmaintainable and there was no easy way to make it work on Windows, even under WSL.
* Version 3.0 (2019) has been rewritten in [Go](https://golang.org/) using [Chromedp](https://github.com/chromedp) as browser-in-browser instead of http-proxy. The initial version was [less than 100 lines of code](https://gist.github.com/tenox7/b0f03c039b0a8b67f6c1bf47e2dd0df0).
* Version 4.0 has been completely refactored to use mouse clicks via imagemap instead parsing a href nodes.
* Version 4.0 has been completely refactored to use mouse clicks via imagemap instead parsing a href nodes.
* Version 4.1 added sending keystrokes in to input boxes. You can now login to Gmail. Also now runs as a Docker container and on Cloud Run/Azure Containers.
* Version 4.5 introduces rendering whole pages in to a single tall image with client scrolling.
* Version 4.6 adds blazing fast gif encoding by [Hill Ma](https://github.com/mahiuchun).
* Version 4.6.3 adds arm64 / aarch64 Docker container support - you can run it on Raspberry PI!
* Version 4.7 add simple html aka reader aka text mode.
## Credits
@@ -145,8 +190,19 @@ $ wrp -ua="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (K
* Thanks to [Hill Ma](https://github.com/mahiuchun) for ultra fast gif encoding algorithm
* Historical Python/Webkit versions and prior art can be seen in [wrp-old](https://github.com/tenox7/wrp-old) repo
## Related
You may also be interested in:
* [VncFox](https://github.com/tenox7/vncfox)
* [Browservice](https://github.com/ttalvitie/browservice)
* [Browsh](https://github.com/browsh-org/browsh)
## Legal Stuff
License: Apache 2.0
Copyright (c) 2013-2018 Antoni Sawicki
```text
License: Apache 2.0
Copyright (c) 2013-2024 Antoni Sawicki
Copyright (c) 2019-2024 Google LLC
```

9
go.mod
View File

@@ -3,18 +3,27 @@ module github.com/tenox7/wrp
go 1.21.5
require (
github.com/JohannesKaufmann/html-to-markdown v1.6.0
github.com/MaxHalford/halfgone v0.0.0-20171017091812-482157b86ccb
github.com/breml/rootcerts v0.2.17
github.com/chromedp/cdproto v0.0.0-20240519224452-66462be74baa
github.com/chromedp/chromedp v0.9.5
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
github.com/soniakeys/quant v1.0.0
github.com/yuin/goldmark v1.7.2
golang.org/x/image v0.18.0
)
require (
github.com/PuerkitoBio/goquery v1.9.2 // indirect
github.com/andybalholm/cascadia v1.3.2 // indirect
github.com/chromedp/sysutil v1.0.0 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

100
go.sum
View File

@@ -1,40 +1,120 @@
github.com/JohannesKaufmann/html-to-markdown v1.6.0 h1:04VXMiE50YYfCfLboJCLcgqF5x+rHJnb1ssNmqpLH/k=
github.com/JohannesKaufmann/html-to-markdown v1.6.0/go.mod h1:NUI78lGg/a7vpEJTz/0uOcYMaibytE4BUOQS8k78yPQ=
github.com/MaxHalford/halfgone v0.0.0-20171017091812-482157b86ccb h1:YQ+d0g0P0F/06oDoeEgDHeZCIrnKgLxXcqYOpe8sTuU=
github.com/MaxHalford/halfgone v0.0.0-20171017091812-482157b86ccb/go.mod h1:J86XzS1wgzJPjpQmpriJ+SetP17JSQUd9l+HWQK86jA=
github.com/chromedp/cdproto v0.0.0-20231011050154-1d073bb38998/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
github.com/chromedp/cdproto v0.0.0-20231205062650-00455a960d61 h1:XD280QPATe9jaz20dylKe3vBsNcH1w3mkssGY0lidn8=
github.com/chromedp/cdproto v0.0.0-20231205062650-00455a960d61/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE=
github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk=
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
github.com/breml/rootcerts v0.2.17 h1:0/M2BE2Apw0qEJCXDOkaiu7d5Sx5ObNfe1BkImJ4u1I=
github.com/breml/rootcerts v0.2.17/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
github.com/chromedp/cdproto v0.0.0-20240519224452-66462be74baa h1:T3Ho4BWIkoEoMPCj90W2HIPF/k56qk4JWzTs6JUBxVw=
github.com/chromedp/cdproto v0.0.0-20240519224452-66462be74baa/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
github.com/chromedp/chromedp v0.9.3 h1:Wq58e0dZOdHsxaj9Owmfcf+ibtpYN1N0FWVbaxa/esg=
github.com/chromedp/chromedp v0.9.3/go.mod h1:NipeUkUcuzIdFbBP8eNNvl9upcceOfWzoJn6cRe4ksA=
github.com/chromedp/chromedp v0.9.5 h1:viASzruPJOiThk7c5bueOUY91jGLJVximoEMGoH93rg=
github.com/chromedp/chromedp v0.9.5/go.mod h1:D4I2qONslauw/C7INoCir1BJkSwBYMyZgx8X276z3+Y=
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.3.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/gobwas/ws v1.3.1 h1:Qi34dfLMWJbiKaNbDVzM9x27nZBjmkaW6i4+Ku+pGVU=
github.com/gobwas/ws v1.3.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/gobwas/ws v1.3.2/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y=
github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/soniakeys/quant v1.0.0 h1:N1um9ktjbkZVcywBVAAYpZYSHxEfJGzshHCxx/DaI0Y=
github.com/soniakeys/quant v1.0.0/go.mod h1:HI1k023QuVbD4H8i9YdfZP2munIHU4QpjsImz6Y6zds=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark v1.7.2 h1:NjGd7lO7zrUn/A7eKwn5PEOt4ONYGqpxSEeZuduvgxc=
github.com/yuin/goldmark v1.7.2/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

221
txt.go Normal file
View File

@@ -0,0 +1,221 @@
package main
// TODO:
// - image type based on form value
// - also size and quality
// - non overlaping image names atomic.int etc
// - garbage collector / delete old images from map
// - add referer header
// - svg support
// - BOG: DomainFromURL always prefixes with http instead of https
// reproduces on vsi vms docs
// - BUG: markdown table errors
// reproduces on hacker news
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"image"
"image/gif"
"image/jpeg"
"image/png"
"io"
"log"
"math/rand"
"net/http"
"strconv"
"strings"
"sync"
"time"
h2m "github.com/JohannesKaufmann/html-to-markdown"
"github.com/JohannesKaufmann/html-to-markdown/plugin"
"github.com/nfnt/resize"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
"golang.org/x/image/webp"
)
var imgStor imageStore
const imgZpfx = "/imgz/"
func init() {
imgStor.img = make(map[string]imageContainer)
// TODO: add garbage collector
// think about how to remove old images
// if removed from cache how to download them later if a browser goes back?
// browser should cache on it's own... but it may request it, what then?
}
type imageContainer struct {
data []byte
url string
added time.Time
}
type imageStore struct {
img map[string]imageContainer
sync.Mutex
}
func (i *imageStore) add(id, url string, img []byte) {
i.Lock()
defer i.Unlock()
i.img[id] = imageContainer{data: img, url: url, added: time.Now()}
}
func (i *imageStore) get(id string) ([]byte, error) {
i.Lock()
defer i.Unlock()
img, ok := i.img[id]
if !ok {
return nil, errors.New("not found")
}
return img.data, nil
}
func (i *imageStore) del(id string) {
i.Lock()
defer i.Unlock()
delete(i.img, id)
}
func fetchImage(id, url string) error {
log.Printf("Downloading IMGZ URL=%q for ID=%q", url, id)
var img []byte
var err error
switch url[:4] {
case "http":
r, err := http.Get(url) // TODO: possibly set a header "referer" here
if err != nil {
return fmt.Errorf("Error downloading %q: %v", url, err)
}
if r.StatusCode != http.StatusOK {
return fmt.Errorf("Error %q HTTP Status Code: %v", url, r.StatusCode)
}
defer r.Body.Close()
img, err = io.ReadAll(r.Body)
if err != nil {
return fmt.Errorf("Error reading %q: %v", url, err)
}
case "data":
idx := strings.Index(url, ",")
if idx < 1 {
return fmt.Errorf("image is embeded but unable to find coma: %q", url)
}
img, err = base64.StdEncoding.DecodeString(url[idx+1:])
if err != nil {
return fmt.Errorf("error decoding image from url embed: %q: %v", url, err)
}
}
gif, err := smallGif(img)
if err != nil {
return fmt.Errorf("Error scaling down image: %v", err)
}
imgStor.add(id, url, gif)
return nil
}
type astTransformer struct{}
func (t *astTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
if link, ok := n.(*ast.Link); ok && entering {
link.Destination = append([]byte("/?t=txt&url="), link.Destination...)
}
if img, ok := n.(*ast.Image); ok && entering {
// TODO: dynamic extension based on form value
id := fmt.Sprintf("txt%05d.gif", rand.Intn(99999)) // BUG: atomic.AddInt64 or something that ever increases - time based?
err := fetchImage(id, string(img.Destination)) // TODO: use goroutines with waitgroup
if err != nil {
log.Print(err)
n.Parent().RemoveChildren(n)
return ast.WalkContinue, nil
}
img.Destination = []byte(imgZpfx + id)
}
return ast.WalkContinue, nil
})
}
func (rq *wrpReq) captureMarkdown() {
log.Printf("Processing Markdown conversion request for %v", rq.url)
// TODO: bug - DomainFromURL always prefixes with http:// instead of https
// this causes issues on some websites, fix or write a smarter DomainFromURL
c := h2m.NewConverter(h2m.DomainFromURL(rq.url), true, nil)
c.Use(plugin.GitHubFlavored())
md, err := c.ConvertURL(rq.url) // We could also get inner html from chromedp
if err != nil {
http.Error(rq.w, err.Error(), http.StatusInternalServerError)
return
}
log.Printf("Got %v bytes md from %v", len(md), rq.url)
t := &astTransformer{}
gm := goldmark.New(
goldmark.WithExtensions(extension.GFM),
goldmark.WithParserOptions(parser.WithASTTransformers(util.Prioritized(t, 100))),
)
var ht bytes.Buffer
err = gm.Convert([]byte(md), &ht)
if err != nil {
http.Error(rq.w, err.Error(), http.StatusInternalServerError)
return
}
log.Printf("Rendered %v bytes html for %v", len(ht.String()), rq.url)
rq.printHTML(printParams{
text: string(asciify([]byte(ht.String()))),
bgColor: "#FFFFFF",
})
}
func imgServerZ(w http.ResponseWriter, r *http.Request) {
log.Printf("%s IMGZ Request for %s", r.RemoteAddr, r.URL.Path)
id := strings.Replace(r.URL.Path, imgZpfx, "", 1)
img, err := imgStor.get(id)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Printf("%s IMGZ error for %s: %v", r.RemoteAddr, r.URL.Path, err)
return
}
imgStor.del(id)
w.Header().Set("Content-Type", http.DetectContentType(img))
w.Header().Set("Content-Length", strconv.Itoa(len(img)))
w.Write(img)
w.(http.Flusher).Flush()
}
// TODO set JPG/GIF/PNG type based on form...
func smallGif(src []byte) ([]byte, error) {
t := http.DetectContentType(src)
var err error
var img image.Image
switch t {
case "image/png":
img, err = png.Decode(bytes.NewReader(src))
case "image/gif":
img, err = gif.Decode(bytes.NewReader(src))
case "image/jpeg":
img, err = jpeg.Decode(bytes.NewReader(src))
case "image/webp":
img, err = webp.Decode(bytes.NewReader(src))
default: // TODO: also add svg
err = errors.New("unknown content type: " + t)
}
if err != nil {
return nil, fmt.Errorf("image decode problem: %v", err)
}
img = resize.Thumbnail(uint(*txtImgSize), uint(*txtImgSize), img, resize.NearestNeighbor)
var gifBuf bytes.Buffer
err = gif.Encode(&gifBuf, gifPalette(img, 216), &gif.Options{})
if err != nil {
return nil, fmt.Errorf("gif encode problem: %v", err)
}
return gifBuf.Bytes(), nil
}

78
wrp.go
View File

@@ -13,7 +13,6 @@ import (
"embed"
"flag"
"fmt"
"html/template"
"image"
"image/color/palette"
"image/gif"
@@ -33,23 +32,27 @@ import (
"strconv"
"strings"
"syscall"
"text/template"
"time"
"github.com/MaxHalford/halfgone"
_ "github.com/breml/rootcerts"
"github.com/chromedp/cdproto/css"
"github.com/chromedp/cdproto/emulation"
"github.com/chromedp/cdproto/input"
"github.com/chromedp/cdproto/page"
"github.com/chromedp/chromedp"
"github.com/soniakeys/quant/median"
)
const version = "4.6.3"
const version = "4.7.2"
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")
defType = flag.String("t", "gif", "Image type: png|gif|jpg|txt")
txtImgSize = flag.Int("ts", 200, "txt mode image size") // make it default, this should come from the from
jpgQual = flag.Int("q", 80, "Jpeg image quality, default 80%")
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")
@@ -89,6 +92,7 @@ type uiData struct {
ImgHeight int
MapURL string
PageHeight string
TeXT string
}
// Parameters for HTML print function
@@ -100,6 +104,7 @@ type printParams struct {
mapURL string
imgWidth int
imgHeight int
text string
}
// WRP Request
@@ -146,6 +151,7 @@ func (rq *wrpReq) parseForm() {
case "png":
case "gif":
case "jpg":
case "txt":
default:
rq.imgType = *defType
}
@@ -158,6 +164,9 @@ func (rq *wrpReq) printHTML(p printParams) {
rq.w.Header().Set("Expires", "-1")
rq.w.Header().Set("Pragma", "no-cache")
rq.w.Header().Set("Content-Type", "text/html")
if p.bgColor == "" {
p.bgColor = "#FFFFFF"
}
data := uiData{
Version: version,
URL: rq.url,
@@ -173,10 +182,11 @@ func (rq *wrpReq) printHTML(p printParams) {
ImgURL: p.imgURL,
MapURL: p.mapURL,
PageHeight: p.pageHeight,
TeXT: p.text,
}
err := htmlTmpl.Execute(rq.w, data)
if err != nil {
log.Fatal(err)
fmt.Fprintf(rq.w, "Error: %v", err)
}
}
@@ -209,6 +219,12 @@ func (rq *wrpReq) action() chromedp.Action {
return chromedp.KeyEvent("\u0301")
case ">":
return chromedp.KeyEvent("\u0303")
case "Up":
return chromedp.KeyEvent("\u0308")
case "Dn":
return chromedp.KeyEvent("\u0307")
case "All": // Select all
return chromedp.KeyEvent("a", chromedp.KeyModifiers(input.ModifierCtrl))
}
}
// Keys
@@ -217,7 +233,7 @@ func (rq *wrpReq) action() chromedp.Action {
return chromedp.KeyEvent(rq.keys)
}
// Navigate to URL
log.Printf("%s Processing Capture Request for %s\n", rq.r.RemoteAddr, rq.url)
log.Printf("%s Processing Navigate Request for %s\n", rq.r.RemoteAddr, rq.url)
return chromedp.Navigate(rq.url)
}
@@ -298,10 +314,10 @@ func gifPalette(i image.Image, n int64) image.Image {
return i
}
// Capture currently rendered web page to an image and fake ISMAP
func (rq *wrpReq) capture() {
func (rq *wrpReq) captureImage() {
var styles []*css.ComputedStyleProperty
var r, g, b int
var bgColorSet bool
var h int64
var pngCap []byte
chromedp.Run(ctx,
@@ -316,12 +332,20 @@ func (rq *wrpReq) capture() {
return nil
}),
)
for _, style := range styles {
if style.Name == "background-color" {
fmt.Sscanf(style.Value, "rgb(%d,%d,%d)", &r, &g, &b)
}
}
log.Printf("%s Landed on: %s, Height: %v\n", rq.r.RemoteAddr, rq.url, h)
for _, style := range styles {
if style.Name != "background-color" {
continue
}
fmt.Sscanf(style.Value, "rgb(%d,%d,%d)", &r, &g, &b)
bgColorSet = true
break
}
if !bgColorSet {
r = 255
g = 255
b = 255
}
height := int64(float64(rq.height) / rq.zoom)
if rq.height == 0 && h > 0 {
height = h + 30
@@ -400,6 +424,18 @@ func (rq *wrpReq) capture() {
log.Printf("%s Done with capture for %s\n", rq.r.RemoteAddr, rq.url)
}
func asciify(s []byte) []byte {
a := make([]byte, len(s))
for i := 0; i < len(s); i++ {
if s[i] > 127 {
a[i] = '.'
continue
}
a[i] = s[i]
}
return a
}
// Process HTTP requests to WRP '/' url
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)
@@ -413,7 +449,11 @@ func pageServer(w http.ResponseWriter, r *http.Request) {
return
}
rq.navigate() // TODO: if error from navigate do not capture
rq.capture()
if rq.imgType == "txt" {
rq.captureMarkdown()
return
}
rq.captureImage()
}
// Process HTTP requests to ISMAP '/map/' url
@@ -442,7 +482,7 @@ func mapServer(w http.ResponseWriter, r *http.Request) {
return
}
rq.navigate() // TODO: if error from navigate do not capture
rq.capture()
rq.captureImage()
}
// Process HTTP requests for images '/img/' url
@@ -458,11 +498,11 @@ func imgServer(w http.ResponseWriter, r *http.Request) {
defer delete(img, r.URL.Path)
}
switch {
case strings.HasPrefix(r.URL.Path, ".gif"):
case strings.HasSuffix(r.URL.Path, ".gif"):
w.Header().Set("Content-Type", "image/gif")
case strings.HasPrefix(r.URL.Path, ".png"):
case strings.HasSuffix(r.URL.Path, ".png"):
w.Header().Set("Content-Type", "image/png")
case strings.HasPrefix(r.URL.Path, ".jpg"):
case strings.HasSuffix(r.URL.Path, ".jpg"):
w.Header().Set("Content-Type", "image/jpeg")
}
w.Header().Set("Content-Length", strconv.Itoa(len(imgBuf.Bytes())))
@@ -552,6 +592,7 @@ func main() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
flag.Parse()
log.Printf("Web Rendering Proxy Version %s (%v)\n", version, runtime.GOARCH)
log.Printf("Using embedded ca-certs from github.com/breml/rootcerts")
log.Printf("Args: %q", os.Args)
if len(os.Getenv("PORT")) > 0 {
*addr = ":" + os.Getenv(("PORT"))
@@ -576,8 +617,6 @@ func main() {
ctx, cncl = chromedp.NewContext(actx)
defer cncl()
rand.Seed(time.Now().UnixNano())
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
@@ -592,6 +631,7 @@ func main() {
http.HandleFunc("/", pageServer)
http.HandleFunc("/map/", mapServer)
http.HandleFunc("/img/", imgServer)
http.HandleFunc(imgZpfx, imgServerZ)
http.HandleFunc("/shutdown/", haltServer)
http.HandleFunc("/favicon.ico", http.NotFound)

View File

@@ -6,9 +6,12 @@
<FORM ACTION="/" METHOD="POST">
<INPUT TYPE="TEXT" NAME="url" VALUE="{{.URL}}" SIZE="20">
<INPUT TYPE="SUBMIT" VALUE="Go">
{{ if ne .ImgType "txt" }}
<INPUT TYPE="SUBMIT" NAME="Fn" VALUE="Bk">
<INPUT TYPE="SUBMIT" NAME="Fn" VALUE="St">
<INPUT TYPE="SUBMIT" NAME="Fn" VALUE="Re">
<INPUT TYPE="SUBMIT" NAME="Fn" VALUE="Up">
<INPUT TYPE="SUBMIT" NAME="Fn" VALUE="Dn">
W <INPUT TYPE="TEXT" NAME="w" VALUE="{{.Width}}" SIZE="4">
H <INPUT TYPE="TEXT" NAME="h" VALUE="{{.Height}}" SIZE="4">
Z <SELECT NAME="z">
@@ -20,11 +23,14 @@
<OPTION VALUE="1.2" {{ if eq .Zoom 1.2}}SELECTED{{end}}>1.2 x</OPTION>
<OPTION VALUE="1.3" {{ if eq .Zoom 1.3}}SELECTED{{end}}>1.3 x</OPTION>
</SELECT>
{{ end }}
T <SELECT NAME="t">
<OPTION VALUE="png" {{ if eq .ImgType "png"}}SELECTED{{end}}>PNG</OPTION>
<OPTION VALUE="gif" {{ if eq .ImgType "gif"}}SELECTED{{end}}>GIF</OPTION>
<OPTION VALUE="jpg" {{ if eq .ImgType "jpg"}}SELECTED{{end}}>JPG</OPTION>
<OPTION VALUE="txt" {{ if eq .ImgType "txt"}}SELECTED{{end}}>TXT</OPTION>
</SELECT>
{{ if ne .ImgType "txt" }}
C <SELECT NAME="c">
<OPTION VALUE="256" {{ if eq .NColors 256}}SELECTED{{end}}>256</OPTION>
<OPTION VALUE="216" {{ if eq .NColors 216}}SELECTED{{end}}>216</OPTION>
@@ -35,11 +41,13 @@
</SELECT>
K <INPUT TYPE="TEXT" NAME="k" VALUE="" SIZE="4">
<INPUT TYPE="SUBMIT" NAME="Fn" VALUE="Bs">
<INPUT TYPE="SUBMIT" NAME="Fn" VALUE="Rt"><!--
<INPUT TYPE="SUBMIT" NAME="Fn" VALUE="Rt">
<INPUT TYPE="SUBMIT" NAME="Fn" VALUE="All"><!--
<INPUT TYPE="SUBMIT" NAME="Fn" VALUE="&lt;">
<INPUT TYPE="SUBMIT" NAME="Fn" VALUE="^">
<INPUT TYPE="SUBMIT" NAME="Fn" VALUE="v">
<INPUT TYPE="SUBMIT" NAME="Fn" VALUE="&gt;" SIZE="1">-->
{{ end }}
</FORM>
<BR>
{{if .ImgURL}}
@@ -48,11 +56,14 @@
</A>
<P>
{{end}}
{{.TeXT}}
<FONT SIZE="-2">
<A HREF="/?url=https://github.com/tenox7/wrp/&w={{.Width}}&h={{.Height}}&s={{printf "%.1f" .Zoom}}&c={{.NColors}}&t={{.ImgType}}">Web Rendering Proxy {{.Version}}</A> |
<A HREF="/shutdown/">Shutdown WRP</A> |
{{ if ne .ImgType "txt" }}
<A HREF="/">Page Height: {{.PageHeight}}</A> |
<A HREF="/">Img Size: {{.ImgSize}}</A>
</FONT>
{{end}}
</FONT>
</BODY>
</HTML>
</HTML>