diff --git a/dropbox/files/client.go b/dropbox/files/client.go index 4231554..c211a38 100644 --- a/dropbox/files/client.go +++ b/dropbox/files/client.go @@ -481,7 +481,7 @@ func (dbx *apiImpl) AlphaUpload(arg *CommitInfoWithProperties, content io.Reader headers := map[string]string{ "Content-Type": "application/octet-stream", - "Dropbox-API-Arg": string(b), + "Dropbox-API-Arg": dropbox.HTTPHeaderSafeJSON(b), } if dbx.Config.AsMemberID != "" { headers["Dropbox-API-Select-User"] = dbx.Config.AsMemberID @@ -1640,7 +1640,7 @@ func (dbx *apiImpl) Download(arg *DownloadArg) (res *FileMetadata, content io.Re } headers := map[string]string{ - "Dropbox-API-Arg": string(b), + "Dropbox-API-Arg": dropbox.HTTPHeaderSafeJSON(b), } for k, v := range arg.ExtraHeaders { headers[k] = v @@ -1710,7 +1710,7 @@ func (dbx *apiImpl) DownloadZip(arg *DownloadZipArg) (res *DownloadZipResult, co } headers := map[string]string{ - "Dropbox-API-Arg": string(b), + "Dropbox-API-Arg": dropbox.HTTPHeaderSafeJSON(b), } if dbx.Config.AsMemberID != "" { headers["Dropbox-API-Select-User"] = dbx.Config.AsMemberID @@ -1777,7 +1777,7 @@ func (dbx *apiImpl) Export(arg *ExportArg) (res *ExportResult, content io.ReadCl } headers := map[string]string{ - "Dropbox-API-Arg": string(b), + "Dropbox-API-Arg": dropbox.HTTPHeaderSafeJSON(b), } if dbx.Config.AsMemberID != "" { headers["Dropbox-API-Select-User"] = dbx.Config.AsMemberID @@ -1921,7 +1921,7 @@ func (dbx *apiImpl) GetPreview(arg *PreviewArg) (res *FileMetadata, content io.R } headers := map[string]string{ - "Dropbox-API-Arg": string(b), + "Dropbox-API-Arg": dropbox.HTTPHeaderSafeJSON(b), } if dbx.Config.AsMemberID != "" { headers["Dropbox-API-Select-User"] = dbx.Config.AsMemberID @@ -2120,7 +2120,7 @@ func (dbx *apiImpl) GetThumbnail(arg *ThumbnailArg) (res *FileMetadata, content } headers := map[string]string{ - "Dropbox-API-Arg": string(b), + "Dropbox-API-Arg": dropbox.HTTPHeaderSafeJSON(b), } if dbx.Config.AsMemberID != "" { headers["Dropbox-API-Select-User"] = dbx.Config.AsMemberID @@ -2187,7 +2187,7 @@ func (dbx *apiImpl) GetThumbnailBatch(arg *GetThumbnailBatchArg) (res *GetThumbn } headers := map[string]string{ - "Dropbox-API-Arg": string(b), + "Dropbox-API-Arg": dropbox.HTTPHeaderSafeJSON(b), } if dbx.Config.AsMemberID != "" { headers["Dropbox-API-Select-User"] = dbx.Config.AsMemberID @@ -3696,7 +3696,7 @@ func (dbx *apiImpl) Upload(arg *CommitInfo, content io.Reader) (res *FileMetadat headers := map[string]string{ "Content-Type": "application/octet-stream", - "Dropbox-API-Arg": string(b), + "Dropbox-API-Arg": dropbox.HTTPHeaderSafeJSON(b), } if dbx.Config.AsMemberID != "" { headers["Dropbox-API-Select-User"] = dbx.Config.AsMemberID @@ -3763,7 +3763,7 @@ func (dbx *apiImpl) UploadSessionAppendV2(arg *UploadSessionAppendArg, content i headers := map[string]string{ "Content-Type": "application/octet-stream", - "Dropbox-API-Arg": string(b), + "Dropbox-API-Arg": dropbox.HTTPHeaderSafeJSON(b), } if dbx.Config.AsMemberID != "" { headers["Dropbox-API-Select-User"] = dbx.Config.AsMemberID @@ -3828,7 +3828,7 @@ func (dbx *apiImpl) UploadSessionAppend(arg *UploadSessionCursor, content io.Rea headers := map[string]string{ "Content-Type": "application/octet-stream", - "Dropbox-API-Arg": string(b), + "Dropbox-API-Arg": dropbox.HTTPHeaderSafeJSON(b), } if dbx.Config.AsMemberID != "" { headers["Dropbox-API-Select-User"] = dbx.Config.AsMemberID @@ -3890,7 +3890,7 @@ func (dbx *apiImpl) UploadSessionFinish(arg *UploadSessionFinishArg, content io. headers := map[string]string{ "Content-Type": "application/octet-stream", - "Dropbox-API-Arg": string(b), + "Dropbox-API-Arg": dropbox.HTTPHeaderSafeJSON(b), } if dbx.Config.AsMemberID != "" { headers["Dropbox-API-Select-User"] = dbx.Config.AsMemberID @@ -4089,7 +4089,7 @@ func (dbx *apiImpl) UploadSessionStart(arg *UploadSessionStartArg, content io.Re headers := map[string]string{ "Content-Type": "application/octet-stream", - "Dropbox-API-Arg": string(b), + "Dropbox-API-Arg": dropbox.HTTPHeaderSafeJSON(b), } if dbx.Config.AsMemberID != "" { headers["Dropbox-API-Select-User"] = dbx.Config.AsMemberID diff --git a/dropbox/paper/client.go b/dropbox/paper/client.go index 73464e4..99f07ee 100644 --- a/dropbox/paper/client.go +++ b/dropbox/paper/client.go @@ -178,7 +178,7 @@ func (dbx *apiImpl) DocsCreate(arg *PaperDocCreateArgs, content io.Reader) (res headers := map[string]string{ "Content-Type": "application/octet-stream", - "Dropbox-API-Arg": string(b), + "Dropbox-API-Arg": dropbox.HTTPHeaderSafeJSON(b), } if dbx.Config.AsMemberID != "" { headers["Dropbox-API-Select-User"] = dbx.Config.AsMemberID @@ -244,7 +244,7 @@ func (dbx *apiImpl) DocsDownload(arg *PaperDocExport) (res *PaperDocExportResult } headers := map[string]string{ - "Dropbox-API-Arg": string(b), + "Dropbox-API-Arg": dropbox.HTTPHeaderSafeJSON(b), } if dbx.Config.AsMemberID != "" { headers["Dropbox-API-Select-User"] = dbx.Config.AsMemberID @@ -830,7 +830,7 @@ func (dbx *apiImpl) DocsUpdate(arg *PaperDocUpdateArgs, content io.Reader) (res headers := map[string]string{ "Content-Type": "application/octet-stream", - "Dropbox-API-Arg": string(b), + "Dropbox-API-Arg": dropbox.HTTPHeaderSafeJSON(b), } if dbx.Config.AsMemberID != "" { headers["Dropbox-API-Select-User"] = dbx.Config.AsMemberID diff --git a/dropbox/sdk.go b/dropbox/sdk.go index 702a37a..46c4966 100644 --- a/dropbox/sdk.go +++ b/dropbox/sdk.go @@ -21,6 +21,7 @@ package dropbox import ( + "bytes" "encoding/json" "fmt" "io" @@ -221,3 +222,20 @@ func HandleCommonAPIErrors(c Config, resp *http.Response, body []byte) error { } return apiError } + +// HTTPHeaderSafeJSON encode the JSON passed in b []byte passed in in +// a way that is suitable for HTTP headers. +// +// See: https://www.dropbox.com/developers/reference/json-encoding +func HTTPHeaderSafeJSON(b []byte) string { + var s bytes.Buffer + s.Grow(len(b)) + for _, r := range string(b) { + if r >= 0x007f { + fmt.Fprintf(&s, "\\u%04x", r) + } else { + s.WriteRune(r) + } + } + return s.String() +} diff --git a/dropbox/sdk_test.go b/dropbox/sdk_test.go index 29bba73..cb61d03 100644 --- a/dropbox/sdk_test.go +++ b/dropbox/sdk_test.go @@ -21,6 +21,7 @@ package dropbox_test import ( + "encoding/json" "fmt" "net/http" "net/http/httptest" @@ -169,3 +170,57 @@ func TestAccessError(t *testing.T) { t.Errorf("Unexpected tag: %s\n", re.AccessError.PaperAccessDenied.Tag) } } + +func TestHTTPHeaderSafeJSON(t *testing.T) { + for _, test := range []struct { + name string + in interface{} + want string + }{ + { + name: "empty string", + in: ``, + want: `""`, + }, + { + name: "integer", + in: 123, + want: `123`, + }, + { + name: "normal string", + in: `Normal string!`, + want: `"Normal string!"`, + }, + { + name: "unicode", + in: `üñîcødé`, + want: `"\u00fc\u00f1\u00eec\u00f8d\u00e9"`, + }, + { + name: "7f", + in: "\x7f", + want: `"\u007f"`, + }, + { + name: "example from the docs", + in: struct { + Field string `json:"field"` + }{ + Field: "some_üñîcødé_and_\x7F", + }, + want: `{"field":"some_\u00fc\u00f1\u00eec\u00f8d\u00e9_and_\u007f"}`, + }, + } { + t.Run(test.name, func(t *testing.T) { + b, err := json.Marshal(test.in) + if err != nil { + t.Fatal(err) + } + got := dropbox.HTTPHeaderSafeJSON(b) + if got != test.want { + t.Errorf("Want %q got %q", test.want, got) + } + }) + } +} diff --git a/dropbox/sharing/client.go b/dropbox/sharing/client.go index 4112ccd..41f6f28 100644 --- a/dropbox/sharing/client.go +++ b/dropbox/sharing/client.go @@ -966,7 +966,7 @@ func (dbx *apiImpl) GetSharedLinkFile(arg *GetSharedLinkMetadataArg) (res IsShar } headers := map[string]string{ - "Dropbox-API-Arg": string(b), + "Dropbox-API-Arg": dropbox.HTTPHeaderSafeJSON(b), } if dbx.Config.AsMemberID != "" { headers["Dropbox-API-Select-User"] = dbx.Config.AsMemberID diff --git a/generator/go_client.stoneg.py b/generator/go_client.stoneg.py index a0ff34f..45250c9 100644 --- a/generator/go_client.stoneg.py +++ b/generator/go_client.stoneg.py @@ -125,7 +125,7 @@ def _generate_request(self, namespace, route): headers = {} if not is_void_type(route.arg_data_type): if host == 'content' or style in ['upload', 'download']: - headers["Dropbox-API-Arg"] = "string(b)" + headers["Dropbox-API-Arg"] = "dropbox.HTTPHeaderSafeJSON(b)" else: headers["Content-Type"] = '"application/json"' if style == 'upload': diff --git a/generator/go_rsrc/sdk.go b/generator/go_rsrc/sdk.go index 8e4edb6..9a00a01 100644 --- a/generator/go_rsrc/sdk.go +++ b/generator/go_rsrc/sdk.go @@ -26,6 +26,7 @@ import ( "io" "log" "net/http" + "strings" "golang.org/x/net/context" "golang.org/x/oauth2" @@ -221,3 +222,20 @@ func HandleCommonAPIErrors(c Config, resp *http.Response, body []byte) error { } return apiError } + +// HTTPHeaderSafeJSON encode the JSON passed in b []byte passed in in +// a way that is suitable for HTTP headers. +// +// See: https://www.dropbox.com/developers/reference/json-encoding +func HTTPHeaderSafeJSON(b []byte) string { + var s strings.Builder + s.Grow(len(b)) + for _, r := range string(b) { + if r >= 0x007f { + fmt.Fprintf(&s, "\\u%04x", r) + } else { + s.WriteRune(r) + } + } + return s.String() +}