gopher2000 0.2.2 → 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -81,7 +81,7 @@ module Gopher
81
81
  end
82
82
 
83
83
  # watch the specified script for changes
84
- # @param [String] script to watch
84
+ # @param [String] f script to watch
85
85
  def watch(f)
86
86
  application.scripts << f
87
87
  end
@@ -27,7 +27,7 @@ module Gopher
27
27
 
28
28
  #
29
29
  # strip slashes, extra dots, etc, from an incoming selector and turn it into a 'normalized' path
30
- # @param [String] path
30
+ # @param [String] p path to check
31
31
  # @return clean path string
32
32
  #
33
33
  def sanitize(p)
@@ -65,12 +65,10 @@ module Gopher
65
65
  #
66
66
  # handle a request
67
67
  #
68
- # @param [Hash] the params as parsed during the dispatching process - the main thing here should be :splat, which will basically be the path requested.
69
- # @param [Request] - the Request object for this session -- not currently used?
68
+ # @param [Hash] params the params as parsed during the dispatching process - the main thing here should be :splat, which will basically be the path requested.
69
+ # @param [Request] request the Request object for this session -- not currently used?
70
70
  #
71
71
  def call(params = {}, request = nil)
72
- # debug_log "DirectoryHandler: call #{params.inspect}, #{request.inspect}"
73
-
74
72
  lookup = request_path(params)
75
73
 
76
74
  raise Gopher::InvalidRequest if ! contained?(lookup)
@@ -86,7 +84,7 @@ module Gopher
86
84
 
87
85
  #
88
86
  # generate a directory listing
89
- # @param [String] path to directory
87
+ # @param [String] dir path to directory
90
88
  # @return rendered directory output for a response
91
89
  #
92
90
  def directory(dir)
@@ -98,7 +96,7 @@ module Gopher
98
96
  # iterate through the contents of this directory.
99
97
  # NOTE: we don't filter this, so we will ALWAYS list subdirectories of a mounted folder
100
98
  #
101
- Dir.glob("#{dir}/*.*").each do |x|
99
+ Dir.glob("#{dir}/*").each do |x|
102
100
  # if this is a directory, then generate a directory link for it
103
101
  if File.directory?(x)
104
102
  m.directory File.basename(x), to_selector(x), @application.host, @application.port
@@ -5,10 +5,14 @@ module Gopher
5
5
  #
6
6
  module Rendering
7
7
 
8
+ require 'artii'
9
+
8
10
  # "A CR LF denotes the end of the item." RFC 1436
9
11
  # @see http://www.faqs.org/rfcs/rfc1436.html
10
12
  LINE_ENDING = "\r\n"
11
13
 
14
+ DEFAULT_ENCODING = 'UTF-8'
15
+
12
16
  #
13
17
  # base class for rendering output. this class provides methods
14
18
  # that can be used when rendering both text and gopher menus
@@ -40,7 +44,7 @@ module Gopher
40
44
  # then adds any required spacing
41
45
  #
42
46
  def text(text)
43
- self << text
47
+ self << text.force_encoding(DEFAULT_ENCODING)
44
48
  add_spacing
45
49
  end
46
50
 
@@ -94,6 +98,32 @@ module Gopher
94
98
  self.to_s
95
99
  end
96
100
 
101
+ #
102
+ # output a figlet, which is a big ASCII art header like this:
103
+ # _ _ _ _ _
104
+ # | | | | | | | | |
105
+ # | |__| | ___| | | ___ | |
106
+ # | __ |/ _ \ | |/ _ \| |
107
+ # | | | | __/ | | (_) |_|
108
+ # |_| |_|\___|_|_|\___/(_)
109
+ #
110
+ # This method doesn't do any width checks, so you should be
111
+ # careful with it.
112
+ # You can get a list of fonts from the artii source code or
113
+ # http://www.figlet.org/examples.html
114
+ # https://github.com/miketierney/artii/tree/master/lib/figlet/fonts
115
+
116
+ # @param [String] str the text you want to use for your figlet
117
+ # @param [String] font name of the font. Defaults to 'big'.
118
+ #
119
+ def figlet(str, font = 'big')
120
+ a = Artii::Base.new(:font => font)
121
+ a.asciify(str).split("\n").each do |l|
122
+ text l
123
+ end
124
+ self.to_s
125
+ end
126
+
97
127
  #
98
128
  # output a centered string with a nice underline below it,
99
129
  # centered on the current output width
@@ -119,6 +149,17 @@ module Gopher
119
149
  underline(@width, under)
120
150
  end
121
151
 
152
+ #
153
+ # output a 'small' header, just the text with an underline
154
+ # @param [String] str - the string to output
155
+ # @param [String] under - the desired underline character
156
+ #
157
+ def small_header(str, under = '=')
158
+ str = " " + str + " "
159
+ text(str)
160
+ underline(str.length, under)
161
+ end
162
+
122
163
  #
123
164
  # output a centered string in a box
124
165
  # @param [String] str the string to output
@@ -1,5 +1,7 @@
1
1
  module Gopher
2
2
  module Rendering
3
+ require 'mimemagic'
4
+
3
5
  #
4
6
  # The MenuContext is for rendering gopher menus in the "pseudo
5
7
  # file-system hierarchy" defined by RFC1436
@@ -7,7 +9,6 @@ module Gopher
7
9
  # @see http://www.ietf.org/rfc/rfc1436.txt
8
10
  #
9
11
  class Menu < Base
10
-
11
12
  # default host value when rendering a line with no selector
12
13
  NO_HOST = '(FALSE)'
13
14
 
@@ -15,7 +16,7 @@ module Gopher
15
16
  NO_PORT = 0
16
17
 
17
18
  # Sanitizes text for use in gopher menus
18
- # @param [String] text text to cleanup
19
+ # @param [String] raw text to cleanup
19
20
  # @return string that can be used in a gopher menu
20
21
  def sanitize_text(raw)
21
22
  raw.
@@ -92,11 +93,51 @@ module Gopher
92
93
  # method instead
93
94
  # @param [String] host for link, defaults to current host
94
95
  # @param [String] port for link, defaults to current port
95
- def link(text, selector, host=nil, port=nil)
96
- type = determine_type(selector)
96
+ # @param [String] real filepath of the link
97
+ # @param [String] selector type. if not specified, we will guess
98
+ def link(text, selector, host=nil, port=nil, filepath=nil, type=nil)
99
+ if !type
100
+ if filepath
101
+ type = determine_type(filepath)
102
+ else
103
+ type = determine_type(selector)
104
+ end
105
+ end
97
106
  line type, text, selector, host, port
98
107
  end
99
108
 
109
+ #
110
+ # output a link to text output
111
+ #
112
+ # @param [String] text the text of the link
113
+ # @param [String] selector the path of the link. the extension of this path will be used to
114
+ # detemine the type of link -- image, archive, etc. If you want
115
+ # to specify a specific link-type, you should use the text
116
+ # method instead
117
+ # @param [String] host for link, defaults to current host
118
+ # @param [String] port for link, defaults to current port
119
+ # @param [String] real filepath of the link
120
+ def text_link(text, selector, host=nil, port=nil, filepath=nil)
121
+ link(text, selector, host, port, filepath, '0')
122
+ end
123
+
124
+ # Create an HTTP link entry. This is how this works (via wikipedia)
125
+ #
126
+ # For example, to create a link to http://gopher.quux.org/, the
127
+ # item type is "h", the display string is the title of the link,
128
+ # the item selector is "URL:http://gopher.quux.org/", and the
129
+ # domain and port are that of the originating Gopher server (so
130
+ # that clients that do not support URL links will query the
131
+ # server and receive an HTML redirection page).
132
+ #
133
+ # @param [String] text the text of the link
134
+ # @param [String] URL of the link
135
+ # @param [String] host for link, defaults to current host
136
+ # @param [String] port for link, defaults to current port
137
+ def http(text, url, host=nil, port=nil)
138
+ line "h", text, "URL:#{url}", host, port
139
+ end
140
+
100
141
  #
101
142
  # output a search entry
102
143
  # @param [String] text the text of the link
@@ -109,19 +150,67 @@ module Gopher
109
150
 
110
151
  #
111
152
  # Determines the gopher type for +selector+ based on the
112
- # extension. This is a pretty simple check based on the entities
113
- # list in http://www.ietf.org/rfc/rfc1436.txt
114
- # @param [String] selector, presumably a link to a file name with an extension
153
+ # information stored in the shared mime database.
154
+ # @param [String] filepath The full path to the file (should also exist, if possible)
115
155
  # @return gopher selector type
116
156
  #
117
- def determine_type(selector)
118
- ext = File.extname(selector).downcase
119
- case ext
120
- when '.zip', '.gz', '.bz2' then '5'
121
- when '.gif' then 'g'
122
- when '.jpg', '.png' then 'I'
123
- when '.mp3', '.wav' then 's'
124
- else '0'
157
+ def determine_type(filepath)
158
+ # Determine MIME type by path
159
+ mimetype = MimeMagic.by_path(filepath)
160
+
161
+ # Determine MIME type by contents
162
+ if !mimetype
163
+ begin
164
+ # Open file
165
+ file = File.open(filepath)
166
+
167
+ # Try to detect MIME type using by recognition of typical characters
168
+ mimetype = MimeMagic.by_magic(file)
169
+
170
+ if !mimetype
171
+ file.rewind
172
+
173
+ # Read up to 1k of file data and look for a "\0\0" sequence (typical for binary files)
174
+ if file.read(1000).include?("\0\0")
175
+ mimetype = MimeMagic.new("application/octet-stream")
176
+ else
177
+ mimetype = MimeMagic.new("text/plain")
178
+ end
179
+
180
+ file.close
181
+ end
182
+ rescue SystemCallError,IOError
183
+ nil
184
+ end
185
+ end
186
+
187
+ if !mimetype
188
+ ext = File.extname(filepath).split(".").last
189
+ mimetype = MimeMagic.by_extension(ext)
190
+ end
191
+
192
+ if !mimetype
193
+ return '9' # Binary file
194
+ elsif mimetype.child_of?('application/gzip') || mimetype.child_of?('application/x-bzip') || mimetype.child_of?('application/zip')
195
+ return '5' # archive
196
+ elsif mimetype.child_of?('image/gif')
197
+ return 'g' # GIF image
198
+ elsif mimetype.child_of?('text/x-uuencode')
199
+ return '6' # UUEncode encoded file
200
+ elsif mimetype.child_of?('application/mac-binhex40')
201
+ return '4' # BinHex encoded file
202
+ elsif mimetype.child_of?('text/html') || mimetype.child_of?('application/xhtml+xml')
203
+ return 'h' # HTML file
204
+ elsif mimetype.mediatype == 'text' || mimetype.child_of?('text/plain')
205
+ return '0' # General text file
206
+ elsif mimetype.mediatype == 'image'
207
+ return 'I' # General image file
208
+ elsif mimetype.mediatype == 'audio'
209
+ return 's' # General audio file
210
+ elsif mimetype.mediatype == 'video'
211
+ return 'v' # General video file
212
+ else
213
+ return '9' # Binary file
125
214
  end
126
215
  end
127
216
  end
@@ -7,10 +7,21 @@ module Gopher
7
7
  attr_accessor :selector, :input, :ip_address
8
8
 
9
9
  def initialize(raw, ip_addr=nil)
10
- @selector, @input = raw.chomp.split("\t")
11
- @ip_address = ip_addr
10
+ @raw = raw
11
+ @selector, @input = @raw.chomp.split("\t")
12
+
13
+ @selector = Gopher::Application.sanitize_selector(@selector)
14
+ @ip_address = ip_addr
12
15
  end
13
16
 
17
+ def url?
18
+ @raw =~ /^URL\:/
19
+ end
20
+
21
+ def url
22
+ @raw.chomp.split("\t").first.gsub(/^URL\:/, '')
23
+ end
24
+
14
25
  # confirm that this is actually a valid gopher request
15
26
  # @return [Boolean] true if the request is valid, false otherwise
16
27
  def valid?
@@ -8,12 +8,12 @@ module Gopher
8
8
 
9
9
  #
10
10
  # constructor
11
- # @param [Application] instance of Gopher::Application we want to run
11
+ # @param [Application] a instance of Gopher::Application we want to run
12
12
  #
13
13
  def initialize(a)
14
14
  @app = a
15
15
  end
16
-
16
+
17
17
  #
18
18
  # @return [String] name of the host specified in our config
19
19
  #
@@ -28,6 +28,13 @@ module Gopher
28
28
  @app.config[:port] ||= 70
29
29
  end
30
30
 
31
+ #
32
+ # @return [String] environment specified in config
33
+ #
34
+ def env
35
+ @app.config[:env] || 'development'
36
+ end
37
+
31
38
  #
32
39
  # main app loop. called via at_exit block defined in DSL
33
40
  #
@@ -77,8 +84,8 @@ module Gopher
77
84
  require 'optparse'
78
85
  OptionParser.new { |op|
79
86
  op.on('-p port', 'set the port (default is 70)') { |val| set :port, Integer(val) }
80
- op.on('-o addr', 'set the host (default is 0.0.0.0)') { |val| set :bind, val }
81
- op.on('-e env', 'set the environment (default is development)') { |val| set :environment, val.to_sym }
87
+ op.on('-o addr', 'set the host (default is 0.0.0.0)') { |val| set :host, val }
88
+ op.on('-e env', 'set the environment (default is development)') { |val| set :env, val.to_sym }
82
89
  }.parse!(ARGV.dup)
83
90
  end
84
91
  end
@@ -1,4 +1,4 @@
1
1
  module Gopher
2
2
  # current version of the app
3
- VERSION = "0.2.2"
3
+ VERSION = "0.5.5"
4
4
  end
@@ -9,46 +9,46 @@ describe Gopher::Application do
9
9
  end
10
10
 
11
11
  it 'should have default host/port' do
12
- @app.host.should == "0.0.0.0"
13
- @app.port.should == 70
12
+ expect(@app.host).to eq("0.0.0.0")
13
+ expect(@app.port).to eq(70)
14
14
  end
15
15
 
16
16
  describe "should_reload?" do
17
17
  it "is false if no scripts" do
18
- @app.should_reload?.should == false
18
+ expect(@app.should_reload?).to eq(false)
19
19
  end
20
20
 
21
21
  it "shouldn't do anything if last_reload not set" do
22
- @app.last_reload.should be_nil
22
+ expect(@app.last_reload).to be_nil
23
23
  @app.scripts << "foo.rb"
24
- @app.should_reload?.should == false
24
+ expect(@app.should_reload?).to eq(false)
25
25
  end
26
26
 
27
27
  it "should check script date" do
28
28
  now = Time.now
29
- Time.stub!(:now).and_return(now)
29
+ allow(Time).to receive(:now).and_return(now)
30
30
 
31
31
  @app.last_reload = Time.now - 1000
32
32
  @app.scripts << "foo.rb"
33
- File.should_receive(:mtime).with("foo.rb").and_return(now)
33
+ expect(File).to receive(:mtime).with("foo.rb").and_return(now)
34
34
 
35
- @app.should_reload?.should == true
35
+ expect(@app.should_reload?).to eq(true)
36
36
  end
37
37
  end
38
38
 
39
39
  describe "reload_stale" do
40
40
  it "should load script and update last_reload" do
41
41
  now = Time.now
42
- Time.stub!(:now).and_return(now)
42
+ allow(Time).to receive(:now).and_return(now)
43
43
 
44
- @app.should_receive(:should_reload?).and_return(true)
44
+ expect(@app).to receive(:should_reload?).and_return(true)
45
45
 
46
46
  @app.last_reload = Time.now - 1000
47
47
  @app.scripts << "foo.rb"
48
- @app.should_receive(:load).with("foo.rb")
48
+ expect(@app).to receive(:load).with("foo.rb")
49
49
  @app.reload_stale
50
50
 
51
- @app.last_reload.should == now
51
+ expect(@app.last_reload).to eq(now)
52
52
  end
53
53
  end
54
54
  end
@@ -24,7 +24,7 @@ describe Gopher::Application do
24
24
  @request = Gopher::Request.new("/about")
25
25
 
26
26
  keys, block = @server.lookup(@request.selector)
27
- keys.should == {}
27
+ expect(keys).to eq({})
28
28
  end
29
29
 
30
30
  it "should translate path" do
@@ -32,7 +32,7 @@ describe Gopher::Application do
32
32
  @request = Gopher::Request.new("/about/x/y")
33
33
 
34
34
  keys, block = @server.lookup(@request.selector)
35
- keys.should == {:foo => 'x', :bar => 'y'}
35
+ expect(keys).to eq({:foo => 'x', :bar => 'y'})
36
36
  end
37
37
 
38
38
  it "should return default route if no other route found, and default is defined" do
@@ -41,8 +41,8 @@ describe Gopher::Application do
41
41
  end
42
42
  @request = Gopher::Request.new("/about/x/y")
43
43
  @response = @server.dispatch(@request)
44
- @response.body.should == "DEFAULT ROUTE"
45
- @response.code.should == :success
44
+ expect(@response.body).to eq("DEFAULT ROUTE")
45
+ expect(@response.code).to eq(:success)
46
46
  end
47
47
 
48
48
  it "should respond with error if no route found" do
@@ -50,7 +50,8 @@ describe Gopher::Application do
50
50
  @request = Gopher::Request.new("/junk/x/y")
51
51
 
52
52
  @response = @server.dispatch(@request)
53
- @response.code.should == :missing
53
+ expect(@response.code).to eq(:missing)
54
+ expect(@response.body).to contain_any_error
54
55
  end
55
56
 
56
57
  it "should respond with error if invalid request" do
@@ -58,7 +59,8 @@ describe Gopher::Application do
58
59
  @request = Gopher::Request.new("x" * 256)
59
60
 
60
61
  @response = @server.dispatch(@request)
61
- @response.code.should == :error
62
+ expect(@response.code).to eq(:error)
63
+ expect(@response.body).to contain_any_error
62
64
  end
63
65
 
64
66
  it "should respond with error if there's an exception" do
@@ -66,7 +68,8 @@ describe Gopher::Application do
66
68
  @request = Gopher::Request.new("/x")
67
69
 
68
70
  @response = @server.dispatch(@request)
69
- @response.code.should == :error
71
+ expect(@response.code).to eq(:error)
72
+ expect(@response.body).to contain_any_error
70
73
  end
71
74
  end
72
75
 
@@ -82,7 +85,7 @@ describe Gopher::Application do
82
85
 
83
86
  it "should run the block" do
84
87
  @response = @server.dispatch(@request)
85
- @response.body.should == "GOPHERTRON"
88
+ expect(@response.body).to eq("GOPHERTRON")
86
89
  end
87
90
  end
88
91
 
@@ -97,37 +100,47 @@ describe Gopher::Application do
97
100
 
98
101
  it "should use incoming params" do
99
102
  @response = @server.dispatch(@request)
100
- @response.body.should == "x/a/y/b"
103
+ expect(@response.body).to eq("x/a/y/b")
101
104
  end
102
105
  end
103
106
 
104
107
  describe "dispatch to mount" do
105
108
  before(:each) do
106
- @h = mock(Gopher::Handlers::DirectoryHandler)
107
- @h.should_receive(:application=).with(@server)
108
- Gopher::Handlers::DirectoryHandler.should_receive(:new).with({:bar => :baz, :mount_point => "/foo"}).and_return(@h)
109
+ @h = double(Gopher::Handlers::DirectoryHandler)
110
+ expect(@h).to receive(:application=).with(@server)
111
+ expect(Gopher::Handlers::DirectoryHandler).to receive(:new).with({:bar => :baz, :mount_point => "/foo"}).and_return(@h)
109
112
 
110
113
  @server.mount "/foo", :bar => :baz
111
114
  end
112
115
 
113
116
  it "should work for root path" do
114
117
  @request = Gopher::Request.new("/foo")
115
- @h.should_receive(:call).with({:splat => ""}, @request)
118
+ expect(@h).to receive(:call).with({:splat => ""}, @request)
116
119
 
117
120
  @response = @server.dispatch(@request)
118
- @response.code.should == :success
121
+ expect(@response.code).to eq(:success)
119
122
  end
120
123
 
121
124
  it "should work for subdir" do
122
125
  @request = Gopher::Request.new("/foo/bar")
123
- @h.should_receive(:call).with({:splat => "bar"}, @request)
126
+ expect(@h).to receive(:call).with({:splat => "bar"}, @request)
124
127
 
125
128
  @response = @server.dispatch(@request)
126
- @response.code.should == :success
129
+ expect(@response.code).to eq(:success)
127
130
  end
128
131
  end
129
132
 
133
+ describe 'dispatch for URL requests' do
134
+ let(:target) { 'URL:http://github.com/muffinista/gopher2000' }
135
+ let(:request) { Gopher::Request.new(target) }
136
+ let(:response) { @server.dispatch(request) }
130
137
 
138
+ it "should return web page" do
139
+ expect(response.body).to include('<meta http-equiv="refresh" content="5;URL=http://github.com/muffinista/gopher2000">')
140
+ end
141
+ end
142
+
143
+
131
144
  describe "globs" do
132
145
  before(:each) do
133
146
  @server.route '/about/*' do
@@ -138,7 +151,15 @@ describe Gopher::Application do
138
151
  it "should put wildcard into param[:splat]" do
139
152
  @request = Gopher::Request.new("/about/a/b")
140
153
  @response = @server.dispatch(@request)
141
- @response.body.should == "a/b"
154
+ expect(@response.body).to eq("a/b")
155
+ end
156
+ end
157
+ end
158
+
159
+ RSpec::Matchers.define :contain_any_error do
160
+ match do |actual|
161
+ actual.split(/\r?\n/).any? do |line|
162
+ line.match(/^3.*?\tnull\t\(FALSE\)\t0$/)
142
163
  end
143
164
  end
144
165
  end