mirror of
https://github.com/restic/restic.git
synced 2026-05-24 11:05:26 +00:00
Update dependencies
This commit is contained in:
+1
-1
@@ -18,4 +18,4 @@ package version
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
// Number contains the semantic version of this SDK.
|
||||
const Number = "v19.1.0"
|
||||
const Number = "v20.1.0"
|
||||
|
||||
+54
-13
@@ -119,7 +119,10 @@ func (f *Future) Done(sender autorest.Sender) (bool, error) {
|
||||
if err := f.pt.updatePollingState(f.pt.provisioningStateApplicable()); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := f.pt.updateHeaders(); err != nil {
|
||||
if err := f.pt.initPollingMethod(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := f.pt.updatePollingMethod(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return f.pt.hasTerminated(), f.pt.pollingError()
|
||||
@@ -264,7 +267,7 @@ type pollingTracker interface {
|
||||
// these methods can differ per tracker
|
||||
|
||||
// checks the response headers and status code to determine the polling mechanism
|
||||
updateHeaders() error
|
||||
updatePollingMethod() error
|
||||
|
||||
// checks the response for tracker-specific error conditions
|
||||
checkForErrors() error
|
||||
@@ -274,6 +277,10 @@ type pollingTracker interface {
|
||||
|
||||
// methods common to all trackers
|
||||
|
||||
// initializes a tracker's polling URL and method, called for each iteration.
|
||||
// these values can be overridden by each polling tracker as required.
|
||||
initPollingMethod() error
|
||||
|
||||
// initializes the tracker's internal state, call this when the tracker is created
|
||||
initializeState() error
|
||||
|
||||
@@ -348,6 +355,10 @@ func (pt *pollingTrackerBase) initializeState() error {
|
||||
case http.StatusOK:
|
||||
if ps := pt.getProvisioningState(); ps != nil {
|
||||
pt.State = *ps
|
||||
if pt.hasFailed() {
|
||||
pt.updateErrorFromResponse()
|
||||
return pt.pollingError()
|
||||
}
|
||||
} else {
|
||||
pt.State = operationSucceeded
|
||||
}
|
||||
@@ -364,8 +375,9 @@ func (pt *pollingTrackerBase) initializeState() error {
|
||||
default:
|
||||
pt.State = operationFailed
|
||||
pt.updateErrorFromResponse()
|
||||
return pt.pollingError()
|
||||
}
|
||||
return nil
|
||||
return pt.initPollingMethod()
|
||||
}
|
||||
|
||||
func (pt pollingTrackerBase) getProvisioningState() *string {
|
||||
@@ -422,6 +434,7 @@ func (pt *pollingTrackerBase) pollForStatus(sender autorest.Sender) error {
|
||||
|
||||
// attempts to unmarshal a ServiceError type from the response body.
|
||||
// if that fails then make a best attempt at creating something meaningful.
|
||||
// NOTE: this assumes that the async operation has failed.
|
||||
func (pt *pollingTrackerBase) updateErrorFromResponse() {
|
||||
var err error
|
||||
if pt.resp.ContentLength != 0 {
|
||||
@@ -431,8 +444,7 @@ func (pt *pollingTrackerBase) updateErrorFromResponse() {
|
||||
re := respErr{}
|
||||
defer pt.resp.Body.Close()
|
||||
var b []byte
|
||||
b, err = ioutil.ReadAll(pt.resp.Body)
|
||||
if err != nil {
|
||||
if b, err = ioutil.ReadAll(pt.resp.Body); err != nil {
|
||||
goto Default
|
||||
}
|
||||
if err = json.Unmarshal(b, &re); err != nil {
|
||||
@@ -445,20 +457,29 @@ func (pt *pollingTrackerBase) updateErrorFromResponse() {
|
||||
goto Default
|
||||
}
|
||||
}
|
||||
if re.ServiceError != nil {
|
||||
// the unmarshaller will ensure re.ServiceError is non-nil
|
||||
// even if there was no content unmarshalled so check the code.
|
||||
if re.ServiceError.Code != "" {
|
||||
pt.Err = re.ServiceError
|
||||
return
|
||||
}
|
||||
}
|
||||
Default:
|
||||
se := &ServiceError{
|
||||
Code: fmt.Sprintf("HTTP status code %v", pt.resp.StatusCode),
|
||||
Message: pt.resp.Status,
|
||||
Code: pt.pollingStatus(),
|
||||
Message: "The async operation failed.",
|
||||
}
|
||||
if err != nil {
|
||||
se.InnerError = make(map[string]interface{})
|
||||
se.InnerError["unmarshalError"] = err.Error()
|
||||
}
|
||||
// stick the response body into the error object in hopes
|
||||
// it contains something useful to help diagnose the failure.
|
||||
if len(pt.rawBody) > 0 {
|
||||
se.AdditionalInfo = []map[string]interface{}{
|
||||
pt.rawBody,
|
||||
}
|
||||
}
|
||||
pt.Err = se
|
||||
}
|
||||
|
||||
@@ -538,13 +559,33 @@ func (pt pollingTrackerBase) baseCheckForErrors() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// default initialization of polling URL/method. each verb tracker will update this as required.
|
||||
func (pt *pollingTrackerBase) initPollingMethod() error {
|
||||
if ao, err := getURLFromAsyncOpHeader(pt.resp); err != nil {
|
||||
return err
|
||||
} else if ao != "" {
|
||||
pt.URI = ao
|
||||
pt.Pm = PollingAsyncOperation
|
||||
return nil
|
||||
}
|
||||
if lh, err := getURLFromLocationHeader(pt.resp); err != nil {
|
||||
return err
|
||||
} else if lh != "" {
|
||||
pt.URI = lh
|
||||
pt.Pm = PollingLocation
|
||||
return nil
|
||||
}
|
||||
// it's ok if we didn't find a polling header, this will be handled elsewhere
|
||||
return nil
|
||||
}
|
||||
|
||||
// DELETE
|
||||
|
||||
type pollingTrackerDelete struct {
|
||||
pollingTrackerBase
|
||||
}
|
||||
|
||||
func (pt *pollingTrackerDelete) updateHeaders() error {
|
||||
func (pt *pollingTrackerDelete) updatePollingMethod() error {
|
||||
// for 201 the Location header is required
|
||||
if pt.resp.StatusCode == http.StatusCreated {
|
||||
if lh, err := getURLFromLocationHeader(pt.resp); err != nil {
|
||||
@@ -600,7 +641,7 @@ type pollingTrackerPatch struct {
|
||||
pollingTrackerBase
|
||||
}
|
||||
|
||||
func (pt *pollingTrackerPatch) updateHeaders() error {
|
||||
func (pt *pollingTrackerPatch) updatePollingMethod() error {
|
||||
// by default we can use the original URL for polling and final GET
|
||||
if pt.URI == "" {
|
||||
pt.URI = pt.resp.Request.URL.String()
|
||||
@@ -658,7 +699,7 @@ type pollingTrackerPost struct {
|
||||
pollingTrackerBase
|
||||
}
|
||||
|
||||
func (pt *pollingTrackerPost) updateHeaders() error {
|
||||
func (pt *pollingTrackerPost) updatePollingMethod() error {
|
||||
// 201 requires Location header
|
||||
if pt.resp.StatusCode == http.StatusCreated {
|
||||
if lh, err := getURLFromLocationHeader(pt.resp); err != nil {
|
||||
@@ -714,7 +755,7 @@ type pollingTrackerPut struct {
|
||||
pollingTrackerBase
|
||||
}
|
||||
|
||||
func (pt *pollingTrackerPut) updateHeaders() error {
|
||||
func (pt *pollingTrackerPut) updatePollingMethod() error {
|
||||
// by default we can use the original URL for polling and final GET
|
||||
if pt.URI == "" {
|
||||
pt.URI = pt.resp.Request.URL.String()
|
||||
@@ -808,7 +849,7 @@ func createPollingTracker(resp *http.Response) (pollingTracker, error) {
|
||||
// this initializes the polling header values, we do this during creation in case the
|
||||
// initial response send us invalid values; this way the API call will return a non-nil
|
||||
// error (not doing this means the error shows up in Future.Done)
|
||||
return pt, pt.updateHeaders()
|
||||
return pt, pt.updatePollingMethod()
|
||||
}
|
||||
|
||||
// gets the polling URL from the Azure-AsyncOperation header.
|
||||
|
||||
+2
-2
@@ -282,7 +282,7 @@ func (fl fileLogger) WriteRequest(req *http.Request, filter Filter) {
|
||||
}
|
||||
fl.mu.Lock()
|
||||
defer fl.mu.Unlock()
|
||||
fmt.Fprintf(fl.logFile, b.String())
|
||||
fmt.Fprint(fl.logFile, b.String())
|
||||
fl.logFile.Sync()
|
||||
}
|
||||
|
||||
@@ -311,7 +311,7 @@ func (fl fileLogger) WriteResponse(resp *http.Response, filter Filter) {
|
||||
}
|
||||
fl.mu.Lock()
|
||||
defer fl.mu.Unlock()
|
||||
fmt.Fprintf(fl.logFile, b.String())
|
||||
fmt.Fprint(fl.logFile, b.String())
|
||||
fl.logFile.Sync()
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ import (
|
||||
)
|
||||
|
||||
// Number contains the semantic version of this SDK.
|
||||
const Number = "v10.15.0"
|
||||
const Number = "v10.15.3"
|
||||
|
||||
var (
|
||||
userAgent = fmt.Sprintf("Go/%s (%s-%s) go-autorest/%s",
|
||||
|
||||
+22
-15
@@ -237,13 +237,18 @@ func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
|
||||
for i, sname := range f.sectionList {
|
||||
sec := f.Section(sname)
|
||||
if len(sec.Comment) > 0 {
|
||||
if sec.Comment[0] != '#' && sec.Comment[0] != ';' {
|
||||
sec.Comment = "; " + sec.Comment
|
||||
} else {
|
||||
sec.Comment = sec.Comment[:1] + " " + strings.TrimSpace(sec.Comment[1:])
|
||||
}
|
||||
if _, err := buf.WriteString(sec.Comment + LineBreak); err != nil {
|
||||
return nil, err
|
||||
// Support multiline comments
|
||||
lines := strings.Split(sec.Comment, LineBreak)
|
||||
for i := range lines {
|
||||
if lines[i][0] != '#' && lines[i][0] != ';' {
|
||||
lines[i] = "; " + lines[i]
|
||||
} else {
|
||||
lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
|
||||
}
|
||||
|
||||
if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,17 +305,19 @@ func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
|
||||
if len(indent) > 0 && sname != DEFAULT_SECTION {
|
||||
buf.WriteString(indent)
|
||||
}
|
||||
if key.Comment[0] != '#' && key.Comment[0] != ';' {
|
||||
key.Comment = "; " + key.Comment
|
||||
} else {
|
||||
key.Comment = key.Comment[:1] + " " + strings.TrimSpace(key.Comment[1:])
|
||||
}
|
||||
|
||||
// Support multiline comments
|
||||
key.Comment = strings.Replace(key.Comment, "\n", "\n; ", -1)
|
||||
lines := strings.Split(key.Comment, LineBreak)
|
||||
for i := range lines {
|
||||
if lines[i][0] != '#' && lines[i][0] != ';' {
|
||||
lines[i] = "; " + lines[i]
|
||||
} else {
|
||||
lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
|
||||
}
|
||||
|
||||
if _, err := buf.WriteString(key.Comment + LineBreak); err != nil {
|
||||
return nil, err
|
||||
if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -34,7 +34,7 @@ const (
|
||||
|
||||
// Maximum allowed depth when recursively substituing variable names.
|
||||
_DEPTH_VALUES = 99
|
||||
_VERSION = "1.38.1"
|
||||
_VERSION = "1.38.2"
|
||||
)
|
||||
|
||||
// Version returns current package version literal.
|
||||
@@ -204,7 +204,7 @@ func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) {
|
||||
return LoadSources(LoadOptions{Insensitive: true}, source, others...)
|
||||
}
|
||||
|
||||
// InsensitiveLoad has exactly same functionality as Load function
|
||||
// ShadowLoad has exactly same functionality as Load function
|
||||
// except it allows have shadow keys.
|
||||
func ShadowLoad(source interface{}, others ...interface{}) (*File, error) {
|
||||
return LoadSources(LoadOptions{AllowShadows: true}, source, others...)
|
||||
|
||||
-3
@@ -1,7 +1,4 @@
|
||||
Go support for Protocol Buffers - Google's data interchange format
|
||||
|
||||
Copyright 2010 The Go Authors. All rights reserved.
|
||||
https://github.com/golang/protobuf
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
|
||||
-18
@@ -37,27 +37,9 @@ package proto
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// RequiredNotSetError is the error returned if Marshal is called with
|
||||
// a protocol buffer struct whose required fields have not
|
||||
// all been initialized. It is also the error returned if Unmarshal is
|
||||
// called with an encoded protocol buffer that does not include all the
|
||||
// required fields.
|
||||
//
|
||||
// When printed, RequiredNotSetError reports the first unset required field in a
|
||||
// message. If the field cannot be precisely determined, it is reported as
|
||||
// "{Unknown}".
|
||||
type RequiredNotSetError struct {
|
||||
field string
|
||||
}
|
||||
|
||||
func (e *RequiredNotSetError) Error() string {
|
||||
return fmt.Sprintf("proto: required field %q not set", e.field)
|
||||
}
|
||||
|
||||
var (
|
||||
// errRepeatedHasNil is the error returned if Marshal is called with
|
||||
// a struct with a repeated field containing a nil element.
|
||||
|
||||
+60
-2
@@ -265,7 +265,6 @@ package proto
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
@@ -274,7 +273,66 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var errInvalidUTF8 = errors.New("proto: invalid UTF-8 string")
|
||||
// RequiredNotSetError is an error type returned by either Marshal or Unmarshal.
|
||||
// Marshal reports this when a required field is not initialized.
|
||||
// Unmarshal reports this when a required field is missing from the wire data.
|
||||
type RequiredNotSetError struct{ field string }
|
||||
|
||||
func (e *RequiredNotSetError) Error() string {
|
||||
if e.field == "" {
|
||||
return fmt.Sprintf("proto: required field not set")
|
||||
}
|
||||
return fmt.Sprintf("proto: required field %q not set", e.field)
|
||||
}
|
||||
func (e *RequiredNotSetError) RequiredNotSet() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type invalidUTF8Error struct{ field string }
|
||||
|
||||
func (e *invalidUTF8Error) Error() string {
|
||||
if e.field == "" {
|
||||
return "proto: invalid UTF-8 detected"
|
||||
}
|
||||
return fmt.Sprintf("proto: field %q contains invalid UTF-8", e.field)
|
||||
}
|
||||
func (e *invalidUTF8Error) InvalidUTF8() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// errInvalidUTF8 is a sentinel error to identify fields with invalid UTF-8.
|
||||
// This error should not be exposed to the external API as such errors should
|
||||
// be recreated with the field information.
|
||||
var errInvalidUTF8 = &invalidUTF8Error{}
|
||||
|
||||
// isNonFatal reports whether the error is either a RequiredNotSet error
|
||||
// or a InvalidUTF8 error.
|
||||
func isNonFatal(err error) bool {
|
||||
if re, ok := err.(interface{ RequiredNotSet() bool }); ok && re.RequiredNotSet() {
|
||||
return true
|
||||
}
|
||||
if re, ok := err.(interface{ InvalidUTF8() bool }); ok && re.InvalidUTF8() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type nonFatal struct{ E error }
|
||||
|
||||
// Merge merges err into nf and reports whether it was successful.
|
||||
// Otherwise it returns false for any fatal non-nil errors.
|
||||
func (nf *nonFatal) Merge(err error) (ok bool) {
|
||||
if err == nil {
|
||||
return true // not an error
|
||||
}
|
||||
if !isNonFatal(err) {
|
||||
return false // fatal error
|
||||
}
|
||||
if nf.E == nil {
|
||||
nf.E = err // store first instance of non-fatal error
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Message is implemented by generated protocol buffer messages.
|
||||
type Message interface {
|
||||
|
||||
+8
-8
@@ -139,7 +139,7 @@ type Properties struct {
|
||||
Repeated bool
|
||||
Packed bool // relevant for repeated primitives only
|
||||
Enum string // set for enum types only
|
||||
proto3 bool // whether this is known to be a proto3 field; set for []byte only
|
||||
proto3 bool // whether this is known to be a proto3 field
|
||||
oneof bool // whether this is a oneof field
|
||||
|
||||
Default string // default value
|
||||
@@ -148,9 +148,9 @@ type Properties struct {
|
||||
stype reflect.Type // set for struct types only
|
||||
sprop *StructProperties // set for struct types only
|
||||
|
||||
mtype reflect.Type // set for map types only
|
||||
mkeyprop *Properties // set for map types only
|
||||
mvalprop *Properties // set for map types only
|
||||
mtype reflect.Type // set for map types only
|
||||
MapKeyProp *Properties // set for map types only
|
||||
MapValProp *Properties // set for map types only
|
||||
}
|
||||
|
||||
// String formats the properties in the protobuf struct field tag style.
|
||||
@@ -275,16 +275,16 @@ func (p *Properties) setFieldProps(typ reflect.Type, f *reflect.StructField, loc
|
||||
|
||||
case reflect.Map:
|
||||
p.mtype = t1
|
||||
p.mkeyprop = &Properties{}
|
||||
p.mkeyprop.init(reflect.PtrTo(p.mtype.Key()), "Key", f.Tag.Get("protobuf_key"), nil, lockGetProp)
|
||||
p.mvalprop = &Properties{}
|
||||
p.MapKeyProp = &Properties{}
|
||||
p.MapKeyProp.init(reflect.PtrTo(p.mtype.Key()), "Key", f.Tag.Get("protobuf_key"), nil, lockGetProp)
|
||||
p.MapValProp = &Properties{}
|
||||
vtype := p.mtype.Elem()
|
||||
if vtype.Kind() != reflect.Ptr && vtype.Kind() != reflect.Slice {
|
||||
// The value type is not a message (*T) or bytes ([]byte),
|
||||
// so we need encoders for the pointer to this type.
|
||||
vtype = reflect.PtrTo(vtype)
|
||||
}
|
||||
p.mvalprop.init(vtype, "Value", f.Tag.Get("protobuf_val"), nil, lockGetProp)
|
||||
p.MapValProp.init(vtype, "Value", f.Tag.Get("protobuf_val"), nil, lockGetProp)
|
||||
}
|
||||
|
||||
if p.stype != nil {
|
||||
|
||||
+138
-52
@@ -231,7 +231,7 @@ func (u *marshalInfo) marshal(b []byte, ptr pointer, deterministic bool) ([]byte
|
||||
return b, err
|
||||
}
|
||||
|
||||
var err, errreq error
|
||||
var err, errLater error
|
||||
// The old marshaler encodes extensions at beginning.
|
||||
if u.extensions.IsValid() {
|
||||
e := ptr.offset(u.extensions).toExtensions()
|
||||
@@ -252,11 +252,13 @@ func (u *marshalInfo) marshal(b []byte, ptr pointer, deterministic bool) ([]byte
|
||||
}
|
||||
}
|
||||
for _, f := range u.fields {
|
||||
if f.required && errreq == nil {
|
||||
if f.required {
|
||||
if ptr.offset(f.field).getPointer().isNil() {
|
||||
// Required field is not set.
|
||||
// We record the error but keep going, to give a complete marshaling.
|
||||
errreq = &RequiredNotSetError{f.name}
|
||||
if errLater == nil {
|
||||
errLater = &RequiredNotSetError{f.name}
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -269,14 +271,21 @@ func (u *marshalInfo) marshal(b []byte, ptr pointer, deterministic bool) ([]byte
|
||||
if err1, ok := err.(*RequiredNotSetError); ok {
|
||||
// Required field in submessage is not set.
|
||||
// We record the error but keep going, to give a complete marshaling.
|
||||
if errreq == nil {
|
||||
errreq = &RequiredNotSetError{f.name + "." + err1.field}
|
||||
if errLater == nil {
|
||||
errLater = &RequiredNotSetError{f.name + "." + err1.field}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err == errRepeatedHasNil {
|
||||
err = errors.New("proto: repeated field " + f.name + " has nil element")
|
||||
}
|
||||
if err == errInvalidUTF8 {
|
||||
if errLater == nil {
|
||||
fullName := revProtoTypes[reflect.PtrTo(u.typ)] + "." + f.name
|
||||
errLater = &invalidUTF8Error{fullName}
|
||||
}
|
||||
continue
|
||||
}
|
||||
return b, err
|
||||
}
|
||||
}
|
||||
@@ -284,7 +293,7 @@ func (u *marshalInfo) marshal(b []byte, ptr pointer, deterministic bool) ([]byte
|
||||
s := *ptr.offset(u.unrecognized).toBytes()
|
||||
b = append(b, s...)
|
||||
}
|
||||
return b, errreq
|
||||
return b, errLater
|
||||
}
|
||||
|
||||
// computeMarshalInfo initializes the marshal info.
|
||||
@@ -530,6 +539,7 @@ func typeMarshaler(t reflect.Type, tags []string, nozero, oneof bool) (sizer, ma
|
||||
|
||||
packed := false
|
||||
proto3 := false
|
||||
validateUTF8 := true
|
||||
for i := 2; i < len(tags); i++ {
|
||||
if tags[i] == "packed" {
|
||||
packed = true
|
||||
@@ -538,6 +548,7 @@ func typeMarshaler(t reflect.Type, tags []string, nozero, oneof bool) (sizer, ma
|
||||
proto3 = true
|
||||
}
|
||||
}
|
||||
validateUTF8 = validateUTF8 && proto3
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Bool:
|
||||
@@ -735,6 +746,18 @@ func typeMarshaler(t reflect.Type, tags []string, nozero, oneof bool) (sizer, ma
|
||||
}
|
||||
return sizeFloat64Value, appendFloat64Value
|
||||
case reflect.String:
|
||||
if validateUTF8 {
|
||||
if pointer {
|
||||
return sizeStringPtr, appendUTF8StringPtr
|
||||
}
|
||||
if slice {
|
||||
return sizeStringSlice, appendUTF8StringSlice
|
||||
}
|
||||
if nozero {
|
||||
return sizeStringValueNoZero, appendUTF8StringValueNoZero
|
||||
}
|
||||
return sizeStringValue, appendUTF8StringValue
|
||||
}
|
||||
if pointer {
|
||||
return sizeStringPtr, appendStringPtr
|
||||
}
|
||||
@@ -1984,9 +2007,6 @@ func appendBoolPackedSlice(b []byte, ptr pointer, wiretag uint64, _ bool) ([]byt
|
||||
}
|
||||
func appendStringValue(b []byte, ptr pointer, wiretag uint64, _ bool) ([]byte, error) {
|
||||
v := *ptr.toString()
|
||||
if !utf8.ValidString(v) {
|
||||
return nil, errInvalidUTF8
|
||||
}
|
||||
b = appendVarint(b, wiretag)
|
||||
b = appendVarint(b, uint64(len(v)))
|
||||
b = append(b, v...)
|
||||
@@ -1997,9 +2017,6 @@ func appendStringValueNoZero(b []byte, ptr pointer, wiretag uint64, _ bool) ([]b
|
||||
if v == "" {
|
||||
return b, nil
|
||||
}
|
||||
if !utf8.ValidString(v) {
|
||||
return nil, errInvalidUTF8
|
||||
}
|
||||
b = appendVarint(b, wiretag)
|
||||
b = appendVarint(b, uint64(len(v)))
|
||||
b = append(b, v...)
|
||||
@@ -2011,24 +2028,83 @@ func appendStringPtr(b []byte, ptr pointer, wiretag uint64, _ bool) ([]byte, err
|
||||
return b, nil
|
||||
}
|
||||
v := *p
|
||||
if !utf8.ValidString(v) {
|
||||
return nil, errInvalidUTF8
|
||||
}
|
||||
b = appendVarint(b, wiretag)
|
||||
b = appendVarint(b, uint64(len(v)))
|
||||
b = append(b, v...)
|
||||
return b, nil
|
||||
}
|
||||
func appendStringSlice(b []byte, ptr pointer, wiretag uint64, _ bool) ([]byte, error) {
|
||||
s := *ptr.toStringSlice()
|
||||
for _, v := range s {
|
||||
b = appendVarint(b, wiretag)
|
||||
b = appendVarint(b, uint64(len(v)))
|
||||
b = append(b, v...)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
func appendUTF8StringValue(b []byte, ptr pointer, wiretag uint64, _ bool) ([]byte, error) {
|
||||
var invalidUTF8 bool
|
||||
v := *ptr.toString()
|
||||
if !utf8.ValidString(v) {
|
||||
invalidUTF8 = true
|
||||
}
|
||||
b = appendVarint(b, wiretag)
|
||||
b = appendVarint(b, uint64(len(v)))
|
||||
b = append(b, v...)
|
||||
if invalidUTF8 {
|
||||
return b, errInvalidUTF8
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
func appendUTF8StringValueNoZero(b []byte, ptr pointer, wiretag uint64, _ bool) ([]byte, error) {
|
||||
var invalidUTF8 bool
|
||||
v := *ptr.toString()
|
||||
if v == "" {
|
||||
return b, nil
|
||||
}
|
||||
if !utf8.ValidString(v) {
|
||||
invalidUTF8 = true
|
||||
}
|
||||
b = appendVarint(b, wiretag)
|
||||
b = appendVarint(b, uint64(len(v)))
|
||||
b = append(b, v...)
|
||||
if invalidUTF8 {
|
||||
return b, errInvalidUTF8
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
func appendUTF8StringPtr(b []byte, ptr pointer, wiretag uint64, _ bool) ([]byte, error) {
|
||||
var invalidUTF8 bool
|
||||
p := *ptr.toStringPtr()
|
||||
if p == nil {
|
||||
return b, nil
|
||||
}
|
||||
v := *p
|
||||
if !utf8.ValidString(v) {
|
||||
invalidUTF8 = true
|
||||
}
|
||||
b = appendVarint(b, wiretag)
|
||||
b = appendVarint(b, uint64(len(v)))
|
||||
b = append(b, v...)
|
||||
if invalidUTF8 {
|
||||
return b, errInvalidUTF8
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
func appendUTF8StringSlice(b []byte, ptr pointer, wiretag uint64, _ bool) ([]byte, error) {
|
||||
var invalidUTF8 bool
|
||||
s := *ptr.toStringSlice()
|
||||
for _, v := range s {
|
||||
if !utf8.ValidString(v) {
|
||||
return nil, errInvalidUTF8
|
||||
invalidUTF8 = true
|
||||
}
|
||||
b = appendVarint(b, wiretag)
|
||||
b = appendVarint(b, uint64(len(v)))
|
||||
b = append(b, v...)
|
||||
}
|
||||
if invalidUTF8 {
|
||||
return b, errInvalidUTF8
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
func appendBytes(b []byte, ptr pointer, wiretag uint64, _ bool) ([]byte, error) {
|
||||
@@ -2107,7 +2183,8 @@ func makeGroupSliceMarshaler(u *marshalInfo) (sizer, marshaler) {
|
||||
},
|
||||
func(b []byte, ptr pointer, wiretag uint64, deterministic bool) ([]byte, error) {
|
||||
s := ptr.getPointerSlice()
|
||||
var err, errreq error
|
||||
var err error
|
||||
var nerr nonFatal
|
||||
for _, v := range s {
|
||||
if v.isNil() {
|
||||
return b, errRepeatedHasNil
|
||||
@@ -2115,22 +2192,14 @@ func makeGroupSliceMarshaler(u *marshalInfo) (sizer, marshaler) {
|
||||
b = appendVarint(b, wiretag) // start group
|
||||
b, err = u.marshal(b, v, deterministic)
|
||||
b = appendVarint(b, wiretag+(WireEndGroup-WireStartGroup)) // end group
|
||||
if err != nil {
|
||||
if _, ok := err.(*RequiredNotSetError); ok {
|
||||
// Required field in submessage is not set.
|
||||
// We record the error but keep going, to give a complete marshaling.
|
||||
if errreq == nil {
|
||||
errreq = err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !nerr.Merge(err) {
|
||||
if err == ErrNil {
|
||||
err = errRepeatedHasNil
|
||||
}
|
||||
return b, err
|
||||
}
|
||||
}
|
||||
return b, errreq
|
||||
return b, nerr.E
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2174,7 +2243,8 @@ func makeMessageSliceMarshaler(u *marshalInfo) (sizer, marshaler) {
|
||||
},
|
||||
func(b []byte, ptr pointer, wiretag uint64, deterministic bool) ([]byte, error) {
|
||||
s := ptr.getPointerSlice()
|
||||
var err, errreq error
|
||||
var err error
|
||||
var nerr nonFatal
|
||||
for _, v := range s {
|
||||
if v.isNil() {
|
||||
return b, errRepeatedHasNil
|
||||
@@ -2184,22 +2254,14 @@ func makeMessageSliceMarshaler(u *marshalInfo) (sizer, marshaler) {
|
||||
b = appendVarint(b, uint64(siz))
|
||||
b, err = u.marshal(b, v, deterministic)
|
||||
|
||||
if err != nil {
|
||||
if _, ok := err.(*RequiredNotSetError); ok {
|
||||
// Required field in submessage is not set.
|
||||
// We record the error but keep going, to give a complete marshaling.
|
||||
if errreq == nil {
|
||||
errreq = err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !nerr.Merge(err) {
|
||||
if err == ErrNil {
|
||||
err = errRepeatedHasNil
|
||||
}
|
||||
return b, err
|
||||
}
|
||||
}
|
||||
return b, errreq
|
||||
return b, nerr.E
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2223,6 +2285,25 @@ func makeMapMarshaler(f *reflect.StructField) (sizer, marshaler) {
|
||||
// value.
|
||||
// Key cannot be pointer-typed.
|
||||
valIsPtr := valType.Kind() == reflect.Ptr
|
||||
|
||||
// If value is a message with nested maps, calling
|
||||
// valSizer in marshal may be quadratic. We should use
|
||||
// cached version in marshal (but not in size).
|
||||
// If value is not message type, we don't have size cache,
|
||||
// but it cannot be nested either. Just use valSizer.
|
||||
valCachedSizer := valSizer
|
||||
if valIsPtr && valType.Elem().Kind() == reflect.Struct {
|
||||
u := getMarshalInfo(valType.Elem())
|
||||
valCachedSizer = func(ptr pointer, tagsize int) int {
|
||||
// Same as message sizer, but use cache.
|
||||
p := ptr.getPointer()
|
||||
if p.isNil() {
|
||||
return 0
|
||||
}
|
||||
siz := u.cachedsize(p)
|
||||
return siz + SizeVarint(uint64(siz)) + tagsize
|
||||
}
|
||||
}
|
||||
return func(ptr pointer, tagsize int) int {
|
||||
m := ptr.asPointerTo(t).Elem() // the map
|
||||
n := 0
|
||||
@@ -2243,24 +2324,26 @@ func makeMapMarshaler(f *reflect.StructField) (sizer, marshaler) {
|
||||
if len(keys) > 1 && deterministic {
|
||||
sort.Sort(mapKeys(keys))
|
||||
}
|
||||
|
||||
var nerr nonFatal
|
||||
for _, k := range keys {
|
||||
ki := k.Interface()
|
||||
vi := m.MapIndex(k).Interface()
|
||||
kaddr := toAddrPointer(&ki, false) // pointer to key
|
||||
vaddr := toAddrPointer(&vi, valIsPtr) // pointer to value
|
||||
b = appendVarint(b, tag)
|
||||
siz := keySizer(kaddr, 1) + valSizer(vaddr, 1) // tag of key = 1 (size=1), tag of val = 2 (size=1)
|
||||
siz := keySizer(kaddr, 1) + valCachedSizer(vaddr, 1) // tag of key = 1 (size=1), tag of val = 2 (size=1)
|
||||
b = appendVarint(b, uint64(siz))
|
||||
b, err = keyMarshaler(b, kaddr, keyWireTag, deterministic)
|
||||
if err != nil {
|
||||
if !nerr.Merge(err) {
|
||||
return b, err
|
||||
}
|
||||
b, err = valMarshaler(b, vaddr, valWireTag, deterministic)
|
||||
if err != nil && err != ErrNil { // allow nil value in map
|
||||
if err != ErrNil && !nerr.Merge(err) { // allow nil value in map
|
||||
return b, err
|
||||
}
|
||||
}
|
||||
return b, nil
|
||||
return b, nerr.E
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2333,6 +2416,7 @@ func (u *marshalInfo) appendExtensions(b []byte, ext *XXX_InternalExtensions, de
|
||||
defer mu.Unlock()
|
||||
|
||||
var err error
|
||||
var nerr nonFatal
|
||||
|
||||
// Fast-path for common cases: zero or one extensions.
|
||||
// Don't bother sorting the keys.
|
||||
@@ -2352,11 +2436,11 @@ func (u *marshalInfo) appendExtensions(b []byte, ext *XXX_InternalExtensions, de
|
||||
v := e.value
|
||||
p := toAddrPointer(&v, ei.isptr)
|
||||
b, err = ei.marshaler(b, p, ei.wiretag, deterministic)
|
||||
if err != nil {
|
||||
if !nerr.Merge(err) {
|
||||
return b, err
|
||||
}
|
||||
}
|
||||
return b, nil
|
||||
return b, nerr.E
|
||||
}
|
||||
|
||||
// Sort the keys to provide a deterministic encoding.
|
||||
@@ -2383,11 +2467,11 @@ func (u *marshalInfo) appendExtensions(b []byte, ext *XXX_InternalExtensions, de
|
||||
v := e.value
|
||||
p := toAddrPointer(&v, ei.isptr)
|
||||
b, err = ei.marshaler(b, p, ei.wiretag, deterministic)
|
||||
if err != nil {
|
||||
if !nerr.Merge(err) {
|
||||
return b, err
|
||||
}
|
||||
}
|
||||
return b, nil
|
||||
return b, nerr.E
|
||||
}
|
||||
|
||||
// message set format is:
|
||||
@@ -2444,6 +2528,7 @@ func (u *marshalInfo) appendMessageSet(b []byte, ext *XXX_InternalExtensions, de
|
||||
defer mu.Unlock()
|
||||
|
||||
var err error
|
||||
var nerr nonFatal
|
||||
|
||||
// Fast-path for common cases: zero or one extensions.
|
||||
// Don't bother sorting the keys.
|
||||
@@ -2470,12 +2555,12 @@ func (u *marshalInfo) appendMessageSet(b []byte, ext *XXX_InternalExtensions, de
|
||||
v := e.value
|
||||
p := toAddrPointer(&v, ei.isptr)
|
||||
b, err = ei.marshaler(b, p, 3<<3|WireBytes, deterministic)
|
||||
if err != nil {
|
||||
if !nerr.Merge(err) {
|
||||
return b, err
|
||||
}
|
||||
b = append(b, 1<<3|WireEndGroup)
|
||||
}
|
||||
return b, nil
|
||||
return b, nerr.E
|
||||
}
|
||||
|
||||
// Sort the keys to provide a deterministic encoding.
|
||||
@@ -2509,11 +2594,11 @@ func (u *marshalInfo) appendMessageSet(b []byte, ext *XXX_InternalExtensions, de
|
||||
p := toAddrPointer(&v, ei.isptr)
|
||||
b, err = ei.marshaler(b, p, 3<<3|WireBytes, deterministic)
|
||||
b = append(b, 1<<3|WireEndGroup)
|
||||
if err != nil {
|
||||
if !nerr.Merge(err) {
|
||||
return b, err
|
||||
}
|
||||
}
|
||||
return b, nil
|
||||
return b, nerr.E
|
||||
}
|
||||
|
||||
// sizeV1Extensions computes the size of encoded data for a V1-API extension field.
|
||||
@@ -2556,6 +2641,7 @@ func (u *marshalInfo) appendV1Extensions(b []byte, m map[int32]Extension, determ
|
||||
sort.Ints(keys)
|
||||
|
||||
var err error
|
||||
var nerr nonFatal
|
||||
for _, k := range keys {
|
||||
e := m[int32(k)]
|
||||
if e.value == nil || e.desc == nil {
|
||||
@@ -2572,11 +2658,11 @@ func (u *marshalInfo) appendV1Extensions(b []byte, m map[int32]Extension, determ
|
||||
v := e.value
|
||||
p := toAddrPointer(&v, ei.isptr)
|
||||
b, err = ei.marshaler(b, p, ei.wiretag, deterministic)
|
||||
if err != nil {
|
||||
if !nerr.Merge(err) {
|
||||
return b, err
|
||||
}
|
||||
}
|
||||
return b, nil
|
||||
return b, nerr.E
|
||||
}
|
||||
|
||||
// newMarshaler is the interface representing objects that can marshal themselves.
|
||||
|
||||
+115
-31
@@ -97,6 +97,8 @@ type unmarshalFieldInfo struct {
|
||||
|
||||
// if a required field, contains a single set bit at this field's index in the required field list.
|
||||
reqMask uint64
|
||||
|
||||
name string // name of the field, for error reporting
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -136,8 +138,8 @@ func (u *unmarshalInfo) unmarshal(m pointer, b []byte) error {
|
||||
if u.isMessageSet {
|
||||
return UnmarshalMessageSet(b, m.offset(u.extensions).toExtensions())
|
||||
}
|
||||
var reqMask uint64 // bitmask of required fields we've seen.
|
||||
var rnse *RequiredNotSetError // an instance of a RequiredNotSetError returned by a submessage.
|
||||
var reqMask uint64 // bitmask of required fields we've seen.
|
||||
var errLater error
|
||||
for len(b) > 0 {
|
||||
// Read tag and wire type.
|
||||
// Special case 1 and 2 byte varints.
|
||||
@@ -176,11 +178,20 @@ func (u *unmarshalInfo) unmarshal(m pointer, b []byte) error {
|
||||
if r, ok := err.(*RequiredNotSetError); ok {
|
||||
// Remember this error, but keep parsing. We need to produce
|
||||
// a full parse even if a required field is missing.
|
||||
rnse = r
|
||||
if errLater == nil {
|
||||
errLater = r
|
||||
}
|
||||
reqMask |= f.reqMask
|
||||
continue
|
||||
}
|
||||
if err != errInternalBadWireType {
|
||||
if err == errInvalidUTF8 {
|
||||
if errLater == nil {
|
||||
fullName := revProtoTypes[reflect.PtrTo(u.typ)] + "." + f.name
|
||||
errLater = &invalidUTF8Error{fullName}
|
||||
}
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
// Fragments with bad wire type are treated as unknown fields.
|
||||
@@ -239,20 +250,16 @@ func (u *unmarshalInfo) unmarshal(m pointer, b []byte) error {
|
||||
emap[int32(tag)] = e
|
||||
}
|
||||
}
|
||||
if rnse != nil {
|
||||
// A required field of a submessage/group is missing. Return that error.
|
||||
return rnse
|
||||
}
|
||||
if reqMask != u.reqMask {
|
||||
if reqMask != u.reqMask && errLater == nil {
|
||||
// A required field of this message is missing.
|
||||
for _, n := range u.reqFields {
|
||||
if reqMask&1 == 0 {
|
||||
return &RequiredNotSetError{n}
|
||||
errLater = &RequiredNotSetError{n}
|
||||
}
|
||||
reqMask >>= 1
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return errLater
|
||||
}
|
||||
|
||||
// computeUnmarshalInfo fills in u with information for use
|
||||
@@ -351,7 +358,7 @@ func (u *unmarshalInfo) computeUnmarshalInfo() {
|
||||
}
|
||||
|
||||
// Store the info in the correct slot in the message.
|
||||
u.setTag(tag, toField(&f), unmarshal, reqMask)
|
||||
u.setTag(tag, toField(&f), unmarshal, reqMask, name)
|
||||
}
|
||||
|
||||
// Find any types associated with oneof fields.
|
||||
@@ -366,10 +373,17 @@ func (u *unmarshalInfo) computeUnmarshalInfo() {
|
||||
|
||||
f := typ.Field(0) // oneof implementers have one field
|
||||
baseUnmarshal := fieldUnmarshaler(&f)
|
||||
tagstr := strings.Split(f.Tag.Get("protobuf"), ",")[1]
|
||||
tag, err := strconv.Atoi(tagstr)
|
||||
tags := strings.Split(f.Tag.Get("protobuf"), ",")
|
||||
fieldNum, err := strconv.Atoi(tags[1])
|
||||
if err != nil {
|
||||
panic("protobuf tag field not an integer: " + tagstr)
|
||||
panic("protobuf tag field not an integer: " + tags[1])
|
||||
}
|
||||
var name string
|
||||
for _, tag := range tags {
|
||||
if strings.HasPrefix(tag, "name=") {
|
||||
name = strings.TrimPrefix(tag, "name=")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Find the oneof field that this struct implements.
|
||||
@@ -380,7 +394,7 @@ func (u *unmarshalInfo) computeUnmarshalInfo() {
|
||||
// That lets us know where this struct should be stored
|
||||
// when we encounter it during unmarshaling.
|
||||
unmarshal := makeUnmarshalOneof(typ, of.ityp, baseUnmarshal)
|
||||
u.setTag(tag, of.field, unmarshal, 0)
|
||||
u.setTag(fieldNum, of.field, unmarshal, 0, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -401,7 +415,7 @@ func (u *unmarshalInfo) computeUnmarshalInfo() {
|
||||
// [0 0] is [tag=0/wiretype=varint varint-encoded-0].
|
||||
u.setTag(0, zeroField, func(b []byte, f pointer, w int) ([]byte, error) {
|
||||
return nil, fmt.Errorf("proto: %s: illegal tag 0 (wire type %d)", t, w)
|
||||
}, 0)
|
||||
}, 0, "")
|
||||
|
||||
// Set mask for required field check.
|
||||
u.reqMask = uint64(1)<<uint(len(u.reqFields)) - 1
|
||||
@@ -413,8 +427,9 @@ func (u *unmarshalInfo) computeUnmarshalInfo() {
|
||||
// tag = tag # for field
|
||||
// field/unmarshal = unmarshal info for that field.
|
||||
// reqMask = if required, bitmask for field position in required field list. 0 otherwise.
|
||||
func (u *unmarshalInfo) setTag(tag int, field field, unmarshal unmarshaler, reqMask uint64) {
|
||||
i := unmarshalFieldInfo{field: field, unmarshal: unmarshal, reqMask: reqMask}
|
||||
// name = short name of the field.
|
||||
func (u *unmarshalInfo) setTag(tag int, field field, unmarshal unmarshaler, reqMask uint64, name string) {
|
||||
i := unmarshalFieldInfo{field: field, unmarshal: unmarshal, reqMask: reqMask, name: name}
|
||||
n := u.typ.NumField()
|
||||
if tag >= 0 && (tag < 16 || tag < 2*n) { // TODO: what are the right numbers here?
|
||||
for len(u.dense) <= tag {
|
||||
@@ -442,11 +457,17 @@ func typeUnmarshaler(t reflect.Type, tags string) unmarshaler {
|
||||
tagArray := strings.Split(tags, ",")
|
||||
encoding := tagArray[0]
|
||||
name := "unknown"
|
||||
proto3 := false
|
||||
validateUTF8 := true
|
||||
for _, tag := range tagArray[3:] {
|
||||
if strings.HasPrefix(tag, "name=") {
|
||||
name = tag[5:]
|
||||
}
|
||||
if tag == "proto3" {
|
||||
proto3 = true
|
||||
}
|
||||
}
|
||||
validateUTF8 = validateUTF8 && proto3
|
||||
|
||||
// Figure out packaging (pointer, slice, or both)
|
||||
slice := false
|
||||
@@ -594,6 +615,15 @@ func typeUnmarshaler(t reflect.Type, tags string) unmarshaler {
|
||||
}
|
||||
return unmarshalBytesValue
|
||||
case reflect.String:
|
||||
if validateUTF8 {
|
||||
if pointer {
|
||||
return unmarshalUTF8StringPtr
|
||||
}
|
||||
if slice {
|
||||
return unmarshalUTF8StringSlice
|
||||
}
|
||||
return unmarshalUTF8StringValue
|
||||
}
|
||||
if pointer {
|
||||
return unmarshalStringPtr
|
||||
}
|
||||
@@ -1448,9 +1478,6 @@ func unmarshalStringValue(b []byte, f pointer, w int) ([]byte, error) {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
v := string(b[:x])
|
||||
if !utf8.ValidString(v) {
|
||||
return nil, errInvalidUTF8
|
||||
}
|
||||
*f.toString() = v
|
||||
return b[x:], nil
|
||||
}
|
||||
@@ -1468,9 +1495,6 @@ func unmarshalStringPtr(b []byte, f pointer, w int) ([]byte, error) {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
v := string(b[:x])
|
||||
if !utf8.ValidString(v) {
|
||||
return nil, errInvalidUTF8
|
||||
}
|
||||
*f.toStringPtr() = &v
|
||||
return b[x:], nil
|
||||
}
|
||||
@@ -1488,14 +1512,72 @@ func unmarshalStringSlice(b []byte, f pointer, w int) ([]byte, error) {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
v := string(b[:x])
|
||||
if !utf8.ValidString(v) {
|
||||
return nil, errInvalidUTF8
|
||||
}
|
||||
s := f.toStringSlice()
|
||||
*s = append(*s, v)
|
||||
return b[x:], nil
|
||||
}
|
||||
|
||||
func unmarshalUTF8StringValue(b []byte, f pointer, w int) ([]byte, error) {
|
||||
if w != WireBytes {
|
||||
return b, errInternalBadWireType
|
||||
}
|
||||
x, n := decodeVarint(b)
|
||||
if n == 0 {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
b = b[n:]
|
||||
if x > uint64(len(b)) {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
v := string(b[:x])
|
||||
*f.toString() = v
|
||||
if !utf8.ValidString(v) {
|
||||
return b[x:], errInvalidUTF8
|
||||
}
|
||||
return b[x:], nil
|
||||
}
|
||||
|
||||
func unmarshalUTF8StringPtr(b []byte, f pointer, w int) ([]byte, error) {
|
||||
if w != WireBytes {
|
||||
return b, errInternalBadWireType
|
||||
}
|
||||
x, n := decodeVarint(b)
|
||||
if n == 0 {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
b = b[n:]
|
||||
if x > uint64(len(b)) {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
v := string(b[:x])
|
||||
*f.toStringPtr() = &v
|
||||
if !utf8.ValidString(v) {
|
||||
return b[x:], errInvalidUTF8
|
||||
}
|
||||
return b[x:], nil
|
||||
}
|
||||
|
||||
func unmarshalUTF8StringSlice(b []byte, f pointer, w int) ([]byte, error) {
|
||||
if w != WireBytes {
|
||||
return b, errInternalBadWireType
|
||||
}
|
||||
x, n := decodeVarint(b)
|
||||
if n == 0 {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
b = b[n:]
|
||||
if x > uint64(len(b)) {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
v := string(b[:x])
|
||||
s := f.toStringSlice()
|
||||
*s = append(*s, v)
|
||||
if !utf8.ValidString(v) {
|
||||
return b[x:], errInvalidUTF8
|
||||
}
|
||||
return b[x:], nil
|
||||
}
|
||||
|
||||
var emptyBuf [0]byte
|
||||
|
||||
func unmarshalBytesValue(b []byte, f pointer, w int) ([]byte, error) {
|
||||
@@ -1674,6 +1756,7 @@ func makeUnmarshalMap(f *reflect.StructField) unmarshaler {
|
||||
// Maps will be somewhat slow. Oh well.
|
||||
|
||||
// Read key and value from data.
|
||||
var nerr nonFatal
|
||||
k := reflect.New(kt)
|
||||
v := reflect.New(vt)
|
||||
for len(b) > 0 {
|
||||
@@ -1694,7 +1777,7 @@ func makeUnmarshalMap(f *reflect.StructField) unmarshaler {
|
||||
err = errInternalBadWireType // skip unknown tag
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
if nerr.Merge(err) {
|
||||
continue
|
||||
}
|
||||
if err != errInternalBadWireType {
|
||||
@@ -1717,7 +1800,7 @@ func makeUnmarshalMap(f *reflect.StructField) unmarshaler {
|
||||
// Insert into map.
|
||||
m.SetMapIndex(k.Elem(), v.Elem())
|
||||
|
||||
return r, nil
|
||||
return r, nerr.E
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1743,15 +1826,16 @@ func makeUnmarshalOneof(typ, ityp reflect.Type, unmarshal unmarshaler) unmarshal
|
||||
// Unmarshal data into holder.
|
||||
// We unmarshal into the first field of the holder object.
|
||||
var err error
|
||||
var nerr nonFatal
|
||||
b, err = unmarshal(b, valToPointer(v).offset(field0), w)
|
||||
if err != nil {
|
||||
if !nerr.Merge(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Write pointer to holder into target field.
|
||||
f.asPointerTo(ityp).Elem().Set(v)
|
||||
|
||||
return b, nil
|
||||
return b, nerr.E
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -353,7 +353,7 @@ func (tm *TextMarshaler) writeStruct(w *textWriter, sv reflect.Value) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := tm.writeAny(w, key, props.mkeyprop); err != nil {
|
||||
if err := tm.writeAny(w, key, props.MapKeyProp); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
@@ -370,7 +370,7 @@ func (tm *TextMarshaler) writeStruct(w *textWriter, sv reflect.Value) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := tm.writeAny(w, val, props.mvalprop); err != nil {
|
||||
if err := tm.writeAny(w, val, props.MapValProp); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
|
||||
+3
-3
@@ -630,17 +630,17 @@ func (p *textParser) readStruct(sv reflect.Value, terminator string) error {
|
||||
if err := p.consumeToken(":"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.readAny(key, props.mkeyprop); err != nil {
|
||||
if err := p.readAny(key, props.MapKeyProp); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.consumeOptionalSeparator(); err != nil {
|
||||
return err
|
||||
}
|
||||
case "value":
|
||||
if err := p.checkForColon(props.mvalprop, dst.Type().Elem()); err != nil {
|
||||
if err := p.checkForColon(props.MapValProp, dst.Type().Elem()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.readAny(val, props.mvalprop); err != nil {
|
||||
if err := p.readAny(val, props.MapValProp); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.consumeOptionalSeparator(); err != nil {
|
||||
|
||||
+4
@@ -2,6 +2,10 @@ language: go
|
||||
go:
|
||||
- tip
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
before_install:
|
||||
- go get github.com/mattn/goveralls
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
package isatty
|
||||
|
||||
// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
|
||||
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
|
||||
// terminal. This is also always false on this environment.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
return false
|
||||
|
||||
+4
@@ -183,6 +183,10 @@ The full API Reference is available here.
|
||||
* [getbucketpolicy.go](https://github.com/minio/minio-go/blob/master/examples/s3/getbucketpolicy.go)
|
||||
* [listbucketpolicies.go](https://github.com/minio/minio-go/blob/master/examples/s3/listbucketpolicies.go)
|
||||
|
||||
### Full Examples : Bucket lifecycle Operations
|
||||
* [setbucketlifecycle.go](https://github.com/minio/minio-go/blob/master/examples/s3/setbucketlifecycle.go)
|
||||
* [getbucketlifecycle.go](https://github.com/minio/minio-go/blob/master/examples/s3/getbucketlifecycle.go)
|
||||
|
||||
### Full Examples : Bucket notification Operations
|
||||
* [setbucketnotification.go](https://github.com/minio/minio-go/blob/master/examples/s3/setbucketnotification.go)
|
||||
* [getbucketnotification.go](https://github.com/minio/minio-go/blob/master/examples/s3/getbucketnotification.go)
|
||||
|
||||
+18
-15
@@ -101,7 +101,11 @@ func (d *DestinationInfo) getUserMetaHeadersMap(withCopyDirectiveHeader bool) ma
|
||||
r["x-amz-metadata-directive"] = "REPLACE"
|
||||
}
|
||||
for k, v := range d.userMetadata {
|
||||
r["x-amz-meta-"+k] = v
|
||||
if isAmzHeader(k) || isStandardHeader(k) || isStorageClassHeader(k) {
|
||||
r[k] = v
|
||||
} else {
|
||||
r["x-amz-meta-"+k] = v
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
@@ -373,15 +377,6 @@ func (c Client) ComposeObjectWithProgress(dst DestinationInfo, srcs []SourceInfo
|
||||
fmt.Sprintf("Client side encryption is used in source object %s/%s", src.bucket, src.object))
|
||||
}
|
||||
|
||||
// Since we did a HEAD to get size, we use the ETag
|
||||
// value to make sure the object has not changed by
|
||||
// the time we perform the copy. This is done, only if
|
||||
// the user has not set their own ETag match
|
||||
// condition.
|
||||
if src.Headers.Get("x-amz-copy-source-if-match") == "" {
|
||||
src.SetMatchETagCond(etag)
|
||||
}
|
||||
|
||||
// Check if a segment is specified, and if so, is the
|
||||
// segment within object bounds?
|
||||
if src.start != -1 {
|
||||
@@ -429,7 +424,15 @@ func (c Client) ComposeObjectWithProgress(dst DestinationInfo, srcs []SourceInfo
|
||||
|
||||
// Now, handle multipart-copy cases.
|
||||
|
||||
// 1. Initiate a new multipart upload.
|
||||
// 1. Ensure that the object has not been changed while
|
||||
// we are copying data.
|
||||
for _, src := range srcs {
|
||||
if src.Headers.Get("x-amz-copy-source-if-match") == "" {
|
||||
src.SetMatchETagCond(etag)
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Initiate a new multipart upload.
|
||||
|
||||
// Set user-metadata on the destination object. If no
|
||||
// user-metadata is specified, and there is only one source,
|
||||
@@ -449,13 +452,13 @@ func (c Client) ComposeObjectWithProgress(dst DestinationInfo, srcs []SourceInfo
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. Perform copy part uploads
|
||||
// 3. Perform copy part uploads
|
||||
objParts := []CompletePart{}
|
||||
partIndex := 1
|
||||
for i, src := range srcs {
|
||||
h := src.Headers
|
||||
if src.encryption != nil {
|
||||
src.encryption.Marshal(h)
|
||||
encrypt.SSECopy(src.encryption).Marshal(h)
|
||||
}
|
||||
// Add destination encryption headers
|
||||
if dst.encryption != nil {
|
||||
@@ -480,14 +483,14 @@ func (c Client) ComposeObjectWithProgress(dst DestinationInfo, srcs []SourceInfo
|
||||
return err
|
||||
}
|
||||
if progress != nil {
|
||||
io.CopyN(ioutil.Discard, progress, start+end-1)
|
||||
io.CopyN(ioutil.Discard, progress, end-start+1)
|
||||
}
|
||||
objParts = append(objParts, complPart)
|
||||
partIndex++
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Make final complete-multipart request.
|
||||
// 4. Make final complete-multipart request.
|
||||
_, err = c.completeMultipartUpload(ctx, dst.bucket, dst.object, uploadID,
|
||||
completeMultipartUpload{Parts: objParts})
|
||||
if err != nil {
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage
|
||||
* Copyright 2015-2017 Minio, Inc.
|
||||
*
|
||||
* 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 minio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/minio/minio-go/pkg/s3utils"
|
||||
)
|
||||
|
||||
// GetBucketLifecycle - get bucket lifecycle.
|
||||
func (c Client) GetBucketLifecycle(bucketName string) (string, error) {
|
||||
// Input validation.
|
||||
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
|
||||
return "", err
|
||||
}
|
||||
bucketLifecycle, err := c.getBucketLifecycle(bucketName)
|
||||
if err != nil {
|
||||
errResponse := ToErrorResponse(err)
|
||||
if errResponse.Code == "NoSuchLifecycleConfiguration" {
|
||||
return "", nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
return bucketLifecycle, nil
|
||||
}
|
||||
|
||||
// Request server for current bucket lifecycle.
|
||||
func (c Client) getBucketLifecycle(bucketName string) (string, error) {
|
||||
// Get resources properly escaped and lined up before
|
||||
// using them in http request.
|
||||
urlValues := make(url.Values)
|
||||
urlValues.Set("lifecycle", "")
|
||||
|
||||
// Execute GET on bucket to get lifecycle.
|
||||
resp, err := c.executeMethod(context.Background(), "GET", requestMetadata{
|
||||
bucketName: bucketName,
|
||||
queryValues: urlValues,
|
||||
})
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp != nil {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", httpRespToErrorResponse(resp, bucketName, "")
|
||||
}
|
||||
}
|
||||
|
||||
bucketLifecycleBuf, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
lifecycle := string(bucketLifecycleBuf)
|
||||
return lifecycle, err
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Minio Go Library for Amazon S3 Compatible Cloud Storage
|
||||
* Copyright 2018 Minio, Inc.
|
||||
*
|
||||
* 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 minio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type accessControlPolicy struct {
|
||||
Owner struct {
|
||||
ID string `xml:"ID"`
|
||||
DisplayName string `xml:"DisplayName"`
|
||||
} `xml:"Owner"`
|
||||
AccessControlList struct {
|
||||
Grant []struct {
|
||||
Grantee struct {
|
||||
ID string `xml:"ID"`
|
||||
DisplayName string `xml:"DisplayName"`
|
||||
URI string `xml:"URI"`
|
||||
} `xml:"Grantee"`
|
||||
Permission string `xml:"Permission"`
|
||||
} `xml:"Grant"`
|
||||
} `xml:"AccessControlList"`
|
||||
}
|
||||
|
||||
//GetObjectACL get object ACLs
|
||||
func (c Client) GetObjectACL(bucketName, objectName string) (*ObjectInfo, error) {
|
||||
|
||||
resp, err := c.executeMethod(context.Background(), "GET", requestMetadata{
|
||||
bucketName: bucketName,
|
||||
objectName: objectName,
|
||||
queryValues: url.Values{
|
||||
"acl": []string{""},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer closeResponse(resp)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, httpRespToErrorResponse(resp, bucketName, objectName)
|
||||
}
|
||||
|
||||
res := &accessControlPolicy{}
|
||||
|
||||
if err := xmlDecoder(resp.Body, res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objInfo, err := c.statObject(context.Background(), bucketName, objectName, StatObjectOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cannedACL := getCannedACL(res)
|
||||
if cannedACL != "" {
|
||||
objInfo.Metadata.Add("X-Amz-Acl", cannedACL)
|
||||
return &objInfo, nil
|
||||
}
|
||||
|
||||
grantACL := getAmzGrantACL(res)
|
||||
for k, v := range grantACL {
|
||||
objInfo.Metadata[k] = v
|
||||
}
|
||||
|
||||
return &objInfo, nil
|
||||
}
|
||||
|
||||
func getCannedACL(aCPolicy *accessControlPolicy) string {
|
||||
grants := aCPolicy.AccessControlList.Grant
|
||||
|
||||
switch {
|
||||
case len(grants) == 1:
|
||||
if grants[0].Grantee.URI == "" && grants[0].Permission == "FULL_CONTROL" {
|
||||
return "private"
|
||||
}
|
||||
case len(grants) == 2:
|
||||
for _, g := range grants {
|
||||
if g.Grantee.URI == "http://acs.amazonaws.com/groups/global/AuthenticatedUsers" && g.Permission == "READ" {
|
||||
return "authenticated-read"
|
||||
}
|
||||
if g.Grantee.URI == "http://acs.amazonaws.com/groups/global/AllUsers" && g.Permission == "READ" {
|
||||
return "public-read"
|
||||
}
|
||||
if g.Permission == "READ" && g.Grantee.ID == aCPolicy.Owner.ID {
|
||||
return "bucket-owner-read"
|
||||
}
|
||||
}
|
||||
case len(grants) == 3:
|
||||
for _, g := range grants {
|
||||
if g.Grantee.URI == "http://acs.amazonaws.com/groups/global/AllUsers" && g.Permission == "WRITE" {
|
||||
return "public-read-write"
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getAmzGrantACL(aCPolicy *accessControlPolicy) map[string][]string {
|
||||
grants := aCPolicy.AccessControlList.Grant
|
||||
res := map[string][]string{}
|
||||
|
||||
for _, g := range grants {
|
||||
switch {
|
||||
case g.Permission == "READ":
|
||||
res["X-Amz-Grant-Read"] = append(res["X-Amz-Grant-Read"], "id="+g.Grantee.ID)
|
||||
case g.Permission == "WRITE":
|
||||
res["X-Amz-Grant-Write"] = append(res["X-Amz-Grant-Write"], "id="+g.Grantee.ID)
|
||||
case g.Permission == "READ_ACP":
|
||||
res["X-Amz-Grant-Read-Acp"] = append(res["X-Amz-Grant-Read-Acp"], "id="+g.Grantee.ID)
|
||||
case g.Permission == "WRITE_ACP":
|
||||
res["X-Amz-Grant-Write-Acp"] = append(res["X-Amz-Grant-Write-Acp"], "id="+g.Grantee.ID)
|
||||
case g.Permission == "FULL_CONTROL":
|
||||
res["X-Amz-Grant-Full-Control"] = append(res["X-Amz-Grant-Full-Control"], "id="+g.Grantee.ID)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
+1
-1
@@ -44,7 +44,7 @@ func (o GetObjectOptions) Header() http.Header {
|
||||
for k, v := range o.headers {
|
||||
headers.Set(k, v)
|
||||
}
|
||||
if o.ServerSideEncryption != nil && o.ServerSideEncryption.Type() != encrypt.S3 {
|
||||
if o.ServerSideEncryption != nil && o.ServerSideEncryption.Type() == encrypt.SSEC {
|
||||
o.ServerSideEncryption.Marshal(headers)
|
||||
}
|
||||
return headers
|
||||
|
||||
+81
@@ -178,6 +178,87 @@ func (c Client) removeBucketPolicy(bucketName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetBucketLifecycle set the lifecycle on an existing bucket.
|
||||
func (c Client) SetBucketLifecycle(bucketName, lifecycle string) error {
|
||||
// Input validation.
|
||||
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If lifecycle is empty then delete it.
|
||||
if lifecycle == "" {
|
||||
return c.removeBucketLifecycle(bucketName)
|
||||
}
|
||||
|
||||
// Save the updated lifecycle.
|
||||
return c.putBucketLifecycle(bucketName, lifecycle)
|
||||
}
|
||||
|
||||
// Saves a new bucket lifecycle.
|
||||
func (c Client) putBucketLifecycle(bucketName, lifecycle string) error {
|
||||
// Input validation.
|
||||
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get resources properly escaped and lined up before
|
||||
// using them in http request.
|
||||
urlValues := make(url.Values)
|
||||
urlValues.Set("lifecycle", "")
|
||||
|
||||
// Content-length is mandatory for put lifecycle request
|
||||
lifecycleReader := strings.NewReader(lifecycle)
|
||||
b, err := ioutil.ReadAll(lifecycleReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqMetadata := requestMetadata{
|
||||
bucketName: bucketName,
|
||||
queryValues: urlValues,
|
||||
contentBody: lifecycleReader,
|
||||
contentLength: int64(len(b)),
|
||||
contentMD5Base64: sumMD5Base64(b),
|
||||
}
|
||||
|
||||
// Execute PUT to upload a new bucket lifecycle.
|
||||
resp, err := c.executeMethod(context.Background(), "PUT", reqMetadata)
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp != nil {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return httpRespToErrorResponse(resp, bucketName, "")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove lifecycle from a bucket.
|
||||
func (c Client) removeBucketLifecycle(bucketName string) error {
|
||||
// Input validation.
|
||||
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
|
||||
return err
|
||||
}
|
||||
// Get resources properly escaped and lined up before
|
||||
// using them in http request.
|
||||
urlValues := make(url.Values)
|
||||
urlValues.Set("lifecycle", "")
|
||||
|
||||
// Execute DELETE on objectName.
|
||||
resp, err := c.executeMethod(context.Background(), "DELETE", requestMetadata{
|
||||
bucketName: bucketName,
|
||||
queryValues: urlValues,
|
||||
contentSHA256Hex: emptySHA256Hex,
|
||||
})
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetBucketNotification saves a new bucket notification.
|
||||
func (c Client) SetBucketNotification(bucketName string, bucketNotification BucketNotification) error {
|
||||
// Input validation.
|
||||
|
||||
+1
-1
@@ -259,7 +259,7 @@ func (c Client) uploadPart(ctx context.Context, bucketName, objectName, uploadID
|
||||
|
||||
// Set encryption headers, if any.
|
||||
customHeader := make(http.Header)
|
||||
if sse != nil && sse.Type() != encrypt.S3 && sse.Type() != encrypt.KMS {
|
||||
if sse != nil {
|
||||
sse.Marshal(customHeader)
|
||||
}
|
||||
|
||||
|
||||
+6
-19
@@ -99,7 +99,7 @@ type Options struct {
|
||||
// Global constants.
|
||||
const (
|
||||
libraryName = "minio-go"
|
||||
libraryVersion = "v6.0.5"
|
||||
libraryVersion = "v6.0.6"
|
||||
)
|
||||
|
||||
// User Agent should always following the below style.
|
||||
@@ -455,22 +455,9 @@ func (c Client) dumpHTTP(req *http.Request, resp *http.Response) error {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// WORKAROUND for https://github.com/golang/go/issues/13942.
|
||||
// httputil.DumpResponse does not print response headers for
|
||||
// all successful calls which have response ContentLength set
|
||||
// to zero. Keep this workaround until the above bug is fixed.
|
||||
if resp.ContentLength == 0 {
|
||||
var buffer bytes.Buffer
|
||||
if err = resp.Header.Write(&buffer); err != nil {
|
||||
return err
|
||||
}
|
||||
respTrace = buffer.Bytes()
|
||||
respTrace = append(respTrace, []byte("\r\n")...)
|
||||
} else {
|
||||
respTrace, err = httputil.DumpResponse(resp, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
respTrace, err = httputil.DumpResponse(resp, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -599,8 +586,8 @@ func (c Client) executeMethod(ctx context.Context, method string, metadata reque
|
||||
// Initiate the request.
|
||||
res, err = c.do(req)
|
||||
if err != nil {
|
||||
// For supported network errors verify.
|
||||
if isNetErrorRetryable(err) {
|
||||
// For supported http requests errors verify.
|
||||
if isHTTPReqErrorRetryable(err) {
|
||||
continue // Retry.
|
||||
}
|
||||
// For other errors, return here no need to retry.
|
||||
|
||||
+6
-3
@@ -21,6 +21,8 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio-go/pkg/encrypt"
|
||||
)
|
||||
|
||||
// Core - Inherits Client and adds new methods to expose the low level S3 APIs.
|
||||
@@ -68,7 +70,7 @@ func (c Core) CopyObjectPart(srcBucket, srcObject, destBucket, destObject string
|
||||
}
|
||||
|
||||
// PutObject - Upload object. Uploads using single PUT call.
|
||||
func (c Core) PutObject(bucket, object string, data io.Reader, size int64, md5Base64, sha256Hex string, metadata map[string]string) (ObjectInfo, error) {
|
||||
func (c Core) PutObject(bucket, object string, data io.Reader, size int64, md5Base64, sha256Hex string, metadata map[string]string, sse encrypt.ServerSide) (ObjectInfo, error) {
|
||||
opts := PutObjectOptions{}
|
||||
m := make(map[string]string)
|
||||
for k, v := range metadata {
|
||||
@@ -89,6 +91,7 @@ func (c Core) PutObject(bucket, object string, data io.Reader, size int64, md5Ba
|
||||
}
|
||||
}
|
||||
opts.UserMetadata = m
|
||||
opts.ServerSideEncryption = sse
|
||||
return c.putObjectDo(context.Background(), bucket, object, data, md5Base64, sha256Hex, size, opts)
|
||||
}
|
||||
|
||||
@@ -104,8 +107,8 @@ func (c Core) ListMultipartUploads(bucket, prefix, keyMarker, uploadIDMarker, de
|
||||
}
|
||||
|
||||
// PutObjectPart - Upload an object part.
|
||||
func (c Core) PutObjectPart(bucket, object, uploadID string, partID int, data io.Reader, size int64, md5Base64, sha256Hex string) (ObjectPart, error) {
|
||||
return c.uploadPart(context.Background(), bucket, object, uploadID, data, partID, md5Base64, sha256Hex, size, nil)
|
||||
func (c Core) PutObjectPart(bucket, object, uploadID string, partID int, data io.Reader, size int64, md5Base64, sha256Hex string, sse encrypt.ServerSide) (ObjectPart, error) {
|
||||
return c.uploadPart(context.Background(), bucket, object, uploadID, data, partID, md5Base64, sha256Hex, size, sse)
|
||||
}
|
||||
|
||||
// ListObjectParts - List uploaded parts of an incomplete upload.x
|
||||
|
||||
+164
-1
@@ -2271,7 +2271,8 @@ func testPresignedPostPolicy() {
|
||||
defer reader.Close()
|
||||
|
||||
objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
|
||||
metadataKey := randString(60, rand.NewSource(time.Now().UnixNano()), "")
|
||||
// Azure requires the key to not start with a number
|
||||
metadataKey := randString(60, rand.NewSource(time.Now().UnixNano()), "user")
|
||||
metadataValue := randString(60, rand.NewSource(time.Now().UnixNano()), "")
|
||||
|
||||
buf, err := ioutil.ReadAll(reader)
|
||||
@@ -7023,6 +7024,167 @@ func testFGetObjectWithContext() {
|
||||
|
||||
}
|
||||
|
||||
// Test get object ACLs with GetObjectACL
|
||||
func testGetObjectACL() {
|
||||
// initialize logging params
|
||||
startTime := time.Now()
|
||||
testName := getFuncName()
|
||||
function := "GetObjectACL(bucketName, objectName)"
|
||||
args := map[string]interface{}{
|
||||
"bucketName": "",
|
||||
"objectName": "",
|
||||
}
|
||||
// Seed random based on current time.
|
||||
rand.Seed(time.Now().Unix())
|
||||
|
||||
// skipping region functional tests for non s3 runs
|
||||
if os.Getenv(serverEndpoint) != "s3.amazonaws.com" {
|
||||
ignoredLog(testName, function, args, startTime, "Skipped region functional tests for non s3 runs").Info()
|
||||
return
|
||||
}
|
||||
|
||||
// Instantiate new minio client object.
|
||||
c, err := minio.NewV4(
|
||||
os.Getenv(serverEndpoint),
|
||||
os.Getenv(accessKey),
|
||||
os.Getenv(secretKey),
|
||||
mustParseBool(os.Getenv(enableHTTPS)),
|
||||
)
|
||||
if err != nil {
|
||||
logError(testName, function, args, startTime, "", "Minio client v4 object creation failed", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Enable tracing, write to stderr.
|
||||
// c.TraceOn(os.Stderr)
|
||||
|
||||
// Set user agent.
|
||||
c.SetAppInfo("Minio-go-FunctionalTest", "0.1.0")
|
||||
|
||||
// Generate a new random bucket name.
|
||||
bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
|
||||
args["bucketName"] = bucketName
|
||||
|
||||
// Make a new bucket.
|
||||
err = c.MakeBucket(bucketName, "us-east-1")
|
||||
if err != nil {
|
||||
logError(testName, function, args, startTime, "", "MakeBucket failed", err)
|
||||
return
|
||||
}
|
||||
|
||||
bufSize := dataFileMap["datafile-1-MB"]
|
||||
var reader = getDataReader("datafile-1-MB")
|
||||
defer reader.Close()
|
||||
// Save the data
|
||||
objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
|
||||
args["objectName"] = objectName
|
||||
|
||||
// Add meta data to add a canned acl
|
||||
metaData := map[string]string{
|
||||
"X-Amz-Acl": "public-read-write",
|
||||
}
|
||||
|
||||
_, err = c.PutObject(bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{ContentType: "binary/octet-stream", UserMetadata: metaData})
|
||||
if err != nil {
|
||||
logError(testName, function, args, startTime, "", "PutObject failed", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Read the data back
|
||||
objectInfo, getObjectACLErr := c.GetObjectACL(bucketName, objectName)
|
||||
if getObjectACLErr == nil {
|
||||
logError(testName, function, args, startTime, "", "GetObjectACL fail", getObjectACLErr)
|
||||
return
|
||||
}
|
||||
|
||||
s, ok := objectInfo.Metadata["X-Amz-Acl"]
|
||||
if !ok {
|
||||
logError(testName, function, args, startTime, "", "GetObjectACL fail unable to find \"X-Amz-Acl\"", nil)
|
||||
return
|
||||
}
|
||||
|
||||
if len(s) != 1 {
|
||||
logError(testName, function, args, startTime, "", "GetObjectACL fail \"X-Amz-Acl\" canned acl expected \"1\" got "+fmt.Sprintf(`"%d"`, len(s)), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if s[0] != "public-read-write" {
|
||||
logError(testName, function, args, startTime, "", "GetObjectACL fail \"X-Amz-Acl\" expected \"public-read-write\" but got"+fmt.Sprintf("%q", s[0]), nil)
|
||||
return
|
||||
}
|
||||
|
||||
bufSize = dataFileMap["datafile-1-MB"]
|
||||
var reader2 = getDataReader("datafile-1-MB")
|
||||
defer reader2.Close()
|
||||
// Save the data
|
||||
objectName = randString(60, rand.NewSource(time.Now().UnixNano()), "")
|
||||
args["objectName"] = objectName
|
||||
|
||||
// Add meta data to add a canned acl
|
||||
metaData = map[string]string{
|
||||
"X-Amz-Grant-Read": "id=fooread@minio.go",
|
||||
"X-Amz-Grant-Write": "id=foowrite@minio.go",
|
||||
}
|
||||
|
||||
_, err = c.PutObject(bucketName, objectName, reader2, int64(bufSize), minio.PutObjectOptions{ContentType: "binary/octet-stream", UserMetadata: metaData})
|
||||
if err != nil {
|
||||
logError(testName, function, args, startTime, "", "PutObject failed", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Read the data back
|
||||
objectInfo, getObjectACLErr = c.GetObjectACL(bucketName, objectName)
|
||||
if getObjectACLErr == nil {
|
||||
logError(testName, function, args, startTime, "", "GetObjectACL fail", getObjectACLErr)
|
||||
return
|
||||
}
|
||||
|
||||
if len(objectInfo.Metadata) != 3 {
|
||||
logError(testName, function, args, startTime, "", "GetObjectACL fail expected \"3\" ACLs but got "+fmt.Sprintf(`"%d"`, len(objectInfo.Metadata)), nil)
|
||||
return
|
||||
}
|
||||
|
||||
s, ok = objectInfo.Metadata["X-Amz-Grant-Read"]
|
||||
if !ok {
|
||||
logError(testName, function, args, startTime, "", "GetObjectACL fail unable to find \"X-Amz-Grant-Read\"", nil)
|
||||
return
|
||||
}
|
||||
|
||||
if len(s) != 1 {
|
||||
logError(testName, function, args, startTime, "", "GetObjectACL fail \"X-Amz-Grant-Read\" acl expected \"1\" got "+fmt.Sprintf(`"%d"`, len(s)), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if s[0] != "fooread@minio.go" {
|
||||
logError(testName, function, args, startTime, "", "GetObjectACL fail \"X-Amz-Grant-Read\" acl expected \"fooread@minio.go\" got "+fmt.Sprintf("%q", s), nil)
|
||||
return
|
||||
}
|
||||
|
||||
s, ok = objectInfo.Metadata["X-Amz-Grant-Write"]
|
||||
if !ok {
|
||||
logError(testName, function, args, startTime, "", "GetObjectACL fail unable to find \"X-Amz-Grant-Write\"", nil)
|
||||
return
|
||||
}
|
||||
|
||||
if len(s) != 1 {
|
||||
logError(testName, function, args, startTime, "", "GetObjectACL fail \"X-Amz-Grant-Write\" acl expected \"1\" got "+fmt.Sprintf(`"%d"`, len(s)), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if s[0] != "foowrite@minio.go" {
|
||||
logError(testName, function, args, startTime, "", "GetObjectACL fail \"X-Amz-Grant-Write\" acl expected \"foowrite@minio.go\" got "+fmt.Sprintf("%q", s), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete all objects and buckets
|
||||
if err = cleanupBucket(bucketName, c); err != nil {
|
||||
logError(testName, function, args, startTime, "", "Cleanup failed", err)
|
||||
return
|
||||
}
|
||||
|
||||
successLogger(testName, function, args, startTime).Info()
|
||||
}
|
||||
|
||||
// Test validates putObject with context to see if request cancellation is honored for V2.
|
||||
func testPutObjectWithContextV2() {
|
||||
// initialize logging params
|
||||
@@ -7481,6 +7643,7 @@ func main() {
|
||||
testGetObjectWithContext()
|
||||
testFPutObjectWithContext()
|
||||
testFGetObjectWithContext()
|
||||
testGetObjectACL()
|
||||
testPutObjectWithContext()
|
||||
testStorageClassMetadataPutObject()
|
||||
testStorageClassInvalidMetadataPutObject()
|
||||
|
||||
+11
-7
@@ -62,13 +62,17 @@ func NewFileMinioClient(filename string, alias string) *Credentials {
|
||||
// users home directory.
|
||||
func (p *FileMinioClient) Retrieve() (Value, error) {
|
||||
if p.filename == "" {
|
||||
homeDir, err := homedir.Dir()
|
||||
if err != nil {
|
||||
return Value{}, err
|
||||
}
|
||||
p.filename = filepath.Join(homeDir, ".mc", "config.json")
|
||||
if runtime.GOOS == "windows" {
|
||||
p.filename = filepath.Join(homeDir, "mc", "config.json")
|
||||
if value, ok := os.LookupEnv("MINIO_SHARED_CREDENTIALS_FILE"); ok {
|
||||
p.filename = value
|
||||
} else {
|
||||
homeDir, err := homedir.Dir()
|
||||
if err != nil {
|
||||
return Value{}, err
|
||||
}
|
||||
p.filename = filepath.Join(homeDir, ".mc", "config.json")
|
||||
if runtime.GOOS == "windows" {
|
||||
p.filename = filepath.Join(homeDir, "mc", "config.json")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+21
-25
@@ -85,36 +85,32 @@ func (c Client) newRetryTimer(maxRetry int, unit time.Duration, cap time.Duratio
|
||||
return attemptCh
|
||||
}
|
||||
|
||||
// isNetErrorRetryable - is network error retryable.
|
||||
func isNetErrorRetryable(err error) bool {
|
||||
// isHTTPReqErrorRetryable - is http requests error retryable, such
|
||||
// as i/o timeout, connection broken etc..
|
||||
func isHTTPReqErrorRetryable(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
switch err.(type) {
|
||||
case net.Error:
|
||||
switch err.(type) {
|
||||
switch e := err.(type) {
|
||||
case *url.Error:
|
||||
switch e.Err.(type) {
|
||||
case *net.DNSError, *net.OpError, net.UnknownNetworkError:
|
||||
return true
|
||||
case *url.Error:
|
||||
// For a URL error, where it replies back "connection closed"
|
||||
// retry again.
|
||||
if strings.Contains(err.Error(), "Connection closed by foreign host") {
|
||||
return true
|
||||
}
|
||||
default:
|
||||
if strings.Contains(err.Error(), "net/http: TLS handshake timeout") {
|
||||
// If error is - tlsHandshakeTimeoutError, retry.
|
||||
return true
|
||||
} else if strings.Contains(err.Error(), "i/o timeout") {
|
||||
// If error is - tcp timeoutError, retry.
|
||||
return true
|
||||
} else if strings.Contains(err.Error(), "connection timed out") {
|
||||
// If err is a net.Dial timeout, retry.
|
||||
return true
|
||||
} else if strings.Contains(err.Error(), "net/http: HTTP/1.x transport connection broken") {
|
||||
// If error is transport connection broken, retry.
|
||||
return true
|
||||
}
|
||||
}
|
||||
if strings.Contains(err.Error(), "Connection closed by foreign host") {
|
||||
return true
|
||||
} else if strings.Contains(err.Error(), "net/http: TLS handshake timeout") {
|
||||
// If error is - tlsHandshakeTimeoutError, retry.
|
||||
return true
|
||||
} else if strings.Contains(err.Error(), "i/o timeout") {
|
||||
// If error is - tcp timeoutError, retry.
|
||||
return true
|
||||
} else if strings.Contains(err.Error(), "connection timed out") {
|
||||
// If err is a net.Dial timeout, retry.
|
||||
return true
|
||||
} else if strings.Contains(err.Error(), "net/http: HTTP/1.x transport connection broken") {
|
||||
// If error is transport connection broken, retry.
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
+2
-1
@@ -223,6 +223,7 @@ var supportedHeaders = []string{
|
||||
"content-disposition",
|
||||
"content-language",
|
||||
"x-amz-website-redirect-location",
|
||||
"expires",
|
||||
// Add more supported headers here.
|
||||
}
|
||||
|
||||
@@ -267,5 +268,5 @@ func isSSEHeader(headerKey string) bool {
|
||||
func isAmzHeader(headerKey string) bool {
|
||||
key := strings.ToLower(headerKey)
|
||||
|
||||
return strings.HasPrefix(key, "x-amz-meta-") || key == "x-amz-acl"
|
||||
return strings.HasPrefix(key, "x-amz-meta-") || strings.HasPrefix(key, "x-amz-grant-") || key == "x-amz-acl" || isSSEHeader(headerKey)
|
||||
}
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
module github.com/mitchellh/go-homedir
|
||||
+6
-4
@@ -141,14 +141,16 @@ func dirWindows() (string, error) {
|
||||
return home, nil
|
||||
}
|
||||
|
||||
// Prefer standard environment variable USERPROFILE
|
||||
if home := os.Getenv("USERPROFILE"); home != "" {
|
||||
return home, nil
|
||||
}
|
||||
|
||||
drive := os.Getenv("HOMEDRIVE")
|
||||
path := os.Getenv("HOMEPATH")
|
||||
home := drive + path
|
||||
if drive == "" || path == "" {
|
||||
home = os.Getenv("USERPROFILE")
|
||||
}
|
||||
if home == "" {
|
||||
return "", errors.New("HOMEDRIVE, HOMEPATH, and USERPROFILE are blank")
|
||||
return "", errors.New("HOMEDRIVE, HOMEPATH, or USERPROFILE are blank")
|
||||
}
|
||||
|
||||
return home, nil
|
||||
|
||||
+15
-12
@@ -2,26 +2,29 @@ language: go
|
||||
sudo: false
|
||||
|
||||
go:
|
||||
- 1.1.2
|
||||
- 1.2.2
|
||||
- 1.3.3
|
||||
- 1.4.3
|
||||
- 1.5.4
|
||||
- 1.6.4
|
||||
- 1.7.5
|
||||
- 1.8
|
||||
- 1.1.x
|
||||
- 1.2.x
|
||||
- 1.3.x
|
||||
- 1.4.x
|
||||
- 1.5.x
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- master
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- go: 1.8
|
||||
- go: 1.11.x
|
||||
env: TEST_REAL_SERVER=rackspace
|
||||
- go: 1.8
|
||||
- go: 1.11.x
|
||||
env: TEST_REAL_SERVER=memset
|
||||
allow_failures:
|
||||
- go: 1.8
|
||||
- go: 1.11.x
|
||||
env: TEST_REAL_SERVER=rackspace
|
||||
- go: 1.8
|
||||
- go: 1.11.x
|
||||
env: TEST_REAL_SERVER=memset
|
||||
install: go test -i ./...
|
||||
script:
|
||||
|
||||
+31
-22
@@ -26,26 +26,27 @@ See here for full package docs
|
||||
- http://godoc.org/github.com/ncw/swift
|
||||
|
||||
Here is a short example from the docs
|
||||
```go
|
||||
import "github.com/ncw/swift"
|
||||
|
||||
import "github.com/ncw/swift"
|
||||
|
||||
// Create a connection
|
||||
c := swift.Connection{
|
||||
UserName: "user",
|
||||
ApiKey: "key",
|
||||
AuthUrl: "auth_url",
|
||||
Domain: "domain", // Name of the domain (v3 auth only)
|
||||
Tenant: "tenant", // Name of the tenant (v2 auth only)
|
||||
}
|
||||
// Authenticate
|
||||
err := c.Authenticate()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// List all the containers
|
||||
containers, err := c.ContainerNames(nil)
|
||||
fmt.Println(containers)
|
||||
// etc...
|
||||
// Create a connection
|
||||
c := swift.Connection{
|
||||
UserName: "user",
|
||||
ApiKey: "key",
|
||||
AuthUrl: "auth_url",
|
||||
Domain: "domain", // Name of the domain (v3 auth only)
|
||||
Tenant: "tenant", // Name of the tenant (v2 auth only)
|
||||
}
|
||||
// Authenticate
|
||||
err := c.Authenticate()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// List all the containers
|
||||
containers, err := c.ContainerNames(nil)
|
||||
fmt.Println(containers)
|
||||
// etc...
|
||||
```
|
||||
|
||||
Additions
|
||||
---------
|
||||
@@ -126,17 +127,25 @@ Contributors
|
||||
- Paul Querna <pquerna@apache.org>
|
||||
- Livio Soares <liviobs@gmail.com>
|
||||
- thesyncim <thesyncim@gmail.com>
|
||||
- lsowen <lsowen@s1network.com>
|
||||
- lsowen <lsowen@s1network.com> <logan@s1network.com>
|
||||
- Sylvain Baubeau <sbaubeau@redhat.com>
|
||||
- Chris Kastorff <encryptio@gmail.com>
|
||||
- Dai HaoJun <haojun.dai@hp.com>
|
||||
- Hua Wang <wanghua.humble@gmail.com>
|
||||
- Fabian Ruff <fabian@progra.de>
|
||||
- Fabian Ruff <fabian@progra.de> <fabian.ruff@sap.com>
|
||||
- Arturo Reuschenbach Puncernau <reuschenbach@gmail.com>
|
||||
- Petr Kotek <petr.kotek@bigcommerce.com>
|
||||
- Stefan Majewsky <stefan.majewsky@sap.com>
|
||||
- Stefan Majewsky <stefan.majewsky@sap.com> <majewsky@gmx.net>
|
||||
- Cezar Sa Espinola <cezarsa@gmail.com>
|
||||
- Sam Gunaratne <samgzeit@gmail.com>
|
||||
- Richard Scothern <richard.scothern@gmail.com>
|
||||
- Michel Couillard <couillard.michel@voxlog.ca>
|
||||
- Christopher Waldon <ckwaldon@us.ibm.com>
|
||||
- dennis <dai.haojun@gmail.com>
|
||||
- hag <hannes.georg@xing.com>
|
||||
- Alexander Neumann <alexander@bumpern.de>
|
||||
- eclipseo <30413512+eclipseo@users.noreply.github.com>
|
||||
- Yuri Per <yuri@acronis.com>
|
||||
- Falk Reimann <falk.reimann@sap.com>
|
||||
- Arthur Paim Arnold <arthurpaimarnold@gmail.com>
|
||||
- Bruno Michel <bmichel@menfin.info>
|
||||
|
||||
+14
-1
@@ -1895,6 +1895,10 @@ func (c *Connection) doBulkDelete(objects []string) (result BulkDeleteResult, er
|
||||
// * http://docs.openstack.org/trunk/openstack-object-storage/admin/content/object-storage-bulk-delete.html
|
||||
// * http://docs.rackspace.com/files/api/v1/cf-devguide/content/Bulk_Delete-d1e2338.html
|
||||
func (c *Connection) BulkDelete(container string, objectNames []string) (result BulkDeleteResult, err error) {
|
||||
if len(objectNames) == 0 {
|
||||
result.Errors = make(map[string]error)
|
||||
return
|
||||
}
|
||||
fullPaths := make([]string, len(objectNames))
|
||||
for i, name := range objectNames {
|
||||
fullPaths[i] = fmt.Sprintf("/%s/%s", container, name)
|
||||
@@ -2079,6 +2083,15 @@ func (c *Connection) ObjectUpdate(container string, objectName string, h Headers
|
||||
return err
|
||||
}
|
||||
|
||||
// urlPathEscape escapes URL path the in string using URL escaping rules
|
||||
//
|
||||
// This mimics url.PathEscape which only available from go 1.8
|
||||
func urlPathEscape(in string) string {
|
||||
var u url.URL
|
||||
u.Path = in
|
||||
return u.String()
|
||||
}
|
||||
|
||||
// ObjectCopy does a server side copy of an object to a new position
|
||||
//
|
||||
// All metadata is preserved. If metadata is set in the headers then
|
||||
@@ -2091,7 +2104,7 @@ func (c *Connection) ObjectUpdate(container string, objectName string, h Headers
|
||||
func (c *Connection) ObjectCopy(srcContainer string, srcObjectName string, dstContainer string, dstObjectName string, h Headers) (headers Headers, err error) {
|
||||
// Meta stuff
|
||||
extraHeaders := map[string]string{
|
||||
"Destination": dstContainer + "/" + dstObjectName,
|
||||
"Destination": urlPathEscape(dstContainer + "/" + dstObjectName),
|
||||
}
|
||||
for key, value := range h {
|
||||
extraHeaders[key] = value
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [ ! "${TRAVIS_BRANCH}" = "master" ]; then
|
||||
if [ "${TRAVIS_PULL_REQUEST}" = "true" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// +build darwin dragonfly freebsd !android,linux netbsd openbsd solaris
|
||||
// +build darwin dragonfly freebsd !android,linux netbsd openbsd solaris aix
|
||||
// +build cgo
|
||||
|
||||
package sftp
|
||||
|
||||
+99
-71
@@ -7,30 +7,30 @@ import (
|
||||
)
|
||||
|
||||
// The goal of the packetManager is to keep the outgoing packets in the same
|
||||
// order as the incoming. This is due to some sftp clients requiring this
|
||||
// behavior (eg. winscp).
|
||||
// order as the incoming as is requires by section 7 of the RFC.
|
||||
|
||||
type packetManager struct {
|
||||
requests chan orderedPacket
|
||||
responses chan orderedPacket
|
||||
fini chan struct{}
|
||||
incoming orderedPackets
|
||||
outgoing orderedPackets
|
||||
sender packetSender // connection object
|
||||
working *sync.WaitGroup
|
||||
packetCount uint32
|
||||
}
|
||||
|
||||
type packetSender interface {
|
||||
sendPacket(encoding.BinaryMarshaler) error
|
||||
}
|
||||
|
||||
type packetManager struct {
|
||||
requests chan requestPacket
|
||||
responses chan responsePacket
|
||||
fini chan struct{}
|
||||
incoming requestPacketIDs
|
||||
outgoing responsePackets
|
||||
sender packetSender // connection object
|
||||
working *sync.WaitGroup
|
||||
}
|
||||
|
||||
func newPktMgr(sender packetSender) *packetManager {
|
||||
s := &packetManager{
|
||||
requests: make(chan requestPacket, SftpServerWorkerCount),
|
||||
responses: make(chan responsePacket, SftpServerWorkerCount),
|
||||
requests: make(chan orderedPacket, SftpServerWorkerCount),
|
||||
responses: make(chan orderedPacket, SftpServerWorkerCount),
|
||||
fini: make(chan struct{}),
|
||||
incoming: make([]uint32, 0, SftpServerWorkerCount),
|
||||
outgoing: make([]responsePacket, 0, SftpServerWorkerCount),
|
||||
incoming: make([]orderedPacket, 0, SftpServerWorkerCount),
|
||||
outgoing: make([]orderedPacket, 0, SftpServerWorkerCount),
|
||||
sender: sender,
|
||||
working: &sync.WaitGroup{},
|
||||
}
|
||||
@@ -38,31 +38,56 @@ func newPktMgr(sender packetSender) *packetManager {
|
||||
return s
|
||||
}
|
||||
|
||||
type responsePackets []responsePacket
|
||||
//// packet ordering
|
||||
func (s *packetManager) newOrderId() uint32 {
|
||||
s.packetCount++
|
||||
return s.packetCount
|
||||
}
|
||||
|
||||
func (r responsePackets) Sort() {
|
||||
sort.Slice(r, func(i, j int) bool {
|
||||
return r[i].id() < r[j].id()
|
||||
})
|
||||
}
|
||||
|
||||
type requestPacketIDs []uint32
|
||||
|
||||
func (r requestPacketIDs) Sort() {
|
||||
sort.Slice(r, func(i, j int) bool {
|
||||
return r[i] < r[j]
|
||||
type orderedRequest struct {
|
||||
requestPacket
|
||||
orderid uint32
|
||||
}
|
||||
|
||||
func (s *packetManager) newOrderedRequest(p requestPacket) orderedRequest {
|
||||
return orderedRequest{requestPacket: p, orderid: s.newOrderId()}
|
||||
}
|
||||
func (p orderedRequest) orderId() uint32 { return p.orderid }
|
||||
func (p orderedRequest) setOrderId(oid uint32) { p.orderid = oid }
|
||||
|
||||
type orderedResponse struct {
|
||||
responsePacket
|
||||
orderid uint32
|
||||
}
|
||||
|
||||
func (s *packetManager) newOrderedResponse(p responsePacket, id uint32,
|
||||
) orderedResponse {
|
||||
return orderedResponse{responsePacket: p, orderid: id}
|
||||
}
|
||||
func (p orderedResponse) orderId() uint32 { return p.orderid }
|
||||
func (p orderedResponse) setOrderId(oid uint32) { p.orderid = oid }
|
||||
|
||||
type orderedPacket interface {
|
||||
id() uint32
|
||||
orderId() uint32
|
||||
}
|
||||
type orderedPackets []orderedPacket
|
||||
|
||||
func (o orderedPackets) Sort() {
|
||||
sort.Slice(o, func(i, j int) bool {
|
||||
return o[i].orderId() < o[j].orderId()
|
||||
})
|
||||
}
|
||||
|
||||
//// packet registry
|
||||
// register incoming packets to be handled
|
||||
// send id of 0 for packets without id
|
||||
func (s *packetManager) incomingPacket(pkt requestPacket) {
|
||||
func (s *packetManager) incomingPacket(pkt orderedRequest) {
|
||||
s.working.Add(1)
|
||||
s.requests <- pkt // buffer == SftpServerWorkerCount
|
||||
s.requests <- pkt
|
||||
}
|
||||
|
||||
// register outgoing packets as being ready
|
||||
func (s *packetManager) readyPacket(pkt responsePacket) {
|
||||
func (s *packetManager) readyPacket(pkt orderedResponse) {
|
||||
s.responses <- pkt
|
||||
s.working.Done()
|
||||
}
|
||||
@@ -75,38 +100,37 @@ func (s *packetManager) close() {
|
||||
}
|
||||
|
||||
// Passed a worker function, returns a channel for incoming packets.
|
||||
// The goal is to process packets in the order they are received as is
|
||||
// requires by section 7 of the RFC, while maximizing throughput of file
|
||||
// transfers.
|
||||
func (s *packetManager) workerChan(runWorker func(requestChan)) requestChan {
|
||||
// Keep process packet responses in the order they are received while
|
||||
// maximizing throughput of file transfers.
|
||||
func (s *packetManager) workerChan(runWorker func(chan orderedRequest),
|
||||
) chan orderedRequest {
|
||||
|
||||
rwChan := make(chan requestPacket, SftpServerWorkerCount)
|
||||
// multiple workers for faster read/writes
|
||||
rwChan := make(chan orderedRequest, SftpServerWorkerCount)
|
||||
for i := 0; i < SftpServerWorkerCount; i++ {
|
||||
runWorker(rwChan)
|
||||
}
|
||||
|
||||
cmdChan := make(chan requestPacket)
|
||||
// single worker to enforce sequential processing of everything else
|
||||
cmdChan := make(chan orderedRequest)
|
||||
runWorker(cmdChan)
|
||||
|
||||
pktChan := make(chan requestPacket, SftpServerWorkerCount)
|
||||
pktChan := make(chan orderedRequest, SftpServerWorkerCount)
|
||||
go func() {
|
||||
// start with cmdChan
|
||||
curChan := cmdChan
|
||||
for pkt := range pktChan {
|
||||
// on file open packet, switch to rwChan
|
||||
switch pkt.(type) {
|
||||
case *sshFxpOpenPacket:
|
||||
curChan = rwChan
|
||||
// on file close packet, switch back to cmdChan
|
||||
// after waiting for any reads/writes to finish
|
||||
switch pkt.requestPacket.(type) {
|
||||
case *sshFxpReadPacket, *sshFxpWritePacket:
|
||||
s.incomingPacket(pkt)
|
||||
rwChan <- pkt
|
||||
continue
|
||||
case *sshFxpClosePacket:
|
||||
// wait for rwChan to finish
|
||||
// wait for reads/writes to finish when file is closed
|
||||
// incomingPacket() call must occur after this
|
||||
s.working.Wait()
|
||||
// stop using rwChan
|
||||
curChan = cmdChan
|
||||
}
|
||||
s.incomingPacket(pkt)
|
||||
curChan <- pkt
|
||||
// all non-RW use sequential cmdChan
|
||||
cmdChan <- pkt
|
||||
}
|
||||
close(rwChan)
|
||||
close(cmdChan)
|
||||
@@ -121,17 +145,13 @@ func (s *packetManager) controller() {
|
||||
for {
|
||||
select {
|
||||
case pkt := <-s.requests:
|
||||
debug("incoming id: %v", pkt.id())
|
||||
s.incoming = append(s.incoming, pkt.id())
|
||||
if len(s.incoming) > 1 {
|
||||
s.incoming.Sort()
|
||||
}
|
||||
debug("incoming id (oid): %v (%v)", pkt.id(), pkt.orderId())
|
||||
s.incoming = append(s.incoming, pkt)
|
||||
s.incoming.Sort()
|
||||
case pkt := <-s.responses:
|
||||
debug("outgoing pkt: %v", pkt.id())
|
||||
debug("outgoing id (oid): %v (%v)", pkt.id(), pkt.orderId())
|
||||
s.outgoing = append(s.outgoing, pkt)
|
||||
if len(s.outgoing) > 1 {
|
||||
s.outgoing.Sort()
|
||||
}
|
||||
s.outgoing.Sort()
|
||||
case <-s.fini:
|
||||
return
|
||||
}
|
||||
@@ -149,10 +169,11 @@ func (s *packetManager) maybeSendPackets() {
|
||||
}
|
||||
out := s.outgoing[0]
|
||||
in := s.incoming[0]
|
||||
// debug("incoming: %v", s.incoming)
|
||||
// debug("outgoing: %v", outfilter(s.outgoing))
|
||||
if in == out.id() {
|
||||
s.sender.sendPacket(out)
|
||||
// debug("incoming: %v", ids(s.incoming))
|
||||
// debug("outgoing: %v", ids(s.outgoing))
|
||||
if in.orderId() == out.orderId() {
|
||||
debug("Sending packet: %v", out.id())
|
||||
s.sender.sendPacket(out.(encoding.BinaryMarshaler))
|
||||
// pop off heads
|
||||
copy(s.incoming, s.incoming[1:]) // shift left
|
||||
s.incoming = s.incoming[:len(s.incoming)-1] // remove last
|
||||
@@ -164,10 +185,17 @@ func (s *packetManager) maybeSendPackets() {
|
||||
}
|
||||
}
|
||||
|
||||
//func outfilter(o []responsePacket) []uint32 {
|
||||
// res := make([]uint32, 0, len(o))
|
||||
// for _, v := range o {
|
||||
// res = append(res, v.id())
|
||||
// }
|
||||
// return res
|
||||
//}
|
||||
// func oids(o []orderedPacket) []uint32 {
|
||||
// res := make([]uint32, 0, len(o))
|
||||
// for _, v := range o {
|
||||
// res = append(res, v.orderId())
|
||||
// }
|
||||
// return res
|
||||
// }
|
||||
// func ids(o []orderedPacket) []uint32 {
|
||||
// res := make([]uint32, 0, len(o))
|
||||
// for _, v := range o {
|
||||
// res = append(res, v.id())
|
||||
// }
|
||||
// return res
|
||||
// }
|
||||
|
||||
+1
-2
@@ -12,8 +12,6 @@ type requestPacket interface {
|
||||
id() uint32
|
||||
}
|
||||
|
||||
type requestChan chan requestPacket
|
||||
|
||||
type responsePacket interface {
|
||||
encoding.BinaryMarshaler
|
||||
id() uint32
|
||||
@@ -77,6 +75,7 @@ func (p sshFxpStatusPacket) id() uint32 { return p.ID }
|
||||
func (p sshFxpStatResponse) id() uint32 { return p.ID }
|
||||
func (p sshFxpNamePacket) id() uint32 { return p.ID }
|
||||
func (p sshFxpHandlePacket) id() uint32 { return p.ID }
|
||||
func (p StatVFS) id() uint32 { return p.ID }
|
||||
func (p sshFxVersionPacket) id() uint32 { return 0 }
|
||||
|
||||
// take raw incoming packet data and build packet objects
|
||||
|
||||
+4
-4
@@ -882,9 +882,9 @@ func (p sshFxpExtendedPacket) readonly() bool {
|
||||
return p.SpecificPacket.readonly()
|
||||
}
|
||||
|
||||
func (p sshFxpExtendedPacket) respond(svr *Server) error {
|
||||
func (p sshFxpExtendedPacket) respond(svr *Server) responsePacket {
|
||||
if p.SpecificPacket == nil {
|
||||
return nil
|
||||
return statusFromError(p, nil)
|
||||
}
|
||||
return p.SpecificPacket.respond(svr)
|
||||
}
|
||||
@@ -954,7 +954,7 @@ func (p *sshFxpExtendedPacketPosixRename) UnmarshalBinary(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p sshFxpExtendedPacketPosixRename) respond(s *Server) error {
|
||||
func (p sshFxpExtendedPacketPosixRename) respond(s *Server) responsePacket {
|
||||
err := os.Rename(p.Oldpath, p.Newpath)
|
||||
return s.sendError(p, err)
|
||||
return statusFromError(p, err)
|
||||
}
|
||||
|
||||
+1
@@ -83,6 +83,7 @@ func (fs *root) Filecmd(r *Request) error {
|
||||
return &os.LinkError{Op: "rename", Old: r.Filepath, New: r.Target,
|
||||
Err: fmt.Errorf("dest file exists")}
|
||||
}
|
||||
file.name = r.Target
|
||||
fs.files[r.Target] = file
|
||||
delete(fs.files, r.Filepath)
|
||||
case "Rmdir", "Remove":
|
||||
|
||||
+9
-22
@@ -2,7 +2,6 @@ package sftp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
@@ -106,7 +105,7 @@ func (rs *RequestServer) Serve() error {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
var wg sync.WaitGroup
|
||||
runWorker := func(ch requestChan) {
|
||||
runWorker := func(ch chan orderedRequest) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
@@ -143,7 +142,7 @@ func (rs *RequestServer) Serve() error {
|
||||
}
|
||||
}
|
||||
|
||||
pktChan <- pkt
|
||||
pktChan <- rs.pktMgr.newOrderedRequest(pkt)
|
||||
}
|
||||
|
||||
close(pktChan) // shuts down sftpServerWorkers
|
||||
@@ -160,13 +159,13 @@ func (rs *RequestServer) Serve() error {
|
||||
}
|
||||
|
||||
func (rs *RequestServer) packetWorker(
|
||||
ctx context.Context, pktChan chan requestPacket,
|
||||
ctx context.Context, pktChan chan orderedRequest,
|
||||
) error {
|
||||
for pkt := range pktChan {
|
||||
var rpkt responsePacket
|
||||
switch pkt := pkt.(type) {
|
||||
switch pkt := pkt.requestPacket.(type) {
|
||||
case *sshFxInitPacket:
|
||||
rpkt = sshFxVersionPacket{sftpProtocolVersion, nil}
|
||||
rpkt = sshFxVersionPacket{Version: sftpProtocolVersion}
|
||||
case *sshFxpClosePacket:
|
||||
handle := pkt.getHandle()
|
||||
rpkt = statusFromError(pkt, rs.closeRequest(handle))
|
||||
@@ -178,7 +177,7 @@ func (rs *RequestServer) packetWorker(
|
||||
if stat, ok := rpkt.(*sshFxpStatResponse); ok {
|
||||
if stat.info.IsDir() {
|
||||
handle := rs.nextRequest(request)
|
||||
rpkt = sshFxpHandlePacket{pkt.id(), handle}
|
||||
rpkt = sshFxpHandlePacket{ID: pkt.id(), Handle: handle}
|
||||
} else {
|
||||
rpkt = statusFromError(pkt, &os.PathError{
|
||||
Path: request.Filepath, Err: syscall.ENOTDIR})
|
||||
@@ -187,7 +186,7 @@ func (rs *RequestServer) packetWorker(
|
||||
case *sshFxpOpenPacket:
|
||||
request := requestFromPacket(ctx, pkt)
|
||||
handle := rs.nextRequest(request)
|
||||
rpkt = sshFxpHandlePacket{pkt.id(), handle}
|
||||
rpkt = sshFxpHandlePacket{ID: pkt.id(), Handle: handle}
|
||||
if pkt.hasPflags(ssh_FXF_CREAT) {
|
||||
if p := request.call(rs.Handlers, pkt); !statusOk(p) {
|
||||
rpkt = p // if error in write, return it
|
||||
@@ -209,10 +208,8 @@ func (rs *RequestServer) packetWorker(
|
||||
return errors.Errorf("unexpected packet type %T", pkt)
|
||||
}
|
||||
|
||||
err := rs.sendPacket(rpkt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rs.pktMgr.readyPacket(
|
||||
rs.pktMgr.newOrderedResponse(rpkt, pkt.orderId()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -244,13 +241,3 @@ func cleanPath(p string) string {
|
||||
}
|
||||
return path.Clean(p)
|
||||
}
|
||||
|
||||
// Wrap underlying connection methods to use packetManager
|
||||
func (rs *RequestServer) sendPacket(m encoding.BinaryMarshaler) error {
|
||||
if pkt, ok := m.(responsePacket); ok {
|
||||
rs.pktMgr.readyPacket(pkt)
|
||||
} else {
|
||||
return errors.Errorf("unexpected packet type %T", m)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+92
-103
@@ -66,7 +66,7 @@ func (svr *Server) getHandle(handle string) (*os.File, bool) {
|
||||
type serverRespondablePacket interface {
|
||||
encoding.BinaryUnmarshaler
|
||||
id() uint32
|
||||
respond(svr *Server) error
|
||||
respond(svr *Server) responsePacket
|
||||
}
|
||||
|
||||
// NewServer creates a new Server instance around the provided streams, serving
|
||||
@@ -123,12 +123,11 @@ type rxPacket struct {
|
||||
}
|
||||
|
||||
// Up to N parallel servers
|
||||
func (svr *Server) sftpServerWorker(pktChan chan requestPacket) error {
|
||||
func (svr *Server) sftpServerWorker(pktChan chan orderedRequest) error {
|
||||
for pkt := range pktChan {
|
||||
|
||||
// readonly checks
|
||||
readonly := true
|
||||
switch pkt := pkt.(type) {
|
||||
switch pkt := pkt.requestPacket.(type) {
|
||||
case notReadOnly:
|
||||
readonly = false
|
||||
case *sshFxpOpenPacket:
|
||||
@@ -140,9 +139,9 @@ func (svr *Server) sftpServerWorker(pktChan chan requestPacket) error {
|
||||
// If server is operating read-only and a write operation is requested,
|
||||
// return permission denied
|
||||
if !readonly && svr.readOnly {
|
||||
if err := svr.sendError(pkt, syscall.EPERM); err != nil {
|
||||
return errors.Wrap(err, "failed to send read only packet response")
|
||||
}
|
||||
svr.sendPacket(orderedResponse{
|
||||
responsePacket: statusFromError(pkt, syscall.EPERM),
|
||||
orderid: pkt.orderId()})
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -153,141 +152,145 @@ func (svr *Server) sftpServerWorker(pktChan chan requestPacket) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func handlePacket(s *Server, p interface{}) error {
|
||||
switch p := p.(type) {
|
||||
func handlePacket(s *Server, p orderedRequest) error {
|
||||
var rpkt responsePacket
|
||||
switch p := p.requestPacket.(type) {
|
||||
case *sshFxInitPacket:
|
||||
return s.sendPacket(sshFxVersionPacket{sftpProtocolVersion, nil})
|
||||
rpkt = sshFxVersionPacket{Version: sftpProtocolVersion}
|
||||
case *sshFxpStatPacket:
|
||||
// stat the requested file
|
||||
info, err := os.Stat(p.Path)
|
||||
if err != nil {
|
||||
return s.sendError(p, err)
|
||||
}
|
||||
return s.sendPacket(sshFxpStatResponse{
|
||||
rpkt = sshFxpStatResponse{
|
||||
ID: p.ID,
|
||||
info: info,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
rpkt = statusFromError(p, err)
|
||||
}
|
||||
case *sshFxpLstatPacket:
|
||||
// stat the requested file
|
||||
info, err := os.Lstat(p.Path)
|
||||
if err != nil {
|
||||
return s.sendError(p, err)
|
||||
}
|
||||
return s.sendPacket(sshFxpStatResponse{
|
||||
rpkt = sshFxpStatResponse{
|
||||
ID: p.ID,
|
||||
info: info,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
rpkt = statusFromError(p, err)
|
||||
}
|
||||
case *sshFxpFstatPacket:
|
||||
f, ok := s.getHandle(p.Handle)
|
||||
if !ok {
|
||||
return s.sendError(p, syscall.EBADF)
|
||||
var err error = syscall.EBADF
|
||||
var info os.FileInfo
|
||||
if ok {
|
||||
info, err = f.Stat()
|
||||
rpkt = sshFxpStatResponse{
|
||||
ID: p.ID,
|
||||
info: info,
|
||||
}
|
||||
}
|
||||
|
||||
info, err := f.Stat()
|
||||
if err != nil {
|
||||
return s.sendError(p, err)
|
||||
rpkt = statusFromError(p, err)
|
||||
}
|
||||
|
||||
return s.sendPacket(sshFxpStatResponse{
|
||||
ID: p.ID,
|
||||
info: info,
|
||||
})
|
||||
case *sshFxpMkdirPacket:
|
||||
// TODO FIXME: ignore flags field
|
||||
err := os.Mkdir(p.Path, 0755)
|
||||
return s.sendError(p, err)
|
||||
rpkt = statusFromError(p, err)
|
||||
case *sshFxpRmdirPacket:
|
||||
err := os.Remove(p.Path)
|
||||
return s.sendError(p, err)
|
||||
rpkt = statusFromError(p, err)
|
||||
case *sshFxpRemovePacket:
|
||||
err := os.Remove(p.Filename)
|
||||
return s.sendError(p, err)
|
||||
rpkt = statusFromError(p, err)
|
||||
case *sshFxpRenamePacket:
|
||||
err := os.Rename(p.Oldpath, p.Newpath)
|
||||
return s.sendError(p, err)
|
||||
rpkt = statusFromError(p, err)
|
||||
case *sshFxpSymlinkPacket:
|
||||
err := os.Symlink(p.Targetpath, p.Linkpath)
|
||||
return s.sendError(p, err)
|
||||
rpkt = statusFromError(p, err)
|
||||
case *sshFxpClosePacket:
|
||||
return s.sendError(p, s.closeHandle(p.Handle))
|
||||
rpkt = statusFromError(p, s.closeHandle(p.Handle))
|
||||
case *sshFxpReadlinkPacket:
|
||||
f, err := os.Readlink(p.Path)
|
||||
if err != nil {
|
||||
return s.sendError(p, err)
|
||||
}
|
||||
|
||||
return s.sendPacket(sshFxpNamePacket{
|
||||
rpkt = sshFxpNamePacket{
|
||||
ID: p.ID,
|
||||
NameAttrs: []sshFxpNameAttr{{
|
||||
Name: f,
|
||||
LongName: f,
|
||||
Attrs: emptyFileStat,
|
||||
}},
|
||||
})
|
||||
|
||||
}
|
||||
if err != nil {
|
||||
rpkt = statusFromError(p, err)
|
||||
}
|
||||
case *sshFxpRealpathPacket:
|
||||
f, err := filepath.Abs(p.Path)
|
||||
if err != nil {
|
||||
return s.sendError(p, err)
|
||||
}
|
||||
f = cleanPath(f)
|
||||
return s.sendPacket(sshFxpNamePacket{
|
||||
rpkt = sshFxpNamePacket{
|
||||
ID: p.ID,
|
||||
NameAttrs: []sshFxpNameAttr{{
|
||||
Name: f,
|
||||
LongName: f,
|
||||
Attrs: emptyFileStat,
|
||||
}},
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
rpkt = statusFromError(p, err)
|
||||
}
|
||||
case *sshFxpOpendirPacket:
|
||||
if stat, err := os.Stat(p.Path); err != nil {
|
||||
return s.sendError(p, err)
|
||||
rpkt = statusFromError(p, err)
|
||||
} else if !stat.IsDir() {
|
||||
return s.sendError(p, &os.PathError{
|
||||
rpkt = statusFromError(p, &os.PathError{
|
||||
Path: p.Path, Err: syscall.ENOTDIR})
|
||||
} else {
|
||||
rpkt = sshFxpOpenPacket{
|
||||
ID: p.ID,
|
||||
Path: p.Path,
|
||||
Pflags: ssh_FXF_READ,
|
||||
}.respond(s)
|
||||
}
|
||||
return sshFxpOpenPacket{
|
||||
ID: p.ID,
|
||||
Path: p.Path,
|
||||
Pflags: ssh_FXF_READ,
|
||||
}.respond(s)
|
||||
case *sshFxpReadPacket:
|
||||
var err error = syscall.EBADF
|
||||
f, ok := s.getHandle(p.Handle)
|
||||
if !ok {
|
||||
return s.sendError(p, syscall.EBADF)
|
||||
if ok {
|
||||
err = nil
|
||||
data := make([]byte, clamp(p.Len, s.maxTxPacket))
|
||||
n, _err := f.ReadAt(data, int64(p.Offset))
|
||||
if _err != nil && (_err != io.EOF || n == 0) {
|
||||
err = _err
|
||||
}
|
||||
rpkt = sshFxpDataPacket{
|
||||
ID: p.ID,
|
||||
Length: uint32(n),
|
||||
Data: data[:n],
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
rpkt = statusFromError(p, err)
|
||||
}
|
||||
|
||||
data := make([]byte, clamp(p.Len, s.maxTxPacket))
|
||||
n, err := f.ReadAt(data, int64(p.Offset))
|
||||
if err != nil && (err != io.EOF || n == 0) {
|
||||
return s.sendError(p, err)
|
||||
}
|
||||
return s.sendPacket(sshFxpDataPacket{
|
||||
ID: p.ID,
|
||||
Length: uint32(n),
|
||||
Data: data[:n],
|
||||
})
|
||||
case *sshFxpWritePacket:
|
||||
f, ok := s.getHandle(p.Handle)
|
||||
if !ok {
|
||||
return s.sendError(p, syscall.EBADF)
|
||||
var err error = syscall.EBADF
|
||||
if ok {
|
||||
_, err = f.WriteAt(p.Data, int64(p.Offset))
|
||||
}
|
||||
|
||||
_, err := f.WriteAt(p.Data, int64(p.Offset))
|
||||
return s.sendError(p, err)
|
||||
rpkt = statusFromError(p, err)
|
||||
case serverRespondablePacket:
|
||||
err := p.respond(s)
|
||||
return errors.Wrap(err, "pkt.respond failed")
|
||||
rpkt = p.respond(s)
|
||||
default:
|
||||
return errors.Errorf("unexpected packet type %T", p)
|
||||
}
|
||||
|
||||
s.pktMgr.readyPacket(s.pktMgr.newOrderedResponse(rpkt, p.orderId()))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Serve serves SFTP connections until the streams stop or the SFTP subsystem
|
||||
// is stopped.
|
||||
func (svr *Server) Serve() error {
|
||||
var wg sync.WaitGroup
|
||||
runWorker := func(ch requestChan) {
|
||||
runWorker := func(ch chan orderedRequest) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
@@ -324,7 +327,7 @@ func (svr *Server) Serve() error {
|
||||
}
|
||||
}
|
||||
|
||||
pktChan <- pkt
|
||||
pktChan <- svr.pktMgr.newOrderedRequest(pkt)
|
||||
}
|
||||
|
||||
close(pktChan) // shuts down sftpServerWorkers
|
||||
@@ -338,20 +341,6 @@ func (svr *Server) Serve() error {
|
||||
return err // error from recvPacket
|
||||
}
|
||||
|
||||
// Wrap underlying connection methods to use packetManager
|
||||
func (svr *Server) sendPacket(m encoding.BinaryMarshaler) error {
|
||||
if pkt, ok := m.(responsePacket); ok {
|
||||
svr.pktMgr.readyPacket(pkt)
|
||||
} else {
|
||||
return errors.Errorf("unexpected packet type %T", m)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svr *Server) sendError(p ider, err error) error {
|
||||
return svr.sendPacket(statusFromError(p, err))
|
||||
}
|
||||
|
||||
type ider interface {
|
||||
id() uint32
|
||||
}
|
||||
@@ -386,7 +375,7 @@ func (p sshFxpOpenPacket) hasPflags(flags ...uint32) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (p sshFxpOpenPacket) respond(svr *Server) error {
|
||||
func (p sshFxpOpenPacket) respond(svr *Server) responsePacket {
|
||||
var osFlags int
|
||||
if p.hasPflags(ssh_FXF_READ, ssh_FXF_WRITE) {
|
||||
osFlags |= os.O_RDWR
|
||||
@@ -396,7 +385,7 @@ func (p sshFxpOpenPacket) respond(svr *Server) error {
|
||||
osFlags |= os.O_RDONLY
|
||||
} else {
|
||||
// how are they opening?
|
||||
return svr.sendError(p, syscall.EINVAL)
|
||||
return statusFromError(p, syscall.EINVAL)
|
||||
}
|
||||
|
||||
if p.hasPflags(ssh_FXF_APPEND) {
|
||||
@@ -414,23 +403,23 @@ func (p sshFxpOpenPacket) respond(svr *Server) error {
|
||||
|
||||
f, err := os.OpenFile(p.Path, osFlags, 0644)
|
||||
if err != nil {
|
||||
return svr.sendError(p, err)
|
||||
return statusFromError(p, err)
|
||||
}
|
||||
|
||||
handle := svr.nextHandle(f)
|
||||
return svr.sendPacket(sshFxpHandlePacket{p.ID, handle})
|
||||
return sshFxpHandlePacket{ID: p.id(), Handle: handle}
|
||||
}
|
||||
|
||||
func (p sshFxpReaddirPacket) respond(svr *Server) error {
|
||||
func (p sshFxpReaddirPacket) respond(svr *Server) responsePacket {
|
||||
f, ok := svr.getHandle(p.Handle)
|
||||
if !ok {
|
||||
return svr.sendError(p, syscall.EBADF)
|
||||
return statusFromError(p, syscall.EBADF)
|
||||
}
|
||||
|
||||
dirname := f.Name()
|
||||
dirents, err := f.Readdir(128)
|
||||
if err != nil {
|
||||
return svr.sendError(p, err)
|
||||
return statusFromError(p, err)
|
||||
}
|
||||
|
||||
ret := sshFxpNamePacket{ID: p.ID}
|
||||
@@ -441,10 +430,10 @@ func (p sshFxpReaddirPacket) respond(svr *Server) error {
|
||||
Attrs: []interface{}{dirent},
|
||||
})
|
||||
}
|
||||
return svr.sendPacket(ret)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (p sshFxpSetstatPacket) respond(svr *Server) error {
|
||||
func (p sshFxpSetstatPacket) respond(svr *Server) responsePacket {
|
||||
// additional unmarshalling is required for each possibility here
|
||||
b := p.Attrs.([]byte)
|
||||
var err error
|
||||
@@ -483,13 +472,13 @@ func (p sshFxpSetstatPacket) respond(svr *Server) error {
|
||||
}
|
||||
}
|
||||
|
||||
return svr.sendError(p, err)
|
||||
return statusFromError(p, err)
|
||||
}
|
||||
|
||||
func (p sshFxpFsetstatPacket) respond(svr *Server) error {
|
||||
func (p sshFxpFsetstatPacket) respond(svr *Server) responsePacket {
|
||||
f, ok := svr.getHandle(p.Handle)
|
||||
if !ok {
|
||||
return svr.sendError(p, syscall.EBADF)
|
||||
return statusFromError(p, syscall.EBADF)
|
||||
}
|
||||
|
||||
// additional unmarshalling is required for each possibility here
|
||||
@@ -530,7 +519,7 @@ func (p sshFxpFsetstatPacket) respond(svr *Server) error {
|
||||
}
|
||||
}
|
||||
|
||||
return svr.sendError(p, err)
|
||||
return statusFromError(p, err)
|
||||
}
|
||||
|
||||
// translateErrno translates a syscall error number to a SFTP error code.
|
||||
|
||||
+4
-4
@@ -9,17 +9,17 @@ import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func (p sshFxpExtendedPacketStatVFS) respond(svr *Server) error {
|
||||
func (p sshFxpExtendedPacketStatVFS) respond(svr *Server) responsePacket {
|
||||
stat := &syscall.Statfs_t{}
|
||||
if err := syscall.Statfs(p.Path, stat); err != nil {
|
||||
return svr.sendPacket(statusFromError(p, err))
|
||||
return statusFromError(p, err)
|
||||
}
|
||||
|
||||
retPkt, err := statvfsFromStatfst(stat)
|
||||
if err != nil {
|
||||
return svr.sendPacket(statusFromError(p, err))
|
||||
return statusFromError(p, err)
|
||||
}
|
||||
retPkt.ID = p.ID
|
||||
|
||||
return svr.sendPacket(retPkt)
|
||||
return retPkt
|
||||
}
|
||||
|
||||
+2
-2
@@ -6,6 +6,6 @@ import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func (p sshFxpExtendedPacketStatVFS) respond(svr *Server) error {
|
||||
return syscall.ENOTSUP
|
||||
func (p sshFxpExtendedPacketStatVFS) respond(svr *Server) responsePacket {
|
||||
return statusFromError(p, syscall.ENOTSUP)
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// +build darwin dragonfly freebsd !android,linux netbsd openbsd solaris
|
||||
// +build darwin dragonfly freebsd !android,linux netbsd openbsd solaris aix
|
||||
// +build cgo
|
||||
|
||||
package sftp
|
||||
|
||||
+104
@@ -1,6 +1,7 @@
|
||||
package pflag
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
@@ -9,10 +10,12 @@ import (
|
||||
// BytesHex adapts []byte for use as a flag. Value of flag is HEX encoded
|
||||
type bytesHexValue []byte
|
||||
|
||||
// String implements pflag.Value.String.
|
||||
func (bytesHex bytesHexValue) String() string {
|
||||
return fmt.Sprintf("%X", []byte(bytesHex))
|
||||
}
|
||||
|
||||
// Set implements pflag.Value.Set.
|
||||
func (bytesHex *bytesHexValue) Set(value string) error {
|
||||
bin, err := hex.DecodeString(strings.TrimSpace(value))
|
||||
|
||||
@@ -25,6 +28,7 @@ func (bytesHex *bytesHexValue) Set(value string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type implements pflag.Value.Type.
|
||||
func (*bytesHexValue) Type() string {
|
||||
return "bytesHex"
|
||||
}
|
||||
@@ -103,3 +107,103 @@ func BytesHex(name string, value []byte, usage string) *[]byte {
|
||||
func BytesHexP(name, shorthand string, value []byte, usage string) *[]byte {
|
||||
return CommandLine.BytesHexP(name, shorthand, value, usage)
|
||||
}
|
||||
|
||||
// BytesBase64 adapts []byte for use as a flag. Value of flag is Base64 encoded
|
||||
type bytesBase64Value []byte
|
||||
|
||||
// String implements pflag.Value.String.
|
||||
func (bytesBase64 bytesBase64Value) String() string {
|
||||
return base64.StdEncoding.EncodeToString([]byte(bytesBase64))
|
||||
}
|
||||
|
||||
// Set implements pflag.Value.Set.
|
||||
func (bytesBase64 *bytesBase64Value) Set(value string) error {
|
||||
bin, err := base64.StdEncoding.DecodeString(strings.TrimSpace(value))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*bytesBase64 = bin
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type implements pflag.Value.Type.
|
||||
func (*bytesBase64Value) Type() string {
|
||||
return "bytesBase64"
|
||||
}
|
||||
|
||||
func newBytesBase64Value(val []byte, p *[]byte) *bytesBase64Value {
|
||||
*p = val
|
||||
return (*bytesBase64Value)(p)
|
||||
}
|
||||
|
||||
func bytesBase64ValueConv(sval string) (interface{}, error) {
|
||||
|
||||
bin, err := base64.StdEncoding.DecodeString(sval)
|
||||
if err == nil {
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid string being converted to Bytes: %s %s", sval, err)
|
||||
}
|
||||
|
||||
// GetBytesBase64 return the []byte value of a flag with the given name
|
||||
func (f *FlagSet) GetBytesBase64(name string) ([]byte, error) {
|
||||
val, err := f.getFlagType(name, "bytesBase64", bytesBase64ValueConv)
|
||||
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
return val.([]byte), nil
|
||||
}
|
||||
|
||||
// BytesBase64Var defines an []byte flag with specified name, default value, and usage string.
|
||||
// The argument p points to an []byte variable in which to store the value of the flag.
|
||||
func (f *FlagSet) BytesBase64Var(p *[]byte, name string, value []byte, usage string) {
|
||||
f.VarP(newBytesBase64Value(value, p), name, "", usage)
|
||||
}
|
||||
|
||||
// BytesBase64VarP is like BytesBase64Var, but accepts a shorthand letter that can be used after a single dash.
|
||||
func (f *FlagSet) BytesBase64VarP(p *[]byte, name, shorthand string, value []byte, usage string) {
|
||||
f.VarP(newBytesBase64Value(value, p), name, shorthand, usage)
|
||||
}
|
||||
|
||||
// BytesBase64Var defines an []byte flag with specified name, default value, and usage string.
|
||||
// The argument p points to an []byte variable in which to store the value of the flag.
|
||||
func BytesBase64Var(p *[]byte, name string, value []byte, usage string) {
|
||||
CommandLine.VarP(newBytesBase64Value(value, p), name, "", usage)
|
||||
}
|
||||
|
||||
// BytesBase64VarP is like BytesBase64Var, but accepts a shorthand letter that can be used after a single dash.
|
||||
func BytesBase64VarP(p *[]byte, name, shorthand string, value []byte, usage string) {
|
||||
CommandLine.VarP(newBytesBase64Value(value, p), name, shorthand, usage)
|
||||
}
|
||||
|
||||
// BytesBase64 defines an []byte flag with specified name, default value, and usage string.
|
||||
// The return value is the address of an []byte variable that stores the value of the flag.
|
||||
func (f *FlagSet) BytesBase64(name string, value []byte, usage string) *[]byte {
|
||||
p := new([]byte)
|
||||
f.BytesBase64VarP(p, name, "", value, usage)
|
||||
return p
|
||||
}
|
||||
|
||||
// BytesBase64P is like BytesBase64, but accepts a shorthand letter that can be used after a single dash.
|
||||
func (f *FlagSet) BytesBase64P(name, shorthand string, value []byte, usage string) *[]byte {
|
||||
p := new([]byte)
|
||||
f.BytesBase64VarP(p, name, shorthand, value, usage)
|
||||
return p
|
||||
}
|
||||
|
||||
// BytesBase64 defines an []byte flag with specified name, default value, and usage string.
|
||||
// The return value is the address of an []byte variable that stores the value of the flag.
|
||||
func BytesBase64(name string, value []byte, usage string) *[]byte {
|
||||
return CommandLine.BytesBase64P(name, "", value, usage)
|
||||
}
|
||||
|
||||
// BytesBase64P is like BytesBase64, but accepts a shorthand letter that can be used after a single dash.
|
||||
func BytesBase64P(name, shorthand string, value []byte, usage string) *[]byte {
|
||||
return CommandLine.BytesBase64P(name, shorthand, value, usage)
|
||||
}
|
||||
|
||||
+2
-1
@@ -990,11 +990,12 @@ func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []strin
|
||||
}
|
||||
|
||||
func (f *FlagSet) parseSingleShortArg(shorthands string, args []string, fn parseFunc) (outShorts string, outArgs []string, err error) {
|
||||
outArgs = args
|
||||
|
||||
if strings.HasPrefix(shorthands, "test.") {
|
||||
return
|
||||
}
|
||||
|
||||
outArgs = args
|
||||
outShorts = shorthands[1:]
|
||||
c := shorthands[0]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user