gopher2000 0.5.3 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -25,11 +25,10 @@ Features
25
25
  * built on Event Machine.
26
26
  * Easy to mount directories and serve up files.
27
27
  * built in logging and stats.
28
- * Runs on Ruby 1.9.2 with all the modern conveniences.
29
28
 
30
29
  Requirements
31
30
  ------------
32
- * Ruby 1.9.2 or greater
31
+ * Ruby 2 or greater
33
32
  * Nerves of steel
34
33
 
35
34
  Examples
@@ -59,7 +58,7 @@ menu :index do
59
58
  br(2)
60
59
 
61
60
  # link somewhere
62
- link 'current time', '/time'
61
+ text_link 'current time', '/time'
63
62
  br
64
63
  end
65
64
 
@@ -108,6 +107,34 @@ Command line options will override defaults specified in your script
108
107
  -- so you can try out things on a different port/address if needed.
109
108
 
110
109
 
110
+ Docker
111
+ ------
112
+
113
+ There's a pretty simple docker script which you can use to run an
114
+ app. To run one of the included examples, you could do something like:
115
+
116
+
117
+ ```
118
+ docker build -t gopher2000 .
119
+ docker run -p 7070:7070 --rm -it gopher2000 examples/simple.rb
120
+ ```
121
+
122
+ This will run the `simple` example on port 7070. You can view it
123
+ locally by running something like:
124
+
125
+ ```
126
+ lynx gopher://0.0.0.0:7070
127
+ ```
128
+
129
+ The Dockerfile is also published to Docker Hub, so you could run
130
+ something like this:
131
+
132
+ ```
133
+ docker run -p 7070:7070 --rm -v $PWD:/opt muffinista/gopher2000 /opt/gopher-script.rb
134
+ ```
135
+
136
+
137
+
111
138
  Developing Gopher Sites
112
139
  -----------------------
113
140
 
data/bin/gopher2000 CHANGED
@@ -31,7 +31,7 @@ params = {
31
31
 
32
32
  opts.on('-d', '--debug', "run in debug mode") { params[:debug] = true }
33
33
  opts.on('-p port', 'set the port (default is 70)') { |val| params[:port] = Integer(val) }
34
- opts.on('-o addr', 'set the host (default is 0.0.0.0)') { |val| params[host] = val }
34
+ opts.on('-o addr', 'set the host (default is 0.0.0.0)') { |val| params[:host] = val }
35
35
  opts.on('-e env', 'set the environment (default is development)') { |val| params[:env] = val.to_sym }
36
36
 
37
37
  opts.on_tail("-h", "--help", "Show this message") do
data/examples/simple.rb CHANGED
@@ -5,6 +5,8 @@
5
5
  # Simple gopher example
6
6
  #
7
7
 
8
+ require "rubygems"
9
+ require "bundler/setup"
8
10
  require 'gopher2000'
9
11
 
10
12
  set :host, '0.0.0.0'
@@ -57,11 +59,11 @@ menu :index do
57
59
  br(2)
58
60
 
59
61
  # link somewhere
60
- link 'current time', '/time'
62
+ text_link 'current time', '/time'
61
63
  br
62
64
 
63
65
  # another link
64
- link 'about', '/about'
66
+ text_link 'about', '/about'
65
67
  br
66
68
 
67
69
  # ask for some input
@@ -54,6 +54,13 @@ module Gopher
54
54
  config[:port] ||= 70
55
55
  end
56
56
 
57
+ #
58
+ # return the application environment
59
+ #
60
+ def env
61
+ config[:env] ||= 'development'
62
+ end
63
+
57
64
  #
58
65
  # are we in debugging mode?
59
66
  #
@@ -162,12 +169,11 @@ module Gopher
162
169
  #
163
170
  def lookup(selector)
164
171
  unless routes.nil?
165
- routes.each do |pattern, keys, block|
166
-
172
+ routes.each do |pattern, keys, block|
167
173
  if match = pattern.match(selector)
168
174
  match = match.to_a
169
175
  url = match.shift
170
-
176
+
171
177
  params = to_params_hash(keys, match)
172
178
 
173
179
  #
@@ -201,6 +207,9 @@ module Gopher
201
207
  if ! @request.valid?
202
208
  response.body = handle_invalid_request
203
209
  response.code = :error
210
+ elsif @request.url?
211
+ response.body = handle_url(@request)
212
+ response.code = :success
204
213
  else
205
214
  begin
206
215
  debug_log("do lookup for #{@request.selector}")
@@ -347,6 +356,15 @@ module Gopher
347
356
  menus.include?(:error) ? :error : :'internal/error'
348
357
  end
349
358
 
359
+
360
+ #
361
+ # get the id of the template that will be used when rendering an html page
362
+ # @return name of error template
363
+ #
364
+ def url_template
365
+ menus.include?(:html) ? :html : :'internal/url'
366
+ end
367
+
350
368
  #
351
369
  # get the id of the template that will be used when rendering an
352
370
  # invalid request
@@ -431,20 +449,18 @@ module Gopher
431
449
 
432
450
  class << self
433
451
 
434
- #
435
- # Sanitizes a gopher selector
436
- #
437
- def sanitize_selector(raw)
438
- "/#{raw}".dup.
439
- strip. # Strip whitespace
440
- sub(/\/$/, ''). # Strip last rslash
441
- sub(/^\/*/, '/'). # Strip extra lslashes
442
- gsub(/\.+/, '.') # Don't want consecutive dots!
443
-
444
- #raw = "/#{raw}" if ! raw.match(/^\//)
445
- end
446
-
447
- #
452
+ #
453
+ # Sanitizes a gopher selector
454
+ #
455
+ def sanitize_selector(raw)
456
+ "/#{raw}".dup.
457
+ strip. # Strip whitespace
458
+ sub(/\/$/, ''). # Strip last rslash
459
+ sub(/^\/*/, '/'). # Strip extra lslashes
460
+ gsub(/\.+/, '.') # Don't want consecutive dots!
461
+ end
462
+
463
+ #
448
464
  # generate a method which we will use to run routes. this is
449
465
  # based on #generate_method as used by sinatra.
450
466
  # @see https://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb
@@ -457,7 +473,7 @@ module Gopher
457
473
  method
458
474
  end
459
475
  end
460
-
476
+
461
477
  #
462
478
  # output a debugging message
463
479
  #
@@ -474,15 +490,37 @@ module Gopher
474
490
  #
475
491
  def register_defaults
476
492
  menu :'internal/not_found' do
477
- text "Sorry, #{@request.selector} was not found"
493
+ error "Sorry, #{@request.selector} was not found"
478
494
  end
479
495
 
480
496
  menu :'internal/error' do |details|
481
- text "Sorry, there was an error #{details}"
497
+ error "Sorry, there was an error #{details}"
482
498
  end
483
499
 
484
500
  menu :'internal/invalid_request' do
485
- text "invalid request"
501
+ error "invalid request"
502
+ end
503
+
504
+ menu :'internal/url' do
505
+ output = <<-EOHTML
506
+ <html>
507
+ <head>
508
+ <meta http-equiv="refresh" content="5;URL=#{@request.url}">
509
+ </head>
510
+ <body>
511
+ <p>
512
+ You are following a link from gopher to a website. If your browser supports it, you will be
513
+ automatically taken to the web site shortly. If you do not get
514
+ sent there, please click <a href="#{@request.url}">here</a>.
515
+ </p>
516
+ <p>
517
+ The URL linked is: <a href="#{@request.url}">#{@request.url}</a>.
518
+ </p>
519
+ <p>Have a nice day!</p>
520
+ </body>
521
+ </html>
522
+ EOHTML
523
+ output
486
524
  end
487
525
  end
488
526
 
@@ -490,6 +528,10 @@ module Gopher
490
528
  render not_found_template
491
529
  end
492
530
 
531
+ def handle_url(request)
532
+ render url_template, request
533
+ end
534
+
493
535
  def handle_error(e)
494
536
  render error_template, e
495
537
  end
@@ -27,15 +27,21 @@ module Gopher
27
27
  #
28
28
  def receive_data data
29
29
  (@buf ||= '') << data
30
-
30
+ first_line = true
31
+
32
+ ip_address = remote_ip
31
33
  while line = @buf.slice!(/(.*)\r?\n/)
32
- receive_line(line)
34
+ is_proxy = first_line && line.match?(/^PROXY TCP[4,6] /)
35
+ receive_line(line, ip_address) unless is_proxy
36
+ ip_address = line.split(/ /)[2] if is_proxy
37
+
38
+ first_line = false
33
39
  end
34
40
  end
35
41
 
36
42
  # Invoked with lines received over the network
37
- def receive_line(line)
38
- call! Request.new(line, remote_ip)
43
+ def receive_line(line, ip_address)
44
+ call! Request.new(line, ip_address)
39
45
  end
40
46
 
41
47
  #
@@ -35,7 +35,7 @@ module Gopher
35
35
  # @param [String] string text to add to the output
36
36
  #
37
37
  def <<(string)
38
- @result << string.to_s
38
+ @result << clean_line(string.to_s)
39
39
  end
40
40
 
41
41
  #
@@ -89,7 +89,9 @@ module Gopher
89
89
  # this is a hack - recombine lines, then re-split on newlines
90
90
  # doing this because word_wrap is returning an array of lines, but
91
91
  # those lines have newlines in them where we should wrap
92
- lines = word_wrap(text, width).join("\n").split("\n")
92
+ #
93
+ lines = word_wrap(text, width)
94
+ .join("\n").split("\n")
93
95
 
94
96
  lines.each do |line|
95
97
  text line.lstrip.rstrip
@@ -206,6 +208,19 @@ module Gopher
206
208
  end
207
209
  end
208
210
 
211
+ #
212
+ # handle lines of a single period not at the end of the
213
+ # transmission
214
+ #
215
+ # RFC 1436 states: Note: Lines beginning with
216
+ # periods must be prepended with an extra period to
217
+ # ensure that the transmission is not terminated
218
+ # early. The client should strip extra periods at
219
+ # the beginning of the line.
220
+ def clean_line(line)
221
+ line.match?(/^\./) ? ['.', line].join('') : line
222
+ end
223
+
209
224
  private
210
225
  def add_spacing
211
226
  br(@spacing)
@@ -7,12 +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")
10
+ @raw = raw
11
+ @selector, @input = @raw.chomp.split("\t")
12
+
13
+ @selector = Gopher::Application.sanitize_selector(@selector)
14
+ @ip_address = ip_addr
15
+ end
11
16
 
12
- @selector = Gopher::Application.sanitize_selector(@selector)
13
- @ip_address = ip_addr
17
+ def url?
18
+ @raw =~ /^URL\:/
14
19
  end
15
20
 
21
+ def url
22
+ @raw.chomp.split("\t").first.gsub(/^URL\:/, '')
23
+ end
24
+
16
25
  # confirm that this is actually a valid gopher request
17
26
  # @return [Boolean] true if the request is valid, false otherwise
18
27
  def valid?
@@ -13,7 +13,7 @@ module Gopher
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.5.3"
3
+ VERSION = "0.7.0"
4
4
  end
@@ -51,6 +51,7 @@ describe Gopher::Application do
51
51
 
52
52
  @response = @server.dispatch(@request)
53
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
@@ -59,6 +60,7 @@ describe Gopher::Application do
59
60
 
60
61
  @response = @server.dispatch(@request)
61
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
@@ -67,6 +69,7 @@ describe Gopher::Application do
67
69
 
68
70
  @response = @server.dispatch(@request)
69
71
  expect(@response.code).to eq(:error)
72
+ expect(@response.body).to contain_any_error
70
73
  end
71
74
  end
72
75
 
@@ -127,7 +130,17 @@ describe Gopher::Application do
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
@@ -142,3 +155,11 @@ describe Gopher::Application do
142
155
  end
143
156
  end
144
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$/)
163
+ end
164
+ end
165
+ end
data/spec/dsl_spec.rb CHANGED
@@ -14,103 +14,112 @@ describe Gopher::DSL do
14
14
 
15
15
  @server = FakeServer.new(@app)
16
16
  @server.send :require, 'gopher2000/dsl'
17
- allow(@server).to receive(:application).and_return(@app)
18
- @app.reset!
19
17
  end
20
18
 
21
-
22
- describe "set" do
23
- it "should set a config var" do
24
- @server.set :foo, 'bar'
25
- expect(@app.config[:foo]).to eq('bar')
19
+ describe "application" do
20
+ it "should return a Gopher::Application" do
21
+ expect(@server.application).to be_a(Gopher::Application)
26
22
  end
27
23
  end
28
24
 
29
- describe "route" do
30
- it "should pass a lookup and block to the app" do
31
- expect(@app).to receive(:route).with('/foo')
32
- @server.route '/foo' do
33
- "hi"
25
+ context "with application" do
26
+ before do
27
+ allow(@server).to receive(:application).and_return(@app)
28
+ @app.reset!
29
+ end
30
+
31
+ describe "set" do
32
+ it "should set a config var" do
33
+ @server.set :foo, 'bar'
34
+ expect(@app.config[:foo]).to eq('bar')
34
35
  end
35
36
  end
36
- end
37
37
 
38
- describe "default_route" do
39
- it "should pass a default block to the app" do
40
- expect(@app).to receive(:default_route)
41
- @server.default_route do
42
- "hi"
38
+ describe "route" do
39
+ it "should pass a lookup and block to the app" do
40
+ expect(@app).to receive(:route).with('/foo')
41
+ @server.route '/foo' do
42
+ "hi"
43
+ end
43
44
  end
44
45
  end
45
- end
46
46
 
47
- describe "mount" do
48
- it "should pass a route, path, and some opts to the app" do
49
- expect(@app).to receive(:mount).with('/foo', {:path => "/bar"})
50
- @server.mount "/foo" => "/bar"
47
+ describe "default_route" do
48
+ it "should pass a default block to the app" do
49
+ expect(@app).to receive(:default_route)
50
+ @server.default_route do
51
+ "hi"
52
+ end
53
+ end
51
54
  end
52
55
 
53
- it "should pass a route, path, filter, and some opts to the app" do
54
- expect(@app).to receive(:mount).with('/foo', {:path => "/bar", :filter => "*.jpg"})
55
- @server.mount "/foo" => "/bar", :filter => "*.jpg"
56
+ describe "mount" do
57
+ it "should pass a route, path, and some opts to the app" do
58
+ expect(@app).to receive(:mount).with('/foo', {:path => "/bar"})
59
+ @server.mount "/foo" => "/bar"
60
+ end
61
+
62
+ it "should pass a route, path, filter, and some opts to the app" do
63
+ expect(@app).to receive(:mount).with('/foo', {:path => "/bar", :filter => "*.jpg"})
64
+ @server.mount "/foo" => "/bar", :filter => "*.jpg"
65
+ end
56
66
  end
57
- end
58
67
 
59
- describe "menu" do
60
- it "should pass a menu key and block to the app" do
61
- expect(@app).to receive(:menu).with('/foo')
62
- @server.menu '/foo' do
63
- "hi"
68
+ describe "menu" do
69
+ it "should pass a menu key and block to the app" do
70
+ expect(@app).to receive(:menu).with('/foo')
71
+ @server.menu '/foo' do
72
+ "hi"
73
+ end
64
74
  end
65
75
  end
66
- end
67
76
 
68
- describe "text" do
69
- it "should pass a text_template key and block to the app" do
70
- expect(@app).to receive(:text).with('/foo')
71
- @server.text '/foo' do
72
- "hi"
77
+ describe "text" do
78
+ it "should pass a text_template key and block to the app" do
79
+ expect(@app).to receive(:text).with('/foo')
80
+ @server.text '/foo' do
81
+ "hi"
82
+ end
73
83
  end
74
84
  end
75
- end
76
85
 
77
- describe "helpers" do
78
- it "should pass a block to the app" do
79
- expect(@app).to receive(:helpers)
80
- @server.helpers do
81
- "hi"
86
+ describe "helpers" do
87
+ it "should pass a block to the app" do
88
+ expect(@app).to receive(:helpers)
89
+ @server.helpers do
90
+ "hi"
91
+ end
82
92
  end
83
93
  end
84
- end
85
94
 
86
- describe "watch" do
87
- it "should pass a script app for watching" do
88
- expect(@app.scripts).to receive(:<<).with("foo")
89
- @server.watch("foo")
95
+ describe "watch" do
96
+ it "should pass a script app for watching" do
97
+ expect(@app.scripts).to receive(:<<).with("foo")
98
+ @server.watch("foo")
99
+ end
90
100
  end
91
- end
92
101
 
93
- describe "run" do
94
- it "should set any incoming opts" do
95
- expect(@server).to receive(:set).with(:x, 1)
96
- expect(@server).to receive(:set).with(:y, 2)
97
- allow(@server).to receive(:load)
102
+ describe "run" do
103
+ it "should set any incoming opts" do
104
+ expect(@server).to receive(:set).with(:x, 1)
105
+ expect(@server).to receive(:set).with(:y, 2)
106
+ allow(@server).to receive(:load)
98
107
 
99
- @server.run("foo", {:x => 1, :y => 2})
100
- end
108
+ @server.run("foo", {:x => 1, :y => 2})
109
+ end
101
110
 
102
- it "should turn on script watching if in debug mode" do
103
- @app.config[:debug] = true
104
- expect(@server).to receive(:watch).with("foo.rb")
105
- expect(@server).to receive(:load).with("foo.rb")
111
+ it "should turn on script watching if in debug mode" do
112
+ @app.config[:debug] = true
113
+ expect(@server).to receive(:watch).with("foo.rb")
114
+ expect(@server).to receive(:load).with("foo.rb")
106
115
 
107
- @server.run("foo.rb")
108
- end
116
+ @server.run("foo.rb")
117
+ end
109
118
 
110
- it "should load the script" do
111
- expect(@server).to receive(:load).with("foo.rb")
112
- @server.run("foo.rb")
119
+ it "should load the script" do
120
+ expect(@server).to receive(:load).with("foo.rb")
121
+ @server.run("foo.rb")
122
+ end
113
123
  end
114
124
  end
115
-
116
125
  end
@@ -10,7 +10,7 @@ describe Gopher::Rendering::Base do
10
10
  @ctx.text("line 2")
11
11
  expect(@ctx.result).to eq("line 1\r\nline 2\r\n")
12
12
  end
13
-
13
+
14
14
  it "should add breaks correctly" do
15
15
  @ctx.spacing 2
16
16
  @ctx.text("line 1")
@@ -18,6 +18,13 @@ describe Gopher::Rendering::Base do
18
18
  expect(@ctx.result).to eq("line 1\r\n\r\nline 2\r\n\r\n")
19
19
  end
20
20
 
21
+ it "should add extra period to lines beginning with period" do
22
+ @ctx.text("line 1")
23
+ @ctx.text(".")
24
+ @ctx.text("line 2")
25
+ expect(@ctx.result).to eq("line 1\r\n..\r\nline 2\r\n")
26
+ end
27
+
21
28
  it "br outputs a bunch of newlines" do
22
29
  expect(@ctx.br(2)).to eq("\r\n\r\n")
23
30
  end
@@ -60,7 +67,12 @@ describe Gopher::Rendering::Base do
60
67
  describe "block" do
61
68
  it "wraps text" do
62
69
  expect(@ctx).to receive(:text).twice.with "a"
63
- @ctx.block("a a",1)
70
+ @ctx.block("a a", 1)
71
+ end
72
+
73
+ it "handles stray period" do
74
+ @ctx.block("a a .", 1)
75
+ expect(@ctx.result).to eq("a\r\na\r\n..\r\n")
64
76
  end
65
77
  end
66
78
  end
@@ -54,9 +54,25 @@ describe Gopher::Rendering::Menu do
54
54
  end
55
55
 
56
56
  describe "link" do
57
- it "should get type with determine_type" do
58
- expect(@ctx).to receive(:determine_type).with("foo.txt").and_return("A")
59
- expect(@ctx.link("FILE", "foo.txt")).to eq("AFILE\tfoo.txt\thost\t1234\r\n")
57
+ context "with filepath" do
58
+ it "should get type with determine_type" do
59
+ expect(@ctx).to receive(:determine_type).with("foo.txt").and_return("A")
60
+ expect(@ctx.link("FILE", "thing", "external-server.com", 70, "foo.txt")).to eq("AFILE\tthing\texternal-server.com\t70\r\n")
61
+ end
62
+ end
63
+
64
+ context "without filepath" do
65
+ it "should get type with determine_type" do
66
+ expect(@ctx).to receive(:determine_type).with("foo.txt").and_return("A")
67
+ expect(@ctx.link("FILE", "foo.txt")).to eq("AFILE\tfoo.txt\thost\t1234\r\n")
68
+ end
69
+ end
70
+
71
+ end
72
+
73
+ describe "text_link" do
74
+ it "should work" do
75
+ expect(@ctx.text_link("text link", "/files/foo.txt")).to eq("0text link\t/files/foo.txt\thost\t1234\r\n")
60
76
  end
61
77
  end
62
78
 
data/spec/request_spec.rb CHANGED
@@ -32,4 +32,10 @@ describe Gopher::Request do
32
32
  request = Gopher::Request.new("x" * 255, "bar")
33
33
  expect(request.valid?).to eq(false)
34
34
  end
35
+
36
+ it 'detects urls' do
37
+ request = Gopher::Request.new("URL:http://github.com/muffinista/gopher2000")
38
+ expect(request).to be_url
39
+ expect(request.url).to eql('http://github.com/muffinista/gopher2000')
40
+ end
35
41
  end