diff --git a/api/tangled/cbor_gen.go b/api/tangled/cbor_gen.go index 1c7a5ea7..1177fe43 100644 --- a/api/tangled/cbor_gen.go +++ b/api/tangled/cbor_gen.go @@ -11324,8 +11324,25 @@ func (t *String) MarshalCBOR(w io.Writer) error { } cw := cbg.NewCborWriter(w) + fieldCount := 7 - if _, err := cw.Write([]byte{165}); err != nil { + if t.Contents == nil { + fieldCount-- + } + + if t.Description == nil { + fieldCount-- + } + + if t.Filename == nil { + fieldCount-- + } + + if t.Title == nil { + fieldCount-- + } + + if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil { return err } @@ -11348,50 +11365,126 @@ func (t *String) MarshalCBOR(w io.Writer) error { return err } - // t.Contents (string) (string) - if len("contents") > 1000000 { - return xerrors.Errorf("Value in field \"contents\" was too long") + // t.Files ([]*tangled.String_File) (slice) + if len("files") > 1000000 { + return xerrors.Errorf("Value in field \"files\" was too long") } - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("contents"))); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("files"))); err != nil { return err } - if _, err := cw.WriteString(string("contents")); err != nil { + if _, err := cw.WriteString(string("files")); err != nil { return err } - if len(t.Contents) > 1000000 { - return xerrors.Errorf("Value in field t.Contents was too long") + if len(t.Files) > 8192 { + return xerrors.Errorf("Slice value in field t.Files was too long") } - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Contents))); err != nil { - return err - } - if _, err := cw.WriteString(string(t.Contents)); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Files))); err != nil { return err } + for _, v := range t.Files { + if err := v.MarshalCBOR(cw); err != nil { + return err + } - // t.Filename (string) (string) - if len("filename") > 1000000 { - return xerrors.Errorf("Value in field \"filename\" was too long") } - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("filename"))); err != nil { - return err - } - if _, err := cw.WriteString(string("filename")); err != nil { - return err - } + // t.Title (string) (string) + if t.Title != nil { + + if len("title") > 1000000 { + return xerrors.Errorf("Value in field \"title\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("title"))); err != nil { + return err + } + if _, err := cw.WriteString(string("title")); err != nil { + return err + } + + if t.Title == nil { + if _, err := cw.Write(cbg.CborNull); err != nil { + return err + } + } else { + if len(*t.Title) > 1000000 { + return xerrors.Errorf("Value in field t.Title was too long") + } - if len(t.Filename) > 1000000 { - return xerrors.Errorf("Value in field t.Filename was too long") + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.Title))); err != nil { + return err + } + if _, err := cw.WriteString(string(*t.Title)); err != nil { + return err + } + } } - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Filename))); err != nil { - return err + // t.Contents (string) (string) + if t.Contents != nil { + + if len("contents") > 1000000 { + return xerrors.Errorf("Value in field \"contents\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("contents"))); err != nil { + return err + } + if _, err := cw.WriteString(string("contents")); err != nil { + return err + } + + if t.Contents == nil { + if _, err := cw.Write(cbg.CborNull); err != nil { + return err + } + } else { + if len(*t.Contents) > 1000000 { + return xerrors.Errorf("Value in field t.Contents was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.Contents))); err != nil { + return err + } + if _, err := cw.WriteString(string(*t.Contents)); err != nil { + return err + } + } } - if _, err := cw.WriteString(string(t.Filename)); err != nil { - return err + + // t.Filename (string) (string) + if t.Filename != nil { + + if len("filename") > 1000000 { + return xerrors.Errorf("Value in field \"filename\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("filename"))); err != nil { + return err + } + if _, err := cw.WriteString(string("filename")); err != nil { + return err + } + + if t.Filename == nil { + if _, err := cw.Write(cbg.CborNull); err != nil { + return err + } + } else { + if len(*t.Filename) > 1000000 { + return xerrors.Errorf("Value in field t.Filename was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.Filename))); err != nil { + return err + } + if _, err := cw.WriteString(string(*t.Filename)); err != nil { + return err + } + } } // t.CreatedAt (string) (string) @@ -11418,26 +11511,35 @@ func (t *String) MarshalCBOR(w io.Writer) error { } // t.Description (string) (string) - if len("description") > 1000000 { - return xerrors.Errorf("Value in field \"description\" was too long") - } + if t.Description != nil { - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("description"))); err != nil { - return err - } - if _, err := cw.WriteString(string("description")); err != nil { - return err - } + if len("description") > 1000000 { + return xerrors.Errorf("Value in field \"description\" was too long") + } - if len(t.Description) > 1000000 { - return xerrors.Errorf("Value in field t.Description was too long") - } + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("description"))); err != nil { + return err + } + if _, err := cw.WriteString(string("description")); err != nil { + return err + } - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Description))); err != nil { - return err - } - if _, err := cw.WriteString(string(t.Description)); err != nil { - return err + if t.Description == nil { + if _, err := cw.Write(cbg.CborNull); err != nil { + return err + } + } else { + if len(*t.Description) > 1000000 { + return xerrors.Errorf("Value in field t.Description was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.Description))); err != nil { + return err + } + if _, err := cw.WriteString(string(*t.Description)); err != nil { + return err + } + } } return nil } @@ -11494,27 +11596,117 @@ func (t *String) UnmarshalCBOR(r io.Reader) (err error) { t.LexiconTypeID = string(sval) } + // t.Files ([]*tangled.String_File) (slice) + case "files": + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > 8192 { + return fmt.Errorf("t.Files: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.Files = make([]*String_File, extra) + } + + for i := 0; i < int(extra); i++ { + { + var maj byte + var extra uint64 + var err error + _ = maj + _ = extra + _ = err + + { + + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + t.Files[i] = new(String_File) + if err := t.Files[i].UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.Files[i] pointer: %w", err) + } + } + + } + + } + } + // t.Title (string) (string) + case "title": + + { + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + + sval, err := cbg.ReadStringWithMax(cr, 1000000) + if err != nil { + return err + } + + t.Title = (*string)(&sval) + } + } // t.Contents (string) (string) case "contents": { - sval, err := cbg.ReadStringWithMax(cr, 1000000) + b, err := cr.ReadByte() if err != nil { return err } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + + sval, err := cbg.ReadStringWithMax(cr, 1000000) + if err != nil { + return err + } - t.Contents = string(sval) + t.Contents = (*string)(&sval) + } } // t.Filename (string) (string) case "filename": { - sval, err := cbg.ReadStringWithMax(cr, 1000000) + b, err := cr.ReadByte() if err != nil { return err } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + + sval, err := cbg.ReadStringWithMax(cr, 1000000) + if err != nil { + return err + } - t.Filename = string(sval) + t.Filename = (*string)(&sval) + } } // t.CreatedAt (string) (string) case "createdAt": @@ -11530,13 +11722,352 @@ func (t *String) UnmarshalCBOR(r io.Reader) (err error) { // t.Description (string) (string) case "description": + { + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + + sval, err := cbg.ReadStringWithMax(cr, 1000000) + if err != nil { + return err + } + + t.Description = (*string)(&sval) + } + } + + default: + // Field doesn't exist on this type, so ignore it + if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil { + return err + } + } + } + + return nil +} +func (t *String_File) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + fieldCount := 3 + + if t.Gzip == nil { + fieldCount-- + } + + if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil { + return err + } + + // t.Gzip (tangled.String_File_Gzip) (struct) + if t.Gzip != nil { + + if len("gzip") > 1000000 { + return xerrors.Errorf("Value in field \"gzip\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("gzip"))); err != nil { + return err + } + if _, err := cw.WriteString(string("gzip")); err != nil { + return err + } + + if err := t.Gzip.MarshalCBOR(cw); err != nil { + return err + } + } + + // t.Name (string) (string) + if len("name") > 1000000 { + return xerrors.Errorf("Value in field \"name\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("name"))); err != nil { + return err + } + if _, err := cw.WriteString(string("name")); err != nil { + return err + } + + if len(t.Name) > 1000000 { + return xerrors.Errorf("Value in field t.Name was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Name))); err != nil { + return err + } + if _, err := cw.WriteString(string(t.Name)); err != nil { + return err + } + + // t.Content (util.LexBlob) (struct) + if len("content") > 1000000 { + return xerrors.Errorf("Value in field \"content\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("content"))); err != nil { + return err + } + if _, err := cw.WriteString(string("content")); err != nil { + return err + } + + if err := t.Content.MarshalCBOR(cw); err != nil { + return err + } + return nil +} + +func (t *String_File) UnmarshalCBOR(r io.Reader) (err error) { + *t = String_File{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("String_File: map struct too large (%d)", extra) + } + + n := extra + + nameBuf := make([]byte, 7) + for i := uint64(0); i < n; i++ { + nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000) + if err != nil { + return err + } + + if !ok { + // Field doesn't exist on this type, so ignore it + if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil { + return err + } + continue + } + + switch string(nameBuf[:nameLen]) { + // t.Gzip (tangled.String_File_Gzip) (struct) + case "gzip": + + { + + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + t.Gzip = new(String_File_Gzip) + if err := t.Gzip.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.Gzip pointer: %w", err) + } + } + + } + // t.Name (string) (string) + case "name": + { sval, err := cbg.ReadStringWithMax(cr, 1000000) if err != nil { return err } - t.Description = string(sval) + t.Name = string(sval) + } + // t.Content (util.LexBlob) (struct) + case "content": + + { + + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + t.Content = new(util.LexBlob) + if err := t.Content.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.Content pointer: %w", err) + } + } + + } + + default: + // Field doesn't exist on this type, so ignore it + if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil { + return err + } + } + } + + return nil +} +func (t *String_File_Gzip) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write([]byte{162}); err != nil { + return err + } + + // t.RealMime (string) (string) + if len("realMime") > 1000000 { + return xerrors.Errorf("Value in field \"realMime\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("realMime"))); err != nil { + return err + } + if _, err := cw.WriteString(string("realMime")); err != nil { + return err + } + + if len(t.RealMime) > 1000000 { + return xerrors.Errorf("Value in field t.RealMime was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.RealMime))); err != nil { + return err + } + if _, err := cw.WriteString(string(t.RealMime)); err != nil { + return err + } + + // t.RealSize (int64) (int64) + if len("realSize") > 1000000 { + return xerrors.Errorf("Value in field \"realSize\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("realSize"))); err != nil { + return err + } + if _, err := cw.WriteString(string("realSize")); err != nil { + return err + } + + if t.RealSize >= 0 { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.RealSize)); err != nil { + return err + } + } else { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.RealSize-1)); err != nil { + return err + } + } + + return nil +} + +func (t *String_File_Gzip) UnmarshalCBOR(r io.Reader) (err error) { + *t = String_File_Gzip{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("String_File_Gzip: map struct too large (%d)", extra) + } + + n := extra + + nameBuf := make([]byte, 8) + for i := uint64(0); i < n; i++ { + nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000) + if err != nil { + return err + } + + if !ok { + // Field doesn't exist on this type, so ignore it + if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil { + return err + } + continue + } + + switch string(nameBuf[:nameLen]) { + // t.RealMime (string) (string) + case "realMime": + + { + sval, err := cbg.ReadStringWithMax(cr, 1000000) + if err != nil { + return err + } + + t.RealMime = string(sval) + } + // t.RealSize (int64) (int64) + case "realSize": + { + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + var extraI int64 + switch maj { + case cbg.MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case cbg.MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative overflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + t.RealSize = int64(extraI) } default: diff --git a/api/tangled/tangledstring.go b/api/tangled/tangledstring.go index 0644c712..1d4224da 100644 --- a/api/tangled/tangledstring.go +++ b/api/tangled/tangledstring.go @@ -18,8 +18,28 @@ func init() { // RECORDTYPE: String type String struct { LexiconTypeID string `json:"$type,const=sh.tangled.string" cborgen:"$type,const=sh.tangled.string"` - Contents string `json:"contents" cborgen:"contents"` - CreatedAt string `json:"createdAt" cborgen:"createdAt"` - Description string `json:"description" cborgen:"description"` - Filename string `json:"filename" cborgen:"filename"` + // contents: DEPRECATED - use files[0].content instead + Contents *string `json:"contents,omitempty" cborgen:"contents,omitempty"` + CreatedAt string `json:"createdAt" cborgen:"createdAt"` + Description *string `json:"description,omitempty" cborgen:"description,omitempty"` + // filename: DEPRECATED - use title instead + Filename *string `json:"filename,omitempty" cborgen:"filename,omitempty"` + Files []*String_File `json:"files" cborgen:"files"` + Title *string `json:"title,omitempty" cborgen:"title,omitempty"` +} + +// String_File is a "file" in the sh.tangled.string schema. +type String_File struct { + // content: file content + Content *util.LexBlob `json:"content" cborgen:"content"` + // gzip: underlying blob metadata. provided when we are using gzip hack for text/* mimetype + Gzip *String_File_Gzip `json:"gzip,omitempty" cborgen:"gzip,omitempty"` + // name: filename including extension + Name string `json:"name" cborgen:"name"` +} + +// underlying blob metadata. provided when we are using gzip hack for text/* mimetype +type String_File_Gzip struct { + RealMime string `json:"realMime" cborgen:"realMime"` + RealSize int64 `json:"realSize" cborgen:"realSize"` } diff --git a/cmd/cborgen/cborgen.go b/cmd/cborgen/cborgen.go index 072f8600..01963daa 100644 --- a/cmd/cborgen/cborgen.go +++ b/cmd/cborgen/cborgen.go @@ -61,6 +61,8 @@ func main() { tangled.Spindle{}, tangled.SpindleMember{}, tangled.String{}, + tangled.String_File{}, + tangled.String_File_Gzip{}, ); err != nil { panic(err) } diff --git a/lexicons/string/string.json b/lexicons/string/string.json index 9ac59db0..e0eda03b 100644 --- a/lexicons/string/string.json +++ b/lexicons/string/string.json @@ -10,20 +10,25 @@ "record": { "type": "object", "required": [ - "filename", - "description", "createdAt", - "contents" + "files" ], "properties": { "filename": { + "type": "string", + "maxGraphemes": 140, + "minGraphemes": 1, + "description": "DEPRECATED - use title instead" + }, + "title": { "type": "string", "maxGraphemes": 140, "minGraphemes": 1 }, "description": { "type": "string", - "maxGraphemes": 280 + "maxGraphemes": 280, + "minGraphemes": 1 }, "createdAt": { "type": "string", @@ -31,10 +36,50 @@ }, "contents": { "type": "string", - "minGraphemes": 1 + "minGraphemes": 1, + "description": "DEPRECATED - use files[0].content instead" + }, + "files": { + "type": "array", + "minLength": 1, + "maxLength": 50, + "items": { + "type": "ref", + "ref": "#file" + } } } } + }, + "file": { + "type": "object", + "required": [ + "name", + "content" + ], + "properties": { + "name": { + "type": "string", + "maxGraphemes": 140, + "minGraphemes": 1, + "description": "filename including extension" + }, + "content": { + "type": "blob", + "accept": ["text/plain", "application/gzip"], + "maxSize": 8388608, + "description": "file content" + }, + "gzip": { + "type": "object", + "required": ["realSize", "realMime"], + "properties": { + "realSize": { "type": "integer" }, + "realMime": { "type": "string" } + }, + "description": "underlying blob metadata. provided when we are using gzip hack for text/* mimetype" + } + } } } }