lyrics 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.document +5 -0
  2. data/.gitignore +21 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +17 -0
  5. data/Rakefile +45 -0
  6. data/VERSION +1 -0
  7. data/bin/lyrics +66 -0
  8. data/lib/lyrics.rb +4 -0
  9. data/lib/lyrics/cli/application.rb +99 -0
  10. data/lib/lyrics/cli/optionsparser.rb +228 -0
  11. data/lib/lyrics/cli/pluginadapter.rb +56 -0
  12. data/lib/lyrics/cli/plugins.rb +79 -0
  13. data/lib/lyrics/cli/wikipluginadapter.rb +139 -0
  14. data/lib/lyrics/i18n/README +1 -0
  15. data/lib/lyrics/i18n/en.rb +181 -0
  16. data/lib/lyrics/i18n/es.rb +181 -0
  17. data/lib/lyrics/i18n/i18n.rb +126 -0
  18. data/lib/lyrics/i18n/sk.rb +174 -0
  19. data/lib/lyrics/itrans/COPYRIGHT +31 -0
  20. data/lib/lyrics/itrans/itrans +0 -0
  21. data/lib/lyrics/itrans/itrans.txt +8 -0
  22. data/lib/lyrics/itrans/lyric.txt +23 -0
  23. data/lib/lyrics/itrans/udvng.ifm +206 -0
  24. data/lib/lyrics/lyrics.rb +567 -0
  25. data/lib/lyrics/lyrics_AZLyrics.rb +113 -0
  26. data/lib/lyrics/lyrics_DarkLyrics.rb +124 -0
  27. data/lib/lyrics/lyrics_Giitaayan.rb +124 -0
  28. data/lib/lyrics/lyrics_Jamendo.rb +166 -0
  29. data/lib/lyrics/lyrics_LeosLyrics.rb +142 -0
  30. data/lib/lyrics/lyrics_LoudSongs.rb +135 -0
  31. data/lib/lyrics/lyrics_LyricWiki.rb +328 -0
  32. data/lib/lyrics/lyrics_LyricsDownload.rb +118 -0
  33. data/lib/lyrics/lyrics_LyricsMania.rb +141 -0
  34. data/lib/lyrics/lyrics_Lyriki.rb +286 -0
  35. data/lib/lyrics/lyrics_SeekLyrics.rb +108 -0
  36. data/lib/lyrics/lyrics_Sing365.rb +103 -0
  37. data/lib/lyrics/lyrics_TerraLetras.rb +126 -0
  38. data/lib/lyrics/mediawikilyrics.rb +1417 -0
  39. data/lib/lyrics/utils/formdata.rb +56 -0
  40. data/lib/lyrics/utils/htmlentities.rb +291 -0
  41. data/lib/lyrics/utils/http.rb +198 -0
  42. data/lib/lyrics/utils/itrans.rb +160 -0
  43. data/lib/lyrics/utils/logger.rb +123 -0
  44. data/lib/lyrics/utils/strings.rb +378 -0
  45. data/lib/lyrics/utils/xmlhash.rb +111 -0
  46. data/lyrics.gemspec +98 -0
  47. data/spec/lyrics_spec.rb +7 -0
  48. data/spec/spec.opts +1 -0
  49. data/spec/spec_helper.rb +9 -0
  50. metadata +137 -0
@@ -0,0 +1,56 @@
1
+ # Copyright (C) 2006-2008 by Sergio Pistone
2
+ # sergio_pistone@yahoo.com.ar
3
+ #
4
+ # This program is free software; you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation; either version 2 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, write to the
16
+ # Free Software Foundation, Inc.,
17
+ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
+
19
+ require "cgi"
20
+
21
+ module URLEncodedFormData
22
+
23
+ def URLEncodedFormData.prepare_query( params )
24
+ query = params.collect { |name, value| "#{name}=#{CGI.escape( value.to_s() )}" }.join( "&" )
25
+ header = { "Content-type" => "application/x-www-form-urlencoded" }
26
+ return query, header
27
+ end
28
+
29
+ end
30
+
31
+ module MultipartFormData
32
+
33
+ @@boundary = "----------nOtA5FcjrNZuZ3TMioysxHGGCO69vA5iYysdBTL2osuNwOjcCfU7uiN"
34
+
35
+ def MultipartFormData.text_param( name, value )
36
+ return "Content-Disposition: form-data; name=\"#{CGI.escape(name)}\"\r\n" \
37
+ "\r\n" \
38
+ "#{value}\r\n"
39
+ end
40
+
41
+ def MultipartFormData.file_param( name, file, mime_type, content )
42
+ return "Content-Disposition: form-data; name=\"#{CGI.escape(name)}\"; filename=\"#{file}\"\r\n" \
43
+ "Content-Transfer-Encoding: binary\r\n" \
44
+ "Content-Type: #{mime_type}\r\n" \
45
+ "\r\n" \
46
+ "#{content}\r\n"
47
+ end
48
+
49
+ def MultipartFormData.prepare_query( params )
50
+ query = params.collect { |param| "--#{@@boundary}\r\n#{param}" }.join( "" ) + "--#{@@boundary}--"
51
+ header = { "Content-type" => "multipart/form-data; boundary=" + @@boundary }
52
+ return query, header
53
+ end
54
+
55
+ end
56
+
@@ -0,0 +1,291 @@
1
+ # Copyright (C) 2006-2008 by Sergio Pistone
2
+ # sergio_pistone@yahoo.com.ar
3
+ #
4
+ # This program is free software; you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation; either version 2 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, write to the
16
+ # Free Software Foundation, Inc.,
17
+ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
+
19
+ module HTMLEntities
20
+
21
+ def HTMLEntities.decode!( text )
22
+ aux1 = text.gsub!( /&([a-zA-Z]+);/ ) { (n = @@named_entities[$1]) ? [n].pack( "U" ) : $0 }
23
+ aux2 = text.gsub!( /&#(\d{2,4});/ ) { [$1.to_i()].pack( "U" ) }
24
+ aux3 = text.gsub!( /&#x([0-9a-fA-F]{2,4});/ ) { [s.to_i(16)].pack( "U" ) }
25
+ return (aux1||aux2||aux3) ? text : nil
26
+ end
27
+
28
+ def HTMLEntities.decode( text )
29
+ text = String.new( text )
30
+ HTMLEntities.decode!( text )
31
+ return text
32
+ end
33
+
34
+ @@named_entities = {
35
+ "quot" => 34,
36
+ "amp" => 38,
37
+ "lt" => 60,
38
+ "gt" => 62,
39
+ "OElig" => 338,
40
+ "oelig" => 339,
41
+ "Scaron" => 352,
42
+ "scaron" => 353,
43
+ "Yuml" => 376,
44
+ "circ" => 710,
45
+ "tilde" => 732,
46
+ "ensp" => 8194,
47
+ "emsp" => 8195,
48
+ "thinsp" => 8201,
49
+ "zwnj" => 8204,
50
+ "zwj" => 8205,
51
+ "lrm" => 8206,
52
+ "rlm" => 8207,
53
+ "ndash" => 8211,
54
+ "mdash" => 8212,
55
+ "lsquo" => 8216,
56
+ "rsquo" => 8217,
57
+ "sbquo" => 8218,
58
+ "ldquo" => 8220,
59
+ "rdquo" => 8221,
60
+ "bdquo" => 8222,
61
+ "dagger" => 8224,
62
+ "Dagger" => 8225,
63
+ "permil" => 8240,
64
+ "lsaquo" => 8249,
65
+ "rsaquo" => 8250,
66
+ "euro" => 8364,
67
+
68
+ "fnof" => 402,
69
+ "Alpha" => 913,
70
+ "Beta" => 914,
71
+ "Gamma" => 915,
72
+ "Delta" => 916,
73
+ "Epsilon" => 917,
74
+ "Zeta" => 918,
75
+ "Eta" => 919,
76
+ "Theta" => 920,
77
+ "Iota" => 921,
78
+ "Kappa" => 922,
79
+ "Lambda" => 923,
80
+ "Mu" => 924,
81
+ "Nu" => 925,
82
+ "Xi" => 926,
83
+ "Omicron" => 927,
84
+ "Pi" => 928,
85
+ "Rho" => 929,
86
+ "Sigma" => 931,
87
+ "Tau" => 932,
88
+ "Upsilon" => 933,
89
+ "Phi" => 934,
90
+ "Chi" => 935,
91
+ "Psi" => 936,
92
+ "Omega" => 937,
93
+ "alpha" => 945,
94
+ "beta" => 946,
95
+ "gamma" => 947,
96
+ "delta" => 948,
97
+ "epsilon" => 949,
98
+ "zeta" => 950,
99
+ "eta" => 951,
100
+ "theta" => 952,
101
+ "iota" => 953,
102
+ "kappa" => 954,
103
+ "lambda" => 955,
104
+ "mu" => 956,
105
+ "nu" => 957,
106
+ "xi" => 958,
107
+ "omicron" => 959,
108
+ "pi" => 960,
109
+ "rho" => 961,
110
+ "sigmaf" => 962,
111
+ "sigma" => 963,
112
+ "tau" => 964,
113
+ "upsilon" => 965,
114
+ "phi" => 966,
115
+ "chi" => 967,
116
+ "psi" => 968,
117
+ "omega" => 969,
118
+ "thetasym" => 977,
119
+ "upsih" => 978,
120
+ "piv" => 982,
121
+ "bull" => 8226,
122
+ "hellip" => 8230,
123
+ "prime" => 8242,
124
+ "Prime" => 8243,
125
+ "oline" => 8254,
126
+ "frasl" => 8260,
127
+ "weierp" => 8472,
128
+ "image" => 8465,
129
+ "real" => 8476,
130
+ "trade" => 8482,
131
+ "alefsym" => 8501,
132
+ "larr" => 8592,
133
+ "uarr" => 8593,
134
+ "rarr" => 8594,
135
+ "darr" => 8595,
136
+ "harr" => 8596,
137
+ "crarr" => 8629,
138
+ "lArr" => 8656,
139
+ "uArr" => 8657,
140
+ "rArr" => 8658,
141
+ "dArr" => 8659,
142
+ "hArr" => 8660,
143
+ "forall" => 8704,
144
+ "part" => 8706,
145
+ "exist" => 8707,
146
+ "empty" => 8709,
147
+ "nabla" => 8711,
148
+ "isin" => 8712,
149
+ "notin" => 8713,
150
+ "ni" => 8715,
151
+ "prod" => 8719,
152
+ "sum" => 8721,
153
+ "minus" => 8722,
154
+ "lowast" => 8727,
155
+ "radic" => 8730,
156
+ "prop" => 8733,
157
+ "infin" => 8734,
158
+ "ang" => 8736,
159
+ "and" => 8743,
160
+ "or" => 8744,
161
+ "cap" => 8745,
162
+ "cup" => 8746,
163
+ "int" => 8747,
164
+ "there4" => 8756,
165
+ "sim" => 8764,
166
+ "cong" => 8773,
167
+ "asymp" => 8776,
168
+ "ne" => 8800,
169
+ "equiv" => 8801,
170
+ "le" => 8804,
171
+ "ge" => 8805,
172
+ "sub" => 8834,
173
+ "sup" => 8835,
174
+ "nsub" => 8836,
175
+ "sube" => 8838,
176
+ "supe" => 8839,
177
+ "oplus" => 8853,
178
+ "otimes" => 8855,
179
+ "perp" => 8869,
180
+ "sdot" => 8901,
181
+ "lceil" => 8968,
182
+ "rceil" => 8969,
183
+ "lfloor" => 8970,
184
+ "rfloor" => 8971,
185
+ "lang" => 9001,
186
+ "rang" => 9002,
187
+ "loz" => 9674,
188
+ "spades" => 9824,
189
+ "clubs" => 9827,
190
+ "hearts" => 9829,
191
+ "diams" => 9830,
192
+
193
+ "nbsp" => 160,
194
+ "iexcl" => 161,
195
+ "cent" => 162,
196
+ "pound" => 163,
197
+ "curren" => 164,
198
+ "yen" => 165,
199
+ "brvbar" => 166,
200
+ "sect" => 167,
201
+ "uml" => 168,
202
+ "copy" => 169,
203
+ "ordf" => 170,
204
+ "laquo" => 171,
205
+ "not" => 172,
206
+ "shy" => 173,
207
+ "reg" => 174,
208
+ "macr" => 175,
209
+ "deg" => 176,
210
+ "plusmn" => 177,
211
+ "sup2" => 178,
212
+ "sup3" => 179,
213
+ "acute" => 180,
214
+ "micro" => 181,
215
+ "para" => 182,
216
+ "middot" => 183,
217
+ "cedil" => 184,
218
+ "sup1" => 185,
219
+ "ordm" => 186,
220
+ "raquo" => 187,
221
+ "frac14" => 188,
222
+ "frac12" => 189,
223
+ "frac34" => 190,
224
+ "iquest" => 191,
225
+ "Agrave" => 192,
226
+ "Aacute" => 193,
227
+ "Acirc" => 194,
228
+ "Atilde" => 195,
229
+ "Auml" => 196,
230
+ "Aring" => 197,
231
+ "AElig" => 198,
232
+ "Ccedil" => 199,
233
+ "Egrave" => 200,
234
+ "Eacute" => 201,
235
+ "Ecirc" => 202,
236
+ "Euml" => 203,
237
+ "Igrave" => 204,
238
+ "Iacute" => 205,
239
+ "Icirc" => 206,
240
+ "Iuml" => 207,
241
+ "ETH" => 208,
242
+ "Ntilde" => 209,
243
+ "Ograve" => 210,
244
+ "Oacute" => 211,
245
+ "Ocirc" => 212,
246
+ "Otilde" => 213,
247
+ "Ouml" => 214,
248
+ "times" => 215,
249
+ "Oslash" => 216,
250
+ "Ugrave" => 217,
251
+ "Uacute" => 218,
252
+ "Ucirc" => 219,
253
+ "Uuml" => 220,
254
+ "Yacute" => 221,
255
+ "THORN" => 222,
256
+ "szlig" => 223,
257
+ "agrave" => 224,
258
+ "aacute" => 225,
259
+ "acirc" => 226,
260
+ "atilde" => 227,
261
+ "auml" => 228,
262
+ "aring" => 229,
263
+ "aelig" => 230,
264
+ "ccedil" => 231,
265
+ "egrave" => 232,
266
+ "eacute" => 233,
267
+ "ecirc" => 234,
268
+ "euml" => 235,
269
+ "igrave" => 236,
270
+ "iacute" => 237,
271
+ "icirc" => 238,
272
+ "iuml" => 239,
273
+ "eth" => 240,
274
+ "ntilde" => 241,
275
+ "ograve" => 242,
276
+ "oacute" => 243,
277
+ "ocirc" => 244,
278
+ "otilde" => 245,
279
+ "ouml" => 246,
280
+ "divide" => 247,
281
+ "oslash" => 248,
282
+ "ugrave" => 249,
283
+ "uacute" => 250,
284
+ "ucirc" => 251,
285
+ "uuml" => 252,
286
+ "yacute" => 253,
287
+ "thorn" => 254,
288
+ "yuml" => 255,
289
+ }
290
+
291
+ end
@@ -0,0 +1,198 @@
1
+ # Copyright (C) 2006-2008 by Sergio Pistone
2
+ # sergio_pistone@yahoo.com.ar
3
+ #
4
+ # This program is free software; you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation; either version 2 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, write to the
16
+ # Free Software Foundation, Inc.,
17
+ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
+
19
+ require File.expand_path( File.dirname( __FILE__ ) + "/formdata" )
20
+
21
+ require "net/http"
22
+ require "uri"
23
+
24
+ module HTTP
25
+
26
+ # @@user_agent = "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.2) Gecko/20060308 Firefox/1.5.0.2"
27
+ @@user_agent = "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.9) Gecko/20071110 Firefox/2.0.0.9"
28
+ # @@user_agent = "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.8.1.12) Gecko/20080218 Firefox/2.0.0.12"
29
+
30
+ @@proxy_url = nil
31
+ @@proxy_excluded_urls = []
32
+ @@proxy_reverse = false # if true, excluded_urls list becomes a list with the _only_ urls the proxy should be used for
33
+
34
+ def HTTP.normalize_url( url, protocol="http" )
35
+ url = url.strip()
36
+ protocol_regexp = /^ *([^: ]+):\/+/
37
+ md = protocol_regexp.match( url )
38
+ return nil if md && md[1] != protocol
39
+ url.gsub!( /\/+$/, "" ) # remove / at the end of the url
40
+ url.gsub!( protocol_regexp, "" ) # remove the protocol part if there was one
41
+ return "#{protocol}://#{url}" # reinsert protocol part assuring protocol:// form
42
+ end
43
+
44
+ def HTTP.set_proxy_settings( proxy_url, excluded_urls=[], reverse=false )
45
+ @@proxy_url = proxy_url ? HTTP.normalize_url( proxy_url, "http" ) : nil
46
+ @@proxy_reverse = @@proxy_url ? reverse : false
47
+ @@proxy_excluded_urls = []
48
+ if @@proxy_url
49
+ excluded_urls.each() do |url|
50
+ url = normalize_url( url, "http" )
51
+ @@proxy_excluded_urls.insert( -1, url ) if url && ! @@proxy_excluded_urls.include?( url )
52
+ end
53
+ end
54
+ end
55
+
56
+ def HTTP.get_proxy_settings()
57
+ ret = [@@proxy_url ? @@proxy_url.dup : nil, [], @@proxy_reverse]
58
+ @@proxy_excluded_urls.each() { |url| ret[1][ret[1].size] = url.dup }
59
+ return ret
60
+ end
61
+
62
+ # returns proxy_host, proxy_port, proxy_user, proxy_pass for given url
63
+ def HTTP.get_url_proxy_settings( url )
64
+
65
+ return nil, nil, nil, nil if ! @@proxy_url
66
+ proxy = HTTP.parse_uri( @@proxy_url )
67
+ return nil, nil, nil, nil if ! proxy.host
68
+ proxy.port = 80 if ! proxy.port
69
+
70
+ # check if given url should be treated specially
71
+ exception = false
72
+ @@proxy_excluded_urls.each() do |exception_url|
73
+ if url.index( exception_url ) == 0
74
+ exception = true
75
+ break
76
+ end
77
+ end
78
+
79
+ if exception && @@proxy_reverse || ! exception && ! @@proxy_reverse
80
+ return proxy.host, proxy.port, proxy.user, proxy.password
81
+ else
82
+ return nil, nil, nil, nil
83
+ end
84
+ end
85
+
86
+ def HTTP.parse_uri( uri )
87
+ begin
88
+ return URI.parse( uri )
89
+ rescue URI::InvalidURIError
90
+ return URI.parse( URI.escape( uri ) )
91
+ end
92
+ end
93
+
94
+ def HTTP.fetch_page_get( url, headers=nil, follow=10 )
95
+ begin
96
+
97
+ p_url = HTTP.parse_uri( url )
98
+ return nil, url if p_url.host == nil || p_url.port == nil || p_url.request_uri == nil
99
+ proxy_host, proxy_port, proxy_user, proxy_pass = HTTP.get_url_proxy_settings( url )
100
+
101
+ full_headers = {
102
+ "User-Agent" => @@user_agent,
103
+ "Referer" => "#{p_url.scheme}://#{p_url.host}",
104
+ }
105
+ full_headers.merge!( headers ) if headers
106
+
107
+ http = Net::HTTP.new( p_url.host, p_url.port, proxy_host, proxy_port, proxy_user, proxy_pass )
108
+ response = http.request_get( p_url.request_uri, full_headers )
109
+
110
+ case response
111
+ when Net::HTTPSuccess
112
+ when Net::HTTPRedirection, Net::HTTPFound
113
+ if follow == 0
114
+ response = nil
115
+ elsif follow > 0
116
+ response, url = HTTP.fetch_page_get( response["location"], nil, follow-1 )
117
+ end
118
+ else
119
+ response = nil
120
+ end
121
+
122
+ return response, url
123
+
124
+ rescue Errno::ETIMEDOUT, Errno::EBADF, EOFError => e
125
+ raise TimeoutError.new( e.to_s )
126
+ end
127
+ end
128
+
129
+ def HTTP.fetch_page_post( url, params, headers=nil, follow=10 )
130
+ begin
131
+ p_url = HTTP.parse_uri( url )
132
+ return nil, url if p_url.host == nil || p_url.port == nil || p_url.request_uri == nil
133
+ proxy_host, proxy_port, proxy_user, proxy_pass = HTTP.get_url_proxy_settings( url )
134
+
135
+ data, full_headers = URLEncodedFormData.prepare_query( params )
136
+ full_headers["User-Agent"] = @@user_agent
137
+ full_headers["Referer"] = "#{p_url.scheme}://#{p_url.host}"
138
+ full_headers.merge!( headers ) if headers
139
+
140
+ http = Net::HTTP.new( p_url.host, p_url.port, proxy_host, proxy_port, proxy_user, proxy_pass )
141
+ response = http.request_post( p_url.request_uri, data, full_headers )
142
+
143
+ case response
144
+ when Net::HTTPSuccess
145
+ when Net::HTTPRedirection, Net::HTTPFound
146
+ if follow == 0
147
+ response = nil
148
+ elsif follow > 0
149
+ response, url = HTTP.fetch_page_get( response["location"], nil, follow-1 )
150
+ end
151
+ else
152
+ response = nil
153
+ end
154
+
155
+ return response, url
156
+
157
+ rescue Errno::ETIMEDOUT, Errno::EBADF, EOFError => e
158
+ raise TimeoutError.new( e.to_s )
159
+ end
160
+ end
161
+
162
+ def HTTP.fetch_page_post_form_multipart( url, params, headers=nil, follow=10 )
163
+ begin
164
+
165
+ p_url = HTTP.parse_uri( url )
166
+ return nil, url if p_url.host == nil || p_url.port == nil || p_url.request_uri == nil
167
+ proxy_host, proxy_port, proxy_user, proxy_pass = HTTP.get_url_proxy_settings( url )
168
+
169
+ data, full_headers = MultipartFormData.prepare_query( params )
170
+ full_headers["User-Agent"] = @@user_agent
171
+ full_headers["Referer"] = "#{p_url.scheme}://#{p_url.host}"
172
+ full_headers.merge!( headers ) if headers
173
+
174
+ http = Net::HTTP.new( p_url.host, p_url.port, proxy_host, proxy_port, proxy_user, proxy_pass )
175
+ response = http.request_post( p_url.request_uri, data, full_headers )
176
+
177
+ case response
178
+ when Net::HTTPSuccess
179
+ when Net::HTTPRedirection, Net::HTTPFound
180
+ if follow == 0
181
+ response = nil
182
+ elsif follow > 0
183
+ response, url = HTTP.fetch_page_get( response["location"], nil, follow-1 )
184
+ end
185
+ else
186
+ response = nil
187
+ end
188
+
189
+ return response, url
190
+
191
+ rescue Errno::ETIMEDOUT, Errno::EBADF, EOFError => e
192
+ raise TimeoutError.new( e.to_s )
193
+ end
194
+ end
195
+
196
+ end
197
+
198
+