initial work on wtv-mail system:
- WTVMail Class - MailStore functions - Can show user mail intro page (see home.js for link example) - Create initial welcome email (poop for now) when creating mailstore - Can list Inbox - no sending yet - no reading yet - no receiving yet
@@ -47,6 +47,7 @@ minisrv v${minisrv_config.version}${(minisrv_config.config.git_commit) ? ' git-'
|
|||||||
<li><a href="client:relog">client:relog (direct)</a></li>
|
<li><a href="client:relog">client:relog (direct)</a></li>
|
||||||
<li><a href="wtv-flashrom:/willie" selected>Ultra Willies</a> ~ <a href="wtv-tricks:/tricks">Tricks</a></li>
|
<li><a href="wtv-flashrom:/willie" selected>Ultra Willies</a> ~ <a href="wtv-tricks:/tricks">Tricks</a></li>
|
||||||
<li><a href="wtv-setup:/setup">Setup (Including BG Music)</a></li>
|
<li><a href="wtv-setup:/setup">Setup (Including BG Music)</a></li>
|
||||||
|
<li><a href="${ssid_sessions[socket.ssid].mailstore.mailstoreExists() ? 'wtv-mail:/listmail' : 'wtv-mail:/DiplomaMail'}">Mail (not ready)</a>
|
||||||
`;
|
`;
|
||||||
if (ssid_sessions[socket.ssid].hasCap("client-can-do-chat")) {
|
if (ssid_sessions[socket.ssid].hasCap("client-can-do-chat")) {
|
||||||
data += "<li><a href=\"wtv-chat:/home\">IRC Chat Test</a></li>\n"
|
data += "<li><a href=\"wtv-chat:/home\">IRC Chat Test</a></li>\n"
|
||||||
|
|||||||
94
zefie_wtvp_minisrv/ServiceVault/wtv-mail/DiplomaMail.js
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
var minisrv_service_file = true;
|
||||||
|
|
||||||
|
headers = `200 OK
|
||||||
|
Content-type: text/html`;
|
||||||
|
|
||||||
|
data = `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>
|
||||||
|
Welcome to Mail
|
||||||
|
</title>
|
||||||
|
<display
|
||||||
|
noscroll
|
||||||
|
>
|
||||||
|
</head>
|
||||||
|
<body hspace=0 vspace=0
|
||||||
|
text='E6E6E6' link='E6E6E6' vlink='E6E6E6'
|
||||||
|
fontsize='medium'
|
||||||
|
bgcolor=00292f
|
||||||
|
>
|
||||||
|
<table cellspacing=0 cellpadding=0>
|
||||||
|
<tr>
|
||||||
|
<td width=560 height=96 valign=top>
|
||||||
|
<table background="wtv-guide:/ROMCache/help/common/helpMastheadBlank.swf" width=560 height=96 cellspacing=0 cellpadding=0>
|
||||||
|
<tr>
|
||||||
|
<td width=107 height=96 valign=top rowspan=2>
|
||||||
|
<spacer type=vertical height=7><br>
|
||||||
|
<spacer type=horizontal width=7>
|
||||||
|
<a href='wtv-home:/home'>
|
||||||
|
<img src="${minisrv_config.config.service_logo}" width=87 height=67>
|
||||||
|
</a>
|
||||||
|
<td width=453 valign=top>
|
||||||
|
<spacer type=vertical height=54><br>
|
||||||
|
<font size=+3><blackface>
|
||||||
|
Welcome to Mail
|
||||||
|
</blackface></font>
|
||||||
|
<tr>
|
||||||
|
<td align=right>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
<tr>
|
||||||
|
<td width=560 valign=top height=225>
|
||||||
|
<table cellpadding=0 cellspacing=0 width=560>
|
||||||
|
<tr>
|
||||||
|
<td width=25 height=17>
|
||||||
|
<td width=535>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<td height=225 rowspan=2 valign=top>
|
||||||
|
<table cellpadding=0 cellspacing=0 height=225 width=535>
|
||||||
|
<tr>
|
||||||
|
<td height=15>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<td valign=top>
|
||||||
|
In Mail, you can exchange typed messages—called
|
||||||
|
<i>e-mail</i>—with anyone in the world who has an e-mail
|
||||||
|
address. This is your e-mail address:
|
||||||
|
<blockquote>
|
||||||
|
<b>${ssid_sessions[socket.ssid].getSessionData("subscriber_username")}@${minisrv_config.config.service_name}</b>
|
||||||
|
</blockquote>
|
||||||
|
Choose <b>Begin</b> to start using Mail. <!-- Or to learn more,
|
||||||
|
choose this link:
|
||||||
|
<spacer type=vertical height=12><br>
|
||||||
|
<table bgcolor=001316 cellpadding=0 cellspacing=8 href="wtv-guide:/help?topic=Common&subtopic=PreTour&tourTopic=Mail">
|
||||||
|
<tr>
|
||||||
|
<td height=20><font color=4B7136>
|
||||||
|
<b>€
|
||||||
|
<td>
|
||||||
|
<font color=4B7136><b>Introductory tutorial about Mail
|
||||||
|
<td>
|
||||||
|
</table>
|
||||||
|
-->
|
||||||
|
<tr>
|
||||||
|
<td width=35>
|
||||||
|
<td width=450>
|
||||||
|
<td width=50>
|
||||||
|
</table>
|
||||||
|
</table>
|
||||||
|
<tr>
|
||||||
|
<td valign=top height=55 align=right>
|
||||||
|
<form>
|
||||||
|
<font color=ffcf69><shadow>
|
||||||
|
<input type=button usestyle borderimage="file://ROM/Borders/ButtonBorder2.bif"
|
||||||
|
action="wtv-mail:/listmail"
|
||||||
|
value="Begin"
|
||||||
|
width='110'
|
||||||
|
selected>
|
||||||
|
<spacer type=horizontal width=20>
|
||||||
|
</shadow></font>
|
||||||
|
</form>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
`;
|
||||||
|
After Width: | Height: | Size: 54 B |
BIN
zefie_wtvp_minisrv/ServiceVault/wtv-mail/content/images/Mail.gif
Normal file
|
After Width: | Height: | Size: 613 B |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
BIN
zefie_wtvp_minisrv/ServiceVault/wtv-mail/content/images/dot.gif
Normal file
|
After Width: | Height: | Size: 348 B |
|
After Width: | Height: | Size: 348 B |
|
After Width: | Height: | Size: 405 B |
321
zefie_wtvp_minisrv/ServiceVault/wtv-mail/listmail.js
Normal file
@@ -0,0 +1,321 @@
|
|||||||
|
var minisrv_service_file = true;
|
||||||
|
|
||||||
|
var mailstore_exists = false;
|
||||||
|
|
||||||
|
function mail_end_error(msg) {
|
||||||
|
var errpage = doErrorPage("400", msg);
|
||||||
|
headers = errpage[0];
|
||||||
|
data = errpage[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if mailstore exists (returns null if guest)
|
||||||
|
mailstore_exists = ssid_sessions[socket.ssid].mailstore.mailstoreExists();
|
||||||
|
|
||||||
|
// create mailstore if it doesnt exist (also returns null if guest)
|
||||||
|
if (!mailstore_exists) mailstore_exists = ssid_sessions[socket.ssid].mailstore.createMailstore();
|
||||||
|
|
||||||
|
if (mailstore_exists) {
|
||||||
|
// mailstore exists and user is not guest
|
||||||
|
|
||||||
|
var default_limit = (minisrv_config.services[service_name].messages_per_page) ? minisrv_config.services[service_name].messages_per_page : 25; // user config or 25
|
||||||
|
var mailbox = (request_headers.query.mailbox) ? parseInt(request_headers.query.mailbox) : 0;
|
||||||
|
var limit = (request_headers.query.limit) ? parseInt(request_headers.query.limit) : default_limit;
|
||||||
|
var reverse_sort = (request_headers.query.reverse_sort) ? true : false;
|
||||||
|
var page = (request_headers.query.page) ? parseInt(request_headers.query.page) : 0;
|
||||||
|
|
||||||
|
// get mailbox name
|
||||||
|
var mailbox_name = ssid_sessions[socket.ssid].mailstore.getMailboxById(parseInt(mailbox));
|
||||||
|
|
||||||
|
// if false or null, then mailbox is invalid
|
||||||
|
if (!mailbox_name) {
|
||||||
|
mail_end_error("Invalid Mailbox ID");
|
||||||
|
} else {
|
||||||
|
// mailboxid is ok
|
||||||
|
if (!ssid_sessions[socket.ssid].mailstore.mailboxExists(mailbox)) {
|
||||||
|
// mailbox does not yet exist, create it
|
||||||
|
var mailbox_exists = ssid_sessions[socket.ssid].mailstore.createMailbox(mailbox);
|
||||||
|
if (!mailbox_exists) {
|
||||||
|
// failed to create mailbox for some reason
|
||||||
|
mail_end_error();
|
||||||
|
} else {
|
||||||
|
if (mailbox === 0) {
|
||||||
|
// Just created Inbox for the first time, so create the welcome message
|
||||||
|
ssid_sessions[socket.ssid].mailstore.createWelcomeMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var message_list = ssid_sessions[socket.ssid].mailstore.listMessages(mailbox, limit, reverse_sort, (page * limit))
|
||||||
|
var total_message_count = ssid_sessions[socket.ssid].mailstore.countMessages(mailbox);
|
||||||
|
var username = ssid_sessions[socket.ssid].getSessionData("subscriber_username");
|
||||||
|
var notImplementedAlert = new clientShowAlert({
|
||||||
|
'image': minisrv_config.config.service_logo,
|
||||||
|
'message': "This feature is not available.",
|
||||||
|
'buttonlabel1': "Okay",
|
||||||
|
'buttonaction1': "client:donothing",
|
||||||
|
'noback': true,
|
||||||
|
}).getURL();
|
||||||
|
|
||||||
|
headers = `200 OK
|
||||||
|
Content-type: text/html`;
|
||||||
|
|
||||||
|
data = `<sendpanel action="wtv-mail:/sendmail"
|
||||||
|
message="Write a new e-mail message"
|
||||||
|
label="Write">
|
||||||
|
<savepanel
|
||||||
|
action="wtv-mail:/listmail?mailbox_name=mbox"
|
||||||
|
message="View your saved e-mail messages"
|
||||||
|
label="View saved e-mail messages">
|
||||||
|
<HTML>
|
||||||
|
<head>
|
||||||
|
<title>${(mailbox_name === "Inbox") ? ' Mail list for ' + username : mailbox_name}
|
||||||
|
</title>
|
||||||
|
</head>
|
||||||
|
<body bgcolor="#171726" text="#82A9D9" link="#BDA73A" vlink="#7A9FCC" fontsize="medium" vspace=0 hspace=0>
|
||||||
|
<sidebar width=109>
|
||||||
|
<table cellspacing=0 cellpadding=0>
|
||||||
|
<tr>
|
||||||
|
<td width=104 height=420 bgcolor=#262E3D valign=top>
|
||||||
|
<table cellspacing=0 cellpadding=0>
|
||||||
|
<tr>
|
||||||
|
<td height=7 colspan=3>
|
||||||
|
<spacer type=vertical size=7>
|
||||||
|
<tr>
|
||||||
|
<td width=7>
|
||||||
|
<spacer type=horizontal size=7>
|
||||||
|
<td width=87 href="wtv-home:/home">
|
||||||
|
<img src="${minisrv_config.config.service_logo}" width=87 height=67>
|
||||||
|
<td width=10>
|
||||||
|
<spacer type=horizontal size=10>
|
||||||
|
</table>
|
||||||
|
<spacer type=vertical size=6>
|
||||||
|
<table cellspacing=0 cellpadding=0 border=0>
|
||||||
|
<tr>
|
||||||
|
<td bgcolor=#4A525A height=2 width=104 colspan=3>
|
||||||
|
<tr>
|
||||||
|
<td width=10 height=26>
|
||||||
|
<td width=89 valign=middle>
|
||||||
|
<table cellspacing=0 cellpadding=0 href="${notImplementedAlert}"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td height=1>
|
||||||
|
<tr>
|
||||||
|
<td><shadow><font sizerange=medium color=#E6CD4A>Write</font></shadow>
|
||||||
|
</table>
|
||||||
|
<td width=5>
|
||||||
|
<tr>
|
||||||
|
<td bgcolor=#4A525A height=2 width=104 colspan=3>
|
||||||
|
<tr>
|
||||||
|
<td width=10 height=26>
|
||||||
|
<td width=89 valign=middle>
|
||||||
|
<table cellspacing=0 cellpadding=0 href="wtv-mail:/folders"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td height=1>
|
||||||
|
<tr>
|
||||||
|
<td><shadow><font sizerange=medium color=#E6CD4A>Storage</font></shadow>
|
||||||
|
</table>
|
||||||
|
<td width=5>
|
||||||
|
<tr>
|
||||||
|
<td bgcolor=#4A525A height=2 width=104 colspan=3>
|
||||||
|
<tr>
|
||||||
|
<td width=10 height=26>
|
||||||
|
<td width=89 valign=middle>
|
||||||
|
<table cellspacing=0 cellpadding=0 href="${notImplementedAlert}"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td height=1>
|
||||||
|
<tr>
|
||||||
|
<td><shadow><font sizerange=medium color=#E6CD4A>Addresses</font></shadow>
|
||||||
|
</table>
|
||||||
|
<td width=5>
|
||||||
|
<tr>
|
||||||
|
<td bgcolor=#4A525A height=2 width=104 colspan=3>
|
||||||
|
<tr>
|
||||||
|
<td width=10 height=26>
|
||||||
|
<td width=89 valign=middle>
|
||||||
|
<table cellspacing=0 cellpadding=0 href="${notImplementedAlert}"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td height=1>
|
||||||
|
<tr>
|
||||||
|
<td><shadow><font sizerange=medium color=#E6CD4A>Clean up</font></shadow>
|
||||||
|
</table>
|
||||||
|
<td width=5>
|
||||||
|
<tr>
|
||||||
|
<td bgcolor=#4A525A height=2 width=104 colspan=3>
|
||||||
|
</table>
|
||||||
|
<table width=109 cellspacing=0 cellpadding=0>
|
||||||
|
<tr>
|
||||||
|
<p> <p>
|
||||||
|
<br><spacer type=vertical size=2>
|
||||||
|
<tr>
|
||||||
|
<td valign=bottom bgcolor=#262E3D>
|
||||||
|
<table cellspacing=9><tr><td>
|
||||||
|
<font size=-1 color=#E6CD4A>Quick Tip:</font>
|
||||||
|
<spacer type=vertical size=5>
|
||||||
|
<!-- <a href="wtv-guide:/help?topic=Mail&subtopic=KnownTip"> -->
|
||||||
|
<div>
|
||||||
|
<img height=10 width=10 src="wtv-mail:/content/images/sidebardot.gif">
|
||||||
|
<font size=-1 color=#E6CD4A>indicates messages from known senders</div></font></td></tr></table>
|
||||||
|
<!-- </a> -->
|
||||||
|
</table>
|
||||||
|
<td width=5 bgcolor=#5B6C81>
|
||||||
|
</table>
|
||||||
|
</sidebar>
|
||||||
|
<table cellspacing=0 cellpadding=0 border=0>
|
||||||
|
<tr>
|
||||||
|
<td width=451 colspan=2 align=center bgcolor=#5B6C81>
|
||||||
|
<spacer type=vertical size=13>
|
||||||
|
<tr>
|
||||||
|
<td height=8 bgcolor=#171726 colspan=2>
|
||||||
|
<img src="wtv-mail:/content/images/CornerTop.gif" width=8 height=8>
|
||||||
|
<tr>
|
||||||
|
<td bgcolor=#171726 width=451 valign=top>
|
||||||
|
<table cellspacing=0 cellpadding=0 width=451>
|
||||||
|
<tr>
|
||||||
|
<td bgcolor=#171726 width=13>
|
||||||
|
<spacer type=horizontal size=13>
|
||||||
|
<td height=80>
|
||||||
|
<img src="wtv-mail:/content/images/Mail.gif" width=87 height=45>
|
||||||
|
`;
|
||||||
|
var icon_image = null;
|
||||||
|
switch (mailbox_name) {
|
||||||
|
case "Inbox":
|
||||||
|
switch (total_message_count) {
|
||||||
|
case 0:
|
||||||
|
icon_image = "OpenMailbox0.gif";
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
icon_image = "OpenMailbox1.gif";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
icon_image = "OpenMailbox2.gif";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Sent":
|
||||||
|
icon_image = "MailboxSent.gif";
|
||||||
|
break;
|
||||||
|
case "Trash":
|
||||||
|
icon_image = "MailboxDiscard.gif";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
icon_image = "MailboxStorage.gif";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
data += `
|
||||||
|
<img src="wtv-mail:/content/images/${icon_image}" width=74 height=45 >
|
||||||
|
<td width=250 align=left><font sizerange=small>
|
||||||
|
</table>
|
||||||
|
<tr>
|
||||||
|
<td colspan=2>
|
||||||
|
<table cellspacing=0 cellpadding=0 bgcolor=#2C323D>
|
||||||
|
<tr>
|
||||||
|
<td width=451 absheight=25>
|
||||||
|
<table cellspacing=0 cellpadding=0>
|
||||||
|
<tr>
|
||||||
|
<td width=13 absheight=25>
|
||||||
|
<spacer type=horizontal size=13>
|
||||||
|
<td width=269 maxlines=1>
|
||||||
|
<font sizerange=medium color=#D6D6D6><blackface> ${(mailbox_name === "Inbox") ? ' Mail list for ' + username : mailbox_name}
|
||||||
|
</blackface></font>
|
||||||
|
<td width=21>
|
||||||
|
<img src="wtv-mail:/content/images/widget.gif" width=16 height=16>
|
||||||
|
<td width=80 >
|
||||||
|
<spacer type=vertical size=1><br>
|
||||||
|
<a href="wtv-setup:/mail"><font sizerange=small color=#E6CD4A><b>Settings</b></font></a>
|
||||||
|
<td width=21>
|
||||||
|
<!--
|
||||||
|
<img src="wtv-mail:/content/images/widget.gif" width=16 height=16 noprint>
|
||||||
|
<td width=36>
|
||||||
|
<spacer type=vertical size=1><br>
|
||||||
|
<a href="wtv-guide:/help?topic=Mail&subtopic=Index&appName=Mail" ><img src="wtv-mail:/content/images/mail_help_image.gif" width=35 height=17 noprint></a>
|
||||||
|
<td width=13>
|
||||||
|
-->
|
||||||
|
<spacer type=horizontal size=13>
|
||||||
|
</table>
|
||||||
|
</table>
|
||||||
|
</table>
|
||||||
|
<table cellspacing=0 cellpadding=0 border=0>
|
||||||
|
<tr>
|
||||||
|
<td bgcolor=#171726 width=13>
|
||||||
|
<spacer type=horizontal size=13>
|
||||||
|
<td bgcolor=#171726 width="438" valign="top">
|
||||||
|
<spacer type=vertical size=13><br>`;
|
||||||
|
if (message_list) {
|
||||||
|
|
||||||
|
data += `
|
||||||
|
<font sizerange=medium> ${total_message_count} e-mail message${(total_message_count != 1) ? 's' : ''} for
|
||||||
|
<table cellspacing=0 cellpadding=0 border=0>
|
||||||
|
<TR><TD maxlines="1">
|
||||||
|
${username}@${minisrv_config.config.service_name}
|
||||||
|
</TD></TR>
|
||||||
|
</TABLE>
|
||||||
|
</font><br>
|
||||||
|
<spacer type=vertical size=6>
|
||||||
|
<hr width=422 align=left>
|
||||||
|
<spacer type=vertical size=5>
|
||||||
|
<table cellspacing=0 cellpadding=0>
|
||||||
|
<tr>
|
||||||
|
<td width=155>
|
||||||
|
<font sizerange=small color=#8897A6><b><spacer type=horizontal size=20>From</b></font>
|
||||||
|
<td width=230><font sizerange=small color=#8897A6><b>Subject</b></font>
|
||||||
|
<td width=47><font sizerange=small color=#8897A6><b>Date</b></font>
|
||||||
|
</table>
|
||||||
|
<spacer type=vertical size=1>
|
||||||
|
<hr width=422 align=left>
|
||||||
|
`;
|
||||||
|
Object.keys(message_list).forEach(function (k) {
|
||||||
|
var message = message_list[k];
|
||||||
|
console.log(message);
|
||||||
|
data += `<spacer type=vertical size=5>
|
||||||
|
<table cellspacing=0 cellpadding=0 border=0>
|
||||||
|
<tr>
|
||||||
|
<td href="readmail?message_id=${message.id}#next" id="id${message.id}" selected>
|
||||||
|
<table cellspacing=0 cellpadding=0>
|
||||||
|
<tr>
|
||||||
|
<td abswidth=20 align=center valign=middle>${(message.known_sender) ? '<img height=10 width=10 src="wtv-mail:/content/images/dot.gif">' : ''}
|
||||||
|
<td abswidth=130 maxlines=1>
|
||||||
|
<font color=#7A9FCC>
|
||||||
|
${(message.from_name) ? message.from_name : message.from_addr}
|
||||||
|
</font>
|
||||||
|
<td abswidth=5>
|
||||||
|
<td abswidth=225 maxlines=1>
|
||||||
|
<font color=#7A9FCC>
|
||||||
|
${(message.subject) ? message.subject : "(No Subject)"}
|
||||||
|
</font>
|
||||||
|
<td abswidth=5>
|
||||||
|
<td abswidth=47 maxlines=1>
|
||||||
|
<font color=#7A9FCC>
|
||||||
|
`;
|
||||||
|
var message_date = new Date(message.date * 1000);
|
||||||
|
data += (message_date.getMonth() + 1) + "/" + message_date.getDate() + "\n";
|
||||||
|
data += `
|
||||||
|
</font>
|
||||||
|
</table>
|
||||||
|
<tr>
|
||||||
|
<td height=5>`;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
data += `
|
||||||
|
<font sizerange=medium> No ${(mailbox_name == "Inbox") ? `new e-mail messages for<table cellspacing=0 cellpadding=0 border=0>
|
||||||
|
<TR><TD maxlines="1">
|
||||||
|
${username}@${minisrv_config.config.service_name}
|
||||||
|
</TD></TR>
|
||||||
|
</TABLE>` : 'e-mail messages in mailbox '+mailbox_name}
|
||||||
|
</font><br>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
data += `
|
||||||
|
<spacer type=vertical size=6>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</HTML>
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mail_end_error("Access Denied");
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ class WTVClientSessionData {
|
|||||||
ssid = null;
|
ssid = null;
|
||||||
data_store = null;
|
data_store = null;
|
||||||
session_store = null;
|
session_store = null;
|
||||||
|
mailstore = null;
|
||||||
login_security = null;
|
login_security = null;
|
||||||
capabilities = null;
|
capabilities = null;
|
||||||
session_storage = "";
|
session_storage = "";
|
||||||
@@ -23,6 +24,7 @@ class WTVClientSessionData {
|
|||||||
if (!minisrv_config) throw ("minisrv_config required");
|
if (!minisrv_config) throw ("minisrv_config required");
|
||||||
var WTVShared = require('./WTVShared.js')['WTVShared'];
|
var WTVShared = require('./WTVShared.js')['WTVShared'];
|
||||||
var WTVMime = require('./WTVMime.js');
|
var WTVMime = require('./WTVMime.js');
|
||||||
|
var WTVMail = require('./WTVMail.js');
|
||||||
this.minisrv_config = minisrv_config;
|
this.minisrv_config = minisrv_config;
|
||||||
this.wtvshared = new WTVShared(minisrv_config);
|
this.wtvshared = new WTVShared(minisrv_config);
|
||||||
this.wtvmime = new WTVMime(minisrv_config);
|
this.wtvmime = new WTVMime(minisrv_config);
|
||||||
@@ -39,6 +41,7 @@ class WTVClientSessionData {
|
|||||||
"wtv-log:/log"
|
"wtv-log:/log"
|
||||||
];
|
];
|
||||||
this.lockdownWhitelist.push(minisrv_config.config.unauthorized_url);
|
this.lockdownWhitelist.push(minisrv_config.config.unauthorized_url);
|
||||||
|
this.mailstore = new WTVMail(minisrv_config, ssid, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,6 +64,8 @@ class WTVClientSessionData {
|
|||||||
storeUserStoreFile(path, data, last_modified = null, overwrite = true) {
|
storeUserStoreFile(path, data, last_modified = null, overwrite = true) {
|
||||||
var store_dir = this.getUserStoreDirectory();
|
var store_dir = this.getUserStoreDirectory();
|
||||||
if (!store_dir) return false; // unregistered
|
if (!store_dir) return false; // unregistered
|
||||||
|
// FileStore
|
||||||
|
store_dir += "FileStore" + this.path.sep;
|
||||||
var result = false;
|
var result = false;
|
||||||
var path_split = path.split('/');
|
var path_split = path.split('/');
|
||||||
var file_name = path_split.pop();
|
var file_name = path_split.pop();
|
||||||
@@ -89,6 +94,8 @@ class WTVClientSessionData {
|
|||||||
getUserStoreFile(path) {
|
getUserStoreFile(path) {
|
||||||
var store_dir = this.getUserStoreDirectory();
|
var store_dir = this.getUserStoreDirectory();
|
||||||
if (!store_dir) return false; // unregistered
|
if (!store_dir) return false; // unregistered
|
||||||
|
// FileStore
|
||||||
|
store_dir += "FileStore" + this.path.sep;
|
||||||
var store_dir_path = this.wtvshared.makeSafePath(store_dir, path.replace('/', this.path.sep));
|
var store_dir_path = this.wtvshared.makeSafePath(store_dir, path.replace('/', this.path.sep));
|
||||||
if (this.fs.existsSync(store_dir_path)) return this.fs.readFileSync(store_dir_path);
|
if (this.fs.existsSync(store_dir_path)) return this.fs.readFileSync(store_dir_path);
|
||||||
else return false;
|
else return false;
|
||||||
@@ -559,6 +566,10 @@ class WTVClientSessionData {
|
|||||||
return this.clientAddress;
|
return this.clientAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setMailstore(mailstore) {
|
||||||
|
this.mailstore = mailstore;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = WTVClientSessionData;
|
module.exports = WTVClientSessionData;
|
||||||
217
zefie_wtvp_minisrv/WTVMail.js
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
class WTVMail {
|
||||||
|
|
||||||
|
fs = require('fs');
|
||||||
|
path = require('path');
|
||||||
|
uuid = require('uuid');
|
||||||
|
|
||||||
|
ssid = null;
|
||||||
|
unread_mail = 0;
|
||||||
|
inbox_store = null;
|
||||||
|
sent_store = null;
|
||||||
|
saved_store = null;
|
||||||
|
minisrv_config = [];
|
||||||
|
wtvshared = null;
|
||||||
|
wtvmime = null;
|
||||||
|
wtvclient = null;
|
||||||
|
mailstore_dir = null;
|
||||||
|
is_guest = null;
|
||||||
|
mailboxes = null;
|
||||||
|
|
||||||
|
constructor(minisrv_config, ssid, WTVClientSessionData) {
|
||||||
|
if (!minisrv_config) throw ("minisrv_config required");
|
||||||
|
var WTVShared = require('./WTVShared.js')['WTVShared'];
|
||||||
|
var WTVMime = require('./WTVMime.js');
|
||||||
|
this.minisrv_config = minisrv_config;
|
||||||
|
this.wtvshared = new WTVShared(minisrv_config);
|
||||||
|
this.wtvmime = new WTVMime(minisrv_config);
|
||||||
|
this.wtvclient = WTVClientSessionData;
|
||||||
|
this.is_guest = !this.wtvclient.isRegistered();
|
||||||
|
this.ssid = ssid;
|
||||||
|
this.unread_mail = this.wtvclient.getSessionData("subscriber_unread_mail") ? this.wtvclient.getSessionData("subscriber_unread_mail") : 0;
|
||||||
|
this.mailboxes = [
|
||||||
|
// referenced by id, so order is important!
|
||||||
|
"Inbox",
|
||||||
|
"Sent",
|
||||||
|
"Saved",
|
||||||
|
"Trash"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
mailstoreExists() {
|
||||||
|
if (!this.isguest) {
|
||||||
|
if (this.mailstore_dir === null) {
|
||||||
|
// set mailstore directory local var so we don't call the function every time
|
||||||
|
var userstore_dir = this.wtvclient.getUserStoreDirectory();
|
||||||
|
|
||||||
|
// MailStore
|
||||||
|
var store_dir = "MailStore" + this.path.sep;
|
||||||
|
this.mailstore_dir = userstore_dir + store_dir;
|
||||||
|
}
|
||||||
|
return this.fs.existsSync(this.mailstore_dir);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
mailboxExists(mailboxid) {
|
||||||
|
if (mailboxid > this.mailboxes.length) return null;
|
||||||
|
var mailbox_dir = null;
|
||||||
|
if (this.mailstoreExists()) {
|
||||||
|
var mailbox_name = this.getMailboxById(mailboxid);
|
||||||
|
if (!mailbox_name) return null;
|
||||||
|
|
||||||
|
var mailbox_dir = mailbox_name + this.path.sep;
|
||||||
|
var store_dir = this.mailstore_dir + mailbox_dir;
|
||||||
|
}
|
||||||
|
return (store_dir !== null) ? this.fs.existsSync(store_dir) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
createMailstore() {
|
||||||
|
if (this.mailstoreExists() === false) {
|
||||||
|
if (!this.fs.existsSync(this.mailstore_dir)) this.fs.mkdirSync(this.mailstore_dir, { recursive: true });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
getMailboxById(mailboxid) {
|
||||||
|
return (mailboxid < this.mailboxes.length - 1) ? this.mailboxes[mailboxid] : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
getMailboxStoreDir(mailboxid) {
|
||||||
|
if (this.mailboxExists(mailboxid)) {
|
||||||
|
var mailbox_name = this.getMailboxById(mailboxid);
|
||||||
|
return this.mailstore_dir + mailbox_name + this.path.sep;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
createMailbox(mailboxid) {
|
||||||
|
var mailbox_exists = this.mailboxExists(mailboxid);
|
||||||
|
if (mailbox_exists === false) {
|
||||||
|
var mailbox_name = this.getMailboxById(mailboxid);
|
||||||
|
var mailbox_dir = mailbox_name + this.path.sep;
|
||||||
|
var store_dir = this.mailstore_dir + mailbox_dir;
|
||||||
|
if (!this.fs.existsSync(store_dir)) this.fs.mkdirSync(store_dir, { recursive: true });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return mailbox_exists;
|
||||||
|
}
|
||||||
|
|
||||||
|
createMessageID() {
|
||||||
|
return this.uuid.v1();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
createMessage(mailboxid, from_addr, to_addr, msgbody, subject = null, from_name = null, to_name = null, date = null, known_sender = false) {
|
||||||
|
if (this.createMailbox(mailboxid)) {
|
||||||
|
if (!date) date = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
|
var mailbox_path = this.getMailboxStoreDir(mailboxid);
|
||||||
|
var message_file = this.createMessageID() + ".zmsg";
|
||||||
|
var message_file_out = mailbox_path + message_file;
|
||||||
|
var message_data = {
|
||||||
|
"from_addr": from_addr,
|
||||||
|
"from_name": from_name,
|
||||||
|
"to_addr": to_addr,
|
||||||
|
"to_name": to_name,
|
||||||
|
"date": date,
|
||||||
|
"subject": subject,
|
||||||
|
"body": msgbody,
|
||||||
|
"known_sender": known_sender
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (this.fs.existsSync(message_file_out)) {
|
||||||
|
console.log(" * ERROR: Message with this UUID already exists (should never happen). Message lost.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// encode message into json
|
||||||
|
var result = this.fs.writeFileSync(message_file_out, JSON.stringify(message_data));
|
||||||
|
if (!result) return false;
|
||||||
|
|
||||||
|
// rely on filesystem times for sorting as it is quicker then reading every file
|
||||||
|
var file_timestamp = new Date(date * 1000);
|
||||||
|
fs.utimesSync(message_file, Date.now(), file_timestamp);
|
||||||
|
if (!result) console.error(" WARNING: Setting timestamp on " + message_file + " failed, mail dates will be inaccurate.");
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error(" # MailErr: Mail Store failed\n", e, "\n", message_file_out, "\n", message_data ,"\n");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createWelcomeMessage() {
|
||||||
|
var from_addr = (this.minisrv_config.config.service_owner_account) ? this.minisrv_config.config.service_owner_account : this.minisrv_config.config.service_owner;
|
||||||
|
from_addr += "@" + this.minisrv_config.config.service_name;
|
||||||
|
var from_name = this.minisrv_config.config.service_owner
|
||||||
|
var to_addr = this.wtvclient.getSessionData("subscriber_username") + "@" + this.minisrv_config.config.service_name;
|
||||||
|
var to_name = this.wtvclient.getSessionData("subscriber_name");
|
||||||
|
var subj = "Welcome to " + this.minisrv_config.config.service_name;
|
||||||
|
var msg = "poop";
|
||||||
|
return this.createMessage(0, from_addr, to_addr, msg, subj, from_name, to_name, null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
getMessage(mailboxid, messageid) {
|
||||||
|
if (this.createMailbox(mailboxid)) {
|
||||||
|
var mailbox_path = this.getMailboxStoreDir(mailboxid);
|
||||||
|
var message_file = messageid + ".zmsg";
|
||||||
|
var message_file_in = mailbox_path + this.path.sep + message_file;
|
||||||
|
var message_data_raw = null;
|
||||||
|
|
||||||
|
if (this.fs.existsSync(message_file_in)) message_data_raw = this.fs.readFileSync(message_file_in);
|
||||||
|
else console.error(" # MailErr: could not find ", message_file_in);
|
||||||
|
|
||||||
|
if (message_data_raw) {
|
||||||
|
var message_data = JSON.parse(message_data_raw);
|
||||||
|
if (message_data) {
|
||||||
|
message_data.id = messageid;
|
||||||
|
return message_data;
|
||||||
|
}
|
||||||
|
else console.error(" # MailErr: could not parse json in ", message_file_in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
listMessages(mailboxid, limit, reverse_sort = false, offset = 0) {
|
||||||
|
if (this.createMailbox(mailboxid)) {
|
||||||
|
var mailbox_path = this.getMailboxStoreDir(mailboxid);
|
||||||
|
var self = this;
|
||||||
|
var files = this.fs.readdirSync(mailbox_path)
|
||||||
|
.map(function (v) {
|
||||||
|
return {
|
||||||
|
name: v,
|
||||||
|
time: self.fs.statSync(mailbox_path + self.path.sep + v).mtime.getTime()
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.sort(function (a, b) {
|
||||||
|
if (reverse_sort) return b.time - a.time;
|
||||||
|
else return a.time - b.time;
|
||||||
|
})
|
||||||
|
.map(function (v) {
|
||||||
|
if (v.name.substring((v.name.length - 5)) === ".zmsg") return v.name.substring(0, (v.name.length - 5));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (files.length == 0) return false; // no messages
|
||||||
|
else {
|
||||||
|
// todo filter previous results when offset
|
||||||
|
var messagelist_out = new Array();
|
||||||
|
Object.keys(files).forEach(function (k) {
|
||||||
|
var message = self.getMessage(mailboxid, files[k]);
|
||||||
|
if (message) messagelist_out.push(mailboxid, message);
|
||||||
|
else console.error(" # MailErr: reading message ID: ", files[k]);
|
||||||
|
})
|
||||||
|
return messagelist_out.filter(function (n) { return n; });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null; // error
|
||||||
|
}
|
||||||
|
|
||||||
|
countMessages(mailboxid) {
|
||||||
|
var messages = this.listMessages(mailboxid, false);
|
||||||
|
return (messages.length) ? messages.length : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = WTVMail;
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
],
|
],
|
||||||
"SessionStore": "SessionStore",
|
"SessionStore": "SessionStore",
|
||||||
"service_owner": "a minisrv user",
|
"service_owner": "a minisrv user",
|
||||||
|
"service_owner_account": "minisrvuser",
|
||||||
"service_name": "WebTV",
|
"service_name": "WebTV",
|
||||||
"service_logo": "WebTVLogoJewel.gif",
|
"service_logo": "WebTVLogoJewel.gif",
|
||||||
"service_splash_logo": "file://ROM/images/SplashLogo1.gif",
|
"service_splash_logo": "file://ROM/images/SplashLogo1.gif",
|
||||||
@@ -71,6 +72,10 @@
|
|||||||
"flags": "0x00000010",
|
"flags": "0x00000010",
|
||||||
"connections": 3
|
"connections": 3
|
||||||
},
|
},
|
||||||
|
"wtv-music": {
|
||||||
|
"port": 1656,
|
||||||
|
"connections": 3
|
||||||
|
},
|
||||||
"wtv-cookie": {
|
"wtv-cookie": {
|
||||||
"port": 1619,
|
"port": 1619,
|
||||||
"connections": 1
|
"connections": 1
|
||||||
@@ -83,6 +88,10 @@
|
|||||||
"port": 1635,
|
"port": 1635,
|
||||||
"connections": 3
|
"connections": 3
|
||||||
},
|
},
|
||||||
|
"wtv-mail": {
|
||||||
|
"port": 1608,
|
||||||
|
"connections": 3
|
||||||
|
},
|
||||||
"http": {
|
"http": {
|
||||||
"port": 1650,
|
"port": 1650,
|
||||||
"connections": 3,
|
"connections": 3,
|
||||||
@@ -101,8 +110,8 @@
|
|||||||
"external_proxy_port": 1080,
|
"external_proxy_port": 1080,
|
||||||
"flags": "0x00000001"
|
"flags": "0x00000001"
|
||||||
},
|
},
|
||||||
"wtv-music": {
|
"wtv-guide": {
|
||||||
"port": 1656,
|
"port": 1621,
|
||||||
"connections": 3
|
"connections": 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
zefie_wtvp_minisrv/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "zefie_wtvp_minisrv",
|
"name": "zefie_wtvp_minisrv",
|
||||||
"version": "0.9.22",
|
"version": "0.9.24",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "zefie_wtvp_minisrv",
|
"name": "zefie_wtvp_minisrv",
|
||||||
"version": "0.9.22",
|
"version": "0.9.24",
|
||||||
"license": "GPL3",
|
"license": "GPL3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
@@ -14,7 +14,8 @@
|
|||||||
"endianness": "^8.0.2",
|
"endianness": "^8.0.2",
|
||||||
"mime-types": "^2.1.33",
|
"mime-types": "^2.1.33",
|
||||||
"proxy-agent": "^5.0.0",
|
"proxy-agent": "^5.0.0",
|
||||||
"strftime": "^0.10.0"
|
"strftime": "^0.10.0",
|
||||||
|
"uuid": "^8.3.2"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "patreon",
|
"type": "patreon",
|
||||||
@@ -586,6 +587,14 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "8.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vm2": {
|
"node_modules/vm2": {
|
||||||
"version": "3.9.5",
|
"version": "3.9.5",
|
||||||
"resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.5.tgz",
|
"resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.5.tgz",
|
||||||
@@ -1045,6 +1054,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||||
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
||||||
},
|
},
|
||||||
|
"uuid": {
|
||||||
|
"version": "8.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
||||||
|
},
|
||||||
"vm2": {
|
"vm2": {
|
||||||
"version": "3.9.5",
|
"version": "3.9.5",
|
||||||
"resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.5.tgz",
|
"resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.5.tgz",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"endianness": "^8.0.2",
|
"endianness": "^8.0.2",
|
||||||
"mime-types": "^2.1.33",
|
"mime-types": "^2.1.33",
|
||||||
"proxy-agent": "^5.0.0",
|
"proxy-agent": "^5.0.0",
|
||||||
"strftime": "^0.10.0"
|
"strftime": "^0.10.0",
|
||||||
|
"uuid": "^8.3.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,10 @@
|
|||||||
<Content Include="ServiceVault\wtv-head-waiter\relogin.js">
|
<Content Include="ServiceVault\wtv-head-waiter\relogin.js">
|
||||||
<SubType>Code</SubType>
|
<SubType>Code</SubType>
|
||||||
</Content>
|
</Content>
|
||||||
|
<Content Include="ServiceVault\wtv-mail\DiplomaMail.js" />
|
||||||
|
<Content Include="ServiceVault\wtv-mail\listmail.js">
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Content>
|
||||||
<Content Include="ServiceVault\wtv-music\demo\hacktv4.gif" />
|
<Content Include="ServiceVault\wtv-music\demo\hacktv4.gif" />
|
||||||
<Content Include="ServiceVault\wtv-music\demo\index.html" />
|
<Content Include="ServiceVault\wtv-music\demo\index.html" />
|
||||||
<Content Include="ServiceVault\wtv-music\demo\midi\acey.mid" />
|
<Content Include="ServiceVault\wtv-music\demo\midi\acey.mid" />
|
||||||
@@ -325,6 +329,9 @@
|
|||||||
<Content Include="WTVLzpf.js">
|
<Content Include="WTVLzpf.js">
|
||||||
<SubType>Code</SubType>
|
<SubType>Code</SubType>
|
||||||
</Content>
|
</Content>
|
||||||
|
<Content Include="WTVMail.js">
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Content>
|
||||||
<Content Include="WTVMime.js">
|
<Content Include="WTVMime.js">
|
||||||
<SubType>Code</SubType>
|
<SubType>Code</SubType>
|
||||||
</Content>
|
</Content>
|
||||||
@@ -345,6 +352,8 @@
|
|||||||
<Folder Include="ServiceDeps\premade_tellyscripts\LC2\" />
|
<Folder Include="ServiceDeps\premade_tellyscripts\LC2\" />
|
||||||
<Folder Include="ServiceVault\" />
|
<Folder Include="ServiceVault\" />
|
||||||
<Folder Include="ServiceVault\http_pc\" />
|
<Folder Include="ServiceVault\http_pc\" />
|
||||||
|
<Folder Include="ServiceVault\wtv-guide\" />
|
||||||
|
<Folder Include="ServiceVault\wtv-mail\" />
|
||||||
<Folder Include="ServiceVault\wtv-chat\" />
|
<Folder Include="ServiceVault\wtv-chat\" />
|
||||||
<Folder Include="ServiceVault\wtv-chat\images\" />
|
<Folder Include="ServiceVault\wtv-chat\images\" />
|
||||||
<Folder Include="ServiceVault\wtv-cookie\" />
|
<Folder Include="ServiceVault\wtv-cookie\" />
|
||||||
|
|||||||