|
15 | 15 | #include <xmlsec/crypto.h> |
16 | 16 | #include <xmlsec/errors.h> |
17 | 17 | #include <xmlsec/base64.h> |
| 18 | +#include <xmlsec/io.h> |
18 | 19 |
|
19 | 20 | #define _PYXMLSEC_FREE_NONE 0 |
20 | 21 | #define _PYXMLSEC_FREE_XMLSEC 1 |
@@ -133,6 +134,229 @@ static PyObject* PyXmlSec_PyEnableDebugOutput(PyObject *self, PyObject* args, Py |
133 | 134 | Py_RETURN_NONE; |
134 | 135 | } |
135 | 136 |
|
| 137 | +// NB: This whole thing assumes that the `xmlsec` callbacks are not re-entrant |
| 138 | +// (i.e. that xmlsec won't come across a link in the reference it's processing |
| 139 | +// and try to open that with these callbacks too). |
| 140 | +typedef struct CbList { |
| 141 | + PyObject* match_cb; |
| 142 | + PyObject* open_cb; |
| 143 | + PyObject* read_cb; |
| 144 | + PyObject* close_cb; |
| 145 | + struct CbList* next; |
| 146 | +} CbList; |
| 147 | + |
| 148 | +static CbList* registered_callbacks = NULL; |
| 149 | +static CbList* rcb_tail = NULL; |
| 150 | + |
| 151 | +static void RCBListAppend(CbList* cb_list_item) { |
| 152 | + if (registered_callbacks == NULL) { |
| 153 | + registered_callbacks = cb_list_item; |
| 154 | + } else { |
| 155 | + rcb_tail->next = cb_list_item; |
| 156 | + } |
| 157 | + rcb_tail = cb_list_item; |
| 158 | +} |
| 159 | + |
| 160 | +static void RCBListClear() { |
| 161 | + CbList* cb_list_item = registered_callbacks; |
| 162 | + while (cb_list_item) { |
| 163 | + Py_XDECREF(cb_list_item->match_cb); |
| 164 | + Py_XDECREF(cb_list_item->open_cb); |
| 165 | + Py_XDECREF(cb_list_item->read_cb); |
| 166 | + Py_XDECREF(cb_list_item->close_cb); |
| 167 | + CbList* next = cb_list_item->next; |
| 168 | + free(cb_list_item); |
| 169 | + cb_list_item = next; |
| 170 | + } |
| 171 | + registered_callbacks = NULL; |
| 172 | + rcb_tail = NULL; |
| 173 | +} |
| 174 | + |
| 175 | +// The currently executing set of Python callbacks: |
| 176 | +static CbList* cur_cb_list_item = NULL; |
| 177 | + |
| 178 | +static int PyXmlSec_MatchCB(const char* filename) { |
| 179 | + if (!cur_cb_list_item) { |
| 180 | + cur_cb_list_item = registered_callbacks; |
| 181 | + } |
| 182 | + while (cur_cb_list_item && !cur_cb_list_item->match_cb) { |
| 183 | + // Spool past any default callback placeholders executed since we were |
| 184 | + // last called back: |
| 185 | + cur_cb_list_item = cur_cb_list_item->next; |
| 186 | + } |
| 187 | + PyGILState_STATE state = PyGILState_Ensure(); |
| 188 | + PyObject* args = Py_BuildValue("(y)", filename); |
| 189 | + while (cur_cb_list_item && cur_cb_list_item->match_cb) { |
| 190 | + PyObject* result = PyObject_CallObject(cur_cb_list_item->match_cb, args); |
| 191 | + if (result && PyObject_IsTrue(result)) { |
| 192 | + Py_DECREF(result); |
| 193 | + Py_DECREF(args); |
| 194 | + PyGILState_Release(state); |
| 195 | + return 1; |
| 196 | + } |
| 197 | + cur_cb_list_item = cur_cb_list_item->next; |
| 198 | + } |
| 199 | + // FIXME: why does having this decref of args cause a segfault?! |
| 200 | + Py_DECREF(args); |
| 201 | + PyGILState_Release(state); |
| 202 | + return 0; |
| 203 | +} |
| 204 | + |
| 205 | +static void* PyXmlSec_OpenCB(const char* filename) { |
| 206 | + PyGILState_STATE state = PyGILState_Ensure(); |
| 207 | + |
| 208 | + // NB: Assumes the match callback left the current callback list item in the |
| 209 | + // right place: |
| 210 | + PyObject* args = Py_BuildValue("(y)", filename); |
| 211 | + PyObject* result = PyObject_CallObject(cur_cb_list_item->open_cb, args); |
| 212 | + Py_DECREF(args); |
| 213 | + |
| 214 | + PyGILState_Release(state); |
| 215 | + return result; |
| 216 | +} |
| 217 | + |
| 218 | +static int PyXmlSec_ReadCB(void* context, char* buffer, int len) { |
| 219 | + PyGILState_STATE state = PyGILState_Ensure(); |
| 220 | + |
| 221 | + // NB: Assumes the match callback left the current callback list item in the |
| 222 | + // right place: |
| 223 | + PyObject* py_buffer = PyMemoryView_FromMemory(buffer, (Py_ssize_t) len, PyBUF_WRITE); |
| 224 | + PyObject* args = Py_BuildValue("(OO)", context, py_buffer); |
| 225 | + PyObject* py_bytes_read = PyObject_CallObject(cur_cb_list_item->read_cb, args); |
| 226 | + Py_DECREF(args); |
| 227 | + Py_DECREF(py_buffer); |
| 228 | + int result; |
| 229 | + if (py_bytes_read && PyLong_Check(py_bytes_read)) { |
| 230 | + result = (int)PyLong_AsLong(py_bytes_read); |
| 231 | + Py_DECREF(py_bytes_read); |
| 232 | + } else { |
| 233 | + result = EOF; |
| 234 | + } |
| 235 | + |
| 236 | + PyGILState_Release(state); |
| 237 | + return result; |
| 238 | +} |
| 239 | + |
| 240 | +static int PyXmlSec_CloseCB(void* context) { |
| 241 | + PyGILState_STATE state = PyGILState_Ensure(); |
| 242 | + |
| 243 | + PyObject* args = Py_BuildValue("(O)", context); |
| 244 | + PyObject* result = PyObject_CallObject(cur_cb_list_item->close_cb, args); |
| 245 | + Py_DECREF(args); |
| 246 | + Py_DECREF(context); |
| 247 | + Py_DECREF(result); |
| 248 | + |
| 249 | + PyGILState_Release(state); |
| 250 | + // We reset `cur_cb_list_item` because we've finished processing the set of |
| 251 | + // callbacks that was matched |
| 252 | + cur_cb_list_item = NULL; |
| 253 | + return 0; |
| 254 | +} |
| 255 | + |
| 256 | +static char PyXmlSec_PyIOCleanupCallbacks__doc__[] = \ |
| 257 | + "Unregister globally all sets of IO callbacks from xmlsec."; |
| 258 | +static PyObject* PyXmlSec_PyIOCleanupCallbacks(PyObject *self) { |
| 259 | + xmlSecIOCleanupCallbacks(); |
| 260 | + // We always have callbacks registered to delegate to any Python callbacks |
| 261 | + // we have registered within these bindings: |
| 262 | + if (xmlSecIORegisterCallbacks( |
| 263 | + PyXmlSec_MatchCB, PyXmlSec_OpenCB, PyXmlSec_ReadCB, |
| 264 | + PyXmlSec_CloseCB) < 0) { |
| 265 | + return NULL; |
| 266 | + }; |
| 267 | + RCBListClear(); |
| 268 | + Py_RETURN_NONE; |
| 269 | +} |
| 270 | + |
| 271 | +static char PyXmlSec_PyIORegisterDefaultCallbacks__doc__[] = \ |
| 272 | + "Register globally xmlsec's own default set of IO callbacks."; |
| 273 | +static PyObject* PyXmlSec_PyIORegisterDefaultCallbacks(PyObject *self) { |
| 274 | + if (xmlSecIORegisterDefaultCallbacks() < 0) { |
| 275 | + return NULL; |
| 276 | + } |
| 277 | + // We place a nulled item on the callback list to represent whenever the |
| 278 | + // default callbacks are going to be invoked: |
| 279 | + CbList* cb_list_item = malloc(sizeof(CbList)); |
| 280 | + if (cb_list_item == NULL) { |
| 281 | + return NULL; |
| 282 | + } |
| 283 | + cb_list_item->match_cb = NULL; |
| 284 | + cb_list_item->open_cb = NULL; |
| 285 | + cb_list_item->read_cb = NULL; |
| 286 | + cb_list_item->close_cb = NULL; |
| 287 | + cb_list_item->next = NULL; |
| 288 | + RCBListAppend(cb_list_item); |
| 289 | + // We need to make sure we can continue trying to match futher Python |
| 290 | + // callbacks if the default callback doesn't match: |
| 291 | + if (xmlSecIORegisterCallbacks( |
| 292 | + PyXmlSec_MatchCB, PyXmlSec_OpenCB, PyXmlSec_ReadCB, |
| 293 | + PyXmlSec_CloseCB) < 0) { |
| 294 | + return NULL; |
| 295 | + }; |
| 296 | + Py_RETURN_NONE; |
| 297 | +} |
| 298 | + |
| 299 | +static char PyXmlSec_PyIORegisterCallbacks__doc__[] = \ |
| 300 | + "Register globally a custom set of IO callbacks with xmlsec.\n\n" |
| 301 | + ":param callable input_match_callback: A callable that takes a filename `bytestring` and " |
| 302 | + "returns a boolean as to whether the other callbacks in this set can handle that name.\n" |
| 303 | + ":param callable input_open_callback: A callable that takes a filename and returns some " |
| 304 | + "context object (e.g. a file object) that the remaining callables in this set will be passed " |
| 305 | + "during handling.\n" |
| 306 | + // FIXME: How do we handle failures in ^^ (e.g. can't find the file)? |
| 307 | + ":param callable input_read_callback: A callable that that takes the context object from the " |
| 308 | + "open callback and a buffer, and should fill the buffer with data (e.g. BytesIO.readinto()). " |
| 309 | + "xmlsec will call this function several times until there is no more data returned.\n" |
| 310 | + ":param callable input_close_callback: A callable that takes the context object from the " |
| 311 | + "open callback and can do any resource cleanup necessary.\n" |
| 312 | + ; |
| 313 | +static PyObject* PyXmlSec_PyIORegisterCallbacks(PyObject *self, PyObject *args, PyObject *kwargs) { |
| 314 | + static char *kwlist[] = { |
| 315 | + "input_match_callback", |
| 316 | + "input_open_callback", |
| 317 | + "input_read_callback", |
| 318 | + "input_close_callback", |
| 319 | + NULL |
| 320 | + }; |
| 321 | + CbList* cb_list_item = malloc(sizeof(CbList)); |
| 322 | + if (cb_list_item == NULL) { |
| 323 | + return NULL; |
| 324 | + } |
| 325 | + if (!PyArg_ParseTupleAndKeywords( |
| 326 | + args, kwargs, "OOOO:register_callbacks", kwlist, |
| 327 | + &cb_list_item->match_cb, &cb_list_item->open_cb, &cb_list_item->read_cb, |
| 328 | + &cb_list_item->close_cb)) { |
| 329 | + free(cb_list_item); |
| 330 | + return NULL; |
| 331 | + } |
| 332 | + if (!PyCallable_Check(cb_list_item->match_cb)) { |
| 333 | + PyErr_SetString(PyExc_TypeError, "input_match_callback must be a callable"); |
| 334 | + return NULL; |
| 335 | + } |
| 336 | + if (!PyCallable_Check(cb_list_item->open_cb)) { |
| 337 | + PyErr_SetString(PyExc_TypeError, "input_open_callback must be a callable"); |
| 338 | + return NULL; |
| 339 | + } |
| 340 | + if (!PyCallable_Check(cb_list_item->read_cb)) { |
| 341 | + PyErr_SetString(PyExc_TypeError, "input_read_callback must be a callable"); |
| 342 | + return NULL; |
| 343 | + } |
| 344 | + if (!PyCallable_Check(cb_list_item->close_cb)) { |
| 345 | + PyErr_SetString(PyExc_TypeError, "input_close_callback must be a callable"); |
| 346 | + return NULL; |
| 347 | + } |
| 348 | + Py_INCREF(cb_list_item->match_cb); |
| 349 | + Py_INCREF(cb_list_item->open_cb); |
| 350 | + Py_INCREF(cb_list_item->read_cb); |
| 351 | + Py_INCREF(cb_list_item->close_cb); |
| 352 | + cb_list_item->next = NULL; |
| 353 | + RCBListAppend(cb_list_item); |
| 354 | + // NB: We don't need to register the callbacks with `xmlsec` here, because |
| 355 | + // we've already registered our helper functions that will trawl through our |
| 356 | + // list of callbacks. |
| 357 | + Py_RETURN_NONE; |
| 358 | +} |
| 359 | + |
136 | 360 | static char PyXmlSec_PyBase64DefaultLineSize__doc__[] = \ |
137 | 361 | "base64_default_line_size(size = None)\n" |
138 | 362 | "Configures the default maximum columns size for base64 encoding.\n\n" |
@@ -181,6 +405,24 @@ static PyMethodDef PyXmlSec_MainMethods[] = { |
181 | 405 | METH_VARARGS|METH_KEYWORDS, |
182 | 406 | PyXmlSec_PyEnableDebugOutput__doc__ |
183 | 407 | }, |
| 408 | + { |
| 409 | + "cleanup_callbacks", |
| 410 | + (PyCFunction)PyXmlSec_PyIOCleanupCallbacks, |
| 411 | + METH_NOARGS, |
| 412 | + PyXmlSec_PyIOCleanupCallbacks__doc__ |
| 413 | + }, |
| 414 | + { |
| 415 | + "register_default_callbacks", |
| 416 | + (PyCFunction)PyXmlSec_PyIORegisterDefaultCallbacks, |
| 417 | + METH_NOARGS, |
| 418 | + PyXmlSec_PyIORegisterDefaultCallbacks__doc__ |
| 419 | + }, |
| 420 | + { |
| 421 | + "register_callbacks", |
| 422 | + (PyCFunction)PyXmlSec_PyIORegisterCallbacks, |
| 423 | + METH_VARARGS|METH_KEYWORDS, |
| 424 | + PyXmlSec_PyIORegisterCallbacks__doc__ |
| 425 | + }, |
184 | 426 | { |
185 | 427 | "base64_default_line_size", |
186 | 428 | (PyCFunction)PyXmlSec_PyBase64DefaultLineSize, |
|
0 commit comments