mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-04 03:27:23 -05:00 
			
		
		
		
	* Create request_id directive #1590 * Address Comments * Fix TestListenerAddrEqual * requestid: Add some tests * Address Comments by tobya * Address Comments
This commit is contained in:
		
							parent
							
								
									b0ab3d4281
								
							
						
					
					
						commit
						133ed18374
					
				@ -24,6 +24,7 @@ import (
 | 
			
		||||
	_ "github.com/mholt/caddy/caddyhttp/proxy"
 | 
			
		||||
	_ "github.com/mholt/caddy/caddyhttp/push"
 | 
			
		||||
	_ "github.com/mholt/caddy/caddyhttp/redirect"
 | 
			
		||||
	_ "github.com/mholt/caddy/caddyhttp/requestid"
 | 
			
		||||
	_ "github.com/mholt/caddy/caddyhttp/rewrite"
 | 
			
		||||
	_ "github.com/mholt/caddy/caddyhttp/root"
 | 
			
		||||
	_ "github.com/mholt/caddy/caddyhttp/status"
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ import (
 | 
			
		||||
// ensure that the standard plugins are in fact plugged in
 | 
			
		||||
// and registered properly; this is a quick/naive way to do it.
 | 
			
		||||
func TestStandardPlugins(t *testing.T) {
 | 
			
		||||
	numStandardPlugins := 31 // importing caddyhttp plugs in this many plugins
 | 
			
		||||
	numStandardPlugins := 32 // importing caddyhttp plugs in this many plugins
 | 
			
		||||
	s := caddy.DescribePlugins()
 | 
			
		||||
	if got, want := strings.Count(s, "\n"), numStandardPlugins+5; got != want {
 | 
			
		||||
		t.Errorf("Expected all standard plugins to be plugged in, got:\n%s", s)
 | 
			
		||||
 | 
			
		||||
@ -205,4 +205,7 @@ const (
 | 
			
		||||
 | 
			
		||||
	// MitmCtxKey is the key for the result of MITM detection
 | 
			
		||||
	MitmCtxKey caddy.CtxKey = "mitm"
 | 
			
		||||
 | 
			
		||||
	// RequestIDCtxKey is the key for the U4 UUID value
 | 
			
		||||
	RequestIDCtxKey caddy.CtxKey = "request_id"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@ -443,6 +443,7 @@ var directives = []string{
 | 
			
		||||
	// services/utilities, or other directives that don't necessarily inject handlers
 | 
			
		||||
	"startup",
 | 
			
		||||
	"shutdown",
 | 
			
		||||
	"requestid",
 | 
			
		||||
	"realip", // github.com/captncraig/caddy-realip
 | 
			
		||||
	"git",    // github.com/abiosoft/caddy-git
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -243,6 +243,9 @@ func (r *replacer) getSubstitution(key string) string {
 | 
			
		||||
	case "{path_escaped}":
 | 
			
		||||
		u, _ := r.request.Context().Value(OriginalURLCtxKey).(url.URL)
 | 
			
		||||
		return url.QueryEscape(u.Path)
 | 
			
		||||
	case "{request_id}":
 | 
			
		||||
		reqid, _ := r.request.Context().Value(RequestIDCtxKey).(string)
 | 
			
		||||
		return reqid
 | 
			
		||||
	case "{rewrite_path}":
 | 
			
		||||
		return r.request.URL.Path
 | 
			
		||||
	case "{rewrite_path_escaped}":
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										34
									
								
								caddyhttp/requestid/requestid.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								caddyhttp/requestid/requestid.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
			
		||||
package requestid
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"github.com/mholt/caddy/caddyhttp/httpserver"
 | 
			
		||||
	uuid "github.com/nu7hatch/gouuid"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Handler is a middleware handler
 | 
			
		||||
type Handler struct {
 | 
			
		||||
	Next httpserver.Handler
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
 | 
			
		||||
	reqid := UUID()
 | 
			
		||||
	c := context.WithValue(r.Context(), httpserver.RequestIDCtxKey, reqid)
 | 
			
		||||
	r = r.WithContext(c)
 | 
			
		||||
 | 
			
		||||
	return h.Next.ServeHTTP(w, r)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UUID returns U4 UUID
 | 
			
		||||
func UUID() string {
 | 
			
		||||
	u4, err := uuid.NewV4()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Printf("[ERROR] generating request ID: %v", err)
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return u4.String()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								caddyhttp/requestid/requestid_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								caddyhttp/requestid/requestid_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
			
		||||
package requestid
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/mholt/caddy/caddyhttp/httpserver"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestRequestID(t *testing.T) {
 | 
			
		||||
	request, err := http.NewRequest("GET", "http://localhost/", nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal("Could not create HTTP request:", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	reqid := UUID()
 | 
			
		||||
 | 
			
		||||
	c := context.WithValue(request.Context(), httpserver.RequestIDCtxKey, reqid)
 | 
			
		||||
 | 
			
		||||
	request = request.WithContext(c)
 | 
			
		||||
 | 
			
		||||
	// See caddyhttp/replacer.go
 | 
			
		||||
	value, _ := request.Context().Value(httpserver.RequestIDCtxKey).(string)
 | 
			
		||||
 | 
			
		||||
	if value == "" {
 | 
			
		||||
		t.Fatal("Request ID should not be empty")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if value != reqid {
 | 
			
		||||
		t.Fatal("Request ID does not match")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								caddyhttp/requestid/setup.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								caddyhttp/requestid/setup.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
			
		||||
package requestid
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/mholt/caddy"
 | 
			
		||||
	"github.com/mholt/caddy/caddyhttp/httpserver"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	caddy.RegisterPlugin("requestid", caddy.Plugin{
 | 
			
		||||
		ServerType: "http",
 | 
			
		||||
		Action:     setup,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setup(c *caddy.Controller) error {
 | 
			
		||||
	for c.Next() {
 | 
			
		||||
		if c.NextArg() {
 | 
			
		||||
			return c.ArgErr() //no arg expected.
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
 | 
			
		||||
		return Handler{Next: next}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										43
									
								
								caddyhttp/requestid/setup_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								caddyhttp/requestid/setup_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,43 @@
 | 
			
		||||
package requestid
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/mholt/caddy"
 | 
			
		||||
	"github.com/mholt/caddy/caddyhttp/httpserver"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestSetup(t *testing.T) {
 | 
			
		||||
	c := caddy.NewTestController("http", `requestid`)
 | 
			
		||||
	err := setup(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Expected no errors, got: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	mids := httpserver.GetConfig(c).Middleware()
 | 
			
		||||
	if len(mids) == 0 {
 | 
			
		||||
		t.Fatal("Expected middleware, got 0 instead")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	handler := mids[0](httpserver.EmptyNext)
 | 
			
		||||
	myHandler, ok := handler.(Handler)
 | 
			
		||||
 | 
			
		||||
	if !ok {
 | 
			
		||||
		t.Fatalf("Expected handler to be type Handler, got: %#v", handler)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !httpserver.SameNext(myHandler.Next, httpserver.EmptyNext) {
 | 
			
		||||
		t.Error("'Next' field of handler was not set properly")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSetupWithArg(t *testing.T) {
 | 
			
		||||
	c := caddy.NewTestController("http", `requestid abc`)
 | 
			
		||||
	err := setup(c)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Errorf("Expected an error, got: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	mids := httpserver.GetConfig(c).Middleware()
 | 
			
		||||
	if len(mids) != 0 {
 | 
			
		||||
		t.Fatal("Expected no middleware")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								vendor/github.com/nu7hatch/gouuid/COPYING
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								vendor/github.com/nu7hatch/gouuid/COPYING
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
Copyright (C) 2011 by Krzysztof Kowalik <chris@nu7hat.ch>
 | 
			
		||||
 | 
			
		||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
 | 
			
		||||
this software and associated documentation files (the "Software"), to deal in
 | 
			
		||||
the Software without restriction, including without limitation the rights to
 | 
			
		||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 | 
			
		||||
of the Software, and to permit persons to whom the Software is furnished to do
 | 
			
		||||
so, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
copies or substantial portions of the Software.
 | 
			
		||||
 | 
			
		||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
SOFTWARE.
 | 
			
		||||
							
								
								
									
										173
									
								
								vendor/github.com/nu7hatch/gouuid/uuid.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								vendor/github.com/nu7hatch/gouuid/uuid.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,173 @@
 | 
			
		||||
// This package provides immutable UUID structs and the functions
 | 
			
		||||
// NewV3, NewV4, NewV5 and Parse() for generating versions 3, 4
 | 
			
		||||
// and 5 UUIDs as specified in RFC 4122.
 | 
			
		||||
//
 | 
			
		||||
// Copyright (C) 2011 by Krzysztof Kowalik <chris@nu7hat.ch>
 | 
			
		||||
package uuid
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/md5"
 | 
			
		||||
	"crypto/rand"
 | 
			
		||||
	"crypto/sha1"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"hash"
 | 
			
		||||
	"regexp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// The UUID reserved variants. 
 | 
			
		||||
const (
 | 
			
		||||
	ReservedNCS       byte = 0x80
 | 
			
		||||
	ReservedRFC4122   byte = 0x40
 | 
			
		||||
	ReservedMicrosoft byte = 0x20
 | 
			
		||||
	ReservedFuture    byte = 0x00
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// The following standard UUIDs are for use with NewV3() or NewV5().
 | 
			
		||||
var (
 | 
			
		||||
	NamespaceDNS, _  = ParseHex("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
 | 
			
		||||
	NamespaceURL, _  = ParseHex("6ba7b811-9dad-11d1-80b4-00c04fd430c8")
 | 
			
		||||
	NamespaceOID, _  = ParseHex("6ba7b812-9dad-11d1-80b4-00c04fd430c8")
 | 
			
		||||
	NamespaceX500, _ = ParseHex("6ba7b814-9dad-11d1-80b4-00c04fd430c8")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Pattern used to parse hex string representation of the UUID.
 | 
			
		||||
// FIXME: do something to consider both brackets at one time,
 | 
			
		||||
// current one allows to parse string with only one opening
 | 
			
		||||
// or closing bracket.
 | 
			
		||||
const hexPattern = "^(urn\\:uuid\\:)?\\{?([a-z0-9]{8})-([a-z0-9]{4})-" +
 | 
			
		||||
	"([1-5][a-z0-9]{3})-([a-z0-9]{4})-([a-z0-9]{12})\\}?$"
 | 
			
		||||
 | 
			
		||||
var re = regexp.MustCompile(hexPattern)
 | 
			
		||||
 | 
			
		||||
// A UUID representation compliant with specification in
 | 
			
		||||
// RFC 4122 document.
 | 
			
		||||
type UUID [16]byte
 | 
			
		||||
 | 
			
		||||
// ParseHex creates a UUID object from given hex string
 | 
			
		||||
// representation. Function accepts UUID string in following
 | 
			
		||||
// formats:
 | 
			
		||||
//
 | 
			
		||||
//     uuid.ParseHex("6ba7b814-9dad-11d1-80b4-00c04fd430c8")
 | 
			
		||||
//     uuid.ParseHex("{6ba7b814-9dad-11d1-80b4-00c04fd430c8}")
 | 
			
		||||
//     uuid.ParseHex("urn:uuid:6ba7b814-9dad-11d1-80b4-00c04fd430c8")
 | 
			
		||||
//
 | 
			
		||||
func ParseHex(s string) (u *UUID, err error) {
 | 
			
		||||
	md := re.FindStringSubmatch(s)
 | 
			
		||||
	if md == nil {
 | 
			
		||||
		err = errors.New("Invalid UUID string")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	hash := md[2] + md[3] + md[4] + md[5] + md[6]
 | 
			
		||||
	b, err := hex.DecodeString(hash)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	u = new(UUID)
 | 
			
		||||
	copy(u[:], b)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Parse creates a UUID object from given bytes slice.
 | 
			
		||||
func Parse(b []byte) (u *UUID, err error) {
 | 
			
		||||
	if len(b) != 16 {
 | 
			
		||||
		err = errors.New("Given slice is not valid UUID sequence")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	u = new(UUID)
 | 
			
		||||
	copy(u[:], b)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Generate a UUID based on the MD5 hash of a namespace identifier
 | 
			
		||||
// and a name.
 | 
			
		||||
func NewV3(ns *UUID, name []byte) (u *UUID, err error) {
 | 
			
		||||
	if ns == nil {
 | 
			
		||||
		err = errors.New("Invalid namespace UUID")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	u = new(UUID)
 | 
			
		||||
	// Set all bits to MD5 hash generated from namespace and name.
 | 
			
		||||
	u.setBytesFromHash(md5.New(), ns[:], name)
 | 
			
		||||
	u.setVariant(ReservedRFC4122)
 | 
			
		||||
	u.setVersion(3)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Generate a random UUID.
 | 
			
		||||
func NewV4() (u *UUID, err error) {
 | 
			
		||||
	u = new(UUID)
 | 
			
		||||
	// Set all bits to randomly (or pseudo-randomly) chosen values.
 | 
			
		||||
	_, err = rand.Read(u[:])
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	u.setVariant(ReservedRFC4122)
 | 
			
		||||
	u.setVersion(4)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Generate a UUID based on the SHA-1 hash of a namespace identifier
 | 
			
		||||
// and a name.
 | 
			
		||||
func NewV5(ns *UUID, name []byte) (u *UUID, err error) {
 | 
			
		||||
	u = new(UUID)
 | 
			
		||||
	// Set all bits to truncated SHA1 hash generated from namespace
 | 
			
		||||
	// and name.
 | 
			
		||||
	u.setBytesFromHash(sha1.New(), ns[:], name)
 | 
			
		||||
	u.setVariant(ReservedRFC4122)
 | 
			
		||||
	u.setVersion(5)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Generate a MD5 hash of a namespace and a name, and copy it to the
 | 
			
		||||
// UUID slice.
 | 
			
		||||
func (u *UUID) setBytesFromHash(hash hash.Hash, ns, name []byte) {
 | 
			
		||||
	hash.Write(ns[:])
 | 
			
		||||
	hash.Write(name)
 | 
			
		||||
	copy(u[:], hash.Sum([]byte{})[:16])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Set the two most significant bits (bits 6 and 7) of the
 | 
			
		||||
// clock_seq_hi_and_reserved to zero and one, respectively.
 | 
			
		||||
func (u *UUID) setVariant(v byte) {
 | 
			
		||||
	switch v {
 | 
			
		||||
	case ReservedNCS:
 | 
			
		||||
		u[8] = (u[8] | ReservedNCS) & 0xBF
 | 
			
		||||
	case ReservedRFC4122:
 | 
			
		||||
		u[8] = (u[8] | ReservedRFC4122) & 0x7F
 | 
			
		||||
	case ReservedMicrosoft:
 | 
			
		||||
		u[8] = (u[8] | ReservedMicrosoft) & 0x3F
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Variant returns the UUID Variant, which determines the internal
 | 
			
		||||
// layout of the UUID. This will be one of the constants: RESERVED_NCS,
 | 
			
		||||
// RFC_4122, RESERVED_MICROSOFT, RESERVED_FUTURE.
 | 
			
		||||
func (u *UUID) Variant() byte {
 | 
			
		||||
	if u[8]&ReservedNCS == ReservedNCS {
 | 
			
		||||
		return ReservedNCS
 | 
			
		||||
	} else if u[8]&ReservedRFC4122 == ReservedRFC4122 {
 | 
			
		||||
		return ReservedRFC4122
 | 
			
		||||
	} else if u[8]&ReservedMicrosoft == ReservedMicrosoft {
 | 
			
		||||
		return ReservedMicrosoft
 | 
			
		||||
	}
 | 
			
		||||
	return ReservedFuture
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Set the four most significant bits (bits 12 through 15) of the
 | 
			
		||||
// time_hi_and_version field to the 4-bit version number.
 | 
			
		||||
func (u *UUID) setVersion(v byte) {
 | 
			
		||||
	u[6] = (u[6] & 0xF) | (v << 4)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Version returns a version number of the algorithm used to
 | 
			
		||||
// generate the UUID sequence.
 | 
			
		||||
func (u *UUID) Version() uint {
 | 
			
		||||
	return uint(u[6] >> 4)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Returns unparsed version of the generated UUID sequence.
 | 
			
		||||
func (u *UUID) String() string {
 | 
			
		||||
	return fmt.Sprintf("%x-%x-%x-%x-%x", u[0:4], u[4:6], u[6:8], u[8:10], u[10:])
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								vendor/manifest
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								vendor/manifest
									
									
									
									
										vendored
									
									
								
							@ -165,6 +165,14 @@
 | 
			
		||||
			"branch": "master",
 | 
			
		||||
			"notests": true
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"importpath": "github.com/nu7hatch/gouuid",
 | 
			
		||||
			"repository": "https://github.com/nu7hatch/gouuid",
 | 
			
		||||
			"vcs": "git",
 | 
			
		||||
			"revision": "179d4d0c4d8d407a32af483c2354df1d2c91e6c3",
 | 
			
		||||
			"branch": "master",
 | 
			
		||||
			"notests": true
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"importpath": "github.com/russross/blackfriday",
 | 
			
		||||
			"repository": "https://github.com/russross/blackfriday",
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user