pax_global_header00006660000000000000000000000064125130132060014503gustar00rootroot0000000000000052 comment=0826b98aaa29c0766956cb40d45cf7482a597671 golang-github-kolo-xmlrpc-0+git20150413.0826b98/000077500000000000000000000000001251301320600205205ustar00rootroot00000000000000golang-github-kolo-xmlrpc-0+git20150413.0826b98/LICENSE000066400000000000000000000020431251301320600215240ustar00rootroot00000000000000Copyright (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+git20150413.0826b98/README.md000066400000000000000000000051271251301320600220040ustar00rootroot00000000000000## Overview xmlrpc is an implementation of client side part of XMLRPC protocol in Go language. ## 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 decoded to array; Structs decoded 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. 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 Feel free to fork the project, submit pull requests, ask questions. ## Authors Dmitry Maksimov (dmtmax@gmail.com) golang-github-kolo-xmlrpc-0+git20150413.0826b98/client.go000066400000000000000000000056141251301320600223330ustar00rootroot00000000000000package xmlrpc import ( "fmt" "io/ioutil" "net/http" "net/http/cookiejar" "net/rpc" "net/url" ) 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 response *Response // ready presents channel, that is used to link request and it`s response. ready chan uint64 } func (codec *clientCodec) WriteRequest(request *rpc.Request, args interface{}) (err error) { httpRequest, err := NewRequest(codec.url.String(), request.ServiceMethod, args) if codec.cookies != nil { for _, cookie := range codec.cookies.Cookies(codec.url) { httpRequest.AddCookie(cookie) } } if err != nil { return err } 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.responses[request.Seq] = httpResponse codec.ready <- request.Seq return nil } func (codec *clientCodec) ReadResponseHeader(response *rpc.Response) (err error) { seq := <-codec.ready httpResponse := codec.responses[seq] if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 300 { return fmt.Errorf("request error: bad status code - %d", httpResponse.StatusCode) } respData, err := ioutil.ReadAll(httpResponse.Body) if err != nil { return err } httpResponse.Body.Close() resp := NewResponse(respData) if resp.Failed() { response.Error = fmt.Sprintf("%v", resp.Err()) } codec.response = resp response.Seq = seq delete(codec.responses, seq) return nil } func (codec *clientCodec) ReadResponseBody(v interface{}) (err error) { if v == nil { return nil } if err = codec.response.Unmarshal(v); err != nil { return err } return nil } func (codec *clientCodec) Close() error { transport := codec.httpClient.Transport.(*http.Transport) transport.CloseIdleConnections() 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, ready: make(chan uint64), responses: make(map[uint64]*http.Response), cookies: jar, } return &Client{rpc.NewClientWithCodec(&codec)}, nil } golang-github-kolo-xmlrpc-0+git20150413.0826b98/client_test.go000066400000000000000000000032761251301320600233740ustar00rootroot00000000000000package xmlrpc import ( "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 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+git20150413.0826b98/decoder.go000066400000000000000000000216071251301320600224620ustar00rootroot00000000000000package xmlrpc import ( "bytes" "encoding/xml" "errors" "fmt" "io" "reflect" "strconv" "strings" "time" ) const iso8601 = "20060102T15:04:05" 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) 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{} pmap = reflect.New(reflect.TypeOf(dummy)).Elem() valType = pmap.Type() 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": pslice := val if checkType(val, reflect.Interface) == nil && val.IsNil() { var dummy []interface{} pslice = reflect.New(reflect.TypeOf(dummy)).Elem() } 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: if t.Name.Local != "data" { return invalidXmlError } slice := reflect.MakeSlice(pslice.Type(), 0, 0) 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 } v := reflect.New(pslice.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 } case xml.EndElement: pslice.Set(slice) val.Set(pslice) 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": t, err := time.Parse(iso8601, string(data)) 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+git20150413.0826b98/decoder_test.go000066400000000000000000000114701251301320600235160ustar00rootroot00000000000000package 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 }{ {100, new(*int), "100"}, {int64(45659074), new(*int64), "45659074"}, {"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"}, {"T25jZSB1cG9uIGEgdGltZQ==", new(*string), "T25jZSB1cG9uIGEgdGltZQ=="}, {true, new(*bool), "1"}, {false, new(*bool), "0"}, {12.134, new(*float32), "12.134"}, {-12.134, new(*float32), "-12.134"}, {time.Unix(1386622812, 0).UTC(), new(*time.Time), "20131209T21:00:12"}, {[]int{1, 5, 7}, new(*[]int), "157"}, {book{"War and Piece", 20}, new(*book), "TitleWar and PieceAmount20"}, {bookUnexported{}, new(*bookUnexported), "titleWar and Pieceamount20"}, {0, new(*int), ""}, {[]interface{}{"A", "5"}, new(interface{}), "A5"}, //{map[string]interface{}{"Name": "John Smith", // "Age": 6, // "Wight": []interface{}{66.67, 100.5}}, // new(interface{}), "NameJohn SmithAge6Wight66.67100.5"}, {map[string]interface{}{"Name": "John Smith"}, new(interface{}), "NameJohn Smith"}, } 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 tt := unmarshalTests[0] var err error if err = unmarshal([]byte(tt.xml), &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) } } 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+git20150413.0826b98/encoder.go000066400000000000000000000064661251301320600225020ustar00rootroot00000000000000package xmlrpc import ( "bytes" "encoding/xml" "fmt" "reflect" "strconv" "time" ) 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(), 'g', -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(val reflect.Value) ([]byte, error) { var b bytes.Buffer b.WriteString("") t := val.Type() for i := 0; i < t.NumField(); i++ { b.WriteString("") f := t.Field(i) name := f.Tag.Get("xmlrpc") if name == "" { name = f.Name } b.WriteString(fmt.Sprintf("%s", name)) p, err := encodeValue(val.FieldByName(f.Name)) if err != nil { return nil, err } b.Write(p) b.WriteString("") } b.WriteString("") return b.Bytes(), nil } 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() 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+git20150413.0826b98/encoder_test.go000066400000000000000000000052011251301320600235230ustar00rootroot00000000000000package 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"}, {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}, "titleWar and Pieceamount20", }, { 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)}}, "NameJohn SmithAge6Wight66.67100.5DatesBirth18291110T23:00:00Death20091110T23:00:00", }, } func Test_marshal(t *testing.T) { 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+git20150413.0826b98/fixtures/000077500000000000000000000000001251301320600223715ustar00rootroot00000000000000golang-github-kolo-xmlrpc-0+git20150413.0826b98/fixtures/cp1251.xml000066400000000000000000000002611251301320600240250ustar00rootroot00000000000000 .. - golang-github-kolo-xmlrpc-0+git20150413.0826b98/request.go000066400000000000000000000021651251301320600225430ustar00rootroot00000000000000package 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+git20150413.0826b98/response.go000066400000000000000000000014311251301320600227040ustar00rootroot00000000000000package xmlrpc import ( "regexp" ) var ( faultRx = regexp.MustCompile(`(\s|\S)+`) ) type failedResponse struct { Code int `xmlrpc:"faultCode"` Error string `xmlrpc:"faultString"` } func (r *failedResponse) err() error { return &xmlrpcError{ code: r.Code, err: r.Error, } } type Response struct { data []byte } func NewResponse(data []byte) *Response { return &Response{ data: data, } } func (r *Response) Failed() bool { return faultRx.Match(r.data) } func (r *Response) Err() error { failedResp := new(failedResponse) if err := unmarshal(r.data, failedResp); err != nil { return err } return failedResp.err() } func (r *Response) Unmarshal(v interface{}) error { if err := unmarshal(r.data, v); err != nil { return err } return nil } golang-github-kolo-xmlrpc-0+git20150413.0826b98/response_test.go000066400000000000000000000032271251301320600237500ustar00rootroot00000000000000package 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 := NewResponse([]byte(faultRespXml)) if !resp.Failed() { t.Fatal("Failed() error: expected true, got false") } if resp.Err() == nil { t.Fatal("Err() error: expected error, got nil") } err := resp.Err().(*xmlrpcError) if err.code != 410 && err.err != "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 := NewResponse([]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+git20150413.0826b98/test_server.rb000066400000000000000000000005201251301320600234070ustar00rootroot00000000000000# 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 golang-github-kolo-xmlrpc-0+git20150413.0826b98/xmlrpc.go000066400000000000000000000005511251301320600223550ustar00rootroot00000000000000package xmlrpc import ( "fmt" ) // xmlrpcError represents errors returned on xmlrpc request. type xmlrpcError struct { code int err string } // Error() method implements Error interface func (e *xmlrpcError) Error() string { return fmt.Sprintf("error: \"%s\" code: %d", e.err, e.code) } // Base64 represents value in base64 encoding type Base64 string