<commit_before>// This file is part of go-utils.
//
// Copyright (C) 2015-2016  David Gamba Rios
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

/*
Package fileutils - file related utilities
*/
package fileutils

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
)

// CopyFile copies the contents of the file named src to the file named
// by dst. The file will be created if it does not already exist. If the
// destination file exists, all it's contents will be replaced by the contents
// of the source file.
func CopyFile(src, dst string) error {
	fmt.Printf("Copy: %s %s\n", src, dst)
	in, err := os.Open(src)
	if err != nil {
		return err
	}
	defer in.Close()
	out, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer func() {
		cerr := out.Close()
		if err == nil {
			err = cerr
		}
	}()
	if _, err = io.Copy(out, in); err != nil {
		return err
	}
	err = out.Sync()
	return err
}

// StringError is a struct containing the string `String` and error `Error`.
type StringError struct {
	String string
	Error  error
}

// GetFileList returns a channel with each file (`channel.String`) or an error indicating failure (`channel.Error`).
func GetFileList(filename string, ignoreDirs bool) <-chan StringError {
	c := make(chan StringError)
	go func() {
		fInfo, err := os.Stat(filename)
		if err != nil {
			c <- StringError{"", err}
			return
		}
		if fInfo.IsDir() {
			if ignoreDirs == false {
				c <- StringError{filename, nil}
			}
			fileSearch := filename + string(filepath.Separator) + "*"
			fileMatches, err := filepath.Glob(fileSearch)
			if err != nil {
				c <- StringError{"", err}
				return
			}
			for _, file := range fileMatches {
				if filepath.Base(filename) == filepath.Base(file) {
					continue
				}
				d := GetFileList(file, ignoreDirs)
				for dirFile := range d {
					if dirFile.Error != nil {
						return
					}
					c <- StringError{dirFile.String, nil}
				}
			}
		} else {
			c <- StringError{filename, nil}
		}
		close(c)
	}()
	return c
}

// GetDirList returns a channel with each file (`channel.String`) or an error indicating failure (`channel.Error`).
func GetDirList(dir string) <-chan StringError {
	c := make(chan StringError)
	go func() {
		fInfo, err := os.Stat(dir)
		if err != nil {
			c <- StringError{"", err}
			return
		}
		if fInfo.IsDir() {
			c <- StringError{dir, nil}
			fileSearch := dir + string(filepath.Separator) + "*"
			fileMatches, err := filepath.Glob(fileSearch)
			if err != nil {
				c <- StringError{"", err}
				return
			}
			for _, file := range fileMatches {
				if filepath.Base(dir) == filepath.Base(file) {
					continue
				}
				d := GetDirList(file)
				for dirFile := range d {
					if dirFile.Error != nil {
						return
					}
					c <- StringError{dirFile.String, nil}
				}
			}
		}
		close(c)
	}()
	return c
}
<commit_msg>Fix unclosed channel cases<commit_after>// This file is part of go-utils.
//
// Copyright (C) 2015-2016  David Gamba Rios
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

/*
Package fileutils - file related utilities
*/
package fileutils

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
)

// CopyFile copies the contents of the file named src to the file named
// by dst. The file will be created if it does not already exist. If the
// destination file exists, all it's contents will be replaced by the contents
// of the source file.
func CopyFile(src, dst string) error {
	in, err := os.Open(src)
	if err != nil {
		return err
	}
	defer in.Close()
	out, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer func() {
		cerr := out.Close()
		if err == nil {
			err = cerr
		}
	}()
	if _, err = io.Copy(out, in); err != nil {
		return err
	}
	err = out.Sync()
	return err
}

// StringError is a struct containing the string `String` and error `Error`.
type StringError struct {
	String string
	Error  error
}

// GetFileList returns a channel with each file (`channel.String`) or an error indicating failure (`channel.Error`).
func GetFileList(filename string, ignoreDirs bool) <-chan StringError {
	c := make(chan StringError)
	go func() {
		fInfo, err := os.Stat(filename)
		if err != nil {
			c <- StringError{"", err}
			close(c)
			return
		}
		if fInfo.IsDir() {
			if ignoreDirs == false {
				c <- StringError{filename, nil}
			}
			fileSearch := filename + string(filepath.Separator) + "*"
			fileMatches, err := filepath.Glob(fileSearch)
			if err != nil {
				c <- StringError{"", err}
				close(c)
				return
			}
			for _, file := range fileMatches {
				if filepath.Base(filename) == filepath.Base(file) {
					continue
				}
				d := GetFileList(file, ignoreDirs)
				for dirFile := range d {
					if dirFile.Error != nil {
						close(c)
						return
					}
					c <- StringError{dirFile.String, nil}
				}
			}
		} else {
			c <- StringError{filename, nil}
		}
		close(c)
	}()
	return c
}

// GetDirList returns a channel with each file (`channel.String`) or an error indicating failure (`channel.Error`).
func GetDirList(dir string) <-chan StringError {
	c := make(chan StringError)
	go func() {
		fInfo, err := os.Stat(dir)
		if err != nil {
			c <- StringError{"", err}
			return
		}
		if fInfo.IsDir() {
			c <- StringError{dir, nil}
			fileSearch := dir + string(filepath.Separator) + "*"
			fileMatches, err := filepath.Glob(fileSearch)
			if err != nil {
				c <- StringError{"", err}
				return
			}
			for _, file := range fileMatches {
				if filepath.Base(dir) == filepath.Base(file) {
					continue
				}
				d := GetDirList(file)
				for dirFile := range d {
					if dirFile.Error != nil {
						return
					}
					c <- StringError{dirFile.String, nil}
				}
			}
		}
		close(c)
	}()
	return c
}
<|endoftext|>


<commit_before>// Copyright 2015 Matthew Holt and The Caddy 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 reverseproxy

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"reflect"
	"time"

	"github.com/caddyserver/caddy/v2"
	"golang.org/x/net/http2"
)

func init() {
	caddy.RegisterModule(HTTPTransport{})
}

// HTTPTransport is essentially a configuration wrapper for http.Transport.
// It defines a JSON structure useful when configuring the HTTP transport
// for Caddy's reverse proxy. It builds its http.Transport at Provision.
type HTTPTransport struct {
	// TODO: It's possible that other transports (like fastcgi) might be
	// able to borrow/use at least some of these config fields; if so,
	// maybe move them into a type called CommonTransport and embed it?

	TLS                   *TLSConfig     `json:"tls,omitempty"`
	KeepAlive             *KeepAlive     `json:"keep_alive,omitempty"`
	Compression           *bool          `json:"compression,omitempty"`
	MaxConnsPerHost       int            `json:"max_conns_per_host,omitempty"`
	DialTimeout           caddy.Duration `json:"dial_timeout,omitempty"`
	FallbackDelay         caddy.Duration `json:"dial_fallback_delay,omitempty"`
	ResponseHeaderTimeout caddy.Duration `json:"response_header_timeout,omitempty"`
	ExpectContinueTimeout caddy.Duration `json:"expect_continue_timeout,omitempty"`
	MaxResponseHeaderSize int64          `json:"max_response_header_size,omitempty"`
	WriteBufferSize       int            `json:"write_buffer_size,omitempty"`
	ReadBufferSize        int            `json:"read_buffer_size,omitempty"`
	Versions              []string       `json:"versions,omitempty"`

	Transport *http.Transport `json:"-"`
}

// CaddyModule returns the Caddy module information.
func (HTTPTransport) CaddyModule() caddy.ModuleInfo {
	return caddy.ModuleInfo{
		ID:  "http.reverse_proxy.transport.http",
		New: func() caddy.Module { return new(HTTPTransport) },
	}
}

// Provision sets up h.Transport with a *http.Transport
// that is ready to use.
func (h *HTTPTransport) Provision(_ caddy.Context) error {
	if len(h.Versions) == 0 {
		h.Versions = []string{"1.1", "2"}
	}

	rt, err := h.newTransport()
	if err != nil {
		return err
	}
	h.Transport = rt

	return nil
}

func (h *HTTPTransport) newTransport() (*http.Transport, error) {
	dialer := &net.Dialer{
		Timeout:       time.Duration(h.DialTimeout),
		FallbackDelay: time.Duration(h.FallbackDelay),
		// TODO: Resolver
	}

	rt := &http.Transport{
		DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
			// the proper dialing information should be embedded into the request's context
			if dialInfo, ok := GetDialInfo(ctx); ok {
				network = dialInfo.Network
				address = dialInfo.Address
			}
			conn, err := dialer.DialContext(ctx, network, address)
			if err != nil {
				// identify this error as one that occurred during
				// dialing, which can be important when trying to
				// decide whether to retry a request
				return nil, DialError{err}
			}
			return conn, nil
		},
		MaxConnsPerHost:        h.MaxConnsPerHost,
		ResponseHeaderTimeout:  time.Duration(h.ResponseHeaderTimeout),
		ExpectContinueTimeout:  time.Duration(h.ExpectContinueTimeout),
		MaxResponseHeaderBytes: h.MaxResponseHeaderSize,
		WriteBufferSize:        h.WriteBufferSize,
		ReadBufferSize:         h.ReadBufferSize,
	}

	if h.TLS != nil {
		rt.TLSHandshakeTimeout = time.Duration(h.TLS.HandshakeTimeout)

		var err error
		rt.TLSClientConfig, err = h.TLS.MakeTLSClientConfig()
		if err != nil {
			return nil, fmt.Errorf("making TLS client config: %v", err)
		}
	}

	if h.KeepAlive != nil {
		dialer.KeepAlive = time.Duration(h.KeepAlive.ProbeInterval)
		if h.KeepAlive.Enabled != nil {
			rt.DisableKeepAlives = !*h.KeepAlive.Enabled
		}
		rt.MaxIdleConns = h.KeepAlive.MaxIdleConns
		rt.MaxIdleConnsPerHost = h.KeepAlive.MaxIdleConnsPerHost
		rt.IdleConnTimeout = time.Duration(h.KeepAlive.IdleConnTimeout)
	}

	if h.Compression != nil {
		rt.DisableCompression = !*h.Compression
	}

	if sliceContains(h.Versions, "2") {
		if err := http2.ConfigureTransport(rt); err != nil {
			return nil, err
		}
	}

	return rt, nil
}

// RoundTrip implements http.RoundTripper.
func (h *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	h.setScheme(req)
	return h.Transport.RoundTrip(req)
}

// setScheme ensures that the outbound request req
// has the scheme set in its URL; the underlying
// http.Transport requires a scheme to be set.
func (h *HTTPTransport) setScheme(req *http.Request) {
	if req.URL.Scheme == "" {
		req.URL.Scheme = "http"
		if h.TLS != nil {
			req.URL.Scheme = "https"
		}
	}
}

// Cleanup implements caddy.CleanerUpper and closes any idle connections.
func (h HTTPTransport) Cleanup() error {
	if h.Transport == nil {
		return nil
	}
	h.Transport.CloseIdleConnections()
	return nil
}

// TLSConfig holds configuration related to the
// TLS configuration for the transport/client.
type TLSConfig struct {
	RootCAPool []string `json:"root_ca_pool,omitempty"`
	// Added to the same pool as above, but brought in from files
	RootCAPemFiles []string `json:"root_ca_pem_files,omitempty"`
	// TODO: Should the client cert+key config use caddytls.CertificateLoader modules?
	ClientCertificateFile    string         `json:"client_certificate_file,omitempty"`
	ClientCertificateKeyFile string         `json:"client_certificate_key_file,omitempty"`
	InsecureSkipVerify       bool           `json:"insecure_skip_verify,omitempty"`
	HandshakeTimeout         caddy.Duration `json:"handshake_timeout,omitempty"`
	ServerName               string         `json:"server_name,omitempty"`
}

// MakeTLSClientConfig returns a tls.Config usable by a client to a backend.
// If there is no custom TLS configuration, a nil config may be returned.
func (t TLSConfig) MakeTLSClientConfig() (*tls.Config, error) {
	cfg := new(tls.Config)

	// client auth
	if t.ClientCertificateFile != "" && t.ClientCertificateKeyFile == "" {
		return nil, fmt.Errorf("client_certificate_file specified without client_certificate_key_file")
	}
	if t.ClientCertificateFile == "" && t.ClientCertificateKeyFile != "" {
		return nil, fmt.Errorf("client_certificate_key_file specified without client_certificate_file")
	}
	if t.ClientCertificateFile != "" && t.ClientCertificateKeyFile != "" {
		cert, err := tls.LoadX509KeyPair(t.ClientCertificateFile, t.ClientCertificateKeyFile)
		if err != nil {
			return nil, fmt.Errorf("loading client certificate key pair: %v", err)
		}
		cfg.Certificates = []tls.Certificate{cert}
	}

	// trusted root CAs
	if len(t.RootCAPool) > 0 || len(t.RootCAPemFiles) > 0 {
		rootPool := x509.NewCertPool()
		for _, encodedCACert := range t.RootCAPool {
			caCert, err := decodeBase64DERCert(encodedCACert)
			if err != nil {
				return nil, fmt.Errorf("parsing CA certificate: %v", err)
			}
			rootPool.AddCert(caCert)
		}
		for _, pemFile := range t.RootCAPemFiles {
			pemData, err := ioutil.ReadFile(pemFile)
			if err != nil {
				return nil, fmt.Errorf("failed reading ca cert: %v", err)
			}
			rootPool.AppendCertsFromPEM(pemData)

		}
		cfg.RootCAs = rootPool
	}

	// custom SNI
	cfg.ServerName = t.ServerName

	// throw all security out the window
	cfg.InsecureSkipVerify = t.InsecureSkipVerify

	// only return a config if it's not empty
	if reflect.DeepEqual(cfg, new(tls.Config)) {
		return nil, nil
	}

	return cfg, nil
}

// KeepAlive holds configuration pertaining to HTTP Keep-Alive.
type KeepAlive struct {
	Enabled             *bool          `json:"enabled,omitempty"`
	ProbeInterval       caddy.Duration `json:"probe_interval,omitempty"`
	MaxIdleConns        int            `json:"max_idle_conns,omitempty"`
	MaxIdleConnsPerHost int            `json:"max_idle_conns_per_host,omitempty"`
	IdleConnTimeout     caddy.Duration `json:"idle_timeout,omitempty"` // how long should connections be kept alive when idle
}

// decodeBase64DERCert base64-decodes, then DER-decodes, certStr.
func decodeBase64DERCert(certStr string) (*x509.Certificate, error) {
	// decode base64
	derBytes, err := base64.StdEncoding.DecodeString(certStr)
	if err != nil {
		return nil, err
	}

	// parse the DER-encoded certificate
	return x509.ParseCertificate(derBytes)
}

// sliceContains returns true if needle is in haystack.
func sliceContains(haystack []string, needle string) bool {
	for _, s := range haystack {
		if s == needle {
			return true
		}
	}
	return false
}

// Interface guards
var (
	_ caddy.Provisioner  = (*HTTPTransport)(nil)
	_ http.RoundTripper  = (*HTTPTransport)(nil)
	_ caddy.CleanerUpper = (*HTTPTransport)(nil)
)
<commit_msg>reverseproxy: Fix casing of RootCAPEMFiles<commit_after>// Copyright 2015 Matthew Holt and The Caddy 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 reverseproxy

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"reflect"
	"time"

	"github.com/caddyserver/caddy/v2"
	"golang.org/x/net/http2"
)

func init() {
	caddy.RegisterModule(HTTPTransport{})
}

// HTTPTransport is essentially a configuration wrapper for http.Transport.
// It defines a JSON structure useful when configuring the HTTP transport
// for Caddy's reverse proxy. It builds its http.Transport at Provision.
type HTTPTransport struct {
	// TODO: It's possible that other transports (like fastcgi) might be
	// able to borrow/use at least some of these config fields; if so,
	// maybe move them into a type called CommonTransport and embed it?

	TLS                   *TLSConfig     `json:"tls,omitempty"`
	KeepAlive             *KeepAlive     `json:"keep_alive,omitempty"`
	Compression           *bool          `json:"compression,omitempty"`
	MaxConnsPerHost       int            `json:"max_conns_per_host,omitempty"`
	DialTimeout           caddy.Duration `json:"dial_timeout,omitempty"`
	FallbackDelay         caddy.Duration `json:"dial_fallback_delay,omitempty"`
	ResponseHeaderTimeout caddy.Duration `json:"response_header_timeout,omitempty"`
	ExpectContinueTimeout caddy.Duration `json:"expect_continue_timeout,omitempty"`
	MaxResponseHeaderSize int64          `json:"max_response_header_size,omitempty"`
	WriteBufferSize       int            `json:"write_buffer_size,omitempty"`
	ReadBufferSize        int            `json:"read_buffer_size,omitempty"`
	Versions              []string       `json:"versions,omitempty"`

	Transport *http.Transport `json:"-"`
}

// CaddyModule returns the Caddy module information.
func (HTTPTransport) CaddyModule() caddy.ModuleInfo {
	return caddy.ModuleInfo{
		ID:  "http.reverse_proxy.transport.http",
		New: func() caddy.Module { return new(HTTPTransport) },
	}
}

// Provision sets up h.Transport with a *http.Transport
// that is ready to use.
func (h *HTTPTransport) Provision(_ caddy.Context) error {
	if len(h.Versions) == 0 {
		h.Versions = []string{"1.1", "2"}
	}

	rt, err := h.newTransport()
	if err != nil {
		return err
	}
	h.Transport = rt

	return nil
}

func (h *HTTPTransport) newTransport() (*http.Transport, error) {
	dialer := &net.Dialer{
		Timeout:       time.Duration(h.DialTimeout),
		FallbackDelay: time.Duration(h.FallbackDelay),
		// TODO: Resolver
	}

	rt := &http.Transport{
		DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
			// the proper dialing information should be embedded into the request's context
			if dialInfo, ok := GetDialInfo(ctx); ok {
				network = dialInfo.Network
				address = dialInfo.Address
			}
			conn, err := dialer.DialContext(ctx, network, address)
			if err != nil {
				// identify this error as one that occurred during
				// dialing, which can be important when trying to
				// decide whether to retry a request
				return nil, DialError{err}
			}
			return conn, nil
		},
		MaxConnsPerHost:        h.MaxConnsPerHost,
		ResponseHeaderTimeout:  time.Duration(h.ResponseHeaderTimeout),
		ExpectContinueTimeout:  time.Duration(h.ExpectContinueTimeout),
		MaxResponseHeaderBytes: h.MaxResponseHeaderSize,
		WriteBufferSize:        h.WriteBufferSize,
		ReadBufferSize:         h.ReadBufferSize,
	}

	if h.TLS != nil {
		rt.TLSHandshakeTimeout = time.Duration(h.TLS.HandshakeTimeout)

		var err error
		rt.TLSClientConfig, err = h.TLS.MakeTLSClientConfig()
		if err != nil {
			return nil, fmt.Errorf("making TLS client config: %v", err)
		}
	}

	if h.KeepAlive != nil {
		dialer.KeepAlive = time.Duration(h.KeepAlive.ProbeInterval)
		if h.KeepAlive.Enabled != nil {
			rt.DisableKeepAlives = !*h.KeepAlive.Enabled
		}
		rt.MaxIdleConns = h.KeepAlive.MaxIdleConns
		rt.MaxIdleConnsPerHost = h.KeepAlive.MaxIdleConnsPerHost
		rt.IdleConnTimeout = time.Duration(h.KeepAlive.IdleConnTimeout)
	}

	if h.Compression != nil {
		rt.DisableCompression = !*h.Compression
	}

	if sliceContains(h.Versions, "2") {
		if err := http2.ConfigureTransport(rt); err != nil {
			return nil, err
		}
	}

	return rt, nil
}

// RoundTrip implements http.RoundTripper.
func (h *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	h.setScheme(req)
	return h.Transport.RoundTrip(req)
}

// setScheme ensures that the outbound request req
// has the scheme set in its URL; the underlying
// http.Transport requires a scheme to be set.
func (h *HTTPTransport) setScheme(req *http.Request) {
	if req.URL.Scheme == "" {
		req.URL.Scheme = "http"
		if h.TLS != nil {
			req.URL.Scheme = "https"
		}
	}
}

// Cleanup implements caddy.CleanerUpper and closes any idle connections.
func (h HTTPTransport) Cleanup() error {
	if h.Transport == nil {
		return nil
	}
	h.Transport.CloseIdleConnections()
	return nil
}

// TLSConfig holds configuration related to the
// TLS configuration for the transport/client.
type TLSConfig struct {
	RootCAPool []string `json:"root_ca_pool,omitempty"`
	// Added to the same pool as above, but brought in from files
	RootCAPEMFiles []string `json:"root_ca_pem_files,omitempty"`
	// TODO: Should the client cert+key config use caddytls.CertificateLoader modules?
	ClientCertificateFile    string         `json:"client_certificate_file,omitempty"`
	ClientCertificateKeyFile string         `json:"client_certificate_key_file,omitempty"`
	InsecureSkipVerify       bool           `json:"insecure_skip_verify,omitempty"`
	HandshakeTimeout         caddy.Duration `json:"handshake_timeout,omitempty"`
	ServerName               string         `json:"server_name,omitempty"`
}

// MakeTLSClientConfig returns a tls.Config usable by a client to a backend.
// If there is no custom TLS configuration, a nil config may be returned.
func (t TLSConfig) MakeTLSClientConfig() (*tls.Config, error) {
	cfg := new(tls.Config)

	// client auth
	if t.ClientCertificateFile != "" && t.ClientCertificateKeyFile == "" {
		return nil, fmt.Errorf("client_certificate_file specified without client_certificate_key_file")
	}
	if t.ClientCertificateFile == "" && t.ClientCertificateKeyFile != "" {
		return nil, fmt.Errorf("client_certificate_key_file specified without client_certificate_file")
	}
	if t.ClientCertificateFile != "" && t.ClientCertificateKeyFile != "" {
		cert, err := tls.LoadX509KeyPair(t.ClientCertificateFile, t.ClientCertificateKeyFile)
		if err != nil {
			return nil, fmt.Errorf("loading client certificate key pair: %v", err)
		}
		cfg.Certificates = []tls.Certificate{cert}
	}

	// trusted root CAs
	if len(t.RootCAPool) > 0 || len(t.RootCAPEMFiles) > 0 {
		rootPool := x509.NewCertPool()
		for _, encodedCACert := range t.RootCAPool {
			caCert, err := decodeBase64DERCert(encodedCACert)
			if err != nil {
				return nil, fmt.Errorf("parsing CA certificate: %v", err)
			}
			rootPool.AddCert(caCert)
		}
		for _, pemFile := range t.RootCAPEMFiles {
			pemData, err := ioutil.ReadFile(pemFile)
			if err != nil {
				return nil, fmt.Errorf("failed reading ca cert: %v", err)
			}
			rootPool.AppendCertsFromPEM(pemData)

		}
		cfg.RootCAs = rootPool
	}

	// custom SNI
	cfg.ServerName = t.ServerName

	// throw all security out the window
	cfg.InsecureSkipVerify = t.InsecureSkipVerify

	// only return a config if it's not empty
	if reflect.DeepEqual(cfg, new(tls.Config)) {
		return nil, nil
	}

	return cfg, nil
}

// KeepAlive holds configuration pertaining to HTTP Keep-Alive.
type KeepAlive struct {
	Enabled             *bool          `json:"enabled,omitempty"`
	ProbeInterval       caddy.Duration `json:"probe_interval,omitempty"`
	MaxIdleConns        int            `json:"max_idle_conns,omitempty"`
	MaxIdleConnsPerHost int            `json:"max_idle_conns_per_host,omitempty"`
	IdleConnTimeout     caddy.Duration `json:"idle_timeout,omitempty"` // how long should connections be kept alive when idle
}

// decodeBase64DERCert base64-decodes, then DER-decodes, certStr.
func decodeBase64DERCert(certStr string) (*x509.Certificate, error) {
	// decode base64
	derBytes, err := base64.StdEncoding.DecodeString(certStr)
	if err != nil {
		return nil, err
	}

	// parse the DER-encoded certificate
	return x509.ParseCertificate(derBytes)
}

// sliceContains returns true if needle is in haystack.
func sliceContains(haystack []string, needle string) bool {
	for _, s := range haystack {
		if s == needle {
			return true
		}
	}
	return false
}

// Interface guards
var (
	_ caddy.Provisioner  = (*HTTPTransport)(nil)
	_ http.RoundTripper  = (*HTTPTransport)(nil)
	_ caddy.CleanerUpper = (*HTTPTransport)(nil)
)
<|endoftext|>


<commit_before>package glplus

import (
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"image/png"
	"io"
	"io/ioutil"
	"os"
	"path"
	"runtime"

	"github.com/go-gl/mathgl/mgl32"
	"github.com/golang/freetype"
	"github.com/golang/freetype/truetype"
	ifont "golang.org/x/image/font"
	"golang.org/x/image/math/fixed"
)

var (
	// fragment shader
	fragShaderFont = `#version 330
  VARYINGIN vec2 out_uvs;
  uniform sampler2D tex1;
  uniform vec4 color;
  uniform vec4 bg;
	COLOROUT

  void main()
  {
    vec4 col0 = TEXTURE2D(tex1, out_uvs);
    FRAGCOLOR = col0.r * color;
    // Porter duff gl.ONE, gl.ONE_MINUS_SRC_ALPHA
    FRAGCOLOR = vec4(FRAGCOLOR.r + bg.r * (1.0-FRAGCOLOR.a), FRAGCOLOR.g + bg.g * (1.0-FRAGCOLOR.a), FRAGCOLOR.b + bg.b * (1.0-FRAGCOLOR.a), FRAGCOLOR.a + bg.a * (1-FRAGCOLOR.a));
  }`

	// vertex shader
	vertShaderFont = `#version 330
  ATTRIBUTE vec4 position;
  ATTRIBUTE vec2 uvs;
  VARYINGOUT vec2 out_uvs;
  uniform mat3 ModelviewMatrix;
  void main()
  {
		gl_Position = vec4(ModelviewMatrix * vec3(position.xy, 1.0), 0.0).xywz;
  	out_uvs = uvs;
  }`
)

// Char ...
type Char struct {
	Index int
	X     int
	Y     int
}

// String ...
type String struct {
	Chars []Char
	Size  image.Point
	vbo   *VBO
	font  *Font
}

// DeleteString ...
func (s *String) DeleteString() {
	if s.vbo != nil {
		s.vbo.DeleteVBO()
	}
}

// Draw ...
func (s *String) Draw(f *Font, color [4]float32, bg [4]float32, mat mgl32.Mat3, scale float32, offsetX float32, offsetY float32) (err error) {
	if f.program == nil {
		var attribs = []string{
			"position",
			"uvs",
		}
		if f.program, err = LoadShaderProgram(vertShaderFont, fragShaderFont, attribs); err != nil {
			return (err)
		}
	}
	if s.vbo == nil {
		s.createVertexBuffer(f)
	}

	f.program.UseProgram()
	f.texture.BindTexture(0)
	s.vbo.Bind(f.program)

	var matrixfont = mat.Mul3(mgl32.Scale2D(scale, scale))
	matrixfont = matrixfont.Mul3(mgl32.Translate2D(offsetX, offsetY))
	f.program.ProgramUniformMatrix3fv("ModelviewMatrix", matrixfont)
	f.program.ProgramUniform1i("tex1", 0)
	f.program.ProgramUniform4fv("color", color)
	f.program.ProgramUniform4fv("bg", bg)

	if err = f.program.ValidateProgram(); err != nil {
		return err
	}

	s.vbo.Draw()

	f.texture.UnbindTexture(0)
	s.vbo.Unbind(f.program)
	f.program.UnuseProgram()

	return nil
}

func (s *String) createVertexBuffer(f *Font) {
	n := len(s.Chars)

	verts := make([]float32, n*20)
	indices := make([]uint32, n*6)

	/*
		verts := [...]float32{
			x, y, 0.0, 0, 0,
			x + w, y, 0.0, 1, 0,
			x + w, y + h, 0.0, 1, 1,
			x, y + h, 0.0, 0, 1,
		}

		indices := [...]uint32{
			0, 1, 2,
			2, 3, 0,
		}
	*/
	var curX float32
	i := 0
	ii := 0
	var jj uint32
	var dv = float32(f.cellssize) / float32(f.texture.Size.Y)
	for j := 0; j < n; j++ {
		var c = s.Chars[j]
		var x = curX
		var y float32
		var w = float32(f.advances[c.Index])
		var h = float32(f.cellssize)
		var u = float32(c.X*f.cellssize) / float32(f.texture.Size.X)
		var v = float32(c.Y*f.cellssize) / float32(f.texture.Size.Y)
		var du = float32(w) / float32(f.texture.Size.X)

		verts[i+0] = x
		verts[i+1] = y
		verts[i+2] = 0
		verts[i+3] = u
		verts[i+4] = v
		i += 5

		verts[i+0] = x + w
		verts[i+1] = y
		verts[i+2] = 0
		verts[i+3] = u + du
		verts[i+4] = v
		i += 5

		verts[i+0] = x + w
		verts[i+1] = y + h
		verts[i+2] = 0
		verts[i+3] = u + du
		verts[i+4] = v + dv
		i += 5

		verts[i+0] = x
		verts[i+1] = y + h
		verts[i+2] = 0
		verts[i+3] = u
		verts[i+4] = v + dv
		i += 5

		indices[ii+0] = 0 + jj
		indices[ii+1] = 1 + jj
		indices[ii+2] = 2 + jj
		indices[ii+3] = 2 + jj
		indices[ii+4] = 3 + jj
		indices[ii+5] = 0 + jj
		ii += 6
		jj += 4

		curX += w
	}

	opt := DefaultVBOOptions()
	opt.Quads = n
	s.vbo = NewVBO(f.program, opt, verts[:], indices[:])
}

// Font ...
type Font struct {
	texture   *GPTexture
	program   *GPProgram
	rows      int
	cellssize int
	advances  []int
}

// DeleteFont ...
func (f *Font) DeleteFont() {
	if f.texture != nil {
		f.texture.DeleteTexture()
	}
	if f.program != nil {
		f.program.DeleteProgram()
	}
}

// BindTexture ...
func (f *Font) BindTexture(unit int) {
	f.texture.BindTexture(unit)
}

// UnbindTexture ...
func (f *Font) UnbindTexture(unit int) {
	f.texture.UnbindTexture(unit)
}

// NewString ...
func (f *Font) NewString(s string) *String {
	var result = &String{
		Chars: make([]Char, len(s)),
		Size:  image.Point{0, 0},
		font:  f,
	}
	var width int
	for i := 0; i < len(s); i++ {
		var ascii = int(s[i])
		var index = ascii - 32
		var xoff = index % f.rows
		var yoff = index / f.rows
		width += f.advances[index]

		//fmt.Printf("ascii: %d, x: %d, y: %d\n", ascii, xoff, yoff)
		result.Chars[i].Index = index
		result.Chars[i].X = xoff
		result.Chars[i].Y = yoff

	}
	result.Size = image.Point{width, f.cellssize}
	return result
}

// FreeSerif ...
func FreeSerif() (*os.File, error) {
	_, filename, _, ok := runtime.Caller(0)
	if !ok {
		return nil, fmt.Errorf("%s", "No caller information")
	}

	return os.Open(path.Join(path.Dir(filename), "FreeSerif.ttf"))
}

// NewFont ...
func NewFont(reader io.Reader) (font *Font, err error) {

	// Read the font data.
	var fontBytes []byte
	if fontBytes, err = ioutil.ReadAll(reader); err != nil {
		return nil, err
	}
	var f *truetype.Font
	if f, err = freetype.ParseFont(fontBytes); err != nil {
		return nil, err
	}
	const fontSize = 48
	var face ifont.Face
	face = truetype.NewFace(f, &truetype.Options{Size: fontSize})
	height := face.Metrics().Height.Round()
	descent := face.Metrics().Descent.Round()
	fmt.Printf("Height: %d\n", height)

	dst := image.NewRGBA(image.Rect(0, 0, height*16, height*16))
	black := color.RGBA{0, 0, 0, 255}
	draw.Draw(dst, dst.Bounds(), &image.Uniform{black}, image.ZP, draw.Src)

	d := &ifont.Drawer{
		Dst:  dst,
		Src:  image.White,
		Face: face,
	}

	var advances = make([]int, 256-32)
	var offx int
	var offy = height
	for i := 32; i < 255; i++ {
		d.Dot = fixed.P(offx, offy-descent)
		var strc = string(i)
		d.DrawString(strc)
		if advance, ok := face.GlyphAdvance(rune(strc[0])); ok {
			advances[i-32] = advance.Round()
		} else {
			advances[i-32] = 0
		}

		offx += height
		if offx >= height*16 {
			offy += height
			offx = 0
		}
	}

	if false {
		w, _ := os.Create("font.png")
		defer w.Close()
		png.Encode(w, dst) //Encode writes the Image m to w in PNG format.
	}

	gray := image.NewGray(dst.Bounds())
	if gray.Stride != gray.Rect.Size().X {
		return nil, fmt.Errorf("unsupported stride")
	}
	draw.Draw(gray, gray.Bounds(), dst, image.Point{0, 0}, draw.Src)

	var texture = GenTexture(gray.Rect.Size())

	texture.BindTexture(0)
	Gl.TexParameteri(Gl.TEXTURE_2D, Gl.TEXTURE_MIN_FILTER, Gl.LINEAR)
	Gl.TexParameteri(Gl.TEXTURE_2D, Gl.TEXTURE_MAG_FILTER, Gl.LINEAR)
	Gl.TexParameteri(Gl.TEXTURE_2D, Gl.TEXTURE_WRAP_S, Gl.CLAMP_TO_EDGE)
	Gl.TexParameteri(Gl.TEXTURE_2D, Gl.TEXTURE_WRAP_T, Gl.CLAMP_TO_EDGE)
	Gl.TexImage2D(
		Gl.TEXTURE_2D,
		0,
		Gl.R8,
		gray.Rect.Size().X,
		gray.Rect.Size().Y,
		Gl.RED,
		Gl.UNSIGNED_BYTE,
		gray.Pix)

	font = &Font{
		texture:   texture,
		rows:      16,
		cellssize: gray.Rect.Size().X / 16,
		advances:  advances,
	}

	return font, nil
}
<commit_msg>Font exports<commit_after>package glplus

import (
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"image/png"
	"io"
	"io/ioutil"
	"os"
	"path"
	"runtime"

	"github.com/go-gl/mathgl/mgl32"
	"github.com/golang/freetype"
	"github.com/golang/freetype/truetype"
	ifont "golang.org/x/image/font"
	"golang.org/x/image/math/fixed"
)

var (
	// fragment shader
	fragShaderFont = `#version 330
  VARYINGIN vec2 out_uvs;
  uniform sampler2D tex1;
  uniform vec4 color;
  uniform vec4 bg;
	COLOROUT

  void main()
  {
    vec4 col0 = TEXTURE2D(tex1, out_uvs);
    FRAGCOLOR = col0.r * color;
    // Porter duff gl.ONE, gl.ONE_MINUS_SRC_ALPHA
    FRAGCOLOR = vec4(FRAGCOLOR.r + bg.r * (1.0-FRAGCOLOR.a), FRAGCOLOR.g + bg.g * (1.0-FRAGCOLOR.a), FRAGCOLOR.b + bg.b * (1.0-FRAGCOLOR.a), FRAGCOLOR.a + bg.a * (1-FRAGCOLOR.a));
  }`

	// vertex shader
	vertShaderFont = `#version 330
  ATTRIBUTE vec4 position;
  ATTRIBUTE vec2 uvs;
  VARYINGOUT vec2 out_uvs;
  uniform mat3 ModelviewMatrix;
  void main()
  {
		gl_Position = vec4(ModelviewMatrix * vec3(position.xy, 1.0), 0.0).xywz;
  	out_uvs = uvs;
  }`
)

// Char ...
type Char struct {
	Index int
	X     int
	Y     int
}

// String ...
type String struct {
	Chars []Char
	Size  image.Point
	vbo   *VBO
	font  *Font
}

// DeleteString ...
func (s *String) DeleteString() {
	if s.vbo != nil {
		s.vbo.DeleteVBO()
	}
}

// Draw ...
func (s *String) Draw(f *Font, color [4]float32, bg [4]float32, mat mgl32.Mat3, scale float32, offsetX float32, offsetY float32) (err error) {
	if f.program == nil {
		var attribs = []string{
			"position",
			"uvs",
		}
		if f.program, err = LoadShaderProgram(vertShaderFont, fragShaderFont, attribs); err != nil {
			return (err)
		}
	}
	if s.vbo == nil {
		s.createVertexBuffer(f)
	}

	f.program.UseProgram()
	f.Texture.BindTexture(0)
	s.vbo.Bind(f.program)

	var matrixfont = mat.Mul3(mgl32.Scale2D(scale, scale))
	matrixfont = matrixfont.Mul3(mgl32.Translate2D(offsetX, offsetY))
	f.program.ProgramUniformMatrix3fv("ModelviewMatrix", matrixfont)
	f.program.ProgramUniform1i("tex1", 0)
	f.program.ProgramUniform4fv("color", color)
	f.program.ProgramUniform4fv("bg", bg)

	if err = f.program.ValidateProgram(); err != nil {
		return err
	}

	s.vbo.Draw()

	f.Texture.UnbindTexture(0)
	s.vbo.Unbind(f.program)
	f.program.UnuseProgram()

	return nil
}

func (s *String) createVertexBuffer(f *Font) {
	n := len(s.Chars)

	verts := make([]float32, n*20)
	indices := make([]uint32, n*6)

	/*
		verts := [...]float32{
			x, y, 0.0, 0, 0,
			x + w, y, 0.0, 1, 0,
			x + w, y + h, 0.0, 1, 1,
			x, y + h, 0.0, 0, 1,
		}

		indices := [...]uint32{
			0, 1, 2,
			2, 3, 0,
		}
	*/
	var curX float32
	i := 0
	ii := 0
	var jj uint32
	var dv = float32(f.Cellssize) / float32(f.Texture.Size.Y)
	for j := 0; j < n; j++ {
		var c = s.Chars[j]
		var x = curX
		var y float32
		var w = float32(f.Advances[c.Index])
		var h = float32(f.Cellssize)
		var u = float32(c.X*f.Cellssize) / float32(f.Texture.Size.X)
		var v = float32(c.Y*f.Cellssize) / float32(f.Texture.Size.Y)
		var du = float32(w) / float32(f.Texture.Size.X)

		verts[i+0] = x
		verts[i+1] = y
		verts[i+2] = 0
		verts[i+3] = u
		verts[i+4] = v
		i += 5

		verts[i+0] = x + w
		verts[i+1] = y
		verts[i+2] = 0
		verts[i+3] = u + du
		verts[i+4] = v
		i += 5

		verts[i+0] = x + w
		verts[i+1] = y + h
		verts[i+2] = 0
		verts[i+3] = u + du
		verts[i+4] = v + dv
		i += 5

		verts[i+0] = x
		verts[i+1] = y + h
		verts[i+2] = 0
		verts[i+3] = u
		verts[i+4] = v + dv
		i += 5

		indices[ii+0] = 0 + jj
		indices[ii+1] = 1 + jj
		indices[ii+2] = 2 + jj
		indices[ii+3] = 2 + jj
		indices[ii+4] = 3 + jj
		indices[ii+5] = 0 + jj
		ii += 6
		jj += 4

		curX += w
	}

	opt := DefaultVBOOptions()
	opt.Quads = n
	s.vbo = NewVBO(f.program, opt, verts[:], indices[:])
}

// Font ...
type Font struct {
	Texture   *GPTexture
	Cellssize int
	Advances  []int

	program *GPProgram
	rows    int
}

// DeleteFont ...
func (f *Font) DeleteFont() {
	if f.Texture != nil {
		f.Texture.DeleteTexture()
	}
	if f.program != nil {
		f.program.DeleteProgram()
	}
}

// BindTexture ...
func (f *Font) BindTexture(unit int) {
	f.Texture.BindTexture(unit)
}

// UnbindTexture ...
func (f *Font) UnbindTexture(unit int) {
	f.Texture.UnbindTexture(unit)
}

// NewString ...
func (f *Font) NewString(s string) *String {
	var result = &String{
		Chars: make([]Char, len(s)),
		Size:  image.Point{0, 0},
		font:  f,
	}
	var width int
	for i := 0; i < len(s); i++ {
		var ascii = int(s[i])
		var index = ascii - 32
		var xoff = index % f.rows
		var yoff = index / f.rows
		width += f.Advances[index]

		//fmt.Printf("ascii: %d, x: %d, y: %d\n", ascii, xoff, yoff)
		result.Chars[i].Index = index
		result.Chars[i].X = xoff
		result.Chars[i].Y = yoff

	}
	result.Size = image.Point{width, f.Cellssize}
	return result
}

// FreeSerif ...
func FreeSerif() (*os.File, error) {
	_, filename, _, ok := runtime.Caller(0)
	if !ok {
		return nil, fmt.Errorf("%s", "No caller information")
	}

	return os.Open(path.Join(path.Dir(filename), "FreeSerif.ttf"))
}

// NewFont ...
func NewFont(reader io.Reader) (font *Font, err error) {

	// Read the font data.
	var fontBytes []byte
	if fontBytes, err = ioutil.ReadAll(reader); err != nil {
		return nil, err
	}
	var f *truetype.Font
	if f, err = freetype.ParseFont(fontBytes); err != nil {
		return nil, err
	}
	const fontSize = 48
	var face ifont.Face
	face = truetype.NewFace(f, &truetype.Options{Size: fontSize})
	height := face.Metrics().Height.Round()
	descent := face.Metrics().Descent.Round()
	fmt.Printf("Height: %d\n", height)

	dst := image.NewRGBA(image.Rect(0, 0, height*16, height*16))
	black := color.RGBA{0, 0, 0, 255}
	draw.Draw(dst, dst.Bounds(), &image.Uniform{black}, image.ZP, draw.Src)

	d := &ifont.Drawer{
		Dst:  dst,
		Src:  image.White,
		Face: face,
	}

	var advances = make([]int, 256-32)
	var offx int
	var offy = height
	for i := 32; i < 255; i++ {
		d.Dot = fixed.P(offx, offy-descent)
		var strc = string(i)
		d.DrawString(strc)
		if advance, ok := face.GlyphAdvance(rune(strc[0])); ok {
			advances[i-32] = advance.Round()
		} else {
			advances[i-32] = 0
		}

		offx += height
		if offx >= height*16 {
			offy += height
			offx = 0
		}
	}

	if false {
		w, _ := os.Create("font.png")
		defer w.Close()
		png.Encode(w, dst) //Encode writes the Image m to w in PNG format.
	}

	gray := image.NewGray(dst.Bounds())
	if gray.Stride != gray.Rect.Size().X {
		return nil, fmt.Errorf("unsupported stride")
	}
	draw.Draw(gray, gray.Bounds(), dst, image.Point{0, 0}, draw.Src)

	var texture = GenTexture(gray.Rect.Size())

	texture.BindTexture(0)
	Gl.TexParameteri(Gl.TEXTURE_2D, Gl.TEXTURE_MIN_FILTER, Gl.LINEAR)
	Gl.TexParameteri(Gl.TEXTURE_2D, Gl.TEXTURE_MAG_FILTER, Gl.LINEAR)
	Gl.TexParameteri(Gl.TEXTURE_2D, Gl.TEXTURE_WRAP_S, Gl.CLAMP_TO_EDGE)
	Gl.TexParameteri(Gl.TEXTURE_2D, Gl.TEXTURE_WRAP_T, Gl.CLAMP_TO_EDGE)
	Gl.TexImage2D(
		Gl.TEXTURE_2D,
		0,
		Gl.R8,
		gray.Rect.Size().X,
		gray.Rect.Size().Y,
		Gl.RED,
		Gl.UNSIGNED_BYTE,
		gray.Pix)

	font = &Font{
		Texture:   texture,
		rows:      16,
		Cellssize: gray.Rect.Size().X / 16,
		Advances:  advances,
	}

	return font, nil
}
<|endoftext|>


<commit_before>package dependency

import (
	"bytes"
	"crypto/sha1"
	"encoding/base64"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/gemnasium/depfile"
	"github.com/gemnasium/toolbelt/api"
	"github.com/gemnasium/toolbelt/config"
	"github.com/gemnasium/toolbelt/project"
	"github.com/olekukonko/tablewriter"
)

func NewDependencyFile(filePath string) *api.DependencyFile {
	content, err := ioutil.ReadFile(filePath)
	if err != nil {
		return nil
	}
	sha, err := GetFileSHA1(filePath)
	if err != nil {
		return nil
	}
	return &api.DependencyFile{Path: filePath, SHA: sha, Content: content}
}

func DependencyFileCheckFileSHA1(df *api.DependencyFile) error {
	sum, err := GetFileSHA1(df.Path)
	if err != nil {
		return err
	}

	if sum != df.SHA {
		return fmt.Errorf("%s: File signature doesn't match (expected: %s, got: %s)", df.Path, df.SHA, sum)
	}
	return nil
}

func DependencyFileUpdateSHA(df *api.DependencyFile) error {
	sha, err := GetFileSHA1(df.Path)
	if err != nil {
		return err
	}
	df.SHA = sha
	return nil
}

func DependencyFileUpdate(df *api.DependencyFile) error {
	content, err := ioutil.ReadFile(df.Path)
	if err != nil {
		return err
	}
	df.Content = content
	err = DependencyFileUpdateSHA(df)
	if err != nil {
		return err
	}

	return nil
}

// Apply patch to the file referenced by Path
// If Content is empty, the file content is read from the file directly
func DependencyFilePatch(df *api.DependencyFile, patch string) error {
	patchPath, err := exec.LookPath("patch")
	if err != nil {
		return err
	}

	cmd := exec.Command(patchPath, df.Path)
	stdin, err := cmd.StdinPipe()
	if err != nil {
		return err
	}
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}
	if err = cmd.Start(); err != nil {
		return err
	}

	_, err = io.WriteString(stdin, patch)
	if err != nil {
		return err
	}
	stdin.Close()

	out, err := ioutil.ReadAll(stdout)
	if err != nil {
		return err
	}
	if err = cmd.Wait(); err != nil {
		fmt.Println(string(out))
		return err
	}

	err = DependencyFileUpdate(df)
	if err != nil {
		return err
	}
	return nil
}

// Return git SHA1 of the given file
// TODO: Make this generic (ie: working with SVN)
func GetFileSHA1(filePath string) (string, error) {
	dat, err := ioutil.ReadFile(filePath)
	if err != nil {
		return "", err
	}
	h := sha1.New()
	header := fmt.Sprintf("blob %d\x00", len(dat))
	io.WriteString(h, header)
	io.Copy(h, bytes.NewReader(dat))
	hash := h.Sum(nil)

	return fmt.Sprintf("%x", hash), nil
}

func ListDependencyFiles(p *api.Project) error {

	dfiles, err := project.ProjectDependencyFiles(p)
	if err != nil {
		return err
	}

	table := tablewriter.NewWriter(os.Stdout)
	table.SetHeader([]string{"Path", "SHA"})

	for _, df := range dfiles {
		table.Append([]string{df.Path, df.SHA})
	}
	table.Render() // Send output

	return nil
}

var getLocalDependencyFiles = func(path string) ([]*api.DependencyFile, error) {
	dfiles := []*api.DependencyFile{}
	excludeDirectory :=map[string]bool{
		"node_modules": true,
		".bundle": true,
		"vendor": true,
	}
	searchDeps := func(path string, info os.FileInfo, err error) error {
		// Skip excluded directories
		for _, pathComponent := range filepath.SplitList(path) {
			if excludeDirectory[pathComponent] {
				return filepath.SkipDir
			}
		}
		// Skip ignored_pathes
		if len(config.IgnoredPaths) > 0 {
			for _, path := range config.IgnoredPaths {
				matched, err := filepath.Match(filepath.Clean(path), info.Name())
				if err != nil {
					return err
				}

				if matched {
					fmt.Println("Skipping", info.Name())
					return filepath.SkipDir
				}
			}
		}

		if df := depfile.Find(path); df != nil {
			fmt.Printf("Found: %s (%s)\n", path, df.Name)
			dfiles = append(dfiles, NewDependencyFile(path))
		}
		return nil
	}
	// Walk the directory
	oldDir, err := os.Getwd()
	if err != nil {
		return nil, err
	}
	if err = os.Chdir(path) ; err != nil {
		return nil, err
	}
	defer os.Chdir(oldDir)
	err = filepath.Walk(".", searchDeps)
	if err != nil {
		return dfiles, err
	}
	return dfiles, nil
}

// Push project dependencies
// The current path will be scanned for supported dependency files.
func PushDependencyFiles(projectSlug string, files []string) error {
	dfiles, err := LookupDependencyFiles(files)
	if err != nil {
		return err
	}

	fmt.Printf("Sending files to Gemnasium: ")
	// API v1 and v2 returns completelly different informations
	switch a := api.APIImpl.(type) {
	case *api.APIv1:
		jsonResp, err := a.DependencyFilesPush(projectSlug, dfiles)
		if err != nil {
			return err
		}

		added := []string{}
		for _, df := range jsonResp["added"] {
			added = append(added, df.Path)
		}
		updated := []string{}
		for _, df := range jsonResp["updated"] {
			updated = append(updated, df.Path)
		}
		unchanged := []string{}
		for _, df := range jsonResp["unchanged"] {
			unchanged = append(unchanged, df.Path)
		}
		unsupported := []string{}
		for _, df := range jsonResp["unsupported"] {
			unsupported = append(unsupported, df.Path)
		}
		fmt.Printf("done.\n\n")
		fmt.Printf("Added: %s\n", strings.Join(added, ", "))
		fmt.Printf("Updated: %s\n", strings.Join(updated, ", "))
		fmt.Printf("Unchanged: %s\n", strings.Join(unchanged, ", "))
		fmt.Printf("Unsupported: %s\n", strings.Join(unsupported, ", "))
	case *api.V2ToV1:
		//Converts dfiles to v2
		v2dfiles := []*api.V2DependencyFile{}
		for _, dfile := range dfiles {
			v2dfile := api.V2DependencyFile{}
			api.V1DependencyFileToV2(dfile, &v2dfile)
			// Base64 encode content
			v2dfile.Content = base64.StdEncoding.EncodeToString([]byte(v2dfile.Content))
			v2dfiles = append(v2dfiles, &v2dfile)
		}
		jsonResp, err := a.APIv2.DependencyFilesPush(projectSlug, v2dfiles)
		if err != nil {
			return err
		}

		fmt.Printf("Commit SHA %s in branch %s has been pushed.\n", jsonResp.CommitSHA, jsonResp.Branch)
		fmt.Printf("done.\n\n")
	}

	return nil
}

// Load dependency files if files is not empty, otherwise search in the current
// path for files
func LookupDependencyFiles(files []string) (dfiles []*api.DependencyFile, err error) {
	if len(files) > 0 {
		for _, path := range files {
			df := NewDependencyFile(path)
			if df == nil {
				err = fmt.Errorf("Unable to read file: %s", path)
				return dfiles, err
			}
			dfiles = append(dfiles, df)
		}
	} else {
		fmt.Println("[warning] No files given, scanning current directory instead.")
		currentDir, err := os.Getwd()
		if err != nil {
			return dfiles, err
		}
		files, err := getLocalDependencyFiles(currentDir)
		if err != nil {
			return nil, err
		}
		dfiles = files
	}
	return dfiles, nil
}
<commit_msg>Don't change directory to project's path, just use the filepath.Walk path parameter.<commit_after>package dependency

import (
	"bytes"
	"crypto/sha1"
	"encoding/base64"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/gemnasium/depfile"
	"github.com/gemnasium/toolbelt/api"
	"github.com/gemnasium/toolbelt/config"
	"github.com/gemnasium/toolbelt/project"
	"github.com/olekukonko/tablewriter"
)

func NewDependencyFile(filePath string) *api.DependencyFile {
	content, err := ioutil.ReadFile(filePath)
	if err != nil {
		return nil
	}
	sha, err := GetFileSHA1(filePath)
	if err != nil {
		return nil
	}
	return &api.DependencyFile{Path: filePath, SHA: sha, Content: content}
}

func DependencyFileCheckFileSHA1(df *api.DependencyFile) error {
	sum, err := GetFileSHA1(df.Path)
	if err != nil {
		return err
	}

	if sum != df.SHA {
		return fmt.Errorf("%s: File signature doesn't match (expected: %s, got: %s)", df.Path, df.SHA, sum)
	}
	return nil
}

func DependencyFileUpdateSHA(df *api.DependencyFile) error {
	sha, err := GetFileSHA1(df.Path)
	if err != nil {
		return err
	}
	df.SHA = sha
	return nil
}

func DependencyFileUpdate(df *api.DependencyFile) error {
	content, err := ioutil.ReadFile(df.Path)
	if err != nil {
		return err
	}
	df.Content = content
	err = DependencyFileUpdateSHA(df)
	if err != nil {
		return err
	}

	return nil
}

// Apply patch to the file referenced by Path
// If Content is empty, the file content is read from the file directly
func DependencyFilePatch(df *api.DependencyFile, patch string) error {
	patchPath, err := exec.LookPath("patch")
	if err != nil {
		return err
	}

	cmd := exec.Command(patchPath, df.Path)
	stdin, err := cmd.StdinPipe()
	if err != nil {
		return err
	}
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}
	if err = cmd.Start(); err != nil {
		return err
	}

	_, err = io.WriteString(stdin, patch)
	if err != nil {
		return err
	}
	stdin.Close()

	out, err := ioutil.ReadAll(stdout)
	if err != nil {
		return err
	}
	if err = cmd.Wait(); err != nil {
		fmt.Println(string(out))
		return err
	}

	err = DependencyFileUpdate(df)
	if err != nil {
		return err
	}
	return nil
}

// Return git SHA1 of the given file
// TODO: Make this generic (ie: working with SVN)
func GetFileSHA1(filePath string) (string, error) {
	dat, err := ioutil.ReadFile(filePath)
	if err != nil {
		return "", err
	}
	h := sha1.New()
	header := fmt.Sprintf("blob %d\x00", len(dat))
	io.WriteString(h, header)
	io.Copy(h, bytes.NewReader(dat))
	hash := h.Sum(nil)

	return fmt.Sprintf("%x", hash), nil
}

func ListDependencyFiles(p *api.Project) error {

	dfiles, err := project.ProjectDependencyFiles(p)
	if err != nil {
		return err
	}

	table := tablewriter.NewWriter(os.Stdout)
	table.SetHeader([]string{"Path", "SHA"})

	for _, df := range dfiles {
		table.Append([]string{df.Path, df.SHA})
	}
	table.Render() // Send output

	return nil
}

var getLocalDependencyFiles = func(rootPath string) ([]*api.DependencyFile, error) {
	dfiles := []*api.DependencyFile{}
	excludeDirectory :=map[string]bool{
		"node_modules": true,
		".bundle": true,
		"vendor": true,
	}
	searchDeps := func(path string, info os.FileInfo, err error) error {
		// Get path relative to rootPath, we don't want to take wrongly into account
		// the elements of rootPath
		relativePath, err := filepath.Rel(rootPath, path)
		if err != nil {
			return err
		}
		// Skip excluded directories
		for _, pathComponent := range filepath.SplitList(relativePath) {
			if excludeDirectory[pathComponent] {
				return filepath.SkipDir
			}
		}
		// Skip ignored_pathes
		if len(config.IgnoredPaths) > 0 {
			for _, path := range config.IgnoredPaths {
				matched, err := filepath.Match(filepath.Clean(path), info.Name())
				if err != nil {
					return err
				}

				if matched {
					fmt.Println("Skipping", info.Name())
					return filepath.SkipDir
				}
			}
		}

		if df := depfile.Find(path); df != nil {
			fmt.Printf("Found: %s (%s)\n", relativePath, df.Name)
			dfile := NewDependencyFile(path)
			// Remove the rootPath from the path field of dfile to keep things clean.
			// we want pathes relative to the project's root
			dfile.Path, err = filepath.Rel(rootPath, dfile.Path)
			if err != nil {
				return err
			}
			dfiles = append(dfiles, dfile)
		}
		return nil
	}
	// Walk the directory
	err := filepath.Walk(rootPath, searchDeps)
	if err != nil {
		return dfiles, err
	}
	return dfiles, nil
}

// Push project dependencies
// The current path will be scanned for supported dependency files.
func PushDependencyFiles(projectSlug string, files []string) error {
	dfiles, err := LookupDependencyFiles(files)
	if err != nil {
		return err
	}

	fmt.Printf("Sending files to Gemnasium: ")
	// API v1 and v2 returns completelly different informations
	switch a := api.APIImpl.(type) {
	case *api.APIv1:
		jsonResp, err := a.DependencyFilesPush(projectSlug, dfiles)
		if err != nil {
			return err
		}

		added := []string{}
		for _, df := range jsonResp["added"] {
			added = append(added, df.Path)
		}
		updated := []string{}
		for _, df := range jsonResp["updated"] {
			updated = append(updated, df.Path)
		}
		unchanged := []string{}
		for _, df := range jsonResp["unchanged"] {
			unchanged = append(unchanged, df.Path)
		}
		unsupported := []string{}
		for _, df := range jsonResp["unsupported"] {
			unsupported = append(unsupported, df.Path)
		}
		fmt.Printf("done.\n\n")
		fmt.Printf("Added: %s\n", strings.Join(added, ", "))
		fmt.Printf("Updated: %s\n", strings.Join(updated, ", "))
		fmt.Printf("Unchanged: %s\n", strings.Join(unchanged, ", "))
		fmt.Printf("Unsupported: %s\n", strings.Join(unsupported, ", "))
	case *api.V2ToV1:
		//Converts dfiles to v2
		v2dfiles := []*api.V2DependencyFile{}
		for _, dfile := range dfiles {
			v2dfile := api.V2DependencyFile{}
			api.V1DependencyFileToV2(dfile, &v2dfile)
			// Base64 encode content
			v2dfile.Content = base64.StdEncoding.EncodeToString([]byte(v2dfile.Content))
			v2dfiles = append(v2dfiles, &v2dfile)
		}
		jsonResp, err := a.APIv2.DependencyFilesPush(projectSlug, v2dfiles)
		if err != nil {
			return err
		}

		fmt.Printf("Commit SHA %s in branch %s has been pushed.\n", jsonResp.CommitSHA, jsonResp.Branch)
		fmt.Printf("done.\n\n")
	}

	return nil
}

// Load dependency files if files is not empty, otherwise search in the current
// path for files
func LookupDependencyFiles(files []string) (dfiles []*api.DependencyFile, err error) {
	if len(files) > 0 {
		for _, path := range files {
			df := NewDependencyFile(path)
			if df == nil {
				err = fmt.Errorf("Unable to read file: %s", path)
				return dfiles, err
			}
			dfiles = append(dfiles, df)
		}
	} else {
		fmt.Println("[warning] No files given, scanning current directory instead.")
		currentDir, err := os.Getwd()
		if err != nil {
			return dfiles, err
		}
		files, err := getLocalDependencyFiles(currentDir)
		if err != nil {
			return nil, err
		}
		dfiles = files
	}
	return dfiles, nil
}
<|endoftext|>


<commit_before>package MQTTg

type MessageType uint8

const (
	Reserved_1 MessageType = iota
	Connect
	Connack
	Publish
	Puback
	Pubrec
	Pubrel
	Pubcomp
	Subscribe
	Suback
	Unsubscribe
	Unsuback
	Pingreq
	Pingresp
	Disconnect
	Reserved_2
)

type FixedHeader struct {
	Type         MessageType
	Dup          bool
	QoS          uint8
	Retain       bool
	RemainLength uint8
}

func NewFixedHeader(mType MessageType, dup bool, qos uint8, retain bool, length uint8) *FixedHeader {
	return &FixedHeader{
		Type:         mType,
		Dup:          dup,
		QoS:          qos,
		Retain:       retain,
		RemainLength: length,
	}
}

type VariableHeader interface {
	VHeaderParse(data []byte)
	VHeaderWire() ([]byte, error)
	VHeaderString() string
}

type Message interface {
	Parse(data []byte)
	GetWire() ([]byte, error)
	String() string
}

type ConnectFlag uint8

const (
	CleanSession ConnectFlag = 0x02
	WillFlag     ConnectFlag = 0x04
	WillQoS_0    ConnectFlag = 0x00
	WillQoS_1    ConnectFlag = 0x08
	WillQoS_2    ConnectFlag = 0x10
	WillQoS_3    ConnectFlag = 0x18
	WillRetain   ConnectFlag = 0x20
	Password     ConnectFlag = 0x40
	UserName     ConnectFlag = 0x80
)

type ConnectMessage struct {
	*FixedHeader
	ProtoName    string
	ProtoLevel   uint8
	ConnectFlags ConnectFlag
	KeepAlive    uint16
}

func NewConnectMessage(connectFlags ConnectFlags, keepAlive uint16) *ConnectMessage {

	return &ConnectMessage{
		FixedHeader: NewFixedHeader(
			Connect,
			false, 0, false,
			0, // TODO:check
		),
		ProtoName:    "MQTT",
		ProtoLevel:   4,
		ConnectFlags: connectFlags,
		KeepAlive:    keepAlive,
	}
}
<commit_msg>define connect return code<commit_after>package MQTTg

type MessageType uint8

const (
	Reserved_1 MessageType = iota
	Connect
	Connack
	Publish
	Puback
	Pubrec
	Pubrel
	Pubcomp
	Subscribe
	Suback
	Unsubscribe
	Unsuback
	Pingreq
	Pingresp
	Disconnect
	Reserved_2
)

type FixedHeader struct {
	Type         MessageType
	Dup          bool
	QoS          uint8
	Retain       bool
	RemainLength uint8
}

func NewFixedHeader(mType MessageType, dup bool, qos uint8, retain bool, length uint8) *FixedHeader {
	return &FixedHeader{
		Type:         mType,
		Dup:          dup,
		QoS:          qos,
		Retain:       retain,
		RemainLength: length,
	}
}

type VariableHeader interface {
	VHeaderParse(data []byte)
	VHeaderWire() ([]byte, error)
	VHeaderString() string
}

type Message interface {
	Parse(data []byte)
	GetWire() ([]byte, error)
	String() string
}

type ConnectFlag uint8

const (
	CleanSession ConnectFlag = 0x02
	WillFlag     ConnectFlag = 0x04
	WillQoS_0    ConnectFlag = 0x00
	WillQoS_1    ConnectFlag = 0x08
	WillQoS_2    ConnectFlag = 0x10
	WillQoS_3    ConnectFlag = 0x18
	WillRetain   ConnectFlag = 0x20
	Password     ConnectFlag = 0x40
	UserName     ConnectFlag = 0x80
)

type ConnectMessage struct {
	*FixedHeader
	ProtoName    string
	ProtoLevel   uint8
	ConnectFlags ConnectFlag
	KeepAlive    uint16
}

func NewConnectMessage(connectFlags ConnectFlags, keepAlive uint16) *ConnectMessage {

	return &ConnectMessage{
		FixedHeader: NewFixedHeader(
			Connect,
			false, 0, false,
			0, // TODO:check
		),
		ProtoName:    "MQTT",
		ProtoLevel:   4,
		ConnectFlags: connectFlags,
		KeepAlive:    keepAlive,
	}
}

type ConnectReturnCode uint8

const (
	Accepted ConnectReturnCode = iota
	UnacceptableProtocolVersion
	IdentifierRejected
	ServerUnavailable
	BadUserNameOrPassword
	NotAuthorized
)
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"log"

	"github.com/mrxinu/gosolar"
)

func main() {
	// define your connection parameters
	hostname := "localhost"
	username := "admin"
	password := ""

	// create a new gosolar client
	c := gosolar.NewClient(hostname, username, password, true)

	// query for the caption and
	res, err := c.Query("SELECT Caption, IPAddress FROM Orion.Nodes", nil)
	if err != nil {
		log.Fatal(err)
	}

	// create struct slice for the nodes
	nodes := []struct {
		NodeName  string `json:"caption"`
		IPAddress string `json:"ipaddress"`
	}{}

	// convert the JSON response
	if err := json.Unmarshal(res, &nodes); err != nil {
		log.Fatal(err)
	}

	// iterate over the nodes and display the information
	for _, n := range nodes {
		fmt.Printf("Device name: %v, IP Address: %v\n", n.NodeName, n.IPAddress)
	}
}
<commit_msg>Fixes issue #75 with typo in comment (#76)<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"log"

	"github.com/mrxinu/gosolar"
)

func main() {
	// define your connection parameters
	hostname := "localhost"
	username := "admin"
	password := ""

	// create a new gosolar client
	c := gosolar.NewClient(hostname, username, password, true)

	// query for the caption and get IP address for each node
	res, err := c.Query("SELECT Caption, IPAddress FROM Orion.Nodes", nil)
	if err != nil {
		log.Fatal(err)
	}

	// create struct slice for the nodes
	nodes := []struct {
		NodeName  string `json:"caption"`
		IPAddress string `json:"ipaddress"`
	}{}

	// convert the JSON response
	if err := json.Unmarshal(res, &nodes); err != nil {
		log.Fatal(err)
	}

	// iterate over the nodes and display the information
	for _, n := range nodes {
		fmt.Printf("Device name: %v, IP Address: %v\n", n.NodeName, n.IPAddress)
	}
}
<|endoftext|>


<commit_before>package puppetmasterless

import (
	"io/ioutil"
	"os"
	"strings"
	"testing"

	"github.com/mitchellh/packer/packer"
)

func testConfig() map[string]interface{} {
	tf, err := ioutil.TempFile("", "packer")
	if err != nil {
		panic(err)
	}

	return map[string]interface{}{
		"manifest_file": tf.Name(),
	}
}

func TestProvisioner_Impl(t *testing.T) {
	var raw interface{}
	raw = &Provisioner{}
	if _, ok := raw.(packer.Provisioner); !ok {
		t.Fatalf("must be a Provisioner")
	}
}

func TestProvisionerPrepare_hieraConfigPath(t *testing.T) {
	config := testConfig()

	delete(config, "hiera_config_path")
	p := new(Provisioner)
	err := p.Prepare(config)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Test with a good one
	tf, err := ioutil.TempFile("", "packer")
	if err != nil {
		t.Fatalf("error tempfile: %s", err)
	}
	defer os.Remove(tf.Name())

	config["hiera_config_path"] = tf.Name()
	p = new(Provisioner)
	err = p.Prepare(config)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
}

func TestProvisionerPrepare_manifestFile(t *testing.T) {
	config := testConfig()

	delete(config, "manifest_file")
	p := new(Provisioner)
	err := p.Prepare(config)
	if err == nil {
		t.Fatal("should be an error")
	}

	// Test with a good one
	tf, err := ioutil.TempFile("", "packer")
	if err != nil {
		t.Fatalf("error tempfile: %s", err)
	}
	defer os.Remove(tf.Name())

	config["manifest_file"] = tf.Name()
	p = new(Provisioner)
	err = p.Prepare(config)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
}

func TestProvisionerPrepare_manifestDir(t *testing.T) {
	config := testConfig()

	delete(config, "manifestdir")
	p := new(Provisioner)
	err := p.Prepare(config)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Test with a good one
	td, err := ioutil.TempDir("", "packer")
	if err != nil {
		t.Fatalf("error: %s", err)
	}
	defer os.RemoveAll(td)

	config["manifest_dir"] = td
	p = new(Provisioner)
	err = p.Prepare(config)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
}

func TestProvisionerPrepare_modulePaths(t *testing.T) {
	config := testConfig()

	delete(config, "module_paths")
	p := new(Provisioner)
	err := p.Prepare(config)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Test with bad paths
	config["module_paths"] = []string{"i-should-not-exist"}
	p = new(Provisioner)
	err = p.Prepare(config)
	if err == nil {
		t.Fatal("should be an error")
	}

	// Test with a good one
	td, err := ioutil.TempDir("", "packer")
	if err != nil {
		t.Fatalf("error: %s", err)
	}
	defer os.RemoveAll(td)

	config["module_paths"] = []string{td}
	p = new(Provisioner)
	err = p.Prepare(config)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
}

func TestProvisionerPrepare_facterFacts(t *testing.T) {
	config := testConfig()

	delete(config, "facter")
	p := new(Provisioner)
	err := p.Prepare(config)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Test with malformed fact
	config["facter"] = "fact=stringified"
	p = new(Provisioner)
	err = p.Prepare(config)
	if err == nil {
		t.Fatal("should be an error")
	}

	// Test with a good one
	td, err := ioutil.TempDir("", "packer")
	if err != nil {
		t.Fatalf("error: %s", err)
	}
	defer os.RemoveAll(td)

	facts := make(map[string]string)
	facts["fact_name"] = "fact_value"
	config["facter"] = facts

	p = new(Provisioner)
	err = p.Prepare(config)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Make sure the default facts are present
	delete(config, "facter")
	p = new(Provisioner)
	err = p.Prepare(config)
	if p.config.Facter == nil {
		t.Fatalf("err: Default facts are not set in the Puppet provisioner!")
	}
}

func TestProvisionerPrepare_extraArguments(t *testing.T) {
	config := testConfig()

	delete(config, "extra_arguments")
	p := new(Provisioner)
	err := p.Prepare(config)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Test with malformed fact
	config["extra_arguments"] = "{{}}"
	p = new(Provisioner)
	err = p.Prepare(config)
	if err == nil {
		t.Fatal("should be an error")
	}

	config["extra_arguments"] = []string{
		"arg",
	}

	p = new(Provisioner)
	err = p.Prepare(config)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
}

func TestProvisionerProvision_extraArguments(t *testing.T) {
	config := testConfig()
	ui := &packer.MachineReadableUi{
		Writer: ioutil.Discard,
	}
	comm := new(packer.MockCommunicator)

	extraArguments := []string{
		"--some-arg=yup",
		"--some-other-arg",
	}
	config["extra_arguments"] = extraArguments

	p := new(Provisioner)
	err := p.Prepare(config)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	err = p.Provision(ui, comm)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	expectedArgs := strings.Join(extraArguments, " ")

	if !strings.Contains(comm.StartCmd.Command, expectedArgs) {
		t.Fatalf("Command %q doesn't contain the expected arguments %q", comm.StartCmd.Command, expectedArgs)
	}
}
<commit_msg>Test for when the config parameter isn't passed<commit_after>package puppetmasterless

import (
	"io/ioutil"
	"os"
	"strings"
	"testing"

	"github.com/mitchellh/packer/packer"
)

func testConfig() map[string]interface{} {
	tf, err := ioutil.TempFile("", "packer")
	if err != nil {
		panic(err)
	}

	return map[string]interface{}{
		"manifest_file": tf.Name(),
	}
}

func TestProvisioner_Impl(t *testing.T) {
	var raw interface{}
	raw = &Provisioner{}
	if _, ok := raw.(packer.Provisioner); !ok {
		t.Fatalf("must be a Provisioner")
	}
}

func TestProvisionerPrepare_hieraConfigPath(t *testing.T) {
	config := testConfig()

	delete(config, "hiera_config_path")
	p := new(Provisioner)
	err := p.Prepare(config)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Test with a good one
	tf, err := ioutil.TempFile("", "packer")
	if err != nil {
		t.Fatalf("error tempfile: %s", err)
	}
	defer os.Remove(tf.Name())

	config["hiera_config_path"] = tf.Name()
	p = new(Provisioner)
	err = p.Prepare(config)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
}

func TestProvisionerPrepare_manifestFile(t *testing.T) {
	config := testConfig()

	delete(config, "manifest_file")
	p := new(Provisioner)
	err := p.Prepare(config)
	if err == nil {
		t.Fatal("should be an error")
	}

	// Test with a good one
	tf, err := ioutil.TempFile("", "packer")
	if err != nil {
		t.Fatalf("error tempfile: %s", err)
	}
	defer os.Remove(tf.Name())

	config["manifest_file"] = tf.Name()
	p = new(Provisioner)
	err = p.Prepare(config)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
}

func TestProvisionerPrepare_manifestDir(t *testing.T) {
	config := testConfig()

	delete(config, "manifestdir")
	p := new(Provisioner)
	err := p.Prepare(config)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Test with a good one
	td, err := ioutil.TempDir("", "packer")
	if err != nil {
		t.Fatalf("error: %s", err)
	}
	defer os.RemoveAll(td)

	config["manifest_dir"] = td
	p = new(Provisioner)
	err = p.Prepare(config)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
}

func TestProvisionerPrepare_modulePaths(t *testing.T) {
	config := testConfig()

	delete(config, "module_paths")
	p := new(Provisioner)
	err := p.Prepare(config)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Test with bad paths
	config["module_paths"] = []string{"i-should-not-exist"}
	p = new(Provisioner)
	err = p.Prepare(config)
	if err == nil {
		t.Fatal("should be an error")
	}

	// Test with a good one
	td, err := ioutil.TempDir("", "packer")
	if err != nil {
		t.Fatalf("error: %s", err)
	}
	defer os.RemoveAll(td)

	config["module_paths"] = []string{td}
	p = new(Provisioner)
	err = p.Prepare(config)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
}

func TestProvisionerPrepare_facterFacts(t *testing.T) {
	config := testConfig()

	delete(config, "facter")
	p := new(Provisioner)
	err := p.Prepare(config)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Test with malformed fact
	config["facter"] = "fact=stringified"
	p = new(Provisioner)
	err = p.Prepare(config)
	if err == nil {
		t.Fatal("should be an error")
	}

	// Test with a good one
	td, err := ioutil.TempDir("", "packer")
	if err != nil {
		t.Fatalf("error: %s", err)
	}
	defer os.RemoveAll(td)

	facts := make(map[string]string)
	facts["fact_name"] = "fact_value"
	config["facter"] = facts

	p = new(Provisioner)
	err = p.Prepare(config)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Make sure the default facts are present
	delete(config, "facter")
	p = new(Provisioner)
	err = p.Prepare(config)
	if p.config.Facter == nil {
		t.Fatalf("err: Default facts are not set in the Puppet provisioner!")
	}
}

func TestProvisionerPrepare_extraArguments(t *testing.T) {
	config := testConfig()

	// Test with missing parameter
	delete(config, "extra_arguments")
	p := new(Provisioner)
	err := p.Prepare(config)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Test with malformed value
	config["extra_arguments"] = "{{}}"
	p = new(Provisioner)
	err = p.Prepare(config)
	if err == nil {
		t.Fatal("should be an error")
	}

	// Test with valid values
	config["extra_arguments"] = []string{
		"arg",
	}

	p = new(Provisioner)
	err = p.Prepare(config)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
}

func TestProvisionerProvision_extraArguments(t *testing.T) {
	config := testConfig()
	ui := &packer.MachineReadableUi{
		Writer: ioutil.Discard,
	}
	comm := new(packer.MockCommunicator)

	extraArguments := []string{
		"--some-arg=yup",
		"--some-other-arg",
	}
	config["extra_arguments"] = extraArguments

	// Test with valid values
	p := new(Provisioner)
	err := p.Prepare(config)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	err = p.Provision(ui, comm)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	expectedArgs := strings.Join(extraArguments, " ")

	if !strings.Contains(comm.StartCmd.Command, expectedArgs) {
		t.Fatalf("Command %q doesn't contain the expected arguments %q", comm.StartCmd.Command, expectedArgs)
	}

	// Test with missing parameter
	delete(config, "extra_arguments")

	p = new(Provisioner)
	err = p.Prepare(config)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	err = p.Provision(ui, comm)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Check the expected `extra_arguments` position for an empty value
	splitCommand := strings.Split(comm.StartCmd.Command, " ")
	if "" == splitCommand[len(splitCommand)-2] {
		t.Fatalf("Command %q contains an extra-space which may cause arg parsing issues", comm.StartCmd.Command)
	}
}
<|endoftext|>


<commit_before>// Package fs provides file system handleFuncs that can be used with kite
// library
package fs

import (
	"errors"
	"log"
	"os"
	"path"
	"sync"

	"github.com/howeyc/fsnotify"
	"github.com/koding/kite"
	"github.com/koding/kite/dnode"
)

var (
	// watcher variables
	once               sync.Once
	newPaths, oldPaths = make(chan string), make(chan string)

	// Limit of watching folders
	watchCallbacks = make(map[string]func(*fsnotify.FileEvent), 100)
	mu             sync.Mutex // protects watchCallbacks
)

func ReadDirectory(r *kite.Request) (interface{}, error) {
	var params struct {
		Path     string
		OnChange dnode.Function
	}

	if r.Args == nil {
		return nil, errors.New("arguments are not passed")
	}

	if r.Args.One().Unmarshal(&params) != nil || params.Path == "" {
		log.Println("params", params)
		return nil, errors.New("{ path: [string], onChange: [function]}")
	}

	response := make(map[string]interface{})

	if params.OnChange.IsValid() {
		onceBody := func() { startWatcher() }
		go once.Do(onceBody)

		// notify new paths to the watcher
		newPaths <- params.Path

		var eventType string
		var fileEntry *FileEntry

		changer := func(ev *fsnotify.FileEvent) {
			if ev.IsCreate() {
				eventType = "added"
				fileEntry, _ = getInfo(ev.Name)
			} else if ev.IsDelete() {
				eventType = "removed"
				fileEntry = NewFileEntry(path.Base(ev.Name), ev.Name)
			}

			event := map[string]interface{}{
				"event": eventType,
				"file":  fileEntry,
			}

			params.OnChange.Call(event)
			return
		}

		mu.Lock()
		watchCallbacks[params.Path] = changer
		mu.Unlock()

		removePath := func() {
			mu.Lock()
			delete(watchCallbacks, params.Path)
			mu.Unlock()

			oldPaths <- params.Path
		}

		// remove the path when the remote client disconnects
		r.Client.OnDisconnect(removePath)

		// this callback is called whenever we receive a 'stopWatching' from the client
		response["stopWatching"] = dnode.Callback(func(r *dnode.Partial) {
			removePath()
		})
	}

	files, err := readDirectory(params.Path)
	if err != nil {
		return nil, err
	}

	response["files"] = files
	return response, nil
}

func startWatcher() {
	var err error
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		log.Fatal(err)
	}

	go func() {
		for {
			select {
			case p := <-newPaths:
				err := watcher.Watch(p)
				if err != nil {
					log.Println("watch path adding", err)
				}
			case p := <-oldPaths:
				err := watcher.RemoveWatch(p)
				if err != nil {
					log.Println("watch remove adding", err)
				}
			}
		}
	}()

	for event := range watcher.Event {
		mu.Lock()
		f, ok := watchCallbacks[path.Dir(event.Name)]
		mu.Unlock()

		if !ok {
			continue
		}

		f(event)
	}
}

func Glob(r *kite.Request) (interface{}, error) {
	var params struct {
		Pattern string
	}

	if r.Args.One().Unmarshal(&params) != nil || params.Pattern == "" {
		return nil, errors.New("{ pattern: [string] }")
	}

	return glob(params.Pattern)
}

func ReadFile(r *kite.Request) (interface{}, error) {
	var params struct {
		Path string
	}

	if r.Args.One().Unmarshal(&params) != nil || params.Path == "" {
		return nil, errors.New("{ path: [string] }")
	}

	return readFile(params.Path)
}

type writeFileParams struct {
	Path           string
	Content        []byte
	DoNotOverwrite bool
	Append         bool
}

func WriteFile(r *kite.Request) (interface{}, error) {
	var params writeFileParams
	if r.Args.One().Unmarshal(&params) != nil || params.Path == "" {
		return nil, errors.New("{ path: [string] }")
	}

	return writeFile(params.Path, params.Content, params.DoNotOverwrite, params.Append)
}

func UniquePath(r *kite.Request) (interface{}, error) {
	var params struct {
		Path string
	}

	if r.Args.One().Unmarshal(&params) != nil || params.Path == "" {
		return nil, errors.New("{ path: [string] }")
	}

	return uniquePath(params.Path)
}

func GetInfo(r *kite.Request) (interface{}, error) {
	var params struct {
		Path string
	}

	if r.Args.One().Unmarshal(&params) != nil || params.Path == "" {
		return nil, errors.New("{ path: [string] }")
	}

	return getInfo(params.Path)
}

type setPermissionsParams struct {
	Path      string
	Mode      os.FileMode
	Recursive bool
}

func SetPermissions(r *kite.Request) (interface{}, error) {
	var params setPermissionsParams

	if r.Args.One().Unmarshal(&params) != nil || params.Path == "" {
		return nil, errors.New("{ path: [string], mode: [integer], recursive: [bool] }")
	}

	err := setPermissions(params.Path, params.Mode, params.Recursive)
	if err != nil {
		return nil, err
	}

	return true, nil
}

func Remove(r *kite.Request) (interface{}, error) {
	var params struct {
		Path      string
		Recursive bool
	}

	if r.Args.One().Unmarshal(&params) != nil || params.Path == "" {
		return nil, errors.New("{ path: [string], recursive: [bool] }")
	}

	if err := remove(params.Path, params.Recursive); err != nil {
		return nil, err
	}

	return true, nil
}

func Rename(r *kite.Request) (interface{}, error) {
	var params struct {
		OldPath string
		NewPath string
	}

	if r.Args.One().Unmarshal(&params) != nil || params.OldPath == "" || params.NewPath == "" {
		return nil, errors.New("{ oldPath: [string], newPath: [string] }")
	}

	err := rename(params.OldPath, params.NewPath)
	if err != nil {
		return nil, err
	}

	return true, nil
}

func CreateDirectory(r *kite.Request) (interface{}, error) {
	var params struct {
		Path      string
		Recursive bool
	}

	if r.Args.One().Unmarshal(&params) != nil || params.Path == "" {
		return nil, errors.New("{ path: [string], recursive: [bool] }")
	}

	err := createDirectory(params.Path, params.Recursive)
	if err != nil {
		return nil, err
	}

	return true, nil
}

func Move(r *kite.Request) (interface{}, error) {
	var params struct {
		OldPath string
		NewPath string
	}

	if r.Args.One().Unmarshal(&params) != nil || params.OldPath == "" || params.NewPath == "" {
		return nil, errors.New("{ oldPath: [string], newPath: [string] }")
	}

	err := rename(params.OldPath, params.NewPath)
	if err != nil {
		return nil, err
	}

	return true, nil
}

func Copy(r *kite.Request) (interface{}, error) {
	var params struct {
		SrcPath string
		DstPath string
	}

	if r.Args.One().Unmarshal(&params) != nil || params.SrcPath == "" || params.DstPath == "" {
		return nil, errors.New("{ srcPath: [string], dstPath: [string] }")
	}

	err := cp(params.SrcPath, params.DstPath)
	if err != nil {
		return nil, err
	}

	return true, nil
}
<commit_msg>kite-handler/fs: do not add multiple watchers for the same path<commit_after>// Package fs provides file system handleFuncs that can be used with kite
// library
package fs

import (
	"errors"
	"log"
	"os"
	"path"
	"sync"

	"github.com/howeyc/fsnotify"
	"github.com/koding/kite"
	"github.com/koding/kite/dnode"
)

var (
	// watcher variables
	once               sync.Once
	newPaths, oldPaths = make(chan string), make(chan string)

	// Limit of watching folders
	watchCallbacks = make(map[string]func(*fsnotify.FileEvent), 100)
	mu             sync.Mutex // protects watchCallbacks
)

func ReadDirectory(r *kite.Request) (interface{}, error) {
	var params struct {
		Path     string
		OnChange dnode.Function
	}

	if r.Args == nil {
		return nil, errors.New("arguments are not passed")
	}

	if r.Args.One().Unmarshal(&params) != nil || params.Path == "" {
		log.Println("params", params)
		return nil, errors.New("{ path: [string], onChange: [function]}")
	}

	response := make(map[string]interface{})

	if params.OnChange.IsValid() {
		onceBody := func() { startWatcher() }
		go once.Do(onceBody)

		var eventType string
		var fileEntry *FileEntry

		changer := func(ev *fsnotify.FileEvent) {
			if ev.IsCreate() {
				eventType = "added"
				fileEntry, _ = getInfo(ev.Name)
			} else if ev.IsDelete() {
				eventType = "removed"
				fileEntry = NewFileEntry(path.Base(ev.Name), ev.Name)
			}

			event := map[string]interface{}{
				"event": eventType,
				"file":  fileEntry,
			}

			params.OnChange.Call(event)
			return
		}

		mu.Lock()
		_, ok := watchCallbacks[params.Path]
		if !ok {
			// notify new paths to the watcher
			newPaths <- params.Path
			watchCallbacks[params.Path] = changer
		}
		mu.Unlock()

		removePath := func() {
			mu.Lock()
			delete(watchCallbacks, params.Path)
			mu.Unlock()

			oldPaths <- params.Path
		}

		// remove the path when the remote client disconnects
		r.Client.OnDisconnect(removePath)

		// this callback is called whenever we receive a 'stopWatching' from the client
		response["stopWatching"] = dnode.Callback(func(r *dnode.Partial) {
			removePath()
		})
	}

	files, err := readDirectory(params.Path)
	if err != nil {
		return nil, err
	}

	response["files"] = files
	return response, nil
}

func startWatcher() {
	var err error
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		log.Fatal(err)
	}

	go func() {
		for {
			select {
			case p := <-newPaths:
				err := watcher.Watch(p)
				if err != nil {
					log.Println("watch path adding", err)
				}
			case p := <-oldPaths:
				err := watcher.RemoveWatch(p)
				if err != nil {
					log.Println("watch remove adding", err)
				}
			}
		}
	}()

	for event := range watcher.Event {
		mu.Lock()
		f, ok := watchCallbacks[path.Dir(event.Name)]
		mu.Unlock()

		if !ok {
			continue
		}

		f(event)
	}
}

func Glob(r *kite.Request) (interface{}, error) {
	var params struct {
		Pattern string
	}

	if r.Args.One().Unmarshal(&params) != nil || params.Pattern == "" {
		return nil, errors.New("{ pattern: [string] }")
	}

	return glob(params.Pattern)
}

func ReadFile(r *kite.Request) (interface{}, error) {
	var params struct {
		Path string
	}

	if r.Args.One().Unmarshal(&params) != nil || params.Path == "" {
		return nil, errors.New("{ path: [string] }")
	}

	return readFile(params.Path)
}

type writeFileParams struct {
	Path           string
	Content        []byte
	DoNotOverwrite bool
	Append         bool
}

func WriteFile(r *kite.Request) (interface{}, error) {
	var params writeFileParams
	if r.Args.One().Unmarshal(&params) != nil || params.Path == "" {
		return nil, errors.New("{ path: [string] }")
	}

	return writeFile(params.Path, params.Content, params.DoNotOverwrite, params.Append)
}

func UniquePath(r *kite.Request) (interface{}, error) {
	var params struct {
		Path string
	}

	if r.Args.One().Unmarshal(&params) != nil || params.Path == "" {
		return nil, errors.New("{ path: [string] }")
	}

	return uniquePath(params.Path)
}

func GetInfo(r *kite.Request) (interface{}, error) {
	var params struct {
		Path string
	}

	if r.Args.One().Unmarshal(&params) != nil || params.Path == "" {
		return nil, errors.New("{ path: [string] }")
	}

	return getInfo(params.Path)
}

type setPermissionsParams struct {
	Path      string
	Mode      os.FileMode
	Recursive bool
}

func SetPermissions(r *kite.Request) (interface{}, error) {
	var params setPermissionsParams

	if r.Args.One().Unmarshal(&params) != nil || params.Path == "" {
		return nil, errors.New("{ path: [string], mode: [integer], recursive: [bool] }")
	}

	err := setPermissions(params.Path, params.Mode, params.Recursive)
	if err != nil {
		return nil, err
	}

	return true, nil
}

func Remove(r *kite.Request) (interface{}, error) {
	var params struct {
		Path      string
		Recursive bool
	}

	if r.Args.One().Unmarshal(&params) != nil || params.Path == "" {
		return nil, errors.New("{ path: [string], recursive: [bool] }")
	}

	if err := remove(params.Path, params.Recursive); err != nil {
		return nil, err
	}

	return true, nil
}

func Rename(r *kite.Request) (interface{}, error) {
	var params struct {
		OldPath string
		NewPath string
	}

	if r.Args.One().Unmarshal(&params) != nil || params.OldPath == "" || params.NewPath == "" {
		return nil, errors.New("{ oldPath: [string], newPath: [string] }")
	}

	err := rename(params.OldPath, params.NewPath)
	if err != nil {
		return nil, err
	}

	return true, nil
}

func CreateDirectory(r *kite.Request) (interface{}, error) {
	var params struct {
		Path      string
		Recursive bool
	}

	if r.Args.One().Unmarshal(&params) != nil || params.Path == "" {
		return nil, errors.New("{ path: [string], recursive: [bool] }")
	}

	err := createDirectory(params.Path, params.Recursive)
	if err != nil {
		return nil, err
	}

	return true, nil
}

func Move(r *kite.Request) (interface{}, error) {
	var params struct {
		OldPath string
		NewPath string
	}

	if r.Args.One().Unmarshal(&params) != nil || params.OldPath == "" || params.NewPath == "" {
		return nil, errors.New("{ oldPath: [string], newPath: [string] }")
	}

	err := rename(params.OldPath, params.NewPath)
	if err != nil {
		return nil, err
	}

	return true, nil
}

func Copy(r *kite.Request) (interface{}, error) {
	var params struct {
		SrcPath string
		DstPath string
	}

	if r.Args.One().Unmarshal(&params) != nil || params.SrcPath == "" || params.DstPath == "" {
		return nil, errors.New("{ srcPath: [string], dstPath: [string] }")
	}

	err := cp(params.SrcPath, params.DstPath)
	if err != nil {
		return nil, err
	}

	return true, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"hash/crc32"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
)

// Game in a steam library. May or may not be installed.
type Game struct {
	// Official appID or custom shortcut ID
	ID string
	// Warning, may contain Unicode characters.
	Name string
	// Tags, including user-created category and Steam's "Favorite" tag.
	Tags []string
	// Image format (.jpg, .jpeg, or .png).
	ImageExt string
	// Raw bytes of the encoded image (jpg or png) without overlays.
	CleanImageBytes []byte
	// Raw bytes of the encoded image (jpg or png) with overlays.
	OverlayImageBytes []byte
	// Description of where the image was found (backup, official, search).
	ImageSource string
	// Is custom shortcut?
	Custom bool
}

// Pattern of game declarations in the public profile. It's actually JSON
// inside Javascript, but this way is easier to extract.
const profileGamePattern = `\{"appid":\s*(\d+),\s*"name":\s*"(.+?)"`

// Fetches the list of games from the public user profile. This is better than
// looking locally because the profiles give the full game name, which can be
// used for image searches later on.
func addGamesFromProfile(user User, games map[string]*Game) (err error) {
	profile, err := GetProfile(user)
	if err != nil {
		return
	}

	// Fetch game list from public profile.
	pattern := regexp.MustCompile(profileGamePattern)
	for _, groups := range pattern.FindAllStringSubmatch(profile, -1) {
		gameID := groups[1]
		gameName := groups[2]
		tags := []string{""}
		games[gameID] = &Game{gameID, gameName, tags, "", nil, nil, "", false}
	}

	return
}

// Loads the categories list. This finds the categories for the games loaded
// from the profile and sometimes find new games, although without names.
func addUnknownGames(user User, games map[string]*Game) {
	// Fetch game categories from local file.
	sharedConfFile := filepath.Join(user.Dir, "7", "remote", "sharedconfig.vdf")
	if _, err := os.Stat(sharedConfFile); err != nil {
		// No categories file found, skipping this part.
		return
	}
	sharedConfBytes, err := ioutil.ReadFile(sharedConfFile)
	if err != nil {
		return
	}

	sharedConf := string(sharedConfBytes)
	// VDF pattern: "steamid" { "tags { "0" "category" } }
	gamePattern := regexp.MustCompile(`"([0-9]+)"\s*{[^}]+?"tags"\s*{([^}]+?)}`)
	tagsPattern := regexp.MustCompile(`"[0-9]+"\s*"(.+?)"`)
	for _, gameGroups := range gamePattern.FindAllStringSubmatch(sharedConf, -1) {
		gameID := gameGroups[1]
		tagsText := gameGroups[2]

		for _, tagGroups := range tagsPattern.FindAllStringSubmatch(tagsText, -1) {
			tag := tagGroups[1]

			game, ok := games[gameID]
			if ok {
				game.Tags = append(game.Tags, tag)
			} else {
				// If for some reason it wasn't included in the profile, create a new
				// entry for it now. Unfortunately we don't have a name.
				gameName := ""
				games[gameID] = &Game{gameID, gameName, []string{tag}, "", nil, nil, "", false}
			}
		}
	}
}

// Adds non-Steam games that have been registered locally.
// This information is in the file config/shortcuts.vdf, in binary format.
// It contains the non-Steam games with names, target (exe location) and
// tags/categories. To create a grid image we must compute the Steam ID, which
// is just crc32(target + label) + "02000000", using IEEE standard polynomials.
func addNonSteamGames(user User, games map[string]*Game) {
	shortcutsVdf := filepath.Join(user.Dir, "config", "shortcuts.vdf")
	if _, err := os.Stat(shortcutsVdf); err != nil {
		return
	}
	shortcutBytes, err := ioutil.ReadFile(shortcutsVdf)
	if err != nil {
		return
	}

	// The actual binary format is known, but using regexes is way easier than
	// parsing the entire file. If I run into any problems I'll replace this.
	gamePattern := regexp.MustCompile("(?i)\x00\x01appname\x00(.+?)\x00\x01exe\x00(.+?)\x00\x01.+?\x00tags\x00\x01(.*?)\x08\x08")
	tagsPattern := regexp.MustCompile("\\d\x00(.+?)\x00")
	for _, gameGroups := range gamePattern.FindAllSubmatch(shortcutBytes, -1) {
		gameName := gameGroups[1]
		target := gameGroups[2]
		uniqueName := bytes.Join([][]byte{target, gameName}, []byte(""))
		// Does IEEE CRC32 of target concatenated with gameName. No idea why Steam chose this operation.
		gameID := strconv.FormatUint(uint64(crc32.ChecksumIEEE(uniqueName)) | 0x80000000, 10)
		game := Game{gameID, string(gameName), []string{}, "", nil, nil, "", true}
		games[gameID] = &game

		tagsText := gameGroups[3]
		for _, tagGroups := range tagsPattern.FindAllSubmatch(tagsText, -1) {
			tag := tagGroups[1]
			game.Tags = append(game.Tags, string(tag))
		}
	}
}

// GetGames returns all games from a given user, using both the public profile and local
// files to gather the data. Returns a map of game by ID.
func GetGames(user User, nonSteamOnly bool) map[string]*Game {
	games := make(map[string]*Game, 0)

	if !nonSteamOnly {
		addGamesFromProfile(user, games)
		addUnknownGames(user, games)
	}
	addNonSteamGames(user, games)

	return games
}
<commit_msg>Fixed Non-steam-games without Tags issue<commit_after>package main

import (
	"bytes"
	"hash/crc32"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
)

// Game in a steam library. May or may not be installed.
type Game struct {
	// Official appID or custom shortcut ID
	ID string
	// Warning, may contain Unicode characters.
	Name string
	// Tags, including user-created category and Steam's "Favorite" tag.
	Tags []string
	// Image format (.jpg, .jpeg, or .png).
	ImageExt string
	// Raw bytes of the encoded image (jpg or png) without overlays.
	CleanImageBytes []byte
	// Raw bytes of the encoded image (jpg or png) with overlays.
	OverlayImageBytes []byte
	// Description of where the image was found (backup, official, search).
	ImageSource string
	// Is custom shortcut?
	Custom bool
}

// Pattern of game declarations in the public profile. It's actually JSON
// inside Javascript, but this way is easier to extract.
const profileGamePattern = `\{"appid":\s*(\d+),\s*"name":\s*"(.+?)"`

// Fetches the list of games from the public user profile. This is better than
// looking locally because the profiles give the full game name, which can be
// used for image searches later on.
func addGamesFromProfile(user User, games map[string]*Game) (err error) {
	profile, err := GetProfile(user)
	if err != nil {
		return
	}

	// Fetch game list from public profile.
	pattern := regexp.MustCompile(profileGamePattern)
	for _, groups := range pattern.FindAllStringSubmatch(profile, -1) {
		gameID := groups[1]
		gameName := groups[2]
		tags := []string{""}
		games[gameID] = &Game{gameID, gameName, tags, "", nil, nil, "", false}
	}

	return
}

// Loads the categories list. This finds the categories for the games loaded
// from the profile and sometimes find new games, although without names.
func addUnknownGames(user User, games map[string]*Game) {
	// Fetch game categories from local file.
	sharedConfFile := filepath.Join(user.Dir, "7", "remote", "sharedconfig.vdf")
	if _, err := os.Stat(sharedConfFile); err != nil {
		// No categories file found, skipping this part.
		return
	}
	sharedConfBytes, err := ioutil.ReadFile(sharedConfFile)
	if err != nil {
		return
	}

	sharedConf := string(sharedConfBytes)
	// VDF pattern: "steamid" { "tags { "0" "category" } }
	gamePattern := regexp.MustCompile(`"([0-9]+)"\s*{[^}]+?"tags"\s*{([^}]+?)}`)
	tagsPattern := regexp.MustCompile(`"[0-9]+"\s*"(.+?)"`)
	for _, gameGroups := range gamePattern.FindAllStringSubmatch(sharedConf, -1) {
		gameID := gameGroups[1]
		tagsText := gameGroups[2]

		for _, tagGroups := range tagsPattern.FindAllStringSubmatch(tagsText, -1) {
			tag := tagGroups[1]

			game, ok := games[gameID]
			if ok {
				game.Tags = append(game.Tags, tag)
			} else {
				// If for some reason it wasn't included in the profile, create a new
				// entry for it now. Unfortunately we don't have a name.
				gameName := ""
				games[gameID] = &Game{gameID, gameName, []string{tag}, "", nil, nil, "", false}
			}
		}
	}
}

// Adds non-Steam games that have been registered locally.
// This information is in the file config/shortcuts.vdf, in binary format.
// It contains the non-Steam games with names, target (exe location) and
// tags/categories. To create a grid image we must compute the Steam ID, which
// is just crc32(target + label) + "02000000", using IEEE standard polynomials.
func addNonSteamGames(user User, games map[string]*Game) {
	shortcutsVdf := filepath.Join(user.Dir, "config", "shortcuts.vdf")
	if _, err := os.Stat(shortcutsVdf); err != nil {
		return
	}
	shortcutBytes, err := ioutil.ReadFile(shortcutsVdf)
	if err != nil {
		return
	}

	// The actual binary format is known, but using regexes is way easier than
	// parsing the entire file. If I run into any problems I'll replace this.
	gamePattern := regexp.MustCompile("(?i)\x00\x01appname\x00([^\x08]+?)\x00\x01exe\x00([^\x08]+?)\x00\x01[^\x08]+?\x00tags\x00(?:\x01([^\x08]+?)|)\x08\x08")
	tagsPattern := regexp.MustCompile("\\d\x00([^\x00\x01\x08]+?)\x00")
	for _, gameGroups := range gamePattern.FindAllSubmatch(shortcutBytes, -1) {
		gameName := gameGroups[1]
		target := gameGroups[2]
		uniqueName := bytes.Join([][]byte{target, gameName}, []byte(""))
		// Does IEEE CRC32 of target concatenated with gameName. No idea why Steam chose this operation.
		gameID := strconv.FormatUint(uint64(crc32.ChecksumIEEE(uniqueName)) | 0x80000000, 10)
		game := Game{gameID, string(gameName), []string{}, "", nil, nil, "", true}
		games[gameID] = &game

		tagsText := gameGroups[3]
		for _, tagGroups := range tagsPattern.FindAllSubmatch(tagsText, -1) {
			tag := tagGroups[1]
			game.Tags = append(game.Tags, string(tag))
		}
	}
}

// GetGames returns all games from a given user, using both the public profile and local
// files to gather the data. Returns a map of game by ID.
func GetGames(user User, nonSteamOnly bool) map[string]*Game {
	games := make(map[string]*Game, 0)

	if !nonSteamOnly {
		addGamesFromProfile(user, games)
		addUnknownGames(user, games)
	}
	addNonSteamGames(user, games)

	return games
}
<|endoftext|>