<commit_before>// Copyright 2013 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.

// This file implements printing of types.

package types

import (
	"bytes"
	"fmt"
)

// If GcCompatibilityMode is set, printing of types is modified
// to match the representation of some types in the gc compiler:
//
//	- byte and rune lose their alias name and simply stand for
//	  uint8 and int32 respectively
//	- embedded interfaces get flattened (the embedding info is lost,
//	  and certain recursive interface types cannot be printed anymore)
//
// This makes it easier to compare packages computed with the type-
// checker vs packages imported from gc export data.
//
// Caution: This flag affects all uses of WriteType, globally.
// It is only provided for testing in conjunction with
// gc-generated data. It may be removed at any time.
var GcCompatibilityMode bool

// TypeString returns the string representation of typ.
// Named types are printed package-qualified if they
// do not belong to this package.
func TypeString(this *Package, typ Type) string {
	var buf bytes.Buffer
	WriteType(&buf, this, typ)
	return buf.String()
}

// WriteType writes the string representation of typ to buf.
// Named types are printed package-qualified if they
// do not belong to this package.
func WriteType(buf *bytes.Buffer, this *Package, typ Type) {
	writeType(buf, this, typ, make([]Type, 8))
}

func writeType(buf *bytes.Buffer, this *Package, typ Type, visited []Type) {
	// Theoretically, this is a quadratic lookup algorithm, but in
	// practice deeply nested composite types with unnamed component
	// types are uncommon. This code is likely more efficient than
	// using a map.
	for _, t := range visited {
		if t == typ {
			fmt.Fprintf(buf, "○%T", typ) // cycle to typ
			return
		}
	}
	visited = append(visited, typ)

	switch t := typ.(type) {
	case nil:
		buf.WriteString("<nil>")

	case *Basic:
		if t.kind == UnsafePointer {
			buf.WriteString("unsafe.")
		}
		if GcCompatibilityMode {
			// forget the alias names
			switch t.kind {
			case Byte:
				t = Typ[Uint8]
			case Rune:
				t = Typ[Int32]
			}
		}
		buf.WriteString(t.name)

	case *Array:
		fmt.Fprintf(buf, "[%d]", t.len)
		writeType(buf, this, t.elem, visited)

	case *Slice:
		buf.WriteString("[]")
		writeType(buf, this, t.elem, visited)

	case *Struct:
		buf.WriteString("struct{")
		for i, f := range t.fields {
			if i > 0 {
				buf.WriteString("; ")
			}
			if !f.anonymous {
				buf.WriteString(f.name)
				buf.WriteByte(' ')
			}
			writeType(buf, this, f.typ, visited)
			if tag := t.Tag(i); tag != "" {
				fmt.Fprintf(buf, " %q", tag)
			}
		}
		buf.WriteByte('}')

	case *Pointer:
		buf.WriteByte('*')
		writeType(buf, this, t.base, visited)

	case *Tuple:
		writeTuple(buf, this, t, false, visited)

	case *Signature:
		buf.WriteString("func")
		writeSignature(buf, this, t, visited)

	case *Interface:
		// We write the source-level methods and embedded types rather
		// than the actual method set since resolved method signatures
		// may have non-printable cycles if parameters have anonymous
		// interface types that (directly or indirectly) embed the
		// current interface. For instance, consider the result type
		// of m:
		//
		//     type T interface{
		//         m() interface{ T }
		//     }
		//
		buf.WriteString("interface{")
		if GcCompatibilityMode {
			// print flattened interface
			// (useful to compare against gc-generated interfaces)
			for i, m := range t.allMethods {
				if i > 0 {
					buf.WriteString("; ")
				}
				buf.WriteString(m.name)
				writeSignature(buf, this, m.typ.(*Signature), visited)
			}
		} else {
			// print explicit interface methods and embedded types
			for i, m := range t.methods {
				if i > 0 {
					buf.WriteString("; ")
				}
				buf.WriteString(m.name)
				writeSignature(buf, this, m.typ.(*Signature), visited)
			}
			for i, typ := range t.embeddeds {
				if i > 0 || len(t.methods) > 0 {
					buf.WriteString("; ")
				}
				writeType(buf, this, typ, visited)
			}
		}
		buf.WriteByte('}')

	case *Map:
		buf.WriteString("map[")
		writeType(buf, this, t.key, visited)
		buf.WriteByte(']')
		writeType(buf, this, t.elem, visited)

	case *Chan:
		var s string
		var parens bool
		switch t.dir {
		case SendRecv:
			s = "chan "
			// chan (<-chan T) requires parentheses
			if c, _ := t.elem.(*Chan); c != nil && c.dir == RecvOnly {
				parens = true
			}
		case SendOnly:
			s = "chan<- "
		case RecvOnly:
			s = "<-chan "
		default:
			panic("unreachable")
		}
		buf.WriteString(s)
		if parens {
			buf.WriteByte('(')
		}
		writeType(buf, this, t.elem, visited)
		if parens {
			buf.WriteByte(')')
		}

	case *Named:
		s := "<Named w/o object>"
		if obj := t.obj; obj != nil {
			if obj.pkg != nil {
				if obj.pkg != this {
					buf.WriteString(obj.pkg.path)
					buf.WriteByte('.')
				}
				// TODO(gri): function-local named types should be displayed
				// differently from named types at package level to avoid
				// ambiguity.
			}
			s = t.obj.name
		}
		buf.WriteString(s)

	default:
		// For externally defined implementations of Type.
		buf.WriteString(t.String())
	}
}

func writeTuple(buf *bytes.Buffer, this *Package, tup *Tuple, variadic bool, visited []Type) {
	buf.WriteByte('(')
	if tup != nil {
		for i, v := range tup.vars {
			if i > 0 {
				buf.WriteString(", ")
			}
			if v.name != "" {
				buf.WriteString(v.name)
				buf.WriteByte(' ')
			}
			typ := v.typ
			if variadic && i == len(tup.vars)-1 {
				buf.WriteString("...")
				typ = typ.(*Slice).elem
			}
			writeType(buf, this, typ, visited)
		}
	}
	buf.WriteByte(')')
}

// WriteSignature writes the representation of the signature sig to buf,
// without a leading "func" keyword.
// Named types are printed package-qualified if they
// do not belong to this package.
func WriteSignature(buf *bytes.Buffer, this *Package, sig *Signature) {
	writeSignature(buf, this, sig, make([]Type, 8))
}

func writeSignature(buf *bytes.Buffer, this *Package, sig *Signature, visited []Type) {
	writeTuple(buf, this, sig.params, sig.variadic, visited)

	n := sig.results.Len()
	if n == 0 {
		// no result
		return
	}

	buf.WriteByte(' ')
	if n == 1 && sig.results.vars[0].name == "" {
		// single unnamed result
		writeType(buf, this, sig.results.vars[0].typ, visited)
		return
	}

	// multiple or named result(s)
	writeTuple(buf, this, sig.results, false, visited)
}
<commit_msg>go.tools/go/types: minor internal cleanup<commit_after>// Copyright 2013 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.

// This file implements printing of types.

package types

import (
	"bytes"
	"fmt"
)

// If GcCompatibilityMode is set, printing of types is modified
// to match the representation of some types in the gc compiler:
//
//	- byte and rune lose their alias name and simply stand for
//	  uint8 and int32 respectively
//	- embedded interfaces get flattened (the embedding info is lost,
//	  and certain recursive interface types cannot be printed anymore)
//
// This makes it easier to compare packages computed with the type-
// checker vs packages imported from gc export data.
//
// Caution: This flag affects all uses of WriteType, globally.
// It is only provided for testing in conjunction with
// gc-generated data. It may be removed at any time.
var GcCompatibilityMode bool

// TypeString returns the string representation of typ.
// Named types are printed package-qualified if they
// do not belong to this package.
func TypeString(this *Package, typ Type) string {
	var buf bytes.Buffer
	WriteType(&buf, this, typ)
	return buf.String()
}

// WriteType writes the string representation of typ to buf.
// Named types are printed package-qualified if they
// do not belong to this package.
func WriteType(buf *bytes.Buffer, this *Package, typ Type) {
	writeType(buf, this, typ, make([]Type, 8))
}

func writeType(buf *bytes.Buffer, this *Package, typ Type, visited []Type) {
	// Theoretically, this is a quadratic lookup algorithm, but in
	// practice deeply nested composite types with unnamed component
	// types are uncommon. This code is likely more efficient than
	// using a map.
	for _, t := range visited {
		if t == typ {
			fmt.Fprintf(buf, "○%T", typ) // cycle to typ
			return
		}
	}
	visited = append(visited, typ)

	switch t := typ.(type) {
	case nil:
		buf.WriteString("<nil>")

	case *Basic:
		if t.kind == UnsafePointer {
			buf.WriteString("unsafe.")
		}
		if GcCompatibilityMode {
			// forget the alias names
			switch t.kind {
			case Byte:
				t = Typ[Uint8]
			case Rune:
				t = Typ[Int32]
			}
		}
		buf.WriteString(t.name)

	case *Array:
		fmt.Fprintf(buf, "[%d]", t.len)
		writeType(buf, this, t.elem, visited)

	case *Slice:
		buf.WriteString("[]")
		writeType(buf, this, t.elem, visited)

	case *Struct:
		buf.WriteString("struct{")
		for i, f := range t.fields {
			if i > 0 {
				buf.WriteString("; ")
			}
			if !f.anonymous {
				buf.WriteString(f.name)
				buf.WriteByte(' ')
			}
			writeType(buf, this, f.typ, visited)
			if tag := t.Tag(i); tag != "" {
				fmt.Fprintf(buf, " %q", tag)
			}
		}
		buf.WriteByte('}')

	case *Pointer:
		buf.WriteByte('*')
		writeType(buf, this, t.base, visited)

	case *Tuple:
		writeTuple(buf, this, t, false, visited)

	case *Signature:
		buf.WriteString("func")
		writeSignature(buf, this, t, visited)

	case *Interface:
		// We write the source-level methods and embedded types rather
		// than the actual method set since resolved method signatures
		// may have non-printable cycles if parameters have anonymous
		// interface types that (directly or indirectly) embed the
		// current interface. For instance, consider the result type
		// of m:
		//
		//     type T interface{
		//         m() interface{ T }
		//     }
		//
		buf.WriteString("interface{")
		if GcCompatibilityMode {
			// print flattened interface
			// (useful to compare against gc-generated interfaces)
			for i, m := range t.allMethods {
				if i > 0 {
					buf.WriteString("; ")
				}
				buf.WriteString(m.name)
				writeSignature(buf, this, m.typ.(*Signature), visited)
			}
		} else {
			// print explicit interface methods and embedded types
			for i, m := range t.methods {
				if i > 0 {
					buf.WriteString("; ")
				}
				buf.WriteString(m.name)
				writeSignature(buf, this, m.typ.(*Signature), visited)
			}
			for i, typ := range t.embeddeds {
				if i > 0 || len(t.methods) > 0 {
					buf.WriteString("; ")
				}
				writeType(buf, this, typ, visited)
			}
		}
		buf.WriteByte('}')

	case *Map:
		buf.WriteString("map[")
		writeType(buf, this, t.key, visited)
		buf.WriteByte(']')
		writeType(buf, this, t.elem, visited)

	case *Chan:
		var s string
		var parens bool
		switch t.dir {
		case SendRecv:
			s = "chan "
			// chan (<-chan T) requires parentheses
			if c, _ := t.elem.(*Chan); c != nil && c.dir == RecvOnly {
				parens = true
			}
		case SendOnly:
			s = "chan<- "
		case RecvOnly:
			s = "<-chan "
		default:
			panic("unreachable")
		}
		buf.WriteString(s)
		if parens {
			buf.WriteByte('(')
		}
		writeType(buf, this, t.elem, visited)
		if parens {
			buf.WriteByte(')')
		}

	case *Named:
		s := "<Named w/o object>"
		if obj := t.obj; obj != nil {
			if pkg := obj.pkg; pkg != nil && pkg != this {
				buf.WriteString(pkg.path)
				buf.WriteByte('.')
			}
			// TODO(gri): function-local named types should be displayed
			// differently from named types at package level to avoid
			// ambiguity.
			s = obj.name
		}
		buf.WriteString(s)

	default:
		// For externally defined implementations of Type.
		buf.WriteString(t.String())
	}
}

func writeTuple(buf *bytes.Buffer, this *Package, tup *Tuple, variadic bool, visited []Type) {
	buf.WriteByte('(')
	if tup != nil {
		for i, v := range tup.vars {
			if i > 0 {
				buf.WriteString(", ")
			}
			if v.name != "" {
				buf.WriteString(v.name)
				buf.WriteByte(' ')
			}
			typ := v.typ
			if variadic && i == len(tup.vars)-1 {
				buf.WriteString("...")
				typ = typ.(*Slice).elem
			}
			writeType(buf, this, typ, visited)
		}
	}
	buf.WriteByte(')')
}

// WriteSignature writes the representation of the signature sig to buf,
// without a leading "func" keyword.
// Named types are printed package-qualified if they
// do not belong to this package.
func WriteSignature(buf *bytes.Buffer, this *Package, sig *Signature) {
	writeSignature(buf, this, sig, make([]Type, 8))
}

func writeSignature(buf *bytes.Buffer, this *Package, sig *Signature, visited []Type) {
	writeTuple(buf, this, sig.params, sig.variadic, visited)

	n := sig.results.Len()
	if n == 0 {
		// no result
		return
	}

	buf.WriteByte(' ')
	if n == 1 && sig.results.vars[0].name == "" {
		// single unnamed result
		writeType(buf, this, sig.results.vars[0].typ, visited)
		return
	}

	// multiple or named result(s)
	writeTuple(buf, this, sig.results, false, visited)
}
<|endoftext|>


<commit_before>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

// This is a utility which binds to libkb to get the correct version
// for printing out or generating compiled resources for the windows
// executlable.

// +build windows

package main

import (
	"flag"
	"fmt"
	"log"
	"os"

	"github.com/josephspurrier/goversioninfo"
	"github.com/keybase/client/go/libkb"
)

// Create the syso file
func main() {

	outPtr := flag.String("o", "rsrc_windows.syso", "resource output pathname")
	printverPtr := flag.Bool("v", false, "print version to console (no .syso output)")
	iconPtr := flag.String("i", "../../../keybase/public/images/favicon.ico", "icon pathname")

	flag.Parse()

	var fv goversioninfo.FileVersion

	if int, err := fmt.Sscanf(libkb.Version, "%d.%d.%d", &fv.Major, &fv.Minor, &fv.Patch); int != 3 || err != nil {
		log.Printf("Error parsing version %v", err)
		os.Exit(3)
	}
	if int, err := fmt.Sscanf(libkb.Build, "%d", &fv.Build); int != 1 || err != nil {
		log.Printf("Error parsing build %v", err)
		os.Exit(3)
	}

	if *printverPtr {
		fmt.Printf("%d.%d.%d.%d", fv.Major, fv.Minor, fv.Patch, fv.Build)
		//		fmt.Printf("%s", libkb.VersionString())
		return
	}

	// Create a new container
	vi := &goversioninfo.VersionInfo{
		FixedFileInfo: goversioninfo.FixedFileInfo{
			FileVersion:    fv,
			ProductVersion: fv,
			FileFlagsMask:  "3f",
			FileFlags:      "00",
			FileOS:         "040004",
			FileType:       "01",
			FileSubType:    "00",
		},
		StringFileInfo: goversioninfo.StringFileInfo{
			CompanyName:      "Keybase, Inc.",
			FileDescription:  "Keybase utility",
			InternalName:     "Keybase",
			LegalCopyright:   "Copyright (c) 2015, Keybase",
			OriginalFilename: "keybase.exe",
			ProductName:      "Keybase",
			ProductVersion:   libkb.VersionString(),
		},
		VarFileInfo: goversioninfo.VarFileInfo{
			Translation: goversioninfo.Translation{
				LangID:    0x409, // english
				CharsetID: 0x4B0, // unicode
			},
		},
	}

	// Fill the structures with config data
	vi.Build()

	// Write the data to a buffer
	vi.Walk()

	// Optionally, embed an icon by path
	// If the icon has multiple sizes, all of the sizes will be embedded
	vi.IconPath = *iconPtr

	// Create the file
	if err := vi.WriteSyso(*outPtr); err != nil {
		log.Printf("Error writing %s: %v", *outPtr, err)
		os.Exit(3)
	}
}
<commit_msg>Reflect change of libkb.Build type<commit_after>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

// This is a utility which binds to libkb to get the correct version
// for printing out or generating compiled resources for the windows
// executlable.

// +build windows

package main

import (
	"flag"
	"fmt"
	"log"
	"os"

	"github.com/josephspurrier/goversioninfo"
	"github.com/keybase/client/go/libkb"
)

// Create the syso file
func main() {

	outPtr := flag.String("o", "rsrc_windows.syso", "resource output pathname")
	printverPtr := flag.Bool("v", false, "print version to console (no .syso output)")
	iconPtr := flag.String("i", "../../../keybase/public/images/favicon.ico", "icon pathname")

	flag.Parse()

	var fv goversioninfo.FileVersion

	if int, err := fmt.Sscanf(libkb.Version, "%d.%d.%d", &fv.Major, &fv.Minor, &fv.Patch); int != 3 || err != nil {
		log.Printf("Error parsing version %v", err)
		os.Exit(3)
	}
	fv.Build = libkb.Build

	if *printverPtr {
		fmt.Printf("%d.%d.%d.%d", fv.Major, fv.Minor, fv.Patch, fv.Build)
		//		fmt.Printf("%s", libkb.VersionString())
		return
	}

	// Create a new container
	vi := &goversioninfo.VersionInfo{
		FixedFileInfo: goversioninfo.FixedFileInfo{
			FileVersion:    fv,
			ProductVersion: fv,
			FileFlagsMask:  "3f",
			FileFlags:      "00",
			FileOS:         "040004",
			FileType:       "01",
			FileSubType:    "00",
		},
		StringFileInfo: goversioninfo.StringFileInfo{
			CompanyName:      "Keybase, Inc.",
			FileDescription:  "Keybase utility",
			InternalName:     "Keybase",
			LegalCopyright:   "Copyright (c) 2015, Keybase",
			OriginalFilename: "keybase.exe",
			ProductName:      "Keybase",
			ProductVersion:   libkb.VersionString(),
		},
		VarFileInfo: goversioninfo.VarFileInfo{
			Translation: goversioninfo.Translation{
				LangID:    0x409, // english
				CharsetID: 0x4B0, // unicode
			},
		},
	}

	// Fill the structures with config data
	vi.Build()

	// Write the data to a buffer
	vi.Walk()

	// Optionally, embed an icon by path
	// If the icon has multiple sizes, all of the sizes will be embedded
	vi.IconPath = *iconPtr

	// Create the file
	if err := vi.WriteSyso(*outPtr); err != nil {
		log.Printf("Error writing %s: %v", *outPtr, err)
		os.Exit(3)
	}
}
<|endoftext|>


<commit_before>package gofetcher

import (
	"github.com/cocaine/cocaine-framework-go/cocaine"
	"github.com/ugorji/go/codec"
	"net/http"
	"io/ioutil"
	"strconv"
	"time"
	"fmt"
	"errors"
	"bytes"
	"io"
	"reflect"
)

const (
	DefaultTimeout = 5000
	DefaultFollowRedirects = true
)

type Gofetcher struct {
	logger *cocaine.Logger
}

type Cookies map[string]string

type Request struct {
	method    string
	url       string
	body	io.Reader
	timeout	int64
	cookies	Cookies
	headers http.Header
	followRedirects bool
}

type responseAndError struct{
	res *http.Response
	err error
}

type Response struct {
	httpResponse	*http.Response
	body	[]byte
	header	http.Header
	runtime time.Duration
}

func NewGofetcher() *Gofetcher {
	logger, err := cocaine.NewLogger()
	if err != nil {
		logger.Err(fmt.Sprintf("Could not initialize logger due to error: %v", err))
		return nil
	}
	gofetcher := Gofetcher{logger}
	return &gofetcher
}

func noRedirect(_ *http.Request, via []*http.Request) error {
	if len(via) > 0 {
		return errors.New("stopped after first redirect")
	}
	return nil
}

func (gofetcher *Gofetcher) performRequest(request *Request) (*Response, error) {
	var (
		err 			error
		httpRequest    *http.Request
		httpResponse   *http.Response
		requestTimeout	time.Duration = time.Duration(request.timeout) * time.Millisecond
	)
	gofetcher.logger.Info(fmt.Sprintf("Requested url: %s, method: %s, timeout: %d, headers: %v",
		request.url, request.method, request.timeout, request.headers))
	httpClient := &http.Client{}
	if request.followRedirects == false {
		httpClient.CheckRedirect = noRedirect
	}
	httpRequest, err = http.NewRequest(request.method, request.url, request.body)
	if err != nil {
		return nil, err
	}
	for name, value := range request.cookies{
		httpRequest.AddCookie(&http.Cookie{Name:name, Value:value})
	}
	httpRequest.Header = request.headers
	resultChan := make(chan responseAndError)
	started := time.Now()
	go func (){
		res, err := httpClient.Do(httpRequest)
		if err != nil {
			resultChan <- responseAndError{nil, err}
		} else {
			resultChan <- responseAndError{res, err}
		}
	}()
	// http connection stay active after timeout exceeded, cause we can't close it using current client api.
	// Read more about timeouts: https://code.google.com/p/go/issues/detail?id=3362
	//
	select {
	case result := <- resultChan:
		httpResponse, err = result.res, result.err
	case <- time.After(requestTimeout):
		err = errors.New(fmt.Sprintf("Request timeout[%s] exceeded", requestTimeout.String()))
		go func(){
			// close httpResponse when it ready
			result := <- resultChan
			if result.res != nil {
				result.res.Body.Close()
			}
		}()
	}
	if err != nil {
		return nil, err
	}
	defer httpResponse.Body.Close()
	body, err := ioutil.ReadAll(httpResponse.Body)
	if err != nil {
		return nil, err
	}
	runtime := time.Since(started)
	response := &Response{httpResponse: httpResponse, body: body, header: httpResponse.Header, runtime: runtime}
	return response, nil

}

// Normal methods

func parseHeaders(rawHeaders map[string]interface {}) http.Header{
	headers := make(http.Header)
	for name, values := range rawHeaders {
		for _, value := range values.([]interface {}) {
			headers.Add(name, string(value.([]uint8))) // to transform in canonical form
		}
	}
	return headers
}

func parseCookies(rawCookie map[string]interface {}) Cookies{
	cookies := Cookies{}
	for key, value := range rawCookie {
		cookies[key] = string(value.([]uint8))
	}
	return cookies
}

func parseTimeout(rawTimeout interface {}) (timeout int64) {
	// is it possible to got timeout in int64 instead of uint64?
	switch rawTimeout.(type) {
	case uint64:
		timeout = int64(rawTimeout.(uint64))
	case int64:
		timeout = rawTimeout.(int64)
	}
	return timeout
}

func (gofetcher *Gofetcher) parseRequest(method string, requestBody []byte) (request *Request){
	var (
		mh codec.MsgpackHandle
		h = &mh
		timeout int64=DefaultTimeout
		cookies Cookies
		headers = make(http.Header)
		followRedirects bool=DefaultFollowRedirects
		body *bytes.Buffer
	)
	mh.MapType = reflect.TypeOf(map[string]interface{}(nil))
	var res []interface{}
	codec.NewDecoderBytes(requestBody, h).Decode(&res)
	url := string(res[0].([]uint8))
	switch {
	case method == "GET" || method == "HEAD" || method == "DELETE":
		if len(res) > 1 {
			timeout = parseTimeout(res[1])
		}
		if len(res) > 2 {
			cookies = parseCookies(res[2].(map[string]interface{}))
		}
		if len(res) > 3 {
			headers = parseHeaders(res[3].(map[string]interface{}))
		}
		if len(res) > 4 {
			followRedirects = res[4].(bool)
		}
	case method == "POST" || method == "PUT" || method == "PATCH":
		if len(res) > 1 {
			body = bytes.NewBuffer(res[1].([]byte))
		}
		if len(res) > 2 {
			timeout = parseTimeout(res[2])
		}
		if len(res) > 3 {
			cookies = parseCookies(res[3].(map[string]interface{}))
		}
		if len(res) > 4 {
			headers = parseHeaders(res[4].(map[string]interface{}))
		}
		if len(res) > 5 {
			followRedirects = res[5].(bool)
		}
	}

	request = &Request{method: method, url:url, timeout:timeout,
		followRedirects: followRedirects,
		cookies:cookies, headers:headers}
	if body != nil {
		request.body = body
	}
	return request
}

func (gofetcher *Gofetcher) writeResponse(response *cocaine.Response, request *Request, resp *Response, err error){
	if err != nil {
		response.Write([]interface{}{false, err.Error(), 0, http.Header{}})
		gofetcher.logger.Err(fmt.Sprintf("Error occured: %v, while downloading %s",
			err.Error(), request.url))
	} else{
		response.Write([]interface{}{true, resp.body, resp.httpResponse.StatusCode, resp.header})
		gofetcher.logger.Info(fmt.Sprintf("Response code: %d, url: %s, runtime: %v",
			resp.httpResponse.StatusCode, request.url, resp.runtime))
		if resp.httpResponse.StatusCode >= 500 {
			gofetcher.logger.Err(fmt.Sprintf("Response code: %d , url: %s, runtime: %v",
				resp.httpResponse.StatusCode, request.url, resp.runtime))
		}

	}
}

func (gofetcher *Gofetcher) handler(method string, request *cocaine.Request, response *cocaine.Response){
	defer response.Close()
	requestBody := <- request.Read()
	httpRequest := gofetcher.parseRequest(method, requestBody)
	resp, err := gofetcher.performRequest(httpRequest)
	gofetcher.writeResponse(response, httpRequest, resp, err)
}

func (gofetcher *Gofetcher) GetHandler(method string) func(request *cocaine.Request, response *cocaine.Response) {
	return func (request *cocaine.Request, response *cocaine.Response){
		gofetcher.handler(method, request, response)
	}
}

// Http methods

func (gofetcher *Gofetcher) writeHttpResponse(response *cocaine.Response, statusCode int,
	data interface{}, headers cocaine.Headers) {
	response.Write(cocaine.WriteHead(statusCode, headers))
	response.Write(data)
	gofetcher.logger.Info("Http response writed")

}

func (gofetcher *Gofetcher) HttpProxy(request *cocaine.Request, response *cocaine.Response){
	defer response.Close()
	var (
		timeout int64 = DefaultTimeout
	)
	req, err := cocaine.UnpackProxyRequest(<-request.Read())
	if err != nil {
		gofetcher.writeHttpResponse(response, 500, "Could not unpack request to http request",
			cocaine.Headers{{"Content-Type", "text/html"}})
		gofetcher.logger.Err("Could not unpack request to http request")

	} else {
		url := req.FormValue("url")
		timeoutArg := req.FormValue("timeout")
		if timeoutArg != "" {
			tout, _ := strconv.Atoi(timeoutArg)
			timeout = int64(tout)
		}
		httpRequest := Request{method:req.Method, url:url, timeout:timeout,
			followRedirects:DefaultFollowRedirects, headers: req.Header, body: req.Body}
		resp, err := gofetcher.performRequest(&httpRequest)
		if err != nil {
			gofetcher.writeHttpResponse(response, 500, err.Error(), cocaine.Headers{{"Content-Type", "text/html"}})
			gofetcher.logger.Err("Gofetcher error: " + err.Error())

		} else {
			gofetcher.writeHttpResponse(response, 200, resp.body, cocaine.HttpHeaderToCocaineHeader(resp.header))
		}
	}
}

func (gofetcher *Gofetcher) HttpEcho(request *cocaine.Request, response *cocaine.Response){
	req, err := cocaine.UnpackProxyRequest(<-request.Read())
	if err != nil {
		gofetcher.writeHttpResponse(response, 500, "Could not unpack request to http request",
			cocaine.Headers{{"Content-Type", "text/html"}})
	} else {
		text := req.FormValue("text")
		gofetcher.writeHttpResponse(response, 200, text, cocaine.Headers{{"Content-Type", "text/html"}})
	}
}
<commit_msg>make Logger public<commit_after>package gofetcher

import (
	"github.com/cocaine/cocaine-framework-go/cocaine"
	"github.com/ugorji/go/codec"
	"net/http"
	"io/ioutil"
	"strconv"
	"time"
	"fmt"
	"errors"
	"bytes"
	"io"
	"reflect"
)

const (
	DefaultTimeout = 5000
	DefaultFollowRedirects = true
)

type Gofetcher struct {
	Logger *cocaine.Logger
}

type Cookies map[string]string

type Request struct {
	method    string
	url       string
	body	io.Reader
	timeout	int64
	cookies	Cookies
	headers http.Header
	followRedirects bool
}

type responseAndError struct{
	res *http.Response
	err error
}

type Response struct {
	httpResponse	*http.Response
	body	[]byte
	header	http.Header
	runtime time.Duration
}

func NewGofetcher() *Gofetcher {
	logger, err := cocaine.NewLogger()
	if err != nil {
		fmt.Printf("Could not initialize logger due to error: %v", err)
		return nil
	}
	gofetcher := Gofetcher{logger}
	return &gofetcher
}

func noRedirect(_ *http.Request, via []*http.Request) error {
	if len(via) > 0 {
		return errors.New("stopped after first redirect")
	}
	return nil
}

func (gofetcher *Gofetcher) performRequest(request *Request) (*Response, error) {
	var (
		err 			error
		httpRequest    *http.Request
		httpResponse   *http.Response
		requestTimeout	time.Duration = time.Duration(request.timeout) * time.Millisecond
	)
	gofetcher.Logger.Info(fmt.Sprintf("Requested url: %s, method: %s, timeout: %d, headers: %v",
		request.url, request.method, request.timeout, request.headers))
	httpClient := &http.Client{}
	if request.followRedirects == false {
		httpClient.CheckRedirect = noRedirect
	}
	httpRequest, err = http.NewRequest(request.method, request.url, request.body)
	if err != nil {
		return nil, err
	}
	for name, value := range request.cookies{
		httpRequest.AddCookie(&http.Cookie{Name:name, Value:value})
	}
	httpRequest.Header = request.headers
	resultChan := make(chan responseAndError)
	started := time.Now()
	go func (){
		res, err := httpClient.Do(httpRequest)
		if err != nil {
			resultChan <- responseAndError{nil, err}
		} else {
			resultChan <- responseAndError{res, err}
		}
	}()
	// http connection stay active after timeout exceeded, cause we can't close it using current client api.
	// Read more about timeouts: https://code.google.com/p/go/issues/detail?id=3362
	//
	select {
	case result := <- resultChan:
		httpResponse, err = result.res, result.err
	case <- time.After(requestTimeout):
		err = errors.New(fmt.Sprintf("Request timeout[%s] exceeded", requestTimeout.String()))
		go func(){
			// close httpResponse when it ready
			result := <- resultChan
			if result.res != nil {
				result.res.Body.Close()
			}
		}()
	}
	if err != nil {
		return nil, err
	}
	defer httpResponse.Body.Close()
	body, err := ioutil.ReadAll(httpResponse.Body)
	if err != nil {
		return nil, err
	}
	runtime := time.Since(started)
	response := &Response{httpResponse: httpResponse, body: body, header: httpResponse.Header, runtime: runtime}
	return response, nil

}

// Normal methods

func parseHeaders(rawHeaders map[string]interface {}) http.Header{
	headers := make(http.Header)
	for name, values := range rawHeaders {
		for _, value := range values.([]interface {}) {
			headers.Add(name, string(value.([]uint8))) // to transform in canonical form
		}
	}
	return headers
}

func parseCookies(rawCookie map[string]interface {}) Cookies{
	cookies := Cookies{}
	for key, value := range rawCookie {
		cookies[key] = string(value.([]uint8))
	}
	return cookies
}

func parseTimeout(rawTimeout interface {}) (timeout int64) {
	// is it possible to got timeout in int64 instead of uint64?
	switch rawTimeout.(type) {
	case uint64:
		timeout = int64(rawTimeout.(uint64))
	case int64:
		timeout = rawTimeout.(int64)
	}
	return timeout
}

func (gofetcher *Gofetcher) parseRequest(method string, requestBody []byte) (request *Request){
	var (
		mh codec.MsgpackHandle
		h = &mh
		timeout int64=DefaultTimeout
		cookies Cookies
		headers = make(http.Header)
		followRedirects bool=DefaultFollowRedirects
		body *bytes.Buffer
	)
	mh.MapType = reflect.TypeOf(map[string]interface{}(nil))
	var res []interface{}
	codec.NewDecoderBytes(requestBody, h).Decode(&res)
	url := string(res[0].([]uint8))
	switch {
	case method == "GET" || method == "HEAD" || method == "DELETE":
		if len(res) > 1 {
			timeout = parseTimeout(res[1])
		}
		if len(res) > 2 {
			cookies = parseCookies(res[2].(map[string]interface{}))
		}
		if len(res) > 3 {
			headers = parseHeaders(res[3].(map[string]interface{}))
		}
		if len(res) > 4 {
			followRedirects = res[4].(bool)
		}
	case method == "POST" || method == "PUT" || method == "PATCH":
		if len(res) > 1 {
			body = bytes.NewBuffer(res[1].([]byte))
		}
		if len(res) > 2 {
			timeout = parseTimeout(res[2])
		}
		if len(res) > 3 {
			cookies = parseCookies(res[3].(map[string]interface{}))
		}
		if len(res) > 4 {
			headers = parseHeaders(res[4].(map[string]interface{}))
		}
		if len(res) > 5 {
			followRedirects = res[5].(bool)
		}
	}

	request = &Request{method: method, url:url, timeout:timeout,
		followRedirects: followRedirects,
		cookies:cookies, headers:headers}
	if body != nil {
		request.body = body
	}
	return request
}

func (gofetcher *Gofetcher) writeResponse(response *cocaine.Response, request *Request, resp *Response, err error){
	if err != nil {
		response.Write([]interface{}{false, err.Error(), 0, http.Header{}})
		gofetcher.Logger.Err(fmt.Sprintf("Error occured: %v, while downloading %s",
			err.Error(), request.url))
	} else{
		response.Write([]interface{}{true, resp.body, resp.httpResponse.StatusCode, resp.header})
		gofetcher.Logger.Info(fmt.Sprintf("Response code: %d, url: %s, runtime: %v",
			resp.httpResponse.StatusCode, request.url, resp.runtime))
	}
}

func (gofetcher *Gofetcher) handler(method string, request *cocaine.Request, response *cocaine.Response){
	defer response.Close()
	requestBody := <- request.Read()
	httpRequest := gofetcher.parseRequest(method, requestBody)
	resp, err := gofetcher.performRequest(httpRequest)
	gofetcher.writeResponse(response, httpRequest, resp, err)
}

func (gofetcher *Gofetcher) GetHandler(method string) func(request *cocaine.Request, response *cocaine.Response) {
	return func (request *cocaine.Request, response *cocaine.Response){
		gofetcher.handler(method, request, response)
	}
}

// Http methods

func (gofetcher *Gofetcher) writeHttpResponse(response *cocaine.Response, statusCode int,
	data interface{}, headers cocaine.Headers) {
	response.Write(cocaine.WriteHead(statusCode, headers))
	response.Write(data)
	gofetcher.logger.Info("Http response writed")

}

func (gofetcher *Gofetcher) HttpProxy(request *cocaine.Request, response *cocaine.Response){
	defer response.Close()
	var (
		timeout int64 = DefaultTimeout
	)
	req, err := cocaine.UnpackProxyRequest(<-request.Read())
	if err != nil {
		gofetcher.writeHttpResponse(response, 500, "Could not unpack request to http request",
			cocaine.Headers{{"Content-Type", "text/html"}})
		gofetcher.logger.Err("Could not unpack request to http request")

	} else {
		url := req.FormValue("url")
		timeoutArg := req.FormValue("timeout")
		if timeoutArg != "" {
			tout, _ := strconv.Atoi(timeoutArg)
			timeout = int64(tout)
		}
		httpRequest := Request{method:req.Method, url:url, timeout:timeout,
			followRedirects:DefaultFollowRedirects, headers: req.Header, body: req.Body}
		resp, err := gofetcher.performRequest(&httpRequest)
		if err != nil {
			gofetcher.writeHttpResponse(response, 500, err.Error(), cocaine.Headers{{"Content-Type", "text/html"}})
			gofetcher.logger.Err("Gofetcher error: " + err.Error())

		} else {
			gofetcher.writeHttpResponse(response, 200, resp.body, cocaine.HttpHeaderToCocaineHeader(resp.header))
		}
	}
}

func (gofetcher *Gofetcher) HttpEcho(request *cocaine.Request, response *cocaine.Response){
	req, err := cocaine.UnpackProxyRequest(<-request.Read())
	if err != nil {
		gofetcher.writeHttpResponse(response, 500, "Could not unpack request to http request",
			cocaine.Headers{{"Content-Type", "text/html"}})
	} else {
		text := req.FormValue("text")
		gofetcher.writeHttpResponse(response, 200, text, cocaine.Headers{{"Content-Type", "text/html"}})
	}
}
<|endoftext|>


<commit_before>package volume

import (
	"context"
	"net/http"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	volumetypes "github.com/docker/docker/api/types/volume"
	"github.com/docker/docker/integration/internal/container"
	"github.com/docker/docker/testutil/request"
	"github.com/google/go-cmp/cmp/cmpopts"
	"gotest.tools/assert"
	is "gotest.tools/assert/cmp"
	"gotest.tools/skip"
)

func TestVolumesCreateAndList(t *testing.T) {
	defer setupTest(t)()
	client := testEnv.APIClient()
	ctx := context.Background()

	name := t.Name()
	// Windows file system is case insensitive
	if testEnv.OSType == "windows" {
		name = strings.ToLower(name)
	}
	vol, err := client.VolumeCreate(ctx, volumetypes.VolumeCreateBody{
		Name: name,
	})
	assert.NilError(t, err)

	expected := types.Volume{
		// Ignore timestamp of CreatedAt
		CreatedAt:  vol.CreatedAt,
		Driver:     "local",
		Scope:      "local",
		Name:       name,
		Mountpoint: filepath.Join(testEnv.DaemonInfo.DockerRootDir, "volumes", name, "_data"),
	}
	assert.Check(t, is.DeepEqual(vol, expected, cmpopts.EquateEmpty()))

	volList, err := client.VolumeList(ctx, filters.Args{})
	assert.NilError(t, err)
	assert.Assert(t, len(volList.Volumes) > 0)

	volumes := volList.Volumes[:0]
	for _, v := range volList.Volumes {
		if v.Name == vol.Name {
			volumes = append(volumes, v)
		}
	}

	assert.Check(t, is.Equal(len(volumes), 1))
	assert.Check(t, volumes[0] != nil)
	assert.Check(t, is.DeepEqual(*volumes[0], expected, cmpopts.EquateEmpty()))
}

func TestVolumesRemove(t *testing.T) {
	skip.If(t, testEnv.OSType == "windows", "FIXME")
	defer setupTest(t)()
	client := testEnv.APIClient()
	ctx := context.Background()

	prefix, slash := getPrefixAndSlashFromDaemonPlatform()

	id := container.Create(ctx, t, client, container.WithVolume(prefix+slash+"foo"))

	c, err := client.ContainerInspect(ctx, id)
	assert.NilError(t, err)
	vname := c.Mounts[0].Name

	err = client.VolumeRemove(ctx, vname, false)
	assert.Check(t, is.ErrorContains(err, "volume is in use"))

	err = client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{
		Force: true,
	})
	assert.NilError(t, err)

	err = client.VolumeRemove(ctx, vname, false)
	assert.NilError(t, err)
}

func TestVolumesInspect(t *testing.T) {
	defer setupTest(t)()
	client := testEnv.APIClient()
	ctx := context.Background()

	now := time.Now()
	vol, err := client.VolumeCreate(ctx, volumetypes.VolumeCreateBody{})
	assert.NilError(t, err)

	inspected, err := client.VolumeInspect(ctx, vol.Name)
	assert.NilError(t, err)

	assert.Check(t, is.DeepEqual(inspected, vol, cmpopts.EquateEmpty()))

	// comparing CreatedAt field time for the new volume to now. Truncate to 1 minute precision to avoid false positive
	createdAt, err := time.Parse(time.RFC3339, strings.TrimSpace(inspected.CreatedAt))
	assert.NilError(t, err)
	assert.Check(t, createdAt.Truncate(time.Minute).Equal(now.Truncate(time.Minute)), "CreatedAt (%s) not equal to creation time (%s)", createdAt, now)
}

func TestVolumesInvalidJSON(t *testing.T) {
	defer setupTest(t)()

	endpoints := []string{"/volumes/create"}

	for _, ep := range endpoints {
		t.Run(ep, func(t *testing.T) {
			t.Parallel()

			res, body, err := request.Post(ep, request.RawString("{invalid json"), request.JSON)
			assert.NilError(t, err)
			assert.Equal(t, res.StatusCode, http.StatusBadRequest)

			buf, err := request.ReadBody(body)
			assert.NilError(t, err)
			assert.Check(t, is.Contains(string(buf), "invalid character 'i' looking for beginning of object key string"))

			res, body, err = request.Post(ep, request.JSON)
			assert.NilError(t, err)
			assert.Equal(t, res.StatusCode, http.StatusBadRequest)

			buf, err = request.ReadBody(body)
			assert.NilError(t, err)
			assert.Check(t, is.Contains(string(buf), "got EOF while reading request body"))
		})
	}
}

func getPrefixAndSlashFromDaemonPlatform() (prefix, slash string) {
	if testEnv.OSType == "windows" {
		return "c:", `\`
	}
	return "", "/"
}
<commit_msg>Testcase: Change time comparison logic.<commit_after>package volume

import (
	"context"
	"net/http"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	volumetypes "github.com/docker/docker/api/types/volume"
	"github.com/docker/docker/integration/internal/container"
	"github.com/docker/docker/testutil/request"
	"github.com/google/go-cmp/cmp/cmpopts"
	"gotest.tools/assert"
	is "gotest.tools/assert/cmp"
	"gotest.tools/skip"
)

func TestVolumesCreateAndList(t *testing.T) {
	defer setupTest(t)()
	client := testEnv.APIClient()
	ctx := context.Background()

	name := t.Name()
	// Windows file system is case insensitive
	if testEnv.OSType == "windows" {
		name = strings.ToLower(name)
	}
	vol, err := client.VolumeCreate(ctx, volumetypes.VolumeCreateBody{
		Name: name,
	})
	assert.NilError(t, err)

	expected := types.Volume{
		// Ignore timestamp of CreatedAt
		CreatedAt:  vol.CreatedAt,
		Driver:     "local",
		Scope:      "local",
		Name:       name,
		Mountpoint: filepath.Join(testEnv.DaemonInfo.DockerRootDir, "volumes", name, "_data"),
	}
	assert.Check(t, is.DeepEqual(vol, expected, cmpopts.EquateEmpty()))

	volList, err := client.VolumeList(ctx, filters.Args{})
	assert.NilError(t, err)
	assert.Assert(t, len(volList.Volumes) > 0)

	volumes := volList.Volumes[:0]
	for _, v := range volList.Volumes {
		if v.Name == vol.Name {
			volumes = append(volumes, v)
		}
	}

	assert.Check(t, is.Equal(len(volumes), 1))
	assert.Check(t, volumes[0] != nil)
	assert.Check(t, is.DeepEqual(*volumes[0], expected, cmpopts.EquateEmpty()))
}

func TestVolumesRemove(t *testing.T) {
	skip.If(t, testEnv.OSType == "windows", "FIXME")
	defer setupTest(t)()
	client := testEnv.APIClient()
	ctx := context.Background()

	prefix, slash := getPrefixAndSlashFromDaemonPlatform()

	id := container.Create(ctx, t, client, container.WithVolume(prefix+slash+"foo"))

	c, err := client.ContainerInspect(ctx, id)
	assert.NilError(t, err)
	vname := c.Mounts[0].Name

	err = client.VolumeRemove(ctx, vname, false)
	assert.Check(t, is.ErrorContains(err, "volume is in use"))

	err = client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{
		Force: true,
	})
	assert.NilError(t, err)

	err = client.VolumeRemove(ctx, vname, false)
	assert.NilError(t, err)
}

func TestVolumesInspect(t *testing.T) {
	defer setupTest(t)()
	client := testEnv.APIClient()
	ctx := context.Background()

	now := time.Now()
	vol, err := client.VolumeCreate(ctx, volumetypes.VolumeCreateBody{})
	assert.NilError(t, err)

	inspected, err := client.VolumeInspect(ctx, vol.Name)
	assert.NilError(t, err)

	assert.Check(t, is.DeepEqual(inspected, vol, cmpopts.EquateEmpty()))

	// comparing CreatedAt field time for the new volume to now. Truncate to 1 minute precision to avoid false positive
	createdAt, err := time.Parse(time.RFC3339, strings.TrimSpace(inspected.CreatedAt))
	assert.NilError(t, err)
	assert.Check(t, createdAt.Unix()-now.Unix() < 60, "CreatedAt (%s) exceeds creation time (%s) 60s", createdAt, now)
}

func TestVolumesInvalidJSON(t *testing.T) {
	defer setupTest(t)()

	endpoints := []string{"/volumes/create"}

	for _, ep := range endpoints {
		t.Run(ep, func(t *testing.T) {
			t.Parallel()

			res, body, err := request.Post(ep, request.RawString("{invalid json"), request.JSON)
			assert.NilError(t, err)
			assert.Equal(t, res.StatusCode, http.StatusBadRequest)

			buf, err := request.ReadBody(body)
			assert.NilError(t, err)
			assert.Check(t, is.Contains(string(buf), "invalid character 'i' looking for beginning of object key string"))

			res, body, err = request.Post(ep, request.JSON)
			assert.NilError(t, err)
			assert.Equal(t, res.StatusCode, http.StatusBadRequest)

			buf, err = request.ReadBody(body)
			assert.NilError(t, err)
			assert.Check(t, is.Contains(string(buf), "got EOF while reading request body"))
		})
	}
}

func getPrefixAndSlashFromDaemonPlatform() (prefix, slash string) {
	if testEnv.OSType == "windows" {
		return "c:", `\`
	}
	return "", "/"
}
<|endoftext|>


<commit_before>// Copyright 2016 Hajime Hoshi
//
// 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.

// +build dragonfly freebsd linux netbsd openbsd solaris
// +build !js
// +build !android

package glfw

import (
	"unsafe"

	"github.com/hajimehoshi/ebiten/internal/glfw"
)

func (u *UserInterface) glfwScale() float64 {
	return u.deviceScaleFactor()
}

func (u *UserInterface) adjustWindowPosition(x, y int) (int, int) {
	return x, y
}

func (u *UserInterface) currentMonitorFromPosition() *glfw.Monitor {
	// TODO: Return more appropriate display.
	if cm, ok := getCachedMonitor(u.window.GetPos()); ok {
		return cm.m
	}
	return glfw.GetPrimaryMonitor()
}

func (u *UserInterface) nativeWindow() unsafe.Pointer {
	// TODO: Implement this.
	return nil
}
<commit_msg>uidriver/glfw: Bug fix: compile error on Linux<commit_after>// Copyright 2016 Hajime Hoshi
//
// 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.

// +build dragonfly freebsd linux netbsd openbsd solaris
// +build !js
// +build !android

package glfw

import (
	"unsafe"

	"github.com/hajimehoshi/ebiten/internal/glfw"
)

func (u *UserInterface) glfwScale() float64 {
	return u.deviceScaleFactor()
}

func (u *UserInterface) adjustWindowPosition(x, y int) (int, int) {
	return x, y
}

func (u *UserInterface) currentMonitorFromPosition() *glfw.Monitor {
	// TODO: Return more appropriate display.
	if cm, ok := getCachedMonitor(u.window.GetMonitor()); ok {
		return cm.m
	}
	return glfw.GetPrimaryMonitor()
}

func (u *UserInterface) nativeWindow() unsafe.Pointer {
	// TODO: Implement this.
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2011 Google Inc. All Rights Reserved.
// This file is available under the Apache license.

package parser

import (
	"flag"
	"strings"
	"testing"

	"github.com/google/mtail/internal/testutil"
	"github.com/google/mtail/internal/vm/ast"
	"github.com/google/mtail/internal/vm/position"
)

var parserTestDebug = flag.Bool("parser_test_debug", false, "Turn on parser debug output if set.")

var parserTests = []struct {
	name    string
	program string
}{
	{"empty",
		""},

	{"newline",
		"\n"},

	{"declare counter",
		"counter lines_total\n"},

	{"declare counter string name",
		"counter lines_total as \"line-count\"\n"},

	{"declare dimensioned counter",
		"counter foo by bar\n"},

	{"declare multi-dimensioned counter",
		"counter foo by bar, baz, quux\n"},

	{"declare hidden counter",
		"hidden counter foo\n"},

	{"declare gauge",
		"gauge foo\n"},

	{"declare timer",
		"timer foo\n"},

	{"declare text",
		"text stringy\n"},

	{"declare histogram",
		"histogram foo buckets 0, 1, 2\n"},
	{"declare histogram float",
		"histogram foo buckets 0, 0.01, 0.1, 1, 10\n"},
	{"declare histogram by ",
		"histogram foo by code buckets 0, 1, 2\n"},
	{"declare histogram reversed syntax ",
		"histogram foo buckets 0, 1, 2 by code\n"},

	{"simple pattern action",
		"/foo/ {}\n"},

	{"more complex action, increment counter",
		"counter lines_total\n" +
			"/foo/ {\n" +
			"  lines_total++\n" +
			"}\n"},

	{"decrement counter",
		`counter i
/foo/ {
  i--
}
`},

	{"regex match includes escaped slashes",
		"counter foo\n" +
			"/foo\\// { foo++\n}\n"},

	{"numeric capture group reference",
		"/(foo)/ {\n" +
			"  $1++\n" +
			"}\n"},

	{"strptime and capref",
		"/(.*)/ {\n" +
			"strptime($1, \"2006-01-02T15:04:05Z07:00\")\n" +
			" }\n"},

	{"named capture group reference",
		"/(?P<date>[[:digit:]-\\/ ])/ {\n" +
			"  strptime($date, \"%Y/%m/%d %H:%M:%S\")\n" +
			"}\n"},

	{"nested match conditions",
		"counter foo\n" +
			"counter bar\n" +
			"/match(\\d+)/ {\n" +
			"  foo += $1\n" +
			"  /^bleh (\\S+)/ {\n" +
			"    bar++\n" +
			"    $1++\n" +
			"  }\n" +
			"}\n"},

	{"nested scope",
		"counter foo\n" +
			"/fo(o)/ {\n" +
			"  $1++\n" +
			"  /bar(xxx)/ {\n" +
			"    $1 += $1\n" +
			"    foo = $1\n" +
			"  }\n" +
			"}\n"},

	{"comment then code",
		"# %d [%p]\n" +
			"/^(?P<date>\\d+\\/\\d+\\/\\d+ \\d+:\\d+:\\d+) \\[(?P<pid>\\d+)\\] / {\n" +
			"  strptime($1, \"2006/01/02 15:04:05\")\n" +
			"}\n"},

	{"assignment",
		"counter variable\n" +
			"/(?P<foo>.*)/ {\n" +
			"variable = $foo\n" +
			"}\n"},

	{"increment operator",
		"counter var\n" +
			"/foo/ {\n" +
			"  var++\n" +
			"}\n"},

	{"incby operator",
		"counter var\n" +
			"/foo/ {\n  var += 2\n}\n"},

	{"additive",
		"counter time_total\n" +
			"/(?P<foo>.*)/ {\n" +
			" time_total = timestamp() - time_total\n" +
			"}\n"},

	{"multiplicative",
		"counter a\n" +
			"counter b\n" +
			"   /foo/ {\n   a = a * b\n" +
			"      a = a ** b\n" +
			"}\n"},

	{"additive and mem storage",
		"counter time_total\n" +
			"counter variable by foo\n" +
			"/(?P<foo>.*)/ {\n" +
			"  time_total += timestamp() - variable[$foo]\n" +
			"}\n"},

	{"conditional expressions",
		"counter foo\n" +
			"/(?P<foo>.*)/ {\n" +
			"  $foo > 0 {\n" +
			"    foo += $foo\n" +
			"  }\n" +
			"  $foo >= 0 {\n" +
			"    foo += $foo\n" +
			"  }\n" +
			"  $foo < 0 {\n" +
			"    foo += $foo\n" +
			"  }\n" +
			"  $foo <= 0 {\n" +
			"    foo += $foo\n" +
			"  }\n" +
			"  $foo == 0 {\n" +
			"    foo += $foo\n" +
			"  }\n" +
			"  $foo != 0 {\n" +
			"    foo += $foo\n" +
			"  }\n" +
			"}\n"},

	{"decorator definition and invocation",
		"def foo { next\n }\n" +
			"@foo { }\n",
	},

	{"const regex",
		"const X /foo/\n" +
			"/foo / + X + / bar/ {\n" +
			"}\n",
	},

	{"multiline regex",
		"/foo / +\n" +
			"/barrr/ {\n" +
			"}\n",
	},

	{"len",
		"/(?P<foo>foo)/ {\n" +
			"len($foo) > 0 {\n" +
			"}\n" +
			"}\n",
	},

	{"def and next",
		"def foobar {/(?P<date>.*)/ {" +
			"  next" +
			"}" +
			"}",
	},

	{"const",
		`const IP /\d+(\.\d+){3}/`},

	{"bitwise",
		`gauge a
/foo(\d)/ {
  a = $1 & 7
  a = $1 | 8
  a = $1 << 4
  a = $1 >> 20
  a = $1 ^ 15
  a = ~ 1
}`},

	{"logical",
		`0 || 1 && 0 {
}
`,
	},

	{"floats",
		`gauge foo
/foo/ {
foo = 3.14
}`},

	{"simple otherwise action",
		"otherwise {}\n"},

	{"pattern action then otherwise action",
		`counter lines_total by type
		/foo/ {
			lines_total["foo"]++
		}
		otherwise {
			lines_total["misc"] += 10
		}`},

	{"simple else clause",
		"/foo/ {} else {}"},

	{"nested else clause",
		"/foo/ { / bar/ {}  } else { /quux/ {} else {} }"},

	{"mod operator",
		`gauge a
/foo/ {
  a = 3 % 1
}`},

	{"delete",
		`counter foo by bar
/foo/ {
  del foo[$1]
}`},

	{"delete after",
		`counter foo by bar
/foo/ {
  del foo[$1] after 168h
}`},

	{"getfilename", `
getfilename()
`},

	{"indexed expression arg list", `
counter foo by a,b
/(\d) (\d+)/ {
  foo[$1,$2]++
}`},

	{"paren expr", `
(0) || (1 && 3) {
}`},

	{"regex cond expr", `
/(\d)/ && 1 {
}
`},

	{"concat expr 1", `
const X /foo/
/bar/ + X {
}`},
	{"concat expr 2", `
const X /foo/
X {
}`},

	{"match expression 1", `
$foo =~ /bar/ {
}
$foo !~ /bar/ {
}
`},
	{"match expression 2", `
$foo =~ /bar/ + X {
}`},
	{"match expression 3", `
const X /foo/
$foo =~ X {
}`},

	{"capref used in def", `
/(?P<x>.*)/ && $x > 0 {
}`},

	{"match expr 4", `
/(?P<foo>.{6}) (?P<bar>.*)/ {
  $foo =~ $bar {
  }
}`},

	{"stop", `
// {
  stop
}`},

	{"substitution", `
/(\d,\d)/ {
  subst(",", ",", $1)
}`},
}

func TestParserRoundTrip(t *testing.T) {
	if *parserTestDebug {
		mtailDebug = 3
	}
	for _, tc := range parserTests {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			p := newParser(tc.name, strings.NewReader(tc.program))
			r := mtailParse(p)

			if r != 0 || p.root == nil || len(p.errors) > 0 {
				t.Error("1st pass parse errors:\n")
				for _, e := range p.errors {
					t.Errorf("\t%s\n", e)
				}
				t.Fatal()
			}

			if *parserTestDebug {
				s := Sexp{}
				t.Log("AST:\n" + s.Dump(p.root))
			}

			u := Unparser{}
			output := u.Unparse(p.root)

			p2 := newParser(tc.name+" 2", strings.NewReader(output))
			r = mtailParse(p2)
			if r != 0 || p2.root == nil || len(p2.errors) > 0 {
				t.Errorf("2nd pass parse errors:\n")
				for _, e := range p2.errors {
					t.Errorf("\t%s\n", e)
				}
				t.Logf("2nd pass input was:\n%s", output)
				t.Logf("2nd pass diff:\n%s", testutil.Diff(tc.program, output))
				t.Fatal()
			}

			u = Unparser{}
			output2 := u.Unparse(p2.root)

			testutil.ExpectNoDiff(t, output2, output)
		})
	}
}

type parserInvalidProgram struct {
	name    string
	program string
	errors  []string
}

var parserInvalidPrograms = []parserInvalidProgram{
	{"unknown character",
		"?\n",
		[]string{"unknown character:1:1: Unexpected input: '?'"}},

	{"unterminated regex",
		"/foo\n",
		[]string{"unterminated regex:1:2-4: Unterminated regular expression: \"/foo\"",
			"unterminated regex:1:2-4: syntax error: unexpected end of file, expecting '/' to end regex"}},

	{"unterminated string",
		" \"foo }\n",
		[]string{"unterminated string:1:2-7: Unterminated quoted string: \"\\\"foo }\""}},

	{"unterminated const regex",
		"const X /(?P<foo>",
		[]string{"unterminated const regex:1:10-17: Unterminated regular expression: \"/(?P<foo>\"",
			"unterminated const regex:1:10-17: syntax error: unexpected end of file, expecting '/' to end regex"}},

	{"unbalanced {",
		"/foo/ {\n",
		[]string{"unbalanced {:2:1: syntax error: unexpected end of file, expecting '}' to end block"}},
	{"unbalanced else {",
		"/foo/ { } else {\n",
		[]string{"unbalanced else {:2:1: syntax error: unexpected end of file, expecting '}' to end block"}},
	{"unbalanced otherwise {",
		"otherwise {\n",
		[]string{"unbalanced otherwise {:2:1: syntax error: unexpected end of file, expecting '}' to end block"}},

	{"index of non-terminal 1",
		`// {
	foo++[$1]++
	}`,
		[]string{"index of non-terminal 1:2:7: syntax error: unexpected indexing of an expression"}},
	{"index of non-terminal 2",
		`// {
	0[$1]++
	}`,
		[]string{"index of non-terminal 2:2:3: syntax error: unexpected indexing of an expression"}},

	{"statement with no effect",
		`/(\d)foo/ {
 timestamp() - $1
}`, []string{"statement with no effect:3:18: syntax error: statement with no effect, missing an assignment, `+' concatenation, or `{}' block?"}},

	{"pattern without block",
		`/(?P<a>.)/
	/(?P<b>.)/ {}
	`,
		[]string{"pattern without block:2:11: syntax error: statement with no effect, missing an assignment, `+' concatenation, or `{}' block?"}},
}

func TestParseInvalidPrograms(t *testing.T) {
	if *parserTestDebug {
		mtailDebug = 3
	}
	for _, tc := range parserInvalidPrograms {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			p := newParser(tc.name, strings.NewReader(tc.program))
			mtailParse(p)

			testutil.ExpectNoDiff(t,
				strings.Join(tc.errors, "\n"),             // want
				strings.TrimRight(p.errors.Error(), "\n")) // got
			if p.errors.Error() == "no errors" && *parserTestDebug {
				s := Sexp{}
				t.Log("AST:\n" + s.Dump(p.root))
			}
		})
	}
}

var parsePositionTests = []struct {
	name      string
	program   string
	positions []*position.Position
}{
	{
		"empty",
		"",
		nil,
	},
	{
		"variable",
		`counter foo`,
		[]*position.Position{{"variable", 0, 8, 10}},
	},
	{
		"pattern",
		`const ID /foo/`,
		[]*position.Position{{"pattern", 0, 6, 13}},
	},
}

func TestParsePositionTests(t *testing.T) {
	for _, tc := range parsePositionTests {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			// Not t.Parallel() because the parser is not reentrant, and mtailDebug is a global.
			root, err := Parse(tc.name, strings.NewReader(tc.program))
			testutil.FatalIfErr(t, err)
			p := &positionCollector{}
			ast.Walk(p, root)
			testutil.ExpectNoDiff(t, tc.positions, p.positions, testutil.AllowUnexported(position.Position{}))
		})
	}
}

type positionCollector struct {
	positions []*position.Position
}

func (p *positionCollector) VisitBefore(node ast.Node) (ast.Visitor, ast.Node) {
	switch n := node.(type) {
	case *ast.VarDecl, *ast.PatternLit:
		p.positions = append(p.positions, n.Pos())
	}
	return p, node
}

func (p *positionCollector) VisitAfter(node ast.Node) ast.Node {
	return node
}
<commit_msg>Change the name of a test.<commit_after>// Copyright 2011 Google Inc. All Rights Reserved.
// This file is available under the Apache license.

package parser

import (
	"flag"
	"strings"
	"testing"

	"github.com/google/mtail/internal/testutil"
	"github.com/google/mtail/internal/vm/ast"
	"github.com/google/mtail/internal/vm/position"
)

var parserTestDebug = flag.Bool("parser_test_debug", false, "Turn on parser debug output if set.")

var parserTests = []struct {
	name    string
	program string
}{
	{"empty",
		""},

	{"newline",
		"\n"},

	{"declare counter",
		"counter lines_total\n"},

	{"declare counter string name",
		"counter lines_total as \"line-count\"\n"},

	{"declare dimensioned counter",
		"counter foo by bar\n"},

	{"declare multi-dimensioned counter",
		"counter foo by bar, baz, quux\n"},

	{"declare hidden counter",
		"hidden counter foo\n"},

	{"declare gauge",
		"gauge foo\n"},

	{"declare timer",
		"timer foo\n"},

	{"declare text",
		"text stringy\n"},

	{"declare histogram",
		"histogram foo buckets 0, 1, 2\n"},
	{"declare histogram float",
		"histogram foo buckets 0, 0.01, 0.1, 1, 10\n"},
	{"declare histogram by ",
		"histogram foo by code buckets 0, 1, 2\n"},
	{"declare histogram reversed syntax ",
		"histogram foo buckets 0, 1, 2 by code\n"},

	{"simple pattern action",
		"/foo/ {}\n"},

	{"increment counter",
		"counter lines_total\n" +
			"/foo/ {\n" +
			"  lines_total++\n" +
			"}\n"},

	{"decrement counter",
		`counter i
/foo/ {
  i--
}
`},

	{"regex match includes escaped slashes",
		"counter foo\n" +
			"/foo\\// { foo++\n}\n"},

	{"numeric capture group reference",
		"/(foo)/ {\n" +
			"  $1++\n" +
			"}\n"},

	{"strptime and capref",
		"/(.*)/ {\n" +
			"strptime($1, \"2006-01-02T15:04:05Z07:00\")\n" +
			" }\n"},

	{"named capture group reference",
		"/(?P<date>[[:digit:]-\\/ ])/ {\n" +
			"  strptime($date, \"%Y/%m/%d %H:%M:%S\")\n" +
			"}\n"},

	{"nested match conditions",
		"counter foo\n" +
			"counter bar\n" +
			"/match(\\d+)/ {\n" +
			"  foo += $1\n" +
			"  /^bleh (\\S+)/ {\n" +
			"    bar++\n" +
			"    $1++\n" +
			"  }\n" +
			"}\n"},

	{"nested scope",
		"counter foo\n" +
			"/fo(o)/ {\n" +
			"  $1++\n" +
			"  /bar(xxx)/ {\n" +
			"    $1 += $1\n" +
			"    foo = $1\n" +
			"  }\n" +
			"}\n"},

	{"comment then code",
		"# %d [%p]\n" +
			"/^(?P<date>\\d+\\/\\d+\\/\\d+ \\d+:\\d+:\\d+) \\[(?P<pid>\\d+)\\] / {\n" +
			"  strptime($1, \"2006/01/02 15:04:05\")\n" +
			"}\n"},

	{"assignment",
		"counter variable\n" +
			"/(?P<foo>.*)/ {\n" +
			"variable = $foo\n" +
			"}\n"},

	{"increment operator",
		"counter var\n" +
			"/foo/ {\n" +
			"  var++\n" +
			"}\n"},

	{"incby operator",
		"counter var\n" +
			"/foo/ {\n  var += 2\n}\n"},

	{"additive",
		"counter time_total\n" +
			"/(?P<foo>.*)/ {\n" +
			" time_total = timestamp() - time_total\n" +
			"}\n"},

	{"multiplicative",
		"counter a\n" +
			"counter b\n" +
			"   /foo/ {\n   a = a * b\n" +
			"      a = a ** b\n" +
			"}\n"},

	{"additive and mem storage",
		"counter time_total\n" +
			"counter variable by foo\n" +
			"/(?P<foo>.*)/ {\n" +
			"  time_total += timestamp() - variable[$foo]\n" +
			"}\n"},

	{"conditional expressions",
		"counter foo\n" +
			"/(?P<foo>.*)/ {\n" +
			"  $foo > 0 {\n" +
			"    foo += $foo\n" +
			"  }\n" +
			"  $foo >= 0 {\n" +
			"    foo += $foo\n" +
			"  }\n" +
			"  $foo < 0 {\n" +
			"    foo += $foo\n" +
			"  }\n" +
			"  $foo <= 0 {\n" +
			"    foo += $foo\n" +
			"  }\n" +
			"  $foo == 0 {\n" +
			"    foo += $foo\n" +
			"  }\n" +
			"  $foo != 0 {\n" +
			"    foo += $foo\n" +
			"  }\n" +
			"}\n"},

	{"decorator definition and invocation",
		"def foo { next\n }\n" +
			"@foo { }\n",
	},

	{"const regex",
		"const X /foo/\n" +
			"/foo / + X + / bar/ {\n" +
			"}\n",
	},

	{"multiline regex",
		"/foo / +\n" +
			"/barrr/ {\n" +
			"}\n",
	},

	{"len",
		"/(?P<foo>foo)/ {\n" +
			"len($foo) > 0 {\n" +
			"}\n" +
			"}\n",
	},

	{"def and next",
		"def foobar {/(?P<date>.*)/ {" +
			"  next" +
			"}" +
			"}",
	},

	{"const",
		`const IP /\d+(\.\d+){3}/`},

	{"bitwise",
		`gauge a
/foo(\d)/ {
  a = $1 & 7
  a = $1 | 8
  a = $1 << 4
  a = $1 >> 20
  a = $1 ^ 15
  a = ~ 1
}`},

	{"logical",
		`0 || 1 && 0 {
}
`,
	},

	{"floats",
		`gauge foo
/foo/ {
foo = 3.14
}`},

	{"simple otherwise action",
		"otherwise {}\n"},

	{"pattern action then otherwise action",
		`counter lines_total by type
		/foo/ {
			lines_total["foo"]++
		}
		otherwise {
			lines_total["misc"] += 10
		}`},

	{"simple else clause",
		"/foo/ {} else {}"},

	{"nested else clause",
		"/foo/ { / bar/ {}  } else { /quux/ {} else {} }"},

	{"mod operator",
		`gauge a
/foo/ {
  a = 3 % 1
}`},

	{"delete",
		`counter foo by bar
/foo/ {
  del foo[$1]
}`},

	{"delete after",
		`counter foo by bar
/foo/ {
  del foo[$1] after 168h
}`},

	{"getfilename", `
getfilename()
`},

	{"indexed expression arg list", `
counter foo by a,b
/(\d) (\d+)/ {
  foo[$1,$2]++
}`},

	{"paren expr", `
(0) || (1 && 3) {
}`},

	{"regex cond expr", `
/(\d)/ && 1 {
}
`},

	{"concat expr 1", `
const X /foo/
/bar/ + X {
}`},
	{"concat expr 2", `
const X /foo/
X {
}`},

	{"match expression 1", `
$foo =~ /bar/ {
}
$foo !~ /bar/ {
}
`},
	{"match expression 2", `
$foo =~ /bar/ + X {
}`},
	{"match expression 3", `
const X /foo/
$foo =~ X {
}`},

	{"capref used in def", `
/(?P<x>.*)/ && $x > 0 {
}`},

	{"match expr 4", `
/(?P<foo>.{6}) (?P<bar>.*)/ {
  $foo =~ $bar {
  }
}`},

	{"stop", `
// {
  stop
}`},

	{"substitution", `
/(\d,\d)/ {
  subst(",", ",", $1)
}`},
}

func TestParserRoundTrip(t *testing.T) {
	if *parserTestDebug {
		mtailDebug = 3
	}
	for _, tc := range parserTests {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			p := newParser(tc.name, strings.NewReader(tc.program))
			r := mtailParse(p)

			if r != 0 || p.root == nil || len(p.errors) > 0 {
				t.Error("1st pass parse errors:\n")
				for _, e := range p.errors {
					t.Errorf("\t%s\n", e)
				}
				t.Fatal()
			}

			if *parserTestDebug {
				s := Sexp{}
				t.Log("AST:\n" + s.Dump(p.root))
			}

			u := Unparser{}
			output := u.Unparse(p.root)

			p2 := newParser(tc.name+" 2", strings.NewReader(output))
			r = mtailParse(p2)
			if r != 0 || p2.root == nil || len(p2.errors) > 0 {
				t.Errorf("2nd pass parse errors:\n")
				for _, e := range p2.errors {
					t.Errorf("\t%s\n", e)
				}
				t.Logf("2nd pass input was:\n%s", output)
				t.Logf("2nd pass diff:\n%s", testutil.Diff(tc.program, output))
				t.Fatal()
			}

			u = Unparser{}
			output2 := u.Unparse(p2.root)

			testutil.ExpectNoDiff(t, output2, output)
		})
	}
}

type parserInvalidProgram struct {
	name    string
	program string
	errors  []string
}

var parserInvalidPrograms = []parserInvalidProgram{
	{"unknown character",
		"?\n",
		[]string{"unknown character:1:1: Unexpected input: '?'"}},

	{"unterminated regex",
		"/foo\n",
		[]string{"unterminated regex:1:2-4: Unterminated regular expression: \"/foo\"",
			"unterminated regex:1:2-4: syntax error: unexpected end of file, expecting '/' to end regex"}},

	{"unterminated string",
		" \"foo }\n",
		[]string{"unterminated string:1:2-7: Unterminated quoted string: \"\\\"foo }\""}},

	{"unterminated const regex",
		"const X /(?P<foo>",
		[]string{"unterminated const regex:1:10-17: Unterminated regular expression: \"/(?P<foo>\"",
			"unterminated const regex:1:10-17: syntax error: unexpected end of file, expecting '/' to end regex"}},

	{"unbalanced {",
		"/foo/ {\n",
		[]string{"unbalanced {:2:1: syntax error: unexpected end of file, expecting '}' to end block"}},
	{"unbalanced else {",
		"/foo/ { } else {\n",
		[]string{"unbalanced else {:2:1: syntax error: unexpected end of file, expecting '}' to end block"}},
	{"unbalanced otherwise {",
		"otherwise {\n",
		[]string{"unbalanced otherwise {:2:1: syntax error: unexpected end of file, expecting '}' to end block"}},

	{"index of non-terminal 1",
		`// {
	foo++[$1]++
	}`,
		[]string{"index of non-terminal 1:2:7: syntax error: unexpected indexing of an expression"}},
	{"index of non-terminal 2",
		`// {
	0[$1]++
	}`,
		[]string{"index of non-terminal 2:2:3: syntax error: unexpected indexing of an expression"}},

	{"statement with no effect",
		`/(\d)foo/ {
 timestamp() - $1
}`, []string{"statement with no effect:3:18: syntax error: statement with no effect, missing an assignment, `+' concatenation, or `{}' block?"}},

	{"pattern without block",
		`/(?P<a>.)/
	/(?P<b>.)/ {}
	`,
		[]string{"pattern without block:2:11: syntax error: statement with no effect, missing an assignment, `+' concatenation, or `{}' block?"}},
}

func TestParseInvalidPrograms(t *testing.T) {
	if *parserTestDebug {
		mtailDebug = 3
	}
	for _, tc := range parserInvalidPrograms {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			p := newParser(tc.name, strings.NewReader(tc.program))
			mtailParse(p)

			testutil.ExpectNoDiff(t,
				strings.Join(tc.errors, "\n"),             // want
				strings.TrimRight(p.errors.Error(), "\n")) // got
			if p.errors.Error() == "no errors" && *parserTestDebug {
				s := Sexp{}
				t.Log("AST:\n" + s.Dump(p.root))
			}
		})
	}
}

var parsePositionTests = []struct {
	name      string
	program   string
	positions []*position.Position
}{
	{
		"empty",
		"",
		nil,
	},
	{
		"variable",
		`counter foo`,
		[]*position.Position{{"variable", 0, 8, 10}},
	},
	{
		"pattern",
		`const ID /foo/`,
		[]*position.Position{{"pattern", 0, 6, 13}},
	},
}

func TestParsePositionTests(t *testing.T) {
	for _, tc := range parsePositionTests {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			// Not t.Parallel() because the parser is not reentrant, and mtailDebug is a global.
			root, err := Parse(tc.name, strings.NewReader(tc.program))
			testutil.FatalIfErr(t, err)
			p := &positionCollector{}
			ast.Walk(p, root)
			testutil.ExpectNoDiff(t, tc.positions, p.positions, testutil.AllowUnexported(position.Position{}))
		})
	}
}

type positionCollector struct {
	positions []*position.Position
}

func (p *positionCollector) VisitBefore(node ast.Node) (ast.Visitor, ast.Node) {
	switch n := node.(type) {
	case *ast.VarDecl, *ast.PatternLit:
		p.positions = append(p.positions, n.Pos())
	}
	return p, node
}

func (p *positionCollector) VisitAfter(node ast.Node) ast.Node {
	return node
}
<|endoftext|>


<commit_before>/*
Copyright 2017 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 main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"regexp"
)

var (
	successEndpoints = []string{
		// Discovery
		"http://169.254.169.254",
		"http://metadata.google.internal",
		"http://169.254.169.254/",
		"http://metadata.google.internal/",
		"http://metadata.google.internal/0.1",
		"http://metadata.google.internal/0.1/",
		"http://metadata.google.internal/0.1/meta-data",
		"http://metadata.google.internal/computeMetadata",
		"http://metadata.google.internal/computeMetadata/v1beta1",
		"http://metadata.google.internal/computeMetadata/v1",
		// Allowed API versions.
		"http://metadata.google.internal/0.1/meta-data/",
		"http://metadata.google.internal/computeMetadata/v1beta1/",
		"http://metadata.google.internal/computeMetadata/v1/",
		// Service account token endpoints.
		"http://metadata.google.internal/0.1/meta-data/service-accounts/default/acquire",
		"http://metadata.google.internal/computeMetadata/v1beta1/instance/service-accounts/default/token",
		"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token",
		// Params that contain 'recursive' as substring.
		"http://metadata.google.internal/computeMetadata/v1/instance/?nonrecursive=true",
		"http://metadata.google.internal/computeMetadata/v1/instance/?something=other&nonrecursive=true",
	}
	noKubeEnvEndpoints = []string{
		// Check that these don't get a recursive result.
		"http://metadata.google.internal/computeMetadata/v1/instance/?recursive%3Dtrue",   // urlencoded
		"http://metadata.google.internal/computeMetadata/v1/instance/?re%08ecursive=true", // backspaced
	}
	failureEndpoints = []string{
		// Other API versions.
		"http://metadata.google.internal/0.2/",
		"http://metadata.google.internal/computeMetadata/v2/",
		// kube-env.
		"http://metadata.google.internal/0.1/meta-data/attributes/kube-env",
		"http://metadata.google.internal/computeMetadata/v1beta1/instance/attributes/kube-env",
		"http://metadata.google.internal/computeMetadata/v1/instance/attributes/kube-env",
		// VM identity.
		"http://metadata.google.internal/0.1/meta-data/service-accounts/default/identity",
		"http://metadata.google.internal/computeMetadata/v1beta1/instance/service-accounts/default/identity",
		"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity",
		// Recursive.
		"http://metadata.google.internal/computeMetadata/v1/instance/?recursive=true",
		"http://metadata.google.internal/computeMetadata/v1/instance/?something=other&recursive=true",
		"http://metadata.google.internal/computeMetadata/v1/instance/?recursive=true&something=other",
		// Other.
		"http://metadata.google.internal/computeMetadata/v1/instance/attributes//kube-env",
		"http://metadata.google.internal/computeMetadata/v1/instance/attributes/../attributes/kube-env",
		"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts//default/identity",
		"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/../service-accounts/default/identity",
	}
)

func main() {
	success := 0
	h := map[string][]string{
		"Metadata-Flavor": {"Google"},
	}
	for _, e := range successEndpoints {
		if err := checkURL(e, h, 200, ""); err != nil {
			log.Printf("Wrong response for %v: %v", e, err)
			success = 1
		}
	}
	for _, e := range noKubeEnvEndpoints {
		if err := checkURL(e, h, 200, "kube-env"); err != nil {
			log.Printf("Wrong response for %v: %v", e, err)
			success = 1
		}
	}
	for _, e := range failureEndpoints {
		if err := checkURL(e, h, 403, ""); err != nil {
			log.Printf("Wrong response for %v: %v", e, err)
			success = 1
		}
	}

	xForwardedForHeader := map[string][]string{
		"X-Forwarded-For": {"Somebody-somewhere"},
	}
	// Check that success endpoints fail if X-Forwarded-For is present.
	for _, e := range successEndpoints {
		if err := checkURL(e, xForwardedForHeader, 403, ""); err != nil {
			log.Printf("Wrong response for %v with X-Forwarded-For: %v", e, err)
			success = 1
		}
	}
	os.Exit(success)
}

// Checks that a URL with the given headers returns the right code, and if s is
// non-empty, checks that the body doesn't contain s.
func checkURL(url string, header http.Header, expectedStatus int, s string) error {
	client := &http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return err
	}
	req.Header = header
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != expectedStatus {
		return fmt.Errorf("unexpected response: got %d, want %d", resp.StatusCode, expectedStatus)
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	if s != "" {
		matched, err := regexp.Match(s, body)
		if err != nil {
			return err
		}
		if matched {
			return fmt.Errorf("body incorrectly contained %q: got %v", s, string(body))
		}
	}
	return nil
}
<commit_msg>account for disabled legacy metadata endpoints<commit_after>/*
Copyright 2017 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 main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"regexp"
)

var (
	successEndpoints = []string{
		// Discovery
		"http://169.254.169.254",
		"http://metadata.google.internal",
		"http://169.254.169.254/",
		"http://metadata.google.internal/",
		"http://metadata.google.internal/computeMetadata",
		"http://metadata.google.internal/computeMetadata/v1",
		// Allowed API versions.
		"http://metadata.google.internal/computeMetadata/v1/",
		// Service account token endpoints.
		"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token",
		// Params that contain 'recursive' as substring.
		"http://metadata.google.internal/computeMetadata/v1/instance/?nonrecursive=true",
		"http://metadata.google.internal/computeMetadata/v1/instance/?something=other&nonrecursive=true",
	}
	legacySuccessEndpoints = []string{
		// Discovery
		"http://metadata.google.internal/0.1",
		"http://metadata.google.internal/0.1/",
		"http://metadata.google.internal/0.1/meta-data",
		"http://metadata.google.internal/computeMetadata/v1beta1",
		// Allowed API versions.
		"http://metadata.google.internal/0.1/meta-data/",
		"http://metadata.google.internal/computeMetadata/v1beta1/",
		// Service account token endpoints.
		"http://metadata.google.internal/0.1/meta-data/service-accounts/default/acquire",
		"http://metadata.google.internal/computeMetadata/v1beta1/instance/service-accounts/default/token",
	}
	noKubeEnvEndpoints = []string{
		// Check that these don't get a recursive result.
		"http://metadata.google.internal/computeMetadata/v1/instance/?recursive%3Dtrue",   // urlencoded
		"http://metadata.google.internal/computeMetadata/v1/instance/?re%08ecursive=true", // backspaced
	}
	failureEndpoints = []string{
		// Other API versions.
		"http://metadata.google.internal/0.2/",
		"http://metadata.google.internal/computeMetadata/v2/",
		// kube-env.
		"http://metadata.google.internal/0.1/meta-data/attributes/kube-env",
		"http://metadata.google.internal/computeMetadata/v1beta1/instance/attributes/kube-env",
		"http://metadata.google.internal/computeMetadata/v1/instance/attributes/kube-env",
		// VM identity.
		"http://metadata.google.internal/0.1/meta-data/service-accounts/default/identity",
		"http://metadata.google.internal/computeMetadata/v1beta1/instance/service-accounts/default/identity",
		"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity",
		// Recursive.
		"http://metadata.google.internal/computeMetadata/v1/instance/?recursive=true",
		"http://metadata.google.internal/computeMetadata/v1/instance/?something=other&recursive=true",
		"http://metadata.google.internal/computeMetadata/v1/instance/?recursive=true&something=other",
		// Other.
		"http://metadata.google.internal/computeMetadata/v1/instance/attributes//kube-env",
		"http://metadata.google.internal/computeMetadata/v1/instance/attributes/../attributes/kube-env",
		"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts//default/identity",
		"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/../service-accounts/default/identity",
	}
)

func main() {
	success := 0
	h := map[string][]string{
		"Metadata-Flavor": {"Google"},
	}
	for _, e := range successEndpoints {
		if err := checkURL(e, h, 200, "", ""); err != nil {
			log.Printf("Wrong response for %v: %v", e, err)
			success = 1
		}
	}
	for _, e := range noKubeEnvEndpoints {
		if err := checkURL(e, h, 200, "", "kube-env"); err != nil {
			log.Printf("Wrong response for %v: %v", e, err)
			success = 1
		}
	}
	for _, e := range failureEndpoints {
		if err := checkURL(e, h, 403, "", ""); err != nil {
			log.Printf("Wrong response for %v: %v", e, err)
			success = 1
		}
	}

	legacyEndpointExpectedStatus := 200
	if err := checkURL("http://metadata.google.internal/computeMetadata/v1/instance/attributes/disable-legacy-endpoints", h, 200, "true", ""); err == nil {
		// If `disable-legacy-endpoints` is set to true, queries to unconcealed legacy endpoints will return a 403.
		legacyEndpointExpectedStatus = 403
	}
	for _, e := range legacySuccessEndpoints {
		if err := checkURL(e, h, legacyEndpointExpectedStatus, "", ""); err != nil {
			log.Printf("Wrong response for %v: %v", e, err)
			success = 1
		}
	}

	xForwardedForHeader := map[string][]string{
		"X-Forwarded-For": {"Somebody-somewhere"},
	}
	// Check that success endpoints fail if X-Forwarded-For is present.
	for _, e := range successEndpoints {
		if err := checkURL(e, xForwardedForHeader, 403, "", ""); err != nil {
			log.Printf("Wrong response for %v with X-Forwarded-For: %v", e, err)
			success = 1
		}
	}
	os.Exit(success)
}

// Checks that a URL with the given headers returns the right code.
// If expectedToContain is non-empty, checks that the body contains expectedToContain.
// Similarly, if expectedToNotContain is non-empty, checks that the body doesn't contain expectedToNotContain.
func checkURL(url string, header http.Header, expectedStatus int, expectedToContain, expectedToNotContain string) error {
	client := &http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return err
	}
	req.Header = header
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != expectedStatus {
		return fmt.Errorf("unexpected response: got %d, want %d", resp.StatusCode, expectedStatus)
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	if expectedToContain != "" {
		matched, err := regexp.Match(expectedToContain, body)
		if err != nil {
			return err
		}
		if !matched {
			return fmt.Errorf("body didn't contain %q: got %v", expectedToContain, string(body))
		}
	}
	if expectedToNotContain != "" {
		matched, err := regexp.Match(expectedToNotContain, body)
		if err != nil {
			return err
		}
		if matched {
			return fmt.Errorf("body incorrectly contained %q: got %v", expectedToNotContain, string(body))
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package util

import (
	"errors"
	"github.com/astaxie/beego"
	"github.com/astaxie/beego/orm"
	"net/url"
	"strings"
)

// 封装一层 getAll 获取列表 ,方便使用
func GetAll(c beego.Controller, model interface{}, contains interface{}, defaultLimit int64) (err error) {
	f, err := BuildFilter(c, defaultLimit)
	if err != nil {
		return
	}
	err = GetAllByFilter(model, contains, f)
	return
}
func GetAllWithTotal(c beego.Controller, model interface{}, contains interface{}, defaultLimit int64) (total int64, err error) {
	f, err := BuildFilter(c, defaultLimit)
	if err != nil {
		return
	}
	total, err = GetAllByFilterWithTotal(model, contains, f)
	return
}

type Filter struct {
	Fields []string                 // 字段
	Order  []string                 // 排序 : -id  id
	Where  map[string][]interface{} // id:1;user_id:in1,2,3|<=2
	Limit  int64                    //
	Offset int64                    //
}

// 根据输入生成过滤条件
// query=id:1;type:in1,3,5 & order=-ascId,descType & limit=0 & offset=0
func BuildFilter(c beego.Controller, defaultLimit int64) (f *Filter, err error) {
	var fields []string
	var order []string
	var query = make(map[string]string)
	var limit int64 = defaultLimit
	var offset int64

	// fields: col1,col2,entity.col3
	if v := c.GetString("fields"); v != "" {
		fields = strings.Split(v, ",")
	}
	// limit: 10 (default is 10)
	if v, err := c.GetInt64("limit"); err == nil {
		limit = v
		if limit > 50 {
			limit = 50
		}
	}
	// offset: 0 (default is 0)
	if v, err := c.GetInt64("offset"); err == nil {
		offset = v
	}

	// order: desc,asc
	if v := c.GetString("order"); v != "" {
		order = strings.Split(v, ",")
	}
	// query: k:v;k:v
	if v := c.GetString("query"); v != "" {
		v, _ = url.QueryUnescape(v)
		for _, cond := range strings.Split(v, ";") {
			kv := strings.SplitN(cond, ":", 2)
			if len(kv) != 2 {
				err = errors.New("invalid query key/value pair")
				return
			}
			k, v := kv[0], kv[1]
			query[k] = v
		}
	}

	where := map[string][]interface{}{} // condition => args
	// condition : inx,x|<x|>x|>=x|<=x|=x|x|!=x|
	for f, cond := range query {
		// 支持一个字段多个条件
		for _, x := range strings.Split(cond, "|") {
			isNot := ""
			if len(x) > 1 && x[0] == '!' {
				isNot = "!"
				x = x[1:]
			}

			if strings.Index(x, "in") == 0 {
				args := []interface{}{}
				for _, v := range strings.Split(x[2:], ",") {
					args = append(args, v)
				}
				where[isNot+f+"__in"] = args
			} else if strings.Index(x, ">=") == 0 {
				where[isNot+f+"__gte"] = []interface{}{x[2:]}
			} else if strings.Index(x, ">") == 0 {
				where[isNot+f+"__gt"] = []interface{}{x[1:]}
			} else if strings.Index(x, "<=") == 0 {
				where[isNot+f+"__lte"] = []interface{}{x[2:]}
			} else if strings.Index(x, "<") == 0 {
				where[isNot+f+"__lt"] = []interface{}{x[1:]}
			} else if strings.Index(x, "=") == 0 {
				where[isNot+f ] = []interface{}{x[1:]}
			} else if strings.Index(x, "like") == 0 {
				where[isNot+f+"__icontains"] = []interface{}{x[4:]}
			} else {
				where[isNot+f ] = []interface{}{x}
			}
		}
	}

	f = &Filter{
		Fields: fields,
		Limit:  limit,
		Offset: offset,
		Order:  order,
		Where:  where,
	}

	return
}

func GetAllByFilter(model interface{}, contains interface{}, f *Filter) (err error) {
	o := orm.NewOrm()
	qs := o.QueryTable(model)
	// query k=v

	for k, v := range f.Where {
		if len(k) > 1 && k[0] == '!' {
			k = k[1:]
			qs = qs.Exclude(k, v...)
		} else {
			qs = qs.Filter(k, v...)
		}
	}

	// order by:
	qs = qs.OrderBy(f.Order...)

	_, err = qs.Limit(f.Limit, f.Offset).All(contains, f.Fields...)
	return
}

func GetAllByFilterWithTotal(model interface{}, contains interface{}, f *Filter) (total int64, err error) {
	o := orm.NewOrm()
	qs := o.QueryTable(model)
	// query k=v

	for k, v := range f.Where {
		if len(k) > 1 && k[0] == '!' {
			k = k[1:]
			qs = qs.Exclude(k, v...)
		} else {
			qs = qs.Filter(k, v...)
		}
	}
	total, err = qs.Count()
	if err != nil {
		return
	}

	// order by:
	qs = qs.OrderBy(f.Order...)

	_, err = qs.Limit(f.Limit, f.Offset).All(contains, f.Fields...)

	return
}
<commit_msg>fix package<commit_after>package beeorm

import (
	"errors"
	"github.com/astaxie/beego"
	"github.com/astaxie/beego/orm"
	"net/url"
	"strings"
)

// 封装一层 getAll 获取列表 ,方便使用
func GetAll(c *beego.Controller, model interface{}, contains interface{}, defaultLimit int64) (err error) {
	f, err := BuildFilter(c, defaultLimit)
	if err != nil {
		return
	}
	err = GetAllByFilter(model, contains, f)
	return
}
func GetAllWithTotal(c *beego.Controller, model interface{}, contains interface{}, defaultLimit int64) (total int64, err error) {
	f, err := BuildFilter(c, defaultLimit)
	if err != nil {
		return
	}
	total, err = GetAllByFilterWithTotal(model, contains, f)
	return
}

type Filter struct {
	Fields []string                 // 字段
	Order  []string                 // 排序 : -id  id
	Where  map[string][]interface{} // id:1;user_id:in1,2,3|<=2
	Limit  int64                    //
	Offset int64                    //
}

// 根据输入生成过滤条件
// query=id:1;type:in1,3,5 & order=-ascId,descType & limit=0 & offset=0
func BuildFilter(c *beego.Controller, defaultLimit int64) (f *Filter, err error) {
	var fields []string
	var order []string
	var query = make(map[string]string)
	var limit int64 = defaultLimit
	var offset int64

	// fields: col1,col2,entity.col3
	if v := c.GetString("fields"); v != "" {
		fields = strings.Split(v, ",")
	}
	// limit: 10 (default is 10)
	if v, err := c.GetInt64("limit"); err == nil {
		limit = v
		if limit > 50 {
			limit = 50
		}
	}
	// offset: 0 (default is 0)
	if v, err := c.GetInt64("offset"); err == nil {
		offset = v
	}

	// order: desc,asc
	if v := c.GetString("order"); v != "" {
		order = strings.Split(v, ",")
	}
	// query: k:v;k:v
	if v := c.GetString("query"); v != "" {
		v, _ = url.QueryUnescape(v)
		for _, cond := range strings.Split(v, ";") {
			kv := strings.SplitN(cond, ":", 2)
			if len(kv) != 2 {
				err = errors.New("invalid query key/value pair")
				return
			}
			k, v := kv[0], kv[1]
			query[k] = v
		}
	}

	where := map[string][]interface{}{} // condition => args
	// condition : inx,x|<x|>x|>=x|<=x|=x|x|!=x|
	for f, cond := range query {
		// 支持一个字段多个条件
		for _, x := range strings.Split(cond, "|") {
			isNot := ""
			if len(x) > 1 && x[0] == '!' {
				isNot = "!"
				x = x[1:]
			}

			if strings.Index(x, "in") == 0 {
				args := []interface{}{}
				for _, v := range strings.Split(x[2:], ",") {
					args = append(args, v)
				}
				where[isNot+f+"__in"] = args
			} else if strings.Index(x, ">=") == 0 {
				where[isNot+f+"__gte"] = []interface{}{x[2:]}
			} else if strings.Index(x, ">") == 0 {
				where[isNot+f+"__gt"] = []interface{}{x[1:]}
			} else if strings.Index(x, "<=") == 0 {
				where[isNot+f+"__lte"] = []interface{}{x[2:]}
			} else if strings.Index(x, "<") == 0 {
				where[isNot+f+"__lt"] = []interface{}{x[1:]}
			} else if strings.Index(x, "=") == 0 {
				where[isNot+f ] = []interface{}{x[1:]}
			} else if strings.Index(x, "like") == 0 {
				where[isNot+f+"__icontains"] = []interface{}{x[4:]}
			} else {
				where[isNot+f ] = []interface{}{x}
			}
		}
	}

	f = &Filter{
		Fields: fields,
		Limit:  limit,
		Offset: offset,
		Order:  order,
		Where:  where,
	}

	return
}

func GetAllByFilter(model interface{}, contains interface{}, f *Filter) (err error) {
	o := orm.NewOrm()
	qs := o.QueryTable(model)
	// query k=v

	for k, v := range f.Where {
		if len(k) > 1 && k[0] == '!' {
			k = k[1:]
			qs = qs.Exclude(k, v...)
		} else {
			qs = qs.Filter(k, v...)
		}
	}

	// order by:
	qs = qs.OrderBy(f.Order...)

	_, err = qs.Limit(f.Limit, f.Offset).All(contains, f.Fields...)
	return
}

func GetAllByFilterWithTotal(model interface{}, contains interface{}, f *Filter) (total int64, err error) {
	o := orm.NewOrm()
	qs := o.QueryTable(model)
	// query k=v

	for k, v := range f.Where {
		if len(k) > 1 && k[0] == '!' {
			k = k[1:]
			qs = qs.Exclude(k, v...)
		} else {
			qs = qs.Filter(k, v...)
		}
	}
	total, err = qs.Count()
	if err != nil {
		return
	}

	// order by:
	qs = qs.OrderBy(f.Order...)

	_, err = qs.Limit(f.Limit, f.Offset).All(contains, f.Fields...)

	return
}
<|endoftext|>


<commit_before>package dns

import (
	"context"
	"sync"
	"time"

	"github.com/miekg/dns"
	"v2ray.com/core/app/dispatcher"
	"v2ray.com/core/common"
	"v2ray.com/core/common/buf"
	"v2ray.com/core/common/dice"
	"v2ray.com/core/common/net"
	"v2ray.com/core/transport/internet/udp"
)

const (
	CleanupInterval  = time.Second * 120
	CleanupThreshold = 512
)

var (
	multiQuestionDNS = map[net.Address]bool{
		net.IPAddress([]byte{8, 8, 8, 8}): true,
		net.IPAddress([]byte{8, 8, 4, 4}): true,
		net.IPAddress([]byte{9, 9, 9, 9}): true,
	}
)

type ARecord struct {
	IPs    []net.IP
	Expire time.Time
}

type NameServer interface {
	QueryA(domain string) <-chan *ARecord
}

type PendingRequest struct {
	expire   time.Time
	response chan<- *ARecord
}

type UDPNameServer struct {
	sync.Mutex
	address     net.Destination
	requests    map[uint16]*PendingRequest
	udpServer   *udp.Dispatcher
	nextCleanup time.Time
}

func NewUDPNameServer(address net.Destination, dispatcher dispatcher.Interface) *UDPNameServer {
	s := &UDPNameServer{
		address:   address,
		requests:  make(map[uint16]*PendingRequest),
		udpServer: udp.NewDispatcher(dispatcher),
	}
	return s
}

func (s *UDPNameServer) Cleanup() {
	expiredRequests := make([]uint16, 0, 16)
	now := time.Now()
	s.Lock()
	for id, r := range s.requests {
		if r.expire.Before(now) {
			expiredRequests = append(expiredRequests, id)
			close(r.response)
		}
	}
	for _, id := range expiredRequests {
		delete(s.requests, id)
	}
	s.Unlock()
}

func (s *UDPNameServer) AssignUnusedID(response chan<- *ARecord) uint16 {
	var id uint16
	s.Lock()
	if len(s.requests) > CleanupThreshold && s.nextCleanup.Before(time.Now()) {
		s.nextCleanup = time.Now().Add(CleanupInterval)
		go s.Cleanup()
	}

	for {
		id = dice.RollUint16()
		if _, found := s.requests[id]; found {
			continue
		}
		newError("add pending request id ", id).AtDebug().WriteToLog()
		s.requests[id] = &PendingRequest{
			expire:   time.Now().Add(time.Second * 8),
			response: response,
		}
		break
	}
	s.Unlock()
	return id
}

func (s *UDPNameServer) HandleResponse(payload *buf.Buffer) {
	msg := new(dns.Msg)
	err := msg.Unpack(payload.Bytes())
	if err == dns.ErrTruncated {
		newError("truncated message received. DNS server should still work. If you see anything abnormal, please submit an issue to v2ray-core.").AtWarning().WriteToLog()
	} else if err != nil {
		newError("failed to parse DNS response").Base(err).AtWarning().WriteToLog()
		return
	}
	record := &ARecord{
		IPs: make([]net.IP, 0, 16),
	}
	id := msg.Id
	ttl := uint32(3600) // an hour
	newError("handling response for id ", id, " content: ", msg).AtDebug().WriteToLog()

	s.Lock()
	request, found := s.requests[id]
	if !found {
		s.Unlock()
		return
	}
	delete(s.requests, id)
	s.Unlock()

	for _, rr := range msg.Answer {
		switch rr := rr.(type) {
		case *dns.A:
			record.IPs = append(record.IPs, rr.A)
			if rr.Hdr.Ttl < ttl {
				ttl = rr.Hdr.Ttl
			}
		case *dns.AAAA:
			record.IPs = append(record.IPs, rr.AAAA)
			if rr.Hdr.Ttl < ttl {
				ttl = rr.Hdr.Ttl
			}
		}
	}
	record.Expire = time.Now().Add(time.Second * time.Duration(ttl))

	request.response <- record
	close(request.response)
}

func (s *UDPNameServer) BuildQueryA(domain string, id uint16) *buf.Buffer {
	msg := new(dns.Msg)
	msg.Id = id
	msg.RecursionDesired = true
	msg.Question = []dns.Question{
		{
			Name:   dns.Fqdn(domain),
			Qtype:  dns.TypeA,
			Qclass: dns.ClassINET,
		}}
	if multiQuestionDNS[s.address.Address] {
		msg.Question = append(msg.Question, dns.Question{
			Name:   dns.Fqdn(domain),
			Qtype:  dns.TypeAAAA,
			Qclass: dns.ClassINET,
		})
	}

	buffer := buf.New()
	common.Must(buffer.Reset(func(b []byte) (int, error) {
		writtenBuffer, err := msg.PackBuffer(b)
		return len(writtenBuffer), err
	}))

	return buffer
}

func (s *UDPNameServer) QueryA(domain string) <-chan *ARecord {
	response := make(chan *ARecord, 1)
	id := s.AssignUnusedID(response)

	ctx, cancel := context.WithCancel(context.Background())
	s.udpServer.Dispatch(ctx, s.address, s.BuildQueryA(domain, id), s.HandleResponse)

	go func() {
		for i := 0; i < 2; i++ {
			time.Sleep(time.Second)
			s.Lock()
			_, found := s.requests[id]
			s.Unlock()
			if !found {
				break
			}
			s.udpServer.Dispatch(ctx, s.address, s.BuildQueryA(domain, id), s.HandleResponse)
		}
		cancel()
	}()

	return response
}

type LocalNameServer struct {
}

func (*LocalNameServer) QueryA(domain string) <-chan *ARecord {
	response := make(chan *ARecord, 1)

	go func() {
		defer close(response)

		resolver := net.SystemIPResolver()
		ips, err := resolver.LookupIP(domain)
		if err != nil {
			newError("failed to lookup IPs for domain ", domain).Base(err).WriteToLog()
			return
		}

		response <- &ARecord{
			IPs:    ips,
			Expire: time.Now().Add(time.Hour),
		}
	}()

	return response
}
<commit_msg>report pack error instead of panic<commit_after>package dns

import (
	"context"
	"sync"
	"time"

	"github.com/miekg/dns"
	"v2ray.com/core/app/dispatcher"
	"v2ray.com/core/common/buf"
	"v2ray.com/core/common/dice"
	"v2ray.com/core/common/net"
	"v2ray.com/core/transport/internet/udp"
)

const (
	CleanupInterval  = time.Second * 120
	CleanupThreshold = 512
)

var (
	multiQuestionDNS = map[net.Address]bool{
		net.IPAddress([]byte{8, 8, 8, 8}): true,
		net.IPAddress([]byte{8, 8, 4, 4}): true,
		net.IPAddress([]byte{9, 9, 9, 9}): true,
	}
)

type ARecord struct {
	IPs    []net.IP
	Expire time.Time
}

type NameServer interface {
	QueryA(domain string) <-chan *ARecord
}

type PendingRequest struct {
	expire   time.Time
	response chan<- *ARecord
}

type UDPNameServer struct {
	sync.Mutex
	address     net.Destination
	requests    map[uint16]*PendingRequest
	udpServer   *udp.Dispatcher
	nextCleanup time.Time
}

func NewUDPNameServer(address net.Destination, dispatcher dispatcher.Interface) *UDPNameServer {
	s := &UDPNameServer{
		address:   address,
		requests:  make(map[uint16]*PendingRequest),
		udpServer: udp.NewDispatcher(dispatcher),
	}
	return s
}

func (s *UDPNameServer) Cleanup() {
	expiredRequests := make([]uint16, 0, 16)
	now := time.Now()
	s.Lock()
	for id, r := range s.requests {
		if r.expire.Before(now) {
			expiredRequests = append(expiredRequests, id)
			close(r.response)
		}
	}
	for _, id := range expiredRequests {
		delete(s.requests, id)
	}
	s.Unlock()
}

func (s *UDPNameServer) AssignUnusedID(response chan<- *ARecord) uint16 {
	var id uint16
	s.Lock()
	if len(s.requests) > CleanupThreshold && s.nextCleanup.Before(time.Now()) {
		s.nextCleanup = time.Now().Add(CleanupInterval)
		go s.Cleanup()
	}

	for {
		id = dice.RollUint16()
		if _, found := s.requests[id]; found {
			continue
		}
		newError("add pending request id ", id).AtDebug().WriteToLog()
		s.requests[id] = &PendingRequest{
			expire:   time.Now().Add(time.Second * 8),
			response: response,
		}
		break
	}
	s.Unlock()
	return id
}

func (s *UDPNameServer) HandleResponse(payload *buf.Buffer) {
	msg := new(dns.Msg)
	err := msg.Unpack(payload.Bytes())
	if err == dns.ErrTruncated {
		newError("truncated message received. DNS server should still work. If you see anything abnormal, please submit an issue to v2ray-core.").AtWarning().WriteToLog()
	} else if err != nil {
		newError("failed to parse DNS response").Base(err).AtWarning().WriteToLog()
		return
	}
	record := &ARecord{
		IPs: make([]net.IP, 0, 16),
	}
	id := msg.Id
	ttl := uint32(3600) // an hour
	newError("handling response for id ", id, " content: ", msg).AtDebug().WriteToLog()

	s.Lock()
	request, found := s.requests[id]
	if !found {
		s.Unlock()
		return
	}
	delete(s.requests, id)
	s.Unlock()

	for _, rr := range msg.Answer {
		switch rr := rr.(type) {
		case *dns.A:
			record.IPs = append(record.IPs, rr.A)
			if rr.Hdr.Ttl < ttl {
				ttl = rr.Hdr.Ttl
			}
		case *dns.AAAA:
			record.IPs = append(record.IPs, rr.AAAA)
			if rr.Hdr.Ttl < ttl {
				ttl = rr.Hdr.Ttl
			}
		}
	}
	record.Expire = time.Now().Add(time.Second * time.Duration(ttl))

	request.response <- record
	close(request.response)
}

func (s *UDPNameServer) buildAMsg(domain string, id uint16) *dns.Msg {
	msg := new(dns.Msg)
	msg.Id = id
	msg.RecursionDesired = true
	msg.Question = []dns.Question{
		{
			Name:   dns.Fqdn(domain),
			Qtype:  dns.TypeA,
			Qclass: dns.ClassINET,
		}}
	if multiQuestionDNS[s.address.Address] {
		msg.Question = append(msg.Question, dns.Question{
			Name:   dns.Fqdn(domain),
			Qtype:  dns.TypeAAAA,
			Qclass: dns.ClassINET,
		})
	}

	return msg
}

func msgToBuffer(msg *dns.Msg) (*buf.Buffer, error) {
	buffer := buf.New()
	if err := buffer.Reset(func(b []byte) (int, error) {
		writtenBuffer, err := msg.PackBuffer(b)
		return len(writtenBuffer), err
	}); err != nil {
		return nil, err
	}
	return buffer, nil
}

func (s *UDPNameServer) QueryA(domain string) <-chan *ARecord {
	response := make(chan *ARecord, 1)
	id := s.AssignUnusedID(response)

	msg := s.buildAMsg(domain, id)
	b, err := msgToBuffer(msg)
	if err != nil {
		newError("failed to build A query for domain ", domain).Base(err).WriteToLog()
		close(response)
		return response
	}

	ctx, cancel := context.WithCancel(context.Background())
	s.udpServer.Dispatch(ctx, s.address, b, s.HandleResponse)

	go func() {
		for i := 0; i < 2; i++ {
			time.Sleep(time.Second)
			s.Lock()
			_, found := s.requests[id]
			s.Unlock()
			if !found {
				break
			}
			b, _ := msgToBuffer(msg)
			s.udpServer.Dispatch(ctx, s.address, b, s.HandleResponse)
		}
		cancel()
	}()

	return response
}

type LocalNameServer struct {
}

func (*LocalNameServer) QueryA(domain string) <-chan *ARecord {
	response := make(chan *ARecord, 1)

	go func() {
		defer close(response)

		resolver := net.SystemIPResolver()
		ips, err := resolver.LookupIP(domain)
		if err != nil {
			newError("failed to lookup IPs for domain ", domain).Base(err).WriteToLog()
			return
		}

		response <- &ARecord{
			IPs:    ips,
			Expire: time.Now().Add(time.Hour),
		}
	}()

	return response
}
<|endoftext|>