Newer
Older
else if (codes[idx] == "46") // Cyan background
else if (codes[idx] == "47") // White background
16005
16006
16007
16008
16009
16010
16011
16012
16013
16014
16015
16016
16017
16018
16019
16020
16021
16022
16023
16024
16025
16026
16027
16028
16029
16030
16031
16032
16033
16034
16035
16036
16037
16038
16039
16040
16041
16042
16043
16044
16045
16046
16047
16048
16049
16050
16051
16052
16053
16054
16055
16056
16057
16058
}
updatedText = updatedText.replace(multiMatches[i], syncCodes);
}
return updatedText;
}
// Returns whether a given message group index & sub-board index (or the current ones,
// based on bbs.curgrp and bbs.cursub) are for the last message sub-board on the system.
//
// Parameters:
// pGrpIdx: Optional - The index of the message group. If not specified, this will
// default to bbs.curgrp. If bbs.curgrp is not defined in that case,
// then this method will return false.
// pSubIdx: Optional - The index of the message sub-board. If not specified, this will
// default to bbs.cursub. If bbs.cursub is not defined in that case,
// then this method will return false.
//
// Return value: Boolean - Whether or not the current/given message group index & sub-board
// index are for the last message sub-board on the system. If there
// are any issues with any of the values (including bbs.curgrp or
// bbs.cursub), this method will return false.
function curMsgSubBoardIsLast(pGrpIdx, pSubIdx)
{
var curGrp = 0;
if (typeof(pGrpIdx) == "number")
curGrp = pGrpIdx;
else if (typeof(bbs.curgrp) == "number")
curGrp = bbs.curgrp;
else
return false;
var curSub = 0;
if (typeof(pSubIdx) == "number")
curSub = pSubIdx;
else if (typeof(bbs.cursub) == "number")
curSub = bbs.cursub;
else
return false;
return (curGrp == msg_area.grp_list.length-1) && (curSub == msg_area.grp_list[msg_area.grp_list.length-1].sub_list.length-1);
}
// Parses arguments, where each argument in the given array is in the format
// -arg=val. If the value is the string "true" or "false", then the value will
// be a boolean. Otherwise, the value will be a string.
//
// Parameters:
// pArgArr: An array of strings containing values in the format -arg=val
//
// Return value: An object containing the argument values. The index will be
// the argument names, converted to lowercase. The values will
// be either the string argument values or boolean values, depending
// on the formats of the arguments passed in.
function parseArgs(pArgArr)
{
16059
16060
16061
16062
16063
16064
16065
16066
16067
16068
16069
16070
16071
16072
16073
16074
16075
16076
16077
16078
16079
16080
16081
16082
16083
16084
16085
16086
var argVals = new Object();
// Set default values for parameters that are just true/false values
argVals.chooseareafirst = false;
argVals.personalemail = false;
argVals.personalemailsent = false;
argVals.verboselogging = false;
argVals.suppresssearchtypetext = false;
// Sanity checking for pArgArr - Make sure it's an array
if ((typeof(pArgArr) != "object") || (typeof(pArgArr.length) != "number"))
return argVals;
// Go through pArgArr looking for strings in the format -arg=val and parse them
// into objects in the argVals array.
var equalsIdx = 0;
var argName = "";
var argVal = "";
var argValLower = ""; // For case-insensitive "true"/"false" matching
var argValIsTrue = false;
for (var i = 0; i < pArgArr.length; ++i)
{
// We're looking for strings that start with "-", except strings that are
// only "-".
if ((typeof(pArgArr[i]) != "string") || (pArgArr[i].length == 0) ||
(pArgArr[i].charAt(0) != "-") || (pArgArr[i] == "-"))
{
continue;
}
16088
16089
16090
16091
16092
16093
16094
16095
16096
16097
16098
16099
16100
16101
16102
16103
16104
16105
16106
16107
// Look for an = and if found, split the string on the =
equalsIdx = pArgArr[i].indexOf("=");
// If a = is found, then split on it and add the argument name & value
// to the array. Otherwise (if the = is not found), then treat the
// argument as a boolean and set it to true (to enable an option).
if (equalsIdx > -1)
{
argName = pArgArr[i].substring(1, equalsIdx).toLowerCase();
argVal = pArgArr[i].substr(equalsIdx+1);
argValLower = argVal.toLowerCase();
// If the argument value is the word "true" or "false", then add it as a
// boolean. Otherwise, add it as a string.
argValIsTrue = (argValLower == "true");
if (argValIsTrue || (argValLower == "false"))
argVals[argName] = argValIsTrue;
else
argVals[argName] = argVal;
}
else // An equals sign (=) was not found. Add as a boolean set to true to enable the option.
{
argName = pArgArr[i].substr(1).toLowerCase();
if ((argName == "chooseareafirst") || (argName == "personalemail") ||
(argName == "personalemailsent") || (argName == "allpersonalemail") ||
(argName == "verboselogging") || (argName == "suppresssearchtypetext"))
{
argVals[argName] = true;
}
}
}
// Sanity checking
// If the arguments include personalEmail and personalEmail is enabled,
// then check to see if a search type was specified - If so, only allow
// keyword search and from name search.
if (argVals.hasOwnProperty("personalemail") && argVals.personalemail)
{
// If a search type is specified, only allow keyword search & from name
// search
if (argVals.hasOwnProperty("search"))
{
var searchValLower = argVals.search.toLowerCase();
if ((searchValLower != "keyword_search") && (searchValLower != "from_name_search"))
delete argVals.search;
}
}
16133
16134
16135
16136
16137
16138
16139
16140
16141
16142
16143
16144
16145
16146
16147
16148
16149
16150
16151
16152
16153
16154
16155
16156
16157
// If the arguments include userNum, make sure the value is all digits. If so,
// add altUserNum to the arguments as a number type for user matching when looking
// for personal email to the user.
if (argVals.hasOwnProperty("usernum"))
{
if (/^[0-9]+$/.test(argVals.usernum))
{
var specifiedUserNum = Number(argVals.usernum);
// If the specified number is different than the current logged-in
// user, then load the other user account and read their name and
// alias and also store their user number in the arg vals as a
// number.
if (specifiedUserNum != user.number)
{
var theUser = new User(specifiedUserNum);
argVals.altUserNum = theUser.number;
argVals.altUserName = theUser.name;
argVals.altUserAlias = theUser.alias;
}
else
delete argVals.usernum;
}
else
delete argVals.usernum;
}
16158
16159
16160
16161
16162
16163
16164
16165
16166
16167
16168
16169
16170
16171
16172
16173
16174
16175
16176
16177
16178
16179
16180
16181
16182
16183
16184
16185
16186
16187
16188
16189
16190
16191
16192
16193
16194
16195
16196
16197
return argVals;
}
// Returns a string describing all message attributes (main, auxiliary, and net).
//
// Parameters:
// pMsgHdr: A message header object.
//
// Return value: A string describing all of the message attributes
function makeAllMsgAttrStr(pMsgHdr)
{
if ((pMsgHdr == null) || (typeof(pMsgHdr) != "object"))
return "";
var msgAttrStr = makeMainMsgAttrStr(pMsgHdr.attr);
var auxAttrStr = makeAuxMsgAttrStr(pMsgHdr.auxattr);
if (auxAttrStr.length > 0)
{
if (msgAttrStr.length > 0)
msgAttrStr += ", ";
msgAttrStr += auxAttrStr;
}
var netAttrStr = makeNetMsgAttrStr(pMsgHdr.netattr);
if (netAttrStr.length > 0)
{
if (msgAttrStr.length > 0)
msgAttrStr += ", ";
msgAttrStr += netAttrStr;
}
return msgAttrStr;
}
// Returns a string describing the main message attributes. Makes use of the
// gMainMsgAttrStrs object for the main message attributes and description
// strings.
//
// Parameters:
// pMainMsgAttrs: The bit field for the main message attributes
// (normally, the 'attr' property of a header object)
// pIfEmptyString: Optional - A string to use if there are no attributes set
//
// Return value: A string describing the main message attributes
function makeMainMsgAttrStr(pMainMsgAttrs, pIfEmptyString)
{
var msgAttrStr = "";
if (typeof(pMainMsgAttrs) == "number")
{
for (var prop in gMainMsgAttrStrs)
{
if ((pMainMsgAttrs & prop) == prop)
{
if (msgAttrStr.length > 0)
msgAttrStr += ", ";
msgAttrStr += gMainMsgAttrStrs[prop];
}
}
}
if ((msgAttrStr.length == 0) && (typeof(pIfEmptyString) == "string"))
msgAttrStr = pIfEmptyString;
return msgAttrStr;
}
// Returns a string describing auxiliary message attributes. Makes use of the
// gAuxMsgAttrStrs object for the auxiliary message attributes and description
// strings.
//
// Parameters:
// pAuxMsgAttrs: The bit field for the auxiliary message attributes
// (normally, the 'auxattr' property of a header object)
// pIfEmptyString: Optional - A string to use if there are no attributes set
//
// Return value: A string describing the auxiliary message attributes
function makeAuxMsgAttrStr(pAuxMsgAttrs, pIfEmptyString)
{
var msgAttrStr = "";
if (typeof(pAuxMsgAttrs) == "number")
{
for (var prop in gAuxMsgAttrStrs)
{
if ((pAuxMsgAttrs & prop) == prop)
{
if (msgAttrStr.length > 0)
msgAttrStr += ", ";
msgAttrStr += gAuxMsgAttrStrs[prop];
}
}
}
if ((msgAttrStr.length == 0) && (typeof(pIfEmptyString) == "string"))
msgAttrStr = pIfEmptyString;
return msgAttrStr;
}
// Returns a string describing network message attributes. Makes use of the
// gNetMsgAttrStrs object for the network message attributes and description
// strings.
//
// Parameters:
// pNetMsgAttrs: The bit field for the network message attributes
// (normally, the 'netattr' property of a header object)
// pIfEmptyString: Optional - A string to use if there are no attributes set
//
// Return value: A string describing the network message attributes
function makeNetMsgAttrStr(pNetMsgAttrs, pIfEmptyString)
{
var msgAttrStr = "";
if (typeof(pNetMsgAttrs) == "number")
{
for (var prop in gNetMsgAttrStrs)
{
if ((pNetMsgAttrs & prop) == prop)
{
if (msgAttrStr.length > 0)
msgAttrStr += ", ";
msgAttrStr += gNetMsgAttrStrs[prop];
}
}
}
if ((msgAttrStr.length == 0) && (typeof(pIfEmptyString) == "string"))
msgAttrStr = pIfEmptyString;
16278
16279
16280
16281
16282
16283
16284
16285
16286
16287
16288
16289
16290
16291
16292
16293
16294
16295
16296
16297
16298
16299
16300
16301
16302
16303
16304
16305
16306
16307
16308
16309
16310
16311
16312
16313
16314
16315
16316
16317
16318
16319
16320
16321
16322
16323
16324
16325
16326
16327
return msgAttrStr;
}
// Given a sub-board code, this function returns a sub-board's group and name.
// If the given sub-board code is "mail", then this will return "Personal mail".
//
// Parameters:
// pSubBoardCode: An internal sub-board code
//
// Return value: A string containing the sub-board code group & name, or
// "Personal email" if it's the personal email sub-board
function subBoardGrpAndName(pSubBoardCode)
{
if (typeof(pSubBoardCode) != "string")
return "";
var subBoardGrpAndName = "";
if (pSubBoardCode == "mail")
subBoardGrpAndName = "Personal mail";
else
{
subBoardGrpAndName = msg_area.sub[pSubBoardCode].grp_name + " - "
+ msg_area.sub[pSubBoardCode].name;
}
return subBoardGrpAndName;
}
// Returns whether a given string matches the current user's name, handle, or alias.
// Does a case-insensitive match.
//
// Parameters:
// pStr: The string to match against the user's name/handle/alias
//
// Return value: Boolean - Whether or not the string matches the current user's name,
// handle, or alias
function userNameHandleAliasMatch(pStr)
{
if (typeof(pStr) != "string")
return false;
var strUpper = pStr.toUpperCase();
return ((strUpper == user.name.toUpperCase()) || (strUpper == user.handle.toUpperCase()) || (strUpper == user.alias.toUpperCase()));
}
// Writes a log message to the system log (using LOG_INFO log level) and to the
// node log. This will prepend the text "Digital Distortion Message Reader ("
// + user.alias + "): " to the log message.
//
// Parameters:
// pMessage: The message to log
// pLogLevel: The log level. Optional - Defaults to LOG_INFO.
function writeToSysAndNodeLog(pMessage, pLogLevel)
{
if (typeof(pMessage) != "string")
return;
var logMessage = "Digital Distortion Message Reader (" + user.alias + "): " + pMessage;
var logLevel = (typeof(pLogLevel) == "number" ? pLogLevel : LOG_INFO);
log(logLevel, logMessage);
bbs.log_str(logMessage);
}
16340
16341
16342
16343
16344
16345
16346
16347
16348
16349
16350
16351
16352
16353
16354
16355
16356
16357
16358
16359
16360
16361
16362
16363
16364
16365
// This function looks up and returns a sub-board code from the sub-board number.
// If no matching sub-board is found, this will return an empty string.
//
// Parameters:
// pSubBoardNum: A sub-board number
//
// Return value: The sub-board code. If no matching sub-board is found, an empty
// string will be returned.
function getSubBoardCodeFromNum(pSubBoardNum)
{
// Ensure we're using a numeric type for the sub-board number
// (in case pSubBoardNum is a string rather than a number)
var subNum = Number(pSubBoardNum);
var subBoardCode = "";
for (var subCode in msg_area.sub)
{
if (msg_area.sub[subCode].number == subNum)
{
subBoardCode = subCode;
break;
}
}
return subBoardCode;
}
16366
16367
16368
16369
16370
16371
16372
16373
16374
16375
16376
16377
16378
16379
16380
16381
16382
16383
16384
16385
16386
16387
16388
16389
16390
16391
16392
16393
16394
16395
16396
16397
16398
16399
16400
16401
16402
16403
16404
16405
16406
16407
16408
16409
16410
16411
// Separates message text and any attachment data.
//
// Parameters:
// pMsgHdr: The message header object
// pMsgText: The text of a message
// pGetB64Data: Optional boolean - Whether or not to get the Base64-encoded
// data for base64-encoded attachments (i.e., in multi-part MIME
// emails). Defaults to true.
//
// Return value: An object containing the following properties:
// msgText: The text of the message, without any of the
// attachment base64-encoded data, etc. If
// the message doesn't have any attachments, then
// this will likely be the same as pMsgText.
// attachments: An array of objects containing the following properties
// for each attachment:
// B64Data: Base64-encoded file data - Only for attachments
// that were attached as base64 in the message (i.e.,
// in a multi-part MIME message). If the attachment
// was uploaded to the user's Synchronet mailbox,
// then the object won't have the B64Data property.
// filename: The name of the attached file
// fullyPathedFilename: The full path & filename of the
// attached file saved on the BBS machine
// errorMsg: An error message if anything went wrong. If
// nothing went wrong, this will be an empty string.
function determineMsgAttachments(pMsgHdr, pMsgText, pGetB64Data)
{
var retObj = new Object();
retObj.msgText = "";
retObj.attachments = [];
retObj.errorMsg = "";
// Keep track of the user's inbox directory: sbbs/data/file/<userNum>.in
var userInboxDir = backslash(backslash(system.data_dir + "file") + format("%04d.in", user.number));
// If the message subject is a filename that exists in the user's
// inbox directory, then add its filename to the list of attached
// filenames that will be returned
var fullyPathedAttachmentFilename = userInboxDir + pMsgHdr.subject;
if (file_exists(fullyPathedAttachmentFilename))
{
retObj.attachments.push({ filename: pMsgHdr.subject,
fullyPathedFilename: fullyPathedAttachmentFilename });
}
// The message to prepend onto the message text if the message has attachments
var msgHasAttachmentsTxt = "\1n\1g\1h- This message contains one or more attachments. Press CTRL-A to download.\1n\r\n"
16413
16414
16415
16416
16417
16418
16419
16420
16421
16422
16423
16424
16425
16426
16427
16428
16429
16430
16431
16432
16433
16434
16435
16436
16437
16438
16439
16440
16441
16442
16443
16444
16445
16446
16447
16448
16449
16450
16451
16452
16453
16454
16455
16456
16457
16458
16459
16460
16461
16462
16463
16464
16465
16466
16467
16468
16469
16470
16471
16472
16473
16474
16475
16476
16477
16478
16479
16480
16481
16482
16483
16484
16485
16486
16487
16488
16489
16490
16491
16492
16493
16494
16495
16496
16497
16498
16499
16500
16501
16502
16503
16504
16505
16506
16507
16508
16509
16510
16511
16512
16513
16514
16515
16516
16517
16518
16519
16520
16521
16522
16523
16524
16525
16526
16527
16528
16529
16530
16531
16532
16533
16534
16535
16536
16537
16538
16539
16540
16541
16542
16543
16544
16545
16546
16547
16548
16549
16550
16551
16552
16553
16554
16555
16556
16557
16558
16559
16560
16561
16562
16563
16564
16565
16566
16567
16568
16569
16570
16571
16572
16573
16574
16575
16576
16577
16578
16579
16580
16581
16582
16583
16584
16585
16586
16587
16588
16589
16590
16591
16592
16593
16594
16595
16596
16597
16598
16599
16600
16601
16602
16603
16604
16605
16606
16607
16608
16609
16610
16611
16612
16613
16614
16615
16616
16617
16618
16619
16620
16621
16622
16623
16624
16625
16626
16627
16628
16629
16630
16631
16632
16633
16634
16635
16636
16637
16638
16639
16640
16641
16642
16643
16644
+ "\1n\1g\1h--------------------------------------------------------------------------\1n\r\n";
// Sanity checking
if (typeof(pMsgText) != "string")
{
// If there are any attachments, prepend the message text with a message
// saying that the message contains attachments.
if (retObj.attachments.length > 0)
retObj.msgText = msgHasAttachmentsTxt + retObj.msgText;
return retObj;
}
// If the message text doesn't include a line starting with -- and a
// line starting with "Content-type:", then then just return the
// the same text in retObj.
//var hasMultiParts = /--\S+\s*Content-Type:/.test(pMsgText);
//var hasMultiParts = ((dashDashIdx > -1) && (/Content-Type/.test(pMsgText)));
var dashDashIdx = pMsgText.indexOf("--");
var hasMultiParts = ((dashDashIdx > -1) && (pMsgText.indexOf("Content-Type", dashDashIdx+1) > dashDashIdx));
if (!hasMultiParts)
{
//retObj.msgText = pMsgText;
// If there are any attachments, prepend the message text with a message
// saying that the message contains attachments.
if (retObj.attachments.length > 0)
retObj.msgText = msgHasAttachmentsTxt + pMsgText;
else
retObj.msgText = pMsgText;
return retObj;
}
var getB64Data = true;
if (typeof(pGetB64Data) == "boolean")
getB64Data = pGetB64Data;
// Look in the message text for a line starting with -- followed by some characters,
// then whitespace
var sepMatches = /--\S+\s/.exec(pMsgText);
var msgSeparator = sepMatches[0];
// If the last character in msgSeparator is a whitepsace character, then
// remove it.
if (/\s/.test(msgSeparator.substr(msgSeparator.length-1, 1)))
msgSeparator = msgSeparator.substr(0, msgSeparator.length-1);
var contentType = ""; // The content type of the current section
var lastContentType = ""; // The content type of the last section
var contentEncodingType = "";
var sepIdx = 0;
var lastSepIdx = -1;
var lastContentTypeIdx = -1;
var lastContentEncodingTypeIdx = -1;
var startIdx = 0;
var gotMessageText = false; // In case the message has both text/plain & text/html
while ((sepIdx = pMsgText.indexOf(msgSeparator, startIdx)) >= 0)
{
var contentEncodingTypeIdx = -1;
// Look for a "Content-Type:" from the starting index
var contentTypeIdx = pMsgText.indexOf("Content-Type: ", startIdx+msgSeparator.length);
if (contentTypeIdx > -1)
{
// Extract the content-type string up to a newline or 15 characters
// if there's no newline
var newlineIdx = pMsgText.indexOf("\n", contentTypeIdx+14);
contentType = pMsgText.substring(contentTypeIdx+14, newlineIdx > -1 ? newlineIdx : contentTypeIdx+29);
// If the last character is whitespace (i.e., a newline), then remove it.
if (/\s/.test(contentType.substr(contentType.length-1, 1)))
contentType = contentType.substr(0, contentType.length-1);
// Update the start index for looking for the next message separator string
// - This should be after the "Content-type:" value.
startIdx = contentTypeIdx + contentType.length;
}
else
{
// No "Content-Type:" string was found
// Update the start index for looking for the next message separator string
startIdx = sepIdx + msgSeparator.length;
}
if ((lastSepIdx > -1) && (lastContentTypeIdx > -1))
{
// msgTextSearchStartIdx stores the index of where to start looking
// for the message text. It could be lastContentTypeIdx, or it could
// be the content encoding type index if the "Content encoding type"
// text is found for the current message part.
var msgTextSearchStartIdx = lastContentTypeIdx;
// Look for "Content-Transfer-Encoding:" right after the content type
// and extract the content encoding type string
contentEncodingTypeIdx = pMsgText.indexOf("Content-Transfer-Encoding:", lastContentTypeIdx);
// If "Content-Transfer-Encoding:" wasn't found after the content type,
// then look just before the content type, but after the last separator
// string.
if (contentEncodingTypeIdx == -1)
contentEncodingTypeIdx = pMsgText.indexOf("Content-Transfer-Encoding:", lastSepIdx);
// If the next "Content-Encoding-Type" is after the current section,
// then this section doesn't have a content type, so blank it out.
if (contentEncodingTypeIdx > sepIdx)
{
contentEncodingTypeIdx = -1;
contentEncodingType = "";
}
else
{
msgTextSearchStartIdx = contentEncodingTypeIdx;
// Extract the content encoding type
var newlineIdx = pMsgText.indexOf("\n", contentEncodingTypeIdx+26);
contentEncodingType = pMsgText.substring(contentEncodingTypeIdx, newlineIdx);
// If the last character is whitespace (i.e., a newline), then remove it.
if (/\s/.test(contentEncodingType.substr(contentEncodingType.length-1, 1)))
contentEncodingType = contentEncodingType.substr(0, contentEncodingType.length-1);
// Update startIdx based on the length of the "content encoding type" string
startIdx += contentEncodingType.length;
// Now, store just the content type in contentEncodingType (i.e., "base64").
contentEncodingType = contentEncodingType.substr(27).toLowerCase();
}
// Look for the message text
var contentTypeSearchIdx = -1;
//if ((contentTypeSearchIdx = lastContentType.indexOf("text/plain")) > -1)
if ((contentTypeSearchIdx = lastContentType.indexOf("text/")) > -1)
{
if (!gotMessageText)
{
var newlineIdx = pMsgText.indexOf("\n", msgTextSearchStartIdx); // Used to be lastContentTypeIdx
if (newlineIdx > -1)
retObj.msgText = pMsgText.substring(newlineIdx+1, sepIdx);
else
retObj.msgText = pMsgText.substring(lastSepIdx, sepIdx);
gotMessageText = true;
}
}
else
{
// Look for a filename in the content-type specification
// If it doesn't contain the filename, then we'll have to look on the
// next line for the filename.
var attachmentFilename = "";
var matches = /name="(.*)"/.exec(lastContentType);
if (matches != null)
{
if (matches.length >= 2)
attachmentFilename = matches[1];
}
if (attachmentFilename.length == 0)
{
// Look for the filename on the next line
var newlineIdx = pMsgText.indexOf("\n", lastContentTypeIdx);
if (newlineIdx > -1)
{
// 1000 chars should be enough
var nextLine = pMsgText.substr(newlineIdx+1, 1000);
var matches = /name="(.*)"/.exec(nextLine);
if (matches != null)
{
if (matches.length >= 2)
attachmentFilename = matches[1];
}
}
}
// If we got a filename, then extract the base64-encoded file data.
if (attachmentFilename.length > 0)
{
var fileInfo = { filename: attachmentFilename,
fullyPathedFilename: gFileAttachDir + attachmentFilename };
// Only extract the base64-encoded data if getB64Data is true
// and the current section's encoding type was actually specified
// as base64.
if (getB64Data && (contentEncodingType == "base64"))
{
// There should be 2 newlines before the base64 data
// TODO: There's a bug here where sometimes it isn't getting
// the correct section for base64 data. The code later that
// looks for an existing filename in the attachments is sort
// of a way around that though.
var lineSeparator = ascii(13) + ascii(10);
var twoNLIdx = pMsgText.indexOf(lineSeparator + lineSeparator, lastContentTypeIdx);
if (twoNLIdx > -1)
{
// Get the base64-encoded data for the current file from the message,
// and remove the newline & carriage return characters and whitespace
// from it.
fileInfo.B64Data = pMsgText.substring(twoNLIdx+2, sepIdx);
fileInfo.B64Data = fileInfo.B64Data.replace(new RegExp(ascii(13) + "|" + ascii(10), "g"), "").trim();
// Update the start index for looking for the next message separator
// string
startIdx = twoNLIdx;
}
}
// Add the file attachment information to the return object.
// If there is already an entry with the filename, then replace
// that one; otherwise, append it.
var fileExists = false;
for (var fileIdx = 0; (fileIdx < retObj.attachments.length) && !fileExists; ++fileIdx)
{
if (retObj.attachments[fileIdx].filename == fileInfo.filename)
{
fileExists = true;
if (getB64Data && fileInfo.hasOwnProperty("B64Data"))
retObj.attachments[fileIdx].B64Data = fileInfo.B64Data;
}
}
if (!fileExists)
retObj.attachments.push(fileInfo);
}
}
}
lastContentType = contentType;
lastSepIdx = sepIdx;
lastContentTypeIdx = contentTypeIdx;
lastContentEncodingTypeIdx = contentEncodingTypeIdx;
// The end of the message will have the message separator string with
// "--" appended to it. If we've reached that point, then we know we
// can stop.
if (pMsgText.substr(sepIdx, msgSeparator.length+2) == msgSeparator + "--")
break;
}
// If there are any attachments, prepend the message text with a message
// saying that the message contains attachments.
if (retObj.attachments.length > 0)
retObj.msgText = msgHasAttachmentsTxt + retObj.msgText;
// If there are attachments and the message text is more than will fit on the
// screen (75% of the console height to account for the ), then append text at
// the end to say there are attachments.
var maxNumCharsOnScreen = 79 * Math.floor(console.screen_rows * 0.75);
if ((retObj.attachments.length > 0) && (retObj.msgText.length > maxNumCharsOnScreen))
{
retObj.msgText += "\1n\r\n\1g\1h--------------------------------------------------------------------------\1n\r\n";
retObj.msgText += "\1g\1h- This message contains one or more attachments. Press CTRL-A to download.\1n";
16646
16647
16648
16649
16650
16651
16652
16653
16654
16655
16656
16657
16658
16659
16660
16661
16662
16663
16664
16665
16666
16667
16668
16669
16670
16671
16672
16673
16674
16675
16676
16677
16678
16679
16680
16681
16682
16683
16684
16685
16686
16687
16688
16689
16690
16691
16692
16693
16694
16695
16696
16697
16698
16699
16700
16701
16702
16703
16704
16705
16706
16707
16708
16709
16710
16711
16712
16713
16714
16715
16716
16717
16718
16719
16720
16721
16722
16723
16724
16725
16726
16727
16728
16729
16730
16731
16732
16733
16734
16735
16736
16737
16738
16739
16740
16741
16742
16743
16744
16745
16746
16747
16748
16749
16750
16751
16752
16753
16754
16755
16756
16757
16758
16759
16760
16761
16762
16763
16764
16765
16766
16767
16768
16769
16770
16771
16772
16773
16774
16775
16776
16777
16778
16779
16780
16781
16782
16783
16784
16785
16786
16787
16788
16789
16790
16791
16792
16793
16794
16795
16796
16797
16798
16799
16800
16801
16802
16803
16804
16805
16806
16807
16808
16809
16810
16811
16812
16813
16814
16815
16816
16817
16818
16819
16820
16821
16822
16823
16824
16825
16826
16827
16828
16829
16830
16831
16832
16833
16834
16835
16836
16837
16838
16839
16840
16841
16842
16843
16844
16845
16846
16847
16848
16849
16850
16851
16852
16853
16854
16855
16856
16857
16858
16859
16860
16861
16862
16863
16864
16865
16866
16867
16868
16869
16870
16871
16872
16873
16874
16875
16876
16877
16878
16879
16880
16881
16882
16883
16884
16885
16886
16887
}
return retObj;
}
// Allows the user to download files that were attached to a message. Takes an
// array of file information given by determineMsgAttachments().
//
// Parameters:
// pAttachments: An array of file attachment information returned by
// determineMsgAttachments()
// for each attachment:
// B64Data: Base64-encoded file data - Only for attachments
// that were attached as base64 in the message (i.e.,
// in a multi-part MIME message). If the attachment
// was uploaded to the user's Synchronet mailbox,
// then the object won't have the B64Data property.
// filename: The name of the attached file
// fullyPathedFilename: The full path & filename of the
// attached file saved on the BBS machine
function sendAttachedFiles(pAttachments)
{
if (Object.prototype.toString.call(pAttachments) !== "[object Array]")
return;
// Synchronet doesn't allow batch downloading of files that aren't in the
// file database, so we have to send each one at a time. :(
// Get the file download confirmation text from text.dat
// 662: "\r\nDownload attached file: \1w%s\1b (%s bytes)"
var DLPromptTextOrig = bbs.text(DownloadAttachedFileQ);
var anyErrors = false;
// For each item in the array, allow the user to download the attachment.
var fileNum = 1;
pAttachments.forEach(function(fileInfo) {
console.print("\1n");
console.crlf();
// If the file doesn't exist and base64 data is available for the file,
// then save it to the temporary attachments directory.
// Note that we need to save the file first in order to get the file's size
// to display in the confirmation prompt to download the file.
// errorMsg will contain an error if something went wrong creating the
// temporary attachments directory, etc.
var errorMsg = "";
var savedFileToBBS = false; // If we base64-decoded the file, we'll want to delete it after it's sent.
if (!file_exists(fileInfo.fullyPathedFilename))
{
if (fileInfo.hasOwnProperty("B64Data"))
{
// If the temporary attachments directory doesn't exist,
// then create it.
var attachmentDirExists = true; // Will be false if it couldn't be created
if (!file_isdir(gFileAttachDir))
{
// If it's a file rather than a directory, then remove it
// before creating it as a directory.
if (file_exists(gFileAttachDir))
file_remove(gFileAttachDir);
attachmentDirExists = mkdir(gFileAttachDir);
}
// Write the file to the BBS machine
if (attachmentDirExists)
{
var attachedFile = new File(fileInfo.fullyPathedFilename);
if (attachedFile.open("wb"))
{
attachedFile.base64 = true;
if (!attachedFile.write(fileInfo.B64Data))
errorMsg = "\1h\1g* \1n\1cCan't send " + quoteStrWithSpaces(fileInfo.filename) + " - Failed to save it to the BBS!";
attachedFile.close();
// Saved the file to the temporary attachments directory (even if it failed
// to write, there's probably still an empty file there).
savedFileToBBS = true;
}
else
errorMsg = "\1h\1g* \1n\1cFailed to save " + quoteStrWithSpaces(fileInfo.filename) + "!";
}
else
errorMsg = "\1h\1g* \1n\1cFailed to create temporary directory on the BBS!";
}
else
errorMsg = "\1h\1g* \1n\1cCan't send " + quoteStrWithSpaces(fileInfo.filename) + " because it doesn't exist or wasn't encoded in a known format";
}
// If we can send the file, then prompt the user for confirmation, and if they
// answer yes, then send it.
// Note that we needed to save the file first in order to get the file's size
// to display in the confirmation prompt.
if (errorMsg.length == 0)
{
// Print the file number
console.print("\1n\1cFile \1g" + fileNum + "\1c of \1g" + pAttachments.length + "\1n");
console.crlf();
// Prompt the user to confirm whether they want to download the
// file. If the user chooses yes, then send it.
var fileSize = Math.round(file_size(fileInfo.fullyPathedFilename));
var DLPromptText = format(DLPromptTextOrig, fileInfo.filename, fileSize);
if (console.yesno(DLPromptText))
bbs.send_file(fileInfo.fullyPathedFilename);
// If the file was base64-decoded and saved to the BBS machine (as opposed to
// being in the user's mailbox), then delete the file.
if (savedFileToBBS)
file_remove(fileInfo.fullyPathedFilename);
}
else
{
// There was an error creating the temporary attachment directory, etc., so
// display the error and pause to let the user read it.
//console.print(errorMsg);
//console.putmsg(word_wrap(errorMsg, console.screen_columns-1, errorMsg.length, false));
//console.crlf();
var errMsgLines = lfexpand(word_wrap(errorMsg, console.screen_columns-1, errorMsg.length, false)).split("\r\n");
console.print("\1n");
for (var errorIdx = 0; errorIdx < errMsgLines.length; ++errorIdx)
{
console.print(errMsgLines[errorIdx]);
console.crlf();
}
console.pause();
}
++fileNum;
});
// If the temporary attachments directory exists, then delete it.
if (file_exists(gFileAttachDir))
deltree(gFileAttachDir);
}
// This function recursively removes a directory and all of its contents. Returns
// whether or not the directory was removed.
//
// Parameters:
// pDir: The directory to remove (with trailing slash).
//
// Return value: Boolean - Whether or not the directory was removed.
function deltree(pDir)
{
if ((pDir == null) || (pDir == undefined))
return false;
if (typeof(pDir) != "string")
return false;
if (pDir.length == 0)
return false;
// Make sure pDir actually specifies a directory.
if (!file_isdir(pDir))
return false;
// Don't wipe out a root directory.
if ((pDir == "/") || (pDir == "\\") || (/:\\$/.test(pDir)) || (/:\/$/.test(pDir)) || (/:$/.test(pDir)))
return false;
// If we're on Windows, then use the "RD /S /Q" command to delete
// the directory. Otherwise, assume *nix and use "rm -rf" to
// delete the directory.
if (deltree.inWindows == undefined)
deltree.inWindows = (/^WIN/.test(system.platform.toUpperCase()));
if (deltree.inWindows)
system.exec("RD " + withoutTrailingSlash(pDir) + " /s /q");
else
system.exec("rm -rf " + withoutTrailingSlash(pDir));
// The directory should be gone, so we should return true. I'd like to verify that the
// directory really is gone, but file_exists() seems to return false for directories,
// even if the directory does exist. So I test to make sure no files are seen in the dir.
return (directory(pDir + "*").length == 0);
/*
// Recursively deleting each file & dir using JavaScript:
var retval = true;
// Open the directory and delete each entry.
var files = directory(pDir + "*");
for (var i = 0; i < files.length; ++i)
{
// If the entry is a directory, then deltree it (Note: The entry
// should have a trailing slash). Otherwise, delete the file.
// If the directory/file couldn't be removed, then break out
// of the loop.
if (file_isdir(files[i]))
{
retval = deltree(files[i]);
if (!retval)
break;
}
else
{
retval = file_remove(files[i]);
if (!retval)
break;
}
}
// Delete the directory specified by pDir.
if (retval)
retval = rmdir(pDir);
return retval;
*/
}
// Removes a trailing (back)slash from a path.
//
// Parameters:
// pPath: A directory path
//
// Return value: The path without a trailing (back)slash.
function withoutTrailingSlash(pPath)
{
if ((pPath == null) || (pPath == undefined))
return "";
var retval = pPath;
if (retval.length > 0)
{
var lastIndex = retval.length - 1;
var lastChar = retval.charAt(lastIndex);
if ((lastChar == "\\") || (lastChar == "/"))
retval = retval.substr(0, lastIndex);
}
return retval;
}
// Adds double-quotes around a string if the string contains spaces.
//
// Parameters:
// pStr: A string to add double-quotes around if it has spaces
//
// Return value: The string with double-quotes if it contains spaces. If the
// string doesn't contain spaces, then the same string will be
// returned.
function quoteStrWithSpaces(pStr)
{
if (typeof(pStr) != "string")
return "";
var strCopy = pStr;
if (pStr.indexOf(" ") > -1)
strCopy = "\"" + pStr + "\"";
return strCopy;
}
// Given a message header field list type number (i.e., the 'type' property for an
// entry in the field_list array in a message header), this returns a text label
// to be used for outputting the field.
//
// Parameters:
// pFieldListType: A field_list entry type (numeric)
// pIncludeTrailingColon: Optional boolean - Whether or not to include a trailing ":"
// at the end of the returned string. Defaults to true.
//
// Return value: A text label for the field (a string)
function msgHdrFieldListTypeToLabel(pFieldListType, pIncludeTrailingColon)
{
// The page at this URL lists the header field types:
// http://synchro.net/docs/smb.html#Header Field Types:

nightfox
committed
var fieldTypeLabel = "Unknown (" + pFieldListType.toString() + ")";
16904
16905
16906
16907
16908
16909
16910
16911
16912
16913
16914
16915
16916
16917
16918
16919
16920
16921
16922
16923
16924
16925
16926
16927
16928
16929
16930
16931
16932
16933
16934
16935
16936
16937
16938
16939
16940
16941
16942
16943
16944
16945
16946
16947
16948
16949
16950
16951
16952
16953
16954
16955
16956
16957
16958
16959
16960
16961
16962
switch (pFieldListType)
{
case 0: // Sender
fieldTypeLabel = "Sender";
break;
case 1: // Sender Agent
fieldTypeLabel = "Sender Agent";
break;
case 2: // Sender net type
fieldTypeLabel = "Sender Net Type";
break;
case 3: // Sender Net Address
fieldTypeLabel = "Sender Net Address";
break;
case 4: // Sender Agent Extension
fieldTypeLabel = "Sender Agent Extension";
break;
case 5: // Sending agent (Sender POS)
fieldTypeLabel = "Sender Agent";
break;
case 6: // Sender organization
fieldTypeLabel = "Sender Organization";
break;
case 16: // Author
fieldTypeLabel = "Author";
break;
case 17: // Author Agent
fieldTypeLabel = "Author Agent";
break;
case 18: // Author Net Type
fieldTypeLabel = "Author Net Type";
break;
case 19: // Author Net Address
fieldTypeLabel = "Author Net Address";
break;
case 20: // Author Extension
fieldTypeLabel = "Author Extension";
break;
case 21: // Author Agent (Author POS)
fieldTypeLabel = "Author Agent";
break;
case 22: // Author Organization
fieldTypeLabel = "Author Organization";
break;
case 32: // Reply To
fieldTypeLabel = "Reply To";
break;
case 33: // Reply To agent
fieldTypeLabel = "Reply To Agent";
break;
case 34: // Reply To net type
fieldTypeLabel = "Reply To net type";
break;
case 35: // Reply To net address
fieldTypeLabel = "Reply To net address";
break;
case 36: // Reply To extension
fieldTypeLabel = "Reply To (extended)";
break;

nightfox
committed
case 37: // Reply To position
16964
16965
16966
16967
16968
16969
16970
16971
16972
16973
16974
16975
16976
16977
16978
16979
16980
fieldTypeLabel = "Reply To position";
break;
case 38: // Reply To organization (0x26 hex)
fieldTypeLabel = "Reply To organization";
break;
case 48: // Recipient (0x30 hex)
fieldTypeLabel = "Recipient";
break;
case 162: // Seen-by
fieldTypeLabel = "Seen-by";
break;
case 163: // Path
fieldTypeLabel = "Path";
break;
case 176: // RFCC822 Header
fieldTypeLabel = "RFCC822 Header";
break;

nightfox
committed
case 177: // RFC822 MSGID
fieldTypeLabel = "RFC822 MSGID";
break;
case 178: // RFC822 REPLYID
fieldTypeLabel = "RFC822 REPLYID";
break;
case 240: // UNKNOWN
fieldTypeLabel = "UNKNOWN";
break;
case 241: // UNKNOWNASCII
fieldTypeLabel = "UNKNOWN (ASCII)";
break;
case 255:
fieldTypeLabel = "UNUSED";
break;

nightfox
committed
fieldTypeLabel = "Unknown (" + pFieldListType.toString() + ")";