Newer
Older
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;
17037
17038
17039
17040
17041
17042
17043
17044
17045
17046
17047
17048
17049
17050
17051
17052
17053
17054
17055
17056
17057
17058
17059
17060
17061
17062
17063
17064
17065
17066
17067
17068
17069
17070
17071
17072
17073
17074
17075
17076
17077
17078
17079
17080
17081
17082
17083
17084
17085
17086
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);
}
17099
17100
17101
17102
17103
17104
17105
17106
17107
17108
17109
17110
17111
17112
17113
17114
17115
17116
17117
17118
17119
17120
17121
17122
17123
17124
// 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;
}
17125
17126
17127
17128
17129
17130
17131
17132
17133
17134
17135
17136
17137
17138
17139
17140
17141
17142
17143
17144
17145
17146
17147
17148
17149
17150
17151
17152
17153
17154
17155
17156
17157
17158
17159
17160
17161
17162
17163
17164
17165
17166
17167
17168
17169
17170
// 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"
17172
17173
17174
17175
17176
17177
17178
17179
17180
17181
17182
17183
17184
17185
17186
17187
17188
17189
17190
17191
17192
17193
17194
17195
17196
17197
17198
17199
17200
17201
17202
17203
17204
17205
17206
17207
17208
17209
17210
17211
17212
17213
17214
17215
17216
17217
17218
17219
17220
17221
17222
17223
17224
17225
17226
17227
17228
17229
17230
17231
17232
17233
17234
17235
17236
17237
17238
17239
17240
17241
17242
17243
17244
17245
17246
17247
17248
17249
17250
17251
17252
17253
17254
17255
17256
17257
17258
17259
17260
17261
17262
17263
17264
17265
17266
17267
17268
17269
17270
17271
17272
17273
17274
17275
17276
17277
17278
17279
17280
17281
17282
17283
17284
17285
17286
17287
17288
17289
17290
17291
17292
17293
17294
17295
17296
17297
17298
17299
17300
17301
17302
17303
17304
17305
17306
17307
17308
17309
17310
17311
17312
17313
17314
17315
17316
17317
17318
17319
17320
17321
17322
17323
17324
17325
17326
17327
17328
17329
17330
17331
17332
17333
17334
17335
17336
17337
17338
17339
17340
17341
17342
17343
17344
17345
17346
17347
17348
17349
17350
17351
17352
17353
17354
17355
17356
17357
17358
17359
17360
17361
17362
17363
17364
17365
17366
17367
17368
17369
17370
17371
17372
17373
17374
17375
17376
17377
17378
17379
17380
17381
17382
17383
17384
17385
17386
17387
17388
17389
17390
17391
17392
17393
17394
17395
17396
17397
17398
17399
17400
17401
17402
17403
+ "\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";
17405
17406
17407
17408
17409
17410
17411
17412
17413
17414
17415
17416
17417
17418
17419
17420
17421
17422
17423
17424
17425
17426
17427
17428
17429
17430
17431
17432
17433
17434
17435
17436
17437
17438
17439
17440
17441
17442
17443
17444
17445
17446
17447
17448
17449
17450
17451
17452
17453
17454
17455
17456
17457
17458
17459
17460
17461
17462
17463
17464
17465
17466
17467
17468
17469
17470
17471
17472
17473
17474
17475
17476
17477
17478
17479
17480
17481
17482
17483
17484
17485
17486
17487
17488
17489
17490
17491
17492
17493
17494
17495
17496
17497
17498
17499
17500
17501
17502
17503
17504
17505
17506
17507
17508
17509
17510
17511
17512
17513
17514
17515
17516
17517
17518
17519
17520
17521
17522
17523
17524
17525
17526
17527
17528
17529
17530
17531
17532
17533
17534
17535
17536
17537
17538
17539
17540
17541
17542
17543
17544
17545
17546
17547
17548
17549
17550
17551
17552
17553
17554
17555
17556
17557
17558
17559
17560
17561
17562
17563
17564
17565
17566
17567
17568
17569
17570
17571
17572
17573
17574
17575
17576
17577
17578
17579
17580
17581
17582
17583
17584
17585
17586
17587
17588
17589
17590
17591
17592
17593
17594
17595
17596
17597
17598
17599
17600
17601
17602
17603
17604
17605
17606
17607
17608
17609
17610
17611
17612
17613
17614
17615
17616
17617
17618
17619
17620
17621
17622
17623
17624
17625
17626
17627
17628
17629
17630
17631
17632
17633
17634
17635
17636
17637
17638
17639
17640
17641
17642
17643
17644
17645
17646
}
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() + ")";
17663
17664
17665
17666
17667
17668
17669
17670
17671
17672
17673
17674
17675
17676
17677
17678
17679
17680
17681
17682
17683
17684
17685
17686
17687
17688
17689
17690
17691
17692
17693
17694
17695
17696
17697
17698
17699
17700
17701
17702
17703
17704
17705
17706
17707
17708
17709
17710
17711
17712
17713
17714
17715
17716
17717
17718
17719
17720
17721
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
17723
17724
17725
17726
17727
17728
17729
17730
17731
17732
17733
17734
17735
17736
17737
17738
17739
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() + ")";
break;
}
var includeTrailingColon = (typeof(pIncludeTrailingColon) == "boolean" ? pIncludeTrailingColon : true);
if (includeTrailingColon)
fieldTypeLabel += ":";
return fieldTypeLabel;
}

nightfox
committed
17767
17768
17769
17770
17771
17772
17773
17774
17775
17776
17777
17778
17779
17780
17781
17782
17783
// Capitalizes the first character of a string.
//
// Parameters:
// pStr: The string to capitalize
//
// Return value: A version of the sting with the first character capitalized
function capitalizeFirstChar(pStr)
{
var retStr = "";
if (typeof(pStr) == "string")
{
if (pStr.length > 0)
retStr = pStr.charAt(0).toUpperCase() + pStr.slice(1);
}
return retStr;
}
17784
17785
17786
17787
17788
17789
17790
17791
17792
17793
17794
17795
17796
17797
17798
17799
17800
17801
17802
17803
17804
17805
17806
17807
17808
17809
17810
17811
17812
17813
17814
17815
17816
17817
17818
17819
17820
17821
17822
17823
17824
17825
17826
17827
17828
17829
17830
17831
17832
17833
17834
17835
17836
17837
17838
17839
17840
17841
17842
17843
17844
17845
17846
17847
17848
17849
17850
17851
17852
17853
17854
17855
17856
17857
17858
17859
17860
17861
17862
17863
17864
17865
17866
17867
17868
17869
17870
17871
17872
17873
17874
17875
17876
17877
17878
17879
17880
17881
17882
17883
17884
// Parses a list of numbers (separated by commas or spaces), which may contain
// ranges separated by dashes. Returns an array of the individual numbers.
//
// Parameters:
// pList: A comma-separated list of numbers, some which may contain
// 2 numbers separated by a dash denoting a range of numbers.
//
// Return value: An array of the individual numbers from the list
function parseNumberList(pList)
{
if (typeof(pList) != "string")
return [];
var numberList = [];
// Split pList on commas or spaces
var commaOrSpaceSepArray = pList.split(/[\s,]+/);
if (commaOrSpaceSepArray.length > 0)
{
// Go through the comma-separated array - If the element is a
// single number, then append it to the number list to be returned.
// If there is a range (2 numbers separated by a dash), then
// append each number in the range individually to the array to be
// returned.
for (var i = 0; i < commaOrSpaceSepArray.length; ++i)
{
// If it's a single number, append it to numberList.
if (/^[0-9]+$/.test(commaOrSpaceSepArray[i]))
numberList.push(+commaOrSpaceSepArray[i]);
// If there are 2 numbers separated by a dash, then split it on the
// dash and generate the intermediate numbers.
else if (/^[0-9]+-[0-9]+$/.test(commaOrSpaceSepArray[i]))
{
var twoNumbers = commaOrSpaceSepArray[i].split("-");
if (twoNumbers.length == 2)
{
var num1 = +twoNumbers[0];
var num2 = +twoNumbers[1];
// If the 1st number is bigger than the 2nd, then swap them.
if (num1 > num2)
{
var temp = num1;
num1 = num2;
num2 = temp;
}
// Append each individual number in the range to numberList.
for (var number = num1; number <= num2; ++number)
numberList.push(number);
}
}
}
}
return numberList;
}
// Inputs a single keypress from the user from a list of valid keys, allowing
// input modes (see K_* in sbbsdefs.js for mode bits). This is similar to
// console.getkeys(), except that this allows mode bits (such as K_NOCRLF, etc.).
//
// Parameters:
// pAllowedKeys: A list of allowed keys (string)
// pMode: Mode bits (see K_* in sbbsdefs.js)
//
// Return value: The user's inputted keypress
function getAllowedKeyWithMode(pAllowedKeys, pMode)
{
var userInput = "";
var keypress = "";
var i = 0;
var matchedKeypress = false;
while (!matchedKeypress)
{
keypress = console.getkey(K_NOECHO|pMode);
// Check to see if the keypress is one of the allowed keys
for (i = 0; i < pAllowedKeys.length; ++i)
{
if (keypress == pAllowedKeys[i])
userInput = keypress;
else if (keypress.toUpperCase() == pAllowedKeys[i])
userInput = keypress.toUpperCase();
else if (keypress.toLowerCase() == pAllowedKeys[i])
userInput = keypress.toLowerCase();
if (userInput.length > 0)
{
matchedKeypress = true;
// If K_NOECHO is not in pMode, then output the user's keypress
if ((pMode & K_NOECHO) == 0)
console.print(userInput);
// If K_NOCRLF is not in pMode, then output a CRLF
if ((pMode & K_NOCRLF) == 0)
console.crlf();
break;
}
}
}
return userInput;
}
17885
17886
17887
17888
17889
17890
17891
17892
17893
17894
17895
17896
17897
17898
17899
17900
17901
17902
17903
17904
17905
17906
17907
17908
17909
17910
17911
17912
17913
17914
17915
17916
17917
17918
17919
// Loads a text file (an .ans or .asc) into an array. This will first look for
// an .ans version, and if exists, convert to Synchronet colors before loading
// it. If an .ans doesn't exist, this will look for an .asc version.
//
// Parameters:
// pFilenameBase: The filename without the extension
// pMaxNumLines: Optional - The maximum number of lines to load from the text file
//
// Return value: An array containing the lines from the text file
function loadTextFileIntoArray(pFilenameBase, pMaxNumLines)
{
if (typeof(pFilenameBase) != "string")
return new Array();
var maxNumLines = (typeof(pMaxNumLines) == "number" ? pMaxNumLines : -1);
var txtFileLines = new Array();
// See if there is a header file that is made for the user's terminal
// width (areaChgHeader-<width>.ans/asc). If not, then just go with
// msgHeader.ans/asc.
var txtFileExists = true;
var txtFilenameFullPath = gStartupPath + pFilenameBase;
var txtFileFilename = "";
if (file_exists(txtFilenameFullPath + "-" + console.screen_columns + ".ans"))
txtFileFilename = txtFilenameFullPath + "-" + console.screen_columns + ".ans";
else if (file_exists(txtFilenameFullPath + "-" + console.screen_columns + ".asc"))
txtFileFilename = txtFilenameFullPath + "-" + console.screen_columns + ".asc";
else if (file_exists(txtFilenameFullPath + ".ans"))
txtFileFilename = txtFilenameFullPath + ".ans";
else if (file_exists(txtFilenameFullPath + ".asc"))
txtFileFilename = txtFilenameFullPath + ".asc";
else
txtFileExists = false;
if (txtFileExists)
{
var syncConvertedHdrFilename = txtFileFilename;
// If the user's console doesn't support ANSI and the header file is ANSI,
// then convert it to Synchronet attribute codes and read that file instead.
if (!console.term_supports(USER_ANSI) && (getStrAfterPeriod(txtFileFilename).toUpperCase() == "ANS"))
{
syncConvertedHdrFilename = txtFilenameFullPath + "_converted.asc";
if (!file_exists(syncConvertedHdrFilename))
{
if (getStrAfterPeriod(txtFileFilename).toUpperCase() == "ANS")
{
var filenameBase = txtFileFilename.substr(0, dotIdx);
var cmdLine = system.exec_dir + "ans2asc \"" + txtFileFilename + "\" \""
+ syncConvertedHdrFilename + "\"";
// Note: Both system.exec(cmdLine) and
// bbs.exec(cmdLine, EX_NATIVE, gStartupPath) could be used to
// execute the command, but system.exec() seems noticeably faster.
system.exec(cmdLine);
}
else
syncConvertedHdrFilename = txtFileFilename;
}
}
17942
17943
17944
17945
17946
17947
17948
17949
17950
17951
17952
17953
17954
17955
17956
17957
17958
17959
17960
17961
17962
/*
// If the header file is ANSI, then convert it to Synchronet attribute
// codes and read that file instead. This is done so that this script can
// accurately get the file line lengths using console.strlen().
var syncConvertedHdrFilename = txtFilenameFullPath + "_converted.asc";
if (!file_exists(syncConvertedHdrFilename))
{
if (getStrAfterPeriod(txtFileFilename).toUpperCase() == "ANS")
{
var filenameBase = txtFileFilename.substr(0, dotIdx);
var cmdLine = system.exec_dir + "ans2asc \"" + txtFileFilename + "\" \""
+ syncConvertedHdrFilename + "\"";
// Note: Both system.exec(cmdLine) and
// bbs.exec(cmdLine, EX_NATIVE, gStartupPath) could be used to
// execute the command, but system.exec() seems noticeably faster.
system.exec(cmdLine);
}
else
syncConvertedHdrFilename = txtFileFilename;
}
*/
17963
17964
17965
17966
17967
17968
17969
17970
17971
17972
17973
17974
17975
17976
17977
17978
17979
17980
17981
17982
17983
17984
17985
17986
17987
17988
17989
17990
17991
17992
// Read the header file into txtFileLines
var hdrFile = new File(syncConvertedHdrFilename);
if (hdrFile.open("r"))
{
var fileLine = null;
while (!hdrFile.eof)
{
// Read the next line from the header file.
fileLine = hdrFile.readln(2048);
// fileLine should be a string, but I've seen some cases
// where it isn't, so check its type.
if (typeof(fileLine) != "string")
continue;
// Make sure the line isn't longer than the user's terminal
//if (fileLine.length > console.screen_columns)
// fileLine = fileLine.substr(0, console.screen_columns);
txtFileLines.push(fileLine);
// If the header array now has the maximum number of lines, then
// stop reading the header file.
if (txtFileLines.length == maxNumLines)
break;
}
hdrFile.close();
}
}
return txtFileLines;
}
// Returns the portion (if any) of a string after the period.
//
// Parameters:
// pStr: The string to extract from
//
// Return value: The portion of the string after the dot, if there is one. If
// not, then an empty string will be returned.
function getStrAfterPeriod(pStr)