otto 1.2.0 → 1.3.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.
@@ -8,8 +8,8 @@ class App
8
8
  attr_reader :req, :res
9
9
 
10
10
  def initialize(req, res)
11
- @req = req
12
- @res = res
11
+ @req = req
12
+ @res = res
13
13
  res.headers['content-type'] = 'text/html; charset=utf-8'
14
14
  end
15
15
 
@@ -21,15 +21,15 @@ class App
21
21
  <p>Minimal Ruby web framework with style</p>
22
22
  </div>
23
23
 
24
- #{otto_card("Send Feedback") do
24
+ #{otto_card('Send Feedback') do
25
25
  otto_form_wrapper do
26
- otto_input("msg", placeholder: "Your message...") +
27
- otto_button("Send Feedback")
26
+ otto_input('msg', placeholder: 'Your message...') +
27
+ otto_button('Send Feedback')
28
28
  end
29
29
  end}
30
30
  HTML
31
31
 
32
- res.send_cookie :sess, 1_234_567, 3600
32
+ res.send_secure_cookie :sess, 1_234_567, 3600
33
33
  res.body = otto_page(content)
34
34
  end
35
35
 
@@ -37,14 +37,14 @@ class App
37
37
  message = req.params['msg']&.strip
38
38
 
39
39
  content = if message.nil? || message.empty?
40
- otto_alert("error", "Empty Message", "Please enter a message before submitting.")
40
+ otto_alert('error', 'Empty Message', 'Please enter a message before submitting.')
41
41
  else
42
- otto_alert("success", "Feedback Received", "Thanks for your message!") +
43
- otto_card("Your Message") { otto_code_block(message, 'text') }
42
+ otto_alert('success', 'Feedback Received', 'Thanks for your message!') +
43
+ otto_card('Your Message') { otto_code_block(message, 'text') }
44
44
  end
45
45
 
46
- content += "<p>#{otto_link("← Back", "/")}</p>"
47
- res.body = otto_page(content, "Feedback")
46
+ content += "<p>#{otto_link('← Back', '/')}</p>"
47
+ res.body = otto_page(content, 'Feedback')
48
48
  end
49
49
 
50
50
  def redirect
@@ -53,26 +53,26 @@ class App
53
53
 
54
54
  def robots_text
55
55
  res.headers['content-type'] = 'text/plain'
56
- res.body = ['User-agent: *', 'Disallow: /private'].join($/)
56
+ res.body = ['User-agent: *', 'Disallow: /private'].join($/)
57
57
  end
58
58
 
59
59
  def display_product
60
60
  res.headers['content-type'] = 'application/json; charset=utf-8'
61
- prodid = req.params[:prodid]
62
- res.body = format('{"product":%s,"msg":"Hint: try another value"}', prodid)
61
+ prodid = req.params[:prodid]
62
+ res.body = format('{"product":%s,"msg":"Hint: try another value"}', prodid)
63
63
  end
64
64
 
65
65
  def not_found
66
66
  res.status = 404
67
- content = otto_alert("error", "Not Found", "The requested page could not be found.")
68
- content += "<p>#{otto_link("← Home", "/")}</p>"
69
- res.body = otto_page(content, "404")
67
+ content = otto_alert('error', 'Not Found', 'The requested page could not be found.')
68
+ content += "<p>#{otto_link('← Home', '/')}</p>"
69
+ res.body = otto_page(content, '404')
70
70
  end
71
71
 
72
72
  def server_error
73
73
  res.status = 500
74
- content = otto_alert("error", "Server Error", "An internal server error occurred.")
75
- content += "<p>#{otto_link("← Home", "/")}</p>"
76
- res.body = otto_page(content, "500")
74
+ content = otto_alert('error', 'Server Error', 'An internal server error occurred.')
75
+ content += "<p>#{otto_link('← Home', '/')}</p>"
76
+ res.body = otto_page(content, '500')
77
77
  end
78
78
  end
@@ -11,7 +11,7 @@ public_path = File.expand_path('../../public', __dir__)
11
11
  require_relative '../../lib/otto'
12
12
  require_relative 'app'
13
13
 
14
- app = Otto.new("routes")
14
+ app = Otto.new('routes')
15
15
 
16
16
  # DEV: Run web apps with extra logging and reloading
17
17
  if Otto.env?(:dev)
@@ -8,8 +8,8 @@ class App
8
8
  attr_reader :req, :res
9
9
 
10
10
  def initialize(req, res)
11
- @req = req
12
- @res = res
11
+ @req = req
12
+ @res = res
13
13
  res.headers['content-type'] = 'text/html; charset=utf-8'
14
14
  end
15
15
 
@@ -23,19 +23,19 @@ class App
23
23
  <p>Minimal Ruby web framework with style</p>
24
24
  </div>
25
25
 
26
- #{otto_card("Dynamic Pages") do
26
+ #{otto_card('Dynamic Pages') do
27
27
  # This is a nested heredoc within the outer HTML heredoc
28
28
  # It uses a different delimiter (EXAMPLES) to avoid conflicts
29
29
  # The #{} interpolation allows the inner heredoc to be embedded
30
30
  <<~EXAMPLES
31
- #{otto_link("Product #100", "/product/100")} - View product page<br>
32
- #{otto_link("Product #42", "/product/42")} - Different product<br>
33
- #{otto_link("API Data", "/product/100.json")} - JSON endpoint
31
+ #{otto_link('Product #100', '/product/100')} - View product page<br>
32
+ #{otto_link('Product #42', '/product/42')} - Different product<br>
33
+ #{otto_link('API Data', '/product/100.json')} - JSON endpoint
34
34
  EXAMPLES
35
35
  end}
36
36
  HTML
37
37
 
38
- res.send_cookie :sess, 1_234_567, 3600
38
+ res.send_secure_cookie :sess, 1_234_567, 3600
39
39
  res.body = otto_page(content)
40
40
  end
41
41
 
@@ -43,14 +43,14 @@ class App
43
43
  message = req.params['msg']&.strip
44
44
 
45
45
  content = if message.nil? || message.empty?
46
- otto_alert("error", "Empty Message", "Please enter a message before submitting.")
46
+ otto_alert('error', 'Empty Message', 'Please enter a message before submitting.')
47
47
  else
48
- otto_alert("success", "Feedback Received", "Thanks for your message!") +
49
- otto_card("Your Message") { otto_code_block(message, 'text') }
48
+ otto_alert('success', 'Feedback Received', 'Thanks for your message!') +
49
+ otto_card('Your Message') { otto_code_block(message, 'text') }
50
50
  end
51
51
 
52
- content += "<p>#{otto_link("← Back", "/")}</p>"
53
- res.body = otto_page(content, "Feedback")
52
+ content += "<p>#{otto_link('← Back', '/')}</p>"
53
+ res.body = otto_page(content, 'Feedback')
54
54
  end
55
55
 
56
56
  def redirect
@@ -59,7 +59,7 @@ class App
59
59
 
60
60
  def robots_text
61
61
  res.headers['content-type'] = 'text/plain'
62
- res.body = ['User-agent: *', 'Disallow: /private'].join($/)
62
+ res.body = ['User-agent: *', 'Disallow: /private'].join($/)
63
63
  end
64
64
 
65
65
  def display_product
@@ -71,27 +71,27 @@ class App
71
71
 
72
72
  if wants_json
73
73
  res.headers['content-type'] = 'application/json; charset=utf-8'
74
- res.body = format('{"product":%s,"msg":"Hint: try another value"}', prodid)
74
+ res.body = format('{"product":%s,"msg":"Hint: try another value"}', prodid)
75
75
  else
76
76
  # Return HTML product page
77
77
  product_data = {
78
- "Product ID" => prodid,
79
- "Name" => "Sample Product ##{prodid}",
80
- "Price" => "$#{rand(10..999)}.99",
81
- "Description" => "This is a demonstration product showing dynamic routing with parameter :prodid",
82
- "Stock" => rand(0..50) > 5 ? "In Stock" : "Out of Stock"
78
+ 'Product ID' => prodid,
79
+ 'Name' => "Sample Product ##{prodid}",
80
+ 'Price' => "$#{rand(10..999)}.99",
81
+ 'Description' => 'This is a demonstration product showing dynamic routing with parameter :prodid',
82
+ 'Stock' => rand(0..50) > 5 ? 'In Stock' : 'Out of Stock',
83
83
  }
84
84
 
85
- product_html = product_data.map { |key, value|
85
+ product_html = product_data.map do |key, value|
86
86
  "<p><strong>#{key}:</strong> #{escape_html(value.to_s)}</p>"
87
- }.join
87
+ end.join
88
88
 
89
89
  content = <<~HTML
90
- #{otto_card("Product Details") { product_html }}
90
+ #{otto_card('Product Details') { product_html }}
91
91
 
92
92
  <p>
93
- #{otto_link("← Back to Home", "/")} |
94
- #{otto_link("View as JSON", "/product/#{prodid}.json")}
93
+ #{otto_link('← Back to Home', '/')} |
94
+ #{otto_link('View as JSON', "/product/#{prodid}.json")}
95
95
  </p>
96
96
  HTML
97
97
 
@@ -101,15 +101,15 @@ class App
101
101
 
102
102
  def not_found
103
103
  res.status = 404
104
- content = otto_alert("error", "Not Found", "The requested page could not be found.")
105
- content += "<p>#{otto_link("← Home", "/")}</p>"
106
- res.body = otto_page(content, "404")
104
+ content = otto_alert('error', 'Not Found', 'The requested page could not be found.')
105
+ content += "<p>#{otto_link('← Home', '/')}</p>"
106
+ res.body = otto_page(content, '404')
107
107
  end
108
108
 
109
109
  def server_error
110
110
  res.status = 500
111
- content = otto_alert("error", "Server Error", "An internal server error occurred.")
112
- content += "<p>#{otto_link("← Home", "/")}</p>"
113
- res.body = otto_page(content, "500")
111
+ content = otto_alert('error', 'Server Error', 'An internal server error occurred.')
112
+ content += "<p>#{otto_link('← Home', '/')}</p>"
113
+ res.body = otto_page(content, '500')
114
114
  end
115
115
  end
@@ -11,7 +11,7 @@ public_path = File.expand_path('../../public', __dir__)
11
11
  require_relative '../../lib/otto'
12
12
  require_relative 'app'
13
13
 
14
- app = Otto.new("routes")
14
+ app = Otto.new('routes')
15
15
 
16
16
  # DEV: Run web apps with extra logging and reloading
17
17
  if Otto.env?(:dev)
@@ -9,8 +9,8 @@ class SecureApp
9
9
  attr_reader :req, :res
10
10
 
11
11
  def initialize(req, res)
12
- @req = req
13
- @res = res
12
+ @req = req
13
+ @res = res
14
14
  res.headers['content-type'] = 'text/html; charset=utf-8'
15
15
  end
16
16
 
@@ -28,49 +28,47 @@ class SecureApp
28
28
  <p class="otto-mb-md">Security demonstration for the Otto framework</p>
29
29
  </div>
30
30
 
31
- #{otto_card("CSRF Protected Feedback") do
32
-
31
+ #{otto_card('CSRF Protected Feedback') do
33
32
  <<~FORM
34
33
  <form method="post" action="/feedback" class="otto-form">
35
34
  #{csrf_tag}
36
35
  <label>Message:</label>
37
- #{otto_textarea("message", placeholder: "Enter your feedback...", required: true)}
38
- #{otto_button("Submit Feedback", variant: "primary")}
36
+ #{otto_textarea('message', placeholder: 'Enter your feedback...', required: true)}
37
+ #{otto_button('Submit Feedback', variant: 'primary')}
39
38
  </form>
40
39
  FORM
41
-
42
40
  end}
43
41
 
44
- #{otto_card("File Upload Validation") do
42
+ #{otto_card('File Upload Validation') do
45
43
  <<~UPLOAD
46
44
  <form method="post" action="/upload" enctype="multipart/form-data" class="otto-form">
47
45
  #{csrf_tag}
48
46
  <label>Choose file:</label>
49
47
  <input type="file" name="upload_file" class="otto-input">
50
- #{otto_button("Upload File", variant: "primary")}
48
+ #{otto_button('Upload File', variant: 'primary')}
51
49
  </form>
52
50
  UPLOAD
53
51
  end}
54
52
 
55
- #{otto_card("User Profile Input Validation") do
53
+ #{otto_card('User Profile Input Validation') do
56
54
  <<~PROFILE
57
55
  <form method="post" action="/profile" class="otto-form">
58
56
  #{csrf_tag}
59
57
  <label>Name:</label>
60
- #{otto_input("name", placeholder: "Your name", required: true)}
58
+ #{otto_input('name', placeholder: 'Your name', required: true)}
61
59
 
62
60
  <label>Email:</label>
63
- #{otto_input("email", type: "email", placeholder: "your@email.com", required: true)}
61
+ #{otto_input('email', type: 'email', placeholder: 'your@email.com', required: true)}
64
62
 
65
63
  <label>Bio:</label>
66
- #{otto_textarea("bio", placeholder: "Tell us about yourself...")}
64
+ #{otto_textarea('bio', placeholder: 'Tell us about yourself...')}
67
65
 
68
- #{otto_button("Update Profile", variant: "primary")}
66
+ #{otto_button('Update Profile', variant: 'primary')}
69
67
  </form>
70
68
  PROFILE
71
69
  end}
72
70
 
73
- #{otto_card("Security Information") do
71
+ #{otto_card('Security Information') do
74
72
  <<~INFO
75
73
  <h3>Security Features Active:</h3>
76
74
  <ul>
@@ -87,13 +85,13 @@ class SecureApp
87
85
  </p>
88
86
 
89
87
  <p class="otto-mt-md">
90
- #{otto_link("View Request Headers", "/headers")}
88
+ #{otto_link('View Request Headers', '/headers')}
91
89
  </p>
92
90
  INFO
93
91
  end}
94
92
  HTML
95
93
 
96
- res.body = otto_page(content, "Otto Security Features")
94
+ res.body = otto_page(content, 'Otto Security Features')
97
95
  end
98
96
 
99
97
  def receive_feedback
@@ -104,29 +102,28 @@ class SecureApp
104
102
  safe_message = validate_input(message, max_length: 1000, allow_html: false)
105
103
  else
106
104
  safe_message = message.to_s.strip
107
- raise "Message too long" if safe_message.length > 1000
105
+ raise 'Message too long' if safe_message.length > 1000
108
106
  end
109
107
 
110
- if safe_message.empty?
111
- content = otto_alert("error", "Validation Error", "Message cannot be empty.")
108
+ content = if safe_message.empty?
109
+ otto_alert('error', 'Validation Error', 'Message cannot be empty.')
112
110
  else
113
- content = <<~HTML
114
- #{otto_alert("success", "Feedback Received", "Thank you for your feedback!")}
111
+ <<~HTML
112
+ #{otto_alert('success', 'Feedback Received', 'Thank you for your feedback!')}
115
113
 
116
- #{otto_card("Your Message") do
114
+ #{otto_card('Your Message') do
117
115
  otto_code_block(safe_message, 'text')
118
116
  end}
119
117
  HTML
120
- end
121
-
122
- rescue Otto::Security::ValidationError => e
123
- content = otto_alert("error", "Security Validation Failed", e.message)
124
- rescue => e
125
- content = otto_alert("error", "Processing Error", "An error occurred processing your request.")
118
+ end
119
+ rescue Otto::Security::ValidationError => ex
120
+ content = otto_alert('error', 'Security Validation Failed', ex.message)
121
+ rescue StandardError
122
+ content = otto_alert('error', 'Processing Error', 'An error occurred processing your request.')
126
123
  end
127
124
 
128
- content += "<p class=\"otto-mt-lg\">#{otto_link("← Back to form", "/")}</p>"
129
- res.body = otto_page(content, "Feedback Response")
125
+ content += "<p class=\"otto-mt-lg\">#{otto_link('← Back to form', '/')}</p>"
126
+ res.body = otto_page(content, 'Feedback Response')
130
127
  end
131
128
 
132
129
  def upload_file
@@ -134,31 +131,39 @@ class SecureApp
134
131
  uploaded_file = req.params['upload_file']
135
132
 
136
133
  if uploaded_file.nil? || uploaded_file.empty?
137
- content = otto_alert("error", "Upload Error", "No file was selected.")
134
+ content = otto_alert('error', 'Upload Error', 'No file was selected.')
138
135
  else
139
- filename = uploaded_file[:filename] rescue uploaded_file.original_filename rescue 'unknown'
136
+ begin
137
+ filename = begin
138
+ uploaded_file[:filename]
139
+ rescue StandardError
140
+ uploaded_file.original_filename
141
+ end
142
+ rescue StandardError
143
+ 'unknown'
144
+ end
140
145
 
141
- if respond_to?(:sanitize_filename)
142
- safe_filename = sanitize_filename(filename)
146
+ safe_filename = if respond_to?(:sanitize_filename)
147
+ sanitize_filename(filename)
143
148
  else
144
- safe_filename = File.basename(filename.to_s).gsub(/[^\w\-_\.]/, '_')
145
- end
149
+ File.basename(filename.to_s).gsub(/[^\w\-_\.]/, '_')
150
+ end
146
151
 
147
152
  file_info = {
148
- "Original filename" => filename,
149
- "Sanitized filename" => safe_filename,
150
- "Content type" => uploaded_file[:type] || 'unknown',
151
- "Security status" => "File validated and processed safely"
153
+ 'Original filename' => filename,
154
+ 'Sanitized filename' => safe_filename,
155
+ 'Content type' => uploaded_file[:type] || 'unknown',
156
+ 'Security status' => 'File validated and processed safely',
152
157
  }
153
158
 
154
- info_html = file_info.map { |key, value|
159
+ info_html = file_info.map do |key, value|
155
160
  "<p><strong>#{key}:</strong> #{escape_html(value)}</p>"
156
- }.join
161
+ end.join
157
162
 
158
163
  content = <<~HTML
159
- #{otto_alert("success", "File Upload Successful", "File processed and validated successfully!")}
164
+ #{otto_alert('success', 'File Upload Successful', 'File processed and validated successfully!')}
160
165
 
161
- #{otto_card("File Information") do
166
+ #{otto_card('File Information') do
162
167
  info_html
163
168
  end}
164
169
 
@@ -168,64 +173,62 @@ class SecureApp
168
173
  </div>
169
174
  HTML
170
175
  end
171
-
172
- rescue Otto::Security::ValidationError => e
173
- content = otto_alert("error", "File Validation Failed", e.message)
174
- rescue => e
175
- content = otto_alert("error", "Upload Error", "An error occurred during file upload.")
176
+ rescue Otto::Security::ValidationError => ex
177
+ content = otto_alert('error', 'File Validation Failed', ex.message)
178
+ rescue StandardError
179
+ content = otto_alert('error', 'Upload Error', 'An error occurred during file upload.')
176
180
  end
177
181
 
178
- content += "<p class=\"otto-mt-lg\">#{otto_link("← Back to form", "/")}</p>"
179
- res.body = otto_page(content, "Upload Response")
182
+ content += "<p class=\"otto-mt-lg\">#{otto_link('← Back to form', '/')}</p>"
183
+ res.body = otto_page(content, 'Upload Response')
180
184
  end
181
185
 
182
186
  def update_profile
183
187
  begin
184
- name = req.params['name']
188
+ name = req.params['name']
185
189
  email = req.params['email']
186
- bio = req.params['bio']
190
+ bio = req.params['bio']
187
191
 
188
192
  if respond_to?(:validate_input)
189
- safe_name = validate_input(name, max_length: 100)
193
+ safe_name = validate_input(name, max_length: 100)
190
194
  safe_email = validate_input(email, max_length: 255)
191
- safe_bio = validate_input(bio, max_length: 500, allow_html: false)
195
+ safe_bio = validate_input(bio, max_length: 500, allow_html: false)
192
196
  else
193
- safe_name = name.to_s.strip[0..99]
197
+ safe_name = name.to_s.strip[0..99]
194
198
  safe_email = email.to_s.strip[0..254]
195
- safe_bio = bio.to_s.strip[0..499]
199
+ safe_bio = bio.to_s.strip[0..499]
196
200
  end
197
201
 
198
202
  unless safe_email.match?(/\A[^@\s]+@[^@\s]+\z/)
199
- raise Otto::Security::ValidationError, "Invalid email format"
203
+ raise Otto::Security::ValidationError, 'Invalid email format'
200
204
  end
201
205
 
202
206
  profile_data = {
203
- "Name" => safe_name,
204
- "Email" => safe_email,
205
- "Bio" => safe_bio,
206
- "Updated" => Time.now.strftime("%Y-%m-%d %H:%M:%S UTC")
207
+ 'Name' => safe_name,
208
+ 'Email' => safe_email,
209
+ 'Bio' => safe_bio,
210
+ 'Updated' => Time.now.strftime('%Y-%m-%d %H:%M:%S UTC'),
207
211
  }
208
212
 
209
- profile_html = profile_data.map { |key, value|
213
+ profile_html = profile_data.map do |key, value|
210
214
  "<p><strong>#{key}:</strong> #{escape_html(value)}</p>"
211
- }.join
215
+ end.join
212
216
 
213
217
  content = <<~HTML
214
- #{otto_alert("success", "Profile Updated", "Your profile has been updated successfully!")}
218
+ #{otto_alert('success', 'Profile Updated', 'Your profile has been updated successfully!')}
215
219
 
216
- #{otto_card("Profile Data") do
220
+ #{otto_card('Profile Data') do
217
221
  profile_html
218
222
  end}
219
223
  HTML
220
-
221
- rescue Otto::Security::ValidationError => e
222
- content = otto_alert("error", "Profile Validation Failed", e.message)
223
- rescue => e
224
- content = otto_alert("error", "Update Error", "An error occurred updating your profile.")
224
+ rescue Otto::Security::ValidationError => ex
225
+ content = otto_alert('error', 'Profile Validation Failed', ex.message)
226
+ rescue StandardError
227
+ content = otto_alert('error', 'Update Error', 'An error occurred updating your profile.')
225
228
  end
226
229
 
227
- content += "<p class=\"otto-mt-lg\">#{otto_link("← Back to form", "/")}</p>"
228
- res.body = otto_page(content, "Profile Update")
230
+ content += "<p class=\"otto-mt-lg\">#{otto_link('← Back to form', '/')}</p>"
231
+ res.body = otto_page(content, 'Profile Update')
229
232
  end
230
233
 
231
234
  def show_headers
@@ -239,16 +242,16 @@ class SecureApp
239
242
  end
240
243
 
241
244
  response_data = {
242
- message: "Request headers analysis (filtered for security)",
245
+ message: 'Request headers analysis (filtered for security)',
243
246
  client_ip: req.respond_to?(:client_ipaddress) ? req.client_ipaddress : req.ip,
244
247
  secure_connection: req.respond_to?(:secure?) ? req.secure? : false,
245
248
  timestamp: Time.now.utc.iso8601,
246
249
  headers: safe_headers,
247
250
  security_analysis: {
248
- csrf_protection: respond_to?(:csrf_token_valid?) ? "Active" : "Basic",
249
- content_security: "Headers validated and filtered",
250
- xss_protection: "HTML escaping enabled"
251
- }
251
+ csrf_protection: respond_to?(:csrf_token_valid?) ? 'Active' : 'Basic',
252
+ content_security: 'Headers validated and filtered',
253
+ xss_protection: 'HTML escaping enabled',
254
+ },
252
255
  }
253
256
 
254
257
  require 'json'
@@ -257,17 +260,17 @@ class SecureApp
257
260
 
258
261
  def not_found
259
262
  res.status = 404
260
- content = otto_alert("error", "Page Not Found", "The requested page could not be found.")
261
- content += "<p>#{otto_link("← Back to home", "/")}</p>"
262
- res.body = otto_page(content, "404 - Not Found")
263
+ content = otto_alert('error', 'Page Not Found', 'The requested page could not be found.')
264
+ content += "<p>#{otto_link('← Back to home', '/')}</p>"
265
+ res.body = otto_page(content, '404 - Not Found')
263
266
  end
264
267
 
265
268
  def server_error
266
269
  res.status = 500
267
- error_id = req.env['otto.error_id'] || SecureRandom.hex(8)
268
- content = otto_alert("error", "Server Error", "An internal server error occurred.")
269
- content += "<p><small>Error ID: #{escape_html(error_id)}</small></p>"
270
- content += "<p>#{otto_link("← Back to home", "/")}</p>"
271
- res.body = otto_page(content, "500 - Server Error")
270
+ error_id = req.env['otto.error_id'] || SecureRandom.hex(8)
271
+ content = otto_alert('error', 'Server Error', 'An internal server error occurred.')
272
+ content += "<p><small>Error ID: #{escape_html(error_id)}</small></p>"
273
+ content += "<p>#{otto_link('← Back to home', '/')}</p>"
274
+ res.body = otto_page(content, '500 - Server Error')
272
275
  end
273
276
  end
@@ -13,7 +13,7 @@ require_relative '../../lib/otto'
13
13
  require_relative 'app'
14
14
 
15
15
  # Create Otto app with security features enabled
16
- app = Otto.new("./routes", {
16
+ app = Otto.new('./routes', {
17
17
  # Enable CSRF protection for POST, PUT, DELETE requests
18
18
  csrf_protection: true,
19
19
 
@@ -42,14 +42,15 @@ app = Otto.new("./routes", {
42
42
  security_headers: {
43
43
  'content-security-policy' => "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'",
44
44
  'strict-transport-security' => 'max-age=31536000; includeSubDomains',
45
- 'x-frame-options' => 'DENY'
46
- }
47
- })
45
+ 'x-frame-options' => 'DENY',
46
+ },
47
+ }
48
+ )
48
49
 
49
50
  # Optional: Configure additional security settings
50
51
  app.security_config.max_request_size = 5 * 1024 * 1024 # 5MB limit
51
- app.security_config.max_param_depth = 10 # Limit parameter nesting
52
- app.security_config.max_param_keys = 50 # Limit parameters per request
52
+ app.security_config.max_param_depth = 10 # Limit parameter nesting
53
+ app.security_config.max_param_keys = 50 # Limit parameters per request
53
54
 
54
55
  # Optional: Add static file serving with security
55
56
  app.option[:public] = public_path
@@ -62,20 +63,21 @@ if ENV['RACK_ENV'] == 'production'
62
63
  # More restrictive CSP for production
63
64
  app.set_security_headers({
64
65
  'content-security-policy' => "default-src 'self'; style-src 'self'; script-src 'self'; object-src 'none'",
65
- 'strict-transport-security' => 'max-age=63072000; includeSubDomains; preload'
66
- })
66
+ 'strict-transport-security' => 'max-age=63072000; includeSubDomains; preload',
67
+ },
68
+ )
67
69
  else
68
70
  # Development-specific settings
69
- puts "🔒 Security features enabled:"
70
- puts " ✓ CSRF Protection"
71
- puts " ✓ Input Validation"
72
- puts " ✓ Request Size Limits"
73
- puts " ✓ Security Headers"
74
- puts " ✓ Trusted Proxy Support"
75
- puts ""
71
+ puts '🔒 Security features enabled:'
72
+ puts ' ✓ CSRF Protection'
73
+ puts ' ✓ Input Validation'
74
+ puts ' ✓ Request Size Limits'
75
+ puts ' ✓ Security Headers'
76
+ puts ' ✓ Trusted Proxy Support'
77
+ puts ''
76
78
  end
77
79
 
78
80
  # Mount the application
79
- map('/') {
81
+ map('/') do
80
82
  run app
81
- }
83
+ end