pax_global_header00006660000000000000000000000064137025563740014526gustar00rootroot0000000000000052 comment=4a59c23d842443cd891d7c319fa28b85d8ea9685 golang-github-kolo-xmlrpc-0.0~git20200310.e035052/000077500000000000000000000000001370255637400207755ustar00rootroot00000000000000golang-github-kolo-xmlrpc-0.0~git20200310.e035052/LICENSE000066400000000000000000000020431370255637400220010ustar00rootroot00000000000000Copyright (C) 2012 Dmitry Maksimov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-github-kolo-xmlrpc-0.0~git20200310.e035052/README.md000066400000000000000000000056071370255637400222640ustar00rootroot00000000000000[![GoDoc](https://godoc.org/github.com/kolo/xmlrpc?status.svg)](https://godoc.org/github.com/kolo/xmlrpc) ## Overview xmlrpc is an implementation of client side part of XMLRPC protocol in Go language. ## Status This project is in minimal maintenance mode with no further development. Bug fixes are accepted, but it might take some time until they will be merged. ## Installation To install xmlrpc package run `go get github.com/kolo/xmlrpc`. To use it in application add `"github.com/kolo/xmlrpc"` string to `import` statement. ## Usage client, _ := xmlrpc.NewClient("https://bugzilla.mozilla.org/xmlrpc.cgi", nil) result := struct{ Version string `xmlrpc:"version"` }{} client.Call("Bugzilla.version", nil, &result) fmt.Printf("Version: %s\n", result.Version) // Version: 4.2.7+ Second argument of NewClient function is an object that implements [http.RoundTripper](http://golang.org/pkg/net/http/#RoundTripper) interface, it can be used to get more control over connection options. By default it initialized by http.DefaultTransport object. ### Arguments encoding xmlrpc package supports encoding of native Go data types to method arguments. Data types encoding rules: * int, int8, int16, int32, int64 encoded to int; * float32, float64 encoded to double; * bool encoded to boolean; * string encoded to string; * time.Time encoded to datetime.iso8601; * xmlrpc.Base64 encoded to base64; * slice encoded to array; Structs encoded to struct by following rules: * all public field become struct members; * field name become member name; * if field has xmlrpc tag, its value become member name. * for fields tagged with `",omitempty"`, empty values are omitted; Server method can accept few arguments, to handle this case there is special approach to handle slice of empty interfaces (`[]interface{}`). Each value of such slice encoded as separate argument. ### Result decoding Result of remote function is decoded to native Go data type. Data types decoding rules: * int, i4 decoded to int, int8, int16, int32, int64; * double decoded to float32, float64; * boolean decoded to bool; * string decoded to string; * array decoded to slice; * structs decoded following the rules described in previous section; * datetime.iso8601 decoded as time.Time data type; * base64 decoded to string. ## Implementation details xmlrpc package contains clientCodec type, that implements [rpc.ClientCodec](http://golang.org/pkg/net/rpc/#ClientCodec) interface of [net/rpc](http://golang.org/pkg/net/rpc) package. xmlrpc package works over HTTP protocol, but some internal functions and data type were made public to make it easier to create another implementation of xmlrpc that works over another protocol. To encode request body there is EncodeMethodCall function. To decode server response Response data type can be used. ## Contribution See [project status](#status). ## Authors Dmitry Maksimov (dmtmax@gmail.com) golang-github-kolo-xmlrpc-0.0~git20200310.e035052/client.go000066400000000000000000000063631370255637400226120ustar00rootroot00000000000000package xmlrpc import ( "errors" "fmt" "io/ioutil" "net/http" "net/http/cookiejar" "net/rpc" "net/url" "sync" ) type Client struct { *rpc.Client } // clientCodec is rpc.ClientCodec interface implementation. type clientCodec struct { // url presents url of xmlrpc service url *url.URL // httpClient works with HTTP protocol httpClient *http.Client // cookies stores cookies received on last request cookies http.CookieJar // responses presents map of active requests. It is required to return request id, that // rpc.Client can mark them as done. responses map[uint64]*http.Response mutex sync.Mutex response Response // ready presents channel, that is used to link request and it`s response. ready chan uint64 // close notifies codec is closed. close chan uint64 } func (codec *clientCodec) WriteRequest(request *rpc.Request, args interface{}) (err error) { httpRequest, err := NewRequest(codec.url.String(), request.ServiceMethod, args) if err != nil { return err } if codec.cookies != nil { for _, cookie := range codec.cookies.Cookies(codec.url) { httpRequest.AddCookie(cookie) } } var httpResponse *http.Response httpResponse, err = codec.httpClient.Do(httpRequest) if err != nil { return err } if codec.cookies != nil { codec.cookies.SetCookies(codec.url, httpResponse.Cookies()) } codec.mutex.Lock() codec.responses[request.Seq] = httpResponse codec.mutex.Unlock() codec.ready <- request.Seq return nil } func (codec *clientCodec) ReadResponseHeader(response *rpc.Response) (err error) { var seq uint64 select { case seq = <-codec.ready: case <-codec.close: return errors.New("codec is closed") } response.Seq = seq codec.mutex.Lock() httpResponse := codec.responses[seq] delete(codec.responses, seq) codec.mutex.Unlock() defer httpResponse.Body.Close() if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 300 { response.Error = fmt.Sprintf("request error: bad status code - %d", httpResponse.StatusCode) return nil } body, err := ioutil.ReadAll(httpResponse.Body) if err != nil { response.Error = err.Error() return nil } resp := Response(body) if err := resp.Err(); err != nil { response.Error = err.Error() return nil } codec.response = resp return nil } func (codec *clientCodec) ReadResponseBody(v interface{}) (err error) { if v == nil { return nil } return codec.response.Unmarshal(v) } func (codec *clientCodec) Close() error { if transport, ok := codec.httpClient.Transport.(*http.Transport); ok { transport.CloseIdleConnections() } close(codec.close) return nil } // NewClient returns instance of rpc.Client object, that is used to send request to xmlrpc service. func NewClient(requrl string, transport http.RoundTripper) (*Client, error) { if transport == nil { transport = http.DefaultTransport } httpClient := &http.Client{Transport: transport} jar, err := cookiejar.New(nil) if err != nil { return nil, err } u, err := url.Parse(requrl) if err != nil { return nil, err } codec := clientCodec{ url: u, httpClient: httpClient, close: make(chan uint64), ready: make(chan uint64), responses: make(map[uint64]*http.Response), cookies: jar, } return &Client{rpc.NewClientWithCodec(&codec)}, nil } golang-github-kolo-xmlrpc-0.0~git20200310.e035052/client_test.go000066400000000000000000000076471370255637400236570ustar00rootroot00000000000000// +build integration package xmlrpc import ( "context" "io" "net/http" "net/http/httptest" "runtime" "sync" "testing" "time" ) func Test_CallWithoutArgs(t *testing.T) { client := newClient(t) defer client.Close() var result time.Time if err := client.Call("service.time", nil, &result); err != nil { t.Fatalf("service.time call error: %v", err) } } func Test_CallWithOneArg(t *testing.T) { client := newClient(t) defer client.Close() var result string if err := client.Call("service.upcase", "xmlrpc", &result); err != nil { t.Fatalf("service.upcase call error: %v", err) } if result != "XMLRPC" { t.Fatalf("Unexpected result of service.upcase: %s != %s", "XMLRPC", result) } } func Test_CallWithTwoArgs(t *testing.T) { client := newClient(t) defer client.Close() var sum int if err := client.Call("service.sum", []interface{}{2, 3}, &sum); err != nil { t.Fatalf("service.sum call error: %v", err) } if sum != 5 { t.Fatalf("Unexpected result of service.sum: %d != %d", 5, sum) } } func Test_TwoCalls(t *testing.T) { client := newClient(t) defer client.Close() var upcase string if err := client.Call("service.upcase", "xmlrpc", &upcase); err != nil { t.Fatalf("service.upcase call error: %v", err) } var sum int if err := client.Call("service.sum", []interface{}{2, 3}, &sum); err != nil { t.Fatalf("service.sum call error: %v", err) } } func Test_FailedCall(t *testing.T) { client := newClient(t) defer client.Close() var result int if err := client.Call("service.error", nil, &result); err == nil { t.Fatal("expected service.error returns error, but it didn't") } } func Test_ConcurrentCalls(t *testing.T) { client := newClient(t) call := func() { var result time.Time client.Call("service.time", nil, &result) } var wg sync.WaitGroup for i := 0; i < 100; i++ { wg.Add(1) go func() { call() wg.Done() }() } wg.Wait() client.Close() } func Test_CloseMemoryLeak(t *testing.T) { expected := runtime.NumGoroutine() for i := 0; i < 3; i++ { client := newClient(t) client.Call("service.time", nil, nil) client.Close() } var actual int // It takes some time to stop running goroutinges. This function checks number of // running goroutines. It finishes execution if number is same as expected or timeout // has been reached. func() { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() for { select { case <-ctx.Done(): return default: actual = runtime.NumGoroutine() if actual == expected { return } } } }() if actual != expected { t.Errorf("expected number of running goroutines to be %d, but got %d", expected, actual) } } func Test_BadStatus(t *testing.T) { // this is a mock xmlrpc server which sends an invalid status code on the first request // and an empty methodResponse for all subsequence requests first := true ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if first { first = false http.Error(w, "bad status", http.StatusInternalServerError) } else { io.WriteString(w, ` `) } })) client, err := NewClient(ts.URL, nil) if err != nil { t.Fatalf("Can't create client: %v", err) } defer client.Close() var result interface{} // expect an error due to the bad status code if err := client.Call("method", nil, &result); err == nil { t.Fatalf("Bad status didn't result in error") } // expect subsequent calls to succeed if err := client.Call("method", nil, &result); err != nil { t.Fatalf("Failed to recover after bad status: %v", err) } } func newClient(t *testing.T) *Client { client, err := NewClient("http://localhost:5001", nil) if err != nil { t.Fatalf("Can't create client: %v", err) } return client } golang-github-kolo-xmlrpc-0.0~git20200310.e035052/decoder.go000066400000000000000000000227721370255637400227430ustar00rootroot00000000000000package xmlrpc import ( "bytes" "encoding/xml" "errors" "fmt" "io" "reflect" "strconv" "strings" "time" ) const ( iso8601 = "20060102T15:04:05" iso8601Z = "20060102T15:04:05Z07:00" iso8601Hyphen = "2006-01-02T15:04:05" iso8601HyphenZ = "2006-01-02T15:04:05Z07:00" ) var ( // CharsetReader is a function to generate reader which converts a non UTF-8 // charset into UTF-8. CharsetReader func(string, io.Reader) (io.Reader, error) timeLayouts = []string{iso8601, iso8601Z, iso8601Hyphen, iso8601HyphenZ} invalidXmlError = errors.New("invalid xml") ) type TypeMismatchError string func (e TypeMismatchError) Error() string { return string(e) } type decoder struct { *xml.Decoder } func unmarshal(data []byte, v interface{}) (err error) { dec := &decoder{xml.NewDecoder(bytes.NewBuffer(data))} if CharsetReader != nil { dec.CharsetReader = CharsetReader } var tok xml.Token for { if tok, err = dec.Token(); err != nil { return err } if t, ok := tok.(xml.StartElement); ok { if t.Name.Local == "value" { val := reflect.ValueOf(v) if val.Kind() != reflect.Ptr { return errors.New("non-pointer value passed to unmarshal") } if err = dec.decodeValue(val.Elem()); err != nil { return err } break } } } // read until end of document err = dec.Skip() if err != nil && err != io.EOF { return err } return nil } func (dec *decoder) decodeValue(val reflect.Value) error { var tok xml.Token var err error if val.Kind() == reflect.Ptr { if val.IsNil() { val.Set(reflect.New(val.Type().Elem())) } val = val.Elem() } var typeName string for { if tok, err = dec.Token(); err != nil { return err } if t, ok := tok.(xml.EndElement); ok { if t.Name.Local == "value" { return nil } else { return invalidXmlError } } if t, ok := tok.(xml.StartElement); ok { typeName = t.Name.Local break } // Treat value data without type identifier as string if t, ok := tok.(xml.CharData); ok { if value := strings.TrimSpace(string(t)); value != "" { if err = checkType(val, reflect.String); err != nil { return err } val.SetString(value) return nil } } } switch typeName { case "struct": ismap := false pmap := val valType := val.Type() if err = checkType(val, reflect.Struct); err != nil { if checkType(val, reflect.Map) == nil { if valType.Key().Kind() != reflect.String { return fmt.Errorf("only maps with string key type can be unmarshalled") } ismap = true } else if checkType(val, reflect.Interface) == nil && val.IsNil() { var dummy map[string]interface{} valType = reflect.TypeOf(dummy) pmap = reflect.New(valType).Elem() val.Set(pmap) ismap = true } else { return err } } var fields map[string]reflect.Value if !ismap { fields = make(map[string]reflect.Value) for i := 0; i < valType.NumField(); i++ { field := valType.Field(i) fieldVal := val.FieldByName(field.Name) if fieldVal.CanSet() { if fn := field.Tag.Get("xmlrpc"); fn != "" { fields[fn] = fieldVal } else { fields[field.Name] = fieldVal } } } } else { // Create initial empty map pmap.Set(reflect.MakeMap(valType)) } // Process struct members. StructLoop: for { if tok, err = dec.Token(); err != nil { return err } switch t := tok.(type) { case xml.StartElement: if t.Name.Local != "member" { return invalidXmlError } tagName, fieldName, err := dec.readTag() if err != nil { return err } if tagName != "name" { return invalidXmlError } var fv reflect.Value ok := true if !ismap { fv, ok = fields[string(fieldName)] } else { fv = reflect.New(valType.Elem()) } if ok { for { if tok, err = dec.Token(); err != nil { return err } if t, ok := tok.(xml.StartElement); ok && t.Name.Local == "value" { if err = dec.decodeValue(fv); err != nil { return err } // if err = dec.Skip(); err != nil { return err } break } } } // if err = dec.Skip(); err != nil { return err } if ismap { pmap.SetMapIndex(reflect.ValueOf(string(fieldName)), reflect.Indirect(fv)) val.Set(pmap) } case xml.EndElement: break StructLoop } } case "array": slice := val if checkType(val, reflect.Interface) == nil && val.IsNil() { slice = reflect.ValueOf([]interface{}{}) } else if err = checkType(val, reflect.Slice); err != nil { return err } ArrayLoop: for { if tok, err = dec.Token(); err != nil { return err } switch t := tok.(type) { case xml.StartElement: var index int if t.Name.Local != "data" { return invalidXmlError } DataLoop: for { if tok, err = dec.Token(); err != nil { return err } switch tt := tok.(type) { case xml.StartElement: if tt.Name.Local != "value" { return invalidXmlError } if index < slice.Len() { v := slice.Index(index) if v.Kind() == reflect.Interface { v = v.Elem() } if v.Kind() != reflect.Ptr { return errors.New("error: cannot write to non-pointer array element") } if err = dec.decodeValue(v); err != nil { return err } } else { v := reflect.New(slice.Type().Elem()) if err = dec.decodeValue(v); err != nil { return err } slice = reflect.Append(slice, v.Elem()) } // if err = dec.Skip(); err != nil { return err } index++ case xml.EndElement: val.Set(slice) break DataLoop } } case xml.EndElement: break ArrayLoop } } default: if tok, err = dec.Token(); err != nil { return err } var data []byte switch t := tok.(type) { case xml.EndElement: return nil case xml.CharData: data = []byte(t.Copy()) default: return invalidXmlError } switch typeName { case "int", "i4", "i8": if checkType(val, reflect.Interface) == nil && val.IsNil() { i, err := strconv.ParseInt(string(data), 10, 64) if err != nil { return err } pi := reflect.New(reflect.TypeOf(i)).Elem() pi.SetInt(i) val.Set(pi) } else if err = checkType(val, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64); err != nil { return err } else { i, err := strconv.ParseInt(string(data), 10, val.Type().Bits()) if err != nil { return err } val.SetInt(i) } case "string", "base64": str := string(data) if checkType(val, reflect.Interface) == nil && val.IsNil() { pstr := reflect.New(reflect.TypeOf(str)).Elem() pstr.SetString(str) val.Set(pstr) } else if err = checkType(val, reflect.String); err != nil { return err } else { val.SetString(str) } case "dateTime.iso8601": var t time.Time var err error for _, layout := range timeLayouts { t, err = time.Parse(layout, string(data)) if err == nil { break } } if err != nil { return err } if checkType(val, reflect.Interface) == nil && val.IsNil() { ptime := reflect.New(reflect.TypeOf(t)).Elem() ptime.Set(reflect.ValueOf(t)) val.Set(ptime) } else if _, ok := val.Interface().(time.Time); !ok { return TypeMismatchError(fmt.Sprintf("error: type mismatch error - can't decode %v to time", val.Kind())) } else { val.Set(reflect.ValueOf(t)) } case "boolean": v, err := strconv.ParseBool(string(data)) if err != nil { return err } if checkType(val, reflect.Interface) == nil && val.IsNil() { pv := reflect.New(reflect.TypeOf(v)).Elem() pv.SetBool(v) val.Set(pv) } else if err = checkType(val, reflect.Bool); err != nil { return err } else { val.SetBool(v) } case "double": if checkType(val, reflect.Interface) == nil && val.IsNil() { i, err := strconv.ParseFloat(string(data), 64) if err != nil { return err } pdouble := reflect.New(reflect.TypeOf(i)).Elem() pdouble.SetFloat(i) val.Set(pdouble) } else if err = checkType(val, reflect.Float32, reflect.Float64); err != nil { return err } else { i, err := strconv.ParseFloat(string(data), val.Type().Bits()) if err != nil { return err } val.SetFloat(i) } default: return errors.New("unsupported type") } // if err = dec.Skip(); err != nil { return err } } return nil } func (dec *decoder) readTag() (string, []byte, error) { var tok xml.Token var err error var name string for { if tok, err = dec.Token(); err != nil { return "", nil, err } if t, ok := tok.(xml.StartElement); ok { name = t.Name.Local break } } value, err := dec.readCharData() if err != nil { return "", nil, err } return name, value, dec.Skip() } func (dec *decoder) readCharData() ([]byte, error) { var tok xml.Token var err error if tok, err = dec.Token(); err != nil { return nil, err } if t, ok := tok.(xml.CharData); ok { return []byte(t.Copy()), nil } else { return nil, invalidXmlError } } func checkType(val reflect.Value, kinds ...reflect.Kind) error { if len(kinds) == 0 { return nil } if val.Kind() == reflect.Ptr { val = val.Elem() } match := false for _, kind := range kinds { if val.Kind() == kind { match = true break } } if !match { return TypeMismatchError(fmt.Sprintf("error: type mismatch - can't unmarshal %v to %v", val.Kind(), kinds[0])) } return nil } golang-github-kolo-xmlrpc-0.0~git20200310.e035052/decoder_test.go000066400000000000000000000160131370255637400237710ustar00rootroot00000000000000package xmlrpc import ( "fmt" "io" "io/ioutil" "reflect" "testing" "time" "golang.org/x/text/encoding/charmap" "golang.org/x/text/transform" ) type book struct { Title string Amount int } type bookUnexported struct { title string amount int } var unmarshalTests = []struct { value interface{} ptr interface{} xml string }{ // int, i4, i8 {0, new(*int), ""}, {100, new(*int), "100"}, {389451, new(*int), "389451"}, {int64(45659074), new(*int64), "45659074"}, // string {"Once upon a time", new(*string), "Once upon a time"}, {"Mike & Mick ", new(*string), "Mike & Mick <London, UK>"}, {"Once upon a time", new(*string), "Once upon a time"}, // base64 {"T25jZSB1cG9uIGEgdGltZQ==", new(*string), "T25jZSB1cG9uIGEgdGltZQ=="}, // boolean {true, new(*bool), "1"}, {false, new(*bool), "0"}, // double {12.134, new(*float32), "12.134"}, {-12.134, new(*float32), "-12.134"}, // datetime.iso8601 {_time("2013-12-09T21:00:12Z"), new(*time.Time), "20131209T21:00:12"}, {_time("2013-12-09T21:00:12Z"), new(*time.Time), "20131209T21:00:12Z"}, {_time("2013-12-09T21:00:12-01:00"), new(*time.Time), "20131209T21:00:12-01:00"}, {_time("2013-12-09T21:00:12+01:00"), new(*time.Time), "20131209T21:00:12+01:00"}, {_time("2013-12-09T21:00:12Z"), new(*time.Time), "2013-12-09T21:00:12"}, {_time("2013-12-09T21:00:12Z"), new(*time.Time), "2013-12-09T21:00:12Z"}, {_time("2013-12-09T21:00:12-01:00"), new(*time.Time), "2013-12-09T21:00:12-01:00"}, {_time("2013-12-09T21:00:12+01:00"), new(*time.Time), "2013-12-09T21:00:12+01:00"}, // array {[]int{1, 5, 7}, new(*[]int), "157"}, {[]interface{}{"A", "5"}, new(interface{}), "A5"}, {[]interface{}{"A", int64(5)}, new(interface{}), "A5"}, // struct {book{"War and Piece", 20}, new(*book), "TitleWar and PieceAmount20"}, {bookUnexported{}, new(*bookUnexported), "titleWar and Pieceamount20"}, {map[string]interface{}{"Name": "John Smith"}, new(interface{}), "NameJohn Smith"}, {map[string]interface{}{}, new(interface{}), ""}, } func _time(s string) time.Time { t, err := time.Parse(time.RFC3339, s) if err != nil { panic(fmt.Sprintf("time parsing error: %v", err)) } return t } func Test_unmarshal(t *testing.T) { for _, tt := range unmarshalTests { v := reflect.New(reflect.TypeOf(tt.value)) if err := unmarshal([]byte(tt.xml), v.Interface()); err != nil { t.Fatalf("unmarshal error: %v", err) } v = v.Elem() if v.Kind() == reflect.Slice { vv := reflect.ValueOf(tt.value) if vv.Len() != v.Len() { t.Fatalf("unmarshal error:\nexpected: %v\n got: %v", tt.value, v.Interface()) } for i := 0; i < v.Len(); i++ { if v.Index(i).Interface() != vv.Index(i).Interface() { t.Fatalf("unmarshal error:\nexpected: %v\n got: %v", tt.value, v.Interface()) } } } else { a1 := v.Interface() a2 := interface{}(tt.value) if !reflect.DeepEqual(a1, a2) { t.Fatalf("unmarshal error:\nexpected: %v\n got: %v", tt.value, v.Interface()) } } } } func Test_unmarshalToNil(t *testing.T) { for _, tt := range unmarshalTests { if err := unmarshal([]byte(tt.xml), tt.ptr); err != nil { t.Fatalf("unmarshal error: %v", err) } } } func Test_typeMismatchError(t *testing.T) { var s string encoded := "100" var err error if err = unmarshal([]byte(encoded), &s); err == nil { t.Fatal("unmarshal error: expected error, but didn't get it") } if _, ok := err.(TypeMismatchError); !ok { t.Fatal("unmarshal error: expected type mistmatch error, but didn't get it") } } func Test_unmarshalEmptyValueTag(t *testing.T) { var v int if err := unmarshal([]byte(""), &v); err != nil { t.Fatalf("unmarshal error: %v", err) } } const structEmptyXML = ` ` func Test_unmarshalEmptyStruct(t *testing.T) { var v interface{} if err := unmarshal([]byte(structEmptyXML), &v); err != nil { t.Fatal(err) } if v == nil { t.Fatalf("got nil map") } } const arrayValueXML = ` 234 1 Hello World Extra Value ` func Test_unmarshalExistingArray(t *testing.T) { var ( v1 int v2 bool v3 string v = []interface{}{&v1, &v2, &v3} ) if err := unmarshal([]byte(arrayValueXML), &v); err != nil { t.Fatal(err) } // check pre-existing values if want := 234; v1 != want { t.Fatalf("want %d, got %d", want, v1) } if want := true; v2 != want { t.Fatalf("want %t, got %t", want, v2) } if want := "Hello World"; v3 != want { t.Fatalf("want %s, got %s", want, v3) } // check the appended result if n := len(v); n != 4 { t.Fatalf("missing appended result") } if got, ok := v[3].(string); !ok || got != "Extra Value" { t.Fatalf("got %s, want %s", got, "Extra Value") } } func Test_decodeNonUTF8Response(t *testing.T) { data, err := ioutil.ReadFile("fixtures/cp1251.xml") if err != nil { t.Fatal(err) } CharsetReader = decode var s string if err = unmarshal(data, &s); err != nil { fmt.Println(err) t.Fatal("unmarshal error: cannot decode non utf-8 response") } expected := "Л.Н. Толстой - Война и Мир" if s != expected { t.Fatalf("unmarshal error:\nexpected: %v\n got: %v", expected, s) } CharsetReader = nil } func decode(charset string, input io.Reader) (io.Reader, error) { if charset != "cp1251" { return nil, fmt.Errorf("unsupported charset") } return transform.NewReader(input, charmap.Windows1251.NewDecoder()), nil } golang-github-kolo-xmlrpc-0.0~git20200310.e035052/encoder.go000066400000000000000000000074241370255637400227520ustar00rootroot00000000000000package xmlrpc import ( "bytes" "encoding/xml" "fmt" "reflect" "sort" "strconv" "strings" "time" ) // Base64 represents value in base64 encoding type Base64 string type encodeFunc func(reflect.Value) ([]byte, error) func marshal(v interface{}) ([]byte, error) { if v == nil { return []byte{}, nil } val := reflect.ValueOf(v) return encodeValue(val) } func encodeValue(val reflect.Value) ([]byte, error) { var b []byte var err error if val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface { if val.IsNil() { return []byte(""), nil } val = val.Elem() } switch val.Kind() { case reflect.Struct: switch val.Interface().(type) { case time.Time: t := val.Interface().(time.Time) b = []byte(fmt.Sprintf("%s", t.Format(iso8601))) default: b, err = encodeStruct(val) } case reflect.Map: b, err = encodeMap(val) case reflect.Slice: b, err = encodeSlice(val) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: b = []byte(fmt.Sprintf("%s", strconv.FormatInt(val.Int(), 10))) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: b = []byte(fmt.Sprintf("%s", strconv.FormatUint(val.Uint(), 10))) case reflect.Float32, reflect.Float64: b = []byte(fmt.Sprintf("%s", strconv.FormatFloat(val.Float(), 'f', -1, val.Type().Bits()))) case reflect.Bool: if val.Bool() { b = []byte("1") } else { b = []byte("0") } case reflect.String: var buf bytes.Buffer xml.Escape(&buf, []byte(val.String())) if _, ok := val.Interface().(Base64); ok { b = []byte(fmt.Sprintf("%s", buf.String())) } else { b = []byte(fmt.Sprintf("%s", buf.String())) } default: return nil, fmt.Errorf("xmlrpc encode error: unsupported type") } if err != nil { return nil, err } return []byte(fmt.Sprintf("%s", string(b))), nil } func encodeStruct(structVal reflect.Value) ([]byte, error) { var b bytes.Buffer b.WriteString("") structType := structVal.Type() for i := 0; i < structType.NumField(); i++ { fieldVal := structVal.Field(i) fieldType := structType.Field(i) name := fieldType.Tag.Get("xmlrpc") // if the tag has the omitempty property, skip it if strings.HasSuffix(name, ",omitempty") && isZero(fieldVal) { continue } name = strings.TrimSuffix(name, ",omitempty") if name == "" { name = fieldType.Name } p, err := encodeValue(fieldVal) if err != nil { return nil, err } b.WriteString("") b.WriteString(fmt.Sprintf("%s", name)) b.Write(p) b.WriteString("") } b.WriteString("") return b.Bytes(), nil } var sortMapKeys bool func encodeMap(val reflect.Value) ([]byte, error) { var t = val.Type() if t.Key().Kind() != reflect.String { return nil, fmt.Errorf("xmlrpc encode error: only maps with string keys are supported") } var b bytes.Buffer b.WriteString("") keys := val.MapKeys() if sortMapKeys { sort.Slice(keys, func(i, j int) bool { return keys[i].String() < keys[j].String() }) } for i := 0; i < val.Len(); i++ { key := keys[i] kval := val.MapIndex(key) b.WriteString("") b.WriteString(fmt.Sprintf("%s", key.String())) p, err := encodeValue(kval) if err != nil { return nil, err } b.Write(p) b.WriteString("") } b.WriteString("") return b.Bytes(), nil } func encodeSlice(val reflect.Value) ([]byte, error) { var b bytes.Buffer b.WriteString("") for i := 0; i < val.Len(); i++ { p, err := encodeValue(val.Index(i)) if err != nil { return nil, err } b.Write(p) } b.WriteString("") return b.Bytes(), nil } golang-github-kolo-xmlrpc-0.0~git20200310.e035052/encoder_test.go000066400000000000000000000067401370255637400240110ustar00rootroot00000000000000package xmlrpc import ( "testing" "time" ) var marshalTests = []struct { value interface{} xml string }{ {100, "100"}, {"Once upon a time", "Once upon a time"}, {"Mike & Mick ", "Mike & Mick <London, UK>"}, {Base64("T25jZSB1cG9uIGEgdGltZQ=="), "T25jZSB1cG9uIGEgdGltZQ=="}, {true, "1"}, {false, "0"}, {12.134, "12.134"}, {-12.134, "-12.134"}, {738777323.0, "738777323"}, {time.Unix(1386622812, 0).UTC(), "20131209T21:00:12"}, {[]interface{}{1, "one"}, "1one"}, {&struct { Title string Amount int }{"War and Piece", 20}, "TitleWar and PieceAmount20"}, {&struct { Value interface{} `xmlrpc:"value"` }{}, "value"}, { map[string]interface{}{"title": "War and Piece", "amount": 20}, "amount20titleWar and Piece", }, { map[string]interface{}{ "Name": "John Smith", "Age": 6, "Wight": []float32{66.67, 100.5}, "Dates": map[string]interface{}{"Birth": time.Date(1829, time.November, 10, 23, 0, 0, 0, time.UTC), "Death": time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)}}, "Age6DatesBirth18291110T23:00:00Death20091110T23:00:00NameJohn SmithWight66.67100.5", }, {&struct { Title string Amount int Author string `xmlrpc:"author,omitempty"` }{ Title: "War and Piece", Amount: 20, }, "TitleWar and PieceAmount20"}, {&struct { Title string Amount int Author string `xmlrpc:"author,omitempty"` }{ Title: "War and Piece", Amount: 20, Author: "Leo Tolstoy", }, "TitleWar and PieceAmount20authorLeo Tolstoy"}, {&struct { }{}, ""}, } func Test_marshal(t *testing.T) { sortMapKeys = true for _, tt := range marshalTests { b, err := marshal(tt.value) if err != nil { t.Fatalf("unexpected marshal error: %v", err) } if string(b) != tt.xml { t.Fatalf("marshal error:\nexpected: %s\n got: %s", tt.xml, string(b)) } } } golang-github-kolo-xmlrpc-0.0~git20200310.e035052/fixtures/000077500000000000000000000000001370255637400226465ustar00rootroot00000000000000golang-github-kolo-xmlrpc-0.0~git20200310.e035052/fixtures/cp1251.xml000066400000000000000000000002611370255637400243020ustar00rootroot00000000000000 .. - golang-github-kolo-xmlrpc-0.0~git20200310.e035052/is_zero.go000066400000000000000000000017371370255637400230060ustar00rootroot00000000000000package xmlrpc import ( "math" . "reflect" ) func isZero(v Value) bool { switch v.Kind() { case Bool: return !v.Bool() case Int, Int8, Int16, Int32, Int64: return v.Int() == 0 case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: return v.Uint() == 0 case Float32, Float64: return math.Float64bits(v.Float()) == 0 case Complex64, Complex128: c := v.Complex() return math.Float64bits(real(c)) == 0 && math.Float64bits(imag(c)) == 0 case Array: for i := 0; i < v.Len(); i++ { if !isZero(v.Index(i)) { return false } } return true case Chan, Func, Interface, Map, Ptr, Slice, UnsafePointer: return v.IsNil() case String: return v.Len() == 0 case Struct: for i := 0; i < v.NumField(); i++ { if !isZero(v.Field(i)) { return false } } return true default: // This should never happens, but will act as a safeguard for // later, as a default value doesn't makes sense here. panic(&ValueError{"reflect.Value.IsZero", v.Kind()}) } } golang-github-kolo-xmlrpc-0.0~git20200310.e035052/request.go000066400000000000000000000021651370255637400230200ustar00rootroot00000000000000package xmlrpc import ( "bytes" "fmt" "net/http" ) func NewRequest(url string, method string, args interface{}) (*http.Request, error) { var t []interface{} var ok bool if t, ok = args.([]interface{}); !ok { if args != nil { t = []interface{}{args} } } body, err := EncodeMethodCall(method, t...) if err != nil { return nil, err } request, err := http.NewRequest("POST", url, bytes.NewReader(body)) if err != nil { return nil, err } request.Header.Set("Content-Type", "text/xml") request.Header.Set("Content-Length", fmt.Sprintf("%d", len(body))) return request, nil } func EncodeMethodCall(method string, args ...interface{}) ([]byte, error) { var b bytes.Buffer b.WriteString(``) b.WriteString(fmt.Sprintf("%s", method)) if args != nil { b.WriteString("") for _, arg := range args { p, err := marshal(arg) if err != nil { return nil, err } b.WriteString(fmt.Sprintf("%s", string(p))) } b.WriteString("") } b.WriteString("") return b.Bytes(), nil } golang-github-kolo-xmlrpc-0.0~git20200310.e035052/response.go000066400000000000000000000013411370255637400231610ustar00rootroot00000000000000package xmlrpc import ( "fmt" "regexp" ) var ( faultRx = regexp.MustCompile(`(\s|\S)+`) ) // FaultError is returned from the server when an invalid call is made type FaultError struct { Code int `xmlrpc:"faultCode"` String string `xmlrpc:"faultString"` } // Error implements the error interface func (e FaultError) Error() string { return fmt.Sprintf("Fault(%d): %s", e.Code, e.String) } type Response []byte func (r Response) Err() error { if !faultRx.Match(r) { return nil } var fault FaultError if err := unmarshal(r, &fault); err != nil { return err } return fault } func (r Response) Unmarshal(v interface{}) error { if err := unmarshal(r, v); err != nil { return err } return nil } golang-github-kolo-xmlrpc-0.0~git20200310.e035052/response_test.go000066400000000000000000000031121370255637400242160ustar00rootroot00000000000000package xmlrpc import ( "testing" ) const faultRespXml = ` faultString You must log in before using this part of Bugzilla. faultCode 410 ` func Test_failedResponse(t *testing.T) { resp := Response([]byte(faultRespXml)) if resp.Err() == nil { t.Fatal("Err() error: expected error, got nil") } fault := resp.Err().(FaultError) if fault.Code != 410 && fault.String != "You must log in before using this part of Bugzilla." { t.Fatal("Err() error: got wrong error") } } const emptyValResp = ` user Joe Smith token ` func Test_responseWithEmptyValue(t *testing.T) { resp := Response([]byte(emptyValResp)) result := struct { User string `xmlrpc:"user"` Token string `xmlrpc:"token"` }{} if err := resp.Unmarshal(&result); err != nil { t.Fatalf("unmarshal error: %v", err) } if result.User != "Joe Smith" || result.Token != "" { t.Fatalf("unexpected result: %v", result) } } golang-github-kolo-xmlrpc-0.0~git20200310.e035052/test_server.rb000066400000000000000000000005241370255637400236700ustar00rootroot00000000000000# encoding: utf-8 require "xmlrpc/server" class Service def time Time.now end def upcase(s) s.upcase end def sum(x, y) x + y end def error raise XMLRPC::FaultException.new(500, "Server error") end end server = XMLRPC::Server.new 5001, 'localhost' server.add_handler "service", Service.new server.serve