Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2019--2025 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU Affero General Public License as published by the Free Software
7 : Foundation; either version 3, or (at your option) any later version.
8 :
9 : TALER is distributed in the hope that it will be useful, but WITHOUT ANY
10 : WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 : A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12 :
13 : You should have received a copy of the GNU General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file taler-merchant-httpd_private-get-orders.c
18 : * @brief implement GET /orders
19 : * @author Christian Grothoff
20 : */
21 : #include "platform.h"
22 : #include "taler-merchant-httpd_private-get-orders.h"
23 : #include <taler_merchant_util.h>
24 : #include <taler/taler_json_lib.h>
25 : #include <taler/taler_dbevents.h>
26 :
27 :
28 : /**
29 : * Sensible bound on TALER_MERCHANTDB_OrderFilter.delta
30 : */
31 : #define MAX_DELTA 1024
32 :
33 :
34 : /**
35 : * A pending GET /orders request.
36 : */
37 : struct TMH_PendingOrder
38 : {
39 :
40 : /**
41 : * Kept in a DLL.
42 : */
43 : struct TMH_PendingOrder *prev;
44 :
45 : /**
46 : * Kept in a DLL.
47 : */
48 : struct TMH_PendingOrder *next;
49 :
50 : /**
51 : * Which connection was suspended.
52 : */
53 : struct MHD_Connection *con;
54 :
55 : /**
56 : * Associated heap node.
57 : */
58 : struct GNUNET_CONTAINER_HeapNode *hn;
59 :
60 : /**
61 : * Which instance is this client polling? This also defines
62 : * which DLL this struct is part of.
63 : */
64 : struct TMH_MerchantInstance *mi;
65 :
66 : /**
67 : * At what time does this request expire? If set in the future, we
68 : * may wait this long for a payment to arrive before responding.
69 : */
70 : struct GNUNET_TIME_Absolute long_poll_timeout;
71 :
72 : /**
73 : * Filter to apply.
74 : */
75 : struct TALER_MERCHANTDB_OrderFilter of;
76 :
77 : /**
78 : * The array of orders.
79 : */
80 : json_t *pa;
81 :
82 : /**
83 : * The name of the instance we are querying for.
84 : */
85 : const char *instance_id;
86 :
87 : /**
88 : * Alias of @a of.summary_filter, but with memory to be released (owner).
89 : */
90 : char *summary_filter;
91 :
92 : /**
93 : * The result after adding the orders (#TALER_EC_NONE for okay, anything else for an error).
94 : */
95 : enum TALER_ErrorCode result;
96 :
97 : /**
98 : * Is the structure in the DLL
99 : */
100 : bool in_dll;
101 : };
102 :
103 :
104 : /**
105 : * Task to timeout pending orders.
106 : */
107 : static struct GNUNET_SCHEDULER_Task *order_timeout_task;
108 :
109 : /**
110 : * Heap for orders in long polling awaiting timeout.
111 : */
112 : static struct GNUNET_CONTAINER_Heap *order_timeout_heap;
113 :
114 :
115 : /**
116 : * We are shutting down (or an instance is being deleted), force resume of all
117 : * GET /orders requests.
118 : *
119 : * @param mi instance to force resuming for
120 : */
121 : void
122 102 : TMH_force_get_orders_resume (struct TMH_MerchantInstance *mi)
123 : {
124 : struct TMH_PendingOrder *po;
125 :
126 102 : while (NULL != (po = mi->po_head))
127 : {
128 0 : GNUNET_assert (po->in_dll);
129 0 : GNUNET_CONTAINER_DLL_remove (mi->po_head,
130 : mi->po_tail,
131 : po);
132 0 : GNUNET_assert (po ==
133 : GNUNET_CONTAINER_heap_remove_root (order_timeout_heap));
134 0 : MHD_resume_connection (po->con);
135 0 : po->in_dll = false;
136 : }
137 102 : if (NULL != mi->po_eh)
138 : {
139 0 : TMH_db->event_listen_cancel (mi->po_eh);
140 0 : mi->po_eh = NULL;
141 : }
142 102 : if (NULL != order_timeout_task)
143 : {
144 2 : GNUNET_SCHEDULER_cancel (order_timeout_task);
145 2 : order_timeout_task = NULL;
146 : }
147 102 : if (NULL != order_timeout_heap)
148 : {
149 2 : GNUNET_CONTAINER_heap_destroy (order_timeout_heap);
150 2 : order_timeout_heap = NULL;
151 : }
152 102 : }
153 :
154 :
155 : /**
156 : * Task run to trigger timeouts on GET /orders requests with long polling.
157 : *
158 : * @param cls unused
159 : */
160 : static void
161 0 : order_timeout (void *cls)
162 : {
163 : struct TMH_PendingOrder *po;
164 : struct TMH_MerchantInstance *mi;
165 :
166 : (void) cls;
167 0 : order_timeout_task = NULL;
168 : while (1)
169 : {
170 0 : po = GNUNET_CONTAINER_heap_peek (order_timeout_heap);
171 0 : if (NULL == po)
172 : {
173 : /* release data structure, we don't need it right now */
174 0 : GNUNET_CONTAINER_heap_destroy (order_timeout_heap);
175 0 : order_timeout_heap = NULL;
176 0 : return;
177 : }
178 0 : if (GNUNET_TIME_absolute_is_future (po->long_poll_timeout))
179 0 : break;
180 0 : GNUNET_assert (po ==
181 : GNUNET_CONTAINER_heap_remove_root (order_timeout_heap));
182 0 : po->hn = NULL;
183 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
184 : "Resuming long polled job due to timeout\n");
185 0 : mi = po->mi;
186 0 : GNUNET_assert (po->in_dll);
187 0 : GNUNET_CONTAINER_DLL_remove (mi->po_head,
188 : mi->po_tail,
189 : po);
190 0 : if ( (NULL == mi->po_head) &&
191 0 : (NULL != mi->po_eh) )
192 : {
193 0 : TMH_db->event_listen_cancel (mi->po_eh);
194 0 : mi->po_eh = NULL;
195 : }
196 0 : po->in_dll = false;
197 0 : MHD_resume_connection (po->con);
198 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
199 : }
200 0 : order_timeout_task = GNUNET_SCHEDULER_add_at (po->long_poll_timeout,
201 : &order_timeout,
202 : NULL);
203 : }
204 :
205 :
206 : /**
207 : * Cleanup our "context", where we stored the JSON array
208 : * we are building for the response.
209 : *
210 : * @param ctx context to clean up, must be a `struct AddOrderState *`
211 : */
212 : static void
213 14 : cleanup (void *ctx)
214 : {
215 14 : struct TMH_PendingOrder *po = ctx;
216 :
217 14 : if (po->in_dll)
218 : {
219 0 : struct TMH_MerchantInstance *mi = po->mi;
220 :
221 0 : GNUNET_CONTAINER_DLL_remove (mi->po_head,
222 : mi->po_tail,
223 : po);
224 : }
225 14 : if (NULL != po->hn)
226 0 : GNUNET_assert (po ==
227 : GNUNET_CONTAINER_heap_remove_node (po->hn));
228 14 : json_decref (po->pa);
229 14 : GNUNET_free (po->summary_filter);
230 14 : GNUNET_free (po);
231 14 : }
232 :
233 :
234 : /**
235 : * Closure for #process_refunds_cb().
236 : */
237 : struct ProcessRefundsClosure
238 : {
239 : /**
240 : * Place where we accumulate the refunds.
241 : */
242 : struct TALER_Amount total_refund_amount;
243 :
244 : /**
245 : * Set to an error code if something goes wrong.
246 : */
247 : enum TALER_ErrorCode ec;
248 : };
249 :
250 :
251 : /**
252 : * Function called with information about a refund.
253 : * It is responsible for summing up the refund amount.
254 : *
255 : * @param cls closure
256 : * @param refund_serial unique serial number of the refund
257 : * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
258 : * @param coin_pub public coin from which the refund comes from
259 : * @param exchange_url URL of the exchange that issued @a coin_pub
260 : * @param rtransaction_id identificator of the refund
261 : * @param reason human-readable explanation of the refund
262 : * @param refund_amount refund amount which is being taken from @a coin_pub
263 : * @param pending true if the this refund was not yet processed by the wallet/exchange
264 : */
265 : static void
266 0 : process_refunds_cb (void *cls,
267 : uint64_t refund_serial,
268 : struct GNUNET_TIME_Timestamp timestamp,
269 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
270 : const char *exchange_url,
271 : uint64_t rtransaction_id,
272 : const char *reason,
273 : const struct TALER_Amount *refund_amount,
274 : bool pending)
275 : {
276 0 : struct ProcessRefundsClosure *prc = cls;
277 :
278 0 : if (GNUNET_OK !=
279 0 : TALER_amount_cmp_currency (&prc->total_refund_amount,
280 : refund_amount))
281 : {
282 : /* Database error, refunds in mixed currency in DB. Not OK! */
283 0 : prc->ec = TALER_EC_GENERIC_DB_INVARIANT_FAILURE;
284 0 : GNUNET_break (0);
285 0 : return;
286 : }
287 0 : GNUNET_assert (0 <=
288 : TALER_amount_add (&prc->total_refund_amount,
289 : &prc->total_refund_amount,
290 : refund_amount));
291 : }
292 :
293 :
294 : /**
295 : * Add order details to our JSON array.
296 : *
297 : * @param cls some closure
298 : * @param orig_order_id the order this is about
299 : * @param order_serial serial ID of the order
300 : * @param creation_time when was the order created
301 : */
302 : static void
303 15 : add_order (void *cls,
304 : const char *orig_order_id,
305 : uint64_t order_serial,
306 : struct GNUNET_TIME_Timestamp creation_time)
307 : {
308 15 : struct TMH_PendingOrder *po = cls;
309 15 : json_t *contract_terms = NULL;
310 : struct TALER_PrivateContractHashP h_contract_terms;
311 : enum GNUNET_DB_QueryStatus qs;
312 15 : char *order_id = NULL;
313 15 : bool refundable = false;
314 : bool paid;
315 : bool wired;
316 15 : struct TALER_MERCHANT_Contract *contract = NULL;
317 15 : int16_t choice_index = -1;
318 :
319 15 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
320 : "Adding order `%s' (%llu) to result set at instance `%s'\n",
321 : orig_order_id,
322 : (unsigned long long) order_serial,
323 : po->instance_id);
324 15 : qs = TMH_db->lookup_order_status_by_serial (TMH_db->cls,
325 : po->instance_id,
326 : order_serial,
327 : &order_id,
328 : &h_contract_terms,
329 : &paid);
330 15 : if (qs < 0)
331 : {
332 0 : GNUNET_break (0);
333 0 : po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
334 2 : return;
335 : }
336 15 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
337 : {
338 : /* Contract terms don't exist, so the order cannot be paid. */
339 3 : paid = false;
340 3 : if (NULL == orig_order_id)
341 : {
342 : /* Got a DB trigger about a new proposal, but it
343 : was already deleted again. Just ignore the event. */
344 2 : return;
345 : }
346 1 : order_id = GNUNET_strdup (orig_order_id);
347 : }
348 :
349 : {
350 : /* First try to find the order in the contracts */
351 : uint64_t os;
352 : bool session_matches;
353 :
354 13 : qs = TMH_db->lookup_contract_terms3 (TMH_db->cls,
355 : po->instance_id,
356 : order_id,
357 : NULL,
358 : &contract_terms,
359 : &os,
360 : &paid,
361 : &wired,
362 : &session_matches,
363 : NULL,
364 : &choice_index);
365 13 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
366 12 : GNUNET_break (os == order_serial);
367 : }
368 13 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
369 : {
370 : /* Might still be unclaimed, so try order table */
371 : struct TALER_MerchantPostDataHashP unused;
372 :
373 1 : paid = false;
374 1 : wired = false;
375 1 : qs = TMH_db->lookup_order (TMH_db->cls,
376 : po->instance_id,
377 : order_id,
378 : NULL,
379 : &unused,
380 : &contract_terms);
381 : }
382 13 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
383 : {
384 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
385 : "Order %llu disappeared during iteration. Skipping.\n",
386 : (unsigned long long) order_serial);
387 0 : goto cleanup;
388 : }
389 13 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
390 : {
391 0 : GNUNET_break (0);
392 0 : po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
393 0 : goto cleanup;
394 : }
395 :
396 13 : contract = TALER_MERCHANT_contract_parse (contract_terms,
397 : true);
398 13 : if (NULL == contract)
399 : {
400 0 : GNUNET_break (0);
401 0 : po->result = TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID;
402 0 : goto cleanup;
403 : }
404 :
405 13 : if (GNUNET_TIME_absolute_is_future (
406 0 : contract->refund_deadline.abs_time) &&
407 : paid)
408 : {
409 0 : struct ProcessRefundsClosure prc = {
410 : .ec = TALER_EC_NONE
411 : };
412 : const struct TALER_Amount *brutto;
413 :
414 0 : switch (contract->version)
415 : {
416 0 : case TALER_MERCHANT_CONTRACT_VERSION_0:
417 0 : brutto = &contract->details.v0.brutto;
418 0 : break;
419 0 : case TALER_MERCHANT_CONTRACT_VERSION_1:
420 : {
421 0 : struct TALER_MERCHANT_ContractChoice *choice
422 0 : = &contract->details.v1.choices[choice_index];
423 :
424 0 : GNUNET_assert (choice_index < contract->details.v1.choices_len);
425 0 : brutto = &choice->amount;
426 : }
427 0 : break;
428 0 : default:
429 0 : GNUNET_break (0);
430 0 : goto cleanup;
431 : }
432 :
433 0 : GNUNET_assert (GNUNET_OK ==
434 : TALER_amount_set_zero (brutto->currency,
435 : &prc.total_refund_amount));
436 0 : qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
437 : po->instance_id,
438 : &h_contract_terms,
439 : &process_refunds_cb,
440 : &prc);
441 0 : if (0 > qs)
442 : {
443 0 : GNUNET_break (0);
444 0 : po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
445 0 : goto cleanup;
446 : }
447 0 : if (TALER_EC_NONE != prc.ec)
448 : {
449 0 : GNUNET_break (0);
450 0 : po->result = prc.ec;
451 0 : goto cleanup;
452 : }
453 0 : if (0 > TALER_amount_cmp (&prc.total_refund_amount,
454 : brutto))
455 0 : refundable = true;
456 : }
457 :
458 13 : switch (contract->version)
459 : {
460 13 : case TALER_MERCHANT_CONTRACT_VERSION_0:
461 13 : if (TALER_amount_is_zero (&contract->details.v0.brutto) &&
462 0 : (po->of.wired != TALER_EXCHANGE_YNA_ALL) )
463 : {
464 : /* If we are actually filtering by wire status,
465 : and the order was over an amount of zero,
466 : do not return it as wire status is not
467 : exactly meaningful for orders over zero. */
468 0 : goto cleanup;
469 : }
470 13 : GNUNET_assert (
471 : 0 ==
472 : json_array_append_new (
473 : po->pa,
474 : GNUNET_JSON_PACK (
475 : GNUNET_JSON_pack_string ("order_id",
476 : contract->order_id),
477 : GNUNET_JSON_pack_uint64 ("row_id",
478 : order_serial),
479 : GNUNET_JSON_pack_timestamp ("timestamp",
480 : creation_time),
481 : TALER_JSON_pack_amount (
482 : "amount",
483 : &contract->details.v0.brutto),
484 : GNUNET_JSON_pack_string ("summary",
485 : contract->summary),
486 : GNUNET_JSON_pack_bool ("refundable",
487 : refundable),
488 : GNUNET_JSON_pack_bool ("paid",
489 : paid))));
490 13 : break;
491 0 : case TALER_MERCHANT_CONTRACT_VERSION_1:
492 0 : if (-1 == choice_index)
493 0 : choice_index = 0; /* default choice */
494 0 : GNUNET_assert (choice_index < contract->details.v1.choices_len);
495 : {
496 0 : struct TALER_MERCHANT_ContractChoice *choice
497 0 : = &contract->details.v1.choices[choice_index];
498 :
499 0 : GNUNET_assert (
500 : 0 ==
501 : json_array_append_new (
502 : po->pa,
503 : GNUNET_JSON_PACK (
504 : GNUNET_JSON_pack_string ("order_id",
505 : contract->order_id),
506 : GNUNET_JSON_pack_uint64 ("row_id",
507 : order_serial),
508 : GNUNET_JSON_pack_timestamp ("timestamp",
509 : creation_time),
510 : TALER_JSON_pack_amount ("amount",
511 : &choice->amount),
512 : GNUNET_JSON_pack_string ("summary",
513 : contract->summary),
514 : GNUNET_JSON_pack_bool ("refundable",
515 : refundable),
516 : GNUNET_JSON_pack_bool ("paid",
517 : paid))));
518 : }
519 0 : break;
520 0 : default:
521 0 : GNUNET_break (0);
522 0 : goto cleanup;
523 : }
524 :
525 13 : cleanup:
526 13 : json_decref (contract_terms);
527 13 : GNUNET_free (order_id);
528 13 : if (NULL != contract)
529 : {
530 13 : TALER_MERCHANT_contract_free (contract);
531 13 : contract = NULL;
532 : }
533 : }
534 :
535 :
536 : /**
537 : * We have received a trigger from the database
538 : * that we should (possibly) resume some requests.
539 : *
540 : * @param cls a `struct TMH_MerchantInstance`
541 : * @param extra a `struct TMH_OrderChangeEventP`
542 : * @param extra_size number of bytes in @a extra
543 : */
544 : static void
545 2 : resume_by_event (void *cls,
546 : const void *extra,
547 : size_t extra_size)
548 : {
549 2 : struct TMH_MerchantInstance *mi = cls;
550 2 : const struct TMH_OrderChangeEventDetailsP *oce = extra;
551 : struct TMH_PendingOrder *pn;
552 : enum TMH_OrderStateFlags osf;
553 : uint64_t order_serial_id;
554 : struct GNUNET_TIME_Timestamp date;
555 :
556 2 : if (sizeof (*oce) != extra_size)
557 : {
558 0 : GNUNET_break (0);
559 0 : return;
560 : }
561 2 : osf = (enum TMH_OrderStateFlags) (int) ntohl (oce->order_state);
562 2 : order_serial_id = GNUNET_ntohll (oce->order_serial_id);
563 2 : date = GNUNET_TIME_timestamp_ntoh (oce->execution_date);
564 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
565 : "Received notification about new order %llu\n",
566 : (unsigned long long) order_serial_id);
567 2 : for (struct TMH_PendingOrder *po = mi->po_head;
568 4 : NULL != po;
569 2 : po = pn)
570 : {
571 2 : pn = po->next;
572 4 : if (! ( ( ((TALER_EXCHANGE_YNA_YES == po->of.paid) ==
573 2 : (0 != (osf & TMH_OSF_PAID))) ||
574 0 : (TALER_EXCHANGE_YNA_ALL == po->of.paid) ) &&
575 2 : ( ((TALER_EXCHANGE_YNA_YES == po->of.refunded) ==
576 2 : (0 != (osf & TMH_OSF_REFUNDED))) ||
577 0 : (TALER_EXCHANGE_YNA_ALL == po->of.refunded) ) &&
578 2 : ( ((TALER_EXCHANGE_YNA_YES == po->of.wired) ==
579 2 : (0 != (osf & TMH_OSF_WIRED))) ||
580 0 : (TALER_EXCHANGE_YNA_ALL == po->of.wired) ) ) )
581 : {
582 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
583 : "Client %p waits on different order type\n",
584 : po);
585 0 : continue;
586 : }
587 2 : if (po->of.delta > 0)
588 : {
589 2 : if (order_serial_id < po->of.start_row)
590 : {
591 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
592 : "Client %p waits on different order row\n",
593 : po);
594 0 : continue;
595 : }
596 2 : if (GNUNET_TIME_timestamp_cmp (date,
597 : <,
598 : po->of.date))
599 : {
600 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
601 : "Client %p waits on different order date\n",
602 : po);
603 0 : continue;
604 : }
605 2 : po->of.delta--;
606 : }
607 : else
608 : {
609 0 : if (order_serial_id > po->of.start_row)
610 : {
611 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
612 : "Client %p waits on different order row\n",
613 : po);
614 0 : continue;
615 : }
616 0 : if (GNUNET_TIME_timestamp_cmp (date,
617 : >,
618 : po->of.date))
619 : {
620 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
621 : "Client %p waits on different order date\n",
622 : po);
623 0 : continue;
624 : }
625 0 : po->of.delta++;
626 : }
627 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
628 : "Waking up client %p!\n",
629 : po);
630 2 : add_order (po,
631 : NULL,
632 : order_serial_id,
633 : date);
634 2 : GNUNET_assert (po->in_dll);
635 2 : GNUNET_CONTAINER_DLL_remove (mi->po_head,
636 : mi->po_tail,
637 : po);
638 2 : po->in_dll = false;
639 2 : GNUNET_assert (po ==
640 : GNUNET_CONTAINER_heap_remove_node (po->hn));
641 2 : po->hn = NULL;
642 2 : MHD_resume_connection (po->con);
643 2 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
644 : }
645 2 : if (NULL == mi->po_head)
646 : {
647 2 : TMH_db->event_listen_cancel (mi->po_eh);
648 2 : mi->po_eh = NULL;
649 : }
650 : }
651 :
652 :
653 : /**
654 : * There has been a change or addition of a new @a order_id. Wake up
655 : * long-polling clients that may have been waiting for this event.
656 : *
657 : * @param mi the instance where the order changed
658 : * @param osf order state flags
659 : * @param date execution date of the order
660 : * @param order_serial_id serial ID of the order in the database
661 : */
662 : void
663 148 : TMH_notify_order_change (struct TMH_MerchantInstance *mi,
664 : enum TMH_OrderStateFlags osf,
665 : struct GNUNET_TIME_Timestamp date,
666 : uint64_t order_serial_id)
667 : {
668 296 : struct TMH_OrderChangeEventDetailsP oce = {
669 148 : .order_serial_id = GNUNET_htonll (order_serial_id),
670 148 : .execution_date = GNUNET_TIME_timestamp_hton (date),
671 148 : .order_state = htonl (osf)
672 : };
673 148 : struct TMH_OrderChangeEventP eh = {
674 148 : .header.type = htons (TALER_DBEVENT_MERCHANT_ORDERS_CHANGE),
675 148 : .header.size = htons (sizeof (eh)),
676 : .merchant_pub = mi->merchant_pub
677 : };
678 :
679 148 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
680 : "Notifying clients of new order %llu at %s\n",
681 : (unsigned long long) order_serial_id,
682 : TALER_B2S (&mi->merchant_pub));
683 148 : TMH_db->event_notify (TMH_db->cls,
684 : &eh.header,
685 : &oce,
686 : sizeof (oce));
687 148 : }
688 :
689 :
690 : /**
691 : * Transforms an (untrusted) input filter into a Postgresql LIKE filter.
692 : * Escapes "%" and "_" in the @a input and adds "%" at the beginning
693 : * and the end to turn the @a input into a suitable Postgresql argument.
694 : *
695 : * @param input text to turn into a substring match expression, or NULL
696 : * @return NULL if @a input was NULL, otherwise transformed @a input
697 : */
698 : static char *
699 14 : tr (const char *input)
700 : {
701 : char *out;
702 : size_t slen;
703 : size_t wpos;
704 :
705 14 : if (NULL == input)
706 14 : return NULL;
707 0 : slen = strlen (input);
708 0 : out = GNUNET_malloc (slen * 2 + 3);
709 0 : wpos = 0;
710 0 : out[wpos++] = '%';
711 0 : for (size_t i = 0; i<slen; i++)
712 : {
713 0 : char c = input[i];
714 :
715 0 : if ( (c == '%') ||
716 : (c == '_') )
717 0 : out[wpos++] = '\\';
718 0 : out[wpos++] = c;
719 : }
720 0 : out[wpos++] = '%';
721 0 : GNUNET_assert (wpos < slen * 2 + 3);
722 0 : return out;
723 : }
724 :
725 :
726 : /**
727 : * Handle a GET "/orders" request.
728 : *
729 : * @param rh context of the handler
730 : * @param connection the MHD connection to handle
731 : * @param[in,out] hc context with further information about the request
732 : * @return MHD result code
733 : */
734 : MHD_RESULT
735 16 : TMH_private_get_orders (const struct TMH_RequestHandler *rh,
736 : struct MHD_Connection *connection,
737 : struct TMH_HandlerContext *hc)
738 : {
739 16 : struct TMH_PendingOrder *po = hc->ctx;
740 : enum GNUNET_DB_QueryStatus qs;
741 :
742 16 : if (NULL != po)
743 : {
744 : /* resumed from long-polling, return answer we already have
745 : in 'hc->ctx' */
746 2 : if (TALER_EC_NONE != po->result)
747 : {
748 0 : GNUNET_break (0);
749 0 : return TALER_MHD_reply_with_error (connection,
750 : MHD_HTTP_INTERNAL_SERVER_ERROR,
751 : po->result,
752 : NULL);
753 : }
754 2 : return TALER_MHD_REPLY_JSON_PACK (
755 : connection,
756 : MHD_HTTP_OK,
757 : GNUNET_JSON_pack_array_incref ("orders",
758 : po->pa));
759 : }
760 14 : po = GNUNET_new (struct TMH_PendingOrder);
761 14 : hc->ctx = po;
762 14 : hc->cc = &cleanup;
763 14 : po->con = connection;
764 14 : po->pa = json_array ();
765 14 : GNUNET_assert (NULL != po->pa);
766 14 : po->instance_id = hc->instance->settings.id;
767 14 : po->mi = hc->instance;
768 :
769 14 : if (! (TALER_MHD_arg_to_yna (connection,
770 : "paid",
771 : TALER_EXCHANGE_YNA_ALL,
772 : &po->of.paid)) )
773 : {
774 0 : GNUNET_break_op (0);
775 0 : return TALER_MHD_reply_with_error (connection,
776 : MHD_HTTP_BAD_REQUEST,
777 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
778 : "paid");
779 : }
780 14 : if (! (TALER_MHD_arg_to_yna (connection,
781 : "refunded",
782 : TALER_EXCHANGE_YNA_ALL,
783 : &po->of.refunded)) )
784 : {
785 0 : GNUNET_break_op (0);
786 0 : return TALER_MHD_reply_with_error (connection,
787 : MHD_HTTP_BAD_REQUEST,
788 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
789 : "refunded");
790 : }
791 14 : if (! (TALER_MHD_arg_to_yna (connection,
792 : "wired",
793 : TALER_EXCHANGE_YNA_ALL,
794 : &po->of.wired)) )
795 : {
796 0 : GNUNET_break_op (0);
797 0 : return TALER_MHD_reply_with_error (connection,
798 : MHD_HTTP_BAD_REQUEST,
799 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
800 : "wired");
801 : }
802 14 : po->of.delta = -20;
803 : /* deprecated in protocol v12 */
804 14 : TALER_MHD_parse_request_snumber (connection,
805 : "delta",
806 : &po->of.delta);
807 : /* since protocol v12 */
808 14 : TALER_MHD_parse_request_snumber (connection,
809 : "limit",
810 : &po->of.delta);
811 14 : if ( (-MAX_DELTA > po->of.delta) ||
812 14 : (po->of.delta > MAX_DELTA) )
813 : {
814 0 : GNUNET_break_op (0);
815 0 : return TALER_MHD_reply_with_error (connection,
816 : MHD_HTTP_BAD_REQUEST,
817 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
818 : "limit");
819 : }
820 : {
821 : const char *date_s_str;
822 :
823 14 : date_s_str = MHD_lookup_connection_value (connection,
824 : MHD_GET_ARGUMENT_KIND,
825 : "date_s");
826 14 : if (NULL == date_s_str)
827 : {
828 14 : if (po->of.delta > 0)
829 2 : po->of.date = GNUNET_TIME_UNIT_ZERO_TS;
830 : else
831 12 : po->of.date = GNUNET_TIME_UNIT_FOREVER_TS;
832 : }
833 : else
834 : {
835 : char dummy;
836 : unsigned long long ll;
837 :
838 0 : if (1 !=
839 0 : sscanf (date_s_str,
840 : "%llu%c",
841 : &ll,
842 : &dummy))
843 : {
844 0 : GNUNET_break_op (0);
845 0 : return TALER_MHD_reply_with_error (connection,
846 : MHD_HTTP_BAD_REQUEST,
847 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
848 : "date_s");
849 : }
850 :
851 0 : po->of.date = GNUNET_TIME_absolute_to_timestamp (
852 : GNUNET_TIME_absolute_from_s (ll));
853 0 : if (GNUNET_TIME_absolute_is_never (po->of.date.abs_time))
854 : {
855 0 : GNUNET_break_op (0);
856 0 : return TALER_MHD_reply_with_error (connection,
857 : MHD_HTTP_BAD_REQUEST,
858 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
859 : "date_s");
860 : }
861 : }
862 : }
863 14 : if (po->of.delta > 0)
864 2 : po->of.start_row = 0;
865 : else
866 12 : po->of.start_row = INT64_MAX;
867 : /* deprecated in protocol v12 */
868 14 : TALER_MHD_parse_request_number (connection,
869 : "start",
870 : &po->of.start_row);
871 : /* since protocol v12 */
872 14 : TALER_MHD_parse_request_number (connection,
873 : "offset",
874 : &po->of.start_row);
875 14 : if (INT64_MAX < po->of.start_row)
876 : {
877 0 : GNUNET_break_op (0);
878 0 : return TALER_MHD_reply_with_error (connection,
879 : MHD_HTTP_BAD_REQUEST,
880 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
881 : "offset");
882 : }
883 14 : po->summary_filter = tr (MHD_lookup_connection_value (connection,
884 : MHD_GET_ARGUMENT_KIND,
885 : "summary_filter"));
886 14 : po->of.summary_filter = po->summary_filter; /* just an (read-only) alias! */
887 : po->of.session_id
888 14 : = MHD_lookup_connection_value (connection,
889 : MHD_GET_ARGUMENT_KIND,
890 : "session_id");
891 : po->of.fulfillment_url
892 14 : = MHD_lookup_connection_value (connection,
893 : MHD_GET_ARGUMENT_KIND,
894 : "fulfillment_url");
895 14 : TALER_MHD_parse_request_timeout (connection,
896 : &po->long_poll_timeout);
897 14 : if (GNUNET_TIME_absolute_is_never (po->long_poll_timeout))
898 : {
899 0 : GNUNET_break_op (0);
900 0 : return TALER_MHD_reply_with_error (connection,
901 : MHD_HTTP_BAD_REQUEST,
902 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
903 : "timeout_ms");
904 : }
905 14 : po->of.timeout = GNUNET_TIME_absolute_get_remaining (po->long_poll_timeout);
906 26 : if ( (0 >= po->of.delta) &&
907 12 : (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) )
908 : {
909 0 : GNUNET_break_op (0);
910 0 : po->of.timeout = GNUNET_TIME_UNIT_ZERO;
911 0 : po->long_poll_timeout = GNUNET_TIME_UNIT_ZERO_ABS;
912 : }
913 :
914 14 : qs = TMH_db->lookup_orders (TMH_db->cls,
915 : po->instance_id,
916 14 : &po->of,
917 : &add_order,
918 : po);
919 14 : if (0 > qs)
920 : {
921 0 : GNUNET_break (0);
922 0 : po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
923 : }
924 14 : if (TALER_EC_NONE != po->result)
925 : {
926 0 : GNUNET_break (0);
927 0 : return TALER_MHD_reply_with_error (connection,
928 : MHD_HTTP_INTERNAL_SERVER_ERROR,
929 : po->result,
930 : NULL);
931 : }
932 20 : if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) &&
933 6 : (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) )
934 : {
935 2 : struct TMH_MerchantInstance *mi = hc->instance;
936 :
937 : /* setup timeout heap (if not yet exists) */
938 2 : if (NULL == order_timeout_heap)
939 : order_timeout_heap
940 2 : = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MIN);
941 2 : po->hn = GNUNET_CONTAINER_heap_insert (order_timeout_heap,
942 : po,
943 : po->long_poll_timeout.abs_value_us);
944 2 : GNUNET_CONTAINER_DLL_insert (mi->po_head,
945 : mi->po_tail,
946 : po);
947 2 : po->in_dll = true;
948 2 : if (NULL == mi->po_eh)
949 : {
950 2 : struct TMH_OrderChangeEventP change_eh = {
951 2 : .header.type = htons (TALER_DBEVENT_MERCHANT_ORDERS_CHANGE),
952 2 : .header.size = htons (sizeof (change_eh)),
953 : .merchant_pub = mi->merchant_pub
954 : };
955 :
956 2 : mi->po_eh = TMH_db->event_listen (TMH_db->cls,
957 : &change_eh.header,
958 2 : GNUNET_TIME_UNIT_FOREVER_REL,
959 : &resume_by_event,
960 : mi);
961 : }
962 2 : MHD_suspend_connection (connection);
963 : {
964 : struct TMH_PendingOrder *pot;
965 :
966 : /* start timeout task */
967 2 : pot = GNUNET_CONTAINER_heap_peek (order_timeout_heap);
968 2 : if (NULL != order_timeout_task)
969 0 : GNUNET_SCHEDULER_cancel (order_timeout_task);
970 2 : order_timeout_task = GNUNET_SCHEDULER_add_at (pot->long_poll_timeout,
971 : &order_timeout,
972 : NULL);
973 : }
974 2 : return MHD_YES;
975 : }
976 12 : return TALER_MHD_REPLY_JSON_PACK (
977 : connection,
978 : MHD_HTTP_OK,
979 : GNUNET_JSON_pack_array_incref ("orders",
980 : po->pa));
981 : }
982 :
983 :
984 : /* end of taler-merchant-httpd_private-get-orders.c */
|