<commit_before>package model

import (
	"database/sql"
	"fmt"
	"log"
	"strconv"
	"strings"
	"time"

	"github.com/dinever/dingo/app/utils"
	"github.com/russross/meddler"
	"net/http"
)

const stmtGetPostById = `SELECT * FROM posts WHERE id = ?`
const stmtGetPostBySlug = `SELECT * FROM posts WHERE slug = ?`
const stmtGetPostsByTag = `SELECT * FROM posts WHERE %s id IN ( SELECT post_id FROM posts_tags WHERE tag_id = ? ) ORDER BY published_at DESC LIMIT ? OFFSET ?`
const stmtGetAllPostsByTag = `SELECT * FROM posts WHERE id IN ( SELECT post_id FROM posts_tags WHERE tag_id = ?) ORDER BY published_at DESC `
const stmtGetPostsCountByTag = "SELECT count(*) FROM posts, posts_tags WHERE posts_tags.post_id = posts.id AND posts.published AND posts_tags.tag_id = ?"
const stmtInsertPostTag = `INSERT INTO posts_tags (id, post_id, tag_id) VALUES (?, ?, ?)`
const stmtDeletePostTagsByPostId = `DELETE FROM posts_tags WHERE post_id = ?`
const stmtNumberOfPosts = "SELECT count(*) FROM posts WHERE id IN ( SELECT post_id FROM posts_tags ) AND %s"
const stmtGetAllPostList = `SELECT * FROM posts WHERE %s ORDER BY %s`
const stmtGetPostList = `SELECT * FROM posts WHERE %s ORDER BY %s LIMIT ? OFFSET ?`
const stmtDeletePostById = `DELETE FROM posts WHERE id = ?`

type Post struct {
	Id              int64      `meddler:"id,pk"`
	Title           string     `meddler:"title"`
	Slug            string     `meddler:"slug"`
	Markdown        string     `meddler:"markdown"`
	Html            string     `meddler:"html"`
	Image           string     `meddler:"image"`
	IsFeatured      bool       `meddler:"featured"`
	IsPage          bool       `meddler:"page"`
	AllowComment    bool       `meddler:"allow_comment"`
	CommentNum      int64      `meddler:"comment_num"`
	IsPublished     bool       `meddler:"published"`
	Language        string     `meddler:"language"`
	MetaTitle       string     `meddler:"meta_title"`
	MetaDescription string     `meddler:"meta_description"`
	CreatedAt       *time.Time `meddler:"created_at"`
	CreatedBy       int64      `meddler:"created_by"`
	UpdatedAt       *time.Time `meddler:"updated_at"`
	UpdatedBy       int64      `meddler:"updated_by"`
	PublishedAt     *time.Time `meddler:"published_at"`
	PublishedBy     int64      `meddler:"published_by"`
	Hits            int64      `meddler:"-"`
	Category        string     `meddler:"-"`
}

type Posts []*Post

func (p Posts) Len() int {
	return len(p)
}

func (p Posts) Get(i int) *Post {
	return p[i]
}

func NewPost() *Post {
	return &Post{
		CreatedAt: utils.Now(),
	}
}

func (p *Post) TagString() string {
	tags := new(Tags)
	_ = tags.GetTagsByPostId(p.Id)
	var tagString string
	for i := 0; i < tags.Len(); i++ {
		if i != tags.Len()-1 {
			tagString += tags.Get(i).Name + ", "
		} else {
			tagString += tags.Get(i).Name
		}
	}
	return tagString
}

func (p *Post) Url() string {
	return "/" + p.Slug
}

func (p *Post) Tags() []*Tag {
	tags := new(Tags)
	err := tags.GetTagsByPostId(p.Id)
	if err != nil {
		return nil
	}
	return tags.GetAll()
}

func (p *Post) Author() *User {
	user := &User{Id: p.CreatedBy}
	err := user.GetUserById()
	if err != nil {
		return ghostUser
	}
	return user
}

func (p *Post) Comments() []*Comment {
	comments := new(Comments)
	err := comments.GetCommentsByPostId(p.Id)
	if err != nil {
		return nil
	}
	return comments.GetAll()
}

func (p *Post) Summary() string {
	text := strings.Split(p.Markdown, "<!--more-->")[0]
	return utils.Markdown2Html(text)
}

func (p *Post) Excerpt() string {
	return utils.Html2Excerpt(p.Html, 255)
}

func (p *Post) Save(tags ...*Tag) error {
	p.Slug = strings.TrimLeft(p.Slug, "/")
	p.Slug = strings.TrimRight(p.Slug, "/")
	if p.Slug == "" {
		return fmt.Errorf("Slug can not be empty or root")
	}

	if p.IsPublished {
		p.PublishedAt = utils.Now()
		p.PublishedBy = p.CreatedBy
	}

	p.UpdatedAt = utils.Now()
	p.UpdatedBy = p.CreatedBy

	if p.Id == 0 {
		// Insert post
		if err := p.Insert(); err != nil {
			return err
		}
	} else {
		if err := p.Update(); err != nil {
			return err
		}
	}
	tagIds := make([]int64, 0)
	// Insert tags
	for _, t := range tags {
		t.CreatedAt = utils.Now()
		t.CreatedBy = p.CreatedBy
		t.Hidden = !p.IsPublished
		t.Save()
		tagIds = append(tagIds, t.Id)
	}
	// Delete old post-tag projections
	err := DeletePostTagsByPostId(p.Id)
	// Insert postTags
	if err != nil {
		return err
	}
	for _, tagId := range tagIds {
		err := InsertPostTag(p.Id, tagId)
		if err != nil {
			return err
		}
	}
	return DeleteOldTags()
}

func (p *Post) Insert() error {
	if !PostChangeSlug(p.Slug) {
		p.Slug = generateNewSlug(p.Slug, 1)
	}
	err := meddler.Insert(db, "posts", p)
	return err
}

func InsertPostTag(post_id int64, tag_id int64) error {
	writeDB, err := db.Begin()
	if err != nil {
		writeDB.Rollback()
		return err
	}
	_, err = writeDB.Exec(stmtInsertPostTag, nil, post_id, tag_id)
	if err != nil {
		writeDB.Rollback()
		return err
	}
	return writeDB.Commit()
}

func (p *Post) Update() error {
	currentPost := &Post{Id: p.Id}
	err := currentPost.GetPostById()
	if err != nil {
		return err
	}
	if p.Slug != currentPost.Slug && !PostChangeSlug(p.Slug) {
		p.Slug = generateNewSlug(p.Slug, 1)
	}
	err = meddler.Update(db, "posts", p)
	return err
}

func (p *Post) UpdateFromRequest(r *http.Request) {
	p.Title = r.FormValue("title")
	p.Slug = r.FormValue("slug")
	p.Markdown = r.FormValue("content")
	p.Html = utils.Markdown2Html(p.Markdown)
	p.AllowComment = r.FormValue("comment") == "on"
	p.Category = r.FormValue("category")
	p.IsPublished = r.FormValue("status") == "on"
}

func DeletePostTagsByPostId(post_id int64) error {
	writeDB, err := db.Begin()
	if err != nil {
		writeDB.Rollback()
		return err
	}
	_, err = writeDB.Exec(stmtDeletePostTagsByPostId, post_id)
	if err != nil {
		writeDB.Rollback()
		return err
	}
	return writeDB.Commit()
}

func DeletePostById(id int64) error {
	writeDB, err := db.Begin()
	if err != nil {
		writeDB.Rollback()
		return err
	}
	_, err = writeDB.Exec(stmtDeletePostById, id)
	if err != nil {
		writeDB.Rollback()
		return err
	}
	err = writeDB.Commit()
	if err != nil {
		return err
	}
	err = DeletePostTagsByPostId(id)
	if err != nil {
		return err
	}
	return DeleteOldTags()
}

func (post *Post) GetPostById() error {
	err := meddler.QueryRow(db, post, stmtGetPostById, post.Id)
	return err
}

func (post *Post) GetPostBySlug(slug string) error {
	err := meddler.QueryRow(db, post, stmtGetPostBySlug, slug)
	return err
}

func (posts *Posts) GetPostsByTag(tagId, page, size int64, onlyPublished bool) (*utils.Pager, error) {
	var (
		pager *utils.Pager
		count int64
	)
	row := db.QueryRow(stmtGetPostsCountByTag, tagId)
	err := row.Scan(&count)
	if err != nil {
		log.Printf("[Error]: ", err.Error())
		return nil, err
	}
	pager = utils.NewPager(page, size, count)
	var where string
	if onlyPublished {
		where = "published AND"
	}
	err = meddler.QueryAll(db, posts, fmt.Sprintf(stmtGetPostsByTag, where), tagId, size, pager.Begin-1)
	return pager, err
}

func (posts *Posts) GetAllPostsByTag(tagId int64) error {
	err := meddler.QueryAll(db, posts, stmtGetAllPostsByTag, tagId)
	return err
}

func GetNumberOfPosts(isPage bool, published bool) (int64, error) {
	var count int64
	var where string
	if isPage {
		where = `page = 1`
	} else {
		where = `page = 0`
	}
	if published {
		where = where + ` AND published`
	}
	var row *sql.Row

	row = db.QueryRow(fmt.Sprintf(stmtNumberOfPosts, where))
	err := row.Scan(&count)
	if err != nil {
		return 0, err
	}
	return count, nil
}

func (posts *Posts) GetPostList(page, size int64, isPage bool, onlyPublished bool, orderBy string) (*utils.Pager, error) {
	var pager *utils.Pager
	count, err := GetNumberOfPosts(isPage, onlyPublished)
	pager = utils.NewPager(page, size, count)

	var where string
	if isPage {
		where = `page = 1`
	} else {
		where = `page = 0`
	}
	if onlyPublished {
		where = where + ` AND published`
	}

	err = meddler.QueryAll(db, posts, fmt.Sprintf(stmtGetPostList, where, orderBy), size, pager.Begin-1)
	return pager, err
}

func (posts *Posts) GetAllPostList(isPage bool, onlyPublished bool, orderBy string) error {
	var where string
	if isPage {
		where = `page = 1`
	} else {
		where = `page = 0`
	}
	if onlyPublished {
		where = where + `AND published`
	}
	err := meddler.QueryAll(db, posts, fmt.Sprintf(stmtGetAllPostList, where, orderBy))
	return err
}

func PostChangeSlug(slug string) bool {
	post := new(Post)
	err := post.GetPostBySlug(slug)
	if err != nil {
		return true
	}
	return false
}

func generateNewSlug(slug string, suffix int) string {
	newSlug := slug + "-" + strconv.Itoa(suffix)
	if !PostChangeSlug(newSlug) {
		return generateNewSlug(slug, suffix+1)
	}
	return newSlug
}
<commit_msg>Prevent possible SQL injection in Post model<commit_after>package model

import (
	"database/sql"
	"fmt"
	"log"
	"strconv"
	"strings"
	"time"

	"github.com/dinever/dingo/app/utils"
	"github.com/russross/meddler"
	"net/http"
)

const stmtGetPostById = `SELECT * FROM posts WHERE id = ?`
const stmtGetPostBySlug = `SELECT * FROM posts WHERE slug = ?`
const stmtGetPostsByTag = `SELECT * FROM posts WHERE %s id IN ( SELECT post_id FROM posts_tags WHERE tag_id = ? ) ORDER BY published_at DESC LIMIT ? OFFSET ?`
const stmtGetAllPostsByTag = `SELECT * FROM posts WHERE id IN ( SELECT post_id FROM posts_tags WHERE tag_id = ?) ORDER BY published_at DESC `
const stmtGetPostsCountByTag = "SELECT count(*) FROM posts, posts_tags WHERE posts_tags.post_id = posts.id AND posts.published AND posts_tags.tag_id = ?"
const stmtInsertPostTag = `INSERT INTO posts_tags (id, post_id, tag_id) VALUES (?, ?, ?)`
const stmtDeletePostTagsByPostId = `DELETE FROM posts_tags WHERE post_id = ?`
const stmtNumberOfPosts = "SELECT count(*) FROM posts WHERE id IN ( SELECT post_id FROM posts_tags ) AND %s"
const stmtGetAllPostList = `SELECT * FROM posts WHERE %s ORDER BY %s`
const stmtGetPostList = `SELECT * FROM posts WHERE %s ORDER BY %s LIMIT ? OFFSET ?`
const stmtDeletePostById = `DELETE FROM posts WHERE id = ?`

var safeOrderByStmt = map[string]string{
	"created_at":        "created_at",
	"created_at DESC":   "created_at DESC",
	"updated_at":        "updated_at",
	"updated_at DESC":   "updated_at DESC",
	"published_at":      "published_at",
	"published_at DESC": "published_at DESC",
}

type Post struct {
	Id              int64      `meddler:"id,pk"`
	Title           string     `meddler:"title"`
	Slug            string     `meddler:"slug"`
	Markdown        string     `meddler:"markdown"`
	Html            string     `meddler:"html"`
	Image           string     `meddler:"image"`
	IsFeatured      bool       `meddler:"featured"`
	IsPage          bool       `meddler:"page"`
	AllowComment    bool       `meddler:"allow_comment"`
	CommentNum      int64      `meddler:"comment_num"`
	IsPublished     bool       `meddler:"published"`
	Language        string     `meddler:"language"`
	MetaTitle       string     `meddler:"meta_title"`
	MetaDescription string     `meddler:"meta_description"`
	CreatedAt       *time.Time `meddler:"created_at"`
	CreatedBy       int64      `meddler:"created_by"`
	UpdatedAt       *time.Time `meddler:"updated_at"`
	UpdatedBy       int64      `meddler:"updated_by"`
	PublishedAt     *time.Time `meddler:"published_at"`
	PublishedBy     int64      `meddler:"published_by"`
	Hits            int64      `meddler:"-"`
	Category        string     `meddler:"-"`
}

type Posts []*Post

func (p Posts) Len() int {
	return len(p)
}

func (p Posts) Get(i int) *Post {
	return p[i]
}

func NewPost() *Post {
	return &Post{
		CreatedAt: utils.Now(),
	}
}

func (p *Post) TagString() string {
	tags := new(Tags)
	_ = tags.GetTagsByPostId(p.Id)
	var tagString string
	for i := 0; i < tags.Len(); i++ {
		if i != tags.Len()-1 {
			tagString += tags.Get(i).Name + ", "
		} else {
			tagString += tags.Get(i).Name
		}
	}
	return tagString
}

func (p *Post) Url() string {
	return "/" + p.Slug
}

func (p *Post) Tags() []*Tag {
	tags := new(Tags)
	err := tags.GetTagsByPostId(p.Id)
	if err != nil {
		return nil
	}
	return tags.GetAll()
}

func (p *Post) Author() *User {
	user := &User{Id: p.CreatedBy}
	err := user.GetUserById()
	if err != nil {
		return ghostUser
	}
	return user
}

func (p *Post) Comments() []*Comment {
	comments := new(Comments)
	err := comments.GetCommentsByPostId(p.Id)
	if err != nil {
		return nil
	}
	return comments.GetAll()
}

func (p *Post) Summary() string {
	text := strings.Split(p.Markdown, "<!--more-->")[0]
	return utils.Markdown2Html(text)
}

func (p *Post) Excerpt() string {
	return utils.Html2Excerpt(p.Html, 255)
}

func (p *Post) Save(tags ...*Tag) error {
	p.Slug = strings.TrimLeft(p.Slug, "/")
	p.Slug = strings.TrimRight(p.Slug, "/")
	if p.Slug == "" {
		return fmt.Errorf("Slug can not be empty or root")
	}

	if p.IsPublished {
		p.PublishedAt = utils.Now()
		p.PublishedBy = p.CreatedBy
	}

	p.UpdatedAt = utils.Now()
	p.UpdatedBy = p.CreatedBy

	if p.Id == 0 {
		// Insert post
		if err := p.Insert(); err != nil {
			return err
		}
	} else {
		if err := p.Update(); err != nil {
			return err
		}
	}
	tagIds := make([]int64, 0)
	// Insert tags
	for _, t := range tags {
		t.CreatedAt = utils.Now()
		t.CreatedBy = p.CreatedBy
		t.Hidden = !p.IsPublished
		t.Save()
		tagIds = append(tagIds, t.Id)
	}
	// Delete old post-tag projections
	err := DeletePostTagsByPostId(p.Id)
	// Insert postTags
	if err != nil {
		return err
	}
	for _, tagId := range tagIds {
		err := InsertPostTag(p.Id, tagId)
		if err != nil {
			return err
		}
	}
	return DeleteOldTags()
}

func (p *Post) Insert() error {
	if !PostChangeSlug(p.Slug) {
		p.Slug = generateNewSlug(p.Slug, 1)
	}
	err := meddler.Insert(db, "posts", p)
	return err
}

func InsertPostTag(post_id int64, tag_id int64) error {
	writeDB, err := db.Begin()
	if err != nil {
		writeDB.Rollback()
		return err
	}
	_, err = writeDB.Exec(stmtInsertPostTag, nil, post_id, tag_id)
	if err != nil {
		writeDB.Rollback()
		return err
	}
	return writeDB.Commit()
}

func (p *Post) Update() error {
	currentPost := &Post{Id: p.Id}
	err := currentPost.GetPostById()
	if err != nil {
		return err
	}
	if p.Slug != currentPost.Slug && !PostChangeSlug(p.Slug) {
		p.Slug = generateNewSlug(p.Slug, 1)
	}
	err = meddler.Update(db, "posts", p)
	return err
}

func (p *Post) UpdateFromRequest(r *http.Request) {
	p.Title = r.FormValue("title")
	p.Slug = r.FormValue("slug")
	p.Markdown = r.FormValue("content")
	p.Html = utils.Markdown2Html(p.Markdown)
	p.AllowComment = r.FormValue("comment") == "on"
	p.Category = r.FormValue("category")
	p.IsPublished = r.FormValue("status") == "on"
}

func DeletePostTagsByPostId(post_id int64) error {
	writeDB, err := db.Begin()
	if err != nil {
		writeDB.Rollback()
		return err
	}
	_, err = writeDB.Exec(stmtDeletePostTagsByPostId, post_id)
	if err != nil {
		writeDB.Rollback()
		return err
	}
	return writeDB.Commit()
}

func DeletePostById(id int64) error {
	writeDB, err := db.Begin()
	if err != nil {
		writeDB.Rollback()
		return err
	}
	_, err = writeDB.Exec(stmtDeletePostById, id)
	if err != nil {
		writeDB.Rollback()
		return err
	}
	err = writeDB.Commit()
	if err != nil {
		return err
	}
	err = DeletePostTagsByPostId(id)
	if err != nil {
		return err
	}
	return DeleteOldTags()
}

func (post *Post) GetPostById() error {
	err := meddler.QueryRow(db, post, stmtGetPostById, post.Id)
	return err
}

func (post *Post) GetPostBySlug(slug string) error {
	err := meddler.QueryRow(db, post, stmtGetPostBySlug, slug)
	return err
}

func (posts *Posts) GetPostsByTag(tagId, page, size int64, onlyPublished bool) (*utils.Pager, error) {
	var (
		pager *utils.Pager
		count int64
	)
	row := db.QueryRow(stmtGetPostsCountByTag, tagId)
	err := row.Scan(&count)
	if err != nil {
		log.Printf("[Error]: ", err.Error())
		return nil, err
	}
	pager = utils.NewPager(page, size, count)
	var where string
	if onlyPublished {
		where = "published AND"
	}
	err = meddler.QueryAll(db, posts, fmt.Sprintf(stmtGetPostsByTag, where), tagId, size, pager.Begin-1)
	return pager, err
}

func (posts *Posts) GetAllPostsByTag(tagId int64) error {
	err := meddler.QueryAll(db, posts, stmtGetAllPostsByTag, tagId)
	return err
}

func GetNumberOfPosts(isPage bool, published bool) (int64, error) {
	var count int64
	var where string
	if isPage {
		where = `page = 1`
	} else {
		where = `page = 0`
	}
	if published {
		where = where + ` AND published`
	}
	var row *sql.Row

	row = db.QueryRow(fmt.Sprintf(stmtNumberOfPosts, where))
	err := row.Scan(&count)
	if err != nil {
		return 0, err
	}
	return count, nil
}

func (posts *Posts) GetPostList(page, size int64, isPage bool, onlyPublished bool, orderBy string) (*utils.Pager, error) {
	var pager *utils.Pager
	count, err := GetNumberOfPosts(isPage, onlyPublished)
	pager = utils.NewPager(page, size, count)

	var where string
	if isPage {
		where = `page = 1`
	} else {
		where = `page = 0`
	}
	if onlyPublished {
		where = where + ` AND published`
	}
	safeOrderBy := getSafeOrderByStmt(orderBy)

	err = meddler.QueryAll(db, posts, fmt.Sprintf(stmtGetPostList, where, safeOrderBy), size, pager.Begin-1)
	return pager, err
}

func (posts *Posts) GetAllPostList(isPage bool, onlyPublished bool, orderBy string) error {
	var where string
	if isPage {
		where = `page = 1`
	} else {
		where = `page = 0`
	}
	if onlyPublished {
		where = where + `AND published`
	}
	safeOrderBy := getSafeOrderByStmt(orderBy)
	err := meddler.QueryAll(db, posts, fmt.Sprintf(stmtGetAllPostList, where, safeOrderBy))
	return err
}

func PostChangeSlug(slug string) bool {
	post := new(Post)
	err := post.GetPostBySlug(slug)
	if err != nil {
		return true
	}
	return false
}

func generateNewSlug(slug string, suffix int) string {
	newSlug := slug + "-" + strconv.Itoa(suffix)
	if !PostChangeSlug(newSlug) {
		return generateNewSlug(slug, suffix+1)
	}
	return newSlug
}

// getSafeOrderByStmt returns a safe `ORDER BY` statement to be when used when
// building SQL queries, in order to prevent SQL injection.
//
// Since we can't use the placeholder `?` to specify the `ORDER BY` values in
// queries, we need to build them using `fmt.Sprintf`. Typically, doing so
// would open you up to SQL injection attacks, since any string can be passed
// into `fmt.Sprintf`, including strings that are valid SQL queries! By using
// this function to check a map of safe values, we guarantee that no unsafe
// values are ever passed to our query building function.
func getSafeOrderByStmt(orderBy string) string {
	if stmt, ok := safeOrderByStmt[orderBy]; ok {
		return stmt
	}
	return "published_at DESC"
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"

	"github.com/hechuangqiang/golis"
)

func main() {
	s := golis.NewServer()
	s.FilterChain().AddLast("test", &filter{})
	s.RunOnPort("tcp", ":9090")
}

type filter struct{}

func (*filter) SessionOpened(session *golis.Iosession) bool {
	fmt.Println("session opened,the client is ", session.GetConn().RemoteAddr().String())
	return true
}

func (*filter) SessionClosed(session *golis.Iosession) bool {
	fmt.Println("session closed")
	return true
}

func (*filter) MsgReceived(session *golis.Iosession, message interface{}) bool {
	if msg, ok := message.(*golis.Buffer); ok {
		bs, _ := msg.ReadBytes(msg.GetWritePos() - msg.GetReadPos())
		fmt.Println("received msg :", string(bs))
		replayMsg := fmt.Sprintf("echoServer received msg : %v", string(bs))
		session.Write([]byte(replayMsg))
		msg.ResetRead()
		msg.ResetWrite()
	} else {
		fmt.Println("not ok")
	}
	return true
}

func (*filter) MsgSend(session *golis.Iosession, message interface{}) bool {
	return true
}

func (*filter) ErrorCaught(session *golis.Iosession, err error) bool {
	return true
}
<commit_msg>fix echoServer<commit_after>package main

import (
	"fmt"

	"github.com/hechuangqiang/golis"
)

func main() {
	s := golis.NewServer()
	s.FilterChain().AddLast("codec", &codecFilter{}).AddLast("test", &filter{})
	s.RunOnPort("tcp", ":9090")
}

type codecFilter struct {
	golis.IoFilterAdapter
}

func (*codecFilter) Decode(message interface{}) (interface{}, bool) {
	if buffer, ok := message.(*golis.Buffer); ok {
		bs, _ := buffer.ReadBytes(buffer.GetWritePos() - buffer.GetReadPos())
		buffer.ResetRead()
		buffer.ResetWrite()
		return bs, true
	}
	return message, false
}

func (*codecFilter) Encode(message interface{}) (interface{}, bool) {
	return message, true
}

type filter struct{}

func (*filter) SessionOpened(session *golis.Iosession) bool {
	fmt.Println("session opened,the client is ", session.GetConn().RemoteAddr().String())
	return true
}

func (*filter) SessionClosed(session *golis.Iosession) bool {
	fmt.Println("session closed")
	return true
}

func (*filter) MsgReceived(session *golis.Iosession, message interface{}) bool {
	if bs, ok := message.([]byte); ok {
		fmt.Println("received msg :", string(bs))
		replayMsg := fmt.Sprintf("echoServer received msg : %v", string(bs))
		session.Write([]byte(replayMsg))
	}
	return true
}

func (*filter) MsgSend(session *golis.Iosession, message interface{}) bool {
	return true
}

func (*filter) ErrorCaught(session *golis.Iosession, err error) bool {
	return true
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"time"
	term "github.com/nsf/termbox-go"
	"io/ioutil"
	"strings"
)

// we use line, offset style. termbox use o, l style.
func SetCursor(l, o int) {
	term.SetCursor(o, l)
}

func SetCell(l, o int, ch rune, fg, bg term.Attribute) {
	term.SetCell(o, l, ch, fg, bg)
}

func SetTermboxCursor(c *Cursor, w *Window, l *Layout) {
	view := l.MainViewerBound()
	p := c.PositionInWindow(w)
	SetCursor(view.min.l+p.l, view.min.o+p.o)
}

func clearScreen(l *Layout) {
	viewer := l.MainViewerBound()
	for l := viewer.min.l ; l < viewer.max.l ; l++ {
		for o := viewer.min.o ; o < viewer.max.o ; o++ {
			SetCell(l, o, ' ', term.ColorDefault, term.ColorDefault)
		}
	}
}

// draw text inside of window at mainviewer
func drawScreen(l *Layout, w *Window, t *Text, sel *Selection) {
	viewer := l.MainViewerBound()
	for l , ln := range t.lines {
		if l < w.min.l || l >= w.max.l {
			continue
		}
		o := 0 // we cannot use index of line([]rune) because some rune have multiple-visible length. ex) tab, korean
		for _, ch := range ln.data {
			if o >= w.max.o {
				break
			}
			bgColor := term.ColorDefault
			if sel.on && sel.Contains(Point{l,o}) {
				bgColor = term.ColorGreen
			}
			// append cell to buffer
			if ch == '\t' {
				for i:=0 ; i<taboffset ; i++ {
					if o >= w.min.o {
						SetCell(l-w.min.l+viewer.min.l, o-w.min.o+viewer.min.o, rune(' '), term.ColorWhite, bgColor)
					}
					o += 1
				}
			} else {
				if o >= w.min.o {
					SetCell(l-w.min.l+viewer.min.l, o-w.min.o+viewer.min.o, rune(ch), term.ColorWhite, bgColor)
				}
				o += 1
			}
		}
	}
}

func printStatus(status string) {
	termw, termh := term.Size()
	statusLine := termh - 1
	for off:=0 ; off<termw ; off++ {
		SetCell(statusLine, off, ' ', term.ColorBlack, term.ColorWhite)
	}
	for off, ch := range status {
		SetCell(statusLine, off, rune(ch), term.ColorBlack, term.ColorWhite)
	}
}

func parseEvent(ev term.Event, sel *Selection) []*Action {
	if ev.Type != term.EventKey {
		panic(fmt.Sprintln("what the..", ev.Type, "event?"))
	}

	switch ev.Key {
	case term.KeyCtrlW:
		return []*Action{&Action{kind:"exit"}}
	case term.KeyCtrlS:
		return []*Action{&Action{kind:"save"}}
	// move
	case term.KeyArrowLeft:
		return []*Action{&Action{kind:"move", value:"left"}}
	case term.KeyArrowRight:
		return []*Action{&Action{kind:"move", value:"right"}}
	case term.KeyArrowUp:
		return []*Action{&Action{kind:"move", value:"up"}}
	case term.KeyArrowDown:
		return []*Action{&Action{kind:"move", value:"down"}}
	// insert
	case term.KeyEnter:
		return []*Action{&Action{kind:"insert", value:"\n"}}
	case term.KeySpace:
		return []*Action{&Action{kind:"insert", value:" "}}
	case term.KeyTab:
		return []*Action{&Action{kind:"insert", value:"\t"}}
	// delete : value will added after actual deletion.
	case term.KeyDelete:
		if sel.on {
			return []*Action{&Action{kind:"deleteSelection"}}
		} else {
			return []*Action{&Action{kind:"delete"}}
		}
	case term.KeyBackspace, term.KeyBackspace2:
		if sel.on {
			return []*Action{&Action{kind:"deleteSelection"}}
		} else {
			return []*Action{&Action{kind:"backspace"}}
		}
	// undo, redo
	case term.KeyCtrlZ:
		return []*Action{&Action{kind:"undo"}}
	case term.KeyCtrlY:
		return []*Action{&Action{kind:"redo"}}
	// copy, paste
	case term.KeyCtrlC:
		return []*Action{&Action{kind:"copy"}}
	case term.KeyCtrlV:
		return []*Action{&Action{kind:"paste"}}
	default:
		if ev.Ch == 0 {
			return []*Action{&Action{kind:"unknown"}}
		}
		if ev.Mod & term.ModAlt != 0 {
			kind := "move"
			if withShift(ev.Ch) {
				kind = "select"
			}
			switch ev.Ch {
			case 'j', 'J':
				return []*Action{&Action{kind:kind, value:"left"}}
			case 'l', 'L':
				return []*Action{&Action{kind:kind, value:"right"}}
			case 'i', 'I':
				return []*Action{&Action{kind:kind, value:"up"}}
			case 'k', 'K':
				return []*Action{&Action{kind:kind, value:"down"}}
			case 'm', 'M':
				return []*Action{&Action{kind:kind, value:"bow"}}
			case '.', '>':
				return []*Action{&Action{kind:kind, value:"eow"}}
			case 'u', 'U':
				return []*Action{&Action{kind:kind, value:"bol"}}
			case 'o', 'O':
				return []*Action{&Action{kind:kind, value:"eol"}}
			case 'h', 'H':
				return []*Action{&Action{kind:kind, value:"pageup"}}
			case 'n', 'N':
				return []*Action{&Action{kind:kind, value:"pagedown"}}
			case 'a', 'A':
				return []*Action{&Action{kind:kind, value:"bof"}}
			case 'z', 'Z':
				return []*Action{&Action{kind:kind, value:"eof"}}
			default:
				return []*Action{&Action{kind:"none"}}
			}
		}
		if sel.on {
			return []*Action{&Action{kind:"deleteSelection"}, &Action{kind:"insert", value:string(ev.Ch)}}
		} else {
			return []*Action{&Action{kind:"insert", value:string(ev.Ch)}}
		}
	}
}

func do(a *Action, c *Cursor, sel *Selection, history *History) {
	if a.kind != "select" {
		sel.on=false
	}
	switch a.kind {
	case "none":
		return
	case "move", "select":
		if a.kind == "select" && !sel.on {
			sel.on = true
			sel.SetStart(c)
		}
		switch a.value {
		case "left":
			c.MoveLeft()
		case "right":
			c.MoveRight()
		case "up":
			c.MoveUp()
		case "down":
			c.MoveDown()
		case "bow":
			c.MoveBow()
		case "eow":
			c.MoveEow()
		case "bol":
			c.MoveBol()
		case "eol":
			c.MoveEol()
		case "pageup":
			c.PageUp()
		case "pagedown":
			c.PageDown()
		case "bof":
			c.MoveBof()
		case "eof":
			c.MoveEof()
		default:
			panic(fmt.Sprintln("what the..", a.value, "move?"))
		}
		if a.kind == "select" {
				sel.SetEnd(c)
		}
	case "insert":
		c.Insert(a.value)
	case "delete":
		a.value = c.Delete()
	case "backspace":
		a.value = c.Backspace()
	case "deleteSelection":
		a.value = c.DeleteSelection(sel)
	case "undo":
		if history.head == 0 {
			return
		}
		history.head--
		action := history.At(history.head)
		// status = fmt.Sprintf("undo : %v", action)
		// holdStatus = true
		switch action.kind {
		case "insert":
			c.Copy(action.afterCursor)
			for range action.value {
				c.Backspace()
			}
		case "backspace":
			c.Copy(action.afterCursor)
			for _, r := range action.value {
				c.Insert(string(r))
			}
		case "delete", "deleteSelection":
			c.Copy(action.afterCursor)
			for _, r := range action.value {
				c.Insert(string(r))
			}
		default:
			panic(fmt.Sprintln("what the..", action.kind, "history?"))
		}
	case "redo":
		if history.head == history.Len() {
			return
		}
		action := history.At(history.head)
		// status = fmt.Sprintf("redo : %v", action)
		// holdStatus = true
		history.head++
		switch action.kind {
		case "insert":
			c.Copy(action.beforeCursor)
			for _, r := range action.value {
				c.Insert(string(r))
			}
		case "backspace":
			c.Copy(action.beforeCursor)
			for range action.value {
				c.Backspace()
			}
		case "delete", "deleteSelection":
			c.Copy(action.beforeCursor)
			for range action.value {
				c.Delete()
			}
		default:
			panic(fmt.Sprintln("what the..", action.kind, "history?"))
		}
	default:
		panic(fmt.Sprintln("what the..", a.kind, "action?"))
	}
}

func main() {
	// check there is an destination file. ex)tor some.file
	args := os.Args[1:]
	if len(args)==0 {
		fmt.Println("please, set text file")
		return
	}
	f := args[0]

	text, err := open(f)
	if err != nil {
		fmt.Println(err)
		return
	}

	err = term.Init()
	if err != nil {
		panic(err)
	}
	defer term.Close()
	term.SetInputMode(term.InputAlt)
	term.Clear(term.ColorDefault, term.ColorDefault)
	term.Flush()

	layout := NewLayout()
	mainview := layout.MainViewerBound()
	win := NewWindow(layout)
	// drawbuf := textToDrawBuffer(text, selection)
	cursor := NewCursor(text)
	selection := NewSelection()
	history := newHistory()
	SetCursor(mainview.min.l, mainview.min.o)

	status := ""
	holdStatus := false
	lastActStr := ""
	copied := ""
	events := make(chan term.Event, 20)
	go func() {
		for {
			events <- term.PollEvent()
		}
	}()
	for {
		win.Follow(cursor)
		clearScreen(layout)
		drawScreen(layout, win, text, selection)

		if !holdStatus {
			if selection.on {
				status = "selection on - " + fmt.Sprintf("(%v, %v) - (%v, %v)", selection.start.l, selection.start.o, selection.end.l, selection.end.o)
			} else {
				status = fmt.Sprintf("linenum:%v, byteoff:%v, visoff:%v, cursoroff:%v, vpos:(%v,%v, %v,%v)", cursor.l, cursor.b, cursor.v, cursor.o, win.min.l, win.min.o, win.max.l, win.max.o)
			}
		}
		printStatus(status)
		holdStatus = false

		SetTermboxCursor(cursor, win, layout)
		term.Flush()

		// wait for keyboard input
		select {
		case ev := <-events:
			switch ev.Type {
			case term.EventKey:
				actions := parseEvent(ev, selection)
				for _, a := range actions {
					// keepSelection := false
					beforeCursor := *cursor

					if a.kind == "exit" {
						return
					}
					if a.kind == "save" {
						err := save(f, text)
						if err != nil {
							panic(err)
						}
						status = fmt.Sprintf("successfully saved : %v", f)
						holdStatus = true
						continue
					}
					if a.kind == "copy" {
						minc, maxc := selection.MinMax()
						copied = text.DataInside(minc, maxc)
						continue
					}
					if a.kind == "paste" {
						plines := strings.Split(copied, "\n")
						plen := len(plines)
						for i, l := range plines {
							cursor.Insert(l)
							if i != plen-1 {
								cursor.SplitLine()
							}
						}
						continue
					}
					do(a, cursor, selection, history)

					switch a.kind {
					case "insert", "delete", "backspace", "deleteSelection":
						// remember the action.
						nc := history.Cut(history.head)
						if nc != 0 {
							lastActStr = ""
						}
						if a.kind == lastActStr {
							lastAct, err := history.Pop()
							if err != nil {
								panic(err)
							}
							history.head--
							beforeCursor = lastAct.beforeCursor
							if a.kind == "insert" || a.kind == "delete" {
								a.value = lastAct.value + a.value
							} else if a.kind == "backspace" {
								a.value = a.value + lastAct.value
							}
						}
						a.beforeCursor = beforeCursor
						if a.kind == "deleteSelection" {
							a.beforeCursor, _ = selection.MinMax();
						}
						a.afterCursor = *cursor
						history.Add(a)
						history.head++
					}
					lastActStr = a.kind
					lastAct := history.Last()
					if lastAct != nil {
						historyFileString := ""
						for i, a := range history.actions {
							if i != 0 {
								historyFileString += "\n"
							}
							historyFileString += fmt.Sprintf("%v, %v", a, history.head)
						}
						ioutil.WriteFile(extendFileName(f, ".history"), []byte(historyFileString), 0755)
					}
				}
			}
		case <-time.After(time.Second):
			holdStatus = true
		// case term.EventResize:
		//	win.resize()
		//	win.clear()
		//	win.draw()
		}
	}
}
<commit_msg>refine flow. after copy, selection turned off.<commit_after>package main

import (
	"fmt"
	"os"
	"time"
	term "github.com/nsf/termbox-go"
	"io/ioutil"
	"strings"
)

// we use line, offset style. termbox use o, l style.
func SetCursor(l, o int) {
	term.SetCursor(o, l)
}

func SetCell(l, o int, ch rune, fg, bg term.Attribute) {
	term.SetCell(o, l, ch, fg, bg)
}

func SetTermboxCursor(c *Cursor, w *Window, l *Layout) {
	view := l.MainViewerBound()
	p := c.PositionInWindow(w)
	SetCursor(view.min.l+p.l, view.min.o+p.o)
}

func clearScreen(l *Layout) {
	viewer := l.MainViewerBound()
	for l := viewer.min.l ; l < viewer.max.l ; l++ {
		for o := viewer.min.o ; o < viewer.max.o ; o++ {
			SetCell(l, o, ' ', term.ColorDefault, term.ColorDefault)
		}
	}
}

// draw text inside of window at mainviewer
func drawScreen(l *Layout, w *Window, t *Text, sel *Selection) {
	viewer := l.MainViewerBound()
	for l , ln := range t.lines {
		if l < w.min.l || l >= w.max.l {
			continue
		}
		o := 0 // we cannot use index of line([]rune) because some rune have multiple-visible length. ex) tab, korean
		for _, ch := range ln.data {
			if o >= w.max.o {
				break
			}
			bgColor := term.ColorDefault
			if sel.on && sel.Contains(Point{l,o}) {
				bgColor = term.ColorGreen
			}
			// append cell to buffer
			if ch == '\t' {
				for i:=0 ; i<taboffset ; i++ {
					if o >= w.min.o {
						SetCell(l-w.min.l+viewer.min.l, o-w.min.o+viewer.min.o, rune(' '), term.ColorWhite, bgColor)
					}
					o += 1
				}
			} else {
				if o >= w.min.o {
					SetCell(l-w.min.l+viewer.min.l, o-w.min.o+viewer.min.o, rune(ch), term.ColorWhite, bgColor)
				}
				o += 1
			}
		}
	}
}

func printStatus(status string) {
	termw, termh := term.Size()
	statusLine := termh - 1
	for off:=0 ; off<termw ; off++ {
		SetCell(statusLine, off, ' ', term.ColorBlack, term.ColorWhite)
	}
	for off, ch := range status {
		SetCell(statusLine, off, rune(ch), term.ColorBlack, term.ColorWhite)
	}
}

func parseEvent(ev term.Event, sel *Selection) []*Action {
	if ev.Type != term.EventKey {
		panic(fmt.Sprintln("what the..", ev.Type, "event?"))
	}

	switch ev.Key {
	case term.KeyCtrlW:
		return []*Action{&Action{kind:"exit"}}
	case term.KeyCtrlS:
		return []*Action{&Action{kind:"save"}}
	// move
	case term.KeyArrowLeft:
		return []*Action{&Action{kind:"move", value:"left"}}
	case term.KeyArrowRight:
		return []*Action{&Action{kind:"move", value:"right"}}
	case term.KeyArrowUp:
		return []*Action{&Action{kind:"move", value:"up"}}
	case term.KeyArrowDown:
		return []*Action{&Action{kind:"move", value:"down"}}
	// insert
	case term.KeyEnter:
		return []*Action{&Action{kind:"insert", value:"\n"}}
	case term.KeySpace:
		return []*Action{&Action{kind:"insert", value:" "}}
	case term.KeyTab:
		return []*Action{&Action{kind:"insert", value:"\t"}}
	// delete : value will added after actual deletion.
	case term.KeyDelete:
		if sel.on {
			return []*Action{&Action{kind:"deleteSelection"}}
		} else {
			return []*Action{&Action{kind:"delete"}}
		}
	case term.KeyBackspace, term.KeyBackspace2:
		if sel.on {
			return []*Action{&Action{kind:"deleteSelection"}}
		} else {
			return []*Action{&Action{kind:"backspace"}}
		}
	// undo, redo
	case term.KeyCtrlZ:
		return []*Action{&Action{kind:"undo"}}
	case term.KeyCtrlY:
		return []*Action{&Action{kind:"redo"}}
	// copy, paste
	case term.KeyCtrlC:
		return []*Action{&Action{kind:"copy"}}
	case term.KeyCtrlV:
		return []*Action{&Action{kind:"paste"}}
	default:
		if ev.Ch == 0 {
			return []*Action{&Action{kind:"unknown"}}
		}
		if ev.Mod & term.ModAlt != 0 {
			kind := "move"
			if withShift(ev.Ch) {
				kind = "select"
			}
			switch ev.Ch {
			case 'j', 'J':
				return []*Action{&Action{kind:kind, value:"left"}}
			case 'l', 'L':
				return []*Action{&Action{kind:kind, value:"right"}}
			case 'i', 'I':
				return []*Action{&Action{kind:kind, value:"up"}}
			case 'k', 'K':
				return []*Action{&Action{kind:kind, value:"down"}}
			case 'm', 'M':
				return []*Action{&Action{kind:kind, value:"bow"}}
			case '.', '>':
				return []*Action{&Action{kind:kind, value:"eow"}}
			case 'u', 'U':
				return []*Action{&Action{kind:kind, value:"bol"}}
			case 'o', 'O':
				return []*Action{&Action{kind:kind, value:"eol"}}
			case 'h', 'H':
				return []*Action{&Action{kind:kind, value:"pageup"}}
			case 'n', 'N':
				return []*Action{&Action{kind:kind, value:"pagedown"}}
			case 'a', 'A':
				return []*Action{&Action{kind:kind, value:"bof"}}
			case 'z', 'Z':
				return []*Action{&Action{kind:kind, value:"eof"}}
			default:
				return []*Action{&Action{kind:"none"}}
			}
		}
		if sel.on {
			return []*Action{&Action{kind:"deleteSelection"}, &Action{kind:"insert", value:string(ev.Ch)}}
		} else {
			return []*Action{&Action{kind:"insert", value:string(ev.Ch)}}
		}
	}
}

func do(a *Action, c *Cursor, sel *Selection, history *History) {
	switch a.kind {
	case "none":
		return
	case "move", "select":
		if a.kind == "select" && !sel.on {
			sel.on = true
			sel.SetStart(c)
		}
		switch a.value {
		case "left":
			c.MoveLeft()
		case "right":
			c.MoveRight()
		case "up":
			c.MoveUp()
		case "down":
			c.MoveDown()
		case "bow":
			c.MoveBow()
		case "eow":
			c.MoveEow()
		case "bol":
			c.MoveBol()
		case "eol":
			c.MoveEol()
		case "pageup":
			c.PageUp()
		case "pagedown":
			c.PageDown()
		case "bof":
			c.MoveBof()
		case "eof":
			c.MoveEof()
		default:
			panic(fmt.Sprintln("what the..", a.value, "move?"))
		}
		if a.kind == "select" {
				sel.SetEnd(c)
		}
	case "insert":
		c.Insert(a.value)
	case "delete":
		a.value = c.Delete()
	case "backspace":
		a.value = c.Backspace()
	case "deleteSelection":
		a.value = c.DeleteSelection(sel)
	case "undo":
		if history.head == 0 {
			return
		}
		history.head--
		action := history.At(history.head)
		// status = fmt.Sprintf("undo : %v", action)
		// holdStatus = true
		switch action.kind {
		case "insert":
			c.Copy(action.afterCursor)
			for range action.value {
				c.Backspace()
			}
		case "backspace":
			c.Copy(action.afterCursor)
			for _, r := range action.value {
				c.Insert(string(r))
			}
		case "delete", "deleteSelection":
			c.Copy(action.afterCursor)
			for _, r := range action.value {
				c.Insert(string(r))
			}
		default:
			panic(fmt.Sprintln("what the..", action.kind, "history?"))
		}
	case "redo":
		if history.head == history.Len() {
			return
		}
		action := history.At(history.head)
		// status = fmt.Sprintf("redo : %v", action)
		// holdStatus = true
		history.head++
		switch action.kind {
		case "insert":
			c.Copy(action.beforeCursor)
			for _, r := range action.value {
				c.Insert(string(r))
			}
		case "backspace":
			c.Copy(action.beforeCursor)
			for range action.value {
				c.Backspace()
			}
		case "delete", "deleteSelection":
			c.Copy(action.beforeCursor)
			for range action.value {
				c.Delete()
			}
		default:
			panic(fmt.Sprintln("what the..", action.kind, "history?"))
		}
	default:
		panic(fmt.Sprintln("what the..", a.kind, "action?"))
	}
}

func main() {
	// check there is an destination file. ex)tor some.file
	args := os.Args[1:]
	if len(args)==0 {
		fmt.Println("please, set text file")
		return
	}
	f := args[0]

	text, err := open(f)
	if err != nil {
		fmt.Println(err)
		return
	}

	err = term.Init()
	if err != nil {
		panic(err)
	}
	defer term.Close()
	term.SetInputMode(term.InputAlt)
	term.Clear(term.ColorDefault, term.ColorDefault)
	term.Flush()

	layout := NewLayout()
	mainview := layout.MainViewerBound()
	win := NewWindow(layout)
	// drawbuf := textToDrawBuffer(text, selection)
	cursor := NewCursor(text)
	selection := NewSelection()
	history := newHistory()
	SetCursor(mainview.min.l, mainview.min.o)

	status := ""
	holdStatus := false
	lastActStr := ""
	copied := ""
	events := make(chan term.Event, 20)
	go func() {
		for {
			events <- term.PollEvent()
		}
	}()
	for {
		win.Follow(cursor)
		clearScreen(layout)
		drawScreen(layout, win, text, selection)

		if !holdStatus {
			if selection.on {
				status = "selection on - " + fmt.Sprintf("(%v, %v) - (%v, %v)", selection.start.l, selection.start.o, selection.end.l, selection.end.o)
			} else {
				status = fmt.Sprintf("linenum:%v, byteoff:%v, visoff:%v, cursoroff:%v, vpos:(%v,%v, %v,%v)", cursor.l, cursor.b, cursor.v, cursor.o, win.min.l, win.min.o, win.max.l, win.max.o)
			}
		}
		printStatus(status)
		holdStatus = false

		SetTermboxCursor(cursor, win, layout)
		term.Flush()

		// wait for keyboard input
		select {
		case ev := <-events:
			switch ev.Type {
			case term.EventKey:
				actions := parseEvent(ev, selection)
				for _, a := range actions {
					// keepSelection := false
					beforeCursor := *cursor

					if a.kind != "select" {
						selection.on=false
					}

					if a.kind == "exit" {
						return
					} else if a.kind == "save" {
						err := save(f, text)
						if err != nil {
							panic(err)
						}
						status = fmt.Sprintf("successfully saved : %v", f)
						holdStatus = true
					} else if a.kind == "copy" {
						minc, maxc := selection.MinMax()
						copied = text.DataInside(minc, maxc)
					} else if a.kind == "paste" {
						plines := strings.Split(copied, "\n")
						plen := len(plines)
						for i, l := range plines {
							cursor.Insert(l)
							if i != plen-1 {
								cursor.SplitLine()
							}
						}
					} else {
						do(a, cursor, selection, history)
					}
					switch a.kind {
					case "insert", "delete", "backspace", "deleteSelection":
						// remember the action.
						nc := history.Cut(history.head)
						if nc != 0 {
							lastActStr = ""
						}
						if a.kind == lastActStr {
							lastAct, err := history.Pop()
							if err != nil {
								panic(err)
							}
							history.head--
							beforeCursor = lastAct.beforeCursor
							if a.kind == "insert" || a.kind == "delete" {
								a.value = lastAct.value + a.value
							} else if a.kind == "backspace" {
								a.value = a.value + lastAct.value
							}
						}
						a.beforeCursor = beforeCursor
						if a.kind == "deleteSelection" {
							a.beforeCursor, _ = selection.MinMax();
						}
						a.afterCursor = *cursor
						history.Add(a)
						history.head++
					}
					lastActStr = a.kind
					lastAct := history.Last()
					if lastAct != nil {
						historyFileString := ""
						for i, a := range history.actions {
							if i != 0 {
								historyFileString += "\n"
							}
							historyFileString += fmt.Sprintf("%v, %v", a, history.head)
						}
						ioutil.WriteFile(extendFileName(f, ".history"), []byte(historyFileString), 0755)
					}
				}
			}
		case <-time.After(time.Second):
			holdStatus = true
		// case term.EventResize:
		//	win.resize()
		//	win.clear()
		//	win.draw()
		}
	}
}
<|endoftext|>


<commit_before>package dalga

import (
	"bytes"
	"database/sql"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/cenkalti/dalga/vendor/github.com/go-sql-driver/mysql"
)

func init() {
	*debugging = true
}

var (
	testPath     = "testPath"
	testBody     = "testBody"
	testInterval time.Duration // zero
	testDelay    = time.Second
)

func TestSchedule(t *testing.T) {
	config := DefaultConfig

	db, err := sql.Open("mysql", config.MySQL.DSN())
	if err != nil {
		t.Errorf(err.Error())
		return
	}

	defer db.Close()

	err = db.Ping()
	if err != nil {
		t.Fatalf("cannot connect to MySQL: %s", err.Error())
	}

	println("connected to db")

	drop_sql := "DROP TABLE " + config.MySQL.Table
	_, err = db.Exec(drop_sql)
	if err != nil {
		if myErr, ok := err.(*mysql.MySQLError); !ok || myErr.Number != 1051 { // Unknown table
			t.Fatal(err)
		}
	}

	println("dropped table")

	called := make(chan string)
	endpoint := func(w http.ResponseWriter, r *http.Request) {
		var buf bytes.Buffer
		buf.ReadFrom(r.Body)
		r.Body.Close()
		called <- buf.String()
	}

	http.HandleFunc("/", endpoint)
	go http.ListenAndServe("127.0.0.1:5000", nil)

	d := New(config)

	err = d.CreateTable()
	if err != nil {
		t.Fatal(err)
	}

	println("created table")

	done := make(chan struct{})
	go func() {
		if err := d.Run(); err != nil {
			t.Fatal(err)
		}
		close(done)
	}()

	<-d.NotifyReady()

	values := make(url.Values)
	values.Set("interval", strconv.Itoa(int(testInterval/time.Second)))
	values.Set("one-off", "1")

	scheduleURL := "http://" + config.Listen.Addr() + "/jobs/" + testPath + "/" + testBody
	req, err := http.NewRequest("PUT", scheduleURL, strings.NewReader(values.Encode()))
	if err != nil {
		t.Fatal(err)
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	var client http.Client
	resp, err := client.Do(req)
	if err != nil {
		t.Fatalf("Cannot schedule new job: %s", err.Error())
	}
	var buf bytes.Buffer
	buf.ReadFrom(resp.Body)
	if resp.StatusCode != 201 {
		t.Fatalf("Unexpected status code: %d, body: %q", resp.StatusCode, buf.String())
	}
	println("PUT response:", buf.String())

	println("scheduled job")

	select {
	case body := <-called:
		println("endpoint is called")
		if string(body) != testBody {
			t.Fatalf("Invalid body: %s", body)
		}
	case <-time.After(testInterval + testDelay):
		t.Fatal("timeout")
	}

	// Teardown
	if err := d.Shutdown(); err != nil {
		t.Fatal(err)
	}

	<-done

	// Cleanup
	db.Exec(drop_sql)
}
<commit_msg>update test<commit_after>package dalga

import (
	"bytes"
	"database/sql"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/cenkalti/dalga/vendor/github.com/go-sql-driver/mysql"
)

func init() {
	*debugging = true
}

var (
	testPath     = "testPath"
	testBody     = "testBody"
	testInterval = time.Duration(0)
	testOneOff   = "true"
	testDelay    = time.Second
)

func TestSchedule(t *testing.T) {
	config := DefaultConfig

	db, err := sql.Open("mysql", config.MySQL.DSN())
	if err != nil {
		t.Errorf(err.Error())
		return
	}

	defer db.Close()

	err = db.Ping()
	if err != nil {
		t.Fatalf("cannot connect to MySQL: %s", err.Error())
	}

	println("connected to db")

	drop_sql := "DROP TABLE " + config.MySQL.Table
	_, err = db.Exec(drop_sql)
	if err != nil {
		if myErr, ok := err.(*mysql.MySQLError); !ok || myErr.Number != 1051 { // Unknown table
			t.Fatal(err)
		}
	}

	println("dropped table")

	called := make(chan string)
	endpoint := func(w http.ResponseWriter, r *http.Request) {
		var buf bytes.Buffer
		buf.ReadFrom(r.Body)
		r.Body.Close()
		called <- buf.String()
	}

	http.HandleFunc("/", endpoint)
	go http.ListenAndServe("127.0.0.1:5000", nil)

	d := New(config)

	err = d.CreateTable()
	if err != nil {
		t.Fatal(err)
	}

	println("created table")

	done := make(chan struct{})
	go func() {
		if err := d.Run(); err != nil {
			t.Fatal(err)
		}
		close(done)
	}()

	<-d.NotifyReady()

	values := make(url.Values)
	values.Set("interval", strconv.Itoa(int(testInterval/time.Second)))
	values.Set("one-off", testOneOff)

	scheduleURL := "http://" + config.Listen.Addr() + "/jobs/" + testPath + "/" + testBody
	req, err := http.NewRequest("PUT", scheduleURL, strings.NewReader(values.Encode()))
	if err != nil {
		t.Fatal(err)
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	var client http.Client
	resp, err := client.Do(req)
	if err != nil {
		t.Fatalf("Cannot schedule new job: %s", err.Error())
	}
	var buf bytes.Buffer
	buf.ReadFrom(resp.Body)
	if resp.StatusCode != 201 {
		t.Fatalf("Unexpected status code: %d, body: %q", resp.StatusCode, buf.String())
	}
	println("PUT response:", buf.String())

	println("scheduled job")

	select {
	case body := <-called:
		println("endpoint is called")
		if string(body) != testBody {
			t.Fatalf("Invalid body: %s", body)
		}
	case <-time.After(testInterval + testDelay):
		t.Fatal("timeout")
	}

	// Teardown
	if err := d.Shutdown(); err != nil {
		t.Fatal(err)
	}

	<-done

	// Cleanup
	db.Exec(drop_sql)
}
<|endoftext|>


<commit_before>package controllers

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"github.com/acsellers/platform/router"
)

type AssetModule struct {
	AssetLocation string
	MaxAge        time.Duration
}

func (am AssetModule) Load(sr *router.SubRoute) {
	files, err := ioutil.ReadDir(am.AssetLocation)
	if err != nil {
		return
	}
	for _, file := range files {
		if file.IsDir() {
			sr.Many(AssetController{
				BaseController: &router.BaseController{},
				Location:       file.Name(),
				CtrlPath:       filepath.Join(am.AssetLocation, file.Name()),
				MaxAge:         am.MaxAge,
			})
		} else {
			fmt.Println(file.Name())
		}
	}
}

type AssetController struct {
	*router.BaseController
	Location string
	CtrlPath string
	MaxAge   time.Duration
}

func (ac AssetController) Path() string {
	return ac.Location
}

func (ac AssetController) Show() router.Result {
	fn := filepath.Join(ac.CtrlPath, ac.Params[":"+ac.Location+"id"])
	_, err := os.Stat(fn)
	if err != nil {
		return router.NotFound{}
	}
	if ac.MaxAge.Seconds() != 0.0 {
		ac.Out.Header().Set(
			"Cache-Control",
			fmt.Sprintf("max-age=%.f", ac.MaxAge.Seconds()),
		)
	}
	http.ServeFile(ac.Out, ac.Request, fn)
	return router.NothingResult{}
}
<commit_msg>Patch the AssetController for the fixed API<commit_after>package controllers

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"github.com/acsellers/platform/router"
)

type AssetModule struct {
	AssetLocation string
	MaxAge        time.Duration
}

func (am AssetModule) Load(sr *router.SubRoute) {
	files, err := ioutil.ReadDir(am.AssetLocation)
	if err != nil {
		return
	}
	for _, file := range files {
		if file.IsDir() {
			sr.Many(AssetController{
				BaseController: &router.BaseController{},
				Location:       file.Name(),
				CtrlPath:       filepath.Join(am.AssetLocation, file.Name()),
				MaxAge:         am.MaxAge,
			})
		} else {
			fmt.Println(file.Name())
		}
	}
}

type AssetController struct {
	*router.BaseController
	Location string
	CtrlPath string
	MaxAge   time.Duration
}

func (ac AssetController) Path() string {
	return ac.Location
}

func (ac AssetController) Show() router.Result {
	fn := filepath.Join(ac.CtrlPath, ac.Params[":"+ac.Location+"id"])
	_, err := os.Stat(fn)
	if err != nil {
		return router.NotFound{}
	}
	if ac.MaxAge.Seconds() != 0.0 {
		ac.ResponseWriter.Header().Set(
			"Cache-Control",
			fmt.Sprintf("max-age=%.f", ac.MaxAge.Seconds()),
		)
	}
	http.ServeFile(ac.ResponseWriter, ac.Request, fn)
	return router.NothingResult{}
}
<|endoftext|>


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

import (
	"os"
	"regexp"
	"strings"

	"github.com/astaxie/beego"
	"github.com/unknwon/gowalker/models"
	"github.com/unknwon/gowalker/utils"
)

type SearchController struct {
	beego.Controller
}

func (this *SearchController) Get() {
	// Get query field
	q := this.Input().Get("q")

	// Empty query string shows home page
	if len(q) == 0 {
		this.Redirect("/", 302)
	}

	// Set properties
	this.TplNames = "search.html"
	this.Layout = "layout.html"

	// Check if it is a browse URL, if not means it's a keyword or import path
	if path, ok := utils.IsBrowseURL(q); ok {
		q = path
	}

	// Check if it is a remote path, if not means it's a keyword
	if utils.IsValidRemotePath(q) {
		// Check documentation of this import path, and update automatically as needed

		/* TODO:WORKING */
		os.Remove("./docs/" + strings.Replace(q, "http://", "", 1) + ".html")
		pdoc, err := models.CheckDoc(q, models.HUMAN_REQUEST)
		q = strings.Replace(q, "http://", "", 1)
		if err == nil {
			// Generate static page

			/* TODO */

			if generatePage(this, pdoc, q) {
				// Redirect to documentation page
				this.Redirect("/"+q+".html", 302)
			}
		} else {
			beego.Error("SearchController.Get:", err)
		}
	}

	// Search packages by the keyword
	this.Data["keyword"] = q
	// Returns a slice of results

	/* TODO */

	pkgs := models.SearchDoc(q)
	// Show results after searched
	if len(pkgs) > 0 {
		this.Data["showpkg"] = true
		this.Data["pkgs"] = pkgs
	}
}

var (
	urlPattern = regexp.MustCompile(`[a-zA-z]+://[^\s]*`)
)

func generatePage(this *SearchController, pdoc *models.Package, q string) bool {
	if pdoc == nil || len(pdoc.Name) == 0 {
		return utils.IsExist("./docs/" + q + ".html")
	}

	// Set properties
	this.TplNames = "docs.html"

	// Set data
	// Introduction
	this.Data["proPath"] = pdoc.BrowseURL
	this.Data["proName"] = pdoc.Name
	this.Data["pkgDocPath"] = pdoc.BrowseURL[7:strings.Index(pdoc.BrowseURL, pdoc.Name)]
	this.Data["importPath"] = pdoc.ImportPath
	this.Data["pkgIntro"] = pdoc.Synopsis
	synIndex := strings.Index(pdoc.Doc, ".")
	refIndex := strings.Index(pdoc.Doc, "Ref")
	if refIndex > -1 {
		this.Data["isHasRef"] = true
		this.Data["pkgRefs"] = urlPattern.FindAllString(pdoc.Doc[refIndex:], -1)
	} else {
		refIndex = len(pdoc.Doc)
	}
	this.Data["pkgFullIntro"] = strings.TrimSpace(pdoc.Doc[synIndex+1 : refIndex])

	// Index
	this.Data["isHasConst"] = len(pdoc.Consts) > 0
	this.Data["isHasVar"] = len(pdoc.Vars) > 0
	this.Data["funcs"] = pdoc.Funcs
	this.Data["types"] = pdoc.Types

	// Constants
	this.Data["consts"] = pdoc.Consts
	// Variables
	this.Data["vars"] = pdoc.Vars
	// Files
	this.Data["files"] = pdoc.Files

	// Create directories
	os.MkdirAll("./docs/"+q[:strings.LastIndex(q, "/")+1], os.ModePerm)
	// Create file
	f, _ := os.Create("./docs/" + q + ".html")
	// Render content
	s, _ := this.RenderString()
	f.WriteString(s)
	f.Close()
	return true
}
<commit_msg>clean<commit_after>// Copyright 2013 Unknown
//
// 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 controllers

import (
	"os"
	"regexp"
	"strings"

	"github.com/astaxie/beego"
	"github.com/unknwon/gowalker/models"
	"github.com/unknwon/gowalker/utils"
)

type SearchController struct {
	beego.Controller
}

func (this *SearchController) Get() {
	// Get query field
	q := this.Input().Get("q")

	// Empty query string shows home page
	if len(q) == 0 {
		this.Redirect("/", 302)
	}

	// Set properties
	this.TplNames = "search.html"
	this.Layout = "layout.html"

	// Check if it is a browse URL, if not means it's a keyword or import path
	if path, ok := utils.IsBrowseURL(q); ok {
		q = path
	}

	// Check if it is a remote path, if not means it's a keyword
	if utils.IsValidRemotePath(q) {
		// Check documentation of this import path, and update automatically as needed

		/* TODO:WORKING */
		//os.Remove("./docs/" + strings.Replace(q, "http://", "", 1) + ".html")
		pdoc, err := models.CheckDoc(q, models.HUMAN_REQUEST)
		q = strings.Replace(q, "http://", "", 1)
		if err == nil {
			// Generate static page

			/* TODO */

			if generatePage(this, pdoc, q) {
				// Redirect to documentation page
				this.Redirect("/"+q+".html", 302)
			}
		} else {
			beego.Error("SearchController.Get:", err)
		}
	}

	// Search packages by the keyword
	this.Data["keyword"] = q
	// Returns a slice of results

	/* TODO */

	pkgs := models.SearchDoc(q)
	// Show results after searched
	if len(pkgs) > 0 {
		this.Data["showpkg"] = true
		this.Data["pkgs"] = pkgs
	}
}

var (
	urlPattern = regexp.MustCompile(`[a-zA-z]+://[^\s]*`)
)

func generatePage(this *SearchController, pdoc *models.Package, q string) bool {
	if pdoc == nil || len(pdoc.Name) == 0 {
		return utils.IsExist("./docs/" + q + ".html")
	}

	// Set properties
	this.TplNames = "docs.html"

	// Set data
	// Introduction
	this.Data["proPath"] = pdoc.BrowseURL
	this.Data["proName"] = pdoc.Name
	this.Data["pkgDocPath"] = pdoc.BrowseURL[7:strings.Index(pdoc.BrowseURL, pdoc.Name)]
	this.Data["importPath"] = pdoc.ImportPath
	this.Data["pkgIntro"] = pdoc.Synopsis
	synIndex := strings.Index(pdoc.Doc, ".")
	refIndex := strings.Index(pdoc.Doc, "Ref")
	if refIndex > -1 {
		this.Data["isHasRef"] = true
		this.Data["pkgRefs"] = urlPattern.FindAllString(pdoc.Doc[refIndex:], -1)
	} else {
		refIndex = len(pdoc.Doc)
	}
	this.Data["pkgFullIntro"] = strings.TrimSpace(pdoc.Doc[synIndex+1 : refIndex])

	// Index
	this.Data["isHasConst"] = len(pdoc.Consts) > 0
	this.Data["isHasVar"] = len(pdoc.Vars) > 0
	this.Data["funcs"] = pdoc.Funcs
	this.Data["types"] = pdoc.Types

	// Constants
	this.Data["consts"] = pdoc.Consts
	// Variables
	this.Data["vars"] = pdoc.Vars
	// Files
	this.Data["files"] = pdoc.Files

	// Create directories
	os.MkdirAll("./docs/"+q[:strings.LastIndex(q, "/")+1], os.ModePerm)
	// Create file
	f, _ := os.Create("./docs/" + q + ".html")
	// Render content
	s, _ := this.RenderString()
	f.WriteString(s)
	f.Close()
	return true
}
<|endoftext|>


<commit_before>package toolkits

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/bitrise-io/bitrise/configs"
	"github.com/bitrise-io/bitrise/models"
	"github.com/bitrise-io/bitrise/tools"
	"github.com/bitrise-io/bitrise/utils"
	"github.com/bitrise-io/go-utils/cmdex"
	"github.com/bitrise-io/go-utils/pathutil"
	"github.com/bitrise-io/go-utils/progress"
	"github.com/bitrise-io/go-utils/retry"
	"github.com/bitrise-io/go-utils/versions"
	stepmanModels "github.com/bitrise-io/stepman/models"
	"github.com/bitrise-tools/gows/gows"
)

const (
	minGoVersionForToolkit = "1.7.1"
)

// === Base Toolkit struct ===

// GoToolkit ...
type GoToolkit struct {
}

// ToolkitName ...
func (toolkit GoToolkit) ToolkitName() string {
	return "go"
}

// === Toolkit: Check ===

// GoConfigurationModel ...
type GoConfigurationModel struct {
	// full path of the go binary to use
	GoBinaryPath string
	// GOROOT env var value to set (unless empty)
	GOROOT string
}

func checkGoConfiguration(goConfig GoConfigurationModel) (bool, ToolkitCheckResult, error) {
	cmdEnvs := os.Environ()
	if len(goConfig.GOROOT) > 0 {
		cmdEnvs = append(cmdEnvs, "GOROOT="+goConfig.GOROOT)
	}
	verOut, err := cmdex.NewCommand(goConfig.GoBinaryPath, "version").SetEnvs(cmdEnvs).RunAndReturnTrimmedOutput()
	if err != nil {
		return false, ToolkitCheckResult{}, fmt.Errorf("Failed to check go version, error: %s", err)
	}

	verStr, err := parseGoVersionFromGoVersionOutput(verOut)
	if err != nil {
		return false, ToolkitCheckResult{}, fmt.Errorf("Failed to parse go version, error: %s", err)
	}

	checkRes := ToolkitCheckResult{
		Path:    goConfig.GoBinaryPath,
		Version: verStr,
	}

	// version check
	isVersionOk, err := versions.IsVersionGreaterOrEqual(verStr, minGoVersionForToolkit)
	if err != nil {
		return false, checkRes, fmt.Errorf("Failed to validate installed go version, error: %s", err)
	}
	if !isVersionOk {
		return true, checkRes, nil
	}

	return false, checkRes, nil
}

func selectGoConfiguration() (bool, ToolkitCheckResult, GoConfigurationModel, error) {
	potentialGoConfigurations := []GoConfigurationModel{}
	// from PATH
	{
		binPath, err := utils.CheckProgramInstalledPath("go")
		if err == nil {
			potentialGoConfigurations = append(potentialGoConfigurations, GoConfigurationModel{GoBinaryPath: binPath})
		}
	}
	// from Bitrise Toolkits
	{
		binPath := goBinaryInToolkitFullPath()
		if isExist, err := pathutil.IsPathExists(binPath); err != nil {
			log.Warnf("Failed to check the status of the 'go' binary inside the Bitrise Toolkit dir, error: %s", err)
		} else if isExist {
			potentialGoConfigurations = append(potentialGoConfigurations, GoConfigurationModel{
				GoBinaryPath: binPath,
				GOROOT:       goToolkitInstallRootPath(),
			})
		}
	}

	isRequireInstall := true
	checkResult := ToolkitCheckResult{}
	goConfig := GoConfigurationModel{}
	var checkError error
	for _, aPotentialGoInfoToUse := range potentialGoConfigurations {
		isInstReq, chkRes, err := checkGoConfiguration(aPotentialGoInfoToUse)
		checkResult = chkRes
		checkError = err
		if !isInstReq {
			// select this one
			goConfig = aPotentialGoInfoToUse
			isRequireInstall = false
			break
		}
	}

	if len(potentialGoConfigurations) > 0 && isRequireInstall {
		log.Warnf("Installed go found (path: %s), but not a supported version: %s", checkResult.Path, checkResult.Version)
	}

	return isRequireInstall, checkResult, goConfig, checkError
}

// Check ...
func (toolkit GoToolkit) Check() (bool, ToolkitCheckResult, error) {
	isInstallRequired, checkResult, _, err := selectGoConfiguration()
	return isInstallRequired, checkResult, err
}

func parseGoVersionFromGoVersionOutput(goVersionCallOutput string) (string, error) {
	origGoVersionCallOutput := goVersionCallOutput
	goVersionCallOutput = strings.TrimSpace(goVersionCallOutput)
	if goVersionCallOutput == "" {
		return "", errors.New("Failed to parse Go version, error: version call output was empty")
	}

	// example goVersionCallOutput: go version go1.7 darwin/amd64
	goVerExp := regexp.MustCompile(`go version go(?P<goVersionNumber>[0-9.]+) (?P<platform>[a-zA-Z0-9]+/[a-zA-Z0-9]+)`)
	expRes := goVerExp.FindStringSubmatch(goVersionCallOutput)
	if expRes == nil {
		return "", fmt.Errorf("Failed to parse Go version, error: failed to find version in input: %s", origGoVersionCallOutput)
	}
	verStr := expRes[1]

	return verStr, nil
}

// IsToolAvailableInPATH ...
func (toolkit GoToolkit) IsToolAvailableInPATH() bool {
	if configs.IsDebugUseSystemTools() {
		log.Warn("[BitriseDebug] Using system tools (system installed Go), instead of the ones in BITRISE_HOME")
		return true
	}

	if _, err := utils.CheckProgramInstalledPath("go"); err != nil {
		return false
	}

	if _, err := cmdex.RunCommandAndReturnStdout("go", "version"); err != nil {
		return false
	}

	return true
}

// === Toolkit: Bootstrap ===

// Bootstrap ...
func (toolkit GoToolkit) Bootstrap() error {
	if toolkit.IsToolAvailableInPATH() {
		return nil
	}

	pthWithGoBins := configs.GeneratePATHEnvString(os.Getenv("PATH"), goToolkitBinsPath())
	if err := os.Setenv("PATH", pthWithGoBins); err != nil {
		return fmt.Errorf("Failed to set PATH to include the Go toolkit bins, error: %s", err)
	}

	if err := os.Setenv("GOROOT", goToolkitInstallRootPath()); err != nil {
		return fmt.Errorf("Failed to set GOROOT to Go toolkit root, error: %s", err)
	}

	return nil
}

// === Toolkit: Install ===

func installGoTar(goTarGzPath string) error {
	installToPath := goToolkitInstallToPath()

	if err := os.RemoveAll(installToPath); err != nil {
		return fmt.Errorf("Failed to remove previous Go toolkit install (path: %s), error: %s", installToPath, err)
	}
	if err := pathutil.EnsureDirExist(installToPath); err != nil {
		return fmt.Errorf("Failed create Go toolkit directory (path: %s), error: %s", installToPath, err)
	}

	cmd := cmdex.NewCommand("tar", "-C", installToPath, "-xzf", goTarGzPath)
	if combinedOut, err := cmd.RunAndReturnTrimmedCombinedOutput(); err != nil {
		log.Errorln(" [!] Failed to uncompress Go toolkit, output:")
		log.Errorln(combinedOut)
		return fmt.Errorf("Failed to uncompress Go toolkit, error: %s", err)
	}
	return nil
}

// Install ...
func (toolkit GoToolkit) Install() error {
	versionStr := minGoVersionForToolkit
	osStr := runtime.GOOS
	archStr := runtime.GOARCH
	extentionStr := "tar.gz"
	if osStr == "windows" {
		extentionStr = "zip"
	}
	downloadURL := fmt.Sprintf("https://storage.googleapis.com/golang/go%s.%s-%s.%s",
		versionStr, osStr, archStr, extentionStr)
	log.Debugln("downloadURL: ", downloadURL)

	goTmpDirPath := goToolkitTmpDirPath()
	if err := pathutil.EnsureDirExist(goTmpDirPath); err != nil {
		return fmt.Errorf("Failed to create Toolkits TMP directory, error: %s", err)
	}

	localFileName := "go." + extentionStr
	goArchiveDownloadPath := filepath.Join(goTmpDirPath, localFileName)

	var downloadErr error
	fmt.Print("=> Downloading ...")
	progress.SimpleProgress(".", 2*time.Second, func() {
		downloadErr = retry.Times(2).Wait(5 * time.Second).Try(func(attempt uint) error {
			if attempt > 0 {
				fmt.Println()
				fmt.Println("==> Download failed, retrying ...")
				fmt.Println()
			}
			return tools.DownloadFile(downloadURL, goArchiveDownloadPath)
		})
	})
	if downloadErr != nil {
		return fmt.Errorf("Failed to download toolkit (%s), error: %s", downloadURL, downloadErr)
	}
	log.Debugln("Toolkit downloaded to: ", goArchiveDownloadPath)

	fmt.Println("=> Installing ...")
	if err := installGoTar(goArchiveDownloadPath); err != nil {
		return fmt.Errorf("Failed to install Go toolkit, error: %s", err)
	}
	if err := os.Remove(goArchiveDownloadPath); err != nil {
		return fmt.Errorf("Failed to remove the downloaded Go archive (path: %s), error: %s", goArchiveDownloadPath, err)
	}
	fmt.Println("=> Installing [DONE]")

	return nil
}

// === Toolkit: Prepare for Step Run ===

func goBuildInIsolation(packageName, srcPath, outputBinPath string) error {
	log.Debugf("=> Installing package (%s) to path (%s) ...", packageName, srcPath)
	workspaceRootPath, err := pathutil.NormalizedOSTempDirPath("bitrise-go-toolkit")
	if err != nil {
		return fmt.Errorf("Failed to create root directory of isolated workspace, error: %s", err)
	}
	log.Debugln("=> Using sandboxed workspace:", workspaceRootPath)

	// origGOPATH := os.Getenv("GOPATH")
	// if origGOPATH == "" {
	// 	return fmt.Errorf("You don't have a GOPATH environment - please set it; GOPATH/bin will be symlinked")
	// }

	// log.Debugln("=> Symlink GOPATH/bin into sandbox ...")
	// if err := gows.CreateGopathBinSymlink(origGOPATH, workspaceRootPath); err != nil {
	// 	return fmt.Errorf("Failed to create GOPATH/bin symlink, error: %s", err)
	// }
	// log.Debugln("   [DONE]")

	fullPackageWorkspacePath := filepath.Join(workspaceRootPath, "src", packageName)
	log.Debugf("=> Creating Symlink: (%s) -> (%s)", srcPath, fullPackageWorkspacePath)
	if err := gows.CreateOrUpdateSymlink(srcPath, fullPackageWorkspacePath); err != nil {
		return fmt.Errorf("Failed to create Project->Workspace symlink, error: %s", err)
	}
	log.Debugf(" [DONE] Symlink is in place")

	log.Debugln("=> Building package " + packageName + " ...")
	{
		isInstallRequired, _, goConfig, err := selectGoConfiguration()
		if err != nil {
			return fmt.Errorf("Failed to select an appropriate Go installation for compiling the step, error: %s", err)
		}
		if isInstallRequired {
			return fmt.Errorf("Failed to select an appropriate Go installation for compiling the step, error: %s",
				"Found Go version is older than required. Please run 'bitrise setup' to check and install the required version")
		}

		cmd := gows.CreateCommand(workspaceRootPath, workspaceRootPath,
			goConfig.GoBinaryPath, "build", "-o", outputBinPath, packageName)
		cmd.Env = append(cmd.Env, "GOROOT="+goConfig.GOROOT)
		if err := cmd.Run(); err != nil {
			return fmt.Errorf("Failed to install package, error: %s", err)
		}
	}
	log.Debugln("   [DONE] Package successfully installed")

	log.Debugln("=> Delete isolated workspace ...")
	{
		if err := os.RemoveAll(workspaceRootPath); err != nil {
			return fmt.Errorf("Failed to delete temporary isolated workspace, error: %s", err)
		}
	}
	log.Debugln("   [DONE]")

	return nil
}

// stepIDorURI : doesn't work for "path::./" yet!!
func stepBinaryFilename(sIDData models.StepIDData) string {
	//
	replaceRexp, err := regexp.Compile("[^A-Za-z0-9.-]")
	if err != nil {
		log.Warn("Invalid regex, error: %s", err)
		return ""
	}

	compositeStepID := fmt.Sprintf("%s-%s-%s",
		sIDData.SteplibSource, sIDData.IDorURI, sIDData.Version)

	safeStepID := replaceRexp.ReplaceAllString(compositeStepID, "_")
	//
	return safeStepID
}

func stepBinaryCacheFullPath(sIDData models.StepIDData) string {
	return filepath.Join(goToolkitCacheRootPath(), stepBinaryFilename(sIDData))
}

// PrepareForStepRun ...
func (toolkit GoToolkit) PrepareForStepRun(step stepmanModels.StepModel, sIDData models.StepIDData, stepAbsDirPath string) error {
	fullStepBinPath := stepBinaryCacheFullPath(sIDData)

	// try to use cached binary, if possible
	if sIDData.IsUniqueResourceID() {
		if exists, err := pathutil.IsPathExists(fullStepBinPath); err != nil {
			log.Warn("Failed to check cached binary for step, error: %s", err)
		} else if exists {
			log.Debugln("No need to compile, binary already exists")
			return nil
		}
	}

	// it's not cached, so compile it

	if step.Toolkit == nil {
		return errors.New("No Toolkit information specified in step!")
	}
	if step.Toolkit.Go == nil {
		return errors.New("No Toolkit.Go information specified in step!")
	}
	packageName := step.Toolkit.Go.PackageName

	return goBuildInIsolation(packageName, stepAbsDirPath, fullStepBinPath)
}

// === Toolkit: Step Run ===

// StepRunCommandArguments ...
func (toolkit GoToolkit) StepRunCommandArguments(step stepmanModels.StepModel, sIDData models.StepIDData, stepAbsDirPath string) ([]string, error) {
	fullStepBinPath := stepBinaryCacheFullPath(sIDData)
	return []string{fullStepBinPath}, nil
}

// === Toolkit path utility function ===

func goToolkitRootPath() string {
	return filepath.Join(configs.GetBitriseToolkitsDirPath(), "go")
}

func goToolkitTmpDirPath() string {
	return filepath.Join(goToolkitRootPath(), "tmp")
}

func goToolkitInstallToPath() string {
	return filepath.Join(goToolkitRootPath(), "inst")
}
func goToolkitCacheRootPath() string {
	return filepath.Join(goToolkitRootPath(), "cache")
}

func goToolkitInstallRootPath() string {
	return filepath.Join(goToolkitInstallToPath(), "go")
}

func goToolkitBinsPath() string {
	return filepath.Join(goToolkitInstallRootPath(), "bin")
}

func goBinaryInToolkitFullPath() string {
	return filepath.Join(goToolkitBinsPath(), "go")
}
<commit_msg>bumped Go version for toolkit to 1.7.3 (#428)<commit_after>package toolkits

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/bitrise-io/bitrise/configs"
	"github.com/bitrise-io/bitrise/models"
	"github.com/bitrise-io/bitrise/tools"
	"github.com/bitrise-io/bitrise/utils"
	"github.com/bitrise-io/go-utils/cmdex"
	"github.com/bitrise-io/go-utils/pathutil"
	"github.com/bitrise-io/go-utils/progress"
	"github.com/bitrise-io/go-utils/retry"
	"github.com/bitrise-io/go-utils/versions"
	stepmanModels "github.com/bitrise-io/stepman/models"
	"github.com/bitrise-tools/gows/gows"
)

const (
	minGoVersionForToolkit = "1.7.3"
)

// === Base Toolkit struct ===

// GoToolkit ...
type GoToolkit struct {
}

// ToolkitName ...
func (toolkit GoToolkit) ToolkitName() string {
	return "go"
}

// === Toolkit: Check ===

// GoConfigurationModel ...
type GoConfigurationModel struct {
	// full path of the go binary to use
	GoBinaryPath string
	// GOROOT env var value to set (unless empty)
	GOROOT string
}

func checkGoConfiguration(goConfig GoConfigurationModel) (bool, ToolkitCheckResult, error) {
	cmdEnvs := os.Environ()
	if len(goConfig.GOROOT) > 0 {
		cmdEnvs = append(cmdEnvs, "GOROOT="+goConfig.GOROOT)
	}
	verOut, err := cmdex.NewCommand(goConfig.GoBinaryPath, "version").SetEnvs(cmdEnvs).RunAndReturnTrimmedOutput()
	if err != nil {
		return false, ToolkitCheckResult{}, fmt.Errorf("Failed to check go version, error: %s", err)
	}

	verStr, err := parseGoVersionFromGoVersionOutput(verOut)
	if err != nil {
		return false, ToolkitCheckResult{}, fmt.Errorf("Failed to parse go version, error: %s", err)
	}

	checkRes := ToolkitCheckResult{
		Path:    goConfig.GoBinaryPath,
		Version: verStr,
	}

	// version check
	isVersionOk, err := versions.IsVersionGreaterOrEqual(verStr, minGoVersionForToolkit)
	if err != nil {
		return false, checkRes, fmt.Errorf("Failed to validate installed go version, error: %s", err)
	}
	if !isVersionOk {
		return true, checkRes, nil
	}

	return false, checkRes, nil
}

func selectGoConfiguration() (bool, ToolkitCheckResult, GoConfigurationModel, error) {
	potentialGoConfigurations := []GoConfigurationModel{}
	// from PATH
	{
		binPath, err := utils.CheckProgramInstalledPath("go")
		if err == nil {
			potentialGoConfigurations = append(potentialGoConfigurations, GoConfigurationModel{GoBinaryPath: binPath})
		}
	}
	// from Bitrise Toolkits
	{
		binPath := goBinaryInToolkitFullPath()
		if isExist, err := pathutil.IsPathExists(binPath); err != nil {
			log.Warnf("Failed to check the status of the 'go' binary inside the Bitrise Toolkit dir, error: %s", err)
		} else if isExist {
			potentialGoConfigurations = append(potentialGoConfigurations, GoConfigurationModel{
				GoBinaryPath: binPath,
				GOROOT:       goToolkitInstallRootPath(),
			})
		}
	}

	isRequireInstall := true
	checkResult := ToolkitCheckResult{}
	goConfig := GoConfigurationModel{}
	var checkError error
	for _, aPotentialGoInfoToUse := range potentialGoConfigurations {
		isInstReq, chkRes, err := checkGoConfiguration(aPotentialGoInfoToUse)
		checkResult = chkRes
		checkError = err
		if !isInstReq {
			// select this one
			goConfig = aPotentialGoInfoToUse
			isRequireInstall = false
			break
		}
	}

	if len(potentialGoConfigurations) > 0 && isRequireInstall {
		log.Warnf("Installed go found (path: %s), but not a supported version: %s", checkResult.Path, checkResult.Version)
	}

	return isRequireInstall, checkResult, goConfig, checkError
}

// Check ...
func (toolkit GoToolkit) Check() (bool, ToolkitCheckResult, error) {
	isInstallRequired, checkResult, _, err := selectGoConfiguration()
	return isInstallRequired, checkResult, err
}

func parseGoVersionFromGoVersionOutput(goVersionCallOutput string) (string, error) {
	origGoVersionCallOutput := goVersionCallOutput
	goVersionCallOutput = strings.TrimSpace(goVersionCallOutput)
	if goVersionCallOutput == "" {
		return "", errors.New("Failed to parse Go version, error: version call output was empty")
	}

	// example goVersionCallOutput: go version go1.7 darwin/amd64
	goVerExp := regexp.MustCompile(`go version go(?P<goVersionNumber>[0-9.]+) (?P<platform>[a-zA-Z0-9]+/[a-zA-Z0-9]+)`)
	expRes := goVerExp.FindStringSubmatch(goVersionCallOutput)
	if expRes == nil {
		return "", fmt.Errorf("Failed to parse Go version, error: failed to find version in input: %s", origGoVersionCallOutput)
	}
	verStr := expRes[1]

	return verStr, nil
}

// IsToolAvailableInPATH ...
func (toolkit GoToolkit) IsToolAvailableInPATH() bool {
	if configs.IsDebugUseSystemTools() {
		log.Warn("[BitriseDebug] Using system tools (system installed Go), instead of the ones in BITRISE_HOME")
		return true
	}

	if _, err := utils.CheckProgramInstalledPath("go"); err != nil {
		return false
	}

	if _, err := cmdex.RunCommandAndReturnStdout("go", "version"); err != nil {
		return false
	}

	return true
}

// === Toolkit: Bootstrap ===

// Bootstrap ...
func (toolkit GoToolkit) Bootstrap() error {
	if toolkit.IsToolAvailableInPATH() {
		return nil
	}

	pthWithGoBins := configs.GeneratePATHEnvString(os.Getenv("PATH"), goToolkitBinsPath())
	if err := os.Setenv("PATH", pthWithGoBins); err != nil {
		return fmt.Errorf("Failed to set PATH to include the Go toolkit bins, error: %s", err)
	}

	if err := os.Setenv("GOROOT", goToolkitInstallRootPath()); err != nil {
		return fmt.Errorf("Failed to set GOROOT to Go toolkit root, error: %s", err)
	}

	return nil
}

// === Toolkit: Install ===

func installGoTar(goTarGzPath string) error {
	installToPath := goToolkitInstallToPath()

	if err := os.RemoveAll(installToPath); err != nil {
		return fmt.Errorf("Failed to remove previous Go toolkit install (path: %s), error: %s", installToPath, err)
	}
	if err := pathutil.EnsureDirExist(installToPath); err != nil {
		return fmt.Errorf("Failed create Go toolkit directory (path: %s), error: %s", installToPath, err)
	}

	cmd := cmdex.NewCommand("tar", "-C", installToPath, "-xzf", goTarGzPath)
	if combinedOut, err := cmd.RunAndReturnTrimmedCombinedOutput(); err != nil {
		log.Errorln(" [!] Failed to uncompress Go toolkit, output:")
		log.Errorln(combinedOut)
		return fmt.Errorf("Failed to uncompress Go toolkit, error: %s", err)
	}
	return nil
}

// Install ...
func (toolkit GoToolkit) Install() error {
	versionStr := minGoVersionForToolkit
	osStr := runtime.GOOS
	archStr := runtime.GOARCH
	extentionStr := "tar.gz"
	if osStr == "windows" {
		extentionStr = "zip"
	}
	downloadURL := fmt.Sprintf("https://storage.googleapis.com/golang/go%s.%s-%s.%s",
		versionStr, osStr, archStr, extentionStr)
	log.Debugln("downloadURL: ", downloadURL)

	goTmpDirPath := goToolkitTmpDirPath()
	if err := pathutil.EnsureDirExist(goTmpDirPath); err != nil {
		return fmt.Errorf("Failed to create Toolkits TMP directory, error: %s", err)
	}

	localFileName := "go." + extentionStr
	goArchiveDownloadPath := filepath.Join(goTmpDirPath, localFileName)

	var downloadErr error
	fmt.Print("=> Downloading ...")
	progress.SimpleProgress(".", 2*time.Second, func() {
		downloadErr = retry.Times(2).Wait(5 * time.Second).Try(func(attempt uint) error {
			if attempt > 0 {
				fmt.Println()
				fmt.Println("==> Download failed, retrying ...")
				fmt.Println()
			}
			return tools.DownloadFile(downloadURL, goArchiveDownloadPath)
		})
	})
	if downloadErr != nil {
		return fmt.Errorf("Failed to download toolkit (%s), error: %s", downloadURL, downloadErr)
	}
	log.Debugln("Toolkit downloaded to: ", goArchiveDownloadPath)

	fmt.Println("=> Installing ...")
	if err := installGoTar(goArchiveDownloadPath); err != nil {
		return fmt.Errorf("Failed to install Go toolkit, error: %s", err)
	}
	if err := os.Remove(goArchiveDownloadPath); err != nil {
		return fmt.Errorf("Failed to remove the downloaded Go archive (path: %s), error: %s", goArchiveDownloadPath, err)
	}
	fmt.Println("=> Installing [DONE]")

	return nil
}

// === Toolkit: Prepare for Step Run ===

func goBuildInIsolation(packageName, srcPath, outputBinPath string) error {
	log.Debugf("=> Installing package (%s) to path (%s) ...", packageName, srcPath)
	workspaceRootPath, err := pathutil.NormalizedOSTempDirPath("bitrise-go-toolkit")
	if err != nil {
		return fmt.Errorf("Failed to create root directory of isolated workspace, error: %s", err)
	}
	log.Debugln("=> Using sandboxed workspace:", workspaceRootPath)

	// origGOPATH := os.Getenv("GOPATH")
	// if origGOPATH == "" {
	// 	return fmt.Errorf("You don't have a GOPATH environment - please set it; GOPATH/bin will be symlinked")
	// }

	// log.Debugln("=> Symlink GOPATH/bin into sandbox ...")
	// if err := gows.CreateGopathBinSymlink(origGOPATH, workspaceRootPath); err != nil {
	// 	return fmt.Errorf("Failed to create GOPATH/bin symlink, error: %s", err)
	// }
	// log.Debugln("   [DONE]")

	fullPackageWorkspacePath := filepath.Join(workspaceRootPath, "src", packageName)
	log.Debugf("=> Creating Symlink: (%s) -> (%s)", srcPath, fullPackageWorkspacePath)
	if err := gows.CreateOrUpdateSymlink(srcPath, fullPackageWorkspacePath); err != nil {
		return fmt.Errorf("Failed to create Project->Workspace symlink, error: %s", err)
	}
	log.Debugf(" [DONE] Symlink is in place")

	log.Debugln("=> Building package " + packageName + " ...")
	{
		isInstallRequired, _, goConfig, err := selectGoConfiguration()
		if err != nil {
			return fmt.Errorf("Failed to select an appropriate Go installation for compiling the step, error: %s", err)
		}
		if isInstallRequired {
			return fmt.Errorf("Failed to select an appropriate Go installation for compiling the step, error: %s",
				"Found Go version is older than required. Please run 'bitrise setup' to check and install the required version")
		}

		cmd := gows.CreateCommand(workspaceRootPath, workspaceRootPath,
			goConfig.GoBinaryPath, "build", "-o", outputBinPath, packageName)
		cmd.Env = append(cmd.Env, "GOROOT="+goConfig.GOROOT)
		if err := cmd.Run(); err != nil {
			return fmt.Errorf("Failed to install package, error: %s", err)
		}
	}
	log.Debugln("   [DONE] Package successfully installed")

	log.Debugln("=> Delete isolated workspace ...")
	{
		if err := os.RemoveAll(workspaceRootPath); err != nil {
			return fmt.Errorf("Failed to delete temporary isolated workspace, error: %s", err)
		}
	}
	log.Debugln("   [DONE]")

	return nil
}

// stepIDorURI : doesn't work for "path::./" yet!!
func stepBinaryFilename(sIDData models.StepIDData) string {
	//
	replaceRexp, err := regexp.Compile("[^A-Za-z0-9.-]")
	if err != nil {
		log.Warn("Invalid regex, error: %s", err)
		return ""
	}

	compositeStepID := fmt.Sprintf("%s-%s-%s",
		sIDData.SteplibSource, sIDData.IDorURI, sIDData.Version)

	safeStepID := replaceRexp.ReplaceAllString(compositeStepID, "_")
	//
	return safeStepID
}

func stepBinaryCacheFullPath(sIDData models.StepIDData) string {
	return filepath.Join(goToolkitCacheRootPath(), stepBinaryFilename(sIDData))
}

// PrepareForStepRun ...
func (toolkit GoToolkit) PrepareForStepRun(step stepmanModels.StepModel, sIDData models.StepIDData, stepAbsDirPath string) error {
	fullStepBinPath := stepBinaryCacheFullPath(sIDData)

	// try to use cached binary, if possible
	if sIDData.IsUniqueResourceID() {
		if exists, err := pathutil.IsPathExists(fullStepBinPath); err != nil {
			log.Warn("Failed to check cached binary for step, error: %s", err)
		} else if exists {
			log.Debugln("No need to compile, binary already exists")
			return nil
		}
	}

	// it's not cached, so compile it

	if step.Toolkit == nil {
		return errors.New("No Toolkit information specified in step!")
	}
	if step.Toolkit.Go == nil {
		return errors.New("No Toolkit.Go information specified in step!")
	}
	packageName := step.Toolkit.Go.PackageName

	return goBuildInIsolation(packageName, stepAbsDirPath, fullStepBinPath)
}

// === Toolkit: Step Run ===

// StepRunCommandArguments ...
func (toolkit GoToolkit) StepRunCommandArguments(step stepmanModels.StepModel, sIDData models.StepIDData, stepAbsDirPath string) ([]string, error) {
	fullStepBinPath := stepBinaryCacheFullPath(sIDData)
	return []string{fullStepBinPath}, nil
}

// === Toolkit path utility function ===

func goToolkitRootPath() string {
	return filepath.Join(configs.GetBitriseToolkitsDirPath(), "go")
}

func goToolkitTmpDirPath() string {
	return filepath.Join(goToolkitRootPath(), "tmp")
}

func goToolkitInstallToPath() string {
	return filepath.Join(goToolkitRootPath(), "inst")
}
func goToolkitCacheRootPath() string {
	return filepath.Join(goToolkitRootPath(), "cache")
}

func goToolkitInstallRootPath() string {
	return filepath.Join(goToolkitInstallToPath(), "go")
}

func goToolkitBinsPath() string {
	return filepath.Join(goToolkitInstallRootPath(), "bin")
}

func goBinaryInToolkitFullPath() string {
	return filepath.Join(goToolkitBinsPath(), "go")
}
<|endoftext|>


<commit_before>// Package tuntap provides a portable interface to create and use
// TUN/TAP virtual network interfaces.
//
// Note that while this package lets you create the interface and pass
// packets to/from it, it does not provide an API to configure the
// interface. Interface configuration is a very large topic and should
// be dealt with separately.
package tuntap

import (
	"encoding/binary"
	"io"
	"net"
	"os"
	"sync"
	"unsafe"
)

type DevKind int

const (
	// Receive/send layer routable 3 packets (IP, IPv6...). Notably,
	// you don't receive link-local multicast with this interface
	// type.
	DevTun DevKind = iota
	// Receive/send Ethernet II frames. You receive all packets that
	// would be visible on an Ethernet link, including broadcast and
	// multicast traffic.
	DevTap
)

const (
	// various ethernet protocols, using the same names as linux does
	ETH_P_IP   uint16 = 0x0800
	ETH_P_IPV6 uint16 = 0x86dd
)

type Packet struct {
	// The raw bytes of the Ethernet payload (for DevTun) or the full
	// Ethernet frame (for DevTap).
	Body []byte
	// The Ethernet type of the packet. Commonly seen values are
	// 0x8000 for IPv4 and 0x86dd for IPv6.
	Protocol uint16
	// True if the packet was too large to be read completely.
	Truncated bool
}

type Interface struct {
	name string
	file *os.File
}

// Disconnect from the tun/tap interface.
//
// If the interface isn't configured to be persistent, it is
// immediately destroyed by the kernel.
func (t *Interface) Close() error {
	return t.file.Close()
}

// The name of the interface. May be different from the name given to
// Open(), if the latter was a pattern.
func (t *Interface) Name() string {
	return t.name
}

// Read a single packet from the kernel.
func (t *Interface) ReadPacket(buffer []byte) (Packet, error) {
	n, err := t.file.Read(buffer)
	if err != nil {
		return Packet{}, err
	}

	pkt := Packet{Body: buffer[4:n]}
	pkt.Protocol = binary.BigEndian.Uint16(buffer[2:4])
	flags := *(*uint16)(unsafe.Pointer(&buffer[0]))
	pkt.Truncated = (flags&flagTruncated != 0)
	return pkt, nil
}

// free 1600 byte buffers
var buffers = sync.Pool{New: func() interface{} { return new([1600]byte) }}

// Send a single packet to the kernel.
func (t *Interface) WritePacket(pkt Packet) error {
	// If only we had writev(), I could do zero-copy here...
	// At least we will manage the buffer so we don't cause the GC extra work
	buf := buffers.Get().(*[1600]byte)
	defer buffers.Put(buf)

	binary.BigEndian.PutUint16(buf[2:4], pkt.Protocol)
	copy(buf[4:], pkt.Body)
	n := 4 + len(pkt.Body)
	a, err := t.file.Write(buf[:n])
	if err != nil {
		return err
	}
	if a != n {
		return io.ErrShortWrite
	}
	return nil
}

// Open connects to the specified tun/tap interface.
//
// If the specified device has been configured as persistent, this
// simply looks like a "cable connected" event to observers of the
// interface. Otherwise, the interface is created out of thin air.
//
// ifPattern can be an exact interface name, e.g. "tun42", or a
// pattern containing one %d format specifier, e.g. "tun%d". In the
// latter case, the kernel will select an available interface name and
// create it.
//
// Returns a TunTap object with channels to send/receive packets, or
// nil and an error if connecting to the interface failed.
func Open(ifPattern string, kind DevKind) (*Interface, error) {
	file, err := os.OpenFile("/dev/net/tun", os.O_RDWR, 0)
	if err != nil {
		return nil, err
	}

	ifName, err := createInterface(file, ifPattern, kind)
	if err != nil {
		file.Close()
		return nil, err
	}

	return &Interface{ifName, file}, nil
}

// query parts of Packets
// NOTE: think whether this wouldn't be better done with a interface and two implemenations, one for each protocol

// return the destination IP
func (p *Packet) DIP() net.IP {
	switch p.Protocol {
	case ETH_P_IP:
		return net.IP(p.Body[16:20])
	case ETH_P_IPV6:
		return net.IP(p.Body[24:40])
	}
	return net.IP{}
}

// return the source IP
func (p *Packet) SIP() net.IP {
	switch p.Protocol {
	case ETH_P_IP:
		return net.IP(p.Body[12:16])
	case ETH_P_IPV6:
		return net.IP(p.Body[8:24])
	}
	return net.IP{}
}

// return the 6-bit DSCP field
func (p *Packet) DSCP() int {
	switch p.Protocol {
	case ETH_P_IP:
		return int(p.Body[1] >> 2)
	case ETH_P_IPV6:
		return int((p.Body[0]&0x0f)<<2 | (p.Body[1]&0xf0)>>6)
	}
	return 0
}

// return the IP protocol, the offset to the IP datagram payload, and true if the payload is from a non-first fragment
// returns 0,0,false if parsing fails or the IPv6 header 59 (no-next-header) is found
func (p *Packet) IPProto() (int, int, bool) {
	fragment := false
	switch p.Protocol {
	case ETH_P_IP:
		fragment = (p.Body[6]&0x1f)|p.Body[7] != 0
		return int(p.Body[9]), int(p.Body[0]&0xf) << 2, fragment
	case ETH_P_IPV6:
		// finding the IP protocol in the case of IPv6 is slightly messy. we have to scan down the IPv6 header chain and find the last one
		next := p.Body[6]
		at := 40
		for true {
			if at+4 > len(p.Body) {
				// off the end of the body. there must have been a garbage value somewhere
				return 0, 0, false
			}
			switch next {
			case 0, // hop-by-hop
				43, // routing extension
				60: // destination options extension
				// skip over this header and continue to the next one
				next = p.Body[at]
				at += 8 + int(p.Body[at+1])*8
			case 44: // fragment extension
				next = p.Body[at]
				at += 8
				fragment = p.Body[at+2]|(p.Body[at+3]&0xf8) != 0
			case 51: // AH header (it is likely that the next proto is ESP, but just in case it isn't we might as well decode it)
				next = p.Body[at]
				at += 8 + int(p.Body[at+1])*4 // note unlike most IPv6 headers the length of AH is in 4-byte units
			case 59: // no next header
				return 0, len(p.Body), fragment
			default:
				return int(next), at, fragment
			}
		}
	}
	return 0, 0, false
}

// returns ipproto, icmp type, icmp code, if this is an ICMP packet, or 0,_,_ if it isn't
func (p *Packet) ICMPType() (int, int, int) {
	proto, at, frag := p.IPProto()
	if !frag {
		switch proto {
		case 1: // IPv4 ICMP
			if at+4 <= len(p.Body) {
				return 1, int(p.Body[at]), int(p.Body[at+1])
			}
		case 58: // ICMP6
			// the header is identical in layout, but the values of the fields are very different
			if at+4 <= len(p.Body) {
				return 58, int(p.Body[at]), int(p.Body[at+1])
			}
		}
	}
	return 0, 0, 0
}
<commit_msg>Avoid defer in tight code<commit_after>// Package tuntap provides a portable interface to create and use
// TUN/TAP virtual network interfaces.
//
// Note that while this package lets you create the interface and pass
// packets to/from it, it does not provide an API to configure the
// interface. Interface configuration is a very large topic and should
// be dealt with separately.
package tuntap

import (
	"encoding/binary"
	"io"
	"net"
	"os"
	"sync"
	"unsafe"
)

type DevKind int

const (
	// Receive/send layer routable 3 packets (IP, IPv6...). Notably,
	// you don't receive link-local multicast with this interface
	// type.
	DevTun DevKind = iota
	// Receive/send Ethernet II frames. You receive all packets that
	// would be visible on an Ethernet link, including broadcast and
	// multicast traffic.
	DevTap
)

const (
	// various ethernet protocols, using the same names as linux does
	ETH_P_IP   uint16 = 0x0800
	ETH_P_IPV6 uint16 = 0x86dd
)

type Packet struct {
	// The raw bytes of the Ethernet payload (for DevTun) or the full
	// Ethernet frame (for DevTap).
	Body []byte
	// The Ethernet type of the packet. Commonly seen values are
	// 0x8000 for IPv4 and 0x86dd for IPv6.
	Protocol uint16
	// True if the packet was too large to be read completely.
	Truncated bool
}

type Interface struct {
	name string
	file *os.File
}

// Disconnect from the tun/tap interface.
//
// If the interface isn't configured to be persistent, it is
// immediately destroyed by the kernel.
func (t *Interface) Close() error {
	return t.file.Close()
}

// The name of the interface. May be different from the name given to
// Open(), if the latter was a pattern.
func (t *Interface) Name() string {
	return t.name
}

// Read a single packet from the kernel.
func (t *Interface) ReadPacket(buffer []byte) (Packet, error) {
	n, err := t.file.Read(buffer)
	if err != nil {
		return Packet{}, err
	}

	pkt := Packet{Body: buffer[4:n]}
	pkt.Protocol = binary.BigEndian.Uint16(buffer[2:4])
	flags := *(*uint16)(unsafe.Pointer(&buffer[0]))
	pkt.Truncated = (flags&flagTruncated != 0)
	return pkt, nil
}

// free 1600 byte buffers
var buffers = sync.Pool{New: func() interface{} { return new([1600]byte) }}

// Send a single packet to the kernel.
func (t *Interface) WritePacket(pkt Packet) error {
	// If only we had writev(), I could do zero-copy here...
	// At least we will manage the buffer so we don't cause the GC extra work
	buf := buffers.Get().(*[1600]byte)

	binary.BigEndian.PutUint16(buf[2:4], pkt.Protocol)
	copy(buf[4:], pkt.Body)
	n := 4 + len(pkt.Body)
	a, err := t.file.Write(buf[:n])
	buffers.Put(buf)
	if err != nil {
		return err
	}
	if a != n {
		return io.ErrShortWrite
	}
	return nil
}

// Open connects to the specified tun/tap interface.
//
// If the specified device has been configured as persistent, this
// simply looks like a "cable connected" event to observers of the
// interface. Otherwise, the interface is created out of thin air.
//
// ifPattern can be an exact interface name, e.g. "tun42", or a
// pattern containing one %d format specifier, e.g. "tun%d". In the
// latter case, the kernel will select an available interface name and
// create it.
//
// Returns a TunTap object with channels to send/receive packets, or
// nil and an error if connecting to the interface failed.
func Open(ifPattern string, kind DevKind) (*Interface, error) {
	file, err := os.OpenFile("/dev/net/tun", os.O_RDWR, 0)
	if err != nil {
		return nil, err
	}

	ifName, err := createInterface(file, ifPattern, kind)
	if err != nil {
		file.Close()
		return nil, err
	}

	return &Interface{ifName, file}, nil
}

// query parts of Packets
// NOTE: think whether this wouldn't be better done with a interface and two implemenations, one for each protocol

// return the destination IP
func (p *Packet) DIP() net.IP {
	switch p.Protocol {
	case ETH_P_IP:
		return net.IP(p.Body[16:20])
	case ETH_P_IPV6:
		return net.IP(p.Body[24:40])
	}
	return net.IP{}
}

// return the source IP
func (p *Packet) SIP() net.IP {
	switch p.Protocol {
	case ETH_P_IP:
		return net.IP(p.Body[12:16])
	case ETH_P_IPV6:
		return net.IP(p.Body[8:24])
	}
	return net.IP{}
}

// return the 6-bit DSCP field
func (p *Packet) DSCP() int {
	switch p.Protocol {
	case ETH_P_IP:
		return int(p.Body[1] >> 2)
	case ETH_P_IPV6:
		return int((p.Body[0]&0x0f)<<2 | (p.Body[1]&0xf0)>>6)
	}
	return 0
}

// return the IP protocol, the offset to the IP datagram payload, and true if the payload is from a non-first fragment
// returns 0,0,false if parsing fails or the IPv6 header 59 (no-next-header) is found
func (p *Packet) IPProto() (int, int, bool) {
	fragment := false
	switch p.Protocol {
	case ETH_P_IP:
		fragment = (p.Body[6]&0x1f)|p.Body[7] != 0
		return int(p.Body[9]), int(p.Body[0]&0xf) << 2, fragment
	case ETH_P_IPV6:
		// finding the IP protocol in the case of IPv6 is slightly messy. we have to scan down the IPv6 header chain and find the last one
		next := p.Body[6]
		at := 40
		for true {
			if at+4 > len(p.Body) {
				// off the end of the body. there must have been a garbage value somewhere
				return 0, 0, false
			}
			switch next {
			case 0, // hop-by-hop
				43, // routing extension
				60: // destination options extension
				// skip over this header and continue to the next one
				next = p.Body[at]
				at += 8 + int(p.Body[at+1])*8
			case 44: // fragment extension
				next = p.Body[at]
				at += 8
				fragment = p.Body[at+2]|(p.Body[at+3]&0xf8) != 0
			case 51: // AH header (it is likely that the next proto is ESP, but just in case it isn't we might as well decode it)
				next = p.Body[at]
				at += 8 + int(p.Body[at+1])*4 // note unlike most IPv6 headers the length of AH is in 4-byte units
			case 59: // no next header
				return 0, len(p.Body), fragment
			default:
				return int(next), at, fragment
			}
		}
	}
	return 0, 0, false
}

// returns ipproto, icmp type, icmp code, if this is an ICMP packet, or 0,_,_ if it isn't
func (p *Packet) ICMPType() (int, int, int) {
	proto, at, frag := p.IPProto()
	if !frag {
		switch proto {
		case 1: // IPv4 ICMP
			if at+4 <= len(p.Body) {
				return 1, int(p.Body[at]), int(p.Body[at+1])
			}
		case 58: // ICMP6
			// the header is identical in layout, but the values of the fields are very different
			if at+4 <= len(p.Body) {
				return 58, int(p.Body[at]), int(p.Body[at+1])
			}
		}
	}
	return 0, 0, 0
}
<|endoftext|>