Newer
Older
{
14002
14003
14004
14005
14006
14007
14008
14009
14010
14011
14012
14013
14014
14015
14016
14017
14018
14019
14020
14021
14022
14023
14024
14025
14026
14027
14028
14029
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;
}
14031
14032
14033
14034
14035
14036
14037
14038
14039
14040
14041
14042
14043
14044
14045
14046
14047
14048
14049
14050
// 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;
}
}
14076
14077
14078
14079
14080
14081
14082
14083
14084
14085
14086
14087
14088
14089
14090
14091
14092
14093
14094
14095
14096
14097
14098
14099
14100
// 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;
}
14101
14102
14103
14104
14105
14106
14107
14108
14109
14110
14111
14112
14113
14114
14115
14116
14117
14118
14119
14120
14121
14122
14123
14124
14125
14126
14127
14128
14129
14130
14131
14132
14133
14134
14135
14136
14137
14138
14139
14140
14141
14142
14143
14144
14145
14146
14147
14148
14149
14150
14151
14152
14153
14154
14155
14156
14157
14158
14159
14160
14161
14162
14163
14164
14165
14166
14167
14168
14169
14170
14171
14172
14173
14174
14175
14176
14177
14178
14179
14180
14181
14182
14183
14184
14185
14186
14187
14188
14189
14190
14191
14192
14193
14194
14195
14196
14197
14198
14199
14200
14201
14202
14203
14204
14205
14206
14207
14208
14209
14210
14211
14212
14213
14214
14215
14216
14217
14218
14219
14220
14221
14222
14223
14224
14225
14226
14227
14228
14229
14230
14231
14232
14233
14234
14235
14236
14237
14238
14239
14240
14241
14242
14243
14244
14245
14246
14247
14248
14249
14250
14251
14252
14253
14254
14255
14256
14257
14258
14259
14260
14261
14262
14263
14264
14265
14266
14267
14268
14269
14270
14271
14272
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)
//
// Return value: A string describing the main message attributes
function makeMainMsgAttrStr(pMainMsgAttrs)
{
var msgAttrStr = "";
if (typeof(pMainMsgAttrs) == "number")
{
for (var prop in gMainMsgAttrStrs)
{
if ((pMainMsgAttrs & prop) == prop)
{
if (msgAttrStr.length > 0)
msgAttrStr += ", ";
msgAttrStr += gMainMsgAttrStrs[prop];
}
}
}
return msgAttrStr;
}
// Returns a string describing auxiliary message attributes. Makes use of the
// gAuxMsgAttrStrs object for the auxiliary message attributes and description
// strings.
//
// Parameters:
// pMainMsgAttrs: The bit field for the auxiliary message attributes
// (normally, the 'auxattr' property of a header object)
//
// Return value: A string describing the auxiliary message attributes
function makeAuxMsgAttrStr(pMainMsgAttrs)
{
var msgAttrStr = "";
if (typeof(pMainMsgAttrs) == "number")
{
for (var prop in gAuxMsgAttrStrs)
{
if ((pMainMsgAttrs & prop) == prop)
{
if (msgAttrStr.length > 0)
msgAttrStr += ", ";
msgAttrStr += gAuxMsgAttrStrs[prop];
}
}
}
return msgAttrStr;
}
// Returns a string describing network message attributes. Makes use of the
// gNetMsgAttrStrs object for the network message attributes and description
// strings.
//
// Parameters:
// pMainMsgAttrs: The bit field for the network message attributes
// (normally, the 'netattr' property of a header object)
//
// Return value: A string describing the network message attributes
function makeNetMsgAttrStr(pMainMsgAttrs)
{
var msgAttrStr = "";
if (typeof(pMainMsgAttrs) == "number")
{
for (var prop in gNetMsgAttrStrs)
{
if ((pMainMsgAttrs & prop) == prop)
{
if (msgAttrStr.length > 0)
msgAttrStr += ", ";
msgAttrStr += gNetMsgAttrStrs[prop];
}
}
}
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
function writeToSysAndNodeLog(pMessage)
{
if (typeof(pMessage) != "string")
return;
var logMessage = "Digital Distortion Message Reader (" + user.alias + "): " + pMessage;
log(LOG_INFO, logMessage);
bbs.log_str(logMessage);
}
14273
14274
14275
14276
14277
14278
14279
14280
14281
14282
14283
14284
14285
14286
14287
14288
14289
14290
14291
14292
14293
14294
14295
14296
14297
14298
// 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;
}
14299
14300
14301
14302
14303
14304
14305
14306
14307
14308
14309
14310
14311
14312
14313
14314
14315
14316
14317
14318
14319
14320
14321
14322
14323
14324
14325
14326
14327
14328
14329
14330
14331
14332
14333
14334
14335
14336
14337
14338
14339
14340
14341
14342
14343
14344
14345
14346
14347
14348
14349
14350
14351
14352
14353
14354
14355
14356
14357
14358
14359
14360
14361
14362
14363
14364
14365
14366
14367
14368
14369
14370
14371
14372
14373
14374
14375
14376
14377
14378
14379
14380
14381
14382
14383
14384
14385
14386
14387
14388
14389
14390
14391
14392
14393
14394
14395
14396
14397
14398
14399
14400
14401
14402
14403
14404
14405
14406
14407
14408
14409
14410
14411
14412
14413
14414
14415
14416
14417
14418
14419
14420
14421
14422
14423
14424
14425
14426
14427
14428
14429
14430
14431
14432
14433
14434
14435
14436
14437
14438
14439
14440
14441
14442
14443
14444
14445
14446
14447
14448
14449
14450
14451
14452
14453
14454
14455
14456
14457
14458
14459
14460
14461
14462
14463
14464
14465
14466
14467
14468
14469
14470
14471
14472
14473
14474
14475
14476
14477
14478
14479
14480
14481
14482
14483
14484
14485
14486
14487
14488
14489
14490
14491
14492
14493
14494
14495
14496
14497
14498
14499
14500
14501
14502
14503
14504
14505
14506
14507
14508
14509
14510
14511
14512
14513
14514
14515
14516
14517
14518
14519
14520
14521
14522
14523
14524
14525
14526
14527
14528
14529
14530
14531
14532
14533
14534
14535
14536
14537
14538
14539
14540
14541
14542
14543
14544
14545
14546
14547
14548
14549
14550
14551
14552
14553
14554
14555
14556
14557
14558
14559
14560
14561
14562
14563
14564
14565
14566
14567
14568
14569
14570
14571
14572
14573
14574
14575
14576
14577
14578
14579
14580
14581
14582
14583
14584
14585
14586
14587
14588
14589
14590
14591
14592
14593
14594
14595
14596
14597
14598
14599
14600
14601
14602
14603
14604
14605
14606
14607
14608
14609
14610
14611
14612
14613
14614
14615
14616
14617
14618
14619
14620
14621
14622
14623
14624
14625
14626
14627
14628
14629
14630
14631
14632
14633
14634
14635
14636
14637
14638
14639
14640
14641
14642
14643
14644
14645
14646
14647
14648
14649
14650
14651
14652
14653
14654
14655
14656
14657
14658
14659
14660
14661
14662
14663
14664
14665
14666
14667
14668
14669
14670
14671
14672
14673
14674
14675
14676
14677
14678
14679
14680
14681
14682
14683
14684
14685
14686
14687
14688
14689
14690
14691
14692
14693
14694
14695
14696
14697
14698
14699
14700
14701
14702
14703
14704
14705
14706
14707
14708
14709
14710
14711
14712
14713
14714
14715
14716
14717
14718
14719
14720
14721
14722
14723
14724
14725
14726
14727
14728
14729
14730
14731
14732
14733
14734
14735
14736
14737
14738
14739
14740
14741
14742
14743
14744
14745
14746
14747
14748
14749
14750
14751
14752
14753
14754
14755
14756
14757
14758
14759
14760
14761
14762
14763
14764
14765
14766
14767
14768
14769
14770
14771
14772
14773
14774
14775
14776
14777
14778
14779
14780
14781
14782
14783
14784
14785
14786
14787
14788
14789
14790
14791
14792
14793
14794
14795
14796
14797
14798
14799
14800
14801
14802
14803
14804
14805
14806
14807
14808
14809
14810
14811
14812
14813
14814
14815
14816
14817
14818
14819
14820
// 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-D to download.\1n\r\n"
+ "\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-D to download.\1n";
}
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;
}
14821
14822
14823
14824
14825
14826
14827
14828
14829
14830
14831
14832
14833
14834
14835
14836
14837
14838
14839
14840
14841
14842
14843
14844
14845
14846
14847
14848
14849
14850
14851
14852
14853
14854
14855
14856
14857
14858
14859
14860
14861
14862
14863
14864
14865
14866
14867
14868
14869
14870
14871
14872
14873
14874
14875
14876
14877
14878
14879
14880
14881
14882
14883
14884
14885
14886
14887
14888
14889
14890
14891
14892
14893
14894
14895
14896
14897
14898
14899
14900
14901
14902
14903
14904
14905
14906
14907
14908
14909
14910
14911
14912
14913
14914
14915
14916
14917
14918
14919
14920
14921
14922
14923
14924
14925
14926
// 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:
var fieldTypeLabel = "";
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;
case 37: // reply To position
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;
// TODO: Try to figure out what other types there are
default:
fieldTypeLabel = pFieldListType.toString();
break;
}
var includeTrailingColon = (typeof(pIncludeTrailingColon) == "boolean" ? pIncludeTrailingColon : true);
if (includeTrailingColon)
fieldTypeLabel += ":";
return fieldTypeLabel;
}
14927
14928
14929
14930
14931
14932
14933
14934
14935
14936
14937
14938
14939
14940
14941
14942
14943
14944
14945
14946
14947
14948
14949
14950
14951
14952
14953
14954
14955
14956
/////////////////////////////////////////////////////////////////////////
// Debug helper function
// Writes some text on the screen at a given location with a given pause.
//
// Parameters:
// pX: The column number on the screen at which to write the message
// pY: The row number on the screen at which to write the message
// pText: The text to write
// pPauseMS: The pause time, in milliseconds
// pClearLineAttrib: Optional - The color/attribute to clear the line with.
// If not specified or null is specified, defaults to normal attribute.
// pClearLineAfter: Whether or not to clear the line again after the message is dispayed and
// the pause occurred. This is optional.
function writeWithPause(pX, pY, pText, pPauseMS, pClearLineAttrib, pClearLineAfter)
{
var clearLineAttrib = "\1n";
if ((pClearLineAttrib != null) && (typeof(pClearLineAttrib) == "string"))
clearLineAttrib = pClearLineAttrib;
console.gotoxy(pX, pY);
console.cleartoeol(clearLineAttrib);
console.print(pText);
if (pPauseMS > 0)
mswait(pPauseMS);
if (pClearLineAfter)
{
console.gotoxy(pX, pY);
console.cleartoeol(clearLineAttrib);
}
}