<commit_before>// Copyright 2019, 2021 Tamás Gulácsi
//
// SPDX-License-Identifier: Apache-2.0

package custom

import (
	"bufio"
	"bytes"
	"database/sql/driver"
	"encoding"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"reflect"
	"strings"
	"time"
	"unsafe"

	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/reflect/protoreflect"
	"google.golang.org/protobuf/types/known/timestamppb"
)

var (
	_ = json.Marshaler((*DateTime)(nil))
	_ = json.Unmarshaler((*DateTime)(nil))
	_ = encoding.TextMarshaler((*DateTime)(nil))
	_ = encoding.TextUnmarshaler((*DateTime)(nil))
	_ = xml.Marshaler((*DateTime)(nil))
	_ = xml.Unmarshaler((*DateTime)(nil))
	_ = proto.Message((*DateTime)(nil))
)

type DateTime struct {
	Time time.Time
}

func getWriter(enc *xml.Encoder) *bufio.Writer {
	rEnc := reflect.ValueOf(enc)
	rP := rEnc.Elem().FieldByName("p").Addr()
	return *(**bufio.Writer)(unsafe.Pointer(rP.Elem().FieldByName("Writer").UnsafeAddr()))
}

func (dt *DateTime) Format(layout string) string {
	if dt == nil {
		return ""
	}
	return dt.Time.Format(layout)
}
func (dt *DateTime) AppendFormat(b []byte, layout string) []byte {
	if dt == nil {
		return nil
	}
	return dt.Time.AppendFormat(b, layout)
}
func (dt *DateTime) Scan(src interface{}) error {
	if src == nil {
		dt.Time = time.Time{}
		return nil
	}
	t, ok := src.(time.Time)
	if !ok {
		return fmt.Errorf("cannot scan %T to DateTime", src)
	}
	dt.Time = t
	return nil
}
func (dt *DateTime) Value() (driver.Value, error) {
	if dt == nil {
		return nil, nil
	}
	return dt.Time, nil
}

func (dt *DateTime) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
	if dt != nil && !dt.IsZero() {
		return enc.EncodeElement(dt.Time.In(time.Local).Format(time.RFC3339), start)
	}
	start.Attr = append(start.Attr,
		xml.Attr{Name: xml.Name{Space: "http://www.w3.org/2001/XMLSchema-instance", Local: "nil"}, Value: "true"})

	bw := getWriter(enc)
	bw.Flush()
	old := *bw
	var buf bytes.Buffer
	*bw = *bufio.NewWriter(&buf)
	if err := enc.EncodeElement("", start); err != nil {
		return err
	}
	b := bytes.ReplaceAll(bytes.ReplaceAll(bytes.ReplaceAll(bytes.ReplaceAll(
		buf.Bytes(),
		[]byte("_XMLSchema-instance:"), []byte("xsi:")),
		[]byte("xmlns:_XMLSchema-instance="), []byte("xmlns:xsi=")),
		[]byte("XMLSchema-instance:"), []byte("xsi:")),
		[]byte("xmlns:XMLSchema-instance="), []byte("xmlns:xsi="))
	*bw = old
	bw.Write(b)
	return bw.Flush()
}
func (dt *DateTime) UnmarshalXML(dec *xml.Decoder, st xml.StartElement) error {
	var s string
	if err := dec.DecodeElement(&s, &st); err != nil {
		return err
	}
	return dt.UnmarshalText([]byte(s))
}

func (dt *DateTime) IsZero() (zero bool) {
	//defer func() { log.Printf("IsZero(%#v): %t", dt, zero) }()
	if dt == nil {
		return true
	}
	defer func() {
		if r := recover(); r != nil {
			zero = true
		}
	}()
	return dt.Time.IsZero()
}
func (dt *DateTime) MarshalJSON() ([]byte, error) {
	if dt == nil || dt.IsZero() {
		return []byte(`""`), nil
	}
	return dt.Time.In(time.Local).MarshalJSON()
}
func (dt *DateTime) UnmarshalJSON(data []byte) error {
	// Ignore null, like in the main JSON package.
	data = bytes.TrimSpace(data)
	if len(data) == 0 || bytes.Equal(data, []byte(`""`)) || bytes.Equal(data, []byte("null")) {
		dt.Time = time.Time{}
		return nil
	}
	return dt.UnmarshalText(data)
}

// MarshalText implements the encoding.TextMarshaler interface.
// The time is formatted in RFC 3339 format, with sub-second precision added if present.
func (dt *DateTime) MarshalText() ([]byte, error) {
	if dt == nil || dt.IsZero() {
		return nil, nil
	}
	return dt.Time.In(time.Local).MarshalText()
}

// UnmarshalText implements the encoding.TextUnmarshaler interface.
// The time is expected to be in RFC 3339 format.
func (dt *DateTime) UnmarshalText(data []byte) error {
	data = bytes.Trim(data, " \"")
	n := len(data)
	if n == 0 {
		dt.Time = time.Time{}
		//log.Println("time=")
		return nil
	}
	layout := time.RFC3339
	if bytes.IndexByte(data, '.') >= 19 {
		layout = time.RFC3339Nano
	}
	if n < 10 {
		layout = "20060102"
	} else {
		if n > len(layout) {
			data = data[:len(layout)]
		} else if n < 4 {
			layout = layout[:4]
		} else {
			for _, i := range []int{4, 7, 10} {
				if n <= i {
					break
				}
				if data[i] != layout[i] {
					data[i] = layout[i]
				}
			}
			if bytes.IndexByte(data, '.') < 0 {
				layout = layout[:n]
			} else if _, err := time.ParseInLocation(layout, string(data), time.Local); err != nil && strings.HasSuffix(err.Error(), `"" as "Z07:00"`) {
				layout = strings.TrimSuffix(layout, "Z07:00")
			}
		}
	}
	var err error
	// Fractional seconds are handled implicitly by Parse.
	dt.Time, err = time.ParseInLocation(layout, string(data), time.Local)
	//log.Printf("s=%q time=%v err=%+v", data, dt.Time, err)
	if err != nil {
		return fmt.Errorf("ParseInLocation(%q, %q): %w", layout, string(data), err)
	}
	return nil
}

func (dt *DateTime) Timestamp() *timestamppb.Timestamp {
	if dt.IsZero() {
		return nil
	}
	return timestamppb.New(dt.Time)
}
func (dt *DateTime) MarshalTo(dAtA []byte) (int, error) {
	if dt.IsZero() {
		return 0, nil
	}
	b, err := proto.MarshalOptions{}.MarshalAppend(dAtA[:0], dt.Timestamp())
	_ = dAtA[len(b)-1] // panic if buffer is too short
	return len(b), err
}
func (dt *DateTime) Marshal() (dAtA []byte, err error) {
	if dt.IsZero() {
		return nil, nil
	}
	return proto.Marshal(dt.Timestamp())
}
func (dt *DateTime) String() string {
	if dt.IsZero() {
		return ""
	}
	return dt.Time.In(time.Local).Format(time.RFC3339)
}

func (dt *DateTime) ProtoMessage() {}

func (dt *DateTime) ProtoSize() (n int) {
	if dt.IsZero() {
		return 0
	}
	return proto.Size(dt.Timestamp())
}
func (dt *DateTime) Reset() {
	if dt != nil {
		dt.Time = time.Time{}
	}
}
func (dt *DateTime) Size() (n int) {
	if dt.IsZero() {
		return 0
	}
	return proto.Size(dt.Timestamp())
}
func (dt *DateTime) Unmarshal(dAtA []byte) error {
	var ts timestamppb.Timestamp
	if err := proto.Unmarshal(dAtA, &ts); err != nil {
		return err
	}
	if ts.Seconds == 0 && ts.Nanos == 0 {
		dt.Time = time.Time{}
	} else {
		dt.Time = ts.AsTime()
	}
	return nil
}

func (dt *DateTime) ProtoReflect() protoreflect.Message {
	return dt.Timestamp().ProtoReflect()
}
<commit_msg>custom.DateTime embed Time<commit_after>// Copyright 2019, 2021 Tamás Gulácsi
//
// SPDX-License-Identifier: Apache-2.0

package custom

import (
	"bufio"
	"bytes"
	"database/sql/driver"
	"encoding"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"reflect"
	"strings"
	"time"
	"unsafe"

	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/reflect/protoreflect"
	"google.golang.org/protobuf/types/known/timestamppb"
)

var (
	_ = json.Marshaler((*DateTime)(nil))
	_ = json.Unmarshaler((*DateTime)(nil))
	_ = encoding.TextMarshaler((*DateTime)(nil))
	_ = encoding.TextUnmarshaler((*DateTime)(nil))
	_ = xml.Marshaler((*DateTime)(nil))
	_ = xml.Unmarshaler((*DateTime)(nil))
	_ = proto.Message((*DateTime)(nil))
)

type DateTime struct {
	time.Time
}

func getWriter(enc *xml.Encoder) *bufio.Writer {
	rEnc := reflect.ValueOf(enc)
	rP := rEnc.Elem().FieldByName("p").Addr()
	return *(**bufio.Writer)(unsafe.Pointer(rP.Elem().FieldByName("Writer").UnsafeAddr()))
}

func (dt *DateTime) Format(layout string) string {
	if dt == nil {
		return ""
	}
	return dt.Time.Format(layout)
}
func (dt *DateTime) AppendFormat(b []byte, layout string) []byte {
	if dt == nil {
		return nil
	}
	return dt.Time.AppendFormat(b, layout)
}
func (dt *DateTime) Scan(src interface{}) error {
	if src == nil {
		dt.Time = time.Time{}
		return nil
	}
	t, ok := src.(time.Time)
	if !ok {
		return fmt.Errorf("cannot scan %T to DateTime", src)
	}
	dt.Time = t
	return nil
}
func (dt *DateTime) Value() (driver.Value, error) {
	if dt == nil {
		return nil, nil
	}
	return dt.Time, nil
}

func (dt *DateTime) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
	if dt != nil && !dt.IsZero() {
		return enc.EncodeElement(dt.Time.In(time.Local).Format(time.RFC3339), start)
	}
	start.Attr = append(start.Attr,
		xml.Attr{Name: xml.Name{Space: "http://www.w3.org/2001/XMLSchema-instance", Local: "nil"}, Value: "true"})

	bw := getWriter(enc)
	bw.Flush()
	old := *bw
	var buf bytes.Buffer
	*bw = *bufio.NewWriter(&buf)
	if err := enc.EncodeElement("", start); err != nil {
		return err
	}
	b := bytes.ReplaceAll(bytes.ReplaceAll(bytes.ReplaceAll(bytes.ReplaceAll(
		buf.Bytes(),
		[]byte("_XMLSchema-instance:"), []byte("xsi:")),
		[]byte("xmlns:_XMLSchema-instance="), []byte("xmlns:xsi=")),
		[]byte("XMLSchema-instance:"), []byte("xsi:")),
		[]byte("xmlns:XMLSchema-instance="), []byte("xmlns:xsi="))
	*bw = old
	bw.Write(b)
	return bw.Flush()
}
func (dt *DateTime) UnmarshalXML(dec *xml.Decoder, st xml.StartElement) error {
	var s string
	if err := dec.DecodeElement(&s, &st); err != nil {
		return err
	}
	return dt.UnmarshalText([]byte(s))
}

func (dt *DateTime) IsZero() (zero bool) {
	//defer func() { log.Printf("IsZero(%#v): %t", dt, zero) }()
	if dt == nil {
		return true
	}
	defer func() {
		if r := recover(); r != nil {
			zero = true
		}
	}()
	return dt.Time.IsZero()
}
func (dt *DateTime) MarshalJSON() ([]byte, error) {
	if dt == nil || dt.IsZero() {
		return []byte(`""`), nil
	}
	return dt.Time.In(time.Local).MarshalJSON()
}
func (dt *DateTime) UnmarshalJSON(data []byte) error {
	// Ignore null, like in the main JSON package.
	data = bytes.TrimSpace(data)
	if len(data) == 0 || bytes.Equal(data, []byte(`""`)) || bytes.Equal(data, []byte("null")) {
		dt.Time = time.Time{}
		return nil
	}
	return dt.UnmarshalText(data)
}

// MarshalText implements the encoding.TextMarshaler interface.
// The time is formatted in RFC 3339 format, with sub-second precision added if present.
func (dt *DateTime) MarshalText() ([]byte, error) {
	if dt == nil || dt.IsZero() {
		return nil, nil
	}
	return dt.Time.In(time.Local).MarshalText()
}

// UnmarshalText implements the encoding.TextUnmarshaler interface.
// The time is expected to be in RFC 3339 format.
func (dt *DateTime) UnmarshalText(data []byte) error {
	data = bytes.Trim(data, " \"")
	n := len(data)
	if n == 0 {
		dt.Time = time.Time{}
		//log.Println("time=")
		return nil
	}
	layout := time.RFC3339
	if bytes.IndexByte(data, '.') >= 19 {
		layout = time.RFC3339Nano
	}
	if n < 10 {
		layout = "20060102"
	} else {
		if n > len(layout) {
			data = data[:len(layout)]
		} else if n < 4 {
			layout = layout[:4]
		} else {
			for _, i := range []int{4, 7, 10} {
				if n <= i {
					break
				}
				if data[i] != layout[i] {
					data[i] = layout[i]
				}
			}
			if bytes.IndexByte(data, '.') < 0 {
				layout = layout[:n]
			} else if _, err := time.ParseInLocation(layout, string(data), time.Local); err != nil && strings.HasSuffix(err.Error(), `"" as "Z07:00"`) {
				layout = strings.TrimSuffix(layout, "Z07:00")
			}
		}
	}
	var err error
	// Fractional seconds are handled implicitly by Parse.
	dt.Time, err = time.ParseInLocation(layout, string(data), time.Local)
	//log.Printf("s=%q time=%v err=%+v", data, dt.Time, err)
	if err != nil {
		return fmt.Errorf("ParseInLocation(%q, %q): %w", layout, string(data), err)
	}
	return nil
}

func (dt *DateTime) Timestamp() *timestamppb.Timestamp {
	if dt.IsZero() {
		return nil
	}
	return timestamppb.New(dt.Time)
}
func (dt *DateTime) MarshalTo(dAtA []byte) (int, error) {
	if dt.IsZero() {
		return 0, nil
	}
	b, err := proto.MarshalOptions{}.MarshalAppend(dAtA[:0], dt.Timestamp())
	_ = dAtA[len(b)-1] // panic if buffer is too short
	return len(b), err
}
func (dt *DateTime) Marshal() (dAtA []byte, err error) {
	if dt.IsZero() {
		return nil, nil
	}
	return proto.Marshal(dt.Timestamp())
}
func (dt *DateTime) String() string {
	if dt.IsZero() {
		return ""
	}
	return dt.Time.In(time.Local).Format(time.RFC3339)
}

func (dt *DateTime) ProtoMessage() {}

func (dt *DateTime) ProtoSize() (n int) {
	if dt.IsZero() {
		return 0
	}
	return proto.Size(dt.Timestamp())
}
func (dt *DateTime) Reset() {
	if dt != nil {
		dt.Time = time.Time{}
	}
}
func (dt *DateTime) Size() (n int) {
	if dt.IsZero() {
		return 0
	}
	return proto.Size(dt.Timestamp())
}
func (dt *DateTime) Unmarshal(dAtA []byte) error {
	var ts timestamppb.Timestamp
	if err := proto.Unmarshal(dAtA, &ts); err != nil {
		return err
	}
	if ts.Seconds == 0 && ts.Nanos == 0 {
		dt.Time = time.Time{}
	} else {
		dt.Time = ts.AsTime()
	}
	return nil
}

func (dt *DateTime) ProtoReflect() protoreflect.Message {
	return dt.Timestamp().ProtoReflect()
}
<|endoftext|>


<commit_before>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package websocket

import (
	"bufio"
	"bytes"
	"fmt"
	"http"
	"http/httptest"
	"io"
	"log"
	"net"
	"sync"
	"testing"
)

var serverAddr string
var once sync.Once

func echoServer(ws *Conn) { io.Copy(ws, ws) }

func startServer() {
	http.Handle("/echo", Handler(echoServer))
	http.Handle("/echoDraft75", Draft75Handler(echoServer))
	server := httptest.NewServer(nil)
	serverAddr = server.Listener.Addr().String()
	log.Print("Test WebSocket server listening on ", serverAddr)
}

// Test the getChallengeResponse function with values from section
// 5.1 of the specification steps 18, 26, and 43 from
// http://www.whatwg.org/specs/web-socket-protocol/
func TestChallenge(t *testing.T) {
	var part1 uint32 = 777007543
	var part2 uint32 = 114997259
	key3 := []byte{0x47, 0x30, 0x22, 0x2D, 0x5A, 0x3F, 0x47, 0x58}
	expected := []byte("0st3Rl&q-2ZU^weu")

	response, err := getChallengeResponse(part1, part2, key3)
	if err != nil {
		t.Errorf("getChallengeResponse: returned error %v", err)
		return
	}
	if !bytes.Equal(expected, response) {
		t.Errorf("getChallengeResponse: expected %q got %q", expected, response)
	}
}

func TestEcho(t *testing.T) {
	once.Do(startServer)

	// websocket.Dial()
	client, err := net.Dial("tcp", serverAddr)
	if err != nil {
		t.Fatal("dialing", err)
	}
	ws, err := newClient("/echo", "localhost", "http://localhost",
		"ws://localhost/echo", "", client, handshake)
	if err != nil {
		t.Errorf("WebSocket handshake error: %v", err)
		return
	}

	msg := []byte("hello, world\n")
	if _, err := ws.Write(msg); err != nil {
		t.Errorf("Write: %v", err)
	}
	var actual_msg = make([]byte, 512)
	n, err := ws.Read(actual_msg)
	if err != nil {
		t.Errorf("Read: %v", err)
	}
	actual_msg = actual_msg[0:n]
	if !bytes.Equal(msg, actual_msg) {
		t.Errorf("Echo: expected %q got %q", msg, actual_msg)
	}
	ws.Close()
}

func TestEchoDraft75(t *testing.T) {
	once.Do(startServer)

	// websocket.Dial()
	client, err := net.Dial("tcp", serverAddr)
	if err != nil {
		t.Fatal("dialing", err)
	}
	ws, err := newClient("/echoDraft75", "localhost", "http://localhost",
		"ws://localhost/echoDraft75", "", client, draft75handshake)
	if err != nil {
		t.Errorf("WebSocket handshake: %v", err)
		return
	}

	msg := []byte("hello, world\n")
	if _, err := ws.Write(msg); err != nil {
		t.Errorf("Write: error %v", err)
	}
	var actual_msg = make([]byte, 512)
	n, err := ws.Read(actual_msg)
	if err != nil {
		t.Errorf("Read: error %v", err)
	}
	actual_msg = actual_msg[0:n]
	if !bytes.Equal(msg, actual_msg) {
		t.Errorf("Echo: expected %q got %q", msg, actual_msg)
	}
	ws.Close()
}

func TestWithQuery(t *testing.T) {
	once.Do(startServer)

	client, err := net.Dial("tcp", serverAddr)
	if err != nil {
		t.Fatal("dialing", err)
	}

	ws, err := newClient("/echo?q=v", "localhost", "http://localhost",
		"ws://localhost/echo?q=v", "", client, handshake)
	if err != nil {
		t.Errorf("WebSocket handshake: %v", err)
		return
	}
	ws.Close()
}

func TestWithProtocol(t *testing.T) {
	once.Do(startServer)

	client, err := net.Dial("tcp", serverAddr)
	if err != nil {
		t.Fatal("dialing", err)
	}

	ws, err := newClient("/echo", "localhost", "http://localhost",
		"ws://localhost/echo", "test", client, handshake)
	if err != nil {
		t.Errorf("WebSocket handshake: %v", err)
		return
	}
	ws.Close()
}

func TestHTTP(t *testing.T) {
	once.Do(startServer)

	// If the client did not send a handshake that matches the protocol
	// specification, the server should abort the WebSocket connection.
	_, _, err := http.Get(fmt.Sprintf("http://%s/echo", serverAddr))
	if err == nil {
		t.Error("Get: unexpected success")
		return
	}
	urlerr, ok := err.(*http.URLError)
	if !ok {
		t.Errorf("Get: not URLError %#v", err)
		return
	}
	if urlerr.Error != io.ErrUnexpectedEOF {
		t.Errorf("Get: error %#v", err)
		return
	}
}

func TestHTTPDraft75(t *testing.T) {
	once.Do(startServer)

	r, _, err := http.Get(fmt.Sprintf("http://%s/echoDraft75", serverAddr))
	if err != nil {
		t.Errorf("Get: error %#v", err)
		return
	}
	if r.StatusCode != http.StatusBadRequest {
		t.Errorf("Get: got status %d", r.StatusCode)
	}
}

func TestTrailingSpaces(t *testing.T) {
	// http://code.google.com/p/go/issues/detail?id=955
	// The last runs of this create keys with trailing spaces that should not be
	// generated by the client.
	once.Do(startServer)
	for i := 0; i < 30; i++ {
		// body
		_, err := Dial(fmt.Sprintf("ws://%s/echo", serverAddr), "",
			"http://localhost/")
		if err != nil {
			panic("Dial failed: " + err.String())
		}
	}
}

func TestSmallBuffer(t *testing.T) {
	// http://code.google.com/p/go/issues/detail?id=1145
	// Read should be able to handle reading a fragment of a frame.
	once.Do(startServer)

	// websocket.Dial()
	client, err := net.Dial("tcp", serverAddr)
	if err != nil {
		t.Fatal("dialing", err)
	}
	ws, err := newClient("/echo", "localhost", "http://localhost",
		"ws://localhost/echo", "", client, handshake)
	if err != nil {
		t.Errorf("WebSocket handshake error: %v", err)
		return
	}

	msg := []byte("hello, world\n")
	if _, err := ws.Write(msg); err != nil {
		t.Errorf("Write: %v", err)
	}
	var small_msg = make([]byte, 8)
	n, err := ws.Read(small_msg)
	if err != nil {
		t.Errorf("Read: %v", err)
	}
	if !bytes.Equal(msg[:len(small_msg)], small_msg) {
		t.Errorf("Echo: expected %q got %q", msg[:len(small_msg)], small_msg)
	}
	var second_msg = make([]byte, len(msg))
	n, err = ws.Read(second_msg)
	if err != nil {
		t.Errorf("Read: %v", err)
	}
	second_msg = second_msg[0:n]
	if !bytes.Equal(msg[len(small_msg):], second_msg) {
		t.Errorf("Echo: expected %q got %q", msg[len(small_msg):], second_msg)
	}
	ws.Close()

}

func testSkipLengthFrame(t *testing.T) {
	b := []byte{'\x80', '\x01', 'x', 0, 'h', 'e', 'l', 'l', 'o', '\xff'}
	buf := bytes.NewBuffer(b)
	br := bufio.NewReader(buf)
	bw := bufio.NewWriter(buf)
	ws := newConn("http://127.0.0.1/", "ws://127.0.0.1/", "", bufio.NewReadWriter(br, bw), nil)
	msg := make([]byte, 5)
	n, err := ws.Read(msg)
	if err != nil {
		t.Errorf("Read: %v", err)
	}
	if !bytes.Equal(b[4:8], msg[0:n]) {
		t.Errorf("Read: expected %q got %q", msg[4:8], msg[0:n])
	}
}

func testSkipNoUTF8Frame(t *testing.T) {
	b := []byte{'\x01', 'n', '\xff', 0, 'h', 'e', 'l', 'l', 'o', '\xff'}
	buf := bytes.NewBuffer(b)
	br := bufio.NewReader(buf)
	bw := bufio.NewWriter(buf)
	ws := newConn("http://127.0.0.1/", "ws://127.0.0.1/", "", bufio.NewReadWriter(br, bw), nil)
	msg := make([]byte, 5)
	n, err := ws.Read(msg)
	if err != nil {
		t.Errorf("Read: %v", err)
	}
	if !bytes.Equal(b[4:8], msg[0:n]) {
		t.Errorf("Read: expected %q got %q", msg[4:8], msg[0:n])
	}
}
<commit_msg>websocket: fix socket leak in test<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package websocket

import (
	"bufio"
	"bytes"
	"fmt"
	"http"
	"http/httptest"
	"io"
	"log"
	"net"
	"sync"
	"testing"
)

var serverAddr string
var once sync.Once

func echoServer(ws *Conn) { io.Copy(ws, ws) }

func startServer() {
	http.Handle("/echo", Handler(echoServer))
	http.Handle("/echoDraft75", Draft75Handler(echoServer))
	server := httptest.NewServer(nil)
	serverAddr = server.Listener.Addr().String()
	log.Print("Test WebSocket server listening on ", serverAddr)
}

// Test the getChallengeResponse function with values from section
// 5.1 of the specification steps 18, 26, and 43 from
// http://www.whatwg.org/specs/web-socket-protocol/
func TestChallenge(t *testing.T) {
	var part1 uint32 = 777007543
	var part2 uint32 = 114997259
	key3 := []byte{0x47, 0x30, 0x22, 0x2D, 0x5A, 0x3F, 0x47, 0x58}
	expected := []byte("0st3Rl&q-2ZU^weu")

	response, err := getChallengeResponse(part1, part2, key3)
	if err != nil {
		t.Errorf("getChallengeResponse: returned error %v", err)
		return
	}
	if !bytes.Equal(expected, response) {
		t.Errorf("getChallengeResponse: expected %q got %q", expected, response)
	}
}

func TestEcho(t *testing.T) {
	once.Do(startServer)

	// websocket.Dial()
	client, err := net.Dial("tcp", serverAddr)
	if err != nil {
		t.Fatal("dialing", err)
	}
	ws, err := newClient("/echo", "localhost", "http://localhost",
		"ws://localhost/echo", "", client, handshake)
	if err != nil {
		t.Errorf("WebSocket handshake error: %v", err)
		return
	}

	msg := []byte("hello, world\n")
	if _, err := ws.Write(msg); err != nil {
		t.Errorf("Write: %v", err)
	}
	var actual_msg = make([]byte, 512)
	n, err := ws.Read(actual_msg)
	if err != nil {
		t.Errorf("Read: %v", err)
	}
	actual_msg = actual_msg[0:n]
	if !bytes.Equal(msg, actual_msg) {
		t.Errorf("Echo: expected %q got %q", msg, actual_msg)
	}
	ws.Close()
}

func TestEchoDraft75(t *testing.T) {
	once.Do(startServer)

	// websocket.Dial()
	client, err := net.Dial("tcp", serverAddr)
	if err != nil {
		t.Fatal("dialing", err)
	}
	ws, err := newClient("/echoDraft75", "localhost", "http://localhost",
		"ws://localhost/echoDraft75", "", client, draft75handshake)
	if err != nil {
		t.Errorf("WebSocket handshake: %v", err)
		return
	}

	msg := []byte("hello, world\n")
	if _, err := ws.Write(msg); err != nil {
		t.Errorf("Write: error %v", err)
	}
	var actual_msg = make([]byte, 512)
	n, err := ws.Read(actual_msg)
	if err != nil {
		t.Errorf("Read: error %v", err)
	}
	actual_msg = actual_msg[0:n]
	if !bytes.Equal(msg, actual_msg) {
		t.Errorf("Echo: expected %q got %q", msg, actual_msg)
	}
	ws.Close()
}

func TestWithQuery(t *testing.T) {
	once.Do(startServer)

	client, err := net.Dial("tcp", serverAddr)
	if err != nil {
		t.Fatal("dialing", err)
	}

	ws, err := newClient("/echo?q=v", "localhost", "http://localhost",
		"ws://localhost/echo?q=v", "", client, handshake)
	if err != nil {
		t.Errorf("WebSocket handshake: %v", err)
		return
	}
	ws.Close()
}

func TestWithProtocol(t *testing.T) {
	once.Do(startServer)

	client, err := net.Dial("tcp", serverAddr)
	if err != nil {
		t.Fatal("dialing", err)
	}

	ws, err := newClient("/echo", "localhost", "http://localhost",
		"ws://localhost/echo", "test", client, handshake)
	if err != nil {
		t.Errorf("WebSocket handshake: %v", err)
		return
	}
	ws.Close()
}

func TestHTTP(t *testing.T) {
	once.Do(startServer)

	// If the client did not send a handshake that matches the protocol
	// specification, the server should abort the WebSocket connection.
	_, _, err := http.Get(fmt.Sprintf("http://%s/echo", serverAddr))
	if err == nil {
		t.Error("Get: unexpected success")
		return
	}
	urlerr, ok := err.(*http.URLError)
	if !ok {
		t.Errorf("Get: not URLError %#v", err)
		return
	}
	if urlerr.Error != io.ErrUnexpectedEOF {
		t.Errorf("Get: error %#v", err)
		return
	}
}

func TestHTTPDraft75(t *testing.T) {
	once.Do(startServer)

	r, _, err := http.Get(fmt.Sprintf("http://%s/echoDraft75", serverAddr))
	if err != nil {
		t.Errorf("Get: error %#v", err)
		return
	}
	if r.StatusCode != http.StatusBadRequest {
		t.Errorf("Get: got status %d", r.StatusCode)
	}
}

func TestTrailingSpaces(t *testing.T) {
	// http://code.google.com/p/go/issues/detail?id=955
	// The last runs of this create keys with trailing spaces that should not be
	// generated by the client.
	once.Do(startServer)
	for i := 0; i < 30; i++ {
		// body
		ws, err := Dial(fmt.Sprintf("ws://%s/echo", serverAddr), "", "http://localhost/")
		if err != nil {
			t.Error("Dial failed:", err.String())
			break
		}
		ws.Close()
	}
}

func TestSmallBuffer(t *testing.T) {
	// http://code.google.com/p/go/issues/detail?id=1145
	// Read should be able to handle reading a fragment of a frame.
	once.Do(startServer)

	// websocket.Dial()
	client, err := net.Dial("tcp", serverAddr)
	if err != nil {
		t.Fatal("dialing", err)
	}
	ws, err := newClient("/echo", "localhost", "http://localhost",
		"ws://localhost/echo", "", client, handshake)
	if err != nil {
		t.Errorf("WebSocket handshake error: %v", err)
		return
	}

	msg := []byte("hello, world\n")
	if _, err := ws.Write(msg); err != nil {
		t.Errorf("Write: %v", err)
	}
	var small_msg = make([]byte, 8)
	n, err := ws.Read(small_msg)
	if err != nil {
		t.Errorf("Read: %v", err)
	}
	if !bytes.Equal(msg[:len(small_msg)], small_msg) {
		t.Errorf("Echo: expected %q got %q", msg[:len(small_msg)], small_msg)
	}
	var second_msg = make([]byte, len(msg))
	n, err = ws.Read(second_msg)
	if err != nil {
		t.Errorf("Read: %v", err)
	}
	second_msg = second_msg[0:n]
	if !bytes.Equal(msg[len(small_msg):], second_msg) {
		t.Errorf("Echo: expected %q got %q", msg[len(small_msg):], second_msg)
	}
	ws.Close()

}

func testSkipLengthFrame(t *testing.T) {
	b := []byte{'\x80', '\x01', 'x', 0, 'h', 'e', 'l', 'l', 'o', '\xff'}
	buf := bytes.NewBuffer(b)
	br := bufio.NewReader(buf)
	bw := bufio.NewWriter(buf)
	ws := newConn("http://127.0.0.1/", "ws://127.0.0.1/", "", bufio.NewReadWriter(br, bw), nil)
	msg := make([]byte, 5)
	n, err := ws.Read(msg)
	if err != nil {
		t.Errorf("Read: %v", err)
	}
	if !bytes.Equal(b[4:8], msg[0:n]) {
		t.Errorf("Read: expected %q got %q", msg[4:8], msg[0:n])
	}
}

func testSkipNoUTF8Frame(t *testing.T) {
	b := []byte{'\x01', 'n', '\xff', 0, 'h', 'e', 'l', 'l', 'o', '\xff'}
	buf := bytes.NewBuffer(b)
	br := bufio.NewReader(buf)
	bw := bufio.NewWriter(buf)
	ws := newConn("http://127.0.0.1/", "ws://127.0.0.1/", "", bufio.NewReadWriter(br, bw), nil)
	msg := make([]byte, 5)
	n, err := ws.Read(msg)
	if err != nil {
		t.Errorf("Read: %v", err)
	}
	if !bytes.Equal(b[4:8], msg[0:n]) {
		t.Errorf("Read: expected %q got %q", msg[4:8], msg[0:n])
	}
}
<|endoftext|>


<commit_before>/**
 * (C) Copyright 2013, Deft Labs
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package dlshared

import (
	"fmt"
	"net/http"
	"io/ioutil"
	"time"
	"bytes"
	"strings"
	"errors"
	"labix.org/v2/mgo/bson"
	"encoding/json"
	"github.com/mreiferson/go-httpclient"
)

const (
	SocketTimeout = 40
	HttpPostMethod = "POST"
	ContentTypeHeader = "Content-Type"
	ContentTypeTextPlain = "text/plain; charset=utf-8"
)

func HttpPostStr(url string, value string) ([]byte, error) {

	httpClient, httpTransport := getDefaultHttpClient()
	defer httpTransport.Close()

	request, err := http.NewRequest("POST", url, bytes.NewReader([]byte(value)))
	if err != nil {
		return nil, err
	}
	request.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	response, err := httpClient.Do(request)
	if err != nil {
		return nil, err
	}

	defer response.Body.Close()

	if data, err := ioutil.ReadAll(response.Body); err != nil {
		return nil, err
	} else {
		return data, nil
	}
}

func HttpPostJson(url string, value interface{}) ([]byte, error) {

	rawJson, err := json.Marshal(value)
	if err != nil {
		return nil, err
	}

	fmt.Println("rawJson:", string(rawJson))

	httpClient, httpTransport := getDefaultHttpClient()
	defer httpTransport.Close()

	request, err := http.NewRequest("POST", url, bytes.NewReader(rawJson))
	if err != nil {
		return nil, err
	}
	request.Header.Set("Content-Type", "application/json")

	response, err := httpClient.Do(request)
	if err != nil {
		return nil, err
	}

	defer response.Body.Close()

	if data, err := ioutil.ReadAll(response.Body); err != nil {
		return nil, err
	} else {
		return data, nil
	}
}

func HttpPostBson(url string, bsonDoc interface{}) ([]byte, error) {

	rawBson, err := bson.Marshal(bsonDoc)
	if err != nil {
		return nil, err
	}

	httpClient, httpTransport := getDefaultHttpClient()
	defer httpTransport.Close()

	request, err := http.NewRequest("POST", url, bytes.NewReader(rawBson))
	if err != nil {
		return nil, err
	}
	request.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	response, err := httpClient.Do(request)
	if err != nil {
		return nil, err
	}

	defer response.Body.Close()

	// We do not return the response so don't report if there is an error.
	if data, err := ioutil.ReadAll(response.Body); err != nil {
		return nil, err
	} else {
		return data, nil
	}
}

func getDefaultHttpClient() (*http.Client, *httpclient.Transport) {
	transport := getDefaultHttpTransport()
	return &http.Client{ Transport: transport }, transport
}

func getDefaultHttpTransport() *httpclient.Transport {
	return &httpclient.Transport {
		ConnectTimeout:        SocketTimeout * time.Second,
		RequestTimeout:        SocketTimeout * time.Second,
		ResponseHeaderTimeout: SocketTimeout * time.Second,
	}
}

func HttpGetBson(url string) (bson.M, error) {

	httpClient, httpTransport := getDefaultHttpClient()
	defer httpTransport.Close()

	request, requestErr := http.NewRequest("GET", url, nil)
	if requestErr != nil {
		return nil, requestErr
	}

	response, err := httpClient.Do(request)

	if err != nil {
		return nil, err
	}

	defer response.Body.Close()

	rawBson, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	var bsonDoc bson.M
	if err := bson.Unmarshal(rawBson, &bsonDoc); err != nil {
		return nil, err
	}

	return bsonDoc, nil
}

// This method returns true if the http request method is a HTTP post. If the
// field missing or incorrect, false is returned. This method will panic if the request
// is nil.
func IsHttpMethodPost(request *http.Request) bool {
	if request == nil {
		panic("request param is nil")
	}
	return len(request.Method) > 0 && strings.ToUpper(request.Method) == HttpPostMethod
}

// Write an http ok response string. The content type is text/plain.
func WriteOkResponseString(response http.ResponseWriter, msg string) error {
	if response == nil {
		panic("response param is nil")
	}

	msgLength := len(msg)

	if msgLength == 0 {
		panic("do not write an empty string to the response")
	}

	response.Header().Set(ContentTypeHeader, ContentTypeTextPlain)

	written, err := response.Write([]byte(msg))

	if err != nil {
		return err
	}

	if written != msgLength {
		return errors.New(fmt.Sprintf("Did not write full message - bytes written %d - expected %d", written, msgLength))
	}

	return nil
}

<commit_msg>removed debug<commit_after>/**
 * (C) Copyright 2013, Deft Labs
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package dlshared

import (
	"fmt"
	"net/http"
	"io/ioutil"
	"time"
	"bytes"
	"strings"
	"errors"
	"labix.org/v2/mgo/bson"
	"encoding/json"
	"github.com/mreiferson/go-httpclient"
)

const (
	SocketTimeout = 40
	HttpPostMethod = "POST"
	ContentTypeHeader = "Content-Type"
	ContentTypeTextPlain = "text/plain; charset=utf-8"
)

func HttpPostStr(url string, value string) ([]byte, error) {

	httpClient, httpTransport := getDefaultHttpClient()
	defer httpTransport.Close()

	request, err := http.NewRequest("POST", url, bytes.NewReader([]byte(value)))
	if err != nil {
		return nil, err
	}
	request.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	response, err := httpClient.Do(request)
	if err != nil {
		return nil, err
	}

	defer response.Body.Close()

	if data, err := ioutil.ReadAll(response.Body); err != nil {
		return nil, err
	} else {
		return data, nil
	}
}

func HttpPostJson(url string, value interface{}) ([]byte, error) {

	rawJson, err := json.Marshal(value)
	if err != nil {
		return nil, err
	}

	httpClient, httpTransport := getDefaultHttpClient()
	defer httpTransport.Close()

	request, err := http.NewRequest("POST", url, bytes.NewReader(rawJson))
	if err != nil {
		return nil, err
	}
	request.Header.Set("Content-Type", "application/json")

	response, err := httpClient.Do(request)
	if err != nil {
		return nil, err
	}

	defer response.Body.Close()

	if data, err := ioutil.ReadAll(response.Body); err != nil {
		return nil, err
	} else {
		return data, nil
	}
}

func HttpPostBson(url string, bsonDoc interface{}) ([]byte, error) {

	rawBson, err := bson.Marshal(bsonDoc)
	if err != nil {
		return nil, err
	}

	httpClient, httpTransport := getDefaultHttpClient()
	defer httpTransport.Close()

	request, err := http.NewRequest("POST", url, bytes.NewReader(rawBson))
	if err != nil {
		return nil, err
	}
	request.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	response, err := httpClient.Do(request)
	if err != nil {
		return nil, err
	}

	defer response.Body.Close()

	// We do not return the response so don't report if there is an error.
	if data, err := ioutil.ReadAll(response.Body); err != nil {
		return nil, err
	} else {
		return data, nil
	}
}

func getDefaultHttpClient() (*http.Client, *httpclient.Transport) {
	transport := getDefaultHttpTransport()
	return &http.Client{ Transport: transport }, transport
}

func getDefaultHttpTransport() *httpclient.Transport {
	return &httpclient.Transport {
		ConnectTimeout:        SocketTimeout * time.Second,
		RequestTimeout:        SocketTimeout * time.Second,
		ResponseHeaderTimeout: SocketTimeout * time.Second,
	}
}

func HttpGetBson(url string) (bson.M, error) {

	httpClient, httpTransport := getDefaultHttpClient()
	defer httpTransport.Close()

	request, requestErr := http.NewRequest("GET", url, nil)
	if requestErr != nil {
		return nil, requestErr
	}

	response, err := httpClient.Do(request)

	if err != nil {
		return nil, err
	}

	defer response.Body.Close()

	rawBson, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	var bsonDoc bson.M
	if err := bson.Unmarshal(rawBson, &bsonDoc); err != nil {
		return nil, err
	}

	return bsonDoc, nil
}

// This method returns true if the http request method is a HTTP post. If the
// field missing or incorrect, false is returned. This method will panic if the request
// is nil.
func IsHttpMethodPost(request *http.Request) bool {
	if request == nil {
		panic("request param is nil")
	}
	return len(request.Method) > 0 && strings.ToUpper(request.Method) == HttpPostMethod
}

// Write an http ok response string. The content type is text/plain.
func WriteOkResponseString(response http.ResponseWriter, msg string) error {
	if response == nil {
		panic("response param is nil")
	}

	msgLength := len(msg)

	if msgLength == 0 {
		panic("do not write an empty string to the response")
	}

	response.Header().Set(ContentTypeHeader, ContentTypeTextPlain)

	written, err := response.Write([]byte(msg))

	if err != nil {
		return err
	}

	if written != msgLength {
		return errors.New(fmt.Sprintf("Did not write full message - bytes written %d - expected %d", written, msgLength))
	}

	return nil
}

<|endoftext|>


<commit_before>package hub

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

	"github.com/captncraig/ssgo"
	ghApi "github.com/google/go-github/github"
)

type GithubAuthenticatedHandler func(w http.ResponseWriter, r *http.Request, user *GithubUser)

// GithubSSO manages all sign ins to github, as well as tracking cookies issued to users and the associated github access tokens.
type GithubSSO interface {
	// Initiate the login process by redirecting the user to the github sign-in and approve page.
	RedirectToLogin(w http.ResponseWriter, r *http.Request)
	// This should be linked to the callback url github has associated with your application.
	// This handler takes care of exchanging the code for an access token, and will drop a cookie before redirecting back to "/".
	ExchangeCodeForToken(w http.ResponseWriter, r *http.Request)
	// Lookup a user from an http request. Will return nil if no valid cookie found.
	LookupUser(r *http.Request) *GithubUser
	// Make a choice based on the incoming request. If user is already authenticated, the loggedin handler will execute.
	// Otherwise, the loggedOut handler will execute.
	Route(loggedOut http.HandlerFunc, loggedIn GithubAuthenticatedHandler) http.HandlerFunc
}

type githubSSO struct {
	clientId, clientSecret string
	requiredScopes         string
}

const ghAuthBucketName = "ghAuth"

func init() {
	if err := ssgo.EnsureBoltBucketExists(ghAuthBucketName); err != nil {
		panic(err)
	}
}

func NewGithubSSO(clientId, clientSecret, scopes string) GithubSSO {
	return &githubSSO{
		clientId:       clientId,
		clientSecret:   clientSecret,
		requiredScopes: scopes,
	}
}

var ghStates = map[string]time.Time{}

func (g *githubSSO) RedirectToLogin(w http.ResponseWriter, r *http.Request) {
	state := ssgo.RandSeq(10)
	ghStates[state] = time.Now()
	url := fmt.Sprintf("https://github.com/login/oauth/authorize?client_id=%s&scope=%s&state=%s", g.clientId, g.requiredScopes, state)
	http.Redirect(w, r, url, 302)
}

func (g *githubSSO) ExchangeCodeForToken(w http.ResponseWriter, r *http.Request) {
	fmt.Println("callback!")
	state := r.URL.Query().Get("state")
	if _, ok := ghStates[state]; state == "" || !ok {
		w.WriteHeader(401)
		io.WriteString(w, "Unknown state detected")
		return
	}
	code := r.URL.Query().Get("code")
	if code == "" {
		w.WriteHeader(400)
		io.WriteString(w, "No code provided")
		return
	}
	exchangeUrl := fmt.Sprintf("https://github.com/login/oauth/access_token?client_id=%s&client_secret=%s&code=%s", g.clientId, g.clientSecret, code)
	res, err := http.Post(exchangeUrl, "text/plain", nil)
	if err != nil || res.StatusCode != 200 {
		w.WriteHeader(502)
		fmt.Fprintf(w, "%s , %d", err, res.StatusCode)
		return
	}
	body, err := ioutil.ReadAll(res.Body)
	if err != nil || strings.Contains(string(body), "error=") {
		w.WriteHeader(500)
		return
	}
	eq := strings.IndexRune(string(body), '=')
	amp := strings.IndexRune(string(body), '&')
	if eq == -1 || amp == -1 || amp < eq {
		w.WriteHeader(500)
		return
	}
	accessToken := string(body[eq+1 : amp])
	cookieVal := ssgo.RandSeq(25)

	gh := ghApi.NewClient(githubApiClient(accessToken))
	u, _, err := gh.Users.Get("")
	if err != nil {
		w.WriteHeader(500)
		return
	}
	uname := *u.Login
	avatar := *u.AvatarURL
	g.storeGithubToken(cookieVal, accessToken, uname, avatar)
	http.SetCookie(w, &http.Cookie{Name: "ghAuthToken", Value: cookieVal, Expires: time.Now().Add(90 * 24 * time.Hour)})
	http.Redirect(w, r, "/", 302)
}

func (g *githubSSO) LookupUser(r *http.Request) *GithubUser {
	cookie, err := r.Cookie("ghAuthToken")
	if err != nil {
		return nil
	}
	user := &GithubUser{}
	err = ssgo.LookupBoltJson(ghAuthBucketName, cookie.Value, user)
	if err != nil {
		return nil
	}
	return user
}

func (g *githubSSO) storeGithubToken(cookie, token, username, avatar string) error {
	return ssgo.StoreBoltJson(ghAuthBucketName, cookie, &GithubUser{username, token, avatar})
}

func (g *githubSSO) Route(loggedOut http.HandlerFunc, loggedIn GithubAuthenticatedHandler) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		user := g.LookupUser(r)
		if user == nil {
			loggedOut(w, r)
		} else {
			loggedIn(w, r, user)
		}
	}
}

// Basic information about a user.
type GithubUser struct {
	Login, AccessToken, AvatarUrl string
}

type ghClient struct {
	token string
}

func (g *ghClient) RoundTrip(r *http.Request) (*http.Response, error) {
	r.Header.Add("Authorization", "token "+g.token)
	return http.DefaultTransport.RoundTrip(r)
}

// Creates an http.Client that can be used to make authenticated requests to the github api
func (u *GithubUser) GithubApiClient() *http.Client {
	return githubApiClient(u.AccessToken)
}

func githubApiClient(accessToken string) *http.Client {
	return &http.Client{Transport: &ghClient{accessToken}}
}
<commit_msg>Update github.go<commit_after>package hub

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

	"github.com/captncraig/ssgo"
	ghApi "github.com/google/go-github/github"
)

type GithubAuthenticatedHandler func(w http.ResponseWriter, r *http.Request, user *GithubUser)

// GithubSSO manages all sign ins to github, as well as tracking cookies issued to users and the associated github access tokens.
type GithubSSO interface {
	// Initiate the login process by redirecting the user to the github sign-in and approve page.
	RedirectToLogin(w http.ResponseWriter, r *http.Request)
	// This should be linked to the callback url github has associated with your application.
	// This handler takes care of exchanging the code for an access token, and will drop a cookie before redirecting back to "/".
	ExchangeCodeForToken(w http.ResponseWriter, r *http.Request)
	// Lookup a user from an http request. Will return nil if no valid cookie found.
	LookupUser(r *http.Request) *GithubUser
	// Make a choice based on the incoming request. If user is already authenticated, the loggedin handler will execute.
	// Otherwise, the loggedOut handler will execute.
	Route(loggedOut http.HandlerFunc, loggedIn GithubAuthenticatedHandler) http.HandlerFunc
}

type githubSSO struct {
	clientId, clientSecret string
	requiredScopes         string
}

const ghAuthBucketName = "ghAuth"

func init() {
	if err := ssgo.EnsureBoltBucketExists(ghAuthBucketName); err != nil {
		panic(err)
	}
}

func NewGithubSSO(clientId, clientSecret, scopes string) GithubSSO {
	return &githubSSO{
		clientId:       clientId,
		clientSecret:   clientSecret,
		requiredScopes: scopes,
	}
}

var ghStates = map[string]time.Time{}

func (g *githubSSO) RedirectToLogin(w http.ResponseWriter, r *http.Request) {
	state := ssgo.RandSeq(10)
	ghStates[state] = time.Now()
	url := fmt.Sprintf("https://github.com/login/oauth/authorize?client_id=%s&scope=%s&state=%s", g.clientId, g.requiredScopes, state)
	http.Redirect(w, r, url, 302)
}

func (g *githubSSO) ExchangeCodeForToken(w http.ResponseWriter, r *http.Request) {
	state := r.URL.Query().Get("state")
	if _, ok := ghStates[state]; state == "" || !ok {
		w.WriteHeader(401)
		io.WriteString(w, "Unknown state detected")
		return
	}
	code := r.URL.Query().Get("code")
	if code == "" {
		w.WriteHeader(400)
		io.WriteString(w, "No code provided")
		return
	}
	exchangeUrl := fmt.Sprintf("https://github.com/login/oauth/access_token?client_id=%s&client_secret=%s&code=%s", g.clientId, g.clientSecret, code)
	res, err := http.Post(exchangeUrl, "text/plain", nil)
	if err != nil || res.StatusCode != 200 {
		w.WriteHeader(502)
		fmt.Fprintf(w, "%s , %d", err, res.StatusCode)
		return
	}
	body, err := ioutil.ReadAll(res.Body)
	if err != nil || strings.Contains(string(body), "error=") {
		w.WriteHeader(500)
		return
	}
	eq := strings.IndexRune(string(body), '=')
	amp := strings.IndexRune(string(body), '&')
	if eq == -1 || amp == -1 || amp < eq {
		w.WriteHeader(500)
		return
	}
	accessToken := string(body[eq+1 : amp])
	cookieVal := ssgo.RandSeq(25)

	gh := ghApi.NewClient(githubApiClient(accessToken))
	u, _, err := gh.Users.Get("")
	if err != nil {
		w.WriteHeader(500)
		return
	}
	uname := *u.Login
	avatar := *u.AvatarURL
	g.storeGithubToken(cookieVal, accessToken, uname, avatar)
	http.SetCookie(w, &http.Cookie{Name: "ghAuthToken", Value: cookieVal, Expires: time.Now().Add(90 * 24 * time.Hour)})
	http.Redirect(w, r, "/", 302)
}

func (g *githubSSO) LookupUser(r *http.Request) *GithubUser {
	cookie, err := r.Cookie("ghAuthToken")
	if err != nil {
		return nil
	}
	user := &GithubUser{}
	err = ssgo.LookupBoltJson(ghAuthBucketName, cookie.Value, user)
	if err != nil {
		return nil
	}
	return user
}

func (g *githubSSO) storeGithubToken(cookie, token, username, avatar string) error {
	return ssgo.StoreBoltJson(ghAuthBucketName, cookie, &GithubUser{username, token, avatar})
}

func (g *githubSSO) Route(loggedOut http.HandlerFunc, loggedIn GithubAuthenticatedHandler) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		user := g.LookupUser(r)
		if user == nil {
			loggedOut(w, r)
		} else {
			loggedIn(w, r, user)
		}
	}
}

// Basic information about a user.
type GithubUser struct {
	Login, AccessToken, AvatarUrl string
}

type ghClient struct {
	token string
}

func (g *ghClient) RoundTrip(r *http.Request) (*http.Response, error) {
	r.Header.Add("Authorization", "token "+g.token)
	return http.DefaultTransport.RoundTrip(r)
}

// Creates an http.Client that can be used to make authenticated requests to the github api
func (u *GithubUser) GithubApiClient() *http.Client {
	return githubApiClient(u.AccessToken)
}

func githubApiClient(accessToken string) *http.Client {
	return &http.Client{Transport: &ghClient{accessToken}}
}
<|endoftext|>


<commit_before>package dnsd

import (
	"context"
	"math/rand"
	"net"
	"time"

	"github.com/HouzuoGuo/laitos/lalog"
	"github.com/HouzuoGuo/laitos/toolbox"

	"github.com/HouzuoGuo/laitos/misc"
)

// GetUDPStatsCollector returns stats collector for the UDP server of this daemon.
func (daemon *Daemon) GetUDPStatsCollector() *misc.Stats {
	return misc.DNSDStatsUDP
}

// Read a feature command from each input line, then invoke the requested feature and write the execution result back to client.
func (daemon *Daemon) HandleUDPClient(logger lalog.Logger, ip string, client *net.UDPAddr, packet []byte, srv *net.UDPConn) {
	if len(packet) < MinNameQuerySize {
		logger.Warning("HandleUDPClient", ip, nil, "packet length is too small")
		return
	}
	var respLenInt int
	var respBody []byte
	if isTextQuery(packet) {
		// Handle toolbox command that arrives as a text query
		respLenInt, respBody = daemon.handleUDPTextQuery(ip, packet)
	} else {
		// Handle other query types such as name query
		respLenInt, respBody = daemon.handleUDPNameOrOtherQuery(ip, packet)
	}
	// Ignore the request if there is no appropriate response
	if respBody == nil || len(respBody) < 3 {
		return
	}
	// Send response to the client, match transaction ID of original query.
	respBody[0] = packet[0]
	respBody[1] = packet[1]
	// Set deadline for responding to my DNS client because the query reader and response writer do not share the same timeout
	logger.MaybeMinorError(srv.SetWriteDeadline(time.Now().Add(ClientTimeoutSec * time.Second)))
	if _, err := srv.WriteTo(respBody[:respLenInt], client); err != nil {
		logger.Warning("HandleUDPQuery", ip, err, "failed to answer to client")
		return
	}
}

func (daemon *Daemon) handleUDPTextQuery(clientIP string, queryBody []byte) (respLenInt int, respBody []byte) {
	queriedName := ExtractTextQueryInput(queryBody)
	if daemon.processQueryTestCaseFunc != nil {
		daemon.processQueryTestCaseFunc(queriedName)
	}
	if dtmfDecoded := DecodeDTMFCommandInput(queriedName); len(dtmfDecoded) > 1 {
		cmdResult := daemon.latestCommands.Execute(context.TODO(), daemon.Processor, clientIP, dtmfDecoded)
		if cmdResult.Error == toolbox.ErrPINAndShortcutNotFound {
			/*
				Because the prefix may appear in an ordinary text record query that is not a toolbox command, when there is
				a PIN mismatch, forward to recursive resolver as if the query is indeed not a toolbox command.
			*/
			daemon.logger.Info("handleUDPTextQuery", clientIP, nil, "input has command prefix but failed PIN check")
			goto forwardToRecursiveResolver
		} else {
			daemon.logger.Info("handleUDPTextQuery", clientIP, nil, "processed a toolbox command")
			respBody = MakeTextResponse(queryBody, cmdResult.CombinedOutput)
			return len(respBody), respBody
		}
	} else {
		daemon.logger.Info("handleUDPTextQuery", clientIP, nil, "handle query \"%s\"", string(queriedName))
	}
forwardToRecursiveResolver:
	// There's a chance of being a typo in the PIN entry, make sure this function does not log the request input.
	return daemon.handleUDPRecursiveQuery(clientIP, queryBody)
}

func (daemon *Daemon) handleUDPNameOrOtherQuery(clientIP string, queryBody []byte) (respLenInt int, respBody []byte) {
	// Handle other query types such as name query
	domainName := ExtractDomainName(queryBody)
	if domainName == "" {
		daemon.logger.Info("handleUDPNameOrOtherQuery", clientIP, nil, "handle non-name query")
	} else {
		if daemon.processQueryTestCaseFunc != nil {
			daemon.processQueryTestCaseFunc(domainName)
		}
		daemon.logger.Info("handleUDPNameOrOtherQuery", clientIP, nil, "handle query \"%s\"", domainName)
	}
	if daemon.IsInBlacklist(domainName) {
		// Formulate a black-hole response to black-listed domain name
		daemon.logger.Info("handleUDPNameOrOtherQuery", clientIP, nil, "handle black-listed \"%s\"", domainName)
		respBody = GetBlackHoleResponse(queryBody)
		respLenInt = len(respBody)
		return
	}
	return daemon.handleUDPRecursiveQuery(clientIP, queryBody)
}

/*
handleUDPRecursiveQuery forward the input query to a randomly chosen recursive resolver and retrieves the response.
Be aware that toolbox command processor may invoke this function with an incorrect PIN entry similar to the real PIN,
therefore this function must not log the input packet content in any way.
*/
func (daemon *Daemon) handleUDPRecursiveQuery(clientIP string, queryBody []byte) (respLenInt int, respBody []byte) {
	respBody = make([]byte, 0)
	if !daemon.checkAllowClientIP(clientIP) {
		daemon.logger.Info("handleUDPRecursiveQuery", clientIP, nil, "client IP is not allowed to query")
		return
	}
	// Forward the query to a randomly chosen recursive resolver and return its response
	randForwarder := daemon.Forwarders[rand.Intn(len(daemon.Forwarders))]
	forwarderConn, err := net.DialTimeout("udp", randForwarder, ForwarderTimeoutSec*time.Second)
	if err != nil {
		daemon.logger.Warning("handleUDPRecursiveQuery", clientIP, err, "failed to dial forwarder's address")
		return
	}
	daemon.logger.MaybeMinorError(forwarderConn.SetDeadline(time.Now().Add(ForwarderTimeoutSec * time.Second)))
	if _, err := forwarderConn.Write(queryBody); err != nil {
		daemon.logger.Warning("handleUDPRecursiveQuery", clientIP, err, "failed to write to forwarder")
		return
	}
	respBody = make([]byte, MaxPacketSize)
	respLenInt, err = forwarderConn.Read(respBody)
	if err != nil {
		daemon.logger.Warning("handleUDPRecursiveQuery", clientIP, err, "failed to read from forwarder")
		return
	}
	if respLenInt < 3 {
		daemon.logger.Warning("handleUDPRecursiveQuery", clientIP, err, "forwarder response is abnormally small")
		return
	}
	return
}
<commit_msg>Fix a FD leak in the DNS-UDP daemon<commit_after>package dnsd

import (
	"context"
	"math/rand"
	"net"
	"time"

	"github.com/HouzuoGuo/laitos/lalog"
	"github.com/HouzuoGuo/laitos/toolbox"

	"github.com/HouzuoGuo/laitos/misc"
)

// GetUDPStatsCollector returns stats collector for the UDP server of this daemon.
func (daemon *Daemon) GetUDPStatsCollector() *misc.Stats {
	return misc.DNSDStatsUDP
}

// Read a feature command from each input line, then invoke the requested feature and write the execution result back to client.
func (daemon *Daemon) HandleUDPClient(logger lalog.Logger, ip string, client *net.UDPAddr, packet []byte, srv *net.UDPConn) {
	if len(packet) < MinNameQuerySize {
		logger.Warning("HandleUDPClient", ip, nil, "packet length is too small")
		return
	}
	var respLenInt int
	var respBody []byte
	if isTextQuery(packet) {
		// Handle toolbox command that arrives as a text query
		respLenInt, respBody = daemon.handleUDPTextQuery(ip, packet)
	} else {
		// Handle other query types such as name query
		respLenInt, respBody = daemon.handleUDPNameOrOtherQuery(ip, packet)
	}
	// Ignore the request if there is no appropriate response
	if respBody == nil || len(respBody) < 3 {
		return
	}
	// Send response to the client, match transaction ID of original query.
	respBody[0] = packet[0]
	respBody[1] = packet[1]
	// Set deadline for responding to my DNS client because the query reader and response writer do not share the same timeout
	logger.MaybeMinorError(srv.SetWriteDeadline(time.Now().Add(ClientTimeoutSec * time.Second)))
	if _, err := srv.WriteTo(respBody[:respLenInt], client); err != nil {
		logger.Warning("HandleUDPQuery", ip, err, "failed to answer to client")
		return
	}
}

func (daemon *Daemon) handleUDPTextQuery(clientIP string, queryBody []byte) (respLenInt int, respBody []byte) {
	queriedName := ExtractTextQueryInput(queryBody)
	if daemon.processQueryTestCaseFunc != nil {
		daemon.processQueryTestCaseFunc(queriedName)
	}
	if dtmfDecoded := DecodeDTMFCommandInput(queriedName); len(dtmfDecoded) > 1 {
		cmdResult := daemon.latestCommands.Execute(context.TODO(), daemon.Processor, clientIP, dtmfDecoded)
		if cmdResult.Error == toolbox.ErrPINAndShortcutNotFound {
			/*
				Because the prefix may appear in an ordinary text record query that is not a toolbox command, when there is
				a PIN mismatch, forward to recursive resolver as if the query is indeed not a toolbox command.
			*/
			daemon.logger.Info("handleUDPTextQuery", clientIP, nil, "input has command prefix but failed PIN check")
			goto forwardToRecursiveResolver
		} else {
			daemon.logger.Info("handleUDPTextQuery", clientIP, nil, "processed a toolbox command")
			respBody = MakeTextResponse(queryBody, cmdResult.CombinedOutput)
			return len(respBody), respBody
		}
	} else {
		daemon.logger.Info("handleUDPTextQuery", clientIP, nil, "handle query \"%s\"", string(queriedName))
	}
forwardToRecursiveResolver:
	// There's a chance of being a typo in the PIN entry, make sure this function does not log the request input.
	return daemon.handleUDPRecursiveQuery(clientIP, queryBody)
}

func (daemon *Daemon) handleUDPNameOrOtherQuery(clientIP string, queryBody []byte) (respLenInt int, respBody []byte) {
	// Handle other query types such as name query
	domainName := ExtractDomainName(queryBody)
	if domainName == "" {
		daemon.logger.Info("handleUDPNameOrOtherQuery", clientIP, nil, "handle non-name query")
	} else {
		if daemon.processQueryTestCaseFunc != nil {
			daemon.processQueryTestCaseFunc(domainName)
		}
		daemon.logger.Info("handleUDPNameOrOtherQuery", clientIP, nil, "handle query \"%s\"", domainName)
	}
	if daemon.IsInBlacklist(domainName) {
		// Formulate a black-hole response to black-listed domain name
		daemon.logger.Info("handleUDPNameOrOtherQuery", clientIP, nil, "handle black-listed \"%s\"", domainName)
		respBody = GetBlackHoleResponse(queryBody)
		respLenInt = len(respBody)
		return
	}
	return daemon.handleUDPRecursiveQuery(clientIP, queryBody)
}

/*
handleUDPRecursiveQuery forward the input query to a randomly chosen recursive resolver and retrieves the response.
Be aware that toolbox command processor may invoke this function with an incorrect PIN entry similar to the real PIN,
therefore this function must not log the input packet content in any way.
*/
func (daemon *Daemon) handleUDPRecursiveQuery(clientIP string, queryBody []byte) (respLenInt int, respBody []byte) {
	respBody = make([]byte, 0)
	if !daemon.checkAllowClientIP(clientIP) {
		daemon.logger.Info("handleUDPRecursiveQuery", clientIP, nil, "client IP is not allowed to query")
		return
	}
	// Forward the query to a randomly chosen recursive resolver and return its response
	randForwarder := daemon.Forwarders[rand.Intn(len(daemon.Forwarders))]
	forwarderConn, err := net.DialTimeout("udp", randForwarder, ForwarderTimeoutSec*time.Second)
	if err != nil {
		daemon.logger.Warning("handleUDPRecursiveQuery", clientIP, err, "failed to dial forwarder's address")
		return
	}
	defer func() {
		daemon.logger.MaybeMinorError(forwarderConn.Close())
	}()
	daemon.logger.MaybeMinorError(forwarderConn.SetDeadline(time.Now().Add(ForwarderTimeoutSec * time.Second)))
	if _, err := forwarderConn.Write(queryBody); err != nil {
		daemon.logger.Warning("handleUDPRecursiveQuery", clientIP, err, "failed to write to forwarder")
		return
	}
	respBody = make([]byte, MaxPacketSize)
	respLenInt, err = forwarderConn.Read(respBody)
	if err != nil {
		daemon.logger.Warning("handleUDPRecursiveQuery", clientIP, err, "failed to read from forwarder")
		return
	}
	if respLenInt < 3 {
		daemon.logger.Warning("handleUDPRecursiveQuery", clientIP, err, "forwarder response is abnormally small")
		return
	}
	return
}
<|endoftext|>


<commit_before>package core

import (
	"fmt"
	"log"
	"path"
	"reflect"
	"errors"
	"github.ibm.com/almaden-containers/ibm-storage-broker.git/model"
	"github.ibm.com/almaden-containers/ibm-storage-broker.git/utils"
	"encoding/json"
	"os"
)

const (
	DEFAULT_POLLING_INTERVAL_SECONDS = 10
	DEFAULT_CONTAINER_PATH           = "/var/vcap/data/"
)

//go:generate counterfeiter -o ./fakes/fake_controller.go . Controller

type Controller interface {
	GetCatalog(logger log.Logger) (model.Catalog, error)
	CreateServiceInstance(logger log.Logger, serverInstanceId string, instance model.ServiceInstance) (model.CreateServiceInstanceResponse, error)
	ServiceInstanceExists(logger log.Logger, serviceInstanceId string) bool
	ServiceInstancePropertiesMatch(logger log.Logger, serviceInstanceId string, instance model.ServiceInstance) bool
	DeleteServiceInstance(logger log.Logger, serviceInstanceId string) error
	BindServiceInstance(logger log.Logger, serverInstanceId string, bindingId string, bindingInfo model.ServiceBinding) (model.CreateServiceBindingResponse, error)
	ServiceBindingExists(logger log.Logger, serviceInstanceId string, bindingId string) bool
	ServiceBindingPropertiesMatch(logger log.Logger, serviceInstanceId string, bindingId string, binding model.ServiceBinding) bool
	GetBinding(logger log.Logger, instanceId, bindingId string) (model.ServiceBinding, error)
	UnbindServiceInstance(logger log.Logger, serverInstanceId string, bindingId string) error
}

type StorageBackend interface {
	GetServices() []model.Service
	CreateVolume(serviceInstance model.ServiceInstance, name string, opts map[string]interface{}) error
	RemoveVolume(serviceInstance model.ServiceInstance, name string) error
	ListVolumes(serviceInstance model.ServiceInstance) ([]model.VolumeMetadata, error)
	GetVolume(serviceInstance model.ServiceInstance, name string) (volumeMetadata *model.VolumeMetadata, clientDriverName *string, config *map[string]interface{}, err error)
}

type controller struct {
	backends    map[*model.Service]StorageBackend
	log         *log.Logger
	instanceMap map[string]*model.ServiceInstance
	bindingMap  map[string]*model.ServiceBinding
	configPath  string
}

func NewController(backends map[*model.Service]StorageBackend, configPath string) Controller {

	existingServiceInstances, err := loadServiceInstances(configPath)
	if err != nil {
		log.Fatal(fmt.Sprintf("error reading existing service instances: %s", err.Error()))
	}
	for _, existingServiceInstance := range existingServiceInstances {
		_, err := getServiceById(backends, existingServiceInstance.ServiceId)
		if err != nil {
			log.Fatal(fmt.Sprintf("error reading existing service instances: service instance refers to non-existing or disabled service (ServiceId: %s)", existingServiceInstance.ServiceId))
		}
	}

	existingServiceBindings, err := loadServiceBindings(configPath)
	if err != nil {
		log.Fatal(fmt.Sprintf("error reading existing service bindings: %s", err.Error()))
	}
	for _, existingServiceBinding := range existingServiceBindings {
		_, err = getServiceById(backends, existingServiceBinding.ServiceId)
		if err != nil {
			log.Fatal(fmt.Sprintf("error reading existing service bindings: service binding refers to non-existing or disabled service (ServiceId: %s)", existingServiceBinding.ServiceId))
		}
	}

	return &controller{backends: backends, configPath: configPath, instanceMap: existingServiceInstances, bindingMap: existingServiceBindings}
}

func (c *controller) GetCatalog(logger log.Logger) (model.Catalog, error) {
	allServices := make([]model.Service, 0, len(c.backends))
	for service := range c.backends {
		allServices = append(allServices, *service)
	}
	catalog := model.Catalog{Services: allServices}
	return catalog, nil
}

func (c *controller) CreateServiceInstance(logger log.Logger, serviceInstanceId string, instance model.ServiceInstance) (model.CreateServiceInstanceResponse, error) {
	service, err := getServiceById(c.backends, instance.ServiceId)
	if err != nil {
		logger.Printf("Error: %s", err.Error())
		return model.CreateServiceInstanceResponse{}, err
	}
	if err := c.backends[service].CreateVolume(instance, serviceInstanceId, nil); err != nil {
		logger.Printf("Error: %s", err.Error())
		return model.CreateServiceInstanceResponse{}, err
	}

	instance.DashboardUrl = "http://dashboard_url"
	instance.Id = serviceInstanceId
	instance.LastOperation = &model.LastOperation{
		State:                    "in progress",
		Description:              "creating service instance...",
		AsyncPollIntervalSeconds: DEFAULT_POLLING_INTERVAL_SECONDS,
	}

	c.instanceMap[serviceInstanceId] = &instance

	if err := persistServiceInstances(c.configPath, c.instanceMap); err != nil {
		logger.Printf("Error: %s", err.Error())
		return model.CreateServiceInstanceResponse{}, err
	}

	response := model.CreateServiceInstanceResponse{
		DashboardUrl:  instance.DashboardUrl,
		LastOperation: instance.LastOperation,
	}

	return response, nil
}

func (c *controller) ServiceInstanceExists(logger log.Logger, serviceInstanceId string) bool {
	_, exists := c.instanceMap[serviceInstanceId]
	return exists
}

func (c *controller) ServiceInstancePropertiesMatch(logger log.Logger, serviceInstanceId string, instance model.ServiceInstance) bool {
	existingServiceInstance, exists := c.instanceMap[serviceInstanceId]
	if exists == false {
		return false
	}
	if existingServiceInstance.PlanId != instance.PlanId {
		return false
	}
	if existingServiceInstance.SpaceGuid != instance.SpaceGuid {
		return false
	}
	if existingServiceInstance.OrganizationGuid != instance.OrganizationGuid {
		return false
	}
	areParamsEqual := reflect.DeepEqual(existingServiceInstance.Parameters, instance.Parameters)
	return areParamsEqual
}

func (c *controller) DeleteServiceInstance(logger log.Logger, serviceInstanceId string) error {
	serviceInstance := c.instanceMap[serviceInstanceId]
	service, err := getServiceById(c.backends, (*serviceInstance).ServiceId)
	if err != nil {
		logger.Printf("Error: %s", err.Error())
		return err
	}
	if err := c.backends[service].RemoveVolume(*serviceInstance, serviceInstanceId); err != nil {
		logger.Printf("Error: %s", err.Error())
		return err
	}

	delete(c.instanceMap, serviceInstanceId)

	if err := persistServiceInstances(c.configPath, c.instanceMap); err != nil {
		logger.Printf("Error: %s", err.Error())
		return err
	}
	return nil
}

func (c *controller) BindServiceInstance(logger log.Logger, serviceInstanceId string, bindingId string, bindingInfo model.ServiceBinding) (model.CreateServiceBindingResponse, error) {
	serviceInstance := c.instanceMap[serviceInstanceId]
	service, err := getServiceById(c.backends, (*serviceInstance).ServiceId)
	if err != nil {
		return model.CreateServiceBindingResponse{}, err
	}

	c.bindingMap[bindingId] = &bindingInfo
	_, clientDriverName, config, err := c.backends[service].GetVolume(*serviceInstance, serviceInstanceId)
	if err != nil {
		logger.Printf("Error: %s", err.Error())
		return model.CreateServiceBindingResponse{}, err
	}
	containerMountPath := determineContainerMountPath(bindingInfo.Parameters, serviceInstanceId)

	configJson, err := json.Marshal(*config)
	if err != nil{
		logger.Printf("Error: %s", err.Error())
		return model.CreateServiceBindingResponse{}, err
	}

	privateDetails := model.VolumeMountPrivateDetails{Driver: *clientDriverName, GroupId: serviceInstanceId, Config: string(configJson)}
	volumeMount := model.VolumeMount{ContainerPath: containerMountPath, Mode: "rw", Private: privateDetails}
	volumeMounts := []model.VolumeMount{volumeMount}

	if err = persistServiceBindings(c.configPath, c.bindingMap); err != nil {
		logger.Printf("Error: %s", err.Error())
		return model.CreateServiceBindingResponse{}, err
	}

	createBindingResponse := model.CreateServiceBindingResponse{VolumeMounts: volumeMounts}
	return createBindingResponse, nil
}

func (c *controller) ServiceBindingExists(logger log.Logger, serviceInstanceId string, bindingId string) bool {
	_, exists := c.bindingMap[bindingId]
	return exists
}

func (c *controller) ServiceBindingPropertiesMatch(logger log.Logger, serviceInstanceId string, bindingId string, binding model.ServiceBinding) bool {
	existingBinding, exists := c.bindingMap[bindingId]
	if exists == false {
		return false
	}
	if existingBinding.AppId != binding.AppId {
		return false
	}
	if existingBinding.ServicePlanId != binding.ServicePlanId {
		return false
	}
	if existingBinding.ServiceId != binding.ServiceId {
		return false
	}
	if existingBinding.ServiceInstanceId != binding.ServiceInstanceId {
		return false
	}
	if existingBinding.Id != binding.Id {
		return false
	}
	return true
}

func (c *controller) GetBinding(logger log.Logger, instanceId, bindingId string) (model.ServiceBinding, error) {
	binding, exists := c.bindingMap[bindingId]
	if exists == true {
		return *binding, nil
	}
	return model.ServiceBinding{}, fmt.Errorf("binding not found")

}

func (c *controller) UnbindServiceInstance(logger log.Logger, serverInstanceId string, bindingId string) error {
	delete(c.bindingMap, bindingId)
	err := utils.MarshalAndRecord(c.bindingMap, c.configPath, "service_bindings.json")
	if err != nil {
		return err
	}
	return nil
}

func determineContainerMountPath(parameters map[string]interface{}, volId string) string {
	if containerPath, ok := parameters["container_path"]; ok {
		return containerPath.(string)
	}
	if containerPath, ok := parameters["path"]; ok {
		return containerPath.(string)
	}
	return path.Join(DEFAULT_CONTAINER_PATH, volId)
}

func getServiceById(backendsMap map[*model.Service]StorageBackend, serviceId string) (*model.Service, error) {
	for service := range backendsMap {
		if (*service).Id == serviceId {
			return service, nil
		}
	}
	return nil, errors.New(fmt.Sprintf("Could not locate service for serviceId %s", serviceId))
}

func loadServiceInstances(configPath string) (map[string]*model.ServiceInstance, error) {
	var serviceInstancesMap map[string]*model.ServiceInstance

	err := utils.ReadAndUnmarshal(&serviceInstancesMap, configPath, "service_instances.json")
	if err != nil {
		if os.IsNotExist(err) {
			fmt.Printf("WARNING: service instance data file '%s' does not exist: \n", "service_instances.json")
			serviceInstancesMap = make(map[string]*model.ServiceInstance)
		} else {
			return nil, errors.New(fmt.Sprintf("Could not load the service instances, message: %s", err.Error()))
		}
	}

	return serviceInstancesMap, nil
}

func persistServiceInstances(configPath string, instanceMap map[string]*model.ServiceInstance) error {
	return utils.MarshalAndRecord(instanceMap, configPath, "service_instances.json")
}

func loadServiceBindings(configPath string) (map[string]*model.ServiceBinding, error) {
	var bindingMap map[string]*model.ServiceBinding
	err := utils.ReadAndUnmarshal(&bindingMap, configPath, "service_bindings.json")
	if err != nil {
		if os.IsNotExist(err) {
			fmt.Printf("WARNING: key map data file '%s' does not exist: \n", "service_bindings.json")
			bindingMap = make(map[string]*model.ServiceBinding)
		} else {
			return nil, errors.New(fmt.Sprintf("Could not load the service instances, message: %s", err.Error()))
		}
	}

	return bindingMap, nil
}

func persistServiceBindings(configPath string, bindingMap map[string]*model.ServiceBinding) error {
	return utils.MarshalAndRecord(bindingMap, configPath, "service_bindings.json")
}<commit_msg>Allow explicit specification of volume name when creating service instances<commit_after>package core

import (
	"fmt"
	"log"
	"path"
	"reflect"
	"errors"
	"github.ibm.com/almaden-containers/ibm-storage-broker.git/model"
	"github.ibm.com/almaden-containers/ibm-storage-broker.git/utils"
	"encoding/json"
	"os"
)

const (
	DEFAULT_POLLING_INTERVAL_SECONDS = 10
	DEFAULT_CONTAINER_PATH           = "/var/vcap/data/"
)

//go:generate counterfeiter -o ./fakes/fake_controller.go . Controller

type Controller interface {
	GetCatalog(logger log.Logger) (model.Catalog, error)
	CreateServiceInstance(logger log.Logger, serverInstanceId string, instance model.ServiceInstance) (model.CreateServiceInstanceResponse, error)
	ServiceInstanceExists(logger log.Logger, serviceInstanceId string) bool
	ServiceInstancePropertiesMatch(logger log.Logger, serviceInstanceId string, instance model.ServiceInstance) bool
	DeleteServiceInstance(logger log.Logger, serviceInstanceId string) error
	BindServiceInstance(logger log.Logger, serverInstanceId string, bindingId string, bindingInfo model.ServiceBinding) (model.CreateServiceBindingResponse, error)
	ServiceBindingExists(logger log.Logger, serviceInstanceId string, bindingId string) bool
	ServiceBindingPropertiesMatch(logger log.Logger, serviceInstanceId string, bindingId string, binding model.ServiceBinding) bool
	GetBinding(logger log.Logger, instanceId, bindingId string) (model.ServiceBinding, error)
	UnbindServiceInstance(logger log.Logger, serverInstanceId string, bindingId string) error
}

type StorageBackend interface {
	GetServices() []model.Service
	CreateVolume(serviceInstance model.ServiceInstance, name string, opts map[string]interface{}) error
	RemoveVolume(serviceInstance model.ServiceInstance, name string) error
	ListVolumes(serviceInstance model.ServiceInstance) ([]model.VolumeMetadata, error)
	GetVolume(serviceInstance model.ServiceInstance, name string) (volumeMetadata *model.VolumeMetadata, clientDriverName *string, config *map[string]interface{}, err error)
}

type controller struct {
	backends    map[*model.Service]StorageBackend
	log         *log.Logger
	instanceMap map[string]*model.ServiceInstance
	bindingMap  map[string]*model.ServiceBinding
	configPath  string
}

func NewController(backends map[*model.Service]StorageBackend, configPath string) Controller {

	existingServiceInstances, err := loadServiceInstances(configPath)
	if err != nil {
		log.Fatal(fmt.Sprintf("error reading existing service instances: %s", err.Error()))
	}
	for _, existingServiceInstance := range existingServiceInstances {
		_, err := getServiceById(backends, existingServiceInstance.ServiceId)
		if err != nil {
			log.Fatal(fmt.Sprintf("error reading existing service instances: service instance refers to non-existing or disabled service (ServiceId: %s)", existingServiceInstance.ServiceId))
		}
	}

	existingServiceBindings, err := loadServiceBindings(configPath)
	if err != nil {
		log.Fatal(fmt.Sprintf("error reading existing service bindings: %s", err.Error()))
	}
	for _, existingServiceBinding := range existingServiceBindings {
		_, err = getServiceById(backends, existingServiceBinding.ServiceId)
		if err != nil {
			log.Fatal(fmt.Sprintf("error reading existing service bindings: service binding refers to non-existing or disabled service (ServiceId: %s)", existingServiceBinding.ServiceId))
		}
	}

	return &controller{backends: backends, configPath: configPath, instanceMap: existingServiceInstances, bindingMap: existingServiceBindings}
}

func (c *controller) GetCatalog(logger log.Logger) (model.Catalog, error) {
	allServices := make([]model.Service, 0, len(c.backends))
	for service := range c.backends {
		allServices = append(allServices, *service)
	}
	catalog := model.Catalog{Services: allServices}
	return catalog, nil
}

func (c *controller) CreateServiceInstance(logger log.Logger, serviceInstanceId string, instance model.ServiceInstance) (model.CreateServiceInstanceResponse, error) {
	service, err := getServiceById(c.backends, instance.ServiceId)
	if err != nil {
		logger.Printf("Error: %s", err.Error())
		return model.CreateServiceInstanceResponse{}, err
	}

	instance.Id = serviceInstanceId
	instance.DashboardUrl = "http://dashboard_url"
	instance.LastOperation = &model.LastOperation{
		State:                    "in progress",
		Description:              "creating service instance...",
		AsyncPollIntervalSeconds: DEFAULT_POLLING_INTERVAL_SECONDS,
	}

	volumeName := getVolumeNameForServiceInstance(&instance)
	fmt.Printf("CreateServiceInstance: Creating service instance %s with volume %s: \n", serviceInstanceId, volumeName)
	if err := c.backends[service].CreateVolume(instance, volumeName, nil); err != nil {
		logger.Printf("Error: %s", err.Error())
		return model.CreateServiceInstanceResponse{}, err
	}

	c.instanceMap[serviceInstanceId] = &instance

	if err := persistServiceInstances(c.configPath, c.instanceMap); err != nil {
		logger.Printf("Error: %s", err.Error())
		return model.CreateServiceInstanceResponse{}, err
	}

	response := model.CreateServiceInstanceResponse{
		DashboardUrl:  instance.DashboardUrl,
		LastOperation: instance.LastOperation,
	}

	return response, nil
}

func (c *controller) ServiceInstanceExists(logger log.Logger, serviceInstanceId string) bool {
	_, exists := c.instanceMap[serviceInstanceId]
	return exists
}

func (c *controller) ServiceInstancePropertiesMatch(logger log.Logger, serviceInstanceId string, instance model.ServiceInstance) bool {
	existingServiceInstance, exists := c.instanceMap[serviceInstanceId]
	if exists == false {
		return false
	}
	if existingServiceInstance.PlanId != instance.PlanId {
		return false
	}
	if existingServiceInstance.SpaceGuid != instance.SpaceGuid {
		return false
	}
	if existingServiceInstance.OrganizationGuid != instance.OrganizationGuid {
		return false
	}
	areParamsEqual := reflect.DeepEqual(existingServiceInstance.Parameters, instance.Parameters)
	return areParamsEqual
}

func (c *controller) DeleteServiceInstance(logger log.Logger, serviceInstanceId string) error {
	serviceInstance := c.instanceMap[serviceInstanceId]
	service, err := getServiceById(c.backends, (*serviceInstance).ServiceId)
	if err != nil {
		logger.Printf("Error: %s", err.Error())
		return err
	}
	if err := c.backends[service].RemoveVolume(*serviceInstance, getVolumeNameForServiceInstance(serviceInstance)); err != nil {
		logger.Printf("Error: %s", err.Error())
		return err
	}

	delete(c.instanceMap, serviceInstanceId)

	if err := persistServiceInstances(c.configPath, c.instanceMap); err != nil {
		logger.Printf("Error: %s", err.Error())
		return err
	}
	return nil
}

func (c *controller) BindServiceInstance(logger log.Logger, serviceInstanceId string, bindingId string, bindingInfo model.ServiceBinding) (model.CreateServiceBindingResponse, error) {
	serviceInstance := c.instanceMap[serviceInstanceId]
	service, err := getServiceById(c.backends, (*serviceInstance).ServiceId)
	if err != nil {
		return model.CreateServiceBindingResponse{}, err
	}

	c.bindingMap[bindingId] = &bindingInfo
	volumeName := getVolumeNameForServiceInstance(serviceInstance)
	_, clientDriverName, config, err := c.backends[service].GetVolume(*serviceInstance, volumeName)
	if err != nil {
		logger.Printf("Error: %s", err.Error())
		return model.CreateServiceBindingResponse{}, err
	}
	containerMountPath := determineContainerMountPath(bindingInfo.Parameters, serviceInstanceId)

	configJson, err := json.Marshal(*config)
	if err != nil{
		logger.Printf("Error: %s", err.Error())
		return model.CreateServiceBindingResponse{}, err
	}

	privateDetails := model.VolumeMountPrivateDetails{Driver: *clientDriverName, GroupId: volumeName, Config: string(configJson)}
	volumeMount := model.VolumeMount{ContainerPath: containerMountPath, Mode: "rw", Private: privateDetails}
	volumeMounts := []model.VolumeMount{volumeMount}

	if err = persistServiceBindings(c.configPath, c.bindingMap); err != nil {
		logger.Printf("Error: %s", err.Error())
		return model.CreateServiceBindingResponse{}, err
	}

	createBindingResponse := model.CreateServiceBindingResponse{VolumeMounts: volumeMounts}
	return createBindingResponse, nil
}

func (c *controller) ServiceBindingExists(logger log.Logger, serviceInstanceId string, bindingId string) bool {
	_, exists := c.bindingMap[bindingId]
	return exists
}

func (c *controller) ServiceBindingPropertiesMatch(logger log.Logger, serviceInstanceId string, bindingId string, binding model.ServiceBinding) bool {
	existingBinding, exists := c.bindingMap[bindingId]
	if exists == false {
		return false
	}
	if existingBinding.AppId != binding.AppId {
		return false
	}
	if existingBinding.ServicePlanId != binding.ServicePlanId {
		return false
	}
	if existingBinding.ServiceId != binding.ServiceId {
		return false
	}
	if existingBinding.ServiceInstanceId != binding.ServiceInstanceId {
		return false
	}
	if existingBinding.Id != binding.Id {
		return false
	}
	return true
}

func (c *controller) GetBinding(logger log.Logger, instanceId, bindingId string) (model.ServiceBinding, error) {
	binding, exists := c.bindingMap[bindingId]
	if exists == true {
		return *binding, nil
	}
	return model.ServiceBinding{}, fmt.Errorf("binding not found")

}

func (c *controller) UnbindServiceInstance(logger log.Logger, serverInstanceId string, bindingId string) error {
	delete(c.bindingMap, bindingId)
	err := utils.MarshalAndRecord(c.bindingMap, c.configPath, "service_bindings.json")
	if err != nil {
		return err
	}
	return nil
}

func determineContainerMountPath(parameters map[string]interface{}, volId string) string {
	if containerPath, ok := parameters["container_path"]; ok {
		return containerPath.(string)
	}
	if containerPath, ok := parameters["path"]; ok {
		return containerPath.(string)
	}
	return path.Join(DEFAULT_CONTAINER_PATH, volId)
}

func getServiceById(backendsMap map[*model.Service]StorageBackend, serviceId string) (*model.Service, error) {
	for service := range backendsMap {
		if (*service).Id == serviceId {
			return service, nil
		}
	}
	return nil, errors.New(fmt.Sprintf("Could not locate service for serviceId %s", serviceId))
}

func loadServiceInstances(configPath string) (map[string]*model.ServiceInstance, error) {
	var serviceInstancesMap map[string]*model.ServiceInstance

	err := utils.ReadAndUnmarshal(&serviceInstancesMap, configPath, "service_instances.json")
	if err != nil {
		if os.IsNotExist(err) {
			fmt.Printf("WARNING: service instance data file '%s' does not exist: \n", "service_instances.json")
			serviceInstancesMap = make(map[string]*model.ServiceInstance)
		} else {
			return nil, errors.New(fmt.Sprintf("Could not load the service instances, message: %s", err.Error()))
		}
	}

	return serviceInstancesMap, nil
}

func persistServiceInstances(configPath string, instanceMap map[string]*model.ServiceInstance) error {
	return utils.MarshalAndRecord(instanceMap, configPath, "service_instances.json")
}

func loadServiceBindings(configPath string) (map[string]*model.ServiceBinding, error) {
	var bindingMap map[string]*model.ServiceBinding
	err := utils.ReadAndUnmarshal(&bindingMap, configPath, "service_bindings.json")
	if err != nil {
		if os.IsNotExist(err) {
			fmt.Printf("WARNING: key map data file '%s' does not exist: \n", "service_bindings.json")
			bindingMap = make(map[string]*model.ServiceBinding)
		} else {
			return nil, errors.New(fmt.Sprintf("Could not load the service instances, message: %s", err.Error()))
		}
	}

	return bindingMap, nil
}

func persistServiceBindings(configPath string, bindingMap map[string]*model.ServiceBinding) error {
	return utils.MarshalAndRecord(bindingMap, configPath, "service_bindings.json")
}

func getVolumeNameForServiceInstance(serviceInstance *model.ServiceInstance) string {
	volumeName := (*serviceInstance).Id // default to Service Instance ID as volume name if not provided
	if (*serviceInstance).Parameters != nil {
		volumeNameParam, ok := (*serviceInstance).Parameters.(map[string]interface{})["volumeName"]
		if ok {
			volumeName = volumeNameParam.(string)
		}
	}
	return volumeName
}<|endoftext|>


<commit_before>package cloud

import (
	"context"
	"errors"
	"fmt"
	"net/http"
)

// StatusCategoryService handles status categories for the Jira instance / API.
//
// Use it to obtain a list of all status categories and the details of a category.
//
// Status categories provided a mechanism for categorizing statuses.
//
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-status-categories/#api-group-workflow-status-categories
type StatusCategoryService service

// StatusCategory represents the category a status belongs to.
// Those categories can be user defined in every Jira instance.
type StatusCategory struct {
	Self      string `json:"self" structs:"self"`
	ID        int    `json:"id" structs:"id"`
	Name      string `json:"name" structs:"name"`
	Key       string `json:"key" structs:"key"`
	ColorName string `json:"colorName" structs:"colorName"`
}

// These constants are the keys of the default Jira status categories
const (
	StatusCategoryComplete   = "done"
	StatusCategoryInProgress = "indeterminate"
	StatusCategoryToDo       = "new"
	StatusCategoryUndefined  = "undefined"
)

// GetList gets all status categories from Jira
//
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-status-categories/#api-rest-api-3-statuscategory-get
func (s *StatusCategoryService) GetList(ctx context.Context) ([]StatusCategory, *Response, error) {
	apiEndpoint := "/rest/api/3/statuscategory"
	req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil)
	if err != nil {
		return nil, nil, err
	}

	var statusCategories []StatusCategory
	resp, err := s.client.Do(req, &statusCategories)
	if err != nil {
		return nil, resp, NewJiraError(resp, err)
	}
	return statusCategories, resp, nil
}

// Get returns a status category.
//
// Status categories provided a mechanism for categorizing statuses.
//
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-status-categories/#api-rest-api-3-statuscategory-idorkey-get
func (s *StatusCategoryService) Get(ctx context.Context, statusCategoryID string) (*StatusCategory, *Response, error) {

	if statusCategoryID == "" {
		return nil, nil, errors.New("jira: not status category set")
	}

	apiEndpoint := fmt.Sprintf("/rest/api/3/statuscategory/%v", statusCategoryID)
	req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil)
	if err != nil {
		return nil, nil, err
	}

	statusCategory := new(StatusCategory)
	resp, err := s.client.Do(req, statusCategory)
	if err != nil {
		return nil, resp, NewJiraError(resp, err)
	}

	return statusCategory, resp, nil
}
<commit_msg>Cloud/Status Category: Smaller godoc changes<commit_after>package cloud

import (
	"context"
	"errors"
	"fmt"
	"net/http"
)

// StatusCategoryService handles status categories for the Jira instance / API.
//
// Use it to obtain a list of all status categories and the details of a category.
// Status categories provided a mechanism for categorizing statuses.
//
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-status-categories/#api-group-workflow-status-categories
type StatusCategoryService service

// StatusCategory represents the category a status belongs to.
// Those categories can be user defined in every Jira instance.
type StatusCategory struct {
	Self      string `json:"self" structs:"self"`
	ID        int    `json:"id" structs:"id"`
	Name      string `json:"name" structs:"name"`
	Key       string `json:"key" structs:"key"`
	ColorName string `json:"colorName" structs:"colorName"`
}

// These constants are the keys of the default Jira status categories
const (
	StatusCategoryComplete   = "done"
	StatusCategoryInProgress = "indeterminate"
	StatusCategoryToDo       = "new"
	StatusCategoryUndefined  = "undefined"
)

// GetList returns a list of all status categories.
//
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-status-categories/#api-rest-api-3-statuscategory-get
func (s *StatusCategoryService) GetList(ctx context.Context) ([]StatusCategory, *Response, error) {
	apiEndpoint := "/rest/api/3/statuscategory"
	req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil)
	if err != nil {
		return nil, nil, err
	}

	var statusCategories []StatusCategory
	resp, err := s.client.Do(req, &statusCategories)
	if err != nil {
		return nil, resp, NewJiraError(resp, err)
	}

	return statusCategories, resp, nil
}

// Get returns a status category.
// Status categories provided a mechanism for categorizing statuses.
//
// statusCategoryID represents the ID or key of the status category.
//
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-status-categories/#api-rest-api-3-statuscategory-idorkey-get
func (s *StatusCategoryService) Get(ctx context.Context, statusCategoryID string) (*StatusCategory, *Response, error) {
	if statusCategoryID == "" {
		return nil, nil, errors.New("jira: not status category set")
	}

	apiEndpoint := fmt.Sprintf("/rest/api/3/statuscategory/%v", statusCategoryID)
	req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil)
	if err != nil {
		return nil, nil, err
	}

	statusCategory := new(StatusCategory)
	resp, err := s.client.Do(req, statusCategory)
	if err != nil {
		return nil, resp, NewJiraError(resp, err)
	}

	return statusCategory, resp, nil
}
<|endoftext|>


<commit_before>package i18n

import (
	"encoding/xml"
	"errors"
	"io/ioutil"
	"os"
)

type Xliff struct {
	XMLName xml.Name `xml:"xliff"`
	File    File     `xml:"file"`
	Version string   `xml:"version,attr"`
	Xmlns   string   `xml:"xmlns,attr"`
}
type File struct {
	Body           Body   `xml:"body"`
	Original       string `xml:"original,attr"`
	Datatype       string `xml:"datatype,attr"`
	SourceLanguage string `xml:"source-language,attr"`
	TargetLanguage string `xml:"target-language,attr,omitempty"`
}
type Body struct {
	TransList []TransUnit `xml:"trans-unit"`
}
type TransUnit struct {
	Source string   `xml:"source"`
	Target string   `xml:"target"`
	Note   []string `xml:"note"`
}

func XliffParser(fp *os.File) (Catalog, error) {
	defer fp.Close()
	xmldata, err := ioutil.ReadAll(fp)
	if err != nil {
		return nil, errors.New("Couldn't read file")
	}

	q := Xliff{}
	err = xml.Unmarshal(xmldata, &q)
	if err != nil {
		return nil, err
	}

	catalog := make(Catalog)

	for _, transunit := range q.File.Body.TransList {
		catalog[transunit.Source] = transunit.Target
	}

	return catalog, nil
}
func (b *Body) Add(source, target string) {
	transunit := TransUnit{
		Source: source,
		Target: target,
	}
	b.TransList = append(b.TransList, transunit)
}

func CreateXliff(catalog Catalog, sourceLanguage, targetLanguage string) []byte {

	xliff := Xliff{
		File: File{
			Body:           Body{},
			Original:       "file.ext",
			Datatype:       "plaintext",
			SourceLanguage: sourceLanguage,
			TargetLanguage: targetLanguage,
		},
		Version: "1.2",
		Xmlns:   "urn:oasis:names:tc:xliff:document:1.2",
	}
	for source, target := range catalog {
		xliff.File.Body.Add(source, target)
	}

	xml, err := xml.Marshal(xliff)
	if err != nil {
		panic(err)
	}

	return xml
}
<commit_msg>Sort alphabetically<commit_after>package i18n

import (
	"encoding/xml"
	"errors"
	"io/ioutil"
	"os"
	"sort"
)

type Xliff struct {
	XMLName xml.Name `xml:"xliff"`
	File    File     `xml:"file"`
	Version string   `xml:"version,attr"`
	Xmlns   string   `xml:"xmlns,attr"`
}
type File struct {
	Body           Body   `xml:"body"`
	Original       string `xml:"original,attr"`
	Datatype       string `xml:"datatype,attr"`
	SourceLanguage string `xml:"source-language,attr"`
	TargetLanguage string `xml:"target-language,attr,omitempty"`
}
type Body struct {
	TransList []TransUnit `xml:"trans-unit"`
}
type TransUnit struct {
	Source string   `xml:"source"`
	Target string   `xml:"target"`
	Note   []string `xml:"note"`
}

func XliffParser(fp *os.File) (Catalog, error) {
	defer fp.Close()
	xmldata, err := ioutil.ReadAll(fp)
	if err != nil {
		return nil, errors.New("Couldn't read file")
	}

	q := Xliff{}
	err = xml.Unmarshal(xmldata, &q)
	if err != nil {
		return nil, err
	}

	catalog := make(Catalog)

	for _, transunit := range q.File.Body.TransList {
		catalog[transunit.Source] = transunit.Target
	}

	return catalog, nil
}
func (b *Body) Add(source, target string) {
	transunit := TransUnit{
		Source: source,
		Target: target,
	}
	b.TransList = append(b.TransList, transunit)
}

type Alphabetically []TransUnit

func (a Alphabetically) Len() int           { return len(a) }
func (a Alphabetically) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a Alphabetically) Less(i, j int) bool { return a[i].Source < a[j].Source }

func (b *Body) SortAlphabetically() {
	sort.Sort(Alphabetically(b.TransList))
}

func CreateXliff(catalog Catalog, sourceLanguage, targetLanguage string) []byte {

	xliff := Xliff{
		File: File{
			Body:           Body{},
			Original:       "file.ext",
			Datatype:       "plaintext",
			SourceLanguage: sourceLanguage,
			TargetLanguage: targetLanguage,
		},
		Version: "1.2",
		Xmlns:   "urn:oasis:names:tc:xliff:document:1.2",
	}
	for source, target := range catalog {
		xliff.File.Body.Add(source, target)
	}

	xliff.File.Body.SortAlphabetically()

	xml, err := xml.Marshal(xliff)
	if err != nil {
		panic(err)
	}

	return xml
}
<|endoftext|>