Skip to content

Commit 1679dc2

Browse files
authored
fix: escape characters (#10)
Some characters are escaped and some are not. There seems to be no rhyme or reason to it.
1 parent d8e23c3 commit 1679dc2

5 files changed

+84
-9
lines changed

consume.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,10 @@ func consumeStringRealPart(data []byte, offset int) (string, int, error) {
8888
// redundant.
8989
offset = newOffset + 1
9090

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

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

9797
func consumeNil(data []byte, offset int) (interface{}, int, error) {

serialize.go

+4
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ func MarshalFloat(value float64, bitSize int) []byte {
9898
// One important distinction is that PHP stores binary data in strings. See
9999
// MarshalBytes for more information.
100100
func MarshalString(value string) []byte {
101+
// As far as I can tell only the single-quote is escaped. Not even the
102+
// backslash itself is escaped. Weird. See escapeTests for more information.
103+
value = strings.Replace(value, "'", "\\'", -1)
104+
101105
return []byte(fmt.Sprintf("s:%d:\"%s\";", len(value), value))
102106
}
103107

serialize_test.go

+14
Original file line numberDiff line numberDiff line change
@@ -171,3 +171,17 @@ func TestMarshalFail(t *testing.T) {
171171
t.Error(err.Error())
172172
}
173173
}
174+
175+
func TestMarshalEscape(t *testing.T) {
176+
for testName, test := range escapeTests {
177+
t.Run(testName, func(t *testing.T) {
178+
options := phpserialize.DefaultMarshalOptions()
179+
result, err := phpserialize.Marshal(test.Unserialized, options)
180+
expectErrorToNotHaveOccurred(t, err)
181+
182+
if test.Serialized != string(result) {
183+
t.Errorf("Expected:\n %#+v\nGot:\n %#+v", test.Serialized, result)
184+
}
185+
})
186+
}
187+
}

unserialize.go

+20-3
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,26 @@ func DecodePHPString(data []byte) string {
2626
var buffer bytes.Buffer
2727
for i := 0; i < len(data); i++ {
2828
if data[i] == '\\' {
29-
b, _ := strconv.ParseInt(string(data[i+2:i+4]), 16, 32)
30-
buffer.WriteByte(byte(b))
31-
i += 3
29+
switch data[i+1] {
30+
case 'x':
31+
b, _ := strconv.ParseInt(string(data[i+2:i+4]), 16, 32)
32+
buffer.WriteByte(byte(b))
33+
i += 3
34+
35+
case 'n':
36+
buffer.WriteByte('\n')
37+
i++
38+
39+
case '\'':
40+
buffer.WriteByte(data[i+1])
41+
i++
42+
43+
default:
44+
// It's a bit annoying but a backlash itself is not escaped. So
45+
// if it was not followed by a known character we have to assume
46+
// this.
47+
buffer.WriteByte('\\')
48+
}
3249
} else {
3350
buffer.WriteByte(data[i])
3451
}

unserialize_test.go

+44-4
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ func TestUnmarshalString(t *testing.T) {
227227
"''": {[]byte("s:0:\"\";"), "", nil},
228228
"'Hello world'": {[]byte("s:11:\"Hello world\";"), "Hello world", nil},
229229
"'Björk Guðmundsdóttir'": {
230-
[]byte("s:23:\"Bj\\xc3\\xb6rk Gu\\xc3\\xb0mundsd\\xc3\\xb3ttir\";"),
230+
[]byte(`s:23:"Björk Guðmundsdóttir";`),
231231
"Björk Guðmundsdóttir",
232232
nil,
233233
},
@@ -242,7 +242,7 @@ func TestUnmarshalString(t *testing.T) {
242242
if test.expectedError == nil {
243243
expectErrorToNotHaveOccurred(t, err)
244244
if result != test.output {
245-
t.Errorf("Expected '%v', got '%v'", result, test.output)
245+
t.Errorf("Expected '%v', got '%v'", test.output, result)
246246
}
247247
} else {
248248
expectErrorToEqual(t, err, test.expectedError)
@@ -257,8 +257,8 @@ func TestUnmarshalBinary(t *testing.T) {
257257
output []byte
258258
expectedError error
259259
}{
260-
"[]byte: \\001\\002\\003": {
261-
[]byte("s:3:\"\\x01\\x02\\x03\";"),
260+
"[]byte: \\x01\\x02\\x03": {
261+
[]byte("s:3:\"\x01\x02\x03\";"),
262262
[]byte{1, 2, 3},
263263
nil,
264264
},
@@ -541,3 +541,43 @@ func TestUnmarshalMultibyte(t *testing.T) {
541541
t.Errorf("Expected:\n %#+v\nGot:\n %#+v", expected, result)
542542
}
543543
}
544+
545+
var escapeTests = map[string]struct{
546+
Unserialized, Serialized string
547+
}{
548+
"SingleQuote": {
549+
"foo'bar", `s:8:"foo\'bar";`,
550+
},
551+
"DoubleQuote": {
552+
"foo\"bar", `s:7:"foo"bar";`,
553+
},
554+
"Backslash": {
555+
"foo\\bar", `s:7:"foo\bar";`,
556+
},
557+
"Dollar": {
558+
"foo$bar", `s:7:"foo$bar";`,
559+
},
560+
"NewLine": {
561+
"foo\nbar", "s:7:\"foo\nbar\";",
562+
},
563+
"HorizontalTab": {
564+
"foo\tbar", "s:7:\"foo\tbar\";",
565+
},
566+
"CarriageReturn": {
567+
"foo\rbar", "s:7:\"foo\rbar\";",
568+
},
569+
}
570+
571+
func TestUnmarshalEscape(t *testing.T) {
572+
for testName, test := range escapeTests {
573+
t.Run(testName, func(t *testing.T) {
574+
var result string
575+
err := phpserialize.Unmarshal([]byte(test.Serialized), &result)
576+
expectErrorToNotHaveOccurred(t, err)
577+
578+
if test.Unserialized != result {
579+
t.Errorf("Expected:\n %#+v\nGot:\n %#+v", test.Unserialized, result)
580+
}
581+
})
582+
}
583+
}

0 commit comments

Comments
 (0)