copy_tuner_client 2.1.1 → 2.1.2
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/lib/copy_tuner_client/copyray/rewriter.rb +7 -4
- data/lib/copy_tuner_client/copyray_middleware.rb +17 -8
- data/lib/copy_tuner_client/version.rb +1 -1
- data/spec/copy_tuner_client/copyray/rewriter_spec.rb +16 -0
- data/spec/copy_tuner_client/copyray_middleware_spec.rb +34 -0
- 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: 34fcab16da8e238f416a8d4be1c68212818ae95afb7f82112391517e901fbb8f
|
|
4
|
+
data.tar.gz: dc9a727af71a83e39cb4d4c4aa41a8d1e982244936f294462da8e44b0b57d9fc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: aed1f966e3c35e5d25f82fc96c56ff5532dd0b8d651be0a275f7f6f85f10a0bc58efe6b414ef004140306be70c2c2294c8c1054a112530a32e21070b098f4557
|
|
7
|
+
data.tar.gz: 9179c177ecbc48104ea0b17db2e6adc1f88e8aa4a5dac4eb0366e171543d2f83bc03e160d7f6cadf65ccb4e8664280904b0c06eca2260d222c9463a51449ff04
|
|
@@ -19,7 +19,10 @@ module CopyTunerClient
|
|
|
19
19
|
# NOTE: 戻り値は [html, skipped]。skipped は data-copyray-key を付与できなかったことを表す
|
|
20
20
|
# (巨大DOMでのスキップ・Nokogiri 例外の双方で true)。ミドルウェアがこれを JS に伝え、
|
|
21
21
|
# オーバーレイ非対応である旨をツールバーで案内する。
|
|
22
|
-
|
|
22
|
+
# NOTE: fragment: true は turbo stream など body に差し込む HTML 断片向け。
|
|
23
|
+
# Nokogiri::HTML だと html/body/DOCTYPE ラッパが付いて断片が壊れるため、
|
|
24
|
+
# fragment パーサでラッパを付けずに走査・除去する。
|
|
25
|
+
def rewrite(html, fragment: false)
|
|
23
26
|
# NOTE: ボディが ASCII-8BIT に転落していると UTF-8 の Marker::PREFIX との include? 比較が
|
|
24
27
|
# Encoding::CompatibilityError を投げる(ミドルウェアのボディ連結で非ASCIIバイトを含む
|
|
25
28
|
# ASCII-8BIT チャンクが混じると発生)。実バイト列は本来 UTF-8 なので判定用に UTF-8 とみなす。
|
|
@@ -35,7 +38,7 @@ module CopyTunerClient
|
|
|
35
38
|
# NOTE: 閾値超は Nokogiri を通さず可視トークン除去のみ。skipped=true で編集導線を諦めた旨を伝える。
|
|
36
39
|
return [strip_markers(scannable), true] if scannable.bytesize > MAX_REWRITE_BYTESIZE
|
|
37
40
|
|
|
38
|
-
[rewrite_with_nokogiri(scannable), false]
|
|
41
|
+
[rewrite_with_nokogiri(scannable, fragment: fragment), false]
|
|
39
42
|
rescue StandardError => e
|
|
40
43
|
# NOTE: Copyray は開発支援機能なので、壊れた HTML 等で Nokogiri 処理が落ちても
|
|
41
44
|
# ページを 500 にしない。data-copyray-key 付与(編集導線)は諦め、最低限可視トークンだけ除去する。
|
|
@@ -51,8 +54,8 @@ module CopyTunerClient
|
|
|
51
54
|
scannable.gsub(Marker::SCAN_REGEXP, '')
|
|
52
55
|
end
|
|
53
56
|
|
|
54
|
-
def rewrite_with_nokogiri(scannable)
|
|
55
|
-
doc = Nokogiri::HTML(scannable)
|
|
57
|
+
def rewrite_with_nokogiri(scannable, fragment: false)
|
|
58
|
+
doc = fragment ? Nokogiri::HTML.fragment(scannable) : Nokogiri::HTML(scannable)
|
|
56
59
|
|
|
57
60
|
doc.traverse do |node|
|
|
58
61
|
if node.text?
|
|
@@ -11,18 +11,22 @@ module CopyTunerClient
|
|
|
11
11
|
def call(env)
|
|
12
12
|
CopyTunerClient::TranslationLog.clear
|
|
13
13
|
status, headers, response = @app.call(env)
|
|
14
|
-
if
|
|
14
|
+
if rewritable?(status, headers) && body = response_body(response)
|
|
15
15
|
csp_nonce = env['action_dispatch.content_security_policy_nonce'] || env['secure_headers_content_security_policy_nonce']
|
|
16
16
|
# NOTE: CSS/JS 挿入の前に Rewriter を通す。serialize 後も </body> は必ず出力されるので
|
|
17
17
|
# append_to_html_body の rindex は機能し、CSS/JS タグはトークン非含有なので二重処理も起きない。
|
|
18
18
|
# NOTE: skipped は data-copyray-key を付与できなかったこと(巨大DOM/Nokogiri例外)を表す。
|
|
19
19
|
# JS にこれを伝え、オーバーレイ非対応である旨をツールバーで案内させる。
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
# NOTE: turbo stream はページ断片なので fragment パーサで走査する(html/body ラッパを付けない)。
|
|
21
|
+
turbo_stream = turbo_stream?(headers)
|
|
22
|
+
body, skipped = CopyTunerClient::Copyray::Rewriter.rewrite(body, fragment: turbo_stream)
|
|
23
|
+
# NOTE: ブートストラップ JS はフルページ読み込み時に一度だけ挿入すればよい。turbo stream 断片には
|
|
24
|
+
# 挿入先の </body> も無く、既に初期化済みのページへマージされるだけなので挿入しない。
|
|
25
|
+
body = append_js(body, csp_nonce, skipped: skipped) unless turbo_stream
|
|
22
26
|
content_length = body.bytesize.to_s
|
|
23
27
|
headers['Content-Length'] = content_length
|
|
24
28
|
# maintains compatibility with other middlewares
|
|
25
|
-
if defined?(ActionDispatch::Response::RackBody) && ActionDispatch::Response::RackBody
|
|
29
|
+
if defined?(ActionDispatch::Response::RackBody) && response.is_a?(ActionDispatch::Response::RackBody)
|
|
26
30
|
ActionDispatch::Response.new(status, headers, [body]).to_a
|
|
27
31
|
else
|
|
28
32
|
[status, headers, [body]]
|
|
@@ -65,14 +69,19 @@ module CopyTunerClient
|
|
|
65
69
|
end
|
|
66
70
|
|
|
67
71
|
def file?(headers)
|
|
68
|
-
headers[
|
|
72
|
+
headers['Content-Transfer-Encoding'] == 'binary'
|
|
69
73
|
end
|
|
70
74
|
|
|
71
|
-
def
|
|
75
|
+
def rewritable?(status, headers)
|
|
72
76
|
[200, 422].include?(status) &&
|
|
77
|
+
headers['Content-Type'] &&
|
|
78
|
+
(headers['Content-Type'].include?('text/html') || turbo_stream?(headers)) &&
|
|
79
|
+
!file?(headers)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def turbo_stream?(headers)
|
|
73
83
|
headers['Content-Type'] &&
|
|
74
|
-
|
|
75
|
-
!file?(headers)
|
|
84
|
+
headers['Content-Type'].include?('text/vnd.turbo-stream.html')
|
|
76
85
|
end
|
|
77
86
|
|
|
78
87
|
def response_body(response)
|
|
@@ -202,6 +202,22 @@ describe CopyTunerClient::Copyray::Rewriter do
|
|
|
202
202
|
end
|
|
203
203
|
end
|
|
204
204
|
|
|
205
|
+
context 'fragment: true(turbo stream などの HTML 断片)' do
|
|
206
|
+
let(:html) { %(<turbo-stream action="replace" target="x"><template><p>#{marker('a.b')}Hello</p></template></turbo-stream>) }
|
|
207
|
+
subject(:result) { described_class.rewrite(html, fragment: true).first }
|
|
208
|
+
|
|
209
|
+
it 'html/body ラッパを足さず断片のまま返す' do
|
|
210
|
+
expect(result).not_to include('<html>')
|
|
211
|
+
expect(result).not_to include('<body>')
|
|
212
|
+
expect(result).to start_with('<turbo-stream')
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
it 'template 内の要素に data-copyray-key を付与しトークンを除去する' do
|
|
216
|
+
expect(result).to include('data-copyray-key="a.b"')
|
|
217
|
+
expect(result).not_to match CopyTunerClient::Copyray::Marker::SCAN_REGEXP
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
205
221
|
context '出力にマーカートークンが一切残らない' do
|
|
206
222
|
let(:html) do
|
|
207
223
|
"<html><head><title>#{marker('t')}T</title></head>" \
|
|
@@ -41,6 +41,40 @@ describe CopyTunerClient::CopyrayMiddleware do
|
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
+
context 'turbo stream レスポンスのとき' do
|
|
45
|
+
let(:headers) { { 'Content-Type' => 'text/vnd.turbo-stream.html' } }
|
|
46
|
+
let(:body) { %(<turbo-stream action="replace" target="x"><template><p>#{marker('a.b')}Hello</p></template></turbo-stream>) }
|
|
47
|
+
|
|
48
|
+
before do
|
|
49
|
+
# NOTE: append_js のトップレベル no-op スタブを外し、turbo stream では JS を挿入しないことを検証する。
|
|
50
|
+
allow(middleware).to receive(:append_js).and_call_original
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'マーカーを data-copyray-key 属性に書き換え、トークンを除去する' do
|
|
54
|
+
_status, _headers, response = middleware.call({})
|
|
55
|
+
result = response.join
|
|
56
|
+
|
|
57
|
+
expect(result).to include('data-copyray-key="a.b"')
|
|
58
|
+
expect(result).not_to match CopyTunerClient::Copyray::Marker::SCAN_REGEXP
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'html/body ラッパを付けず turbo-stream 断片のまま返す' do
|
|
62
|
+
_status, _headers, response = middleware.call({})
|
|
63
|
+
result = response.join
|
|
64
|
+
|
|
65
|
+
expect(result).to start_with('<turbo-stream')
|
|
66
|
+
expect(result).not_to include('<body>')
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'ブートストラップ JS を挿入しない(断片なので)' do
|
|
70
|
+
_status, _headers, response = middleware.call({})
|
|
71
|
+
result = response.join
|
|
72
|
+
|
|
73
|
+
expect(result).not_to include('window.CopyTuner')
|
|
74
|
+
expect(result).not_to include('copytuner')
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
44
78
|
context 'HTML 以外のレスポンスのとき' do
|
|
45
79
|
let(:headers) { { 'Content-Type' => 'application/json' } }
|
|
46
80
|
let(:body) { "{\"x\":\"#{marker('a.b')}\"}" }
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: copy_tuner_client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.1.
|
|
4
|
+
version: 2.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- SonicGarden
|
|
@@ -301,7 +301,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
301
301
|
- !ruby/object:Gem::Version
|
|
302
302
|
version: '0'
|
|
303
303
|
requirements: []
|
|
304
|
-
rubygems_version: 4.0.
|
|
304
|
+
rubygems_version: 4.0.15
|
|
305
305
|
specification_version: 4
|
|
306
306
|
summary: Client for the CopyTuner copy management service
|
|
307
307
|
test_files: []
|