Repository: rack/rack (Ruby)

Issue:
Rack::Lint warning for invalid to_path might not work
There is supposed to be a check for a body responding to `to_path` but not returning a valid path.
Pipes return `nil` for `to_path` naturally, which can cause servers to crash.

I can't get this check from Rack::Lint triggered though

```
app = Rack::Builder.new do
  use Rack::Lint
  run ->(env) {
    r, w = IO.pipe
    [200, {'content-type' => 'text/html'}, r]
  }
end

run app
```

This example causes a crash within puma


Discussion: I'm not sure whether to open another issue regarding the `to_path` handling.

The spec states:

> If the Body responds to `to_path`, it must return a `String` path for the local file system whose contents are identical to that produced by calling `each`.

The above example uses an `IO.pipe`, which returns two `IO` objects.  `to_path` is defined in `IO`, and it can return `nil` or a `String`.

Hence, should the spec allow `to_path` to return a nil value?
Since `IO#to_path` can return `nil`, I think we should change SPEC to explicitly allow a `nil` return value.
Be happy to submit a PR.  Question - many of the spec 'comments' in `lint.rb` are very long single lines.  I just ran rake spec and rdoc, and rdoc formats the comments properly when line returns are added.  Hence, can line returns be added, making it easier to view in terminal without wrapping?
@MSP-Greg The change to use very long lines (soft wrapping) was deliberate (#2299).  My personal preference is hard wrapping, but I don't have strong feelings in this area.  If you could submit a PR, that would speed things up.  Thanks!
If you have an issue with the line formatting, please open a separate issue to discuss.



Fix (patch):
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4b17e8ac3..8c9dc88f3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file. For info on
 ### SPEC Changes
 
 - Request environment keys must now be strings. [#2310](https://github.com/rack/rack/issues/2310), [@jeremyevans])
+- Add `nil` as a valid return from a Response `body.to_path` ([#2318](https://github.com/rack/rack/pull/2318), [@MSP-Greg])
 
 ### Added
 
diff --git a/SPEC.rdoc b/SPEC.rdoc
index 45cf43e99..386692ebd 100644
--- a/SPEC.rdoc
+++ b/SPEC.rdoc
@@ -235,7 +235,7 @@ The Body must either be consumed or returned. The Body is consumed by optionally
 
 After calling +close+, the Body is considered closed and should not be consumed again. If the original Body is replaced by a new Body, the new Body must also consume the original Body by calling +close+ if possible.
 
-If the Body responds to +to_path+, it must return a +String+ path for the local file system whose contents are identical to that produced by calling +each+; this may be used by the server as an alternative, possibly more efficient way to transport the response. The +to_path+ method does not consume the body.
+If the Body responds to +to_path+, it must return either +nil+ or a +String+. If a +String+ is returned, it must be a path for the local file system whose contents are identical to that produced by calling +each+; this may be used by the server as an alternative, possibly more efficient way to transport the response. The +to_path+ method does not consume the body.
 
 ==== Enumerable Body
 
diff --git a/lib/rack/lint.rb b/lib/rack/lint.rb
index f96469dff..d0f058579 100644
--- a/lib/rack/lint.rb
+++ b/lib/rack/lint.rb
@@ -835,10 +835,14 @@ def close
 
       def verify_to_path
         ##
-        ## If the Body responds to +to_path+, it must return a +String+ path for the local file system whose contents are identical to that produced by calling +each+; this may be used by the server as an alternative, possibly more efficient way to transport the response. The +to_path+ method does not consume the body.
+        ## If the Body responds to +to_path+, it must return either +nil+ or a +String+. If a +String+ is returned, it must be a path for the local file system whose contents are identical to that produced by calling +each+; this may be used by the server as an alternative, possibly more efficient way to transport the response. The +to_path+ method does not consume the body.
         if @body.respond_to?(:to_path)
-          unless ::File.exist? @body.to_path
-            raise LintError, "The file identified by body.to_path does not exist"
+          optional_path = @body.to_path
+
+          if optional_path != nil
+            unless optional_path.is_a?(String) && ::File.exist?(optional_path)
+              raise LintError, "body.to_path must be nil or a path to an existing file"
+            end
           end
         end
       end