<commit_before>package gurgling

import (
    "net/http"
    "sync"
    "io"
    . "github.com/levythu/gurgling/definition"
    "github.com/levythu/gurgling/encoding"
    fp "path/filepath"
    MIME "mime"
    "os"
    "encoding/json"
)

// Depended by: gurgling/midwares/analyzer
type Response interface {
    // Quick send with code 200. While done, any other operation except Write is not allowed anymore.
    // However, due to the framework of net/http, the response will not be closed until
    // the function returns. So it is suggested to return immediately.
    Send(string) error

    // Set headers.
    Set(string, string) error

    // Get the value in the headers. If nothing, returns "".
    Get(string) string

    // Write data to response body. It allows any corresponding operation.
    Write([]byte) (int, error)

    // Send files without any extra headers except contenttype and encrypt.
    // if contenttype is "", it will be inferred from file extension.
    // if encoder is nil, no encoder is used
    SendFileEx(string, string, encoding.Encoder, int) error
    // Shorthand for SendFileEx, infer mime, using gzip and return 200.
    SendFile(string) error

    // While done, any other operation except Write is not allowed anymore.
    Status(string, int) error

    // While done, any other operation except Write is not allowed anymore.
    SendCode(int) error

    // While done, any other operation except Write is not allowed anymore.
    Redirect(string) error
    RedirectEX(string, int) error

    // While done, any other operation except Write is not allowed anymore.
    JSON(interface{}) error
    JSONEx(interface{}, int) error

    // get the Original resonse, only use it for advanced purpose
    R() http.ResponseWriter

    // extra use for midwares. Most of time the value is a function.
    F() map[string]Tout
}
func NewResponse(w http.ResponseWriter) Response {
    return &OriResponse{
        r: w,
        haveSent: false,
        lock: &sync.Mutex{},
        f: make(map[string]Tout),
    }
}

type OriResponse struct {
    // the Original resonse, only use it for advanced purpose
    r http.ResponseWriter
    // to guarantee the send action is only triggered once
    haveSent bool
    f map[string]Tout

    lock *sync.Mutex
}

func (this *OriResponse)Send(content string) error {
    return this.Status(content, 200)
}

func (this *OriResponse)SendCode(code int) error {
    this.lock.Lock()
    defer this.lock.Unlock()

    if (this.haveSent) {
        return RES_HEAD_ALREADY_SENT
    }
    this.r.WriteHeader(code)
    this.haveSent=true

    return nil
}

func (this *OriResponse)Status(content string, code int) error {
    this.lock.Lock()
    defer this.lock.Unlock()

    if (this.haveSent) {
        return RES_HEAD_ALREADY_SENT
    }
    this.r.Header().Set("Content-Type", "text/plain; charset=utf-8")
    this.r.WriteHeader(code)
    this.haveSent=true
    _, err:=io.WriteString(this.r, content)

    return err
}

func (this *OriResponse)Set(key string, val string) error {
    this.lock.Lock()
    defer this.lock.Unlock()

    if (this.haveSent) {
        return RES_HEAD_ALREADY_SENT
    }
    this.r.Header().Set(key, val)
    return nil
}

func (this *OriResponse)Get(key string) string {
    return this.r.Header().Get(key)
}

func (this *OriResponse)Write(content []byte) (int, error) {
    return this.r.Write(content)
}

func (this *OriResponse)R() http.ResponseWriter {
    return this.r
}

func (this *OriResponse)F() map[string]Tout {
    return this.f
}

func (this *OriResponse)RedirectEX(newAddr string, code int) error {
    this.lock.Lock()
    defer this.lock.Unlock()
    if (this.haveSent) {
        return RES_HEAD_ALREADY_SENT
    }

    this.haveSent=true

    this.r.Header().Set(LOCATION_HEADER, newAddr)
    this.r.WriteHeader(code) // moved temporarily

    return nil
}
func (this *OriResponse)Redirect(newAddr string) error {
    return this.RedirectEX(newAddr, 307)
}
func (this *OriResponse)JSONEx(obj interface{}, code int) error {
    this.lock.Lock()
    defer this.lock.Unlock()
    if (this.haveSent) {
        return RES_HEAD_ALREADY_SENT
    }

    var result, err=json.Marshal(obj)
    if err!=nil {
        return JSON_STRINGIFY_ERROR
    }

    this.haveSent=true
    this.r.WriteHeader(code)
    _, err=this.r.Write(result)
    return err
}
func (this *OriResponse)JSON(obj interface{}) error {
    return this.JSONEx(obj, 200)
}

func (this *OriResponse)SendFile(filepath string) error {
    return this.SendFileEx(filepath, "", encoding.GZipEncoder, 200)
}

func (this *OriResponse)SendFileEx(filepath string, mime string, encoder encoding.Encoder, httpCode int) error {
    this.lock.Lock()
    defer this.lock.Unlock()
    if (this.haveSent) {
        return RES_HEAD_ALREADY_SENT
    }

    // prepare file
    var fileHandler, fileErr=os.Open(filepath)
    if fileErr!=nil {
        return SENDFILE_FILEPATH_ERROR
    }

    // init mime
    if mime=="" {
        // infer the content type
        mime=MIME.TypeByExtension(fp.Ext(filepath))
        if mime=="" {
            mime=DEFAULT_CONTENT_TYPE
        }
    }
    // init encoder
    if encoder==nil {
        encoder=encoding.NOEncoder
    }
    var desWriter=encoder.WriterWrapper(this.r)
    if desWriter==nil {
        // fail to create. return a safe encoder or error? Now returns error.
        return SENDFILE_ENCODER_NOT_READY
    }

    this.haveSent=true

    // set headers
    this.r.Header().Set(CONTENT_TYPE_KEY, mime)
    if encoder.ContentEncoding()!="" {
        this.r.Header().Set(CONTENT_ENCODING, encoder.ContentEncoding())
    }

    this.r.WriteHeader(httpCode)

    // trnsfer file
    _, copyError:=io.Copy(desWriter, fileHandler)
    desWriter.Close()
    fileHandler.Close()
    // No need to close request writer. There's no such an interface.
    if copyError!=nil {
        // Attetez: seems not so accurate
        return SENT_BUT_ABORT
    }

    return nil
}
<commit_msg>add automatic header set for JSON/JSONEx<commit_after>package gurgling

import (
    "net/http"
    "sync"
    "io"
    . "github.com/levythu/gurgling/definition"
    "github.com/levythu/gurgling/encoding"
    fp "path/filepath"
    MIME "mime"
    "os"
    "encoding/json"
)

// Depended by: gurgling/midwares/analyzer
type Response interface {
    // Quick send with code 200. While done, any other operation except Write is not allowed anymore.
    // However, due to the framework of net/http, the response will not be closed until
    // the function returns. So it is suggested to return immediately.
    Send(string) error

    // Set headers.
    Set(string, string) error

    // Get the value in the headers. If nothing, returns "".
    Get(string) string

    // Write data to response body. It allows any corresponding operation.
    Write([]byte) (int, error)

    // Send files without any extra headers except contenttype and encrypt.
    // if contenttype is "", it will be inferred from file extension.
    // if encoder is nil, no encoder is used
    SendFileEx(string, string, encoding.Encoder, int) error
    // Shorthand for SendFileEx, infer mime, using gzip and return 200.
    SendFile(string) error

    // While done, any other operation except Write is not allowed anymore.
    Status(string, int) error

    // While done, any other operation except Write is not allowed anymore.
    SendCode(int) error

    // While done, any other operation except Write is not allowed anymore.
    Redirect(string) error
    RedirectEX(string, int) error

    // While done, any other operation except Write is not allowed anymore.
    JSON(interface{}) error
    JSONEx(interface{}, int) error

    // get the Original resonse, only use it for advanced purpose
    R() http.ResponseWriter

    // extra use for midwares. Most of time the value is a function.
    F() map[string]Tout
}
func NewResponse(w http.ResponseWriter) Response {
    return &OriResponse{
        r: w,
        haveSent: false,
        lock: &sync.Mutex{},
        f: make(map[string]Tout),
    }
}

type OriResponse struct {
    // the Original resonse, only use it for advanced purpose
    r http.ResponseWriter
    // to guarantee the send action is only triggered once
    haveSent bool
    f map[string]Tout

    lock *sync.Mutex
}

func (this *OriResponse)Send(content string) error {
    return this.Status(content, 200)
}

func (this *OriResponse)SendCode(code int) error {
    this.lock.Lock()
    defer this.lock.Unlock()

    if (this.haveSent) {
        return RES_HEAD_ALREADY_SENT
    }
    this.r.WriteHeader(code)
    this.haveSent=true

    return nil
}

func (this *OriResponse)Status(content string, code int) error {
    this.lock.Lock()
    defer this.lock.Unlock()

    if (this.haveSent) {
        return RES_HEAD_ALREADY_SENT
    }
    this.r.Header().Set("Content-Type", "text/plain; charset=utf-8")
    this.r.WriteHeader(code)
    this.haveSent=true
    _, err:=io.WriteString(this.r, content)

    return err
}

func (this *OriResponse)Set(key string, val string) error {
    this.lock.Lock()
    defer this.lock.Unlock()

    if (this.haveSent) {
        return RES_HEAD_ALREADY_SENT
    }
    this.r.Header().Set(key, val)
    return nil
}

func (this *OriResponse)Get(key string) string {
    return this.r.Header().Get(key)
}

func (this *OriResponse)Write(content []byte) (int, error) {
    return this.r.Write(content)
}

func (this *OriResponse)R() http.ResponseWriter {
    return this.r
}

func (this *OriResponse)F() map[string]Tout {
    return this.f
}

func (this *OriResponse)RedirectEX(newAddr string, code int) error {
    this.lock.Lock()
    defer this.lock.Unlock()
    if (this.haveSent) {
        return RES_HEAD_ALREADY_SENT
    }

    this.haveSent=true

    this.r.Header().Set(LOCATION_HEADER, newAddr)
    this.r.WriteHeader(code) // moved temporarily

    return nil
}
func (this *OriResponse)Redirect(newAddr string) error {
    return this.RedirectEX(newAddr, 307)
}
func (this *OriResponse)JSONEx(obj interface{}, code int) error {
    this.lock.Lock()
    defer this.lock.Unlock()
    if (this.haveSent) {
        return RES_HEAD_ALREADY_SENT
    }

    var result, err=json.Marshal(obj)
    if err!=nil {
        return JSON_STRINGIFY_ERROR
    }

    this.haveSent=true
    this.r.Header().Set("Content-Type", "application/json; charset=utf-8")
    this.r.WriteHeader(code)
    _, err=this.r.Write(result)
    return err
}
func (this *OriResponse)JSON(obj interface{}) error {
    return this.JSONEx(obj, 200)
}

func (this *OriResponse)SendFile(filepath string) error {
    return this.SendFileEx(filepath, "", encoding.GZipEncoder, 200)
}

func (this *OriResponse)SendFileEx(filepath string, mime string, encoder encoding.Encoder, httpCode int) error {
    this.lock.Lock()
    defer this.lock.Unlock()
    if (this.haveSent) {
        return RES_HEAD_ALREADY_SENT
    }

    // prepare file
    var fileHandler, fileErr=os.Open(filepath)
    if fileErr!=nil {
        return SENDFILE_FILEPATH_ERROR
    }

    // init mime
    if mime=="" {
        // infer the content type
        mime=MIME.TypeByExtension(fp.Ext(filepath))
        if mime=="" {
            mime=DEFAULT_CONTENT_TYPE
        }
    }
    // init encoder
    if encoder==nil {
        encoder=encoding.NOEncoder
    }
    var desWriter=encoder.WriterWrapper(this.r)
    if desWriter==nil {
        // fail to create. return a safe encoder or error? Now returns error.
        return SENDFILE_ENCODER_NOT_READY
    }

    this.haveSent=true

    // set headers
    this.r.Header().Set(CONTENT_TYPE_KEY, mime)
    if encoder.ContentEncoding()!="" {
        this.r.Header().Set(CONTENT_ENCODING, encoder.ContentEncoding())
    }

    this.r.WriteHeader(httpCode)

    // trnsfer file
    _, copyError:=io.Copy(desWriter, fileHandler)
    desWriter.Close()
    fileHandler.Close()
    // No need to close request writer. There's no such an interface.
    if copyError!=nil {
        // Attetez: seems not so accurate
        return SENT_BUT_ABORT
    }

    return nil
}
<|endoftext|>


<commit_before>package allyourbase

import "testing"

// Note: ConvertToBase should accept leading zeroes in its input,
// but never emit leading zeroes in its output.
// Exception: If the value of the output is zero, represent it with a single zero.
var testCases = []struct {
	description  string
	inputBase    uint64
	outputBase   uint64
	inputDigits  []uint64
	outputDigits []uint64
	error        error
}{
	{
		description:  "single bit one to decimal",
		inputBase:    2,
		inputDigits:  []uint64{1},
		outputBase:   10,
		outputDigits: []uint64{1},
	},
	{
		description:  "binary to single decimal",
		inputBase:    2,
		inputDigits:  []uint64{1, 0, 1},
		outputBase:   10,
		outputDigits: []uint64{5},
	},
	{
		description:  "single decimal to binary",
		inputBase:    10,
		inputDigits:  []uint64{5},
		outputBase:   2,
		outputDigits: []uint64{1, 0, 1},
	},
	{
		description:  "binary to multiple decimal",
		inputBase:    2,
		inputDigits:  []uint64{1, 0, 1, 0, 1, 0},
		outputBase:   10,
		outputDigits: []uint64{4, 2},
	},
	{
		description:  "decimal to binary",
		inputBase:    10,
		inputDigits:  []uint64{4, 2},
		outputBase:   2,
		outputDigits: []uint64{1, 0, 1, 0, 1, 0},
	},
	{
		description:  "trinary to hexadecimal",
		inputBase:    3,
		inputDigits:  []uint64{1, 1, 2, 0},
		outputBase:   16,
		outputDigits: []uint64{2, 10},
	},
	{
		description:  "hexadecimal to trinary",
		inputBase:    16,
		inputDigits:  []uint64{2, 10},
		outputBase:   3,
		outputDigits: []uint64{1, 1, 2, 0},
	},
	{
		description:  "15-bit integer",
		inputBase:    97,
		inputDigits:  []uint64{3, 46, 60},
		outputBase:   73,
		outputDigits: []uint64{6, 10, 45},
	},
	{
		description:  "empty list",
		inputBase:    2,
		inputDigits:  []uint64{},
		outputBase:   10,
		outputDigits: []uint64{0},
		error:        nil,
	},
	{
		description:  "single zero",
		inputBase:    10,
		inputDigits:  []uint64{0},
		outputBase:   2,
		outputDigits: []uint64{0},
	},
	{
		description:  "multiple zeros",
		inputBase:    10,
		inputDigits:  []uint64{0, 0, 0},
		outputBase:   2,
		outputDigits: []uint64{0},
	},
	{
		description:  "leading zeros",
		inputBase:    7,
		inputDigits:  []uint64{0, 6, 0},
		outputBase:   10,
		outputDigits: []uint64{4, 2},
	},
	{
		description:  "invalid positive digit",
		inputBase:    2,
		inputDigits:  []uint64{1, 2, 1, 0, 1, 0},
		outputBase:   10,
		outputDigits: nil,
		error:        ErrInvalidDigit,
	},
	{
		description:  "first base is one",
		inputBase:    1,
		inputDigits:  []uint64{},
		outputBase:   10,
		outputDigits: nil,
		error:        ErrInvalidBase,
	},
	{
		description:  "second base is one",
		inputBase:    2,
		inputDigits:  []uint64{1, 0, 1, 0, 1, 0},
		outputBase:   1,
		outputDigits: nil,
		error:        ErrInvalidBase,
	},
	{
		description:  "first base is zero",
		inputBase:    0,
		inputDigits:  []uint64{},
		outputBase:   10,
		outputDigits: nil,
		error:        ErrInvalidBase,
	},
	{
		description:  "second base is zero",
		inputBase:    10,
		inputDigits:  []uint64{7},
		outputBase:   0,
		outputDigits: nil,
		error:        ErrInvalidBase,
	},
}

func digitsEqual(a, b []uint64) bool {
	if len(a) != len(b) {
		return false
	}

	for i := 0; i < len(a); i++ {
		if a[i] != b[i] {
			return false
		}
	}

	return true
}

func TestConvertToBase(t *testing.T) {
	for _, c := range testCases {
		output, err := ConvertToBase(c.inputBase, c.inputDigits, c.outputBase)
		if err != c.error {
			t.Fatalf(`FAIL: %s
	Expected error: %v
	Got: %v`, c.description, c.error, err)
		}

		if !digitsEqual(c.outputDigits, output) {
			t.Fatalf(`FAIL: %s
    Input base: %d
    Input digits: %v
    Output base: %d
    Expected output digits: %v
    Got: %v`, c.description, c.inputBase, c.inputDigits, c.outputBase, c.outputDigits, output)
		} else {
			t.Logf("PASS: %s", c.description)
		}
	}
}

const targetTestVersion = 1

func TestTestVersion(t *testing.T) {
	if testVersion != targetTestVersion {
		t.Errorf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
	}
}
<commit_msg>brought exercise all-your-base into test consistency, see #470<commit_after>package allyourbase

import "testing"

const targetTestVersion = 1

// Note: ConvertToBase should accept leading zeroes in its input,
// but never emit leading zeroes in its output.
// Exception: If the value of the output is zero, represent it with a single zero.
var testCases = []struct {
	description  string
	inputBase    uint64
	outputBase   uint64
	inputDigits  []uint64
	outputDigits []uint64
	error        error
}{
	{
		description:  "single bit one to decimal",
		inputBase:    2,
		inputDigits:  []uint64{1},
		outputBase:   10,
		outputDigits: []uint64{1},
	},
	{
		description:  "binary to single decimal",
		inputBase:    2,
		inputDigits:  []uint64{1, 0, 1},
		outputBase:   10,
		outputDigits: []uint64{5},
	},
	{
		description:  "single decimal to binary",
		inputBase:    10,
		inputDigits:  []uint64{5},
		outputBase:   2,
		outputDigits: []uint64{1, 0, 1},
	},
	{
		description:  "binary to multiple decimal",
		inputBase:    2,
		inputDigits:  []uint64{1, 0, 1, 0, 1, 0},
		outputBase:   10,
		outputDigits: []uint64{4, 2},
	},
	{
		description:  "decimal to binary",
		inputBase:    10,
		inputDigits:  []uint64{4, 2},
		outputBase:   2,
		outputDigits: []uint64{1, 0, 1, 0, 1, 0},
	},
	{
		description:  "trinary to hexadecimal",
		inputBase:    3,
		inputDigits:  []uint64{1, 1, 2, 0},
		outputBase:   16,
		outputDigits: []uint64{2, 10},
	},
	{
		description:  "hexadecimal to trinary",
		inputBase:    16,
		inputDigits:  []uint64{2, 10},
		outputBase:   3,
		outputDigits: []uint64{1, 1, 2, 0},
	},
	{
		description:  "15-bit integer",
		inputBase:    97,
		inputDigits:  []uint64{3, 46, 60},
		outputBase:   73,
		outputDigits: []uint64{6, 10, 45},
	},
	{
		description:  "empty list",
		inputBase:    2,
		inputDigits:  []uint64{},
		outputBase:   10,
		outputDigits: []uint64{0},
		error:        nil,
	},
	{
		description:  "single zero",
		inputBase:    10,
		inputDigits:  []uint64{0},
		outputBase:   2,
		outputDigits: []uint64{0},
	},
	{
		description:  "multiple zeros",
		inputBase:    10,
		inputDigits:  []uint64{0, 0, 0},
		outputBase:   2,
		outputDigits: []uint64{0},
	},
	{
		description:  "leading zeros",
		inputBase:    7,
		inputDigits:  []uint64{0, 6, 0},
		outputBase:   10,
		outputDigits: []uint64{4, 2},
	},
	{
		description:  "invalid positive digit",
		inputBase:    2,
		inputDigits:  []uint64{1, 2, 1, 0, 1, 0},
		outputBase:   10,
		outputDigits: nil,
		error:        ErrInvalidDigit,
	},
	{
		description:  "first base is one",
		inputBase:    1,
		inputDigits:  []uint64{},
		outputBase:   10,
		outputDigits: nil,
		error:        ErrInvalidBase,
	},
	{
		description:  "second base is one",
		inputBase:    2,
		inputDigits:  []uint64{1, 0, 1, 0, 1, 0},
		outputBase:   1,
		outputDigits: nil,
		error:        ErrInvalidBase,
	},
	{
		description:  "first base is zero",
		inputBase:    0,
		inputDigits:  []uint64{},
		outputBase:   10,
		outputDigits: nil,
		error:        ErrInvalidBase,
	},
	{
		description:  "second base is zero",
		inputBase:    10,
		inputDigits:  []uint64{7},
		outputBase:   0,
		outputDigits: nil,
		error:        ErrInvalidBase,
	},
}

func digitsEqual(a, b []uint64) bool {
	if len(a) != len(b) {
		return false
	}

	for i := 0; i < len(a); i++ {
		if a[i] != b[i] {
			return false
		}
	}

	return true
}

func TestTestVersion(t *testing.T) {
	if testVersion != targetTestVersion {
		t.Fatalf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
	}
}

func TestConvertToBase(t *testing.T) {
	for _, c := range testCases {
		output, err := ConvertToBase(c.inputBase, c.inputDigits, c.outputBase)
		if err != c.error {
			t.Fatalf(`FAIL: %s
	Expected error: %v
	Got: %v`, c.description, c.error, err)
		}

		if !digitsEqual(c.outputDigits, output) {
			t.Fatalf(`FAIL: %s
    Input base: %d
    Input digits: %v
    Output base: %d
    Expected output digits: %v
    Got: %v`, c.description, c.inputBase, c.inputDigits, c.outputBase, c.outputDigits, output)
		} else {
			t.Logf("PASS: %s", c.description)
		}
	}
}
<|endoftext|>


<commit_before><commit_msg>UPD: masterapi: pass testing.T to the fixture factory.<commit_after><|endoftext|>


<commit_before>package log
<commit_msg>remove unused log_test<commit_after><|endoftext|>


<commit_before>package flotilla

import (
	"testing"

	"github.com/simulatedsimian/assert"
	"github.com/simulatedsimian/flotilla/dock"
)

type RequiredModules struct {
	M1 Matrix
	M2 Matrix
	Touch
	Number
	Dial
}

func TestAquire(t *testing.T) {
	mustPanic := assert.MustPanic
	assert := assert.Make(t)

	modules := RequiredModules{}

	assert(structMembersToInterfaces(&modules)).Equal(
		[]interface{}{&Matrix{}, &Matrix{}, &Touch{}, &Number{}, &Dial{}},
	)

	mustPanic(t, func(t *testing.T) {
		structMembersToInterfaces(modules)
	})

	mustPanic(t, func(t *testing.T) {
		structMembersToInterfaces(0)
	})
}

func TestConnectDisconnect(t *testing.T) {
	assert := assert.Make(t)

	e1, e2 := dock.NewPipe().Endpoints()

	client, _ := ConnectToDocksRaw(e1)
	sim := dock.NewSimulator(e2)

	var modules RequiredModules

	client.AquireModules(&modules)
	assert(modules.M1.Connected()).Equal(false)

	sim.Connect(dock.Matrix, 3)
	client.processEvent()
	assert(modules.M1.Connected()).Equal(true)

	sim.Disconnect(3)
	client.processEvent()
	assert(modules.M1.Connected()).Equal(false)

	e1.Close()
}
<commit_msg>more tests<commit_after>package flotilla

import (
	"testing"

	"github.com/simulatedsimian/assert"
	"github.com/simulatedsimian/flotilla/dock"
)

type RequiredModules struct {
	M1 Matrix
	M2 Matrix
	Touch
	Number
	Dial
}

func TestAquire(t *testing.T) {
	mustPanic := assert.MustPanic
	assert := assert.Make(t)

	modules := RequiredModules{}

	assert(structMembersToInterfaces(&modules)).Equal(
		[]interface{}{&Matrix{}, &Matrix{}, &Touch{}, &Number{}, &Dial{}},
	)

	mustPanic(t, func(t *testing.T) {
		structMembersToInterfaces(modules)
	})

	mustPanic(t, func(t *testing.T) {
		structMembersToInterfaces(0)
	})
}

func TestConnectDisconnect(t *testing.T) {
	assert := assert.Make(t)

	e1, e2 := dock.NewPipe().Endpoints()

	client, _ := ConnectToDocksRaw(e1)
	sim := dock.NewSimulator(e2)

	var modules RequiredModules

	client.AquireModules(&modules)
	assert(modules.M1.Connected()).Equal(false)

	sim.Connect(dock.Matrix, 3)
	client.processEvent()
	assert(modules.M1.Connected()).Equal(true)

	sim.Disconnect(3)
	client.processEvent()
	assert(modules.M1.Connected()).Equal(false)

	e1.Close()

	assert(client.processEvent()).HasError()
}
<|endoftext|>


<commit_before>package dnsr

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/miekg/dns"
)

// DNS Resolution configuration.
var (
	Timeout             = 2000 * time.Millisecond
	TypicalResponseTime = 100 * time.Millisecond
	MaxRecursion        = 10
	MaxNameservers      = 4
	MaxIPs              = 2
)

// Resolver errors.
var (
	NXDOMAIN = fmt.Errorf("NXDOMAIN")

	ErrMaxRecursion = fmt.Errorf("maximum recursion depth reached: %d", MaxRecursion)
	ErrMaxIPs       = fmt.Errorf("maximum name server IPs queried: %d", MaxIPs)
	ErrNoARecords   = fmt.Errorf("no A records found for name server")
	ErrNoResponse   = fmt.Errorf("no responses received")
	ErrTimeout      = fmt.Errorf("timeout expired") // TODO: Timeouter interface? e.g. func (e) Timeout() bool { return true }
)

// Resolver implements a primitive, non-recursive, caching DNS resolver.
type Resolver struct {
	cache   *cache
	expire  bool
	timeout time.Duration
}

// New initializes a Resolver with the specified cache size.
func New(capacity int) *Resolver {
	return NewWithTimeout(capacity, Timeout)
}

// NewWithTimeout initializes a Resolver with the specified cache size and resolution timeout.
func NewWithTimeout(capacity int, timeout time.Duration) *Resolver {
	r := &Resolver{
		cache:   newCache(capacity, false),
		expire:  false,
		timeout: timeout,
	}
	return r
}

// NewExpiring initializes an expiring Resolver with the specified cache size.
func NewExpiring(capacity int) *Resolver {
	return NewExpiringWithTimeout(capacity, Timeout)
}

// NewExpiringWithTimeout initializes an expiring Resolved with the specified cache size and resolution timeout.
func NewExpiringWithTimeout(capacity int, timeout time.Duration) *Resolver {
	r := &Resolver{
		cache:   newCache(capacity, true),
		expire:  true,
		timeout: timeout,
	}
	return r
}

// Resolve calls ResolveErr to find DNS records of type qtype for the domain qname.
// For nonexistent domains (NXDOMAIN), it will return an empty, non-nil slice.
func (r *Resolver) Resolve(qname, qtype string) RRs {
	rrs, err := r.ResolveErr(qname, qtype)
	if err == NXDOMAIN {
		return emptyRRs
	}
	if err != nil {
		return nil
	}
	return rrs
}

// ResolveErr finds DNS records of type qtype for the domain qname.
// For nonexistent domains, it will return an NXDOMAIN error.
// Specify an empty string in qtype to receive any DNS records found
// (currently A, AAAA, NS, CNAME, SOA, and TXT).
func (r *Resolver) ResolveErr(qname, qtype string) (RRs, error) {
	ctx, cancel := context.WithTimeout(context.Background(), r.timeout)
	defer cancel()
	return r.resolve(ctx, toLowerFQDN(qname), qtype, 0)
}

// ResolveCtx finds DNS records of type qtype for the domain qname using
// the supplied context. Requests may time out earlier if timeout is
// shorter than a deadline set in ctx.
// For nonexistent domains, it will return an NXDOMAIN error.
// Specify an empty string in qtype to receive any DNS records found
// (currently A, AAAA, NS, CNAME, SOA, and TXT).
func (r *Resolver) ResolveCtx(ctx context.Context, qname, qtype string) (RRs, error) {
	ctx, cancel := context.WithTimeout(ctx, r.timeout)
	defer cancel()
	return r.resolve(ctx, toLowerFQDN(qname), qtype, 0)
}

func (r *Resolver) resolve(ctx context.Context, qname, qtype string, depth int) (RRs, error) {
	if depth++; depth > MaxRecursion {
		logMaxRecursion(qname, qtype, depth)
		return nil, ErrMaxRecursion
	}
	rrs, err := r.cacheGet(ctx, qname, qtype)
	if err != nil {
		return nil, err
	}
	if len(rrs) > 0 {
		return rrs, nil
	}
	logResolveStart(qname, qtype, depth)
	start := time.Now()
	rrs, err = r.iterateParents(ctx, qname, qtype, depth)
	logResolveEnd(qname, qtype, rrs, depth, start, err)
	return rrs, err
}

func (r *Resolver) iterateParents(ctx context.Context, qname, qtype string, depth int) (RRs, error) {
	chanRRs := make(chan RRs, MaxNameservers)
	chanErrs := make(chan error, MaxNameservers)
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()
	for pname, ok := qname, true; ok; pname, ok = parent(pname) {
		// If we’re looking for [foo.com,NS], then move on to the parent ([com,NS])
		if pname == qname && qtype == "NS" {
			continue
		}

		// Only query TLDs against the root nameservers
		if pname == "." && dns.CountLabel(qname) != 1 {
			// fmt.Fprintf(os.Stderr, "Warning: non-TLD query at root: dig +norecurse %s %s\n", qname, qtype)
			return nil, nil
		}

		// Get nameservers
		nrrs, err := r.resolve(ctx, pname, "NS", depth)
		if err == NXDOMAIN || err == ErrTimeout || err == context.DeadlineExceeded {
			return nil, err
		}
		if err != nil {
			continue
		}

		// Check cache for specific queries
		if len(nrrs) > 0 && qtype != "" {
			rrs, err := r.cacheGet(ctx, qname, qtype)
			if err != nil {
				return nil, err
			}
			if len(rrs) > 0 {
				return rrs, nil
			}
		}

		// Query all nameservers in parallel
		count := 0
		for i := 0; i < len(nrrs) && count < MaxNameservers; i++ {
			nrr := nrrs[i]
			if nrr.Type != "NS" {
				continue
			}

			go func(host string) {
				rrs, err := r.exchange(ctx, host, qname, qtype, depth)
				if err != nil {
					chanErrs <- err
				} else {
					chanRRs <- rrs
				}
			}(nrr.Value)

			count++
		}

		// Wait for answer, error, or cancellation
		for ; count > 0; count-- {
			select {
			case <-ctx.Done():
				return nil, ctx.Err()
			case rrs := <-chanRRs:
				for _, nrr := range nrrs {
					if nrr.Name == qname {
						rrs = append(rrs, nrr)
					}
				}
				cancel() // stop any other work here before recursing
				return r.resolveCNAMEs(ctx, qname, qtype, rrs, depth)
			case err = <-chanErrs:
				if err == NXDOMAIN {
					return nil, err
				}
			}

		}

		// NS queries naturally recurse, so stop further iteration
		if qtype == "NS" {
			return nil, err
		}
	}

	return nil, ErrNoResponse
}

func (r *Resolver) exchange(ctx context.Context, host, qname, qtype string, depth int) (RRs, error) {
	count := 0
	arrs, err := r.resolve(ctx, host, "A", depth)
	if err != nil {
		return nil, err
	}
	for _, arr := range arrs {
		// FIXME: support AAAA records?
		if arr.Type != "A" {
			continue
		}

		// Never query more than MaxIPs for any nameserver
		if count++; count > MaxIPs {
			return nil, ErrMaxIPs
		}

		rrs, err := r.exchangeIP(ctx, host, arr.Value, qname, qtype, depth)
		if err == nil || err == NXDOMAIN || err == ErrTimeout {
			return rrs, err
		}

		if ctx.Err() != nil {
			return nil, ctx.Err()
		}
	}

	return nil, ErrNoARecords
}

func (r *Resolver) exchangeIP(ctx context.Context, host, ip, qname, qtype string, depth int) (RRs, error) {
	dtype := dns.StringToType[qtype]
	if dtype == 0 {
		dtype = dns.TypeA
	}
	var qmsg dns.Msg
	qmsg.SetQuestion(qname, dtype)
	qmsg.MsgHdr.RecursionDesired = false

	// Synchronously query this DNS server
	start := time.Now()
	timeout := r.timeout // belt and suspenders, since ctx has a deadline from ResolveErr
	if dl, ok := ctx.Deadline(); ok {
		if start.After(dl.Add(-TypicalResponseTime)) { // bail if we can't finish in time (start is too close to deadline)
			return nil, ErrTimeout
		}
		timeout = dl.Sub(start)
	}

	client := &dns.Client{Timeout: timeout} // client must finish within remaining timeout
	rmsg, dur, err := client.Exchange(&qmsg, ip+":53")
	select {
	case <-ctx.Done(): // Finished too late
		logCancellation(host, &qmsg, rmsg, depth, dur, timeout)
		return nil, ctx.Err()
	default:
		logExchange(host, &qmsg, rmsg, depth, dur, timeout, err) // Log hostname instead of IP
	}
	if err != nil {
		return nil, err
	}

	// FIXME: cache NXDOMAIN responses responsibly
	if rmsg.Rcode == dns.RcodeNameError {
		var hasSOA bool
		if qtype == "NS" {
			for _, drr := range rmsg.Ns {
				rr, ok := convertRR(drr, r.expire)
				if !ok {
					continue
				}
				if rr.Type == "SOA" {
					hasSOA = true
					break
				}
			}
		}
		if !hasSOA {
			r.cache.addNX(qname)
			return nil, NXDOMAIN
		}
	} else if rmsg.Rcode != dns.RcodeSuccess {
		return nil, errors.New(dns.RcodeToString[rmsg.Rcode]) // FIXME: should (*Resolver).exchange special-case this error?
	}

	// Cache records returned
	rrs := r.saveDNSRR(host, qname, append(append(rmsg.Answer, rmsg.Ns...), rmsg.Extra...))

	// Resolve IP addresses of TLD name servers if NS query doesn’t return additional section
	if qtype == "NS" {
		for _, rr := range rrs {
			if rr.Type != "NS" {
				continue
			}
			arrs, err := r.cacheGet(ctx, rr.Value, "A")
			if err != nil {
				continue
			}
			if len(arrs) == 0 {
				arrs, err = r.exchangeIP(ctx, host, ip, rr.Value, "A", depth)
				if err != nil {
					continue
				}
				// fmt.Printf("dig @%s %s A\n\t%#v\n", host, rr.Value, arrs)
			}
			rrs = append(rrs, arrs...)
		}
	}

	return rrs, nil
}

func (r *Resolver) resolveCNAMEs(ctx context.Context, qname, qtype string, crrs RRs, depth int) (RRs, error) {
	var rrs RRs
	for _, crr := range crrs {
		rrs = append(rrs, crr)
		if crr.Type != "CNAME" || crr.Name != qname {
			continue
		}
		logCNAME(crr.String(), depth)
		crrs, _ := r.resolve(ctx, crr.Value, qtype, depth)
		for _, rr := range crrs {
			r.cache.add(qname, rr)
			rrs = append(rrs, crr)
		}
	}
	return rrs, nil
}

// saveDNSRR saves 1 or more DNS records to the resolver cache.
func (r *Resolver) saveDNSRR(host, qname string, drrs []dns.RR) RRs {
	var rrs RRs
	cl := dns.CountLabel(qname)
	for _, drr := range drrs {
		rr, ok := convertRR(drr, r.expire)
		if !ok {
			continue
		}
		if dns.CountLabel(rr.Name) < cl && dns.CompareDomainName(qname, rr.Name) < 2 {
			// fmt.Fprintf(os.Stderr, "Warning: potential poisoning from %s: %s -> %s\n", host, qname, drr.String())
			continue
		}
		r.cache.add(rr.Name, rr)
		if rr.Name != qname {
			continue
		}
		rrs = append(rrs, rr)
	}
	return rrs
}

// cacheGet returns a randomly ordered slice of DNS records.
func (r *Resolver) cacheGet(ctx context.Context, qname, qtype string) (RRs, error) {
	select {
	case <-ctx.Done():
		return nil, ctx.Err()
	default:
	}
	any := r.cache.get(qname)
	if any == nil {
		any = rootCache.get(qname)
	}
	if any == nil {
		return nil, nil
	}
	if len(any) == 0 {
		return nil, NXDOMAIN
	}
	rrs := make(RRs, 0, len(any))
	for _, rr := range any {
		if qtype == "" || rr.Type == qtype {
			rrs = append(rrs, rr)
		}
	}
	if len(rrs) == 0 && (qtype != "" && qtype != "NS") {
		return nil, nil
	}
	return rrs, nil
}
<commit_msg>Short-circuit brute force A resolution on error<commit_after>package dnsr

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/miekg/dns"
)

// DNS Resolution configuration.
var (
	Timeout             = 2000 * time.Millisecond
	TypicalResponseTime = 100 * time.Millisecond
	MaxRecursion        = 10
	MaxNameservers      = 4
	MaxIPs              = 2
)

// Resolver errors.
var (
	NXDOMAIN = fmt.Errorf("NXDOMAIN")

	ErrMaxRecursion = fmt.Errorf("maximum recursion depth reached: %d", MaxRecursion)
	ErrMaxIPs       = fmt.Errorf("maximum name server IPs queried: %d", MaxIPs)
	ErrNoARecords   = fmt.Errorf("no A records found for name server")
	ErrNoResponse   = fmt.Errorf("no responses received")
	ErrTimeout      = fmt.Errorf("timeout expired") // TODO: Timeouter interface? e.g. func (e) Timeout() bool { return true }
)

// Resolver implements a primitive, non-recursive, caching DNS resolver.
type Resolver struct {
	cache   *cache
	expire  bool
	timeout time.Duration
}

// New initializes a Resolver with the specified cache size.
func New(capacity int) *Resolver {
	return NewWithTimeout(capacity, Timeout)
}

// NewWithTimeout initializes a Resolver with the specified cache size and resolution timeout.
func NewWithTimeout(capacity int, timeout time.Duration) *Resolver {
	r := &Resolver{
		cache:   newCache(capacity, false),
		expire:  false,
		timeout: timeout,
	}
	return r
}

// NewExpiring initializes an expiring Resolver with the specified cache size.
func NewExpiring(capacity int) *Resolver {
	return NewExpiringWithTimeout(capacity, Timeout)
}

// NewExpiringWithTimeout initializes an expiring Resolved with the specified cache size and resolution timeout.
func NewExpiringWithTimeout(capacity int, timeout time.Duration) *Resolver {
	r := &Resolver{
		cache:   newCache(capacity, true),
		expire:  true,
		timeout: timeout,
	}
	return r
}

// Resolve calls ResolveErr to find DNS records of type qtype for the domain qname.
// For nonexistent domains (NXDOMAIN), it will return an empty, non-nil slice.
func (r *Resolver) Resolve(qname, qtype string) RRs {
	rrs, err := r.ResolveErr(qname, qtype)
	if err == NXDOMAIN {
		return emptyRRs
	}
	if err != nil {
		return nil
	}
	return rrs
}

// ResolveErr finds DNS records of type qtype for the domain qname.
// For nonexistent domains, it will return an NXDOMAIN error.
// Specify an empty string in qtype to receive any DNS records found
// (currently A, AAAA, NS, CNAME, SOA, and TXT).
func (r *Resolver) ResolveErr(qname, qtype string) (RRs, error) {
	ctx, cancel := context.WithTimeout(context.Background(), r.timeout)
	defer cancel()
	return r.resolve(ctx, toLowerFQDN(qname), qtype, 0)
}

// ResolveCtx finds DNS records of type qtype for the domain qname using
// the supplied context. Requests may time out earlier if timeout is
// shorter than a deadline set in ctx.
// For nonexistent domains, it will return an NXDOMAIN error.
// Specify an empty string in qtype to receive any DNS records found
// (currently A, AAAA, NS, CNAME, SOA, and TXT).
func (r *Resolver) ResolveCtx(ctx context.Context, qname, qtype string) (RRs, error) {
	ctx, cancel := context.WithTimeout(ctx, r.timeout)
	defer cancel()
	return r.resolve(ctx, toLowerFQDN(qname), qtype, 0)
}

func (r *Resolver) resolve(ctx context.Context, qname, qtype string, depth int) (RRs, error) {
	if depth++; depth > MaxRecursion {
		logMaxRecursion(qname, qtype, depth)
		return nil, ErrMaxRecursion
	}
	rrs, err := r.cacheGet(ctx, qname, qtype)
	if err != nil {
		return nil, err
	}
	if len(rrs) > 0 {
		return rrs, nil
	}
	logResolveStart(qname, qtype, depth)
	start := time.Now()
	rrs, err = r.iterateParents(ctx, qname, qtype, depth)
	logResolveEnd(qname, qtype, rrs, depth, start, err)
	return rrs, err
}

func (r *Resolver) iterateParents(ctx context.Context, qname, qtype string, depth int) (RRs, error) {
	chanRRs := make(chan RRs, MaxNameservers)
	chanErrs := make(chan error, MaxNameservers)
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()
	for pname, ok := qname, true; ok; pname, ok = parent(pname) {
		// If we’re looking for [foo.com,NS], then move on to the parent ([com,NS])
		if pname == qname && qtype == "NS" {
			continue
		}

		// Only query TLDs against the root nameservers
		if pname == "." && dns.CountLabel(qname) != 1 {
			// fmt.Fprintf(os.Stderr, "Warning: non-TLD query at root: dig +norecurse %s %s\n", qname, qtype)
			return nil, nil
		}

		// Get nameservers
		nrrs, err := r.resolve(ctx, pname, "NS", depth)
		if err == NXDOMAIN || err == ErrTimeout || err == context.DeadlineExceeded {
			return nil, err
		}
		if err != nil {
			continue
		}

		// Check cache for specific queries
		if len(nrrs) > 0 && qtype != "" {
			rrs, err := r.cacheGet(ctx, qname, qtype)
			if err != nil {
				return nil, err
			}
			if len(rrs) > 0 {
				return rrs, nil
			}
		}

		// Query all nameservers in parallel
		count := 0
		for i := 0; i < len(nrrs) && count < MaxNameservers; i++ {
			nrr := nrrs[i]
			if nrr.Type != "NS" {
				continue
			}

			go func(host string) {
				rrs, err := r.exchange(ctx, host, qname, qtype, depth)
				if err != nil {
					chanErrs <- err
				} else {
					chanRRs <- rrs
				}
			}(nrr.Value)

			count++
		}

		// Wait for answer, error, or cancellation
		for ; count > 0; count-- {
			select {
			case <-ctx.Done():
				return nil, ctx.Err()
			case rrs := <-chanRRs:
				for _, nrr := range nrrs {
					if nrr.Name == qname {
						rrs = append(rrs, nrr)
					}
				}
				cancel() // stop any other work here before recursing
				return r.resolveCNAMEs(ctx, qname, qtype, rrs, depth)
			case err = <-chanErrs:
				if err == NXDOMAIN {
					return nil, err
				}
			}

		}

		// NS queries naturally recurse, so stop further iteration
		if qtype == "NS" {
			return nil, err
		}
	}

	return nil, ErrNoResponse
}

func (r *Resolver) exchange(ctx context.Context, host, qname, qtype string, depth int) (RRs, error) {
	count := 0
	arrs, err := r.resolve(ctx, host, "A", depth)
	if err != nil {
		return nil, err
	}
	for _, arr := range arrs {
		// FIXME: support AAAA records?
		if arr.Type != "A" {
			continue
		}

		// Never query more than MaxIPs for any nameserver
		if count++; count > MaxIPs {
			return nil, ErrMaxIPs
		}

		rrs, err := r.exchangeIP(ctx, host, arr.Value, qname, qtype, depth)
		if err == nil || err == NXDOMAIN || err == ErrTimeout {
			return rrs, err
		}

		if ctx.Err() != nil {
			return nil, ctx.Err()
		}
	}

	return nil, ErrNoARecords
}

func (r *Resolver) exchangeIP(ctx context.Context, host, ip, qname, qtype string, depth int) (RRs, error) {
	dtype := dns.StringToType[qtype]
	if dtype == 0 {
		dtype = dns.TypeA
	}
	var qmsg dns.Msg
	qmsg.SetQuestion(qname, dtype)
	qmsg.MsgHdr.RecursionDesired = false

	// Synchronously query this DNS server
	start := time.Now()
	timeout := r.timeout // belt and suspenders, since ctx has a deadline from ResolveErr
	if dl, ok := ctx.Deadline(); ok {
		if start.After(dl.Add(-TypicalResponseTime)) { // bail if we can't finish in time (start is too close to deadline)
			return nil, ErrTimeout
		}
		timeout = dl.Sub(start)
	}

	client := &dns.Client{Timeout: timeout} // client must finish within remaining timeout
	rmsg, dur, err := client.Exchange(&qmsg, ip+":53")
	select {
	case <-ctx.Done(): // Finished too late
		logCancellation(host, &qmsg, rmsg, depth, dur, timeout)
		return nil, ctx.Err()
	default:
		logExchange(host, &qmsg, rmsg, depth, dur, timeout, err) // Log hostname instead of IP
	}
	if err != nil {
		return nil, err
	}

	// FIXME: cache NXDOMAIN responses responsibly
	if rmsg.Rcode == dns.RcodeNameError {
		var hasSOA bool
		if qtype == "NS" {
			for _, drr := range rmsg.Ns {
				rr, ok := convertRR(drr, r.expire)
				if !ok {
					continue
				}
				if rr.Type == "SOA" {
					hasSOA = true
					break
				}
			}
		}
		if !hasSOA {
			r.cache.addNX(qname)
			return nil, NXDOMAIN
		}
	} else if rmsg.Rcode != dns.RcodeSuccess {
		return nil, errors.New(dns.RcodeToString[rmsg.Rcode]) // FIXME: should (*Resolver).exchange special-case this error?
	}

	// Cache records returned
	rrs := r.saveDNSRR(host, qname, append(append(rmsg.Answer, rmsg.Ns...), rmsg.Extra...))

	// Resolve IP addresses of TLD name servers if NS query doesn’t return additional section
	if qtype == "NS" {
		for _, rr := range rrs {
			if rr.Type != "NS" {
				continue
			}
			arrs, err := r.cacheGet(ctx, rr.Value, "A")
			if err == NXDOMAIN {
				continue
			}
			if err != nil {
				break
			}
			if len(arrs) == 0 {
				arrs, err = r.exchangeIP(ctx, host, ip, rr.Value, "A", depth+1)
				if err != nil {
					break
				}
			}
			rrs = append(rrs, arrs...)
		}
	}

	return rrs, nil
}

func (r *Resolver) resolveCNAMEs(ctx context.Context, qname, qtype string, crrs RRs, depth int) (RRs, error) {
	var rrs RRs
	for _, crr := range crrs {
		rrs = append(rrs, crr)
		if crr.Type != "CNAME" || crr.Name != qname {
			continue
		}
		logCNAME(crr.String(), depth)
		crrs, _ := r.resolve(ctx, crr.Value, qtype, depth)
		for _, rr := range crrs {
			r.cache.add(qname, rr)
			rrs = append(rrs, crr)
		}
	}
	return rrs, nil
}

// saveDNSRR saves 1 or more DNS records to the resolver cache.
func (r *Resolver) saveDNSRR(host, qname string, drrs []dns.RR) RRs {
	var rrs RRs
	cl := dns.CountLabel(qname)
	for _, drr := range drrs {
		rr, ok := convertRR(drr, r.expire)
		if !ok {
			continue
		}
		if dns.CountLabel(rr.Name) < cl && dns.CompareDomainName(qname, rr.Name) < 2 {
			// fmt.Fprintf(os.Stderr, "Warning: potential poisoning from %s: %s -> %s\n", host, qname, drr.String())
			continue
		}
		r.cache.add(rr.Name, rr)
		if rr.Name != qname {
			continue
		}
		rrs = append(rrs, rr)
	}
	return rrs
}

// cacheGet returns a randomly ordered slice of DNS records.
func (r *Resolver) cacheGet(ctx context.Context, qname, qtype string) (RRs, error) {
	select {
	case <-ctx.Done():
		return nil, ctx.Err()
	default:
	}
	any := r.cache.get(qname)
	if any == nil {
		any = rootCache.get(qname)
	}
	if any == nil {
		return nil, nil
	}
	if len(any) == 0 {
		return nil, NXDOMAIN
	}
	rrs := make(RRs, 0, len(any))
	for _, rr := range any {
		if qtype == "" || rr.Type == qtype {
			rrs = append(rrs, rr)
		}
	}
	if len(rrs) == 0 && (qtype != "" && qtype != "NS") {
		return nil, nil
	}
	return rrs, nil
}
<|endoftext|>


<commit_before>package messenger

import (
	"bytes"
	"encoding/json"
	"fmt"
	"image"
	"image/jpeg"
	"io"
	"mime/multipart"
	"net/http"
)

// AttachmentType is attachment type.
type AttachmentType string

const (
	// SendMessageURL is API endpoint for sending messages.
	SendMessageURL = "https://graph.facebook.com/v2.6/me/messages"

	// ImageAttachment is image attachment type.
	ImageAttachment AttachmentType = "image"
	// AudioAttachment is audio attachment type.
	AudioAttachment AttachmentType = "audio"
	// VideoAttachment is video attachment type.
	VideoAttachment AttachmentType = "video"
	// FileAttachment is file attachment type.
	FileAttachment AttachmentType = "file"
)

// QueryResponse is the response sent back by Facebook when setting up things
// like greetings or call-to-actions
type QueryResponse struct {
	Error  *QueryError `json:"error,omitempty"`
	Result string      `json:"result,omitempty"`
}

// QueryError is representing an error sent back by Facebook
type QueryError struct {
	Message   string `json:"message"`
	Type      string `json:"type"`
	Code      int    `json:"code"`
	FBTraceID string `json:"fbtrace_id"`
}

func checkFacebookError(r io.Reader) error {
	var err error

	qr := QueryResponse{}
	err = json.NewDecoder(r).Decode(&qr)
	if qr.Error != nil {
		err = fmt.Errorf("Facebook error : %s", qr.Error.Message)
		return err
	}

	return nil
}

// Response is used for responding to events with messages.
type Response struct {
	token string
	to    Recipient
}

// Text sends a textual message.
func (r *Response) Text(message string) error {
	return r.TextWithReplies(message, nil)
}

// TextWithReplies sends a textual message with some replies
func (r *Response) TextWithReplies(message string, replies []QuickReply) error {
	m := SendMessage{
		Recipient: r.to,
		Message: MessageData{
			Text:         message,
			Attachment:   nil,
			QuickReplies: replies,
		},
	}
	return r.DispatchMessage(&m)
}

// AttachmentWithReplies sends a attachment message with some replies
func (r *Response) AttachmentWithReplies(attachment *StructuredMessageAttachment, replies []QuickReply) error {
	m := SendMessage{
		Recipient: r.to,
		Message: MessageData{
			Attachment:   attachment,
			QuickReplies: replies,
		},
	}
	return r.DispatchMessage(&m)
}

// Image sends an image.
func (r *Response) Image(im image.Image) error {
	imageBytes := new(bytes.Buffer)
	err := jpeg.Encode(imageBytes, im, nil)
	if err != nil {
		return err
	}

	return r.AttachmentData(ImageAttachment, "meme.jpg", imageBytes)
}

// Attachment sends an image, sound, video or a regular file to a chat.
func (r *Response) Attachment(dataType AttachmentType, url string) error {
	m := SendStructuredMessage{
		Recipient: r.to,
		Message: StructuredMessageData{
			Attachment: StructuredMessageAttachment{
				Type: dataType,
				Payload: StructuredMessagePayload{
					Url: url,
				},
			},
		},
	}
	return r.DispatchMessage(&m)
}

// AttachmentData sends an image, sound, video or a regular file to a chat via an io.Reader.
func (r *Response) AttachmentData(dataType AttachmentType, filename string, filedata io.Reader) error {
	var b bytes.Buffer
	w := multipart.NewWriter(&b)

	data, err := w.CreateFormFile("filedata", filename)
	if err != nil {
		return err
	}

	_, err = io.Copy(data, filedata)
	if err != nil {
		return err
	}

	w.WriteField("recipient", fmt.Sprintf(`{"id":"%v"}`, r.to.ID))
	w.WriteField("message", fmt.Sprintf(`{"attachment":{"type":"%v", "payload":{}}}`, dataType))

	req, err := http.NewRequest("POST", SendMessageURL, &b)
	if err != nil {
		return err
	}

	req.URL.RawQuery = "access_token=" + r.token

	req.Header.Set("Content-Type", w.FormDataContentType())

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}

	return checkFacebookError(resp.Body)
}

// ButtonTemplate sends a message with the main contents being button elements
func (r *Response) ButtonTemplate(text string, buttons *[]StructuredMessageButton) error {
	m := SendStructuredMessage{
		Recipient: r.to,
		Message: StructuredMessageData{
			Attachment: StructuredMessageAttachment{
				Type: "template",
				Payload: StructuredMessagePayload{
					TemplateType: "button",
					Text:         text,
					Buttons:      buttons,
					Elements:     nil,
				},
			},
		},
	}

	return r.DispatchMessage(&m)
}

// GenericTemplate is a message which allows for structural elements to be sent
func (r *Response) GenericTemplate(elements *[]StructuredMessageElement) error {
	m := SendStructuredMessage{
		Recipient: r.to,
		Message: StructuredMessageData{
			Attachment: StructuredMessageAttachment{
				Type: "template",
				Payload: StructuredMessagePayload{
					TemplateType: "generic",
					Buttons:      nil,
					Elements:     elements,
				},
			},
		},
	}
	return r.DispatchMessage(&m)
}

// SenderAction sends a info about sender action
func (r *Response) SenderAction(action string) error {
	m := SendSenderAction{
		Recipient:    r.to,
		SenderAction: action,
	}
	return r.DispatchMessage(&m)
}

// DispatchMessage posts the message to messenger, return the error if there's any
func (r *Response) DispatchMessage(m interface{}) error {
	data, err := json.Marshal(m)
	if err != nil {
		return err
	}

	req, err := http.NewRequest("POST", SendMessageURL, bytes.NewBuffer(data))
	if err != nil {
		return err
	}

	req.Header.Set("Content-Type", "application/json")
	req.URL.RawQuery = "access_token=" + r.token

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode == 200 {
		return nil
	}
	return checkFacebookError(resp.Body)
}

// SendMessage is the information sent in an API request to Facebook.
type SendMessage struct {
	Recipient Recipient   `json:"recipient"`
	Message   MessageData `json:"message"`
}

// MessageData is a message consisting of text or an attachment, with an additional selection of optional quick replies.
type MessageData struct {
	Text         string                       `json:"text,omitempty"`
	Attachment   *StructuredMessageAttachment `json:"attachment,omitempty"`
	QuickReplies []QuickReply                 `json:"quick_replies,omitempty"`
}

// SendStructuredMessage is a structured message template.
type SendStructuredMessage struct {
	Recipient Recipient             `json:"recipient"`
	Message   StructuredMessageData `json:"message"`
}

// StructuredMessageData is an attachment sent with a structured message.
type StructuredMessageData struct {
	Attachment StructuredMessageAttachment `json:"attachment"`
}

// StructuredMessageAttachment is the attachment of a structured message.
type StructuredMessageAttachment struct {
	// Type must be template
	Type AttachmentType `json:"type"`
	// Payload is the information for the file which was sent in the attachment.
	Payload StructuredMessagePayload `json:"payload"`
}

// StructuredMessagePayload is the actual payload of an attachment
type StructuredMessagePayload struct {
	// TemplateType must be button, generic or receipt
	TemplateType string                      `json:"template_type,omitempty"`
	Text         string                      `json:"text,omitempty"`
	Elements     *[]StructuredMessageElement `json:"elements,omitempty"`
	Buttons      *[]StructuredMessageButton  `json:"buttons,omitempty"`
	Url          string                      `json:"url,omitempty"`
}

// StructuredMessageElement is a response containing structural elements
type StructuredMessageElement struct {
	Title    string                    `json:"title"`
	ImageURL string                    `json:"image_url"`
	ItemURL  string                    `json:"item_url"`
	Subtitle string                    `json:"subtitle"`
	Buttons  []StructuredMessageButton `json:"buttons"`
}

// StructuredMessageButton is a response containing buttons
type StructuredMessageButton struct {
	Type                string `json:"type"`
	URL                 string `json:"url,omitempty"`
	Title               string `json:"title,omitempty"`
	Payload             string `json:"payload,omitempty"`
	WebviewHeightRatio  string `json:"webview_height_ratio,omitempty"`
	MessengerExtensions bool   `json:"messenger_extensions,omitempty"`
	FallbackURL         string `json:"fallback_url,omitempty"`
	WebviewShareButton  string `json:"webview_share_button,omitempty"`
}

// SendSenderAction is the information about sender action
type SendSenderAction struct {
	Recipient    Recipient `json:"recipient"`
	SenderAction string    `json:"sender_action"`
}
<commit_msg>Correct content-type for attachments (#33)<commit_after>package messenger

import (
	"bytes"
	"encoding/json"
	"fmt"
	"image"
	"image/jpeg"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"net/textproto"
	"strings"
)

// AttachmentType is attachment type.
type AttachmentType string

const (
	// SendMessageURL is API endpoint for sending messages.
	SendMessageURL = "https://graph.facebook.com/v2.6/me/messages"

	// ImageAttachment is image attachment type.
	ImageAttachment AttachmentType = "image"
	// AudioAttachment is audio attachment type.
	AudioAttachment AttachmentType = "audio"
	// VideoAttachment is video attachment type.
	VideoAttachment AttachmentType = "video"
	// FileAttachment is file attachment type.
	FileAttachment AttachmentType = "file"
)

// QueryResponse is the response sent back by Facebook when setting up things
// like greetings or call-to-actions
type QueryResponse struct {
	Error  *QueryError `json:"error,omitempty"`
	Result string      `json:"result,omitempty"`
}

// QueryError is representing an error sent back by Facebook
type QueryError struct {
	Message   string `json:"message"`
	Type      string `json:"type"`
	Code      int    `json:"code"`
	FBTraceID string `json:"fbtrace_id"`
}

func checkFacebookError(r io.Reader) error {
	var err error

	qr := QueryResponse{}
	err = json.NewDecoder(r).Decode(&qr)
	if qr.Error != nil {
		err = fmt.Errorf("Facebook error : %s", qr.Error.Message)
		return err
	}

	return nil
}

// Response is used for responding to events with messages.
type Response struct {
	token string
	to    Recipient
}

// Text sends a textual message.
func (r *Response) Text(message string) error {
	return r.TextWithReplies(message, nil)
}

// TextWithReplies sends a textual message with some replies
func (r *Response) TextWithReplies(message string, replies []QuickReply) error {
	m := SendMessage{
		Recipient: r.to,
		Message: MessageData{
			Text:         message,
			Attachment:   nil,
			QuickReplies: replies,
		},
	}
	return r.DispatchMessage(&m)
}

// AttachmentWithReplies sends a attachment message with some replies
func (r *Response) AttachmentWithReplies(attachment *StructuredMessageAttachment, replies []QuickReply) error {
	m := SendMessage{
		Recipient: r.to,
		Message: MessageData{
			Attachment:   attachment,
			QuickReplies: replies,
		},
	}
	return r.DispatchMessage(&m)
}

// Image sends an image.
func (r *Response) Image(im image.Image) error {
	imageBytes := new(bytes.Buffer)
	err := jpeg.Encode(imageBytes, im, nil)
	if err != nil {
		return err
	}

	return r.AttachmentData(ImageAttachment, "meme.jpg", imageBytes)
}

// Attachment sends an image, sound, video or a regular file to a chat.
func (r *Response) Attachment(dataType AttachmentType, url string) error {
	m := SendStructuredMessage{
		Recipient: r.to,
		Message: StructuredMessageData{
			Attachment: StructuredMessageAttachment{
				Type: dataType,
				Payload: StructuredMessagePayload{
					Url: url,
				},
			},
		},
	}
	return r.DispatchMessage(&m)
}

// copied from multipart package
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")

// copied from multipart package
func escapeQuotes(s string) string {
	return quoteEscaper.Replace(s)
}

// copied from multipart package with slight changes due to fixed content-type there
func createFormFile(filename string, w *multipart.Writer, contentType string) (io.Writer, error) {
	h := make(textproto.MIMEHeader)
	h.Set("Content-Disposition",
		fmt.Sprintf(`form-data; name="filedata"; filename="%s"`,
			escapeQuotes(filename)))
	h.Set("Content-Type", contentType)
	return w.CreatePart(h)
}

// AttachmentData sends an image, sound, video or a regular file to a chat via an io.Reader.
func (r *Response) AttachmentData(dataType AttachmentType, filename string, filedata io.Reader) error {

	filedataBytes, err := ioutil.ReadAll(filedata)
	if err != nil {
		return err
	}
	contentType := http.DetectContentType(filedataBytes[:512])
	fmt.Println("Content-type detected:", contentType)

	var body bytes.Buffer
	multipartWriter := multipart.NewWriter(&body)
	data, err := createFormFile(filename, multipartWriter, contentType)
	if err != nil {
		return err
	}

	_, err = bytes.NewBuffer(filedataBytes).WriteTo(data)
	if err != nil {
		return err
	}

	multipartWriter.WriteField("recipient", fmt.Sprintf(`{"id":"%v"}`, r.to.ID))
	multipartWriter.WriteField("message", fmt.Sprintf(`{"attachment":{"type":"%v", "payload":{}}}`, dataType))

	req, err := http.NewRequest("POST", SendMessageURL, &body)
	if err != nil {
		return err
	}

	req.URL.RawQuery = "access_token=" + r.token

	req.Header.Set("Content-Type", multipartWriter.FormDataContentType())

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}

	return checkFacebookError(resp.Body)
}

// ButtonTemplate sends a message with the main contents being button elements
func (r *Response) ButtonTemplate(text string, buttons *[]StructuredMessageButton) error {
	m := SendStructuredMessage{
		Recipient: r.to,
		Message: StructuredMessageData{
			Attachment: StructuredMessageAttachment{
				Type: "template",
				Payload: StructuredMessagePayload{
					TemplateType: "button",
					Text:         text,
					Buttons:      buttons,
					Elements:     nil,
				},
			},
		},
	}

	return r.DispatchMessage(&m)
}

// GenericTemplate is a message which allows for structural elements to be sent
func (r *Response) GenericTemplate(elements *[]StructuredMessageElement) error {
	m := SendStructuredMessage{
		Recipient: r.to,
		Message: StructuredMessageData{
			Attachment: StructuredMessageAttachment{
				Type: "template",
				Payload: StructuredMessagePayload{
					TemplateType: "generic",
					Buttons:      nil,
					Elements:     elements,
				},
			},
		},
	}
	return r.DispatchMessage(&m)
}

// SenderAction sends a info about sender action
func (r *Response) SenderAction(action string) error {
	m := SendSenderAction{
		Recipient:    r.to,
		SenderAction: action,
	}
	return r.DispatchMessage(&m)
}

// DispatchMessage posts the message to messenger, return the error if there's any
func (r *Response) DispatchMessage(m interface{}) error {
	data, err := json.Marshal(m)
	if err != nil {
		return err
	}

	req, err := http.NewRequest("POST", SendMessageURL, bytes.NewBuffer(data))
	if err != nil {
		return err
	}

	req.Header.Set("Content-Type", "application/json")
	req.URL.RawQuery = "access_token=" + r.token

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode == 200 {
		return nil
	}
	return checkFacebookError(resp.Body)
}

// SendMessage is the information sent in an API request to Facebook.
type SendMessage struct {
	Recipient Recipient   `json:"recipient"`
	Message   MessageData `json:"message"`
}

// MessageData is a message consisting of text or an attachment, with an additional selection of optional quick replies.
type MessageData struct {
	Text         string                       `json:"text,omitempty"`
	Attachment   *StructuredMessageAttachment `json:"attachment,omitempty"`
	QuickReplies []QuickReply                 `json:"quick_replies,omitempty"`
}

// SendStructuredMessage is a structured message template.
type SendStructuredMessage struct {
	Recipient Recipient             `json:"recipient"`
	Message   StructuredMessageData `json:"message"`
}

// StructuredMessageData is an attachment sent with a structured message.
type StructuredMessageData struct {
	Attachment StructuredMessageAttachment `json:"attachment"`
}

// StructuredMessageAttachment is the attachment of a structured message.
type StructuredMessageAttachment struct {
	// Type must be template
	Type AttachmentType `json:"type"`
	// Payload is the information for the file which was sent in the attachment.
	Payload StructuredMessagePayload `json:"payload"`
}

// StructuredMessagePayload is the actual payload of an attachment
type StructuredMessagePayload struct {
	// TemplateType must be button, generic or receipt
	TemplateType string                      `json:"template_type,omitempty"`
	Text         string                      `json:"text,omitempty"`
	Elements     *[]StructuredMessageElement `json:"elements,omitempty"`
	Buttons      *[]StructuredMessageButton  `json:"buttons,omitempty"`
	Url          string                      `json:"url,omitempty"`
}

// StructuredMessageElement is a response containing structural elements
type StructuredMessageElement struct {
	Title    string                    `json:"title"`
	ImageURL string                    `json:"image_url"`
	ItemURL  string                    `json:"item_url"`
	Subtitle string                    `json:"subtitle"`
	Buttons  []StructuredMessageButton `json:"buttons"`
}

// StructuredMessageButton is a response containing buttons
type StructuredMessageButton struct {
	Type                string `json:"type"`
	URL                 string `json:"url,omitempty"`
	Title               string `json:"title,omitempty"`
	Payload             string `json:"payload,omitempty"`
	WebviewHeightRatio  string `json:"webview_height_ratio,omitempty"`
	MessengerExtensions bool   `json:"messenger_extensions,omitempty"`
	FallbackURL         string `json:"fallback_url,omitempty"`
	WebviewShareButton  string `json:"webview_share_button,omitempty"`
}

// SendSenderAction is the information about sender action
type SendSenderAction struct {
	Recipient    Recipient `json:"recipient"`
	SenderAction string    `json:"sender_action"`
}
<|endoftext|>


<commit_before>package restful

// Copyright 2013 Ernest Micklei. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.

import (
	"errors"
	"net/http"
)

// DEPRECATED, use DefaultResponseContentType(mime)
var DefaultResponseMimeType string

//PrettyPrintResponses controls the indentation feature of XML and JSON serialization
var PrettyPrintResponses = true

// Response is a wrapper on the actual http ResponseWriter
// It provides several convenience methods to prepare and write response content.
type Response struct {
	http.ResponseWriter
	requestAccept string   // mime-type what the Http Request says it wants to receive
	routeProduces []string // mime-types what the Route says it can produce
	statusCode    int      // HTTP status code that has been written explicity (if zero then net/http has written 200)
	contentLength int      // number of bytes written for the response body
	prettyPrint   bool     // controls the indentation feature of XML and JSON serialization. It is initialized using var PrettyPrintResponses.
	err           error    // err property is kept when WriteError is called
}

// Creates a new response based on a http ResponseWriter.
func NewResponse(httpWriter http.ResponseWriter) *Response {
	return &Response{httpWriter, "", []string{}, http.StatusOK, 0, PrettyPrintResponses, nil} // empty content-types
}

// If Accept header matching fails, fall back to this type.
// Valid values are restful.MIME_JSON and restful.MIME_XML
// Example:
// 	restful.DefaultResponseContentType(restful.MIME_JSON)
func DefaultResponseContentType(mime string) {
	DefaultResponseMimeType = mime
}

// InternalServerError writes the StatusInternalServerError header.
// DEPRECATED, use WriteErrorString(http.StatusInternalServerError,reason)
func (r Response) InternalServerError() Response {
	r.WriteHeader(http.StatusInternalServerError)
	return r
}

// PrettyPrint changes whether this response must produce pretty (line-by-line, indented) JSON or XML output.
func (r *Response) PrettyPrint(bePretty bool) {
	r.prettyPrint = bePretty
}

// AddHeader is a shortcut for .Header().Add(header,value)
func (r Response) AddHeader(header string, value string) Response {
	r.Header().Add(header, value)
	return r
}

// SetRequestAccepts tells the response what Mime-type(s) the HTTP request said it wants to accept. Exposed for testing.
func (r *Response) SetRequestAccepts(mime string) {
	r.requestAccept = mime
}

// EntityWriter returns the registered EntityWriter that the entity (requested resource)
// can write according to what the request wants (Accept) and what the Route can produce or what the restful defaults say.
// If called before WriteEntity and WriteHeader then a false return value can be used to write a 406: Not Acceptable.
func (r *Response) EntityWriter() (EntityReaderWriter, bool) {
	sorted := sortedMimes(r.requestAccept)
	for _, eachAccept := range sorted {
		for _, eachProduce := range r.routeProduces {
			if eachProduce == eachAccept.media {
				w, ok := entityAccessRegistry.AccessorAt(eachAccept.media)
				if ok {
					return w, true
				}
			}
		}
		if eachAccept.media == "*/*" {
			for _, each := range r.routeProduces {
				if MIME_JSON == each {
					return entityAccessRegistry.AccessorAt(MIME_JSON)
				}
				if MIME_XML == each {
					return entityAccessRegistry.AccessorAt(MIME_XML)
				}
			}
		}
	}
	writer, ok := entityAccessRegistry.AccessorAt(r.requestAccept)
	if !ok {
		// if not registered then fallback to the defaults (if set)
		if DefaultResponseMimeType == MIME_JSON {
			return entityAccessRegistry.AccessorAt(MIME_JSON)
		}
		if DefaultResponseMimeType == MIME_XML {
			return entityAccessRegistry.AccessorAt(MIME_XML)
		}
		if trace {
			traceLogger.Printf("no registered EntityReaderWriter found for %s", r.requestAccept)
		}
	}
	return writer, ok
}

// WriteEntity calls WriteHeaderAndEntity with Http Status OK (200)
func (r *Response) WriteEntity(value interface{}) error {
	return r.WriteHeaderAndEntity(http.StatusOK, value)
}

// WriteHeaderAndEntity marshals the value using the representation denoted by the Accept Header and the registered EntityWriters.
// If no Accept header is specified (or */*) then respond with the Content-Type as specified by the first in the Route.Produces.
// If an Accept header is specified then respond with the Content-Type as specified by the first in the Route.Produces that is matched with the Accept header.
// If the value is nil then no response is send except for the Http status. You may want to call WriteHeader(http.StatusNotFound) instead.
// If there is no writer available that can represent the value in the requested MIME type then Http Status NotAcceptable is written.
// Current implementation ignores any q-parameters in the Accept Header.
// Returns an error if the value could not be written on the response.
func (r *Response) WriteHeaderAndEntity(status int, value interface{}) error {
	writer, ok := r.EntityWriter()
	if !ok {
		r.WriteHeader(http.StatusNotAcceptable)
		return nil
	}
	return writer.Write(r, status, value)
}

// WriteAsXml is a convenience method for writing a value in xml (requires Xml tags on the value)
// It uses the standard encoding/xml package for marshalling the valuel ; not using a registered EntityReaderWriter.
func (r *Response) WriteAsXml(value interface{}) error {
	return writeXML(r, http.StatusOK, MIME_XML, value)
}

// WriteHeaderAndXml is a convenience method for writing a status and value in xml (requires Xml tags on the value)
// It uses the standard encoding/xml package for marshalling the valuel ; not using a registered EntityReaderWriter.
func (r *Response) WriteHeaderAndXml(status int, value interface{}) error {
	return writeXML(r, status, MIME_XML, value)
}

// WriteAsJson is a convenience method for writing a value in json.
// It uses the standard encoding/json package for marshalling the valuel ; not using a registered EntityReaderWriter.
func (r *Response) WriteAsJson(value interface{}) error {
	return writeJSON(r, http.StatusOK, MIME_JSON, value)
}

// WriteJson is a convenience method for writing a value in Json with a given Content-Type.
// It uses the standard encoding/json package for marshalling the valuel ; not using a registered EntityReaderWriter.
func (r *Response) WriteJson(value interface{}, contentType string) error {
	return writeJSON(r, http.StatusOK, contentType, value)
}

// WriteHeaderAndJson is a convenience method for writing the status and a value in Json with a given Content-Type.
// It uses the standard encoding/json package for marshalling the value ; not using a registered EntityReaderWriter.
func (r *Response) WriteHeaderAndJson(status int, value interface{}, contentType string) error {
	return writeJSON(r, status, contentType, value)
}

// WriteError write the http status and the error string on the response.
func (r *Response) WriteError(httpStatus int, err error) error {
	r.err = err
	return r.WriteErrorString(httpStatus, err.Error())
}

// WriteServiceError is a convenience method for a responding with a status and a ServiceError
func (r *Response) WriteServiceError(httpStatus int, err ServiceError) error {
	r.err = err
	return r.WriteHeaderAndEntity(httpStatus, err)
}

// WriteErrorString is a convenience method for an error status with the actual error
func (r *Response) WriteErrorString(httpStatus int, errorReason string) error {
	if r.err == nil {
		// if not called from WriteError
		r.err = errors.New(errorReason)
	}
	r.WriteHeader(httpStatus)
	if _, err := r.Write([]byte(errorReason)); err != nil {
		return err
	}
	return nil
}

// Flush implements http.Flusher interface, which sends any buffered data to the client.
func (r *Response) Flush() {
	if f, ok := r.ResponseWriter.(http.Flusher); ok {
		f.Flush()
	} else if trace {
		traceLogger.Printf("ResponseWriter %v doesn't support Flush", r)
	}
}

// WriteHeader is overridden to remember the Status Code that has been written.
// Changes to the Header of the response have no effect after this.
func (r *Response) WriteHeader(httpStatus int) {
	r.statusCode = httpStatus
	r.ResponseWriter.WriteHeader(httpStatus)
}

// StatusCode returns the code that has been written using WriteHeader.
func (r Response) StatusCode() int {
	if 0 == r.statusCode {
		// no status code has been written yet; assume OK
		return http.StatusOK
	}
	return r.statusCode
}

// Write writes the data to the connection as part of an HTTP reply.
// Write is part of http.ResponseWriter interface.
func (r *Response) Write(bytes []byte) (int, error) {
	written, err := r.ResponseWriter.Write(bytes)
	r.contentLength += written
	return written, err
}

// ContentLength returns the number of bytes written for the response content.
// Note that this value is only correct if all data is written through the Response using its Write* methods.
// Data written directly using the underlying http.ResponseWriter is not accounted for.
func (r Response) ContentLength() int {
	return r.contentLength
}

// CloseNotify is part of http.CloseNotifier interface
func (r Response) CloseNotify() <-chan bool {
	return r.ResponseWriter.(http.CloseNotifier).CloseNotify()
}

// Error returns the err created by WriteError
func (r Response) Error() error {
	return r.err
}
<commit_msg>simplify finding accessor in response<commit_after>package restful

// Copyright 2013 Ernest Micklei. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.

import (
	"errors"
	"net/http"
)

// DEPRECATED, use DefaultResponseContentType(mime)
var DefaultResponseMimeType string

//PrettyPrintResponses controls the indentation feature of XML and JSON serialization
var PrettyPrintResponses = true

// Response is a wrapper on the actual http ResponseWriter
// It provides several convenience methods to prepare and write response content.
type Response struct {
	http.ResponseWriter
	requestAccept string   // mime-type what the Http Request says it wants to receive
	routeProduces []string // mime-types what the Route says it can produce
	statusCode    int      // HTTP status code that has been written explicity (if zero then net/http has written 200)
	contentLength int      // number of bytes written for the response body
	prettyPrint   bool     // controls the indentation feature of XML and JSON serialization. It is initialized using var PrettyPrintResponses.
	err           error    // err property is kept when WriteError is called
}

// Creates a new response based on a http ResponseWriter.
func NewResponse(httpWriter http.ResponseWriter) *Response {
	return &Response{httpWriter, "", []string{}, http.StatusOK, 0, PrettyPrintResponses, nil} // empty content-types
}

// If Accept header matching fails, fall back to this type.
// Valid values are restful.MIME_JSON and restful.MIME_XML
// Example:
// 	restful.DefaultResponseContentType(restful.MIME_JSON)
func DefaultResponseContentType(mime string) {
	DefaultResponseMimeType = mime
}

// InternalServerError writes the StatusInternalServerError header.
// DEPRECATED, use WriteErrorString(http.StatusInternalServerError,reason)
func (r Response) InternalServerError() Response {
	r.WriteHeader(http.StatusInternalServerError)
	return r
}

// PrettyPrint changes whether this response must produce pretty (line-by-line, indented) JSON or XML output.
func (r *Response) PrettyPrint(bePretty bool) {
	r.prettyPrint = bePretty
}

// AddHeader is a shortcut for .Header().Add(header,value)
func (r Response) AddHeader(header string, value string) Response {
	r.Header().Add(header, value)
	return r
}

// SetRequestAccepts tells the response what Mime-type(s) the HTTP request said it wants to accept. Exposed for testing.
func (r *Response) SetRequestAccepts(mime string) {
	r.requestAccept = mime
}

// EntityWriter returns the registered EntityWriter that the entity (requested resource)
// can write according to what the request wants (Accept) and what the Route can produce or what the restful defaults say.
// If called before WriteEntity and WriteHeader then a false return value can be used to write a 406: Not Acceptable.
func (r *Response) EntityWriter() (EntityReaderWriter, bool) {
	sorted := sortedMimes(r.requestAccept)
	for _, eachAccept := range sorted {
		for _, eachProduce := range r.routeProduces {
			if eachProduce == eachAccept.media {
				w, ok := entityAccessRegistry.AccessorAt(eachAccept.media)
				if ok {
					return w, true
				}
			}
		}
		if eachAccept.media == "*/*" {
			for _, each := range r.routeProduces {
				w, ok := entityAccessRegistry.AccessorAt(each)
				if ok {
					return w, true
				}
			}
		}
	}
	// if requestAccept is empty
	writer, ok := entityAccessRegistry.AccessorAt(r.requestAccept)
	if !ok {
		// if not registered then fallback to the defaults (if set)
		if DefaultResponseMimeType == MIME_JSON {
			return entityAccessRegistry.AccessorAt(MIME_JSON)
		}
		if DefaultResponseMimeType == MIME_XML {
			return entityAccessRegistry.AccessorAt(MIME_XML)
		}
		if trace {
			traceLogger.Printf("no registered EntityReaderWriter found for %s", r.requestAccept)
		}
	}
	return writer, ok
}

// WriteEntity calls WriteHeaderAndEntity with Http Status OK (200)
func (r *Response) WriteEntity(value interface{}) error {
	return r.WriteHeaderAndEntity(http.StatusOK, value)
}

// WriteHeaderAndEntity marshals the value using the representation denoted by the Accept Header and the registered EntityWriters.
// If no Accept header is specified (or */*) then respond with the Content-Type as specified by the first in the Route.Produces.
// If an Accept header is specified then respond with the Content-Type as specified by the first in the Route.Produces that is matched with the Accept header.
// If the value is nil then no response is send except for the Http status. You may want to call WriteHeader(http.StatusNotFound) instead.
// If there is no writer available that can represent the value in the requested MIME type then Http Status NotAcceptable is written.
// Current implementation ignores any q-parameters in the Accept Header.
// Returns an error if the value could not be written on the response.
func (r *Response) WriteHeaderAndEntity(status int, value interface{}) error {
	writer, ok := r.EntityWriter()
	if !ok {
		r.WriteHeader(http.StatusNotAcceptable)
		return nil
	}
	return writer.Write(r, status, value)
}

// WriteAsXml is a convenience method for writing a value in xml (requires Xml tags on the value)
// It uses the standard encoding/xml package for marshalling the valuel ; not using a registered EntityReaderWriter.
func (r *Response) WriteAsXml(value interface{}) error {
	return writeXML(r, http.StatusOK, MIME_XML, value)
}

// WriteHeaderAndXml is a convenience method for writing a status and value in xml (requires Xml tags on the value)
// It uses the standard encoding/xml package for marshalling the valuel ; not using a registered EntityReaderWriter.
func (r *Response) WriteHeaderAndXml(status int, value interface{}) error {
	return writeXML(r, status, MIME_XML, value)
}

// WriteAsJson is a convenience method for writing a value in json.
// It uses the standard encoding/json package for marshalling the valuel ; not using a registered EntityReaderWriter.
func (r *Response) WriteAsJson(value interface{}) error {
	return writeJSON(r, http.StatusOK, MIME_JSON, value)
}

// WriteJson is a convenience method for writing a value in Json with a given Content-Type.
// It uses the standard encoding/json package for marshalling the valuel ; not using a registered EntityReaderWriter.
func (r *Response) WriteJson(value interface{}, contentType string) error {
	return writeJSON(r, http.StatusOK, contentType, value)
}

// WriteHeaderAndJson is a convenience method for writing the status and a value in Json with a given Content-Type.
// It uses the standard encoding/json package for marshalling the value ; not using a registered EntityReaderWriter.
func (r *Response) WriteHeaderAndJson(status int, value interface{}, contentType string) error {
	return writeJSON(r, status, contentType, value)
}

// WriteError write the http status and the error string on the response.
func (r *Response) WriteError(httpStatus int, err error) error {
	r.err = err
	return r.WriteErrorString(httpStatus, err.Error())
}

// WriteServiceError is a convenience method for a responding with a status and a ServiceError
func (r *Response) WriteServiceError(httpStatus int, err ServiceError) error {
	r.err = err
	return r.WriteHeaderAndEntity(httpStatus, err)
}

// WriteErrorString is a convenience method for an error status with the actual error
func (r *Response) WriteErrorString(httpStatus int, errorReason string) error {
	if r.err == nil {
		// if not called from WriteError
		r.err = errors.New(errorReason)
	}
	r.WriteHeader(httpStatus)
	if _, err := r.Write([]byte(errorReason)); err != nil {
		return err
	}
	return nil
}

// Flush implements http.Flusher interface, which sends any buffered data to the client.
func (r *Response) Flush() {
	if f, ok := r.ResponseWriter.(http.Flusher); ok {
		f.Flush()
	} else if trace {
		traceLogger.Printf("ResponseWriter %v doesn't support Flush", r)
	}
}

// WriteHeader is overridden to remember the Status Code that has been written.
// Changes to the Header of the response have no effect after this.
func (r *Response) WriteHeader(httpStatus int) {
	r.statusCode = httpStatus
	r.ResponseWriter.WriteHeader(httpStatus)
}

// StatusCode returns the code that has been written using WriteHeader.
func (r Response) StatusCode() int {
	if 0 == r.statusCode {
		// no status code has been written yet; assume OK
		return http.StatusOK
	}
	return r.statusCode
}

// Write writes the data to the connection as part of an HTTP reply.
// Write is part of http.ResponseWriter interface.
func (r *Response) Write(bytes []byte) (int, error) {
	written, err := r.ResponseWriter.Write(bytes)
	r.contentLength += written
	return written, err
}

// ContentLength returns the number of bytes written for the response content.
// Note that this value is only correct if all data is written through the Response using its Write* methods.
// Data written directly using the underlying http.ResponseWriter is not accounted for.
func (r Response) ContentLength() int {
	return r.contentLength
}

// CloseNotify is part of http.CloseNotifier interface
func (r Response) CloseNotify() <-chan bool {
	return r.ResponseWriter.(http.CloseNotifier).CloseNotify()
}

// Error returns the err created by WriteError
func (r Response) Error() error {
	return r.err
}
<|endoftext|>


<commit_before>package login

import (
	"crypto/rand"
	"encoding/hex"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"strings"
	"time"

	"github.com/tarent/loginsrv/logging"
	"github.com/tarent/loginsrv/oauth2"
)

var jwtDefaultSecret string

func init() {
	var err error
	jwtDefaultSecret, err = randStringBytes(32)
	if err != nil {
		panic(err)
	}
}

// DefaultConfig for the loginsrv handler
func DefaultConfig() *Config {
	return &Config{
		Host:                   "localhost",
		Port:                   "6789",
		LogLevel:               "info",
		JwtSecret:              jwtDefaultSecret,
		JwtAlgo:                "HS512",
		JwtExpiry:              24 * time.Hour,
		JwtRefreshes:           0,
		SuccessURL:             "/",
		Redirect:               true,
		RedirectQueryParameter: "backTo",
		RedirectCheckReferer:   true,
		RedirectHostFile:       "",
		LogoutURL:              "",
		LoginPath:              "/login",
		CookieName:             "jwt_token",
		CookieHTTPOnly:         true,
		CookieSecure:           true,
		Backends:               Options{},
		Oauth:                  Options{},
		GracePeriod:            5 * time.Second,
		UserFile:               "",
		UserEndpoint:           "",
		UserEndpointToken:      "",
		UserEndpointTimeout:    5 * time.Second,
	}
}

const envPrefix = "LOGINSRV_"

// Config for the loginsrv handler
type Config struct {
	Host                   string
	Port                   string
	LogLevel               string
	TextLogging            bool
	JwtSecret              string
	JwtSecretFile          string
	JwtAlgo                string
	JwtExpiry              time.Duration
	JwtRefreshes           int
	SuccessURL             string
	Redirect               bool
	RedirectQueryParameter string
	RedirectCheckReferer   bool
	RedirectHostFile       string
	LogoutURL              string
	Template               string
	LoginPath              string
	CookieName             string
	CookieExpiry           time.Duration
	CookieDomain           string
	CookieHTTPOnly         bool
	CookieSecure           bool
	Backends               Options
	Oauth                  Options
	GracePeriod            time.Duration
	UserFile               string
	UserEndpoint           string
	UserEndpointToken      string
	UserEndpointTimeout    time.Duration
}

// Options is the configuration structure for oauth and backend provider
// key is the providername, value is a options map.
type Options map[string]map[string]string

// addOauthOpts adds the options for a provider in the form of key=value,key=value,..
func (c *Config) addOauthOpts(providerName, optsKvList string) error {
	opts, err := parseOptions(optsKvList)
	if err != nil {
		return err
	}

	c.Oauth[providerName] = opts
	return nil
}

// addBackendOpts adds the options for a provider in the form of key=value,key=value,..
func (c *Config) addBackendOpts(providerName, optsKvList string) error {
	opts, err := parseOptions(optsKvList)
	if err != nil {
		return err
	}

	c.Backends[providerName] = opts
	return nil
}

// ResolveFileReferences resolves configuration values, which are dynamically referenced via files
func (c *Config) ResolveFileReferences() error {
	// Try to load the secret from a file, if set
	if c.JwtSecretFile != "" {
		secretBytes, err := ioutil.ReadFile(c.JwtSecretFile)
		if err != nil {
			return err
		}

		c.JwtSecret = string(secretBytes)
	}

	return nil
}

// ConfigureFlagSet adds all flags to the supplied flag set
func (c *Config) ConfigureFlagSet(f *flag.FlagSet) {
	f.StringVar(&c.Host, "host", c.Host, "The host to listen on")
	f.StringVar(&c.Port, "port", c.Port, "The port to listen on")
	f.StringVar(&c.LogLevel, "log-level", c.LogLevel, "The log level")
	f.BoolVar(&c.TextLogging, "text-logging", c.TextLogging, "Log in text format instead of json")
	f.StringVar(&c.JwtSecret, "jwt-secret", c.JwtSecret, "The secret to sign the jwt token")
	f.StringVar(&c.JwtSecretFile, "jwt-secret-file", c.JwtSecretFile, "Path to a file containing the secret to sign the jwt token (overrides jwt-secret)")
	f.StringVar(&c.JwtAlgo, "jwt-algo", c.JwtAlgo, "The singing algorithm to use (ES256, ES384, ES512, RS256, RS384, RS512, HS256, HS384, HS512")
	f.DurationVar(&c.JwtExpiry, "jwt-expiry", c.JwtExpiry, "The expiry duration for the jwt token, e.g. 2h or 3h30m")
	f.IntVar(&c.JwtRefreshes, "jwt-refreshes", c.JwtRefreshes, "The maximum amount of jwt refreshes. 0 by Default")
	f.StringVar(&c.CookieName, "cookie-name", c.CookieName, "The name of the jwt cookie")
	f.BoolVar(&c.CookieHTTPOnly, "cookie-http-only", c.CookieHTTPOnly, "Set the cookie with the http only flag")
	f.BoolVar(&c.CookieSecure, "cookie-secure", c.CookieSecure, "Set the cookie with the secure flag")
	f.DurationVar(&c.CookieExpiry, "cookie-expiry", c.CookieExpiry, "The expiry duration for the cookie, e.g. 2h or 3h30m. Default is browser session")
	f.StringVar(&c.CookieDomain, "cookie-domain", c.CookieDomain, "The optional domain parameter for the cookie")
	f.StringVar(&c.SuccessURL, "success-url", c.SuccessURL, "The url to redirect after login")
	f.BoolVar(&c.Redirect, "redirect", c.Redirect, "Allow dynamic overwriting of the the success by query parameter")
	f.StringVar(&c.RedirectQueryParameter, "redirect-query-parameter", c.RedirectQueryParameter, "URL parameter for the redirect target")
	f.BoolVar(&c.RedirectCheckReferer, "redirect-check-referer", c.RedirectCheckReferer, "When redirecting check that the referer is the same domain")
	f.StringVar(&c.RedirectHostFile, "redirect-host-file", c.RedirectHostFile, "A file containing a list of domains that redirects are allowed to, one domain per line")

	f.StringVar(&c.LogoutURL, "logout-url", c.LogoutURL, "The url or path to redirect after logout")
	f.StringVar(&c.Template, "template", c.Template, "An alternative template for the login form")
	f.StringVar(&c.LoginPath, "login-path", c.LoginPath, "The path of the login resource")
	f.DurationVar(&c.GracePeriod, "grace-period", c.GracePeriod, "Graceful shutdown grace period")
	f.StringVar(&c.UserFile, "user-file", c.UserFile, "A YAML file with user specific data for the tokens")
	f.StringVar(&c.UserEndpoint, "user-endpoint", c.UserEndpoint, "URL of an endpoint providing user specific data for the tokens")
	f.StringVar(&c.UserEndpointToken, "user-endpoint-token", c.UserEndpointToken, "Authentication token used when communicating with the user endpoint")
	f.DurationVar(&c.UserEndpointTimeout, "user-endpoint-timeout", c.UserEndpointTimeout, "Timeout used when communicating with the user endpoint")

	// the -backends is deprecated, but we support it for backwards compatibility
	deprecatedBackends := setFunc(func(optsKvList string) error {
		logging.Logger.Warn("DEPRECATED: '-backend' is no longer supported. Please set the backends by explicit parameters")
		opts, err := parseOptions(optsKvList)
		if err != nil {
			return err
		}
		pName, ok := opts["provider"]
		if !ok {
			return errors.New("missing provider name provider=...")
		}
		delete(opts, "provider")
		c.Backends[pName] = opts
		return nil
	})
	f.Var(deprecatedBackends, "backend", "Deprecated, please use the explicit flags")

	// One option for each oauth provider
	for _, pName := range oauth2.ProviderList() {
		func(pName string) {
			setter := setFunc(func(optsKvList string) error {
				return c.addOauthOpts(pName, optsKvList)
			})
			f.Var(setter, pName, "Oauth config in the form: client_id=..,client_secret=..[,scope=..,][redirect_uri=..]")
		}(pName)
	}

	// One option for each backend provider
	for _, pName := range ProviderList() {
		func(pName string) {
			setter := setFunc(func(optsKvList string) error {
				return c.addBackendOpts(pName, optsKvList)
			})
			desc, _ := GetProviderDescription(pName)
			f.Var(setter, pName, desc.HelpText)
		}(pName)
	}
}

// ReadConfig from the commandline args
func ReadConfig() *Config {
	c, err := readConfig(flag.CommandLine, os.Args[1:])
	if err != nil {
		// should never happen, because of flag default policy ExitOnError
		panic(err)
	}
	return c
}

func readConfig(f *flag.FlagSet, args []string) (*Config, error) {
	config := DefaultConfig()
	config.ConfigureFlagSet(f)

	// fist use the environment settings
	f.VisitAll(func(f *flag.Flag) {
		if val, isPresent := os.LookupEnv(envName(f.Name)); isPresent {
			f.Value.Set(val)
		}
	})

	// prefer flags over environment settings
	err := f.Parse(args)
	if err != nil {
		return nil, err
	}

	if err := config.ResolveFileReferences(); err != nil {
		return nil, err
	}

	return config, err
}

func randStringBytes(n int) (string, error) {
	b := make([]byte, n)
	_, err := rand.Read(b)
	if err != nil {
		return "", err
	}
	return hex.EncodeToString(b), nil
}

func envName(flagName string) string {
	return envPrefix + strings.Replace(strings.ToUpper(flagName), "-", "_", -1)
}

func parseOptions(b string) (map[string]string, error) {
	opts := map[string]string{}
	pairs := strings.Split(b, ",")
	for _, p := range pairs {
		pair := strings.SplitN(p, "=", 2)
		if len(pair) != 2 {
			return nil, fmt.Errorf("provider configuration has to be in form 'key1=value1,key2=..', but was %v", p)
		}
		opts[pair[0]] = pair[1]
	}
	return opts, nil
}

// Helper type to wrap a function closure with the Value interface
type setFunc func(optsKvList string) error

func (f setFunc) Set(value string) error {
	return f(value)
}

func (f setFunc) String() string {
	return "setFunc"
}
<commit_msg>Use 64 byte to generate the jwt default secret<commit_after>package login

import (
	"crypto/rand"
	"encoding/hex"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"strings"
	"time"

	"github.com/tarent/loginsrv/logging"
	"github.com/tarent/loginsrv/oauth2"
)

var jwtDefaultSecret string

func init() {
	var err error
	jwtDefaultSecret, err = randStringBytes(64)
	if err != nil {
		panic(err)
	}
}

// DefaultConfig for the loginsrv handler
func DefaultConfig() *Config {
	return &Config{
		Host:                   "localhost",
		Port:                   "6789",
		LogLevel:               "info",
		JwtSecret:              jwtDefaultSecret,
		JwtAlgo:                "HS512",
		JwtExpiry:              24 * time.Hour,
		JwtRefreshes:           0,
		SuccessURL:             "/",
		Redirect:               true,
		RedirectQueryParameter: "backTo",
		RedirectCheckReferer:   true,
		RedirectHostFile:       "",
		LogoutURL:              "",
		LoginPath:              "/login",
		CookieName:             "jwt_token",
		CookieHTTPOnly:         true,
		CookieSecure:           true,
		Backends:               Options{},
		Oauth:                  Options{},
		GracePeriod:            5 * time.Second,
		UserFile:               "",
		UserEndpoint:           "",
		UserEndpointToken:      "",
		UserEndpointTimeout:    5 * time.Second,
	}
}

const envPrefix = "LOGINSRV_"

// Config for the loginsrv handler
type Config struct {
	Host                   string
	Port                   string
	LogLevel               string
	TextLogging            bool
	JwtSecret              string
	JwtSecretFile          string
	JwtAlgo                string
	JwtExpiry              time.Duration
	JwtRefreshes           int
	SuccessURL             string
	Redirect               bool
	RedirectQueryParameter string
	RedirectCheckReferer   bool
	RedirectHostFile       string
	LogoutURL              string
	Template               string
	LoginPath              string
	CookieName             string
	CookieExpiry           time.Duration
	CookieDomain           string
	CookieHTTPOnly         bool
	CookieSecure           bool
	Backends               Options
	Oauth                  Options
	GracePeriod            time.Duration
	UserFile               string
	UserEndpoint           string
	UserEndpointToken      string
	UserEndpointTimeout    time.Duration
}

// Options is the configuration structure for oauth and backend provider
// key is the providername, value is a options map.
type Options map[string]map[string]string

// addOauthOpts adds the options for a provider in the form of key=value,key=value,..
func (c *Config) addOauthOpts(providerName, optsKvList string) error {
	opts, err := parseOptions(optsKvList)
	if err != nil {
		return err
	}

	c.Oauth[providerName] = opts
	return nil
}

// addBackendOpts adds the options for a provider in the form of key=value,key=value,..
func (c *Config) addBackendOpts(providerName, optsKvList string) error {
	opts, err := parseOptions(optsKvList)
	if err != nil {
		return err
	}

	c.Backends[providerName] = opts
	return nil
}

// ResolveFileReferences resolves configuration values, which are dynamically referenced via files
func (c *Config) ResolveFileReferences() error {
	// Try to load the secret from a file, if set
	if c.JwtSecretFile != "" {
		secretBytes, err := ioutil.ReadFile(c.JwtSecretFile)
		if err != nil {
			return err
		}

		c.JwtSecret = string(secretBytes)
	}

	return nil
}

// ConfigureFlagSet adds all flags to the supplied flag set
func (c *Config) ConfigureFlagSet(f *flag.FlagSet) {
	f.StringVar(&c.Host, "host", c.Host, "The host to listen on")
	f.StringVar(&c.Port, "port", c.Port, "The port to listen on")
	f.StringVar(&c.LogLevel, "log-level", c.LogLevel, "The log level")
	f.BoolVar(&c.TextLogging, "text-logging", c.TextLogging, "Log in text format instead of json")
	f.StringVar(&c.JwtSecret, "jwt-secret", c.JwtSecret, "The secret to sign the jwt token")
	f.StringVar(&c.JwtSecretFile, "jwt-secret-file", c.JwtSecretFile, "Path to a file containing the secret to sign the jwt token (overrides jwt-secret)")
	f.StringVar(&c.JwtAlgo, "jwt-algo", c.JwtAlgo, "The singing algorithm to use (ES256, ES384, ES512, RS256, RS384, RS512, HS256, HS384, HS512")
	f.DurationVar(&c.JwtExpiry, "jwt-expiry", c.JwtExpiry, "The expiry duration for the jwt token, e.g. 2h or 3h30m")
	f.IntVar(&c.JwtRefreshes, "jwt-refreshes", c.JwtRefreshes, "The maximum amount of jwt refreshes. 0 by Default")
	f.StringVar(&c.CookieName, "cookie-name", c.CookieName, "The name of the jwt cookie")
	f.BoolVar(&c.CookieHTTPOnly, "cookie-http-only", c.CookieHTTPOnly, "Set the cookie with the http only flag")
	f.BoolVar(&c.CookieSecure, "cookie-secure", c.CookieSecure, "Set the cookie with the secure flag")
	f.DurationVar(&c.CookieExpiry, "cookie-expiry", c.CookieExpiry, "The expiry duration for the cookie, e.g. 2h or 3h30m. Default is browser session")
	f.StringVar(&c.CookieDomain, "cookie-domain", c.CookieDomain, "The optional domain parameter for the cookie")
	f.StringVar(&c.SuccessURL, "success-url", c.SuccessURL, "The url to redirect after login")
	f.BoolVar(&c.Redirect, "redirect", c.Redirect, "Allow dynamic overwriting of the the success by query parameter")
	f.StringVar(&c.RedirectQueryParameter, "redirect-query-parameter", c.RedirectQueryParameter, "URL parameter for the redirect target")
	f.BoolVar(&c.RedirectCheckReferer, "redirect-check-referer", c.RedirectCheckReferer, "When redirecting check that the referer is the same domain")
	f.StringVar(&c.RedirectHostFile, "redirect-host-file", c.RedirectHostFile, "A file containing a list of domains that redirects are allowed to, one domain per line")

	f.StringVar(&c.LogoutURL, "logout-url", c.LogoutURL, "The url or path to redirect after logout")
	f.StringVar(&c.Template, "template", c.Template, "An alternative template for the login form")
	f.StringVar(&c.LoginPath, "login-path", c.LoginPath, "The path of the login resource")
	f.DurationVar(&c.GracePeriod, "grace-period", c.GracePeriod, "Graceful shutdown grace period")
	f.StringVar(&c.UserFile, "user-file", c.UserFile, "A YAML file with user specific data for the tokens")
	f.StringVar(&c.UserEndpoint, "user-endpoint", c.UserEndpoint, "URL of an endpoint providing user specific data for the tokens")
	f.StringVar(&c.UserEndpointToken, "user-endpoint-token", c.UserEndpointToken, "Authentication token used when communicating with the user endpoint")
	f.DurationVar(&c.UserEndpointTimeout, "user-endpoint-timeout", c.UserEndpointTimeout, "Timeout used when communicating with the user endpoint")

	// the -backends is deprecated, but we support it for backwards compatibility
	deprecatedBackends := setFunc(func(optsKvList string) error {
		logging.Logger.Warn("DEPRECATED: '-backend' is no longer supported. Please set the backends by explicit parameters")
		opts, err := parseOptions(optsKvList)
		if err != nil {
			return err
		}
		pName, ok := opts["provider"]
		if !ok {
			return errors.New("missing provider name provider=...")
		}
		delete(opts, "provider")
		c.Backends[pName] = opts
		return nil
	})
	f.Var(deprecatedBackends, "backend", "Deprecated, please use the explicit flags")

	// One option for each oauth provider
	for _, pName := range oauth2.ProviderList() {
		func(pName string) {
			setter := setFunc(func(optsKvList string) error {
				return c.addOauthOpts(pName, optsKvList)
			})
			f.Var(setter, pName, "Oauth config in the form: client_id=..,client_secret=..[,scope=..,][redirect_uri=..]")
		}(pName)
	}

	// One option for each backend provider
	for _, pName := range ProviderList() {
		func(pName string) {
			setter := setFunc(func(optsKvList string) error {
				return c.addBackendOpts(pName, optsKvList)
			})
			desc, _ := GetProviderDescription(pName)
			f.Var(setter, pName, desc.HelpText)
		}(pName)
	}
}

// ReadConfig from the commandline args
func ReadConfig() *Config {
	c, err := readConfig(flag.CommandLine, os.Args[1:])
	if err != nil {
		// should never happen, because of flag default policy ExitOnError
		panic(err)
	}
	return c
}

func readConfig(f *flag.FlagSet, args []string) (*Config, error) {
	config := DefaultConfig()
	config.ConfigureFlagSet(f)

	// fist use the environment settings
	f.VisitAll(func(f *flag.Flag) {
		if val, isPresent := os.LookupEnv(envName(f.Name)); isPresent {
			f.Value.Set(val)
		}
	})

	// prefer flags over environment settings
	err := f.Parse(args)
	if err != nil {
		return nil, err
	}

	if err := config.ResolveFileReferences(); err != nil {
		return nil, err
	}

	return config, err
}

func randStringBytes(n int) (string, error) {
	b := make([]byte, n)
	_, err := rand.Read(b)
	if err != nil {
		return "", err
	}
	return hex.EncodeToString(b), nil
}

func envName(flagName string) string {
	return envPrefix + strings.Replace(strings.ToUpper(flagName), "-", "_", -1)
}

func parseOptions(b string) (map[string]string, error) {
	opts := map[string]string{}
	pairs := strings.Split(b, ",")
	for _, p := range pairs {
		pair := strings.SplitN(p, "=", 2)
		if len(pair) != 2 {
			return nil, fmt.Errorf("provider configuration has to be in form 'key1=value1,key2=..', but was %v", p)
		}
		opts[pair[0]] = pair[1]
	}
	return opts, nil
}

// Helper type to wrap a function closure with the Value interface
type setFunc func(optsKvList string) error

func (f setFunc) Set(value string) error {
	return f(value)
}

func (f setFunc) String() string {
	return "setFunc"
}
<|endoftext|>