原文来自:https://xmpp.org/extensions/xep-0363.html,只翻译了技术方面的内容。
摘要:这个规范定义了一种协议,用于从另一个实体请求权限上传文件到HTTP服务器指定的目录,同时接收到一个URL,以后就可以从该URL再次下载该文件。
状态:草案。
注意:这里定义的协议是XMPP标准基金会的一个标准草案。我们鼓励实现这个协议,协议适合在生产系统中部署,但是在协议变成最终标准前可以对其进行一些修改。
类型:Standards Track(可参考RFC 2026)
版本:1.0.0(2020-02-11)
1.介绍
XMPP扩展协议定义了点对点文件传输的方法,例如 SI File Transfer (XEP-0096)或Jingle File Transfer (XEP-0234),但是由于它们是点对点的,所以在需要将文件一次性发送给多个收件人或同一收件人多个时,它们的性能不是很好。它们同样不能和离线存储,MUC历史记录和消息存档管理(XEP-0313) 一起工作。
手动上传文件到HTTP服务器并共享链接,这是一个长期以来的解决方案。虽然用户根据这种手动方法缺点中选择各种服务,但是XMPP客户端不能自动代表用户自动执行此过程,因为跟这些服务不是共享一个公共API。此外,使用第三方服务可能需要用户向XMPP客户端输入额外的凭证,热别是文件上传。
这个XEP定义了一个方法用于用其它实体请求权限上传文件到HTTP服务器的指定路径,并且同时接受一个能够下载文件的URL。这些由PUT和GET-URL组成的元组称为槽(slot)。
2.要求
(1)容易实现。这是基于这样的一个想法,就是大多数编程语言已经有了可用的HTTP库。
(2)不可知实际URL的分布。用户可以选择发送包含URL在body的message stanza,使用Out-of-Band Data (XEP-0066), Jingle HTTP Transport Method (XEP-0370),甚至可以使用它作为在 User Avatar (XEP-0084)的头像。
(3)任何知道这个URL的人都可以访问。
3.发现支持(这里的发现是名词,指的是服务发现)
实体通过在启服务发现信息特性中包含'urn:xmpp:http:upload:0'来通知对该协议的支持,服务发现如:Service Discovery (XEP-0030)或 Entity Capabilities (XEP-0115)。为了避免非必要的往返,实体应该包含在Service Discovery Extensions (XEP-0128)中定义的最大文件尺寸,当然是在该属性存在时才返回。这个字段是'max-file-size'并且值是以bytes为单位。
用户服务器应该将提供此类服务的任何已知实体包含在其服务发现项中。
Example 1:客户端发送服务发现请求给服务器 <iq from='romeo@montague.tld/garden' id='step_01' to='montague.tld' type='get'> <query xmlns='http://jabber.org/protocol/disco#items'/> </iq>
Example 2:服务器回复服务发现请求 <iq from='montague.tld' id='step_01' to='romeo@montague.tld/garden' type='result'> <query xmlns='http://jabber.org/protocol/disco#items'> <item jid='upload.montague.tld' name='HTTP File Upload' /> <item jid='conference.montague.tld' name='Chatroom Service' /> </query> </iq>
Example 3:客户端发送服务发现请求给上传服务器 <iq from='romeo@montague.tld/garden' id='step_02' to='upload.montague.tld' type='get'> <query xmlns='http://jabber.org/protocol/disco#info'/> </iq>
Example 4:上传服务回复服务发现请求并且报告最大尺寸为5MB <iq from='upload.montague.tld' id='step_02' to='romeo@montague.tld/garden' type='result'> <query xmlns='http://jabber.org/protocol/disco#info'> <identity category='store' type='file' name='HTTP File Upload' /> <feature var='urn:xmpp:http:upload:0' /> <x type='result' xmlns='jabber:x:data'> <field var='FORM_TYPE' type='hidden'> <value>urn:xmpp:http:upload:0</value> </field> <field var='max-file-size'> <value>5242880</value> </field> </x> </query> </iq>
4.请求槽
客户端通过发送一个IQ-get给上传服务来请求一个新的上传槽,这个IQ-get包含一个<request>子元素,定义了'urn:xmpp:http:upload:0'的命名空间。
包含内容类型的属性content-type是可选的。
Example 5:客户端请求在上传服务的槽 <iq from='romeo@montague.tld/garden' id='step_03' to='upload.montague.tld' type='get'> <request xmlns='urn:xmpp:http:upload:0' filename='très cool.jpg' size='23456' content-type='image/jpeg' /> </iq>
上传服务响应一个以<slot>元素包装的PUT和GET URL。服务应该保持文件名特别是文件结尾完整。对PUT和GET使用相同的主机名是可选的。主机必须提供TLS(RFC 5246 )。所有的HTTPS URL都必须遵守RFC 3986。非ASCII字符必须使用百分号转义。
<put>元素可能包含一系列的<header>元素,它指向HTTP头部字段。每个<header>元素必须有name属性和header值的内容。只有如下header name被允许,Authorization, Cookie, Expires。其它的必须要被请求的实体忽略,并且不能在HTTP请求中存在。请求的实体在执行HTTP请求以前必须从header name和value中删除任何换行字符。
Example 6:上传服务响应槽 <iq from='upload.montague.tld' id='step_03' to='romeo@montague.tld/garden' type='result'> <slot xmlns='urn:xmpp:http:upload:0'> <put url='https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/tr%C3%A8s%20cool.jpg'> <header name='Authorization'>Basic Base64String==</header> <header name='Cookie'>foo=bar; user=romeo</header> </put> <get url='https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/tr%C3%A8s%20cool.jpg' /> </slot> </iq>
5.错误条件
当请求文件尺寸太大时服务可能反馈一个错误,而不是提供客户端一个slot。此外,实体可能会通知请求方最大文件尺寸。
Example 7:如果文件太大 <iq from='upload.montague.tld' id='step_03' to='romeo@montague.tld/garden' type='error'> <request xmlns='urn:xmpp:http:upload:0' filename='très cool.jpg' size='23456' content-type='image/jpeg' /> <error type='modify'> <not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas' /> <text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>File too large. The maximum file size is 20000 bytes</text> <file-too-large xmlns='urn:xmpp:http:upload:0'> <max-file-size>20000</max-file-size> </file-too-large> </error> </iq>
对于任何其它类型的错误,服务应该使用适当的错误类型来指示临时或永久的错误。
对于临时错误,如超过个人配额,服务可能包含一个<retry/>元素,该元素由'urn:xmpp:http:upload:0'命名空间限定,为<error/>的子元素。retry元素必须包含一个'stamp'属性,指定请求实体可以重试的时间。时间戳的格式必须符合在XMPP Date and Time Profiles (XEP-0082)指定的data-time格式且以UTC显示。
Example 8:上传服务在客户端超过配额后提示一个临时错误 <iq from='upload.montague.tld' id='step_03' to='romeo@montague.tld/garden' type='error'> <request xmlns='urn:xmpp:http:upload:0' filename='très cool.jpg' size='23456' content-type='image/jpeg' /> <error type='wait'> <resource-constraint xmlns='urn:ietf:params:xml:ns:xmpp-stanzas' /> <text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>Quota reached. You can only upload 5 files in 5 minutes</text> <retry xmlns='urn:xmpp:http:upload:0' stamp='2017-12-03T23:42:05Z' /> </error> </iq>
Example 9:上传服务提示客户端不被允许上传文件 <iq from='upload.montague.tld' id='step_03' to='romeo@montague.tld/garden' type='error'> <request xmlns='urn:xmpp:http:upload:0' filename='très cool.jpg' size='23456' content-type='image/jpeg' /> <error type='auth'> <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas' /> <text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>Only premium members are allowed to upload files</text> </error> </iq>
6.上传
文件实际上是通过HTTP-PUT上传的。如果内容长度不匹配slot请求的大小,上传服务必须拒绝文件上传。如果与预先指定的内容类型不匹配,服务应该拒绝文件。如果客户端没有指定content-type,则服务假定为application/octet-stream的类型。
此外,除了Content-Length和Content-Type的头部,客户端还必须包含slot分配附带的所有允许的header。
上传服务和客户端之间不需要进一步的XMPP通信。HTTP状态码201代表服务器现在可以通过GET URL来提供文件。如果上传失败,无论是什么原因,客户端可能会请求一个新的slot。
7.实现的注意事项
上传服务应该为PUT URl的有效期选择一个适当的超时时间。因为客户端没有理由在请求slot和开始上传的这段时间内等待,建议设置一个相对较低的超时时间,在300s左右。
为了使HTTP上传工作在web客户端进行(包括托管在不同域名中的客户端),上传服务应该设置适当的CORS-Headers。可能包括Access-Control-Allow-Origin, Access-Control-Allow-Methods 和 Access-Control-Allow-Headers。对于使用自定义授权或cookie请求头的HTTP上传服务,Access-Control-Allow-Credentials也很重要。
一些零散的CORS Header: Access-Control-Allow-Origin: * Access-Control-Allow-Methods: OPTIONS, HEAD, GET, PUT Access-Control-Allow-Headers: Authorization, Content-Type Access-Control-Allow-Credentials: true
8.安全注意事项
8.1 服务端
注意:本节不规范,当将来一般的web安全建议发生变化时,它可能会发生改变。
建议运行GET请求的HTTP上传域名与其它基于HTTP的服务适当隔离,避免用户生成的恶意脚本在上述服务上下文中执行。隔离技术包括但不限于设置Content-Secutity-Policy。
HTTP上传服务器头部,以获得更好地内容安全策略
Content-Security-Policy: default-src 'none'; frame-ancestors 'none';
提供的策略会禁止浏览器执行来自HTTP上传域名的所有活动内容(default-src 'none'),并且禁止从其它页面中嵌入这些内容(frame-ancestors 'none')。关于Content-Secutity-Policy的更多信息可查询infosec.mozilla.org。
更进一步,我们可以将这些文件存放在完全不同的域名中,而不是使用子域名。
8.2 上传者
(1)在发出PUT请求之前,请求实体必须从HTTP header name和value中删除换行字符。
(2)请求实体必须确保只有本文中允许的Headers(Authorization, Cookie, Expires)才能从slot响应复制到HTTP请求中。
8.3 常规情况
(1)服务实现者应该在URL中使用长随机部分,这样就不可能猜测到任意文件的位置了。
(2)实现者应该知道,如果没有额外的点对点加密,文件上传到本文档中描述的服务中会以明文的形式存储。建议客户端实现者仅用于半公开文件(如公共MUC或PEP头像)或实现适合的点对点加密。
(3)启动和下载文件会泄露客户端的ip地址给HTTP服务器。HTTP服务器可能与客户端当前连接的XMPP服务不同。
9.INNA注意事项
本XEP不需要和Internet Assigned Numbers Authority (IANA)交互。
10.XMPP注册注意事项
本规范定义了下面的XML命名空间:
urn:xmpp:http:upload:0
当本规范从Experimental状态发展到Draft状态时,XMPP Registrar应该将上述命名空间添加到https://xmpp.org/registrar/namespaces.html,就像 XMPP Registrar Function (XEP-0053) 第四节描述的一样。
10.XML文档结构
<xml version="1.0" encoding="utf8"> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:xmpp:http:upload:0" xmlns="urn:xmpp:http:upload:0"> <xs:element name="request"> <xs:complexType> <xs:attribute name="filename" type="xs:string" use="required"/> <xs:attribute name="size" type="xs:positiveInteger" use="required"/> <xs:attribute name="content-type" type="xs:string" use="optional"/> </xs:complexType> </xs:element> <xs:element name="slot"> <xs:complexType> <xs:sequence> <xs:element name="put" minOccurs="1" maxOccurs="1"> <xs:complexType> <xs:attribute name="url" type="xs:string" use="required"/> <xs:sequence> <xs:element name="header" minOccurs="0" maxOccurs="unbounded" type="xs:string"> <xs:complexType> <xs:attribute name="name" use="required"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="Authorization"/> <xs:enumeration value="Cookie"/> <xs:enumeration value="Expires"/> </xs:restriction> </xs:simpleType> </xs:attribute> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="get" minOccurs="1" maxOccurs="1"> <xs:complexType> <xs:attribute name="url" type="xs:string" use="required"/> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="file-too-large"> <xs:complexType> <xs:sequence> <xs:element name="max-file-size" type="xs:positiveInteger" minOccurs="0" maxOccurs="1"/> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="retry"> <xs:complexType> <xs:attribute name="stamp" type="xs:string" use="required"/> </xs:complexType> </xs:element> </xs:schema>