<commit_before>package dbus

import (
	"reflect"
	"strings"
)

var (
	byteType       = reflect.TypeOf(byte(0))
	boolType       = reflect.TypeOf(false)
	uint8Type      = reflect.TypeOf(uint8(0))
	int16Type      = reflect.TypeOf(int16(0))
	uint16Type     = reflect.TypeOf(uint16(0))
	int32Type      = reflect.TypeOf(int32(0))
	uint32Type     = reflect.TypeOf(uint32(0))
	int64Type      = reflect.TypeOf(int64(0))
	uint64Type     = reflect.TypeOf(uint64(0))
	float64Type    = reflect.TypeOf(float64(0))
	stringType     = reflect.TypeOf("")
	signatureType  = reflect.TypeOf(Signature{""})
	objectPathType = reflect.TypeOf(ObjectPath(""))
	variantType    = reflect.TypeOf(Variant{Signature{""}, nil})
	interfacesType = reflect.TypeOf([]interface{}{})
)

type invalidTypeError struct {
	reflect.Type
}

func (err invalidTypeError) Error() string {
	return "dbus: invalid type " + err.Type.String()
}

var sigToType = map[byte]reflect.Type{
	'y': byteType,
	'b': boolType,
	'n': int16Type,
	'q': uint16Type,
	'i': int32Type,
	'u': uint32Type,
	'x': int64Type,
	't': uint64Type,
	'd': float64Type,
	's': stringType,
	'g': signatureType,
	'o': objectPathType,
	'v': variantType,
}

// Signature represents a correct type signature as specified
// by the DBus specification.
type Signature struct {
	str string
}

// GetSignature returns the concatenation of all the signatures
// of the given values. It panics if one of them is not representable
// in DBus.
func GetSignature(vs ...interface{}) Signature {
	var s string
	for _, v := range vs {
		s += getSignature(reflect.TypeOf(v))
	}
	return Signature{s}
}

func getSignature(v reflect.Type) string {
	// handle simple types first
	switch v.Kind() {
	case reflect.Uint8:
		return "y"
	case reflect.Bool:
		return "b"
	case reflect.Int16:
		return "n"
	case reflect.Uint16:
		return "q"
	case reflect.Int32, reflect.Int:
		return "i"
	case reflect.Uint32, reflect.Uint:
		return "u"
	case reflect.Int64:
		return "x"
	case reflect.Uint64:
		return "t"
	case reflect.Float64:
		return "d"
	case reflect.Ptr:
		return getSignature(v.Elem())
	case reflect.String:
		if v == objectPathType {
			return "o"
		}
		return "s"
	case reflect.Struct:
		if v == variantType {
			return "v"
		} else if v == signatureType {
			return "g"
		}
		var s string
		for i := 0; i < v.NumField(); i++ {
			s += getSignature(v.Field(i).Type)
		}
		return "(" + s + ")"
	case reflect.Array, reflect.Slice:
		return "a" + getSignature(v.Elem())
	case reflect.Map:
		return "a{" + getSignature(v.Key()) + getSignature(v.Elem()) + "}"
	}
	panic("unknown type " + v.String())
}

// StringToSig returns the signature represented by this string, or a
// SignatureError if the string is not a valid signature.
func StringToSig(s string) (sig Signature, err error) {
	if len(s) == 0 {
		return
	}
	sig.str = s
	for err == nil && len(s) != 0 {
		err, s = validSingle(s)
	}
	if err != nil {
		sig = Signature{""}
	}

	return
}

// SrintToSigMust behaves like StringToSig, except that it panics if s is not
// valid.
func StringToSigMust(s string) Signature {
	sig, err := StringToSig(s)
	if err != nil {
		panic(err)
	}
	return sig
}

// Empty retruns whether the signature is the empty signature.
func (s Signature) Empty() bool {
	return s.str == ""
}

// Single returns whether the signature represents a single, complete type.
func (s Signature) Single() bool {
	_, r := validSingle(s.str)
	return r == ""
}

// String returns the signature's string representation.
func (s Signature) String() string {
	return s.str
}

// Values returns a slice of pointers to values that match the given signature.
func (s Signature) Values() []interface{} {
	slice := make([]interface{}, 0)
	str := s.str
	for str != "" {
		slice = append(slice, reflect.New(value(str)).Interface())
		_, str = validSingle(str)
	}
	return slice
}

// A SignatureError indicates that a signature passed to a function or received
// on a connection is not a valid signature.
type SignatureError struct {
	Sig    string
	Reason string
}

func (err SignatureError) Error() string {
	return "invalid signature: '" + err.Sig + "' (" + err.Reason + ")"
}

// An ObjectPath is an object path as defined by the DBus spec.
type ObjectPath string

// IsValid returns whether the path is valid.
func (o ObjectPath) IsValid() bool {
	s := string(o)
	if len(s) == 0 {
		return false
	}
	if s[0] != '/' {
		return false
	}
	if s[len(s)-1] == '/' && len(s) != 1 {
		return false
	}
	// probably not used, but technically possible
	if s == "/" {
		return true
	}
	split := strings.Split(s[1:], "/")
	for _, v := range split {
		if len(v) == 0 {
			return false
		}
		for _, c := range v {
			if !((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
				(c >= 'a' && c <= 'z') || c == '_') {

				return false
			}
		}
	}
	return true
}

// Variant represents a DBus variant type.
type Variant struct {
	sig   Signature
	value interface{}
}

// MakeVariant converts the given value to a Variant.
func MakeVariant(v interface{}) Variant {
	return Variant{GetSignature(v), v}
}

// Signature returns the DBus signature of the underlying value of v.
func (v Variant) Signature() Signature {
	return v.sig
}

// Value returns the underlying value of v.
func (v Variant) Value() interface{} {
	return v.value
}

func alignment(t reflect.Type) int {
	n, ok := map[reflect.Type]int{
		variantType:    1,
		objectPathType: 4,
		signatureType:  1,
	}[t]
	if ok {
		return n
	}
	switch t.Kind() {
	case reflect.Uint8:
		return 1
	case reflect.Uint16, reflect.Int16:
		return 2
	case reflect.Uint32, reflect.Int32, reflect.String, reflect.Array, reflect.Slice:
		return 4
	case reflect.Uint64, reflect.Int64, reflect.Float64, reflect.Struct:
		return 8
	case reflect.Ptr:
		return alignment(t.Elem())
	}
	return 1
}

func isKeyType(t reflect.Type) bool {
	switch t.Kind() {
	case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float64,
		reflect.String:

		return true
	}
	return false
}

// Try to read a single type from this string. If it was successfull, valid is
// true and r is the remaining unparsed part. Otherwise, valid is false and r is
// "".
func validSingle(s string) (err error, rem string) {
	if s == "" {
		return SignatureError{Sig: s, Reason: "empty signature"}, ""
	}
	switch s[0] {
	case 'y', 'b', 'n', 'q', 'i', 'u', 'x', 't', 'd', 's', 'g', 'o', 'v':
		return nil, s[1:]
	case 'a':
		if len(s) > 1 && s[1] == '{' {
			i := strings.LastIndex(s, "}")
			if i == -1 {
				return SignatureError{Sig: s, Reason: "unmatched '{'"}, ""
			}
			rem = s[i+1:]
			s = s[1:i]
			if err, _ = validSingle(s[0:1]); err != nil {
				return SignatureError{Sig: s, Reason: "invalid map key type"}, ""
			}
			err, nr := validSingle(s[1:])
			if err != nil {
				return err, ""
			}
			if nr != "" {
				return SignatureError{Sig: s, Reason: "too many types in dict"}, ""
			}
			return nil, rem
		}
		return validSingle(s[1:])
	case '(':
		i := strings.LastIndex(s, ")")
		if i == -1 {
			return SignatureError{Sig: s, Reason: "unmatched ')'"}, ""
		}
		rem = s[i+1:]
		s = s[1:i]
		for err == nil && s != "" {
			err, s = validSingle(s)
		}
		if err != nil {
			rem = ""
		}
		return
	}
	return SignatureError{Sig: s, Reason: "invalid type character"}, ""
}

// value returns the type of the given signature. It ignores any left over
// characters and panics if s doesn't start with a valid type signature.
func value(s string) (t reflect.Type) {
	err, _ := validSingle(s)
	if err != nil {
		panic(err)
	}

	if t, ok := sigToType[s[0]]; ok {
		return t
	}
	switch s[0] {
	case 'a':
		t = reflect.SliceOf(sigToType[s[1]])
	case '(':
		t = reflect.TypeOf([]interface{}{})
	case '{':
		t = reflect.MapOf(value(s[1:2]), value(s[2:]))
	}
	return
}
<commit_msg>Return the correct types for struct / dict signatures<commit_after>package dbus

import (
	"reflect"
	"strings"
)

var (
	byteType       = reflect.TypeOf(byte(0))
	boolType       = reflect.TypeOf(false)
	uint8Type      = reflect.TypeOf(uint8(0))
	int16Type      = reflect.TypeOf(int16(0))
	uint16Type     = reflect.TypeOf(uint16(0))
	int32Type      = reflect.TypeOf(int32(0))
	uint32Type     = reflect.TypeOf(uint32(0))
	int64Type      = reflect.TypeOf(int64(0))
	uint64Type     = reflect.TypeOf(uint64(0))
	float64Type    = reflect.TypeOf(float64(0))
	stringType     = reflect.TypeOf("")
	signatureType  = reflect.TypeOf(Signature{""})
	objectPathType = reflect.TypeOf(ObjectPath(""))
	variantType    = reflect.TypeOf(Variant{Signature{""}, nil})
	interfacesType = reflect.TypeOf([]interface{}{})
)

type invalidTypeError struct {
	reflect.Type
}

func (err invalidTypeError) Error() string {
	return "dbus: invalid type " + err.Type.String()
}

var sigToType = map[byte]reflect.Type{
	'y': byteType,
	'b': boolType,
	'n': int16Type,
	'q': uint16Type,
	'i': int32Type,
	'u': uint32Type,
	'x': int64Type,
	't': uint64Type,
	'd': float64Type,
	's': stringType,
	'g': signatureType,
	'o': objectPathType,
	'v': variantType,
}

// Signature represents a correct type signature as specified
// by the DBus specification.
type Signature struct {
	str string
}

// GetSignature returns the concatenation of all the signatures
// of the given values. It panics if one of them is not representable
// in DBus.
func GetSignature(vs ...interface{}) Signature {
	var s string
	for _, v := range vs {
		s += getSignature(reflect.TypeOf(v))
	}
	return Signature{s}
}

func getSignature(v reflect.Type) string {
	// handle simple types first
	switch v.Kind() {
	case reflect.Uint8:
		return "y"
	case reflect.Bool:
		return "b"
	case reflect.Int16:
		return "n"
	case reflect.Uint16:
		return "q"
	case reflect.Int32, reflect.Int:
		return "i"
	case reflect.Uint32, reflect.Uint:
		return "u"
	case reflect.Int64:
		return "x"
	case reflect.Uint64:
		return "t"
	case reflect.Float64:
		return "d"
	case reflect.Ptr:
		return getSignature(v.Elem())
	case reflect.String:
		if v == objectPathType {
			return "o"
		}
		return "s"
	case reflect.Struct:
		if v == variantType {
			return "v"
		} else if v == signatureType {
			return "g"
		}
		var s string
		for i := 0; i < v.NumField(); i++ {
			s += getSignature(v.Field(i).Type)
		}
		return "(" + s + ")"
	case reflect.Array, reflect.Slice:
		return "a" + getSignature(v.Elem())
	case reflect.Map:
		return "a{" + getSignature(v.Key()) + getSignature(v.Elem()) + "}"
	}
	panic("unknown type " + v.String())
}

// StringToSig returns the signature represented by this string, or a
// SignatureError if the string is not a valid signature.
func StringToSig(s string) (sig Signature, err error) {
	if len(s) == 0 {
		return
	}
	sig.str = s
	for err == nil && len(s) != 0 {
		err, s = validSingle(s)
	}
	if err != nil {
		sig = Signature{""}
	}

	return
}

// SrintToSigMust behaves like StringToSig, except that it panics if s is not
// valid.
func StringToSigMust(s string) Signature {
	sig, err := StringToSig(s)
	if err != nil {
		panic(err)
	}
	return sig
}

// Empty retruns whether the signature is the empty signature.
func (s Signature) Empty() bool {
	return s.str == ""
}

// Single returns whether the signature represents a single, complete type.
func (s Signature) Single() bool {
	_, r := validSingle(s.str)
	return r == ""
}

// String returns the signature's string representation.
func (s Signature) String() string {
	return s.str
}

// Values returns a slice of pointers to values that match the given signature.
func (s Signature) Values() []interface{} {
	slice := make([]interface{}, 0)
	str := s.str
	for str != "" {
		slice = append(slice, reflect.New(value(str)).Interface())
		_, str = validSingle(str)
	}
	return slice
}

// A SignatureError indicates that a signature passed to a function or received
// on a connection is not a valid signature.
type SignatureError struct {
	Sig    string
	Reason string
}

func (err SignatureError) Error() string {
	return "invalid signature: '" + err.Sig + "' (" + err.Reason + ")"
}

// An ObjectPath is an object path as defined by the DBus spec.
type ObjectPath string

// IsValid returns whether the path is valid.
func (o ObjectPath) IsValid() bool {
	s := string(o)
	if len(s) == 0 {
		return false
	}
	if s[0] != '/' {
		return false
	}
	if s[len(s)-1] == '/' && len(s) != 1 {
		return false
	}
	// probably not used, but technically possible
	if s == "/" {
		return true
	}
	split := strings.Split(s[1:], "/")
	for _, v := range split {
		if len(v) == 0 {
			return false
		}
		for _, c := range v {
			if !((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
				(c >= 'a' && c <= 'z') || c == '_') {

				return false
			}
		}
	}
	return true
}

// Variant represents a DBus variant type.
type Variant struct {
	sig   Signature
	value interface{}
}

// MakeVariant converts the given value to a Variant.
func MakeVariant(v interface{}) Variant {
	return Variant{GetSignature(v), v}
}

// Signature returns the DBus signature of the underlying value of v.
func (v Variant) Signature() Signature {
	return v.sig
}

// Value returns the underlying value of v.
func (v Variant) Value() interface{} {
	return v.value
}

func alignment(t reflect.Type) int {
	n, ok := map[reflect.Type]int{
		variantType:    1,
		objectPathType: 4,
		signatureType:  1,
	}[t]
	if ok {
		return n
	}
	switch t.Kind() {
	case reflect.Uint8:
		return 1
	case reflect.Uint16, reflect.Int16:
		return 2
	case reflect.Uint32, reflect.Int32, reflect.String, reflect.Array, reflect.Slice:
		return 4
	case reflect.Uint64, reflect.Int64, reflect.Float64, reflect.Struct:
		return 8
	case reflect.Ptr:
		return alignment(t.Elem())
	}
	return 1
}

func isKeyType(t reflect.Type) bool {
	switch t.Kind() {
	case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float64,
		reflect.String:

		return true
	}
	return false
}

// Try to read a single type from this string. If it was successfull, valid is
// true and r is the remaining unparsed part. Otherwise, valid is false and r is
// "".
func validSingle(s string) (err error, rem string) {
	if s == "" {
		return SignatureError{Sig: s, Reason: "empty signature"}, ""
	}
	switch s[0] {
	case 'y', 'b', 'n', 'q', 'i', 'u', 'x', 't', 'd', 's', 'g', 'o', 'v':
		return nil, s[1:]
	case 'a':
		if len(s) > 1 && s[1] == '{' {
			i := strings.LastIndex(s, "}")
			if i == -1 {
				return SignatureError{Sig: s, Reason: "unmatched '{'"}, ""
			}
			rem = s[i+1:]
			s = s[2:i]
			if err, _ = validSingle(s[0:1]); err != nil {
				return SignatureError{Sig: s, Reason: "invalid map key type"}, ""
			}
			err, nr := validSingle(s[1:])
			if err != nil {
				return err, ""
			}
			if nr != "" {
				return SignatureError{Sig: s, Reason: "too many types in dict"}, ""
			}
			return nil, rem
		}
		return validSingle(s[1:])
	case '(':
		i := strings.LastIndex(s, ")")
		if i == -1 {
			return SignatureError{Sig: s, Reason: "unmatched ')'"}, ""
		}
		rem = s[i+1:]
		s = s[1:i]
		for err == nil && s != "" {
			err, s = validSingle(s)
		}
		if err != nil {
			rem = ""
		}
		return
	}
	return SignatureError{Sig: s, Reason: "invalid type character"}, ""
}

// value returns the type of the given signature. It ignores any left over
// characters and panics if s doesn't start with a valid type signature.
func value(s string) (t reflect.Type) {
	err, _ := validSingle(s)
	if err != nil {
		panic(err)
	}

	if t, ok := sigToType[s[0]]; ok {
		return t
	}
	switch s[0] {
	case 'a':
		if s[1] == '{' {
			i := strings.LastIndex(s, "}")
			t = reflect.MapOf(sigToType[s[2]], value(s[3:i]))
		} else if s[1] == '(' {
			t = interfacesType
		} else {
			t = reflect.SliceOf(sigToType[s[1]])
		}
	case '(':
		t = interfacesType
	}
	return
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
	"log"
	"net/http"
	"strings"
	"time"

	"github.com/elazarl/goproxy"
	"github.com/elazarl/goproxy/ext/html"
)

type ContextUserData struct {
	Store bool
	Time  int64
}

type Content struct {
	//Id bson.ObjectId
	Request  Request  "request"
	Response Response "response"
}

type Request struct {
	Body    string      "body"
	Date    time.Time   "date"
	Host    string      "host"
	Method  string      "method"
	Path    string      "path"
	Time    float32     "time"
	Headers http.Header "headers"
}

type Response struct {
	Body    string      "body"
	Headers http.Header "headers"
	Status  int         "status"
}

func main() {
	verbose := flag.Bool("v", false, "should every proxy request be logged to stdout")
	mongourl := flag.String("mongourl", "", "record request/response in mongodb")
	mock := flag.Bool("m", false, "send fake responses")
	addr := flag.String("l", ":8080", "on which address should the proxy listen")

	flag.Parse()

	c := &mgo.Collection{}

	if len(*mongourl) != 0 {
		// Mongo DB connection
		session, err := mgo.Dial(*mongourl)
		if err != nil {
			panic(err)
		}
		defer session.Close()

		// Optional. Switch the session to a monotonic behavior.
		session.SetMode(mgo.Monotonic, true)

		c = session.DB("proxyservice").C("log")
	}

	proxy := goproxy.NewProxyHttpServer()
	proxy.Verbose = *verbose

	proxy.OnRequest().HandleConnectFunc(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {
		ctx.Logf("received connect for", host)
		return goproxy.MitmConnect, host
	})

	proxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
		log.Printf("Request: %s %s %s", req.Method, req.Host, req.RequestURI)

		host := req.Host
		// Request to domain--name-co-uk.mocky.dev
		// will be forwarded to domain-name.co.uk
		if strings.Contains(host, ".mocky.dev") {
			host = strings.Replace(host, ".mocky.dev", "", 1)
			host = strings.Replace(host, "-", ".", -1)
			host = strings.Replace(host, "..", "-", -1)
			log.Printf("Target Host: %s", host)
			req.Host = host

		}

		if c != nil && *mock && req.Method != "CONNECT" {
			result := Content{}
			ctx.Logf("Looking for existing request")
			/*fmt.Println("RequestURI:", req.RequestURI)
			  fmt.Println("Path:", req.URL.Path)
			  fmt.Println("Host:", req.Host)
			  fmt.Println("Method:", req.Method)*/
			err := c.Find(bson.M{"request.host": req.Host, "request.method": req.Method, "response.status": 200, "request.path": req.URL.Path}).Sort("-request.date").One(&result)
			if err == nil {
				ctx.Logf("Found one")
				/*fmt.Println("Path:", result.Request.Path)
				  //fmt.Println("Body:", result.Request.Body)
				  fmt.Println("Method:", result.Request.Method)
				  fmt.Println("Host:", result.Request.Host)
				  fmt.Println("Time:", result.Request.Time)
				  fmt.Println("Date:", result.Request.Date)
				  fmt.Println("Headers:", result.Request.Headers)

				  //fmt.Println("Body:", result.Response.Body)
				  fmt.Println("Status:", result.Response.Status)
				  fmt.Println("Headers:", result.Response.Headers)*/

				resp := goproxy.NewResponse(req, goproxy.ContentTypeHtml, result.Response.Status, result.Response.Body)
				ctx.UserData = ContextUserData{Store: false, Time: 0}
				return req, resp
			}

		}

		ctx.UserData = ContextUserData{Store: true, Time: time.Now().UnixNano()}
		return req, nil
	})

	proxy.OnResponse().Do(goproxy_html.HandleString(
		func(s string, ctx *goproxy.ProxyCtx) string {
			if c != nil && ctx.UserData != nil && ctx.UserData.(ContextUserData).Store && ctx.Resp.Request.Method != "CONNECT" && ctx.Resp.StatusCode == 200 {
				ctx.Logf("We should probably save this response")
				content := Content{
					//Id: bson.NewObjectId(),
					Request:  Request{Path: ctx.Resp.Request.URL.Path, Host: ctx.Resp.Request.Host, Method: ctx.Resp.Request.Method, Date: time.Now(), Time: float32(ctx.UserData.(ContextUserData).Time) / 1.0e9, Headers: ctx.Resp.Request.Header},
					Response: Response{Status: ctx.Resp.StatusCode, Headers: ctx.Resp.Header, Body: s}}

				err := c.Insert(content)
				if err != nil {
					ctx.Logf("Can't insert document: %v\n", err)
				}
			}

			ctx.Logf(s)
			return s
		}))

	log.Println("Starting Proxy")
	log.Fatalln(http.ListenAndServe(*addr, proxy))
}
<commit_msg>HandleBytes instead of HandleString<commit_after>package main

import (
	"flag"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
	"log"
	"net/http"
	"strings"
	"time"

	"github.com/elazarl/goproxy"
	//"github.com/elazarl/goproxy/ext/html"
)

type ContextUserData struct {
	Store bool
	Time  int64
}

type Content struct {
	//Id bson.ObjectId
	Request  Request  "request"
	Response Response "response"
}

type Request struct {
	Body    string      "body"
	Date    time.Time   "date"
	Host    string      "host"
	Method  string      "method"
	Path    string      "path"
	Time    float32     "time"
	Headers http.Header "headers"
}

type Response struct {
	Body    string      "body"
	Headers http.Header "headers"
	Status  int         "status"
}

func main() {
	verbose := flag.Bool("v", false, "should every proxy request be logged to stdout")
	mongourl := flag.String("mongourl", "", "record request/response in mongodb")
	mock := flag.Bool("m", false, "send fake responses")
	addr := flag.String("l", ":8080", "on which address should the proxy listen")

	flag.Parse()

	c := &mgo.Collection{}

	if len(*mongourl) != 0 {
		// Mongo DB connection
		session, err := mgo.Dial(*mongourl)
		if err != nil {
			panic(err)
		}
		defer session.Close()

		// Optional. Switch the session to a monotonic behavior.
		session.SetMode(mgo.Monotonic, true)

		c = session.DB("proxyservice").C("log")
	}

	proxy := goproxy.NewProxyHttpServer()
	proxy.Verbose = *verbose

	proxy.OnRequest().HandleConnectFunc(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {
		ctx.Logf("received connect for", host)
		return goproxy.MitmConnect, host
	})

	proxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
		log.Printf("Request: %s %s %s", req.Method, req.Host, req.RequestURI)

		host := req.Host
		// Request to domain--name-co-uk.mocky.dev
		// will be forwarded to domain-name.co.uk
		if strings.Contains(host, ".mocky.dev") {
			host = strings.Replace(host, ".mocky.dev", "", 1)
			host = strings.Replace(host, "-", ".", -1)
			host = strings.Replace(host, "..", "-", -1)
			log.Printf("Target Host: %s", host)
			req.Host = host

		}

		if c != nil && *mock && req.Method != "CONNECT" {
			result := Content{}
			ctx.Logf("Looking for existing request")
			/*fmt.Println("RequestURI:", req.RequestURI)
			  fmt.Println("Path:", req.URL.Path)
			  fmt.Println("Host:", req.Host)
			  fmt.Println("Method:", req.Method)*/
			err := c.Find(bson.M{"request.host": req.Host, "request.method": req.Method, "response.status": 200, "request.path": req.URL.Path}).Sort("-request.date").One(&result)
			if err == nil {
				ctx.Logf("Found one")
				/*fmt.Println("Path:", result.Request.Path)
				  //fmt.Println("Body:", result.Request.Body)
				  fmt.Println("Method:", result.Request.Method)
				  fmt.Println("Host:", result.Request.Host)
				  fmt.Println("Time:", result.Request.Time)
				  fmt.Println("Date:", result.Request.Date)
				  fmt.Println("Headers:", result.Request.Headers)

				  //fmt.Println("Body:", result.Response.Body)
				  fmt.Println("Status:", result.Response.Status)
				  fmt.Println("Headers:", result.Response.Headers)*/

				resp := goproxy.NewResponse(req, goproxy.ContentTypeHtml, result.Response.Status, result.Response.Body)
				ctx.UserData = ContextUserData{Store: false, Time: 0}
				return req, resp
			}

		}

		ctx.UserData = ContextUserData{Store: true, Time: time.Now().UnixNano()}
		return req, nil
	})

	proxy.OnResponse().Do(goproxy.HandleBytes(
		func(b []byte, ctx *goproxy.ProxyCtx) []byte {
			ctx.Logf("Method: %s - host: %s", ctx.Resp.Request.Method, ctx.Resp.Request.Host)
			if c != nil && ctx.UserData != nil && ctx.UserData.(ContextUserData).Store && ctx.Resp.StatusCode == 200 && ctx.Resp.Request.Method != "CONNECT" {
				// Get Body as string
				s := string(b[:])
				ctx.Logf("Recording both request/response")
				content := Content{
					//Id: bson.NewObjectId(),
					Request:  Request{Path: ctx.Resp.Request.URL.Path, Host: ctx.Resp.Request.Host, Method: ctx.Resp.Request.Method, Date: time.Now(), Time: float32(ctx.UserData.(ContextUserData).Time) / 1.0e9, Headers: ctx.Resp.Request.Header},
					Response: Response{Status: ctx.Resp.StatusCode, Headers: ctx.Resp.Header, Body: s}}

				err := c.Insert(content)
				if err != nil {
					ctx.Logf("Can't insert document: %v\n", err)
				}
				ctx.Logf("Body: %s", s)
			}

			return b
		}))

	log.Println("Starting Proxy")
	log.Fatalln(http.ListenAndServe(*addr, proxy))
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"

	"github.com/cloudflare/unsee/internal/alertmanager"
	"github.com/cloudflare/unsee/internal/config"
	"github.com/gin-gonic/gin"

	log "github.com/sirupsen/logrus"
)

func proxyPathPrefix(name string) string {
	return fmt.Sprintf("%sproxy/alertmanager/%s", config.Config.Listen.Prefix, name)
}

// NewAlertmanagerProxy creates a proxy instance for given alertmanager instance
func NewAlertmanagerProxy(alertmanager *alertmanager.Alertmanager) (*httputil.ReverseProxy, error) {
	upstreamURL, err := url.Parse(alertmanager.URI)
	if err != nil {
		return nil, err
	}
	proxy := httputil.ReverseProxy{
		Director: func(req *http.Request) {
			req.URL.Scheme = upstreamURL.Scheme
			req.URL.Host = upstreamURL.Host
			req.URL.Path = strings.TrimPrefix(req.URL.Path, proxyPathPrefix(alertmanager.Name))
			// drop Accept-Encoding header so we always get uncompressed reponses from
			// upstream, there's a gzip middleware that's global so we don't want it
			// to gzip twice
			req.Header.Del("Accept-Encoding")
			log.Debugf("[%s] Proxy request for %s", alertmanager.Name, req.URL.Path)
		},
		ModifyResponse: func(resp *http.Response) error {
			// drop Content-Length header from upstream responses, gzip middleware
			// will compress those and that could cause a mismatch
			resp.Header.Del("Content-Length")
			return nil
		},
	}
	return &proxy, nil
}

func setupRouterProxyHandlers(router *gin.Engine, alertmanager *alertmanager.Alertmanager) error {
	proxy, err := NewAlertmanagerProxy(alertmanager)
	if err != nil {
		return err
	}
	router.POST(fmt.Sprintf("%s/api/v1/silences", proxyPathPrefix(alertmanager.Name)), gin.WrapH(proxy))
	router.DELETE(fmt.Sprintf("%s/api/v1/silence/*id", proxyPathPrefix(alertmanager.Name)), gin.WrapH(proxy))
	return nil
}
<commit_msg>Don't modify path in the proxy, strip prefix in the handler<commit_after>package main

import (
	"fmt"
	"net/http"
	"net/http/httputil"
	"net/url"

	"github.com/cloudflare/unsee/internal/alertmanager"
	"github.com/cloudflare/unsee/internal/config"
	"github.com/gin-gonic/gin"

	log "github.com/sirupsen/logrus"
)

func proxyPathPrefix(name string) string {
	return fmt.Sprintf("%sproxy/alertmanager/%s", config.Config.Listen.Prefix, name)
}

// NewAlertmanagerProxy creates a proxy instance for given alertmanager instance
func NewAlertmanagerProxy(alertmanager *alertmanager.Alertmanager) (*httputil.ReverseProxy, error) {
	upstreamURL, err := url.Parse(alertmanager.URI)
	if err != nil {
		return nil, err
	}
	proxy := httputil.ReverseProxy{
		Director: func(req *http.Request) {
			req.URL.Scheme = upstreamURL.Scheme
			req.URL.Host = upstreamURL.Host
			// drop Accept-Encoding header so we always get uncompressed reponses from
			// upstream, there's a gzip middleware that's global so we don't want it
			// to gzip twice
			req.Header.Del("Accept-Encoding")
			log.Debugf("[%s] Proxy request for %s", alertmanager.Name, req.URL.Path)
		},
		ModifyResponse: func(resp *http.Response) error {
			// drop Content-Length header from upstream responses, gzip middleware
			// will compress those and that could cause a mismatch
			resp.Header.Del("Content-Length")
			return nil
		},
	}
	return &proxy, nil
}

func setupRouterProxyHandlers(router *gin.Engine, alertmanager *alertmanager.Alertmanager) error {
	proxy, err := NewAlertmanagerProxy(alertmanager)
	if err != nil {
		return err
	}
	router.POST(
		fmt.Sprintf("%s/api/v1/silences", proxyPathPrefix(alertmanager.Name)),
		gin.WrapH(http.StripPrefix(proxyPathPrefix(alertmanager.Name), proxy)))
	router.DELETE(
		fmt.Sprintf("%s/api/v1/silence/*id", proxyPathPrefix(alertmanager.Name)),
		gin.WrapH(http.StripPrefix(proxyPathPrefix(alertmanager.Name), proxy)))
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"io/ioutil"
	"net/http"
	"strings"
	"time"

	"github.com/jasonsoft/napnap"
)

type Proxy struct {
	client     *http.Client
	hopHeaders []string
}

func NewProxy() *Proxy {
	p := &Proxy{}

	p.client = &http.Client{
		Transport: &http.Transport{
			MaxIdleConnsPerHost: 20,
		},
		Timeout: time.Duration(30) * time.Second,
	}

	// Hop-by-hop headers. These are removed when sent to the backend.
	// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
	p.hopHeaders = []string{
		"Connection",
		"Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google
		"Keep-Alive",
		"Proxy-Authenticate",
		"Proxy-Authorization",
		"Te",      // canonicalized version of "TE"
		"Trailer", // not Trailers per URL above; http://www.rfc-editor.org/errata_search.php?eid=4522
		"Transfer-Encoding",
		"Upgrade",

		// custom
		"Content-Length",
		"Access-Control-Allow-Origin",
		"Access-Control-Allow-Headers",
		"Access-Control-Allow-Methods",
		"Cache-Control",
	}
	return p
}

func (p *Proxy) Invoke(c *napnap.Context, next napnap.HandlerFunc) {
	_logger.debug("begin proxy")
	var api *Api
	requestHost := c.Request.URL.Host
	requestPath := c.Request.URL.Path
	consumer := c.Get("consumer").(Consumer)

	// find api entry which match the request.
	for _, apiEntry := range _config.Apis {
		// ensure request host is match
		if apiEntry.RequestHost != "*" {
			if requestHost != apiEntry.RequestHost {
				continue
			}
		}
		// ensure request path is match
		if strings.HasPrefix(requestPath, apiEntry.RequestPath) == false {
			continue
		}
		// ensure the consumer has access permission
		if apiEntry.isAllow(consumer) == false {
			if consumer.isAuthenticated() {
				c.Status(403)
				return
			}
			c.Status(401)
			return
		}
		api = &apiEntry
		break
	}

	// none of api enties are match
	if api == nil {
		next(c) // go to notFound middleware
		return
	}

	_logger.debugf("api host: %s", api.RequestHost)
	_logger.debugf("api path: %s", api.RequestPath)

	// exchange url
	var url string
	if api.StripRequestPath {
		newPath := strings.TrimPrefix(requestPath, api.RequestPath)
		url = api.TargetURL + newPath
	} else {
		url = api.TargetURL + requestPath
	}

	rawQuery := c.Request.URL.RawQuery
	if len(rawQuery) > 0 {
		url += "?" + rawQuery
	}

	_logger.debugf("URL: %s", url)

	method := c.Request.Method
	body, _ := ioutil.ReadAll(c.Request.Body)

	outReq, err := http.NewRequest(method, url, bytes.NewReader(body))
	if err != nil {
		panic(err)
	}

	// copy the request header
	p.copyHeader(outReq.Header, c.Request.Header)
	for _, h := range p.hopHeaders {
		outReq.Header.Del(h)
	}

	// forward reuqest ip
	if _config.ForwardRequestIP {
		clientIP := c.RemoteIpAddress()
		if clientIP == "::1" {
			clientIP = "127.0.0.1"
		}
		outReq.Header.Set("X-Forwarded-For", clientIP)
	}

	// forward reuqest id
	if _config.ForwardRequestID {
		requestID := c.Get("request_id").(string)
		outReq.Header.Set("X-Request-Id", requestID)
	}

	// forward consumer information
	if len(consumer.ID) > 0 {
		outReq.Header.Set("X-Consumer-Id", consumer.ID)
		if len(consumer.App) > 0 {
			outReq.Header.Set("X-Consumer-App", consumer.App)
		}
		if len(consumer.Username) > 0 {
			outReq.Header.Set("X-Consumer-Username", consumer.Username)
		}
		if len(consumer.CustomID) > 0 {
			outReq.Header.Set("X-Consumer-Custom-Id", consumer.CustomID)
		}
		if len(consumer.CustomFields) > 0 {
			for key, field := range consumer.CustomFields {
				if len(key) > 0 && len(field) > 0 {
					outReq.Header.Set("X-Consumer-"+key, field)
				}
			}
		}
	}

	// send to target
	resp, err := p.client.Do(outReq)
	if err != nil {
		// upsteam server is down
		if strings.Contains(err.Error(), "No connection could be made") {
			c.Writer.WriteHeader(504)
			return
		}
		panic(err)
	}
	defer respClose(resp.Body)

	// copy the response header
	/*
		p.copyHeader(c.Writer.Header(), resp.Header)
		for _, h := range p.hopHeaders {
			c.Writer.Header().Del(h)
		}*/

	// write body
	body, _ = ioutil.ReadAll(resp.Body)
	c.Writer.Write(body)
}

// CopyHeaders copies http headers from source to destination, it
// does not overide, but adds multiple headers
func (p *Proxy) copyHeader(dst, src http.Header) {
	for k, vv := range src {
		for _, v := range vv {
			dst.Add(k, v)
		}
	}
}
<commit_msg>bug fix<commit_after>package main

import (
	"bytes"
	"io/ioutil"
	"net/http"
	"strings"
	"time"

	"github.com/jasonsoft/napnap"
)

type Proxy struct {
	client      *http.Client
	hopHeaders  []string
	corsHeaders []string
}

func NewProxy() *Proxy {
	p := &Proxy{}

	p.client = &http.Client{
		Transport: &http.Transport{
			MaxIdleConnsPerHost: 20,
		},
		Timeout: time.Duration(30) * time.Second,
	}

	// Hop-by-hop headers. These are removed when sent to the backend.
	// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
	p.hopHeaders = []string{
		"Connection",
		"Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google
		"Keep-Alive",
		"Proxy-Authenticate",
		"Proxy-Authorization",
		"Te",      // canonicalized version of "TE"
		"Trailer", // not Trailers per URL above; http://www.rfc-editor.org/errata_search.php?eid=4522
		"Transfer-Encoding",
		"Upgrade",

		// custom
		"Content-Length",
		"Cache-Control",
	}

	p.corsHeaders = []string{
		"Access-Control-Allow-Origin",
		"Access-Control-Allow-Headers",
		"Access-Control-Allow-Methods",
	}

	return p
}

func (p *Proxy) Invoke(c *napnap.Context, next napnap.HandlerFunc) {
	_logger.debug("begin proxy")
	var api *Api
	requestHost := c.Request.URL.Host
	requestPath := c.Request.URL.Path
	consumer := c.Get("consumer").(Consumer)

	// find api entry which match the request.
	for _, apiEntry := range _config.Apis {
		// ensure request host is match
		if apiEntry.RequestHost != "*" {
			if requestHost != apiEntry.RequestHost {
				continue
			}
		}
		// ensure request path is match
		if strings.HasPrefix(requestPath, apiEntry.RequestPath) == false {
			continue
		}
		// ensure the consumer has access permission
		if apiEntry.isAllow(consumer) == false {
			if consumer.isAuthenticated() {
				c.Status(403)
				return
			}
			c.Status(401)
			return
		}
		api = &apiEntry
		break
	}

	// none of api enties are match
	if api == nil {
		next(c) // go to notFound middleware
		return
	}

	_logger.debugf("api host: %s", api.RequestHost)
	_logger.debugf("api path: %s", api.RequestPath)

	// exchange url
	var url string
	if api.StripRequestPath {
		newPath := strings.TrimPrefix(requestPath, api.RequestPath)
		url = api.TargetURL + newPath
	} else {
		url = api.TargetURL + requestPath
	}

	rawQuery := c.Request.URL.RawQuery
	if len(rawQuery) > 0 {
		url += "?" + rawQuery
	}

	_logger.debugf("URL: %s", url)

	method := c.Request.Method
	body, _ := ioutil.ReadAll(c.Request.Body)

	outReq, err := http.NewRequest(method, url, bytes.NewReader(body))
	if err != nil {
		panic(err)
	}

	// copy the request header
	p.copyHeader(outReq.Header, c.Request.Header)
	p.removeHeader(outReq.Header)

	// forward reuqest ip
	if _config.ForwardRequestIP {
		clientIP := c.RemoteIpAddress()
		if clientIP == "::1" {
			clientIP = "127.0.0.1"
		}
		outReq.Header.Set("X-Forwarded-For", clientIP)
	}

	// forward reuqest id
	if _config.ForwardRequestID {
		requestID := c.Get("request_id").(string)
		outReq.Header.Set("X-Request-Id", requestID)
	}

	// forward consumer information
	if len(consumer.ID) > 0 {
		outReq.Header.Set("X-Consumer-Id", consumer.ID)
		if len(consumer.App) > 0 {
			outReq.Header.Set("X-Consumer-App", consumer.App)
		}
		if len(consumer.Username) > 0 {
			outReq.Header.Set("X-Consumer-Username", consumer.Username)
		}
		if len(consumer.CustomID) > 0 {
			outReq.Header.Set("X-Consumer-Custom-Id", consumer.CustomID)
		}
		if len(consumer.CustomFields) > 0 {
			for key, field := range consumer.CustomFields {
				if len(key) > 0 && len(field) > 0 {
					outReq.Header.Set("X-Consumer-"+key, field)
				}
			}
		}
	}

	// send to target
	resp, err := p.client.Do(outReq)
	if err != nil {
		// upsteam server is down
		if strings.Contains(err.Error(), "No connection could be made") {
			c.Writer.WriteHeader(504)
			return
		}
		panic(err)
	}
	defer respClose(resp.Body)

	// copy the response header
	p.removeHeader(resp.Header)
	p.copyHeader(c.Writer.Header(), resp.Header)

	// write body
	body, _ = ioutil.ReadAll(resp.Body)
	c.Writer.Write(body)
}

// CopyHeaders copies http headers from source to destination, it
// does not overide, but adds multiple headers
func (p *Proxy) copyHeader(dst, src http.Header) {
	for k, vv := range src {
		for _, v := range vv {
			dst.Add(k, v)
		}
	}
}

func (p *Proxy) removeHeader(header http.Header) {
	for _, h := range p.hopHeaders {
		header.Del(h)
	}
	if _config.Cors.Enable {
		for _, corsHeader := range p.corsHeaders {
			header.Del(corsHeader)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/Sirupsen/logrus"
	"github.com/Sirupsen/tomb"

	"net"
)

// Proxy represents the proxy in its entirity with all its links. The main
// responsibility of Proxy is to accept new client and create Links between the
// client and upstream.
type Proxy struct {
	Name     string
	Listen   string
	Upstream string

	started chan error

	tomb  tomb.Tomb
	links []*link
}

func NewProxy() *Proxy {
	proxy := &Proxy{}
	proxy.allocate()
	return proxy
}

// allocate instantiates the necessary dependencies. This is in a seperate
// method because it allows us to read Proxies from JSON and then call
// `allocate()` on them, sharing this with `NewProxy()`.
func (proxy *Proxy) allocate() {
	proxy.started = make(chan error)
}

func (proxy *Proxy) Start() error {
	go proxy.server()
	return <-proxy.started
}

// server runs the Proxy server, accepting new clients and creating Links to
// connect them to upstreams.
func (proxy *Proxy) server() {
	ln, err := net.Listen("tcp", proxy.Listen)
	if err != nil {
		proxy.started <- err
		return
	}

	proxy.started <- nil

	logrus.WithFields(logrus.Fields{
		"name":     proxy.Name,
		"proxy":    proxy.Listen,
		"upstream": proxy.Upstream,
	}).Info("Started proxy")

	quit := make(chan bool, 1)
	done := make(chan bool)

	defer close(done)

	// This channel is to kill the blocking Accept() call below by closing the
	// net.Listener.
	go func() {
		<-proxy.tomb.Dying()

		quit <- true
		err := ln.Close()
		if err != nil {
			logrus.WithFields(logrus.Fields{
				"proxy":  proxy.Name,
				"listen": proxy.Listen,
			}).Warn("Attempted to close an already closed proxy server")
		}

		<-done
		proxy.tomb.Done()
	}()

	for {
		client, err := ln.Accept()
		if err != nil {
			// This is to confirm we're being shut down in a legit way. Unfortunately,
			// Go doesn't export the error when it's closed from Close() so we have to
			// sync up with a channel here.
			//
			// See http://zhen.org/blog/graceful-shutdown-of-go-net-dot-listeners/
			select {
			case <-quit:
			default:
				logrus.WithFields(logrus.Fields{
					"proxy":  proxy.Name,
					"listen": proxy.Listen,
					"err":    err,
				}).Warn("Error while accepting client")
			}
			return
		}

		logrus.WithFields(logrus.Fields{
			"name":     proxy.Name,
			"client":   client.RemoteAddr(),
			"proxy":    proxy.Listen,
			"upstream": proxy.Upstream,
		}).Info("Accepted client")

		link := NewLink(proxy, client)
		proxy.links = append(proxy.links, link)

		if err := link.Open(); err != nil {
			logrus.WithFields(logrus.Fields{
				"name":     proxy.Name,
				"client":   client.RemoteAddr(),
				"proxy":    proxy.Listen,
				"upstream": proxy.Upstream,
			}).Error("Unable to open connection to upstream")
		}
	}
}

func (proxy *Proxy) Stop() {
	proxy.tomb.Killf("Shutting down from stop()")
	proxy.tomb.Wait() // Wait until we stop accepting new connections

	for _, link := range proxy.links {
		link.Close()
	}

	logrus.WithFields(logrus.Fields{
		"name":     proxy.Name,
		"proxy":    proxy.Listen,
		"upstream": proxy.Upstream,
	}).Info("Terminated proxy")
}
<commit_msg>Switch quit/done to use a tomb in proxy.go<commit_after>package main

import (
	"github.com/Sirupsen/logrus"
	"github.com/Sirupsen/tomb"

	"net"
)

// Proxy represents the proxy in its entirity with all its links. The main
// responsibility of Proxy is to accept new client and create Links between the
// client and upstream.
type Proxy struct {
	Name     string
	Listen   string
	Upstream string

	started chan error

	tomb  tomb.Tomb
	links []*link
}

func NewProxy() *Proxy {
	proxy := &Proxy{}
	proxy.allocate()
	return proxy
}

// allocate instantiates the necessary dependencies. This is in a seperate
// method because it allows us to read Proxies from JSON and then call
// `allocate()` on them, sharing this with `NewProxy()`.
func (proxy *Proxy) allocate() {
	proxy.started = make(chan error)
}

func (proxy *Proxy) Start() error {
	go proxy.server()
	return <-proxy.started
}

// server runs the Proxy server, accepting new clients and creating Links to
// connect them to upstreams.
func (proxy *Proxy) server() {
	ln, err := net.Listen("tcp", proxy.Listen)
	if err != nil {
		proxy.started <- err
		return
	}

	proxy.started <- nil

	logrus.WithFields(logrus.Fields{
		"name":     proxy.Name,
		"proxy":    proxy.Listen,
		"upstream": proxy.Upstream,
	}).Info("Started proxy")

	acceptTomb := tomb.Tomb{}
	defer acceptTomb.Done()

	// This channel is to kill the blocking Accept() call below by closing the
	// net.Listener.
	go func() {
		<-proxy.tomb.Dying()

		// Notify ln.Accept() that the shutdown was safe
		acceptTomb.Killf("Shutting down from stop()")
		// Unblock ln.Accept()
		err := ln.Close()
		if err != nil {
			logrus.WithFields(logrus.Fields{
				"proxy":  proxy.Name,
				"listen": proxy.Listen,
			}).Warn("Attempted to close an already closed proxy server")
		}

		// Wait for the accept loop to finish processing
		acceptTomb.Wait()
		proxy.tomb.Done()
	}()

	for {
		client, err := ln.Accept()
		if err != nil {
			// This is to confirm we're being shut down in a legit way. Unfortunately,
			// Go doesn't export the error when it's closed from Close() so we have to
			// sync up with a channel here.
			//
			// See http://zhen.org/blog/graceful-shutdown-of-go-net-dot-listeners/
			select {
			case <-acceptTomb.Dying():
			default:
				logrus.WithFields(logrus.Fields{
					"proxy":  proxy.Name,
					"listen": proxy.Listen,
					"err":    err,
				}).Warn("Error while accepting client")
			}
			return
		}

		logrus.WithFields(logrus.Fields{
			"name":     proxy.Name,
			"client":   client.RemoteAddr(),
			"proxy":    proxy.Listen,
			"upstream": proxy.Upstream,
		}).Info("Accepted client")

		link := NewLink(proxy, client)
		proxy.links = append(proxy.links, link)

		if err := link.Open(); err != nil {
			logrus.WithFields(logrus.Fields{
				"name":     proxy.Name,
				"client":   client.RemoteAddr(),
				"proxy":    proxy.Listen,
				"upstream": proxy.Upstream,
			}).Error("Unable to open connection to upstream")
		}
	}
}

func (proxy *Proxy) Stop() {
	proxy.tomb.Killf("Shutting down from stop()")
	proxy.tomb.Wait() // Wait until we stop accepting new connections

	for _, link := range proxy.links {
		link.Close()
	}

	logrus.WithFields(logrus.Fields{
		"name":     proxy.Name,
		"proxy":    proxy.Listen,
		"upstream": proxy.Upstream,
	}).Info("Terminated proxy")
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"strings"
)

type proxyHandler struct {
	// TLS is whether this is an HTTPS connection.
	TLS bool

	// connectPort is the server port that was specified in a CONNECT request.
	connectPort string
}

// lanAddress returns whether addr is in one of the LAN address ranges.
func lanAddress(addr string) bool {
	ip := net.ParseIP(addr)
	if ip4 := ip.To4(); ip4 != nil {
		switch ip4[0] {
		case 10:
			return true
		case 172:
			return ip4[1]&0xf0 == 16
		case 192:
			return ip4[1] == 168
		}
		return false
	}

	if ip[0]&0xfe == 0xfc {
		return true
	}
	if ip[0] == 0xfe && (ip[1]&0xfc) == 0x80 {
		return true
	}

	return false
}

func (h proxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	client := r.RemoteAddr
	host, _, err := net.SplitHostPort(client)
	if err == nil {
		client = host
	}
	user := client

	if !h.TLS && !lanAddress(client) {
		u := authenticate(w, r)
		if u == "" {
			return
		}
		user = u
	}

	if r.Host == "203.0.113.1" {
		http.DefaultServeMux.ServeHTTP(w, r)
		return
	}

	if r.Method == "CONNECT" {
		if !tlsReady {
			http.Error(w, "This proxy server is not configured for HTTPS.", http.StatusMethodNotAllowed)
			return
		}
		conn, err := newHijackedConn(w)
		if err != nil {
			fmt.Fprintln(conn, "HTTP/1.1 500 Internal Server Error")
			fmt.Fprintln(conn)
			fmt.Fprintln(conn, err)
			conn.Close()
			return
		}
		fmt.Fprint(conn, "HTTP/1.1 200 Connection Established\r\n\r\n")
		SSLBump(conn, r.URL.Host)
		return
	}

	if r.Header.Get("Upgrade") == "websocket" {
		h.makeWebsocketConnection(w, r)
		return
	}

	r.Header.Add("Via", r.Proto+" Redwood")
	r.Header.Add("X-Forwarded-For", client)
	r.Header.Del("Accept-Encoding")

	// Reconstruct the URL if it is incomplete (i.e. on a transparent proxy).
	if r.URL.Host == "" {
		r.URL.Host = r.Host
	}
	if r.URL.Scheme == "" {
		if h.TLS {
			r.URL.Scheme = "https"
		} else {
			r.URL.Scheme = "http"
		}
	}

	sc := scorecard{
		tally: URLRules.MatchingRules(r.URL),
	}
	sc.calculate(user)
	if sc.action == BLOCK {
		showBlockPage(w, r, &sc)
		logAccess(r, nil, sc, "", 0, false, user)
		return
	}

	changeQuery(r.URL)

	resp, err := transport.RoundTrip(r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusServiceUnavailable)
		log.Printf("error fetching %s: %s", r.URL, err)
		logAccess(r, nil, sc, "", 0, false, user)
		return
	}
	defer resp.Body.Close()

	contentType, action := checkContentType(resp)

	switch action {
	case BLOCK:
		sc.action = BLOCK
		sc.blocked = []string{"blocked-mime"}
		showBlockPage(w, r, &sc)
		logAccess(r, resp, sc, contentType, 0, false, user)
		return

	case ALLOW:
		sc.action = IGNORE
		copyResponseHeader(w, resp)
		io.Copy(w, resp.Body)
		logAccess(r, resp, sc, contentType, 0, false, user)
		return
	}

	content, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Printf("error while reading response body (URL: %s): %s", r.URL, err)
	}

	modified := false
	charset := findCharset(resp.Header.Get("Content-Type"), content)
	if strings.Contains(contentType, "html") {
		modified = pruneContent(r.URL, &content, charset)
		if modified {
			resp.Header.Set("Content-Type", "text/html; charset=utf-8")
			charset = "utf-8"
		}
	}

	scanContent(content, contentType, charset, sc.tally)
	sc.calculate(user)

	if sc.action == BLOCK {
		showBlockPage(w, r, &sc)
		logAccess(r, resp, sc, contentType, len(content), modified, user)
		return
	}

	copyResponseHeader(w, resp)
	w.Write(content)
	logAccess(r, resp, sc, contentType, len(content), modified, user)
}

// copyResponseHeader writes resp's header and status code to w.
func copyResponseHeader(w http.ResponseWriter, resp *http.Response) {
	newHeader := w.Header()
	for key, values := range resp.Header {
		for _, v := range values {
			newHeader.Add(key, v)
		}
	}

	w.WriteHeader(resp.StatusCode)
}

// A hijackedConn is a connection that has been hijacked (to fulfill a CONNECT
// request).
type hijackedConn struct {
	net.Conn
	*bufio.ReadWriter
}

func (hc *hijackedConn) Read(b []byte) (int, error) {
	return hc.ReadWriter.Read(b)
}

func (hc *hijackedConn) Write(b []byte) (n int, err error) {
	n, err = hc.ReadWriter.Write(b)
	if err != nil {
		return
	}
	err = hc.ReadWriter.Flush()
	return
}

func newHijackedConn(w http.ResponseWriter) (*hijackedConn, error) {
	hj, ok := w.(http.Hijacker)
	if !ok {
		return nil, errors.New("connection doesn't support hijacking")
	}
	conn, bufrw, err := hj.Hijack()
	if err != nil {
		return nil, err
	}
	return &hijackedConn{
		Conn:       conn,
		ReadWriter: bufrw,
	}, nil
}

// retryTransport is an http.RoundTripper that automatically retries
// failed GET and HEAD requests.
type retryTransport struct {
	http.Transport
}

var transport = retryTransport{
	http.Transport{
		TLSClientConfig: unverifiedClientConfig,
		Proxy:           http.ProxyFromEnvironment,
	},
}

func (t *retryTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
	switch req.Method {
	case "GET", "HEAD":
		for i := 0; i < 3; i++ {
			resp, err = t.Transport.RoundTrip(req)
			if err == nil {
				return resp, err
			}
		}
		return nil, err
	}

	return t.Transport.RoundTrip(req)
}

func (h proxyHandler) makeWebsocketConnection(w http.ResponseWriter, r *http.Request) {
	addr := r.Host
	if _, _, err := net.SplitHostPort(addr); err != nil {
		// There is no port specified; we need to add it.
		port := h.connectPort
		if port == "" {
			port = "80"
		}
		addr = net.JoinHostPort(addr, port)
	}
	var err error
	var serverConn net.Conn
	if h.TLS {
		serverConn, err = tls.Dial("tcp", addr, unverifiedClientConfig)
	} else {
		serverConn, err = net.Dial("tcp", addr)
	}
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	err = r.Write(serverConn)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	hj, ok := w.(http.Hijacker)
	if !ok {
		http.Error(w, "Couldn't create a websocket connection", http.StatusInternalServerError)
		return
	}
	conn, bufrw, err := hj.Hijack()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	go func() {
		io.Copy(conn, serverConn)
		conn.Close()
	}()
	io.Copy(serverConn, bufrw)
	serverConn.Close()
	return
}
<commit_msg>Treat localhost as a LAN address.<commit_after>package main

import (
	"bufio"
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"strings"
)

type proxyHandler struct {
	// TLS is whether this is an HTTPS connection.
	TLS bool

	// connectPort is the server port that was specified in a CONNECT request.
	connectPort string
}

// lanAddress returns whether addr is in one of the LAN address ranges.
func lanAddress(addr string) bool {
	ip := net.ParseIP(addr)
	if ip4 := ip.To4(); ip4 != nil {
		switch ip4[0] {
		case 10, 127:
			return true
		case 172:
			return ip4[1]&0xf0 == 16
		case 192:
			return ip4[1] == 168
		}
		return false
	}

	if ip[0]&0xfe == 0xfc {
		return true
	}
	if ip[0] == 0xfe && (ip[1]&0xfc) == 0x80 {
		return true
	}

	return false
}

func (h proxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	client := r.RemoteAddr
	host, _, err := net.SplitHostPort(client)
	if err == nil {
		client = host
	}
	user := client

	if !h.TLS && !lanAddress(client) {
		u := authenticate(w, r)
		if u == "" {
			return
		}
		user = u
	}

	if r.Host == "203.0.113.1" {
		http.DefaultServeMux.ServeHTTP(w, r)
		return
	}

	if r.Method == "CONNECT" {
		if !tlsReady {
			http.Error(w, "This proxy server is not configured for HTTPS.", http.StatusMethodNotAllowed)
			return
		}
		conn, err := newHijackedConn(w)
		if err != nil {
			fmt.Fprintln(conn, "HTTP/1.1 500 Internal Server Error")
			fmt.Fprintln(conn)
			fmt.Fprintln(conn, err)
			conn.Close()
			return
		}
		fmt.Fprint(conn, "HTTP/1.1 200 Connection Established\r\n\r\n")
		SSLBump(conn, r.URL.Host)
		return
	}

	if r.Header.Get("Upgrade") == "websocket" {
		h.makeWebsocketConnection(w, r)
		return
	}

	r.Header.Add("Via", r.Proto+" Redwood")
	r.Header.Add("X-Forwarded-For", client)
	r.Header.Del("Accept-Encoding")

	// Reconstruct the URL if it is incomplete (i.e. on a transparent proxy).
	if r.URL.Host == "" {
		r.URL.Host = r.Host
	}
	if r.URL.Scheme == "" {
		if h.TLS {
			r.URL.Scheme = "https"
		} else {
			r.URL.Scheme = "http"
		}
	}

	sc := scorecard{
		tally: URLRules.MatchingRules(r.URL),
	}
	sc.calculate(user)
	if sc.action == BLOCK {
		showBlockPage(w, r, &sc)
		logAccess(r, nil, sc, "", 0, false, user)
		return
	}

	changeQuery(r.URL)

	resp, err := transport.RoundTrip(r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusServiceUnavailable)
		log.Printf("error fetching %s: %s", r.URL, err)
		logAccess(r, nil, sc, "", 0, false, user)
		return
	}
	defer resp.Body.Close()

	contentType, action := checkContentType(resp)

	switch action {
	case BLOCK:
		sc.action = BLOCK
		sc.blocked = []string{"blocked-mime"}
		showBlockPage(w, r, &sc)
		logAccess(r, resp, sc, contentType, 0, false, user)
		return

	case ALLOW:
		sc.action = IGNORE
		copyResponseHeader(w, resp)
		io.Copy(w, resp.Body)
		logAccess(r, resp, sc, contentType, 0, false, user)
		return
	}

	content, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Printf("error while reading response body (URL: %s): %s", r.URL, err)
	}

	modified := false
	charset := findCharset(resp.Header.Get("Content-Type"), content)
	if strings.Contains(contentType, "html") {
		modified = pruneContent(r.URL, &content, charset)
		if modified {
			resp.Header.Set("Content-Type", "text/html; charset=utf-8")
			charset = "utf-8"
		}
	}

	scanContent(content, contentType, charset, sc.tally)
	sc.calculate(user)

	if sc.action == BLOCK {
		showBlockPage(w, r, &sc)
		logAccess(r, resp, sc, contentType, len(content), modified, user)
		return
	}

	copyResponseHeader(w, resp)
	w.Write(content)
	logAccess(r, resp, sc, contentType, len(content), modified, user)
}

// copyResponseHeader writes resp's header and status code to w.
func copyResponseHeader(w http.ResponseWriter, resp *http.Response) {
	newHeader := w.Header()
	for key, values := range resp.Header {
		for _, v := range values {
			newHeader.Add(key, v)
		}
	}

	w.WriteHeader(resp.StatusCode)
}

// A hijackedConn is a connection that has been hijacked (to fulfill a CONNECT
// request).
type hijackedConn struct {
	net.Conn
	*bufio.ReadWriter
}

func (hc *hijackedConn) Read(b []byte) (int, error) {
	return hc.ReadWriter.Read(b)
}

func (hc *hijackedConn) Write(b []byte) (n int, err error) {
	n, err = hc.ReadWriter.Write(b)
	if err != nil {
		return
	}
	err = hc.ReadWriter.Flush()
	return
}

func newHijackedConn(w http.ResponseWriter) (*hijackedConn, error) {
	hj, ok := w.(http.Hijacker)
	if !ok {
		return nil, errors.New("connection doesn't support hijacking")
	}
	conn, bufrw, err := hj.Hijack()
	if err != nil {
		return nil, err
	}
	return &hijackedConn{
		Conn:       conn,
		ReadWriter: bufrw,
	}, nil
}

// retryTransport is an http.RoundTripper that automatically retries
// failed GET and HEAD requests.
type retryTransport struct {
	http.Transport
}

var transport = retryTransport{
	http.Transport{
		TLSClientConfig: unverifiedClientConfig,
		Proxy:           http.ProxyFromEnvironment,
	},
}

func (t *retryTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
	switch req.Method {
	case "GET", "HEAD":
		for i := 0; i < 3; i++ {
			resp, err = t.Transport.RoundTrip(req)
			if err == nil {
				return resp, err
			}
		}
		return nil, err
	}

	return t.Transport.RoundTrip(req)
}

func (h proxyHandler) makeWebsocketConnection(w http.ResponseWriter, r *http.Request) {
	addr := r.Host
	if _, _, err := net.SplitHostPort(addr); err != nil {
		// There is no port specified; we need to add it.
		port := h.connectPort
		if port == "" {
			port = "80"
		}
		addr = net.JoinHostPort(addr, port)
	}
	var err error
	var serverConn net.Conn
	if h.TLS {
		serverConn, err = tls.Dial("tcp", addr, unverifiedClientConfig)
	} else {
		serverConn, err = net.Dial("tcp", addr)
	}
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	err = r.Write(serverConn)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	hj, ok := w.(http.Hijacker)
	if !ok {
		http.Error(w, "Couldn't create a websocket connection", http.StatusInternalServerError)
		return
	}
	conn, bufrw, err := hj.Hijack()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	go func() {
		io.Copy(conn, serverConn)
		conn.Close()
	}()
	io.Copy(serverConn, bufrw)
	serverConn.Close()
	return
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"regexp"
	"strings"
	"encoding/json"

	"github.com/valyala/fasthttp"
)

type replace interface {
	apply(data []byte) []byte
}

type regexReplace struct {
	regex       *regexp.Regexp
	replacement []byte
}

func newRegexReplace(regex, replace string) *regexReplace {
	return &regexReplace{
		regex:       regexp.MustCompile(regex),
		replacement: []byte(replace),
	}
}

func (v *regexReplace) apply(data []byte) []byte {
	return v.regex.ReplaceAll(data, v.replacement)
}

type stringReplace struct {
	needle      []byte
	replacement []byte
}

func newStringReplace(needle, replace string) *stringReplace {
	return &stringReplace{
		needle:      []byte(needle),
		replacement: []byte(replace),
	}
}

func (v *stringReplace) apply(data []byte) []byte {
	return bytes.Replace(data, v.needle, v.replacement, -1)
}

type DomainConfig struct {
	apiReplaces                []replace
	apiOfficialLongpollReplace replace
	apiLongpollReplace         replace
	siteHlsReplace             replace
}

// Constants
var (
	apiOfficialLongpollPath = []byte("/method/execute")
	apiOfficialNewsfeedPath = []byte("/method/execute.getNewsfeedSmart")
	apiLongpollPath         = []byte("/method/messages.getLongPollServer")
	videoHlsPath            = []byte("/video_hls.php")
	atPath                  = []byte("/%40")
	https                   = []byte("https")
	apiHost                 = []byte("api.vk.com")
	siteHost                = []byte("vk.com")
	siteHostRoot            = []byte(".vk.com")
)

var client = &fasthttp.Client{}
var domains = make(map[string]*DomainConfig)

func getDomainConfig(domain string) *DomainConfig {
	cfg, ok := domains[domain]
	if !ok {
		cfg = &DomainConfig{}
		cfg.apiReplaces = []replace{
			newRegexReplace(`"https:\\/\\/(pu\.vk\.com|[-a-z0-9]+\.(?:userapi\.com|vk-cdn\.net|vk\.me|vkuser(?:live|video|audio)\.(?:net|com)))\\/`, `"https:\/\/`+domain+`\/_\/$1\/`),
			// This replace mey no longer be needed, but let it be for the time being
			newStringReplace(`"https:\/\/vk\.com\/video_hls.php`, `"https:\/\/`+domain+`\/@vk.com\/video_hls.php`),
			newRegexReplace(`"https:\\/\\/vk\.com\\/(images\\/|doc-?[0-9]+_)`, `"https:\/\/`+domain+`\/_\/vk.com\/$1`),
		}
		cfg.apiOfficialLongpollReplace = newStringReplace(`"server":"api.vk.com\/newuim`, `"server":"`+domain+`\/newuim`)
		cfg.apiLongpollReplace = newStringReplace(`"server":"`, `"server":"`+domain+`\/@`)
		cfg.siteHlsReplace = newRegexReplace(`https:\/\/([-a-z0-9]+\.(?:userapi\.com|vk-cdn\.net|vk\.me|vkuser(?:live|video)\.(?:net|com)))\/`, `https://`+domain+`/_/$1/`)
		domains[domain] = cfg
	}
	return cfg
}

func reverseProxyHandler(ctx *fasthttp.RequestCtx) {
	defer func() {
		if r := recover(); r != nil {
			ctx.Logger().Printf("panic when proxying the request: %s", r)
			ctx.Response.Reset()
			ctx.SetStatusCode(500)
			ctx.SetBodyString("500 Internal Server Error")
		}
	}()

	var config *DomainConfig
	if Config.domain != "" {
		config = getDomainConfig(Config.domain)
	} else {
		config = getDomainConfig(string(ctx.Host()))
	}

	req := &ctx.Request
	if !preRequest(req) {
		ctx.Response.SetStatusCode(400)
		ctx.Response.SetBodyString("400 Bad Request")
		return
	}

	if err := client.Do(req, &ctx.Response); err != nil {
		ctx.Logger().Printf("error when proxying the request: %s", err)
		ctx.Response.Reset()
		if strings.Contains(err.Error(), "timed out") || strings.Contains(err.Error(), "timeout") {
			ctx.SetStatusCode(408)
			ctx.SetBodyString("408 Request Timeout")
		} else {
			ctx.SetStatusCode(500)
			ctx.SetBodyString("500 Internal Server Error")
		}
		trackRequest(ctx, 0)
	} else {
		postResponse(config, ctx)
	}
}

func preRequest(req *fasthttp.Request) bool {
	path := req.RequestURI()
	if bytes.HasPrefix(path, atPath) {
		slashIndex := bytes.IndexRune(path[1:], '/')
		if slashIndex == -1 {
			return false
		}
		endpoint := []byte(path[4:slashIndex+1])
		if !bytes.Equal(endpoint, siteHost) && !bytes.HasSuffix(endpoint, siteHostRoot) {
			return false
		}
		req.Header.SetHostBytes(endpoint)
		req.SetRequestURIBytes([]byte(path[1+slashIndex:]))
	} else {
		req.SetHostBytes(apiHost)
	}
	// After req.URI() call it is impossible to modify URI
	req.URI().SetSchemeBytes(https)
	req.Header.Del("Accept-Encoding")
	return true
}

func postResponse(config *DomainConfig, ctx *fasthttp.RequestCtx) {
	uri := ctx.Request.URI()
	res := &ctx.Response
	res.Header.Del("Set-Cookie")
	body := res.Body()

	if bytes.Equal(uri.Host(), siteHost) {
		if bytes.Equal(uri.Path(), videoHlsPath) {
			body = config.siteHlsReplace.apply(body)
		}
	} else {
		for _, replace := range config.apiReplaces {
			body = replace.apply(body)
		}

		// Replace longpoll server
		if bytes.Equal(uri.Path(), apiLongpollPath) {
			body = config.apiLongpollReplace.apply(body)
		} else

		// Replace longpoll server for official app
		if bytes.Equal(uri.Path(), apiOfficialLongpollPath) {
			body = config.apiOfficialLongpollReplace.apply(body)
		} else

		// Clear feed from SPAM
		if bytes.Equal(uri.Path(), apiOfficialNewsfeedPath) {
			var parsed map[string]interface{}
			if err := json.Unmarshal(body, &parsed); err == nil {
				removed := 0
				if parsed["response"] != nil {
					response := parsed["response"].(map[string]interface{})
					if response["items"] != nil {
						items := response["items"].([]interface{})
						for i := len(items) - 1; i >= 0; i-- {
							post := items[i].(map[string]interface{})
							if post["type"] == "ads" || (post["type"] == "post" && post["marked_as_ads"] != nil && post["marked_as_ads"].(float64) == 1) {
								items[i] = items[len(items)-1]
								items[len(items)-1] = nil
								items = items[:len(items)-1]
								removed++
							}
						}
						if removed > 0 {
							newItems := make([]interface{}, len(items))
							copy(newItems, items)
							response["items"] = newItems
						}
					}
				}
				if removed > 0 {
					body, err = json.Marshal(parsed)
				}
			}
		}
	}
	res.SetBody(body)

	trackRequest(ctx, len(body))
}
<commit_msg>Починены видеозаписи<commit_after>package main

import (
	"bytes"
	"regexp"
	"strings"
	"encoding/json"

	"github.com/valyala/fasthttp"
)

type replace interface {
	apply(data []byte) []byte
}

type regexReplace struct {
	regex       *regexp.Regexp
	replacement []byte
}

func newRegexReplace(regex, replace string) *regexReplace {
	return &regexReplace{
		regex:       regexp.MustCompile(regex),
		replacement: []byte(replace),
	}
}

func (v *regexReplace) apply(data []byte) []byte {
	return v.regex.ReplaceAll(data, v.replacement)
}

type stringReplace struct {
	needle      []byte
	replacement []byte
}

func newStringReplace(needle, replace string) *stringReplace {
	return &stringReplace{
		needle:      []byte(needle),
		replacement: []byte(replace),
	}
}

func (v *stringReplace) apply(data []byte) []byte {
	return bytes.Replace(data, v.needle, v.replacement, -1)
}

type DomainConfig struct {
	apiReplaces                []replace
	apiOfficialLongpollReplace replace
	apiLongpollReplace         replace
	siteHlsReplace             replace
}

// Constants
var (
	apiOfficialLongpollPath = []byte("/method/execute")
	apiOfficialNewsfeedPath = []byte("/method/execute.getNewsfeedSmart")
	apiLongpollPath         = []byte("/method/messages.getLongPollServer")
	videoHlsPath            = []byte("/video_hls.php")
	atPath                  = []byte("/%40")
	https                   = []byte("https")
	apiHost                 = []byte("api.vk.com")
	siteHost                = []byte("vk.com")
	siteHostRoot            = []byte(".vk.com")
)

var client = &fasthttp.Client{}
var domains = make(map[string]*DomainConfig)

func getDomainConfig(domain string) *DomainConfig {
	cfg, ok := domains[domain]
	if !ok {
		cfg = &DomainConfig{}
		cfg.apiReplaces = []replace{
			newRegexReplace(`"https:\\/\\/(pu\.vk\.com|[-a-z0-9]+\.(?:userapi\.com|vk-cdn\.net|vk\.me|vkuser(?:live|video|audio)\.(?:net|com)))\\/`, `"https:\/\/`+domain+`\/_\/$1\/`),
			newStringReplace(`"https:\/\/vk.com\/video_hls.php`, `"https:\/\/`+domain+`\/@vk.com\/video_hls.php`),
			newRegexReplace(`"https:\\/\\/vk\.com\\/(images\\/|doc-?[0-9]+_)`, `"https:\/\/`+domain+`\/_\/vk.com\/$1`),
		}
		cfg.apiOfficialLongpollReplace = newStringReplace(`"server":"api.vk.com\/newuim`, `"server":"`+domain+`\/newuim`)
		cfg.apiLongpollReplace = newStringReplace(`"server":"`, `"server":"`+domain+`\/@`)
		cfg.siteHlsReplace = newRegexReplace(`https:\/\/([-a-z0-9]+\.(?:userapi\.com|vk-cdn\.net|vk\.me|vkuser(?:live|video)\.(?:net|com)))\/`, `https://`+domain+`/_/$1/`)
		domains[domain] = cfg
	}
	return cfg
}

func reverseProxyHandler(ctx *fasthttp.RequestCtx) {
	defer func() {
		if r := recover(); r != nil {
			ctx.Logger().Printf("panic when proxying the request: %s", r)
			ctx.Response.Reset()
			ctx.SetStatusCode(500)
			ctx.SetBodyString("500 Internal Server Error")
		}
	}()

	var config *DomainConfig
	if Config.domain != "" {
		config = getDomainConfig(Config.domain)
	} else {
		config = getDomainConfig(string(ctx.Host()))
	}

	req := &ctx.Request
	if !preRequest(req) {
		ctx.Response.SetStatusCode(400)
		ctx.Response.SetBodyString("400 Bad Request")
		return
	}

	if err := client.Do(req, &ctx.Response); err != nil {
		ctx.Logger().Printf("error when proxying the request: %s", err)
		ctx.Response.Reset()
		if strings.Contains(err.Error(), "timed out") || strings.Contains(err.Error(), "timeout") {
			ctx.SetStatusCode(408)
			ctx.SetBodyString("408 Request Timeout")
		} else {
			ctx.SetStatusCode(500)
			ctx.SetBodyString("500 Internal Server Error")
		}
		trackRequest(ctx, 0)
	} else {
		postResponse(config, ctx)
	}
}

func preRequest(req *fasthttp.Request) bool {
	path := req.RequestURI()
	if bytes.HasPrefix(path, atPath) {
		slashIndex := bytes.IndexRune(path[1:], '/')
		if slashIndex == -1 {
			return false
		}
		endpoint := []byte(path[4:slashIndex+1])
		if !bytes.Equal(endpoint, siteHost) && !bytes.HasSuffix(endpoint, siteHostRoot) {
			return false
		}
		req.Header.SetHostBytes(endpoint)
		req.SetRequestURIBytes([]byte(path[1+slashIndex:]))
	} else {
		req.SetHostBytes(apiHost)
	}
	// After req.URI() call it is impossible to modify URI
	req.URI().SetSchemeBytes(https)
	req.Header.Del("Accept-Encoding")
	return true
}

func postResponse(config *DomainConfig, ctx *fasthttp.RequestCtx) {
	uri := ctx.Request.URI()
	res := &ctx.Response
	res.Header.Del("Set-Cookie")
	body := res.Body()

	if bytes.Equal(uri.Host(), siteHost) {
		if bytes.Equal(uri.Path(), videoHlsPath) {
			body = config.siteHlsReplace.apply(body)
		}
	} else {
		for _, replace := range config.apiReplaces {
			body = replace.apply(body)
		}

		// Replace longpoll server
		if bytes.Equal(uri.Path(), apiLongpollPath) {
			body = config.apiLongpollReplace.apply(body)
		} else

		// Replace longpoll server for official app
		if bytes.Equal(uri.Path(), apiOfficialLongpollPath) {
			body = config.apiOfficialLongpollReplace.apply(body)
		} else

		// Clear feed from SPAM
		if bytes.Equal(uri.Path(), apiOfficialNewsfeedPath) {
			var parsed map[string]interface{}
			if err := json.Unmarshal(body, &parsed); err == nil {
				removed := 0
				if parsed["response"] != nil {
					response := parsed["response"].(map[string]interface{})
					if response["items"] != nil {
						items := response["items"].([]interface{})
						for i := len(items) - 1; i >= 0; i-- {
							post := items[i].(map[string]interface{})
							if post["type"] == "ads" || (post["type"] == "post" && post["marked_as_ads"] != nil && post["marked_as_ads"].(float64) == 1) {
								items[i] = items[len(items)-1]
								items[len(items)-1] = nil
								items = items[:len(items)-1]
								removed++
							}
						}
						if removed > 0 {
							newItems := make([]interface{}, len(items))
							copy(newItems, items)
							response["items"] = newItems
						}
					}
				}
				if removed > 0 {
					body, err = json.Marshal(parsed)
				}
			}
		}
	}
	res.SetBody(body)

	trackRequest(ctx, len(body))
}
<|endoftext|>


<commit_before>/*
The 'proxy' service implemented here provides both a proxy for outbound
service requests and a multiplexer for inbound requests. The diagram below
illustrates one way proxies interoperate.

      Proxy A                   Proxy B
      +-----------+             +-----------+
    22250         |     +---->22250 ---------------+
      |           |     |       |           |      |
 +-->3306 --------------+       |           |      |
 +-->4369 --------------+       |           |      |
 |    |           |             |           |      |
 |    +-----------+             +-----------+      |
 |                                                 |
 +----zensvc                    mysql/3306 <-------+
                                rabbitmq/4369 <----+

Proxy A exposes MySQL and RabbitMQ ports, 3306 and 4369 respectively, to its
zensvc. When zensvc connects to those ports Proxy A forwards the resulting
traffic to the appropriate remote services via the TCPMux port exposed by
Proxy B.

Start the service from the command line by typing

proxy [OPTIONS] SERVICE_ID

  -certfile="": path to public certificate file (defaults to compiled in public cert)
  -endpoint="127.0.0.1:4979": serviced endpoint address
  -keyfile="": path to private key file (defaults to compiled in private key)
  -mux=true: enable port multiplexing
  -muxport=22250: multiplexing port to use
  -tls=true: enable TLS

To terminate the proxy service connect to it via port 4321 and it will exit.
The netcat (nc) command is particularly useful for this:

    nc 127.0.0.1 4321
*/
package serviced

import (
	"crypto/tls"
	"fmt"
	"github.com/zenoss/glog"
	"io"
	"net"
	"strconv"
	"strings"
)

type Proxy struct {
	name         string          // Name of the remote service
	addresses    []string        // Public IP:Port of the remote service
	tcpMuxPort   uint16          // the port to use for TCP Muxing, 0 is disabled
	useTLS       bool            // use encryption over mux port
	closing      chan chan error // internal shutdown signal
	newAddresses chan []string   // a stream of updates to the addresses
	listener     net.Listener    // handle on the listening socket
}

// NewProxy create a new Proxy object. It starts listening on the proxy port asynchronously.
func NewProxy(name string, tcpMuxPort uint16, useTLS bool, listener net.Listener) (proxy *Proxy, err error) {
	if len(name) == 0 {
		return nil, fmt.Errorf("proxy: name can not be empty")
	}
	proxy = &Proxy{
		name:       name,
		addresses:  make([]string, 0),
		tcpMuxPort: tcpMuxPort,
		useTLS:     useTLS,
		listener:   listener,
	}
	proxy.newAddresses = make(chan []string, 2)
	go proxy.listenAndProxy()
	return proxy, nil
}

// Name() returns the application name associated with the proxy
func (p *Proxy) Name() string {
	return p.name
}

// String() pretty prints the Proxy struct.
func (p *Proxy) String() string {
	return fmt.Sprintf("Proxy[%s; %s]=>%v", p.name, p.listener, p.addresses)
}

// TCPMuxPort() returns the tcp port use for muxing, 0 if not used.
func (p *Proxy) TCPMuxPort() uint16 {
	return p.tcpMuxPort
}

// UseTLS() returns true if TLS is used during tcp muxing.
func (p *Proxy) UseTLS() bool {
	return p.useTLS
}

// Set a new Destination Address set for the proxy
func (p *Proxy) SetNewAddresses(addresses []string) {
	p.newAddresses <- addresses
}

// Close() terminates the proxy; it can not be restarted.
func (p *Proxy) Close() error {
	p.listener.Close()
	errc := make(chan error)
	p.closing <- errc
	return <-errc
}

// listenAndProxy listens, locally, on the proxy's specified Port. For each
// incoming connection a goroutine running the proxy method is created.
func (p *Proxy) listenAndProxy() {

	connections := make(chan net.Conn)
	go func(lsocket net.Listener, conns chan net.Conn) {
		for {
			conn, err := lsocket.Accept()
			if err != nil {
				glog.Fatal("Error (net.Accept): ", err)
			}
			conns <- conn
		}
	}(p.listener, connections)

	i := 0
	for {
		select {
		case conn := <-connections:
			if len(p.addresses) == 0 {
				glog.Warningf("No remote services available for proxying %s", p)
				conn.Close()
				continue
			}
			i += 1
			// round robin connections to list of addresses
			glog.V(1).Infof("choosing address from %v", p.addresses)
			go p.proxy(conn, p.addresses[i%len(p.addresses)])
		case p.addresses = <-p.newAddresses:
		case errc := <-p.closing:
			p.listener.Close()
			errc <- nil
			return
		}
	}
}

// proxy takes an established local connection, Dials the remote address specified
// by the Proxy structure and then copies data to and from the resulting pair
// of endpoints.
func (p *Proxy) proxy(local net.Conn, address string) {
	remoteAddr := address
	// NOTE: here we are relying on the initial remoteAddr to have the
	//       publicly exposed port for the target service. If TCPMux is
	//       in play that port will be replaced with the TCPMux port, so
	//       we grab it here in order to be able to create a proper Zen-Service
	//       header later.
	remotePort, err := strconv.Atoi(strings.Split(remoteAddr, ":")[1])
	if err != nil {
		glog.Error("Error (strconv.Atoi): ", err)
	}

	if p.tcpMuxPort > 0 {
		remoteAddr = fmt.Sprintf("%s:%d", strings.Split(remoteAddr, ":")[0], p.tcpMuxPort)
	}

	var remote net.Conn

	glog.Infof("Dialing hostAgent:%v to proxy %v<->%v<->%v",
		remoteAddr, local.LocalAddr(), local.RemoteAddr(), address)
	if p.useTLS && (p.tcpMuxPort > 0) { // Only do TLS if connecting to a TCPMux
		config := tls.Config{InsecureSkipVerify: true}
		remote, err = tls.Dial("tcp4", remoteAddr, &config)
	} else {
		remote, err = net.Dial("tcp4", remoteAddr)
	}
	if err != nil {
		glog.Error("Error (net.Dial): ", err)
		return
	}

	if p.tcpMuxPort > 0 {
		io.WriteString(remote, fmt.Sprintf("Zen-Service: %s/%d\n\n", p.name, remotePort))
	}

	glog.Infof("Using   hostAgent:%v to proxy %v<->%v<->%v<->%v",
		remote.RemoteAddr(), local.LocalAddr(), local.RemoteAddr(), remote.LocalAddr(), address)
	go func() {
		defer local.Close()
		defer remote.Close()
		io.Copy(local, remote)
	}()
	go func() {
		defer local.Close()
		defer remote.Close()
		io.Copy(remote, local)
	}()
}
<commit_msg>added logging for proxy close()<commit_after>/*
The 'proxy' service implemented here provides both a proxy for outbound
service requests and a multiplexer for inbound requests. The diagram below
illustrates one way proxies interoperate.

      Proxy A                   Proxy B
      +-----------+             +-----------+
    22250         |     +---->22250 ---------------+
      |           |     |       |           |      |
 +-->3306 --------------+       |           |      |
 +-->4369 --------------+       |           |      |
 |    |           |             |           |      |
 |    +-----------+             +-----------+      |
 |                                                 |
 +----zensvc                    mysql/3306 <-------+
                                rabbitmq/4369 <----+

Proxy A exposes MySQL and RabbitMQ ports, 3306 and 4369 respectively, to its
zensvc. When zensvc connects to those ports Proxy A forwards the resulting
traffic to the appropriate remote services via the TCPMux port exposed by
Proxy B.

Start the service from the command line by typing

proxy [OPTIONS] SERVICE_ID

  -certfile="": path to public certificate file (defaults to compiled in public cert)
  -endpoint="127.0.0.1:4979": serviced endpoint address
  -keyfile="": path to private key file (defaults to compiled in private key)
  -mux=true: enable port multiplexing
  -muxport=22250: multiplexing port to use
  -tls=true: enable TLS

To terminate the proxy service connect to it via port 4321 and it will exit.
The netcat (nc) command is particularly useful for this:

    nc 127.0.0.1 4321
*/
package serviced

import (
	"crypto/tls"
	"fmt"
	"github.com/zenoss/glog"
	"io"
	"net"
	"strconv"
	"strings"
)

type Proxy struct {
	name         string          // Name of the remote service
	addresses    []string        // Public IP:Port of the remote service
	tcpMuxPort   uint16          // the port to use for TCP Muxing, 0 is disabled
	useTLS       bool            // use encryption over mux port
	closing      chan chan error // internal shutdown signal
	newAddresses chan []string   // a stream of updates to the addresses
	listener     net.Listener    // handle on the listening socket
}

// NewProxy create a new Proxy object. It starts listening on the proxy port asynchronously.
func NewProxy(name string, tcpMuxPort uint16, useTLS bool, listener net.Listener) (proxy *Proxy, err error) {
	if len(name) == 0 {
		return nil, fmt.Errorf("proxy: name can not be empty")
	}
	proxy = &Proxy{
		name:       name,
		addresses:  make([]string, 0),
		tcpMuxPort: tcpMuxPort,
		useTLS:     useTLS,
		listener:   listener,
	}
	proxy.newAddresses = make(chan []string, 2)
	go proxy.listenAndProxy()
	return proxy, nil
}

// Name() returns the application name associated with the proxy
func (p *Proxy) Name() string {
	return p.name
}

// String() pretty prints the Proxy struct.
func (p *Proxy) String() string {
	return fmt.Sprintf("Proxy[%s; %s]=>%v", p.name, p.listener, p.addresses)
}

// TCPMuxPort() returns the tcp port use for muxing, 0 if not used.
func (p *Proxy) TCPMuxPort() uint16 {
	return p.tcpMuxPort
}

// UseTLS() returns true if TLS is used during tcp muxing.
func (p *Proxy) UseTLS() bool {
	return p.useTLS
}

// Set a new Destination Address set for the proxy
func (p *Proxy) SetNewAddresses(addresses []string) {
	p.newAddresses <- addresses
}

// Close() terminates the proxy; it can not be restarted.
func (p *Proxy) Close() error {
	p.listener.Close()
	errc := make(chan error)
	p.closing <- errc
	return <-errc
}

// listenAndProxy listens, locally, on the proxy's specified Port. For each
// incoming connection a goroutine running the proxy method is created.
func (p *Proxy) listenAndProxy() {

	connections := make(chan net.Conn)
	go func(lsocket net.Listener, conns chan net.Conn) {
		for {
			conn, err := lsocket.Accept()
			if err != nil {
				glog.Fatal("Error (net.Accept): ", err)
			}
			conns <- conn
		}
	}(p.listener, connections)

	i := 0
	for {
		select {
		case conn := <-connections:
			if len(p.addresses) == 0 {
				glog.Warningf("No remote services available for proxying %s", p)
				conn.Close()
				continue
			}
			i += 1
			// round robin connections to list of addresses
			glog.V(1).Infof("choosing address from %v", p.addresses)
			go p.proxy(conn, p.addresses[i%len(p.addresses)])
		case p.addresses = <-p.newAddresses:
		case errc := <-p.closing:
			p.listener.Close()
			errc <- nil
			return
		}
	}
}

// proxy takes an established local connection, Dials the remote address specified
// by the Proxy structure and then copies data to and from the resulting pair
// of endpoints.
func (p *Proxy) proxy(local net.Conn, address string) {
	remoteAddr := address
	// NOTE: here we are relying on the initial remoteAddr to have the
	//       publicly exposed port for the target service. If TCPMux is
	//       in play that port will be replaced with the TCPMux port, so
	//       we grab it here in order to be able to create a proper Zen-Service
	//       header later.
	remotePort, err := strconv.Atoi(strings.Split(remoteAddr, ":")[1])
	if err != nil {
		glog.Error("Error (strconv.Atoi): ", err)
	}

	if p.tcpMuxPort > 0 {
		remoteAddr = fmt.Sprintf("%s:%d", strings.Split(remoteAddr, ":")[0], p.tcpMuxPort)
	}

	var remote net.Conn

	glog.Infof("Dialing hostAgent:%v to proxy %v<->%v<->%v",
		remoteAddr, local.LocalAddr(), local.RemoteAddr(), address)
	if p.useTLS && (p.tcpMuxPort > 0) { // Only do TLS if connecting to a TCPMux
		config := tls.Config{InsecureSkipVerify: true}
		remote, err = tls.Dial("tcp4", remoteAddr, &config)
	} else {
		remote, err = net.Dial("tcp4", remoteAddr)
	}
	if err != nil {
		glog.Error("Error (net.Dial): ", err)
		return
	}

	if p.tcpMuxPort > 0 {
		io.WriteString(remote, fmt.Sprintf("Zen-Service: %s/%d\n\n", p.name, remotePort))
	}

	glog.Infof("Using   hostAgent:%v to proxy %v<->%v<->%v<->%v",
		remote.RemoteAddr(), local.LocalAddr(), local.RemoteAddr(), remote.LocalAddr(), address)
	go func(address string) {
		defer local.Close()
		defer remote.Close()
		io.Copy(local, remote)
		glog.Infof("Closing hostAgent:%v to proxy %v<->%v<->%v<->%v",
			remote.RemoteAddr(), local.LocalAddr(), local.RemoteAddr(), remote.LocalAddr(), address)
	}(address)
	go func(address string) {
		defer local.Close()
		defer remote.Close()
		io.Copy(remote, local)
		glog.Infof("closing hostAgent:%v to proxy %v<->%v<->%v<->%v",
			remote.RemoteAddr(), local.LocalAddr(), local.RemoteAddr(), remote.LocalAddr(), address)
	}(address)
}
<|endoftext|>


<commit_before>// HTTP proxy that strips headers from the backend response
package main

import (
	"flag"
	"fmt"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"strings"
)

var (
	target *url.URL // Proxied backend
)

// StripTransport strips headers from backend response
type StripTransport struct {
	http.RoundTripper
	headers map[string]bool
}

// RoundTrip handles request, then removes headers from the response
func (lt *StripTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	resp, err := lt.RoundTripper.RoundTrip(req)
	if err != nil {
		return nil, err
	}

	for hdr := range resp.Header {
		if lt.headers[strings.ToUpper(hdr)] {
			resp.Header.Del(hdr)
		}
	}

	return resp, nil
}

// parseHeaders creates a dictionary where keys are headers in uppercase
func parseHeaders(items []string) map[string]bool {
	out := make(map[string]bool)
	for _, item := range items {
		out[strings.ToUpper(item)] = true
	}
	return out
}

func main() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "usage: %s PROXY_URL [HEADER [HEADER ...]]\n", os.Args[0])
		flag.PrintDefaults()
	}
	port := flag.Int("port", 8080, "proxy port (8080)")
	flag.Parse()

	if flag.NArg() < 1 {
		fmt.Fprintf(os.Stderr, "error: wrong number of arguments\n")
		os.Exit(1)
	}

	target, err := url.Parse(flag.Arg(0))
	if err != nil {
		fmt.Fprintf(os.Stderr, "error: bad proxy url - %s\n", err)
		os.Exit(1)
	}

	proxy := &httputil.ReverseProxy{
		Transport: &StripTransport{
			http.DefaultTransport,
			parseHeaders(flag.Args()[1:]), // First argument is proxy URL
		},
		Director: func(r *http.Request) {
			r.URL.Scheme = target.Scheme
			r.URL.Host = target.Host
		},
	}
	http.Handle("/", proxy)
	http.ListenAndServe(fmt.Sprintf(":%d", *port), nil)
}
<commit_msg>No need for global target<commit_after>// HTTP proxy that strips headers from the backend response
package main

import (
	"flag"
	"fmt"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"strings"
)

// StripTransport strips headers from backend response
type StripTransport struct {
	http.RoundTripper
	headers map[string]bool
}

// RoundTrip handles request, then removes headers from the response
func (lt *StripTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	resp, err := lt.RoundTripper.RoundTrip(req)
	if err != nil {
		return nil, err
	}

	for hdr := range resp.Header {
		if lt.headers[strings.ToUpper(hdr)] {
			resp.Header.Del(hdr)
		}
	}

	return resp, nil
}

// parseHeaders creates a dictionary where keys are headers in uppercase
func parseHeaders(items []string) map[string]bool {
	out := make(map[string]bool)
	for _, item := range items {
		out[strings.ToUpper(item)] = true
	}
	return out
}

func main() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "usage: %s PROXY_URL [HEADER [HEADER ...]]\n", os.Args[0])
		flag.PrintDefaults()
	}
	port := flag.Int("port", 8080, "proxy port (8080)")
	flag.Parse()

	if flag.NArg() < 1 {
		fmt.Fprintf(os.Stderr, "error: wrong number of arguments\n")
		os.Exit(1)
	}

	target, err := url.Parse(flag.Arg(0))
	if err != nil {
		fmt.Fprintf(os.Stderr, "error: bad proxy url - %s\n", err)
		os.Exit(1)
	}

	proxy := &httputil.ReverseProxy{
		Transport: &StripTransport{
			http.DefaultTransport,
			parseHeaders(flag.Args()[1:]), // First argument is proxy URL
		},
		Director: func(r *http.Request) {
			r.URL.Scheme = target.Scheme
			r.URL.Host = target.Host
		},
	}
	http.Handle("/", proxy)
	http.ListenAndServe(fmt.Sprintf(":%d", *port), nil)
}
<|endoftext|>


<commit_before>//Package ps implements the PostScript backend for libcairo rendering.
//
//EPS
//
//EPS files must contain only one page.
//
//Note that libcairo does not include the device independent preview.
//
//The Encapsulated PostScript Specfication† describes how to embed EPS
//files into PostScript files
//
//† https://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
//
//Fonts
//
//The PostScript surface natively supports Type 1 and TrueType fonts.
//All other font types (including OpenType/PS) are converted to Type 1.
//
//Fonts are always subsetted and embedded.
//
//Fallback images
//
//Cairo will ensure that the PostScript output looks the same as an image
//surface for the same set of operations.
//When cairo drawing operations are performed that cannot be represented
//natively in PostScript, the drawing is rasterized and embedded in the output.
//
//The rasterization of unsupported operations is limited to the smallest
//rectangle, or set of rectangles, required to draw the unsupported operations.
//
//Fallback images are the main cause of large file sizes
//and slow printing times.
//Fallback images have a comment containing the size and location
//of the fallback.
//	> grep 'Fallback' output.ps
//	output.ps:% Fallback Image: x=100, y=100, w=299, h=50 res=300dpi size=783750
//	output.ps:% Fallback Image: x=100, y=150, w=350, h=250 res=300dpi size=4560834
//	output.ps:% Fallback Image: x=150, y=400, w=299, h=50 res=300dpi size=783750
//
//Supported Features
//
//The following tables lists all features natively supported by the PostScript
//surface.
//
//	FEATURE                          NOTES
//	Paint/Fill/Stroke/ShowGlyphs     depending on pattern
//	Fonts                            some may be converted to Type 1
//	Opaque colors
//	Images
//	Linear gradients                 level 3 only
//	Radial gradients                 level 3 and when one circle is inside
//	                                 the other and extent is ExtendNone or
//	                                 ExtendPad only.
//	PushGroup/CreateSimilar
//	OpSource
//	OpOver
//
//
//Requirements
//
//Libcairo must be compiled with
//	CAIRO_HAS_PS_SURFACE
//in addition to the requirements of cairo.
package ps

//#cgo pkg-config: cairo
//#include <stdlib.h>
//#include <cairo/cairo.h>
//#include <cairo/cairo-ps.h>
import "C"

import (
	"io"
	"unsafe"

	"github.com/jimmyfrasche/cairo"
)

//Surface is a PostScript surface.
//
//Surface implements cairo.PagedVectorSurface
type Surface struct {
	cairo.XtensionPagedVectorSurface
	eps bool
}

func news(s *C.cairo_surface_t, eps bool) (Surface, error) {
	S := Surface{
		XtensionPagedVectorSurface: cairo.NewXtensionPagedVectorSurface(s),
		eps: eps,
	}
	return S, S.Err()
}

func cNew(s *C.cairo_surface_t) (cairo.Surface, error) {
	eps := C.cairo_ps_surface_get_eps(s) == 1
	//Note that if the surface was created with a Writer we have no way of
	//getting it here but that's okay as long as the original reference lives on.
	S, err := news(s, eps)
	return S, err
}

func init() {
	cairo.XtensionRegisterRawToSurface(cairo.SurfaceTypePS, cNew)
}

func cfgSurf(ps *C.cairo_surface_t, eps bool, header, setup Comments) {
	var ceps C.cairo_bool_t
	if eps {
		ceps = 1
	}
	C.cairo_ps_surface_set_eps(ps, ceps)

	for _, c := range header {
		s := C.CString(c.String())
		C.cairo_ps_surface_dsc_comment(ps, s)
		C.free(unsafe.Pointer(s))
	}

	if len(setup) > 0 {
		C.cairo_ps_surface_dsc_begin_setup(ps)

		for _, c := range setup {
			s := C.CString(c.String())
			C.cairo_ps_surface_dsc_comment(ps, s)
			C.free(unsafe.Pointer(s))
		}
	}

	//ensure calls to AddComments apply to page even if no drawing has been performed.
	C.cairo_ps_surface_dsc_begin_page_setup(ps)
}

func errChk(header, setup Comments) (err error) {
	if err = header.Err(); err != nil {
		return
	}
	return setup.Err()
}

//New creates a new PostScript of the specified size.
//
//W is the Writer the PostScript is written to.
//Width and height are in the unit of a typographical point
//(1 point = 1/72 inch).
//Eps specifies whether this will be Encapsulated PostScript.
//Header is any DSC comments to apply to the header section.
//Setup is any DSC comment to apply to the setup section.
//
//Originally cairo_ps_surface_create_for_stream
//and cairo_ps_surface_set_eps and cairo_ps_surface_dsc_comment
//and cairo_ps_surface_dsc_begin_setup and
//cairo_ps_surface_dsc_begin_page_setup.
func New(w io.Writer, width, height float64, eps bool, header, setup Comments) (S Surface, err error) {
	if err = errChk(header, setup); err != nil {
		return
	}

	wp := cairo.XtensionWrapWriter(w)
	ps := C.cairo_ps_surface_create_for_stream(cairo.XtensionCairoWriteFuncT, wp, C.double(width), C.double(height))

	cfgSurf(ps, eps, header, setup)

	S, err = news(ps, eps)

	S.XtensionRegisterWriter(wp)

	return S, err
}

//RestrictTo restricts the generated PostScript to the specified level.
//The default is Level3.
//
//This method should only be called before any drawing operations have been
//performed on this surface.
//
//Originally cairo_ps_surface_restrict_to_level.
func (s Surface) RestrictTo(level level) error {
	C.cairo_ps_surface_restrict_to_level(s.XtensionRaw(), level.c())
	return s.Err()
}

//SetSize changes the size of the PostScript surface for the current
//and subsequent pages.
//
//This method should only be called before any drawing operations have
//been performed on the current page.
//
//Originally cairo_ps_surface_set_size.
func (s Surface) SetSize(width, height float64) error {
	C.cairo_ps_surface_set_size(s.XtensionRaw(), C.double(width), C.double(height))
	return s.Err()
}

//EPS reports whether s is Encapsulated PostScript.
//
//Originally cairo_ps_surface_get_eps.
func (s Surface) EPS() bool {
	return s.eps
}

//AddComments adds comments to the PageSetup sections.
//
//Originally cairo_ps_surface_dsc_comment.
func (s Surface) AddComments(comments Comments) (err error) {
	if err = comments.Err(); err != nil {
		return
	}

	for _, c := range comments {
		str := C.CString(c.String())
		C.cairo_ps_surface_dsc_comment(s.XtensionRaw(), str)
		C.free(unsafe.Pointer(str))
	}

	return s.Err()
}

//AddComment is shorthand for adding a single comment.
func (s Surface) AddComment(key, value string) error {
	return s.AddComments(Comments{Comment(key, value)})
}

//AddCommentf is shorthand for adding a single formatted comment.
func (s Surface) AddCommentf(key, value string, vars ...interface{}) error {
	return s.AddComments(Comments{Comment(key, value)})
}

//BUG(jmf) need to override copy/show page to run begin_page_setup beforehand?
<commit_msg>bug not necessary<commit_after>//Package ps implements the PostScript backend for libcairo rendering.
//
//EPS
//
//EPS files must contain only one page.
//
//Note that libcairo does not include the device independent preview.
//
//The Encapsulated PostScript Specfication† describes how to embed EPS
//files into PostScript files
//
//† https://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
//
//Fonts
//
//The PostScript surface natively supports Type 1 and TrueType fonts.
//All other font types (including OpenType/PS) are converted to Type 1.
//
//Fonts are always subsetted and embedded.
//
//Fallback images
//
//Cairo will ensure that the PostScript output looks the same as an image
//surface for the same set of operations.
//When cairo drawing operations are performed that cannot be represented
//natively in PostScript, the drawing is rasterized and embedded in the output.
//
//The rasterization of unsupported operations is limited to the smallest
//rectangle, or set of rectangles, required to draw the unsupported operations.
//
//Fallback images are the main cause of large file sizes
//and slow printing times.
//Fallback images have a comment containing the size and location
//of the fallback.
//	> grep 'Fallback' output.ps
//	output.ps:% Fallback Image: x=100, y=100, w=299, h=50 res=300dpi size=783750
//	output.ps:% Fallback Image: x=100, y=150, w=350, h=250 res=300dpi size=4560834
//	output.ps:% Fallback Image: x=150, y=400, w=299, h=50 res=300dpi size=783750
//
//Supported Features
//
//The following tables lists all features natively supported by the PostScript
//surface.
//
//	FEATURE                          NOTES
//	Paint/Fill/Stroke/ShowGlyphs     depending on pattern
//	Fonts                            some may be converted to Type 1
//	Opaque colors
//	Images
//	Linear gradients                 level 3 only
//	Radial gradients                 level 3 and when one circle is inside
//	                                 the other and extent is ExtendNone or
//	                                 ExtendPad only.
//	PushGroup/CreateSimilar
//	OpSource
//	OpOver
//
//
//Requirements
//
//Libcairo must be compiled with
//	CAIRO_HAS_PS_SURFACE
//in addition to the requirements of cairo.
package ps

//#cgo pkg-config: cairo
//#include <stdlib.h>
//#include <cairo/cairo.h>
//#include <cairo/cairo-ps.h>
import "C"

import (
	"io"
	"unsafe"

	"github.com/jimmyfrasche/cairo"
)

//Surface is a PostScript surface.
//
//Surface implements cairo.PagedVectorSurface
type Surface struct {
	cairo.XtensionPagedVectorSurface
	eps bool
}

func news(s *C.cairo_surface_t, eps bool) (Surface, error) {
	S := Surface{
		XtensionPagedVectorSurface: cairo.NewXtensionPagedVectorSurface(s),
		eps: eps,
	}
	return S, S.Err()
}

func cNew(s *C.cairo_surface_t) (cairo.Surface, error) {
	eps := C.cairo_ps_surface_get_eps(s) == 1
	//Note that if the surface was created with a Writer we have no way of
	//getting it here but that's okay as long as the original reference lives on.
	S, err := news(s, eps)
	return S, err
}

func init() {
	cairo.XtensionRegisterRawToSurface(cairo.SurfaceTypePS, cNew)
}

func cfgSurf(ps *C.cairo_surface_t, eps bool, header, setup Comments) {
	var ceps C.cairo_bool_t
	if eps {
		ceps = 1
	}
	C.cairo_ps_surface_set_eps(ps, ceps)

	for _, c := range header {
		s := C.CString(c.String())
		C.cairo_ps_surface_dsc_comment(ps, s)
		C.free(unsafe.Pointer(s))
	}

	if len(setup) > 0 {
		C.cairo_ps_surface_dsc_begin_setup(ps)

		for _, c := range setup {
			s := C.CString(c.String())
			C.cairo_ps_surface_dsc_comment(ps, s)
			C.free(unsafe.Pointer(s))
		}
	}

	//ensure calls to AddComments apply to page even if no drawing has been performed.
	C.cairo_ps_surface_dsc_begin_page_setup(ps)
}

func errChk(header, setup Comments) (err error) {
	if err = header.Err(); err != nil {
		return
	}
	return setup.Err()
}

//New creates a new PostScript of the specified size.
//
//W is the Writer the PostScript is written to.
//Width and height are in the unit of a typographical point
//(1 point = 1/72 inch).
//Eps specifies whether this will be Encapsulated PostScript.
//Header is any DSC comments to apply to the header section.
//Setup is any DSC comment to apply to the setup section.
//
//Originally cairo_ps_surface_create_for_stream
//and cairo_ps_surface_set_eps and cairo_ps_surface_dsc_comment
//and cairo_ps_surface_dsc_begin_setup and
//cairo_ps_surface_dsc_begin_page_setup.
func New(w io.Writer, width, height float64, eps bool, header, setup Comments) (S Surface, err error) {
	if err = errChk(header, setup); err != nil {
		return
	}

	wp := cairo.XtensionWrapWriter(w)
	ps := C.cairo_ps_surface_create_for_stream(cairo.XtensionCairoWriteFuncT, wp, C.double(width), C.double(height))

	cfgSurf(ps, eps, header, setup)

	S, err = news(ps, eps)

	S.XtensionRegisterWriter(wp)

	return S, err
}

//RestrictTo restricts the generated PostScript to the specified level.
//The default is Level3.
//
//This method should only be called before any drawing operations have been
//performed on this surface.
//
//Originally cairo_ps_surface_restrict_to_level.
func (s Surface) RestrictTo(level level) error {
	C.cairo_ps_surface_restrict_to_level(s.XtensionRaw(), level.c())
	return s.Err()
}

//SetSize changes the size of the PostScript surface for the current
//and subsequent pages.
//
//This method should only be called before any drawing operations have
//been performed on the current page.
//
//Originally cairo_ps_surface_set_size.
func (s Surface) SetSize(width, height float64) error {
	C.cairo_ps_surface_set_size(s.XtensionRaw(), C.double(width), C.double(height))
	return s.Err()
}

//EPS reports whether s is Encapsulated PostScript.
//
//Originally cairo_ps_surface_get_eps.
func (s Surface) EPS() bool {
	return s.eps
}

//AddComments adds comments to the PageSetup sections.
//
//Originally cairo_ps_surface_dsc_comment.
func (s Surface) AddComments(comments Comments) (err error) {
	if err = comments.Err(); err != nil {
		return
	}

	for _, c := range comments {
		str := C.CString(c.String())
		C.cairo_ps_surface_dsc_comment(s.XtensionRaw(), str)
		C.free(unsafe.Pointer(str))
	}

	return s.Err()
}

//AddComment is shorthand for adding a single comment.
func (s Surface) AddComment(key, value string) error {
	return s.AddComments(Comments{Comment(key, value)})
}

//AddCommentf is shorthand for adding a single formatted comment.
func (s Surface) AddCommentf(key, value string, vars ...interface{}) error {
	return s.AddComments(Comments{Comment(key, value)})
}
<|endoftext|>