<commit_before>package main

import (
	"bytes"
	"crypto/tls"
	"errors"
	"fmt"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/fclairamb/ftpserver/server"
	"log"
	"os"
	"path/filepath"
	"strings"
	"time"
)

type S3Driver struct {
	baseDir   string
	s3Session *session.Session
	s3Service *s3.S3
}

func (d *S3Driver) WelcomeUser(cc server.ClientContext) (string, error) {
	cc.SetDebug(true)
	return "Welcome to the FTP server for S3", nil
}

func (d *S3Driver) AuthUser(cc server.ClientContext, user, pass string) (server.ClientHandlingDriver, error) {

	if user != FTP_USER {
		log.Println("username does not match expected user", user)
		return nil, fmt.Errorf("incorrect username: %s", user)
	}

	if pass != FTP_PASSWD {
		log.Println("incorrect password")
		return nil, errors.New("incorrect password")
	}

	_, err := session.NewSession()
	if err != nil {
		log.Println("error creating S3 session (check AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY env vars on server):", err)
		return nil, err
	}

	return d, nil
}

func (d *S3Driver) GetTLSConfig() (*tls.Config, error) {
	return nil, errors.New("TLS not implemented")
}

func (d *S3Driver) ChangeDirectory(cc server.ClientContext, directory string) error {
	// Query S3 to see if the directory exists.  If it does update the baseDir variable to emulate cd-ing to that
	// directory

	var err error
	var dirname string
	if dirname, err = getS3Key(directory + "/"); err != nil {
		return err
	}

	delimiter := "/"
	params := &s3.ListObjectsV2Input{
		Bucket:    &S3_BUCKET_NAME,
		Prefix:    &dirname,
		Delimiter: &delimiter, // limits the search to directories (they have a trailing slash)
	}

	var resp *s3.ListObjectsV2Output
	if resp, err = d.s3Service.ListObjectsV2(params); err != nil {
		return err
	}

	if *resp.KeyCount == 0 {
		return errors.New("No such directory: " + directory)
	}

	d.baseDir = dirname

	return nil
}

func (d *S3Driver) MakeDirectory(cc server.ClientContext, directory string) error {

	var err error
	var dirname string
	if dirname, err = getS3Key(directory + "/"); err != nil {
		return err
	}

	params := &s3.PutObjectInput{
		Bucket: &S3_BUCKET_NAME,
		Key:    &dirname,
		Body:   bytes.NewReader([]byte("")),
	}

	if _, err = d.s3Service.PutObject(params); err != nil {
		return err
	}

	return nil
}

func (d *S3Driver) ListFiles(cc server.ClientContext) ([]os.FileInfo, error) {

	delimiter := "/" // delimiter keeps the listing from being recursive
	params := &s3.ListObjectsV2Input{
		Bucket:    &S3_BUCKET_NAME,
		Prefix:    &d.baseDir,
		Delimiter: &delimiter,
	}

	var err error
	var resp *s3.ListObjectsV2Output
	if resp, err = d.s3Service.ListObjectsV2(params); err != nil {
		return nil, err
	}

	files := []os.FileInfo{}

	// directories other than CWD
	for _, dir := range resp.CommonPrefixes {
		var dirInfo os.FileInfo

		if dirInfo, err = d.GetFileInfo(cc, filepath.Join("/", *dir.Prefix)+"/"); err != nil {
			return nil, err
		}

		files = append(files, dirInfo)
	}

	// files and CWD
	for _, f := range resp.Contents {

		// don't list CWD in the list of files
		if *f.Key == d.baseDir {
			continue
		}

		var fi os.FileInfo
		if fi, err = d.getFakeFileInfo(*f.Key, *f.Size, *f.LastModified); err != nil {
			return nil, err
		}
		files = append(files, fi)
	}

	return files, nil
}

func (d *S3Driver) UserLeft(cc server.ClientContext) {

}

func (d *S3Driver) OpenFile(cc server.ClientContext, path string, flag int) (server.FileStream, error) {
	// our implementation uses an interface that mimics a file but uploads/downloads to S3
	var err error
	var s3file *S3VirtualFile

	if s3file, err = NewS3VirtualFile(path, d.s3Session, d.s3Service); err != nil {
		return nil, err
	}

	return s3file, nil
}

func (d *S3Driver) GetFileInfo(cc server.ClientContext, path string) (os.FileInfo, error) {

	var err error
	var relPath string
	if relPath, err = getS3Key(path); err != nil {
		return nil, err
	}

	params := &s3.GetObjectInput{
		Bucket: &S3_BUCKET_NAME,
		Key:    &relPath,
	}

	req, resp := d.s3Service.GetObjectRequest(params)

	if err := req.Send(); err != nil {
		return nil, err
	}

	// resp.ContentLength and LastModified are sometimes nil (!) so check for this state
	var objectSize int64
	if resp.ContentLength != nil {
		objectSize = *resp.ContentLength
	}

	var modTime time.Time
	if resp.LastModified != nil {
		modTime = *resp.LastModified
	}

	var f os.FileInfo
	if f, err = d.getFakeFileInfo(relPath, objectSize, modTime); err != nil {
		return nil, err
	}

	return f, nil
}

func (d *S3Driver) CanAllocate(cc server.ClientContext, size int) (bool, error) {
	return true, nil
}

func (d *S3Driver) ChmodFile(cc server.ClientContext, path string, mode os.FileMode) error {
	return errors.New("ChmodFile not implemented")
}

func (d *S3Driver) DeleteFile(cc server.ClientContext, path string) error {
	// list objects matching the path, then use DeleteObjects on all of them.  Needed because you must delete all
	// child key/objects belonging to a directory key before deleting that key

	var err error
	var relPath string
	if relPath, err = getS3Key(path); err != nil {
		return err
	}

	var isDir bool
	if isDir, err = d.isS3Dir(relPath); err != nil {
		return err
	}

	if isDir {
		relPath += "/"
	}

	listParams := &s3.ListObjectsV2Input{
		Bucket:    &S3_BUCKET_NAME,
		Prefix:    &relPath,
		//Delimiter: &delimiter, // empty delim makes this recursive
	}

	var resp *s3.ListObjectsV2Output
	if resp, err = d.s3Service.ListObjectsV2(listParams); err != nil {
		return err
	}

	var delObjects []*s3.ObjectIdentifier
	for _, f := range resp.Contents {
		delObjects = append(delObjects, &s3.ObjectIdentifier{Key:f.Key})
	}

	delParams := &s3.DeleteObjectsInput{
		Bucket: &S3_BUCKET_NAME,
		Delete: &s3.Delete{Objects:delObjects},
	}

	if _, err = d.s3Service.DeleteObjects(delParams); err != nil {
		return err
	}

	return nil
}

// returns true or false depending on if the path exists as a file (no trailing slash) or a directory (trailing slash)
// in S3.
func (d *S3Driver) isS3Dir(path string) (bool, error) {
	var err, errFile, errDir error
	var s3Path string

	log.Println("DEBUG", path)
	if s3Path, err = getS3Key(path); err != nil {
		return false, err
	}

	// see if it's file
	_, errFile = d.GetFileInfo(nil, s3Path)
	if errFile == nil {
		return false, nil
	}

	// see if it's a directory
	_, errDir = d.GetFileInfo(nil, s3Path + "/")
	if errDir == nil {
		return true, nil
	}

	return false, errors.New("No such file or directory")
}

func (d *S3Driver) RenameFile(cc server.ClientContext, from, to string) error {
	// S3 doesn't have rename (or move) so we need to copy (CopyObject) and delete the old one (DeleteObject).
	var err error
	var relFrom string
	if relFrom, err = getS3Key(from); err != nil {
		return err
	}

	var relTo string
	if relTo, err = getS3Key(to); err != nil {
		return err
	}

	copySrc := S3_BUCKET_NAME + "/" + relFrom
	copyParams := &s3.CopyObjectInput{
		Bucket:     &S3_BUCKET_NAME,
		Key:        &relTo,
		CopySource: &copySrc,
	}

	if _, err = d.s3Service.CopyObject(copyParams); err != nil {
		return err
	}

	if err = d.DeleteFile(cc, from); err != nil {
		return err
	}

	return nil
}

func (d *S3Driver) GetSettings() *server.Settings {
	config := server.Settings{
		Host:           "0.0.0.0",
		Port:           FTP_PORT,
		MaxConnections: 300,
		MonitorOn:      true,
		MonitorPort:    3379,
	}
	return &config
}

// Return a fakeInfo struct that satisfies the os.FileInfo interface, emulating a file from an S3 object
func (d *S3Driver) getFakeFileInfo(name string, size int64, modTime time.Time) (os.FileInfo, error) {
	var err error

	displayPath := name
	// make the path we display relative to the current working directory
	if strings.HasPrefix(displayPath, d.baseDir) {
		if displayPath, err = filepath.Rel(d.baseDir, displayPath); err != nil {
			return nil, err
		}
	}

	isDir := strings.HasSuffix(name, "/")

	mode := os.FileMode(0666)
	if isDir {
		mode = mode | os.ModeDir
	}

	f := fakeInfo{
		name:    displayPath,
		size:    size,
		mode:    mode,
		modTime: modTime,
		isDir:   isDir,
	}

	return f, nil
}

func NewS3Driver() (*S3Driver, error) {

	var err error

	driver := &S3Driver{}

	if driver.s3Session, err = session.NewSession(); err != nil {
		log.Println("error creating S3 session:", err)
		return nil, err
	}

	driver.s3Service = s3.New(driver.s3Session)

	driver.baseDir = ""

	return driver, nil
}

// cleans the input path and queries S3 to see if it's a directory or file key.  Will return an error if it cannot find
// either a file or directory key (with a trailing slash)
func getS3Key(path string) (string, error) {
	var err error
	s3Key := path

	// join paths and keep relative to root dir.
	if strings.HasPrefix(s3Key, "/") {
		if s3Key, err = filepath.Rel("/", path); err != nil {
			return "", err
		}
	}

	// "/" relative to "/" is "." but in S3 this should be ""
	if s3Key == "." {
		s3Key = ""
	}

	// directories in S3 must always have a trailing slash (filepath.Rel removes this)
	if strings.HasSuffix(path, "/") && len(s3Key) > 0 && !strings.HasSuffix(s3Key, "/") {
		s3Key += "/"
	}

	return s3Key, nil
}
<commit_msg>Implemented recursive rename, handles nested directories now.  Also refactored a bit.<commit_after>package main

import (
	"bytes"
	"crypto/tls"
	"errors"
	"fmt"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/fclairamb/ftpserver/server"
	"log"
	"os"
	"path/filepath"
	"strings"
	"time"
)

type S3Driver struct {
	baseDir   string
	s3Session *session.Session
	s3Service *s3.S3
}

func (d *S3Driver) WelcomeUser(cc server.ClientContext) (string, error) {
	cc.SetDebug(true)
	return "Welcome to the FTP server for S3", nil
}

func (d *S3Driver) AuthUser(cc server.ClientContext, user, pass string) (server.ClientHandlingDriver, error) {

	if user != FTP_USER {
		log.Println("username does not match expected user", user)
		return nil, fmt.Errorf("incorrect username: %s", user)
	}

	if pass != FTP_PASSWD {
		log.Println("incorrect password")
		return nil, errors.New("incorrect password")
	}

	_, err := session.NewSession()
	if err != nil {
		log.Println("error creating S3 session (check AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY env vars on server):", err)
		return nil, err
	}

	return d, nil
}

func (d *S3Driver) GetTLSConfig() (*tls.Config, error) {
	return nil, errors.New("TLS not implemented")
}

func (d *S3Driver) ChangeDirectory(cc server.ClientContext, directory string) error {
	// Query S3 to see if the directory exists.  If it does update the baseDir variable to emulate cd-ing to that
	// directory

	var err error
	var dirname string
	if dirname, err = getS3Key(directory + "/"); err != nil {
		return err
	}

	delimiter := "/"
	params := &s3.ListObjectsV2Input{
		Bucket:    &S3_BUCKET_NAME,
		Prefix:    &dirname,
		Delimiter: &delimiter, // limits the search to directories (they have a trailing slash)
	}

	var resp *s3.ListObjectsV2Output
	if resp, err = d.s3Service.ListObjectsV2(params); err != nil {
		return err
	}

	if *resp.KeyCount == 0 {
		return errors.New("No such directory: " + directory)
	}

	d.baseDir = dirname

	return nil
}

func (d *S3Driver) MakeDirectory(cc server.ClientContext, directory string) error {

	var err error
	var dirname string
	if dirname, err = getS3Key(directory + "/"); err != nil {
		return err
	}

	params := &s3.PutObjectInput{
		Bucket: &S3_BUCKET_NAME,
		Key:    &dirname,
		Body:   bytes.NewReader([]byte("")),
	}

	if _, err = d.s3Service.PutObject(params); err != nil {
		return err
	}

	return nil
}

func (d *S3Driver) ListFiles(cc server.ClientContext) ([]os.FileInfo, error) {

	delimiter := "/" // delimiter keeps the listing from being recursive
	params := &s3.ListObjectsV2Input{
		Bucket:    &S3_BUCKET_NAME,
		Prefix:    &d.baseDir,
		Delimiter: &delimiter,
	}

	var err error
	var resp *s3.ListObjectsV2Output
	if resp, err = d.s3Service.ListObjectsV2(params); err != nil {
		return nil, err
	}

	files := []os.FileInfo{}

	// directories other than CWD
	for _, dir := range resp.CommonPrefixes {
		var dirInfo os.FileInfo

		if dirInfo, err = d.GetFileInfo(cc, filepath.Join("/", *dir.Prefix)+"/"); err != nil {
			return nil, err
		}

		files = append(files, dirInfo)
	}

	// files and CWD
	for _, f := range resp.Contents {

		// don't list CWD in the list of files
		if *f.Key == d.baseDir {
			continue
		}

		var fi os.FileInfo
		if fi, err = d.getFakeFileInfo(*f.Key, *f.Size, *f.LastModified); err != nil {
			return nil, err
		}
		files = append(files, fi)
	}

	return files, nil
}

func (d *S3Driver) UserLeft(cc server.ClientContext) {

}

func (d *S3Driver) OpenFile(cc server.ClientContext, path string, flag int) (server.FileStream, error) {
	// our implementation uses an interface that mimics a file but uploads/downloads to S3
	var err error
	var s3file *S3VirtualFile

	if s3file, err = NewS3VirtualFile(path, d.s3Session, d.s3Service); err != nil {
		return nil, err
	}

	return s3file, nil
}

func (d *S3Driver) getObjectInfo(key string) (*s3.GetObjectOutput, error) {
	params := &s3.GetObjectInput{
		Bucket: &S3_BUCKET_NAME,
		Key:    &key,
	}

	req, resp := d.s3Service.GetObjectRequest(params)

	if err := req.Send(); err != nil {
		return nil, err
	}

	return resp, nil
}

func (d *S3Driver) GetFileInfo(cc server.ClientContext, path string) (os.FileInfo, error) {

	var err error
	relPath := path
	if relPath, err = getS3Key(path); err != nil {
		return nil, err
	}

	var resp *s3.GetObjectOutput
	// check for directories (trailing slashes) if we can't find the file
	if resp, err = d.getObjectInfo(relPath); err != nil {

		if resp, err = d.getObjectInfo(relPath + "/"); err != nil {
			relPath += "/"
		} else {
			return nil, err
		}
	}

	// resp.ContentLength and LastModified are sometimes nil (!) so check for this state
	var objectSize int64
	if resp.ContentLength != nil {
		objectSize = *resp.ContentLength
	}

	var modTime time.Time
	if resp.LastModified != nil {
		modTime = *resp.LastModified
	}

	var f os.FileInfo
	if f, err = d.getFakeFileInfo(relPath, objectSize, modTime); err != nil {
		return nil, err
	}

	return f, nil
}

func (d *S3Driver) CanAllocate(cc server.ClientContext, size int) (bool, error) {
	return true, nil
}

func (d *S3Driver) ChmodFile(cc server.ClientContext, path string, mode os.FileMode) error {
	return errors.New("ChmodFile not implemented")
}

func (d *S3Driver) DeleteFile(cc server.ClientContext, path string) error {
	// list objects matching the path, then use DeleteObjects on all of them.  Needed because you must delete all
	// child key/objects belonging to a directory key before deleting that key

	var err error
	var relPath string
	if relPath, err = getS3Key(path); err != nil {
		return err
	}

	var isDir bool
	if isDir, err = d.isS3Dir(relPath); err != nil {
		return err
	}

	if isDir && !strings.HasSuffix(relPath, "/") {
		relPath += "/"
	}

	listParams := &s3.ListObjectsV2Input{
		Bucket: &S3_BUCKET_NAME,
		Prefix: &relPath,
		//Delimiter: &delimiter, // empty delim makes this recursive
	}

	var resp *s3.ListObjectsV2Output
	if resp, err = d.s3Service.ListObjectsV2(listParams); err != nil {
		return err
	}

	var delObjects []*s3.ObjectIdentifier
	for _, f := range resp.Contents {
		delObjects = append(delObjects, &s3.ObjectIdentifier{Key: f.Key})
	}

	delParams := &s3.DeleteObjectsInput{
		Bucket: &S3_BUCKET_NAME,
		Delete: &s3.Delete{Objects: delObjects},
	}

	if _, err = d.s3Service.DeleteObjects(delParams); err != nil {
		return err
	}

	return nil
}

// returns true or false depending on if the path exists as a file (no trailing slash) or a directory (trailing slash)
// in S3.
func (d *S3Driver) isS3Dir(path string) (bool, error) {
	var err error
	var s3Path string
	if s3Path, err = getS3Key(path); err != nil {
		return false, err
	}

	_, err = d.getObjectInfo(s3Path)
	if err == nil {
		return false, nil
	}

	_, err = d.getObjectInfo(s3Path + "/")
	if err == nil {
		return true, nil
	}

	return false, errors.New("No such file or directory")
}

func (d *S3Driver) RenameFile(cc server.ClientContext, from, to string) error {
	// S3 doesn't have rename (or move).  We're copying all objects that match the input file or directory key
	// to the new key name

	var err error
	var relFrom, relTo string

	if relFrom, err = getS3Key(from); err != nil {
		return err
	}

	if relTo, err = getS3Key(to); err != nil {
		return err
	}

	var isDir bool
	if isDir, err = d.isS3Dir(relFrom); err != nil {
		return err
	}

	if isDir {
		if !strings.HasSuffix(relFrom, "/") {
			relFrom += "/"
		}
		if !strings.HasSuffix(relTo, "/") {
			relTo += "/"
		}
	}

	listParams := &s3.ListObjectsV2Input{
		Bucket: &S3_BUCKET_NAME,
		Prefix: &relFrom,
	}

	var resp *s3.ListObjectsV2Output
	if resp, err = d.s3Service.ListObjectsV2(listParams); err != nil {
		return err
	}

	var srcObjects []*s3.ObjectIdentifier
	for _, f := range resp.Contents {
		srcObjects = append(srcObjects, &s3.ObjectIdentifier{Key: f.Key})
	}

	// copy all destinations objects from source to dest (already ordered from top level directory key)
	for _, objId := range srcObjects {

		toPath := strings.Replace(*objId.Key, relFrom, relTo, 1)
		copySrc := S3_BUCKET_NAME + "/" + *objId.Key
		copyParams := &s3.CopyObjectInput{
			Bucket:     &S3_BUCKET_NAME,
			Key:        &toPath,
			CopySource: &copySrc,
		}

		if _, err = d.s3Service.CopyObject(copyParams); err != nil {
			return err
		}
	}

	// delete original file (or nested directory of matching keys).  Faster than looping over them.
	delParams := &s3.DeleteObjectsInput{
		Bucket: &S3_BUCKET_NAME,
		Delete: &s3.Delete{Objects: srcObjects},
	}

	if _, err = d.s3Service.DeleteObjects(delParams); err != nil {
		return err
	}

	return nil
}

func (d *S3Driver) GetSettings() *server.Settings {
	config := server.Settings{
		Host:           "0.0.0.0",
		Port:           FTP_PORT,
		MaxConnections: 300,
		MonitorOn:      true,
		MonitorPort:    3379,
	}
	return &config
}

// Return a fakeInfo struct that satisfies the os.FileInfo interface, emulating a file from an S3 object
func (d *S3Driver) getFakeFileInfo(name string, size int64, modTime time.Time) (os.FileInfo, error) {
	var err error

	displayPath := name
	// make the path we display relative to the current working directory
	if strings.HasPrefix(displayPath, d.baseDir) {
		if displayPath, err = filepath.Rel(d.baseDir, displayPath); err != nil {
			return nil, err
		}
	}

	isDir := strings.HasSuffix(name, "/")

	mode := os.FileMode(0666)
	if isDir {
		mode = mode | os.ModeDir
	}

	f := fakeInfo{
		name:    displayPath,
		size:    size,
		mode:    mode,
		modTime: modTime,
		isDir:   isDir,
	}

	return f, nil
}

func NewS3Driver() (*S3Driver, error) {

	var err error

	driver := &S3Driver{}

	if driver.s3Session, err = session.NewSession(); err != nil {
		log.Println("error creating S3 session:", err)
		return nil, err
	}

	driver.s3Service = s3.New(driver.s3Session)

	driver.baseDir = ""

	return driver, nil
}

// cleans the input path and queries S3 to see if it's a directory or file key.  Will return an error if it cannot find
// either a file or directory key (with a trailing slash)
func getS3Key(path string) (string, error) {
	var err error
	s3Key := path

	// join paths and keep relative to root dir.
	if strings.HasPrefix(s3Key, "/") {
		if s3Key, err = filepath.Rel("/", path); err != nil {
			return "", err
		}
	}

	// "/" relative to "/" is "." but in S3 this should be ""
	if s3Key == "." {
		s3Key = ""
	}

	// directories in S3 must always have a trailing slash (filepath.Rel removes this)
	if strings.HasSuffix(path, "/") && len(s3Key) > 0 && !strings.HasSuffix(s3Key, "/") {
		s3Key += "/"
	}

	return s3Key, nil
}
<|endoftext|>


<commit_before>package net_test

import (
	"errors"
	gonet "net"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	. "bosh/platform/net"
	fakenet "bosh/platform/net/fakes"
	boshsettings "bosh/settings"
)

type NotIPNet struct{}

func (i NotIPNet) String() string  { return "" }
func (i NotIPNet) Network() string { return "" }

var _ = Describe("defaultNetworkResolver", func() {
	var (
		routesSearcher *fakenet.FakeRoutesSearcher
		resolver       DefaultNetworkResolver
	)

	BeforeEach(func() {
		routesSearcher = &fakenet.FakeRoutesSearcher{}
		resolver = NewDefaultNetworkResolver(routesSearcher, DefaultInterfaceToAddrsFunc)
	})

	Describe("Resolve", func() {
		It("returns a network associated with a first default gateway", func() {
			routesSearcher.SearchRoutesRoutes = []Route{
				Route{
					Destination: "fake-route1-dest",
					Gateway: "fake-route1-gatewa	y",
					InterfaceName: "fake-route1-iface",
				},
				Route{
					Destination:   "0.0.0.0",
					Gateway:       "fake-route2-gateway",
					InterfaceName: "en0",
				},
			}

			network, err := resolver.GetDefaultNetwork()
			Expect(err).ToNot(HaveOccurred())

			ip := gonet.ParseIP(network.IP)
			Expect(ip).ToNot(BeNil())
			Expect(ip).ToNot(Equal(gonet.ParseIP("0.0.0.0")))

			netmask := gonet.ParseIP(network.Netmask)
			Expect(netmask).ToNot(BeNil())
			Expect(netmask).ToNot(Equal(gonet.ParseIP("0.0.0.0")))

			Expect(network.Gateway).To(Equal("fake-route2-gateway"))
		})

		It("returns error if searching routes fails", func() {
			routesSearcher.SearchRoutesErr = errors.New("fake-search-routes-err")

			network, err := resolver.GetDefaultNetwork()
			Expect(err).To(HaveOccurred())
			Expect(err.Error()).To(ContainSubstring("fake-search-routes-err"))
			Expect(network).To(Equal(boshsettings.Network{}))
		})

		Context("when default route is found", func() {
			BeforeEach(func() {
				routesSearcher.SearchRoutesRoutes = []Route{
					Route{
						Destination:   "0.0.0.0",
						Gateway:       "fake-gateway",
						InterfaceName: "fake-interface-name",
					},
				}
			})

			Context("when interface associated with found route cannot be found", func() {
				var (
					addrs []gonet.Addr
				)

				BeforeEach(func() {
					resolver = NewDefaultNetworkResolver(
						routesSearcher,
						func(_ string) ([]gonet.Addr, error) { return addrs, nil },
					)
				})

				It("returns first ipv4 address from associated interface", func() {
					addrs = []gonet.Addr{
						NotIPNet{},
						&gonet.IPNet{IP: gonet.IPv6linklocalallrouters},
						&gonet.IPNet{IP: gonet.ParseIP("127.0.0.1"), Mask: gonet.CIDRMask(16, 32)},
						&gonet.IPNet{IP: gonet.ParseIP("127.0.0.10"), Mask: gonet.CIDRMask(24, 32)},
					}

					network, err := resolver.GetDefaultNetwork()
					Expect(err).ToNot(HaveOccurred())
					Expect(network).To(Equal(boshsettings.Network{
						IP:      "127.0.0.1",
						Netmask: "255.255.0.0",
						Gateway: "fake-gateway",
					}))
				})

				It("returns error if associated interface does not have any addresses", func() {
					addrs = []gonet.Addr{}

					network, err := resolver.GetDefaultNetwork()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("No addresses"))
					Expect(network).To(Equal(boshsettings.Network{}))
				})

				It("returns error if associated interface only has non-IPNet addresses", func() {
					addrs = []gonet.Addr{NotIPNet{}}

					network, err := resolver.GetDefaultNetwork()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("Failed to find IPv4 address"))
					Expect(network).To(Equal(boshsettings.Network{}))
				})

				It("returns error if associated interface only has ipv6 addresses", func() {
					addrs = []gonet.Addr{&gonet.IPNet{IP: gonet.IPv6linklocalallrouters}}

					network, err := resolver.GetDefaultNetwork()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("Failed to find IPv4 address"))
					Expect(network).To(Equal(boshsettings.Network{}))
				})
			})

			Context("when interface associated with found route cannot be found", func() {
				// using default InterfaceToAddrsFunc so fake-interface-name is not going to be found

				It("returns error", func() {
					network, err := resolver.GetDefaultNetwork()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-interface-name: net: no such interface"))
					Expect(network).To(Equal(boshsettings.Network{}))
				})
			})
		})

		Context("when default route is found", func() {
			BeforeEach(func() {
				routesSearcher.SearchRoutesRoutes = []Route{
					Route{Destination: "fake-route-dest"},
				}
			})

			It("returns error", func() {
				network, err := resolver.GetDefaultNetwork()
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("Failed to find default route"))
				Expect(network).To(Equal(boshsettings.Network{}))
			})
		})

		Context("when there are no routes", func() {
			BeforeEach(func() {
				routesSearcher.SearchRoutesRoutes = []Route{}
			})

			It("returns error if there are no routes", func() {
				network, err := resolver.GetDefaultNetwork()
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("No routes"))
				Expect(network).To(Equal(boshsettings.Network{}))
			})
		})
	})
})
<commit_msg>conditionally use en0/eth0 in tests<commit_after>package net_test

import (
	"errors"
	gonet "net"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	. "bosh/platform/net"
	fakenet "bosh/platform/net/fakes"
	boshsettings "bosh/settings"
)

type NotIPNet struct{}

func (i NotIPNet) String() string  { return "" }
func (i NotIPNet) Network() string { return "" }

var _ = Describe("defaultNetworkResolver", func() {
	var (
		routesSearcher *fakenet.FakeRoutesSearcher
		resolver       DefaultNetworkResolver
	)

	BeforeEach(func() {
		routesSearcher = &fakenet.FakeRoutesSearcher{}
		resolver = NewDefaultNetworkResolver(routesSearcher, DefaultInterfaceToAddrsFunc)
	})

	Describe("Resolve", func() {
		It("returns a network associated with a first default gateway", func() {
			var ifaceName string

			if _, err := gonet.InterfaceByName("en0"); err == nil {
				ifaceName = "en0"
			} else if _, err := gonet.InterfaceByName("eth0"); err == nil {
				ifaceName = "eth0"
			} else {
				panic("Not sure which interface name to use: en0 and eth0 are not found")
			}

			routesSearcher.SearchRoutesRoutes = []Route{
				Route{
					Destination:   "fake-route1-dest",
					Gateway:       "fake-route1-gateway",
					InterfaceName: "fake-route1-iface",
				},
				Route{
					Destination:   "0.0.0.0",
					Gateway:       "fake-route2-gateway",
					InterfaceName: ifaceName,
				},
			}

			network, err := resolver.GetDefaultNetwork()
			Expect(err).ToNot(HaveOccurred())

			ip := gonet.ParseIP(network.IP)
			Expect(ip).ToNot(BeNil())
			Expect(ip).ToNot(Equal(gonet.ParseIP("0.0.0.0")))

			netmask := gonet.ParseIP(network.Netmask)
			Expect(netmask).ToNot(BeNil())
			Expect(netmask).ToNot(Equal(gonet.ParseIP("0.0.0.0")))

			Expect(network.Gateway).To(Equal("fake-route2-gateway"))
		})

		It("returns error if searching routes fails", func() {
			routesSearcher.SearchRoutesErr = errors.New("fake-search-routes-err")

			network, err := resolver.GetDefaultNetwork()
			Expect(err).To(HaveOccurred())
			Expect(err.Error()).To(ContainSubstring("fake-search-routes-err"))
			Expect(network).To(Equal(boshsettings.Network{}))
		})

		Context("when default route is found", func() {
			BeforeEach(func() {
				routesSearcher.SearchRoutesRoutes = []Route{
					Route{
						Destination:   "0.0.0.0",
						Gateway:       "fake-gateway",
						InterfaceName: "fake-interface-name",
					},
				}
			})

			Context("when interface associated with found route cannot be found", func() {
				var (
					addrs []gonet.Addr
				)

				BeforeEach(func() {
					resolver = NewDefaultNetworkResolver(
						routesSearcher,
						func(_ string) ([]gonet.Addr, error) { return addrs, nil },
					)
				})

				It("returns first ipv4 address from associated interface", func() {
					addrs = []gonet.Addr{
						NotIPNet{},
						&gonet.IPNet{IP: gonet.IPv6linklocalallrouters},
						&gonet.IPNet{IP: gonet.ParseIP("127.0.0.1"), Mask: gonet.CIDRMask(16, 32)},
						&gonet.IPNet{IP: gonet.ParseIP("127.0.0.10"), Mask: gonet.CIDRMask(24, 32)},
					}

					network, err := resolver.GetDefaultNetwork()
					Expect(err).ToNot(HaveOccurred())
					Expect(network).To(Equal(boshsettings.Network{
						IP:      "127.0.0.1",
						Netmask: "255.255.0.0",
						Gateway: "fake-gateway",
					}))
				})

				It("returns error if associated interface does not have any addresses", func() {
					addrs = []gonet.Addr{}

					network, err := resolver.GetDefaultNetwork()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("No addresses"))
					Expect(network).To(Equal(boshsettings.Network{}))
				})

				It("returns error if associated interface only has non-IPNet addresses", func() {
					addrs = []gonet.Addr{NotIPNet{}}

					network, err := resolver.GetDefaultNetwork()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("Failed to find IPv4 address"))
					Expect(network).To(Equal(boshsettings.Network{}))
				})

				It("returns error if associated interface only has ipv6 addresses", func() {
					addrs = []gonet.Addr{&gonet.IPNet{IP: gonet.IPv6linklocalallrouters}}

					network, err := resolver.GetDefaultNetwork()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("Failed to find IPv4 address"))
					Expect(network).To(Equal(boshsettings.Network{}))
				})
			})

			Context("when interface associated with found route cannot be found", func() {
				// using default InterfaceToAddrsFunc so fake-interface-name is not going to be found

				It("returns error", func() {
					network, err := resolver.GetDefaultNetwork()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-interface-name: net: no such interface"))
					Expect(network).To(Equal(boshsettings.Network{}))
				})
			})
		})

		Context("when default route is found", func() {
			BeforeEach(func() {
				routesSearcher.SearchRoutesRoutes = []Route{
					Route{Destination: "fake-route-dest"},
				}
			})

			It("returns error", func() {
				network, err := resolver.GetDefaultNetwork()
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("Failed to find default route"))
				Expect(network).To(Equal(boshsettings.Network{}))
			})
		})

		Context("when there are no routes", func() {
			BeforeEach(func() {
				routesSearcher.SearchRoutesRoutes = []Route{}
			})

			It("returns error if there are no routes", func() {
				network, err := resolver.GetDefaultNetwork()
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("No routes"))
				Expect(network).To(Equal(boshsettings.Network{}))
			})
		})
	})
})
<|endoftext|>


<commit_before>package main

import (
	"testing"
)

func BenchmarkGenericBuf(b *testing.B) {
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			GenericBuf()
		}
	})
}

func BenchmarkGenericStackBuf(b *testing.B) {
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			GenericStackBuf()
		}
	})
}

func BenchmarkAllocBuf(b *testing.B) {
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			AllocBuf()
		}
	})
}

func BenchmarkSyncPoolBuf(b *testing.B) {
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			SyncBuf()
		}
	})
}

func BenchmarkBpoolPoolBuf(b *testing.B) {
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			BpoolBuf()
		}
	})
}

func BenchmarkByteBufferPoolBuf(b *testing.B) {
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			BBpoolBuf()
		}
	})
}

func BenchmarkEasyJsonBuffer(b *testing.B) {
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			BBpoolBuf()
		}
	})
}
<commit_msg>fix typo<commit_after>package main

import (
	"testing"
)

func BenchmarkGenericBuf(b *testing.B) {
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			GenericBuf()
		}
	})
}

func BenchmarkGenericStackBuf(b *testing.B) {
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			GenericStackBuf()
		}
	})
}

func BenchmarkAllocBuf(b *testing.B) {
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			AllocBuf()
		}
	})
}

func BenchmarkSyncPoolBuf(b *testing.B) {
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			SyncBuf()
		}
	})
}

func BenchmarkBpoolPoolBuf(b *testing.B) {
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			BpoolBuf()
		}
	})
}

func BenchmarkByteBufferPoolBuf(b *testing.B) {
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			BBpoolBuf()
		}
	})
}

func BenchmarkEasyJsonBuffer(b *testing.B) {
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			EasyJsonBuf()
		}
	})
}
<|endoftext|>


<commit_before>package hamlengine

import (
  "os"
  "io/ioutil"
  "log"
  "fmt"
  "strings"
  "path/filepath"
  "html/template"
  "github.com/golib/revel"
  "github.com/realistschuckle/gohaml"
)

func init() {
  revel.RegisterTemplateEnginer("haml", revel.TemplateEnginer(new(hamlTemplateEngine)))
}

type hamlTemplateEngine struct {
  revel.TemplateEngine
}

func (haml *hamlTemplateEngine) Refresh() *revel.Error {
  paths := haml.Paths()

  revel.TRACE.Printf("Refreshing templates from %s", paths)

  var compileError *revel.Error
  var templatePaths map[string]string

  // Set the template delimiters for the project if present, then split into left
  // and right delimiters around a space character
  var splitDelims []string
  if revel.TemplateDelims != "" {
    splitDelims = strings.Split(revel.TemplateDelims, " ")
    if len(splitDelims) != 2 {
      log.Fatalln("app.conf: Incorrect format for template.delimiters")
    }
  }

  var hamlScope = make(map[string]interface{})
  hamlScope["lang"] = "HAML"

  // Walk through the template engine's paths and build up a template set.
  var templateSet *template.Template = nil

  for _, basePath := range paths {
    // Walk only returns an error if the template engine is completely unusable
    // (namely, if one of the TemplateFuncs does not have an acceptable signature).
    funcErr := filepath.Walk(basePath, func(path string, info os.FileInfo, err error) error {
      if err != nil {
        revel.ERROR.Println("error walking templates:", err)
        return nil
      }

      // Walk into watchable directories
      if info.IsDir() {
        if !haml.WatchDir(info) {
          return filepath.SkipDir
        }
        return nil
      }

      // Only add watchable
      if !haml.WatchFile(info.Name()) {
        return nil
      }

      var fileStr string

      // addTemplate allows the same template to be added multiple
      // times with different template names.
      addTemplate := func(templateName string) (err error) {
        // Convert template names to use forward slashes, even on Windows.
        if os.PathSeparator == '\\' {
          templateName = strings.Replace(templateName, `\`, `/`, -1) // `
        }

        // If we already loaded a template of this name, skip it.
        if _, ok := templatePaths[templateName]; ok {
          return nil
        }
        templatePaths[templateName] = path

        // Load the file if we haven't already
        if fileStr == "" {
          fileBytes, err := ioutil.ReadFile(path)
          if err != nil {
            revel.ERROR.Println("Failed reading file:", path)
            return nil
          }

          hamlEngine, _ := gohaml.NewEngine(string(fileBytes))
          fileStr = hamlEngine.Render(hamlScope)
        }

        if templateSet == nil {
          // Create the template set.  This panics if any of the funcs do not
          // conform to expectations, so we wrap it in a func and handle those
          // panics by serving an error page.
          var funcError *revel.Error
          func() {
            defer func() {
              if err := recover(); err != nil {
                funcError = &revel.Error{
                  Title:       "Panic (Template engine)",
                  Description: fmt.Sprintln(err),
                }
              }
            }()
            templateSet = template.New(templateName).Funcs(revel.TemplateFuncs)
            // If alternate delimiters set for the project, change them for this set
            if splitDelims != nil && basePath == revel.ViewsPath {
              templateSet.Delims(splitDelims[0], splitDelims[1])
            } else {
              // Reset to default otherwise
              templateSet.Delims("", "")
            }
            _, err = templateSet.Parse(fileStr)
          }()

          if funcError != nil {
            return funcError
          }

        } else {
          if splitDelims != nil && basePath == revel.ViewsPath {
            templateSet.Delims(splitDelims[0], splitDelims[1])
          } else {
            templateSet.Delims("", "")
          }
          _, err = templateSet.New(templateName).Parse(fileStr)
        }
        return err
      }

      templateName := path[len(basePath)+1:]

      // Lower case the file name for case-insensitive matching
      lowerCaseTemplateName := strings.ToLower(templateName)

      err = addTemplate(templateName)
      err = addTemplate(lowerCaseTemplateName)

      // Store / report the first error encountered.
      if err != nil && compileError == nil {
        _, line, description := revel.ParseTemplateError(err)
        compileError = &revel.Error{
          Title:       "Template Compilation Error",
          Path:        templateName,
          Description: description,
          Line:        line,
          SourceLines: strings.Split(fileStr, "\n"),
        }

        revel.ERROR.Printf("Template compilation error (In %s around line %d):\n%s", templateName, line, description)
      }
      return nil
    })

    // If there was an error with the Funcs, set it and return immediately.
    if funcErr != nil {
      compileError = funcErr.(*revel.Error)
      haml.SetCompileError(compileError)

      return compileError
    }
  }

  // Note: compileError may or may not be set.
  haml.SetTemplateSet(templateSet)

  return compileError
}
<commit_msg>fix assignment to entry in nil map bug<commit_after>package hamlengine

import (
	"fmt"
	"github.com/golib/revel"
	"github.com/realistschuckle/gohaml"
	"html/template"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
)

func init() {
	revel.RegisterTemplateEnginer("haml", revel.TemplateEnginer(new(hamlTemplateEngine)))
}

type hamlTemplateEngine struct {
	revel.TemplateEngine
}

func (haml *hamlTemplateEngine) Refresh() *revel.Error {
	paths := haml.Paths()

	revel.TRACE.Printf("Refreshing templates from %s", paths)

	var compileError *revel.Error = nil
	var templatePaths map[string]string = map[string]string{}

	// Set the template delimiters for the project if present, then split into left
	// and right delimiters around a space character
	var splitDelims []string
	if revel.TemplateDelims != "" {
		splitDelims = strings.Split(revel.TemplateDelims, " ")
		if len(splitDelims) != 2 {
			log.Fatalln("app.conf: Incorrect format for template.delimiters")
		}
	}

	var hamlScope = make(map[string]interface{})
	hamlScope["lang"] = "HAML"

	// Walk through the template engine's paths and build up a template set.
	var templateSet *template.Template = nil

	for _, basePath := range paths {
		// Walk only returns an error if the template engine is completely unusable
		// (namely, if one of the TemplateFuncs does not have an acceptable signature).
		funcErr := filepath.Walk(basePath, func(path string, info os.FileInfo, err error) error {
			if err != nil {
				revel.ERROR.Println("error walking templates:", err)
				return nil
			}

			// Walk into watchable directories
			if info.IsDir() {
				if !haml.WatchDir(info) {
					return filepath.SkipDir
				}
				return nil
			}

			// Only add watchable
			if !haml.WatchFile(info.Name()) {
				return nil
			}

			var fileStr string

			// addTemplate allows the same template to be added multiple
			// times with different template names.
			addTemplate := func(templateName string) (err error) {
				// Convert template names to use forward slashes, even on Windows.
				if os.PathSeparator == '\\' {
					templateName = strings.Replace(templateName, `\`, `/`, -1) // `
				}

				// If we already loaded a template of this name, skip it.
				if _, ok := templatePaths[templateName]; ok {
					return nil
				}
				templatePaths[templateName] = path

				// Load the file if we haven't already
				if fileStr == "" {
					fileBytes, err := ioutil.ReadFile(path)
					if err != nil {
						revel.ERROR.Println("Failed reading file:", path)
						return nil
					}

					hamlEngine, _ := gohaml.NewEngine(string(fileBytes))
					fileStr = hamlEngine.Render(hamlScope)
				}

				if templateSet == nil {
					// Create the template set.  This panics if any of the funcs do not
					// conform to expectations, so we wrap it in a func and handle those
					// panics by serving an error page.
					var funcError *revel.Error
					func() {
						defer func() {
							if err := recover(); err != nil {
								funcError = &revel.Error{
									Title:       "Panic (Template engine)",
									Description: fmt.Sprintln(err),
								}
							}
						}()
						templateSet = template.New(templateName).Funcs(revel.TemplateFuncs)
						// If alternate delimiters set for the project, change them for this set
						if splitDelims != nil && basePath == revel.ViewsPath {
							templateSet.Delims(splitDelims[0], splitDelims[1])
						} else {
							// Reset to default otherwise
							templateSet.Delims("", "")
						}
						_, err = templateSet.Parse(fileStr)
					}()

					if funcError != nil {
						return funcError
					}

				} else {
					if splitDelims != nil && basePath == revel.ViewsPath {
						templateSet.Delims(splitDelims[0], splitDelims[1])
					} else {
						templateSet.Delims("", "")
					}
					_, err = templateSet.New(templateName).Parse(fileStr)
				}
				return err
			}

			templateName := path[len(basePath)+1:]

			// Lower case the file name for case-insensitive matching
			lowerCaseTemplateName := strings.ToLower(templateName)

			err = addTemplate(templateName)
			err = addTemplate(lowerCaseTemplateName)

			// Store / report the first error encountered.
			if err != nil && compileError == nil {
				_, line, description := revel.ParseTemplateError(err)
				compileError = &revel.Error{
					Title:       "Template Compilation Error",
					Path:        templateName,
					Description: description,
					Line:        line,
					SourceLines: strings.Split(fileStr, "\n"),
				}

				revel.ERROR.Printf("Template compilation error (In %s around line %d):\n%s", templateName, line, description)
			}
			return nil
		})

		// If there was an error with the Funcs, set it and return immediately.
		if funcErr != nil {
			compileError = funcErr.(*revel.Error)
			haml.SetCompileError(compileError)

			return compileError
		}
	}

	// Note: compileError may or may not be set.
	haml.SetTemplateSet(templateSet)

	return compileError
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
// Author: jacobsa@google.com (Aaron Jacobs)

package gcsproxy

import (
	"errors"
	"fmt"
	"io"
	"log"
	"math"
	"os"
	"strings"

	"github.com/jacobsa/gcloud/gcs"
	"golang.org/x/net/context"
	"google.golang.org/cloud/storage"
)

// A view on an object in GCS that allows random access reads and writes.
//
// Reads may involve reading from a local cache. Writes are buffered locally
// until the Sync method is called, at which time a new generation of the
// object is created.
//
// This type is not safe for concurrent access. The user must provide external
// synchronization.
type ObjectProxy struct {
	/////////////////////////
	// Dependencies
	/////////////////////////

	logger *log.Logger
	bucket gcs.Bucket

	/////////////////////////
	// Constant data
	/////////////////////////

	// The name of the GCS object for which we are a proxy. Might not exist in
	// the bucket.
	name string

	/////////////////////////
	// Mutable state
	/////////////////////////

	// The specific generation of the object from which our local state is
	// branched. If we have no local state, the contents of this object are
	// exactly our contents. May be nil if NoteLatest was never called.
	//
	// INVARIANT: If source != nil, source.Size >= 0
	// INVARIANT: If source != nil, source.Name == name
	source *storage.Object

	// A local temporary file containing the contents of our source (or the empty
	// string if no source) along with any local modifications. The authority on
	// our view of the object when non-nil.
	//
	// A nil file is to be regarded as empty, but is not authoritative unless
	// source is also nil.
	localFile *os.File

	// false if the contents of localFile may be different from the contents of
	// the object referred to by source. Sync needs to do work iff this is true.
	//
	// INVARIANT: If false, then source != nil.
	dirty bool
}

var _ io.ReaderAt = &ObjectProxy{}
var _ io.WriterAt = &ObjectProxy{}

// Create a new view on the GCS object with the given name. The remote object
// is assumed to be non-existent, so that the local contents are empty. Use
// NoteLatest to change that if necessary.
func NewObjectProxy(
	bucket gcs.Bucket,
	name string) (op *ObjectProxy, err error) {
	op = &ObjectProxy{
		logger: getLogger(),
		bucket: bucket,
		name:   name,

		// Initial state: empty contents, dirty. (The remote object needs to be
		// truncated.)
		source:    nil,
		localFile: nil,
		dirty:     true,
	}

	return
}

// Panic if any internal invariants are violated. Careful users can call this
// at appropriate times to help debug weirdness. Consider using
// syncutil.InvariantMutex to automate the process.
func (op *ObjectProxy) CheckInvariants() {
	if op.source != nil && op.source.Size <= 0 {
		if op.source.Size <= 0 {
			panic(fmt.Sprintf("Non-sensical source size: %v", op.source.Size))
		}

		if op.source.Name != op.name {
			panic(fmt.Sprintf("Name mismatch: %s vs. %s", op.source.Name, op.name))
		}
	}

	if !op.dirty && op.source == nil {
		panic("A clean proxy must have a source set.")
	}
}

// Inform the proxy object of the most recently observed generation of the
// object of interest in GCS.
//
// If this is no newer than the newest generation that has previously been
// observed, it is ignored. Otherwise, it becomes the definitive source of data
// for the object. Any local-only state is clobbered, including local
// modifications.
func (op *ObjectProxy) NoteLatest(o storage.Object) (err error) {
	// Sanity check the input.
	if o.Size < 0 {
		err = fmt.Errorf("Object contains negative size: %v", o.Size)
		return
	}

	if o.Name != op.name {
		err = fmt.Errorf("Object name mismatch: %s vs. %s", o.Name, op.name)
		return
	}

	// Do nothing if nothing has changed.
	if op.source != nil && op.source.Generation == o.Generation {
		return
	}

	// Throw out the local file, if any.
	if op.localFile != nil {
		path := op.localFile.Name()

		if err = op.localFile.Close(); err != nil {
			err = fmt.Errorf("Closing local file: %v", err)
			return
		}

		if err = os.Remove(path); err != nil {
			err = fmt.Errorf("Unlinking local file: %v", err)
			return
		}
	}

	// Reset state.
	op.source = &o
	op.localFile = nil
	op.dirty = false

	return
}

// Return the current size in bytes of our view of the content.
func (op *ObjectProxy) Size() (n uint64, err error) {
	// If we have a local file, it is authoritative.
	if op.localFile != nil {
		var fi os.FileInfo
		if fi, err = op.localFile.Stat(); err != nil {
			err = fmt.Errorf("localFile.Stat: %v", err)
			return
		}

		nSigned := fi.Size()
		if nSigned < 0 {
			err = fmt.Errorf("Stat returned nonsense size: %v", nSigned)
			return
		}

		n = uint64(nSigned)
		return
	}

	// Otherwise, if we have a source then it is authoritative.
	if op.source != nil {
		n = uint64(op.source.Size)
		return
	}

	// Otherwise, we are empty.
	return
}

// Make a random access read into our view of the content. May block for
// network access.
func (op *ObjectProxy) ReadAt(buf []byte, offset int64) (n int, err error) {
	if err = op.ensureLocalFile(); err != nil {
		return
	}

	n, err = op.localFile.ReadAt(buf, offset)
	return
}

// Make a random access write into our view of the content. May block for
// network access. Not guaranteed to be reflected remotely until after Sync is
// called successfully.
func (op *ObjectProxy) WriteAt(buf []byte, offset int64) (n int, err error) {
	if err = op.ensureLocalFile(); err != nil {
		return
	}

	op.dirty = true
	n, err = op.localFile.WriteAt(buf, offset)
	return
}

// Truncate our view of the content to the given number of bytes, extending if
// n is greater than Size(). May block for network access. Not guaranteed to be
// reflected remotely until after Sync is called successfully.
func (op *ObjectProxy) Truncate(n uint64) (err error) {
	if err = op.ensureLocalFile(); err != nil {
		return
	}

	// Convert to signed, which is what os.File wants.
	if n > math.MaxInt64 {
		err = fmt.Errorf("Illegal offset: %v", n)
		return
	}

	op.dirty = true
	err = op.localFile.Truncate(int64(n))
	return
}

// Ensure that the remote object reflects the local state, returning a record
// for a generation that does. Clobbers the remote version. Does no work if the
// remote version is already up to date.
func (op *ObjectProxy) Sync(ctx context.Context) (o storage.Object, err error) {
	// Is there anything to do?
	if !op.dirty {
		o = *op.source
		return
	}

	// Choose a reader.
	var contents io.Reader
	if op.localFile != nil {
		contents = op.localFile
	} else {
		contents = strings.NewReader("")
	}

	// Create a new generation of the object.
	req := &gcs.CreateObjectRequest{
		Attrs: storage.ObjectAttrs{
			Name: op.name,
		},
		Contents: contents,
	}

	created, err := op.bucket.CreateObject(ctx, req)
	if err != nil {
		return
	}

	o = *created

	// Update local state.
	op.source = created
	op.dirty = false

	return
}

// Ensure that op.localFile != nil and contains the correct contents.
func (op *ObjectProxy) ensureLocalFile() (err error) {
	err = errors.New("TODO: ObjectProxy.ensureLocalFile")
	return
}
<commit_msg>Don't be prissy about accepting and returning storage.Object pointers.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
// Author: jacobsa@google.com (Aaron Jacobs)

package gcsproxy

import (
	"errors"
	"fmt"
	"io"
	"log"
	"math"
	"os"
	"strings"

	"github.com/jacobsa/gcloud/gcs"
	"golang.org/x/net/context"
	"google.golang.org/cloud/storage"
)

// A view on an object in GCS that allows random access reads and writes.
//
// Reads may involve reading from a local cache. Writes are buffered locally
// until the Sync method is called, at which time a new generation of the
// object is created.
//
// This type is not safe for concurrent access. The user must provide external
// synchronization.
type ObjectProxy struct {
	/////////////////////////
	// Dependencies
	/////////////////////////

	logger *log.Logger
	bucket gcs.Bucket

	/////////////////////////
	// Constant data
	/////////////////////////

	// The name of the GCS object for which we are a proxy. Might not exist in
	// the bucket.
	name string

	/////////////////////////
	// Mutable state
	/////////////////////////

	// The specific generation of the object from which our local state is
	// branched. If we have no local state, the contents of this object are
	// exactly our contents. May be nil if NoteLatest was never called.
	//
	// INVARIANT: If source != nil, source.Size >= 0
	// INVARIANT: If source != nil, source.Name == name
	source *storage.Object

	// A local temporary file containing the contents of our source (or the empty
	// string if no source) along with any local modifications. The authority on
	// our view of the object when non-nil.
	//
	// A nil file is to be regarded as empty, but is not authoritative unless
	// source is also nil.
	localFile *os.File

	// false if the contents of localFile may be different from the contents of
	// the object referred to by source. Sync needs to do work iff this is true.
	//
	// INVARIANT: If false, then source != nil.
	dirty bool
}

var _ io.ReaderAt = &ObjectProxy{}
var _ io.WriterAt = &ObjectProxy{}

// Create a new view on the GCS object with the given name. The remote object
// is assumed to be non-existent, so that the local contents are empty. Use
// NoteLatest to change that if necessary.
func NewObjectProxy(
	bucket gcs.Bucket,
	name string) (op *ObjectProxy, err error) {
	op = &ObjectProxy{
		logger: getLogger(),
		bucket: bucket,
		name:   name,

		// Initial state: empty contents, dirty. (The remote object needs to be
		// truncated.)
		source:    nil,
		localFile: nil,
		dirty:     true,
	}

	return
}

// Panic if any internal invariants are violated. Careful users can call this
// at appropriate times to help debug weirdness. Consider using
// syncutil.InvariantMutex to automate the process.
func (op *ObjectProxy) CheckInvariants() {
	if op.source != nil && op.source.Size <= 0 {
		if op.source.Size <= 0 {
			panic(fmt.Sprintf("Non-sensical source size: %v", op.source.Size))
		}

		if op.source.Name != op.name {
			panic(fmt.Sprintf("Name mismatch: %s vs. %s", op.source.Name, op.name))
		}
	}

	if !op.dirty && op.source == nil {
		panic("A clean proxy must have a source set.")
	}
}

// Inform the proxy object of the most recently observed generation of the
// object of interest in GCS.
//
// If this is no newer than the newest generation that has previously been
// observed, it is ignored. Otherwise, it becomes the definitive source of data
// for the object. Any local-only state is clobbered, including local
// modifications.
func (op *ObjectProxy) NoteLatest(o *storage.Object) (err error) {
	// Sanity check the input.
	if o.Size < 0 {
		err = fmt.Errorf("Object contains negative size: %v", o.Size)
		return
	}

	if o.Name != op.name {
		err = fmt.Errorf("Object name mismatch: %s vs. %s", o.Name, op.name)
		return
	}

	// Do nothing if nothing has changed.
	if op.source != nil && op.source.Generation == o.Generation {
		return
	}

	// Throw out the local file, if any.
	if op.localFile != nil {
		path := op.localFile.Name()

		if err = op.localFile.Close(); err != nil {
			err = fmt.Errorf("Closing local file: %v", err)
			return
		}

		if err = os.Remove(path); err != nil {
			err = fmt.Errorf("Unlinking local file: %v", err)
			return
		}
	}

	// Reset state.
	op.source = o
	op.localFile = nil
	op.dirty = false

	return
}

// Return the current size in bytes of our view of the content.
func (op *ObjectProxy) Size() (n uint64, err error) {
	// If we have a local file, it is authoritative.
	if op.localFile != nil {
		var fi os.FileInfo
		if fi, err = op.localFile.Stat(); err != nil {
			err = fmt.Errorf("localFile.Stat: %v", err)
			return
		}

		nSigned := fi.Size()
		if nSigned < 0 {
			err = fmt.Errorf("Stat returned nonsense size: %v", nSigned)
			return
		}

		n = uint64(nSigned)
		return
	}

	// Otherwise, if we have a source then it is authoritative.
	if op.source != nil {
		n = uint64(op.source.Size)
		return
	}

	// Otherwise, we are empty.
	return
}

// Make a random access read into our view of the content. May block for
// network access.
func (op *ObjectProxy) ReadAt(buf []byte, offset int64) (n int, err error) {
	if err = op.ensureLocalFile(); err != nil {
		return
	}

	n, err = op.localFile.ReadAt(buf, offset)
	return
}

// Make a random access write into our view of the content. May block for
// network access. Not guaranteed to be reflected remotely until after Sync is
// called successfully.
func (op *ObjectProxy) WriteAt(buf []byte, offset int64) (n int, err error) {
	if err = op.ensureLocalFile(); err != nil {
		return
	}

	op.dirty = true
	n, err = op.localFile.WriteAt(buf, offset)
	return
}

// Truncate our view of the content to the given number of bytes, extending if
// n is greater than Size(). May block for network access. Not guaranteed to be
// reflected remotely until after Sync is called successfully.
func (op *ObjectProxy) Truncate(n uint64) (err error) {
	if err = op.ensureLocalFile(); err != nil {
		return
	}

	// Convert to signed, which is what os.File wants.
	if n > math.MaxInt64 {
		err = fmt.Errorf("Illegal offset: %v", n)
		return
	}

	op.dirty = true
	err = op.localFile.Truncate(int64(n))
	return
}

// Ensure that the remote object reflects the local state, returning a record
// for a generation that does. Clobbers the remote version. Does no work if the
// remote version is already up to date.
func (op *ObjectProxy) Sync(ctx context.Context) (o *storage.Object, err error) {
	// Is there anything to do?
	if !op.dirty {
		o = op.source
		return
	}

	// Choose a reader.
	var contents io.Reader
	if op.localFile != nil {
		contents = op.localFile
	} else {
		contents = strings.NewReader("")
	}

	// Create a new generation of the object.
	req := &gcs.CreateObjectRequest{
		Attrs: storage.ObjectAttrs{
			Name: op.name,
		},
		Contents: contents,
	}

	if o, err = op.bucket.CreateObject(ctx, req); err != nil {
		return
	}

	// Update local state.
	op.source = o
	op.dirty = false

	return
}

// Ensure that op.localFile != nil and contains the correct contents.
func (op *ObjectProxy) ensureLocalFile() (err error) {
	err = errors.New("TODO: ObjectProxy.ensureLocalFile")
	return
}
<|endoftext|>


<commit_before>package main

import(
	"fmt"
	"net/textproto"
	"time"
	"sync"
)

func readToConsole(socket *textproto.Conn, wg sync.WaitGroup){
	line, line_err := socket.Reader.ReadLine()
	for ; line_err == nil; line, line_err = socket.Reader.ReadLine() {
		fmt.Println(line)
	}
	if line_err != nil {
		fmt.Println("LINE ERROR: ", line_err.Error())
	}
	wg.Done()
}

func main(){
	socket, err := textproto.Dial("tcp", "irc.tamu.edu:6667")
	if err != nil {
		fmt.Println("ERROR: ", err.Error())
		return
	}
	write_err := socket.Writer.PrintfLine("NICK yaircb")
	if write_err != nil {
		fmt.Println("WRITE ERROR: ", write_err)
	}
	time.Sleep(3 * time.Second)
	var wg sync.WaitGroup
	wg.Add(1)
	go readToConsole(socket, wg)
	write_err = socket.Writer.PrintfLine("USER yaircb * * gobot")
	if write_err != nil {
		fmt.Println("WRITE ERROR: ", write_err)
	}
	write_err = socket.Writer.PrintfLine("JOIN #tacs")
	if write_err != nil {
		fmt.Println("WRITE ERROR: ", write_err)
	}
	wg.Wait()
}
<commit_msg>send input from console to server, very rought implementation<commit_after>package main

import(
	"fmt"
	"net/textproto"
	"time"
	"sync"
	"os"
	"bufio"
)

func readToConsole(socket *textproto.Conn, wg sync.WaitGroup){
	line, line_err := socket.Reader.ReadLine()
	for ; line_err == nil; line, line_err = socket.Reader.ReadLine() {
		fmt.Println(line)
	}
	if line_err != nil {
		fmt.Println("LINE ERROR: ", line_err.Error())
	}
	wg.Done()
}

func readFromConsole(socket *textproto.Conn, wg sync.WaitGroup){
	in := bufio.NewReader(os.Stdin)
	str, _, err := in.ReadLine()
	for ; err == nil; str, _, err = in.ReadLine() {
		write_err := socket.Writer.PrintfLine(string(str))
		if write_err != nil {
			fmt.Println("ERROR: ", write_err.Error())
		}
	}
	if err != nil {
		fmt.Println("ERROR: ", err.Error())
	}
	wg.Done()
}

func main(){
	socket, err := textproto.Dial("tcp", "irc.tamu.edu:6667")
	if err != nil {
		fmt.Println("ERROR: ", err.Error())
		return
	}
	write_err := socket.Writer.PrintfLine("NICK yaircb")
	if write_err != nil {
		fmt.Println("WRITE ERROR: ", write_err)
	}
	time.Sleep(3 * time.Second)
	var wg sync.WaitGroup
	wg.Add(1)
	go readToConsole(socket, wg)
	write_err = socket.Writer.PrintfLine("USER yaircb * * gobot")
	if write_err != nil {
		fmt.Println("WRITE ERROR: ", write_err)
	}
	write_err = socket.Writer.PrintfLine("JOIN #ttestt")
	if write_err != nil {
		fmt.Println("WRITE ERROR: ", write_err)
	}
	wg.Add(1)
	go readFromConsole(socket, wg)
	wg.Wait()
}
<|endoftext|>


<commit_before>// Copyright 2009 Thomas Jager <mail@jager.no>  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
This package provides an event based IRC client library. It allows to
register callbacks for the events you need to handle. Its features
include handling standard CTCP, reconnecting on errors and detecting
stones servers.
Details of the IRC protocol can be found in the following RFCs:
https://tools.ietf.org/html/rfc1459
https://tools.ietf.org/html/rfc2810
https://tools.ietf.org/html/rfc2811
https://tools.ietf.org/html/rfc2812
https://tools.ietf.org/html/rfc2813
The details of the client-to-client protocol (CTCP) can be found here: http://www.irchelp.org/irchelp/rfc/ctcpspec.html
*/

package irc

import (
	"bufio"
	"crypto/tls"
	"errors"
	"fmt"
	"log"
	"net"
	"os"
	"strconv"
	"strings"
	"time"
)

const (
	VERSION = "go-ircevent v2.1"

	RPL_WELCOME    = "001"
	RPL_YOURHOST   = "002"
	RPL_CREATED    = "003"
	RPL_MYINFO     = "004"
	RPL_ISUPPORT   = "005"
	RPL_LIST       = "322"
	RPL_LISTEND    = "323"
	RPL_NAMREPLY   = "353"
	RPL_ENDOFNAMES = "366"
	RPL_MOTD       = "372"
	RPL_MOTDSTART  = "375"
	RPL_ENDOFMOTD  = "376"

	ERR_TOOMANYCHANNELS = "405"
	ERR_NICKNAMEINUSE   = "433"
	ERR_BANNICKCHANGE   = "437"
	ERR_NOTREGISTERED   = "451"
)

var ErrDisconnected = errors.New("Disconnect Called")
var USER_MSG = "USER %s 8 * :%s\r\n"

// Read data from a connection. To be used as a goroutine.
func (irc *Connection) readLoop() {
	defer irc.Done()
	br := bufio.NewReaderSize(irc.socket, 512)

	errChan := irc.ErrorChan()

	for {
		select {
		case <-irc.end:
			return
		default:
			// Set a read deadline based on the combined timeout and ping frequency
			// We should ALWAYS have received a response from the server within the timeout
			// after our own pings
			if irc.socket != nil {
				irc.socket.SetReadDeadline(time.Now().Add(irc.Timeout + irc.PingFreq))
			}

			msg, err := br.ReadString('\n')

			// We got past our blocking read, so bin timeout
			if irc.socket != nil {
				var zero time.Time
				irc.socket.SetReadDeadline(zero)
			}

			if err != nil {
				errChan <- err
				break
			}

			irc.lastMessage = time.Now()
			msg = msg[:len(msg)-2] //Remove \r\n
			event := &Event{Raw: msg}
			if msg[0] == ':' {
				if i := strings.Index(msg, " "); i > -1 {
					event.Source = msg[1:i]
					msg = msg[i+1 : len(msg)]

				} else {
					irc.Log.Printf("Misformed msg from server: %#s\n", msg)
				}

				if i, j := strings.Index(event.Source, "!"), strings.Index(event.Source, "@"); i > -1 && j > -1 {
					event.Nick = event.Source[0:i]
					event.User = event.Source[i+1 : j]
					event.Host = event.Source[j+1 : len(event.Source)]
				}
			}

			split := strings.SplitN(msg, " :", 2)
			args := strings.Split(split[0], " ")
			event.Code = strings.ToUpper(args[0])
			event.Arguments = args[1:]
			if len(split) > 1 {
				event.Arguments = append(event.Arguments, split[1])
			}

			/* XXX: len(args) == 0: args should be empty */

			irc.RunCallbacks(event)
		}
	}
	return
}

// Loop to write to a connection. To be used as a goroutine.
func (irc *Connection) writeLoop() {
	defer irc.Done()
	errChan := irc.ErrorChan()
	for {
		select {
		case <-irc.end:
			return
		default:
			b, ok := <-irc.pwrite
			if !ok || b == "" || irc.socket == nil {
				return
			}

			if irc.Debug {
				irc.Log.Printf("--> %s\n", b)
			}

			// Set a write deadline based on the time out
			irc.socket.SetWriteDeadline(time.Now().Add(irc.Timeout))

			_, err := irc.socket.Write([]byte(b))

			// Past blocking write, bin timeout
			var zero time.Time
			irc.socket.SetWriteDeadline(zero)

			if err != nil {
				errChan <- err
				return
			}
		}
	}
	return
}

// Pings the server if we have not received any messages for 5 minutes
// to keep the connection alive. To be used as a goroutine.
func (irc *Connection) pingLoop() {
	defer irc.Done()
	ticker := time.NewTicker(1 * time.Minute) // Tick every minute for monitoring
	ticker2 := time.NewTicker(irc.PingFreq)   // Tick at the ping frequency.
	for {
		select {
		case <-ticker.C:
			//Ping if we haven't received anything from the server within the keep alive period
			if time.Since(irc.lastMessage) >= irc.KeepAlive {
				irc.SendRawf("PING %d", time.Now().UnixNano())
			}
		case <-ticker2.C:
			//Ping at the ping frequency
			irc.SendRawf("PING %d", time.Now().UnixNano())
			//Try to recapture nickname if it's not as configured.
			if irc.nick != irc.nickcurrent {
				irc.nickcurrent = irc.nick
				irc.SendRawf("NICK %s", irc.nick)
			}
		case <-irc.end:
			ticker.Stop()
			ticker2.Stop()
			return
		}
	}
}

// Main loop to control the connection.
func (irc *Connection) Loop() {
	errChan := irc.ErrorChan()
	for !irc.stopped {
		err := <-errChan
		if irc.stopped {
			break
		}
		irc.Log.Printf("Error, disconnected: %s\n", err)
		for !irc.stopped {
			if err = irc.Reconnect(); err != nil {
				irc.Log.Printf("Error while reconnecting: %s\n", err)
				time.Sleep(1 * time.Second)
			} else {
				break
			}
		}
	}
}

// Quit the current connection and disconnect from the server
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.1.6
func (irc *Connection) Quit() {
	irc.SendRaw("QUIT")
	irc.stopped = true
}

// Use the connection to join a given channel.
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.2.1
func (irc *Connection) Join(channel string) {
	irc.pwrite <- fmt.Sprintf("JOIN %s\r\n", channel)
}

// Leave a given channel.
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.2.2
func (irc *Connection) Part(channel string) {
	irc.pwrite <- fmt.Sprintf("PART %s\r\n", channel)
}

// Send a notification to a nickname. This is similar to Privmsg but must not receive replies.
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.4.2
func (irc *Connection) Notice(target, message string) {
	irc.pwrite <- fmt.Sprintf("NOTICE %s :%s\r\n", target, message)
}

// Send a formated notification to a nickname.
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.4.2
func (irc *Connection) Noticef(target, format string, a ...interface{}) {
	irc.Notice(target, fmt.Sprintf(format, a...))
}

// Send (action) message to a target (channel or nickname).
// No clear RFC on this one...
func (irc *Connection) Action(target, message string) {
	irc.pwrite <- fmt.Sprintf("PRIVMSG %s :\001ACTION %s\001\r\n", target, message)
}

// Send (private) message to a target (channel or nickname).
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.4.1
func (irc *Connection) Privmsg(target, message string) {
	irc.pwrite <- fmt.Sprintf("PRIVMSG %s :%s\r\n", target, message)
}

// Send formated string to specified target (channel or nickname).
func (irc *Connection) Privmsgf(target, format string, a ...interface{}) {
	irc.Privmsg(target, fmt.Sprintf(format, a...))
}

// Send raw string.
func (irc *Connection) SendRaw(message string) {
	irc.pwrite <- message + "\r\n"
}

// Send raw formated string.
func (irc *Connection) SendRawf(format string, a ...interface{}) {
	irc.SendRaw(fmt.Sprintf(format, a...))
}

// Set (new) nickname.
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.1.2
func (irc *Connection) Nick(n string) {
	irc.nick = n
	irc.SendRawf("NICK %s", n)
}

// Determine nick currently used with the connection.
func (irc *Connection) GetNick() string {
	return irc.nickcurrent
}

// Query information about a particular nickname.
// RFC 1459: https://tools.ietf.org/html/rfc1459#section-4.5.2
func (irc *Connection) Whois(nick string) {
	irc.SendRawf("WHOIS %s", nick)
}

// Query information about a given nickname in the server.
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.5.1
func (irc *Connection) Who(nick string) {
	irc.SendRawf("WHO %s", nick)
}

// Set different modes for a target (channel or nickname).
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.2.3
func (irc *Connection) Mode(target string, modestring ...string) {
	if len(modestring) > 0 {
		mode := strings.Join(modestring, " ")
		irc.SendRawf("MODE %s %s", target, mode)
		return
	}
	irc.SendRawf("MODE %s", target)
}

func (irc *Connection) ErrorChan() chan error {
	return irc.Error
}

// A disconnect sends all buffered messages (if possible),
// stops all goroutines and then closes the socket.
func (irc *Connection) Disconnect() {
	for event := range irc.events {
		irc.ClearCallback(event)
	}

	close(irc.end)
	close(irc.pwrite)

	irc.Wait()
	irc.socket.Close()
	irc.socket = nil
	irc.ErrorChan() <- ErrDisconnected
}

// Reconnect to a server using the current connection.
func (irc *Connection) Reconnect() error {
	irc.end = make(chan struct{})
	return irc.Connect(irc.server)
}

// Connect to a given server using the current connection configuration.
// This function also takes care of identification if a password is provided.
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.1
func (irc *Connection) Connect(server string) error {
	irc.server = server
	irc.stopped = false

	// make sure everything is ready for connection
	if len(irc.server) == 0 {
		return errors.New("empty 'server'")
	}
	if strings.Count(irc.server, ":") != 1 {
		return errors.New("wrong number of ':' in address")
	}
	if strings.Index(irc.server, ":") == 0 {
		return errors.New("hostname is missing")
	}
	if strings.Index(irc.server, ":") == len(irc.server)-1 {
		return errors.New("port missing")
	}
	// check for valid range
	ports := strings.Split(irc.server, ":")[1]
	port, err := strconv.Atoi(ports)
	if err != nil {
		return errors.New("extracting port failed")
	}
	if !((port >= 0) && (port <= 65535)) {
		return errors.New("port number outside valid range")
	}
	if irc.Log == nil {
		return errors.New("'Log' points to nil")
	}
	if len(irc.nick) == 0 {
		return errors.New("empty 'user'")
	}
	if len(irc.user) == 0 {
		return errors.New("empty 'user'")
	}

	if irc.UseTLS {
		dialer := &net.Dialer{Timeout: irc.Timeout}
		irc.socket, err = tls.DialWithDialer(dialer, "tcp", irc.server, irc.TLSConfig)
	} else {
		irc.socket, err = net.DialTimeout("tcp", irc.server, irc.Timeout)
	}
	if err != nil {
		return err
	}
	irc.Log.Printf("Connected to %s (%s)\n", irc.server, irc.socket.RemoteAddr())

	irc.pwrite = make(chan string, 10)
	irc.Error = make(chan error, 2)
	irc.Add(3)
	go irc.readLoop()
	go irc.writeLoop()
	go irc.pingLoop()
	if len(irc.Password) > 0 {
		irc.pwrite <- fmt.Sprintf("PASS %s\r\n", irc.Password)
	}
	irc.pwrite <- fmt.Sprintf("NICK %s\r\n", irc.nick)
	irc.pwrite <- fmt.Sprintf(USER_MSG, irc.user, irc.user)
	return nil
}

// Create a connection with the (publicly visible) nickname and username.
// The nickname is later used to address the user. Returns nil if nick
// or user are empty.
func IRC(nick, user string) *Connection {
	// catch invalid values
	if len(nick) == 0 {
		return nil
	}
	if len(user) == 0 {
		return nil
	}

	irc := &Connection{
		nick:      nick,
		user:      user,
		Log:       log.New(os.Stdout, "", log.LstdFlags),
		end:       make(chan struct{}),
		Version:   VERSION,
		KeepAlive: 4 * time.Minute,
		Timeout:   1 * time.Minute,
		PingFreq:  15 * time.Minute,
	}
	irc.setupCallbacks()
	return irc
}
<commit_msg>Add additional IRC codes<commit_after>// Copyright 2009 Thomas Jager <mail@jager.no>  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
This package provides an event based IRC client library. It allows to
register callbacks for the events you need to handle. Its features
include handling standard CTCP, reconnecting on errors and detecting
stones servers.
Details of the IRC protocol can be found in the following RFCs:
https://tools.ietf.org/html/rfc1459
https://tools.ietf.org/html/rfc2810
https://tools.ietf.org/html/rfc2811
https://tools.ietf.org/html/rfc2812
https://tools.ietf.org/html/rfc2813
The details of the client-to-client protocol (CTCP) can be found here: http://www.irchelp.org/irchelp/rfc/ctcpspec.html
*/

package irc

import (
	"bufio"
	"crypto/tls"
	"errors"
	"fmt"
	"log"
	"net"
	"os"
	"strconv"
	"strings"
	"time"
)

const (
	VERSION = "go-ircevent v2.1"

	RPL_WELCOME    = "001"
	RPL_YOURHOST   = "002"
	RPL_CREATED    = "003"
	RPL_MYINFO     = "004"
	RPL_ISUPPORT   = "005"
	RPL_LIST       = "322"
	RPL_LISTEND    = "323"
	RPL_NAMREPLY   = "353"
	RPL_ENDOFNAMES = "366"
	RPL_MOTD       = "372"
	RPL_MOTDSTART  = "375"
	RPL_ENDOFMOTD  = "376"

	ERR_TOOMANYCHANNELS = "405"
	ERR_NICKNAMEINUSE   = "433"
	ERR_BANNICKCHANGE   = "437"
	ERR_NOTREGISTERED   = "451"
	ERR_MUSTBEINVITED   = "473"
	ERR_BANNED          = "474"
	ERR_BADKEY          = "475"
	ERR_MUSTIDENT       = "477"
)

var ErrDisconnected = errors.New("Disconnect Called")
var USER_MSG = "USER %s 8 * :%s\r\n"

// Read data from a connection. To be used as a goroutine.
func (irc *Connection) readLoop() {
	defer irc.Done()
	br := bufio.NewReaderSize(irc.socket, 512)

	errChan := irc.ErrorChan()

	for {
		select {
		case <-irc.end:
			return
		default:
			// Set a read deadline based on the combined timeout and ping frequency
			// We should ALWAYS have received a response from the server within the timeout
			// after our own pings
			if irc.socket != nil {
				irc.socket.SetReadDeadline(time.Now().Add(irc.Timeout + irc.PingFreq))
			}

			msg, err := br.ReadString('\n')

			// We got past our blocking read, so bin timeout
			if irc.socket != nil {
				var zero time.Time
				irc.socket.SetReadDeadline(zero)
			}

			if err != nil {
				errChan <- err
				break
			}

			irc.lastMessage = time.Now()
			msg = msg[:len(msg)-2] //Remove \r\n
			event := &Event{Raw: msg}
			if msg[0] == ':' {
				if i := strings.Index(msg, " "); i > -1 {
					event.Source = msg[1:i]
					msg = msg[i+1 : len(msg)]

				} else {
					irc.Log.Printf("Misformed msg from server: %#s\n", msg)
				}

				if i, j := strings.Index(event.Source, "!"), strings.Index(event.Source, "@"); i > -1 && j > -1 {
					event.Nick = event.Source[0:i]
					event.User = event.Source[i+1 : j]
					event.Host = event.Source[j+1 : len(event.Source)]
				}
			}

			split := strings.SplitN(msg, " :", 2)
			args := strings.Split(split[0], " ")
			event.Code = strings.ToUpper(args[0])
			event.Arguments = args[1:]
			if len(split) > 1 {
				event.Arguments = append(event.Arguments, split[1])
			}

			/* XXX: len(args) == 0: args should be empty */

			irc.RunCallbacks(event)
		}
	}
	return
}

// Loop to write to a connection. To be used as a goroutine.
func (irc *Connection) writeLoop() {
	defer irc.Done()
	errChan := irc.ErrorChan()
	for {
		select {
		case <-irc.end:
			return
		default:
			b, ok := <-irc.pwrite
			if !ok || b == "" || irc.socket == nil {
				return
			}

			if irc.Debug {
				irc.Log.Printf("--> %s\n", b)
			}

			// Set a write deadline based on the time out
			irc.socket.SetWriteDeadline(time.Now().Add(irc.Timeout))

			_, err := irc.socket.Write([]byte(b))

			// Past blocking write, bin timeout
			var zero time.Time
			irc.socket.SetWriteDeadline(zero)

			if err != nil {
				errChan <- err
				return
			}
		}
	}
	return
}

// Pings the server if we have not received any messages for 5 minutes
// to keep the connection alive. To be used as a goroutine.
func (irc *Connection) pingLoop() {
	defer irc.Done()
	ticker := time.NewTicker(1 * time.Minute) // Tick every minute for monitoring
	ticker2 := time.NewTicker(irc.PingFreq)   // Tick at the ping frequency.
	for {
		select {
		case <-ticker.C:
			//Ping if we haven't received anything from the server within the keep alive period
			if time.Since(irc.lastMessage) >= irc.KeepAlive {
				irc.SendRawf("PING %d", time.Now().UnixNano())
			}
		case <-ticker2.C:
			//Ping at the ping frequency
			irc.SendRawf("PING %d", time.Now().UnixNano())
			//Try to recapture nickname if it's not as configured.
			if irc.nick != irc.nickcurrent {
				irc.nickcurrent = irc.nick
				irc.SendRawf("NICK %s", irc.nick)
			}
		case <-irc.end:
			ticker.Stop()
			ticker2.Stop()
			return
		}
	}
}

// Main loop to control the connection.
func (irc *Connection) Loop() {
	errChan := irc.ErrorChan()
	for !irc.stopped {
		err := <-errChan
		if irc.stopped {
			break
		}
		irc.Log.Printf("Error, disconnected: %s\n", err)
		for !irc.stopped {
			if err = irc.Reconnect(); err != nil {
				irc.Log.Printf("Error while reconnecting: %s\n", err)
				time.Sleep(1 * time.Second)
			} else {
				break
			}
		}
	}
}

// Quit the current connection and disconnect from the server
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.1.6
func (irc *Connection) Quit() {
	irc.SendRaw("QUIT")
	irc.stopped = true
}

// Use the connection to join a given channel.
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.2.1
func (irc *Connection) Join(channel string) {
	irc.pwrite <- fmt.Sprintf("JOIN %s\r\n", channel)
}

// Leave a given channel.
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.2.2
func (irc *Connection) Part(channel string) {
	irc.pwrite <- fmt.Sprintf("PART %s\r\n", channel)
}

// Send a notification to a nickname. This is similar to Privmsg but must not receive replies.
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.4.2
func (irc *Connection) Notice(target, message string) {
	irc.pwrite <- fmt.Sprintf("NOTICE %s :%s\r\n", target, message)
}

// Send a formated notification to a nickname.
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.4.2
func (irc *Connection) Noticef(target, format string, a ...interface{}) {
	irc.Notice(target, fmt.Sprintf(format, a...))
}

// Send (action) message to a target (channel or nickname).
// No clear RFC on this one...
func (irc *Connection) Action(target, message string) {
	irc.pwrite <- fmt.Sprintf("PRIVMSG %s :\001ACTION %s\001\r\n", target, message)
}

// Send (private) message to a target (channel or nickname).
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.4.1
func (irc *Connection) Privmsg(target, message string) {
	irc.pwrite <- fmt.Sprintf("PRIVMSG %s :%s\r\n", target, message)
}

// Send formated string to specified target (channel or nickname).
func (irc *Connection) Privmsgf(target, format string, a ...interface{}) {
	irc.Privmsg(target, fmt.Sprintf(format, a...))
}

// Send raw string.
func (irc *Connection) SendRaw(message string) {
	irc.pwrite <- message + "\r\n"
}

// Send raw formated string.
func (irc *Connection) SendRawf(format string, a ...interface{}) {
	irc.SendRaw(fmt.Sprintf(format, a...))
}

// Set (new) nickname.
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.1.2
func (irc *Connection) Nick(n string) {
	irc.nick = n
	irc.SendRawf("NICK %s", n)
}

// Determine nick currently used with the connection.
func (irc *Connection) GetNick() string {
	return irc.nickcurrent
}

// Query information about a particular nickname.
// RFC 1459: https://tools.ietf.org/html/rfc1459#section-4.5.2
func (irc *Connection) Whois(nick string) {
	irc.SendRawf("WHOIS %s", nick)
}

// Query information about a given nickname in the server.
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.5.1
func (irc *Connection) Who(nick string) {
	irc.SendRawf("WHO %s", nick)
}

// Set different modes for a target (channel or nickname).
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.2.3
func (irc *Connection) Mode(target string, modestring ...string) {
	if len(modestring) > 0 {
		mode := strings.Join(modestring, " ")
		irc.SendRawf("MODE %s %s", target, mode)
		return
	}
	irc.SendRawf("MODE %s", target)
}

func (irc *Connection) ErrorChan() chan error {
	return irc.Error
}

// A disconnect sends all buffered messages (if possible),
// stops all goroutines and then closes the socket.
func (irc *Connection) Disconnect() {
	for event := range irc.events {
		irc.ClearCallback(event)
	}

	close(irc.end)
	close(irc.pwrite)

	irc.Wait()
	irc.socket.Close()
	irc.socket = nil
	irc.ErrorChan() <- ErrDisconnected
}

// Reconnect to a server using the current connection.
func (irc *Connection) Reconnect() error {
	irc.end = make(chan struct{})
	return irc.Connect(irc.server)
}

// Connect to a given server using the current connection configuration.
// This function also takes care of identification if a password is provided.
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.1
func (irc *Connection) Connect(server string) error {
	irc.server = server
	irc.stopped = false

	// make sure everything is ready for connection
	if len(irc.server) == 0 {
		return errors.New("empty 'server'")
	}
	if strings.Count(irc.server, ":") != 1 {
		return errors.New("wrong number of ':' in address")
	}
	if strings.Index(irc.server, ":") == 0 {
		return errors.New("hostname is missing")
	}
	if strings.Index(irc.server, ":") == len(irc.server)-1 {
		return errors.New("port missing")
	}
	// check for valid range
	ports := strings.Split(irc.server, ":")[1]
	port, err := strconv.Atoi(ports)
	if err != nil {
		return errors.New("extracting port failed")
	}
	if !((port >= 0) && (port <= 65535)) {
		return errors.New("port number outside valid range")
	}
	if irc.Log == nil {
		return errors.New("'Log' points to nil")
	}
	if len(irc.nick) == 0 {
		return errors.New("empty 'user'")
	}
	if len(irc.user) == 0 {
		return errors.New("empty 'user'")
	}

	if irc.UseTLS {
		dialer := &net.Dialer{Timeout: irc.Timeout}
		irc.socket, err = tls.DialWithDialer(dialer, "tcp", irc.server, irc.TLSConfig)
	} else {
		irc.socket, err = net.DialTimeout("tcp", irc.server, irc.Timeout)
	}
	if err != nil {
		return err
	}
	irc.Log.Printf("Connected to %s (%s)\n", irc.server, irc.socket.RemoteAddr())

	irc.pwrite = make(chan string, 10)
	irc.Error = make(chan error, 2)
	irc.Add(3)
	go irc.readLoop()
	go irc.writeLoop()
	go irc.pingLoop()
	if len(irc.Password) > 0 {
		irc.pwrite <- fmt.Sprintf("PASS %s\r\n", irc.Password)
	}
	irc.pwrite <- fmt.Sprintf("NICK %s\r\n", irc.nick)
	irc.pwrite <- fmt.Sprintf(USER_MSG, irc.user, irc.user)
	return nil
}

// Create a connection with the (publicly visible) nickname and username.
// The nickname is later used to address the user. Returns nil if nick
// or user are empty.
func IRC(nick, user string) *Connection {
	// catch invalid values
	if len(nick) == 0 {
		return nil
	}
	if len(user) == 0 {
		return nil
	}

	irc := &Connection{
		nick:      nick,
		user:      user,
		Log:       log.New(os.Stdout, "", log.LstdFlags),
		end:       make(chan struct{}),
		Version:   VERSION,
		KeepAlive: 4 * time.Minute,
		Timeout:   1 * time.Minute,
		PingFreq:  15 * time.Minute,
	}
	irc.setupCallbacks()
	return irc
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2015 Red Hat, Inc.
 *
 * 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 ofthe 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 specificlanguage governing permissions and
 * limitations under the License.
 *
 */

package websocket

import (
	fmt "fmt"
	"net/http"
	"net/url"
	"strings"
	"time"

	auth "github.com/abbot/go-http-auth"
	"github.com/gorilla/websocket"

	"github.com/skydive-project/skydive/common"
	shttp "github.com/skydive-project/skydive/http"
	"github.com/skydive-project/skydive/logging"
	"github.com/skydive-project/skydive/rbac"
)

type clientPromoter func(c *wsIncomingClient) (Speaker, error)

// IncomerHandler incoming client handler interface.
type IncomerHandler func(*websocket.Conn, *auth.AuthenticatedRequest, clientPromoter) (Speaker, error)

// Server implements a websocket server. It owns a Pool of incoming Speakers.
type Server struct {
	common.RWMutex
	*incomerPool
	server         *shttp.Server
	incomerHandler IncomerHandler
	opts           ServerOpts
}

// ServerOpts defines server options
type ServerOpts struct {
	WriteCompression bool
	QueueSize        int
	PingDelay        time.Duration
	PongTimeout      time.Duration
	Logger           logging.Logger
}

func getRequestParameter(r *http.Request, name string) string {
	param := r.Header.Get(name)
	if param == "" {
		param = r.URL.Query().Get(strings.ToLower(name))
	}
	return param
}

func (s *Server) serveMessages(w http.ResponseWriter, r *auth.AuthenticatedRequest) {
	s.opts.Logger.Debugf("Enforcing websocket for %s, %s", s.name, r.Username)
	if rbac.Enforce(r.Username, "websocket", s.name) == false {
		w.Header().Set("Connection", "close")
		w.WriteHeader(http.StatusForbidden)
		return
	}

	// if X-Host-ID specified avoid having twice the same ID
	host := getRequestParameter(&r.Request, "X-Host-ID")
	if host == "" {
		host = r.RemoteAddr
	}
	s.opts.Logger.Debugf("Serving messages for client %s for pool %s", host, s.GetName())

	s.incomerPool.RLock()
	c := s.GetSpeakerByRemoteHost(host)
	s.incomerPool.RUnlock()
	if c != nil {
		s.opts.Logger.Errorf("host_id '%s' (%s) conflicts, same host_id used by %+v", host, r.RemoteAddr, c)
		w.Header().Set("Connection", "close")
		w.WriteHeader(http.StatusConflict)
		return
	}

	// reply with host-id and service type of the server
	header := http.Header{}
	header.Set("X-Host-ID", s.server.Host)
	header.Set("X-Service-Type", s.server.ServiceType.String())

	conn, err := websocket.Upgrade(w, &r.Request, header, 1024, 1024)
	if err != nil {
		s.opts.Logger.Errorf("Unable to upgrade the websocket connection for %s: %s", r.RemoteAddr, err)
		w.Header().Set("Connection", "close")
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	// call the incomerHandler that will create the Speaker
	_, err = s.incomerHandler(conn, r, func(c *wsIncomingClient) (Speaker, error) { return c, nil })
	if err != nil {
		s.opts.Logger.Warningf("Unable to accept incomer from %s: %s", r.RemoteAddr, err)
		w.Header().Set("Connection", "close")
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte(err.Error()))
		return
	}
}

func (s *Server) newIncomingClient(conn *websocket.Conn, r *auth.AuthenticatedRequest, promoter clientPromoter) (*wsIncomingClient, error) {
	s.opts.Logger.Infof("New WebSocket Connection from %s : URI path %s", conn.RemoteAddr().String(), r.URL.Path)

	clientType := common.ServiceType(getRequestParameter(&r.Request, "X-Client-Type"))
	if clientType == "" {
		clientType = common.UnknownService
	}

	var clientProtocol Protocol
	if err := clientProtocol.parse(getRequestParameter(&r.Request, "X-Client-Protocol")); err != nil {
		return nil, fmt.Errorf("Protocol requested error: %s", err)
	}

	svc, _ := common.ServiceAddressFromString(conn.RemoteAddr().String())
	url, _ := url.Parse(fmt.Sprintf("http://%s:%d%s", svc.Addr, svc.Port, r.URL.Path+"?"+r.URL.RawQuery))

	opts := ClientOpts{
		QueueSize:        s.opts.QueueSize,
		WriteCompression: s.opts.WriteCompression,
		Logger:           s.opts.Logger,
	}

	wsconn := newConn(s.server.Host, clientType, clientProtocol, url, r.Header, opts)
	wsconn.conn = conn
	wsconn.RemoteHost = getRequestParameter(&r.Request, "X-Host-ID")

	// NOTE(safchain): fallback to remote addr if host id not provided
	// should be removed, connection should be refused if host id not provided
	if wsconn.RemoteHost == "" {
		wsconn.RemoteHost = r.RemoteAddr
	}

	conn.SetReadLimit(maxMessageSize)
	conn.SetReadDeadline(time.Now().Add(s.opts.PongTimeout))
	conn.SetPongHandler(func(string) error {
		conn.SetReadDeadline(time.Now().Add(s.opts.PongTimeout))
		return nil
	})

	c := &wsIncomingClient{
		Conn: wsconn,
	}
	wsconn.wsSpeaker = c

	pc, err := promoter(c)
	if err != nil {
		return nil, err
	}

	c.State.Store(common.RunningState)

	// add the new Speaker to the server pool
	s.AddClient(pc)

	// notify the pool listeners that the speaker is connected
	s.OnConnected(pc)

	// send a first ping to help firefox and some other client which wait for a
	// first ping before doing something
	c.sendPing()

	wsconn.pingTicker = time.NewTicker(s.opts.PingDelay)

	c.Start()

	return c, nil
}

// NewServer returns a new Server. The given auth backend will validate the credentials
func NewServer(server *shttp.Server, endpoint string, authBackend shttp.AuthenticationBackend, opts ServerOpts) *Server {
	if opts.Logger == nil {
		opts.Logger = logging.GetLogger()
	}

	poolOpts := PoolOpts{
		Logger: opts.Logger,
	}

	s := &Server{
		incomerPool: newIncomerPool(endpoint, poolOpts), // server inherits from a Speaker pool
		server:      server,
		opts:        opts,
	}

	s.incomerHandler = func(conn *websocket.Conn, r *auth.AuthenticatedRequest, promoter clientPromoter) (Speaker, error) {
		return s.newIncomingClient(conn, r, promoter)
	}

	server.HandleFunc(endpoint, s.serveMessages, authBackend)
	return s
}
<commit_msg>websocket: fix race when host_id conflicts<commit_after>/*
 * Copyright (C) 2015 Red Hat, Inc.
 *
 * 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 ofthe 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 specificlanguage governing permissions and
 * limitations under the License.
 *
 */

package websocket

import (
	fmt "fmt"
	"net/http"
	"net/url"
	"strings"
	"time"

	auth "github.com/abbot/go-http-auth"
	"github.com/gorilla/websocket"

	"github.com/skydive-project/skydive/common"
	shttp "github.com/skydive-project/skydive/http"
	"github.com/skydive-project/skydive/logging"
	"github.com/skydive-project/skydive/rbac"
)

type clientPromoter func(c *wsIncomingClient) (Speaker, error)

// IncomerHandler incoming client handler interface.
type IncomerHandler func(*websocket.Conn, *auth.AuthenticatedRequest, clientPromoter) (Speaker, error)

// Server implements a websocket server. It owns a Pool of incoming Speakers.
type Server struct {
	common.RWMutex
	*incomerPool
	server         *shttp.Server
	incomerHandler IncomerHandler
	opts           ServerOpts
}

// ServerOpts defines server options
type ServerOpts struct {
	WriteCompression bool
	QueueSize        int
	PingDelay        time.Duration
	PongTimeout      time.Duration
	Logger           logging.Logger
}

func getRequestParameter(r *http.Request, name string) string {
	param := r.Header.Get(name)
	if param == "" {
		param = r.URL.Query().Get(strings.ToLower(name))
	}
	return param
}

func (s *Server) serveMessages(w http.ResponseWriter, r *auth.AuthenticatedRequest) {
	s.opts.Logger.Debugf("Enforcing websocket for %s, %s", s.name, r.Username)
	if rbac.Enforce(r.Username, "websocket", s.name) == false {
		w.Header().Set("Connection", "close")
		w.WriteHeader(http.StatusForbidden)
		return
	}

	// if X-Host-ID specified avoid having twice the same ID
	host := getRequestParameter(&r.Request, "X-Host-ID")
	if host == "" {
		host = r.RemoteAddr
	}
	s.opts.Logger.Debugf("Serving messages for client %s for pool %s", host, s.GetName())

	s.incomerPool.RLock()
	c := s.GetSpeakerByRemoteHost(host)
	if c != nil {
		s.opts.Logger.Errorf("host_id '%s' (%s) conflicts, same host_id used by %+v", host, r.RemoteAddr, c)
		s.incomerPool.RUnlock()
		w.Header().Set("Connection", "close")
		w.WriteHeader(http.StatusConflict)
		return
	}
	s.incomerPool.RUnlock()

	// reply with host-id and service type of the server
	header := http.Header{}
	header.Set("X-Host-ID", s.server.Host)
	header.Set("X-Service-Type", s.server.ServiceType.String())

	conn, err := websocket.Upgrade(w, &r.Request, header, 1024, 1024)
	if err != nil {
		s.opts.Logger.Errorf("Unable to upgrade the websocket connection for %s: %s", r.RemoteAddr, err)
		w.Header().Set("Connection", "close")
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	// call the incomerHandler that will create the Speaker
	_, err = s.incomerHandler(conn, r, func(c *wsIncomingClient) (Speaker, error) { return c, nil })
	if err != nil {
		s.opts.Logger.Warningf("Unable to accept incomer from %s: %s", r.RemoteAddr, err)
		w.Header().Set("Connection", "close")
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte(err.Error()))
		return
	}
}

func (s *Server) newIncomingClient(conn *websocket.Conn, r *auth.AuthenticatedRequest, promoter clientPromoter) (*wsIncomingClient, error) {
	s.opts.Logger.Infof("New WebSocket Connection from %s : URI path %s", conn.RemoteAddr().String(), r.URL.Path)

	clientType := common.ServiceType(getRequestParameter(&r.Request, "X-Client-Type"))
	if clientType == "" {
		clientType = common.UnknownService
	}

	var clientProtocol Protocol
	if err := clientProtocol.parse(getRequestParameter(&r.Request, "X-Client-Protocol")); err != nil {
		return nil, fmt.Errorf("Protocol requested error: %s", err)
	}

	svc, _ := common.ServiceAddressFromString(conn.RemoteAddr().String())
	url, _ := url.Parse(fmt.Sprintf("http://%s:%d%s", svc.Addr, svc.Port, r.URL.Path+"?"+r.URL.RawQuery))

	opts := ClientOpts{
		QueueSize:        s.opts.QueueSize,
		WriteCompression: s.opts.WriteCompression,
		Logger:           s.opts.Logger,
	}

	wsconn := newConn(s.server.Host, clientType, clientProtocol, url, r.Header, opts)
	wsconn.conn = conn
	wsconn.RemoteHost = getRequestParameter(&r.Request, "X-Host-ID")

	// NOTE(safchain): fallback to remote addr if host id not provided
	// should be removed, connection should be refused if host id not provided
	if wsconn.RemoteHost == "" {
		wsconn.RemoteHost = r.RemoteAddr
	}

	conn.SetReadLimit(maxMessageSize)
	conn.SetReadDeadline(time.Now().Add(s.opts.PongTimeout))
	conn.SetPongHandler(func(string) error {
		conn.SetReadDeadline(time.Now().Add(s.opts.PongTimeout))
		return nil
	})

	c := &wsIncomingClient{
		Conn: wsconn,
	}
	wsconn.wsSpeaker = c

	pc, err := promoter(c)
	if err != nil {
		return nil, err
	}

	c.State.Store(common.RunningState)

	// add the new Speaker to the server pool
	s.AddClient(pc)

	// notify the pool listeners that the speaker is connected
	s.OnConnected(pc)

	// send a first ping to help firefox and some other client which wait for a
	// first ping before doing something
	c.sendPing()

	wsconn.pingTicker = time.NewTicker(s.opts.PingDelay)

	c.Start()

	return c, nil
}

// NewServer returns a new Server. The given auth backend will validate the credentials
func NewServer(server *shttp.Server, endpoint string, authBackend shttp.AuthenticationBackend, opts ServerOpts) *Server {
	if opts.Logger == nil {
		opts.Logger = logging.GetLogger()
	}

	poolOpts := PoolOpts{
		Logger: opts.Logger,
	}

	s := &Server{
		incomerPool: newIncomerPool(endpoint, poolOpts), // server inherits from a Speaker pool
		server:      server,
		opts:        opts,
	}

	s.incomerHandler = func(conn *websocket.Conn, r *auth.AuthenticatedRequest, promoter clientPromoter) (Speaker, error) {
		return s.newIncomingClient(conn, r, promoter)
	}

	server.HandleFunc(endpoint, s.serveMessages, authBackend)
	return s
}
<|endoftext|>


<commit_before>package util

import (
	"fmt"

	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/spf13/viper"
)

type Configuration interface {
	GetString(key string) string
	GetBool(key string) bool
	GetInt(key string) int
	GetInt64(key string) int64
	GetFloat64(key string) float64
	GetStringSlice(key string) []string
}

func LoadConfiguration(configFileName string, required bool) (loaded bool) {

	// find a filer store
	viper.SetConfigName(configFileName)     // name of config file (without extension)
	viper.AddConfigPath(".")                // optionally look for config in the working directory
	viper.AddConfigPath("$HOME/.seaweedfs") // call multiple times to add many search paths
	viper.AddConfigPath("/etc/seaweedfs/")  // path to look for the config file in

	glog.V(1).Infof("Reading %s.toml from %s", configFileName, viper.ConfigFileUsed())

	if err := viper.MergeInConfig(); err != nil { // Handle errors reading the config file
		glog.V(0).Infof("Reading %s: %v", viper.ConfigFileUsed(), err)
		if required {
			glog.Fatalf("Failed to load %s.toml file from current directory, or $HOME/.seaweedfs/, or /etc/seaweedfs/"+
				"\n\nPlease follow this example and add a filer.toml file to "+
				"current directory, or $HOME/.seaweedfs/, or /etc/seaweedfs/:\n"+
				"    https://github.com/chrislusf/seaweedfs/blob/master/weed/%s.toml\n"+
				"\nOr use this command to generate the default toml file\n"+
				"    weed scaffold -config=%s -output=.\n\n\n",
				configFileName, configFileName, configFileName)
		} else {
			return false
		}
	}

	return true
}

func Config() Configuration {
	return viper.GetViper()
}

func SubConfig(subKey string) (Configuration, error) {
	if subKey != "" {
		sub := viper.GetViper().Sub(subKey)
		if sub == nil {
			return nil, fmt.Errorf("sub config [%s] not exist", subKey)
		}
		return sub, nil
	}
	return  viper.GetViper(), nil
}<commit_msg>delete unused function<commit_after>package util

import (
	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/spf13/viper"
)

type Configuration interface {
	GetString(key string) string
	GetBool(key string) bool
	GetInt(key string) int
	GetInt64(key string) int64
	GetFloat64(key string) float64
	GetStringSlice(key string) []string
}

func LoadConfiguration(configFileName string, required bool) (loaded bool) {

	// find a filer store
	viper.SetConfigName(configFileName)     // name of config file (without extension)
	viper.AddConfigPath(".")                // optionally look for config in the working directory
	viper.AddConfigPath("$HOME/.seaweedfs") // call multiple times to add many search paths
	viper.AddConfigPath("/etc/seaweedfs/")  // path to look for the config file in

	glog.V(1).Infof("Reading %s.toml from %s", configFileName, viper.ConfigFileUsed())

	if err := viper.MergeInConfig(); err != nil { // Handle errors reading the config file
		glog.V(0).Infof("Reading %s: %v", viper.ConfigFileUsed(), err)
		if required {
			glog.Fatalf("Failed to load %s.toml file from current directory, or $HOME/.seaweedfs/, or /etc/seaweedfs/"+
				"\n\nPlease follow this example and add a filer.toml file to "+
				"current directory, or $HOME/.seaweedfs/, or /etc/seaweedfs/:\n"+
				"    https://github.com/chrislusf/seaweedfs/blob/master/weed/%s.toml\n"+
				"\nOr use this command to generate the default toml file\n"+
				"    weed scaffold -config=%s -output=.\n\n\n",
				configFileName, configFileName, configFileName)
		} else {
			return false
		}
	}

	return true
}

func Config() Configuration {
	return viper.GetViper()
}

<|endoftext|>