<commit_before>package bongo

import (
	"encoding/json"
	"errors"
	"fmt"
	"strconv"
	"strings"

	"github.com/jinzhu/gorm"
)

// Fetch fetches the data from db by given parameters(fields of the struct)
func (b *Bongo) Fetch(i Modellable) error {
	if i.GetId() == 0 {
		return errors.New(fmt.Sprintf("Id is not set for %s", i.TableName()))
	}

	if err := b.DB.Table(i.TableName()).
		Find(i).
		Error; err != nil {
		return err
	}

	return nil
}

// ById Fetches data from db by it's id
func (b *Bongo) ById(i Modellable, id int64) error {
	if err := b.DB.
		Table(i.TableName()).
		Where("id = ?", id).
		Find(i).
		Error; err != nil {
		return err
	}

	return nil
}

// Creates a new record with the given struct and its fields
func (b *Bongo) Create(i Modellable) error {
	if err := b.DB.Save(i).Error; err != nil {
		return err
	}

	return nil
}

// Update updates all fields of a struct with assigned data
func (b *Bongo) Update(i Modellable) error {
	if i.GetId() == 0 {
		return errors.New(fmt.Sprintf("Id is not set for %s", i.TableName()))
	}

	// Update and Create is using the Save method, so they are
	// same functions but GORM handles, AfterCreate and AfterUpdate
	// in correct manner
	return b.Create(i)
}

// Delete deletes the data by it's id, it doesnt take any other fields
// into consideration
func (b *Bongo) Delete(i Modellable) error {
	if i.GetId() == 0 {
		return errors.New(fmt.Sprintf("Id is not set for %s", i.TableName()))
	}

	if err := b.DB.Delete(i).Error; err != nil {
		return err
	}

	return nil
}

// FetchByIds fetches data from db by their ids in ordered fashion,
// if non-found this function doesnt return any error.
func (b *Bongo) FetchByIds(i Modellable, data interface{}, ids []int64) error {
	if len(ids) == 0 {
		return nil
	}

	orderByQuery := ""
	comma := ""
	for _, id := range ids {
		orderByQuery = orderByQuery + comma + " id = " + strconv.FormatInt(id, 10) + " desc"
		comma = ","
	}

	// init query
	query := b.DB.Model(i)

	// add table name
	query = query.Table(i.TableName())

	query = query.Order(orderByQuery)

	query = query.Where(ids)

	query = query.Find(data)

	// supress not found errors
	return CheckErr(query)

}

func (b *Bongo) UpdatePartial(i Modellable, set map[string]interface{}) error {
	if i.GetId() == 0 {
		return errors.New(fmt.Sprintf("Id is not set for %s", i.TableName()))
	}

	query := b.DB.Table(i.TableName())

	query = query.Where(i.GetId())

	if err := query.Update(set).Error; err != nil {
		return err
	}

	if err := b.Fetch(i); err != nil {
		return err
	}

	b.AfterUpdate(i)
	return nil
}

// selector, set
func (b *Bongo) UpdateMulti(i Modellable, rest ...map[string]interface{}) error {
	var set, selector map[string]interface{}

	switch len(rest) {
	case 1:
		set = rest[0]
		selector = nil
	case 2:
		selector = rest[0]
		set = rest[1]
	default:
		return errors.New("Update partial parameter list is wrong")
	}

	query := b.DB.Table(i.TableName())

	//add selector
	query = addWhere(query, selector)

	if err := query.Update(set).Error; err != nil {
		return err
	}

	return nil
}

func (b *Bongo) Count(i Modellable, where ...interface{}) (int, error) {
	var count int

	// init query
	query := b.DB.Model(i)

	// add table name
	query = query.Table(i.TableName())

	// add query
	query = query.Where(where[0], where[1:len(where)]...)

	return count, query.Count(&count).Error
}

func (b *Bongo) CountWithQuery(i Modellable, q *Query) (int, error) {
	query := b.BuildQuery(i, q)
	var count int
	return count, query.Count(&count).Error
}

type Query struct {
	Selector   map[string]interface{}
	Sort       map[string]string
	Pluck      string
	Pagination Pagination
}

type Pagination struct {
	Limit int
	Skip  int
}

func NewPagination(limit int, skip int) *Pagination {
	return &Pagination{
		Limit: limit,
		Skip:  skip,
	}
}

func NewQS(selector map[string]interface{}) *Query {
	return &Query{
		Selector: selector,
	}
}

// selector, sort, limit, pluck,
func (b *Bongo) Some(i Modellable, data interface{}, q *Query) error {
	err := b.executeQuery(i, data, q)
	if err == gorm.RecordNotFound {
		return nil
	}
	return err
}

func (b *Bongo) One(i Modellable, data interface{}, q *Query) error {
	q.Pagination.Limit = 1
	return b.executeQuery(i, data, q)
}

func (b *Bongo) BuildQuery(i Modellable, q *Query) *gorm.DB {
	// init query
	query := b.DB.Model(i)

	// add table name
	query = query.Table(i.TableName())

	// add sort options
	query = addSort(query, q.Sort)

	query = addSkip(query, q.Pagination.Skip)

	query = addLimit(query, q.Pagination.Limit)

	// add selector
	query = addWhere(query, q.Selector)

	return query
}

func (b *Bongo) executeQuery(i Modellable, data interface{}, q *Query) error {
	// init query
	query := b.BuildQuery(i, q)

	var err error
	// TODO refactor this part
	if q.Pluck != "" {
		if strings.Contains(q.Pluck, ",") {
			// add pluck data
			query = addPluck(query, q.Pluck)

			err = query.Find(data).Error
		} else {
			err = query.Pluck(q.Pluck, data).Error
		}
	} else {
		err = query.Find(data).Error
	}

	return err
}

func (b *Bongo) PublishEvent(eventName string, i Modellable) error {
	data, err := json.Marshal(i)
	if err != nil {
		b.log.Error("Error while marshalling for publish %s", err)
		return err
	}

	err = b.Broker.Publish(i.TableName()+"_"+eventName, data)
	if err != nil {
		b.log.Error("Error while publishing %s", err)
		return err
	}

	return nil
}

func (b *Bongo) AfterCreate(i Modellable) {
	b.PublishEvent("created", i)
}

func (b *Bongo) AfterUpdate(i Modellable) {
	b.PublishEvent("updated", i)
}

func (b *Bongo) AfterDelete(i Modellable) {
	b.PublishEvent("deleted", i)
}

// addSort injects sort parameters into query
func addSort(query *gorm.DB, options map[string]string) *gorm.DB {

	if options == nil {
		return query
	}

	if len(options) == 0 {
		return query
	}

	var opts []string
	for key, val := range options {
		opts = append(opts, fmt.Sprintf("%s %v", key, val))
	}
	return query.Order(strings.Join(opts, ","))
}

// addPluck basically adds select statement for
// only required fields
func addPluck(query *gorm.DB, plucked string) *gorm.DB {
	if plucked == "" {
		return query
	}

	return query.Select(plucked)
}

// addWhere adds where query
func addWhere(query *gorm.DB, selector map[string]interface{}) *gorm.DB {
	if selector == nil {
		return query
	}

	// instead sending one selector, do chaining here
	return query.Where(selector)
}

// addSkip adds skip parameter into sql query
func addSkip(query *gorm.DB, skip int) *gorm.DB {
	if skip > 0 {
		return query.Offset(skip)
	}

	return query
}

// addLimit adds limit into query if set
func addLimit(query *gorm.DB, limit int) *gorm.DB {
	// if limit is minus or 0 ignore
	if limit > 0 {
		return query.Limit(limit)
	}

	return query
}

// CheckErr checks error exitence and returns
// if found, but this function suppress RecordNotFound errors
func CheckErr(res *gorm.DB) error {
	if res == nil {
		return nil
	}

	if res.Error == nil {
		return nil
	}

	if res.Error == gorm.RecordNotFound {
		return nil
	}

	return res.Error
}
<commit_msg>Social: init queries with gorm.Model<commit_after>package bongo

import (
	"encoding/json"
	"errors"
	"fmt"
	"strconv"
	"strings"

	"github.com/jinzhu/gorm"
)

// Fetch fetches the data from db by given parameters(fields of the struct)
func (b *Bongo) Fetch(i Modellable) error {
	if i.GetId() == 0 {
		return errors.New(fmt.Sprintf("Id is not set for %s", i.TableName()))
	}

	if err := b.DB.Table(i.TableName()).
		Find(i).
		Error; err != nil {
		return err
	}

	return nil
}

// ById Fetches data from db by it's id
func (b *Bongo) ById(i Modellable, id int64) error {
	if err := b.DB.
		Table(i.TableName()).
		Where("id = ?", id).
		Find(i).
		Error; err != nil {
		return err
	}

	return nil
}

// Creates a new record with the given struct and its fields
func (b *Bongo) Create(i Modellable) error {
	if err := b.DB.Save(i).Error; err != nil {
		return err
	}

	return nil
}

// Update updates all fields of a struct with assigned data
func (b *Bongo) Update(i Modellable) error {
	if i.GetId() == 0 {
		return errors.New(fmt.Sprintf("Id is not set for %s", i.TableName()))
	}

	// Update and Create is using the Save method, so they are
	// same functions but GORM handles, AfterCreate and AfterUpdate
	// in correct manner
	return b.Create(i)
}

// Delete deletes the data by it's id, it doesnt take any other fields
// into consideration
func (b *Bongo) Delete(i Modellable) error {
	if i.GetId() == 0 {
		return errors.New(fmt.Sprintf("Id is not set for %s", i.TableName()))
	}

	if err := b.DB.Delete(i).Error; err != nil {
		return err
	}

	return nil
}

// FetchByIds fetches data from db by their ids in ordered fashion,
// if non-found this function doesnt return any error.
func (b *Bongo) FetchByIds(i Modellable, data interface{}, ids []int64) error {
	if len(ids) == 0 {
		return nil
	}

	orderByQuery := ""
	comma := ""
	for _, id := range ids {
		orderByQuery = orderByQuery + comma + " id = " + strconv.FormatInt(id, 10) + " desc"
		comma = ","
	}

	// init query
	query := b.DB.Model(i)

	// add table name
	query = query.Table(i.TableName())

	query = query.Order(orderByQuery)

	query = query.Where(ids)

	query = query.Find(data)

	// supress not found errors
	return CheckErr(query)

}

func (b *Bongo) UpdatePartial(i Modellable, set map[string]interface{}) error {
	if i.GetId() == 0 {
		return errors.New(fmt.Sprintf("Id is not set for %s", i.TableName()))
	}

	// init query
	query := b.DB.Model(i)

	query = query.Table(i.TableName())

	query = query.Where(i.GetId())

	if err := query.Update(set).Error; err != nil {
		return err
	}

	if err := b.Fetch(i); err != nil {
		return err
	}

	b.AfterUpdate(i)
	return nil
}

// selector, set
func (b *Bongo) UpdateMulti(i Modellable, rest ...map[string]interface{}) error {
	var set, selector map[string]interface{}

	switch len(rest) {
	case 1:
		set = rest[0]
		selector = nil
	case 2:
		selector = rest[0]
		set = rest[1]
	default:
		return errors.New("Update partial parameter list is wrong")
	}

	query := b.DB.Model(i)

	query = query.Table(i.TableName())

	//add selector
	query = addWhere(query, selector)

	if err := query.Update(set).Error; err != nil {
		return err
	}

	return nil
}

func (b *Bongo) Count(i Modellable, where ...interface{}) (int, error) {
	var count int

	// init query
	query := b.DB.Model(i)

	// add table name
	query = query.Table(i.TableName())

	// add query
	query = query.Where(where[0], where[1:len(where)]...)

	return count, query.Count(&count).Error
}

func (b *Bongo) CountWithQuery(i Modellable, q *Query) (int, error) {
	query := b.BuildQuery(i, q)
	var count int
	return count, query.Count(&count).Error
}

type Query struct {
	Selector   map[string]interface{}
	Sort       map[string]string
	Pluck      string
	Pagination Pagination
}

type Pagination struct {
	Limit int
	Skip  int
}

func NewPagination(limit int, skip int) *Pagination {
	return &Pagination{
		Limit: limit,
		Skip:  skip,
	}
}

func NewQS(selector map[string]interface{}) *Query {
	return &Query{
		Selector: selector,
	}
}

// selector, sort, limit, pluck,
func (b *Bongo) Some(i Modellable, data interface{}, q *Query) error {
	err := b.executeQuery(i, data, q)
	if err == gorm.RecordNotFound {
		return nil
	}
	return err
}

func (b *Bongo) One(i Modellable, data interface{}, q *Query) error {
	q.Pagination.Limit = 1
	return b.executeQuery(i, data, q)
}

func (b *Bongo) BuildQuery(i Modellable, q *Query) *gorm.DB {
	// init query
	query := b.DB.Model(i)

	// add table name
	query = query.Table(i.TableName())

	// add sort options
	query = addSort(query, q.Sort)

	query = addSkip(query, q.Pagination.Skip)

	query = addLimit(query, q.Pagination.Limit)

	// add selector
	query = addWhere(query, q.Selector)

	return query
}

func (b *Bongo) executeQuery(i Modellable, data interface{}, q *Query) error {
	// init query
	query := b.BuildQuery(i, q)

	var err error
	// TODO refactor this part
	if q.Pluck != "" {
		if strings.Contains(q.Pluck, ",") {
			// add pluck data
			query = addPluck(query, q.Pluck)

			err = query.Find(data).Error
		} else {
			err = query.Pluck(q.Pluck, data).Error
		}
	} else {
		err = query.Find(data).Error
	}

	return err
}

func (b *Bongo) PublishEvent(eventName string, i Modellable) error {
	data, err := json.Marshal(i)
	if err != nil {
		b.log.Error("Error while marshalling for publish %s", err)
		return err
	}

	err = b.Broker.Publish(i.TableName()+"_"+eventName, data)
	if err != nil {
		b.log.Error("Error while publishing %s", err)
		return err
	}

	return nil
}

func (b *Bongo) AfterCreate(i Modellable) {
	b.PublishEvent("created", i)
}

func (b *Bongo) AfterUpdate(i Modellable) {
	b.PublishEvent("updated", i)
}

func (b *Bongo) AfterDelete(i Modellable) {
	b.PublishEvent("deleted", i)
}

// addSort injects sort parameters into query
func addSort(query *gorm.DB, options map[string]string) *gorm.DB {

	if options == nil {
		return query
	}

	if len(options) == 0 {
		return query
	}

	var opts []string
	for key, val := range options {
		opts = append(opts, fmt.Sprintf("%s %v", key, val))
	}
	return query.Order(strings.Join(opts, ","))
}

// addPluck basically adds select statement for
// only required fields
func addPluck(query *gorm.DB, plucked string) *gorm.DB {
	if plucked == "" {
		return query
	}

	return query.Select(plucked)
}

// addWhere adds where query
func addWhere(query *gorm.DB, selector map[string]interface{}) *gorm.DB {
	if selector == nil {
		return query
	}

	// instead sending one selector, do chaining here
	return query.Where(selector)
}

// addSkip adds skip parameter into sql query
func addSkip(query *gorm.DB, skip int) *gorm.DB {
	if skip > 0 {
		return query.Offset(skip)
	}

	return query
}

// addLimit adds limit into query if set
func addLimit(query *gorm.DB, limit int) *gorm.DB {
	// if limit is minus or 0 ignore
	if limit > 0 {
		return query.Limit(limit)
	}

	return query
}

// CheckErr checks error exitence and returns
// if found, but this function suppress RecordNotFound errors
func CheckErr(res *gorm.DB) error {
	if res == nil {
		return nil
	}

	if res.Error == nil {
		return nil
	}

	if res.Error == gorm.RecordNotFound {
		return nil
	}

	return res.Error
}
<|endoftext|>


<commit_before>// 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 subtle provides subtle implementations of the Streaming AEAD primitive.
package subtle

import (
	"crypto/aes"
	"crypto/cipher"
	"encoding/binary"
	"errors"
	"fmt"
	"io"

	subtleaead "github.com/google/tink/go/aead/subtle"
	"github.com/google/tink/go/subtle/random"
	"github.com/google/tink/go/subtle"
)

const (
	// nonceSizeInBytes is the size of the IVs for GCM.
	nonceSizeInBytes = 12

	// NoncePrefixInBytes is the nonce has the format nonce_prefix || ctr || last_block.
	// The nonce_prefix is constant for the whole file.
	// The ctr is a 32 bit ctr, the last_block is 1 if this is the
	// last block of the file and 0 otherwise.
	NoncePrefixInBytes = 7

	// TagSizeInBytes is the size of the tags of each ciphertext segment.
	TagSizeInBytes = 16
)

// AESGCMHKDF implements streaming encryption using AES-GCM with HKDF as key derivation function.
//
// Each ciphertext uses a new AES-GCM key that is derived from the key derivation key, a randomly
// chosen salt of the same size as the key and a nonce prefix.
//
// The format of a ciphertext is header || segment_0 || segment_1 || ... || segment_k. The
// header has size HeaderLength(). Its format is headerLength || salt || prefix. where
// headerLength is 1 byte determining the size of the header, salt is a salt used in the key
// derivation and prefix is the prefix of the nonce. In principle headerLength is redundant
// information, since the length of the header can be determined from the key size.
//
// segment_i is the i-th segment of the ciphertext. The size of segment_1 .. segment_{k-1} is
// ciphertextSegmentSize. segment_0 is shorter, so that segment_0, the header and other information
// of size firstSegmentOffset align with ciphertextSegmentSize.
type AESGCMHKDF struct {
	MainKey                      []byte
	hkdfAlg                      string
	keySizeInBytes               int
	ciphertextSegmentSize        int
	firstCiphertextSegmentOffset int
	plaintextSegmentSize         int
}

// NewAESGCMHKDF initializes a streaming primitive with a key derivation key and encryption parameters.
//
// mainKey argument is an input keying material used to derive sub keys.
// hkdfAlg argument is a JCE MAC algorithm name, e.g., HmacSha256, used for the HKDF key derivation.
// keySizeInBytes argument is a key size of the sub keys
// ciphertextSegmentSize argument is the size of ciphertext segments.
// firstSegmentOffset argument is the offset of the first ciphertext segment. That means the first
// segment has size ciphertextSegmentSize - HeaderLength() - firstSegmentOffset
func NewAESGCMHKDF(
	mainKey []byte,
	hkdfAlg string,
	keySizeInBytes int,
	ciphertextSegmentSize int,
	firstSegmentOffset int,
) (*AESGCMHKDF, error) {
	if len(mainKey) < 16 || len(mainKey) < keySizeInBytes {
		return nil, errors.New("mainKey too short")
	}
	if err := subtleaead.ValidateAESKeySize(uint32(keySizeInBytes)); err != nil {
		return nil, err
	}
	headerLen := 1 + keySizeInBytes + NoncePrefixInBytes
	if ciphertextSegmentSize <= firstSegmentOffset+headerLen+TagSizeInBytes {
		return nil, errors.New("ciphertextSegmentSize too small")
	}

	keyClone := make([]byte, len(mainKey))
	copy(keyClone, mainKey)

	return &AESGCMHKDF{
		MainKey:                      keyClone,
		hkdfAlg:                      hkdfAlg,
		keySizeInBytes:               keySizeInBytes,
		ciphertextSegmentSize:        ciphertextSegmentSize,
		firstCiphertextSegmentOffset: firstSegmentOffset + headerLen,
		plaintextSegmentSize:         ciphertextSegmentSize - TagSizeInBytes,
	}, nil
}

// HeaderLength returns a length of the encryption header.
func (a *AESGCMHKDF) HeaderLength() int {
	return 1 + a.keySizeInBytes + NoncePrefixInBytes
}

// deriveKey returns a key derived from the given main key using salt and aad parameters.
func (a *AESGCMHKDF) deriveKey(salt, aad []byte) ([]byte, error) {
	return subtle.ComputeHKDF(a.hkdfAlg, a.MainKey, salt, aad, uint32(a.keySizeInBytes))
}

// aesGCMHKDFWriter works as a wrapper around underlying io.Writer, which is responsible for
// encrypting written data. The data is encrypted and flushed in segments of a given size.
// Once all the data is written aesGCMHKDFWriter must be closed.
type aesGCMHKDFWriter struct {
	encryptedSegments int
	noncePrefix       []byte
	cipher            cipher.AEAD
	wr                io.Writer

	pt                           []byte
	ptPos                        int
	ct                           []byte
	firstCiphertextSegmentOffset int

	closed bool
}

// NewEncryptingWriter returns a wrapper around underlying io.Writer, such that any write-operation
// via the wrapper results in AEAD-encryption of the written data, using aad
// as associated authenticated data. The associated data is not included in the ciphertext
// and has to be passed in as parameter for decryption.
func (a *AESGCMHKDF) NewEncryptingWriter(w io.Writer, aad []byte) (io.WriteCloser, error) {
	salt := random.GetRandomBytes(uint32(a.keySizeInBytes))
	noncePrefix := random.GetRandomBytes(NoncePrefixInBytes)

	dkey, err := a.deriveKey(salt, aad)
	if err != nil {
		return nil, err
	}

	cipher, err := newCipher(dkey)
	if err != nil {
		return nil, err
	}

	header := make([]byte, a.HeaderLength())
	header[0] = byte(a.HeaderLength())
	copy(header[1:], salt)
	copy(header[1+len(salt):], noncePrefix)
	if _, err := w.Write(header); err != nil {
		return nil, err
	}

	return &aesGCMHKDFWriter{
		noncePrefix: noncePrefix,
		cipher:      cipher,
		wr:          w,

		pt:                           make([]byte, a.plaintextSegmentSize),
		firstCiphertextSegmentOffset: a.firstCiphertextSegmentOffset,
	}, nil
}

// Write encrypts passed data and passes the encrypted data to the underlying writer.
func (w *aesGCMHKDFWriter) Write(p []byte) (int, error) {
	if w.closed {
		return 0, errors.New("write on closed writer")
	}

	pos := 0
	for {
		ptLim := len(w.pt)
		if w.encryptedSegments == 0 {
			ptLim = len(w.pt) - w.firstCiphertextSegmentOffset
		}
		n := copy(w.pt[w.ptPos:ptLim], p[pos:])
		w.ptPos += n
		pos += n
		if pos == len(p) {
			break
		}

		nonce := generateSegmentNonce(w.noncePrefix, w.encryptedSegments, false)

		w.ct = w.cipher.Seal(w.ct[0:0], nonce, w.pt[:ptLim], nil)
		if _, err := w.wr.Write(w.ct); err != nil {
			return pos, err
		}
		w.ptPos = 0
		w.encryptedSegments++
	}
	return pos, nil
}

// Close encrypts the remaining data, flushes it to the underlying writer and closes this writer.
func (w *aesGCMHKDFWriter) Close() error {
	if w.closed {
		return nil
	}

	nonce := generateSegmentNonce(w.noncePrefix, w.encryptedSegments, true)

	w.ct = w.cipher.Seal(w.ct[0:0], nonce, w.pt[0:w.ptPos], nil)
	if _, err := w.wr.Write(w.ct); err != nil {
		return err
	}
	w.ptPos = 0
	w.encryptedSegments++
	w.closed = true
	return nil
}

// aesGCMHKDFReader works as a wrapper around underlying io.Reader.
type aesGCMHKDFReader struct {
	decryptedSegments int
	noncePrefix       []byte
	cipher            cipher.AEAD
	underlyingReader  io.Reader

	pt                 []byte
	ptPos              int
	ct                 []byte
	ctPos              int
	firstSegmentOffset int
}

// NewDecryptingReader returns a wrapper around underlying io.Reader, such that any read-operation
// via the wrapper results in AEAD-decryption of the underlying ciphertext,
// using aad as associated authenticated data.
func (a *AESGCMHKDF) NewDecryptingReader(r io.Reader, aad []byte) (io.Reader, error) {
	hlen := make([]byte, 1)
	if _, err := io.ReadFull(r, hlen); err != nil {
		return nil, err
	}
	if hlen[0] != byte(a.HeaderLength()) {
		return nil, errors.New("invalid header length")
	}

	salt := make([]byte, a.keySizeInBytes)
	if _, err := io.ReadFull(r, salt); err != nil {
		return nil, fmt.Errorf("cannot read salt: %v", err)
	}

	noncePrefix := make([]byte, NoncePrefixInBytes)
	if _, err := io.ReadFull(r, noncePrefix); err != nil {
		return nil, fmt.Errorf("cannot read noncePrefix: %v", err)
	}

	dkey, err := a.deriveKey(salt, aad)
	if err != nil {
		return nil, err
	}

	cipher, err := newCipher(dkey)
	if err != nil {
		return nil, err
	}

	return &aesGCMHKDFReader{
		noncePrefix:      noncePrefix,
		cipher:           cipher,
		underlyingReader: r,

		// Allocate an extra byte to detect last segment.
		ct:                 make([]byte, a.ciphertextSegmentSize+1),
		firstSegmentOffset: a.firstCiphertextSegmentOffset,
	}, nil
}

// Read decrypts data from underlying reader and passes it to p.
func (r *aesGCMHKDFReader) Read(p []byte) (int, error) {
	if r.ptPos < len(r.pt) {
		n := copy(p, r.pt[r.ptPos:])
		r.ptPos += n
		return n, nil
	}

	ctLim := len(r.ct)
	if r.decryptedSegments == 0 {
		ctLim -= r.firstSegmentOffset
	}

	n, err := io.ReadFull(r.underlyingReader, r.ct[r.ctPos:ctLim])
	if err != nil && err != io.ErrUnexpectedEOF {
		return 0, err
	}
	var (
		lastSegment bool
		segment     int
	)
	if err != nil {
		lastSegment = true
		segment = r.ctPos + n
	} else {
		segment = r.ctPos + n - 1
	}
	nonce := generateSegmentNonce(r.noncePrefix, r.decryptedSegments, lastSegment)
	r.pt, err = r.cipher.Open(r.pt[0:0], nonce, r.ct[:segment], nil)
	if err != nil {
		return 0, err
	}

	// Copy 1 byte remainder to the beginning of ct.
	if !lastSegment {
		r.ct[0] = r.ct[segment]
		r.ctPos = 1
	}

	r.decryptedSegments++
	r.ptPos = 0

	n = copy(p, r.pt)
	r.ptPos = n
	return n, nil
}

// newCipher creates a new AES-GCM cipher using the given key and the crypto library.
func newCipher(key []byte) (cipher.AEAD, error) {
	aesCipher, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	ret, err := cipher.NewGCMWithTagSize(aesCipher, TagSizeInBytes)
	if err != nil {
		return nil, err
	}
	return ret, nil
}

func generateSegmentNonce(noncePrefix []byte, segmentNr int, last bool) []byte {
	var l byte
	if last {
		l = 1
	}

	nonce := make([]byte, nonceSizeInBytes)
	offs := 0
	copy(nonce, noncePrefix)
	offs += len(noncePrefix)
	binary.BigEndian.PutUint32(nonce[offs:], uint32(segmentNr))
	offs += 4
	nonce[offs] = l
	return nonce
}
<commit_msg>Golang: Check that the segment counter is in [0, 2^32).<commit_after>// 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 subtle provides subtle implementations of the Streaming AEAD primitive.
package subtle

import (
	"crypto/aes"
	"crypto/cipher"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"math"

	subtleaead "github.com/google/tink/go/aead/subtle"
	"github.com/google/tink/go/subtle/random"
	"github.com/google/tink/go/subtle"
)

const (
	// nonceSizeInBytes is the size of the IVs for GCM.
	nonceSizeInBytes = 12

	// NoncePrefixInBytes is the nonce has the format nonce_prefix || ctr || last_block.
	// The nonce_prefix is constant for the whole file.
	// The ctr is a 32 bit ctr, the last_block is 1 if this is the
	// last block of the file and 0 otherwise.
	NoncePrefixInBytes = 7

	// TagSizeInBytes is the size of the tags of each ciphertext segment.
	TagSizeInBytes = 16
)

// AESGCMHKDF implements streaming encryption using AES-GCM with HKDF as key derivation function.
//
// Each ciphertext uses a new AES-GCM key that is derived from the key derivation key, a randomly
// chosen salt of the same size as the key and a nonce prefix.
//
// The format of a ciphertext is header || segment_0 || segment_1 || ... || segment_k. The
// header has size HeaderLength(). Its format is headerLength || salt || prefix. where
// headerLength is 1 byte determining the size of the header, salt is a salt used in the key
// derivation and prefix is the prefix of the nonce. In principle headerLength is redundant
// information, since the length of the header can be determined from the key size.
//
// segment_i is the i-th segment of the ciphertext. The size of segment_1 .. segment_{k-1} is
// ciphertextSegmentSize. segment_0 is shorter, so that segment_0, the header and other information
// of size firstSegmentOffset align with ciphertextSegmentSize.
type AESGCMHKDF struct {
	MainKey                      []byte
	hkdfAlg                      string
	keySizeInBytes               int
	ciphertextSegmentSize        int
	firstCiphertextSegmentOffset int
	plaintextSegmentSize         int
}

// NewAESGCMHKDF initializes a streaming primitive with a key derivation key and encryption parameters.
//
// mainKey argument is an input keying material used to derive sub keys.
// hkdfAlg argument is a JCE MAC algorithm name, e.g., HmacSha256, used for the HKDF key derivation.
// keySizeInBytes argument is a key size of the sub keys
// ciphertextSegmentSize argument is the size of ciphertext segments.
// firstSegmentOffset argument is the offset of the first ciphertext segment. That means the first
// segment has size ciphertextSegmentSize - HeaderLength() - firstSegmentOffset
func NewAESGCMHKDF(
	mainKey []byte,
	hkdfAlg string,
	keySizeInBytes int,
	ciphertextSegmentSize int,
	firstSegmentOffset int,
) (*AESGCMHKDF, error) {
	if len(mainKey) < 16 || len(mainKey) < keySizeInBytes {
		return nil, errors.New("mainKey too short")
	}
	if err := subtleaead.ValidateAESKeySize(uint32(keySizeInBytes)); err != nil {
		return nil, err
	}
	headerLen := 1 + keySizeInBytes + NoncePrefixInBytes
	if ciphertextSegmentSize <= firstSegmentOffset+headerLen+TagSizeInBytes {
		return nil, errors.New("ciphertextSegmentSize too small")
	}

	keyClone := make([]byte, len(mainKey))
	copy(keyClone, mainKey)

	return &AESGCMHKDF{
		MainKey:                      keyClone,
		hkdfAlg:                      hkdfAlg,
		keySizeInBytes:               keySizeInBytes,
		ciphertextSegmentSize:        ciphertextSegmentSize,
		firstCiphertextSegmentOffset: firstSegmentOffset + headerLen,
		plaintextSegmentSize:         ciphertextSegmentSize - TagSizeInBytes,
	}, nil
}

// HeaderLength returns a length of the encryption header.
func (a *AESGCMHKDF) HeaderLength() int {
	return 1 + a.keySizeInBytes + NoncePrefixInBytes
}

// deriveKey returns a key derived from the given main key using salt and aad parameters.
func (a *AESGCMHKDF) deriveKey(salt, aad []byte) ([]byte, error) {
	return subtle.ComputeHKDF(a.hkdfAlg, a.MainKey, salt, aad, uint32(a.keySizeInBytes))
}

// aesGCMHKDFWriter works as a wrapper around underlying io.Writer, which is responsible for
// encrypting written data. The data is encrypted and flushed in segments of a given size.
// Once all the data is written aesGCMHKDFWriter must be closed.
type aesGCMHKDFWriter struct {
	encryptedSegments uint64
	noncePrefix       []byte
	cipher            cipher.AEAD
	wr                io.Writer

	pt                           []byte
	ptPos                        int
	ct                           []byte
	firstCiphertextSegmentOffset int

	closed bool
}

// NewEncryptingWriter returns a wrapper around underlying io.Writer, such that any write-operation
// via the wrapper results in AEAD-encryption of the written data, using aad
// as associated authenticated data. The associated data is not included in the ciphertext
// and has to be passed in as parameter for decryption.
func (a *AESGCMHKDF) NewEncryptingWriter(w io.Writer, aad []byte) (io.WriteCloser, error) {
	salt := random.GetRandomBytes(uint32(a.keySizeInBytes))
	noncePrefix := random.GetRandomBytes(NoncePrefixInBytes)

	dkey, err := a.deriveKey(salt, aad)
	if err != nil {
		return nil, err
	}

	cipher, err := newCipher(dkey)
	if err != nil {
		return nil, err
	}

	header := make([]byte, a.HeaderLength())
	header[0] = byte(a.HeaderLength())
	copy(header[1:], salt)
	copy(header[1+len(salt):], noncePrefix)
	if _, err := w.Write(header); err != nil {
		return nil, err
	}

	return &aesGCMHKDFWriter{
		noncePrefix: noncePrefix,
		cipher:      cipher,
		wr:          w,

		pt:                           make([]byte, a.plaintextSegmentSize),
		firstCiphertextSegmentOffset: a.firstCiphertextSegmentOffset,
	}, nil
}

// Write encrypts passed data and passes the encrypted data to the underlying writer.
func (w *aesGCMHKDFWriter) Write(p []byte) (int, error) {
	if w.closed {
		return 0, errors.New("write on closed writer")
	}

	pos := 0
	for {
		ptLim := len(w.pt)
		if w.encryptedSegments == 0 {
			ptLim = len(w.pt) - w.firstCiphertextSegmentOffset
		}
		n := copy(w.pt[w.ptPos:ptLim], p[pos:])
		w.ptPos += n
		pos += n
		if pos == len(p) {
			break
		}

		nonce, err := generateSegmentNonce(w.noncePrefix, w.encryptedSegments, false)
		if err != nil {
			return 0, err
		}

		w.ct = w.cipher.Seal(w.ct[0:0], nonce, w.pt[:ptLim], nil)
		if _, err := w.wr.Write(w.ct); err != nil {
			return pos, err
		}
		w.ptPos = 0
		w.encryptedSegments++
	}
	return pos, nil
}

// Close encrypts the remaining data, flushes it to the underlying writer and closes this writer.
func (w *aesGCMHKDFWriter) Close() error {
	if w.closed {
		return nil
	}

	nonce, err := generateSegmentNonce(w.noncePrefix, w.encryptedSegments, true)
	if err != nil {
		return err
	}

	w.ct = w.cipher.Seal(w.ct[0:0], nonce, w.pt[0:w.ptPos], nil)
	if _, err := w.wr.Write(w.ct); err != nil {
		return err
	}
	w.ptPos = 0
	w.encryptedSegments++
	w.closed = true
	return nil
}

// aesGCMHKDFReader works as a wrapper around underlying io.Reader.
type aesGCMHKDFReader struct {
	decryptedSegments uint64
	noncePrefix       []byte
	cipher            cipher.AEAD
	underlyingReader  io.Reader

	pt                 []byte
	ptPos              int
	ct                 []byte
	ctPos              int
	firstSegmentOffset int
}

// NewDecryptingReader returns a wrapper around underlying io.Reader, such that any read-operation
// via the wrapper results in AEAD-decryption of the underlying ciphertext,
// using aad as associated authenticated data.
func (a *AESGCMHKDF) NewDecryptingReader(r io.Reader, aad []byte) (io.Reader, error) {
	hlen := make([]byte, 1)
	if _, err := io.ReadFull(r, hlen); err != nil {
		return nil, err
	}
	if hlen[0] != byte(a.HeaderLength()) {
		return nil, errors.New("invalid header length")
	}

	salt := make([]byte, a.keySizeInBytes)
	if _, err := io.ReadFull(r, salt); err != nil {
		return nil, fmt.Errorf("cannot read salt: %v", err)
	}

	noncePrefix := make([]byte, NoncePrefixInBytes)
	if _, err := io.ReadFull(r, noncePrefix); err != nil {
		return nil, fmt.Errorf("cannot read noncePrefix: %v", err)
	}

	dkey, err := a.deriveKey(salt, aad)
	if err != nil {
		return nil, err
	}

	cipher, err := newCipher(dkey)
	if err != nil {
		return nil, err
	}

	return &aesGCMHKDFReader{
		noncePrefix:      noncePrefix,
		cipher:           cipher,
		underlyingReader: r,

		// Allocate an extra byte to detect last segment.
		ct:                 make([]byte, a.ciphertextSegmentSize+1),
		firstSegmentOffset: a.firstCiphertextSegmentOffset,
	}, nil
}

// Read decrypts data from underlying reader and passes it to p.
func (r *aesGCMHKDFReader) Read(p []byte) (int, error) {
	if r.ptPos < len(r.pt) {
		n := copy(p, r.pt[r.ptPos:])
		r.ptPos += n
		return n, nil
	}

	ctLim := len(r.ct)
	if r.decryptedSegments == 0 {
		ctLim -= r.firstSegmentOffset
	}

	n, err := io.ReadFull(r.underlyingReader, r.ct[r.ctPos:ctLim])
	if err != nil && err != io.ErrUnexpectedEOF {
		return 0, err
	}
	var (
		lastSegment bool
		segment     int
	)
	if err != nil {
		lastSegment = true
		segment = r.ctPos + n
	} else {
		segment = r.ctPos + n - 1
	}
	nonce, err := generateSegmentNonce(r.noncePrefix, r.decryptedSegments, lastSegment)
	if err != nil {
		return 0, nil
	}

	r.pt, err = r.cipher.Open(r.pt[0:0], nonce, r.ct[:segment], nil)
	if err != nil {
		return 0, err
	}

	// Copy 1 byte remainder to the beginning of ct.
	if !lastSegment {
		r.ct[0] = r.ct[segment]
		r.ctPos = 1
	}

	r.decryptedSegments++
	r.ptPos = 0

	n = copy(p, r.pt)
	r.ptPos = n
	return n, nil
}

// newCipher creates a new AES-GCM cipher using the given key and the crypto library.
func newCipher(key []byte) (cipher.AEAD, error) {
	aesCipher, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	ret, err := cipher.NewGCMWithTagSize(aesCipher, TagSizeInBytes)
	if err != nil {
		return nil, err
	}
	return ret, nil
}

func generateSegmentNonce(noncePrefix []byte, segmentNr uint64, last bool) ([]byte, error) {
	var l byte
	if last {
		l = 1
	}

	nonce := make([]byte, nonceSizeInBytes)
	offs := 0
	copy(nonce, noncePrefix)
	offs += len(noncePrefix)
	if segmentNr >= math.MaxUint32 {
		return nil, errors.New("too many segments")
	}
	binary.BigEndian.PutUint32(nonce[offs:], uint32(segmentNr))
	offs += 4
	nonce[offs] = l
	return nonce, nil
}
<|endoftext|>


<commit_before>package refresh

import (
	"context"
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"
	"sync"
	"time"

	"github.com/fsnotify/fsnotify"
)

type Manager struct {
	*Configuration
	Logger     *Logger
	Restart    chan bool
	gil        *sync.Once
	ID         string
	context    context.Context
	cancelFunc context.CancelFunc
}

func New(c *Configuration) *Manager {
	return NewWithContext(c, context.Background())
}

func NewWithContext(c *Configuration, ctx context.Context) *Manager {
	ctx, cancelFunc := context.WithCancel(ctx)
	m := &Manager{
		Configuration: c,
		Logger:        NewLogger(c),
		Restart:       make(chan bool),
		gil:           &sync.Once{},
		ID:            ID(),
		context:       ctx,
		cancelFunc:    cancelFunc,
	}
	return m
}

func (r *Manager) Start() error {
	w := NewWatcher(r)
	w.Start()
	go r.build(fsnotify.Event{Name: ":start:"})
	go func() {
		for {
			select {
			case event := <-w.Events:
				if event.Op != fsnotify.Chmod {
					go r.build(event)
				}
				w.Remove(event.Name)
				w.Add(event.Name)
			case <-r.context.Done():
				break
			}
		}
	}()
	go func() {
		for {
			select {
			case err := <-w.Errors:
				r.Logger.Error(err)
			case <-r.context.Done():
				break
			}
		}
	}()
	r.runner()
	return nil
}

func (r *Manager) build(event fsnotify.Event) {
	r.gil.Do(func() {
		defer func() {
			r.gil = &sync.Once{}
		}()
		r.buildTransaction(func() error {
			// time.Sleep(r.BuildDelay * time.Millisecond)

			now := time.Now()
			r.Logger.Print("Rebuild on: %s", event.Name)
			cmd := exec.Command("go", "build", "-v", "-i", "-o", r.FullBuildPath())
			err := r.runAndListen(cmd)
			if err != nil {
				if strings.Contains(err.Error(), "no buildable Go source files") {
					r.cancelFunc()
					log.Fatal(err)
				}
				return err
			}

			tt := time.Since(now)
			r.Logger.Success("Building Completed (PID: %d) (Time: %s)", cmd.Process.Pid, tt)
			r.Restart <- true
			return nil
		})
	})
}

func (r *Manager) buildTransaction(fn func() error) {
	lpath := ErrorLogPath()
	err := fn()
	if err != nil {
		f, _ := os.Create(lpath)
		fmt.Fprint(f, err)
		r.Logger.Error("Error!")
		r.Logger.Error(err)
	} else {
		os.Remove(lpath)
	}
}
<commit_msg>fix(build-target) use buildTargetPath<commit_after>package refresh

import (
	"context"
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"
	"sync"
	"time"

	"github.com/fsnotify/fsnotify"
)

type Manager struct {
	*Configuration
	Logger     *Logger
	Restart    chan bool
	gil        *sync.Once
	ID         string
	context    context.Context
	cancelFunc context.CancelFunc
}

func New(c *Configuration) *Manager {
	return NewWithContext(c, context.Background())
}

func NewWithContext(c *Configuration, ctx context.Context) *Manager {
	ctx, cancelFunc := context.WithCancel(ctx)
	m := &Manager{
		Configuration: c,
		Logger:        NewLogger(c),
		Restart:       make(chan bool),
		gil:           &sync.Once{},
		ID:            ID(),
		context:       ctx,
		cancelFunc:    cancelFunc,
	}
	return m
}

func (r *Manager) Start() error {
	w := NewWatcher(r)
	w.Start()
	go r.build(fsnotify.Event{Name: ":start:"})
	go func() {
		for {
			select {
			case event := <-w.Events:
				if event.Op != fsnotify.Chmod {
					go r.build(event)
				}
				w.Remove(event.Name)
				w.Add(event.Name)
			case <-r.context.Done():
				break
			}
		}
	}()
	go func() {
		for {
			select {
			case err := <-w.Errors:
				r.Logger.Error(err)
			case <-r.context.Done():
				break
			}
		}
	}()
	r.runner()
	return nil
}

func (r *Manager) build(event fsnotify.Event) {
	r.gil.Do(func() {
		defer func() {
			r.gil = &sync.Once{}
		}()
		r.buildTransaction(func() error {
			// time.Sleep(r.BuildDelay * time.Millisecond)

			now := time.Now()
			r.Logger.Print("Rebuild on: %s", event.Name)
			cmd := exec.Command("go", "build", "-v", "-i", "-o", r.FullBuildPath(), r.BuildTargetPath)
			err := r.runAndListen(cmd)
			if err != nil {
				if strings.Contains(err.Error(), "no buildable Go source files") {
					r.cancelFunc()
					log.Fatal(err)
				}
				return err
			}

			tt := time.Since(now)
			r.Logger.Success("Building Completed (PID: %d) (Time: %s)", cmd.Process.Pid, tt)
			r.Restart <- true
			return nil
		})
	})
}

func (r *Manager) buildTransaction(fn func() error) {
	lpath := ErrorLogPath()
	err := fn()
	if err != nil {
		f, _ := os.Create(lpath)
		fmt.Fprint(f, err)
		r.Logger.Error("Error!")
		r.Logger.Error(err)
	} else {
		os.Remove(lpath)
	}
}
<|endoftext|>


<commit_before>// Package release provides functionality for appcast releases.
package release

import "github.com/hashicorp/go-version"

// Releaser is the interface that wraps the Release methods.
type Releaser interface {
	VersionOrBuildString() string
	Version() *version.Version
	SetVersion(version *version.Version)
	SetVersionString(value string) error
	Build() string
	SetBuild(build string)
	Title() string
	SetTitle(title string)
	Description() string
	SetDescription(description string)
	PublishedDateTime() *PublishedDateTime
	SetPublishedDateTime(publishedDateTime *PublishedDateTime)
	ReleaseNotesLink() string
	SetReleaseNotesLink(releaseNotesLink string)
	AddDownload(d Download)
	Downloads() []Download
	SetDownloads(downloads []Download)
	IsPreRelease() bool
	SetIsPreRelease(isPreRelease bool)
}

// Release represents a single application release.
type Release struct {
	// version specifies a release version. It should follow the SemVer
	// specification.
	version *version.Version

	// build specifies a release build. This could have any value.
	build string

	// title specifies a release title.
	title string

	// description specifies a release description.
	description string

	// publishedDateTime specifies the release published date and time.
	publishedDateTime *PublishedDateTime

	// releaseNotesLink specifies a link to the release notes.
	releaseNotesLink string

	// downloads specifies a slice of Download structs which represents a list
	// of all current release downloads.
	downloads []Download

	// isPreRelease specifies whether a release is not stable.
	//
	// By default, each release is considered to be stable, so the default value
	// is false. If the release version, build or any other provider-specific
	// value points that a release is unstable, the value should become true.
	isPreRelease bool
}

// New returns a new Release instance pointer. Requires both version and build
// strings. By default, Release.IsPrerelease is set to false, so the release
// will be considered as stable.
func New(version string, build string) (*Release, error) {
	r := &Release{
		isPreRelease: false,
	}

	// add version
	err := r.SetVersionString(version)
	if err != nil {
		return nil, err
	}

	// add build, if its not empty
	if build != "" {
		r.build = build
	}

	return r, nil
}

// VersionOrBuildString retrieves the release version string if it's available.
// Otherwise, returns the release build string.
func (r *Release) VersionOrBuildString() string {
	if r.version == nil || r.version.String() == "" {
		return r.build
	}

	return r.version.String()
}

// Version is a Release.version getter.
func (r *Release) Version() *version.Version {
	return r.version
}

// SetVersion is a Release.version setter.
func (r *Release) SetVersion(version *version.Version) {
	r.version = version
}

// SetVersionString sets the Release.version from the provided version value
// string. Returns an error, if the provided version string value doesn't follow
// the SemVer specification.
func (r *Release) SetVersionString(value string) error {
	v, err := version.NewVersion(value)
	if err != nil {
		return err
	}

	r.version = v

	return nil
}

// Build is a Release.build getter.
func (r *Release) Build() string {
	return r.build
}

// SetBuild is a Release.build setter.
func (r *Release) SetBuild(build string) {
	r.build = build
}

// Title is a Release.title getter.
func (r *Release) Title() string {
	return r.title
}

// SetTitle is a Release.title setter.
func (r *Release) SetTitle(title string) {
	r.title = title
}

// Description is a Release.description getter.
func (r *Release) Description() string {
	return r.description
}

// SetDescription is a Release.description setter.
func (r *Release) SetDescription(description string) {
	r.description = description
}

// PublishedDateTime is a Release.publishedDateTime getter.
func (r *Release) PublishedDateTime() *PublishedDateTime {
	return r.publishedDateTime
}

// SetPublishedDateTime is a Release.publishedDateTime setter.
func (r *Release) SetPublishedDateTime(publishedDateTime *PublishedDateTime) {
	r.publishedDateTime = publishedDateTime
}

// ReleaseNotesLink is a Release.releaseNotesLink getter.
func (r *Release) ReleaseNotesLink() string {
	return r.releaseNotesLink
}

// SetReleaseNotesLink is a Release.releaseNotesLink setter.
func (r *Release) SetReleaseNotesLink(releaseNotesLink string) {
	r.releaseNotesLink = releaseNotesLink
}

// AddDownload appends the provided Download to the Release.downloads slice.
func (r *Release) AddDownload(d Download) {
	r.downloads = append(r.downloads, d)
}

// Downloads is a Release.downloads getter.
func (r *Release) Downloads() []Download {
	return r.downloads
}

// SetDownloads is a Release.downloads setter.
func (r *Release) SetDownloads(downloads []Download) {
	r.downloads = downloads
}

// IsPreRelease is a Release.isPreRelease getter.
func (r *Release) IsPreRelease() bool {
	return r.isPreRelease
}

// SetIsPreRelease is a Release.isPreRelease setter.
func (r *Release) SetIsPreRelease(isPreRelease bool) {
	r.isPreRelease = isPreRelease
}
<commit_msg>Add Release.minimumSystemVersion<commit_after>// Package release provides functionality for appcast releases.
package release

import "github.com/hashicorp/go-version"

// Releaser is the interface that wraps the Release methods.
type Releaser interface {
	VersionOrBuildString() string
	Version() *version.Version
	SetVersion(version *version.Version)
	SetVersionString(value string) error
	Build() string
	SetBuild(build string)
	Title() string
	SetTitle(title string)
	Description() string
	SetDescription(description string)
	PublishedDateTime() *PublishedDateTime
	SetPublishedDateTime(publishedDateTime *PublishedDateTime)
	ReleaseNotesLink() string
	SetReleaseNotesLink(releaseNotesLink string)
	MinimumSystemVersion() string
	SetMinimumSystemVersion(minimumSystemVersion string)
	AddDownload(d Download)
	Downloads() []Download
	SetDownloads(downloads []Download)
	IsPreRelease() bool
	SetIsPreRelease(isPreRelease bool)
}

// Release represents a single application release.
type Release struct {
	// version specifies a release version. It should follow the SemVer
	// specification.
	version *version.Version

	// build specifies a release build. This could have any value.
	build string

	// title specifies a release title.
	title string

	// description specifies a release description.
	description string

	// publishedDateTime specifies the release published date and time.
	publishedDateTime *PublishedDateTime

	// releaseNotesLink specifies a link to the release notes.
	releaseNotesLink string

	// minimumSystemVersion specifies the required system version for the
	// current app release.
	minimumSystemVersion string

	// downloads specifies a slice of Download structs which represents a list
	// of all current release downloads.
	downloads []Download

	// isPreRelease specifies whether a release is not stable.
	//
	// By default, each release is considered to be stable, so the default value
	// is false. If the release version, build or any other provider-specific
	// value points that a release is unstable, the value should become true.
	isPreRelease bool
}

// New returns a new Release instance pointer. Requires both version and build
// strings. By default, Release.IsPrerelease is set to false, so the release
// will be considered as stable.
func New(version string, build string) (*Release, error) {
	r := &Release{
		isPreRelease: false,
	}

	// add version
	err := r.SetVersionString(version)
	if err != nil {
		return nil, err
	}

	// add build, if its not empty
	if build != "" {
		r.build = build
	}

	return r, nil
}

// VersionOrBuildString retrieves the release version string if it's available.
// Otherwise, returns the release build string.
func (r *Release) VersionOrBuildString() string {
	if r.version == nil || r.version.String() == "" {
		return r.build
	}

	return r.version.String()
}

// Version is a Release.version getter.
func (r *Release) Version() *version.Version {
	return r.version
}

// SetVersion is a Release.version setter.
func (r *Release) SetVersion(version *version.Version) {
	r.version = version
}

// SetVersionString sets the Release.version from the provided version value
// string. Returns an error, if the provided version string value doesn't follow
// the SemVer specification.
func (r *Release) SetVersionString(value string) error {
	v, err := version.NewVersion(value)
	if err != nil {
		return err
	}

	r.version = v

	return nil
}

// Build is a Release.build getter.
func (r *Release) Build() string {
	return r.build
}

// SetBuild is a Release.build setter.
func (r *Release) SetBuild(build string) {
	r.build = build
}

// Title is a Release.title getter.
func (r *Release) Title() string {
	return r.title
}

// SetTitle is a Release.title setter.
func (r *Release) SetTitle(title string) {
	r.title = title
}

// Description is a Release.description getter.
func (r *Release) Description() string {
	return r.description
}

// SetDescription is a Release.description setter.
func (r *Release) SetDescription(description string) {
	r.description = description
}

// PublishedDateTime is a Release.publishedDateTime getter.
func (r *Release) PublishedDateTime() *PublishedDateTime {
	return r.publishedDateTime
}

// SetPublishedDateTime is a Release.publishedDateTime setter.
func (r *Release) SetPublishedDateTime(publishedDateTime *PublishedDateTime) {
	r.publishedDateTime = publishedDateTime
}

// ReleaseNotesLink is a Release.releaseNotesLink getter.
func (r *Release) ReleaseNotesLink() string {
	return r.releaseNotesLink
}

// SetReleaseNotesLink is a Release.releaseNotesLink setter.
func (r *Release) SetReleaseNotesLink(releaseNotesLink string) {
	r.releaseNotesLink = releaseNotesLink
}

// MinimumSystemVersion is a Release.minimumSystemVersion getter.
func (r *Release) MinimumSystemVersion() string {
	return r.minimumSystemVersion
}

// SetMinimumSystemVersion is a Release.minimumSystemVersion setter.
func (r *Release) SetMinimumSystemVersion(minimumSystemVersion string) {
	r.minimumSystemVersion = minimumSystemVersion
}

// AddDownload appends the provided Download to the Release.downloads slice.
func (r *Release) AddDownload(d Download) {
	r.downloads = append(r.downloads, d)
}

// Downloads is a Release.downloads getter.
func (r *Release) Downloads() []Download {
	return r.downloads
}

// SetDownloads is a Release.downloads setter.
func (r *Release) SetDownloads(downloads []Download) {
	r.downloads = downloads
}

// IsPreRelease is a Release.isPreRelease getter.
func (r *Release) IsPreRelease() bool {
	return r.isPreRelease
}

// SetIsPreRelease is a Release.isPreRelease setter.
func (r *Release) SetIsPreRelease(isPreRelease bool) {
	r.isPreRelease = isPreRelease
}
<|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 docker

import (
	"context"
	"net/http"
	"net/url"
	"path"
	"strconv"
	"strings"

	"github.com/containerd/containerd/errdefs"
	"github.com/containerd/containerd/images"
	"github.com/containerd/containerd/log"
	"github.com/containerd/containerd/reference"
	"github.com/containerd/containerd/remotes"
	digest "github.com/opencontainers/go-digest"
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"golang.org/x/net/context/ctxhttp"
)

var (
	// ErrNoToken is returned if a request is successful but the body does not
	// contain an authorization token.
	ErrNoToken = errors.New("authorization server did not include a token in the response")

	// ErrInvalidAuthorization is used when credentials are passed to a server but
	// those credentials are rejected.
	ErrInvalidAuthorization = errors.New("authorization failed")
)

// Authorizer is used to authorize HTTP requests based on 401 HTTP responses.
// An Authorizer is responsible for caching tokens or credentials used by
// requests.
type Authorizer interface {
	// Authorize sets the appropriate `Authorization` header on the given
	// request.
	//
	// If no authorization is found for the request, the request remains
	// unmodified. It may also add an `Authorization` header as
	//  "bearer <some bearer token>"
	//  "basic <base64 encoded credentials>"
	Authorize(context.Context, *http.Request) error

	// AddResponses adds a 401 response for the authorizer to consider when
	// authorizing requests. The last response should be unauthorized and
	// the previous requests are used to consider redirects and retries
	// that may have led to the 401.
	//
	// If response is not handled, returns `ErrNotImplemented`
	AddResponses(context.Context, []*http.Response) error
}

// ResolverOptions are used to configured a new Docker register resolver
type ResolverOptions struct {
	// Authorizer is used to authorize registry requests
	Authorizer Authorizer

	// Credentials provides username and secret given a host.
	// If username is empty but a secret is given, that secret
	// is interpretted as a long lived token.
	// Deprecated: use Authorizer
	Credentials func(string) (string, string, error)

	// Host provides the hostname given a namespace.
	Host func(string) (string, error)

	// PlainHTTP specifies to use plain http and not https
	PlainHTTP bool

	// Client is the http client to used when making registry requests
	Client *http.Client

	// Tracker is used to track uploads to the registry. This is used
	// since the registry does not have upload tracking and the existing
	// mechanism for getting blob upload status is expensive.
	Tracker StatusTracker
}

// DefaultHost is the default host function.
func DefaultHost(ns string) (string, error) {
	if ns == "docker.io" {
		return "registry-1.docker.io", nil
	}
	return ns, nil
}

type dockerResolver struct {
	auth      Authorizer
	host      func(string) (string, error)
	plainHTTP bool
	client    *http.Client
	tracker   StatusTracker
}

// NewResolver returns a new resolver to a Docker registry
func NewResolver(options ResolverOptions) remotes.Resolver {
	if options.Tracker == nil {
		options.Tracker = NewInMemoryTracker()
	}
	if options.Host == nil {
		options.Host = DefaultHost
	}
	if options.Authorizer == nil {
		options.Authorizer = NewAuthorizer(options.Client, options.Credentials)
	}
	return &dockerResolver{
		auth:      options.Authorizer,
		host:      options.Host,
		plainHTTP: options.PlainHTTP,
		client:    options.Client,
		tracker:   options.Tracker,
	}
}

var _ remotes.Resolver = &dockerResolver{}

func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocispec.Descriptor, error) {
	refspec, err := reference.Parse(ref)
	if err != nil {
		return "", ocispec.Descriptor{}, err
	}

	if refspec.Object == "" {
		return "", ocispec.Descriptor{}, reference.ErrObjectRequired
	}

	base, err := r.base(refspec)
	if err != nil {
		return "", ocispec.Descriptor{}, err
	}

	fetcher := dockerFetcher{
		dockerBase: base,
	}

	var (
		urls []string
		dgst = refspec.Digest()
	)

	if dgst != "" {
		if err := dgst.Validate(); err != nil {
			// need to fail here, since we can't actually resolve the invalid
			// digest.
			return "", ocispec.Descriptor{}, err
		}

		// turns out, we have a valid digest, make a url.
		urls = append(urls, fetcher.url("manifests", dgst.String()))

		// fallback to blobs on not found.
		urls = append(urls, fetcher.url("blobs", dgst.String()))
	} else {
		urls = append(urls, fetcher.url("manifests", refspec.Object))
	}

	ctx, err = contextWithRepositoryScope(ctx, refspec, false)
	if err != nil {
		return "", ocispec.Descriptor{}, err
	}
	for _, u := range urls {
		req, err := http.NewRequest(http.MethodHead, u, nil)
		if err != nil {
			return "", ocispec.Descriptor{}, err
		}

		// set headers for all the types we support for resolution.
		req.Header.Set("Accept", strings.Join([]string{
			images.MediaTypeDockerSchema2Manifest,
			images.MediaTypeDockerSchema2ManifestList,
			ocispec.MediaTypeImageManifest,
			ocispec.MediaTypeImageIndex, "*"}, ", "))

		log.G(ctx).Debug("resolving")
		resp, err := fetcher.doRequestWithRetries(ctx, req, nil)
		if err != nil {
			if errors.Cause(err) == ErrInvalidAuthorization {
				err = errors.Wrapf(err, "pull access denied, repository does not exist or may require authorization")
			}
			return "", ocispec.Descriptor{}, err
		}
		resp.Body.Close() // don't care about body contents.

		if resp.StatusCode > 299 {
			if resp.StatusCode == http.StatusNotFound {
				continue
			}
			return "", ocispec.Descriptor{}, errors.Errorf("unexpected status code %v: %v", u, resp.Status)
		}

		// this is the only point at which we trust the registry. we use the
		// content headers to assemble a descriptor for the name. when this becomes
		// more robust, we mostly get this information from a secure trust store.
		dgstHeader := digest.Digest(resp.Header.Get("Docker-Content-Digest"))

		if dgstHeader != "" {
			if err := dgstHeader.Validate(); err != nil {
				return "", ocispec.Descriptor{}, errors.Wrapf(err, "%q in header not a valid digest", dgstHeader)
			}
			dgst = dgstHeader
		}

		if dgst == "" {
			return "", ocispec.Descriptor{}, errors.Errorf("could not resolve digest for %v", ref)
		}

		var (
			size       int64
			sizeHeader = resp.Header.Get("Content-Length")
		)

		size, err = strconv.ParseInt(sizeHeader, 10, 64)
		if err != nil {

			return "", ocispec.Descriptor{}, errors.Wrapf(err, "invalid size header: %q", sizeHeader)
		}
		if size < 0 {
			return "", ocispec.Descriptor{}, errors.Errorf("%q in header not a valid size", sizeHeader)
		}

		desc := ocispec.Descriptor{
			Digest:    dgst,
			MediaType: resp.Header.Get("Content-Type"), // need to strip disposition?
			Size:      size,
		}

		log.G(ctx).WithField("desc.digest", desc.Digest).Debug("resolved")
		return ref, desc, nil
	}

	return "", ocispec.Descriptor{}, errors.Errorf("%v not found", ref)
}

func (r *dockerResolver) Fetcher(ctx context.Context, ref string) (remotes.Fetcher, error) {
	refspec, err := reference.Parse(ref)
	if err != nil {
		return nil, err
	}

	base, err := r.base(refspec)
	if err != nil {
		return nil, err
	}

	return dockerFetcher{
		dockerBase: base,
	}, nil
}

func (r *dockerResolver) Pusher(ctx context.Context, ref string) (remotes.Pusher, error) {
	refspec, err := reference.Parse(ref)
	if err != nil {
		return nil, err
	}

	// Manifests can be pushed by digest like any other object, but the passed in
	// reference cannot take a digest without the associated content. A tag is allowed
	// and will be used to tag pushed manifests.
	if refspec.Object != "" && strings.Contains(refspec.Object, "@") {
		return nil, errors.New("cannot use digest reference for push locator")
	}

	base, err := r.base(refspec)
	if err != nil {
		return nil, err
	}

	return dockerPusher{
		dockerBase: base,
		tag:        refspec.Object,
		tracker:    r.tracker,
	}, nil
}

type dockerBase struct {
	refspec reference.Spec
	base    url.URL

	client *http.Client
	auth   Authorizer
}

func (r *dockerResolver) base(refspec reference.Spec) (*dockerBase, error) {
	var (
		err  error
		base url.URL
	)

	host := refspec.Hostname()
	base.Host = host
	if r.host != nil {
		base.Host, err = r.host(host)
		if err != nil {
			return nil, err
		}
	}

	base.Scheme = "https"
	if r.plainHTTP || strings.HasPrefix(base.Host, "localhost:") {
		base.Scheme = "http"
	}

	prefix := strings.TrimPrefix(refspec.Locator, host+"/")
	base.Path = path.Join("/v2", prefix)

	return &dockerBase{
		refspec: refspec,
		base:    base,
		client:  r.client,
		auth:    r.auth,
	}, nil
}

func (r *dockerBase) url(ps ...string) string {
	url := r.base
	url.Path = path.Join(url.Path, path.Join(ps...))
	return url.String()
}

func (r *dockerBase) authorize(ctx context.Context, req *http.Request) error {
	// Check if has header for host
	if r.auth != nil {
		if err := r.auth.Authorize(ctx, req); err != nil {
			return err
		}
	}

	return nil
}

func (r *dockerBase) doRequest(ctx context.Context, req *http.Request) (*http.Response, error) {
	ctx = log.WithLogger(ctx, log.G(ctx).WithField("url", req.URL.String()))
	log.G(ctx).WithField("request.headers", req.Header).WithField("request.method", req.Method).Debug("do request")
	if err := r.authorize(ctx, req); err != nil {
		return nil, errors.Wrap(err, "failed to authorize")
	}
	resp, err := ctxhttp.Do(ctx, r.client, req)
	if err != nil {
		return nil, errors.Wrap(err, "failed to do request")
	}
	log.G(ctx).WithFields(logrus.Fields{
		"status":           resp.Status,
		"response.headers": resp.Header,
	}).Debug("fetch response received")
	return resp, nil
}

func (r *dockerBase) doRequestWithRetries(ctx context.Context, req *http.Request, responses []*http.Response) (*http.Response, error) {
	resp, err := r.doRequest(ctx, req)
	if err != nil {
		return nil, err
	}

	responses = append(responses, resp)
	req, err = r.retryRequest(ctx, req, responses)
	if err != nil {
		resp.Body.Close()
		return nil, err
	}
	if req != nil {
		resp.Body.Close()
		return r.doRequestWithRetries(ctx, req, responses)
	}
	return resp, err
}

func (r *dockerBase) retryRequest(ctx context.Context, req *http.Request, responses []*http.Response) (*http.Request, error) {
	if len(responses) > 5 {
		return nil, nil
	}
	last := responses[len(responses)-1]
	if last.StatusCode == http.StatusUnauthorized {
		log.G(ctx).WithField("header", last.Header.Get("WWW-Authenticate")).Debug("Unauthorized")
		if r.auth != nil {
			if err := r.auth.AddResponses(ctx, responses); err == nil {
				return copyRequest(req)
			} else if !errdefs.IsNotImplemented(err) {
				return nil, err
			}
		}

		return nil, nil
	} else if last.StatusCode == http.StatusMethodNotAllowed && req.Method == http.MethodHead {
		// Support registries which have not properly implemented the HEAD method for
		// manifests endpoint
		if strings.Contains(req.URL.Path, "/manifests/") {
			// TODO: copy request?
			req.Method = http.MethodGet
			return copyRequest(req)
		}
	}

	// TODO: Handle 50x errors accounting for attempt history
	return nil, nil
}

func copyRequest(req *http.Request) (*http.Request, error) {
	ireq := *req
	if ireq.GetBody != nil {
		var err error
		ireq.Body, err = ireq.GetBody()
		if err != nil {
			return nil, err
		}
	}
	return &ireq, nil
}
<commit_msg>Add custom headers option to dockerResolver<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 docker

import (
	"context"
	"net/http"
	"net/url"
	"path"
	"strconv"
	"strings"

	"github.com/containerd/containerd/errdefs"
	"github.com/containerd/containerd/images"
	"github.com/containerd/containerd/log"
	"github.com/containerd/containerd/reference"
	"github.com/containerd/containerd/remotes"
	"github.com/containerd/containerd/version"
	digest "github.com/opencontainers/go-digest"
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"golang.org/x/net/context/ctxhttp"
)

var (
	// ErrNoToken is returned if a request is successful but the body does not
	// contain an authorization token.
	ErrNoToken = errors.New("authorization server did not include a token in the response")

	// ErrInvalidAuthorization is used when credentials are passed to a server but
	// those credentials are rejected.
	ErrInvalidAuthorization = errors.New("authorization failed")
)

// Authorizer is used to authorize HTTP requests based on 401 HTTP responses.
// An Authorizer is responsible for caching tokens or credentials used by
// requests.
type Authorizer interface {
	// Authorize sets the appropriate `Authorization` header on the given
	// request.
	//
	// If no authorization is found for the request, the request remains
	// unmodified. It may also add an `Authorization` header as
	//  "bearer <some bearer token>"
	//  "basic <base64 encoded credentials>"
	Authorize(context.Context, *http.Request) error

	// AddResponses adds a 401 response for the authorizer to consider when
	// authorizing requests. The last response should be unauthorized and
	// the previous requests are used to consider redirects and retries
	// that may have led to the 401.
	//
	// If response is not handled, returns `ErrNotImplemented`
	AddResponses(context.Context, []*http.Response) error
}

// ResolverOptions are used to configured a new Docker register resolver
type ResolverOptions struct {
	// Authorizer is used to authorize registry requests
	Authorizer Authorizer

	// Credentials provides username and secret given a host.
	// If username is empty but a secret is given, that secret
	// is interpretted as a long lived token.
	// Deprecated: use Authorizer
	Credentials func(string) (string, string, error)

	// Host provides the hostname given a namespace.
	Host func(string) (string, error)

	// Headers are the HTTP request header fields sent by the resolver
	Headers http.Header

	// PlainHTTP specifies to use plain http and not https
	PlainHTTP bool

	// Client is the http client to used when making registry requests
	Client *http.Client

	// Tracker is used to track uploads to the registry. This is used
	// since the registry does not have upload tracking and the existing
	// mechanism for getting blob upload status is expensive.
	Tracker StatusTracker
}

// DefaultHost is the default host function.
func DefaultHost(ns string) (string, error) {
	if ns == "docker.io" {
		return "registry-1.docker.io", nil
	}
	return ns, nil
}

type dockerResolver struct {
	auth      Authorizer
	host      func(string) (string, error)
	headers   http.Header
	plainHTTP bool
	client    *http.Client
	tracker   StatusTracker
}

// NewResolver returns a new resolver to a Docker registry
func NewResolver(options ResolverOptions) remotes.Resolver {
	if options.Tracker == nil {
		options.Tracker = NewInMemoryTracker()
	}
	if options.Host == nil {
		options.Host = DefaultHost
	}
	if options.Headers == nil {
		options.Headers = make(http.Header)
	}
	if _, ok := options.Headers["Accept"]; !ok {
		// set headers for all the types we support for resolution.
		options.Headers.Set("Accept", strings.Join([]string{
			images.MediaTypeDockerSchema2Manifest,
			images.MediaTypeDockerSchema2ManifestList,
			ocispec.MediaTypeImageManifest,
			ocispec.MediaTypeImageIndex, "*"}, ", "))
	}
	if _, ok := options.Headers["User-Agent"]; !ok {
		options.Headers.Set("User-Agent", "containerd/"+version.Version)
	}
	if options.Authorizer == nil {
		options.Authorizer = NewAuthorizer(options.Client, options.Credentials)
	}
	return &dockerResolver{
		auth:      options.Authorizer,
		host:      options.Host,
		headers:   options.Headers,
		plainHTTP: options.PlainHTTP,
		client:    options.Client,
		tracker:   options.Tracker,
	}
}

var _ remotes.Resolver = &dockerResolver{}

func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocispec.Descriptor, error) {
	refspec, err := reference.Parse(ref)
	if err != nil {
		return "", ocispec.Descriptor{}, err
	}

	if refspec.Object == "" {
		return "", ocispec.Descriptor{}, reference.ErrObjectRequired
	}

	base, err := r.base(refspec)
	if err != nil {
		return "", ocispec.Descriptor{}, err
	}

	fetcher := dockerFetcher{
		dockerBase: base,
	}

	var (
		urls []string
		dgst = refspec.Digest()
	)

	if dgst != "" {
		if err := dgst.Validate(); err != nil {
			// need to fail here, since we can't actually resolve the invalid
			// digest.
			return "", ocispec.Descriptor{}, err
		}

		// turns out, we have a valid digest, make a url.
		urls = append(urls, fetcher.url("manifests", dgst.String()))

		// fallback to blobs on not found.
		urls = append(urls, fetcher.url("blobs", dgst.String()))
	} else {
		urls = append(urls, fetcher.url("manifests", refspec.Object))
	}

	ctx, err = contextWithRepositoryScope(ctx, refspec, false)
	if err != nil {
		return "", ocispec.Descriptor{}, err
	}
	for _, u := range urls {
		req, err := http.NewRequest(http.MethodHead, u, nil)
		if err != nil {
			return "", ocispec.Descriptor{}, err
		}

		req.Header = r.headers

		log.G(ctx).Debug("resolving")
		resp, err := fetcher.doRequestWithRetries(ctx, req, nil)
		if err != nil {
			if errors.Cause(err) == ErrInvalidAuthorization {
				err = errors.Wrapf(err, "pull access denied, repository does not exist or may require authorization")
			}
			return "", ocispec.Descriptor{}, err
		}
		resp.Body.Close() // don't care about body contents.

		if resp.StatusCode > 299 {
			if resp.StatusCode == http.StatusNotFound {
				continue
			}
			return "", ocispec.Descriptor{}, errors.Errorf("unexpected status code %v: %v", u, resp.Status)
		}

		// this is the only point at which we trust the registry. we use the
		// content headers to assemble a descriptor for the name. when this becomes
		// more robust, we mostly get this information from a secure trust store.
		dgstHeader := digest.Digest(resp.Header.Get("Docker-Content-Digest"))

		if dgstHeader != "" {
			if err := dgstHeader.Validate(); err != nil {
				return "", ocispec.Descriptor{}, errors.Wrapf(err, "%q in header not a valid digest", dgstHeader)
			}
			dgst = dgstHeader
		}

		if dgst == "" {
			return "", ocispec.Descriptor{}, errors.Errorf("could not resolve digest for %v", ref)
		}

		var (
			size       int64
			sizeHeader = resp.Header.Get("Content-Length")
		)

		size, err = strconv.ParseInt(sizeHeader, 10, 64)
		if err != nil {

			return "", ocispec.Descriptor{}, errors.Wrapf(err, "invalid size header: %q", sizeHeader)
		}
		if size < 0 {
			return "", ocispec.Descriptor{}, errors.Errorf("%q in header not a valid size", sizeHeader)
		}

		desc := ocispec.Descriptor{
			Digest:    dgst,
			MediaType: resp.Header.Get("Content-Type"), // need to strip disposition?
			Size:      size,
		}

		log.G(ctx).WithField("desc.digest", desc.Digest).Debug("resolved")
		return ref, desc, nil
	}

	return "", ocispec.Descriptor{}, errors.Errorf("%v not found", ref)
}

func (r *dockerResolver) Fetcher(ctx context.Context, ref string) (remotes.Fetcher, error) {
	refspec, err := reference.Parse(ref)
	if err != nil {
		return nil, err
	}

	base, err := r.base(refspec)
	if err != nil {
		return nil, err
	}

	return dockerFetcher{
		dockerBase: base,
	}, nil
}

func (r *dockerResolver) Pusher(ctx context.Context, ref string) (remotes.Pusher, error) {
	refspec, err := reference.Parse(ref)
	if err != nil {
		return nil, err
	}

	// Manifests can be pushed by digest like any other object, but the passed in
	// reference cannot take a digest without the associated content. A tag is allowed
	// and will be used to tag pushed manifests.
	if refspec.Object != "" && strings.Contains(refspec.Object, "@") {
		return nil, errors.New("cannot use digest reference for push locator")
	}

	base, err := r.base(refspec)
	if err != nil {
		return nil, err
	}

	return dockerPusher{
		dockerBase: base,
		tag:        refspec.Object,
		tracker:    r.tracker,
	}, nil
}

type dockerBase struct {
	refspec reference.Spec
	base    url.URL

	client *http.Client
	auth   Authorizer
}

func (r *dockerResolver) base(refspec reference.Spec) (*dockerBase, error) {
	var (
		err  error
		base url.URL
	)

	host := refspec.Hostname()
	base.Host = host
	if r.host != nil {
		base.Host, err = r.host(host)
		if err != nil {
			return nil, err
		}
	}

	base.Scheme = "https"
	if r.plainHTTP || strings.HasPrefix(base.Host, "localhost:") {
		base.Scheme = "http"
	}

	prefix := strings.TrimPrefix(refspec.Locator, host+"/")
	base.Path = path.Join("/v2", prefix)

	return &dockerBase{
		refspec: refspec,
		base:    base,
		client:  r.client,
		auth:    r.auth,
	}, nil
}

func (r *dockerBase) url(ps ...string) string {
	url := r.base
	url.Path = path.Join(url.Path, path.Join(ps...))
	return url.String()
}

func (r *dockerBase) authorize(ctx context.Context, req *http.Request) error {
	// Check if has header for host
	if r.auth != nil {
		if err := r.auth.Authorize(ctx, req); err != nil {
			return err
		}
	}

	return nil
}

func (r *dockerBase) doRequest(ctx context.Context, req *http.Request) (*http.Response, error) {
	ctx = log.WithLogger(ctx, log.G(ctx).WithField("url", req.URL.String()))
	log.G(ctx).WithField("request.headers", req.Header).WithField("request.method", req.Method).Debug("do request")
	if err := r.authorize(ctx, req); err != nil {
		return nil, errors.Wrap(err, "failed to authorize")
	}
	resp, err := ctxhttp.Do(ctx, r.client, req)
	if err != nil {
		return nil, errors.Wrap(err, "failed to do request")
	}
	log.G(ctx).WithFields(logrus.Fields{
		"status":           resp.Status,
		"response.headers": resp.Header,
	}).Debug("fetch response received")
	return resp, nil
}

func (r *dockerBase) doRequestWithRetries(ctx context.Context, req *http.Request, responses []*http.Response) (*http.Response, error) {
	resp, err := r.doRequest(ctx, req)
	if err != nil {
		return nil, err
	}

	responses = append(responses, resp)
	req, err = r.retryRequest(ctx, req, responses)
	if err != nil {
		resp.Body.Close()
		return nil, err
	}
	if req != nil {
		resp.Body.Close()
		return r.doRequestWithRetries(ctx, req, responses)
	}
	return resp, err
}

func (r *dockerBase) retryRequest(ctx context.Context, req *http.Request, responses []*http.Response) (*http.Request, error) {
	if len(responses) > 5 {
		return nil, nil
	}
	last := responses[len(responses)-1]
	if last.StatusCode == http.StatusUnauthorized {
		log.G(ctx).WithField("header", last.Header.Get("WWW-Authenticate")).Debug("Unauthorized")
		if r.auth != nil {
			if err := r.auth.AddResponses(ctx, responses); err == nil {
				return copyRequest(req)
			} else if !errdefs.IsNotImplemented(err) {
				return nil, err
			}
		}

		return nil, nil
	} else if last.StatusCode == http.StatusMethodNotAllowed && req.Method == http.MethodHead {
		// Support registries which have not properly implemented the HEAD method for
		// manifests endpoint
		if strings.Contains(req.URL.Path, "/manifests/") {
			// TODO: copy request?
			req.Method = http.MethodGet
			return copyRequest(req)
		}
	}

	// TODO: Handle 50x errors accounting for attempt history
	return nil, nil
}

func copyRequest(req *http.Request) (*http.Request, error) {
	ireq := *req
	if ireq.GetBody != nil {
		var err error
		ireq.Body, err = ireq.GetBody()
		if err != nil {
			return nil, err
		}
	}
	return &ireq, nil
}
<|endoftext|>


<commit_before>package gist7480523

import (
	"fmt"
	"go/build"
	"os"
	"strings"

	"github.com/shurcooL/go/exp/12"
	"golang.org/x/tools/go/vcs"

	. "github.com/shurcooL/go/gists/gist5504644"
	. "github.com/shurcooL/go/gists/gist7802150"
)

type GoPackageStringer func(*GoPackage) string

// A GoPackage describes a single package found in a directory.
// This is partially a copy of "cmd/go".Package, except it can be imported and reused. =.=
// https://code.google.com/p/go/source/browse/src/cmd/go/pkg.go?name=release#24
type GoPackage struct {
	Bpkg    *build.Package
	BpkgErr error

	Dir *exp12.Directory
}

func GoPackageFromImportPathFound(importPathFound ImportPathFound) *GoPackage {
	bpkg, err := BuildPackageFromSrcDir(importPathFound.FullPath())
	return goPackageFromBuildPackage(bpkg, err)
}

func GoPackageFromImportPath(importPath string) *GoPackage {
	bpkg, err := BuildPackageFromImportPath(importPath)
	return goPackageFromBuildPackage(bpkg, err)
}

func GoPackageFromPath(path, srcDir string) (*GoPackage, error) {
	bpkg, err := BuildPackageFromPath(path, srcDir)
	if err != nil {
		if _, noGo := err.(*build.NoGoError); noGo || bpkg.Dir == "" {
			return nil, err
		}
	}
	return goPackageFromBuildPackage(bpkg, err), nil
}

func goPackageFromBuildPackage(bpkg *build.Package, bpkgErr error) *GoPackage {
	if bpkgErr != nil {
		if _, noGo := bpkgErr.(*build.NoGoError); noGo || bpkg.Dir == "" {
			return nil
		}
	}

	if bpkg.ConflictDir != "" {
		fmt.Fprintf(os.Stderr, "warning: ConflictDir=%q (Dir=%q)\n", bpkg.ConflictDir, bpkg.Dir)
		return nil
	}

	goPackage := &GoPackage{
		Bpkg:    bpkg,
		BpkgErr: bpkgErr,

		Dir: exp12.LookupDirectory(bpkg.Dir),
	}

	/*if goPackage.Bpkg.Goroot == false { // Optimization that assume packages under Goroot are not under vcs
		// TODO: markAsNotNeedToUpdate() because of external insight?
	}*/

	return goPackage
}

// This is okay to call concurrently (a mutex is used internally).
// Actually, not completely okay because MakeUpdated technology is not thread-safe.
func (this *GoPackage) UpdateVcs() {
	if this.Bpkg.Goroot == false { // Optimization that assume packages under Goroot are not under vcs
		MakeUpdated(this.Dir)
	}
}

func (this *GoPackage) UpdateVcsFields() {
	if this.Dir.Repo != nil {
		MakeUpdated(this.Dir.Repo.VcsLocal)
		MakeUpdated(this.Dir.Repo.VcsRemote)

		repoImportPath := GetRepoImportPath(this.Dir.Repo.Vcs.RootPath(), this.Bpkg.SrcRoot)
		if repoRoot, err := vcs.RepoRootForImportPath(repoImportPath, false); err == nil {
			this.Dir.Repo.RepoRoot = repoRoot
		}
	}
}

func GetRepoImportPath(repoPath, srcRoot string) (repoImportPath string) {
	// Detect and handle case mismatch in prefix.
	if prefixLen := len(srcRoot + "/"); len(repoPath) >= prefixLen && srcRoot+"/" != repoPath[:prefixLen] && strings.EqualFold(srcRoot+"/", repoPath[:prefixLen]) {
		fmt.Fprintln(os.Stderr, "warning: GetRepoImportPath: prefix case doesn't match")
		return repoPath[prefixLen:]
	}

	return strings.TrimPrefix(repoPath, srcRoot+"/")
}
func GetRepoImportPathPattern(repoPath, srcRoot string) (repoImportPathPattern string) {
	return GetRepoImportPath(repoPath, srcRoot) + "/..."
}

func (this *GoPackage) String() string {
	return this.Bpkg.ImportPath
}

// =====

// GoPackageRepo represents a collection of Go packages contained by one VCS repository.
type GoPackageRepo struct {
	rootPath   string
	goPackages []*GoPackage
}

func NewGoPackageRepo(rootPath string, goPackages []*GoPackage) GoPackageRepo {
	return GoPackageRepo{rootPath, goPackages}
}

// RepoImportPath returns what would be the import path of the root folder of the repository. It may or may not
// be an actual Go package. E.g.,
//
//	"github.com/owner/repo"
func (repo GoPackageRepo) RepoImportPath() string {
	return GetRepoImportPath(repo.rootPath, repo.goPackages[0].Bpkg.SrcRoot)
}

// ImportPathPattern returns an import path pattern that matches all of the Go packages in this repo.
// E.g.,
//
//	"github.com/owner/repo/..."
func (repo GoPackageRepo) ImportPathPattern() string {
	return GetRepoImportPathPattern(repo.rootPath, repo.goPackages[0].Bpkg.SrcRoot)
}

// RootPath returns the path to the root workdir folder of the repository.
func (repo GoPackageRepo) RootPath() string         { return repo.rootPath }
func (repo GoPackageRepo) GoPackages() []*GoPackage { return repo.goPackages }

// ImportPaths returns a newline separated list of all import paths.
func (repo GoPackageRepo) ImportPaths() string {
	var importPaths []string
	for _, goPackage := range repo.goPackages {
		importPaths = append(importPaths, goPackage.Bpkg.ImportPath)
	}
	return strings.Join(importPaths, "\n")
}
<commit_msg>Resolve symlinks in GetRepoImportPath.<commit_after>package gist7480523

import (
	"fmt"
	"go/build"
	"os"
	"path/filepath"
	"strings"

	"github.com/shurcooL/go/exp/12"
	"golang.org/x/tools/go/vcs"

	. "github.com/shurcooL/go/gists/gist5504644"
	. "github.com/shurcooL/go/gists/gist7802150"
)

type GoPackageStringer func(*GoPackage) string

// A GoPackage describes a single package found in a directory.
// This is partially a copy of "cmd/go".Package, except it can be imported and reused. =.=
// https://code.google.com/p/go/source/browse/src/cmd/go/pkg.go?name=release#24
type GoPackage struct {
	Bpkg    *build.Package
	BpkgErr error

	Dir *exp12.Directory
}

func GoPackageFromImportPathFound(importPathFound ImportPathFound) *GoPackage {
	bpkg, err := BuildPackageFromSrcDir(importPathFound.FullPath())
	return goPackageFromBuildPackage(bpkg, err)
}

func GoPackageFromImportPath(importPath string) *GoPackage {
	bpkg, err := BuildPackageFromImportPath(importPath)
	return goPackageFromBuildPackage(bpkg, err)
}

func GoPackageFromPath(path, srcDir string) (*GoPackage, error) {
	bpkg, err := BuildPackageFromPath(path, srcDir)
	if err != nil {
		if _, noGo := err.(*build.NoGoError); noGo || bpkg.Dir == "" {
			return nil, err
		}
	}
	return goPackageFromBuildPackage(bpkg, err), nil
}

func goPackageFromBuildPackage(bpkg *build.Package, bpkgErr error) *GoPackage {
	if bpkgErr != nil {
		if _, noGo := bpkgErr.(*build.NoGoError); noGo || bpkg.Dir == "" {
			return nil
		}
	}

	if bpkg.ConflictDir != "" {
		fmt.Fprintf(os.Stderr, "warning: ConflictDir=%q (Dir=%q)\n", bpkg.ConflictDir, bpkg.Dir)
		return nil
	}

	goPackage := &GoPackage{
		Bpkg:    bpkg,
		BpkgErr: bpkgErr,

		Dir: exp12.LookupDirectory(bpkg.Dir),
	}

	/*if goPackage.Bpkg.Goroot == false { // Optimization that assume packages under Goroot are not under vcs
		// TODO: markAsNotNeedToUpdate() because of external insight?
	}*/

	return goPackage
}

// This is okay to call concurrently (a mutex is used internally).
// Actually, not completely okay because MakeUpdated technology is not thread-safe.
func (this *GoPackage) UpdateVcs() {
	if this.Bpkg.Goroot == false { // Optimization that assume packages under Goroot are not under vcs
		MakeUpdated(this.Dir)
	}
}

func (this *GoPackage) UpdateVcsFields() {
	if this.Dir.Repo != nil {
		MakeUpdated(this.Dir.Repo.VcsLocal)
		MakeUpdated(this.Dir.Repo.VcsRemote)

		repoImportPath := GetRepoImportPath(this.Dir.Repo.Vcs.RootPath(), this.Bpkg.SrcRoot)
		if repoRoot, err := vcs.RepoRootForImportPath(repoImportPath, false); err == nil {
			this.Dir.Repo.RepoRoot = repoRoot
		}
	}
}

func GetRepoImportPath(repoPath, srcRoot string) string {
	if s, err := filepath.EvalSymlinks(repoPath); err == nil {
		repoPath = s
	} else {
		fmt.Fprintln(os.Stderr, "warning: GetRepoImportPath: can't resolve symlink")
	}
	if s, err := filepath.EvalSymlinks(srcRoot); err == nil {
		srcRoot = s
	} else {
		fmt.Fprintln(os.Stderr, "warning: GetRepoImportPath: can't resolve symlink")
	}

	// Detect and handle case mismatch in prefix.
	if prefixLen := len(srcRoot + "/"); len(repoPath) >= prefixLen && srcRoot+"/" != repoPath[:prefixLen] && strings.EqualFold(srcRoot+"/", repoPath[:prefixLen]) {
		fmt.Fprintln(os.Stderr, "warning: GetRepoImportPath: prefix case doesn't match")
		return repoPath[prefixLen:]
	}

	return strings.TrimPrefix(repoPath, srcRoot+"/")
}
func GetRepoImportPathPattern(repoPath, srcRoot string) (repoImportPathPattern string) {
	return GetRepoImportPath(repoPath, srcRoot) + "/..."
}

func (this *GoPackage) String() string {
	return this.Bpkg.ImportPath
}

// =====

// GoPackageRepo represents a collection of Go packages contained by one VCS repository.
type GoPackageRepo struct {
	rootPath   string
	goPackages []*GoPackage
}

func NewGoPackageRepo(rootPath string, goPackages []*GoPackage) GoPackageRepo {
	return GoPackageRepo{rootPath, goPackages}
}

// RepoImportPath returns what would be the import path of the root folder of the repository. It may or may not
// be an actual Go package. E.g.,
//
//	"github.com/owner/repo"
func (repo GoPackageRepo) RepoImportPath() string {
	return GetRepoImportPath(repo.rootPath, repo.goPackages[0].Bpkg.SrcRoot)
}

// ImportPathPattern returns an import path pattern that matches all of the Go packages in this repo.
// E.g.,
//
//	"github.com/owner/repo/..."
func (repo GoPackageRepo) ImportPathPattern() string {
	return GetRepoImportPathPattern(repo.rootPath, repo.goPackages[0].Bpkg.SrcRoot)
}

// RootPath returns the path to the root workdir folder of the repository.
func (repo GoPackageRepo) RootPath() string         { return repo.rootPath }
func (repo GoPackageRepo) GoPackages() []*GoPackage { return repo.goPackages }

// ImportPaths returns a newline separated list of all import paths.
func (repo GoPackageRepo) ImportPaths() string {
	var importPaths []string
	for _, goPackage := range repo.goPackages {
		importPaths = append(importPaths, goPackage.Bpkg.ImportPath)
	}
	return strings.Join(importPaths, "\n")
}
<|endoftext|>


<commit_before>package release

import (
	"archive/tar"
	"compress/gzip"
	"io"
	"log"
	"os"
	"os/user"
	"path"
	"strconv"
	"time"
)

const (
	tarIsDir = 040000
)

func CreateTarball(name string, topLevelFolder string, srcFilenames []string) {
	log.Print("[release] Creating tarball")
	targetFile, err := os.Create(name)
	if err != nil {
		log.Fatal(err)
	}
	gz := gzip.NewWriter(targetFile)
	tarball := tar.NewWriter(gz)
	defer func() {
		if err := tarball.Close(); err != nil {
			log.Fatal(err)
		}
		if err := gz.Close(); err != nil {
			log.Fatal(err)
		}
		if err := targetFile.Close(); err != nil {
			log.Fatal(err)
		}
	}()

	log.Print("[release] Writing fake top level directory to tarball")
	writeAppDir(tarball, topLevelFolder)

	log.Print("[release] Adding requested files to archive")
	for _, f := range srcFilenames {
		finfo, err := os.Stat(f)
		if err != nil {
			log.Fatal(err)
		}
		dir := path.Dir(f)
		archiveFiles(tarball, []os.FileInfo{finfo}, dir, topLevelFolder)
	}
}

func writeAppDir(tarball *tar.Writer, appDir string) {
	user, err := user.Current()
	if err != nil {
		log.Fatal(err)
	}
	uid, err := strconv.Atoi(user.Uid)
	if err != nil {
		log.Fatal(err)
	}
	gid, err := strconv.Atoi(user.Gid)
	if err != nil {
		log.Fatal(err)
	}
	now := time.Now()
	header := &tar.Header{
		// "./" needs to preceed all files in a heroku slug.
		Name:       "./" + appDir,
		Mode:       int64(os.ModePerm | tarIsDir),
		Uid:        uid,
		Gid:        gid,
		ModTime:    now,
		AccessTime: now,
		ChangeTime: now,
		Typeflag:   tar.TypeDir,
	}
	tarball.WriteHeader(header)
}

func archiveFiles(tarball *tar.Writer, srcFiles []os.FileInfo, directory string, destPrefix string) {
	for _, finfo := range srcFiles {
		srcName := finfo.Name()
		if directory != "" {
			srcName = path.Join(directory, srcName)
		}

		src, err := os.Open(srcName)
		if err != nil {
			log.Fatal(err)
		}
		defer src.Close()

		tarHeader, err := tar.FileInfoHeader(finfo, "")
		if err != nil {
			log.Fatal(err)
		}
		// As in writeAppDir, "./" always needs to preceed entries in a
		// heroku slug.
		tarHeader.Name = "./" + path.Join(destPrefix, srcName)
		tarball.WriteHeader(tarHeader)
		io.Copy(tarball, src)

		if finfo.IsDir() {
			contents, err := src.Readdir(0)
			if err != nil {
				log.Fatal(err)
			}
			archiveFiles(tarball, contents, srcName, destPrefix)
		}
	}
}
<commit_msg>Add symlink info to tarball<commit_after>package release

import (
	"archive/tar"
	"compress/gzip"
	"io"
	"log"
	"os"
	"os/user"
	"path"
	"strconv"
	"time"
)

const (
	tarIsDir = 040000
)

func CreateTarball(name string, topLevelFolder string, srcFilenames []string) {
	log.Print("[release] Creating tarball")
	targetFile, err := os.Create(name)
	if err != nil {
		log.Fatalf("[release][error] %s", err)
	}
	gz := gzip.NewWriter(targetFile)
	tarball := tar.NewWriter(gz)
	defer func() {
		if err := tarball.Close(); err != nil {
			log.Fatalf("[release][error] %s", err)
		}
		if err := gz.Close(); err != nil {
			log.Fatalf("[release][error] %s", err)
		}
		if err := targetFile.Close(); err != nil {
			log.Fatalf("[release][error] %s", err)
		}
	}()

	log.Print("[release] Writing fake top level directory to tarball")
	writeAppDir(tarball, topLevelFolder)

	log.Print("[release] Adding requested files to archive")
	for _, f := range srcFilenames {
		finfo, err := os.Stat(f)
		if err != nil {
			log.Fatalf("[release][error] %s", err)
		}
		dir := path.Dir(f)
		archiveFiles(tarball, []os.FileInfo{finfo}, dir, topLevelFolder)
	}
}

func writeAppDir(tarball *tar.Writer, appDir string) {
	user, err := user.Current()
	if err != nil {
		log.Fatalf("[release][error] %s", err)
	}
	uid, err := strconv.Atoi(user.Uid)
	if err != nil {
		log.Fatalf("[release][error] %s", err)
	}
	gid, err := strconv.Atoi(user.Gid)
	if err != nil {
		log.Fatalf("[release][error] %s", err)
	}
	now := time.Now()
	header := &tar.Header{
		// "./" needs to preceed all files in a heroku slug.
		Name:       "./" + appDir,
		Mode:       int64(os.ModePerm | tarIsDir),
		Uid:        uid,
		Gid:        gid,
		ModTime:    now,
		AccessTime: now,
		ChangeTime: now,
		Typeflag:   tar.TypeDir,
	}
	tarball.WriteHeader(header)
}

func archiveFiles(tarball *tar.Writer, srcFiles []os.FileInfo, directory string, destPrefix string) {
	for _, finfo := range srcFiles {
		srcName := finfo.Name()
		if directory != "" {
			srcName = path.Join(directory, srcName)
		}

		src, err := os.Open(srcName)
		if err != nil {
			log.Fatalf("[release][error] %s", err)
		}
		defer src.Close()

		symlink := ""
		if finfo.Mode()|os.ModeType == os.ModeSymlink {
			symlink, err = os.Readlink(srcName)
			if err != nil {
				log.Fatalf("[release][error] %s", err)
			}
		}

		tarHeader, err := tar.FileInfoHeader(finfo, symlink)
		if err != nil {
			log.Fatalf("[release][error] %s", err)
		}
		// As in writeAppDir, "./" always needs to preceed entries in a
		// heroku slug.
		tarHeader.Name = "./" + path.Join(destPrefix, srcName)
		tarball.WriteHeader(tarHeader)
		io.Copy(tarball, src)

		if finfo.IsDir() {
			contents, err := src.Readdir(0)
			if err != nil {
				log.Fatalf("[release][error] %s", err)
			}
			archiveFiles(tarball, contents, srcName, destPrefix)
		}
	}
}
<|endoftext|>


<commit_before><commit_msg>Update comments to reflect float64 type<commit_after><|endoftext|>


<commit_before>package repository

import (
	"context"
	"fmt"

	"github.com/jmoiron/sqlx"
)

var (
	selectFeedsForCategoriesQuery = `
SELECT
	*,
	(select count(*) from entry e where e.read_at is null and e.feed_id = f.id) un_read,
	coalesce(f.last_read_at < (select max(e.published_at) from entry e where e.feed_id = f.id), false) new_entries
FROM feed f
where f.deleted_at is null and f.category_id in (?)
ORDER BY "f.order" ASC;`
	selectFeedsQuery = `SELECT * FROM feed where deleted_at is null ORDER BY "order" ASC;`
	getFeedQuery     = `select * from feed where id = ? and deleted_at is null;`
	updateFeedQuery  = `
update feed
set feed_title = :feed_title, feed_url = :feed_url, feed_image = :feed_image, feed_subtitle = :feed_subtitle, site_url = :site_url,
site_favicon_url = :site_favicon_url, site_favicon = :site_favicon, category_id = :category_id, last_read_at = :last_read_at,
created_at = :created_at, updated_at = :updated_at, deleted_at = :deleted_at
where id = :id and deleted_at is null;`
	createFeedQuery = `insert into feed (feed_title, feed_url, feed_image, feed_subtitle, site_url, site_favicon_url, site_favicon, category_id, last_read_at, created_at)
values (:feed_title, :feed_url, :feed_image, :feed_subtitle, :site_url, :site_favicon_url, :site_favicon, :category_id, :last_read_at, :created_at);`
)

type feedRepository struct {
	db *sqlx.DB
}

func NewFeedRepository(db *sqlx.DB) *feedRepository {
	return &feedRepository{
		db: db,
	}
}

func (r *feedRepository) Get(ctx context.Context, id int64) (Feed, error) {
	feed := Feed{}
	if err := r.db.GetContext(ctx, &feed, getFeedQuery, id); err != nil {
		return Feed{}, fmt.Errorf("cannot fetch feed: %w", err)
	}
	return feed, nil
}

func (r *feedRepository) ListForCategories(ctx context.Context, categoriesIDs []int64) ([]Feed, error) {
	query, args, err := sqlx.In(selectFeedsForCategoriesQuery, categoriesIDs)
	feeds := []Feed{}
	if err = r.db.SelectContext(ctx, &feeds, r.db.Rebind(query), args...); err != nil {
		return nil, fmt.Errorf("cannot select feeds: %w", err)
	}
	return feeds, nil
}

func (r *feedRepository) List(ctx context.Context) ([]Feed, error) {
	feeds := []Feed{}
	if err := r.db.SelectContext(ctx, &feeds, selectFeedsQuery); err != nil {
		return nil, fmt.Errorf("cannot select feeds: %w", err)
	}
	return feeds, nil
}

func (r *feedRepository) Update(ctx context.Context, feed Feed) error {
	if _, err := r.db.NamedExecContext(ctx, updateFeedQuery, feed); err != nil {
		return fmt.Errorf("cannot update feed: %w", err)
	}
	return nil
}

func (r *feedRepository) Create(ctx context.Context, feed Feed) (int64, error) {
	res, err := r.db.NamedExecContext(ctx, createFeedQuery, feed)
	if err != nil {
		return 0, fmt.Errorf("cannot create feed: %w", err)
	}
	lastInsertedID, err := res.LastInsertId()
	if err != nil {
		return 0, fmt.Errorf("cannot fetch last inserted id: %w", err)
	}
	return lastInsertedID, nil
}
<commit_msg>Fix error when there are no categories<commit_after>package repository

import (
	"context"
	"fmt"

	"github.com/jmoiron/sqlx"
)

var (
	selectFeedsForCategoriesQuery = `
SELECT
	*,
	(select count(*) from entry e where e.read_at is null and e.feed_id = f.id) un_read,
	coalesce(f.last_read_at < (select max(e.published_at) from entry e where e.feed_id = f.id), false) new_entries
FROM feed f
where f.deleted_at is null and f.category_id in (?)
ORDER BY "f.order" ASC;`
	selectFeedsQuery = `SELECT * FROM feed where deleted_at is null ORDER BY "order" ASC;`
	getFeedQuery     = `select * from feed where id = ? and deleted_at is null;`
	updateFeedQuery  = `
update feed
set feed_title = :feed_title, feed_url = :feed_url, feed_image = :feed_image, feed_subtitle = :feed_subtitle, site_url = :site_url,
site_favicon_url = :site_favicon_url, site_favicon = :site_favicon, category_id = :category_id, last_read_at = :last_read_at,
created_at = :created_at, updated_at = :updated_at, deleted_at = :deleted_at
where id = :id and deleted_at is null;`
	createFeedQuery = `insert into feed (feed_title, feed_url, feed_image, feed_subtitle, site_url, site_favicon_url, site_favicon, category_id, last_read_at, created_at)
values (:feed_title, :feed_url, :feed_image, :feed_subtitle, :site_url, :site_favicon_url, :site_favicon, :category_id, :last_read_at, :created_at);`
)

type feedRepository struct {
	db *sqlx.DB
}

func NewFeedRepository(db *sqlx.DB) *feedRepository {
	return &feedRepository{
		db: db,
	}
}

func (r *feedRepository) Get(ctx context.Context, id int64) (Feed, error) {
	feed := Feed{}
	if err := r.db.GetContext(ctx, &feed, getFeedQuery, id); err != nil {
		return Feed{}, fmt.Errorf("cannot fetch feed: %w", err)
	}
	return feed, nil
}

func (r *feedRepository) ListForCategories(ctx context.Context, categoriesIDs []int64) ([]Feed, error) {
	if len(categoriesIDs) == 0 {
		return []Feed{}, nil
	}

	query, args, err := sqlx.In(selectFeedsForCategoriesQuery, categoriesIDs)
	if err != nil {
		return nil, fmt.Errorf("error preparing query 'in' values: %w", err)
	}
	feeds := []Feed{}
	if err = r.db.SelectContext(ctx, &feeds, r.db.Rebind(query), args...); err != nil {
		return nil, fmt.Errorf("cannot select feeds: %w", err)
	}
	return feeds, nil
}

func (r *feedRepository) List(ctx context.Context) ([]Feed, error) {
	feeds := []Feed{}
	if err := r.db.SelectContext(ctx, &feeds, selectFeedsQuery); err != nil {
		return nil, fmt.Errorf("cannot select feeds: %w", err)
	}
	return feeds, nil
}

func (r *feedRepository) Update(ctx context.Context, feed Feed) error {
	if _, err := r.db.NamedExecContext(ctx, updateFeedQuery, feed); err != nil {
		return fmt.Errorf("cannot update feed: %w", err)
	}
	return nil
}

func (r *feedRepository) Create(ctx context.Context, feed Feed) (int64, error) {
	res, err := r.db.NamedExecContext(ctx, createFeedQuery, feed)
	if err != nil {
		return 0, fmt.Errorf("cannot create feed: %w", err)
	}
	lastInsertedID, err := res.LastInsertId()
	if err != nil {
		return 0, fmt.Errorf("cannot fetch last inserted id: %w", err)
	}
	return lastInsertedID, nil
}
<|endoftext|>