• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zopefoundation / Zope / 3956162881

pending completion
3956162881

push

github

Michael Howitz
Update to deprecation warning free releases.

4401 of 7036 branches covered (62.55%)

Branch coverage included in aggregate %.

27161 of 31488 relevant lines covered (86.26%)

0.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.5
/src/ZPublisher/utils.py
1
##############################################################################
2
#
3
# Copyright (c) 2002 Zope Foundation and Contributors.
4
#
5
# This software is subject to the provisions of the Zope Public License,
6
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
7
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
8
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
9
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
10
# FOR A PARTICULAR PURPOSE
11
#
12
##############################################################################
13

14
import base64
1✔
15
import logging
1✔
16

17
import transaction
1✔
18
from Acquisition import aq_inner
1✔
19
from Acquisition import aq_parent
1✔
20

21

22
logger = logging.getLogger('ZPublisher')
1✔
23
AC_LOGGER = logging.getLogger('event.AccessControl')
1✔
24

25

26
def recordMetaData(object, request):
1✔
27
    if hasattr(object, 'getPhysicalPath'):
1✔
28
        path = '/'.join(object.getPhysicalPath())
1✔
29
    else:
30
        # Try hard to get the physical path of the object,
31
        # but there are many circumstances where that's not possible.
32
        to_append = ()
1✔
33

34
        if hasattr(object, '__self__') and hasattr(object, '__name__'):
1✔
35
            # object is a Python method.
36
            to_append = (object.__name__,)
1✔
37
            object = object.__self__
1✔
38

39
        while object is not None and not hasattr(object, 'getPhysicalPath'):
1✔
40
            if getattr(object, '__name__', None) is None:
1✔
41
                object = None
1✔
42
                break
1✔
43
            to_append = (object.__name__,) + to_append
1✔
44
            object = aq_parent(aq_inner(object))
1✔
45

46
        if object is not None:
1✔
47
            path = '/'.join(object.getPhysicalPath() + to_append)
1✔
48
        else:
49
            # As Jim would say, "Waaaaaaaa!"
50
            # This may cause problems with virtual hosts
51
            # since the physical path is different from the path
52
            # used to retrieve the object.
53
            path = request.get('PATH_INFO')
1✔
54

55
    T = transaction.get()
1✔
56
    T.note(safe_unicode(path))
1✔
57
    auth_user = request.get('AUTHENTICATED_USER', None)
1✔
58
    if auth_user:
1✔
59
        auth_folder = aq_parent(auth_user)
1✔
60
        if auth_folder is None:
1!
61
            AC_LOGGER.warning(
×
62
                'A user object of type %s has no aq_parent.',
63
                type(auth_user))
64
            auth_path = request.get('AUTHENTICATION_PATH')
×
65
        else:
66
            auth_path = '/'.join(auth_folder.getPhysicalPath()[1:-1])
1✔
67
        user_id = auth_user.getId()
1✔
68
        user_id = safe_unicode(user_id) if user_id else 'None'
1✔
69
        T.setUser(user_id, safe_unicode(auth_path))
1✔
70

71

72
def safe_unicode(value):
1✔
73
    if isinstance(value, str):
1✔
74
        return value
1✔
75
    elif isinstance(value, bytes):
1!
76
        try:
1✔
77
            value = str(value, 'utf-8')
1✔
78
        except UnicodeDecodeError:
1✔
79
            value = value.decode('utf-8', 'replace')
1✔
80
    return value
1✔
81

82

83
def basic_auth_encode(user, password=None):
1✔
84
    # user / password and the return value are of type str
85
    value = user
1✔
86
    if password is not None:
1✔
87
        value = value + ':' + password
1✔
88
    header = b'Basic ' + base64.b64encode(value.encode())
1✔
89
    header = header.decode()
1✔
90
    return header
1✔
91

92

93
def basic_auth_decode(token):
1✔
94
    # token and the return values are of type str
95
    if not token:
1!
96
        return None
×
97
    if not token[:6].lower() == 'basic ':
1!
98
        return None
×
99
    value = token.split()[-1]  # Strip 'Basic '
1✔
100
    plain = base64.b64decode(value).decode()
1✔
101
    user, password = plain.split(':', 1)  # Split at most once
1✔
102
    return (user, password)
1✔
103

104

105
def _string_tuple(value):
1✔
106
    if not value:
1!
107
        return ()
×
108
    return tuple([safe_unicode(element) for element in value])
1✔
109

110

111
def fix_properties(obj, path=None):
1✔
112
    """Fix properties on object.
113

114
    This does two things:
115

116
    1. Make sure lines contain only strings, instead of bytes,
117
       or worse: a combination of strings and bytes.
118
    2. Replace deprecated ulines, utext, utoken, and ustring properties
119
       with their non-unicode variant, using native strings.
120

121
    See https://github.com/zopefoundation/Zope/issues/987
122

123
    Since Zope 5.3, a lines property stores strings instead of bytes.
124
    But there is no migration yet.  (We do that here.)
125
    Result is that getProperty on an already created lines property
126
    will return the old value with bytes, but a newly created lines property
127
    will return strings.  And you might get combinations.
128

129
    Also since Zope 5.3, the ulines property type is deprecated.
130
    You should use a lines property instead.
131
    Same for a few others: utext, utoken, ustring.
132
    The unicode variants are planned to be removed in Zope 6.
133

134
    Intended usage:
135
    app.ZopeFindAndApply(app, apply_func=fix_properties)
136
    """
137
    if path is None:
1!
138
        # When using ZopeFindAndApply, path is always given.
139
        # But we may be called by other code.
140
        if hasattr(object, 'getPhysicalPath'):
1!
141
            path = '/'.join(object.getPhysicalPath())
×
142
        else:
143
            # Some simple object, for example in tests.
144
            # We don't care about the path then, it is only shown in logs.
145
            path = "/dummy"
1✔
146

147
    if not hasattr(obj, "_updateProperty"):
1✔
148
        # Seen with portal_url tool, most items in portal_skins,
149
        # catalog lexicons, workflow states/transitions/variables, etc.
150
        return
1✔
151
    try:
1✔
152
        prop_map = obj.propertyMap()
1✔
153
    except (AttributeError, TypeError, KeyError, ValueError):
1✔
154
        # If getting the property map fails, there is nothing we can do.
155
        # Problems seen in practice:
156
        # - Object does not inherit from PropertyManager,
157
        #   for example 'MountedObject'.
158
        # - Object is a no longer existing skin layer.
159
        logger.warning("Error getting property map from %s", path)
1✔
160
        return
1✔
161

162
    for prop_info in prop_map:
1✔
163
        # Example: {'id': 'title', 'type': 'string', 'mode': 'w'}
164
        prop_id = prop_info.get("id")
1✔
165
        current = obj.getProperty(prop_id)
1✔
166
        if current is None:
1!
167
            continue
×
168
        new_type = prop_type = prop_info.get("type")
1✔
169
        if prop_type == "lines":
1✔
170
            new = _string_tuple(current)
1✔
171
        elif prop_type == "ulines":
1✔
172
            new_type = "lines"
1✔
173
            new = _string_tuple(current)
1✔
174
        elif prop_type == "utokens":
1✔
175
            new_type = "tokens"
1✔
176
            new = _string_tuple(current)
1✔
177
        elif prop_type == "utext":
1✔
178
            new_type = "text"
1✔
179
            new = safe_unicode(current)
1✔
180
        elif prop_type == "ustring":
1!
181
            new_type = "string"
1✔
182
            new = safe_unicode(current)
1✔
183
        else:
184
            continue
×
185
        if prop_type != new_type:
1✔
186
            # Replace with non-unicode variant.
187
            # This could easily lead to:
188
            # Exceptions.BadRequest: Invalid or duplicate property id.
189
            #   obj._delProperty(prop_id)
190
            #   obj._setProperty(prop_id, new, new_type)
191
            # So fix it by using internal details.
192
            for prop in obj._properties:
1!
193
                if prop.get("id") == prop_id:
1✔
194
                    prop["type"] = new_type
1✔
195
                    obj._p_changed = True
1✔
196
                    break
1✔
197
            else:
198
                # This probably cannot happen.
199
                # If it does, we want to know.
200
                logger.warning(
×
201
                    "Could not change property %s from %s to %s for %s",
202
                    prop_id,
203
                    prop_type,
204
                    new_type,
205
                    path,
206
                )
207
                continue
×
208
            obj._updateProperty(prop_id, new)
1✔
209
            logger.info(
1✔
210
                "Changed property %s from %s to %s for %s",
211
                prop_id,
212
                prop_type,
213
                new_type,
214
                path,
215
            )
216
            continue
1✔
217
        if current != new:
1!
218
            obj._updateProperty(prop_id, new)
1✔
219
            logger.info(
1✔
220
                "Changed property %s at %s so value fits the type %s: %r",
221
                prop_id,
222
                path,
223
                prop_type,
224
                new,
225
            )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc