rufio 1.0.2 → 1.0.3
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/lib/rufio/command_logger.rb +2 -1
- data/lib/rufio/file_opener.rb +42 -26
- data/lib/rufio/search_controller.rb +2 -1
- data/lib/rufio/terminal_ui.rb +22 -3
- data/lib/rufio/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 247130c9fd76d05ed1ca70b859ea587e0b42f82ca7491fdc8b1ebea80d0257d4
|
|
4
|
+
data.tar.gz: 0122c547241cbd60c870798324b9cddaef6a3767a3153f52ca3b6b6cee3efa07
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6ef019b2c59bc7bea03293c837ca7ff1f40cb4015e585056c9d12628dd552e33533a6ab3e6d012473110ebac0456978d1bb5c762b6192f13d7d8a269fd0826a9
|
|
7
|
+
data.tar.gz: 75138dbdeb740c137045ef17ba6ede44a8a4b2aa85450618e457b3e8d04ca0d5d6157622aadccb9abb934e4c60b4c5a830923406d9854d02128c5f65e4c19e53
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ All notable changes to rufio will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.0.3] - 2026-10-23
|
|
9
|
+
|
|
10
|
+
### Security
|
|
11
|
+
- **Fix shell command injection via file names**: `FileOpener` interpolated file paths into shell strings and only quoted them when they contained spaces or quotes, so opening a file named like `a;touch pwned` or containing `$(...)` / backticks could execute arbitrary commands. Commands are now executed in array form without a shell (`system(*argv)`), paths starting with `-` are prefixed with `./`, and the Windows default-app launch uses `rundll32 url.dll,FileProtocolHandler` instead of `cmd /c start` (avoids `%VAR%` / `&` expansion). Removed `quote_shell_argument`
|
|
12
|
+
- **rga option injection**: A `--` separator is now passed before the search query so queries such as `--pre=cmd` are not interpreted as rga options
|
|
13
|
+
- **Command logs are now written with `0600` permissions**, since command output may contain secrets
|
|
14
|
+
|
|
15
|
+
### Removed
|
|
16
|
+
- `TestUISnapshot` and its `test/snapshots/` fixtures (tests were failing independent of code changes)
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- **Windows: Sluggish key handling**: The main loop processed only one input byte per frame and then slept ~33ms, so key repeat could not keep up. Now up to 32 queued inputs are processed per frame with a single redraw, and the Windows frame interval is ~8ms
|
|
20
|
+
- **Windows: Input queue wait**: Uses `Queue#pop(timeout:)` on Ruby 3.2+ instead of 1ms `sleep` polling (inaccurate under the ~15ms Windows timer resolution)
|
|
21
|
+
- **Windows: Arrow keys misread as ESC**: The wait for escape sequence follow-up bytes was extended from 5ms to 25ms to tolerate ConPTY byte delays
|
|
22
|
+
|
|
8
23
|
## [1.0.0] - 2026-04-28
|
|
9
24
|
|
|
10
25
|
### Added
|
data/lib/rufio/command_logger.rb
CHANGED
|
@@ -30,7 +30,8 @@ module Rufio
|
|
|
30
30
|
# ディレクトリが存在しない場合は作成(バックグラウンドスレッドでの実行時の競合を防ぐ)
|
|
31
31
|
FileUtils.mkdir_p(@log_dir) unless Dir.exist?(@log_dir)
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
# コマンド出力に機密情報が含まれ得るため、所有者のみ読み書き可能にする
|
|
34
|
+
File.write(filepath, content, perm: 0o600)
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
# ログファイル一覧を取得(新しい順)
|
data/lib/rufio/file_opener.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'shellwords'
|
|
4
|
+
|
|
3
5
|
module Rufio
|
|
4
6
|
class FileOpener
|
|
5
7
|
def initialize
|
|
@@ -43,26 +45,26 @@ module Rufio
|
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
def execute_command(application, file_path)
|
|
46
|
-
|
|
48
|
+
path = safe_path(file_path)
|
|
47
49
|
|
|
48
50
|
case RbConfig::CONFIG['host_os']
|
|
49
51
|
when /mswin|mingw|cygwin/
|
|
50
|
-
# Windows
|
|
51
|
-
|
|
52
|
+
# Windows: cmd.exe を経由せず(%VAR% や & の展開を避ける)既定アプリで開く
|
|
53
|
+
run_command(['rundll32', 'url.dll,FileProtocolHandler', path])
|
|
52
54
|
when /darwin/
|
|
53
55
|
# macOS
|
|
54
56
|
if application == 'open'
|
|
55
|
-
|
|
57
|
+
run_command(['open', path])
|
|
56
58
|
else
|
|
57
59
|
# VSCodeなど特定のアプリケーション
|
|
58
|
-
|
|
60
|
+
run_command([*application_argv(application), path])
|
|
59
61
|
end
|
|
60
62
|
else
|
|
61
63
|
# Linux/Unix
|
|
62
64
|
if application == 'open'
|
|
63
|
-
|
|
65
|
+
run_command(['xdg-open', path])
|
|
64
66
|
else
|
|
65
|
-
|
|
67
|
+
run_command([*application_argv(application), path])
|
|
66
68
|
end
|
|
67
69
|
end
|
|
68
70
|
rescue StandardError => e
|
|
@@ -71,37 +73,39 @@ module Rufio
|
|
|
71
73
|
end
|
|
72
74
|
|
|
73
75
|
def execute_command_with_line(application, file_path, line_number)
|
|
74
|
-
|
|
76
|
+
path = safe_path(file_path)
|
|
77
|
+
line = line_number.to_i
|
|
78
|
+
app = application_argv(application)
|
|
75
79
|
|
|
76
80
|
case RbConfig::CONFIG['host_os']
|
|
77
81
|
when /mswin|mingw|cygwin/
|
|
78
82
|
# Windows
|
|
79
83
|
if application.include?('code')
|
|
80
|
-
|
|
84
|
+
run_command([*app, '--goto', "#{path}:#{line}"])
|
|
81
85
|
else
|
|
82
|
-
|
|
86
|
+
run_command(['rundll32', 'url.dll,FileProtocolHandler', path])
|
|
83
87
|
end
|
|
84
88
|
when /darwin/
|
|
85
89
|
# macOS
|
|
86
90
|
if application == 'open'
|
|
87
|
-
|
|
91
|
+
run_command(['open', path])
|
|
88
92
|
elsif application.include?('code')
|
|
89
|
-
|
|
93
|
+
run_command([*app, '--goto', "#{path}:#{line}"])
|
|
90
94
|
elsif application.include?('vim') || application.include?('nvim')
|
|
91
|
-
|
|
95
|
+
run_command([*app, "+#{line}", path])
|
|
92
96
|
else
|
|
93
|
-
|
|
97
|
+
run_command([*app, path])
|
|
94
98
|
end
|
|
95
99
|
else
|
|
96
100
|
# Linux/Unix
|
|
97
101
|
if application == 'open'
|
|
98
|
-
|
|
102
|
+
run_command(['xdg-open', path])
|
|
99
103
|
elsif application.include?('code')
|
|
100
|
-
|
|
104
|
+
run_command([*app, '--goto', "#{path}:#{line}"])
|
|
101
105
|
elsif application.include?('vim') || application.include?('nvim')
|
|
102
|
-
|
|
106
|
+
run_command([*app, "+#{line}", path])
|
|
103
107
|
else
|
|
104
|
-
|
|
108
|
+
run_command([*app, path])
|
|
105
109
|
end
|
|
106
110
|
end
|
|
107
111
|
rescue StandardError => e
|
|
@@ -109,27 +113,39 @@ module Rufio
|
|
|
109
113
|
false
|
|
110
114
|
end
|
|
111
115
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
116
|
+
# シェルを介さず配列形式で実行する(ファイル名経由のコマンドインジェクション防止)
|
|
117
|
+
def run_command(argv)
|
|
118
|
+
system(*argv)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# 設定のアプリケーション指定(例: "code -r")を引数配列に分解する
|
|
122
|
+
def application_argv(application)
|
|
123
|
+
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
|
|
124
|
+
# Windows のパスはバックスラッシュを含むため POSIX 分割は使わない
|
|
125
|
+
File.exist?(application) ? [application] : application.split
|
|
115
126
|
else
|
|
116
|
-
|
|
127
|
+
Shellwords.split(application)
|
|
117
128
|
end
|
|
118
129
|
end
|
|
119
130
|
|
|
131
|
+
# '-' で始まるパスがオプションとして解釈されないようにする
|
|
132
|
+
def safe_path(path)
|
|
133
|
+
path.start_with?('-') ? File.join('.', path) : path
|
|
134
|
+
end
|
|
135
|
+
|
|
120
136
|
def execute_explorer_command(directory_path)
|
|
121
|
-
|
|
137
|
+
path = safe_path(directory_path)
|
|
122
138
|
|
|
123
139
|
case RbConfig::CONFIG['host_os']
|
|
124
140
|
when /mswin|mingw|cygwin/
|
|
125
141
|
# Windows
|
|
126
|
-
|
|
142
|
+
run_command(['explorer', path])
|
|
127
143
|
when /darwin/
|
|
128
144
|
# macOS
|
|
129
|
-
|
|
145
|
+
run_command(['open', path])
|
|
130
146
|
else
|
|
131
147
|
# Linux/Unix
|
|
132
|
-
|
|
148
|
+
run_command(['xdg-open', path])
|
|
133
149
|
end
|
|
134
150
|
rescue StandardError => e
|
|
135
151
|
warn "Failed to open directory: #{e.message}"
|
|
@@ -57,7 +57,8 @@ module Rufio
|
|
|
57
57
|
search_results = nil
|
|
58
58
|
Dir.chdir(current_path) do
|
|
59
59
|
escaped_query = Shellwords.escape(search_query)
|
|
60
|
-
|
|
60
|
+
# "--" で検索語がオプション(--pre=コマンド 等)として解釈されるのを防ぐ
|
|
61
|
+
search_results = `rga --line-number --with-filename -- #{escaped_query} . 2>/dev/null`
|
|
61
62
|
end
|
|
62
63
|
|
|
63
64
|
if search_results.empty?
|
data/lib/rufio/terminal_ui.rb
CHANGED
|
@@ -237,7 +237,8 @@ module Rufio
|
|
|
237
237
|
# Windows: IO.select を使わずキューから取得(5ms タイムアウト)。
|
|
238
238
|
def read_next_input_byte
|
|
239
239
|
if windows?
|
|
240
|
-
|
|
240
|
+
# ConPTY ではバイト到着が遅れることがあるため 5ms では ESC 単体と誤判定する
|
|
241
|
+
b = windows_queue_pop_with_timeout(0.025)
|
|
241
242
|
b.nil? ? nil : b.chr(Encoding::BINARY)
|
|
242
243
|
else
|
|
243
244
|
STDIN.read_nonblock(1) rescue nil
|
|
@@ -280,7 +281,15 @@ module Rufio
|
|
|
280
281
|
end
|
|
281
282
|
|
|
282
283
|
# Windows 専用: @input_queue からタイムアウト付きで Integer バイトを取り出す。
|
|
284
|
+
# Ruby 3.2+ は Queue#pop(timeout:) で正確に待機する(sleep ポーリングは
|
|
285
|
+
# Windows のタイマー分解能 約15ms により遅延する)。それ以前はポーリング。
|
|
286
|
+
QUEUE_POP_TIMEOUT_SUPPORTED = Queue.instance_method(:pop).parameters.any? { |_, n| n == :timeout }
|
|
287
|
+
|
|
283
288
|
def windows_queue_pop_with_timeout(timeout_sec)
|
|
289
|
+
if QUEUE_POP_TIMEOUT_SUPPORTED
|
|
290
|
+
return @input_queue.pop(timeout: timeout_sec)
|
|
291
|
+
end
|
|
292
|
+
|
|
284
293
|
deadline = Time.now + timeout_sec
|
|
285
294
|
loop do
|
|
286
295
|
begin
|
|
@@ -344,10 +353,13 @@ module Rufio
|
|
|
344
353
|
# ゲームループパターンのmain_loop(CPU最適化版:フレームスキップ対応)
|
|
345
354
|
# UPDATE → DRAW → RENDER → SLEEP のサイクル
|
|
346
355
|
# 変更がない場合は描画をスキップしてCPU使用率を削減
|
|
356
|
+
MAX_INPUTS_PER_FRAME = 32
|
|
357
|
+
|
|
347
358
|
def main_loop
|
|
348
359
|
# CPU最適化: 固定FPSをやめて、イベントドリブンに変更
|
|
349
360
|
# 最小スリープ時間(入力チェック間隔)
|
|
350
|
-
|
|
361
|
+
# Windows は入力をキュー経由で受けるため、入力遅延を抑えるよう短めにする
|
|
362
|
+
min_sleep_interval = windows? ? 0.008 : 0.0333 # Windows: 約8ms / Unix: 30FPS
|
|
351
363
|
check_interval = 0.1 # バックグラウンドタスクのチェック間隔
|
|
352
364
|
|
|
353
365
|
# Phase 3: Screen/Rendererを初期化
|
|
@@ -402,7 +414,14 @@ module Rufio
|
|
|
402
414
|
|
|
403
415
|
# UPDATE phase - ノンブロッキング入力処理
|
|
404
416
|
# 入力があった場合は再描画が必要
|
|
405
|
-
|
|
417
|
+
# 1フレームで溜まった入力をまとめて処理し、再描画は1回だけにする
|
|
418
|
+
# (1フレーム1キーだとキーリピートに追いつけず操作がもたつく)
|
|
419
|
+
had_input = false
|
|
420
|
+
MAX_INPUTS_PER_FRAME.times do
|
|
421
|
+
break unless handle_input_nonblocking
|
|
422
|
+
had_input = true
|
|
423
|
+
break unless @running
|
|
424
|
+
end
|
|
406
425
|
needs_redraw = true if had_input
|
|
407
426
|
|
|
408
427
|
# バックグラウンドコマンドの完了チェック(0.1秒ごと)
|
data/lib/rufio/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rufio
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- masisz
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: io-console
|