mirror of
https://github.com/bettercap/bettercap
synced 2025-07-14 09:03:39 -07:00
adding vendor folder
This commit is contained in:
parent
49c65021ea
commit
c304ca4696
1145 changed files with 369961 additions and 2 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,7 +4,6 @@
|
||||||
pcaps
|
pcaps
|
||||||
caplets
|
caplets
|
||||||
build
|
build
|
||||||
vendor
|
|
||||||
bettercap*.*
|
bettercap*.*
|
||||||
bettercap*
|
bettercap*
|
||||||
bettercap.history
|
bettercap.history
|
||||||
|
|
|
@ -18,7 +18,7 @@ Make sure you have a correctly configured **Go >= 1.8** environment, that `$GOPA
|
||||||
|
|
||||||
$ go get github.com/bettercap/bettercap
|
$ go get github.com/bettercap/bettercap
|
||||||
|
|
||||||
This command will download bettercap, install its dependencies, compile it and move the `bettercap` executable to `$GOPATH/bin`.
|
This command will download bettercap, install its dependencies, compile it and move the `bettercap` executable to `$GOPATH/bin`.
|
||||||
|
|
||||||
Now you can use `sudo bettercap -h` to show the basic command line options and just `sudo bettercap` to start an
|
Now you can use `sudo bettercap -h` to show the basic command line options and just `sudo bettercap` to start an
|
||||||
[interactive session](https://github.com/bettercap/bettercap/wiki/Interactive-Mode) on your default network interface, otherwise you can [load a caplet](https://github.com/bettercap/bettercap/wiki/Caplets) from [the dedicated repository](https://github.com/bettercap/caplets).
|
[interactive session](https://github.com/bettercap/bettercap/wiki/Interactive-Mode) on your default network interface, otherwise you can [load a caplet](https://github.com/bettercap/bettercap/wiki/Caplets) from [the dedicated repository](https://github.com/bettercap/caplets).
|
||||||
|
|
30
vendor/github.com/adrianmo/go-nmea/.travis.yml
generated
vendored
Normal file
30
vendor/github.com/adrianmo/go-nmea/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# Travis CI (http://travis-ci.org/) is a continuous integration
|
||||||
|
# service for open source projects. This file configures it
|
||||||
|
# to run unit tests for go-nmea.
|
||||||
|
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.7
|
||||||
|
- tip
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
fast_finish: true
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- go get golang.org/x/tools/cmd/cover
|
||||||
|
- go get github.com/golang/lint/golint
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
- go get github.com/stretchr/testify/assert
|
||||||
|
|
||||||
|
install:
|
||||||
|
- go get -d -v ./... && go build -v ./...
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go vet -x ./...
|
||||||
|
- $HOME/gopath/bin/golint -set_exit_status ./...
|
||||||
|
- go test -v ./...
|
||||||
|
- go test -covermode=count -coverprofile=profile.cov .
|
||||||
|
|
||||||
|
after_script:
|
||||||
|
- $HOME/gopath/bin/goveralls -coverprofile=profile.cov -service=travis-ci
|
21
vendor/github.com/adrianmo/go-nmea/LICENSE
generated
vendored
Normal file
21
vendor/github.com/adrianmo/go-nmea/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Adrian Moreno
|
||||||
|
|
||||||
|
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.
|
52
vendor/github.com/adrianmo/go-nmea/README.md
generated
vendored
Normal file
52
vendor/github.com/adrianmo/go-nmea/README.md
generated
vendored
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
# go-nmea [](https://travis-ci.org/adrianmo/go-nmea) [](https://goreportcard.com/report/github.com/adrianmo/go-nmea) [](https://coveralls.io/github/adrianmo/go-nmea?branch=master) [](https://godoc.org/github.com/adrianmo/go-nmea)
|
||||||
|
|
||||||
|
This is a NMEA library for the Go programming language (http://golang.org).
|
||||||
|
|
||||||
|
## Installing
|
||||||
|
|
||||||
|
### Using `go get`
|
||||||
|
|
||||||
|
go get github.com/adrianmo/go-nmea
|
||||||
|
|
||||||
|
After this command *go-nmea* is ready to use. Its source will be in:
|
||||||
|
|
||||||
|
$GOPATH/src/github.com/adrianmo/go-nmea
|
||||||
|
|
||||||
|
## Supported sentences
|
||||||
|
|
||||||
|
At this moment, this library supports the following sentence types:
|
||||||
|
|
||||||
|
- [GPRMC](http://aprs.gids.nl/nmea/#rmc) - Recommended Minimum Specific GPS/Transit data
|
||||||
|
- [GNRMC](http://aprs.gids.nl/nmea/#rmc) - Recommended Minimum Specific GNSS data
|
||||||
|
- [GPGGA](http://aprs.gids.nl/nmea/#gga) - GPS Positioning System Fix Data
|
||||||
|
- [GNGGA](http://aprs.gids.nl/nmea/#gga) - GNSS Positioning System Fix Data
|
||||||
|
- [GPGSA](http://aprs.gids.nl/nmea/#gsa) - GPS DOP and active satellites
|
||||||
|
- [GPGSV](http://aprs.gids.nl/nmea/#gsv) - GPS Satellites in view
|
||||||
|
- [GLGSV](http://aprs.gids.nl/nmea/#gsv) - GLONASS Satellites in view
|
||||||
|
- [GPGLL](http://aprs.gids.nl/nmea/#gll) - Geographic Position, Latitude / Longitude and time
|
||||||
|
- [GPVTG](http://aprs.gids.nl/nmea/#vtg) - Track Made Good and Ground Speed
|
||||||
|
- [GPZDA](http://aprs.gids.nl/nmea/#zda) - Date & time data
|
||||||
|
- [PGRME](http://aprs.gids.nl/nmea/#rme) - Estimated Position Error (Garmin proprietary sentence)
|
||||||
|
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/adrianmo/go-nmea"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
m, err := nmea.Parse("$GPRMC,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W*70")
|
||||||
|
if err == nil {
|
||||||
|
fmt.Printf("%+v\n", m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributions
|
||||||
|
|
||||||
|
Please, feel free to implement support for new sentences, fix bugs, refactor code, etc. and send a pull-request to update the library.
|
47
vendor/github.com/adrianmo/go-nmea/glgsv.go
generated
vendored
Normal file
47
vendor/github.com/adrianmo/go-nmea/glgsv.go
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PrefixGLGSV prefix
|
||||||
|
PrefixGLGSV = "GLGSV"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GLGSV represents the GPS Satellites in view
|
||||||
|
// http://aprs.gids.nl/nmea/#glgsv
|
||||||
|
type GLGSV struct {
|
||||||
|
Sent
|
||||||
|
TotalMessages int64 // Total number of messages of this type in this cycle
|
||||||
|
MessageNumber int64 // Message number
|
||||||
|
NumberSVsInView int64 // Total number of SVs in view
|
||||||
|
Info []GLGSVInfo // visible satellite info (0-4 of these)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GLGSVInfo represents information about a visible satellite
|
||||||
|
type GLGSVInfo struct {
|
||||||
|
SVPRNNumber int64 // SV PRN number, pseudo-random noise or gold code
|
||||||
|
Elevation int64 // Elevation in degrees, 90 maximum
|
||||||
|
Azimuth int64 // Azimuth, degrees from true north, 000 to 359
|
||||||
|
SNR int64 // SNR, 00-99 dB (null when not tracking)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGLGSV constructor
|
||||||
|
func NewGLGSV(s Sent) (GLGSV, error) {
|
||||||
|
p := newParser(s, PrefixGLGSV)
|
||||||
|
m := GLGSV{
|
||||||
|
Sent: s,
|
||||||
|
TotalMessages: p.Int64(0, "total number of messages"),
|
||||||
|
MessageNumber: p.Int64(1, "message number"),
|
||||||
|
NumberSVsInView: p.Int64(2, "number of SVs in view"),
|
||||||
|
}
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
if 5*i+4 > len(m.Fields) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
m.Info = append(m.Info, GLGSVInfo{
|
||||||
|
SVPRNNumber: p.Int64(3+i*4, "SV prn number"),
|
||||||
|
Elevation: p.Int64(4+i*4, "elevation"),
|
||||||
|
Azimuth: p.Int64(5+i*4, "azimuth"),
|
||||||
|
SNR: p.Int64(6+i*4, "SNR"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return m, p.Err()
|
||||||
|
}
|
96
vendor/github.com/adrianmo/go-nmea/glgsv_test.go
generated
vendored
Normal file
96
vendor/github.com/adrianmo/go-nmea/glgsv_test.go
generated
vendored
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGLGSVGoodSentence(t *testing.T) {
|
||||||
|
goodMsg := "$GLGSV,3,1,11,03,03,111,00,04,15,270,00,06,01,010,12,13,06,292,00*6B"
|
||||||
|
s, err := Parse(goodMsg)
|
||||||
|
|
||||||
|
assert.NoError(t, err, "Unexpected error parsing good sentence")
|
||||||
|
assert.Equal(t, PrefixGLGSV, s.Prefix(), "Prefix does not match")
|
||||||
|
|
||||||
|
sentence := s.(GLGSV)
|
||||||
|
assert.Equal(t, int64(3), sentence.TotalMessages, "Total messages does not match")
|
||||||
|
assert.Equal(t, int64(1), sentence.MessageNumber, "Message number does not match")
|
||||||
|
assert.Equal(t, int64(11), sentence.NumberSVsInView, "Number of SVs in view does not match")
|
||||||
|
|
||||||
|
assert.Equal(t, int64(3), sentence.Info[0].SVPRNNumber, "Number of Info[0] SV PRN does not match")
|
||||||
|
assert.Equal(t, int64(3), sentence.Info[0].Elevation, "Number of Info[0] Elevation does not match")
|
||||||
|
assert.Equal(t, int64(111), sentence.Info[0].Azimuth, "Number of Info[0] Azimuth does not match")
|
||||||
|
assert.Equal(t, int64(0), sentence.Info[0].SNR, "Number of Info[0] SNR does not match")
|
||||||
|
|
||||||
|
assert.Equal(t, int64(4), sentence.Info[1].SVPRNNumber, "Number of Info[1] SV PRN does not match")
|
||||||
|
assert.Equal(t, int64(15), sentence.Info[1].Elevation, "Number of Info[1] Elevation does not match")
|
||||||
|
assert.Equal(t, int64(270), sentence.Info[1].Azimuth, "Number of Info[1] Azimuth does not match")
|
||||||
|
assert.Equal(t, int64(0), sentence.Info[1].SNR, "Number of Info[1] SNR does not match")
|
||||||
|
|
||||||
|
assert.Equal(t, int64(6), sentence.Info[2].SVPRNNumber, "Number of Info[2] SV PRN does not match")
|
||||||
|
assert.Equal(t, int64(1), sentence.Info[2].Elevation, "Number of Info[2] Elevation does not match")
|
||||||
|
assert.Equal(t, int64(10), sentence.Info[2].Azimuth, "Number of Info[2] Azimuth does not match")
|
||||||
|
assert.Equal(t, int64(12), sentence.Info[2].SNR, "Number of Info[2] SNR does not match")
|
||||||
|
|
||||||
|
assert.Equal(t, int64(13), sentence.Info[3].SVPRNNumber, "Number of Info[3] SV PRN does not match")
|
||||||
|
assert.Equal(t, int64(6), sentence.Info[3].Elevation, "Number of Info[3] Elevation does not match")
|
||||||
|
assert.Equal(t, int64(292), sentence.Info[3].Azimuth, "Number of Info[3] Azimuth does not match")
|
||||||
|
assert.Equal(t, int64(0), sentence.Info[3].SNR, "Number of Info[3] SNR does not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGLGSVShort(t *testing.T) {
|
||||||
|
goodMsg := "$GLGSV,3,1,11,03,03,111,00,04,15,270,00,06,01,010,12*56"
|
||||||
|
s, err := Parse(goodMsg)
|
||||||
|
|
||||||
|
assert.NoError(t, err, "Unexpected error parsing good sentence")
|
||||||
|
assert.Equal(t, PrefixGLGSV, s.Prefix(), "Prefix does not match")
|
||||||
|
|
||||||
|
sentence := s.(GLGSV)
|
||||||
|
assert.Equal(t, int64(3), sentence.TotalMessages, "Total messages does not match")
|
||||||
|
assert.Equal(t, int64(1), sentence.MessageNumber, "Message number does not match")
|
||||||
|
assert.Equal(t, int64(11), sentence.NumberSVsInView, "Number of SVs in view does not match")
|
||||||
|
|
||||||
|
assert.Equal(t, int64(3), sentence.Info[0].SVPRNNumber, "Number of Info[0] SV PRN does not match")
|
||||||
|
assert.Equal(t, int64(3), sentence.Info[0].Elevation, "Number of Info[0] Elevation does not match")
|
||||||
|
assert.Equal(t, int64(111), sentence.Info[0].Azimuth, "Number of Info[0] Azimuth does not match")
|
||||||
|
assert.Equal(t, int64(0), sentence.Info[0].SNR, "Number of Info[0] SNR does not match")
|
||||||
|
|
||||||
|
assert.Equal(t, int64(4), sentence.Info[1].SVPRNNumber, "Number of Info[1] SV PRN does not match")
|
||||||
|
assert.Equal(t, int64(15), sentence.Info[1].Elevation, "Number of Info[1] Elevation does not match")
|
||||||
|
assert.Equal(t, int64(270), sentence.Info[1].Azimuth, "Number of Info[1] Azimuth does not match")
|
||||||
|
assert.Equal(t, int64(0), sentence.Info[1].SNR, "Number of Info[1] SNR does not match")
|
||||||
|
|
||||||
|
assert.Equal(t, int64(6), sentence.Info[2].SVPRNNumber, "Number of Info[2] SV PRN does not match")
|
||||||
|
assert.Equal(t, int64(1), sentence.Info[2].Elevation, "Number of Info[2] Elevation does not match")
|
||||||
|
assert.Equal(t, int64(10), sentence.Info[2].Azimuth, "Number of Info[2] Azimuth does not match")
|
||||||
|
assert.Equal(t, int64(12), sentence.Info[2].SNR, "Number of Info[2] SNR does not match")
|
||||||
|
}
|
||||||
|
func TestGLGSVBadSentence(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Input string
|
||||||
|
Error string
|
||||||
|
}{
|
||||||
|
{"$GLGSV,3,1,11.2,03,03,111,00,04,15,270,00,06,01,010,12,13,06,292,00*77", "nmea: GLGSV invalid number of SVs in view: 11.2"},
|
||||||
|
{"$GLGSV,A3,1,11,03,03,111,00,04,15,270,00,06,01,010,12,13,06,292,00*2A", "nmea: GLGSV invalid total number of messages: A3"},
|
||||||
|
{"$GLGSV,3,A1,11,03,03,111,00,04,15,270,00,06,01,010,12,13,06,292,00*2A", "nmea: GLGSV invalid message number: A1"},
|
||||||
|
{"$GLGSV,3,1,11,A03,03,111,00,04,15,270,00,06,01,010,12,13,06,292,00*2A", "nmea: GLGSV invalid SV prn number: A03"},
|
||||||
|
{"$GLGSV,3,1,11,03,A03,111,00,04,15,270,00,06,01,010,12,13,06,292,00*2A", "nmea: GLGSV invalid elevation: A03"},
|
||||||
|
{"$GLGSV,3,1,11,03,03,A111,00,04,15,270,00,06,01,010,12,13,06,292,00*2A", "nmea: GLGSV invalid azimuth: A111"},
|
||||||
|
{"$GLGSV,3,1,11,03,03,111,A00,04,15,270,00,06,01,010,12,13,06,292,00*2A", "nmea: GLGSV invalid SNR: A00"},
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
_, err := Parse(tc.Input)
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, tc.Error, err.Error(), "Incorrect error message")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGLGSVWrongSentence(t *testing.T) {
|
||||||
|
wrongMsg := "$GPXTE,A,A,4.07,L,N*6D"
|
||||||
|
sent, _ := ParseSentence(wrongMsg)
|
||||||
|
_, err := NewGLGSV(sent)
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, "nmea: GLGSV invalid prefix: GPXTE", err.Error(), "Incorrect error message")
|
||||||
|
}
|
39
vendor/github.com/adrianmo/go-nmea/gngga.go
generated
vendored
Normal file
39
vendor/github.com/adrianmo/go-nmea/gngga.go
generated
vendored
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PrefixGNGGA prefix
|
||||||
|
PrefixGNGGA = "GNGGA"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GNGGA is the Time, position, and fix related data of the receiver.
|
||||||
|
type GNGGA struct {
|
||||||
|
Sent
|
||||||
|
Time Time // Time of fix.
|
||||||
|
Latitude LatLong // Latitude.
|
||||||
|
Longitude LatLong // Longitude.
|
||||||
|
FixQuality string // Quality of fix.
|
||||||
|
NumSatellites int64 // Number of satellites in use.
|
||||||
|
HDOP float64 // Horizontal dilution of precision.
|
||||||
|
Altitude float64 // Altitude.
|
||||||
|
Separation float64 // Geoidal separation
|
||||||
|
DGPSAge string // Age of differential GPD data.
|
||||||
|
DGPSId string // DGPS reference station ID.
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGNGGA constructor
|
||||||
|
func NewGNGGA(s Sent) (GNGGA, error) {
|
||||||
|
p := newParser(s, PrefixGNGGA)
|
||||||
|
return GNGGA{
|
||||||
|
Sent: s,
|
||||||
|
Time: p.Time(0, "time"),
|
||||||
|
Latitude: p.LatLong(1, 2, "latitude"),
|
||||||
|
Longitude: p.LatLong(3, 4, "longitude"),
|
||||||
|
FixQuality: p.EnumString(5, "fix quality", Invalid, GPS, DGPS),
|
||||||
|
NumSatellites: p.Int64(6, "number of satelites"),
|
||||||
|
HDOP: p.Float64(7, "hdop"),
|
||||||
|
Altitude: p.Float64(8, "altitude"),
|
||||||
|
Separation: p.Float64(10, "separation"),
|
||||||
|
DGPSAge: p.String(12, "dgps age"),
|
||||||
|
DGPSId: p.String(13, "dgps id"),
|
||||||
|
}, p.Err()
|
||||||
|
}
|
71
vendor/github.com/adrianmo/go-nmea/gngga_test.go
generated
vendored
Normal file
71
vendor/github.com/adrianmo/go-nmea/gngga_test.go
generated
vendored
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGNGGAGoodSentence(t *testing.T) {
|
||||||
|
goodMsg := "$GNGGA,203415.000,6325.6138,N,01021.4290,E,1,8,2.42,72.5,M,41.5,M,,*7C"
|
||||||
|
sentence, err := Parse(goodMsg)
|
||||||
|
|
||||||
|
assert.NoError(t, err, "Unexpected error parsing good sentence")
|
||||||
|
|
||||||
|
lat, _ := ParseLatLong("6325.6138 N")
|
||||||
|
lon, _ := ParseLatLong("01021.4290 E")
|
||||||
|
// Attributes of the parsed sentence, and their expected values.
|
||||||
|
expected := GNGGA{
|
||||||
|
Sent: Sent{
|
||||||
|
Type: "GNGGA",
|
||||||
|
Fields: []string{"203415.000", "6325.6138", "N", "01021.4290", "E", "1", "8", "2.42", "72.5", "M", "41.5", "M", "", ""},
|
||||||
|
Checksum: "7C",
|
||||||
|
Raw: "$GNGGA,203415.000,6325.6138,N,01021.4290,E,1,8,2.42,72.5,M,41.5,M,,*7C",
|
||||||
|
},
|
||||||
|
Time: Time{true, 20, 34, 15, 0},
|
||||||
|
Latitude: lat,
|
||||||
|
Longitude: lon,
|
||||||
|
FixQuality: GPS,
|
||||||
|
NumSatellites: 8,
|
||||||
|
HDOP: 2.42,
|
||||||
|
Altitude: 72.5,
|
||||||
|
Separation: 41.5,
|
||||||
|
DGPSAge: "",
|
||||||
|
DGPSId: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.EqualValues(t, expected, sentence, "Sentence values do not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGNGGABadType(t *testing.T) {
|
||||||
|
badType := "$GPRMC,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W*70"
|
||||||
|
s, err := Parse(badType)
|
||||||
|
|
||||||
|
assert.NoError(t, err, "Unexpected error parsing sentence")
|
||||||
|
assert.NotEqual(t, "GNGGA", s.Prefix(), "Unexpected sentence type")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGNGGABadLatitude(t *testing.T) {
|
||||||
|
badLat := "$GNGGA,034225.077,A,S,15124.5567,E,1,03,9.7,-25.0,M,21.0,M,,0000*24"
|
||||||
|
_, err := Parse(badLat)
|
||||||
|
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, "nmea: GNGGA invalid latitude: cannot parse [A S], unknown format", err.Error(), "Error message does not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGNGGABadLongitude(t *testing.T) {
|
||||||
|
badLon := "$GNGGA,034225.077,3356.4650,S,A,E,1,03,9.7,-25.0,M,21.0,M,,0000*12"
|
||||||
|
_, err := Parse(badLon)
|
||||||
|
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, "nmea: GNGGA invalid longitude: cannot parse [A E], unknown format", err.Error(), "Error message does not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGNGGABadFixQuality(t *testing.T) {
|
||||||
|
// Make sure bad fix mode is detected.
|
||||||
|
badMode := "$GNGGA,034225.077,3356.4650,S,15124.5567,E,5,03,9.7,-25.0,M,21.0,M,,0000*4B"
|
||||||
|
_, err := Parse(badMode)
|
||||||
|
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, err.Error(), "nmea: GNGGA invalid fix quality: 5", "Error message not as expected")
|
||||||
|
}
|
40
vendor/github.com/adrianmo/go-nmea/gnrmc.go
generated
vendored
Normal file
40
vendor/github.com/adrianmo/go-nmea/gnrmc.go
generated
vendored
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PrefixGNRMC prefix of GNRMC sentence type
|
||||||
|
PrefixGNRMC = "GNRMC"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GNRMC is the Recommended Minimum Specific GNSS data.
|
||||||
|
// http://aprs.gids.nl/nmea/#rmc
|
||||||
|
type GNRMC struct {
|
||||||
|
Sent
|
||||||
|
Time Time // Time Stamp
|
||||||
|
Validity string // validity - A-ok, V-invalid
|
||||||
|
Latitude LatLong // Latitude
|
||||||
|
Longitude LatLong // Longitude
|
||||||
|
Speed float64 // Speed in knots
|
||||||
|
Course float64 // True course
|
||||||
|
Date Date // Date
|
||||||
|
Variation float64 // Magnetic variation
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGNRMC constructor
|
||||||
|
func NewGNRMC(s Sent) (GNRMC, error) {
|
||||||
|
p := newParser(s, PrefixGNRMC)
|
||||||
|
m := GNRMC{
|
||||||
|
Sent: s,
|
||||||
|
Time: p.Time(0, "time"),
|
||||||
|
Validity: p.EnumString(1, "validity", ValidRMC, InvalidRMC),
|
||||||
|
Latitude: p.LatLong(2, 3, "latitude"),
|
||||||
|
Longitude: p.LatLong(4, 5, "longitude"),
|
||||||
|
Speed: p.Float64(6, "speed"),
|
||||||
|
Course: p.Float64(7, "course"),
|
||||||
|
Date: p.Date(8, "date"),
|
||||||
|
Variation: p.Float64(9, "variation"),
|
||||||
|
}
|
||||||
|
if p.EnumString(10, "direction", West, East) == West {
|
||||||
|
m.Variation = 0 - m.Variation
|
||||||
|
}
|
||||||
|
return m, p.Err()
|
||||||
|
}
|
78
vendor/github.com/adrianmo/go-nmea/gnrmc_test.go
generated
vendored
Normal file
78
vendor/github.com/adrianmo/go-nmea/gnrmc_test.go
generated
vendored
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var gnrmctests = []struct {
|
||||||
|
Input string
|
||||||
|
Output GNRMC
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"$GNRMC,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W*6E",
|
||||||
|
GNRMC{
|
||||||
|
Time: Time{true, 22, 05, 16, 0},
|
||||||
|
Validity: "A",
|
||||||
|
Speed: 173.8,
|
||||||
|
Course: 231.8,
|
||||||
|
Date: Date{true, 13, 06, 94},
|
||||||
|
Variation: -4.2,
|
||||||
|
Latitude: MustParseGPS("5133.82 N"),
|
||||||
|
Longitude: MustParseGPS("00042.24 W"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$GNRMC,142754.0,A,4302.539570,N,07920.379823,W,0.0,,070617,0.0,E,A*21",
|
||||||
|
GNRMC{
|
||||||
|
Time: Time{true, 14, 27, 54, 0},
|
||||||
|
Validity: "A",
|
||||||
|
Speed: 0,
|
||||||
|
Course: 0,
|
||||||
|
Date: Date{true, 7, 6, 17},
|
||||||
|
Variation: 0,
|
||||||
|
Latitude: MustParseGPS("4302.539570 N"),
|
||||||
|
Longitude: MustParseGPS("07920.379823 W"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGNRMCGoodSentence(t *testing.T) {
|
||||||
|
|
||||||
|
for _, tt := range gnrmctests {
|
||||||
|
|
||||||
|
s, err := Parse(tt.Input)
|
||||||
|
|
||||||
|
assert.NoError(t, err, "Unexpected error parsing good sentence")
|
||||||
|
assert.Equal(t, PrefixGNRMC, s.Prefix(), "Prefix does not match")
|
||||||
|
|
||||||
|
sentence := s.(GNRMC)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.Output.Time, sentence.Time, "Time does not match")
|
||||||
|
assert.Equal(t, tt.Output.Validity, sentence.Validity, "Status does not match")
|
||||||
|
assert.Equal(t, tt.Output.Speed, sentence.Speed, "Speed does not match")
|
||||||
|
assert.Equal(t, tt.Output.Course, sentence.Course, "Course does not match")
|
||||||
|
assert.Equal(t, tt.Output.Date, sentence.Date, "Date does not match")
|
||||||
|
assert.Equal(t, tt.Output.Variation, sentence.Variation, "Variation does not match")
|
||||||
|
assert.Equal(t, tt.Output.Latitude, sentence.Latitude, "Latitude does not match")
|
||||||
|
assert.Equal(t, tt.Output.Longitude, sentence.Longitude, "Longitude does not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGNRMCBadSentence(t *testing.T) {
|
||||||
|
badMsg := "$GNRMC,220516,D,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W*6B"
|
||||||
|
_, err := Parse(badMsg)
|
||||||
|
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, "nmea: GNRMC invalid validity: D", err.Error(), "Incorrect error message")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGNRMCWrongSentence(t *testing.T) {
|
||||||
|
wrongMsg := "$GPXTE,A,A,4.07,L,N*6D"
|
||||||
|
_, err := Parse(wrongMsg)
|
||||||
|
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, "nmea: sentence type 'GPXTE' not implemented", err.Error(), "Incorrect error message")
|
||||||
|
}
|
47
vendor/github.com/adrianmo/go-nmea/gpgga.go
generated
vendored
Normal file
47
vendor/github.com/adrianmo/go-nmea/gpgga.go
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PrefixGPGGA prefix
|
||||||
|
PrefixGPGGA = "GPGGA"
|
||||||
|
// Invalid fix quality.
|
||||||
|
Invalid = "0"
|
||||||
|
// GPS fix quality
|
||||||
|
GPS = "1"
|
||||||
|
// DGPS fix quality
|
||||||
|
DGPS = "2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GPGGA represents fix data.
|
||||||
|
// http://aprs.gids.nl/nmea/#gga
|
||||||
|
type GPGGA struct {
|
||||||
|
Sent
|
||||||
|
Time Time // Time of fix.
|
||||||
|
Latitude LatLong // Latitude.
|
||||||
|
Longitude LatLong // Longitude.
|
||||||
|
FixQuality string // Quality of fix.
|
||||||
|
NumSatellites int64 // Number of satellites in use.
|
||||||
|
HDOP float64 // Horizontal dilution of precision.
|
||||||
|
Altitude float64 // Altitude.
|
||||||
|
Separation float64 // Geoidal separation
|
||||||
|
DGPSAge string // Age of differential GPD data.
|
||||||
|
DGPSId string // DGPS reference station ID.
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGPGGA parses the GPGGA sentence into this struct.
|
||||||
|
// e.g: $GPGGA,034225.077,3356.4650,S,15124.5567,E,1,03,9.7,-25.0,M,21.0,M,,0000*58
|
||||||
|
func NewGPGGA(s Sent) (GPGGA, error) {
|
||||||
|
p := newParser(s, PrefixGPGGA)
|
||||||
|
return GPGGA{
|
||||||
|
Sent: s,
|
||||||
|
Time: p.Time(0, "time"),
|
||||||
|
Latitude: p.LatLong(1, 2, "latitude"),
|
||||||
|
Longitude: p.LatLong(3, 4, "longitude"),
|
||||||
|
FixQuality: p.EnumString(5, "fix quality", Invalid, GPS, DGPS),
|
||||||
|
NumSatellites: p.Int64(6, "number of satelites"),
|
||||||
|
HDOP: p.Float64(7, "hdap"),
|
||||||
|
Altitude: p.Float64(8, "altitude"),
|
||||||
|
Separation: p.Float64(10, "separation"),
|
||||||
|
DGPSAge: p.String(12, "dgps age"),
|
||||||
|
DGPSId: p.String(13, "dgps id"),
|
||||||
|
}, p.Err()
|
||||||
|
}
|
71
vendor/github.com/adrianmo/go-nmea/gpgga_test.go
generated
vendored
Normal file
71
vendor/github.com/adrianmo/go-nmea/gpgga_test.go
generated
vendored
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGPGGAGoodSentence(t *testing.T) {
|
||||||
|
goodMsg := "$GPGGA,034225.077,3356.4650,S,15124.5567,E,1,03,9.7,-25.0,M,21.0,M,,0000*51"
|
||||||
|
sentence, err := Parse(goodMsg)
|
||||||
|
|
||||||
|
assert.NoError(t, err, "Unexpected error parsing good sentence")
|
||||||
|
|
||||||
|
lat, _ := ParseLatLong("3356.4650 S")
|
||||||
|
lon, _ := ParseLatLong("15124.5567 E")
|
||||||
|
// Attributes of the parsed sentence, and their expected values.
|
||||||
|
expected := GPGGA{
|
||||||
|
Sent: Sent{
|
||||||
|
Type: "GPGGA",
|
||||||
|
Fields: []string{"034225.077", "3356.4650", "S", "15124.5567", "E", "1", "03", "9.7", "-25.0", "M", "21.0", "M", "", "0000"},
|
||||||
|
Checksum: "51",
|
||||||
|
Raw: "$GPGGA,034225.077,3356.4650,S,15124.5567,E,1,03,9.7,-25.0,M,21.0,M,,0000*51",
|
||||||
|
},
|
||||||
|
Time: Time{true, 3, 42, 25, 77},
|
||||||
|
Latitude: lat,
|
||||||
|
Longitude: lon,
|
||||||
|
FixQuality: GPS,
|
||||||
|
NumSatellites: 03,
|
||||||
|
HDOP: 9.7,
|
||||||
|
Altitude: -25.0,
|
||||||
|
Separation: 21.0,
|
||||||
|
DGPSAge: "",
|
||||||
|
DGPSId: "0000",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.EqualValues(t, expected, sentence, "Sentence values do not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGPGGABadType(t *testing.T) {
|
||||||
|
badType := "$GPRMC,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W*70"
|
||||||
|
s, err := Parse(badType)
|
||||||
|
|
||||||
|
assert.NoError(t, err, "Unexpected error parsing sentence")
|
||||||
|
assert.NotEqual(t, "GPGGA", s.Prefix(), "Unexpected sentence type")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGPGGABadLatitude(t *testing.T) {
|
||||||
|
badLat := "$GPGGA,034225.077,A,S,15124.5567,E,1,03,9.7,-25.0,M,21.0,M,,0000*3A"
|
||||||
|
_, err := Parse(badLat)
|
||||||
|
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, "nmea: GPGGA invalid latitude: cannot parse [A S], unknown format", err.Error(), "Error message does not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGPGGABadLongitude(t *testing.T) {
|
||||||
|
badLon := "$GPGGA,034225.077,3356.4650,S,A,E,1,03,9.7,-25.0,M,21.0,M,,0000*0C"
|
||||||
|
_, err := Parse(badLon)
|
||||||
|
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, "nmea: GPGGA invalid longitude: cannot parse [A E], unknown format", err.Error(), "Error message does not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGPGGABadFixQuality(t *testing.T) {
|
||||||
|
// Make sure bad fix mode is detected.
|
||||||
|
badMode := "$GPGGA,034225.077,3356.4650,S,15124.5567,E,5,03,9.7,-25.0,M,21.0,M,,0000*55"
|
||||||
|
_, err := Parse(badMode)
|
||||||
|
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, err.Error(), "nmea: GPGGA invalid fix quality: 5", "Error message not as expected")
|
||||||
|
}
|
32
vendor/github.com/adrianmo/go-nmea/gpgll.go
generated
vendored
Normal file
32
vendor/github.com/adrianmo/go-nmea/gpgll.go
generated
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PrefixGPGLL prefix for GPGLL sentence type
|
||||||
|
PrefixGPGLL = "GPGLL"
|
||||||
|
// ValidGLL character
|
||||||
|
ValidGLL = "A"
|
||||||
|
// InvalidGLL character
|
||||||
|
InvalidGLL = "V"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GPGLL is Geographic Position, Latitude / Longitude and time.
|
||||||
|
// http://aprs.gids.nl/nmea/#gll
|
||||||
|
type GPGLL struct {
|
||||||
|
Sent
|
||||||
|
Latitude LatLong // Latitude
|
||||||
|
Longitude LatLong // Longitude
|
||||||
|
Time Time // Time Stamp
|
||||||
|
Validity string // validity - A-valid
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGPGLL constructor
|
||||||
|
func NewGPGLL(s Sent) (GPGLL, error) {
|
||||||
|
p := newParser(s, PrefixGPGLL)
|
||||||
|
return GPGLL{
|
||||||
|
Sent: s,
|
||||||
|
Latitude: p.LatLong(0, 1, "latitude"),
|
||||||
|
Longitude: p.LatLong(2, 3, "longitude"),
|
||||||
|
Time: p.Time(4, "time"),
|
||||||
|
Validity: p.EnumString(5, "validity", ValidGLL, InvalidGLL),
|
||||||
|
}, p.Err()
|
||||||
|
}
|
38
vendor/github.com/adrianmo/go-nmea/gpgll_test.go
generated
vendored
Normal file
38
vendor/github.com/adrianmo/go-nmea/gpgll_test.go
generated
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGPGLLGoodSentence(t *testing.T) {
|
||||||
|
goodMsg := "$GPGLL,3926.7952,N,12000.5947,W,022732,A,A*58"
|
||||||
|
s, err := Parse(goodMsg)
|
||||||
|
|
||||||
|
assert.NoError(t, err, "Unexpected error parsing good sentence")
|
||||||
|
assert.Equal(t, PrefixGPGLL, s.Prefix(), "Prefix does not match")
|
||||||
|
|
||||||
|
sentence := s.(GPGLL)
|
||||||
|
|
||||||
|
assert.Equal(t, "3926.7952", sentence.Latitude.PrintGPS(), "Latitude does not match")
|
||||||
|
assert.Equal(t, "12000.5947", sentence.Longitude.PrintGPS(), "Longitude does not match")
|
||||||
|
assert.Equal(t, Time{true, 2, 27, 32, 0}, sentence.Time, "Time does not match")
|
||||||
|
assert.Equal(t, "A", sentence.Validity, "Status does not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGPGLLBadSentence(t *testing.T) {
|
||||||
|
badMsg := "$GPGLL,3926.7952,N,12000.5947,W,022732,D,A*5D"
|
||||||
|
_, err := Parse(badMsg)
|
||||||
|
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, "nmea: GPGLL invalid validity: D", err.Error(), "Incorrect error message")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGPGLLWrongSentence(t *testing.T) {
|
||||||
|
wrongMsg := "$GPXTE,A,A,4.07,L,N*6D"
|
||||||
|
_, err := Parse(wrongMsg)
|
||||||
|
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, "nmea: sentence type 'GPXTE' not implemented", err.Error(), "Incorrect error message")
|
||||||
|
}
|
49
vendor/github.com/adrianmo/go-nmea/gpgsa.go
generated
vendored
Normal file
49
vendor/github.com/adrianmo/go-nmea/gpgsa.go
generated
vendored
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PrefixGPGSA prefix of GPGSA sentence type
|
||||||
|
PrefixGPGSA = "GPGSA"
|
||||||
|
// Auto - Field 1, auto or manual fix.
|
||||||
|
Auto = "A"
|
||||||
|
// Manual - Field 1, auto or manual fix.
|
||||||
|
Manual = "M"
|
||||||
|
// FixNone - Field 2, fix type.
|
||||||
|
FixNone = "1"
|
||||||
|
// Fix2D - Field 2, fix type.
|
||||||
|
Fix2D = "2"
|
||||||
|
// Fix3D - Field 2, fix type.
|
||||||
|
Fix3D = "3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GPGSA represents overview satellite data.
|
||||||
|
// http://aprs.gids.nl/nmea/#gsa
|
||||||
|
type GPGSA struct {
|
||||||
|
Sent
|
||||||
|
Mode string // The selection mode.
|
||||||
|
FixType string // The fix type.
|
||||||
|
SV []string // List of satellite PRNs used for this fix.
|
||||||
|
PDOP float64 // Dilution of precision.
|
||||||
|
HDOP float64 // Horizontal dilution of precision.
|
||||||
|
VDOP float64 // Vertical dilution of precision.
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGPGSA parses the GPGSA sentence into this struct.
|
||||||
|
func NewGPGSA(s Sent) (GPGSA, error) {
|
||||||
|
p := newParser(s, PrefixGPGSA)
|
||||||
|
m := GPGSA{
|
||||||
|
Sent: s,
|
||||||
|
Mode: p.EnumString(0, "selection mode", Auto, Manual),
|
||||||
|
FixType: p.EnumString(1, "fix type", FixNone, Fix2D, Fix3D),
|
||||||
|
}
|
||||||
|
// Satellites in view.
|
||||||
|
for i := 2; i < 14; i++ {
|
||||||
|
if v := p.String(i, "satelite in view"); v != "" {
|
||||||
|
m.SV = append(m.SV, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Dilution of precision.
|
||||||
|
m.PDOP = p.Float64(14, "pdop")
|
||||||
|
m.HDOP = p.Float64(15, "hdop")
|
||||||
|
m.VDOP = p.Float64(16, "vdop")
|
||||||
|
return m, p.Err()
|
||||||
|
}
|
50
vendor/github.com/adrianmo/go-nmea/gpgsa_test.go
generated
vendored
Normal file
50
vendor/github.com/adrianmo/go-nmea/gpgsa_test.go
generated
vendored
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGPGSAGoodSentence(t *testing.T) {
|
||||||
|
goodMsg := "$GPGSA,A,3,22,19,18,27,14,03,,,,,,,3.1,2.0,2.4*36"
|
||||||
|
sentence, err := Parse(goodMsg)
|
||||||
|
|
||||||
|
assert.NoError(t, err, "Unexpected error parsing good sentence")
|
||||||
|
|
||||||
|
// Attributes of the parsed sentence, and their expected values.
|
||||||
|
expected := GPGSA{
|
||||||
|
Sent: Sent{
|
||||||
|
Type: "GPGSA",
|
||||||
|
Fields: []string{"A", "3", "22", "19", "18", "27", "14", "03", "", "", "", "", "", "", "3.1", "2.0", "2.4"},
|
||||||
|
Checksum: "36",
|
||||||
|
Raw: "$GPGSA,A,3,22,19,18,27,14,03,,,,,,,3.1,2.0,2.4*36",
|
||||||
|
},
|
||||||
|
Mode: Auto,
|
||||||
|
FixType: Fix3D,
|
||||||
|
PDOP: 3.1,
|
||||||
|
HDOP: 2.0,
|
||||||
|
VDOP: 2.4,
|
||||||
|
SV: []string{"22", "19", "18", "27", "14", "03"},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.EqualValues(t, expected, sentence, "Sentence values do not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGPGSABadMode(t *testing.T) {
|
||||||
|
// Make sure bad fix mode is detected.
|
||||||
|
badMode := "$GPGSA,F,3,22,19,18,27,14,03,,,,,,,3.1,2.0,2.4*31"
|
||||||
|
_, err := Parse(badMode)
|
||||||
|
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, "nmea: GPGSA invalid selection mode: F", err.Error(), "Error message does not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGPGSABadFix(t *testing.T) {
|
||||||
|
// Make sure bad fix type is detected.
|
||||||
|
badFixType := "$GPGSA,A,6,22,19,18,27,14,03,,,,,,,3.1,2.0,2.4*33"
|
||||||
|
_, err := Parse(badFixType)
|
||||||
|
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, "nmea: GPGSA invalid fix type: 6", err.Error(), "Error message does not match")
|
||||||
|
}
|
47
vendor/github.com/adrianmo/go-nmea/gpgsv.go
generated
vendored
Normal file
47
vendor/github.com/adrianmo/go-nmea/gpgsv.go
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PrefixGPGSV prefix
|
||||||
|
PrefixGPGSV = "GPGSV"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GPGSV represents the GPS Satellites in view
|
||||||
|
// http://aprs.gids.nl/nmea/#gpgsv
|
||||||
|
type GPGSV struct {
|
||||||
|
Sent
|
||||||
|
TotalMessages int64 // Total number of messages of this type in this cycle
|
||||||
|
MessageNumber int64 // Message number
|
||||||
|
NumberSVsInView int64 // Total number of SVs in view
|
||||||
|
Info []GPGSVInfo // visible satellite info (0-4 of these)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GPGSVInfo represents information about a visible satellite
|
||||||
|
type GPGSVInfo struct {
|
||||||
|
SVPRNNumber int64 // SV PRN number, pseudo-random noise or gold code
|
||||||
|
Elevation int64 // Elevation in degrees, 90 maximum
|
||||||
|
Azimuth int64 // Azimuth, degrees from true north, 000 to 359
|
||||||
|
SNR int64 // SNR, 00-99 dB (null when not tracking)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGPGSV constructor
|
||||||
|
func NewGPGSV(s Sent) (GPGSV, error) {
|
||||||
|
p := newParser(s, PrefixGPGSV)
|
||||||
|
m := GPGSV{
|
||||||
|
Sent: s,
|
||||||
|
TotalMessages: p.Int64(0, "total number of messages"),
|
||||||
|
MessageNumber: p.Int64(1, "message number"),
|
||||||
|
NumberSVsInView: p.Int64(2, "number of SVs in view"),
|
||||||
|
}
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
if 5*i+4 > len(m.Fields) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
m.Info = append(m.Info, GPGSVInfo{
|
||||||
|
SVPRNNumber: p.Int64(3+i*4, "SV prn number"),
|
||||||
|
Elevation: p.Int64(4+i*4, "elevation"),
|
||||||
|
Azimuth: p.Int64(5+i*4, "azimuth"),
|
||||||
|
SNR: p.Int64(6+i*4, "SNR"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return m, p.Err()
|
||||||
|
}
|
96
vendor/github.com/adrianmo/go-nmea/gpgsv_test.go
generated
vendored
Normal file
96
vendor/github.com/adrianmo/go-nmea/gpgsv_test.go
generated
vendored
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGPGSVGoodSentence(t *testing.T) {
|
||||||
|
goodMsg := "$GPGSV,3,1,11,03,03,111,00,04,15,270,00,06,01,010,12,13,06,292,00*77"
|
||||||
|
s, err := Parse(goodMsg)
|
||||||
|
|
||||||
|
assert.NoError(t, err, "Unexpected error parsing good sentence")
|
||||||
|
assert.Equal(t, PrefixGPGSV, s.Prefix(), "Prefix does not match")
|
||||||
|
|
||||||
|
sentence := s.(GPGSV)
|
||||||
|
assert.Equal(t, int64(3), sentence.TotalMessages, "Total messages does not match")
|
||||||
|
assert.Equal(t, int64(1), sentence.MessageNumber, "Message number does not match")
|
||||||
|
assert.Equal(t, int64(11), sentence.NumberSVsInView, "Number of SVs in view does not match")
|
||||||
|
|
||||||
|
assert.Equal(t, int64(3), sentence.Info[0].SVPRNNumber, "Number of Info[0] SV PRN does not match")
|
||||||
|
assert.Equal(t, int64(3), sentence.Info[0].Elevation, "Number of Info[0] Elevation does not match")
|
||||||
|
assert.Equal(t, int64(111), sentence.Info[0].Azimuth, "Number of Info[0] Azimuth does not match")
|
||||||
|
assert.Equal(t, int64(0), sentence.Info[0].SNR, "Number of Info[0] SNR does not match")
|
||||||
|
|
||||||
|
assert.Equal(t, int64(4), sentence.Info[1].SVPRNNumber, "Number of Info[1] SV PRN does not match")
|
||||||
|
assert.Equal(t, int64(15), sentence.Info[1].Elevation, "Number of Info[1] Elevation does not match")
|
||||||
|
assert.Equal(t, int64(270), sentence.Info[1].Azimuth, "Number of Info[1] Azimuth does not match")
|
||||||
|
assert.Equal(t, int64(0), sentence.Info[1].SNR, "Number of Info[1] SNR does not match")
|
||||||
|
|
||||||
|
assert.Equal(t, int64(6), sentence.Info[2].SVPRNNumber, "Number of Info[2] SV PRN does not match")
|
||||||
|
assert.Equal(t, int64(1), sentence.Info[2].Elevation, "Number of Info[2] Elevation does not match")
|
||||||
|
assert.Equal(t, int64(10), sentence.Info[2].Azimuth, "Number of Info[2] Azimuth does not match")
|
||||||
|
assert.Equal(t, int64(12), sentence.Info[2].SNR, "Number of Info[2] SNR does not match")
|
||||||
|
|
||||||
|
assert.Equal(t, int64(13), sentence.Info[3].SVPRNNumber, "Number of Info[3] SV PRN does not match")
|
||||||
|
assert.Equal(t, int64(6), sentence.Info[3].Elevation, "Number of Info[3] Elevation does not match")
|
||||||
|
assert.Equal(t, int64(292), sentence.Info[3].Azimuth, "Number of Info[3] Azimuth does not match")
|
||||||
|
assert.Equal(t, int64(0), sentence.Info[3].SNR, "Number of Info[3] SNR does not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGPGSVShort(t *testing.T) {
|
||||||
|
goodMsg := "$GPGSV,3,1,11,03,03,111,00,04,15,270,00,06,01,010,12*4A"
|
||||||
|
s, err := Parse(goodMsg)
|
||||||
|
|
||||||
|
assert.NoError(t, err, "Unexpected error parsing good sentence")
|
||||||
|
assert.Equal(t, PrefixGPGSV, s.Prefix(), "Prefix does not match")
|
||||||
|
|
||||||
|
sentence := s.(GPGSV)
|
||||||
|
assert.Equal(t, int64(3), sentence.TotalMessages, "Total messages does not match")
|
||||||
|
assert.Equal(t, int64(1), sentence.MessageNumber, "Message number does not match")
|
||||||
|
assert.Equal(t, int64(11), sentence.NumberSVsInView, "Number of SVs in view does not match")
|
||||||
|
|
||||||
|
assert.Equal(t, int64(3), sentence.Info[0].SVPRNNumber, "Number of Info[0] SV PRN does not match")
|
||||||
|
assert.Equal(t, int64(3), sentence.Info[0].Elevation, "Number of Info[0] Elevation does not match")
|
||||||
|
assert.Equal(t, int64(111), sentence.Info[0].Azimuth, "Number of Info[0] Azimuth does not match")
|
||||||
|
assert.Equal(t, int64(0), sentence.Info[0].SNR, "Number of Info[0] SNR does not match")
|
||||||
|
|
||||||
|
assert.Equal(t, int64(4), sentence.Info[1].SVPRNNumber, "Number of Info[1] SV PRN does not match")
|
||||||
|
assert.Equal(t, int64(15), sentence.Info[1].Elevation, "Number of Info[1] Elevation does not match")
|
||||||
|
assert.Equal(t, int64(270), sentence.Info[1].Azimuth, "Number of Info[1] Azimuth does not match")
|
||||||
|
assert.Equal(t, int64(0), sentence.Info[1].SNR, "Number of Info[1] SNR does not match")
|
||||||
|
|
||||||
|
assert.Equal(t, int64(6), sentence.Info[2].SVPRNNumber, "Number of Info[2] SV PRN does not match")
|
||||||
|
assert.Equal(t, int64(1), sentence.Info[2].Elevation, "Number of Info[2] Elevation does not match")
|
||||||
|
assert.Equal(t, int64(10), sentence.Info[2].Azimuth, "Number of Info[2] Azimuth does not match")
|
||||||
|
assert.Equal(t, int64(12), sentence.Info[2].SNR, "Number of Info[2] SNR does not match")
|
||||||
|
}
|
||||||
|
func TestGPGSVBadSentence(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Input string
|
||||||
|
Error string
|
||||||
|
}{
|
||||||
|
{"$GPGSV,3,1,11.2,03,03,111,00,04,15,270,00,06,01,010,12,13,06,292,00*6b", "nmea: GPGSV invalid number of SVs in view: 11.2"},
|
||||||
|
{"$GPGSV,A3,1,11,03,03,111,00,04,15,270,00,06,01,010,12,13,06,292,00*36", "nmea: GPGSV invalid total number of messages: A3"},
|
||||||
|
{"$GPGSV,3,A1,11,03,03,111,00,04,15,270,00,06,01,010,12,13,06,292,00*36", "nmea: GPGSV invalid message number: A1"},
|
||||||
|
{"$GPGSV,3,1,11,A03,03,111,00,04,15,270,00,06,01,010,12,13,06,292,00*36", "nmea: GPGSV invalid SV prn number: A03"},
|
||||||
|
{"$GPGSV,3,1,11,03,A03,111,00,04,15,270,00,06,01,010,12,13,06,292,00*36", "nmea: GPGSV invalid elevation: A03"},
|
||||||
|
{"$GPGSV,3,1,11,03,03,A111,00,04,15,270,00,06,01,010,12,13,06,292,00*36", "nmea: GPGSV invalid azimuth: A111"},
|
||||||
|
{"$GPGSV,3,1,11,03,03,111,A00,04,15,270,00,06,01,010,12,13,06,292,00*36", "nmea: GPGSV invalid SNR: A00"},
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
_, err := Parse(tc.Input)
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, tc.Error, err.Error(), "Incorrect error message")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGPGSVWrongSentence(t *testing.T) {
|
||||||
|
wrongMsg := "$GPXTE,A,A,4.07,L,N*6D"
|
||||||
|
sent, _ := ParseSentence(wrongMsg)
|
||||||
|
_, err := NewGPGSV(sent)
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, "nmea: GPGSV invalid prefix: GPXTE", err.Error(), "Incorrect error message")
|
||||||
|
}
|
44
vendor/github.com/adrianmo/go-nmea/gprmc.go
generated
vendored
Normal file
44
vendor/github.com/adrianmo/go-nmea/gprmc.go
generated
vendored
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PrefixGPRMC prefix of GPRMC sentence type
|
||||||
|
PrefixGPRMC = "GPRMC"
|
||||||
|
// ValidRMC character
|
||||||
|
ValidRMC = "A"
|
||||||
|
// InvalidRMC character
|
||||||
|
InvalidRMC = "V"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GPRMC is the Recommended Minimum Specific GNSS data.
|
||||||
|
// http://aprs.gids.nl/nmea/#rmc
|
||||||
|
type GPRMC struct {
|
||||||
|
Sent
|
||||||
|
Time Time // Time Stamp
|
||||||
|
Validity string // validity - A-ok, V-invalid
|
||||||
|
Latitude LatLong // Latitude
|
||||||
|
Longitude LatLong // Longitude
|
||||||
|
Speed float64 // Speed in knots
|
||||||
|
Course float64 // True course
|
||||||
|
Date Date // Date
|
||||||
|
Variation float64 // Magnetic variation
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGPRMC constructor
|
||||||
|
func NewGPRMC(s Sent) (GPRMC, error) {
|
||||||
|
p := newParser(s, PrefixGPRMC)
|
||||||
|
m := GPRMC{
|
||||||
|
Sent: s,
|
||||||
|
Time: p.Time(0, "time"),
|
||||||
|
Validity: p.EnumString(1, "validity", ValidRMC, InvalidRMC),
|
||||||
|
Latitude: p.LatLong(2, 3, "latitude"),
|
||||||
|
Longitude: p.LatLong(4, 5, "longitude"),
|
||||||
|
Speed: p.Float64(6, "speed"),
|
||||||
|
Course: p.Float64(7, "course"),
|
||||||
|
Date: p.Date(8, "date"),
|
||||||
|
Variation: p.Float64(9, "variation"),
|
||||||
|
}
|
||||||
|
if p.EnumString(10, "variation", West, East) == West {
|
||||||
|
m.Variation = 0 - m.Variation
|
||||||
|
}
|
||||||
|
return m, p.Err()
|
||||||
|
}
|
86
vendor/github.com/adrianmo/go-nmea/gprmc_test.go
generated
vendored
Normal file
86
vendor/github.com/adrianmo/go-nmea/gprmc_test.go
generated
vendored
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MustParseGPS(s string) LatLong {
|
||||||
|
ll, err := ParseGPS(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return ll
|
||||||
|
}
|
||||||
|
|
||||||
|
var gprmctests = []struct {
|
||||||
|
Input string
|
||||||
|
Output GPRMC
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"$GPRMC,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W*70",
|
||||||
|
GPRMC{
|
||||||
|
Time: Time{true, 22, 05, 16, 0},
|
||||||
|
Validity: "A",
|
||||||
|
Speed: 173.8,
|
||||||
|
Course: 231.8,
|
||||||
|
Date: Date{true, 13, 6, 94},
|
||||||
|
Variation: -4.2,
|
||||||
|
Latitude: MustParseGPS("5133.82 N"),
|
||||||
|
Longitude: MustParseGPS("00042.24 W"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$GPRMC,142754.0,A,4302.539570,N,07920.379823,W,0.0,,070617,0.0,E,A*3F",
|
||||||
|
GPRMC{
|
||||||
|
Time: Time{true, 14, 27, 54, 0},
|
||||||
|
Validity: "A",
|
||||||
|
Speed: 0,
|
||||||
|
Course: 0,
|
||||||
|
Date: Date{true, 7, 6, 17},
|
||||||
|
Variation: 0,
|
||||||
|
Latitude: MustParseGPS("4302.539570 N"),
|
||||||
|
Longitude: MustParseGPS("07920.379823 W"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGPRMCGoodSentence(t *testing.T) {
|
||||||
|
|
||||||
|
for _, tt := range gprmctests {
|
||||||
|
|
||||||
|
s, err := Parse(tt.Input)
|
||||||
|
|
||||||
|
assert.NoError(t, err, "Unexpected error parsing good sentence")
|
||||||
|
assert.Equal(t, PrefixGPRMC, s.Prefix(), "Prefix does not match")
|
||||||
|
|
||||||
|
sentence := s.(GPRMC)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.Output.Time, sentence.Time, "Time does not match")
|
||||||
|
assert.Equal(t, tt.Output.Validity, sentence.Validity, "Status does not match")
|
||||||
|
assert.Equal(t, tt.Output.Speed, sentence.Speed, "Speed does not match")
|
||||||
|
assert.Equal(t, tt.Output.Course, sentence.Course, "Course does not match")
|
||||||
|
assert.Equal(t, tt.Output.Date, sentence.Date, "Date does not match")
|
||||||
|
assert.Equal(t, tt.Output.Variation, sentence.Variation, "Variation does not match")
|
||||||
|
assert.Equal(t, tt.Output.Latitude, sentence.Latitude, "Latitude does not match")
|
||||||
|
assert.Equal(t, tt.Output.Longitude, sentence.Longitude, "Longitude does not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGPRMCBadSentence(t *testing.T) {
|
||||||
|
badMsg := "$GPRMC,220516,D,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W*75"
|
||||||
|
_, err := Parse(badMsg)
|
||||||
|
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, "nmea: GPRMC invalid validity: D", err.Error(), "Incorrect error message")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGPRMCWrongSentence(t *testing.T) {
|
||||||
|
wrongMsg := "$GPXTE,A,A,4.07,L,N*6D"
|
||||||
|
_, err := Parse(wrongMsg)
|
||||||
|
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, "nmea: sentence type 'GPXTE' not implemented", err.Error(), "Incorrect error message")
|
||||||
|
}
|
29
vendor/github.com/adrianmo/go-nmea/gpvtg.go
generated
vendored
Normal file
29
vendor/github.com/adrianmo/go-nmea/gpvtg.go
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PrefixGPVTG prefix
|
||||||
|
PrefixGPVTG = "GPVTG"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GPVTG represents track & speed data.
|
||||||
|
// http://aprs.gids.nl/nmea/#vtg
|
||||||
|
type GPVTG struct {
|
||||||
|
Sent
|
||||||
|
TrueTrack float64
|
||||||
|
MagneticTrack float64
|
||||||
|
GroundSpeedKnots float64
|
||||||
|
GroundSpeedKPH float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGPVTG parses the GPVTG sentence into this struct.
|
||||||
|
// e.g: $GPVTG,360.0,T,348.7,M,000.0,N,000.0,K*43
|
||||||
|
func NewGPVTG(s Sent) (GPVTG, error) {
|
||||||
|
p := newParser(s, PrefixGPVTG)
|
||||||
|
return GPVTG{
|
||||||
|
Sent: s,
|
||||||
|
TrueTrack: p.Float64(0, "true track"),
|
||||||
|
MagneticTrack: p.Float64(2, "magnetic track"),
|
||||||
|
GroundSpeedKnots: p.Float64(4, "ground speed (knots)"),
|
||||||
|
GroundSpeedKPH: p.Float64(6, "ground speed (km/h)"),
|
||||||
|
}, p.Err()
|
||||||
|
}
|
38
vendor/github.com/adrianmo/go-nmea/gpvtg_test.go
generated
vendored
Normal file
38
vendor/github.com/adrianmo/go-nmea/gpvtg_test.go
generated
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGPVTGGoodSentence(t *testing.T) {
|
||||||
|
goodMsg := "$GPVTG,45.5,T,67.5,M,30.45,N,56.40,K*4B"
|
||||||
|
s, err := Parse(goodMsg)
|
||||||
|
|
||||||
|
assert.NoError(t, err, "Unexpected error parsing good sentence")
|
||||||
|
assert.Equal(t, PrefixGPVTG, s.Prefix(), "Prefix does not match")
|
||||||
|
|
||||||
|
sentence := s.(GPVTG)
|
||||||
|
|
||||||
|
assert.Equal(t, 45.5, sentence.TrueTrack, "True track does not match")
|
||||||
|
assert.Equal(t, 67.5, sentence.MagneticTrack, "Magnetic track does not match")
|
||||||
|
assert.Equal(t, 30.45, sentence.GroundSpeedKnots, "Ground speed (knots) does not match")
|
||||||
|
assert.Equal(t, 56.40, sentence.GroundSpeedKPH, "Ground speed (km/h) does not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGPVTGBadSentence(t *testing.T) {
|
||||||
|
badMsg := "$GPVTG,T,45.5,67.5,M,30.45,N,56.40,K*4B"
|
||||||
|
_, err := Parse(badMsg)
|
||||||
|
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, "nmea: GPVTG invalid true track: T", err.Error(), "Incorrect error message")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGPVTGWrongSentence(t *testing.T) {
|
||||||
|
wrongMsg := "$GPXTE,A,A,4.07,L,N*6D"
|
||||||
|
_, err := Parse(wrongMsg)
|
||||||
|
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, "nmea: sentence type 'GPXTE' not implemented", err.Error(), "Incorrect error message")
|
||||||
|
}
|
32
vendor/github.com/adrianmo/go-nmea/gpzda.go
generated
vendored
Normal file
32
vendor/github.com/adrianmo/go-nmea/gpzda.go
generated
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PrefixGPZDA prefix
|
||||||
|
PrefixGPZDA = "GPZDA"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GPZDA represents date & time data.
|
||||||
|
// http://aprs.gids.nl/nmea/#zda
|
||||||
|
type GPZDA struct {
|
||||||
|
Sent
|
||||||
|
Time Time
|
||||||
|
Day int64
|
||||||
|
Month int64
|
||||||
|
Year int64
|
||||||
|
OffsetHours int64 // Local time zone offset from GMT, hours
|
||||||
|
OffsetMinutes int64 // Local time zone offset from GMT, minutes
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGPZDA constructor
|
||||||
|
func NewGPZDA(s Sent) (GPZDA, error) {
|
||||||
|
p := newParser(s, PrefixGPZDA)
|
||||||
|
return GPZDA{
|
||||||
|
Sent: s,
|
||||||
|
Time: p.Time(0, "time"),
|
||||||
|
Day: p.Int64(1, "day"),
|
||||||
|
Month: p.Int64(2, "month"),
|
||||||
|
Year: p.Int64(3, "year"),
|
||||||
|
OffsetHours: p.Int64(4, "offset (hours)"),
|
||||||
|
OffsetMinutes: p.Int64(5, "offset (minutes)"),
|
||||||
|
}, p.Err()
|
||||||
|
}
|
40
vendor/github.com/adrianmo/go-nmea/gpzda_test.go
generated
vendored
Normal file
40
vendor/github.com/adrianmo/go-nmea/gpzda_test.go
generated
vendored
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGPZDAGoodSentence(t *testing.T) {
|
||||||
|
goodMsg := "$GPZDA,172809.456,12,07,1996,00,00*57"
|
||||||
|
s, err := Parse(goodMsg)
|
||||||
|
|
||||||
|
assert.NoError(t, err, "Unexpected error parsing good sentence")
|
||||||
|
assert.Equal(t, PrefixGPZDA, s.Prefix(), "Prefix does not match")
|
||||||
|
|
||||||
|
sentence := s.(GPZDA)
|
||||||
|
|
||||||
|
assert.Equal(t, Time{true, 17, 28, 9, 456}, sentence.Time, "Time does not match")
|
||||||
|
assert.Equal(t, int64(12), sentence.Day, "Day does not match")
|
||||||
|
assert.Equal(t, int64(7), sentence.Month, "Month does not match")
|
||||||
|
assert.Equal(t, int64(1996), sentence.Year, "Yeah does not match")
|
||||||
|
assert.Equal(t, int64(0), sentence.OffsetHours, "Offset (hours) does not match")
|
||||||
|
assert.Equal(t, int64(0), sentence.OffsetMinutes, "Offset (minutes) does not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGPZDABadSentence(t *testing.T) {
|
||||||
|
badMsg := "$GPZDA,220516,D,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W*76"
|
||||||
|
_, err := Parse(badMsg)
|
||||||
|
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, "nmea: GPZDA invalid day: D", err.Error(), "Incorrect error message")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGPZDAWrongSentence(t *testing.T) {
|
||||||
|
wrongMsg := "$GPXTE,A,A,4.07,L,N*6D"
|
||||||
|
_, err := Parse(wrongMsg)
|
||||||
|
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, "nmea: sentence type 'GPXTE' not implemented", err.Error(), "Incorrect error message")
|
||||||
|
}
|
141
vendor/github.com/adrianmo/go-nmea/parser.go
generated
vendored
Normal file
141
vendor/github.com/adrianmo/go-nmea/parser.go
generated
vendored
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// parser provides a simple way of accessing and parsing
|
||||||
|
// sentence fields
|
||||||
|
type parser struct {
|
||||||
|
Sent
|
||||||
|
prefix string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// newParser constructor
|
||||||
|
func newParser(s Sent, prefix string) *parser {
|
||||||
|
p := &parser{Sent: s, prefix: prefix}
|
||||||
|
if p.Type != prefix {
|
||||||
|
p.SetErr("prefix", p.Type)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns the first error encounterd during the parser's usage.
|
||||||
|
func (p *parser) Err() error {
|
||||||
|
return p.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetErr assigns an error. Calling this method has no
|
||||||
|
// effect if there is already an error.
|
||||||
|
func (p *parser) SetErr(context, value string) {
|
||||||
|
if p.err == nil {
|
||||||
|
p.err = fmt.Errorf("nmea: %s invalid %s: %s", p.prefix, context, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the field value at the specified index.
|
||||||
|
func (p *parser) String(i int, context string) string {
|
||||||
|
if p.err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if i < 0 || i >= len(p.Fields) {
|
||||||
|
p.SetErr(context, "index out of range")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return p.Fields[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnumString returns the field value at the specified index.
|
||||||
|
// An error occurs if the value is not one of the options.
|
||||||
|
func (p *parser) EnumString(i int, context string, options ...string) string {
|
||||||
|
s := p.String(i, context)
|
||||||
|
if p.err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
for _, o := range options {
|
||||||
|
if o == s {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.SetErr(context, s)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 returns the int64 value at the specified index.
|
||||||
|
// If the value is an emtpy string, 0 is returned.
|
||||||
|
func (p *parser) Int64(i int, context string) int64 {
|
||||||
|
s := p.String(i, context)
|
||||||
|
if p.err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
v, err := strconv.ParseInt(s, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
p.SetErr(context, s)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64 returns the float64 value at the specified index.
|
||||||
|
// If the value is an empty string, 0 is returned.
|
||||||
|
func (p *parser) Float64(i int, context string) float64 {
|
||||||
|
s := p.String(i, context)
|
||||||
|
if p.err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
v, err := strconv.ParseFloat(s, 64)
|
||||||
|
if err != nil {
|
||||||
|
p.SetErr(context, s)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time returns the Time value at the specified index.
|
||||||
|
// If the value is empty, the Time is marked as invalid.
|
||||||
|
func (p *parser) Time(i int, context string) Time {
|
||||||
|
s := p.String(i, context)
|
||||||
|
if p.err != nil {
|
||||||
|
return Time{}
|
||||||
|
}
|
||||||
|
v, err := ParseTime(s)
|
||||||
|
if err != nil {
|
||||||
|
p.SetErr(context, s)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Date returns the Date value at the specified index.
|
||||||
|
// If the value is empty, the Date is marked as invalid.
|
||||||
|
func (p *parser) Date(i int, context string) Date {
|
||||||
|
s := p.String(i, context)
|
||||||
|
if p.err != nil {
|
||||||
|
return Date{}
|
||||||
|
}
|
||||||
|
v, err := ParseDate(s)
|
||||||
|
if err != nil {
|
||||||
|
p.SetErr(context, s)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// LatLong returns the coordinate value of the specified fields.
|
||||||
|
func (p *parser) LatLong(i, j int, context string) LatLong {
|
||||||
|
a := p.String(i, context)
|
||||||
|
b := p.String(j, context)
|
||||||
|
if p.err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
s := fmt.Sprintf("%s %s", a, b)
|
||||||
|
v, err := ParseLatLong(s)
|
||||||
|
if err != nil {
|
||||||
|
p.SetErr(context, err.Error())
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
226
vendor/github.com/adrianmo/go-nmea/parser_test.go
generated
vendored
Normal file
226
vendor/github.com/adrianmo/go-nmea/parser_test.go
generated
vendored
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var parsertests = []struct {
|
||||||
|
name string
|
||||||
|
fields []string
|
||||||
|
expected interface{}
|
||||||
|
hasErr bool
|
||||||
|
parse func(p *parser) interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "String",
|
||||||
|
fields: []string{"foo", "bar"},
|
||||||
|
expected: "bar",
|
||||||
|
parse: func(p *parser) interface{} {
|
||||||
|
return p.String(1, "")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "String out of range",
|
||||||
|
fields: []string{"wot"},
|
||||||
|
expected: "",
|
||||||
|
hasErr: true,
|
||||||
|
parse: func(p *parser) interface{} {
|
||||||
|
return p.String(5, "thing")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "String with existing error",
|
||||||
|
expected: "",
|
||||||
|
hasErr: true,
|
||||||
|
parse: func(p *parser) interface{} {
|
||||||
|
p.SetErr("context", "value")
|
||||||
|
return p.String(123, "blah")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EnumString",
|
||||||
|
fields: []string{"a", "b", "c"},
|
||||||
|
expected: "b",
|
||||||
|
parse: func(p *parser) interface{} {
|
||||||
|
return p.EnumString(1, "context", "b", "d")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EnumString invalid",
|
||||||
|
fields: []string{"a", "b", "c"},
|
||||||
|
expected: "",
|
||||||
|
hasErr: true,
|
||||||
|
parse: func(p *parser) interface{} {
|
||||||
|
return p.EnumString(1, "context", "x", "y")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EnumString with existing error",
|
||||||
|
fields: []string{"a", "b", "c"},
|
||||||
|
expected: "",
|
||||||
|
hasErr: true,
|
||||||
|
parse: func(p *parser) interface{} {
|
||||||
|
p.SetErr("context", "value")
|
||||||
|
return p.EnumString(1, "context", "a", "b")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Int64",
|
||||||
|
fields: []string{"123"},
|
||||||
|
expected: int64(123),
|
||||||
|
parse: func(p *parser) interface{} {
|
||||||
|
return p.Int64(0, "context")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Int64 empty field is zero",
|
||||||
|
fields: []string{""},
|
||||||
|
expected: int64(0),
|
||||||
|
parse: func(p *parser) interface{} {
|
||||||
|
return p.Int64(0, "context")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Int64 invalid",
|
||||||
|
fields: []string{"abc"},
|
||||||
|
expected: int64(0),
|
||||||
|
hasErr: true,
|
||||||
|
parse: func(p *parser) interface{} {
|
||||||
|
return p.Int64(0, "context")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Int64 with existing error",
|
||||||
|
fields: []string{"123"},
|
||||||
|
expected: int64(0),
|
||||||
|
hasErr: true,
|
||||||
|
parse: func(p *parser) interface{} {
|
||||||
|
p.SetErr("context", "value")
|
||||||
|
return p.Int64(0, "context")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Float64",
|
||||||
|
fields: []string{"123.123"},
|
||||||
|
expected: float64(123.123),
|
||||||
|
parse: func(p *parser) interface{} {
|
||||||
|
return p.Float64(0, "context")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Float64 empty field is zero",
|
||||||
|
fields: []string{""},
|
||||||
|
expected: float64(0),
|
||||||
|
parse: func(p *parser) interface{} {
|
||||||
|
return p.Float64(0, "context")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Float64 invalid",
|
||||||
|
fields: []string{"abc"},
|
||||||
|
expected: float64(0),
|
||||||
|
hasErr: true,
|
||||||
|
parse: func(p *parser) interface{} {
|
||||||
|
return p.Float64(0, "context")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Float64 with existing error",
|
||||||
|
fields: []string{"123.123"},
|
||||||
|
expected: float64(0),
|
||||||
|
hasErr: true,
|
||||||
|
parse: func(p *parser) interface{} {
|
||||||
|
p.SetErr("context", "value")
|
||||||
|
return p.Float64(0, "context")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Time",
|
||||||
|
fields: []string{"123456"},
|
||||||
|
expected: Time{true, 12, 34, 56, 0},
|
||||||
|
parse: func(p *parser) interface{} {
|
||||||
|
return p.Time(0, "context")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Time empty field is zero",
|
||||||
|
fields: []string{""},
|
||||||
|
expected: Time{},
|
||||||
|
parse: func(p *parser) interface{} {
|
||||||
|
return p.Time(0, "context")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Time with existing error",
|
||||||
|
fields: []string{"123456"},
|
||||||
|
expected: Time{},
|
||||||
|
hasErr: true,
|
||||||
|
parse: func(p *parser) interface{} {
|
||||||
|
p.SetErr("context", "value")
|
||||||
|
return p.Time(0, "context")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Time invalid",
|
||||||
|
fields: []string{"wrong"},
|
||||||
|
expected: Time{},
|
||||||
|
hasErr: true,
|
||||||
|
parse: func(p *parser) interface{} {
|
||||||
|
return p.Time(0, "context")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Date",
|
||||||
|
fields: []string{"010203"},
|
||||||
|
expected: Date{true, 1, 2, 3},
|
||||||
|
parse: func(p *parser) interface{} {
|
||||||
|
return p.Date(0, "context")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Date empty field is zero",
|
||||||
|
fields: []string{""},
|
||||||
|
expected: Date{},
|
||||||
|
parse: func(p *parser) interface{} {
|
||||||
|
return p.Date(0, "context")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Date invalid",
|
||||||
|
fields: []string{"Hello"},
|
||||||
|
expected: Date{},
|
||||||
|
hasErr: true,
|
||||||
|
parse: func(p *parser) interface{} {
|
||||||
|
return p.Date(0, "context")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Date with existing error",
|
||||||
|
fields: []string{"010203"},
|
||||||
|
expected: Date{},
|
||||||
|
hasErr: true,
|
||||||
|
parse: func(p *parser) interface{} {
|
||||||
|
p.SetErr("context", "value")
|
||||||
|
return p.Date(0, "context")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParser(t *testing.T) {
|
||||||
|
for _, tt := range parsertests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
p := newParser(Sent{
|
||||||
|
Type: "type",
|
||||||
|
Fields: tt.fields,
|
||||||
|
}, "type")
|
||||||
|
assert.Equal(t, tt.expected, tt.parse(p))
|
||||||
|
if tt.hasErr {
|
||||||
|
assert.Error(t, p.Err())
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, p.Err())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
38
vendor/github.com/adrianmo/go-nmea/pgrme.go
generated
vendored
Normal file
38
vendor/github.com/adrianmo/go-nmea/pgrme.go
generated
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PrefixPGRME prefix for PGRME sentence type
|
||||||
|
PrefixPGRME = "PGRME"
|
||||||
|
// ErrorUnit must be meters (M)
|
||||||
|
ErrorUnit = "M"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PGRME is Estimated Position Error (Garmin proprietary sentence)
|
||||||
|
// http://aprs.gids.nl/nmea/#rme
|
||||||
|
type PGRME struct {
|
||||||
|
Sent
|
||||||
|
Horizontal float64 // Estimated horizontal position error (HPE) in metres
|
||||||
|
Vertical float64 // Estimated vertical position error (VPE) in metres
|
||||||
|
Spherical float64 // Overall spherical equivalent position error in meters
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPGRME constructor
|
||||||
|
func NewPGRME(s Sent) (PGRME, error) {
|
||||||
|
p := newParser(s, PrefixPGRME)
|
||||||
|
|
||||||
|
horizontal := p.Float64(0, "horizontal error")
|
||||||
|
_ = p.EnumString(1, "horizontal error unit", ErrorUnit)
|
||||||
|
|
||||||
|
vertial := p.Float64(2, "vertical error")
|
||||||
|
_ = p.EnumString(3, "vertical error unit", ErrorUnit)
|
||||||
|
|
||||||
|
spherical := p.Float64(4, "spherical error")
|
||||||
|
_ = p.EnumString(5, "spherical error unit", ErrorUnit)
|
||||||
|
|
||||||
|
return PGRME{
|
||||||
|
Sent: s,
|
||||||
|
Horizontal: horizontal,
|
||||||
|
Vertical: vertial,
|
||||||
|
Spherical: spherical,
|
||||||
|
}, p.Err()
|
||||||
|
}
|
70
vendor/github.com/adrianmo/go-nmea/pgrme_test.go
generated
vendored
Normal file
70
vendor/github.com/adrianmo/go-nmea/pgrme_test.go
generated
vendored
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPGRMEGoodSentence(t *testing.T) {
|
||||||
|
goodMsg := "$PGRME,3.3,M,4.9,M,6.0,M*25"
|
||||||
|
s, err := Parse(goodMsg)
|
||||||
|
|
||||||
|
assert.NoError(t, err, "Unexpected error parsing good sentence")
|
||||||
|
assert.Equal(t, PrefixPGRME, s.Prefix(), "Prefix does not match")
|
||||||
|
|
||||||
|
sentence := s.(PGRME)
|
||||||
|
|
||||||
|
assert.Equal(t, 3.3, sentence.Horizontal, "Horizontal error does not match")
|
||||||
|
assert.Equal(t, 4.9, sentence.Vertical, "Vertical error does not match")
|
||||||
|
assert.Equal(t, 6.0, sentence.Spherical, "Spherical error does not match")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPGRMEInvalidHorizontalError(t *testing.T) {
|
||||||
|
badMsg := "$PGRME,A,M,4.9,M,6.0,M*4A"
|
||||||
|
_, err := Parse(badMsg)
|
||||||
|
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, "nmea: PGRME invalid horizontal error: A", err.Error(), "Incorrect error message")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPGRMEInvalidHorizontalErrorUnit(t *testing.T) {
|
||||||
|
badMsg := "$PGRME,3.3,A,4.9,M,6.0,M*29"
|
||||||
|
_, err := Parse(badMsg)
|
||||||
|
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, "nmea: PGRME invalid horizontal error unit: A", err.Error(), "Incorrect error message")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPGRMEInvalidVerticalError(t *testing.T) {
|
||||||
|
badMsg := "$PGRME,3.3,M,A,M,6.0,M*47"
|
||||||
|
_, err := Parse(badMsg)
|
||||||
|
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, "nmea: PGRME invalid vertical error: A", err.Error(), "Incorrect error message")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPGRMEInvalidVerticalErrorUnit(t *testing.T) {
|
||||||
|
badMsg := "$PGRME,3.3,M,4.9,A,6.0,M*29"
|
||||||
|
_, err := Parse(badMsg)
|
||||||
|
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, "nmea: PGRME invalid vertical error unit: A", err.Error(), "Incorrect error message")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPGRMEInvalidSphericalError(t *testing.T) {
|
||||||
|
badMsg := "$PGRME,3.3,M,4.9,M,A,M*4C"
|
||||||
|
_, err := Parse(badMsg)
|
||||||
|
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, "nmea: PGRME invalid spherical error: A", err.Error(), "Incorrect error message")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPGRMEInvalidSphericalErrorUnit(t *testing.T) {
|
||||||
|
badMsg := "$PGRME,3.3,M,4.9,M,6.0,A*29"
|
||||||
|
_, err := Parse(badMsg)
|
||||||
|
|
||||||
|
assert.Error(t, err, "Parse error not returned")
|
||||||
|
assert.Equal(t, "nmea: PGRME invalid spherical error unit: A", err.Error(), "Incorrect error message")
|
||||||
|
}
|
118
vendor/github.com/adrianmo/go-nmea/sentence.go
generated
vendored
Normal file
118
vendor/github.com/adrianmo/go-nmea/sentence.go
generated
vendored
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// SentenceStart is the token to indicate the start of a sentence.
|
||||||
|
SentenceStart = "$"
|
||||||
|
|
||||||
|
// FieldSep is the token to delimit fields of a sentence.
|
||||||
|
FieldSep = ","
|
||||||
|
|
||||||
|
// ChecksumSep is the token to delimit the checksum of a sentence.
|
||||||
|
ChecksumSep = "*"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Message interface for all NMEA sentence
|
||||||
|
type Message interface {
|
||||||
|
fmt.Stringer
|
||||||
|
Sentence() Sent
|
||||||
|
Prefix() string
|
||||||
|
Validate() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sent contains the information about the NMEA sentence
|
||||||
|
type Sent struct {
|
||||||
|
Type string // The sentence type (e.g $GPGSA)
|
||||||
|
Fields []string // Array of fields
|
||||||
|
Checksum string // The Checksum
|
||||||
|
Raw string // The raw NMEA sentence received
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sentence returns the Messages Sent
|
||||||
|
func (s Sent) Sentence() Sent { return s }
|
||||||
|
|
||||||
|
// Prefix returns the type of the message
|
||||||
|
func (s Sent) Prefix() string { return s.Type }
|
||||||
|
|
||||||
|
// String formats the sentence into a string
|
||||||
|
func (s Sent) String() string { return s.Raw }
|
||||||
|
|
||||||
|
// Validate returns an error if the sentence is not valid
|
||||||
|
func (s Sent) Validate() error { return nil }
|
||||||
|
|
||||||
|
// ParseSentence parses a raw message into it's fields
|
||||||
|
func ParseSentence(raw string) (Sent, error) {
|
||||||
|
startIndex := strings.Index(raw, SentenceStart)
|
||||||
|
if startIndex != 0 {
|
||||||
|
return Sent{}, fmt.Errorf("nmea: sentence does not start with a '$'")
|
||||||
|
}
|
||||||
|
sumSepIndex := strings.Index(raw, ChecksumSep)
|
||||||
|
if sumSepIndex == -1 {
|
||||||
|
return Sent{}, fmt.Errorf("nmea: sentence does not contain checksum separator")
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
fieldsRaw = raw[startIndex+1 : sumSepIndex]
|
||||||
|
fields = strings.Split(fieldsRaw, FieldSep)
|
||||||
|
checksumRaw = strings.ToUpper(raw[sumSepIndex+1:])
|
||||||
|
checksum = xorChecksum(fieldsRaw)
|
||||||
|
)
|
||||||
|
// Validate the checksum
|
||||||
|
if checksum != checksumRaw {
|
||||||
|
return Sent{}, fmt.Errorf(
|
||||||
|
"nmea: sentence checksum mismatch [%s != %s]", checksum, checksumRaw)
|
||||||
|
}
|
||||||
|
return Sent{
|
||||||
|
Type: fields[0],
|
||||||
|
Fields: fields[1:],
|
||||||
|
Checksum: checksumRaw,
|
||||||
|
Raw: raw,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// xor all the bytes in a string an return it
|
||||||
|
// as an uppercase hex string
|
||||||
|
func xorChecksum(s string) string {
|
||||||
|
var checksum uint8
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
checksum ^= s[i]
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%02X", checksum)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses the given string into the correct sentence type.
|
||||||
|
func Parse(raw string) (Message, error) {
|
||||||
|
s, err := ParseSentence(raw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch s.Type {
|
||||||
|
case PrefixGPRMC:
|
||||||
|
return NewGPRMC(s)
|
||||||
|
case PrefixGNRMC:
|
||||||
|
return NewGNRMC(s)
|
||||||
|
case PrefixGPGGA:
|
||||||
|
return NewGPGGA(s)
|
||||||
|
case PrefixGNGGA:
|
||||||
|
return NewGNGGA(s)
|
||||||
|
case PrefixGPGSA:
|
||||||
|
return NewGPGSA(s)
|
||||||
|
case PrefixGPGLL:
|
||||||
|
return NewGPGLL(s)
|
||||||
|
case PrefixGPVTG:
|
||||||
|
return NewGPVTG(s)
|
||||||
|
case PrefixGPZDA:
|
||||||
|
return NewGPZDA(s)
|
||||||
|
case PrefixPGRME:
|
||||||
|
return NewPGRME(s)
|
||||||
|
case PrefixGPGSV:
|
||||||
|
return NewGPGSV(s)
|
||||||
|
case PrefixGLGSV:
|
||||||
|
return NewGLGSV(s)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("nmea: sentence type '%s' not implemented", s.Type)
|
||||||
|
}
|
||||||
|
}
|
96
vendor/github.com/adrianmo/go-nmea/sentence_test.go
generated
vendored
Normal file
96
vendor/github.com/adrianmo/go-nmea/sentence_test.go
generated
vendored
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestChecksumOK(t *testing.T) {
|
||||||
|
_, err := ParseSentence("$GPFOO,1,2,3.3,x,y,zz,*51")
|
||||||
|
assert.NoError(t, err, "Checksum check failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChecksumBad(t *testing.T) {
|
||||||
|
_, err := ParseSentence("$GPFOO,1,2,3.4,x,y,zz,*51")
|
||||||
|
assert.Error(t, err, "Checksum check failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChecksumBadRaw(t *testing.T) {
|
||||||
|
badRaw := "$GPFOO,1,2,3.3,x,y,zz,*33"
|
||||||
|
_, err := Parse(badRaw)
|
||||||
|
assert.Error(t, err, "Expected 'Sentence checksum mismatch [51 != 33]'")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadStartCharacter(t *testing.T) {
|
||||||
|
// Check that a bad start character is flagged.
|
||||||
|
rawBadStart := "%GPFOO,1,2,3,x,y,z*1A"
|
||||||
|
_, err := Parse(rawBadStart)
|
||||||
|
assert.Error(t, err, "Expected 'Sentence does not start with a '$''")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadChecksumDelimiter(t *testing.T) {
|
||||||
|
// Check that a bad checksum delimiter is flagged.
|
||||||
|
rawBadSumSep := "$GPFOO,1,2,3,x,y,z"
|
||||||
|
_, err := Parse(rawBadSumSep)
|
||||||
|
assert.Error(t, err, "Expected 'Sentence does not contain single checksum separator'")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoodParsing(t *testing.T) {
|
||||||
|
// Check for good parsing.
|
||||||
|
raw := "$GPRMC,235236,A,3925.9479,N,11945.9211,W,44.7,153.6,250905,15.2,E,A*0C"
|
||||||
|
_, err := Parse(raw)
|
||||||
|
assert.NoError(t, err, "Parse error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoodFields(t *testing.T) {
|
||||||
|
raw := "$GPRMC,235236,A,3925.9479,N,11945.9211,W,44.7,153.6,250905,15.2,E,A*0C"
|
||||||
|
expectedFields := []string{"235236", "A", "3925.9479", "N", "11945.9211", "W", "44.7", "153.6", "250905", "15.2", "E", "A"}
|
||||||
|
m, _ := Parse(raw)
|
||||||
|
assert.EqualValues(t, expectedFields, m.Sentence().Fields, "Got '%q', expected '%q'", m.Sentence().Fields, expectedFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoodSentenceType(t *testing.T) {
|
||||||
|
raw := "$GPRMC,235236,A,3925.9479,N,11945.9211,W,44.7,153.6,250905,15.2,E,A*0C"
|
||||||
|
expected := "GPRMC"
|
||||||
|
m, _ := Parse(raw)
|
||||||
|
assert.Equal(t, expected, m.Prefix(), "Got '%s', expected '%s'", m.Prefix(), expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoodRawSentence(t *testing.T) {
|
||||||
|
raw := "$GPRMC,235236,A,3925.9479,N,11945.9211,W,44.7,153.6,250905,15.2,E,A*0C"
|
||||||
|
m, _ := Parse(raw)
|
||||||
|
assert.Equal(t, raw, m.String(), "Bad raw sentence")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultipleStartDelimiterSentence(t *testing.T) {
|
||||||
|
raw := "$$$$GPRMC,235236,A,3925.9479,N,11945.9211,W,44.7,153.6,250905,15.2,E,A*0C"
|
||||||
|
result, err := Parse(raw)
|
||||||
|
assert.Nil(t, result, "Result should be nil")
|
||||||
|
assert.NotNil(t, err, "Err should be an error")
|
||||||
|
assert.Equal(t, "nmea: sentence checksum mismatch [28 != 0C]", err.Error(), "Error sentence mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoStartDelimiterSentence(t *testing.T) {
|
||||||
|
raw := "abc$GPRMC,235236,A,3925.9479,N,11945.9211,W,44.7,153.6,250905,15.2,E,A*0C"
|
||||||
|
result, err := Parse(raw)
|
||||||
|
assert.Nil(t, result, "Result should be nil")
|
||||||
|
assert.NotNil(t, err, "Err should be an error")
|
||||||
|
assert.Equal(t, "nmea: sentence does not start with a '$'", err.Error(), "Error sentence mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoContainDelimiterSentence(t *testing.T) {
|
||||||
|
raw := "GPRMC,235236,A,3925.9479,N,11945.9211,W,44.7,153.6,250905,15.2,E,A*0C"
|
||||||
|
result, err := Parse(raw)
|
||||||
|
assert.Nil(t, result, "Result should be nil")
|
||||||
|
assert.NotNil(t, err, "Err should be an error")
|
||||||
|
assert.Equal(t, "nmea: sentence does not start with a '$'", err.Error(), "Error sentence mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReturnValues(t *testing.T) {
|
||||||
|
// Ensure Parse returns errors when appropriate.
|
||||||
|
result, err := Parse("$GPRMC,235236,A,3925.9479,N,11945.9211,W,44.7,153.6,250905,15.2,E,A*0A")
|
||||||
|
assert.Nil(t, result, "Result should be nil")
|
||||||
|
assert.NotNil(t, err, "Err should be an error")
|
||||||
|
assert.Equal(t, "nmea: sentence checksum mismatch [0C != 0A]", err.Error(), "Error sentence mismatch")
|
||||||
|
}
|
259
vendor/github.com/adrianmo/go-nmea/types.go
generated
vendored
Normal file
259
vendor/github.com/adrianmo/go-nmea/types.go
generated
vendored
Normal file
|
@ -0,0 +1,259 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
// Latitude / longitude representation.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
// "unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Degrees value
|
||||||
|
Degrees = '\u00B0'
|
||||||
|
// Minutes value
|
||||||
|
Minutes = '\''
|
||||||
|
// Seconds value
|
||||||
|
Seconds = '"'
|
||||||
|
// Point value
|
||||||
|
Point = '.'
|
||||||
|
// North value
|
||||||
|
North = "N"
|
||||||
|
// South value
|
||||||
|
South = "S"
|
||||||
|
// East value
|
||||||
|
East = "E"
|
||||||
|
// West value
|
||||||
|
West = "W"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LatLong type
|
||||||
|
type LatLong float64
|
||||||
|
|
||||||
|
// PrintGPS returns the GPS format for the given LatLong.
|
||||||
|
func (l LatLong) PrintGPS() string {
|
||||||
|
padding := ""
|
||||||
|
value := float64(l)
|
||||||
|
degrees := math.Floor(math.Abs(value))
|
||||||
|
fraction := (math.Abs(value) - degrees) * 60
|
||||||
|
if fraction < 10 {
|
||||||
|
padding = "0"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d%s%.4f", int(degrees), padding, fraction)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintDMS returns the degrees, minutes, seconds format for the given LatLong.
|
||||||
|
func (l LatLong) PrintDMS() string {
|
||||||
|
val := math.Abs(float64(l))
|
||||||
|
degrees := int(math.Floor(val))
|
||||||
|
minutes := int(math.Floor(60 * (val - float64(degrees))))
|
||||||
|
seconds := 3600 * (val - float64(degrees) - (float64(minutes) / 60))
|
||||||
|
|
||||||
|
return fmt.Sprintf("%d\u00B0 %d' %f\"", degrees, minutes, seconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
//ValidRange validates if the range is between -180 and +180.
|
||||||
|
func (l LatLong) ValidRange() bool {
|
||||||
|
return -180.0 <= l && l <= 180.0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNear returns whether the coordinate is near the other coordinate,
|
||||||
|
// by no further than the given distance away.
|
||||||
|
func (l LatLong) IsNear(o LatLong, max float64) bool {
|
||||||
|
return math.Abs(float64(l-o)) <= max
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseLatLong parses the supplied string into the LatLong.
|
||||||
|
//
|
||||||
|
// Supported formats are:
|
||||||
|
// - DMS (e.g. 33° 23' 22")
|
||||||
|
// - Decimal (e.g. 33.23454)
|
||||||
|
// - GPS (e.g 15113.4322S)
|
||||||
|
//
|
||||||
|
func ParseLatLong(s string) (LatLong, error) {
|
||||||
|
var l LatLong
|
||||||
|
var err error
|
||||||
|
invalid := LatLong(0.0) // The invalid value to return.
|
||||||
|
if l, err = ParseDMS(s); err == nil {
|
||||||
|
return l, nil
|
||||||
|
} else if l, err = ParseGPS(s); err == nil {
|
||||||
|
return l, nil
|
||||||
|
} else if l, err = ParseDecimal(s); err == nil {
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
if !l.ValidRange() {
|
||||||
|
return invalid, errors.New("coordinate is not in range -180, 180")
|
||||||
|
}
|
||||||
|
return invalid, fmt.Errorf("cannot parse [%s], unknown format", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseGPS parses a GPS/NMEA coordinate.
|
||||||
|
// e.g 15113.4322S
|
||||||
|
func ParseGPS(s string) (LatLong, error) {
|
||||||
|
parts := strings.Split(s, " ")
|
||||||
|
dir := parts[1]
|
||||||
|
value, err := strconv.ParseFloat(parts[0], 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("parse error: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
degrees := math.Floor(value / 100)
|
||||||
|
minutes := value - (degrees * 100)
|
||||||
|
value = degrees + minutes/60
|
||||||
|
|
||||||
|
if dir == North || dir == East {
|
||||||
|
return LatLong(value), nil
|
||||||
|
} else if dir == South || dir == West {
|
||||||
|
return LatLong(0 - value), nil
|
||||||
|
} else {
|
||||||
|
return 0, fmt.Errorf("invalid direction [%s]", dir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseDecimal parses a decimal format coordinate.
|
||||||
|
// e.g: 151.196019
|
||||||
|
func ParseDecimal(s string) (LatLong, error) {
|
||||||
|
// Make sure it parses as a float.
|
||||||
|
l, err := strconv.ParseFloat(s, 64)
|
||||||
|
if err != nil || s[0] != '-' && len(strings.Split(s, ".")[0]) > 3 {
|
||||||
|
return LatLong(0.0), errors.New("parse error (not decimal coordinate)")
|
||||||
|
}
|
||||||
|
return LatLong(l), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseDMS parses a coordinate in degrees, minutes, seconds.
|
||||||
|
// - e.g. 33° 23' 22"
|
||||||
|
func ParseDMS(s string) (LatLong, error) {
|
||||||
|
degrees := 0
|
||||||
|
minutes := 0
|
||||||
|
seconds := 0.0
|
||||||
|
// Whether a number has finished parsing (i.e whitespace after it)
|
||||||
|
endNumber := false
|
||||||
|
// Temporary parse buffer.
|
||||||
|
tmpBytes := []byte{}
|
||||||
|
var err error
|
||||||
|
|
||||||
|
for i, r := range s {
|
||||||
|
if unicode.IsNumber(r) || r == '.' {
|
||||||
|
if !endNumber {
|
||||||
|
tmpBytes = append(tmpBytes, s[i])
|
||||||
|
} else {
|
||||||
|
return 0, errors.New("parse error (no delimiter)")
|
||||||
|
}
|
||||||
|
} else if unicode.IsSpace(r) && len(tmpBytes) > 0 {
|
||||||
|
endNumber = true
|
||||||
|
} else if r == Degrees {
|
||||||
|
if degrees, err = strconv.Atoi(string(tmpBytes)); err != nil {
|
||||||
|
return 0, errors.New("parse error (degrees)")
|
||||||
|
}
|
||||||
|
tmpBytes = tmpBytes[:0]
|
||||||
|
endNumber = false
|
||||||
|
} else if s[i] == Minutes {
|
||||||
|
if minutes, err = strconv.Atoi(string(tmpBytes)); err != nil {
|
||||||
|
return 0, errors.New("parse error (minutes)")
|
||||||
|
}
|
||||||
|
tmpBytes = tmpBytes[:0]
|
||||||
|
endNumber = false
|
||||||
|
} else if s[i] == Seconds {
|
||||||
|
if seconds, err = strconv.ParseFloat(string(tmpBytes), 64); err != nil {
|
||||||
|
return 0, errors.New("parse error (seconds)")
|
||||||
|
}
|
||||||
|
tmpBytes = tmpBytes[:0]
|
||||||
|
endNumber = false
|
||||||
|
} else if unicode.IsSpace(r) && len(tmpBytes) == 0 {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
return 0, fmt.Errorf("parse error (unknown symbol [%d])", s[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val := LatLong(float64(degrees) + (float64(minutes) / 60.0) + (float64(seconds) / 60.0 / 60.0))
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time type
|
||||||
|
type Time struct {
|
||||||
|
Valid bool
|
||||||
|
Hour int
|
||||||
|
Minute int
|
||||||
|
Second int
|
||||||
|
Millisecond int
|
||||||
|
}
|
||||||
|
|
||||||
|
// String representation of Time
|
||||||
|
func (t Time) String() string {
|
||||||
|
return fmt.Sprintf("%02d:%02d:%02d.%04d", t.Hour, t.Minute, t.Second, t.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseTime parses wall clock time.
|
||||||
|
// e.g. hhmmss.ssss
|
||||||
|
// An empty time string will result in an invalid time.
|
||||||
|
func ParseTime(s string) (Time, error) {
|
||||||
|
if s == "" {
|
||||||
|
return Time{}, nil
|
||||||
|
}
|
||||||
|
ms := "0000"
|
||||||
|
hhmmss := s
|
||||||
|
if parts := strings.SplitN(s, ".", 2); len(parts) > 1 {
|
||||||
|
hhmmss, ms = parts[0], parts[1]
|
||||||
|
}
|
||||||
|
if len(hhmmss) != 6 {
|
||||||
|
return Time{}, fmt.Errorf("parse time: exptected hhmmss.ss format, got '%s'", s)
|
||||||
|
}
|
||||||
|
hour, err := strconv.Atoi(hhmmss[0:2])
|
||||||
|
if err != nil {
|
||||||
|
return Time{}, errors.New(hhmmss)
|
||||||
|
}
|
||||||
|
minute, err := strconv.Atoi(hhmmss[2:4])
|
||||||
|
if err != nil {
|
||||||
|
return Time{}, errors.New(hhmmss)
|
||||||
|
}
|
||||||
|
second, err := strconv.Atoi(hhmmss[4:6])
|
||||||
|
if err != nil {
|
||||||
|
return Time{}, errors.New(hhmmss)
|
||||||
|
}
|
||||||
|
millisecond, err := strconv.Atoi(ms)
|
||||||
|
if err != nil {
|
||||||
|
return Time{}, errors.New(hhmmss)
|
||||||
|
}
|
||||||
|
return Time{true, hour, minute, second, millisecond}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Date type
|
||||||
|
type Date struct {
|
||||||
|
Valid bool
|
||||||
|
DD int
|
||||||
|
MM int
|
||||||
|
YY int
|
||||||
|
}
|
||||||
|
|
||||||
|
// String representation of date
|
||||||
|
func (d Date) String() string {
|
||||||
|
return fmt.Sprintf("%02d/%02d/%02d", d.DD, d.MM, d.YY)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseDate field ddmmyy format
|
||||||
|
func ParseDate(ddmmyy string) (Date, error) {
|
||||||
|
if ddmmyy == "" {
|
||||||
|
return Date{}, nil
|
||||||
|
}
|
||||||
|
if len(ddmmyy) != 6 {
|
||||||
|
return Date{}, fmt.Errorf("parse date: exptected ddmmyy format, got '%s'", ddmmyy)
|
||||||
|
}
|
||||||
|
dd, err := strconv.Atoi(ddmmyy[0:2])
|
||||||
|
if err != nil {
|
||||||
|
return Date{}, errors.New(ddmmyy)
|
||||||
|
}
|
||||||
|
mm, err := strconv.Atoi(ddmmyy[2:4])
|
||||||
|
if err != nil {
|
||||||
|
return Date{}, errors.New(ddmmyy)
|
||||||
|
}
|
||||||
|
yy, err := strconv.Atoi(ddmmyy[4:6])
|
||||||
|
if err != nil {
|
||||||
|
return Date{}, errors.New(ddmmyy)
|
||||||
|
}
|
||||||
|
return Date{true, dd, mm, yy}, nil
|
||||||
|
}
|
152
vendor/github.com/adrianmo/go-nmea/types_test.go
generated
vendored
Normal file
152
vendor/github.com/adrianmo/go-nmea/types_test.go
generated
vendored
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
package nmea
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
var nearDistance = 0.001
|
||||||
|
|
||||||
|
func TestLatLongParse(t *testing.T) {
|
||||||
|
var l LatLong
|
||||||
|
var err error
|
||||||
|
value, expected := "3345.1232 N", LatLong(33.752054)
|
||||||
|
if l, err = ParseGPS(value); err != nil {
|
||||||
|
t.Errorf("ParseGPS error: %s", err)
|
||||||
|
} else if !l.IsNear(expected, nearDistance) {
|
||||||
|
t.Errorf("ParseGPS got %f, expected %f", l, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
value, expected = "15145.9877 S", LatLong(-151.76646)
|
||||||
|
if l, err = ParseGPS(value); err != nil {
|
||||||
|
t.Errorf("ParseGPS error: %s", err)
|
||||||
|
} else if !l.IsNear(expected, nearDistance) {
|
||||||
|
t.Errorf("ParseGPS got %f, expected %f", l, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
value, expected = "33\u00B0 12' 34.3423\"", LatLong(33.209540)
|
||||||
|
if l, err = ParseDMS(value); err != nil {
|
||||||
|
t.Errorf("ParseDMS error: %s", err)
|
||||||
|
} else if !l.IsNear(expected, nearDistance) {
|
||||||
|
t.Errorf("ParseDMS got %f, expected %f", l, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
value, expected = "151.234532", LatLong(151.234532)
|
||||||
|
if l, err = ParseDecimal(value); err != nil {
|
||||||
|
t.Errorf("ParseDecimal error: %s", err)
|
||||||
|
} else if !l.IsNear(expected, nearDistance) {
|
||||||
|
t.Errorf("ParseDecimal got %f, expected %f", l, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
value, expected = "-151.234532", LatLong(-151.234532)
|
||||||
|
if l, err = ParseDecimal(value); err != nil {
|
||||||
|
t.Errorf("ParseDecimal error: %s", err)
|
||||||
|
} else if !l.IsNear(expected, nearDistance) {
|
||||||
|
t.Errorf("ParseDecimal got %f, expected %f", l, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLatLongPrint(t *testing.T) {
|
||||||
|
l, _ := ParseDecimal("151.434367")
|
||||||
|
exp := "15126.0620"
|
||||||
|
if s := l.PrintGPS(); s != exp {
|
||||||
|
t.Errorf("PrintGPS() got %s expected %s", s, exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
l, _ = ParseGPS("3356.4343 N")
|
||||||
|
exp = "3356.4343"
|
||||||
|
if s := l.PrintGPS(); s != exp {
|
||||||
|
t.Errorf("PrintGPS() got %s expected %s", s, exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
exp = "33° 56' 26.058000\""
|
||||||
|
if s := l.PrintDMS(); s != exp {
|
||||||
|
t.Errorf("PrintDMS() got %s expected %s", s, exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimeParse(t *testing.T) {
|
||||||
|
timetests := []struct {
|
||||||
|
value string
|
||||||
|
expected Time
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{"123456", Time{true, 12, 34, 56, 0}, true},
|
||||||
|
{"", Time{}, true},
|
||||||
|
{"112233.123", Time{true, 11, 22, 33, 123}, true},
|
||||||
|
{"010203.04", Time{true, 1, 2, 3, 4}, true},
|
||||||
|
{"10203.04", Time{}, false},
|
||||||
|
{"x0u2xd", Time{}, false},
|
||||||
|
{"xx2233.123", Time{}, false},
|
||||||
|
{"11xx33.123", Time{}, false},
|
||||||
|
{"1122xx.123", Time{}, false},
|
||||||
|
{"112233.xxx", Time{}, false},
|
||||||
|
}
|
||||||
|
for _, tt := range timetests {
|
||||||
|
actual, err := ParseTime(tt.value)
|
||||||
|
if !tt.ok {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("ParseTime(%s) expected error", tt.value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ParseTime(%s) %s", tt.value, err)
|
||||||
|
}
|
||||||
|
if actual != tt.expected {
|
||||||
|
t.Errorf("ParseTime(%s) got %s expected %s", tt.value, actual, tt.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimeString(t *testing.T) {
|
||||||
|
d := Time{
|
||||||
|
Hour: 1,
|
||||||
|
Minute: 2,
|
||||||
|
Second: 3,
|
||||||
|
Millisecond: 4,
|
||||||
|
}
|
||||||
|
expected := "01:02:03.0004"
|
||||||
|
if s := d.String(); s != expected {
|
||||||
|
t.Fatalf("got %s, expected %s", s, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDateParse(t *testing.T) {
|
||||||
|
datetests := []struct {
|
||||||
|
value string
|
||||||
|
expected Date
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{"010203", Date{true, 1, 2, 3}, true},
|
||||||
|
{"01003", Date{}, false},
|
||||||
|
{"", Date{}, true},
|
||||||
|
{"xx0203", Date{}, false},
|
||||||
|
{"01xx03", Date{}, false},
|
||||||
|
{"0102xx", Date{}, false},
|
||||||
|
}
|
||||||
|
for _, tt := range datetests {
|
||||||
|
actual, err := ParseDate(tt.value)
|
||||||
|
if !tt.ok {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("ParseDate(%s) expected error", tt.value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ParseDate(%s) %s", tt.value, err)
|
||||||
|
}
|
||||||
|
if actual != tt.expected {
|
||||||
|
t.Errorf("ParseDate(%s) got %s expected %s", tt.value, actual, tt.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDateString(t *testing.T) {
|
||||||
|
d := Date{
|
||||||
|
DD: 1,
|
||||||
|
MM: 2,
|
||||||
|
YY: 3,
|
||||||
|
}
|
||||||
|
expected := "01/02/03"
|
||||||
|
if s := d.String(); s != expected {
|
||||||
|
t.Fatalf("got %s expected %s", s, expected)
|
||||||
|
}
|
||||||
|
}
|
21
vendor/github.com/dustin/go-humanize/.travis.yml
generated
vendored
Normal file
21
vendor/github.com/dustin/go-humanize/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
sudo: false
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.3.x
|
||||||
|
- 1.5.x
|
||||||
|
- 1.6.x
|
||||||
|
- 1.7.x
|
||||||
|
- 1.8.x
|
||||||
|
- 1.9.x
|
||||||
|
- master
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- go: master
|
||||||
|
fast_finish: true
|
||||||
|
install:
|
||||||
|
- # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
|
||||||
|
script:
|
||||||
|
- go get -t -v ./...
|
||||||
|
- diff -u <(echo -n) <(gofmt -d -s .)
|
||||||
|
- go tool vet .
|
||||||
|
- go test -v -race ./...
|
21
vendor/github.com/dustin/go-humanize/LICENSE
generated
vendored
Normal file
21
vendor/github.com/dustin/go-humanize/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
Copyright (c) 2005-2008 Dustin Sallings <dustin@spy.net>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<http://www.opensource.org/licenses/mit-license.php>
|
124
vendor/github.com/dustin/go-humanize/README.markdown
generated
vendored
Normal file
124
vendor/github.com/dustin/go-humanize/README.markdown
generated
vendored
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
# Humane Units [](https://travis-ci.org/dustin/go-humanize) [](https://godoc.org/github.com/dustin/go-humanize)
|
||||||
|
|
||||||
|
Just a few functions for helping humanize times and sizes.
|
||||||
|
|
||||||
|
`go get` it as `github.com/dustin/go-humanize`, import it as
|
||||||
|
`"github.com/dustin/go-humanize"`, use it as `humanize`.
|
||||||
|
|
||||||
|
See [godoc](https://godoc.org/github.com/dustin/go-humanize) for
|
||||||
|
complete documentation.
|
||||||
|
|
||||||
|
## Sizes
|
||||||
|
|
||||||
|
This lets you take numbers like `82854982` and convert them to useful
|
||||||
|
strings like, `83 MB` or `79 MiB` (whichever you prefer).
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
fmt.Printf("That file is %s.", humanize.Bytes(82854982)) // That file is 83 MB.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Times
|
||||||
|
|
||||||
|
This lets you take a `time.Time` and spit it out in relative terms.
|
||||||
|
For example, `12 seconds ago` or `3 days from now`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
fmt.Printf("This was touched %s.", humanize.Time(someTimeInstance)) // This was touched 7 hours ago.
|
||||||
|
```
|
||||||
|
|
||||||
|
Thanks to Kyle Lemons for the time implementation from an IRC
|
||||||
|
conversation one day. It's pretty neat.
|
||||||
|
|
||||||
|
## Ordinals
|
||||||
|
|
||||||
|
From a [mailing list discussion][odisc] where a user wanted to be able
|
||||||
|
to label ordinals.
|
||||||
|
|
||||||
|
0 -> 0th
|
||||||
|
1 -> 1st
|
||||||
|
2 -> 2nd
|
||||||
|
3 -> 3rd
|
||||||
|
4 -> 4th
|
||||||
|
[...]
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
fmt.Printf("You're my %s best friend.", humanize.Ordinal(193)) // You are my 193rd best friend.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commas
|
||||||
|
|
||||||
|
Want to shove commas into numbers? Be my guest.
|
||||||
|
|
||||||
|
0 -> 0
|
||||||
|
100 -> 100
|
||||||
|
1000 -> 1,000
|
||||||
|
1000000000 -> 1,000,000,000
|
||||||
|
-100000 -> -100,000
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
fmt.Printf("You owe $%s.\n", humanize.Comma(6582491)) // You owe $6,582,491.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ftoa
|
||||||
|
|
||||||
|
Nicer float64 formatter that removes trailing zeros.
|
||||||
|
|
||||||
|
```go
|
||||||
|
fmt.Printf("%f", 2.24) // 2.240000
|
||||||
|
fmt.Printf("%s", humanize.Ftoa(2.24)) // 2.24
|
||||||
|
fmt.Printf("%f", 2.0) // 2.000000
|
||||||
|
fmt.Printf("%s", humanize.Ftoa(2.0)) // 2
|
||||||
|
```
|
||||||
|
|
||||||
|
## SI notation
|
||||||
|
|
||||||
|
Format numbers with [SI notation][sinotation].
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
humanize.SI(0.00000000223, "M") // 2.23 nM
|
||||||
|
```
|
||||||
|
|
||||||
|
## English-specific functions
|
||||||
|
|
||||||
|
The following functions are in the `humanize/english` subpackage.
|
||||||
|
|
||||||
|
### Plurals
|
||||||
|
|
||||||
|
Simple English pluralization
|
||||||
|
|
||||||
|
```go
|
||||||
|
english.PluralWord(1, "object", "") // object
|
||||||
|
english.PluralWord(42, "object", "") // objects
|
||||||
|
english.PluralWord(2, "bus", "") // buses
|
||||||
|
english.PluralWord(99, "locus", "loci") // loci
|
||||||
|
|
||||||
|
english.Plural(1, "object", "") // 1 object
|
||||||
|
english.Plural(42, "object", "") // 42 objects
|
||||||
|
english.Plural(2, "bus", "") // 2 buses
|
||||||
|
english.Plural(99, "locus", "loci") // 99 loci
|
||||||
|
```
|
||||||
|
|
||||||
|
### Word series
|
||||||
|
|
||||||
|
Format comma-separated words lists with conjuctions:
|
||||||
|
|
||||||
|
```go
|
||||||
|
english.WordSeries([]string{"foo"}, "and") // foo
|
||||||
|
english.WordSeries([]string{"foo", "bar"}, "and") // foo and bar
|
||||||
|
english.WordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar and baz
|
||||||
|
|
||||||
|
english.OxfordWordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar, and baz
|
||||||
|
```
|
||||||
|
|
||||||
|
[odisc]: https://groups.google.com/d/topic/golang-nuts/l8NhI74jl-4/discussion
|
||||||
|
[sinotation]: http://en.wikipedia.org/wiki/Metric_prefix
|
31
vendor/github.com/dustin/go-humanize/big.go
generated
vendored
Normal file
31
vendor/github.com/dustin/go-humanize/big.go
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
)
|
||||||
|
|
||||||
|
// order of magnitude (to a max order)
|
||||||
|
func oomm(n, b *big.Int, maxmag int) (float64, int) {
|
||||||
|
mag := 0
|
||||||
|
m := &big.Int{}
|
||||||
|
for n.Cmp(b) >= 0 {
|
||||||
|
n.DivMod(n, b, m)
|
||||||
|
mag++
|
||||||
|
if mag == maxmag && maxmag >= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag
|
||||||
|
}
|
||||||
|
|
||||||
|
// total order of magnitude
|
||||||
|
// (same as above, but with no upper limit)
|
||||||
|
func oom(n, b *big.Int) (float64, int) {
|
||||||
|
mag := 0
|
||||||
|
m := &big.Int{}
|
||||||
|
for n.Cmp(b) >= 0 {
|
||||||
|
n.DivMod(n, b, m)
|
||||||
|
mag++
|
||||||
|
}
|
||||||
|
return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag
|
||||||
|
}
|
173
vendor/github.com/dustin/go-humanize/bigbytes.go
generated
vendored
Normal file
173
vendor/github.com/dustin/go-humanize/bigbytes.go
generated
vendored
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
bigIECExp = big.NewInt(1024)
|
||||||
|
|
||||||
|
// BigByte is one byte in bit.Ints
|
||||||
|
BigByte = big.NewInt(1)
|
||||||
|
// BigKiByte is 1,024 bytes in bit.Ints
|
||||||
|
BigKiByte = (&big.Int{}).Mul(BigByte, bigIECExp)
|
||||||
|
// BigMiByte is 1,024 k bytes in bit.Ints
|
||||||
|
BigMiByte = (&big.Int{}).Mul(BigKiByte, bigIECExp)
|
||||||
|
// BigGiByte is 1,024 m bytes in bit.Ints
|
||||||
|
BigGiByte = (&big.Int{}).Mul(BigMiByte, bigIECExp)
|
||||||
|
// BigTiByte is 1,024 g bytes in bit.Ints
|
||||||
|
BigTiByte = (&big.Int{}).Mul(BigGiByte, bigIECExp)
|
||||||
|
// BigPiByte is 1,024 t bytes in bit.Ints
|
||||||
|
BigPiByte = (&big.Int{}).Mul(BigTiByte, bigIECExp)
|
||||||
|
// BigEiByte is 1,024 p bytes in bit.Ints
|
||||||
|
BigEiByte = (&big.Int{}).Mul(BigPiByte, bigIECExp)
|
||||||
|
// BigZiByte is 1,024 e bytes in bit.Ints
|
||||||
|
BigZiByte = (&big.Int{}).Mul(BigEiByte, bigIECExp)
|
||||||
|
// BigYiByte is 1,024 z bytes in bit.Ints
|
||||||
|
BigYiByte = (&big.Int{}).Mul(BigZiByte, bigIECExp)
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
bigSIExp = big.NewInt(1000)
|
||||||
|
|
||||||
|
// BigSIByte is one SI byte in big.Ints
|
||||||
|
BigSIByte = big.NewInt(1)
|
||||||
|
// BigKByte is 1,000 SI bytes in big.Ints
|
||||||
|
BigKByte = (&big.Int{}).Mul(BigSIByte, bigSIExp)
|
||||||
|
// BigMByte is 1,000 SI k bytes in big.Ints
|
||||||
|
BigMByte = (&big.Int{}).Mul(BigKByte, bigSIExp)
|
||||||
|
// BigGByte is 1,000 SI m bytes in big.Ints
|
||||||
|
BigGByte = (&big.Int{}).Mul(BigMByte, bigSIExp)
|
||||||
|
// BigTByte is 1,000 SI g bytes in big.Ints
|
||||||
|
BigTByte = (&big.Int{}).Mul(BigGByte, bigSIExp)
|
||||||
|
// BigPByte is 1,000 SI t bytes in big.Ints
|
||||||
|
BigPByte = (&big.Int{}).Mul(BigTByte, bigSIExp)
|
||||||
|
// BigEByte is 1,000 SI p bytes in big.Ints
|
||||||
|
BigEByte = (&big.Int{}).Mul(BigPByte, bigSIExp)
|
||||||
|
// BigZByte is 1,000 SI e bytes in big.Ints
|
||||||
|
BigZByte = (&big.Int{}).Mul(BigEByte, bigSIExp)
|
||||||
|
// BigYByte is 1,000 SI z bytes in big.Ints
|
||||||
|
BigYByte = (&big.Int{}).Mul(BigZByte, bigSIExp)
|
||||||
|
)
|
||||||
|
|
||||||
|
var bigBytesSizeTable = map[string]*big.Int{
|
||||||
|
"b": BigByte,
|
||||||
|
"kib": BigKiByte,
|
||||||
|
"kb": BigKByte,
|
||||||
|
"mib": BigMiByte,
|
||||||
|
"mb": BigMByte,
|
||||||
|
"gib": BigGiByte,
|
||||||
|
"gb": BigGByte,
|
||||||
|
"tib": BigTiByte,
|
||||||
|
"tb": BigTByte,
|
||||||
|
"pib": BigPiByte,
|
||||||
|
"pb": BigPByte,
|
||||||
|
"eib": BigEiByte,
|
||||||
|
"eb": BigEByte,
|
||||||
|
"zib": BigZiByte,
|
||||||
|
"zb": BigZByte,
|
||||||
|
"yib": BigYiByte,
|
||||||
|
"yb": BigYByte,
|
||||||
|
// Without suffix
|
||||||
|
"": BigByte,
|
||||||
|
"ki": BigKiByte,
|
||||||
|
"k": BigKByte,
|
||||||
|
"mi": BigMiByte,
|
||||||
|
"m": BigMByte,
|
||||||
|
"gi": BigGiByte,
|
||||||
|
"g": BigGByte,
|
||||||
|
"ti": BigTiByte,
|
||||||
|
"t": BigTByte,
|
||||||
|
"pi": BigPiByte,
|
||||||
|
"p": BigPByte,
|
||||||
|
"ei": BigEiByte,
|
||||||
|
"e": BigEByte,
|
||||||
|
"z": BigZByte,
|
||||||
|
"zi": BigZiByte,
|
||||||
|
"y": BigYByte,
|
||||||
|
"yi": BigYiByte,
|
||||||
|
}
|
||||||
|
|
||||||
|
var ten = big.NewInt(10)
|
||||||
|
|
||||||
|
func humanateBigBytes(s, base *big.Int, sizes []string) string {
|
||||||
|
if s.Cmp(ten) < 0 {
|
||||||
|
return fmt.Sprintf("%d B", s)
|
||||||
|
}
|
||||||
|
c := (&big.Int{}).Set(s)
|
||||||
|
val, mag := oomm(c, base, len(sizes)-1)
|
||||||
|
suffix := sizes[mag]
|
||||||
|
f := "%.0f %s"
|
||||||
|
if val < 10 {
|
||||||
|
f = "%.1f %s"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(f, val, suffix)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// BigBytes produces a human readable representation of an SI size.
|
||||||
|
//
|
||||||
|
// See also: ParseBigBytes.
|
||||||
|
//
|
||||||
|
// BigBytes(82854982) -> 83 MB
|
||||||
|
func BigBytes(s *big.Int) string {
|
||||||
|
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
|
||||||
|
return humanateBigBytes(s, bigSIExp, sizes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BigIBytes produces a human readable representation of an IEC size.
|
||||||
|
//
|
||||||
|
// See also: ParseBigBytes.
|
||||||
|
//
|
||||||
|
// BigIBytes(82854982) -> 79 MiB
|
||||||
|
func BigIBytes(s *big.Int) string {
|
||||||
|
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}
|
||||||
|
return humanateBigBytes(s, bigIECExp, sizes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseBigBytes parses a string representation of bytes into the number
|
||||||
|
// of bytes it represents.
|
||||||
|
//
|
||||||
|
// See also: BigBytes, BigIBytes.
|
||||||
|
//
|
||||||
|
// ParseBigBytes("42 MB") -> 42000000, nil
|
||||||
|
// ParseBigBytes("42 mib") -> 44040192, nil
|
||||||
|
func ParseBigBytes(s string) (*big.Int, error) {
|
||||||
|
lastDigit := 0
|
||||||
|
hasComma := false
|
||||||
|
for _, r := range s {
|
||||||
|
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if r == ',' {
|
||||||
|
hasComma = true
|
||||||
|
}
|
||||||
|
lastDigit++
|
||||||
|
}
|
||||||
|
|
||||||
|
num := s[:lastDigit]
|
||||||
|
if hasComma {
|
||||||
|
num = strings.Replace(num, ",", "", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
val := &big.Rat{}
|
||||||
|
_, err := fmt.Sscanf(num, "%f", val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
|
||||||
|
if m, ok := bigBytesSizeTable[extra]; ok {
|
||||||
|
mv := (&big.Rat{}).SetInt(m)
|
||||||
|
val.Mul(val, mv)
|
||||||
|
rv := &big.Int{}
|
||||||
|
rv.Div(val.Num(), val.Denom())
|
||||||
|
return rv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("unhandled size name: %v", extra)
|
||||||
|
}
|
220
vendor/github.com/dustin/go-humanize/bigbytes_test.go
generated
vendored
Normal file
220
vendor/github.com/dustin/go-humanize/bigbytes_test.go
generated
vendored
Normal file
|
@ -0,0 +1,220 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBigByteParsing(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in string
|
||||||
|
exp uint64
|
||||||
|
}{
|
||||||
|
{"42", 42},
|
||||||
|
{"42MB", 42000000},
|
||||||
|
{"42MiB", 44040192},
|
||||||
|
{"42mb", 42000000},
|
||||||
|
{"42mib", 44040192},
|
||||||
|
{"42MIB", 44040192},
|
||||||
|
{"42 MB", 42000000},
|
||||||
|
{"42 MiB", 44040192},
|
||||||
|
{"42 mb", 42000000},
|
||||||
|
{"42 mib", 44040192},
|
||||||
|
{"42 MIB", 44040192},
|
||||||
|
{"42.5MB", 42500000},
|
||||||
|
{"42.5MiB", 44564480},
|
||||||
|
{"42.5 MB", 42500000},
|
||||||
|
{"42.5 MiB", 44564480},
|
||||||
|
// No need to say B
|
||||||
|
{"42M", 42000000},
|
||||||
|
{"42Mi", 44040192},
|
||||||
|
{"42m", 42000000},
|
||||||
|
{"42mi", 44040192},
|
||||||
|
{"42MI", 44040192},
|
||||||
|
{"42 M", 42000000},
|
||||||
|
{"42 Mi", 44040192},
|
||||||
|
{"42 m", 42000000},
|
||||||
|
{"42 mi", 44040192},
|
||||||
|
{"42 MI", 44040192},
|
||||||
|
{"42.5M", 42500000},
|
||||||
|
{"42.5Mi", 44564480},
|
||||||
|
{"42.5 M", 42500000},
|
||||||
|
{"42.5 Mi", 44564480},
|
||||||
|
{"1,005.03 MB", 1005030000},
|
||||||
|
// Large testing, breaks when too much larger than
|
||||||
|
// this.
|
||||||
|
{"12.5 EB", uint64(12.5 * float64(EByte))},
|
||||||
|
{"12.5 E", uint64(12.5 * float64(EByte))},
|
||||||
|
{"12.5 EiB", uint64(12.5 * float64(EiByte))},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range tests {
|
||||||
|
got, err := ParseBigBytes(p.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Couldn't parse %v: %v", p.in, err)
|
||||||
|
} else {
|
||||||
|
if got.Uint64() != p.exp {
|
||||||
|
t.Errorf("Expected %v for %v, got %v",
|
||||||
|
p.exp, p.in, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBigByteErrors(t *testing.T) {
|
||||||
|
got, err := ParseBigBytes("84 JB")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected error, got %v", got)
|
||||||
|
}
|
||||||
|
got, err = ParseBigBytes("")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected error parsing nothing")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bbyte(in uint64) string {
|
||||||
|
return BigBytes((&big.Int{}).SetUint64(in))
|
||||||
|
}
|
||||||
|
|
||||||
|
func bibyte(in uint64) string {
|
||||||
|
return BigIBytes((&big.Int{}).SetUint64(in))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBigBytes(t *testing.T) {
|
||||||
|
testList{
|
||||||
|
{"bytes(0)", bbyte(0), "0 B"},
|
||||||
|
{"bytes(1)", bbyte(1), "1 B"},
|
||||||
|
{"bytes(803)", bbyte(803), "803 B"},
|
||||||
|
{"bytes(999)", bbyte(999), "999 B"},
|
||||||
|
|
||||||
|
{"bytes(1024)", bbyte(1024), "1.0 kB"},
|
||||||
|
{"bytes(1MB - 1)", bbyte(MByte - Byte), "1000 kB"},
|
||||||
|
|
||||||
|
{"bytes(1MB)", bbyte(1024 * 1024), "1.0 MB"},
|
||||||
|
{"bytes(1GB - 1K)", bbyte(GByte - KByte), "1000 MB"},
|
||||||
|
|
||||||
|
{"bytes(1GB)", bbyte(GByte), "1.0 GB"},
|
||||||
|
{"bytes(1TB - 1M)", bbyte(TByte - MByte), "1000 GB"},
|
||||||
|
|
||||||
|
{"bytes(1TB)", bbyte(TByte), "1.0 TB"},
|
||||||
|
{"bytes(1PB - 1T)", bbyte(PByte - TByte), "999 TB"},
|
||||||
|
|
||||||
|
{"bytes(1PB)", bbyte(PByte), "1.0 PB"},
|
||||||
|
{"bytes(1PB - 1T)", bbyte(EByte - PByte), "999 PB"},
|
||||||
|
|
||||||
|
{"bytes(1EB)", bbyte(EByte), "1.0 EB"},
|
||||||
|
// Overflows.
|
||||||
|
// {"bytes(1EB - 1P)", Bytes((KByte*EByte)-PByte), "1023EB"},
|
||||||
|
|
||||||
|
{"bytes(0)", bibyte(0), "0 B"},
|
||||||
|
{"bytes(1)", bibyte(1), "1 B"},
|
||||||
|
{"bytes(803)", bibyte(803), "803 B"},
|
||||||
|
{"bytes(1023)", bibyte(1023), "1023 B"},
|
||||||
|
|
||||||
|
{"bytes(1024)", bibyte(1024), "1.0 KiB"},
|
||||||
|
{"bytes(1MB - 1)", bibyte(MiByte - IByte), "1024 KiB"},
|
||||||
|
|
||||||
|
{"bytes(1MB)", bibyte(1024 * 1024), "1.0 MiB"},
|
||||||
|
{"bytes(1GB - 1K)", bibyte(GiByte - KiByte), "1024 MiB"},
|
||||||
|
|
||||||
|
{"bytes(1GB)", bibyte(GiByte), "1.0 GiB"},
|
||||||
|
{"bytes(1TB - 1M)", bibyte(TiByte - MiByte), "1024 GiB"},
|
||||||
|
|
||||||
|
{"bytes(1TB)", bibyte(TiByte), "1.0 TiB"},
|
||||||
|
{"bytes(1PB - 1T)", bibyte(PiByte - TiByte), "1023 TiB"},
|
||||||
|
|
||||||
|
{"bytes(1PB)", bibyte(PiByte), "1.0 PiB"},
|
||||||
|
{"bytes(1PB - 1T)", bibyte(EiByte - PiByte), "1023 PiB"},
|
||||||
|
|
||||||
|
{"bytes(1EiB)", bibyte(EiByte), "1.0 EiB"},
|
||||||
|
// Overflows.
|
||||||
|
// {"bytes(1EB - 1P)", bibyte((KIByte*EIByte)-PiByte), "1023EB"},
|
||||||
|
|
||||||
|
{"bytes(5.5GiB)", bibyte(5.5 * GiByte), "5.5 GiB"},
|
||||||
|
|
||||||
|
{"bytes(5.5GB)", bbyte(5.5 * GByte), "5.5 GB"},
|
||||||
|
}.validate(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVeryBigBytes(t *testing.T) {
|
||||||
|
b, _ := (&big.Int{}).SetString("15347691069326346944512", 10)
|
||||||
|
s := BigBytes(b)
|
||||||
|
if s != "15 ZB" {
|
||||||
|
t.Errorf("Expected 15 ZB, got %v", s)
|
||||||
|
}
|
||||||
|
s = BigIBytes(b)
|
||||||
|
if s != "13 ZiB" {
|
||||||
|
t.Errorf("Expected 13 ZiB, got %v", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, _ = (&big.Int{}).SetString("15716035654990179271180288", 10)
|
||||||
|
s = BigBytes(b)
|
||||||
|
if s != "16 YB" {
|
||||||
|
t.Errorf("Expected 16 YB, got %v", s)
|
||||||
|
}
|
||||||
|
s = BigIBytes(b)
|
||||||
|
if s != "13 YiB" {
|
||||||
|
t.Errorf("Expected 13 YiB, got %v", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVeryVeryBigBytes(t *testing.T) {
|
||||||
|
b, _ := (&big.Int{}).SetString("16093220510709943573688614912", 10)
|
||||||
|
s := BigBytes(b)
|
||||||
|
if s != "16093 YB" {
|
||||||
|
t.Errorf("Expected 16093 YB, got %v", s)
|
||||||
|
}
|
||||||
|
s = BigIBytes(b)
|
||||||
|
if s != "13312 YiB" {
|
||||||
|
t.Errorf("Expected 13312 YiB, got %v", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseVeryBig(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in string
|
||||||
|
out string
|
||||||
|
}{
|
||||||
|
{"16 ZB", "16000000000000000000000"},
|
||||||
|
{"16 ZiB", "18889465931478580854784"},
|
||||||
|
{"16.5 ZB", "16500000000000000000000"},
|
||||||
|
{"16.5 ZiB", "19479761741837286506496"},
|
||||||
|
{"16 Z", "16000000000000000000000"},
|
||||||
|
{"16 Zi", "18889465931478580854784"},
|
||||||
|
{"16.5 Z", "16500000000000000000000"},
|
||||||
|
{"16.5 Zi", "19479761741837286506496"},
|
||||||
|
|
||||||
|
{"16 YB", "16000000000000000000000000"},
|
||||||
|
{"16 YiB", "19342813113834066795298816"},
|
||||||
|
{"16.5 YB", "16500000000000000000000000"},
|
||||||
|
{"16.5 YiB", "19947276023641381382651904"},
|
||||||
|
{"16 Y", "16000000000000000000000000"},
|
||||||
|
{"16 Yi", "19342813113834066795298816"},
|
||||||
|
{"16.5 Y", "16500000000000000000000000"},
|
||||||
|
{"16.5 Yi", "19947276023641381382651904"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
x, err := ParseBigBytes(test.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error parsing %q: %v", test.in, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if x.String() != test.out {
|
||||||
|
t.Errorf("Expected %q for %q, got %v", test.out, test.in, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkParseBigBytes(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
ParseBigBytes("16.5 Z")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkBigBytes(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bibyte(16.5 * GByte)
|
||||||
|
}
|
||||||
|
}
|
143
vendor/github.com/dustin/go-humanize/bytes.go
generated
vendored
Normal file
143
vendor/github.com/dustin/go-humanize/bytes.go
generated
vendored
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IEC Sizes.
|
||||||
|
// kibis of bits
|
||||||
|
const (
|
||||||
|
Byte = 1 << (iota * 10)
|
||||||
|
KiByte
|
||||||
|
MiByte
|
||||||
|
GiByte
|
||||||
|
TiByte
|
||||||
|
PiByte
|
||||||
|
EiByte
|
||||||
|
)
|
||||||
|
|
||||||
|
// SI Sizes.
|
||||||
|
const (
|
||||||
|
IByte = 1
|
||||||
|
KByte = IByte * 1000
|
||||||
|
MByte = KByte * 1000
|
||||||
|
GByte = MByte * 1000
|
||||||
|
TByte = GByte * 1000
|
||||||
|
PByte = TByte * 1000
|
||||||
|
EByte = PByte * 1000
|
||||||
|
)
|
||||||
|
|
||||||
|
var bytesSizeTable = map[string]uint64{
|
||||||
|
"b": Byte,
|
||||||
|
"kib": KiByte,
|
||||||
|
"kb": KByte,
|
||||||
|
"mib": MiByte,
|
||||||
|
"mb": MByte,
|
||||||
|
"gib": GiByte,
|
||||||
|
"gb": GByte,
|
||||||
|
"tib": TiByte,
|
||||||
|
"tb": TByte,
|
||||||
|
"pib": PiByte,
|
||||||
|
"pb": PByte,
|
||||||
|
"eib": EiByte,
|
||||||
|
"eb": EByte,
|
||||||
|
// Without suffix
|
||||||
|
"": Byte,
|
||||||
|
"ki": KiByte,
|
||||||
|
"k": KByte,
|
||||||
|
"mi": MiByte,
|
||||||
|
"m": MByte,
|
||||||
|
"gi": GiByte,
|
||||||
|
"g": GByte,
|
||||||
|
"ti": TiByte,
|
||||||
|
"t": TByte,
|
||||||
|
"pi": PiByte,
|
||||||
|
"p": PByte,
|
||||||
|
"ei": EiByte,
|
||||||
|
"e": EByte,
|
||||||
|
}
|
||||||
|
|
||||||
|
func logn(n, b float64) float64 {
|
||||||
|
return math.Log(n) / math.Log(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func humanateBytes(s uint64, base float64, sizes []string) string {
|
||||||
|
if s < 10 {
|
||||||
|
return fmt.Sprintf("%d B", s)
|
||||||
|
}
|
||||||
|
e := math.Floor(logn(float64(s), base))
|
||||||
|
suffix := sizes[int(e)]
|
||||||
|
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
|
||||||
|
f := "%.0f %s"
|
||||||
|
if val < 10 {
|
||||||
|
f = "%.1f %s"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(f, val, suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes produces a human readable representation of an SI size.
|
||||||
|
//
|
||||||
|
// See also: ParseBytes.
|
||||||
|
//
|
||||||
|
// Bytes(82854982) -> 83 MB
|
||||||
|
func Bytes(s uint64) string {
|
||||||
|
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
|
||||||
|
return humanateBytes(s, 1000, sizes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IBytes produces a human readable representation of an IEC size.
|
||||||
|
//
|
||||||
|
// See also: ParseBytes.
|
||||||
|
//
|
||||||
|
// IBytes(82854982) -> 79 MiB
|
||||||
|
func IBytes(s uint64) string {
|
||||||
|
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
|
||||||
|
return humanateBytes(s, 1024, sizes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseBytes parses a string representation of bytes into the number
|
||||||
|
// of bytes it represents.
|
||||||
|
//
|
||||||
|
// See Also: Bytes, IBytes.
|
||||||
|
//
|
||||||
|
// ParseBytes("42 MB") -> 42000000, nil
|
||||||
|
// ParseBytes("42 mib") -> 44040192, nil
|
||||||
|
func ParseBytes(s string) (uint64, error) {
|
||||||
|
lastDigit := 0
|
||||||
|
hasComma := false
|
||||||
|
for _, r := range s {
|
||||||
|
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if r == ',' {
|
||||||
|
hasComma = true
|
||||||
|
}
|
||||||
|
lastDigit++
|
||||||
|
}
|
||||||
|
|
||||||
|
num := s[:lastDigit]
|
||||||
|
if hasComma {
|
||||||
|
num = strings.Replace(num, ",", "", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := strconv.ParseFloat(num, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
|
||||||
|
if m, ok := bytesSizeTable[extra]; ok {
|
||||||
|
f *= float64(m)
|
||||||
|
if f >= math.MaxUint64 {
|
||||||
|
return 0, fmt.Errorf("too large: %v", s)
|
||||||
|
}
|
||||||
|
return uint64(f), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, fmt.Errorf("unhandled size name: %v", extra)
|
||||||
|
}
|
146
vendor/github.com/dustin/go-humanize/bytes_test.go
generated
vendored
Normal file
146
vendor/github.com/dustin/go-humanize/bytes_test.go
generated
vendored
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestByteParsing(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in string
|
||||||
|
exp uint64
|
||||||
|
}{
|
||||||
|
{"42", 42},
|
||||||
|
{"42MB", 42000000},
|
||||||
|
{"42MiB", 44040192},
|
||||||
|
{"42mb", 42000000},
|
||||||
|
{"42mib", 44040192},
|
||||||
|
{"42MIB", 44040192},
|
||||||
|
{"42 MB", 42000000},
|
||||||
|
{"42 MiB", 44040192},
|
||||||
|
{"42 mb", 42000000},
|
||||||
|
{"42 mib", 44040192},
|
||||||
|
{"42 MIB", 44040192},
|
||||||
|
{"42.5MB", 42500000},
|
||||||
|
{"42.5MiB", 44564480},
|
||||||
|
{"42.5 MB", 42500000},
|
||||||
|
{"42.5 MiB", 44564480},
|
||||||
|
// No need to say B
|
||||||
|
{"42M", 42000000},
|
||||||
|
{"42Mi", 44040192},
|
||||||
|
{"42m", 42000000},
|
||||||
|
{"42mi", 44040192},
|
||||||
|
{"42MI", 44040192},
|
||||||
|
{"42 M", 42000000},
|
||||||
|
{"42 Mi", 44040192},
|
||||||
|
{"42 m", 42000000},
|
||||||
|
{"42 mi", 44040192},
|
||||||
|
{"42 MI", 44040192},
|
||||||
|
{"42.5M", 42500000},
|
||||||
|
{"42.5Mi", 44564480},
|
||||||
|
{"42.5 M", 42500000},
|
||||||
|
{"42.5 Mi", 44564480},
|
||||||
|
// Bug #42
|
||||||
|
{"1,005.03 MB", 1005030000},
|
||||||
|
// Large testing, breaks when too much larger than
|
||||||
|
// this.
|
||||||
|
{"12.5 EB", uint64(12.5 * float64(EByte))},
|
||||||
|
{"12.5 E", uint64(12.5 * float64(EByte))},
|
||||||
|
{"12.5 EiB", uint64(12.5 * float64(EiByte))},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range tests {
|
||||||
|
got, err := ParseBytes(p.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Couldn't parse %v: %v", p.in, err)
|
||||||
|
}
|
||||||
|
if got != p.exp {
|
||||||
|
t.Errorf("Expected %v for %v, got %v",
|
||||||
|
p.exp, p.in, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestByteErrors(t *testing.T) {
|
||||||
|
got, err := ParseBytes("84 JB")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected error, got %v", got)
|
||||||
|
}
|
||||||
|
got, err = ParseBytes("")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected error parsing nothing")
|
||||||
|
}
|
||||||
|
got, err = ParseBytes("16 EiB")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected error, got %v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBytes(t *testing.T) {
|
||||||
|
testList{
|
||||||
|
{"bytes(0)", Bytes(0), "0 B"},
|
||||||
|
{"bytes(1)", Bytes(1), "1 B"},
|
||||||
|
{"bytes(803)", Bytes(803), "803 B"},
|
||||||
|
{"bytes(999)", Bytes(999), "999 B"},
|
||||||
|
|
||||||
|
{"bytes(1024)", Bytes(1024), "1.0 kB"},
|
||||||
|
{"bytes(9999)", Bytes(9999), "10 kB"},
|
||||||
|
{"bytes(1MB - 1)", Bytes(MByte - Byte), "1000 kB"},
|
||||||
|
|
||||||
|
{"bytes(1MB)", Bytes(1024 * 1024), "1.0 MB"},
|
||||||
|
{"bytes(1GB - 1K)", Bytes(GByte - KByte), "1000 MB"},
|
||||||
|
|
||||||
|
{"bytes(1GB)", Bytes(GByte), "1.0 GB"},
|
||||||
|
{"bytes(1TB - 1M)", Bytes(TByte - MByte), "1000 GB"},
|
||||||
|
{"bytes(10MB)", Bytes(9999 * 1000), "10 MB"},
|
||||||
|
|
||||||
|
{"bytes(1TB)", Bytes(TByte), "1.0 TB"},
|
||||||
|
{"bytes(1PB - 1T)", Bytes(PByte - TByte), "999 TB"},
|
||||||
|
|
||||||
|
{"bytes(1PB)", Bytes(PByte), "1.0 PB"},
|
||||||
|
{"bytes(1PB - 1T)", Bytes(EByte - PByte), "999 PB"},
|
||||||
|
|
||||||
|
{"bytes(1EB)", Bytes(EByte), "1.0 EB"},
|
||||||
|
// Overflows.
|
||||||
|
// {"bytes(1EB - 1P)", Bytes((KByte*EByte)-PByte), "1023EB"},
|
||||||
|
|
||||||
|
{"bytes(0)", IBytes(0), "0 B"},
|
||||||
|
{"bytes(1)", IBytes(1), "1 B"},
|
||||||
|
{"bytes(803)", IBytes(803), "803 B"},
|
||||||
|
{"bytes(1023)", IBytes(1023), "1023 B"},
|
||||||
|
|
||||||
|
{"bytes(1024)", IBytes(1024), "1.0 KiB"},
|
||||||
|
{"bytes(1MB - 1)", IBytes(MiByte - IByte), "1024 KiB"},
|
||||||
|
|
||||||
|
{"bytes(1MB)", IBytes(1024 * 1024), "1.0 MiB"},
|
||||||
|
{"bytes(1GB - 1K)", IBytes(GiByte - KiByte), "1024 MiB"},
|
||||||
|
|
||||||
|
{"bytes(1GB)", IBytes(GiByte), "1.0 GiB"},
|
||||||
|
{"bytes(1TB - 1M)", IBytes(TiByte - MiByte), "1024 GiB"},
|
||||||
|
|
||||||
|
{"bytes(1TB)", IBytes(TiByte), "1.0 TiB"},
|
||||||
|
{"bytes(1PB - 1T)", IBytes(PiByte - TiByte), "1023 TiB"},
|
||||||
|
|
||||||
|
{"bytes(1PB)", IBytes(PiByte), "1.0 PiB"},
|
||||||
|
{"bytes(1PB - 1T)", IBytes(EiByte - PiByte), "1023 PiB"},
|
||||||
|
|
||||||
|
{"bytes(1EiB)", IBytes(EiByte), "1.0 EiB"},
|
||||||
|
// Overflows.
|
||||||
|
// {"bytes(1EB - 1P)", IBytes((KIByte*EIByte)-PiByte), "1023EB"},
|
||||||
|
|
||||||
|
{"bytes(5.5GiB)", IBytes(5.5 * GiByte), "5.5 GiB"},
|
||||||
|
|
||||||
|
{"bytes(5.5GB)", Bytes(5.5 * GByte), "5.5 GB"},
|
||||||
|
}.validate(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkParseBytes(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
ParseBytes("16.5 GB")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkBytes(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Bytes(16.5 * GByte)
|
||||||
|
}
|
||||||
|
}
|
108
vendor/github.com/dustin/go-humanize/comma.go
generated
vendored
Normal file
108
vendor/github.com/dustin/go-humanize/comma.go
generated
vendored
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Comma produces a string form of the given number in base 10 with
|
||||||
|
// commas after every three orders of magnitude.
|
||||||
|
//
|
||||||
|
// e.g. Comma(834142) -> 834,142
|
||||||
|
func Comma(v int64) string {
|
||||||
|
sign := ""
|
||||||
|
|
||||||
|
// Min int64 can't be negated to a usable value, so it has to be special cased.
|
||||||
|
if v == math.MinInt64 {
|
||||||
|
return "-9,223,372,036,854,775,808"
|
||||||
|
}
|
||||||
|
|
||||||
|
if v < 0 {
|
||||||
|
sign = "-"
|
||||||
|
v = 0 - v
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := []string{"", "", "", "", "", "", ""}
|
||||||
|
j := len(parts) - 1
|
||||||
|
|
||||||
|
for v > 999 {
|
||||||
|
parts[j] = strconv.FormatInt(v%1000, 10)
|
||||||
|
switch len(parts[j]) {
|
||||||
|
case 2:
|
||||||
|
parts[j] = "0" + parts[j]
|
||||||
|
case 1:
|
||||||
|
parts[j] = "00" + parts[j]
|
||||||
|
}
|
||||||
|
v = v / 1000
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
parts[j] = strconv.Itoa(int(v))
|
||||||
|
return sign + strings.Join(parts[j:], ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commaf produces a string form of the given number in base 10 with
|
||||||
|
// commas after every three orders of magnitude.
|
||||||
|
//
|
||||||
|
// e.g. Commaf(834142.32) -> 834,142.32
|
||||||
|
func Commaf(v float64) string {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
if v < 0 {
|
||||||
|
buf.Write([]byte{'-'})
|
||||||
|
v = 0 - v
|
||||||
|
}
|
||||||
|
|
||||||
|
comma := []byte{','}
|
||||||
|
|
||||||
|
parts := strings.Split(strconv.FormatFloat(v, 'f', -1, 64), ".")
|
||||||
|
pos := 0
|
||||||
|
if len(parts[0])%3 != 0 {
|
||||||
|
pos += len(parts[0]) % 3
|
||||||
|
buf.WriteString(parts[0][:pos])
|
||||||
|
buf.Write(comma)
|
||||||
|
}
|
||||||
|
for ; pos < len(parts[0]); pos += 3 {
|
||||||
|
buf.WriteString(parts[0][pos : pos+3])
|
||||||
|
buf.Write(comma)
|
||||||
|
}
|
||||||
|
buf.Truncate(buf.Len() - 1)
|
||||||
|
|
||||||
|
if len(parts) > 1 {
|
||||||
|
buf.Write([]byte{'.'})
|
||||||
|
buf.WriteString(parts[1])
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// BigComma produces a string form of the given big.Int in base 10
|
||||||
|
// with commas after every three orders of magnitude.
|
||||||
|
func BigComma(b *big.Int) string {
|
||||||
|
sign := ""
|
||||||
|
if b.Sign() < 0 {
|
||||||
|
sign = "-"
|
||||||
|
b.Abs(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
athousand := big.NewInt(1000)
|
||||||
|
c := (&big.Int{}).Set(b)
|
||||||
|
_, m := oom(c, athousand)
|
||||||
|
parts := make([]string, m+1)
|
||||||
|
j := len(parts) - 1
|
||||||
|
|
||||||
|
mod := &big.Int{}
|
||||||
|
for b.Cmp(athousand) >= 0 {
|
||||||
|
b.DivMod(b, athousand, mod)
|
||||||
|
parts[j] = strconv.FormatInt(mod.Int64(), 10)
|
||||||
|
switch len(parts[j]) {
|
||||||
|
case 2:
|
||||||
|
parts[j] = "0" + parts[j]
|
||||||
|
case 1:
|
||||||
|
parts[j] = "00" + parts[j]
|
||||||
|
}
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
parts[j] = strconv.Itoa(int(b.Int64()))
|
||||||
|
return sign + strings.Join(parts[j:], ",")
|
||||||
|
}
|
136
vendor/github.com/dustin/go-humanize/comma_test.go
generated
vendored
Normal file
136
vendor/github.com/dustin/go-humanize/comma_test.go
generated
vendored
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCommas(t *testing.T) {
|
||||||
|
testList{
|
||||||
|
{"0", Comma(0), "0"},
|
||||||
|
{"10", Comma(10), "10"},
|
||||||
|
{"100", Comma(100), "100"},
|
||||||
|
{"1,000", Comma(1000), "1,000"},
|
||||||
|
{"10,000", Comma(10000), "10,000"},
|
||||||
|
{"100,000", Comma(100000), "100,000"},
|
||||||
|
{"10,000,000", Comma(10000000), "10,000,000"},
|
||||||
|
{"10,100,000", Comma(10100000), "10,100,000"},
|
||||||
|
{"10,010,000", Comma(10010000), "10,010,000"},
|
||||||
|
{"10,001,000", Comma(10001000), "10,001,000"},
|
||||||
|
{"123,456,789", Comma(123456789), "123,456,789"},
|
||||||
|
{"maxint", Comma(9.223372e+18), "9,223,372,000,000,000,000"},
|
||||||
|
{"math.maxint", Comma(math.MaxInt64), "9,223,372,036,854,775,807"},
|
||||||
|
{"math.minint", Comma(math.MinInt64), "-9,223,372,036,854,775,808"},
|
||||||
|
{"minint", Comma(-9.223372e+18), "-9,223,372,000,000,000,000"},
|
||||||
|
{"-123,456,789", Comma(-123456789), "-123,456,789"},
|
||||||
|
{"-10,100,000", Comma(-10100000), "-10,100,000"},
|
||||||
|
{"-10,010,000", Comma(-10010000), "-10,010,000"},
|
||||||
|
{"-10,001,000", Comma(-10001000), "-10,001,000"},
|
||||||
|
{"-10,000,000", Comma(-10000000), "-10,000,000"},
|
||||||
|
{"-100,000", Comma(-100000), "-100,000"},
|
||||||
|
{"-10,000", Comma(-10000), "-10,000"},
|
||||||
|
{"-1,000", Comma(-1000), "-1,000"},
|
||||||
|
{"-100", Comma(-100), "-100"},
|
||||||
|
{"-10", Comma(-10), "-10"},
|
||||||
|
}.validate(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommafs(t *testing.T) {
|
||||||
|
testList{
|
||||||
|
{"0", Commaf(0), "0"},
|
||||||
|
{"10.11", Commaf(10.11), "10.11"},
|
||||||
|
{"100", Commaf(100), "100"},
|
||||||
|
{"1,000", Commaf(1000), "1,000"},
|
||||||
|
{"10,000", Commaf(10000), "10,000"},
|
||||||
|
{"100,000", Commaf(100000), "100,000"},
|
||||||
|
{"834,142.32", Commaf(834142.32), "834,142.32"},
|
||||||
|
{"10,000,000", Commaf(10000000), "10,000,000"},
|
||||||
|
{"10,100,000", Commaf(10100000), "10,100,000"},
|
||||||
|
{"10,010,000", Commaf(10010000), "10,010,000"},
|
||||||
|
{"10,001,000", Commaf(10001000), "10,001,000"},
|
||||||
|
{"123,456,789", Commaf(123456789), "123,456,789"},
|
||||||
|
{"maxf64", Commaf(math.MaxFloat64), "179,769,313,486,231,570,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000"},
|
||||||
|
{"minf64", Commaf(math.SmallestNonzeroFloat64), "0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005"},
|
||||||
|
{"-123,456,789", Commaf(-123456789), "-123,456,789"},
|
||||||
|
{"-10,100,000", Commaf(-10100000), "-10,100,000"},
|
||||||
|
{"-10,010,000", Commaf(-10010000), "-10,010,000"},
|
||||||
|
{"-10,001,000", Commaf(-10001000), "-10,001,000"},
|
||||||
|
{"-10,000,000", Commaf(-10000000), "-10,000,000"},
|
||||||
|
{"-100,000", Commaf(-100000), "-100,000"},
|
||||||
|
{"-10,000", Commaf(-10000), "-10,000"},
|
||||||
|
{"-1,000", Commaf(-1000), "-1,000"},
|
||||||
|
{"-100.11", Commaf(-100.11), "-100.11"},
|
||||||
|
{"-10", Commaf(-10), "-10"},
|
||||||
|
}.validate(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCommas(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Comma(1234567890)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCommaf(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Commaf(1234567890.83584)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkBigCommas(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
BigComma(big.NewInt(1234567890))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bigComma(i int64) string {
|
||||||
|
return BigComma(big.NewInt(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBigCommas(t *testing.T) {
|
||||||
|
testList{
|
||||||
|
{"0", bigComma(0), "0"},
|
||||||
|
{"10", bigComma(10), "10"},
|
||||||
|
{"100", bigComma(100), "100"},
|
||||||
|
{"1,000", bigComma(1000), "1,000"},
|
||||||
|
{"10,000", bigComma(10000), "10,000"},
|
||||||
|
{"100,000", bigComma(100000), "100,000"},
|
||||||
|
{"10,000,000", bigComma(10000000), "10,000,000"},
|
||||||
|
{"10,100,000", bigComma(10100000), "10,100,000"},
|
||||||
|
{"10,010,000", bigComma(10010000), "10,010,000"},
|
||||||
|
{"10,001,000", bigComma(10001000), "10,001,000"},
|
||||||
|
{"123,456,789", bigComma(123456789), "123,456,789"},
|
||||||
|
{"maxint", bigComma(9.223372e+18), "9,223,372,000,000,000,000"},
|
||||||
|
{"minint", bigComma(-9.223372e+18), "-9,223,372,000,000,000,000"},
|
||||||
|
{"-123,456,789", bigComma(-123456789), "-123,456,789"},
|
||||||
|
{"-10,100,000", bigComma(-10100000), "-10,100,000"},
|
||||||
|
{"-10,010,000", bigComma(-10010000), "-10,010,000"},
|
||||||
|
{"-10,001,000", bigComma(-10001000), "-10,001,000"},
|
||||||
|
{"-10,000,000", bigComma(-10000000), "-10,000,000"},
|
||||||
|
{"-100,000", bigComma(-100000), "-100,000"},
|
||||||
|
{"-10,000", bigComma(-10000), "-10,000"},
|
||||||
|
{"-1,000", bigComma(-1000), "-1,000"},
|
||||||
|
{"-100", bigComma(-100), "-100"},
|
||||||
|
{"-10", bigComma(-10), "-10"},
|
||||||
|
}.validate(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVeryBigCommas(t *testing.T) {
|
||||||
|
tests := []struct{ in, exp string }{
|
||||||
|
{
|
||||||
|
"84889279597249724975972597249849757294578485",
|
||||||
|
"84,889,279,597,249,724,975,972,597,249,849,757,294,578,485",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"-84889279597249724975972597249849757294578485",
|
||||||
|
"-84,889,279,597,249,724,975,972,597,249,849,757,294,578,485",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
n, _ := (&big.Int{}).SetString(test.in, 10)
|
||||||
|
got := BigComma(n)
|
||||||
|
if test.exp != got {
|
||||||
|
t.Errorf("Expected %q, got %q", test.exp, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
vendor/github.com/dustin/go-humanize/commaf.go
generated
vendored
Normal file
40
vendor/github.com/dustin/go-humanize/commaf.go
generated
vendored
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// +build go1.6
|
||||||
|
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BigCommaf produces a string form of the given big.Float in base 10
|
||||||
|
// with commas after every three orders of magnitude.
|
||||||
|
func BigCommaf(v *big.Float) string {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
if v.Sign() < 0 {
|
||||||
|
buf.Write([]byte{'-'})
|
||||||
|
v.Abs(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
comma := []byte{','}
|
||||||
|
|
||||||
|
parts := strings.Split(v.Text('f', -1), ".")
|
||||||
|
pos := 0
|
||||||
|
if len(parts[0])%3 != 0 {
|
||||||
|
pos += len(parts[0]) % 3
|
||||||
|
buf.WriteString(parts[0][:pos])
|
||||||
|
buf.Write(comma)
|
||||||
|
}
|
||||||
|
for ; pos < len(parts[0]); pos += 3 {
|
||||||
|
buf.WriteString(parts[0][pos : pos+3])
|
||||||
|
buf.Write(comma)
|
||||||
|
}
|
||||||
|
buf.Truncate(buf.Len() - 1)
|
||||||
|
|
||||||
|
if len(parts) > 1 {
|
||||||
|
buf.Write([]byte{'.'})
|
||||||
|
buf.WriteString(parts[1])
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
44
vendor/github.com/dustin/go-humanize/commaf_test.go
generated
vendored
Normal file
44
vendor/github.com/dustin/go-humanize/commaf_test.go
generated
vendored
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// +build go1.6
|
||||||
|
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkBigCommaf(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Commaf(1234567890.83584)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBigCommafs(t *testing.T) {
|
||||||
|
testList{
|
||||||
|
{"0", BigCommaf(big.NewFloat(0)), "0"},
|
||||||
|
{"10.11", BigCommaf(big.NewFloat(10.11)), "10.11"},
|
||||||
|
{"100", BigCommaf(big.NewFloat(100)), "100"},
|
||||||
|
{"1,000", BigCommaf(big.NewFloat(1000)), "1,000"},
|
||||||
|
{"10,000", BigCommaf(big.NewFloat(10000)), "10,000"},
|
||||||
|
{"100,000", BigCommaf(big.NewFloat(100000)), "100,000"},
|
||||||
|
{"834,142.32", BigCommaf(big.NewFloat(834142.32)), "834,142.32"},
|
||||||
|
{"10,000,000", BigCommaf(big.NewFloat(10000000)), "10,000,000"},
|
||||||
|
{"10,100,000", BigCommaf(big.NewFloat(10100000)), "10,100,000"},
|
||||||
|
{"10,010,000", BigCommaf(big.NewFloat(10010000)), "10,010,000"},
|
||||||
|
{"10,001,000", BigCommaf(big.NewFloat(10001000)), "10,001,000"},
|
||||||
|
{"123,456,789", BigCommaf(big.NewFloat(123456789)), "123,456,789"},
|
||||||
|
{"maxf64", BigCommaf(big.NewFloat(math.MaxFloat64)), "179,769,313,486,231,570,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000"},
|
||||||
|
{"minf64", BigCommaf(big.NewFloat(math.SmallestNonzeroFloat64)), "0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004940656458412465"},
|
||||||
|
{"-123,456,789", BigCommaf(big.NewFloat(-123456789)), "-123,456,789"},
|
||||||
|
{"-10,100,000", BigCommaf(big.NewFloat(-10100000)), "-10,100,000"},
|
||||||
|
{"-10,010,000", BigCommaf(big.NewFloat(-10010000)), "-10,010,000"},
|
||||||
|
{"-10,001,000", BigCommaf(big.NewFloat(-10001000)), "-10,001,000"},
|
||||||
|
{"-10,000,000", BigCommaf(big.NewFloat(-10000000)), "-10,000,000"},
|
||||||
|
{"-100,000", BigCommaf(big.NewFloat(-100000)), "-100,000"},
|
||||||
|
{"-10,000", BigCommaf(big.NewFloat(-10000)), "-10,000"},
|
||||||
|
{"-1,000", BigCommaf(big.NewFloat(-1000)), "-1,000"},
|
||||||
|
{"-100.11", BigCommaf(big.NewFloat(-100.11)), "-100.11"},
|
||||||
|
{"-10", BigCommaf(big.NewFloat(-10)), "-10"},
|
||||||
|
}.validate(t)
|
||||||
|
}
|
18
vendor/github.com/dustin/go-humanize/common_test.go
generated
vendored
Normal file
18
vendor/github.com/dustin/go-humanize/common_test.go
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testList []struct {
|
||||||
|
name, got, exp string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tl testList) validate(t *testing.T) {
|
||||||
|
for _, test := range tl {
|
||||||
|
if test.got != test.exp {
|
||||||
|
t.Errorf("On %v, expected '%v', but got '%v'",
|
||||||
|
test.name, test.exp, test.got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
96
vendor/github.com/dustin/go-humanize/english/words.go
generated
vendored
Normal file
96
vendor/github.com/dustin/go-humanize/english/words.go
generated
vendored
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
// Package english provides utilities to generate more user-friendly English output.
|
||||||
|
package english
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// These are included because they are common technical terms.
|
||||||
|
var specialPlurals = map[string]string{
|
||||||
|
"index": "indices",
|
||||||
|
"matrix": "matrices",
|
||||||
|
"vertex": "vertices",
|
||||||
|
}
|
||||||
|
|
||||||
|
var sibilantEndings = []string{"s", "sh", "tch", "x"}
|
||||||
|
|
||||||
|
var isVowel = map[byte]bool{
|
||||||
|
'A': true, 'E': true, 'I': true, 'O': true, 'U': true,
|
||||||
|
'a': true, 'e': true, 'i': true, 'o': true, 'u': true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// PluralWord builds the plural form of an English word.
|
||||||
|
// The simple English rules of regular pluralization will be used
|
||||||
|
// if the plural form is an empty string (i.e. not explicitly given).
|
||||||
|
// The special cases are not guaranteed to work for strings outside ASCII.
|
||||||
|
func PluralWord(quantity int, singular, plural string) string {
|
||||||
|
if quantity == 1 {
|
||||||
|
return singular
|
||||||
|
}
|
||||||
|
if plural != "" {
|
||||||
|
return plural
|
||||||
|
}
|
||||||
|
if plural = specialPlurals[singular]; plural != "" {
|
||||||
|
return plural
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to guess what the English plural might be. Keep this
|
||||||
|
// function simple! It doesn't need to know about every possiblity;
|
||||||
|
// only regular rules and the most common special cases.
|
||||||
|
//
|
||||||
|
// Reference: http://en.wikipedia.org/wiki/English_plural
|
||||||
|
|
||||||
|
for _, ending := range sibilantEndings {
|
||||||
|
if strings.HasSuffix(singular, ending) {
|
||||||
|
return singular + "es"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l := len(singular)
|
||||||
|
if l >= 2 && singular[l-1] == 'o' && !isVowel[singular[l-2]] {
|
||||||
|
return singular + "es"
|
||||||
|
}
|
||||||
|
if l >= 2 && singular[l-1] == 'y' && !isVowel[singular[l-2]] {
|
||||||
|
return singular[:l-1] + "ies"
|
||||||
|
}
|
||||||
|
|
||||||
|
return singular + "s"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plural formats an integer and a string into a single pluralized string.
|
||||||
|
// The simple English rules of regular pluralization will be used
|
||||||
|
// if the plural form is an empty string (i.e. not explicitly given).
|
||||||
|
func Plural(quantity int, singular, plural string) string {
|
||||||
|
return fmt.Sprintf("%d %s", quantity, PluralWord(quantity, singular, plural))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WordSeries converts a list of words into a word series in English.
|
||||||
|
// It returns a string containing all the given words separated by commas,
|
||||||
|
// the coordinating conjunction, and a serial comma, as appropriate.
|
||||||
|
func WordSeries(words []string, conjunction string) string {
|
||||||
|
switch len(words) {
|
||||||
|
case 0:
|
||||||
|
return ""
|
||||||
|
case 1:
|
||||||
|
return words[0]
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%s %s %s", strings.Join(words[:len(words)-1], ", "), conjunction, words[len(words)-1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OxfordWordSeries converts a list of words into a word series in English,
|
||||||
|
// using an Oxford comma (https://en.wikipedia.org/wiki/Serial_comma). It
|
||||||
|
// returns a string containing all the given words separated by commas, the
|
||||||
|
// coordinating conjunction, and a serial comma, as appropriate.
|
||||||
|
func OxfordWordSeries(words []string, conjunction string) string {
|
||||||
|
switch len(words) {
|
||||||
|
case 0:
|
||||||
|
return ""
|
||||||
|
case 1:
|
||||||
|
return words[0]
|
||||||
|
case 2:
|
||||||
|
return strings.Join(words, fmt.Sprintf(" %s ", conjunction))
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%s, %s %s", strings.Join(words[:len(words)-1], ", "), conjunction, words[len(words)-1])
|
||||||
|
}
|
||||||
|
}
|
94
vendor/github.com/dustin/go-humanize/english/words_test.go
generated
vendored
Normal file
94
vendor/github.com/dustin/go-humanize/english/words_test.go
generated
vendored
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
package english
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPluralWord(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
n int
|
||||||
|
singular, plural string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{0, "object", "", "objects"},
|
||||||
|
{1, "object", "", "object"},
|
||||||
|
{-1, "object", "", "objects"},
|
||||||
|
{42, "object", "", "objects"},
|
||||||
|
{2, "vax", "vaxen", "vaxen"},
|
||||||
|
|
||||||
|
// special cases
|
||||||
|
{2, "index", "", "indices"},
|
||||||
|
|
||||||
|
// ending in a sibilant sound
|
||||||
|
{2, "bus", "", "buses"},
|
||||||
|
{2, "bush", "", "bushes"},
|
||||||
|
{2, "watch", "", "watches"},
|
||||||
|
{2, "box", "", "boxes"},
|
||||||
|
|
||||||
|
// ending with 'o' preceded by a consonant
|
||||||
|
{2, "hero", "", "heroes"},
|
||||||
|
|
||||||
|
// ending with 'y' preceded by a consonant
|
||||||
|
{2, "lady", "", "ladies"},
|
||||||
|
{2, "day", "", "days"},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
if got := PluralWord(tt.n, tt.singular, tt.plural); got != tt.want {
|
||||||
|
t.Errorf("PluralWord(%d, %q, %q)=%q; want: %q", tt.n, tt.singular, tt.plural, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlural(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
n int
|
||||||
|
singular, plural string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{1, "object", "", "1 object"},
|
||||||
|
{42, "object", "", "42 objects"},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
if got := Plural(tt.n, tt.singular, tt.plural); got != tt.want {
|
||||||
|
t.Errorf("Plural(%d, %q, %q)=%q; want: %q", tt.n, tt.singular, tt.plural, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWordSeries(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
words []string
|
||||||
|
conjunction string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{[]string{}, "and", ""},
|
||||||
|
{[]string{"foo"}, "and", "foo"},
|
||||||
|
{[]string{"foo", "bar"}, "and", "foo and bar"},
|
||||||
|
{[]string{"foo", "bar", "baz"}, "and", "foo, bar and baz"},
|
||||||
|
{[]string{"foo", "bar", "baz"}, "or", "foo, bar or baz"},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
if got := WordSeries(tt.words, tt.conjunction); got != tt.want {
|
||||||
|
t.Errorf("WordSeries(%q, %q)=%q; want: %q", tt.words, tt.conjunction, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOxfordWordSeries(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
words []string
|
||||||
|
conjunction string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{[]string{}, "and", ""},
|
||||||
|
{[]string{"foo"}, "and", "foo"},
|
||||||
|
{[]string{"foo", "bar"}, "and", "foo and bar"},
|
||||||
|
{[]string{"foo", "bar", "baz"}, "and", "foo, bar, and baz"},
|
||||||
|
{[]string{"foo", "bar", "baz"}, "or", "foo, bar, or baz"},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
if got := OxfordWordSeries(tt.words, tt.conjunction); got != tt.want {
|
||||||
|
t.Errorf("OxfordWordSeries(%q, %q)=%q; want: %q", tt.words, tt.conjunction, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
vendor/github.com/dustin/go-humanize/ftoa.go
generated
vendored
Normal file
23
vendor/github.com/dustin/go-humanize/ftoa.go
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func stripTrailingZeros(s string) string {
|
||||||
|
offset := len(s) - 1
|
||||||
|
for offset > 0 {
|
||||||
|
if s[offset] == '.' {
|
||||||
|
offset--
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if s[offset] != '0' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
offset--
|
||||||
|
}
|
||||||
|
return s[:offset+1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ftoa converts a float to a string with no trailing zeros.
|
||||||
|
func Ftoa(num float64) string {
|
||||||
|
return stripTrailingZeros(strconv.FormatFloat(num, 'f', 6, 64))
|
||||||
|
}
|
55
vendor/github.com/dustin/go-humanize/ftoa_test.go
generated
vendored
Normal file
55
vendor/github.com/dustin/go-humanize/ftoa_test.go
generated
vendored
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFtoa(t *testing.T) {
|
||||||
|
testList{
|
||||||
|
{"200", Ftoa(200), "200"},
|
||||||
|
{"2", Ftoa(2), "2"},
|
||||||
|
{"2.2", Ftoa(2.2), "2.2"},
|
||||||
|
{"2.02", Ftoa(2.02), "2.02"},
|
||||||
|
{"200.02", Ftoa(200.02), "200.02"},
|
||||||
|
}.validate(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkFtoaRegexTrailing(b *testing.B) {
|
||||||
|
trailingZerosRegex := regexp.MustCompile(`\.?0+$`)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
trailingZerosRegex.ReplaceAllString("2.00000", "")
|
||||||
|
trailingZerosRegex.ReplaceAllString("2.0000", "")
|
||||||
|
trailingZerosRegex.ReplaceAllString("2.000", "")
|
||||||
|
trailingZerosRegex.ReplaceAllString("2.00", "")
|
||||||
|
trailingZerosRegex.ReplaceAllString("2.0", "")
|
||||||
|
trailingZerosRegex.ReplaceAllString("2", "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkFtoaFunc(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
stripTrailingZeros("2.00000")
|
||||||
|
stripTrailingZeros("2.0000")
|
||||||
|
stripTrailingZeros("2.000")
|
||||||
|
stripTrailingZeros("2.00")
|
||||||
|
stripTrailingZeros("2.0")
|
||||||
|
stripTrailingZeros("2")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkFmtF(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = fmt.Sprintf("%f", 2.03584)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkStrconvF(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
strconv.FormatFloat(2.03584, 'f', 6, 64)
|
||||||
|
}
|
||||||
|
}
|
8
vendor/github.com/dustin/go-humanize/humanize.go
generated
vendored
Normal file
8
vendor/github.com/dustin/go-humanize/humanize.go
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
/*
|
||||||
|
Package humanize converts boring ugly numbers to human-friendly strings and back.
|
||||||
|
|
||||||
|
Durations can be turned into strings such as "3 days ago", numbers
|
||||||
|
representing sizes like 82854982 into useful strings like, "83 MB" or
|
||||||
|
"79 MiB" (whichever you prefer).
|
||||||
|
*/
|
||||||
|
package humanize
|
192
vendor/github.com/dustin/go-humanize/number.go
generated
vendored
Normal file
192
vendor/github.com/dustin/go-humanize/number.go
generated
vendored
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
/*
|
||||||
|
Slightly adapted from the source to fit go-humanize.
|
||||||
|
|
||||||
|
Author: https://github.com/gorhill
|
||||||
|
Source: https://gist.github.com/gorhill/5285193
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
renderFloatPrecisionMultipliers = [...]float64{
|
||||||
|
1,
|
||||||
|
10,
|
||||||
|
100,
|
||||||
|
1000,
|
||||||
|
10000,
|
||||||
|
100000,
|
||||||
|
1000000,
|
||||||
|
10000000,
|
||||||
|
100000000,
|
||||||
|
1000000000,
|
||||||
|
}
|
||||||
|
|
||||||
|
renderFloatPrecisionRounders = [...]float64{
|
||||||
|
0.5,
|
||||||
|
0.05,
|
||||||
|
0.005,
|
||||||
|
0.0005,
|
||||||
|
0.00005,
|
||||||
|
0.000005,
|
||||||
|
0.0000005,
|
||||||
|
0.00000005,
|
||||||
|
0.000000005,
|
||||||
|
0.0000000005,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// FormatFloat produces a formatted number as string based on the following user-specified criteria:
|
||||||
|
// * thousands separator
|
||||||
|
// * decimal separator
|
||||||
|
// * decimal precision
|
||||||
|
//
|
||||||
|
// Usage: s := RenderFloat(format, n)
|
||||||
|
// The format parameter tells how to render the number n.
|
||||||
|
//
|
||||||
|
// See examples: http://play.golang.org/p/LXc1Ddm1lJ
|
||||||
|
//
|
||||||
|
// Examples of format strings, given n = 12345.6789:
|
||||||
|
// "#,###.##" => "12,345.67"
|
||||||
|
// "#,###." => "12,345"
|
||||||
|
// "#,###" => "12345,678"
|
||||||
|
// "#\u202F###,##" => "12 345,68"
|
||||||
|
// "#.###,###### => 12.345,678900
|
||||||
|
// "" (aka default format) => 12,345.67
|
||||||
|
//
|
||||||
|
// The highest precision allowed is 9 digits after the decimal symbol.
|
||||||
|
// There is also a version for integer number, FormatInteger(),
|
||||||
|
// which is convenient for calls within template.
|
||||||
|
func FormatFloat(format string, n float64) string {
|
||||||
|
// Special cases:
|
||||||
|
// NaN = "NaN"
|
||||||
|
// +Inf = "+Infinity"
|
||||||
|
// -Inf = "-Infinity"
|
||||||
|
if math.IsNaN(n) {
|
||||||
|
return "NaN"
|
||||||
|
}
|
||||||
|
if n > math.MaxFloat64 {
|
||||||
|
return "Infinity"
|
||||||
|
}
|
||||||
|
if n < -math.MaxFloat64 {
|
||||||
|
return "-Infinity"
|
||||||
|
}
|
||||||
|
|
||||||
|
// default format
|
||||||
|
precision := 2
|
||||||
|
decimalStr := "."
|
||||||
|
thousandStr := ","
|
||||||
|
positiveStr := ""
|
||||||
|
negativeStr := "-"
|
||||||
|
|
||||||
|
if len(format) > 0 {
|
||||||
|
format := []rune(format)
|
||||||
|
|
||||||
|
// If there is an explicit format directive,
|
||||||
|
// then default values are these:
|
||||||
|
precision = 9
|
||||||
|
thousandStr = ""
|
||||||
|
|
||||||
|
// collect indices of meaningful formatting directives
|
||||||
|
formatIndx := []int{}
|
||||||
|
for i, char := range format {
|
||||||
|
if char != '#' && char != '0' {
|
||||||
|
formatIndx = append(formatIndx, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(formatIndx) > 0 {
|
||||||
|
// Directive at index 0:
|
||||||
|
// Must be a '+'
|
||||||
|
// Raise an error if not the case
|
||||||
|
// index: 0123456789
|
||||||
|
// +0.000,000
|
||||||
|
// +000,000.0
|
||||||
|
// +0000.00
|
||||||
|
// +0000
|
||||||
|
if formatIndx[0] == 0 {
|
||||||
|
if format[formatIndx[0]] != '+' {
|
||||||
|
panic("RenderFloat(): invalid positive sign directive")
|
||||||
|
}
|
||||||
|
positiveStr = "+"
|
||||||
|
formatIndx = formatIndx[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Two directives:
|
||||||
|
// First is thousands separator
|
||||||
|
// Raise an error if not followed by 3-digit
|
||||||
|
// 0123456789
|
||||||
|
// 0.000,000
|
||||||
|
// 000,000.00
|
||||||
|
if len(formatIndx) == 2 {
|
||||||
|
if (formatIndx[1] - formatIndx[0]) != 4 {
|
||||||
|
panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers")
|
||||||
|
}
|
||||||
|
thousandStr = string(format[formatIndx[0]])
|
||||||
|
formatIndx = formatIndx[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// One directive:
|
||||||
|
// Directive is decimal separator
|
||||||
|
// The number of digit-specifier following the separator indicates wanted precision
|
||||||
|
// 0123456789
|
||||||
|
// 0.00
|
||||||
|
// 000,0000
|
||||||
|
if len(formatIndx) == 1 {
|
||||||
|
decimalStr = string(format[formatIndx[0]])
|
||||||
|
precision = len(format) - formatIndx[0] - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate sign part
|
||||||
|
var signStr string
|
||||||
|
if n >= 0.000000001 {
|
||||||
|
signStr = positiveStr
|
||||||
|
} else if n <= -0.000000001 {
|
||||||
|
signStr = negativeStr
|
||||||
|
n = -n
|
||||||
|
} else {
|
||||||
|
signStr = ""
|
||||||
|
n = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
// split number into integer and fractional parts
|
||||||
|
intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision])
|
||||||
|
|
||||||
|
// generate integer part string
|
||||||
|
intStr := strconv.FormatInt(int64(intf), 10)
|
||||||
|
|
||||||
|
// add thousand separator if required
|
||||||
|
if len(thousandStr) > 0 {
|
||||||
|
for i := len(intStr); i > 3; {
|
||||||
|
i -= 3
|
||||||
|
intStr = intStr[:i] + thousandStr + intStr[i:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no fractional part, we can leave now
|
||||||
|
if precision == 0 {
|
||||||
|
return signStr + intStr
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate fractional part
|
||||||
|
fracStr := strconv.Itoa(int(fracf * renderFloatPrecisionMultipliers[precision]))
|
||||||
|
// may need padding
|
||||||
|
if len(fracStr) < precision {
|
||||||
|
fracStr = "000000000000000"[:precision-len(fracStr)] + fracStr
|
||||||
|
}
|
||||||
|
|
||||||
|
return signStr + intStr + decimalStr + fracStr
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatInteger produces a formatted number as string.
|
||||||
|
// See FormatFloat.
|
||||||
|
func FormatInteger(format string, n int) string {
|
||||||
|
return FormatFloat(format, float64(n))
|
||||||
|
}
|
79
vendor/github.com/dustin/go-humanize/number_test.go
generated
vendored
Normal file
79
vendor/github.com/dustin/go-humanize/number_test.go
generated
vendored
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestStruct struct {
|
||||||
|
name string
|
||||||
|
format string
|
||||||
|
num float64
|
||||||
|
formatted string
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatFloat(t *testing.T) {
|
||||||
|
tests := []TestStruct{
|
||||||
|
{"default", "", 12345.6789, "12,345.68"},
|
||||||
|
{"#", "#", 12345.6789, "12345.678900000"},
|
||||||
|
{"#.", "#.", 12345.6789, "12346"},
|
||||||
|
{"#,#", "#,#", 12345.6789, "12345,7"},
|
||||||
|
{"#,##", "#,##", 12345.6789, "12345,68"},
|
||||||
|
{"#,###", "#,###", 12345.6789, "12345,679"},
|
||||||
|
{"#,###.", "#,###.", 12345.6789, "12,346"},
|
||||||
|
{"#,###.##", "#,###.##", 12345.6789, "12,345.68"},
|
||||||
|
{"#,###.###", "#,###.###", 12345.6789, "12,345.679"},
|
||||||
|
{"#,###.####", "#,###.####", 12345.6789, "12,345.6789"},
|
||||||
|
{"#.###,######", "#.###,######", 12345.6789, "12.345,678900"},
|
||||||
|
{"bug46", "#,###.##", 52746220055.92342, "52,746,220,055.92"},
|
||||||
|
{"#\u202f###,##", "#\u202f###,##", 12345.6789, "12 345,68"},
|
||||||
|
|
||||||
|
// special cases
|
||||||
|
{"NaN", "#", math.NaN(), "NaN"},
|
||||||
|
{"+Inf", "#", math.Inf(1), "Infinity"},
|
||||||
|
{"-Inf", "#", math.Inf(-1), "-Infinity"},
|
||||||
|
{"signStr <= -0.000000001", "", -0.000000002, "-0.00"},
|
||||||
|
{"signStr = 0", "", 0, "0.00"},
|
||||||
|
{"Format directive must start with +", "+000", 12345.6789, "+12345.678900000"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
got := FormatFloat(test.format, test.num)
|
||||||
|
if got != test.formatted {
|
||||||
|
t.Errorf("On %v (%v, %v), got %v, wanted %v",
|
||||||
|
test.name, test.format, test.num, got, test.formatted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Test a single integer
|
||||||
|
got := FormatInteger("#", 12345)
|
||||||
|
if got != "12345.000000000" {
|
||||||
|
t.Errorf("On %v (%v, %v), got %v, wanted %v",
|
||||||
|
"integerTest", "#", 12345, got, "12345.000000000")
|
||||||
|
}
|
||||||
|
// Test the things that could panic
|
||||||
|
panictests := []TestStruct{
|
||||||
|
{"RenderFloat(): invalid positive sign directive", "-", 12345.6789, "12,345.68"},
|
||||||
|
{"RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers", "0.01", 12345.6789, "12,345.68"},
|
||||||
|
}
|
||||||
|
for _, test := range panictests {
|
||||||
|
didPanic := false
|
||||||
|
var message interface{}
|
||||||
|
func() {
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if message = recover(); message != nil {
|
||||||
|
didPanic = true
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// call the target function
|
||||||
|
_ = FormatFloat(test.format, test.num)
|
||||||
|
|
||||||
|
}()
|
||||||
|
if didPanic != true {
|
||||||
|
t.Errorf("On %v, should have panic and did not.",
|
||||||
|
test.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
25
vendor/github.com/dustin/go-humanize/ordinals.go
generated
vendored
Normal file
25
vendor/github.com/dustin/go-humanize/ordinals.go
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
// Ordinal gives you the input number in a rank/ordinal format.
|
||||||
|
//
|
||||||
|
// Ordinal(3) -> 3rd
|
||||||
|
func Ordinal(x int) string {
|
||||||
|
suffix := "th"
|
||||||
|
switch x % 10 {
|
||||||
|
case 1:
|
||||||
|
if x%100 != 11 {
|
||||||
|
suffix = "st"
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
if x%100 != 12 {
|
||||||
|
suffix = "nd"
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
if x%100 != 13 {
|
||||||
|
suffix = "rd"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strconv.Itoa(x) + suffix
|
||||||
|
}
|
22
vendor/github.com/dustin/go-humanize/ordinals_test.go
generated
vendored
Normal file
22
vendor/github.com/dustin/go-humanize/ordinals_test.go
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOrdinals(t *testing.T) {
|
||||||
|
testList{
|
||||||
|
{"0", Ordinal(0), "0th"},
|
||||||
|
{"1", Ordinal(1), "1st"},
|
||||||
|
{"2", Ordinal(2), "2nd"},
|
||||||
|
{"3", Ordinal(3), "3rd"},
|
||||||
|
{"4", Ordinal(4), "4th"},
|
||||||
|
{"10", Ordinal(10), "10th"},
|
||||||
|
{"11", Ordinal(11), "11th"},
|
||||||
|
{"12", Ordinal(12), "12th"},
|
||||||
|
{"13", Ordinal(13), "13th"},
|
||||||
|
{"101", Ordinal(101), "101st"},
|
||||||
|
{"102", Ordinal(102), "102nd"},
|
||||||
|
{"103", Ordinal(103), "103rd"},
|
||||||
|
}.validate(t)
|
||||||
|
}
|
113
vendor/github.com/dustin/go-humanize/si.go
generated
vendored
Normal file
113
vendor/github.com/dustin/go-humanize/si.go
generated
vendored
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var siPrefixTable = map[float64]string{
|
||||||
|
-24: "y", // yocto
|
||||||
|
-21: "z", // zepto
|
||||||
|
-18: "a", // atto
|
||||||
|
-15: "f", // femto
|
||||||
|
-12: "p", // pico
|
||||||
|
-9: "n", // nano
|
||||||
|
-6: "µ", // micro
|
||||||
|
-3: "m", // milli
|
||||||
|
0: "",
|
||||||
|
3: "k", // kilo
|
||||||
|
6: "M", // mega
|
||||||
|
9: "G", // giga
|
||||||
|
12: "T", // tera
|
||||||
|
15: "P", // peta
|
||||||
|
18: "E", // exa
|
||||||
|
21: "Z", // zetta
|
||||||
|
24: "Y", // yotta
|
||||||
|
}
|
||||||
|
|
||||||
|
var revSIPrefixTable = revfmap(siPrefixTable)
|
||||||
|
|
||||||
|
// revfmap reverses the map and precomputes the power multiplier
|
||||||
|
func revfmap(in map[float64]string) map[string]float64 {
|
||||||
|
rv := map[string]float64{}
|
||||||
|
for k, v := range in {
|
||||||
|
rv[v] = math.Pow(10, k)
|
||||||
|
}
|
||||||
|
return rv
|
||||||
|
}
|
||||||
|
|
||||||
|
var riParseRegex *regexp.Regexp
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ri := `^([\-0-9.]+)\s?([`
|
||||||
|
for _, v := range siPrefixTable {
|
||||||
|
ri += v
|
||||||
|
}
|
||||||
|
ri += `]?)(.*)`
|
||||||
|
|
||||||
|
riParseRegex = regexp.MustCompile(ri)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComputeSI finds the most appropriate SI prefix for the given number
|
||||||
|
// and returns the prefix along with the value adjusted to be within
|
||||||
|
// that prefix.
|
||||||
|
//
|
||||||
|
// See also: SI, ParseSI.
|
||||||
|
//
|
||||||
|
// e.g. ComputeSI(2.2345e-12) -> (2.2345, "p")
|
||||||
|
func ComputeSI(input float64) (float64, string) {
|
||||||
|
if input == 0 {
|
||||||
|
return 0, ""
|
||||||
|
}
|
||||||
|
mag := math.Abs(input)
|
||||||
|
exponent := math.Floor(logn(mag, 10))
|
||||||
|
exponent = math.Floor(exponent/3) * 3
|
||||||
|
|
||||||
|
value := mag / math.Pow(10, exponent)
|
||||||
|
|
||||||
|
// Handle special case where value is exactly 1000.0
|
||||||
|
// Should return 1 M instead of 1000 k
|
||||||
|
if value == 1000.0 {
|
||||||
|
exponent += 3
|
||||||
|
value = mag / math.Pow(10, exponent)
|
||||||
|
}
|
||||||
|
|
||||||
|
value = math.Copysign(value, input)
|
||||||
|
|
||||||
|
prefix := siPrefixTable[exponent]
|
||||||
|
return value, prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
// SI returns a string with default formatting.
|
||||||
|
//
|
||||||
|
// SI uses Ftoa to format float value, removing trailing zeros.
|
||||||
|
//
|
||||||
|
// See also: ComputeSI, ParseSI.
|
||||||
|
//
|
||||||
|
// e.g. SI(1000000, "B") -> 1 MB
|
||||||
|
// e.g. SI(2.2345e-12, "F") -> 2.2345 pF
|
||||||
|
func SI(input float64, unit string) string {
|
||||||
|
value, prefix := ComputeSI(input)
|
||||||
|
return Ftoa(value) + " " + prefix + unit
|
||||||
|
}
|
||||||
|
|
||||||
|
var errInvalid = errors.New("invalid input")
|
||||||
|
|
||||||
|
// ParseSI parses an SI string back into the number and unit.
|
||||||
|
//
|
||||||
|
// See also: SI, ComputeSI.
|
||||||
|
//
|
||||||
|
// e.g. ParseSI("2.2345 pF") -> (2.2345e-12, "F", nil)
|
||||||
|
func ParseSI(input string) (float64, string, error) {
|
||||||
|
found := riParseRegex.FindStringSubmatch(input)
|
||||||
|
if len(found) != 4 {
|
||||||
|
return 0, "", errInvalid
|
||||||
|
}
|
||||||
|
mag := revSIPrefixTable[found[2]]
|
||||||
|
unit := found[3]
|
||||||
|
|
||||||
|
base, err := strconv.ParseFloat(found[1], 64)
|
||||||
|
return base * mag, unit, err
|
||||||
|
}
|
101
vendor/github.com/dustin/go-humanize/si_test.go
generated
vendored
Normal file
101
vendor/github.com/dustin/go-humanize/si_test.go
generated
vendored
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSI(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
num float64
|
||||||
|
formatted string
|
||||||
|
}{
|
||||||
|
{"e-24", 1e-24, "1 yF"},
|
||||||
|
{"e-21", 1e-21, "1 zF"},
|
||||||
|
{"e-18", 1e-18, "1 aF"},
|
||||||
|
{"e-15", 1e-15, "1 fF"},
|
||||||
|
{"e-12", 1e-12, "1 pF"},
|
||||||
|
{"e-12", 2.2345e-12, "2.2345 pF"},
|
||||||
|
{"e-12", 2.23e-12, "2.23 pF"},
|
||||||
|
{"e-11", 2.23e-11, "22.3 pF"},
|
||||||
|
{"e-10", 2.2e-10, "220 pF"},
|
||||||
|
{"e-9", 2.2e-9, "2.2 nF"},
|
||||||
|
{"e-8", 2.2e-8, "22 nF"},
|
||||||
|
{"e-7", 2.2e-7, "220 nF"},
|
||||||
|
{"e-6", 2.2e-6, "2.2 µF"},
|
||||||
|
{"e-6", 1e-6, "1 µF"},
|
||||||
|
{"e-5", 2.2e-5, "22 µF"},
|
||||||
|
{"e-4", 2.2e-4, "220 µF"},
|
||||||
|
{"e-3", 2.2e-3, "2.2 mF"},
|
||||||
|
{"e-2", 2.2e-2, "22 mF"},
|
||||||
|
{"e-1", 2.2e-1, "220 mF"},
|
||||||
|
{"e+0", 2.2e-0, "2.2 F"},
|
||||||
|
{"e+0", 2.2, "2.2 F"},
|
||||||
|
{"e+1", 2.2e+1, "22 F"},
|
||||||
|
{"0", 0, "0 F"},
|
||||||
|
{"e+1", 22, "22 F"},
|
||||||
|
{"e+2", 2.2e+2, "220 F"},
|
||||||
|
{"e+2", 220, "220 F"},
|
||||||
|
{"e+3", 2.2e+3, "2.2 kF"},
|
||||||
|
{"e+3", 2200, "2.2 kF"},
|
||||||
|
{"e+4", 2.2e+4, "22 kF"},
|
||||||
|
{"e+4", 22000, "22 kF"},
|
||||||
|
{"e+5", 2.2e+5, "220 kF"},
|
||||||
|
{"e+6", 2.2e+6, "2.2 MF"},
|
||||||
|
{"e+6", 1e+6, "1 MF"},
|
||||||
|
{"e+7", 2.2e+7, "22 MF"},
|
||||||
|
{"e+8", 2.2e+8, "220 MF"},
|
||||||
|
{"e+9", 2.2e+9, "2.2 GF"},
|
||||||
|
{"e+10", 2.2e+10, "22 GF"},
|
||||||
|
{"e+11", 2.2e+11, "220 GF"},
|
||||||
|
{"e+12", 2.2e+12, "2.2 TF"},
|
||||||
|
{"e+15", 2.2e+15, "2.2 PF"},
|
||||||
|
{"e+18", 2.2e+18, "2.2 EF"},
|
||||||
|
{"e+21", 2.2e+21, "2.2 ZF"},
|
||||||
|
{"e+24", 2.2e+24, "2.2 YF"},
|
||||||
|
|
||||||
|
// special case
|
||||||
|
{"1F", 1000 * 1000, "1 MF"},
|
||||||
|
{"1F", 1e6, "1 MF"},
|
||||||
|
|
||||||
|
// negative number
|
||||||
|
{"-100 F", -100, "-100 F"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
got := SI(test.num, "F")
|
||||||
|
if got != test.formatted {
|
||||||
|
t.Errorf("On %v (%v), got %v, wanted %v",
|
||||||
|
test.name, test.num, got, test.formatted)
|
||||||
|
}
|
||||||
|
|
||||||
|
gotf, gotu, err := ParseSI(test.formatted)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error parsing %v (%v): %v", test.name, test.formatted, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if math.Abs(1-(gotf/test.num)) > 0.01 {
|
||||||
|
t.Errorf("On %v (%v), got %v, wanted %v (±%v)",
|
||||||
|
test.name, test.formatted, gotf, test.num,
|
||||||
|
math.Abs(1-(gotf/test.num)))
|
||||||
|
}
|
||||||
|
if gotu != "F" {
|
||||||
|
t.Errorf("On %v (%v), expected unit F, got %v",
|
||||||
|
test.name, test.formatted, gotu)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse error
|
||||||
|
gotf, gotu, err := ParseSI("x1.21JW") // 1.21 jigga whats
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected error on x1.21JW, got %v %v", gotf, gotu)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkParseSI(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
ParseSI("2.2346ZB")
|
||||||
|
}
|
||||||
|
}
|
117
vendor/github.com/dustin/go-humanize/times.go
generated
vendored
Normal file
117
vendor/github.com/dustin/go-humanize/times.go
generated
vendored
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Seconds-based time units
|
||||||
|
const (
|
||||||
|
Day = 24 * time.Hour
|
||||||
|
Week = 7 * Day
|
||||||
|
Month = 30 * Day
|
||||||
|
Year = 12 * Month
|
||||||
|
LongTime = 37 * Year
|
||||||
|
)
|
||||||
|
|
||||||
|
// Time formats a time into a relative string.
|
||||||
|
//
|
||||||
|
// Time(someT) -> "3 weeks ago"
|
||||||
|
func Time(then time.Time) string {
|
||||||
|
return RelTime(then, time.Now(), "ago", "from now")
|
||||||
|
}
|
||||||
|
|
||||||
|
// A RelTimeMagnitude struct contains a relative time point at which
|
||||||
|
// the relative format of time will switch to a new format string. A
|
||||||
|
// slice of these in ascending order by their "D" field is passed to
|
||||||
|
// CustomRelTime to format durations.
|
||||||
|
//
|
||||||
|
// The Format field is a string that may contain a "%s" which will be
|
||||||
|
// replaced with the appropriate signed label (e.g. "ago" or "from
|
||||||
|
// now") and a "%d" that will be replaced by the quantity.
|
||||||
|
//
|
||||||
|
// The DivBy field is the amount of time the time difference must be
|
||||||
|
// divided by in order to display correctly.
|
||||||
|
//
|
||||||
|
// e.g. if D is 2*time.Minute and you want to display "%d minutes %s"
|
||||||
|
// DivBy should be time.Minute so whatever the duration is will be
|
||||||
|
// expressed in minutes.
|
||||||
|
type RelTimeMagnitude struct {
|
||||||
|
D time.Duration
|
||||||
|
Format string
|
||||||
|
DivBy time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultMagnitudes = []RelTimeMagnitude{
|
||||||
|
{time.Second, "now", time.Second},
|
||||||
|
{2 * time.Second, "1 second %s", 1},
|
||||||
|
{time.Minute, "%d seconds %s", time.Second},
|
||||||
|
{2 * time.Minute, "1 minute %s", 1},
|
||||||
|
{time.Hour, "%d minutes %s", time.Minute},
|
||||||
|
{2 * time.Hour, "1 hour %s", 1},
|
||||||
|
{Day, "%d hours %s", time.Hour},
|
||||||
|
{2 * Day, "1 day %s", 1},
|
||||||
|
{Week, "%d days %s", Day},
|
||||||
|
{2 * Week, "1 week %s", 1},
|
||||||
|
{Month, "%d weeks %s", Week},
|
||||||
|
{2 * Month, "1 month %s", 1},
|
||||||
|
{Year, "%d months %s", Month},
|
||||||
|
{18 * Month, "1 year %s", 1},
|
||||||
|
{2 * Year, "2 years %s", 1},
|
||||||
|
{LongTime, "%d years %s", Year},
|
||||||
|
{math.MaxInt64, "a long while %s", 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
// RelTime formats a time into a relative string.
|
||||||
|
//
|
||||||
|
// It takes two times and two labels. In addition to the generic time
|
||||||
|
// delta string (e.g. 5 minutes), the labels are used applied so that
|
||||||
|
// the label corresponding to the smaller time is applied.
|
||||||
|
//
|
||||||
|
// RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier"
|
||||||
|
func RelTime(a, b time.Time, albl, blbl string) string {
|
||||||
|
return CustomRelTime(a, b, albl, blbl, defaultMagnitudes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomRelTime formats a time into a relative string.
|
||||||
|
//
|
||||||
|
// It takes two times two labels and a table of relative time formats.
|
||||||
|
// In addition to the generic time delta string (e.g. 5 minutes), the
|
||||||
|
// labels are used applied so that the label corresponding to the
|
||||||
|
// smaller time is applied.
|
||||||
|
func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnitude) string {
|
||||||
|
lbl := albl
|
||||||
|
diff := b.Sub(a)
|
||||||
|
|
||||||
|
if a.After(b) {
|
||||||
|
lbl = blbl
|
||||||
|
diff = a.Sub(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
n := sort.Search(len(magnitudes), func(i int) bool {
|
||||||
|
return magnitudes[i].D > diff
|
||||||
|
})
|
||||||
|
|
||||||
|
if n >= len(magnitudes) {
|
||||||
|
n = len(magnitudes) - 1
|
||||||
|
}
|
||||||
|
mag := magnitudes[n]
|
||||||
|
args := []interface{}{}
|
||||||
|
escaped := false
|
||||||
|
for _, ch := range mag.Format {
|
||||||
|
if escaped {
|
||||||
|
switch ch {
|
||||||
|
case 's':
|
||||||
|
args = append(args, lbl)
|
||||||
|
case 'd':
|
||||||
|
args = append(args, diff/mag.DivBy)
|
||||||
|
}
|
||||||
|
escaped = false
|
||||||
|
} else {
|
||||||
|
escaped = ch == '%'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(mag.Format, args...)
|
||||||
|
}
|
124
vendor/github.com/dustin/go-humanize/times_test.go
generated
vendored
Normal file
124
vendor/github.com/dustin/go-humanize/times_test.go
generated
vendored
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPast(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
testList{
|
||||||
|
{"now", Time(now), "now"},
|
||||||
|
{"1 second ago", Time(now.Add(-1 * time.Second)), "1 second ago"},
|
||||||
|
{"12 seconds ago", Time(now.Add(-12 * time.Second)), "12 seconds ago"},
|
||||||
|
{"30 seconds ago", Time(now.Add(-30 * time.Second)), "30 seconds ago"},
|
||||||
|
{"45 seconds ago", Time(now.Add(-45 * time.Second)), "45 seconds ago"},
|
||||||
|
{"1 minute ago", Time(now.Add(-63 * time.Second)), "1 minute ago"},
|
||||||
|
{"15 minutes ago", Time(now.Add(-15 * time.Minute)), "15 minutes ago"},
|
||||||
|
{"1 hour ago", Time(now.Add(-63 * time.Minute)), "1 hour ago"},
|
||||||
|
{"2 hours ago", Time(now.Add(-2 * time.Hour)), "2 hours ago"},
|
||||||
|
{"21 hours ago", Time(now.Add(-21 * time.Hour)), "21 hours ago"},
|
||||||
|
{"1 day ago", Time(now.Add(-26 * time.Hour)), "1 day ago"},
|
||||||
|
{"2 days ago", Time(now.Add(-49 * time.Hour)), "2 days ago"},
|
||||||
|
{"3 days ago", Time(now.Add(-3 * Day)), "3 days ago"},
|
||||||
|
{"1 week ago (1)", Time(now.Add(-7 * Day)), "1 week ago"},
|
||||||
|
{"1 week ago (2)", Time(now.Add(-12 * Day)), "1 week ago"},
|
||||||
|
{"2 weeks ago", Time(now.Add(-15 * Day)), "2 weeks ago"},
|
||||||
|
{"1 month ago", Time(now.Add(-39 * Day)), "1 month ago"},
|
||||||
|
{"3 months ago", Time(now.Add(-99 * Day)), "3 months ago"},
|
||||||
|
{"1 year ago (1)", Time(now.Add(-365 * Day)), "1 year ago"},
|
||||||
|
{"1 year ago (1)", Time(now.Add(-400 * Day)), "1 year ago"},
|
||||||
|
{"2 years ago (1)", Time(now.Add(-548 * Day)), "2 years ago"},
|
||||||
|
{"2 years ago (2)", Time(now.Add(-725 * Day)), "2 years ago"},
|
||||||
|
{"2 years ago (3)", Time(now.Add(-800 * Day)), "2 years ago"},
|
||||||
|
{"3 years ago", Time(now.Add(-3 * Year)), "3 years ago"},
|
||||||
|
{"long ago", Time(now.Add(-LongTime)), "a long while ago"},
|
||||||
|
}.validate(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReltimeOffbyone(t *testing.T) {
|
||||||
|
testList{
|
||||||
|
{"1w-1", RelTime(time.Unix(0, 0), time.Unix(7*24*60*60, -1), "ago", ""), "6 days ago"},
|
||||||
|
{"1w±0", RelTime(time.Unix(0, 0), time.Unix(7*24*60*60, 0), "ago", ""), "1 week ago"},
|
||||||
|
{"1w+1", RelTime(time.Unix(0, 0), time.Unix(7*24*60*60, 1), "ago", ""), "1 week ago"},
|
||||||
|
{"2w-1", RelTime(time.Unix(0, 0), time.Unix(14*24*60*60, -1), "ago", ""), "1 week ago"},
|
||||||
|
{"2w±0", RelTime(time.Unix(0, 0), time.Unix(14*24*60*60, 0), "ago", ""), "2 weeks ago"},
|
||||||
|
{"2w+1", RelTime(time.Unix(0, 0), time.Unix(14*24*60*60, 1), "ago", ""), "2 weeks ago"},
|
||||||
|
}.validate(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFuture(t *testing.T) {
|
||||||
|
// Add a little time so that these things properly line up in
|
||||||
|
// the future.
|
||||||
|
now := time.Now().Add(time.Millisecond * 250)
|
||||||
|
testList{
|
||||||
|
{"now", Time(now), "now"},
|
||||||
|
{"1 second from now", Time(now.Add(+1 * time.Second)), "1 second from now"},
|
||||||
|
{"12 seconds from now", Time(now.Add(+12 * time.Second)), "12 seconds from now"},
|
||||||
|
{"30 seconds from now", Time(now.Add(+30 * time.Second)), "30 seconds from now"},
|
||||||
|
{"45 seconds from now", Time(now.Add(+45 * time.Second)), "45 seconds from now"},
|
||||||
|
{"15 minutes from now", Time(now.Add(+15 * time.Minute)), "15 minutes from now"},
|
||||||
|
{"2 hours from now", Time(now.Add(+2 * time.Hour)), "2 hours from now"},
|
||||||
|
{"21 hours from now", Time(now.Add(+21 * time.Hour)), "21 hours from now"},
|
||||||
|
{"1 day from now", Time(now.Add(+26 * time.Hour)), "1 day from now"},
|
||||||
|
{"2 days from now", Time(now.Add(+49 * time.Hour)), "2 days from now"},
|
||||||
|
{"3 days from now", Time(now.Add(+3 * Day)), "3 days from now"},
|
||||||
|
{"1 week from now (1)", Time(now.Add(+7 * Day)), "1 week from now"},
|
||||||
|
{"1 week from now (2)", Time(now.Add(+12 * Day)), "1 week from now"},
|
||||||
|
{"2 weeks from now", Time(now.Add(+15 * Day)), "2 weeks from now"},
|
||||||
|
{"1 month from now", Time(now.Add(+30 * Day)), "1 month from now"},
|
||||||
|
{"1 year from now", Time(now.Add(+365 * Day)), "1 year from now"},
|
||||||
|
{"2 years from now", Time(now.Add(+2 * Year)), "2 years from now"},
|
||||||
|
{"a while from now", Time(now.Add(+LongTime)), "a long while from now"},
|
||||||
|
}.validate(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRange(t *testing.T) {
|
||||||
|
start := time.Time{}
|
||||||
|
end := time.Unix(math.MaxInt64, math.MaxInt64)
|
||||||
|
x := RelTime(start, end, "ago", "from now")
|
||||||
|
if x != "a long while from now" {
|
||||||
|
t.Errorf("Expected a long while from now, got %q", x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomRelTime(t *testing.T) {
|
||||||
|
now := time.Now().Add(time.Millisecond * 250)
|
||||||
|
magnitudes := []RelTimeMagnitude{
|
||||||
|
{time.Second, "now", time.Second},
|
||||||
|
{2 * time.Second, "1 second %s", 1},
|
||||||
|
{time.Minute, "%d seconds %s", time.Second},
|
||||||
|
{Day - time.Second, "%d minutes %s", time.Minute},
|
||||||
|
{Day, "%d hours %s", time.Hour},
|
||||||
|
{2 * Day, "1 day %s", 1},
|
||||||
|
{Week, "%d days %s", Day},
|
||||||
|
{2 * Week, "1 week %s", 1},
|
||||||
|
{6 * Month, "%d weeks %s", Week},
|
||||||
|
{Year, "%d months %s", Month},
|
||||||
|
}
|
||||||
|
customRelTime := func(then time.Time) string {
|
||||||
|
return CustomRelTime(then, time.Now(), "ago", "from now", magnitudes)
|
||||||
|
}
|
||||||
|
testList{
|
||||||
|
{"now", customRelTime(now), "now"},
|
||||||
|
{"1 second from now", customRelTime(now.Add(+1 * time.Second)), "1 second from now"},
|
||||||
|
{"12 seconds from now", customRelTime(now.Add(+12 * time.Second)), "12 seconds from now"},
|
||||||
|
{"30 seconds from now", customRelTime(now.Add(+30 * time.Second)), "30 seconds from now"},
|
||||||
|
{"45 seconds from now", customRelTime(now.Add(+45 * time.Second)), "45 seconds from now"},
|
||||||
|
{"15 minutes from now", customRelTime(now.Add(+15 * time.Minute)), "15 minutes from now"},
|
||||||
|
{"2 hours from now", customRelTime(now.Add(+2 * time.Hour)), "120 minutes from now"},
|
||||||
|
{"21 hours from now", customRelTime(now.Add(+21 * time.Hour)), "1260 minutes from now"},
|
||||||
|
{"1 day from now", customRelTime(now.Add(+26 * time.Hour)), "1 day from now"},
|
||||||
|
{"2 days from now", customRelTime(now.Add(+49 * time.Hour)), "2 days from now"},
|
||||||
|
{"3 days from now", customRelTime(now.Add(+3 * Day)), "3 days from now"},
|
||||||
|
{"1 week from now (1)", customRelTime(now.Add(+7 * Day)), "1 week from now"},
|
||||||
|
{"1 week from now (2)", customRelTime(now.Add(+12 * Day)), "1 week from now"},
|
||||||
|
{"2 weeks from now", customRelTime(now.Add(+15 * Day)), "2 weeks from now"},
|
||||||
|
{"1 month from now", customRelTime(now.Add(+30 * Day)), "4 weeks from now"},
|
||||||
|
{"6 months from now", customRelTime(now.Add(+6*Month - time.Second)), "25 weeks from now"},
|
||||||
|
{"1 year from now", customRelTime(now.Add(+365 * Day)), "12 months from now"},
|
||||||
|
{"2 years from now", customRelTime(now.Add(+2 * Year)), "24 months from now"},
|
||||||
|
{"a while from now", customRelTime(now.Add(+LongTime)), "444 months from now"},
|
||||||
|
}.validate(t)
|
||||||
|
}
|
2
vendor/github.com/elazarl/goproxy/.gitignore
generated
vendored
Normal file
2
vendor/github.com/elazarl/goproxy/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
bin
|
||||||
|
*.swp
|
27
vendor/github.com/elazarl/goproxy/LICENSE
generated
vendored
Normal file
27
vendor/github.com/elazarl/goproxy/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2012 Elazar Leibovich. 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 Elazar Leibovich. 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.
|
122
vendor/github.com/elazarl/goproxy/README.md
generated
vendored
Normal file
122
vendor/github.com/elazarl/goproxy/README.md
generated
vendored
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
[](https://godoc.org/github.com/elazarl/goproxy)
|
||||||
|
[](https://gitter.im/elazarl/goproxy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
|
Package goproxy provides a customizable HTTP proxy library for Go (golang),
|
||||||
|
|
||||||
|
It supports regular HTTP proxy, HTTPS through CONNECT, and "hijacking" HTTPS
|
||||||
|
connection using "Man in the Middle" style attack.
|
||||||
|
|
||||||
|
The intent of the proxy, is to be usable with reasonable amount of traffic
|
||||||
|
yet, customizable and programmable.
|
||||||
|
|
||||||
|
The proxy itself is simply a `net/http` handler.
|
||||||
|
|
||||||
|
In order to use goproxy, one should set their browser to use goproxy as an HTTP
|
||||||
|
proxy. Here is how you do that [in Chrome](https://support.google.com/chrome/answer/96815?hl=en)
|
||||||
|
and [in Firefox](http://www.wikihow.com/Enter-Proxy-Settings-in-Firefox).
|
||||||
|
|
||||||
|
For example, the URL you should use as proxy when running `./bin/basic` is
|
||||||
|
`localhost:8080`, as this is the default binding for the basic proxy.
|
||||||
|
|
||||||
|
## Mailing List
|
||||||
|
|
||||||
|
New features would be discussed on the [mailing list](https://groups.google.com/forum/#!forum/goproxy-dev)
|
||||||
|
before their development.
|
||||||
|
|
||||||
|
## Latest Stable Release
|
||||||
|
|
||||||
|
Get the latest goproxy from `gopkg.in/elazarl/goproxy.v1`.
|
||||||
|
|
||||||
|
# Why not Fiddler2?
|
||||||
|
|
||||||
|
Fiddler is an excellent software with similar intent. However, Fiddler is not
|
||||||
|
as customizable as goproxy intend to be. The main difference is, Fiddler is not
|
||||||
|
intended to be used as a real proxy.
|
||||||
|
|
||||||
|
A possible use case that suits goproxy but
|
||||||
|
not Fiddler, is, gathering statistics on page load times for a certain website over a week.
|
||||||
|
With goproxy you could ask all your users to set their proxy to a dedicated machine running a
|
||||||
|
goproxy server. Fiddler is a GUI app not designed to be ran like a server for multiple users.
|
||||||
|
|
||||||
|
# A taste of goproxy
|
||||||
|
|
||||||
|
To get a taste of `goproxy`, a basic HTTP/HTTPS transparent proxy
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/elazarl/goproxy"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
proxy := goproxy.NewProxyHttpServer()
|
||||||
|
proxy.Verbose = true
|
||||||
|
log.Fatal(http.ListenAndServe(":8080", proxy))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This line will add `X-GoProxy: yxorPoG-X` header to all requests sent through the proxy
|
||||||
|
|
||||||
|
```go
|
||||||
|
proxy.OnRequest().DoFunc(
|
||||||
|
func(r *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) {
|
||||||
|
r.Header.Set("X-GoProxy","yxorPoG-X")
|
||||||
|
return r,nil
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
`DoFunc` will process all incoming requests to the proxy. It will add a header to the request
|
||||||
|
and return it. The proxy will send the modified request.
|
||||||
|
|
||||||
|
Note that we returned nil value as the response. Had we returned a response, goproxy would
|
||||||
|
have discarded the request and sent the new response to the client.
|
||||||
|
|
||||||
|
In order to refuse connections to reddit at work time
|
||||||
|
|
||||||
|
```go
|
||||||
|
proxy.OnRequest(goproxy.DstHostIs("www.reddit.com")).DoFunc(
|
||||||
|
func(r *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) {
|
||||||
|
if h,_,_ := time.Now().Clock(); h >= 8 && h <= 17 {
|
||||||
|
return r,goproxy.NewResponse(r,
|
||||||
|
goproxy.ContentTypeText,http.StatusForbidden,
|
||||||
|
"Don't waste your time!")
|
||||||
|
}
|
||||||
|
return r,nil
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
`DstHostIs` returns a `ReqCondition`, that is a function receiving a `Request` and returning a boolean
|
||||||
|
we will only process requests that matches the condition. `DstHostIs("www.reddit.com")` will return
|
||||||
|
a `ReqCondition` accepting only requests directed to "www.reddit.com".
|
||||||
|
|
||||||
|
`DoFunc` will receive a function that will preprocess the request. We can change the request, or
|
||||||
|
return a response. If the time is between 8:00am and 17:00pm, we will neglect the request, and
|
||||||
|
return a precanned text response saying "do not waste your time".
|
||||||
|
|
||||||
|
See additional examples in the examples directory.
|
||||||
|
|
||||||
|
# What's New
|
||||||
|
|
||||||
|
1. Ability to `Hijack` CONNECT requests. See
|
||||||
|
[the eavesdropper example](https://github.com/elazarl/goproxy/blob/master/examples/goproxy-eavesdropper/main.go#L27)
|
||||||
|
2. Transparent proxy support for http/https including MITM certificate generation for TLS. See the [transparent example.](https://github.com/elazarl/goproxy/tree/master/examples/goproxy-transparent)
|
||||||
|
|
||||||
|
# License
|
||||||
|
|
||||||
|
I put the software temporarily under the Go-compatible BSD license,
|
||||||
|
if this prevents someone from using the software, do let me know and I'll consider changing it.
|
||||||
|
|
||||||
|
At any rate, user feedback is very important for me, so I'll be delighted to know if you're using this package.
|
||||||
|
|
||||||
|
# Beta Software
|
||||||
|
|
||||||
|
I've received a positive feedback from a few people who use goproxy in production settings.
|
||||||
|
I believe it is good enough for usage.
|
||||||
|
|
||||||
|
I'll try to keep reasonable backwards compatibility. In case of a major API change,
|
||||||
|
I'll change the import path.
|
57
vendor/github.com/elazarl/goproxy/actions.go
generated
vendored
Normal file
57
vendor/github.com/elazarl/goproxy/actions.go
generated
vendored
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package goproxy
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// ReqHandler will "tamper" with the request coming to the proxy server
|
||||||
|
// If Handle returns req,nil the proxy will send the returned request
|
||||||
|
// to the destination server. If it returns nil,resp the proxy will
|
||||||
|
// skip sending any requests, and will simply return the response `resp`
|
||||||
|
// to the client.
|
||||||
|
type ReqHandler interface {
|
||||||
|
Handle(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A wrapper that would convert a function to a ReqHandler interface type
|
||||||
|
type FuncReqHandler func(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response)
|
||||||
|
|
||||||
|
// FuncReqHandler.Handle(req,ctx) <=> FuncReqHandler(req,ctx)
|
||||||
|
func (f FuncReqHandler) Handle(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response) {
|
||||||
|
return f(req, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// after the proxy have sent the request to the destination server, it will
|
||||||
|
// "filter" the response through the RespHandlers it has.
|
||||||
|
// The proxy server will send to the client the response returned by the RespHandler.
|
||||||
|
// In case of error, resp will be nil, and ctx.RoundTrip.Error will contain the error
|
||||||
|
type RespHandler interface {
|
||||||
|
Handle(resp *http.Response, ctx *ProxyCtx) *http.Response
|
||||||
|
}
|
||||||
|
|
||||||
|
// A wrapper that would convert a function to a RespHandler interface type
|
||||||
|
type FuncRespHandler func(resp *http.Response, ctx *ProxyCtx) *http.Response
|
||||||
|
|
||||||
|
// FuncRespHandler.Handle(req,ctx) <=> FuncRespHandler(req,ctx)
|
||||||
|
func (f FuncRespHandler) Handle(resp *http.Response, ctx *ProxyCtx) *http.Response {
|
||||||
|
return f(resp, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// When a client send a CONNECT request to a host, the request is filtered through
|
||||||
|
// all the HttpsHandlers the proxy has, and if one returns true, the connection is
|
||||||
|
// sniffed using Man in the Middle attack.
|
||||||
|
// That is, the proxy will create a TLS connection with the client, another TLS
|
||||||
|
// connection with the destination the client wished to connect to, and would
|
||||||
|
// send back and forth all messages from the server to the client and vice versa.
|
||||||
|
// The request and responses sent in this Man In the Middle channel are filtered
|
||||||
|
// through the usual flow (request and response filtered through the ReqHandlers
|
||||||
|
// and RespHandlers)
|
||||||
|
type HttpsHandler interface {
|
||||||
|
HandleConnect(req string, ctx *ProxyCtx) (*ConnectAction, string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A wrapper that would convert a function to a HttpsHandler interface type
|
||||||
|
type FuncHttpsHandler func(host string, ctx *ProxyCtx) (*ConnectAction, string)
|
||||||
|
|
||||||
|
// FuncHttpsHandler should implement the RespHandler interface
|
||||||
|
func (f FuncHttpsHandler) HandleConnect(host string, ctx *ProxyCtx) (*ConnectAction, string) {
|
||||||
|
return f(host, ctx)
|
||||||
|
}
|
15
vendor/github.com/elazarl/goproxy/all.bash
generated
vendored
Executable file
15
vendor/github.com/elazarl/goproxy/all.bash
generated
vendored
Executable file
|
@ -0,0 +1,15 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
go test || exit
|
||||||
|
for action in $@; do go $action; done
|
||||||
|
|
||||||
|
mkdir -p bin
|
||||||
|
find regretable examples/* ext/* -maxdepth 0 -type d | while read d; do
|
||||||
|
(cd $d
|
||||||
|
go build -o ../../bin/$(basename $d)
|
||||||
|
find *_test.go -maxdepth 0 2>/dev/null|while read f;do
|
||||||
|
for action in $@; do go $action; done
|
||||||
|
go test
|
||||||
|
break
|
||||||
|
done)
|
||||||
|
done
|
34
vendor/github.com/elazarl/goproxy/ca.pem
generated
vendored
Normal file
34
vendor/github.com/elazarl/goproxy/ca.pem
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIF9DCCA9ygAwIBAgIJAODqYUwoVjJkMA0GCSqGSIb3DQEBCwUAMIGOMQswCQYD
|
||||||
|
VQQGEwJJTDEPMA0GA1UECAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoM
|
||||||
|
B0dvUHJveHkxEDAOBgNVBAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0
|
||||||
|
aHViLmlvMSAwHgYJKoZIhvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTAeFw0xNzA0
|
||||||
|
MDUyMDAwMTBaFw0zNzAzMzEyMDAwMTBaMIGOMQswCQYDVQQGEwJJTDEPMA0GA1UE
|
||||||
|
CAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoMB0dvUHJveHkxEDAOBgNV
|
||||||
|
BAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0aHViLmlvMSAwHgYJKoZI
|
||||||
|
hvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
|
||||||
|
ADCCAgoCggIBAJ4Qy+H6hhoY1s0QRcvIhxrjSHaO/RbaFj3rwqcnpOgFq07gRdI9
|
||||||
|
3c0TFKQJHpgv6feLRhEvX/YllFYu4J35lM9ZcYY4qlKFuStcX8Jm8fqpgtmAMBzP
|
||||||
|
sqtqDi8M9RQGKENzU9IFOnCV7SAeh45scMuI3wz8wrjBcH7zquHkvqUSYZz035t9
|
||||||
|
V6WTrHyTEvT4w+lFOVN2bA/6DAIxrjBiF6DhoJqnha0SZtDfv77XpwGG3EhA/qoh
|
||||||
|
hiYrDruYK7zJdESQL44LwzMPupVigqalfv+YHfQjbhT951IVurW2NJgRyBE62dLr
|
||||||
|
lHYdtT9tCTCrd+KJNMJ+jp9hAjdIu1Br/kifU4F4+4ZLMR9Ueji0GkkPKsYdyMnq
|
||||||
|
j0p0PogyvP1l4qmboPImMYtaoFuYmMYlebgC9LN10bL91K4+jLt0I1YntEzrqgJo
|
||||||
|
WsJztYDw543NzSy5W+/cq4XRYgtq1b0RWwuUiswezmMoeyHZ8BQJe2xMjAOllASD
|
||||||
|
fqa8OK3WABHJpy4zUrnUBiMuPITzD/FuDx4C5IwwlC68gHAZblNqpBZCX0nFCtKj
|
||||||
|
YOcI2So5HbQ2OC8QF+zGVuduHUSok4hSy2BBfZ1pfvziqBeetWJwFvapGB44nIHh
|
||||||
|
WKNKvqOxLNIy7e+TGRiWOomrAWM18VSR9LZbBxpJK7PLSzWqYJYTRCZHAgMBAAGj
|
||||||
|
UzBRMB0GA1UdDgQWBBR4uDD9Y6x7iUoHO+32ioOcw1ICZTAfBgNVHSMEGDAWgBR4
|
||||||
|
uDD9Y6x7iUoHO+32ioOcw1ICZTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB
|
||||||
|
CwUAA4ICAQAaCEupzGGqcdh+L7BzhX7zyd7yzAKUoLxFrxaZY34Xyj3lcx1XoK6F
|
||||||
|
AqsH2JM25GixgadzhNt92JP7vzoWeHZtLfstrPS638Y1zZi6toy4E49viYjFk5J0
|
||||||
|
C6ZcFC04VYWWx6z0HwJuAS08tZ37JuFXpJGfXJOjZCQyxse0Lg0tuKLMeXDCk2Y3
|
||||||
|
Ba0noeuNyHRoWXXPyiUoeApkVCU5gIsyiJSWOjhJ5hpJG06rQNfNYexgKrrraEin
|
||||||
|
o0jmEMtJMx5TtD83hSnLCnFGBBq5lkE7jgXME1KsbIE3lJZzRX1mQwUK8CJDYxye
|
||||||
|
i6M/dzSvy0SsPvz8fTAlprXRtWWtJQmxgWENp3Dv+0Pmux/l+ilk7KA4sMXGhsfr
|
||||||
|
bvTOeWl1/uoFTPYiWR/ww7QEPLq23yDFY04Q7Un0qjIk8ExvaY8lCkXMgc8i7sGY
|
||||||
|
VfvOYb0zm67EfAQl3TW8Ky5fl5CcxpVCD360Bzi6hwjYixa3qEeBggOixFQBFWft
|
||||||
|
8wrkKTHpOQXjn4sDPtet8imm9UYEtzWrFX6T9MFYkBR0/yye0FIh9+YPiTA6WB86
|
||||||
|
NCNwK5Yl6HuvF97CIH5CdgO+5C7KifUtqTOL8pQKbNwy0S3sNYvB+njGvRpR7pKV
|
||||||
|
BUnFpB/Atptqr4CUlTXrc5IPLAqAfmwk5IKcwy3EXUbruf9Dwz69YA==
|
||||||
|
-----END CERTIFICATE-----
|
111
vendor/github.com/elazarl/goproxy/certs.go
generated
vendored
Normal file
111
vendor/github.com/elazarl/goproxy/certs.go
generated
vendored
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
package goproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if goproxyCaErr != nil {
|
||||||
|
panic("Error parsing builtin CA " + goproxyCaErr.Error())
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if GoproxyCa.Leaf, err = x509.ParseCertificate(GoproxyCa.Certificate[0]); err != nil {
|
||||||
|
panic("Error parsing builtin CA " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tlsClientSkipVerify = &tls.Config{InsecureSkipVerify: true}
|
||||||
|
|
||||||
|
var defaultTLSConfig = &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
var CA_CERT = []byte(`-----BEGIN CERTIFICATE-----
|
||||||
|
MIIF9DCCA9ygAwIBAgIJAODqYUwoVjJkMA0GCSqGSIb3DQEBCwUAMIGOMQswCQYD
|
||||||
|
VQQGEwJJTDEPMA0GA1UECAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoM
|
||||||
|
B0dvUHJveHkxEDAOBgNVBAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0
|
||||||
|
aHViLmlvMSAwHgYJKoZIhvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTAeFw0xNzA0
|
||||||
|
MDUyMDAwMTBaFw0zNzAzMzEyMDAwMTBaMIGOMQswCQYDVQQGEwJJTDEPMA0GA1UE
|
||||||
|
CAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoMB0dvUHJveHkxEDAOBgNV
|
||||||
|
BAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0aHViLmlvMSAwHgYJKoZI
|
||||||
|
hvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
|
||||||
|
ADCCAgoCggIBAJ4Qy+H6hhoY1s0QRcvIhxrjSHaO/RbaFj3rwqcnpOgFq07gRdI9
|
||||||
|
3c0TFKQJHpgv6feLRhEvX/YllFYu4J35lM9ZcYY4qlKFuStcX8Jm8fqpgtmAMBzP
|
||||||
|
sqtqDi8M9RQGKENzU9IFOnCV7SAeh45scMuI3wz8wrjBcH7zquHkvqUSYZz035t9
|
||||||
|
V6WTrHyTEvT4w+lFOVN2bA/6DAIxrjBiF6DhoJqnha0SZtDfv77XpwGG3EhA/qoh
|
||||||
|
hiYrDruYK7zJdESQL44LwzMPupVigqalfv+YHfQjbhT951IVurW2NJgRyBE62dLr
|
||||||
|
lHYdtT9tCTCrd+KJNMJ+jp9hAjdIu1Br/kifU4F4+4ZLMR9Ueji0GkkPKsYdyMnq
|
||||||
|
j0p0PogyvP1l4qmboPImMYtaoFuYmMYlebgC9LN10bL91K4+jLt0I1YntEzrqgJo
|
||||||
|
WsJztYDw543NzSy5W+/cq4XRYgtq1b0RWwuUiswezmMoeyHZ8BQJe2xMjAOllASD
|
||||||
|
fqa8OK3WABHJpy4zUrnUBiMuPITzD/FuDx4C5IwwlC68gHAZblNqpBZCX0nFCtKj
|
||||||
|
YOcI2So5HbQ2OC8QF+zGVuduHUSok4hSy2BBfZ1pfvziqBeetWJwFvapGB44nIHh
|
||||||
|
WKNKvqOxLNIy7e+TGRiWOomrAWM18VSR9LZbBxpJK7PLSzWqYJYTRCZHAgMBAAGj
|
||||||
|
UzBRMB0GA1UdDgQWBBR4uDD9Y6x7iUoHO+32ioOcw1ICZTAfBgNVHSMEGDAWgBR4
|
||||||
|
uDD9Y6x7iUoHO+32ioOcw1ICZTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB
|
||||||
|
CwUAA4ICAQAaCEupzGGqcdh+L7BzhX7zyd7yzAKUoLxFrxaZY34Xyj3lcx1XoK6F
|
||||||
|
AqsH2JM25GixgadzhNt92JP7vzoWeHZtLfstrPS638Y1zZi6toy4E49viYjFk5J0
|
||||||
|
C6ZcFC04VYWWx6z0HwJuAS08tZ37JuFXpJGfXJOjZCQyxse0Lg0tuKLMeXDCk2Y3
|
||||||
|
Ba0noeuNyHRoWXXPyiUoeApkVCU5gIsyiJSWOjhJ5hpJG06rQNfNYexgKrrraEin
|
||||||
|
o0jmEMtJMx5TtD83hSnLCnFGBBq5lkE7jgXME1KsbIE3lJZzRX1mQwUK8CJDYxye
|
||||||
|
i6M/dzSvy0SsPvz8fTAlprXRtWWtJQmxgWENp3Dv+0Pmux/l+ilk7KA4sMXGhsfr
|
||||||
|
bvTOeWl1/uoFTPYiWR/ww7QEPLq23yDFY04Q7Un0qjIk8ExvaY8lCkXMgc8i7sGY
|
||||||
|
VfvOYb0zm67EfAQl3TW8Ky5fl5CcxpVCD360Bzi6hwjYixa3qEeBggOixFQBFWft
|
||||||
|
8wrkKTHpOQXjn4sDPtet8imm9UYEtzWrFX6T9MFYkBR0/yye0FIh9+YPiTA6WB86
|
||||||
|
NCNwK5Yl6HuvF97CIH5CdgO+5C7KifUtqTOL8pQKbNwy0S3sNYvB+njGvRpR7pKV
|
||||||
|
BUnFpB/Atptqr4CUlTXrc5IPLAqAfmwk5IKcwy3EXUbruf9Dwz69YA==
|
||||||
|
-----END CERTIFICATE-----`)
|
||||||
|
|
||||||
|
var CA_KEY = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIJKAIBAAKCAgEAnhDL4fqGGhjWzRBFy8iHGuNIdo79FtoWPevCpyek6AWrTuBF
|
||||||
|
0j3dzRMUpAkemC/p94tGES9f9iWUVi7gnfmUz1lxhjiqUoW5K1xfwmbx+qmC2YAw
|
||||||
|
HM+yq2oOLwz1FAYoQ3NT0gU6cJXtIB6Hjmxwy4jfDPzCuMFwfvOq4eS+pRJhnPTf
|
||||||
|
m31XpZOsfJMS9PjD6UU5U3ZsD/oMAjGuMGIXoOGgmqeFrRJm0N+/vtenAYbcSED+
|
||||||
|
qiGGJisOu5grvMl0RJAvjgvDMw+6lWKCpqV+/5gd9CNuFP3nUhW6tbY0mBHIETrZ
|
||||||
|
0uuUdh21P20JMKt34ok0wn6On2ECN0i7UGv+SJ9TgXj7hksxH1R6OLQaSQ8qxh3I
|
||||||
|
yeqPSnQ+iDK8/WXiqZug8iYxi1qgW5iYxiV5uAL0s3XRsv3Urj6Mu3QjVie0TOuq
|
||||||
|
AmhawnO1gPDnjc3NLLlb79yrhdFiC2rVvRFbC5SKzB7OYyh7IdnwFAl7bEyMA6WU
|
||||||
|
BIN+prw4rdYAEcmnLjNSudQGIy48hPMP8W4PHgLkjDCULryAcBluU2qkFkJfScUK
|
||||||
|
0qNg5wjZKjkdtDY4LxAX7MZW524dRKiTiFLLYEF9nWl+/OKoF561YnAW9qkYHjic
|
||||||
|
geFYo0q+o7Es0jLt75MZGJY6iasBYzXxVJH0tlsHGkkrs8tLNapglhNEJkcCAwEA
|
||||||
|
AQKCAgAwSuNvxHHqUUJ3XoxkiXy1u1EtX9x1eeYnvvs2xMb+WJURQTYz2NEGUdkR
|
||||||
|
kPO2/ZSXHAcpQvcnpi2e8y2PNmy/uQ0VPATVt6NuWweqxncR5W5j82U/uDlXY8y3
|
||||||
|
lVbfak4s5XRri0tikHvlP06dNgZ0OPok5qi7d+Zd8yZ3Y8LXfjkykiIrSG1Z2jdt
|
||||||
|
zCWTkNmSUKMGG/1CGFxI41Lb12xuq+C8v4f469Fb6bCUpyCQN9rffHQSGLH6wVb7
|
||||||
|
+68JO+d49zCATpmx5RFViMZwEcouXxRvvc9pPHXLP3ZPBD8nYu9kTD220mEGgWcZ
|
||||||
|
3L9dDlZPcSocbjw295WMvHz2QjhrDrb8gXwdpoRyuyofqgCyNxSnEC5M13SjOxtf
|
||||||
|
pjGzjTqh0kDlKXg2/eTkd9xIHjVhFYiHIEeITM/lHCfWwBCYxViuuF7pSRPzTe8U
|
||||||
|
C440b62qZSPMjVoquaMg+qx0n9fKSo6n1FIKHypv3Kue2G0WhDeK6u0U288vQ1t4
|
||||||
|
Ood3Qa13gZ+9hwDLbM/AoBfVBDlP/tpAwa7AIIU1ZRDNbZr7emFdctx9B6kLINv3
|
||||||
|
4PDOGM2xrjOuACSGMq8Zcu7LBz35PpIZtviJOeKNwUd8/xHjWC6W0itgfJb5I1Nm
|
||||||
|
V6Vj368pGlJx6Se26lvXwyyrc9pSw6jSAwARBeU4YkNWpi4i6QKCAQEA0T7u3P/9
|
||||||
|
jZJSnDN1o2PXymDrJulE61yguhc/QSmLccEPZe7or06/DmEhhKuCbv+1MswKDeag
|
||||||
|
/1JdFPGhL2+4G/f/9BK3BJPdcOZSz7K6Ty8AMMBf8AehKTcSBqwkJWcbEvpHpKJ6
|
||||||
|
eDqn1B6brXTNKMT6fEEXCuZJGPBpNidyLv/xXDcN7kCOo3nGYKfB5OhFpNiL63tw
|
||||||
|
+LntU56WESZwEqr8Pf80uFvsyXQK3a5q5HhIQtxl6tqQuPlNjsDBvCqj0x72mmaJ
|
||||||
|
ZVsVWlv7khUrCwAXz7Y8K7mKKBd2ekF5hSbryfJsxFyvEaWUPhnJpTKV85lAS+tt
|
||||||
|
FQuIp9TvKYlRQwKCAQEAwWJN8jysapdhi67jO0HtYOEl9wwnF4w6XtiOYtllkMmC
|
||||||
|
06/e9h7RsRyWPMdu3qRDPUYFaVDy6+dpUDSQ0+E2Ot6AHtVyvjeUTIL651mFIo/7
|
||||||
|
OSUCEc+HRo3SfPXdPhSQ2thNTxl6y9XcFacuvbthgr70KXbvC4k6IEmdpf/0Kgs9
|
||||||
|
7QTZCG26HDrEZ2q9yMRlRaL2SRD+7Y2xra7gB+cQGFj6yn0Wd/07er49RqMXidQf
|
||||||
|
KR2oYfev2BDtHXoSZFfhFGHlOdLvWRh90D4qZf4vQ+g/EIMgcNSoxjvph1EShmKt
|
||||||
|
sjhTHtoHuu+XmEQvIewk2oCI+JvofBkcnpFrVvUUrQKCAQAaTIufETmgCo0BfuJB
|
||||||
|
N/JOSGIl0NnNryWwXe2gVgVltbsmt6FdL0uKFiEtWJUbOF5g1Q5Kcvs3O/XhBQGa
|
||||||
|
QbNlKIVt+tAv7hm97+Tmn/MUsraWagdk1sCluns0hXxBizT27KgGhDlaVRz05yfv
|
||||||
|
5CdJAYDuDwxDXXBAhy7iFJEgYSDH00+X61tCJrMNQOh4ycy/DEyBu1EWod+3S85W
|
||||||
|
t3sMjZsIe8P3i+4137Th6eMbdha2+JaCrxfTd9oMoCN5b+6JQXIDM/H+4DTN15PF
|
||||||
|
540yY7+aZrAnWrmHknNcqFAKsTqfdi2/fFqwoBwCtiEG91WreU6AfEWIiJuTZIru
|
||||||
|
sIibAoIBAAqIwlo5t+KukF+9jR9DPh0S5rCIdvCvcNaN0WPNF91FPN0vLWQW1bFi
|
||||||
|
L0TsUDvMkuUZlV3hTPpQxsnZszH3iK64RB5p3jBCcs+gKu7DT59MXJEGVRCHT4Um
|
||||||
|
YJryAbVKBYIGWl++sZO8+JotWzx2op8uq7o+glMMjKAJoo7SXIiVyC/LHc95urOi
|
||||||
|
9+PySphPKn0anXPpexmRqGYfqpCDo7rPzgmNutWac80B4/CfHb8iUPg6Z1u+1FNe
|
||||||
|
yKvcZHgW2Wn00znNJcCitufLGyAnMofudND/c5rx2qfBx7zZS7sKUQ/uRYjes6EZ
|
||||||
|
QBbJUA/2/yLv8YYpaAaqj4aLwV8hRpkCggEBAIh3e25tr3avCdGgtCxS7Y1blQ2c
|
||||||
|
ue4erZKmFP1u8wTNHQ03T6sECZbnIfEywRD/esHpclfF3kYAKDRqIP4K905Rb0iH
|
||||||
|
759ZWt2iCbqZznf50XTvptdmjm5KxvouJzScnQ52gIV6L+QrCKIPelLBEIqCJREh
|
||||||
|
pmcjjocD/UCCSuHgbAYNNnO/JdhnSylz1tIg26I+2iLNyeTKIepSNlsBxnkLmqM1
|
||||||
|
cj/azKBaT04IOMLaN8xfSqitJYSraWMVNgGJM5vfcVaivZnNh0lZBv+qu6YkdM88
|
||||||
|
4/avCJ8IutT+FcMM+GbGazOm5ALWqUyhrnbLGc4CQMPfe7Il6NxwcrOxT8w=
|
||||||
|
-----END RSA PRIVATE KEY-----`)
|
||||||
|
|
||||||
|
var GoproxyCa, goproxyCaErr = tls.X509KeyPair(CA_CERT, CA_KEY)
|
8
vendor/github.com/elazarl/goproxy/certs/openssl-gen.sh
generated
vendored
Executable file
8
vendor/github.com/elazarl/goproxy/certs/openssl-gen.sh
generated
vendored
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -ex
|
||||||
|
# generate CA's key
|
||||||
|
openssl genrsa -aes256 -passout pass:1 -out ca.key.pem 4096
|
||||||
|
openssl rsa -passin pass:1 -in ca.key.pem -out ca.key.pem.tmp
|
||||||
|
mv ca.key.pem.tmp ca.key.pem
|
||||||
|
|
||||||
|
openssl req -config openssl.cnf -key ca.key.pem -new -x509 -days 7300 -sha256 -extensions v3_ca -out ca.pem
|
39
vendor/github.com/elazarl/goproxy/certs/openssl.cnf
generated
vendored
Normal file
39
vendor/github.com/elazarl/goproxy/certs/openssl.cnf
generated
vendored
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
[ ca ]
|
||||||
|
default_ca = CA_default
|
||||||
|
[ CA_default ]
|
||||||
|
default_md = sha256
|
||||||
|
[ v3_ca ]
|
||||||
|
subjectKeyIdentifier=hash
|
||||||
|
authorityKeyIdentifier=keyid:always,issuer
|
||||||
|
basicConstraints = critical,CA:true
|
||||||
|
[ req ]
|
||||||
|
distinguished_name = req_distinguished_name
|
||||||
|
[ req_distinguished_name ]
|
||||||
|
countryName = Country Name (2 letter code)
|
||||||
|
countryName_default = IL
|
||||||
|
countryName_min = 2
|
||||||
|
countryName_max = 2
|
||||||
|
|
||||||
|
stateOrProvinceName = State or Province Name (full name)
|
||||||
|
stateOrProvinceName_default = Center
|
||||||
|
|
||||||
|
localityName = Locality Name (eg, city)
|
||||||
|
localityName_default = Lod
|
||||||
|
|
||||||
|
0.organizationName = Organization Name (eg, company)
|
||||||
|
0.organizationName_default = GoProxy
|
||||||
|
|
||||||
|
# we can do this but it is not needed normally :-)
|
||||||
|
#1.organizationName = Second Organization Name (eg, company)
|
||||||
|
#1.organizationName_default = World Wide Web Pty Ltd
|
||||||
|
|
||||||
|
organizationalUnitName = Organizational Unit Name (eg, section)
|
||||||
|
organizationalUnitName_default = GoProxy
|
||||||
|
|
||||||
|
commonName = Common Name (e.g. server FQDN or YOUR name)
|
||||||
|
commonName_default = goproxy.github.io
|
||||||
|
commonName_max = 64
|
||||||
|
|
||||||
|
emailAddress = Email Address
|
||||||
|
emailAddress_default = elazarl@gmail.com
|
||||||
|
emailAddress_max = 64
|
59
vendor/github.com/elazarl/goproxy/chunked.go
generated
vendored
Normal file
59
vendor/github.com/elazarl/goproxy/chunked.go
generated
vendored
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
// Taken from $GOROOT/src/pkg/net/http/chunked
|
||||||
|
// needed to write https responses to client.
|
||||||
|
package goproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// newChunkedWriter returns a new chunkedWriter that translates writes into HTTP
|
||||||
|
// "chunked" format before writing them to w. Closing the returned chunkedWriter
|
||||||
|
// sends the final 0-length chunk that marks the end of the stream.
|
||||||
|
//
|
||||||
|
// newChunkedWriter is not needed by normal applications. The http
|
||||||
|
// package adds chunking automatically if handlers don't set a
|
||||||
|
// Content-Length header. Using newChunkedWriter inside a handler
|
||||||
|
// would result in double chunking or chunking with a Content-Length
|
||||||
|
// length, both of which are wrong.
|
||||||
|
func newChunkedWriter(w io.Writer) io.WriteCloser {
|
||||||
|
return &chunkedWriter{w}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writing to chunkedWriter translates to writing in HTTP chunked Transfer
|
||||||
|
// Encoding wire format to the underlying Wire chunkedWriter.
|
||||||
|
type chunkedWriter struct {
|
||||||
|
Wire io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the contents of data as one chunk to Wire.
|
||||||
|
// NOTE: Note that the corresponding chunk-writing procedure in Conn.Write has
|
||||||
|
// a bug since it does not check for success of io.WriteString
|
||||||
|
func (cw *chunkedWriter) Write(data []byte) (n int, err error) {
|
||||||
|
|
||||||
|
// Don't send 0-length data. It looks like EOF for chunked encoding.
|
||||||
|
if len(data) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
head := strconv.FormatInt(int64(len(data)), 16) + "\r\n"
|
||||||
|
|
||||||
|
if _, err = io.WriteString(cw.Wire, head); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if n, err = cw.Wire.Write(data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n != len(data) {
|
||||||
|
err = io.ErrShortWrite
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = io.WriteString(cw.Wire, "\r\n")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cw *chunkedWriter) Close() error {
|
||||||
|
_, err := io.WriteString(cw.Wire, "0\r\n")
|
||||||
|
return err
|
||||||
|
}
|
68
vendor/github.com/elazarl/goproxy/counterecryptor.go
generated
vendored
Normal file
68
vendor/github.com/elazarl/goproxy/counterecryptor.go
generated
vendored
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
package goproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CounterEncryptorRand struct {
|
||||||
|
cipher cipher.Block
|
||||||
|
counter []byte
|
||||||
|
rand []byte
|
||||||
|
ix int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCounterEncryptorRandFromKey(key interface{}, seed []byte) (r CounterEncryptorRand, err error) {
|
||||||
|
var keyBytes []byte
|
||||||
|
switch key := key.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
keyBytes = x509.MarshalPKCS1PrivateKey(key)
|
||||||
|
default:
|
||||||
|
err = errors.New("only RSA keys supported")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h := sha256.New()
|
||||||
|
if r.cipher, err = aes.NewCipher(h.Sum(keyBytes)[:aes.BlockSize]); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.counter = make([]byte, r.cipher.BlockSize())
|
||||||
|
if seed != nil {
|
||||||
|
copy(r.counter, h.Sum(seed)[:r.cipher.BlockSize()])
|
||||||
|
}
|
||||||
|
r.rand = make([]byte, r.cipher.BlockSize())
|
||||||
|
r.ix = len(r.rand)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CounterEncryptorRand) Seed(b []byte) {
|
||||||
|
if len(b) != len(c.counter) {
|
||||||
|
panic("SetCounter: wrong counter size")
|
||||||
|
}
|
||||||
|
copy(c.counter, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CounterEncryptorRand) refill() {
|
||||||
|
c.cipher.Encrypt(c.rand, c.counter)
|
||||||
|
for i := 0; i < len(c.counter); i++ {
|
||||||
|
if c.counter[i]++; c.counter[i] != 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.ix = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CounterEncryptorRand) Read(b []byte) (n int, err error) {
|
||||||
|
if c.ix == len(c.rand) {
|
||||||
|
c.refill()
|
||||||
|
}
|
||||||
|
if n = len(c.rand) - c.ix; n > len(b) {
|
||||||
|
n = len(b)
|
||||||
|
}
|
||||||
|
copy(b, c.rand[c.ix:c.ix+n])
|
||||||
|
c.ix += n
|
||||||
|
return
|
||||||
|
}
|
99
vendor/github.com/elazarl/goproxy/counterecryptor_test.go
generated
vendored
Normal file
99
vendor/github.com/elazarl/goproxy/counterecryptor_test.go
generated
vendored
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
package goproxy_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rsa"
|
||||||
|
"encoding/binary"
|
||||||
|
"github.com/elazarl/goproxy"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RandSeedReader struct {
|
||||||
|
r rand.Rand
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RandSeedReader) Read(b []byte) (n int, err error) {
|
||||||
|
for i := range b {
|
||||||
|
b[i] = byte(r.r.Int() & 0xFF)
|
||||||
|
}
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCounterEncDifferentConsecutive(t *testing.T) {
|
||||||
|
k, err := rsa.GenerateKey(&RandSeedReader{*rand.New(rand.NewSource(0xFF43109))}, 128)
|
||||||
|
fatalOnErr(err, "rsa.GenerateKey", t)
|
||||||
|
c, err := goproxy.NewCounterEncryptorRandFromKey(k, []byte("the quick brown fox run over the lazy dog"))
|
||||||
|
fatalOnErr(err, "NewCounterEncryptorRandFromKey", t)
|
||||||
|
for i := 0; i < 100*1000; i++ {
|
||||||
|
var a, b int64
|
||||||
|
binary.Read(&c, binary.BigEndian, &a)
|
||||||
|
binary.Read(&c, binary.BigEndian, &b)
|
||||||
|
if a == b {
|
||||||
|
t.Fatal("two consecutive equal int64", a, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCounterEncIdenticalStreams(t *testing.T) {
|
||||||
|
k, err := rsa.GenerateKey(&RandSeedReader{*rand.New(rand.NewSource(0xFF43109))}, 128)
|
||||||
|
fatalOnErr(err, "rsa.GenerateKey", t)
|
||||||
|
c1, err := goproxy.NewCounterEncryptorRandFromKey(k, []byte("the quick brown fox run over the lazy dog"))
|
||||||
|
fatalOnErr(err, "NewCounterEncryptorRandFromKey", t)
|
||||||
|
c2, err := goproxy.NewCounterEncryptorRandFromKey(k, []byte("the quick brown fox run over the lazy dog"))
|
||||||
|
fatalOnErr(err, "NewCounterEncryptorRandFromKey", t)
|
||||||
|
nout := 1000
|
||||||
|
out1, out2 := make([]byte, nout), make([]byte, nout)
|
||||||
|
io.ReadFull(&c1, out1)
|
||||||
|
tmp := out2[:]
|
||||||
|
rand.Seed(0xFF43109)
|
||||||
|
for len(tmp) > 0 {
|
||||||
|
n := 1 + rand.Intn(256)
|
||||||
|
if n > len(tmp) {
|
||||||
|
n = len(tmp)
|
||||||
|
}
|
||||||
|
n, err := c2.Read(tmp[:n])
|
||||||
|
fatalOnErr(err, "CounterEncryptorRand.Read", t)
|
||||||
|
tmp = tmp[n:]
|
||||||
|
}
|
||||||
|
if !bytes.Equal(out1, out2) {
|
||||||
|
t.Error("identical CSPRNG does not produce the same output")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stddev(data []int) float64 {
|
||||||
|
var sum, sum_sqr float64 = 0, 0
|
||||||
|
for _, h := range data {
|
||||||
|
sum += float64(h)
|
||||||
|
sum_sqr += float64(h) * float64(h)
|
||||||
|
}
|
||||||
|
n := float64(len(data))
|
||||||
|
variance := (sum_sqr - ((sum * sum) / n)) / (n - 1)
|
||||||
|
return math.Sqrt(variance)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCounterEncStreamHistogram(t *testing.T) {
|
||||||
|
k, err := rsa.GenerateKey(&RandSeedReader{*rand.New(rand.NewSource(0xFF43109))}, 128)
|
||||||
|
fatalOnErr(err, "rsa.GenerateKey", t)
|
||||||
|
c, err := goproxy.NewCounterEncryptorRandFromKey(k, []byte("the quick brown fox run over the lazy dog"))
|
||||||
|
fatalOnErr(err, "NewCounterEncryptorRandFromKey", t)
|
||||||
|
nout := 100 * 1000
|
||||||
|
out := make([]byte, nout)
|
||||||
|
io.ReadFull(&c, out)
|
||||||
|
refhist := make([]int, 256)
|
||||||
|
for i := 0; i < nout; i++ {
|
||||||
|
refhist[rand.Intn(256)]++
|
||||||
|
}
|
||||||
|
hist := make([]int, 256)
|
||||||
|
for _, b := range out {
|
||||||
|
hist[int(b)]++
|
||||||
|
}
|
||||||
|
refstddev, stddev := stddev(refhist), stddev(hist)
|
||||||
|
// due to lack of time, I guestimate
|
||||||
|
t.Logf("ref:%v - act:%v = %v", refstddev, stddev, math.Abs(refstddev-stddev))
|
||||||
|
if math.Abs(refstddev-stddev) >= 1 {
|
||||||
|
t.Errorf("stddev of ref histogram different than regular PRNG: %v %v", refstddev, stddev)
|
||||||
|
}
|
||||||
|
}
|
87
vendor/github.com/elazarl/goproxy/ctx.go
generated
vendored
Normal file
87
vendor/github.com/elazarl/goproxy/ctx.go
generated
vendored
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
package goproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProxyCtx is the Proxy context, contains useful information about every request. It is passed to
|
||||||
|
// every user function. Also used as a logger.
|
||||||
|
type ProxyCtx struct {
|
||||||
|
// Will contain the client request from the proxy
|
||||||
|
Req *http.Request
|
||||||
|
// Will contain the remote server's response (if available. nil if the request wasn't send yet)
|
||||||
|
Resp *http.Response
|
||||||
|
RoundTripper RoundTripper
|
||||||
|
// will contain the recent error that occurred while trying to send receive or parse traffic
|
||||||
|
Error error
|
||||||
|
// A handle for the user to keep data in the context, from the call of ReqHandler to the
|
||||||
|
// call of RespHandler
|
||||||
|
UserData interface{}
|
||||||
|
// Will connect a request to a response
|
||||||
|
Session int64
|
||||||
|
proxy *ProxyHttpServer
|
||||||
|
}
|
||||||
|
|
||||||
|
type RoundTripper interface {
|
||||||
|
RoundTrip(req *http.Request, ctx *ProxyCtx) (*http.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RoundTripperFunc func(req *http.Request, ctx *ProxyCtx) (*http.Response, error)
|
||||||
|
|
||||||
|
func (f RoundTripperFunc) RoundTrip(req *http.Request, ctx *ProxyCtx) (*http.Response, error) {
|
||||||
|
return f(req, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *ProxyCtx) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
if ctx.RoundTripper != nil {
|
||||||
|
return ctx.RoundTripper.RoundTrip(req, ctx)
|
||||||
|
}
|
||||||
|
return ctx.proxy.Tr.RoundTrip(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *ProxyCtx) printf(msg string, argv ...interface{}) {
|
||||||
|
ctx.proxy.Logger.Printf("[%03d] "+msg+"\n", append([]interface{}{ctx.Session & 0xFF}, argv...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logf prints a message to the proxy's log. Should be used in a ProxyHttpServer's filter
|
||||||
|
// This message will be printed only if the Verbose field of the ProxyHttpServer is set to true
|
||||||
|
//
|
||||||
|
// proxy.OnRequest().DoFunc(func(r *http.Request,ctx *goproxy.ProxyCtx) (*http.Request, *http.Response){
|
||||||
|
// nr := atomic.AddInt32(&counter,1)
|
||||||
|
// ctx.Printf("So far %d requests",nr)
|
||||||
|
// return r, nil
|
||||||
|
// })
|
||||||
|
func (ctx *ProxyCtx) Logf(msg string, argv ...interface{}) {
|
||||||
|
if ctx.proxy.Verbose {
|
||||||
|
ctx.printf("INFO: "+msg, argv...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnf prints a message to the proxy's log. Should be used in a ProxyHttpServer's filter
|
||||||
|
// This message will always be printed.
|
||||||
|
//
|
||||||
|
// proxy.OnRequest().DoFunc(func(r *http.Request,ctx *goproxy.ProxyCtx) (*http.Request, *http.Response){
|
||||||
|
// f,err := os.OpenFile(cachedContent)
|
||||||
|
// if err != nil {
|
||||||
|
// ctx.Warnf("error open file %v: %v",cachedContent,err)
|
||||||
|
// return r, nil
|
||||||
|
// }
|
||||||
|
// return r, nil
|
||||||
|
// })
|
||||||
|
func (ctx *ProxyCtx) Warnf(msg string, argv ...interface{}) {
|
||||||
|
ctx.printf("WARN: "+msg, argv...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var charsetFinder = regexp.MustCompile("charset=([^ ;]*)")
|
||||||
|
|
||||||
|
// Will try to infer the character set of the request from the headers.
|
||||||
|
// Returns the empty string if we don't know which character set it used.
|
||||||
|
// Currently it will look for charset=<charset> in the Content-Type header of the request.
|
||||||
|
func (ctx *ProxyCtx) Charset() string {
|
||||||
|
charsets := charsetFinder.FindStringSubmatch(ctx.Resp.Header.Get("Content-Type"))
|
||||||
|
if charsets == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return charsets[1]
|
||||||
|
}
|
325
vendor/github.com/elazarl/goproxy/dispatcher.go
generated
vendored
Normal file
325
vendor/github.com/elazarl/goproxy/dispatcher.go
generated
vendored
Normal file
|
@ -0,0 +1,325 @@
|
||||||
|
package goproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReqCondition.HandleReq will decide whether or not to use the ReqHandler on an HTTP request
|
||||||
|
// before sending it to the remote server
|
||||||
|
type ReqCondition interface {
|
||||||
|
RespCondition
|
||||||
|
HandleReq(req *http.Request, ctx *ProxyCtx) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// RespCondition.HandleReq will decide whether or not to use the RespHandler on an HTTP response
|
||||||
|
// before sending it to the proxy client. Note that resp might be nil, in case there was an
|
||||||
|
// error sending the request.
|
||||||
|
type RespCondition interface {
|
||||||
|
HandleResp(resp *http.Response, ctx *ProxyCtx) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReqConditionFunc.HandleReq(req,ctx) <=> ReqConditionFunc(req,ctx)
|
||||||
|
type ReqConditionFunc func(req *http.Request, ctx *ProxyCtx) bool
|
||||||
|
|
||||||
|
// RespConditionFunc.HandleResp(resp,ctx) <=> RespConditionFunc(resp,ctx)
|
||||||
|
type RespConditionFunc func(resp *http.Response, ctx *ProxyCtx) bool
|
||||||
|
|
||||||
|
func (c ReqConditionFunc) HandleReq(req *http.Request, ctx *ProxyCtx) bool {
|
||||||
|
return c(req, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReqConditionFunc cannot test responses. It only satisfies RespCondition interface so that
|
||||||
|
// to be usable as RespCondition.
|
||||||
|
func (c ReqConditionFunc) HandleResp(resp *http.Response, ctx *ProxyCtx) bool {
|
||||||
|
return c(ctx.Req, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c RespConditionFunc) HandleResp(resp *http.Response, ctx *ProxyCtx) bool {
|
||||||
|
return c(resp, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UrlHasPrefix returns a ReqCondition checking wether the destination URL the proxy client has requested
|
||||||
|
// has the given prefix, with or without the host.
|
||||||
|
// For example UrlHasPrefix("host/x") will match requests of the form 'GET host/x', and will match
|
||||||
|
// requests to url 'http://host/x'
|
||||||
|
func UrlHasPrefix(prefix string) ReqConditionFunc {
|
||||||
|
return func(req *http.Request, ctx *ProxyCtx) bool {
|
||||||
|
return strings.HasPrefix(req.URL.Path, prefix) ||
|
||||||
|
strings.HasPrefix(req.URL.Host+req.URL.Path, prefix) ||
|
||||||
|
strings.HasPrefix(req.URL.Scheme+req.URL.Host+req.URL.Path, prefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UrlIs returns a ReqCondition, testing whether or not the request URL is one of the given strings
|
||||||
|
// with or without the host prefix.
|
||||||
|
// UrlIs("google.com/","foo") will match requests 'GET /' to 'google.com', requests `'GET google.com/' to
|
||||||
|
// any host, and requests of the form 'GET foo'.
|
||||||
|
func UrlIs(urls ...string) ReqConditionFunc {
|
||||||
|
urlSet := make(map[string]bool)
|
||||||
|
for _, u := range urls {
|
||||||
|
urlSet[u] = true
|
||||||
|
}
|
||||||
|
return func(req *http.Request, ctx *ProxyCtx) bool {
|
||||||
|
_, pathOk := urlSet[req.URL.Path]
|
||||||
|
_, hostAndOk := urlSet[req.URL.Host+req.URL.Path]
|
||||||
|
return pathOk || hostAndOk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReqHostMatches returns a ReqCondition, testing whether the host to which the request was directed to matches
|
||||||
|
// any of the given regular expressions.
|
||||||
|
func ReqHostMatches(regexps ...*regexp.Regexp) ReqConditionFunc {
|
||||||
|
return func(req *http.Request, ctx *ProxyCtx) bool {
|
||||||
|
for _, re := range regexps {
|
||||||
|
if re.MatchString(req.Host) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReqHostIs returns a ReqCondition, testing whether the host to which the request is directed to equal
|
||||||
|
// to one of the given strings
|
||||||
|
func ReqHostIs(hosts ...string) ReqConditionFunc {
|
||||||
|
hostSet := make(map[string]bool)
|
||||||
|
for _, h := range hosts {
|
||||||
|
hostSet[h] = true
|
||||||
|
}
|
||||||
|
return func(req *http.Request, ctx *ProxyCtx) bool {
|
||||||
|
_, ok := hostSet[req.URL.Host]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var localHostIpv4 = regexp.MustCompile(`127\.0\.0\.\d+`)
|
||||||
|
|
||||||
|
// IsLocalHost checks whether the destination host is explicitly local host
|
||||||
|
// (buggy, there can be IPv6 addresses it doesn't catch)
|
||||||
|
var IsLocalHost ReqConditionFunc = func(req *http.Request, ctx *ProxyCtx) bool {
|
||||||
|
return req.URL.Host == "::1" ||
|
||||||
|
req.URL.Host == "0:0:0:0:0:0:0:1" ||
|
||||||
|
localHostIpv4.MatchString(req.URL.Host) ||
|
||||||
|
req.URL.Host == "localhost"
|
||||||
|
}
|
||||||
|
|
||||||
|
// UrlMatches returns a ReqCondition testing whether the destination URL
|
||||||
|
// of the request matches the given regexp, with or without prefix
|
||||||
|
func UrlMatches(re *regexp.Regexp) ReqConditionFunc {
|
||||||
|
return func(req *http.Request, ctx *ProxyCtx) bool {
|
||||||
|
return re.MatchString(req.URL.Path) ||
|
||||||
|
re.MatchString(req.URL.Host+req.URL.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DstHostIs returns a ReqCondition testing wether the host in the request url is the given string
|
||||||
|
func DstHostIs(host string) ReqConditionFunc {
|
||||||
|
return func(req *http.Request, ctx *ProxyCtx) bool {
|
||||||
|
return req.URL.Host == host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SrcIpIs returns a ReqCondition testing whether the source IP of the request is one of the given strings
|
||||||
|
func SrcIpIs(ips ...string) ReqCondition {
|
||||||
|
return ReqConditionFunc(func(req *http.Request, ctx *ProxyCtx) bool {
|
||||||
|
for _, ip := range ips {
|
||||||
|
if strings.HasPrefix(req.RemoteAddr, ip+":") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not returns a ReqCondition negating the given ReqCondition
|
||||||
|
func Not(r ReqCondition) ReqConditionFunc {
|
||||||
|
return func(req *http.Request, ctx *ProxyCtx) bool {
|
||||||
|
return !r.HandleReq(req, ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentTypeIs returns a RespCondition testing whether the HTTP response has Content-Type header equal
|
||||||
|
// to one of the given strings.
|
||||||
|
func ContentTypeIs(typ string, types ...string) RespCondition {
|
||||||
|
types = append(types, typ)
|
||||||
|
return RespConditionFunc(func(resp *http.Response, ctx *ProxyCtx) bool {
|
||||||
|
if resp == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
contentType := resp.Header.Get("Content-Type")
|
||||||
|
for _, typ := range types {
|
||||||
|
if contentType == typ || strings.HasPrefix(contentType, typ+";") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProxyHttpServer.OnRequest Will return a temporary ReqProxyConds struct, aggregating the given condtions.
|
||||||
|
// You will use the ReqProxyConds struct to register a ReqHandler, that would filter
|
||||||
|
// the request, only if all the given ReqCondition matched.
|
||||||
|
// Typical usage:
|
||||||
|
// proxy.OnRequest(UrlIs("example.com/foo"),UrlMatches(regexp.MustParse(`.*\.exampl.\com\./.*`)).Do(...)
|
||||||
|
func (proxy *ProxyHttpServer) OnRequest(conds ...ReqCondition) *ReqProxyConds {
|
||||||
|
return &ReqProxyConds{proxy, conds}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReqProxyConds aggregate ReqConditions for a ProxyHttpServer. Upon calling Do, it will register a ReqHandler that would
|
||||||
|
// handle the request if all conditions on the HTTP request are met.
|
||||||
|
type ReqProxyConds struct {
|
||||||
|
proxy *ProxyHttpServer
|
||||||
|
reqConds []ReqCondition
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoFunc is equivalent to proxy.OnRequest().Do(FuncReqHandler(f))
|
||||||
|
func (pcond *ReqProxyConds) DoFunc(f func(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response)) {
|
||||||
|
pcond.Do(FuncReqHandler(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReqProxyConds.Do will register the ReqHandler on the proxy,
|
||||||
|
// the ReqHandler will handle the HTTP request if all the conditions
|
||||||
|
// aggregated in the ReqProxyConds are met. Typical usage:
|
||||||
|
// proxy.OnRequest().Do(handler) // will call handler.Handle(req,ctx) on every request to the proxy
|
||||||
|
// proxy.OnRequest(cond1,cond2).Do(handler)
|
||||||
|
// // given request to the proxy, will test if cond1.HandleReq(req,ctx) && cond2.HandleReq(req,ctx) are true
|
||||||
|
// // if they are, will call handler.Handle(req,ctx)
|
||||||
|
func (pcond *ReqProxyConds) Do(h ReqHandler) {
|
||||||
|
pcond.proxy.reqHandlers = append(pcond.proxy.reqHandlers,
|
||||||
|
FuncReqHandler(func(r *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response) {
|
||||||
|
for _, cond := range pcond.reqConds {
|
||||||
|
if !cond.HandleReq(r, ctx) {
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return h.Handle(r, ctx)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleConnect is used when proxy receives an HTTP CONNECT request,
|
||||||
|
// it'll then use the HttpsHandler to determine what should it
|
||||||
|
// do with this request. The handler returns a ConnectAction struct, the Action field in the ConnectAction
|
||||||
|
// struct returned will determine what to do with this request. ConnectAccept will simply accept the request
|
||||||
|
// forwarding all bytes from the client to the remote host, ConnectReject will close the connection with the
|
||||||
|
// client, and ConnectMitm, will assume the underlying connection is an HTTPS connection, and will use Man
|
||||||
|
// in the Middle attack to eavesdrop the connection. All regular handler will be active on this eavesdropped
|
||||||
|
// connection.
|
||||||
|
// The ConnectAction struct contains possible tlsConfig that will be used for eavesdropping. If nil, the proxy
|
||||||
|
// will use the default tls configuration.
|
||||||
|
// proxy.OnRequest().HandleConnect(goproxy.AlwaysReject) // rejects all CONNECT requests
|
||||||
|
func (pcond *ReqProxyConds) HandleConnect(h HttpsHandler) {
|
||||||
|
pcond.proxy.httpsHandlers = append(pcond.proxy.httpsHandlers,
|
||||||
|
FuncHttpsHandler(func(host string, ctx *ProxyCtx) (*ConnectAction, string) {
|
||||||
|
for _, cond := range pcond.reqConds {
|
||||||
|
if !cond.HandleReq(ctx.Req, ctx) {
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return h.HandleConnect(host, ctx)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleConnectFunc is equivalent to HandleConnect,
|
||||||
|
// for example, accepting CONNECT request if they contain a password in header
|
||||||
|
// io.WriteString(h,password)
|
||||||
|
// passHash := h.Sum(nil)
|
||||||
|
// proxy.OnRequest().HandleConnectFunc(func(host string, ctx *ProxyCtx) (*ConnectAction, string) {
|
||||||
|
// c := sha1.New()
|
||||||
|
// io.WriteString(c,ctx.Req.Header.Get("X-GoProxy-Auth"))
|
||||||
|
// if c.Sum(nil) == passHash {
|
||||||
|
// return OkConnect, host
|
||||||
|
// }
|
||||||
|
// return RejectConnect, host
|
||||||
|
// })
|
||||||
|
func (pcond *ReqProxyConds) HandleConnectFunc(f func(host string, ctx *ProxyCtx) (*ConnectAction, string)) {
|
||||||
|
pcond.HandleConnect(FuncHttpsHandler(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pcond *ReqProxyConds) HijackConnect(f func(req *http.Request, client net.Conn, ctx *ProxyCtx)) {
|
||||||
|
pcond.proxy.httpsHandlers = append(pcond.proxy.httpsHandlers,
|
||||||
|
FuncHttpsHandler(func(host string, ctx *ProxyCtx) (*ConnectAction, string) {
|
||||||
|
for _, cond := range pcond.reqConds {
|
||||||
|
if !cond.HandleReq(ctx.Req, ctx) {
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &ConnectAction{Action: ConnectHijack, Hijack: f}, host
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProxyConds is used to aggregate RespConditions for a ProxyHttpServer.
|
||||||
|
// Upon calling ProxyConds.Do, it will register a RespHandler that would
|
||||||
|
// handle the HTTP response from remote server if all conditions on the HTTP response are met.
|
||||||
|
type ProxyConds struct {
|
||||||
|
proxy *ProxyHttpServer
|
||||||
|
reqConds []ReqCondition
|
||||||
|
respCond []RespCondition
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProxyConds.DoFunc is equivalent to proxy.OnResponse().Do(FuncRespHandler(f))
|
||||||
|
func (pcond *ProxyConds) DoFunc(f func(resp *http.Response, ctx *ProxyCtx) *http.Response) {
|
||||||
|
pcond.Do(FuncRespHandler(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProxyConds.Do will register the RespHandler on the proxy, h.Handle(resp,ctx) will be called on every
|
||||||
|
// request that matches the conditions aggregated in pcond.
|
||||||
|
func (pcond *ProxyConds) Do(h RespHandler) {
|
||||||
|
pcond.proxy.respHandlers = append(pcond.proxy.respHandlers,
|
||||||
|
FuncRespHandler(func(resp *http.Response, ctx *ProxyCtx) *http.Response {
|
||||||
|
for _, cond := range pcond.reqConds {
|
||||||
|
if !cond.HandleReq(ctx.Req, ctx) {
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, cond := range pcond.respCond {
|
||||||
|
if !cond.HandleResp(resp, ctx) {
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return h.Handle(resp, ctx)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnResponse is used when adding a response-filter to the HTTP proxy, usual pattern is
|
||||||
|
// proxy.OnResponse(cond1,cond2).Do(handler) // handler.Handle(resp,ctx) will be used
|
||||||
|
// // if cond1.HandleResp(resp) && cond2.HandleResp(resp)
|
||||||
|
func (proxy *ProxyHttpServer) OnResponse(conds ...RespCondition) *ProxyConds {
|
||||||
|
return &ProxyConds{proxy, make([]ReqCondition, 0), conds}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AlwaysMitm is a HttpsHandler that always eavesdrop https connections, for example to
|
||||||
|
// eavesdrop all https connections to www.google.com, we can use
|
||||||
|
// proxy.OnRequest(goproxy.ReqHostIs("www.google.com")).HandleConnect(goproxy.AlwaysMitm)
|
||||||
|
var AlwaysMitm FuncHttpsHandler = func(host string, ctx *ProxyCtx) (*ConnectAction, string) {
|
||||||
|
return MitmConnect, host
|
||||||
|
}
|
||||||
|
|
||||||
|
// AlwaysReject is a HttpsHandler that drops any CONNECT request, for example, this code will disallow
|
||||||
|
// connections to hosts on any other port than 443
|
||||||
|
// proxy.OnRequest(goproxy.Not(goproxy.ReqHostMatches(regexp.MustCompile(":443$"))).
|
||||||
|
// HandleConnect(goproxy.AlwaysReject)
|
||||||
|
var AlwaysReject FuncHttpsHandler = func(host string, ctx *ProxyCtx) (*ConnectAction, string) {
|
||||||
|
return RejectConnect, host
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleBytes will return a RespHandler that read the entire body of the request
|
||||||
|
// to a byte array in memory, would run the user supplied f function on the byte arra,
|
||||||
|
// and will replace the body of the original response with the resulting byte array.
|
||||||
|
func HandleBytes(f func(b []byte, ctx *ProxyCtx) []byte) RespHandler {
|
||||||
|
return FuncRespHandler(func(resp *http.Response, ctx *ProxyCtx) *http.Response {
|
||||||
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Warnf("Cannot read response %s", err)
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
resp.Body = ioutil.NopCloser(bytes.NewBuffer(f(b, ctx)))
|
||||||
|
return resp
|
||||||
|
})
|
||||||
|
}
|
100
vendor/github.com/elazarl/goproxy/doc.go
generated
vendored
Normal file
100
vendor/github.com/elazarl/goproxy/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
/*
|
||||||
|
Package goproxy provides a customizable HTTP proxy,
|
||||||
|
supporting hijacking HTTPS connection.
|
||||||
|
|
||||||
|
The intent of the proxy, is to be usable with reasonable amount of traffic
|
||||||
|
yet, customizable and programable.
|
||||||
|
|
||||||
|
The proxy itself is simply an `net/http` handler.
|
||||||
|
|
||||||
|
Typical usage is
|
||||||
|
|
||||||
|
proxy := goproxy.NewProxyHttpServer()
|
||||||
|
proxy.OnRequest(..conditions..).Do(..requesthandler..)
|
||||||
|
proxy.OnRequest(..conditions..).DoFunc(..requesthandlerFunction..)
|
||||||
|
proxy.OnResponse(..conditions..).Do(..responesHandler..)
|
||||||
|
proxy.OnResponse(..conditions..).DoFunc(..responesHandlerFunction..)
|
||||||
|
http.ListenAndServe(":8080", proxy)
|
||||||
|
|
||||||
|
Adding a header to each request
|
||||||
|
|
||||||
|
proxy.OnRequest().DoFunc(func(r *http.Request,ctx *goproxy.ProxyCtx) (*http.Request, *http.Response){
|
||||||
|
r.Header.Set("X-GoProxy","1")
|
||||||
|
return r, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
Note that the function is called before the proxy sends the request to the server
|
||||||
|
|
||||||
|
For printing the content type of all incoming responses
|
||||||
|
|
||||||
|
proxy.OnResponse().DoFunc(func(r *http.Response, ctx *goproxy.ProxyCtx)*http.Response{
|
||||||
|
println(ctx.Req.Host,"->",r.Header.Get("Content-Type"))
|
||||||
|
return r
|
||||||
|
})
|
||||||
|
|
||||||
|
note that we used the ProxyCtx context variable here. It contains the request
|
||||||
|
and the response (Req and Resp, Resp is nil if unavailable) of this specific client
|
||||||
|
interaction with the proxy.
|
||||||
|
|
||||||
|
To print the content type of all responses from a certain url, we'll add a
|
||||||
|
ReqCondition to the OnResponse function:
|
||||||
|
|
||||||
|
proxy.OnResponse(goproxy.UrlIs("golang.org/pkg")).DoFunc(func(r *http.Response, ctx *goproxy.ProxyCtx)*http.Response{
|
||||||
|
println(ctx.Req.Host,"->",r.Header.Get("Content-Type"))
|
||||||
|
return r
|
||||||
|
})
|
||||||
|
|
||||||
|
We can write the condition ourselves, conditions can be set on request and on response
|
||||||
|
|
||||||
|
var random = ReqConditionFunc(func(r *http.Request) bool {
|
||||||
|
return rand.Intn(1) == 0
|
||||||
|
})
|
||||||
|
var hasGoProxyHeader = RespConditionFunc(func(resp *http.Response,req *http.Request)bool {
|
||||||
|
return resp.Header.Get("X-GoProxy") != ""
|
||||||
|
})
|
||||||
|
|
||||||
|
Caution! If you give a RespCondition to the OnRequest function, you'll get a run time panic! It doesn't
|
||||||
|
make sense to read the response, if you still haven't got it!
|
||||||
|
|
||||||
|
Finally, we have convenience function to throw a quick response
|
||||||
|
|
||||||
|
proxy.OnResponse(hasGoProxyHeader).DoFunc(func(r*http.Response,ctx *goproxy.ProxyCtx)*http.Response {
|
||||||
|
r.Body.Close()
|
||||||
|
return goproxy.ForbiddenTextResponse(ctx.Req,"Can't see response with X-GoProxy header!")
|
||||||
|
})
|
||||||
|
|
||||||
|
we close the body of the original repsonse, and return a new 403 response with a short message.
|
||||||
|
|
||||||
|
Example use cases:
|
||||||
|
|
||||||
|
1. https://github.com/elazarl/goproxy/tree/master/examples/goproxy-avgsize
|
||||||
|
|
||||||
|
To measure the average size of an Html served in your site. One can ask
|
||||||
|
all the QA team to access the website by a proxy, and the proxy will
|
||||||
|
measure the average size of all text/html responses from your host.
|
||||||
|
|
||||||
|
2. [not yet implemented]
|
||||||
|
|
||||||
|
All requests to your web servers should be directed through the proxy,
|
||||||
|
when the proxy will detect html pieces sent as a response to AJAX
|
||||||
|
request, it'll send a warning email.
|
||||||
|
|
||||||
|
3. https://github.com/elazarl/goproxy/blob/master/examples/goproxy-httpdump/
|
||||||
|
|
||||||
|
Generate a real traffic to your website by real users using through
|
||||||
|
proxy. Record the traffic, and try it again for more real load testing.
|
||||||
|
|
||||||
|
4. https://github.com/elazarl/goproxy/tree/master/examples/goproxy-no-reddit-at-worktime
|
||||||
|
|
||||||
|
Will allow browsing to reddit.com between 8:00am and 17:00pm
|
||||||
|
|
||||||
|
5. https://github.com/elazarl/goproxy/tree/master/examples/goproxy-jquery-version
|
||||||
|
|
||||||
|
Will warn if multiple versions of jquery are used in the same domain.
|
||||||
|
|
||||||
|
6. https://github.com/elazarl/goproxy/blob/master/examples/goproxy-upside-down-ternet/
|
||||||
|
|
||||||
|
Modifies image files in an HTTP response via goproxy's image extension found in ext/.
|
||||||
|
|
||||||
|
*/
|
||||||
|
package goproxy
|
29
vendor/github.com/elazarl/goproxy/examples/goproxy-basic/README.md
generated
vendored
Normal file
29
vendor/github.com/elazarl/goproxy/examples/goproxy-basic/README.md
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# Simple HTTP Proxy
|
||||||
|
|
||||||
|
`goproxy-basic` starts an HTTP proxy on :8080. It only handles explicit CONNECT
|
||||||
|
requests.
|
||||||
|
|
||||||
|
Start it in one shell:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
goproxy-basic -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Fetch goproxy homepage in another:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
http_proxy=http://127.0.0.1:8080 wget -O - \
|
||||||
|
http://ripper234.com/p/introducing-goproxy-light-http-proxy/
|
||||||
|
```
|
||||||
|
|
||||||
|
The homepage HTML content should be displayed in the console. The proxy should
|
||||||
|
have logged the request being processed:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
2015/04/09 18:19:17 [001] INFO: Got request /p/introducing-goproxy-light-http-proxy/ ripper234.com GET http://ripper234.com/p/introducing-goproxy-light-http-proxy/
|
||||||
|
2015/04/09 18:19:17 [001] INFO: Sending request GET http://ripper234.com/p/introducing-goproxy-light-http-proxy/
|
||||||
|
2015/04/09 18:19:18 [001] INFO: Received response 200 OK
|
||||||
|
2015/04/09 18:19:18 [001] INFO: Copying response to client 200 OK [200]
|
||||||
|
2015/04/09 18:19:18 [001] INFO: Copied 44333 bytes to client error=<nil>
|
||||||
|
```
|
||||||
|
|
17
vendor/github.com/elazarl/goproxy/examples/goproxy-basic/main.go
generated
vendored
Normal file
17
vendor/github.com/elazarl/goproxy/examples/goproxy-basic/main.go
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/elazarl/goproxy"
|
||||||
|
"log"
|
||||||
|
"flag"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
verbose := flag.Bool("v", false, "should every proxy request be logged to stdout")
|
||||||
|
addr := flag.String("addr", ":8080", "proxy listen address")
|
||||||
|
flag.Parse()
|
||||||
|
proxy := goproxy.NewProxyHttpServer()
|
||||||
|
proxy.Verbose = *verbose
|
||||||
|
log.Fatal(http.ListenAndServe(*addr, proxy))
|
||||||
|
}
|
75
vendor/github.com/elazarl/goproxy/examples/goproxy-customca/cert.go
generated
vendored
Normal file
75
vendor/github.com/elazarl/goproxy/examples/goproxy-customca/cert.go
generated
vendored
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
|
||||||
|
"github.com/elazarl/goproxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
var caCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDkzCCAnugAwIBAgIJAKe/ZGdfcHdPMA0GCSqGSIb3DQEBCwUAMGAxCzAJBgNV
|
||||||
|
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
|
||||||
|
aWRnaXRzIFB0eSBMdGQxGTAXBgNVBAMMEGRlbW8gZm9yIGdvcHJveHkwHhcNMTYw
|
||||||
|
OTI3MTQzNzQ3WhcNMTkwOTI3MTQzNzQ3WjBgMQswCQYDVQQGEwJBVTETMBEGA1UE
|
||||||
|
CAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk
|
||||||
|
MRkwFwYDVQQDDBBkZW1vIGZvciBnb3Byb3h5MIIBIjANBgkqhkiG9w0BAQEFAAOC
|
||||||
|
AQ8AMIIBCgKCAQEA2+W48YZoch72zj0a+ZlyFVY2q2MWmqsEY9f/u53fAeTxvPE6
|
||||||
|
1/DnqsydnA3FnGvxw9Dz0oZO6xG+PZvp+lhN07NZbuXK1nie8IpxCa342axpu4C0
|
||||||
|
69lZwxikpGyJO4IL5ywp/qfb5a2DxPTAyQOQ8ROAaydoEmktRp25yicnQ2yeZW//
|
||||||
|
1SIQxt7gRxQIGmuOQ/Gqr/XN/z2cZdbGJVRUvQXk7N6NhQiCX1zlmp1hzUW9jwC+
|
||||||
|
JEKKF1XVpQbc94Bo5supxhkKJ70CREPy8TH9mAUcQUZQRohnPvvt/lKneYAGhjHK
|
||||||
|
vhpajwlbMMSocVXFvY7o/IqIE/+ZUeQTs1SUwQIDAQABo1AwTjAdBgNVHQ4EFgQU
|
||||||
|
GnlWcIbfsWJW7GId+6xZIK8YlFEwHwYDVR0jBBgwFoAUGnlWcIbfsWJW7GId+6xZ
|
||||||
|
IK8YlFEwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAoFUjSD15rKlY
|
||||||
|
xudzyVlr6n0fRNhITkiZMX3JlFOvtHNYif8RfK4TH/oHNBTmle69AgixjMgy8GGd
|
||||||
|
H90prytGQ5zCs1tKcCFsN5gRSgdAkc2PpRFOK6u8HwOITV5lV7sjucsddXJcOJbQ
|
||||||
|
4fyVe47V9TTxI+A7lRnUP2HYTR1Bd0R/IgRAH57d1ZHs7omHIuQ+Ea8ph2ppXMnP
|
||||||
|
DXVOlZ9zfczSnPnQoomqULOU9Fq2ycyi8Y/ROtAHP6O7wCFbYHXhxojdaHSdhkcd
|
||||||
|
troTflFMD2/4O6MtBKbHxSmEG6H0FBYz5xUZhZq7WUH24V3xYsfge29/lOCd5/Xf
|
||||||
|
A+j0RJc/lQ==
|
||||||
|
-----END CERTIFICATE-----`)
|
||||||
|
|
||||||
|
var caKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEowIBAAKCAQEA2+W48YZoch72zj0a+ZlyFVY2q2MWmqsEY9f/u53fAeTxvPE6
|
||||||
|
1/DnqsydnA3FnGvxw9Dz0oZO6xG+PZvp+lhN07NZbuXK1nie8IpxCa342axpu4C0
|
||||||
|
69lZwxikpGyJO4IL5ywp/qfb5a2DxPTAyQOQ8ROAaydoEmktRp25yicnQ2yeZW//
|
||||||
|
1SIQxt7gRxQIGmuOQ/Gqr/XN/z2cZdbGJVRUvQXk7N6NhQiCX1zlmp1hzUW9jwC+
|
||||||
|
JEKKF1XVpQbc94Bo5supxhkKJ70CREPy8TH9mAUcQUZQRohnPvvt/lKneYAGhjHK
|
||||||
|
vhpajwlbMMSocVXFvY7o/IqIE/+ZUeQTs1SUwQIDAQABAoIBAHK94ww8W0G5QIWL
|
||||||
|
Qwkc9XeGvg4eLUxVknva2Ll4fkZJxY4WveKx9OCd1lv4n7WoacYIwUGIDaQBZShW
|
||||||
|
s/eKnkmqGy+PvpC87gqL4sHvQpuqqJ1LYpxylLEFqduWOuGPUVC2Lc+QnWCycsCS
|
||||||
|
CgqZzsbMq0S+kkKRGSvw32JJneZCzqLgLNssQNVk+Gm6SI3s4jJsGPesjhnvoPaa
|
||||||
|
xZK14uFpltaA05GSTDaQeZJFEdnnb3f/eNPc2xMEfi0S2ZlJ6Q92WJEOepAetDlR
|
||||||
|
cRFi004bNyTb4Bphg8s4+9Cti5is199aFkGCRDWxeqEnc6aMY3Ezu9Qg3uttLVUd
|
||||||
|
uy830GUCgYEA7qS0X+9UH1R02L3aoANyADVbFt2ZpUwQGauw9WM92pH52xeHAw1S
|
||||||
|
ohus6FI3OC8xQq2CN525tGLUbFDZnNZ3YQHqFsfgevfnTs1//gbKXomitev0oFKh
|
||||||
|
VT+WYS4lkgYtPlXzhdGuk32q99T/wIocAguvCUY3PiA7yBz93ReyausCgYEA6+P8
|
||||||
|
bugMqT8qjoiz1q/YCfxsw9bAGWjlVqme2xmp256AKtxvCf1BPsToAaJU3nFi3vkw
|
||||||
|
ICLxUWAYoMBODJ3YnbOsIZOavdXZwYHv54JqwqFealC3DG0Du6fZYZdiY8pK+E6m
|
||||||
|
3fiYzP1WoVK5tU4bH8ibuIQvpcI8j7Gy0cV6/AMCgYBHl7fZNAZro72uLD7DVGVF
|
||||||
|
9LvP/0kR0uDdoqli5JPw12w6szM40i1hHqZfyBJy042WsFDpeHL2z9Nkb1jpeVm1
|
||||||
|
C4r7rJkGqwqElJf6UHUzqVzb8N6hnkhyN7JYkyyIQzwdgFGfaslRzBiXYxoa3BQM
|
||||||
|
9Q5c3OjDxY3JuhDa3DoVYwKBgDNqrWJLSD832oHZAEIicBe1IswJKjQfriWWsV6W
|
||||||
|
mHSbdtpg0/88aZVR/DQm+xLFakSp0jifBTS0momngRu06Dtvp2xmLQuF6oIIXY97
|
||||||
|
2ON1owvPbibSOEcWDgb8pWCU/oRjOHIXts6vxctCKeKAFN93raGphm0+Ck9T72NU
|
||||||
|
BTubAoGBAMEhI/Wy9wAETuXwN84AhmPdQsyCyp37YKt2ZKaqu37x9v2iL8JTbPEz
|
||||||
|
pdBzkA2Gc0Wdb6ekIzRrTsJQl+c/0m9byFHsRsxXW2HnezfOFX1H4qAmF6KWP0ub
|
||||||
|
M8aIn6Rab4sNPSrvKGrU6rFpv/6M33eegzldVnV9ku6uPJI1fFTC
|
||||||
|
-----END RSA PRIVATE KEY-----`)
|
||||||
|
|
||||||
|
func setCA(caCert, caKey []byte) error {
|
||||||
|
goproxyCa, err := tls.X509KeyPair(caCert, caKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if goproxyCa.Leaf, err = x509.ParseCertificate(goproxyCa.Certificate[0]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
goproxy.GoproxyCa = goproxyCa
|
||||||
|
goproxy.OkConnect = &goproxy.ConnectAction{Action: goproxy.ConnectAccept, TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)}
|
||||||
|
goproxy.MitmConnect = &goproxy.ConnectAction{Action: goproxy.ConnectMitm, TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)}
|
||||||
|
goproxy.HTTPMitmConnect = &goproxy.ConnectAction{Action: goproxy.ConnectHTTPMitm, TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)}
|
||||||
|
goproxy.RejectConnect = &goproxy.ConnectAction{Action: goproxy.ConnectReject, TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)}
|
||||||
|
return nil
|
||||||
|
}
|
20
vendor/github.com/elazarl/goproxy/examples/goproxy-customca/main.go
generated
vendored
Normal file
20
vendor/github.com/elazarl/goproxy/examples/goproxy-customca/main.go
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/elazarl/goproxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
verbose := flag.Bool("v", false, "should every proxy request be logged to stdout")
|
||||||
|
addr := flag.String("addr", ":8080", "proxy listen address")
|
||||||
|
flag.Parse()
|
||||||
|
setCA(caCert, caKey)
|
||||||
|
proxy := goproxy.NewProxyHttpServer()
|
||||||
|
proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)
|
||||||
|
proxy.Verbose = *verbose
|
||||||
|
log.Fatal(http.ListenAndServe(*addr, proxy))
|
||||||
|
}
|
56
vendor/github.com/elazarl/goproxy/examples/goproxy-eavesdropper/main.go
generated
vendored
Normal file
56
vendor/github.com/elazarl/goproxy/examples/goproxy-eavesdropper/main.go
generated
vendored
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/elazarl/goproxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
func orPanic(err error) {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
proxy := goproxy.NewProxyHttpServer()
|
||||||
|
proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*baidu.com$"))).
|
||||||
|
HandleConnect(goproxy.AlwaysReject)
|
||||||
|
proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*$"))).
|
||||||
|
HandleConnect(goproxy.AlwaysMitm)
|
||||||
|
// enable curl -p for all hosts on port 80
|
||||||
|
proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*:80$"))).
|
||||||
|
HijackConnect(func(req *http.Request, client net.Conn, ctx *goproxy.ProxyCtx) {
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
ctx.Logf("error connecting to remote: %v", e)
|
||||||
|
client.Write([]byte("HTTP/1.1 500 Cannot reach destination\r\n\r\n"))
|
||||||
|
}
|
||||||
|
client.Close()
|
||||||
|
}()
|
||||||
|
clientBuf := bufio.NewReadWriter(bufio.NewReader(client), bufio.NewWriter(client))
|
||||||
|
remote, err := net.Dial("tcp", req.URL.Host)
|
||||||
|
orPanic(err)
|
||||||
|
remoteBuf := bufio.NewReadWriter(bufio.NewReader(remote), bufio.NewWriter(remote))
|
||||||
|
for {
|
||||||
|
req, err := http.ReadRequest(clientBuf.Reader)
|
||||||
|
orPanic(err)
|
||||||
|
orPanic(req.Write(remoteBuf))
|
||||||
|
orPanic(remoteBuf.Flush())
|
||||||
|
resp, err := http.ReadResponse(remoteBuf.Reader, req)
|
||||||
|
orPanic(err)
|
||||||
|
orPanic(resp.Write(clientBuf.Writer))
|
||||||
|
orPanic(clientBuf.Flush())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
verbose := flag.Bool("v", false, "should every proxy request be logged to stdout")
|
||||||
|
addr := flag.String("addr", ":8080", "proxy listen address")
|
||||||
|
flag.Parse()
|
||||||
|
proxy.Verbose = *verbose
|
||||||
|
log.Fatal(http.ListenAndServe(*addr, proxy))
|
||||||
|
}
|
30
vendor/github.com/elazarl/goproxy/examples/goproxy-httpdump/README.md
generated
vendored
Normal file
30
vendor/github.com/elazarl/goproxy/examples/goproxy-httpdump/README.md
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# Trace HTTP Requests and Responses
|
||||||
|
|
||||||
|
`goproxy-httpdump` starts an HTTP proxy on :8080. It handles explicit CONNECT
|
||||||
|
requests and traces them in a "db" directory created in the proxy working
|
||||||
|
directory. Each request type and headers are logged in a "log" file, while
|
||||||
|
their bodies are dumped in files prefixed with the request session identifier.
|
||||||
|
|
||||||
|
Additionally, the example demonstrates how to:
|
||||||
|
- Log information asynchronously (see HttpLogger)
|
||||||
|
- Allow the proxy to be stopped manually while ensuring all pending requests
|
||||||
|
have been processed (in this case, logged).
|
||||||
|
|
||||||
|
Start it in one shell:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
goproxy-httpdump
|
||||||
|
```
|
||||||
|
|
||||||
|
Fetch goproxy homepage in another:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
http_proxy=http://127.0.0.1:8080 wget -O - \
|
||||||
|
http://ripper234.com/p/introducing-goproxy-light-http-proxy/
|
||||||
|
```
|
||||||
|
|
||||||
|
A "db" directory should have appeared where you started the proxy, containing
|
||||||
|
two files:
|
||||||
|
- log: the request/response traces
|
||||||
|
- 1\_resp: the first response body
|
||||||
|
|
285
vendor/github.com/elazarl/goproxy/examples/goproxy-httpdump/httpdump.go
generated
vendored
Normal file
285
vendor/github.com/elazarl/goproxy/examples/goproxy-httpdump/httpdump.go
generated
vendored
Normal file
|
@ -0,0 +1,285 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"path"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/elazarl/goproxy"
|
||||||
|
"github.com/elazarl/goproxy/transport"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileStream struct {
|
||||||
|
path string
|
||||||
|
f *os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFileStream(path string) *FileStream {
|
||||||
|
return &FileStream{path, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *FileStream) Write(b []byte) (nr int, err error) {
|
||||||
|
if fs.f == nil {
|
||||||
|
fs.f, err = os.Create(fs.path)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fs.f.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *FileStream) Close() error {
|
||||||
|
fmt.Println("Close", fs.path)
|
||||||
|
if fs.f == nil {
|
||||||
|
return errors.New("FileStream was never written into")
|
||||||
|
}
|
||||||
|
return fs.f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Meta struct {
|
||||||
|
req *http.Request
|
||||||
|
resp *http.Response
|
||||||
|
err error
|
||||||
|
t time.Time
|
||||||
|
sess int64
|
||||||
|
bodyPath string
|
||||||
|
from string
|
||||||
|
}
|
||||||
|
|
||||||
|
func fprintf(nr *int64, err *error, w io.Writer, pat string, a ...interface{}) {
|
||||||
|
if *err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var n int
|
||||||
|
n, *err = fmt.Fprintf(w, pat, a...)
|
||||||
|
*nr += int64(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func write(nr *int64, err *error, w io.Writer, b []byte) {
|
||||||
|
if *err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var n int
|
||||||
|
n, *err = w.Write(b)
|
||||||
|
*nr += int64(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Meta) WriteTo(w io.Writer) (nr int64, err error) {
|
||||||
|
if m.req != nil {
|
||||||
|
fprintf(&nr, &err, w, "Type: request\r\n")
|
||||||
|
} else if m.resp != nil {
|
||||||
|
fprintf(&nr, &err, w, "Type: response\r\n")
|
||||||
|
}
|
||||||
|
fprintf(&nr, &err, w, "ReceivedAt: %v\r\n", m.t)
|
||||||
|
fprintf(&nr, &err, w, "Session: %d\r\n", m.sess)
|
||||||
|
fprintf(&nr, &err, w, "From: %v\r\n", m.from)
|
||||||
|
if m.err != nil {
|
||||||
|
// note the empty response
|
||||||
|
fprintf(&nr, &err, w, "Error: %v\r\n\r\n\r\n\r\n", m.err)
|
||||||
|
} else if m.req != nil {
|
||||||
|
fprintf(&nr, &err, w, "\r\n")
|
||||||
|
buf, err2 := httputil.DumpRequest(m.req, false)
|
||||||
|
if err2 != nil {
|
||||||
|
return nr, err2
|
||||||
|
}
|
||||||
|
write(&nr, &err, w, buf)
|
||||||
|
} else if m.resp != nil {
|
||||||
|
fprintf(&nr, &err, w, "\r\n")
|
||||||
|
buf, err2 := httputil.DumpResponse(m.resp, false)
|
||||||
|
if err2 != nil {
|
||||||
|
return nr, err2
|
||||||
|
}
|
||||||
|
write(&nr, &err, w, buf)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// HttpLogger is an asynchronous HTTP request/response logger. It traces
|
||||||
|
// requests and responses headers in a "log" file in logger directory and dumps
|
||||||
|
// their bodies in files prefixed with the session identifiers.
|
||||||
|
// Close it to ensure pending items are correctly logged.
|
||||||
|
type HttpLogger struct {
|
||||||
|
path string
|
||||||
|
c chan *Meta
|
||||||
|
errch chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLogger(basepath string) (*HttpLogger, error) {
|
||||||
|
f, err := os.Create(path.Join(basepath, "log"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
logger := &HttpLogger{basepath, make(chan *Meta), make(chan error)}
|
||||||
|
go func() {
|
||||||
|
for m := range logger.c {
|
||||||
|
if _, err := m.WriteTo(f); err != nil {
|
||||||
|
log.Println("Can't write meta", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.errch <- f.Close()
|
||||||
|
}()
|
||||||
|
return logger, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *HttpLogger) LogResp(resp *http.Response, ctx *goproxy.ProxyCtx) {
|
||||||
|
body := path.Join(logger.path, fmt.Sprintf("%d_resp", ctx.Session))
|
||||||
|
from := ""
|
||||||
|
if ctx.UserData != nil {
|
||||||
|
from = ctx.UserData.(*transport.RoundTripDetails).TCPAddr.String()
|
||||||
|
}
|
||||||
|
if resp == nil {
|
||||||
|
resp = emptyResp
|
||||||
|
} else {
|
||||||
|
resp.Body = NewTeeReadCloser(resp.Body, NewFileStream(body))
|
||||||
|
}
|
||||||
|
logger.LogMeta(&Meta{
|
||||||
|
resp: resp,
|
||||||
|
err: ctx.Error,
|
||||||
|
t: time.Now(),
|
||||||
|
sess: ctx.Session,
|
||||||
|
from: from})
|
||||||
|
}
|
||||||
|
|
||||||
|
var emptyResp = &http.Response{}
|
||||||
|
var emptyReq = &http.Request{}
|
||||||
|
|
||||||
|
func (logger *HttpLogger) LogReq(req *http.Request, ctx *goproxy.ProxyCtx) {
|
||||||
|
body := path.Join(logger.path, fmt.Sprintf("%d_req", ctx.Session))
|
||||||
|
if req == nil {
|
||||||
|
req = emptyReq
|
||||||
|
} else {
|
||||||
|
req.Body = NewTeeReadCloser(req.Body, NewFileStream(body))
|
||||||
|
}
|
||||||
|
logger.LogMeta(&Meta{
|
||||||
|
req: req,
|
||||||
|
err: ctx.Error,
|
||||||
|
t: time.Now(),
|
||||||
|
sess: ctx.Session,
|
||||||
|
from: req.RemoteAddr})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *HttpLogger) LogMeta(m *Meta) {
|
||||||
|
logger.c <- m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *HttpLogger) Close() error {
|
||||||
|
close(logger.c)
|
||||||
|
return <-logger.errch
|
||||||
|
}
|
||||||
|
|
||||||
|
// TeeReadCloser extends io.TeeReader by allowing reader and writer to be
|
||||||
|
// closed.
|
||||||
|
type TeeReadCloser struct {
|
||||||
|
r io.Reader
|
||||||
|
w io.WriteCloser
|
||||||
|
c io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTeeReadCloser(r io.ReadCloser, w io.WriteCloser) io.ReadCloser {
|
||||||
|
return &TeeReadCloser{io.TeeReader(r, w), w, r}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TeeReadCloser) Read(b []byte) (int, error) {
|
||||||
|
return t.r.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close attempts to close the reader and write. It returns an error if both
|
||||||
|
// failed to Close.
|
||||||
|
func (t *TeeReadCloser) Close() error {
|
||||||
|
err1 := t.c.Close()
|
||||||
|
err2 := t.w.Close()
|
||||||
|
if err1 != nil {
|
||||||
|
return err1
|
||||||
|
}
|
||||||
|
return err2
|
||||||
|
}
|
||||||
|
|
||||||
|
// stoppableListener serves stoppableConn and tracks their lifetime to notify
|
||||||
|
// when it is safe to terminate the application.
|
||||||
|
type stoppableListener struct {
|
||||||
|
net.Listener
|
||||||
|
sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
type stoppableConn struct {
|
||||||
|
net.Conn
|
||||||
|
wg *sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStoppableListener(l net.Listener) *stoppableListener {
|
||||||
|
return &stoppableListener{l, sync.WaitGroup{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl *stoppableListener) Accept() (net.Conn, error) {
|
||||||
|
c, err := sl.Listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
sl.Add(1)
|
||||||
|
return &stoppableConn{c, &sl.WaitGroup}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *stoppableConn) Close() error {
|
||||||
|
sc.wg.Done()
|
||||||
|
return sc.Conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
verbose := flag.Bool("v", false, "should every proxy request be logged to stdout")
|
||||||
|
addr := flag.String("l", ":8080", "on which address should the proxy listen")
|
||||||
|
flag.Parse()
|
||||||
|
proxy := goproxy.NewProxyHttpServer()
|
||||||
|
proxy.Verbose = *verbose
|
||||||
|
if err := os.MkdirAll("db", 0755); err != nil {
|
||||||
|
log.Fatal("Can't create dir", err)
|
||||||
|
}
|
||||||
|
logger, err := NewLogger("db")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("can't open log file", err)
|
||||||
|
}
|
||||||
|
tr := transport.Transport{Proxy: transport.ProxyFromEnvironment}
|
||||||
|
// For every incoming request, override the RoundTripper to extract
|
||||||
|
// connection information. Store it is session context log it after
|
||||||
|
// handling the response.
|
||||||
|
proxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
|
||||||
|
ctx.RoundTripper = goproxy.RoundTripperFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (resp *http.Response, err error) {
|
||||||
|
ctx.UserData, resp, err = tr.DetailedRoundTrip(req)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
logger.LogReq(req, ctx)
|
||||||
|
return req, nil
|
||||||
|
})
|
||||||
|
proxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {
|
||||||
|
logger.LogResp(resp, ctx)
|
||||||
|
return resp
|
||||||
|
})
|
||||||
|
l, err := net.Listen("tcp", *addr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("listen:", err)
|
||||||
|
}
|
||||||
|
sl := newStoppableListener(l)
|
||||||
|
ch := make(chan os.Signal)
|
||||||
|
signal.Notify(ch, os.Interrupt)
|
||||||
|
go func() {
|
||||||
|
<-ch
|
||||||
|
log.Println("Got SIGINT exiting")
|
||||||
|
sl.Add(1)
|
||||||
|
sl.Close()
|
||||||
|
logger.Close()
|
||||||
|
sl.Done()
|
||||||
|
}()
|
||||||
|
log.Println("Starting Proxy")
|
||||||
|
http.Serve(sl, proxy)
|
||||||
|
sl.Wait()
|
||||||
|
log.Println("All connections closed - exit")
|
||||||
|
}
|
31
vendor/github.com/elazarl/goproxy/examples/goproxy-jquery-version/README.md
generated
vendored
Normal file
31
vendor/github.com/elazarl/goproxy/examples/goproxy-jquery-version/README.md
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# Content Analysis
|
||||||
|
|
||||||
|
`goproxy-jquery-version` starts an HTTP proxy on :8080. It checks HTML
|
||||||
|
responses, looks for scripts referencing jQuery library and emits warnings if
|
||||||
|
different versions of the library are being used for a given host.
|
||||||
|
|
||||||
|
Start it in one shell:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
goproxy-jquery-version
|
||||||
|
```
|
||||||
|
|
||||||
|
Fetch goproxy homepage in another:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
http_proxy=http://127.0.0.1:8080 wget -O - \
|
||||||
|
http://ripper234.com/p/introducing-goproxy-light-http-proxy/
|
||||||
|
```
|
||||||
|
|
||||||
|
Goproxy homepage uses jQuery and a mix of plugins. First the proxy reports the
|
||||||
|
first use of jQuery it detects for the domain. Then, because the regular
|
||||||
|
expression matching the jQuery sources is imprecise, it reports a mismatch with
|
||||||
|
a plugin reference:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
2015/04/11 11:23:02 [001] WARN: ripper234.com uses //ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js
|
||||||
|
2015/04/11 11:23:02 [001] WARN: In http://ripper234.com/p/introducing-goproxy-light-http-proxy/, \
|
||||||
|
Contradicting jqueries //ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js \
|
||||||
|
http://ripper234.wpengine.netdna-cdn.com/wp-content/plugins/wp-ajax-edit-comments/js/jquery.colorbox.min.js?ver=5.0.36
|
||||||
|
```
|
||||||
|
|
8
vendor/github.com/elazarl/goproxy/examples/goproxy-jquery-version/jquery1.html
generated
vendored
Normal file
8
vendor/github.com/elazarl/goproxy/examples/goproxy-jquery-version/jquery1.html
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="jquery.1.4.js"></script>
|
||||||
|
</head>
|
||||||
|
<body/>
|
||||||
|
</html>
|
||||||
|
|
8
vendor/github.com/elazarl/goproxy/examples/goproxy-jquery-version/jquery2.html
generated
vendored
Normal file
8
vendor/github.com/elazarl/goproxy/examples/goproxy-jquery-version/jquery2.html
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="jquery.1.3.js"></script>
|
||||||
|
</head>
|
||||||
|
<body/>
|
||||||
|
</html>
|
||||||
|
|
233
vendor/github.com/elazarl/goproxy/examples/goproxy-jquery-version/jquery_homepage.html
generated
vendored
Normal file
233
vendor/github.com/elazarl/goproxy/examples/goproxy-jquery-version/jquery_homepage.html
generated
vendored
Normal file
|
@ -0,0 +1,233 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||||
|
<title>jQuery: The Write Less, Do More, JavaScript Library</title>
|
||||||
|
<link rel="stylesheet" href="http://static.jquery.com/files/rocker/css/reset.css" type="text/css" />
|
||||||
|
<link rel="stylesheet" href="http://static.jquery.com/files/rocker/css/screen.css" type="text/css" />
|
||||||
|
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
|
||||||
|
<script>!window.jQuery && document.write('<script src="http://code.jquery.com/jquery-1.4.2.min.js"><\/script>');</script>
|
||||||
|
<script src="http://static.jquery.com/files/rocker/scripts/custom.js"></script>
|
||||||
|
<link rel="alternate" type="application/rss+xml" title="jQuery Blog" href="http://jquery.com/blog/feed/" />
|
||||||
|
<link rel="shortcut icon" href="http://static.jquery.com/favicon.ico" type="image/x-icon"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="jq-siteContain">
|
||||||
|
<div id="jq-header">
|
||||||
|
<a id="jq-siteLogo" href="http://jquery.com" title="jQuery Home"><img src="http://static.jquery.com/files/rocker/images/logo_jquery_215x53.gif" width="215" height="53" alt="jQuery: Write Less, Do More." /></a>
|
||||||
|
|
||||||
|
<div id="jq-primaryNavigation">
|
||||||
|
<ul>
|
||||||
|
<li class="jq-jquery jq-current"><a href="http://jquery.com/" title="jQuery Home">jQuery</a></li>
|
||||||
|
<li class="jq-ui"><a href="http://jqueryui.com/" title="jQuery UI">UI</a></li>
|
||||||
|
<li class="jq-mobile"><a href="http://jquerymobile.com/" title="jQuery Mobile">Mobile</a></li>
|
||||||
|
<li class="jq-plugins"><a href="http://plugins.jquery.com/" title="jQuery Plugins">Plugins</a></li>
|
||||||
|
<li class="jq-meetup"><a href="http://meetups.jquery.com/" title="jQuery Meetups">Meetups</a></li>
|
||||||
|
<li class="jq-forum"><a href="http://forum.jquery.com/" title="jQuery Forum">Forum</a></li>
|
||||||
|
<li class="jq-blog"><a href="http://blog.jquery.com/" title="jQuery Blog">Blog</a></li>
|
||||||
|
<li class="jq-about"><a href="http://jquery.org/about" title="About jQuery">About</a></li>
|
||||||
|
<li class="jq-donate"><a href="http://jquery.org/donate" title="Donate to jQuery">Donate</a></li>
|
||||||
|
</ul>
|
||||||
|
</div><!-- /#primaryNavigation -->
|
||||||
|
|
||||||
|
<div id="jq-secondaryNavigation">
|
||||||
|
<ul>
|
||||||
|
<li class="jq-download jq-first"><a href="http://docs.jquery.com/Downloading_jQuery">Download</a></li>
|
||||||
|
|
||||||
|
<li class="jq-documentation"><a href="http://docs.jquery.com">Documentation</a></li>
|
||||||
|
<li class="jq-tutorials"><a href="http://docs.jquery.com/Tutorials">Tutorials</a></li>
|
||||||
|
<li class="jq-bugTracker"><a href="http://dev.jquery.com/">Bug Tracker</a></li>
|
||||||
|
<li class="jq-discussion jq-last"><a href="http://docs.jquery.com/Discussion">Discussion</a></li>
|
||||||
|
</ul>
|
||||||
|
</div><!-- /#secondaryNavigation -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div><!-- /#header -->
|
||||||
|
|
||||||
|
<div id="jq-content" class="jq-clearfix">
|
||||||
|
|
||||||
|
<div id="jq-intro" class="jq-clearfix">
|
||||||
|
<h2><span class="jq-jquery"><span>jQuery</span></span> is a new kind of JavaScript Library.</h2>
|
||||||
|
<p>jQuery is a fast and concise JavaScript Library that simplifies HTML document traversing, event handling, animating, and Ajax interactions for rapid web development. <strong>jQuery is designed to change the way that you write JavaScript.</strong></p>
|
||||||
|
<ul class="jq-checkpoints jq-clearfix">
|
||||||
|
<li><a href="http://docs.jquery.com/Tutorials" title="Lightweight Footprint" class="jq-thickbox">Lightweight Footprint</a>
|
||||||
|
<div class="jq-checkpointSubhead">
|
||||||
|
|
||||||
|
<p>About 31KB in size <em>(Minified and Gzipped)</em></p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li><a href="http://docs.jquery.com/Tutorials" title="CSS3 Compliant" class="jq-thickbox">CSS3 Compliant</a>
|
||||||
|
<div class="jq-checkpointSubhead">
|
||||||
|
<p>Supports CSS 1-3 selectors and more!</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li><a href="http://docs.jquery.com/Tutorials" title="Cross-browser" class="jq-thickbox">Cross-browser</a>
|
||||||
|
<div class="jq-checkpointSubhead">
|
||||||
|
<p>IE 6.0+, FF 3.6+, Safari 5.0+, Opera, Chrome</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div><!-- /#intro -->
|
||||||
|
|
||||||
|
<div id="jq-download">
|
||||||
|
<h2>Grab the latest version!</h2>
|
||||||
|
<form action="" method="get">
|
||||||
|
<fieldset>
|
||||||
|
<legend>Choose your compression level:</legend>
|
||||||
|
<div id="jq-compression" class="jq-clearfix">
|
||||||
|
<input type="radio" name="name" value="http://code.jquery.com/jquery-1.7.2.min.js" id="jq-production" checked="checked" />
|
||||||
|
<a class="jq-radioToggle name jq-checked" href="http://code.jquery.com/jquery-1.7.2.min.js">jquery-1.7.2.min.js</a>
|
||||||
|
<label for="jq-production">Production <em>(<strong>32KB</strong>, Minified and Gzipped)</em></label>
|
||||||
|
<input type="radio" name="name" value="http://code.jquery.com/jquery-1.7.2.js" id="jq-development" />
|
||||||
|
<a class="jq-radioToggle name" href="http://code.jquery.com/jquery-1.7.2.js">jquery-1.7.2.js</a>
|
||||||
|
<label for="jq-development">Development <em>(<strong>247KB</strong>, Uncompressed Code)</em></label>
|
||||||
|
</div>
|
||||||
|
<button type="submit" name="downloadBtn" id="jq-downloadBtn"><span>Download</span></button>
|
||||||
|
<p class="jq-version"><strong>Current Release:</strong> v1.7.2</p>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
<script>
|
||||||
|
jQuery("#jq-download form").submit(function(){
|
||||||
|
window.location = jQuery(this).find("input:checked").val();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</div><!-- /#download -->
|
||||||
|
|
||||||
|
<div id="jq-whosUsing">
|
||||||
|
<h2 class="jq-whosUsing">Who's using jQuery?</h2>
|
||||||
|
<ul class="jq-whosUsing">
|
||||||
|
<li><a href="http://www.google.com" class="jq-google" title="Google">Google</a></li>
|
||||||
|
<li><a href="http://www.dell.com" class="jq-dell" title="Dell">Dell</a></li>
|
||||||
|
<li><a href="http://www.bankofamerica.com" class="jq-boa" title="Bank of America">Bank of America</a></li>
|
||||||
|
<li><a href="http://www.mlb.com" class="jq-mlb" title="Major League Baseball">Major League Baseball</a></li>
|
||||||
|
<li><a href="http://www.digg.com" class="jq-digg" title="Digg">Digg</a></li>
|
||||||
|
<li><a href="http://www.nbc.com" class="jq-nbc" title="NBC">NBC</a></li>
|
||||||
|
<li><a href="http://www.cbs.com" class="jq-cbs" title="CBS News">CBS News</a></li>
|
||||||
|
<li><a href="http://www.netflix.com" class="jq-netflix" title="Netflix">Netflix</a></li>
|
||||||
|
<li><a href="http://www.technorati.com" class="jq-technorati" title="Technorati">Technorati</a></li>
|
||||||
|
<li><a href="http://www.mozilla.org" class="jq-mozilla" title="Mozilla">Mozilla</a></li>
|
||||||
|
<li><a href="http://www.wordpress.org" class="jq-wordpress" title="Wordpress">Wordpress</a></li>
|
||||||
|
<li><a href="http://www.drupal.org" class="jq-drupal" title="Drupal">Drupal</a></li>
|
||||||
|
</ul>
|
||||||
|
</div><!-- /#jq-whosUsing -->
|
||||||
|
|
||||||
|
|
||||||
|
<div id="jq-learnjQuery" class="jq-clearfix">
|
||||||
|
|
||||||
|
<div id="jq-learnNow">
|
||||||
|
<h2>Learn <span class="jq-jquery"><span>jQuery</span></span> Now!</h2>
|
||||||
|
<p>What does jQuery code look like? Here's the quick and dirty:</p>
|
||||||
|
<div class="jq-codeDemo jq-clearfix">
|
||||||
|
<pre><code>$("p.neat").addClass("ohmy").show("slow");</code></pre>
|
||||||
|
<a href="http://docs.jquery.com/Tutorials" class="jq-runCode">Run Code</a>
|
||||||
|
|
||||||
|
<p class="neat"><strong>Congratulations!</strong> You just ran a snippet of jQuery code. Wasn't that easy? There's lots of example code throughout the <strong><a href="http://docs.jquery.com/">documentation</a></strong> on this site. Be sure to give all the code a test run to see what happens.</p>
|
||||||
|
</div>
|
||||||
|
</div><!-- /#learnNow -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div id="jq-resources" class="clearfix">
|
||||||
|
<h2>jQuery Resources</h2>
|
||||||
|
|
||||||
|
<div class="jq-gettingStarted">
|
||||||
|
<h3>Getting Started With jQuery</h3>
|
||||||
|
<ul>
|
||||||
|
<li><a href="http://docs.jquery.com/How_jQuery_Works">How jQuery Works</a></li>
|
||||||
|
<li><a href="http://docs.jquery.com/Tutorials">Tutorials</a></li>
|
||||||
|
<li><a href="http://docs.jquery.com/Using_jQuery_with_Other_Libraries">Using jQuery with other libraries</a></li>
|
||||||
|
<li><a href="http://docs.jquery.com/">jQuery Documentation</a></li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="jq-devResources">
|
||||||
|
<h3>Developer Resources</h3>
|
||||||
|
<ul>
|
||||||
|
<li><a href="http://docs.jquery.com/Discussion">Mailing List</a></li>
|
||||||
|
<li><a href="http://docs.jquery.com/Downloading_jQuery">Source code / Git</a></li>
|
||||||
|
|
||||||
|
<li><a href="http://docs.jquery.com/Plugins/Authoring">Plugin Authoring</a></li>
|
||||||
|
<li><a href="http://dev.jquery.com/newticket/">Submit a New Bug Report</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div><!-- /#resources -->
|
||||||
|
|
||||||
|
</div><!-- /#learnjQuery -->
|
||||||
|
|
||||||
|
<div id="jq-books" style="width:auto; float: none">
|
||||||
|
<h2>Books About jQuery</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li class="jq-clearfix" style="width:270px;float:left;clear:none;">
|
||||||
|
<a href="http://link.packtpub.com/S3Fr9Q" class="jq-bookImg"><img src="http://learningjquery.kswedberg.netdna-cdn.com/wp-content/themes/ljq/images/ljq3rded.jpg" alt="Learning jQuery Third Edition" width="55" height="70" /></a>
|
||||||
|
<h3><a href="http://link.packtpub.com/S3Fr9Q">Learning jQuery Third Edition</a></h3>
|
||||||
|
<div class="jq-author">Karl Swedberg and <br />Jonathan Chaffer</div>
|
||||||
|
<a href="http://link.packtpub.com/S3Fr9Q" class="jq-buyNow">Buy Now</a>
|
||||||
|
</li>
|
||||||
|
<li class="jq-clearfix" style="width:270px;float:left;clear:none;">
|
||||||
|
<a href="http://www.packtpub.com/jquery-1-4-animation-techniques-beginners-guide/book/mid/1803111nkj15" class="jq-bookImg"><img src="http://static.jquery.com/books/jquery-animation-beginners-guide.jpg" alt="jQuery 1.4 Animation Techniques: Beginners Guide" width="55" height="70" /></a>
|
||||||
|
<h3><a href="http://www.packtpub.com/jquery-1-4-animation-techniques-beginners-guide/book/mid/1803111nkj15">jQuery 1.4 Animation Techniques: Beginners Guide</a></h3>
|
||||||
|
<div class="jq-author">Dan Wellman</div>
|
||||||
|
<a href="http://www.packtpub.com/jquery-1-4-animation-techniques-beginners-guide/book/mid/1803111nkj15" class="jq-buyNow">Buy Now</a>
|
||||||
|
</li>
|
||||||
|
<li class="jq-clearfix" style="width:270px;float:left;clear:none;">
|
||||||
|
<a href="http://www.packtpub.com/jquery-plugin-development-beginners-guide/book/mid/1911104odmdz" class="jq-bookImg"><img src="http://static.jquery.com/books/jquery-plugin-developers-guide_thumb.jpg" alt="jQuery Plugin Development Beginner's Guide" width="55" height="70" /></a>
|
||||||
|
<h3><a href="http://www.packtpub.com/jquery-plugin-development-beginners-guide/book/mid/1911104odmdz">jQuery Plugin Development Beginner's Guide</a></h3>
|
||||||
|
<div class="jq-author">Guilio Bai</div>
|
||||||
|
<a href="http://www.packtpub.com/jquery-plugin-development-beginners-guide/book/mid/1911104odmdz" class="jq-buyNow">Buy Now</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="jq-clearfix" style="width:270px;float:left;clear:left;">
|
||||||
|
<a href="http://www.manning.com/affiliate/idevaffiliate.php?id=648_176" class="jq-bookImg"><img src="http://static.jquery.com/books/jquery-in-action-2ed_thumb.jpg" alt="jQuery in Action" width="55" height="70" /></a>
|
||||||
|
<h3><a href="http://www.manning.com/affiliate/idevaffiliate.php?id=648_176">jQuery in Action</a></h3>
|
||||||
|
<div class="jq-author">Bear Bibeault
|
||||||
|
<br />and Yehuda Katz</div>
|
||||||
|
<a href="http://www.manning.com/affiliate/idevaffiliate.php?id=648_176" class="jq-buyNow">Buy Now</a>
|
||||||
|
</li>
|
||||||
|
<li class="jq-clearfix" style="width:270px;float:left;clear:none;">
|
||||||
|
<a class="jq-bookImg" href="http://jqueryenlightenment.com/"><img src="http://static.jquery.com/books/jquery-enlightenment_thumb.jpg" alt="jQuery Enlightenment" width="55" height="70" /></a>
|
||||||
|
<h3><a href="http://jqueryenlightenment.com/">jQuery Enlightenment</a></h3>
|
||||||
|
<div class="jq-author">Cody Lindley</div>
|
||||||
|
<a href="http://jqueryenlightenment.com/" class="jq-buyNow">Buy Now</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div><!-- /#news -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div><!-- /#content -->
|
||||||
|
|
||||||
|
|
||||||
|
<div id="jq-footer" class="jq-clearfix">
|
||||||
|
|
||||||
|
<div id="jq-credits">
|
||||||
|
<p id="jq-copyright">© 2010 <a href="http://jquery.org/">The jQuery Project</a></p>
|
||||||
|
<p id="jq-hosting">Sponsored by <a href="http://mediatemple.net" class="jq-mediaTemple">Media Temple</a> and <a href="http://jquery.org/sponsors">others</a>.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="jq-footerNavigation">
|
||||||
|
<ul>
|
||||||
|
<li class="jq-download jq-first"><a href="http://docs.jquery.com/Downloading_jQuery">Download</a></li>
|
||||||
|
<li class="jq-documentation"><a href="http://docs.jquery.com">Documentation</a></li>
|
||||||
|
|
||||||
|
<li class="jq-tutorials"><a href="http://docs.jquery.com/Tutorials">Tutorials</a></li>
|
||||||
|
<li class="jq-bugTracker"><a href="http://dev.jquery.com/">Bug Tracker</a></li>
|
||||||
|
<li class="jq-discussion jq-last"><a href="http://docs.jquery.com/Discussion">Discussion</a></li>
|
||||||
|
</ul>
|
||||||
|
</div><!-- /#secondaryNavigation -->
|
||||||
|
|
||||||
|
</div><!-- /#footer -->
|
||||||
|
</div><!-- /#siteContain -->
|
||||||
|
<script src="http://static.jquery.com/donate/donate.js" type="text/javascript"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
var _gaq = _gaq || []; _gaq.push(['_setAccount', 'UA-1076265-1']); _gaq.push(['_trackPageview']); _gaq.push(['_setDomainName', '.jquery.com']);
|
||||||
|
(function() {var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||||
|
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||||
|
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(ga);})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
118
vendor/github.com/elazarl/goproxy/examples/goproxy-jquery-version/jquery_test.go
generated
vendored
Normal file
118
vendor/github.com/elazarl/goproxy/examples/goproxy-jquery-version/jquery_test.go
generated
vendored
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func equal(u, v []string) bool {
|
||||||
|
if len(u) != len(v) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, _ := range u {
|
||||||
|
if u[i] != v[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func readFile(fname string, t *testing.T) string {
|
||||||
|
b, err := ioutil.ReadFile(fname)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("readFile", err)
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefectiveScriptParser(t *testing.T) {
|
||||||
|
if l := len(findScriptSrc(`<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<video width="320" height="240" controls="controls">
|
||||||
|
<source src="movie.mp4" type="video/mp4" />
|
||||||
|
<source src="movie.ogg" type="video/ogg" />
|
||||||
|
<source src="movie.webm" type="video/webm" />
|
||||||
|
Your browser does not support the video tag.
|
||||||
|
</video>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>`)); l != 0 {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
urls := findScriptSrc(readFile("w3schools.html", t))
|
||||||
|
if !equal(urls, []string{"http://partner.googleadservices.com/gampad/google_service.js",
|
||||||
|
"//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"}) {
|
||||||
|
t.Error("w3schools.html", "src scripts are not recognized", urls)
|
||||||
|
}
|
||||||
|
urls = findScriptSrc(readFile("jquery_homepage.html", t))
|
||||||
|
if !equal(urls, []string{"http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js",
|
||||||
|
"http://code.jquery.com/jquery-1.4.2.min.js",
|
||||||
|
"http://static.jquery.com/files/rocker/scripts/custom.js",
|
||||||
|
"http://static.jquery.com/donate/donate.js"}) {
|
||||||
|
t.Error("jquery_homepage.html", "src scripts are not recognized", urls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func proxyWithLog() (*http.Client, *bytes.Buffer) {
|
||||||
|
proxy := NewJqueryVersionProxy()
|
||||||
|
proxyServer := httptest.NewServer(proxy)
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
proxy.Logger = log.New(buf, "", 0)
|
||||||
|
proxyUrl, _ := url.Parse(proxyServer.URL)
|
||||||
|
tr := &http.Transport{Proxy: http.ProxyURL(proxyUrl)}
|
||||||
|
client := &http.Client{Transport: tr}
|
||||||
|
return client, buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func get(t *testing.T, server *httptest.Server, client *http.Client, url string) {
|
||||||
|
resp, err := client.Get(server.URL + url)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("cannot get proxy", err)
|
||||||
|
}
|
||||||
|
ioutil.ReadAll(resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProxyServiceTwoVersions(t *testing.T) {
|
||||||
|
var fs = httptest.NewServer(http.FileServer(http.Dir(".")))
|
||||||
|
defer fs.Close()
|
||||||
|
|
||||||
|
client, buf := proxyWithLog()
|
||||||
|
|
||||||
|
get(t, fs, client, "/w3schools.html")
|
||||||
|
get(t, fs, client, "/php_man.html")
|
||||||
|
if buf.String() != "" &&
|
||||||
|
!strings.Contains(buf.String(), " uses jquery ") {
|
||||||
|
t.Error("shouldn't warn on a single URL", buf.String())
|
||||||
|
}
|
||||||
|
get(t, fs, client, "/jquery1.html")
|
||||||
|
warnings := buf.String()
|
||||||
|
if !strings.Contains(warnings, "http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js") ||
|
||||||
|
!strings.Contains(warnings, "jquery.1.4.js") ||
|
||||||
|
!strings.Contains(warnings, "Contradicting") {
|
||||||
|
t.Error("contradicting jquery versions (php_man.html, w3schools.html) does not issue warning", warnings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProxyService(t *testing.T) {
|
||||||
|
var fs = httptest.NewServer(http.FileServer(http.Dir(".")))
|
||||||
|
defer fs.Close()
|
||||||
|
|
||||||
|
client, buf := proxyWithLog()
|
||||||
|
|
||||||
|
get(t, fs, client, "/jquery_homepage.html")
|
||||||
|
warnings := buf.String()
|
||||||
|
if !strings.Contains(warnings, "http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js") ||
|
||||||
|
!strings.Contains(warnings, "http://code.jquery.com/jquery-1.4.2.min.js") ||
|
||||||
|
!strings.Contains(warnings, "Contradicting") {
|
||||||
|
t.Error("contradicting jquery versions does not issue warning")
|
||||||
|
}
|
||||||
|
}
|
64
vendor/github.com/elazarl/goproxy/examples/goproxy-jquery-version/main.go
generated
vendored
Normal file
64
vendor/github.com/elazarl/goproxy/examples/goproxy-jquery-version/main.go
generated
vendored
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/elazarl/goproxy"
|
||||||
|
"github.com/elazarl/goproxy/ext/html"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// who said we can't parse HTML with regexp?
|
||||||
|
scriptMatcher = regexp.MustCompile(`(?i:<script\s+)`)
|
||||||
|
srcAttrMatcher = regexp.MustCompile(`^(?i:[^>]*\ssrc=["']([^"']*)["'])`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// findScripts returns all sources of HTML script tags found in input text.
|
||||||
|
func findScriptSrc(html string) []string {
|
||||||
|
srcs := make([]string, 0)
|
||||||
|
matches := scriptMatcher.FindAllStringIndex(html, -1)
|
||||||
|
for _, match := range matches {
|
||||||
|
// -1 to capture the whitespace at the end of the script tag
|
||||||
|
srcMatch := srcAttrMatcher.FindStringSubmatch(html[match[1]-1:])
|
||||||
|
if srcMatch != nil {
|
||||||
|
srcs = append(srcs, srcMatch[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return srcs
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJQueryVersionProxy creates a proxy checking responses HTML content, looks
|
||||||
|
// for scripts referencing jQuery library and emits warnings if different
|
||||||
|
// versions of the library are being used for a given host.
|
||||||
|
func NewJqueryVersionProxy() *goproxy.ProxyHttpServer {
|
||||||
|
proxy := goproxy.NewProxyHttpServer()
|
||||||
|
m := make(map[string]string)
|
||||||
|
jqueryMatcher := regexp.MustCompile(`(?i:jquery\.)`)
|
||||||
|
proxy.OnResponse(goproxy_html.IsHtml).Do(goproxy_html.HandleString(
|
||||||
|
func(s string, ctx *goproxy.ProxyCtx) string {
|
||||||
|
for _, src := range findScriptSrc(s) {
|
||||||
|
if !jqueryMatcher.MatchString(src) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
prev, ok := m[ctx.Req.Host]
|
||||||
|
if ok {
|
||||||
|
if prev != src {
|
||||||
|
ctx.Warnf("In %v, Contradicting jqueries %v %v",
|
||||||
|
ctx.Req.URL, prev, src)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.Warnf("%s uses jquery %s", ctx.Req.Host, src)
|
||||||
|
m[ctx.Req.Host] = src
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}))
|
||||||
|
return proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
proxy := NewJqueryVersionProxy()
|
||||||
|
log.Fatal(http.ListenAndServe(":8080", proxy))
|
||||||
|
}
|
323
vendor/github.com/elazarl/goproxy/examples/goproxy-jquery-version/php_man.html
generated
vendored
Normal file
323
vendor/github.com/elazarl/goproxy/examples/goproxy-jquery-version/php_man.html
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
1610
vendor/github.com/elazarl/goproxy/examples/goproxy-jquery-version/w3schools.html
generated
vendored
Normal file
1610
vendor/github.com/elazarl/goproxy/examples/goproxy-jquery-version/w3schools.html
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
21
vendor/github.com/elazarl/goproxy/examples/goproxy-no-reddit-at-worktime/README.md
generated
vendored
Normal file
21
vendor/github.com/elazarl/goproxy/examples/goproxy-no-reddit-at-worktime/README.md
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# Request Filtering
|
||||||
|
|
||||||
|
`goproxy-no-reddit-at-work` starts an HTTP proxy on :8080. It denies requests
|
||||||
|
to "www.reddit.com" made between 8am to 5pm inclusive, local time.
|
||||||
|
|
||||||
|
Start it in one shell:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ goproxy-no-reddit-at-work
|
||||||
|
```
|
||||||
|
|
||||||
|
Fetch reddit in another:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ http_proxy=http://127.0.0.1:8080 wget -O - http://www.reddit.com
|
||||||
|
--2015-04-11 16:59:01-- http://www.reddit.com/
|
||||||
|
Connecting to 127.0.0.1:8080... connected.
|
||||||
|
Proxy request sent, awaiting response... 403 Forbidden
|
||||||
|
2015-04-11 16:59:01 ERROR 403: Forbidden.
|
||||||
|
```
|
||||||
|
|
25
vendor/github.com/elazarl/goproxy/examples/goproxy-no-reddit-at-worktime/noreddit.go
generated
vendored
Normal file
25
vendor/github.com/elazarl/goproxy/examples/goproxy-no-reddit-at-worktime/noreddit.go
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/elazarl/goproxy"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
proxy := goproxy.NewProxyHttpServer()
|
||||||
|
proxy.OnRequest(goproxy.DstHostIs("www.reddit.com")).DoFunc(
|
||||||
|
func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
|
||||||
|
h, _, _ := time.Now().Clock()
|
||||||
|
if h >= 8 && h <= 17 {
|
||||||
|
return r, goproxy.NewResponse(r,
|
||||||
|
goproxy.ContentTypeText, http.StatusForbidden,
|
||||||
|
"Don't waste your time!")
|
||||||
|
} else {
|
||||||
|
ctx.Warnf("clock: %d, you can waste your time...", h)
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
})
|
||||||
|
log.Fatalln(http.ListenAndServe(":8080", proxy))
|
||||||
|
}
|
25
vendor/github.com/elazarl/goproxy/examples/goproxy-sokeepalive/sokeepalive.go
generated
vendored
Normal file
25
vendor/github.com/elazarl/goproxy/examples/goproxy-sokeepalive/sokeepalive.go
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"github.com/elazarl/goproxy"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
verbose := flag.Bool("v", false, "should every proxy request be logged to stdout")
|
||||||
|
addr := flag.String("addr", ":8080", "proxy listen address")
|
||||||
|
flag.Parse()
|
||||||
|
proxy := goproxy.NewProxyHttpServer()
|
||||||
|
proxy.Tr.Dial = func(network, addr string) (c net.Conn, err error) {
|
||||||
|
c, err = net.Dial(network, addr)
|
||||||
|
if c, ok := c.(*net.TCPConn); err == nil && ok {
|
||||||
|
c.SetKeepAlive(true)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
proxy.Verbose = *verbose
|
||||||
|
log.Fatal(http.ListenAndServe(*addr, proxy))
|
||||||
|
}
|
24
vendor/github.com/elazarl/goproxy/examples/goproxy-sslstrip/sslstrip.go
generated
vendored
Normal file
24
vendor/github.com/elazarl/goproxy/examples/goproxy-sslstrip/sslstrip.go
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/elazarl/goproxy"
|
||||||
|
"log"
|
||||||
|
"flag"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
verbose := flag.Bool("v", false, "should every proxy request be logged to stdout")
|
||||||
|
addr := flag.String("addr", ":8080", "proxy listen address")
|
||||||
|
flag.Parse()
|
||||||
|
proxy := goproxy.NewProxyHttpServer()
|
||||||
|
proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)
|
||||||
|
proxy.OnRequest().DoFunc(func (req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
|
||||||
|
if req.URL.Scheme == "https" {
|
||||||
|
req.URL.Scheme = "http"
|
||||||
|
}
|
||||||
|
return req, nil
|
||||||
|
})
|
||||||
|
proxy.Verbose = *verbose
|
||||||
|
log.Fatal(http.ListenAndServe(*addr, proxy))
|
||||||
|
}
|
43
vendor/github.com/elazarl/goproxy/examples/goproxy-stats/README.md
generated
vendored
Normal file
43
vendor/github.com/elazarl/goproxy/examples/goproxy-stats/README.md
generated
vendored
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# Gather Browsing Statistics
|
||||||
|
|
||||||
|
`goproxy-stats` starts an HTTP proxy on :8080, counts the bytes received for
|
||||||
|
web resources and prints the cumulative sum per URL every 20 seconds.
|
||||||
|
|
||||||
|
Start it in one shell:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
goproxy-stats
|
||||||
|
```
|
||||||
|
|
||||||
|
Fetch goproxy homepage in another:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir tmp
|
||||||
|
cd tmp
|
||||||
|
http_proxy=http://127.0.0.1:8080 wget -r -l 1 -H \
|
||||||
|
http://ripper234.com/p/introducing-goproxy-light-http-proxy/
|
||||||
|
```
|
||||||
|
|
||||||
|
Stop it after a moment. `goproxy-stats` should eventually print:
|
||||||
|
```sh
|
||||||
|
listening on :8080
|
||||||
|
statistics
|
||||||
|
http://www.telerik.com/fiddler -> 84335
|
||||||
|
http://msmvps.com/robots.txt -> 157
|
||||||
|
http://eli.thegreenplace.net/robots.txt -> 294
|
||||||
|
http://www.phdcomics.com/robots.txt -> 211
|
||||||
|
http://resharper.blogspot.com/robots.txt -> 221
|
||||||
|
http://idanz.blogli.co.il/robots.txt -> 271
|
||||||
|
http://ripper234.com/p/introducing-goproxy-light-http-proxy/ -> 44407
|
||||||
|
http://live.gnome.org/robots.txt -> 298
|
||||||
|
http://ponetium.wordpress.com/robots.txt -> 178
|
||||||
|
http://pilaheleg.blogli.co.il/robots.txt -> 321
|
||||||
|
http://pilaheleg.wordpress.com/robots.txt -> 178
|
||||||
|
http://blogli.co.il/ -> 9165
|
||||||
|
http://nimrod-code.org/robots.txt -> 289
|
||||||
|
http://www.joelonsoftware.com/robots.txt -> 1245
|
||||||
|
http://top-performance.blogspot.com/robots.txt -> 227
|
||||||
|
http://ooc-lang.org/robots.txt -> 345
|
||||||
|
http://blogs.jetbrains.com/robots.txt -> 293
|
||||||
|
```
|
||||||
|
|
66
vendor/github.com/elazarl/goproxy/examples/goproxy-stats/main.go
generated
vendored
Normal file
66
vendor/github.com/elazarl/goproxy/examples/goproxy-stats/main.go
generated
vendored
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/elazarl/goproxy"
|
||||||
|
"github.com/elazarl/goproxy/ext/html"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
. "net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Count struct {
|
||||||
|
Id string
|
||||||
|
Count int64
|
||||||
|
}
|
||||||
|
type CountReadCloser struct {
|
||||||
|
Id string
|
||||||
|
R io.ReadCloser
|
||||||
|
ch chan<- Count
|
||||||
|
nr int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CountReadCloser) Read(b []byte) (n int, err error) {
|
||||||
|
n, err = c.R.Read(b)
|
||||||
|
c.nr += int64(n)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (c CountReadCloser) Close() error {
|
||||||
|
c.ch <- Count{c.Id, c.nr}
|
||||||
|
return c.R.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
proxy := goproxy.NewProxyHttpServer()
|
||||||
|
timer := make(chan bool)
|
||||||
|
ch := make(chan Count, 10)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
time.Sleep(20 * time.Second)
|
||||||
|
timer <- true
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
m := make(map[string]int64)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case c := <-ch:
|
||||||
|
m[c.Id] = m[c.Id] + c.Count
|
||||||
|
case <-timer:
|
||||||
|
fmt.Printf("statistics\n")
|
||||||
|
for k, v := range m {
|
||||||
|
fmt.Printf("%s -> %d\n", k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// IsWebRelatedText filters on html/javascript/css resources
|
||||||
|
proxy.OnResponse(goproxy_html.IsWebRelatedText).DoFunc(func(resp *Response, ctx *goproxy.ProxyCtx) *Response {
|
||||||
|
resp.Body = &CountReadCloser{ctx.Req.URL.String(), resp.Body, ch, 0}
|
||||||
|
return resp
|
||||||
|
})
|
||||||
|
fmt.Printf("listening on :8080\n")
|
||||||
|
log.Fatal(ListenAndServe(":8080", proxy))
|
||||||
|
}
|
17
vendor/github.com/elazarl/goproxy/examples/goproxy-transparent/README.md
generated
vendored
Normal file
17
vendor/github.com/elazarl/goproxy/examples/goproxy-transparent/README.md
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# Transparent Proxy
|
||||||
|
|
||||||
|
This transparent example in goproxy is meant to show how to transparenty proxy and hijack all http and https connections while doing a man-in-the-middle to the TLS session. It requires that goproxy sees all the packets traversing out to the internet. Linux iptables rules deal with changing the source/destination IPs to act transparently, but you do need to setup your network configuration so that goproxy is a mandatory stop on the outgoing route. Primarily you can do this by placing the proxy inline. goproxy does not have any WCCP support itself; patches welcome.
|
||||||
|
|
||||||
|
## Why not explicit?
|
||||||
|
|
||||||
|
Transparent proxies are more difficult to maintain and setup from a server side, but they require no configuration on the client(s) which could be in unmanaged systems or systems that don't support a proxy configuration. See the [eavesdropper example](https://github.com/elazarl/goproxy/blob/master/examples/goproxy-eavesdropper/main.go) if you want to see an explicit proxy example.
|
||||||
|
|
||||||
|
## Potential Issues
|
||||||
|
|
||||||
|
Support for very old clients using HTTPS will fail. Clients need to send the SNI value in the TLS ClientHello which most modern clients do these days, but old clients will break.
|
||||||
|
|
||||||
|
If you're routing table allows for it, an explicit http request to goproxy will cause it to fail in an endless loop since it will try to request resources from itself repeatedly. This could be solved in the goproxy code by looking up the hostnames, but it adds a delay that is much easier/faster to handle on the routing side.
|
||||||
|
|
||||||
|
## Routing Rules
|
||||||
|
|
||||||
|
Example routing rules are included in [proxy.sh](https://github.com/elazarl/goproxy/blob/master/examples/goproxy-transparent/proxy.sh) but are best when setup using your distribution's configuration.
|
29
vendor/github.com/elazarl/goproxy/examples/goproxy-transparent/proxy.sh
generated
vendored
Normal file
29
vendor/github.com/elazarl/goproxy/examples/goproxy-transparent/proxy.sh
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# goproxy IP
|
||||||
|
GOPROXY_SERVER="10.10.10.1"
|
||||||
|
# goproxy port
|
||||||
|
GOPROXY_PORT="3129"
|
||||||
|
GOPROXY_PORT_TLS="3128"
|
||||||
|
# DO NOT MODIFY BELOW
|
||||||
|
# Load IPTABLES modules for NAT and IP conntrack support
|
||||||
|
modprobe ip_conntrack
|
||||||
|
modprobe ip_conntrack_ftp
|
||||||
|
echo 1 > /proc/sys/net/ipv4/ip_forward
|
||||||
|
echo 2 > /proc/sys/net/ipv4/conf/all/rp_filter
|
||||||
|
|
||||||
|
# Clean old firewall
|
||||||
|
iptables -t nat -F
|
||||||
|
iptables -t nat -X
|
||||||
|
iptables -t mangle -F
|
||||||
|
iptables -t mangle -X
|
||||||
|
|
||||||
|
# Write new rules
|
||||||
|
iptables -t nat -A PREROUTING -s $GOPROXY_SERVER -p tcp --dport $GOPROXY_PORT -j ACCEPT
|
||||||
|
iptables -t nat -A PREROUTING -s $GOPROXY_SERVER -p tcp --dport $GOPROXY_PORT_TLS -j ACCEPT
|
||||||
|
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination $GOPROXY_SERVER:$GOPROXY_PORT
|
||||||
|
iptables -t nat -A PREROUTING -p tcp --dport 443 -j DNAT --to-destination $GOPROXY_SERVER:$GOPROXY_PORT_TLS
|
||||||
|
# The following line supports using goproxy as an explicit proxy in addition
|
||||||
|
iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination $GOPROXY_SERVER:$GOPROXY_PORT
|
||||||
|
iptables -t nat -A POSTROUTING -j MASQUERADE
|
||||||
|
iptables -t mangle -A PREROUTING -p tcp --dport $GOPROXY_PORT -j DROP
|
||||||
|
iptables -t mangle -A PREROUTING -p tcp --dport $GOPROXY_PORT_TLS -j DROP
|
148
vendor/github.com/elazarl/goproxy/examples/goproxy-transparent/transparent.go
generated
vendored
Normal file
148
vendor/github.com/elazarl/goproxy/examples/goproxy-transparent/transparent.go
generated
vendored
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/elazarl/goproxy"
|
||||||
|
"github.com/inconshreveable/go-vhost"
|
||||||
|
)
|
||||||
|
|
||||||
|
func orPanic(err error) {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
verbose := flag.Bool("v", true, "should every proxy request be logged to stdout")
|
||||||
|
http_addr := flag.String("httpaddr", ":3129", "proxy http listen address")
|
||||||
|
https_addr := flag.String("httpsaddr", ":3128", "proxy https listen address")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
proxy := goproxy.NewProxyHttpServer()
|
||||||
|
proxy.Verbose = *verbose
|
||||||
|
if proxy.Verbose {
|
||||||
|
log.Printf("Server starting up! - configured to listen on http interface %s and https interface %s", *http_addr, *https_addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy.NonproxyHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
if req.Host == "" {
|
||||||
|
fmt.Fprintln(w, "Cannot handle requests without Host header, e.g., HTTP 1.0")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.URL.Scheme = "http"
|
||||||
|
req.URL.Host = req.Host
|
||||||
|
proxy.ServeHTTP(w, req)
|
||||||
|
})
|
||||||
|
proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*$"))).
|
||||||
|
HandleConnect(goproxy.AlwaysMitm)
|
||||||
|
proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*:80$"))).
|
||||||
|
HijackConnect(func(req *http.Request, client net.Conn, ctx *goproxy.ProxyCtx) {
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
ctx.Logf("error connecting to remote: %v", e)
|
||||||
|
client.Write([]byte("HTTP/1.1 500 Cannot reach destination\r\n\r\n"))
|
||||||
|
}
|
||||||
|
client.Close()
|
||||||
|
}()
|
||||||
|
clientBuf := bufio.NewReadWriter(bufio.NewReader(client), bufio.NewWriter(client))
|
||||||
|
remote, err := connectDial(proxy, "tcp", req.URL.Host)
|
||||||
|
orPanic(err)
|
||||||
|
remoteBuf := bufio.NewReadWriter(bufio.NewReader(remote), bufio.NewWriter(remote))
|
||||||
|
for {
|
||||||
|
req, err := http.ReadRequest(clientBuf.Reader)
|
||||||
|
orPanic(err)
|
||||||
|
orPanic(req.Write(remoteBuf))
|
||||||
|
orPanic(remoteBuf.Flush())
|
||||||
|
resp, err := http.ReadResponse(remoteBuf.Reader, req)
|
||||||
|
orPanic(err)
|
||||||
|
orPanic(resp.Write(clientBuf.Writer))
|
||||||
|
orPanic(clientBuf.Flush())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
log.Fatalln(http.ListenAndServe(*http_addr, proxy))
|
||||||
|
}()
|
||||||
|
|
||||||
|
// listen to the TLS ClientHello but make it a CONNECT request instead
|
||||||
|
ln, err := net.Listen("tcp", *https_addr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error listening for https connections - %v", err)
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
c, err := ln.Accept()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error accepting new connection - %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go func(c net.Conn) {
|
||||||
|
tlsConn, err := vhost.TLS(c)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error accepting new connection - %v", err)
|
||||||
|
}
|
||||||
|
if tlsConn.Host() == "" {
|
||||||
|
log.Printf("Cannot support non-SNI enabled clients")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
connectReq := &http.Request{
|
||||||
|
Method: "CONNECT",
|
||||||
|
URL: &url.URL{
|
||||||
|
Opaque: tlsConn.Host(),
|
||||||
|
Host: net.JoinHostPort(tlsConn.Host(), "443"),
|
||||||
|
},
|
||||||
|
Host: tlsConn.Host(),
|
||||||
|
Header: make(http.Header),
|
||||||
|
}
|
||||||
|
resp := dumbResponseWriter{tlsConn}
|
||||||
|
proxy.ServeHTTP(resp, connectReq)
|
||||||
|
}(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// copied/converted from https.go
|
||||||
|
func dial(proxy *goproxy.ProxyHttpServer, network, addr string) (c net.Conn, err error) {
|
||||||
|
if proxy.Tr.Dial != nil {
|
||||||
|
return proxy.Tr.Dial(network, addr)
|
||||||
|
}
|
||||||
|
return net.Dial(network, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// copied/converted from https.go
|
||||||
|
func connectDial(proxy *goproxy.ProxyHttpServer, network, addr string) (c net.Conn, err error) {
|
||||||
|
if proxy.ConnectDial == nil {
|
||||||
|
return dial(proxy, network, addr)
|
||||||
|
}
|
||||||
|
return proxy.ConnectDial(network, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
type dumbResponseWriter struct {
|
||||||
|
net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dumb dumbResponseWriter) Header() http.Header {
|
||||||
|
panic("Header() should not be called on this ResponseWriter")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dumb dumbResponseWriter) Write(buf []byte) (int, error) {
|
||||||
|
if bytes.Equal(buf, []byte("HTTP/1.0 200 OK\r\n\r\n")) {
|
||||||
|
return len(buf), nil // throw away the HTTP OK response from the faux CONNECT request
|
||||||
|
}
|
||||||
|
return dumb.Conn.Write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dumb dumbResponseWriter) WriteHeader(code int) {
|
||||||
|
panic("WriteHeader() should not be called on this ResponseWriter")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dumb dumbResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
|
return dumb, bufio.NewReadWriter(bufio.NewReader(dumb), bufio.NewWriter(dumb)), nil
|
||||||
|
}
|
26
vendor/github.com/elazarl/goproxy/examples/goproxy-upside-down-ternet/main.go
generated
vendored
Normal file
26
vendor/github.com/elazarl/goproxy/examples/goproxy-upside-down-ternet/main.go
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/elazarl/goproxy"
|
||||||
|
"github.com/elazarl/goproxy/ext/image"
|
||||||
|
"image"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
proxy := goproxy.NewProxyHttpServer()
|
||||||
|
proxy.OnResponse().Do(goproxy_image.HandleImage(func(img image.Image, ctx *goproxy.ProxyCtx) image.Image {
|
||||||
|
dx, dy := img.Bounds().Dx(), img.Bounds().Dy()
|
||||||
|
|
||||||
|
nimg := image.NewRGBA(img.Bounds())
|
||||||
|
for i := 0; i < dx; i++ {
|
||||||
|
for j := 0; j <= dy; j++ {
|
||||||
|
nimg.Set(i, j, img.At(i, dy-j-1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nimg
|
||||||
|
}))
|
||||||
|
proxy.Verbose = true
|
||||||
|
log.Fatal(http.ListenAndServe(":8080", proxy))
|
||||||
|
}
|
91
vendor/github.com/elazarl/goproxy/examples/goproxy-yui-minify/yui.go
generated
vendored
Normal file
91
vendor/github.com/elazarl/goproxy/examples/goproxy-yui-minify/yui.go
generated
vendored
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
// This example would minify standalone Javascript files (identified by their content type)
|
||||||
|
// using the command line utility YUI compressor http://yui.github.io/yuicompressor/
|
||||||
|
// Example usage:
|
||||||
|
//
|
||||||
|
// ./yui -java /usr/local/bin/java -yuicompressor ~/Downloads/yuicompressor-2.4.8.jar
|
||||||
|
// $ curl -vx localhost:8080 http://golang.org/lib/godoc/godocs.js
|
||||||
|
// (function(){function g(){var u=$("#search");if(u.length===0){return}function t(){if(....
|
||||||
|
// $ curl http://golang.org/lib/godoc/godocs.js | head -n 3
|
||||||
|
// // Copyright 2012 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/elazarl/goproxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
verbose := flag.Bool("v", false, "should every proxy request be logged to stdout")
|
||||||
|
addr := flag.String("addr", ":8080", "proxy listen address")
|
||||||
|
java := flag.String("javapath", "java", "where the Java executable is located")
|
||||||
|
yuicompressor := flag.String("yuicompressor", "", "where the yuicompressor is located, assumed to be in CWD")
|
||||||
|
yuicompressordir := flag.String("yuicompressordir", ".", "a folder to search yuicompressor in, will be ignored if yuicompressor is set")
|
||||||
|
flag.Parse()
|
||||||
|
if *yuicompressor == "" {
|
||||||
|
files, err := ioutil.ReadDir(*yuicompressordir)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Cannot find yuicompressor jar")
|
||||||
|
}
|
||||||
|
for _, file := range files {
|
||||||
|
if strings.HasPrefix(file.Name(), "yuicompressor") && strings.HasSuffix(file.Name(), ".jar") {
|
||||||
|
c := path.Join(*yuicompressordir, file.Name())
|
||||||
|
yuicompressor = &c
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if *yuicompressor == "" {
|
||||||
|
log.Fatal("Can't find yuicompressor jar, searched yuicompressor*.jar in dir ", *yuicompressordir)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(*yuicompressor); os.IsNotExist(err) {
|
||||||
|
log.Fatal("Can't find yuicompressor jar specified ", *yuicompressor)
|
||||||
|
}
|
||||||
|
proxy := goproxy.NewProxyHttpServer()
|
||||||
|
proxy.Verbose = *verbose
|
||||||
|
proxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {
|
||||||
|
contentType := resp.Header.Get("Content-Type")
|
||||||
|
if contentType == "application/javascript" || contentType == "application/x-javascript" {
|
||||||
|
// in real code, response should be streamed as well
|
||||||
|
var err error
|
||||||
|
cmd := exec.Command(*java, "-jar", *yuicompressor, "--type", "js")
|
||||||
|
cmd.Stdin = resp.Body
|
||||||
|
resp.Body, err = cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
ctx.Warnf("Cannot minify content in %v: %v", ctx.Req.URL, err)
|
||||||
|
return goproxy.TextResponse(ctx.Req, "Error getting stdout pipe")
|
||||||
|
}
|
||||||
|
stderr, err := cmd.StderrPipe()
|
||||||
|
if err != nil {
|
||||||
|
ctx.Logf("Error obtaining stderr from yuicompress: %s", err)
|
||||||
|
return goproxy.TextResponse(ctx.Req, "Error getting stderr pipe")
|
||||||
|
}
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
ctx.Warnf("Cannot minify content in %v: %v", ctx.Req.URL, err)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
defer stderr.Close()
|
||||||
|
const kb = 1024
|
||||||
|
msg, err := ioutil.ReadAll(&io.LimitedReader{stderr, 50 * kb})
|
||||||
|
if len(msg) != 0 {
|
||||||
|
ctx.Logf("Error executing yuicompress: %s", string(msg))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
ctx.Logf("Error reading stderr from yuicompress: %s", string(msg))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
})
|
||||||
|
log.Fatal(http.ListenAndServe(*addr, proxy))
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue