<commit_before>// Copyright 2012 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 net

import (
	"reflect"
	"runtime"
	"strings"
	"testing"
	"time"
)

func TestResolveUDPAddr(t *testing.T) {
	for _, tt := range resolveTCPAddrTests {
		net := strings.Replace(tt.net, "tcp", "udp", -1)
		addr, err := ResolveUDPAddr(net, tt.litAddrOrName)
		if err != tt.err {
			t.Fatalf("ResolveUDPAddr(%q, %q) failed: %v", net, tt.litAddrOrName, err)
		}
		if !reflect.DeepEqual(addr, (*UDPAddr)(tt.addr)) {
			t.Fatalf("ResolveUDPAddr(%q, %q) = %#v, want %#v", net, tt.litAddrOrName, addr, tt.addr)
		}
		if err == nil {
			str := addr.String()
			addr1, err := ResolveUDPAddr(net, str)
			if err != nil {
				t.Fatalf("ResolveUDPAddr(%q, %q) [from %q]: %v", net, str, tt.litAddrOrName, err)
			}
			if !reflect.DeepEqual(addr1, addr) {
				t.Fatalf("ResolveUDPAddr(%q, %q) [from %q] = %#v, want %#v", net, str, tt.litAddrOrName, addr1, addr)
			}
		}
	}
}

func TestReadFromUDP(t *testing.T) {
	ra, err := ResolveUDPAddr("udp", "127.0.0.1:7")
	if err != nil {
		t.Fatal(err)
	}

	la, err := ResolveUDPAddr("udp", "127.0.0.1:0")
	if err != nil {
		t.Fatal(err)
	}

	c, err := ListenUDP("udp", la)
	if err != nil {
		t.Fatal(err)
	}
	defer c.Close()

	_, err = c.WriteToUDP([]byte("a"), ra)
	if err != nil {
		t.Fatal(err)
	}

	err = c.SetDeadline(time.Now().Add(100 * time.Millisecond))
	if err != nil {
		t.Fatal(err)
	}
	b := make([]byte, 1)
	_, _, err = c.ReadFromUDP(b)
	if err == nil {
		t.Fatal("ReadFromUDP should fail")
	} else if !isTimeout(err) {
		t.Fatal(err)
	}
}

func TestWriteToUDP(t *testing.T) {
	switch runtime.GOOS {
	case "plan9":
		t.Skipf("skipping test on %q", runtime.GOOS)
	}

	l, err := ListenPacket("udp", "127.0.0.1:0")
	if err != nil {
		t.Fatalf("Listen failed: %v", err)
	}
	defer l.Close()

	testWriteToConn(t, l.LocalAddr().String())
	testWriteToPacketConn(t, l.LocalAddr().String())
}

func testWriteToConn(t *testing.T, raddr string) {
	c, err := Dial("udp", raddr)
	if err != nil {
		t.Fatalf("Dial failed: %v", err)
	}
	defer c.Close()

	ra, err := ResolveUDPAddr("udp", raddr)
	if err != nil {
		t.Fatalf("ResolveUDPAddr failed: %v", err)
	}

	_, err = c.(*UDPConn).WriteToUDP([]byte("Connection-oriented mode socket"), ra)
	if err == nil {
		t.Fatal("WriteToUDP should fail")
	}
	if err != nil && err.(*OpError).Err != ErrWriteToConnected {
		t.Fatalf("WriteToUDP should fail as ErrWriteToConnected: %v", err)
	}

	_, err = c.(*UDPConn).WriteTo([]byte("Connection-oriented mode socket"), ra)
	if err == nil {
		t.Fatal("WriteTo should fail")
	}
	if err != nil && err.(*OpError).Err != ErrWriteToConnected {
		t.Fatalf("WriteTo should fail as ErrWriteToConnected: %v", err)
	}

	_, err = c.Write([]byte("Connection-oriented mode socket"))
	if err != nil {
		t.Fatalf("Write failed: %v", err)
	}
}

func testWriteToPacketConn(t *testing.T, raddr string) {
	c, err := ListenPacket("udp", "127.0.0.1:0")
	if err != nil {
		t.Fatalf("ListenPacket failed: %v", err)
	}
	defer c.Close()

	ra, err := ResolveUDPAddr("udp", raddr)
	if err != nil {
		t.Fatalf("ResolveUDPAddr failed: %v", err)
	}

	_, err = c.(*UDPConn).WriteToUDP([]byte("Connection-less mode socket"), ra)
	if err != nil {
		t.Fatalf("WriteToUDP failed: %v", err)
	}

	_, err = c.WriteTo([]byte("Connection-less mode socket"), ra)
	if err != nil {
		t.Fatalf("WriteTo failed: %v", err)
	}

	_, err = c.(*UDPConn).Write([]byte("Connection-less mode socket"))
	if err == nil {
		t.Fatal("Write should fail")
	}
}

var udpConnLocalNameTests = []struct {
	net   string
	laddr *UDPAddr
}{
	{"udp4", &UDPAddr{IP: IPv4(127, 0, 0, 1)}},
	{"udp4", &UDPAddr{}},
	{"udp4", nil},
}

func TestUDPConnLocalName(t *testing.T) {
	if testing.Short() || !*testExternal {
		t.Skip("skipping test to avoid external network")
	}

	for _, tt := range udpConnLocalNameTests {
		c, err := ListenUDP(tt.net, tt.laddr)
		if err != nil {
			t.Fatalf("ListenUDP failed: %v", err)
		}
		defer c.Close()
		la := c.LocalAddr()
		if a, ok := la.(*UDPAddr); !ok || a.Port == 0 {
			t.Fatalf("got %v; expected a proper address with non-zero port number", la)
		}
	}
}

func TestUDPConnLocalAndRemoteNames(t *testing.T) {
	for _, laddr := range []string{"", "127.0.0.1:0"} {
		c1, err := ListenPacket("udp", "127.0.0.1:0")
		if err != nil {
			t.Fatalf("ListenUDP failed: %v", err)
		}
		defer c1.Close()

		var la *UDPAddr
		if laddr != "" {
			var err error
			if la, err = ResolveUDPAddr("udp", laddr); err != nil {
				t.Fatalf("ResolveUDPAddr failed: %v", err)
			}
		}
		c2, err := DialUDP("udp", la, c1.LocalAddr().(*UDPAddr))
		if err != nil {
			t.Fatalf("DialUDP failed: %v", err)
		}
		defer c2.Close()

		var connAddrs = [4]struct {
			got Addr
			ok  bool
		}{
			{c1.LocalAddr(), true},
			{c1.(*UDPConn).RemoteAddr(), false},
			{c2.LocalAddr(), true},
			{c2.RemoteAddr(), true},
		}
		for _, ca := range connAddrs {
			if a, ok := ca.got.(*UDPAddr); ok != ca.ok || ok && a.Port == 0 {
				t.Fatalf("got %v; expected a proper address with non-zero port number", ca.got)
			}
		}
	}
}

func TestIPv6LinkLocalUnicastUDP(t *testing.T) {
	if testing.Short() || !*testExternal {
		t.Skip("skipping test to avoid external network")
	}
	if !supportsIPv6 {
		t.Skip("ipv6 is not supported")
	}
	ifi := loopbackInterface()
	if ifi == nil {
		t.Skip("loopback interface not found")
	}
	laddr := ipv6LinkLocalUnicastAddr(ifi)
	if laddr == "" {
		t.Skip("ipv6 unicast address on loopback not found")
	}

	type test struct {
		net, addr  string
		nameLookup bool
	}
	var tests = []test{
		{"udp", "[" + laddr + "%" + ifi.Name + "]:0", false},
		{"udp6", "[" + laddr + "%" + ifi.Name + "]:0", false},
	}
	// The first udp test fails on DragonFly - see issue 7473.
	if runtime.GOOS == "dragonfly" {
		tests = tests[1:]
	}
	switch runtime.GOOS {
	case "darwin", "dragonfly", "freebsd", "openbsd", "netbsd":
		tests = append(tests, []test{
			{"udp", "[localhost%" + ifi.Name + "]:0", true},
			{"udp6", "[localhost%" + ifi.Name + "]:0", true},
		}...)
	case "linux":
		tests = append(tests, []test{
			{"udp", "[ip6-localhost%" + ifi.Name + "]:0", true},
			{"udp6", "[ip6-localhost%" + ifi.Name + "]:0", true},
		}...)
	}
	for _, tt := range tests {
		c1, err := ListenPacket(tt.net, tt.addr)
		if err != nil {
			// It might return "LookupHost returned no
			// suitable address" error on some platforms.
			t.Logf("ListenPacket failed: %v", err)
			continue
		}
		defer c1.Close()
		if la, ok := c1.LocalAddr().(*UDPAddr); !ok || !tt.nameLookup && la.Zone == "" {
			t.Fatalf("got %v; expected a proper address with zone identifier", la)
		}

		c2, err := Dial(tt.net, c1.LocalAddr().String())
		if err != nil {
			t.Fatalf("Dial failed: %v", err)
		}
		defer c2.Close()
		if la, ok := c2.LocalAddr().(*UDPAddr); !ok || !tt.nameLookup && la.Zone == "" {
			t.Fatalf("got %v; expected a proper address with zone identifier", la)
		}
		if ra, ok := c2.RemoteAddr().(*UDPAddr); !ok || !tt.nameLookup && ra.Zone == "" {
			t.Fatalf("got %v; expected a proper address with zone identifier", ra)
		}

		if _, err := c2.Write([]byte("UDP OVER IPV6 LINKLOCAL TEST")); err != nil {
			t.Fatalf("Conn.Write failed: %v", err)
		}
		b := make([]byte, 32)
		if _, from, err := c1.ReadFrom(b); err != nil {
			t.Fatalf("PacketConn.ReadFrom failed: %v", err)
		} else {
			if ra, ok := from.(*UDPAddr); !ok || !tt.nameLookup && ra.Zone == "" {
				t.Fatalf("got %v; expected a proper address with zone identifier", ra)
			}
		}
	}
}
<commit_msg>net: skip new TestReadFromUDP on nacl and plan9 (fixes build)<commit_after>// Copyright 2012 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 net

import (
	"reflect"
	"runtime"
	"strings"
	"testing"
	"time"
)

func TestResolveUDPAddr(t *testing.T) {
	for _, tt := range resolveTCPAddrTests {
		net := strings.Replace(tt.net, "tcp", "udp", -1)
		addr, err := ResolveUDPAddr(net, tt.litAddrOrName)
		if err != tt.err {
			t.Fatalf("ResolveUDPAddr(%q, %q) failed: %v", net, tt.litAddrOrName, err)
		}
		if !reflect.DeepEqual(addr, (*UDPAddr)(tt.addr)) {
			t.Fatalf("ResolveUDPAddr(%q, %q) = %#v, want %#v", net, tt.litAddrOrName, addr, tt.addr)
		}
		if err == nil {
			str := addr.String()
			addr1, err := ResolveUDPAddr(net, str)
			if err != nil {
				t.Fatalf("ResolveUDPAddr(%q, %q) [from %q]: %v", net, str, tt.litAddrOrName, err)
			}
			if !reflect.DeepEqual(addr1, addr) {
				t.Fatalf("ResolveUDPAddr(%q, %q) [from %q] = %#v, want %#v", net, str, tt.litAddrOrName, addr1, addr)
			}
		}
	}
}

func TestReadFromUDP(t *testing.T) {
	switch runtime.GOOS {
	case "nacl", "plan9":
		t.Skipf("skipping test on %q", runtime.GOOS)
	}

	ra, err := ResolveUDPAddr("udp", "127.0.0.1:7")
	if err != nil {
		t.Fatal(err)
	}

	la, err := ResolveUDPAddr("udp", "127.0.0.1:0")
	if err != nil {
		t.Fatal(err)
	}

	c, err := ListenUDP("udp", la)
	if err != nil {
		t.Fatal(err)
	}
	defer c.Close()

	_, err = c.WriteToUDP([]byte("a"), ra)
	if err != nil {
		t.Fatal(err)
	}

	err = c.SetDeadline(time.Now().Add(100 * time.Millisecond))
	if err != nil {
		t.Fatal(err)
	}
	b := make([]byte, 1)
	_, _, err = c.ReadFromUDP(b)
	if err == nil {
		t.Fatal("ReadFromUDP should fail")
	} else if !isTimeout(err) {
		t.Fatal(err)
	}
}

func TestWriteToUDP(t *testing.T) {
	switch runtime.GOOS {
	case "plan9":
		t.Skipf("skipping test on %q", runtime.GOOS)
	}

	l, err := ListenPacket("udp", "127.0.0.1:0")
	if err != nil {
		t.Fatalf("Listen failed: %v", err)
	}
	defer l.Close()

	testWriteToConn(t, l.LocalAddr().String())
	testWriteToPacketConn(t, l.LocalAddr().String())
}

func testWriteToConn(t *testing.T, raddr string) {
	c, err := Dial("udp", raddr)
	if err != nil {
		t.Fatalf("Dial failed: %v", err)
	}
	defer c.Close()

	ra, err := ResolveUDPAddr("udp", raddr)
	if err != nil {
		t.Fatalf("ResolveUDPAddr failed: %v", err)
	}

	_, err = c.(*UDPConn).WriteToUDP([]byte("Connection-oriented mode socket"), ra)
	if err == nil {
		t.Fatal("WriteToUDP should fail")
	}
	if err != nil && err.(*OpError).Err != ErrWriteToConnected {
		t.Fatalf("WriteToUDP should fail as ErrWriteToConnected: %v", err)
	}

	_, err = c.(*UDPConn).WriteTo([]byte("Connection-oriented mode socket"), ra)
	if err == nil {
		t.Fatal("WriteTo should fail")
	}
	if err != nil && err.(*OpError).Err != ErrWriteToConnected {
		t.Fatalf("WriteTo should fail as ErrWriteToConnected: %v", err)
	}

	_, err = c.Write([]byte("Connection-oriented mode socket"))
	if err != nil {
		t.Fatalf("Write failed: %v", err)
	}
}

func testWriteToPacketConn(t *testing.T, raddr string) {
	c, err := ListenPacket("udp", "127.0.0.1:0")
	if err != nil {
		t.Fatalf("ListenPacket failed: %v", err)
	}
	defer c.Close()

	ra, err := ResolveUDPAddr("udp", raddr)
	if err != nil {
		t.Fatalf("ResolveUDPAddr failed: %v", err)
	}

	_, err = c.(*UDPConn).WriteToUDP([]byte("Connection-less mode socket"), ra)
	if err != nil {
		t.Fatalf("WriteToUDP failed: %v", err)
	}

	_, err = c.WriteTo([]byte("Connection-less mode socket"), ra)
	if err != nil {
		t.Fatalf("WriteTo failed: %v", err)
	}

	_, err = c.(*UDPConn).Write([]byte("Connection-less mode socket"))
	if err == nil {
		t.Fatal("Write should fail")
	}
}

var udpConnLocalNameTests = []struct {
	net   string
	laddr *UDPAddr
}{
	{"udp4", &UDPAddr{IP: IPv4(127, 0, 0, 1)}},
	{"udp4", &UDPAddr{}},
	{"udp4", nil},
}

func TestUDPConnLocalName(t *testing.T) {
	if testing.Short() || !*testExternal {
		t.Skip("skipping test to avoid external network")
	}

	for _, tt := range udpConnLocalNameTests {
		c, err := ListenUDP(tt.net, tt.laddr)
		if err != nil {
			t.Fatalf("ListenUDP failed: %v", err)
		}
		defer c.Close()
		la := c.LocalAddr()
		if a, ok := la.(*UDPAddr); !ok || a.Port == 0 {
			t.Fatalf("got %v; expected a proper address with non-zero port number", la)
		}
	}
}

func TestUDPConnLocalAndRemoteNames(t *testing.T) {
	for _, laddr := range []string{"", "127.0.0.1:0"} {
		c1, err := ListenPacket("udp", "127.0.0.1:0")
		if err != nil {
			t.Fatalf("ListenUDP failed: %v", err)
		}
		defer c1.Close()

		var la *UDPAddr
		if laddr != "" {
			var err error
			if la, err = ResolveUDPAddr("udp", laddr); err != nil {
				t.Fatalf("ResolveUDPAddr failed: %v", err)
			}
		}
		c2, err := DialUDP("udp", la, c1.LocalAddr().(*UDPAddr))
		if err != nil {
			t.Fatalf("DialUDP failed: %v", err)
		}
		defer c2.Close()

		var connAddrs = [4]struct {
			got Addr
			ok  bool
		}{
			{c1.LocalAddr(), true},
			{c1.(*UDPConn).RemoteAddr(), false},
			{c2.LocalAddr(), true},
			{c2.RemoteAddr(), true},
		}
		for _, ca := range connAddrs {
			if a, ok := ca.got.(*UDPAddr); ok != ca.ok || ok && a.Port == 0 {
				t.Fatalf("got %v; expected a proper address with non-zero port number", ca.got)
			}
		}
	}
}

func TestIPv6LinkLocalUnicastUDP(t *testing.T) {
	if testing.Short() || !*testExternal {
		t.Skip("skipping test to avoid external network")
	}
	if !supportsIPv6 {
		t.Skip("ipv6 is not supported")
	}
	ifi := loopbackInterface()
	if ifi == nil {
		t.Skip("loopback interface not found")
	}
	laddr := ipv6LinkLocalUnicastAddr(ifi)
	if laddr == "" {
		t.Skip("ipv6 unicast address on loopback not found")
	}

	type test struct {
		net, addr  string
		nameLookup bool
	}
	var tests = []test{
		{"udp", "[" + laddr + "%" + ifi.Name + "]:0", false},
		{"udp6", "[" + laddr + "%" + ifi.Name + "]:0", false},
	}
	// The first udp test fails on DragonFly - see issue 7473.
	if runtime.GOOS == "dragonfly" {
		tests = tests[1:]
	}
	switch runtime.GOOS {
	case "darwin", "dragonfly", "freebsd", "openbsd", "netbsd":
		tests = append(tests, []test{
			{"udp", "[localhost%" + ifi.Name + "]:0", true},
			{"udp6", "[localhost%" + ifi.Name + "]:0", true},
		}...)
	case "linux":
		tests = append(tests, []test{
			{"udp", "[ip6-localhost%" + ifi.Name + "]:0", true},
			{"udp6", "[ip6-localhost%" + ifi.Name + "]:0", true},
		}...)
	}
	for _, tt := range tests {
		c1, err := ListenPacket(tt.net, tt.addr)
		if err != nil {
			// It might return "LookupHost returned no
			// suitable address" error on some platforms.
			t.Logf("ListenPacket failed: %v", err)
			continue
		}
		defer c1.Close()
		if la, ok := c1.LocalAddr().(*UDPAddr); !ok || !tt.nameLookup && la.Zone == "" {
			t.Fatalf("got %v; expected a proper address with zone identifier", la)
		}

		c2, err := Dial(tt.net, c1.LocalAddr().String())
		if err != nil {
			t.Fatalf("Dial failed: %v", err)
		}
		defer c2.Close()
		if la, ok := c2.LocalAddr().(*UDPAddr); !ok || !tt.nameLookup && la.Zone == "" {
			t.Fatalf("got %v; expected a proper address with zone identifier", la)
		}
		if ra, ok := c2.RemoteAddr().(*UDPAddr); !ok || !tt.nameLookup && ra.Zone == "" {
			t.Fatalf("got %v; expected a proper address with zone identifier", ra)
		}

		if _, err := c2.Write([]byte("UDP OVER IPV6 LINKLOCAL TEST")); err != nil {
			t.Fatalf("Conn.Write failed: %v", err)
		}
		b := make([]byte, 32)
		if _, from, err := c1.ReadFrom(b); err != nil {
			t.Fatalf("PacketConn.ReadFrom failed: %v", err)
		} else {
			if ra, ok := from.(*UDPAddr); !ok || !tt.nameLookup && ra.Zone == "" {
				t.Fatalf("got %v; expected a proper address with zone identifier", ra)
			}
		}
	}
}
<|endoftext|>


<commit_before>package pinboard

import (
	"encoding/json"
	"errors"
	"net/url"
	"strings"
	"time"
)

// Aliasing this to get a custom UnmarshalJSON because Pinboard
// can return `"description": false` in the JSON output.
type descriptionType string

// Post represents a bookmark.
type Post struct {
	// URL of bookmark.
	Href *url.URL

	// Title of bookmark. This field is unfortunately named
	// 'description' for backwards compatibility with the
	// delicious API
	Description string

	// Description of the item. Called 'extended' for backwards
	// compatibility with delicious API.
	Extended []byte

	// Tags of bookmark.
	Tags []string

	// If the bookmark is private or public.
	Shared bool

	// If the bookmark is marked to read later.
	Toread bool

	// Create time for this bookmark.
	Time time.Time

	// Change detection signature of the bookmark.
	Meta []byte

	// Hash of the bookmark.
	Hash []byte

	// The number of other users who have bookmarked this same
	// item.
	Others int
}

// post represents intermediate post response data before type
// conversion.
type post struct {
	Href        string
	Description descriptionType
	Extended    string
	Tags        string
	Shared      string
	Toread      string
	Time        string
	Meta        string
	Hash        string
	Others      int
}

// toPost converts a post to a type correct Post.
func (p *post) toPost() (*Post, error) {
	href, err := url.Parse(p.Href)
	if err != nil {
		return nil, err
	}

	tags := strings.Split(p.Tags, " ")

	var shared, toread bool
	if p.Shared == "yes" {
		shared = true
	}

	if p.Toread == "yes" {
		toread = true
	}

	dt, err := time.Parse(time.RFC3339, p.Time)
	if err != nil {
		return nil, err
	}

	P := Post{
		Href:        href,
		Description: p.Description.String(),
		Extended:    []byte(p.Extended),
		Tags:        tags,
		Shared:      shared,
		Toread:      toread,
		Time:        dt,
		Meta:        []byte(p.Meta),
		Hash:        []byte(p.Hash),
		Others:      p.Others,
	}

	return &P, nil
}

// postsResponse represents a response from certain /posts/ endpoints.
type postsResponse struct {
	UpdateTime string `json:"update_time,omitempty"`
	ResultCode string `json:"result_code,omitempty"`
	Date       string `json:"date,omitempty"`
	User       string `json:"user,omitempty"`
	Posts      []post `json:"posts,omitempty"`
}

// PostsUpdate returns the most recent time a bookmark was added,
// updated or deleted.
//
// https://pinboard.in/api/#posts_update
func PostsUpdate() (time.Time, error) {
	resp, err := get("postsUpdate", nil)
	if err != nil {
		return time.Time{}, err
	}

	var pr postsResponse
	err = json.Unmarshal(resp, &pr)
	if err != nil {
		return time.Time{}, err
	}

	update, err := time.Parse(time.RFC3339, pr.UpdateTime)
	if err != nil {
		return time.Time{}, err
	}

	return update, nil
}

// PostsAddOptions represents the required and optional arguments for
// adding a bookmark.
type PostsAddOptions struct {
	// Required: The URL of the item.
	URL string

	// Required: Title of the item. This field is unfortunately
	// named 'description' for backwards compatibility with the
	// delicious API.
	Description string

	// Description of the item. Called 'extended' for backwards
	// compatibility with delicious API.
	Extended []byte

	// List of up to 100 tags.
	Tags []string

	// Creation time for this bookmark. Defaults to current
	// time. Datestamps more than 10 minutes ahead of server time
	// will be reset to current server time.
	Dt time.Time

	// Replace any existing bookmark with this URL. Default is
	// yes. If set to no, will throw an error if bookmark exists.
	Replace bool

	// Make bookmark public. Default is "yes" unless user has
	// enabled the "save all bookmarks as private" user setting,
	// in which case default is "no".
	Shared bool

	// Marks the bookmark as unread. Default is "no".
	Toread bool
}

// PostsAdd adds a bookmark.
//
// https://pinboard.in/api/#posts_add
func PostsAdd(opt *PostsAddOptions) error {
	if opt.URL == "" {
		return errors.New("error: missing url")
	}

	if opt.Description == "" {
		return errors.New("error: missing description")
	}

	resp, err := get("postsAdd", opt)
	if err != nil {
		return err
	}

	var pr postsResponse
	err = json.Unmarshal(resp, &pr)
	if err != nil {
		return err
	}

	if pr.ResultCode != "done" {
		return errors.New(pr.ResultCode)
	}

	return nil
}

// postsDeleteOptions represents the single required argument for
// deleting a bookmark.
type postsDeleteOptions struct {
	URL string
}

// PostsDelete deletes the bookmark by url.
//
// https://pinboard.in/api/#posts_delete
func PostsDelete(url string) error {
	resp, err := get("postsDelete", &postsDeleteOptions{URL: url})
	if err != nil {
		return err
	}

	var pr postsResponse
	err = json.Unmarshal(resp, &pr)
	if err != nil {
		return err
	}

	if pr.ResultCode != "done" {
		return errors.New(pr.ResultCode)
	}

	return nil
}

// PostsGetOptions represents the optional arguments for getting
// bookmarks.
type PostsGetOptions struct {
	// Filter by up to three tags.
	Tag []string

	// Return results bookmarked on this day. UTC date in this
	// format: 2010-12-11.
	Dt time.Time

	// Return bookmark for this URL.
	URL string

	// Include a change detection signature in a meta attribute.
	Meta bool
}

// PostsGet returns one or more posts (on a single day) matching the
// arguments. If no date or URL is given, date of most recent bookmark
// will be used.Returns one or more posts on a single day matching the
// arguments. If no date or URL is given, date of most recent bookmark
// will be used.
//
// https://pinboard.in/api/#posts_get
func PostsGet(opt *PostsGetOptions) ([]*Post, error) {
	resp, err := get("postsGet", opt)
	if err != nil {
		return nil, err
	}

	var pr postsResponse
	err = json.Unmarshal(resp, &pr)
	if err != nil {
		return nil, err
	}

	var posts []*Post
	for _, p := range pr.Posts {
		post, err := p.toPost()
		if err != nil {
			return nil, err
		}

		posts = append(posts, post)
	}

	return posts, nil
}

// PostsRecentOptions represents the optional arguments for returning
// the user's most recent posts.
type PostsRecentOptions struct {
	// Filter by up to three tags.
	Tag []string

	// Number of results to return. Default is 15, max is 100.
	Count int
}

// PostsRecent returns a list of the user's most recent posts,
// filtered by tag.
//
// https://pinboard.in/api/#posts_recent
func PostsRecent(opt *PostsRecentOptions) ([]*Post, error) {
	resp, err := get("postsRecent", opt)
	if err != nil {
		return nil, err
	}

	var pr postsResponse
	err = json.Unmarshal(resp, &pr)
	if err != nil {
		return nil, err
	}

	var posts []*Post
	for _, p := range pr.Posts {
		post, err := p.toPost()
		if err != nil {
			return nil, err
		}

		posts = append(posts, post)
	}

	return posts, nil
}

// postsDatesResponse represents the response from /posts/dates.
type postsDatesResponse struct {
	User  string            `json:"user"`
	Tag   string            `json:"tag"`
	Dates map[string]string `json:"dates"`
}

// PostsDatesOptions represents the single optional argument for
// returning a list of dates with the number of posts at each date.
type PostsDatesOptions struct {
	// Filter by up to three tags.
	Tag []string
}

// PostsDates returns a list of dates with the number of posts at each
// date.
//
// https://pinboard.in/api/#posts_dates
func PostsDates(opt *PostsDatesOptions) (map[string]string, error) {
	resp, err := get("postsDates", opt)
	if err != nil {
		return nil, err
	}

	var pr postsDatesResponse
	err = json.Unmarshal(resp, &pr)
	if err != nil {
		return nil, err
	}

	return pr.Dates, nil
}

// PostsAllOptions represents the optional arguments for returning all
// bookmarks in the user's account.
type PostsAllOptions struct {
	// Filter by up to three tags.
	Tag []string

	// Offset value (default is 0).
	Start int

	// Number of results to return. Default is all.
	Results int

	// Return only bookmarks created after this time.
	Fromdt time.Time

	// Return only bookmarks created before this time.
	Todt time.Time

	// Include a change detection signature for each bookmark.
	//
	// Note: This probably doesn't work. A meta field is always
	// returned. The Pinboard API says the datatype is an int but
	// changing the value has no impact on the results. Using a
	// yes/no string like all the other meta options doesn't work
	// either.
	Meta int
}

// PostsAll returns all bookmarks in the user's account.
//
// https://pinboard.in/api/#posts_all
func PostsAll(opt *PostsAllOptions) ([]*Post, error) {
	resp, err := get("postsAll", opt)
	if err != nil {
		return nil, err
	}

	var pr []post
	err = json.Unmarshal(resp, &pr)
	if err != nil {
		return nil, err
	}

	var posts []*Post
	for _, p := range pr {
		post, err := p.toPost()
		if err != nil {
			return nil, err
		}

		posts = append(posts, post)
	}

	return posts, nil
}

// postSuggestResponse represents the response from /posts/suggest.
type postsSuggestResponse struct {
	Popular     []string `json:"popular"`
	Recommended []string `json:"recommended"`
}

// postSuggestOptions represents the single required argument, url,
// for suggesting tags for a post.
type postsSuggestOptions struct {
	URL string
}

// PostsSuggestPopular returns a slice of popular tags for a given
// URL. Popular tags are tags used site-wide for the url.
//
// https://pinboard.in/api/#posts_suggest
func PostsSuggestPopular(url string) ([]string, error) {
	resp, err := get("postsSuggest", &postsSuggestOptions{URL: url})
	if err != nil {
		return nil, err
	}

	var pr []postsSuggestResponse
	err = json.Unmarshal(resp, &pr)
	if err != nil {
		return nil, err
	}

	return pr[0].Popular, nil
}

// PostsSuggestRecommended returns a slice of recommended tags for a
// given URL. Recommended tags are drawn from the user's own tags.
//
// https://pinboard.in/api/#posts_suggest
func PostsSuggestRecommended(url string) ([]string, error) {
	resp, err := get("postsSuggest", &postsSuggestOptions{URL: url})
	if err != nil {
		return nil, err
	}

	var pr []postsSuggestResponse
	err = json.Unmarshal(resp, &pr)
	if err != nil {
		return nil, err
	}

	return pr[1].Recommended, nil
}

func (d *descriptionType) UnmarshalJSON(data []byte) error {
	if err := json.Unmarshal(data, &d); err != nil {
		*d = ""
	}
	return nil
}

func (d *descriptionType) String() string {
	return string(*d)
}
<commit_msg>Fix the infinite loop problem<commit_after>package pinboard

import (
	"encoding/json"
	"errors"
	"net/url"
	"strings"
	"time"
)

// Aliasing this to get a custom UnmarshalJSON because Pinboard
// can return `"description": false` in the JSON output.
type descriptionType string

// Post represents a bookmark.
type Post struct {
	// URL of bookmark.
	Href *url.URL

	// Title of bookmark. This field is unfortunately named
	// 'description' for backwards compatibility with the
	// delicious API
	Description string

	// Description of the item. Called 'extended' for backwards
	// compatibility with delicious API.
	Extended []byte

	// Tags of bookmark.
	Tags []string

	// If the bookmark is private or public.
	Shared bool

	// If the bookmark is marked to read later.
	Toread bool

	// Create time for this bookmark.
	Time time.Time

	// Change detection signature of the bookmark.
	Meta []byte

	// Hash of the bookmark.
	Hash []byte

	// The number of other users who have bookmarked this same
	// item.
	Others int
}

// post represents intermediate post response data before type
// conversion.
type post struct {
	Href        string
	Description descriptionType
	Extended    string
	Tags        string
	Shared      string
	Toread      string
	Time        string
	Meta        string
	Hash        string
	Others      int
}

// toPost converts a post to a type correct Post.
func (p *post) toPost() (*Post, error) {
	href, err := url.Parse(p.Href)
	if err != nil {
		return nil, err
	}

	tags := strings.Split(p.Tags, " ")

	var shared, toread bool
	if p.Shared == "yes" {
		shared = true
	}

	if p.Toread == "yes" {
		toread = true
	}

	dt, err := time.Parse(time.RFC3339, p.Time)
	if err != nil {
		return nil, err
	}

	P := Post{
		Href:        href,
		Description: p.Description.String(),
		Extended:    []byte(p.Extended),
		Tags:        tags,
		Shared:      shared,
		Toread:      toread,
		Time:        dt,
		Meta:        []byte(p.Meta),
		Hash:        []byte(p.Hash),
		Others:      p.Others,
	}

	return &P, nil
}

// postsResponse represents a response from certain /posts/ endpoints.
type postsResponse struct {
	UpdateTime string `json:"update_time,omitempty"`
	ResultCode string `json:"result_code,omitempty"`
	Date       string `json:"date,omitempty"`
	User       string `json:"user,omitempty"`
	Posts      []post `json:"posts,omitempty"`
}

// PostsUpdate returns the most recent time a bookmark was added,
// updated or deleted.
//
// https://pinboard.in/api/#posts_update
func PostsUpdate() (time.Time, error) {
	resp, err := get("postsUpdate", nil)
	if err != nil {
		return time.Time{}, err
	}

	var pr postsResponse
	err = json.Unmarshal(resp, &pr)
	if err != nil {
		return time.Time{}, err
	}

	update, err := time.Parse(time.RFC3339, pr.UpdateTime)
	if err != nil {
		return time.Time{}, err
	}

	return update, nil
}

// PostsAddOptions represents the required and optional arguments for
// adding a bookmark.
type PostsAddOptions struct {
	// Required: The URL of the item.
	URL string

	// Required: Title of the item. This field is unfortunately
	// named 'description' for backwards compatibility with the
	// delicious API.
	Description string

	// Description of the item. Called 'extended' for backwards
	// compatibility with delicious API.
	Extended []byte

	// List of up to 100 tags.
	Tags []string

	// Creation time for this bookmark. Defaults to current
	// time. Datestamps more than 10 minutes ahead of server time
	// will be reset to current server time.
	Dt time.Time

	// Replace any existing bookmark with this URL. Default is
	// yes. If set to no, will throw an error if bookmark exists.
	Replace bool

	// Make bookmark public. Default is "yes" unless user has
	// enabled the "save all bookmarks as private" user setting,
	// in which case default is "no".
	Shared bool

	// Marks the bookmark as unread. Default is "no".
	Toread bool
}

// PostsAdd adds a bookmark.
//
// https://pinboard.in/api/#posts_add
func PostsAdd(opt *PostsAddOptions) error {
	if opt.URL == "" {
		return errors.New("error: missing url")
	}

	if opt.Description == "" {
		return errors.New("error: missing description")
	}

	resp, err := get("postsAdd", opt)
	if err != nil {
		return err
	}

	var pr postsResponse
	err = json.Unmarshal(resp, &pr)
	if err != nil {
		return err
	}

	if pr.ResultCode != "done" {
		return errors.New(pr.ResultCode)
	}

	return nil
}

// postsDeleteOptions represents the single required argument for
// deleting a bookmark.
type postsDeleteOptions struct {
	URL string
}

// PostsDelete deletes the bookmark by url.
//
// https://pinboard.in/api/#posts_delete
func PostsDelete(url string) error {
	resp, err := get("postsDelete", &postsDeleteOptions{URL: url})
	if err != nil {
		return err
	}

	var pr postsResponse
	err = json.Unmarshal(resp, &pr)
	if err != nil {
		return err
	}

	if pr.ResultCode != "done" {
		return errors.New(pr.ResultCode)
	}

	return nil
}

// PostsGetOptions represents the optional arguments for getting
// bookmarks.
type PostsGetOptions struct {
	// Filter by up to three tags.
	Tag []string

	// Return results bookmarked on this day. UTC date in this
	// format: 2010-12-11.
	Dt time.Time

	// Return bookmark for this URL.
	URL string

	// Include a change detection signature in a meta attribute.
	Meta bool
}

// PostsGet returns one or more posts (on a single day) matching the
// arguments. If no date or URL is given, date of most recent bookmark
// will be used.Returns one or more posts on a single day matching the
// arguments. If no date or URL is given, date of most recent bookmark
// will be used.
//
// https://pinboard.in/api/#posts_get
func PostsGet(opt *PostsGetOptions) ([]*Post, error) {
	resp, err := get("postsGet", opt)
	if err != nil {
		return nil, err
	}

	var pr postsResponse
	err = json.Unmarshal(resp, &pr)
	if err != nil {
		return nil, err
	}

	var posts []*Post
	for _, p := range pr.Posts {
		post, err := p.toPost()
		if err != nil {
			return nil, err
		}

		posts = append(posts, post)
	}

	return posts, nil
}

// PostsRecentOptions represents the optional arguments for returning
// the user's most recent posts.
type PostsRecentOptions struct {
	// Filter by up to three tags.
	Tag []string

	// Number of results to return. Default is 15, max is 100.
	Count int
}

// PostsRecent returns a list of the user's most recent posts,
// filtered by tag.
//
// https://pinboard.in/api/#posts_recent
func PostsRecent(opt *PostsRecentOptions) ([]*Post, error) {
	resp, err := get("postsRecent", opt)
	if err != nil {
		return nil, err
	}

	var pr postsResponse
	err = json.Unmarshal(resp, &pr)
	if err != nil {
		return nil, err
	}

	var posts []*Post
	for _, p := range pr.Posts {
		post, err := p.toPost()
		if err != nil {
			return nil, err
		}

		posts = append(posts, post)
	}

	return posts, nil
}

// postsDatesResponse represents the response from /posts/dates.
type postsDatesResponse struct {
	User  string            `json:"user"`
	Tag   string            `json:"tag"`
	Dates map[string]string `json:"dates"`
}

// PostsDatesOptions represents the single optional argument for
// returning a list of dates with the number of posts at each date.
type PostsDatesOptions struct {
	// Filter by up to three tags.
	Tag []string
}

// PostsDates returns a list of dates with the number of posts at each
// date.
//
// https://pinboard.in/api/#posts_dates
func PostsDates(opt *PostsDatesOptions) (map[string]string, error) {
	resp, err := get("postsDates", opt)
	if err != nil {
		return nil, err
	}

	var pr postsDatesResponse
	err = json.Unmarshal(resp, &pr)
	if err != nil {
		return nil, err
	}

	return pr.Dates, nil
}

// PostsAllOptions represents the optional arguments for returning all
// bookmarks in the user's account.
type PostsAllOptions struct {
	// Filter by up to three tags.
	Tag []string

	// Offset value (default is 0).
	Start int

	// Number of results to return. Default is all.
	Results int

	// Return only bookmarks created after this time.
	Fromdt time.Time

	// Return only bookmarks created before this time.
	Todt time.Time

	// Include a change detection signature for each bookmark.
	//
	// Note: This probably doesn't work. A meta field is always
	// returned. The Pinboard API says the datatype is an int but
	// changing the value has no impact on the results. Using a
	// yes/no string like all the other meta options doesn't work
	// either.
	Meta int
}

// PostsAll returns all bookmarks in the user's account.
//
// https://pinboard.in/api/#posts_all
func PostsAll(opt *PostsAllOptions) ([]*Post, error) {
	resp, err := get("postsAll", opt)
	if err != nil {
		return nil, err
	}

	var pr []post
	err = json.Unmarshal(resp, &pr)
	if err != nil {
		return nil, err
	}

	var posts []*Post
	for _, p := range pr {
		post, err := p.toPost()
		if err != nil {
			return nil, err
		}

		posts = append(posts, post)
	}

	return posts, nil
}

// postSuggestResponse represents the response from /posts/suggest.
type postsSuggestResponse struct {
	Popular     []string `json:"popular"`
	Recommended []string `json:"recommended"`
}

// postSuggestOptions represents the single required argument, url,
// for suggesting tags for a post.
type postsSuggestOptions struct {
	URL string
}

// PostsSuggestPopular returns a slice of popular tags for a given
// URL. Popular tags are tags used site-wide for the url.
//
// https://pinboard.in/api/#posts_suggest
func PostsSuggestPopular(url string) ([]string, error) {
	resp, err := get("postsSuggest", &postsSuggestOptions{URL: url})
	if err != nil {
		return nil, err
	}

	var pr []postsSuggestResponse
	err = json.Unmarshal(resp, &pr)
	if err != nil {
		return nil, err
	}

	return pr[0].Popular, nil
}

// PostsSuggestRecommended returns a slice of recommended tags for a
// given URL. Recommended tags are drawn from the user's own tags.
//
// https://pinboard.in/api/#posts_suggest
func PostsSuggestRecommended(url string) ([]string, error) {
	resp, err := get("postsSuggest", &postsSuggestOptions{URL: url})
	if err != nil {
		return nil, err
	}

	var pr []postsSuggestResponse
	err = json.Unmarshal(resp, &pr)
	if err != nil {
		return nil, err
	}

	return pr[1].Recommended, nil
}

// UnmarshalJSON converts a `descriptionType` into a `string`.
func (d *descriptionType) UnmarshalJSON(data []byte) error {
	// Have to do the type dance to avoid an infinite loop.
	type descriptionTypeAlias descriptionType
	var d2 descriptionTypeAlias

	if err := json.Unmarshal(data, &d2); err != nil {
		d2 = ""
	}
	*d = descriptionType(d2)
	return nil
}

// String returns the `string` value of our `descriptionType`.
func (d *descriptionType) String() string {
	return string(*d)
}
<|endoftext|>


<commit_before>// Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package sh

import (
	"fmt"
	"io"
)

func (p *printer) nodeJoin(ns []Node, sep string) {
	for i, n := range ns {
		if i > 0 {
			p.pr(sep)
		}
		p.node(n)
	}
}

func (p *printer) wordJoin(ws []Word, sep string) {
	for i, w := range ws {
		if i > 0 {
			p.pr(sep)
		}
		p.node(w)
	}
}

func newlineAfter(s Stmt) bool {
	for _, r := range s.Redirs {
		if r.Op == SHL || r.Op == DHEREDOC {
			return true
		}
	}
	return false
}

func (p *printer) stmtJoinWithEnd(stmts []Stmt, end bool) {
	p.needNewline = false
	for i, s := range stmts {
		if p.needNewline {
			p.needNewline = false
			p.pr("\n")
		} else if i > 0 {
			p.pr("; ")
		}
		p.node(s)
		p.needNewline = newlineAfter(s)
	}
}

func (p *printer) stmtJoin(stmts []Stmt) { p.stmtJoinWithEnd(stmts, true) }

func (p *printer) stmtList(stmts []Stmt) {
	if len(stmts) == 0 {
		p.pr(SEMICOLON, " ")
		return
	}
	p.pr(" ")
	p.stmtJoin(stmts)
	if p.needNewline {
		p.pr("\n")
	} else {
		p.pr(SEMICOLON, " ")
	}
}

func (p *printer) semicolonIfNil(v interface{}) {
	if v == nil {
		p.pr(SEMICOLON, " ")
		return
	}
	p.node(v)
}

func Fprint(w io.Writer, v interface{}) error {
	p := printer{w: w}
	p.node(v)
	return p.err
}

type printer struct {
	w   io.Writer
	err error

	needNewline bool
}

func (p *printer) pr(a ...interface{}) {
	for _, v := range a {
		if p.err != nil {
			break
		}
		_, p.err = fmt.Fprint(p.w, v)
	}
}

func (p *printer) node(v interface{}) {
	switch x := v.(type) {
	case File:
		p.stmtJoinWithEnd(x.Stmts, false)
	case Stmt:
		first := true
		if x.Negated {
			p.pr(NOT)
			first = false
		}
		for _, a := range x.Assigns {
			if !first {
				p.pr(" ")
			}
			p.node(a)
			first = false
		}
		if x.Node != nil {
			if !first {
				p.pr(" ")
			}
			p.node(x.Node)
			first = false
		}
		for _, r := range x.Redirs {
			if !first {
				p.pr(" ")
			}
			p.node(r.N)
			p.pr(r.Op)
			if _, ok := r.Word.Parts[0].(CmdInput); ok {
				p.pr(" ")
			}
			p.node(r.Word)
			first = false
		}
		if x.Background {
			if !first {
				p.pr(" ")
			}
			p.pr(AND)
		}
	case Assign:
		if x.Name != nil {
			p.node(x.Name)
			if x.Append {
				p.pr("+=")
			} else {
				p.pr("=")
			}
		}
		p.node(x.Value)
	case Command:
		for i, w := range x.Args {
			if i > 0 {
				p.pr(" ")
			}
			p.node(w)
		}
	case Subshell:
		p.pr(LPAREN)
		if len(x.Stmts) == 0 {
			// A space in between to avoid confusion with ()
			p.pr(" ")
		}
		p.stmtJoinWithEnd(x.Stmts, false)
		p.pr(RPAREN)
	case Block:
		p.pr(LBRACE)
		p.stmtList(x.Stmts)
		p.pr(RBRACE)
	case IfStmt:
		p.pr(IF)
		p.semicolonIfNil(x.Cond)
		p.pr(THEN)
		p.stmtList(x.ThenStmts)
		for _, el := range x.Elifs {
			p.pr(ELIF)
			p.semicolonIfNil(el.Cond)
			p.pr(THEN)
			p.stmtList(el.ThenStmts)
		}
		if len(x.ElseStmts) > 0 {
			p.pr(ELSE)
			p.stmtList(x.ElseStmts)
		}
		p.pr(FI)
	case StmtCond:
		p.stmtList(x.Stmts)
	case CStyleCond:
		p.pr(" ((")
		p.node(x.Cond)
		p.pr(")); ")
	case WhileStmt:
		p.pr(WHILE)
		p.semicolonIfNil(x.Cond)
		p.pr(DO)
		p.stmtList(x.DoStmts)
		p.pr(DONE)
	case UntilStmt:
		p.pr(UNTIL)
		p.semicolonIfNil(x.Cond)
		p.pr(DO)
		p.stmtList(x.DoStmts)
		p.pr(DONE)
	case ForStmt:
		p.pr(FOR, " ")
		p.node(x.Cond)
		p.pr("; ", DO)
		p.stmtList(x.DoStmts)
		p.pr(DONE)
	case WordIter:
		p.node(x.Name)
		if len(x.List) > 0 {
			p.pr(" ", IN, " ")
			p.wordJoin(x.List, " ")
		}
	case CStyleLoop:
		p.pr("((")
		p.node(x.Init)
		p.pr("; ")
		p.node(x.Cond)
		p.pr("; ")
		p.node(x.Post)
		p.pr("))")
	case UnaryExpr:
		if !x.Post {
			p.pr(x.Op)
		}
		p.node(x.X)
		if x.Post {
			p.pr(x.Op)
		}
	case BinaryExpr:
		p.node(x.X)
		if x.Op != COMMA {
			p.pr(" ")
		}
		p.pr(x.Op, " ")
		p.node(x.Y)
	case FuncDecl:
		if x.BashStyle {
			p.pr(FUNCTION, " ")
		}
		p.node(x.Name)
		p.pr("() ")
		p.node(x.Body)
	case Word:
		p.nodeJoin(x.Parts, "")
	case Lit:
		p.pr(x.Value)
	case SglQuoted:
		p.pr(SQUOTE, x.Value, SQUOTE)
	case Quoted:
		stop := x.Quote
		if stop == DOLLSQ {
			stop = SQUOTE
		} else if stop == DOLLDQ {
			stop = DQUOTE
		}
		p.pr(x.Quote)
		p.nodeJoin(x.Parts, "")
		p.pr(stop)
	case CmdSubst:
		if x.Backquotes {
			p.pr(BQUOTE)
		} else {
			p.pr(DOLLAR, "", LPAREN)
		}
		p.stmtJoin(x.Stmts)
		if x.Backquotes {
			p.pr(BQUOTE)
		} else {
			p.pr(RPAREN)
		}
	case ParamExp:
		if x.Short {
			p.pr(DOLLAR)
			p.node(x.Param)
			return
		}
		p.pr("${")
		if x.Length {
			p.pr(HASH)
		}
		p.node(x.Param)
		if x.Ind != nil {
			p.node(*x.Ind)
		}
		if x.Repl != nil {
			p.node(*x.Repl)
		}
		if x.Exp != nil {
			p.node(*x.Exp)
		}
		p.pr("}")
	case Index:
		p.pr(LBRACK)
		p.node(x.Word)
		p.pr(RBRACK)
	case Replace:
		if x.All {
			p.pr(QUO)
		}
		p.pr(QUO)
		p.node(x.Orig)
		p.pr(QUO)
		p.node(x.With)
	case Expansion:
		p.pr(x.Op)
		p.node(x.Word)
	case ArithmExpr:
		p.pr("$((")
		if x.X != nil {
			p.node(x.X)
		}
		p.pr("))")
	case ParenExpr:
		p.pr("(")
		p.node(x.X)
		p.pr(")")
	case CaseStmt:
		p.pr(CASE, " ")
		p.node(x.Word)
		p.pr(" ", IN)
		for i, pl := range x.List {
			if i > 0 {
				p.pr(";;")
			}
			p.pr(" ")
			p.wordJoin(pl.Patterns, " | ")
			p.pr(") ")
			p.stmtJoin(pl.Stmts)
		}
		p.pr("; ", ESAC)
	case DeclStmt:
		if x.Local {
			p.pr(LOCAL)
		} else {
			p.pr(DECLARE)
		}
		for _, w := range x.Opts {
			p.pr(" ")
			p.node(w)
		}
		for _, a := range x.Assigns {
			p.pr(" ")
			p.node(a)
		}
	case ArrayExpr:
		p.pr(LPAREN)
		p.wordJoin(x.List, " ")
		p.pr(RPAREN)
	case CmdInput:
		p.pr(CMDIN)
		p.stmtJoin(x.Stmts)
		p.pr(RPAREN)
	case EvalStmt:
		p.pr(EVAL, " ")
		p.node(x.Stmt)
	case LetStmt:
		p.pr(LET, " ")
		p.nodeJoin(x.Exprs, " ")
	}
}
<commit_msg>Simplify recursive print calls<commit_after>// Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package sh

import (
	"fmt"
	"io"
)

func Fprint(w io.Writer, v interface{}) error {
	p := printer{w: w}
	p.node(v)
	return p.err
}

type printer struct {
	w   io.Writer
	err error

	needNewline bool
}

func (p *printer) pr(a ...interface{}) {
	for _, v := range a {
		if p.err != nil {
			break
		}
		switch v.(type) {
		case string, Token:
			_, p.err = fmt.Fprint(p.w, v)
		default:
			p.node(v)
		}
	}
}

func (p *printer) node(v interface{}) {
	switch x := v.(type) {
	case File:
		p.stmtJoinWithEnd(x.Stmts, false)
	case Stmt:
		first := true
		if x.Negated {
			p.pr(NOT)
			first = false
		}
		for _, a := range x.Assigns {
			if !first {
				p.pr(" ")
			}
			p.pr(a)
			first = false
		}
		if x.Node != nil {
			if !first {
				p.pr(" ")
			}
			p.pr(x.Node)
			first = false
		}
		for _, r := range x.Redirs {
			if !first {
				p.pr(" ")
			}
			p.pr(r.N, r.Op)
			if _, ok := r.Word.Parts[0].(CmdInput); ok {
				p.pr(" ")
			}
			p.pr(r.Word)
			first = false
		}
		if x.Background {
			if !first {
				p.pr(" ")
			}
			p.pr(AND)
		}
	case Assign:
		if x.Name != nil {
			p.pr(x.Name)
			if x.Append {
				p.pr("+=")
			} else {
				p.pr("=")
			}
		}
		p.pr(x.Value)
	case Command:
		for i, w := range x.Args {
			if i > 0 {
				p.pr(" ")
			}
			p.pr(w)
		}
	case Subshell:
		p.pr(LPAREN)
		if len(x.Stmts) == 0 {
			// A space in between to avoid confusion with ()
			p.pr(" ")
		}
		p.stmtJoinWithEnd(x.Stmts, false)
		p.pr(RPAREN)
	case Block:
		p.pr(LBRACE)
		p.stmtList(x.Stmts)
		p.pr(RBRACE)
	case IfStmt:
		p.pr(IF)
		p.semicolonIfNil(x.Cond)
		p.pr(THEN)
		p.stmtList(x.ThenStmts)
		for _, el := range x.Elifs {
			p.pr(ELIF)
			p.semicolonIfNil(el.Cond)
			p.pr(THEN)
			p.stmtList(el.ThenStmts)
		}
		if len(x.ElseStmts) > 0 {
			p.pr(ELSE)
			p.stmtList(x.ElseStmts)
		}
		p.pr(FI)
	case StmtCond:
		p.stmtList(x.Stmts)
	case CStyleCond:
		p.pr(" ((", x.Cond, ")); ")
	case WhileStmt:
		p.pr(WHILE)
		p.semicolonIfNil(x.Cond)
		p.pr(DO)
		p.stmtList(x.DoStmts)
		p.pr(DONE)
	case UntilStmt:
		p.pr(UNTIL)
		p.semicolonIfNil(x.Cond)
		p.pr(DO)
		p.stmtList(x.DoStmts)
		p.pr(DONE)
	case ForStmt:
		p.pr(FOR, " ", x.Cond, "; ", DO)
		p.stmtList(x.DoStmts)
		p.pr(DONE)
	case WordIter:
		p.pr(x.Name)
		if len(x.List) > 0 {
			p.pr(" ", IN, " ")
			p.wordJoin(x.List, " ")
		}
	case CStyleLoop:
		p.pr("((", x.Init, "; ", x.Cond, "; ", x.Post, "))")
	case UnaryExpr:
		if !x.Post {
			p.pr(x.Op)
		}
		p.pr(x.X)
		if x.Post {
			p.pr(x.Op)
		}
	case BinaryExpr:
		p.pr(x.X)
		if x.Op != COMMA {
			p.pr(" ")
		}
		p.pr(x.Op, " ", x.Y)
	case FuncDecl:
		if x.BashStyle {
			p.pr(FUNCTION, " ")
		}
		p.pr(x.Name, "() ", x.Body)
	case Word:
		p.nodeJoin(x.Parts, "")
	case Lit:
		p.pr(x.Value)
	case SglQuoted:
		p.pr(SQUOTE, x.Value, SQUOTE)
	case Quoted:
		stop := x.Quote
		if stop == DOLLSQ {
			stop = SQUOTE
		} else if stop == DOLLDQ {
			stop = DQUOTE
		}
		p.pr(x.Quote)
		p.nodeJoin(x.Parts, "")
		p.pr(stop)
	case CmdSubst:
		if x.Backquotes {
			p.pr(BQUOTE)
		} else {
			p.pr(DOLLAR, "", LPAREN)
		}
		p.stmtJoin(x.Stmts)
		if x.Backquotes {
			p.pr(BQUOTE)
		} else {
			p.pr(RPAREN)
		}
	case ParamExp:
		if x.Short {
			p.pr(DOLLAR, x.Param)
			return
		}
		p.pr("${")
		if x.Length {
			p.pr(HASH)
		}
		p.pr(x.Param)
		if x.Ind != nil {
			p.pr(*x.Ind)
		}
		if x.Repl != nil {
			p.pr(*x.Repl)
		}
		if x.Exp != nil {
			p.pr(*x.Exp)
		}
		p.pr("}")
	case Index:
		p.pr(LBRACK, x.Word, RBRACK)
	case Replace:
		if x.All {
			p.pr(QUO)
		}
		p.pr(QUO, x.Orig, QUO, x.With)
	case Expansion:
		p.pr(x.Op, x.Word)
	case ArithmExpr:
		p.pr("$((")
		if x.X != nil {
			p.pr(x.X)
		}
		p.pr("))")
	case ParenExpr:
		p.pr("(", x.X, ")")
	case CaseStmt:
		p.pr(CASE, " ", x.Word, " ", IN)
		for i, pl := range x.List {
			if i > 0 {
				p.pr(";;")
			}
			p.pr(" ")
			p.wordJoin(pl.Patterns, " | ")
			p.pr(") ")
			p.stmtJoin(pl.Stmts)
		}
		p.pr("; ", ESAC)
	case DeclStmt:
		if x.Local {
			p.pr(LOCAL)
		} else {
			p.pr(DECLARE)
		}
		for _, w := range x.Opts {
			p.pr(" ", w)
		}
		for _, a := range x.Assigns {
			p.pr(" ", a)
		}
	case ArrayExpr:
		p.pr(LPAREN)
		p.wordJoin(x.List, " ")
		p.pr(RPAREN)
	case CmdInput:
		p.pr(CMDIN)
		p.stmtJoin(x.Stmts)
		p.pr(RPAREN)
	case EvalStmt:
		p.pr(EVAL, " ", x.Stmt)
	case LetStmt:
		p.pr(LET, " ")
		p.nodeJoin(x.Exprs, " ")
	}
}

func (p *printer) nodeJoin(ns []Node, sep string) {
	for i, n := range ns {
		if i > 0 {
			p.pr(sep)
		}
		p.node(n)
	}
}

func (p *printer) wordJoin(ws []Word, sep string) {
	for i, w := range ws {
		if i > 0 {
			p.pr(sep)
		}
		p.node(w)
	}
}

func newlineAfter(s Stmt) bool {
	for _, r := range s.Redirs {
		if r.Op == SHL || r.Op == DHEREDOC {
			return true
		}
	}
	return false
}

func (p *printer) stmtJoinWithEnd(stmts []Stmt, end bool) {
	p.needNewline = false
	for i, s := range stmts {
		if p.needNewline {
			p.needNewline = false
			p.pr("\n")
		} else if i > 0 {
			p.pr("; ")
		}
		p.node(s)
		p.needNewline = newlineAfter(s)
	}
}

func (p *printer) stmtJoin(stmts []Stmt) { p.stmtJoinWithEnd(stmts, true) }

func (p *printer) stmtList(stmts []Stmt) {
	if len(stmts) == 0 {
		p.pr(SEMICOLON, " ")
		return
	}
	p.pr(" ")
	p.stmtJoin(stmts)
	if p.needNewline {
		p.pr("\n")
	} else {
		p.pr(SEMICOLON, " ")
	}
}

func (p *printer) semicolonIfNil(v interface{}) {
	if v == nil {
		p.pr(SEMICOLON, " ")
		return
	}
	p.node(v)
}
<|endoftext|>


<commit_before>package proxy

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"

	"github.com/go-openapi/errors"
	"github.com/go-openapi/spec"
	"github.com/go-openapi/strfmt"
	"github.com/go-openapi/swag"
	"github.com/go-openapi/validate"
	"github.com/gorilla/mux"
)

type Proxy struct {
	// Opts
	target  string
	verbose bool

	router       *mux.Router
	routes       map[*mux.Route]*spec.Operation
	reverseProxy http.Handler

	reporter Reporter

	doc               interface{} // This is useful for validate (TODO: find a better way)
	spec              *spec.Swagger
	pendingOperations map[*spec.Operation]struct{}
}

type ProxyOpt func(*Proxy)

func WithTarget(target string) ProxyOpt { return func(proxy *Proxy) { proxy.target = target } }
func WithVerbose(v bool) ProxyOpt       { return func(proxy *Proxy) { proxy.verbose = v } }

func New(s *spec.Swagger, reporter Reporter, opts ...ProxyOpt) (*Proxy, error) {
	proxy := &Proxy{
		target:   "http://localhost:8080",
		router:   mux.NewRouter(),
		routes:   make(map[*mux.Route]*spec.Operation),
		reporter: reporter,
	}

	if err := proxy.SetSpec(s); err != nil {
		return nil, err
	}

	for _, opt := range opts {
		opt(proxy)
	}

	rpURL, err := url.Parse(proxy.target)
	if err != nil {
		return nil, err
	}
	proxy.reverseProxy = httputil.NewSingleHostReverseProxy(rpURL)

	proxy.router.NotFoundHandler = http.HandlerFunc(proxy.notFound)
	proxy.registerPaths()

	return proxy, nil
}

func (proxy *Proxy) SetSpec(spec *spec.Swagger) error {
	// validate.NewSchemaValidator requires the spec as an interface{}
	// That's why we Unmarshal(Marshal()) the document
	data, err := json.Marshal(spec)
	if err != nil {
		return err
	}

	var doc interface{}
	if err := json.Unmarshal(data, &doc); err != nil {
		return err
	}

	proxy.doc = doc
	proxy.spec = spec

	proxy.registerPaths()
	return nil
}

func (proxy *Proxy) Router() http.Handler {
	return proxy.router
}

func (proxy *Proxy) Target() string {
	return proxy.target
}

func (proxy *Proxy) registerPaths() {
	proxy.pendingOperations = make(map[*spec.Operation]struct{})
	base := proxy.spec.BasePath

	router := mux.NewRouter()
	WalkOps(proxy.spec, func(path, method string, op *spec.Operation) {
		newPath := base + path
		if proxy.verbose {
			log.Printf("Register %s %s", method, newPath)
		}
		route := router.Handle(newPath, proxy.newHandler()).Methods(method)
		proxy.routes[route] = op
		proxy.pendingOperations[op] = struct{}{}
	})
	*proxy.router = *router
}

func (proxy *Proxy) notFound(w http.ResponseWriter, req *http.Request) {
	proxy.reporter.Warning(req, "Route not defined on the Spec")
	proxy.reverseProxy.ServeHTTP(w, req)
}

func (proxy *Proxy) newHandler() http.Handler {
	return proxy.Handler(proxy.reverseProxy)
}
func (proxy *Proxy) Handler(next http.Handler) http.Handler {
	fn := func(w http.ResponseWriter, req *http.Request) {
		wr := &WriterRecorder{ResponseWriter: w}
		next.ServeHTTP(wr, req)

		var match mux.RouteMatch
		proxy.router.Match(req, &match)
		op := proxy.routes[match.Route]

		if match.Handler == nil || op == nil {
			proxy.reporter.Warning(req, "Route not defined on the Spec")
			// Route hasn't been registered on the muxer
			return
		}
		proxy.operationExecuted(op)

		if err := proxy.Validate(wr, op); err != nil {
			proxy.reporter.Error(req, err)
		} else {
			proxy.reporter.Success(req)
		}
	}
	return http.HandlerFunc(fn)
}

type validatorFunc func(Response, *spec.Operation) error

func (proxy *Proxy) Validate(resp Response, op *spec.Operation) error {
	if _, ok := op.Responses.StatusCodeResponses[resp.Status()]; !ok {
		return fmt.Errorf("Server Status %d not defined by the spec", resp.Status())
	}

	var validators = []validatorFunc{
		proxy.ValidateMIME,
		proxy.ValidateHeaders,
		proxy.ValidateBody,
	}

	var errs []error
	for _, v := range validators {
		if err := v(resp, op); err != nil {
			if cErr, ok := err.(*errors.CompositeError); ok {
				errs = append(errs, cErr.Errors...)
			} else {
				errs = append(errs, err)
			}
		}
	}

	if len(errs) == 0 {
		return nil
	}
	return errors.CompositeValidationError(errs...)
}

func (proxy *Proxy) ValidateMIME(resp Response, op *spec.Operation) error {
	// Use Operation Spec or fallback to root
	produces := op.Produces
	if len(produces) == 0 {
		produces = proxy.spec.Produces
	}

	ct := resp.Header().Get("Content-Type")
	if len(produces) == 0 {
		return nil
	}

	for _, mime := range produces {
		if ct == mime {
			return nil
		}
	}

	return fmt.Errorf("Content-Type Error: Should produce %q, but got: '%s'", produces, ct)
}

func (proxy *Proxy) ValidateHeaders(resp Response, op *spec.Operation) error {
	var errs []error

	r := op.Responses.StatusCodeResponses[resp.Status()]
	for key, spec := range r.Headers {
		if err := validateHeaderValue(key, resp.Header().Get(key), &spec); err != nil {
			errs = append(errs, err)
		}
	}

	if len(errs) == 0 {
		return nil
	}

	return errors.CompositeValidationError(errs...)
}

func (proxy *Proxy) ValidateBody(resp Response, op *spec.Operation) error {
	r := op.Responses.StatusCodeResponses[resp.Status()]
	if r.Schema == nil {
		return nil
	}

	var data interface{}
	if err := json.Unmarshal(resp.Body(), &data); err != nil {
		return err
	}

	v := validate.NewSchemaValidator(r.Schema, proxy.doc, "", strfmt.Default)
	if result := v.Validate(data); result.HasErrors() {
		return result.AsError()
	}

	return nil
}

func validateHeaderValue(key, value string, spec *spec.Header) error {
	if value == "" {
		return fmt.Errorf("%s in headers is missing", key)
	}

	// TODO: Implement the rest of the format validators
	switch spec.Format {
	case "int32":
		_, err := swag.ConvertInt32(value)
		return err
	case "date-time":
		_, err := strfmt.ParseDateTime(value)
		return err
	}
	return nil
}

func (proxy *Proxy) PendingOperations() []*spec.Operation {
	var ops []*spec.Operation
	for op, _ := range proxy.pendingOperations {
		ops = append(ops, op)
	}
	return ops
}

func (proxy *Proxy) operationExecuted(op *spec.Operation) {
	delete(proxy.pendingOperations, op)
}

type WalkOpsFunc func(path, meth string, op *spec.Operation)

func WalkOps(spec *spec.Swagger, fn WalkOpsFunc) {
	for path, props := range spec.Paths.Paths {
		for meth, op := range getOperations(&props) {
			fn(path, meth, op)
		}
	}
}

func getOperations(props *spec.PathItem) map[string]*spec.Operation {
	ops := map[string]*spec.Operation{
		"DELETE":  props.Delete,
		"GET":     props.Get,
		"HEAD":    props.Head,
		"OPTIONS": props.Options,
		"PATCH":   props.Patch,
		"POST":    props.Post,
		"PUT":     props.Put,
	}

	// Keep those != nil
	for key, op := range ops {
		if op == nil {
			delete(ops, key)
		}
	}

	return ops
}
<commit_msg>fmt. instead of log.<commit_after>package proxy

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

	"github.com/go-openapi/errors"
	"github.com/go-openapi/spec"
	"github.com/go-openapi/strfmt"
	"github.com/go-openapi/swag"
	"github.com/go-openapi/validate"
	"github.com/gorilla/mux"
)

type Proxy struct {
	// Opts
	target  string
	verbose bool

	router       *mux.Router
	routes       map[*mux.Route]*spec.Operation
	reverseProxy http.Handler

	reporter Reporter

	doc               interface{} // This is useful for validate (TODO: find a better way)
	spec              *spec.Swagger
	pendingOperations map[*spec.Operation]struct{}
}

type ProxyOpt func(*Proxy)

func WithTarget(target string) ProxyOpt { return func(proxy *Proxy) { proxy.target = target } }
func WithVerbose(v bool) ProxyOpt       { return func(proxy *Proxy) { proxy.verbose = v } }

func New(s *spec.Swagger, reporter Reporter, opts ...ProxyOpt) (*Proxy, error) {
	proxy := &Proxy{
		target:   "http://localhost:8080",
		router:   mux.NewRouter(),
		routes:   make(map[*mux.Route]*spec.Operation),
		reporter: reporter,
	}

	if err := proxy.SetSpec(s); err != nil {
		return nil, err
	}

	for _, opt := range opts {
		opt(proxy)
	}

	rpURL, err := url.Parse(proxy.target)
	if err != nil {
		return nil, err
	}
	proxy.reverseProxy = httputil.NewSingleHostReverseProxy(rpURL)

	proxy.router.NotFoundHandler = http.HandlerFunc(proxy.notFound)
	proxy.registerPaths()

	return proxy, nil
}

func (proxy *Proxy) SetSpec(spec *spec.Swagger) error {
	// validate.NewSchemaValidator requires the spec as an interface{}
	// That's why we Unmarshal(Marshal()) the document
	data, err := json.Marshal(spec)
	if err != nil {
		return err
	}

	var doc interface{}
	if err := json.Unmarshal(data, &doc); err != nil {
		return err
	}

	proxy.doc = doc
	proxy.spec = spec

	proxy.registerPaths()
	return nil
}

func (proxy *Proxy) Router() http.Handler {
	return proxy.router
}

func (proxy *Proxy) Target() string {
	return proxy.target
}

func (proxy *Proxy) registerPaths() {
	proxy.pendingOperations = make(map[*spec.Operation]struct{})
	base := proxy.spec.BasePath

	router := mux.NewRouter()
	WalkOps(proxy.spec, func(path, method string, op *spec.Operation) {
		newPath := base + path
		if proxy.verbose {
			fmt.Printf("Register %s %s", method, newPath)
		}
		route := router.Handle(newPath, proxy.newHandler()).Methods(method)
		proxy.routes[route] = op
		proxy.pendingOperations[op] = struct{}{}
	})
	*proxy.router = *router
}

func (proxy *Proxy) notFound(w http.ResponseWriter, req *http.Request) {
	proxy.reporter.Warning(req, "Route not defined on the Spec")
	proxy.reverseProxy.ServeHTTP(w, req)
}

func (proxy *Proxy) newHandler() http.Handler {
	return proxy.Handler(proxy.reverseProxy)
}
func (proxy *Proxy) Handler(next http.Handler) http.Handler {
	fn := func(w http.ResponseWriter, req *http.Request) {
		wr := &WriterRecorder{ResponseWriter: w}
		next.ServeHTTP(wr, req)

		var match mux.RouteMatch
		proxy.router.Match(req, &match)
		op := proxy.routes[match.Route]

		if match.Handler == nil || op == nil {
			proxy.reporter.Warning(req, "Route not defined on the Spec")
			// Route hasn't been registered on the muxer
			return
		}
		proxy.operationExecuted(op)

		if err := proxy.Validate(wr, op); err != nil {
			proxy.reporter.Error(req, err)
		} else {
			proxy.reporter.Success(req)
		}
	}
	return http.HandlerFunc(fn)
}

type validatorFunc func(Response, *spec.Operation) error

func (proxy *Proxy) Validate(resp Response, op *spec.Operation) error {
	if _, ok := op.Responses.StatusCodeResponses[resp.Status()]; !ok {
		return fmt.Errorf("Server Status %d not defined by the spec", resp.Status())
	}

	var validators = []validatorFunc{
		proxy.ValidateMIME,
		proxy.ValidateHeaders,
		proxy.ValidateBody,
	}

	var errs []error
	for _, v := range validators {
		if err := v(resp, op); err != nil {
			if cErr, ok := err.(*errors.CompositeError); ok {
				errs = append(errs, cErr.Errors...)
			} else {
				errs = append(errs, err)
			}
		}
	}

	if len(errs) == 0 {
		return nil
	}
	return errors.CompositeValidationError(errs...)
}

func (proxy *Proxy) ValidateMIME(resp Response, op *spec.Operation) error {
	// Use Operation Spec or fallback to root
	produces := op.Produces
	if len(produces) == 0 {
		produces = proxy.spec.Produces
	}

	ct := resp.Header().Get("Content-Type")
	if len(produces) == 0 {
		return nil
	}

	for _, mime := range produces {
		if ct == mime {
			return nil
		}
	}

	return fmt.Errorf("Content-Type Error: Should produce %q, but got: '%s'", produces, ct)
}

func (proxy *Proxy) ValidateHeaders(resp Response, op *spec.Operation) error {
	var errs []error

	r := op.Responses.StatusCodeResponses[resp.Status()]
	for key, spec := range r.Headers {
		if err := validateHeaderValue(key, resp.Header().Get(key), &spec); err != nil {
			errs = append(errs, err)
		}
	}

	if len(errs) == 0 {
		return nil
	}

	return errors.CompositeValidationError(errs...)
}

func (proxy *Proxy) ValidateBody(resp Response, op *spec.Operation) error {
	r := op.Responses.StatusCodeResponses[resp.Status()]
	if r.Schema == nil {
		return nil
	}

	var data interface{}
	if err := json.Unmarshal(resp.Body(), &data); err != nil {
		return err
	}

	v := validate.NewSchemaValidator(r.Schema, proxy.doc, "", strfmt.Default)
	if result := v.Validate(data); result.HasErrors() {
		return result.AsError()
	}

	return nil
}

func validateHeaderValue(key, value string, spec *spec.Header) error {
	if value == "" {
		return fmt.Errorf("%s in headers is missing", key)
	}

	// TODO: Implement the rest of the format validators
	switch spec.Format {
	case "int32":
		_, err := swag.ConvertInt32(value)
		return err
	case "date-time":
		_, err := strfmt.ParseDateTime(value)
		return err
	}
	return nil
}

func (proxy *Proxy) PendingOperations() []*spec.Operation {
	var ops []*spec.Operation
	for op, _ := range proxy.pendingOperations {
		ops = append(ops, op)
	}
	return ops
}

func (proxy *Proxy) operationExecuted(op *spec.Operation) {
	delete(proxy.pendingOperations, op)
}

type WalkOpsFunc func(path, meth string, op *spec.Operation)

func WalkOps(spec *spec.Swagger, fn WalkOpsFunc) {
	for path, props := range spec.Paths.Paths {
		for meth, op := range getOperations(&props) {
			fn(path, meth, op)
		}
	}
}

func getOperations(props *spec.PathItem) map[string]*spec.Operation {
	ops := map[string]*spec.Operation{
		"DELETE":  props.Delete,
		"GET":     props.Get,
		"HEAD":    props.Head,
		"OPTIONS": props.Options,
		"PATCH":   props.Patch,
		"POST":    props.Post,
		"PUT":     props.Put,
	}

	// Keep those != nil
	for key, op := range ops {
		if op == nil {
			delete(ops, key)
		}
	}

	return ops
}
<|endoftext|>


<commit_before>package solidproxy

import (
	"crypto/tls"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"sync"
	"time"
)

const (
	maxIdleConnections int = 20
)

var (
	cookies  = map[string]map[string][]*http.Cookie{}
	cookiesL = new(sync.RWMutex)

	privateUris    = map[string]bool{}
	privateUrisL   = new(sync.RWMutex)
	requestTimeout = 2
)

// Proxy is a structure that encapsulates both clients (agent and fetcher), agent object and logger object.
type Proxy struct {
	HTTPClient      *http.Client
	HTTPAgentClient *http.Client
	Log             *log.Logger
	Agent           *Agent
}

// NewProxy returns a new Proxy object based on the provided agent configuration. The skip parameter is used to indicate if the client should ship server certificate verification.
func NewProxy(agent *Agent, skip bool) *Proxy {
	p := &Proxy{
		HTTPClient: NewClient(skip),
		Agent:      agent,
		Log:        InitLogger(false),
	}

	if agent.Cert != nil {
		p.HTTPAgentClient = agent.NewAgentClient(skip)
	}

	return p
}

// Handler is the main HTTP handler for the proxy/agent server.
func (p *Proxy) Handler(w http.ResponseWriter, req *http.Request) {
	p.Log.Println("New request from:", req.RemoteAddr, "for URI:", req.URL.String())
	// Log the time it takes to finish the request (for debugging)
	defer timeTrack(time.Now(), req.Method+" operation", p.Log)

	uri := req.FormValue("uri")
	if len(uri) == 0 {
		msg := "HTTP 400 - Bad Request. Please provide a URI to the proxy."
		p.Log.Println(msg, req.URL.String())
		w.WriteHeader(400)
		w.Write([]byte(msg))
		return
	}

	resource, err := url.ParseRequestURI(uri)
	if err != nil {
		p.Log.Println("Error parsing URL:", req.URL, err.Error())
		w.WriteHeader(400)
		w.Write([]byte("HTTP 400 - Bad Request. You must provide a valid URI: " + req.URL.String()))
		return
	}
	// rewrite URL
	req.URL = resource
	req.Host = resource.Host
	req.RequestURI = resource.RequestURI()
	// get user
	user := req.Header.Get("User")

	// check if we need to authenticate from the start
	authenticated := false
	if requiresAuth(req.URL.String()) {
		authenticated = true
		p.Log.Println("Request will use credentials for cached URI:", req.URL.String())
	}

	p.Log.Println("Proxying request for URI:", req.URL, "and user:", user, "using Agent:", p.Agent.WebID)

	// build new response
	var r *http.Response
	r, err = p.NewRequest(req, user, authenticated)
	if err != nil {
		p.execError(w, err)
		return
	}
	// the resource might have turned public, no need to remember it anymore
	if r.StatusCode >= 200 && r.StatusCode <= 400 {
		forgetURI(req.URL.String())
	}
	// Retry with server credentials if authentication is required
	if r.StatusCode == 401 {
		// Close the response to reuse the connection
		defer r.Body.Close()

		saved := rememberURI(req.URL.String())
		if saved {
			p.Log.Println(req.URL.String(), "saved to auth list")
		}
		if len(user) > 0 && p.HTTPAgentClient != nil {
			authenticated = true
			r, err = p.NewRequest(req, user, authenticated)
			if err != nil {
				p.execError(w, err)
				return
			}
			defer r.Body.Close()
		}
	}

	// Write data back
	// CORS
	w.Header().Set("Access-Control-Allow-Credentials", "true")
	w.Header().Set("Access-Control-Expose-Headers", "User, Triples, Location, Origin, Link, Vary, Last-Modified, Content-Length")
	w.Header().Set("Access-Control-Max-Age", "60")
	origin := req.Header.Get("Origin")
	if len(origin) > 0 {
		w.Header().Set("Access-Control-Allow-Origin", origin)
	}

	// copy headers
	CopyHeaders(r.Header, w.Header())

	w.WriteHeader(r.StatusCode)
	// r.Body will be empty at worst, so it should never trigger an error
	body, _ := ioutil.ReadAll(r.Body)
	w.Write(body)

	p.Log.Println("Response received with HTTP status", r.StatusCode)
	return
}

// CopyHeaders is used to copy headers between two http.Header objects (usually two request/response objects)
func CopyHeaders(from http.Header, to http.Header) {
	for key, values := range from {
		if key != "User" && key != "Cookie" {
			for _, value := range values {
				to.Set(key, value)
			}
		}
	}
}

// NewRequest creates a new HTTP request for a given resource and user.
func (p *Proxy) NewRequest(req *http.Request, user string, authenticated bool) (*http.Response, error) {
	// prepare new request
	request, err := http.NewRequest(req.Method, req.URL.String(), req.Body)
	// copy headers
	CopyHeaders(req.Header, request.Header)
	// overwrite User Agent
	request.Header.Set("User-Agent", GetServerFullName())

	// build new response
	if !authenticated || len(user) == 0 {
		return p.HTTPClient.Do(request)
	}

	request.Header.Set("On-Behalf-Of", user)
	solutionMsg := "Retrying with WebID-TLS"

	// Retry the request
	if len(cookies[user]) > 0 && len(cookies[user][req.Host]) > 0 { // Use existing cookie
		solutionMsg = "Retrying with cookies"
		request.AddCookie(cookies[user][req.Host][0])
	}
	// perform the request
	r, err := p.HTTPAgentClient.Do(request)
	if err != nil {
		return r, err
	}

	// Store cookies per user and request host
	if len(r.Cookies()) > 0 {
		cookiesL.Lock()
		// TODO: should store cookies based on domain value AND path from cookie
		cookies[user] = map[string][]*http.Cookie{}
		cookies[user][req.Host] = r.Cookies()
		p.Log.Printf("Cookies: %+v\n", cookies)
		cookiesL.Unlock()
	}
	p.Log.Println("Resource "+request.URL.String(),
		"requires authentication (HTTP 401).", solutionMsg,
		"resulted in HTTP", r.StatusCode)

	p.Log.Println("Got authenticated response code:", r.StatusCode)
	return r, err
}

func rememberURI(uri string) bool {
	if !privateUris[uri] {
		privateUrisL.Lock()
		privateUris[uri] = true
		privateUrisL.Unlock()
		return true
	}
	return false
}

func forgetURI(uri string) bool {
	if privateUris[uri] {
		delete(privateUris, uri)
		return true
	}
	return false
}

func requiresAuth(uri string) bool {
	if len(privateUris) > 0 && privateUris[uri] {
		return true
	}
	return false
}

func (p *Proxy) execError(w http.ResponseWriter, err error) {
	p.Log.Println("Request execution error:", err)
	w.WriteHeader(500)
	w.Write([]byte(err.Error()))
}

// NewClient creates a new http.Client object to be used for fetching resources. The skip parameter is used to indicate if the client should ship server certificate verification.
func NewClient(skip bool) *http.Client {
	return &http.Client{
		Transport: &http.Transport{
			MaxIdleConnsPerHost: maxIdleConnections,
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: skip,
			},
		},
		Timeout: time.Duration(requestTimeout) * time.Second,
	}
}

// NewAgentClient creates a new http.Client to be used for agent requests. The skip parameter is used to indicate if the client should ship server certificate verification.
func (agent *Agent) NewAgentClient(skip bool) *http.Client {
	return &http.Client{
		Transport: &http.Transport{
			MaxIdleConnsPerHost: maxIdleConnections,
			TLSClientConfig: &tls.Config{
				Certificates:       []tls.Certificate{*agent.Cert},
				InsecureSkipVerify: skip,
			},
		},
		Timeout: time.Duration(requestTimeout) * time.Second,
	}
}

func timeTrack(start time.Time, name string, logger *log.Logger) {
	elapsed := time.Since(start)
	logger.Printf("%s finished in %s", name, elapsed)
}

// SetRequestTimeout sets the timeout value in seconds for all request
func SetRequestTimeout(sec int) {
	requestTimeout = sec
}
<commit_msg>added example for godoc<commit_after>// Copyright 2017 Andrei Sambra and the Solid Project team. All rights reserved.
// Use of this source code is governed by an MIT-style license that can be found
// in the LICENSE file.

// Package solidproxy is a transparent browser that can handle WebID-TLS delegated
// auth for resources that require authentication.
//
// A trivial example is:
//
// package main

// import (
// 	"log"
// 	"net/http"
// 	"os"

// 	"github.com/solid/solidproxy"
// )

// func main() {
// 	mux := http.NewServeMux()

// 	// Init logger
// 	logger := log.New(os.Stderr, "[debug] ", log.Flags()|log.Lshortfile)

// 	// Next we create a new (local) agent object with its corresponding key
// 	// pair and profile document and serve it under /agent
// 	// Alternatively, we can create a "remote" agent to which we need to
// 	// provide a cert (tls.Certificate) you can load from somewhere:
// 	// agent, err := solidproxy.NewAgent("https://example.org/agent#me")
// 	// agent.Cert = someTLScert

// 	agent, err := solidproxy.NewAgentLocal("http://localhost:8080/agent#me")
// 	if err != nil {
// 		log.Println("Error creating new agent:", err.Error())
// 		return
// 	}
// 	// assign logger
// 	agent.Log = logger

// 	// Skip verifying trust chain for certificates?
// 	// Use true when dealing with self-signed certs (testing, etc.)
// 	insecureSkipVerify := true
// 	// Create a new proxy object
// 	proxy := solidproxy.NewProxy(agent, insecureSkipVerify)
// 	// assign logger
// 	proxy.Log = logger

// 	// Prepare proxy handler and serve it at http://localhost:8080/proxy
// 	mux.HandleFunc("/proxy", proxy.Handler)

// 	// The handleAgent is only needed if you plan to serve the agent's WebID
// 	// profile yourself; it will be available at http://localhost:8080/agent
// 	mux.HandleFunc("/agent", agent.Handler)

// 	logger.Println("Listening...")
// 	http.ListenAndServe(":8080", mux)
// }

package solidproxy

import (
	"crypto/tls"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"sync"
	"time"
)

const (
	maxIdleConnections int = 20
)

var (
	cookies  = map[string]map[string][]*http.Cookie{}
	cookiesL = new(sync.RWMutex)

	privateUris    = map[string]bool{}
	privateUrisL   = new(sync.RWMutex)
	requestTimeout = 2
)

// Proxy is a structure that encapsulates both clients (agent and fetcher), agent object and logger object.
type Proxy struct {
	HTTPClient      *http.Client
	HTTPAgentClient *http.Client
	Log             *log.Logger
	Agent           *Agent
}

// NewProxy returns a new Proxy object based on the provided agent configuration. The skip parameter is used to indicate if the client should ship server certificate verification.
func NewProxy(agent *Agent, skip bool) *Proxy {
	p := &Proxy{
		HTTPClient: NewClient(skip),
		Agent:      agent,
		Log:        InitLogger(false),
	}

	if agent.Cert != nil {
		p.HTTPAgentClient = agent.NewAgentClient(skip)
	}

	return p
}

// Handler is the main HTTP handler for the proxy/agent server.
func (p *Proxy) Handler(w http.ResponseWriter, req *http.Request) {
	p.Log.Println("New request from:", req.RemoteAddr, "for URI:", req.URL.String())
	// Log the time it takes to finish the request (for debugging)
	defer timeTrack(time.Now(), req.Method+" operation", p.Log)

	uri := req.FormValue("uri")
	if len(uri) == 0 {
		msg := "HTTP 400 - Bad Request. Please provide a URI to the proxy."
		p.Log.Println(msg, req.URL.String())
		w.WriteHeader(400)
		w.Write([]byte(msg))
		return
	}

	resource, err := url.ParseRequestURI(uri)
	if err != nil {
		p.Log.Println("Error parsing URL:", req.URL, err.Error())
		w.WriteHeader(400)
		w.Write([]byte("HTTP 400 - Bad Request. You must provide a valid URI: " + req.URL.String()))
		return
	}
	// rewrite URL
	req.URL = resource
	req.Host = resource.Host
	req.RequestURI = resource.RequestURI()
	// get user
	user := req.Header.Get("User")

	// check if we need to authenticate from the start
	authenticated := false
	if requiresAuth(req.URL.String()) {
		authenticated = true
		p.Log.Println("Request will use credentials for cached URI:", req.URL.String())
	}

	p.Log.Println("Proxying request for URI:", req.URL, "and user:", user, "using Agent:", p.Agent.WebID)

	// build new response
	var r *http.Response
	r, err = p.NewRequest(req, user, authenticated)
	if err != nil {
		p.execError(w, err)
		return
	}
	// the resource might have turned public, no need to remember it anymore
	if r.StatusCode >= 200 && r.StatusCode <= 400 {
		forgetURI(req.URL.String())
	}
	// Retry with server credentials if authentication is required
	if r.StatusCode == 401 {
		// Close the response to reuse the connection
		defer r.Body.Close()

		saved := rememberURI(req.URL.String())
		if saved {
			p.Log.Println(req.URL.String(), "saved to auth list")
		}
		if len(user) > 0 && p.HTTPAgentClient != nil {
			authenticated = true
			r, err = p.NewRequest(req, user, authenticated)
			if err != nil {
				p.execError(w, err)
				return
			}
			defer r.Body.Close()
		}
	}

	// Write data back
	// CORS
	w.Header().Set("Access-Control-Allow-Credentials", "true")
	w.Header().Set("Access-Control-Expose-Headers", "User, Triples, Location, Origin, Link, Vary, Last-Modified, Content-Length")
	w.Header().Set("Access-Control-Max-Age", "60")
	origin := req.Header.Get("Origin")
	if len(origin) > 0 {
		w.Header().Set("Access-Control-Allow-Origin", origin)
	}

	// copy headers
	CopyHeaders(r.Header, w.Header())

	w.WriteHeader(r.StatusCode)
	// r.Body will be empty at worst, so it should never trigger an error
	body, _ := ioutil.ReadAll(r.Body)
	w.Write(body)

	p.Log.Println("Response received with HTTP status", r.StatusCode)
	return
}

// CopyHeaders is used to copy headers between two http.Header objects (usually two request/response objects)
func CopyHeaders(from http.Header, to http.Header) {
	for key, values := range from {
		if key != "User" && key != "Cookie" {
			for _, value := range values {
				to.Set(key, value)
			}
		}
	}
}

// NewRequest creates a new HTTP request for a given resource and user.
func (p *Proxy) NewRequest(req *http.Request, user string, authenticated bool) (*http.Response, error) {
	// prepare new request
	request, err := http.NewRequest(req.Method, req.URL.String(), req.Body)
	// copy headers
	CopyHeaders(req.Header, request.Header)
	// overwrite User Agent
	request.Header.Set("User-Agent", GetServerFullName())

	// build new response
	if !authenticated || len(user) == 0 {
		return p.HTTPClient.Do(request)
	}

	request.Header.Set("On-Behalf-Of", user)
	solutionMsg := "Retrying with WebID-TLS"

	// Retry the request
	if len(cookies[user]) > 0 && len(cookies[user][req.Host]) > 0 { // Use existing cookie
		solutionMsg = "Retrying with cookies"
		request.AddCookie(cookies[user][req.Host][0])
	}
	// perform the request
	r, err := p.HTTPAgentClient.Do(request)
	if err != nil {
		return r, err
	}

	// Store cookies per user and request host
	if len(r.Cookies()) > 0 {
		cookiesL.Lock()
		// TODO: should store cookies based on domain value AND path from cookie
		cookies[user] = map[string][]*http.Cookie{}
		cookies[user][req.Host] = r.Cookies()
		p.Log.Printf("Cookies: %+v\n", cookies)
		cookiesL.Unlock()
	}
	p.Log.Println("Resource "+request.URL.String(),
		"requires authentication (HTTP 401).", solutionMsg,
		"resulted in HTTP", r.StatusCode)

	p.Log.Println("Got authenticated response code:", r.StatusCode)
	return r, err
}

func rememberURI(uri string) bool {
	if !privateUris[uri] {
		privateUrisL.Lock()
		privateUris[uri] = true
		privateUrisL.Unlock()
		return true
	}
	return false
}

func forgetURI(uri string) bool {
	if privateUris[uri] {
		delete(privateUris, uri)
		return true
	}
	return false
}

func requiresAuth(uri string) bool {
	if len(privateUris) > 0 && privateUris[uri] {
		return true
	}
	return false
}

func (p *Proxy) execError(w http.ResponseWriter, err error) {
	p.Log.Println("Request execution error:", err)
	w.WriteHeader(500)
	w.Write([]byte(err.Error()))
}

// NewClient creates a new http.Client object to be used for fetching resources. The skip parameter is used to indicate if the client should ship server certificate verification.
func NewClient(skip bool) *http.Client {
	return &http.Client{
		Transport: &http.Transport{
			MaxIdleConnsPerHost: maxIdleConnections,
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: skip,
			},
		},
		Timeout: time.Duration(requestTimeout) * time.Second,
	}
}

// NewAgentClient creates a new http.Client to be used for agent requests. The skip parameter is used to indicate if the client should ship server certificate verification.
func (agent *Agent) NewAgentClient(skip bool) *http.Client {
	return &http.Client{
		Transport: &http.Transport{
			MaxIdleConnsPerHost: maxIdleConnections,
			TLSClientConfig: &tls.Config{
				Certificates:       []tls.Certificate{*agent.Cert},
				InsecureSkipVerify: skip,
			},
		},
		Timeout: time.Duration(requestTimeout) * time.Second,
	}
}

func timeTrack(start time.Time, name string, logger *log.Logger) {
	elapsed := time.Since(start)
	logger.Printf("%s finished in %s", name, elapsed)
}

// SetRequestTimeout sets the timeout value in seconds for all request
func SetRequestTimeout(sec int) {
	requestTimeout = sec
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2015 YAMAMOTO Masaya <pandax381@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */
package main

import (
	"crypto/tls"
	"flag"
	"io"
	"log"
	"net"
	"os"
	"os/signal"
	"sync"
	"syscall"
)

type config struct {
	laddr      string
	saddr      string
	raddr      string
	tlsAccept  bool
	tlsKey     string
	tlsCert    string
	tlsConnect bool
}

type proxy struct {
	conf config
	cert tls.Certificate
	quit chan bool
}

func NewProxy(conf config) *proxy {
	var cert tls.Certificate
	if conf.tlsAccept {
		var err error
		cert, err = tls.LoadX509KeyPair(conf.tlsCert, conf.tlsKey)
		if err != nil {
			log.Println(err)
			return nil
		}
	}
	return &proxy{
		conf: conf,
		cert: cert,
		quit: make(chan bool),
	}
}

func (p *proxy) Run() {
	listener, err := net.Listen("tcp", p.conf.laddr)
	if err != nil {
		log.Println(err)
		return
	}
	log.Printf("Listen on %s\n", listener.Addr())
	complete := make(chan bool)
	go func() {
		wg := &sync.WaitGroup{}
		quit := make(chan bool)
		for {
			conn, err := listener.Accept()
			if err != nil {
				if e, ok := err.(net.Error); ok && e.Temporary() {
					continue
				}
				listener.Close()
				close(quit)
				wg.Wait()
				close(complete)
				return
			}
			wg.Add(1)
			go p.handle(conn, wg, quit)
		}
	}()
	for {
		select {
		case <-p.quit:
			listener.Close()
		case <-complete:
			return
		}
	}
	// does not reach
}

func (p *proxy) handle(conn1 net.Conn, wg *sync.WaitGroup, quit chan bool) {
	defer func() {
		if err := recover(); err != nil {
			log.Println(err)
		}
		wg.Done()
		log.Println("Close Session")
	}()
	log.Println("Accept New Session")
	if p.conf.tlsAccept {
		tlsConfig := &tls.Config{
			Certificates: []tls.Certificate{p.cert},
		}
		conn1 = tls.Server(conn1, tlsConfig)
	}
	defer conn1.Close()
	log.Println("Connect Remote Host")
	saddr, err := net.ResolveTCPAddr("tcp", p.conf.saddr)
	if err != nil {
		log.Println(err)
		return
	}
	raddr, err := net.ResolveTCPAddr("tcp", p.conf.raddr)
	if err != nil {
		log.Println(err)
		return
	}
	var conn2 net.Conn
	conn2, err = net.DialTCP("tcp", saddr, raddr)
	if err != nil {
		log.Println(err)
		return
	}
	if p.conf.tlsConnect {
		tlsConfig := &tls.Config{
			InsecureSkipVerify: true,
		}
		conn2 = tls.Client(conn2, tlsConfig)
	}
	defer conn2.Close()
	complete := make(chan int64)
	go transfer(conn1, conn2, complete)
	go transfer(conn2, conn1, complete)
	for n := 2; n > 0; n-- {
		select {
		case <-complete:
			break
		case <-quit:
			conn1.Close()
			conn2.Close()
			for ; n > 0; n-- {
				<-complete
			}
			return
		}
	}
}

func transfer(dst, src net.Conn, complete chan<- int64) {
	n, err := io.Copy(dst, src)
	if err != nil {
		log.Println(err)
		if e, ok := err.(*net.OpError); ok && e.Err == syscall.EPIPE {
			if _, ok := src.(*net.TCPConn); ok {
				src.(*net.TCPConn).CloseRead()
			} else {
				src.Close()
			}
		}
	}
	if _, ok := dst.(*net.TCPConn); ok {
		dst.(*net.TCPConn).CloseWrite()
	} else {
		dst.Close()
	}
	complete <- n
}

func (p *proxy) shutdown() {
	close(p.quit)
}

func main() {
	laddr := flag.String("l", ":8000", "Listen Address")
	saddr := flag.String("s", "", "Source Address")
	raddr := flag.String("r", "localhost:8080", "Remote Address")
	tlsAccept := flag.Bool("tls-accept", false, "Enable TLS Accept")
	tlsCert := flag.String("tls-cert", "./server.crt", "Certificate File")
	tlsKey := flag.String("tls-key", "./server.key", "Privatekey File")
	tlsConnect := flag.Bool("tls-connect", false, "Enable TLS Connect")
	flag.Parse()

	log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile)
	log.Println("PID:", os.Getpid())

	config := config{
		laddr:      *laddr,
		saddr:      *saddr,
		raddr:      *raddr,
		tlsAccept:  *tlsAccept,
		tlsCert:    *tlsCert,
		tlsKey:     *tlsKey,
		tlsConnect: *tlsConnect,
	}
	proxy := NewProxy(config)
	complete := make(chan bool)
	go func() {
		defer close(complete)
		proxy.Run()
	}()
	sigch := make(chan os.Signal)
	signal.Notify(sigch, os.Interrupt)
	for {
		select {
		case s := <-sigch:
			log.Println("Recieve signal:", s)
			proxy.shutdown()
		case <-complete:
			log.Println("Good bye")
			return
		}
	}
	// does not reach
}
<commit_msg>パッケージ化し易くした<commit_after>/*
 * Copyright (c) 2015 YAMAMOTO Masaya <pandax381@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */
package main

import (
	"crypto/tls"
	"flag"
	"io"
	"log"
	"net"
	"os"
	"os/signal"
	"sync"
	"syscall"
)

type Config struct {
	ListenAddr string
	SourceAddr string
	RemoteAddr string
	TLSAccept  bool
	TLSKey     string
	TLSCert    string
	TLSConnect bool
}

type Proxy struct {
	conf Config
	cert tls.Certificate
	quit chan bool
}

func NewProxy(conf Config) *Proxy {
	var cert tls.Certificate
	if conf.TLSAccept {
		var err error
		cert, err = tls.LoadX509KeyPair(conf.TLSCert, conf.TLSKey)
		if err != nil {
			log.Println(err)
			return nil
		}
	}
	return &Proxy{
		conf: conf,
		cert: cert,
		quit: make(chan bool),
	}
}

func (p *Proxy) Run() {
	listener, err := net.Listen("tcp", p.conf.ListenAddr)
	if err != nil {
		log.Println(err)
		return
	}
	log.Printf("Listen on %s\n", listener.Addr())
	complete := make(chan bool)
	go func() {
		wg := &sync.WaitGroup{}
		quit := make(chan bool)
		for {
			conn, err := listener.Accept()
			if err != nil {
				if e, ok := err.(net.Error); ok && e.Temporary() {
					continue
				}
				listener.Close()
				close(quit)
				wg.Wait()
				close(complete)
				return
			}
			wg.Add(1)
			go p.handle(conn, wg, quit)
		}
	}()
	for {
		select {
		case <-p.quit:
			listener.Close()
		case <-complete:
			return
		}
	}
	// does not reach
}

func (p *Proxy) handle(conn1 net.Conn, wg *sync.WaitGroup, quit chan bool) {
	defer func() {
		if err := recover(); err != nil {
			log.Println(err)
		}
		wg.Done()
		log.Println("Close Session")
	}()
	log.Println("Accept New Session")
	if p.conf.TLSAccept {
		tlsConfig := &tls.Config{
			Certificates: []tls.Certificate{p.cert},
		}
		conn1 = tls.Server(conn1, tlsConfig)
	}
	defer conn1.Close()
	log.Println("Connect Remote Host")
	saddr, err := net.ResolveTCPAddr("tcp", p.conf.SourceAddr)
	if err != nil {
		log.Println(err)
		return
	}
	raddr, err := net.ResolveTCPAddr("tcp", p.conf.RemoteAddr)
	if err != nil {
		log.Println(err)
		return
	}
	var conn2 net.Conn
	conn2, err = net.DialTCP("tcp", saddr, raddr)
	if err != nil {
		log.Println(err)
		return
	}
	if p.conf.TLSConnect {
		tlsConfig := &tls.Config{
			InsecureSkipVerify: true,
		}
		conn2 = tls.Client(conn2, tlsConfig)
	}
	defer conn2.Close()
	complete := make(chan int64)
	go transfer(conn1, conn2, complete)
	go transfer(conn2, conn1, complete)
	for n := 2; n > 0; n-- {
		select {
		case <-complete:
			break
		case <-quit:
			conn1.Close()
			conn2.Close()
			for ; n > 0; n-- {
				<-complete
			}
			return
		}
	}
}

func transfer(dst, src net.Conn, complete chan<- int64) {
	n, err := io.Copy(dst, src)
	if err != nil {
		log.Println(err)
		if e, ok := err.(*net.OpError); ok && e.Err == syscall.EPIPE {
			if _, ok := src.(*net.TCPConn); ok {
				src.(*net.TCPConn).CloseRead()
			} else {
				src.Close()
			}
		}
	}
	if _, ok := dst.(*net.TCPConn); ok {
		dst.(*net.TCPConn).CloseWrite()
	} else {
		dst.Close()
	}
	complete <- n
}

func (p *Proxy) Shutdown() {
	close(p.quit)
}

func main() {
    config := Config{}
	flag.StringVar(&config.ListenAddr, "l", ":8000", "Listen Address")
	flag.StringVar(&config.SourceAddr, "s", "", "Source Address")
	flag.StringVar(&config.RemoteAddr, "r", "localhost:8080", "Remote Address")
	flag.BoolVar(&config.TLSAccept, "tls-accept", false, "Enable TLS Accept")
	flag.StringVar(&config.TLSCert, "tls-cert", "./server.crt", "Certificate File")
	flag.StringVar(&config.TLSKey,  "tls-key", "./server.key", "Privatekey File")
	flag.BoolVar(&config.TLSConnect, "tls-connect", false, "Enable TLS Connect")
	flag.Parse()

	log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile)
	log.Println("PID:", os.Getpid())

	proxy := NewProxy(config)
	complete := make(chan bool)
	go func() {
		defer close(complete)
		proxy.Run()
	}()
	sigch := make(chan os.Signal)
	signal.Notify(sigch, os.Interrupt)
	for {
		select {
		case s := <-sigch:
			log.Println("Recieve signal:", s)
			proxy.Shutdown()
		case <-complete:
			log.Println("Good bye")
			return
		}
	}
	// does not reach
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"flag"
	"log"
	"net/http"
	"net/url"
	"strings"
	"io"
	"io/ioutil"
	"encoding/json"
)

type commitData struct {
	Before     string
	After      string
	Ref        string
	UserName   string
	Repository struct {
		Url string
	}
}

var (
	listen = flag.String("listen", "localhost:9080", "listen on address")
	logp   = flag.Bool("log", false, "enable logging")
)

func main() {
	flag.Parse()

	tr := &http.Transport{
        TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
    }
    client := &http.Client{Transport: tr}
	proxyHandler := client.HandlerFunc(proxyHandlerFunc)
	log.Fatal(http.ListenAndServe(*listen, proxyHandler))
}

func readerToString(r io.Reader) string {
	if b, err := ioutil.ReadAll(r); err == nil {
		return string(b)
	}
	return ""
}

func setGitData(form url.Values, g commitData) {
	form.Set("START", g.Before)
	form.Set("END", g.After)
	form.Set("REFNAME", g.Ref)

	refToWork := g.Ref
	s := strings.Split(refToWork, "/");
	log.Printf("Tag is : %v\n", s[2])

	form.Set("TAG_NAME", s[2])
	form.Set("GITURL", g.Repository.Url)
}

func proxyToEndpoint(url string, form url.Values, w http.ResponseWriter) error {
	resp, err := http.PostForm(url, form)
	log.Printf("Posting to: %v\n", url)
	log.Printf("Posting to: %v\n", form)

	if err != nil {
		log.Print(err)
		fmt.Fprintf(w, "ERROR")
	} else {
		defer resp.Body.Close()
		resp.Write(w)
	}
	return err
}

func infoPage(notice string) string {
	return fmt.Sprintf(
		"
githookproxy
"+
			"
Proxy takes JSON body in the format of:
"+
			"
Gitlab Webhook
"+
			"
It will converts it to parameters and will post to url specified by 'url' param.
"+
			"
Parameters will include:"+
			"

payload:JSON body

URL: url of git repo
"+
			"
START: Start commit hash

END: End commit hash
"+
			"
REFNAME: Ref name

"+
			"
To use, add this to your Gitlab webook: http://[proxy_listen_url]?url=[target_url]
"+
			"
Notice: %v
"+
			"
Code: Github",
		notice)
}

func proxyHandlerFunc(w http.ResponseWriter, r *http.Request) {
	if *logp {
		log.Println(r.URL)
	}

	body := readerToString(r.Body)
	decoder := json.NewDecoder(strings.NewReader(body))
	var gitData commitData
	err := decoder.Decode(&gitData)

	if err != nil {
		log.Print(err)
		fmt.Fprintf(w, infoPage("JSON body not found or invalid!"))
	} else if r.FormValue("url") == "" {
		log.Print("URL not found!")
		fmt.Fprintf(w, infoPage("URL not found!"))
	} else {
		form := make(url.Values)
		setGitData(form, gitData)
		form.Set("PAYLOAD", body)

		postUrl := r.FormValue("url")
		proxyToEndpoint(postUrl, form, w)
	}
}
<commit_msg>implemented the logic for accept all certificates<commit_after>package main

import (
	"fmt"
	"flag"
	"log"
	"net/http"
	"net/url"
	"strings"
	"io"
	"io/ioutil"
	"encoding/json"
	"crypto/tls"
)

type commitData struct {
	Before     string
	After      string
	Ref        string
	UserName   string
	Repository struct {
		Url string
	}
}

var (
	listen = flag.String("listen", "localhost:9080", "listen on address")
	logp   = flag.Bool("log", false, "enable logging")
)

func main() {
	flag.Parse()
	proxyHandler := http.HandlerFunc(proxyHandlerFunc)
	log.Fatal(http.ListenAndServe(*listen, proxyHandler))
}

func readerToString(r io.Reader) string {
	if b, err := ioutil.ReadAll(r); err == nil {
		return string(b)
	}
	return ""
}

func setGitData(form url.Values, g commitData) {
	form.Set("START", g.Before)
	form.Set("END", g.After)
	form.Set("REFNAME", g.Ref)

	refToWork := g.Ref
	s := strings.Split(refToWork, "/");
	log.Printf("Tag is : %v\n", s[2])

	form.Set("TAG_NAME", s[2])
	form.Set("GITURL", g.Repository.Url)
}

func proxyToEndpoint(url string, form url.Values, w http.ResponseWriter) error {

	tr := &http.Transport{
        TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
    }
    client := &http.Client{Transport: tr}
	resp, err := client.PostForm(url, form)
	log.Printf("Posting to: %v\n", url)
	log.Printf("Posting to: %v\n", form)

	if err != nil {
		log.Print(err)
		fmt.Fprintf(w, "ERROR")
	} else {
		defer resp.Body.Close()
		resp.Write(w)
	}
	return err
}

func infoPage(notice string) string {
	return fmt.Sprintf(
		"
githookproxy
"+
			"
Proxy takes JSON body in the format of:
"+
			"
Gitlab Webhook
"+
			"
It will converts it to parameters and will post to url specified by 'url' param.
"+
			"
Parameters will include:"+
			"

payload:JSON body

URL: url of git repo
"+
			"
START: Start commit hash

END: End commit hash
"+
			"
REFNAME: Ref name

"+
			"
To use, add this to your Gitlab webook: http://[proxy_listen_url]?url=[target_url]
"+
			"
Notice: %v
"+
			"
Code: Github",
		notice)
}

func proxyHandlerFunc(w http.ResponseWriter, r *http.Request) {
	if *logp {
		log.Println(r.URL)
	}

	body := readerToString(r.Body)
	decoder := json.NewDecoder(strings.NewReader(body))
	var gitData commitData
	err := decoder.Decode(&gitData)

	if err != nil {
		log.Print(err)
		fmt.Fprintf(w, infoPage("JSON body not found or invalid!"))
	} else if r.FormValue("url") == "" {
		log.Print("URL not found!")
		fmt.Fprintf(w, infoPage("URL not found!"))
	} else {
		form := make(url.Values)
		setGitData(form, gitData)
		form.Set("PAYLOAD", body)

		postUrl := r.FormValue("url")
		proxyToEndpoint(postUrl, form, w)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/orderbynull/lottip/chat"
	"github.com/orderbynull/lottip/protocol"
	"io"
	"log"
	"net"
)

type RequestPacketParser struct {
	connId        string
	cmdId         *int
	cmdChan       chan chat.Cmd
	connStateChan chan chat.ConnState
}

func (pp *RequestPacketParser) Write(p []byte) (n int, err error) {
	*pp.cmdId++

	switch protocol.GetPacketType(p) {
	case protocol.ComStmtPrepare:
	case protocol.ComQuery:
		decoded, err := protocol.DecodeQueryRequest(p)
		if err == nil {
			pp.cmdChan <- chat.Cmd{pp.connId, *pp.cmdId, "", decoded.Query, nil, false}
		}
	case protocol.ComQuit:
		pp.connStateChan <- chat.ConnState{pp.connId, protocol.ConnStateFinished}
	}

	return len(p), nil
}

type ResponsePacketParser struct {
	connId        string
	cmdId         *int
	cmdResultChan chan chat.CmdResult
}

func (pp *ResponsePacketParser) Write(p []byte) (n int, err error) {
	switch protocol.GetPacketType(p) {
	case protocol.ResponseErr:
		pp.cmdResultChan <- chat.CmdResult{pp.connId, *pp.cmdId, protocol.ResponseErr, "Fuck!", "1s"}
	default:
		pp.cmdResultChan <- chat.CmdResult{pp.connId, *pp.cmdId, protocol.ResponseOk, "", "1s"}
	}

	return len(p), nil
}

// proxy implements server for capturing and forwarding MySQL traffic.
type proxy struct {
	cmdChan       chan chat.Cmd
	cmdResultChan chan chat.CmdResult
	connStateChan chan chat.ConnState
	appReadyChan  chan bool
	mysqlHost     string
	proxyHost     string
}

// run starts accepting TCP connection and forwarding it to MySQL server.
// Each incoming TCP connection is handled in own goroutine.
func (p *proxy) run() {
	listener, err := net.Listen("tcp", p.proxyHost)
	if err != nil {
		log.Fatal(err.Error())
	}
	defer listener.Close()

	go func() {
		p.appReadyChan <- true
		close(p.appReadyChan)
	}()

	for {
		client, err := listener.Accept()
		if err != nil {
			log.Print(err.Error())
		}

		go p.handleConnection(client)
	}
}

// handleConnection ...
func (p *proxy) handleConnection(client net.Conn) {
	defer client.Close()

	// New connection to MySQL is made per each incoming TCP request to proxy server.
	server, err := net.Dial("tcp", p.mysqlHost)
	if err != nil {
		log.Print(err.Error())
		return
	}
	defer server.Close()

	connId := fmt.Sprintf("%s -> %s", client.RemoteAddr().String(), server.RemoteAddr().String())

	defer func() { p.connStateChan <- chat.ConnState{connId, protocol.ConnStateFinished} }()

	var cmdId int

	// Copy bytes from client to [server, requestParser]
	go io.Copy(io.MultiWriter(server, &RequestPacketParser{connId, &cmdId, p.cmdChan, p.connStateChan}), client)

	// Copy bytes from server to [client, responseParser]
	io.Copy(io.MultiWriter(client, &ResponsePacketParser{connId, &cmdId, p.cmdResultChan}), server)
}
<commit_msg>Code refactor<commit_after>package main

import (
	"fmt"
	"github.com/orderbynull/lottip/chat"
	"github.com/orderbynull/lottip/protocol"
	"io"
	"log"
	"net"
)

type RequestPacketParser struct {
	connId        string
	cmdId         *int
	cmdChan       chan chat.Cmd
	connStateChan chan chat.ConnState
}

func (pp *RequestPacketParser) Write(p []byte) (n int, err error) {
	*pp.cmdId++

	switch protocol.GetPacketType(p) {
	case protocol.ComStmtPrepare:
	case protocol.ComQuery:
		decoded, err := protocol.DecodeQueryRequest(p)
		if err == nil {
			pp.cmdChan <- chat.Cmd{pp.connId, *pp.cmdId, "", decoded.Query, nil, false}
		}
	case protocol.ComQuit:
		pp.connStateChan <- chat.ConnState{pp.connId, protocol.ConnStateFinished}
	}

	return len(p), nil
}

type ResponsePacketParser struct {
	connId        string
	cmdId         *int
	cmdResultChan chan chat.CmdResult
}

func (pp *ResponsePacketParser) Write(p []byte) (n int, err error) {
	switch protocol.GetPacketType(p) {
	case protocol.ResponseErr:
		pp.cmdResultChan <- chat.CmdResult{pp.connId, *pp.cmdId, protocol.ResponseErr, "Fuck!", "1s"}
	default:
		pp.cmdResultChan <- chat.CmdResult{pp.connId, *pp.cmdId, protocol.ResponseOk, "", "1s"}
	}

	return len(p), nil
}

// proxy implements server for capturing and forwarding MySQL traffic.
type proxy struct {
	cmdChan       chan chat.Cmd
	cmdResultChan chan chat.CmdResult
	connStateChan chan chat.ConnState
	appReadyChan  chan bool
	mysqlHost     string
	proxyHost     string
}

// run starts accepting TCP connection and forwarding it to MySQL server.
// Each incoming TCP connection is handled in own goroutine.
func (p *proxy) run() {
	listener, err := net.Listen("tcp", p.proxyHost)
	if err != nil {
		log.Fatal(err.Error())
	}
	defer listener.Close()

	go func() {
		p.appReadyChan <- true
		close(p.appReadyChan)
	}()

	for {
		client, err := listener.Accept()
		if err != nil {
			log.Print(err.Error())
		}

		go p.handleConnection(client)
	}
}

// handleConnection ...
func (p *proxy) handleConnection(client net.Conn) {
	defer client.Close()

	// New connection to MySQL is made per each incoming TCP request to proxy server.
	server, err := net.Dial("tcp", p.mysqlHost)
	if err != nil {
		log.Print(err.Error())
		return
	}
	defer server.Close()

	connId := fmt.Sprintf("%s => %s", client.RemoteAddr().String(), server.RemoteAddr().String())

	defer func() { p.connStateChan <- chat.ConnState{connId, protocol.ConnStateFinished} }()

	var cmdId int

	// Copy bytes from client to server and requestParser
	go io.Copy(io.MultiWriter(server, &RequestPacketParser{connId, &cmdId, p.cmdChan, p.connStateChan}), client)

	// Copy bytes from server to client and responseParser
	io.Copy(io.MultiWriter(client, &ResponsePacketParser{connId, &cmdId, p.cmdResultChan}), server)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"

	"github.com/Ralph-Lee/HelloGo/util"
)

func main() {
	fmt.Println(util.Reverse("!oG ,olleH"))
}
<commit_msg>update<commit_after>package main

import (
	"fmt"

	"github.com/Ralph-Lee/HelloGo/util"
)

func main() {
	fmt.Println(util.Reverse("!oG ,olleH"))
	fmt.Println("It's work out of box.")
}
<|endoftext|>