Extract cache package
This commit is contained in:
parent
d05f351f3e
commit
8b12193c46
|
@ -0,0 +1,133 @@
|
|||
package cache
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/atlassian/go-artifactory/pkg/artifactory"
|
||||
"github.com/elazarl/goproxy"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Cache struct {
|
||||
client *artifactory.Client
|
||||
log *log.Logger
|
||||
config ArtifactoryConfig
|
||||
}
|
||||
|
||||
type ArtifactoryConfig struct {
|
||||
URL string
|
||||
Token string
|
||||
Repository string
|
||||
}
|
||||
|
||||
func NewCache(config ArtifactoryConfig, logger *log.Logger) (*Cache, error) {
|
||||
tp := artifactory.TokenAuthTransport{Token: config.Token}
|
||||
|
||||
client, err := artifactory.NewClient(config.URL, tp.Client())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, _, err = client.System.Ping(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cache := Cache{client: client, log: logger, config: config}
|
||||
|
||||
return &cache, nil
|
||||
}
|
||||
|
||||
func (c *Cache) Handle(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
|
||||
url, ok := c.isCached(req)
|
||||
if ok {
|
||||
c.log.Printf("Hitting cache: %s for: %s", url, req.URL.String())
|
||||
req.URL = url
|
||||
} else {
|
||||
c.log.Printf("Caching: %s here: %s", req.URL.String(), url)
|
||||
go c.cache(req, ctx)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (c *Cache) HandleReq(req *http.Request, _ *goproxy.ProxyCtx) bool {
|
||||
if req.Method == http.MethodGet {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *Cache) HandleResp(_ *http.Response, _ *goproxy.ProxyCtx) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Cache) cache(req *http.Request, ctx *goproxy.ProxyCtx) {
|
||||
creq, err := http.NewRequest(req.Method, req.URL.String(), nil)
|
||||
if err != nil {
|
||||
c.log.Printf("Error while creating cache request for %s: %s",
|
||||
req.URL.String(), err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := ctx.RoundTrip(creq)
|
||||
if err != nil {
|
||||
c.log.Printf("Error while performing cache request for %s: %s",
|
||||
req.URL.String(), err)
|
||||
return
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
c.log.Printf("Error while reading cache response body for %s: %s",
|
||||
req.URL.String(), err)
|
||||
return
|
||||
}
|
||||
|
||||
areq, err := c.client.NewRequest(
|
||||
http.MethodPut,
|
||||
c.getCachePath(req),
|
||||
bytes.NewReader(body),
|
||||
)
|
||||
if err != nil {
|
||||
c.log.Printf("Error while building artifactory cache request for %s: %s",
|
||||
req.URL.String(), err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = c.client.Do(context.Background(), areq, nil)
|
||||
if err != nil {
|
||||
c.log.Printf("Error while performing artifactory cache request for %s: %s",
|
||||
req.URL.String(), err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) isCached(req *http.Request) (*url.URL, bool) {
|
||||
url := c.getCacheURL(req)
|
||||
hreq, _ := http.NewRequest(http.MethodHead, url.String(), nil)
|
||||
resp, err := c.client.Do(context.Background(), hreq, nil)
|
||||
if err == nil && resp.StatusCode == http.StatusOK {
|
||||
return url, true
|
||||
}
|
||||
return url, false
|
||||
}
|
||||
|
||||
func (c *Cache) getCacheURL(req *http.Request) *url.URL {
|
||||
url, _ := url.Parse(c.config.URL)
|
||||
url.Path = filepath.Join(url.Path, c.getCachePath(req))
|
||||
return url
|
||||
}
|
||||
|
||||
func (c *Cache) getCachePath(req *http.Request) string {
|
||||
return filepath.Join(c.config.Repository, getId(req).String())
|
||||
}
|
||||
|
||||
func getId(req *http.Request) uuid.UUID {
|
||||
return uuid.NewSHA1(uuid.Nil, []byte(fmt.Sprintf("%s %s", req.Method, req.URL)))
|
||||
}
|
5
go.mod
5
go.mod
|
@ -5,6 +5,7 @@ require (
|
|||
github.com/elazarl/goproxy v0.0.0-20181111060418-2ce16c963a8a
|
||||
github.com/google/go-querystring v1.0.0 // indirect
|
||||
github.com/google/uuid v1.1.0
|
||||
github.com/onsi/ginkgo v1.7.0
|
||||
github.com/onsi/gomega v1.4.3
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||
github.com/onsi/ginkgo v1.7.0 // indirect
|
||||
github.com/onsi/gomega v1.4.3 // indirect
|
||||
)
|
||||
|
|
2
go.sum
2
go.sum
|
@ -12,6 +12,8 @@ github.com/google/uuid v1.1.0 h1:Jf4mxPC/ziBnoPIdpQdPJ9OeiomAUHLvxmPRSPH9m4s=
|
|||
github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
|
|
148
main.go
148
main.go
|
@ -1,22 +1,14 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/atlassian/go-artifactory/pkg/artifactory"
|
||||
"github.com/elazarl/goproxy"
|
||||
"github.com/google/uuid"
|
||||
"github.com/starkandwayne/artifactory-cache-proxy/cache"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -24,142 +16,26 @@ var (
|
|||
logger *log.Logger
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
func handleRequest(req *http.Request, client net.Conn, ctx *goproxy.ProxyCtx) {
|
||||
logger.Println("making request")
|
||||
tp := artifactory.TokenAuthTransport{
|
||||
Token: "AKCp5budTFpbypBqQbGJPz3pGCi28pPivfWczqjfYb9drAmd9LbRZbj6UpKFxJXA8ksWGc9fM",
|
||||
}
|
||||
|
||||
aclient, err := artifactory.NewClient("http://localhost:8081/artifactory", tp.Client())
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
_, _, err = aclient.System.Ping(context.Background())
|
||||
if err != nil {
|
||||
fmt.Printf("\nerror: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("OK")
|
||||
}
|
||||
|
||||
clientBuf := bufio.NewReadWriter(bufio.NewReader(client), bufio.NewWriter(client))
|
||||
|
||||
remote, err := connectDial(proxy, "tcp", req.URL.Host)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
remoteBuf := bufio.NewReadWriter(bufio.NewReader(remote), bufio.NewWriter(remote))
|
||||
|
||||
req, err = http.ReadRequest(clientBuf.Reader)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
err = req.Write(remoteBuf)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = remoteBuf.Flush()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
resp, err := http.ReadResponse(remoteBuf.Reader, req)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// create the pipe and tee reader
|
||||
pr, pw := io.Pipe()
|
||||
tr := io.TeeReader(pr, clientBuf.Writer)
|
||||
|
||||
// create channel to synchronize
|
||||
done := make(chan bool)
|
||||
defer close(done)
|
||||
|
||||
go func() {
|
||||
defer pr.Close()
|
||||
err = resp.Write(pw)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = clientBuf.Flush()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
panic(err)
|
||||
}
|
||||
done <- true
|
||||
}()
|
||||
|
||||
go func() {
|
||||
id := uuid.NewSHA1(uuid.Nil, []byte(fmt.Sprintf("%s %s", req.Method, req.URL)))
|
||||
|
||||
buffreader := bufio.NewReader(tr)
|
||||
foo, err := http.ReadResponse(buffreader, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(foo.Body)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
areq, err := aclient.NewRequest("PUT", filepath.Join("/proxy", id.String()), bytes.NewReader(b))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = aclient.Do(context.Background(), areq, nil)
|
||||
if err != nil {
|
||||
fmt.Println("Foo", err)
|
||||
panic(err)
|
||||
}
|
||||
done <- true
|
||||
}()
|
||||
|
||||
for c := 0; c < 2; c++ {
|
||||
<-done
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func main() {
|
||||
logger = log.New(os.Stdout, "", 0)
|
||||
verbose := flag.Bool("v", false, "should every proxy request be logged to stdout")
|
||||
addr := flag.String("addr", ":8080", "proxy listen address")
|
||||
|
||||
cache, err := cache.NewCache(cache.ArtifactoryConfig{
|
||||
URL: "http://localhost:8081/artifactory",
|
||||
Token: "AKCp5budTFpbypBqQbGJPz3pGCi28pPivfWczqjfYb9drAmd9LbRZbj6UpKFxJXA8ksWGc9fM",
|
||||
Repository: "proxy",
|
||||
}, logger)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
proxy = goproxy.NewProxyHttpServer()
|
||||
proxy.Verbose = *verbose
|
||||
proxy.Logger = logger
|
||||
proxy.OnRequest().HijackConnect(handleRequest)
|
||||
proxy.OnRequest(cache).HandleConnect(goproxy.AlwaysMitm)
|
||||
proxy.OnRequest(cache).Do(cache)
|
||||
|
||||
log.Fatal(http.ListenAndServe(*addr, proxy))
|
||||
}
|
||||
|
|
3
test.sh
3
test.sh
|
@ -3,6 +3,7 @@
|
|||
go run main.go -v &
|
||||
sleep 1
|
||||
|
||||
curl --proxy localhost:8080 --proxytunnel 'http://example.com?foo=bar'
|
||||
# curl --proxy localhost:8080 --proxytunnel 'https://example.com?foo=bar'
|
||||
curl --proxy localhost:8080 'http://example.com?foo=bar'
|
||||
|
||||
kill -9 $(lsof -iTCP:8080 -sTCP:LISTEN | tail -n1 | xargs | cut -d' ' -f2)
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
Copyright @ 2018 Atlassian Pty Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
386
vendor/github.com/atlassian/go-artifactory/pkg/artifactory/artifactory.go
generated
vendored
Normal file
386
vendor/github.com/atlassian/go-artifactory/pkg/artifactory/artifactory.go
generated
vendored
Normal file
|
@ -0,0 +1,386 @@
|
|||
package artifactory
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-querystring/query"
|
||||
"path"
|
||||
)
|
||||
|
||||
const (
|
||||
userAgent = "go-artifactory"
|
||||
|
||||
headerChecksumSha1 = "X-Checksum-Sha1"
|
||||
headerResultDetail = "X-Result-Detail"
|
||||
headerApiToken = "X-JFrog-Art-Api"
|
||||
|
||||
mediaTypePlain = "text/plain"
|
||||
mediaTypeJson = "application/json"
|
||||
mediaTypeXml = "application/xml"
|
||||
mediaTypeLocalRepository = "application/vnd.org.jfrog.artifactory.repositories.LocalRepositoryConfiguration+json"
|
||||
mediaTypeRemoteRepository = "application/vnd.org.jfrog.artifactory.repositories.RemoteRepositoryConfiguration+json"
|
||||
mediaTypeVirtualRepository = "application/vnd.org.jfrog.artifactory.repositories.VirtualRepositoryConfiguration+json"
|
||||
mediaTypeRepositoryDetails = "application/vnd.org.jfrog.artifactory.repositories.RepositoryDetailsList+json"
|
||||
mediaTypeSystemVersion = "application/vnd.org.jfrog.artifactory.system.Version+json"
|
||||
mediaTypeUsers = "application/vnd.org.jfrog.artifactory.security.Users+json"
|
||||
mediaTypeUser = "application/vnd.org.jfrog.artifactory.security.User+json"
|
||||
mediaTypeGroups = "application/vnd.org.jfrog.artifactory.security.Groups+json"
|
||||
mediaTypeGroup = "application/vnd.org.jfrog.artifactory.security.Group+json"
|
||||
mediaTypePermissionTargets = "application/vnd.org.jfrog.artifactory.security.PermissionTargets+json"
|
||||
mediaTypePermissionTarget = "application/vnd.org.jfrog.artifactory.security.PermissionTarget+json"
|
||||
mediaTypeItemPermissions = "application/vnd.org.jfrog.artifactory.storage.ItemPermissions+json"
|
||||
mediaTypeForm = "application/x-www-form-urlencoded"
|
||||
mediaTypeReplicationConfig = "application/vnd.org.jfrog.artifactory.replications.ReplicationConfigRequest+json"
|
||||
)
|
||||
|
||||
// Client is the container for all the api methods
|
||||
type Client struct {
|
||||
client *http.Client // HTTP client used to communicate with the API.
|
||||
|
||||
// Base URL for API requests. BaseURL should always be specified with a trailing slash.
|
||||
BaseURL *url.URL
|
||||
|
||||
// User agent used when communicating with the Artifactory API.
|
||||
UserAgent string
|
||||
|
||||
common service // Reuse a single struct instead of allocating one for each service on the heap.
|
||||
|
||||
// Services used for talking to different parts of the Artifactory API.
|
||||
Repositories *RepositoriesService
|
||||
Security *SecurityService
|
||||
System *SystemService
|
||||
Artifacts *ArtifactService
|
||||
}
|
||||
|
||||
type service struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// NewClient creates a Client from a provided base url for an artifactory instance and a http client
|
||||
func NewClient(baseURL string, httpClient *http.Client) (*Client, error) {
|
||||
if httpClient == nil {
|
||||
httpClient = http.DefaultClient
|
||||
}
|
||||
|
||||
baseEndpoint, err := url.Parse(baseURL)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(baseEndpoint.Path, "/") {
|
||||
baseEndpoint.Path += "/"
|
||||
}
|
||||
|
||||
c := &Client{client: httpClient, BaseURL: baseEndpoint, UserAgent: userAgent}
|
||||
c.common.client = c
|
||||
|
||||
c.Repositories = (*RepositoriesService)(&c.common)
|
||||
c.Security = (*SecurityService)(&c.common)
|
||||
c.System = (*SystemService)(&c.common)
|
||||
c.Artifacts = (*ArtifactService)(&c.common)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// NewRequest creates an API request. A relative URL can be provided in urlStr, in which case it is resolved relative to the BaseURL
|
||||
// of the Client. Relative URLs should always be specified without a preceding slash. If specified, the value pointed to
|
||||
// by body is included as the request body.
|
||||
func (c *Client) NewRequest(method, urlStr string, body io.Reader) (*http.Request, error) {
|
||||
u, err := c.BaseURL.Parse(path.Join(c.BaseURL.Path, urlStr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, u.String(), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.UserAgent != "" {
|
||||
req.Header.Set("User-Agent", c.UserAgent)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewJSONEncodedRequest is a wrapper around client.NewRequest which encodes the body as a JSON object
|
||||
func (c *Client) NewJSONEncodedRequest(method, urlStr string, body interface{}) (*http.Request, error) {
|
||||
var buf io.ReadWriter
|
||||
if body != nil {
|
||||
buf = new(bytes.Buffer)
|
||||
enc := json.NewEncoder(buf)
|
||||
enc.SetEscapeHTML(false)
|
||||
err := enc.Encode(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
req, err := c.NewRequest(method, urlStr, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if body != nil {
|
||||
req.Header.Set("Content-Type", mediaTypeJson)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewURLEncodedRequest is a wrapper around client.NewRequest which encodes the body with URL encoding
|
||||
func (c *Client) NewURLEncodedRequest(method, urlStr string, body interface{}) (*http.Request, error) {
|
||||
var buf io.Reader
|
||||
if body != nil {
|
||||
urlVals, err := query.Values(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = strings.NewReader(urlVals.Encode())
|
||||
}
|
||||
|
||||
req, err := c.NewRequest(method, urlStr, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if body != nil {
|
||||
req.Header.Set("Content-Type", mediaTypeForm)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// Do executes a give request with the given context. If the parameter v is a writer the body will be written to it in
|
||||
// raw format, else v is assumed to be a struct to unmarshal the body into assuming JSON format. If v is nil then the
|
||||
// body is not read and can be manually parsed from the response
|
||||
func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*http.Response, error) {
|
||||
req = req.WithContext(ctx)
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
// If we got an error, and the context has been canceled,
|
||||
// the context's error is probably more useful.
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
if e, ok := err.(*url.Error); ok {
|
||||
if url2, err := url.Parse(e.URL); err == nil {
|
||||
e.URL = url2.String()
|
||||
return nil, e
|
||||
}
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = checkResponse(resp)
|
||||
if err != nil {
|
||||
// even though there was an error, we still return the response
|
||||
// in case the caller wants to inspect it further
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if v != nil {
|
||||
if w, ok := v.(io.Writer); ok {
|
||||
io.Copy(w, resp.Body)
|
||||
} else {
|
||||
err = json.NewDecoder(resp.Body).Decode(v)
|
||||
if err == io.EOF {
|
||||
err = nil // ignore EOF errors caused by empty response body
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func addOptions(s string, opt interface{}) (string, error) {
|
||||
v := reflect.ValueOf(opt)
|
||||
if v.Kind() == reflect.Ptr && v.IsNil() {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
qs, err := query.Values(opt)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
u.RawQuery = qs.Encode()
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
// CheckResponse checks the API response for errors, and returns them if present. A response is considered an error if
|
||||
// it has a status code outside the 200 range. If parsing the response leads to an empty error object, the response will
|
||||
// be returned as plain text
|
||||
func checkResponse(r *http.Response) error {
|
||||
if c := r.StatusCode; 200 <= c && c <= 299 {
|
||||
return nil
|
||||
}
|
||||
errorResponse := &ErrorResponse{Response: r}
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
if err == nil && data != nil {
|
||||
err = json.Unmarshal(data, errorResponse)
|
||||
if err != nil || len(errorResponse.Errors) == 0 {
|
||||
return fmt.Errorf(string(data))
|
||||
}
|
||||
}
|
||||
|
||||
return errorResponse
|
||||
}
|
||||
|
||||
// ErrorResponse reports one or more errors caused by an API request.
|
||||
type ErrorResponse struct {
|
||||
Response *http.Response `json:"-"` // HTTP response that caused this error
|
||||
Errors []Status `json:"errors,omitempty"` // Individual errors
|
||||
}
|
||||
|
||||
// Status is the individual error provided by the API
|
||||
type Status struct {
|
||||
Status int `json:"status"` // Validation error status code
|
||||
Message string `json:"message"` // Message describing the error. Errors with Code == "custom" will always have this set.
|
||||
}
|
||||
|
||||
func (e *Status) Error() string {
|
||||
return fmt.Sprintf("%d error caused by %s", e.Status, e.Message)
|
||||
}
|
||||
|
||||
func (r *ErrorResponse) Error() string {
|
||||
return fmt.Sprintf("%v %v: %d %+v", r.Response.Request.Method, r.Response.Request.URL,
|
||||
r.Response.StatusCode, r.Errors)
|
||||
}
|
||||
|
||||
// ClientProvider exposes a Client method and enforces a standard for the custom transoirts
|
||||
type ClientProvider interface {
|
||||
Client() *http.Client
|
||||
}
|
||||
|
||||
// BasicAuthTransport allows the construction of a HTTP client that authenticates with basic auth
|
||||
// It also adds the correct headers to the request
|
||||
type BasicAuthTransport struct {
|
||||
Username string
|
||||
Password string
|
||||
Transport http.RoundTripper
|
||||
}
|
||||
|
||||
// Client returns a HTTP client and injects the basic auth transport
|
||||
func (t *BasicAuthTransport) Client() *http.Client {
|
||||
return &http.Client{Transport: t}
|
||||
}
|
||||
|
||||
func (t *BasicAuthTransport) transport() http.RoundTripper {
|
||||
if t.Transport != nil {
|
||||
return t.Transport
|
||||
}
|
||||
return http.DefaultTransport
|
||||
}
|
||||
|
||||
// RoundTrip allows us to add headers to every request
|
||||
func (t *BasicAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
// To set extra headers, we must make a copy of the Request so
|
||||
// that we don't modify the Request we were given. This is required by the
|
||||
// specification of http.RoundTripper.
|
||||
//
|
||||
// Since we are going to modify only req.Header here, we only need a deep copy
|
||||
// of req.Header.
|
||||
req2 := new(http.Request)
|
||||
deepCopyRequest(req, req2)
|
||||
|
||||
req2.SetBasicAuth(t.Username, t.Password)
|
||||
req2.Header.Add(headerResultDetail, "info, properties")
|
||||
|
||||
if req.Body != nil {
|
||||
reader, _ := req.GetBody()
|
||||
buf, _ := ioutil.ReadAll(reader)
|
||||
chkSum := getSha1(buf)
|
||||
req.Header.Add(headerChecksumSha1, fmt.Sprintf("%x", chkSum))
|
||||
}
|
||||
|
||||
return t.transport().RoundTrip(req2)
|
||||
}
|
||||
|
||||
// TokenAuthTransport exposes a HTTP client which uses this transport. It authenticates via an Artifactory API token
|
||||
// It also adds the correct headers to the request
|
||||
type TokenAuthTransport struct {
|
||||
Token string
|
||||
Transport http.RoundTripper
|
||||
}
|
||||
|
||||
// Client returns a HTTP client and injects the token auth transport
|
||||
func (t *TokenAuthTransport) Client() *http.Client {
|
||||
return &http.Client{Transport: t}
|
||||
}
|
||||
|
||||
func (t *TokenAuthTransport) transport() http.RoundTripper {
|
||||
if t.Transport != nil {
|
||||
return t.Transport
|
||||
}
|
||||
return http.DefaultTransport
|
||||
}
|
||||
|
||||
// RoundTrip allows us to add headers to every request
|
||||
func (t *TokenAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
// To set extra headers, we must make a copy of the Request so
|
||||
// that we don't modify the Request we were given. This is required by the
|
||||
// specification of http.RoundTripper.
|
||||
//
|
||||
// Since we are going to modify only req.Header here, we only need a deep copy
|
||||
// of req.Header.
|
||||
req2 := new(http.Request)
|
||||
deepCopyRequest(req, req2)
|
||||
|
||||
req2.Header.Set(headerApiToken, t.Token)
|
||||
req2.Header.Add(headerResultDetail, "info, properties")
|
||||
|
||||
if req.Body != nil {
|
||||
reader, _ := req.GetBody()
|
||||
buf, _ := ioutil.ReadAll(reader)
|
||||
chkSum := getSha1(buf)
|
||||
req.Header.Add(headerChecksumSha1, fmt.Sprintf("%x", chkSum))
|
||||
}
|
||||
|
||||
return t.transport().RoundTrip(req2)
|
||||
}
|
||||
|
||||
func getSha1(buf []byte) []byte {
|
||||
h := sha1.New()
|
||||
h.Write(buf)
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func deepCopyRequest(req *http.Request, req2 *http.Request) {
|
||||
*req2 = *req
|
||||
req2.Header = make(http.Header, len(req.Header))
|
||||
for k, s := range req.Header {
|
||||
req2.Header[k] = append([]string(nil), s...)
|
||||
}
|
||||
}
|
||||
|
||||
// Bool is a helper routine that allocates a new bool value
|
||||
// to store v and returns a pointer to it.
|
||||
func Bool(v bool) *bool { return &v }
|
||||
|
||||
// Int is a helper routine that allocates a new int value
|
||||
// to store v and returns a pointer to it.
|
||||
func Int(v int) *int { return &v }
|
||||
|
||||
// Int64 is a helper routine that allocates a new int64 value
|
||||
// to store v and returns a pointer to it.
|
||||
func Int64(v int64) *int64 { return &v }
|
||||
|
||||
// String is a helper routine that allocates a new string value
|
||||
// to store v and returns a pointer to it.
|
||||
func String(v string) *string { return &v }
|
111
vendor/github.com/atlassian/go-artifactory/pkg/artifactory/artifacts.go
generated
vendored
Normal file
111
vendor/github.com/atlassian/go-artifactory/pkg/artifactory/artifacts.go
generated
vendored
Normal file
|
@ -0,0 +1,111 @@
|
|||
package artifactory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// ArtifactService exposes the Artifact API endpoints from Artifactory
|
||||
type ArtifactService service
|
||||
|
||||
// SingleReplicationConfig is the model of the Artifactory Replication Config
|
||||
type SingleReplicationConfig struct {
|
||||
RepoKey *string `json:"repoKey,omitempty"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
SocketTimeoutMillis *int `json:"socketTimeoutMillis,omitempty"`
|
||||
Username *string `json:"username,omitempty"`
|
||||
Password *string `json:"password,omitempty"`
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
SyncDeletes *bool `json:"syncDeletes,omitempty"`
|
||||
SyncProperties *bool `json:"syncProperties,omitempty"`
|
||||
SyncStatistics *bool `json:"syncStatistics,omitempty"`
|
||||
PathPrefix *string `json:"pathPrefix,omitempty"`
|
||||
CronExp *string `json:"cronExp,omitempty"` // Only required when getting list of repositories as C*UD operations will be done through a repConfig obj
|
||||
EnableEventReplication *bool `json:"enableEventReplication,omitempty"`
|
||||
}
|
||||
|
||||
// ReplicationConfig is the model for the multi replication config API endpoints. Its usage is preferred over
|
||||
// SingleReplicationConfig as it is a more direct mapping of the replicationConfig in the UI
|
||||
type ReplicationConfig struct {
|
||||
RepoKey *string `json:"-"`
|
||||
CronExp *string `json:"cronExp,omitempty"`
|
||||
EnableEventReplication *bool `json:"enableEventReplication,omitempty"`
|
||||
Replications *[]SingleReplicationConfig `json:"replications,omitempty"`
|
||||
}
|
||||
|
||||
func (r ReplicationConfig) String() string {
|
||||
res, _ := json.MarshalIndent(r, "", " ")
|
||||
return string(res)
|
||||
}
|
||||
|
||||
// Add or replace replication configuration for given repository key. Supported by local and remote repositories. Accepts the JSON payload returned from Get Repository Replication Configuration for a single and an array of configurations. If the payload is an array of replication configurations, then values for cronExp and enableEventReplication in the first element in the array will determine the corresponding values when setting the repository replication configuration.
|
||||
// Notes: Requires Artifactory Pro
|
||||
// Security: Requires a privileged user
|
||||
func (s *ArtifactService) SetRepositoryReplicationConfig(ctx context.Context, repoKey string, config *ReplicationConfig) (*http.Response, error) {
|
||||
path := fmt.Sprintf("/api/replications/multiple/%s", repoKey)
|
||||
req, err := s.client.NewJSONEncodedRequest("PUT", path, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.Do(ctx, req, nil)
|
||||
}
|
||||
|
||||
// Returns the replication configuration for the given repository key, if found. Supported by local and remote repositories. Note: The 'enableEventReplication' parameter refers to both push and pull replication.
|
||||
// Notes: Requires Artifactory Pro
|
||||
// Security: Requires a privileged user
|
||||
func (s *ArtifactService) GetRepositoryReplicationConfig(ctx context.Context, repoKey string) (*ReplicationConfig, *http.Response, error) {
|
||||
path := fmt.Sprintf("/api/replications/%s", repoKey)
|
||||
req, err := s.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req.Header.Set("Accept", mediaTypeReplicationConfig)
|
||||
|
||||
replications := make([]SingleReplicationConfig, 0)
|
||||
resp, err := s.client.Do(ctx, req, &replications)
|
||||
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
replicationConfig := new(ReplicationConfig)
|
||||
|
||||
if len(replications) > 0 {
|
||||
replicationConfig.Replications = new([]SingleReplicationConfig)
|
||||
}
|
||||
|
||||
for _, replication := range replications {
|
||||
replicationConfig.RepoKey = replication.RepoKey
|
||||
replicationConfig.CronExp = replication.CronExp
|
||||
replicationConfig.EnableEventReplication = replication.EnableEventReplication
|
||||
|
||||
*replicationConfig.Replications = append(*replicationConfig.Replications, replication)
|
||||
}
|
||||
|
||||
return replicationConfig, resp, nil
|
||||
}
|
||||
|
||||
// Update existing replication configuration for given repository key, if found. Supported by local and remote repositories.
|
||||
// Notes: Requires Artifactory Pro
|
||||
// Security: Requires a privileged user
|
||||
func (s *ArtifactService) UpdateRepositoryReplicationConfig(ctx context.Context, repoKey string, config *ReplicationConfig) (*http.Response, error) {
|
||||
path := fmt.Sprintf("/api/replications/multiple/%s", repoKey)
|
||||
req, err := s.client.NewJSONEncodedRequest("POST", path, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.Do(ctx, req, nil)
|
||||
}
|
||||
|
||||
func (s *ArtifactService) DeleteRepositoryReplicationConfig(ctx context.Context, repoKey string) (*http.Response, error) {
|
||||
path := fmt.Sprintf("/api/replications/%s", repoKey)
|
||||
req, err := s.client.NewJSONEncodedRequest("DELETE", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.client.Do(ctx, req, nil)
|
||||
}
|
397
vendor/github.com/atlassian/go-artifactory/pkg/artifactory/repositories.go
generated
vendored
Normal file
397
vendor/github.com/atlassian/go-artifactory/pkg/artifactory/repositories.go
generated
vendored
Normal file
|
@ -0,0 +1,397 @@
|
|||
package artifactory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type RepositoriesService service
|
||||
|
||||
type RepositoryDetails struct {
|
||||
Key *string `json:"key,omitempty"`
|
||||
Type *string `json:"type,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
func (r RepositoryDetails) String() string {
|
||||
res, _ := json.MarshalIndent(r, "", " ")
|
||||
return string(res)
|
||||
}
|
||||
|
||||
type RepositoryListOptions struct {
|
||||
// Type of repositories to list.
|
||||
// Can be one of local|remote|virtual|distribution. Default: all
|
||||
Type string `url:"type,omitempty"`
|
||||
}
|
||||
|
||||
// Returns a list of minimal repository details for all repositories of the specified type.
|
||||
// Since: 2.2.0
|
||||
// Security: Requires a privileged user (can be anonymous)
|
||||
func (s *RepositoriesService) ListRepositories(ctx context.Context, opt *RepositoryListOptions) (*[]RepositoryDetails, *http.Response, error) {
|
||||
path := "/api/repositories/"
|
||||
req, err := s.client.NewJSONEncodedRequest("GET", path, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req.Header.Set("Accept", mediaTypeRepositoryDetails)
|
||||
|
||||
repos := new([]RepositoryDetails)
|
||||
resp, err := s.client.Do(ctx, req, &repos)
|
||||
return repos, resp, err
|
||||
}
|
||||
|
||||
// application/vnd.org.jfrog.artifactory.repositories.LocalRepositoryConfiguration+json
|
||||
type LocalRepository struct {
|
||||
Key *string `json:"key,omitempty"`
|
||||
RClass *string `json:"rclass,omitempty"` // Mandatory element in create/replace queries (optional in "update" queries)
|
||||
PackageType *string `json:"packageType,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Notes *string `json:"notes,omitempty"`
|
||||
IncludesPattern *string `json:"includesPattern,omitempty"`
|
||||
ExcludesPattern *string `json:"excludesPattern,omitempty"`
|
||||
ArchiveBrowsingEnabled *bool `json:"archiveBrowsingEnabled,omitempty"`
|
||||
BlackedOut *bool `json:"blackedOut,omitempty"`
|
||||
BlockXrayUnscannedArtifacts *bool `json:"blockXrayUnscannedArtifacts,omitempty"`
|
||||
CalculateYumMetadata *bool `json:"calculateYumMetadata,omitempty"`
|
||||
ChecksumPolicyType *string `json:"checksumPolicyType,omitempty"`
|
||||
DebianTrivialLayout *bool `json:"debianTrivialLayout,omitempty"`
|
||||
DockerApiVersion *string `json:"dockerApiVersion,omitempty"`
|
||||
EnableBowerSupport *bool `json:"enableBowerSupport,omitempty"`
|
||||
EnableCocoaPodsSupport *bool `json:"enableCocoaPodsSupport,omitempty"`
|
||||
EnableComposerSupport *bool `json:"enableComposerSupport,omitempty"`
|
||||
EnableConanSupport *bool `json:"enableConanSupport,omitempty"`
|
||||
EnableDebianSupport *bool `json:"enableDebianSupport,omitempty"`
|
||||
EnableDistRepoSupport *bool `json:"enableDistRepoSupport,omitempty"`
|
||||
EnableDockerSupport *bool `json:"enableDockerSupport,omitempty"`
|
||||
EnableFileListsIndexing *bool `json:"enableFileListsIndexing,omitempty"`
|
||||
EnableGemsSupport *bool `json:"enableGemsSupport,omitempty"`
|
||||
EnableGitLfsSupport *bool `json:"enableGitLfsSupport,omitempty"`
|
||||
EnableNpmSupport *bool `json:"enableNpmSupport,omitempty"`
|
||||
EnableNuGetSupport *bool `json:"enableNuGetSupport,omitempty"`
|
||||
EnablePuppetSupport *bool `json:"enablePuppetSupport,omitempty"`
|
||||
EnablePypiSupport *bool `json:"enablePypiSupport,omitempty"`
|
||||
EnableVagrantSupport *bool `json:"enableVagrantSupport,omitempty"`
|
||||
EnabledChefSupport *bool `json:"enabledChefSupport,omitempty"`
|
||||
ForceNugetAuthentication *bool `json:"forceNugetAuthentication,omitempty"`
|
||||
HandleReleases *bool `json:"handleReleases,omitempty"`
|
||||
HandleSnapshots *bool `json:"handleSnapshots,omitempty"`
|
||||
MaxUniqueSnapshots *int `json:"maxUniqueSnapshots,omitempty"`
|
||||
MaxUniqueTags *int `json:"maxUniqueTags,omitempty"`
|
||||
PropertySets *[]string `json:"propertySets,omitempty"`
|
||||
RepoLayoutRef *string `json:"repoLayoutRef,omitempty"`
|
||||
SnapshotVersionBehavior *string `json:"snapshotVersionBehavior,omitempty"`
|
||||
SuppressPomConsistencyChecks *bool `json:"suppressPomConsistencyChecks,omitempty"`
|
||||
XrayIndex *bool `json:"xrayIndex,omitempty"`
|
||||
XrayMinimumBlockedSeverity *string `json:"xrayMinimumBlockedSeverity,omitempty"`
|
||||
YumRootDepth *int `json:"yumRootDepth,omitempty"`
|
||||
}
|
||||
|
||||
func (r LocalRepository) String() string {
|
||||
res, _ := json.MarshalIndent(r, "", " ")
|
||||
return string(res)
|
||||
}
|
||||
|
||||
// Creates a new repository in Artifactory with the provided configuration.
|
||||
// Since: 2.3.0
|
||||
// Notes: Requires Artifactory Pro
|
||||
// An existing repository with the same key are removed from the configuration and its content is removed!
|
||||
// Missing values are set to the default values as defined by the consumed type spec.
|
||||
// Security: Requires an admin user
|
||||
func (s *RepositoriesService) CreateLocal(ctx context.Context, repo *LocalRepository) (*http.Response, error) {
|
||||
return s.create(ctx, *repo.Key, repo)
|
||||
}
|
||||
|
||||
// Retrieves the current configuration of a repository.
|
||||
// Since: 2.3.0
|
||||
// Notes: Requires Artifactory Pro
|
||||
// Security: Requires an admin user for complete repository configuration. Non-admin users will receive only partial configuration data.
|
||||
func (s *RepositoriesService) GetLocal(ctx context.Context, repo string) (*LocalRepository, *http.Response, error) {
|
||||
repository, resp, err := s.get(ctx, repo, new(LocalRepository))
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return repository.(*LocalRepository), resp, nil
|
||||
}
|
||||
|
||||
// Updates an exiting repository configuration in Artifactory with the provided configuration elements.
|
||||
// Since: 2.3.0
|
||||
// Notes: Requires Artifactory Pro
|
||||
// The class of a repository (the rclass attribute cannot be updated.
|
||||
// Security: Requires an admin user
|
||||
func (s *RepositoriesService) UpdateLocal(ctx context.Context, repo string, repository *LocalRepository) (*http.Response, error) {
|
||||
return s.update(ctx, repo, repository)
|
||||
}
|
||||
|
||||
// Removes a repository configuration together with the whole repository content.
|
||||
// Since: 2.3.0
|
||||
// Notes: Requires Artifactory Pro
|
||||
// Security: Requires an admin user
|
||||
func (s *RepositoriesService) DeleteLocal(ctx context.Context, repo string) (*http.Response, error) {
|
||||
return s.delete(ctx, repo)
|
||||
}
|
||||
|
||||
type Statistics struct {
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
}
|
||||
|
||||
type Properties struct {
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
}
|
||||
|
||||
type Source struct {
|
||||
OriginAbsenceDetection *bool `json:"originAbsenceDetection,omitempty"`
|
||||
}
|
||||
|
||||
type ContentSynchronisation struct {
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
Statistics *Statistics `json:"statistics,omitempty"`
|
||||
Properties *Properties `json:"properties,omitempty"`
|
||||
Source *Source `json:"source,omitempty"`
|
||||
}
|
||||
|
||||
type RemoteRepository struct {
|
||||
Key *string `json:"key,omitempty"`
|
||||
RClass *string `json:"rclass,omitempty"` // Mandatory element in create/replace queries (optional in "update" queries)
|
||||
PackageType *string `json:"packageType,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Notes *string `json:"notes,omitempty"`
|
||||
IncludesPattern *string `json:"includesPattern,omitempty"`
|
||||
ExcludesPattern *string `json:"excludesPattern,omitempty"`
|
||||
AllowAnyHostAuth *bool `json:"allowAnyHostAuth,omitempty"`
|
||||
ArchiveBrowsingEnabled *bool `json:"archiveBrowsingEnabled,omitempty"`
|
||||
AssumedOfflinePeriodSecs *int `json:"assumedOfflinePeriodSecs,omitempty"`
|
||||
BlackedOut *bool `json:"blackedOut,omitempty"`
|
||||
BlockMismatchingMimeTypes *bool `json:"blockMismatchingMimeTypes,omitempty"`
|
||||
BlockXrayUnscannedArtifacts *bool `json:"blockXrayUnscannedArtifacts,omitempty"`
|
||||
BypassHeadRequests *bool `json:"bypassHeadRequests,omitempty"`
|
||||
ContentSynchronisation *ContentSynchronisation `json:"contentSynchronisation,omitempty"`
|
||||
DebianTrivialLayout *bool `json:"debianTrivialLayout,omitempty"`
|
||||
DockerApiVersion *string `json:"dockerApiVersion,omitempty"`
|
||||
EnableBowerSupport *bool `json:"enableBowerSupport,omitempty"`
|
||||
EnableCocoaPodsSupport *bool `json:"enableCocoaPodsSupport,omitempty"`
|
||||
EnableConanSupport *bool `json:"enableConanSupport,omitempty"`
|
||||
EnableCookieManagement *bool `json:"enableCookieManagement,omitempty"`
|
||||
EnabledChefSupport *bool `json:"enabledChefSupport,omitempty"`
|
||||
EnableComposerSupport *bool `json:"enableComposerSupport,omitempty"`
|
||||
EnableDebianSupport *bool `json:"enableDebianSupport,omitempty"`
|
||||
EnableDistRepoSupport *bool `json:"enableDistRepoSupport,omitempty"`
|
||||
EnableDockerSupport *bool `json:"enableDockerSupport,omitempty"`
|
||||
EnableGemsSupport *bool `json:"enableGemsSupport,omitempty"`
|
||||
EnableGitLfsSupport *bool `json:"enableGitLfsSupport,omitempty"`
|
||||
EnableNpmSupport *bool `json:"enableNpmSupport,omitempty"`
|
||||
EnableNuGetSupport *bool `json:"enableNuGetSupport,omitempty"`
|
||||
EnablePuppetSupport *bool `json:"enablePuppetSupport,omitempty"`
|
||||
EnablePypiSupport *bool `json:"enablePypiSupport,omitempty"`
|
||||
EnableTokenAuthentication *bool `json:"enableTokenAuthentication,omitempty"`
|
||||
EnableVagrantSupport *bool `json:"enableVagrantSupport,omitempty"`
|
||||
FailedRetrievalCachePeriodSecs *int `json:"failedRetrievalCachePeriodSecs,omitempty"`
|
||||
FetchJarsEagerly *bool `json:"fetchJarsEagerly,omitempty"`
|
||||
FetchSourcesEagerly *bool `json:"fetchSourcesEagerly,omitempty"`
|
||||
ForceNugetAuthentication *bool `json:"forceNugetAuthentication,omitempty"`
|
||||
HandleReleases *bool `json:"handleReleases,omitempty"`
|
||||
HandleSnapshots *bool `json:"handleSnapshots,omitempty"`
|
||||
HardFail *bool `json:"hardFail,omitempty"`
|
||||
ListRemoteFolderItems *bool `json:"listRemoteFolderItems,omitempty"`
|
||||
LocalAddress *string `json:"localAddress,omitempty"`
|
||||
MaxUniqueSnapshots *int `json:"maxUniqueSnapshots,omitempty"`
|
||||
MaxUniqueTags *int `json:"maxUniqueTags,omitempty"`
|
||||
MismatchingMimeTypesOverrideList *string `json:"mismatchingMimeTypesOverrideList,omitempty"`
|
||||
MissedRetrievalCachePeriodSecs *int `json:"missedRetrievalCachePeriodSecs,omitempty"`
|
||||
Offline *bool `json:"offline,omitempty"`
|
||||
Password *string `json:"password,omitempty"`
|
||||
PropagateQueryParams *bool `json:"propagateQueryParams,omitempty"`
|
||||
PropertySets *[]string `json:"propertySets,omitempty"`
|
||||
Proxy *string `json:"proxy,omitempty"`
|
||||
RejectInvalidJars *bool `json:"rejectInvalidJars,omitempty"`
|
||||
RemoteRepoChecksumPolicyType *string `json:"remoteRepoChecksumPolicyType,omitempty"`
|
||||
RepoLayoutRef *string `json:"repoLayoutRef,omitempty"`
|
||||
RetrievalCachePeriodSecs *int `json:"retrievalCachePeriodSecs,omitempty"`
|
||||
ShareConfiguration *bool `json:"shareConfiguration,omitempty"`
|
||||
SocketTimeoutMillis *int `json:"socketTimeoutMillis,omitempty"`
|
||||
StoreArtifactsLocally *bool `json:"storeArtifactsLocally,omitempty"`
|
||||
SuppressPomConsistencyChecks *bool `json:"suppressPomConsistencyChecks,omitempty"`
|
||||
SynchronizeProperties *bool `json:"synchronizeProperties,omitempty"`
|
||||
UnusedArtifactsCleanupEnabled *bool `json:"unusedArtifactsCleanupEnabled,omitempty"`
|
||||
UnusedArtifactsCleanupPeriodHours *int `json:"unusedArtifactsCleanupPeriodHours,omitempty"`
|
||||
Url *string `json:"url,omitempty"`
|
||||
Username *string `json:"username,omitempty"` // Mandatory element in create/replace queries (optional in "update" queries)
|
||||
XrayIndex *bool `json:"xrayIndex,omitempty"`
|
||||
XrayMinimumBlockedSeverity *string `json:"xrayMinimumBlockedSeverity,omitempty"`
|
||||
BowerRegistryURL *string `json:"bowerRegistryUrl,omitempty"`
|
||||
VcsType *string `json:"vcsType,omitempty"`
|
||||
VcsGitProvider *string `json:"vcsGitProvider,omitempty"`
|
||||
VcsGitDownloadUrl *string `json:"vcsGitDownloadUrl,omitempty"`
|
||||
ClientTLSCertificate *string `json:"clientTlsCertificate,omitempty"`
|
||||
PyPiRegistryUrl *string `json:"pyPiRegistryUrl,omitempty"`
|
||||
}
|
||||
|
||||
func (r RemoteRepository) String() string {
|
||||
res, _ := json.MarshalIndent(r, "", " ")
|
||||
return string(res)
|
||||
}
|
||||
|
||||
// Creates a new repository in Artifactory with the provided configuration.
|
||||
// Since: 2.3.0
|
||||
// Notes: Requires Artifactory Pro
|
||||
// An existing repository with the same key are removed from the configuration and its content is removed!
|
||||
// Missing values are set to the default values as defined by the consumed type spec.
|
||||
// Security: Requires an admin user
|
||||
func (s *RepositoriesService) CreateRemote(ctx context.Context, repo *RemoteRepository) (*http.Response, error) {
|
||||
return s.create(ctx, *repo.Key, repo)
|
||||
}
|
||||
|
||||
// Retrieves the current configuration of a repository.
|
||||
// Since: 2.3.0
|
||||
// Notes: Requires Artifactory Pro
|
||||
// Security: Requires an admin user for complete repository configuration. Non-admin users will receive only partial configuration data.
|
||||
func (s *RepositoriesService) GetRemote(ctx context.Context, repo string) (*RemoteRepository, *http.Response, error) {
|
||||
repository, resp, err := s.get(ctx, repo, new(RemoteRepository))
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return repository.(*RemoteRepository), resp, nil
|
||||
}
|
||||
|
||||
// Updates an exiting repository configuration in Artifactory with the provided configuration elements.
|
||||
// Since: 2.3.0
|
||||
// Notes: Requires Artifactory Pro
|
||||
// The class of a repository (the rclass attribute cannot be updated.
|
||||
// Security: Requires an admin user
|
||||
func (s *RepositoriesService) UpdateRemote(ctx context.Context, repo string, repository *RemoteRepository) (*http.Response, error) {
|
||||
return s.update(ctx, repo, repository)
|
||||
}
|
||||
|
||||
// Removes a repository configuration together with the whole repository content.
|
||||
// Since: 2.3.0
|
||||
// Notes: Requires Artifactory Pro
|
||||
// Security: Requires an admin user
|
||||
func (s *RepositoriesService) DeleteRemote(ctx context.Context, repo string) (*http.Response, error) {
|
||||
return s.delete(ctx, repo)
|
||||
}
|
||||
|
||||
type VirtualRepository struct {
|
||||
Key *string `json:"key,omitempty"`
|
||||
RClass *string `json:"rclass,omitempty"` // Mandatory element in create/replace queries (optional in "update" queries)
|
||||
PackageType *string `json:"packageType,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Notes *string `json:"notes,omitempty"`
|
||||
IncludesPattern *string `json:"includesPattern,omitempty"`
|
||||
ExcludesPattern *string `json:"excludesPattern,omitempty"`
|
||||
ArtifactoryRequestsCanRetrieveRemoteArtifacts *bool `json:"artifactoryRequestsCanRetrieveRemoteArtifacts,omitempty"`
|
||||
DebianTrivialLayout *bool `json:"debianTrivialLayout,omitempty"`
|
||||
DefaultDeploymentRepo *string `json:"defaultDeploymentRepo,omitempty"`
|
||||
DockerApiVersion *string `json:"dockerApiVersion,omitempty"`
|
||||
EnableBowerSupport *bool `json:"enableBowerSupport,omitempty"`
|
||||
EnableCocoaPodsSupport *bool `json:"enableCocoaPodsSupport,omitempty"`
|
||||
EnableConanSupport *bool `json:"enableConanSupport,omitempty"`
|
||||
EnableComposerSupport *bool `json:"enableComposerSupport,omitempty"`
|
||||
EnabledChefSupport *bool `json:"enabledChefSupport,omitempty"`
|
||||
EnableDebianSupport *bool `json:"enableDebianSupport,omitempty"`
|
||||
EnableDistRepoSupport *bool `json:"enableDistRepoSupport,omitempty"`
|
||||
EnableDockerSupport *bool `json:"enableDockerSupport,omitempty"`
|
||||
EnableGemsSupport *bool `json:"enableGemsSupport,omitempty"`
|
||||
EnableGitLfsSupport *bool `json:"enableGitLfsSupport,omitempty"`
|
||||
EnableNpmSupport *bool `json:"enableNpmSupport,omitempty"`
|
||||
EnableNuGetSupport *bool `json:"enableNuGetSupport,omitempty"`
|
||||
EnablePuppetSupport *bool `json:"enablePuppetSupport,omitempty"`
|
||||
EnablePypiSupport *bool `json:"enablePypiSupport,omitempty"`
|
||||
EnableVagrantSupport *bool `json:"enableVagrantSupport,omitempty"`
|
||||
ExternalDependenciesEnabled *bool `json:"externalDependenciesEnabled,omitempty"`
|
||||
ForceNugetAuthentication *bool `json:"forceNugetAuthentication,omitempty"`
|
||||
KeyPair *string `json:"keyPair,omitempty"`
|
||||
PomRepositoryReferencesCleanupPolicy *string `json:"pomRepositoryReferencesCleanupPolicy,omitempty"`
|
||||
Repositories *[]string `json:"repositories,omitempty"`
|
||||
VirtualRetrievalCachePeriodSecs *int `json:"virtualRetrievalCachePeriodSecs,omitempty"`
|
||||
}
|
||||
|
||||
func (r VirtualRepository) String() string {
|
||||
res, _ := json.MarshalIndent(r, "", " ")
|
||||
return string(res)
|
||||
}
|
||||
|
||||
// Creates a new repository in Artifactory with the provided configuration.
|
||||
// Since: 2.3.0
|
||||
// Notes: Requires Artifactory Pro
|
||||
// An existing repository with the same key are removed from the configuration and its content is removed!
|
||||
// Missing values are set to the default values as defined by the consumed type spec.
|
||||
// Security: Requires an admin user
|
||||
func (s *RepositoriesService) CreateVirtual(ctx context.Context, repo *VirtualRepository) (*http.Response, error) {
|
||||
return s.create(ctx, *repo.Key, repo)
|
||||
}
|
||||
|
||||
// Retrieves the current configuration of a repository.
|
||||
// Since: 2.3.0
|
||||
// Notes: Requires Artifactory Pro
|
||||
// Security: Requires an admin user for complete repository configuration. Non-admin users will receive only partial configuration data.
|
||||
func (s *RepositoriesService) GetVirtual(ctx context.Context, repo string) (*VirtualRepository, *http.Response, error) {
|
||||
repository, resp, err := s.get(ctx, repo, new(VirtualRepository))
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return repository.(*VirtualRepository), resp, nil
|
||||
}
|
||||
|
||||
// Updates an exiting repository configuration in Artifactory with the provided configuration elements.
|
||||
// Since: 2.3.0
|
||||
// Notes: Requires Artifactory Pro
|
||||
// The class of a repository (the rclass attribute cannot be updated.
|
||||
// Security: Requires an admin user
|
||||
func (s *RepositoriesService) UpdateVirtual(ctx context.Context, repo string, repository *VirtualRepository) (*http.Response, error) {
|
||||
return s.update(ctx, repo, repository)
|
||||
}
|
||||
|
||||
// Removes a repository configuration together with the whole repository content.
|
||||
// Since: 2.3.0
|
||||
// Notes: Requires Artifactory Pro
|
||||
// Security: Requires an admin user
|
||||
func (s *RepositoriesService) DeleteVirtual(ctx context.Context, repo string) (*http.Response, error) {
|
||||
return s.delete(ctx, repo)
|
||||
}
|
||||
|
||||
// Generic repo CRUD operations
|
||||
func (s *RepositoriesService) create(ctx context.Context, repo string, v interface{}) (*http.Response, error) {
|
||||
path := fmt.Sprintf("/api/repositories/%s", repo)
|
||||
req, err := s.client.NewJSONEncodedRequest("PUT", path, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.Do(ctx, req, nil)
|
||||
}
|
||||
|
||||
func (s *RepositoriesService) get(ctx context.Context, repo string, v interface{}) (interface{}, *http.Response, error) {
|
||||
path := fmt.Sprintf("/api/repositories/%v", repo)
|
||||
req, err := s.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
acceptHeaders := []string{mediaTypeLocalRepository, mediaTypeVirtualRepository, mediaTypeRemoteRepository}
|
||||
req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
|
||||
|
||||
resp, err := s.client.Do(ctx, req, v)
|
||||
return v, resp, err
|
||||
}
|
||||
|
||||
func (s *RepositoriesService) update(ctx context.Context, repo string, v interface{}) (*http.Response, error) {
|
||||
path := fmt.Sprintf("/api/repositories/%v", repo)
|
||||
req, err := s.client.NewJSONEncodedRequest("POST", path, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.Do(ctx, req, nil)
|
||||
}
|
||||
|
||||
func (s *RepositoriesService) delete(ctx context.Context, repo string) (*http.Response, error) {
|
||||
path := fmt.Sprintf("/api/repositories/%v", repo)
|
||||
req, err := s.client.NewJSONEncodedRequest("DELETE", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.client.Do(ctx, req, nil)
|
||||
}
|
1065
vendor/github.com/atlassian/go-artifactory/pkg/artifactory/security.go
generated
vendored
Normal file
1065
vendor/github.com/atlassian/go-artifactory/pkg/artifactory/security.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
328
vendor/github.com/atlassian/go-artifactory/pkg/artifactory/system.go
generated
vendored
Normal file
328
vendor/github.com/atlassian/go-artifactory/pkg/artifactory/system.go
generated
vendored
Normal file
|
@ -0,0 +1,328 @@
|
|||
package artifactory
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type SystemService service
|
||||
|
||||
// System Info
|
||||
// Get general system information.
|
||||
// Since: 2.2.0
|
||||
// Security: Requires a valid admin user
|
||||
func (s *SystemService) GetSystemInfo(ctx context.Context) (*string, *http.Response, error) {
|
||||
path := "/api/system"
|
||||
req, err := s.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req.Header.Set("Accept", mediaTypePlain)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
resp, err := s.client.Do(ctx, req, buf)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return String(buf.String()), resp, nil
|
||||
}
|
||||
|
||||
// Get a simple status response about the state of Artifactory
|
||||
// Returns 200 code with an 'OK' text if Artifactory is working properly, if not will return an HTTP error code with a reason.
|
||||
// Since: 2.3.0
|
||||
// Security: Requires a valid user (can be anonymous). If artifactory.ping.allowUnauthenticated=true is set in
|
||||
// artifactory.system.properties, then no authentication is required even if anonymous access is disabled.
|
||||
func (s *SystemService) Ping(ctx context.Context) (*string, *http.Response, error) {
|
||||
path := "/api/system/ping"
|
||||
req, err := s.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req.Header.Set("Accept", mediaTypePlain)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
resp, err := s.client.Do(ctx, req, buf)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return String(buf.String()), resp, nil
|
||||
}
|
||||
|
||||
type VerifyConnectionOptions struct {
|
||||
Endpoint *string `json:"endpoint,omitempty"` // Mandatory
|
||||
Username *string `json:"username,omitempty"` // Optional
|
||||
Password *string `json:"password,omitempty"` // Optional
|
||||
}
|
||||
|
||||
// Verifies a two-way connection between Artifactory and another product
|
||||
// Returns Success (200) if Artifactory receives a similar success code (200) from the provided endpoint.
|
||||
// Upon error, returns 400 along with a JSON object that contains the error returned from the other system.
|
||||
// Since: 4.15.0
|
||||
// Security: Requires an admin user.
|
||||
func (s *SystemService) VerifyConnection(ctx context.Context, opt *VerifyConnectionOptions) (*string, *http.Response, error) {
|
||||
url := "/api/system/verifyconnection"
|
||||
|
||||
req, err := s.client.NewJSONEncodedRequest("POST", url, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
resp, err := s.client.Do(ctx, req, buf)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return String(buf.String()), resp, nil
|
||||
}
|
||||
|
||||
// Get the general configuration (artifactory.config.xml).
|
||||
// Since: 2.2.0
|
||||
// Security: Requires a valid admin user
|
||||
func (s *SystemService) GetConfiguration(ctx context.Context) (*string, *http.Response, error) {
|
||||
path := "/api/system/configuration"
|
||||
req, err := s.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req.Header.Set("Accept", mediaTypeXml)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
resp, err := s.client.Do(ctx, req, buf)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return String(buf.String()), resp, nil
|
||||
}
|
||||
|
||||
// Changes the Custom URL base
|
||||
// Since: 3.9.0
|
||||
// Security: Requires a valid admin user
|
||||
func (s *SystemService) UpdateUrlBase(ctx context.Context, newUrl string) (*http.Response, error) {
|
||||
path := "/api/system/configuration/baseUrl"
|
||||
req, err := s.client.NewJSONEncodedRequest("PUT", path, newUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-type", mediaTypePlain)
|
||||
|
||||
return s.client.Do(ctx, req, nil)
|
||||
}
|
||||
|
||||
type LicenseDetails struct {
|
||||
Type *string `json:"type,omitempty"`
|
||||
ValidThrough *string `json:"validThrough,omitempty"`
|
||||
LicensedTo *string `json:"licensedTo,omitempty"`
|
||||
}
|
||||
|
||||
func (r LicenseDetails) String() string {
|
||||
res, _ := json.MarshalIndent(r, "", " ")
|
||||
return string(res)
|
||||
}
|
||||
|
||||
// Retrieve information about the currently installed license.
|
||||
// Since: 3.3.0
|
||||
// Security: Requires a valid admin user
|
||||
func (s *SystemService) GetLicense(ctx context.Context) (*LicenseDetails, *http.Response, error) {
|
||||
path := "/api/system/license"
|
||||
req, err := s.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req.Header.Set("Accept", mediaTypeJson)
|
||||
|
||||
v := new(LicenseDetails)
|
||||
resp, err := s.client.Do(ctx, req, v)
|
||||
return v, resp, err
|
||||
}
|
||||
|
||||
type LicenseKey struct {
|
||||
LicenseKey *string `json:"licenseKey,omitempty"`
|
||||
}
|
||||
|
||||
// Install new license key or change the current one.
|
||||
// Since: 3.3.0
|
||||
// Security: Requires a valid admin user
|
||||
func (s *SystemService) InstallLicense(ctx context.Context, licenseKey *LicenseKey) (*Status, *http.Response, error) {
|
||||
path := "/api/system/licenses"
|
||||
req, err := s.client.NewJSONEncodedRequest("POST", path, licenseKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req.Header.Set("Accept", mediaTypeJson)
|
||||
|
||||
v := new(Status)
|
||||
resp, err := s.client.Do(ctx, req, v)
|
||||
return v, resp, err
|
||||
}
|
||||
|
||||
type HALicense struct {
|
||||
Type *string `json:"type,omitempty"`
|
||||
ValidThrough *string `json:"validThrough,omitempty"` // validity date formatted MMM DD, YYYY
|
||||
LicensedTo *string `json:"licensedTo,omitempty"`
|
||||
LicenseHash *string `json:"licenseHash,omitempty"`
|
||||
NodeId *string `json:"nodeId,omitempty"` // Node ID of the node activated with this license | Not in use
|
||||
NodeUrl *string `json:"nodeUrl,omitempty"` // URL of the node activated with this license | Not in use
|
||||
Expired *bool `json:"expired,omitempty"`
|
||||
}
|
||||
|
||||
type HALicenses struct {
|
||||
Licenses *[]HALicense `json:"licenses,omitempty"`
|
||||
}
|
||||
|
||||
func (r HALicenses) String() string {
|
||||
res, _ := json.MarshalIndent(r, "", " ")
|
||||
return string(res)
|
||||
}
|
||||
|
||||
// Retrieve information about the currently installed licenses in an HA cluster.
|
||||
// Since: 5.0.0
|
||||
// Security: Requires a valid admin user
|
||||
func (s *SystemService) ListHALicenses(ctx context.Context) (*HALicenses, *http.Response, error) {
|
||||
path := "/api/system/licenses"
|
||||
req, err := s.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req.Header.Set("Accept", mediaTypeJson)
|
||||
|
||||
v := new(HALicenses)
|
||||
resp, err := s.client.Do(ctx, req, v)
|
||||
return v, resp, err
|
||||
}
|
||||
|
||||
// Install a new license key(s) on an HA cluster.
|
||||
// Since: 5.0.0
|
||||
// Security: Requires an admin user
|
||||
func (s *SystemService) InstallHALicenses(ctx context.Context, licenses []LicenseKey) (*Status, *http.Response, error) {
|
||||
path := "/api/system/licenses"
|
||||
req, err := s.client.NewJSONEncodedRequest("POST", path, licenses)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req.Header.Set("Accept", mediaTypeJson)
|
||||
|
||||
v := new(Status)
|
||||
resp, err := s.client.Do(ctx, req, v)
|
||||
return v, resp, err
|
||||
}
|
||||
|
||||
type HALicenseHashes struct {
|
||||
LicenseHash *[]string `url:"licenseHash,omitempty"`
|
||||
}
|
||||
|
||||
// Deletes a license key from an HA cluster.
|
||||
// Since: 5.0.0
|
||||
// Security: Requires an admin user
|
||||
func (s *SystemService) DeleteHALicenses(ctx context.Context, licenseHashes HALicenseHashes) (*Status, *http.Response, error) {
|
||||
path, err := addOptions("/api/system/licenses", licenseHashes)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req, err := s.client.NewRequest("DELETE", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req.Header.Set("Accept", mediaTypeJson)
|
||||
|
||||
v := new(Status)
|
||||
resp, err := s.client.Do(ctx, req, v)
|
||||
return v, resp, err
|
||||
}
|
||||
|
||||
type VersionAddOns struct {
|
||||
Version *string `json:"version,omitempty"`
|
||||
Revision *string `json:"revision,omitempty"`
|
||||
Addons *[]string `json:"addons,omitempty"`
|
||||
}
|
||||
|
||||
func (r VersionAddOns) String() string {
|
||||
res, _ := json.MarshalIndent(r, "", " ")
|
||||
return string(res)
|
||||
}
|
||||
|
||||
// Retrieve information about the current Artifactory version, revision, and currently installed Add-ons
|
||||
// Since: 2.2.2
|
||||
// Security: Requires a valid user (can be anonymous)
|
||||
func (s *SystemService) GetVersionAndAddons(ctx context.Context) (*VersionAddOns, *http.Response, error) {
|
||||
path := "/api/system/version"
|
||||
req, err := s.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req.Header.Set("Accept", mediaTypeSystemVersion)
|
||||
|
||||
v := new(VersionAddOns)
|
||||
resp, err := s.client.Do(ctx, req, v)
|
||||
return v, resp, err
|
||||
}
|
||||
|
||||
type ReverseProxyConfig struct {
|
||||
Key *string `json:"key,omitempty"`
|
||||
WebServerType *string `json:"webServerType,omitempty"`
|
||||
ArtifactoryAppContext *string `json:"artifactoryAppContext,omitempty"`
|
||||
PublicAppContext *string `json:"publicAppContext,omitempty"`
|
||||
ServerName *string `json:"serverName,omitempty"`
|
||||
ServerNameExpression *string `json:"serverNameExpression,omitempty"`
|
||||
ArtifactoryServerName *string `json:"artifactoryServerName,omitempty"`
|
||||
ArtifactoryPort *int `json:"artifactoryPort,omitempty"`
|
||||
SslCertificate *string `json:"sslCertificate,omitempty"`
|
||||
SslKey *string `json:"sslKey,omitempty"`
|
||||
DockerReverseProxyMethod *string `json:"dockerReverseProxyMethod,omitempty"`
|
||||
UseHttps *bool `json:"useHttps,omitempty"`
|
||||
UseHttp *bool `json:"useHttp,omitempty"`
|
||||
SslPort *int `json:"sslPort,omitempty"`
|
||||
HttpPort *int `json:"httpPort,omitempty"`
|
||||
}
|
||||
|
||||
func (r ReverseProxyConfig) String() string {
|
||||
res, _ := json.MarshalIndent(r, "", " ")
|
||||
return string(res)
|
||||
}
|
||||
|
||||
// Retrieves the reverse proxy configuration
|
||||
// Since: 4.3.1
|
||||
// Security: Requires a valid admin user
|
||||
func (s *SystemService) GetReverseProxyConfig(ctx context.Context) (*ReverseProxyConfig, *http.Response, error) {
|
||||
path := "/api/system/configuration/webServer"
|
||||
req, err := s.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req.Header.Set("Accept", mediaTypeJson)
|
||||
|
||||
v := new(ReverseProxyConfig)
|
||||
resp, err := s.client.Do(ctx, req, v)
|
||||
return v, resp, err
|
||||
}
|
||||
|
||||
// Updates the reverse proxy configuration
|
||||
// Since: 4.3.1
|
||||
// Security: Requires an admin user
|
||||
func (s *SystemService) UpdateReverseProxyConfig(ctx context.Context, config *ReverseProxyConfig) (*http.Response, error) {
|
||||
path := "/api/system/configuration/webServer"
|
||||
req, err := s.client.NewJSONEncodedRequest("POST", path, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.Do(ctx, req, nil)
|
||||
}
|
||||
|
||||
// Gets the reverse proxy configuration snippet in text format
|
||||
// Since: 4.3.1
|
||||
// Security: Requires a valid user (not anonymous)
|
||||
func (s *SystemService) GetReverseProxySnippet(ctx context.Context) (*string, *http.Response, error) {
|
||||
path := "/api/system/configuration/reverseProxy/nginx"
|
||||
req, err := s.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req.Header.Set("Accept", mediaTypePlain)
|
||||
|
||||
v := new(bytes.Buffer)
|
||||
resp, err := s.client.Do(ctx, req, v)
|
||||
return String(v.String()), resp, err
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
bin
|
||||
*.swp
|
|
@ -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.
|
|
@ -0,0 +1,122 @@
|
|||
# Introduction
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/elazarl/goproxy?status.svg)](https://godoc.org/github.com/elazarl/goproxy)
|
||||
[![Join the chat at https://gitter.im/elazarl/goproxy](https://badges.gitter.im/Join%20Chat.svg)](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 will 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 intends 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 run 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 match 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 reject 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 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.
|
|
@ -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)
|
||||
}
|
|
@ -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
|
|
@ -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-----
|
|
@ -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)
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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]
|
||||
}
|
|
@ -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
|
||||
})
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,421 @@
|
|||
package goproxy
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type ConnectActionLiteral int
|
||||
|
||||
const (
|
||||
ConnectAccept = iota
|
||||
ConnectReject
|
||||
ConnectMitm
|
||||
ConnectHijack
|
||||
ConnectHTTPMitm
|
||||
ConnectProxyAuthHijack
|
||||
)
|
||||
|
||||
var (
|
||||
OkConnect = &ConnectAction{Action: ConnectAccept, TLSConfig: TLSConfigFromCA(&GoproxyCa)}
|
||||
MitmConnect = &ConnectAction{Action: ConnectMitm, TLSConfig: TLSConfigFromCA(&GoproxyCa)}
|
||||
HTTPMitmConnect = &ConnectAction{Action: ConnectHTTPMitm, TLSConfig: TLSConfigFromCA(&GoproxyCa)}
|
||||
RejectConnect = &ConnectAction{Action: ConnectReject, TLSConfig: TLSConfigFromCA(&GoproxyCa)}
|
||||
httpsRegexp = regexp.MustCompile(`^https:\/\/`)
|
||||
)
|
||||
|
||||
type ConnectAction struct {
|
||||
Action ConnectActionLiteral
|
||||
Hijack func(req *http.Request, client net.Conn, ctx *ProxyCtx)
|
||||
TLSConfig func(host string, ctx *ProxyCtx) (*tls.Config, error)
|
||||
}
|
||||
|
||||
func stripPort(s string) string {
|
||||
ix := strings.IndexRune(s, ':')
|
||||
if ix == -1 {
|
||||
return s
|
||||
}
|
||||
return s[:ix]
|
||||
}
|
||||
|
||||
func (proxy *ProxyHttpServer) dial(network, addr string) (c net.Conn, err error) {
|
||||
if proxy.Tr.Dial != nil {
|
||||
return proxy.Tr.Dial(network, addr)
|
||||
}
|
||||
return net.Dial(network, addr)
|
||||
}
|
||||
|
||||
func (proxy *ProxyHttpServer) connectDial(network, addr string) (c net.Conn, err error) {
|
||||
if proxy.ConnectDial == nil {
|
||||
return proxy.dial(network, addr)
|
||||
}
|
||||
return proxy.ConnectDial(network, addr)
|
||||
}
|
||||
|
||||
func (proxy *ProxyHttpServer) handleHttps(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := &ProxyCtx{Req: r, Session: atomic.AddInt64(&proxy.sess, 1), proxy: proxy}
|
||||
|
||||
hij, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
panic("httpserver does not support hijacking")
|
||||
}
|
||||
|
||||
proxyClient, _, e := hij.Hijack()
|
||||
if e != nil {
|
||||
panic("Cannot hijack connection " + e.Error())
|
||||
}
|
||||
|
||||
ctx.Logf("Running %d CONNECT handlers", len(proxy.httpsHandlers))
|
||||
todo, host := OkConnect, r.URL.Host
|
||||
for i, h := range proxy.httpsHandlers {
|
||||
newtodo, newhost := h.HandleConnect(host, ctx)
|
||||
|
||||
// If found a result, break the loop immediately
|
||||
if newtodo != nil {
|
||||
todo, host = newtodo, newhost
|
||||
ctx.Logf("on %dth handler: %v %s", i, todo, host)
|
||||
break
|
||||
}
|
||||
}
|
||||
switch todo.Action {
|
||||
case ConnectAccept:
|
||||
if !hasPort.MatchString(host) {
|
||||
host += ":80"
|
||||
}
|
||||
targetSiteCon, err := proxy.connectDial("tcp", host)
|
||||
if err != nil {
|
||||
httpError(proxyClient, ctx, err)
|
||||
return
|
||||
}
|
||||
ctx.Logf("Accepting CONNECT to %s", host)
|
||||
proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
|
||||
|
||||
targetTCP, targetOK := targetSiteCon.(*net.TCPConn)
|
||||
proxyClientTCP, clientOK := proxyClient.(*net.TCPConn)
|
||||
if targetOK && clientOK {
|
||||
go copyAndClose(ctx, targetTCP, proxyClientTCP)
|
||||
go copyAndClose(ctx, proxyClientTCP, targetTCP)
|
||||
} else {
|
||||
go func() {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go copyOrWarn(ctx, targetSiteCon, proxyClient, &wg)
|
||||
go copyOrWarn(ctx, proxyClient, targetSiteCon, &wg)
|
||||
wg.Wait()
|
||||
proxyClient.Close()
|
||||
targetSiteCon.Close()
|
||||
|
||||
}()
|
||||
}
|
||||
|
||||
case ConnectHijack:
|
||||
ctx.Logf("Hijacking CONNECT to %s", host)
|
||||
proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
|
||||
todo.Hijack(r, proxyClient, ctx)
|
||||
case ConnectHTTPMitm:
|
||||
proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
|
||||
ctx.Logf("Assuming CONNECT is plain HTTP tunneling, mitm proxying it")
|
||||
targetSiteCon, err := proxy.connectDial("tcp", host)
|
||||
if err != nil {
|
||||
ctx.Warnf("Error dialing to %s: %s", host, err.Error())
|
||||
return
|
||||
}
|
||||
for {
|
||||
client := bufio.NewReader(proxyClient)
|
||||
remote := bufio.NewReader(targetSiteCon)
|
||||
req, err := http.ReadRequest(client)
|
||||
if err != nil && err != io.EOF {
|
||||
ctx.Warnf("cannot read request of MITM HTTP client: %+#v", err)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req, resp := proxy.filterRequest(req, ctx)
|
||||
if resp == nil {
|
||||
if err := req.Write(targetSiteCon); err != nil {
|
||||
httpError(proxyClient, ctx, err)
|
||||
return
|
||||
}
|
||||
resp, err = http.ReadResponse(remote, req)
|
||||
if err != nil {
|
||||
httpError(proxyClient, ctx, err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
resp = proxy.filterResponse(resp, ctx)
|
||||
if err := resp.Write(proxyClient); err != nil {
|
||||
httpError(proxyClient, ctx, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
case ConnectMitm:
|
||||
proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
|
||||
ctx.Logf("Assuming CONNECT is TLS, mitm proxying it")
|
||||
// this goes in a separate goroutine, so that the net/http server won't think we're
|
||||
// still handling the request even after hijacking the connection. Those HTTP CONNECT
|
||||
// request can take forever, and the server will be stuck when "closed".
|
||||
// TODO: Allow Server.Close() mechanism to shut down this connection as nicely as possible
|
||||
tlsConfig := defaultTLSConfig
|
||||
if todo.TLSConfig != nil {
|
||||
var err error
|
||||
tlsConfig, err = todo.TLSConfig(host, ctx)
|
||||
if err != nil {
|
||||
httpError(proxyClient, ctx, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
go func() {
|
||||
//TODO: cache connections to the remote website
|
||||
rawClientTls := tls.Server(proxyClient, tlsConfig)
|
||||
if err := rawClientTls.Handshake(); err != nil {
|
||||
ctx.Warnf("Cannot handshake client %v %v", r.Host, err)
|
||||
return
|
||||
}
|
||||
defer rawClientTls.Close()
|
||||
clientTlsReader := bufio.NewReader(rawClientTls)
|
||||
for !isEof(clientTlsReader) {
|
||||
req, err := http.ReadRequest(clientTlsReader)
|
||||
var ctx = &ProxyCtx{Req: req, Session: atomic.AddInt64(&proxy.sess, 1), proxy: proxy, UserData: ctx.UserData}
|
||||
if err != nil && err != io.EOF {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
ctx.Warnf("Cannot read TLS request from mitm'd client %v %v", r.Host, err)
|
||||
return
|
||||
}
|
||||
req.RemoteAddr = r.RemoteAddr // since we're converting the request, need to carry over the original connecting IP as well
|
||||
ctx.Logf("req %v", r.Host)
|
||||
|
||||
if !httpsRegexp.MatchString(req.URL.String()) {
|
||||
req.URL, err = url.Parse("https://" + r.Host + req.URL.String())
|
||||
}
|
||||
|
||||
// Bug fix which goproxy fails to provide request
|
||||
// information URL in the context when does HTTPS MITM
|
||||
ctx.Req = req
|
||||
|
||||
req, resp := proxy.filterRequest(req, ctx)
|
||||
if resp == nil {
|
||||
if err != nil {
|
||||
ctx.Warnf("Illegal URL %s", "https://"+r.Host+req.URL.Path)
|
||||
return
|
||||
}
|
||||
removeProxyHeaders(ctx, req)
|
||||
resp, err = ctx.RoundTrip(req)
|
||||
if err != nil {
|
||||
ctx.Warnf("Cannot read TLS response from mitm'd server %v", err)
|
||||
return
|
||||
}
|
||||
ctx.Logf("resp %v", resp.Status)
|
||||
}
|
||||
resp = proxy.filterResponse(resp, ctx)
|
||||
defer resp.Body.Close()
|
||||
|
||||
text := resp.Status
|
||||
statusCode := strconv.Itoa(resp.StatusCode) + " "
|
||||
if strings.HasPrefix(text, statusCode) {
|
||||
text = text[len(statusCode):]
|
||||
}
|
||||
// always use 1.1 to support chunked encoding
|
||||
if _, err := io.WriteString(rawClientTls, "HTTP/1.1"+" "+statusCode+text+"\r\n"); err != nil {
|
||||
ctx.Warnf("Cannot write TLS response HTTP status from mitm'd client: %v", err)
|
||||
return
|
||||
}
|
||||
// Since we don't know the length of resp, return chunked encoded response
|
||||
// TODO: use a more reasonable scheme
|
||||
resp.Header.Del("Content-Length")
|
||||
resp.Header.Set("Transfer-Encoding", "chunked")
|
||||
// Force connection close otherwise chrome will keep CONNECT tunnel open forever
|
||||
resp.Header.Set("Connection", "close")
|
||||
if err := resp.Header.Write(rawClientTls); err != nil {
|
||||
ctx.Warnf("Cannot write TLS response header from mitm'd client: %v", err)
|
||||
return
|
||||
}
|
||||
if _, err = io.WriteString(rawClientTls, "\r\n"); err != nil {
|
||||
ctx.Warnf("Cannot write TLS response header end from mitm'd client: %v", err)
|
||||
return
|
||||
}
|
||||
chunked := newChunkedWriter(rawClientTls)
|
||||
if _, err := io.Copy(chunked, resp.Body); err != nil {
|
||||
ctx.Warnf("Cannot write TLS response body from mitm'd client: %v", err)
|
||||
return
|
||||
}
|
||||
if err := chunked.Close(); err != nil {
|
||||
ctx.Warnf("Cannot write TLS chunked EOF from mitm'd client: %v", err)
|
||||
return
|
||||
}
|
||||
if _, err = io.WriteString(rawClientTls, "\r\n"); err != nil {
|
||||
ctx.Warnf("Cannot write TLS response chunked trailer from mitm'd client: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.Logf("Exiting on EOF")
|
||||
}()
|
||||
case ConnectProxyAuthHijack:
|
||||
proxyClient.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\n"))
|
||||
todo.Hijack(r, proxyClient, ctx)
|
||||
case ConnectReject:
|
||||
if ctx.Resp != nil {
|
||||
if err := ctx.Resp.Write(proxyClient); err != nil {
|
||||
ctx.Warnf("Cannot write response that reject http CONNECT: %v", err)
|
||||
}
|
||||
}
|
||||
proxyClient.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func httpError(w io.WriteCloser, ctx *ProxyCtx, err error) {
|
||||
if _, err := io.WriteString(w, "HTTP/1.1 502 Bad Gateway\r\n\r\n"); err != nil {
|
||||
ctx.Warnf("Error responding to client: %s", err)
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
ctx.Warnf("Error closing client connection: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func copyOrWarn(ctx *ProxyCtx, dst io.Writer, src io.Reader, wg *sync.WaitGroup) {
|
||||
if _, err := io.Copy(dst, src); err != nil {
|
||||
ctx.Warnf("Error copying to client: %s", err)
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
func copyAndClose(ctx *ProxyCtx, dst, src *net.TCPConn) {
|
||||
if _, err := io.Copy(dst, src); err != nil {
|
||||
ctx.Warnf("Error copying to client: %s", err)
|
||||
}
|
||||
|
||||
dst.CloseWrite()
|
||||
src.CloseRead()
|
||||
}
|
||||
|
||||
func dialerFromEnv(proxy *ProxyHttpServer) func(network, addr string) (net.Conn, error) {
|
||||
https_proxy := os.Getenv("HTTPS_PROXY")
|
||||
if https_proxy == "" {
|
||||
https_proxy = os.Getenv("https_proxy")
|
||||
}
|
||||
if https_proxy == "" {
|
||||
return nil
|
||||
}
|
||||
return proxy.NewConnectDialToProxy(https_proxy)
|
||||
}
|
||||
|
||||
func (proxy *ProxyHttpServer) NewConnectDialToProxy(https_proxy string) func(network, addr string) (net.Conn, error) {
|
||||
return proxy.NewConnectDialToProxyWithHandler(https_proxy, nil)
|
||||
}
|
||||
|
||||
func (proxy *ProxyHttpServer) NewConnectDialToProxyWithHandler(https_proxy string, connectReqHandler func(req *http.Request)) func(network, addr string) (net.Conn, error) {
|
||||
u, err := url.Parse(https_proxy)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if u.Scheme == "" || u.Scheme == "http" {
|
||||
if strings.IndexRune(u.Host, ':') == -1 {
|
||||
u.Host += ":80"
|
||||
}
|
||||
return func(network, addr string) (net.Conn, error) {
|
||||
connectReq := &http.Request{
|
||||
Method: "CONNECT",
|
||||
URL: &url.URL{Opaque: addr},
|
||||
Host: addr,
|
||||
Header: make(http.Header),
|
||||
}
|
||||
if connectReqHandler != nil {
|
||||
connectReqHandler(connectReq)
|
||||
}
|
||||
c, err := proxy.dial(network, u.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
connectReq.Write(c)
|
||||
// Read response.
|
||||
// Okay to use and discard buffered reader here, because
|
||||
// TLS server will not speak until spoken to.
|
||||
br := bufio.NewReader(c)
|
||||
resp, err := http.ReadResponse(br, connectReq)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 200 {
|
||||
resp, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Close()
|
||||
return nil, errors.New("proxy refused connection" + string(resp))
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
if u.Scheme == "https" {
|
||||
if strings.IndexRune(u.Host, ':') == -1 {
|
||||
u.Host += ":443"
|
||||
}
|
||||
return func(network, addr string) (net.Conn, error) {
|
||||
c, err := proxy.dial(network, u.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c = tls.Client(c, proxy.Tr.TLSClientConfig)
|
||||
connectReq := &http.Request{
|
||||
Method: "CONNECT",
|
||||
URL: &url.URL{Opaque: addr},
|
||||
Host: addr,
|
||||
Header: make(http.Header),
|
||||
}
|
||||
if connectReqHandler != nil {
|
||||
connectReqHandler(connectReq)
|
||||
}
|
||||
connectReq.Write(c)
|
||||
// Read response.
|
||||
// Okay to use and discard buffered reader here, because
|
||||
// TLS server will not speak until spoken to.
|
||||
br := bufio.NewReader(c)
|
||||
resp, err := http.ReadResponse(br, connectReq)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 200 {
|
||||
body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 500))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Close()
|
||||
return nil, errors.New("proxy refused connection" + string(body))
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TLSConfigFromCA(ca *tls.Certificate) func(host string, ctx *ProxyCtx) (*tls.Config, error) {
|
||||
return func(host string, ctx *ProxyCtx) (*tls.Config, error) {
|
||||
config := *defaultTLSConfig
|
||||
ctx.Logf("signing for %s", stripPort(host))
|
||||
cert, err := signHost(*ca, []string{stripPort(host)})
|
||||
if err != nil {
|
||||
ctx.Warnf("Cannot sign host certificate with provided CA: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
config.Certificates = append(config.Certificates, cert)
|
||||
return &config, nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
-----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-----
|
|
@ -0,0 +1,166 @@
|
|||
package goproxy
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// The basic proxy type. Implements http.Handler.
|
||||
type ProxyHttpServer struct {
|
||||
// session variable must be aligned in i386
|
||||
// see http://golang.org/src/pkg/sync/atomic/doc.go#L41
|
||||
sess int64
|
||||
// KeepDestinationHeaders indicates the proxy should retain any headers present in the http.Response before proxying
|
||||
KeepDestinationHeaders bool
|
||||
// setting Verbose to true will log information on each request sent to the proxy
|
||||
Verbose bool
|
||||
Logger *log.Logger
|
||||
NonproxyHandler http.Handler
|
||||
reqHandlers []ReqHandler
|
||||
respHandlers []RespHandler
|
||||
httpsHandlers []HttpsHandler
|
||||
Tr *http.Transport
|
||||
// ConnectDial will be used to create TCP connections for CONNECT requests
|
||||
// if nil Tr.Dial will be used
|
||||
ConnectDial func(network string, addr string) (net.Conn, error)
|
||||
}
|
||||
|
||||
var hasPort = regexp.MustCompile(`:\d+$`)
|
||||
|
||||
func copyHeaders(dst, src http.Header, keepDestHeaders bool) {
|
||||
if !keepDestHeaders {
|
||||
for k := range dst {
|
||||
dst.Del(k)
|
||||
}
|
||||
}
|
||||
for k, vs := range src {
|
||||
for _, v := range vs {
|
||||
dst.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isEof(r *bufio.Reader) bool {
|
||||
_, err := r.Peek(1)
|
||||
if err == io.EOF {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (proxy *ProxyHttpServer) filterRequest(r *http.Request, ctx *ProxyCtx) (req *http.Request, resp *http.Response) {
|
||||
req = r
|
||||
for _, h := range proxy.reqHandlers {
|
||||
req, resp = h.Handle(r, ctx)
|
||||
// non-nil resp means the handler decided to skip sending the request
|
||||
// and return canned response instead.
|
||||
if resp != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
func (proxy *ProxyHttpServer) filterResponse(respOrig *http.Response, ctx *ProxyCtx) (resp *http.Response) {
|
||||
resp = respOrig
|
||||
for _, h := range proxy.respHandlers {
|
||||
ctx.Resp = resp
|
||||
resp = h.Handle(resp, ctx)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func removeProxyHeaders(ctx *ProxyCtx, r *http.Request) {
|
||||
r.RequestURI = "" // this must be reset when serving a request with the client
|
||||
ctx.Logf("Sending request %v %v", r.Method, r.URL.String())
|
||||
// If no Accept-Encoding header exists, Transport will add the headers it can accept
|
||||
// and would wrap the response body with the relevant reader.
|
||||
r.Header.Del("Accept-Encoding")
|
||||
// curl can add that, see
|
||||
// https://jdebp.eu./FGA/web-proxy-connection-header.html
|
||||
r.Header.Del("Proxy-Connection")
|
||||
r.Header.Del("Proxy-Authenticate")
|
||||
r.Header.Del("Proxy-Authorization")
|
||||
// Connection, Authenticate and Authorization are single hop Header:
|
||||
// http://www.w3.org/Protocols/rfc2616/rfc2616.txt
|
||||
// 14.10 Connection
|
||||
// The Connection general-header field allows the sender to specify
|
||||
// options that are desired for that particular connection and MUST NOT
|
||||
// be communicated by proxies over further connections.
|
||||
r.Header.Del("Connection")
|
||||
}
|
||||
|
||||
// Standard net/http function. Shouldn't be used directly, http.Serve will use it.
|
||||
func (proxy *ProxyHttpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
//r.Header["X-Forwarded-For"] = w.RemoteAddr()
|
||||
if r.Method == "CONNECT" {
|
||||
proxy.handleHttps(w, r)
|
||||
} else {
|
||||
ctx := &ProxyCtx{Req: r, Session: atomic.AddInt64(&proxy.sess, 1), proxy: proxy}
|
||||
|
||||
var err error
|
||||
ctx.Logf("Got request %v %v %v %v", r.URL.Path, r.Host, r.Method, r.URL.String())
|
||||
if !r.URL.IsAbs() {
|
||||
proxy.NonproxyHandler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
r, resp := proxy.filterRequest(r, ctx)
|
||||
|
||||
if resp == nil {
|
||||
removeProxyHeaders(ctx, r)
|
||||
resp, err = ctx.RoundTrip(r)
|
||||
if err != nil {
|
||||
ctx.Error = err
|
||||
resp = proxy.filterResponse(nil, ctx)
|
||||
if resp == nil {
|
||||
ctx.Logf("error read response %v %v:", r.URL.Host, err.Error())
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.Logf("Received response %v", resp.Status)
|
||||
}
|
||||
origBody := resp.Body
|
||||
resp = proxy.filterResponse(resp, ctx)
|
||||
defer origBody.Close()
|
||||
ctx.Logf("Copying response to client %v [%d]", resp.Status, resp.StatusCode)
|
||||
// http.ResponseWriter will take care of filling the correct response length
|
||||
// Setting it now, might impose wrong value, contradicting the actual new
|
||||
// body the user returned.
|
||||
// We keep the original body to remove the header only if things changed.
|
||||
// This will prevent problems with HEAD requests where there's no body, yet,
|
||||
// the Content-Length header should be set.
|
||||
if origBody != resp.Body {
|
||||
resp.Header.Del("Content-Length")
|
||||
}
|
||||
copyHeaders(w.Header(), resp.Header, proxy.KeepDestinationHeaders)
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
nr, err := io.Copy(w, resp.Body)
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
ctx.Warnf("Can't close response body %v", err)
|
||||
}
|
||||
ctx.Logf("Copied %v bytes to client error=%v", nr, err)
|
||||
}
|
||||
}
|
||||
|
||||
// NewProxyHttpServer creates and returns a proxy server, logging to stderr by default
|
||||
func NewProxyHttpServer() *ProxyHttpServer {
|
||||
proxy := ProxyHttpServer{
|
||||
Logger: log.New(os.Stderr, "", log.LstdFlags),
|
||||
reqHandlers: []ReqHandler{},
|
||||
respHandlers: []RespHandler{},
|
||||
httpsHandlers: []HttpsHandler{},
|
||||
NonproxyHandler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
http.Error(w, "This is a proxy server. Does not respond to non-proxy requests.", 500)
|
||||
}),
|
||||
Tr: &http.Transport{TLSClientConfig: tlsClientSkipVerify, Proxy: http.ProxyFromEnvironment},
|
||||
}
|
||||
proxy.ConnectDial = dialerFromEnv(&proxy)
|
||||
|
||||
return &proxy
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package goproxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Will generate a valid http response to the given request the response will have
|
||||
// the given contentType, and http status.
|
||||
// Typical usage, refuse to process requests to local addresses:
|
||||
//
|
||||
// proxy.OnRequest(IsLocalHost()).DoFunc(func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request,*http.Response) {
|
||||
// return nil,NewResponse(r,goproxy.ContentTypeHtml,http.StatusUnauthorized,
|
||||
// `<!doctype html><html><head><title>Can't use proxy for local addresses</title></head><body/></html>`)
|
||||
// })
|
||||
func NewResponse(r *http.Request, contentType string, status int, body string) *http.Response {
|
||||
resp := &http.Response{}
|
||||
resp.Request = r
|
||||
resp.TransferEncoding = r.TransferEncoding
|
||||
resp.Header = make(http.Header)
|
||||
resp.Header.Add("Content-Type", contentType)
|
||||
resp.StatusCode = status
|
||||
resp.Status = http.StatusText(status)
|
||||
buf := bytes.NewBufferString(body)
|
||||
resp.ContentLength = int64(buf.Len())
|
||||
resp.Body = ioutil.NopCloser(buf)
|
||||
return resp
|
||||
}
|
||||
|
||||
const (
|
||||
ContentTypeText = "text/plain"
|
||||
ContentTypeHtml = "text/html"
|
||||
)
|
||||
|
||||
// Alias for NewResponse(r,ContentTypeText,http.StatusAccepted,text)
|
||||
func TextResponse(r *http.Request, text string) *http.Response {
|
||||
return NewResponse(r, ContentTypeText, http.StatusAccepted, text)
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
package goproxy
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"math/big"
|
||||
"net"
|
||||
"runtime"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
func hashSorted(lst []string) []byte {
|
||||
c := make([]string, len(lst))
|
||||
copy(c, lst)
|
||||
sort.Strings(c)
|
||||
h := sha1.New()
|
||||
for _, s := range c {
|
||||
h.Write([]byte(s + ","))
|
||||
}
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func hashSortedBigInt(lst []string) *big.Int {
|
||||
rv := new(big.Int)
|
||||
rv.SetBytes(hashSorted(lst))
|
||||
return rv
|
||||
}
|
||||
|
||||
var goproxySignerVersion = ":goroxy1"
|
||||
|
||||
func signHost(ca tls.Certificate, hosts []string) (cert tls.Certificate, err error) {
|
||||
var x509ca *x509.Certificate
|
||||
|
||||
// Use the provided ca and not the global GoproxyCa for certificate generation.
|
||||
if x509ca, err = x509.ParseCertificate(ca.Certificate[0]); err != nil {
|
||||
return
|
||||
}
|
||||
start := time.Unix(0, 0)
|
||||
end, err := time.Parse("2006-01-02", "2049-12-31")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
hash := hashSorted(append(hosts, goproxySignerVersion, ":"+runtime.Version()))
|
||||
serial := new(big.Int)
|
||||
serial.SetBytes(hash)
|
||||
template := x509.Certificate{
|
||||
// TODO(elazar): instead of this ugly hack, just encode the certificate and hash the binary form.
|
||||
SerialNumber: serial,
|
||||
Issuer: x509ca.Subject,
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"GoProxy untrusted MITM proxy Inc"},
|
||||
},
|
||||
NotBefore: start,
|
||||
NotAfter: end,
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
for _, h := range hosts {
|
||||
if ip := net.ParseIP(h); ip != nil {
|
||||
template.IPAddresses = append(template.IPAddresses, ip)
|
||||
} else {
|
||||
template.DNSNames = append(template.DNSNames, h)
|
||||
template.Subject.CommonName = h
|
||||
}
|
||||
}
|
||||
var csprng CounterEncryptorRand
|
||||
if csprng, err = NewCounterEncryptorRandFromKey(ca.PrivateKey, hash); err != nil {
|
||||
return
|
||||
}
|
||||
var certpriv *rsa.PrivateKey
|
||||
if certpriv, err = rsa.GenerateKey(&csprng, 2048); err != nil {
|
||||
return
|
||||
}
|
||||
var derBytes []byte
|
||||
if derBytes, err = x509.CreateCertificate(&csprng, &template, x509ca, &certpriv.PublicKey, ca.PrivateKey); err != nil {
|
||||
return
|
||||
}
|
||||
return tls.Certificate{
|
||||
Certificate: [][]byte{derBytes, ca.Certificate[0]},
|
||||
PrivateKey: certpriv,
|
||||
}, nil
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2013 Google. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,320 @@
|
|||
// Copyright 2013 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 query implements encoding of structs into URL query parameters.
|
||||
//
|
||||
// As a simple example:
|
||||
//
|
||||
// type Options struct {
|
||||
// Query string `url:"q"`
|
||||
// ShowAll bool `url:"all"`
|
||||
// Page int `url:"page"`
|
||||
// }
|
||||
//
|
||||
// opt := Options{ "foo", true, 2 }
|
||||
// v, _ := query.Values(opt)
|
||||
// fmt.Print(v.Encode()) // will output: "q=foo&all=true&page=2"
|
||||
//
|
||||
// The exact mapping between Go values and url.Values is described in the
|
||||
// documentation for the Values() function.
|
||||
package query
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var timeType = reflect.TypeOf(time.Time{})
|
||||
|
||||
var encoderType = reflect.TypeOf(new(Encoder)).Elem()
|
||||
|
||||
// Encoder is an interface implemented by any type that wishes to encode
|
||||
// itself into URL values in a non-standard way.
|
||||
type Encoder interface {
|
||||
EncodeValues(key string, v *url.Values) error
|
||||
}
|
||||
|
||||
// Values returns the url.Values encoding of v.
|
||||
//
|
||||
// Values expects to be passed a struct, and traverses it recursively using the
|
||||
// following encoding rules.
|
||||
//
|
||||
// Each exported struct field is encoded as a URL parameter unless
|
||||
//
|
||||
// - the field's tag is "-", or
|
||||
// - the field is empty and its tag specifies the "omitempty" option
|
||||
//
|
||||
// The empty values are false, 0, any nil pointer or interface value, any array
|
||||
// slice, map, or string of length zero, and any time.Time that returns true
|
||||
// for IsZero().
|
||||
//
|
||||
// The URL parameter name defaults to the struct field name but can be
|
||||
// specified in the struct field's tag value. The "url" key in the struct
|
||||
// field's tag value is the key name, followed by an optional comma and
|
||||
// options. For example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field int `url:"-"`
|
||||
//
|
||||
// // Field appears as URL parameter "myName".
|
||||
// Field int `url:"myName"`
|
||||
//
|
||||
// // Field appears as URL parameter "myName" and the field is omitted if
|
||||
// // its value is empty
|
||||
// Field int `url:"myName,omitempty"`
|
||||
//
|
||||
// // Field appears as URL parameter "Field" (the default), but the field
|
||||
// // is skipped if empty. Note the leading comma.
|
||||
// Field int `url:",omitempty"`
|
||||
//
|
||||
// For encoding individual field values, the following type-dependent rules
|
||||
// apply:
|
||||
//
|
||||
// Boolean values default to encoding as the strings "true" or "false".
|
||||
// Including the "int" option signals that the field should be encoded as the
|
||||
// strings "1" or "0".
|
||||
//
|
||||
// time.Time values default to encoding as RFC3339 timestamps. Including the
|
||||
// "unix" option signals that the field should be encoded as a Unix time (see
|
||||
// time.Unix())
|
||||
//
|
||||
// Slice and Array values default to encoding as multiple URL values of the
|
||||
// same name. Including the "comma" option signals that the field should be
|
||||
// encoded as a single comma-delimited value. Including the "space" option
|
||||
// similarly encodes the value as a single space-delimited string. Including
|
||||
// the "semicolon" option will encode the value as a semicolon-delimited string.
|
||||
// Including the "brackets" option signals that the multiple URL values should
|
||||
// have "[]" appended to the value name. "numbered" will append a number to
|
||||
// the end of each incidence of the value name, example:
|
||||
// name0=value0&name1=value1, etc.
|
||||
//
|
||||
// Anonymous struct fields are usually encoded as if their inner exported
|
||||
// fields were fields in the outer struct, subject to the standard Go
|
||||
// visibility rules. An anonymous struct field with a name given in its URL
|
||||
// tag is treated as having that name, rather than being anonymous.
|
||||
//
|
||||
// Non-nil pointer values are encoded as the value pointed to.
|
||||
//
|
||||
// Nested structs are encoded including parent fields in value names for
|
||||
// scoping. e.g:
|
||||
//
|
||||
// "user[name]=acme&user[addr][postcode]=1234&user[addr][city]=SFO"
|
||||
//
|
||||
// All other values are encoded using their default string representation.
|
||||
//
|
||||
// Multiple fields that encode to the same URL parameter name will be included
|
||||
// as multiple URL values of the same name.
|
||||
func Values(v interface{}) (url.Values, error) {
|
||||
values := make(url.Values)
|
||||
val := reflect.ValueOf(v)
|
||||
for val.Kind() == reflect.Ptr {
|
||||
if val.IsNil() {
|
||||
return values, nil
|
||||
}
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
if v == nil {
|
||||
return values, nil
|
||||
}
|
||||
|
||||
if val.Kind() != reflect.Struct {
|
||||
return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind())
|
||||
}
|
||||
|
||||
err := reflectValue(values, val, "")
|
||||
return values, err
|
||||
}
|
||||
|
||||
// reflectValue populates the values parameter from the struct fields in val.
|
||||
// Embedded structs are followed recursively (using the rules defined in the
|
||||
// Values function documentation) breadth-first.
|
||||
func reflectValue(values url.Values, val reflect.Value, scope string) error {
|
||||
var embedded []reflect.Value
|
||||
|
||||
typ := val.Type()
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
sf := typ.Field(i)
|
||||
if sf.PkgPath != "" && !sf.Anonymous { // unexported
|
||||
continue
|
||||
}
|
||||
|
||||
sv := val.Field(i)
|
||||
tag := sf.Tag.Get("url")
|
||||
if tag == "-" {
|
||||
continue
|
||||
}
|
||||
name, opts := parseTag(tag)
|
||||
if name == "" {
|
||||
if sf.Anonymous && sv.Kind() == reflect.Struct {
|
||||
// save embedded struct for later processing
|
||||
embedded = append(embedded, sv)
|
||||
continue
|
||||
}
|
||||
|
||||
name = sf.Name
|
||||
}
|
||||
|
||||
if scope != "" {
|
||||
name = scope + "[" + name + "]"
|
||||
}
|
||||
|
||||
if opts.Contains("omitempty") && isEmptyValue(sv) {
|
||||
continue
|
||||
}
|
||||
|
||||
if sv.Type().Implements(encoderType) {
|
||||
if !reflect.Indirect(sv).IsValid() {
|
||||
sv = reflect.New(sv.Type().Elem())
|
||||
}
|
||||
|
||||
m := sv.Interface().(Encoder)
|
||||
if err := m.EncodeValues(name, &values); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array {
|
||||
var del byte
|
||||
if opts.Contains("comma") {
|
||||
del = ','
|
||||
} else if opts.Contains("space") {
|
||||
del = ' '
|
||||
} else if opts.Contains("semicolon") {
|
||||
del = ';'
|
||||
} else if opts.Contains("brackets") {
|
||||
name = name + "[]"
|
||||
}
|
||||
|
||||
if del != 0 {
|
||||
s := new(bytes.Buffer)
|
||||
first := true
|
||||
for i := 0; i < sv.Len(); i++ {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
s.WriteByte(del)
|
||||
}
|
||||
s.WriteString(valueString(sv.Index(i), opts))
|
||||
}
|
||||
values.Add(name, s.String())
|
||||
} else {
|
||||
for i := 0; i < sv.Len(); i++ {
|
||||
k := name
|
||||
if opts.Contains("numbered") {
|
||||
k = fmt.Sprintf("%s%d", name, i)
|
||||
}
|
||||
values.Add(k, valueString(sv.Index(i), opts))
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
for sv.Kind() == reflect.Ptr {
|
||||
if sv.IsNil() {
|
||||
break
|
||||
}
|
||||
sv = sv.Elem()
|
||||
}
|
||||
|
||||
if sv.Type() == timeType {
|
||||
values.Add(name, valueString(sv, opts))
|
||||
continue
|
||||
}
|
||||
|
||||
if sv.Kind() == reflect.Struct {
|
||||
reflectValue(values, sv, name)
|
||||
continue
|
||||
}
|
||||
|
||||
values.Add(name, valueString(sv, opts))
|
||||
}
|
||||
|
||||
for _, f := range embedded {
|
||||
if err := reflectValue(values, f, scope); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// valueString returns the string representation of a value.
|
||||
func valueString(v reflect.Value, opts tagOptions) string {
|
||||
for v.Kind() == reflect.Ptr {
|
||||
if v.IsNil() {
|
||||
return ""
|
||||
}
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
if v.Kind() == reflect.Bool && opts.Contains("int") {
|
||||
if v.Bool() {
|
||||
return "1"
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
if v.Type() == timeType {
|
||||
t := v.Interface().(time.Time)
|
||||
if opts.Contains("unix") {
|
||||
return strconv.FormatInt(t.Unix(), 10)
|
||||
}
|
||||
return t.Format(time.RFC3339)
|
||||
}
|
||||
|
||||
return fmt.Sprint(v.Interface())
|
||||
}
|
||||
|
||||
// isEmptyValue checks if a value should be considered empty for the purposes
|
||||
// of omitting fields with the "omitempty" option.
|
||||
func isEmptyValue(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len() == 0
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
return v.IsNil()
|
||||
}
|
||||
|
||||
if v.Type() == timeType {
|
||||
return v.Interface().(time.Time).IsZero()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// tagOptions is the string following a comma in a struct field's "url" tag, or
|
||||
// the empty string. It does not include the leading comma.
|
||||
type tagOptions []string
|
||||
|
||||
// parseTag splits a struct field's url tag into its name and comma-separated
|
||||
// options.
|
||||
func parseTag(tag string) (string, tagOptions) {
|
||||
s := strings.Split(tag, ",")
|
||||
return s[0], s[1:]
|
||||
}
|
||||
|
||||
// Contains checks whether the tagOptions contains the specified option.
|
||||
func (o tagOptions) Contains(option string) bool {
|
||||
for _, s := range o {
|
||||
if s == option {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.4.3
|
||||
- 1.5.3
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go test -v ./...
|
|
@ -0,0 +1,10 @@
|
|||
# How to contribute
|
||||
|
||||
We definitely welcome patches and contribution to this project!
|
||||
|
||||
### Legal requirements
|
||||
|
||||
In order to protect both you and ourselves, you will need to sign the
|
||||
[Contributor License Agreement](https://cla.developers.google.com/clas).
|
||||
|
||||
You may have already signed it for other Google projects.
|
|
@ -0,0 +1,9 @@
|
|||
Paul Borman <borman@google.com>
|
||||
bmatsuo
|
||||
shawnps
|
||||
theory
|
||||
jboverfelt
|
||||
dsymonds
|
||||
cd1
|
||||
wallclockbuilder
|
||||
dansouza
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2009,2014 Google Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,19 @@
|
|||
# uuid ![build status](https://travis-ci.org/google/uuid.svg?branch=master)
|
||||
The uuid package generates and inspects UUIDs based on
|
||||
[RFC 4122](http://tools.ietf.org/html/rfc4122)
|
||||
and DCE 1.1: Authentication and Security Services.
|
||||
|
||||
This package is based on the github.com/pborman/uuid package (previously named
|
||||
code.google.com/p/go-uuid). It differs from these earlier packages in that
|
||||
a UUID is a 16 byte array rather than a byte slice. One loss due to this
|
||||
change is the ability to represent an invalid UUID (vs a NIL UUID).
|
||||
|
||||
###### Install
|
||||
`go get github.com/google/uuid`
|
||||
|
||||
###### Documentation
|
||||
[![GoDoc](https://godoc.org/github.com/google/uuid?status.svg)](http://godoc.org/github.com/google/uuid)
|
||||
|
||||
Full `go doc` style documentation for the package can be viewed online without
|
||||
installing this package by using the GoDoc site here:
|
||||
http://godoc.org/github.com/google/uuid
|
|
@ -0,0 +1,80 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// A Domain represents a Version 2 domain
|
||||
type Domain byte
|
||||
|
||||
// Domain constants for DCE Security (Version 2) UUIDs.
|
||||
const (
|
||||
Person = Domain(0)
|
||||
Group = Domain(1)
|
||||
Org = Domain(2)
|
||||
)
|
||||
|
||||
// NewDCESecurity returns a DCE Security (Version 2) UUID.
|
||||
//
|
||||
// The domain should be one of Person, Group or Org.
|
||||
// On a POSIX system the id should be the users UID for the Person
|
||||
// domain and the users GID for the Group. The meaning of id for
|
||||
// the domain Org or on non-POSIX systems is site defined.
|
||||
//
|
||||
// For a given domain/id pair the same token may be returned for up to
|
||||
// 7 minutes and 10 seconds.
|
||||
func NewDCESecurity(domain Domain, id uint32) (UUID, error) {
|
||||
uuid, err := NewUUID()
|
||||
if err == nil {
|
||||
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
|
||||
uuid[9] = byte(domain)
|
||||
binary.BigEndian.PutUint32(uuid[0:], id)
|
||||
}
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
|
||||
// domain with the id returned by os.Getuid.
|
||||
//
|
||||
// NewDCESecurity(Person, uint32(os.Getuid()))
|
||||
func NewDCEPerson() (UUID, error) {
|
||||
return NewDCESecurity(Person, uint32(os.Getuid()))
|
||||
}
|
||||
|
||||
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
|
||||
// domain with the id returned by os.Getgid.
|
||||
//
|
||||
// NewDCESecurity(Group, uint32(os.Getgid()))
|
||||
func NewDCEGroup() (UUID, error) {
|
||||
return NewDCESecurity(Group, uint32(os.Getgid()))
|
||||
}
|
||||
|
||||
// Domain returns the domain for a Version 2 UUID. Domains are only defined
|
||||
// for Version 2 UUIDs.
|
||||
func (uuid UUID) Domain() Domain {
|
||||
return Domain(uuid[9])
|
||||
}
|
||||
|
||||
// ID returns the id for a Version 2 UUID. IDs are only defined for Version 2
|
||||
// UUIDs.
|
||||
func (uuid UUID) ID() uint32 {
|
||||
return binary.BigEndian.Uint32(uuid[0:4])
|
||||
}
|
||||
|
||||
func (d Domain) String() string {
|
||||
switch d {
|
||||
case Person:
|
||||
return "Person"
|
||||
case Group:
|
||||
return "Group"
|
||||
case Org:
|
||||
return "Org"
|
||||
}
|
||||
return fmt.Sprintf("Domain%d", int(d))
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package uuid generates and inspects UUIDs.
|
||||
//
|
||||
// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security
|
||||
// Services.
|
||||
//
|
||||
// A UUID is a 16 byte (128 bit) array. UUIDs may be used as keys to
|
||||
// maps or compared directly.
|
||||
package uuid
|
|
@ -0,0 +1 @@
|
|||
module github.com/google/uuid
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// Well known namespace IDs and UUIDs
|
||||
var (
|
||||
NameSpaceDNS = Must(Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NameSpaceURL = Must(Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NameSpaceOID = Must(Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NameSpaceX500 = Must(Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8"))
|
||||
Nil UUID // empty UUID, all zeros
|
||||
)
|
||||
|
||||
// NewHash returns a new UUID derived from the hash of space concatenated with
|
||||
// data generated by h. The hash should be at least 16 byte in length. The
|
||||
// first 16 bytes of the hash are used to form the UUID. The version of the
|
||||
// UUID will be the lower 4 bits of version. NewHash is used to implement
|
||||
// NewMD5 and NewSHA1.
|
||||
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
|
||||
h.Reset()
|
||||
h.Write(space[:])
|
||||
h.Write(data)
|
||||
s := h.Sum(nil)
|
||||
var uuid UUID
|
||||
copy(uuid[:], s)
|
||||
uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
|
||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
|
||||
return uuid
|
||||
}
|
||||
|
||||
// NewMD5 returns a new MD5 (Version 3) UUID based on the
|
||||
// supplied name space and data. It is the same as calling:
|
||||
//
|
||||
// NewHash(md5.New(), space, data, 3)
|
||||
func NewMD5(space UUID, data []byte) UUID {
|
||||
return NewHash(md5.New(), space, data, 3)
|
||||
}
|
||||
|
||||
// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
|
||||
// supplied name space and data. It is the same as calling:
|
||||
//
|
||||
// NewHash(sha1.New(), space, data, 5)
|
||||
func NewSHA1(space UUID, data []byte) UUID {
|
||||
return NewHash(sha1.New(), space, data, 5)
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import "fmt"
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler.
|
||||
func (uuid UUID) MarshalText() ([]byte, error) {
|
||||
var js [36]byte
|
||||
encodeHex(js[:], uuid)
|
||||
return js[:], nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||
func (uuid *UUID) UnmarshalText(data []byte) error {
|
||||
id, err := ParseBytes(data)
|
||||
if err == nil {
|
||||
*uuid = id
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||
func (uuid UUID) MarshalBinary() ([]byte, error) {
|
||||
return uuid[:], nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||
func (uuid *UUID) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != 16 {
|
||||
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
|
||||
}
|
||||
copy(uuid[:], data)
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
nodeMu sync.Mutex
|
||||
ifname string // name of interface being used
|
||||
nodeID [6]byte // hardware for version 1 UUIDs
|
||||
zeroID [6]byte // nodeID with only 0's
|
||||
)
|
||||
|
||||
// NodeInterface returns the name of the interface from which the NodeID was
|
||||
// derived. The interface "user" is returned if the NodeID was set by
|
||||
// SetNodeID.
|
||||
func NodeInterface() string {
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
return ifname
|
||||
}
|
||||
|
||||
// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
|
||||
// If name is "" then the first usable interface found will be used or a random
|
||||
// Node ID will be generated. If a named interface cannot be found then false
|
||||
// is returned.
|
||||
//
|
||||
// SetNodeInterface never fails when name is "".
|
||||
func SetNodeInterface(name string) bool {
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
return setNodeInterface(name)
|
||||
}
|
||||
|
||||
func setNodeInterface(name string) bool {
|
||||
iname, addr := getHardwareInterface(name) // null implementation for js
|
||||
if iname != "" && addr != nil {
|
||||
ifname = iname
|
||||
copy(nodeID[:], addr)
|
||||
return true
|
||||
}
|
||||
|
||||
// We found no interfaces with a valid hardware address. If name
|
||||
// does not specify a specific interface generate a random Node ID
|
||||
// (section 4.1.6)
|
||||
if name == "" {
|
||||
randomBits(nodeID[:])
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
|
||||
// if not already set.
|
||||
func NodeID() []byte {
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
if nodeID == zeroID {
|
||||
setNodeInterface("")
|
||||
}
|
||||
nid := nodeID
|
||||
return nid[:]
|
||||
}
|
||||
|
||||
// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
|
||||
// of id are used. If id is less than 6 bytes then false is returned and the
|
||||
// Node ID is not set.
|
||||
func SetNodeID(id []byte) bool {
|
||||
if len(id) < 6 {
|
||||
return false
|
||||
}
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
copy(nodeID[:], id)
|
||||
ifname = "user"
|
||||
return true
|
||||
}
|
||||
|
||||
// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
|
||||
// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
|
||||
func (uuid UUID) NodeID() []byte {
|
||||
var node [6]byte
|
||||
copy(node[:], uuid[10:])
|
||||
return node[:]
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build js
|
||||
|
||||
package uuid
|
||||
|
||||
// getHardwareInterface returns nil values for the JS version of the code.
|
||||
// This remvoves the "net" dependency, because it is not used in the browser.
|
||||
// Using the "net" library inflates the size of the transpiled JS code by 673k bytes.
|
||||
func getHardwareInterface(name string) (string, []byte) { return "", nil }
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !js
|
||||
|
||||
package uuid
|
||||
|
||||
import "net"
|
||||
|
||||
var interfaces []net.Interface // cached list of interfaces
|
||||
|
||||
// getHardwareInterface returns the name and hardware address of interface name.
|
||||
// If name is "" then the name and hardware address of one of the system's
|
||||
// interfaces is returned. If no interfaces are found (name does not exist or
|
||||
// there are no interfaces) then "", nil is returned.
|
||||
//
|
||||
// Only addresses of at least 6 bytes are returned.
|
||||
func getHardwareInterface(name string) (string, []byte) {
|
||||
if interfaces == nil {
|
||||
var err error
|
||||
interfaces, err = net.Interfaces()
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
for _, ifs := range interfaces {
|
||||
if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
|
||||
return ifs.Name, ifs.HardwareAddr
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Scan implements sql.Scanner so UUIDs can be read from databases transparently
|
||||
// Currently, database types that map to string and []byte are supported. Please
|
||||
// consult database-specific driver documentation for matching types.
|
||||
func (uuid *UUID) Scan(src interface{}) error {
|
||||
switch src := src.(type) {
|
||||
case nil:
|
||||
return nil
|
||||
|
||||
case string:
|
||||
// if an empty UUID comes from a table, we return a null UUID
|
||||
if src == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// see Parse for required string format
|
||||
u, err := Parse(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Scan: %v", err)
|
||||
}
|
||||
|
||||
*uuid = u
|
||||
|
||||
case []byte:
|
||||
// if an empty UUID comes from a table, we return a null UUID
|
||||
if len(src) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// assumes a simple slice of bytes if 16 bytes
|
||||
// otherwise attempts to parse
|
||||
if len(src) != 16 {
|
||||
return uuid.Scan(string(src))
|
||||
}
|
||||
copy((*uuid)[:], src)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("Scan: unable to scan type %T into UUID", src)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements sql.Valuer so that UUIDs can be written to databases
|
||||
// transparently. Currently, UUIDs map to strings. Please consult
|
||||
// database-specific driver documentation for matching types.
|
||||
func (uuid UUID) Value() (driver.Value, error) {
|
||||
return uuid.String(), nil
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
|
||||
// 1582.
|
||||
type Time int64
|
||||
|
||||
const (
|
||||
lillian = 2299160 // Julian day of 15 Oct 1582
|
||||
unix = 2440587 // Julian day of 1 Jan 1970
|
||||
epoch = unix - lillian // Days between epochs
|
||||
g1582 = epoch * 86400 // seconds between epochs
|
||||
g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
|
||||
)
|
||||
|
||||
var (
|
||||
timeMu sync.Mutex
|
||||
lasttime uint64 // last time we returned
|
||||
clockSeq uint16 // clock sequence for this run
|
||||
|
||||
timeNow = time.Now // for testing
|
||||
)
|
||||
|
||||
// UnixTime converts t the number of seconds and nanoseconds using the Unix
|
||||
// epoch of 1 Jan 1970.
|
||||
func (t Time) UnixTime() (sec, nsec int64) {
|
||||
sec = int64(t - g1582ns100)
|
||||
nsec = (sec % 10000000) * 100
|
||||
sec /= 10000000
|
||||
return sec, nsec
|
||||
}
|
||||
|
||||
// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
|
||||
// clock sequence as well as adjusting the clock sequence as needed. An error
|
||||
// is returned if the current time cannot be determined.
|
||||
func GetTime() (Time, uint16, error) {
|
||||
defer timeMu.Unlock()
|
||||
timeMu.Lock()
|
||||
return getTime()
|
||||
}
|
||||
|
||||
func getTime() (Time, uint16, error) {
|
||||
t := timeNow()
|
||||
|
||||
// If we don't have a clock sequence already, set one.
|
||||
if clockSeq == 0 {
|
||||
setClockSequence(-1)
|
||||
}
|
||||
now := uint64(t.UnixNano()/100) + g1582ns100
|
||||
|
||||
// If time has gone backwards with this clock sequence then we
|
||||
// increment the clock sequence
|
||||
if now <= lasttime {
|
||||
clockSeq = ((clockSeq + 1) & 0x3fff) | 0x8000
|
||||
}
|
||||
lasttime = now
|
||||
return Time(now), clockSeq, nil
|
||||
}
|
||||
|
||||
// ClockSequence returns the current clock sequence, generating one if not
|
||||
// already set. The clock sequence is only used for Version 1 UUIDs.
|
||||
//
|
||||
// The uuid package does not use global static storage for the clock sequence or
|
||||
// the last time a UUID was generated. Unless SetClockSequence is used, a new
|
||||
// random clock sequence is generated the first time a clock sequence is
|
||||
// requested by ClockSequence, GetTime, or NewUUID. (section 4.2.1.1)
|
||||
func ClockSequence() int {
|
||||
defer timeMu.Unlock()
|
||||
timeMu.Lock()
|
||||
return clockSequence()
|
||||
}
|
||||
|
||||
func clockSequence() int {
|
||||
if clockSeq == 0 {
|
||||
setClockSequence(-1)
|
||||
}
|
||||
return int(clockSeq & 0x3fff)
|
||||
}
|
||||
|
||||
// SetClockSequence sets the clock sequence to the lower 14 bits of seq. Setting to
|
||||
// -1 causes a new sequence to be generated.
|
||||
func SetClockSequence(seq int) {
|
||||
defer timeMu.Unlock()
|
||||
timeMu.Lock()
|
||||
setClockSequence(seq)
|
||||
}
|
||||
|
||||
func setClockSequence(seq int) {
|
||||
if seq == -1 {
|
||||
var b [2]byte
|
||||
randomBits(b[:]) // clock sequence
|
||||
seq = int(b[0])<<8 | int(b[1])
|
||||
}
|
||||
oldSeq := clockSeq
|
||||
clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant
|
||||
if oldSeq != clockSeq {
|
||||
lasttime = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
|
||||
// uuid. The time is only defined for version 1 and 2 UUIDs.
|
||||
func (uuid UUID) Time() Time {
|
||||
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
|
||||
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
|
||||
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
|
||||
return Time(time)
|
||||
}
|
||||
|
||||
// ClockSequence returns the clock sequence encoded in uuid.
|
||||
// The clock sequence is only well defined for version 1 and 2 UUIDs.
|
||||
func (uuid UUID) ClockSequence() int {
|
||||
return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// randomBits completely fills slice b with random data.
|
||||
func randomBits(b []byte) {
|
||||
if _, err := io.ReadFull(rander, b); err != nil {
|
||||
panic(err.Error()) // rand should never fail
|
||||
}
|
||||
}
|
||||
|
||||
// xvalues returns the value of a byte as a hexadecimal digit or 255.
|
||||
var xvalues = [256]byte{
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
|
||||
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
}
|
||||
|
||||
// xtob converts hex characters x1 and x2 into a byte.
|
||||
func xtob(x1, x2 byte) (byte, bool) {
|
||||
b1 := xvalues[x1]
|
||||
b2 := xvalues[x2]
|
||||
return (b1 << 4) | b2, b1 != 255 && b2 != 255
|
||||
}
|
|
@ -0,0 +1,245 @@
|
|||
// Copyright 2018 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
|
||||
// 4122.
|
||||
type UUID [16]byte
|
||||
|
||||
// A Version represents a UUID's version.
|
||||
type Version byte
|
||||
|
||||
// A Variant represents a UUID's variant.
|
||||
type Variant byte
|
||||
|
||||
// Constants returned by Variant.
|
||||
const (
|
||||
Invalid = Variant(iota) // Invalid UUID
|
||||
RFC4122 // The variant specified in RFC4122
|
||||
Reserved // Reserved, NCS backward compatibility.
|
||||
Microsoft // Reserved, Microsoft Corporation backward compatibility.
|
||||
Future // Reserved for future definition.
|
||||
)
|
||||
|
||||
var rander = rand.Reader // random function
|
||||
|
||||
// Parse decodes s into a UUID or returns an error. Both the standard UUID
|
||||
// forms of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded as well as the
|
||||
// Microsoft encoding {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} and the raw hex
|
||||
// encoding: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
|
||||
func Parse(s string) (UUID, error) {
|
||||
var uuid UUID
|
||||
switch len(s) {
|
||||
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
case 36:
|
||||
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
case 36 + 9:
|
||||
if strings.ToLower(s[:9]) != "urn:uuid:" {
|
||||
return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9])
|
||||
}
|
||||
s = s[9:]
|
||||
|
||||
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||
case 36 + 2:
|
||||
s = s[1:]
|
||||
|
||||
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
case 32:
|
||||
var ok bool
|
||||
for i := range uuid {
|
||||
uuid[i], ok = xtob(s[i*2], s[i*2+1])
|
||||
if !ok {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
}
|
||||
return uuid, nil
|
||||
default:
|
||||
return uuid, fmt.Errorf("invalid UUID length: %d", len(s))
|
||||
}
|
||||
// s is now at least 36 bytes long
|
||||
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
for i, x := range [16]int{
|
||||
0, 2, 4, 6,
|
||||
9, 11,
|
||||
14, 16,
|
||||
19, 21,
|
||||
24, 26, 28, 30, 32, 34} {
|
||||
v, ok := xtob(s[x], s[x+1])
|
||||
if !ok {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
uuid[i] = v
|
||||
}
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
// ParseBytes is like Parse, except it parses a byte slice instead of a string.
|
||||
func ParseBytes(b []byte) (UUID, error) {
|
||||
var uuid UUID
|
||||
switch len(b) {
|
||||
case 36: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
case 36 + 9: // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
if !bytes.Equal(bytes.ToLower(b[:9]), []byte("urn:uuid:")) {
|
||||
return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9])
|
||||
}
|
||||
b = b[9:]
|
||||
case 36 + 2: // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||
b = b[1:]
|
||||
case 32: // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
var ok bool
|
||||
for i := 0; i < 32; i += 2 {
|
||||
uuid[i/2], ok = xtob(b[i], b[i+1])
|
||||
if !ok {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
}
|
||||
return uuid, nil
|
||||
default:
|
||||
return uuid, fmt.Errorf("invalid UUID length: %d", len(b))
|
||||
}
|
||||
// s is now at least 36 bytes long
|
||||
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
for i, x := range [16]int{
|
||||
0, 2, 4, 6,
|
||||
9, 11,
|
||||
14, 16,
|
||||
19, 21,
|
||||
24, 26, 28, 30, 32, 34} {
|
||||
v, ok := xtob(b[x], b[x+1])
|
||||
if !ok {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
uuid[i] = v
|
||||
}
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
// MustParse is like Parse but panics if the string cannot be parsed.
|
||||
// It simplifies safe initialization of global variables holding compiled UUIDs.
|
||||
func MustParse(s string) UUID {
|
||||
uuid, err := Parse(s)
|
||||
if err != nil {
|
||||
panic(`uuid: Parse(` + s + `): ` + err.Error())
|
||||
}
|
||||
return uuid
|
||||
}
|
||||
|
||||
// FromBytes creates a new UUID from a byte slice. Returns an error if the slice
|
||||
// does not have a length of 16. The bytes are copied from the slice.
|
||||
func FromBytes(b []byte) (uuid UUID, err error) {
|
||||
err = uuid.UnmarshalBinary(b)
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
// Must returns uuid if err is nil and panics otherwise.
|
||||
func Must(uuid UUID, err error) UUID {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return uuid
|
||||
}
|
||||
|
||||
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
// , or "" if uuid is invalid.
|
||||
func (uuid UUID) String() string {
|
||||
var buf [36]byte
|
||||
encodeHex(buf[:], uuid)
|
||||
return string(buf[:])
|
||||
}
|
||||
|
||||
// URN returns the RFC 2141 URN form of uuid,
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
|
||||
func (uuid UUID) URN() string {
|
||||
var buf [36 + 9]byte
|
||||
copy(buf[:], "urn:uuid:")
|
||||
encodeHex(buf[9:], uuid)
|
||||
return string(buf[:])
|
||||
}
|
||||
|
||||
func encodeHex(dst []byte, uuid UUID) {
|
||||
hex.Encode(dst, uuid[:4])
|
||||
dst[8] = '-'
|
||||
hex.Encode(dst[9:13], uuid[4:6])
|
||||
dst[13] = '-'
|
||||
hex.Encode(dst[14:18], uuid[6:8])
|
||||
dst[18] = '-'
|
||||
hex.Encode(dst[19:23], uuid[8:10])
|
||||
dst[23] = '-'
|
||||
hex.Encode(dst[24:], uuid[10:])
|
||||
}
|
||||
|
||||
// Variant returns the variant encoded in uuid.
|
||||
func (uuid UUID) Variant() Variant {
|
||||
switch {
|
||||
case (uuid[8] & 0xc0) == 0x80:
|
||||
return RFC4122
|
||||
case (uuid[8] & 0xe0) == 0xc0:
|
||||
return Microsoft
|
||||
case (uuid[8] & 0xe0) == 0xe0:
|
||||
return Future
|
||||
default:
|
||||
return Reserved
|
||||
}
|
||||
}
|
||||
|
||||
// Version returns the version of uuid.
|
||||
func (uuid UUID) Version() Version {
|
||||
return Version(uuid[6] >> 4)
|
||||
}
|
||||
|
||||
func (v Version) String() string {
|
||||
if v > 15 {
|
||||
return fmt.Sprintf("BAD_VERSION_%d", v)
|
||||
}
|
||||
return fmt.Sprintf("VERSION_%d", v)
|
||||
}
|
||||
|
||||
func (v Variant) String() string {
|
||||
switch v {
|
||||
case RFC4122:
|
||||
return "RFC4122"
|
||||
case Reserved:
|
||||
return "Reserved"
|
||||
case Microsoft:
|
||||
return "Microsoft"
|
||||
case Future:
|
||||
return "Future"
|
||||
case Invalid:
|
||||
return "Invalid"
|
||||
}
|
||||
return fmt.Sprintf("BadVariant%d", int(v))
|
||||
}
|
||||
|
||||
// SetRand sets the random number generator to r, which implements io.Reader.
|
||||
// If r.Read returns an error when the package requests random data then
|
||||
// a panic will be issued.
|
||||
//
|
||||
// Calling SetRand with nil sets the random number generator to the default
|
||||
// generator.
|
||||
func SetRand(r io.Reader) {
|
||||
if r == nil {
|
||||
rander = rand.Reader
|
||||
return
|
||||
}
|
||||
rander = r
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
|
||||
// sequence, and the current time. If the NodeID has not been set by SetNodeID
|
||||
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
|
||||
// be set NewUUID returns nil. If clock sequence has not been set by
|
||||
// SetClockSequence then it will be set automatically. If GetTime fails to
|
||||
// return the current NewUUID returns nil and an error.
|
||||
//
|
||||
// In most cases, New should be used.
|
||||
func NewUUID() (UUID, error) {
|
||||
nodeMu.Lock()
|
||||
if nodeID == zeroID {
|
||||
setNodeInterface("")
|
||||
}
|
||||
nodeMu.Unlock()
|
||||
|
||||
var uuid UUID
|
||||
now, seq, err := GetTime()
|
||||
if err != nil {
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
timeLow := uint32(now & 0xffffffff)
|
||||
timeMid := uint16((now >> 32) & 0xffff)
|
||||
timeHi := uint16((now >> 48) & 0x0fff)
|
||||
timeHi |= 0x1000 // Version 1
|
||||
|
||||
binary.BigEndian.PutUint32(uuid[0:], timeLow)
|
||||
binary.BigEndian.PutUint16(uuid[4:], timeMid)
|
||||
binary.BigEndian.PutUint16(uuid[6:], timeHi)
|
||||
binary.BigEndian.PutUint16(uuid[8:], seq)
|
||||
copy(uuid[10:], nodeID[:])
|
||||
|
||||
return uuid, nil
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import "io"
|
||||
|
||||
// New creates a new random UUID or panics. New is equivalent to
|
||||
// the expression
|
||||
//
|
||||
// uuid.Must(uuid.NewRandom())
|
||||
func New() UUID {
|
||||
return Must(NewRandom())
|
||||
}
|
||||
|
||||
// NewRandom returns a Random (Version 4) UUID.
|
||||
//
|
||||
// The strength of the UUIDs is based on the strength of the crypto/rand
|
||||
// package.
|
||||
//
|
||||
// A note about uniqueness derived from the UUID Wikipedia entry:
|
||||
//
|
||||
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
|
||||
// hit by a meteorite is estimated to be one chance in 17 billion, that
|
||||
// means the probability is about 0.00000000006 (6 × 10−11),
|
||||
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
|
||||
// year and having one duplicate.
|
||||
func NewRandom() (UUID, error) {
|
||||
var uuid UUID
|
||||
_, err := io.ReadFull(rander, uuid[:])
|
||||
if err != nil {
|
||||
return Nil, err
|
||||
}
|
||||
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
|
||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
|
||||
return uuid, nil
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
# github.com/atlassian/go-artifactory v1.2.0
|
||||
github.com/atlassian/go-artifactory/pkg/artifactory
|
||||
# github.com/elazarl/goproxy v0.0.0-20181111060418-2ce16c963a8a
|
||||
github.com/elazarl/goproxy
|
||||
# github.com/google/go-querystring v1.0.0
|
||||
github.com/google/go-querystring/query
|
||||
# github.com/google/uuid v1.1.0
|
||||
github.com/google/uuid
|
Loading…
Reference in New Issue