Commit e8d9c8d1 authored by Russ Cox's avatar Russ Cox

[dev.cc] all: merge master (6a10f720) into dev.cc

To pick up darwin/arm fix and hopefully fix build.

Change-Id: I06996d0b13b777e476f65405aee031482fc76439
parents 20f9f544 6a10f720
...@@ -772,6 +772,10 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action, ...@@ -772,6 +772,10 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
recompileForTest(pmain, p, ptest, testDir) recompileForTest(pmain, p, ptest, testDir)
} }
if buildContext.GOOS == "darwin" && buildContext.GOARCH == "arm" {
t.NeedCgo = true
}
for _, cp := range pmain.imports { for _, cp := range pmain.imports {
if len(cp.coverVars) > 0 { if len(cp.coverVars) > 0 {
t.Cover = append(t.Cover, coverInfo{cp, cp.coverVars}) t.Cover = append(t.Cover, coverInfo{cp, cp.coverVars})
...@@ -1207,6 +1211,7 @@ type testFuncs struct { ...@@ -1207,6 +1211,7 @@ type testFuncs struct {
NeedTest bool NeedTest bool
ImportXtest bool ImportXtest bool
NeedXtest bool NeedXtest bool
NeedCgo bool
Cover []coverInfo Cover []coverInfo
} }
...@@ -1310,6 +1315,10 @@ import ( ...@@ -1310,6 +1315,10 @@ import (
{{range $i, $p := .Cover}} {{range $i, $p := .Cover}}
_cover{{$i}} {{$p.Package.ImportPath | printf "%q"}} _cover{{$i}} {{$p.Package.ImportPath | printf "%q"}}
{{end}} {{end}}
{{if .NeedCgo}}
_ "runtime/cgo"
{{end}}
) )
var tests = []testing.InternalTest{ var tests = []testing.InternalTest{
......
...@@ -252,7 +252,7 @@ var pkgDeps = map[string][]string{ ...@@ -252,7 +252,7 @@ var pkgDeps = map[string][]string{
// Uses of networking. // Uses of networking.
"log/syslog": {"L4", "OS", "net"}, "log/syslog": {"L4", "OS", "net"},
"net/mail": {"L4", "NET", "OS"}, "net/mail": {"L4", "NET", "OS", "internal/mime"},
"net/textproto": {"L4", "OS", "net"}, "net/textproto": {"L4", "OS", "net"},
// Core crypto. // Core crypto.
......
...@@ -21,7 +21,7 @@ type Position struct { ...@@ -21,7 +21,7 @@ type Position struct {
Filename string // filename, if any Filename string // filename, if any
Offset int // offset, starting at 0 Offset int // offset, starting at 0
Line int // line number, starting at 1 Line int // line number, starting at 1
Column int // column number, starting at 1 (character count) Column int // column number, starting at 1 (byte count)
} }
// IsValid returns true if the position is valid. // IsValid returns true if the position is valid.
...@@ -56,8 +56,8 @@ func (pos Position) String() string { ...@@ -56,8 +56,8 @@ func (pos Position) String() string {
// where base and size are specified when adding the file to the file set via // where base and size are specified when adding the file to the file set via
// AddFile. // AddFile.
// //
// To create the Pos value for a specific source offset, first add // To create the Pos value for a specific source offset (measured in bytes),
// the respective file to the current file set (via FileSet.AddFile) // first add the respective file to the current file set using FileSet.AddFile
// and then call File.Pos(offset) for that file. Given a Pos value p // and then call File.Pos(offset) for that file. Given a Pos value p
// for a specific file set fset, the corresponding Position value is // for a specific file set fset, the corresponding Position value is
// obtained by calling fset.Position(p). // obtained by calling fset.Position(p).
......
// Copyright 2015 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.
package mime
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"io"
"io/ioutil"
"strconv"
"strings"
"unicode"
)
// EncodeWord encodes a string into an RFC 2047 encoded-word.
func EncodeWord(s string) string {
// UTF-8 "Q" encoding
b := bytes.NewBufferString("=?utf-8?q?")
for i := 0; i < len(s); i++ {
switch c := s[i]; {
case c == ' ':
b.WriteByte('_')
case isVchar(c) && c != '=' && c != '?' && c != '_':
b.WriteByte(c)
default:
fmt.Fprintf(b, "=%02X", c)
}
}
b.WriteString("?=")
return b.String()
}
// DecodeWord decodes an RFC 2047 encoded-word.
func DecodeWord(s string) (string, error) {
fields := strings.Split(s, "?")
if len(fields) != 5 || fields[0] != "=" || fields[4] != "=" {
return "", errors.New("address not RFC 2047 encoded")
}
charset, enc := strings.ToLower(fields[1]), strings.ToLower(fields[2])
if charset != "us-ascii" && charset != "iso-8859-1" && charset != "utf-8" {
return "", fmt.Errorf("charset not supported: %q", charset)
}
in := bytes.NewBufferString(fields[3])
var r io.Reader
switch enc {
case "b":
r = base64.NewDecoder(base64.StdEncoding, in)
case "q":
r = qDecoder{r: in}
default:
return "", fmt.Errorf("RFC 2047 encoding not supported: %q", enc)
}
dec, err := ioutil.ReadAll(r)
if err != nil {
return "", err
}
switch charset {
case "us-ascii":
b := new(bytes.Buffer)
for _, c := range dec {
if c >= 0x80 {
b.WriteRune(unicode.ReplacementChar)
} else {
b.WriteRune(rune(c))
}
}
return b.String(), nil
case "iso-8859-1":
b := new(bytes.Buffer)
for _, c := range dec {
b.WriteRune(rune(c))
}
return b.String(), nil
case "utf-8":
return string(dec), nil
}
panic("unreachable")
}
type qDecoder struct {
r io.Reader
scratch [2]byte
}
func (qd qDecoder) Read(p []byte) (n int, err error) {
// This method writes at most one byte into p.
if len(p) == 0 {
return 0, nil
}
if _, err := qd.r.Read(qd.scratch[:1]); err != nil {
return 0, err
}
switch c := qd.scratch[0]; {
case c == '=':
if _, err := io.ReadFull(qd.r, qd.scratch[:2]); err != nil {
return 0, err
}
x, err := strconv.ParseInt(string(qd.scratch[:2]), 16, 64)
if err != nil {
return 0, fmt.Errorf("mime: invalid RFC 2047 encoding: %q", qd.scratch[:2])
}
p[0] = byte(x)
case c == '_':
p[0] = ' '
default:
p[0] = c
}
return 1, nil
}
// isVchar returns true if c is an RFC 5322 VCHAR character.
func isVchar(c byte) bool {
// Visible (printing) characters.
return '!' <= c && c <= '~'
}
...@@ -39,10 +39,10 @@ const debugFloat = true // enable for debugging ...@@ -39,10 +39,10 @@ const debugFloat = true // enable for debugging
// and according to its rounding mode, unless specified otherwise. If the // and according to its rounding mode, unless specified otherwise. If the
// result precision is 0 (see below), it is set to the precision of the // result precision is 0 (see below), it is set to the precision of the
// argument with the largest precision value before any rounding takes // argument with the largest precision value before any rounding takes
// place. The rounding mode remains unchanged, thus uninitialized Floats // place, and the rounding mode remains unchanged. Thus, uninitialized Floats
// provided as result arguments will "inherit" a reasonble precision from // provided as result arguments will have their precision set to a reasonable
// the incoming arguments and their mode is the zero value for RoundingMode // value determined by the operands and their mode is the zero value for
// (ToNearestEven). // RoundingMode (ToNearestEven).
// //
// By setting the desired precision to 24 or 53 and using ToNearestEven // By setting the desired precision to 24 or 53 and using ToNearestEven
// rounding, Float operations produce the same results as the corresponding // rounding, Float operations produce the same results as the corresponding
...@@ -62,6 +62,9 @@ type Float struct { ...@@ -62,6 +62,9 @@ type Float struct {
prec uint // TODO(gri) make this a 32bit field prec uint // TODO(gri) make this a 32bit field
} }
// TODO(gri) provide a couple of Example tests showing typical Float intialization
// and use.
// Internal representation: The mantissa bits x.mant of a Float x are stored // Internal representation: The mantissa bits x.mant of a Float x are stored
// in a nat slice long enough to hold up to x.prec bits; the slice may (but // in a nat slice long enough to hold up to x.prec bits; the slice may (but
// doesn't have to) be shorter if the mantissa contains trailing 0 bits. // doesn't have to) be shorter if the mantissa contains trailing 0 bits.
...@@ -158,7 +161,7 @@ func (z *Float) SetPrec(prec uint) *Float { ...@@ -158,7 +161,7 @@ func (z *Float) SetPrec(prec uint) *Float {
// SetMode sets z's rounding mode to mode and returns an exact z. // SetMode sets z's rounding mode to mode and returns an exact z.
// z remains unchanged otherwise. // z remains unchanged otherwise.
func (z *Float) SetMode(mode RoundingMode) *Float { func (z *Float) SetMode(mode RoundingMode) *Float {
z.acc = Exact z.acc = Exact // TODO(gri) should we not do this? what's the general rule for setting accuracy?
z.mode = mode z.mode = mode
return z return z
} }
...@@ -274,23 +277,21 @@ func (z *Float) setExp(e int64) { ...@@ -274,23 +277,21 @@ func (z *Float) setExp(e int64) {
} }
// debugging support // debugging support
func validate(args ...*Float) { func validate(x *Float) {
for i, x := range args {
const msb = 1 << (_W - 1) const msb = 1 << (_W - 1)
m := len(x.mant) m := len(x.mant)
if m == 0 { if m == 0 {
// 0.0 or Inf // 0.0 or Inf
if x.exp != 0 && x.exp != infExp { if x.exp != 0 && x.exp != infExp {
panic(fmt.Sprintf("#%d: %empty matissa with invalid exponent %d", i, x.exp)) panic(fmt.Sprintf("%empty matissa with invalid exponent %d", x.exp))
} }
continue return
} }
if x.mant[m-1]&msb == 0 { if x.mant[m-1]&msb == 0 {
panic(fmt.Sprintf("#%d: msb not set in last word %#x of %s", i, x.mant[m-1], x.Format('p', 0))) panic(fmt.Sprintf("msb not set in last word %#x of %s", x.mant[m-1], x.Format('p', 0)))
} }
if x.prec <= 0 { if x.prec <= 0 {
panic(fmt.Sprintf("#%d: invalid precision %d", i, x.prec)) panic(fmt.Sprintf("invalid precision %d", x.prec))
}
} }
} }
...@@ -1064,7 +1065,8 @@ func (x *Float) ucmp(y *Float) int { ...@@ -1064,7 +1065,8 @@ func (x *Float) ucmp(y *Float) int {
// result. // result.
func (z *Float) Add(x, y *Float) *Float { func (z *Float) Add(x, y *Float) *Float {
if debugFloat { if debugFloat {
validate(x, y) validate(x)
validate(y)
} }
if z.prec == 0 { if z.prec == 0 {
...@@ -1104,7 +1106,8 @@ func (z *Float) Add(x, y *Float) *Float { ...@@ -1104,7 +1106,8 @@ func (z *Float) Add(x, y *Float) *Float {
// Precision, rounding, and accuracy reporting are as for Add. // Precision, rounding, and accuracy reporting are as for Add.
func (z *Float) Sub(x, y *Float) *Float { func (z *Float) Sub(x, y *Float) *Float {
if debugFloat { if debugFloat {
validate(x, y) validate(x)
validate(y)
} }
if z.prec == 0 { if z.prec == 0 {
...@@ -1143,7 +1146,8 @@ func (z *Float) Sub(x, y *Float) *Float { ...@@ -1143,7 +1146,8 @@ func (z *Float) Sub(x, y *Float) *Float {
// Precision, rounding, and accuracy reporting are as for Add. // Precision, rounding, and accuracy reporting are as for Add.
func (z *Float) Mul(x, y *Float) *Float { func (z *Float) Mul(x, y *Float) *Float {
if debugFloat { if debugFloat {
validate(x, y) validate(x)
validate(y)
} }
if z.prec == 0 { if z.prec == 0 {
...@@ -1171,7 +1175,8 @@ func (z *Float) Mul(x, y *Float) *Float { ...@@ -1171,7 +1175,8 @@ func (z *Float) Mul(x, y *Float) *Float {
// Precision, rounding, and accuracy reporting are as for Add. // Precision, rounding, and accuracy reporting are as for Add.
func (z *Float) Quo(x, y *Float) *Float { func (z *Float) Quo(x, y *Float) *Float {
if debugFloat { if debugFloat {
validate(x, y) validate(x)
validate(y)
} }
if z.prec == 0 { if z.prec == 0 {
...@@ -1251,7 +1256,8 @@ func (z *Float) Rsh(x *Float, s uint) *Float { ...@@ -1251,7 +1256,8 @@ func (z *Float) Rsh(x *Float, s uint) *Float {
// Infinities with matching sign are equal. // Infinities with matching sign are equal.
func (x *Float) Cmp(y *Float) int { func (x *Float) Cmp(y *Float) int {
if debugFloat { if debugFloat {
validate(x, y) validate(x)
validate(y)
} }
mx := x.ord() mx := x.ord()
......
...@@ -1077,6 +1077,8 @@ func TestFloatQuoSmoke(t *testing.T) { ...@@ -1077,6 +1077,8 @@ func TestFloatQuoSmoke(t *testing.T) {
} }
} }
// TODO(gri) Add tests that check correctness in the presence of aliasing.
// For rounding modes ToNegativeInf and ToPositiveInf, rounding is affected // For rounding modes ToNegativeInf and ToPositiveInf, rounding is affected
// by the sign of the value to be rounded. Test that rounding happens after // by the sign of the value to be rounded. Test that rounding happens after
// the sign of a result has been set. // the sign of a result has been set.
......
...@@ -126,11 +126,9 @@ func (z *Float) Scan(r io.ByteScanner, base int) (f *Float, b int, err error) { ...@@ -126,11 +126,9 @@ func (z *Float) Scan(r io.ByteScanner, base int) (f *Float, b int, err error) {
} }
// Parse is like z.Scan(r, base), but instead of reading from an // Parse is like z.Scan(r, base), but instead of reading from an
// io.ByteScanner, it parses the string s. An error is returned if the // io.ByteScanner, it parses the string s. An error is returned if
// string contains invalid or trailing characters not belonging to the // the string contains invalid or trailing bytes not belonging to
// number. // the number.
//
// TODO(gri) define possible errors more precisely
func (z *Float) Parse(s string, base int) (f *Float, b int, err error) { func (z *Float) Parse(s string, base int) (f *Float, b int, err error) {
r := strings.NewReader(s) r := strings.NewReader(s)
...@@ -139,11 +137,10 @@ func (z *Float) Parse(s string, base int) (f *Float, b int, err error) { ...@@ -139,11 +137,10 @@ func (z *Float) Parse(s string, base int) (f *Float, b int, err error) {
} }
// entire string must have been consumed // entire string must have been consumed
var ch byte if ch, err2 := r.ReadByte(); err2 == nil {
if ch, err = r.ReadByte(); err != io.EOF {
if err == nil {
err = fmt.Errorf("expected end of string, found %q", ch) err = fmt.Errorf("expected end of string, found %q", ch)
} } else if err2 != io.EOF {
err = err2
} }
return return
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
package big package big
import ( import (
"io"
"math" "math"
"strconv" "strconv"
"testing" "testing"
...@@ -59,7 +58,7 @@ func TestFloatSetFloat64String(t *testing.T) { ...@@ -59,7 +58,7 @@ func TestFloatSetFloat64String(t *testing.T) {
{"+10000000000000000000000000000000000000000e-0", 1e40}, {"+10000000000000000000000000000000000000000e-0", 1e40},
} { } {
var x Float var x Float
x.prec = 53 // TODO(gri) find better solution x.SetPrec(53)
_, ok := x.SetString(test.s) _, ok := x.SetString(test.s)
if !ok { if !ok {
t.Errorf("%s: parse error", test.s) t.Errorf("%s: parse error", test.s)
...@@ -313,8 +312,7 @@ func TestFloatFormat(t *testing.T) { ...@@ -313,8 +312,7 @@ func TestFloatFormat(t *testing.T) {
{"3.14", 'x', 0, "%x"}, {"3.14", 'x', 0, "%x"},
} { } {
f, _, err := ParseFloat(test.x, 0, 1000, ToNearestEven) f, _, err := ParseFloat(test.x, 0, 1000, ToNearestEven)
// TODO(gri) should we return io.EOF at the end? if err != nil {
if err != nil && err != io.EOF {
t.Errorf("%v: %s", test, err) t.Errorf("%v: %s", test, err)
continue continue
} }
......
...@@ -361,7 +361,7 @@ func (x *Int) Uint64() uint64 { ...@@ -361,7 +361,7 @@ func (x *Int) Uint64() uint64 {
// and returns z and a boolean indicating success. If SetString fails, // and returns z and a boolean indicating success. If SetString fails,
// the value of z is undefined but the returned value is nil. // the value of z is undefined but the returned value is nil.
// //
// The base argument must be 0 or a value from 2 through MaxBase. If the base // The base argument must be 0 or a value between 2 and MaxBase. If the base
// is 0, the string prefix determines the actual conversion base. A prefix of // is 0, the string prefix determines the actual conversion base. A prefix of
// ``0x'' or ``0X'' selects base 16; the ``0'' prefix selects base 8, and a // ``0x'' or ``0X'' selects base 16; the ``0'' prefix selects base 8, and a
// ``0b'' or ``0B'' prefix selects base 2. Otherwise the selected base is 10. // ``0b'' or ``0B'' prefix selects base 2. Otherwise the selected base is 10.
......
...@@ -60,7 +60,7 @@ func pow(x Word, n int) (p Word) { ...@@ -60,7 +60,7 @@ func pow(x Word, n int) (p Word) {
// digit = "0" ... "9" | "a" ... "z" | "A" ... "Z" . // digit = "0" ... "9" | "a" ... "z" | "A" ... "Z" .
// //
// Unless fracOk is set, the base argument must be 0 or a value between // Unless fracOk is set, the base argument must be 0 or a value between
// 2 through MaxBase. If fracOk is set, the base argument must be one of // 2 and MaxBase. If fracOk is set, the base argument must be one of
// 0, 2, 10, or 16. Providing an invalid base argument leads to a run- // 0, 2, 10, or 16. Providing an invalid base argument leads to a run-
// time panic. // time panic.
// //
......
...@@ -18,17 +18,14 @@ package mail ...@@ -18,17 +18,14 @@ package mail
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"encoding/base64"
"errors" "errors"
"fmt" "fmt"
"internal/mime"
"io" "io"
"io/ioutil"
"log" "log"
"net/textproto" "net/textproto"
"strconv"
"strings" "strings"
"time" "time"
"unicode"
) )
var debug = debugT(false) var debug = debugT(false)
...@@ -180,21 +177,7 @@ func (a *Address) String() string { ...@@ -180,21 +177,7 @@ func (a *Address) String() string {
return b.String() return b.String()
} }
// UTF-8 "Q" encoding return mime.EncodeWord(a.Name) + " " + s
b := bytes.NewBufferString("=?utf-8?q?")
for i := 0; i < len(a.Name); i++ {
switch c := a.Name[i]; {
case c == ' ':
b.WriteByte('_')
case isVchar(c) && c != '=' && c != '?' && c != '_':
b.WriteByte(c)
default:
fmt.Fprintf(b, "=%02X", c)
}
}
b.WriteString("?= ")
b.WriteString(s)
return b.String()
} }
type addrParser []byte type addrParser []byte
...@@ -352,7 +335,7 @@ func (p *addrParser) consumePhrase() (phrase string, err error) { ...@@ -352,7 +335,7 @@ func (p *addrParser) consumePhrase() (phrase string, err error) {
// RFC 2047 encoded-word starts with =?, ends with ?=, and has two other ?s. // RFC 2047 encoded-word starts with =?, ends with ?=, and has two other ?s.
if err == nil && strings.HasPrefix(word, "=?") && strings.HasSuffix(word, "?=") && strings.Count(word, "?") == 4 { if err == nil && strings.HasPrefix(word, "=?") && strings.HasSuffix(word, "?=") && strings.Count(word, "?") == 4 {
word, err = decodeRFC2047Word(word) word, err = mime.DecodeWord(word)
} }
if err != nil { if err != nil {
...@@ -440,86 +423,6 @@ func (p *addrParser) len() int { ...@@ -440,86 +423,6 @@ func (p *addrParser) len() int {
return len(*p) return len(*p)
} }
func decodeRFC2047Word(s string) (string, error) {
fields := strings.Split(s, "?")
if len(fields) != 5 || fields[0] != "=" || fields[4] != "=" {
return "", errors.New("address not RFC 2047 encoded")
}
charset, enc := strings.ToLower(fields[1]), strings.ToLower(fields[2])
if charset != "us-ascii" && charset != "iso-8859-1" && charset != "utf-8" {
return "", fmt.Errorf("charset not supported: %q", charset)
}
in := bytes.NewBufferString(fields[3])
var r io.Reader
switch enc {
case "b":
r = base64.NewDecoder(base64.StdEncoding, in)
case "q":
r = qDecoder{r: in}
default:
return "", fmt.Errorf("RFC 2047 encoding not supported: %q", enc)
}
dec, err := ioutil.ReadAll(r)
if err != nil {
return "", err
}
switch charset {
case "us-ascii":
b := new(bytes.Buffer)
for _, c := range dec {
if c >= 0x80 {
b.WriteRune(unicode.ReplacementChar)
} else {
b.WriteRune(rune(c))
}
}
return b.String(), nil
case "iso-8859-1":
b := new(bytes.Buffer)
for _, c := range dec {
b.WriteRune(rune(c))
}
return b.String(), nil
case "utf-8":
return string(dec), nil
}
panic("unreachable")
}
type qDecoder struct {
r io.Reader
scratch [2]byte
}
func (qd qDecoder) Read(p []byte) (n int, err error) {
// This method writes at most one byte into p.
if len(p) == 0 {
return 0, nil
}
if _, err := qd.r.Read(qd.scratch[:1]); err != nil {
return 0, err
}
switch c := qd.scratch[0]; {
case c == '=':
if _, err := io.ReadFull(qd.r, qd.scratch[:2]); err != nil {
return 0, err
}
x, err := strconv.ParseInt(string(qd.scratch[:2]), 16, 64)
if err != nil {
return 0, fmt.Errorf("mail: invalid RFC 2047 encoding: %q", qd.scratch[:2])
}
p[0] = byte(x)
case c == '_':
p[0] = ' '
default:
p[0] = c
}
return 1, nil
}
var atextChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + var atextChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
"abcdefghijklmnopqrstuvwxyz" + "abcdefghijklmnopqrstuvwxyz" +
"0123456789" + "0123456789" +
......
...@@ -553,7 +553,8 @@ func mallocgc(size uintptr, typ *_type, flags uint32) unsafe.Pointer { ...@@ -553,7 +553,8 @@ func mallocgc(size uintptr, typ *_type, flags uint32) unsafe.Pointer {
} }
s.freelist = v.ptr().next s.freelist = v.ptr().next
s.ref++ s.ref++
//TODO: prefetch v.next // prefetchnta offers best performance, see change list message.
prefetchnta(uintptr(v.ptr().next))
x = unsafe.Pointer(v) x = unsafe.Pointer(v)
(*[2]uint64)(x)[0] = 0 (*[2]uint64)(x)[0] = 0
(*[2]uint64)(x)[1] = 0 (*[2]uint64)(x)[1] = 0
...@@ -584,7 +585,8 @@ func mallocgc(size uintptr, typ *_type, flags uint32) unsafe.Pointer { ...@@ -584,7 +585,8 @@ func mallocgc(size uintptr, typ *_type, flags uint32) unsafe.Pointer {
} }
s.freelist = v.ptr().next s.freelist = v.ptr().next
s.ref++ s.ref++
//TODO: prefetch // prefetchnta offers best performance, see change list message.
prefetchnta(uintptr(v.ptr().next))
x = unsafe.Pointer(v) x = unsafe.Pointer(v)
if flags&flagNoZero == 0 { if flags&flagNoZero == 0 {
v.ptr().next = 0 v.ptr().next = 0
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment