<commit_before>package telegram

import (
	"encoding/json"
	"net/url"
	"strconv"
)

// BaseChat describes chat settings. It's an abstract type.
//
// You must set ID or ChannelUsername.
type BaseChat struct {
	// Unique identifier for the target chat
	ID int64
	// Username of the target channel (in the format @channelusername)
	ChannelUsername string
}

// Values returns RequiredError if neither ID or ChannelUsername are empty.
// Prefers ChannelUsername if both ID and ChannelUsername are not empty.
func (c BaseChat) Values() (url.Values, error) {
	v := url.Values{}
	if c.ChannelUsername != "" {
		v.Add("chat_id", c.ChannelUsername)
		return v, nil
	}
	if c.ID != 0 {
		v.Add("chat_id", strconv.FormatInt(c.ID, 10))
		return v, nil
	}
	return nil, NewRequiredError("ID", "ChannelUsername")
}

// GetChatCfg contains information about a getChat request.
type GetChatCfg struct {
	BaseChat
}

// Name returns method name
func (cfg GetChatCfg) Name() string {
	return getChatMethod
}

// Values returns a url.Values representation of GetChatCfg.
// Returns RequiredError if Chat is not set.
func (cfg GetChatCfg) Values() (url.Values, error) {
	return cfg.BaseChat.Values()
}

// GetChatAdministratorsCfg contains information about a getChat request.
type GetChatAdministratorsCfg struct {
	BaseChat
}

// Name returns method name
func (cfg GetChatAdministratorsCfg) Name() string {
	return getChatAdministratorsMethod
}

// Values returns a url.Values representation of GetChatCfg.
// Returns RequiredError if Chat is not set.
func (cfg GetChatAdministratorsCfg) Values() (url.Values, error) {
	return cfg.BaseChat.Values()
}

// GetChatMembersCountCfg contains information about a getChatMemberCount request.
type GetChatMembersCountCfg struct {
	BaseChat
}

// Name returns method name
func (cfg GetChatMembersCountCfg) Name() string {
	return getChatMembersCountMethod
}

// Values returns a url.Values representation of GetChatMembersCountCfg.
// Returns RequiredError if Chat is not set.
func (cfg GetChatMembersCountCfg) Values() (url.Values, error) {
	return cfg.BaseChat.Values()
}

// GetChatMemberCfg contains information about a getChatMember request.
type GetChatMemberCfg struct {
	BaseChat
	UserID int64 `json:"user_id"`
}

// Name returns method name
func (cfg GetChatMemberCfg) Name() string {
	return getChatMemberMethod
}

// Values returns a url.Values representation of GetChatMemberCfg.
// Returns RequiredError if Chat or UserID are not set.
func (cfg GetChatMemberCfg) Values() (url.Values, error) {
	v, err := cfg.BaseChat.Values()
	if err != nil {
		return nil, err
	}
	if cfg.UserID == 0 {
		return nil, NewRequiredError("UserID")
	}
	v.Add("user_id", strconv.FormatInt(cfg.UserID, 10))
	return v, nil
}

// KickChatMemberCfg contains information about a kickChatMember request.
type KickChatMemberCfg struct {
	BaseChat
	UserID int64 `json:"user_id"`
}

// Name returns method name
func (cfg KickChatMemberCfg) Name() string {
	return kickChatMemberMethod
}

// Values returns a url.Values representation of KickChatMemberCfg.
// Returns RequiredError if Chat or UserID are not set.
func (cfg KickChatMemberCfg) Values() (url.Values, error) {
	v, err := cfg.BaseChat.Values()
	if err != nil {
		return nil, err
	}
	if cfg.UserID == 0 {
		return nil, NewRequiredError("UserID")
	}
	v.Add("user_id", strconv.FormatInt(cfg.UserID, 10))
	return v, nil
}

// UnbanChatMemberCfg contains information about a unbanChatMember request.
type UnbanChatMemberCfg struct {
	BaseChat
	UserID int64 `json:"user_id"`
}

// Name returns method name
func (cfg UnbanChatMemberCfg) Name() string {
	return unbanChatMemberMethod
}

// Values returns a url.Values representation of UnbanChatMemberCfg.
// Returns RequiredError if Chat or UserID are not set.
func (cfg UnbanChatMemberCfg) Values() (url.Values, error) {
	v, err := cfg.BaseChat.Values()
	if err != nil {
		return nil, err
	}
	if cfg.UserID == 0 {
		return nil, NewRequiredError("UserID")
	}
	v.Add("user_id", strconv.FormatInt(cfg.UserID, 10))
	return v, nil
}

// LeaveChatCfg contains information about a leaveChat request.
type LeaveChatCfg struct {
	BaseChat
}

// Name returns method name
func (cfg LeaveChatCfg) Name() string {
	return leaveChatMethod
}

// Values returns a url.Values representation of LeaveChatCfg.
// Returns RequiredError if Chat is not set.
func (cfg LeaveChatCfg) Values() (url.Values, error) {
	return cfg.BaseChat.Values()
}

// MeCfg contains information about a getMe request.
type MeCfg struct{}

// Name returns method name
func (cfg MeCfg) Name() string {
	return getMeMethod
}

// Values for getMe is empty
func (cfg MeCfg) Values() (url.Values, error) {
	return nil, nil
}

// UpdateCfg contains information about a getUpdates request.
type UpdateCfg struct {
	// Identifier of the first update to be returned.
	// Must be greater by one than the highest
	// among the identifiers of previously received updates.
	// By default, updates starting with the earliest
	// unconfirmed update are returned. An update is considered confirmed
	// as soon as getUpdates is called with an offset
	// higher than its update_id. The negative offset
	// can be specified to retrieve updates starting
	// from -offset update from the end of the updates queue.
	// All previous updates will forgotten.
	Offset int64
	// Limits the number of updates to be retrieved.
	// Values between 1—100 are accepted. Defaults to 100.
	Limit int
	// Timeout in seconds for long polling.
	// Defaults to 0, i.e. usual short polling
	Timeout int
}

// Name returns method name
func (cfg UpdateCfg) Name() string {
	return getUpdatesMethod
}

// Values returns getUpdate params.
// It returns error if Limit is not between 0 and 100.
// Zero params are not included to request.
func (cfg UpdateCfg) Values() (url.Values, error) {
	if cfg.Limit < 0 || cfg.Limit > 100 {
		return nil, NewValidationError(
			"Limit",
			"should be between 1 and 100",
		)
	}
	v := url.Values{}
	if cfg.Offset != 0 {
		v.Add("offset", strconv.FormatInt(cfg.Offset, 10))
	}
	if cfg.Limit > 0 {
		v.Add("limit", strconv.Itoa(cfg.Limit))
	}
	if cfg.Timeout > 0 {
		v.Add("timeout", strconv.Itoa(cfg.Timeout))
	}
	return v, nil
}

// ChatActionCfg contains information about a SendChatAction request.
// Action field is required.
type ChatActionCfg struct {
	BaseChat
	// Type of action to broadcast.
	// Choose one, depending on what the user is about to receive:
	// typing for text messages, upload_photo for photos,
	// record_video or upload_video for videos,
	// record_audio or upload_audio for audio files,
	// upload_document for general files,
	// find_location for location data.
	// Use one of constants: ActionTyping, ActionFindLocation, etc
	Action string
}

// Name returns method name
func (cfg ChatActionCfg) Name() string {
	return sendChatActionMethod
}

// Values returns a url.Values representation of ChatActionCfg.
// Returns a RequiredError if Action is empty.
func (cfg ChatActionCfg) Values() (url.Values, error) {
	v, err := cfg.BaseChat.Values()
	if err != nil {
		return nil, err
	}
	if cfg.Action == "" {
		return nil, NewRequiredError("Action")
	}
	v.Add("action", cfg.Action)
	return v, nil
}

// UserProfilePhotosCfg contains information about a
// GetUserProfilePhotos request.
type UserProfilePhotosCfg struct {
	UserID int64
	// Sequential number of the first photo to be returned.
	// By default, all photos are returned.
	Offset int
	// Limits the number of photos to be retrieved.
	// Values between 1—100 are accepted. Defaults to 100.
	Limit int
}

// Name returns method name
func (cfg UserProfilePhotosCfg) Name() string {
	return getUserProfilePhotosMethod
}

// Values returns a url.Values representation of UserProfilePhotosCfg.
// Returns RequiredError if UserID is empty.
func (cfg UserProfilePhotosCfg) Values() (url.Values, error) {
	if cfg.Limit < 0 || cfg.Limit > 100 {
		return nil, NewValidationError(
			"Limit",
			"should be between 1 and 100",
		)
	}
	v := url.Values{}
	if cfg.UserID == 0 {
		return nil, NewRequiredError("UserID")
	}
	v.Add("user_id", strconv.FormatInt(cfg.UserID, 10))
	if cfg.Offset != 0 {
		v.Add("offset", strconv.Itoa(cfg.Offset))
	}
	if cfg.Limit != 0 {
		v.Add("limit", strconv.Itoa(cfg.Limit))
	}
	return v, nil
}

// FileCfg has information about a file hosted on Telegram.
type FileCfg struct {
	FileID string
}

// Name returns method name
func (cfg FileCfg) Name() string {
	return getFileMethod
}

// Values returns a url.Values representation of FileCfg.
func (cfg FileCfg) Values() (url.Values, error) {
	v := url.Values{}
	v.Add("file_id", cfg.FileID)
	return v, nil
}

// WebhookCfg contains information about a SetWebhook request.
// Implements Method and Filer interface
type WebhookCfg struct {
	URL string
	// self generated TLS certificate
	Certificate InputFile
}

// Name method returns Telegram API method name for sending Location.
func (cfg WebhookCfg) Name() string {
	return setWebhookMethod
}

// Values returns a url.Values representation of Webhook config.
func (cfg WebhookCfg) Values() (url.Values, error) {
	v := url.Values{}
	v.Add("url", cfg.URL)
	return v, nil
}

// Field returns name for webhook file data
func (cfg WebhookCfg) Field() string {
	return "certificate"
}

// File returns certificate data
func (cfg WebhookCfg) File() InputFile {
	return cfg.Certificate
}

// Exist is true if we don't have a certificate to upload.
// It's kind of confusing.
func (cfg WebhookCfg) Exist() bool {
	return cfg.Certificate == nil
}

// Reset method sets new Certificate
func (cfg *WebhookCfg) Reset(i InputFile) {
	cfg.Certificate = i
}

// GetFileID for webhook is always empty
func (cfg *WebhookCfg) GetFileID() string {
	return ""
}

// AnswerCallbackCfg contains information on making a anserCallbackQuery response.
type AnswerCallbackCfg struct {
	CallbackQueryID string `json:"callback_query_id"`
	Text            string `json:"text"`
	ShowAlert       bool   `json:"show_alert"`
}

// Name returns method name
func (cfg AnswerCallbackCfg) Name() string {
	return answerCallbackQueryMethod
}

// Values returns a url.Values representation of AnswerCallbackCfg.
// Returns a RequiredError if Action is empty.
func (cfg AnswerCallbackCfg) Values() (url.Values, error) {
	v := url.Values{}
	v.Add("callback_query_id", cfg.CallbackQueryID)
	v.Add("text", cfg.Text)
	v.Add("show_alert", strconv.FormatBool(cfg.ShowAlert))
	return v, nil
}

// AnswerInlineQueryCfg contains information on making an InlineQuery response.
type AnswerInlineQueryCfg struct {
	// Unique identifier for the answered query
	InlineQueryID string              `json:"inline_query_id"`
	Results       []InlineQueryResult `json:"results"`
	// The maximum amount of time in seconds
	// that the result of the inline query may be cached on the server.
	// Defaults to 300.
	CacheTime int `json:"cache_time,omitempty"`
	// Pass True, if results may be cached on the server side
	// only for the user that sent the query.
	// By default, results may be returned to any user
	// who sends the same query
	IsPersonal bool `json:"is_personal,omitempty"`
	// Pass the offset that a client should send in the next query
	// with the same text to receive more results.
	// Pass an empty string if there are no more results
	// or if you don‘t support pagination.
	// Offset length can’t exceed 64 bytes.
	NextOffset string `json:"next_offset,omitempty"`
	// If passed, clients will display a button with specified text
	// that switches the user to a private chat with the bot and
	// sends the bot a start message with the parameter switch_pm_parameter
	SwitchPMText string `json:"switch_pm_text,omitempty"`
	// Parameter for the start message sent to the bot
	// when user presses the switch button
	SwitchPMParameter string `json:"switch_pm_parameter"`
}

// Name returns method name
func (cfg AnswerInlineQueryCfg) Name() string {
	return answerInlineQueryMethod
}

// Values returns a url.Values representation of AnswerInlineQueryCfg.
// Returns a RequiredError if Action is empty.
func (cfg AnswerInlineQueryCfg) Values() (url.Values, error) {
	v := url.Values{}
	if cfg.Results == nil || len(cfg.Results) == 0 {
		return nil, NewRequiredError("Results")
	}
	data, err := json.Marshal(cfg.Results)
	if err != nil {
		return nil, err
	}
	v.Add("results", string(data))
	v.Add("inline_query_id", cfg.InlineQueryID)
	if cfg.CacheTime > 0 {
		v.Add("cache_time", strconv.Itoa(cfg.CacheTime))
	}
	if cfg.IsPersonal {
		v.Add("is_personal", strconv.FormatBool(cfg.IsPersonal))
	}
	if cfg.NextOffset != "" {
		v.Add("next_offset", cfg.NextOffset)
	}
	if cfg.SwitchPMText != "" {
		v.Add("switch_pm_text", cfg.SwitchPMText)
	}
	if cfg.SwitchPMParameter != "" {
		v.Add("switch_pm_parameter", cfg.SwitchPMParameter)
	}
	return v, nil
}
<commit_msg>Add helper to set ChatID<commit_after>package telegram

import (
	"encoding/json"
	"net/url"
	"strconv"
)

// BaseChat describes chat settings. It's an abstract type.
//
// You must set ID or ChannelUsername.
type BaseChat struct {
	// Unique identifier for the target chat
	ID int64
	// Username of the target channel (in the format @channelusername)
	ChannelUsername string
}

// Values returns RequiredError if neither ID or ChannelUsername are empty.
// Prefers ChannelUsername if both ID and ChannelUsername are not empty.
func (c BaseChat) Values() (url.Values, error) {
	v := url.Values{}
	if c.ChannelUsername != "" {
		v.Add("chat_id", c.ChannelUsername)
		return v, nil
	}
	if c.ID != 0 {
		v.Add("chat_id", strconv.FormatInt(c.ID, 10))
		return v, nil
	}
	return nil, NewRequiredError("ID", "ChannelUsername")
}

// SetChatID sets new chat id
func (c *BaseChat) SetChatID(id int64) {
	c.ID = id
}

// GetChatCfg contains information about a getChat request.
type GetChatCfg struct {
	BaseChat
}

// Name returns method name
func (cfg GetChatCfg) Name() string {
	return getChatMethod
}

// Values returns a url.Values representation of GetChatCfg.
// Returns RequiredError if Chat is not set.
func (cfg GetChatCfg) Values() (url.Values, error) {
	return cfg.BaseChat.Values()
}

// GetChatAdministratorsCfg contains information about a getChat request.
type GetChatAdministratorsCfg struct {
	BaseChat
}

// Name returns method name
func (cfg GetChatAdministratorsCfg) Name() string {
	return getChatAdministratorsMethod
}

// Values returns a url.Values representation of GetChatCfg.
// Returns RequiredError if Chat is not set.
func (cfg GetChatAdministratorsCfg) Values() (url.Values, error) {
	return cfg.BaseChat.Values()
}

// GetChatMembersCountCfg contains information about a getChatMemberCount request.
type GetChatMembersCountCfg struct {
	BaseChat
}

// Name returns method name
func (cfg GetChatMembersCountCfg) Name() string {
	return getChatMembersCountMethod
}

// Values returns a url.Values representation of GetChatMembersCountCfg.
// Returns RequiredError if Chat is not set.
func (cfg GetChatMembersCountCfg) Values() (url.Values, error) {
	return cfg.BaseChat.Values()
}

// GetChatMemberCfg contains information about a getChatMember request.
type GetChatMemberCfg struct {
	BaseChat
	UserID int64 `json:"user_id"`
}

// Name returns method name
func (cfg GetChatMemberCfg) Name() string {
	return getChatMemberMethod
}

// Values returns a url.Values representation of GetChatMemberCfg.
// Returns RequiredError if Chat or UserID are not set.
func (cfg GetChatMemberCfg) Values() (url.Values, error) {
	v, err := cfg.BaseChat.Values()
	if err != nil {
		return nil, err
	}
	if cfg.UserID == 0 {
		return nil, NewRequiredError("UserID")
	}
	v.Add("user_id", strconv.FormatInt(cfg.UserID, 10))
	return v, nil
}

// KickChatMemberCfg contains information about a kickChatMember request.
type KickChatMemberCfg struct {
	BaseChat
	UserID int64 `json:"user_id"`
}

// Name returns method name
func (cfg KickChatMemberCfg) Name() string {
	return kickChatMemberMethod
}

// Values returns a url.Values representation of KickChatMemberCfg.
// Returns RequiredError if Chat or UserID are not set.
func (cfg KickChatMemberCfg) Values() (url.Values, error) {
	v, err := cfg.BaseChat.Values()
	if err != nil {
		return nil, err
	}
	if cfg.UserID == 0 {
		return nil, NewRequiredError("UserID")
	}
	v.Add("user_id", strconv.FormatInt(cfg.UserID, 10))
	return v, nil
}

// UnbanChatMemberCfg contains information about a unbanChatMember request.
type UnbanChatMemberCfg struct {
	BaseChat
	UserID int64 `json:"user_id"`
}

// Name returns method name
func (cfg UnbanChatMemberCfg) Name() string {
	return unbanChatMemberMethod
}

// Values returns a url.Values representation of UnbanChatMemberCfg.
// Returns RequiredError if Chat or UserID are not set.
func (cfg UnbanChatMemberCfg) Values() (url.Values, error) {
	v, err := cfg.BaseChat.Values()
	if err != nil {
		return nil, err
	}
	if cfg.UserID == 0 {
		return nil, NewRequiredError("UserID")
	}
	v.Add("user_id", strconv.FormatInt(cfg.UserID, 10))
	return v, nil
}

// LeaveChatCfg contains information about a leaveChat request.
type LeaveChatCfg struct {
	BaseChat
}

// Name returns method name
func (cfg LeaveChatCfg) Name() string {
	return leaveChatMethod
}

// Values returns a url.Values representation of LeaveChatCfg.
// Returns RequiredError if Chat is not set.
func (cfg LeaveChatCfg) Values() (url.Values, error) {
	return cfg.BaseChat.Values()
}

// MeCfg contains information about a getMe request.
type MeCfg struct{}

// Name returns method name
func (cfg MeCfg) Name() string {
	return getMeMethod
}

// Values for getMe is empty
func (cfg MeCfg) Values() (url.Values, error) {
	return nil, nil
}

// UpdateCfg contains information about a getUpdates request.
type UpdateCfg struct {
	// Identifier of the first update to be returned.
	// Must be greater by one than the highest
	// among the identifiers of previously received updates.
	// By default, updates starting with the earliest
	// unconfirmed update are returned. An update is considered confirmed
	// as soon as getUpdates is called with an offset
	// higher than its update_id. The negative offset
	// can be specified to retrieve updates starting
	// from -offset update from the end of the updates queue.
	// All previous updates will forgotten.
	Offset int64
	// Limits the number of updates to be retrieved.
	// Values between 1—100 are accepted. Defaults to 100.
	Limit int
	// Timeout in seconds for long polling.
	// Defaults to 0, i.e. usual short polling
	Timeout int
}

// Name returns method name
func (cfg UpdateCfg) Name() string {
	return getUpdatesMethod
}

// Values returns getUpdate params.
// It returns error if Limit is not between 0 and 100.
// Zero params are not included to request.
func (cfg UpdateCfg) Values() (url.Values, error) {
	if cfg.Limit < 0 || cfg.Limit > 100 {
		return nil, NewValidationError(
			"Limit",
			"should be between 1 and 100",
		)
	}
	v := url.Values{}
	if cfg.Offset != 0 {
		v.Add("offset", strconv.FormatInt(cfg.Offset, 10))
	}
	if cfg.Limit > 0 {
		v.Add("limit", strconv.Itoa(cfg.Limit))
	}
	if cfg.Timeout > 0 {
		v.Add("timeout", strconv.Itoa(cfg.Timeout))
	}
	return v, nil
}

// ChatActionCfg contains information about a SendChatAction request.
// Action field is required.
type ChatActionCfg struct {
	BaseChat
	// Type of action to broadcast.
	// Choose one, depending on what the user is about to receive:
	// typing for text messages, upload_photo for photos,
	// record_video or upload_video for videos,
	// record_audio or upload_audio for audio files,
	// upload_document for general files,
	// find_location for location data.
	// Use one of constants: ActionTyping, ActionFindLocation, etc
	Action string
}

// Name returns method name
func (cfg ChatActionCfg) Name() string {
	return sendChatActionMethod
}

// Values returns a url.Values representation of ChatActionCfg.
// Returns a RequiredError if Action is empty.
func (cfg ChatActionCfg) Values() (url.Values, error) {
	v, err := cfg.BaseChat.Values()
	if err != nil {
		return nil, err
	}
	if cfg.Action == "" {
		return nil, NewRequiredError("Action")
	}
	v.Add("action", cfg.Action)
	return v, nil
}

// UserProfilePhotosCfg contains information about a
// GetUserProfilePhotos request.
type UserProfilePhotosCfg struct {
	UserID int64
	// Sequential number of the first photo to be returned.
	// By default, all photos are returned.
	Offset int
	// Limits the number of photos to be retrieved.
	// Values between 1—100 are accepted. Defaults to 100.
	Limit int
}

// Name returns method name
func (cfg UserProfilePhotosCfg) Name() string {
	return getUserProfilePhotosMethod
}

// Values returns a url.Values representation of UserProfilePhotosCfg.
// Returns RequiredError if UserID is empty.
func (cfg UserProfilePhotosCfg) Values() (url.Values, error) {
	if cfg.Limit < 0 || cfg.Limit > 100 {
		return nil, NewValidationError(
			"Limit",
			"should be between 1 and 100",
		)
	}
	v := url.Values{}
	if cfg.UserID == 0 {
		return nil, NewRequiredError("UserID")
	}
	v.Add("user_id", strconv.FormatInt(cfg.UserID, 10))
	if cfg.Offset != 0 {
		v.Add("offset", strconv.Itoa(cfg.Offset))
	}
	if cfg.Limit != 0 {
		v.Add("limit", strconv.Itoa(cfg.Limit))
	}
	return v, nil
}

// FileCfg has information about a file hosted on Telegram.
type FileCfg struct {
	FileID string
}

// Name returns method name
func (cfg FileCfg) Name() string {
	return getFileMethod
}

// Values returns a url.Values representation of FileCfg.
func (cfg FileCfg) Values() (url.Values, error) {
	v := url.Values{}
	v.Add("file_id", cfg.FileID)
	return v, nil
}

// WebhookCfg contains information about a SetWebhook request.
// Implements Method and Filer interface
type WebhookCfg struct {
	URL string
	// self generated TLS certificate
	Certificate InputFile
}

// Name method returns Telegram API method name for sending Location.
func (cfg WebhookCfg) Name() string {
	return setWebhookMethod
}

// Values returns a url.Values representation of Webhook config.
func (cfg WebhookCfg) Values() (url.Values, error) {
	v := url.Values{}
	v.Add("url", cfg.URL)
	return v, nil
}

// Field returns name for webhook file data
func (cfg WebhookCfg) Field() string {
	return "certificate"
}

// File returns certificate data
func (cfg WebhookCfg) File() InputFile {
	return cfg.Certificate
}

// Exist is true if we don't have a certificate to upload.
// It's kind of confusing.
func (cfg WebhookCfg) Exist() bool {
	return cfg.Certificate == nil
}

// Reset method sets new Certificate
func (cfg *WebhookCfg) Reset(i InputFile) {
	cfg.Certificate = i
}

// GetFileID for webhook is always empty
func (cfg *WebhookCfg) GetFileID() string {
	return ""
}

// AnswerCallbackCfg contains information on making a anserCallbackQuery response.
type AnswerCallbackCfg struct {
	CallbackQueryID string `json:"callback_query_id"`
	Text            string `json:"text"`
	ShowAlert       bool   `json:"show_alert"`
}

// Name returns method name
func (cfg AnswerCallbackCfg) Name() string {
	return answerCallbackQueryMethod
}

// Values returns a url.Values representation of AnswerCallbackCfg.
// Returns a RequiredError if Action is empty.
func (cfg AnswerCallbackCfg) Values() (url.Values, error) {
	v := url.Values{}
	v.Add("callback_query_id", cfg.CallbackQueryID)
	v.Add("text", cfg.Text)
	v.Add("show_alert", strconv.FormatBool(cfg.ShowAlert))
	return v, nil
}

// AnswerInlineQueryCfg contains information on making an InlineQuery response.
type AnswerInlineQueryCfg struct {
	// Unique identifier for the answered query
	InlineQueryID string              `json:"inline_query_id"`
	Results       []InlineQueryResult `json:"results"`
	// The maximum amount of time in seconds
	// that the result of the inline query may be cached on the server.
	// Defaults to 300.
	CacheTime int `json:"cache_time,omitempty"`
	// Pass True, if results may be cached on the server side
	// only for the user that sent the query.
	// By default, results may be returned to any user
	// who sends the same query
	IsPersonal bool `json:"is_personal,omitempty"`
	// Pass the offset that a client should send in the next query
	// with the same text to receive more results.
	// Pass an empty string if there are no more results
	// or if you don‘t support pagination.
	// Offset length can’t exceed 64 bytes.
	NextOffset string `json:"next_offset,omitempty"`
	// If passed, clients will display a button with specified text
	// that switches the user to a private chat with the bot and
	// sends the bot a start message with the parameter switch_pm_parameter
	SwitchPMText string `json:"switch_pm_text,omitempty"`
	// Parameter for the start message sent to the bot
	// when user presses the switch button
	SwitchPMParameter string `json:"switch_pm_parameter"`
}

// Name returns method name
func (cfg AnswerInlineQueryCfg) Name() string {
	return answerInlineQueryMethod
}

// Values returns a url.Values representation of AnswerInlineQueryCfg.
// Returns a RequiredError if Action is empty.
func (cfg AnswerInlineQueryCfg) Values() (url.Values, error) {
	v := url.Values{}
	if cfg.Results == nil || len(cfg.Results) == 0 {
		return nil, NewRequiredError("Results")
	}
	data, err := json.Marshal(cfg.Results)
	if err != nil {
		return nil, err
	}
	v.Add("results", string(data))
	v.Add("inline_query_id", cfg.InlineQueryID)
	if cfg.CacheTime > 0 {
		v.Add("cache_time", strconv.Itoa(cfg.CacheTime))
	}
	if cfg.IsPersonal {
		v.Add("is_personal", strconv.FormatBool(cfg.IsPersonal))
	}
	if cfg.NextOffset != "" {
		v.Add("next_offset", cfg.NextOffset)
	}
	if cfg.SwitchPMText != "" {
		v.Add("switch_pm_text", cfg.SwitchPMText)
	}
	if cfg.SwitchPMParameter != "" {
		v.Add("switch_pm_parameter", cfg.SwitchPMParameter)
	}
	return v, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/docker/docker/pkg/reexec"
	"github.com/mrosset/util/file"
	"gopkg.in/urfave/cli.v2"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"syscall"
)

const (
	BIND_RO = syscall.MS_BIND | syscall.MS_RDONLY | syscall.MS_REC
	BIND_RW = syscall.MS_BIND | syscall.MS_REC
)

var (
	viabin          = filepath.Join(os.Getenv("GOPATH"), "bin/via")
	containCommands = []*cli.Command{
		&cli.Command{
			Name:   "contain",
			Usage:  "enters a container",
			Action: contain,
		},
	}
)

func init() {
	reexec.Register("init", initialize)
	if reexec.Init() {
		os.Exit(0)
	}
	app.Commands = append(app.Commands, containCommands...)
}

func linksh(root string) error {
	var (
		source = filepath.Join(config.Prefix, "bin", "bash")
		bin    = filepath.Join(root, "bin")
		target = filepath.Join(bin, "sh")
	)
	if err := os.MkdirAll(bin, 0755); err != nil {
		return err
	}
	return os.Link(source, target)
}

func bindbin(root string) error {
	var (
		source = filepath.Join(config.Prefix, "bin")
		target = filepath.Join(root, "bin")
	)
	if err := os.MkdirAll(target, 0755); err != nil {
		return err
	}
	return syscall.Mount(
		source,
		target,
		"",
		BIND_RO,
		"",
	)
}

// instead of linking, bind sh to bash to avoid cross linking across
// devices
func bindsh(root string) error {
	var (
		source = filepath.Join(config.Prefix, "bin", "bash")
		bin    = filepath.Join(root, "bin")
		target = filepath.Join(bin, "sh")
	)
	if err := os.MkdirAll(bin, 0755); err != nil {
		return err
	}
	if err := file.Touch(target); err != nil {
		return err
	}
	return syscall.Mount(
		source,
		target,
		"",
		BIND_RO,
		"",
	)
}

func initialize() {
	root, err := ioutil.TempDir("", "via-build")
	if err != nil {
		elog.Fatal(err)
	}
	// set hostname
	if err := syscall.Sethostname([]byte("via-build")); err != nil {
		elog.Fatalf("could not set hostname: %s", err)
	}
	if err := os.MkdirAll(root, 0700); err != nil {
		elog.Fatal(err)
	}
	// setup all our mounts
	if err := mount(root); err != nil {
		elog.Fatal(err)
	}
	// bind
	if err := bindsh(root); err != nil {
		elog.Fatal(err)
	}
	// finally pivot our root
	if err := pivot(root); err != nil {
		elog.Fatal(err)
	}
	run()
}
func run() {
	var (
		path = "/bin/sh"
		args = []string{}
	)

	switch {
	case len(os.Args) >= 2 && os.Args[1] == "build":
		path = viabin
		args = append([]string{"via"}, os.Args[1:]...)
	case len(os.Args) >= 2 && os.Args[1] == "contain":
		path = "/bin/sh"
		args = []string{}
	default:
		elog.Fatalf("can not handle arguments %v", os.Args)
	}

	cmd := &exec.Cmd{
		Path:   path,
		Args:   args,
		Stdin:  os.Stdin,
		Stdout: os.Stdout,
		Stderr: os.Stderr,
		Env: []string{
			fmt.Sprintf("TERM=%s", os.Getenv("TERM")),
			fmt.Sprintf("HOME=%s", os.Getenv("HOME")),
			fmt.Sprintf("GOPATH=%s", os.Getenv("GOPATH")),
			fmt.Sprintf("PATH=/bin:%s/bin:/home/mrosset/gocode/bin", config.Prefix),
			"PS1=-[via-build]- # ",
		},
		SysProcAttr: &syscall.SysProcAttr{
			Cloneflags: syscall.CLONE_NEWNS |
				syscall.CLONE_NEWUTS |
				syscall.CLONE_NEWIPC |
				syscall.CLONE_NEWPID |
				syscall.CLONE_NEWUSER,
			UidMappings: []syscall.SysProcIDMap{
				{
					ContainerID: 1000,
					HostID:      os.Getuid(),
					Size:        1,
				},
			},
			GidMappings: []syscall.SysProcIDMap{
				{
					ContainerID: 1001,
					HostID:      os.Getgid(),
					Size:        1,
				},
			},
		},
	}
	if err := cmd.Run(); err != nil {
		elog.Fatal(err)
	}
}

func contain(ctx *cli.Context) error {
	var (
		args = []string{}
	)
	if len(os.Args) > 1 && os.Args[1] == "build" {
		args = []string{"init", "build", "-real"}
	} else {
		args = []string{"init", "contain"}
	}
	// maybe there is a better way to chain down flags?
	for _, f := range ctx.FlagNames() {
		args = append(args, fmt.Sprintf("-%s", f))
	}
	args = append(args, ctx.Args().Slice()...)
	cmd := reexec.Command(args...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags: syscall.CLONE_NEWNS |
			syscall.CLONE_NEWUTS |
			syscall.CLONE_NEWIPC |
			syscall.CLONE_NEWPID |
			syscall.CLONE_NEWUSER,
		UidMappings: []syscall.SysProcIDMap{
			{
				ContainerID: 0,
				HostID:      os.Getuid(),
				Size:        1,
			},
		},
		GidMappings: []syscall.SysProcIDMap{
			{
				ContainerID: 0,
				HostID:      os.Getgid(),
				Size:        1,
			},
		},
	}
	if err := cmd.Run(); err != nil {
		return err
	}
	return nil
}

type FileSystem struct {
	Source string
	Type   string
	Target string
	Flags  int
	Data   string
	MakeFn func(string) error
}

func (fs FileSystem) Mount(root string) error {
	target := filepath.Join(root, fs.Target)
	if err := os.MkdirAll(target, 0755); err != nil {
		return err
	}
	return syscall.Mount(
		fs.Source,
		target,
		fs.Type,
		uintptr(fs.Flags),
		fs.Data,
	)
}

func mkdir(path string) error {
	return os.MkdirAll(path, 0755)
}

func busybox(root string) error {
	bin := filepath.Join(root, "bin")
	if err := os.MkdirAll(bin, 0755); err != nil {
		return err
	}
	bpath := "/bin/busybox"
	cmd := exec.Cmd{
		Path:   bpath,
		Args:   []string{"busybox", "--install", "-s", bin},
		Stderr: os.Stderr,
		Stdout: os.Stdout,
	}
	if err := cmd.Run(); err != nil {
		return err
	}
	out, err := os.OpenFile(filepath.Join(bin, "busybox"), os.O_RDWR|os.O_CREATE, 0755)
	if err != nil {
		return err
	}
	defer out.Close()
	return file.Copy(out, bpath)
}

func bind(source, root string) error {
	target := filepath.Join(root, source)
	stat, err := os.Stat(source)
	if err != nil {
		return err
	}
	if stat.IsDir() {
		os.MkdirAll(target, 0755)
	} else {
		dir := filepath.Dir(target)
		os.MkdirAll(dir, 0755)
		if err := file.Touch(target); err != nil {
			return err
		}
	}
	return syscall.Mount(
		source,
		target,
		"",
		BIND_RO,
		"",
	)
}

func mount(root string) error {
	// our binds
	binds := []string{
		"/dev",
		"/etc/resolv.conf",
		"/etc/ssl",
		"/etc/passwd",
		os.ExpandEnv("$HOME/.ccache"),
		config.Cache.String(),
		config.Plans,
		config.Repo,
		config.Prefix,
		viabin,
	}
	// our filesystems
	fs := []FileSystem{
		{
			Source: "proc",
			Target: "/proc",
			Type:   "proc",
		},
		{
			Source: "tmpfs",
			Target: "/tmp",
			Type:   "tmpfs",
		},
	}
	// mount our binds
	for _, source := range binds {
		if err := bind(source, root); err != nil {
			elog.Printf("binding %s to %s", source, filepath.Join(root, source))
			return err
		}
	}
	// mount our filesystems
	for _, m := range fs {
		if err := m.Mount(root); err != nil {
			elog.Printf("mounting %s to %s", m.Source, filepath.Join(root, m.Source))
			return err
		}
	}
	return nil
}

func pivot(newroot string) error {
	oldroot := filepath.Join(newroot, "/.root")

	// bind mount newroot to itself - this is a slight hack
	// needed to work around a pivot_root requirement
	if err := syscall.Mount(
		newroot,
		newroot,
		"",
		BIND_RO,
		"",
	); err != nil {
		return err
	}

	// create oldroot directory
	if err := os.MkdirAll(oldroot, 0700); err != nil {
		return err
	}

	// call pivot_root
	if err := syscall.PivotRoot(newroot, oldroot); err != nil {
		return err
	}

	// ensure current working directory is set to new root
	if err := os.Chdir("/"); err != nil {
		return err
	}

	// umount oldroot, which now lives at /.pivot_root
	oldroot = "/.root"
	if err := syscall.Unmount(
		oldroot,
		syscall.MNT_DETACH,
	); err != nil {
		return err
	}

	return os.RemoveAll(oldroot)
}
<commit_msg>contain: entering container now uses enter command<commit_after>package main

import (
	"fmt"
	"github.com/docker/docker/pkg/reexec"
	"github.com/mrosset/util/file"
	"gopkg.in/urfave/cli.v2"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"syscall"
)

const (
	BIND_RO = syscall.MS_BIND | syscall.MS_RDONLY | syscall.MS_REC
	BIND_RW = syscall.MS_BIND | syscall.MS_REC
)

var (
	viabin          = filepath.Join(os.Getenv("GOPATH"), "bin/via")
	containCommands = []*cli.Command{
		&cli.Command{
			Name:   "enter",
			Usage:  "enter via build namespace",
			Action: contain,
		},
	}
)

func init() {
	reexec.Register("init", initialize)
	if reexec.Init() {
		os.Exit(0)
	}
	app.Commands = append(app.Commands, containCommands...)
}

func linksh(root string) error {
	var (
		source = filepath.Join(config.Prefix, "bin", "bash")
		bin    = filepath.Join(root, "bin")
		target = filepath.Join(bin, "sh")
	)
	if err := os.MkdirAll(bin, 0755); err != nil {
		return err
	}
	return os.Link(source, target)
}

func bindbin(root string) error {
	var (
		source = filepath.Join(config.Prefix, "bin")
		target = filepath.Join(root, "bin")
	)
	if err := os.MkdirAll(target, 0755); err != nil {
		return err
	}
	return syscall.Mount(
		source,
		target,
		"",
		BIND_RO,
		"",
	)
}

// instead of linking, bind sh to bash to avoid cross linking across
// devices
func bindsh(root string) error {
	var (
		source = filepath.Join(config.Prefix, "bin", "bash")
		bin    = filepath.Join(root, "bin")
		target = filepath.Join(bin, "sh")
	)
	if err := os.MkdirAll(bin, 0755); err != nil {
		return err
	}
	if err := file.Touch(target); err != nil {
		return err
	}
	return syscall.Mount(
		source,
		target,
		"",
		BIND_RO,
		"",
	)
}

func initialize() {
	root, err := ioutil.TempDir("", "via-build")
	if err != nil {
		elog.Fatal(err)
	}
	// set hostname
	if err := syscall.Sethostname([]byte("via-build")); err != nil {
		elog.Fatalf("could not set hostname: %s", err)
	}
	if err := os.MkdirAll(root, 0700); err != nil {
		elog.Fatal(err)
	}
	// setup all our mounts
	if err := mount(root); err != nil {
		elog.Fatal(err)
	}
	// bind
	if err := bindsh(root); err != nil {
		elog.Fatal(err)
	}
	// finally pivot our root
	if err := pivot(root); err != nil {
		elog.Fatal(err)
	}
	run()
}
func run() {
	var (
		path = "/bin/sh"
		args = []string{}
	)

	switch {
	case len(os.Args) >= 2 && os.Args[1] == "build":
		path = viabin
		args = append([]string{"via"}, os.Args[1:]...)
	case len(os.Args) >= 2 && os.Args[1] == "contain":
		path = "/bin/sh"
		args = []string{}
	default:
		elog.Fatalf("can not handle arguments %v", os.Args)
	}

	cmd := &exec.Cmd{
		Path:   path,
		Args:   args,
		Stdin:  os.Stdin,
		Stdout: os.Stdout,
		Stderr: os.Stderr,
		Env: []string{
			fmt.Sprintf("TERM=%s", os.Getenv("TERM")),
			fmt.Sprintf("HOME=%s", os.Getenv("HOME")),
			fmt.Sprintf("GOPATH=%s", os.Getenv("GOPATH")),
			fmt.Sprintf("PATH=/bin:%s/bin:/home/mrosset/gocode/bin", config.Prefix),
			"PS1=-[via-build]- # ",
		},
		SysProcAttr: &syscall.SysProcAttr{
			Cloneflags: syscall.CLONE_NEWUSER,
			UidMappings: []syscall.SysProcIDMap{
				{
					ContainerID: 1000,
					HostID:      os.Getuid(),
					Size:        1,
				},
			},
			GidMappings: []syscall.SysProcIDMap{
				{
					ContainerID: 1001,
					HostID:      os.Getgid(),
					Size:        1,
				},
			},
		},
	}
	if err := cmd.Run(); err != nil {
		elog.Fatal(err)
	}
}

func contain(ctx *cli.Context) error {
	var (
		args = []string{}
	)
	if len(os.Args) > 1 && os.Args[1] == "build" {
		args = []string{"init", "build", "-real"}
	} else {
		args = []string{"init", "contain"}
	}
	// maybe there is a better way to chain down flags?
	for _, f := range ctx.FlagNames() {
		args = append(args, fmt.Sprintf("-%s", f))
	}
	args = append(args, ctx.Args().Slice()...)
	cmd := reexec.Command(args...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags: syscall.CLONE_NEWNS |
			syscall.CLONE_NEWUTS |
			syscall.CLONE_NEWIPC |
			syscall.CLONE_NEWPID |
			syscall.CLONE_NEWUSER,
		UidMappings: []syscall.SysProcIDMap{
			{
				ContainerID: 0,
				HostID:      os.Getuid(),
				Size:        1,
			},
		},
		GidMappings: []syscall.SysProcIDMap{
			{
				ContainerID: 0,
				HostID:      os.Getgid(),
				Size:        1,
			},
		},
	}
	if err := cmd.Run(); err != nil {
		return err
	}
	return nil
}

type FileSystem struct {
	Source string
	Type   string
	Target string
	Flags  int
	Data   string
	MakeFn func(string) error
}

func (fs FileSystem) Mount(root string) error {
	target := filepath.Join(root, fs.Target)
	if err := os.MkdirAll(target, 0755); err != nil {
		return err
	}
	return syscall.Mount(
		fs.Source,
		target,
		fs.Type,
		uintptr(fs.Flags),
		fs.Data,
	)
}

func mkdir(path string) error {
	return os.MkdirAll(path, 0755)
}

func busybox(root string) error {
	bin := filepath.Join(root, "bin")
	if err := os.MkdirAll(bin, 0755); err != nil {
		return err
	}
	bpath := "/bin/busybox"
	cmd := exec.Cmd{
		Path:   bpath,
		Args:   []string{"busybox", "--install", "-s", bin},
		Stderr: os.Stderr,
		Stdout: os.Stdout,
	}
	if err := cmd.Run(); err != nil {
		return err
	}
	out, err := os.OpenFile(filepath.Join(bin, "busybox"), os.O_RDWR|os.O_CREATE, 0755)
	if err != nil {
		return err
	}
	defer out.Close()
	return file.Copy(out, bpath)
}

func bind(source, root string) error {
	target := filepath.Join(root, source)
	stat, err := os.Stat(source)
	if err != nil {
		return err
	}
	if stat.IsDir() {
		os.MkdirAll(target, 0755)
	} else {
		dir := filepath.Dir(target)
		os.MkdirAll(dir, 0755)
		if err := file.Touch(target); err != nil {
			return err
		}
	}
	return syscall.Mount(
		source,
		target,
		"",
		BIND_RO,
		"",
	)
}

func mount(root string) error {
	// our binds
	binds := []string{
		"/dev",
		"/etc/resolv.conf",
		"/etc/ssl",
		"/etc/passwd",
		os.ExpandEnv("$HOME/.ccache"),
		config.Cache.String(),
		config.Plans,
		config.Repo,
		config.Prefix,
		viabin,
	}
	// our filesystems
	fs := []FileSystem{
		{
			Source: "proc",
			Target: "/proc",
			Type:   "proc",
		},
		{
			Source: "tmpfs",
			Target: "/tmp",
			Type:   "tmpfs",
		},
	}
	// mount our binds
	for _, source := range binds {
		if err := bind(source, root); err != nil {
			elog.Printf("binding %s to %s", source, filepath.Join(root, source))
			return err
		}
	}
	// mount our filesystems
	for _, m := range fs {
		if err := m.Mount(root); err != nil {
			elog.Printf("mounting %s to %s", m.Source, filepath.Join(root, m.Source))
			return err
		}
	}
	return nil
}

func pivot(newroot string) error {
	oldroot := filepath.Join(newroot, "/.root")

	// bind mount newroot to itself - this is a slight hack
	// needed to work around a pivot_root requirement
	if err := syscall.Mount(
		newroot,
		newroot,
		"",
		BIND_RO,
		"",
	); err != nil {
		return err
	}

	// create oldroot directory
	if err := os.MkdirAll(oldroot, 0700); err != nil {
		return err
	}

	// call pivot_root
	if err := syscall.PivotRoot(newroot, oldroot); err != nil {
		return err
	}

	// ensure current working directory is set to new root
	if err := os.Chdir("/"); err != nil {
		return err
	}

	// umount oldroot, which now lives at /.pivot_root
	oldroot = "/.root"
	if err := syscall.Unmount(
		oldroot,
		syscall.MNT_DETACH,
	); err != nil {
		return err
	}

	return os.RemoveAll(oldroot)
}
<|endoftext|>


<commit_before>package ssh

import (
	"bytes"
	"fmt"
	"github.com/lunixbochs/vtclean"
	"github.com/pkg/errors"
	"github.com/viant/toolbox"
	"golang.org/x/crypto/ssh"
	"io"
	"strings"
	"sync/atomic"
	"time"
)

//ErrTerminated - command session terminated
var ErrTerminated = errors.New("terminate")

const defaultShell = "/bin/bash"

const (
	drainTimeoutMs       = 10
	defaultTimeoutMs     = 5000
	initTimeoutMs        = 1000
	defaultTickFrequency = 100
)

//Listener represent command listener (it will send stdout fragments as thier being available on stdout)
type Listener func(stdout string, hasMore bool)

//MultiCommandSession represents a multi command session
type MultiCommandSession interface {

	Run(command string, listener Listener, timeoutMs int, terminators ...string) (string, error)

	ShellPrompt() string

	System() string

	Reconnect() error

	Close()
}

//multiCommandSession represents a multi command session
//a new command are send vi stdin
type multiCommandSession struct {
	service            *service
	config             *SessionConfig
	replayCommands     *ReplayCommands
	recordSession      bool
	session            *ssh.Session
	stdOutput          chan string
	stdError           chan string
	stdInput           io.WriteCloser
	promptSequence     string
	shellPrompt        string
	escapedShellPrompt string
	system             string
	running            int32
}

func (s *multiCommandSession) Run(command string, listener Listener, timeoutMs int, terminators ...string) (string, error) {
	if atomic.LoadInt32(&s.running) == 0 {
		return "", ErrTerminated
	}
	s.drainStdout()
	var stdin = command + "\n"
	_, err := s.stdInput.Write([]byte(stdin))
	if err != nil {
		return "", fmt.Errorf("failed to execute command: %v, err: %v", command, err)
	}
	var output string
	output, _, err = s.readResponse(timeoutMs, listener, terminators...)
	if s.recordSession {
		s.replayCommands.Register(stdin, output)
	}
	return output, err
}

//ShellPrompt returns a shell prompt
func (s *multiCommandSession) ShellPrompt() string {
	return s.shellPrompt
}

//System returns a system name
func (s *multiCommandSession) System() string {
	return s.system
}

//Close closes the session with its resources
func (s *multiCommandSession) Close() {
	atomic.StoreInt32(&s.running, 0)
	s.stdInput.Close()
	if s.session != nil {
		s.session.Close()
	}

}

func (s *multiCommandSession) closeIfError(err error) bool {
	if err != nil {
		s.Close()
		return true
	}
	return false
}

func (s *multiCommandSession) start(shell string) (output string, err error) {
	var reader, errReader io.Reader
	reader, err = s.session.StdoutPipe()
	if err != nil {
		return "", err
	}
	go s.copy(reader, s.stdOutput)

	errReader, err = s.session.StderrPipe()
	if err != nil {
		return "", err
	}
	go s.copy(errReader, s.stdError)
	if shell == "" {
		shell = defaultShell
	}
	err = s.session.Start(shell)
	if err != nil {
		return "", err
	}
	output, _, err = s.readResponse(drainTimeoutMs, nil)
	return output, err
}

//copy copy data from reader to channel
func (s *multiCommandSession) copy(reader io.Reader, out chan string) {
	var written int64 = 0
	buf := make([]byte, 128*1024)
	var err error
	var bytesRead int
	for {
		writer := new(bytes.Buffer)
		if atomic.LoadInt32(&s.running) == 0 {
			return
		}
		bytesRead, err = reader.Read(buf)
		if bytesRead > 0 {
			bytesWritten, writeError := writer.Write(buf[:bytesRead])
			if s.closeIfError(writeError) {
				return
			}
			if bytesWritten > 0 {
				written += int64(bytesWritten)
			}

			if bytesRead != bytesWritten {
				if s.closeIfError(io.ErrShortWrite) {
					return
				}
			}
			out <- string(writer.Bytes())
		}

		if s.closeIfError(err) {
			return
		}
	}
}

func escapeInput(input string) string {
	input = vtclean.Clean(input, false)
	if input == "" {
		return input
	}
	return strings.Trim(input, "\n\r\t ")
}

func (s *multiCommandSession) Reconnect() (err error) {
	atomic.StoreInt32(&s.running, 1)
	s.service.Reconnect()
	s.session, err = s.service.client.NewSession()
	defer func() {
		if err != nil {
			s.service.client.Close()
		}
	}()

	if err != nil {
		return err
	}

	return s.init()
}

func (s *multiCommandSession) hasPrompt(input string) bool {
	escapedInput := escapeInput(input)
	var shellPrompt = s.shellPrompt
	if shellPrompt == "" {
		shellPrompt = "$"
	}
	if s.escapedShellPrompt == "" && s.shellPrompt != "" {
		s.escapedShellPrompt = escapeInput(s.shellPrompt)
	}

	if s.escapedShellPrompt != "" && strings.HasSuffix(escapedInput, s.escapedShellPrompt) || strings.HasSuffix(input, s.shellPrompt) {
		return true
	}
	return false
}

func (s *multiCommandSession) hasTerminator(input string, terminators ...string) bool {
	escapedInput := escapeInput(input)
	input = escapedInput
	for _, candidate := range terminators {
		candidateLen := len(candidate)
		if candidateLen == 0 {
			continue
		}
		if candidate[0:1] == "^" && strings.HasPrefix(input, candidate[1:]) {
			return true
		}
		if candidate[candidateLen-1:] == "$" && strings.HasSuffix(input, candidate[:candidateLen-1]) {
			return true
		}
		if strings.Contains(input, candidate) {
			return true
		}
	}
	return false
}

func (s *multiCommandSession) removePromptIfNeeded(stdout string) string {
	if strings.Contains(stdout, s.shellPrompt) {
		stdout = strings.Replace(stdout, s.shellPrompt, "", 1)
		stdout = strings.Replace(stdout, "\r", "", len(stdout))
		var lines = []string{}
		for _, line := range strings.Split(stdout, "\n") {
			if strings.TrimSpace(line) == "" {
				continue
			}
			lines = append(lines, line)
		}
		stdout = strings.Join(lines, "\r\n")
	}
	return stdout
}

func (s *multiCommandSession) readResponse(timeoutMs int, listener Listener, terminators ...string) (out string, has bool, err error) {
	if timeoutMs == 0 {
		timeoutMs = defaultTimeoutMs
	}
	defer func() {
		if listener != nil {
			listener("", false)
		}
	}()

	var done int32
	defer atomic.StoreInt32(&done, 1)
	var errOut string
	var hasOutput bool

	var waitTimeMs = 0
	var tickFrequencyMs = defaultTickFrequency
	if tickFrequencyMs > timeoutMs {
		tickFrequencyMs = timeoutMs
	}
	var timeoutDuration = time.Duration(tickFrequencyMs) * time.Millisecond

	var hasPrompt, hasTerminator bool

outer:
	for {
		select {
		case o := <-s.stdOutput:
			if len(o) > 0 {
				waitTimeMs = 0
				if listener != nil {
					listener(s.removePromptIfNeeded(o), true)
				}
			}
			out += o
			hasPrompt = s.hasPrompt(out)
			hasTerminator = s.hasTerminator(out, terminators...)
			if (hasPrompt || hasTerminator) && len(s.stdOutput) == 0 {
				break outer
			}
		case e := <-s.stdError:
			errOut += e
			if listener != nil {
				listener(s.removePromptIfNeeded(e), true)
			}
			hasPrompt = s.hasPrompt(errOut)
			hasTerminator = s.hasTerminator(errOut, terminators...)
			if (hasPrompt || hasTerminator) && len(s.stdOutput) == 0 {
				break outer
			}
		case <-time.After(timeoutDuration):
			waitTimeMs += tickFrequencyMs
			if waitTimeMs >= timeoutMs {
				break outer
			}
		}
	}
	if hasTerminator {
		s.drainStdout()
	}
	if errOut != "" {
		err = errors.New(errOut)
	}

	if len(out) > 0 {
		hasOutput = true
		out = s.removePromptIfNeeded(out)
	}
	return out, hasOutput, err
}

func (s *multiCommandSession) drainStdout() {
	//read any outstanding output
	for {
		_, has, _ := s.readResponse(drainTimeoutMs, nil, "")
		if !has {
			return
		}
	}
}

func (s *multiCommandSession) shellInit() (err error) {
	if s.promptSequence != "" {
		if _, err = s.Run(s.promptSequence, nil, initTimeoutMs); err != nil {
			return err
		}
	}
	var ts = toolbox.AsString(time.Now().UnixNano())
	s.promptSequence = "PS1=\"\\h:\\u" + ts + "\\$\""
	s.shellPrompt = ""
	s.escapedShellPrompt = ""

	for i := 0; i < 3; i++ { //for slow connection, make sure that you have right promot
		s.shellPrompt, err = s.Run(s.promptSequence, nil, initTimeoutMs)
		if err != nil {
			return err
		}
		if strings.Contains(s.shellPrompt, ts+"$") {
			break
		}
	}
	if !strings.Contains(s.shellPrompt, ts+"$") {
		s.shellPrompt = ts + "$"
	}
	if s.closeIfError(err) {
		return err
	}
	s.escapedShellPrompt = escapeInput(s.shellPrompt)
	s.system, err = s.Run("uname -s", nil, initTimeoutMs)
	s.system = strings.ToLower(s.system)
	return nil
}

func (s *multiCommandSession) init() (err error) {
	s.session, err = s.service.client.NewSession()
	defer func() {
		if err != nil {
			s.service.client.Close()
		}
	}()
	s.stdOutput = make(chan string)
	s.stdError = make(chan string)
	for k, v := range s.config.EnvVariables {
		err = s.session.Setenv(k, v)
		if err != nil {
			return err
		}
	}
	modes := ssh.TerminalModes{
		ssh.ECHO:          0,     // disable echoing
		ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
		ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
	}

	if err := s.session.RequestPty(s.config.Term, s.config.Rows, s.config.Columns, modes); err != nil {
		return err
	}

	if s.stdInput, err = s.session.StdinPipe(); err != nil {
		return err
	}

	_, err = s.start(s.config.Shell)
	if s.closeIfError(err) {
		return err
	}

	return s.shellInit()

}

func newMultiCommandSession(service *service, config *SessionConfig, replayCommands *ReplayCommands, recordSession bool) (MultiCommandSession, error) {
	if config == nil {
		config = &SessionConfig{}
	}
	config.applyDefault()

	result := &multiCommandSession{
		service:        service,
		config:         config,
		running:        1,
		recordSession:  recordSession,
		replayCommands: replayCommands,
	}
	return result, result.init()
}
<commit_msg>tune SSH init timeout<commit_after>package ssh

import (
	"bytes"
	"fmt"
	"github.com/lunixbochs/vtclean"
	"github.com/pkg/errors"
	"github.com/viant/toolbox"
	"golang.org/x/crypto/ssh"
	"io"
	"strings"
	"sync/atomic"
	"time"
	"sync"
)

//ErrTerminated - command session terminated
var ErrTerminated = errors.New("terminate")

const defaultShell = "/bin/bash"

const (
	drainTimeoutMs       = 10
	defaultTimeoutMs     = 5000
	initTimeoutMs        = 200
	defaultTickFrequency = 100
)

//Listener represent command listener (it will send stdout fragments as thier being available on stdout)
type Listener func(stdout string, hasMore bool)

//MultiCommandSession represents a multi command session
type MultiCommandSession interface {

	Run(command string, listener Listener, timeoutMs int, terminators ...string) (string, error)

	ShellPrompt() string

	System() string

	Reconnect() error

	Close()
}

//multiCommandSession represents a multi command session
//a new command are send vi stdin
type multiCommandSession struct {
	service            *service
	config             *SessionConfig
	replayCommands     *ReplayCommands
	recordSession      bool
	session            *ssh.Session
	stdOutput          chan string
	stdError           chan string
	stdInput           io.WriteCloser
	promptSequence     string
	shellPrompt        string
	escapedShellPrompt string
	system             string
	running            int32
}

func (s *multiCommandSession) Run(command string, listener Listener, timeoutMs int, terminators ...string) (string, error) {
	if atomic.LoadInt32(&s.running) == 0 {
		return "", ErrTerminated
	}
	s.drainStdout()
	var stdin = command + "\n"
	_, err := s.stdInput.Write([]byte(stdin))
	if err != nil {
		return "", fmt.Errorf("failed to execute command: %v, err: %v", command, err)
	}
	var output string
	output, _, err = s.readResponse(timeoutMs, listener, terminators...)
	if s.recordSession {
		s.replayCommands.Register(stdin, output)
	}
	return output, err
}

//ShellPrompt returns a shell prompt
func (s *multiCommandSession) ShellPrompt() string {
	return s.shellPrompt
}

//System returns a system name
func (s *multiCommandSession) System() string {
	return s.system
}

//Close closes the session with its resources
func (s *multiCommandSession) Close() {
	atomic.StoreInt32(&s.running, 0)
	s.stdInput.Close()
	if s.session != nil {
		s.session.Close()
	}

}

func (s *multiCommandSession) closeIfError(err error) bool {
	if err != nil {
		s.Close()
		return true
	}
	return false
}

func (s *multiCommandSession) start(shell string) (output string, err error) {
	var reader, errReader io.Reader
	reader, err = s.session.StdoutPipe()
	if err != nil {
		return "", err
	}
	errReader, err = s.session.StderrPipe()
	if err != nil {
		return "", err
	}

	waitGroup := sync.WaitGroup{}
	waitGroup.Add(2)
	go func(){
		waitGroup.Done()
		s.copy(reader, s.stdOutput)
	}()
	go func() {
		waitGroup.Done()
		s.copy(errReader, s.stdError)
	}()

	if shell == "" {
		shell = defaultShell
	}
	waitGroup.Wait()
	err = s.session.Start(shell)
	if err != nil {
		return "", err
	}
	output, _, err = s.readResponse(drainTimeoutMs, nil)
	return output, err
}

//copy copy data from reader to channel
func (s *multiCommandSession) copy(reader io.Reader, out chan string) {
	var written int64 = 0
	buf := make([]byte, 128*1024)
	var err error
	var bytesRead int
	for {
		writer := new(bytes.Buffer)
		if atomic.LoadInt32(&s.running) == 0 {
			return
		}
		bytesRead, err = reader.Read(buf)
		if bytesRead > 0 {
			bytesWritten, writeError := writer.Write(buf[:bytesRead])
			if s.closeIfError(writeError) {
				return
			}
			if bytesWritten > 0 {
				written += int64(bytesWritten)
			}

			if bytesRead != bytesWritten {
				if s.closeIfError(io.ErrShortWrite) {
					return
				}
			}
			out <- string(writer.Bytes())
		}

		if s.closeIfError(err) {
			return
		}
	}
}

func escapeInput(input string) string {
	input = vtclean.Clean(input, false)
	if input == "" {
		return input
	}
	return strings.Trim(input, "\n\r\t ")
}

func (s *multiCommandSession) Reconnect() (err error) {
	atomic.StoreInt32(&s.running, 1)
	s.service.Reconnect()
	s.session, err = s.service.client.NewSession()
	defer func() {
		if err != nil {
			s.service.client.Close()
		}
	}()

	if err != nil {
		return err
	}

	return s.init()
}

func (s *multiCommandSession) hasPrompt(input string) bool {
	escapedInput := escapeInput(input)
	var shellPrompt = s.shellPrompt
	if shellPrompt == "" {
		shellPrompt = "$"
	}
	if s.escapedShellPrompt == "" && s.shellPrompt != "" {
		s.escapedShellPrompt = escapeInput(s.shellPrompt)
	}

	if s.escapedShellPrompt != "" && strings.HasSuffix(escapedInput, s.escapedShellPrompt) || strings.HasSuffix(input, s.shellPrompt) {
		return true
	}
	return false
}

func (s *multiCommandSession) hasTerminator(input string, terminators ...string) bool {
	escapedInput := escapeInput(input)
	input = escapedInput
	for _, candidate := range terminators {
		candidateLen := len(candidate)
		if candidateLen == 0 {
			continue
		}
		if candidate[0:1] == "^" && strings.HasPrefix(input, candidate[1:]) {
			return true
		}
		if candidate[candidateLen-1:] == "$" && strings.HasSuffix(input, candidate[:candidateLen-1]) {
			return true
		}
		if strings.Contains(input, candidate) {
			return true
		}
	}
	return false
}

func (s *multiCommandSession) removePromptIfNeeded(stdout string) string {
	if strings.Contains(stdout, s.shellPrompt) {
		stdout = strings.Replace(stdout, s.shellPrompt, "", 1)
		stdout = strings.Replace(stdout, "\r", "", len(stdout))
		var lines = []string{}
		for _, line := range strings.Split(stdout, "\n") {
			if strings.TrimSpace(line) == "" {
				continue
			}
			lines = append(lines, line)
		}
		stdout = strings.Join(lines, "\r\n")
	}
	return stdout
}

func (s *multiCommandSession) readResponse(timeoutMs int, listener Listener, terminators ...string) (out string, has bool, err error) {
	if timeoutMs == 0 {
		timeoutMs = defaultTimeoutMs
	}
	defer func() {
		if listener != nil {
			listener("", false)
		}
	}()

	var done int32
	defer atomic.StoreInt32(&done, 1)
	var errOut string
	var hasOutput bool

	var waitTimeMs = 0
	var tickFrequencyMs = defaultTickFrequency
	if tickFrequencyMs > timeoutMs {
		tickFrequencyMs = timeoutMs
	}
	var timeoutDuration = time.Duration(tickFrequencyMs) * time.Millisecond

	var hasPrompt, hasTerminator bool

outer:
	for {
		select {
		case o := <-s.stdOutput:
			if len(o) > 0 {
				waitTimeMs = 0
				if listener != nil {
					listener(s.removePromptIfNeeded(o), true)
				}
			}
			out += o
			hasPrompt = s.hasPrompt(out)
			hasTerminator = s.hasTerminator(out, terminators...)
			if (hasPrompt || hasTerminator) && len(s.stdOutput) == 0 {
				break outer
			}
		case e := <-s.stdError:
			errOut += e
			if listener != nil {
				listener(s.removePromptIfNeeded(e), true)
			}
			hasPrompt = s.hasPrompt(errOut)
			hasTerminator = s.hasTerminator(errOut, terminators...)
			if (hasPrompt || hasTerminator) && len(s.stdOutput) == 0 {
				break outer
			}
		case <-time.After(timeoutDuration):
			waitTimeMs += tickFrequencyMs
			if waitTimeMs >= timeoutMs {
				break outer
			}
		}
	}
	if hasTerminator {
		s.drainStdout()
	}
	if errOut != "" {
		err = errors.New(errOut)
	}

	if len(out) > 0 {
		hasOutput = true
		out = s.removePromptIfNeeded(out)
	}
	return out, hasOutput, err
}

func (s *multiCommandSession) drainStdout() {
	//read any outstanding output
	for {
		_, has, _ := s.readResponse(drainTimeoutMs, nil, "")
		if !has {
			return
		}
	}
}

func (s *multiCommandSession) shellInit() (err error) {
	if s.promptSequence != "" {
		if _, err = s.Run(s.promptSequence, nil, initTimeoutMs); err != nil {
			return err
		}
	}

	var ts = toolbox.AsString(time.Now().UnixNano())
	s.promptSequence = "PS1=\"\\h:\\u" + ts + "\\$\""
	s.shellPrompt = ""
	s.escapedShellPrompt = ""
	for i := 1; i < 10; i++ { //for slow connection, make sure that you have right promot
		s.shellPrompt, err = s.Run(s.promptSequence, nil, i * initTimeoutMs)
		if err != nil {
			return err
		}
		if strings.Contains(s.shellPrompt, ts+"$") {
			fmt.Printf("has shell %v\n", i)
			break
		}
	}
	if !strings.Contains(s.shellPrompt, ts+"$") {
		s.shellPrompt = ts + "$"
	}
	if s.closeIfError(err) {
		return err
	}
	s.escapedShellPrompt = escapeInput(s.shellPrompt)
	s.system, err = s.Run("uname -s", nil, defaultTimeoutMs)
	s.system = strings.ToLower(s.system)
	return nil
}

func (s *multiCommandSession) init() (err error) {
	s.session, err = s.service.client.NewSession()
	defer func() {
		if err != nil {
			s.service.client.Close()
		}
	}()
	s.stdOutput = make(chan string)
	s.stdError = make(chan string)
	for k, v := range s.config.EnvVariables {
		err = s.session.Setenv(k, v)
		if err != nil {
			return err
		}
	}
	modes := ssh.TerminalModes{
		ssh.ECHO:          0,     // disable echoing
		ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
		ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
	}

	if err := s.session.RequestPty(s.config.Term, s.config.Rows, s.config.Columns, modes); err != nil {
		return err
	}

	if s.stdInput, err = s.session.StdinPipe(); err != nil {
		return err
	}

	_, err = s.start(s.config.Shell)
	if s.closeIfError(err) {
		return err
	}
	return s.shellInit()
}

func newMultiCommandSession(service *service, config *SessionConfig, replayCommands *ReplayCommands, recordSession bool) (MultiCommandSession, error) {
	if config == nil {
		config = &SessionConfig{}
	}
	config.applyDefault()

	result := &multiCommandSession{
		service:        service,
		config:         config,
		running:        1,
		recordSession:  recordSession,
		replayCommands: replayCommands,
	}
	return result, result.init()
}
<|endoftext|>


<commit_before>// +build linux

package hsup

import (
	"bufio"
	"compress/gzip"
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"

	"github.com/htcat/htcat"
	"gopkg.in/yaml.v2"
)

const (
	// currently available versions of Heroku stack images
	HEROKU_STACKS_MANIFEST_URL = "https://s3.amazonaws.com/heroku_stacks_production/manifest.yml"
)

// HerokuStackImage models stack images as they are distributed by Heroku:
// binary disk images, usually intended to be mounted on loopback devices.
// The common use is to mount those images read-only, so a single immutable
// image can be shared by multiple containers, saving disk space and memory
// due to Linux CoW page sharing.
//
// Support for Heroku stack images is currently only enabled when building for
// Linux, because these images are currently only used by the libcontainer
// driver.
type HerokuStackImage struct {
	Name    string
	Version string
	URL     string `yaml:"url"`
	Md5     string
	Primary bool

	basedir string
}

func HerokuStacksFromManifest(stacksDir string) ([]HerokuStackImage, error) {
	// TODO: cache the manifest
	resp, err := http.Get(HEROKU_STACKS_MANIFEST_URL)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf(
			"invalid stacks manifest at %q",
			HEROKU_STACKS_MANIFEST_URL,
		)
	}
	manifest, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	var stacks []HerokuStackImage
	if err := yaml.Unmarshal(manifest, &stacks); err != nil {
		return nil, err
	}
	for i := range stacks {
		stacks[i].basedir = stacksDir
	}
	return stacks, nil
}

func CurrentStackImagePath(stacksDir, name string) (string, error) {
	// TODO: check if it is really mounted
	// TODO: order by version numbers and pick the last
	names, err := filepath.Glob(filepath.Join(stacksDir, name+"-*"))
	if err != nil {
		return "", err
	}
	last := len(names) - 1
	for i := range names {
		n := names[last-i]
		if !strings.HasSuffix(n, ".img") {
			return n, nil
		}
	}

	return "", errors.New("no matching stack image found")
}

func (img *HerokuStackImage) Dir() string {
	return filepath.Join(img.basedir, img.Name+"-"+img.Version)
}

func (img *HerokuStackImage) Filename() string {
	return img.Dir() + ".img"
}

//TODO: avoid multiple processes trying to mount the same stack image
func (img *HerokuStackImage) mount() error {
	var (
		imgFile = img.Filename()
		imgDir  = img.Dir()
	)
	if _, err := os.Stat(imgFile); err != nil {
		if err := img.fetch(); err != nil {
			return err
		}
	}
	if err := os.MkdirAll(imgDir, 0755); err != nil {
		return err
	}
	if contents, err := ioutil.ReadDir(imgDir); err != nil {
		return err
	} else if len(contents) != 0 {
		return nil // already mounted
	}
	log.Printf("Mounting stach image %q onto %q", imgFile, imgDir)
	out, err := exec.Command(
		"/bin/mount", "-o", "loop,nosuid,nodev,noatime,nodiratime,rw",
		imgFile, imgDir,
	).CombinedOutput()
	if err != nil {
		log.Println(string(out))
		return err
	}
	if err := img.addMissingDirectories(); err != nil {
		return err
	}
	return syscall.Mount("", imgDir, "", syscall.MS_REMOUNT|syscall.MS_RDONLY, "")

}

// addMissingDirectories is required until https://github.com/heroku/stack-images/pull/13
// gets merged.
func (img *HerokuStackImage) addMissingDirectories() error {
	return os.MkdirAll(filepath.Join(img.Dir(), "sys"), 0755)
}

//TODO: avoid multiple processes trying to fetch the same stack image
func (img *HerokuStackImage) fetch() error {
	log.Printf("Downloading stach image %q. This may take a while...", img.Name)
	// TODO check md5
	pr, pw := io.Pipe()
	defer pr.Close()

	u, err := url.Parse(img.URL)
	if err != nil {
		return err
	}

	// buffered channel: avoid blocking the writer goroutine
	dlResult := make(chan error, 1)
	go func() {
		// close the pipe as soon as the download finishes so readers receive EOF
		defer pw.Close()
		client := *http.DefaultClient
		if u.Scheme == "https" {
			client.Transport = &http.Transport{
				TLSClientConfig: &tls.Config{},
			}
		}
		// buffer the download in case gunzip can't keep up
		w := bufio.NewWriter(pw)
		_, err := htcat.New(&client, u, 5).WriteTo(w)
		w.Flush()
		dlResult <- err
	}()

	r, err := gzip.NewReader(pr)
	if err != nil {
		return err
	}
	defer r.Close()

	imageFile, err := os.Create(img.Filename())
	if err != nil {
		return err
	}
	defer imageFile.Close()
	if err := imageFile.Chmod(0644); err != nil {
		return err
	}

	if _, err := io.Copy(imageFile, r); err != nil {
		return err
	}
	log.Println("Stack image download finished")
	return <-dlResult
}
<commit_msg>Fix typos<commit_after>// +build linux

package hsup

import (
	"bufio"
	"compress/gzip"
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"

	"github.com/htcat/htcat"
	"gopkg.in/yaml.v2"
)

const (
	// currently available versions of Heroku stack images
	HEROKU_STACKS_MANIFEST_URL = "https://s3.amazonaws.com/heroku_stacks_production/manifest.yml"
)

// HerokuStackImage models stack images as they are distributed by Heroku:
// binary disk images, usually intended to be mounted on loopback devices.
// The common use is to mount those images read-only, so a single immutable
// image can be shared by multiple containers, saving disk space and memory
// due to Linux CoW page sharing.
//
// Support for Heroku stack images is currently only enabled when building for
// Linux, because these images are currently only used by the libcontainer
// driver.
type HerokuStackImage struct {
	Name    string
	Version string
	URL     string `yaml:"url"`
	Md5     string
	Primary bool

	basedir string
}

func HerokuStacksFromManifest(stacksDir string) ([]HerokuStackImage, error) {
	// TODO: cache the manifest
	resp, err := http.Get(HEROKU_STACKS_MANIFEST_URL)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf(
			"invalid stacks manifest at %q",
			HEROKU_STACKS_MANIFEST_URL,
		)
	}
	manifest, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	var stacks []HerokuStackImage
	if err := yaml.Unmarshal(manifest, &stacks); err != nil {
		return nil, err
	}
	for i := range stacks {
		stacks[i].basedir = stacksDir
	}
	return stacks, nil
}

func CurrentStackImagePath(stacksDir, name string) (string, error) {
	// TODO: check if it is really mounted
	// TODO: order by version numbers and pick the last
	names, err := filepath.Glob(filepath.Join(stacksDir, name+"-*"))
	if err != nil {
		return "", err
	}
	last := len(names) - 1
	for i := range names {
		n := names[last-i]
		if !strings.HasSuffix(n, ".img") {
			return n, nil
		}
	}

	return "", errors.New("no matching stack image found")
}

func (img *HerokuStackImage) Dir() string {
	return filepath.Join(img.basedir, img.Name+"-"+img.Version)
}

func (img *HerokuStackImage) Filename() string {
	return img.Dir() + ".img"
}

//TODO: avoid multiple processes trying to mount the same stack image
func (img *HerokuStackImage) mount() error {
	var (
		imgFile = img.Filename()
		imgDir  = img.Dir()
	)
	if _, err := os.Stat(imgFile); err != nil {
		if err := img.fetch(); err != nil {
			return err
		}
	}
	if err := os.MkdirAll(imgDir, 0755); err != nil {
		return err
	}
	if contents, err := ioutil.ReadDir(imgDir); err != nil {
		return err
	} else if len(contents) != 0 {
		return nil // already mounted
	}
	log.Printf("Mounting stack image %q onto %q", imgFile, imgDir)
	out, err := exec.Command(
		"/bin/mount", "-o", "loop,nosuid,nodev,noatime,nodiratime,rw",
		imgFile, imgDir,
	).CombinedOutput()
	if err != nil {
		log.Println(string(out))
		return err
	}
	if err := img.addMissingDirectories(); err != nil {
		return err
	}
	return syscall.Mount("", imgDir, "", syscall.MS_REMOUNT|syscall.MS_RDONLY, "")

}

// addMissingDirectories is required until https://github.com/heroku/stack-images/pull/13
// gets merged.
func (img *HerokuStackImage) addMissingDirectories() error {
	return os.MkdirAll(filepath.Join(img.Dir(), "sys"), 0755)
}

//TODO: avoid multiple processes trying to fetch the same stack image
func (img *HerokuStackImage) fetch() error {
	log.Printf("Downloading stack image %q. This may take a while...", img.Name)
	// TODO check md5
	pr, pw := io.Pipe()
	defer pr.Close()

	u, err := url.Parse(img.URL)
	if err != nil {
		return err
	}

	// buffered channel: avoid blocking the writer goroutine
	dlResult := make(chan error, 1)
	go func() {
		// close the pipe as soon as the download finishes so readers receive EOF
		defer pw.Close()
		client := *http.DefaultClient
		if u.Scheme == "https" {
			client.Transport = &http.Transport{
				TLSClientConfig: &tls.Config{},
			}
		}
		// buffer the download in case gunzip can't keep up
		w := bufio.NewWriter(pw)
		_, err := htcat.New(&client, u, 5).WriteTo(w)
		w.Flush()
		dlResult <- err
	}()

	r, err := gzip.NewReader(pr)
	if err != nil {
		return err
	}
	defer r.Close()

	imageFile, err := os.Create(img.Filename())
	if err != nil {
		return err
	}
	defer imageFile.Close()
	if err := imageFile.Chmod(0644); err != nil {
		return err
	}

	if _, err := io.Copy(imageFile, r); err != nil {
		return err
	}
	log.Println("Stack image download finished")
	return <-dlResult
}
<|endoftext|>


<commit_before>package echo

import (
	"encoding/json"
	"encoding/xml"
	"io"
	"mime"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"github.com/labstack/echo/engine"
	"github.com/labstack/gommon/log"

	"bytes"

	netContext "golang.org/x/net/context"
)

type (
	// Context represents the context of the current HTTP request. It holds request and
	// response objects, path, path parameters, data and registered handler.
	Context interface {
		netContext.Context

		// NetContext returns `http://blog.golang.org/context.Context` interface.
		NetContext() netContext.Context

		// SetNetContext sets `http://blog.golang.org/context.Context` interface.
		SetNetContext(netContext.Context)

		// Request returns `engine.Request` interface.
		Request() engine.Request

		// Request returns `engine.Response` interface.
		Response() engine.Response

		// Path returns the registered path for the handler.
		Path() string

		// SetPath sets the registered path for the handler.
		SetPath(string)

		// P returns path parameter by index.
		P(int) string

		// Param returns path parameter by name.
		Param(string) string

		// ParamNames returns path parameter names.
		ParamNames() []string

		// SetParamNames sets path parameter names.
		SetParamNames(...string)

		// ParamValues returns path parameter values.
		ParamValues() []string

		// SetParamValues sets path parameter values.
		SetParamValues(...string)

		// QueryParam returns the query param for the provided name. It is an alias
		// for `engine.URL#QueryParam()`.
		QueryParam(string) string

		// QueryParams returns the query parameters as map. It is an alias for `engine.URL#QueryParams()`.
		QueryParams() map[string][]string

		// FormValue returns the form field value for the provided name. It is an
		// alias for `engine.Request#FormValue()`.
		FormValue(string) string

		// FormParams returns the form parameters as map. It is an alias for `engine.Request#FormParams()`.
		FormParams() map[string][]string

		// FormFile returns the multipart form file for the provided name. It is an
		// alias for `engine.Request#FormFile()`.
		FormFile(string) (*multipart.FileHeader, error)

		// MultipartForm returns the multipart form. It is an alias for `engine.Request#MultipartForm()`.
		MultipartForm() (*multipart.Form, error)

		// Get retrieves data from the context.
		Get(string) interface{}

		// Set saves data in the context.
		Set(string, interface{})

		// Del data from the context
		Del(string)

		// Bind binds the request body into provided type `i`. The default binder
		// does it based on Content-Type header.
		Bind(interface{}) error

		// Render renders a template with data and sends a text/html response with status
		// code. Templates can be registered using `Echo.SetRenderer()`.
		Render(int, string, interface{}) error

		// HTML sends an HTTP response with status code.
		HTML(int, string) error

		// String sends a string response with status code.
		String(int, string) error

		// JSON sends a JSON response with status code.
		JSON(int, interface{}) error

		// JSONBlob sends a JSON blob response with status code.
		JSONBlob(int, []byte) error

		// JSONP sends a JSONP response with status code. It uses `callback` to construct
		// the JSONP payload.
		JSONP(int, string, interface{}) error

		// XML sends an XML response with status code.
		XML(int, interface{}) error

		// XMLBlob sends a XML blob response with status code.
		XMLBlob(int, []byte) error

		// File sends a response with the content of the file.
		File(string) error

		// Attachment sends a response from `io.ReaderSeeker` as attachment, prompting
		// client to save the file.
		Attachment(io.ReadSeeker, string) error

		// NoContent sends a response with no body and a status code.
		NoContent(int) error

		// Redirect redirects the request with status code.
		Redirect(int, string) error

		// Error invokes the registered HTTP error handler. Generally used by middleware.
		Error(err error)

		// Handler returns the matched handler by router.
		Handler() HandlerFunc

		// SetHandler sets the matched handler by router.
		SetHandler(HandlerFunc)

		// Logger returns the `Logger` instance.
		Logger() *log.Logger

		// Echo returns the `Echo` instance.
		Echo() *Echo

		// ServeContent sends static content from `io.Reader` and handles caching
		// via `If-Modified-Since` request header. It automatically sets `Content-Type`
		// and `Last-Modified` response headers.
		ServeContent(io.ReadSeeker, string, time.Time) error

		// Reset resets the context after request completes. It must be called along
		// with `Echo#GetContext()` and `Echo#PutContext()`. See `Echo#ServeHTTP()`
		Reset(engine.Request, engine.Response)
	}

	context struct {
		netContext netContext.Context
		request    engine.Request
		response   engine.Response
		path       string
		pnames     []string
		pvalues    []string
		store      store
		handler    HandlerFunc
		echo       *Echo
	}

	store map[string]interface{}
)

const (
	indexPage = "index.html"
)

func (c *context) NetContext() netContext.Context {
	return c.netContext
}

func (c *context) SetNetContext(ctx netContext.Context) {
	c.netContext = ctx
}

func (c *context) Deadline() (deadline time.Time, ok bool) {
	return c.netContext.Deadline()
}

func (c *context) Done() <-chan struct{} {
	return c.netContext.Done()
}

func (c *context) Err() error {
	return c.netContext.Err()
}

func (c *context) Value(key interface{}) interface{} {
	return c.netContext.Value(key)
}

func (c *context) Request() engine.Request {
	return c.request
}

func (c *context) Response() engine.Response {
	return c.response
}

func (c *context) Path() string {
	return c.path
}

func (c *context) SetPath(p string) {
	c.path = p
}

func (c *context) P(i int) (value string) {
	l := len(c.pnames)
	if i < l {
		value = c.pvalues[i]
	}
	return
}

func (c *context) Param(name string) (value string) {
	l := len(c.pnames)
	for i, n := range c.pnames {
		if n == name && i < l {
			value = c.pvalues[i]
			break
		}
	}
	return
}

func (c *context) ParamNames() []string {
	return c.pnames
}

func (c *context) SetParamNames(names ...string) {
	c.pnames = names
}

func (c *context) ParamValues() []string {
	return c.pvalues
}

func (c *context) SetParamValues(values ...string) {
	c.pvalues = values
}

func (c *context) QueryParam(name string) string {
	return c.request.URL().QueryParam(name)
}

func (c *context) QueryParams() map[string][]string {
	return c.request.URL().QueryParams()
}

func (c *context) FormValue(name string) string {
	return c.request.FormValue(name)
}

func (c *context) FormParams() map[string][]string {
	return c.request.FormParams()
}

func (c *context) FormFile(name string) (*multipart.FileHeader, error) {
	return c.request.FormFile(name)
}

func (c *context) MultipartForm() (*multipart.Form, error) {
	return c.request.MultipartForm()
}

func (c *context) Set(key string, val interface{}) {
	if c.store == nil {
		c.store = make(store)
	}
	c.store[key] = val
}

func (c *context) Get(key string) interface{} {
	return c.store[key]
}

func (c *context) Del(key string) {
	delete(c.store, key)
}

func (c *context) Bind(i interface{}) error {
	return c.echo.binder.Bind(i, c)
}

func (c *context) Render(code int, name string, data interface{}) (err error) {
	if c.echo.renderer == nil {
		return ErrRendererNotRegistered
	}
	buf := new(bytes.Buffer)
	if err = c.echo.renderer.Render(buf, name, data, c); err != nil {
		return
	}
	c.response.Header().Set(HeaderContentType, MIMETextHTMLCharsetUTF8)
	c.response.WriteHeader(code)
	_, err = c.response.Write(buf.Bytes())
	return
}

func (c *context) HTML(code int, html string) (err error) {
	c.response.Header().Set(HeaderContentType, MIMETextHTMLCharsetUTF8)
	c.response.WriteHeader(code)
	_, err = c.response.Write([]byte(html))
	return
}

func (c *context) String(code int, s string) (err error) {
	c.response.Header().Set(HeaderContentType, MIMETextPlainCharsetUTF8)
	c.response.WriteHeader(code)
	_, err = c.response.Write([]byte(s))
	return
}

func (c *context) JSON(code int, i interface{}) (err error) {
	b, err := json.Marshal(i)
	if c.echo.Debug() {
		b, err = json.MarshalIndent(i, "", "  ")
	}
	if err != nil {
		return err
	}
	return c.JSONBlob(code, b)
}

func (c *context) JSONBlob(code int, b []byte) (err error) {
	c.response.Header().Set(HeaderContentType, MIMEApplicationJSONCharsetUTF8)
	c.response.WriteHeader(code)
	_, err = c.response.Write(b)
	return
}

func (c *context) JSONP(code int, callback string, i interface{}) (err error) {
	b, err := json.Marshal(i)
	if err != nil {
		return err
	}
	c.response.Header().Set(HeaderContentType, MIMEApplicationJavaScriptCharsetUTF8)
	c.response.WriteHeader(code)
	if _, err = c.response.Write([]byte(callback + "(")); err != nil {
		return
	}
	if _, err = c.response.Write(b); err != nil {
		return
	}
	_, err = c.response.Write([]byte(");"))
	return
}

func (c *context) XML(code int, i interface{}) (err error) {
	b, err := xml.Marshal(i)
	if c.echo.Debug() {
		b, err = xml.MarshalIndent(i, "", "  ")
	}
	if err != nil {
		return err
	}
	return c.XMLBlob(code, b)
}

func (c *context) XMLBlob(code int, b []byte) (err error) {
	c.response.Header().Set(HeaderContentType, MIMEApplicationXMLCharsetUTF8)
	c.response.WriteHeader(code)
	if _, err = c.response.Write([]byte(xml.Header)); err != nil {
		return
	}
	_, err = c.response.Write(b)
	return
}

func (c *context) File(file string) error {
	f, err := os.Open(file)
	if err != nil {
		return ErrNotFound
	}
	defer f.Close()

	fi, _ := f.Stat()
	if fi.IsDir() {
		file = filepath.Join(file, "index.html")
		f, err = os.Open(file)
		if err != nil {
			return ErrNotFound
		}
		if fi, err = f.Stat(); err != nil {
			return err
		}
	}
	return c.ServeContent(f, fi.Name(), fi.ModTime())
}

func (c *context) Attachment(r io.ReadSeeker, name string) (err error) {
	c.response.Header().Set(HeaderContentType, ContentTypeByExtension(name))
	c.response.Header().Set(HeaderContentDisposition, "attachment; filename="+name)
	c.response.WriteHeader(http.StatusOK)
	_, err = io.Copy(c.response, r)
	return
}

func (c *context) NoContent(code int) error {
	c.response.WriteHeader(code)
	return nil
}

func (c *context) Redirect(code int, url string) error {
	if code < http.StatusMultipleChoices || code > http.StatusTemporaryRedirect {
		return ErrInvalidRedirectCode
	}
	c.response.Header().Set(HeaderLocation, url)
	c.response.WriteHeader(code)
	return nil
}

func (c *context) Error(err error) {
	c.echo.httpErrorHandler(err, c)
}

func (c *context) Echo() *Echo {
	return c.echo
}

func (c *context) Handler() HandlerFunc {
	return c.handler
}

func (c *context) SetHandler(h HandlerFunc) {
	c.handler = h
}

func (c *context) Logger() *log.Logger {
	return c.echo.logger
}

func (c *context) ServeContent(content io.ReadSeeker, name string, modtime time.Time) error {
	rq := c.Request()
	rs := c.Response()

	if t, err := time.Parse(http.TimeFormat, rq.Header().Get(HeaderIfModifiedSince)); err == nil && modtime.Before(t.Add(1*time.Second)) {
		rs.Header().Del(HeaderContentType)
		rs.Header().Del(HeaderContentLength)
		return c.NoContent(http.StatusNotModified)
	}

	rs.Header().Set(HeaderContentType, ContentTypeByExtension(name))
	rs.Header().Set(HeaderLastModified, modtime.UTC().Format(http.TimeFormat))
	rs.WriteHeader(http.StatusOK)
	_, err := io.Copy(rs, content)
	return err
}

// ContentTypeByExtension returns the MIME type associated with the file based on
// its extension. It returns `application/octet-stream` incase MIME type is not
// found.
func ContentTypeByExtension(name string) (t string) {
	if t = mime.TypeByExtension(filepath.Ext(name)); t == "" {
		t = MIMEOctetStream
	}
	return
}

func (c *context) Reset(rq engine.Request, rs engine.Response) {
	c.netContext = nil
	c.request = rq
	c.response = rs
	c.store = nil
	c.handler = notFoundHandler
}
<commit_msg>Add Exists() method<commit_after>package echo

import (
	"encoding/json"
	"encoding/xml"
	"io"
	"mime"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"github.com/labstack/echo/engine"
	"github.com/labstack/gommon/log"

	"bytes"

	netContext "golang.org/x/net/context"
)

type (
	// Context represents the context of the current HTTP request. It holds request and
	// response objects, path, path parameters, data and registered handler.
	Context interface {
		netContext.Context

		// NetContext returns `http://blog.golang.org/context.Context` interface.
		NetContext() netContext.Context

		// SetNetContext sets `http://blog.golang.org/context.Context` interface.
		SetNetContext(netContext.Context)

		// Request returns `engine.Request` interface.
		Request() engine.Request

		// Request returns `engine.Response` interface.
		Response() engine.Response

		// Path returns the registered path for the handler.
		Path() string

		// SetPath sets the registered path for the handler.
		SetPath(string)

		// P returns path parameter by index.
		P(int) string

		// Param returns path parameter by name.
		Param(string) string

		// ParamNames returns path parameter names.
		ParamNames() []string

		// SetParamNames sets path parameter names.
		SetParamNames(...string)

		// ParamValues returns path parameter values.
		ParamValues() []string

		// SetParamValues sets path parameter values.
		SetParamValues(...string)

		// QueryParam returns the query param for the provided name. It is an alias
		// for `engine.URL#QueryParam()`.
		QueryParam(string) string

		// QueryParams returns the query parameters as map. It is an alias for `engine.URL#QueryParams()`.
		QueryParams() map[string][]string

		// FormValue returns the form field value for the provided name. It is an
		// alias for `engine.Request#FormValue()`.
		FormValue(string) string

		// FormParams returns the form parameters as map. It is an alias for `engine.Request#FormParams()`.
		FormParams() map[string][]string

		// FormFile returns the multipart form file for the provided name. It is an
		// alias for `engine.Request#FormFile()`.
		FormFile(string) (*multipart.FileHeader, error)

		// MultipartForm returns the multipart form. It is an alias for `engine.Request#MultipartForm()`.
		MultipartForm() (*multipart.Form, error)

		// Get retrieves data from the context.
		Get(string) interface{}

		// Set saves data in the context.
		Set(string, interface{})

		// Del deletes data from the context.
		Del(string)

		// Exists checks if that key exists in the context.
		Exists(string) bool

		// Bind binds the request body into provided type `i`. The default binder
		// does it based on Content-Type header.
		Bind(interface{}) error

		// Render renders a template with data and sends a text/html response with status
		// code. Templates can be registered using `Echo.SetRenderer()`.
		Render(int, string, interface{}) error

		// HTML sends an HTTP response with status code.
		HTML(int, string) error

		// String sends a string response with status code.
		String(int, string) error

		// JSON sends a JSON response with status code.
		JSON(int, interface{}) error

		// JSONBlob sends a JSON blob response with status code.
		JSONBlob(int, []byte) error

		// JSONP sends a JSONP response with status code. It uses `callback` to construct
		// the JSONP payload.
		JSONP(int, string, interface{}) error

		// XML sends an XML response with status code.
		XML(int, interface{}) error

		// XMLBlob sends a XML blob response with status code.
		XMLBlob(int, []byte) error

		// File sends a response with the content of the file.
		File(string) error

		// Attachment sends a response from `io.ReaderSeeker` as attachment, prompting
		// client to save the file.
		Attachment(io.ReadSeeker, string) error

		// NoContent sends a response with no body and a status code.
		NoContent(int) error

		// Redirect redirects the request with status code.
		Redirect(int, string) error

		// Error invokes the registered HTTP error handler. Generally used by middleware.
		Error(err error)

		// Handler returns the matched handler by router.
		Handler() HandlerFunc

		// SetHandler sets the matched handler by router.
		SetHandler(HandlerFunc)

		// Logger returns the `Logger` instance.
		Logger() *log.Logger

		// Echo returns the `Echo` instance.
		Echo() *Echo

		// ServeContent sends static content from `io.Reader` and handles caching
		// via `If-Modified-Since` request header. It automatically sets `Content-Type`
		// and `Last-Modified` response headers.
		ServeContent(io.ReadSeeker, string, time.Time) error

		// Reset resets the context after request completes. It must be called along
		// with `Echo#GetContext()` and `Echo#PutContext()`. See `Echo#ServeHTTP()`
		Reset(engine.Request, engine.Response)
	}

	context struct {
		netContext netContext.Context
		request    engine.Request
		response   engine.Response
		path       string
		pnames     []string
		pvalues    []string
		store      store
		handler    HandlerFunc
		echo       *Echo
	}

	store map[string]interface{}
)

const (
	indexPage = "index.html"
)

func (c *context) NetContext() netContext.Context {
	return c.netContext
}

func (c *context) SetNetContext(ctx netContext.Context) {
	c.netContext = ctx
}

func (c *context) Deadline() (deadline time.Time, ok bool) {
	return c.netContext.Deadline()
}

func (c *context) Done() <-chan struct{} {
	return c.netContext.Done()
}

func (c *context) Err() error {
	return c.netContext.Err()
}

func (c *context) Value(key interface{}) interface{} {
	return c.netContext.Value(key)
}

func (c *context) Request() engine.Request {
	return c.request
}

func (c *context) Response() engine.Response {
	return c.response
}

func (c *context) Path() string {
	return c.path
}

func (c *context) SetPath(p string) {
	c.path = p
}

func (c *context) P(i int) (value string) {
	l := len(c.pnames)
	if i < l {
		value = c.pvalues[i]
	}
	return
}

func (c *context) Param(name string) (value string) {
	l := len(c.pnames)
	for i, n := range c.pnames {
		if n == name && i < l {
			value = c.pvalues[i]
			break
		}
	}
	return
}

func (c *context) ParamNames() []string {
	return c.pnames
}

func (c *context) SetParamNames(names ...string) {
	c.pnames = names
}

func (c *context) ParamValues() []string {
	return c.pvalues
}

func (c *context) SetParamValues(values ...string) {
	c.pvalues = values
}

func (c *context) QueryParam(name string) string {
	return c.request.URL().QueryParam(name)
}

func (c *context) QueryParams() map[string][]string {
	return c.request.URL().QueryParams()
}

func (c *context) FormValue(name string) string {
	return c.request.FormValue(name)
}

func (c *context) FormParams() map[string][]string {
	return c.request.FormParams()
}

func (c *context) FormFile(name string) (*multipart.FileHeader, error) {
	return c.request.FormFile(name)
}

func (c *context) MultipartForm() (*multipart.Form, error) {
	return c.request.MultipartForm()
}

func (c *context) Set(key string, val interface{}) {
	if c.store == nil {
		c.store = make(store)
	}
	c.store[key] = val
}

func (c *context) Get(key string) interface{} {
	return c.store[key]
}

func (c *context) Del(key string) {
	delete(c.store, key)
}

func (c *context) Exists(key string) bool {
	_, ok := c.store[key]
	return ok
}

func (c *context) Bind(i interface{}) error {
	return c.echo.binder.Bind(i, c)
}

func (c *context) Render(code int, name string, data interface{}) (err error) {
	if c.echo.renderer == nil {
		return ErrRendererNotRegistered
	}
	buf := new(bytes.Buffer)
	if err = c.echo.renderer.Render(buf, name, data, c); err != nil {
		return
	}
	c.response.Header().Set(HeaderContentType, MIMETextHTMLCharsetUTF8)
	c.response.WriteHeader(code)
	_, err = c.response.Write(buf.Bytes())
	return
}

func (c *context) HTML(code int, html string) (err error) {
	c.response.Header().Set(HeaderContentType, MIMETextHTMLCharsetUTF8)
	c.response.WriteHeader(code)
	_, err = c.response.Write([]byte(html))
	return
}

func (c *context) String(code int, s string) (err error) {
	c.response.Header().Set(HeaderContentType, MIMETextPlainCharsetUTF8)
	c.response.WriteHeader(code)
	_, err = c.response.Write([]byte(s))
	return
}

func (c *context) JSON(code int, i interface{}) (err error) {
	b, err := json.Marshal(i)
	if c.echo.Debug() {
		b, err = json.MarshalIndent(i, "", "  ")
	}
	if err != nil {
		return err
	}
	return c.JSONBlob(code, b)
}

func (c *context) JSONBlob(code int, b []byte) (err error) {
	c.response.Header().Set(HeaderContentType, MIMEApplicationJSONCharsetUTF8)
	c.response.WriteHeader(code)
	_, err = c.response.Write(b)
	return
}

func (c *context) JSONP(code int, callback string, i interface{}) (err error) {
	b, err := json.Marshal(i)
	if err != nil {
		return err
	}
	c.response.Header().Set(HeaderContentType, MIMEApplicationJavaScriptCharsetUTF8)
	c.response.WriteHeader(code)
	if _, err = c.response.Write([]byte(callback + "(")); err != nil {
		return
	}
	if _, err = c.response.Write(b); err != nil {
		return
	}
	_, err = c.response.Write([]byte(");"))
	return
}

func (c *context) XML(code int, i interface{}) (err error) {
	b, err := xml.Marshal(i)
	if c.echo.Debug() {
		b, err = xml.MarshalIndent(i, "", "  ")
	}
	if err != nil {
		return err
	}
	return c.XMLBlob(code, b)
}

func (c *context) XMLBlob(code int, b []byte) (err error) {
	c.response.Header().Set(HeaderContentType, MIMEApplicationXMLCharsetUTF8)
	c.response.WriteHeader(code)
	if _, err = c.response.Write([]byte(xml.Header)); err != nil {
		return
	}
	_, err = c.response.Write(b)
	return
}

func (c *context) File(file string) error {
	f, err := os.Open(file)
	if err != nil {
		return ErrNotFound
	}
	defer f.Close()

	fi, _ := f.Stat()
	if fi.IsDir() {
		file = filepath.Join(file, "index.html")
		f, err = os.Open(file)
		if err != nil {
			return ErrNotFound
		}
		if fi, err = f.Stat(); err != nil {
			return err
		}
	}
	return c.ServeContent(f, fi.Name(), fi.ModTime())
}

func (c *context) Attachment(r io.ReadSeeker, name string) (err error) {
	c.response.Header().Set(HeaderContentType, ContentTypeByExtension(name))
	c.response.Header().Set(HeaderContentDisposition, "attachment; filename="+name)
	c.response.WriteHeader(http.StatusOK)
	_, err = io.Copy(c.response, r)
	return
}

func (c *context) NoContent(code int) error {
	c.response.WriteHeader(code)
	return nil
}

func (c *context) Redirect(code int, url string) error {
	if code < http.StatusMultipleChoices || code > http.StatusTemporaryRedirect {
		return ErrInvalidRedirectCode
	}
	c.response.Header().Set(HeaderLocation, url)
	c.response.WriteHeader(code)
	return nil
}

func (c *context) Error(err error) {
	c.echo.httpErrorHandler(err, c)
}

func (c *context) Echo() *Echo {
	return c.echo
}

func (c *context) Handler() HandlerFunc {
	return c.handler
}

func (c *context) SetHandler(h HandlerFunc) {
	c.handler = h
}

func (c *context) Logger() *log.Logger {
	return c.echo.logger
}

func (c *context) ServeContent(content io.ReadSeeker, name string, modtime time.Time) error {
	rq := c.Request()
	rs := c.Response()

	if t, err := time.Parse(http.TimeFormat, rq.Header().Get(HeaderIfModifiedSince)); err == nil && modtime.Before(t.Add(1*time.Second)) {
		rs.Header().Del(HeaderContentType)
		rs.Header().Del(HeaderContentLength)
		return c.NoContent(http.StatusNotModified)
	}

	rs.Header().Set(HeaderContentType, ContentTypeByExtension(name))
	rs.Header().Set(HeaderLastModified, modtime.UTC().Format(http.TimeFormat))
	rs.WriteHeader(http.StatusOK)
	_, err := io.Copy(rs, content)
	return err
}

// ContentTypeByExtension returns the MIME type associated with the file based on
// its extension. It returns `application/octet-stream` incase MIME type is not
// found.
func ContentTypeByExtension(name string) (t string) {
	if t = mime.TypeByExtension(filepath.Ext(name)); t == "" {
		t = MIMEOctetStream
	}
	return
}

func (c *context) Reset(rq engine.Request, rs engine.Response) {
	c.netContext = nil
	c.request = rq
	c.response = rs
	c.store = nil
	c.handler = notFoundHandler
}
<|endoftext|>