RFC6121

来自Jabber/XMPP中文翻译计划
2013年5月20日 (一) 03:18Admin (讨论 | 贡献)的版本
跳转到: 导航, 搜索


"本文的英文原文来自RFC 6121

互联网工程任务组(IETF) P. Saint-Andre
申请讨论: 6121 Cisco
取代: 3921 2011年3月
类别: 标准跟踪
ISSN: 2070-1721
可扩展的消息和出席信息协议 (XMPP): 即时消息和出席信息

摘要

本文定义提供了遵循RFC2779要求的基本的即时消息(IM)和出席信息功能的可扩展的消息和出席信息协议(XMPP)的核心功能的扩展. 本文取代了 RFC 3921.

本文的状态

这是一个互联网标准跟踪文档.
本文是互联网工程工作组(IETF)的一个成果. 它代表了IETF社区的一致意见. 它已经公开审核并由互联网工程控制组(IESG)批准发布了. 更多关于互联网标准的信息请参见RFC 5741第2章.
关于本文当前状态的信息, 任何错误, 以及如何对它提出反馈,请到 http://www.rfc-editor.org/info/rfc6121 .

版权声明

Copyright (c) 2011 IETF Trust and the persons identified as the document authors. All rights reserved.
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (http://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Simplified BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Simplified BSD License.

目录

序论

概述

可扩展的消息和出席信息协议(XMPP)是可扩展的标记语言XML的一个应用范畴,它能使两个或更多网络实体之间的结构化并且可扩展的数据进行准实时的交换. 在XMPP‑CORE中定义的XMPP的核心特性提供了各种类型的准实时应用的积木, 通过发送由特定的XML命名空间(参考XML‑NAMES)所限定的应用特有的数据,它们可被叠加在核心之上. 本文定义的XMPP扩展提供一个IMP‑REQS所述的即时消息(IM)和出席信息应用所预期的基本功能.

历史

XMPP的基本语法和语义最开始是由Jabber开源社区开发的, 主要是在1999年. 2002年, 根据 IMP‑REQS ,XMPP工作组被允许基于Jabber协议开发一个适合IETF的即时消息和出席信息技术. 到了2004年10月, 发布了 RFC3920RFC3921 , 意味着那时候XMPP的主要定义完成了.

从2004年开始,互联网社区已经获得了广泛的XMPP实现和布署经验, 包括XMPP标准基金会(XSF)主持下开展的正式的互操作性测试. 本文全面整合了从软件开发者和XMPP服务提供者得到的反馈, 包含了一系列向后兼容的修改,见 附录E . 结果是, 本文反映了互联网社区对于XMPP1.0的即时消息和出席信息特性的初步共识, 因此废止了RFC 3921.

需求

传统上, IM应用组合了以下因素:

  1. 关注的中心点是某人的联系人的列表或 "好友们" (在XMPP中这个列表被称为一个 "roster"(名册)).
  2. 使用这类应用的目标是和特定的联系人准实时地交换相关的短文本消息 -- 在章节5.1描述的一对一形式的"聊天会话"中, 快速连续的相关消息的数量经常是很大的.
  3. 交换消息的催化剂是 "presence" -- 即, 关于特定联系人们的网络可用性的信息(这样就知道谁在线并且有空进行一对一的聊天会话).
  4. 出席信息仅提供给通过一个被称为"出席信息订阅"的明确协议被授权的联系人.

所以在高层上本文假定用户能完成下列用例:

  • 在某人的联系人列表中管理条目
  • 和某人的联系人们交换消息
  • 和某人的联系人们交换出席信息
  • 管理对某人的联系人们发出和接受出席信息的订阅

这些功能性领域的详细定义被包含在 RFC 2779 IMP‑REQS 中, 关于深度需求感兴趣的读者可以参考那个文档. 尽管 XMPP IM 和这里定义的出席信息扩展满足了 RFC 2779 的需求, 它们不是特意为那个协议设计的, 因为在 RFC 2779 出现之前,Jabber开源社区就通过一个开放的开发流程进化出了基本协议. 尽管在XMP标准基金会的XEP系列中发表了一些其他功能领域的XMPP协议扩展(例如, XEP-0045定义的多用户文本聊天), 这类扩展未在本文中定义,因为它们未被 RFC 2779 授权.

实现备注: RFC 2779 规定出席信息服务必须独立于IM服务,反之亦然; 即, 必须有可能使用本协议提供一个出席信息服务, 一个消息服务, 或同时提供两者. 尽管本文的文本假定实现和部署会希望提供统一的IM和出席信息服务, 对于一个XMPP服务来说,并不强制同时提供一个出席信息服务和一个消息服务, 并且协议使得为出席信息和为消息提供独立和特别的服务成为可能. (例如, 如果一个客户端尝试发送一个<message/>节, 一个只有出席信息的服务可能返回一个<service-unavailable/>节错误.)

功能汇总

这个非规范性的章节提供一个对开发者友好的, 基于XMPP的IM和出席信息特性的功能汇总; 参考接下来的的章节来察看这些特性的规范性的定义.

XMPP‑CORE 定义了一个XMPP客户端如何连接到一个XMPP服务器. 特别是, 它定义了客户端在一个XMPP网络中被允许发送XML节(XMPP中的基本含意单位)给其他实体之前需要满足的前提条件. 这些前提条件包括,XML流的协商,XML流头的交换, 可选的通过传输层安全TLS的通道加密, 通过简单验证和安全层SASL强制验证, 和把资源绑定到客户端所在的流上. 关于这些前提条件的细节,读者可以参考 XMPP‑CORE, XMPP‑CORE 的知识假定已经在此.

互操作性备注: RFC3921 定义了一个额外的前提条件: 一个即时消息和出席信息会话的正式建立. 实现和部署经验已经表明这个额外的步骤是不必要的. 无论如何, 为了向后兼容,一个实现可以仍提供那个特性. 这使得新软件节省一个回合而旧软件能够连接.

满足了XMPP‑CORE中的前提条件之后, XMPP客户端就有了一个和一个XMPP服务器长连的XML流, 这使得该用户能控制客户端在该流上发送和接收基本上不限数量的XML节. 这样一个流可被用于交换消息, 分享出席信息, 并且准实时地进行结构化的 请求-应答 交互. 在XML流的协商之后, 即时消息和出席信息会话的典型流程如下:

  1. 接收某人的名册. (见 章节2.2.)
  2. 发送最初的出席信息给服务器用于广播给所有已订阅的联系人, 也就是从XMPP通讯角度来看的 "上线" . (见 章节4.2 .)
  3. 交换消息, 管理出席信息订阅, 执行名册更新, 以及在一般过程中,贯穿该会话生命周期的以特定语义生成的其他XML节. (见 章节 5, 3, 2, 和 6.)
  4. 当需要的时候,通过发送unavailable出席信息并关闭下面的XML流来中止该会话. (见 章节4.5.)

术语

本文中的关键字 "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", 和 "OPTIONAL" 的解释参见RFC 2119 关键字 .

本文继承了 XMPP‑CORE 中定义的术语.

术语 "automated client" 和 "interactive client" 的含义和 TLS‑CERTS 中定义的相同.

为了方便, 本文借用了术语 "user" 来指代一个XMPP帐号的所有者; 无论如何, 帐号所有者不需要是自然人并且可以是机器人, 设备, 或其他自动化的应用.

一些其他的术语, 类似 "interested resource", 在本文的正文中定义.

接下来的"XML Notation"用在IRI中展示无法用纯ASCII文档表达的字符串, 本文的一些例子中使用了格式 "&#x...." 作为一个符号设备来展示 UNICODE 字符串(例如, 字符串 "ř" 代表 Unicode 字符串 LATIN SMALL LETTER R WITH CARON); 这个格式在XMPP系统中绝对不会在网络上被发送.

在例子中, 每一行已经被封装成便于阅读的样子, "[...]" 意味着省略, 并且使用了以下前缀(这些前缀不会在通讯中被发送):

  • C: = 客户端
  • CC: = 联系人的客户端
  • CS: = 联系人的服务器
  • S: = 服务器
  • UC: = 用户的客户端
  • US: = 用户的服务器

读者需要注意这些例子不包括细节, 并且例子里的一些协议流程中, 展示的备用步骤不一定是由前一个步骤发送的确切的数据触发的; 本文或常用参考文档中的协议规范所用到的所有用例里面提供的例子都遵从上述规则. 所有例子都是虚构的并且交换的信息 (例如, 用户名和密码) 不代表任何现存的用户和服务器.

管理名册

在XMPP中, 一个用户的名册包含任意数量的特定联系人。一个用户的花名册由用户的服务器代替用户储存,从而使用户可以从任何资源设备访问花名册信息。当用户添加或修改名册条目时,如果无错误发生,服务器应该尽可能不加修改地存储那些数据,并且当一个授权的客户端请求名册时,服务器必须返回那些数据。

安全警告: 因为用户的名册包含保密数据,因此服务器必须限制对这些数据的访问,只有经授权的实体(典型的为帐户拥有者)才有权利获取,修改和删除它。

RFC 3921 假定用户只把他们的名册存储在该用户帐号注册并验证访问XMPP网络的那台服务器上. 本协议移出了对名册存储和帐号注册以及网络验证的耦合的限制, 结果是用户可以把他们的名册存储在另一个地方, 或者可以有多个名册存在不同的地方. 无论如何, 在没有实现和部署实践一个更弹性化的名册存储模型的情况下, 本协议保留了 RFC 3921 的术语 "client" 和 "server" (并把 "the roster" 替换成 "a roster"), 而不是造出一个新的术语代表 "某个用户存储某一个名册的地方". 未来的文档可能为非服务器名册或多名册管理提供规范性规则, 但这些规则超出了本文的范围.

语法和语义

名册是用 <iq/> 节(见 XMPP‑CORE 的8.2.3节)管理的, 确切的说就是一个由'jabber:iq:roster'命名空间限定的<query/>子元素. 详细的语法和语义在接下来的章节中定义.

Ver属性

'ver'属性是一个标识名册信息的特定版本的字符串. 它的值必须仅由服务器生成并且必须由客户端不透明地处理. 服务器可以使用任何适当的方法来生成该版本ID, 类似名册数据的哈希值或一个严格递增的序列号.

建议包含'ver'属性.

对'ver'属性的使用的完整描述在 章节2.6.

互操作性备注: <query/>元素的'ver'属性在 RFC 3921 中没有定义,在本协议中是新定义的.

名册条目

一个roster set中的<query/>元素包含一个<item/>子元素, 而一个roster result典型地包含多个<item/>子元素. 每个<item/>元素描述了一个唯一的"名册条目"(又是也称为一个"联系人").

<item/>元素的语法参见接下来的章节.

Approved属性

布尔值'approved'属性值为"true"被用于发送章节3.4所述的预批准信号(缺省为"false", 依据XML‑DATATYPES).

服务器应该包含该'approved'属性来通知客户端订阅预批准. 客户端不能(MUST NOT)在它发送给服务器的roster sets中包含'approved'属性, 但必须用类型为"subscribed"和"unsubscribed"的presence节来管理章节3.4所述的预批准.

互操作性备注: <item/>元素的'approved'属性在 RFC 3921 中没有被定义,是在本协议中新出现的.
Ask属性

<item/>元素的'ask'属性值为"subscribe"被用于发布订阅子状态,包括章节3.1.2所述的"待处理"等等.

服务器应该包含'ask'属性来通知客户端"待处理"子状态. 客户端不能(MUST NOT)在它发送给该服务器的roster sets中包含'ask'属性, 而必须使用类型为"subscribe"和"unsubscribe" 的presence节来管理章节3.1.2所述的这类子状态.

JID属性

<item/>元素的'jid'属性指定唯一性地标识该名册条目的Jabber标识符(JID).

'jid'属性是必需的,无论是客户端还是服务器添加, 更新, 删除, 或返回一个名册条目.

Name属性

<item/>元素的'name'属性指定和该JID相关的"处理", 这是由该用户(不是该联系人)决定的. 尽管'name'属性的值可以代表一个自然人用户, 对该服务器来说它是不透明的. 无论如何, 'name'属性在多个XMPP扩展的上下文中可被服务器用于匹配目的(一个可能的比较方法是XMPP‑ADDR所述的XMPP资源部分).

对于客户端来说,当添加或更新一个名册条目的时候,包含'name'属性是可选的.

Subscription属性

出席信息订阅的状态被放在<item/>元素的'subscription'属性中. 已定义的订阅相关的值如下:

    none:
        该用户没有订阅一该联系人的出席信息, 并且联系人也没有订阅该用户的出席信息; 这是缺省值, 所以如果没有subscription属性,那么该状态被理解为"none" 
    to:
        该用户定于了该联系人的出席信息, 但是联系人没有订阅用户的出席信息 
    from:
        该联系人订阅了该用户的出席信息, 但是该用户没有订阅联系人的出席信息
    both:
        该用户和该联系人互相订阅了对方的出席信息(也称为"相互订阅")

在一个roster result中, 除了 "none", "to", "from", 或 "both"值,该客户端必须忽略 'subscription'属性的(其他)值.

在一个roster push中, 除了 "none", "to", "from", "both", 或 "remove"值,该客户端必须忽略'subscription'属性的(其他)值.

在一个roster set中, 'subscription'属性的值可以包括 "remove", 它表示该条目已经从名册中移除了; 在一个 roster set 中,该服务器必须忽略除了"remove"之外'subscription'属性的所有值.

包含'subscription'属性是可选的.

Group属性

<group/>子元素指定一个类别或"桶" ,让客户端把名册条目分成组. 一个<item/>元素可以包含多个<group/>元素, 这意味着名册组不是排他的. 尽管<group/>元素的XML字符串元素可以意味着一个自然人用户, 它对于服务器是不透明的. 无论如何, 在各种XMPP扩展的上下文中, <group/>元素可以被服务器用于匹配的目的(一个可能的的比较方法用于XMPP资源部分,详见XMPP‑ADDR).

对于客户端来说,在添加或更新一个名册条目的时候包含<group/>元素是可选的. 如果一个roster set不包含<group/>元素, 那么该条目被理解为不属于任何组.

名册获取

一个 "roster get" 是一个客户端为了让服务器返回某名册而发出的请求; 在语法构成上它是一个从客户端到服务器的类型为"get"的IQ节,并且包含一个由'jabber:iq:roster'命名空间限定的<query/>元素, 这里的<query/>元素不能(MUST NOT)包含任何<item/>子元素.

C: <iq from='juliet@example.com/balcony'
       id='bv1bs71f'
       type='get'>
    <query xmlns='jabber:iq:roster'/>
  </iq>

发送一个roster get的预期结果是服务器返回一个roster result.

名册结果

一个"roster result"是服务器对一个名册获取的应答; 在语法构成上它是一个从服务器到客户端的类型为"result"的IQ节,并且包含一个由'jabber:iq:roster'命名空间限定的<query/>元素.

该 <query/> 元素是一个名册结果,为每一个联系人包含一个 <item/> 元素并且因而可以包含多个<item/>元素.

S: <iq id='bv1bs71f'
       to='juliet@example.com/chamber'
       type='result'>
    <query xmlns='jabber:iq:roster' ver='ver7'>
      <item jid='nurse@example.com'/>
      <item jid='romeo@example.net'/>
    </query>
  </iq>

如果该名册存在但是在名册中没有联系人, 那么该服务器必须返回一个IQ-result,包含一个<query/>子元素而不包含任何<item/>子元素(即, 服务器不能(MUST NOT)返回空的类型为"error"的<iq/>节).

S: <iq id='bv1bs71f'
       to='juliet@example.com/chamber'
       type='result'>
    <query xmlns='jabber:iq:roster' ver='ver9'/>
  </iq>

如果该名册不存在, 那么该服务器必须返回一个节错误,条件为<item-not-found/>.

S: <iq id='bv1bs71f'
       to='juliet@example.com/chamber'
       type='error'>
    <error type='cancel'>
      <item-not-found
          xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
    </error>
  </iq>

名册设置

一个"roster set"是一个客户端向服务器发出的编辑(即, 创建, 更新, 或删除)一个名册条目的请求; 在语法构成上它是一个从客户端到服务器的类型为"set"的IQ节,并且包含一个由'jabber:iq:roster'命名空间限定的<query/>元素.

以下规则适用于名册设置:

  1. <query/>元素必须包含一个并且只含有一个<item/>元素.
  2. 服务器必须忽略'subscription'属性的除"remove"以外的任何值(见章节2.1.2.5).
安全警告: 传统上, 名册设置的IQ节不包含'to'地址, 结果是所有名册设置都那个需要更新名册的帐号的某个已验证资源(全JID)发出. 此外, RFC 3921 要求一个服务器执行名册设置的特定情景的检查来忽略'to'地址; 无论如何, 本协议移除了那个特定情景, 这意味着一个名册设置可以包含一个和发送者不同的'to'地址. 所以, 处理一个名册设置的实体必须验证名册设置的发送者已被授权更新该名册, 如果没有授权则返回一个<forbidden/>错误.
C: <iq from='juliet@example.com/balcony'
       id='rs1'
       type='set'>
    <query xmlns='jabber:iq:roster'>
      <item jid='nurse@example.com'/>
    </query>
  </iq>

名册推送

一个"roster push"是一个服务器向客户端发出的最新创建, 更新, 或删除的名册条目; 在语法构成上它是一个从服务器到客户端的类型为"set"的IQ节,并且包含一个由'jabber:iq:roster'命名空间限定的<query/>元素.

以下规则适用于名册推送:

  1. 在一个名册推送中的<query/>元素必须包含且仅包含一个<item/>元素.
  2. 一个接收的客户端必须忽略这个节,除非它满足以下条件:没有'from'属性(即, 隐式地来自该用户帐号的纯JID)或它有一个值和该用户的纯JID<user@domainpart>匹配的'from'属性.
S: <iq id='a78b4q6ha463'
       to='juliet@example.com/chamber'
       type='set'>
    <query xmlns='jabber:iq:roster'>
      <item jid='nurse@example.com'/>
    </query>
  </iq>

作为XMPP‑CORE定义的IQ节语义的强制要求, 每个接收一个来自服务器的名册推送的资源应该应答一个类型为"result"或"error"的IQ节(无论如何, 大家知道很多现有的客户端不应答名册推送).

C: <iq from='juliet@example.com/balcony'
       id='a78b4q6ha463'
       type='result'/>
 
C: <iq from='juliet@example.com/chamber'
       id='a78b4q6ha463'
       type='result'/>
安全警告: 传统上, 名册推送不包含'from'地址, 结果是所有名册推送都隐式地从该帐号本身的纯JID发出. 无论如何, 本协议允许非该用户的服务器实体来维护名册信息, 这意味着一个名册推送可以包含不同于该用户帐号的纯JID的'from'地址. 所以, 该客户端必须检查'from'地址来验证名册推送的发送者被售前更新名册. 如果该客户端从一个未授权实体接收到了一个名册推送, 它不能(MUST NOT)处理被推送的数据; 此外, 该客户端要么返回一个节错误<service-unavailable/>要么干脆拒绝返回一个节错误(后面那个行为覆盖了一个来自XMPP‑CORE的 必须-级 的要求, 目的是防止出席信息泄露).
实现备注: 对于名册推送的客户端处理没有错误情景; 如果服务器在对名册推送的应答中接收到了一个类型为"error"的IQ,那么它应该忽略那个错误.

登陆时接收名册

在通过一个服务器的验证并绑定一个资源之后(也就是成为一个XMPP‑CORE中定义的已连接资源), 一个客户端应该在发送初始的出席信息之前请求名册(无论如何, 因为接收名册不是对所有的资源都是必要的, 例如, 一个有限带宽的连接, 该客户端对名册的请求不是强制的). 在一个已连接的资源发送初始出席信息之后(见章节4.2), 它被称为一个"可用的资源". 如果一个已连接资源或可用资源请求名册, 它被成为一个"有兴趣的资源". 服务器必须发送名册推送到所有有兴趣的资源.

实现备注: 出席信息订阅请求被发送到可用的资源, 而和订阅状态改变相关的名册推送被发送到感兴趣的资源. 所以, 如果一个资源希望同时接收到订阅请求和名册推送, 它必须既发送初始出席信息又请求名册.

一个客户端通过向服务器发送一个roster get来请求名册.

C: <iq from='juliet@example.com/balcony'
       id='hu2bac18'
       type='get'>
     <query xmlns='jabber:iq:roster'/>
   </iq>
S: <iq id='hu2bac18'
       to='juliet@example.com/balcony'
       type='result'>
     <query xmlns='jabber:iq:roster' ver='ver11'>
       <item jid='romeo@example.net'
             name='Romeo'
             subscription='both'>
         <group>Friends</group>
       </item>
       <item jid='mercutio@example.com'
             name='Mercutio'
             subscription='from'/>
       <item jid='benvolio@example.net'
             name='Benvolio'
             subscription='both'/>
     </query>
   </iq>

如果服务器不处理该roster get, 它必须返回一个XMPP‑CORE所述的适当的节错误(如果是名册命名空间不被支持则返回 <service-unavailable/>,如果该服务器处理过程或返回该名册时遇到麻烦则返回 <internal-server-error/> ).

添加一个名册条目

请求

任何时候, 一个客户端可以添加一个条目到名册. 只要发送一个包含一个新条目的roster set就可以做到.

C: <iq from='juliet@example.com/balcony'
       id='ph1xaz53'
       type='set'>
     <query xmlns='jabber:iq:roster'>
       <item jid='nurse@example.com'
             name='Nurse'>
         <group>Servants</group>
       </item>
     </query>
   </iq>

成功情景

如果服务器成功地处理了关于该新条目的roster set(即, 如果没有错误发生), 它必须在该用户的名册中新增该条目并做如下处理.

该服务器必须返回一个类型为"result"的IQ节给发送该roster set的已连接资源.

S: <iq id='ph1xaz53'
       to='juliet@example.com/balcony'
       type='result'/>

该服务器也必须发送一个包含了该新名册条目的roster push到该用户的所有感兴趣的资源, 包括生成了该roster set的资源.

S: <iq to='juliet@example.com/balcony'
       id='a78b4q6ha463'
       type='set'>
     <query xmlns='jabber:iq:roster' ver='ver13'>
       <item jid='nurse@example.com'
             name='Nurse'
             subscription='none'>
         <group>Servants</group>
       </item>
     </query>
   </iq>
 
S: <iq to='juliet@example.com/chamber'
       id='x81g3bdy4n19'
       type='set'>
     <query xmlns='jabber:iq:roster' ver='ver13'>
       <item jid='nurse@example.com'
             name='Nurse'
             subscription='none'>
         <group>Servants</group>
       </item>
     </query>
   </iq>

作为在XMPP‑CORE中定义的IQ节语义的强制要求, 建议每个从该服务器接收到一个roster push的资源应答一个类型为"result"或"error"的IQ节(无论如何, 大家知道很多现有的客户端不应答roster pushes).

C: <iq from='juliet@example.com/balcony'
       id='a78b4q6ha463'
       type='result'/>
 
C: <iq from='juliet@example.com/chamber'
       id='x81g3bdy4n19'
       type='result'/>

错误情景

如果服务器未能成功处理roster set, 它必须返回一个节错误. 以下错误场景已被定义. 自然的, 可能会发生其他节错误, 例如,如果服务器在处理roster get(译者注:这里应该是roster set,疑为原文笔误)时发生了内部问题则返回 <internal-server-error/>, 或甚至服务器只允许通过类似web接口这样的非XMPP方法来修改名册则返回<not-allowed/>.

如果roster set的发送者未被授权更新该名册(典型的情况是该帐号本身只有一个已验证的资源被授权),则服务器必须返回一个<forbidden/>节错误给客户端.

服务器必须返回一个<bad-request/>节错误给客户端,如果roster set包含以下任何违规情况:

  1. <query/>元素包含不止一个<item/>子元素.
  2. <item/>元素包含不止一个<group/>元素, 但是有重复的groups (一个可能的用来确定重复的比较方法见于XMPP‑ADDR中关于XMPP资源部分的描述).

服务器必须返回一个<not-acceptable/>节错误给客户端,如果roster set包含以下任何违规情况:

  1. 'name'属性的长度大于一个服务器配置的限制.
  2. <group/>元素的XML字符数据长度为零(为了把一个条目从所有组移除, 客户端需要从roster set排除任何<group/>元素).
  3. <group/>元素的XML字符数据长度大于一个服务器配置的限制.

错误: Roster set由未授权实体初始化

C: <iq from='juliet@example.com/balcony'
       id='ix7s53v2'
       to='romeo@example.net'
       type='set'>
     <query xmlns='jabber:iq:roster'>
       <item jid='nurse@example.com'/>
     </query>
   </iq>
 
S: <iq id='ix7s53v2'
       to='juliet@example.com/balcony'
       type='error'>
    <error type='auth'>
      <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
    </error>
  </iq>

错误: Roster set包含多个条目

C: <iq from='juliet@example.com/balcony'
       id='nw83vcj4'
       type='set'>
     <query xmlns='jabber:iq:roster'>
       <item jid='nurse@example.com'
             name='Nurse'>
         <group>Servants</group>
       </item>
       <item jid='mother@example.com'
             name='Mom'>
         <group>Family</group>
       </item>
     </query>
   </iq>
 
S: <iq id='nw83vcj4'
       to='juliet@example.com/balcony'
       type='error'>
    <error type='modify'>
      <bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
    </error>
  </iq>

错误: Roster set包含的条目超长

C: <iq from='juliet@example.com/balcony'
       id='yl491b3d'
       type='set'>
     <query xmlns='jabber:iq:roster'>
       <item jid='nurse@example.com'
             name='[ ... some-very-long-handle ... ]'>
         <group>Servants</group>
       </item>
     </query>
   </iq>
 
S: <iq id='yl491b3d'
       to='juliet@example.com/balcony'
       type='error'>
     <error type='modify'>
       <not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
     </error>
   </iq>

错误: Roster set包含重复的groups

C: <iq from='juliet@example.com/balcony'
       id='tk3va749'
       type='set'>
     <query xmlns='jabber:iq:roster'>
       <item jid='nurse@example.com'
             name='Nurse'>
         <group>Servants</group>
         <group>Servants</group>
       </item>
     </query>
   </iq>
 
S: <iq id='tk3va749'
       to='juliet@example.com/balcony'
       type='error'>
     <error type='modify'>
       <bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
     </error>
   </iq>

错误: Roster set包含空group

C: <iq from='juliet@example.com/balcony'
       id='fl3b486u'
       type='set'>
     <query xmlns='jabber:iq:roster'>
       <item jid='nurse@example.com'
             name='Nurse'>
         <group></group>
       </item>
     </query>
   </iq>
 
S: <iq id='fl3b486u'
       to='juliet@example.com/balcony'
       type='error'>
     <error type='modify'>
       <not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
     </error>
   </iq>

错误: Roster set包含超长group名

C: <iq from='juliet@example.com/balcony'
       id='qh3b4v19'
       type='set'>
     <query xmlns='jabber:iq:roster'>
       <item jid='nurse@example.com'
             name='Nurse'>
         <group>[ ... some-very-long-group-name ... ]</group>
       </item>
     </query>
   </iq>
 
S: <iq id='qh3b4v19'
       to='juliet@example.com/balcony'
       type='error'>
     <error type='modify'>
       <not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
     </error>
   </iq>
互操作性备注: 如果<item/>元素的'jid'属性值和该用户的帐号的纯JID<localpart@domainpart>匹配,一些服务器会返回<not-allowed/>节错误给客户端.

更新一个名册条目

请求

更新一个现有的名册条目和新增一个名册条目是以相同方法完成的, 即, 发送一个roster set给服务器. 因为名册条目是原子的, 该条目必须如roster set提供的那样被准确地更新.

关于为什么一个客户端可能更新一个名册条目有很多原因:

  1. 新增一个组
  2. 删除一个组
  3. 修改操作
  4. 删除操作

考虑一个定义如下的名册条目:

    <item jid='romeo@example.net'
          name='Romeo'>
      <group>Friends</group>
    </item>

在她的名册中拥有该用户的条目,她可能想把该条目添加到另一个组.

C: <iq from='juliet@example.com/balcony'
       id='di43b2x9'
       type='set'>
     <query xmlns='jabber:iq:roster'>
       <item jid='romeo@example.net'
             name='Romeo'>
         <group>Friends</group>
         <group>Lovers</group>
       </item>
     </query>
   </iq>

有时候晚些时候, 该用户可能希望从原有的组移除该条目.

C: <iq from='juliet@example.com/balcony'
       id='lf72v157'
       type='set'>
     <query xmlns='jabber:iq:roster'>
       <item jid='romeo@example.net'
             name='Romeo'>
         <group>Lovers</group>
       </item>
     </query>
   </iq>

该用户可能想从所有组移除该条目.

C: <iq from='juliet@example.com/balcony'
       id='ju4b62a5'
       type='set'>
     <query xmlns='jabber:iq:roster'>
       <item jid='romeo@example.net'/>
     </query>
   </iq>

该用户也可能想修改对该条目的操作.

C: <iq from='juliet@example.com/balcony'
       id='gb3sv487'
       type='set'>
     <query xmlns='jabber:iq:roster'>
       <item jid='romeo@example.net'
             name='MyRomeo'/>
     </query>
   </iq>

该用户然后可能想完全移除该操作.

C: <iq from='juliet@example.com/balcony'
       id='o3bx66s5'
       type='set'>
     <query xmlns='jabber:iq:roster'>
       <item jid='romeo@example.net'
             name=''/>
     </query>
   </iq>
实现备注: 包含一个空的'name'属性等价于不包含'name'属性; 两个操作都是把name设为空字符串.

成功情景

和添加一个名册条目一样, 如果该名册条目能被成功处理那么服务器必须在该用户的名册中更新该条目, 发送一个roster push到所有该用户的感兴趣资源, 并且发送一个IQ result给初始的资源; 详见章节2.3.

错误情景

章节2.3.3中错误情景的描述也适用于更新一个名册条目.

删除一个名册条目

请求

任何时候, 一个客户端可以通过发送一个并把'subscription'属性值设为"remove"来把一个条目从他或她的名册中删除.

C: <iq from='juliet@example.com/balcony'
       id='hm4hs97y'
       type='set'>
     <query xmlns='jabber:iq:roster'>
       <item jid='nurse@example.com'
             subscription='remove'/>
     </query>
   </iq>

成功情景

如同增加一个名册条目, 如果服务器能成功地处理roster set那么它必须在该用户的名册中更新该条目, 发送一个roster push到该用户的所有感兴趣的资源(其中的'subscription'属性值设为"remove"), 并发送一个IQ result给初始的资源; 详见章节2.3.

另外, 该用户的服务器可能需要生成一个或更多subscription相关的presence节, 如下:

  1. 如果该用户对该联系人有一个出席信息订阅, 那么该用户的服务器必须发送一个type为"unsubscribe"的presence节给该联系人(为了对该联系人的出席信息取消订阅).
  2. 如果该联系人对该用户有一个出席信息订阅, 那么该用户的服务器必须发送一个type为"unsubscribed"的presence节给该联系人(为了取消该联系人对该用户的订阅).
  3. 如果出席信息订阅是相互的, 那么该用户的服务器必须同时发送type为"unsubscribe"presence节和type为"unsubscribed"的的presence节给该联系人.
S: <presence from='juliet@example.com'
             id='lm3ba81g'
             to='nurse@example.com'
             type='unsubscribe'/>
 
S: <presence from='juliet@example.com'
             id='xb2c1v4k'
             to='nurse@example.com'
             type='unsubscribed'/>

错误情景

如果'jid'属性的值指定的一个条目不在该名册中, 那么服务器必须返回一个 <item-not-found/> 节错误.

错误: 未发现名册条目

C: <iq from='juliet@example.com/balcony'
       id='uj4b1ca8'
       type='set'>
     <query xmlns='jabber:iq:roster'>
       <item jid='[ ... non-existent-jid ... ]'
             subscription='remove'/>
     </query>
   </iq>
 
S: <iq id='uj4b1ca8'
       to='juliet@example.com/balcony'
       type='error'>
     <error type='modify'>
       <item-not-found
           xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
     </error>
   </iq>

名册版本

流特性

如果一个服务器支持名册版本, 那么它必须在流协商期间声明以下流特性.

<ver xmlns='urn:xmpp:features:rosterver'/>

名册版本流特性很少见所以永远不会是强制协商的.

请求

如果一个客户端支持名册版本并且它所连接的服务器如前一节说的那样声明支持名册版本, 那么该客户端应该在它对名册请求中包含'ver'元素. 如果该服务器没有声明支持名册版本, 客户端不能(MUST NOT)包含'ver'属性. 如果客户端在它的roster get中包含了'ver'属性, 它就把该属性的值设成了它最后的名册缓存中的相关的版本ID.

C: <iq from='romeo@example.net/home'
       id='r1h3vzp7'
       to='romeo@example.net'
       type='get'>
     <query xmlns='jabber:iq:roster' ver='ver14'/>
   </iq>

如果该客户端还未缓存该名册或该缓存遗失或被中断了, 但是该客户端希望引导名册版本的使用, 它必须把'ver'属性设为空字符串(即, ver="").

自然的, 如果该客户端不支持名册版本或不希望引导名册版本的使用, 它将不包含'ver'属性.

成功情景

无论名册从被客户端列举版本ID之后是否被修改过, 服务器必须要么如章节2.1.4(包含一个'ver'属性代表最后的版本)所述返回完整的名册,要么返回一个空的IQ-result(这样就表示任何名册的修改将通过roster pushes被发送, 如下所述). 通常, 除非返回完整名册 (1) 将比发送独立的roster pushes给客户端使用更少的带宽(例如, 如果名册只包含很少的条目) 或 (2) 服务器不能把版本ID关联到任何之前已经在文件中有的版本, 服务器应该发送一个空的IQ-result然后通过roster pushes发送修改(如果有的话).

S: <iq from='romeo@example.net'
       id='r1h3vzp7'
       to='romeo@example.net/home'
       type='result'/>
实现备注: 这个空的IQ-result不同于一个空的<query/>元素, 这和使用一个空名册是不同的.

如果允许使用名册版本并且名册从被客户端列举版本ID之后未被修改过, 服务器将简单地不发送任何roster pushes给客户端(直到并且除非在该客户端会话的生命周期中一些相关的事件触发一个roster push).

如果名册从被客户端列举版本ID之后被修改过, 那么该服务器必须为每一个从被客户端列举版本ID之后被修改过的条目发送一个roster push给该客户端. (我们把一个用于名册版本同步的被发送的roster push称为一个"暂时的roster push".)

定义: 一个"roster modification"表示对名册数据的任何改变并将导致一个roster push到已连接客户端. 所以, 和服务器对名册处理相关的内部状态将不会导致一个roster push到已连接客户端因而不需要改变版本.
S: <iq from='romeo@example.net'
       id='ah382g67'
       to='romeo@example.net/home'
       type='set'>
     <query xmlns='jabber:iq:roster' ver='ver34'>
       <item jid='tybalt@example.org' subscription='remove'/>
     </query>
   </iq>
 
S: <iq from='romeo@example.net'
       id='b2gs90j5'
       to='romeo@example.net/home'
       type='set'>
     <query xmlns='jabber:iq:roster' ver='ver42'>
       <item jid='bill@example.org' subscription='both'/>
     </query>
   </iq>
 
S: <iq from='romeo@example.net'
       id='c73gs419'
       to='romeo@example.net/home'
       type='set'>
     <query xmlns='jabber:iq:roster' ver='ver72'>
       <item jid='nurse@example.org'
             name='Nurse'
             subscription='to'>
         <group>Servants</group>
       </item>
     </query>
   </iq>
 
S: <iq from='romeo@example.net'
       id='dh361f35'
       to='romeo@example.net/home'
       type='set'>
     <query xmlns='jabber:iq:roster' ver='ver96'>
       <item jid='juliet@example.org'
             name='Juliet'
             subscription='both'>
         <group>VIPs</group>
       </item>
     </query>
   </iq>

这些"暂时的roster pushes"可以作如下理解:

  1. 想想客户端在它缓存的名册版本(假设是, "ver14")和新的名册版本(假设是, "ver96")之间有一个活跃的出席信息会话.
  2. 在此期间, 该客户端可能已经接收到一个和各种名册版本相关的roster pushes(它可能已经发生, 假设是, "ver51" 和 "ver79"). 无论如何, 这些roster pushes中的一些可能已经包含了同一个名册条目的中间更新(例如, 把对bill@example.org的订阅状态从"none"改为"to"又从"to"改为"both").
  3. 这个暂时的roster pushes将不包含所有中间步骤, 而只有在该客户端实际上离线的时候每个条目修改后的最终结果(这可能发生, 假设是, "ver34", "ver42", "ver72", 和 "ver96").

客户端必须和处理任何roster push一样处理一个"interim roster push"(实际上, 从客户端的视角不能说一个"暂时的"roster push和一个"live"的 roster push是不同的,因而当它没办法知道自己收到是否是暂时的roster pushes). 当重连后请求名册, 客户端应该请求和它在前一个会话中收到的最后的roster push相关的版本, 而不是它在前一个会话的开始时收到的roster result的相关版本.

当名册版本被允许, 服务器必须在每一个roster push中包含更新的名册版本. Roster pushes必须按照变更的顺序产生并且在一个roster push中包含的版本必须是唯一的. 即使客户端未在它的roster gets或sets 中包含'ver'属性, 服务器也应该在它发送给客户端的所有roster pushes和results中包含'ver'属性.

实现备注: 名册版本的指南和详细例子见XEP‑0237.

管理出席信息订阅

为了保护XMPP用户们的隐私, 出席信息仅向某个用户已经批准的其他实体披露. 当某个同意允许其他实体察看其出席信息时, 该实体被称为对该用户的出席信息有一个"订阅(subscription)". 一个对某用户的出席信息有订阅的实体或一个用户对其有出席信息订阅的实体被称为"联系人(contact)" (在本文中术语"联系人(contact)"在不严格的概念上也用于指代潜在的联系人或一个用户的名册中的任何条目).

在XMPP中, 一个订阅是超越出席信息会话存在的; 实际上, 直到联系人取消订阅或用户撤销之前已授权的订阅之前它都会持续存在. (这个模式不同于会话初始协议(SIP)中对出新信息订阅的使用, 参见SIP‑PRES.)

在XMPP中订阅的管理是通过发送包含特别定义的属性("subscribe", "unsubscribe", "subscribed", 和 "unsubscribed")的presence节来实现的.

实现备注: 当一个服务器处理或生成一个类型为"subscribe", "subscribed", "unsubscribe", 或 "unsubscribed"的出站出席信息节, 该服务器必须在出站出席信息节中标记发送的实体的纯JID <localpart@domainpart>, 而不是全JID <localpart@domainpart/resourcepart>. 这个规则的强制性简化了出席信息订阅的模式并且有助于防止出席信息泄露; 关于出席信息泄露的信息, 参见XMPP‑CORE的安全事项.

订阅状态在用户和联系人两边的名册中都可以被反映. 本章不覆盖每个和出席信息订阅相关的可能的情景, 而主要是讲述一个用户和一个联系人之间开启一个共同的订阅的协议流程. 关于订阅状态的完整细节可以在附录A中找到.

请求订阅

一个"订阅请求(subscription request)"是一个某用户为了得到永久性地订阅某个联系人出席信息的授权而发出的请求; 语法构成上它是一个'type'属性值为"subscribe"的presence节. 一个订阅请求由某用户客户端生成, 由(潜在的)联系人的服务器处理, 从表现上来看则是联系人通过联系人的客户端来处理. 以下章节描述其工作流程.

实现备注: 出席信息订阅请求被发送到可用的资源, 然而和订阅状态变更相关的名册推送被发送到感兴趣的资源. 所以, 如果一个资源希望同时接收到订阅请求和名册推送, 它必须同时发送初始出席信息和请求名册.

客户端生成出站订阅请求

一个用户的客户端发送一个类型为"subscribe"并且指定了一个潜在联系人的纯JID<contact@domainpart>为'to'地址的presence节,生成一个订阅请求.

UC: <presence id='xk3h1v69'
              to='juliet@example.com'
              type='subscribe'/>

当一个用户发送一个出席信息订阅请求给一个潜在的即时消息和出席信息联系人, 其'to'属性的值必须是一个纯JID <contact@domainpart>而不是一个全JID <contact@domainpart/resourcepart>, 因为预期的结果是该用户从该联系人的所有资源接收出席信息, 而不仅是在'to'属性中指定的特定资源. 纯JIDs的使用也简化了该用户的服务器和联系人的服务器的订阅过程, 出席信息探测, 和出席信息通知.

为了跟踪的目的, 客户端应该在一个出席信息订阅请求中包含一个'id'属性.

实现备注: 很多XMPP客户端在生成一个出站出席信息订阅的时候向用户提示关于潜在联系人的信息(例如, "handle" 以及期望的名册组),因而在发送该出站出席信息订阅请求之前发送一个roster set. 这个行为是可选的, 因为客户端在上传用户提供的关于该联系人的信息之前可以先等待从服务器接收初始的roster push. 服务器必须以任何顺序处理roster set和出站出席信息订阅请求(即, 以客户端生成的任何顺序).

服务器处理出站订阅请求

接收出站出席信息订阅请求之后, 该用户的服务器必须处理如下.

在处理该请求之前, 该用户的服务器必须检查包含在'to'属性中的JID的语法(无论如何, 据信一些现有的实现不执行这个检查). 如果该JID的格式是<contact@domainpart/resourcepart>而不是<contact@domainpart>, 该用户的服务器应该把该请求当成是指向该联系人的纯JID并据此修改'to'地址. 该服务器也可以遵循XMPP‑ADDR]定义的格式来检查该JID地址并可能返回一个<jid-malformed/>节错误.
如果潜在的联系人位于和该用户相同的服务器上, 那么在处理该订阅请求和递送它到本地联系人的时候该服务器必须遵循章节3.1.3定义的规则.
如果潜在的联系人位于一个远端服务器, 受本地服务策略的约束该用户的服务器必须接着按照核心XMPP节处理规则路由该节到那个远端的域. (这可能导致返回一个适当的节错误给该用户, 类似<remote-server-timeout/>.)

如上所述, 在本地递送或远程路由该出席信息订阅请求之前, 该用户的服务器必须给该出站订阅请求标记该用户的纯JID <user@domainpart>.

US: <presence from='romeo@example.net'
              id='xk3h1v69'
              to='juliet@example.com'
              type='subscribe'/>

如果该出席信息订阅请求不是本地递送或远程路由的(例如, 因为该请求格式异常, 该本地联系人不存在, 该远程服务器不存在, 尝试联系远端服务器超时, 或任何被该用户的服务器经历或确认的其他错误), 那么该用户的服务器必须返回一个适当的错误节给该用户. 例子如下.

US: <presence from='juliet@example.com'
              id='xk3h1v69'
              to='romeo@example.net'
              type='error'>
      <error type='modify'>
        <remote-server-not-found
            xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
      </error>
    </presence>

在本地递送或远程路由该出席信息订阅请求之后, 该用户的服务器必须接着发送一个roster push给所有该用户的感兴趣的资源, 包含带一个"none"订阅状态并且带一个表明订阅等待批准符号(一个值为"subscribe"的'ask'属性)的潜在联系人 .

US: <iq id='b89c5r7ib574'
        to='romeo@example.net/foo'
        type='set'>
      <query xmlns='jabber:iq:roster'>
        <item ask='subscribe'
              jid='juliet@example.com'
              subscription='none'/>
      </query>
    </iq>
 
US: <iq id='b89c5r7ib575'
        to='romeo@example.net/bar'
        type='set'>
      <query xmlns='jabber:iq:roster'>
        <item ask='subscribe'
              jid='juliet@example.com'
              subscription='none'/>
      </query>
    </iq>

如果一个远端联系人在可配置的时间内不批准或拒绝该订阅请求, 该用户的服务器应该基于一个实现特有的机制重新发送该订阅请求给该联系人(例如, 任何该用户的一个新资源可用的时候, 或在过了一个特定数量的时间之后); 这有助于从原有的订阅请求被路由到远程域发生的短暂的,无声的错误中恢复. 这样做的时候, 建议该服务器包含一个'id'属性,这样它能跟踪重发的订阅请求的应答.

服务器处理入站订阅请求

在处理入站出席信息订阅请求之前, 联系人的服务器应该检查包含在'to'属性中的JID的语法.如果该JID的格式是<contact@domainpart/resourcepart>而不是<contact@domainpart>, 该联系人的服务器应该把该请求当成是被指向该联系人的纯JID并据此修改'to'地址. 该服务器也可以遵循XMPP‑ADDR]定义的格式来检查该JID地址并可能返回一个<jid-malformed/>节错误.

处理入站出席信息订阅请求的时候, 联系人的服务器必须遵循以下规则:

1. 首先, 联系人的服务器不能(MUST NOT)以联系人的身份批准订阅请求 -- 除非该来联系人已经 (a) 如章节3.4所述预批准来自用户的订阅请求, (b) 配置它的账户自动批准订阅请求, 或 (c) 从它的服务器提供者那里接受了一个协议以允许自动批准(例如, 在一个企业部署之中通过一个雇佣协议). 否则, 如果一个订阅请求要求批准那么联系人的服务器必须递送那个请求到联系人的可用资源并由该联系人批准或拒绝.
2. 如果联系人存在并且该用户已经有一个对该联系人的出席信息的订阅, 那么该联系人的服务器必须把一个类型为"subscribed"的presence节从该联系人的纯JID发送到该用户的纯JID以代表该联系人自动应答. 同样, 如果该联系人以前发送过一个类型为"subscribed"的presence节并且联系人的服务器假设对于该用户的出席信息订阅是"预批准的"(见章节3.4), 那么该联系人的服务器也应该以该联系人的身份自动应答.
    CS: <presence from='juliet@example.com'
                  id='xk3h1v69'
                  to='romeo@example.net'
                  type='subscribed'/>
3. 另外, 如果在订阅请求被联系人的服务器接收到的时候该联系人有至少一个可用的资源, 那么该联系人的服务器必须依据第八章发送那个订阅请求给所有可用的资源. 作为获得该出席信息订阅请求的回执的一个方法, 该联系人的服务器可以从该联系人的纯JID发送一个类型为"unavailable"的presence节到该用户的纯JID(该用户的客户端不能(MUST NOT)假定这个回执提供了关于该联系人的出席信息, 因为它来自该联系人的纯JID并且在该订阅请求被批准之前就收到了).
4. 另外, 如果在订阅请求被联系人的服务器接收到的时候该联系人没有可用的资源, 那么该联系人的服务器必须保持一个完整的包括该订阅请求的presence节的记录, 包括其中任何扩展的内容(见XMPP‑CORE的章节8.4), 并且在该联系人接下来有一个可用的资源的时候递送该请求. 无论何时该联系人建立了一个可用的资源该联系人的服务器必须继续递送该订阅请求, 直到该联系人要么批准要么拒绝该请求. (当该联系人接下来有一个可用的资源的时候,该联系人的服务器不能(MUST NOT)从任何给定用户递送多个订阅请求; 例如, 如果该用户发送多个订阅请求到该联系人而此时该联系人是离线的, 该联系人的服务器应该只存储那些请求中的一个, 比如第一个请求或最后一个请求, 并且当该联系人接下来有一个可用资源的时候必须只递送这些请求中的一个; 这有助于防止"垃圾订阅请求".)
安全警告: 直到以及除非该联系人如章节3.1.4所述批准该订阅请求, 该联系人的服务器不能(MUST NOT)在该联系人的名册上添加该用户的条目.
安全警告: 对于联系人的服务器存储完整的出席信息订阅请求的节的强制要求带来了一个应用程序资源衰竭攻击的可能性(见DOS的章节2.1.2), 例如, 由一个流氓服务器或一个协调好的用户群(例如, 一个 botnet) 攻击该联系人的服务器或特定联系人. 提醒服务器实现者们关注这类攻击的可能性并为对抗它提供工具, 比如允许服务管理员设置服务器整体或任何给定联系人存储的入站出席信息订阅请求的数量或大小的限制.

客户端处理入站订阅请求

当一个可交互的客户端接收到一个订阅请求, 它必须把该请求展示给控制该客户端的自然人(即, 该 "联系人") 来批准, 除非该联系人被显式地配置成该客户端自动批准或拒绝如上所述的某些或全部订阅请求. 一个不是被自然人控制的自动客户端将有自己的应用特有的规则以批准或拒绝订阅请求.

客户端通过发送一个类型为"subscribed"的presence节来批准一个订阅请求, 联系人的服务器会作章节3.1.5所述的处理,用户的服务器会作章节3.1.6所述的处理.

CC: <presence id='h4v1c4kj'
              to='romeo@example.net'
              type='subscribed'/>

客户端通过发送一个类型为"unsubscribed"的presence节来拒绝一个订阅请求, 联系人的服务器和用户的服务器都会作章节3.2所述的处理.

CC: <presence id='tb2m1b59'
              to='romeo@example.net'
              type='unsubscribed'/>

为了跟踪的目的, 客户端应该在一个订阅批准或订阅拒绝中包含一个'id'属性; 这个'id'属性不能(MUST NOT)镜像该订阅请求的'id'属性.

服务器处理出站订阅批准

当联系人的客户端发送订阅批准的时候, 联系人的服务器必须以联系人的纯JID<contact@domainpart>标记这个出站的节并本地递送或远程路由该节到用户那里.

CS: <presence from='juliet@example.com'
              id='h4v1c4kj'
              to='romeo@example.net'
              type='subscribed'/>

联系人的服务器接着必须发送一个updated roster push(更新名册推送)给所有联系人的感兴趣的资源, 其中的'subscription'属性值设为"from". (这里假定该联系人未曾订阅该用户; 如果是那种情况(译者注:即该联系人已经订阅该用户), 则'subscription'属性值将设置为"both", 如附录A所述.)

CS: <iq id='a78b4q6ha463'
        to='juliet@example.com/balcony'
        type='set'>
      <query xmlns='jabber:iq:roster'>
        <item jid='romeo@example.net'
              subscription='from'/>
      </query>
    </iq>
 
CS: <iq id='x81g3bdy4n19'
        to='juliet@example.com/chamber'
        type='set'>
      <query xmlns='jabber:iq:roster'>
        <item jid='romeo@example.net'
              subscription='from'/>
      </query>
    </iq>

从联系人的视角来看, 现在存在一个来自用户的订阅, 这就是为什么'subscription'属性值要设为"from". (这里假定该联系人未曾订阅该用户; 如果是那种情况(译者注:即该联系人已经订阅该用户), 则'subscription'属性值将设置为"both", 如附录A所述.)

该联系人的服务器接着也必须从该联系人的可用资源发送当前的出席信息给给该用户.

CS: <presence from='juliet@example.com/balcony'
              id='pw72bc5j'
              to='romeo@example.net'/>
 
CS: <presence from='juliet@example.com/chamber'
              id='ux31da4q'
              to='romeo@example.net'/>

为了订阅该用户的出席信息, 该联系人需要发送一个订阅请求给该用户. (XMPP客户端经常会自动发送订阅请求而不是要求联系人发起这个订阅请求, 因为假定期望的结束状态是一个双向订阅.) 自然, 当该联系人发送一个订阅请求给该用户, 这个订阅状态将不同于前面示例中展示的状态(见 附录A) 并且角色将被反转.

服务器处理入站订阅批准

当用户的服务器收到一个订阅批准, 它必须首先检查是否该联系人在该用户的名册中并且 subscription='none' 或 subscription='from' 以及 'ask' 标记设为 "subscribe" (即, 订阅状态为 "None + Pending Out", "None + Pending Out+In", 或 "From + Pending Out"; 见 附录A). 如果这个检查成功了, 那么该用户的服务器必须:

1. 递送入站订阅批准到该用户的所有感兴趣的资源(这有助于给该用户的客户端关于订阅批准的正确的上下文,使得它们能分辨由该用户的另一个资源生成的名册推送和从联系人收到的订阅批准的不同). 这一步必须在下一步的发送名册推送之前去做.
    US: <presence from='juliet@example.com'
                  id='h4v1c4kj'
                  to='romeo@example.net'
                  type='subscribed'/>
2. 发起一个roster push给该用户的所有感兴趣的资源, 包含一个该;联系人的更新的名册条目,其'subscription'属性值设为"to" (如果订阅状态是 "None + Pending Out" 或 "None + Pending Out+In") 或 "both" (如果订阅状态是 "From + Pending Out").
    US: <iq id='b89c5r7ib576'
            to='romeo@example.net/foo'
            type='set'>
          <query xmlns='jabber:iq:roster'>
            <item jid='juliet@example.com'
                  subscription='to'/>
          </query>
        </iq>
 
    US: <iq id='b89c5r7ib577'
            to='romeo@example.net/bar'
            type='set'>
          <query xmlns='jabber:iq:roster'>
            <item jid='juliet@example.com'
                  subscription='to'/>
          </query>
        </iq>
3. 该用户的服务器也必须把从该联系人的每个可用资源收到的可用的出席信息节递送给该用户的每个可用资源.
    [ ... to resource1 ... ]
 
    US: <presence from='juliet@example.com/balcony'
                  id='pw72bc5j'
                  to='romeo@example.net'/>
 
    [ ... to resource2 ... ]
 
    US: <presence from='juliet@example.com/balcony'
                  id='pw72bc5j'
                  to='romeo@example.net'/>
 
    [ ... to resource1 ... ]
 
    US: <presence from='juliet@example.com/chamber'
                  id='ux31da4q'
                  to='romeo@example.net'/>
 
    [ ... to resource2 ... ]
 
    US: <presence from='juliet@example.com/chamber'
                  id='ux31da4q'
                  to='romeo@example.net'/>
实现备注: 如果在收到入站订阅批准通知的时候该用户的帐号没有可用资源, 该用户的服务器可以保持该通知的记录(理想的是完整的出席信息节)并且等到该帐号有一个可用资源的时候再递送给它. 这一行为向用户提供了当用户离线的时候发生名册变更的原因的更完整的信息.

另外 -- 就是, 如果该用户不存在, 如果该联系人不在该用户的名册中, 或如果该联系人在该用户的名册中但是订阅状态不在前述的检查中描述的情况之中 -- 那么该用户的服务器必须安静地忽略该订阅批准通知而不递送给该用户, 也不修改该用户的名册, 也不生成名册推送到该用户的感兴趣的资源.

从该用户的角度来看, 现在存在一个到该联系人的出席信息的订阅(这就是为什么'subscription'属性值要设为"to").

撤销被订阅

客户端生成撤销被订阅

如果一个联系人想撤销它之前接受某个用户订阅的授权, 以撤销一个预批准的被订阅(章节3.4), 或拒绝一个订阅请求, 它发送一个类型为"unsubscribed"的出席信息节.

CC: <presence id='ij5b1v7g'
              to='romeo@example.net'
              type='unsubscribed'/>

服务器处理出站被订阅撤销

在接收到出站被订阅撤销之后, 该联系人的服务器必须做如下处理.

  1. 如果该用户的纯JID还不在该联系人的名册或在联系人名册中的状态是"None", "None + Pending Out", 或 "To", 该联系人的服务器不能(SHOULD NOT)路由或递送类型为"unsubscribed"的出席信息节给该用户,并且不能(MUST NOT)发送接下来所说的类型为"unavailable"的出席信息通知给该用户.
  2. 如果该用户的纯JID在该联系人的名册的状态为"None", "None + Pending Out", 或 "To" 并且 'approved' 标记被设为 "true" (所以代表了章节3.4所述的订阅预批准), 该联系人的服务器必须移除该 预批准 并且不能(MUST NOT)路由或递送类型为"unsubscribed"的出席信息节给该用户.
  3. 否则, 按如下的示例, 该联系人的服务器必须同时路由或递送类型为"unavailable"的出席信息通知和类型为"unsubscribed"的出席信息节给该用户,并且必须发送一个名册推送给该联系人.

因为该用户仍然订阅了该联系人的出席信息(即, 在该联系人的服务器路由或递送类型为"unsubscribed"的出席信息给该用户之前), 该联系人的服务器必须从该联系人的所有在线资源发送一个类型为"unavailable"的出席信息节给该用户.

CS: <presence from='juliet@example.com/balcony'
              id='i8bsg3h3'
              type='unavailable'/>
 
CS: <presence from='juliet@example.com/chamber'
              id='bvx2c9mk'
              type='unavailable'/>

然后该联系人的服务器必须路由或递送类型为"unsubscribed"的出席信息节给该用户, 确保在出站被订阅取消要带上该联系人的纯JID<contact@domainpart>.

CS: <presence from='juliet@example.com'
              id='ij5b1v7g'
              to='romeo@example.net'
              type='unsubscribed'/>

该联系人从的服务器必须发送一个带有更新的名册条目的名册推送给该联系人的所有感兴趣的资源, 这里订阅状态现在要么是"none"要么是"to" (见附录A).

CS: <iq id='pw3f2v175b34'
        to='juliet@example.com/balcony'
        type='set'>
      <query xmlns='jabber:iq:roster'>
        <item jid='romeo@example.net'
              subscription='none'/>
      </query>
    </iq>
 
CS: <iq id='zu2y3f571v35'
        to='juliet@example.com/chamber'
        type='set'>
      <query xmlns='jabber:iq:roster'>
        <item jid='romeo@example.net'
              subscription='none'/>
      </query>
    </iq>

服务器处理入站被订阅撤销

取消订阅

客户端生成取消订阅

服务器处理出站取消订阅

服务器处理入站取消订阅

预批准被订阅请求

客户端生成被订阅预批准

服务器处理被订阅预批准

个人工具