Skip to content

Commit

Permalink
fix: escape characters (#10)
Browse files Browse the repository at this point in the history
Some characters are escaped and some are not. There seems to be no rhyme or reason to it.
  • Loading branch information
elliotchance authored Jan 18, 2019
1 parent d8e23c3 commit 1679dc2
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 9 deletions.
4 changes: 2 additions & 2 deletions consume.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,10 @@ func consumeStringRealPart(data []byte, offset int) (string, int, error) {
// redundant.
offset = newOffset + 1

s := DecodePHPString(data)
s := DecodePHPString(data[offset:length+offset])

// The +2 is to skip over the final '";'
return s[offset: offset+length], offset + length + 2, nil
return s, offset + length + 2, nil
}

func consumeNil(data []byte, offset int) (interface{}, int, error) {
Expand Down
4 changes: 4 additions & 0 deletions serialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ func MarshalFloat(value float64, bitSize int) []byte {
// One important distinction is that PHP stores binary data in strings. See
// MarshalBytes for more information.
func MarshalString(value string) []byte {
// As far as I can tell only the single-quote is escaped. Not even the
// backslash itself is escaped. Weird. See escapeTests for more information.
value = strings.Replace(value, "'", "\\'", -1)

return []byte(fmt.Sprintf("s:%d:\"%s\";", len(value), value))
}

Expand Down
14 changes: 14 additions & 0 deletions serialize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,17 @@ func TestMarshalFail(t *testing.T) {
t.Error(err.Error())
}
}

func TestMarshalEscape(t *testing.T) {
for testName, test := range escapeTests {
t.Run(testName, func(t *testing.T) {
options := phpserialize.DefaultMarshalOptions()
result, err := phpserialize.Marshal(test.Unserialized, options)
expectErrorToNotHaveOccurred(t, err)

if test.Serialized != string(result) {
t.Errorf("Expected:\n %#+v\nGot:\n %#+v", test.Serialized, result)
}
})
}
}
23 changes: 20 additions & 3 deletions unserialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,26 @@ func DecodePHPString(data []byte) string {
var buffer bytes.Buffer
for i := 0; i < len(data); i++ {
if data[i] == '\\' {
b, _ := strconv.ParseInt(string(data[i+2:i+4]), 16, 32)
buffer.WriteByte(byte(b))
i += 3
switch data[i+1] {
case 'x':
b, _ := strconv.ParseInt(string(data[i+2:i+4]), 16, 32)
buffer.WriteByte(byte(b))
i += 3

case 'n':
buffer.WriteByte('\n')
i++

case '\'':
buffer.WriteByte(data[i+1])
i++

default:
// It's a bit annoying but a backlash itself is not escaped. So
// if it was not followed by a known character we have to assume
// this.
buffer.WriteByte('\\')
}
} else {
buffer.WriteByte(data[i])
}
Expand Down
48 changes: 44 additions & 4 deletions unserialize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ func TestUnmarshalString(t *testing.T) {
"''": {[]byte("s:0:\"\";"), "", nil},
"'Hello world'": {[]byte("s:11:\"Hello world\";"), "Hello world", nil},
"'Björk Guðmundsdóttir'": {
[]byte("s:23:\"Bj\\xc3\\xb6rk Gu\\xc3\\xb0mundsd\\xc3\\xb3ttir\";"),
[]byte(`s:23:"Björk Guðmundsdóttir";`),
"Björk Guðmundsdóttir",
nil,
},
Expand All @@ -242,7 +242,7 @@ func TestUnmarshalString(t *testing.T) {
if test.expectedError == nil {
expectErrorToNotHaveOccurred(t, err)
if result != test.output {
t.Errorf("Expected '%v', got '%v'", result, test.output)
t.Errorf("Expected '%v', got '%v'", test.output, result)
}
} else {
expectErrorToEqual(t, err, test.expectedError)
Expand All @@ -257,8 +257,8 @@ func TestUnmarshalBinary(t *testing.T) {
output []byte
expectedError error
}{
"[]byte: \\001\\002\\003": {
[]byte("s:3:\"\\x01\\x02\\x03\";"),
"[]byte: \\x01\\x02\\x03": {
[]byte("s:3:\"\x01\x02\x03\";"),
[]byte{1, 2, 3},
nil,
},
Expand Down Expand Up @@ -541,3 +541,43 @@ func TestUnmarshalMultibyte(t *testing.T) {
t.Errorf("Expected:\n %#+v\nGot:\n %#+v", expected, result)
}
}

var escapeTests = map[string]struct{
Unserialized, Serialized string
}{
"SingleQuote": {
"foo'bar", `s:8:"foo\'bar";`,
},
"DoubleQuote": {
"foo\"bar", `s:7:"foo"bar";`,
},
"Backslash": {
"foo\\bar", `s:7:"foo\bar";`,
},
"Dollar": {
"foo$bar", `s:7:"foo$bar";`,
},
"NewLine": {
"foo\nbar", "s:7:\"foo\nbar\";",
},
"HorizontalTab": {
"foo\tbar", "s:7:\"foo\tbar\";",
},
"CarriageReturn": {
"foo\rbar", "s:7:\"foo\rbar\";",
},
}

func TestUnmarshalEscape(t *testing.T) {
for testName, test := range escapeTests {
t.Run(testName, func(t *testing.T) {
var result string
err := phpserialize.Unmarshal([]byte(test.Serialized), &result)
expectErrorToNotHaveOccurred(t, err)

if test.Unserialized != result {
t.Errorf("Expected:\n %#+v\nGot:\n %#+v", test.Unserialized, result)
}
})
}
}

0 comments on commit 1679dc2

Please sign in to comment.