diff --git a/CHANGES.rst b/CHANGES.rst
index 89abf2fc4..4f9d5e270 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -11,6 +11,9 @@ https://github.com/zopefoundation/Zope/blob/4.x/CHANGES.rst
 5.4 (unreleased)
 ----------------
 
+- Enable WebDAV PUT factories to change a newly created object's ID
+  (`#997 <https://github.com/zopefoundation/Zope/issues/997>`_)
+
 - Fix potential race condition in ``App.version_txt.getZopeVersion``
   (`#999 <https://github.com/zopefoundation/Zope/issues/999>`_)
 
diff --git a/src/webdav/NullResource.py b/src/webdav/NullResource.py
index 97f53af06..331cddc80 100644
--- a/src/webdav/NullResource.py
+++ b/src/webdav/NullResource.py
@@ -184,6 +184,9 @@ class NullResource(Persistent, Implicit, Resource):
                    (ob.__class__, repr(parent), sys.exc_info()[1],)
             raise Unauthorized(sMsg)
 
+        # A PUT factory may have changed the object's ID
+        name = ob.getId() or name
+
         # Delegate actual PUT handling to the new object,
         # SDS: But just *after* it has been stored.
         self.__parent__._setObject(name, ob)

diff --git a/src/webdav/tests/testPUT_factory.py b/src/webdav/tests/testPUT_factory.py
index 36597f19e..79c824569 100644
--- a/src/webdav/tests/testPUT_factory.py
+++ b/src/webdav/tests/testPUT_factory.py
@@ -90,3 +90,20 @@ class TestPUTFactory(unittest.TestCase):
                          'PUT factory should not acquire content')
         # check for the newly created file
         self.assertEqual(str(self.app.A.B.a), 'bar')
+
+    def testPUT_factory_changes_name(self):
+        # A custom PUT factory may want to change the object ID,
+        # for example to remove file name extensions.
+        from OFS.Image import File
+
+        def custom_put_factory(name, typ, body):
+            new_name = 'newname'
+            if not isinstance(body, bytes):
+                body = body.encode('UTF-8')
+            return File(new_name, '', body, content_type=typ)
+        self.app.folder.PUT_factory = custom_put_factory
+
+        request = self.app.REQUEST
+        put = request.traverse('/folder/doc')
+        put(request, request.RESPONSE)
+        self.assertTrue('newname' in self.folder.objectIds())

Webdav _default_PUT_factory not working as expected
### What I did:

After a Zope update (possibly to 5.3 but it could be an earlier one) certain files do not get converted to Zope objects as expected when uploaded via webdav.

### What I expect to happen:
- Files with the extensions 'html' or 'pt' should result in a Page Template object
- Image files (jpeg or png) should result in an Image object.

### What actually happened:
- files with the extension 'dtml' result in DTML Document objects
- all other files result in File objects.

### Where's the problem
When I upload a file, the type is always 'application/octet-stream'. So that is problably what's changed, hence the new, different behaviour.

As a DTML Document is recognized from its file extension, this one still works.

### Workaround
As a workaround other files could also be recognized from their file extension.

### What version of Python and Zope/Addons I am using:
- Operating Systems:
    - MacOS Catalina
    - FreeBSD 12.2
- Python 3.8.6
- Zope 5.3

src/webdav/tests/testPUT_factory.py::TestPUTFactory::testPUT_factory_changes_name
src/webdav/tests/testPUT_factory.py::TestPUTFactory::testCollector2261 src/webdav/tests/testPUT_factory.py::TestPUTFactory::testInsideOutVirtualHosting src/webdav/tests/testPUT_factory.py::TestPUTFactory::testNoVirtualHosting src/webdav/tests/testPUT_factory.py::TestPUTFactory::testSimpleVirtualHosting src/webdav/tests/testPUT_factory.py::TestPUTFactory::testSubfolderInsideOutVirtualHosting src/webdav/tests/testPUT_factory.py::TestPUTFactory::testSubfolderVirtualHosting