<?xml version="1.0" encoding="utf-8"?>
<!-- name="GENERATOR" content="github.com/mmarkdown/mmark Mmark Markdown Processor - mmark.miek.nl" -->
<rfc version="3" ipr="trust200902" docName="draft-ietf-jmap-filenode-01" submissionType="IETF" category="std" xml:lang="en" xmlns:xi="http://www.w3.org/2001/XInclude" updates="8620" indexInclude="true" consensus="true">

<front>
<title abbrev="JMAP FileNode">JMAP File Storage extension</title><seriesInfo value="draft-ietf-jmap-filenode-01" stream="IETF" status="standard" name="Internet-Draft"></seriesInfo>
<author role="editor" initials="B." surname="Gondwana" fullname="Bron Gondwana"><organization>Fastmail</organization><address><postal><street>Level 2, 114 William St</street>
<city>Melbourne</city>
<code>VIC 3000</code>
<country>Australia</country>
</postal><email>brong@fastmailteam.com</email>
<uri>https://www.fastmail.com</uri>
</address></author><date year="2025" month="July" day="7"></date>
<area>Applications</area>
<workgroup>JMAP</workgroup>
<keyword>jmap</keyword>

<abstract>
<t>The JMAP base protocol (RFC8620) provides the ability to upload and download
arbitrary binary data.  This binary data is called a &quot;blob&quot;, and can be used
in all other JMAP extensions.</t>
<t>This extension adds a method to expose blobs as a filesystem along with the
types of metadata that are provided by other remote filesystem protocols.</t>
</abstract>

</front>

<middle>

<section anchor="introduction"><name>Introduction</name>
<t>JMAP (<xref target="RFC8620"></xref> <u format="char-num">—</u> JSON Meta Application Protocol) is a generic
protocol for synchronizing data between a client and a server.
It is optimized for mobile and web environments, and aims to
provide a consistent interface to different data types.</t>
<t>In the same way that JMAP Calendars (<xref target="I-D.ietf-jmap-calendars"></xref>) replaces
CalDAV (<xref target="RFC5545"></xref>) and JMAP Contacts (<xref target="I-D.ietf-jmap-contacts"></xref>) replaces
CardDAV (<xref target="RFC6352"></xref>), this document replaces the use of WebDAV (<xref target="RFC4918"></xref>)
for remote filesystem access.</t>
</section>

<section anchor="conventions-used-in-this-document"><name>Conventions Used In This Document</name>
<t>The key words &quot;MUST&quot;, &quot;MUST NOT&quot;, &quot;REQUIRED&quot;, &quot;SHALL&quot;, &quot;SHALL
NOT&quot;, &quot;SHOULD&quot;, &quot;SHOULD NOT&quot;, &quot;RECOMMENDED&quot;, &quot;NOT RECOMMENDED&quot;,
&quot;MAY&quot;, and &quot;OPTIONAL&quot; in this document are to be interpreted as
described in BCP 14 <xref target="RFC2119"></xref> <xref target="RFC8174"></xref> when, and only when,
they appear in all capitals, as shown here.</t>
<t>The definitions of JSON keys and datatypes in the document follow
the conventions described in the core JMAP specification <xref target="RFC8620"></xref>.</t>
</section>

<section anchor="addition-to-the-capabilities-object"><name>Addition to the Capabilities Object</name>
<t>The capabilities object is returned as part of the JMAP Session
object; see <xref target="RFC8620"></xref>, Section 2.</t>
<t>This document defines an additional capability URI.</t>

<section anchor="urn-ietf-params-jmap-filenode"><name>urn:ietf:params:jmap:filenode</name>
<t>The capability <tt>urn:ietf:params:jmap:filenode</tt> being present in the
&quot;accountCapabilities&quot; property of an account represents support
for the FileNode datatype.  Servers that include the capability
in one or more &quot;accountCapabilities&quot; properties MUST also include
the property in the &quot;capabilities&quot; property.</t>
<t>The value of this property in the JMAP session &quot;capabilities&quot;
property MUST be an empty object.</t>
<t>The value of this property in an account's &quot;accountCapabilities&quot;
property is an object that MUST contain the following information
on server capabilities and permissions for that account:</t>

<ul>
<li><t>maxFileNodeDepth: &quot;UnsignedInt|null&quot;</t>
<t>The maximum depth of the FileNode hierarchy (i.e., one more than
the maximum number of ancestors a FileNode may have), or null for
no limit.</t>
</li>
<li><t>maxSizeFileNodeName: &quot;UnsignedInt&quot;:</t>
<t>The maximum length, in (UTF-8) octets, allowed for the name of a
FileNode.  This MUST be at least 100, although it is recommended
servers allow more.</t>
</li>
<li><t>fileNodeQuerySortOptions: &quot;String[]&quot;</t>
<t>A list of all the values the server supports for the &quot;property&quot;
field of the Comparator object in an &quot;FileNode/query&quot; sort (see
Section XXX).  This MAY include properties the client does not
recognise (for example, custom properties specified in a vendor
extension).  Clients MUST ignore any unknown properties in the
list.</t>
</li>
<li><t>mayCreateTopLevelFileNode: &quot;Boolean&quot;</t>
</li>
</ul>
<t>If true, the user may create a FileNode (see Section XXX) in this
   account with a null parentId.  (Permission for creating a child of
   an existing FileNode is given by the &quot;myRights&quot; property on that
   FileNode.)</t>

<section anchor="capability-example"><name>Capability Example</name>
<t>TODO</t>
</section>
</section>
</section>

<section anchor="filenode-data-type"><name>FileNode Data Type</name>
<t>A FileNode is a set of metadata which behaves similar to an inode in
a filesystem.  In <xref target="RFC4918"></xref> terminology a FileNode can refer to either
a collection or a resource.</t>
<t>The following JMAP Methods are selected by the
<tt>urn:ietf:params:jmap:filenode</tt> capability.</t>

<section anchor="filenode-objects"><name>FileNode objects</name>
<t>The filenode object has the following keys:</t>

<ul>
<li><t>id: &quot;Id&quot; (immutable; server-set)</t>
<t>The Id of the FileNode.</t>
</li>
<li><t>parentId: &quot;Id|null&quot;</t>
<t>The Id of the parent node, or null if this is the root node.</t>
</li>
<li><t>blobId: &quot;Id|null&quot;</t>
<t>The blobId for the content of this node, or null if this node is a collection.  NOTE the
zero byte file MUST have a non-null blobId.</t>
</li>
<li><t>size: &quot;UnsignedInt|null&quot; (server-set)</t>
<t>The size in bytes of the associated blob data.  This must be null if, and only if, the blobId is null.</t>
</li>
<li><t>name: &quot;String&quot;</t>
<t>User-visible name for the FileNode.  This MUST be a
Net-Unicode string <xref target="RFC5198"></xref> of at least 1 character in length,
subject to the maximum size given in the capability object.  There
MUST NOT be two sibling Mailboxes with both the same parent and
the same name.  Servers MAY reject names that violate server
policy (e.g., names containing control characters).  Further:</t>

<ul spacing="compact">
<li>The name MUST NOT be &quot;.&quot; or &quot;..&quot;</li>
<li>The name MUST NOT contain a &quot;/&quot;</li>
</ul></li>
<li><t>type: &quot;String|null&quot;</t>
<t>The media type of the FileNode. This MUST be <tt>null</tt> if, and only if, the node does not have a <tt>blobId</tt>.</t>
<t>Valid values are found in the IANA media-types registry.</t>
<t>Servers MUST NOT reject media types that are not recognised.</t>
<t>Servers MUST reject media types if the value does not conform to the ABNF of <xref target="RFC6938"></xref> Section 4.2.</t>
</li>
<li><t>created: &quot;UTCDate&quot; (default: current server time)</t>
<t>The date the node was created.</t>
</li>
<li><t>modified: &quot;UTCDate&quot; (default: current server time)</t>
<t>The date the node was last updated.
NOTE: this is not updated by the server, clients must store a new value when making changes.</t>
</li>
<li><t>accessed: &quot;UTCDate&quot; (default: current server time)</t>
<t>The date the node was last accessed.
NOTE: this is not updated by the server, clients must store a new value.  See Implementation Considerations
for comments on the use of this field.</t>
</li>
<li><t>executable: &quot;Boolean&quot; (default: false)</t>
<t>If true, the file is should be treated as an executable by operating systems that support this flag.</t>
</li>
<li><t>myRights: &quot;FilesRights&quot; (server-set)</t>
<t>The set of rights (ACLs) the user has in relation to this folder. A <strong>FilesRights</strong> object has the following properties:</t>

<ul spacing="compact">
<li>mayRead: <tt>Boolean</tt> The user may read the contents of this node.</li>
<li>mayWrite: <tt>Boolean</tt> The user may modify the properties of this node, including renaming children.</li>
<li>mayAdmin: <tt>Boolean</tt> The user may change the sharing of this node (see <xref target="I-D.ietf-jmap-sharing"></xref>)</li>
</ul></li>
<li><t>shareWith: &quot;String[FilesRights]|null&quot;</t>
<t>A map of userId to rights for users this node is shared with. The owner of the node MUST NOT be in this set. This is <tt>null</tt> if the user requesting the object does not have <tt>myRights.mayAdmin</tt>, or if the node is not shared with anyone.</t>
</li>
</ul>
</section>

<section anchor="filenode-methods"><name>FileNode Methods</name>

<section anchor="filenode-set"><name>FileNode/set</name>
<t>This is a standard Foo/set method, except for some things:</t>
<t>An additional top level argument:</t>

<ul spacing="compact">
<li>onDestroyRemoveChildren: &quot;Boolean&quot; (default: <tt>false</tt>)</li>
</ul>
<t>If false, an attempt to destroy a FileNode which is the parentId of another FileNode will be rejected with a <tt>nodeHasChildren</tt> error.  NOTE: if the other nodes are also been destroyed in the same operation, then the server MUST NOT return this error.  Servers must either sort the destroys children before parents, or only check this constraint on the final state, remembering that JMAP <tt>set</tt> operations must be atomic.</t>
<t>If true, then all child nodes will also be destroyed when a node is destroyed.</t>
<t>Further, since parentId creates a tree structure, an attempt to move a node to a parent for which this node is also an ancestor is an error, and an <tt>invalidProperties</tt> error will be returned.</t>
</section>

<section anchor="filenode-get"><name>FileNode/get</name>
<t>This is a standard Foo/get method.</t>
</section>

<section anchor="filenode-changes"><name>FileNode/changes</name>
<t>This is a standard Foo/changes method.</t>
</section>

<section anchor="filenode-query"><name>FileNode/query</name>
<t>This is a standard Foo/query method except for the following:</t>
<t>There's one more property to the query:</t>

<ul>
<li><t>depth: &quot;UnsignedInt|null&quot;</t>
<t>The number of levels of subdiretories to recurse into.  If absent, null, or zero, do not recurse.</t>
</li>
</ul>
<t>The following filter criteria are defined:</t>

<ul>
<li><t>hasParentId: &quot;Boolean&quot;</t>
<t>If true, the node must have a non-null parentId (i.e. is not a root node).</t>
</li>
<li><t>parentId: &quot;Id&quot;</t>
<t>A FileNode id. A node must have a parentId equal to this to match the condition.</t>
</li>
<li><t>ancestorId: &quot;Id&quot;</t>
<t>A FileNode id. A node must have an ancestor (parent, parent of parent, etc.) with an id equal to this to to match the condition.</t>
</li>
<li><t>hasType: &quot;Boolean&quot;</t>
<t>If <tt>true</tt>, the FileNode must be a file/resource, not a directory/collection.</t>
</li>
<li><t>blobId: &quot;Id&quot;</t>
<t>A FileNode must have a blobId equal to this to match the condition.</t>
</li>
<li><t>isExecutable: &quot;Boolean&quot;</t>
<t>If <tt>true</tt>, the FileNode must have a true executable value.</t>
</li>
<li><t>createdBefore: &quot;UTCDate&quot;</t>
<t>The creation date of the node (as returned on the FileNode object) must be before this date to match the condition.</t>
</li>
<li><t>createdAfter: &quot;UTCDate&quot;</t>
<t>The creation date of the node (as returned on the FileNode object) must be on or after this date to match the condition.</t>
</li>
<li><t>modifiedBefore: &quot;UTCDate&quot;</t>
<t>The modified date of the node (as returned on the FileNode object) must be before this date to match the condition.</t>
</li>
<li><t>modifiedAfter: &quot;UTCDate&quot;</t>
<t>The modified date of the node (as returned on the FileNode object) must be on or after this date to match the condition.</t>
</li>
<li><t>accessedBefore: &quot;UTCDate&quot;</t>
<t>The accessed date of the node (as returned on the FileNode object) must be before this date to match the condition.</t>
</li>
<li><t>accessedAfter: &quot;UTCDate&quot;</t>
<t>The accessed date of the node (as returned on the FileNode object) must be on or after this date to match the condition.</t>
</li>
<li><t>minSize: &quot;UnsignedInt&quot;</t>
<t>The size of the node in bytes (as returned on the FileNode object) must be equal to or greater than this number to match the condition.</t>
</li>
<li><t>maxSize: &quot;UnsignedInt&quot;</t>
<t>The size of the node in bytes (as returned on the FileNode object) must be less than this number to match the condition.</t>
</li>
<li><t>name: &quot;String&quot;</t>
<t>A FileNode must have exactly the same octets in its name property to match the condition.</t>
</li>
<li><t>nameMatch: &quot;String&quot;</t>
<t>Does a glob match of the specified name against the <em>name</em> property of the node.</t>
</li>
<li><t>type: &quot;String&quot;</t>
<t>A FileNode must have exactly the same octets in its type property to match the condition</t>
</li>
<li><t>typeMatch: &quot;String&quot;</t>
<t>Does a glob match of the specified type against the <em>type</em> property of the node.</t>
</li>
</ul>
<t>It also supports the following additional sort properties:</t>

<ul>
<li><t>tree:</t>
<t>Sort by tree; which means by name, but any directory/collection node is immediately followed by the recursive application of the same sort to its child nodes.  This is similar to the output of the <tt>find</tt> command on a filesystem with the depth parameter provided above.</t>
</li>
<li><t>hasType:</t>
<t>Sort directories before files (false sorts before true)</t>
</li>
<li><t>type:</t>
<t>Sorts directories first, and sorts by media type for files</t>
</li>
</ul>
</section>

<section anchor="filenode-querychanges"><name>FileNode/queryChanges</name>
<t>This is a standard Foo/queryChanges method.</t>
</section>
</section>
</section>

<section anchor="security-considerations"><name>Security considerations</name>
<t>TODO: lots of &quot;filesystems are risky&quot; - I guess look at the referenced
RFCs and what they said.</t>
</section>

<section anchor="iana-considerations"><name>IANA considerations</name>

<section anchor="jmap-capability-registration-for-filenode"><name>JMAP Capability registration for &quot;filenode&quot;</name>
<t>IANA is requested to register the &quot;filenode&quot; JMAP Capability as follows:</t>
<t>Capability Name: urn:ietf:params:jmap:filenode</t>
<t>Specification document: this document</t>
<t>Intended use: common</t>
<t>Change Controller: IETF</t>
<t>Security and privacy considerations: this document, Section XXX</t>
</section>

<section anchor="jmap-error-codes-registration-for-nodehaschildren"><name>JMAP Error Codes registration for &quot;nodeHasChildren&quot;</name>
<t>IANA is requested to register the &quot;nodeHasChildren&quot; JMAP Error Code as follows:</t>
<t>JMAP Error Code: nodeHasChildren</t>
<t>Intended use: common</t>
<t>Change Controller: IETF</t>
<t>Description: The node being destroyed is still referenced by other nodes which have not been destroyed.</t>
<t>Reference: this document</t>
</section>

<section anchor="jmap-data-types-registration-for-filenode"><name>JMAP Data Types registration for &quot;FileNode&quot;</name>
<t>IANA is requested to register the &quot;FileNode&quot; JMAP Data Type as follows:</t>
<t>Type Name: FileNode</t>
<t>Can Reference Blobs: Yes</t>
<t>Can Use For State Change: Yes</t>
<t>Capability: urn:ietf:params:jmap:filenode</t>
<t>Reference: this document</t>
</section>
</section>

<section anchor="todo"><name>TODO</name>

<ul>
<li><t>support SYMLINK types <xref target="RFC4437"></xref></t>
</li>
<li><t>design and document the capabilities object</t>
</li>
<li><t>create real-world clients to test this</t>
</li>
<li><t>security considerations</t>
</li>
<li><t>a way to get or query all ancestor nodes</t>
</li>
<li><t>QUESTION: should all the file-related fields be embedded in a sub-object?  There's lots
of &quot;must be NULL if and only-if this other field is also NULL&quot; - we could enforce that
more easily with a sub-object.</t>
</li>
<li><t>We need to address how shareWith and myRights expiration are done; because both a potential <tt>fullPath</tt> and the real <tt>myRights</tt> depend on changes to parent nodes.</t>
</li>
</ul>
</section>

<section anchor="changes"><name>Changes</name>
<t>EDITOR: please remove this section before publication.</t>
<t>The source of this document exists on github at: <eref target="https://github.com/brong/draft-gondwana-jmap-filenode/">https://github.com/brong/draft-gondwana-jmap-filenode/</eref></t>
<t><strong>draft-ietf-jmap-filenode-01</strong></t>

<ul spacing="compact">
<li>Refreshing draft only</li>
</ul>
<t><strong>draft-ietf-jmap-filenode-00</strong></t>

<ul spacing="compact">
<li>upload as a working group document</li>
</ul>
<t><strong>draft-gondwana-jmap-filenode-01</strong></t>

<ul spacing="compact">
<li>require a blobId for the zero-byte file</li>
<li>make size also null for collections</li>
<li>add more to the TODO section</li>
<li>bikeshed; FileNode</li>
<li>correct UTCDate, UnsignedInt, and normalised UTF-8.</li>
<li>add some fields to the capabilities object</li>
</ul>
<t><strong>draft-gondwana-jmap-filenode-00</strong></t>

<ul spacing="compact">
<li>initial proposal</li>
</ul>
</section>

<section anchor="acknowledgements"><name>Acknowledgements</name>
<t>Neil Jenkins and the JMAP working group at the IETF.</t>
</section>

</middle>

<back>
<references><name>Normative References</name>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml-ids/reference.I-D.ietf-jmap-sharing.xml"/>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml/reference.RFC.2119.xml"/>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml/reference.RFC.4918.xml"/>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml/reference.RFC.5198.xml"/>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml/reference.RFC.6938.xml"/>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml/reference.RFC.8174.xml"/>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml/reference.RFC.8620.xml"/>
</references>
<references><name>Informative References</name>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml-ids/reference.I-D.ietf-jmap-calendars.xml"/>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml-ids/reference.I-D.ietf-jmap-contacts.xml"/>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml/reference.RFC.4437.xml"/>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml/reference.RFC.5545.xml"/>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml/reference.RFC.6352.xml"/>
</references>

</back>

</rfc>
