php_session 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +56 -0
- data/Rakefile +6 -0
- data/lib/php_session.rb +60 -0
- data/lib/php_session/decoder.rb +224 -0
- data/lib/php_session/encoder.rb +92 -0
- data/lib/php_session/errors.rb +4 -0
- data/lib/php_session/version.rb +4 -0
- data/php_session.gemspec +24 -0
- data/spec/php_session/decoder_spec.rb +183 -0
- data/spec/php_session/encoder_spec.rb +95 -0
- data/spec/php_session_spec.rb +84 -0
- data/spec/spec_helper.rb +14 -0
- metadata +108 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 301319f11805c591578f708b2a3cc3023e3c0fe7
|
4
|
+
data.tar.gz: 09259e558139f5fb3a242fc8398084178de42cfa
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 24532c8a7b0f755b6a60c5ae4461c898827e2254b9cd6525b4174f48fd7cdd7128970be7a4882852b61d8bc510748a1828eff43e8ca03a85a0472ee374b78a82
|
7
|
+
data.tar.gz: be39ee678cb923830646b666f2f82d018036072f903367993df75d913956a50a0390225f0c65c59b869c771cff583800aef3fb0b141a95e6eee7ebf27185c590
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Shinpei Maruyama
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# PHPSession
|
2
|
+
[](https://travis-ci.org/Shinpeim/ruby_php_session)
|
3
|
+
|
4
|
+
PHPSession is a php session file reader/writer. Multibyte string and exclusive control are supported.
|
5
|
+
|
6
|
+
When decoding php session data to ruby objects,
|
7
|
+
|
8
|
+
* An associative array in PHP is mapped to a hash in ruby.
|
9
|
+
* Object in PHP is mapped to Struct::ClassName in ruby.
|
10
|
+
|
11
|
+
When encoding ruby objects to php session data,
|
12
|
+
|
13
|
+
* Strings or symbols in ruby is mapped to strings in PHP.
|
14
|
+
* Instances of Struct::ClassName in ruby is mapped to a objects in PHP.
|
15
|
+
* Arrays in ruby is mapped to a associative arrays which's keys are integer in PHP.
|
16
|
+
* Hashes in ruby is mapped to a associative arrays which's keys are string in PHP.
|
17
|
+
|
18
|
+
## Installation
|
19
|
+
|
20
|
+
Add this line to your application's Gemfile:
|
21
|
+
|
22
|
+
gem 'php_session'
|
23
|
+
|
24
|
+
And then execute:
|
25
|
+
|
26
|
+
$ bundle install
|
27
|
+
|
28
|
+
Or install it yourself as:
|
29
|
+
|
30
|
+
$ gem install php_session
|
31
|
+
|
32
|
+
## Usage
|
33
|
+
# initialize
|
34
|
+
session = PHPSession.new(session_file_dir, session_id)
|
35
|
+
|
36
|
+
begin
|
37
|
+
# load session data from file and obtain a lock
|
38
|
+
data = session.load
|
39
|
+
|
40
|
+
# save session and release the lock
|
41
|
+
session.commit(data)
|
42
|
+
|
43
|
+
# delete session file and release the lock
|
44
|
+
session.destroy
|
45
|
+
ensure
|
46
|
+
# please ensure that the lock is released and the file is closed.
|
47
|
+
session.ensure_file_closed
|
48
|
+
end
|
49
|
+
|
50
|
+
## Contributing
|
51
|
+
|
52
|
+
1. Fork it
|
53
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
54
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
55
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
56
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/lib/php_session.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require "php_session/version"
|
3
|
+
require "php_session/errors"
|
4
|
+
require "php_session/decoder"
|
5
|
+
require "php_session/encoder"
|
6
|
+
|
7
|
+
class PHPSession
|
8
|
+
attr_reader :data
|
9
|
+
def initialize(session_dir, session_id)
|
10
|
+
@session_dir = File.expand_path(session_dir)
|
11
|
+
set_session_id(session_id)
|
12
|
+
|
13
|
+
@file = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def load
|
17
|
+
@file = File.open(file_path, File::CREAT|File::RDWR)
|
18
|
+
|
19
|
+
unless @file.flock(File::LOCK_EX)
|
20
|
+
raise PHPSession::Errors, "can't obtain lock of session file"
|
21
|
+
end
|
22
|
+
|
23
|
+
data = Decoder.decode(@file.read) || {}
|
24
|
+
@file.rewind
|
25
|
+
data
|
26
|
+
end
|
27
|
+
|
28
|
+
def destroy
|
29
|
+
if @file && ! @file.closed?
|
30
|
+
@file.truncate(0)
|
31
|
+
end
|
32
|
+
ensure_file_closed
|
33
|
+
File.delete(file_path)
|
34
|
+
rescue Errno::ENOENT => e
|
35
|
+
# file already deleted
|
36
|
+
end
|
37
|
+
|
38
|
+
def commit(data)
|
39
|
+
@file.truncate(0)
|
40
|
+
@file.write(Encoder.encode(data))
|
41
|
+
ensure_file_closed
|
42
|
+
end
|
43
|
+
|
44
|
+
def ensure_file_closed
|
45
|
+
if @file && ! @file.closed?
|
46
|
+
@file.close
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def set_session_id(session_id)
|
53
|
+
@session_id = session_id
|
54
|
+
raise Errors::SecurityError, "directory traversal detected" unless file_path.index(@session_dir) == 0
|
55
|
+
end
|
56
|
+
|
57
|
+
def file_path
|
58
|
+
File.expand_path(File.join(@session_dir, "sess_#{@session_id}"))
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
class PHPSession
|
3
|
+
class Decoder
|
4
|
+
attr_accessor :buffer, :state, :stack, :array
|
5
|
+
attr_reader :encoding
|
6
|
+
|
7
|
+
def self.decode(string, encoding = "UTF-8")
|
8
|
+
self.new(string, encoding).decode
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(string, encoding)
|
12
|
+
@encoding = encoding
|
13
|
+
@buffer = string.force_encoding("ASCII-8BIT")
|
14
|
+
@data = {}
|
15
|
+
@state = State::VarName
|
16
|
+
@stack = []
|
17
|
+
@array = [] # array of array
|
18
|
+
end
|
19
|
+
|
20
|
+
def decode
|
21
|
+
loop do
|
22
|
+
break if @buffer.size == 0
|
23
|
+
@state.parse(self)
|
24
|
+
end
|
25
|
+
@data
|
26
|
+
end
|
27
|
+
|
28
|
+
def start_array(length, klass = nil)
|
29
|
+
# [length, comsumed?, class]
|
30
|
+
@array.unshift({
|
31
|
+
:length => length,
|
32
|
+
:consumed_count => 0,
|
33
|
+
:klass => klass
|
34
|
+
})
|
35
|
+
end
|
36
|
+
def elements_count
|
37
|
+
@array[0][:length]
|
38
|
+
end
|
39
|
+
def in_array
|
40
|
+
@array.size > 0
|
41
|
+
end
|
42
|
+
def consume_array
|
43
|
+
@array[0][:consumed_count] += 1
|
44
|
+
end
|
45
|
+
def finished_array
|
46
|
+
@array[0][:length] * 2 == @array[0][:consumed_count]
|
47
|
+
end
|
48
|
+
|
49
|
+
def extract_stack(count)
|
50
|
+
poped = @stack[(@stack.size - count) .. -1]
|
51
|
+
@stack = @stack.slice(0, @stack.size - count)
|
52
|
+
poped
|
53
|
+
end
|
54
|
+
|
55
|
+
def process_empty_array_value
|
56
|
+
array_which_finished = @array.shift
|
57
|
+
|
58
|
+
klass = array_which_finished[:klass];
|
59
|
+
if klass
|
60
|
+
struct = define_or_find_struct(klass, [])
|
61
|
+
process_value(struct.new)
|
62
|
+
else
|
63
|
+
process_value({})
|
64
|
+
end
|
65
|
+
@state = State::ArrayEnd
|
66
|
+
end
|
67
|
+
def process_value(value)
|
68
|
+
if in_array
|
69
|
+
@stack.push(value)
|
70
|
+
consume_array
|
71
|
+
|
72
|
+
if finished_array
|
73
|
+
array_which_finished = @array.shift
|
74
|
+
key_values_array = extract_stack(array_which_finished[:length] * 2)
|
75
|
+
key_values = key_values_array.group_by.with_index{|el, i| i%2 == 0 ? :key : :value}
|
76
|
+
|
77
|
+
klass = array_which_finished[:klass];
|
78
|
+
if klass
|
79
|
+
struct = define_or_find_struct(klass, key_values[:key])
|
80
|
+
process_value(struct.new(*key_values[:value]))
|
81
|
+
@state = State::ArrayEnd
|
82
|
+
@state.parse(self)
|
83
|
+
else
|
84
|
+
hash = {}
|
85
|
+
key_values_array.each_slice(2) do |kv|
|
86
|
+
hash[kv[0]] = kv[1]
|
87
|
+
end
|
88
|
+
process_value(hash)
|
89
|
+
|
90
|
+
@state = State::ArrayEnd
|
91
|
+
@state.parse(self)
|
92
|
+
end
|
93
|
+
else
|
94
|
+
@state = State::VarType
|
95
|
+
end
|
96
|
+
else
|
97
|
+
varname = @stack.pop;
|
98
|
+
@data[varname] = value;
|
99
|
+
@state = State::VarName
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def define_or_find_struct(name, properties)
|
106
|
+
if Struct.const_defined?(name)
|
107
|
+
struct = Struct.const_get(name)
|
108
|
+
if struct.members.sort != properties.map(&:to_sym).sort
|
109
|
+
raise Errors::ParseError, "objects properties don't match with the other object which has same class"
|
110
|
+
end
|
111
|
+
else
|
112
|
+
struct = Struct.new(name, *properties)
|
113
|
+
end
|
114
|
+
struct
|
115
|
+
end
|
116
|
+
|
117
|
+
module State
|
118
|
+
class VarName
|
119
|
+
def self.parse(decoder)
|
120
|
+
matches = /^(.*?)\|(.*)$/.match(decoder.buffer)
|
121
|
+
raise Errors::ParseError, "invalid format" if matches.nil?
|
122
|
+
varName = matches[1]
|
123
|
+
decoder.buffer = matches[2]
|
124
|
+
|
125
|
+
decoder.stack.push(varName)
|
126
|
+
decoder.state = VarType
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
class VarType
|
131
|
+
def self.parse(decoder)
|
132
|
+
case decoder.buffer
|
133
|
+
when /^s:(\d+):(.*)/ # string
|
134
|
+
decoder.buffer = $2
|
135
|
+
decoder.stack.push($1.to_i)
|
136
|
+
decoder.state = String
|
137
|
+
when /^i:(-?\d+);(.*)/ #int
|
138
|
+
decoder.buffer = $2
|
139
|
+
decoder.process_value($1.to_i)
|
140
|
+
when /^d:(-?\d+(?:\.\d+)?);(.*)/ # double
|
141
|
+
decoder.buffer = $2
|
142
|
+
decoder.process_value($1.to_f)
|
143
|
+
when /^a:(\d+):(.*)/ # array
|
144
|
+
decoder.buffer = $2
|
145
|
+
decoder.start_array($1.to_i)
|
146
|
+
decoder.state = ArrayStart
|
147
|
+
when /^(N);(.*)/ # null
|
148
|
+
decoder.buffer = $2
|
149
|
+
decoder.process_value(nil)
|
150
|
+
when /^b:([01]);(.*)/ # boolean
|
151
|
+
decoder.buffer = $2
|
152
|
+
bool = case $1
|
153
|
+
when "0" then false
|
154
|
+
when "1" then true
|
155
|
+
end
|
156
|
+
decoder.process_value(bool)
|
157
|
+
when /^O:(\d+):(.*)/ # object
|
158
|
+
decoder.buffer = $2
|
159
|
+
decoder.stack.push($1.to_i);
|
160
|
+
decoder.state = ClassName
|
161
|
+
when /^[Rr]:(\d+);(.*)/ # reference count?
|
162
|
+
decoder.buffer = $2
|
163
|
+
decoder.process_value($1)
|
164
|
+
else
|
165
|
+
raise Errors::ParseError, "invalid session format"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
class String
|
171
|
+
def self.parse(decoder)
|
172
|
+
length = decoder.stack.pop
|
173
|
+
length_include_quotes = length + 3
|
174
|
+
|
175
|
+
value_include_quotes = decoder.buffer[0, length_include_quotes]
|
176
|
+
value = value_include_quotes.gsub(/^"/,'').gsub(/";$/, '')
|
177
|
+
value.force_encoding(decoder.encoding)
|
178
|
+
|
179
|
+
decoder.buffer = decoder.buffer[length_include_quotes .. -1]
|
180
|
+
|
181
|
+
decoder.process_value(value)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
class ArrayStart
|
186
|
+
def self.parse(decoder)
|
187
|
+
raise Errors::ParseError, "invalid array format" unless decoder.buffer =~ /^{/
|
188
|
+
decoder.buffer = decoder.buffer[1..-1]
|
189
|
+
if decoder.elements_count > 0
|
190
|
+
decoder.state = VarType
|
191
|
+
else
|
192
|
+
decoder.process_empty_array_value
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
class ArrayEnd
|
198
|
+
def self.parse(decoder)
|
199
|
+
raise Errors::ParseError, "invalid array format" unless decoder.buffer =~ /^}/
|
200
|
+
decoder.buffer = decoder.buffer[1..-1]
|
201
|
+
next_state = decoder.in_array ? VarType : VarName;
|
202
|
+
decoder.state = next_state
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
class ClassName
|
207
|
+
def self.parse(decoder)
|
208
|
+
length = decoder.stack.pop;
|
209
|
+
length_include_quotes = length + 3
|
210
|
+
|
211
|
+
value_include_quotes = decoder.buffer[0, length_include_quotes]
|
212
|
+
klass = value_include_quotes.gsub(/^"/,'').gsub(/":$/,'')
|
213
|
+
|
214
|
+
decoder.buffer = decoder.buffer[length_include_quotes..-1]
|
215
|
+
|
216
|
+
raise Errors::ParseError, "invalid class format" unless decoder.buffer =~ /^(\d+):(.*)/
|
217
|
+
decoder.buffer = $2
|
218
|
+
decoder.start_array($1.to_i, klass)
|
219
|
+
decoder.state = ArrayStart
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
class PHPSession
|
3
|
+
class Encoder
|
4
|
+
def self.encode(hash)
|
5
|
+
serialized = hash.map do|k,v|
|
6
|
+
"#{k}|#{serialize(v)}"
|
7
|
+
end
|
8
|
+
serialized.join
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def self.serialize(value)
|
14
|
+
get_serializer(value.class).serialize(value)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.get_serializer(klass)
|
18
|
+
case
|
19
|
+
when klass <= String || klass <= Symbol
|
20
|
+
StringSerializer
|
21
|
+
when klass <= Integer
|
22
|
+
IntegerSerializer
|
23
|
+
when klass <= Float
|
24
|
+
FloatSerializer
|
25
|
+
when klass <= NilClass
|
26
|
+
NilSerializer
|
27
|
+
when klass <= TrueClass || klass <= FalseClass
|
28
|
+
BooleanSerializer
|
29
|
+
when klass <= Hash
|
30
|
+
HashSerializer
|
31
|
+
when klass <= Array
|
32
|
+
ArraySerializer
|
33
|
+
when klass <= Struct
|
34
|
+
StructSerializer
|
35
|
+
else
|
36
|
+
raise Errors::EncodeError, "unsupported class:#{klass.to_s} is passed."
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class StringSerializer
|
41
|
+
def self.serialize(value)
|
42
|
+
s = value.to_s
|
43
|
+
%|s:#{s.bytesize}:"#{s}";|
|
44
|
+
end
|
45
|
+
end
|
46
|
+
class IntegerSerializer
|
47
|
+
def self.serialize(value)
|
48
|
+
%|i:#{value};|
|
49
|
+
end
|
50
|
+
end
|
51
|
+
class FloatSerializer
|
52
|
+
def self.serialize(value)
|
53
|
+
%|d:#{value};|
|
54
|
+
end
|
55
|
+
end
|
56
|
+
class NilSerializer
|
57
|
+
def self.serialize(value)
|
58
|
+
%|N;|
|
59
|
+
end
|
60
|
+
end
|
61
|
+
class BooleanSerializer
|
62
|
+
def self.serialize(value)
|
63
|
+
%|b:#{value ? 1 : 0};|
|
64
|
+
end
|
65
|
+
end
|
66
|
+
class HashSerializer
|
67
|
+
def self.serialize(value)
|
68
|
+
serialized_values = value.map do |k, v|
|
69
|
+
[Encoder.serialize(k), Encoder.serialize(v)]
|
70
|
+
end
|
71
|
+
%|a:#{value.size}:{#{serialized_values.flatten.join}}|
|
72
|
+
end
|
73
|
+
end
|
74
|
+
class ArraySerializer
|
75
|
+
def self.serialize(value)
|
76
|
+
key_values = value.map.with_index{|el, i| [i, el]}
|
77
|
+
hash = Hash[key_values]
|
78
|
+
HashSerializer.serialize(hash)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
class StructSerializer
|
82
|
+
def self.serialize(value)
|
83
|
+
key_values = value.members.zip(value.values)
|
84
|
+
serialized_key_values = key_values.map do |kv|
|
85
|
+
kv.map {|el| Encoder.serialize(el)}
|
86
|
+
end
|
87
|
+
class_name = value.class.to_s.gsub(/^Struct::/,'')
|
88
|
+
%|o:#{class_name.bytesize}:"#{class_name}":#{key_values.size}:{#{serialized_key_values.flatten.join}}|
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
data/php_session.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'php_session/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "php_session"
|
8
|
+
spec.version = PHPSession::VERSION
|
9
|
+
spec.authors = ["Shinpei Maruyama"]
|
10
|
+
spec.email = ["shinpeim@gmail.com"]
|
11
|
+
spec.description = %q{php session reader/writer}
|
12
|
+
spec.summary = %q{php_session is a php session file reader/writer. Multibyte string and exclusiv control is supported}
|
13
|
+
spec.homepage = "https://github.com/Shinpeim/ruby_php_session"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe PHPSession::Decoder do
|
5
|
+
describe ".decode" do
|
6
|
+
context "when given invalid session format" do
|
7
|
+
it "should raise ParseError" do
|
8
|
+
expect {
|
9
|
+
PHPSession::Decoder.decode("invalid format string")
|
10
|
+
}.to raise_error(PHPSession::Errors::ParseError)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
context "when given a String value in session data" do
|
14
|
+
it "should return a hash which has a string" do
|
15
|
+
expect(
|
16
|
+
PHPSession::Decoder.decode('key|s:3:"str";')
|
17
|
+
).to eq({"key" => "str"})
|
18
|
+
end
|
19
|
+
end
|
20
|
+
context "when given a multibyte string in session data" do
|
21
|
+
it "should return a hash which has a multibyte string" do
|
22
|
+
expect(
|
23
|
+
PHPSession::Decoder.decode('key|s:9:"テスト";')
|
24
|
+
).to eq({"key" => "テスト"})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
context "when given a Integer value in session data" do
|
28
|
+
it "should return a hash which has a int value" do
|
29
|
+
expect(
|
30
|
+
PHPSession::Decoder.decode('key|i:10;')
|
31
|
+
).to eq({"key" => 10})
|
32
|
+
end
|
33
|
+
end
|
34
|
+
context "when given a doulble value in session data" do
|
35
|
+
it "should return a hash which has a float value" do
|
36
|
+
expect(
|
37
|
+
PHPSession::Decoder.decode('key|d:3.1415;')
|
38
|
+
).to eq({"key" => 3.1415})
|
39
|
+
end
|
40
|
+
end
|
41
|
+
context "when given a null in session data" do
|
42
|
+
it "should return a hash which has a nil" do
|
43
|
+
expect(
|
44
|
+
PHPSession::Decoder.decode('key|N;')
|
45
|
+
).to eq({"key" => nil})
|
46
|
+
end
|
47
|
+
end
|
48
|
+
context "when given boolean in session data" do
|
49
|
+
it "should return a hash which has a boolean" do
|
50
|
+
expect(
|
51
|
+
PHPSession::Decoder.decode('key|b:1;')
|
52
|
+
).to eq({"key" => true})
|
53
|
+
expect(
|
54
|
+
PHPSession::Decoder.decode('key|b:0;')
|
55
|
+
).to eq({"key" => false})
|
56
|
+
end
|
57
|
+
end
|
58
|
+
context "when given a empty array in session data" do
|
59
|
+
it "should return a hash which contains no data" do
|
60
|
+
expect(
|
61
|
+
PHPSession::Decoder.decode('key|a:0:{}')
|
62
|
+
).to eq({"key" => {}})
|
63
|
+
end
|
64
|
+
end
|
65
|
+
context "when given a AssociatedArray value in session data" do
|
66
|
+
it "should return a hash which has a hash" do
|
67
|
+
expect(
|
68
|
+
PHPSession::Decoder.decode('key|a:2:{s:2:"k1";s:2:"v1";s:2:"k2";s:2:"v2";}')
|
69
|
+
).to eq({
|
70
|
+
"key" => {
|
71
|
+
"k1" => "v1",
|
72
|
+
"k2" => "v2"
|
73
|
+
}
|
74
|
+
})
|
75
|
+
end
|
76
|
+
end
|
77
|
+
context "when given a object" do
|
78
|
+
it "should return a hash which has a object" do
|
79
|
+
data = PHPSession::Decoder.decode('key|O:4:"Nyan":1:{s:1:"k";s:1:"v";}')
|
80
|
+
expect(data["key"].class).to eq(Struct::Nyan)
|
81
|
+
expect(data["key"].k).to eq("v")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
context "when given a object which has no property" do
|
85
|
+
it "should return a hash which has a object" do
|
86
|
+
data = PHPSession::Decoder.decode('key|O:4:"Piyo":0:{}')
|
87
|
+
expect(data["key"].class).to eq(Struct::Piyo)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
context "when given objects which have same class" do
|
91
|
+
it "should return same class Structs" do
|
92
|
+
data = PHPSession::Decoder.decode('key|a:2:{s:1:"1";O:4:"Nyan":1:{s:1:"k";s:1:"v";}s:1:"2";O:4:"Nyan":1:{s:1:"k";s:1:"v";}}')
|
93
|
+
expect(data["key"]["1"].class).to eq(Struct::Nyan)
|
94
|
+
expect(data["key"]["1"].k).to eq("v")
|
95
|
+
end
|
96
|
+
end
|
97
|
+
context "when given objects which have same class but different properties" do
|
98
|
+
it "should raise ParseError" do
|
99
|
+
expect {
|
100
|
+
PHPSession::Decoder.decode('key|a:2:{s:1:"1";O:4:"Nyan":1:{s:1:"k";s:1:"v";}s:1:"2";O:4:"Nyan":0:{}}')
|
101
|
+
}.to raise_error(PHPSession::Errors::ParseError)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
context "when given multi key session data" do
|
105
|
+
it "should return a hash which has multipul keys" do
|
106
|
+
expect(
|
107
|
+
PHPSession::Decoder.decode('key1|d:3.1415;key2|s:4:"hoge";')
|
108
|
+
).to eq({
|
109
|
+
"key1" => 3.1415,
|
110
|
+
"key2" => "hoge",
|
111
|
+
})
|
112
|
+
end
|
113
|
+
end
|
114
|
+
context "when given nested array" do
|
115
|
+
it "should return a nested array" do
|
116
|
+
expect(
|
117
|
+
PHPSession::Decoder.decode('key1|a:1:{s:1:"a";a:1:{s:1:"b";s:1:"c";}}')
|
118
|
+
).to eq({
|
119
|
+
"key1" => {
|
120
|
+
"a" => {
|
121
|
+
"b" => "c"
|
122
|
+
}
|
123
|
+
}
|
124
|
+
})
|
125
|
+
end
|
126
|
+
end
|
127
|
+
context "when given a complex data" do
|
128
|
+
it "should return a complex hash" do
|
129
|
+
session_data = <<'EOS'
|
130
|
+
Config|a:3:{s:9:"userAgent";s:32:"d2673506d1c64ae55d1ea2421143f994";s:4:"time";i:1382414678;s:7:"timeout";i:10;}Account|a:2:{s:7:"Account";a:14:{s:2:"id";s:1:"1";s:7:"user_id";s:1:"1";s:5:"state";s:6:"active";s:5:"email";s:16:"test@example.com";s:8:"password";s:32:"5a2c1429a27a9783609e8b429748aa33";s:10:"owner_flag";b:1;s:4:"name";s:9:"テスト";s:7:"created";s:19:"2013-10-16 11:21:35";s:8:"modified";s:19:"2013-10-16 11:21:36";s:4:"uuid";s:36:"0e866700-c572-4d17-aab3-de0f35cf502e";s:10:"user_state";s:6:"active";s:8:"pc1_flag";b:0;s:10:"account_id";s:1:"1";s:8:"rmc_type";s:2:"pc";}s:4:"User";a:20:{s:2:"id";s:1:"1";s:12:"company_name";s:9:"テスト";s:13:"producer_name";s:9:"テスト";s:16:"consumer_message";s:0:"";s:7:"created";s:19:"2013-10-16 11:21:35";s:8:"modified";s:19:"2013-10-16 11:21:35";s:5:"state";s:6:"active";s:10:"apply_flag";b:0;s:9:"paid_flag";b:0;s:17:"company_name_kana";s:0:"";s:18:"producer_name_kana";s:0:"";s:15:"payer_name_kana";s:0:"";s:8:"zip_code";s:0:"";s:10:"prefecture";s:0:"";s:7:"address";s:0:"";s:8:"address2";s:0:"";s:5:"phone";s:0:"";s:5:"other";s:0:"";s:4:"uuid";s:36:"f6120e89-0554-449a-9c79-aa330ba9d6c5";s:8:"pc1_flag";b:0;}}Auth|a:0:{}
|
131
|
+
EOS
|
132
|
+
expect(PHPSession::Decoder.decode(session_data)).to eq({
|
133
|
+
"Config" => {
|
134
|
+
"userAgent"=>"d2673506d1c64ae55d1ea2421143f994",
|
135
|
+
"time"=>1382414678,
|
136
|
+
"timeout"=>10
|
137
|
+
},
|
138
|
+
"Account" => {
|
139
|
+
"Account"=> {
|
140
|
+
"id"=>"1",
|
141
|
+
"user_id"=>"1",
|
142
|
+
"state"=>"active",
|
143
|
+
"email"=>"test@example.com",
|
144
|
+
"password"=>"5a2c1429a27a9783609e8b429748aa33",
|
145
|
+
"owner_flag"=>true,
|
146
|
+
"name"=>"テスト",
|
147
|
+
"created"=>"2013-10-16 11:21:35",
|
148
|
+
"modified"=>"2013-10-16 11:21:36",
|
149
|
+
"uuid"=>"0e866700-c572-4d17-aab3-de0f35cf502e",
|
150
|
+
"user_state"=>"active",
|
151
|
+
"pc1_flag"=>false,
|
152
|
+
"account_id"=>"1",
|
153
|
+
"rmc_type"=>"pc",
|
154
|
+
},
|
155
|
+
"User"=> {
|
156
|
+
"id"=>"1",
|
157
|
+
"company_name"=>"テスト",
|
158
|
+
"producer_name"=>"テスト",
|
159
|
+
"consumer_message"=>"",
|
160
|
+
"created"=>"2013-10-16 11:21:35",
|
161
|
+
"modified"=>"2013-10-16 11:21:35",
|
162
|
+
"state"=>"active",
|
163
|
+
"apply_flag"=>false,
|
164
|
+
"paid_flag"=>false,
|
165
|
+
"company_name_kana"=>"",
|
166
|
+
"producer_name_kana"=>"",
|
167
|
+
"payer_name_kana"=>"",
|
168
|
+
"zip_code"=>"",
|
169
|
+
"prefecture"=>"",
|
170
|
+
"address"=>"",
|
171
|
+
"address2"=>"",
|
172
|
+
"phone"=>"",
|
173
|
+
"other"=>"",
|
174
|
+
"uuid"=>"f6120e89-0554-449a-9c79-aa330ba9d6c5",
|
175
|
+
"pc1_flag"=>false}
|
176
|
+
},
|
177
|
+
"Auth"=>{
|
178
|
+
}
|
179
|
+
})
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe PHPSession::Encoder do
|
5
|
+
describe ".encode" do
|
6
|
+
context "when given string value" do
|
7
|
+
it "should return 'KEY|SERIALIZED_STRING'" do
|
8
|
+
expect(
|
9
|
+
PHPSession::Encoder.encode({:hoge => "nyan"})
|
10
|
+
).to eq('hoge|s:4:"nyan";')
|
11
|
+
end
|
12
|
+
end
|
13
|
+
context "when given multi string value" do
|
14
|
+
it "should return 'KEY|SERIALIZED_STRING'" do
|
15
|
+
expect(
|
16
|
+
PHPSession::Encoder.encode({:hoge => "テスト"})
|
17
|
+
).to eq('hoge|s:9:"テスト";')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
context "when given int value" do
|
21
|
+
it "should return 'KEY|SERIALIZED_INT" do
|
22
|
+
expect(
|
23
|
+
PHPSession::Encoder.encode({:hoge => 1})
|
24
|
+
).to eq ('hoge|i:1;')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
context "when given double value" do
|
28
|
+
it "should return 'KEY|DOUBLE|'" do
|
29
|
+
expect(
|
30
|
+
PHPSession::Encoder.encode({:hoge => 1.1})
|
31
|
+
).to eq('hoge|d:1.1;')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
context "when given nil value" do
|
35
|
+
it "should return 'KEY|N;'" do
|
36
|
+
expect(
|
37
|
+
PHPSession::Encoder.encode({:hoge => nil})
|
38
|
+
).to eq('hoge|N;')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
context "when given boolean value" do
|
42
|
+
it "should return 'KEY|b:(0|1);'" do
|
43
|
+
expect(
|
44
|
+
PHPSession::Encoder.encode({:hoge => true})
|
45
|
+
).to eq('hoge|b:1;')
|
46
|
+
expect(
|
47
|
+
PHPSession::Encoder.encode({:hoge => false})
|
48
|
+
).to eq('hoge|b:0;')
|
49
|
+
end
|
50
|
+
end
|
51
|
+
context "when given hash value" do
|
52
|
+
it "should return 'KEY|a:SIZE:ARRAY'" do
|
53
|
+
expect(
|
54
|
+
PHPSession::Encoder.encode({:hoge => {:a => 1,:b => 2,:c => 3}})
|
55
|
+
).to eq('hoge|a:3:{s:1:"a";i:1;s:1:"b";i:2;s:1:"c";i:3;}')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
context "when given array value" do
|
59
|
+
it "should return 'KEY|a:SIZE:ARRAY'" do
|
60
|
+
expect(
|
61
|
+
PHPSession::Encoder.encode({:hoge => [1, 2, 3]})
|
62
|
+
).to eq('hoge|a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
context "when given Struct" do
|
66
|
+
it "should return 'KEY|o:CLASSNAME_SIZE:PROPERTIES_SIZE:{PROPERTIES_AND_VALUES}'" do
|
67
|
+
piyo = Struct.const_defined?(:Test) ? Struct.const_get(:Test) : Struct.new("Test", :p1, :p2)
|
68
|
+
expect(
|
69
|
+
PHPSession::Encoder.encode(:hoge => piyo.new(1, 2))
|
70
|
+
).to eq('hoge|o:4:"Test":2:{s:2:"p1";i:1;s:2:"p2";i:2;}')
|
71
|
+
end
|
72
|
+
end
|
73
|
+
context "when given nested value" do
|
74
|
+
it "should return nested serialized string" do
|
75
|
+
hash = {
|
76
|
+
:key => {
|
77
|
+
:hoge => {
|
78
|
+
:fuga => "nyan"
|
79
|
+
}
|
80
|
+
}
|
81
|
+
}
|
82
|
+
expect(
|
83
|
+
PHPSession::Encoder.encode(hash)
|
84
|
+
).to eq('key|a:1:{s:4:"hoge";a:1:{s:4:"fuga";s:4:"nyan";}}')
|
85
|
+
end
|
86
|
+
end
|
87
|
+
context "when given other types" do
|
88
|
+
it "should raise EncodeError" do
|
89
|
+
expect {
|
90
|
+
PHPSession::Encoder.encode({:key => Date.today})
|
91
|
+
}.to raise_error PHPSession::Errors::EncodeError
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe PHPSession do
|
5
|
+
describe "load" do
|
6
|
+
context "when session file exists" do
|
7
|
+
before do
|
8
|
+
@session_file = create_dummy_session_file('key|s:1:"a";')
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should return session data" do
|
12
|
+
session = PHPSession.new(@session_file[:dir_name], @session_file[:session_id])
|
13
|
+
begin
|
14
|
+
data = session.load
|
15
|
+
expect(data).to eq({"key" => "a"})
|
16
|
+
ensure
|
17
|
+
session.ensure_file_closed
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
after do
|
22
|
+
File.delete(@session_file[:file_path])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "when session file dosen't exist" do
|
27
|
+
it "should return new session data" do
|
28
|
+
session = PHPSession.new(Dir.tmpdir,"session_id")
|
29
|
+
begin
|
30
|
+
data = session.load
|
31
|
+
expect(data).to eq({})
|
32
|
+
ensure
|
33
|
+
session.ensure_file_closed
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#commit" do
|
40
|
+
before do
|
41
|
+
@session_file = create_dummy_session_file('key|s:1:"a";')
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should save session_data in session_file" do
|
45
|
+
session = PHPSession.new(@session_file[:dir_name], @session_file[:session_id])
|
46
|
+
data = session.load
|
47
|
+
data["key"] = "b"
|
48
|
+
session.commit(data)
|
49
|
+
|
50
|
+
expect(IO.read(@session_file[:file_path])).to eq('key|s:1:"b";')
|
51
|
+
end
|
52
|
+
|
53
|
+
after do
|
54
|
+
File.delete(@session_file[:file_path])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "#destroy" do
|
59
|
+
context "when session file exists and loaded" do
|
60
|
+
before do
|
61
|
+
@session_file = create_dummy_session_file('key|s:1:"a";')
|
62
|
+
@session = PHPSession.new(@session_file[:dir_name], @session_file[:session_id])
|
63
|
+
@session.load
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should delete session file" do
|
67
|
+
@session.destroy
|
68
|
+
expect(File.exists?(@session_file[:file_path])).to eq(false)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "when session file exists and not loaded" do
|
73
|
+
before do
|
74
|
+
@session_file = create_dummy_session_file('key|s:1:"a";')
|
75
|
+
@session = PHPSession.new(@session_file[:dir_name], @session_file[:session_id])
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should delete session file" do
|
79
|
+
@session.destroy
|
80
|
+
expect(File.exists?(@session_file[:file_path])).to eq(false)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'php_session'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
def create_dummy_session_file(text)
|
6
|
+
file_path = Tempfile.open(["sess_", ""]) {|f|
|
7
|
+
f.write(text)
|
8
|
+
f.path
|
9
|
+
}
|
10
|
+
dirname = File.dirname(file_path)
|
11
|
+
session_id = File.basename(file_path).gsub(/^sess_/,'')
|
12
|
+
|
13
|
+
{:file_path => file_path, :dir_name => dirname, :session_id => session_id}
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: php_session
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Shinpei Maruyama
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-10-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: php session reader/writer
|
56
|
+
email:
|
57
|
+
- shinpeim@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- .gitignore
|
63
|
+
- .rspec
|
64
|
+
- .travis.yml
|
65
|
+
- Gemfile
|
66
|
+
- LICENSE.txt
|
67
|
+
- README.md
|
68
|
+
- Rakefile
|
69
|
+
- lib/php_session.rb
|
70
|
+
- lib/php_session/decoder.rb
|
71
|
+
- lib/php_session/encoder.rb
|
72
|
+
- lib/php_session/errors.rb
|
73
|
+
- lib/php_session/version.rb
|
74
|
+
- php_session.gemspec
|
75
|
+
- spec/php_session/decoder_spec.rb
|
76
|
+
- spec/php_session/encoder_spec.rb
|
77
|
+
- spec/php_session_spec.rb
|
78
|
+
- spec/spec_helper.rb
|
79
|
+
homepage: https://github.com/Shinpeim/ruby_php_session
|
80
|
+
licenses:
|
81
|
+
- MIT
|
82
|
+
metadata: {}
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - '>='
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
requirements: []
|
98
|
+
rubyforge_project:
|
99
|
+
rubygems_version: 2.0.0
|
100
|
+
signing_key:
|
101
|
+
specification_version: 4
|
102
|
+
summary: php_session is a php session file reader/writer. Multibyte string and exclusiv
|
103
|
+
control is supported
|
104
|
+
test_files:
|
105
|
+
- spec/php_session/decoder_spec.rb
|
106
|
+
- spec/php_session/encoder_spec.rb
|
107
|
+
- spec/php_session_spec.rb
|
108
|
+
- spec/spec_helper.rb
|