Skip to content

Commit cea5dab

Browse files
author
Maksim Naumov
committed
1) Decode objects with Serializable interface and store its raw data; 2) You can have your own decode function for object with custom serialize function ; 3) Improve NULL decoding
1 parent 954db21 commit cea5dab

File tree

6 files changed

+181
-9
lines changed

6 files changed

+181
-9
lines changed

common.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@ const VALUE_NAME_SEPARATOR = '|'
88
const TYPE_VALUE_SEPARATOR = ':'
99
const VALUES_SEPARATOR = ';'
1010

11+
type SerializableDecodeFunc func(string) (PhpSessionData, error)
12+
1113
type PhpValue interface{}
1214

1315
type PhpSessionData map[string]PhpValue
1416

1517
type PhpObject struct {
16-
members PhpSessionData
17-
className string
18+
RawData string
19+
members PhpSessionData
20+
className string
1821
}
1922

2023
func NewPhpObject() *PhpObject {

data/test.session

126 Bytes
Binary file not shown.

decoder.go

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,22 @@ import (
66
"fmt"
77
"strconv"
88
"strings"
9+
"log"
910
)
1011

1112
type PhpDecoder struct {
12-
source *strings.Reader
13-
data PhpSessionData
13+
DecodeFunc SerializableDecodeFunc
14+
RawData string
15+
source *strings.Reader
16+
data PhpSessionData
1417
}
1518

1619
func NewPhpDecoder(phpSession string) *PhpDecoder {
1720
sessionData := make(PhpSessionData)
1821
d := &PhpDecoder{
19-
source: strings.NewReader(phpSession),
20-
data: sessionData,
22+
RawData: phpSession,
23+
source: strings.NewReader(phpSession),
24+
data: sessionData,
2125
}
2226
return d
2327
}
@@ -50,7 +54,6 @@ func (decoder *PhpDecoder) DecodeValue() (PhpValue, error) {
5054
switch token {
5155
case 'N':
5256
value = nil
53-
decoder.expect(VALUES_SEPARATOR)
5457
case 'b':
5558
if rawValue, _, _err := decoder.source.ReadRune(); _err == nil {
5659
value = rawValue == '1'
@@ -82,8 +85,12 @@ func (decoder *PhpDecoder) DecodeValue() (PhpValue, error) {
8285
case 'a':
8386
value, err = decoder.decodeArray()
8487
decoder.allow(VALUES_SEPARATOR)
85-
case 'O', 'C':
88+
case 'O':
8689
value, err = decoder.decodeObject()
90+
case 'C':
91+
value, err = decoder.decodeSerializableObject()
92+
default:
93+
log.Fatalf("Undefined token: %v [%#U]", token, token)
8794
}
8895
}
8996
return value, err
@@ -100,6 +107,22 @@ func (decoder *PhpDecoder) decodeObject() (*PhpObject, error) {
100107
return value, err
101108
}
102109

110+
func (decoder *PhpDecoder) decodeSerializableObject() (*PhpObject, error) {
111+
value := &PhpObject{}
112+
var err error
113+
114+
if value.className, err = decoder.decodeString(); err == nil {
115+
decoder.expect(TYPE_VALUE_SEPARATOR)
116+
value.RawData, err = decoder.decodeString()
117+
}
118+
119+
if decoder.DecodeFunc != nil {
120+
value.members, err = decoder.DecodeFunc(value.RawData)
121+
}
122+
123+
return value, err
124+
}
125+
103126
func (decoder *PhpDecoder) decodeArray() (PhpSessionData, error) {
104127
value := make(PhpSessionData)
105128
var err error
@@ -194,3 +217,22 @@ func (decoder *PhpDecoder) allow(expectRune rune) error {
194217
}
195218
return err
196219
}
220+
221+
func SerializableDecode(s string) (valueData PhpSessionData, err error) {
222+
var (
223+
value PhpValue
224+
ok bool
225+
)
226+
227+
decoder := NewPhpDecoder(s)
228+
decoder.DecodeFunc = SerializableDecode
229+
230+
if value, err = decoder.DecodeValue(); err == nil {
231+
valueData, ok = value.(PhpSessionData)
232+
if !ok {
233+
err = errors.New("Error casting to PhpSessionData")
234+
}
235+
}
236+
237+
return
238+
}

decoder_test.go

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,13 +252,64 @@ func TestDecodeMultipleArraysWithoutSemicolons(t *testing.T) {
252252
}
253253
}
254254

255+
const SERIALIZABLE_OBJECT_VALUE_NO_FUNC_ENCODED = "obj|C:10:\"TestObject\":49:{a:3:{s:1:\"a\";i:5;s:1:\"b\";s:4:\"priv\";s:1:\"c\";i:8;}}"
256+
257+
func TestDecodeSerializableObjectValueNoFunc(t *testing.T) {
258+
decoder := NewPhpDecoder(SERIALIZABLE_OBJECT_VALUE_NO_FUNC_ENCODED)
259+
if result, err := decoder.Decode(); err != nil {
260+
t.Errorf("Can not decode object value %#v \n", err)
261+
} else {
262+
if v, ok := (result)["obj"]; !ok {
263+
t.Errorf("Object value was not decoded \n")
264+
} else if objValue, ok := v.(*PhpObject); ok != true {
265+
t.Errorf("Object value was decoded incorrectly: %#v \n", v)
266+
} else if objValue.GetClassName() != "TestObject" {
267+
t.Errorf("Object name was decoded incorrectly: %#v\n", objValue.GetClassName())
268+
} else if objValue.RawData != "a:3:{s:1:\"a\";i:5;s:1:\"b\";s:4:\"priv\";s:1:\"c\";i:8;}" {
269+
t.Errorf("RawData of object was decoded incorrectly: %#v\n", objValue.RawData)
270+
}
271+
}
272+
}
273+
274+
const SERIALIZABLE_OBJECT_VALUE_ENCODED = "object|C:10:\"TestObject\":96:{a:1:{s:4:\"item\";O:8:\"AbcClass\":3:{s:1:\"a\";i:5;s:11:\"\x00AbcClass\x00b\";s:7:\"private\";s:4:\"\x00*\x00c\";i:8;}}}"
275+
276+
func TestDecodeSerializableObjectValue(t *testing.T) {
277+
decoder := NewPhpDecoder(SERIALIZABLE_OBJECT_VALUE_ENCODED)
278+
decoder.DecodeFunc = SerializableDecode
279+
if result, err := decoder.Decode(); err != nil {
280+
t.Errorf("Can not decode object value %#v \n", err)
281+
} else {
282+
if v, ok := (result)["object"]; !ok {
283+
t.Errorf("Object value was not decoded \n")
284+
} else if objValue, ok := v.(*PhpObject); ok != true {
285+
t.Errorf("Object value was decoded incorrectly: %#v \n", v)
286+
} else if objValue.GetClassName() != "TestObject" {
287+
t.Errorf("Object name was decoded incorrectly: %#v\n", objValue.GetClassName())
288+
} else if objValue.RawData != "a:1:{s:4:\"item\";O:8:\"AbcClass\":3:{s:1:\"a\";i:5;s:11:\"\x00AbcClass\x00b\";s:7:\"private\";s:4:\"\x00*\x00c\";i:8;}}" {
289+
t.Errorf("RawData of object was decoded incorrectly: %#v\n", objValue.RawData)
290+
} else if itemValue, ok := objValue.GetPublicMemberValue("item"); !ok {
291+
t.Errorf("Public member of object was decoded incorrectly: %#v\n", objValue.GetMembers())
292+
} else if itemObjValue, ok := itemValue.(*PhpObject); ok != true {
293+
t.Errorf("Object value was decoded incorrectly: %#v \n", v)
294+
} else if itemObjValue.GetClassName() != "AbcClass" {
295+
t.Errorf("Object name was decoded incorrectly: %#v\n", itemObjValue.GetClassName())
296+
} else if value1, ok := itemObjValue.GetPublicMemberValue("a"); !ok || value1 != 5 {
297+
t.Errorf("Public member of object was decoded incorrectly: %#v\n", objValue.GetMembers())
298+
} else if value2, ok := itemObjValue.GetPrivateMemberValue("b"); !ok || value2 != "private" {
299+
t.Errorf("Private member of object was decoded incorrectly: %#v\n", objValue.GetMembers())
300+
} else if value3, ok := itemObjValue.GetProtectedMemberValue("c"); !ok || value3 != 8 {
301+
t.Errorf("Protected member of object was decoded incorrectly: %#v\n", objValue.GetMembers())
302+
}
303+
}
304+
}
305+
255306
func TestDecodeRealData(t *testing.T) {
256307
testData, _ := ioutil.ReadFile("./data/test.session")
257308
decoder := NewPhpDecoder(string(testData))
258309
if result, err := decoder.Decode(); err != nil {
259310
t.Errorf("Can not decode array value %#v \n", err)
260311
} else {
261-
rootKeys := []string{"product_last_viewed", "core", "customer", "checkout", "store_default", "catalog"}
312+
rootKeys := []string{"product_last_viewed", "core", "customer", "checkout", "store_default", "catalog", "object"}
262313
for _, v := range rootKeys {
263314
if _, ok := result[v]; !ok {
264315
t.Errorf("Can not find %v key\n", v)

php/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
session*

php/test.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
class TestSessionHandler implements SessionHandlerInterface
4+
{
5+
private $dir;
6+
7+
public function __construct()
8+
{
9+
$this->dir = dirname(__FILE__) . '/';
10+
}
11+
12+
public function getFileName($id)
13+
{
14+
return $this->dir . 'session_' . $id;
15+
}
16+
17+
function open($savePath, $sessionName)
18+
{
19+
return true;
20+
}
21+
22+
function close()
23+
{
24+
return true;
25+
}
26+
27+
function read($id)
28+
{
29+
$data = @file_get_contents($this->getFileName($id));
30+
return false === $data ? '' : $data;
31+
}
32+
33+
function write($id, $data)
34+
{
35+
echo json_encode($data).PHP_EOL;
36+
return file_put_contents($this->getFileName($id), $data) === false ? false : true;
37+
}
38+
39+
function destroy($id)
40+
{
41+
return true;
42+
}
43+
44+
function gc($maxlifetime)
45+
{
46+
return true;
47+
}
48+
}
49+
50+
class AbcClass {
51+
public $a = 5;
52+
private $b = 'private';
53+
protected $c = 8;
54+
}
55+
56+
class TestObject implements Serializable {
57+
public $item;
58+
public function serialize() {
59+
return serialize(array('item' => $this->item));
60+
}
61+
public function unserialize($serialized) {
62+
return unserialize($serialized);
63+
}
64+
}
65+
66+
$object = new TestObject();
67+
$object->item = new AbcClass();
68+
69+
$handler = new TestSessionHandler();
70+
session_set_save_handler($handler, true);
71+
session_start();
72+
73+
$_SESSION['object'] = $object;
74+
75+
echo $handler->getFileName(session_id()) . PHP_EOL;

0 commit comments

Comments
 (0)