lib/roda/plugins/sinatra_helpers.rb in roda-3.43.1 vs lib/roda/plugins/sinatra_helpers.rb in roda-3.44.0

- old
+ new

@@ -209,10 +209,14 @@ # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. module SinatraHelpers + UTF8_ENCODING = Encoding.find('UTF-8') + ISO88591_ENCODING = Encoding.find('ISO-8859-1') + BINARY_ENCODING = Encoding.find('BINARY') + # Depend on the status_303 plugin. def self.load_dependencies(app, _opts = nil) app.plugin :status_303 end @@ -430,18 +434,36 @@ # Set the Content-Disposition to "attachment" with the specified filename, # instructing the user agents to prompt to save. def attachment(filename = nil, disposition='attachment') if filename - params = "; filename=#{File.basename(filename).inspect}" + param_filename = File.basename(filename) + encoding = param_filename.encoding + + needs_encoding = param_filename.gsub!(/[^ 0-9a-zA-Z!\#$&\+\.\^_`\|~]+/, '-') + params = "; filename=#{param_filename.inspect}" + + if needs_encoding && (encoding == UTF8_ENCODING || encoding == ISO88591_ENCODING) + # File name contains non attr-char characters from RFC 5987 Section 3.2.1 + + encoded_filename = File.basename(filename).force_encoding(BINARY_ENCODING) + # Similar regexp as above, but treat each byte separately, and encode + # space characters, since those aren't allowed in attr-char + encoded_filename.gsub!(/[^0-9a-zA-Z!\#$&\+\.\^_`\|~]/) do |c| + "%%%X" % c.ord + end + + encoded_params = "; filename*=#{encoding.to_s}''#{encoded_filename}" + end + unless @headers["Content-Type"] ext = File.extname(filename) unless ext.empty? content_type(ext) end end end - @headers["Content-Disposition"] = "#{disposition}#{params}" + @headers["Content-Disposition"] = "#{disposition}#{params}#{encoded_params}" end # Whether or not the status is set to 1xx. Returns nil if status not yet set. def informational? @status.between?(100, 199) if @status