2020-10-16 09:51:43 +00:00
package neofs
import (
"context"
"errors"
"fmt"
"net/url"
"strconv"
"strings"
2021-03-12 11:19:04 +00:00
"unicode/utf8"
2020-10-16 09:51:43 +00:00
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
2021-03-12 11:19:04 +00:00
"github.com/nspcc-dev/neo-go/pkg/util"
2021-11-23 16:36:59 +00:00
"github.com/nspcc-dev/neofs-sdk-go/client"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
"github.com/nspcc-dev/neofs-sdk-go/object"
2020-10-16 09:51:43 +00:00
)
const (
// URIScheme is the name of neofs URI scheme.
URIScheme = "neofs"
// rangeSep is a separator between offset and length.
rangeSep = '|'
rangeCmd = "range"
headerCmd = "header"
hashCmd = "hash"
)
// Various validation errors.
var (
ErrInvalidScheme = errors . New ( "invalid URI scheme" )
ErrMissingObject = errors . New ( "object ID is missing from URI" )
ErrInvalidContainer = errors . New ( "container ID is invalid" )
ErrInvalidObject = errors . New ( "object ID is invalid" )
ErrInvalidRange = errors . New ( "object range is invalid (expected 'Offset|Length')" )
ErrInvalidCommand = errors . New ( "invalid command" )
)
2022-04-20 18:30:09 +00:00
// Get returns a neofs object from the provided url.
2021-04-06 13:56:19 +00:00
// URI scheme is "neofs:<Container-ID>/<Object-ID/<Command>/<Params>".
2020-10-16 09:51:43 +00:00
// If Command is not provided, full object is requested.
func Get ( ctx context . Context , priv * keys . PrivateKey , u * url . URL , addr string ) ( [ ] byte , error ) {
objectAddr , ps , err := parseNeoFSURL ( u )
if err != nil {
return nil , err
}
2022-01-13 13:28:23 +00:00
c , err := client . New (
client . WithDefaultPrivateKey ( & priv . PrivateKey ) ,
client . WithURIAddress ( addr , nil ) ,
client . WithNeoFSErrorParsing ( ) ,
)
2020-10-16 09:51:43 +00:00
if err != nil {
return nil , err
}
switch {
case len ( ps ) == 0 || ps [ 0 ] == "" : // Get request
return getPayload ( ctx , c , objectAddr )
case ps [ 0 ] == rangeCmd :
return getRange ( ctx , c , objectAddr , ps [ 1 : ] ... )
case ps [ 0 ] == headerCmd :
return getHeader ( ctx , c , objectAddr )
case ps [ 0 ] == hashCmd :
return getHash ( ctx , c , objectAddr , ps [ 1 : ] ... )
default :
return nil , ErrInvalidCommand
}
}
// parseNeoFSURL returns parsed neofs address.
func parseNeoFSURL ( u * url . URL ) ( * object . Address , [ ] string , error ) {
if u . Scheme != URIScheme {
return nil , nil , ErrInvalidScheme
}
2021-04-06 13:56:19 +00:00
ps := strings . Split ( u . Opaque , "/" )
if len ( ps ) < 2 {
2020-10-16 09:51:43 +00:00
return nil , nil , ErrMissingObject
}
2021-06-07 11:20:21 +00:00
containerID := cid . New ( )
2021-04-06 13:56:19 +00:00
if err := containerID . Parse ( ps [ 0 ] ) ; err != nil {
2023-03-15 12:47:38 +00:00
return nil , nil , fmt . Errorf ( "%w: %v" , ErrInvalidContainer , err ) //nolint:errorlint // errorlint: non-wrapping format verb for fmt.Errorf. Use `%w` to format errors
2020-10-16 09:51:43 +00:00
}
objectID := object . NewID ( )
2021-04-06 13:56:19 +00:00
if err := objectID . Parse ( ps [ 1 ] ) ; err != nil {
2023-03-15 12:47:38 +00:00
return nil , nil , fmt . Errorf ( "%w: %v" , ErrInvalidObject , err ) //nolint:errorlint // errorlint: non-wrapping format verb for fmt.Errorf. Use `%w` to format errors
2020-10-16 09:51:43 +00:00
}
objectAddr := object . NewAddress ( )
objectAddr . SetContainerID ( containerID )
objectAddr . SetObjectID ( objectID )
2021-04-06 13:56:19 +00:00
return objectAddr , ps [ 2 : ] , nil
2020-10-16 09:51:43 +00:00
}
2022-01-13 13:28:23 +00:00
func getPayload ( ctx context . Context , c * client . Client , addr * object . Address ) ( [ ] byte , error ) {
2021-11-23 16:36:59 +00:00
res , err := c . GetObject ( ctx , new ( client . GetObjectParams ) . WithAddress ( addr ) )
2022-01-13 13:28:23 +00:00
if err != nil {
2021-11-23 16:36:59 +00:00
return nil , err
2020-10-16 09:51:43 +00:00
}
2021-11-23 16:36:59 +00:00
return checkUTF8 ( res . Object ( ) . Payload ( ) )
2020-10-16 09:51:43 +00:00
}
2022-01-13 13:28:23 +00:00
func getRange ( ctx context . Context , c * client . Client , addr * object . Address , ps ... string ) ( [ ] byte , error ) {
2020-10-16 09:51:43 +00:00
if len ( ps ) == 0 {
return nil , ErrInvalidRange
}
r , err := parseRange ( ps [ 0 ] )
if err != nil {
return nil , err
}
2021-11-23 16:36:59 +00:00
res , err := c . ObjectPayloadRangeData ( ctx , new ( client . RangeDataParams ) . WithAddress ( addr ) . WithRange ( r ) )
2022-01-13 13:28:23 +00:00
if err != nil {
2021-11-23 16:36:59 +00:00
return nil , err
2021-03-12 11:19:04 +00:00
}
2021-11-23 16:36:59 +00:00
return checkUTF8 ( res . Data ( ) )
2020-10-16 09:51:43 +00:00
}
2022-01-13 13:28:23 +00:00
func getHeader ( ctx context . Context , c * client . Client , addr * object . Address ) ( [ ] byte , error ) {
2021-11-23 16:36:59 +00:00
res , err := c . HeadObject ( ctx , new ( client . ObjectHeaderParams ) . WithAddress ( addr ) )
2022-01-13 13:28:23 +00:00
if err != nil {
2021-11-23 16:36:59 +00:00
return nil , err
2020-10-16 09:51:43 +00:00
}
2021-11-23 16:36:59 +00:00
return res . Object ( ) . MarshalHeaderJSON ( )
2020-10-16 09:51:43 +00:00
}
2022-01-13 13:28:23 +00:00
func getHash ( ctx context . Context , c * client . Client , addr * object . Address , ps ... string ) ( [ ] byte , error ) {
2020-10-16 09:51:43 +00:00
if len ( ps ) == 0 || ps [ 0 ] == "" { // hash of the full payload
2021-11-23 16:36:59 +00:00
res , err := c . HeadObject ( ctx , new ( client . ObjectHeaderParams ) . WithAddress ( addr ) )
2022-01-13 13:28:23 +00:00
if err != nil {
2021-11-23 16:36:59 +00:00
return nil , err
2020-10-16 09:51:43 +00:00
}
2021-11-23 16:36:59 +00:00
return res . Object ( ) . PayloadChecksum ( ) . Sum ( ) , nil
2020-10-16 09:51:43 +00:00
}
r , err := parseRange ( ps [ 0 ] )
if err != nil {
return nil , err
}
2021-11-23 16:36:59 +00:00
res , err := c . HashObjectPayloadRanges ( ctx ,
2020-10-16 09:51:43 +00:00
new ( client . RangeChecksumParams ) . WithAddress ( addr ) . WithRangeList ( r ) )
2022-01-13 13:28:23 +00:00
if err != nil {
2021-11-23 16:36:59 +00:00
return nil , err
2020-10-16 09:51:43 +00:00
}
2021-11-23 16:36:59 +00:00
hashes := res . Hashes ( )
2020-10-16 09:51:43 +00:00
if len ( hashes ) == 0 {
return nil , fmt . Errorf ( "%w: empty response" , ErrInvalidRange )
}
2021-11-23 16:36:59 +00:00
u256 , err := util . Uint256DecodeBytesBE ( hashes [ 0 ] )
if err != nil {
return nil , fmt . Errorf ( "decode Uint256: %w" , err )
}
return u256 . MarshalJSON ( )
2020-10-16 09:51:43 +00:00
}
func parseRange ( s string ) ( * object . Range , error ) {
sepIndex := strings . IndexByte ( s , rangeSep )
if sepIndex < 0 {
return nil , ErrInvalidRange
}
offset , err := strconv . ParseUint ( s [ : sepIndex ] , 10 , 64 )
if err != nil {
return nil , fmt . Errorf ( "%w: invalid offset" , ErrInvalidRange )
}
length , err := strconv . ParseUint ( s [ sepIndex + 1 : ] , 10 , 64 )
if err != nil {
return nil , fmt . Errorf ( "%w: invalid length" , ErrInvalidRange )
}
r := object . NewRange ( )
r . SetOffset ( offset )
r . SetLength ( length )
return r , nil
}
2021-03-12 11:19:04 +00:00
func checkUTF8 ( v [ ] byte ) ( [ ] byte , error ) {
if ! utf8 . Valid ( v ) {
return nil , errors . New ( "invalid UTF-8" )
}
return v , nil
}