http-cookie 0.1.0

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.
@@ -0,0 +1,5 @@
1
+ module HTTP
2
+ class Cookie
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,233 @@
1
+ require 'http/cookie'
2
+
3
+ ##
4
+ # This class is used to manage the Cookies that have been returned from
5
+ # any particular website.
6
+
7
+ class HTTP::CookieJar
8
+ autoload :AbstractSaver, 'http/cookie_jar/abstract_saver'
9
+ autoload :AbstractStore, 'http/cookie_jar/abstract_store'
10
+
11
+ attr_reader :store
12
+
13
+ def initialize(store = :hash, options = nil)
14
+ case store
15
+ when Symbol
16
+ @store = AbstractStore.implementation(store).new(options)
17
+ when AbstractStore
18
+ options.empty? or
19
+ raise ArgumentError, 'wrong number of arguments (%d for 1)' % (1 + options.size)
20
+ @store = store
21
+ else
22
+ raise TypeError, 'wrong object given as cookie store: %s' % store.inspect
23
+ end
24
+ end
25
+
26
+ def initialize_copy(other)
27
+ @store = other.instance_eval { @store.dup }
28
+ end
29
+
30
+ # Add a +cookie+ to the jar and return self.
31
+ def add(cookie, *_)
32
+ _.empty? or
33
+ raise ArgumentError, 'HTTP::Cookie equivalent for Mechanize::CookieJar#add(uri, cookie) is #add(cookie) after setting cookie.origin = uri.'
34
+
35
+ if cookie.domain.nil? || cookie.path.nil?
36
+ raise ArgumentError, "a cookie with unknown domain or path cannot be added"
37
+ end
38
+
39
+ @store.add(cookie)
40
+ self
41
+ end
42
+ alias << add
43
+
44
+ # Used to exist in Mechanize::CookieJar. Use #add().
45
+ def add!(cookie)
46
+ raise NoMethodError, 'HTTP::Cookie equivalent for Mechanize::CookieJar#add!() is #add().'
47
+ end
48
+
49
+ # Fetch the cookies that should be used for the URL/URI.
50
+ def cookies(url)
51
+ now = Time.now
52
+ each(url).select { |cookie|
53
+ !cookie.expired? && (cookie.accessed_at = now)
54
+ }.sort
55
+ end
56
+
57
+ # Tests if the jar is empty. If url is given, tests if there is no
58
+ # cookie for the URL.
59
+ def empty?(url = nil)
60
+ if url
61
+ each(url) { return false }
62
+ return true
63
+ else
64
+ @store.empty?
65
+ end
66
+ end
67
+
68
+ # Iterates over all cookies that are not expired.
69
+ #
70
+ # Available option keywords are below:
71
+ #
72
+ # * +uri+
73
+ #
74
+ # Specify a URI/URL indicating the destination of the cookies
75
+ # being selected. Every cookie yielded should be good to send to
76
+ # the given URI, i.e. cookie.valid_for_uri?(uri) evaluates to
77
+ # true.
78
+ #
79
+ # If (and only if) this option is given, last access time of each
80
+ # cookie is updated to the current time.
81
+ def each(uri = nil, &block)
82
+ block_given? or return enum_for(__method__, uri)
83
+
84
+ if uri
85
+ block = proc { |cookie|
86
+ yield cookie if cookie.valid_for_uri?(uri)
87
+ }
88
+ end
89
+
90
+ @store.each(uri, &block)
91
+ self
92
+ end
93
+ include Enumerable
94
+
95
+ # call-seq:
96
+ # jar.save(filename_or_io, **options)
97
+ # jar.save(filename_or_io, format = :yaml, **options)
98
+ #
99
+ # Save the cookie jar into a file or an IO in the format specified
100
+ # and return self. If the given object responds to #write it is
101
+ # taken as an IO, or taken as a filename otherwise.
102
+ #
103
+ # Available option keywords are below:
104
+ #
105
+ # * +format+
106
+ # [<tt>:yaml</tt>]
107
+ # YAML structure (default)
108
+ # [<tt>:cookiestxt</tt>]
109
+ # Mozilla's cookies.txt format
110
+ # * +session+
111
+ # [+true+]
112
+ # Save session cookies as well.
113
+ # [+false+]
114
+ # Do not save session cookies. (default)
115
+ #
116
+ # All options given are passed through to the underlying cookie
117
+ # saver module.
118
+ def save(writable, *options)
119
+ opthash = {
120
+ :format => :yaml,
121
+ :session => false,
122
+ }
123
+ case options.size
124
+ when 0
125
+ when 1
126
+ case options = options.first
127
+ when Symbol
128
+ opthash[:format] = options
129
+ else
130
+ opthash.update(options) if options
131
+ end
132
+ when 2
133
+ opthash[:format], options = options
134
+ opthash.update(options) if options
135
+ else
136
+ raise ArgumentError, 'wrong number of arguments (%d for 1-3)' % (1 + options.size)
137
+ end
138
+
139
+ begin
140
+ saver = AbstractSaver.implementation(opthash[:format]).new(opthash)
141
+ rescue KeyError => e
142
+ raise ArgumentError, e.message
143
+ end
144
+
145
+ if writable.respond_to?(:write)
146
+ saver.save(writable, self)
147
+ else
148
+ File.open(writable, 'w') { |io|
149
+ saver.save(io, self)
150
+ }
151
+ end
152
+
153
+ self
154
+ end
155
+
156
+ # Used to exist in Mechanize::CookieJar. Use #save().
157
+ def save_as(*args)
158
+ raise NoMethodError, 'HTTP::Cookie equivalent for Mechanize::CookieJar#save_as() is #save().'
159
+ end
160
+
161
+ # call-seq:
162
+ # jar.load(filename_or_io, **options)
163
+ # jar.load(filename_or_io, format = :yaml, **options)
164
+ #
165
+ # Load cookies recorded in a file or an IO in the format specified
166
+ # into the jar and return self. If the given object responds to
167
+ # #read it is taken as an IO, or taken as a filename otherwise.
168
+ #
169
+ # Available option keywords are below:
170
+ #
171
+ # * +format+
172
+ # [<tt>:yaml</tt>]
173
+ # YAML structure (default)
174
+ # [<tt>:cookiestxt</tt>]
175
+ # Mozilla's cookies.txt format
176
+ #
177
+ # All options given are passed through to the underlying cookie
178
+ # saver module.
179
+ def load(readable, *options)
180
+ opthash = {
181
+ :format => :yaml,
182
+ :session => false,
183
+ }
184
+ case options.size
185
+ when 0
186
+ when 1
187
+ case options = options.first
188
+ when Symbol
189
+ opthash[:format] = options
190
+ else
191
+ opthash.update(options) if options
192
+ end
193
+ when 2
194
+ opthash[:format], options = options
195
+ opthash.update(options) if options
196
+ else
197
+ raise ArgumentError, 'wrong number of arguments (%d for 1-3)' % (1 + options.size)
198
+ end
199
+
200
+ begin
201
+ saver = AbstractSaver.implementation(opthash[:format]).new(opthash)
202
+ rescue KeyError => e
203
+ raise ArgumentError, e.message
204
+ end
205
+
206
+ if readable.respond_to?(:write)
207
+ saver.load(readable, self)
208
+ else
209
+ File.open(readable, 'r') { |io|
210
+ saver.load(io, self)
211
+ }
212
+ end
213
+
214
+ self
215
+ end
216
+
217
+ # Clear the cookie jar and return self.
218
+ def clear
219
+ @store.clear
220
+ self
221
+ end
222
+
223
+ # Used to exist in Mechanize::CookieJar. Use #clear().
224
+ def clear!(*args)
225
+ raise NoMethodError, 'HTTP::Cookie equivalent for Mechanize::CookieJar#clear!() is #clear().'
226
+ end
227
+
228
+ # Remove expired cookies and return self.
229
+ def cleanup(session = false)
230
+ @store.cleanup session
231
+ self
232
+ end
233
+ end
@@ -0,0 +1,53 @@
1
+ require 'http/cookie_jar'
2
+
3
+ class HTTP::CookieJar::AbstractSaver
4
+ class << self
5
+ @@class_map = {}
6
+
7
+ # Gets an implementation class by the name, optionally trying to
8
+ # load "http/cookie_jar/*_saver" if not found. If loading fails,
9
+ # KeyError is raised.
10
+ def implementation(symbol)
11
+ @@class_map.fetch(symbol)
12
+ rescue KeyError
13
+ begin
14
+ require 'http/cookie_jar/%s_saver' % symbol
15
+ @@class_map.fetch(symbol)
16
+ rescue LoadError, KeyError => e
17
+ raise KeyError, 'cookie saver unavailable: %s' % symbol.inspect
18
+ end
19
+ end
20
+
21
+ def inherited(subclass)
22
+ @@class_map[class_to_symbol(subclass)] = subclass
23
+ end
24
+
25
+ def class_to_symbol(klass)
26
+ klass.name[/[^:]+?(?=Saver$|$)/].downcase.to_sym
27
+ end
28
+ end
29
+
30
+ def default_options
31
+ {}
32
+ end
33
+ private :default_options
34
+
35
+ def initialize(options = nil)
36
+ options ||= {}
37
+ @logger = options[:logger]
38
+ @session = options[:session]
39
+ # Initializes each instance variable of the same name as option
40
+ # keyword.
41
+ default_options.each_pair { |key, default|
42
+ instance_variable_set("@#{key}", options.key?(key) ? options[key] : default)
43
+ }
44
+ end
45
+
46
+ def save(io, jar)
47
+ raise
48
+ end
49
+
50
+ def load(io, jar)
51
+ raise
52
+ end
53
+ end
@@ -0,0 +1,90 @@
1
+ require 'http/cookie_jar'
2
+
3
+ class HTTP::CookieJar::AbstractStore
4
+ class << self
5
+ @@class_map = {}
6
+
7
+ # Gets an implementation class by the name, optionally trying to
8
+ # load "http/cookie_jar/*_store" if not found. If loading fails,
9
+ # KeyError is raised.
10
+ def implementation(symbol)
11
+ @@class_map.fetch(symbol)
12
+ rescue KeyError
13
+ begin
14
+ require 'http/cookie_jar/%s_store' % symbol
15
+ @@class_map.fetch(symbol)
16
+ rescue LoadError, KeyError => e
17
+ raise KeyError, 'cookie store unavailable: %s' % symbol.inspect
18
+ end
19
+ end
20
+
21
+ def inherited(subclass)
22
+ @@class_map[class_to_symbol(subclass)] = subclass
23
+ end
24
+
25
+ def class_to_symbol(klass)
26
+ klass.name[/[^:]+?(?=Store$|$)/].downcase.to_sym
27
+ end
28
+ end
29
+
30
+ def default_options
31
+ {}
32
+ end
33
+ private :default_options
34
+
35
+ def initialize(options = nil)
36
+ options ||= {}
37
+ @logger = options[:logger]
38
+ # Initializes each instance variable of the same name as option
39
+ # keyword.
40
+ default_options.each_pair { |key, default|
41
+ instance_variable_set("@#{key}", options.key?(key) ? options[key] : default)
42
+ }
43
+ end
44
+
45
+ def initialize_copy(other)
46
+ raise
47
+ self
48
+ end
49
+
50
+ def add(cookie)
51
+ raise
52
+ self
53
+ end
54
+
55
+ # Iterates over all cookies that are not expired.
56
+ #
57
+ # Available option keywords are below:
58
+ #
59
+ # * +uri+
60
+ #
61
+ # Specify a URI object indicating the destination of the cookies
62
+ # being selected. Every cookie yielded should be good to send to
63
+ # the given URI, i.e. cookie.valid_for_uri?(uri) evaluates to
64
+ # true.
65
+ #
66
+ # If (and only if) this option is given, last access time of each
67
+ # cookie is updated to the current time.
68
+ def each(options = nil, &block)
69
+ raise
70
+ self
71
+ end
72
+ include Enumerable
73
+
74
+ def clear
75
+ raise
76
+ self
77
+ end
78
+
79
+ def cleanup(session = false)
80
+ if session
81
+ select { |cookie| cookie.session? || cookie.expired? }
82
+ else
83
+ select(&:expired?)
84
+ end.each { |cookie|
85
+ add(cookie.expire)
86
+ }
87
+ # subclasses can optionally remove over-the-limit cookies.
88
+ self
89
+ end
90
+ end
@@ -0,0 +1,74 @@
1
+ require 'http/cookie_jar'
2
+
3
+ # CookiestxtSaver saves and loads cookies in the cookies.txt format.
4
+ class HTTP::CookieJar::CookiestxtSaver < HTTP::CookieJar::AbstractSaver
5
+ True = "TRUE"
6
+ False = "FALSE"
7
+
8
+ def save(io, jar)
9
+ io.puts @header if @header
10
+ jar.each { |cookie|
11
+ next if !@session && cookie.session?
12
+ io.print cookie_to_record(cookie)
13
+ }
14
+ end
15
+
16
+ def load(io, jar)
17
+ io.each_line { |line|
18
+ cookie = parse_record(line) and jar.add(cookie)
19
+ }
20
+ end
21
+
22
+ private
23
+
24
+ def default_options
25
+ {
26
+ header: "# HTTP Cookie File",
27
+ linefeed: "\n",
28
+ }
29
+ end
30
+
31
+ # Serializes the cookie into a cookies.txt line.
32
+ def cookie_to_record(cookie)
33
+ cookie.instance_eval {
34
+ [
35
+ @domain,
36
+ @for_domain ? True : False,
37
+ @path,
38
+ @secure ? True : False,
39
+ @expires.to_i,
40
+ @name,
41
+ @value
42
+ ]
43
+ }.join("\t") << @linefeed
44
+ end
45
+
46
+ # Parses a line from cookies.txt and returns a cookie object if the
47
+ # line represents a cookie record or returns nil otherwise.
48
+ def parse_record(line)
49
+ return nil if line.match(/^#/)
50
+
51
+ domain,
52
+ s_for_domain, # Whether this cookie is for domain
53
+ path, # Path for which the cookie is relevant
54
+ s_secure, # Requires a secure connection
55
+ s_expires, # Time the cookie expires (Unix epoch time)
56
+ name, value = line.split("\t", 7)
57
+ return nil if value.nil?
58
+
59
+ value.chomp!
60
+
61
+ if (expires_seconds = s_expires.to_i).nonzero?
62
+ expires = Time.at(expires_seconds)
63
+ return nil if expires < Time.now
64
+ end
65
+
66
+ HTTP::Cookie.new(name, value,
67
+ :domain => domain,
68
+ :for_domain => s_for_domain == True,
69
+ :path => path,
70
+ :secure => s_secure == True,
71
+ :expires => expires,
72
+ :version => 0)
73
+ end
74
+ end