Skip to content

Commit d628376

Browse files
satoryuclaude
andcommitted
Scope ZIP64 disabling to save/stream instead of global config
PR #170 fixed #168 by setting Zip.write_zip64_support = false at the top level when docx is required. That mutates global rubyzip state for the whole host application, which can break other code that relies on ZIP64 (flagged by Copilot review on #170). Instead, disable ZIP64 only while writing in #save / #stream via a with_zip64_disabled helper that restores the previous global value in an ensure block, so other rubyzip users are unaffected. Adds regression specs asserting the setting is not leaked after saving/streaming, and a CHANGELOG entry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 16e42c2 commit d628376

2 files changed

Lines changed: 64 additions & 28 deletions

File tree

lib/docx/document.rb

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,6 @@
55
require 'nokogiri'
66
require 'zip'
77

8-
# Disable ZIP64 support to maintain compatibility with the previous default behavior
9-
# (before rubyzip 3.x) and ensure compatibility with all readers. Rubyzip 3.x enables
10-
# ZIP64 by default, but many readers (including pandoc) don't support it for small files.
11-
Zip.write_zip64_support = false
12-
138
module Docx
149
# The Document class wraps around a docx file and provides methods to
1510
# interface with it.
@@ -133,40 +128,44 @@ def to_html
133128
# call-seq:
134129
# save(filepath) => void
135130
def save(path)
136-
update
137-
Zip::OutputStream.open(path) do |out|
138-
zip.each do |entry|
139-
next unless entry.file?
131+
with_zip64_disabled do
132+
update
133+
Zip::OutputStream.open(path) do |out|
134+
zip.each do |entry|
135+
next unless entry.file?
140136

141-
out.put_next_entry(entry.name)
142-
value = @replace[entry.name] || zip.read(entry.name)
137+
out.put_next_entry(entry.name)
138+
value = @replace[entry.name] || zip.read(entry.name)
143139

144-
out.write(value)
145-
end
140+
out.write(value)
141+
end
146142

143+
end
144+
zip.close
147145
end
148-
zip.close
149146
end
150147

151148
# Output entire document as a StringIO object
152149
def stream
153-
update
154-
stream = Zip::OutputStream.write_buffer do |out|
155-
zip.each do |entry|
156-
next unless entry.file?
157-
158-
out.put_next_entry(entry.name)
159-
160-
if @replace[entry.name]
161-
out.write(@replace[entry.name])
162-
else
163-
out.write(zip.read(entry.name))
150+
with_zip64_disabled do
151+
update
152+
stream = Zip::OutputStream.write_buffer do |out|
153+
zip.each do |entry|
154+
next unless entry.file?
155+
156+
out.put_next_entry(entry.name)
157+
158+
if @replace[entry.name]
159+
out.write(@replace[entry.name])
160+
else
161+
out.write(zip.read(entry.name))
162+
end
164163
end
165164
end
166-
end
167165

168-
stream.rewind
169-
stream
166+
stream.rewind
167+
stream
168+
end
170169
end
171170

172171
alias text to_s
@@ -189,6 +188,18 @@ def styles_configuration
189188

190189
private
191190

191+
# rubyzip 3.x enables ZIP64 by default, which breaks readers (e.g. pandoc)
192+
# that don't support it for small files (issue #168). Disable ZIP64 only
193+
# while writing, and restore the previous global value afterwards so we
194+
# don't affect other rubyzip users in the consuming application.
195+
def with_zip64_disabled
196+
previous = Zip.write_zip64_support
197+
Zip.write_zip64_support = false
198+
yield
199+
ensure
200+
Zip.write_zip64_support = previous
201+
end
202+
192203
def load_styles
193204
@styles_xml = @zip.read('word/styles.xml')
194205
@styles = Nokogiri::XML(@styles_xml)

spec/docx/document_spec.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,31 @@
376376

377377
after { File.delete(@new_doc_path) if File.exist?(@new_doc_path) }
378378
end
379+
380+
context 'ZIP64 global configuration' do
381+
before { @doc = Docx::Document.open(@fixtures_path + '/basic.docx') }
382+
383+
around do |example|
384+
previous = Zip.write_zip64_support
385+
example.run
386+
Zip.write_zip64_support = previous
387+
end
388+
389+
it 'does not leak the disabled ZIP64 setting after saving' do
390+
Zip.write_zip64_support = true
391+
@new_doc_path = @fixtures_path + '/new_save.docx'
392+
@doc.save(@new_doc_path)
393+
expect(Zip.write_zip64_support).to eq(true)
394+
end
395+
396+
it 'does not leak the disabled ZIP64 setting after streaming' do
397+
Zip.write_zip64_support = true
398+
@doc.stream
399+
expect(Zip.write_zip64_support).to eq(true)
400+
end
401+
402+
after { File.delete(@new_doc_path) if @new_doc_path && File.exist?(@new_doc_path) }
403+
end
379404
end
380405

381406
describe 'streaming' do

0 commit comments

Comments
 (0)