<commit_before>package logi

import (
	"os"
	"time"
)

var (
	loggerMap = make(map[string]*txLogger)

	flushTicker *time.Ticker
	tickerQuit  = make(chan struct{})
)

func flushTimely() {
	for {
		select {
		case <-flushTicker.C:
			logf("flushTimely:%d", len(loggerMap))
			FlushAll()
		case <-tickerQuit:
			flushTicker.Stop()
			return
		}
	}
}

func writeLog(name string, prefix string, s string) {
	// get current time early.
	now := time.Now()

	// retrieve/new logger
	lg, ok := loggerMap[name]
	if !ok {
		lg = newTXLogger(name)
		loggerMap[name] = lg
	}

	// build log content
	var buf []byte
	buf = buf[:0]
	formatHeader(&buf, now)
	buf = append(buf, prefix...)
	buf = append(buf, s...)

	if len(s) == 0 || s[len(s)-1] != '\n' {
		buf = append(buf, '\n')
	}

	// write console
	if !CfgQuietMode() {
		os.Stdout.Write(buf)
	}

	// write to in-memory buffer
	lg.buffer(buf)
}

// Folk from "log" package
func formatHeader(buf *[]byte, t time.Time) {
	// ignore
	// *buf = append(*buf, l.prefix...)
	year, month, day := t.Date()
	itoa(buf, year, 4)
	*buf = append(*buf, '/')
	itoa(buf, int(month), 2)
	*buf = append(*buf, '/')
	itoa(buf, day, 2)
	*buf = append(*buf, ' ')

	hour, min, sec := t.Clock()
	itoa(buf, hour, 2)
	*buf = append(*buf, ':')
	itoa(buf, min, 2)
	*buf = append(*buf, ':')
	itoa(buf, sec, 2)
	// *buf = append(*buf, '.')
	// itoa(buf, t.Nanosecond()/1e3, 6)
	*buf = append(*buf, ' ')
}

// Folk from "log" package
// Cheap integer to fixed-width decimal ASCII.  Give a negative width to avoid zero-padding.
func itoa(buf *[]byte, i int, wid int) {
	// Assemble decimal in reverse order.
	var b [20]byte
	bp := len(b) - 1
	for i >= 10 || wid > 1 {
		wid--
		q := i / 10
		b[bp] = byte('0' + i - q*10)
		bp--
		i = q
	}
	// i < 10
	b[bp] = byte('0' + i)
	*buf = append(*buf, b[bp:]...)
}
<commit_msg>bug-fix<commit_after>package logi

import (
	"os"
	"time"
)

var (
	loggerMap = make(map[string]*txLogger)

	flushTicker *time.Ticker
)

func flushTimely() {
	for t := range flushTicker.C {
		t.String()
		FlushAll()
	}
}

func writeLog(name string, prefix string, s string) {
	// get current time early.
	now := time.Now()

	// retrieve/new logger
	lg, ok := loggerMap[name]
	if !ok {
		lg = newTXLogger(name)
		loggerMap[name] = lg
	}

	// build log content
	var buf []byte
	buf = buf[:0]
	formatHeader(&buf, now)
	buf = append(buf, prefix...)
	buf = append(buf, s...)

	if len(s) == 0 || s[len(s)-1] != '\n' {
		buf = append(buf, '\n')
	}

	// write console
	if !CfgQuietMode() {
		os.Stdout.Write(buf)
	}

	// write to in-memory buffer
	lg.buffer(buf)
}

// Folk from "log" package
func formatHeader(buf *[]byte, t time.Time) {
	// ignore
	// *buf = append(*buf, l.prefix...)
	year, month, day := t.Date()
	itoa(buf, year, 4)
	*buf = append(*buf, '/')
	itoa(buf, int(month), 2)
	*buf = append(*buf, '/')
	itoa(buf, day, 2)
	*buf = append(*buf, ' ')

	hour, min, sec := t.Clock()
	itoa(buf, hour, 2)
	*buf = append(*buf, ':')
	itoa(buf, min, 2)
	*buf = append(*buf, ':')
	itoa(buf, sec, 2)
	// *buf = append(*buf, '.')
	// itoa(buf, t.Nanosecond()/1e3, 6)
	*buf = append(*buf, ' ')
}

// Folk from "log" package
// Cheap integer to fixed-width decimal ASCII.  Give a negative width to avoid zero-padding.
func itoa(buf *[]byte, i int, wid int) {
	// Assemble decimal in reverse order.
	var b [20]byte
	bp := len(b) - 1
	for i >= 10 || wid > 1 {
		wid--
		q := i / 10
		b[bp] = byte('0' + i - q*10)
		bp--
		i = q
	}
	// i < 10
	b[bp] = byte('0' + i)
	*buf = append(*buf, b[bp:]...)
}
<|endoftext|>


<commit_before>// SPDX-License-Identifier: MIT

package mux

import (
	"errors"
	"net/http"
	"strconv"
	"strings"

	"github.com/issue9/sliceutil"

	"github.com/issue9/mux/v5/internal/tree"
)

// CORS 跨域请求设置项
//
// https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS
type CORS struct {
	// AllowedOrigins 允许的外部域名列表
	//
	// 可以是 *，如果包含了 *，那么其它的设置将不再启作用。
	// 此字段将被用于与请求头的 Origin 字段作验证，以确定是否放行该请求。
	//
	// 如果此值为空，表示不启用跨域的相关设置。
	AllowedOrigins []string
	anyOrigins     bool
	deny           bool

	// AllowedHeaders 实际请求中允许携带的报头
	//
	// 可以包含 *，表示可以是任意值，其它值将不再启作用。
	AllowedHeaders       []string
	allowedHeadersString string
	anyHeaders           bool

	// ExposedHeaders Access-Control-Expose-Headers
	ExposedHeaders       []string
	exposedHeadersString string

	// MaxAge 当前报头信息可被缓存的秒数
	//
	// 0 不输出该 Access-Control-Max-Age 报头；
	// 其它 >= -1 的值正常输出数值；
	MaxAge       int
	maxAgeString string

	// AllowCredentials 是否允许 cookie
	AllowCredentials bool
}

// AllowedCORS 允许跨域请求
func AllowedCORS() *CORS {
	return &CORS{
		AllowedOrigins: []string{"*"},
		AllowedHeaders: []string{"*"},
		MaxAge:         3600,
	}
}

// DeniedCORS 禁用跨域请求
func DeniedCORS() *CORS { return &CORS{} }

func (c *CORS) sanitize() error {
	for _, o := range c.AllowedOrigins {
		if o == "*" {
			c.anyOrigins = true
			break
		}
	}
	c.deny = len(c.AllowedOrigins) == 0

	for _, h := range c.AllowedHeaders {
		if h == "*" {
			c.allowedHeadersString = "*"
			c.anyHeaders = true
			break
		}
	}
	if c.allowedHeadersString == "" && len(c.AllowedHeaders) > 0 {
		c.allowedHeadersString = strings.Join(c.AllowedHeaders, ",")
	}

	if len(c.ExposedHeaders) > 0 {
		c.exposedHeadersString = strings.Join(c.ExposedHeaders, ",")
	}

	switch {
	case c.MaxAge == 0:
	case c.MaxAge >= -1:
		c.maxAgeString = strconv.Itoa(c.MaxAge)
	default:
		return errors.New("MaxAge 的值只能是 >= -1")
	}

	if c.anyOrigins && c.AllowCredentials {
		return errors.New("AllowedOrigin=* 和 AllowCredentials=true 不能同时成立")
	}

	return nil
}

func (c *CORS) handle(node *tree.Node, w http.ResponseWriter, r *http.Request) {
	if c.deny {
		return
	}

	// Origin 是可以为空的，所以采用 Access-Control-Request-Method 判断是否为预检。
	reqMethod := r.Header.Get("Access-Control-Request-Method")
	preflight := r.Method == http.MethodOptions && reqMethod != ""

	wh := w.Header()

	if preflight {
		// Access-Control-Allow-Methods
		methods := node.Methods()
		if sliceutil.Index(methods, func(i int) bool { return methods[i] == reqMethod }) == -1 {
			return
		}
		wh.Set("Access-Control-Allow-Methods", node.Options())
		wh.Add("Vary", "Access-Control-Request-Method")

		// Access-Control-Allow-Headers
		if !c.headerIsAllowed(r) {
			return
		}
		if c.allowedHeadersString != "" {
			wh.Set("Access-Control-Allow-Headers", c.allowedHeadersString)
			wh.Add("Vary", "Access-Control-Request-Headers")
		}

		// Access-Control-Max-Age
		if c.maxAgeString != "" {
			wh.Set("Access-Control-Max-Age", c.maxAgeString)
		}
	}

	// Access-Control-Allow-Origin
	allowOrigin := "*"
	if !c.anyOrigins {
		origin := r.Header.Get("Origin")
		i := sliceutil.Index(c.AllowedOrigins, func(i int) bool { return c.AllowedOrigins[i] == origin })
		if i == -1 {
			return
		}
		allowOrigin = origin
	}
	wh.Set("Access-Control-Allow-Origin", allowOrigin)
	wh.Add("Vary", "Origin")

	// Access-Control-Allow-Credentials
	if c.AllowCredentials {
		wh.Set("Access-Control-Allow-Credentials", "true")
	}

	// Access-Control-Expose-Headers
	if c.exposedHeadersString != "" {
		wh.Set("Access-Control-Expose-Headers", c.exposedHeadersString)
	}
}

func (c *CORS) headerIsAllowed(r *http.Request) bool {
	if c.anyHeaders {
		return true
	}

	h := strings.TrimSpace(r.Header.Get("Access-Control-Request-Headers"))
	if h == "" {
		return true
	}

	headers := strings.Split(h, ",")
	for _, v := range headers {
		v = strings.TrimSpace(v)
		i := sliceutil.Index(c.AllowedHeaders, func(i int) bool { return c.AllowedHeaders[i] == v })
		if i == -1 {
			return false
		}
	}

	return true
}
<commit_msg>feat: 为 CORS 添加 struct tag<commit_after>// SPDX-License-Identifier: MIT

package mux

import (
	"errors"
	"net/http"
	"strconv"
	"strings"

	"github.com/issue9/sliceutil"

	"github.com/issue9/mux/v5/internal/tree"
)

// CORS 跨域请求设置项
//
// https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS
type CORS struct {
	XMLName struct{} `json:"-" yaml:"-" xml:"cors"`

	// AllowedOrigins 对应 Origin
	//
	// 可以是 *，如果包含了 *，那么其它的设置将不再启作用。
	// 此字段将被用于与请求头的 Origin 字段作验证，以确定是否放行该请求。
	//
	// 如果此值为空，表示不启用跨域的相关设置。
	AllowedOrigins []string `json:"allowedOrigins,omitempty" yaml:"allowedOrigins,omitempty" xml:"allowedOrigins,omitempty"`
	anyOrigins     bool
	deny           bool

	// AllowedHeaders 对应 Access-Control-Allow-Headers
	//
	// 可以包含 *，表示可以是任意值，其它值将不再启作用。
	AllowedHeaders       []string `json:"allowedHeaders,omitempty" yaml:"allowedHeaders,omitempty" xml:"allowedHeaders,omitempty"`
	allowedHeadersString string
	anyHeaders           bool

	// ExposedHeaders 对应 Access-Control-Expose-Headers
	ExposedHeaders       []string `json:"exposedHeaders,omitempty" yaml:"exposedHeaders,omitempty" xml:"exposedHeaders,omitempty"`
	exposedHeadersString string

	// MaxAge 当前报头信息可被缓存的秒数
	//
	// 0 不输出该 Access-Control-Max-Age 报头；
	// 其它 >= -1 的值正常输出数值；
	MaxAge       int `json:"maxAge,omitempty" yaml:"maxAge,omitempty" xml:"maxAge,attr,omitempty"`
	maxAgeString string

	// AllowCredentials 是否允许 cookie
	AllowCredentials bool `json:"allowCredentials,omitempty" yaml:"allowCredentials,omitempty" xml:"allowCredentials,attr,omitempty"`
}

// AllowedCORS 允许跨域请求
func AllowedCORS() *CORS {
	return &CORS{
		AllowedOrigins: []string{"*"},
		AllowedHeaders: []string{"*"},
		MaxAge:         3600,
	}
}

// DeniedCORS 禁用跨域请求
func DeniedCORS() *CORS { return &CORS{} }

func (c *CORS) sanitize() error {
	for _, o := range c.AllowedOrigins {
		if o == "*" {
			c.anyOrigins = true
			break
		}
	}
	c.deny = len(c.AllowedOrigins) == 0

	for _, h := range c.AllowedHeaders {
		if h == "*" {
			c.allowedHeadersString = "*"
			c.anyHeaders = true
			break
		}
	}
	if c.allowedHeadersString == "" && len(c.AllowedHeaders) > 0 {
		c.allowedHeadersString = strings.Join(c.AllowedHeaders, ",")
	}

	if len(c.ExposedHeaders) > 0 {
		c.exposedHeadersString = strings.Join(c.ExposedHeaders, ",")
	}

	switch {
	case c.MaxAge == 0:
	case c.MaxAge >= -1:
		c.maxAgeString = strconv.Itoa(c.MaxAge)
	default:
		return errors.New("MaxAge 的值只能是 >= -1")
	}

	if c.anyOrigins && c.AllowCredentials {
		return errors.New("AllowedOrigin=* 和 AllowCredentials=true 不能同时成立")
	}

	return nil
}

func (c *CORS) handle(node *tree.Node, w http.ResponseWriter, r *http.Request) {
	if c.deny {
		return
	}

	// Origin 是可以为空的，所以采用 Access-Control-Request-Method 判断是否为预检。
	reqMethod := r.Header.Get("Access-Control-Request-Method")
	preflight := r.Method == http.MethodOptions && reqMethod != ""

	wh := w.Header()

	if preflight {
		// Access-Control-Allow-Methods
		methods := node.Methods()
		if sliceutil.Index(methods, func(i int) bool { return methods[i] == reqMethod }) == -1 {
			return
		}
		wh.Set("Access-Control-Allow-Methods", node.Options())
		wh.Add("Vary", "Access-Control-Request-Method")

		// Access-Control-Allow-Headers
		if !c.headerIsAllowed(r) {
			return
		}
		if c.allowedHeadersString != "" {
			wh.Set("Access-Control-Allow-Headers", c.allowedHeadersString)
			wh.Add("Vary", "Access-Control-Request-Headers")
		}

		// Access-Control-Max-Age
		if c.maxAgeString != "" {
			wh.Set("Access-Control-Max-Age", c.maxAgeString)
		}
	}

	// Access-Control-Allow-Origin
	allowOrigin := "*"
	if !c.anyOrigins {
		origin := r.Header.Get("Origin")
		i := sliceutil.Index(c.AllowedOrigins, func(i int) bool { return c.AllowedOrigins[i] == origin })
		if i == -1 {
			return
		}
		allowOrigin = origin
	}
	wh.Set("Access-Control-Allow-Origin", allowOrigin)
	wh.Add("Vary", "Origin")

	// Access-Control-Allow-Credentials
	if c.AllowCredentials {
		wh.Set("Access-Control-Allow-Credentials", "true")
	}

	// Access-Control-Expose-Headers
	if c.exposedHeadersString != "" {
		wh.Set("Access-Control-Expose-Headers", c.exposedHeadersString)
	}
}

func (c *CORS) headerIsAllowed(r *http.Request) bool {
	if c.anyHeaders {
		return true
	}

	h := strings.TrimSpace(r.Header.Get("Access-Control-Request-Headers"))
	if h == "" {
		return true
	}

	headers := strings.Split(h, ",")
	for _, v := range headers {
		v = strings.TrimSpace(v)
		i := sliceutil.Index(c.AllowedHeaders, func(i int) bool { return c.AllowedHeaders[i] == v })
		if i == -1 {
			return false
		}
	}

	return true
}
<|endoftext|>


<commit_before>/*
Package cors is net/http handler to handle CORS related requests
as defined by http://www.w3.org/TR/cors/

You can configure it by passing an option struct to cors.New:

    c := cors.New(cors.Options{
        AllowedOrigins: []string{"foo.com"},
        AllowedMethods: []string{"GET", "POST", "DELETE"},
        AllowCredentials: true,
    })

Then insert the handler in the chain:

    handler = c.Handler(handler)

See Options documentation for more options.

The resulting handler is a standard net/http handler.
*/
package cors

import (
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"

	"github.com/rs/xhandler"
	"golang.org/x/net/context"
)

// Options is a configuration container to setup the CORS middleware.
type Options struct {
	// AllowedOrigins is a list of origins a cross-domain request can be executed from.
	// If the special "*" value is present in the list, all origins will be allowed.
	// An origin may contain a wildcard (*) to replace 0 or more characters
	// (i.e.: http://*.domain.com). Usage of wildcards implies a small performance penality.
	// Only one wildcard can be used per origin.
	// Default value is ["*"]
	AllowedOrigins []string
	// AllowOriginFunc is a custom function to validate the origin. It take the origin
	// as argument and returns true if allowed or false otherwise. If this option is
	// set, the content of AllowedOrigins is ignored.
	AllowOriginFunc func(origin string) bool
	// AllowedMethods is a list of methods the client is allowed to use with
	// cross-domain requests. Default value is simple methods (GET and POST)
	AllowedMethods []string
	// AllowedHeaders is list of non simple headers the client is allowed to use with
	// cross-domain requests.
	// If the special "*" value is present in the list, all headers will be allowed.
	// Default value is [] but "Origin" is always appended to the list.
	AllowedHeaders []string
	// ExposedHeaders indicates which headers are safe to expose to the API of a CORS
	// API specification
	ExposedHeaders []string
	// AllowCredentials indicates whether the request can include user credentials like
	// cookies, HTTP authentication or client side SSL certificates.
	AllowCredentials bool
	// MaxAge indicates how long (in seconds) the results of a preflight request
	// can be cached
	MaxAge int
	// OptionsPassthrough instructs preflight to let other potential next handlers to
	// process the OPTIONS method. Turn this on if your application handles OPTIONS.
	OptionsPassthrough bool
	// Debugging flag adds additional output to debug server side CORS issues
	Debug bool
}

// Cors http handler
type Cors struct {
	// Debug logger
	Log *log.Logger
	// Set to true when allowed origins contains a "*"
	allowedOriginsAll bool
	// Normalized list of plain allowed origins
	allowedOrigins []string
	// List of allowed origins containing wildcards
	allowedWOrigins []wildcard
	// Optional origin validator function
	allowOriginFunc func(origin string) bool
	// Set to true when allowed headers contains a "*"
	allowedHeadersAll bool
	// Normalized list of allowed headers
	allowedHeaders []string
	// Normalized list of allowed methods
	allowedMethods []string
	// Normalized list of exposed headers
	exposedHeaders    []string
	allowCredentials  bool
	maxAge            int
	optionPassthrough bool
}

// New creates a new Cors handler with the provided options.
func New(options Options) *Cors {
	c := &Cors{
		exposedHeaders:    convert(options.ExposedHeaders, http.CanonicalHeaderKey),
		allowOriginFunc:   options.AllowOriginFunc,
		allowCredentials:  options.AllowCredentials,
		maxAge:            options.MaxAge,
		optionPassthrough: options.OptionsPassthrough,
	}
	if options.Debug {
		c.Log = log.New(os.Stdout, "[cors] ", log.LstdFlags)
	}

	// Normalize options
	// Note: for origins and methods matching, the spec requires a case-sensitive matching.
	// As it may error prone, we chose to ignore the spec here.

	// Allowed Origins
	if len(options.AllowedOrigins) == 0 {
		// Default is all origins
		c.allowedOriginsAll = true
	} else {
		c.allowedOrigins = []string{}
		c.allowedWOrigins = []wildcard{}
		for _, origin := range options.AllowedOrigins {
			// Normalize
			origin = strings.ToLower(origin)
			if origin == "*" {
				// If "*" is present in the list, turn the whole list into a match all
				c.allowedOriginsAll = true
				c.allowedOrigins = nil
				c.allowedWOrigins = nil
				break
			} else if i := strings.IndexByte(origin, '*'); i >= 0 {
				// Split the origin in two: start and end string without the *
				w := wildcard{origin[0:i], origin[i+1 : len(origin)]}
				c.allowedWOrigins = append(c.allowedWOrigins, w)
			} else {
				c.allowedOrigins = append(c.allowedOrigins, origin)
			}
		}
	}

	// Allowed Headers
	if len(options.AllowedHeaders) == 0 {
		// Use sensible defaults
		c.allowedHeaders = []string{"Origin", "Accept", "Content-Type"}
	} else {
		// Origin is always appended as some browsers will always request for this header at preflight
		c.allowedHeaders = convert(append(options.AllowedHeaders, "Origin"), http.CanonicalHeaderKey)
		for _, h := range options.AllowedHeaders {
			if h == "*" {
				c.allowedHeadersAll = true
				c.allowedHeaders = nil
				break
			}
		}
	}

	// Allowed Methods
	if len(options.AllowedMethods) == 0 {
		// Default is spec's "simple" methods
		c.allowedMethods = []string{"GET", "POST"}
	} else {
		c.allowedMethods = convert(options.AllowedMethods, strings.ToUpper)
	}

	return c
}

// Default creates a new Cors handler with default options
func Default() *Cors {
	return New(Options{})
}

// Handler apply the CORS specification on the request, and add relevant CORS headers
// as necessary.
func (c *Cors) Handler(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method == "OPTIONS" {
			c.logf("Handler: Preflight request")
			c.handlePreflight(w, r)
			// Preflight requests are standalone and should stop the chain as some other
			// middleware may not handle OPTIONS requests correctly. One typical example
			// is authentication middleware ; OPTIONS requests won't carry authentication
			// headers (see #1)
			if c.optionPassthrough {
				h.ServeHTTP(w, r)
			} else {
				w.WriteHeader(http.StatusOK)
			}
		} else {
			c.logf("Handler: Actual request")
			c.handleActualRequest(w, r)
			h.ServeHTTP(w, r)
		}
	})
}

// HandlerC is net/context aware handler
func (c *Cors) HandlerC(h xhandler.HandlerC) xhandler.HandlerC {
	return xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		if r.Method == "OPTIONS" {
			c.logf("Handler: Preflight request")
			c.handlePreflight(w, r)
			// Preflight requests are standalone and should stop the chain as some other
			// middleware may not handle OPTIONS requests correctly. One typical example
			// is authentication middleware ; OPTIONS requests won't carry authentication
			// headers (see #1)
			if c.optionPassthrough {
				h.ServeHTTPC(ctx, w, r)
			}
		} else {
			c.logf("Handler: Actual request")
			c.handleActualRequest(w, r)
			h.ServeHTTPC(ctx, w, r)
		}
	})
}

// HandlerFunc provides Martini compatible handler
func (c *Cors) HandlerFunc(w http.ResponseWriter, r *http.Request) {
	if r.Method == "OPTIONS" {
		c.logf("HandlerFunc: Preflight request")
		c.handlePreflight(w, r)
	} else {
		c.logf("HandlerFunc: Actual request")
		c.handleActualRequest(w, r)
	}
}

// Negroni compatible interface
func (c *Cors) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	if r.Method == "OPTIONS" {
		c.logf("ServeHTTP: Preflight request")
		c.handlePreflight(w, r)
		// Preflight requests are standalone and should stop the chain as some other
		// middleware may not handle OPTIONS requests correctly. One typical example
		// is authentication middleware ; OPTIONS requests won't carry authentication
		// headers (see #1)
		if c.optionPassthrough {
			next(w, r)
		}
	} else {
		c.logf("ServeHTTP: Actual request")
		c.handleActualRequest(w, r)
		next(w, r)
	}
}

// handlePreflight handles pre-flight CORS requests
func (c *Cors) handlePreflight(w http.ResponseWriter, r *http.Request) {
	headers := w.Header()
	origin := r.Header.Get("Origin")

	if r.Method != "OPTIONS" {
		c.logf("  Preflight aborted: %s!=OPTIONS", r.Method)
		return
	}
	// Always set Vary headers
	// see https://github.com/rs/cors/issues/10,
	//     https://github.com/rs/cors/commit/dbdca4d95feaa7511a46e6f1efb3b3aa505bc43f#commitcomment-12352001
	headers.Add("Vary", "Origin")
	headers.Add("Vary", "Access-Control-Request-Method")
	headers.Add("Vary", "Access-Control-Request-Headers")

	if origin == "" {
		c.logf("  Preflight aborted: empty origin")
		return
	}
	if !c.isOriginAllowed(origin) {
		c.logf("  Preflight aborted: origin '%s' not allowed", origin)
		return
	}

	reqMethod := r.Header.Get("Access-Control-Request-Method")
	if !c.isMethodAllowed(reqMethod) {
		c.logf("  Preflight aborted: method '%s' not allowed", reqMethod)
		return
	}
	reqHeaders := parseHeaderList(r.Header.Get("Access-Control-Request-Headers"))
	if !c.areHeadersAllowed(reqHeaders) {
		c.logf("  Preflight aborted: headers '%v' not allowed", reqHeaders)
		return
	}
	headers.Set("Access-Control-Allow-Origin", origin)
	// Spec says: Since the list of methods can be unbounded, simply returning the method indicated
	// by Access-Control-Request-Method (if supported) can be enough
	headers.Set("Access-Control-Allow-Methods", strings.ToUpper(reqMethod))
	if len(reqHeaders) > 0 {

		// Spec says: Since the list of headers can be unbounded, simply returning supported headers
		// from Access-Control-Request-Headers can be enough
		headers.Set("Access-Control-Allow-Headers", strings.Join(reqHeaders, ", "))
	}
	if c.allowCredentials {
		headers.Set("Access-Control-Allow-Credentials", "true")
	}
	if c.maxAge > 0 {
		headers.Set("Access-Control-Max-Age", strconv.Itoa(c.maxAge))
	}
	c.logf("  Preflight response headers: %v", headers)
}

// handleActualRequest handles simple cross-origin requests, actual request or redirects
func (c *Cors) handleActualRequest(w http.ResponseWriter, r *http.Request) {
	headers := w.Header()
	origin := r.Header.Get("Origin")

	if r.Method == "OPTIONS" {
		c.logf("  Actual request no headers added: method == %s", r.Method)
		return
	}
	// Always set Vary, see https://github.com/rs/cors/issues/10
	headers.Add("Vary", "Origin")
	if origin == "" {
		c.logf("  Actual request no headers added: missing origin")
		return
	}
	if !c.isOriginAllowed(origin) {
		c.logf("  Actual request no headers added: origin '%s' not allowed", origin)
		return
	}

	// Note that spec does define a way to specifically disallow a simple method like GET or
	// POST. Access-Control-Allow-Methods is only used for pre-flight requests and the
	// spec doesn't instruct to check the allowed methods for simple cross-origin requests.
	// We think it's a nice feature to be able to have control on those methods though.
	if !c.isMethodAllowed(r.Method) {
		c.logf("  Actual request no headers added: method '%s' not allowed", r.Method)

		return
	}
	headers.Set("Access-Control-Allow-Origin", origin)
	if len(c.exposedHeaders) > 0 {
		headers.Set("Access-Control-Expose-Headers", strings.Join(c.exposedHeaders, ", "))
	}
	if c.allowCredentials {
		headers.Set("Access-Control-Allow-Credentials", "true")
	}
	c.logf("  Actual response added headers: %v", headers)
}

// convenience method. checks if debugging is turned on before printing
func (c *Cors) logf(format string, a ...interface{}) {
	if c.Log != nil {
		c.Log.Printf(format, a...)
	}
}

// isOriginAllowed checks if a given origin is allowed to perform cross-domain requests
// on the endpoint
func (c *Cors) isOriginAllowed(origin string) bool {
	if c.allowOriginFunc != nil {
		return c.allowOriginFunc(origin)
	}
	if c.allowedOriginsAll {
		return true
	}
	origin = strings.ToLower(origin)
	for _, o := range c.allowedOrigins {
		if o == origin {
			return true
		}
	}
	for _, w := range c.allowedWOrigins {
		if w.match(origin) {
			return true
		}
	}
	return false
}

// isMethodAllowed checks if a given method can be used as part of a cross-domain request
// on the endpoing
func (c *Cors) isMethodAllowed(method string) bool {
	if len(c.allowedMethods) == 0 {
		// If no method allowed, always return false, even for preflight request
		return false
	}
	method = strings.ToUpper(method)
	if method == "OPTIONS" {
		// Always allow preflight requests
		return true
	}
	for _, m := range c.allowedMethods {
		if m == method {
			return true
		}
	}
	return false
}

// areHeadersAllowed checks if a given list of headers are allowed to used within
// a cross-domain request.
func (c *Cors) areHeadersAllowed(requestedHeaders []string) bool {
	if c.allowedHeadersAll || len(requestedHeaders) == 0 {
		return true
	}
	for _, header := range requestedHeaders {
		header = http.CanonicalHeaderKey(header)
		found := false
		for _, h := range c.allowedHeaders {
			if h == header {
				found = true
			}
		}
		if !found {
			return false
		}
	}
	return true
}
<commit_msg>add remaining 200's when passthrough is false<commit_after>/*
Package cors is net/http handler to handle CORS related requests
as defined by http://www.w3.org/TR/cors/

You can configure it by passing an option struct to cors.New:

    c := cors.New(cors.Options{
        AllowedOrigins: []string{"foo.com"},
        AllowedMethods: []string{"GET", "POST", "DELETE"},
        AllowCredentials: true,
    })

Then insert the handler in the chain:

    handler = c.Handler(handler)

See Options documentation for more options.

The resulting handler is a standard net/http handler.
*/
package cors

import (
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"

	"github.com/rs/xhandler"
	"golang.org/x/net/context"
)

// Options is a configuration container to setup the CORS middleware.
type Options struct {
	// AllowedOrigins is a list of origins a cross-domain request can be executed from.
	// If the special "*" value is present in the list, all origins will be allowed.
	// An origin may contain a wildcard (*) to replace 0 or more characters
	// (i.e.: http://*.domain.com). Usage of wildcards implies a small performance penality.
	// Only one wildcard can be used per origin.
	// Default value is ["*"]
	AllowedOrigins []string
	// AllowOriginFunc is a custom function to validate the origin. It take the origin
	// as argument and returns true if allowed or false otherwise. If this option is
	// set, the content of AllowedOrigins is ignored.
	AllowOriginFunc func(origin string) bool
	// AllowedMethods is a list of methods the client is allowed to use with
	// cross-domain requests. Default value is simple methods (GET and POST)
	AllowedMethods []string
	// AllowedHeaders is list of non simple headers the client is allowed to use with
	// cross-domain requests.
	// If the special "*" value is present in the list, all headers will be allowed.
	// Default value is [] but "Origin" is always appended to the list.
	AllowedHeaders []string
	// ExposedHeaders indicates which headers are safe to expose to the API of a CORS
	// API specification
	ExposedHeaders []string
	// AllowCredentials indicates whether the request can include user credentials like
	// cookies, HTTP authentication or client side SSL certificates.
	AllowCredentials bool
	// MaxAge indicates how long (in seconds) the results of a preflight request
	// can be cached
	MaxAge int
	// OptionsPassthrough instructs preflight to let other potential next handlers to
	// process the OPTIONS method. Turn this on if your application handles OPTIONS.
	OptionsPassthrough bool
	// Debugging flag adds additional output to debug server side CORS issues
	Debug bool
}

// Cors http handler
type Cors struct {
	// Debug logger
	Log *log.Logger
	// Set to true when allowed origins contains a "*"
	allowedOriginsAll bool
	// Normalized list of plain allowed origins
	allowedOrigins []string
	// List of allowed origins containing wildcards
	allowedWOrigins []wildcard
	// Optional origin validator function
	allowOriginFunc func(origin string) bool
	// Set to true when allowed headers contains a "*"
	allowedHeadersAll bool
	// Normalized list of allowed headers
	allowedHeaders []string
	// Normalized list of allowed methods
	allowedMethods []string
	// Normalized list of exposed headers
	exposedHeaders    []string
	allowCredentials  bool
	maxAge            int
	optionPassthrough bool
}

// New creates a new Cors handler with the provided options.
func New(options Options) *Cors {
	c := &Cors{
		exposedHeaders:    convert(options.ExposedHeaders, http.CanonicalHeaderKey),
		allowOriginFunc:   options.AllowOriginFunc,
		allowCredentials:  options.AllowCredentials,
		maxAge:            options.MaxAge,
		optionPassthrough: options.OptionsPassthrough,
	}
	if options.Debug {
		c.Log = log.New(os.Stdout, "[cors] ", log.LstdFlags)
	}

	// Normalize options
	// Note: for origins and methods matching, the spec requires a case-sensitive matching.
	// As it may error prone, we chose to ignore the spec here.

	// Allowed Origins
	if len(options.AllowedOrigins) == 0 {
		// Default is all origins
		c.allowedOriginsAll = true
	} else {
		c.allowedOrigins = []string{}
		c.allowedWOrigins = []wildcard{}
		for _, origin := range options.AllowedOrigins {
			// Normalize
			origin = strings.ToLower(origin)
			if origin == "*" {
				// If "*" is present in the list, turn the whole list into a match all
				c.allowedOriginsAll = true
				c.allowedOrigins = nil
				c.allowedWOrigins = nil
				break
			} else if i := strings.IndexByte(origin, '*'); i >= 0 {
				// Split the origin in two: start and end string without the *
				w := wildcard{origin[0:i], origin[i+1 : len(origin)]}
				c.allowedWOrigins = append(c.allowedWOrigins, w)
			} else {
				c.allowedOrigins = append(c.allowedOrigins, origin)
			}
		}
	}

	// Allowed Headers
	if len(options.AllowedHeaders) == 0 {
		// Use sensible defaults
		c.allowedHeaders = []string{"Origin", "Accept", "Content-Type"}
	} else {
		// Origin is always appended as some browsers will always request for this header at preflight
		c.allowedHeaders = convert(append(options.AllowedHeaders, "Origin"), http.CanonicalHeaderKey)
		for _, h := range options.AllowedHeaders {
			if h == "*" {
				c.allowedHeadersAll = true
				c.allowedHeaders = nil
				break
			}
		}
	}

	// Allowed Methods
	if len(options.AllowedMethods) == 0 {
		// Default is spec's "simple" methods
		c.allowedMethods = []string{"GET", "POST"}
	} else {
		c.allowedMethods = convert(options.AllowedMethods, strings.ToUpper)
	}

	return c
}

// Default creates a new Cors handler with default options
func Default() *Cors {
	return New(Options{})
}

// Handler apply the CORS specification on the request, and add relevant CORS headers
// as necessary.
func (c *Cors) Handler(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method == "OPTIONS" {
			c.logf("Handler: Preflight request")
			c.handlePreflight(w, r)
			// Preflight requests are standalone and should stop the chain as some other
			// middleware may not handle OPTIONS requests correctly. One typical example
			// is authentication middleware ; OPTIONS requests won't carry authentication
			// headers (see #1)
			if c.optionPassthrough {
				h.ServeHTTP(w, r)
			} else {
				w.WriteHeader(http.StatusOK)
			}
		} else {
			c.logf("Handler: Actual request")
			c.handleActualRequest(w, r)
			h.ServeHTTP(w, r)
		}
	})
}

// HandlerC is net/context aware handler
func (c *Cors) HandlerC(h xhandler.HandlerC) xhandler.HandlerC {
	return xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		if r.Method == "OPTIONS" {
			c.logf("Handler: Preflight request")
			c.handlePreflight(w, r)
			// Preflight requests are standalone and should stop the chain as some other
			// middleware may not handle OPTIONS requests correctly. One typical example
			// is authentication middleware ; OPTIONS requests won't carry authentication
			// headers (see #1)
			if c.optionPassthrough {
				h.ServeHTTPC(ctx, w, r)
			} else {
				w.WriteHeader(http.StatusOK)
			}
		} else {
			c.logf("Handler: Actual request")
			c.handleActualRequest(w, r)
			h.ServeHTTPC(ctx, w, r)
		}
	})
}

// HandlerFunc provides Martini compatible handler
func (c *Cors) HandlerFunc(w http.ResponseWriter, r *http.Request) {
	if r.Method == "OPTIONS" {
		c.logf("HandlerFunc: Preflight request")
		c.handlePreflight(w, r)
	} else {
		c.logf("HandlerFunc: Actual request")
		c.handleActualRequest(w, r)
	}
}

// Negroni compatible interface
func (c *Cors) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	if r.Method == "OPTIONS" {
		c.logf("ServeHTTP: Preflight request")
		c.handlePreflight(w, r)
		// Preflight requests are standalone and should stop the chain as some other
		// middleware may not handle OPTIONS requests correctly. One typical example
		// is authentication middleware ; OPTIONS requests won't carry authentication
		// headers (see #1)
		if c.optionPassthrough {
			next(w, r)
		} else {
			w.WriteHeader(http.StatusOK)
		}
	} else {
		c.logf("ServeHTTP: Actual request")
		c.handleActualRequest(w, r)
		next(w, r)
	}
}

// handlePreflight handles pre-flight CORS requests
func (c *Cors) handlePreflight(w http.ResponseWriter, r *http.Request) {
	headers := w.Header()
	origin := r.Header.Get("Origin")

	if r.Method != "OPTIONS" {
		c.logf("  Preflight aborted: %s!=OPTIONS", r.Method)
		return
	}
	// Always set Vary headers
	// see https://github.com/rs/cors/issues/10,
	//     https://github.com/rs/cors/commit/dbdca4d95feaa7511a46e6f1efb3b3aa505bc43f#commitcomment-12352001
	headers.Add("Vary", "Origin")
	headers.Add("Vary", "Access-Control-Request-Method")
	headers.Add("Vary", "Access-Control-Request-Headers")

	if origin == "" {
		c.logf("  Preflight aborted: empty origin")
		return
	}
	if !c.isOriginAllowed(origin) {
		c.logf("  Preflight aborted: origin '%s' not allowed", origin)
		return
	}

	reqMethod := r.Header.Get("Access-Control-Request-Method")
	if !c.isMethodAllowed(reqMethod) {
		c.logf("  Preflight aborted: method '%s' not allowed", reqMethod)
		return
	}
	reqHeaders := parseHeaderList(r.Header.Get("Access-Control-Request-Headers"))
	if !c.areHeadersAllowed(reqHeaders) {
		c.logf("  Preflight aborted: headers '%v' not allowed", reqHeaders)
		return
	}
	headers.Set("Access-Control-Allow-Origin", origin)
	// Spec says: Since the list of methods can be unbounded, simply returning the method indicated
	// by Access-Control-Request-Method (if supported) can be enough
	headers.Set("Access-Control-Allow-Methods", strings.ToUpper(reqMethod))
	if len(reqHeaders) > 0 {

		// Spec says: Since the list of headers can be unbounded, simply returning supported headers
		// from Access-Control-Request-Headers can be enough
		headers.Set("Access-Control-Allow-Headers", strings.Join(reqHeaders, ", "))
	}
	if c.allowCredentials {
		headers.Set("Access-Control-Allow-Credentials", "true")
	}
	if c.maxAge > 0 {
		headers.Set("Access-Control-Max-Age", strconv.Itoa(c.maxAge))
	}
	c.logf("  Preflight response headers: %v", headers)
}

// handleActualRequest handles simple cross-origin requests, actual request or redirects
func (c *Cors) handleActualRequest(w http.ResponseWriter, r *http.Request) {
	headers := w.Header()
	origin := r.Header.Get("Origin")

	if r.Method == "OPTIONS" {
		c.logf("  Actual request no headers added: method == %s", r.Method)
		return
	}
	// Always set Vary, see https://github.com/rs/cors/issues/10
	headers.Add("Vary", "Origin")
	if origin == "" {
		c.logf("  Actual request no headers added: missing origin")
		return
	}
	if !c.isOriginAllowed(origin) {
		c.logf("  Actual request no headers added: origin '%s' not allowed", origin)
		return
	}

	// Note that spec does define a way to specifically disallow a simple method like GET or
	// POST. Access-Control-Allow-Methods is only used for pre-flight requests and the
	// spec doesn't instruct to check the allowed methods for simple cross-origin requests.
	// We think it's a nice feature to be able to have control on those methods though.
	if !c.isMethodAllowed(r.Method) {
		c.logf("  Actual request no headers added: method '%s' not allowed", r.Method)

		return
	}
	headers.Set("Access-Control-Allow-Origin", origin)
	if len(c.exposedHeaders) > 0 {
		headers.Set("Access-Control-Expose-Headers", strings.Join(c.exposedHeaders, ", "))
	}
	if c.allowCredentials {
		headers.Set("Access-Control-Allow-Credentials", "true")
	}
	c.logf("  Actual response added headers: %v", headers)
}

// convenience method. checks if debugging is turned on before printing
func (c *Cors) logf(format string, a ...interface{}) {
	if c.Log != nil {
		c.Log.Printf(format, a...)
	}
}

// isOriginAllowed checks if a given origin is allowed to perform cross-domain requests
// on the endpoint
func (c *Cors) isOriginAllowed(origin string) bool {
	if c.allowOriginFunc != nil {
		return c.allowOriginFunc(origin)
	}
	if c.allowedOriginsAll {
		return true
	}
	origin = strings.ToLower(origin)
	for _, o := range c.allowedOrigins {
		if o == origin {
			return true
		}
	}
	for _, w := range c.allowedWOrigins {
		if w.match(origin) {
			return true
		}
	}
	return false
}

// isMethodAllowed checks if a given method can be used as part of a cross-domain request
// on the endpoing
func (c *Cors) isMethodAllowed(method string) bool {
	if len(c.allowedMethods) == 0 {
		// If no method allowed, always return false, even for preflight request
		return false
	}
	method = strings.ToUpper(method)
	if method == "OPTIONS" {
		// Always allow preflight requests
		return true
	}
	for _, m := range c.allowedMethods {
		if m == method {
			return true
		}
	}
	return false
}

// areHeadersAllowed checks if a given list of headers are allowed to used within
// a cross-domain request.
func (c *Cors) areHeadersAllowed(requestedHeaders []string) bool {
	if c.allowedHeadersAll || len(requestedHeaders) == 0 {
		return true
	}
	for _, header := range requestedHeaders {
		header = http.CanonicalHeaderKey(header)
		found := false
		for _, h := range c.allowedHeaders {
			if h == header {
				found = true
			}
		}
		if !found {
			return false
		}
	}
	return true
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"log"
	"net/http"
	"reflect"

	"github.com/go-martini/martini"
	"github.com/martini-contrib/render"
)

type Repository interface {
	Add(thing interface{}) error
	Get(id string) (interface{}, error)
	List() (interface{}, error)
}

type Remover interface {
	Remove(string) error
}

type Updater interface {
	Update(string, map[string]interface{}) (interface{}, error)
}

func crud(resource string, example interface{}, repo Repository, r martini.Router) interface{} {
	resourceType := reflect.TypeOf(example)
	resourcePtr := reflect.PtrTo(resourceType)
	prefix := "/" + resource

	r.Post(prefix, func(req *http.Request, r render.Render) {
		thing := reflect.New(resourceType).Interface()
		err := json.NewDecoder(req.Body).Decode(thing)
		if err != nil {
			// 400?
			return
		}

		err = repo.Add(thing)
		if err != nil {
			log.Println(err)
			r.JSON(500, struct{}{})
			return
		}
		r.JSON(200, thing)
	})

	lookup := func(c martini.Context, params martini.Params, req *http.Request, w http.ResponseWriter) {
		thing, err := repo.Get(params[resource+"_id"])
		if err != nil {
			if err == ErrNotFound {
				w.WriteHeader(404)
				return
			}
			log.Println(err)
			w.WriteHeader(500)
			return
		}
		c.Map(thing)
	}

	singletonPath := prefix + "/:" + resource + "_id"
	r.Get(singletonPath, lookup, func(c martini.Context, r render.Render) {
		r.JSON(200, c.Get(resourcePtr).Interface())
	})

	r.Get(prefix, func(r render.Render) {
		list, err := repo.List()
		if err != nil {
			log.Println(err)
			r.JSON(500, struct{}{})
			return
		}
		r.JSON(200, list)
	})

	if remover, ok := repo.(Remover); ok {
		r.Delete(singletonPath, lookup, func(params martini.Params, w http.ResponseWriter) {
			if err := remover.Remove(params[resource+"_id"]); err != nil {
				log.Println(err)
				w.WriteHeader(500)
				return
			}
		})
	}

	if updater, ok := repo.(Updater); ok {
		r.Post(singletonPath, func(params martini.Params, req *http.Request, r render.Render) {
			var data map[string]interface{}
			if err := json.NewDecoder(req.Body).Decode(&data); err != nil {
				r.JSON(400, struct{}{})
				return
			}
			app, err := updater.Update(params[resource+"_id"], data)
			if err != nil {
				log.Println(err)
				r.JSON(500, struct{}{})
				return
			}
			r.JSON(200, app)
		})
	}

	return lookup
}
<commit_msg>controller: Render an empty JSON array when crud lists are empty<commit_after>package main

import (
	"encoding/json"
	"log"
	"net/http"
	"reflect"

	"github.com/go-martini/martini"
	"github.com/martini-contrib/render"
)

type Repository interface {
	Add(thing interface{}) error
	Get(id string) (interface{}, error)
	List() (interface{}, error)
}

type Remover interface {
	Remove(string) error
}

type Updater interface {
	Update(string, map[string]interface{}) (interface{}, error)
}

func crud(resource string, example interface{}, repo Repository, r martini.Router) interface{} {
	resourceType := reflect.TypeOf(example)
	resourcePtr := reflect.PtrTo(resourceType)
	prefix := "/" + resource

	r.Post(prefix, func(req *http.Request, r render.Render) {
		thing := reflect.New(resourceType).Interface()
		err := json.NewDecoder(req.Body).Decode(thing)
		if err != nil {
			// 400?
			return
		}

		err = repo.Add(thing)
		if err != nil {
			log.Println(err)
			r.JSON(500, struct{}{})
			return
		}
		r.JSON(200, thing)
	})

	lookup := func(c martini.Context, params martini.Params, req *http.Request, w http.ResponseWriter) {
		thing, err := repo.Get(params[resource+"_id"])
		if err != nil {
			if err == ErrNotFound {
				w.WriteHeader(404)
				return
			}
			log.Println(err)
			w.WriteHeader(500)
			return
		}
		c.Map(thing)
	}

	singletonPath := prefix + "/:" + resource + "_id"
	r.Get(singletonPath, lookup, func(c martini.Context, r render.Render) {
		r.JSON(200, c.Get(resourcePtr).Interface())
	})

	r.Get(prefix, func(r render.Render) {
		list, err := repo.List()
		if err != nil {
			log.Println(err)
			r.JSON(500, struct{}{})
			return
		}
		// render an empty JSON array (rather than null) if list is nil
		if list == nil {
			list = []struct{}{}
		}
		r.JSON(200, list)
	})

	if remover, ok := repo.(Remover); ok {
		r.Delete(singletonPath, lookup, func(params martini.Params, w http.ResponseWriter) {
			if err := remover.Remove(params[resource+"_id"]); err != nil {
				log.Println(err)
				w.WriteHeader(500)
				return
			}
		})
	}

	if updater, ok := repo.(Updater); ok {
		r.Post(singletonPath, func(params martini.Params, req *http.Request, r render.Render) {
			var data map[string]interface{}
			if err := json.NewDecoder(req.Body).Decode(&data); err != nil {
				r.JSON(400, struct{}{})
				return
			}
			app, err := updater.Update(params[resource+"_id"], data)
			if err != nil {
				log.Println(err)
				r.JSON(500, struct{}{})
				return
			}
			r.JSON(200, app)
		})
	}

	return lookup
}
<|endoftext|>


<commit_before>package dmr

import (
	"bytes"
	"errors"
	"fmt"

	"golang.org/x/text/encoding"
	"golang.org/x/text/encoding/charmap"
	"golang.org/x/text/encoding/unicode"
)

const (
	// n_DFragMax, see DMR AI spec. page 163.
	MaxPacketFragmentSize = 1500
)

type DataBlock struct {
	Serial uint8
	CRC    uint16
	OK     bool
	Data   []byte
	Length uint8
}

func ParseDataBlock(data []byte, dataType uint8, confirmed bool) (*DataBlock, error) {
	var (
		crc uint16
		db  = &DataBlock{
			Length: dataBlockLength(dataType, confirmed),
		}
	)

	if confirmed {
		db.Serial = data[0] >> 1
		db.CRC = uint16(data[0]&B00000001)<<8 | uint16(data[1])
		db.Data = make([]byte, db.Length)
		copy(db.Data, data[2:2+db.Length])

		for _, block := range db.Data {
			crc9(&crc, block, 8)
		}
		crc9(&crc, db.Serial, 7)
		crc9end(&crc, 8)

		// Inverting according to the inversion polynomial.
		crc = ^crc
		crc &= 0x01ff
		// Applying CRC mask, see DMR AI spec. page 143
		crc ^= 0x01ff

		// FIXME(pd0mz): this is not working
		if crc != db.CRC {
			return nil, fmt.Errorf("dmr: block CRC error (%#04x != %#04x)", crc, db.CRC)
		}
	} else {
		copy(db.Data, data[:db.Length])
	}

	db.OK = true
	return db, nil
}

func (db *DataBlock) Bytes(dataType uint8, confirmed bool) []byte {
	var (
		size = dataBlockLength(dataType, confirmed)
		data = make([]byte, size)
	)

	if confirmed {
		for _, block := range db.Data {
			crc9(&db.CRC, block, 8)
		}
		crc9(&db.CRC, db.Serial, 7)
		crc9end(&db.CRC, 8)

		// Inverting according to the inversion polynomial.
		db.CRC = ^db.CRC
		db.CRC &= 0x01ff
		// Applying CRC mask, see DMR AI spec. page 143
		db.CRC ^= 0x01ff

		data[0] = (db.Serial << 1) | (uint8(db.CRC>>8) & 0x01)
		data[1] = uint8(db.CRC)
		copy(data[2:], db.Data)
	} else {
		copy(data, db.Data)
	}

	return data
}

func dataBlockLength(dataType uint8, confirmed bool) uint8 {
	var size uint8

	switch dataType {
	case Data:
		size = 22
		break
	case Rate12Data:
		size = 10
		break
	case Rate34Data:
		size = 16
		break
	default:
		return 0
	}

	if !confirmed {
		return size + 2
	}
	return size
}

type DataFragment struct {
	Data   []byte
	Stored int
	Needed int
	CRC    uint32
}

func (df *DataFragment) DataBlocks(dataType uint8, confirm bool) ([]*DataBlock, error) {
	df.Stored = len(df.Data)
	if df.Stored > MaxPacketFragmentSize {
		df.Stored = MaxPacketFragmentSize
	}

	// See DMR AI spec. page. 73. for block sizes.
	var size = int(dataBlockLength(dataType, confirm))
	df.Needed = (df.Stored + size - 1) / size

	// Leave enough room for the 4 bytes CRC32
	if (df.Needed*size)-df.Stored < 4 {
		df.Needed++
	}

	// Calculate fragment CRC32
	for i := 0; i < (df.Needed*size)-4; i += 2 {
		if i+1 < df.Stored {
			crc32(&df.CRC, df.Data[i+1])
		} else {
			crc32(&df.CRC, 0)
		}
		if i < df.Stored {
			crc32(&df.CRC, df.Data[i])
		} else {
			crc32(&df.CRC, 0)
		}
	}
	crc32end(&df.CRC)

	var (
		blocks = make([]*DataBlock, df.Needed)
		stored int
	)
	for i := range blocks {
		block := &DataBlock{
			Serial: uint8(i % 128),
			Length: dataBlockLength(dataType, confirm),
		}
		block.Data = make([]byte, block.Length)

		store := int(block.Length)
		if df.Stored-stored < store {
			store = df.Stored - store
		}
		copy(block.Data, df.Data[stored:stored+store])
		stored += store

		if i == (df.Needed - 1) {
			block.Data[block.Length-1] = uint8(df.CRC >> 24)
			block.Data[block.Length-2] = uint8(df.CRC >> 16)
			block.Data[block.Length-3] = uint8(df.CRC >> 8)
			block.Data[block.Length-4] = uint8(df.CRC)
		}

		// Calculate block CRC9
		block.CRC = 0
		for _, b := range block.Data {
			crc9(&block.CRC, b, 8)
		}
		crc9(&block.CRC, block.Serial, 7)
		crc9end(&block.CRC, 8)

		// Inverting according to the inversion polynomial
		block.CRC = ^block.CRC
		block.CRC &= 0x01ff
		// Applying CRC mask, see DMR AI spec. page 143
		block.CRC ^= 0x01ff

		blocks[i] = block
	}

	return blocks, nil
}

func CombineDataBlocks(blocks []*DataBlock) (*DataFragment, error) {
	if blocks == nil || len(blocks) == 0 {
		return nil, errors.New("dmr: no data blocks to combine")
	}

	f := &DataFragment{
		Data: make([]byte, MaxPacketFragmentSize),
	}
	for i, block := range blocks {
		if block.Length == 0 {
			continue
		}
		if i < (len(blocks) - 1) {
			if f.Stored+int(block.Length) < len(f.Data) {
				copy(f.Data[f.Stored:], block.Data[:block.Length])
				f.Stored += int(block.Length)
			}
		} else {
			if f.Stored+int(block.Length)-4 < len(f.Data) {
				copy(f.Data[f.Stored:], block.Data[:block.Length])
				f.Stored += int(block.Length)
			}
			f.CRC = 0
			f.CRC |= uint32(block.Data[block.Length-4])
			f.CRC |= uint32(block.Data[block.Length-3]) << 8
			f.CRC |= uint32(block.Data[block.Length-2]) << 16
			f.CRC |= uint32(block.Data[block.Length-1]) << 24
		}
	}

	var crc uint32
	for i := 0; i < f.Stored-4; i += 2 {
		crc32(&crc, f.Data[i+1])
		crc32(&crc, f.Data[i])
	}
	crc32end(&crc)

	if crc != f.CRC {
		return nil, fmt.Errorf("dmr: fragment CRC error (%#08x != %#08x)", crc, f.CRC)
	}
	return f, nil
}

var encodingMap map[uint8]encoding.Encoding

func BuildMessageData(msg string, ddFormat uint8, nullTerminated bool) ([]byte, error) {
	if e, ok := encodingMap[ddFormat]; ok {
		enc := e.NewEncoder()
		data, err := enc.Bytes([]byte(msg))
		if err != nil {
			return nil, err
		}
		if nullTerminated {
			data = append(data, []byte{0x00, 0x00}...)
		}
		return data, nil
	}
	return nil, fmt.Errorf("dmr: encoding %s text is not supported", DDFormatName[ddFormat])
}

func ParseMessageData(data []byte, ddFormat uint8, nullTerminated bool) (string, error) {
	if e, ok := encodingMap[ddFormat]; ok {
		dec := e.NewDecoder()
		str, err := dec.Bytes(data)
		if err != nil {
			return "", err
		}
		if nullTerminated {
			if idx := bytes.IndexByte(str, 0x00); idx >= 0 {
				str = str[:idx]
			}
		}
		return string(str), nil
	}
	return "", fmt.Errorf("dmr: decoding %s text is not supported", DDFormatName[ddFormat])
}

func init() {
	encodingMap = map[uint8]encoding.Encoding{
		DDFormatBinary:         binaryEncoding{},
		DDFormat8BitISO8859_2:  charmap.ISO8859_2,
		DDFormat8BitISO8859_3:  charmap.ISO8859_3,
		DDFormat8BitISO8859_4:  charmap.ISO8859_4,
		DDFormat8BitISO8859_5:  charmap.ISO8859_5,
		DDFormat8BitISO8859_6:  charmap.ISO8859_6,
		DDFormat8BitISO8859_7:  charmap.ISO8859_7,
		DDFormat8BitISO8859_8:  charmap.ISO8859_8,
		DDFormat8BitISO8859_10: charmap.ISO8859_10,
		DDFormat8BitISO8859_13: charmap.ISO8859_13,
		DDFormat8BitISO8859_14: charmap.ISO8859_14,
		DDFormat8BitISO8859_15: charmap.ISO8859_15,
		DDFormat8BitISO8859_16: charmap.ISO8859_16,
		DDFormatUTF8:           unicode.UTF8,
		DDFormatUTF16:          unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM),
		DDFormatUTF16BE:        unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM),
		DDFormatUTF16LE:        unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM),
	}
}
<commit_msg>Fix ParseDataBlock of unconfirmed data<commit_after>package dmr

import (
	"bytes"
	"errors"
	"fmt"

	"golang.org/x/text/encoding"
	"golang.org/x/text/encoding/charmap"
	"golang.org/x/text/encoding/unicode"
)

const (
	// n_DFragMax, see DMR AI spec. page 163.
	MaxPacketFragmentSize = 1500
)

type DataBlock struct {
	Serial uint8
	CRC    uint16
	OK     bool
	Data   []byte
	Length uint8
}

func ParseDataBlock(data []byte, dataType uint8, confirmed bool) (*DataBlock, error) {
	var (
		crc uint16
		db  = &DataBlock{
			Length: dataBlockLength(dataType, confirmed),
		}
	)

	if confirmed {
		db.Serial = data[0] >> 1
		db.CRC = uint16(data[0]&B00000001)<<8 | uint16(data[1])
		db.Data = make([]byte, db.Length)
		copy(db.Data, data[2:2+db.Length])

		for _, block := range db.Data {
			crc9(&crc, block, 8)
		}
		crc9(&crc, db.Serial, 7)
		crc9end(&crc, 8)

		// Inverting according to the inversion polynomial.
		crc = ^crc
		crc &= 0x01ff
		// Applying CRC mask, see DMR AI spec. page 143
		crc ^= 0x01ff

		// FIXME(pd0mz): this is not working
		if crc != db.CRC {
			return nil, fmt.Errorf("dmr: block CRC error (%#04x != %#04x)", crc, db.CRC)
		}
	} else {
		db.Data = make([]byte, db.Length)
		copy(db.Data, data[:db.Length])
	}

	db.OK = true
	return db, nil
}

func (db *DataBlock) Bytes(dataType uint8, confirmed bool) []byte {
	var (
		size = dataBlockLength(dataType, confirmed)
		data = make([]byte, size)
	)

	if confirmed {
		for _, block := range db.Data {
			crc9(&db.CRC, block, 8)
		}
		crc9(&db.CRC, db.Serial, 7)
		crc9end(&db.CRC, 8)

		// Inverting according to the inversion polynomial.
		db.CRC = ^db.CRC
		db.CRC &= 0x01ff
		// Applying CRC mask, see DMR AI spec. page 143
		db.CRC ^= 0x01ff

		data[0] = (db.Serial << 1) | (uint8(db.CRC>>8) & 0x01)
		data[1] = uint8(db.CRC)
		copy(data[2:], db.Data)
	} else {
		copy(data, db.Data)
	}

	return data
}

func dataBlockLength(dataType uint8, confirmed bool) uint8 {
	var size uint8

	switch dataType {
	case Data:
		size = 22
		break
	case Rate12Data:
		size = 10
		break
	case Rate34Data:
		size = 16
		break
	default:
		return 0
	}

	if !confirmed {
		return size + 2
	}
	return size
}

type DataFragment struct {
	Data   []byte
	Stored int
	Needed int
	CRC    uint32
}

func (df *DataFragment) DataBlocks(dataType uint8, confirm bool) ([]*DataBlock, error) {
	df.Stored = len(df.Data)
	if df.Stored > MaxPacketFragmentSize {
		df.Stored = MaxPacketFragmentSize
	}

	// See DMR AI spec. page. 73. for block sizes.
	var size = int(dataBlockLength(dataType, confirm))
	df.Needed = (df.Stored + size - 1) / size

	// Leave enough room for the 4 bytes CRC32
	if (df.Needed*size)-df.Stored < 4 {
		df.Needed++
	}

	// Calculate fragment CRC32
	for i := 0; i < (df.Needed*size)-4; i += 2 {
		if i+1 < df.Stored {
			crc32(&df.CRC, df.Data[i+1])
		} else {
			crc32(&df.CRC, 0)
		}
		if i < df.Stored {
			crc32(&df.CRC, df.Data[i])
		} else {
			crc32(&df.CRC, 0)
		}
	}
	crc32end(&df.CRC)

	var (
		blocks = make([]*DataBlock, df.Needed)
		stored int
	)
	for i := range blocks {
		block := &DataBlock{
			Serial: uint8(i % 128),
			Length: dataBlockLength(dataType, confirm),
		}
		block.Data = make([]byte, block.Length)

		store := int(block.Length)
		if df.Stored-stored < store {
			store = df.Stored - store
		}
		copy(block.Data, df.Data[stored:stored+store])
		stored += store

		if i == (df.Needed - 1) {
			block.Data[block.Length-1] = uint8(df.CRC >> 24)
			block.Data[block.Length-2] = uint8(df.CRC >> 16)
			block.Data[block.Length-3] = uint8(df.CRC >> 8)
			block.Data[block.Length-4] = uint8(df.CRC)
		}

		// Calculate block CRC9
		block.CRC = 0
		for _, b := range block.Data {
			crc9(&block.CRC, b, 8)
		}
		crc9(&block.CRC, block.Serial, 7)
		crc9end(&block.CRC, 8)

		// Inverting according to the inversion polynomial
		block.CRC = ^block.CRC
		block.CRC &= 0x01ff
		// Applying CRC mask, see DMR AI spec. page 143
		block.CRC ^= 0x01ff

		blocks[i] = block
	}

	return blocks, nil
}

func CombineDataBlocks(blocks []*DataBlock) (*DataFragment, error) {
	if blocks == nil || len(blocks) == 0 {
		return nil, errors.New("dmr: no data blocks to combine")
	}

	f := &DataFragment{
		Data: make([]byte, MaxPacketFragmentSize),
	}
	for i, block := range blocks {
		if block.Length == 0 {
			continue
		}
		if i < (len(blocks) - 1) {
			if f.Stored+int(block.Length) < len(f.Data) {
				copy(f.Data[f.Stored:], block.Data[:block.Length])
				f.Stored += int(block.Length)
			}
		} else {
			if f.Stored+int(block.Length)-4 < len(f.Data) {
				copy(f.Data[f.Stored:], block.Data[:block.Length])
				f.Stored += int(block.Length)
			}
			f.CRC = 0
			f.CRC |= uint32(block.Data[block.Length-4])
			f.CRC |= uint32(block.Data[block.Length-3]) << 8
			f.CRC |= uint32(block.Data[block.Length-2]) << 16
			f.CRC |= uint32(block.Data[block.Length-1]) << 24
		}
	}

	var crc uint32
	for i := 0; i < f.Stored-4; i += 2 {
		crc32(&crc, f.Data[i+1])
		crc32(&crc, f.Data[i])
	}
	crc32end(&crc)

	if crc != f.CRC {
		return nil, fmt.Errorf("dmr: fragment CRC error (%#08x != %#08x)", crc, f.CRC)
	}
	return f, nil
}

var encodingMap map[uint8]encoding.Encoding

func BuildMessageData(msg string, ddFormat uint8, nullTerminated bool) ([]byte, error) {
	if e, ok := encodingMap[ddFormat]; ok {
		enc := e.NewEncoder()
		data, err := enc.Bytes([]byte(msg))
		if err != nil {
			return nil, err
		}
		if nullTerminated {
			data = append(data, []byte{0x00, 0x00}...)
		}
		return data, nil
	}
	return nil, fmt.Errorf("dmr: encoding %s text is not supported", DDFormatName[ddFormat])
}

func ParseMessageData(data []byte, ddFormat uint8, nullTerminated bool) (string, error) {
	if e, ok := encodingMap[ddFormat]; ok {
		dec := e.NewDecoder()
		str, err := dec.Bytes(data)
		if err != nil {
			return "", err
		}
		if nullTerminated {
			if idx := bytes.IndexByte(str, 0x00); idx >= 0 {
				str = str[:idx]
			}
		}
		return string(str), nil
	}
	return "", fmt.Errorf("dmr: decoding %s text is not supported", DDFormatName[ddFormat])
}

func init() {
	encodingMap = map[uint8]encoding.Encoding{
		DDFormatBinary:         binaryEncoding{},
		DDFormat8BitISO8859_2:  charmap.ISO8859_2,
		DDFormat8BitISO8859_3:  charmap.ISO8859_3,
		DDFormat8BitISO8859_4:  charmap.ISO8859_4,
		DDFormat8BitISO8859_5:  charmap.ISO8859_5,
		DDFormat8BitISO8859_6:  charmap.ISO8859_6,
		DDFormat8BitISO8859_7:  charmap.ISO8859_7,
		DDFormat8BitISO8859_8:  charmap.ISO8859_8,
		DDFormat8BitISO8859_10: charmap.ISO8859_10,
		DDFormat8BitISO8859_13: charmap.ISO8859_13,
		DDFormat8BitISO8859_14: charmap.ISO8859_14,
		DDFormat8BitISO8859_15: charmap.ISO8859_15,
		DDFormat8BitISO8859_16: charmap.ISO8859_16,
		DDFormatUTF8:           unicode.UTF8,
		DDFormatUTF16:          unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM),
		DDFormatUTF16BE:        unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM),
		DDFormatUTF16LE:        unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM),
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"os"

	"github.com/redhat-developer/henge/pkg/transformers"
)

// Loop over a array of filepaths and check if it exists
// if it exists check if it is not a directory.
func ifFileExists(files []string) error {
	for _, filename := range files {
		fileInfo, err := os.Stat(filename)
		if err != nil {
			return fmt.Errorf("main: file %q not found", filename)
		}
		if fileInfo.IsDir() {
			return fmt.Errorf("main: %q is a directory", filename)
		}
	}
	return nil
}

func main() {
	provider := flag.String("target", "", "Target platform (openshift or kubernetes)")
	interactive := flag.Bool("interactive", false, "Ask questions about missing arguments.")

	flag.Parse()

	if *provider == "" {
		fmt.Fprintln(os.Stderr, "You must provide target platform using -platform argument.")
		os.Exit(1)
	}

	files := flag.Args()
	err := ifFileExists(files)
	if err != nil {
		fmt.Fprintln(os.Stderr, err.Error())
		os.Exit(1)
	}

	err = transformers.Transform(*provider, *interactive, flag.Args()[0:]...)
	if err != nil {
		fmt.Fprintln(os.Stderr, err.Error())
		os.Exit(1)
	}
}
<commit_msg>fix error message for required -target argument<commit_after>package main

import (
	"flag"
	"fmt"
	"os"

	"github.com/redhat-developer/henge/pkg/transformers"
)

// Loop over a array of filepaths and check if it exists
// if it exists check if it is not a directory.
func ifFileExists(files []string) error {
	for _, filename := range files {
		fileInfo, err := os.Stat(filename)
		if err != nil {
			return fmt.Errorf("main: file %q not found", filename)
		}
		if fileInfo.IsDir() {
			return fmt.Errorf("main: %q is a directory", filename)
		}
	}
	return nil
}

func main() {
	provider := flag.String("target", "", "Target platform (openshift or kubernetes)")
	interactive := flag.Bool("interactive", false, "Ask questions about missing arguments.")

	flag.Parse()

	if *provider == "" {
		fmt.Fprintln(os.Stderr, "You must provide target platform using -target argument.")
		os.Exit(1)
	}

	files := flag.Args()
	err := ifFileExists(files)
	if err != nil {
		fmt.Fprintln(os.Stderr, err.Error())
		os.Exit(1)
	}

	err = transformers.Transform(*provider, *interactive, flag.Args()[0:]...)
	if err != nil {
		fmt.Fprintln(os.Stderr, err.Error())
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package zkteco

import (
	"fmt"
	"regexp"
	"strconv"
	"time"
)

// GetStartDate returns start time of attendance by given a time range.
func GetStartDate(timeRange string) (t time.Time) {
	var (
		loc              *time.Location
		year, month, day int64
		err              error
		datePattern      = `^(\d{4}).(\d*).(\d*)`
	)

	re := regexp.MustCompile(datePattern)
	arr := re.FindStringSubmatch(timeRange)
	if year, err = strconv.ParseInt(arr[1], 10, 32); err != nil {
		return
	}

	if month, err = strconv.ParseInt(arr[2], 10, 32); err != nil {
		return
	}

	if day, err = strconv.ParseInt(arr[3], 10, 32); err != nil {
		return
	}
	loc, err = time.LoadLocation("Local")

	t = time.Date(int(year), time.Month(month), int(day), 0, 0, 0, 0, loc)
	return t

}

// GetField adds days and returns according to i.
func GetField(t time.Time, i int) (field string) {
	n := t.AddDate(0, 0, i)
	a, b, c := n.Date()
	field = fmt.Sprintf("%v-%v-%v", a, int(b), c)
	return field
}
<commit_msg>Check error<commit_after>package zkteco

import (
	"fmt"
	"regexp"
	"strconv"
	"time"
)

// GetStartDate returns start time of attendance by given a time range.
func GetStartDate(timeRange string) (t time.Time) {
	var (
		loc              *time.Location
		year, month, day int64
		err              error
		datePattern      = `^(\d{4}).(\d*).(\d*)`
	)

	re := regexp.MustCompile(datePattern)
	arr := re.FindStringSubmatch(timeRange)
	if year, err = strconv.ParseInt(arr[1], 10, 32); err != nil {
		return
	}

	if month, err = strconv.ParseInt(arr[2], 10, 32); err != nil {
		return
	}

	if day, err = strconv.ParseInt(arr[3], 10, 32); err != nil {
		return
	}

	if loc, err = time.LoadLocation("Local"); err != nil {
		return
	}

	t = time.Date(int(year), time.Month(month), int(day), 0, 0, 0, 0, loc)
	return t

}

// GetField adds days and returns according to i.
func GetField(t time.Time, i int) (field string) {
	n := t.AddDate(0, 0, i)
	a, b, c := n.Date()
	field = fmt.Sprintf("%v-%v-%v", a, int(b), c)
	return field
}
<|endoftext|>


<commit_before>package main

import (
  "fmt"
  "os"
  "os/exec"
  "log"
  "io/ioutil"

  "gopkg.in/yaml.v2"
)

const file_name string = ".hydra.yml"

type Config struct {
  Services []struct {
    Git string
    Config struct {
      Src string
      Dest string
    }
  }
}

func main() {
  d, e := ioutil.ReadFile(file_name)
  if e != nil {
    log.Fatal(e)
    os.Exit(1)
  }

  c := Config{}
  err := yaml.Unmarshal(d, &c)
  if err != nil {
    log.Fatalf("error: %v", err)
    os.Exit(1)
  }

  exec.Command("mkdir", ".hydra").Output()

  for i := 0; i < len(c.Services); i += 1 {
    base := ".hydra/" + c.Services[i].Git
    exec.Command("mkdir", base).Output()
    u := base + "/" + c.Services[i].Config.Dest
    fmt.Printf("%s\n", u)
    _, err := exec.Command("touch", u).Output()
    if err != nil {
      log.Fatal(err)
    }
  }
}
<commit_msg>Fixed so dirs are copied to .hydra<commit_after>package main

import (
  "fmt"
  "path"
  "os"
  "os/exec"
  "log"
  "io/ioutil"

  "flag"

  "gopkg.in/yaml.v2"
)

const file_name string = ".hydra.yml"

type Config struct {
  Services []struct {
    Path string
    Start string
    Config struct {
      Src string
      Dest string
    }
  }
}

func main() {
  var clean = flag.Bool("clean", false, "If we want a clean run")
  flag.Parse()

  d, e := ioutil.ReadFile(file_name)
  if e != nil {
    log.Fatal(e)
    os.Exit(1)
  }

  c := Config{}
  err := yaml.Unmarshal(d, &c)
  if err != nil {
    log.Fatalf("error: %v", err)
    os.Exit(1)
  }

  if *clean {
    _, e := exec.Command("rm", "-rf", ".hydra").Output()
    if e != nil {
      log.Fatal(e)
    }
  }

  exec.Command("mkdir", ".hydra").Output()

  for i := 0; i < len(c.Services); i += 1 {
    p := c.Services[i].Path
    _, name := path.Split(p)
    base := path.Join(".hydra/", name)
    if _, err := os.Stat(base); os.IsNotExist(err) {
      fmt.Println("Creating", name)
      _, e := exec.Command("cp", "-R", p, base).Output()
      if e != nil {
        log.Fatal(e)
      }

      u := path.Join(base, c.Services[i].Config.Dest)
      fmt.Printf("%s\n", u)
      _, err := exec.Command("touch", u).Output()
      if err != nil {
        log.Fatal(err)
      }
    }
  }
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"strconv"
	"strings"
)

var (
	ErrDimensionsSeparator      = errors.New("Dimensions separator not found")
	ErrDimensionsNonNegative    = errors.New("x and y must be non-negative")
	ErrBothDimensionEqualToZero = errors.New("At least x and y must be greater than zero")
	ErrCropDimensionEqualToZero = errors.New("Both x and y must be greater than zero")
	ErrNotCropFormat            = errors.New("Not in crop format")
	ErrInvalidCropDimensions    = errors.New("Invalid crop format dimensions")
	ErrNonEmptyParameterQueue   = errors.New("Can't process all parameters")
)

const (
	defaultInputExtension = "jpg"
	RAW                   = "raw"
)

type Image struct {
	Id        string `json:"id"`
	Extension string `json:"extension"`
}

type Crop struct {
	X      int `json:"x"`
	Y      int `json:"y"`
	Width  int `json:"width"`
	Height int `json:"height"`
}

type Transform struct {
	Image  `json:"image"`
	Raw    bool   `json:"original"`
	Width  int    `json:"width"`
	Height int    `json:"height"`
	Crop   Crop   `json:"crop"`
	Output string `json:"output"`
}

func (i *Image) name() (name string, fullname string) {
	fullname = i.Id

	if i.Extension != "" {
		fullname += "." + i.Extension
	}

	last := strings.LastIndex(fullname, "/")

	if last == -1 {
		return fullname, fullname
	}

	return fullname[last+1 : len(fullname)], fullname
}

func Decode(path string) (transform Transform, err error, errs []error) {
	t := Transform{}

	paramsSubstringStart := getParamsSubstringStart(path)

	imgId := ""
	paramsString := ""
	output := ""

	if paramsSubstringStart == -1 {
		imgId, output = getFilePathParts(path)
		t.Image.Id = unescapeRawUrlParts(imgId)

		extension := output

		if extension == "" {
			extension = defaultInputExtension
		}

		t.Image.Extension = unescapeRawUrlParts(extension)
		t.Output = unescapeRawUrlParts(output)

		return t, err, errs
	}

	imgId = path[0 : paramsSubstringStart-1]
	paramsString, output = getFilePathParts(path[paramsSubstringStart-1 : len(path)])
	t.Image.Id = unescapeRawUrlParts(imgId)

	err, errs = extractParams(paramsString, output, &t)
	t.Output = unescapeRawUrlParts(output)

	return t, err, errs
}

func Encode(transform Transform) (url string) {
	image := transform.Image
	url = escapeRawUrlParts(image.Id)

	inputExtension := image.Extension

	if inputExtension == "" {
		inputExtension = defaultInputExtension
	}

	if transform.Raw {
		url += "_" + RAW + "." + inputExtension

		return url
	}

	url += encodeParam(encodeCrop(transform.Crop))

	url += encodeParam(EncodeDimension(transform))

	if transform.Output != inputExtension && (inputExtension != defaultInputExtension || transform.Output != "") {
		url += encodeParam(escapeRawUrlParts(inputExtension))
	}

	if transform.Output != "" {
		url += "." + escapeRawUrlParts(transform.Output)
	}

	return url
}

func escapeRawUrlParts(raw string) string {
	return strings.Replace(raw, "_", "__", -1)
}

func unescapeRawUrlParts(raw string) string {
	return strings.Replace(raw, "__", "_", -1)
}

func encodeCrop(c Crop) (crop string) {
	if c.Width != 0 && c.Height != 0 {
		crop = fmt.Sprintf("%dx%d:%dx%d", c.X, c.Y, c.Width, c.Height)
	}

	return crop
}

func EncodeDimension(transform Transform) (dim string) {
	if transform.Width == 0 && transform.Height == 0 {
		return dim
	}

	if transform.Width > 0 {
		dim += fmt.Sprintf("%d", transform.Width)
	}

	dim += "x"

	if transform.Height > 0 {
		dim += fmt.Sprintf("%d", transform.Height)
	}

	return dim
}

func encodeParam(param string) string {
	if param != "" {
		param = "_" + param
	}

	return param
}

func getParamsSubstringStart(sp string) int {
	next_ := -1
	pivot := 0

	for {
		next_ = strings.Index(sp[pivot:], "_")

		if next_ == -1 {
			break
		}

		pivot += next_ + 1

		if sp[pivot:][:1] != "_" && (pivot < 2 || sp[pivot-2:][:1] != "_") {
			return pivot
		}
	}

	return -1
}

func getDimensions(c string) (x int, y int, errs []error) {
	var err error
	div := strings.Index(c, "x")

	if div == -1 {
		errs = append(errs, ErrDimensionsSeparator)
		return x, y, errs
	}

	current := c[0:div]

	if current != "" {
		x, err = strconv.Atoi(current)
	}

	if err != nil {
		errs = append(errs, err)
		return x, y, errs
	}

	current = c[div+1 : len(c)]

	if current != "" {
		y, err = strconv.Atoi(current)
	}

	if err != nil {
		errs = append(errs, err)
		return x, y, errs
	}

	if x < 0 || y < 0 {
		errs = append(errs, ErrDimensionsNonNegative)
	}

	if x == 0 && y == 0 {
		errs = append(errs, ErrBothDimensionEqualToZero)
	}

	return x, y, errs
}

func getCropDimensions(c string) (x int, y int, errs []error) {
	x, y, errs = getDimensions(c)

	if x == 0 || y == 0 {
		errs = append(errs, ErrCropDimensionEqualToZero)
	}

	return x, y, errs
}

func extractCrop(c string) (crop Crop, errs []error) {
	dot := strings.Index(c, ":")

	if dot == -1 {
		errs = append(errs, ErrNotCropFormat)
		return crop, errs
	}

	x, y, errs1 := getDimensions(c[0:dot])
	width, height, errs2 := getCropDimensions(c[dot+1 : len(c)])

	errs = append(errs, errs1...)
	errs = append(errs, errs2...)

	if len(errs1) != 0 || len(errs2) != 0 {
		errs = append(errs, ErrInvalidCropDimensions)
	}

	crop = Crop{
		X:      x,
		Y:      y,
		Width:  width,
		Height: height,
	}

	return crop, errs
}

func extractParams(part string, output string, t *Transform) (err error, errs []error) {
	params := strings.Split(part, "_")

	for i := range params {
		params[i] = unescapeRawUrlParts(params[i])
	}

	pos := 1

	if len(params) == 2 && params[pos] == RAW {
		t.Raw = true
		t.Image.Extension = output
		return err, errs
	}

	crop, errsCrop := extractCrop(params[pos])

	if len(errsCrop) == 0 {
		t.Crop = crop
		pos += 1
	}

	errs = append(errs, errsCrop...)

	if pos < len(params) {
		width, height, errsResize := getDimensions(params[pos])

		if len(errsResize) == 0 {
			t.Width, t.Height = width, height
			pos += 1
		}

		errs = append(errs, errsResize...)
	}

	extension := t.Output

	if pos != len(params) && params[pos] != t.Output {
		extension = params[pos]
		pos += 1
	}

	if extension == "" {
		extension = defaultInputExtension
	}

	t.Image.Extension = extension

	if pos != len(params) {
		err = ErrNonEmptyParameterQueue
		errs = append(errs, err)
	}

	return err, errs
}

func getFilePathParts(part string) (string, string) {
	last := strings.LastIndex(part, ".")

	if last == -1 {
		return part[0:len(part)], ""
	}

	return part[0:last], part[last+1 : len(part)]
}
<commit_msg>Unescaping extension.<commit_after>package main

import (
	"errors"
	"fmt"
	"strconv"
	"strings"
)

var (
	ErrDimensionsSeparator      = errors.New("Dimensions separator not found")
	ErrDimensionsNonNegative    = errors.New("x and y must be non-negative")
	ErrBothDimensionEqualToZero = errors.New("At least x and y must be greater than zero")
	ErrCropDimensionEqualToZero = errors.New("Both x and y must be greater than zero")
	ErrNotCropFormat            = errors.New("Not in crop format")
	ErrInvalidCropDimensions    = errors.New("Invalid crop format dimensions")
	ErrNonEmptyParameterQueue   = errors.New("Can't process all parameters")
)

const (
	defaultInputExtension = "jpg"
	RAW                   = "raw"
)

type Image struct {
	Id        string `json:"id"`
	Extension string `json:"extension"`
}

type Crop struct {
	X      int `json:"x"`
	Y      int `json:"y"`
	Width  int `json:"width"`
	Height int `json:"height"`
}

type Transform struct {
	Image  `json:"image"`
	Raw    bool   `json:"original"`
	Width  int    `json:"width"`
	Height int    `json:"height"`
	Crop   Crop   `json:"crop"`
	Output string `json:"output"`
}

func (i *Image) name() (name string, fullname string) {
	fullname = i.Id

	if i.Extension != "" {
		fullname += "." + i.Extension
	}

	last := strings.LastIndex(fullname, "/")

	if last == -1 {
		return fullname, fullname
	}

	return fullname[last+1 : len(fullname)], fullname
}

func Decode(path string) (transform Transform, err error, errs []error) {
	t := Transform{}

	paramsSubstringStart := getParamsSubstringStart(path)

	imgId := ""
	paramsString := ""
	output := ""

	if paramsSubstringStart == -1 {
		imgId, output = getFilePathParts(path)
		t.Image.Id = unescapeRawUrlParts(imgId)

		extension := output

		if extension == "" {
			extension = defaultInputExtension
		}

		t.Image.Extension = unescapeRawUrlParts(extension)
		t.Output = unescapeRawUrlParts(output)

		return t, err, errs
	}

	imgId = path[0 : paramsSubstringStart-1]
	paramsString, output = getFilePathParts(path[paramsSubstringStart-1 : len(path)])
	t.Image.Id = unescapeRawUrlParts(imgId)

	err, errs = extractParams(paramsString, output, &t)
	t.Output = unescapeRawUrlParts(output)

	return t, err, errs
}

func Encode(transform Transform) (url string) {
	image := transform.Image
	url = escapeRawUrlParts(image.Id)

	inputExtension := image.Extension

	if inputExtension == "" {
		inputExtension = defaultInputExtension
	}

	if transform.Raw {
		url += "_" + RAW + "." + inputExtension

		return url
	}

	url += encodeParam(encodeCrop(transform.Crop))

	url += encodeParam(EncodeDimension(transform))

	if transform.Output != inputExtension && (inputExtension != defaultInputExtension || transform.Output != "") {
		url += encodeParam(escapeRawUrlParts(inputExtension))
	}

	if transform.Output != "" {
		url += "." + escapeRawUrlParts(transform.Output)
	}

	return url
}

func escapeRawUrlParts(raw string) string {
	return strings.Replace(raw, "_", "__", -1)
}

func unescapeRawUrlParts(raw string) string {
	return strings.Replace(raw, "__", "_", -1)
}

func encodeCrop(c Crop) (crop string) {
	if c.Width != 0 && c.Height != 0 {
		crop = fmt.Sprintf("%dx%d:%dx%d", c.X, c.Y, c.Width, c.Height)
	}

	return crop
}

func EncodeDimension(transform Transform) (dim string) {
	if transform.Width == 0 && transform.Height == 0 {
		return dim
	}

	if transform.Width > 0 {
		dim += fmt.Sprintf("%d", transform.Width)
	}

	dim += "x"

	if transform.Height > 0 {
		dim += fmt.Sprintf("%d", transform.Height)
	}

	return dim
}

func encodeParam(param string) string {
	if param != "" {
		param = "_" + param
	}

	return param
}

func getParamsSubstringStart(sp string) int {
	next_ := -1
	pivot := 0

	for {
		next_ = strings.Index(sp[pivot:], "_")

		if next_ == -1 {
			break
		}

		pivot += next_ + 1

		if sp[pivot:][:1] != "_" && (pivot < 2 || sp[pivot-2:][:1] != "_") {
			return pivot
		}
	}

	return -1
}

func getDimensions(c string) (x int, y int, errs []error) {
	var err error
	div := strings.Index(c, "x")

	if div == -1 {
		errs = append(errs, ErrDimensionsSeparator)
		return x, y, errs
	}

	current := c[0:div]

	if current != "" {
		x, err = strconv.Atoi(current)
	}

	if err != nil {
		errs = append(errs, err)
		return x, y, errs
	}

	current = c[div+1 : len(c)]

	if current != "" {
		y, err = strconv.Atoi(current)
	}

	if err != nil {
		errs = append(errs, err)
		return x, y, errs
	}

	if x < 0 || y < 0 {
		errs = append(errs, ErrDimensionsNonNegative)
	}

	if x == 0 && y == 0 {
		errs = append(errs, ErrBothDimensionEqualToZero)
	}

	return x, y, errs
}

func getCropDimensions(c string) (x int, y int, errs []error) {
	x, y, errs = getDimensions(c)

	if x == 0 || y == 0 {
		errs = append(errs, ErrCropDimensionEqualToZero)
	}

	return x, y, errs
}

func extractCrop(c string) (crop Crop, errs []error) {
	dot := strings.Index(c, ":")

	if dot == -1 {
		errs = append(errs, ErrNotCropFormat)
		return crop, errs
	}

	x, y, errs1 := getDimensions(c[0:dot])
	width, height, errs2 := getCropDimensions(c[dot+1 : len(c)])

	errs = append(errs, errs1...)
	errs = append(errs, errs2...)

	if len(errs1) != 0 || len(errs2) != 0 {
		errs = append(errs, ErrInvalidCropDimensions)
	}

	crop = Crop{
		X:      x,
		Y:      y,
		Width:  width,
		Height: height,
	}

	return crop, errs
}

func extractParams(part string, output string, t *Transform) (err error, errs []error) {
	params := strings.Split(part, "_")

	for i := range params {
		params[i] = unescapeRawUrlParts(params[i])
	}

	pos := 1

	if len(params) == 2 && params[pos] == RAW {
		t.Raw = true
		t.Image.Extension = output
		return err, errs
	}

	crop, errsCrop := extractCrop(params[pos])

	if len(errsCrop) == 0 {
		t.Crop = crop
		pos += 1
	}

	errs = append(errs, errsCrop...)

	if pos < len(params) {
		width, height, errsResize := getDimensions(params[pos])

		if len(errsResize) == 0 {
			t.Width, t.Height = width, height
			pos += 1
		}

		errs = append(errs, errsResize...)
	}

	extension := t.Output

	if pos != len(params) && params[pos] != t.Output {
		extension = params[pos]
		pos += 1
	}

	if extension == "" {
		extension = defaultInputExtension
	}

	t.Image.Extension = unescapeRawUrlParts(extension)

	if pos != len(params) {
		err = ErrNonEmptyParameterQueue
		errs = append(errs, err)
	}

	return err, errs
}

func getFilePathParts(part string) (string, string) {
	last := strings.LastIndex(part, ".")

	if last == -1 {
		return part[0:len(part)], ""
	}

	return part[0:last], part[last+1 : len(part)]
}
<|endoftext|>


<commit_before>/*
   Copyright The containerd Authors.

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

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

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

package containerd

import (
	"context"
	"fmt"
	"strings"
	"sync/atomic"

	"github.com/containerd/containerd/content"
	"github.com/containerd/containerd/diff"
	"github.com/containerd/containerd/errdefs"
	"github.com/containerd/containerd/images"
	"github.com/containerd/containerd/platforms"
	"github.com/containerd/containerd/rootfs"
	"github.com/containerd/containerd/snapshots"
	"github.com/opencontainers/go-digest"
	"github.com/opencontainers/image-spec/identity"
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/pkg/errors"
	"golang.org/x/sync/semaphore"
)

// Image describes an image used by containers
type Image interface {
	// Name of the image
	Name() string
	// Target descriptor for the image content
	Target() ocispec.Descriptor
	// Labels of the image
	Labels() map[string]string
	// Unpack unpacks the image's content into a snapshot
	Unpack(context.Context, string, ...UnpackOpt) error
	// RootFS returns the unpacked diffids that make up images rootfs.
	RootFS(ctx context.Context) ([]digest.Digest, error)
	// Size returns the total size of the image's packed resources.
	Size(ctx context.Context) (int64, error)
	// Usage returns a usage calculation for the image.
	Usage(context.Context, ...UsageOpt) (int64, error)
	// Config descriptor for the image.
	Config(ctx context.Context) (ocispec.Descriptor, error)
	// IsUnpacked returns whether or not an image is unpacked.
	IsUnpacked(context.Context, string) (bool, error)
	// ContentStore provides a content store which contains image blob data
	ContentStore() content.Store
}

type usageOptions struct {
	manifestLimit *int
	manifestOnly  bool
	snapshots     bool
}

// UsageOpt is used to configure the usage calculation
type UsageOpt func(*usageOptions) error

// WithUsageManifestLimit sets the limit to the number of manifests which will
// be walked for usage. Setting this value to 0 will require all manifests to
// be walked, returning ErrNotFound if manifests are missing.
// NOTE: By default all manifests which exist will be walked
// and any non-existent manifests and their subobjects will be ignored.
func WithUsageManifestLimit(i int) UsageOpt {
	// If 0 then don't filter any manifests
	// By default limits to current platform
	return func(o *usageOptions) error {
		o.manifestLimit = &i
		return nil
	}
}

// WithSnapshotUsage will check for referenced snapshots from the image objects
// and include the snapshot size in the total usage.
func WithSnapshotUsage() UsageOpt {
	return func(o *usageOptions) error {
		o.snapshots = true
		return nil
	}
}

// WithManifestUsage is used to get the usage for an image based on what is
// reported by the manifests rather than what exists in the content store.
// NOTE: This function is best used with the manifest limit set to get a
// consistent value, otherwise non-existent manifests will be excluded.
func WithManifestUsage() UsageOpt {
	return func(o *usageOptions) error {
		o.manifestOnly = true
		return nil
	}
}

var _ = (Image)(&image{})

// NewImage returns a client image object from the metadata image
func NewImage(client *Client, i images.Image) Image {
	return &image{
		client:   client,
		i:        i,
		platform: client.platform,
	}
}

// NewImageWithPlatform returns a client image object from the metadata image
func NewImageWithPlatform(client *Client, i images.Image, platform platforms.MatchComparer) Image {
	return &image{
		client:   client,
		i:        i,
		platform: platform,
	}
}

type image struct {
	client *Client

	i        images.Image
	platform platforms.MatchComparer
}

func (i *image) Name() string {
	return i.i.Name
}

func (i *image) Target() ocispec.Descriptor {
	return i.i.Target
}

func (i *image) Labels() map[string]string {
	return i.i.Labels
}

func (i *image) RootFS(ctx context.Context) ([]digest.Digest, error) {
	provider := i.client.ContentStore()
	return i.i.RootFS(ctx, provider, i.platform)
}

func (i *image) Size(ctx context.Context) (int64, error) {
	return i.Usage(ctx, WithUsageManifestLimit(1), WithManifestUsage())
}

func (i *image) Usage(ctx context.Context, opts ...UsageOpt) (int64, error) {
	var config usageOptions
	for _, opt := range opts {
		if err := opt(&config); err != nil {
			return 0, err
		}
	}

	var (
		provider  = i.client.ContentStore()
		handler   = images.ChildrenHandler(provider)
		size      int64
		mustExist bool
	)

	if config.manifestLimit != nil {
		handler = images.LimitManifests(handler, i.platform, *config.manifestLimit)
		mustExist = true
	}

	var wh images.HandlerFunc = func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
		var usage int64
		children, err := handler(ctx, desc)
		if err != nil {
			if !errdefs.IsNotFound(err) || mustExist {
				return nil, err
			}
			if !config.manifestOnly {
				// Do not count size of non-existent objects
				desc.Size = 0
			}
		} else if config.snapshots || !config.manifestOnly {
			info, err := provider.Info(ctx, desc.Digest)
			if err != nil {
				if !errdefs.IsNotFound(err) {
					return nil, err
				}
				if !config.manifestOnly {
					// Do not count size of non-existent objects
					desc.Size = 0
				}
			} else if info.Size > desc.Size {
				// Count actual usage, Size may be unset or -1
				desc.Size = info.Size
			}

			for k, v := range info.Labels {
				const prefix = "containerd.io/gc.ref.snapshot."
				if !strings.HasPrefix(k, prefix) {
					continue
				}

				sn := i.client.SnapshotService(k[len(prefix):])
				if sn == nil {
					continue
				}

				u, err := sn.Usage(ctx, v)
				if err != nil {
					if !errdefs.IsNotFound(err) && !errdefs.IsInvalidArgument(err) {
						return nil, err
					}
				} else {
					usage += u.Size
				}
			}
		}

		// Ignore unknown sizes. Generally unknown sizes should
		// never be set in manifests, however, the usage
		// calculation does not need to enforce this.
		if desc.Size >= 0 {
			usage += desc.Size
		}

		atomic.AddInt64(&size, usage)

		return children, nil
	}

	l := semaphore.NewWeighted(3)
	if err := images.Dispatch(ctx, wh, l, i.i.Target); err != nil {
		return 0, err
	}

	return size, nil
}

func (i *image) Config(ctx context.Context) (ocispec.Descriptor, error) {
	provider := i.client.ContentStore()
	return i.i.Config(ctx, provider, i.platform)
}

func (i *image) IsUnpacked(ctx context.Context, snapshotterName string) (bool, error) {
	sn, err := i.client.getSnapshotter(ctx, snapshotterName)
	if err != nil {
		return false, err
	}
	cs := i.client.ContentStore()

	diffs, err := i.i.RootFS(ctx, cs, i.platform)
	if err != nil {
		return false, err
	}

	chainID := identity.ChainID(diffs)
	_, err = sn.Stat(ctx, chainID.String())
	if err == nil {
		return true, nil
	} else if !errdefs.IsNotFound(err) {
		return false, err
	}

	return false, nil
}

// UnpackConfig provides configuration for the unpack of an image
type UnpackConfig struct {
	// ApplyOpts for applying a diff to a snapshotter
	ApplyOpts []diff.ApplyOpt
	// SnapshotOpts for configuring a snapshotter
	SnapshotOpts []snapshots.Opt
}

// UnpackOpt provides configuration for unpack
type UnpackOpt func(context.Context, *UnpackConfig) error

func (i *image) Unpack(ctx context.Context, snapshotterName string, opts ...UnpackOpt) error {
	ctx, done, err := i.client.WithLease(ctx)
	if err != nil {
		return err
	}
	defer done(ctx)

	var config UnpackConfig
	for _, o := range opts {
		if err := o(ctx, &config); err != nil {
			return err
		}
	}

	layers, err := i.getLayers(ctx, i.platform)
	if err != nil {
		return err
	}

	var (
		a  = i.client.DiffService()
		cs = i.client.ContentStore()

		chain    []digest.Digest
		unpacked bool
	)
	snapshotterName, err = i.client.resolveSnapshotterName(ctx, snapshotterName)
	if err != nil {
		return err
	}
	sn, err := i.client.getSnapshotter(ctx, snapshotterName)
	if err != nil {
		return err
	}
	for _, layer := range layers {
		unpacked, err = rootfs.ApplyLayerWithOpts(ctx, layer, chain, sn, a, config.SnapshotOpts, config.ApplyOpts)
		if err != nil {
			return err
		}

		if unpacked {
			// Set the uncompressed label after the uncompressed
			// digest has been verified through apply.
			cinfo := content.Info{
				Digest: layer.Blob.Digest,
				Labels: map[string]string{
					"containerd.io/uncompressed": layer.Diff.Digest.String(),
				},
			}
			if _, err := cs.Update(ctx, cinfo, "labels.containerd.io/uncompressed"); err != nil {
				return err
			}
		}

		chain = append(chain, layer.Diff.Digest)
	}

	desc, err := i.i.Config(ctx, cs, i.platform)
	if err != nil {
		return err
	}

	rootfs := identity.ChainID(chain).String()

	cinfo := content.Info{
		Digest: desc.Digest,
		Labels: map[string]string{
			fmt.Sprintf("containerd.io/gc.ref.snapshot.%s", snapshotterName): rootfs,
		},
	}

	_, err = cs.Update(ctx, cinfo, fmt.Sprintf("labels.containerd.io/gc.ref.snapshot.%s", snapshotterName))
	return err
}

func (i *image) getLayers(ctx context.Context, platform platforms.MatchComparer) ([]rootfs.Layer, error) {
	cs := i.client.ContentStore()

	manifest, err := images.Manifest(ctx, cs, i.i.Target, platform)
	if err != nil {
		return nil, err
	}

	diffIDs, err := i.i.RootFS(ctx, cs, platform)
	if err != nil {
		return nil, errors.Wrap(err, "failed to resolve rootfs")
	}
	if len(diffIDs) != len(manifest.Layers) {
		return nil, errors.Errorf("mismatched image rootfs and manifest layers")
	}
	layers := make([]rootfs.Layer, len(diffIDs))
	for i := range diffIDs {
		layers[i].Diff = ocispec.Descriptor{
			// TODO: derive media type from compressed type
			MediaType: ocispec.MediaTypeImageLayer,
			Digest:    diffIDs[i],
		}
		layers[i].Blob = manifest.Layers[i]
	}
	return layers, nil
}

func (i *image) ContentStore() content.Store {
	return i.client.ContentStore()
}
<commit_msg>Return the underlying images metadata<commit_after>/*
   Copyright The containerd Authors.

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

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

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

package containerd

import (
	"context"
	"fmt"
	"strings"
	"sync/atomic"

	"github.com/containerd/containerd/content"
	"github.com/containerd/containerd/diff"
	"github.com/containerd/containerd/errdefs"
	"github.com/containerd/containerd/images"
	"github.com/containerd/containerd/platforms"
	"github.com/containerd/containerd/rootfs"
	"github.com/containerd/containerd/snapshots"
	"github.com/opencontainers/go-digest"
	"github.com/opencontainers/image-spec/identity"
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/pkg/errors"
	"golang.org/x/sync/semaphore"
)

// Image describes an image used by containers
type Image interface {
	// Name of the image
	Name() string
	// Target descriptor for the image content
	Target() ocispec.Descriptor
	// Labels of the image
	Labels() map[string]string
	// Unpack unpacks the image's content into a snapshot
	Unpack(context.Context, string, ...UnpackOpt) error
	// RootFS returns the unpacked diffids that make up images rootfs.
	RootFS(ctx context.Context) ([]digest.Digest, error)
	// Size returns the total size of the image's packed resources.
	Size(ctx context.Context) (int64, error)
	// Usage returns a usage calculation for the image.
	Usage(context.Context, ...UsageOpt) (int64, error)
	// Config descriptor for the image.
	Config(ctx context.Context) (ocispec.Descriptor, error)
	// IsUnpacked returns whether or not an image is unpacked.
	IsUnpacked(context.Context, string) (bool, error)
	// ContentStore provides a content store which contains image blob data
	ContentStore() content.Store
	// Metadata returns the underlying image metadata
	Metadata() images.Image
}

type usageOptions struct {
	manifestLimit *int
	manifestOnly  bool
	snapshots     bool
}

// UsageOpt is used to configure the usage calculation
type UsageOpt func(*usageOptions) error

// WithUsageManifestLimit sets the limit to the number of manifests which will
// be walked for usage. Setting this value to 0 will require all manifests to
// be walked, returning ErrNotFound if manifests are missing.
// NOTE: By default all manifests which exist will be walked
// and any non-existent manifests and their subobjects will be ignored.
func WithUsageManifestLimit(i int) UsageOpt {
	// If 0 then don't filter any manifests
	// By default limits to current platform
	return func(o *usageOptions) error {
		o.manifestLimit = &i
		return nil
	}
}

// WithSnapshotUsage will check for referenced snapshots from the image objects
// and include the snapshot size in the total usage.
func WithSnapshotUsage() UsageOpt {
	return func(o *usageOptions) error {
		o.snapshots = true
		return nil
	}
}

// WithManifestUsage is used to get the usage for an image based on what is
// reported by the manifests rather than what exists in the content store.
// NOTE: This function is best used with the manifest limit set to get a
// consistent value, otherwise non-existent manifests will be excluded.
func WithManifestUsage() UsageOpt {
	return func(o *usageOptions) error {
		o.manifestOnly = true
		return nil
	}
}

var _ = (Image)(&image{})

// NewImage returns a client image object from the metadata image
func NewImage(client *Client, i images.Image) Image {
	return &image{
		client:   client,
		i:        i,
		platform: client.platform,
	}
}

// NewImageWithPlatform returns a client image object from the metadata image
func NewImageWithPlatform(client *Client, i images.Image, platform platforms.MatchComparer) Image {
	return &image{
		client:   client,
		i:        i,
		platform: platform,
	}
}

type image struct {
	client *Client

	i        images.Image
	platform platforms.MatchComparer
}

func (i *image) Metadata() images.Image {
	return i.i
}

func (i *image) Name() string {
	return i.i.Name
}

func (i *image) Target() ocispec.Descriptor {
	return i.i.Target
}

func (i *image) Labels() map[string]string {
	return i.i.Labels
}

func (i *image) RootFS(ctx context.Context) ([]digest.Digest, error) {
	provider := i.client.ContentStore()
	return i.i.RootFS(ctx, provider, i.platform)
}

func (i *image) Size(ctx context.Context) (int64, error) {
	return i.Usage(ctx, WithUsageManifestLimit(1), WithManifestUsage())
}

func (i *image) Usage(ctx context.Context, opts ...UsageOpt) (int64, error) {
	var config usageOptions
	for _, opt := range opts {
		if err := opt(&config); err != nil {
			return 0, err
		}
	}

	var (
		provider  = i.client.ContentStore()
		handler   = images.ChildrenHandler(provider)
		size      int64
		mustExist bool
	)

	if config.manifestLimit != nil {
		handler = images.LimitManifests(handler, i.platform, *config.manifestLimit)
		mustExist = true
	}

	var wh images.HandlerFunc = func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
		var usage int64
		children, err := handler(ctx, desc)
		if err != nil {
			if !errdefs.IsNotFound(err) || mustExist {
				return nil, err
			}
			if !config.manifestOnly {
				// Do not count size of non-existent objects
				desc.Size = 0
			}
		} else if config.snapshots || !config.manifestOnly {
			info, err := provider.Info(ctx, desc.Digest)
			if err != nil {
				if !errdefs.IsNotFound(err) {
					return nil, err
				}
				if !config.manifestOnly {
					// Do not count size of non-existent objects
					desc.Size = 0
				}
			} else if info.Size > desc.Size {
				// Count actual usage, Size may be unset or -1
				desc.Size = info.Size
			}

			for k, v := range info.Labels {
				const prefix = "containerd.io/gc.ref.snapshot."
				if !strings.HasPrefix(k, prefix) {
					continue
				}

				sn := i.client.SnapshotService(k[len(prefix):])
				if sn == nil {
					continue
				}

				u, err := sn.Usage(ctx, v)
				if err != nil {
					if !errdefs.IsNotFound(err) && !errdefs.IsInvalidArgument(err) {
						return nil, err
					}
				} else {
					usage += u.Size
				}
			}
		}

		// Ignore unknown sizes. Generally unknown sizes should
		// never be set in manifests, however, the usage
		// calculation does not need to enforce this.
		if desc.Size >= 0 {
			usage += desc.Size
		}

		atomic.AddInt64(&size, usage)

		return children, nil
	}

	l := semaphore.NewWeighted(3)
	if err := images.Dispatch(ctx, wh, l, i.i.Target); err != nil {
		return 0, err
	}

	return size, nil
}

func (i *image) Config(ctx context.Context) (ocispec.Descriptor, error) {
	provider := i.client.ContentStore()
	return i.i.Config(ctx, provider, i.platform)
}

func (i *image) IsUnpacked(ctx context.Context, snapshotterName string) (bool, error) {
	sn, err := i.client.getSnapshotter(ctx, snapshotterName)
	if err != nil {
		return false, err
	}
	cs := i.client.ContentStore()

	diffs, err := i.i.RootFS(ctx, cs, i.platform)
	if err != nil {
		return false, err
	}

	chainID := identity.ChainID(diffs)
	_, err = sn.Stat(ctx, chainID.String())
	if err == nil {
		return true, nil
	} else if !errdefs.IsNotFound(err) {
		return false, err
	}

	return false, nil
}

// UnpackConfig provides configuration for the unpack of an image
type UnpackConfig struct {
	// ApplyOpts for applying a diff to a snapshotter
	ApplyOpts []diff.ApplyOpt
	// SnapshotOpts for configuring a snapshotter
	SnapshotOpts []snapshots.Opt
}

// UnpackOpt provides configuration for unpack
type UnpackOpt func(context.Context, *UnpackConfig) error

func (i *image) Unpack(ctx context.Context, snapshotterName string, opts ...UnpackOpt) error {
	ctx, done, err := i.client.WithLease(ctx)
	if err != nil {
		return err
	}
	defer done(ctx)

	var config UnpackConfig
	for _, o := range opts {
		if err := o(ctx, &config); err != nil {
			return err
		}
	}

	layers, err := i.getLayers(ctx, i.platform)
	if err != nil {
		return err
	}

	var (
		a  = i.client.DiffService()
		cs = i.client.ContentStore()

		chain    []digest.Digest
		unpacked bool
	)
	snapshotterName, err = i.client.resolveSnapshotterName(ctx, snapshotterName)
	if err != nil {
		return err
	}
	sn, err := i.client.getSnapshotter(ctx, snapshotterName)
	if err != nil {
		return err
	}
	for _, layer := range layers {
		unpacked, err = rootfs.ApplyLayerWithOpts(ctx, layer, chain, sn, a, config.SnapshotOpts, config.ApplyOpts)
		if err != nil {
			return err
		}

		if unpacked {
			// Set the uncompressed label after the uncompressed
			// digest has been verified through apply.
			cinfo := content.Info{
				Digest: layer.Blob.Digest,
				Labels: map[string]string{
					"containerd.io/uncompressed": layer.Diff.Digest.String(),
				},
			}
			if _, err := cs.Update(ctx, cinfo, "labels.containerd.io/uncompressed"); err != nil {
				return err
			}
		}

		chain = append(chain, layer.Diff.Digest)
	}

	desc, err := i.i.Config(ctx, cs, i.platform)
	if err != nil {
		return err
	}

	rootfs := identity.ChainID(chain).String()

	cinfo := content.Info{
		Digest: desc.Digest,
		Labels: map[string]string{
			fmt.Sprintf("containerd.io/gc.ref.snapshot.%s", snapshotterName): rootfs,
		},
	}

	_, err = cs.Update(ctx, cinfo, fmt.Sprintf("labels.containerd.io/gc.ref.snapshot.%s", snapshotterName))
	return err
}

func (i *image) getLayers(ctx context.Context, platform platforms.MatchComparer) ([]rootfs.Layer, error) {
	cs := i.client.ContentStore()

	manifest, err := images.Manifest(ctx, cs, i.i.Target, platform)
	if err != nil {
		return nil, err
	}

	diffIDs, err := i.i.RootFS(ctx, cs, platform)
	if err != nil {
		return nil, errors.Wrap(err, "failed to resolve rootfs")
	}
	if len(diffIDs) != len(manifest.Layers) {
		return nil, errors.Errorf("mismatched image rootfs and manifest layers")
	}
	layers := make([]rootfs.Layer, len(diffIDs))
	for i := range diffIDs {
		layers[i].Diff = ocispec.Descriptor{
			// TODO: derive media type from compressed type
			MediaType: ocispec.MediaTypeImageLayer,
			Digest:    diffIDs[i],
		}
		layers[i].Blob = manifest.Layers[i]
	}
	return layers, nil
}

func (i *image) ContentStore() content.Store {
	return i.client.ContentStore()
}
<|endoftext|>