From a82765244e36ebb4d712c7e206b0da3b2361bbd1 Mon Sep 17 00:00:00 2001 From: Stephen Lee Date: Sun, 3 Apr 2022 11:07:42 -0400 Subject: [PATCH] Output QR Code to Make Login Easier --- cmd/cloudflared/tunnel/login.go | 12 +- go.mod | 2 + go.sum | 9 + token/token.go | 2 +- token/transfer.go | 13 +- .../github.com/mdp/qrterminal/v3/.goreleaser | 18 + .../mdp/qrterminal/v3/.goreleaser.yml | 57 ++ .../github.com/mdp/qrterminal/v3/.travis.yml | 4 + .../github.com/mdp/qrterminal/v3/CHANGELOG.md | 27 + .../github.com/mdp/qrterminal/v3/Dockerfile | 3 + vendor/github.com/mdp/qrterminal/v3/LICENSE | 19 + vendor/github.com/mdp/qrterminal/v3/Makefile | 13 + vendor/github.com/mdp/qrterminal/v3/README.md | 103 +++ .../mdp/qrterminal/v3/qrterminal.go | 153 ++++ vendor/modules.txt | 8 + vendor/rsc.io/qr/LICENSE | 27 + vendor/rsc.io/qr/README.md | 3 + vendor/rsc.io/qr/coding/qr.go | 815 ++++++++++++++++++ vendor/rsc.io/qr/gf256/gf256.go | 241 ++++++ vendor/rsc.io/qr/png.go | 400 +++++++++ vendor/rsc.io/qr/qr.go | 116 +++ 21 files changed, 2040 insertions(+), 5 deletions(-) create mode 100644 vendor/github.com/mdp/qrterminal/v3/.goreleaser create mode 100644 vendor/github.com/mdp/qrterminal/v3/.goreleaser.yml create mode 100644 vendor/github.com/mdp/qrterminal/v3/.travis.yml create mode 100644 vendor/github.com/mdp/qrterminal/v3/CHANGELOG.md create mode 100644 vendor/github.com/mdp/qrterminal/v3/Dockerfile create mode 100644 vendor/github.com/mdp/qrterminal/v3/LICENSE create mode 100644 vendor/github.com/mdp/qrterminal/v3/Makefile create mode 100644 vendor/github.com/mdp/qrterminal/v3/README.md create mode 100644 vendor/github.com/mdp/qrterminal/v3/qrterminal.go create mode 100644 vendor/rsc.io/qr/LICENSE create mode 100644 vendor/rsc.io/qr/README.md create mode 100644 vendor/rsc.io/qr/coding/qr.go create mode 100644 vendor/rsc.io/qr/gf256/gf256.go create mode 100644 vendor/rsc.io/qr/png.go create mode 100644 vendor/rsc.io/qr/qr.go diff --git a/cmd/cloudflared/tunnel/login.go b/cmd/cloudflared/tunnel/login.go index 8b519147..0753e4ff 100644 --- a/cmd/cloudflared/tunnel/login.go +++ b/cmd/cloudflared/tunnel/login.go @@ -23,13 +23,22 @@ const ( callbackStoreURL = "https://login.cloudflareaccess.org/" ) +var ( + outputQrcodeFlag = &cli.BoolFlag{ + Name: "output-qr-code", + Usage: "Output a QR Code containing a link to tunnel login authorization workflow", + Aliases: []string{"qr"}, + } +) + func buildLoginSubcommand(hidden bool) *cli.Command { return &cli.Command{ Name: "login", Action: cliutil.ConfiguredAction(login), Usage: "Generate a configuration file with your login details", - ArgsUsage: " ", + UsageText: "cloudflared tunnel [tunnel command options] login [subcommand options]", Hidden: hidden, + Flags: []cli.Flag{outputQrcodeFlag}, } } @@ -57,6 +66,7 @@ func login(c *cli.Context) error { callbackStoreURL, false, false, + c.Bool("output-qr-code"), log, ) if err != nil { diff --git a/go.mod b/go.mod index bc8abee8..58edc623 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/lucas-clemente/quic-go v0.24.0 github.com/mattn/go-colorable v0.1.8 + github.com/mdp/qrterminal/v3 v3.0.0 github.com/miekg/dns v1.1.45 github.com/mitchellh/go-homedir v1.1.0 github.com/pkg/errors v0.9.1 @@ -93,6 +94,7 @@ require ( google.golang.org/protobuf v1.27.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + rsc.io/qr v0.2.0 // indirect ) replace github.com/urfave/cli/v2 => github.com/ipostelnik/cli/v2 v2.3.1-0.20210324024421-b6ea8234fe3d diff --git a/go.sum b/go.sum index 3c7888fa..7f0e63b3 100644 --- a/go.sum +++ b/go.sum @@ -399,8 +399,10 @@ github.com/marten-seemann/qtls-go1-17 v0.1.0 h1:P9ggrs5xtwiqXv/FHNwntmuLMNq3KaSI github.com/marten-seemann/qtls-go1-17 v0.1.0/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8= github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1 h1:EnzzN9fPUkUck/1CuY1FlzBaIYMoiBsdwTNmNGkwUUM= github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1/go.mod h1:PUhIQk19LoFt2174H4+an8TYvWOGjb/hHwphBeaDHwI= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -408,6 +410,10 @@ github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuuj github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mdp/qrterminal v1.0.1 h1:07+fzVDlPuBlXS8tB0ktTAyf+Lp1j2+2zK3fBOL5b7c= +github.com/mdp/qrterminal v1.0.1/go.mod h1:Z33WhxQe9B6CdW37HaVqcRKzP+kByF3q/qLxOGe12xQ= +github.com/mdp/qrterminal/v3 v3.0.0 h1:ywQqLRBXWTktytQNDKFjhAvoGkLVN3J2tAFZ0kMd9xQ= +github.com/mdp/qrterminal/v3 v3.0.0/go.mod h1:NJpfAs7OAm77Dy8EkWrtE4aq+cE6McoLXlBqXQEwvE0= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.45 h1:g5fRIhm9nx7g8osrAvgb16QJfmyMsyOCb+J7LSv+Qzk= @@ -732,6 +738,7 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1093,6 +1100,8 @@ k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lV k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY= +rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= diff --git a/token/token.go b/token/token.go index abccf257..31b81e37 100644 --- a/token/token.go +++ b/token/token.go @@ -218,7 +218,7 @@ func getTokensFromEdge(appURL *url.URL, appTokenPath, orgTokenPath string, useHo // this weird parameter is the resource name (token) and the key/value // we want to send to the transfer service. the key is token and the value // is blank (basically just the id generated in the transfer service) - resourceData, err := RunTransfer(appURL, keyName, keyName, "", true, useHostOnly, log) + resourceData, err := RunTransfer(appURL, keyName, keyName, "", true, useHostOnly, false, log) if err != nil { return "", errors.Wrap(err, "failed to run transfer service") } diff --git a/token/transfer.go b/token/transfer.go index e285c49e..cfc02d7e 100644 --- a/token/transfer.go +++ b/token/transfer.go @@ -10,6 +10,7 @@ import ( "os" "time" + "github.com/mdp/qrterminal/v3" "github.com/pkg/errors" "github.com/rs/zerolog" ) @@ -25,7 +26,7 @@ const ( // The "dance" we refer to is building a HTTP request, opening that in a browser waiting for // the user to complete an action, while it long polls in the background waiting for an // action to be completed to download the resource. -func RunTransfer(transferURL *url.URL, resourceName, key, value string, shouldEncrypt bool, useHostOnly bool, log *zerolog.Logger) ([]byte, error) { +func RunTransfer(transferURL *url.URL, resourceName, key, value string, shouldEncrypt bool, useHostOnly bool, outputQrCode bool, log *zerolog.Logger) ([]byte, error) { encrypterClient, err := NewEncrypter("cloudflared_priv.pem", "cloudflared_pub.pem") if err != nil { return nil, err @@ -37,10 +38,16 @@ func RunTransfer(transferURL *url.URL, resourceName, key, value string, shouldEn // See AUTH-1423 for why we use stderr (the way git wraps ssh) err = OpenBrowser(requestURL) + qrcode := "" + if outputQrCode { + buf := new(bytes.Buffer) + qrterminal.GenerateHalfBlock(requestURL, qrterminal.H, buf) + qrcode = fmt.Sprintf("%s\n", buf.String()) + } if err != nil { - fmt.Fprintf(os.Stderr, "Please open the following URL and log in with your Cloudflare account:\n\n%s\n\nLeave cloudflared running to download the %s automatically.\n", requestURL, resourceName) + fmt.Fprintf(os.Stderr, "Please open the following URL and log in with your Cloudflare account:\n\n%s%s\n\nLeave cloudflared running to download the %s automatically.\n", qrcode, requestURL, resourceName) } else { - fmt.Fprintf(os.Stderr, "A browser window should have opened at the following URL:\n\n%s\n\nIf the browser failed to open, please visit the URL above directly in your browser.\n", requestURL) + fmt.Fprintf(os.Stderr, "A browser window should have opened at the following URL:\n\n%s%s\n\nIf the browser failed to open, please visit the URL above directly in your browser.\n", qrcode, requestURL) } var resourceData []byte diff --git a/vendor/github.com/mdp/qrterminal/v3/.goreleaser b/vendor/github.com/mdp/qrterminal/v3/.goreleaser new file mode 100644 index 00000000..834d9fdf --- /dev/null +++ b/vendor/github.com/mdp/qrterminal/v3/.goreleaser @@ -0,0 +1,18 @@ +#!/bin/bash + +source $HOME/.config/goreleaser.env + +if [ -x "$(command -v goreleaser)" ]; then + goreleaser @$ +else + GO_SRC="/app" + docker run --rm \ + -v $PWD:$GO_SRC \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -w $GO_SRC \ + -e GITHUB_TOKEN=$GITHUB_TOKEN \ + -e DOCKER_USERNAME=$DOCKER_USERNAME \ + -e DOCKER_PASSWORD=$DOCKER_PASSWORD \ + goreleaser/goreleaser $@ +fi + diff --git a/vendor/github.com/mdp/qrterminal/v3/.goreleaser.yml b/vendor/github.com/mdp/qrterminal/v3/.goreleaser.yml new file mode 100644 index 00000000..6b12fab0 --- /dev/null +++ b/vendor/github.com/mdp/qrterminal/v3/.goreleaser.yml @@ -0,0 +1,57 @@ +# This is an example goreleaser.yaml file with some sane defaults. +# Make sure to check the documentation at http://goreleaser.com +before: + hooks: + # you may remove this if you don't use vgo + - go mod download + # you may remove this if you don't need go generate + - go generate ./... +builds: +- + main: ./cmd/qrterminal/main.go + binary: qrterminal + env: + - CGO_ENABLED=0 + goarch: + - amd64 + - 386 + - arm + goos: + - linux + - darwin + - windows + ignore: + - goos: darwin + goarch: 386 + - goos: windows + goarch: arm + +archives: +- replacements: + 386: i386 + amd64: x86_64 +checksum: + name_template: 'checksums.txt' +snapshot: + name_template: "{{ .Tag }}-next" +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' +brew: + name: qrterminal + github: + owner: mdp + name: homebrew-tap + commit_author: + name: mdp + email: m@mdp.im + homepage: https://github.com/mdp/qrterminal + description: 'Create and display QR codes on the command line' +dockers: + - + image_templates: + - "mpercival/qrterminal:latest" + - "mpercival/qrterminal:{{ .Tag }}" diff --git a/vendor/github.com/mdp/qrterminal/v3/.travis.yml b/vendor/github.com/mdp/qrterminal/v3/.travis.yml new file mode 100644 index 00000000..b54e2c3e --- /dev/null +++ b/vendor/github.com/mdp/qrterminal/v3/.travis.yml @@ -0,0 +1,4 @@ +language: go + +go: + - tip diff --git a/vendor/github.com/mdp/qrterminal/v3/CHANGELOG.md b/vendor/github.com/mdp/qrterminal/v3/CHANGELOG.md new file mode 100644 index 00000000..d3213338 --- /dev/null +++ b/vendor/github.com/mdp/qrterminal/v3/CHANGELOG.md @@ -0,0 +1,27 @@ +## 3.0.0 + +Adjust go.mod to include required version string + +## 2.0.1 + +Add goreleaser and release to Homebrew and Github + +## 2.0.0 + +Add a command line tool and QuietZone around QRCode + +## 1.0.1 + +Add go.mod + +## 1.0.0 + +Update to add a quiet zone border to the QR Code - #5 and fixed by [WindomZ](https://github.com/WindomZ) #8 + + - This can be configured with the `QuietZone int` option + - Defaults to 4 'pixels' wide to match the QR Code spec + - This alters the size of the barcode considerably and is therefore a breaking change, resulting in a bump to v1.0.0 + +## 0.2.1 + +Fix direction of the qr code #6 by (https://github.com/mattn) diff --git a/vendor/github.com/mdp/qrterminal/v3/Dockerfile b/vendor/github.com/mdp/qrterminal/v3/Dockerfile new file mode 100644 index 00000000..d379b58f --- /dev/null +++ b/vendor/github.com/mdp/qrterminal/v3/Dockerfile @@ -0,0 +1,3 @@ +FROM scratch +COPY qrterminal / +ENTRYPOINT ["/qrterminal"] diff --git a/vendor/github.com/mdp/qrterminal/v3/LICENSE b/vendor/github.com/mdp/qrterminal/v3/LICENSE new file mode 100644 index 00000000..f47a0957 --- /dev/null +++ b/vendor/github.com/mdp/qrterminal/v3/LICENSE @@ -0,0 +1,19 @@ +Copyright 2019 Mark Percival + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/mdp/qrterminal/v3/Makefile b/vendor/github.com/mdp/qrterminal/v3/Makefile new file mode 100644 index 00000000..119c2aed --- /dev/null +++ b/vendor/github.com/mdp/qrterminal/v3/Makefile @@ -0,0 +1,13 @@ +APP ?= "./cmd/qrterminal" + +build: + @go build "$(APP)" + +release: + ./.goreleaser release --rm-dist + +reltest: + ./.goreleaser release --snapshot --rm-dist --skip-publish + +test: + @go test -cover diff --git a/vendor/github.com/mdp/qrterminal/v3/README.md b/vendor/github.com/mdp/qrterminal/v3/README.md new file mode 100644 index 00000000..6b03e4d3 --- /dev/null +++ b/vendor/github.com/mdp/qrterminal/v3/README.md @@ -0,0 +1,103 @@ +# QRCode Terminal + +[![Build Status](https://api.travis-ci.org/mdp/qrterminal.svg)](https://travis-ci.org/mdp/qrterminal) + +A golang library for generating QR codes in the terminal. + +Originally this was a port of the [NodeJS version](https://github.com/gtanner/qrcode-terminal). Recently it's been updated to allow for smaller code generation using ASCII 'half blocks' + +## Example +Full size ASCII block QR Code: +alt text + +Smaller 'half blocks' in the terminal: +alt text + +## Install + +For command line usage [see below](https://github.com/mdp/qrterminal#command-line), or grab the binary from the [releases page](https://github.com/mdp/qrterminal/releases) + +As a library in an application + +`go get github.com/mdp/qrterminal/v3` + +## Usage + +```go +import ( + "github.com/mdp/qrterminal/v3" + "os" + ) + +func main() { + // Generate a 'dense' qrcode with the 'Low' level error correction and write it to Stdout + qrterminal.Generate("https://github.com/mdp/qrterminal", qrterminal.L, os.Stdout) +} +``` + +### More complicated + +Large Inverted barcode with medium redundancy and a 1 pixel border +```go +import ( + "github.com/mdp/qrterminal/v3" + "os" + ) + +func main() { + config := qrterminal.Config{ + Level: qrterminal.M, + Writer: os.Stdout, + BlackChar: qrterminal.WHITE, + WhiteChar: qrterminal.BLACK, + QuietZone: 1, + } + qrterminal.GenerateWithConfig("https://github.com/mdp/qrterminal", config) +} +``` + +HalfBlock barcode with medium redundancy +```go +import ( + "github.com/mdp/qrterminal/v3" + "os" + ) + +func main() { + config := qrterminal.Config{ + HalfBlocks: true, + Level: qrterminal.M, + Writer: os.Stdout, + } + qrterminal.Generate("https://github.com/mdp/qrterminal", config) +} +``` + + +## Command Line + +#### Installation + +OSX: `brew install mdp/tap/qrterminal` + +Others: Download from the [releases page](https://github.com/mdp/qrterminal/releases) + +Source: `go get -u github.com/mdp/qrterminal/v3/cmd/qrterminal` + +#### Usage + +Print out a basic QR code in your terminal: +`qrterminal https://github.com/mdp/qrterminal` + +Using 'medium' error correction: +`qrterminal https://github.com/mdp/qrterminal -l M` + +Or just use Docker: `docker run --rm mpercival/qrterminal:latest 'https://github.com/mdp/qrterminal'` + +### Contributors/Credits: + +- [Mark Percival](https://github.com/mdp) +- [Matthew Kennerly](https://github.com/mtkennerly) +- [Viric](https://github.com/viric) +- [WindomZ](https://github.com/WindomZ) +- [mattn](https://github.com/mattn) diff --git a/vendor/github.com/mdp/qrterminal/v3/qrterminal.go b/vendor/github.com/mdp/qrterminal/v3/qrterminal.go new file mode 100644 index 00000000..c44a13c4 --- /dev/null +++ b/vendor/github.com/mdp/qrterminal/v3/qrterminal.go @@ -0,0 +1,153 @@ +package qrterminal + +import ( + "io" + "strings" + + "rsc.io/qr" +) + +const WHITE = "\033[47m \033[0m" +const BLACK = "\033[40m \033[0m" + +// Use ascii blocks to form the QR Code +const BLACK_WHITE = "▄" +const BLACK_BLACK = " " +const WHITE_BLACK = "▀" +const WHITE_WHITE = "█" + +// Level - the QR Code's redundancy level +const H = qr.H +const M = qr.M +const L = qr.L + +// default is 4-pixel-wide white quiet zone +const QUIET_ZONE = 4 + +//Config for generating a barcode +type Config struct { + Level qr.Level + Writer io.Writer + HalfBlocks bool + BlackChar string + BlackWhiteChar string + WhiteChar string + WhiteBlackChar string + QuietZone int +} + +func (c *Config) writeFullBlocks(w io.Writer, code *qr.Code) { + white := c.WhiteChar + black := c.BlackChar + + // Frame the barcode in a 1 pixel border + w.Write([]byte(stringRepeat(stringRepeat(white, + code.Size+c.QuietZone*2)+"\n", c.QuietZone))) // top border + for i := 0; i <= code.Size; i++ { + w.Write([]byte(stringRepeat(white, c.QuietZone))) // left border + for j := 0; j <= code.Size; j++ { + if code.Black(j, i) { + w.Write([]byte(black)) + } else { + w.Write([]byte(white)) + } + } + w.Write([]byte(stringRepeat(white, c.QuietZone-1) + "\n")) // right border + } + w.Write([]byte(stringRepeat(stringRepeat(white, + code.Size+c.QuietZone*2)+"\n", c.QuietZone-1))) // bottom border +} + +func (c *Config) writeHalfBlocks(w io.Writer, code *qr.Code) { + ww := c.WhiteChar + bb := c.BlackChar + wb := c.WhiteBlackChar + bw := c.BlackWhiteChar + // Frame the barcode in a 4 pixel border + // top border + if c.QuietZone%2 != 0 { + w.Write([]byte(stringRepeat(bw, code.Size+c.QuietZone*2) + "\n")) + w.Write([]byte(stringRepeat(stringRepeat(ww, + code.Size+c.QuietZone*2)+"\n", c.QuietZone/2))) + } else { + w.Write([]byte(stringRepeat(stringRepeat(ww, + code.Size+c.QuietZone*2)+"\n", c.QuietZone/2))) + } + for i := 0; i <= code.Size; i += 2 { + w.Write([]byte(stringRepeat(ww, c.QuietZone))) // left border + for j := 0; j <= code.Size; j++ { + next_black := false + if i+1 < code.Size { + next_black = code.Black(j, i+1) + } + curr_black := code.Black(j, i) + if curr_black && next_black { + w.Write([]byte(bb)) + } else if curr_black && !next_black { + w.Write([]byte(bw)) + } else if !curr_black && !next_black { + w.Write([]byte(ww)) + } else { + w.Write([]byte(wb)) + } + } + w.Write([]byte(stringRepeat(ww, c.QuietZone-1) + "\n")) // right border + } + // bottom border + if c.QuietZone%2 == 0 { + w.Write([]byte(stringRepeat(stringRepeat(ww, + code.Size+c.QuietZone*2)+"\n", c.QuietZone/2-1))) + w.Write([]byte(stringRepeat(wb, code.Size+c.QuietZone*2) + "\n")) + } else { + w.Write([]byte(stringRepeat(stringRepeat(ww, + code.Size+c.QuietZone*2)+"\n", c.QuietZone/2))) + } +} + +func stringRepeat(s string, count int) string { + if count <= 0 { + return "" + } + return strings.Repeat(s, count) +} + +// GenerateWithConfig expects a string to encode and a config +func GenerateWithConfig(text string, config Config) { + if config.QuietZone < 1 { + config.QuietZone = 1 // at least 1-pixel-wide white quiet zone + } + w := config.Writer + code, _ := qr.Encode(text, config.Level) + if config.HalfBlocks { + config.writeHalfBlocks(w, code) + } else { + config.writeFullBlocks(w, code) + } +} + +// Generate a QR Code and write it out to io.Writer +func Generate(text string, l qr.Level, w io.Writer) { + config := Config{ + Level: l, + Writer: w, + BlackChar: BLACK, + WhiteChar: WHITE, + QuietZone: QUIET_ZONE, + } + GenerateWithConfig(text, config) +} + +// Generate a QR Code with half blocks and write it out to io.Writer +func GenerateHalfBlock(text string, l qr.Level, w io.Writer) { + config := Config{ + Level: l, + Writer: w, + HalfBlocks: true, + BlackChar: BLACK_BLACK, + WhiteBlackChar: WHITE_BLACK, + WhiteChar: WHITE_WHITE, + BlackWhiteChar: BLACK_WHITE, + QuietZone: QUIET_ZONE, + } + GenerateWithConfig(text, config) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 3b296241..914193f4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -232,6 +232,9 @@ github.com/mattn/go-runewidth # github.com/matttproud/golang_protobuf_extensions v1.0.1 ## explicit github.com/matttproud/golang_protobuf_extensions/pbutil +# github.com/mdp/qrterminal/v3 v3.0.0 +## explicit +github.com/mdp/qrterminal/v3 # github.com/miekg/dns v1.1.45 ## explicit; go 1.14 github.com/miekg/dns @@ -548,6 +551,11 @@ gopkg.in/yaml.v2 # gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b ## explicit gopkg.in/yaml.v3 +# rsc.io/qr v0.2.0 +## explicit +rsc.io/qr +rsc.io/qr/coding +rsc.io/qr/gf256 # zombiezen.com/go/capnproto2 v2.18.0+incompatible ## explicit zombiezen.com/go/capnproto2 diff --git a/vendor/rsc.io/qr/LICENSE b/vendor/rsc.io/qr/LICENSE new file mode 100644 index 00000000..6a66aea5 --- /dev/null +++ b/vendor/rsc.io/qr/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/rsc.io/qr/README.md b/vendor/rsc.io/qr/README.md new file mode 100644 index 00000000..0ba6214d --- /dev/null +++ b/vendor/rsc.io/qr/README.md @@ -0,0 +1,3 @@ +Basic QR encoder. + +go get [-u] rsc.io/qr diff --git a/vendor/rsc.io/qr/coding/qr.go b/vendor/rsc.io/qr/coding/qr.go new file mode 100644 index 00000000..bfc3ea40 --- /dev/null +++ b/vendor/rsc.io/qr/coding/qr.go @@ -0,0 +1,815 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package coding implements low-level QR coding details. +package coding // import "rsc.io/qr/coding" + +import ( + "fmt" + "strconv" + "strings" + + "rsc.io/qr/gf256" +) + +// Field is the field for QR error correction. +var Field = gf256.NewField(0x11d, 2) + +// A Version represents a QR version. +// The version specifies the size of the QR code: +// a QR code with version v has 4v+17 pixels on a side. +// Versions number from 1 to 40: the larger the version, +// the more information the code can store. +type Version int + +const MinVersion = 1 +const MaxVersion = 40 + +func (v Version) String() string { + return strconv.Itoa(int(v)) +} + +func (v Version) sizeClass() int { + if v <= 9 { + return 0 + } + if v <= 26 { + return 1 + } + return 2 +} + +// DataBytes returns the number of data bytes that can be +// stored in a QR code with the given version and level. +func (v Version) DataBytes(l Level) int { + vt := &vtab[v] + lev := &vt.level[l] + return vt.bytes - lev.nblock*lev.check +} + +// Encoding implements a QR data encoding scheme. +// The implementations--Numeric, Alphanumeric, and String--specify +// the character set and the mapping from UTF-8 to code bits. +// The more restrictive the mode, the fewer code bits are needed. +type Encoding interface { + Check() error + Bits(v Version) int + Encode(b *Bits, v Version) +} + +type Bits struct { + b []byte + nbit int +} + +func (b *Bits) Reset() { + b.b = b.b[:0] + b.nbit = 0 +} + +func (b *Bits) Bits() int { + return b.nbit +} + +func (b *Bits) Bytes() []byte { + if b.nbit%8 != 0 { + panic("fractional byte") + } + return b.b +} + +func (b *Bits) Append(p []byte) { + if b.nbit%8 != 0 { + panic("fractional byte") + } + b.b = append(b.b, p...) + b.nbit += 8 * len(p) +} + +func (b *Bits) Write(v uint, nbit int) { + for nbit > 0 { + n := nbit + if n > 8 { + n = 8 + } + if b.nbit%8 == 0 { + b.b = append(b.b, 0) + } else { + m := -b.nbit & 7 + if n > m { + n = m + } + } + b.nbit += n + sh := uint(nbit - n) + b.b[len(b.b)-1] |= uint8(v >> sh << uint(-b.nbit&7)) + v -= v >> sh << sh + nbit -= n + } +} + +// Num is the encoding for numeric data. +// The only valid characters are the decimal digits 0 through 9. +type Num string + +func (s Num) String() string { + return fmt.Sprintf("Num(%#q)", string(s)) +} + +func (s Num) Check() error { + for _, c := range s { + if c < '0' || '9' < c { + return fmt.Errorf("non-numeric string %#q", string(s)) + } + } + return nil +} + +var numLen = [3]int{10, 12, 14} + +func (s Num) Bits(v Version) int { + return 4 + numLen[v.sizeClass()] + (10*len(s)+2)/3 +} + +func (s Num) Encode(b *Bits, v Version) { + b.Write(1, 4) + b.Write(uint(len(s)), numLen[v.sizeClass()]) + var i int + for i = 0; i+3 <= len(s); i += 3 { + w := uint(s[i]-'0')*100 + uint(s[i+1]-'0')*10 + uint(s[i+2]-'0') + b.Write(w, 10) + } + switch len(s) - i { + case 1: + w := uint(s[i] - '0') + b.Write(w, 4) + case 2: + w := uint(s[i]-'0')*10 + uint(s[i+1]-'0') + b.Write(w, 7) + } +} + +// Alpha is the encoding for alphanumeric data. +// The valid characters are 0-9A-Z$%*+-./: and space. +type Alpha string + +const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:" + +func (s Alpha) String() string { + return fmt.Sprintf("Alpha(%#q)", string(s)) +} + +func (s Alpha) Check() error { + for _, c := range s { + if strings.IndexRune(alphabet, c) < 0 { + return fmt.Errorf("non-alphanumeric string %#q", string(s)) + } + } + return nil +} + +var alphaLen = [3]int{9, 11, 13} + +func (s Alpha) Bits(v Version) int { + return 4 + alphaLen[v.sizeClass()] + (11*len(s)+1)/2 +} + +func (s Alpha) Encode(b *Bits, v Version) { + b.Write(2, 4) + b.Write(uint(len(s)), alphaLen[v.sizeClass()]) + var i int + for i = 0; i+2 <= len(s); i += 2 { + w := uint(strings.IndexRune(alphabet, rune(s[i])))*45 + + uint(strings.IndexRune(alphabet, rune(s[i+1]))) + b.Write(w, 11) + } + + if i < len(s) { + w := uint(strings.IndexRune(alphabet, rune(s[i]))) + b.Write(w, 6) + } +} + +// String is the encoding for 8-bit data. All bytes are valid. +type String string + +func (s String) String() string { + return fmt.Sprintf("String(%#q)", string(s)) +} + +func (s String) Check() error { + return nil +} + +var stringLen = [3]int{8, 16, 16} + +func (s String) Bits(v Version) int { + return 4 + stringLen[v.sizeClass()] + 8*len(s) +} + +func (s String) Encode(b *Bits, v Version) { + b.Write(4, 4) + b.Write(uint(len(s)), stringLen[v.sizeClass()]) + for i := 0; i < len(s); i++ { + b.Write(uint(s[i]), 8) + } +} + +// A Pixel describes a single pixel in a QR code. +type Pixel uint32 + +const ( + Black Pixel = 1 << iota + Invert +) + +func (p Pixel) Offset() uint { + return uint(p >> 6) +} + +func OffsetPixel(o uint) Pixel { + return Pixel(o << 6) +} + +func (r PixelRole) Pixel() Pixel { + return Pixel(r << 2) +} + +func (p Pixel) Role() PixelRole { + return PixelRole(p>>2) & 15 +} + +func (p Pixel) String() string { + s := p.Role().String() + if p&Black != 0 { + s += "+black" + } + if p&Invert != 0 { + s += "+invert" + } + s += "+" + strconv.FormatUint(uint64(p.Offset()), 10) + return s +} + +// A PixelRole describes the role of a QR pixel. +type PixelRole uint32 + +const ( + _ PixelRole = iota + Position // position squares (large) + Alignment // alignment squares (small) + Timing // timing strip between position squares + Format // format metadata + PVersion // version pattern + Unused // unused pixel + Data // data bit + Check // error correction check bit + Extra +) + +var roles = []string{ + "", + "position", + "alignment", + "timing", + "format", + "pversion", + "unused", + "data", + "check", + "extra", +} + +func (r PixelRole) String() string { + if Position <= r && r <= Check { + return roles[r] + } + return strconv.Itoa(int(r)) +} + +// A Level represents a QR error correction level. +// From least to most tolerant of errors, they are L, M, Q, H. +type Level int + +const ( + L Level = iota + M + Q + H +) + +func (l Level) String() string { + if L <= l && l <= H { + return "LMQH"[l : l+1] + } + return strconv.Itoa(int(l)) +} + +// A Code is a square pixel grid. +type Code struct { + Bitmap []byte // 1 is black, 0 is white + Size int // number of pixels on a side + Stride int // number of bytes per row +} + +func (c *Code) Black(x, y int) bool { + return 0 <= x && x < c.Size && 0 <= y && y < c.Size && + c.Bitmap[y*c.Stride+x/8]&(1<= pad { + break + } + b.Write(0x11, 8) + } + } +} + +func (b *Bits) AddCheckBytes(v Version, l Level) { + nd := v.DataBytes(l) + if b.nbit < nd*8 { + b.Pad(nd*8 - b.nbit) + } + if b.nbit != nd*8 { + panic("qr: too much data") + } + + dat := b.Bytes() + vt := &vtab[v] + lev := &vt.level[l] + db := nd / lev.nblock + extra := nd % lev.nblock + chk := make([]byte, lev.check) + rs := gf256.NewRSEncoder(Field, lev.check) + for i := 0; i < lev.nblock; i++ { + if i == lev.nblock-extra { + db++ + } + rs.ECC(dat[:db], chk) + b.Append(chk) + dat = dat[db:] + } + + if len(b.Bytes()) != vt.bytes { + panic("qr: internal error") + } +} + +func (p *Plan) Encode(text ...Encoding) (*Code, error) { + var b Bits + for _, t := range text { + if err := t.Check(); err != nil { + return nil, err + } + t.Encode(&b, p.Version) + } + if b.Bits() > p.DataBytes*8 { + return nil, fmt.Errorf("cannot encode %d bits into %d-bit code", b.Bits(), p.DataBytes*8) + } + b.AddCheckBytes(p.Version, p.Level) + bytes := b.Bytes() + + // Now we have the checksum bytes and the data bytes. + // Construct the actual code. + c := &Code{Size: len(p.Pixel), Stride: (len(p.Pixel) + 7) &^ 7} + c.Bitmap = make([]byte, c.Stride*c.Size) + crow := c.Bitmap + for _, row := range p.Pixel { + for x, pix := range row { + switch pix.Role() { + case Data, Check: + o := pix.Offset() + if bytes[o/8]&(1< 40 { + return nil, fmt.Errorf("invalid QR version %d", int(v)) + } + siz := 17 + int(v)*4 + m := grid(siz) + p.Pixel = m + + // Timing markers (overwritten by boxes). + const ti = 6 // timing is in row/column 6 (counting from 0) + for i := range m { + p := Timing.Pixel() + if i&1 == 0 { + p |= Black + } + m[i][ti] = p + m[ti][i] = p + } + + // Position boxes. + posBox(m, 0, 0) + posBox(m, siz-7, 0) + posBox(m, 0, siz-7) + + // Alignment boxes. + info := &vtab[v] + for x := 4; x+5 < siz; { + for y := 4; y+5 < siz; { + // don't overwrite timing markers + if (x < 7 && y < 7) || (x < 7 && y+5 >= siz-7) || (x+5 >= siz-7 && y < 7) { + } else { + alignBox(m, x, y) + } + if y == 4 { + y = info.apos + } else { + y += info.astride + } + } + if x == 4 { + x = info.apos + } else { + x += info.astride + } + } + + // Version pattern. + pat := vtab[v].pattern + if pat != 0 { + v := pat + for x := 0; x < 6; x++ { + for y := 0; y < 3; y++ { + p := PVersion.Pixel() + if v&1 != 0 { + p |= Black + } + m[siz-11+y][x] = p + m[x][siz-11+y] = p + v >>= 1 + } + } + } + + // One lonely black pixel + m[siz-8][8] = Unused.Pixel() | Black + + return p, nil +} + +// fplan adds the format pixels +func fplan(l Level, m Mask, p *Plan) error { + // Format pixels. + fb := uint32(l^1) << 13 // level: L=01, M=00, Q=11, H=10 + fb |= uint32(m) << 10 // mask + const formatPoly = 0x537 + rem := fb + for i := 14; i >= 10; i-- { + if rem&(1<>i)&1 == 1 { + pix |= Black + } + if (invert>>i)&1 == 1 { + pix ^= Invert | Black + } + // top left + switch { + case i < 6: + p.Pixel[i][8] = pix + case i < 8: + p.Pixel[i+1][8] = pix + case i < 9: + p.Pixel[8][7] = pix + default: + p.Pixel[8][14-i] = pix + } + // bottom right + switch { + case i < 8: + p.Pixel[8][siz-1-int(i)] = pix + default: + p.Pixel[siz-1-int(14-i)][8] = pix + } + } + return nil +} + +// lplan edits a version-only Plan to add information +// about the error correction levels. +func lplan(v Version, l Level, p *Plan) error { + p.Level = l + + nblock := vtab[v].level[l].nblock + ne := vtab[v].level[l].check + nde := (vtab[v].bytes - ne*nblock) / nblock + extra := (vtab[v].bytes - ne*nblock) % nblock + dataBits := (nde*nblock + extra) * 8 + checkBits := ne * nblock * 8 + + p.DataBytes = vtab[v].bytes - ne*nblock + p.CheckBytes = ne * nblock + p.Blocks = nblock + + // Make data + checksum pixels. + data := make([]Pixel, dataBits) + for i := range data { + data[i] = Data.Pixel() | OffsetPixel(uint(i)) + } + check := make([]Pixel, checkBits) + for i := range check { + check[i] = Check.Pixel() | OffsetPixel(uint(i+dataBits)) + } + + // Split into blocks. + dataList := make([][]Pixel, nblock) + checkList := make([][]Pixel, nblock) + for i := 0; i < nblock; i++ { + // The last few blocks have an extra data byte (8 pixels). + nd := nde + if i >= nblock-extra { + nd++ + } + dataList[i], data = data[0:nd*8], data[nd*8:] + checkList[i], check = check[0:ne*8], check[ne*8:] + } + if len(data) != 0 || len(check) != 0 { + panic("data/check math") + } + + // Build up bit sequence, taking first byte of each block, + // then second byte, and so on. Then checksums. + bits := make([]Pixel, dataBits+checkBits) + dst := bits + for i := 0; i < nde+1; i++ { + for _, b := range dataList { + if i*8 < len(b) { + copy(dst, b[i*8:(i+1)*8]) + dst = dst[8:] + } + } + } + for i := 0; i < ne; i++ { + for _, b := range checkList { + if i*8 < len(b) { + copy(dst, b[i*8:(i+1)*8]) + dst = dst[8:] + } + } + } + if len(dst) != 0 { + panic("dst math") + } + + // Sweep up pair of columns, + // then down, assigning to right then left pixel. + // Repeat. + // See Figure 2 of http://www.pclviewer.com/rs2/qrtopology.htm + siz := len(p.Pixel) + rem := make([]Pixel, 7) + for i := range rem { + rem[i] = Extra.Pixel() + } + src := append(bits, rem...) + for x := siz; x > 0; { + for y := siz - 1; y >= 0; y-- { + if p.Pixel[y][x-1].Role() == 0 { + p.Pixel[y][x-1], src = src[0], src[1:] + } + if p.Pixel[y][x-2].Role() == 0 { + p.Pixel[y][x-2], src = src[0], src[1:] + } + } + x -= 2 + if x == 7 { // vertical timing strip + x-- + } + for y := 0; y < siz; y++ { + if p.Pixel[y][x-1].Role() == 0 { + p.Pixel[y][x-1], src = src[0], src[1:] + } + if p.Pixel[y][x-2].Role() == 0 { + p.Pixel[y][x-2], src = src[0], src[1:] + } + } + x -= 2 + } + return nil +} + +// mplan edits a version+level-only Plan to add the mask. +func mplan(m Mask, p *Plan) error { + p.Mask = m + for y, row := range p.Pixel { + for x, pix := range row { + if r := pix.Role(); (r == Data || r == Check || r == Extra) && p.Mask.Invert(y, x) { + row[x] ^= Black | Invert + } + } + } + return nil +} + +// posBox draws a position (large) box at upper left x, y. +func posBox(m [][]Pixel, x, y int) { + pos := Position.Pixel() + // box + for dy := 0; dy < 7; dy++ { + for dx := 0; dx < 7; dx++ { + p := pos + if dx == 0 || dx == 6 || dy == 0 || dy == 6 || 2 <= dx && dx <= 4 && 2 <= dy && dy <= 4 { + p |= Black + } + m[y+dy][x+dx] = p + } + } + // white border + for dy := -1; dy < 8; dy++ { + if 0 <= y+dy && y+dy < len(m) { + if x > 0 { + m[y+dy][x-1] = pos + } + if x+7 < len(m) { + m[y+dy][x+7] = pos + } + } + } + for dx := -1; dx < 8; dx++ { + if 0 <= x+dx && x+dx < len(m) { + if y > 0 { + m[y-1][x+dx] = pos + } + if y+7 < len(m) { + m[y+7][x+dx] = pos + } + } + } +} + +// alignBox draw an alignment (small) box at upper left x, y. +func alignBox(m [][]Pixel, x, y int) { + // box + align := Alignment.Pixel() + for dy := 0; dy < 5; dy++ { + for dx := 0; dx < 5; dx++ { + p := align + if dx == 0 || dx == 4 || dy == 0 || dy == 4 || dx == 2 && dy == 2 { + p |= Black + } + m[y+dy][x+dx] = p + } + } +} diff --git a/vendor/rsc.io/qr/gf256/gf256.go b/vendor/rsc.io/qr/gf256/gf256.go new file mode 100644 index 00000000..05e56455 --- /dev/null +++ b/vendor/rsc.io/qr/gf256/gf256.go @@ -0,0 +1,241 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package gf256 implements arithmetic over the Galois Field GF(256). +package gf256 // import "rsc.io/qr/gf256" + +import "strconv" + +// A Field represents an instance of GF(256) defined by a specific polynomial. +type Field struct { + log [256]byte // log[0] is unused + exp [510]byte +} + +// NewField returns a new field corresponding to the polynomial poly +// and generator α. The Reed-Solomon encoding in QR codes uses +// polynomial 0x11d with generator 2. +// +// The choice of generator α only affects the Exp and Log operations. +func NewField(poly, α int) *Field { + if poly < 0x100 || poly >= 0x200 || reducible(poly) { + panic("gf256: invalid polynomial: " + strconv.Itoa(poly)) + } + + var f Field + x := 1 + for i := 0; i < 255; i++ { + if x == 1 && i != 0 { + panic("gf256: invalid generator " + strconv.Itoa(α) + + " for polynomial " + strconv.Itoa(poly)) + } + f.exp[i] = byte(x) + f.exp[i+255] = byte(x) + f.log[x] = byte(i) + x = mul(x, α, poly) + } + f.log[0] = 255 + for i := 0; i < 255; i++ { + if f.log[f.exp[i]] != byte(i) { + panic("bad log") + } + if f.log[f.exp[i+255]] != byte(i) { + panic("bad log") + } + } + for i := 1; i < 256; i++ { + if f.exp[f.log[i]] != byte(i) { + panic("bad log") + } + } + + return &f +} + +// nbit returns the number of significant in p. +func nbit(p int) uint { + n := uint(0) + for ; p > 0; p >>= 1 { + n++ + } + return n +} + +// polyDiv divides the polynomial p by q and returns the remainder. +func polyDiv(p, q int) int { + np := nbit(p) + nq := nbit(q) + for ; np >= nq; np-- { + if p&(1<<(np-1)) != 0 { + p ^= q << (np - nq) + } + } + return p +} + +// mul returns the product x*y mod poly, a GF(256) multiplication. +func mul(x, y, poly int) int { + z := 0 + for x > 0 { + if x&1 != 0 { + z ^= y + } + x >>= 1 + y <<= 1 + if y&0x100 != 0 { + y ^= poly + } + } + return z +} + +// reducible reports whether p is reducible. +func reducible(p int) bool { + // Multiplying n-bit * n-bit produces (2n-1)-bit, + // so if p is reducible, one of its factors must be + // of np/2+1 bits or fewer. + np := nbit(p) + for q := 2; q < 1<<(np/2+1); q++ { + if polyDiv(p, q) == 0 { + return true + } + } + return false +} + +// Add returns the sum of x and y in the field. +func (f *Field) Add(x, y byte) byte { + return x ^ y +} + +// Exp returns the base-α exponential of e in the field. +// If e < 0, Exp returns 0. +func (f *Field) Exp(e int) byte { + if e < 0 { + return 0 + } + return f.exp[e%255] +} + +// Log returns the base-α logarithm of x in the field. +// If x == 0, Log returns -1. +func (f *Field) Log(x byte) int { + if x == 0 { + return -1 + } + return int(f.log[x]) +} + +// Inv returns the multiplicative inverse of x in the field. +// If x == 0, Inv returns 0. +func (f *Field) Inv(x byte) byte { + if x == 0 { + return 0 + } + return f.exp[255-f.log[x]] +} + +// Mul returns the product of x and y in the field. +func (f *Field) Mul(x, y byte) byte { + if x == 0 || y == 0 { + return 0 + } + return f.exp[int(f.log[x])+int(f.log[y])] +} + +// An RSEncoder implements Reed-Solomon encoding +// over a given field using a given number of error correction bytes. +type RSEncoder struct { + f *Field + c int + gen []byte + lgen []byte + p []byte +} + +func (f *Field) gen(e int) (gen, lgen []byte) { + // p = 1 + p := make([]byte, e+1) + p[e] = 1 + + for i := 0; i < e; i++ { + // p *= (x + Exp(i)) + // p[j] = p[j]*Exp(i) + p[j+1]. + c := f.Exp(i) + for j := 0; j < e; j++ { + p[j] = f.Mul(p[j], c) ^ p[j+1] + } + p[e] = f.Mul(p[e], c) + } + + // lp = log p. + lp := make([]byte, e+1) + for i, c := range p { + if c == 0 { + lp[i] = 255 + } else { + lp[i] = byte(f.Log(c)) + } + } + + return p, lp +} + +// NewRSEncoder returns a new Reed-Solomon encoder +// over the given field and number of error correction bytes. +func NewRSEncoder(f *Field, c int) *RSEncoder { + gen, lgen := f.gen(c) + return &RSEncoder{f: f, c: c, gen: gen, lgen: lgen} +} + +// ECC writes to check the error correcting code bytes +// for data using the given Reed-Solomon parameters. +func (rs *RSEncoder) ECC(data []byte, check []byte) { + if len(check) < rs.c { + panic("gf256: invalid check byte length") + } + if rs.c == 0 { + return + } + + // The check bytes are the remainder after dividing + // data padded with c zeros by the generator polynomial. + + // p = data padded with c zeros. + var p []byte + n := len(data) + rs.c + if len(rs.p) >= n { + p = rs.p + } else { + p = make([]byte, n) + } + copy(p, data) + for i := len(data); i < len(p); i++ { + p[i] = 0 + } + + // Divide p by gen, leaving the remainder in p[len(data):]. + // p[0] is the most significant term in p, and + // gen[0] is the most significant term in the generator, + // which is always 1. + // To avoid repeated work, we store various values as + // lv, not v, where lv = log[v]. + f := rs.f + lgen := rs.lgen[1:] + for i := 0; i < len(data); i++ { + c := p[i] + if c == 0 { + continue + } + q := p[i+1:] + exp := f.exp[f.log[c]:] + for j, lg := range lgen { + if lg != 255 { // lgen uses 255 for log 0 + q[j] ^= exp[lg] + } + } + } + copy(check, p[len(data):]) + rs.p = p +} diff --git a/vendor/rsc.io/qr/png.go b/vendor/rsc.io/qr/png.go new file mode 100644 index 00000000..db49d057 --- /dev/null +++ b/vendor/rsc.io/qr/png.go @@ -0,0 +1,400 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package qr + +// PNG writer for QR codes. + +import ( + "bytes" + "encoding/binary" + "hash" + "hash/crc32" +) + +// PNG returns a PNG image displaying the code. +// +// PNG uses a custom encoder tailored to QR codes. +// Its compressed size is about 2x away from optimal, +// but it runs about 20x faster than calling png.Encode +// on c.Image(). +func (c *Code) PNG() []byte { + var p pngWriter + return p.encode(c) +} + +type pngWriter struct { + tmp [16]byte + wctmp [4]byte + buf bytes.Buffer + zlib bitWriter + crc hash.Hash32 +} + +var pngHeader = []byte("\x89PNG\r\n\x1a\n") + +func (w *pngWriter) encode(c *Code) []byte { + scale := c.Scale + siz := c.Size + + w.buf.Reset() + + // Header + w.buf.Write(pngHeader) + + // Header block + binary.BigEndian.PutUint32(w.tmp[0:4], uint32((siz+8)*scale)) + binary.BigEndian.PutUint32(w.tmp[4:8], uint32((siz+8)*scale)) + w.tmp[8] = 1 // 1-bit + w.tmp[9] = 0 // gray + w.tmp[10] = 0 + w.tmp[11] = 0 + w.tmp[12] = 0 + w.writeChunk("IHDR", w.tmp[:13]) + + // Comment + w.writeChunk("tEXt", comment) + + // Data + w.zlib.writeCode(c) + w.writeChunk("IDAT", w.zlib.bytes.Bytes()) + + // End + w.writeChunk("IEND", nil) + + return w.buf.Bytes() +} + +var comment = []byte("Software\x00QR-PNG http://qr.swtch.com/") + +func (w *pngWriter) writeChunk(name string, data []byte) { + if w.crc == nil { + w.crc = crc32.NewIEEE() + } + binary.BigEndian.PutUint32(w.wctmp[0:4], uint32(len(data))) + w.buf.Write(w.wctmp[0:4]) + w.crc.Reset() + copy(w.wctmp[0:4], name) + w.buf.Write(w.wctmp[0:4]) + w.crc.Write(w.wctmp[0:4]) + w.buf.Write(data) + w.crc.Write(data) + crc := w.crc.Sum32() + binary.BigEndian.PutUint32(w.wctmp[0:4], crc) + w.buf.Write(w.wctmp[0:4]) +} + +func (b *bitWriter) writeCode(c *Code) { + const ftNone = 0 + + b.adler32.Reset() + b.bytes.Reset() + b.nbit = 0 + + scale := c.Scale + siz := c.Size + + // zlib header + b.tmp[0] = 0x78 + b.tmp[1] = 0 + b.tmp[1] += uint8(31 - (uint16(b.tmp[0])<<8+uint16(b.tmp[1]))%31) + b.bytes.Write(b.tmp[0:2]) + + // Start flate block. + b.writeBits(1, 1, false) // final block + b.writeBits(1, 2, false) // compressed, fixed Huffman tables + + // White border. + // First row. + b.byte(ftNone) + n := (scale*(siz+8) + 7) / 8 + b.byte(255) + b.repeat(n-1, 1) + // 4*scale rows total. + b.repeat((4*scale-1)*(1+n), 1+n) + + for i := 0; i < 4*scale; i++ { + b.adler32.WriteNByte(ftNone, 1) + b.adler32.WriteNByte(255, n) + } + + row := make([]byte, 1+n) + for y := 0; y < siz; y++ { + row[0] = ftNone + j := 1 + var z uint8 + nz := 0 + for x := -4; x < siz+4; x++ { + // Raw data. + for i := 0; i < scale; i++ { + z <<= 1 + if !c.Black(x, y) { + z |= 1 + } + if nz++; nz == 8 { + row[j] = z + j++ + nz = 0 + } + } + } + if j < len(row) { + row[j] = z + } + for _, z := range row { + b.byte(z) + } + + // Scale-1 copies. + b.repeat((scale-1)*(1+n), 1+n) + + b.adler32.WriteN(row, scale) + } + + // White border. + // First row. + b.byte(ftNone) + b.byte(255) + b.repeat(n-1, 1) + // 4*scale rows total. + b.repeat((4*scale-1)*(1+n), 1+n) + + for i := 0; i < 4*scale; i++ { + b.adler32.WriteNByte(ftNone, 1) + b.adler32.WriteNByte(255, n) + } + + // End of block. + b.hcode(256) + b.flushBits() + + // adler32 + binary.BigEndian.PutUint32(b.tmp[0:], b.adler32.Sum32()) + b.bytes.Write(b.tmp[0:4]) +} + +// A bitWriter is a write buffer for bit-oriented data like deflate. +type bitWriter struct { + bytes bytes.Buffer + bit uint32 + nbit uint + + tmp [4]byte + adler32 adigest +} + +func (b *bitWriter) writeBits(bit uint32, nbit uint, rev bool) { + // reverse, for huffman codes + if rev { + br := uint32(0) + for i := uint(0); i < nbit; i++ { + br |= ((bit >> i) & 1) << (nbit - 1 - i) + } + bit = br + } + b.bit |= bit << b.nbit + b.nbit += nbit + for b.nbit >= 8 { + b.bytes.WriteByte(byte(b.bit)) + b.bit >>= 8 + b.nbit -= 8 + } +} + +func (b *bitWriter) flushBits() { + if b.nbit > 0 { + b.bytes.WriteByte(byte(b.bit)) + b.nbit = 0 + b.bit = 0 + } +} + +func (b *bitWriter) hcode(v int) { + /* + Lit Value Bits Codes + --------- ---- ----- + 0 - 143 8 00110000 through + 10111111 + 144 - 255 9 110010000 through + 111111111 + 256 - 279 7 0000000 through + 0010111 + 280 - 287 8 11000000 through + 11000111 + */ + switch { + case v <= 143: + b.writeBits(uint32(v)+0x30, 8, true) + case v <= 255: + b.writeBits(uint32(v-144)+0x190, 9, true) + case v <= 279: + b.writeBits(uint32(v-256)+0, 7, true) + case v <= 287: + b.writeBits(uint32(v-280)+0xc0, 8, true) + default: + panic("invalid hcode") + } +} + +func (b *bitWriter) byte(x byte) { + b.hcode(int(x)) +} + +func (b *bitWriter) codex(c int, val int, nx uint) { + b.hcode(c + val>>nx) + b.writeBits(uint32(val)&(1<= 258+3; n -= 258 { + b.repeat1(258, d) + } + if n > 258 { + // 258 < n < 258+3 + b.repeat1(10, d) + b.repeat1(n-10, d) + return + } + if n < 3 { + panic("invalid flate repeat") + } + b.repeat1(n, d) +} + +func (b *bitWriter) repeat1(n, d int) { + /* + Extra Extra Extra + Code Bits Length(s) Code Bits Lengths Code Bits Length(s) + ---- ---- ------ ---- ---- ------- ---- ---- ------- + 257 0 3 267 1 15,16 277 4 67-82 + 258 0 4 268 1 17,18 278 4 83-98 + 259 0 5 269 2 19-22 279 4 99-114 + 260 0 6 270 2 23-26 280 4 115-130 + 261 0 7 271 2 27-30 281 5 131-162 + 262 0 8 272 2 31-34 282 5 163-194 + 263 0 9 273 3 35-42 283 5 195-226 + 264 0 10 274 3 43-50 284 5 227-257 + 265 1 11,12 275 3 51-58 285 0 258 + 266 1 13,14 276 3 59-66 + */ + switch { + case n <= 10: + b.codex(257, n-3, 0) + case n <= 18: + b.codex(265, n-11, 1) + case n <= 34: + b.codex(269, n-19, 2) + case n <= 66: + b.codex(273, n-35, 3) + case n <= 130: + b.codex(277, n-67, 4) + case n <= 257: + b.codex(281, n-131, 5) + case n == 258: + b.hcode(285) + default: + panic("invalid repeat length") + } + + /* + Extra Extra Extra + Code Bits Dist Code Bits Dist Code Bits Distance + ---- ---- ---- ---- ---- ------ ---- ---- -------- + 0 0 1 10 4 33-48 20 9 1025-1536 + 1 0 2 11 4 49-64 21 9 1537-2048 + 2 0 3 12 5 65-96 22 10 2049-3072 + 3 0 4 13 5 97-128 23 10 3073-4096 + 4 1 5,6 14 6 129-192 24 11 4097-6144 + 5 1 7,8 15 6 193-256 25 11 6145-8192 + 6 2 9-12 16 7 257-384 26 12 8193-12288 + 7 2 13-16 17 7 385-512 27 12 12289-16384 + 8 3 17-24 18 8 513-768 28 13 16385-24576 + 9 3 25-32 19 8 769-1024 29 13 24577-32768 + */ + if d <= 4 { + b.writeBits(uint32(d-1), 5, true) + } else if d <= 32768 { + nbit := uint(16) + for d <= 1<<(nbit-1) { + nbit-- + } + v := uint32(d - 1) + v &^= 1 << (nbit - 1) // top bit is implicit + code := uint32(2*nbit - 2) // second bit is low bit of code + code |= v >> (nbit - 2) + v &^= 1 << (nbit - 2) + b.writeBits(code, 5, true) + // rest of bits follow + b.writeBits(uint32(v), nbit-2, false) + } else { + panic("invalid repeat distance") + } +} + +func (b *bitWriter) run(v byte, n int) { + if n == 0 { + return + } + b.byte(v) + if n-1 < 3 { + for i := 0; i < n-1; i++ { + b.byte(v) + } + } else { + b.repeat(n-1, 1) + } +} + +type adigest struct { + a, b uint32 +} + +func (d *adigest) Reset() { d.a, d.b = 1, 0 } + +const amod = 65521 + +func aupdate(a, b uint32, pi byte, n int) (aa, bb uint32) { + // TODO(rsc): 6g doesn't do magic multiplies for b %= amod, + // only for b = b%amod. + + // invariant: a, b < amod + if pi == 0 { + b += uint32(n%amod) * a + b = b % amod + return a, b + } + + // n times: + // a += pi + // b += a + // is same as + // b += n*a + n*(n+1)/2*pi + // a += n*pi + m := uint32(n) + b += (m % amod) * a + b = b % amod + b += (m * (m + 1) / 2) % amod * uint32(pi) + b = b % amod + a += (m % amod) * uint32(pi) + a = a % amod + return a, b +} + +func afinish(a, b uint32) uint32 { + return b<<16 | a +} + +func (d *adigest) WriteN(p []byte, n int) { + for i := 0; i < n; i++ { + for _, pi := range p { + d.a, d.b = aupdate(d.a, d.b, pi, 1) + } + } +} + +func (d *adigest) WriteNByte(pi byte, n int) { + d.a, d.b = aupdate(d.a, d.b, pi, n) +} + +func (d *adigest) Sum32() uint32 { return afinish(d.a, d.b) } diff --git a/vendor/rsc.io/qr/qr.go b/vendor/rsc.io/qr/qr.go new file mode 100644 index 00000000..ace7e6f1 --- /dev/null +++ b/vendor/rsc.io/qr/qr.go @@ -0,0 +1,116 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package qr encodes QR codes. +*/ +package qr // import "rsc.io/qr" + +import ( + "errors" + "image" + "image/color" + + "rsc.io/qr/coding" +) + +// A Level denotes a QR error correction level. +// From least to most tolerant of errors, they are L, M, Q, H. +type Level int + +const ( + L Level = iota // 20% redundant + M // 38% redundant + Q // 55% redundant + H // 65% redundant +) + +// Encode returns an encoding of text at the given error correction level. +func Encode(text string, level Level) (*Code, error) { + // Pick data encoding, smallest first. + // We could split the string and use different encodings + // but that seems like overkill for now. + var enc coding.Encoding + switch { + case coding.Num(text).Check() == nil: + enc = coding.Num(text) + case coding.Alpha(text).Check() == nil: + enc = coding.Alpha(text) + default: + enc = coding.String(text) + } + + // Pick size. + l := coding.Level(level) + var v coding.Version + for v = coding.MinVersion; ; v++ { + if v > coding.MaxVersion { + return nil, errors.New("text too long to encode as QR") + } + if enc.Bits(v) <= v.DataBytes(l)*8 { + break + } + } + + // Build and execute plan. + p, err := coding.NewPlan(v, l, 0) + if err != nil { + return nil, err + } + cc, err := p.Encode(enc) + if err != nil { + return nil, err + } + + // TODO: Pick appropriate mask. + + return &Code{cc.Bitmap, cc.Size, cc.Stride, 8}, nil +} + +// A Code is a square pixel grid. +// It implements image.Image and direct PNG encoding. +type Code struct { + Bitmap []byte // 1 is black, 0 is white + Size int // number of pixels on a side + Stride int // number of bytes per row + Scale int // number of image pixels per QR pixel +} + +// Black returns true if the pixel at (x,y) is black. +func (c *Code) Black(x, y int) bool { + return 0 <= x && x < c.Size && 0 <= y && y < c.Size && + c.Bitmap[y*c.Stride+x/8]&(1<