diff --git a/third_party/src/code.google.com/p/google-api-go-client/googleapi/googleapi.go b/third_party/src/code.google.com/p/google-api-go-client/googleapi/googleapi.go new file mode 100644 index 00000000000..65e77d068c6 --- /dev/null +++ b/third_party/src/code.google.com/p/google-api-go-client/googleapi/googleapi.go @@ -0,0 +1,307 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package googleapi contains the common code shared by all Google API +// libraries. +package googleapi + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "mime/multipart" + "net/http" + "net/textproto" + "net/url" + "os" + "strings" +) + +// ContentTyper is an interface for Readers which know (or would like +// to override) their Content-Type. If a media body doesn't implement +// ContentTyper, the type is sniffed from the content using +// http.DetectContentType. +type ContentTyper interface { + ContentType() string +} + +const Version = "0.5" + +// Error contains an error response from the server. +type Error struct { + // Code is the HTTP response status code and will always be populated. + Code int `json:"code"` + // Message is the server response message and is only populated when + // explicitly referenced by the JSON server response. + Message string `json:"message"` + // Body is the raw response returned by the server. + // It is often but not always JSON, depending on how the request fails. + Body string +} + +func (e *Error) Error() string { + if e.Message != "" { + return fmt.Sprintf("googleapi: Error %d: %v", e.Code, e.Message) + } + return fmt.Sprintf("googleapi: got HTTP response code %d with body: %v", e.Code, e.Body) +} + +type errorReply struct { + Error *Error `json:"error"` +} + +// CheckResponse returns an error (of type *Error) if the response +// status code is not 2xx. +func CheckResponse(res *http.Response) error { + if res.StatusCode >= 200 && res.StatusCode <= 299 { + return nil + } + slurp, err := ioutil.ReadAll(res.Body) + if err == nil { + jerr := new(errorReply) + err = json.Unmarshal(slurp, jerr) + if err == nil && jerr.Error != nil { + if jerr.Error.Code == 0 { + jerr.Error.Code = res.StatusCode + } + jerr.Error.Body = string(slurp) + return jerr.Error + } + } + return &Error{ + Code: res.StatusCode, + Body: string(slurp), + } +} + +type MarshalStyle bool + +var WithDataWrapper = MarshalStyle(true) +var WithoutDataWrapper = MarshalStyle(false) + +func (wrap MarshalStyle) JSONReader(v interface{}) (io.Reader, error) { + buf := new(bytes.Buffer) + if wrap { + buf.Write([]byte(`{"data": `)) + } + err := json.NewEncoder(buf).Encode(v) + if err != nil { + return nil, err + } + if wrap { + buf.Write([]byte(`}`)) + } + return buf, nil +} + +func getMediaType(media io.Reader) (io.Reader, string) { + if typer, ok := media.(ContentTyper); ok { + return media, typer.ContentType() + } + + typ := "application/octet-stream" + buf := make([]byte, 1024) + n, err := media.Read(buf) + buf = buf[:n] + if err == nil { + typ = http.DetectContentType(buf) + } + return io.MultiReader(bytes.NewBuffer(buf), media), typ +} + +type Lengther interface { + Len() int +} + +// endingWithErrorReader from r until it returns an error. If the +// final error from r is os.EOF and e is non-nil, e is used instead. +type endingWithErrorReader struct { + r io.Reader + e error +} + +func (er endingWithErrorReader) Read(p []byte) (n int, err error) { + n, err = er.r.Read(p) + if err == io.EOF && er.e != nil { + err = er.e + } + return +} + +func getReaderSize(r io.Reader) (io.Reader, int64) { + // Ideal case, the reader knows its own size. + if lr, ok := r.(Lengther); ok { + return r, int64(lr.Len()) + } + + // But maybe it's a seeker and we can seek to the end to find its size. + if s, ok := r.(io.Seeker); ok { + pos0, err := s.Seek(0, os.SEEK_CUR) + if err == nil { + posend, err := s.Seek(0, os.SEEK_END) + if err == nil { + _, err = s.Seek(pos0, os.SEEK_SET) + if err == nil { + return r, posend - pos0 + } else { + // We moved it forward but can't restore it. + // Seems unlikely, but can't really restore now. + return endingWithErrorReader{strings.NewReader(""), err}, posend - pos0 + } + } + } + } + + // Otherwise we have to make a copy to calculate how big the reader is. + buf := new(bytes.Buffer) + // TODO(bradfitz): put a cap on this copy? spill to disk after + // a certain point? + _, err := io.Copy(buf, r) + return endingWithErrorReader{buf, err}, int64(buf.Len()) +} + +func typeHeader(contentType string) textproto.MIMEHeader { + h := make(textproto.MIMEHeader) + h.Set("Content-Type", contentType) + return h +} + +// countingWriter counts the number of bytes it receives to write, but +// discards them. +type countingWriter struct { + n *int64 +} + +func (w countingWriter) Write(p []byte) (int, error) { + *w.n += int64(len(p)) + return len(p), nil +} + +// ConditionallyIncludeMedia does nothing if media is nil. +// +// bodyp is an in/out parameter. It should initially point to the +// reader of the application/json (or whatever) payload to send in the +// API request. It's updated to point to the multipart body reader. +// +// ctypep is an in/out parameter. It should initially point to the +// content type of the bodyp, usually "application/json". It's updated +// to the "multipart/related" content type, with random boundary. +// +// The return value is the content-length of the entire multpart body. +func ConditionallyIncludeMedia(media io.Reader, bodyp *io.Reader, ctypep *string) (totalContentLength int64, ok bool) { + if media == nil { + return + } + // Get the media type and size. The type check might return a + // different reader instance, so do the size check first, + // which looks at the specific type of the io.Reader. + var mediaType string + if typer, ok := media.(ContentTyper); ok { + mediaType = typer.ContentType() + } + media, mediaSize := getReaderSize(media) + if mediaType == "" { + media, mediaType = getMediaType(media) + } + body, bodyType := *bodyp, *ctypep + body, bodySize := getReaderSize(body) + + // Calculate how big the the multipart will be. + { + totalContentLength = bodySize + mediaSize + mpw := multipart.NewWriter(countingWriter{&totalContentLength}) + mpw.CreatePart(typeHeader(bodyType)) + mpw.CreatePart(typeHeader(mediaType)) + mpw.Close() + } + + pr, pw := io.Pipe() + mpw := multipart.NewWriter(pw) + *bodyp = pr + *ctypep = "multipart/related; boundary=" + mpw.Boundary() + go func() { + defer pw.Close() + defer mpw.Close() + + w, err := mpw.CreatePart(typeHeader(bodyType)) + if err != nil { + return + } + _, err = io.Copy(w, body) + if err != nil { + return + } + + w, err = mpw.CreatePart(typeHeader(mediaType)) + if err != nil { + return + } + _, err = io.Copy(w, media) + if err != nil { + return + } + }() + return totalContentLength, true +} + +func ResolveRelative(basestr, relstr string) string { + u, _ := url.Parse(basestr) + rel, _ := url.Parse(relstr) + u = u.ResolveReference(rel) + us := u.String() + us = strings.Replace(us, "%7B", "{", -1) + us = strings.Replace(us, "%7D", "}", -1) + return us +} + +// has4860Fix is whether this Go environment contains the fix for +// http://golang.org/issue/4860 +var has4860Fix bool + +// init initializes has4860Fix by checking the behavior of the net/http package. +func init() { + r := http.Request{ + URL: &url.URL{ + Scheme: "http", + Opaque: "//opaque", + }, + } + b := &bytes.Buffer{} + r.Write(b) + has4860Fix = bytes.HasPrefix(b.Bytes(), []byte("GET http")) +} + +// SetOpaque sets u.Opaque from u.Path such that HTTP requests to it +// don't alter any hex-escaped characters in u.Path. +func SetOpaque(u *url.URL) { + u.Opaque = "//" + u.Host + u.Path + if !has4860Fix { + u.Opaque = u.Scheme + ":" + u.Opaque + } +} + +// CloseBody is used to close res.Body. +// Prior to calling Close, it also tries to Read a small amount to see an EOF. +// Not seeing an EOF can prevent HTTP Transports from reusing connections. +func CloseBody(res *http.Response) { + if res == nil || res.Body == nil { + return + } + // Justification for 3 byte reads: two for up to "\r\n" after + // a JSON/XML document, and then 1 to see EOF if we haven't yet. + // TODO(bradfitz): detect Go 1.3+ and skip these reads. + // See https://codereview.appspot.com/58240043 + // and https://codereview.appspot.com/49570044 + buf := make([]byte, 1) + for i := 0; i < 3; i++ { + _, err := res.Body.Read(buf) + if err != nil { + break + } + } + res.Body.Close() + +} diff --git a/third_party/src/code.google.com/p/google-api-go-client/googleapi/googleapi_test.go b/third_party/src/code.google.com/p/google-api-go-client/googleapi/googleapi_test.go new file mode 100644 index 00000000000..e646665e3da --- /dev/null +++ b/third_party/src/code.google.com/p/google-api-go-client/googleapi/googleapi_test.go @@ -0,0 +1,152 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package googleapi + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "reflect" + "strings" + "testing" +) + +type SetOpaqueTest struct { + in *url.URL + wantRequestURI string +} + +var setOpaqueTests = []SetOpaqueTest{ + // no path + { + &url.URL{ + Scheme: "http", + Host: "www.golang.org", + }, + "http://www.golang.org", + }, + // path + { + &url.URL{ + Scheme: "http", + Host: "www.golang.org", + Path: "/", + }, + "http://www.golang.org/", + }, + // file with hex escaping + { + &url.URL{ + Scheme: "https", + Host: "www.golang.org", + Path: "/file%20one&two", + }, + "https://www.golang.org/file%20one&two", + }, + // query + { + &url.URL{ + Scheme: "http", + Host: "www.golang.org", + Path: "/", + RawQuery: "q=go+language", + }, + "http://www.golang.org/?q=go+language", + }, + // file with hex escaping in path plus query + { + &url.URL{ + Scheme: "https", + Host: "www.golang.org", + Path: "/file%20one&two", + RawQuery: "q=go+language", + }, + "https://www.golang.org/file%20one&two?q=go+language", + }, + // query with hex escaping + { + &url.URL{ + Scheme: "http", + Host: "www.golang.org", + Path: "/", + RawQuery: "q=go%20language", + }, + "http://www.golang.org/?q=go%20language", + }, +} + +// prefixTmpl is a template for the expected prefix of the output of writing +// an HTTP request. +const prefixTmpl = "GET %v HTTP/1.1\r\nHost: %v\r\n" + +func TestSetOpaque(t *testing.T) { + for _, test := range setOpaqueTests { + u := *test.in + SetOpaque(&u) + + w := &bytes.Buffer{} + r := &http.Request{URL: &u} + if err := r.Write(w); err != nil { + t.Errorf("write request: %v", err) + continue + } + + prefix := fmt.Sprintf(prefixTmpl, test.wantRequestURI, test.in.Host) + if got := string(w.Bytes()); !strings.HasPrefix(got, prefix) { + t.Errorf("got %q expected prefix %q", got, prefix) + } + } +} + +type CheckResponseTest struct { + in *http.Response + bodyText string + want error +} + +var checkResponseTests = []CheckResponseTest{ + { + &http.Response{ + StatusCode: http.StatusOK, + }, + "", + nil, + }, + { + &http.Response{ + StatusCode: http.StatusNotFound, + }, + `{"error":{"message":"Error message for StatusNotFound."}}`, + &Error{ + Code: http.StatusNotFound, + Message: "Error message for StatusNotFound.", + Body: `{"error":{"message":"Error message for StatusNotFound."}}`, + }, + }, + { + &http.Response{ + StatusCode: http.StatusBadRequest, + }, + `{"error":"invalid_token","error_description":"Invalid Value"}`, + &Error{ + Code: http.StatusBadRequest, + Body: `{"error":"invalid_token","error_description":"Invalid Value"}`, + }, + }, +} + +func TestCheckResponse(t *testing.T) { + for _, test := range checkResponseTests { + res := test.in + if test.bodyText != "" { + res.Body = ioutil.NopCloser(strings.NewReader(test.bodyText)) + } + if g := CheckResponse(res); !reflect.DeepEqual(g, test.want) { + t.Errorf("CheckResponse: got %v, want %v", g, test.want) + } + } +} diff --git a/third_party/src/code.google.com/p/google-api-go-client/googleapi/transport/apikey.go b/third_party/src/code.google.com/p/google-api-go-client/googleapi/transport/apikey.go new file mode 100644 index 00000000000..eca1ea25077 --- /dev/null +++ b/third_party/src/code.google.com/p/google-api-go-client/googleapi/transport/apikey.go @@ -0,0 +1,38 @@ +// Copyright 2012 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package transport contains HTTP transports used to make +// authenticated API requests. +package transport + +import ( + "errors" + "net/http" +) + +// APIKey is an HTTP Transport which wraps an underlying transport and +// appends an API Key "key" parameter to the URL of outgoing requests. +type APIKey struct { + // Key is the API Key to set on requests. + Key string + + // Transport is the underlying HTTP transport. + // If nil, http.DefaultTransport is used. + Transport http.RoundTripper +} + +func (t *APIKey) RoundTrip(req *http.Request) (*http.Response, error) { + rt := t.Transport + if rt == nil { + rt = http.DefaultTransport + if rt == nil { + return nil, errors.New("googleapi/transport: no Transport specified or available") + } + } + newReq := *req + args := newReq.URL.Query() + args.Set("key", t.Key) + newReq.URL.RawQuery = args.Encode() + return rt.RoundTrip(&newReq) +} diff --git a/third_party/src/code.google.com/p/google-api-go-client/googleapi/types.go b/third_party/src/code.google.com/p/google-api-go-client/googleapi/types.go new file mode 100644 index 00000000000..7ed7dd98233 --- /dev/null +++ b/third_party/src/code.google.com/p/google-api-go-client/googleapi/types.go @@ -0,0 +1,150 @@ +// Copyright 2013 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package googleapi + +import ( + "encoding/json" + "strconv" +) + +// Int64s is a slice of int64s that marshal as quoted strings in JSON. +type Int64s []int64 + +func (q *Int64s) UnmarshalJSON(raw []byte) error { + *q = (*q)[:0] + var ss []string + if err := json.Unmarshal(raw, &ss); err != nil { + return err + } + for _, s := range ss { + v, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return err + } + *q = append(*q, int64(v)) + } + return nil +} + +// Int32s is a slice of int32s that marshal as quoted strings in JSON. +type Int32s []int32 + +func (q *Int32s) UnmarshalJSON(raw []byte) error { + *q = (*q)[:0] + var ss []string + if err := json.Unmarshal(raw, &ss); err != nil { + return err + } + for _, s := range ss { + v, err := strconv.ParseInt(s, 10, 32) + if err != nil { + return err + } + *q = append(*q, int32(v)) + } + return nil +} + +// Uint64s is a slice of uint64s that marshal as quoted strings in JSON. +type Uint64s []uint64 + +func (q *Uint64s) UnmarshalJSON(raw []byte) error { + *q = (*q)[:0] + var ss []string + if err := json.Unmarshal(raw, &ss); err != nil { + return err + } + for _, s := range ss { + v, err := strconv.ParseUint(s, 10, 64) + if err != nil { + return err + } + *q = append(*q, uint64(v)) + } + return nil +} + +// Uint32s is a slice of uint32s that marshal as quoted strings in JSON. +type Uint32s []uint32 + +func (q *Uint32s) UnmarshalJSON(raw []byte) error { + *q = (*q)[:0] + var ss []string + if err := json.Unmarshal(raw, &ss); err != nil { + return err + } + for _, s := range ss { + v, err := strconv.ParseUint(s, 10, 32) + if err != nil { + return err + } + *q = append(*q, uint32(v)) + } + return nil +} + +// Float64s is a slice of float64s that marshal as quoted strings in JSON. +type Float64s []float64 + +func (q *Float64s) UnmarshalJSON(raw []byte) error { + *q = (*q)[:0] + var ss []string + if err := json.Unmarshal(raw, &ss); err != nil { + return err + } + for _, s := range ss { + v, err := strconv.ParseFloat(s, 64) + if err != nil { + return err + } + *q = append(*q, float64(v)) + } + return nil +} + +func quotedList(n int, fn func(dst []byte, i int) []byte) ([]byte, error) { + dst := make([]byte, 0, 2+n*10) // somewhat arbitrary + dst = append(dst, '[') + for i := 0; i < n; i++ { + if i > 0 { + dst = append(dst, ',') + } + dst = append(dst, '"') + dst = fn(dst, i) + dst = append(dst, '"') + } + dst = append(dst, ']') + return dst, nil +} + +func (s Int64s) MarshalJSON() ([]byte, error) { + return quotedList(len(s), func(dst []byte, i int) []byte { + return strconv.AppendInt(dst, s[i], 10) + }) +} + +func (s Int32s) MarshalJSON() ([]byte, error) { + return quotedList(len(s), func(dst []byte, i int) []byte { + return strconv.AppendInt(dst, int64(s[i]), 10) + }) +} + +func (s Uint64s) MarshalJSON() ([]byte, error) { + return quotedList(len(s), func(dst []byte, i int) []byte { + return strconv.AppendUint(dst, s[i], 10) + }) +} + +func (s Uint32s) MarshalJSON() ([]byte, error) { + return quotedList(len(s), func(dst []byte, i int) []byte { + return strconv.AppendUint(dst, uint64(s[i]), 10) + }) +} + +func (s Float64s) MarshalJSON() ([]byte, error) { + return quotedList(len(s), func(dst []byte, i int) []byte { + return strconv.AppendFloat(dst, s[i], 'g', -1, 64) + }) +} diff --git a/third_party/src/code.google.com/p/google-api-go-client/googleapi/types_test.go b/third_party/src/code.google.com/p/google-api-go-client/googleapi/types_test.go new file mode 100644 index 00000000000..a6b2045156e --- /dev/null +++ b/third_party/src/code.google.com/p/google-api-go-client/googleapi/types_test.go @@ -0,0 +1,44 @@ +// Copyright 2013 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package googleapi + +import ( + "encoding/json" + "reflect" + "testing" +) + +func TestTypes(t *testing.T) { + type T struct { + I32 Int32s + I64 Int64s + U32 Uint32s + U64 Uint64s + F64 Float64s + } + v := &T{ + I32: Int32s{-1, 2, 3}, + I64: Int64s{-1, 2, 1 << 33}, + U32: Uint32s{1, 2}, + U64: Uint64s{1, 2, 1 << 33}, + F64: Float64s{1.5, 3.33}, + } + got, err := json.Marshal(v) + if err != nil { + t.Fatal(err) + } + want := `{"I32":["-1","2","3"],"I64":["-1","2","8589934592"],"U32":["1","2"],"U64":["1","2","8589934592"],"F64":["1.5","3.33"]}` + if string(got) != want { + t.Fatalf("Marshal mismatch.\n got: %s\nwant: %s\n", got, want) + } + + v2 := new(T) + if err := json.Unmarshal(got, v2); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + if !reflect.DeepEqual(v, v2) { + t.Fatalf("Unmarshal didn't produce same results.\n got: %#v\nwant: %#v\n", v, v2) + } +}