Newer
Older

nightfox
committed
return ANSICount;
}
// Removes ANSI codes from a string.
//
// Parameters:
// pStr: The string to remove ANSI codes from

nightfox
committed
// pANSIRegexes: An array of regular expressions to use for searching for ANSI codes
//
// Return value: A version of the string without ANSI codes

nightfox
committed
function removeANSIFromStr(pStr, pANSIRegexes)
{
if (typeof(pStr) != "string")
return "";
var theStr = pStr;

nightfox
committed
for (var i = 0; i < pANSIRegexes.length; ++i)
theStr = theStr.replace(pANSIRegexes[i], "");
17019
17020
17021
17022
17023
17024
17025
17026
17027
17028
17029
17030
17031
17032
17033
17034
17035
17036
17037
17038
17039
17040
17041
17042
17043
17044
17045
17046
17047
17048
return theStr;
}
// Returns the last index in a string where a regex is found.
// From this page:
// http://stackoverflow.com/questions/273789/is-there-a-version-of-javascripts-string-indexof-that-allows-for-regular-expr
//
// Parameters:
// pStr: The string to search
// pRegex: The regular expression to match in the string
// pStartPos: Optional - The starting position in the string. If this is not
// passed, then the end of the string will be used.
//
// Return value: The last index in the string where the regex is found, or -1 if not found.
function regexLastIndexOf(pStr, pRegex, pStartPos)
{
pRegex = (pRegex.global) ? pRegex : new RegExp(pRegex.source, "g" + (pRegex.ignoreCase ? "i" : "") + (pRegex.multiLine ? "m" : ""));
if (typeof(pStartPos) == "undefined")
pStartPos = pStr.length;
else if (pStartPos < 0)
pStartPos = 0;
var stringToWorkWith = pStr.substring(0, pStartPos + 1);
var lastIndexOf = -1;
var nextStop = 0;
while ((result = pRegex.exec(stringToWorkWith)) != null)
{
lastIndexOf = result.index;
pRegex.lastIndex = ++nextStop;
}
return lastIndexOf;
17051
17052
17053
17054
17055
17056
17057
17058
17059
17060
17061
17062
17063
17064
17065
17066
17067
17068
17069
17070
// Returns the first index in a string where a regex is found.
//
// Parameters:
// pStr: The string to search
// pRegex: The regular expression to match in the string
//
// Return value: The first index in the string where the regex is found, or -1 if not found.
function regexFirstIndexOf(pStr, pRegex)
{
pRegex = (pRegex.global) ? pRegex : new RegExp(pRegex.source, "g" + (pRegex.ignoreCase ? "i" : "") + (pRegex.multiLine ? "m" : ""));
var indexOfRegex = -1;
var nextStop = 0;
while ((result = pRegex.exec(pStr)) != null)
{
indexOfRegex = result.index;
pRegex.lastIndex = ++nextStop;
}
return indexOfRegex;
}
17071
17072
17073
17074
17075
17076
17077
17078
17079
17080
17081
17082
17083
17084
17085
17086
17087
17088
17089
17090
17091
17092
17093
17094
17095
17096
17097
17098
17099
17100
17101
17102
17103
17104
17105
17106
17107
17108
17109
17110
17111
17112
17113
17114
17115
17116
// Converts ANSI ;-delimited modes (such as [Value;...;Valuem) to Synchronet
// attribute codes
//
// Parameters:
// pText: The text with ANSI ;-delimited modes to convert
//
// Return value: The text with ANSI ;-delimited modes converted to Synchronet attributes
function ANSIMultiConvertToSyncCodes(pText)
{
var multiMatches = pText.match(/\[[0-9]+(;[0-9]+)+m/g);
if (multiMatches == null)
return pText;
var updatedText = pText;
for (var i = 0; i < multiMatches.length; ++i)
{
// Copy the string, with the [ removed from the beginning and the
// trailing 'm' removed
var text = multiMatches[i].substr(2);
text = text.substr(0, text.length-1);
var codes = text.split(";");
var syncCodes = "";
for (var idx = 0; idx < codes.length; ++idx)
{
if (codes[idx] == "0") // All attributes off
syncCodes += "\1n";
else if (codes[idx] == "1") // Bold on (high intensity)
syncCodes += "\1h";
else if (codes[idx] == "5") // Blink on
syncCodes += "\1i";
else if (codes[idx] == "30") // Black foreground
syncCodes += "\1k";
else if (codes[idx] == "31") // Red foreground
syncCodes += "\1r";
else if (codes[idx] == "32") // Green foreground
syncCodes += "\1g";
else if (codes[idx] == "33") // Yellow foreground
syncCodes += "\1y";
else if (codes[idx] == "34") // Blue foreground
syncCodes += "\1b";
else if (codes[idx] == "35") // Magenta foreground
syncCodes += "\1m";
else if (codes[idx] == "36") // Cyan foreground
syncCodes += "\1c";
else if (codes[idx] == "37") // White foreground
syncCodes += "\1w";
else if (codes[idx] == "40") // Black background
else if (codes[idx] == "41") // Red background
else if (codes[idx] == "42") // Green background
else if (codes[idx] == "43") // Yellow background
else if (codes[idx] == "44") // Blue background
else if (codes[idx] == "45") // Magenta background
else if (codes[idx] == "46") // Cyan background
else if (codes[idx] == "47") // White background
}
updatedText = updatedText.replace(multiMatches[i], syncCodes);
}
return updatedText;
}
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
17171
17172
17173
17174
17175
17176
17177
17178
17179
17180
17181
17182
17183
17184
17185
17186
// Given some text, this converts ANSI color codes to Synchronet codes and
// removes unwanted ANSI codes (such as cursor movement codes, etc.).
//
// Parameters:
// pText: A string to process
//
// Return value: A version of the string with Synchronet color codes converted to
// Synchronet attribute codes and unwanted ANSI codes removed
function cvtANSIToSyncAndRemoveUnwantedANSI(pText)
{
// Attributes
var txt = pText.replace(/\[0[mM]/g, "\1n"); // All attributes off
txt = txt.replace(/\[1[mM]/g, "\1h"); // Bold on (use high intensity)
txt = txt.replace(/\[5[mM]/g, "\1i"); // Blink on
// Foreground colors
txt = txt.replace(/\[30[mM]/g, "\1k"); // Black foreground
txt = txt.replace(/\[31[mM]/g, "\1r"); // Red foreground
txt = txt.replace(/\[32[mM]/g, "\1g"); // Green foreground
txt = txt.replace(/\[33[mM]/g, "\1y"); // Yellow foreground
txt = txt.replace(/\[34[mM]/g, "\1b"); // Blue foreground
txt = txt.replace(/\[35[mM]/g, "\1m"); // Magenta foreground
txt = txt.replace(/\[36[mM]/g, "\1c"); // Cyan foreground
txt = txt.replace(/\[37[mM]/g, "\1w"); // White foreground
// Background colors
txt = txt.replace(/\[40[mM]/g, "\1" + "0"); // Black background
txt = txt.replace(/\[41[mM]/g, "\1" + "1"); // Red background
txt = txt.replace(/\[42[mM]/g, "\1" + "2"); // Green background
txt = txt.replace(/\[43[mM]/g, "\1" + "3"); // Yellow background
txt = txt.replace(/\[44[mM]/g, "\1" + "4"); // Blue background
txt = txt.replace(/\[45[mM]/g, "\1" + "5"); // Magenta background
txt = txt.replace(/\[46[mM]/g, "\1" + "6"); // Cyan background
txt = txt.replace(/\[47[mM]/g, "\1" + "7"); // White background
// Convert ;-delimited modes (such as [Value;...;Valuem)
txt = ANSIMultiConvertToSyncCodes(txt);
// Remove ANSI codes that are not wanted (such as moving the cursor, etc.)
txt = txt.replace(/\[[0-9]+[aA]/g, ""); // Cursor up
txt = txt.replace(/\[[0-9]+[bB]/g, ""); // Cursor down
txt = txt.replace(/\[[0-9]+[cC]/g, ""); // Cursor forward
txt = txt.replace(/\[[0-9]+[dD]/g, ""); // Cursor backward
txt = txt.replace(/\[[0-9]+;[0-9]+[hH]/g, ""); // Cursor position
txt = txt.replace(/\[[0-9]+;[0-9]+[fF]/g, ""); // Cursor position
txt = txt.replace(/\[[sS]/g, ""); // Restore cursor position
txt = txt.replace(/\[2[jJ]/g, ""); // Erase display
txt = txt.replace(/\[[kK]/g, ""); // Erase line
txt = txt.replace(/\[=[0-9]+[hH]/g, ""); // Set various screen modes
txt = txt.replace(/\[=[0-9]+[lL]/g, ""); // Reset various screen modes
return txt;
}
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
// 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)
{
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
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;
}
17264
17265
17266
17267
17268
17269
17270
17271
17272
17273
17274
17275
17276
17277
17278
17279
17280
17281
17282
17283
// 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;
}
}
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
// 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;
}
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
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;
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
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);
}
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
// 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;
}
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
// 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"
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
17647
17648
17649
17650
17651
17652
17653
17654
17655
17656
17657
17658
17659
17660
17661
17662
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
17722
17723
17724
17725
17726
17727
17728
17729
17730
17731
17732
17733
17734
17735
17736
17737
17738
17739
17740
17741
17742
17743
17744
17745
17746
17747
17748
17749
17750
17751
17752
17753
17754
17755
17756
17757
17758
17759
17760
17761
17762
17763
17764
17765
17766
17767
17768
17769
17770
17771
17772
17773
17774
17775
17776
17777
17778
17779
17780
17781
17782
17783
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
+ "\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";
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
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
17920
17921
17922
17923
17924
17925
17926
17927
17928
17929
17930
17931
17932
17933
17934
17935
17936
17937
17938
17939
17940
17941
17942
17943
17944
17945
17946
17947
17948
17949
17950
17951
17952
17953
17954
17955
17956
17957
17958
17959
17960
17961
17962
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
17993
17994
17995
17996
17997
17998
17999
18000
}
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