154
154
% % The routing key is either defined in the ATTACH frame and static for
155
155
% % the life time of the link or dynamically provided in each message's
156
156
% % "to" field (address v2) or "subject" field (address v1).
157
+ % % (A publisher can set additional routing keys via the x-cc message annotation.)
157
158
routing_key :: rabbit_types :routing_key () | to | subject ,
158
159
% % queue_name_bin is only set if the link target address refers to a queue.
159
160
queue_name_bin :: undefined | rabbit_misc :resource_name (),
@@ -2369,11 +2370,11 @@ incoming_link_transfer(
2369
2370
2370
2371
Mc0 = mc :init (mc_amqp , PayloadBin , #{}),
2371
2372
case lookup_target (LinkExchange , LinkRKey , Mc0 , Vhost , User , PermCache0 ) of
2372
- {ok , X , RoutingKey , Mc1 , PermCache } ->
2373
+ {ok , X , RoutingKeys , Mc1 , PermCache } ->
2373
2374
Mc2 = rabbit_message_interceptor :intercept (Mc1 ),
2374
2375
check_user_id (Mc2 , User ),
2375
- TopicPermCache = check_write_permitted_on_topic (
2376
- X , User , RoutingKey , TopicPermCache0 ),
2376
+ TopicPermCache = check_write_permitted_on_topics (
2377
+ X , User , RoutingKeys , TopicPermCache0 ),
2377
2378
QNames = rabbit_exchange :route (X , Mc2 , #{return_binding_keys => true }),
2378
2379
rabbit_trace :tap_in (Mc2 , QNames , ConnName , ChannelNum , Username , Trace ),
2379
2380
Opts = #{correlation => {HandleInt , DeliveryId }},
@@ -2408,14 +2409,14 @@ incoming_link_transfer(
2408
2409
" delivery_tag=~p , delivery_id=~p , reason=~p " ,
2409
2410
[DeliveryTag , DeliveryId , Reason ])
2410
2411
end ;
2411
- {error , # 'v1_0.error' {} = Err } ->
2412
+ {error , { anonymous_terminus , false }, # 'v1_0.error' {} = Err } ->
2412
2413
Disposition = case Settled of
2413
2414
true -> [];
2414
2415
false -> [released (DeliveryId )]
2415
2416
end ,
2416
2417
Detach = [detach (HandleInt , Link0 , Err )],
2417
2418
{error , Disposition ++ Detach };
2418
- {error , anonymous_terminus , # 'v1_0.error' {} = Err } ->
2419
+ {error , { anonymous_terminus , true } , # 'v1_0.error' {} = Err } ->
2419
2420
% % https://docs.oasis-open.org/amqp/anonterm/v1.0/cs01/anonterm-v1.0-cs01.html#doc-routingerrors
2420
2421
case Settled of
2421
2422
true ->
@@ -2440,13 +2441,13 @@ incoming_link_transfer(
2440
2441
end .
2441
2442
2442
2443
lookup_target (# exchange {} = X , LinkRKey , Mc , _ , _ , PermCache ) ->
2443
- lookup_routing_key (X , LinkRKey , Mc , PermCache );
2444
+ lookup_routing_key (X , LinkRKey , Mc , false , PermCache );
2444
2445
lookup_target (# resource {} = XName , LinkRKey , Mc , _ , _ , PermCache ) ->
2445
2446
case rabbit_exchange :lookup (XName ) of
2446
2447
{ok , X } ->
2447
- lookup_routing_key (X , LinkRKey , Mc , PermCache );
2448
+ lookup_routing_key (X , LinkRKey , Mc , false , PermCache );
2448
2449
{error , not_found } ->
2449
- {error , error_not_found (XName )}
2450
+ {error , { anonymous_terminus , false }, error_not_found (XName )}
2450
2451
end ;
2451
2452
lookup_target (to , to , Mc , Vhost , User , PermCache0 ) ->
2452
2453
case mc :property (to , Mc ) of
@@ -2458,25 +2459,26 @@ lookup_target(to, to, Mc, Vhost, User, PermCache0) ->
2458
2459
case rabbit_exchange :lookup (XName ) of
2459
2460
{ok , X } ->
2460
2461
check_internal_exchange (X ),
2461
- lookup_routing_key (X , RKey , Mc , PermCache );
2462
+ lookup_routing_key (X , RKey , Mc , true , PermCache );
2462
2463
{error , not_found } ->
2463
- {error , anonymous_terminus , error_not_found (XName )}
2464
+ {error , { anonymous_terminus , true } , error_not_found (XName )}
2464
2465
end ;
2465
2466
{error , bad_address } ->
2466
- {error , anonymous_terminus ,
2467
+ {error , { anonymous_terminus , true } ,
2467
2468
# 'v1_0.error' {
2468
2469
condition = ? V_1_0_AMQP_ERROR_PRECONDITION_FAILED ,
2469
2470
description = {utf8 , <<" bad 'to' address string: " , String /binary >>}}}
2470
2471
end ;
2471
2472
undefined ->
2472
- {error , anonymous_terminus ,
2473
+ {error , { anonymous_terminus , true } ,
2473
2474
# 'v1_0.error' {
2474
2475
condition = ? V_1_0_AMQP_ERROR_PRECONDITION_FAILED ,
2475
2476
description = {utf8 , <<" anonymous terminus requires 'to' address to be set" >>}}}
2476
2477
end .
2477
2478
2478
2479
lookup_routing_key (X = # exchange {name = # resource {name = XNameBin }},
2479
- RKey0 , Mc0 , PermCache ) ->
2480
+ RKey0 , Mc0 , AnonTerm , PermCache ) ->
2481
+ Mc1 = mc :set_annotation (? ANN_EXCHANGE , XNameBin , Mc0 ),
2480
2482
RKey = case RKey0 of
2481
2483
subject ->
2482
2484
case mc :property (subject , Mc0 ) of
@@ -2488,9 +2490,31 @@ lookup_routing_key(X = #exchange{name = #resource{name = XNameBin}},
2488
2490
_ when is_binary (RKey0 ) ->
2489
2491
RKey0
2490
2492
end ,
2491
- Mc1 = mc :set_annotation (? ANN_EXCHANGE , XNameBin , Mc0 ),
2492
- Mc = mc :set_annotation (? ANN_ROUTING_KEYS , [RKey ], Mc1 ),
2493
- {ok , X , RKey , Mc , PermCache }.
2493
+ case mc :x_header (<<" x-cc" >>, Mc0 ) of
2494
+ undefined ->
2495
+ RKeys = [RKey ],
2496
+ Mc = mc :set_annotation (? ANN_ROUTING_KEYS , RKeys , Mc1 ),
2497
+ {ok , X , RKeys , Mc , PermCache };
2498
+ {list , CCs0 } = L ->
2499
+ try lists :map (fun ({utf8 , CC }) -> CC end , CCs0 ) of
2500
+ CCs ->
2501
+ RKeys = [RKey | CCs ],
2502
+ Mc = mc :set_annotation (? ANN_ROUTING_KEYS , RKeys , Mc1 ),
2503
+ {ok , X , RKeys , Mc , PermCache }
2504
+ catch error :function_clause ->
2505
+ {error , {anonymous_terminus , AnonTerm }, bad_x_cc (L )}
2506
+ end ;
2507
+ BadValue ->
2508
+ {error , {anonymous_terminus , AnonTerm }, bad_x_cc (BadValue )}
2509
+ end .
2510
+
2511
+ bad_x_cc (Value ) ->
2512
+ Desc = unicode :characters_to_binary (
2513
+ lists :flatten (
2514
+ io_lib :format (
2515
+ " bad value for 'x-cc' message-annotation: ~tp " , [Value ]))),
2516
+ # 'v1_0.error' {condition = ? V_1_0_AMQP_ERROR_INVALID_FIELD ,
2517
+ description = {utf8 , Desc }}.
2494
2518
2495
2519
process_routing_confirm ([], _SenderSettles = true , _ , U ) ->
2496
2520
rabbit_global_counters :messages_unroutable_dropped (? PROTOCOL , 1 ),
@@ -3445,14 +3469,20 @@ check_resource_access(Resource, Perm, User, Cache) ->
3445
3469
end
3446
3470
end .
3447
3471
3448
- -spec check_write_permitted_on_topic (
3472
+ -spec check_write_permitted_on_topics (
3449
3473
rabbit_types :exchange (),
3450
3474
rabbit_types :user (),
3451
- rabbit_types :routing_key (),
3475
+ [ rabbit_types :routing_key (),...] ,
3452
3476
topic_permission_cache ()) ->
3453
3477
topic_permission_cache ().
3454
- check_write_permitted_on_topic (Resource , User , RoutingKey , TopicPermCache ) ->
3455
- check_topic_authorisation (Resource , User , RoutingKey , write , TopicPermCache ).
3478
+ check_write_permitted_on_topics (# exchange {type = topic } = Resource ,
3479
+ User , RoutingKeys , TopicPermCache ) ->
3480
+ lists :foldl (
3481
+ fun (RoutingKey , Cache ) ->
3482
+ check_topic_authorisation (Resource , User , RoutingKey , write , Cache )
3483
+ end , TopicPermCache , RoutingKeys );
3484
+ check_write_permitted_on_topics (_ , _ , _ , TopicPermCache ) ->
3485
+ TopicPermCache .
3456
3486
3457
3487
-spec check_read_permitted_on_topic (
3458
3488
rabbit_types :exchange (),
0 commit comments