2532a37945
Improve error reporting slightly.
212 lines
4 KiB
Go
212 lines
4 KiB
Go
// 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.
|
|
|
|
// bencode parser.
|
|
// See the bittorrent protocol
|
|
|
|
package bencode
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strconv"
|
|
)
|
|
|
|
type Reader interface {
|
|
io.Reader
|
|
ReadByte() (c byte, err os.Error)
|
|
UnreadByte() os.Error
|
|
}
|
|
|
|
// Parser
|
|
//
|
|
// Implements parsing but not the actions. Those are
|
|
// carried out by the implementation of the Builder interface.
|
|
// A Builder represents the object being created.
|
|
// Calling a method like Int64(i) sets that object to i.
|
|
// Calling a method like Elem(i) or Key(s) creates a
|
|
// new builder for a subpiece of the object (logically,
|
|
// an array element or a map key).
|
|
//
|
|
// There are two Builders, in other files.
|
|
// The decoder builds a generic bencode structures
|
|
// in which maps are maps.
|
|
// The structBuilder copies data into a possibly
|
|
// nested data structure, using the "map keys"
|
|
// as struct field names.
|
|
|
|
|
|
// A Builder is an interface implemented by clients and passed
|
|
// to the bencode parser. It gives clients full control over the
|
|
// eventual representation returned by the parser.
|
|
type Builder interface {
|
|
// Set value
|
|
Int64(i int64)
|
|
Uint64(i uint64)
|
|
String(s string)
|
|
Array()
|
|
Map()
|
|
|
|
// Create sub-Builders
|
|
Elem(i int) Builder
|
|
Key(s string) Builder
|
|
|
|
// Flush changes to parent Builder if necessary.
|
|
Flush()
|
|
}
|
|
|
|
func collectInt(r Reader, delim byte) (buf []byte, err os.Error) {
|
|
for {
|
|
c, err := r.ReadByte()
|
|
if err != nil {
|
|
return
|
|
}
|
|
if c == delim {
|
|
return
|
|
}
|
|
if !(c == '-' || (c >= '0' && c <= '9')) {
|
|
err = os.NewError("expected digit")
|
|
return
|
|
}
|
|
buf = bytes.AddByte(buf, c)
|
|
}
|
|
return
|
|
}
|
|
|
|
func decodeInt64(r Reader, delim byte) (data int64, err os.Error) {
|
|
buf, err := collectInt(r, delim)
|
|
if err != nil {
|
|
return
|
|
}
|
|
data, err = strconv.Atoi64(string(buf))
|
|
return
|
|
}
|
|
|
|
func decodeString(r Reader) (data string, err os.Error) {
|
|
len, err := decodeInt64(r, ':')
|
|
if err != nil {
|
|
return
|
|
}
|
|
if len < 0 {
|
|
err = os.NewError("Bad string length")
|
|
return
|
|
}
|
|
var buf = make([]byte, len)
|
|
_, err = io.ReadFull(r, buf)
|
|
if err != nil {
|
|
return
|
|
}
|
|
data = string(buf)
|
|
return
|
|
}
|
|
|
|
func parse(r Reader, build Builder) (err os.Error) {
|
|
Switch:
|
|
c, err := r.ReadByte()
|
|
if err != nil {
|
|
goto exit
|
|
}
|
|
switch {
|
|
case c >= '0' && c <= '9':
|
|
// String
|
|
err = r.UnreadByte()
|
|
if err != nil {
|
|
goto exit
|
|
}
|
|
var str string
|
|
str, err = decodeString(r)
|
|
if err != nil {
|
|
goto exit
|
|
}
|
|
build.String(str)
|
|
|
|
case c == 'd':
|
|
// dictionary
|
|
|
|
build.Map()
|
|
for {
|
|
c, err = r.ReadByte()
|
|
if err != nil {
|
|
goto exit
|
|
}
|
|
if c == 'e' {
|
|
break
|
|
}
|
|
err = r.UnreadByte()
|
|
if err != nil {
|
|
goto exit
|
|
}
|
|
var key string
|
|
key, err = decodeString(r)
|
|
if err != nil {
|
|
goto exit
|
|
}
|
|
// TODO: in pendantic mode, check for keys in ascending order.
|
|
err = parse(r, build.Key(key))
|
|
if err != nil {
|
|
goto exit
|
|
}
|
|
}
|
|
|
|
case c == 'i':
|
|
var buf []byte
|
|
buf, err = collectInt(r, 'e')
|
|
if err != nil {
|
|
goto exit
|
|
}
|
|
var str string
|
|
var i int64
|
|
var i2 uint64
|
|
str = string(buf)
|
|
// If the number is exactly an integer, use that.
|
|
if i, err = strconv.Atoi64(str); err == nil {
|
|
build.Int64(i)
|
|
} else if i2, err = strconv.Atoui64(str); err == nil {
|
|
build.Uint64(i2)
|
|
} else {
|
|
err = os.NewError("Bad integer")
|
|
}
|
|
|
|
case c == 'l':
|
|
// array
|
|
build.Array()
|
|
n := 0
|
|
for {
|
|
c, err = r.ReadByte()
|
|
if err != nil {
|
|
goto exit
|
|
}
|
|
if c == 'e' {
|
|
break
|
|
}
|
|
err = r.UnreadByte()
|
|
if err != nil {
|
|
goto exit
|
|
}
|
|
err = parse(r, build.Elem(n))
|
|
if err != nil {
|
|
goto exit
|
|
}
|
|
n++
|
|
}
|
|
default:
|
|
err = os.NewError(fmt.Sprintf("Unexpected character: '%v'", c))
|
|
}
|
|
exit:
|
|
build.Flush()
|
|
return
|
|
}
|
|
|
|
// Parse parses the bencode stream and makes calls to
|
|
// the builder to construct a parsed representation.
|
|
func Parse(r io.Reader, builder Builder) (err os.Error) {
|
|
rr, ok := r.(Reader)
|
|
if !ok {
|
|
rr = bufio.NewReader(r)
|
|
}
|
|
return parse(rr, builder)
|
|
}
|