2010-01-06 15:38:33 +00:00
|
|
|
// Copyright 2009 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
// Marshalling and unmarshalling of
|
|
|
|
// bit torrent bencode data into Go structs using reflection.
|
|
|
|
//
|
|
|
|
// Based upon the standard Go language JSON package.
|
|
|
|
|
|
|
|
package bencode
|
|
|
|
|
|
|
|
import (
|
2011-12-17 16:35:20 +00:00
|
|
|
"errors"
|
2010-01-06 15:38:33 +00:00
|
|
|
"fmt"
|
|
|
|
"io"
|
2011-12-17 16:35:20 +00:00
|
|
|
|
2010-01-06 15:38:33 +00:00
|
|
|
"reflect"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
type structBuilder struct {
|
|
|
|
val reflect.Value
|
|
|
|
|
|
|
|
// if map_ != nil, write val to map_[key] on each change
|
2011-12-17 16:35:20 +00:00
|
|
|
map_ reflect.Value
|
2010-01-06 15:38:33 +00:00
|
|
|
key reflect.Value
|
|
|
|
}
|
|
|
|
|
|
|
|
var nobuilder *structBuilder
|
|
|
|
|
|
|
|
func isfloat(v reflect.Value) bool {
|
2011-12-17 16:35:20 +00:00
|
|
|
switch v.Kind() {
|
|
|
|
case reflect.Float32, reflect.Float64:
|
2010-01-06 15:38:33 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func setfloat(v reflect.Value, f float64) {
|
2011-12-17 16:35:20 +00:00
|
|
|
switch v.Kind() {
|
|
|
|
case reflect.Float32, reflect.Float64:
|
|
|
|
v.SetFloat(f)
|
2010-01-06 15:38:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func setint(val reflect.Value, i int64) {
|
2011-12-17 16:35:20 +00:00
|
|
|
switch v := val; v.Kind() {
|
|
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
|
|
v.SetInt(int64(i))
|
|
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
|
|
v.SetUint(uint64(i))
|
|
|
|
case reflect.Interface:
|
|
|
|
v.Set(reflect.ValueOf(i))
|
2012-07-16 00:29:17 +00:00
|
|
|
default:
|
|
|
|
panic("setint called for bogus type: " + val.Kind().String())
|
2010-01-06 15:38:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If updating b.val is not enough to update the original,
|
|
|
|
// copy a changed b.val out to the original.
|
|
|
|
func (b *structBuilder) Flush() {
|
|
|
|
if b == nil {
|
|
|
|
return
|
|
|
|
}
|
2011-12-17 16:35:20 +00:00
|
|
|
if b.map_.IsValid() {
|
|
|
|
b.map_.SetMapIndex(b.key, b.val)
|
2010-01-06 15:38:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *structBuilder) Int64(i int64) {
|
|
|
|
if b == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
v := b.val
|
|
|
|
if isfloat(v) {
|
|
|
|
setfloat(v, float64(i))
|
|
|
|
} else {
|
|
|
|
setint(v, i)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *structBuilder) Uint64(i uint64) {
|
|
|
|
if b == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
v := b.val
|
|
|
|
if isfloat(v) {
|
|
|
|
setfloat(v, float64(i))
|
|
|
|
} else {
|
|
|
|
setint(v, int64(i))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *structBuilder) Float64(f float64) {
|
|
|
|
if b == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
v := b.val
|
|
|
|
if isfloat(v) {
|
|
|
|
setfloat(v, f)
|
|
|
|
} else {
|
|
|
|
setint(v, int64(f))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *structBuilder) String(s string) {
|
|
|
|
if b == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2012-07-29 13:29:17 +00:00
|
|
|
switch b.val.Kind() {
|
2011-12-17 16:35:20 +00:00
|
|
|
case reflect.String:
|
2012-07-29 13:29:17 +00:00
|
|
|
if !b.val.CanSet() {
|
|
|
|
x := ""
|
|
|
|
b.val = reflect.ValueOf(&x).Elem()
|
|
|
|
|
|
|
|
}
|
|
|
|
b.val.SetString(s)
|
2011-12-17 16:35:20 +00:00
|
|
|
case reflect.Interface:
|
2012-07-29 13:29:17 +00:00
|
|
|
b.val.Set(reflect.ValueOf(s))
|
2010-01-06 15:38:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *structBuilder) Array() {
|
|
|
|
if b == nil {
|
|
|
|
return
|
|
|
|
}
|
2011-12-17 16:35:20 +00:00
|
|
|
if v := b.val; v.Kind() == reflect.Slice {
|
2010-01-06 15:38:33 +00:00
|
|
|
if v.IsNil() {
|
2011-12-17 16:35:20 +00:00
|
|
|
v.Set(reflect.MakeSlice(v.Type(), 0, 8))
|
2010-01-06 15:38:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *structBuilder) Elem(i int) Builder {
|
|
|
|
if b == nil || i < 0 {
|
|
|
|
return nobuilder
|
|
|
|
}
|
2011-12-17 16:35:20 +00:00
|
|
|
switch v := b.val; v.Kind() {
|
|
|
|
case reflect.Array:
|
2010-01-06 15:38:33 +00:00
|
|
|
if i < v.Len() {
|
2011-12-17 16:35:20 +00:00
|
|
|
return &structBuilder{val: v.Index(i)}
|
2010-01-06 15:38:33 +00:00
|
|
|
}
|
2011-12-17 16:35:20 +00:00
|
|
|
case reflect.Slice:
|
2010-01-06 15:38:33 +00:00
|
|
|
if i >= v.Cap() {
|
|
|
|
n := v.Cap()
|
|
|
|
if n < 8 {
|
|
|
|
n = 8
|
|
|
|
}
|
|
|
|
for n <= i {
|
|
|
|
n *= 2
|
|
|
|
}
|
2011-12-17 16:35:20 +00:00
|
|
|
nv := reflect.MakeSlice(v.Type(), v.Len(), n)
|
2011-02-12 08:17:47 +00:00
|
|
|
reflect.Copy(nv, v)
|
2010-01-06 15:38:33 +00:00
|
|
|
v.Set(nv)
|
|
|
|
}
|
|
|
|
if v.Len() <= i && i < v.Cap() {
|
|
|
|
v.SetLen(i + 1)
|
|
|
|
}
|
|
|
|
if i < v.Len() {
|
2011-12-17 16:35:20 +00:00
|
|
|
return &structBuilder{val: v.Index(i)}
|
2010-01-06 15:38:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nobuilder
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *structBuilder) Map() {
|
|
|
|
if b == nil {
|
|
|
|
return
|
|
|
|
}
|
2011-12-17 16:35:20 +00:00
|
|
|
if v := b.val; v.Kind() == reflect.Ptr && v.IsNil() {
|
2010-01-06 15:38:33 +00:00
|
|
|
if v.IsNil() {
|
2011-12-17 16:35:20 +00:00
|
|
|
v.Set(reflect.Zero(v.Type().Elem()).Addr())
|
2010-01-06 15:38:33 +00:00
|
|
|
b.Flush()
|
|
|
|
}
|
2011-12-17 16:35:20 +00:00
|
|
|
b.map_ = reflect.Value{}
|
2010-01-06 15:38:33 +00:00
|
|
|
b.val = v.Elem()
|
|
|
|
}
|
2011-12-17 16:35:20 +00:00
|
|
|
if v := b.val; v.Kind() == reflect.Map && v.IsNil() {
|
|
|
|
v.Set(reflect.MakeMap(v.Type()))
|
2010-01-06 15:38:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *structBuilder) Key(k string) Builder {
|
|
|
|
if b == nil {
|
|
|
|
return nobuilder
|
|
|
|
}
|
2011-12-17 16:35:20 +00:00
|
|
|
switch v := reflect.Indirect(b.val); v.Kind() {
|
|
|
|
case reflect.Struct:
|
|
|
|
t := v.Type()
|
2010-01-06 15:38:33 +00:00
|
|
|
// Case-insensitive field lookup.
|
|
|
|
k = strings.ToLower(k)
|
|
|
|
for i := 0; i < t.NumField(); i++ {
|
|
|
|
field := t.Field(i)
|
2011-12-17 16:35:20 +00:00
|
|
|
if strings.ToLower(string(field.Tag)) == k ||
|
2010-01-06 15:38:33 +00:00
|
|
|
strings.ToLower(field.Name) == k {
|
|
|
|
return &structBuilder{val: v.Field(i)}
|
|
|
|
}
|
|
|
|
}
|
2011-12-17 16:35:20 +00:00
|
|
|
case reflect.Map:
|
|
|
|
t := v.Type()
|
|
|
|
if t.Key() != reflect.TypeOf(k) {
|
2010-01-06 15:38:33 +00:00
|
|
|
break
|
|
|
|
}
|
2011-12-17 16:35:20 +00:00
|
|
|
key := reflect.ValueOf(k)
|
|
|
|
elem := v.MapIndex(key)
|
|
|
|
if !elem.IsValid() {
|
|
|
|
v.SetMapIndex(key, reflect.Zero(t.Elem()))
|
|
|
|
elem = v.MapIndex(key)
|
2010-01-06 15:38:33 +00:00
|
|
|
}
|
|
|
|
return &structBuilder{val: elem, map_: v, key: key}
|
|
|
|
}
|
|
|
|
return nobuilder
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unmarshal parses the bencode syntax string s and fills in
|
|
|
|
// an arbitrary struct or slice pointed at by val.
|
|
|
|
// It uses the reflect package to assign to fields
|
|
|
|
// and arrays embedded in val. Well-formed data that does not fit
|
|
|
|
// into the struct is discarded.
|
|
|
|
//
|
|
|
|
// For example, given these definitions:
|
|
|
|
//
|
|
|
|
// type Email struct {
|
|
|
|
// Where string;
|
|
|
|
// Addr string;
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// type Result struct {
|
|
|
|
// Name string;
|
|
|
|
// Phone string;
|
|
|
|
// Email []Email
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// var r = Result{ "name", "phone", nil }
|
|
|
|
//
|
|
|
|
// unmarshalling the bencode syntax string
|
|
|
|
//
|
|
|
|
// d5:emailld5:where4:home4:addr15:gre@example.come\
|
|
|
|
// d5:where4:work4:addr12:gre@work.comee4:name14:Gr\
|
|
|
|
// ace R. Emlin7:address15:123 Main Streete
|
|
|
|
//
|
|
|
|
// via Unmarshal(s, &r) is equivalent to assigning
|
|
|
|
//
|
|
|
|
// r = Result{
|
|
|
|
// "Grace R. Emlin", // name
|
|
|
|
// "phone", // no phone given
|
|
|
|
// []Email{
|
|
|
|
// Email{ "home", "gre@example.com" },
|
|
|
|
// Email{ "work", "gre@work.com" }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// Note that the field r.Phone has not been modified and
|
|
|
|
// that the bencode field "address" was discarded.
|
|
|
|
//
|
|
|
|
// Because Unmarshal uses the reflect package, it can only
|
|
|
|
// assign to upper case fields. Unmarshal uses a case-insensitive
|
|
|
|
// comparison to match bencode field names to struct field names.
|
|
|
|
//
|
|
|
|
// If you provide a tag string for a struct member, the tag string
|
|
|
|
// will be used as the bencode dictionary key for that member.
|
|
|
|
//
|
|
|
|
// To unmarshal a top-level bencode array, pass in a pointer to an empty
|
|
|
|
// slice of the correct type.
|
|
|
|
//
|
|
|
|
|
2011-12-17 16:35:20 +00:00
|
|
|
func Unmarshal(r io.Reader, val interface{}) (err error) {
|
2010-01-20 04:53:53 +00:00
|
|
|
// If e represents a value, the answer won't get back to the
|
|
|
|
// caller. Make sure it's a pointer.
|
2011-12-17 16:35:20 +00:00
|
|
|
if reflect.TypeOf(val).Kind() != reflect.Ptr {
|
|
|
|
err = errors.New("Attempt to unmarshal into a non-pointer")
|
2010-01-20 04:53:53 +00:00
|
|
|
return
|
|
|
|
}
|
2012-07-16 00:29:17 +00:00
|
|
|
err = UnmarshalValue(r, reflect.Indirect(reflect.ValueOf(val)))
|
2010-01-06 15:38:33 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// This API is public primarily to make testing easier, but it is available if you
|
|
|
|
// have a use for it.
|
|
|
|
|
2011-12-17 16:35:20 +00:00
|
|
|
func UnmarshalValue(r io.Reader, v reflect.Value) (err error) {
|
2010-01-06 15:38:33 +00:00
|
|
|
var b *structBuilder
|
|
|
|
|
2012-07-16 00:29:17 +00:00
|
|
|
// XXX: Decide if the extra codnitions are needed. Affect map?
|
2011-12-17 16:35:20 +00:00
|
|
|
if ptr := v; ptr.Kind() == reflect.Ptr {
|
2012-07-16 00:29:17 +00:00
|
|
|
if slice := ptr.Elem(); slice.Kind() == reflect.Slice || slice.Kind() == reflect.Int || slice.Kind() == reflect.String {
|
2010-01-06 15:38:33 +00:00
|
|
|
b = &structBuilder{val: slice}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if b == nil {
|
|
|
|
b = &structBuilder{val: v}
|
|
|
|
}
|
|
|
|
|
|
|
|
err = Parse(r, b)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
type MarshalError struct {
|
|
|
|
T reflect.Type
|
|
|
|
}
|
|
|
|
|
2011-12-17 16:35:20 +00:00
|
|
|
func (e *MarshalError) Error() string {
|
2010-01-06 15:38:33 +00:00
|
|
|
return "bencode cannot encode value of type " + e.T.String()
|
|
|
|
}
|
|
|
|
|
2011-12-17 16:35:20 +00:00
|
|
|
func writeArrayOrSlice(w io.Writer, val reflect.Value) (err error) {
|
2010-01-06 15:38:33 +00:00
|
|
|
_, err = fmt.Fprint(w, "l")
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for i := 0; i < val.Len(); i++ {
|
2011-12-17 16:35:20 +00:00
|
|
|
if err := writeValue(w, val.Index(i)); err != nil {
|
2010-01-06 15:38:33 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = fmt.Fprint(w, "e")
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type StringValue struct {
|
|
|
|
key string
|
|
|
|
value reflect.Value
|
|
|
|
}
|
|
|
|
|
|
|
|
type StringValueArray []StringValue
|
|
|
|
|
|
|
|
// Satisfy sort.Interface
|
|
|
|
|
|
|
|
func (a StringValueArray) Len() int { return len(a) }
|
|
|
|
|
|
|
|
func (a StringValueArray) Less(i, j int) bool { return a[i].key < a[j].key }
|
|
|
|
|
|
|
|
func (a StringValueArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
|
2011-12-17 16:35:20 +00:00
|
|
|
func writeSVList(w io.Writer, svList StringValueArray) (err error) {
|
2010-01-06 15:38:33 +00:00
|
|
|
sort.Sort(svList)
|
|
|
|
|
2010-07-10 23:06:14 +00:00
|
|
|
for _, sv := range svList {
|
2010-01-21 06:39:57 +00:00
|
|
|
if isValueNil(sv.value) {
|
|
|
|
continue // Skip null values
|
|
|
|
}
|
2010-01-06 15:38:33 +00:00
|
|
|
s := sv.key
|
|
|
|
_, err = fmt.Fprintf(w, "%d:%s", len(s), s)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = writeValue(w, sv.value); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2011-12-17 16:35:20 +00:00
|
|
|
func writeMap(w io.Writer, val reflect.Value) (err error) {
|
|
|
|
key := val.Type().Key()
|
|
|
|
if key.Kind() != reflect.String {
|
2010-01-06 15:38:33 +00:00
|
|
|
return &MarshalError{val.Type()}
|
|
|
|
}
|
|
|
|
_, err = fmt.Fprint(w, "d")
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2011-12-17 16:35:20 +00:00
|
|
|
keys := val.MapKeys()
|
2010-01-06 15:38:33 +00:00
|
|
|
|
|
|
|
// Sort keys
|
|
|
|
|
|
|
|
svList := make(StringValueArray, len(keys))
|
2010-07-10 23:06:14 +00:00
|
|
|
for i, key := range keys {
|
2011-12-17 16:35:20 +00:00
|
|
|
svList[i].key = key.String()
|
|
|
|
svList[i].value = val.MapIndex(key)
|
2010-01-06 15:38:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
err = writeSVList(w, svList)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = fmt.Fprint(w, "e")
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2011-12-17 16:35:20 +00:00
|
|
|
func writeStruct(w io.Writer, val reflect.Value) (err error) {
|
2010-01-06 15:38:33 +00:00
|
|
|
_, err = fmt.Fprint(w, "d")
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2011-12-17 16:35:20 +00:00
|
|
|
typ := val.Type()
|
2010-01-06 15:38:33 +00:00
|
|
|
|
|
|
|
numFields := val.NumField()
|
|
|
|
svList := make(StringValueArray, numFields)
|
|
|
|
|
|
|
|
for i := 0; i < numFields; i++ {
|
|
|
|
field := typ.Field(i)
|
|
|
|
key := field.Name
|
|
|
|
if len(field.Tag) > 0 {
|
2011-12-17 16:35:20 +00:00
|
|
|
key = string(field.Tag)
|
2010-01-06 15:38:33 +00:00
|
|
|
}
|
|
|
|
svList[i].key = key
|
|
|
|
svList[i].value = val.Field(i)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = writeSVList(w, svList)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = fmt.Fprint(w, "e")
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2011-12-17 16:35:20 +00:00
|
|
|
func writeValue(w io.Writer, val reflect.Value) (err error) {
|
|
|
|
if !val.IsValid() {
|
|
|
|
err = errors.New("Can't write null value")
|
2010-01-06 15:38:33 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2011-12-17 16:35:20 +00:00
|
|
|
switch v := val; v.Kind() {
|
|
|
|
case reflect.String:
|
|
|
|
s := v.String()
|
2010-01-06 15:38:33 +00:00
|
|
|
_, err = fmt.Fprintf(w, "%d:%s", len(s), s)
|
2011-12-17 16:35:20 +00:00
|
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
|
|
_, err = fmt.Fprintf(w, "i%de", v.Int())
|
|
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
|
|
_, err = fmt.Fprintf(w, "i%de", v.Uint())
|
|
|
|
case reflect.Array:
|
2010-01-06 15:38:33 +00:00
|
|
|
err = writeArrayOrSlice(w, v)
|
2011-12-17 16:35:20 +00:00
|
|
|
case reflect.Slice:
|
2010-01-06 15:38:33 +00:00
|
|
|
err = writeArrayOrSlice(w, v)
|
2011-12-17 16:35:20 +00:00
|
|
|
case reflect.Map:
|
2010-01-06 15:38:33 +00:00
|
|
|
err = writeMap(w, v)
|
2011-12-17 16:35:20 +00:00
|
|
|
case reflect.Struct:
|
2010-01-06 15:38:33 +00:00
|
|
|
err = writeStruct(w, v)
|
2011-12-17 16:35:20 +00:00
|
|
|
case reflect.Interface:
|
2010-01-06 15:38:33 +00:00
|
|
|
err = writeValue(w, v.Elem())
|
|
|
|
default:
|
|
|
|
err = &MarshalError{val.Type()}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2010-01-20 15:20:25 +00:00
|
|
|
func isValueNil(val reflect.Value) bool {
|
2011-12-17 16:35:20 +00:00
|
|
|
if !val.IsValid() {
|
2010-01-21 06:39:57 +00:00
|
|
|
return true
|
|
|
|
}
|
2011-12-17 16:35:20 +00:00
|
|
|
switch v := val; v.Kind() {
|
|
|
|
case reflect.Interface:
|
2010-01-20 15:20:25 +00:00
|
|
|
return isValueNil(v.Elem())
|
|
|
|
default:
|
2010-01-21 06:39:57 +00:00
|
|
|
return false
|
2010-01-20 15:20:25 +00:00
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2011-12-17 16:35:20 +00:00
|
|
|
func Marshal(w io.Writer, val interface{}) error {
|
|
|
|
return writeValue(w, reflect.ValueOf(val))
|
2010-01-06 15:38:33 +00:00
|
|
|
}
|