@ -2,10 +2,21 @@
const Promise = require ( "bluebird" ) ;
const Promise = require ( "bluebird" ) ;
const defaultValue = require ( "default-value" ) ;
const defaultValue = require ( "default-value" ) ;
const asExpression = require ( "as-expression" ) ;
const matchValue = require ( "match-value" ) ;
const unreachable = require ( "@joepie91/unreachable" ) ;
const { validateArguments } = require ( "@validatem/core" ) ;
const required = require ( "@validatem/required" ) ;
const anyProperty = require ( "@validatem/any-property" ) ;
const isString = require ( "@validatem/is-string" ) ;
const isFunction = require ( "@validatem/is-function" ) ;
const { command , unsafeRaw , already7Bit } = require ( "./util/command" ) ;
const { command , unsafeRaw , already7Bit } = require ( "./util/command" ) ;
const pInterval = require ( "./util/p-interval" ) ;
const pInterval = require ( "./util/p-interval" ) ;
const createFetchTaskTracker = require ( "./util/fetch-task" ) ;
const createFetchTaskTracker = require ( "./util/fetch-task" ) ;
const createBoxTreeBuilder = require ( "./util/box-tree-builder" ) ;
const ensureArray = require ( "./util/ensure-array" ) ;
var tls = require ( 'tls' ) ,
var tls = require ( 'tls' ) ,
Socket = require ( 'net' ) . Socket ,
Socket = require ( 'net' ) . Socket ,
@ -1234,117 +1245,360 @@ Object.defineProperty(Connection.prototype, 'seq', { get: function() {
} ;
} ;
} } ) ;
} } ) ;
// type: type,
function parseTypes ( typeString ) {
// num: num,
if ( typeString === NoRequest || typeString === AnyRequest ) {
// textCode: textCode,
return [ typeString ] ;
// text: val
} else {
return typeString
. split ( /\s*,\s*/ )
. map ( ( type ) => type . toUpperCase ( ) ) ;
}
}
Connection . prototype . _resUntagged = function ( { type , num , textCode , text : payload } ) {
function createCommandHandlers ( _rules ) {
var i , len , box , destinationKey ;
let [ rules ] = validateArguments ( arguments , {
rules : anyProperty ( {
key : [ required ] ,
value : [ required , {
untagged : anyProperty ( {
key : [ isString ] ,
value : [ required , isFunction ]
} ) ,
tagged : [ isFunction ]
} ]
} )
} ) ;
let untaggedHandlers = new Map ( ) ;
let taggedHandlers = new Map ( ) ;
if ( type === 'bye' ) {
function setUntaggedHandler ( requestType , responseType , handler ) {
this . _sock . end ( ) ;
if ( ! untaggedHandlers . has ( requestType ) ) {
} else if ( type === 'namespace' ) {
untaggedHandlers . set ( requestType , new Map ( ) ) ;
this . namespaces = payload ;
}
} else if ( type === 'id' ) {
this . _curReq . cbargs . push ( payload ) ;
let handlers = untaggedHandlers . get ( requestType ) ;
} else if ( type === 'capability' ) {
this . _caps = payload . map ( ( v ) => v . toUpperCase ( ) ) ;
if ( ! handlers . has ( responseType ) ) {
} else if ( type === 'preauth' ) {
handlers . set ( responseType , handler ) ;
this . state = 'authenticated' ;
} else {
} else if ( type === 'sort' || type === 'thread' || type === 'esearch' ) {
throw new Error ( ` Duplicate handler definition for untagged type ' ${ requestType } ' -> ' ${ responseType } ' ` ) ;
this . _curReq . cbargs . push ( payload ) ;
}
} else if ( type === 'search' ) {
}
if ( payload . results !== undefined ) {
// CONDSTORE-modified search results
function setTaggedHandler ( requestType , handler ) {
this . _curReq . cbargs . push ( payload . results ) ;
if ( ! taggedHandlers . has ( requestType ) ) {
this . _curReq . cbargs . push ( payload . modseq ) ;
taggedHandlers . set ( requestType , handler ) ;
} else {
} else {
th is. _curReq . cbargs . push ( payload ) ;
th row new Error ( ` Duplicate handler definition for tagged type ` ) ;
}
}
} else if ( type === 'quota' ) {
}
var cbargs = this . _curReq . cbargs ;
if ( ! cbargs . length ) {
function getRequestType ( request ) {
cbargs . push ( [ ] ) ;
return ( request != null )
? request . type . toUpperCase ( )
: NoRequest ;
}
function getTaggedHandler ( request ) {
return taggedHandlers . get ( getRequestType ( request ) ) ;
}
function getUntaggedHandler ( request , responseType ) {
let typeHandlers = untaggedHandlers . get ( getRequestType ( request ) ) ;
let anyRequestHandlers = untaggedHandlers . get ( AnyRequest ) ;
// console.log({
// requestType: getRequestType(request),
// responseType: responseType,
// anyHandler: anyRequestHandlers.get(responseType)
// });
if ( typeHandlers != null && typeHandlers . has ( responseType ) ) {
return typeHandlers . get ( responseType ) ;
} else if ( anyRequestHandlers != null && anyRequestHandlers . has ( responseType ) ) {
return anyRequestHandlers . get ( responseType ) ;
}
}
}
cbargs [ 0 ] . push ( payload ) ;
for ( let [ typeString , options ] of allEntries ( rules ) ) {
} else if ( type === 'recent' ) {
for ( let type of parseTypes ( typeString ) ) {
if ( ! this . _box && RE _OPENBOX . test ( this . _curReq . type ) ) {
if ( options . untagged != null ) {
this . _createCurrentBox ( ) ;
for ( let [ responseTypeString , handler ] of allEntries ( options . untagged ) ) {
for ( let responseType of parseTypes ( responseTypeString ) ) {
setUntaggedHandler ( type , responseType , handler ) ;
}
}
}
if ( options . tagged != null ) {
setTaggedHandler ( type , options . tagged ) ;
}
}
}
}
// REFACTOR: Eventually remove `.call(this` hackery
// REFACTOR: Remove all the toUpperCase stuff and normalize this in the core instead, so that we can just *assume* it to be upper-case here
return {
canHandleUntagged : function ( request , data ) {
return ( getUntaggedHandler ( request , data . type . toUpperCase ( ) ) != null ) ;
} ,
canHandleTagged : function ( request , _data ) {
return ( getTaggedHandler ( request ) != null ) ;
} ,
handleUntagged : function ( request , data ) {
let handler = getUntaggedHandler ( request , data . type . toUpperCase ( ) ) ;
if ( this . _box ) {
return handler . call ( this , request , data ) ;
this . _box . messages . new = num ;
} ,
handleTagged : function ( request , data ) {
let handler = getTaggedHandler ( request ) ;
return handler . call ( this , request , data ) ;
}
}
} else if ( type === 'flags' ) {
} ;
if ( ! this . _box && RE _OPENBOX . test ( this . _curReq . type ) ) {
}
this . _createCurrentBox ( ) ;
function allEntries ( object ) {
// The equivalent of Object.entries but also including Symbol keys
return Reflect . ownKeys ( object ) . map ( ( key ) => {
return [ key , object [ key ] ] ;
} ) ;
}
let NoRequest = Symbol ( "NoRequest" ) ;
let AnyRequest = Symbol ( "AnyRequest" ) ;
// FIXME: Strip "UID" prefix from command names before matching, as these only affect the output format, but not the type of response
// Exception: EXPUNGE is allowed during UID commands, but not during their non-UID equivalents: https://datatracker.ietf.org/doc/html/rfc3501#page-73
let commandHandlers = createCommandHandlers ( {
[ AnyRequest ] : {
untagged : {
"BYE" : function ( _request , _ ) {
this . _sock . end ( ) ;
} ,
"CAPABILITY" : function ( _request , { payload } ) {
this . _caps = payload . map ( ( v ) => v . toUpperCase ( ) ) ;
} ,
"NAMESPACE" : function ( _request , { payload } ) {
this . namespaces = payload ;
} ,
"PREAUTH" : function ( _request , _ ) {
this . state = 'authenticated' ;
} ,
"EXPUNGE" : function ( _request , { sequenceNumber } ) {
if ( this . _box != null ) {
if ( this . _box . messages . total > 0 ) {
this . _box . messages . total -= 1 ;
}
this . emit ( 'expunge' , sequenceNumber ) ;
}
} ,
}
}
} ,
"STATUS" : {
untagged : {
"STATUS" : function ( request , { payload } ) {
// REFACTOR: Improve this?
let attrs = defaultValue ( payload . attrs , { } ) ;
let box = {
name : payload . name ,
uidnext : defaultValue ( attrs . uidnext , 0 ) ,
uidvalidity : defaultValue ( attrs . uidvalidity , 0 ) ,
messages : {
total : defaultValue ( attrs . messages , 0 ) ,
new : defaultValue ( attrs . recent , 0 ) ,
unseen : defaultValue ( attrs . unseen , 0 )
} ,
// CONDSTORE
highestmodseq : ( attrs . highestmodseq != null )
? String ( attrs . highestmodseq )
: undefined
} ;
if ( this . _box ) {
request . legacyArgs . push ( box ) ;
this . _box . flags = payload ;
Object . assign ( request . responseData , box ) ;
}
}
}
} else if ( type === 'bad' || type === 'no' ) {
} ,
if ( this . state === 'connected' && ! this . _curReq ) {
"LIST, XLIST, LSUB" : {
clearTimeout ( this . _connectionTimeout ) ;
untagged : {
clearTimeout ( this . _authenticationTimeout ) ;
"LIST, XLIST, LSUB" : function ( request , { payload } ) {
var err = new Error ( 'Received negative welcome: ' + payload ) ;
if ( request . delimiter === undefined ) {
err . source = 'protocol' ;
request . delimiter = payload . delimiter ;
this . emit ( 'error' , err ) ;
} else {
this . _sock . end ( ) ;
if ( request . boxBuilder == null ) {
request . boxBuilder = createBoxTreeBuilder ( ) ;
}
request . boxBuilder . add ( payload ) ;
}
}
} ,
tagged : function ( request , _ ) {
// FIXME: Check request types for correctness
let boxTree = ( request . boxBuilder != null )
? request . boxBuilder . done ( )
: { } ; // No response items were received
request . legacyArgs . push ( boxTree ) ;
request . responseData . boxTree = boxTree ;
}
}
} else if ( type === 'exists' ) {
} ,
if ( ! this . _box && RE _OPENBOX . test ( this . _curReq . type ) ) {
"ID" : {
this . _createCurrentBox ( ) ;
// https://datatracker.ietf.org/doc/html/rfc2971
// Used for communicating server/client name, version, etc.
untagged : {
"ID" : function ( request , { payload } ) {
request . responseData . serverVersion = payload ;
request . legacyArgs . push ( payload ) ;
}
}
}
} ,
if ( this . _box ) {
"ESEARCH" : {
var prev = this . _box . messages . total , now = num ;
// https://datatracker.ietf.org/doc/html/rfc4731 / https://datatracker.ietf.org/doc/html/rfc7377
this . _box . messages . total = now ;
untagged : {
if ( now > prev && this . state === 'authenticated' ) {
"ESEARCH" : function ( request , { payload } ) {
this . _box . messages . new = now - prev ;
Object . assign ( request . responseData , payload ) ; // Protocol-defined attributes. TODO: Improve the key names for this? Or is there extensibility?
this . emit ( 'mail' , this . _box . messages . new ) ;
request . legacyArgs . push ( payload ) ;
}
}
} ,
"SORT" : {
// https://datatracker.ietf.org/doc/html/rfc5256
untagged : {
"SORT" : function ( request , { payload } ) {
request . responseData . UIDs = payload ;
request . legacyArgs . push ( payload ) ;
}
}
} ,
"THREAD" : {
// https://datatracker.ietf.org/doc/html/rfc5256
untagged : {
"THREAD" : function ( request , { payload } ) {
request . responseData . threads = payload ; // FIXME: Work out the exact format
request . legacyArgs . push ( payload ) ;
}
}
}
}
} else if ( type === 'expunge' ) {
} ,
if ( this . _box ) {
"SEARCH" : {
if ( this . _box . messages . total > 0 ) {
untagged : {
-- this . _box . messages . total ;
"SEARCH" : function ( request , { payload } ) {
if ( payload . results !== undefined ) {
// CONDSTORE-modified search results
request . legacyArgs . push ( payload . results ) ;
request . legacyArgs . push ( payload . modseq ) ;
} else {
request . legacyArgs . push ( payload ) ;
}
}
}
} ,
"GETQUOTA, GETQUOTAROOT" : {
// https://datatracker.ietf.org/doc/html/rfc2087
untagged : {
"QUOTA" : function ( request , { payload } ) {
ensureArray ( request . responseData , "quota" ) ;
request . responseData . quota . push ( payload ) ;
ensureArray ( request . legacyArgs , 0 ) ;
request . legacyArgs [ 0 ] . push ( payload ) ;
} ,
"QUOTAROOT" : function ( _request , _ ) {
throw new Error ( ` Not implemented ` ) ;
}
}
}
} ,
"SELECT, EXAMINE" : {
untagged : {
"RECENT" : function ( _request , { sequenceNumber } ) {
this . _ensureBox ( ) ;
// FIXME: This conditional is always true?
if ( this . _box ) {
this . _box . messages . new = sequenceNumber ;
}
} ,
"FLAGS" : function ( _request , { payload } ) {
this . _ensureBox ( ) ;
// FIXME: This conditional is always true?
if ( this . _box ) {
this . _box . flags = payload ;
}
} ,
"EXISTS" : function ( _request , { sequenceNumber } ) {
this . _ensureBox ( ) ;
// FIXME: This conditional is always true?
if ( this . _box ) {
var prev = this . _box . messages . total , now = sequenceNumber ;
this . _box . messages . total = now ;
if ( now > prev && this . state === 'authenticated' ) {
this . _box . messages . new = now - prev ;
this . emit ( 'mail' , this . _box . messages . new ) ;
}
}
} ,
}
} ,
} ) ;
this . emit ( 'expunge' , num ) ;
Connection . prototype . _ensureBox = function ( ) {
if ( ! this . _box ) {
if ( RE _OPENBOX . test ( this . _curReq . type ) ) {
this . _resetCurrentBox ( ) ;
} else {
throw new Error ( ` Received a box-related response while not processing a box-related request ` ) ;
}
}
}
} ;
// type: type,
// num: num, -- sequence number of the affected nessage, used for FETCH and EXPUNGE only (message-data) and maybe RECENT and EXISTS (mailbox-data)?
// textCode: textCode,
// text: val
// NOTE: responseData is meant to contain machine-readable data, payload is meant to contain human-readable data, but in practice payload is also often machine-parsed
Connection . prototype . _resUntagged = function ( { type , num : sequenceNumber , textCode : responseData , text : payload } ) {
// console.log("resUntagged", { type, num: sequenceNumber, payload, textCode: responseData });
var i , len , box , destinationKey ;
let response = { type , sequenceNumber , payload } ;
if ( commandHandlers . canHandleUntagged ( this . _curReq , response ) ) {
// FIXME: Include other fields
commandHandlers . handleUntagged . call ( this , this . _curReq , response ) ;
} else if ( type === 'ok' ) {
} else if ( type === 'ok' ) {
if ( this . state === 'connected' && ! this . _curReq ) {
if ( this . state === 'connected' && ! this . _curReq ) {
this . _login ( ) ;
this . _login ( ) ;
} else if ( typeof textCode === 'string' && textCode . toUpperCase ( ) === 'ALERT' ) {
} else if ( typeof responseData === 'string' && responseData . toUpperCase ( ) === 'ALERT' ) {
this . emit ( 'alert' , payload ) ;
this . emit ( 'alert' , payload ) ;
}
} else if ( this . _curReq && responseData && ( RE _OPENBOX . test ( this . _curReq . type ) ) ) {
else if ( this . _curReq
&& textCode
&& ( RE _OPENBOX . test ( this . _curReq . type ) ) ) {
// we're opening a mailbox
// we're opening a mailbox
if ( ! this . _box ) {
if ( ! this . _box ) {
this . _createCurrentBox ( ) ;
this . _ reset CurrentBox( ) ;
}
}
if ( textCode . key ) {
let destinationKey = ( responseData . key != null )
destinationKey = textCode . key . toUpperCase ( ) ;
? responseData . key . toUpperCase ( )
} else {
: responseData ;
destinationKey = textCode ;
}
if ( destinationKey === 'UIDVALIDITY' ) {
if ( destinationKey === 'UIDVALIDITY' ) {
this . _box . uidvalidity = textCode . val ;
this . _box . uidvalidity = responseData . val ;
} else if ( destinationKey === 'UIDNEXT' ) {
} else if ( destinationKey === 'UIDNEXT' ) {
this . _box . uidnext = textCode . val ;
this . _box . uidnext = responseData . val ;
} else if ( destinationKey === 'HIGHESTMODSEQ' ) {
} else if ( destinationKey === 'HIGHESTMODSEQ' ) {
this . _box . highestmodseq = '' + textCode . val ;
this . _box . highestmodseq = '' + responseData . val ;
} else if ( destinationKey === 'PERMANENTFLAGS' ) {
} else if ( destinationKey === 'PERMANENTFLAGS' ) {
var idx , permFlags , keywords ;
var idx , permFlags , keywords ;
this . _box . permFlags = permFlags = textCode . val ;
this . _box . permFlags = permFlags = responseData . val ;
if ( ( idx = this . _box . permFlags . indexOf ( '\\*' ) ) > - 1 ) {
if ( ( idx = this . _box . permFlags . indexOf ( '\\*' ) ) > - 1 ) {
this . _box . newKeywords = true ;
this . _box . newKeywords = true ;
@ -1356,138 +1610,79 @@ Connection.prototype._resUntagged = function({ type, num, textCode, text: payloa
for ( i = 0 , len = keywords . length ; i < len ; ++ i ) {
for ( i = 0 , len = keywords . length ; i < len ; ++ i ) {
permFlags . splice ( permFlags . indexOf ( keywords [ i ] ) , 1 ) ;
permFlags . splice ( permFlags . indexOf ( keywords [ i ] ) , 1 ) ;
}
}
} else if ( destinationKey === 'UIDNOTSTICKY' )
} else if ( destinationKey === 'UIDNOTSTICKY' ) {
this . _box . persistentUIDs = false ;
this . _box . persistentUIDs = false ;
else if ( destinationKey === 'NOMODSEQ' )
} else if ( destinationKey === 'NOMODSEQ' ) {
this . _box . nomodseq = true ;
this . _box . nomodseq = true ;
} else if ( typeof textCode === 'string'
&& textCode . toUpperCase ( ) === 'UIDVALIDITY' )
this . emit ( 'uidvalidity' , payload ) ;
} else if ( type === 'list' || type === 'lsub' || type === 'xlist' ) {
if ( this . delimiter === undefined ) {
this . delimiter = payload . delimiter ;
} else {
if ( this . _curReq . cbargs . length === 0 ) {
this . _curReq . cbargs . push ( { } ) ;
}
box = {
attribs : payload . flags ,
delimiter : payload . delimiter ,
children : null ,
parent : null
} ;
for ( i = 0 , len = SPECIAL _USE _ATTRIBUTES . length ; i < len ; ++ i ) {
if ( box . attribs . indexOf ( SPECIAL _USE _ATTRIBUTES [ i ] ) > - 1 ) {
box . special _use _attrib = SPECIAL _USE _ATTRIBUTES [ i ] ;
}
}
}
} else if ( typeof responseData === 'string' && responseData . toUpperCase ( ) === 'UIDVALIDITY' ) {
var name = payload . name ,
this . emit ( 'uidvalidity' , payload ) ;
curChildren = this . _curReq . cbargs [ 0 ] ;
}
} else if ( type === 'bad' || type === 'no' ) {
if ( box . delimiter ) {
if ( this . state === 'connected' && ! this . _curReq ) {
var path = name . split ( box . delimiter ) ,
clearTimeout ( this . _connectionTimeout ) ;
parent = null ;
clearTimeout ( this . _authenticationTimeout ) ;
var err = new Error ( 'Received negative welcome: ' + payload ) ;
name = path . pop ( ) ;
err . source = 'protocol' ;
this . emit ( 'error' , err ) ;
for ( i = 0 , len = path . length ; i < len ; ++ i ) {
this . _sock . end ( ) ;
if ( ! curChildren [ path [ i ] ] ) {
curChildren [ path [ i ] ] = { } ;
}
if ( ! curChildren [ path [ i ] ] . children ) {
curChildren [ path [ i ] ] . children = { } ;
}
parent = curChildren [ path [ i ] ] ;
curChildren = curChildren [ path [ i ] ] . children ;
}
box . parent = parent ;
}
if ( curChildren [ name ] ) {
box . children = curChildren [ name ] . children ;
}
curChildren [ name ] = box ;
}
}
} else if ( type === 'status' ) {
let attrs = defaultValue ( payload . attrs , { } ) ;
box = {
name : payload . name ,
uidnext : defaultValue ( attrs . uidnext , 0 ) ,
uidvalidity : defaultValue ( attrs . uidvalidity , 0 ) ,
messages : {
total : defaultValue ( attrs . messages , 0 ) ,
new : defaultValue ( attrs . recent , 0 ) ,
unseen : defaultValue ( attrs . unseen , 0 )
} ,
// CONDSTORE
highestmodseq : ( attrs . highestmodseq != null )
? String ( attrs . highestmodseq )
: undefined
} ;
// FIXME
this . _curReq . cbargs . push ( box ) ;
} else if ( type === 'fetch' ) {
} else if ( type === 'fetch' ) {
if ( /^(?:UID )?FETCH/ . test ( this . _curReq . fullcmd ) ) {
if ( /^(?:UID )?FETCH/ . test ( this . _curReq . fullcmd ) ) {
// FETCH response sent as result of FETCH request
// FETCH response sent as result of FETCH request
let task = this . _curReq . fetchCache . get ( num) ;
let task = this . _curReq . fetchCache . get ( sequenceNumber ) ;
// FIXME: Refactor, probably make the task itself an event emitter
// FIXME: Refactor, probably make the task itself an event emitter
if ( task == null ) {
if ( task == null ) {
task = this . _curReq . fetchCache . create ( num, this . _curReq . fetching . slice ( ) ) ;
task = this . _curReq . fetchCache . create ( sequenceNumber , this . _curReq . fetching . slice ( ) ) ;
this . _curReq . bodyEmitter . emit ( 'message' , task . emitter , num) ;
this . _curReq . bodyEmitter . emit ( 'message' , task . emitter , sequenceNumber ) ;
}
}
task . processFetchResponse ( payload ) ;
task . processFetchResponse ( payload ) ;
} else {
} else {
// FETCH response sent as result of STORE request or sent unilaterally,
// FETCH response sent as result of STORE request or sent unilaterally,
// treat them as the same for now for simplicity
// treat them as the same for now for simplicity
this . emit ( 'update' , num, payload ) ;
this . emit ( 'update' , sequenceNumber , payload ) ;
}
}
}
}
} ;
} ;
Connection . prototype . _resTagged = function ( info ) {
Connection . prototype . _resTagged = function ( { type , tagnum , text : payload , textCode : responseCode } ) {
var req = this . _curReq ;
// console.log("resTagged", { type, tagnum, payload, textCode: responseCode });
// REFACTOR: textCode: either just the key, or a {key, val} object
var request = this . _curReq ;
if ( req != null ) {
if ( req uest != null ) {
var err ;
var err ;
this . _curReq = undefined ;
this . _curReq = undefined ;
if ( info. type === 'no' || info . type === 'bad' ) {
if ( type === 'no' || type === 'bad' ) {
// TODO: Can info. text be an empty string?
// TODO: Can text be an empty string?
let errorText = defaultValue ( info. text , req . oauthError ) ;
let errorText = defaultValue ( payload, request . oauthError ) ;
err = Object . assign ( new Error ( errorText ) , {
err = Object . assign ( new Error ( errorText ) , {
type : info. type,
type : type,
text : info. text Code,
text : response Code,
source : "protocol"
source : "protocol"
} ) ;
} ) ;
} else if ( this . _box != null ) {
} else if ( this . _box != null ) {
if ( req . type === 'EXAMINE' || req . type === 'SELECT' ) {
if ( req uest . type === 'EXAMINE' || req uest . type === 'SELECT' ) {
this . _box . readOnly = (
this . _box . readOnly = (
typeof info. text Code === 'string'
typeof response Code === 'string'
&& info. text Code. toUpperCase ( ) === 'READ-ONLY'
&& response Code. toUpperCase ( ) === 'READ-ONLY'
) ;
) ;
}
}
// According to RFC 3501, UID commands do not give errors for
// According to RFC 3501, UID commands do not give errors for
// non-existant user-supplied UIDs, so give the callback empty results
// non-existant user-supplied UIDs, so give the callback empty results
// if we unexpectedly received no untagged responses.
// if we unexpectedly received no untagged responses.
if ( RE _UIDCMD _HASRESULTS . test ( req . fullcmd ) && req . cbargs . length === 0 ) {
if ( RE _UIDCMD _HASRESULTS . test ( req uest . fullcmd ) && req uest . cbargs . length === 0 ) {
req . cbargs . push ( [ ] ) ;
req uest . cbargs . push ( [ ] ) ;
}
}
}
}
if ( req . bodyEmitter ) {
if ( req uest . bodyEmitter != null ) {
var bodyEmitter = req . bodyEmitter ;
var bodyEmitter = req uest . bodyEmitter ;
if ( err ) {
if ( err ) {
bodyEmitter . emit ( 'error' , err ) ;
bodyEmitter . emit ( 'error' , err ) ;
@ -1497,19 +1692,50 @@ Connection.prototype._resTagged = function(info) {
bodyEmitter . emit ( 'end' ) ;
bodyEmitter . emit ( 'end' ) ;
} ) ;
} ) ;
} else {
} else {
req . cbargs . unshift ( err ) ;
let extraArguments = asExpression ( ( ) => {
if ( responseCode != null && responseCode . key != null ) {
if ( info . textCode && info . textCode . key ) {
return matchValue ( responseCode . key . toUpperCase ( ) , {
var key = info . textCode . key . toUpperCase ( ) ;
// [uidvalidity, newUID]
if ( key === 'APPENDUID' ) { // [uidvalidity, newUID]
APPENDUID : [ responseCode . val [ 1 ] ] ,
req . cbargs . push ( info . textCode . val [ 1 ] ) ;
// [uidvalidity, sourceUIDs, destUIDs]
} else if ( key === 'COPYUID' ) { // [uidvalidity, sourceUIDs, destUIDs]
COPYUID : [ responseCode . val [ 2 ] ] ,
req . cbargs . push ( info . textCode . val [ 2 ] ) ;
_ : [ ]
} ) ;
} else {
return [ ] ;
}
}
} ) ;
if ( responseCode != null && responseCode . key != null ) {
// FIXME: This eventually should replace the extraArguments array stuff
matchValue ( responseCode . key . toUpperCase ( ) , {
APPENDUID : ( ) => {
request . responseData . newUID = responseCode . val [ 1 ] ;
} ,
COPYUID : ( ) => {
// FIXME: Parsing? Looks like it will be multiple items
request . responseData . destinationUIDs = responseCode . val [ 2 ] ;
}
} ) ;
}
}
if ( req . cb != null ) {
let response = { type , payload } ;
req . cb . apply ( this , req . cbargs ) ;
if ( commandHandlers . canHandleTagged ( request , response ) ) {
// FIXME: Add other fields with a sensible name
commandHandlers . handleTagged . call ( this , request , response ) ;
}
// console.dir({ done: request.cbargs }, { depth: null, colors: true });
if ( request . cb2 != null ) {
request . cb . apply ( this , request . responseData ) ;
} else if ( request . cb != null ) {
request . cb . apply ( this , [
err ,
... request . cbargs ,
... extraArguments
] ) ;
}
}
}
}
@ -1528,7 +1754,7 @@ Connection.prototype._resTagged = function(info) {
}
}
} ;
} ;
Connection . prototype . _ create CurrentBox = function ( ) {
Connection . prototype . _ reset CurrentBox = function ( ) {
this . _box = {
this . _box = {
name : '' ,
name : '' ,
flags : [ ] ,
flags : [ ] ,
@ -1744,25 +1970,31 @@ Connection.prototype._sockWriteAppendData = function(appendData)
this . _sock . write ( CRLF ) ;
this . _sock . write ( CRLF ) ;
} ;
} ;
Connection . prototype . _enqueue = function ( fullcmd , promote , cb ) {
Connection . prototype . _enqueue = function ( fullcmd , promote , cb , newAPI ) {
// TODO: Remove variability
// TODO: Remove variability
if ( typeof promote === 'function' ) {
if ( typeof promote === 'function' ) {
cb = promote ;
cb = promote ;
promote = false ;
promote = false ;
}
}
var info = {
var request = {
type : fullcmd . match ( RE _CMD ) [ 1 ] ,
type : fullcmd . match ( RE _CMD ) [ 1 ] ,
fullcmd : fullcmd ,
fullcmd : fullcmd ,
cb : cb ,
cb : ( newAPI ) ? null : cb ,
cbargs : [ ]
cb2 : ( newAPI ) ? cb : null ,
} ,
cbargs : [ ] ,
self = this ;
responseData : { }
} ;
// Alias
request . legacyArgs = request . cbargs ;
var self = this ;
if ( promote ) {
if ( promote ) {
this . _queue . unshift ( info ) ;
this . _queue . unshift ( request ) ;
} else {
} else {
this . _queue . push ( info ) ;
this . _queue . push ( request ) ;
}
}
if ( ! this . _curReq
if ( ! this . _curReq
@ -1796,7 +2028,7 @@ Connection.prototype._enqueue2 = function (command, options = {}) {
return this . _enqueueAsync ( string , insertInFront ) ;
return this . _enqueueAsync ( string , insertInFront ) ;
} else {
} else {
// TODO: Use `unreachable`
// TODO: Use `unreachable`
throw new Error ( ` Must use a command template string ` ) ;
throw unreachable ( ` Must use a command template string ` ) ;
}
}
} ;
} ;