<commit_before>// +build ffmpeg

package ffmpeg

import (
	"fmt"
	"io"
	"net/url"
	"reflect"
	"strconv"
	"sync"
	"unsafe"
)

////////////////////////////////////////////////////////////////////////////////
// CGO

/*
#cgo pkg-config: libavformat
#include <libavformat/avformat.h>
*/
import "C"

////////////////////////////////////////////////////////////////////////////////
// TYPES

type (
	AVFormatContext C.struct_AVFormatContext
	AVInputFormat   C.struct_AVInputFormat
	AVOutputFormat  C.struct_AVOutputFormat
	AVStream        C.struct_AVStream
	AVIOContext     C.struct_AVIOContext
)

////////////////////////////////////////////////////////////////////////////////
// GLOBALS

var (
	once_init, once_deinit sync.Once
)

////////////////////////////////////////////////////////////////////////////////
// INIT AND DEINIT

// Register and Deregister
func AVFormatInit() {
	once_init.Do(func() {
		C.avformat_network_init()
	})
}

func AVFormatDeinit() {
	once_deinit.Do(func() {
		C.avformat_network_deinit()
	})
}

////////////////////////////////////////////////////////////////////////////////
// GET FORMATS

// AllMuxers returns all registered multiplexers
func AllMuxers() []*AVOutputFormat {
	muxers := make([]*AVOutputFormat, 0)
	ptr := unsafe.Pointer(nil)
	for {
		if muxer := C.av_muxer_iterate(&ptr); muxer == nil {
			break
		} else {
			muxers = append(muxers, (*AVOutputFormat)(muxer))
		}
	}
	return muxers
}

// AllDemuxers returns all registered demultiplexers
func AllDemuxers() []*AVInputFormat {
	demuxers := make([]*AVInputFormat, 0)
	ptr := unsafe.Pointer(nil)
	for {
		if demuxer := C.av_demuxer_iterate(&ptr); demuxer == nil {
			break
		} else {
			demuxers = append(demuxers, (*AVInputFormat)(demuxer))
		}
	}
	return demuxers
}

////////////////////////////////////////////////////////////////////////////////
// AVIO

func NewAVIOContext(url *url.URL, flags AVIOFlag) (*AVIOContext, error) {
	url_ := C.CString(url.String())
	defer C.free(unsafe.Pointer(url_))
	ctx := (*C.AVIOContext)(unsafe.Pointer(nil))
	if err := AVError(C.avio_open(
		&ctx,
		url_,
		C.int(flags),
	)); err != 0 {
		return nil, err
	} else {
		return (*AVIOContext)(ctx), nil
	}
}

func (this *AVIOContext) Free() {
	ctx := (*C.AVIOContext)(unsafe.Pointer(this))
	C.avio_context_free(&ctx)
}

////////////////////////////////////////////////////////////////////////////////
// AVFormatContext

// NewAVFormatContext creates a new empty format context
func NewAVFormatContext() *AVFormatContext {
	return (*AVFormatContext)(C.avformat_alloc_context())
}

// NewAVFormatOutputContext creates a new format context with
// context populated with output parameters
func NewAVFormatOutputContext(filename string, output_format *AVOutputFormat) (*AVFormatContext, error) {
	filename_ := C.CString(filename)
	defer C.free(unsafe.Pointer(filename_))
	ctx := (*C.AVFormatContext)(unsafe.Pointer(nil))
	if err := AVError(C.avformat_alloc_output_context2(
		&ctx,
		(*C.AVOutputFormat)(output_format),
		nil,
		filename_,
	)); err != 0 {
		return nil, err
	} else {
		return (*AVFormatContext)(ctx), nil
	}
}

// Free AVFormatContext
func (this *AVFormatContext) Free() {
	ctx := (*C.AVFormatContext)(unsafe.Pointer(this))
	C.avformat_free_context(ctx)
}

// Open Input
func (this *AVFormatContext) OpenInput(filename string, input_format *AVInputFormat) error {
	filename_ := C.CString(filename)
	defer C.free(unsafe.Pointer(filename_))
	ctx := (*C.AVFormatContext)(unsafe.Pointer(this))
	dict := new(AVDictionary)
	if err := AVError(C.avformat_open_input(
		&ctx,
		filename_,
		(*C.struct_AVInputFormat)(input_format),
		(**C.struct_AVDictionary)(unsafe.Pointer(dict)),
	)); err != 0 {
		return err
	} else {
		return nil
	}
}

// Close Input
func (this *AVFormatContext) CloseInput() {
	ctx := (*C.AVFormatContext)(unsafe.Pointer(this))
	C.avformat_close_input(&ctx)
}

// Write header
func (this *AVFormatContext) WriteHeader(dict *AVDictionary) error {
<<<<<<< HEAD
	// TODO
	return nil
=======
	ctx := (*C.AVFormatContext)(this)
	dictctx := (**C.AVDictionary)(nil)
	if dict != nil {
		dictctx = &dict.ctx
	}
	if ret := AVError(C.avformat_write_header(ctx, dictctx)); ret != 0 {
		return ret
	} else {
		return nil
	}
}

// Write trailer
func (this *AVFormatContext) WriteTrailer() error {
	ctx := (*C.AVFormatContext)(this)
	if ret := AVError(C.av_write_trailer(ctx)); ret != 0 {
		return ret
	} else {
		return nil
	}
>>>>>>> d38a319... Updated ffmpeg
}

// Return Metadata Dictionary
func (this *AVFormatContext) Metadata() *AVDictionary {
	return &AVDictionary{ctx: this.metadata}
}

// Find Stream Info
func (this *AVFormatContext) FindStreamInfo() (*AVDictionary, error) {
	ctx := (*C.AVFormatContext)(unsafe.Pointer(this))
	dict := new(AVDictionary)
	if err := AVError(C.avformat_find_stream_info(
		ctx,
		(**C.struct_AVDictionary)(unsafe.Pointer(dict)),
	)); err != 0 {
		return nil, err
	} else {
		return dict, nil
	}
}

// Return Filename
func (this *AVFormatContext) Filename() string {
	return C.GoString(&this.filename[0])
}

// Return URL
func (this *AVFormatContext) Url() *url.URL {
	url_ := C.GoString(this.url)
	if url_ == "" {
		return nil
	} else if url, err := url.Parse(url_); err != nil {
		return nil
	} else {
		return url
	}
}

// Return number of streams
func (this *AVFormatContext) NumStreams() uint {
	ctx := (*C.AVFormatContext)(unsafe.Pointer(this))
	return uint(ctx.nb_streams)
}

// Return Streams
func (this *AVFormatContext) Streams() []*AVStream {
	var streams []*AVStream

	// Get context
	ctx := (*C.AVFormatContext)(unsafe.Pointer(this))

	// Make a fake slice
	if nb_streams := this.NumStreams(); nb_streams > 0 {
		// Make a fake slice
		sliceHeader := (*reflect.SliceHeader)((unsafe.Pointer(&streams)))
		sliceHeader.Cap = int(nb_streams)
		sliceHeader.Len = int(nb_streams)
		sliceHeader.Data = uintptr(unsafe.Pointer(ctx.streams))
	}
	return streams
}

// Return Input Format
func (this *AVFormatContext) InputFormat() *AVInputFormat {
	ctx := (*C.AVFormatContext)(unsafe.Pointer(this))
	return (*AVInputFormat)(ctx.iformat)
}

// Return Output Format
func (this *AVFormatContext) OutputFormat() *AVOutputFormat {
	ctx := (*C.AVFormatContext)(unsafe.Pointer(this))
	return (*AVOutputFormat)(ctx.oformat)
}

// IOContext returns the current IO context
func (this *AVFormatContext) IOContext() *AVIOContext {
	ctx := (*C.AVFormatContext)(this)
	return (*AVIOContext)(ctx.pb)
}

// SetIOContext sets the current IO context
func (this *AVFormatContext) SetIOContext(avio *AVIOContext) {
	ctx := (*C.AVFormatContext)(this)
	ctx.pb = (*C.AVIOContext)(avio)
}

func (this *AVFormatContext) String() string {
	str := "<AVFormatContext"
	str += " filename=" + strconv.Quote(this.Filename())
	if u := this.Url(); u != nil {
		str += " url=" + strconv.Quote(u.String())
	}
	if ifmt := this.InputFormat(); ifmt != nil {
		str += " iformat=" + fmt.Sprint(ifmt)
	}
	if ofmt := this.OutputFormat(); ofmt != nil {
		str += " oformat=" + fmt.Sprint(ofmt)
	}
	str += " num_streams=" + fmt.Sprint(this.NumStreams())
	str += " metadata=" + fmt.Sprint(this.Metadata())
	return str + ">"
}

func (this *AVFormatContext) Dump(index int) {
	if this.OutputFormat() != nil {
		AVDumpFormat(this, index, this.Url().String(), true)
	} else if this.InputFormat() != nil {
		AVDumpFormat(this, index, this.Url().String(), false)
	}
}

func (this *AVFormatContext) ReadPacket(packet *AVPacket) error {
	ctx := (*C.AVFormatContext)(unsafe.Pointer(this))
	packetctx := (*C.AVPacket)(packet)
	if ret := int(C.av_read_frame(ctx, packetctx)); ret >= 0 {
		return nil
	} else {
		return io.EOF
	}
}

////////////////////////////////////////////////////////////////////////////////
// AVInputFormat and AVOutputFormat

// Return input formats
func EnumerateInputFormats() []*AVInputFormat {
	a := make([]*AVInputFormat, 0, 100)
	p := unsafe.Pointer(uintptr(0))
	for {
		if iformat := (*AVInputFormat)(C.av_demuxer_iterate(&p)); iformat == nil {
			break
		} else {
			a = append(a, iformat)
		}
	}
	return a
}

// Return output formats
func EnumerateOutputFormats() []*AVOutputFormat {
	a := make([]*AVOutputFormat, 0, 100)
	p := unsafe.Pointer(uintptr(0))
	for {
		if oformat := (*AVOutputFormat)(C.av_muxer_iterate(&p)); oformat == nil {
			break
		} else {
			a = append(a, oformat)
		}
	}
	return a
}

func (this *AVInputFormat) Name() string {
	return C.GoString(this.name)
}

func (this *AVInputFormat) Description() string {
	return C.GoString(this.long_name)
}

func (this *AVInputFormat) Ext() string {
	return C.GoString(this.extensions)
}

func (this *AVInputFormat) MimeType() string {
	return C.GoString(this.mime_type)
}

func (this *AVInputFormat) Flags() AVFormatFlag {
	return AVFormatFlag(this.flags)
}

func (this *AVOutputFormat) Name() string {
	return C.GoString(this.name)
}

func (this *AVOutputFormat) Description() string {
	return C.GoString(this.long_name)
}

func (this *AVOutputFormat) Ext() string {
	return C.GoString(this.extensions)
}

func (this *AVOutputFormat) MimeType() string {
	return C.GoString(this.mime_type)
}

func (this *AVOutputFormat) Flags() AVFormatFlag {
	return AVFormatFlag(this.flags)
}

func (this *AVInputFormat) String() string {
	str := "<AVInputFormat"
	str += " name=" + strconv.Quote(this.Name())
	str += " description=" + strconv.Quote(this.Description())
	str += " ext=" + strconv.Quote(this.Ext())
	str += " mime_type=" + strconv.Quote(this.MimeType())
	str += " flags=" + fmt.Sprint(this.Flags())
	return str + ">"
}

func (this *AVOutputFormat) String() string {
	str := "<AVOutputFormat"
	str += " name=" + strconv.Quote(this.Name())
	str += " description=" + strconv.Quote(this.Description())
	str += " ext=" + strconv.Quote(this.Ext())
	str += " mime_type=" + strconv.Quote(this.MimeType())
	str += " flags=" + fmt.Sprint(this.Flags())
	return str + ">"
}

////////////////////////////////////////////////////////////////////////////////
// AVStream

func NewStream(ctx *AVFormatContext, codec *AVCodec) *AVStream {
	return (*AVStream)(C.avformat_new_stream(
		(*C.AVFormatContext)(ctx),
		(*C.AVCodec)(codec),
	))
}

func (this *AVStream) Index() int {
	ctx := (*C.AVStream)(unsafe.Pointer(this))
	return int(ctx.index)
}

func (this *AVStream) Id() int {
	ctx := (*C.AVStream)(unsafe.Pointer(this))
	return int(ctx.id)
}

func (this *AVStream) Metadata() *AVDictionary {
	return &AVDictionary{ctx: this.metadata}
}

func (this *AVStream) CodecPar() *AVCodecParameters {
	ctx := (*C.AVStream)(unsafe.Pointer(this))
	return (*AVCodecParameters)(ctx.codecpar)
}

func (this *AVStream) Disposition() AVDisposition {
	ctx := (*C.AVStream)(unsafe.Pointer(this))
	return AVDisposition(ctx.disposition)
}

func (this *AVStream) AttachedPicture() *AVPacket {
	ctx := (*C.AVStream)(unsafe.Pointer(this))
	if AVDisposition(ctx.disposition)&AV_DISPOSITION_ATTACHED_PIC == 0 {
		return nil
	} else {
		return (*AVPacket)(&this.attached_pic)
	}
}

func (this *AVStream) Duration() int64 {
	ctx := (*C.AVStream)(unsafe.Pointer(this))
	return int64(ctx.duration)
}

func (this *AVStream) NumFrames() int64 {
	ctx := (*C.AVStream)(unsafe.Pointer(this))
	return int64(ctx.nb_frames)
}

func (this *AVStream) StartTime() int64 {
	ctx := (*C.AVStream)(unsafe.Pointer(this))
	return int64(ctx.start_time)
}

func (this *AVStream) TimeBase() AVRational {
	ctx := (*C.AVStream)(unsafe.Pointer(this))
	return AVRational(ctx.time_base)
}

func (this *AVStream) MeanFrameRate() AVRational {
	ctx := (*C.AVStream)(unsafe.Pointer(this))
	return AVRational(ctx.avg_frame_rate)
}

func (this *AVStream) String() string {
	str := "<AVStream"
	str += " index=" + fmt.Sprint(this.Index())
	str += " id=" + fmt.Sprint(this.Id())
	str += " metadata=" + fmt.Sprint(this.Metadata())
	str += " codecpar=" + fmt.Sprint(this.CodecPar())
	if d := this.Disposition(); d != AV_DISPOSITION_NONE {
		str += " disposition=" + fmt.Sprint(this.Disposition())
	}
	return str + ">"
}

////////////////////////////////////////////////////////////////////////////////
// UTILITY METHODS

func AVDumpFormat(ctx *AVFormatContext, index int, filename string, is_output bool) {
	filename_ := C.CString(filename)
	defer C.free(unsafe.Pointer(filename_))
	is_output_ := 0
	if is_output {
		is_output_ = 1
	}
	C.av_dump_format((*C.AVFormatContext)(ctx), C.int(index), filename_, C.int(is_output_))
}
<commit_msg>Updated avformat<commit_after>// +build ffmpeg

package ffmpeg

import (
	"fmt"
	"io"
	"net/url"
	"reflect"
	"strconv"
	"sync"
	"unsafe"
)

////////////////////////////////////////////////////////////////////////////////
// CGO

/*
#cgo pkg-config: libavformat
#include <libavformat/avformat.h>
*/
import "C"

////////////////////////////////////////////////////////////////////////////////
// TYPES

type (
	AVFormatContext C.struct_AVFormatContext
	AVInputFormat   C.struct_AVInputFormat
	AVOutputFormat  C.struct_AVOutputFormat
	AVStream        C.struct_AVStream
)

////////////////////////////////////////////////////////////////////////////////
// GLOBALS

var (
	once_init, once_deinit sync.Once
)

////////////////////////////////////////////////////////////////////////////////
// INIT AND DEINIT

// Register and Deregister
func AVFormatInit() {
	once_init.Do(func() {
		C.avformat_network_init()
	})
}

func AVFormatDeinit() {
	once_deinit.Do(func() {
		C.avformat_network_deinit()
	})
}

////////////////////////////////////////////////////////////////////////////////
// GET FORMATS

// AllMuxers returns all registered multiplexers
func AllMuxers() []*AVOutputFormat {
	muxers := make([]*AVOutputFormat, 0)
	ptr := unsafe.Pointer(nil)
	for {
		if muxer := C.av_muxer_iterate(&ptr); muxer == nil {
			break
		} else {
			muxers = append(muxers, (*AVOutputFormat)(muxer))
		}
	}
	return muxers
}

// AllDemuxers returns all registered demultiplexers
func AllDemuxers() []*AVInputFormat {
	demuxers := make([]*AVInputFormat, 0)
	ptr := unsafe.Pointer(nil)
	for {
		if demuxer := C.av_demuxer_iterate(&ptr); demuxer == nil {
			break
		} else {
			demuxers = append(demuxers, (*AVInputFormat)(demuxer))
		}
	}
	return demuxers
}

////////////////////////////////////////////////////////////////////////////////
// AVFormatContext

// NewAVFormatContext creates a new empty format context
func NewAVFormatContext() *AVFormatContext {
	return (*AVFormatContext)(C.avformat_alloc_context())
}

// NewAVFormatOutputContext creates a new format context with
// context populated with output parameters
func NewAVFormatOutputContext(filename string, output_format *AVOutputFormat) (*AVFormatContext, error) {
	filename_ := C.CString(filename)
	defer C.free(unsafe.Pointer(filename_))
	ctx := (*C.AVFormatContext)(unsafe.Pointer(nil))
	if err := AVError(C.avformat_alloc_output_context2(
		&ctx,
		(*C.AVOutputFormat)(output_format),
		nil,
		filename_,
	)); err != 0 {
		return nil, err
	} else {
		return (*AVFormatContext)(ctx), nil
	}
}

// Free AVFormatContext
func (this *AVFormatContext) Free() {
	ctx := (*C.AVFormatContext)(unsafe.Pointer(this))
	C.avformat_free_context(ctx)
}

// Open Input
func (this *AVFormatContext) OpenInput(filename string, input_format *AVInputFormat) error {
	filename_ := C.CString(filename)
	defer C.free(unsafe.Pointer(filename_))
	ctx := (*C.AVFormatContext)(unsafe.Pointer(this))
	dict := new(AVDictionary)
	if err := AVError(C.avformat_open_input(
		&ctx,
		filename_,
		(*C.struct_AVInputFormat)(input_format),
		(**C.struct_AVDictionary)(unsafe.Pointer(dict)),
	)); err != 0 {
		return err
	} else {
		return nil
	}
}

// Close Input
func (this *AVFormatContext) CloseInput() {
	ctx := (*C.AVFormatContext)(unsafe.Pointer(this))
	C.avformat_close_input(&ctx)
}

// Write header
func (this *AVFormatContext) WriteHeader(dict *AVDictionary) error {
	ctx := (*C.AVFormatContext)(this)
	dictctx := (**C.AVDictionary)(nil)
	if dict != nil {
		dictctx = &dict.ctx
	}
	if ret := AVError(C.avformat_write_header(ctx, dictctx)); ret != 0 {
		return ret
	} else {
		return nil
	}
}

// Write trailer
func (this *AVFormatContext) WriteTrailer() error {
	ctx := (*C.AVFormatContext)(this)
	if ret := AVError(C.av_write_trailer(ctx)); ret != 0 {
		return ret
	} else {
		return nil
	}
}

// Return Metadata Dictionary
func (this *AVFormatContext) Metadata() *AVDictionary {
	return &AVDictionary{ctx: this.metadata}
}

// Find Stream Info
func (this *AVFormatContext) FindStreamInfo() (*AVDictionary, error) {
	ctx := (*C.AVFormatContext)(unsafe.Pointer(this))
	dict := new(AVDictionary)
	if err := AVError(C.avformat_find_stream_info(
		ctx,
		(**C.struct_AVDictionary)(unsafe.Pointer(dict)),
	)); err != 0 {
		return nil, err
	} else {
		return dict, nil
	}
}

// Return Filename
func (this *AVFormatContext) Filename() string {
	return C.GoString(&this.filename[0])
}

// Return URL
func (this *AVFormatContext) Url() *url.URL {
	url_ := C.GoString(this.url)
	if url_ == "" {
		return nil
	} else if url, err := url.Parse(url_); err != nil {
		return nil
	} else {
		return url
	}
}

// Return number of streams
func (this *AVFormatContext) NumStreams() uint {
	ctx := (*C.AVFormatContext)(unsafe.Pointer(this))
	return uint(ctx.nb_streams)
}

// Return Streams
func (this *AVFormatContext) Streams() []*AVStream {
	var streams []*AVStream

	// Get context
	ctx := (*C.AVFormatContext)(unsafe.Pointer(this))

	// Make a fake slice
	if nb_streams := this.NumStreams(); nb_streams > 0 {
		// Make a fake slice
		sliceHeader := (*reflect.SliceHeader)((unsafe.Pointer(&streams)))
		sliceHeader.Cap = int(nb_streams)
		sliceHeader.Len = int(nb_streams)
		sliceHeader.Data = uintptr(unsafe.Pointer(ctx.streams))
	}
	return streams
}

// Return Input Format
func (this *AVFormatContext) InputFormat() *AVInputFormat {
	ctx := (*C.AVFormatContext)(unsafe.Pointer(this))
	return (*AVInputFormat)(ctx.iformat)
}

// Return Output Format
func (this *AVFormatContext) OutputFormat() *AVOutputFormat {
	ctx := (*C.AVFormatContext)(unsafe.Pointer(this))
	return (*AVOutputFormat)(ctx.oformat)
}

// IOContext returns the current IO context
func (this *AVFormatContext) IOContext() *AVIOContext {
	ctx := (*C.AVFormatContext)(this)
	return (*AVIOContext)(ctx.pb)
}

// SetIOContext sets the current IO context
func (this *AVFormatContext) SetIOContext(avio *AVIOContext) {
	ctx := (*C.AVFormatContext)(this)
	ctx.pb = (*C.AVIOContext)(avio)
}

func (this *AVFormatContext) String() string {
	str := "<AVFormatContext"
	str += " filename=" + strconv.Quote(this.Filename())
	if u := this.Url(); u != nil {
		str += " url=" + strconv.Quote(u.String())
	}
	if ifmt := this.InputFormat(); ifmt != nil {
		str += " iformat=" + fmt.Sprint(ifmt)
	}
	if ofmt := this.OutputFormat(); ofmt != nil {
		str += " oformat=" + fmt.Sprint(ofmt)
	}
	str += " num_streams=" + fmt.Sprint(this.NumStreams())
	str += " metadata=" + fmt.Sprint(this.Metadata())
	return str + ">"
}

func (this *AVFormatContext) Dump(index int) {
	if this.OutputFormat() != nil {
		AVDumpFormat(this, index, this.Url().String(), true)
	} else if this.InputFormat() != nil {
		AVDumpFormat(this, index, this.Url().String(), false)
	}
}

func (this *AVFormatContext) ReadPacket(packet *AVPacket) error {
	ctx := (*C.AVFormatContext)(unsafe.Pointer(this))
	packetctx := (*C.AVPacket)(packet)
	if ret := int(C.av_read_frame(ctx, packetctx)); ret >= 0 {
		return nil
	} else {
		return io.EOF
	}
}

////////////////////////////////////////////////////////////////////////////////
// AVInputFormat and AVOutputFormat

// Return input formats
func EnumerateInputFormats() []*AVInputFormat {
	a := make([]*AVInputFormat, 0, 100)
	p := unsafe.Pointer(uintptr(0))
	for {
		if iformat := (*AVInputFormat)(C.av_demuxer_iterate(&p)); iformat == nil {
			break
		} else {
			a = append(a, iformat)
		}
	}
	return a
}

// Return output formats
func EnumerateOutputFormats() []*AVOutputFormat {
	a := make([]*AVOutputFormat, 0, 100)
	p := unsafe.Pointer(uintptr(0))
	for {
		if oformat := (*AVOutputFormat)(C.av_muxer_iterate(&p)); oformat == nil {
			break
		} else {
			a = append(a, oformat)
		}
	}
	return a
}

func (this *AVInputFormat) Name() string {
	return C.GoString(this.name)
}

func (this *AVInputFormat) Description() string {
	return C.GoString(this.long_name)
}

func (this *AVInputFormat) Ext() string {
	return C.GoString(this.extensions)
}

func (this *AVInputFormat) MimeType() string {
	return C.GoString(this.mime_type)
}

func (this *AVInputFormat) Flags() AVFormatFlag {
	return AVFormatFlag(this.flags)
}

func (this *AVOutputFormat) Name() string {
	return C.GoString(this.name)
}

func (this *AVOutputFormat) Description() string {
	return C.GoString(this.long_name)
}

func (this *AVOutputFormat) Ext() string {
	return C.GoString(this.extensions)
}

func (this *AVOutputFormat) MimeType() string {
	return C.GoString(this.mime_type)
}

func (this *AVOutputFormat) Flags() AVFormatFlag {
	return AVFormatFlag(this.flags)
}

func (this *AVInputFormat) String() string {
	str := "<AVInputFormat"
	str += " name=" + strconv.Quote(this.Name())
	str += " description=" + strconv.Quote(this.Description())
	str += " ext=" + strconv.Quote(this.Ext())
	str += " mime_type=" + strconv.Quote(this.MimeType())
	str += " flags=" + fmt.Sprint(this.Flags())
	return str + ">"
}

func (this *AVOutputFormat) String() string {
	str := "<AVOutputFormat"
	str += " name=" + strconv.Quote(this.Name())
	str += " description=" + strconv.Quote(this.Description())
	str += " ext=" + strconv.Quote(this.Ext())
	str += " mime_type=" + strconv.Quote(this.MimeType())
	str += " flags=" + fmt.Sprint(this.Flags())
	return str + ">"
}

////////////////////////////////////////////////////////////////////////////////
// AVStream

func NewStream(ctx *AVFormatContext, codec *AVCodec) *AVStream {
	return (*AVStream)(C.avformat_new_stream(
		(*C.AVFormatContext)(ctx),
		(*C.AVCodec)(codec),
	))
}

func (this *AVStream) Index() int {
	ctx := (*C.AVStream)(unsafe.Pointer(this))
	return int(ctx.index)
}

func (this *AVStream) Id() int {
	ctx := (*C.AVStream)(unsafe.Pointer(this))
	return int(ctx.id)
}

func (this *AVStream) Metadata() *AVDictionary {
	return &AVDictionary{ctx: this.metadata}
}

func (this *AVStream) CodecPar() *AVCodecParameters {
	ctx := (*C.AVStream)(unsafe.Pointer(this))
	return (*AVCodecParameters)(ctx.codecpar)
}

func (this *AVStream) Disposition() AVDisposition {
	ctx := (*C.AVStream)(unsafe.Pointer(this))
	return AVDisposition(ctx.disposition)
}

func (this *AVStream) AttachedPicture() *AVPacket {
	ctx := (*C.AVStream)(unsafe.Pointer(this))
	if AVDisposition(ctx.disposition)&AV_DISPOSITION_ATTACHED_PIC == 0 {
		return nil
	} else {
		return (*AVPacket)(&this.attached_pic)
	}
}

func (this *AVStream) Duration() int64 {
	ctx := (*C.AVStream)(unsafe.Pointer(this))
	return int64(ctx.duration)
}

func (this *AVStream) NumFrames() int64 {
	ctx := (*C.AVStream)(unsafe.Pointer(this))
	return int64(ctx.nb_frames)
}

func (this *AVStream) StartTime() int64 {
	ctx := (*C.AVStream)(unsafe.Pointer(this))
	return int64(ctx.start_time)
}

func (this *AVStream) TimeBase() AVRational {
	ctx := (*C.AVStream)(unsafe.Pointer(this))
	return AVRational(ctx.time_base)
}

func (this *AVStream) MeanFrameRate() AVRational {
	ctx := (*C.AVStream)(unsafe.Pointer(this))
	return AVRational(ctx.avg_frame_rate)
}

func (this *AVStream) String() string {
	str := "<AVStream"
	str += " index=" + fmt.Sprint(this.Index())
	str += " id=" + fmt.Sprint(this.Id())
	str += " metadata=" + fmt.Sprint(this.Metadata())
	str += " codecpar=" + fmt.Sprint(this.CodecPar())
	if d := this.Disposition(); d != AV_DISPOSITION_NONE {
		str += " disposition=" + fmt.Sprint(this.Disposition())
	}
	return str + ">"
}

////////////////////////////////////////////////////////////////////////////////
// UTILITY METHODS

func AVDumpFormat(ctx *AVFormatContext, index int, filename string, is_output bool) {
	filename_ := C.CString(filename)
	defer C.free(unsafe.Pointer(filename_))
	is_output_ := 0
	if is_output {
		is_output_ = 1
	}
	C.av_dump_format((*C.AVFormatContext)(ctx), C.int(index), filename_, C.int(is_output_))
}
<|endoftext|>


<commit_before>package types

import (
	"github.com/openshift/installer/pkg/ipnet"
	"github.com/openshift/installer/pkg/types/aws"
	"github.com/openshift/installer/pkg/types/libvirt"
	"github.com/openshift/installer/pkg/types/none"
	"github.com/openshift/installer/pkg/types/openstack"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
	// InstallConfigVersion is the version supported by this package.
	InstallConfigVersion = "v1beta3"
)

var (
	// PlatformNames is a slice with all the visibly-supported
	// platform names in alphabetical order. This is the list of
	// platforms presented to the user in the interactive wizard.
	PlatformNames = []string{
		aws.Name,
	}
	// HiddenPlatformNames is a slice with all the
	// hidden-but-supported platform names. This list isn't presented
	// to the user in the interactive wizard.
	HiddenPlatformNames = []string{
		none.Name,
		openstack.Name,
	}
)

// InstallConfig is the configuration for an OpenShift install.
type InstallConfig struct {
	// +optional
	metav1.TypeMeta `json:",inline"`

	metav1.ObjectMeta `json:"metadata"`

	// SSHKey is the public ssh key to provide access to instances.
	// +optional
	SSHKey string `json:"sshKey,omitempty"`

	// BaseDomain is the base domain to which the cluster should belong.
	BaseDomain string `json:"baseDomain"`

	// Networking defines the pod network provider in the cluster.
	*Networking `json:"networking,omitempty"`

	// ControlPlane is the configuration for the machines that comprise the
	// control plane.
	// +optional
	ControlPlane *MachinePool `json:"controlPlane,omitempty"`

	// Compute is the list of compute MachinePools that need to be installed.
	// +optional
	Compute []MachinePool `json:"compute,omitempty"`

	// Platform is the configuration for the specific platform upon which to
	// perform the installation.
	Platform `json:"platform"`

	// PullSecret is the secret to use when pulling images.
	PullSecret string `json:"pullSecret"`
}

// Platform is the configuration for the specific platform upon which to perform
// the installation. Only one of the platform configuration should be set.
type Platform struct {
	// AWS is the configuration used when installing on AWS.
	// +optional
	AWS *aws.Platform `json:"aws,omitempty"`

	// Libvirt is the configuration used when installing on libvirt.
	// +optional
	Libvirt *libvirt.Platform `json:"libvirt,omitempty"`

	// None is the empty configuration used when installing on an unsupported
	// platform.
	None *none.Platform `json:"none,omitempty"`

	// OpenStack is the configuration used when installing on OpenStack.
	// +optional
	OpenStack *openstack.Platform `json:"openstack,omitempty"`
}

// Name returns a string representation of the platform (e.g. "aws" if
// AWS is non-nil).  It returns an empty string if no platform is
// configured.
func (p *Platform) Name() string {
	if p == nil {
		return ""
	}
	if p.AWS != nil {
		return aws.Name
	}
	if p.Libvirt != nil {
		return libvirt.Name
	}
	if p.None != nil {
		return none.Name
	}
	if p.OpenStack != nil {
		return openstack.Name
	}
	return ""
}

// Networking defines the pod network provider in the cluster.
type Networking struct {
	// MachineCIDR is the IP address space from which to assign machine IPs.
	// +optional
	// Default is 10.0.0.0/16 for all platforms other than Libvirt.
	// For Libvirt, the default is 192.168.126.0/24.
	MachineCIDR *ipnet.IPNet `json:"machineCIDR,omitempty"`

	// Type is the network type to install
	// +optional
	// Default is OpenShiftSDN.
	Type string `json:"type,omitempty"`

	// ServiceCIDR is the IP address space from which to assign service IPs.
	// +optional
	// Default is 172.30.0.0/16.
	ServiceCIDR *ipnet.IPNet `json:"serviceCIDR,omitempty"`

	// ClusterNetworks is the IP address space from which to assign pod IPs.
	// +optional
	// Default is a single cluster network with a CIDR of 10.128.0.0/14
	// and a host subnet length of 9.
	ClusterNetworks []ClusterNetworkEntry `json:"clusterNetworks,omitempty"`
}

// ClusterNetworkEntry is a single IP address block for pod IP blocks. IP blocks
// are allocated with size 2^HostSubnetLength.
type ClusterNetworkEntry struct {
	// The IP block address pool
	CIDR ipnet.IPNet `json:"cidr"`

	// The size of blocks to allocate from the larger pool.
	// This is the length in bits - so a 9 here will allocate a /23.
	HostSubnetLength int32 `json:"hostSubnetLength"`
}
<commit_msg>types: add ClusterDomain helper for InstallConfig<commit_after>package types

import (
	"fmt"

	"github.com/openshift/installer/pkg/ipnet"
	"github.com/openshift/installer/pkg/types/aws"
	"github.com/openshift/installer/pkg/types/libvirt"
	"github.com/openshift/installer/pkg/types/none"
	"github.com/openshift/installer/pkg/types/openstack"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
	// InstallConfigVersion is the version supported by this package.
	InstallConfigVersion = "v1beta3"
)

var (
	// PlatformNames is a slice with all the visibly-supported
	// platform names in alphabetical order. This is the list of
	// platforms presented to the user in the interactive wizard.
	PlatformNames = []string{
		aws.Name,
	}
	// HiddenPlatformNames is a slice with all the
	// hidden-but-supported platform names. This list isn't presented
	// to the user in the interactive wizard.
	HiddenPlatformNames = []string{
		none.Name,
		openstack.Name,
	}
)

// InstallConfig is the configuration for an OpenShift install.
type InstallConfig struct {
	// +optional
	metav1.TypeMeta `json:",inline"`

	metav1.ObjectMeta `json:"metadata"`

	// SSHKey is the public ssh key to provide access to instances.
	// +optional
	SSHKey string `json:"sshKey,omitempty"`

	// BaseDomain is the base domain to which the cluster should belong.
	BaseDomain string `json:"baseDomain"`

	// Networking defines the pod network provider in the cluster.
	*Networking `json:"networking,omitempty"`

	// ControlPlane is the configuration for the machines that comprise the
	// control plane.
	// +optional
	ControlPlane *MachinePool `json:"controlPlane,omitempty"`

	// Compute is the list of compute MachinePools that need to be installed.
	// +optional
	Compute []MachinePool `json:"compute,omitempty"`

	// Platform is the configuration for the specific platform upon which to
	// perform the installation.
	Platform `json:"platform"`

	// PullSecret is the secret to use when pulling images.
	PullSecret string `json:"pullSecret"`
}

// ClusterDomain returns the DNS domain that all records for a cluster must belong to.
func (c *InstallConfig) ClusterDomain() string {
	return fmt.Sprintf("%s.%s", c.ObjectMeta.Name, c.BaseDomain)
}

// Platform is the configuration for the specific platform upon which to perform
// the installation. Only one of the platform configuration should be set.
type Platform struct {
	// AWS is the configuration used when installing on AWS.
	// +optional
	AWS *aws.Platform `json:"aws,omitempty"`

	// Libvirt is the configuration used when installing on libvirt.
	// +optional
	Libvirt *libvirt.Platform `json:"libvirt,omitempty"`

	// None is the empty configuration used when installing on an unsupported
	// platform.
	None *none.Platform `json:"none,omitempty"`

	// OpenStack is the configuration used when installing on OpenStack.
	// +optional
	OpenStack *openstack.Platform `json:"openstack,omitempty"`
}

// Name returns a string representation of the platform (e.g. "aws" if
// AWS is non-nil).  It returns an empty string if no platform is
// configured.
func (p *Platform) Name() string {
	if p == nil {
		return ""
	}
	if p.AWS != nil {
		return aws.Name
	}
	if p.Libvirt != nil {
		return libvirt.Name
	}
	if p.None != nil {
		return none.Name
	}
	if p.OpenStack != nil {
		return openstack.Name
	}
	return ""
}

// Networking defines the pod network provider in the cluster.
type Networking struct {
	// MachineCIDR is the IP address space from which to assign machine IPs.
	// +optional
	// Default is 10.0.0.0/16 for all platforms other than Libvirt.
	// For Libvirt, the default is 192.168.126.0/24.
	MachineCIDR *ipnet.IPNet `json:"machineCIDR,omitempty"`

	// Type is the network type to install
	// +optional
	// Default is OpenShiftSDN.
	Type string `json:"type,omitempty"`

	// ServiceCIDR is the IP address space from which to assign service IPs.
	// +optional
	// Default is 172.30.0.0/16.
	ServiceCIDR *ipnet.IPNet `json:"serviceCIDR,omitempty"`

	// ClusterNetworks is the IP address space from which to assign pod IPs.
	// +optional
	// Default is a single cluster network with a CIDR of 10.128.0.0/14
	// and a host subnet length of 9.
	ClusterNetworks []ClusterNetworkEntry `json:"clusterNetworks,omitempty"`
}

// ClusterNetworkEntry is a single IP address block for pod IP blocks. IP blocks
// are allocated with size 2^HostSubnetLength.
type ClusterNetworkEntry struct {
	// The IP block address pool
	CIDR ipnet.IPNet `json:"cidr"`

	// The size of blocks to allocate from the larger pool.
	// This is the length in bits - so a 9 here will allocate a /23.
	HostSubnetLength int32 `json:"hostSubnetLength"`
}
<|endoftext|>


<commit_before>package archive

import (
	"archive/tar"
	"fmt"
	"io"
	"io/fs"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/containers/storage/pkg/idtools"
	"github.com/containers/storage/pkg/pools"
	"github.com/containers/storage/pkg/system"
	"github.com/sirupsen/logrus"
)

// UnpackLayer unpack `layer` to a `dest`. The stream `layer` can be
// compressed or uncompressed.
// Returns the size in bytes of the contents of the layer.
func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, err error) {
	tr := tar.NewReader(layer)
	trBuf := pools.BufioReader32KPool.Get(tr)
	defer pools.BufioReader32KPool.Put(trBuf)

	var dirs []*tar.Header
	unpackedPaths := make(map[string]struct{})

	if options == nil {
		options = &TarOptions{}
	}
	if options.ExcludePatterns == nil {
		options.ExcludePatterns = []string{}
	}
	idMappings := idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps)

	aufsTempdir := ""
	aufsHardlinks := make(map[string]*tar.Header)
	buffer := make([]byte, 1<<20)

	// Iterate through the files in the archive.
	for {
		hdr, err := tr.Next()
		if err == io.EOF {
			// end of tar archive
			break
		}
		if err != nil {
			return 0, err
		}

		size += hdr.Size

		// Normalize name, for safety and for a simple is-root check
		hdr.Name = filepath.Clean(hdr.Name)

		// Windows does not support filenames with colons in them. Ignore
		// these files. This is not a problem though (although it might
		// appear that it is). Let's suppose a client is running docker pull.
		// The daemon it points to is Windows. Would it make sense for the
		// client to be doing a docker pull Ubuntu for example (which has files
		// with colons in the name under /usr/share/man/man3)? No, absolutely
		// not as it would really only make sense that they were pulling a
		// Windows image. However, for development, it is necessary to be able
		// to pull Linux images which are in the repository.
		//
		// TODO Windows. Once the registry is aware of what images are Windows-
		// specific or Linux-specific, this warning should be changed to an error
		// to cater for the situation where someone does manage to upload a Linux
		// image but have it tagged as Windows inadvertently.
		if runtime.GOOS == windows {
			if strings.Contains(hdr.Name, ":") {
				logrus.Warnf("Windows: Ignoring %s (is this a Linux image?)", hdr.Name)
				continue
			}
		}

		// Note as these operations are platform specific, so must the slash be.
		if !strings.HasSuffix(hdr.Name, string(os.PathSeparator)) {
			// Not the root directory, ensure that the parent directory exists.
			// This happened in some tests where an image had a tarfile without any
			// parent directories.
			parent := filepath.Dir(hdr.Name)
			parentPath := filepath.Join(dest, parent)

			if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
				err = os.MkdirAll(parentPath, 0600)
				if err != nil {
					return 0, err
				}
			}
		}

		// Skip AUFS metadata dirs
		if strings.HasPrefix(hdr.Name, WhiteoutMetaPrefix) {
			// Regular files inside /.wh..wh.plnk can be used as hardlink targets
			// We don't want this directory, but we need the files in them so that
			// such hardlinks can be resolved.
			if strings.HasPrefix(hdr.Name, WhiteoutLinkDir) && hdr.Typeflag == tar.TypeReg {
				basename := filepath.Base(hdr.Name)
				aufsHardlinks[basename] = hdr
				if aufsTempdir == "" {
					if aufsTempdir, err = ioutil.TempDir("", "storageplnk"); err != nil {
						return 0, err
					}
					defer os.RemoveAll(aufsTempdir)
				}
				if err := createTarFile(filepath.Join(aufsTempdir, basename), dest, hdr, tr, true, nil, options.InUserNS, options.IgnoreChownErrors, options.ForceMask, buffer); err != nil {
					return 0, err
				}
			}

			if hdr.Name != WhiteoutOpaqueDir {
				continue
			}
		}
		path := filepath.Join(dest, hdr.Name)
		rel, err := filepath.Rel(dest, path)
		if err != nil {
			return 0, err
		}

		// Note as these operations are platform specific, so must the slash be.
		if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
			return 0, breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest))
		}
		base := filepath.Base(path)

		if strings.HasPrefix(base, WhiteoutPrefix) {
			dir := filepath.Dir(path)
			if base == WhiteoutOpaqueDir {
				_, err := os.Lstat(dir)
				if err != nil {
					return 0, err
				}
				err = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
					if err != nil {
						if os.IsNotExist(err) {
							err = nil // parent was deleted
						}
						return err
					}
					if path == dir {
						return nil
					}
					if _, exists := unpackedPaths[path]; !exists {
						err := os.RemoveAll(path)
						return err
					}
					return nil
				})
				if err != nil {
					return 0, err
				}
			} else {
				originalBase := base[len(WhiteoutPrefix):]
				originalPath := filepath.Join(dir, originalBase)
				if err := os.RemoveAll(originalPath); err != nil {
					return 0, err
				}
			}
		} else {
			// If path exits we almost always just want to remove and replace it.
			// The only exception is when it is a directory *and* the file from
			// the layer is also a directory. Then we want to merge them (i.e.
			// just apply the metadata from the layer).
			if fi, err := os.Lstat(path); err == nil {
				if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) {
					if err := os.RemoveAll(path); err != nil {
						return 0, err
					}
				}
			}

			trBuf.Reset(tr)
			srcData := io.Reader(trBuf)
			srcHdr := hdr

			// Hard links into /.wh..wh.plnk don't work, as we don't extract that directory, so
			// we manually retarget these into the temporary files we extracted them into
			if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(filepath.Clean(hdr.Linkname), WhiteoutLinkDir) {
				linkBasename := filepath.Base(hdr.Linkname)
				srcHdr = aufsHardlinks[linkBasename]
				if srcHdr == nil {
					return 0, fmt.Errorf("invalid aufs hardlink")
				}
				tmpFile, err := os.Open(filepath.Join(aufsTempdir, linkBasename))
				if err != nil {
					return 0, err
				}
				defer tmpFile.Close()
				srcData = tmpFile
			}

			if err := remapIDs(nil, idMappings, options.ChownOpts, srcHdr); err != nil {
				return 0, err
			}

			if err := createTarFile(path, dest, srcHdr, srcData, true, nil, options.InUserNS, options.IgnoreChownErrors, options.ForceMask, buffer); err != nil {
				return 0, err
			}

			// Directory mtimes must be handled at the end to avoid further
			// file creation in them to modify the directory mtime
			if hdr.Typeflag == tar.TypeDir {
				dirs = append(dirs, hdr)
			}
			unpackedPaths[path] = struct{}{}
		}
	}

	for _, hdr := range dirs {
		path := filepath.Join(dest, hdr.Name)
		if err := system.Chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil {
			return 0, err
		}
	}

	return size, nil
}

// ApplyLayer parses a diff in the standard layer format from `layer`,
// and applies it to the directory `dest`. The stream `layer` can be
// compressed or uncompressed.
// Returns the size in bytes of the contents of the layer.
func ApplyLayer(dest string, layer io.Reader) (int64, error) {
	return applyLayerHandler(dest, layer, &TarOptions{}, true)
}

// ApplyUncompressedLayer parses a diff in the standard layer format from
// `layer`, and applies it to the directory `dest`. The stream `layer`
// can only be uncompressed.
// Returns the size in bytes of the contents of the layer.
func ApplyUncompressedLayer(dest string, layer io.Reader, options *TarOptions) (int64, error) {
	return applyLayerHandler(dest, layer, options, false)
}

// do the bulk load of ApplyLayer, but allow for not calling DecompressStream
func applyLayerHandler(dest string, layer io.Reader, options *TarOptions, decompress bool) (int64, error) {
	dest = filepath.Clean(dest)

	// We need to be able to set any perms
	oldmask, err := system.Umask(0)
	if err != nil {
		return 0, err
	}
	defer system.Umask(oldmask) // ignore err, ErrNotSupportedPlatform

	if decompress {
		layer, err = DecompressStream(layer)
		if err != nil {
			return 0, err
		}
	}
	return UnpackLayer(dest, layer, options)
}
<commit_msg>diff: create missing layers dirs with 0755<commit_after>package archive

import (
	"archive/tar"
	"fmt"
	"io"
	"io/fs"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/containers/storage/pkg/idtools"
	"github.com/containers/storage/pkg/pools"
	"github.com/containers/storage/pkg/system"
	"github.com/sirupsen/logrus"
)

// UnpackLayer unpack `layer` to a `dest`. The stream `layer` can be
// compressed or uncompressed.
// Returns the size in bytes of the contents of the layer.
func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, err error) {
	tr := tar.NewReader(layer)
	trBuf := pools.BufioReader32KPool.Get(tr)
	defer pools.BufioReader32KPool.Put(trBuf)

	var dirs []*tar.Header
	unpackedPaths := make(map[string]struct{})

	if options == nil {
		options = &TarOptions{}
	}
	if options.ExcludePatterns == nil {
		options.ExcludePatterns = []string{}
	}
	idMappings := idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps)

	aufsTempdir := ""
	aufsHardlinks := make(map[string]*tar.Header)
	buffer := make([]byte, 1<<20)

	// Iterate through the files in the archive.
	for {
		hdr, err := tr.Next()
		if err == io.EOF {
			// end of tar archive
			break
		}
		if err != nil {
			return 0, err
		}

		size += hdr.Size

		// Normalize name, for safety and for a simple is-root check
		hdr.Name = filepath.Clean(hdr.Name)

		// Windows does not support filenames with colons in them. Ignore
		// these files. This is not a problem though (although it might
		// appear that it is). Let's suppose a client is running docker pull.
		// The daemon it points to is Windows. Would it make sense for the
		// client to be doing a docker pull Ubuntu for example (which has files
		// with colons in the name under /usr/share/man/man3)? No, absolutely
		// not as it would really only make sense that they were pulling a
		// Windows image. However, for development, it is necessary to be able
		// to pull Linux images which are in the repository.
		//
		// TODO Windows. Once the registry is aware of what images are Windows-
		// specific or Linux-specific, this warning should be changed to an error
		// to cater for the situation where someone does manage to upload a Linux
		// image but have it tagged as Windows inadvertently.
		if runtime.GOOS == windows {
			if strings.Contains(hdr.Name, ":") {
				logrus.Warnf("Windows: Ignoring %s (is this a Linux image?)", hdr.Name)
				continue
			}
		}

		// Note as these operations are platform specific, so must the slash be.
		if !strings.HasSuffix(hdr.Name, string(os.PathSeparator)) {
			// Not the root directory, ensure that the parent directory exists.
			// This happened in some tests where an image had a tarfile without any
			// parent directories.
			parent := filepath.Dir(hdr.Name)
			parentPath := filepath.Join(dest, parent)

			if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
				err = os.MkdirAll(parentPath, 0755)
				if err != nil {
					return 0, err
				}
			}
		}

		// Skip AUFS metadata dirs
		if strings.HasPrefix(hdr.Name, WhiteoutMetaPrefix) {
			// Regular files inside /.wh..wh.plnk can be used as hardlink targets
			// We don't want this directory, but we need the files in them so that
			// such hardlinks can be resolved.
			if strings.HasPrefix(hdr.Name, WhiteoutLinkDir) && hdr.Typeflag == tar.TypeReg {
				basename := filepath.Base(hdr.Name)
				aufsHardlinks[basename] = hdr
				if aufsTempdir == "" {
					if aufsTempdir, err = ioutil.TempDir("", "storageplnk"); err != nil {
						return 0, err
					}
					defer os.RemoveAll(aufsTempdir)
				}
				if err := createTarFile(filepath.Join(aufsTempdir, basename), dest, hdr, tr, true, nil, options.InUserNS, options.IgnoreChownErrors, options.ForceMask, buffer); err != nil {
					return 0, err
				}
			}

			if hdr.Name != WhiteoutOpaqueDir {
				continue
			}
		}
		path := filepath.Join(dest, hdr.Name)
		rel, err := filepath.Rel(dest, path)
		if err != nil {
			return 0, err
		}

		// Note as these operations are platform specific, so must the slash be.
		if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
			return 0, breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest))
		}
		base := filepath.Base(path)

		if strings.HasPrefix(base, WhiteoutPrefix) {
			dir := filepath.Dir(path)
			if base == WhiteoutOpaqueDir {
				_, err := os.Lstat(dir)
				if err != nil {
					return 0, err
				}
				err = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
					if err != nil {
						if os.IsNotExist(err) {
							err = nil // parent was deleted
						}
						return err
					}
					if path == dir {
						return nil
					}
					if _, exists := unpackedPaths[path]; !exists {
						err := os.RemoveAll(path)
						return err
					}
					return nil
				})
				if err != nil {
					return 0, err
				}
			} else {
				originalBase := base[len(WhiteoutPrefix):]
				originalPath := filepath.Join(dir, originalBase)
				if err := os.RemoveAll(originalPath); err != nil {
					return 0, err
				}
			}
		} else {
			// If path exits we almost always just want to remove and replace it.
			// The only exception is when it is a directory *and* the file from
			// the layer is also a directory. Then we want to merge them (i.e.
			// just apply the metadata from the layer).
			if fi, err := os.Lstat(path); err == nil {
				if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) {
					if err := os.RemoveAll(path); err != nil {
						return 0, err
					}
				}
			}

			trBuf.Reset(tr)
			srcData := io.Reader(trBuf)
			srcHdr := hdr

			// Hard links into /.wh..wh.plnk don't work, as we don't extract that directory, so
			// we manually retarget these into the temporary files we extracted them into
			if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(filepath.Clean(hdr.Linkname), WhiteoutLinkDir) {
				linkBasename := filepath.Base(hdr.Linkname)
				srcHdr = aufsHardlinks[linkBasename]
				if srcHdr == nil {
					return 0, fmt.Errorf("invalid aufs hardlink")
				}
				tmpFile, err := os.Open(filepath.Join(aufsTempdir, linkBasename))
				if err != nil {
					return 0, err
				}
				defer tmpFile.Close()
				srcData = tmpFile
			}

			if err := remapIDs(nil, idMappings, options.ChownOpts, srcHdr); err != nil {
				return 0, err
			}

			if err := createTarFile(path, dest, srcHdr, srcData, true, nil, options.InUserNS, options.IgnoreChownErrors, options.ForceMask, buffer); err != nil {
				return 0, err
			}

			// Directory mtimes must be handled at the end to avoid further
			// file creation in them to modify the directory mtime
			if hdr.Typeflag == tar.TypeDir {
				dirs = append(dirs, hdr)
			}
			unpackedPaths[path] = struct{}{}
		}
	}

	for _, hdr := range dirs {
		path := filepath.Join(dest, hdr.Name)
		if err := system.Chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil {
			return 0, err
		}
	}

	return size, nil
}

// ApplyLayer parses a diff in the standard layer format from `layer`,
// and applies it to the directory `dest`. The stream `layer` can be
// compressed or uncompressed.
// Returns the size in bytes of the contents of the layer.
func ApplyLayer(dest string, layer io.Reader) (int64, error) {
	return applyLayerHandler(dest, layer, &TarOptions{}, true)
}

// ApplyUncompressedLayer parses a diff in the standard layer format from
// `layer`, and applies it to the directory `dest`. The stream `layer`
// can only be uncompressed.
// Returns the size in bytes of the contents of the layer.
func ApplyUncompressedLayer(dest string, layer io.Reader, options *TarOptions) (int64, error) {
	return applyLayerHandler(dest, layer, options, false)
}

// do the bulk load of ApplyLayer, but allow for not calling DecompressStream
func applyLayerHandler(dest string, layer io.Reader, options *TarOptions, decompress bool) (int64, error) {
	dest = filepath.Clean(dest)

	// We need to be able to set any perms
	oldmask, err := system.Umask(0)
	if err != nil {
		return 0, err
	}
	defer system.Umask(oldmask) // ignore err, ErrNotSupportedPlatform

	if decompress {
		layer, err = DecompressStream(layer)
		if err != nil {
			return 0, err
		}
	}
	return UnpackLayer(dest, layer, options)
}
<|endoftext|>


<commit_before>// Copyright 2017 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

package email

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"io"
	"io/ioutil"
	"mime"
	"mime/multipart"
	"net/mail"
	"regexp"
	"sort"
	"strings"
)

type Email struct {
	BugID       string
	MessageID   string
	Link        string
	Subject     string
	From        string
	Cc          []string
	Body        string // text/plain part
	Patch       string // attached patch, if any
	Command     string // command to bot (#syz is stripped)
	CommandArgs string // arguments for the command
}

const commandPrefix = "#syz "

var groupsLinkRe = regexp.MustCompile("\nTo view this discussion on the web visit (https://groups\\.google\\.com/.*?)\\.(?:\r)?\n")

func Parse(r io.Reader, ownEmail string) (*Email, error) {
	msg, err := mail.ReadMessage(r)
	if err != nil {
		return nil, fmt.Errorf("failed to read email: %v", err)
	}
	from, err := msg.Header.AddressList("From")
	if err != nil {
		return nil, fmt.Errorf("failed to parse email header 'From': %v", err)
	}
	if len(from) == 0 {
		return nil, fmt.Errorf("failed to parse email header 'To': no senders")
	}
	to, err := msg.Header.AddressList("To")
	if err != nil {
		return nil, fmt.Errorf("failed to parse email header 'To': %v", err)
	}
	// AddressList fails if the header is not present.
	cc, _ := msg.Header.AddressList("Cc")
	bugID := ""
	var ccList []string
	if addr, err := mail.ParseAddress(ownEmail); err == nil {
		ownEmail = addr.Address
	}
	fromMe := false
	for _, addr := range from {
		cleaned, _, _ := RemoveAddrContext(addr.Address)
		if addr, err := mail.ParseAddress(cleaned); err == nil && addr.Address == ownEmail {
			fromMe = true
		}
	}
	for _, addr := range append(append(cc, to...), from...) {
		cleaned, context, _ := RemoveAddrContext(addr.Address)
		if addr, err := mail.ParseAddress(cleaned); err == nil {
			cleaned = addr.Address
		}
		if cleaned == ownEmail {
			if bugID == "" {
				bugID = context
			}
		} else {
			ccList = append(ccList, cleaned)
		}
	}
	ccList = MergeEmailLists(ccList)
	body, attachments, err := parseBody(msg.Body, msg.Header)
	if err != nil {
		return nil, err
	}
	bodyStr := string(body)
	patch, cmd, cmdArgs := "", "", ""
	if !fromMe {
		for _, a := range attachments {
			_, patch, _ = ParsePatch(string(a))
			if patch != "" {
				break
			}
		}
		if patch == "" {
			_, patch, _ = ParsePatch(bodyStr)
		}
		cmd, cmdArgs = extractCommand(body)
	}
	link := ""
	if match := groupsLinkRe.FindStringSubmatchIndex(bodyStr); match != nil {
		link = bodyStr[match[2]:match[3]]
	}
	email := &Email{
		BugID:       bugID,
		MessageID:   msg.Header.Get("Message-ID"),
		Link:        link,
		Subject:     msg.Header.Get("Subject"),
		From:        from[0].String(),
		Cc:          ccList,
		Body:        string(body),
		Patch:       patch,
		Command:     cmd,
		CommandArgs: cmdArgs,
	}
	return email, nil
}

// AddAddrContext embeds context into local part of the provided email address using '+'.
// Returns the resulting email address.
func AddAddrContext(email, context string) (string, error) {
	addr, err := mail.ParseAddress(email)
	if err != nil {
		return "", fmt.Errorf("failed to parse %q as email: %v", email, err)
	}
	at := strings.IndexByte(addr.Address, '@')
	if at == -1 {
		return "", fmt.Errorf("failed to parse %q as email: no @", email)
	}
	addr.Address = addr.Address[:at] + "+" + context + addr.Address[at:]
	return addr.String(), nil
}

// RemoveAddrContext extracts context after '+' from the local part of the provided email address.
// Returns address without the context and the context.
func RemoveAddrContext(email string) (string, string, error) {
	addr, err := mail.ParseAddress(email)
	if err != nil {
		return "", "", fmt.Errorf("failed to parse %q as email: %v", email, err)
	}
	at := strings.IndexByte(addr.Address, '@')
	if at == -1 {
		return "", "", fmt.Errorf("failed to parse %q as email: no @", email)
	}
	plus := strings.LastIndexByte(addr.Address[:at], '+')
	if plus == -1 {
		return email, "", nil
	}
	context := addr.Address[plus+1 : at]
	addr.Address = addr.Address[:plus] + addr.Address[at:]
	return addr.String(), context, nil
}

func CanonicalEmail(email string) string {
	addr, err := mail.ParseAddress(email)
	if err != nil {
		return email
	}
	at := strings.IndexByte(addr.Address, '@')
	if at == -1 {
		return email
	}
	if plus := strings.IndexByte(addr.Address[:at], '+'); plus != -1 {
		addr.Address = addr.Address[:plus] + addr.Address[at:]
	}
	return strings.ToLower(addr.Address)
}

// extractCommand extracts command to syzbot from email body.
// Commands are of the following form:
// ^#syz cmd args...
func extractCommand(body []byte) (cmd, args string) {
	cmdPos := bytes.Index(append([]byte{'\n'}, body...), []byte("\n"+commandPrefix))
	if cmdPos == -1 {
		return
	}
	cmdPos += len(commandPrefix)
	cmdEnd := bytes.IndexByte(body[cmdPos:], '\n')
	if cmdEnd == -1 {
		cmdEnd = len(body) - cmdPos
	}
	cmdLine := strings.TrimSpace(string(body[cmdPos : cmdPos+cmdEnd]))
	if cmdLine == "" {
		return
	}
	split := strings.Split(cmdLine, " ")
	cmd = split[0]
	var aargs []string
	if len(split) > 1 {
		aargs = split[1:]
	}
	// Text emails are split at 80 columns are the transformation is irrevesible.
	// Try to restore args for some commands that don't have spaces in args.
	want := 0
	switch cmd {
	case "test:":
		want = 2
	case "test_5_arg_cmd":
		want = 5
	}
	for pos := cmdPos + cmdEnd + 1; len(aargs) < want && pos < len(body); {
		lineEnd := bytes.IndexByte(body[pos:], '\n')
		if lineEnd == -1 {
			lineEnd = len(body) - pos
		}
		line := strings.TrimSpace(string(body[pos : pos+lineEnd]))
		if line != "" {
			aargs = append(aargs, strings.Split(line, " ")...)
		}
		pos += lineEnd + 1
	}
	args = strings.TrimSpace(strings.Join(aargs, " "))
	return
}

func parseBody(r io.Reader, headers mail.Header) (body []byte, attachments [][]byte, err error) {
	mediaType, params, err := mime.ParseMediaType(headers.Get("Content-Type"))
	if err != nil {
		return nil, nil, fmt.Errorf("failed to parse email header 'Content-Type': %v", err)
	}
	// Note: mime package handles quoted-printable internally.
	if strings.ToLower(headers.Get("Content-Transfer-Encoding")) == "base64" {
		r = base64.NewDecoder(base64.StdEncoding, r)
	}
	disp, _, _ := mime.ParseMediaType(headers.Get("Content-Disposition"))
	if disp == "attachment" {
		attachment, err := ioutil.ReadAll(r)
		if err != nil {
			return nil, nil, fmt.Errorf("failed to read email body: %v", err)
		}
		return nil, [][]byte{attachment}, nil
	}
	if mediaType == "text/plain" {
		body, err := ioutil.ReadAll(r)
		if err != nil {
			return nil, nil, fmt.Errorf("failed to read email body: %v", err)
		}
		return body, nil, nil
	}
	if !strings.HasPrefix(mediaType, "multipart/") {
		return nil, nil, nil
	}
	mr := multipart.NewReader(r, params["boundary"])
	for {
		p, err := mr.NextPart()
		if err == io.EOF {
			return body, attachments, nil
		}
		if err != nil {
			return nil, nil, fmt.Errorf("failed to parse MIME parts: %v", err)
		}
		body1, attachments1, err1 := parseBody(p, mail.Header(p.Header))
		if err1 != nil {
			return nil, nil, err1
		}
		if body == nil {
			body = body1
		}
		attachments = append(attachments, attachments1...)
	}
}

// MergeEmailLists merges several email lists removing duplicates and invalid entries.
func MergeEmailLists(lists ...[]string) []string {
	const (
		maxEmailLen = 1000
		maxEmails   = 50
	)
	merged := make(map[string]bool)
	for _, list := range lists {
		for _, email := range list {
			addr, err := mail.ParseAddress(email)
			if err != nil || len(addr.Address) > maxEmailLen {
				continue
			}
			merged[addr.Address] = true
		}
	}
	var result []string
	for e := range merged {
		result = append(result, e)
	}
	sort.Strings(result)
	if len(result) > maxEmails {
		result = result[:maxEmails]
	}
	return result
}
<commit_msg>pkg/email: ignore missing To: header<commit_after>// Copyright 2017 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

package email

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"io"
	"io/ioutil"
	"mime"
	"mime/multipart"
	"net/mail"
	"regexp"
	"sort"
	"strings"
)

type Email struct {
	BugID       string
	MessageID   string
	Link        string
	Subject     string
	From        string
	Cc          []string
	Body        string // text/plain part
	Patch       string // attached patch, if any
	Command     string // command to bot (#syz is stripped)
	CommandArgs string // arguments for the command
}

const commandPrefix = "#syz "

var groupsLinkRe = regexp.MustCompile("\nTo view this discussion on the web visit (https://groups\\.google\\.com/.*?)\\.(?:\r)?\n")

func Parse(r io.Reader, ownEmail string) (*Email, error) {
	msg, err := mail.ReadMessage(r)
	if err != nil {
		return nil, fmt.Errorf("failed to read email: %v", err)
	}
	from, err := msg.Header.AddressList("From")
	if err != nil {
		return nil, fmt.Errorf("failed to parse email header 'From': %v", err)
	}
	if len(from) == 0 {
		return nil, fmt.Errorf("failed to parse email header 'To': no senders")
	}
	// Ignore errors since To: header may not be present (we've seen such case).
	to, _ := msg.Header.AddressList("To")
	// AddressList fails if the header is not present.
	cc, _ := msg.Header.AddressList("Cc")
	bugID := ""
	var ccList []string
	if addr, err := mail.ParseAddress(ownEmail); err == nil {
		ownEmail = addr.Address
	}
	fromMe := false
	for _, addr := range from {
		cleaned, _, _ := RemoveAddrContext(addr.Address)
		if addr, err := mail.ParseAddress(cleaned); err == nil && addr.Address == ownEmail {
			fromMe = true
		}
	}
	for _, addr := range append(append(cc, to...), from...) {
		cleaned, context, _ := RemoveAddrContext(addr.Address)
		if addr, err := mail.ParseAddress(cleaned); err == nil {
			cleaned = addr.Address
		}
		if cleaned == ownEmail {
			if bugID == "" {
				bugID = context
			}
		} else {
			ccList = append(ccList, cleaned)
		}
	}
	ccList = MergeEmailLists(ccList)
	body, attachments, err := parseBody(msg.Body, msg.Header)
	if err != nil {
		return nil, err
	}
	bodyStr := string(body)
	patch, cmd, cmdArgs := "", "", ""
	if !fromMe {
		for _, a := range attachments {
			_, patch, _ = ParsePatch(string(a))
			if patch != "" {
				break
			}
		}
		if patch == "" {
			_, patch, _ = ParsePatch(bodyStr)
		}
		cmd, cmdArgs = extractCommand(body)
	}
	link := ""
	if match := groupsLinkRe.FindStringSubmatchIndex(bodyStr); match != nil {
		link = bodyStr[match[2]:match[3]]
	}
	email := &Email{
		BugID:       bugID,
		MessageID:   msg.Header.Get("Message-ID"),
		Link:        link,
		Subject:     msg.Header.Get("Subject"),
		From:        from[0].String(),
		Cc:          ccList,
		Body:        string(body),
		Patch:       patch,
		Command:     cmd,
		CommandArgs: cmdArgs,
	}
	return email, nil
}

// AddAddrContext embeds context into local part of the provided email address using '+'.
// Returns the resulting email address.
func AddAddrContext(email, context string) (string, error) {
	addr, err := mail.ParseAddress(email)
	if err != nil {
		return "", fmt.Errorf("failed to parse %q as email: %v", email, err)
	}
	at := strings.IndexByte(addr.Address, '@')
	if at == -1 {
		return "", fmt.Errorf("failed to parse %q as email: no @", email)
	}
	addr.Address = addr.Address[:at] + "+" + context + addr.Address[at:]
	return addr.String(), nil
}

// RemoveAddrContext extracts context after '+' from the local part of the provided email address.
// Returns address without the context and the context.
func RemoveAddrContext(email string) (string, string, error) {
	addr, err := mail.ParseAddress(email)
	if err != nil {
		return "", "", fmt.Errorf("failed to parse %q as email: %v", email, err)
	}
	at := strings.IndexByte(addr.Address, '@')
	if at == -1 {
		return "", "", fmt.Errorf("failed to parse %q as email: no @", email)
	}
	plus := strings.LastIndexByte(addr.Address[:at], '+')
	if plus == -1 {
		return email, "", nil
	}
	context := addr.Address[plus+1 : at]
	addr.Address = addr.Address[:plus] + addr.Address[at:]
	return addr.String(), context, nil
}

func CanonicalEmail(email string) string {
	addr, err := mail.ParseAddress(email)
	if err != nil {
		return email
	}
	at := strings.IndexByte(addr.Address, '@')
	if at == -1 {
		return email
	}
	if plus := strings.IndexByte(addr.Address[:at], '+'); plus != -1 {
		addr.Address = addr.Address[:plus] + addr.Address[at:]
	}
	return strings.ToLower(addr.Address)
}

// extractCommand extracts command to syzbot from email body.
// Commands are of the following form:
// ^#syz cmd args...
func extractCommand(body []byte) (cmd, args string) {
	cmdPos := bytes.Index(append([]byte{'\n'}, body...), []byte("\n"+commandPrefix))
	if cmdPos == -1 {
		return
	}
	cmdPos += len(commandPrefix)
	cmdEnd := bytes.IndexByte(body[cmdPos:], '\n')
	if cmdEnd == -1 {
		cmdEnd = len(body) - cmdPos
	}
	cmdLine := strings.TrimSpace(string(body[cmdPos : cmdPos+cmdEnd]))
	if cmdLine == "" {
		return
	}
	split := strings.Split(cmdLine, " ")
	cmd = split[0]
	var aargs []string
	if len(split) > 1 {
		aargs = split[1:]
	}
	// Text emails are split at 80 columns are the transformation is irrevesible.
	// Try to restore args for some commands that don't have spaces in args.
	want := 0
	switch cmd {
	case "test:":
		want = 2
	case "test_5_arg_cmd":
		want = 5
	}
	for pos := cmdPos + cmdEnd + 1; len(aargs) < want && pos < len(body); {
		lineEnd := bytes.IndexByte(body[pos:], '\n')
		if lineEnd == -1 {
			lineEnd = len(body) - pos
		}
		line := strings.TrimSpace(string(body[pos : pos+lineEnd]))
		if line != "" {
			aargs = append(aargs, strings.Split(line, " ")...)
		}
		pos += lineEnd + 1
	}
	args = strings.TrimSpace(strings.Join(aargs, " "))
	return
}

func parseBody(r io.Reader, headers mail.Header) (body []byte, attachments [][]byte, err error) {
	mediaType, params, err := mime.ParseMediaType(headers.Get("Content-Type"))
	if err != nil {
		return nil, nil, fmt.Errorf("failed to parse email header 'Content-Type': %v", err)
	}
	// Note: mime package handles quoted-printable internally.
	if strings.ToLower(headers.Get("Content-Transfer-Encoding")) == "base64" {
		r = base64.NewDecoder(base64.StdEncoding, r)
	}
	disp, _, _ := mime.ParseMediaType(headers.Get("Content-Disposition"))
	if disp == "attachment" {
		attachment, err := ioutil.ReadAll(r)
		if err != nil {
			return nil, nil, fmt.Errorf("failed to read email body: %v", err)
		}
		return nil, [][]byte{attachment}, nil
	}
	if mediaType == "text/plain" {
		body, err := ioutil.ReadAll(r)
		if err != nil {
			return nil, nil, fmt.Errorf("failed to read email body: %v", err)
		}
		return body, nil, nil
	}
	if !strings.HasPrefix(mediaType, "multipart/") {
		return nil, nil, nil
	}
	mr := multipart.NewReader(r, params["boundary"])
	for {
		p, err := mr.NextPart()
		if err == io.EOF {
			return body, attachments, nil
		}
		if err != nil {
			return nil, nil, fmt.Errorf("failed to parse MIME parts: %v", err)
		}
		body1, attachments1, err1 := parseBody(p, mail.Header(p.Header))
		if err1 != nil {
			return nil, nil, err1
		}
		if body == nil {
			body = body1
		}
		attachments = append(attachments, attachments1...)
	}
}

// MergeEmailLists merges several email lists removing duplicates and invalid entries.
func MergeEmailLists(lists ...[]string) []string {
	const (
		maxEmailLen = 1000
		maxEmails   = 50
	)
	merged := make(map[string]bool)
	for _, list := range lists {
		for _, email := range list {
			addr, err := mail.ParseAddress(email)
			if err != nil || len(addr.Address) > maxEmailLen {
				continue
			}
			merged[addr.Address] = true
		}
	}
	var result []string
	for e := range merged {
		result = append(result, e)
	}
	sort.Strings(result)
	if len(result) > maxEmails {
		result = result[:maxEmails]
	}
	return result
}
<|endoftext|>


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

package plugins

import (
	"fmt"
	"sort"
	"strings"

	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack"
	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/limits"
	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/quotasets"
	"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
	"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
	"github.com/gophercloud/gophercloud/pagination"
	"github.com/sapcc/limes/pkg/limes"
	"github.com/sapcc/limes/pkg/util"
)

//use a name that's unique to github.com/gophercloud/gophercloud/openstack/imageservice/v2/images
//to ensure that goimports does not mistakenly replace it by .../compute/v2/images
var _ images.ImageVisibility

type novaPlugin struct {
	cfg             limes.ServiceConfiguration
	scrapeInstances bool
	resources       []limes.ResourceInfo
	flavors         map[string]*flavors.Flavor
	osTypeForImage  map[string]string
}

var novaDefaultResources = []limes.ResourceInfo{
	{
		Name: "cores",
		Unit: limes.UnitNone,
	},
	{
		Name: "instances",
		Unit: limes.UnitNone,
	},
	{
		Name: "ram",
		Unit: limes.UnitMebibytes,
	},
}

func init() {
	limes.RegisterQuotaPlugin(func(c limes.ServiceConfiguration, scrapeSubresources map[string]bool) limes.QuotaPlugin {
		return &novaPlugin{
			cfg:             c,
			scrapeInstances: scrapeSubresources["instances"],
			resources:       novaDefaultResources,
		}
	})
}

//Init implements the limes.QuotaPlugin interface.
func (p *novaPlugin) Init(provider *gophercloud.ProviderClient) error {
	client, err := p.Client(provider)
	if err != nil {
		return err
	}

	//look at quota class "default" to determine which quotas exist
	url := client.ServiceURL("os-quota-class-sets", "default")
	var result gophercloud.Result
	_, err = client.Get(url, &result.Body, nil)
	if err != nil {
		return err
	}

	//At SAP Converged Cloud, we use per-flavor instance quotas for baremetal
	//(Ironic) flavors, to control precisely how many baremetal machines can be
	//used by each domain/project. Each such quota has the resource name
	//"instances_${FLAVOR_NAME}".
	var body struct {
		QuotaClassSet map[string]int64 `yaml:"quota_class_set"`
	}
	err = result.ExtractInto(&body)
	if err != nil {
		return err
	}

	for key := range body.QuotaClassSet {
		if strings.HasPrefix(key, "instances_") {
			p.resources = append(p.resources, limes.ResourceInfo{
				Name:     key,
				Category: "per_flavor",
				Unit:     limes.UnitNone,
			})
		}
	}

	sort.Slice(p.resources, func(i, j int) bool {
		return p.resources[i].Name < p.resources[j].Name
	})
	return nil
}

//ServiceInfo implements the limes.QuotaPlugin interface.
func (p *novaPlugin) ServiceInfo() limes.ServiceInfo {
	return limes.ServiceInfo{
		Type:        "compute",
		ProductName: "nova",
		Area:        "compute",
	}
}

//Resources implements the limes.QuotaPlugin interface.
func (p *novaPlugin) Resources() []limes.ResourceInfo {
	return p.resources
}

func (p *novaPlugin) Client(provider *gophercloud.ProviderClient) (*gophercloud.ServiceClient, error) {
	return openstack.NewComputeV2(provider,
		gophercloud.EndpointOpts{Availability: gophercloud.AvailabilityPublic},
	)
}

func (p *novaPlugin) GlanceClient(provider *gophercloud.ProviderClient) (*gophercloud.ServiceClient, error) {
	return openstack.NewImageServiceV2(provider,
		gophercloud.EndpointOpts{Availability: gophercloud.AvailabilityPublic},
	)
}

//Scrape implements the limes.QuotaPlugin interface.
func (p *novaPlugin) Scrape(provider *gophercloud.ProviderClient, domainUUID, projectUUID string) (map[string]limes.ResourceData, error) {
	client, err := p.Client(provider)
	if err != nil {
		return nil, err
	}

	var limitsData struct {
		Limits struct {
			Absolute struct {
				MaxTotalCores      int64  `json:"maxTotalCores"`
				MaxTotalInstances  int64  `json:"maxTotalInstances"`
				MaxTotalRAMSize    int64  `json:"maxTotalRAMSize"`
				TotalCoresUsed     uint64 `json:"totalCoresUsed"`
				TotalInstancesUsed uint64 `json:"totalInstancesUsed"`
				TotalRAMUsed       uint64 `json:"totalRAMUsed"`
			} `json:"absolute"`
			AbsolutePerFlavor map[string]struct {
				MaxTotalInstances  int64  `json:"maxTotalInstances"`
				TotalInstancesUsed uint64 `json:"totalInstancesUsed"`
			} `json:"absolutePerFlavor"`
		} `json:"limits"`
	}
	err = limits.Get(client, limits.GetOpts{TenantID: projectUUID}).ExtractInto(&limitsData)
	if err != nil {
		return nil, err
	}

	result := map[string]*limes.ResourceData{
		"cores": {
			Quota: limitsData.Limits.Absolute.MaxTotalCores,
			Usage: limitsData.Limits.Absolute.TotalCoresUsed,
		},
		"instances": {
			Quota: limitsData.Limits.Absolute.MaxTotalInstances,
			Usage: limitsData.Limits.Absolute.TotalInstancesUsed,
		},
		"ram": {
			Quota: limitsData.Limits.Absolute.MaxTotalRAMSize,
			Usage: limitsData.Limits.Absolute.TotalRAMUsed,
		},
	}
	for flavorName, flavorLimits := range limitsData.Limits.AbsolutePerFlavor {
		result["instances_"+flavorName] = &limes.ResourceData{
			Quota: flavorLimits.MaxTotalInstances,
			Usage: flavorLimits.TotalInstancesUsed,
		}
	}

	if p.scrapeInstances {
		listOpts := novaServerListOpts{
			AllTenants: true,
			TenantID:   projectUUID,
		}

		err := servers.List(client, listOpts).EachPage(func(page pagination.Page) (bool, error) {
			instances, err := servers.ExtractServers(page)
			if err != nil {
				return false, err
			}

			for _, instance := range instances {
				subResource := map[string]interface{}{
					"id":     instance.ID,
					"name":   instance.Name,
					"status": instance.Status,
				}
				flavorID := instance.Flavor["id"].(string)
				flavor, err := p.getFlavor(client, flavorID)
				if err == nil {
					subResource["flavor"] = flavor.Name
					subResource["vcpu"] = flavor.VCPUs
					subResource["ram"] = limes.ValueWithUnit{
						Value: uint64(flavor.RAM),
						Unit:  limes.UnitMebibytes,
					}
					subResource["disk"] = limes.ValueWithUnit{
						Value: uint64(flavor.Disk),
						Unit:  limes.UnitGibibytes,
					}
				} else {
					util.LogError("error while trying to retrieve data for flavor %s: %s", flavorID, err.Error())
				}

				imageID := instance.Image["id"].(string)
				osType, err := p.getOSType(provider, imageID)
				if err == nil {
					subResource["os_type"] = osType
				} else {
					util.LogError("error while trying to find OS type for image %s: %s", imageID, err.Error())
				}

				resource, exists := result["instances_"+flavor.Name]
				if !exists {
					resource = result["instances"]
				}
				resource.Subresources = append(resource.Subresources, subResource)
			}
			return true, nil
		})
		if err != nil {
			return nil, err
		}
	}

	//remove references (which we needed to apply the subresources correctly)
	result2 := make(map[string]limes.ResourceData, len(result))
	for name, data := range result {
		result2[name] = *data
	}
	return result2, nil
}

//SetQuota implements the limes.QuotaPlugin interface.
func (p *novaPlugin) SetQuota(provider *gophercloud.ProviderClient, domainUUID, projectUUID string, quotas map[string]uint64) error {
	client, err := p.Client(provider)
	if err != nil {
		return err
	}

	return quotasets.Update(client, projectUUID, novaQuotaUpdateOpts(quotas)).Err
}

//Getting and caching flavor details
//Changing a flavor is not supported from OpenStack, so no invalidating of the cache needed
//Acces to the map is not thread safe
func (p *novaPlugin) getFlavor(client *gophercloud.ServiceClient, flavorID string) (*flavors.Flavor, error) {
	if p.flavors == nil {
		p.flavors = make(map[string]*flavors.Flavor)
	}

	if flavor, ok := p.flavors[flavorID]; ok {
		return flavor, nil
	}

	flavor, err := flavors.Get(client, flavorID).Extract()
	if err == nil {
		p.flavors[flavorID] = flavor
	}
	return flavor, err
}

func (p *novaPlugin) getOSType(provider *gophercloud.ProviderClient, imageID string) (string, error) {
	if p.osTypeForImage == nil {
		p.osTypeForImage = make(map[string]string)
	}

	if osType, ok := p.osTypeForImage[imageID]; ok {
		return osType, nil
	}

	osType, err := p.findOSType(provider, imageID)
	if err == nil {
		p.osTypeForImage[imageID] = osType
	} else {
		fmt.Printf("internal error: %#v\n", err)
	}
	return osType, err
}

func (p *novaPlugin) findOSType(provider *gophercloud.ProviderClient, imageID string) (string, error) {
	client, err := p.Client(provider)
	if err != nil {
		return "", err
	}
	var result struct {
		Tags         []string `json:"tags"`
		VMwareOSType string   `json:"vmware_ostype"`
	}
	err = images.Get(client, imageID).ExtractInto(&result)
	if err != nil {
		//report a dummy value if image has been deleted...
		if _, ok := err.(gophercloud.ErrDefault404); ok {
			return "image-deleted", nil
		}
		//otherwise, try to GET image again during next scrape
		return "", err
	}

	//prefer vmware_ostype attribute since this is validated by Nova upon booting the VM
	if isValidVMwareOSType[result.VMwareOSType] {
		return result.VMwareOSType, nil
	}
	//look for a tag like "ostype:rhel7" or "ostype:windows8Server64"
	osType := ""
	for _, tag := range result.Tags {
		if strings.HasPrefix(tag, "ostype:") {
			if osType == "" {
				osType = strings.TrimPrefix(tag, "ostype:")
			} else {
				//multiple such tags -> wtf
				osType = ""
				break
			}
		}
	}

	//report "unknown" as a last resort
	if osType == "" {
		osType = "unknown"
	}
	return osType, nil
}

func makeIntPointer(value int) *int {
	return &value
}

type novaServerListOpts struct {
	AllTenants bool   `q:"all_tenants"`
	TenantID   string `q:"tenant_id"`
}

func (opts novaServerListOpts) ToServerListQuery() (string, error) {
	q, err := gophercloud.BuildQueryString(opts)
	return q.String(), err
}

type novaQuotaUpdateOpts map[string]uint64

func (opts novaQuotaUpdateOpts) ToComputeQuotaUpdateMap() (map[string]interface{}, error) {
	result := make(map[string]interface{}, len(opts))
	for key, val := range opts {
		result[key] = val
	}
	return map[string]interface{}{"quota_set": result}, nil
}
<commit_msg>fix crash when instance has no image ID<commit_after>/*******************************************************************************
*
* Copyright 2017 SAP SE
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You should have received a copy of the License along with this
* program. If not, you may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*******************************************************************************/

package plugins

import (
	"fmt"
	"sort"
	"strings"

	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack"
	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/limits"
	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/quotasets"
	"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
	"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
	"github.com/gophercloud/gophercloud/pagination"
	"github.com/sapcc/limes/pkg/limes"
	"github.com/sapcc/limes/pkg/util"
)

//use a name that's unique to github.com/gophercloud/gophercloud/openstack/imageservice/v2/images
//to ensure that goimports does not mistakenly replace it by .../compute/v2/images
var _ images.ImageVisibility

type novaPlugin struct {
	cfg             limes.ServiceConfiguration
	scrapeInstances bool
	resources       []limes.ResourceInfo
	flavors         map[string]*flavors.Flavor
	osTypeForImage  map[string]string
}

var novaDefaultResources = []limes.ResourceInfo{
	{
		Name: "cores",
		Unit: limes.UnitNone,
	},
	{
		Name: "instances",
		Unit: limes.UnitNone,
	},
	{
		Name: "ram",
		Unit: limes.UnitMebibytes,
	},
}

func init() {
	limes.RegisterQuotaPlugin(func(c limes.ServiceConfiguration, scrapeSubresources map[string]bool) limes.QuotaPlugin {
		return &novaPlugin{
			cfg:             c,
			scrapeInstances: scrapeSubresources["instances"],
			resources:       novaDefaultResources,
		}
	})
}

//Init implements the limes.QuotaPlugin interface.
func (p *novaPlugin) Init(provider *gophercloud.ProviderClient) error {
	client, err := p.Client(provider)
	if err != nil {
		return err
	}

	//look at quota class "default" to determine which quotas exist
	url := client.ServiceURL("os-quota-class-sets", "default")
	var result gophercloud.Result
	_, err = client.Get(url, &result.Body, nil)
	if err != nil {
		return err
	}

	//At SAP Converged Cloud, we use per-flavor instance quotas for baremetal
	//(Ironic) flavors, to control precisely how many baremetal machines can be
	//used by each domain/project. Each such quota has the resource name
	//"instances_${FLAVOR_NAME}".
	var body struct {
		QuotaClassSet map[string]int64 `yaml:"quota_class_set"`
	}
	err = result.ExtractInto(&body)
	if err != nil {
		return err
	}

	for key := range body.QuotaClassSet {
		if strings.HasPrefix(key, "instances_") {
			p.resources = append(p.resources, limes.ResourceInfo{
				Name:     key,
				Category: "per_flavor",
				Unit:     limes.UnitNone,
			})
		}
	}

	sort.Slice(p.resources, func(i, j int) bool {
		return p.resources[i].Name < p.resources[j].Name
	})
	return nil
}

//ServiceInfo implements the limes.QuotaPlugin interface.
func (p *novaPlugin) ServiceInfo() limes.ServiceInfo {
	return limes.ServiceInfo{
		Type:        "compute",
		ProductName: "nova",
		Area:        "compute",
	}
}

//Resources implements the limes.QuotaPlugin interface.
func (p *novaPlugin) Resources() []limes.ResourceInfo {
	return p.resources
}

func (p *novaPlugin) Client(provider *gophercloud.ProviderClient) (*gophercloud.ServiceClient, error) {
	return openstack.NewComputeV2(provider,
		gophercloud.EndpointOpts{Availability: gophercloud.AvailabilityPublic},
	)
}

func (p *novaPlugin) GlanceClient(provider *gophercloud.ProviderClient) (*gophercloud.ServiceClient, error) {
	return openstack.NewImageServiceV2(provider,
		gophercloud.EndpointOpts{Availability: gophercloud.AvailabilityPublic},
	)
}

//Scrape implements the limes.QuotaPlugin interface.
func (p *novaPlugin) Scrape(provider *gophercloud.ProviderClient, domainUUID, projectUUID string) (map[string]limes.ResourceData, error) {
	client, err := p.Client(provider)
	if err != nil {
		return nil, err
	}

	var limitsData struct {
		Limits struct {
			Absolute struct {
				MaxTotalCores      int64  `json:"maxTotalCores"`
				MaxTotalInstances  int64  `json:"maxTotalInstances"`
				MaxTotalRAMSize    int64  `json:"maxTotalRAMSize"`
				TotalCoresUsed     uint64 `json:"totalCoresUsed"`
				TotalInstancesUsed uint64 `json:"totalInstancesUsed"`
				TotalRAMUsed       uint64 `json:"totalRAMUsed"`
			} `json:"absolute"`
			AbsolutePerFlavor map[string]struct {
				MaxTotalInstances  int64  `json:"maxTotalInstances"`
				TotalInstancesUsed uint64 `json:"totalInstancesUsed"`
			} `json:"absolutePerFlavor"`
		} `json:"limits"`
	}
	err = limits.Get(client, limits.GetOpts{TenantID: projectUUID}).ExtractInto(&limitsData)
	if err != nil {
		return nil, err
	}

	result := map[string]*limes.ResourceData{
		"cores": {
			Quota: limitsData.Limits.Absolute.MaxTotalCores,
			Usage: limitsData.Limits.Absolute.TotalCoresUsed,
		},
		"instances": {
			Quota: limitsData.Limits.Absolute.MaxTotalInstances,
			Usage: limitsData.Limits.Absolute.TotalInstancesUsed,
		},
		"ram": {
			Quota: limitsData.Limits.Absolute.MaxTotalRAMSize,
			Usage: limitsData.Limits.Absolute.TotalRAMUsed,
		},
	}
	for flavorName, flavorLimits := range limitsData.Limits.AbsolutePerFlavor {
		result["instances_"+flavorName] = &limes.ResourceData{
			Quota: flavorLimits.MaxTotalInstances,
			Usage: flavorLimits.TotalInstancesUsed,
		}
	}

	if p.scrapeInstances {
		listOpts := novaServerListOpts{
			AllTenants: true,
			TenantID:   projectUUID,
		}

		err := servers.List(client, listOpts).EachPage(func(page pagination.Page) (bool, error) {
			instances, err := servers.ExtractServers(page)
			if err != nil {
				return false, err
			}

			for _, instance := range instances {
				subResource := map[string]interface{}{
					"id":     instance.ID,
					"name":   instance.Name,
					"status": instance.Status,
				}
				flavorID := instance.Flavor["id"].(string)
				flavor, err := p.getFlavor(client, flavorID)
				if err == nil {
					subResource["flavor"] = flavor.Name
					subResource["vcpu"] = flavor.VCPUs
					subResource["ram"] = limes.ValueWithUnit{
						Value: uint64(flavor.RAM),
						Unit:  limes.UnitMebibytes,
					}
					subResource["disk"] = limes.ValueWithUnit{
						Value: uint64(flavor.Disk),
						Unit:  limes.UnitGibibytes,
					}
				} else {
					util.LogError("error while trying to retrieve data for flavor %s: %s", flavorID, err.Error())
				}

				imageID, ok := instance.Image["id"].(string)
				if ok {
					osType, err := p.getOSType(provider, imageID)
					if err == nil {
						subResource["os_type"] = osType
					} else {
						util.LogError("error while trying to find OS type for image %s: %s", imageID, err.Error())
					}
				} else {
					subResource["os_type"] = "image-missing"
				}

				resource, exists := result["instances_"+flavor.Name]
				if !exists {
					resource = result["instances"]
				}
				resource.Subresources = append(resource.Subresources, subResource)
			}
			return true, nil
		})
		if err != nil {
			return nil, err
		}
	}

	//remove references (which we needed to apply the subresources correctly)
	result2 := make(map[string]limes.ResourceData, len(result))
	for name, data := range result {
		result2[name] = *data
	}
	return result2, nil
}

//SetQuota implements the limes.QuotaPlugin interface.
func (p *novaPlugin) SetQuota(provider *gophercloud.ProviderClient, domainUUID, projectUUID string, quotas map[string]uint64) error {
	client, err := p.Client(provider)
	if err != nil {
		return err
	}

	return quotasets.Update(client, projectUUID, novaQuotaUpdateOpts(quotas)).Err
}

//Getting and caching flavor details
//Changing a flavor is not supported from OpenStack, so no invalidating of the cache needed
//Acces to the map is not thread safe
func (p *novaPlugin) getFlavor(client *gophercloud.ServiceClient, flavorID string) (*flavors.Flavor, error) {
	if p.flavors == nil {
		p.flavors = make(map[string]*flavors.Flavor)
	}

	if flavor, ok := p.flavors[flavorID]; ok {
		return flavor, nil
	}

	flavor, err := flavors.Get(client, flavorID).Extract()
	if err == nil {
		p.flavors[flavorID] = flavor
	}
	return flavor, err
}

func (p *novaPlugin) getOSType(provider *gophercloud.ProviderClient, imageID string) (string, error) {
	if p.osTypeForImage == nil {
		p.osTypeForImage = make(map[string]string)
	}

	if osType, ok := p.osTypeForImage[imageID]; ok {
		return osType, nil
	}

	osType, err := p.findOSType(provider, imageID)
	if err == nil {
		p.osTypeForImage[imageID] = osType
	} else {
		fmt.Printf("internal error: %#v\n", err)
	}
	return osType, err
}

func (p *novaPlugin) findOSType(provider *gophercloud.ProviderClient, imageID string) (string, error) {
	client, err := p.Client(provider)
	if err != nil {
		return "", err
	}
	var result struct {
		Tags         []string `json:"tags"`
		VMwareOSType string   `json:"vmware_ostype"`
	}
	err = images.Get(client, imageID).ExtractInto(&result)
	if err != nil {
		//report a dummy value if image has been deleted...
		if _, ok := err.(gophercloud.ErrDefault404); ok {
			return "image-deleted", nil
		}
		//otherwise, try to GET image again during next scrape
		return "", err
	}

	//prefer vmware_ostype attribute since this is validated by Nova upon booting the VM
	if isValidVMwareOSType[result.VMwareOSType] {
		return result.VMwareOSType, nil
	}
	//look for a tag like "ostype:rhel7" or "ostype:windows8Server64"
	osType := ""
	for _, tag := range result.Tags {
		if strings.HasPrefix(tag, "ostype:") {
			if osType == "" {
				osType = strings.TrimPrefix(tag, "ostype:")
			} else {
				//multiple such tags -> wtf
				osType = ""
				break
			}
		}
	}

	//report "unknown" as a last resort
	if osType == "" {
		osType = "unknown"
	}
	return osType, nil
}

func makeIntPointer(value int) *int {
	return &value
}

type novaServerListOpts struct {
	AllTenants bool   `q:"all_tenants"`
	TenantID   string `q:"tenant_id"`
}

func (opts novaServerListOpts) ToServerListQuery() (string, error) {
	q, err := gophercloud.BuildQueryString(opts)
	return q.String(), err
}

type novaQuotaUpdateOpts map[string]uint64

func (opts novaQuotaUpdateOpts) ToComputeQuotaUpdateMap() (map[string]interface{}, error) {
	result := make(map[string]interface{}, len(opts))
	for key, val := range opts {
		result[key] = val
	}
	return map[string]interface{}{"quota_set": result}, nil
}
<|endoftext|>


<commit_before>package router

import (
	"net/http"
	"strings"
)

type router struct {
	// List of handlers that associated with known http methods (GET, POST ...)
	handlers map[string]*parser

	// If enabled, the router automatically replies to OPTIONS requests.
	// Nevertheless OPTIONS handlers take priority over automatic replies.
	optionsRepliesEnabled bool

	// Configurable handler which is called when a request cannot be routed.
	notAllowed func(Control)

	// Configurable handler which is called when panic happen.
	recoveryHandler func(Control)

	// Configurable handler which is allowed to take control
	// before it is called standard methods e.g. GET, PUT.
	middlewareHandler func(func(Control)) func(Control)

	// Configurable http.Handler which is called when URL path has not defined method.
	// If it is not set, http.NotFound is used.
	notFound func(Control)
}

func newRouter() Router {
	return &router{
		handlers: make(map[string]*parser),
	}
}

// GET registers a new request handle for HTTP GET method.
func (r *router) GET(path string, f func(Control)) {
	r.register("GET", path, f)
}

// PUT registers a new request handle for HTTP PUT method.
func (r *router) PUT(path string, f func(Control)) {
	r.register("PUT", path, f)
}

// POST registers a new request handle for HTTP POST method.
func (r *router) POST(path string, f func(Control)) {
	r.register("POST", path, f)
}

// DELETE registers a new request handle for HTTP DELETE method.
func (r *router) DELETE(path string, f func(Control)) {
	r.register("DELETE", path, f)
}

// HEAD registers a new request handle for HTTP HEAD method.
func (r *router) HEAD(path string, f func(Control)) {
	r.register("HEAD", path, f)
}

// OPTIONS registers a new request handle for HTTP OPTIONS method.
func (r *router) OPTIONS(path string, f func(Control)) {
	r.register("OPTIONS", path, f)
}

// PATCH registers a new request handle for HTTP PATCH method.
func (r *router) PATCH(path string, f func(Control)) {
	r.register("PATCH", path, f)
}

// If enabled, the router automatically replies to OPTIONS requests.
// Nevertheless OPTIONS handlers take priority over automatic replies.
// By default this option is disabled
func (r *router) UseOptionsReplies(enabled bool) {
	r.optionsRepliesEnabled = enabled
}

// SetupNotAllowedHandler defines own handler which is called when a request
// cannot be routed.
func (r *router) SetupNotAllowedHandler(f func(Control)) {
	r.notAllowed = f
}

// SetupNotFoundHandler allows to define own handler for undefined URL path.
// If it is not set, http.NotFound is used.
func (r *router) SetupNotFoundHandler(f func(Control)) {
	r.notFound = f
}

// SetupRecoveryHandler allows to define handler that called when panic happen.
// The handler prevents your server from crashing and should be used to return
// http status code http.StatusInternalServerError (500)
func (r *router) SetupRecoveryHandler(f func(Control)) {
	r.recoveryHandler = f
}

// SetupMiddleware defines handler is allowed to take control
// before it is called standard methods e.g. GET, PUT.
func (r *router) SetupMiddleware(f func(func(Control)) func(Control)) {
	r.middlewareHandler = f
}

// Listen and serve on requested host and port
func (r *router) Listen(hostPort string) error {
	return http.ListenAndServe(hostPort, r)
}

// registers a new handler with the given path and method.
func (r *router) register(method, path string, f func(Control)) {
	if r.handlers[method] == nil {
		r.handlers[method] = newParser()
	}
	r.handlers[method].register(path, f)
}

func (r *router) recovery(w http.ResponseWriter, req *http.Request) {
	if recv := recover(); recv != nil {
		c := newControl(w, req)
		r.recoveryHandler(c)
	}
}

// AllowedMethods returns list of allowed methods
func (r *router) allowedMethods(path string) []string {
	var allowed []string
	for method, parser := range r.handlers {
		if _, _, ok := parser.get(path); ok {
			allowed = append(allowed, method)
		}
	}

	return allowed
}

// ServeHTTP implements http.Handler interface.
func (r *router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if r.recoveryHandler != nil {
		defer r.recovery(w, req)
	}
	if _, ok := r.handlers[req.Method]; ok {
		if handle, params, ok := r.handlers[req.Method].get(req.URL.Path); ok {
			c := newControl(w, req)
			if len(params) > 0 {
				for _, item := range params {
					c.Param(item.key, item.value)
				}
			}
			if r.middlewareHandler != nil {
				r.middlewareHandler(handle)(c)
			} else {
				handle(c)
			}
			return
		}
	}
	allowed := r.allowedMethods(req.URL.Path)

	if len(allowed) == 0 {
		if r.notFound != nil {
			c := newControl(w, req)
			r.notFound(c)
		} else {
			http.NotFound(w, req)
		}
		return
	}

	w.Header().Set("Allow", strings.Join(allowed, ", "))
	if req.Method == "OPTIONS" && r.optionsRepliesEnabled {
		return
	}
	if r.notAllowed != nil {
		c := newControl(w, req)
		r.notAllowed(c)
	} else {
		http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
	}
}
<commit_msg>copyright<commit_after>// Copyright 2017 Igor Dolzhikov. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package router

import (
	"net/http"
	"strings"
)

type router struct {
	// List of handlers that associated with known http methods (GET, POST ...)
	handlers map[string]*parser

	// If enabled, the router automatically replies to OPTIONS requests.
	// Nevertheless OPTIONS handlers take priority over automatic replies.
	optionsRepliesEnabled bool

	// Configurable handler which is called when a request cannot be routed.
	notAllowed func(Control)

	// Configurable handler which is called when panic happen.
	recoveryHandler func(Control)

	// Configurable handler which is allowed to take control
	// before it is called standard methods e.g. GET, PUT.
	middlewareHandler func(func(Control)) func(Control)

	// Configurable http.Handler which is called when URL path has not defined method.
	// If it is not set, http.NotFound is used.
	notFound func(Control)
}

func newRouter() Router {
	return &router{
		handlers: make(map[string]*parser),
	}
}

// GET registers a new request handle for HTTP GET method.
func (r *router) GET(path string, f func(Control)) {
	r.register("GET", path, f)
}

// PUT registers a new request handle for HTTP PUT method.
func (r *router) PUT(path string, f func(Control)) {
	r.register("PUT", path, f)
}

// POST registers a new request handle for HTTP POST method.
func (r *router) POST(path string, f func(Control)) {
	r.register("POST", path, f)
}

// DELETE registers a new request handle for HTTP DELETE method.
func (r *router) DELETE(path string, f func(Control)) {
	r.register("DELETE", path, f)
}

// HEAD registers a new request handle for HTTP HEAD method.
func (r *router) HEAD(path string, f func(Control)) {
	r.register("HEAD", path, f)
}

// OPTIONS registers a new request handle for HTTP OPTIONS method.
func (r *router) OPTIONS(path string, f func(Control)) {
	r.register("OPTIONS", path, f)
}

// PATCH registers a new request handle for HTTP PATCH method.
func (r *router) PATCH(path string, f func(Control)) {
	r.register("PATCH", path, f)
}

// If enabled, the router automatically replies to OPTIONS requests.
// Nevertheless OPTIONS handlers take priority over automatic replies.
// By default this option is disabled
func (r *router) UseOptionsReplies(enabled bool) {
	r.optionsRepliesEnabled = enabled
}

// SetupNotAllowedHandler defines own handler which is called when a request
// cannot be routed.
func (r *router) SetupNotAllowedHandler(f func(Control)) {
	r.notAllowed = f
}

// SetupNotFoundHandler allows to define own handler for undefined URL path.
// If it is not set, http.NotFound is used.
func (r *router) SetupNotFoundHandler(f func(Control)) {
	r.notFound = f
}

// SetupRecoveryHandler allows to define handler that called when panic happen.
// The handler prevents your server from crashing and should be used to return
// http status code http.StatusInternalServerError (500)
func (r *router) SetupRecoveryHandler(f func(Control)) {
	r.recoveryHandler = f
}

// SetupMiddleware defines handler is allowed to take control
// before it is called standard methods e.g. GET, PUT.
func (r *router) SetupMiddleware(f func(func(Control)) func(Control)) {
	r.middlewareHandler = f
}

// Listen and serve on requested host and port
func (r *router) Listen(hostPort string) error {
	return http.ListenAndServe(hostPort, r)
}

// registers a new handler with the given path and method.
func (r *router) register(method, path string, f func(Control)) {
	if r.handlers[method] == nil {
		r.handlers[method] = newParser()
	}
	r.handlers[method].register(path, f)
}

func (r *router) recovery(w http.ResponseWriter, req *http.Request) {
	if recv := recover(); recv != nil {
		c := newControl(w, req)
		r.recoveryHandler(c)
	}
}

// AllowedMethods returns list of allowed methods
func (r *router) allowedMethods(path string) []string {
	var allowed []string
	for method, parser := range r.handlers {
		if _, _, ok := parser.get(path); ok {
			allowed = append(allowed, method)
		}
	}

	return allowed
}

// ServeHTTP implements http.Handler interface.
func (r *router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if r.recoveryHandler != nil {
		defer r.recovery(w, req)
	}
	if _, ok := r.handlers[req.Method]; ok {
		if handle, params, ok := r.handlers[req.Method].get(req.URL.Path); ok {
			c := newControl(w, req)
			if len(params) > 0 {
				for _, item := range params {
					c.Param(item.key, item.value)
				}
			}
			if r.middlewareHandler != nil {
				r.middlewareHandler(handle)(c)
			} else {
				handle(c)
			}
			return
		}
	}
	allowed := r.allowedMethods(req.URL.Path)

	if len(allowed) == 0 {
		if r.notFound != nil {
			c := newControl(w, req)
			r.notFound(c)
		} else {
			http.NotFound(w, req)
		}
		return
	}

	w.Header().Set("Allow", strings.Join(allowed, ", "))
	if req.Method == "OPTIONS" && r.optionsRepliesEnabled {
		return
	}
	if r.notAllowed != nil {
		c := newControl(w, req)
		r.notAllowed(c)
	} else {
		http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
	}
}
<|endoftext|>