<commit_before>package main

import (
	"bytes"
	"fmt"
	"log"
	"os"
	"os/exec"
	"syscall"
	"unsafe"
)

const (
	ansiEraseDisplay = "\033[2J"
	ansiResetCursor  = "\033[H"
	carriageReturn   = "\015"
	defaultPrompt    = ">> "
)

var originalSttyState bytes.Buffer
var winRows uint16
var winCols uint16

type winsize struct {
	rows, cols, xpixel, ypixel uint16
}

func getWinsize() winsize {
	ws := winsize{}
	syscall.Syscall(syscall.SYS_IOCTL,
		uintptr(0), uintptr(syscall.TIOCGWINSZ),
		uintptr(unsafe.Pointer(&ws)))
	return ws
}

func NewTTY() (t *TTY, err error) {
	fh, err := os.OpenFile("/dev/tty", os.O_RDWR, 0666)
	if err != nil {
		return
	}
	t = &TTY{fh, defaultPrompt}
	return
}

type TTY struct {
	*os.File
	prompt string
}

func (t *TTY) getSttyState(state *bytes.Buffer) (err error) {
	cmd := exec.Command("stty", "-g")
	cmd.Stdin = t.File
	cmd.Stdout = state
	return cmd.Run()
}

func (t *TTY) setSttyState(state *bytes.Buffer) (err error) {
	cmd := exec.Command("stty", state.String())
	cmd.Stdin = t.File
	cmd.Stdout = t.File
	return cmd.Run()
}

// Clears the screen and sets the cursor to first row, first column
func (t *TTY) resetScreen() {
	// TODO: this is probably wrong since it does not remove the clutter from
	// the tty, but only pushes it to the top where its hidden
	// Instead of using reset screen, we need to go back and redraw the screen.
	fmt.Fprint(t.File, ansiEraseDisplay+ansiResetCursor)
}

// Print prompt with `in`
func (t *TTY) printPrompt(in []byte) {
	fmt.Fprintf(t.File, t.prompt+"%s", in)
}

// Positions the cursor after the prompt and `inlen` colums to the right
func (t *TTY) cursorAfterPrompt(inlen int) {
	t.setCursorPos(0, len(t.prompt)+inlen)
}

// Sets the cursor to `line` and `col`
func (t *TTY) setCursorPos(line int, col int) {
	fmt.Fprintf(t.File, "\033[%d;%dH", line+1, col+1)
}

func init() {
	ws := getWinsize()
	winRows = ws.rows
	winCols = ws.cols
}

func main() {
	tty, err := NewTTY()
	if err != nil {
		log.Fatal(err)
	}

	err = tty.getSttyState(&originalSttyState)
	if err != nil {
		log.Fatal(err)
	}
	// TODO: this needs to be run when the process is interrupted
	defer tty.setSttyState(&originalSttyState)

	tty.setSttyState(bytes.NewBufferString("cbreak -echo"))

	cmdTemplate := "ag {{}}"
	placeholder := "{{}}"

	printer := NewPrinter(tty, int(winCols), int(winRows)-3)

	runner := &Runner{
		printer:     printer,
		template:    cmdTemplate,
		placeholder: placeholder,
		buf:         new(bytes.Buffer),
	}

	// TODO: Clean this up. This is a mess.
	var input []byte = make([]byte, 0)
	var b []byte = make([]byte, 1)

	for {
		tty.resetScreen()
		tty.printPrompt(input[:len(input)])

		if len(input) > 0 {
			runner.killCurrent()

			fmt.Fprintf(tty, "\n")

			go func() {
				runner.runWithInput(input[:len(input)])
				tty.cursorAfterPrompt(len(input))
			}()
		}

		tty.Read(b)
		switch b[0] {
		case 127:
			// Backspace
			if len(input) > 1 {
				input = input[:len(input)-1]
			} else if len(input) == 1 {
				input = nil
			}
		case 4, 10, 13:
			// Ctrl-D, line feed, carriage return
			tty.resetScreen()
			runner.writeCmdStdout(os.Stdout)
			return
		default:
			// TODO: Default is wrong here. Only append printable characters to
			// input
			input = append(input, b...)
		}
	}
}
<commit_msg>Short variable declaration and initialization<commit_after>package main

import (
	"bytes"
	"fmt"
	"log"
	"os"
	"os/exec"
	"syscall"
	"unsafe"
)

const (
	ansiEraseDisplay = "\033[2J"
	ansiResetCursor  = "\033[H"
	carriageReturn   = "\015"
	defaultPrompt    = ">> "
)

var originalSttyState bytes.Buffer
var winRows uint16
var winCols uint16

type winsize struct {
	rows, cols, xpixel, ypixel uint16
}

func getWinsize() winsize {
	ws := winsize{}
	syscall.Syscall(syscall.SYS_IOCTL,
		uintptr(0), uintptr(syscall.TIOCGWINSZ),
		uintptr(unsafe.Pointer(&ws)))
	return ws
}

func NewTTY() (t *TTY, err error) {
	fh, err := os.OpenFile("/dev/tty", os.O_RDWR, 0666)
	if err != nil {
		return
	}
	t = &TTY{fh, defaultPrompt}
	return
}

type TTY struct {
	*os.File
	prompt string
}

func (t *TTY) getSttyState(state *bytes.Buffer) (err error) {
	cmd := exec.Command("stty", "-g")
	cmd.Stdin = t.File
	cmd.Stdout = state
	return cmd.Run()
}

func (t *TTY) setSttyState(state *bytes.Buffer) (err error) {
	cmd := exec.Command("stty", state.String())
	cmd.Stdin = t.File
	cmd.Stdout = t.File
	return cmd.Run()
}

// Clears the screen and sets the cursor to first row, first column
func (t *TTY) resetScreen() {
	// TODO: this is probably wrong since it does not remove the clutter from
	// the tty, but only pushes it to the top where its hidden
	// Instead of using reset screen, we need to go back and redraw the screen.
	fmt.Fprint(t.File, ansiEraseDisplay+ansiResetCursor)
}

// Print prompt with `in`
func (t *TTY) printPrompt(in []byte) {
	fmt.Fprintf(t.File, t.prompt+"%s", in)
}

// Positions the cursor after the prompt and `inlen` colums to the right
func (t *TTY) cursorAfterPrompt(inlen int) {
	t.setCursorPos(0, len(t.prompt)+inlen)
}

// Sets the cursor to `line` and `col`
func (t *TTY) setCursorPos(line int, col int) {
	fmt.Fprintf(t.File, "\033[%d;%dH", line+1, col+1)
}

func init() {
	ws := getWinsize()
	winRows = ws.rows
	winCols = ws.cols
}

func main() {
	tty, err := NewTTY()
	if err != nil {
		log.Fatal(err)
	}

	err = tty.getSttyState(&originalSttyState)
	if err != nil {
		log.Fatal(err)
	}
	// TODO: this needs to be run when the process is interrupted
	defer tty.setSttyState(&originalSttyState)

	tty.setSttyState(bytes.NewBufferString("cbreak -echo"))

	cmdTemplate := "ag {{}}"
	placeholder := "{{}}"

	printer := NewPrinter(tty, int(winCols), int(winRows)-3)

	runner := &Runner{
		printer:     printer,
		template:    cmdTemplate,
		placeholder: placeholder,
		buf:         new(bytes.Buffer),
	}

	input := make([]byte, 0)
	b := make([]byte, 1)

	for {
		tty.resetScreen()
		tty.printPrompt(input[:len(input)])

		if len(input) > 0 {
			runner.killCurrent()

			fmt.Fprintf(tty, "\n")

			go func() {
				runner.runWithInput(input[:len(input)])
				tty.cursorAfterPrompt(len(input))
			}()
		}

		tty.Read(b)
		switch b[0] {
		case 127:
			// Backspace
			if len(input) > 1 {
				input = input[:len(input)-1]
			} else if len(input) == 1 {
				input = nil
			}
		case 4, 10, 13:
			// Ctrl-D, line feed, carriage return
			tty.resetScreen()
			runner.writeCmdStdout(os.Stdout)
			return
		default:
			// TODO: Default is wrong here. Only append printable characters to
			// input
			input = append(input, b...)
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package git

import (
	"fmt"
	"strings"
	"time"
)

const _VERSION = "0.2.3"

func Version() string {
	return _VERSION
}

var (
	// Debug enables verbose logging on everything.
	// This should be false in case Gogs starts in SSH mode.
	Debug  = false
	Prefix = "[git-module] "
)

func log(format string, args ...interface{}) {
	if !Debug {
		return
	}

	fmt.Print(Prefix)
	if len(args) == 0 {
		fmt.Println(format)
	} else {
		fmt.Printf(format+"\n", args...)
	}
}

var gitVersion string

// Version returns current Git version from shell.
func BinVersion() (string, error) {
	if len(gitVersion) > 0 {
		return gitVersion, nil
	}

	stdout, err := NewCommand("version").Run()
	if err != nil {
		return "", err
	}

	fields := strings.Fields(stdout)
	if len(fields) < 3 {
		return "", fmt.Errorf("not enough output: %s", stdout)
	}

	// Handle special case on Windows.
	i := strings.Index(fields[2], "windows")
	if i >= 1 {
		gitVersion = fields[2][:i-1]
		return gitVersion, nil
	}

	gitVersion = fields[2]
	return gitVersion, nil
}

func init() {
	BinVersion()
}

// Fsck verifies the connectivity and validity of the objects in the database
func Fsck(repoPath string, timeout time.Duration, args ...string) error {
	_, err := NewCommand("fsck").AddArguments(args...).RunInDirTimeout(timeout, repoPath)
	return err
}
<commit_msg>Add timeout safe check for fsck<commit_after>// Copyright 2015 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package git

import (
	"fmt"
	"strings"
	"time"
)

const _VERSION = "0.2.4"

func Version() string {
	return _VERSION
}

var (
	// Debug enables verbose logging on everything.
	// This should be false in case Gogs starts in SSH mode.
	Debug  = false
	Prefix = "[git-module] "
)

func log(format string, args ...interface{}) {
	if !Debug {
		return
	}

	fmt.Print(Prefix)
	if len(args) == 0 {
		fmt.Println(format)
	} else {
		fmt.Printf(format+"\n", args...)
	}
}

var gitVersion string

// Version returns current Git version from shell.
func BinVersion() (string, error) {
	if len(gitVersion) > 0 {
		return gitVersion, nil
	}

	stdout, err := NewCommand("version").Run()
	if err != nil {
		return "", err
	}

	fields := strings.Fields(stdout)
	if len(fields) < 3 {
		return "", fmt.Errorf("not enough output: %s", stdout)
	}

	// Handle special case on Windows.
	i := strings.Index(fields[2], "windows")
	if i >= 1 {
		gitVersion = fields[2][:i-1]
		return gitVersion, nil
	}

	gitVersion = fields[2]
	return gitVersion, nil
}

func init() {
	BinVersion()
}

// Fsck verifies the connectivity and validity of the objects in the database
func Fsck(repoPath string, timeout time.Duration, args ...string) error {
	// Make sure timeout makes sense.
	if timeout <= 0 {
		timeout == -1
	}
	_, err := NewCommand("fsck").AddArguments(args...).RunInDirTimeout(timeout, repoPath)
	return err
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
	"sync"

	"github.com/FreekKalter/ansi/color"
	"github.com/FreekKalter/text/columnswriter"
	"github.com/FreekKalter/text/tabwriter"
	Optarg "github.com/jteeuwen/go-pkg-optarg"
)

type colorFunc func(interface{}) *color.Escape

var colorMap map[string]colorFunc = map[string]colorFunc{
	"ok":                 func(i interface{}) *color.Escape { return color.BgDefault(color.Default(i)) },
	"file":               func(i interface{}) *color.Escape { return color.BgDefault(color.Default(i)) },
	"no_version_control": func(i interface{}) *color.Escape { return color.Bold(color.Blue(i)) },                  // Blue
	"dirty":              func(i interface{}) *color.Escape { return color.Bold(color.Red(i)) },                   // Red
	"no_remote":          func(i interface{}) *color.Escape { return color.BgBlue(color.Bold(color.Red(i))) },     // Red on Blue
	"fetch_failed":       func(i interface{}) *color.Escape { return color.BgRed(color.Bold(color.Blue(i))) },     // Blue on Red
	"branch_ahead":       func(i interface{}) *color.Escape { return color.BgYellow(color.Bold(color.Green(i))) }, // Green on Yellow
	"branch_behind":      func(i interface{}) *color.Escape { return color.BgYellow(color.Bold(color.Red(i))) },   // Red on Yellow
}

// Struct passed between gls and main
type Project struct {
	Name, State string
	Info        os.FileInfo
}
type Projects []*Project

func (projects Projects) Len() int      { return len(projects) }
func (projects Projects) Swap(i, j int) { projects[i], projects[j] = projects[j], projects[i] }

type ByName struct{ Projects }
type ByState struct{ Projects }

func (s ByName) Less(i, j int) bool {
	return strings.ToLower(s.Projects[i].Name) < strings.ToLower(s.Projects[j].Name)
}
func (s ByState) Less(i, j int) bool {
	return sortOrderStates[s.Projects[i].State] < sortOrderStates[s.Projects[j].State]
}

var (
	cleanGitRegex = regexp.MustCompile("nothing to commit")
	fetchErrors   = regexp.MustCompile("^fatal")
	branchAhead   = regexp.MustCompile("branch is ahead of")
	branchBehind  = regexp.MustCompile("branch is behind")
)

var help, list, onlyDirty, sortByState, all bool
var sortOrderStates = map[string]int{"ok": 0, "no_version_control": 1, "dirty": 2, "no_remote": 3, "fetch_failed": 4, "branch_ahead": 5, "branch_behind": 6}
var TimeFormat = "Jan 2,2006 15:04"
var wg sync.WaitGroup

func main() {
	//flag.BoolVar(&help, "help", false, "print help message")
	//flag.BoolVar(&list, "list", false, "display results in 1 long list")
	//flag.BoolVar(&all, "all", false, "display files and folders staring with a dot")
	//flag.BoolVar(&onlyDirty, "dirty", false, "only show diry dirs, this is very fast because it does not check remotes")
	//flag.BoolVar(&sortByState, "statesort", false, "sort output by state")
	//flag.Parse()
	Optarg.Add("h", "help", "print help message", false)
	Optarg.Add("l", "list", "display results in 1 long list", false)
	Optarg.Add("a", "all", "display files and folders staring with a dot", false)
	Optarg.Add("d", "dirty", "only show diry dirs, this is very fast because it does not check remotes", false)
	Optarg.Add("s", "statesort", "sort output by state", false)
	for opt := range Optarg.Parse() {
		switch opt.ShortName {
		case "h":
			Optarg.Usage()
			fmt.Println("")
			fmt.Println("Color codes:")
			for k, v := range colorMap {
				fmt.Println(v(k))
			}
			return
		case "l":
			list = true
		case "a":
			all = true
		case "d":
			onlyDirty = true
		case "s":
			sortByState = true
		}
	}

	// Sort out path and files in that dir
	var path string
	if len(flag.Args()) > 0 {
		path = flag.Arg(0)
	} else {
		path = "."
	}
	var files []string
	var err error
	if all {
		files, err = filepath.Glob(filepath.Join(path, "*"))
	} else {
		files, err = filepath.Glob(filepath.Join(path, "[^.]*"))
	}
	if err != nil {
		panic(err)
	}

	// Start goroutine for every dir found
	glsResults := make(chan *Project, 1000)
	var projects Projects
	for _, file := range files {
		file_info, _ := os.Stat(file)
		if file_info.IsDir() {
			wg.Add(1)
			go gls(&Project{Name: file, Info: file_info}, glsResults)
		} else {
			projects = append(projects, &Project{Name: file, State: "file", Info: file_info})
		}
	}
	wg.Wait()
	close(glsResults)

	// Gather results and process them
	for res := range glsResults {
		// make a copy to add to []projects, because res always points to the same address space
		toAppend := res
		toAppend.Name = filepath.Base(res.Name)
		projects = append(projects, toAppend)
	}
	if sortByState {
		sort.Sort(ByState{projects})
	} else {
		sort.Sort(ByName{projects})
	}

	if list {
		//TODO: 0 pad dates
		w := new(tabwriter.Writer)
		w.Init(os.Stdout, 0, 8, 0, '\t', tabwriter.StripEscape)
		for _, p := range projects {
			fmt.Fprintf(w, "%s\t%s\t%s\n", colorMap[p.State](p.Name), humanReadable(p.Info.Size()), p.Info.ModTime().Format(TimeFormat))
		}
		w.Flush()
	} else {

		var projectsString string
		for _, p := range projects {
			projectsString = fmt.Sprintf("%s\t%s", projectsString, colorMap[p.State](p.Name))
		}

		w := columnswriter.New(os.Stdout, '\t', 0, 2)
		fmt.Fprint(w, projectsString)
		w.Flush()
	}
}

func gls(project *Project /*dirName string*/, result chan *Project) {
	defer wg.Done()

	// First chek, is the directory under (git) version control
	if ok, _ := exists(filepath.Join(project.Name, ".git")); !ok {
		if !onlyDirty {
			project.State = "no_version_control"
			result <- project
		}
		return
	}

	gitDir := fmt.Sprintf("--git-dir=%s", filepath.Join(project.Name, ".git"))
	gitTree := fmt.Sprintf("--work-tree=%s", project.Name)
	output, err := exec.Command("git", gitDir, gitTree, "status").Output() //, gitDir, gitTree, "status")
	if err != nil {
		panic(err)
	}
	// Are there uncommitted changes is the directory (dirty)
	if !cleanGitRegex.MatchString(strings.TrimSpace(string(output))) {
		project.State = "dirty"
		result <- project
		return
	} else if onlyDirty {
		return
	}

	// Check if the repo has a remote
	output, err = exec.Command("git", gitDir, gitTree, "remote", "-v").Output()
	if err != nil {
		panic(err)
	}
	if len(output) == 0 {
		project.State = "no_remote"
		result <- project
		return
	}

	// Fetch latest changes from remote
	output, err = exec.Command("git", gitDir, gitTree, "fetch").Output()
	if err != nil {
		project.State = "fetch_failed"
		result <- project
		return
	}
	outputStr := strings.TrimSpace(string(output))
	if fetchErrors.MatchString(outputStr) {
		project.State = "fetch_failed"
		result <- project
		return
	}

	output, err = exec.Command("git", gitDir, gitTree, "status").Output()
	if err != nil {
		panic(err)
	}
	outputStr = strings.TrimSpace(string(output))

	// Is branch ahead of behind of remote
	if branchAhead.MatchString(outputStr) {
		project.State = "branch_ahead"
		result <- project
		return
	} else if branchBehind.MatchString(outputStr) {
		project.State = "branch_behind"
		result <- project
		return
	}

	project.State = "ok"
	result <- project
}

func exists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

func humanReadable(filesize int64) string {
	fs := float64(filesize)
	for _, x := range []string{"b", "kb", "mb", "gb", "tb"} {
		if fs < 1024 {
			return fmt.Sprintf("%3.1f % s", fs, x)
		}
		fs /= 1024
	}
	return ""
}
<commit_msg>Fixed zero padding in long-list output<commit_after>package main

import (
	"flag"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
	"sync"

	"github.com/FreekKalter/ansi/color"
	"github.com/FreekKalter/text/columnswriter"
	"github.com/FreekKalter/text/tabwriter"
	Optarg "github.com/jteeuwen/go-pkg-optarg"
)

type colorFunc func(interface{}) *color.Escape

var colorMap map[string]colorFunc = map[string]colorFunc{
	"ok":                 func(i interface{}) *color.Escape { return color.BgDefault(color.Default(i)) },
	"file":               func(i interface{}) *color.Escape { return color.BgDefault(color.Default(i)) },
	"no_version_control": func(i interface{}) *color.Escape { return color.Bold(color.Blue(i)) },                  // Blue
	"dirty":              func(i interface{}) *color.Escape { return color.Bold(color.Red(i)) },                   // Red
	"no_remote":          func(i interface{}) *color.Escape { return color.BgBlue(color.Bold(color.Red(i))) },     // Red on Blue
	"fetch_failed":       func(i interface{}) *color.Escape { return color.BgRed(color.Bold(color.Blue(i))) },     // Blue on Red
	"branch_ahead":       func(i interface{}) *color.Escape { return color.BgYellow(color.Bold(color.Green(i))) }, // Green on Yellow
	"branch_behind":      func(i interface{}) *color.Escape { return color.BgYellow(color.Bold(color.Red(i))) },   // Red on Yellow
}

// Struct passed between gls and main
type Project struct {
	Name, State string
	Info        os.FileInfo
}
type Projects []*Project

func (projects Projects) Len() int      { return len(projects) }
func (projects Projects) Swap(i, j int) { projects[i], projects[j] = projects[j], projects[i] }

type ByName struct{ Projects }
type ByState struct{ Projects }

func (s ByName) Less(i, j int) bool {
	return strings.ToLower(s.Projects[i].Name) < strings.ToLower(s.Projects[j].Name)
}
func (s ByState) Less(i, j int) bool {
	return sortOrderStates[s.Projects[i].State] < sortOrderStates[s.Projects[j].State]
}

var (
	cleanGitRegex = regexp.MustCompile("nothing to commit")
	fetchErrors   = regexp.MustCompile("^fatal")
	branchAhead   = regexp.MustCompile("branch is ahead of")
	branchBehind  = regexp.MustCompile("branch is behind")
)

var help, list, onlyDirty, sortByState, all bool
var sortOrderStates = map[string]int{"ok": 0, "no_version_control": 1, "dirty": 2, "no_remote": 3, "fetch_failed": 4, "branch_ahead": 5, "branch_behind": 6}
var TimeFormat = "Jan 02,2006 15:04"
var wg sync.WaitGroup

func main() {
	//flag.BoolVar(&help, "help", false, "print help message")
	//flag.BoolVar(&list, "list", false, "display results in 1 long list")
	//flag.BoolVar(&all, "all", false, "display files and folders staring with a dot")
	//flag.BoolVar(&onlyDirty, "dirty", false, "only show diry dirs, this is very fast because it does not check remotes")
	//flag.BoolVar(&sortByState, "statesort", false, "sort output by state")
	//flag.Parse()
	Optarg.Add("h", "help", "print help message", false)
	Optarg.Add("l", "list", "display results in 1 long list", false)
	Optarg.Add("a", "all", "display files and folders staring with a dot", false)
	Optarg.Add("d", "dirty", "only show diry dirs, this is very fast because it does not check remotes", false)
	Optarg.Add("s", "statesort", "sort output by state", false)
	for opt := range Optarg.Parse() {
		switch opt.ShortName {
		case "h":
			Optarg.Usage()
			fmt.Println("")
			fmt.Println("Color codes:")
			for k, v := range colorMap {
				fmt.Println(v(k))
			}
			return
		case "l":
			list = true
		case "a":
			all = true
		case "d":
			onlyDirty = true
		case "s":
			sortByState = true
		}
	}

	// Sort out path and files in that dir
	var path string
	if len(flag.Args()) > 0 {
		path = flag.Arg(0)
	} else {
		path = "."
	}
	var files []string
	var err error
	if all {
		files, err = filepath.Glob(filepath.Join(path, "*"))
	} else {
		files, err = filepath.Glob(filepath.Join(path, "[^.]*"))
	}
	if err != nil {
		panic(err)
	}

	// Start goroutine for every dir found
	glsResults := make(chan *Project, 1000)
	var projects Projects
	for _, file := range files {
		file_info, _ := os.Stat(file)
		if file_info.IsDir() {
			wg.Add(1)
			go gls(&Project{Name: file, Info: file_info}, glsResults)
		} else {
			projects = append(projects, &Project{Name: file, State: "file", Info: file_info})
		}
	}
	wg.Wait()
	close(glsResults)

	// Gather results and process them
	for res := range glsResults {
		// make a copy to add to []projects, because res always points to the same address space
		toAppend := res
		toAppend.Name = filepath.Base(res.Name)
		projects = append(projects, toAppend)
	}
	if sortByState {
		sort.Sort(ByState{projects})
	} else {
		sort.Sort(ByName{projects})
	}

	if list {
		w := new(tabwriter.Writer)
		w.Init(os.Stdout, 0, 8, 0, '\t', tabwriter.StripEscape)
		for _, p := range projects {
			fmt.Fprintf(w, "%s\t%s\t%s\n", colorMap[p.State](p.Name), humanReadable(p.Info.Size()), p.Info.ModTime().Format(TimeFormat))
		}
		w.Flush()
	} else {

		var projectsString string
		for _, p := range projects {
			projectsString = fmt.Sprintf("%s\t%s", projectsString, colorMap[p.State](p.Name))
		}

		w := columnswriter.New(os.Stdout, '\t', 0, 2)
		fmt.Fprint(w, projectsString)
		w.Flush()
	}
}

func gls(project *Project /*dirName string*/, result chan *Project) {
	defer wg.Done()

	// First chek, is the directory under (git) version control
	if ok, _ := exists(filepath.Join(project.Name, ".git")); !ok {
		if !onlyDirty {
			project.State = "no_version_control"
			result <- project
		}
		return
	}

	gitDir := fmt.Sprintf("--git-dir=%s", filepath.Join(project.Name, ".git"))
	gitTree := fmt.Sprintf("--work-tree=%s", project.Name)
	output, err := exec.Command("git", gitDir, gitTree, "status").Output() //, gitDir, gitTree, "status")
	if err != nil {
		panic(err)
	}
	// Are there uncommitted changes is the directory (dirty)
	if !cleanGitRegex.MatchString(strings.TrimSpace(string(output))) {
		project.State = "dirty"
		result <- project
		return
	} else if onlyDirty {
		return
	}

	// Check if the repo has a remote
	output, err = exec.Command("git", gitDir, gitTree, "remote", "-v").Output()
	if err != nil {
		panic(err)
	}
	if len(output) == 0 {
		project.State = "no_remote"
		result <- project
		return
	}

	// Fetch latest changes from remote
	output, err = exec.Command("git", gitDir, gitTree, "fetch").Output()
	if err != nil {
		project.State = "fetch_failed"
		result <- project
		return
	}
	outputStr := strings.TrimSpace(string(output))
	if fetchErrors.MatchString(outputStr) {
		project.State = "fetch_failed"
		result <- project
		return
	}

	output, err = exec.Command("git", gitDir, gitTree, "status").Output()
	if err != nil {
		panic(err)
	}
	outputStr = strings.TrimSpace(string(output))

	// Is branch ahead of behind of remote
	if branchAhead.MatchString(outputStr) {
		project.State = "branch_ahead"
		result <- project
		return
	} else if branchBehind.MatchString(outputStr) {
		project.State = "branch_behind"
		result <- project
		return
	}

	project.State = "ok"
	result <- project
}

func exists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

func humanReadable(filesize int64) string {
	fs := float64(filesize)
	for _, x := range []string{"b", "kb", "mb", "gb", "tb"} {
		if fs < 1024 {
			return fmt.Sprintf("%3.1f % s", fs, x)
		}
		fs /= 1024
	}
	return ""
}
<|endoftext|>


<commit_before>// hcl is a package for decoding HCL into usable Go structures.
//
// hcl input can come in either pure HCL format or JSON format.
// It can be parsed into an AST, and then decoded into a structure,
// or it can be decoded directly from a string into a structure.
//
// If you choose to parse HCL into a raw AST, the benefit is that you
// can write custom visitor implementations to implement custom
// semantic checks. By default, HCL does not perform any semantic
// checks.
package hcl
<commit_msg>Use Go convention of docs with "Package" prefix<commit_after>// Package hcl decodes HCL into usable Go structures.
//
// hcl input can come in either pure HCL format or JSON format.
// It can be parsed into an AST, and then decoded into a structure,
// or it can be decoded directly from a string into a structure.
//
// If you choose to parse HCL into a raw AST, the benefit is that you
// can write custom visitor implementations to implement custom
// semantic checks. By default, HCL does not perform any semantic
// checks.
package hcl
<|endoftext|>


<commit_before>package githubutils

import (
	"context"
	"fmt"
	"strings"
	"sync"

	"github.com/haya14busa/go-actions-toolkit/core"
	"github.com/reviewdog/reviewdog"
)

var _ reviewdog.CommentService = &GitHubActionLogWriter{}

// GitHubActionLogWriter reports results via logging command to create
// annotations.
// https://help.github.com/en/actions/automating-your-workflow-with-github-actions/development-tools-for-github-actions#example-5
type GitHubActionLogWriter struct {
	level     string
	reportNum int
}

// NewGitHubActionLogWriter returns new GitHubActionLogWriter.
func NewGitHubActionLogWriter(level string) *GitHubActionLogWriter {
	return &GitHubActionLogWriter{level: level}
}

func (lw *GitHubActionLogWriter) Post(_ context.Context, c *reviewdog.Comment) error {
	lw.reportNum++
	if lw.reportNum == 10 {
		WarnTooManyAnnotationOnce()
	}
	ReportAsGitHubActionsLog(c.ToolName, lw.level, c.CheckResult)
	return nil
}

// Flush checks overall error at last.
func (lw *GitHubActionLogWriter) Flush(ctx context.Context) error {
	if lw.reportNum > 9 {
		return fmt.Errorf("GitHubActionLogWriter: reported too many annotation (N=%d)", lw.reportNum)
	}
	return nil
}

// ReportAsGitHubActionsLog reports results via logging command to create
// annotations.
// https://help.github.com/en/actions/automating-your-workflow-with-github-actions/development-tools-for-github-actions#example-5
func ReportAsGitHubActionsLog(toolName, level string, c *reviewdog.CheckResult) {
	mes := fmt.Sprintf("[%s] reported by reviewdog 🐶\n%s\n\nRaw Output:\n%s",
		toolName, c.Message, strings.Join(c.Lines, "\n"))
	opt := &core.LogOption{
		File: c.Path,
		Line: c.Lnum,
		Col:  c.Col,
	}
	switch level {
	// no info command with location data.
	case "warning", "info":
		core.Warning(mes, opt)
	case "error", "":
		core.Error(mes, opt)
	default:
		core.Error(fmt.Sprintf("Unknown level: %s", level), nil)
		core.Error(mes, opt)
	}
}

func WarnTooManyAnnotationOnce() {
	warnTooManyAnnotationOnce.Do(warnTooManyAnnotation)
}

var warnTooManyAnnotationOnce sync.Once

func warnTooManyAnnotation() {
	core.Error(`reviewdog: Too many results (annotations) in diff.
You may miss some annotations due to GitHub limitation for annotation created by logging command.

Limitation:
- 10 warning annotations and 10 error annotations per step
- 50 annotations per job (sum of annotations from all the steps)
- 50 annotations per run (separate from the job annotations, these annotations aren't created by users)

Source: https://github.community/t5/GitHub-Actions/Maximum-number-of-annotations-that-can-be-created-using-GitHub/m-p/39085`, nil)
}
<commit_msg>lint fix<commit_after>package githubutils

import (
	"context"
	"fmt"
	"strings"
	"sync"

	"github.com/haya14busa/go-actions-toolkit/core"
	"github.com/reviewdog/reviewdog"
)

var _ reviewdog.CommentService = &GitHubActionLogWriter{}

// GitHubActionLogWriter reports results via logging command to create
// annotations.
// https://help.github.com/en/actions/automating-your-workflow-with-github-actions/development-tools-for-github-actions#example-5
type GitHubActionLogWriter struct {
	level     string
	reportNum int
}

// NewGitHubActionLogWriter returns new GitHubActionLogWriter.
func NewGitHubActionLogWriter(level string) *GitHubActionLogWriter {
	return &GitHubActionLogWriter{level: level}
}

func (lw *GitHubActionLogWriter) Post(_ context.Context, c *reviewdog.Comment) error {
	lw.reportNum++
	if lw.reportNum == 10 {
		WarnTooManyAnnotationOnce()
	}
	ReportAsGitHubActionsLog(c.ToolName, lw.level, c.CheckResult)
	return nil
}

// Flush checks overall error at last.
func (lw *GitHubActionLogWriter) Flush(_ context.Context) error {
	if lw.reportNum > 9 {
		return fmt.Errorf("GitHubActionLogWriter: reported too many annotation (N=%d)", lw.reportNum)
	}
	return nil
}

// ReportAsGitHubActionsLog reports results via logging command to create
// annotations.
// https://help.github.com/en/actions/automating-your-workflow-with-github-actions/development-tools-for-github-actions#example-5
func ReportAsGitHubActionsLog(toolName, level string, c *reviewdog.CheckResult) {
	mes := fmt.Sprintf("[%s] reported by reviewdog 🐶\n%s\n\nRaw Output:\n%s",
		toolName, c.Message, strings.Join(c.Lines, "\n"))
	opt := &core.LogOption{
		File: c.Path,
		Line: c.Lnum,
		Col:  c.Col,
	}

	switch level {
	// no info command with location data.
	case "warning", "info":
		core.Warning(mes, opt)
	case "error", "":
		core.Error(mes, opt)
	default:
		core.Error(fmt.Sprintf("Unknown level: %s", level), nil)
		core.Error(mes, opt)
	}
}

func WarnTooManyAnnotationOnce() {
	warnTooManyAnnotationOnce.Do(warnTooManyAnnotation)
}

var warnTooManyAnnotationOnce sync.Once

func warnTooManyAnnotation() {
	core.Error(`reviewdog: Too many results (annotations) in diff.
You may miss some annotations due to GitHub limitation for annotation created by logging command.

Limitation:
- 10 warning annotations and 10 error annotations per step
- 50 annotations per job (sum of annotations from all the steps)
- 50 annotations per run (separate from the job annotations, these annotations aren't created by users)

Source: https://github.community/t5/GitHub-Actions/Maximum-number-of-annotations-that-can-be-created-using-GitHub/m-p/39085`, nil)
}
<|endoftext|>


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

package services

import (
	"crypto/subtle"

	"github.com/golang/protobuf/ptypes/empty"

	ds "go.chromium.org/gae/service/datastore"

	"go.chromium.org/luci/common/clock"
	log "go.chromium.org/luci/common/logging"
	"go.chromium.org/luci/common/proto/google"
	"go.chromium.org/luci/grpc/grpcutil"
	"go.chromium.org/luci/logdog/api/config/svcconfig"
	"go.chromium.org/luci/logdog/api/endpoints/coordinator/services/v1"
	"go.chromium.org/luci/logdog/appengine/coordinator"
	"go.chromium.org/luci/logdog/appengine/coordinator/config"
	"go.chromium.org/luci/logdog/appengine/coordinator/endpoints"
	"go.chromium.org/luci/logdog/appengine/coordinator/mutations"
	"go.chromium.org/luci/logdog/appengine/coordinator/tasks"
	"go.chromium.org/luci/tumble"

	"golang.org/x/net/context"
	"google.golang.org/grpc/codes"
)

// TerminateStream is an idempotent stream state terminate operation.
func (s *server) TerminateStream(c context.Context, req *logdog.TerminateStreamRequest) (*empty.Empty, error) {
	log.Fields{
		"project":       req.Project,
		"id":            req.Id,
		"terminalIndex": req.TerminalIndex,
	}.Infof(c, "Request to terminate log stream.")

	if req.TerminalIndex < 0 {
		return nil, grpcutil.Errf(codes.InvalidArgument, "Negative terminal index.")
	}

	id := coordinator.HashID(req.Id)
	if err := id.Normalize(); err != nil {
		return nil, grpcutil.Errf(codes.InvalidArgument, "Invalid ID (%s): %s", id, err)
	}

	// Load our service and project configs.
	svc := coordinator.GetServices(c)
	cfg, err := svc.Config(c)
	if err != nil {
		log.WithError(err).Errorf(c, "Failed to load configuration.")
		return nil, grpcutil.Internal
	}

	pcfg, err := coordinator.CurrentProjectConfig(c)
	if err != nil {
		log.WithError(err).Errorf(c, "Failed to load current project configuration.")
		return nil, grpcutil.Internal
	}

	// Initialize our archival parameters.
	params := standardArchivalParams(cfg, pcfg)

	// Initialize our log stream state.
	lst := coordinator.NewLogStreamState(c, id)

	// Transactionally validate and update the terminal index.
	err = ds.RunInTransaction(c, func(c context.Context) error {
		if err := ds.Get(c, lst); err != nil {
			if err == ds.ErrNoSuchEntity {
				log.Debugf(c, "Log stream state not found.")
				return grpcutil.Errf(codes.NotFound, "Log stream %q is not registered", id)
			}

			log.WithError(err).Errorf(c, "Failed to load LogEntry.")
			return grpcutil.Internal
		}

		switch {
		case subtle.ConstantTimeCompare(lst.Secret, req.Secret) != 1:
			log.Errorf(c, "Secrets do not match.")
			return grpcutil.Errf(codes.InvalidArgument, "Request secret doesn't match the stream secret.")

		case lst.Terminated():
			// Succeed if this is non-conflicting (idempotent).
			if lst.TerminalIndex == req.TerminalIndex {
				log.Fields{
					"terminalIndex": lst.TerminalIndex,
				}.Infof(c, "Log stream is already terminated.")
				return nil
			}

			log.Fields{
				"terminalIndex": lst.TerminalIndex,
			}.Warningf(c, "Log stream is already incompatibly terminated.")
			return grpcutil.Errf(codes.FailedPrecondition, "Log stream is incompatibly terminated.")

		default:
			// Everything looks good, let's proceed...
			now := clock.Now(c).UTC()
			lst.Updated = now
			lst.TerminalIndex = req.TerminalIndex
			lst.TerminatedTime = now

			if err := ds.Put(c, lst); err != nil {
				log.Fields{
					log.ErrorKey: err,
				}.Errorf(c, "Failed to Put() LogStream.")
				return grpcutil.Internal
			}

			// Replace the pessimistic archive expiration task scheduled in
			// RegisterStream with an optimistic archival task.
			if err := tasks.CreateArchivalTask(c, id, logdog.ArchiveDispatchTask_TERMINATED,
				params.SettleDelay, params); err != nil {

				log.WithError(err).Errorf(c, "Failed to create terminated archival task.")
				return grpcutil.Internal
			}

			if err := tasks.DeleteArchiveStreamExpiredTask(c, id); err != nil {
				// If we can't delete this task, it will just run, notice that the
				// stream is archived, and quit. No big deal.
				log.WithError(err).Warningf(c, "(Non-fatal) Failed to delete expired archival task.")
			}

			// In case the stream was *registered* with Tumble, but is now being
			// processed with task queue code, clear the Tumble archival mutation.
			//
			// TODO(dnj): Remove this once Tumble is drained.
			archiveMutation := mutations.CreateArchiveTask{ID: id}
			if err := tumble.CancelNamedMutations(c, archiveMutation.Root(c), archiveMutation.TaskName(c)); err != nil {
				log.WithError(err).Warningf(c, "(Non-fatal) Failed to cancel archive mutation.")
			}

			log.Fields{
				"terminalIndex":  lst.TerminalIndex,
				"settleDelay":    params.SettleDelay,
				"completePeriod": params.CompletePeriod,
			}.Debugf(c, "Terminal index was set, and archival task was scheduled.")
			return nil
		}
	}, nil)
	if err != nil {
		log.Fields{
			log.ErrorKey: err,
		}.Errorf(c, "Failed to update LogStream.")
		return nil, err
	}

	return &empty.Empty{}, nil
}

func standardArchivalParams(cfg *config.Config, pcfg *svcconfig.ProjectConfig) *coordinator.ArchivalParams {
	return &coordinator.ArchivalParams{
		SettleDelay:    google.DurationFromProto(cfg.Coordinator.ArchiveSettleDelay),
		CompletePeriod: endpoints.MinDuration(cfg.Coordinator.ArchiveDelayMax, pcfg.MaxStreamAge),
	}
}
<commit_msg>[logdog] Fix task queue deletion.<commit_after>// Copyright 2015 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package services

import (
	"crypto/subtle"

	"github.com/golang/protobuf/ptypes/empty"

	ds "go.chromium.org/gae/service/datastore"

	"go.chromium.org/luci/common/clock"
	log "go.chromium.org/luci/common/logging"
	"go.chromium.org/luci/common/proto/google"
	"go.chromium.org/luci/grpc/grpcutil"
	"go.chromium.org/luci/logdog/api/config/svcconfig"
	"go.chromium.org/luci/logdog/api/endpoints/coordinator/services/v1"
	"go.chromium.org/luci/logdog/appengine/coordinator"
	"go.chromium.org/luci/logdog/appengine/coordinator/config"
	"go.chromium.org/luci/logdog/appengine/coordinator/endpoints"
	"go.chromium.org/luci/logdog/appengine/coordinator/mutations"
	"go.chromium.org/luci/logdog/appengine/coordinator/tasks"
	"go.chromium.org/luci/tumble"

	"golang.org/x/net/context"
	"google.golang.org/grpc/codes"
)

// TerminateStream is an idempotent stream state terminate operation.
func (s *server) TerminateStream(c context.Context, req *logdog.TerminateStreamRequest) (*empty.Empty, error) {
	log.Fields{
		"project":       req.Project,
		"id":            req.Id,
		"terminalIndex": req.TerminalIndex,
	}.Infof(c, "Request to terminate log stream.")

	if req.TerminalIndex < 0 {
		return nil, grpcutil.Errf(codes.InvalidArgument, "Negative terminal index.")
	}

	id := coordinator.HashID(req.Id)
	if err := id.Normalize(); err != nil {
		return nil, grpcutil.Errf(codes.InvalidArgument, "Invalid ID (%s): %s", id, err)
	}

	// Load our service and project configs.
	svc := coordinator.GetServices(c)
	cfg, err := svc.Config(c)
	if err != nil {
		log.WithError(err).Errorf(c, "Failed to load configuration.")
		return nil, grpcutil.Internal
	}

	pcfg, err := coordinator.CurrentProjectConfig(c)
	if err != nil {
		log.WithError(err).Errorf(c, "Failed to load current project configuration.")
		return nil, grpcutil.Internal
	}

	// Initialize our archival parameters.
	params := standardArchivalParams(cfg, pcfg)

	// Initialize our log stream state.
	lst := coordinator.NewLogStreamState(c, id)

	// Transactionally validate and update the terminal index.
	err = ds.RunInTransaction(c, func(c context.Context) error {
		if err := ds.Get(c, lst); err != nil {
			if err == ds.ErrNoSuchEntity {
				log.Debugf(c, "Log stream state not found.")
				return grpcutil.Errf(codes.NotFound, "Log stream %q is not registered", id)
			}

			log.WithError(err).Errorf(c, "Failed to load LogEntry.")
			return grpcutil.Internal
		}

		switch {
		case subtle.ConstantTimeCompare(lst.Secret, req.Secret) != 1:
			log.Errorf(c, "Secrets do not match.")
			return grpcutil.Errf(codes.InvalidArgument, "Request secret doesn't match the stream secret.")

		case lst.Terminated():
			// Succeed if this is non-conflicting (idempotent).
			if lst.TerminalIndex == req.TerminalIndex {
				log.Fields{
					"terminalIndex": lst.TerminalIndex,
				}.Infof(c, "Log stream is already terminated.")
				return nil
			}

			log.Fields{
				"terminalIndex": lst.TerminalIndex,
			}.Warningf(c, "Log stream is already incompatibly terminated.")
			return grpcutil.Errf(codes.FailedPrecondition, "Log stream is incompatibly terminated.")

		default:
			// Everything looks good, let's proceed...
			now := clock.Now(c).UTC()
			lst.Updated = now
			lst.TerminalIndex = req.TerminalIndex
			lst.TerminatedTime = now

			if err := ds.Put(c, lst); err != nil {
				log.Fields{
					log.ErrorKey: err,
				}.Errorf(c, "Failed to Put() LogStream.")
				return grpcutil.Internal
			}

			// Replace the pessimistic archive expiration task scheduled in
			// RegisterStream with an optimistic archival task.
			if err := tasks.CreateArchivalTask(c, id, logdog.ArchiveDispatchTask_TERMINATED,
				params.SettleDelay, params); err != nil {

				log.WithError(err).Errorf(c, "Failed to create terminated archival task.")
				return grpcutil.Internal
			}

			// In case the stream was *registered* with Tumble, but is now being
			// processed with task queue code, clear the Tumble archival mutation.
			//
			// TODO(dnj): Remove this once Tumble is drained.
			archiveMutation := mutations.CreateArchiveTask{ID: id}
			if err := tumble.CancelNamedMutations(c, archiveMutation.Root(c), archiveMutation.TaskName(c)); err != nil {
				log.WithError(err).Warningf(c, "(Non-fatal) Failed to cancel archive mutation.")
			}

			log.Fields{
				"terminalIndex":  lst.TerminalIndex,
				"settleDelay":    params.SettleDelay,
				"completePeriod": params.CompletePeriod,
			}.Debugf(c, "Terminal index was set, and archival task was scheduled.")
			return nil
		}
	}, nil)
	if err != nil {
		log.Fields{
			log.ErrorKey: err,
		}.Errorf(c, "Failed to update LogStream.")
		return nil, err
	}

	// Try and delete the archive expired task. We must do this outside of a
	// transaction.
	if err := tasks.DeleteArchiveStreamExpiredTask(c, id); err != nil {
		// If we can't delete this task, it will just run, notice that the
		// stream is archived, and quit. No big deal.
		log.WithError(err).Warningf(c, "(Non-fatal) Failed to delete expired archival task.")
	}

	return &empty.Empty{}, nil
}

func standardArchivalParams(cfg *config.Config, pcfg *svcconfig.ProjectConfig) *coordinator.ArchivalParams {
	return &coordinator.ArchivalParams{
		SettleDelay:    google.DurationFromProto(cfg.Coordinator.ArchiveSettleDelay),
		CompletePeriod: endpoints.MinDuration(cfg.Coordinator.ArchiveDelayMax, pcfg.MaxStreamAge),
	}
}
<|endoftext|>


<commit_before>// Package ini provides functions for parsing INI configuration files.
package ini

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"regexp"
	"strings"
)

var (
	sectionRegex = regexp.MustCompile(`^\[(.*)\]$`)
	assignRegex  = regexp.MustCompile(`([^=]+)=(.*)$`)
)

// ErrSyntax is returned when there is a syntax error in an INI file.
type ErrSyntax struct {
	Line   int
	Source string
}

func (e ErrSyntax) Error() string {
	return fmt.Sprintf("invalid INI syntax on line %d: %s", e.Line, e.Source)
}

// A File represents a parsed INI file.
type File map[string]Section

// A Section represents a single section of an INI file.
type Section map[string]string

// Returns a named Section. A Section will be created if one does not already exist for the given name.
func (f File) Section(name string) Section {
	section := f[name]
	if section == nil {
		section = make(Section)
		f[name] = section
	}
	return section
}

// Looks up a value for a key in a section and returns that value, along with a boolean result similar to a map lookup.
func (f File) Get(section, key string) (value string, ok bool) {
	if s := f[section]; s != nil {
		value, ok = s[key]
	}
	return
}

// Loads INI data from a reader and stores the data in the File.
func (f File) Load(in io.Reader) (err error) {
	bufin, ok := in.(*bufio.Reader)
	if !ok {
		bufin = bufio.NewReader(in)
	}
	return parseFile(bufin, f)
}

// Loads INI data from a named file and stores the data in the File.
func (f File) LoadFile(file string) (err error) {
	in, err := os.Open(file)
	if err != nil {
		return
	}
	return f.Load(in)
}

func parseFile(in *bufio.Reader, file File) (err error) {
	section := file.Section("")
	lineNum := 0
	for {
		var line string
		if line, err = in.ReadString('\n'); err != nil {
			if err == io.EOF {
				break
			}
			return
		}
		lineNum++
		line = strings.TrimSpace(line)
		if len(line) == 0 {
			// Skip blank lines
			continue
		}
		if line[0] == ';' || line[0] == '#' {
			// Skip comments
			continue
		}

		if groups := assignRegex.FindStringSubmatch(line); groups != nil {
			key, val := groups[1], groups[2]
			key, val = strings.TrimSpace(key), strings.TrimSpace(val)
			section[key] = val
		} else if groups := sectionRegex.FindStringSubmatch(line); groups != nil {
			name := strings.TrimSpace(groups[1])
			section = file.Section(name)
		} else {
			return ErrSyntax{lineNum, line}
		}

	}
	return nil
}

// Loads and returns a File from a reader.
func Load(in io.Reader) (File, error) {
	file := make(File)
	err := file.Load(in)
	return file, err
}

// Loads and returns an ini File from a file on disk.
func LoadFile(filename string) (File, error) {
	file := make(File)
	err := file.LoadFile(filename)
	return file, err
}
<commit_msg>Documented ErrSyntax.Source<commit_after>// Package ini provides functions for parsing INI configuration files.
package ini

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"regexp"
	"strings"
)

var (
	sectionRegex = regexp.MustCompile(`^\[(.*)\]$`)
	assignRegex  = regexp.MustCompile(`([^=]+)=(.*)$`)
)

// ErrSyntax is returned when there is a syntax error in an INI file.
type ErrSyntax struct {
	Line   int
	Source string // The contents of the erroneous line, without leading and trailing whitespace
}

func (e ErrSyntax) Error() string {
	return fmt.Sprintf("invalid INI syntax on line %d: %s", e.Line, e.Source)
}

// A File represents a parsed INI file.
type File map[string]Section

// A Section represents a single section of an INI file.
type Section map[string]string

// Returns a named Section. A Section will be created if one does not already exist for the given name.
func (f File) Section(name string) Section {
	section := f[name]
	if section == nil {
		section = make(Section)
		f[name] = section
	}
	return section
}

// Looks up a value for a key in a section and returns that value, along with a boolean result similar to a map lookup.
func (f File) Get(section, key string) (value string, ok bool) {
	if s := f[section]; s != nil {
		value, ok = s[key]
	}
	return
}

// Loads INI data from a reader and stores the data in the File.
func (f File) Load(in io.Reader) (err error) {
	bufin, ok := in.(*bufio.Reader)
	if !ok {
		bufin = bufio.NewReader(in)
	}
	return parseFile(bufin, f)
}

// Loads INI data from a named file and stores the data in the File.
func (f File) LoadFile(file string) (err error) {
	in, err := os.Open(file)
	if err != nil {
		return
	}
	return f.Load(in)
}

func parseFile(in *bufio.Reader, file File) (err error) {
	section := file.Section("")
	lineNum := 0
	for {
		var line string
		if line, err = in.ReadString('\n'); err != nil {
			if err == io.EOF {
				break
			}
			return
		}
		lineNum++
		line = strings.TrimSpace(line)
		if len(line) == 0 {
			// Skip blank lines
			continue
		}
		if line[0] == ';' || line[0] == '#' {
			// Skip comments
			continue
		}

		if groups := assignRegex.FindStringSubmatch(line); groups != nil {
			key, val := groups[1], groups[2]
			key, val = strings.TrimSpace(key), strings.TrimSpace(val)
			section[key] = val
		} else if groups := sectionRegex.FindStringSubmatch(line); groups != nil {
			name := strings.TrimSpace(groups[1])
			section = file.Section(name)
		} else {
			return ErrSyntax{lineNum, line}
		}

	}
	return nil
}

// Loads and returns a File from a reader.
func Load(in io.Reader) (File, error) {
	file := make(File)
	err := file.Load(in)
	return file, err
}

// Loads and returns an ini File from a file on disk.
func LoadFile(filename string) (File, error) {
	file := make(File)
	err := file.LoadFile(filename)
	return file, err
}
<|endoftext|>


<commit_before>// Copyright (c) 2016-2020 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package main

import (
	"bufio"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"os"
)

// Messages sent over a pipe are encoded using a simple binary message format:
//
//   - Protocol version (1 byte, currently 1)
//   - Message type length (1 byte)
//   - Message type string (encoded as UTF8, no longer than 255 bytes)
//   - Message payload length (4 bytes, little endian)
//   - Message payload bytes (no longer than 2^32 - 1 bytes)
type pipeMessage interface {
	Type() string
	PayloadSize() uint32
	WritePayload(w io.Writer) error
}

var outgoingPipeMessages = make(chan pipeMessage)

// serviceControlPipeRx reads from the file descriptor fd of a read end pipe.
// This is intended to be used as a simple control mechanism for parent
// processes to communicate with and manage the lifetime of a dcrd child
// process using a unidirectional pipe (on Windows, this is an anonymous pipe,
// not a named pipe).
//
// When the pipe is closed or any other errors occur reading the control
// message, shutdown begins.  This prevents dcrd from continuing to run
// unsupervised after the parent process closes unexpectedly.
//
// No control messages are currently defined and the only use for the pipe is to
// start clean shutdown when the pipe is closed.  Control messages that follow
// the pipe message format can be added later as needed.
func serviceControlPipeRx(fd uintptr) {
	pipe := os.NewFile(fd, fmt.Sprintf("|%v", fd))
	r := bufio.NewReader(pipe)
	for {
		_, err := r.Discard(1024)
		if errors.Is(err, io.EOF) {
			break
		}
		if err != nil {
			dcrdLog.Errorf("Failed to read from pipe: %v", err)
			break
		}
	}

	select {
	case shutdownRequestChannel <- struct{}{}:
	default:
	}
}

// serviceControlPipeTx sends pipe messages to the file descriptor fd of a write
// end pipe.  This is intended to be a simple response and notification system
// for a child dcrd process to communicate with a parent process without the
// need to go through the RPC server.
//
// See the comment on the pipeMessage interface for the binary encoding of a
// pipe message.
func serviceControlPipeTx(fd uintptr) {
	defer drainOutgoingPipeMessages()

	pipe := os.NewFile(fd, fmt.Sprintf("|%v", fd))
	w := bufio.NewWriter(pipe)
	headerBuffer := make([]byte, 0, 1+1+255+4) // capped to max header size
	var err error
	for m := range outgoingPipeMessages {
		const protocolVersion byte = 1

		mtype := m.Type()
		psize := m.PayloadSize()

		headerBuffer = append(headerBuffer, protocolVersion)
		headerBuffer = append(headerBuffer, byte(len(mtype)))
		headerBuffer = append(headerBuffer, mtype...)
		buf := make([]byte, 4)
		binary.LittleEndian.PutUint32(buf, psize)
		headerBuffer = append(headerBuffer, buf...)

		_, err = w.Write(headerBuffer)
		if err != nil {
			break
		}

		err = m.WritePayload(w)
		if err != nil {
			break
		}

		err = w.Flush()
		if err != nil {
			break
		}

		headerBuffer = headerBuffer[:0]
	}

	dcrdLog.Errorf("Failed to write to pipe: %v", err)
}

func drainOutgoingPipeMessages() {
	for range outgoingPipeMessages {
	}
}

// The lifetimeEvent describes a startup or shutdown event.  The message type
// string is "lifetimeevent".
//
// The payload size is always 2 bytes long.  The first byte describes whether a
// service or event is about to run or whether startup has completed.  The
// second byte, when applicable, describes which event or service is about to
// start or stop.
//
//   0 <event id>:  The startup event is about to run
//   1 <ignored>:   All startup tasks have completed
//   2 <event id>:  The shutdown event is about to run
//
// Event IDs can take on the following values:
//
//   0: Database opening/closing
//   1: Ticket database opening/closing
//   2: Peer-to-peer server starting/stopping
//
// Note that not all subsystems are started/stopped or events run during the
// program's lifetime depending on what features are enabled through the config.
//
// As an example, the following messages may be sent during a typical execution:
//
//   0 0: The database is being opened
//   0 1: The ticket DB is being opened
//   0 2: The P2P server is starting
//   1 0: All startup tasks have completed
//   2 2: The P2P server is stopping
//   2 1: The ticket DB is being closed and written to disk
//   2 0: The database is being closed
type lifetimeEvent struct {
	event  lifetimeEventID
	action lifetimeAction
}

var _ pipeMessage = (*lifetimeEvent)(nil)

type lifetimeEventID byte

const (
	startupEvent lifetimeEventID = iota
	startupComplete
	shutdownEvent
)

type lifetimeAction byte

const (
	lifetimeEventDBOpen lifetimeAction = iota
	lifetimeEventP2PServer
)

func (*lifetimeEvent) Type() string          { return "lifetimeevent" }
func (e *lifetimeEvent) PayloadSize() uint32 { return 2 }
func (e *lifetimeEvent) WritePayload(w io.Writer) error {
	_, err := w.Write([]byte{byte(e.event), byte(e.action)})
	return err
}

type lifetimeEventServer chan<- pipeMessage

func newLifetimeEventServer(outChan chan<- pipeMessage) lifetimeEventServer {
	return lifetimeEventServer(outChan)
}

func (s lifetimeEventServer) notifyStartupEvent(action lifetimeAction) {
	if s == nil {
		return
	}
	s <- &lifetimeEvent{
		event:  startupEvent,
		action: action,
	}
}

func (s lifetimeEventServer) notifyStartupComplete() {
	if s == nil {
		return
	}
	s <- &lifetimeEvent{
		event:  startupComplete,
		action: 0,
	}
}

func (s lifetimeEventServer) notifyShutdownEvent(action lifetimeAction) {
	if s == nil {
		return
	}
	s <- &lifetimeEvent{
		event:  shutdownEvent,
		action: action,
	}
}
<commit_msg>ipc: Fix lifetimeEvent comments.<commit_after>// Copyright (c) 2016-2021 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package main

import (
	"bufio"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"os"
)

// Messages sent over a pipe are encoded using a simple binary message format:
//
//   - Protocol version (1 byte, currently 1)
//   - Message type length (1 byte)
//   - Message type string (encoded as UTF8, no longer than 255 bytes)
//   - Message payload length (4 bytes, little endian)
//   - Message payload bytes (no longer than 2^32 - 1 bytes)
type pipeMessage interface {
	Type() string
	PayloadSize() uint32
	WritePayload(w io.Writer) error
}

var outgoingPipeMessages = make(chan pipeMessage)

// serviceControlPipeRx reads from the file descriptor fd of a read end pipe.
// This is intended to be used as a simple control mechanism for parent
// processes to communicate with and manage the lifetime of a dcrd child
// process using a unidirectional pipe (on Windows, this is an anonymous pipe,
// not a named pipe).
//
// When the pipe is closed or any other errors occur reading the control
// message, shutdown begins.  This prevents dcrd from continuing to run
// unsupervised after the parent process closes unexpectedly.
//
// No control messages are currently defined and the only use for the pipe is to
// start clean shutdown when the pipe is closed.  Control messages that follow
// the pipe message format can be added later as needed.
func serviceControlPipeRx(fd uintptr) {
	pipe := os.NewFile(fd, fmt.Sprintf("|%v", fd))
	r := bufio.NewReader(pipe)
	for {
		_, err := r.Discard(1024)
		if errors.Is(err, io.EOF) {
			break
		}
		if err != nil {
			dcrdLog.Errorf("Failed to read from pipe: %v", err)
			break
		}
	}

	select {
	case shutdownRequestChannel <- struct{}{}:
	default:
	}
}

// serviceControlPipeTx sends pipe messages to the file descriptor fd of a write
// end pipe.  This is intended to be a simple response and notification system
// for a child dcrd process to communicate with a parent process without the
// need to go through the RPC server.
//
// See the comment on the pipeMessage interface for the binary encoding of a
// pipe message.
func serviceControlPipeTx(fd uintptr) {
	defer drainOutgoingPipeMessages()

	pipe := os.NewFile(fd, fmt.Sprintf("|%v", fd))
	w := bufio.NewWriter(pipe)
	headerBuffer := make([]byte, 0, 1+1+255+4) // capped to max header size
	var err error
	for m := range outgoingPipeMessages {
		const protocolVersion byte = 1

		mtype := m.Type()
		psize := m.PayloadSize()

		headerBuffer = append(headerBuffer, protocolVersion)
		headerBuffer = append(headerBuffer, byte(len(mtype)))
		headerBuffer = append(headerBuffer, mtype...)
		buf := make([]byte, 4)
		binary.LittleEndian.PutUint32(buf, psize)
		headerBuffer = append(headerBuffer, buf...)

		_, err = w.Write(headerBuffer)
		if err != nil {
			break
		}

		err = m.WritePayload(w)
		if err != nil {
			break
		}

		err = w.Flush()
		if err != nil {
			break
		}

		headerBuffer = headerBuffer[:0]
	}

	dcrdLog.Errorf("Failed to write to pipe: %v", err)
}

func drainOutgoingPipeMessages() {
	for range outgoingPipeMessages {
	}
}

// The lifetimeEvent describes a startup or shutdown event.  The message type
// string is "lifetimeevent".
//
// The payload size is always 2 bytes long.  The first byte describes whether a
// service or event is about to run or whether startup has completed.  The
// second byte, when applicable, describes which event or service is about to
// start or stop.
//
//   0 <event id>:  The startup event is about to run
//   1 <ignored>:   All startup tasks have completed
//   2 <event id>:  The shutdown event is about to run
//
// Event IDs can take on the following values:
//
//   0: Database opening/closing
//   1: Peer-to-peer server starting/stopping
//
// Note that not all subsystems are started/stopped or events run during the
// program's lifetime depending on what features are enabled through the config.
//
// As an example, the following messages may be sent during a typical execution:
//
//   0 0: The database is being opened
//   0 1: The P2P server is starting
//   1 0: All startup tasks have completed
//   2 1: The P2P server is stopping
//   2 0: The database is being closed
type lifetimeEvent struct {
	event  lifetimeEventID
	action lifetimeAction
}

var _ pipeMessage = (*lifetimeEvent)(nil)

type lifetimeEventID byte

const (
	startupEvent lifetimeEventID = iota
	startupComplete
	shutdownEvent
)

type lifetimeAction byte

const (
	lifetimeEventDBOpen lifetimeAction = iota
	lifetimeEventP2PServer
)

func (*lifetimeEvent) Type() string          { return "lifetimeevent" }
func (e *lifetimeEvent) PayloadSize() uint32 { return 2 }
func (e *lifetimeEvent) WritePayload(w io.Writer) error {
	_, err := w.Write([]byte{byte(e.event), byte(e.action)})
	return err
}

type lifetimeEventServer chan<- pipeMessage

func newLifetimeEventServer(outChan chan<- pipeMessage) lifetimeEventServer {
	return lifetimeEventServer(outChan)
}

func (s lifetimeEventServer) notifyStartupEvent(action lifetimeAction) {
	if s == nil {
		return
	}
	s <- &lifetimeEvent{
		event:  startupEvent,
		action: action,
	}
}

func (s lifetimeEventServer) notifyStartupComplete() {
	if s == nil {
		return
	}
	s <- &lifetimeEvent{
		event:  startupComplete,
		action: 0,
	}
}

func (s lifetimeEventServer) notifyShutdownEvent(action lifetimeAction) {
	if s == nil {
		return
	}
	s <- &lifetimeEvent{
		event:  shutdownEvent,
		action: action,
	}
}
<|endoftext|>


<commit_before>package redisq

import (
	"strconv"

	"github.com/garyburd/redigo/redis"
)

type Job interface {
	Do() error
}

type JobQ struct {
	Name      string
	inputQ    *Q
	consumers []*Q
	doneQ     *Q
	failQ     *Q
	newJob    func() Job
}

func NewJobQ(name string, consumerCount int, newJob func() Job, pool *redis.Pool) (*JobQ, error) {
	proxy := &JobQ{
		Name:      name,
		inputQ:    New(name+":job", pool),
		consumers: make([]*Q, consumerCount),
		doneQ:     New(name+":done", pool),
		failQ:     New(name+":done", pool),
		newJob:    newJob,
	}
	for i := range proxy.consumers {
		proxy.consumers[i] = New(name+":consumer:"+strconv.Itoa(i), pool)
	}
	// TODO: move working back to job queue
	if err := proxy.run(); err != nil {
		return nil, err
	}
	return proxy, nil
}

func (p *JobQ) Put(job Job) error {
	return p.inputQ.Put(job)
}

func (p *JobQ) run() error {
	for _, c := range p.consumers {
		go p.process(c)
	}
	return nil
}

func (p *JobQ) process(c *Q) {
	job := p.newJob()
	for {
		if err := p.inputQ.PopTo(c, &job); err != nil {
			continue
		}
		if err := job.Do(); err != nil {
			if err := c.PopTo(p.failQ, nil); err != nil {
			}
			continue
		}
		if err := c.PopTo(p.doneQ, nil); err != nil {
		}
	}
}
<commit_msg>add logging.<commit_after>package redisq

import (
	"strconv"

	"github.com/garyburd/redigo/redis"
)

type Job interface {
	Do() error
}

type JobQ struct {
	Name      string
	inputQ    *Q
	consumers []*Q
	doneQ     *Q
	failQ     *Q
	newJob    func() Job
	errCh     chan error
}

type JobQError struct {
	Msg string
	Job Job
	Err error
}

func (e *JobQError) Error() string {
	return e.Msg
}

func NewJobQ(name string, consumerCount int, newJob func() Job, pool *redis.Pool) (*JobQ, error) {
	q := &JobQ{
		Name:      name,
		inputQ:    New(name+":job", pool),
		consumers: make([]*Q, consumerCount),
		doneQ:     New(name+":done", pool),
		failQ:     New(name+":done", pool),
		newJob:    newJob,
		errCh:     make(chan error),
	}
	for i := range q.consumers {
		q.consumers[i] = New(name+":consumer:"+strconv.Itoa(i), pool)
	}
	// TODO: move working back to job queue
	return q, nil
}

func (p *JobQ) Put(job Job) error {
	return p.inputQ.Put(job)
}

func (p *JobQ) Consume() <-chan error {
	for _, c := range p.consumers {
		go p.process(c)
	}
	return p.errCh
}

func (p *JobQ) process(consumer *Q) {
	for {
		job := p.newJob()
		if err := p.inputQ.PopTo(consumer, &job); err != nil {
			p.sendErr("fail to get job from input queue", job, err)
			continue
		}
		if err := job.Do(); err != nil {
			p.sendErr("fail to do the job", job, err)
			if err := consumer.PopTo(p.failQ, nil); err != nil {
				p.sendErr("fail to put job into the fail queue", job, err)
			}
			continue
		}
		if err := consumer.PopTo(p.doneQ, nil); err != nil {
			p.sendErr("fail to pu job into the done queue", job, err)
		}
	}
}

func (p *JobQ) sendErr(msg string, job Job, err error) {
	p.errCh <- &JobQError{Msg: msg, Job: job, Err: err}
}
<|endoftext|>


<commit_before>// Copyright 2014 Matthew Endsley
// All rights reserved
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted providing that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
// IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package gojwe

import (
	"bytes"
	"compress/flate"
	"crypto"
	"crypto/aes"
	"crypto/cipher"
	"crypto/hmac"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha1"
	"crypto/sha256"
	"encoding/json"
	"errors"
	"fmt"
	"hash"
	"io"
	"io/ioutil"
	"strings"
)

// Verify and decrypt a draft-7 JWE object
func VerifyAndDecryptDraft7(jwe string, key crypto.PrivateKey) ([]byte, error) {
	parts := strings.Split(jwe, ".")
	if len(parts) != 5 {
		return nil, errors.New("Wrong number of parts")
	}

	// decode the JWE header
	var header struct {
		Alg string `json:"alg"`
		Enc string `json:"enc"`
		Zip string `json:"zip"`
	}
	data, err := safeDecode(parts[0])
	if err != nil {
		return nil, fmt.Errorf("Malformed header: %v", err)
	}
	err = json.Unmarshal(data, &header)
	if err != nil {
		return nil, fmt.Errorf("Failed to decode header: %v", err)
	}

	var encryptionKey []byte
	encryptionKeyData, err := safeDecode(parts[1])
	if err != nil {
		return nil, fmt.Errorf("Malformed encryption key: %v", err)
	}

	// decode the encryption key
	switch header.Alg {
	case "RSA-OAEP", "RSA-OAEP-256":
		rsaKey, ok := key.(*rsa.PrivateKey)
		if !ok {
			return nil, fmt.Errorf("Expected an RSA private key. Got %T", key)
		}

		var h hash.Hash
		if header.Alg == "RSA-OAEP" {
			h = sha1.New()
		} else if header.Alg == "RSA-OAEP-256" {
			h = sha256.New()
		} else {
			return nil, fmt.Errorf("Unknown RSA-OAEP keytype %s", header.Alg)
		}

		encryptionKey, err = rsa.DecryptOAEP(h, rand.Reader, rsaKey, encryptionKeyData, nil)
		if err != nil {
			return nil, fmt.Errorf("Failed to decrypt encryption key: %v", err)
		}

	default:
		return nil, fmt.Errorf("Unsupported ALG keytype %s", header.Alg)
	}

	// decode IV
	iv, err := safeDecode(parts[2])
	if err != nil {
		return nil, fmt.Errorf("Malformed IV: %v", err)
	}

	// decode cipher text
	cipherText, err := safeDecode(parts[3])
	if err != nil {
		return nil, fmt.Errorf("Malformed cipher text: %v", err)
	}

	// decode authtag
	authTag, err := safeDecode(parts[4])
	if err != nil {
		return nil, fmt.Errorf("Malformed authtag: %v", err)
	}

	// decrypt and verify cipher text
	var plainText []byte

	switch header.Enc {
	case "A128CBC+HS256":
		// derive keys
		encKey, macKey := concatKDF(encryptionKey, header.Enc, 128, 256)

		// verify authtag
		hm := hmac.New(sha256.New, macKey)
		io.WriteString(hm, parts[0])
		io.WriteString(hm, ".")
		io.WriteString(hm, parts[1])
		io.WriteString(hm, ".")
		io.WriteString(hm, parts[2])
		io.WriteString(hm, ".")
		io.WriteString(hm, parts[3])
		if !hmac.Equal(authTag, hm.Sum(nil)) {
			return nil, errors.New("Integrity check failed")
		}

		// decrpyt ciphertext (can be done in-place)
		block, err := aes.NewCipher(encKey)
		if err != nil {
			return nil, fmt.Errorf("Failed to create an AES block encryptor: %v", err)
		}

		c := cipher.NewCBCDecrypter(block, iv)
		c.CryptBlocks(cipherText, cipherText)
		plainText = cipherText

		// remove PCKS#7 padding
		padding := int(plainText[len(plainText)-1])
		plainText = plainText[:len(plainText)-padding]

	default:
		return nil, fmt.Errorf("Unsupported ENC keytype %s", header.Enc)
	}

	// need to deflate plain text?
	if header.Zip == "DEF" {
		plainText, err = ioutil.ReadAll(flate.NewReader(bytes.NewReader(plainText)))
		if err != nil {
			return nil, fmt.Errorf("Failed to inflate plain text: %v", err)
		}
	}

	return plainText, nil
}
<commit_msg>Add support for RSA1_5 key encryption<commit_after>// Copyright 2014 Matthew Endsley
// All rights reserved
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted providing that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
// IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package gojwe

import (
	"bytes"
	"compress/flate"
	"crypto"
	"crypto/aes"
	"crypto/cipher"
	"crypto/hmac"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha1"
	"crypto/sha256"
	"encoding/json"
	"errors"
	"fmt"
	"hash"
	"io"
	"io/ioutil"
	"strings"
)

// Verify and decrypt a draft-7 JWE object
func VerifyAndDecryptDraft7(jwe string, key crypto.PrivateKey) ([]byte, error) {
	parts := strings.Split(jwe, ".")
	if len(parts) != 5 {
		return nil, errors.New("Wrong number of parts")
	}

	// decode the JWE header
	var header struct {
		Alg string `json:"alg"`
		Enc string `json:"enc"`
		Zip string `json:"zip"`
	}
	data, err := safeDecode(parts[0])
	if err != nil {
		return nil, fmt.Errorf("Malformed header: %v", err)
	}
	err = json.Unmarshal(data, &header)
	if err != nil {
		return nil, fmt.Errorf("Failed to decode header: %v", err)
	}

	var encryptionKey []byte
	encryptionKeyData, err := safeDecode(parts[1])
	if err != nil {
		return nil, fmt.Errorf("Malformed encryption key: %v", err)
	}

	// decode the encryption key
	switch header.Alg {
	case "RSA-OAEP", "RSA-OAEP-256":
		rsaKey, ok := key.(*rsa.PrivateKey)
		if !ok {
			return nil, fmt.Errorf("Expected an RSA private key. Got %T", key)
		}

		var h hash.Hash
		if header.Alg == "RSA-OAEP" {
			h = sha1.New()
		} else if header.Alg == "RSA-OAEP-256" {
			h = sha256.New()
		} else {
			return nil, fmt.Errorf("Unknown RSA-OAEP keytype %s", header.Alg)
		}

		encryptionKey, err = rsa.DecryptOAEP(h, rand.Reader, rsaKey, encryptionKeyData, nil)
		if err != nil {
			return nil, fmt.Errorf("Failed to decrypt encryption key: %v", err)
		}

	case "RSA1_5":
		rsaKey, ok := key.(*rsa.PrivateKey)
		if !ok {
			return nil, fmt.Errorf("Expected RSA private key. Got %T", key)
		}

		encryptionKey, err = rsa.DecryptPKCS1v15(rand.Reader, rsaKey, encryptionKeyData)
		if err != nil {
			return nil, fmt.Errorf("Failed to decrypt encryption key: %v", err)
		}

	default:
		return nil, fmt.Errorf("Unsupported ALG keytype %s", header.Alg)
	}

	// decode IV
	iv, err := safeDecode(parts[2])
	if err != nil {
		return nil, fmt.Errorf("Malformed IV: %v", err)
	}

	// decode cipher text
	cipherText, err := safeDecode(parts[3])
	if err != nil {
		return nil, fmt.Errorf("Malformed cipher text: %v", err)
	}

	// decode authtag
	authTag, err := safeDecode(parts[4])
	if err != nil {
		return nil, fmt.Errorf("Malformed authtag: %v", err)
	}

	// decrypt and verify cipher text
	var plainText []byte

	switch header.Enc {
	case "A128CBC+HS256":
		// derive keys
		encKey, macKey := concatKDF(encryptionKey, header.Enc, 128, 256)

		// verify authtag
		hm := hmac.New(sha256.New, macKey)
		io.WriteString(hm, parts[0])
		io.WriteString(hm, ".")
		io.WriteString(hm, parts[1])
		io.WriteString(hm, ".")
		io.WriteString(hm, parts[2])
		io.WriteString(hm, ".")
		io.WriteString(hm, parts[3])
		if !hmac.Equal(authTag, hm.Sum(nil)) {
			return nil, errors.New("Integrity check failed")
		}

		// decrpyt ciphertext (can be done in-place)
		block, err := aes.NewCipher(encKey)
		if err != nil {
			return nil, fmt.Errorf("Failed to create an AES block encryptor: %v", err)
		}

		c := cipher.NewCBCDecrypter(block, iv)
		c.CryptBlocks(cipherText, cipherText)
		plainText = cipherText

		// remove PCKS#7 padding
		padding := int(plainText[len(plainText)-1])
		plainText = plainText[:len(plainText)-padding]

	default:
		return nil, fmt.Errorf("Unsupported ENC keytype %s", header.Enc)
	}

	// need to deflate plain text?
	if header.Zip == "DEF" {
		plainText, err = ioutil.ReadAll(flate.NewReader(bytes.NewReader(plainText)))
		if err != nil {
			return nil, fmt.Errorf("Failed to inflate plain text: %v", err)
		}
	}

	return plainText, nil
}
<|endoftext|>


<commit_before>//go:generate go run ./internal/generate -f -o kml22gx.gen.go -n gx: xsd/kml22gx.xsd
//go:generate go run ./internal/generate -f -o ogckml22.gen.go xsd/ogckml22.xsd

// Package kml provides convenience methods for creating and writing KML documents.
//
// See https://developers.google.com/kml/
//
// Goals
//
//   - Convenient API for creating both simple and complex KML documents.
//   - 1:1 mapping between functions and KML elements.
//
// Non-goals
//
//   - Protection against generating invalid documents.
//   - Concealment of KML complexity.
//   - Fine-grained control over generated XML.
package kml

import (
	"encoding/xml"
	"fmt"
	"image/color"
	"io"
	"strconv"
	"time"
)

// An Element represents an abstract KML element.
type Element interface {
	xml.Marshaler
	Write(io.Writer) error
	WriteIndent(io.Writer, string, string) error
}

// A SimpleElement is an Element with a single value.
type SimpleElement struct {
	xml.StartElement
	value string
}

// A CompoundElement is an Element with children.
type CompoundElement struct {
	xml.StartElement
	children []Element
}

// A SharedElement is an element with an id.
type SharedElement struct {
	CompoundElement
	id string
}

// MarshalXML marshals se to e. start is ignored.
func (se *SimpleElement) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
	return e.EncodeElement(xml.CharData(se.value), se.StartElement)
}

// Write writes an XML header and se to w.
func (se *SimpleElement) Write(w io.Writer) error {
	return write(w, "", "", se)
}

// WriteIndent writes an XML header and se to w.
func (se *SimpleElement) WriteIndent(w io.Writer, prefix, indent string) error {
	return write(w, prefix, indent, se)
}

// Add adds children to ce.
func (ce *CompoundElement) Add(children ...Element) *CompoundElement {
	ce.children = append(ce.children, children...)
	return ce
}

// MarshalXML marshals ce to e. start is ignored.
func (ce *CompoundElement) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
	if err := e.EncodeToken(ce.StartElement); err != nil {
		return err
	}
	for _, c := range ce.children {
		if err := e.EncodeElement(c, ce.StartElement); err != nil {
			return err
		}
	}
	return e.EncodeToken(ce.End())
}

// Write writes an XML header and ce to w.
func (ce *CompoundElement) Write(w io.Writer) error {
	return write(w, "", "", ce)
}

// WriteIndent writes an XML header and ce to w.
func (ce *CompoundElement) WriteIndent(w io.Writer, prefix, indent string) error {
	return write(w, prefix, indent, ce)
}

// ID returns se's id.
func (se *SharedElement) ID() string {
	return se.id
}

// URL returns se's URL.
func (se *SharedElement) URL() string {
	return "#" + se.ID()
}

func write(w io.Writer, prefix, indent string, m xml.Marshaler) error {
	if _, err := w.Write([]byte(xml.Header)); err != nil {
		return err
	}
	e := xml.NewEncoder(w)
	e.Indent(prefix, indent)
	return e.Encode(m)
}

func newSEBool(name string, value bool) *SimpleElement {
	var v string
	if value {
		v = "1"
	} else {
		v = "0"
	}
	return &SimpleElement{
		StartElement: xml.StartElement{Name: xml.Name{Local: name}},
		value:        v,
	}
}

func newSEColor(name string, value color.Color) *SimpleElement {
	r, g, b, a := value.RGBA()
	return &SimpleElement{
		StartElement: xml.StartElement{Name: xml.Name{Local: name}},
		value:        fmt.Sprintf("%02x%02x%02x%02x", a/256, b/256, g/256, r/256),
	}
}

func newSEElement(name string, value Element) *CompoundElement {
	return &CompoundElement{
		StartElement: xml.StartElement{
			Name: xml.Name{Local: name},
		},
		children: []Element{value},
	}
}

func newSEFloat(name string, value float64) *SimpleElement {
	return &SimpleElement{
		StartElement: xml.StartElement{Name: xml.Name{Local: name}},
		value:        strconv.FormatFloat(value, 'f', -1, 64),
	}
}

func newSEInt(name string, value int) *SimpleElement {
	return &SimpleElement{
		StartElement: xml.StartElement{Name: xml.Name{Local: name}},
		value:        strconv.Itoa(value),
	}
}

func newSEVec2(name string, value Vec2) *SimpleElement {
	return &SimpleElement{
		StartElement: xml.StartElement{
			Name: xml.Name{Local: name},
			Attr: []xml.Attr{
				{Name: xml.Name{Local: "x"}, Value: strconv.FormatFloat(value.X, 'f', -1, 64)},
				{Name: xml.Name{Local: "y"}, Value: strconv.FormatFloat(value.Y, 'f', -1, 64)},
				{Name: xml.Name{Local: "xunits"}, Value: string(value.XUnits)},
				{Name: xml.Name{Local: "yunits"}, Value: string(value.YUnits)},
			},
		},
	}
}

func newSEString(name, value string) *SimpleElement {
	return &SimpleElement{
		StartElement: xml.StartElement{Name: xml.Name{Local: name}},
		value:        value,
	}
}

func newSETime(name string, value time.Time) *SimpleElement {
	return &SimpleElement{
		StartElement: xml.StartElement{Name: xml.Name{Local: name}},
		value:        value.Format(time.RFC3339),
	}
}

func newCE(name string, children []Element) *CompoundElement {
	return &CompoundElement{
		StartElement: xml.StartElement{
			Name: xml.Name{Local: name},
		},
		children: children,
	}
}

func newSharedE(name, id string, children []Element) *SharedElement {
	var attr []xml.Attr
	if id != "" {
		attr = append(attr, xml.Attr{Name: xml.Name{Local: "id"}, Value: id})
	}
	return &SharedElement{
		CompoundElement: CompoundElement{
			StartElement: xml.StartElement{
				Name: xml.Name{Local: name},
				Attr: attr,
			},
			children: children,
		},
		id: id,
	}
}
<commit_msg>Factor out format* functions<commit_after>//go:generate go run ./internal/generate -f -o kml22gx.gen.go -n gx: xsd/kml22gx.xsd
//go:generate go run ./internal/generate -f -o ogckml22.gen.go xsd/ogckml22.xsd

// Package kml provides convenience methods for creating and writing KML documents.
//
// See https://developers.google.com/kml/
//
// Goals
//
//   - Convenient API for creating both simple and complex KML documents.
//   - 1:1 mapping between functions and KML elements.
//
// Non-goals
//
//   - Protection against generating invalid documents.
//   - Concealment of KML complexity.
//   - Fine-grained control over generated XML.
package kml

import (
	"encoding/xml"
	"fmt"
	"image/color"
	"io"
	"strconv"
	"time"
)

// An Element represents an abstract KML element.
type Element interface {
	xml.Marshaler
	Write(io.Writer) error
	WriteIndent(io.Writer, string, string) error
}

// A SimpleElement is an Element with a single value.
type SimpleElement struct {
	xml.StartElement
	value string
}

// A CompoundElement is an Element with children.
type CompoundElement struct {
	xml.StartElement
	children []Element
}

// A SharedElement is an element with an id.
type SharedElement struct {
	CompoundElement
	id string
}

// MarshalXML marshals se to e. start is ignored.
func (se *SimpleElement) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
	return e.EncodeElement(xml.CharData(se.value), se.StartElement)
}

// Write writes an XML header and se to w.
func (se *SimpleElement) Write(w io.Writer) error {
	return write(w, "", "", se)
}

// WriteIndent writes an XML header and se to w.
func (se *SimpleElement) WriteIndent(w io.Writer, prefix, indent string) error {
	return write(w, prefix, indent, se)
}

// Add adds children to ce.
func (ce *CompoundElement) Add(children ...Element) *CompoundElement {
	ce.children = append(ce.children, children...)
	return ce
}

// MarshalXML marshals ce to e. start is ignored.
func (ce *CompoundElement) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
	if err := e.EncodeToken(ce.StartElement); err != nil {
		return err
	}
	for _, c := range ce.children {
		if err := e.EncodeElement(c, ce.StartElement); err != nil {
			return err
		}
	}
	return e.EncodeToken(ce.End())
}

// Write writes an XML header and ce to w.
func (ce *CompoundElement) Write(w io.Writer) error {
	return write(w, "", "", ce)
}

// WriteIndent writes an XML header and ce to w.
func (ce *CompoundElement) WriteIndent(w io.Writer, prefix, indent string) error {
	return write(w, prefix, indent, ce)
}

// ID returns se's id.
func (se *SharedElement) ID() string {
	return se.id
}

// SetID sets se's id.
func (se *SharedElement) SetID(id string) {
	se.id = id
}

// URL returns se's URL.
func (se *SharedElement) URL() string {
	return "#" + se.ID()
}

func formatBool(value bool) string {
	if value {
		return "1"
	}
	return "0"
}

func formatColor(value color.Color) string {
	r, g, b, a := value.RGBA()
	return fmt.Sprintf("%02x%02x%02x%02x", a/256, b/256, g/256, r/256)
}

func formatInt(value int) string {
	return strconv.Itoa(value)
}

func formatFloat(value float64) string {
	return strconv.FormatFloat(value, 'f', -1, 64)
}

func formatTime(value time.Time) string {
	return value.Format(time.RFC3339)
}

func write(w io.Writer, prefix, indent string, m xml.Marshaler) error {
	if _, err := w.Write([]byte(xml.Header)); err != nil {
		return err
	}
	e := xml.NewEncoder(w)
	e.Indent(prefix, indent)
	return e.Encode(m)
}

func newSEBool(name string, value bool) *SimpleElement {
	return &SimpleElement{
		StartElement: xml.StartElement{Name: xml.Name{Local: name}},
		value:        formatBool(value),
	}
}

func newSEColor(name string, value color.Color) *SimpleElement {
	return &SimpleElement{
		StartElement: xml.StartElement{Name: xml.Name{Local: name}},
		value:        formatColor(value),
	}
}

func newSEElement(name string, value Element) *CompoundElement {
	return &CompoundElement{
		StartElement: xml.StartElement{
			Name: xml.Name{Local: name},
		},
		children: []Element{value},
	}
}

func newSEFloat(name string, value float64) *SimpleElement {
	return &SimpleElement{
		StartElement: xml.StartElement{Name: xml.Name{Local: name}},
		value:        formatFloat(value),
	}
}

func newSEInt(name string, value int) *SimpleElement {
	return &SimpleElement{
		StartElement: xml.StartElement{Name: xml.Name{Local: name}},
		value:        formatInt(value),
	}
}

func newSEVec2(name string, value Vec2) *SimpleElement {
	return &SimpleElement{
		StartElement: xml.StartElement{
			Name: xml.Name{Local: name},
			Attr: []xml.Attr{
				{Name: xml.Name{Local: "x"}, Value: strconv.FormatFloat(value.X, 'f', -1, 64)},
				{Name: xml.Name{Local: "y"}, Value: strconv.FormatFloat(value.Y, 'f', -1, 64)},
				{Name: xml.Name{Local: "xunits"}, Value: string(value.XUnits)},
				{Name: xml.Name{Local: "yunits"}, Value: string(value.YUnits)},
			},
		},
	}
}

func newSEString(name, value string) *SimpleElement {
	return &SimpleElement{
		StartElement: xml.StartElement{Name: xml.Name{Local: name}},
		value:        value,
	}
}

func newSETime(name string, value time.Time) *SimpleElement {
	return &SimpleElement{
		StartElement: xml.StartElement{Name: xml.Name{Local: name}},
		value:        value.Format(time.RFC3339),
	}
}

func newCE(name string, children []Element) *CompoundElement {
	return &CompoundElement{
		StartElement: xml.StartElement{
			Name: xml.Name{Local: name},
		},
		children: children,
	}
}

func newSharedE(name, id string, children []Element) *SharedElement {
	var attr []xml.Attr
	if id != "" {
		attr = append(attr, xml.Attr{Name: xml.Name{Local: "id"}, Value: id})
	}
	return &SharedElement{
		CompoundElement: CompoundElement{
			StartElement: xml.StartElement{
				Name: xml.Name{Local: name},
				Attr: attr,
			},
			children: children,
		},
		id: id,
	}
}
<|endoftext|>