pax_global_header 0000666 0000000 0000000 00000000064 13702556374 0014526 g ustar 00root root 0000000 0000000 52 comment=4a59c23d842443cd891d7c319fa28b85d8ea9685
golang-github-kolo-xmlrpc-0.0~git20200310.e035052/ 0000775 0000000 0000000 00000000000 13702556374 0020775 5 ustar 00root root 0000000 0000000 golang-github-kolo-xmlrpc-0.0~git20200310.e035052/LICENSE 0000664 0000000 0000000 00000002043 13702556374 0022001 0 ustar 00root root 0000000 0000000 Copyright (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.md 0000664 0000000 0000000 00000005607 13702556374 0022264 0 ustar 00root root 0000000 0000000 [](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.go 0000664 0000000 0000000 00000006363 13702556374 0022612 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000007647 13702556374 0023657 0 ustar 00root root 0000000 0000000 // +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.go 0000664 0000000 0000000 00000022772 13702556374 0022743 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000016013 13702556374 0023771 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000007424 13702556374 0022752 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000006740 13702556374 0024011 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13702556374 0022646 5 ustar 00root root 0000000 0000000 golang-github-kolo-xmlrpc-0.0~git20200310.e035052/fixtures/cp1251.xml 0000664 0000000 0000000 00000000261 13702556374 0024302 0 ustar 00root root 0000000 0000000
.. -