From 85058dfd832ee60449561b8a121002540d8b25f6 Mon Sep 17 00:00:00 2001 From: William Chong Date: Sun, 2 Jun 2024 00:52:25 +0800 Subject: [PATCH] [webhook] support cbor as metadata format --- webhook/attribute.go | 6 +++--- webhook/client.go | 32 ++++++++++++++++++++++++++++---- webhook/webhook.go | 26 ++++++++++++++++---------- 3 files changed, 47 insertions(+), 17 deletions(-) diff --git a/webhook/attribute.go b/webhook/attribute.go index e5e8fe0..c77cc67 100644 --- a/webhook/attribute.go +++ b/webhook/attribute.go @@ -4,13 +4,13 @@ import ( "github.com/starlinglab/integrity-v2/aa" ) -// ParseJsonToAttributes parses a JSON map and a file stat map +// ParseMapToAttributes parses a map and a file stat map // to a slice of attributes for POSTing to the AA server -func ParseJsonToAttributes(jsonMap map[string]any, fileAttributes map[string]any) []aa.PostKV { +func ParseMapToAttributes(attrMap map[string]any, fileAttributes map[string]any) []aa.PostKV { var attributes []aa.PostKV - for k, v := range jsonMap { + for k, v := range attrMap { // TODO: add whitelist/blacklist for attributes in config if k != "private" { attributes = append(attributes, aa.PostKV{Key: k, Value: v}) diff --git a/webhook/client.go b/webhook/client.go index a21097d..c4bf98c 100644 --- a/webhook/client.go +++ b/webhook/client.go @@ -7,10 +7,13 @@ import ( "io" "mime/multipart" "net/http" + "net/textproto" urlpkg "net/url" "os" "path/filepath" + "strings" + "github.com/fxamacker/cbor/v2" "github.com/starlinglab/integrity-v2/config" ) @@ -20,6 +23,7 @@ var client = &http.Client{} type PostGenericWebhookOpt struct { Source string // Source is the origin of the asset, which is used to determine the webhook endpoint ProjectId string // ProjectId is the project specific ID where the asset belongs + Format string // Format is "json" or "cbor" } type PostGenericWebhookResponse struct { @@ -27,6 +31,16 @@ type PostGenericWebhookResponse struct { Error error `json:"error,omitempty"` } +// createFormFieldWithContentType is a modified multipart.Writer.CreateFormField +// which allow setting of content-type +func createFormFieldWithContentType(w *multipart.Writer, fieldName string, contentType string) (io.Writer, error) { + h := make(textproto.MIMEHeader) + h.Set("Content-Disposition", + fmt.Sprintf(`form-data; name="%s"`, strings.NewReplacer("\\", "\\\\", `"`, "\\\"").Replace(fieldName))) + h.Set("Content-Type", contentType) + return w.CreatePart(h) +} + // PostFileToWebHook posts a file and its metadata to the webhook server func PostFileToWebHook(filePath string, metadata map[string]any, opts PostGenericWebhookOpt) (*PostGenericWebhookResponse, error) { sourcePath := opts.Source @@ -44,13 +58,23 @@ func PostFileToWebHook(filePath string, metadata map[string]any, opts PostGeneri pr, pw := io.Pipe() mp := multipart.NewWriter(pw) + metadataFormatType := "application/json" + if opts.Format == "cbor" { + metadataFormatType = "application/cbor" + } + go func() { - metadataString, err := json.Marshal(metadata) + metadataPart, err := createFormFieldWithContentType(mp, "metadata", metadataFormatType) if err != nil { pw.CloseWithError(err) return } - err = mp.WriteField("metadata", string(metadataString)) + if metadataFormatType == "application/cbor" { + err = cbor.NewEncoder(metadataPart).Encode(metadata) + + } else { + err = json.NewEncoder(metadataPart).Encode(metadata) + } if err != nil { pw.CloseWithError(err) return @@ -61,12 +85,12 @@ func PostFileToWebHook(filePath string, metadata map[string]any, opts PostGeneri return } defer file.Close() - part, err := mp.CreateFormFile("file", filepath.Base(filePath)) + filePart, err := mp.CreateFormFile("file", filepath.Base(filePath)) if err != nil { pw.CloseWithError(err) return } - _, err = io.Copy(part, file) + _, err = io.Copy(filePart, file) if err != nil { pw.CloseWithError(err) return diff --git a/webhook/webhook.go b/webhook/webhook.go index 3148f98..ea01eba 100644 --- a/webhook/webhook.go +++ b/webhook/webhook.go @@ -11,6 +11,7 @@ import ( "os" "path/filepath" + "github.com/fxamacker/cbor/v2" "github.com/go-chi/chi/v5" "github.com/go-chi/jwtauth/v5" "github.com/starlinglab/integrity-v2/aa" @@ -73,7 +74,6 @@ func handleGenericFileUpload(w http.ResponseWriter, r *http.Request) { writeJsonResponse(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } - metadataString := []byte{} outputDirectory, err := getFileOutputDirectory() if err != nil { @@ -89,6 +89,7 @@ func handleGenericFileUpload(w http.ResponseWriter, r *http.Request) { defer os.Remove(tempFile.Name()) cid := "" fileAttributes := map[string]any{} + var metadataMap map[string]any for { part, err := form.NextPart() if err == io.EOF { @@ -98,13 +99,24 @@ func handleGenericFileUpload(w http.ResponseWriter, r *http.Request) { return } if part.FormName() == "metadata" { - metadataString, err = io.ReadAll(part) + metadataFormatType := part.Header.Get("Content-Type") + metadataValue, err := io.ReadAll(part) defer part.Close() if err != nil { writeJsonResponse(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } - //do something with files + switch metadataFormatType { + case "application/cbor": + err = cbor.Unmarshal(metadataValue, &metadataMap) + case "application/json": + err = json.Unmarshal(metadataValue, &metadataMap) + } + if err != nil { + writeJsonResponse(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + return + } + } else if part.FormName() == "file" { pr, pw := io.Pipe() cidChan := make(chan string, 1) @@ -161,13 +173,7 @@ func handleGenericFileUpload(w http.ResponseWriter, r *http.Request) { return } - var jsonMap map[string]any - err = json.Unmarshal(metadataString, &jsonMap) - if err != nil { - writeJsonResponse(w, http.StatusBadRequest, map[string]string{"error": err.Error()}) - return - } - attributes := ParseJsonToAttributes(jsonMap, fileAttributes) + attributes := ParseMapToAttributes(metadataMap, fileAttributes) err = aa.SetAttestations(cid, false, attributes) if err != nil { fmt.Println("Error setting attestations:", err)