VulpesOS/gaia/apps/email/js/ext/db/mail_rep.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

283 lines
10 KiB
JavaScript
Executable file

/**
* Centralize the creation of our header and body object representations.
*
* We provide constructor functions which take input objects that should
* basically look like the output object, but the function enforces
* consistency and provides the ability to assert about the state of the
* representation at the call-site. We discussed making sure to check
* representations when we are inserting records into our database, but we
* might also want to opt to do it at creation time too so we can explode
* slightly closer to the source of the problem.
*
* This module will also provide representation checking functions to make
* sure all the data structures are well-formed/have no obvious problems.
*
* @module mailapi/db/mail_rep
**/
define(function() {
/*
* @typedef[HeaderInfo @dict[
* @key[id]{
* An id allocated by the back-end that names the message within the folder.
* We use this instead of the server-issued UID because if we used the UID
* for this purpose then we would still need to issue our own temporary
* speculative id's for offline operations and would need to implement
* renaming and it all gets complicated.
* }
* @key[srvid]{
* The server-issued UID for the folder, or 0 if the folder is an offline
* header.
* }
* @key[suid]{
* Basically "account id/folder id/message id", although technically the
* folder id includes the account id.
* }
* @key[guid String]{
* This is the message-id header value of the message.
* }
* @key[author NameAddressPair]
* @key[to #:optional @listof[NameAddressPair]]
* @key[cc #:optional @listof[NameAddressPair]]
* @key[bcc #:optional @listof[NameAddressPair]]
* @key[replyTo #:optional String]{
* The contents of the reply-to header.
* }
* @key[date DateMS]
* @key[flags @listof[String]]
* @key[hasAttachments Boolean]
* @key[subject @oneof [String null]]
* @key[snippet @oneof[
* @case[null]{
* We haven't tried to generate a snippet yet.
* }
* @case['']{
* We tried to generate a snippet, but got nothing useful. Note that we
* may try and generate a snippet from a partial body fetch; this does not
* indicate that we should avoid computing a better snippet. Whenever the
* snippet is falsey and we have retrieved more body data, we should
* always try and derive a snippet.
* }
* @case[String]{
* A non-empty string means we managed to produce some snippet data. It
* is still appropriate to regenerate the snippet if more body data is
* fetched since our snippet may be a fallback where we chose quoted text
* instead of authored text, etc.
* }
* ]]
* ]]
*/
function makeHeaderInfo(raw) {
// All messages absolutely need the following; the caller needs to make up
// values if they're missing.
if (!raw.author)
throw new Error('No author?!');
if (!raw.date)
throw new Error('No date?!');
// We also want/require a valid id, but we check that at persistence time
// since POP3 assigns the id/suid slightly later on. We check the suid at
// that point too. (Checked in FolderStorage.addMessageHeader.)
return {
id: raw.id,
srvid: raw.srvid || null,
suid: raw.suid || null,
guid: raw.guid || null,
author: raw.author,
to: raw.to || null,
cc: raw.cc || null,
bcc: raw.bcc || null,
replyTo: raw.replyTo || null,
date: raw.date,
flags: raw.flags || [],
hasAttachments: raw.hasAttachments || false,
// These can be empty strings which are falsey, so no ||
subject: (raw.subject != null) ? raw.subject : null,
snippet: (raw.snippet != null) ? raw.snippet : null,
imapMissingInSyncRange: raw.imapMissingInSyncRange || null
};
}
/*
* @typedef[BodyInfo @dict[
* @key[date DateMS]{
* Redundantly stored date info for block splitting purposes. We pretty
* much need this no matter what because our ordering is on the tuples of
* dates and UIDs, so we could have trouble efficiently locating our header
* from the body without this.
* }
* @key[size Number]
* @key[attaching #:optional AttachmentInfo]{
* Because of memory limitations, we need to encode and attach attachments
* in small pieces. An attachment in the process of being attached is
* stored here until fully processed. Its 'file' field contains a list of
* Blobs.
* }
* @key[attachments @listof[AttachmentInfo]]{
* Proper attachments for explicit downloading.
* }
* @key[relatedParts @oneof[null @listof[AttachmentInfo]]]{
* Attachments for inline display in the contents of the (hopefully)
* multipart/related message.
* }
* @key[references @oneof[null @listof[String]]]{
* The contents of the references header as a list of de-quoted ('<' and
* '>' removed) message-id's. If there was no header, this is null.
* }
* @key[bodyReps @listof[BodyPartInfo]]
* ]]{
* Information on the message body that is only for full message display.
* The to/cc/bcc information may get moved up to the header in the future,
* but our driving UI doesn't need it right now.
* }
*/
function makeBodyInfo(raw) {
if (!raw.date)
throw new Error('No date?!');
if (!raw.attachments || !raw.bodyReps)
throw new Error('No attachments / bodyReps?!');
return {
date: raw.date,
size: raw.size || 0,
attachments: raw.attachments,
relatedParts: raw.relatedParts || null,
references: raw.references || null,
bodyReps: raw.bodyReps
};
}
/*
* @typedef[BodyPartInfo @dict[
* @key[type @oneof['plain' 'html']]{
* The type of body; this is actually the MIME sub-type.
* }
* @key[part String]{
* IMAP part number.
* }
* @key[sizeEstimate Number]
* @key[amountDownloaded Number]
* @key[isDownloaded Boolean]
* @key[_partInfo #:optional RawIMAPPartInfo]
* @key[content]{
* The representation for 'plain' values is a `quotechew.js` processed
* body representation which is pair-wise list where the first item in each
* pair is a packed integer identifier and the second is a string containing
* the text for that block.
*
* The body representation for 'html' values is an already sanitized and
* already quote-normalized String representation that could be directly
* fed into innerHTML safely if you were so inclined. See `htmlchew.js`
* for more on that process.
* }
* ]]
*/
function makeBodyPart(raw) {
// We don't persist body types to our representation that we don't understand.
if (raw.type !== 'plain' &&
raw.type !== 'html')
throw new Error('Bad body type: ' + raw.type);
// 0 is an okay body size, but not giving us a guess is not!
if (raw.sizeEstimate === undefined)
throw new Error('Need size estimate!');
return {
type: raw.type,
part: raw.part || null,
sizeEstimate: raw.sizeEstimate,
amountDownloaded: raw.amountDownloaded || 0,
isDownloaded: raw.isDownloaded || false,
_partInfo: raw._partInfo || null,
content: raw.content || ''
};
}
/*
* @typedef[AttachmentInfo @dict[
* @key[name String]{
* The filename of the attachment, if any.
* }
* @key[contentId String]{
* The content-id of the attachment if this is a related part for inline
* display.
* }
* @key[type String]{
* The (full) mime-type of the attachment.
* }
* @key[part String]{
* The IMAP part number for fetching the attachment.
* }
* @key[encoding String]{
* The encoding of the attachment so we know how to decode it. For
* ActiveSync, the server takes care of this for us so there is no encoding
* from our perspective. (Although the attachment may get base64 encoded
* for transport in the inline case, but that's a protocol thing and has
* nothing to do with the message itself.)
* }
* @key[sizeEstimate Number]{
* Estimated file size in bytes. Gets updated to be the correct size on
* attachment download.
* }
* @key[file @oneof[
* @case[null]{
* The attachment has not been downloaded, the file size is an estimate.
* }
* @case[@list["device storage type" "file path"]{
* The DeviceStorage type (ex: pictures) and the path to the file within
* device storage.
* }
* @case[HTMLBlob]{
* The Blob that contains the attachment. It can be thought of as a
* handle/name to access the attachment. IndexedDB in Gecko stores the
* blobs as (quota-tracked) files on the file-system rather than inline
* with the record, so the attachments don't need to count against our
* block size since they are not part of the direct I/O burden for the
* block.
* }
* @case[@listof[HTMLBlob]]{
* For draft messages, a list of one or more pre-base64-encoded attachment
* pieces that were sliced up in chunks due to Gecko's inability to stream
* Blobs to disk off the main thread.
* }
* ]]
* @key[charset @oneof[undefined String]]{
* The character set, for example "ISO-8859-1". If not specified, as is
* likely for binary attachments, this should be null.
* }
* @key[textFormat @oneof[undefined String]]{
* The text format, for example, "flowed" for format=flowed. If not
* specified, as is likely for binary attachments, this should be null.
* }
* ]]
*/
function makeAttachmentPart(raw) {
// Something is very wrong if there is no size estimate.
if (raw.sizeEstimate === undefined)
throw new Error('Need size estimate!');
return {
// XXX ActiveSync may leave this null, although it's conceivable the
// server might do normalization to save us. This needs a better treatment.
// IMAP generates a made-up name for us if there isn't one.
name: (raw.name != null) ? raw.name : null,
contentId: raw.contentId || null,
type: raw.type || 'application/octet-stream',
part: raw.part || null,
encoding: raw.encoding || null,
sizeEstimate: raw.sizeEstimate,
file: raw.file || null,
charset: raw.charset || null,
textFormat: raw.textFormat || null
};
}
return {
makeHeaderInfo: makeHeaderInfo,
makeBodyInfo: makeBodyInfo,
makeBodyPart: makeBodyPart,
makeAttachmentPart: makeAttachmentPart
};
}); // end define