This allows for easy comparison with QR codes extracted from PDFs: pdfimages -png invoice.pdf invoice && \ zbarimg --raw invoice*.png > code.txt
279 lines
7.1 KiB
Go
279 lines
7.1 KiB
Go
// Copyright 2020 Google LLC
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"flag"
|
|
"fmt"
|
|
"image/png"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/mattn/go-isatty"
|
|
"github.com/stapelberg/qrbill"
|
|
|
|
_ "net/http/pprof"
|
|
)
|
|
|
|
func ifEmpty(form url.Values, key, alternative string) string {
|
|
if len(form[key]) == 0 {
|
|
return alternative
|
|
}
|
|
return form[key][0]
|
|
}
|
|
|
|
func qrchFromRequest(r *http.Request) *qrbill.QRCH {
|
|
return &qrbill.QRCH{
|
|
CdtrInf: qrbill.QRCHCdtrInf{
|
|
IBAN: ifEmpty(r.Form, "criban", "CH0209000000870913543"),
|
|
Cdtr: qrbill.Address{
|
|
AdrTp: qrbill.AddressType(ifEmpty(r.Form, "craddrtype", string(qrbill.AddressTypeStructured))),
|
|
Name: ifEmpty(r.Form, "crname", "Legalize it"),
|
|
StrtNmOrAdrLine1: ifEmpty(r.Form, "craddr1", "Quellenstrasse"),
|
|
BldgNbOrAdrLine2: ifEmpty(r.Form, "craddr2", "25"),
|
|
PstCd: ifEmpty(r.Form, "crpost", "8005"),
|
|
TwnNm: ifEmpty(r.Form, "crcity", "Zürich"),
|
|
Ctry: ifEmpty(r.Form, "crcountry", "CH"),
|
|
},
|
|
},
|
|
CcyAmt: qrbill.QRCHCcyAmt{
|
|
Amt: ifEmpty(r.Form, "amount", ""),
|
|
Ccy: "CHF",
|
|
},
|
|
UltmtDbtr: qrbill.Address{
|
|
AdrTp: qrbill.AddressType(ifEmpty(r.Form, "udaddrtype", string(qrbill.AddressTypeCombined))),
|
|
Name: ifEmpty(r.Form, "udname", "Michael Stapelberg"),
|
|
StrtNmOrAdrLine1: ifEmpty(r.Form, "udaddr1", "Stauffacherstr 42"),
|
|
BldgNbOrAdrLine2: ifEmpty(r.Form, "udaddr2", "8004 Zürich"),
|
|
PstCd: ifEmpty(r.Form, "udpost", ""),
|
|
TwnNm: ifEmpty(r.Form, "udcity", ""),
|
|
Ctry: ifEmpty(r.Form, "udcountry", "CH"),
|
|
},
|
|
RmtInf: qrbill.QRCHRmtInf{
|
|
Tp: "NON", // Reference type
|
|
Ref: "", // Reference
|
|
AddInf: qrbill.QRCHRmtInfAddInf{
|
|
Ustrd: ifEmpty(r.Form, "message", "Spende 420"),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// Overridden in api_gokrazy.go
|
|
var defaultListenAddress = "localhost:9933"
|
|
|
|
func logic() error {
|
|
var listen = flag.String("listen", defaultListenAddress, "[host]:port to listen on")
|
|
flag.Parse()
|
|
|
|
mux := http.NewServeMux()
|
|
|
|
mux.HandleFunc("/qr", func(w http.ResponseWriter, r *http.Request) {
|
|
prefix := "[" + r.RemoteAddr + "]"
|
|
format := r.FormValue("format")
|
|
log.Printf("%s handling request for %s, format=%s", prefix, r.URL.Path, format)
|
|
defer log.Printf("%s request completed (%s)", prefix, r.URL.Path)
|
|
|
|
if format == "" {
|
|
msg := fmt.Sprintf("no ?format= parameter specified. Try %s",
|
|
"http://"+*listen+"/qr?format=html")
|
|
log.Printf("%s %s", prefix, msg)
|
|
http.Error(w, msg, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if format != "png" &&
|
|
format != "svg" &&
|
|
format != "pdf" &&
|
|
format != "txt" &&
|
|
format != "html" &&
|
|
format != "wv" &&
|
|
format != "eps" {
|
|
msg := fmt.Sprintf("format (%q) must be one of png, svg, pdf, eps, txt or html", format)
|
|
log.Printf("%s %s", prefix, msg)
|
|
http.Error(w, msg, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if err := r.ParseForm(); err != nil {
|
|
log.Printf("%s %s", prefix, err)
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
qrch := qrchFromRequest(r)
|
|
|
|
bill, err := qrch.Encode()
|
|
if err != nil {
|
|
log.Printf("%s %s", prefix, err)
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
var b []byte
|
|
switch format {
|
|
case "png":
|
|
code, err := bill.EncodeToImage()
|
|
if err != nil {
|
|
log.Printf("%s %s", prefix, err)
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if err := png.Encode(&buf, code); err != nil {
|
|
log.Printf("%s %s", prefix, err)
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
b = buf.Bytes()
|
|
w.Header().Add("Content-Type", "image/png")
|
|
|
|
case "svg":
|
|
var err error
|
|
b, err = bill.EncodeToSVG()
|
|
if err != nil {
|
|
log.Printf("%s %s", prefix, err)
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Add("Content-Type", "image/svg+xml")
|
|
|
|
case "pdf":
|
|
var err error
|
|
b, err = bill.EncodeToPDF()
|
|
if err != nil {
|
|
log.Printf("%s %s", prefix, err)
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Add("Content-Type", "application/pdf")
|
|
|
|
case "eps":
|
|
var err error
|
|
b, err = bill.EncodeToEPS()
|
|
if err != nil {
|
|
log.Printf("%s %s", prefix, err)
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Add("Content-Type", "image/eps")
|
|
w.Header().Add("Content-Disposition", `attachment; filename="qr.eps"`)
|
|
|
|
case "txt":
|
|
w.Header().Add("Content-Type", "text/plain; charset=utf-8")
|
|
fmt.Fprintf(w, "%s", bill.EncodeToString())
|
|
|
|
case "html":
|
|
debugHTML(w, r, prefix, qrch)
|
|
|
|
case "wv":
|
|
w.Header().Add("Content-Type", "text/html; charset=utf-8")
|
|
|
|
r.URL.Path = "/qr"
|
|
v := r.URL.Query()
|
|
v.Set("format", "png")
|
|
r.URL.RawQuery = v.Encode()
|
|
|
|
fmt.Fprintf(w, `<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<style type="text/css">
|
|
img {
|
|
width: 100vw;
|
|
height: 100vh;
|
|
}
|
|
body {
|
|
margin: 0; padding: 0;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<img src="%s">
|
|
</body>
|
|
</html>`, r.URL.String())
|
|
|
|
}
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
|
|
// […] this alone is the only directive you need in preventing cached
|
|
// responses on modern browsers.
|
|
w.Header().Add("Cache-Control", "no-store")
|
|
|
|
if _, err := io.Copy(w, bytes.NewReader(b)); err != nil {
|
|
log.Printf("%s %s", prefix, err)
|
|
return
|
|
}
|
|
})
|
|
|
|
mux.HandleFunc("/robots.txt", func(w http.ResponseWriter, r *http.Request) {
|
|
io.WriteString(w, `User-agent: *
|
|
Disallow: /
|
|
`)
|
|
})
|
|
|
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/" {
|
|
http.Error(w, "not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
http.Redirect(w, r, "/qr?format=html", http.StatusFound)
|
|
})
|
|
|
|
if flag.NArg() > 0 {
|
|
srv := httptest.NewServer(mux)
|
|
defer srv.Close()
|
|
for _, arg := range flag.Args() {
|
|
u, err := url.Parse(arg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
u.Host = strings.TrimPrefix(srv.URL, "http://")
|
|
resp, err := srv.Client().Get(u.String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ct := resp.Header.Get("Content-Type")
|
|
if !strings.HasPrefix(ct, "text/") &&
|
|
isatty.IsTerminal(os.Stdout.Fd()) {
|
|
fmt.Fprintf(os.Stderr, "not writing raw image data to terminal, did you forget to redirect the output?\n")
|
|
os.Exit(2)
|
|
}
|
|
if _, err := io.Copy(os.Stdout, resp.Body); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
log.Printf("QR Bill generation URL: http://%s/qr?format=html", *listen)
|
|
return http.ListenAndServe(*listen, mux)
|
|
}
|
|
|
|
func main() {
|
|
if err := logic(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|