Skip to content

Commit 5687863

Browse files
authored
Merge pull request #90 – Fix set_option() of timeout to handle -1 as infinity
#90
2 parents c5ad802 + 2aa8bca commit 5687863

File tree

5 files changed

+129
-25
lines changed

5 files changed

+129
-25
lines changed

CHANGES

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Modules/
3636
* Fix memory leak in whoami
3737
* Fix internal error handling of LDAPControl_to_List()
3838
* Fix two memory leaks and release GIL in encode_assertion_control
39+
* Allow set_option() to set timeouts to infinity
3940
and, thanks to Michael Ströder:
4041
* removed unused code schema.c
4142
* moved code from version.c to ldapmodule.c

Doc/reference/ldap.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@ following option identifiers are defined as constants:
156156
157157
.. py:data:: OPT_NETWORK_TIMEOUT
158158
159+
.. versionchanged:: 3.0
160+
A timeout of ``-1`` resets timeout to infinity.
161+
159162
.. py:data:: OPT_PROTOCOL_VERSION
160163
161164
Sets the LDAP protocol version used for a connection. This is mapped to
@@ -180,6 +183,9 @@ following option identifiers are defined as constants:
180183
181184
.. py:data:: OPT_TIMEOUT
182185
186+
.. versionchanged:: 3.0
187+
A timeout of ``-1`` resets timeout to infinity.
188+
183189
.. py:data:: OPT_URI
184190
185191
.. _ldap-sasl-options:

Modules/options.c

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,20 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value)
140140
if (!PyArg_Parse(value, "d:set_option", &doubleval))
141141
return 0;
142142
if (doubleval >= 0) {
143-
set_timeval_from_double( &tv, doubleval );
143+
set_timeval_from_double( &tv, doubleval );
144+
ptr = &tv;
145+
} else if (doubleval == -1) {
146+
/* -1 is infinity timeout */
147+
tv.tv_sec = -1;
148+
tv.tv_usec = 0;
144149
ptr = &tv;
145150
} else {
146-
ptr = NULL;
151+
PyErr_Format(
152+
PyExc_ValueError,
153+
"timeout must be >= 0 or -1 for infinity, got %d",
154+
option
155+
);
156+
return 0;
147157
}
148158
break;
149159
case LDAP_OPT_SERVER_CONTROLS:
@@ -180,10 +190,9 @@ LDAP_get_option(LDAPObject *self, int option)
180190
struct timeval *tv;
181191
LDAPAPIInfo apiinfo;
182192
LDAPControl **lcs;
183-
LDAPControl *lc;
184193
char *strval;
185-
PyObject *extensions, *v, *tup;
186-
Py_ssize_t i, num_extensions, num_controls;
194+
PyObject *extensions, *v;
195+
Py_ssize_t i, num_extensions;
187196
LDAP *ld;
188197

189198
ld = self ? self->ldap : NULL;
@@ -352,27 +361,8 @@ LDAP_get_option(LDAPObject *self, int option)
352361
if (res != LDAP_OPT_SUCCESS)
353362
return option_error(res, "ldap_get_option");
354363

355-
if (lcs == NULL)
356-
return PyList_New(0);
357-
358-
/* Get the number of controls */
359-
num_controls = 0;
360-
while (lcs[num_controls])
361-
num_controls++;
362-
363-
/* We'll build a list of controls, with each control a tuple */
364-
v = PyList_New(num_controls);
365-
for (i = 0; i < num_controls; i++) {
366-
lc = lcs[i];
367-
tup = Py_BuildValue("(sbs)",
368-
lc->ldctl_oid,
369-
lc->ldctl_iscritical,
370-
lc->ldctl_value.bv_val);
371-
PyList_SET_ITEM(v, i, tup);
372-
}
373-
364+
v = LDAPControls_to_List(lcs);
374365
ldap_controls_free(lcs);
375-
376366
return v;
377367

378368
default:

Tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@
2222
from . import t_ldap_schema_subentry
2323
from . import t_untested_mods
2424
from . import t_ldap_controls_libldap
25+
from . import t_ldap_options

Tests/t_ldap_options.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import os
2+
import unittest
3+
4+
# Switch off processing .ldaprc or ldap.conf before importing _ldap
5+
os.environ['LDAPNOINIT'] = '1'
6+
7+
import ldap
8+
from ldap.controls import RequestControlTuples
9+
from ldap.controls.pagedresults import SimplePagedResultsControl
10+
from ldap.controls.openldap import SearchNoOpControl
11+
from slapdtest import requires_tls
12+
13+
14+
SENTINEL = object()
15+
16+
TEST_CTRL = RequestControlTuples([
17+
# with BER data
18+
SimplePagedResultsControl(criticality=0, size=5, cookie=b'cookie'),
19+
# value-less
20+
SearchNoOpControl(criticality=1),
21+
])
22+
TEST_CTRL_EXPECTED = [
23+
TEST_CTRL[0],
24+
# get_option returns empty bytes
25+
(TEST_CTRL[1][0], TEST_CTRL[1][1], b''),
26+
]
27+
28+
29+
class TestGlobalOptions(unittest.TestCase):
30+
def _check_option(self, option, value, expected=SENTINEL,
31+
nonevalue=None):
32+
old = ldap.get_option(option)
33+
try:
34+
ldap.set_option(option, value)
35+
new = ldap.get_option(option)
36+
if expected is SENTINEL:
37+
self.assertEqual(new, value)
38+
else:
39+
self.assertEqual(new, expected)
40+
finally:
41+
ldap.set_option(option, old if old is not None else nonevalue)
42+
self.assertEqual(ldap.get_option(option), old)
43+
44+
def test_invalid(self):
45+
with self.assertRaises(ValueError):
46+
ldap.get_option(-1)
47+
with self.assertRaises(ValueError):
48+
ldap.set_option(-1, '')
49+
50+
def test_timeout(self):
51+
self._check_option(ldap.OPT_TIMEOUT, 0, nonevalue=-1)
52+
self._check_option(ldap.OPT_TIMEOUT, 10.5, nonevalue=-1)
53+
with self.assertRaises(ValueError):
54+
self._check_option(ldap.OPT_TIMEOUT, -5, nonevalue=-1)
55+
with self.assertRaises(TypeError):
56+
ldap.set_option(ldap.OPT_TIMEOUT, object)
57+
58+
def test_network_timeout(self):
59+
self._check_option(ldap.OPT_NETWORK_TIMEOUT, 0, nonevalue=-1)
60+
self._check_option(ldap.OPT_NETWORK_TIMEOUT, 10.5, nonevalue=-1)
61+
with self.assertRaises(ValueError):
62+
self._check_option(ldap.OPT_NETWORK_TIMEOUT, -5, nonevalue=-1)
63+
64+
def _test_controls(self, option):
65+
self._check_option(option, [])
66+
self._check_option(option, TEST_CTRL, TEST_CTRL_EXPECTED)
67+
self._check_option(option, tuple(TEST_CTRL), TEST_CTRL_EXPECTED)
68+
with self.assertRaises(TypeError):
69+
ldap.set_option(option, object)
70+
71+
with self.assertRaises(TypeError):
72+
# must contain a tuple
73+
ldap.set_option(option, [list(TEST_CTRL[0])])
74+
with self.assertRaises(TypeError):
75+
# data must be bytes or None
76+
ldap.set_option(
77+
option,
78+
[TEST_CTRL[0][0], TEST_CTRL[0][1], u'data']
79+
)
80+
81+
def test_client_controls(self):
82+
self._test_controls(ldap.OPT_CLIENT_CONTROLS)
83+
84+
def test_server_controls(self):
85+
self._test_controls(ldap.OPT_SERVER_CONTROLS)
86+
87+
def test_uri(self):
88+
self._check_option(ldap.OPT_URI, "ldapi:///path/to/socket")
89+
with self.assertRaises(TypeError):
90+
ldap.set_option(ldap.OPT_URI, object)
91+
92+
@requires_tls()
93+
def test_cafile(self):
94+
# None or a distribution or OS-specific path
95+
ldap.get_option(ldap.OPT_X_TLS_CACERTFILE)
96+
97+
def test_readonly(self):
98+
value = ldap.get_option(ldap.OPT_API_INFO)
99+
self.assertIsInstance(value, dict)
100+
with self.assertRaises(ValueError) as e:
101+
ldap.set_option(ldap.OPT_API_INFO, value)
102+
self.assertIn('read-only', str(e.exception))
103+
104+
105+
if __name__ == '__main__':
106+
unittest.main()

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy