homura-runtime 0.3.6 → 0.3.8

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.
@@ -41,15 +41,17 @@
41
41
  # HTTP fetch-style interaction with DO instances, which is enough for
42
42
  # counters / session state / rate limiters.
43
43
 
44
- require 'json'
44
+ require "json"
45
45
 
46
46
  module Cloudflare
47
47
  class DurableObjectError < StandardError
48
48
  attr_reader :operation, :do_class
49
49
  def initialize(message, operation: nil, do_class: nil)
50
50
  @operation = operation
51
- @do_class = do_class
52
- super("[Cloudflare::DurableObject] class=#{do_class || '?'} op=#{operation || 'fetch'}: #{message}")
51
+ @do_class = do_class
52
+ super(
53
+ "[Cloudflare::DurableObject] class=#{do_class || "?"} op=#{operation || "fetch"}: #{message}"
54
+ )
53
55
  end
54
56
  end
55
57
 
@@ -136,7 +138,7 @@ module Cloudflare
136
138
  # responses — the 101 Response carries its WebSocket in a
137
139
  # `.webSocket` property that disappears if we reconstruct the
138
140
  # Response via the HTTPResponse wrapper.
139
- def fetch_raw(url_or_request, method: 'GET', headers: nil, body: nil)
141
+ def fetch_raw(url_or_request, method: "GET", headers: nil, body: nil)
140
142
  hdrs = headers || {}
141
143
  method_str = method.to_s.upcase
142
144
  js_headers = Cloudflare::HTTP.ruby_headers_to_js(hdrs)
@@ -152,7 +154,7 @@ module Cloudflare
152
154
  # the prefix), so user code can use any pathname it wants as its
153
155
  # internal DO command channel. Returns a JS Promise the caller
154
156
  # `__await__`s to get a `Cloudflare::HTTPResponse`.
155
- def fetch(url_or_request, method: 'GET', headers: nil, body: nil)
157
+ def fetch(url_or_request, method: "GET", headers: nil, body: nil)
156
158
  js_stub = @js
157
159
  hdrs = headers || {}
158
160
  method_str = method.to_s.upcase
@@ -161,11 +163,12 @@ module Cloudflare
161
163
  url_str = url_or_request.to_s
162
164
  err_klass = Cloudflare::DurableObjectError
163
165
  response_klass = Cloudflare::HTTPResponse
164
- do_class_label = 'DurableObjectStub'
166
+ do_class_label = "DurableObjectStub"
165
167
 
166
168
  # Single-line IIFE — see `lib/homura/runtime/cache.rb#put`
167
169
  # for why Opal can silently drop a multi-line x-string Promise.
168
- js_promise = `(async function(stub, url_str, method_str, js_headers, js_body, Kernel, err_klass, do_class_label) { var init = { method: method_str, headers: js_headers }; if (js_body !== null && js_body !== undefined && js_body !== Opal.nil) { init.body = js_body; } var resp; try { resp = await stub.fetch(url_str, init); } catch (e) { Kernel.$raise(err_klass.$new(e && e.message ? e.message : String(e), Opal.hash({ operation: 'stub.fetch', do_class: do_class_label }))); } var text = ''; try { text = await resp.text(); } catch (_) { text = ''; } var hk = []; var hv = []; if (resp.headers && typeof resp.headers.forEach === 'function') { resp.headers.forEach(function(v, k) { hk.push(String(k).toLowerCase()); hv.push(String(v)); }); } return { status: resp.status|0, text: text, hkeys: hk, hvals: hv }; })(#{js_stub}, #{url_str}, #{method_str}, #{js_headers}, #{js_body}, #{Kernel}, #{err_klass}, #{do_class_label})`
170
+ js_promise =
171
+ `(async function(stub, url_str, method_str, js_headers, js_body, Kernel, err_klass, do_class_label) { var init = { method: method_str, headers: js_headers }; if (js_body !== null && js_body !== undefined && js_body !== Opal.nil) { init.body = js_body; } var resp; try { resp = await stub.fetch(url_str, init); } catch (e) { Kernel.$raise(err_klass.$new(e && e.message ? e.message : String(e), Opal.hash({ operation: 'stub.fetch', do_class: do_class_label }))); } var text = ''; try { text = await resp.text(); } catch (_) { text = ''; } var hk = []; var hv = []; if (resp.headers && typeof resp.headers.forEach === 'function') { resp.headers.forEach(function(v, k) { hk.push(String(k).toLowerCase()); hv.push(String(v)); }); } return { status: resp.status|0, text: text, hkeys: hk, hvals: hv }; })(#{js_stub}, #{url_str}, #{method_str}, #{js_headers}, #{js_body}, #{Kernel}, #{err_klass}, #{do_class_label})`
169
172
 
170
173
  js_result = js_promise.__await__
171
174
  hkeys = `#{js_result}.hkeys`
@@ -179,39 +182,39 @@ module Cloudflare
179
182
  end
180
183
 
181
184
  response_klass.new(
182
- status: `#{js_result}.status`,
185
+ status: `#{js_result}.status`,
183
186
  headers: h,
184
- body: `#{js_result}.text`,
185
- url: url_str
187
+ body: `#{js_result}.text`,
188
+ url: url_str
186
189
  )
187
190
  end
188
191
 
189
- def request(path, method: 'GET', headers: nil, body: nil)
192
+ def request(path, method: "GET", headers: nil, body: nil)
190
193
  hdrs = headers ? headers.dup : {}
191
194
  request_body = body
192
195
  if body.is_a?(Hash) || body.is_a?(Array)
193
196
  request_body = body.to_json
194
- unless hdrs.key?('content-type') || hdrs.key?('Content-Type')
195
- hdrs['content-type'] = 'application/json'
197
+ unless hdrs.key?("content-type") || hdrs.key?("Content-Type")
198
+ hdrs["content-type"] = "application/json"
196
199
  end
197
200
  end
198
201
  fetch(path, method: method, headers: hdrs, body: request_body)
199
202
  end
200
203
 
201
204
  def get(path, headers: nil)
202
- request(path, method: 'GET', headers: headers)
205
+ request(path, method: "GET", headers: headers)
203
206
  end
204
207
 
205
208
  def post(path, body = nil, headers: nil)
206
- request(path, method: 'POST', headers: headers, body: body)
209
+ request(path, method: "POST", headers: headers, body: body)
207
210
  end
208
211
 
209
212
  def put(path, body = nil, headers: nil)
210
- request(path, method: 'PUT', headers: headers, body: body)
213
+ request(path, method: "PUT", headers: headers, body: body)
211
214
  end
212
215
 
213
216
  def delete(path, headers: nil)
214
- request(path, method: 'DELETE', headers: headers)
217
+ request(path, method: "DELETE", headers: headers)
215
218
  end
216
219
  end
217
220
 
@@ -251,12 +254,15 @@ module Cloudflare
251
254
  # The block must return a Rack-style triple `[status, headers, body]`.
252
255
  # `body` may be a String or an object that responds to `to_s`.
253
256
  def self.define(class_name, &block)
254
- raise ArgumentError, 'define requires a block' unless block
255
- raise ArgumentError, 'class_name must be a String' unless class_name.is_a?(String)
257
+ raise ArgumentError, "define requires a block" unless block
258
+ unless class_name.is_a?(String)
259
+ raise ArgumentError, "class_name must be a String"
260
+ end
256
261
  @handlers ||= {}
257
262
  # Wrap via define_method so Opal's `# await: true` picks it up as
258
263
  # async (same trick Sinatra::Scheduled uses for its jobs).
259
- method_name = "__do_handler_#{class_name.gsub(/[^A-Za-z0-9_]/, '_')}".to_sym
264
+ method_name =
265
+ "__do_handler_#{class_name.gsub(/[^A-Za-z0-9_]/, "_")}".to_sym
260
266
  DurableObjectRequestContext.send(:define_method, method_name, &block)
261
267
  unbound = DurableObjectRequestContext.instance_method(method_name)
262
268
  DurableObjectRequestContext.send(:remove_method, method_name)
@@ -279,12 +285,17 @@ module Cloudflare
279
285
  # class (wired by the exported HomuraCounterDO in
280
286
  # src/worker.mjs). Return value is ignored — the runtime doesn't
281
287
  # expect a body.
282
- def self.define_web_socket_handlers(class_name, on_message: nil, on_close: nil, on_error: nil)
288
+ def self.define_web_socket_handlers(
289
+ class_name,
290
+ on_message: nil,
291
+ on_close: nil,
292
+ on_error: nil
293
+ )
283
294
  @ws_handlers ||= {}
284
295
  @ws_handlers[class_name] = {
285
296
  on_message: on_message,
286
- on_close: on_close,
287
- on_error: on_error
297
+ on_close: on_close,
298
+ on_error: on_error
288
299
  }.compact
289
300
  nil
290
301
  end
@@ -303,8 +314,12 @@ module Cloudflare
303
314
  def self.dispatch_js(class_name, js_state, js_env, js_request, body_text)
304
315
  handler = handler_for(class_name)
305
316
  if handler.nil?
306
- body = { 'error' => "no Ruby handler for DurableObject class #{class_name}" }.to_json
307
- return build_js_response(500, { 'content-type' => 'application/json' }, body)
317
+ body = {
318
+ "error" => "no Ruby handler for DurableObject class #{class_name}"
319
+ }.to_json
320
+ return(
321
+ build_js_response(500, { "content-type" => "application/json" }, body)
322
+ )
308
323
  end
309
324
 
310
325
  state = DurableObjectState.new(js_state)
@@ -334,12 +349,12 @@ module Cloudflare
334
349
  [result[0].to_i, result[1] || {}, result[2].to_s]
335
350
  elsif result.is_a?(Hash)
336
351
  [
337
- (result['status'] || result[:status] || 200).to_i,
338
- result['headers'] || result[:headers] || {},
339
- (result['body'] || result[:body] || '').to_s
352
+ (result["status"] || result[:status] || 200).to_i,
353
+ result["headers"] || result[:headers] || {},
354
+ (result["body"] || result[:body] || "").to_s
340
355
  ]
341
356
  else
342
- [200, { 'content-type' => 'text/plain; charset=utf-8' }, result.to_s]
357
+ [200, { "content-type" => "text/plain; charset=utf-8" }, result.to_s]
343
358
  end
344
359
  end
345
360
 
@@ -358,7 +373,13 @@ module Cloudflare
358
373
  # WebSocket dispatchers — called from the JS DO class's
359
374
  # `webSocketMessage` / `webSocketClose` / `webSocketError`
360
375
  # methods. Each returns a JS Promise that resolves to undefined.
361
- def self.dispatch_ws_message(class_name, js_ws, js_message, js_state, js_env)
376
+ def self.dispatch_ws_message(
377
+ class_name,
378
+ js_ws,
379
+ js_message,
380
+ js_state,
381
+ js_env
382
+ )
362
383
  h = web_socket_handlers_for(class_name)
363
384
  return nil if h.nil? || h[:on_message].nil?
364
385
  state = DurableObjectState.new(js_state)
@@ -366,7 +387,15 @@ module Cloudflare
366
387
  nil
367
388
  end
368
389
 
369
- def self.dispatch_ws_close(class_name, js_ws, code, reason, was_clean, js_state, js_env)
390
+ def self.dispatch_ws_close(
391
+ class_name,
392
+ js_ws,
393
+ code,
394
+ reason,
395
+ was_clean,
396
+ js_state,
397
+ js_env
398
+ )
370
399
  h = web_socket_handlers_for(class_name)
371
400
  return nil if h.nil? || h[:on_close].nil?
372
401
  state = DurableObjectState.new(js_state)
@@ -411,7 +440,7 @@ module Cloudflare
411
440
 
412
441
  def initialize(js_state)
413
442
  @js_state = js_state
414
- @storage = DurableObjectStorage.new(`#{js_state} && #{js_state}.storage`)
443
+ @storage = DurableObjectStorage.new(`#{js_state} && #{js_state}.storage`)
415
444
  end
416
445
 
417
446
  # Unique id of this DO instance as a hex String.
@@ -442,7 +471,10 @@ module Cloudflare
442
471
  js_state = @js_state
443
472
  if tags && !tags.empty?
444
473
  js_tags = `([])`
445
- tags.each { |t| ts = t.to_s; `#{js_tags}.push(#{ts})` }
474
+ tags.each do |t|
475
+ ts = t.to_s
476
+ `#{js_tags}.push(#{ts})`
477
+ end
446
478
  `#{js_state}.acceptWebSocket(#{js_ws}, #{js_tags})`
447
479
  else
448
480
  `#{js_state}.acceptWebSocket(#{js_ws})`
@@ -455,12 +487,13 @@ module Cloudflare
455
487
  # `getWebSockets(tag)`.
456
488
  def web_sockets(tag: nil)
457
489
  js_state = @js_state
458
- js_arr = if tag
459
- ts = tag.to_s
460
- `(#{js_state}.getWebSockets ? #{js_state}.getWebSockets(#{ts}) : [])`
461
- else
462
- `(#{js_state}.getWebSockets ? #{js_state}.getWebSockets() : [])`
463
- end
490
+ js_arr =
491
+ if tag
492
+ ts = tag.to_s
493
+ `(#{js_state}.getWebSockets ? #{js_state}.getWebSockets(#{ts}) : [])`
494
+ else
495
+ `(#{js_state}.getWebSockets ? #{js_state}.getWebSockets() : [])`
496
+ end
464
497
  out = []
465
498
  len = `#{js_arr}.length`
466
499
  i = 0
@@ -494,7 +527,7 @@ module Cloudflare
494
527
  def put(key, value)
495
528
  js = @js
496
529
  err_klass = Cloudflare::DurableObjectError
497
- js_value = value.nil? ? 'null' : value.to_json
530
+ js_value = value.nil? ? "null" : value.to_json
498
531
  `#{js}.put(#{key.to_s}, #{js_value}).catch(function(e) { #{Kernel}.$raise(#{err_klass}.$new(e && e.message ? e.message : String(e), Opal.hash({ operation: 'storage.put' }))); })`
499
532
  end
500
533
 
@@ -530,11 +563,12 @@ module Cloudflare
530
563
  err_klass = Cloudflare::DurableObjectError
531
564
  js_opts = `({})`
532
565
  `#{js_opts}.prefix = #{prefix.to_s}` unless prefix.nil?
533
- `#{js_opts}.limit = #{limit.to_i}` unless limit.nil?
534
- `#{js_opts}.reverse = #{!!reverse}` unless reverse.nil?
535
- `#{js_opts}.start = #{start.to_s}` unless start.nil?
566
+ `#{js_opts}.limit = #{limit.to_i}` unless limit.nil?
567
+ `#{js_opts}.reverse = #{!!reverse}` unless reverse.nil?
568
+ `#{js_opts}.start = #{start.to_s}` unless start.nil?
536
569
  `#{js_opts}.end = #{end_key.to_s}` unless end_key.nil?
537
- js_promise = `#{js}.list(#{js_opts}).catch(function(e) { #{Kernel}.$raise(#{err_klass}.$new(e && e.message ? e.message : String(e), Opal.hash({ operation: 'storage.list' }))); })`
570
+ js_promise =
571
+ `#{js}.list(#{js_opts}).catch(function(e) { #{Kernel}.$raise(#{err_klass}.$new(e && e.message ? e.message : String(e), Opal.hash({ operation: 'storage.list' }))); })`
538
572
  js_result = js_promise.__await__
539
573
  out = {}
540
574
  return out if `#{js_result} == null`
@@ -549,7 +583,7 @@ module Cloudflare
549
583
  class DurableObjectRequest
550
584
  attr_reader :js_request, :body
551
585
 
552
- def initialize(js_request, body_text = '')
586
+ def initialize(js_request, body_text = "")
553
587
  @js_request = js_request
554
588
  @body = body_text.to_s
555
589
  end
@@ -566,7 +600,7 @@ module Cloudflare
566
600
 
567
601
  def path
568
602
  u = url
569
- return '' if u.nil? || u.empty?
603
+ return "" if u.nil? || u.empty?
570
604
  begin
571
605
  # Extract pathname via URL() so relative paths aren't mangled.
572
606
  u_str = u
@@ -609,17 +643,37 @@ module Cloudflare
609
643
  @request = request
610
644
  end
611
645
 
612
- def storage; @state.storage; end
613
- def cf_env; env['cloudflare.env']; end
614
- def cf_ctx; env['cloudflare.ctx']; end
646
+ def storage
647
+ @state.storage
648
+ end
649
+ def cf_env
650
+ env["cloudflare.env"]
651
+ end
652
+ def cf_ctx
653
+ env["cloudflare.ctx"]
654
+ end
615
655
 
616
- def d1; env['cloudflare.DB']; end
617
- def db; d1; end
618
- def kv; env['cloudflare.KV']; end
619
- def bucket; env['cloudflare.BUCKET']; end
620
- def ai; Cloudflare::Bindings.ai(env); end
621
- def send_email; env['cloudflare.SEND_EMAIL']; end
622
- def jobs_queue; env['cloudflare.QUEUE_JOBS']; end
656
+ def d1
657
+ env["cloudflare.DB"]
658
+ end
659
+ def db
660
+ d1
661
+ end
662
+ def kv
663
+ env["cloudflare.KV"]
664
+ end
665
+ def bucket
666
+ env["cloudflare.BUCKET"]
667
+ end
668
+ def ai
669
+ Cloudflare::Bindings.ai(env)
670
+ end
671
+ def send_email
672
+ env["cloudflare.SEND_EMAIL"]
673
+ end
674
+ def jobs_queue
675
+ env["cloudflare.QUEUE_JOBS"]
676
+ end
623
677
  def durable_object(name, id_or_name = nil)
624
678
  Cloudflare::Bindings.durable_object(env, name, id_or_name)
625
679
  end
@@ -42,19 +42,30 @@ module Cloudflare
42
42
  def send(to:, from:, subject:, text: nil, html: nil, reply_to: nil)
43
43
  js = @js
44
44
  err_klass = Cloudflare::Email::Error
45
- raise Error, 'send_email binding not bound' unless available?
45
+ raise Error, "send_email binding not bound" unless available?
46
46
 
47
- raise Error, 'subject is required' if subject.nil? || subject.to_s.strip.empty?
47
+ if subject.nil? || subject.to_s.strip.empty?
48
+ raise Error, "subject is required"
49
+ end
48
50
 
49
51
  has_text = !(text.nil? || text.to_s.empty?)
50
52
  has_html = !(html.nil? || html.to_s.empty?)
51
- raise Error, 'text or html is required' unless has_text || has_html
52
-
53
- payload = build_send_payload(to: to, from: from, subject: subject.to_s, text: text, html: html, reply_to: reply_to)
53
+ raise Error, "text or html is required" unless has_text || has_html
54
+
55
+ payload =
56
+ build_send_payload(
57
+ to: to,
58
+ from: from,
59
+ subject: subject.to_s,
60
+ text: text,
61
+ html: html,
62
+ reply_to: reply_to
63
+ )
54
64
 
55
65
  cf = Cloudflare
56
66
  # 多行 x-string をメソッド末尾に置くと Opal が Promise を返さない出力になることがあるため return を明示する。
57
- return `(async function(binding, payload, Kernel, Err, cf) {
67
+ return(
68
+ `(async function(binding, payload, Kernel, Err, cf) {
58
69
  try {
59
70
  var r = await binding.send(payload);
60
71
  if (r == null || r === undefined) {
@@ -73,6 +84,7 @@ module Cloudflare
73
84
  Kernel.$raise(Err.$new(msg, Opal.hash({ code: code })));
74
85
  }
75
86
  })(#{js}, #{payload}, #{Kernel}, #{err_klass}, #{cf})`
87
+ )
76
88
  end
77
89
 
78
90
  private
@@ -103,7 +115,7 @@ module Cloudflare
103
115
  # Returns a JS array: mix of address strings and `{ email, name? }` objects (Workers API shape).
104
116
  def normalize_to_js(raw)
105
117
  entries = flatten_recipients(raw)
106
- raise Error, 'to is empty' if entries.empty?
118
+ raise Error, "to is empty" if entries.empty?
107
119
 
108
120
  arr = `([])`
109
121
  entries.each do |e|
@@ -113,7 +125,9 @@ module Cloudflare
113
125
  when Hash
114
126
  js = `({})`
115
127
  `#{js}.email = #{e[:email]}`
116
- `#{js}.name = #{e[:name].to_s}` if e[:name] && !e[:name].to_s.strip.empty?
128
+ if e[:name] && !e[:name].to_s.strip.empty?
129
+ `#{js}.name = #{e[:name].to_s}`
130
+ end
117
131
  `#{arr}.push(#{js})`
118
132
  end
119
133
  end
@@ -129,9 +143,9 @@ module Cloudflare
129
143
  s = raw.strip
130
144
  s.empty? ? [] : [s]
131
145
  when Hash
132
- em = raw[:email] || raw['email']
146
+ em = raw[:email] || raw["email"]
133
147
  return [] if em.nil? || em.to_s.strip.empty?
134
- nm = raw[:name] || raw['name']
148
+ nm = raw[:name] || raw["name"]
135
149
  if nm.nil? || nm.to_s.strip.empty?
136
150
  [em.to_s.strip]
137
151
  else
@@ -162,12 +176,12 @@ module Cloudflare
162
176
  case raw
163
177
  when String
164
178
  s = raw.strip
165
- raise Error, 'from address is empty' if s.empty?
179
+ raise Error, "from address is empty" if s.empty?
166
180
  return s
167
181
  when Hash
168
- em = raw[:email] || raw['email']
169
- nm = raw[:name] || raw['name']
170
- raise Error, 'from.email is required' if em.nil? || em.to_s.strip.empty?
182
+ em = raw[:email] || raw["email"]
183
+ nm = raw[:name] || raw["name"]
184
+ raise Error, "from.email is required" if em.nil? || em.to_s.strip.empty?
171
185
  js = `({})`
172
186
  `#{js}.email = #{em.to_s.strip}`
173
187
  `#{js}.name = #{nm.to_s}` unless nm.nil? || nm.to_s.strip.empty?
@@ -15,7 +15,7 @@
15
15
  # goes through `globalThis.fetch`, because Cloudflare Workers does not
16
16
  # expose a TCP socket API.
17
17
 
18
- require 'json'
18
+ require "json"
19
19
 
20
20
  module Cloudflare
21
21
  class HTTPError < StandardError
@@ -23,7 +23,7 @@ module Cloudflare
23
23
  def initialize(message, url: nil, method: nil)
24
24
  @url = url
25
25
  @method = method
26
- super("[Cloudflare::HTTP] #{method || 'GET'} #{url}: #{message}")
26
+ super("[Cloudflare::HTTP] #{method || "GET"} #{url}: #{message}")
27
27
  end
28
28
  end
29
29
 
@@ -67,7 +67,7 @@ module Cloudflare
67
67
  #
68
68
  # The whole response body is awaited and returned as a String.
69
69
  # Use `Cloudflare::HTTPResponse#body` to access raw text.
70
- def self.fetch(url, method: 'GET', headers: nil, body: nil)
70
+ def self.fetch(url, method: "GET", headers: nil, body: nil)
71
71
  hdrs = headers || DEFAULT_HEADERS
72
72
  method_str = method.to_s.upcase
73
73
  js_headers = ruby_headers_to_js(hdrs)
@@ -87,7 +87,8 @@ module Cloudflare
87
87
  # expression. See the single-line IIFE pattern used in
88
88
  # lib/homura/runtime/{cache,queue,durable_object}.rb#put for
89
89
  # the alternative that survives either position. (Phase 11B audit.)
90
- js_promise = `
90
+ js_promise =
91
+ `
91
92
  (async function() {
92
93
  var init = { method: #{method_str}, headers: #{js_headers}, redirect: 'follow' };
93
94
  if (#{js_body} !== nil && #{js_body} != null) { init.body = #{js_body}; }
@@ -43,12 +43,12 @@ module Cloudflare
43
43
  class UploadedFile
44
44
  attr_reader :filename, :content_type, :name, :head, :bytes_binstr
45
45
 
46
- def initialize(filename:, content_type:, name:, head: '', bytes_binstr: '')
46
+ def initialize(filename:, content_type:, name:, head: "", bytes_binstr: "")
47
47
  @filename = filename
48
- @content_type = content_type || 'application/octet-stream'
48
+ @content_type = content_type || "application/octet-stream"
49
49
  @name = name
50
50
  @head = head
51
- @bytes_binstr = bytes_binstr || ''
51
+ @bytes_binstr = bytes_binstr || ""
52
52
  end
53
53
 
54
54
  # Byte length of the part (not the JS string length — they're the
@@ -100,9 +100,9 @@ module Cloudflare
100
100
  def to_h
101
101
  {
102
102
  filename: @filename,
103
- type: @content_type,
104
- name: @name,
105
- head: @head,
103
+ type: @content_type,
104
+ name: @name,
105
+ head: @head,
106
106
  tempfile: self
107
107
  }
108
108
  end
@@ -127,7 +127,7 @@ module Cloudflare
127
127
  def self.parse_boundary(content_type)
128
128
  return nil if content_type.nil?
129
129
  ct = content_type.to_s
130
- return nil unless ct.downcase.include?('multipart/')
130
+ return nil unless ct.downcase.include?("multipart/")
131
131
  # Prefer the quoted form. The quoted value may contain any byte
132
132
  # except a literal `"` (RFC 2046 §5.1.1 bans `"` in the value).
133
133
  if (m = ct.match(/boundary="([^"]+)"/i))
@@ -151,20 +151,18 @@ module Cloudflare
151
151
  return {} if boundary.nil?
152
152
  return {} if body_binstr.nil? || body_binstr.empty?
153
153
 
154
- sep = '--' + boundary
155
- term = '--' + boundary + '--'
156
- sep_line = sep + CRLF
157
- sep_last = sep + CRLF # the very first boundary may skip the leading CRLF
158
- body = body_binstr.to_s
154
+ sep = "--" + boundary
155
+ term = "--" + boundary + "--"
156
+ sep_line = sep + CRLF
157
+ sep_last = sep + CRLF # the very first boundary may skip the leading CRLF
158
+ body = body_binstr.to_s
159
159
 
160
160
  # Skip any preamble before the first boundary.
161
161
  start_idx = body.index(sep)
162
162
  return {} if start_idx.nil?
163
163
  cursor = start_idx + sep.length
164
164
  # consume possible CRLF right after the first boundary
165
- if body[cursor, 2] == CRLF
166
- cursor += 2
167
- end
165
+ cursor += 2 if body[cursor, 2] == CRLF
168
166
 
169
167
  parts = {}
170
168
 
@@ -181,28 +179,32 @@ module Cloudflare
181
179
  headers_end = part.index(CRLF + CRLF)
182
180
  if headers_end
183
181
  raw_headers = part[0...headers_end]
184
- raw_body = part[(headers_end + 4)..-1] || ''
182
+ raw_body = part[(headers_end + 4)..-1] || ""
185
183
  else
186
184
  raw_headers = part
187
- raw_body = ''
185
+ raw_body = ""
188
186
  end
189
187
 
190
188
  disposition = nil
191
- ctype = nil
192
- raw_headers.split(CRLF).each do |line|
193
- name, value = line.split(':', 2)
194
- next if name.nil? || value.nil?
195
- name = name.strip.downcase
196
- value = value.strip
197
- case name
198
- when 'content-disposition' then disposition = value
199
- when 'content-type' then ctype = value
189
+ ctype = nil
190
+ raw_headers
191
+ .split(CRLF)
192
+ .each do |line|
193
+ name, value = line.split(":", 2)
194
+ next if name.nil? || value.nil?
195
+ name = name.strip.downcase
196
+ value = value.strip
197
+ case name
198
+ when "content-disposition"
199
+ disposition = value
200
+ when "content-type"
201
+ ctype = value
202
+ end
200
203
  end
201
- end
202
204
 
203
205
  if disposition
204
- field_name = extract_disposition_param(disposition, 'name')
205
- filename = extract_disposition_param(disposition, 'filename')
206
+ field_name = extract_disposition_param(disposition, "name")
207
+ filename = extract_disposition_param(disposition, "filename")
206
208
  if field_name
207
209
  if filename && !filename.empty?
208
210
  parts[field_name] = UploadedFile.new(
@@ -220,12 +222,8 @@ module Cloudflare
220
222
 
221
223
  cursor = next_sep + CRLF.length + sep.length
222
224
  # Check whether this is the terminator `--boundary--`
223
- if body[cursor, 2] == '--'
224
- break
225
- end
226
- if body[cursor, 2] == CRLF
227
- cursor += 2
228
- end
225
+ break if body[cursor, 2] == "--"
226
+ cursor += 2 if body[cursor, 2] == CRLF
229
227
  end
230
228
 
231
229
  parts
@@ -274,14 +272,14 @@ module Cloudflare
274
272
  #
275
273
  # Called lazily from our patched Rack::Request#POST.
276
274
  def self.rack_params(env)
277
- cached = env['cloudflare.multipart']
275
+ cached = env["cloudflare.multipart"]
278
276
  return cached if cached
279
277
 
280
- ct = env['CONTENT_TYPE']
281
- return ({}) unless ct && ct.to_s.downcase.include?('multipart/')
278
+ ct = env["CONTENT_TYPE"]
279
+ return({}) unless ct && ct.to_s.downcase.include?("multipart/")
282
280
 
283
- io = env['rack.input']
284
- return ({}) if io.nil?
281
+ io = env["rack.input"]
282
+ return({}) if io.nil?
285
283
 
286
284
  # `rack.input` is normally a StringIO wrapping the body_binstr
287
285
  # we staged in src/worker.mjs. Read the full body; it's already
@@ -290,14 +288,14 @@ module Cloudflare
290
288
  if io.respond_to?(:rewind)
291
289
  begin
292
290
  io.rewind
293
- rescue
291
+ rescue StandardError
294
292
  # some stubs don't support rewind — ignore
295
293
  end
296
294
  end
297
- body = io.respond_to?(:read) ? io.read.to_s : ''
295
+ body = io.respond_to?(:read) ? io.read.to_s : ""
298
296
 
299
297
  parsed = parse(body, ct)
300
- env['cloudflare.multipart'] = parsed
298
+ env["cloudflare.multipart"] = parsed
301
299
  parsed
302
300
  end
303
301
  end
@@ -314,15 +312,17 @@ end
314
312
  # bespoke Cloudflare::Multipart parser for multipart requests, falling
315
313
  # back to the gem implementation for everything else.
316
314
 
317
- require 'rack/request'
315
+ require "rack/request"
318
316
 
319
317
  module Rack
320
318
  class Request
321
- alias_method :__homura_original_POST, :POST unless method_defined?(:__homura_original_POST)
319
+ unless method_defined?(:__homura_original_POST)
320
+ alias_method :__homura_original_POST, :POST
321
+ end
322
322
 
323
323
  def POST
324
- ct = env['CONTENT_TYPE']
325
- if ct && ct.to_s.downcase.include?('multipart/')
324
+ ct = env["CONTENT_TYPE"]
325
+ if ct && ct.to_s.downcase.include?("multipart/")
326
326
  ::Cloudflare::Multipart.rack_params(env)
327
327
  else
328
328
  __homura_original_POST