<commit_before>package id

import (
	"fmt"
	"net/http"

	jwt "github.com/dgrijalva/jwt-go"
)

// Handler is the function definition for our middleware.
type Handler func(w http.ResponseWriter, r *http.Request) error

// Middleware executes all our middleware.
func Middleware(handlers ...Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		for _, handler := range handlers {
			err := handler(w, r)
			if err != nil {
				w.Write([]byte(err.Error()))
				return
			}
		}
	})
}

// Verify checks to make sure there is a cookie with a
// valid JWT.
func Verify(w http.ResponseWriter, r *http.Request) error {
	cookie, err := r.Cookie("id")
	if err != nil {
		return err
	}

	token, err := jwt.Parse(cookie.Value, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
		}
		return "SECURE_STRING_HERE", nil
	})

	fmt.Printf("Token: %+v\n", token)

	return nil
}

func Verified(w http.ResponseWriter, r *http.Request) error {
	w.Write([]byte("You're authenticated"))
	return nil
}
<commit_msg>Add todo for error handling<commit_after>package id

import (
	"fmt"
	"net/http"

	jwt "github.com/dgrijalva/jwt-go"
)

// Handler is the function definition for our middleware.
type Handler func(w http.ResponseWriter, r *http.Request) error

// Middleware executes all our middleware.
func Middleware(handlers ...Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		for _, handler := range handlers {
			err := handler(w, r)
			if err != nil {
				w.Write([]byte(err.Error()))
				switch err.(type) {
				// @TODO: Handle the huge number of
				// possible errors
				default:
					return
				}
			}
		}
	})
}

// Verify checks to make sure there is a cookie with a
// valid JWT.
func Verify(w http.ResponseWriter, r *http.Request) error {
	cookie, err := r.Cookie("id")
	if err != nil {
		return err
	}

	token, err := jwt.Parse(cookie.Value, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
		}
		return "SECURE_STRING_HERE", nil
	})

	fmt.Printf("Token: %+v\n", token)

	return nil
}

func Verified(w http.ResponseWriter, r *http.Request) error {
	w.Write([]byte("You're authenticated"))
	return nil
}
<|endoftext|>


<commit_before>package master

import (
	"reflect"
	"sync"
)

type Bus struct {
	//Channels the user can read from and the bus writes to
	readChannels      []chan interface{}
	readChannelsMutex sync.Mutex

	//Channels the user can write to and the bus reads from
	writeChannels      []chan interface{}
	writeChannelsMutex sync.Mutex

	//Write channel used to send commands to the Bus.Run() goroutine
	internalChannel chan interface{}
}

type killBusMessage struct{}
type interruptBusMessage struct{}

func NewBus() *Bus {
	var b Bus
	b.internalChannel = make(chan interface{}, 1000)
	b.readChannels = []chan interface{}{}
	b.writeChannels = []chan interface{}{b.internalChannel}
	return &b
}

func (b *Bus) GetNewReadChannel() chan interface{} {
	channel := make(chan interface{}, 1000)

	b.readChannelsMutex.Lock()
	b.readChannels = append(b.readChannels, channel)
	b.readChannelsMutex.Unlock()

	return channel
}

func (b *Bus) GetNewWriteChannel() chan interface{} {
	channel := make(chan interface{}, 1000)

	b.writeChannelsMutex.Lock()
	b.writeChannels = append(b.writeChannels, channel)
	b.writeChannelsMutex.Unlock()

	//interrupt bus so that new channel is included in next select
	b.internalChannel <- interruptBusMessage{}

	return channel

}

func (b *Bus) Run() {
	for {
		//Read from write channels
		b.writeChannelsMutex.Lock()
		selects := make([]reflect.SelectCase, len(b.writeChannels))
		for i, channel := range b.writeChannels {
			selects[i] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(channel)}
		}
		b.writeChannelsMutex.Unlock()

		_, recv, recvOk := reflect.Select(selects)
		if recvOk {
			if _, ok := recv.Interface().(killBusMessage); ok {
				//kill the bus
				return
			}
			if _, ok := recv.Interface().(interruptBusMessage); ok {
				//continue with new select so that new channels are included in select
				continue
			}

			//Send to read channels
			b.readChannelsMutex.Lock()
			for _, channel := range b.readChannels {
				//TODO: Check if channel is full
				channel <- recv.Interface()
			}
			b.readChannelsMutex.Unlock()
		}
	}
}

func (b *Bus) Kill() {
	b.internalChannel <- killBusMessage{}
}
<commit_msg>UPD: master: bus: check if channel is full before sending<commit_after>package master

import (
	"log"
	"reflect"
	"sync"
)

type Bus struct {
	//Channels the user can read from and the bus writes to
	readChannels      []chan interface{}
	readChannelsMutex sync.Mutex

	//Channels the user can write to and the bus reads from
	writeChannels      []chan interface{}
	writeChannelsMutex sync.Mutex

	//Write channel used to send commands to the Bus.Run() goroutine
	internalChannel chan interface{}
}

type killBusMessage struct{}
type interruptBusMessage struct{}

func NewBus() *Bus {
	var b Bus
	b.internalChannel = make(chan interface{}, 1000)
	b.readChannels = []chan interface{}{}
	b.writeChannels = []chan interface{}{b.internalChannel}
	return &b
}

func (b *Bus) GetNewReadChannel() chan interface{} {
	channel := make(chan interface{}, 1000)

	b.readChannelsMutex.Lock()
	b.readChannels = append(b.readChannels, channel)
	b.readChannelsMutex.Unlock()

	return channel
}

func (b *Bus) GetNewWriteChannel() chan interface{} {
	channel := make(chan interface{}, 1000)

	b.writeChannelsMutex.Lock()
	b.writeChannels = append(b.writeChannels, channel)
	b.writeChannelsMutex.Unlock()

	//interrupt bus so that new channel is included in next select
	b.internalChannel <- interruptBusMessage{}

	return channel

}

func (b *Bus) Run() {
	for {
		//Read from write channels
		b.writeChannelsMutex.Lock()
		selects := make([]reflect.SelectCase, len(b.writeChannels))
		for i, channel := range b.writeChannels {
			selects[i] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(channel)}
		}
		b.writeChannelsMutex.Unlock()

		_, recv, recvOk := reflect.Select(selects)
		if recvOk {
			if _, ok := recv.Interface().(killBusMessage); ok {
				//kill the bus
				return
			}
			if _, ok := recv.Interface().(interruptBusMessage); ok {
				//continue with new select so that new channels are included in select
				continue
			}

			//Send to read channels
			b.readChannelsMutex.Lock()
			for _, channel := range b.readChannels {

				//Only send to bus if channel is not full
				select {
				case channel <- recv.Interface():
				default:
					log.Println("Bus channel full - dropping message")
				}
			}
			b.readChannelsMutex.Unlock()
		}
	}
}

func (b *Bus) Kill() {
	b.internalChannel <- killBusMessage{}
}
<|endoftext|>


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

package models

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"path"
	"path/filepath"
	"strings"
	"sync"

	"github.com/Unknwon/com"
	"github.com/Unknwon/log"
	"github.com/mschoch/blackfriday-text"
	"github.com/russross/blackfriday"
	"gopkg.in/ini.v1"

	"github.com/peachdocs/peach/modules/setting"
)

type Node struct {
	Name    string // Name in TOC
	Title   string // Name in given language
	content []byte
	Text    string // Clean text without formatting

	Plain    bool   // Root node without content
	FileName string // Full path with .md extension
	Nodes    []*Node
}

var textRender = blackfridaytext.TextRenderer()

func parseNodeName(name string, data []byte) (string, []byte) {
	data = bytes.TrimSpace(data)
	if len(data) < 3 || string(data[:3]) != "---" {
		return name, []byte("")
	}
	endIdx := bytes.Index(data[3:], []byte("---")) + 3
	if endIdx == -1 {
		return name, []byte("")
	}

	opts := strings.Split(strings.TrimSpace(string(string(data[3:endIdx]))), "\n")

	title := name
	for _, opt := range opts {
		infos := strings.SplitN(opt, ":", 2)
		if len(infos) != 2 {
			continue
		}

		switch strings.TrimSpace(infos[0]) {
		case "name":
			title = strings.TrimSpace(infos[1])
		}
	}

	return title, data[endIdx+3:]
}

func (n *Node) ReloadContent() error {
	data, err := ioutil.ReadFile(n.FileName)
	if err != nil {
		return err
	}

	n.Title, data = parseNodeName(n.Name, data)
	n.Plain = len(bytes.TrimSpace(data)) == 0

	if !n.Plain {
		n.content = markdown(data)
		n.Text = string(bytes.ToLower(blackfriday.Markdown(data, textRender, 0)))
	}
	return nil
}

func (n *Node) Content() []byte {
	if !setting.ProdMode {
		if err := n.ReloadContent(); err != nil {
			log.Error("Fail to reload content: %v", err)
		}
	}

	return n.content
}

// Toc represents table of content in a specific language.
type Toc struct {
	RootPath string
	Lang     string
	Nodes    []*Node
	Pages    []*Node
}

// GetDoc should only be called by top level toc.
func (t *Toc) GetDoc(name string) (string, []byte, bool) {
	name = strings.TrimPrefix(name, "/")

	// Returns first node whatever avaiable as default.
	if len(name) == 0 {
		if len(t.Nodes) == 0 ||
			t.Nodes[0].Plain {
			return "", nil, false
		}
		return t.Nodes[0].Title, t.Nodes[0].Content(), false
	}

	infos := strings.Split(name, "/")

	// Dir node.
	if len(infos) == 1 {
		for i := range t.Nodes {
			if t.Nodes[i].Name == infos[0] {
				return t.Nodes[i].Title, t.Nodes[i].Content(), false
			}
		}
		return "", nil, false
	}

	// File node.
	for i := range t.Nodes {
		if t.Nodes[i].Name == infos[0] {
			for j := range t.Nodes[i].Nodes {
				if t.Nodes[i].Nodes[j].Name == infos[1] {
					if com.IsFile(t.Nodes[i].Nodes[j].FileName) {
						return t.Nodes[i].Nodes[j].Title, t.Nodes[i].Nodes[j].Content(), false
					}

					// If not default language, try again.
					title, content, _ := Tocs[setting.Docs.Langs[0]].GetDoc(name)
					return title, content, true
				}
			}
		}
	}

	return "", nil, false
}

type SearchResult struct {
	Title string
	Path  string
	Match string
}

func adjustRange(start, end, length int) (int, int) {
	start -= 20
	if start < 0 {
		start = 0
	}
	end += 230
	if end > length {
		end = length
	}
	return start, end
}

func (t *Toc) Search(q string) []*SearchResult {
	if len(q) == 0 {
		return nil
	}
	q = strings.ToLower(q)

	results := make([]*SearchResult, 0, 5)

	// Dir node.
	for i := range t.Nodes {
		if idx := strings.Index(t.Nodes[i].Text, q); idx > -1 {
			start, end := adjustRange(idx, idx+len(q), len(t.Nodes[i].Text))
			results = append(results, &SearchResult{
				Title: t.Nodes[i].Title,
				Path:  t.Nodes[i].Name,
				Match: t.Nodes[i].Text[start:end],
			})
		}
	}

	// File node.
	for i := range t.Nodes {
		for j := range t.Nodes[i].Nodes {
			if idx := strings.Index(t.Nodes[i].Nodes[j].Text, q); idx > -1 {
				start, end := adjustRange(idx, idx+len(q), len(t.Nodes[i].Nodes[j].Text))
				results = append(results, &SearchResult{
					Title: t.Nodes[i].Nodes[j].Title,
					Path:  path.Join(t.Nodes[i].Name, t.Nodes[i].Nodes[j].Name),
					Match: t.Nodes[i].Nodes[j].Text[start:end],
				})
			}
		}
	}

	return results
}

var (
	tocLocker = sync.RWMutex{}
	Tocs      map[string]*Toc
)

func initToc(localRoot string) (map[string]*Toc, error) {
	tocPath := path.Join(localRoot, "TOC.ini")
	if !com.IsFile(tocPath) {
		return nil, fmt.Errorf("TOC not found: %s", tocPath)
	}

	// Generate Toc.
	tocCfg, err := ini.Load(tocPath)
	if err != nil {
		return nil, fmt.Errorf("Fail to load TOC.ini: %v", err)
	}

	tocs := make(map[string]*Toc)
	for _, lang := range setting.Docs.Langs {
		toc := &Toc{
			RootPath: localRoot,
			Lang:     lang,
		}
		dirs := tocCfg.Section("").KeyStrings()
		toc.Nodes = make([]*Node, 0, len(dirs))
		for _, dir := range dirs {
			dirName := tocCfg.Section("").Key(dir).String()
			fmt.Println(dirName + "/")
			files := tocCfg.Section(dirName).KeyStrings()

			// Skip empty directory.
			if len(files) == 0 {
				continue
			}

			dirNode := &Node{
				Name:     dirName,
				FileName: path.Join(localRoot, lang, dirName, tocCfg.Section(dirName).Key(files[0]).String()) + ".md",
				Nodes:    make([]*Node, 0, len(files)-1),
			}
			toc.Nodes = append(toc.Nodes, dirNode)

			for _, file := range files[1:] {
				fileName := tocCfg.Section(dirName).Key(file).String()
				fmt.Println(strings.Repeat(" ", len(dirName))+"|__", fileName)

				node := &Node{
					Name:     fileName,
					FileName: path.Join(localRoot, lang, dirName, fileName) + ".md",
				}
				dirNode.Nodes = append(dirNode.Nodes, node)
			}
		}

		// Single pages.
		pages := tocCfg.Section("pages").KeyStrings()
		toc.Pages = make([]*Node, 0, len(pages))
		for _, page := range pages {
			pageName := tocCfg.Section("pages").Key(page).String()
			fmt.Println(pageName)

			toc.Pages = append(toc.Pages, &Node{
				Name:     pageName,
				FileName: path.Join(localRoot, lang, pageName) + ".md",
			})
		}

		tocs[lang] = toc
	}
	return tocs, nil
}

func ReloadDocs() error {
	tocLocker.Lock()
	defer tocLocker.Unlock()

	localRoot := setting.Docs.Target

	// Fetch docs from remote.
	if setting.Docs.Type == "remote" {
		localRoot = "data/docs"

		absRoot, err := filepath.Abs(localRoot)
		if err != nil {
			return fmt.Errorf("filepath.Abs: %v", err)
		}

		// Clone new or pull to update.
		if com.IsDir(absRoot) {
			stdout, stderr, err := com.ExecCmdDir(absRoot, "git", "pull")
			if err != nil {
				return fmt.Errorf("Fail to update docs from remote source(%s): %v - %s", setting.Docs.Target, err, stderr)
			}
			fmt.Println(stdout)
		} else {
			stdout, stderr, err := com.ExecCmd("git", "clone", setting.Docs.Target, absRoot)
			if err != nil {
				return fmt.Errorf("Fail to clone docs from remote source(%s): %v - %s", setting.Docs.Target, err, stderr)
			}
			fmt.Println(stdout)
		}
	}

	if !com.IsDir(localRoot) {
		return fmt.Errorf("Documentation not found: %s - %s", setting.Docs.Type, localRoot)
	}

	tocs, err := initToc(localRoot)
	if err != nil {
		return fmt.Errorf("initToc: %v", err)
	}
	initDocs(tocs, localRoot)
	Tocs = tocs
	return nil
}

func NewContext() {
	if err := ReloadDocs(); err != nil {
		log.Fatal("Fail to init docs: %v", err)
	}
}
<commit_msg>加入本地HTML生成功能<commit_after>// Copyright 2015 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package models

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"strings"
	"sync"

	"github.com/Unknwon/com"
	"github.com/Unknwon/log"
	"github.com/mschoch/blackfriday-text"
	"github.com/russross/blackfriday"
	"gopkg.in/ini.v1"

	"github.com/peachdocs/peach/modules/setting"
)

type Node struct {
	Name    string // Name in TOC
	Title   string // Name in given language
	content []byte
	Text    string // Clean text without formatting

	Plain    bool   // Root node without content
	FileName string // Full path with .md extension
	Nodes    []*Node
}

var textRender = blackfridaytext.TextRenderer()

func parseNodeName(name string, data []byte) (string, []byte) {
	data = bytes.TrimSpace(data)
	if len(data) < 3 || string(data[:3]) != "---" {
		return name, []byte("")
	}
	endIdx := bytes.Index(data[3:], []byte("---")) + 3
	if endIdx == -1 {
		return name, []byte("")
	}

	opts := strings.Split(strings.TrimSpace(string(string(data[3:endIdx]))), "\n")

	title := name
	for _, opt := range opts {
		infos := strings.SplitN(opt, ":", 2)
		if len(infos) != 2 {
			continue
		}

		switch strings.TrimSpace(infos[0]) {
		case "name":
			title = strings.TrimSpace(infos[1])
		}
	}

	return title, data[endIdx+3:]
}

func (n *Node) ReloadContent() error {
	data, err := ioutil.ReadFile(n.FileName)
	if err != nil {
		return err
	}

	n.Title, data = parseNodeName(n.Name, data)
	n.Plain = len(bytes.TrimSpace(data)) == 0

	if !n.Plain {
		n.content = markdown(data)
		n.Text = string(bytes.ToLower(blackfriday.Markdown(data, textRender, 0)))
	}

	htmlRoot := "data/html"
	return n.GenLocalHTML(htmlRoot)
}

// Generate local HTML
func (n *Node) GenLocalHTML(htmlRoot string) error {

	r := strings.NewReplacer("data/docs", htmlRoot, ".md", ".html")
	htmlFile := r.Replace(n.FileName)

	return com.WriteFile(htmlFile, n.content)
}

func (n *Node) Content() []byte {
	if !setting.ProdMode {
		if err := n.ReloadContent(); err != nil {
			log.Error("Fail to reload content: %v", err)
		}
	}

	return n.content
}

// Toc represents table of content in a specific language.
type Toc struct {
	RootPath string
	Lang     string
	Nodes    []*Node
	Pages    []*Node
}

// GetDoc should only be called by top level toc.
func (t *Toc) GetDoc(name string) (string, []byte, bool) {
	name = strings.TrimPrefix(name, "/")

	// Returns first node whatever avaiable as default.
	if len(name) == 0 {
		if len(t.Nodes) == 0 ||
			t.Nodes[0].Plain {
			return "", nil, false
		}
		return t.Nodes[0].Title, t.Nodes[0].Content(), false
	}

	infos := strings.Split(name, "/")

	// Dir node.
	if len(infos) == 1 {
		for i := range t.Nodes {
			if t.Nodes[i].Name == infos[0] {
				return t.Nodes[i].Title, t.Nodes[i].Content(), false
			}
		}
		return "", nil, false
	}

	// File node.
	for i := range t.Nodes {
		if t.Nodes[i].Name == infos[0] {
			for j := range t.Nodes[i].Nodes {
				if t.Nodes[i].Nodes[j].Name == infos[1] {
					if com.IsFile(t.Nodes[i].Nodes[j].FileName) {
						return t.Nodes[i].Nodes[j].Title, t.Nodes[i].Nodes[j].Content(), false
					}

					// If not default language, try again.
					title, content, _ := Tocs[setting.Docs.Langs[0]].GetDoc(name)
					return title, content, true
				}
			}
		}
	}

	return "", nil, false
}

type SearchResult struct {
	Title string
	Path  string
	Match string
}

func adjustRange(start, end, length int) (int, int) {
	start -= 20
	if start < 0 {
		start = 0
	}
	end += 230
	if end > length {
		end = length
	}
	return start, end
}

func (t *Toc) Search(q string) []*SearchResult {
	if len(q) == 0 {
		return nil
	}
	q = strings.ToLower(q)

	results := make([]*SearchResult, 0, 5)

	// Dir node.
	for i := range t.Nodes {
		if idx := strings.Index(t.Nodes[i].Text, q); idx > -1 {
			start, end := adjustRange(idx, idx+len(q), len(t.Nodes[i].Text))
			results = append(results, &SearchResult{
				Title: t.Nodes[i].Title,
				Path:  t.Nodes[i].Name,
				Match: t.Nodes[i].Text[start:end],
			})
		}
	}

	// File node.
	for i := range t.Nodes {
		for j := range t.Nodes[i].Nodes {
			if idx := strings.Index(t.Nodes[i].Nodes[j].Text, q); idx > -1 {
				start, end := adjustRange(idx, idx+len(q), len(t.Nodes[i].Nodes[j].Text))
				results = append(results, &SearchResult{
					Title: t.Nodes[i].Nodes[j].Title,
					Path:  path.Join(t.Nodes[i].Name, t.Nodes[i].Nodes[j].Name),
					Match: t.Nodes[i].Nodes[j].Text[start:end],
				})
			}
		}
	}

	return results
}

var (
	tocLocker = sync.RWMutex{}
	Tocs      map[string]*Toc
)

func initToc(localRoot string) (map[string]*Toc, error) {
	tocPath := path.Join(localRoot, "TOC.ini")
	if !com.IsFile(tocPath) {
		return nil, fmt.Errorf("TOC not found: %s", tocPath)
	}

	// Generate Toc.
	tocCfg, err := ini.Load(tocPath)
	if err != nil {
		return nil, fmt.Errorf("Fail to load TOC.ini: %v", err)
	}

	tocs := make(map[string]*Toc)
	for _, lang := range setting.Docs.Langs {
		toc := &Toc{
			RootPath: localRoot,
			Lang:     lang,
		}
		dirs := tocCfg.Section("").KeyStrings()
		toc.Nodes = make([]*Node, 0, len(dirs))
		for _, dir := range dirs {
			dirName := tocCfg.Section("").Key(dir).String()
			fmt.Println(dirName + "/")
			files := tocCfg.Section(dirName).KeyStrings()

			// Skip empty directory.
			if len(files) == 0 {
				continue
			}

			dirNode := &Node{
				Name:     dirName,
				FileName: path.Join(localRoot, lang, dirName, tocCfg.Section(dirName).Key(files[0]).String()) + ".md",
				Nodes:    make([]*Node, 0, len(files)-1),
			}
			toc.Nodes = append(toc.Nodes, dirNode)

			for _, file := range files[1:] {
				fileName := tocCfg.Section(dirName).Key(file).String()
				fmt.Println(strings.Repeat(" ", len(dirName))+"|__", fileName)

				node := &Node{
					Name:     fileName,
					FileName: path.Join(localRoot, lang, dirName, fileName) + ".md",
				}
				dirNode.Nodes = append(dirNode.Nodes, node)
			}
		}

		// Single pages.
		pages := tocCfg.Section("pages").KeyStrings()
		toc.Pages = make([]*Node, 0, len(pages))
		for _, page := range pages {
			pageName := tocCfg.Section("pages").Key(page).String()
			fmt.Println(pageName)

			toc.Pages = append(toc.Pages, &Node{
				Name:     pageName,
				FileName: path.Join(localRoot, lang, pageName) + ".md",
			})
		}

		tocs[lang] = toc
	}
	return tocs, nil
}

func ReloadDocs() error {
	tocLocker.Lock()
	defer tocLocker.Unlock()

	localRoot := setting.Docs.Target

	// Del htmlRoot path
	htmlRoot := "data/html"
	if com.IsExist(htmlRoot) {
		err := os.RemoveAll(htmlRoot)
		if err != nil {
			return fmt.Errorf("htmlRoot not found:: %v", err)
		}
	}

	// Fetch docs from remote.
	if setting.Docs.Type == "remote" {
		localRoot = "data/docs"

		absRoot, err := filepath.Abs(localRoot)
		if err != nil {
			return fmt.Errorf("filepath.Abs: %v", err)
		}

		// Clone new or pull to update.
		if com.IsDir(absRoot) {
			stdout, stderr, err := com.ExecCmdDir(absRoot, "git", "pull")
			if err != nil {
				return fmt.Errorf("Fail to update docs from remote source(%s): %v - %s", setting.Docs.Target, err, stderr)
			}
			fmt.Println(stdout)
		} else {
			stdout, stderr, err := com.ExecCmd("git", "clone", setting.Docs.Target, absRoot)
			if err != nil {
				return fmt.Errorf("Fail to clone docs from remote source(%s): %v - %s", setting.Docs.Target, err, stderr)
			}
			fmt.Println(stdout)
		}
	}

	if !com.IsDir(localRoot) {
		return fmt.Errorf("Documentation not found: %s - %s", setting.Docs.Type, localRoot)
	}

	tocs, err := initToc(localRoot)
	if err != nil {
		return fmt.Errorf("initToc: %v", err)
	}
	initDocs(tocs, localRoot)
	Tocs = tocs
	return nil
}

func NewContext() {
	if err := ReloadDocs(); err != nil {
		log.Fatal("Fail to init docs: %v", err)
	}
}
<|endoftext|>


<commit_before><commit_msg>Updated aggregation constant docs<commit_after><|endoftext|>


<commit_before>package memio

import (
	"io"
	"testing"
)

var (
	_ io.Reader     = new(ReadMem)
	_ io.Closer     = new(ReadMem)
	_ io.Seeker     = new(ReadMem)
	_ io.WriterTo   = new(ReadMem)
	_ io.ByteReader = new(ReadMem)
	_ io.ReaderAt   = new(ReadMem)

	_ io.Writer     = new(WriteMem)
	_ io.Closer     = new(WriteMem)
	_ io.Seeker     = new(WriteMem)
	_ io.ReaderFrom = new(WriteMem)
	_ io.ByteWriter = new(WriteMem)
	_ io.WriterAt   = new(WriteMem)
)

func TestRead(t *testing.T) {
	data := []byte("Hello, World!")
	reader := Open(data)
	toRead := make([]byte, 5)
	if n, err := reader.Read(toRead); n != 5 {
		t.Errorf("expecting to read 5 bytes, read %d", n)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if string(toRead) != "Hello" {
		t.Errorf("expecting %q, got %q", "Hello", string(toRead))
		return
	}
	if n, err := reader.Read(toRead); n != 5 {
		t.Errorf("expecting to read 5 bytes, read %d", n)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if string(toRead) != ", Wor" {
		t.Errorf("expecting %q, got %q", ", Wor", string(toRead))
		return
	}
	if n, err := reader.Read(toRead); n != 3 {
		t.Errorf("expecting to read 3 bytes, read %d", n)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if string(toRead) != "ld!or" {
		t.Errorf("expecting %q, got %q", "ld!or", string(toRead))
		return
	}
	if n, err := reader.Read(toRead); n != 0 {
		t.Errorf("expecting to read 0 bytes, read %d", n)
		return
	} else if err != io.EOF {
		t.Errorf("expecting EOF")
	}
	if pos, err := reader.Seek(2, 0); pos != 2 {
		t.Errorf("expected to be at postion 2, got %d", pos)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if n, err := reader.Read(toRead); n != 5 {
		t.Errorf("expecting to read 5 bytes, read %d", n)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if string(toRead) != "llo, " {
		t.Errorf("expecting %q, got %q", "llo, ", string(toRead))
		return
	}
	if pos, err := reader.Seek(2, 1); pos != 9 {
		t.Errorf("expected to be at postion 9, got %d", pos)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if n, err := reader.Read(toRead); n != 4 {
		t.Errorf("expecting to read 4 bytes, read %d", n)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if string(toRead) != "rld! " {
		t.Errorf("expecting %q, got %q", "rld! ", string(toRead))
		return
	}
	if pos, err := reader.Seek(-6, 2); pos != 7 {
		t.Errorf("expected to be at postion 7, got %d", pos)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if n, err := reader.Read(toRead); n != 5 {
		t.Errorf("expecting to read 5 bytes, read %d", n)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if string(toRead) != "World" {
		t.Errorf("expecting %q, got %q", "World", string(toRead))
		return
	}
	if _, err := reader.Seek(1, 0); err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	}
	reader.Close()
}

func TestWrite(t *testing.T) {
	data := []byte("Beep")
	writer := Create(&data)
	if n, err := writer.Write([]byte("J")); n != 1 {
		t.Errorf("expecting to write 1 byte, wrote %d", n)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if string(data) != "Jeep" {
		t.Errorf("expecting %q, got %q", "Jeep", string(data))
		return
	}
	if n, err := writer.Write([]byte("ohn")); n != 3 {
		t.Errorf("expecting to write 3 bytes, wrote %d", n)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if string(data) != "John" {
		t.Errorf("expecting %q, got %q", "John", string(data))
		return
	}
	if n, err := writer.Write([]byte("ny")); n != 2 {
		t.Errorf("expecting to write 2 bytes, wrote %d", n)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if string(data) != "Johnny" {
		t.Errorf("expecting %q, got %q", "Johnny", string(data))
		return
	}
	if pos, err := writer.Seek(0, 0); pos != 0 {
		t.Errorf("expected to be at postion 0, got %d", pos)
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if n, err := writer.Write([]byte("Edmund")); n != 6 {
		t.Errorf("expecting to write 6 bytes, wrote %d", n)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if string(data) != "Edmund" {
		t.Errorf("expecting %q, got %q", "Edmund", string(data))
		return
	}
	if pos, err := writer.Seek(-4, 2); pos != 2 {
		t.Errorf("expected to be at postion 0, got %d", pos)
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if n, err := writer.Write([]byte("war")); n != 3 {
		t.Errorf("expecting to write 3 bytes, wrote %d", n)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if string(data) != "Edward" {
		t.Errorf("expecting %q, got %q", "Edward", string(data))
		return
	}
	if pos, err := writer.Seek(1, 1); pos != 6 {
		t.Errorf("expected to be at postion 6, got %d", pos)
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if n, err := writer.Write([]byte("o")); n != 1 {
		t.Errorf("expecting to write 1 bytes, wrote %d", n)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if string(data) != "Edwardo" {
		t.Errorf("expecting %q, got %q", "Edwardo", string(data))
		return
	}
	writer.Close()
	_, err := writer.Seek(0, 0)
	if err != ErrClosed {
		t.Errorf("expecting close error")
		return
	}
	_, err = writer.Write([]byte("Beep"))
	if err != ErrClosed {
		t.Errorf("expecting close error")
		return
	}
}

func TestNewWrite(t *testing.T) {
	var data []byte
	writer := Create(&data)
	if n, err := writer.Write([]byte("Hello")); err != nil {
		t.Errorf("got error: %q", err.Error())
	} else if n != 5 {
		t.Errorf("expecting to write 5 bytes, wrote %d", n)
	} else if len(data) != 5 {
		t.Errorf("expecting buf to have 5 bytes, has %d", n)
	} else if string(data) != "Hello" {
		t.Errorf("expecting %q, got %q", "Hello", string(data))
	}
}

func TestReadFrom(t *testing.T) {
	data := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
	toRead := make([]byte, 0, 2480)
	for i := 0; i < 40; i++ {
		toRead = append(toRead, data...)
	}
	reader := Open(toRead)
	var toWrite []byte
	writer := Create(&toWrite)
	if n, err := writer.ReadFrom(reader); err != nil {
		t.Errorf("got error: %q", err.Error())
	} else if n != 2480 {
		t.Errorf("expecting to write 2480 bytes, wrote %d", n)
	} else if string(toRead) != string(toWrite) {
		t.Errorf("expecting %q, got %q", string(toRead), string(toWrite))
	}

}
<commit_msg>Added addition write in test to check pos update<commit_after>package memio

import (
	"io"
	"testing"
)

var (
	_ io.Reader     = new(ReadMem)
	_ io.Closer     = new(ReadMem)
	_ io.Seeker     = new(ReadMem)
	_ io.WriterTo   = new(ReadMem)
	_ io.ByteReader = new(ReadMem)
	_ io.ReaderAt   = new(ReadMem)

	_ io.Writer     = new(WriteMem)
	_ io.Closer     = new(WriteMem)
	_ io.Seeker     = new(WriteMem)
	_ io.ReaderFrom = new(WriteMem)
	_ io.ByteWriter = new(WriteMem)
	_ io.WriterAt   = new(WriteMem)
)

func TestRead(t *testing.T) {
	data := []byte("Hello, World!")
	reader := Open(data)
	toRead := make([]byte, 5)
	if n, err := reader.Read(toRead); n != 5 {
		t.Errorf("expecting to read 5 bytes, read %d", n)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if string(toRead) != "Hello" {
		t.Errorf("expecting %q, got %q", "Hello", string(toRead))
		return
	}
	if n, err := reader.Read(toRead); n != 5 {
		t.Errorf("expecting to read 5 bytes, read %d", n)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if string(toRead) != ", Wor" {
		t.Errorf("expecting %q, got %q", ", Wor", string(toRead))
		return
	}
	if n, err := reader.Read(toRead); n != 3 {
		t.Errorf("expecting to read 3 bytes, read %d", n)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if string(toRead) != "ld!or" {
		t.Errorf("expecting %q, got %q", "ld!or", string(toRead))
		return
	}
	if n, err := reader.Read(toRead); n != 0 {
		t.Errorf("expecting to read 0 bytes, read %d", n)
		return
	} else if err != io.EOF {
		t.Errorf("expecting EOF")
	}
	if pos, err := reader.Seek(2, 0); pos != 2 {
		t.Errorf("expected to be at postion 2, got %d", pos)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if n, err := reader.Read(toRead); n != 5 {
		t.Errorf("expecting to read 5 bytes, read %d", n)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if string(toRead) != "llo, " {
		t.Errorf("expecting %q, got %q", "llo, ", string(toRead))
		return
	}
	if pos, err := reader.Seek(2, 1); pos != 9 {
		t.Errorf("expected to be at postion 9, got %d", pos)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if n, err := reader.Read(toRead); n != 4 {
		t.Errorf("expecting to read 4 bytes, read %d", n)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if string(toRead) != "rld! " {
		t.Errorf("expecting %q, got %q", "rld! ", string(toRead))
		return
	}
	if pos, err := reader.Seek(-6, 2); pos != 7 {
		t.Errorf("expected to be at postion 7, got %d", pos)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if n, err := reader.Read(toRead); n != 5 {
		t.Errorf("expecting to read 5 bytes, read %d", n)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if string(toRead) != "World" {
		t.Errorf("expecting %q, got %q", "World", string(toRead))
		return
	}
	if _, err := reader.Seek(1, 0); err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	}
	reader.Close()
}

func TestWrite(t *testing.T) {
	data := []byte("Beep")
	writer := Create(&data)
	if n, err := writer.Write([]byte("J")); n != 1 {
		t.Errorf("expecting to write 1 byte, wrote %d", n)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if string(data) != "Jeep" {
		t.Errorf("expecting %q, got %q", "Jeep", string(data))
		return
	}
	if n, err := writer.Write([]byte("ohn")); n != 3 {
		t.Errorf("expecting to write 3 bytes, wrote %d", n)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if string(data) != "John" {
		t.Errorf("expecting %q, got %q", "John", string(data))
		return
	}
	if n, err := writer.Write([]byte("ny")); n != 2 {
		t.Errorf("expecting to write 2 bytes, wrote %d", n)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if string(data) != "Johnny" {
		t.Errorf("expecting %q, got %q", "Johnny", string(data))
		return
	}
	if pos, err := writer.Seek(0, 0); pos != 0 {
		t.Errorf("expected to be at postion 0, got %d", pos)
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if n, err := writer.Write([]byte("Edmund")); n != 6 {
		t.Errorf("expecting to write 6 bytes, wrote %d", n)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if string(data) != "Edmund" {
		t.Errorf("expecting %q, got %q", "Edmund", string(data))
		return
	}
	if pos, err := writer.Seek(-4, 2); pos != 2 {
		t.Errorf("expected to be at postion 0, got %d", pos)
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if n, err := writer.Write([]byte("war")); n != 3 {
		t.Errorf("expecting to write 3 bytes, wrote %d", n)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if string(data) != "Edward" {
		t.Errorf("expecting %q, got %q", "Edward", string(data))
		return
	}
	if pos, err := writer.Seek(1, 1); pos != 6 {
		t.Errorf("expected to be at postion 6, got %d", pos)
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if n, err := writer.Write([]byte("o")); n != 1 {
		t.Errorf("expecting to write 1 bytes, wrote %d", n)
		return
	} else if err != nil {
		t.Errorf("got error: %q", err.Error())
		return
	} else if string(data) != "Edwardo" {
		t.Errorf("expecting %q, got %q", "Edwardo", string(data))
		return
	}
	writer.Close()
	_, err := writer.Seek(0, 0)
	if err != ErrClosed {
		t.Errorf("expecting close error")
		return
	}
	_, err = writer.Write([]byte("Beep"))
	if err != ErrClosed {
		t.Errorf("expecting close error")
		return
	}
}

func TestNewWrite(t *testing.T) {
	var data []byte
	writer := Create(&data)
	if n, err := writer.Write([]byte("Hello")); err != nil {
		t.Errorf("got error: %q", err.Error())
	} else if n != 5 {
		t.Errorf("expecting to write 5 bytes, wrote %d", n)
	} else if len(data) != 5 {
		t.Errorf("expecting buf to have 5 bytes, has %d", n)
	} else if string(data) != "Hello" {
		t.Errorf("expecting %q, got %q", "Hello", string(data))
	}
	if n, err := writer.Write([]byte("World")); err != nil {
		t.Errorf("got error: %q", err.Error())
	} else if n != 5 {
		t.Errorf("expecting to write 5 bytes, wrote %d", n)
	} else if len(data) != 10 {
		t.Errorf("expecting buf to have 5 bytes, has %d", n)
	} else if string(data) != "HelloWorld" {
		t.Errorf("expecting %q, got %q", "Hello", string(data))
	}
}

func TestReadFrom(t *testing.T) {
	data := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
	toRead := make([]byte, 0, 2480)
	for i := 0; i < 40; i++ {
		toRead = append(toRead, data...)
	}
	reader := Open(toRead)
	var toWrite []byte
	writer := Create(&toWrite)
	if n, err := writer.ReadFrom(reader); err != nil {
		t.Errorf("got error: %q", err.Error())
	} else if n != 2480 {
		t.Errorf("expecting to write 2480 bytes, wrote %d", n)
	} else if string(toRead) != string(toWrite) {
		t.Errorf("expecting %q, got %q", string(toRead), string(toWrite))
	}

}
<|endoftext|>


<commit_before>/*
Copyright (c) 2015 VMware, Inc. All Rights Reserved.

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

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

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

package object

import (
	"github.com/vmware/govmomi/vim25"
	"github.com/vmware/govmomi/vim25/methods"
	"github.com/vmware/govmomi/vim25/types"
	"golang.org/x/net/context"
)

type ListView struct {
	Common
}

func NewListView(c *vim25.Client, ref types.ManagedObjectReference) *ListView {
	return &ListView{
		Common: NewCommon(c, ref),
	}
}

func (v ListView) Destroy(ctx context.Context) error {
	req := types.DestroyView{
		This: v.Reference(),
	}

	_, err := methods.DestroyView(ctx, v.c, &req)
	return err
}
<commit_msg>Implemented additional ListView methods<commit_after>/*
Copyright (c) 2015 VMware, Inc. All Rights Reserved.

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

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

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

package object

import (
	"github.com/vmware/govmomi/vim25"
	"github.com/vmware/govmomi/vim25/methods"
	"github.com/vmware/govmomi/vim25/types"
	"golang.org/x/net/context"
)

type ListView struct {
	Common
}

func NewListView(c *vim25.Client, ref types.ManagedObjectReference) *ListView {
	return &ListView{
		Common: NewCommon(c, ref),
	}
}

func (v ListView) Destroy(ctx context.Context) error {
	req := types.DestroyView{
		This: v.Reference(),
	}
	_, err := methods.DestroyView(ctx, v.c, &req)
	return err
}

func (v ListView) Add(ctx context.Context, refs []types.ManagedObjectReference) error {
	req := types.ModifyListView{
		This: v.Reference(),
		Add:  refs,
	}
	_, err := methods.ModifyListView(ctx, v.c, &req)
	return err
}

func (v ListView) Remove(ctx context.Context, refs []types.ManagedObjectReference) error {
	req := types.ModifyListView{
		This:   v.Reference(),
		Remove: refs,
	}
	_, err := methods.ModifyListView(ctx, v.c, &req)
	return err
}

func (v ListView) Reset(ctx context.Context, refs []types.ManagedObjectReference) error {
	req := types.ResetListView{
		This: v.Reference(),
		Obj:  refs,
	}
	_, err := methods.ResetListView(ctx, v.c, &req)
	return err
}
<|endoftext|>


<commit_before>// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.

// Package gcfg provides reading, caching and managing for configuration.
package gcfg

import (
	"bytes"
	"errors"
	"fmt"
	"github.com/gogf/gf/internal/intlog"
	"github.com/gogf/gf/os/gcmd"
	"github.com/gogf/gf/text/gstr"

	"github.com/gogf/gf/os/gres"

	"github.com/gogf/gf/container/garray"
	"github.com/gogf/gf/container/gmap"
	"github.com/gogf/gf/encoding/gjson"
	"github.com/gogf/gf/os/gfile"
	"github.com/gogf/gf/os/gfsnotify"
	"github.com/gogf/gf/os/glog"
	"github.com/gogf/gf/os/gspath"
)

const (
	DefaultConfigFile = "config.toml" // The default configuration file name.
	cmdEnvKey         = "gf.gcfg"     // Configuration key for command argument or environment.
)

// Configuration struct.
type Config struct {
	defaultName   string           // Default configuration file name.
	searchPaths   *garray.StrArray // Searching path array.
	jsonMap       *gmap.StrAnyMap  // The pared JSON objects for configuration files.
	violenceCheck bool             // Whether do violence check in value index searching. It affects the performance when set true(false in default).
}

var (
	supportedFileTypes = []string{"toml", "yaml", "yml", "json", "ini", "xml"}
	resourceTryFiles   = []string{"", "/", "config/", "config", "/config", "/config/"}
)

// New returns a new configuration management object.
// The parameter <file> specifies the default configuration file name for reading.
func New(file ...string) *Config {
	name := DefaultConfigFile
	if len(file) > 0 {
		name = file[0]
	} else {
		// Custom default configuration file name from command line or environment.
		if customFile := gcmd.GetOptWithEnv(fmt.Sprintf("%s.file", cmdEnvKey)).String(); customFile != "" {
			name = customFile
		}
	}
	c := &Config{
		defaultName: name,
		searchPaths: garray.NewStrArray(true),
		jsonMap:     gmap.NewStrAnyMap(true),
	}
	// Customized dir path from env/cmd.
	if customPath := gcmd.GetOptWithEnv(fmt.Sprintf("%s.path", cmdEnvKey)).String(); customPath != "" {
		if gfile.Exists(customPath) {
			_ = c.SetPath(customPath)
		} else {
			if errorPrint() {
				glog.Errorf("Configuration directory path does not exist: %s", customPath)
			}
		}
	} else {
		// Dir path of main package.
		if mainPath := gfile.MainPkgPath(); mainPath != "" && gfile.Exists(mainPath) {
			_ = c.AddPath(mainPath)
		}
		// Dir path of binary.
		if selfPath := gfile.SelfDir(); selfPath != "" && gfile.Exists(selfPath) {
			_ = c.AddPath(selfPath)
		}
		// Dir path of working dir.
		_ = c.AddPath(gfile.Pwd())
	}
	return c
}

// getSearchPaths is used for checking and using `MainPkgPath` purpose.
// As the `MainPkgPath` is retrieved only by main goroutine,
// it should be checked multiple times when used in configuration file searching.
// It only makes sense in source development environment.
func (c *Config) getSearchPaths() []string {
	// Custom configuration directory path.
	if !gcmd.GetOptWithEnv(fmt.Sprintf("%s.path", cmdEnvKey)).IsEmpty() {
		return c.searchPaths.Slice()
	}

	var (
		searchPaths = c.searchPaths.Slice()
		mainPkgPath = gfile.MainPkgPath()
	)
	if mainPkgPath != "" {
		if !gstr.InArray(searchPaths, mainPkgPath) {
			searchPaths = append([]string{mainPkgPath}, searchPaths...)
		}
	}
	return searchPaths
}

// filePath returns the absolute configuration file path for the given filename by <file>.
func (c *Config) filePath(file ...string) (path string) {
	name := c.defaultName
	if len(file) > 0 {
		name = file[0]
	}
	path = c.FilePath(name)
	if path == "" {
		var (
			searchPaths = c.getSearchPaths()
			buffer      = bytes.NewBuffer(nil)
		)

		if len(searchPaths) > 0 {
			buffer.WriteString(fmt.Sprintf("[gcfg] cannot find config file \"%s\" in following paths:", name))
			index := 1
			for _, v := range searchPaths {
				v = gstr.TrimRight(v, `\/`)
				buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v))
				index++
				buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v+gfile.Separator+"config"))
				index++
			}
		} else {
			buffer.WriteString(fmt.Sprintf("[gcfg] cannot find config file \"%s\" with no path set/add", name))
		}
		if errorPrint() {
			glog.Error(buffer.String())
		}
	}
	return path
}

// SetPath sets the configuration directory path for file search.
// The parameter <path> can be absolute or relative path,
// but absolute path is strongly recommended.
func (c *Config) SetPath(path string) error {
	var (
		isDir    = false
		realPath = ""
	)
	if file := gres.Get(path); file != nil {
		realPath = path
		isDir = file.FileInfo().IsDir()
	} else {
		// Absolute path.
		realPath = gfile.RealPath(path)
		if realPath == "" {
			// Relative path.
			c.searchPaths.RLockFunc(func(array []string) {
				for _, v := range array {
					if path, _ := gspath.Search(v, path); path != "" {
						realPath = path
						break
					}
				}
			})
		}
		if realPath != "" {
			isDir = gfile.IsDir(realPath)
		}
	}
	// Path not exist.
	if realPath == "" {
		buffer := bytes.NewBuffer(nil)
		if c.searchPaths.Len() > 0 {
			buffer.WriteString(fmt.Sprintf("[gcfg] SetPath failed: cannot find directory \"%s\" in following paths:", path))
			c.searchPaths.RLockFunc(func(array []string) {
				for k, v := range array {
					buffer.WriteString(fmt.Sprintf("\n%d. %s", k+1, v))
				}
			})
		} else {
			buffer.WriteString(fmt.Sprintf(`[gcfg] SetPath failed: path "%s" does not exist`, path))
		}
		err := errors.New(buffer.String())
		if errorPrint() {
			glog.Error(err)
		}
		return err
	}
	// Should be a directory.
	if !isDir {
		err := fmt.Errorf(`[gcfg] SetPath failed: path "%s" should be directory type`, path)
		if errorPrint() {
			glog.Error(err)
		}
		return err
	}
	// Repeated path check.
	if c.searchPaths.Search(realPath) != -1 {
		return nil
	}
	c.jsonMap.Clear()
	c.searchPaths.Clear()
	c.searchPaths.Append(realPath)
	intlog.Print("SetPath:", realPath)
	return nil
}

// SetViolenceCheck sets whether to perform hierarchical conflict checking.
// This feature needs to be enabled when there is a level symbol in the key name.
// It is off in default.
//
// Note that, turning on this feature is quite expensive, and it is not recommended
// to allow separators in the key names. It is best to avoid this on the application side.
func (c *Config) SetViolenceCheck(check bool) {
	c.violenceCheck = check
	c.Clear()
}

// AddPath adds a absolute or relative path to the search paths.
func (c *Config) AddPath(path string) error {
	var (
		isDir    = false
		realPath = ""
	)
	// It firstly checks the resource manager,
	// and then checks the filesystem for the path.
	if file := gres.Get(path); file != nil {
		realPath = path
		isDir = file.FileInfo().IsDir()
	} else {
		// Absolute path.
		realPath = gfile.RealPath(path)
		if realPath == "" {
			// Relative path.
			c.searchPaths.RLockFunc(func(array []string) {
				for _, v := range array {
					if path, _ := gspath.Search(v, path); path != "" {
						realPath = path
						break
					}
				}
			})
		}
		if realPath != "" {
			isDir = gfile.IsDir(realPath)
		}
	}
	if realPath == "" {
		buffer := bytes.NewBuffer(nil)
		if c.searchPaths.Len() > 0 {
			buffer.WriteString(fmt.Sprintf("[gcfg] AddPath failed: cannot find directory \"%s\" in following paths:", path))
			c.searchPaths.RLockFunc(func(array []string) {
				for k, v := range array {
					buffer.WriteString(fmt.Sprintf("\n%d. %s", k+1, v))
				}
			})
		} else {
			buffer.WriteString(fmt.Sprintf(`[gcfg] AddPath failed: path "%s" does not exist`, path))
		}
		err := errors.New(buffer.String())
		if errorPrint() {
			glog.Error(err)
		}
		return err
	}
	if !isDir {
		err := fmt.Errorf(`[gcfg] AddPath failed: path "%s" should be directory type`, path)
		if errorPrint() {
			glog.Error(err)
		}
		return err
	}
	// Repeated path check.
	if c.searchPaths.Search(realPath) != -1 {
		return nil
	}
	c.searchPaths.Append(realPath)
	intlog.Print("AddPath:", realPath)
	return nil
}

// GetFilePath returns the absolute path of the specified configuration file.
// If <file> is not passed, it returns the configuration file path of the default name.
// If the specified configuration file does not exist,
// an empty string is returned.
func (c *Config) FilePath(file ...string) (path string) {
	name := c.defaultName
	if len(file) > 0 {
		name = file[0]
	}
	searchPaths := c.getSearchPaths()
	// Searching resource manager.
	if !gres.IsEmpty() {
		for _, v := range resourceTryFiles {
			if file := gres.Get(v + name); file != nil {
				path = file.Name()
				return
			}
		}
		for _, prefix := range searchPaths {
			for _, v := range resourceTryFiles {
				if file := gres.Get(prefix + v + name); file != nil {
					path = file.Name()
					return
				}
			}
		}
	}
	// Searching the file system.
	for _, prefix := range searchPaths {
		prefix = gstr.TrimRight(prefix, `\/`)
		if path, _ = gspath.Search(prefix, name); path != "" {
			return
		}
		if path, _ = gspath.Search(prefix+gfile.Separator+"config", name); path != "" {
			return
		}
	}
	return
}

// SetFileName sets the default configuration file name.
func (c *Config) SetFileName(name string) *Config {
	c.defaultName = name
	return c
}

// GetFileName returns the default configuration file name.
func (c *Config) GetFileName() string {
	return c.defaultName
}

// Available checks and returns whether configuration of given <file> is available.
func (c *Config) Available(file ...string) bool {
	var name string
	if len(file) > 0 && file[0] != "" {
		name = file[0]
	} else {
		name = c.defaultName
	}
	if c.FilePath(name) != "" {
		return true
	}
	if GetContent(name) != "" {
		return true
	}
	return false
}

// getJson returns a *gjson.Json object for the specified <file> content.
// It would print error if file reading fails. It return nil if any error occurs.
func (c *Config) getJson(file ...string) *gjson.Json {
	var name string
	if len(file) > 0 && file[0] != "" {
		name = file[0]
	} else {
		name = c.defaultName
	}
	r := c.jsonMap.GetOrSetFuncLock(name, func() interface{} {
		var (
			content  = ""
			filePath = ""
		)
		// The configured content can be any kind of data type different from its file type.
		isFromConfigContent := true
		if content = GetContent(name); content == "" {
			isFromConfigContent = false
			filePath = c.filePath(name)
			if filePath == "" {
				return nil
			}
			if file := gres.Get(filePath); file != nil {
				content = string(file.Content())
			} else {
				content = gfile.GetContents(filePath)
			}
		}
		// Note that the underlying configuration json object operations are concurrent safe.
		var (
			j   *gjson.Json
			err error
		)
		dataType := gfile.ExtName(name)
		if gjson.IsValidDataType(dataType) && !isFromConfigContent {
			j, err = gjson.LoadContentType(dataType, content, true)
		} else {
			j, err = gjson.LoadContent(content, true)
		}
		if err == nil {
			j.SetViolenceCheck(c.violenceCheck)
			// Add monitor for this configuration file,
			// any changes of this file will refresh its cache in Config object.
			if filePath != "" && !gres.Contains(filePath) {
				_, err = gfsnotify.Add(filePath, func(event *gfsnotify.Event) {
					c.jsonMap.Remove(name)
				})
				if err != nil && errorPrint() {
					glog.Error(err)
				}
			}
			return j
		} else {
			if errorPrint() {
				if filePath != "" {
					glog.Criticalf(`[gcfg] Load config file "%s" failed: %s`, filePath, err.Error())
				} else {
					glog.Criticalf(`[gcfg] Load configuration failed: %s`, err.Error())
				}
			}
		}
		return nil
	})
	if r != nil {
		return r.(*gjson.Json)
	}
	return nil
}
<commit_msg>improve package gcfg for main package path searching<commit_after>// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.

// Package gcfg provides reading, caching and managing for configuration.
package gcfg

import (
	"bytes"
	"errors"
	"fmt"
	"github.com/gogf/gf/internal/intlog"
	"github.com/gogf/gf/os/gcmd"
	"github.com/gogf/gf/text/gstr"

	"github.com/gogf/gf/os/gres"

	"github.com/gogf/gf/container/garray"
	"github.com/gogf/gf/container/gmap"
	"github.com/gogf/gf/encoding/gjson"
	"github.com/gogf/gf/os/gfile"
	"github.com/gogf/gf/os/gfsnotify"
	"github.com/gogf/gf/os/glog"
	"github.com/gogf/gf/os/gspath"
)

const (
	DefaultConfigFile = "config.toml" // The default configuration file name.
	cmdEnvKey         = "gf.gcfg"     // Configuration key for command argument or environment.
)

// Configuration struct.
type Config struct {
	defaultName   string           // Default configuration file name.
	searchPaths   *garray.StrArray // Searching path array.
	jsonMap       *gmap.StrAnyMap  // The pared JSON objects for configuration files.
	violenceCheck bool             // Whether do violence check in value index searching. It affects the performance when set true(false in default).
}

var (
	supportedFileTypes = []string{"toml", "yaml", "yml", "json", "ini", "xml"}
	resourceTryFiles   = []string{"", "/", "config/", "config", "/config", "/config/"}
)

// New returns a new configuration management object.
// The parameter <file> specifies the default configuration file name for reading.
func New(file ...string) *Config {
	name := DefaultConfigFile
	if len(file) > 0 {
		name = file[0]
	} else {
		// Custom default configuration file name from command line or environment.
		if customFile := gcmd.GetOptWithEnv(fmt.Sprintf("%s.file", cmdEnvKey)).String(); customFile != "" {
			name = customFile
		}
	}
	c := &Config{
		defaultName: name,
		searchPaths: garray.NewStrArray(true),
		jsonMap:     gmap.NewStrAnyMap(true),
	}
	// Customized dir path from env/cmd.
	if customPath := gcmd.GetOptWithEnv(fmt.Sprintf("%s.path", cmdEnvKey)).String(); customPath != "" {
		if gfile.Exists(customPath) {
			_ = c.SetPath(customPath)
		} else {
			if errorPrint() {
				glog.Errorf("Configuration directory path does not exist: %s", customPath)
			}
		}
	} else {
		// Dir path of main package.
		if mainPath := gfile.MainPkgPath(); mainPath != "" && gfile.Exists(mainPath) {
			if err := c.AddPath(mainPath); err != nil {
				intlog.Error(err)
			}
		}

		// Dir path of working dir.
		if err := c.AddPath(gfile.Pwd()); err != nil {
			intlog.Error(err)
		}

		// Dir path of binary.
		if selfPath := gfile.SelfDir(); selfPath != "" && gfile.Exists(selfPath) {
			if err := c.AddPath(selfPath); err != nil {
				intlog.Error(err)
			}
		}
	}
	return c
}

// getSearchPaths is used for checking and using `MainPkgPath` purpose.
// As the `MainPkgPath` is retrieved only by main goroutine,
// it should be checked multiple times when used in configuration file searching.
// It only makes sense in source development environment.
func (c *Config) getSearchPaths() []string {
	// Custom configuration directory path.
	if !gcmd.GetOptWithEnv(fmt.Sprintf("%s.path", cmdEnvKey)).IsEmpty() {
		return c.searchPaths.Slice()
	}

	var (
		searchPaths = c.searchPaths.Slice()
		mainPkgPath = gfile.MainPkgPath()
	)
	if mainPkgPath != "" {
		if !gstr.InArray(searchPaths, mainPkgPath) {
			searchPaths = append([]string{mainPkgPath}, searchPaths...)
		}
	}
	return searchPaths
}

// filePath returns the absolute configuration file path for the given filename by <file>.
func (c *Config) filePath(file ...string) (path string) {
	name := c.defaultName
	if len(file) > 0 {
		name = file[0]
	}
	path = c.FilePath(name)
	if path == "" {
		var (
			searchPaths = c.getSearchPaths()
			buffer      = bytes.NewBuffer(nil)
		)

		if len(searchPaths) > 0 {
			buffer.WriteString(fmt.Sprintf("[gcfg] cannot find config file \"%s\" in following paths:", name))
			index := 1
			for _, v := range searchPaths {
				v = gstr.TrimRight(v, `\/`)
				buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v))
				index++
				buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v+gfile.Separator+"config"))
				index++
			}
		} else {
			buffer.WriteString(fmt.Sprintf("[gcfg] cannot find config file \"%s\" with no path set/add", name))
		}
		if errorPrint() {
			glog.Error(buffer.String())
		}
	}
	return path
}

// SetPath sets the configuration directory path for file search.
// The parameter <path> can be absolute or relative path,
// but absolute path is strongly recommended.
func (c *Config) SetPath(path string) error {
	var (
		isDir    = false
		realPath = ""
	)
	if file := gres.Get(path); file != nil {
		realPath = path
		isDir = file.FileInfo().IsDir()
	} else {
		// Absolute path.
		realPath = gfile.RealPath(path)
		if realPath == "" {
			// Relative path.
			c.searchPaths.RLockFunc(func(array []string) {
				for _, v := range array {
					if path, _ := gspath.Search(v, path); path != "" {
						realPath = path
						break
					}
				}
			})
		}
		if realPath != "" {
			isDir = gfile.IsDir(realPath)
		}
	}
	// Path not exist.
	if realPath == "" {
		buffer := bytes.NewBuffer(nil)
		if c.searchPaths.Len() > 0 {
			buffer.WriteString(fmt.Sprintf("[gcfg] SetPath failed: cannot find directory \"%s\" in following paths:", path))
			c.searchPaths.RLockFunc(func(array []string) {
				for k, v := range array {
					buffer.WriteString(fmt.Sprintf("\n%d. %s", k+1, v))
				}
			})
		} else {
			buffer.WriteString(fmt.Sprintf(`[gcfg] SetPath failed: path "%s" does not exist`, path))
		}
		err := errors.New(buffer.String())
		if errorPrint() {
			glog.Error(err)
		}
		return err
	}
	// Should be a directory.
	if !isDir {
		err := fmt.Errorf(`[gcfg] SetPath failed: path "%s" should be directory type`, path)
		if errorPrint() {
			glog.Error(err)
		}
		return err
	}
	// Repeated path check.
	if c.searchPaths.Search(realPath) != -1 {
		return nil
	}
	c.jsonMap.Clear()
	c.searchPaths.Clear()
	c.searchPaths.Append(realPath)
	intlog.Print("SetPath:", realPath)
	return nil
}

// SetViolenceCheck sets whether to perform hierarchical conflict checking.
// This feature needs to be enabled when there is a level symbol in the key name.
// It is off in default.
//
// Note that, turning on this feature is quite expensive, and it is not recommended
// to allow separators in the key names. It is best to avoid this on the application side.
func (c *Config) SetViolenceCheck(check bool) {
	c.violenceCheck = check
	c.Clear()
}

// AddPath adds a absolute or relative path to the search paths.
func (c *Config) AddPath(path string) error {
	var (
		isDir    = false
		realPath = ""
	)
	// It firstly checks the resource manager,
	// and then checks the filesystem for the path.
	if file := gres.Get(path); file != nil {
		realPath = path
		isDir = file.FileInfo().IsDir()
	} else {
		// Absolute path.
		realPath = gfile.RealPath(path)
		if realPath == "" {
			// Relative path.
			c.searchPaths.RLockFunc(func(array []string) {
				for _, v := range array {
					if path, _ := gspath.Search(v, path); path != "" {
						realPath = path
						break
					}
				}
			})
		}
		if realPath != "" {
			isDir = gfile.IsDir(realPath)
		}
	}
	if realPath == "" {
		buffer := bytes.NewBuffer(nil)
		if c.searchPaths.Len() > 0 {
			buffer.WriteString(fmt.Sprintf("[gcfg] AddPath failed: cannot find directory \"%s\" in following paths:", path))
			c.searchPaths.RLockFunc(func(array []string) {
				for k, v := range array {
					buffer.WriteString(fmt.Sprintf("\n%d. %s", k+1, v))
				}
			})
		} else {
			buffer.WriteString(fmt.Sprintf(`[gcfg] AddPath failed: path "%s" does not exist`, path))
		}
		err := errors.New(buffer.String())
		if errorPrint() {
			glog.Error(err)
		}
		return err
	}
	if !isDir {
		err := fmt.Errorf(`[gcfg] AddPath failed: path "%s" should be directory type`, path)
		if errorPrint() {
			glog.Error(err)
		}
		return err
	}
	// Repeated path check.
	if c.searchPaths.Search(realPath) != -1 {
		return nil
	}
	c.searchPaths.Append(realPath)
	intlog.Print("AddPath:", realPath)
	return nil
}

// GetFilePath returns the absolute path of the specified configuration file.
// If <file> is not passed, it returns the configuration file path of the default name.
// If the specified configuration file does not exist,
// an empty string is returned.
func (c *Config) FilePath(file ...string) (path string) {
	name := c.defaultName
	if len(file) > 0 {
		name = file[0]
	}
	searchPaths := c.getSearchPaths()
	// Searching resource manager.
	if !gres.IsEmpty() {
		for _, v := range resourceTryFiles {
			if file := gres.Get(v + name); file != nil {
				path = file.Name()
				return
			}
		}
		for _, prefix := range searchPaths {
			for _, v := range resourceTryFiles {
				if file := gres.Get(prefix + v + name); file != nil {
					path = file.Name()
					return
				}
			}
		}
	}
	// Searching the file system.
	for _, prefix := range searchPaths {
		prefix = gstr.TrimRight(prefix, `\/`)
		if path, _ = gspath.Search(prefix, name); path != "" {
			return
		}
		if path, _ = gspath.Search(prefix+gfile.Separator+"config", name); path != "" {
			return
		}
	}
	return
}

// SetFileName sets the default configuration file name.
func (c *Config) SetFileName(name string) *Config {
	c.defaultName = name
	return c
}

// GetFileName returns the default configuration file name.
func (c *Config) GetFileName() string {
	return c.defaultName
}

// Available checks and returns whether configuration of given <file> is available.
func (c *Config) Available(file ...string) bool {
	var name string
	if len(file) > 0 && file[0] != "" {
		name = file[0]
	} else {
		name = c.defaultName
	}
	if c.FilePath(name) != "" {
		return true
	}
	if GetContent(name) != "" {
		return true
	}
	return false
}

// getJson returns a *gjson.Json object for the specified <file> content.
// It would print error if file reading fails. It return nil if any error occurs.
func (c *Config) getJson(file ...string) *gjson.Json {
	var name string
	if len(file) > 0 && file[0] != "" {
		name = file[0]
	} else {
		name = c.defaultName
	}
	r := c.jsonMap.GetOrSetFuncLock(name, func() interface{} {
		var (
			content  = ""
			filePath = ""
		)
		// The configured content can be any kind of data type different from its file type.
		isFromConfigContent := true
		if content = GetContent(name); content == "" {
			isFromConfigContent = false
			filePath = c.filePath(name)
			if filePath == "" {
				return nil
			}
			if file := gres.Get(filePath); file != nil {
				content = string(file.Content())
			} else {
				content = gfile.GetContents(filePath)
			}
		}
		// Note that the underlying configuration json object operations are concurrent safe.
		var (
			j   *gjson.Json
			err error
		)
		dataType := gfile.ExtName(name)
		if gjson.IsValidDataType(dataType) && !isFromConfigContent {
			j, err = gjson.LoadContentType(dataType, content, true)
		} else {
			j, err = gjson.LoadContent(content, true)
		}
		if err == nil {
			j.SetViolenceCheck(c.violenceCheck)
			// Add monitor for this configuration file,
			// any changes of this file will refresh its cache in Config object.
			if filePath != "" && !gres.Contains(filePath) {
				_, err = gfsnotify.Add(filePath, func(event *gfsnotify.Event) {
					c.jsonMap.Remove(name)
				})
				if err != nil && errorPrint() {
					glog.Error(err)
				}
			}
			return j
		} else {
			if errorPrint() {
				if filePath != "" {
					glog.Criticalf(`[gcfg] Load config file "%s" failed: %s`, filePath, err.Error())
				} else {
					glog.Criticalf(`[gcfg] Load configuration failed: %s`, err.Error())
				}
			}
		}
		return nil
	})
	if r != nil {
		return r.(*gjson.Json)
	}
	return nil
}
<|endoftext|>


<commit_before>/*
 * Copyright 2015 1&1 Internet AG, http://1und1.de . All rights reserved. Licensed under the Apache v2 License.
 */

package oneandone_cloudserver_api

import (
	assert "github.com/stretchr/testify/assert"
	"testing"
)

func TestNew(t *testing.T) {
	api := New("134", "abc")

	assert.Equal(t, "134", api.AuthToken)
	assert.Equal(t, "abc", api.Endpoint)
}

func TestPrepareSession(t *testing.T) {
	api := New("134", "abc")

	session := api.prepareSession()
	assert.Equal(t, "134", session.Header.Get("X_TOKEN"))
}

func TestCreateUrl_1(t *testing.T) {
	api := New("token", "http://test.de/v1")

	result := createUrl(api)
	assert.Equal(t, "http://test.de/v1", result)
}

func TestCreateUrl_2(t *testing.T) {
	api := New("token", "http://test.de/v1")

	result := createUrl(api, "servers")
	assert.Equal(t, "http://test.de/v1/servers", result)
}

func TestCreateUrl_3(t *testing.T) {
	api := New("token", "http://test.de/v1")

	result := createUrl(api, "servers", 1)
	assert.Equal(t, "http://test.de/v1/servers/1", result)
}

func TestInt2Pointer_1(t *testing.T) {
	result := Int2Pointer(42)

	assert.Equal(t, 42, *result)
}
<commit_msg>Fix package_test.go and implement new tests for getMapKeys<commit_after>/*
 * Copyright 2015 1&1 Internet AG, http://1und1.de . All rights reserved. Licensed under the Apache v2 License.
 */

package oneandone_cloudserver_api

import (
	"github.com/stretchr/testify/assert"
	"testing"
	"strconv"
)

func TestNew(t *testing.T) {
	api := New("134", "abc")

	assert.Equal(t, "abc", api.Endpoint)
}

func TestCreateUrl_1(t *testing.T) {
	api := New("token", "http://test.de/v1")

	result := createUrl(api)
	assert.Equal(t, "http://test.de/v1", result)
}

func TestCreateUrl_2(t *testing.T) {
	api := New("token", "http://test.de/v1")

	result := createUrl(api, "servers")
	assert.Equal(t, "http://test.de/v1/servers", result)
}

func TestCreateUrl_3(t *testing.T) {
	api := New("token", "http://test.de/v1")

	result := createUrl(api, "servers", 1)
	assert.Equal(t, "http://test.de/v1/servers/1", result)
}

func TestInt2Pointer_1(t *testing.T) {
	result := Int2Pointer(42)

	assert.Equal(t, 42, *result)
}

func TestGetMapKeysString(t *testing.T) {
	tMap := make(map[string]int)
	for i := 0; i <= 10; i++ {
		tMap["test_" + strconv.Itoa(i)] = 1
	}
	keys := getMapKeysString(tMap)
	assert.Equal(t, len(tMap), len(keys))

	for _, value := range keys {
		assert.Equal(t, 1, tMap[value])
		delete(tMap, value)
	}

	assert.Equal(t, 0, len(tMap))
}

func TestGetMapKeysInt(t *testing.T) {
	tMap := make(map[int]int)
	for i := 0; i <= 10; i++ {
		tMap[i] = 1
	}
	keys := getMapKeysInt(tMap)
	assert.Equal(t, len(tMap), len(keys))

	for _, value := range keys {
		assert.Equal(t, 1, tMap[value])
		delete(tMap, value)
	}

	assert.Equal(t, 0, len(tMap))
}
<|endoftext|>


<commit_before>package nodes

import (
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/stampzilla/gozwave/commands"
	"github.com/stampzilla/gozwave/database"
	"github.com/stampzilla/gozwave/events"
	"github.com/stampzilla/gozwave/functions"
	"github.com/stampzilla/gozwave/serialapi"
)

func New(address byte, connection *serialapi.Connection, eventQue chan interface{}) (*node, chan struct{}) {
	n := &node{
		Id_:        address,
		connection: connection,
		eventQue:   eventQue,
	}

	c := make(chan struct{})

	go n.Worker(c)

	return n, c
}

type node struct {
	Id_     byte `json:"id"`
	IsAwake bool `json:"is_awake"`

	ProtocolInfo        *functions.FuncGetNodeProtocolInfo
	ManufacurerSpecific *commands.CmdManufacturerSpecific

	Device interface{}

	connection *serialapi.Connection
	eventQue   chan interface{}

	awake      chan struct{}
	identified bool
}

func (n *node) Id() byte {
	return n.Id_
}

func (n *node) isAwake() chan struct{} {
	if n.IsAwake {
		c := make(chan struct{})
		close(c)
		return c
	}

	if n.awake == nil {
		n.awake = make(chan struct{})
	}

	return n.awake
}

func (n *node) Worker(basicDone chan struct{}) {

	updateChan := n.connection.RegisterNode(n.Id())
	go func() {
		for {
			select {
			case update := <-updateChan:
				//logrus.Errorf("Node received update: %T", update)
				switch msg := update.(type) {
				case *serialapi.Message:
					switch body := msg.Data.(type) {
					case *functions.FuncApplicationCommandHandler:
						switch body.Data.(type) {
						case *commands.CmdWakeUp:
							logrus.Error("NODE RECEIVED WAKEUP")
							if n.awake != nil {
								close(n.awake)
								n.awake = nil
							}
						}
					}
				}
			}
		}
	}()

	go n.Identify(basicDone)

	if n.Id() != 1 {
		n.pushEvent(events.NodeDiscoverd{
			Address: n.Id(),
		})
	}

	for {
		select {}
	}
}
func (n *node) Identify(basicDone chan struct{}) {
	logrus.Warnf("Started identification on node %d", n.Id())
	defer logrus.Warnf("Ended identification on node %d", n.Id())

	defer func() {
		if basicDone != nil {
			close(basicDone)
			basicDone = nil
		}
	}()

	if n.Id() == 1 {
		return
	}

	for {
		if n.ProtocolInfo == nil {
			err := n.RequestProtocolInfo()
			if err != nil {
				<-time.After(time.Second * 10)
				continue
			}
			n.pushEvent(events.NodeUpdated{
				Address: n.Id(),
			})
		}

		n.IsAwake = n.ProtocolInfo.Listening

		if basicDone != nil {
			close(basicDone)
			basicDone = nil
		}

		<-n.isAwake()
		//<-self.Connection.SendRaw([]byte{functions.GetNodeProtocolInfo, byte(index + 1)}) // Request node information
		//		nodeinfo := self.WaitForGetNodeProtocolInfo()

		// All manufacturer specific information such as vendor, vendors product ID and product type
		if n.ManufacurerSpecific == nil {
			err := n.RequestManufacturerSpecific()
			if err != nil {
				<-time.After(time.Second * 10)
				continue
			}

			n.Device = database.New(n.ManufacurerSpecific.Manufacturer, n.ManufacurerSpecific.Type, n.ManufacurerSpecific.Id)

			n.pushEvent(events.NodeUpdated{
				Address: n.Id(),
			})

		}

		//<-self.Connection.SendRaw([]byte{functions.IsFailedNode, byte(index + 1)}) // Request is failed node
		//	<-self.SendRaw([]byte{0xa0, byte(index + 1)}) // Request ?

		// All firmware and Z-Wave version information
		// All switching and reporting capabilities including current switching states and sensor values.
		// Number and maximum size of association groups
		// Configuration values if known
		// The Gateways will then do some initial setup:
		// If needed a certain predefined wakeup interval is set for battery operated devices
		// If available and requested certain standard defaults behaviours are configured in the device
		// If association groups are available the gateway will always put its own Node ID into these association groups in order to be informed about status changes and to be prepared for using associations for scene switching
		// The user can change all values. However it needs to be clear that particularly removing the gateways Node ID from the association groups may cause malfunctions of the gateway.

		n.identified = true
		return
	}
}

func (self *node) pushEvent(event interface{}) {
	select {
	case self.eventQue <- event:
	default:
	}
}
<commit_msg>Added a command "GetVersion" call in the identification sequence<commit_after>package nodes

import (
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/stampzilla/gozwave/commands"
	"github.com/stampzilla/gozwave/database"
	"github.com/stampzilla/gozwave/events"
	"github.com/stampzilla/gozwave/functions"
	"github.com/stampzilla/gozwave/serialapi"
)

func New(address byte, connection *serialapi.Connection, eventQue chan interface{}) (*node, chan struct{}) {
	n := &node{
		Id_:        address,
		connection: connection,
		eventQue:   eventQue,
	}

	c := make(chan struct{})

	go n.Worker(c)

	return n, c
}

type node struct {
	Id_     byte `json:"id"`
	IsAwake bool `json:"is_awake"`

	ProtocolInfo        *functions.FuncGetNodeProtocolInfo
	ManufacurerSpecific *commands.CmdManufacturerSpecific

	Device interface{}

	connection *serialapi.Connection
	eventQue   chan interface{}

	awake      chan struct{}
	identified bool
}

func (n *node) Id() byte {
	return n.Id_
}

func (n *node) isAwake() chan struct{} {
	if n.IsAwake {
		c := make(chan struct{})
		close(c)
		return c
	}

	if n.awake == nil {
		n.awake = make(chan struct{})
	}

	return n.awake
}

func (n *node) Worker(basicDone chan struct{}) {

	updateChan := n.connection.RegisterNode(n.Id())
	go func() {
		for {
			select {
			case update := <-updateChan:
				//logrus.Errorf("Node received update: %T", update)
				switch msg := update.(type) {
				case *serialapi.Message:
					switch body := msg.Data.(type) {
					case *functions.FuncApplicationCommandHandler:
						switch body.Data.(type) {
						case *commands.CmdWakeUp:
							logrus.Error("NODE RECEIVED WAKEUP")
							if n.awake != nil {
								close(n.awake)
								n.awake = nil
							}
						}
					}
				}
			}
		}
	}()

	go n.Identify(basicDone)

	if n.Id() != 1 {
		n.pushEvent(events.NodeDiscoverd{
			Address: n.Id(),
		})
	}

	for {
		select {}
	}
}
func (n *node) Identify(basicDone chan struct{}) {
	logrus.Warnf("Started identification on node %d", n.Id())
	defer logrus.Warnf("Ended identification on node %d", n.Id())

	defer func() {
		if basicDone != nil {
			close(basicDone)
			basicDone = nil
		}
	}()

	if n.Id() == 1 {
		return
	}

	for {
		if n.ProtocolInfo == nil {
			err := n.RequestProtocolInfo()
			if err != nil {
				<-time.After(time.Second * 10)
				continue
			}
			n.pushEvent(events.NodeUpdated{
				Address: n.Id(),
			})
		}

		n.IsAwake = n.ProtocolInfo.Listening

		if basicDone != nil {
			close(basicDone)
			basicDone = nil
		}

		<-n.isAwake()
		//<-self.Connection.SendRaw([]byte{functions.GetNodeProtocolInfo, byte(index + 1)}) // Request node information
		//		nodeinfo := self.WaitForGetNodeProtocolInfo()

		// All manufacturer specific information such as vendor, vendors product ID and product type
		if n.ManufacurerSpecific == nil {
			err := n.RequestManufacturerSpecific()
			if err != nil {
				<-time.After(time.Second * 10)
				continue
			}

			n.Device = database.New(n.ManufacurerSpecific.Manufacturer, n.ManufacurerSpecific.Type, n.ManufacurerSpecific.Id)

			n.pushEvent(events.NodeUpdated{
				Address: n.Id(),
			})

		}

		resp := <-n.connection.SendRaw([]byte{
			functions.SendData, // Function
			byte(n.Id()),       // Node id
			0x02,               // Length
			commands.Version,   // Command
			0x11,               // VersionCmd_Get
			0x00,
			//0x05, // TransmitOptions?
			//0x23, // Callback?
		}, time.Second*10) // Request node information

		logrus.Println("%#v", resp)

		//<-self.Connection.SendRaw([]byte{functions.IsFailedNode, byte(index + 1)}) // Request is failed node
		//	<-self.SendRaw([]byte{0xa0, byte(index + 1)}) // Request ?

		// All firmware and Z-Wave version information
		// All switching and reporting capabilities including current switching states and sensor values.
		// Number and maximum size of association groups
		// Configuration values if known
		// The Gateways will then do some initial setup:
		// If needed a certain predefined wakeup interval is set for battery operated devices
		// If available and requested certain standard defaults behaviours are configured in the device
		// If association groups are available the gateway will always put its own Node ID into these association groups in order to be informed about status changes and to be prepared for using associations for scene switching
		// The user can change all values. However it needs to be clear that particularly removing the gateways Node ID from the association groups may cause malfunctions of the gateway.

		n.identified = true
		return
	}
}

func (self *node) pushEvent(event interface{}) {
	select {
	case self.eventQue <- event:
	default:
	}
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/elazarl/goproxy"

	"flag"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"os/signal"
	"regexp"
	"strings"
	"syscall"
	"time"
)

const (
	appendLog int = iota
	reopenLog int = iota
)

// Digest auth. operation type
const (
	validateUser int = iota
	getNonce     int = iota
)

// Digest auth. resp status
const (
	authOk     int = iota
	authFailed int = iota
	nonceOk    int = iota
)

const proxyForwardedForHeader = "X-Forwarded-For"

type Meta struct {
	action int
	req    *http.Request
	resp   *http.Response
	err    error
	time   time.Time
}

type BasicAuthRequest struct {
	data        *BasicAuthData
	respChannel chan *BasicAuthResponse
}

type BasicAuthResponse struct {
	status bool
}

type DigestAuthRequest struct {
	data        *DigestAuthData
	op          int
	respChannel chan *DigestAuthResponse
}

type DigestAuthResponse struct {
	data   string
	status int
}

func makeBasicAuthValidator(auth *BasicAuth) BasicAuthFunc {
	channel := make(chan *BasicAuthRequest)
	validator := func() {
		for e := range channel {
			status := auth.Validate(e.data)
			e.respChannel <- &BasicAuthResponse{status}
		}
	}

	go validator()

	return func(authData *BasicAuthData) *BasicAuthResponse {
		request := &BasicAuthRequest{authData, make(chan *BasicAuthResponse)}
		channel <- request
		return <-request.respChannel
	}
}

func makeDigestAuthValidator(auth *DigestAuth) DigestAuthFunc {
	channel := make(chan *DigestAuthRequest)
	processor := func() {
		for e := range channel {
			var response *DigestAuthResponse
			switch e.op {
			case validateUser:
				status := auth.Validate(e.data)
				if status {
					response = &DigestAuthResponse{status: authOk}
				} else {
					response = &DigestAuthResponse{status: authFailed}
				}
			case getNonce:
				nonce := auth.NewNonce()
				response = &DigestAuthResponse{status: nonceOk, data: nonce}
			default:
				panic("unexpected operation type")
			}
			e.respChannel <- response
		}
	}

	go processor()

	f := func(authData *DigestAuthData, op int) *DigestAuthResponse {
		request := &DigestAuthRequest{data: authData, op: op, respChannel: make(chan *DigestAuthResponse)}
		channel <- request
		return <-request.respChannel
	}

	return f
}

func fprintf(nr *int64, err *error, w io.Writer, pat string, a ...interface{}) {
	if *err != nil {
		return
	}
	var n int
	n, *err = fmt.Fprintf(w, pat, a...)
	*nr += int64(n)
}

func write(nr *int64, err *error, w io.Writer, b []byte) {
	if *err != nil {
		return
	}
	var n int
	n, *err = w.Write(b)
	*nr += int64(n)
}

func (m *Meta) WriteTo(w io.Writer) (nr int64, err error) {
	if m.resp != nil {
		if m.resp.Request != nil {
			fprintf(&nr, &err, w,
				"%v %v %v %v %v %v\n",
				m.time.Format(time.RFC3339),
				m.resp.Request.RemoteAddr,
				m.resp.Request.Method,
				m.resp.Request.URL,
				m.resp.StatusCode,
				m.resp.ContentLength)
		} else {
			fprintf(&nr, &err, w,
				"%v %v %v %v %v %v\n",
				m.time.Format(time.RFC3339),
				"-",
				"-",
				"-",
				m.resp.StatusCode,
				m.resp.ContentLength)
		}
	} else if m.req != nil {
		fprintf(&nr, &err, w,
			"%v %v %v %v %v %v\n",
			m.time.Format(time.RFC3339),
			m.req.RemoteAddr,
			m.req.Method,
			m.req.URL,
			"-",
			"-")
	}

	return
}

type HttpLogger struct {
	path        string
	logChannel  chan *Meta
	errorChanel chan error
}

func NewLogger(conf *Configuration) *HttpLogger {
	var fh *os.File

	if conf.AccessLog != "" {
		var err error
		fh, err = os.OpenFile(conf.AccessLog, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
		if err != nil {
			log.Fatalf("Couldn't open log file: %v", err)
		}
	}

	logger := &HttpLogger{conf.AccessLog, make(chan *Meta), make(chan error)}

	go func() {
		for m := range logger.logChannel {
			if fh != nil {
				switch m.action {
				case appendLog:
					if _, err := m.WriteTo(fh); err != nil {
						log.Println("Can't write meta", err)
					}
				case reopenLog:
					fh.Close()
					var err error
					fh, err = os.OpenFile(conf.AccessLog, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
					if err != nil {
						log.Fatalf("Couldn't reopen log file: %v", err)
					}
				}
			}
		}
		logger.errorChanel <- fh.Close()
	}()

	return logger
}

var emptyResp = &http.Response{}
var emptyReq = &http.Request{}

func (logger *HttpLogger) LogReq(req *http.Request, ctx *goproxy.ProxyCtx) {
	if req == nil {
		req = emptyReq
	}

	logger.LogMeta(&Meta{
		action: appendLog,
		req:    req,
		err:    ctx.Error,
		time:   time.Now()})
}

func (logger *HttpLogger) LogResp(resp *http.Response, ctx *goproxy.ProxyCtx) {
	if resp == nil {
		resp = emptyResp
	}

	logger.LogMeta(&Meta{
		action: appendLog,
		resp:   resp,
		err:    ctx.Error,
		time:   time.Now()})
}

func (logger *HttpLogger) LogMeta(m *Meta) {
	logger.logChannel <- m
}

func (logger *HttpLogger) Close() error {
	close(logger.logChannel)
	return <-logger.errorChanel
}

func (logger *HttpLogger) Reopen() {
	logger.LogMeta(&Meta{action: reopenLog})
}

func setAllowedNetworksHandler(conf *Configuration, proxy *goproxy.ProxyHttpServer) {
	if conf.AllowedNetworks != nil && len(conf.AllowedNetworks) > 0 {
		proxy.OnRequest(goproxy.Not(sourceIpMatches(conf.AllowedNetworks))).HandleConnect(goproxy.AlwaysReject)
		proxy.OnRequest(goproxy.Not(sourceIpMatches(conf.AllowedNetworks))).DoFunc(
			func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
				return req, goproxy.NewResponse(req, goproxy.ContentTypeHtml, http.StatusForbidden, "Access denied")
			})
	}

	if conf.DisallowedNetworks != nil && len(conf.DisallowedNetworks) > 0 {
		proxy.OnRequest(sourceIpMatches(conf.DisallowedNetworks)).HandleConnect(goproxy.AlwaysReject)
		proxy.OnRequest(sourceIpMatches(conf.DisallowedNetworks)).DoFunc(
			func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
				return req, goproxy.NewResponse(req, goproxy.ContentTypeHtml, http.StatusForbidden, "Access denied")
			})
	}
}

func sourceIpMatches(networks []string) goproxy.ReqConditionFunc {
	cidrs := make([](*net.IPNet), len(networks))
	for idx, network := range networks {
		_, cidrnet, _ := net.ParseCIDR(network)
		cidrs[idx] = cidrnet
	}

	return func(req *http.Request, ctx *goproxy.ProxyCtx) bool {
		ip, _, err := net.SplitHostPort(req.RemoteAddr)
		if err != nil {
			ctx.Warnf("coudn't parse remote address %v: %v", req.RemoteAddr, err)
			return false
		}
		addr := net.ParseIP(ip)
		for _, network := range cidrs {
			if network.Contains(addr) {
				return true
			}
		}
		return false
	}
}

func setAllowedConnectPortsHandler(conf *Configuration, proxy *goproxy.ProxyHttpServer) {
	if conf.AllowedConnectPorts != nil && len(conf.AllowedConnectPorts) > 0 {
		ports := make([]string, len(conf.AllowedConnectPorts))
		for i, v := range conf.AllowedConnectPorts {
			ports[i] = ":" + v
		}
		rx := "(" + strings.Join(ports, "|") + ")$"
		proxy.OnRequest(goproxy.Not(goproxy.ReqHostMatches(regexp.MustCompile(rx)))).HandleConnect(goproxy.AlwaysReject)
	}
}

func setForwardedForHeaderHandler(conf *Configuration, proxy *goproxy.ProxyHttpServer) {
	f := func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
		ip, _, err := net.SplitHostPort(req.RemoteAddr)
		if err != nil {
			ctx.Warnf("coudn't parse remote address %v: %v", req.RemoteAddr, err)
			return req, nil
		}

		switch conf.ForwardedFor {
		case "on":
			header := req.Header.Get(proxyForwardedForHeader)
			if header == "" {
				req.Header.Add(proxyForwardedForHeader, ip)
			} else {
				header = header + ", " + ip
				req.Header.Del(proxyForwardedForHeader)
				req.Header.Add(proxyForwardedForHeader, header)
			}
		case "delete":
			req.Header.Del(proxyForwardedForHeader)
		case "truncate":
			req.Header.Del(proxyForwardedForHeader)
			req.Header.Add(proxyForwardedForHeader, ip)
		}

		return req, nil
	}

	proxy.OnRequest().DoFunc(f)
}

func makeCustomDial(localAddr *net.TCPAddr) func(string, string) (net.Conn, error) {
	return func(network, addr string) (net.Conn, error) {
		remoteAddr, err := net.ResolveTCPAddr(network, addr)
		if err != nil {
			return nil, err
		}
		c, err := net.DialTCP(network, localAddr, remoteAddr)
		return c, err
	}
}

func createProxy(conf *Configuration) *goproxy.ProxyHttpServer {
	proxy := goproxy.NewProxyHttpServer()
	setActivityLog(conf, proxy)

	if conf.BindIP != "" {
		var laddr string
		addressOk := true
		if ip := net.ParseIP(conf.BindIP); ip != nil {
			if ip4 := ip.To4(); ip4 != nil {
				laddr = conf.BindIP + ":0"
			} else if ip16 := ip.To16(); ip16 != nil {
				laddr = "[" + conf.BindIP + "]:0"
			} else {
				proxy.Logger.Printf("[WARN] couldn't use \"%v\" as outgoing request address.\n", conf.BindIP)
				addressOk = false
			}
		}
		if addressOk {
			if addr, err := net.ResolveTCPAddr("tcp", laddr); err == nil {
				proxy.Tr.Dial = makeCustomDial(addr)
			} else {
				proxy.Logger.Printf("[WARN] couldn't use \"%v\" as outgoing request address. %v\n", conf.BindIP, err)
			}
		}
	}

	return proxy
}

func setActivityLog(conf *Configuration, proxy *goproxy.ProxyHttpServer) {
	if conf.ActivityLog != "" {
		fh, err := os.OpenFile(conf.ActivityLog, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
		if err != nil {
			log.Fatalf("couldn't open activity log file %v: %v", conf.ActivityLog, err)
		}
		proxy.Logger = log.New(fh, "", log.LstdFlags)
	}
}

func main() {
	config := flag.String("config", "microproxy.json", "proxy configuration file")
	verbose := flag.Bool("v", false, "enable verbose debug mode")

	flag.Parse()

	conf := NewConfiguration(*config)

	proxy := createProxy(conf)
	proxy.Verbose = *verbose

	logger := NewLogger(conf)

	if conf.AuthFile != "" {
		if conf.AuthType == "basic" {
			auth, err := NewBasicAuthFromFile(conf.AuthFile)
			if err != nil {
				proxy.Logger.Fatalf("couldn't create basic auth structure: %v\n", err)
			}
			ProxyBasic(proxy, conf.AuthRealm, makeBasicAuthValidator(auth))
		} else {
			auth, err := NewDigestAuthFromFile(conf.AuthFile)
			if err != nil {
				proxy.Logger.Fatalf("couldn't create digest auth structure: %v\n", err)
			}
			ProxyDigest(proxy, conf.AuthRealm, makeDigestAuthValidator(auth))
		}
	}

	proxy.OnRequest().HandleConnectFunc(
		func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {
			if ctx.Req == nil {
				ctx.Req = emptyReq
			}

			logger.LogMeta(&Meta{
				action: appendLog,
				req:    ctx.Req,
				err:    ctx.Error,
				time:   time.Now()})

			return goproxy.OkConnect, host
		})

	setAllowedConnectPortsHandler(conf, proxy)
	setAllowedNetworksHandler(conf, proxy)
	setForwardedForHeaderHandler(conf, proxy)

	proxy.OnResponse().DoFunc(
		func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {
			logger.LogResp(resp, ctx)
			return resp
		})

	signalChannel := make(chan os.Signal)
	signal.Notify(signalChannel, os.Interrupt, syscall.SIGUSR1)

	go func() {
		for {
			sig := <-signalChannel
			switch sig {
			case os.Interrupt:
				proxy.Logger.Println("got interrupt signal, exiting")
				logger.Close()
				os.Exit(0)
			case syscall.SIGUSR1:
				proxy.Logger.Println("got USR1 signal, reopening logs")
				// Reopen access log
				logger.Reopen()
				// Reopen activity log
				setActivityLog(conf, proxy)
			}
		}
	}()

	proxy.Logger.Println("starting proxy")

	log.Fatal(http.ListenAndServe(conf.Listen, proxy))
}
<commit_msg>refactoring<commit_after>package main

import (
	"github.com/elazarl/goproxy"

	"flag"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"os/signal"
	"regexp"
	"strings"
	"syscall"
	"time"
)

const (
	appendLog int = iota
	reopenLog int = iota
)

// Digest auth. operation type
const (
	validateUser int = iota
	getNonce     int = iota
)

// Digest auth. resp status
const (
	authOk     int = iota
	authFailed int = iota
	nonceOk    int = iota
)

const proxyForwardedForHeader = "X-Forwarded-For"

type Meta struct {
	action int
	req    *http.Request
	resp   *http.Response
	err    error
	time   time.Time
}

type BasicAuthRequest struct {
	data        *BasicAuthData
	respChannel chan *BasicAuthResponse
}

type BasicAuthResponse struct {
	status bool
}

type DigestAuthRequest struct {
	data        *DigestAuthData
	op          int
	respChannel chan *DigestAuthResponse
}

type DigestAuthResponse struct {
	data   string
	status int
}

func makeBasicAuthValidator(auth *BasicAuth) BasicAuthFunc {
	channel := make(chan *BasicAuthRequest)
	validator := func() {
		for e := range channel {
			status := auth.Validate(e.data)
			e.respChannel <- &BasicAuthResponse{status}
		}
	}

	go validator()

	return func(authData *BasicAuthData) *BasicAuthResponse {
		request := &BasicAuthRequest{authData, make(chan *BasicAuthResponse)}
		channel <- request
		return <-request.respChannel
	}
}

func makeDigestAuthValidator(auth *DigestAuth) DigestAuthFunc {
	channel := make(chan *DigestAuthRequest)
	processor := func() {
		for e := range channel {
			var response *DigestAuthResponse
			switch e.op {
			case validateUser:
				status := auth.Validate(e.data)
				if status {
					response = &DigestAuthResponse{status: authOk}
				} else {
					response = &DigestAuthResponse{status: authFailed}
				}
			case getNonce:
				nonce := auth.NewNonce()
				response = &DigestAuthResponse{status: nonceOk, data: nonce}
			default:
				panic("unexpected operation type")
			}
			e.respChannel <- response
		}
	}

	go processor()

	f := func(authData *DigestAuthData, op int) *DigestAuthResponse {
		request := &DigestAuthRequest{data: authData, op: op, respChannel: make(chan *DigestAuthResponse)}
		channel <- request
		return <-request.respChannel
	}

	return f
}

func fprintf(nr *int64, err *error, w io.Writer, pat string, a ...interface{}) {
	if *err != nil {
		return
	}
	var n int
	n, *err = fmt.Fprintf(w, pat, a...)
	*nr += int64(n)
}

func write(nr *int64, err *error, w io.Writer, b []byte) {
	if *err != nil {
		return
	}
	var n int
	n, *err = w.Write(b)
	*nr += int64(n)
}

func (m *Meta) WriteTo(w io.Writer) (nr int64, err error) {
	if m.resp != nil {
		if m.resp.Request != nil {
			fprintf(&nr, &err, w,
				"%v %v %v %v %v %v\n",
				m.time.Format(time.RFC3339),
				m.resp.Request.RemoteAddr,
				m.resp.Request.Method,
				m.resp.Request.URL,
				m.resp.StatusCode,
				m.resp.ContentLength)
		} else {
			fprintf(&nr, &err, w,
				"%v %v %v %v %v %v\n",
				m.time.Format(time.RFC3339),
				"-",
				"-",
				"-",
				m.resp.StatusCode,
				m.resp.ContentLength)
		}
	} else if m.req != nil {
		fprintf(&nr, &err, w,
			"%v %v %v %v %v %v\n",
			m.time.Format(time.RFC3339),
			m.req.RemoteAddr,
			m.req.Method,
			m.req.URL,
			"-",
			"-")
	}

	return
}

type HttpLogger struct {
	path        string
	logChannel  chan *Meta
	errorChanel chan error
}

func NewLogger(conf *Configuration) *HttpLogger {
	var fh *os.File

	if conf.AccessLog != "" {
		var err error
		fh, err = os.OpenFile(conf.AccessLog, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
		if err != nil {
			log.Fatalf("Couldn't open log file: %v", err)
		}
	}

	logger := &HttpLogger{conf.AccessLog, make(chan *Meta), make(chan error)}

	go func() {
		for m := range logger.logChannel {
			if fh != nil {
				switch m.action {
				case appendLog:
					if _, err := m.WriteTo(fh); err != nil {
						log.Println("Can't write meta", err)
					}
				case reopenLog:
					fh.Close()
					var err error
					fh, err = os.OpenFile(conf.AccessLog, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
					if err != nil {
						log.Fatalf("Couldn't reopen log file: %v", err)
					}
				}
			}
		}
		logger.errorChanel <- fh.Close()
	}()

	return logger
}

var emptyResp = &http.Response{}
var emptyReq = &http.Request{}

func (logger *HttpLogger) LogReq(req *http.Request, ctx *goproxy.ProxyCtx) {
	if req == nil {
		req = emptyReq
	}

	logger.LogMeta(&Meta{
		action: appendLog,
		req:    req,
		err:    ctx.Error,
		time:   time.Now()})
}

func (logger *HttpLogger) LogResp(resp *http.Response, ctx *goproxy.ProxyCtx) {
	if resp == nil {
		resp = emptyResp
	}

	logger.LogMeta(&Meta{
		action: appendLog,
		resp:   resp,
		err:    ctx.Error,
		time:   time.Now()})
}

func (logger *HttpLogger) LogMeta(m *Meta) {
	logger.logChannel <- m
}

func (logger *HttpLogger) Close() error {
	close(logger.logChannel)
	return <-logger.errorChanel
}

func (logger *HttpLogger) Reopen() {
	logger.LogMeta(&Meta{action: reopenLog})
}

func setAllowedNetworksHandler(conf *Configuration, proxy *goproxy.ProxyHttpServer) {
	if conf.AllowedNetworks != nil && len(conf.AllowedNetworks) > 0 {
		proxy.OnRequest(goproxy.Not(sourceIpMatches(conf.AllowedNetworks))).HandleConnect(goproxy.AlwaysReject)
		proxy.OnRequest(goproxy.Not(sourceIpMatches(conf.AllowedNetworks))).DoFunc(
			func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
				return req, goproxy.NewResponse(req, goproxy.ContentTypeHtml, http.StatusForbidden, "Access denied")
			})
	}

	if conf.DisallowedNetworks != nil && len(conf.DisallowedNetworks) > 0 {
		proxy.OnRequest(sourceIpMatches(conf.DisallowedNetworks)).HandleConnect(goproxy.AlwaysReject)
		proxy.OnRequest(sourceIpMatches(conf.DisallowedNetworks)).DoFunc(
			func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
				return req, goproxy.NewResponse(req, goproxy.ContentTypeHtml, http.StatusForbidden, "Access denied")
			})
	}
}

func sourceIpMatches(networks []string) goproxy.ReqConditionFunc {
	cidrs := make([](*net.IPNet), len(networks))
	for idx, network := range networks {
		_, cidrnet, _ := net.ParseCIDR(network)
		cidrs[idx] = cidrnet
	}

	return func(req *http.Request, ctx *goproxy.ProxyCtx) bool {
		ip, _, err := net.SplitHostPort(req.RemoteAddr)
		if err != nil {
			ctx.Warnf("coudn't parse remote address %v: %v", req.RemoteAddr, err)
			return false
		}
		addr := net.ParseIP(ip)
		for _, network := range cidrs {
			if network.Contains(addr) {
				return true
			}
		}
		return false
	}
}

func setAllowedConnectPortsHandler(conf *Configuration, proxy *goproxy.ProxyHttpServer) {
	if conf.AllowedConnectPorts != nil && len(conf.AllowedConnectPorts) > 0 {
		ports := make([]string, len(conf.AllowedConnectPorts))
		for i, v := range conf.AllowedConnectPorts {
			ports[i] = ":" + v
		}
		rx := "(" + strings.Join(ports, "|") + ")$"
		proxy.OnRequest(goproxy.Not(goproxy.ReqHostMatches(regexp.MustCompile(rx)))).HandleConnect(goproxy.AlwaysReject)
	}
}

func setForwardedForHeaderHandler(conf *Configuration, proxy *goproxy.ProxyHttpServer) {
	f := func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
		ip, _, err := net.SplitHostPort(req.RemoteAddr)
		if err != nil {
			ctx.Warnf("coudn't parse remote address %v: %v", req.RemoteAddr, err)
			return req, nil
		}

		switch conf.ForwardedFor {
		case "on":
			header := req.Header.Get(proxyForwardedForHeader)
			if header == "" {
				req.Header.Add(proxyForwardedForHeader, ip)
			} else {
				header = header + ", " + ip
				req.Header.Del(proxyForwardedForHeader)
				req.Header.Add(proxyForwardedForHeader, header)
			}
		case "delete":
			req.Header.Del(proxyForwardedForHeader)
		case "truncate":
			req.Header.Del(proxyForwardedForHeader)
			req.Header.Add(proxyForwardedForHeader, ip)
		}

		return req, nil
	}

	proxy.OnRequest().DoFunc(f)
}

func makeCustomDial(localAddr *net.TCPAddr) func(string, string) (net.Conn, error) {
	return func(network, addr string) (net.Conn, error) {
		remoteAddr, err := net.ResolveTCPAddr(network, addr)
		if err != nil {
			return nil, err
		}
		c, err := net.DialTCP(network, localAddr, remoteAddr)
		return c, err
	}
}

func createProxy(conf *Configuration) *goproxy.ProxyHttpServer {
	proxy := goproxy.NewProxyHttpServer()
	setActivityLog(conf, proxy)

	if conf.BindIP != "" {
		var laddr string
		addressOk := true
		if ip := net.ParseIP(conf.BindIP); ip != nil {
			if ip4 := ip.To4(); ip4 != nil {
				laddr = conf.BindIP + ":0"
			} else if ip16 := ip.To16(); ip16 != nil {
				laddr = "[" + conf.BindIP + "]:0"
			} else {
				proxy.Logger.Printf("[WARN] couldn't use \"%v\" as outgoing request address.\n", conf.BindIP)
				addressOk = false
			}
		}
		if addressOk {
			if addr, err := net.ResolveTCPAddr("tcp", laddr); err == nil {
				proxy.Tr.Dial = makeCustomDial(addr)
			} else {
				proxy.Logger.Printf("[WARN] couldn't use \"%v\" as outgoing request address. %v\n", conf.BindIP, err)
			}
		}
	}

	return proxy
}

func setActivityLog(conf *Configuration, proxy *goproxy.ProxyHttpServer) {
	if conf.ActivityLog != "" {
		fh, err := os.OpenFile(conf.ActivityLog, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
		if err != nil {
			log.Fatalf("couldn't open activity log file %v: %v", conf.ActivityLog, err)
		}
		proxy.Logger = log.New(fh, "", log.LstdFlags)
	}
}

func setSignalHandler(conf *Configuration, proxy *goproxy.ProxyHttpServer, logger *HttpLogger) {
	signalChannel := make(chan os.Signal)
	signal.Notify(signalChannel, os.Interrupt, syscall.SIGUSR1)

	go func() {
		for {
			sig := <-signalChannel
			switch sig {
			case os.Interrupt:
				proxy.Logger.Println("got interrupt signal, exiting")
				logger.Close()
				os.Exit(0)
			case syscall.SIGUSR1:
				proxy.Logger.Println("got USR1 signal, reopening logs")
				// Reopen access log
				logger.Reopen()
				// Reopen activity log
				setActivityLog(conf, proxy)
			}
		}
	}()
}

func main() {
	config := flag.String("config", "microproxy.json", "proxy configuration file")
	verbose := flag.Bool("v", false, "enable verbose debug mode")

	flag.Parse()

	conf := NewConfiguration(*config)

	proxy := createProxy(conf)
	proxy.Verbose = *verbose

	logger := NewLogger(conf)

	if conf.AuthFile != "" {
		if conf.AuthType == "basic" {
			auth, err := NewBasicAuthFromFile(conf.AuthFile)
			if err != nil {
				proxy.Logger.Fatalf("couldn't create basic auth structure: %v\n", err)
			}
			ProxyBasic(proxy, conf.AuthRealm, makeBasicAuthValidator(auth))
		} else {
			auth, err := NewDigestAuthFromFile(conf.AuthFile)
			if err != nil {
				proxy.Logger.Fatalf("couldn't create digest auth structure: %v\n", err)
			}
			ProxyDigest(proxy, conf.AuthRealm, makeDigestAuthValidator(auth))
		}
	}

	proxy.OnRequest().HandleConnectFunc(
		func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {
			if ctx.Req == nil {
				ctx.Req = emptyReq
			}

			logger.LogMeta(&Meta{
				action: appendLog,
				req:    ctx.Req,
				err:    ctx.Error,
				time:   time.Now()})

			return goproxy.OkConnect, host
		})

	setAllowedConnectPortsHandler(conf, proxy)
	setAllowedNetworksHandler(conf, proxy)
	setForwardedForHeaderHandler(conf, proxy)
	setSignalHandler(conf, proxy, logger)

	proxy.OnResponse().DoFunc(
		func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {
			logger.LogResp(resp, ctx)
			return resp
		})

	proxy.Logger.Println("starting proxy")

	log.Fatal(http.ListenAndServe(conf.Listen, proxy))
}
<|endoftext|>