154154 % % The routing key is either defined in the ATTACH frame and static for
155155 % % the life time of the link or dynamically provided in each message's
156156 % % "to" field (address v2) or "subject" field (address v1).
157+ % % (A publisher can set additional routing keys via the x-cc message annotation.)
157158 routing_key :: rabbit_types :routing_key () | to | subject ,
158159 % % queue_name_bin is only set if the link target address refers to a queue.
159160 queue_name_bin :: undefined | rabbit_misc :resource_name (),
@@ -2369,11 +2370,11 @@ incoming_link_transfer(
23692370
23702371 Mc0 = mc :init (mc_amqp , PayloadBin , #{}),
23712372 case lookup_target (LinkExchange , LinkRKey , Mc0 , Vhost , User , PermCache0 ) of
2372- {ok , X , RoutingKey , Mc1 , PermCache } ->
2373+ {ok , X , RoutingKeys , Mc1 , PermCache } ->
23732374 Mc2 = rabbit_message_interceptor :intercept (Mc1 ),
23742375 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 ),
23772378 QNames = rabbit_exchange :route (X , Mc2 , #{return_binding_keys => true }),
23782379 rabbit_trace :tap_in (Mc2 , QNames , ConnName , ChannelNum , Username , Trace ),
23792380 Opts = #{correlation => {HandleInt , DeliveryId }},
@@ -2408,14 +2409,14 @@ incoming_link_transfer(
24082409 " delivery_tag=~p , delivery_id=~p , reason=~p " ,
24092410 [DeliveryTag , DeliveryId , Reason ])
24102411 end ;
2411- {error , # 'v1_0.error' {} = Err } ->
2412+ {error , { anonymous_terminus , false }, # 'v1_0.error' {} = Err } ->
24122413 Disposition = case Settled of
24132414 true -> [];
24142415 false -> [released (DeliveryId )]
24152416 end ,
24162417 Detach = [detach (HandleInt , Link0 , Err )],
24172418 {error , Disposition ++ Detach };
2418- {error , anonymous_terminus , # 'v1_0.error' {} = Err } ->
2419+ {error , { anonymous_terminus , true } , # 'v1_0.error' {} = Err } ->
24192420 % % https://docs.oasis-open.org/amqp/anonterm/v1.0/cs01/anonterm-v1.0-cs01.html#doc-routingerrors
24202421 case Settled of
24212422 true ->
@@ -2440,13 +2441,13 @@ incoming_link_transfer(
24402441 end .
24412442
24422443lookup_target (# exchange {} = X , LinkRKey , Mc , _ , _ , PermCache ) ->
2443- lookup_routing_key (X , LinkRKey , Mc , PermCache );
2444+ lookup_routing_key (X , LinkRKey , Mc , false , PermCache );
24442445lookup_target (# resource {} = XName , LinkRKey , Mc , _ , _ , PermCache ) ->
24452446 case rabbit_exchange :lookup (XName ) of
24462447 {ok , X } ->
2447- lookup_routing_key (X , LinkRKey , Mc , PermCache );
2448+ lookup_routing_key (X , LinkRKey , Mc , false , PermCache );
24482449 {error , not_found } ->
2449- {error , error_not_found (XName )}
2450+ {error , { anonymous_terminus , false }, error_not_found (XName )}
24502451 end ;
24512452lookup_target (to , to , Mc , Vhost , User , PermCache0 ) ->
24522453 case mc :property (to , Mc ) of
@@ -2458,25 +2459,26 @@ lookup_target(to, to, Mc, Vhost, User, PermCache0) ->
24582459 case rabbit_exchange :lookup (XName ) of
24592460 {ok , X } ->
24602461 check_internal_exchange (X ),
2461- lookup_routing_key (X , RKey , Mc , PermCache );
2462+ lookup_routing_key (X , RKey , Mc , true , PermCache );
24622463 {error , not_found } ->
2463- {error , anonymous_terminus , error_not_found (XName )}
2464+ {error , { anonymous_terminus , true } , error_not_found (XName )}
24642465 end ;
24652466 {error , bad_address } ->
2466- {error , anonymous_terminus ,
2467+ {error , { anonymous_terminus , true } ,
24672468 # 'v1_0.error' {
24682469 condition = ? V_1_0_AMQP_ERROR_PRECONDITION_FAILED ,
24692470 description = {utf8 , <<" bad 'to' address string: " , String /binary >>}}}
24702471 end ;
24712472 undefined ->
2472- {error , anonymous_terminus ,
2473+ {error , { anonymous_terminus , true } ,
24732474 # 'v1_0.error' {
24742475 condition = ? V_1_0_AMQP_ERROR_PRECONDITION_FAILED ,
24752476 description = {utf8 , <<" anonymous terminus requires 'to' address to be set" >>}}}
24762477 end .
24772478
24782479lookup_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 ),
24802482 RKey = case RKey0 of
24812483 subject ->
24822484 case mc :property (subject , Mc0 ) of
@@ -2488,9 +2490,31 @@ lookup_routing_key(X = #exchange{name = #resource{name = XNameBin}},
24882490 _ when is_binary (RKey0 ) ->
24892491 RKey0
24902492 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 }}.
24942518
24952519process_routing_confirm ([], _SenderSettles = true , _ , U ) ->
24962520 rabbit_global_counters :messages_unroutable_dropped (? PROTOCOL , 1 ),
@@ -3445,14 +3469,20 @@ check_resource_access(Resource, Perm, User, Cache) ->
34453469 end
34463470 end .
34473471
3448- -spec check_write_permitted_on_topic (
3472+ -spec check_write_permitted_on_topics (
34493473 rabbit_types :exchange (),
34503474 rabbit_types :user (),
3451- rabbit_types :routing_key (),
3475+ [ rabbit_types :routing_key (),...] ,
34523476 topic_permission_cache ()) ->
34533477 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 .
34563486
34573487-spec check_read_permitted_on_topic (
34583488 rabbit_types :exchange (),
0 commit comments