<commit_before>package server

import (
	"encoding/json"
	"net/http"
	"path"
	"time"

	"strings"

	"net/url"

	"io/ioutil"

	"net"

	"fmt"

	"os"

	"github.com/asaskevich/govalidator"
	"github.com/ellcrys/cocoon/core/config"
	"github.com/ellcrys/cocoon/core/connector/server/proto_connector"
	"github.com/ellcrys/util"
	"github.com/gorilla/mux"
	logging "github.com/op/go-logging"
	"github.com/pkg/errors"
	context "golang.org/x/net/context"
)

var httpLog *logging.Logger

// MaxBytesRead limits the number of bytes to read per request
const MaxBytesRead = 10000000

// InvokeRequest describes the body of a structured invoke request
type InvokeRequest struct {
	ID       string   `json:"id"`
	Function string   `json:"function"`
	Params   []string `json:"params"`
}

// InvokeError describes an invoke error
type InvokeError struct {
	Code  string `json:"code"`
	Msg   string `json:"msg"`
	Error bool   `json:"error"`
}

// Success describes a successful response from a cocoon code
type Success struct {
	Body []byte `json:"body"`
}

// headerToMap converts http.Header to a map[string]string.
// Header keys with multiple values are joined to as a single string with
// a comma `,` as a delimeter.
func headerToMap(header http.Header) map[string]string {
	var newMap = make(map[string]string)
	for h, v := range header {
		newMap[h] = strings.Join(v, ",")
	}
	return newMap
}

// headerToMap converts url.Values to a map[string]string.
// Values keys with multiple values are joined to as a single string with
// a comma `,` as a delimeter.
func valuesToMap(values url.Values) map[string]string {
	var newMap = make(map[string]string)
	for h, v := range values {
		newMap[h] = strings.Join(v, ",")
	}
	return newMap
}

// prepareInvokeHeader collects and sets values to be included in the header
// that will be sent to the cocoon code.
func prepareInvokeHeader(w http.ResponseWriter, r *http.Request) http.Header {
	header := r.Header
	header.Set("Method", r.Method)
	header.Set("Host", r.Host)
	header.Set("Remote-Addr", r.RemoteAddr)
	if len(r.Cookies()) > 0 {
		cookiesToJSON, _ := util.ToJSON(r.Cookies())
		header.Set("Cookies", string(cookiesToJSON))
	}
	header.Set("Referer", r.Referer())
	header.Set("URL", r.URL.String())
	header.Set("Request-Uri", r.RequestURI)
	if len(r.URL.Query()) > 0 {
		queryToJSON, _ := util.ToJSON(valuesToMap(r.URL.Query()))
		header.Set("Query", string(queryToJSON))
	}

	r.ParseForm()
	r.ParseMultipartForm(MaxBytesRead)
	if len(r.PostForm) > 0 {
		formValToJSON, _ := util.ToJSON(valuesToMap(r.PostForm))
		header.Set("Form", string(formValToJSON))
	}
	return header
}

// HTTP defines a structure for an HTTP server
// that provides REST API services.
type HTTP struct {
	rpc *RPC
}

// NewHTTP creates an new http server instance
func NewHTTP(rpc *RPC) *HTTP {
	httpLog = config.MakeLogger("connector.http")
	return &HTTP{rpc}
}

// getRouter returns the router
func (s *HTTP) getRouter() *mux.Router {
	r := mux.NewRouter()
	r.HandleFunc("/", s.index)
	r.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(path.Join(os.Getenv("SHARED_DIR"), "static")))))

	// v1 API routes
	v1 := r.PathPrefix("/v1").Subrouter()
	v1.HandleFunc("/invoke", s.invokeCocoonCode)

	return r
}

// Start starts the http server. Passes true to the startedCh channel
// when started
func (s *HTTP) Start(addr string, startedCh chan bool) error {
	_, port, err := net.SplitHostPort(addr)
	if err != nil {
		log.Errorf("%+v", errors.Wrap(err, "failed to parse address"))
		startedCh <- false
		return err
	}

	time.AfterFunc(2*time.Second, func() {
		httpLog.Infof("Started HTTP API server @ :%s", port)
		startedCh <- true
	})

	return http.ListenAndServe(addr, s.getRouter())
}

// index page
func (s *HTTP) index(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
	w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")

	fmt.Fprint(w, "Hello!")
}

// invokeCocoonCode handles cocoon code invocation
func (s *HTTP) invokeCocoonCode(w http.ResponseWriter, r *http.Request) {

	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
	w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, Authorization")

	// return 200 for options request
	if r.Method == http.MethodOptions {
		return
	}

	var err error
	var resp *proto_connector.Response
	var invokeRequest InvokeRequest
	var body []byte
	var ctx context.Context
	var cc context.CancelFunc

	defer r.Body.Close()

	// prepare header
	r.Body = http.MaxBytesReader(w, r.Body, MaxBytesRead)
	preparedHeader := prepareInvokeHeader(w, r)

	// set response content type
	w.Header().Set("Content-Type", "application/json")

	// get request are considered as unstructured
	if r.Method == "GET" {
		goto UNSTRUCTURED
	}

	// get body
	if r.Method == "POST" {
		body, err = ioutil.ReadAll(r.Body)
		if err != nil {
			w.WriteHeader(500)
			json.NewEncoder(w).Encode(InvokeError{
				Error: true,
				Code:  "server_error",
				Msg:   "Invalid invoke body structure",
			})
			return
		}
	}

	// non-json content type are considered as unstructured request
	if r.Header.Get("Content-Type") != "application/json" {
		goto UNSTRUCTURED
	}

	// attempt to decode json body to an invoke request structure
	err = util.FromJSON(body, &invokeRequest)
	if err != nil {
		w.WriteHeader(400)
		json.NewEncoder(w).Encode(InvokeError{
			Error: true,
			Code:  "1",
			Msg:   "Invalid invoke body structure",
		})
		return
	}

	// expect ID, if set to be a UUIDv4 string
	if len(invokeRequest.ID) > 0 && !govalidator.IsUUIDv4(invokeRequest.ID) {
		w.WriteHeader(400)
		json.NewEncoder(w).Encode(InvokeError{
			Error: true,
			Code:  "2",
			Msg:   "invalid ID. ID must be a UUIDv4 value",
		})
		return
	}

	// return error if request function is not set
	if len(strings.TrimSpace(invokeRequest.Function)) == 0 {
		w.WriteHeader(400)
		json.NewEncoder(w).Encode(InvokeError{
			Error: true,
			Code:  "3",
			Msg:   "function is required",
		})
		return
	}

	// set ID of not set
	if len(invokeRequest.ID) == 0 {
		invokeRequest.ID = util.UUID4()
	}

	preparedHeader.Set("Transaction-Id", invokeRequest.ID)
	preparedHeader.Set("Structured", "yes")

	ctx, cc = context.WithTimeout(context.Background(), 2*time.Minute)
	defer cc()
	resp, err = s.rpc.cocoonCodeOps.Handle(ctx, &proto_connector.CocoonCodeOperation{
		ID:       invokeRequest.ID,
		Function: invokeRequest.Function,
		Params:   invokeRequest.Params,
		Header:   headerToMap(preparedHeader),
	})
	if err != nil {
		w.WriteHeader(500)
		json.NewEncoder(w).Encode(InvokeError{
			Error: true,
			Code:  "code_error",
			Msg:   err.Error(),
		})
		return
	}

	if r.Method == "OPTIONS" {
		return
	}

	json.NewEncoder(w).Encode(Success{
		Body: resp.GetBody(),
	})
	return

UNSTRUCTURED:

	preparedHeader.Set("Structured", "no")

	if r.Method != "GET" {
		preparedHeader.Set("Raw-Body", string(body))
	}

	ctx, cc = context.WithTimeout(context.Background(), 2*time.Minute)
	defer cc()
	resp, err = s.rpc.cocoonCodeOps.Handle(ctx, &proto_connector.CocoonCodeOperation{
		Header: headerToMap(preparedHeader),
	})
	if err != nil {
		w.WriteHeader(500)
		json.NewEncoder(w).Encode(InvokeError{
			Error: true,
			Code:  "code_error",
			Msg:   err.Error(),
		})
		return
	}

	w.WriteHeader(200)
	json.NewEncoder(w).Encode(Success{
		Body: resp.GetBody(),
	})
}
<commit_msg>fixed static file route<commit_after>package server

import (
	"encoding/json"
	"net/http"
	"path"
	"time"

	"strings"

	"net/url"

	"io/ioutil"

	"net"

	"fmt"

	"os"

	"github.com/asaskevich/govalidator"
	"github.com/ellcrys/cocoon/core/config"
	"github.com/ellcrys/cocoon/core/connector/server/proto_connector"
	"github.com/ellcrys/util"
	"github.com/gorilla/mux"
	logging "github.com/op/go-logging"
	"github.com/pkg/errors"
	context "golang.org/x/net/context"
)

var httpLog *logging.Logger

// MaxBytesRead limits the number of bytes to read per request
const MaxBytesRead = 10000000

// InvokeRequest describes the body of a structured invoke request
type InvokeRequest struct {
	ID       string   `json:"id"`
	Function string   `json:"function"`
	Params   []string `json:"params"`
}

// InvokeError describes an invoke error
type InvokeError struct {
	Code  string `json:"code"`
	Msg   string `json:"msg"`
	Error bool   `json:"error"`
}

// Success describes a successful response from a cocoon code
type Success struct {
	Body []byte `json:"body"`
}

// headerToMap converts http.Header to a map[string]string.
// Header keys with multiple values are joined to as a single string with
// a comma `,` as a delimeter.
func headerToMap(header http.Header) map[string]string {
	var newMap = make(map[string]string)
	for h, v := range header {
		newMap[h] = strings.Join(v, ",")
	}
	return newMap
}

// headerToMap converts url.Values to a map[string]string.
// Values keys with multiple values are joined to as a single string with
// a comma `,` as a delimeter.
func valuesToMap(values url.Values) map[string]string {
	var newMap = make(map[string]string)
	for h, v := range values {
		newMap[h] = strings.Join(v, ",")
	}
	return newMap
}

// prepareInvokeHeader collects and sets values to be included in the header
// that will be sent to the cocoon code.
func prepareInvokeHeader(w http.ResponseWriter, r *http.Request) http.Header {
	header := r.Header
	header.Set("Method", r.Method)
	header.Set("Host", r.Host)
	header.Set("Remote-Addr", r.RemoteAddr)
	if len(r.Cookies()) > 0 {
		cookiesToJSON, _ := util.ToJSON(r.Cookies())
		header.Set("Cookies", string(cookiesToJSON))
	}
	header.Set("Referer", r.Referer())
	header.Set("URL", r.URL.String())
	header.Set("Request-Uri", r.RequestURI)
	if len(r.URL.Query()) > 0 {
		queryToJSON, _ := util.ToJSON(valuesToMap(r.URL.Query()))
		header.Set("Query", string(queryToJSON))
	}

	r.ParseForm()
	r.ParseMultipartForm(MaxBytesRead)
	if len(r.PostForm) > 0 {
		formValToJSON, _ := util.ToJSON(valuesToMap(r.PostForm))
		header.Set("Form", string(formValToJSON))
	}
	return header
}

// HTTP defines a structure for an HTTP server
// that provides REST API services.
type HTTP struct {
	rpc *RPC
}

// NewHTTP creates an new http server instance
func NewHTTP(rpc *RPC) *HTTP {
	httpLog = config.MakeLogger("connector.http")
	return &HTTP{rpc}
}

// getRouter returns the router
func (s *HTTP) getRouter() *mux.Router {
	r := mux.NewRouter()
	r.HandleFunc("/", s.index)

	// Add route to static files in the shared directory
	fs := http.StripPrefix("/static/", http.FileServer(http.Dir(path.Join(os.Getenv("SHARED_DIR"), "static"))))
	r.PathPrefix("/static/").Handler(fs)

	// v1 API routes
	v1 := r.PathPrefix("/v1").Subrouter()
	v1.HandleFunc("/invoke", s.invokeCocoonCode)

	return r
}

// Start starts the http server. Passes true to the startedCh channel
// when started
func (s *HTTP) Start(addr string, startedCh chan bool) error {
	_, port, err := net.SplitHostPort(addr)
	if err != nil {
		log.Errorf("%+v", errors.Wrap(err, "failed to parse address"))
		startedCh <- false
		return err
	}

	time.AfterFunc(2*time.Second, func() {
		httpLog.Infof("Started HTTP API server @ :%s", port)
		startedCh <- true
	})

	return http.ListenAndServe(addr, s.getRouter())
}

// index page
func (s *HTTP) index(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
	w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")

	fmt.Fprint(w, "Hello!")
}

// invokeCocoonCode handles cocoon code invocation
func (s *HTTP) invokeCocoonCode(w http.ResponseWriter, r *http.Request) {

	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
	w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, Authorization")

	// return 200 for options request
	if r.Method == http.MethodOptions {
		return
	}

	var err error
	var resp *proto_connector.Response
	var invokeRequest InvokeRequest
	var body []byte
	var ctx context.Context
	var cc context.CancelFunc

	defer r.Body.Close()

	// prepare header
	r.Body = http.MaxBytesReader(w, r.Body, MaxBytesRead)
	preparedHeader := prepareInvokeHeader(w, r)

	// set response content type
	w.Header().Set("Content-Type", "application/json")

	// get request are considered as unstructured
	if r.Method == "GET" {
		goto UNSTRUCTURED
	}

	// get body
	if r.Method == "POST" {
		body, err = ioutil.ReadAll(r.Body)
		if err != nil {
			w.WriteHeader(500)
			json.NewEncoder(w).Encode(InvokeError{
				Error: true,
				Code:  "server_error",
				Msg:   "Invalid invoke body structure",
			})
			return
		}
	}

	// non-json content type are considered as unstructured request
	if r.Header.Get("Content-Type") != "application/json" {
		goto UNSTRUCTURED
	}

	// attempt to decode json body to an invoke request structure
	err = util.FromJSON(body, &invokeRequest)
	if err != nil {
		w.WriteHeader(400)
		json.NewEncoder(w).Encode(InvokeError{
			Error: true,
			Code:  "1",
			Msg:   "Invalid invoke body structure",
		})
		return
	}

	// expect ID, if set to be a UUIDv4 string
	if len(invokeRequest.ID) > 0 && !govalidator.IsUUIDv4(invokeRequest.ID) {
		w.WriteHeader(400)
		json.NewEncoder(w).Encode(InvokeError{
			Error: true,
			Code:  "2",
			Msg:   "invalid ID. ID must be a UUIDv4 value",
		})
		return
	}

	// return error if request function is not set
	if len(strings.TrimSpace(invokeRequest.Function)) == 0 {
		w.WriteHeader(400)
		json.NewEncoder(w).Encode(InvokeError{
			Error: true,
			Code:  "3",
			Msg:   "function is required",
		})
		return
	}

	// set ID of not set
	if len(invokeRequest.ID) == 0 {
		invokeRequest.ID = util.UUID4()
	}

	preparedHeader.Set("Transaction-Id", invokeRequest.ID)
	preparedHeader.Set("Structured", "yes")

	ctx, cc = context.WithTimeout(context.Background(), 2*time.Minute)
	defer cc()
	resp, err = s.rpc.cocoonCodeOps.Handle(ctx, &proto_connector.CocoonCodeOperation{
		ID:       invokeRequest.ID,
		Function: invokeRequest.Function,
		Params:   invokeRequest.Params,
		Header:   headerToMap(preparedHeader),
	})
	if err != nil {
		w.WriteHeader(500)
		json.NewEncoder(w).Encode(InvokeError{
			Error: true,
			Code:  "code_error",
			Msg:   err.Error(),
		})
		return
	}

	if r.Method == "OPTIONS" {
		return
	}

	json.NewEncoder(w).Encode(Success{
		Body: resp.GetBody(),
	})
	return

UNSTRUCTURED:

	preparedHeader.Set("Structured", "no")

	if r.Method != "GET" {
		preparedHeader.Set("Raw-Body", string(body))
	}

	ctx, cc = context.WithTimeout(context.Background(), 2*time.Minute)
	defer cc()
	resp, err = s.rpc.cocoonCodeOps.Handle(ctx, &proto_connector.CocoonCodeOperation{
		Header: headerToMap(preparedHeader),
	})
	if err != nil {
		w.WriteHeader(500)
		json.NewEncoder(w).Encode(InvokeError{
			Error: true,
			Code:  "code_error",
			Msg:   err.Error(),
		})
		return
	}

	w.WriteHeader(200)
	json.NewEncoder(w).Encode(Success{
		Body: resp.GetBody(),
	})
}
<|endoftext|>


<commit_before>package gateway

import (
	"bytes"
	"encoding/base64"
	"io"
	"io/ioutil"
	"net/http"
	"runtime/pprof"
	"strconv"
	"strings"
	"time"

	cache "github.com/pmylund/go-cache"

	"github.com/TykTechnologies/tyk/config"
	"github.com/TykTechnologies/tyk/ctx"
	"github.com/TykTechnologies/tyk/headers"
	"github.com/TykTechnologies/tyk/request"
	"github.com/TykTechnologies/tyk/user"
)

const (
	keyDataDeveloperID    = "tyk_developer_id"
	keyDataDeveloperEmail = "tyk_developer_email"
)

var (
	// key session memory cache
	SessionCache = cache.New(10*time.Second, 5*time.Second)

	// org session memory cache
	ExpiryCache = cache.New(600*time.Second, 10*time.Minute)

	// memory cache to store arbitrary items
	UtilCache = cache.New(time.Hour, 10*time.Minute)
)

type ProxyResponse struct {
	Response *http.Response
	// UpstreamLatency the time it takes to do roundtrip to upstream. Total time
	// taken for the gateway to receive response from upstream host.
	UpstreamLatency time.Duration
}

type ReturningHttpHandler interface {
	ServeHTTP(http.ResponseWriter, *http.Request) ProxyResponse
	ServeHTTPForCache(http.ResponseWriter, *http.Request) ProxyResponse
	CopyResponse(io.Writer, io.Reader, time.Duration)
}

// SuccessHandler represents the final ServeHTTP() request for a proxied API request
type SuccessHandler struct {
	BaseMiddleware
}

func tagHeaders(r *http.Request, th []string, tags []string) []string {
	for k, v := range r.Header {
		cleanK := strings.ToLower(k)
		ok := false
		for _, hname := range th {
			if hname == cleanK {
				ok = true
				break
			}
		}

		if ok {
			for _, val := range v {
				tagName := cleanK + "-" + val
				tags = append(tags, tagName)
			}
		}
	}

	return tags
}

func addVersionHeader(w http.ResponseWriter, r *http.Request, globalConf config.Config) {
	if ctxGetDefaultVersion(r) {
		if vinfo := ctxGetVersionInfo(r); vinfo != nil {
			if globalConf.VersionHeader != "" {
				w.Header().Set(globalConf.VersionHeader, vinfo.Name)
			}
		}
	}
}

func estimateTagsCapacity(session *user.SessionState, apiSpec *APISpec) int {
	size := 5 // that number of tags expected to be added at least before we record hit
	if session != nil {
		size += len(session.Tags)

		size += len(session.ApplyPolicies)

		if session.MetaData != nil {
			if _, ok := session.MetaData[keyDataDeveloperID]; ok {
				size += 1
			}
		}
	}

	if apiSpec.GlobalConfig.DBAppConfOptions.NodeIsSegmented {
		size += len(apiSpec.GlobalConfig.DBAppConfOptions.Tags)
	}

	size += len(apiSpec.TagHeaders)

	return size
}

func getSessionTags(session *user.SessionState) []string {
	tags := make([]string, 0, len(session.Tags)+len(session.ApplyPolicies)+1)

	// add policy IDs
	for _, polID := range session.ApplyPolicies {
		tags = append(tags, "pol-"+polID)
	}

	if session.MetaData != nil {
		if developerID, ok := session.MetaData[keyDataDeveloperID].(string); ok {
			tags = append(tags, "dev-"+developerID)
		}
	}

	tags = append(tags, session.Tags...)

	return tags
}

func (s *SuccessHandler) RecordHit(r *http.Request, timing Latency, code int, responseCopy *http.Response) {

	if s.Spec.DoNotTrack || ctxGetDoNotTrack(r) {
		return
	}

	ip := request.RealIP(r)
	if s.Spec.GlobalConfig.StoreAnalytics(ip) {

		t := time.Now()

		// Track the key ID if it exists
		token := ctxGetAuthToken(r)

		// Track version data
		version := s.Spec.getVersionFromRequest(r)
		if version == "" {
			version = "Non Versioned"
		}

		// If OAuth, we need to grab it from the session, which may or may not exist
		oauthClientID := ""
		var alias string
		session := ctxGetSession(r)
		tags := make([]string, 0, estimateTagsCapacity(session, s.Spec))
		if session != nil {
			oauthClientID = session.OauthClientID
			tags = append(tags, getSessionTags(session)...)
			alias = session.Alias
		}

		if len(s.Spec.TagHeaders) > 0 {
			tags = tagHeaders(r, s.Spec.TagHeaders, tags)
		}

		rawRequest := ""
		rawResponse := ""

		if recordDetail(r, s.Spec) {
			// Get the wire format representation
			var wireFormatReq bytes.Buffer
			r.Write(&wireFormatReq)
			rawRequest = base64.StdEncoding.EncodeToString(wireFormatReq.Bytes())
			// responseCopy, unlike requestCopy, can be nil
			// here - if the response was cached in
			// mw_redis_cache, RecordHit gets passed a nil
			// response copy.
			// TODO: pass a copy of the cached response in
			// mw_redis_cache instead? is there a reason not
			// to include that in the analytics?
			if responseCopy != nil {
				contents, err := ioutil.ReadAll(responseCopy.Body)
				if err != nil {
					log.Error("Couldn't read response body", err)
				}

				responseCopy.Body = respBodyReader(r, responseCopy)

				// Get the wire format representation
				var wireFormatRes bytes.Buffer
				responseCopy.Write(&wireFormatRes)
				responseCopy.Body = ioutil.NopCloser(bytes.NewBuffer(contents))
				rawResponse = base64.StdEncoding.EncodeToString(wireFormatRes.Bytes())
			}
		}

		trackEP := false
		trackedPath := r.URL.Path
		if p := ctxGetTrackedPath(r); p != "" {
			trackEP = true
			trackedPath = p
		}

		host := r.URL.Host
		if host == "" && s.Spec.target != nil {
			host = s.Spec.target.Host
		}

		record := AnalyticsRecord{
			r.Method,
			host,
			trackedPath,
			r.URL.Path,
			r.ContentLength,
			r.Header.Get(headers.UserAgent),
			t.Day(),
			t.Month(),
			t.Year(),
			t.Hour(),
			code,
			token,
			t,
			version,
			s.Spec.Name,
			s.Spec.APIID,
			s.Spec.OrgID,
			oauthClientID,
			timing.Total,
			timing,
			rawRequest,
			rawResponse,
			ip,
			GeoData{},
			NetworkStats{},
			tags,
			alias,
			trackEP,
			t,
		}

		if s.Spec.GlobalConfig.AnalyticsConfig.EnableGeoIP {
			record.GetGeo(ip)
		}

		expiresAfter := s.Spec.ExpireAnalyticsAfter
		if s.Spec.GlobalConfig.EnforceOrgDataAge {
			orgExpireDataTime := s.OrgSessionExpiry(s.Spec.OrgID)

			if orgExpireDataTime > 0 {
				expiresAfter = orgExpireDataTime
			}
		}

		record.SetExpiry(expiresAfter)

		if s.Spec.GlobalConfig.AnalyticsConfig.NormaliseUrls.Enabled {
			record.NormalisePath(&s.Spec.GlobalConfig)
		}

		analytics.RecordHit(&record)
	}

	// Report in health check
	reportHealthValue(s.Spec, RequestLog, strconv.FormatInt(timing.Total, 10))

	if memProfFile != nil {
		pprof.WriteHeapProfile(memProfFile)
	}
}

func recordDetail(r *http.Request, spec *APISpec) bool {

	// when streaming in grpc, we do not record the request
	if IsGrpcStreaming(r) {
		return false
	}

	if spec.EnableDetailedRecording {
		return true
	}

	session := ctxGetSession(r)
	if session != nil {
		if session.EnableDetailedRecording || session.EnableDetailRecording {
			return true
		}
	}

	// Are we even checking?
	if !spec.GlobalConfig.EnforceOrgDataDetailLogging {
		return spec.GlobalConfig.AnalyticsConfig.EnableDetailedRecording
	}

	// We are, so get session data
	ses := r.Context().Value(ctx.OrgSessionContext)
	if ses == nil {
		// no session found, use global config
		return spec.GlobalConfig.AnalyticsConfig.EnableDetailedRecording
	}

	// Session found
	sess := ses.(*user.SessionState)
	return sess.EnableDetailRecording || sess.EnableDetailedRecording
}

// ServeHTTP will store the request details in the analytics store if necessary and proxy the request to it's
// final destination, this is invoked by the ProxyHandler or right at the start of a request chain if the URL
// Spec states the path is Ignored
func (s *SuccessHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) *http.Response {
	log.Debug("Started proxy")
	defer s.Base().UpdateRequestSession(r)

	versionDef := s.Spec.VersionDefinition
	if !s.Spec.VersionData.NotVersioned && versionDef.Location == "url" && versionDef.StripPath {
		part := s.Spec.getVersionFromRequest(r)

		log.Info("Stripping version from url: ", part)

		r.URL.Path = strings.Replace(r.URL.Path, part+"/", "", 1)
		r.URL.RawPath = strings.Replace(r.URL.RawPath, part+"/", "", 1)
	}

	// Make sure we get the correct target URL
	if s.Spec.Proxy.StripListenPath {
		log.Debug("Stripping: ", s.Spec.Proxy.ListenPath)
		r.URL.Path = s.Spec.StripListenPath(r, r.URL.Path)
		r.URL.RawPath = s.Spec.StripListenPath(r, r.URL.RawPath)
		log.Debug("Upstream Path is: ", r.URL.Path)
	}

	addVersionHeader(w, r, s.Spec.GlobalConfig)

	t1 := time.Now()
	resp := s.Proxy.ServeHTTP(w, r)

	millisec := DurationToMillisecond(time.Since(t1))
	log.Debug("Upstream request took (ms): ", millisec)

	if resp.Response != nil {
		latency := Latency{
			Total:    int64(millisec),
			Upstream: int64(DurationToMillisecond(resp.UpstreamLatency)),
		}
		s.RecordHit(r, latency, resp.Response.StatusCode, resp.Response)
	}
	log.Debug("Done proxy")
	return nil
}

// ServeHTTPWithCache will store the request details in the analytics store if necessary and proxy the request to it's
// final destination, this is invoked by the ProxyHandler or right at the start of a request chain if the URL
// Spec states the path is Ignored Itwill also return a response object for the cache
func (s *SuccessHandler) ServeHTTPWithCache(w http.ResponseWriter, r *http.Request) ProxyResponse {

	versionDef := s.Spec.VersionDefinition
	if !s.Spec.VersionData.NotVersioned && versionDef.Location == "url" && versionDef.StripPath {
		part := s.Spec.getVersionFromRequest(r)

		log.Debug("Stripping version from URL: ", part)

		r.URL.Path = strings.Replace(r.URL.Path, part+"/", "", 1)
		r.URL.RawPath = strings.Replace(r.URL.RawPath, part+"/", "", 1)
	}

	// Make sure we get the correct target URL
	if s.Spec.Proxy.StripListenPath {
		r.URL.Path = s.Spec.StripListenPath(r, r.URL.Path)
		r.URL.RawPath = s.Spec.StripListenPath(r, r.URL.RawPath)
	}

	t1 := time.Now()
	inRes := s.Proxy.ServeHTTPForCache(w, r)
	millisec := DurationToMillisecond(time.Since(t1))

	addVersionHeader(w, r, s.Spec.GlobalConfig)

	log.Debug("Upstream request took (ms): ", millisec)

	if inRes.Response != nil {
		latency := Latency{
			Total:    int64(millisec),
			Upstream: int64(DurationToMillisecond(inRes.UpstreamLatency)),
		}
		s.RecordHit(r, latency, inRes.Response.StatusCode, inRes.Response)
	}

	return inRes
}
<commit_msg>relax "Stripping version from url" log level (#2934)<commit_after>package gateway

import (
	"bytes"
	"encoding/base64"
	"io"
	"io/ioutil"
	"net/http"
	"runtime/pprof"
	"strconv"
	"strings"
	"time"

	cache "github.com/pmylund/go-cache"

	"github.com/TykTechnologies/tyk/config"
	"github.com/TykTechnologies/tyk/ctx"
	"github.com/TykTechnologies/tyk/headers"
	"github.com/TykTechnologies/tyk/request"
	"github.com/TykTechnologies/tyk/user"
)

const (
	keyDataDeveloperID    = "tyk_developer_id"
	keyDataDeveloperEmail = "tyk_developer_email"
)

var (
	// key session memory cache
	SessionCache = cache.New(10*time.Second, 5*time.Second)

	// org session memory cache
	ExpiryCache = cache.New(600*time.Second, 10*time.Minute)

	// memory cache to store arbitrary items
	UtilCache = cache.New(time.Hour, 10*time.Minute)
)

type ProxyResponse struct {
	Response *http.Response
	// UpstreamLatency the time it takes to do roundtrip to upstream. Total time
	// taken for the gateway to receive response from upstream host.
	UpstreamLatency time.Duration
}

type ReturningHttpHandler interface {
	ServeHTTP(http.ResponseWriter, *http.Request) ProxyResponse
	ServeHTTPForCache(http.ResponseWriter, *http.Request) ProxyResponse
	CopyResponse(io.Writer, io.Reader, time.Duration)
}

// SuccessHandler represents the final ServeHTTP() request for a proxied API request
type SuccessHandler struct {
	BaseMiddleware
}

func tagHeaders(r *http.Request, th []string, tags []string) []string {
	for k, v := range r.Header {
		cleanK := strings.ToLower(k)
		ok := false
		for _, hname := range th {
			if hname == cleanK {
				ok = true
				break
			}
		}

		if ok {
			for _, val := range v {
				tagName := cleanK + "-" + val
				tags = append(tags, tagName)
			}
		}
	}

	return tags
}

func addVersionHeader(w http.ResponseWriter, r *http.Request, globalConf config.Config) {
	if ctxGetDefaultVersion(r) {
		if vinfo := ctxGetVersionInfo(r); vinfo != nil {
			if globalConf.VersionHeader != "" {
				w.Header().Set(globalConf.VersionHeader, vinfo.Name)
			}
		}
	}
}

func estimateTagsCapacity(session *user.SessionState, apiSpec *APISpec) int {
	size := 5 // that number of tags expected to be added at least before we record hit
	if session != nil {
		size += len(session.Tags)

		size += len(session.ApplyPolicies)

		if session.MetaData != nil {
			if _, ok := session.MetaData[keyDataDeveloperID]; ok {
				size += 1
			}
		}
	}

	if apiSpec.GlobalConfig.DBAppConfOptions.NodeIsSegmented {
		size += len(apiSpec.GlobalConfig.DBAppConfOptions.Tags)
	}

	size += len(apiSpec.TagHeaders)

	return size
}

func getSessionTags(session *user.SessionState) []string {
	tags := make([]string, 0, len(session.Tags)+len(session.ApplyPolicies)+1)

	// add policy IDs
	for _, polID := range session.ApplyPolicies {
		tags = append(tags, "pol-"+polID)
	}

	if session.MetaData != nil {
		if developerID, ok := session.MetaData[keyDataDeveloperID].(string); ok {
			tags = append(tags, "dev-"+developerID)
		}
	}

	tags = append(tags, session.Tags...)

	return tags
}

func (s *SuccessHandler) RecordHit(r *http.Request, timing Latency, code int, responseCopy *http.Response) {

	if s.Spec.DoNotTrack || ctxGetDoNotTrack(r) {
		return
	}

	ip := request.RealIP(r)
	if s.Spec.GlobalConfig.StoreAnalytics(ip) {

		t := time.Now()

		// Track the key ID if it exists
		token := ctxGetAuthToken(r)

		// Track version data
		version := s.Spec.getVersionFromRequest(r)
		if version == "" {
			version = "Non Versioned"
		}

		// If OAuth, we need to grab it from the session, which may or may not exist
		oauthClientID := ""
		var alias string
		session := ctxGetSession(r)
		tags := make([]string, 0, estimateTagsCapacity(session, s.Spec))
		if session != nil {
			oauthClientID = session.OauthClientID
			tags = append(tags, getSessionTags(session)...)
			alias = session.Alias
		}

		if len(s.Spec.TagHeaders) > 0 {
			tags = tagHeaders(r, s.Spec.TagHeaders, tags)
		}

		rawRequest := ""
		rawResponse := ""

		if recordDetail(r, s.Spec) {
			// Get the wire format representation
			var wireFormatReq bytes.Buffer
			r.Write(&wireFormatReq)
			rawRequest = base64.StdEncoding.EncodeToString(wireFormatReq.Bytes())
			// responseCopy, unlike requestCopy, can be nil
			// here - if the response was cached in
			// mw_redis_cache, RecordHit gets passed a nil
			// response copy.
			// TODO: pass a copy of the cached response in
			// mw_redis_cache instead? is there a reason not
			// to include that in the analytics?
			if responseCopy != nil {
				contents, err := ioutil.ReadAll(responseCopy.Body)
				if err != nil {
					log.Error("Couldn't read response body", err)
				}

				responseCopy.Body = respBodyReader(r, responseCopy)

				// Get the wire format representation
				var wireFormatRes bytes.Buffer
				responseCopy.Write(&wireFormatRes)
				responseCopy.Body = ioutil.NopCloser(bytes.NewBuffer(contents))
				rawResponse = base64.StdEncoding.EncodeToString(wireFormatRes.Bytes())
			}
		}

		trackEP := false
		trackedPath := r.URL.Path
		if p := ctxGetTrackedPath(r); p != "" {
			trackEP = true
			trackedPath = p
		}

		host := r.URL.Host
		if host == "" && s.Spec.target != nil {
			host = s.Spec.target.Host
		}

		record := AnalyticsRecord{
			r.Method,
			host,
			trackedPath,
			r.URL.Path,
			r.ContentLength,
			r.Header.Get(headers.UserAgent),
			t.Day(),
			t.Month(),
			t.Year(),
			t.Hour(),
			code,
			token,
			t,
			version,
			s.Spec.Name,
			s.Spec.APIID,
			s.Spec.OrgID,
			oauthClientID,
			timing.Total,
			timing,
			rawRequest,
			rawResponse,
			ip,
			GeoData{},
			NetworkStats{},
			tags,
			alias,
			trackEP,
			t,
		}

		if s.Spec.GlobalConfig.AnalyticsConfig.EnableGeoIP {
			record.GetGeo(ip)
		}

		expiresAfter := s.Spec.ExpireAnalyticsAfter
		if s.Spec.GlobalConfig.EnforceOrgDataAge {
			orgExpireDataTime := s.OrgSessionExpiry(s.Spec.OrgID)

			if orgExpireDataTime > 0 {
				expiresAfter = orgExpireDataTime
			}
		}

		record.SetExpiry(expiresAfter)

		if s.Spec.GlobalConfig.AnalyticsConfig.NormaliseUrls.Enabled {
			record.NormalisePath(&s.Spec.GlobalConfig)
		}

		analytics.RecordHit(&record)
	}

	// Report in health check
	reportHealthValue(s.Spec, RequestLog, strconv.FormatInt(timing.Total, 10))

	if memProfFile != nil {
		pprof.WriteHeapProfile(memProfFile)
	}
}

func recordDetail(r *http.Request, spec *APISpec) bool {

	// when streaming in grpc, we do not record the request
	if IsGrpcStreaming(r) {
		return false
	}

	if spec.EnableDetailedRecording {
		return true
	}

	session := ctxGetSession(r)
	if session != nil {
		if session.EnableDetailedRecording || session.EnableDetailRecording {
			return true
		}
	}

	// Are we even checking?
	if !spec.GlobalConfig.EnforceOrgDataDetailLogging {
		return spec.GlobalConfig.AnalyticsConfig.EnableDetailedRecording
	}

	// We are, so get session data
	ses := r.Context().Value(ctx.OrgSessionContext)
	if ses == nil {
		// no session found, use global config
		return spec.GlobalConfig.AnalyticsConfig.EnableDetailedRecording
	}

	// Session found
	sess := ses.(*user.SessionState)
	return sess.EnableDetailRecording || sess.EnableDetailedRecording
}

// ServeHTTP will store the request details in the analytics store if necessary and proxy the request to it's
// final destination, this is invoked by the ProxyHandler or right at the start of a request chain if the URL
// Spec states the path is Ignored
func (s *SuccessHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) *http.Response {
	log.Debug("Started proxy")
	defer s.Base().UpdateRequestSession(r)

	versionDef := s.Spec.VersionDefinition
	if !s.Spec.VersionData.NotVersioned && versionDef.Location == "url" && versionDef.StripPath {
		part := s.Spec.getVersionFromRequest(r)

		log.Debug("Stripping version from url: ", part)

		r.URL.Path = strings.Replace(r.URL.Path, part+"/", "", 1)
		r.URL.RawPath = strings.Replace(r.URL.RawPath, part+"/", "", 1)
	}

	// Make sure we get the correct target URL
	if s.Spec.Proxy.StripListenPath {
		log.Debug("Stripping: ", s.Spec.Proxy.ListenPath)
		r.URL.Path = s.Spec.StripListenPath(r, r.URL.Path)
		r.URL.RawPath = s.Spec.StripListenPath(r, r.URL.RawPath)
		log.Debug("Upstream Path is: ", r.URL.Path)
	}

	addVersionHeader(w, r, s.Spec.GlobalConfig)

	t1 := time.Now()
	resp := s.Proxy.ServeHTTP(w, r)

	millisec := DurationToMillisecond(time.Since(t1))
	log.Debug("Upstream request took (ms): ", millisec)

	if resp.Response != nil {
		latency := Latency{
			Total:    int64(millisec),
			Upstream: int64(DurationToMillisecond(resp.UpstreamLatency)),
		}
		s.RecordHit(r, latency, resp.Response.StatusCode, resp.Response)
	}
	log.Debug("Done proxy")
	return nil
}

// ServeHTTPWithCache will store the request details in the analytics store if necessary and proxy the request to it's
// final destination, this is invoked by the ProxyHandler or right at the start of a request chain if the URL
// Spec states the path is Ignored Itwill also return a response object for the cache
func (s *SuccessHandler) ServeHTTPWithCache(w http.ResponseWriter, r *http.Request) ProxyResponse {

	versionDef := s.Spec.VersionDefinition
	if !s.Spec.VersionData.NotVersioned && versionDef.Location == "url" && versionDef.StripPath {
		part := s.Spec.getVersionFromRequest(r)

		log.Debug("Stripping version from URL: ", part)

		r.URL.Path = strings.Replace(r.URL.Path, part+"/", "", 1)
		r.URL.RawPath = strings.Replace(r.URL.RawPath, part+"/", "", 1)
	}

	// Make sure we get the correct target URL
	if s.Spec.Proxy.StripListenPath {
		r.URL.Path = s.Spec.StripListenPath(r, r.URL.Path)
		r.URL.RawPath = s.Spec.StripListenPath(r, r.URL.RawPath)
	}

	t1 := time.Now()
	inRes := s.Proxy.ServeHTTPForCache(w, r)
	millisec := DurationToMillisecond(time.Since(t1))

	addVersionHeader(w, r, s.Spec.GlobalConfig)

	log.Debug("Upstream request took (ms): ", millisec)

	if inRes.Response != nil {
		latency := Latency{
			Total:    int64(millisec),
			Upstream: int64(DurationToMillisecond(inRes.UpstreamLatency)),
		}
		s.RecordHit(r, latency, inRes.Response.StatusCode, inRes.Response)
	}

	return inRes
}
<|endoftext|>


<commit_before>// imsto core objects
package storage

import (
	"bytes"
	"crypto/md5"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"path"
	"time"
	"wpst.me/calf/base"
	"wpst.me/calf/config"
	cdb "wpst.me/calf/db"
	iimg "wpst.me/calf/image"
	"wpst.me/calf/storage/backend"
)

type EntryId struct {
	id   string
	hash string
}

func NewEntryIdFromHash(hash string) (*EntryId, error) {
	id, err := base.BaseConvert(hash, 16, 36)

	return &EntryId{id, hash}, err
}

func NewEntryId(id string) (*EntryId, error) {
	hash, err := base.BaseConvert(id, 36, 16)
	return &EntryId{id, hash}, err
}

func (ei *EntryId) String() string {
	return ei.id
}

func (ei *EntryId) MarshalJSON() ([]byte, error) {
	return json.Marshal(ei.id)
}

func (ei *EntryId) Hashed() string {
	return ei.hash
}

func (ei *EntryId) tip() string {
	return ei.id[:1]
}

type AppId uint8

type Author uint32

type Entry struct {
	Id        *EntryId   `json:"id,omitempty"`
	Name      string     `json:"name,omitempty"`
	Size      uint32     `json:"size"`
	Path      string     `json:"path"`
	Mime      string     `json:"mime,omitempty"`
	Status    uint8      `json:"-"`
	Hashes    cdb.Qarray `json:"-"`
	Ids       cdb.Qarray `json:"-"`
	Roofs     cdb.Qarray `json:"roofs"`
	Meta      *iimg.Attr `json:"meta,omitempty"`
	AppId     AppId      `json:"appid,omitempty"`
	Author    Author     `json:"author,omitempty"`
	Modified  uint64     `json:"modified,omitempty"`
	Created   time.Time  `json:"created,omitempty"`
	imageType int
	exif      cdb.Hstore
	sev       cdb.Hstore
	b         []byte
	h         string
	_treked   bool
	ret       int       // db saved result
	Done      chan bool `json:"-"`
	ready     int
}

const (
	min_size = 43
)

func NewEntry(data []byte, name string) (e *Entry, err error) {
	if len(data) < min_size {
		err = errors.New("data is too small, maybe not a valid image")
		return
	}

	hash := HashContent(data)
	var id *EntryId
	id, err = NewEntryIdFromHash(hash)

	if err != nil {
		log.Println(err)
		return
	}

	e = &Entry{
		Id:      id,
		Name:    name,
		Size:    uint32(len(data)),
		Created: time.Now(),
		b:       data,
		h:       hash,
	}

	// entry = &Entry{Id: id, Name: name, Size: ia.Size, Meta: ia, Path: path, Mime: mimetype, Hashes: hashes, Ids: ids}

	return
}

// 处理图片信息并填充
func (e *Entry) Trek(roof string) (err error) {
	if e._treked {
		return
	}
	e._treked = true
	var im iimg.Image
	rd := bytes.NewReader(e.b)
	im, err = iimg.Open(rd)

	if err != nil {
		log.Printf("image open error: %s", err)
		return
	}

	defer im.Close()

	ia := im.GetAttr()
	// log.Println(ia)

	max_quality := iimg.Quality(config.GetInt(roof, "max_quality"))
	if ia.Quality > max_quality {
		im.SetOption(iimg.WriteOption{Quality: max_quality, StripAll: true})
		log.Printf("jpeg quality %d is too high, set to %d", ia.Quality, max_quality)
	}

	max_width := iimg.Dimension(config.GetInt(roof, "max_width"))
	max_height := iimg.Dimension(config.GetInt(roof, "max_height"))
	if ia.Width > max_width || ia.Height > max_height {
		err = fmt.Errorf("dimension %dx%d of %s is too big", ia.Width, ia.Height, e.Name)
		return
	}

	min_width := iimg.Dimension(config.GetInt(roof, "min_width"))
	min_height := iimg.Dimension(config.GetInt(roof, "min_height"))
	if ia.Width < min_width || ia.Height < min_height {
		err = fmt.Errorf("dimension %dx%d of %s is too small", ia.Width, ia.Height, e.Name)
		return
	}

	var data []byte
	data, err = im.GetBlob() // tack new data

	if err != nil {
		log.Printf("GetBlob error: %s", err)
		return
	}

	hashes := cdb.Qarray{e.h}
	ids := cdb.Qarray{e.Id.String()}

	var hash2 string
	size := len(data)
	if max_file_size := config.GetInt(roof, "max_file_size"); size > max_file_size {
		err = fmt.Errorf("file: %s size %d is too big, max is %d", e.Name, size, max_file_size)
		return
	}

	hash2 = HashContent(data)
	if hash2 != e.h {
		hashes = append(hashes, hash2)
		var id2 *EntryId
		id2, err = NewEntryIdFromHash(hash2)
		if err != nil {
			// log.Println(err)
			return
		}
		ids = append(ids, id2.String())
		e.Id = id2 // 使用新的 Id 作为主键
		e.h = hash2
		e.b = data
		e.Size = uint32(size)
	}

	ia.Size = iimg.Size(size) // 更新后的大小
	ia.Name = e.Name

	path := newPath(e.Id, ia.Ext)

	log.Printf("ext: %s, mime: %s\n", ia.Ext, ia.Mime)

	e.Meta = ia
	e.Path = path
	e.Mime = ia.Mime
	e.Hashes = hashes
	e.Ids = ids
	return
}

// return hash value string
func (e *Entry) Hashed() string {
	return e.h
}

// return binary bytes
func (e *Entry) Blob() []byte {
	return e.b
}

func (e *Entry) IsDone() bool {
	return e.ready != 1
}

func (e *Entry) store(roof string) (err error) {

	mw := NewMetaWrapper(roof)
	eh, _err := mw.GetHash(e.h)
	if _err != nil { // ok, not exsits
		log.Printf("check hash error: %s", _err)
	} else if eh != nil && eh.id != "" {
		if _id, _err := NewEntryId(eh.id); _err == nil {
			e.Id = _id
			_ne, _err := mw.GetEntry(*_id)
			if _err == nil { // path, mime, size, sev, status, created
				if _ne.Roofs.Contains(roof) {
					e.Name = _ne.Name
					e.Path = _ne.Path
					e.Size = _ne.Size
					e.Mime = _ne.Mime
					// e.Meta = _ne.Meta
					// e.sev = _ne.sev
					e.Created = _ne.Created
					e.Roofs = _ne.Roofs
					e.reset()
					e._treked = true

					log.Printf("exist: %s, %s", e.Id, e.Path)
					return
				}

				log.Printf("[%s]%s not in %s, so resubmit it", roof, e.Id, _ne.Roofs)

				// for _, _roof := range _ne.Roofs {
				// 	_mw := NewMetaWrapper(fmt.Sprint(_roof))
				// 	t, te := _mw.GetMeta(*_ne.Id)
				// 	if te == nil {
				// 		e = t
				// 		err = mw.Save(t)
				// 		return
				// 	}
				// }

				// e.Done <- true
			} else {
				log.Printf("get entry error: %s", _err)
			}
		}
	}

	if err = e.Trek(roof); err != nil {
		return
	}
	log.Printf("new id: %v, size: %d, path: %v\n", e.Id, e.Size, e.Path)

	data := e.Blob()
	// size := len(data)
	// log.Printf("blob length: %d", size)
	thumb_root := config.GetValue(roof, "thumb_root")
	filename := path.Join(thumb_root, "orig", e.Path)
	err = SaveFile(filename, data)
	if err != nil {
		return
	}

	err = mw.Ready(e)
	if err != nil {
		return
	}
	e.ready = 1

	e.Done = make(chan bool, 1)
	go func() {
		err = e._save(roof)
		if err != nil {
			log.Printf("_save error: %s", err)
		}
		e.Done <- true
	}()

	log.Printf("[%s] store ready ok %s", roof, e.Path)

	return
}

func (e *Entry) _save(roof string) (err error) {
	en := config.GetValue(roof, "engine")
	log.Printf("start save to engine %s", en)

	e.sev, err = PushBlob(e, roof)
	if err != nil {
		log.Printf("engine push error: %s", err)
		return
	}
	log.Print("engine push ok")

	mw := NewMetaWrapper(roof)
	if err = mw.SetDone(*e.Id, e.sev); err != nil {
		log.Println(err)
		// if err = mw.Save(e); err != nil {
		// 	return
		// }
		// return
	}
	e.ready = -1
	log.Print("meta set done ok")
	return
}

func (e *Entry) reset() {
	e.b = []byte{}
}

func newPath(ei *EntryId, ext string) string {
	r := ei.id
	p := r[0:2] + "/" + r[2:4] + "/" + r[4:] + ext

	return p
}

func HashContent(data []byte) string {
	return fmt.Sprintf("%x", md5.Sum(data))
}

func PullBlob(e *Entry, roof string) (data []byte, err error) {
	var em backend.Wagoner
	em, err = backend.FarmEngine(roof)
	if err != nil {
		// log.Println(err)
		return
	}
	// var data []byte
	data, err = em.Get(e.Path)
	return
}

func PushBlob(e *Entry, roof string) (sev cdb.Hstore, err error) {
	var em backend.Wagoner
	em, err = backend.FarmEngine(roof)
	if err != nil {
		log.Printf("farm engine error: %s", err)
		return
	}
	sev, err = em.Put(e.Path, e.Blob(), e.Meta.Hstore())
	return
}
<commit_msg>fixes entry: always set jpeg WriteOption<commit_after>// imsto core objects
package storage

import (
	"bytes"
	"crypto/md5"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"path"
	"time"
	"wpst.me/calf/base"
	"wpst.me/calf/config"
	cdb "wpst.me/calf/db"
	iimg "wpst.me/calf/image"
	"wpst.me/calf/storage/backend"
)

type EntryId struct {
	id   string
	hash string
}

func NewEntryIdFromHash(hash string) (*EntryId, error) {
	id, err := base.BaseConvert(hash, 16, 36)

	return &EntryId{id, hash}, err
}

func NewEntryId(id string) (*EntryId, error) {
	hash, err := base.BaseConvert(id, 36, 16)
	return &EntryId{id, hash}, err
}

func (ei *EntryId) String() string {
	return ei.id
}

func (ei *EntryId) MarshalJSON() ([]byte, error) {
	return json.Marshal(ei.id)
}

func (ei *EntryId) Hashed() string {
	return ei.hash
}

func (ei *EntryId) tip() string {
	return ei.id[:1]
}

type AppId uint8

type Author uint32

type Entry struct {
	Id        *EntryId   `json:"id,omitempty"`
	Name      string     `json:"name,omitempty"`
	Size      uint32     `json:"size"`
	Path      string     `json:"path"`
	Mime      string     `json:"mime,omitempty"`
	Status    uint8      `json:"-"`
	Hashes    cdb.Qarray `json:"-"`
	Ids       cdb.Qarray `json:"-"`
	Roofs     cdb.Qarray `json:"roofs"`
	Meta      *iimg.Attr `json:"meta,omitempty"`
	AppId     AppId      `json:"appid,omitempty"`
	Author    Author     `json:"author,omitempty"`
	Modified  uint64     `json:"modified,omitempty"`
	Created   time.Time  `json:"created,omitempty"`
	imageType int
	exif      cdb.Hstore
	sev       cdb.Hstore
	b         []byte
	h         string
	_treked   bool
	ret       int       // db saved result
	Done      chan bool `json:"-"`
	ready     int
}

const (
	min_size = 43
)

func NewEntry(data []byte, name string) (e *Entry, err error) {
	if len(data) < min_size {
		err = errors.New("data is too small, maybe not a valid image")
		return
	}

	hash := HashContent(data)
	var id *EntryId
	id, err = NewEntryIdFromHash(hash)

	if err != nil {
		log.Println(err)
		return
	}

	e = &Entry{
		Id:      id,
		Name:    name,
		Size:    uint32(len(data)),
		Created: time.Now(),
		b:       data,
		h:       hash,
	}

	// entry = &Entry{Id: id, Name: name, Size: ia.Size, Meta: ia, Path: path, Mime: mimetype, Hashes: hashes, Ids: ids}

	return
}

// 处理图片信息并填充
func (e *Entry) Trek(roof string) (err error) {
	if e._treked {
		return
	}
	e._treked = true
	var im iimg.Image
	rd := bytes.NewReader(e.b)
	im, err = iimg.Open(rd)

	if err != nil {
		log.Printf("image open error: %s", err)
		return
	}

	defer im.Close()

	ia := im.GetAttr()
	// log.Println(ia)

	max_quality := iimg.Quality(config.GetInt(roof, "max_quality"))
	if ia.Quality > max_quality {
		log.Printf("jpeg quality %d is too high, set to %d", ia.Quality, max_quality)
	} else {
		max_quality = ia.Quality
		log.Printf("jpeg quality %d is too low", ia.Quality)
	}
	im.SetOption(iimg.WriteOption{Quality: max_quality, StripAll: true})

	max_width := iimg.Dimension(config.GetInt(roof, "max_width"))
	max_height := iimg.Dimension(config.GetInt(roof, "max_height"))
	if ia.Width > max_width || ia.Height > max_height {
		err = fmt.Errorf("dimension %dx%d of %s is too big", ia.Width, ia.Height, e.Name)
		return
	}

	min_width := iimg.Dimension(config.GetInt(roof, "min_width"))
	min_height := iimg.Dimension(config.GetInt(roof, "min_height"))
	if ia.Width < min_width || ia.Height < min_height {
		err = fmt.Errorf("dimension %dx%d of %s is too small", ia.Width, ia.Height, e.Name)
		return
	}

	var data []byte
	data, err = im.GetBlob() // tack new data

	if err != nil {
		log.Printf("GetBlob error: %s", err)
		return
	}

	hashes := cdb.Qarray{e.h}
	ids := cdb.Qarray{e.Id.String()}

	var hash2 string
	size := len(data)
	if max_file_size := config.GetInt(roof, "max_file_size"); size > max_file_size {
		err = fmt.Errorf("file: %s size %d is too big, max is %d", e.Name, size, max_file_size)
		return
	}

	hash2 = HashContent(data)
	if hash2 != e.h {
		hashes = append(hashes, hash2)
		var id2 *EntryId
		id2, err = NewEntryIdFromHash(hash2)
		if err != nil {
			// log.Println(err)
			return
		}
		ids = append(ids, id2.String())
		e.Id = id2 // 使用新的 Id 作为主键
		e.h = hash2
		e.b = data
		e.Size = uint32(size)
	}

	ia.Size = iimg.Size(size) // 更新后的大小
	ia.Name = e.Name

	path := newPath(e.Id, ia.Ext)

	log.Printf("ext: %s, mime: %s\n", ia.Ext, ia.Mime)

	e.Meta = ia
	e.Path = path
	e.Mime = ia.Mime
	e.Hashes = hashes
	e.Ids = ids
	return
}

// return hash value string
func (e *Entry) Hashed() string {
	return e.h
}

// return binary bytes
func (e *Entry) Blob() []byte {
	return e.b
}

func (e *Entry) IsDone() bool {
	return e.ready != 1
}

func (e *Entry) store(roof string) (err error) {

	mw := NewMetaWrapper(roof)
	eh, _err := mw.GetHash(e.h)
	if _err != nil { // ok, not exsits
		log.Printf("check hash error: %s", _err)
	} else if eh != nil && eh.id != "" {
		if _id, _err := NewEntryId(eh.id); _err == nil {
			e.Id = _id
			_ne, _err := mw.GetEntry(*_id)
			if _err == nil { // path, mime, size, sev, status, created
				if _ne.Roofs.Contains(roof) {
					e.Name = _ne.Name
					e.Path = _ne.Path
					e.Size = _ne.Size
					e.Mime = _ne.Mime
					// e.Meta = _ne.Meta
					// e.sev = _ne.sev
					e.Created = _ne.Created
					e.Roofs = _ne.Roofs
					e.reset()
					e._treked = true

					log.Printf("exist: %s, %s", e.Id, e.Path)
					return
				}

				log.Printf("[%s]%s not in %s, so resubmit it", roof, e.Id, _ne.Roofs)

				// for _, _roof := range _ne.Roofs {
				// 	_mw := NewMetaWrapper(fmt.Sprint(_roof))
				// 	t, te := _mw.GetMeta(*_ne.Id)
				// 	if te == nil {
				// 		e = t
				// 		err = mw.Save(t)
				// 		return
				// 	}
				// }

				// e.Done <- true
			} else {
				log.Printf("get entry error: %s", _err)
			}
		}
	}

	if err = e.Trek(roof); err != nil {
		return
	}
	log.Printf("new id: %v, size: %d, path: %v\n", e.Id, e.Size, e.Path)

	data := e.Blob()
	// size := len(data)
	// log.Printf("blob length: %d", size)
	thumb_root := config.GetValue(roof, "thumb_root")
	filename := path.Join(thumb_root, "orig", e.Path)
	err = SaveFile(filename, data)
	if err != nil {
		return
	}

	err = mw.Ready(e)
	if err != nil {
		return
	}
	e.ready = 1

	e.Done = make(chan bool, 1)
	go func() {
		err = e._save(roof)
		if err != nil {
			log.Printf("_save error: %s", err)
		}
		e.Done <- true
	}()

	log.Printf("[%s] store ready ok %s", roof, e.Path)

	return
}

func (e *Entry) _save(roof string) (err error) {
	en := config.GetValue(roof, "engine")
	log.Printf("start save to engine %s", en)

	e.sev, err = PushBlob(e, roof)
	if err != nil {
		log.Printf("engine push error: %s", err)
		return
	}
	log.Print("engine push ok")

	mw := NewMetaWrapper(roof)
	if err = mw.SetDone(*e.Id, e.sev); err != nil {
		log.Println(err)
		// if err = mw.Save(e); err != nil {
		// 	return
		// }
		// return
	}
	e.ready = -1
	log.Print("meta set done ok")
	return
}

func (e *Entry) reset() {
	e.b = []byte{}
}

func newPath(ei *EntryId, ext string) string {
	r := ei.id
	p := r[0:2] + "/" + r[2:4] + "/" + r[4:] + ext

	return p
}

func HashContent(data []byte) string {
	return fmt.Sprintf("%x", md5.Sum(data))
}

func PullBlob(e *Entry, roof string) (data []byte, err error) {
	var em backend.Wagoner
	em, err = backend.FarmEngine(roof)
	if err != nil {
		// log.Println(err)
		return
	}
	// var data []byte
	data, err = em.Get(e.Path)
	return
}

func PushBlob(e *Entry, roof string) (sev cdb.Hstore, err error) {
	var em backend.Wagoner
	em, err = backend.FarmEngine(roof)
	if err != nil {
		log.Printf("farm engine error: %s", err)
		return
	}
	sev, err = em.Put(e.Path, e.Blob(), e.Meta.Hstore())
	return
}
<|endoftext|>


<commit_before>// Copyright 2013 docker-cluster 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 storage provides some implementations of the Storage interface,
// defined in the cluster package.
package storage

import (
	"errors"
	"github.com/garyburd/redigo/redis"
	"github.com/globocom/docker-cluster/cluster"
)

var (
	ErrNoSuchContainer = errors.New("No such container")
	ErrNoSuchImage     = errors.New("No such image")
)

type redisStorage struct {
	pool   *redis.Pool
	prefix string
}

func (s *redisStorage) key(value string) string {
	if s.prefix == "" {
		return value
	}
	return s.prefix + ":" + value
}

func (s *redisStorage) StoreContainer(container, host string) error {
	conn := s.pool.Get()
	defer conn.Close()
	_, err := conn.Do("SET", s.key(container), host)
	return err
}

func (s *redisStorage) RetrieveContainer(container string) (string, error) {
	conn := s.pool.Get()
	defer conn.Close()
	result, err := conn.Do("GET", s.key(container))
	if err != nil {
		return "", err
	}
	if result == nil {
		return "", ErrNoSuchContainer
	}
	return string(result.([]byte)), nil
}

func (s *redisStorage) RemoveContainer(container string) error {
	conn := s.pool.Get()
	defer conn.Close()
	result, err := conn.Do("DEL", s.key(container))
	if err != nil {
		return err
	}
	if result.(int64) < 1 {
		return ErrNoSuchContainer
	}
	return nil
}

func (s *redisStorage) StoreImage(image, host string) error {
	conn := s.pool.Get()
	defer conn.Close()
	_, err := conn.Do("SET", s.key("image:"+image), host)
	return err
}

func (s *redisStorage) RetrieveImage(id string) (string, error) {
	conn := s.pool.Get()
	defer conn.Close()
	result, err := conn.Do("GET", s.key("image:"+id))
	if err != nil {
		return "", err
	}
	if result == nil {
		return "", ErrNoSuchImage
	}
	return string(result.([]byte)), nil
}

func (s *redisStorage) RemoveImage(id string) error {
	conn := s.pool.Get()
	defer conn.Close()
	result, err := conn.Do("DEL", s.key("image:"+id))
	if err != nil {
		return err
	}
	if result.(int64) < 1 {
		return ErrNoSuchImage
	}
	return nil
}

// Redis returns a storage instance that uses Redis to store nodes and
// containers relation.
//
// The addres must be in the format <host>:<port>. For servers that require
// authentication, use AuthenticatedRedis.
func Redis(addr, prefix string) cluster.Storage {
	return rStorage(addr, "", prefix)
}

// AuthenticatedRedis works like Redis, but supports password authentication.
func AuthenticatedRedis(addr, password, prefix string) cluster.Storage {
	return rStorage(addr, password, prefix)
}

func rStorage(addr, password, prefix string) cluster.Storage {
	pool := redis.NewPool(func() (redis.Conn, error) {
		conn, err := redis.Dial("tcp", addr)
		if err != nil {
			return nil, err
		}
		if password != "" {
			_, err = conn.Do("AUTH", password)
			if err != nil {
				return nil, err
			}
		}
		return conn, nil
	}, 10)
	return &redisStorage{pool: pool, prefix: prefix}
}
<commit_msg>storage/redis: set MaxActive and IdleTimeout on connection pool<commit_after>// Copyright 2013 docker-cluster 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 storage provides some implementations of the Storage interface,
// defined in the cluster package.
package storage

import (
	"errors"
	"github.com/garyburd/redigo/redis"
	"github.com/globocom/docker-cluster/cluster"
)

var (
	ErrNoSuchContainer = errors.New("No such container")
	ErrNoSuchImage     = errors.New("No such image")
)

type redisStorage struct {
	pool   *redis.Pool
	prefix string
}

func (s *redisStorage) key(value string) string {
	if s.prefix == "" {
		return value
	}
	return s.prefix + ":" + value
}

func (s *redisStorage) StoreContainer(container, host string) error {
	conn := s.pool.Get()
	defer conn.Close()
	_, err := conn.Do("SET", s.key(container), host)
	return err
}

func (s *redisStorage) RetrieveContainer(container string) (string, error) {
	conn := s.pool.Get()
	defer conn.Close()
	result, err := conn.Do("GET", s.key(container))
	if err != nil {
		return "", err
	}
	if result == nil {
		return "", ErrNoSuchContainer
	}
	return string(result.([]byte)), nil
}

func (s *redisStorage) RemoveContainer(container string) error {
	conn := s.pool.Get()
	defer conn.Close()
	result, err := conn.Do("DEL", s.key(container))
	if err != nil {
		return err
	}
	if result.(int64) < 1 {
		return ErrNoSuchContainer
	}
	return nil
}

func (s *redisStorage) StoreImage(image, host string) error {
	conn := s.pool.Get()
	defer conn.Close()
	_, err := conn.Do("SET", s.key("image:"+image), host)
	return err
}

func (s *redisStorage) RetrieveImage(id string) (string, error) {
	conn := s.pool.Get()
	defer conn.Close()
	result, err := conn.Do("GET", s.key("image:"+id))
	if err != nil {
		return "", err
	}
	if result == nil {
		return "", ErrNoSuchImage
	}
	return string(result.([]byte)), nil
}

func (s *redisStorage) RemoveImage(id string) error {
	conn := s.pool.Get()
	defer conn.Close()
	result, err := conn.Do("DEL", s.key("image:"+id))
	if err != nil {
		return err
	}
	if result.(int64) < 1 {
		return ErrNoSuchImage
	}
	return nil
}

// Redis returns a storage instance that uses Redis to store nodes and
// containers relation.
//
// The addres must be in the format <host>:<port>. For servers that require
// authentication, use AuthenticatedRedis.
func Redis(addr, prefix string) cluster.Storage {
	return rStorage(addr, "", prefix)
}

// AuthenticatedRedis works like Redis, but supports password authentication.
func AuthenticatedRedis(addr, password, prefix string) cluster.Storage {
	return rStorage(addr, password, prefix)
}

func rStorage(addr, password, prefix string) cluster.Storage {
	pool := redis.NewPool(func() (redis.Conn, error) {
		conn, err := redis.Dial("tcp", addr)
		if err != nil {
			return nil, err
		}
		if password != "" {
			_, err = conn.Do("AUTH", password)
			if err != nil {
				return nil, err
			}
		}
		return conn, nil
	}, 5)
	pool.IdleTimeout = 180e9
	pool.MaxActive = 10
	return &redisStorage{pool: pool, prefix: prefix}
}
<|endoftext|>


<commit_before>// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// 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.

package defaultrolemanager

import (
	"github.com/casbin/casbin/rbac"
	"github.com/casbin/casbin/util"
)

type RoleManager struct {
	allRoles map[string]*Role
	level    int
}

// NewRoleManager is the constructor for creating an instance of the
// default RoleManager implementation.
func NewRoleManager(level int) rbac.RoleManager {
	rm := RoleManager{}
	rm.allRoles = make(map[string]*Role)
	rm.level = level
	return &rm
}

func (rm *RoleManager) hasRole(name string) bool {
	_, ok := rm.allRoles[name]
	return ok
}

func (rm *RoleManager) createRole(name string) *Role {
	if !rm.hasRole(name) {
		rm.allRoles[name] = newRole(name)
	}

	return rm.allRoles[name]
}

// Clear clears all stored data and resets the role manager to the initial state.
func (rm *RoleManager) Clear() {
	rm.allRoles = make(map[string]*Role)
}

// AddLink adds the inheritance link between role: name1 and role: name2.
// aka role: name1 inherits role: name2.
// domain is a prefix to the roles.
func (rm *RoleManager) AddLink(name1 string, name2 string, domain ...string) {
	if len(domain) == 1 {
		name1 = domain[0] + "::" + name1
		name2 = domain[0] + "::" + name2
	}

	role1 := rm.createRole(name1)
	role2 := rm.createRole(name2)
	role1.addRole(role2)
}

// DeleteLink deletes the inheritance link between role: name1 and role: name2.
// aka role: name1 does not inherit role: name2 any more.
// domain is a prefix to the roles.
func (rm *RoleManager) DeleteLink(name1 string, name2 string, domain ...string) {
	if len(domain) == 1 {
		name1 = domain[0] + "::" + name1
		name2 = domain[0] + "::" + name2
	}

	if !rm.hasRole(name1) || !rm.hasRole(name2) {
		return
	}

	role1 := rm.createRole(name1)
	role2 := rm.createRole(name2)
	role1.deleteRole(role2)
}

// HasLink determines whether role: name1 inherits role: name2.
// domain is a prefix to the roles.
func (rm *RoleManager) HasLink(name1 string, name2 string, domain ...string) bool {
	if len(domain) == 1 {
		name1 = domain[0] + "::" + name1
		name2 = domain[0] + "::" + name2
	}

	if name1 == name2 {
		return true
	}

	if !rm.hasRole(name1) || !rm.hasRole(name2) {
		return false
	}

	role1 := rm.createRole(name1)
	return role1.hasRole(name2, rm.level)
}

// GetRoles gets the roles that a subject inherits.
// domain is a prefix to the roles.
func (rm *RoleManager) GetRoles(name string, domain ...string) []string {
	if len(domain) == 1 {
		name = domain[0] + "::" + name
	}

	if !rm.hasRole(name) {
		return nil
	}

	roles := rm.createRole(name).getRoles()
	if len(domain) == 1 {
		for i := range roles {
			roles[i] = roles[i][len(domain[0])+2:]
		}
	}
	return roles
}

// GetUsers gets the users that inherits a subject.
// domain is an unreferenced parameter here, may be used in other implementations.
func (rm *RoleManager) GetUsers(name string, domain ...string) []string {
	if !rm.hasRole(name) {
		return nil
	}

	names := []string{}
	for _, role := range rm.allRoles {
		if role.hasDirectRole(name) {
			names = append(names, role.name)
		}
	}
	return names
}

// PrintRoles prints all the roles to log.
func (rm *RoleManager) PrintRoles() {
	for _, role := range rm.allRoles {
		util.LogPrint(role.toString())
	}
}

// Role represents the data structure for a role in RBAC.
type Role struct {
	name  string
	roles []*Role
}

func newRole(name string) *Role {
	r := Role{}
	r.name = name
	return &r
}

func (r *Role) addRole(role *Role) {
	for _, rr := range r.roles {
		if rr.name == role.name {
			return
		}
	}

	r.roles = append(r.roles, role)
}

func (r *Role) deleteRole(role *Role) {
	for i, rr := range r.roles {
		if rr.name == role.name {
			r.roles = append(r.roles[:i], r.roles[i+1:]...)
			return
		}
	}
}

func (r *Role) hasRole(name string, level int) bool {
	if r.name == name {
		return true
	}

	if level <= 0 {
		return false
	}

	for _, role := range r.roles {
		if role.hasRole(name, level-1) {
			return true
		}
	}
	return false
}

func (r *Role) hasDirectRole(name string) bool {
	for _, role := range r.roles {
		if role.name == name {
			return true
		}
	}

	return false
}

func (r *Role) toString() string {
	names := ""
	for i, role := range r.roles {
		if i == 0 {
			names += role.name
		} else {
			names += ", " + role.name
		}
	}
	return r.name + " < " + names
}

func (r *Role) getRoles() []string {
	names := []string{}
	for _, role := range r.roles {
		names = append(names, role.name)
	}
	return names
}
<commit_msg>Rename level to hierarchyLevel in default role manager.<commit_after>// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// 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.

package defaultrolemanager

import (
	"github.com/casbin/casbin/rbac"
	"github.com/casbin/casbin/util"
)

type RoleManager struct {
	allRoles             map[string]*Role
	maxHierarchyLevel    int
}

// NewRoleManager is the constructor for creating an instance of the
// default RoleManager implementation.
func NewRoleManager(maxHierarchyLevel int) rbac.RoleManager {
	rm := RoleManager{}
	rm.allRoles = make(map[string]*Role)
	rm.maxHierarchyLevel = maxHierarchyLevel
	return &rm
}

func (rm *RoleManager) hasRole(name string) bool {
	_, ok := rm.allRoles[name]
	return ok
}

func (rm *RoleManager) createRole(name string) *Role {
	if !rm.hasRole(name) {
		rm.allRoles[name] = newRole(name)
	}

	return rm.allRoles[name]
}

// Clear clears all stored data and resets the role manager to the initial state.
func (rm *RoleManager) Clear() {
	rm.allRoles = make(map[string]*Role)
}

// AddLink adds the inheritance link between role: name1 and role: name2.
// aka role: name1 inherits role: name2.
// domain is a prefix to the roles.
func (rm *RoleManager) AddLink(name1 string, name2 string, domain ...string) {
	if len(domain) == 1 {
		name1 = domain[0] + "::" + name1
		name2 = domain[0] + "::" + name2
	}

	role1 := rm.createRole(name1)
	role2 := rm.createRole(name2)
	role1.addRole(role2)
}

// DeleteLink deletes the inheritance link between role: name1 and role: name2.
// aka role: name1 does not inherit role: name2 any more.
// domain is a prefix to the roles.
func (rm *RoleManager) DeleteLink(name1 string, name2 string, domain ...string) {
	if len(domain) == 1 {
		name1 = domain[0] + "::" + name1
		name2 = domain[0] + "::" + name2
	}

	if !rm.hasRole(name1) || !rm.hasRole(name2) {
		return
	}

	role1 := rm.createRole(name1)
	role2 := rm.createRole(name2)
	role1.deleteRole(role2)
}

// HasLink determines whether role: name1 inherits role: name2.
// domain is a prefix to the roles.
func (rm *RoleManager) HasLink(name1 string, name2 string, domain ...string) bool {
	if len(domain) == 1 {
		name1 = domain[0] + "::" + name1
		name2 = domain[0] + "::" + name2
	}

	if name1 == name2 {
		return true
	}

	if !rm.hasRole(name1) || !rm.hasRole(name2) {
		return false
	}

	role1 := rm.createRole(name1)
	return role1.hasRole(name2, rm.maxHierarchyLevel)
}

// GetRoles gets the roles that a subject inherits.
// domain is a prefix to the roles.
func (rm *RoleManager) GetRoles(name string, domain ...string) []string {
	if len(domain) == 1 {
		name = domain[0] + "::" + name
	}

	if !rm.hasRole(name) {
		return nil
	}

	roles := rm.createRole(name).getRoles()
	if len(domain) == 1 {
		for i := range roles {
			roles[i] = roles[i][len(domain[0])+2:]
		}
	}
	return roles
}

// GetUsers gets the users that inherits a subject.
// domain is an unreferenced parameter here, may be used in other implementations.
func (rm *RoleManager) GetUsers(name string, domain ...string) []string {
	if !rm.hasRole(name) {
		return nil
	}

	names := []string{}
	for _, role := range rm.allRoles {
		if role.hasDirectRole(name) {
			names = append(names, role.name)
		}
	}
	return names
}

// PrintRoles prints all the roles to log.
func (rm *RoleManager) PrintRoles() {
	for _, role := range rm.allRoles {
		util.LogPrint(role.toString())
	}
}

// Role represents the data structure for a role in RBAC.
type Role struct {
	name  string
	roles []*Role
}

func newRole(name string) *Role {
	r := Role{}
	r.name = name
	return &r
}

func (r *Role) addRole(role *Role) {
	for _, rr := range r.roles {
		if rr.name == role.name {
			return
		}
	}

	r.roles = append(r.roles, role)
}

func (r *Role) deleteRole(role *Role) {
	for i, rr := range r.roles {
		if rr.name == role.name {
			r.roles = append(r.roles[:i], r.roles[i+1:]...)
			return
		}
	}
}

func (r *Role) hasRole(name string, hierarchyLevel int) bool {
	if r.name == name {
		return true
	}

	if hierarchyLevel <= 0 {
		return false
	}

	for _, role := range r.roles {
		if role.hasRole(name, hierarchyLevel-1) {
			return true
		}
	}
	return false
}

func (r *Role) hasDirectRole(name string) bool {
	for _, role := range r.roles {
		if role.name == name {
			return true
		}
	}

	return false
}

func (r *Role) toString() string {
	names := ""
	for i, role := range r.roles {
		if i == 0 {
			names += role.name
		} else {
			names += ", " + role.name
		}
	}
	return r.name + " < " + names
}

func (r *Role) getRoles() []string {
	names := []string{}
	for _, role := range r.roles {
		names = append(names, role.name)
	}
	return names
}
<|endoftext|>


<commit_before>package apptail

import (
	"encoding/json"
	"fmt"
	"github.com/ActiveState/log"
	"github.com/ActiveState/tail"
	"logyard"
	"logyard/util/pubsub"
	"os"
	"time"
	"unicode/utf8"
)

// AppInstance is the NATS message sent by dea/stager to notify of new
// instances.
type AppInstance struct {
	AppID    int
	AppName  string
	AppGroup string `json:"group"`
	Type     string
	Index    int
	LogFiles map[string]string
}

// AppLogMessage is a struct corresponding to an entry in the app log stream.
type AppLogMessage struct {
	Text          string
	LogFilename   string
	UnixTime      int64
	HumanTime     string
	Source        string // example: app, staging, stackato.dea, stackato.stager
	InstanceIndex int
	AppID         int
	AppName       string
	AppGroup      string
	NodeID        string // Host (DEA,stager) IP of this app instance
}

// Publish publishes an AppLogMessage to logyard after sanity checks.
func (line *AppLogMessage) Publish(pub *pubsub.Publisher, allowInvalidJson bool) error {
	// JSON must be a UTF-8 encoded string.
	if !utf8.ValidString(line.Text) {
		line.Text = string([]rune(line.Text))
	}

	data, err := json.Marshal(line)
	if err != nil {
		if allowInvalidJson {
			log.Errorf("Cannot encode %+v into JSON -- %s. Skipping this message", line, err)
		} else {
			return fmt.Errorf("Failed to encode app log record to JSON: ", err)
		}
	}
	key := fmt.Sprintf("apptail.%d", line.AppID)
	pub.MustPublish(key, string(data))
	return nil
}

// AppInstanceStarted is a function to be invoked when dea/stager
// starts an application instance.
func AppInstanceStarted(instance *AppInstance, nodeid string) {
	log.Infof("New app instance was started: %+v", instance)

	// convert MB to limit in bytes.
	filesize_limit := GetConfig().FileSizeLimit * 1024 * 1024

	if !(filesize_limit > 0) {
		panic("invalid value for `read_limit' in apptail config")
	}

	for name, filename := range instance.LogFiles {
		go func(name string, filename string) {
			pub := logyard.Broker.NewPublisherMust()
			defer pub.Stop()

			fi, err := os.Stat(filename)
			if err != nil {
				log.Errorf("Cannot stat file (%s); %s", filename, err)
			}
			size := fi.Size()
			limit := filesize_limit
			if size > filesize_limit {
				err := fmt.Errorf("Skipping much of the larger file (%s); %v > %v",
					name, size, filesize_limit)
				// Publish special error message.
				PublishLine(instance, nodeid, name, pub, &tail.Line{
					Text: err.Error(),
					Time: time.Now(),
					Err:  err})
			} else {
				limit = size
			}

			tail, err := tail.TailFile(filename, tail.Config{
				MaxLineSize: GetConfig().MaxRecordSize,
				MustExist:   true,
				Follow:      true,
				Location:    &tail.SeekInfo{-limit, os.SEEK_END},
				ReOpen:      false,
				Poll:        true,
				LimitRate:   GetConfig().RateLimit})
			if err != nil {
				log.Errorf("Cannot tail file (%s); %s", filename, err)
				return
			}

			for line := range tail.Lines {
				PublishLine(instance, nodeid, name, pub, line)
			}

			err = tail.Wait()
			if err != nil {
				log.Error(err)
			}
		}(name, filename)
	}
}

func PublishLine(
	instance *AppInstance, nodeid string,
	name string, pub *pubsub.Publisher,
	line *tail.Line) {

	msg := &AppLogMessage{
		Text:          line.Text,
		LogFilename:   name,
		UnixTime:      line.Time.Unix(),
		HumanTime:     ToHerokuTime(line.Time),
		Source:        instance.Type,
		InstanceIndex: instance.Index,
		AppID:         instance.AppID,
		AppName:       instance.AppName,
		AppGroup:      instance.AppGroup,
		NodeID:        nodeid,
	}

	if line.Err != nil {
		// Mark this as a special error record, as it is
		// coming from tail, not the app.
		msg.Source = "stackato.apptail"
		msg.LogFilename = ""
		log.Warnf("Published special rec: %+v", line)
	}

	err := msg.Publish(pub, false)
	if err != nil {
		log.Fatal(err)
	}

}
<commit_msg>better error message for read_limit violation<commit_after>package apptail

import (
	"encoding/json"
	"fmt"
	"github.com/ActiveState/log"
	"github.com/ActiveState/tail"
	"logyard"
	"logyard/util/pubsub"
	"os"
	"time"
	"unicode/utf8"
)

// AppInstance is the NATS message sent by dea/stager to notify of new
// instances.
type AppInstance struct {
	AppID    int
	AppName  string
	AppGroup string `json:"group"`
	Type     string
	Index    int
	LogFiles map[string]string
}

// AppLogMessage is a struct corresponding to an entry in the app log stream.
type AppLogMessage struct {
	Text          string
	LogFilename   string
	UnixTime      int64
	HumanTime     string
	Source        string // example: app, staging, stackato.dea, stackato.stager
	InstanceIndex int
	AppID         int
	AppName       string
	AppGroup      string
	NodeID        string // Host (DEA,stager) IP of this app instance
}

// Publish publishes an AppLogMessage to logyard after sanity checks.
func (line *AppLogMessage) Publish(pub *pubsub.Publisher, allowInvalidJson bool) error {
	// JSON must be a UTF-8 encoded string.
	if !utf8.ValidString(line.Text) {
		line.Text = string([]rune(line.Text))
	}

	data, err := json.Marshal(line)
	if err != nil {
		if allowInvalidJson {
			log.Errorf("Cannot encode %+v into JSON -- %s. Skipping this message", line, err)
		} else {
			return fmt.Errorf("Failed to encode app log record to JSON: ", err)
		}
	}
	key := fmt.Sprintf("apptail.%d", line.AppID)
	pub.MustPublish(key, string(data))
	return nil
}

// AppInstanceStarted is a function to be invoked when dea/stager
// starts an application instance.
func AppInstanceStarted(instance *AppInstance, nodeid string) {
	log.Infof("New app instance was started: %+v", instance)

	// convert MB to limit in bytes.
	filesize_limit := GetConfig().FileSizeLimit * 1024 * 1024

	if !(filesize_limit > 0) {
		panic("invalid value for `read_limit' in apptail config")
	}

	for name, filename := range instance.LogFiles {
		go func(name string, filename string) {
			pub := logyard.Broker.NewPublisherMust()
			defer pub.Stop()

			fi, err := os.Stat(filename)
			if err != nil {
				log.Errorf("Cannot stat file (%s); %s", filename, err)
			}
			size := fi.Size()
			limit := filesize_limit
			if size > filesize_limit {
				err := fmt.Errorf("Skipping much of a large log file (%s); size (%v bytes) > read_limit (%v bytes)",
					name, size, filesize_limit)
				// Publish special error message.
				PublishLine(instance, nodeid, name, pub, &tail.Line{
					Text: err.Error(),
					Time: time.Now(),
					Err:  err})
			} else {
				limit = size
			}

			tail, err := tail.TailFile(filename, tail.Config{
				MaxLineSize: GetConfig().MaxRecordSize,
				MustExist:   true,
				Follow:      true,
				Location:    &tail.SeekInfo{-limit, os.SEEK_END},
				ReOpen:      false,
				Poll:        true,
				LimitRate:   GetConfig().RateLimit})
			if err != nil {
				log.Errorf("Cannot tail file (%s); %s", filename, err)
				return
			}

			for line := range tail.Lines {
				PublishLine(instance, nodeid, name, pub, line)
			}

			err = tail.Wait()
			if err != nil {
				log.Error(err)
			}
		}(name, filename)
	}
}

func PublishLine(
	instance *AppInstance, nodeid string,
	name string, pub *pubsub.Publisher,
	line *tail.Line) {

	msg := &AppLogMessage{
		Text:          line.Text,
		LogFilename:   name,
		UnixTime:      line.Time.Unix(),
		HumanTime:     ToHerokuTime(line.Time),
		Source:        instance.Type,
		InstanceIndex: instance.Index,
		AppID:         instance.AppID,
		AppName:       instance.AppName,
		AppGroup:      instance.AppGroup,
		NodeID:        nodeid,
	}

	if line.Err != nil {
		// Mark this as a special error record, as it is
		// coming from tail, not the app.
		msg.Source = "stackato.apptail"
		msg.LogFilename = ""
		log.Warnf("[%s] %s", instance.AppName, line.Text)
	}

	err := msg.Publish(pub, false)
	if err != nil {
		log.Fatal(err)
	}

}
<|endoftext|>


<commit_before>package configo

import (
	"testing"
)

type Config struct {
	Listen  string   `cfg:"listen, :8805, netaddr, listen address of server"`
	Cluster []string `cfg:"cluster, ['127.0.0.1:8800'], dialstring"`
}

func TestUnmarshal(t *testing.T) {
	//data := []byte("listen = ':8804'")
	data := []byte("")
	c := &Config{}
	if err := Unmarshal(data, c); err != nil {
		t.Error(err)
	}
	t.Logf("Unmarshal result %s\v\n", c)
}
<commit_msg>Add Marshal test<commit_after>package configo

import (
	//"github.com/naoina/toml"
	"fmt"
	"testing"
)

type Config struct {
	Listen  string   `cfg:"listen, :8805, netaddr, listen address of server"`
	Cluster []string `cfg:"cluster, ['127.0.0.1:8800'], dialstring"`
	MaxConn int      `cfg:"max-conn, required, dialstring"`
}

func TestUnmarshal(t *testing.T) {
	//data := []byte("listen = ':8804'")
	data := []byte("max-conn=100")
	c := &Config{}
	if err := Unmarshal(data, c); err != nil {
		t.Error(err)
	}
	t.Logf("Unmarshal result %s\v\n", c)
}
func TestMarshal(t *testing.T) {
	c := &Config{}
	if data, err := Marshal(c); err != nil {
		t.Error(err)
	} else {
		fmt.Printf("%s", data)
	}
}
<|endoftext|>


<commit_before>package mutualtls_test

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io/ioutil"
	"lib/mutualtls"
	"math/rand"
	"net/http"
	"os"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/tedsuo/ifrit"
	"github.com/tedsuo/ifrit/grouper"
	"github.com/tedsuo/ifrit/http_server"
	"github.com/tedsuo/ifrit/sigmon"
)

var _ = Describe("TLS config for internal API server", func() {
	var (
		serverListenAddr string
		clientTLSConfig  *tls.Config
	)

	BeforeEach(func() {
		var err error
		serverListenAddr = fmt.Sprintf("127.0.0.1:%d", 40000+rand.Intn(10000))
		clientTLSConfig, err = mutualtls.NewClientTLSConfig(paths.ClientCertPath, paths.ClientKeyPath, paths.ServerCACertPath)
		Expect(err).NotTo(HaveOccurred())
	})

	startServer := func(tlsConfig *tls.Config) ifrit.Process {
		testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.Write([]byte("hello"))
		})
		someServer := http_server.NewTLSServer(serverListenAddr, testHandler, tlsConfig)

		members := grouper.Members{{
			Name:   "http_server",
			Runner: someServer,
		}}
		group := grouper.NewOrdered(os.Interrupt, members)
		monitor := ifrit.Invoke(sigmon.New(group))

		Eventually(monitor.Ready()).Should(BeClosed())
		return monitor
	}

	makeRequest := func(serverAddr string, clientTLSConfig *tls.Config) (*http.Response, error) {
		req, err := http.NewRequest("GET", "https://"+serverAddr+"/", nil)
		Expect(err).NotTo(HaveOccurred())
		client := &http.Client{
			Transport: &http.Transport{
				TLSClientConfig: clientTLSConfig,
			},
		}
		return client.Do(req)
	}

	Describe("NewServerTLSConfig", func() {
		It("returns a TLSConfig that can be used by an HTTP server", func() {
			serverTLSConfig, err := mutualtls.NewServerTLSConfig(paths.ServerCertPath, paths.ServerKeyPath, paths.ClientCACertPath)
			Expect(err).NotTo(HaveOccurred())

			server := startServer(serverTLSConfig)

			resp, err := makeRequest(serverListenAddr, clientTLSConfig)
			Expect(err).NotTo(HaveOccurred())
			Expect(resp.StatusCode).To(Equal(http.StatusOK))
			respBytes, err := ioutil.ReadAll(resp.Body)
			Expect(err).NotTo(HaveOccurred())
			Expect(respBytes).To(Equal([]byte("hello")))
			Expect(resp.Body.Close()).To(Succeed())

			server.Signal(os.Interrupt)
			Eventually(server.Wait()).Should(Receive())
		})

		Context("when the key pair cannot be created", func() {
			It("returns a meaningful error", func() {
				_, err := mutualtls.NewServerTLSConfig("", "", "")
				Expect(err).To(MatchError(HavePrefix("unable to load cert or key")))
			})
		})

		Context("when the server has been configured with the wrong CA for the client", func() {
			It("refuses to connect to the client", func() {
				serverTLSConfig, err := mutualtls.NewServerTLSConfig(paths.ServerCertPath, paths.ServerKeyPath, paths.WrongClientCACertPath)
				Expect(err).NotTo(HaveOccurred())

				server := startServer(serverTLSConfig)

				_, err = makeRequest(serverListenAddr, clientTLSConfig)
				Expect(err).To(MatchError(ContainSubstring("remote error")))

				server.Signal(os.Interrupt)
				Eventually(server.Wait()).Should(Receive())
			})
		})

		Context("when the client has been configured without a CA", func() {
			BeforeEach(func() {
				clientTLSConfig.RootCAs = nil
			})

			It("refuses to connect to the server", func() {
				serverTLSConfig, err := mutualtls.NewServerTLSConfig(paths.ServerCertPath, paths.ServerKeyPath, paths.ClientCACertPath)
				Expect(err).NotTo(HaveOccurred())

				server := startServer(serverTLSConfig)

				_, err = makeRequest(serverListenAddr, clientTLSConfig)
				Expect(err).To(MatchError(ContainSubstring("x509: certificate signed by unknown authority")))

				server.Signal(os.Interrupt)
				Eventually(server.Wait()).Should(Receive())
			})
		})

		Context("when the client has been configured with the wrong CA for the server", func() {
			BeforeEach(func() {
				wrongServerCACert, err := ioutil.ReadFile(paths.ClientCACertPath)
				Expect(err).NotTo(HaveOccurred())

				clientCertPool := x509.NewCertPool()
				clientCertPool.AppendCertsFromPEM(wrongServerCACert)
				clientTLSConfig.RootCAs = clientCertPool
			})

			It("refuses to connect to the server", func() {
				serverTLSConfig, err := mutualtls.NewServerTLSConfig(paths.ServerCertPath, paths.ServerKeyPath, paths.ClientCACertPath)
				Expect(err).NotTo(HaveOccurred())

				server := startServer(serverTLSConfig)

				_, err = makeRequest(serverListenAddr, clientTLSConfig)
				Expect(err).To(MatchError(ContainSubstring("x509: certificate signed by unknown authority")))

				server.Signal(os.Interrupt)
				Eventually(server.Wait()).Should(Receive())
			})
		})

		Context("when the client does not present client certificates to the server", func() {
			BeforeEach(func() {
				clientTLSConfig.Certificates = nil
			})

			It("refuses the connection from the client", func() {
				serverTLSConfig, err := mutualtls.NewServerTLSConfig(paths.ServerCertPath, paths.ServerKeyPath, paths.ClientCACertPath)
				Expect(err).NotTo(HaveOccurred())

				server := startServer(serverTLSConfig)

				_, err = makeRequest(serverListenAddr, clientTLSConfig)
				Expect(err).To(MatchError(ContainSubstring("remote error")))

				server.Signal(os.Interrupt)
				Eventually(server.Wait()).Should(Receive())
			})
		})

		Context("when the client presents certificates that the server does not trust", func() {
			BeforeEach(func() {
				invalidClient, err := tls.LoadX509KeyPair(paths.WrongClientCertPath, paths.WrongClientKeyPath)
				Expect(err).NotTo(HaveOccurred())
				clientTLSConfig.Certificates = []tls.Certificate{invalidClient}
			})

			It("refuses the connection from the client", func() {
				serverTLSConfig, err := mutualtls.NewServerTLSConfig(paths.ServerCertPath, paths.ServerKeyPath, paths.ClientCACertPath)
				Expect(err).NotTo(HaveOccurred())

				server := startServer(serverTLSConfig)

				_, err = makeRequest(serverListenAddr, clientTLSConfig)
				Expect(err).To(MatchError(ContainSubstring("remote error")))

				server.Signal(os.Interrupt)
				Eventually(server.Wait()).Should(Receive())
			})
		})

		It("returns config with reasonable security properties", func() {
			serverTLSConfig, err := mutualtls.NewServerTLSConfig(paths.ServerCertPath, paths.ServerKeyPath, paths.ClientCACertPath)
			Expect(err).NotTo(HaveOccurred())

			Expect(serverTLSConfig.PreferServerCipherSuites).To(BeTrue())
			Expect(serverTLSConfig.MinVersion).To(Equal(uint16(tls.VersionTLS12)))
			Expect(serverTLSConfig.CipherSuites).To(Equal([]uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}))
		})
	})
})
<commit_msg>Add test to mutualtls for unsupported ciphersuites<commit_after>package mutualtls_test

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io/ioutil"
	"lib/mutualtls"
	"math/rand"
	"net/http"
	"os"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/tedsuo/ifrit"
	"github.com/tedsuo/ifrit/grouper"
	"github.com/tedsuo/ifrit/http_server"
	"github.com/tedsuo/ifrit/sigmon"
)

var _ = Describe("TLS config for internal API server", func() {
	var (
		serverListenAddr string
		clientTLSConfig  *tls.Config
	)

	BeforeEach(func() {
		var err error
		serverListenAddr = fmt.Sprintf("127.0.0.1:%d", 40000+rand.Intn(10000))
		clientTLSConfig, err = mutualtls.NewClientTLSConfig(paths.ClientCertPath, paths.ClientKeyPath, paths.ServerCACertPath)
		Expect(err).NotTo(HaveOccurred())
	})

	startServer := func(tlsConfig *tls.Config) ifrit.Process {
		testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.Write([]byte("hello"))
		})
		someServer := http_server.NewTLSServer(serverListenAddr, testHandler, tlsConfig)

		members := grouper.Members{{
			Name:   "http_server",
			Runner: someServer,
		}}
		group := grouper.NewOrdered(os.Interrupt, members)
		monitor := ifrit.Invoke(sigmon.New(group))

		Eventually(monitor.Ready()).Should(BeClosed())
		return monitor
	}

	makeRequest := func(serverAddr string, clientTLSConfig *tls.Config) (*http.Response, error) {
		req, err := http.NewRequest("GET", "https://"+serverAddr+"/", nil)
		Expect(err).NotTo(HaveOccurred())
		client := &http.Client{
			Transport: &http.Transport{
				TLSClientConfig: clientTLSConfig,
			},
		}
		return client.Do(req)
	}

	Describe("NewServerTLSConfig", func() {
		It("returns a TLSConfig that can be used by an HTTP server", func() {
			serverTLSConfig, err := mutualtls.NewServerTLSConfig(paths.ServerCertPath, paths.ServerKeyPath, paths.ClientCACertPath)
			Expect(err).NotTo(HaveOccurred())

			server := startServer(serverTLSConfig)

			resp, err := makeRequest(serverListenAddr, clientTLSConfig)
			Expect(err).NotTo(HaveOccurred())
			Expect(resp.StatusCode).To(Equal(http.StatusOK))
			respBytes, err := ioutil.ReadAll(resp.Body)
			Expect(err).NotTo(HaveOccurred())
			Expect(respBytes).To(Equal([]byte("hello")))
			Expect(resp.Body.Close()).To(Succeed())

			server.Signal(os.Interrupt)
			Eventually(server.Wait()).Should(Receive())
		})

		Context("when the key pair cannot be created", func() {
			It("returns a meaningful error", func() {
				_, err := mutualtls.NewServerTLSConfig("", "", "")
				Expect(err).To(MatchError(HavePrefix("unable to load cert or key")))
			})
		})

		Context("when the server has been configured with the wrong CA for the client", func() {
			It("refuses to connect to the client", func() {
				serverTLSConfig, err := mutualtls.NewServerTLSConfig(paths.ServerCertPath, paths.ServerKeyPath, paths.WrongClientCACertPath)
				Expect(err).NotTo(HaveOccurred())

				server := startServer(serverTLSConfig)

				_, err = makeRequest(serverListenAddr, clientTLSConfig)
				Expect(err).To(MatchError(ContainSubstring("remote error")))

				server.Signal(os.Interrupt)
				Eventually(server.Wait()).Should(Receive())
			})
		})

		Context("when the client has been configured without a CA", func() {
			BeforeEach(func() {
				clientTLSConfig.RootCAs = nil
			})

			It("refuses to connect to the server", func() {
				serverTLSConfig, err := mutualtls.NewServerTLSConfig(paths.ServerCertPath, paths.ServerKeyPath, paths.ClientCACertPath)
				Expect(err).NotTo(HaveOccurred())

				server := startServer(serverTLSConfig)

				_, err = makeRequest(serverListenAddr, clientTLSConfig)
				Expect(err).To(MatchError(ContainSubstring("x509: certificate signed by unknown authority")))

				server.Signal(os.Interrupt)
				Eventually(server.Wait()).Should(Receive())
			})
		})

		Context("when the client has been configured with the wrong CA for the server", func() {
			BeforeEach(func() {
				wrongServerCACert, err := ioutil.ReadFile(paths.ClientCACertPath)
				Expect(err).NotTo(HaveOccurred())

				clientCertPool := x509.NewCertPool()
				clientCertPool.AppendCertsFromPEM(wrongServerCACert)
				clientTLSConfig.RootCAs = clientCertPool
			})

			It("refuses to connect to the server", func() {
				serverTLSConfig, err := mutualtls.NewServerTLSConfig(paths.ServerCertPath, paths.ServerKeyPath, paths.ClientCACertPath)
				Expect(err).NotTo(HaveOccurred())

				server := startServer(serverTLSConfig)

				_, err = makeRequest(serverListenAddr, clientTLSConfig)
				Expect(err).To(MatchError(ContainSubstring("x509: certificate signed by unknown authority")))

				server.Signal(os.Interrupt)
				Eventually(server.Wait()).Should(Receive())
			})
		})

		Context("when the client does not present client certificates to the server", func() {
			BeforeEach(func() {
				clientTLSConfig.Certificates = nil
			})

			It("refuses the connection from the client", func() {
				serverTLSConfig, err := mutualtls.NewServerTLSConfig(paths.ServerCertPath, paths.ServerKeyPath, paths.ClientCACertPath)
				Expect(err).NotTo(HaveOccurred())

				server := startServer(serverTLSConfig)

				_, err = makeRequest(serverListenAddr, clientTLSConfig)
				Expect(err).To(MatchError(ContainSubstring("remote error")))

				server.Signal(os.Interrupt)
				Eventually(server.Wait()).Should(Receive())
			})
		})

		Context("when the client presents certificates that the server does not trust", func() {
			BeforeEach(func() {
				invalidClient, err := tls.LoadX509KeyPair(paths.WrongClientCertPath, paths.WrongClientKeyPath)
				Expect(err).NotTo(HaveOccurred())
				clientTLSConfig.Certificates = []tls.Certificate{invalidClient}
			})

			It("refuses the connection from the client", func() {
				serverTLSConfig, err := mutualtls.NewServerTLSConfig(paths.ServerCertPath, paths.ServerKeyPath, paths.ClientCACertPath)
				Expect(err).NotTo(HaveOccurred())

				server := startServer(serverTLSConfig)

				_, err = makeRequest(serverListenAddr, clientTLSConfig)
				Expect(err).To(MatchError(ContainSubstring("remote error")))

				server.Signal(os.Interrupt)
				Eventually(server.Wait()).Should(Receive())
			})
		})

		Context("when the client is configured to use an unsupported ciphersuite", func() {
			BeforeEach(func() {
				clientTLSConfig.CipherSuites = []uint16{tls.TLS_RSA_WITH_AES_256_GCM_SHA384}
			})

			It("refuses the connection from the client", func() {
				serverTLSConfig, err := mutualtls.NewServerTLSConfig(paths.ServerCertPath, paths.ServerKeyPath, paths.ClientCACertPath)
				Expect(err).NotTo(HaveOccurred())

				server := startServer(serverTLSConfig)

				_, err = makeRequest(serverListenAddr, clientTLSConfig)
				Expect(err).To(MatchError(ContainSubstring("remote error")))

				server.Signal(os.Interrupt)
				Eventually(server.Wait()).Should(Receive())
			})
		})

		It("returns config with reasonable security properties", func() {
			serverTLSConfig, err := mutualtls.NewServerTLSConfig(paths.ServerCertPath, paths.ServerKeyPath, paths.ClientCACertPath)
			Expect(err).NotTo(HaveOccurred())

			Expect(serverTLSConfig.PreferServerCipherSuites).To(BeTrue())
			Expect(serverTLSConfig.MinVersion).To(Equal(uint16(tls.VersionTLS12)))
			Expect(serverTLSConfig.CipherSuites).To(Equal([]uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}))
		})
	})
})
<|endoftext|>