github.com/zignig/go-ipfs@v0.0.0-20141111235910-c9e5fdf55a52/core/commands2/object.go (about)

     1  package commands
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"io"
     8  	"io/ioutil"
     9  
    10  	cmds "github.com/jbenet/go-ipfs/commands"
    11  	"github.com/jbenet/go-ipfs/core"
    12  	dag "github.com/jbenet/go-ipfs/merkledag"
    13  )
    14  
    15  // ErrObjectTooLarge is returned when too much data was read from stdin. current limit 512k
    16  var ErrObjectTooLarge = errors.New("input object was too large. limit is 512kbytes")
    17  
    18  const inputLimit = 512 * 1024
    19  
    20  type Node struct {
    21  	Links []Link
    22  	Data  []byte
    23  }
    24  
    25  var objectCmd = &cmds.Command{
    26  	Description: "Interact with ipfs objects",
    27  	Help:        `'ipfs object' is a plumbing command used to manipulate DAG objects directly.`,
    28  
    29  	Subcommands: map[string]*cmds.Command{
    30  		"data":  objectDataCmd,
    31  		"links": objectLinksCmd,
    32  		"get":   objectGetCmd,
    33  		"put":   objectPutCmd,
    34  	},
    35  }
    36  
    37  var objectDataCmd = &cmds.Command{
    38  	Description: "Outputs the raw bytes in an IPFS object",
    39  	Help: `ipfs data is a plumbing command for retreiving the raw bytes stored in a DAG node.
    40  It outputs to stdout, and <key> is a base58 encoded multihash.
    41  
    42  Note that the "--encoding" option does not affect the output, since the
    43  output is the raw data of the object.
    44  `,
    45  
    46  	Arguments: []cmds.Argument{
    47  		cmds.StringArg("key", true, false, "Key of the object to retrieve, in base58-encoded multihash format"),
    48  	},
    49  	Run: func(req cmds.Request) (interface{}, error) {
    50  		n := req.Context().Node
    51  
    52  		key, ok := req.Arguments()[0].(string)
    53  		if !ok {
    54  			return nil, errors.New("cast error")
    55  		}
    56  
    57  		return objectData(n, key)
    58  	},
    59  }
    60  
    61  var objectLinksCmd = &cmds.Command{
    62  	Description: "Outputs the links pointed to by the specified object",
    63  	Help: `'ipfs block get' is a plumbing command for retreiving raw IPFS blocks.
    64  It outputs to stdout, and <key> is a base58 encoded multihash.`,
    65  
    66  	Arguments: []cmds.Argument{
    67  		cmds.StringArg("key", true, false, "Key of the object to retrieve, in base58-encoded multihash format"),
    68  	},
    69  	Run: func(req cmds.Request) (interface{}, error) {
    70  		n := req.Context().Node
    71  
    72  		key, ok := req.Arguments()[0].(string)
    73  		if !ok {
    74  			return nil, errors.New("cast error")
    75  		}
    76  
    77  		return objectLinks(n, key)
    78  	},
    79  	Type: &Object{},
    80  }
    81  
    82  var objectGetCmd = &cmds.Command{
    83  	Description: "Get and serialize the DAG node named by <key>",
    84  	Help: `'ipfs object get' is a plumbing command for retreiving DAG nodes.
    85  It serializes the DAG node to the format specified by the "--encoding" flag.
    86  It outputs to stdout, and <key> is a base58 encoded multihash.
    87  
    88  This command outputs data in the following encodings:
    89    * "protobuf"
    90    * "json"
    91    * "xml"
    92  (Specified by the "--encoding" or "-enc" flags)`,
    93  
    94  	Arguments: []cmds.Argument{
    95  		cmds.StringArg("key", true, false, "Key of the object to retrieve\n(in base58-encoded multihash format)"),
    96  	},
    97  	Run: func(req cmds.Request) (interface{}, error) {
    98  		n := req.Context().Node
    99  
   100  		key, ok := req.Arguments()[0].(string)
   101  		if !ok {
   102  			return nil, errors.New("cast error")
   103  		}
   104  
   105  		object, err := objectGet(n, key)
   106  		if err != nil {
   107  			return nil, err
   108  		}
   109  
   110  		node := &Node{
   111  			Links: make([]Link, len(object.Links)),
   112  			Data:  object.Data,
   113  		}
   114  
   115  		for i, link := range object.Links {
   116  			node.Links[i] = Link{
   117  				Hash: link.Hash.B58String(),
   118  				Name: link.Name,
   119  				Size: link.Size,
   120  			}
   121  		}
   122  
   123  		return node, nil
   124  	},
   125  	Type: &Node{},
   126  	Marshallers: map[cmds.EncodingType]cmds.Marshaller{
   127  		cmds.EncodingType("protobuf"): func(res cmds.Response) ([]byte, error) {
   128  			object := res.Output().(*dag.Node)
   129  			return object.Marshal()
   130  		},
   131  	},
   132  }
   133  
   134  var objectPutCmd = &cmds.Command{
   135  	Description: "Stores input as a DAG object, outputs its key",
   136  	Help: `'ipfs object put' is a plumbing command for storing DAG nodes.
   137  It reads from stdin, and the output is a base58 encoded multihash.
   138  
   139  Data should be in the format specified by <encoding>.
   140  <encoding> may be one of the following:
   141    * "protobuf"
   142    * "json"
   143  `,
   144  
   145  	Arguments: []cmds.Argument{
   146  		cmds.FileArg("data", true, false, "Data to be stored as a DAG object\nMust be encoded as specified in <encoding>"),
   147  		cmds.StringArg("encoding", true, false, "Encoding type of <data>, either \"protobuf\" or \"json\""),
   148  	},
   149  	Run: func(req cmds.Request) (interface{}, error) {
   150  		n := req.Context().Node
   151  
   152  		input, ok := req.Arguments()[0].(io.Reader)
   153  		if !ok {
   154  			return nil, errors.New("cast error")
   155  		}
   156  
   157  		encoding, ok := req.Arguments()[1].(string)
   158  		if !ok {
   159  			return nil, errors.New("cast error")
   160  		}
   161  
   162  		output, err := objectPut(n, input, encoding)
   163  		if err != nil {
   164  			errType := cmds.ErrNormal
   165  			if err == ErrUnknownObjectEnc {
   166  				errType = cmds.ErrClient
   167  			}
   168  			return nil, cmds.Error{err.Error(), errType}
   169  		}
   170  
   171  		return output, nil
   172  	},
   173  	Type: &Object{},
   174  }
   175  
   176  // objectData takes a key string and writes out the raw bytes of that node (if there is one)
   177  func objectData(n *core.IpfsNode, key string) (io.Reader, error) {
   178  	dagnode, err := n.Resolver.ResolvePath(key)
   179  	if err != nil {
   180  		return nil, err
   181  	}
   182  
   183  	log.Debugf("objectData: found dagnode %q (# of bytes: %d - # links: %d)", key, len(dagnode.Data), len(dagnode.Links))
   184  
   185  	return bytes.NewReader(dagnode.Data), nil
   186  }
   187  
   188  // objectLinks takes a key string and lists the links it points to
   189  func objectLinks(n *core.IpfsNode, key string) (*Object, error) {
   190  	dagnode, err := n.Resolver.ResolvePath(key)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  
   195  	log.Debugf("objectLinks: found dagnode %q (# of bytes: %d - # links: %d)", key, len(dagnode.Data), len(dagnode.Links))
   196  
   197  	return getOutput(dagnode)
   198  }
   199  
   200  // objectGet takes a key string from args and a format option and serializes the dagnode to that format
   201  func objectGet(n *core.IpfsNode, key string) (*dag.Node, error) {
   202  	dagnode, err := n.Resolver.ResolvePath(key)
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  
   207  	log.Debugf("objectGet: found dagnode %q (# of bytes: %d - # links: %d)", key, len(dagnode.Data), len(dagnode.Links))
   208  
   209  	return dagnode, nil
   210  }
   211  
   212  // objectPut takes a format option, serializes bytes from stdin and updates the dag with that data
   213  func objectPut(n *core.IpfsNode, input io.Reader, encoding string) (*Object, error) {
   214  	var (
   215  		dagnode *dag.Node
   216  		data    []byte
   217  		err     error
   218  	)
   219  
   220  	data, err = ioutil.ReadAll(io.LimitReader(input, inputLimit+10))
   221  	if err != nil {
   222  		return nil, err
   223  	}
   224  
   225  	if len(data) >= inputLimit {
   226  		return nil, ErrObjectTooLarge
   227  	}
   228  
   229  	switch getObjectEnc(encoding) {
   230  	case objectEncodingJSON:
   231  		dagnode = new(dag.Node)
   232  		err = json.Unmarshal(data, dagnode)
   233  
   234  	case objectEncodingProtobuf:
   235  		dagnode, err = dag.Decoded(data)
   236  
   237  	default:
   238  		return nil, ErrUnknownObjectEnc
   239  	}
   240  
   241  	if err != nil {
   242  		return nil, err
   243  	}
   244  
   245  	err = addNode(n, dagnode)
   246  	if err != nil {
   247  		return nil, err
   248  	}
   249  
   250  	return getOutput(dagnode)
   251  }
   252  
   253  // ErrUnknownObjectEnc is returned if a invalid encoding is supplied
   254  var ErrUnknownObjectEnc = errors.New("unknown object encoding")
   255  
   256  type objectEncoding string
   257  
   258  const (
   259  	objectEncodingJSON     objectEncoding = "json"
   260  	objectEncodingProtobuf                = "protobuf"
   261  )
   262  
   263  func getObjectEnc(o interface{}) objectEncoding {
   264  	v, ok := o.(string)
   265  	if !ok {
   266  		// chosen as default because it's human readable
   267  		log.Warning("option is not a string - falling back to json")
   268  		return objectEncodingJSON
   269  	}
   270  
   271  	return objectEncoding(v)
   272  }
   273  
   274  func getOutput(dagnode *dag.Node) (*Object, error) {
   275  	key, err := dagnode.Key()
   276  	if err != nil {
   277  		return nil, err
   278  	}
   279  
   280  	output := &Object{
   281  		Hash:  key.Pretty(),
   282  		Links: make([]Link, len(dagnode.Links)),
   283  	}
   284  
   285  	for i, link := range dagnode.Links {
   286  		output.Links[i] = Link{
   287  			Name: link.Name,
   288  			Hash: link.Hash.B58String(),
   289  			Size: link.Size,
   290  		}
   291  	}
   292  
   293  	return output, nil
   294  }