[CMF-checkins] SVN: CMF/branches/1.4/ Backported several hand-picked fixes from CMF 1.5 branch, most of them

Sidnei da Silva sidnei at enfoldsystems.com
Fri Sep 2 09:58:14 EDT 2005


Log message for revision 38244:
  Backported several hand-picked fixes from CMF 1.5 branch, most of them
  related to caching and the caching policy manager and a couple other
  ones that I've considered critical by reading the log messages.
  
      - DCWorkflow Guard check now uses utils._checkPermission instead of Zope's
        checkPermission (allows using proxy roles)
  
      - Added check for executable owner and proxy roles to _checkPermission
  
      - Put the DiscussionItem in its container before indexing it.
  
      - CMFCore.utils: The return value from _mergedLocalRoles allowed
        direct manipulation of objects' local role settings since it
        contained references to the actual values instead of copies.
        (http://www.zope.org/Collectors/CMF/376)
  
      - CMFDefault.Image and CMFDefault.File: When calling the constructor,
        any intelligent content type detection would be destroyed by the
        call to initialize Dublin Core values, which would overwrite the
        content_type. It is now preserved, if possible.
        (http://www.zope.org/Collectors/CMF/370)
  
      - CMFCore.FSImage and FSFile: Unlike the current behavior of Zope
        itself, FSImage and FSFile would set a content-length response
        header for 304 (not modified) responses, which should not be
        done according to RFC 2616. It won't do so anymore, but Zope
        itself will still force a content-length header in
        ZServer.HTTPResponse. This misbehavior has been filed as a Zope
        issue (http://www.zope.org/Collectors/Zope/1866).
        (http://www.zope.org/Collectors/CMF/372)
  
      - CMFCore.PortalContent, CMFCore.FSSTXMethod, CMFTopic.Topic,
        CMFDefault.SkinnedFolder: Cache headers from the Caching Policy
        Manager never got set for DTML-based skins due to the way the
        view template __call__ method was
        invoked. (http://www.zope.org/Collectors/CMF/374)
  
      - Make CookieCrumbler set a 'Cache-Control' header, default to
        'private' when a autentication cookie is present.
  
      - Be verbose about what caching policy manager set headers by
        including a 'X-Cache-Headers-Set-By' header.
  
      - Added 'Vary' setting to Caching Policy Manager.
  
      - Provide a __delattr__ method to FSDirectoryView to avoid ZODB
        bloat (http://www.zope.org/Collectors/CMF/316).
  
      - Preserve cache manager associations when customizing
        FSObject-based objects into the ZODB.
  
  

Changed:
  U   CMF/branches/1.4/CHANGES.txt
  U   CMF/branches/1.4/CMFCore/CachingPolicyManager.py
  U   CMF/branches/1.4/CMFCore/CookieCrumbler.py
  U   CMF/branches/1.4/CMFCore/DirectoryView.py
  U   CMF/branches/1.4/CMFCore/FSDTMLMethod.py
  U   CMF/branches/1.4/CMFCore/FSFile.py
  U   CMF/branches/1.4/CMFCore/FSImage.py
  U   CMF/branches/1.4/CMFCore/FSObject.py
  U   CMF/branches/1.4/CMFCore/FSSTXMethod.py
  U   CMF/branches/1.4/CMFCore/PortalContent.py
  U   CMF/branches/1.4/CMFCore/dtml/cachingPolicies.dtml
  U   CMF/branches/1.4/CMFCore/tests/base/dummy.py
  A   CMF/branches/1.4/CMFCore/tests/fake_skins/fake_skin/testDTML.dtml
  A   CMF/branches/1.4/CMFCore/tests/fake_skins/fake_skin/testDTML.dtml.metadata
  U   CMF/branches/1.4/CMFCore/tests/test_CachingPolicyManager.py
  U   CMF/branches/1.4/CMFCore/tests/test_DirectoryView.py
  A   CMF/branches/1.4/CMFCore/tests/test_FSDTMLMethod.py
  U   CMF/branches/1.4/CMFCore/tests/test_FSFile.py
  U   CMF/branches/1.4/CMFCore/tests/test_FSImage.py
  U   CMF/branches/1.4/CMFCore/tests/test_FSPageTemplate.py
  A   CMF/branches/1.4/CMFCore/tests/test_utils.py
  U   CMF/branches/1.4/CMFCore/utils.py
  U   CMF/branches/1.4/CMFDefault/DiscussionItem.py
  U   CMF/branches/1.4/CMFDefault/File.py
  U   CMF/branches/1.4/CMFDefault/Image.py
  U   CMF/branches/1.4/CMFDefault/SkinnedFolder.py
  U   CMF/branches/1.4/CMFDefault/tests/test_Image.py
  U   CMF/branches/1.4/CMFTopic/Topic.py
  U   CMF/branches/1.4/DCWorkflow/Guard.py

-=-
Modified: CMF/branches/1.4/CHANGES.txt
===================================================================
--- CMF/branches/1.4/CHANGES.txt	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CHANGES.txt	2005-09-02 13:58:14 UTC (rev 38244)
@@ -2,11 +2,58 @@
 
   Bug Fixes
 
+    - DCWorkflow Guard check now uses utils._checkPermission instead of Zope's
+      checkPermission (allows using proxy roles)
+
+    - Added check for executable owner and proxy roles to _checkPermission
+
+    - Put the DiscussionItem in its container before indexing it.
+
+    - CMFCore.utils: The return value from _mergedLocalRoles allowed
+      direct manipulation of objects' local role settings since it
+      contained references to the actual values instead of copies.
+      (http://www.zope.org/Collectors/CMF/376)
+
+    - CMFDefault.Image and CMFDefault.File: When calling the constructor,
+      any intelligent content type detection would be destroyed by the
+      call to initialize Dublin Core values, which would overwrite the
+      content_type. It is now preserved, if possible.
+      (http://www.zope.org/Collectors/CMF/370)
+
+    - CMFCore.FSImage and FSFile: Unlike the current behavior of Zope
+      itself, FSImage and FSFile would set a content-length response
+      header for 304 (not modified) responses, which should not be
+      done according to RFC 2616. It won't do so anymore, but Zope
+      itself will still force a content-length header in
+      ZServer.HTTPResponse. This misbehavior has been filed as a Zope
+      issue (http://www.zope.org/Collectors/Zope/1866).
+      (http://www.zope.org/Collectors/CMF/372)
+
+    - CMFCore.PortalContent, CMFCore.FSSTXMethod, CMFTopic.Topic,
+      CMFDefault.SkinnedFolder: Cache headers from the Caching Policy
+      Manager never got set for DTML-based skins due to the way the
+      view template __call__ method was
+      invoked. (http://www.zope.org/Collectors/CMF/374)
+
+    - Make CookieCrumbler set a 'Cache-Control' header, default to
+      'private' when a autentication cookie is present.
+
+    - Be verbose about what caching policy manager set headers by
+      including a 'X-Cache-Headers-Set-By' header.
+
+    - Added 'Vary' setting to Caching Policy Manager.
+
+    - Provide a __delattr__ method to FSDirectoryView to avoid ZODB
+      bloat (http://www.zope.org/Collectors/CMF/316).
+
+    - Preserve cache manager associations when customizing
+      FSObject-based objects into the ZODB.
+
     - CMFCore.FSObject:  Make FSObject-derivatives role managers, so that
       role settings in '.metadata' files work.  Thanks to Dieter Maurer for
       the patch!  (http://www.zope.org/Collectors/CMF/359)
 
-    - DCWorkflow.Scripts:  Usinng inherited manage_main requires wrapping 
+    - DCWorkflow.Scripts:  Usinng inherited manage_main requires wrapping
       for Zope 2.8 compatibility.
 
     - CMFDefault.SyndicationTool: Zope 2.8 raises AttributeError where
@@ -86,16 +133,16 @@
       due to inappropriate borrowing of an unbound method from
       ZopePageTemplate.
 
-    - CMFDefault.DiscussionItem: Workflow did not get notified when a 
+    - CMFDefault.DiscussionItem: Workflow did not get notified when a
       DiscussionItem was added to content.
       (http://zope.org/Collectors/CMF/280)
-  
+
 CMF 1.4.7 (2004/08/11)
 
   New Features
 
-    - Minor featurelet: The "Action Providers" ZMI tab on the portal_actions 
-      tool now links directly to the tools shown 
+    - Minor featurelet: The "Action Providers" ZMI tab on the portal_actions
+      tool now links directly to the tools shown
       (http://zope.org/Collectors/CMF/181)
 
   Bug Fixes
@@ -123,7 +170,7 @@
 
     - CMFCore.PortalFolder: Unlike other content, only Managers were able
       to create PortalFolders using mkdir in FTP. Fixed by inserting
-      missing security declaration for PortalFolder.manage_addFolder 
+      missing security declaration for PortalFolder.manage_addFolder
       (http://zope.org/Collectors/CMF/167)
 
     - Default text format for NewsItems is now structured-text, just like
@@ -135,7 +182,7 @@
       was faulty.
 
     - CMFDefault.DublinCore: Use the portal_metadata tool's 'getPublisher'
-      for the DublinCore 'Publisher' element (thanks to Eric Brown for the 
+      for the DublinCore 'Publisher' element (thanks to Eric Brown for the
       patch).
 
     - CMFDefault.Document: Make Document render compliant XHTML when format
@@ -169,10 +216,10 @@
     - CMFDefault.Document and CMFDefault.Link: PUT() caused improper
       splitting of 'Contributors' metadata header.
       (http://plone.org/collector/3217)
-      
+
     - CMFCore.utils: Introduced contributorsplitter() utility function.
 
-    - CMFCore.PortalFolder: checkIdAvailable() failed to catch 
+    - CMFCore.PortalFolder: checkIdAvailable() failed to catch
       zExceptions.BadRequest.
 
 CMF 1.4.5 (2004/07/08)

Modified: CMF/branches/1.4/CMFCore/CachingPolicyManager.py
===================================================================
--- CMF/branches/1.4/CMFCore/CachingPolicyManager.py	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFCore/CachingPolicyManager.py	2005-09-02 13:58:14 UTC (rev 38244)
@@ -99,6 +99,15 @@
             the "Cache-control" header will be set using 'max_age_secs',
             if passed;  it should be an integer value in seconds.
 
+          - The "Vary" HTTP response headers will be set if a value is 
+            provided. The Vary header is described in RFC 2616. In essence,
+            it instructs caches that respect this header (such as Squid
+            after version 2.4) to distinguish between requests not just by
+            the request URL, but also by values found in the headers showing
+            in the Vary tag. "Vary: Cookie" would force Squid to also take 
+            Cookie headers into account when deciding what cached object to 
+            choose and serve in response to a request.
+
           - Other tokens will be added to the "Cache-control" HTTP response
             header as follows:
 
@@ -117,6 +126,7 @@
                 , no_cache=0
                 , no_store=0
                 , must_revalidate=0
+                , vary=''
                 ):
 
         if not predicate:
@@ -135,6 +145,7 @@
         self._no_cache = int( no_cache )
         self._no_store = int( no_store )
         self._must_revalidate = int( must_revalidate )
+        self._vary = vary
 
     def getPolicyId( self ):
         """
@@ -171,6 +182,11 @@
         """
         return self._must_revalidate
 
+    def getVary( self ):
+        """
+        """
+        return getattr(self, '_vary', '')
+
     def getHeaders( self, expr_context ):
         """
             Does this request match our predicate?  If so, return a
@@ -211,6 +227,9 @@
             if control:
                 headers.append( ( 'Cache-control', ', '.join( control ) ) )
 
+            if self.getVary():
+                headers.append( ( 'Vary', self._vary ) )
+
         return headers
 
 
@@ -266,6 +285,7 @@
                  , no_cache         # boolean (def. 0)
                  , no_store         # boolean (def. 0)
                  , must_revalidate  # boolean (def. 0)
+                 , vary
                  , REQUEST=None
                  ):
         """
@@ -278,6 +298,7 @@
                        , no_cache
                        , no_store
                        , must_revalidate
+                       , vary
                        )
         if REQUEST is not None: 
             REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
@@ -295,6 +316,7 @@
                     , no_cache          # boolean (def. 0)
                     , no_store          # boolean (def. 0)
                     , must_revalidate   # boolean (def. 0)
+                    , vary
                     , REQUEST=None
                     ):
         """
@@ -307,6 +329,7 @@
                           , no_cache
                           , no_store
                           , must_revalidate
+                          , vary
                           )
         if REQUEST is not None: 
             REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
@@ -375,6 +398,7 @@
                   , no_cache
                   , no_store
                   , must_revalidate
+                  , vary
                   ):
         """
             Add a policy to our registry.
@@ -394,6 +418,7 @@
                                                    , no_cache
                                                    , no_store
                                                    , must_revalidate
+                                                   , vary
                                                    )
         idlist = list( self._policy_ids )
         idlist.append( policy_id )
@@ -408,6 +433,7 @@
                      , no_cache
                      , no_store
                      , must_revalidate
+                     , vary
                      ):
         """
             Update a policy in our registry.
@@ -422,6 +448,7 @@
                                                    , no_cache
                                                    , no_store
                                                    , must_revalidate
+                                                   , vary
                                                    )
 
     security.declarePrivate( '_reorderPolicy' )

Modified: CMF/branches/1.4/CMFCore/CookieCrumbler.py
===================================================================
--- CMF/branches/1.4/CMFCore/CookieCrumbler.py	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFCore/CookieCrumbler.py	2005-09-02 13:58:14 UTC (rev 38244)
@@ -68,6 +68,8 @@
                     'label':'Auto-login page ID'},
                    {'id':'logout_page', 'type': 'string', 'mode':'w',
                     'label':'Logout page ID'},
+                   {'id':'cache_header_value', 'type': 'string', 'mode':'w',
+                    'label':'Cache-Control header value'},
                    )
 
     auth_cookie = '__ac'
@@ -76,6 +78,7 @@
     persist_cookie = '__ac_persistent'
     auto_login_page = 'login_form'
     logout_page = 'logged_out'
+    cache_header_value = 'private'
 
     security.declarePrivate('delRequestVar')
     def delRequestVar(self, req, name):
@@ -167,6 +170,12 @@
                 resp.unauthorized = self.unauthorized
                 resp._unauthorized = self._unauthorized
         if attempt != ATTEMPT_NONE:
+            if self.cache_header_value:
+                # we don't want caches to cache the resulting page
+                resp.setHeader('Cache-Control', self.cache_header_value)
+                # demystify this in the response.
+                resp.setHeader('X-Cache-Control-Hdr-Modified-By',
+                               'CookieCrumbler')
             phys_path = self.getPhysicalPath()
             if self.logout_page:
                 # Cookies are in use.

Modified: CMF/branches/1.4/CMFCore/DirectoryView.py
===================================================================
--- CMF/branches/1.4/CMFCore/DirectoryView.py	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFCore/DirectoryView.py	2005-09-02 13:58:14 UTC (rev 38244)
@@ -415,6 +415,11 @@
         d[name] = value
         setattr(d['_real'], name, value)
 
+    def __delattr__(self, name):
+        d = self.__dict__
+        del d[name]
+        delattr(d['_real'], name)
+
     security.declareProtected(ManagePortal, 'manage_propertiesForm')
     manage_propertiesForm = DTMLFile( 'dirview_properties', _dtmldir )
 

Modified: CMF/branches/1.4/CMFCore/FSDTMLMethod.py
===================================================================
--- CMF/branches/1.4/CMFCore/FSDTMLMethod.py	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFCore/FSDTMLMethod.py	2005-09-02 13:58:14 UTC (rev 38244)
@@ -23,7 +23,7 @@
 from OFS.DTMLMethod import DTMLMethod, decapitate, guess_content_type
 from AccessControl.Role import RoleManager
 
-from utils import _dtmldir
+from utils import _dtmldir, _setCacheHeaders
 from CMFCorePermissions import View
 from CMFCorePermissions import ViewManagementScreens
 from CMFCorePermissions import FTPAccess
@@ -153,6 +153,9 @@
             else:
                 c, e=guess_content_type(self.getId(), r)
             RESPONSE.setHeader('Content-Type', c)
+        if RESPONSE is not None:
+            # caching policy manager hook
+            _setCacheHeaders(self, {})
         result = decapitate(r, RESPONSE)
         if not self._cache_namespace_keys:
             self.ZCacheable_set(result)

Modified: CMF/branches/1.4/CMFCore/FSFile.py
===================================================================
--- CMF/branches/1.4/CMFCore/FSFile.py	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFCore/FSFile.py	2005-09-02 13:58:14 UTC (rev 38244)
@@ -90,6 +90,9 @@
         """
         self._updateFromFS()
         data = self._readFile(0)
+        data_len = len(data)
+        last_mod = self._file_mod_time
+        status = 200
         # HTTP If-Modified-Since header handling.
         header=REQUEST.get_header('If-Modified-Since', None)
         if header is not None:
@@ -100,23 +103,31 @@
             # with common servers such as Apache (which can usually
             # understand the screwy date string as a lucky side effect
             # of the way they parse it).
-            try:    mod_since=long(DateTime(header).timeTime())
-            except: mod_since=None
+            try:
+                mod_since=long(DateTime(header).timeTime())
+            except:
+                mod_since=None
+
             if mod_since is not None:
-                last_mod = self._file_mod_time
                 if last_mod > 0 and last_mod <= mod_since:
-                    # Set header values since apache caching will return
-                    # Content-Length of 0 in response if size is not set here
-                    RESPONSE.setHeader('Last-Modified', rfc1123_date(last_mod))
-                    RESPONSE.setHeader('Content-Type', self.content_type)
-                    RESPONSE.setHeader('Content-Length', self.get_size())
-                    RESPONSE.setStatus(304)
-                    return ''
+                    status = 304
+                    data = ''
 
-        RESPONSE.setHeader('Last-Modified', rfc1123_date(self._file_mod_time))
+        #Last-Modified will get stomped on by a cache policy it there is
+        #one set....
+        RESPONSE.setStatus(status)
+        RESPONSE.setHeader('Last-Modified', rfc1123_date(last_mod))
         RESPONSE.setHeader('Content-Type', self.content_type)
-        RESPONSE.setHeader('Content-Length', len(data))
 
+        if status != 304:
+            # Avoid setting content-length for a 304. See RFC 2616.
+            # Zope might still, for better or for worse, set a
+            # content-length header with value "0".
+            RESPONSE.setHeader('Content-Length', data_len)
+
+        #There are 2 Cache Managers which can be in play....
+        #need to decide which to use to determine where the cache headers
+        #are decided on.
         if self.ZCacheable_getManager() is not None:
             self.ZCacheable_set(None)
         else:

Modified: CMF/branches/1.4/CMFCore/FSImage.py
===================================================================
--- CMF/branches/1.4/CMFCore/FSImage.py	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFCore/FSImage.py	2005-09-02 13:58:14 UTC (rev 38244)
@@ -122,8 +122,13 @@
         RESPONSE.setStatus(status)
         RESPONSE.setHeader('Last-Modified', rfc1123_date(last_mod))
         RESPONSE.setHeader('Content-Type', self.content_type)
-        RESPONSE.setHeader('Content-Length', data_len)
 
+        if status != 304:
+            # Avoid setting content-length for a 304. See RFC 2616.
+            # Zope might still, for better or for worse, set a
+            # content-length header with value "0".
+            RESPONSE.setHeader('Content-Length', data_len)
+
         #There are 2 Cache Managers which can be in play....
         #need to decide which to use to determine where the cache headers
         #are decided on.

Modified: CMF/branches/1.4/CMFCore/FSObject.py
===================================================================
--- CMF/branches/1.4/CMFCore/FSObject.py	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFCore/FSObject.py	2005-09-02 13:58:14 UTC (rev 38244)
@@ -78,7 +78,13 @@
         """
 
         obj = self._createZODBClone()
-        
+
+        # Preserve cache manager associations
+        cachemgr_id = self.ZCacheable_getManagerId()
+        if ( cachemgr_id and
+             getattr(obj, 'ZCacheable_setManagerId', None) is not None ):
+            obj.ZCacheable_setManagerId(cachemgr_id)
+
         id = obj.getId()
         fpath = tuple(split(folder_path, '/'))
         portal_skins = getToolByName(self,'portal_skins') 

Modified: CMF/branches/1.4/CMFCore/FSSTXMethod.py
===================================================================
--- CMF/branches/1.4/CMFCore/FSSTXMethod.py	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFCore/FSSTXMethod.py	2005-09-02 13:58:14 UTC (rev 38244)
@@ -130,7 +130,7 @@
         template = getattr( self, 'stxmethod_view', self._default_template )
 
         if getattr( template, 'isDocTemp', 0 ):
-            posargs = ( self, REQUEST )
+            posargs = ( self, REQUEST, RESPONSE )
         else:
             posargs = ()
         

Modified: CMF/branches/1.4/CMFCore/PortalContent.py
===================================================================
--- CMF/branches/1.4/CMFCore/PortalContent.py	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFCore/PortalContent.py	2005-09-02 13:58:14 UTC (rev 38244)
@@ -111,7 +111,7 @@
         '''
         view = _getViewFor(self)
         if getattr(aq_base(view), 'isDocTemp', 0):
-            return view(self, self.REQUEST)
+            return view(self, self.REQUEST, self.REQUEST['RESPONSE'])
         else:
             return view()
 

Modified: CMF/branches/1.4/CMFCore/dtml/cachingPolicies.dtml
===================================================================
--- CMF/branches/1.4/CMFCore/dtml/cachingPolicies.dtml	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFCore/dtml/cachingPolicies.dtml	2005-09-02 13:58:14 UTC (rev 38244)
@@ -82,6 +82,16 @@
        </tr>
 
        <tr valign="top">
+       <th align="right"> Vary </th>
+       <td colspan="3">
+         <input type="text"
+                name="vary"
+                value="&dtml-getVary;"
+                size="40">
+       </td>
+       </tr>
+
+       <tr valign="top">
        <td><br /></td>
        <td colspan="3">
          <input type="submit" name="updatePolicy:method" value=" Change ">
@@ -89,6 +99,7 @@
          <input type="submit" name="movePolicyUp:method" value=" Up ">
          <input type="submit" name="movePolicyDown:method" value=" Down ">
        </td>
+       </tr>
 
        </table>
 
@@ -157,7 +168,7 @@
          <input type="text" name="max_age_secs:int" value="0">
        </td>
 
-       <th align="right"> Most-revalidate? </th>
+       <th align="right"> Must-revalidate? </th>
        <td>
          <input type="checkbox" name="must_revalidate:int"
                               value="1">
@@ -165,6 +176,13 @@
        </tr>
 
        <tr valign="top">
+       <th align="right"> Vary </th>
+       <td colspan="3">
+         <input type="text" name="vary" size="40">
+       </td>
+       </tr>
+
+       <tr valign="top">
        <td><br /></td>
        <td>
          <input type="submit" name="addPolicy:method" value=" Add ">

Modified: CMF/branches/1.4/CMFCore/tests/base/dummy.py
===================================================================
--- CMF/branches/1.4/CMFCore/tests/base/dummy.py	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFCore/tests/base/dummy.py	2005-09-02 13:58:14 UTC (rev 38244)
@@ -216,7 +216,10 @@
     def getRoles(self):
         return ('Authenticated', 'Member')
 
+    def _check_context(self, object):
+        return 1
 
+
 class DummyUserFolder(Implicit):
     """ A dummy User Folder with 2 dummy Users.
     """
@@ -283,3 +286,14 @@
 
     def notifyCreated(self, ob):
         self.test_notified = ob
+
+class DummyCachingManager:
+
+    def getHTTPCachingHeaders( self, content, view_name, keywords, time=None ):
+        return (
+            ('foo', 'Foo'), ('bar', 'Bar'),
+            ('test_path', '/'.join(content.getPhysicalPath())),
+            )
+
+    def getPhysicalPath(self):
+        return ('baz',)

Added: CMF/branches/1.4/CMFCore/tests/fake_skins/fake_skin/testDTML.dtml
===================================================================
--- CMF/branches/1.4/CMFCore/tests/fake_skins/fake_skin/testDTML.dtml	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFCore/tests/fake_skins/fake_skin/testDTML.dtml	2005-09-02 13:58:14 UTC (rev 38244)
@@ -0,0 +1 @@
+<dtml-var expr="REQUEST.get('SERVER_NAME')">


Property changes on: CMF/branches/1.4/CMFCore/tests/fake_skins/fake_skin/testDTML.dtml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: CMF/branches/1.4/CMFCore/tests/fake_skins/fake_skin/testDTML.dtml.metadata
===================================================================
--- CMF/branches/1.4/CMFCore/tests/fake_skins/fake_skin/testDTML.dtml.metadata	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFCore/tests/fake_skins/fake_skin/testDTML.dtml.metadata	2005-09-02 13:58:14 UTC (rev 38244)
@@ -0,0 +1,2 @@
+[default]
+title=Zope Pope

Modified: CMF/branches/1.4/CMFCore/tests/test_CachingPolicyManager.py
===================================================================
--- CMF/branches/1.4/CMFCore/tests/test_CachingPolicyManager.py	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFCore/tests/test_CachingPolicyManager.py	2005-09-02 13:58:14 UTC (rev 38244)
@@ -251,14 +251,14 @@
         self.assertEqual( len( headers ), 0 )
 
         self.assertRaises( KeyError, mgr._updatePolicy
-                         , 'xyzzy', None, None, None, None, None, None )
+                         , 'xyzzy', None, None, None, None, None, None, '' )
         self.assertRaises( KeyError, mgr._removePolicy, 'xyzzy' )
         self.assertRaises( KeyError, mgr._reorderPolicy, 'xyzzy', -1 )
     
     def test_addPolicy( self ):
 
         mgr = self._makeOne()
-        mgr._addPolicy( 'first', 'python:1', None, 0, 0, 0, 0 )
+        mgr._addPolicy( 'first', 'python:1', None, 0, 0, 0, 0, '' )
         headers = mgr.getHTTPCachingHeaders( content=DummyContent(self._epoch)
                                            , view_method='foo_view'
                                            , keywords={}
@@ -283,7 +283,7 @@
         for policy_id in policy_ids:
             mgr._addPolicy( policy_id
                           , 'python:"%s" in keywords.keys()' % policy_id
-                          , None, 0, 0, 0, 0 )
+                          , None, 0, 0, 0, 0, '' )
 
         ids = tuple( map( lambda x: x[0], mgr.listPolicies() ) )
         self.assertEqual( ids, policy_ids )
@@ -306,7 +306,7 @@
         for policy_id, max_age_secs in policy_tuples:
             mgr._addPolicy( policy_id
                           , 'python:"%s" in keywords.keys()' % policy_id
-                          , None, max_age_secs, 0, 0, 0 )
+                          , None, max_age_secs, 0, 0, 0, '' )
 
         return mgr
 

Modified: CMF/branches/1.4/CMFCore/tests/test_DirectoryView.py
===================================================================
--- CMF/branches/1.4/CMFCore/tests/test_DirectoryView.py	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFCore/tests/test_DirectoryView.py	2005-09-02 13:58:14 UTC (rev 38244)
@@ -125,6 +125,31 @@
         # Test that the .test1.py is ignored
         assert('#test1' not in self.ob.fake_skin.objectIds())
 
+    def test_surrogate_writethrough(self):
+        # CMF Collector 316: It is possible to cause ZODB writes because
+        # setting attributes on the non-persistent surrogate writes them
+        # into the persistent DirectoryView as well. This is bad in situations
+        # where you only want to store markers and remove them before the
+        # transaction has ended - they never got removed because there was
+        # no equivalent __delattr__ on the surrogate that would clean up
+        # the persistent DirectoryView as well.
+        fs = self.ob.fake_skin
+        test_foo = 'My Foovalue'
+        fs.foo = test_foo
+
+        self.assertEqual(fs.foo, test_foo)
+        self.assertEqual(fs.__dict__['_real'].foo, test_foo)
+
+        del fs.foo
+
+        self.assertRaises(AttributeError, getattr, fs, 'foo')
+        self.assertRaises( AttributeError
+                         , getattr
+                         , fs.__dict__['_real']
+                         , 'foo'
+                         )
+
+
 if DevelopmentMode:
 
   class DebugModeTests( FSDVTest ):

Added: CMF/branches/1.4/CMFCore/tests/test_FSDTMLMethod.py
===================================================================
--- CMF/branches/1.4/CMFCore/tests/test_FSDTMLMethod.py	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFCore/tests/test_FSDTMLMethod.py	2005-09-02 13:58:14 UTC (rev 38244)
@@ -0,0 +1,130 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+""" Unit tests for FSDTMLMethod module.
+
+$Id$
+"""
+from unittest import TestSuite, makeSuite, main
+import Testing
+try:
+    import Zope2
+except ImportError: # BBB: for Zope 2.7
+    import Zope as Zope2
+Zope2.startup()
+
+from os.path import join as path_join
+
+from OFS.Folder import Folder
+from Products.PageTemplates.TALES import Undefined
+from Products.StandardCacheManagers import RAMCacheManager
+
+from Products.CMFCore.FSDTMLMethod import FSDTMLMethod
+from Products.CMFCore.FSMetadata import FSMetadata
+from Products.CMFCore.tests.base.dummy import DummyCachingManager
+from Products.CMFCore.tests.base.testcase import FSDVTest
+from Products.CMFCore.tests.base.testcase import RequestTest
+from Products.CMFCore.tests.base.testcase import SecurityTest
+
+
+class FSDTMLMaker(FSDVTest):
+
+    def _makeOne( self, id, filename ):
+        path = path_join(self.skin_path_name, filename)
+        metadata = FSMetadata(path)
+        metadata.read()
+        return FSDTMLMethod( id, path, properties=metadata.getProperties() )
+
+
+class FSDTMLMethodTests( RequestTest, FSDTMLMaker ):
+
+    def setUp(self):
+        FSDTMLMaker.setUp(self)
+        RequestTest.setUp(self)
+
+    def tearDown(self):
+        RequestTest.tearDown(self)
+        FSDTMLMaker.tearDown(self)
+
+    def test_Call( self ):
+        script = self._makeOne( 'testDTML', 'testDTML.dtml' )
+        script = script.__of__(self.root)
+        self.assertEqual(script(self.root, self.REQUEST), 'foo\n')
+
+    def test_caching( self ):
+        #   Test HTTP caching headers.
+        self.root.caching_policy_manager = DummyCachingManager()
+        original_len = len( self.RESPONSE.headers )
+        script = self._makeOne('testDTML', 'testDTML.dtml')
+        script = script.__of__(self.root)
+        script(self.root, self.REQUEST, self.RESPONSE)
+        self.failUnless( len( self.RESPONSE.headers ) >= original_len + 2 )
+        self.failUnless( 'foo' in self.RESPONSE.headers.keys() )
+        self.failUnless( 'bar' in self.RESPONSE.headers.keys() )
+
+
+class FSDTMLMethodCustomizationTests( SecurityTest, FSDTMLMaker ):
+
+    def setUp( self ):
+        FSDTMLMaker.setUp(self)
+        SecurityTest.setUp( self )
+
+        self.root._setObject( 'portal_skins', Folder( 'portal_skins' ) )
+        self.skins = self.root.portal_skins
+
+        self.skins._setObject( 'custom', Folder( 'custom' ) )
+        self.custom = self.skins.custom
+
+        self.skins._setObject( 'fsdir', Folder( 'fsdir' ) )
+        self.fsdir = self.skins.fsdir
+
+        self.fsdir._setObject( 'testDTML'
+                             , self._makeOne( 'testDTML', 'testDTML.dtml' ) )
+
+        self.fsDTML = self.fsdir.testDTML
+
+    def test_customize( self ):
+
+        self.fsDTML.manage_doCustomize( folder_path='custom' )
+
+        self.assertEqual( len( self.custom.objectIds() ), 1 )
+        self.failUnless( 'testDTML' in self.custom.objectIds() )
+
+    def test_customize_caching(self):
+        # Test to ensure that cache manager associations survive customizing
+        cache_id = 'gofast'
+        RAMCacheManager.manage_addRAMCacheManager( self.root
+                                                 , cache_id
+                                                 , REQUEST=None
+                                                 )
+        self.fsDTML.ZCacheable_setManagerId(cache_id, REQUEST=None)
+
+        self.assertEqual(self.fsDTML.ZCacheable_getManagerId(), cache_id)
+
+        self.fsDTML.manage_doCustomize(folder_path='custom')
+        custom_pt = self.custom.testDTML
+
+        self.assertEqual(custom_pt.ZCacheable_getManagerId(), cache_id)
+
+    def tearDown(self):
+        SecurityTest.tearDown(self)
+        FSDTMLMaker.tearDown(self)
+
+
+def test_suite():
+    return TestSuite((
+        makeSuite(FSDTMLMethodTests),
+        makeSuite(FSDTMLMethodCustomizationTests),
+        ))
+
+if __name__ == '__main__':
+    main(defaultTest='test_suite')


Property changes on: CMF/branches/1.4/CMFCore/tests/test_FSDTMLMethod.py
___________________________________________________________________
Name: svn:keywords
   + Author Date Id Revision
Name: svn:eol-style
   + native

Modified: CMF/branches/1.4/CMFCore/tests/test_FSFile.py
===================================================================
--- CMF/branches/1.4/CMFCore/tests/test_FSFile.py	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFCore/tests/test_FSFile.py	2005-09-02 13:58:14 UTC (rev 38244)
@@ -1,3 +1,19 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+""" Unit tests for FSFile module.
+
+$Id$
+"""
 import unittest
 import Zope
 import os.path
@@ -2,10 +18,4 @@
 
-class DummyCachingManager:
-    def getHTTPCachingHeaders( self, content, view_name, keywords, time=None ):
-        return (
-            ('foo', 'Foo'), ('bar', 'Bar'),
-            ('test_path', '/'.join(content.getPhysicalPath())),
-            )
-
 from Products.CMFCore.tests.base.testcase import RequestTest, FSDVTest
+from Products.CMFCore.tests.base.dummy import DummyCachingManager
 
@@ -99,6 +109,9 @@
         data = file.index_html( self.REQUEST, self.RESPONSE )
 
         self.assertEqual( data, '' )
+        # test that we don't supply a content-length
+        self.assertEqual( self.RESPONSE.getHeader('Content-Length'.lower()),
+                                                  None )
         self.assertEqual( self.RESPONSE.getStatus(), 304 )
 
 

Modified: CMF/branches/1.4/CMFCore/tests/test_FSImage.py
===================================================================
--- CMF/branches/1.4/CMFCore/tests/test_FSImage.py	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFCore/tests/test_FSImage.py	2005-09-02 13:58:14 UTC (rev 38244)
@@ -1,3 +1,19 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+""" Unit tests for FSImage module.
+
+$Id$
+"""
 import unittest
 import Zope
 import os.path
@@ -2,10 +18,4 @@
 
-class DummyCachingManager:
-    def getHTTPCachingHeaders( self, content, view_name, keywords, time=None ):
-        return (
-            ('foo', 'Foo'), ('bar', 'Bar'),
-            ('test_path', '/'.join(content.getPhysicalPath())),
-            )
-
 from Products.CMFCore.tests.base.testcase import RequestTest, FSDVTest
+from Products.CMFCore.tests.base.dummy import DummyCachingManager
 
@@ -93,6 +103,9 @@
         data = image.index_html( self.REQUEST, self.RESPONSE )
 
         self.assertEqual( data, '' )
+        # test that we don't supply a content-length
+        self.assertEqual( self.RESPONSE.getHeader('Content-Length'.lower()),
+                                                  None )
         self.assertEqual( self.RESPONSE.getStatus(), 304 )
 
 
@@ -157,6 +170,35 @@
         self.failUnless('bar' in headers.keys())
         self.assertEqual(headers['test_path'], '/test_image')
 
+    def test_index_html_with_304_and_caching( self ):
+
+        # See collector #355
+        self.root.caching_policy_manager = DummyCachingManager()
+        original_len = len(self.RESPONSE.headers)
+        path, ref = self._extractFile()
+
+        import os
+        from webdav.common import rfc1123_date
+
+        mod_time = os.stat( path )[ 8 ]
+
+        image = self._makeOne( 'test_image', 'test_image.gif' )
+        image = image.__of__( self.root )
+
+        self.REQUEST.environ[ 'IF_MODIFIED_SINCE'
+                            ] = '%s;' % rfc1123_date( mod_time+3600 )
+
+        data = image.index_html( self.REQUEST, self.RESPONSE )
+
+        self.assertEqual( data, '' )
+        self.assertEqual( self.RESPONSE.getStatus(), 304 )
+
+        headers = self.RESPONSE.headers
+        self.failUnless(len(headers) >= original_len + 3)
+        self.failUnless('foo' in headers.keys())
+        self.failUnless('bar' in headers.keys())
+        self.assertEqual(headers['test_path'], '/test_image')
+
 def test_suite():
     return unittest.TestSuite((
         unittest.makeSuite(FSImageTests),

Modified: CMF/branches/1.4/CMFCore/tests/test_FSPageTemplate.py
===================================================================
--- CMF/branches/1.4/CMFCore/tests/test_FSPageTemplate.py	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFCore/tests/test_FSPageTemplate.py	2005-09-02 13:58:14 UTC (rev 38244)
@@ -4,11 +4,9 @@
 from Products.CMFCore.tests.base.testcase import RequestTest
 from Products.CMFCore.tests.base.testcase import SecurityTest
 from Products.CMFCore.tests.base.testcase import FSDVTest
+from Products.StandardCacheManagers import RAMCacheManager
+from Products.CMFCore.tests.base.dummy import DummyCachingManager
 
-class DummyCachingManager:
-    def getHTTPCachingHeaders( self, content, view_name, keywords, time=None ):
-        return ( ( 'foo', 'Foo' ), ( 'bar', 'Bar' ) )
-
 class FSPTMaker(FSDVTest):
 
     def _makeOne( self, id, filename ):
@@ -110,6 +108,23 @@
         self.assertEqual( len( self.custom.objectIds() ), 1 )
         self.failUnless( 'testPT' in self.custom.objectIds() )
 
+    def test_customize_caching(self):
+        # Test to ensure that cache manager associations survive customizing
+        cache_id = 'gofast'
+        RAMCacheManager.manage_addRAMCacheManager( self.root
+                                                 , cache_id
+                                                 , REQUEST=None
+                                                 )
+        self.fsPT.ZCacheable_setManagerId(cache_id, REQUEST=None)
+
+        self.assertEqual(self.fsPT.ZCacheable_getManagerId(), cache_id)
+
+        self.fsPT.manage_doCustomize(folder_path='custom')
+        custom_pt = self.custom.testPT
+
+        self.assertEqual(custom_pt.ZCacheable_getManagerId(), cache_id)
+
+
     def test_dontExpandOnCreation( self ):
 
         self.fsPT.manage_doCustomize( folder_path='custom' )

Added: CMF/branches/1.4/CMFCore/tests/test_utils.py
===================================================================
--- CMF/branches/1.4/CMFCore/tests/test_utils.py	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFCore/tests/test_utils.py	2005-09-02 13:58:14 UTC (rev 38244)
@@ -0,0 +1,188 @@
+from unittest import TestSuite, makeSuite, main
+
+import Testing
+import Zope
+try:
+    Zope.startup()
+except AttributeError:
+    # for Zope versions before 2.6.1
+    pass
+
+from AccessControl import getSecurityManager
+from AccessControl.Owned import Owned
+from AccessControl.Permission import Permission
+
+from Products.CMFCore.tests.base.dummy import DummyObject
+from Products.CMFCore.tests.base.dummy import DummySite
+from Products.CMFCore.tests.base.dummy import DummyUserFolder
+from Products.CMFCore.tests.base.testcase import SecurityTest
+from Products.CMFCore.utils import _checkPermission
+
+
+class DummyObject(Owned, DummyObject):
+    pass
+
+
+class CoreUtilsTests(SecurityTest):
+
+    def setUp(self):
+        SecurityTest.setUp(self)
+        self.site = DummySite('site').__of__(self.root)
+        self.site._setObject( 'acl_users', DummyUserFolder() )
+        self.site._setObject('content_dummy', DummyObject(id='content_dummy'))
+        self.site._setObject('actions_dummy', DummyObject(id='actions_dummy'))
+
+    def test__checkPermission(self):
+        o = self.site.actions_dummy
+        Permission('View',(),o).setRoles(('Anonymous',))
+        Permission('WebDAV access',(),o).setRoles(('Authenticated',))
+        Permission('Manage users',(),o).setRoles(('Manager',))
+        eo = self.site.content_dummy
+        eo._owner = (['acl_users'], 'user_foo')
+        getSecurityManager().addContext(eo)
+        self.failUnless( _checkPermission('View', o) )
+        self.failIf( _checkPermission('WebDAV access', o) )
+        self.failIf( _checkPermission('Manage users', o) )
+
+        eo._proxy_roles = ('Authenticated',)
+        self.failIf( _checkPermission('View', o) )
+        self.failUnless( _checkPermission('WebDAV access', o) )
+        self.failIf( _checkPermission('Manage users', o) )
+
+
+def test_suite():
+    return TestSuite((
+        makeSuite(CoreUtilsTests),
+        ))
+
+if __name__ == '__main__':
+    main(defaultTest='test_suite')
+from unittest import TestSuite, makeSuite, main
+import Testing
+try:
+    import Zope2
+except ImportError: # BBB: for Zope 2.7
+    import Zope as Zope2
+Zope2.startup()
+
+from Products.CMFCore.tests.base.testcase import SecurityTest
+
+class CoreUtilsTests(SecurityTest):
+
+    def _makeSite(self):
+        from AccessControl.Owned import Owned
+        from Products.CMFCore.tests.base.dummy import DummySite
+        from Products.CMFCore.tests.base.dummy import DummyUserFolder
+        from Products.CMFCore.tests.base.dummy import DummyObject
+
+        class _DummyObject(Owned, DummyObject):
+            pass
+
+        site = DummySite('site').__of__(self.root)
+        site._setObject( 'acl_users', DummyUserFolder() )
+        site._setObject('content_dummy', _DummyObject(id='content_dummy'))
+        site._setObject('actions_dummy', _DummyObject(id='actions_dummy'))
+
+        return site
+
+    def test__checkPermission(self):
+        from AccessControl import getSecurityManager
+        from AccessControl.Permission import Permission
+        from Products.CMFCore.utils import _checkPermission
+
+        site = self._makeSite()
+        o = site.actions_dummy
+        Permission('View',(),o).setRoles(('Anonymous',))
+        Permission('WebDAV access',(),o).setRoles(('Authenticated',))
+        Permission('Manage users',(),o).setRoles(('Manager',))
+        eo = site.content_dummy
+        eo._owner = (['acl_users'], 'user_foo')
+        getSecurityManager().addContext(eo)
+        self.failUnless( _checkPermission('View', o) )
+        self.failIf( _checkPermission('WebDAV access', o) )
+        self.failIf( _checkPermission('Manage users', o) )
+
+        eo._proxy_roles = ('Authenticated',)
+        self.failIf( _checkPermission('View', o) )
+        self.failUnless( _checkPermission('WebDAV access', o) )
+        self.failIf( _checkPermission('Manage users', o) )
+
+    def test_normalize(self):
+        from Products.CMFCore.utils import normalize
+
+        self.assertEqual( normalize('foo/bar'), 'foo/bar' )
+        self.assertEqual( normalize('foo\\bar'), 'foo/bar' )
+
+    def test_keywordsplitter_empty(self):
+        from Products.CMFCore.utils import keywordsplitter
+
+        for x in [ '', ' ', ',', ',,', ';', ';;' ]:
+            self.assertEqual( keywordsplitter({'Keywords': x}), 
+                              [] )
+
+    def test_keywordsplitter_single(self):
+        from Products.CMFCore.utils import keywordsplitter
+
+        for x in [ 'foo', ' foo ', 'foo,', 'foo ,,', 'foo;', 'foo ;;' ]:
+            self.assertEqual( keywordsplitter({'Keywords': x}), 
+                              ['foo'] )
+
+    def test_keywordsplitter_multi(self):
+        from Products.CMFCore.utils import keywordsplitter
+
+        for x in [ 'foo, bar, baz'
+                 , 'foo, bar , baz'
+                 , 'foo, bar,, baz'
+                 , 'foo; bar; baz'
+                 ]:
+            self.assertEqual( keywordsplitter({'Keywords': x}), 
+                              ['foo', 'bar', 'baz'] )
+
+    def test_contributorsplitter_emtpy(self):
+        from Products.CMFCore.utils import contributorsplitter
+
+        for x in [ '', ' ', ';', ';;' ]:
+            self.assertEqual( contributorsplitter({'Contributors': x}), 
+                              [] )
+
+    def test_contributorsplitter_single(self):
+        from Products.CMFCore.utils import contributorsplitter
+
+        for x in [ 'foo', ' foo ', 'foo;', 'foo ;;' ]:
+            self.assertEqual( contributorsplitter({'Contributors': x}), 
+                              ['foo'] )
+
+    def test_contributorsplitter_multi(self):
+        from Products.CMFCore.utils import contributorsplitter
+
+        for x in [ 'foo; bar; baz'
+                 , 'foo; bar ; baz'
+                 , 'foo; bar;; baz'
+                 ]:
+            self.assertEqual( contributorsplitter({'Contributors': x}), 
+                              ['foo', 'bar', 'baz'] )
+
+    def test_mergedLocalRolesManipulation(self):
+        # The _mergedLocalRoles function used to return references to
+        # actual local role settings and it was possible to manipulate them
+        # by changing the return value. http://www.zope.org/Collectors/CMF/376
+        from Products.CMFCore.tests.base.dummy import DummyContent
+        from Products.CMFCore.utils import _mergedLocalRoles
+        obj = DummyContent()
+        obj.manage_addLocalRoles('dummyuser1', ['Manager', 'Owner'])
+        self.assertEqual(len(obj.get_local_roles_for_userid('dummyuser1')), 2)
+
+        merged_roles = _mergedLocalRoles(obj)
+        merged_roles['dummyuser1'].append('FOO')
+
+        # The values on the object itself should still the the same
+        self.assertEqual(len(obj.get_local_roles_for_userid('dummyuser1')), 2)
+
+
+def test_suite():
+    return TestSuite((
+        makeSuite(CoreUtilsTests),
+        ))
+
+if __name__ == '__main__':
+    main(defaultTest='test_suite')


Property changes on: CMF/branches/1.4/CMFCore/tests/test_utils.py
___________________________________________________________________
Name: svn:keywords
   + Author Date Id Revision
Name: svn:eol-style
   + native

Modified: CMF/branches/1.4/CMFCore/utils.py
===================================================================
--- CMF/branches/1.4/CMFCore/utils.py	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFCore/utils.py	2005-09-02 13:58:14 UTC (rev 38244)
@@ -15,7 +15,8 @@
 from os import path as os_path
 import re
 import operator
-from types import StringType
+from types import StringType, UnicodeType
+from copy import deepcopy
 
 from Globals import package_home
 from Globals import HTMLFile
@@ -25,7 +26,7 @@
 
 from ExtensionClass import Base
 from Acquisition import Implicit
-from Acquisition import aq_get, aq_inner, aq_parent
+from Acquisition import aq_base, aq_get, aq_inner, aq_parent
 
 from AccessControl import ClassSecurityInfo
 from AccessControl import ModuleSecurityInfo
@@ -114,12 +115,31 @@
 security.declarePrivate('_checkPermission')
 def _checkPermission(permission, obj, StringType = type('')):
     roles = rolesForPermissionOn(permission, obj)
-    if type(roles) is StringType:
+    if type(roles) in (StringType, UnicodeType):
         roles=[roles]
-    if _getAuthenticatedUser( obj ).allowed( obj, roles ):
-        return 1
-    return 0
+    context = getSecurityManager()._context
 
+    # check executable owner and proxy roles
+    # this code is ported from ZopeSecurityPolicy.validate
+    stack = context.stack
+    if stack:
+        eo = stack[-1]
+        owner = eo.getOwner()
+        if owner is not None:
+            if not owner.allowed(obj, roles):
+                return 0
+            proxy_roles = getattr(eo, '_proxy_roles', None)
+            if proxy_roles:
+                if obj is not aq_base(obj):
+                    if not owner._check_context(obj):
+                        return 0
+                for r in proxy_roles:
+                    if r in roles:
+                         return 1
+                return 0
+
+    return context.user.allowed(obj, roles)
+
 security.declarePrivate('_verifyActionPermissions')
 def _verifyActionPermissions(obj, action):
     pp = action.getPermissions()
@@ -225,8 +245,9 @@
             object=getattr(object, 'aq_inner', object)
             continue
         break
-    return merged
 
+    return deepcopy(merged)
+
 mergedLocalRoles = _mergedLocalRoles    # XXX: Deprecated spelling
 
 security.declarePrivate('_ac_inherited_permissions')
@@ -301,6 +322,10 @@
             RESPONSE = REQUEST['RESPONSE']
             for key, value in headers:
                 RESPONSE.setHeader(key, value)
+            if headers:
+                RESPONSE.setHeader('X-Cache-Headers-Set-By',
+                                   'CachingPolicyManager: %s' %
+                                   '/'.join(manager.getPhysicalPath()))
 
 class _ViewEmulator(Implicit):
     """Auxiliary class used to adapt FSFile and FSImage

Modified: CMF/branches/1.4/CMFDefault/DiscussionItem.py
===================================================================
--- CMF/branches/1.4/CMFDefault/DiscussionItem.py	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFDefault/DiscussionItem.py	2005-09-02 13:58:14 UTC (rev 38244)
@@ -288,7 +288,7 @@
     #   Discussable interface
     #
     security.declareProtected(ReplyToItem, 'createReply')
-    def createReply( self, title, text, Creator=None ):
+    def createReply( self, title, text, Creator=None, text_format='structured-text' ):
         """
             Create a reply in the proper place
         """
@@ -297,21 +297,21 @@
         id = int(DateTime().timeTime())
         while self._container.get( str(id), None ) is not None:
             id = id + 1
+
         id = str( id )
 
         item = DiscussionItem( id, title=title, description=title )
-        item._edit( text_format='structured-text', text=text )
+        self._container[id] = item
+        item = item.__of__(self)
 
+        item._edit( text_format=text_format, text=text )
         if Creator:
             item.creator = Creator
+        item.indexObject()
 
-        item.__of__( self ).indexObject()
-
         item.setReplyTo( self._getDiscussable() )
-        item.__of__( self ).notifyWorkflowCreated()
+        item.notifyWorkflowCreated()
 
-        self._container[ id ] = item
-
         return id
 
     security.declareProtected(ManagePortal, 'deleteReply')

Modified: CMF/branches/1.4/CMFDefault/File.py
===================================================================
--- CMF/branches/1.4/CMFDefault/File.py	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFDefault/File.py	2005-09-02 13:58:14 UTC (rev 38244)
@@ -147,12 +147,20 @@
                 , contributors=()
                 , effective_date=None
                 , expiration_date=None
-                , format='text/html'
+                , format=None
                 , language='en-US'
                 , rights=''
                 ):
         OFS.Image.File.__init__( self, id, title, file
                                , content_type, precondition )
+
+        # If no file format has been passed in, rely on what OFS.Image.File
+        # detected. Unlike Images, which have code to try and pick the content
+        # type out of the binary data, File objects only provide the correct
+        # type if a "hint" in the form of a filename extension is given.
+        if format is None:
+            format = self.content_type 
+
         DefaultDublinCoreImpl.__init__( self, title, subject, description
                                , contributors, effective_date, expiration_date
                                , format, language, rights )

Modified: CMF/branches/1.4/CMFDefault/Image.py
===================================================================
--- CMF/branches/1.4/CMFDefault/Image.py	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFDefault/Image.py	2005-09-02 13:58:14 UTC (rev 38244)
@@ -139,12 +139,18 @@
                 , contributors=()
                 , effective_date=None
                 , expiration_date=None
-                , format='image/png'
+                , format=None
                 , language='en-US'
                 , rights=''
                 ):
         OFS.Image.File.__init__( self, id, title, file
                                , content_type, precondition )
+
+        # If no file format has been passed in, rely on what OFS.Image.File
+        # detected.
+        if format is None:
+            format = self.content_type
+
         DefaultDublinCoreImpl.__init__( self, title, subject, description
                                , contributors, effective_date, expiration_date
                                , format, language, rights )

Modified: CMF/branches/1.4/CMFDefault/SkinnedFolder.py
===================================================================
--- CMF/branches/1.4/CMFDefault/SkinnedFolder.py	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFDefault/SkinnedFolder.py	2005-09-02 13:58:14 UTC (rev 38244)
@@ -75,7 +75,7 @@
         '''
         view = _getViewFor(self)
         if getattr(aq_base(view), 'isDocTemp', 0):
-            return view(self, self.REQUEST)
+            return view(self, self.REQUEST, self.REQUEST['RESPONSE'])
         else:
             return view()
 

Modified: CMF/branches/1.4/CMFDefault/tests/test_Image.py
===================================================================
--- CMF/branches/1.4/CMFDefault/tests/test_Image.py	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFDefault/tests/test_Image.py	2005-09-02 13:58:14 UTC (rev 38244)
@@ -51,8 +51,28 @@
         image.setFormat('image/gif')
         self.assertEqual(image.Format(), 'image/gif')
         self.assertEqual(image.content_type, 'image/gif')
- 
 
+    def test_ImageContentTypeUponConstruction(self):
+        # Test the content type after calling the constructor with the
+        # file object being passed in (http://www.zope.org/Collectors/CMF/370)
+        testfile = open(TEST_JPG, 'rb')
+        image = Image('testimage', file=testfile)
+        testfile.close()
+        self.assertEqual(image.Format(), 'image/jpeg')
+        self.assertEqual(image.content_type, 'image/jpeg')
+
+    def test_FileContentTypeUponConstruction(self):
+        # Test the content type after calling the constructor with the
+        # file object being passed in (http://www.zope.org/Collectors/CMF/370)
+        testfile = open(TEST_JPG, 'rb')
+        # Notice the cheat? File objects lack the extra intelligence that
+        # picks content types from the actual file data, so it needs to be
+        # helped along with a file extension...
+        file = File('testfile.jpg', file=testfile)
+        testfile.close()
+        self.assertEqual(file.Format(), 'image/jpeg')
+        self.assertEqual(file.content_type, 'image/jpeg')
+
 class TestImageCopyPaste(RequestTest):
 
     # Tests related to http://www.zope.org/Collectors/CMF/176

Modified: CMF/branches/1.4/CMFTopic/Topic.py
===================================================================
--- CMF/branches/1.4/CMFTopic/Topic.py	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/CMFTopic/Topic.py	2005-09-02 13:58:14 UTC (rev 38244)
@@ -117,7 +117,7 @@
         """
         view = _getViewFor( self )
         if getattr( aq_base( view ), 'isDocTemp', 0 ):
-            return view(self, self.REQUEST)
+            return view(self, self.REQUEST, self.REQUEST['RESPONSE'])
         else:
             return view()
 

Modified: CMF/branches/1.4/DCWorkflow/Guard.py
===================================================================
--- CMF/branches/1.4/DCWorkflow/Guard.py	2005-09-02 13:40:29 UTC (rev 38243)
+++ CMF/branches/1.4/DCWorkflow/Guard.py	2005-09-02 13:58:14 UTC (rev 38244)
@@ -24,6 +24,7 @@
 from Acquisition import Explicit
 
 from Products.CMFCore.CMFCorePermissions import ManagePortal
+from Products.CMFCore.utils import _checkPermission
 
 from Expression import Expression, StateChangeInfo, createExprContext
 from utils import _dtmldir
@@ -47,7 +48,7 @@
         if pp:
             found = 0
             for p in pp:
-                if sm.checkPermission(p, ob):
+                if _checkPermission(p, ob):
                     found = 1
                     break
             if not found:
@@ -102,7 +103,7 @@
                 res.append('<br/>')
             res.append('Requires expr:')
             res.append('<code>' + escape(self.expr.text) + '</code>')
-        return join(res, ' ')
+        return ' '.join(res)
 
     def changeFromProperties(self, props):
         '''
@@ -114,12 +115,12 @@
         s = props.get('guard_permissions', None)
         if s:
             res = 1
-            p = map(strip, split(s, ';'))
+            p = [ permission.strip() for permission in s.split(';') ]
             self.permissions = tuple(p)
         s = props.get('guard_roles', None)
         if s:
             res = 1
-            r = map(strip, split(s, ';'))
+            p = [ role.strip() for role in s.split(';') ]
             self.roles = tuple(r)
         s = props.get('guard_expr', None)
         if s:
@@ -131,13 +132,13 @@
     def getPermissionsText(self):
         if not self.permissions:
             return ''
-        return join(self.permissions, '; ')
+        return '; '.join(self.permissions)
 
     security.declareProtected(ManagePortal, 'getRolesText')
     def getRolesText(self):
         if not self.roles:
             return ''
-        return join(self.roles, '; ')
+        return '; '.join(self.roles)
 
     security.declareProtected(ManagePortal, 'getExprText')
     def getExprText(self):



More information about the CMF-checkins mailing list