<commit_before>package endpoint

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"math"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/bluele/gcache"
	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
	"github.com/google/gopacket/pcap"
)

const (
	bufSize                     = 8 * 1024 * 1024 // 8MB
	maxReverseDNSrecords        = 10000
	maxLogsPerDecodingError     = 4
	maxDecodingErrorCardinality = 1000
)

// DNSSnooper is a snopper of DNS queries
type DNSSnooper struct {
	stop                chan struct{}
	pcapHandle          *pcap.Handle
	reverseDNSCache     gcache.Cache
	decodingErrorCounts map[string]uint64 // for limiting
}

// NewDNSSnooper creates a new snooper of DNS queries
func NewDNSSnooper() (*DNSSnooper, error) {
	pcapHandle, err := newPcapHandle()
	if err != nil {
		return nil, err
	}
	reverseDNSCache := gcache.New(maxReverseDNSrecords).LRU().Build()

	s := &DNSSnooper{
		stop:                make(chan struct{}),
		pcapHandle:          pcapHandle,
		reverseDNSCache:     reverseDNSCache,
		decodingErrorCounts: map[string]uint64{},
	}
	go s.run()
	return s, nil
}

func newPcapHandle() (*pcap.Handle, error) {
	inactive, err := pcap.NewInactiveHandle("any")
	if err != nil {
		return nil, err
	}
	defer inactive.CleanUp()
	// pcap timeout blackmagic copied from Weave Net to reduce CPU consumption
	// see https://github.com/weaveworks/weave/commit/025315363d5ea8b8265f1b3ea800f24df2be51a4
	if err = inactive.SetTimeout(time.Duration(math.MaxInt64)); err != nil {
		return nil, err
	}
	if err = inactive.SetImmediateMode(true); err != nil {
		// If gopacket is compiled against an older pcap.h that
		// doesn't have pcap_set_immediate_mode, it supplies a dummy
		// definition that always returns PCAP_ERROR.  That becomes
		// "Generic error", which is not very helpful.  The real
		// pcap_set_immediate_mode never returns PCAP_ERROR, so this
		// turns it into a more informative message.
		if fmt.Sprint(err) == "Generic error" {
			return nil, fmt.Errorf("compiled against an old version of libpcap; please compile against libpcap-1.5.0 or later")
		}

		return nil, err
	}
	if err = inactive.SetBufferSize(bufSize); err != nil {
		return nil, err
	}
	pcapHandle, err := inactive.Activate()
	if err != nil {
		return nil, err
	}
	if err := pcapHandle.SetDirection(pcap.DirectionIn); err != nil {
		pcapHandle.Close()
		return nil, err
	}
	if err := pcapHandle.SetBPFFilter("inbound and port 53"); err != nil {
		pcapHandle.Close()
		return nil, err
	}

	return pcapHandle, nil
}

// CachedNamesForIP obtains the domains associated to an IP,
// obtained while snooping A-record queries
func (s *DNSSnooper) CachedNamesForIP(ip string) []string {
	result := []string{}
	if s == nil {
		return result
	}
	domains, err := s.reverseDNSCache.Get(ip)
	if err != nil {
		return result
	}

	for domain := range domains.(map[string]struct{}) {
		result = append(result, domain)
	}

	return result
}

// Stop makes the snooper stop inspecting DNS communications
func (s *DNSSnooper) Stop() {
	if s != nil {
		close(s.stop)
	}
}

// Gopacket doesn't provide direct support for DNS over TCP, see https://github.com/google/gopacket/issues/236
type tcpWithDNSSupport struct {
	tcp layers.TCP
}

func (m *tcpWithDNSSupport) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
	return m.tcp.DecodeFromBytes(data, df)
}

func (m *tcpWithDNSSupport) CanDecode() gopacket.LayerClass { return m.tcp.CanDecode() }

// Determine if a TCP segment contains a full DNS message (i.e. not fragmented)
func (m *tcpWithDNSSupport) hasSelfContainedDNSPayload() bool {
	payload := m.tcp.LayerPayload()
	if len(payload) < 2 {
		return false
	}

	// Assume it's a self-contained DNS message if the Length field
	// matches the length of the TCP segment
	dnsLengthField := binary.BigEndian.Uint16(payload)
	return int(dnsLengthField) == len(payload)-2
}

func (m *tcpWithDNSSupport) NextLayerType() gopacket.LayerType {
	// TODO: deal with TCP fragmentation and out-of-order segments
	if (m.tcp.SrcPort == 53 || m.tcp.DstPort == 53) && m.hasSelfContainedDNSPayload() {
		return layers.LayerTypeDNS
	}
	return m.tcp.NextLayerType()
}

func (m *tcpWithDNSSupport) LayerPayload() []byte {
	payload := m.tcp.LayerPayload()
	if len(payload) > 1 && (m.tcp.SrcPort == 53 || m.tcp.DstPort == 53) {
		// Omit the DNS length field, only included
		// in TCP, in order to reuse the DNS UDP parser
		payload = payload[2:]
	}
	return payload
}

func (s *DNSSnooper) run() {
	var (
		decodedLayers []gopacket.LayerType
		dns           layers.DNS
		udp           layers.UDP
		tcp           tcpWithDNSSupport
		ip4           layers.IPv4
		ip6           layers.IPv6
		eth           layers.Ethernet
		dot1q         layers.Dot1Q
		sll           layers.LinuxSLL
	)

	// assumes that the "any" interface is being used (see https://wiki.wireshark.org/SLL)
	packetParser := gopacket.NewDecodingLayerParser(layers.LayerTypeLinuxSLL, &sll, &dot1q, &eth, &ip4, &ip6, &udp, &tcp, &dns)

	for {
		select {
		case <-s.stop:
			s.pcapHandle.Close()
			return
		default:
		}

		packet, _, err := s.pcapHandle.ZeroCopyReadPacketData()
		if err != nil {
			// TimeoutExpired is acceptable due to the Timeout black magic
			// on the handle.
			if err != pcap.NextErrorTimeoutExpired {
				log.Errorf("DNSSnooper: error reading packet data: %s", err)
			}
			continue
		}

		if err := packetParser.DecodeLayers(packet, &decodedLayers); err != nil {
			// LayerTypePayload indicates the TCP payload has non-DNS data, which we are not interested in
			if layer, ok := err.(gopacket.UnsupportedLayerType); !ok || gopacket.LayerType(layer) != gopacket.LayerTypePayload {
				s.handleDecodingError(err)
			}
			continue
		}

		for _, layerType := range decodedLayers {
			if layerType == layers.LayerTypeDNS {
				s.processDNSMessage(&dns)
			}
		}
	}
}

// handleDecodeError logs errors up to the maximum allowed count
func (s *DNSSnooper) handleDecodingError(err error) {
	// prevent potential memory leak
	if len(s.decodingErrorCounts) > maxDecodingErrorCardinality {
		return
	}

	str := err.Error()
	count := s.decodingErrorCounts[str]
	count++
	s.decodingErrorCounts[str] = count
	switch {
	case count == maxLogsPerDecodingError:
		log.Errorf("DNSSnooper: error decoding packet: %s (reached %d occurrences, silencing)", str, maxLogsPerDecodingError)
	case count < maxLogsPerDecodingError:
		log.Errorf("DNSSnooper: error decoding packet: %s", str)
	}
}

func (s *DNSSnooper) processDNSMessage(dns *layers.DNS) {

	// Only consider responses to singleton, A-record questions
	if !dns.QR || dns.ResponseCode != 0 || len(dns.Questions) != 1 {
		return
	}
	question := dns.Questions[0]
	if question.Type != layers.DNSTypeA || question.Class != layers.DNSClassIN {
		return
	}

	var (
		domainQueried = question.Name
		records       = append(dns.Answers, dns.Additionals...)
		ips           = map[string]struct{}{}
		alias         []byte
	)

	// Traverse records for a CNAME first since the DNS RFCs don't seem to guarantee it
	// appearing before its A-records
	for _, record := range records {
		if record.Type == layers.DNSTypeCNAME && record.Class == layers.DNSClassIN && bytes.Equal(domainQueried, record.Name) {
			alias = record.CNAME
			break
		}
	}

	// Finally, get the answer
	for _, record := range records {
		if record.Type != layers.DNSTypeA || record.Class != layers.DNSClassIN {
			continue
		}
		if bytes.Equal(domainQueried, record.Name) || (alias != nil && bytes.Equal(alias, record.Name)) {
			ips[record.IP.String()] = struct{}{}
		}
	}

	// Update cache
	newDomain := string(domainQueried)
	log.Debugf("DNSSnooper: caught DNS lookup: %s -> %v", newDomain, ips)
	for ip := range ips {
		if existingDomains, err := s.reverseDNSCache.Get(ip); err != nil {
			s.reverseDNSCache.Set(ip, map[string]struct{}{newDomain: {}})
		} else {
			// TODO: Be smarter about the expiration of entries with pre-existing associated domains
			existingDomains.(map[string]struct{})[newDomain] = struct{}{}
		}
	}
}
<commit_msg>Avoid race conditions in DNSSnooper's cached domains<commit_after>package endpoint

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"math"
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/bluele/gcache"
	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
	"github.com/google/gopacket/pcap"
)

const (
	bufSize                     = 8 * 1024 * 1024 // 8MB
	maxReverseDNSrecords        = 10000
	maxLogsPerDecodingError     = 4
	maxDecodingErrorCardinality = 1000
)

// DNSSnooper is a snopper of DNS queries
type DNSSnooper struct {
	stop                chan struct{}
	pcapHandle          *pcap.Handle
	reverseDNSCache     gcache.Cache
	decodingErrorCounts map[string]uint64 // for limiting
}

type domainSet struct {
	// Not worth using a RMutex since we don't expect a lot of contention
	// and they are considerably larger (8 bytes vs 24 bytes), which would
	// bloat the cache
	sync.Mutex
	domains map[string]struct{}
}

// NewDNSSnooper creates a new snooper of DNS queries
func NewDNSSnooper() (*DNSSnooper, error) {
	pcapHandle, err := newPcapHandle()
	if err != nil {
		return nil, err
	}
	reverseDNSCache := gcache.New(maxReverseDNSrecords).LRU().Build()

	s := &DNSSnooper{
		stop:                make(chan struct{}),
		pcapHandle:          pcapHandle,
		reverseDNSCache:     reverseDNSCache,
		decodingErrorCounts: map[string]uint64{},
	}
	go s.run()
	return s, nil
}

func newPcapHandle() (*pcap.Handle, error) {
	inactive, err := pcap.NewInactiveHandle("any")
	if err != nil {
		return nil, err
	}
	defer inactive.CleanUp()
	// pcap timeout blackmagic copied from Weave Net to reduce CPU consumption
	// see https://github.com/weaveworks/weave/commit/025315363d5ea8b8265f1b3ea800f24df2be51a4
	if err = inactive.SetTimeout(time.Duration(math.MaxInt64)); err != nil {
		return nil, err
	}
	if err = inactive.SetImmediateMode(true); err != nil {
		// If gopacket is compiled against an older pcap.h that
		// doesn't have pcap_set_immediate_mode, it supplies a dummy
		// definition that always returns PCAP_ERROR.  That becomes
		// "Generic error", which is not very helpful.  The real
		// pcap_set_immediate_mode never returns PCAP_ERROR, so this
		// turns it into a more informative message.
		if fmt.Sprint(err) == "Generic error" {
			return nil, fmt.Errorf("compiled against an old version of libpcap; please compile against libpcap-1.5.0 or later")
		}

		return nil, err
	}
	if err = inactive.SetBufferSize(bufSize); err != nil {
		return nil, err
	}
	pcapHandle, err := inactive.Activate()
	if err != nil {
		return nil, err
	}
	if err := pcapHandle.SetDirection(pcap.DirectionIn); err != nil {
		pcapHandle.Close()
		return nil, err
	}
	if err := pcapHandle.SetBPFFilter("inbound and port 53"); err != nil {
		pcapHandle.Close()
		return nil, err
	}

	return pcapHandle, nil
}

// CachedNamesForIP obtains the domains associated to an IP,
// obtained while snooping A-record queries
func (s *DNSSnooper) CachedNamesForIP(ip string) []string {
	result := []string{}
	if s == nil {
		return result
	}
	domains, err := s.reverseDNSCache.Get(ip)
	if err != nil {
		return result
	}

	domainSet := domains.(domainSet)
	domainSet.Lock()
	for domain := range domainSet.domains {
		result = append(result, domain)
	}
	domainSet.Unlock()

	return result
}

// Stop makes the snooper stop inspecting DNS communications
func (s *DNSSnooper) Stop() {
	if s != nil {
		close(s.stop)
	}
}

// Gopacket doesn't provide direct support for DNS over TCP, see https://github.com/google/gopacket/issues/236
type tcpWithDNSSupport struct {
	tcp layers.TCP
}

func (m *tcpWithDNSSupport) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
	return m.tcp.DecodeFromBytes(data, df)
}

func (m *tcpWithDNSSupport) CanDecode() gopacket.LayerClass { return m.tcp.CanDecode() }

// Determine if a TCP segment contains a full DNS message (i.e. not fragmented)
func (m *tcpWithDNSSupport) hasSelfContainedDNSPayload() bool {
	payload := m.tcp.LayerPayload()
	if len(payload) < 2 {
		return false
	}

	// Assume it's a self-contained DNS message if the Length field
	// matches the length of the TCP segment
	dnsLengthField := binary.BigEndian.Uint16(payload)
	return int(dnsLengthField) == len(payload)-2
}

func (m *tcpWithDNSSupport) NextLayerType() gopacket.LayerType {
	// TODO: deal with TCP fragmentation and out-of-order segments
	if (m.tcp.SrcPort == 53 || m.tcp.DstPort == 53) && m.hasSelfContainedDNSPayload() {
		return layers.LayerTypeDNS
	}
	return m.tcp.NextLayerType()
}

func (m *tcpWithDNSSupport) LayerPayload() []byte {
	payload := m.tcp.LayerPayload()
	if len(payload) > 1 && (m.tcp.SrcPort == 53 || m.tcp.DstPort == 53) {
		// Omit the DNS length field, only included
		// in TCP, in order to reuse the DNS UDP parser
		payload = payload[2:]
	}
	return payload
}

func (s *DNSSnooper) run() {
	var (
		decodedLayers []gopacket.LayerType
		dns           layers.DNS
		udp           layers.UDP
		tcp           tcpWithDNSSupport
		ip4           layers.IPv4
		ip6           layers.IPv6
		eth           layers.Ethernet
		dot1q         layers.Dot1Q
		sll           layers.LinuxSLL
	)

	// assumes that the "any" interface is being used (see https://wiki.wireshark.org/SLL)
	packetParser := gopacket.NewDecodingLayerParser(layers.LayerTypeLinuxSLL, &sll, &dot1q, &eth, &ip4, &ip6, &udp, &tcp, &dns)

	for {
		select {
		case <-s.stop:
			s.pcapHandle.Close()
			return
		default:
		}

		packet, _, err := s.pcapHandle.ZeroCopyReadPacketData()
		if err != nil {
			// TimeoutExpired is acceptable due to the Timeout black magic
			// on the handle.
			if err != pcap.NextErrorTimeoutExpired {
				log.Errorf("DNSSnooper: error reading packet data: %s", err)
			}
			continue
		}

		if err := packetParser.DecodeLayers(packet, &decodedLayers); err != nil {
			// LayerTypePayload indicates the TCP payload has non-DNS data, which we are not interested in
			if layer, ok := err.(gopacket.UnsupportedLayerType); !ok || gopacket.LayerType(layer) != gopacket.LayerTypePayload {
				s.handleDecodingError(err)
			}
			continue
		}

		for _, layerType := range decodedLayers {
			if layerType == layers.LayerTypeDNS {
				s.processDNSMessage(&dns)
			}
		}
	}
}

// handleDecodeError logs errors up to the maximum allowed count
func (s *DNSSnooper) handleDecodingError(err error) {
	// prevent potential memory leak
	if len(s.decodingErrorCounts) > maxDecodingErrorCardinality {
		return
	}

	str := err.Error()
	count := s.decodingErrorCounts[str]
	count++
	s.decodingErrorCounts[str] = count
	switch {
	case count == maxLogsPerDecodingError:
		log.Errorf("DNSSnooper: error decoding packet: %s (reached %d occurrences, silencing)", str, maxLogsPerDecodingError)
	case count < maxLogsPerDecodingError:
		log.Errorf("DNSSnooper: error decoding packet: %s", str)
	}
}

func (s *DNSSnooper) processDNSMessage(dns *layers.DNS) {

	// Only consider responses to singleton, A-record questions
	if !dns.QR || dns.ResponseCode != 0 || len(dns.Questions) != 1 {
		return
	}
	question := dns.Questions[0]
	if question.Type != layers.DNSTypeA || question.Class != layers.DNSClassIN {
		return
	}

	var (
		domainQueried = question.Name
		records       = append(dns.Answers, dns.Additionals...)
		ips           = map[string]struct{}{}
		alias         []byte
	)

	// Traverse records for a CNAME first since the DNS RFCs don't seem to guarantee it
	// appearing before its A-records
	for _, record := range records {
		if record.Type == layers.DNSTypeCNAME && record.Class == layers.DNSClassIN && bytes.Equal(domainQueried, record.Name) {
			alias = record.CNAME
			break
		}
	}

	// Finally, get the answer
	for _, record := range records {
		if record.Type != layers.DNSTypeA || record.Class != layers.DNSClassIN {
			continue
		}
		if bytes.Equal(domainQueried, record.Name) || (alias != nil && bytes.Equal(alias, record.Name)) {
			ips[record.IP.String()] = struct{}{}
		}
	}

	// Update cache
	newDomain := string(domainQueried)
	log.Debugf("DNSSnooper: caught DNS lookup: %s -> %v", newDomain, ips)
	for ip := range ips {
		if existingDomains, err := s.reverseDNSCache.Get(ip); err != nil {
			s.reverseDNSCache.Set(ip, domainSet{domains: map[string]struct{}{newDomain: {}}})
		} else {
			// TODO: Be smarter about the expiration of entries with pre-existing associated domains
			domainSet := existingDomains.(domainSet)
			domainSet.Lock()
			domainSet.domains[newDomain] = struct{}{}
			domainSet.Unlock()
		}
	}
}
<|endoftext|>


<commit_before>package gogo

import (
	"bytes"
	"encoding/gob"
	"encoding/json"
	"encoding/xml"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"sync"

	"gopkg.in/mgo.v2/bson"

	"github.com/golib/httprouter"
)

type AppParams struct {
	mux     sync.RWMutex
	request *http.Request
	params  httprouter.Params
	rawBody []byte
	rawErr  error
	readed  bool
}

func NewAppParams(r *http.Request, params httprouter.Params) *AppParams {
	return &AppParams{
		request: r,
		params:  params,
	}
}

// HasQuery returns whether named param is exist for URL query string.
func (p *AppParams) HasQuery(name string) bool {
	_, ok := p.request.URL.Query()[name]

	return ok
}

// HasForm returns whether named param is exist for POST/PUT request body.
func (p *AppParams) HasForm(name string) bool {
	p.request.ParseMultipartForm(DefaultMaxMultiformBytes)

	_, ok := p.request.PostForm[name]

	return ok
}

func (p *AppParams) RawBody() ([]byte, error) {
	if !p.readed {
		p.mux.Lock()
		if !p.readed {
			p.rawBody, p.rawErr = ioutil.ReadAll(p.request.Body)

			// close the request.Body
			p.request.Body.Close()

			p.readed = true
		}
		p.mux.Unlock()
	}

	return p.rawBody, p.rawErr
}

// Get returns the first value for the named component of the request.
// NOTE: httprouter.Params takes precedence over URL query string values.
func (p *AppParams) Get(name string) string {
	value := p.params.ByName(name)

	// trye URL query string if value of route is empty
	if value == "" {
		value = p.request.URL.Query().Get(name)
	}

	return value
}

// Post returns the named comonent of the request by calling http.Request.FormValue()
func (p *AppParams) Post(name string) string {
	return p.request.FormValue(name)
}

// File retrieves multipart uploaded file of HTTP POST request
func (p *AppParams) File(name string) (multipart.File, *multipart.FileHeader, error) {
	return p.request.FormFile(name)
}

// Json unmarshals request body with json codec
func (p *AppParams) Json(v interface{}) error {
	data, err := p.RawBody()
	if err != nil {
		return err
	}

	return json.Unmarshal(data, v)
}

// Xml unmarshals request body with xml codec
func (p *AppParams) Xml(v interface{}) error {
	data, err := p.RawBody()
	if err != nil {
		return err
	}

	return xml.Unmarshal(data, v)
}

// Gob decode request body with gob codec
func (p *AppParams) Gob(v interface{}) error {
	data, err := p.RawBody()
	if err != nil {
		return err
	}

	return gob.NewDecoder(bytes.NewBuffer(data)).Decode(v)
}

// Bson unmarshals request body with bson codec
func (p *AppParams) Bson(v interface{}) error {
	data, err := p.RawBody()
	if err != nil {
		return err
	}

	return bson.Unmarshal(data, v)
}
<commit_msg>add get params fucntions<commit_after>package gogo

import (
	"bytes"
	"encoding/gob"
	"encoding/json"
	"encoding/xml"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"strconv"
	"sync"

	"gopkg.in/mgo.v2/bson"

	"github.com/golib/httprouter"
)

type AppParams struct {
	mux     sync.RWMutex
	request *http.Request
	params  httprouter.Params
	rawBody []byte
	rawErr  error
	readed  bool
}

func NewAppParams(r *http.Request, params httprouter.Params) *AppParams {
	return &AppParams{
		request: r,
		params:  params,
	}
}

// HasQuery returns whether named param is exist for URL query string.
func (p *AppParams) HasQuery(name string) bool {
	_, ok := p.request.URL.Query()[name]

	return ok
}

// HasForm returns whether named param is exist for POST/PUT request body.
func (p *AppParams) HasForm(name string) bool {
	p.request.ParseMultipartForm(DefaultMaxMultiformBytes)

	_, ok := p.request.PostForm[name]

	return ok
}

func (p *AppParams) RawBody() ([]byte, error) {
	if !p.readed {
		p.mux.Lock()
		if !p.readed {
			p.rawBody, p.rawErr = ioutil.ReadAll(p.request.Body)

			// close the request.Body
			p.request.Body.Close()

			p.readed = true
		}
		p.mux.Unlock()
	}

	return p.rawBody, p.rawErr
}

// Get returns the first value for the named component of the request.
// NOTE: httprouter.Params takes precedence over URL query string values.
func (p *AppParams) Get(name string) string {
	value := p.params.ByName(name)

	// trye URL query string if value of route is empty
	if value == "" {
		value = p.request.URL.Query().Get(name)
	}

	return value
}

func (p *AppParams) GetInt(name string) (int, error) {
	value := p.params.ByName(name)
	if value == "" {
		value = p.request.URL.Query().Get(name)
	}

	return strconv.Atoi(value)
}

func (p *AppParams) GetInt8(name string) (int8, error) {
	value := p.params.ByName(name)
	if value == "" {
		value = p.request.URL.Query().Get(name)
	}

	result, err := strconv.ParseInt(value, 10, 8)
	return int8(result), err
}

func (p *AppParams) GetUint8(name string) (uint8, error) {
	value := p.params.ByName(name)
	if value == "" {
		value = p.request.URL.Query().Get(name)
	}

	result, err := strconv.ParseUint(value, 10, 8)
	return uint8(result), err
}

func (p *AppParams) GetInt16(name string) (int16, error) {
	value := p.params.ByName(name)
	if value == "" {
		value = p.request.URL.Query().Get(name)
	}

	result, err := strconv.ParseInt(value, 10, 16)
	return int16(result), err
}

func (p *AppParams) GetUint16(name string) (uint16, error) {
	value := p.params.ByName(name)
	if value == "" {
		value = p.request.URL.Query().Get(name)
	}

	result, err := strconv.ParseUint(value, 10, 16)
	return uint16(result), err
}

func (p *AppParams) GetUint32(name string) (uint32, error) {
	value := p.params.ByName(name)
	if value == "" {
		value = p.request.URL.Query().Get(name)
	}

	result, err := strconv.ParseUint(value, 10, 32)
	return uint32(result), err
}

func (p *AppParams) GetInt32(name string) (int32, error) {
	value := p.params.ByName(name)
	if value == "" {
		value = p.request.URL.Query().Get(name)
	}

	result, err := strconv.ParseInt(value, 10, 32)
	return int32(result), err
}

func (p *AppParams) GetInt64(name string) (int64, error) {
	value := p.params.ByName(name)
	if value == "" {
		value = p.request.URL.Query().Get(name)
	}

	return strconv.ParseInt(value, 10, 64)
}

func (p *AppParams) GetUint64(name string) (uint64, error) {
	value := p.params.ByName(name)
	if value == "" {
		value = p.request.URL.Query().Get(name)
	}

	return strconv.ParseUint(value, 10, 64)
}

func (p *AppParams) GetFloat(name string) (float64, error) {
	value := p.params.ByName(name)
	if value == "" {
		value = p.request.URL.Query().Get(name)
	}

	return strconv.ParseFloat(value, 64)
}

func (p *AppParams) GetBool(name string) (bool, error) {
	value := p.params.ByName(name)
	if value == "" {
		value = p.request.URL.Query().Get(name)
	}

	return strconv.ParseBool(value)
}

// Post returns the named comonent of the request by calling http.Request.FormValue()
func (p *AppParams) Post(name string) string {
	return p.request.FormValue(name)
}

// File retrieves multipart uploaded file of HTTP POST request
func (p *AppParams) File(name string) (multipart.File, *multipart.FileHeader, error) {
	return p.request.FormFile(name)
}

// Json unmarshals request body with json codec
func (p *AppParams) Json(v interface{}) error {
	data, err := p.RawBody()
	if err != nil {
		return err
	}

	return json.Unmarshal(data, v)
}

// Xml unmarshals request body with xml codec
func (p *AppParams) Xml(v interface{}) error {
	data, err := p.RawBody()
	if err != nil {
		return err
	}

	return xml.Unmarshal(data, v)
}

// Gob decode request body with gob codec
func (p *AppParams) Gob(v interface{}) error {
	data, err := p.RawBody()
	if err != nil {
		return err
	}

	return gob.NewDecoder(bytes.NewBuffer(data)).Decode(v)
}

// Bson unmarshals request body with bson codec
func (p *AppParams) Bson(v interface{}) error {
	data, err := p.RawBody()
	if err != nil {
		return err
	}

	return bson.Unmarshal(data, v)
}
<|endoftext|>


<commit_before>package goparse

import (
	"go/ast"
	"go/parser"
	"go/token"
	"reflect"
	"strings"
)

// StructDesc contains description of parsed struct
type StructDesc struct {
	Name  string
	Field []struct {
		Name string
		Type string
		Tags []string
	}
}

func getTypeName(t ast.Expr) string {
	switch e := t.(type) {
	case *ast.Ident:
		return e.Name
	case *ast.ArrayType:
		return "[]" + getTypeName(e.Elt)
	case *ast.StarExpr:
		return "*" + getTypeName(e.X)
	}
	return "unknown"
}

// GetFileStructs returns structs descriptions from parsed go file
func GetFileStructs(filename string, prefix string, tag string) ([]StructDesc, error) {
	result := make([]StructDesc, 0, 5)

	fset := token.NewFileSet()

	f, err := parser.ParseFile(fset, filename, nil, 0)
	if nil != err {
		return result, err
	}

	for i := range f.Decls {
		if g, ok := f.Decls[i].(*ast.GenDecl); ok {
			for _, s := range g.Specs {
				if ts, ok := s.(*ast.TypeSpec); ok {
					if "" == prefix || strings.HasPrefix(ts.Name.String(), prefix) {
						if tt, ok := ts.Type.(*ast.StructType); ok {
							newStruct := StructDesc{Name: ts.Name.String(), Field: make([]struct {
								Name string
								Type string
								Tags []string
							}, 0, len(tt.Fields.List))}
							for _, field := range tt.Fields.List {
								newField := struct {
									Name string
									Type string
									Tags []string
								}{}
								if len(field.Names) < 1 {
									continue
								}
								newField.Name = field.Names[0].Name
								newField.Type = getTypeName(field.Type)
								if nil != field.Tag {
									newField.Tags = strings.Split(reflect.StructTag(strings.Trim(field.Tag.Value, "`")).Get(tag), ",")
								}
								newStruct.Field = append(newStruct.Field, newField)
							}
							result = append(result, newStruct)
						}
					}
				}
			}
		}
	}

	return result, nil
}
<commit_msg>Return tags as map<commit_after>package goparse

import (
	"go/ast"
	"go/parser"
	"go/token"
	"reflect"
	"strings"
)

// StructDesc contains description of parsed struct
type StructDesc struct {
	Name  string
	Field []StructField
}

// StructField describes field itself
type StructField struct {
	Name string
	Type string
	Tags map[string]string
}

func getTypeName(t ast.Expr) string {
	switch e := t.(type) {
	case *ast.Ident:
		return e.Name
	case *ast.ArrayType:
		return "[]" + getTypeName(e.Elt)
	case *ast.StarExpr:
		return "*" + getTypeName(e.X)
	}
	return "unknown"
}

// GetFileStructs returns structs descriptions from parsed go file
func GetFileStructs(filename string, prefix string, tag string) ([]StructDesc, error) {
	result := make([]StructDesc, 0, 5)

	fset := token.NewFileSet()

	f, err := parser.ParseFile(fset, filename, nil, 0)
	if nil != err {
		return result, err
	}

	for i := range f.Decls {
		if g, ok := f.Decls[i].(*ast.GenDecl); ok {
			for _, s := range g.Specs {
				if ts, ok := s.(*ast.TypeSpec); ok {
					if "" == prefix || strings.HasPrefix(ts.Name.String(), prefix) {
						if tt, ok := ts.Type.(*ast.StructType); ok {
							newStruct := StructDesc{Name: ts.Name.String(), Field: make([]StructField, 0, len(tt.Fields.List))}
							for _, field := range tt.Fields.List {
								newField := StructField{}
								if len(field.Names) < 1 {
									continue
								}
								newField.Name = field.Names[0].Name
								newField.Type = getTypeName(field.Type)
								if nil != field.Tag {
									tags := strings.Split(reflect.StructTag(strings.Trim(field.Tag.Value, "`")).Get(tag), ",")
									newField.Tags = make(map[string]string, len(tags))
									for _, tag := range tags {
										ts := strings.SplitN(tag, "=", 2)
										if len(ts) == 1 {
											newField.Tags[ts[0]] = ""
										} else {
											newField.Tags[ts[0]] = ts[1]
										}
									}
								}
								newStruct.Field = append(newStruct.Field, newField)
							}
							result = append(result, newStruct)
						}
					}
				}
			}
		}
	}

	return result, nil
}
<|endoftext|>


<commit_before>package yamlpatch

import (
	"fmt"
	"strconv"
	"strings"

	yaml "gopkg.in/yaml.v2"
)

const (
	eRaw = iota
	eDoc
	eAry
)

const unknown string = "unknown"

type lazyNode struct {
	raw   *interface{}
	doc   partialDoc
	ary   partialArray
	which int
}

func newLazyNode(raw *interface{}) *lazyNode {
	return &lazyNode{raw: raw, which: eRaw}
}

func (n *lazyNode) MarshalYAML() (interface{}, error) {
	switch n.which {
	case eRaw:
		if n.raw == nil {
			panic(fmt.Sprintf("type is raw but raw is nil: %p", n))
		}
		return *n.raw, nil
	case eDoc:
		return n.doc, nil
	case eAry:
		return n.ary, nil
	default:
		return nil, fmt.Errorf("Unknown type")
	}
}

func (n *lazyNode) UnmarshalYAML(unmarshal func(interface{}) error) error {
	var data interface{}

	err := unmarshal(&data)
	if err != nil {
		return err
	}

	n.raw = &data
	n.which = eRaw
	return nil
}

func (n *lazyNode) intoDoc() (*partialDoc, error) {
	if n.which == eDoc {
		return &n.doc, nil
	}

	raw := *n.raw

	switch rt := raw.(type) {
	case map[interface{}]interface{}:
		doc := map[interface{}]*lazyNode{}

		for k := range rt {
			v := rt[k]
			doc[k] = newLazyNode(&v)
		}

		n.doc = doc
		n.which = eDoc
		return &n.doc, nil
	default:
		return nil, fmt.Errorf("don't know how to convert %T into doc", raw)
	}
}

func (n *lazyNode) intoAry() (*partialArray, error) {
	if n.which == eAry {
		return &n.ary, nil
	}

	raw := *n.raw

	switch rt := raw.(type) {
	case []interface{}:
		array := make(partialArray, len(rt))

		for i := range rt {
			array[i] = newLazyNode(&rt[i])
		}

		n.ary = array
		n.which = eAry
		return &n.ary, nil
	default:
		return nil, fmt.Errorf("don't know how to convert %T into ary", raw)
	}
}

type operation struct {
	Op       string       `yaml:"op,omitempty"`
	Path     string       `yaml:"path,omitempty"`
	From     string       `yaml:"from,omitempty"`
	RawValue *interface{} `yaml:"value,omitempty"`
}

func (o operation) value() *lazyNode {
	if o.RawValue == nil {
		panic(fmt.Sprintf("value is nil: %#v", o))
	}
	return newLazyNode(o.RawValue)
}

// Patch is an ordered collection of operations.
type Patch []operation

type container interface {
	get(key string) (*lazyNode, error)
	set(key string, val *lazyNode) error
	add(key string, val *lazyNode) error
	remove(key string) error
}

func isArray(iface interface{}) bool {
	_, ok := iface.([]interface{})
	return ok
}

func findObject(c *container, path string) (container, string) {
	doc := *c

	split := strings.Split(path, "/")

	if len(split) < 2 {
		return nil, ""
	}

	parts := split[1 : len(split)-1]

	key := split[len(split)-1]

	for _, part := range parts {
		next, err := doc.get(decodePatchKey(part))
		if next == nil || err != nil {
			return nil, ""
		}

		if isArray(*next.raw) {
			doc, err = next.intoAry()
			if err != nil {
				return nil, ""
			}
		} else {
			doc, err = next.intoDoc()
			if err != nil {
				return nil, ""
			}
		}
	}

	return doc, decodePatchKey(key)
}

type partialDoc map[interface{}]*lazyNode

func (d *partialDoc) set(key string, val *lazyNode) error {
	(*d)[key] = val
	return nil
}

func (d *partialDoc) add(key string, val *lazyNode) error {
	(*d)[key] = val
	return nil
}

func (d *partialDoc) get(key string) (*lazyNode, error) {
	return (*d)[key], nil
}

func (d *partialDoc) remove(key string) error {
	_, ok := (*d)[key]
	if !ok {
		return fmt.Errorf("Unable to remove nonexistent key: %s", key)
	}

	delete(*d, key)
	return nil
}

type partialArray []*lazyNode

func (d *partialArray) set(key string, val *lazyNode) error {
	idx, err := strconv.Atoi(key)
	if err != nil {
		return err
	}

	sz := len(*d)
	if idx+1 > sz {
		sz = idx + 1
	}

	ary := make([]*lazyNode, sz)

	cur := *d

	copy(ary, cur)

	if idx >= len(ary) {
		return fmt.Errorf("Unable to access invalid index: %d", idx)
	}

	ary[idx] = val

	*d = ary
	return nil
}

func (d *partialArray) add(key string, val *lazyNode) error {
	if key == "-" {
		*d = append(*d, val)
		return nil
	}

	idx, err := strconv.Atoi(key)
	if err != nil {
		return err
	}

	ary := make([]*lazyNode, len(*d)+1)

	cur := *d

	copy(ary[0:idx], cur[0:idx])
	ary[idx] = val
	copy(ary[idx+1:], cur[idx:])

	*d = ary
	return nil
}

func (d *partialArray) get(key string) (*lazyNode, error) {
	idx, err := strconv.Atoi(key)
	if err != nil {
		return nil, err
	}

	if idx >= len(*d) {
		return nil, fmt.Errorf("Unable to access invalid index: %d", idx)
	}

	return (*d)[idx], nil
}

func (d *partialArray) remove(key string) error {
	idx, err := strconv.Atoi(key)
	if err != nil {
		return err
	}

	cur := *d

	if idx >= len(cur) {
		return fmt.Errorf("Unable to remove invalid index: %d", idx)
	}

	ary := make([]*lazyNode, len(cur)-1)

	copy(ary[0:idx], cur[0:idx])
	copy(ary[idx:], cur[idx+1:])

	*d = ary
	return nil

}

func add(doc *container, op operation) error {
	con, key := findObject(doc, op.Path)

	if con == nil {
		return fmt.Errorf("yamlpatch add operation does not apply: doc is missing path: %s", op.Path)
	}

	return con.add(key, op.value())
}

func remove(doc *container, op operation) error {
	con, key := findObject(doc, op.Path)

	if con == nil {
		return fmt.Errorf("yamlpatch remove operation does not apply: doc is missing path: %s", op.Path)
	}

	return con.remove(key)
}

func replace(doc *container, op operation) error {
	con, key := findObject(doc, op.Path)

	if con == nil {
		return fmt.Errorf("yamlpatch replace operation does not apply: doc is missing path: %s", op.Path)
	}

	val, err := con.get(key)
	if val == nil || err != nil {
		return fmt.Errorf("yamlpatch replace operation does not apply: doc is missing key: %s", op.Path)
	}

	return con.set(key, op.value())
}

func move(doc *container, op operation) error {
	con, key := findObject(doc, op.From)
	if con == nil {
		return fmt.Errorf("yamlpatch move operation does not apply: doc is missing from path: %s", op.From)
	}

	val, err := con.get(key)
	if err != nil {
		return err
	}

	err = con.remove(key)
	if err != nil {
		return err
	}

	con, key = findObject(doc, op.Path)
	if con == nil {
		return fmt.Errorf("yamlpatch move operation does not apply: doc is missing destination path: %s", op.Path)
	}

	return con.set(key, val)
}

func copyOp(doc *container, op operation) error {
	con, key := findObject(doc, op.From)
	if con == nil {
		return fmt.Errorf("copy operation does not apply: doc is missing from path: %s", op.From)
	}

	val, err := con.get(key)
	if err != nil {
		return err
	}

	con, key = findObject(doc, op.Path)
	if con == nil {
		return fmt.Errorf("copy operation does not apply: doc is missing destination path: %s", op.Path)
	}

	return con.set(key, val)
}

// DecodePatch decodes the passed YAML document as if it were an RFC 6902 patch
func DecodePatch(bs []byte) (Patch, error) {
	var p Patch

	err := yaml.Unmarshal(bs, &p)
	if err != nil {
		return nil, err
	}

	return p, nil
}

// Apply returns a YAML document that has been mutated per the patch
func (p Patch) Apply(doc []byte) ([]byte, error) {
	var c container

	c = &partialDoc{}
	err := yaml.Unmarshal(doc, c)
	if err != nil {
		c = &partialArray{}
		err = yaml.Unmarshal(doc, c)
		if err != nil {
			return nil, fmt.Errorf("failed unmarshaling doc: %s\n\n%s", string(doc), err)
		}
	}

	for _, op := range p {
		switch op.Op {
		case "add":
			err = add(&c, op)
		case "remove":
			err = remove(&c, op)
		case "replace":
			err = replace(&c, op)
		case "move":
			err = move(&c, op)
		case "copy":
			err = copyOp(&c, op)
		default:
			err = fmt.Errorf("Unexpected op: %s", op.Op)
		}

		if err != nil {
			return nil, err
		}
	}

	return yaml.Marshal(c)
}

// From http://tools.ietf.org/html/rfc6901#section-4 :
//
// Evaluation of each reference token begins by decoding any escaped
// character sequence.  This is performed by first transforming any
// occurrence of the sequence '~1' to '/', and then transforming any
// occurrence of the sequence '~0' to '~'.

var (
	rfc6901Encoder = strings.NewReplacer("~", "~0", "/", "~1")
	rfc6901Decoder = strings.NewReplacer("~1", "/", "~0", "~")
)

func decodePatchKey(k string) string {
	return rfc6901Decoder.Replace(k)
}

func encodePatchKey(k string) string {
	return rfc6901Encoder.Replace(k)
}
<commit_msg>Remove unused const<commit_after>package yamlpatch

import (
	"fmt"
	"strconv"
	"strings"

	yaml "gopkg.in/yaml.v2"
)

const (
	eRaw = iota
	eDoc
	eAry
)

type lazyNode struct {
	raw   *interface{}
	doc   partialDoc
	ary   partialArray
	which int
}

func newLazyNode(raw *interface{}) *lazyNode {
	return &lazyNode{raw: raw, which: eRaw}
}

func (n *lazyNode) MarshalYAML() (interface{}, error) {
	switch n.which {
	case eRaw:
		if n.raw == nil {
			panic(fmt.Sprintf("type is raw but raw is nil: %p", n))
		}
		return *n.raw, nil
	case eDoc:
		return n.doc, nil
	case eAry:
		return n.ary, nil
	default:
		return nil, fmt.Errorf("Unknown type")
	}
}

func (n *lazyNode) UnmarshalYAML(unmarshal func(interface{}) error) error {
	var data interface{}

	err := unmarshal(&data)
	if err != nil {
		return err
	}

	n.raw = &data
	n.which = eRaw
	return nil
}

func (n *lazyNode) intoDoc() (*partialDoc, error) {
	if n.which == eDoc {
		return &n.doc, nil
	}

	raw := *n.raw

	switch rt := raw.(type) {
	case map[interface{}]interface{}:
		doc := map[interface{}]*lazyNode{}

		for k := range rt {
			v := rt[k]
			doc[k] = newLazyNode(&v)
		}

		n.doc = doc
		n.which = eDoc
		return &n.doc, nil
	default:
		return nil, fmt.Errorf("don't know how to convert %T into doc", raw)
	}
}

func (n *lazyNode) intoAry() (*partialArray, error) {
	if n.which == eAry {
		return &n.ary, nil
	}

	raw := *n.raw

	switch rt := raw.(type) {
	case []interface{}:
		array := make(partialArray, len(rt))

		for i := range rt {
			array[i] = newLazyNode(&rt[i])
		}

		n.ary = array
		n.which = eAry
		return &n.ary, nil
	default:
		return nil, fmt.Errorf("don't know how to convert %T into ary", raw)
	}
}

type operation struct {
	Op       string       `yaml:"op,omitempty"`
	Path     string       `yaml:"path,omitempty"`
	From     string       `yaml:"from,omitempty"`
	RawValue *interface{} `yaml:"value,omitempty"`
}

func (o operation) value() *lazyNode {
	if o.RawValue == nil {
		panic(fmt.Sprintf("value is nil: %#v", o))
	}
	return newLazyNode(o.RawValue)
}

// Patch is an ordered collection of operations.
type Patch []operation

type container interface {
	get(key string) (*lazyNode, error)
	set(key string, val *lazyNode) error
	add(key string, val *lazyNode) error
	remove(key string) error
}

func isArray(iface interface{}) bool {
	_, ok := iface.([]interface{})
	return ok
}

func findObject(c *container, path string) (container, string) {
	doc := *c

	split := strings.Split(path, "/")

	if len(split) < 2 {
		return nil, ""
	}

	parts := split[1 : len(split)-1]

	key := split[len(split)-1]

	for _, part := range parts {
		next, err := doc.get(decodePatchKey(part))
		if next == nil || err != nil {
			return nil, ""
		}

		if isArray(*next.raw) {
			doc, err = next.intoAry()
			if err != nil {
				return nil, ""
			}
		} else {
			doc, err = next.intoDoc()
			if err != nil {
				return nil, ""
			}
		}
	}

	return doc, decodePatchKey(key)
}

type partialDoc map[interface{}]*lazyNode

func (d *partialDoc) set(key string, val *lazyNode) error {
	(*d)[key] = val
	return nil
}

func (d *partialDoc) add(key string, val *lazyNode) error {
	(*d)[key] = val
	return nil
}

func (d *partialDoc) get(key string) (*lazyNode, error) {
	return (*d)[key], nil
}

func (d *partialDoc) remove(key string) error {
	_, ok := (*d)[key]
	if !ok {
		return fmt.Errorf("Unable to remove nonexistent key: %s", key)
	}

	delete(*d, key)
	return nil
}

type partialArray []*lazyNode

func (d *partialArray) set(key string, val *lazyNode) error {
	idx, err := strconv.Atoi(key)
	if err != nil {
		return err
	}

	sz := len(*d)
	if idx+1 > sz {
		sz = idx + 1
	}

	ary := make([]*lazyNode, sz)

	cur := *d

	copy(ary, cur)

	if idx >= len(ary) {
		return fmt.Errorf("Unable to access invalid index: %d", idx)
	}

	ary[idx] = val

	*d = ary
	return nil
}

func (d *partialArray) add(key string, val *lazyNode) error {
	if key == "-" {
		*d = append(*d, val)
		return nil
	}

	idx, err := strconv.Atoi(key)
	if err != nil {
		return err
	}

	ary := make([]*lazyNode, len(*d)+1)

	cur := *d

	copy(ary[0:idx], cur[0:idx])
	ary[idx] = val
	copy(ary[idx+1:], cur[idx:])

	*d = ary
	return nil
}

func (d *partialArray) get(key string) (*lazyNode, error) {
	idx, err := strconv.Atoi(key)
	if err != nil {
		return nil, err
	}

	if idx >= len(*d) {
		return nil, fmt.Errorf("Unable to access invalid index: %d", idx)
	}

	return (*d)[idx], nil
}

func (d *partialArray) remove(key string) error {
	idx, err := strconv.Atoi(key)
	if err != nil {
		return err
	}

	cur := *d

	if idx >= len(cur) {
		return fmt.Errorf("Unable to remove invalid index: %d", idx)
	}

	ary := make([]*lazyNode, len(cur)-1)

	copy(ary[0:idx], cur[0:idx])
	copy(ary[idx:], cur[idx+1:])

	*d = ary
	return nil

}

func add(doc *container, op operation) error {
	con, key := findObject(doc, op.Path)

	if con == nil {
		return fmt.Errorf("yamlpatch add operation does not apply: doc is missing path: %s", op.Path)
	}

	return con.add(key, op.value())
}

func remove(doc *container, op operation) error {
	con, key := findObject(doc, op.Path)

	if con == nil {
		return fmt.Errorf("yamlpatch remove operation does not apply: doc is missing path: %s", op.Path)
	}

	return con.remove(key)
}

func replace(doc *container, op operation) error {
	con, key := findObject(doc, op.Path)

	if con == nil {
		return fmt.Errorf("yamlpatch replace operation does not apply: doc is missing path: %s", op.Path)
	}

	val, err := con.get(key)
	if val == nil || err != nil {
		return fmt.Errorf("yamlpatch replace operation does not apply: doc is missing key: %s", op.Path)
	}

	return con.set(key, op.value())
}

func move(doc *container, op operation) error {
	con, key := findObject(doc, op.From)
	if con == nil {
		return fmt.Errorf("yamlpatch move operation does not apply: doc is missing from path: %s", op.From)
	}

	val, err := con.get(key)
	if err != nil {
		return err
	}

	err = con.remove(key)
	if err != nil {
		return err
	}

	con, key = findObject(doc, op.Path)
	if con == nil {
		return fmt.Errorf("yamlpatch move operation does not apply: doc is missing destination path: %s", op.Path)
	}

	return con.set(key, val)
}

func copyOp(doc *container, op operation) error {
	con, key := findObject(doc, op.From)
	if con == nil {
		return fmt.Errorf("copy operation does not apply: doc is missing from path: %s", op.From)
	}

	val, err := con.get(key)
	if err != nil {
		return err
	}

	con, key = findObject(doc, op.Path)
	if con == nil {
		return fmt.Errorf("copy operation does not apply: doc is missing destination path: %s", op.Path)
	}

	return con.set(key, val)
}

// DecodePatch decodes the passed YAML document as if it were an RFC 6902 patch
func DecodePatch(bs []byte) (Patch, error) {
	var p Patch

	err := yaml.Unmarshal(bs, &p)
	if err != nil {
		return nil, err
	}

	return p, nil
}

// Apply returns a YAML document that has been mutated per the patch
func (p Patch) Apply(doc []byte) ([]byte, error) {
	var c container

	c = &partialDoc{}
	err := yaml.Unmarshal(doc, c)
	if err != nil {
		c = &partialArray{}
		err = yaml.Unmarshal(doc, c)
		if err != nil {
			return nil, fmt.Errorf("failed unmarshaling doc: %s\n\n%s", string(doc), err)
		}
	}

	for _, op := range p {
		switch op.Op {
		case "add":
			err = add(&c, op)
		case "remove":
			err = remove(&c, op)
		case "replace":
			err = replace(&c, op)
		case "move":
			err = move(&c, op)
		case "copy":
			err = copyOp(&c, op)
		default:
			err = fmt.Errorf("Unexpected op: %s", op.Op)
		}

		if err != nil {
			return nil, err
		}
	}

	return yaml.Marshal(c)
}

// From http://tools.ietf.org/html/rfc6901#section-4 :
//
// Evaluation of each reference token begins by decoding any escaped
// character sequence.  This is performed by first transforming any
// occurrence of the sequence '~1' to '/', and then transforming any
// occurrence of the sequence '~0' to '~'.

var (
	rfc6901Encoder = strings.NewReplacer("~", "~0", "/", "~1")
	rfc6901Decoder = strings.NewReplacer("~1", "/", "~0", "~")
)

func decodePatchKey(k string) string {
	return rfc6901Decoder.Replace(k)
}

func encodePatchKey(k string) string {
	return rfc6901Encoder.Replace(k)
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package create

import (
	"github.com/spf13/cobra"

	createcluster "k8s.io/kubeadm/kinder/cmd/kinder/create/cluster"
)

// NewCommand returns a new cobra.Command for cluster creation
func NewCommand() *cobra.Command {
	cmd := &cobra.Command{
		Args:  cobra.NoArgs,
		Use:   "create",
		Short: "Creates one of [cluster, worker-node, control-plane-node]",
		Long:  "Creates one of local Kubernetes cluster (cluster), or nodes in a local kubernetes cluster (worker-node, control-plane-node)",
	}
	cmd.AddCommand(createcluster.NewCommand())
	return cmd
}
<commit_msg>updated confusing command help text<commit_after>/*
Copyright 2019 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package create

import (
	"github.com/spf13/cobra"

	createcluster "k8s.io/kubeadm/kinder/cmd/kinder/create/cluster"
)

// NewCommand returns a new cobra.Command for cluster creation
func NewCommand() *cobra.Command {
	cmd := &cobra.Command{
		Args:  cobra.NoArgs,
		Use:   "create",
		Short: "Creates one of [cluster]",
		Long:  "Creates one of local Kubernetes cluster (cluster)",
	}
	cmd.AddCommand(createcluster.NewCommand())
	return cmd
}
<|endoftext|>


<commit_before>package ploop

import "os/exec"
import "sync"

// #cgo CFLAGS: -D_GNU_SOURCE
// #cgo LDFLAGS: -lploop -lxml2 -lrt
// #include <ploop/libploop.h>
import "C"

// Possible SetVerboseLevel arguments
const (
	NoConsole  = C.PLOOP_LOG_NOCONSOLE
	NoStdout   = C.PLOOP_LOG_NOSTDOUT
	Timestamps = C.PLOOP_LOG_TIMESTAMPS
)

// SetVerboseLevel sets a level of verbosity when logging to stdout/stderr
func SetVerboseLevel(v int) {
	C.ploop_set_verbose_level(C.int(v))
}

// SetLogFile enables logging to a file and sets log file name
func SetLogFile(file string) error {
	cfile := C.CString(file)
	defer cfree(cfile)

	ret := C.ploop_set_log_file(cfile)

	return mkerr(ret)
}

// SetLogLevel sets a level of verbosity when logging to a file
func SetLogLevel(v int) {
	C.ploop_set_log_level(C.int(v))
}

// Ploop is a type containing DiskDescriptor.xml opened by the library
type Ploop struct {
	d *C.struct_ploop_disk_images_data
}

var once sync.Once

// load ploop modules
func load_kmod() {
	// try to load ploop modules
	modules := []string{"ploop", "pfmt_ploop1", "pfmt_raw", "pio_direct", "pio_nfs", "pio_kaio"}
	for _, m := range modules {
		exec.Command("modprobe", m).Run()
	}
}

// Open opens a ploop DiskDescriptor.xml, most ploop operations require it
func Open(file string) (Ploop, error) {
	var d Ploop

	once.Do(load_kmod)

	cfile := C.CString(file)
	defer cfree(cfile)

	ret := C.ploop_open_dd(&d.d, cfile)

	return d, mkerr(ret)
}

// Close closes a ploop disk descriptor when it is no longer needed
func (d Ploop) Close() {
	C.ploop_close_dd(d.d)
}

type ImageMode int

// Possible values for ImageMode
const (
	Expanded     ImageMode = C.PLOOP_EXPANDED_MODE
	Preallocated ImageMode = C.PLOOP_EXPANDED_PREALLOCATED_MODE
	Raw          ImageMode = C.PLOOP_RAW_MODE
)

// CreateParam is a set of parameters for a newly created ploop
type CreateParam struct {
	Size uint64 // image size, in kilobytes (FS size is about 10% smaller)
	Mode ImageMode
	File string // path to and a file name for base delta image
}

// Create creates a ploop image and its DiskDescriptor.xml
func Create(p *CreateParam) error {
	var a C.struct_ploop_create_param

	once.Do(load_kmod)

	// default image file name
	if p.File == "" {
		p.File = "root.hdd"
	}

	a.size = convertSize(p.Size)
	a.mode = C.int(p.Mode)
	a.image = C.CString(p.File)
	defer cfree(a.image)
	a.fstype = C.CString("ext4")
	defer cfree(a.fstype)

	ret := C.ploop_create_image(&a)
	return mkerr(ret)
}

// MountParam is a set of parameters to pass to Mount()
type MountParam struct {
	UUID     string // snapshot uuid (empty for top delta)
	Target   string // mount point (empty if no mount is needed)
	Flags    int    // bit mount flags such as MS_NOATIME
	Data     string // auxiliary mount options
	Readonly bool   // mount read-only
	Fsck     bool   // do fsck before mounting inner FS
	Quota    bool   // enable quota for inner FS
}

// Mount creates a ploop device and (optionally) mounts it
func (d Ploop) Mount(p *MountParam) (string, error) {
	var a C.struct_ploop_mount_param
	var device string

	if p.UUID != "" {
		a.guid = C.CString(p.UUID)
		defer cfree(a.guid)
	}
	if p.Target != "" {
		a.target = C.CString(p.Target)
		defer cfree(a.target)
	}

	// mount_data should not be NULL
	a.mount_data = C.CString(p.Data)
	defer cfree(a.mount_data)

	a.flags = C.int(p.Flags)
	a.ro = bool2cint(p.Readonly)
	a.fsck = bool2cint(p.Fsck)
	a.quota = bool2cint(p.Quota)

	ret := C.ploop_mount_image(d.d, &a)
	if ret == 0 {
		device = C.GoString(&a.device[0])
		// TODO? fsck_code = C.GoString(a.fsck_rc)
	}
	return device, mkerr(ret)
}

// Umount unmounts the ploop filesystem and dismantles the device
func (d Ploop) Umount() error {
	ret := C.ploop_umount_image(d.d)

	return mkerr(ret)
}

// Resize changes the ploop size. Online resize is recommended.
func (d Ploop) Resize(size uint64, offline bool) error {
	var p C.struct_ploop_resize_param

	p.size = convertSize(size)
	p.offline_resize = bool2cint(offline)

	ret := C.ploop_resize_image(d.d, &p)
	return mkerr(ret)
}

// Snapshot creates a ploop snapshot, returning its uuid
func (d Ploop) Snapshot() (string, error) {
	var p C.struct_ploop_snapshot_param
	var uuid, err = UUID()
	if err != nil {
		return "", err
	}
	p.guid = C.CString(uuid)
	defer cfree(p.guid)

	ret := C.ploop_create_snapshot(d.d, &p)
	if ret == 0 {
		uuid = C.GoString(p.guid)
	}

	return uuid, mkerr(ret)
}

// SwitchSnapshot switches to a specified snapshot,
// creates a new empty delta on top of it, and makes it a top one
// (i.e. the one new data will be written to).
// Old top delta (i.e. data modified since the last snapshot) is lost.
func (d Ploop) SwitchSnapshot(uuid string) error {
	var p C.struct_ploop_snapshot_switch_param

	p.guid = C.CString(uuid)
	defer cfree(p.guid)

	ret := C.ploop_switch_snapshot_ex(d.d, &p)

	return mkerr(ret)
}

// Possible values for SwitchSnapshotExtended flags argument
type SwitchFlag uint

const (
	// SkipDestroy, if set, modifies the behavior of
	// SwitchSnapshotExtended to not delete the old top delta, but
	// make it a snapshot and return its uuid. Without this flag,
	// old top delta (i.e. data modified since the last snapshot)
	// is lost.
	SkipDestroy SwitchFlag = C.PLOOP_SNAP_SKIP_TOPDELTA_DESTROY
	// SkipCreate flag, if set, modifies the behavior of
	// SwitchSnapshotExtended to not create a new top delta,
	// but rather transform the specified snapshot itself to be
	// the new top delta), so all new changes will be written
	// right to it. Snapshot UUID is lost in this case.
	SkipCreate SwitchFlag = C.PLOOP_SNAP_SKIP_TOPDELTA_CREATE
)

// SwitchSnapshotExtended is same as SwitchSnapshot but with additional
// flags modifying its behavior. Please see individual flags description.
// Returns uuid of what was the old top delta if SkipDestroy flag is set.
func (d Ploop) SwitchSnapshotExtended(uuid string, flags SwitchFlag) (string, error) {
	var p C.struct_ploop_snapshot_switch_param
	old_uuid := ""

	p.guid = C.CString(uuid)
	defer cfree(p.guid)

	p.flags = C.int(flags)

	if flags&SkipDestroy != 0 {
		old_uuid, err := UUID()
		if err != nil {
			return "", err
		}
		p.guid_old = C.CString(old_uuid)
		defer cfree(p.guid_old)
	}

	ret := C.ploop_switch_snapshot_ex(d.d, &p)

	return old_uuid, mkerr(ret)
}

// DeleteSnapshot deletes a snapshot (merging it down if necessary)
func (d Ploop) DeleteSnapshot(uuid string) error {
	cuuid := C.CString(uuid)
	defer cfree(cuuid)

	ret := C.ploop_delete_snapshot(d.d, cuuid)

	return mkerr(ret)
}

type ReplaceFlag int

// Possible values for ReplaceParam.flags
const (
	// KeepName renames the new file to old file name after replace;
	// note that if this option is used the old file is removed.
	KeepName ReplaceFlag = C.PLOOP_REPLACE_KEEP_NAME
)

// ReplaceParam is a set of parameters to Replace()
type ReplaceParam struct {
	File string // new image file name
	// Image to be replaced is specified by either
	// uuid, current file name, or level,
	// in the above order of preference.
	UUID    string
	CurFile string
	Level   int
	Flags   ReplaceFlag
}

// Replace replaces a ploop image to a different (but identical) one
func (d Ploop) Replace(p *ReplaceParam) error {
	var a C.struct_ploop_replace_param

	a.file = C.CString(p.File)
	defer cfree(a.file)

	if p.UUID != "" {
		a.guid = C.CString(p.UUID)
		defer cfree(a.guid)
	} else if p.CurFile != "" {
		a.cur_file = C.CString(p.CurFile)
		defer cfree(a.cur_file)
	} else {
		a.level = C.int(p.Level)
	}

	a.flags = C.int(p.Flags)

	ret := C.ploop_replace_image(d.d, &a)

	return mkerr(ret)
}

// IsMounted returns true if ploop is mounted
func (d Ploop) IsMounted() (bool, error) {
	ret := C.ploop_is_mounted(d.d)
	if ret == 0 {
		return false, nil
	} else if ret == 1 {
		return true, nil
	} else {
		// error, but no code, make our own
		return false, mkerr(E_SYS)
	}
}

// FSInfoData holds information about ploop inner file system
type FSInfoData struct {
	Blocksize   uint64
	Blocks      uint64
	Blocks_free uint64
	Inodes      uint64
	Inodes_free uint64
}

// FSInfo gets info of ploop's inner file system
func FSInfo(file string) (FSInfoData, error) {
	var cinfo C.struct_ploop_info
	var info FSInfoData
	cfile := C.CString(file)
	defer cfree(cfile)

	once.Do(load_kmod)

	ret := C.ploop_get_info_by_descr(cfile, &cinfo)
	if ret == 0 {
		info.Blocksize = uint64(cinfo.fs_bsize)
		info.Blocks = uint64(cinfo.fs_blocks)
		info.Blocks_free = uint64(cinfo.fs_bfree)
		info.Inodes = uint64(cinfo.fs_inodes)
		info.Inodes_free = uint64(cinfo.fs_ifree)
	}

	return info, mkerr(ret)
}

// ImageInfoData holds information about ploop image
type ImageInfoData struct {
	Blocks    uint64
	Blocksize uint32
	Version   int
}

// ImageInfo gets information about a ploop image
func (d Ploop) ImageInfo() (ImageInfoData, error) {
	var cinfo C.struct_ploop_spec
	var info ImageInfoData

	ret := C.ploop_get_spec(d.d, &cinfo)
	if ret == 0 {
		info.Blocks = uint64(cinfo.size)
		info.Blocksize = uint32(cinfo.blocksize)
		info.Version = int(cinfo.fmt_version)
	}

	return info, mkerr(ret)
}

// TopDeltaFile returns file name of top delta
func (d Ploop) TopDeltaFile() (string, error) {
	const len = 4096 // PATH_MAX
	var out [len]C.char

	ret := C.ploop_get_top_delta_fname(d.d, &out[0], len)
	if ret != 0 {
		// error, but no code, make our own
		return "", mkerr(E_SYS)
	}

	file := C.GoString(&out[0])
	return file, nil
}

// UUID generates a ploop UUID
func UUID() (string, error) {
	var cuuid [39]C.char

	ret := C.ploop_uuid_generate(&cuuid[0], 39)
	if ret != 0 {
		return "", mkerr(ret)
	}

	uuid := C.GoString(&cuuid[0])
	return uuid, nil
}
<commit_msg>CreateParam: add CLog param<commit_after>package ploop

import "os/exec"
import "sync"

// #cgo CFLAGS: -D_GNU_SOURCE
// #cgo LDFLAGS: -lploop -lxml2 -lrt
// #include <ploop/libploop.h>
import "C"

// Possible SetVerboseLevel arguments
const (
	NoConsole  = C.PLOOP_LOG_NOCONSOLE
	NoStdout   = C.PLOOP_LOG_NOSTDOUT
	Timestamps = C.PLOOP_LOG_TIMESTAMPS
)

// SetVerboseLevel sets a level of verbosity when logging to stdout/stderr
func SetVerboseLevel(v int) {
	C.ploop_set_verbose_level(C.int(v))
}

// SetLogFile enables logging to a file and sets log file name
func SetLogFile(file string) error {
	cfile := C.CString(file)
	defer cfree(cfile)

	ret := C.ploop_set_log_file(cfile)

	return mkerr(ret)
}

// SetLogLevel sets a level of verbosity when logging to a file
func SetLogLevel(v int) {
	C.ploop_set_log_level(C.int(v))
}

// Ploop is a type containing DiskDescriptor.xml opened by the library
type Ploop struct {
	d *C.struct_ploop_disk_images_data
}

var once sync.Once

// load ploop modules
func load_kmod() {
	// try to load ploop modules
	modules := []string{"ploop", "pfmt_ploop1", "pfmt_raw", "pio_direct", "pio_nfs", "pio_kaio"}
	for _, m := range modules {
		exec.Command("modprobe", m).Run()
	}
}

// Open opens a ploop DiskDescriptor.xml, most ploop operations require it
func Open(file string) (Ploop, error) {
	var d Ploop

	once.Do(load_kmod)

	cfile := C.CString(file)
	defer cfree(cfile)

	ret := C.ploop_open_dd(&d.d, cfile)

	return d, mkerr(ret)
}

// Close closes a ploop disk descriptor when it is no longer needed
func (d Ploop) Close() {
	C.ploop_close_dd(d.d)
}

type ImageMode int

// Possible values for ImageMode
const (
	Expanded     ImageMode = C.PLOOP_EXPANDED_MODE
	Preallocated ImageMode = C.PLOOP_EXPANDED_PREALLOCATED_MODE
	Raw          ImageMode = C.PLOOP_RAW_MODE
)

// CreateParam is a set of parameters for a newly created ploop
type CreateParam struct {
	Size uint64 // image size, in kilobytes (FS size is about 10% smaller)
	Mode ImageMode
	File string // path to and a file name for base delta image
	CLog uint   // cluster block size log (6 to 15, default 11)
}

// Create creates a ploop image and its DiskDescriptor.xml
func Create(p *CreateParam) error {
	var a C.struct_ploop_create_param

	once.Do(load_kmod)

	// default image file name
	if p.File == "" {
		p.File = "root.hdd"
	}

	a.size = convertSize(p.Size)
	a.mode = C.int(p.Mode)
	if p.CLog != 0 {
		// ploop cluster block size, in 512-byte sectors
		// default is 1M cluster block size (CLog=11)
		// 2^11 = 2048 sectors, 2048*512 = 1M
		a.blocksize = 1 << p.CLog
	}
	a.image = C.CString(p.File)
	defer cfree(a.image)
	a.fstype = C.CString("ext4")
	defer cfree(a.fstype)

	ret := C.ploop_create_image(&a)
	return mkerr(ret)
}

// MountParam is a set of parameters to pass to Mount()
type MountParam struct {
	UUID     string // snapshot uuid (empty for top delta)
	Target   string // mount point (empty if no mount is needed)
	Flags    int    // bit mount flags such as MS_NOATIME
	Data     string // auxiliary mount options
	Readonly bool   // mount read-only
	Fsck     bool   // do fsck before mounting inner FS
	Quota    bool   // enable quota for inner FS
}

// Mount creates a ploop device and (optionally) mounts it
func (d Ploop) Mount(p *MountParam) (string, error) {
	var a C.struct_ploop_mount_param
	var device string

	if p.UUID != "" {
		a.guid = C.CString(p.UUID)
		defer cfree(a.guid)
	}
	if p.Target != "" {
		a.target = C.CString(p.Target)
		defer cfree(a.target)
	}

	// mount_data should not be NULL
	a.mount_data = C.CString(p.Data)
	defer cfree(a.mount_data)

	a.flags = C.int(p.Flags)
	a.ro = bool2cint(p.Readonly)
	a.fsck = bool2cint(p.Fsck)
	a.quota = bool2cint(p.Quota)

	ret := C.ploop_mount_image(d.d, &a)
	if ret == 0 {
		device = C.GoString(&a.device[0])
		// TODO? fsck_code = C.GoString(a.fsck_rc)
	}
	return device, mkerr(ret)
}

// Umount unmounts the ploop filesystem and dismantles the device
func (d Ploop) Umount() error {
	ret := C.ploop_umount_image(d.d)

	return mkerr(ret)
}

// Resize changes the ploop size. Online resize is recommended.
func (d Ploop) Resize(size uint64, offline bool) error {
	var p C.struct_ploop_resize_param

	p.size = convertSize(size)
	p.offline_resize = bool2cint(offline)

	ret := C.ploop_resize_image(d.d, &p)
	return mkerr(ret)
}

// Snapshot creates a ploop snapshot, returning its uuid
func (d Ploop) Snapshot() (string, error) {
	var p C.struct_ploop_snapshot_param
	var uuid, err = UUID()
	if err != nil {
		return "", err
	}
	p.guid = C.CString(uuid)
	defer cfree(p.guid)

	ret := C.ploop_create_snapshot(d.d, &p)
	if ret == 0 {
		uuid = C.GoString(p.guid)
	}

	return uuid, mkerr(ret)
}

// SwitchSnapshot switches to a specified snapshot,
// creates a new empty delta on top of it, and makes it a top one
// (i.e. the one new data will be written to).
// Old top delta (i.e. data modified since the last snapshot) is lost.
func (d Ploop) SwitchSnapshot(uuid string) error {
	var p C.struct_ploop_snapshot_switch_param

	p.guid = C.CString(uuid)
	defer cfree(p.guid)

	ret := C.ploop_switch_snapshot_ex(d.d, &p)

	return mkerr(ret)
}

// Possible values for SwitchSnapshotExtended flags argument
type SwitchFlag uint

const (
	// SkipDestroy, if set, modifies the behavior of
	// SwitchSnapshotExtended to not delete the old top delta, but
	// make it a snapshot and return its uuid. Without this flag,
	// old top delta (i.e. data modified since the last snapshot)
	// is lost.
	SkipDestroy SwitchFlag = C.PLOOP_SNAP_SKIP_TOPDELTA_DESTROY
	// SkipCreate flag, if set, modifies the behavior of
	// SwitchSnapshotExtended to not create a new top delta,
	// but rather transform the specified snapshot itself to be
	// the new top delta), so all new changes will be written
	// right to it. Snapshot UUID is lost in this case.
	SkipCreate SwitchFlag = C.PLOOP_SNAP_SKIP_TOPDELTA_CREATE
)

// SwitchSnapshotExtended is same as SwitchSnapshot but with additional
// flags modifying its behavior. Please see individual flags description.
// Returns uuid of what was the old top delta if SkipDestroy flag is set.
func (d Ploop) SwitchSnapshotExtended(uuid string, flags SwitchFlag) (string, error) {
	var p C.struct_ploop_snapshot_switch_param
	old_uuid := ""

	p.guid = C.CString(uuid)
	defer cfree(p.guid)

	p.flags = C.int(flags)

	if flags&SkipDestroy != 0 {
		old_uuid, err := UUID()
		if err != nil {
			return "", err
		}
		p.guid_old = C.CString(old_uuid)
		defer cfree(p.guid_old)
	}

	ret := C.ploop_switch_snapshot_ex(d.d, &p)

	return old_uuid, mkerr(ret)
}

// DeleteSnapshot deletes a snapshot (merging it down if necessary)
func (d Ploop) DeleteSnapshot(uuid string) error {
	cuuid := C.CString(uuid)
	defer cfree(cuuid)

	ret := C.ploop_delete_snapshot(d.d, cuuid)

	return mkerr(ret)
}

type ReplaceFlag int

// Possible values for ReplaceParam.flags
const (
	// KeepName renames the new file to old file name after replace;
	// note that if this option is used the old file is removed.
	KeepName ReplaceFlag = C.PLOOP_REPLACE_KEEP_NAME
)

// ReplaceParam is a set of parameters to Replace()
type ReplaceParam struct {
	File string // new image file name
	// Image to be replaced is specified by either
	// uuid, current file name, or level,
	// in the above order of preference.
	UUID    string
	CurFile string
	Level   int
	Flags   ReplaceFlag
}

// Replace replaces a ploop image to a different (but identical) one
func (d Ploop) Replace(p *ReplaceParam) error {
	var a C.struct_ploop_replace_param

	a.file = C.CString(p.File)
	defer cfree(a.file)

	if p.UUID != "" {
		a.guid = C.CString(p.UUID)
		defer cfree(a.guid)
	} else if p.CurFile != "" {
		a.cur_file = C.CString(p.CurFile)
		defer cfree(a.cur_file)
	} else {
		a.level = C.int(p.Level)
	}

	a.flags = C.int(p.Flags)

	ret := C.ploop_replace_image(d.d, &a)

	return mkerr(ret)
}

// IsMounted returns true if ploop is mounted
func (d Ploop) IsMounted() (bool, error) {
	ret := C.ploop_is_mounted(d.d)
	if ret == 0 {
		return false, nil
	} else if ret == 1 {
		return true, nil
	} else {
		// error, but no code, make our own
		return false, mkerr(E_SYS)
	}
}

// FSInfoData holds information about ploop inner file system
type FSInfoData struct {
	Blocksize   uint64
	Blocks      uint64
	Blocks_free uint64
	Inodes      uint64
	Inodes_free uint64
}

// FSInfo gets info of ploop's inner file system
func FSInfo(file string) (FSInfoData, error) {
	var cinfo C.struct_ploop_info
	var info FSInfoData
	cfile := C.CString(file)
	defer cfree(cfile)

	once.Do(load_kmod)

	ret := C.ploop_get_info_by_descr(cfile, &cinfo)
	if ret == 0 {
		info.Blocksize = uint64(cinfo.fs_bsize)
		info.Blocks = uint64(cinfo.fs_blocks)
		info.Blocks_free = uint64(cinfo.fs_bfree)
		info.Inodes = uint64(cinfo.fs_inodes)
		info.Inodes_free = uint64(cinfo.fs_ifree)
	}

	return info, mkerr(ret)
}

// ImageInfoData holds information about ploop image
type ImageInfoData struct {
	Blocks    uint64
	Blocksize uint32
	Version   int
}

// ImageInfo gets information about a ploop image
func (d Ploop) ImageInfo() (ImageInfoData, error) {
	var cinfo C.struct_ploop_spec
	var info ImageInfoData

	ret := C.ploop_get_spec(d.d, &cinfo)
	if ret == 0 {
		info.Blocks = uint64(cinfo.size)
		info.Blocksize = uint32(cinfo.blocksize)
		info.Version = int(cinfo.fmt_version)
	}

	return info, mkerr(ret)
}

// TopDeltaFile returns file name of top delta
func (d Ploop) TopDeltaFile() (string, error) {
	const len = 4096 // PATH_MAX
	var out [len]C.char

	ret := C.ploop_get_top_delta_fname(d.d, &out[0], len)
	if ret != 0 {
		// error, but no code, make our own
		return "", mkerr(E_SYS)
	}

	file := C.GoString(&out[0])
	return file, nil
}

// UUID generates a ploop UUID
func UUID() (string, error) {
	var cuuid [39]C.char

	ret := C.ploop_uuid_generate(&cuuid[0], 39)
	if ret != 0 {
		return "", mkerr(ret)
	}

	uuid := C.GoString(&cuuid[0])
	return uuid, nil
}
<|endoftext|>


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

package grpc

import (
	"fmt"
	"net"
	"testing"
	"time"

	"google.golang.org/grpc/resolver"
)

func TestParseTarget(t *testing.T) {
	for _, test := range []resolver.Target{
		{"dns", "", "google.com"},
		{"dns", "a.server.com", "google.com"},
		{"dns", "a.server.com", "google.com/?a=b"},
		{"passthrough", "", "/unix/socket/address"},
	} {
		str := test.Scheme + "://" + test.Authority + "/" + test.Endpoint
		got := parseTarget(str)
		if got != test {
			t.Errorf("parseTarget(%q) = %+v, want %+v", str, got, test)
		}
	}
}

func TestParseTargetString(t *testing.T) {
	for _, test := range []struct {
		targetStr string
		want      resolver.Target
	}{
		{"", resolver.Target{"", "", ""}},
		{":///", resolver.Target{"", "", ""}},
		{"a:///", resolver.Target{"a", "", ""}},
		{"://a/", resolver.Target{"", "a", ""}},
		{":///a", resolver.Target{"", "", "a"}},
		{"a://b/", resolver.Target{"a", "b", ""}},
		{"a:///b", resolver.Target{"a", "", "b"}},
		{"://a/b", resolver.Target{"", "a", "b"}},
		{"a://b/c", resolver.Target{"a", "b", "c"}},
		{"dns:///google.com", resolver.Target{"dns", "", "google.com"}},
		{"dns://a.server.com/google.com", resolver.Target{"dns", "a.server.com", "google.com"}},
		{"dns://a.server.com/google.com/?a=b", resolver.Target{"dns", "a.server.com", "google.com/?a=b"}},

		{"/", resolver.Target{"", "", "/"}},
		{"google.com", resolver.Target{"", "", "google.com"}},
		{"google.com/?a=b", resolver.Target{"", "", "google.com/?a=b"}},
		{"/unix/socket/address", resolver.Target{"", "", "/unix/socket/address"}},

		// If we can only parse part of the target.
		{"://", resolver.Target{"", "", "://"}},
		{"unix://domain", resolver.Target{"", "", "unix://domain"}},
		{"a:b", resolver.Target{"", "", "a:b"}},
		{"a/b", resolver.Target{"", "", "a/b"}},
		{"a:/b", resolver.Target{"", "", "a:/b"}},
		{"a//b", resolver.Target{"", "", "a//b"}},
		{"a://b", resolver.Target{"", "", "a://b"}},
	} {
		got := parseTarget(test.targetStr)
		if got != test.want {
			t.Errorf("parseTarget(%q) = %+v, want %+v", test.targetStr, got, test.want)
		}
	}
}

// The target string with unknown scheme should be kept unchanged and passed to
// the dialer.
func TestDialParseTargetUnknownScheme(t *testing.T) {
	for _, test := range []struct {
		targetStr string
		want      string
	}{
		{"/unix/socket/address", "/unix/socket/address"},

		// Special test for "unix:///".
		{"unix:///unix/socket/address", "unix:///unix/socket/address"},

		// For known scheme.
		{"passthrough://a.server.com/google.com", "google.com"},
	} {
		dialStrCh := make(chan string, 1)
		cc, err := Dial(test.targetStr, WithInsecure(), WithDialer(func(t string, _ time.Duration) (net.Conn, error) {
			dialStrCh <- t
			return nil, fmt.Errorf("test dialer, always error")
		}))
		if err != nil {
			t.Fatalf("Failed to create ClientConn: %v", err)
		}
		got := <-dialStrCh
		cc.Close()
		if got != test.want {
			t.Errorf("Dial(%q), dialer got %q, want %q", test.targetStr, got, test.want)
		}
	}
}
<commit_msg>go vet: fix composite literal uses unkeyed fields (#2005)<commit_after>/*
 *
 * Copyright 2017 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package grpc

import (
	"fmt"
	"net"
	"testing"
	"time"

	"google.golang.org/grpc/resolver"
)

func TestParseTarget(t *testing.T) {
	for _, test := range []resolver.Target{
		{Scheme: "dns", Authority: "", Endpoint: "google.com"},
		{Scheme: "dns", Authority: "a.server.com", Endpoint: "google.com"},
		{Scheme: "dns", Authority: "a.server.com", Endpoint: "google.com/?a=b"},
		{Scheme: "passthrough", Authority: "", Endpoint: "/unix/socket/address"},
	} {
		str := test.Scheme + "://" + test.Authority + "/" + test.Endpoint
		got := parseTarget(str)
		if got != test {
			t.Errorf("parseTarget(%q) = %+v, want %+v", str, got, test)
		}
	}
}

func TestParseTargetString(t *testing.T) {
	for _, test := range []struct {
		targetStr string
		want      resolver.Target
	}{
		{targetStr: "", want: resolver.Target{Scheme: "", Authority: "", Endpoint: ""}},
		{targetStr: ":///", want: resolver.Target{Scheme: "", Authority: "", Endpoint: ""}},
		{targetStr: "a:///", want: resolver.Target{Scheme: "a", Authority: "", Endpoint: ""}},
		{targetStr: "://a/", want: resolver.Target{Scheme: "", Authority: "a", Endpoint: ""}},
		{targetStr: ":///a", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "a"}},
		{targetStr: "a://b/", want: resolver.Target{Scheme: "a", Authority: "b", Endpoint: ""}},
		{targetStr: "a:///b", want: resolver.Target{Scheme: "a", Authority: "", Endpoint: "b"}},
		{targetStr: "://a/b", want: resolver.Target{Scheme: "", Authority: "a", Endpoint: "b"}},
		{targetStr: "a://b/c", want: resolver.Target{Scheme: "a", Authority: "b", Endpoint: "c"}},
		{targetStr: "dns:///google.com", want: resolver.Target{Scheme: "dns", Authority: "", Endpoint: "google.com"}},
		{targetStr: "dns://a.server.com/google.com", want: resolver.Target{Scheme: "dns", Authority: "a.server.com", Endpoint: "google.com"}},
		{targetStr: "dns://a.server.com/google.com/?a=b", want: resolver.Target{Scheme: "dns", Authority: "a.server.com", Endpoint: "google.com/?a=b"}},

		{targetStr: "/", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "/"}},
		{targetStr: "google.com", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "google.com"}},
		{targetStr: "google.com/?a=b", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "google.com/?a=b"}},
		{targetStr: "/unix/socket/address", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "/unix/socket/address"}},

		// If we can only parse part of the target.
		{targetStr: "://", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "://"}},
		{targetStr: "unix://domain", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "unix://domain"}},
		{targetStr: "a:b", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "a:b"}},
		{targetStr: "a/b", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "a/b"}},
		{targetStr: "a:/b", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "a:/b"}},
		{targetStr: "a//b", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "a//b"}},
		{targetStr: "a://b", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "a://b"}},
	} {
		got := parseTarget(test.targetStr)
		if got != test.want {
			t.Errorf("parseTarget(%q) = %+v, want %+v", test.targetStr, got, test.want)
		}
	}
}

// The target string with unknown scheme should be kept unchanged and passed to
// the dialer.
func TestDialParseTargetUnknownScheme(t *testing.T) {
	for _, test := range []struct {
		targetStr string
		want      string
	}{
		{"/unix/socket/address", "/unix/socket/address"},

		// Special test for "unix:///".
		{"unix:///unix/socket/address", "unix:///unix/socket/address"},

		// For known scheme.
		{"passthrough://a.server.com/google.com", "google.com"},
	} {
		dialStrCh := make(chan string, 1)
		cc, err := Dial(test.targetStr, WithInsecure(), WithDialer(func(t string, _ time.Duration) (net.Conn, error) {
			dialStrCh <- t
			return nil, fmt.Errorf("test dialer, always error")
		}))
		if err != nil {
			t.Fatalf("Failed to create ClientConn: %v", err)
		}
		got := <-dialStrCh
		cc.Close()
		if got != test.want {
			t.Errorf("Dial(%q), dialer got %q, want %q", test.targetStr, got, test.want)
		}
	}
}
<|endoftext|>


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

package caddyesi

import (
	"bufio"
	"io"
	"net"
	"net/http"
	"net/http/httptest"
	"testing"

	"io/ioutil"

	"github.com/SchumacherFM/caddyesi/esitag"
	"github.com/stretchr/testify/assert"
)

// Check if types have the interfaces implemented.
var _ http.CloseNotifier = (*injectingFancyWriter)(nil)
var _ http.Flusher = (*injectingFancyWriter)(nil)
var _ http.Hijacker = (*injectingFancyWriter)(nil)
var _ http.Pusher = (*injectingFancyWriter)(nil)
var _ io.ReaderFrom = (*injectingFancyWriter)(nil)
var _ http.Flusher = (*injectingFlushWriter)(nil)

// Check if types have the interfaces implemented.
var _ http.CloseNotifier = (*responseMock)(nil)
var _ http.Flusher = (*responseMock)(nil)
var _ http.Hijacker = (*responseMock)(nil)
var _ http.Pusher = (*responseMock)(nil)
var _ io.ReaderFrom = (*responseMock)(nil)
var _ http.Flusher = (*responseMock)(nil)

var _ io.Reader = (*simpleReader)(nil)

type responseMock struct {
	http.ResponseWriter
}

func newResponseMock() http.ResponseWriter {
	return &responseMock{
		ResponseWriter: httptest.NewRecorder(),
	}
}

func (f *responseMock) CloseNotify() <-chan bool {
	return nil
}
func (f *responseMock) Flush() {}
func (f *responseMock) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	return nil, nil, nil
}
func (f *responseMock) Push(target string, opts *http.PushOptions) error {
	return nil
}

// ReadFrom writes r into the underlying buffer
func (f *responseMock) ReadFrom(r io.Reader) (int64, error) {
	return 0, nil
}

func TestResponseWrapInjector(t *testing.T) {

	t.Run("WriteHeader with additional Content-Length (Idempotence)", func(t *testing.T) {
		dtChan := make(chan esitag.DataTag, 1)
		dtChan <- esitag.DataTag{End: 5, Start: 1} // Final calculation 0-5-1 = -4
		close(dtChan)

		rec := httptest.NewRecorder()
		rwi := responseWrapInjector(dtChan, rec)
		rwi.Header().Set("Content-LENGTH", "300")

		for i := 0; i < 3; i++ {
			// Test for being idempotent
			rwi.WriteHeader(http.StatusMultipleChoices)
			assert.Exactly(t, http.StatusMultipleChoices, rec.Code, "Expecting http.StatusMultipleChoices")
			assert.Exactly(t, "296", rec.Header().Get("Content-Length"), "Expecting Content-Length value")
		}
	})

	t.Run("Get injecting Flush Writer", func(t *testing.T) {
		dtChan := make(chan esitag.DataTag, 1)
		dtChan <- esitag.DataTag{}
		close(dtChan)

		rwi := responseWrapInjector(dtChan, httptest.NewRecorder())
		_, ok := rwi.(*injectingFlushWriter)
		assert.True(t, ok, "Expecting a injectingFlushWriter type")
	})

	t.Run("Get injecting Fancy Writer", func(t *testing.T) {
		dtChan := make(chan esitag.DataTag, 1)
		dtChan <- esitag.DataTag{}
		close(dtChan)

		rwi := responseWrapInjector(dtChan, newResponseMock())
		_, ok := rwi.(*injectingFancyWriter)
		assert.True(t, ok, "Expecting a injectingFancyWriter type")
	})

	t.Run("Dot not run injector on binary data", func(t *testing.T) {
		dtChan := make(chan esitag.DataTag, 1)
		dtChan <- esitag.DataTag{End: 5, Start: 1} // Final calculation 0-5-1 = -4
		close(dtChan)

		rec := httptest.NewRecorder()
		rwi := responseWrapInjector(dtChan, rec)
		png := []byte("\x89\x50\x4E\x47\x0D\x0A\x1A\x0A")
		if _, err := rwi.Write(png); err != nil {
			t.Fatal(err)
		}
		if _, err := rwi.Write(png); err != nil {
			t.Fatal(err)
		}
		assert.Exactly(t, append(png, png...), rec.Body.Bytes())
	})

	t.Run("Run injector once on text data", func(t *testing.T) {
		dtChan := make(chan esitag.DataTag, 1)
		dtChan <- esitag.DataTag{Data: []byte(`Hello XML`), Start: 12, End: 16}
		close(dtChan)

		rec := httptest.NewRecorder()
		rwi := responseWrapInjector(dtChan, rec)
		html := []byte(`<HtMl><bOdY>blah blah blah</body></html>`)
		if _, err := rwi.Write(html); err != nil {
			t.Fatal(err)
		}
		assert.Exactly(t, `<HtMl><bOdY>Hello XML blah blah</body></html>`, rec.Body.String())
	})

	t.Run("Run injector twice on text data", func(t *testing.T) {
		dtChan := make(chan esitag.DataTag, 1)
		dtChan <- esitag.DataTag{Data: []byte(`<Hello><world status="sinking"></world></Hello>`), Start: 13, End: 34}
		close(dtChan)

		rec := httptest.NewRecorder()
		rwi := responseWrapInjector(dtChan, rec)
		html1 := []byte(`<HtMl><bOdY> <esi:include src=""/>|`)
		html2 := []byte(`<data>Text and much more content.</data></body></html>`)
		if _, err := rwi.Write(html1); err != nil {
			t.Fatal(err)
		}
		if _, err := rwi.Write(html2); err != nil {
			t.Fatal(err)
		}

		assert.Exactly(t,
			" <Hello><world status=\"sinking\"></world></Hello>|<data>Text and much more content.</data>",
			rec.Body.String())
	})

	t.Run("xyzRun injector on large file with multiple different sized writes", func(t *testing.T) {
		// Changing the page09.html content, you have also to adjust the DataTag ...
		dtChan := make(chan esitag.DataTag, 1)
		dtChan <- esitag.DataTag{Start: 25297, End: 25450, Data: []byte("

Key	Value
\n
Session	session_
\n
Next Session Integer	5
\n
RequestURI	/page_blog_post.html
\n
Headers	User-Agent: curl/7.47.1
\n
\n
Time	Sun, 05 Mar 2017 20:15:14 +0100
\n
\n\n\n")}
		close(dtChan)

		html, err := ioutil.ReadFile("testdata/page09.html")
		if err != nil {
			t.Fatal(err)
		}

		rec := httptest.NewRecorder()
		rwi := responseWrapInjector(dtChan, rec)

		// First we write 32768 and then the rest 42291-32768=9523
		from := 0
		to := 32768

		n, err := rwi.Write(html[from:to])
		if err != nil {
			t.Fatal(err)
		}

		from = to
		to = 42701
		n2, err := rwi.Write(html[from:to])
		if err != nil {
			t.Fatal(err)
		}

		//if err := ioutil.WriteFile("testdata/page09_out.html", rec.Body.Bytes(), 0644); err != nil {
		//	t.Fatal(err)
		//}
		//assert.Exactly(t, 43111, rec.Body.Len())
		//assert.Exactly(t, n+n2, rec.Body.Len()) // extra data 410
	})
}
<commit_msg>caddyesi: TestResponseWrapInjector remove unused variables. WIP fix.<commit_after>// Copyright 2016-2017, Cyrill @ Schumacher.fm and the CaddyESI Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

package caddyesi

import (
	"bufio"
	"io"
	"net"
	"net/http"
	"net/http/httptest"
	"testing"

	"io/ioutil"

	"github.com/SchumacherFM/caddyesi/esitag"
	"github.com/stretchr/testify/assert"
)

// Check if types have the interfaces implemented.
var _ http.CloseNotifier = (*injectingFancyWriter)(nil)
var _ http.Flusher = (*injectingFancyWriter)(nil)
var _ http.Hijacker = (*injectingFancyWriter)(nil)
var _ http.Pusher = (*injectingFancyWriter)(nil)
var _ io.ReaderFrom = (*injectingFancyWriter)(nil)
var _ http.Flusher = (*injectingFlushWriter)(nil)

// Check if types have the interfaces implemented.
var _ http.CloseNotifier = (*responseMock)(nil)
var _ http.Flusher = (*responseMock)(nil)
var _ http.Hijacker = (*responseMock)(nil)
var _ http.Pusher = (*responseMock)(nil)
var _ io.ReaderFrom = (*responseMock)(nil)
var _ http.Flusher = (*responseMock)(nil)

var _ io.Reader = (*simpleReader)(nil)

type responseMock struct {
	http.ResponseWriter
}

func newResponseMock() http.ResponseWriter {
	return &responseMock{
		ResponseWriter: httptest.NewRecorder(),
	}
}

func (f *responseMock) CloseNotify() <-chan bool {
	return nil
}
func (f *responseMock) Flush() {}
func (f *responseMock) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	return nil, nil, nil
}
func (f *responseMock) Push(target string, opts *http.PushOptions) error {
	return nil
}

// ReadFrom writes r into the underlying buffer
func (f *responseMock) ReadFrom(r io.Reader) (int64, error) {
	return 0, nil
}

func TestResponseWrapInjector(t *testing.T) {

	t.Run("WriteHeader with additional Content-Length (Idempotence)", func(t *testing.T) {
		dtChan := make(chan esitag.DataTag, 1)
		dtChan <- esitag.DataTag{End: 5, Start: 1} // Final calculation 0-5-1 = -4
		close(dtChan)

		rec := httptest.NewRecorder()
		rwi := responseWrapInjector(dtChan, rec)
		rwi.Header().Set("Content-LENGTH", "300")

		for i := 0; i < 3; i++ {
			// Test for being idempotent
			rwi.WriteHeader(http.StatusMultipleChoices)
			assert.Exactly(t, http.StatusMultipleChoices, rec.Code, "Expecting http.StatusMultipleChoices")
			assert.Exactly(t, "296", rec.Header().Get("Content-Length"), "Expecting Content-Length value")
		}
	})

	t.Run("Get injecting Flush Writer", func(t *testing.T) {
		dtChan := make(chan esitag.DataTag, 1)
		dtChan <- esitag.DataTag{}
		close(dtChan)

		rwi := responseWrapInjector(dtChan, httptest.NewRecorder())
		_, ok := rwi.(*injectingFlushWriter)
		assert.True(t, ok, "Expecting a injectingFlushWriter type")
	})

	t.Run("Get injecting Fancy Writer", func(t *testing.T) {
		dtChan := make(chan esitag.DataTag, 1)
		dtChan <- esitag.DataTag{}
		close(dtChan)

		rwi := responseWrapInjector(dtChan, newResponseMock())
		_, ok := rwi.(*injectingFancyWriter)
		assert.True(t, ok, "Expecting a injectingFancyWriter type")
	})

	t.Run("Dot not run injector on binary data", func(t *testing.T) {
		dtChan := make(chan esitag.DataTag, 1)
		dtChan <- esitag.DataTag{End: 5, Start: 1} // Final calculation 0-5-1 = -4
		close(dtChan)

		rec := httptest.NewRecorder()
		rwi := responseWrapInjector(dtChan, rec)
		png := []byte("\x89\x50\x4E\x47\x0D\x0A\x1A\x0A")
		if _, err := rwi.Write(png); err != nil {
			t.Fatal(err)
		}
		if _, err := rwi.Write(png); err != nil {
			t.Fatal(err)
		}
		assert.Exactly(t, append(png, png...), rec.Body.Bytes())
	})

	t.Run("Run injector once on text data", func(t *testing.T) {
		dtChan := make(chan esitag.DataTag, 1)
		dtChan <- esitag.DataTag{Data: []byte(`Hello XML`), Start: 12, End: 16}
		close(dtChan)

		rec := httptest.NewRecorder()
		rwi := responseWrapInjector(dtChan, rec)
		html := []byte(`<HtMl><bOdY>blah blah blah</body></html>`)
		if _, err := rwi.Write(html); err != nil {
			t.Fatal(err)
		}
		assert.Exactly(t, `<HtMl><bOdY>Hello XML blah blah</body></html>`, rec.Body.String())
	})

	t.Run("Run injector twice on text data", func(t *testing.T) {
		dtChan := make(chan esitag.DataTag, 1)
		dtChan <- esitag.DataTag{Data: []byte(`<Hello><world status="sinking"></world></Hello>`), Start: 13, End: 34}
		close(dtChan)

		rec := httptest.NewRecorder()
		rwi := responseWrapInjector(dtChan, rec)
		html1 := []byte(`<HtMl><bOdY> <esi:include src=""/>|`)
		html2 := []byte(`<data>Text and much more content.</data></body></html>`)
		if _, err := rwi.Write(html1); err != nil {
			t.Fatal(err)
		}
		if _, err := rwi.Write(html2); err != nil {
			t.Fatal(err)
		}

		assert.Exactly(t,
			" <Hello><world status=\"sinking\"></world></Hello>|<data>Text and much more content.</data>",
			rec.Body.String())
	})

	t.Run("xyzRun injector on large file with multiple different sized writes", func(t *testing.T) {
		// Changing the page09.html content, you have also to adjust the DataTag ...
		dtChan := make(chan esitag.DataTag, 1)
		dtChan <- esitag.DataTag{Start: 25297, End: 25450, Data: []byte("

Key	Value
\n
Session	session_
\n
Next Session Integer	5
\n
RequestURI	/page_blog_post.html
\n
Headers	User-Agent: curl/7.47.1
\n
\n
Time	Sun, 05 Mar 2017 20:15:14 +0100
\n
\n\n\n")}
		close(dtChan)

		html, err := ioutil.ReadFile("testdata/page09.html")
		if err != nil {
			t.Fatal(err)
		}

		rec := httptest.NewRecorder()
		rwi := responseWrapInjector(dtChan, rec)

		// First we write 32768 and then the rest 42291-32768=9523
		from := 0
		to := 32768

		n, err := rwi.Write(html[from:to])
		if err != nil {
			t.Fatal(err)
		}

		from = to
		to = 42701
		n2, err := rwi.Write(html[from:to])
		if err != nil {
			t.Fatal(err)
		}

		_ = n + n2

		//if err := ioutil.WriteFile("testdata/page09_out.html", rec.Body.Bytes(), 0644); err != nil {
		//	t.Fatal(err)
		//}
		// bug: todo fix it: the bug is that 410 \x00 bytes gets written ...
		//assert.Exactly(t, 43111, rec.Body.Len())
		//assert.Exactly(t, n+n2, rec.Body.Len()) // extra data 410
	})
}
<|endoftext|>