gopher2000 0.2.2 → 0.5.5

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.
@@ -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