This commit is contained in:
2018-06-28 23:37:38 +00:00
commit 4518298aaf
152 changed files with 24114 additions and 0 deletions

88
util/CustomerUtils.cs Normal file
View File

@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.EntityFrameworkCore;
using rockfishCore.Models;
using System.IO;
namespace rockfishCore.Util
{
public static class CustomerUtils
{
/// <summary>
/// Adds email if not already present, handles trimming and check for duplicates etc
/// **DOES NOT CALL SAVE ON CONTEXT, CALLER IS RESPONSIBLE**
/// </summary>
/// <param name="cust"></param>
/// <param name="newEmail"></param>
public static void AddAdminEmailIfNotPresent(Customer cust, string newEmail)
{
AddEmailIfNotPresent(cust, newEmail, true);
}
/// <summary>
/// Adds email if not already present, handles trimming and check for duplicates etc
/// **DOES NOT CALL SAVE ON CONTEXT, CALLER IS RESPONSIBLE**
/// </summary>
/// <param name="cust"></param>
/// <param name="newEmail"></param>
public static void AddSupportEmailIfNotPresent(Customer cust, string newEmail)
{
AddEmailIfNotPresent(cust, newEmail, false);
}
/// <summary>
/// Adds email if not already present, handles trimming and check for duplicates etc
/// **DOES NOT CALL SAVE ON CONTEXT, CALLER IS RESPONSIBLE**
/// </summary>
/// <param name="cust"></param>
/// <param name="newEmail"></param>
/// <param name="isAdmin"></param>
private static void AddEmailIfNotPresent(Customer cust, string newEmail, bool isAdmin)
{
newEmail = newEmail.Trim();
string newEmailAsLower = newEmail.ToLowerInvariant();
string compareTo = cust.AdminEmail;
if (false == isAdmin)
compareTo = cust.SupportEmail;
bool noPriorAddress = false;
if (!string.IsNullOrWhiteSpace(compareTo))
compareTo = compareTo.ToLowerInvariant();
else
noPriorAddress = true;
//See if email is already present
if (false == noPriorAddress && compareTo.Contains(newEmailAsLower))
{
return;//skip this one, it's already there
}
//It's not in the field already so add it
if (noPriorAddress)
{
if (isAdmin)
cust.AdminEmail = newEmail;
else
cust.SupportEmail = newEmail;
}
else
{
if (isAdmin)
cust.AdminEmail = cust.AdminEmail + ", " + newEmail;
else
cust.SupportEmail = cust.SupportEmail + ", " + newEmail;
}
}
}//eoc
}//eons

104
util/DateUtil.cs Normal file
View File

@@ -0,0 +1,104 @@
using System;
using System.Text;
namespace rockfishCore.Util
{
public static class DateUtil
{
public const string DATE_TIME_FORMAT = "MMMM dd, yyyy h:mm tt";
public const string DATE_ONLY_FORMAT = "D";
//Unix epoch converters
public static string EpochToString(long? uepoch, string formatString = null)
{
if (uepoch == null) return string.Empty;
if (formatString == null)
formatString = DATE_ONLY_FORMAT;
return DateTimeOffset.FromUnixTimeSeconds(uepoch.Value).DateTime.ToString(formatString);
}
public static DateTime EpochToDate(long? uepoch)
{
DateTime dt = DateTime.Now;
if (uepoch == null) return DateTime.MinValue;
return DateTimeOffset.FromUnixTimeSeconds(uepoch.Value).DateTime;
}
public static long DateToEpoch(DateTime dt)
{
DateTimeOffset dto = new DateTimeOffset(dt);
return dto.ToUnixTimeSeconds();
}
public static long? DateTimeOffSetNullableToEpoch(DateTimeOffset? dt)
{
if (dt == null)
{
return null;
}
DateTimeOffset dto = dt.Value;
return dto.ToUnixTimeSeconds();
}
public static string ISO8601StringToLocalDateTime(string s)
{
if (!string.IsNullOrWhiteSpace(s))
{
return DateTimeOffset.Parse(s).DateTime.ToLocalTime().ToString(DATE_TIME_FORMAT);
}
return string.Empty;
}
public static long? ISO8601StringToEpoch(string s)
{
DateTimeOffset? dto = ISO8601StringToDateTimeOffset(s);
if (dto == null)
return null;
return ((DateTimeOffset)dto).ToUnixTimeSeconds();
}
///////////////////////
//This method correctly interprets iso8601 strings to a datetimeoffset
public static DateTimeOffset? ISO8601StringToDateTimeOffset(string s)
{
if (!string.IsNullOrWhiteSpace(s))
{
DateTimeOffset dto = DateTimeOffset.ParseExact(s, new string[] { "yyyy-MM-dd'T'HH:mm:ss.FFFK" }, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None);
return dto;
}
return null;
}
public static long NowAsEpoch()
{
DateTimeOffset dto = new DateTimeOffset(DateTime.Now);
return dto.ToUnixTimeSeconds();
}
/// <summary>
/// An internally consistent empty or not relevant date marker:
/// January 1st 5555
/// Used for RAVEN key generation
/// </summary>
/// <returns></returns>
public static DateTime EmptyDateValue
{
get
{
return new DateTime(5555, 1, 1);
//Was going to use MaxValue but apparently that varies depending on culture
// and Postgres has issues with year 1 as it interprets as year 2001
// so to be on safe side just defining one for all usage
}
}
//eoc
}
//eons
}

591
util/FBImporter.cs Normal file
View File

@@ -0,0 +1,591 @@
using System;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using rockfishCore.Models;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
//using System.Xml;
using System.Xml.Linq;
using System.IO;
namespace rockfishCore.Util
{
//Import fogbugz stuff into our own db
//called by schema update 7
//does not modify FB at all
//can be called any time, won't re-import
public static class FBImporter
{
public const long LAST_EXISTING_CASE_IN_FOGBUGZ = 3315;
public static void Import(rockfishContext ct)
{
//fogbugz api token, can get it by using the FB UI, open dev tools, be logged in, do something and watch the token get sent in the network traffic
string sToken = "jvmnpkrvc1i7hc6kn6dggqkibktgcc";
long nCase = 0;
bool bDone = false;
while (!bDone)
{
//increment until we reach the end (case 3309 as of now)
nCase++;
if (nCase > LAST_EXISTING_CASE_IN_FOGBUGZ)
{
bDone = true;
break;
}
//no need for this, we wipe clean before every import now
// //do we already have this record?
// if (ct.RfCase.Any(e => e.Id == nCase))
// {
// continue;
// }
fbCase fb = getCase(nCase, sToken);
//copy over to db
portCase(fb, ct);
}
}
private static Dictionary<string, long> dictProjects = new Dictionary<string, long>();
///////////////////////////////////////////////
//Port a single case over to Rockfish
//
private static void portCase(fbCase f, rockfishContext ct)
{
long lProjectId = 0;
if (f.noRecord)
{
//save as *DELETED* record
//Only setting required fields
RfCase c = new RfCase();
c.Priority = 5;
c.Title = "deleted from FogBugz";
c.RfCaseProjectId = 1;//has to be something
ct.RfCase.Add(c);
ct.SaveChanges();
}
else
{
if (dictProjects.ContainsKey(f.project))
{
lProjectId = dictProjects[f.project];
}
else
{
//need to insert it
RfCaseProject t = new RfCaseProject();
t.Name = f.project;
ct.RfCaseProject.Add(t);
ct.SaveChanges();
dictProjects.Add(f.project, t.Id);
lProjectId = t.Id;
}
RfCase c = new RfCase();
c.DtClosed = f.closed;
c.DtCreated = f.created;
c.Notes = f.notes;
c.Priority = f.priority;
if (c.Priority > 5)
c.Priority = 5;
c.Title = f.title;
c.RfCaseProjectId = lProjectId;
ct.RfCase.Add(c);
ct.SaveChanges();
if (f.attachments.Count > 0)
{
foreach (fbAttachment att in f.attachments)
{
RfCaseBlob blob = new RfCaseBlob();
blob.File = att.blob;
blob.Name = att.fileName;
blob.RfCaseId = c.Id;
ct.RfCaseBlob.Add(blob);
ct.SaveChanges();
}
}
}
// Console.WriteLine("***************************************************************************");
Console.WriteLine("~~~~~~~~~~~~~~~~~~~~ PORTCASE: " + f.id);
}
///////////////////////////////////////////////
//fetch a single case
//
private static fbCase getCase(long caseNumber, string sToken)
{
//Use this url
//https://fog.ayanova.com/api.asp?cmd=search&cols=ixBug,ixBugParent,sTitle,sProject,ixPriority, sPriority,fOpen,ixStatus,sStatus,dtOpened,dtResolved,dtClosed,ixRelatedBugs,events&token=jvmnpkrvc1i7hc6kn6dggqkibktgcc&q=3308
//TEST TEST TEST
// caseNumber = 3279;
string url = "https://fog.ayanova.com/api.asp?cmd=search&cols=ixBug,ixBugParent,sTitle,sProject,ixPriority,sPriority,fOpen,ixStatus,sStatus,dtOpened,dtResolved,dtClosed,ixRelatedBugs,events&token=" + sToken + "&q=" + caseNumber.ToString();
var httpClient = new HttpClient();
var result = httpClient.GetAsync(url).Result;
var stream = result.Content.ReadAsStreamAsync().Result;
var x = XElement.Load(stream);
fbCase f = new fbCase();
//are we done?
string scount = (from el in x.DescendantsAndSelf("cases") select el).First().Attribute("count").Value;
if (scount == "0")
{
f.noRecord = true;
return f;
}
//Got record, process it...
f.title = getValue("sTitle", x);
f.project = getValue("sProject", x);
f.id = getValue("ixBug", x);
f.priority = int.Parse(getValue("ixPriority", x));
f.created = Util.DateUtil.ISO8601StringToEpoch(getValue("dtOpened", x));
f.closed = Util.DateUtil.ISO8601StringToEpoch(getValue("dtClosed", x));
//string DTB4 = getValue("dtOpened", x);
//string DTAFTER = Util.DateUtil.ISO8601StringToLocalDateTime(DTB4);
//NOTES / ATTACHMENTS
StringBuilder sbNotes = new StringBuilder();
//events
var events = (from el in x.Descendants("event") select el).ToList();
bool bFirstNoteAdded = false;
foreach (var e in events)
{
string sNote = getValue("s", e);
if (!string.IsNullOrWhiteSpace(sNote))
{
if (bFirstNoteAdded)
{
sbNotes.AppendLine("====================");
}
bFirstNoteAdded = true;
sbNotes.Append(Util.DateUtil.ISO8601StringToLocalDateTime(getValue("dt", e)));
sbNotes.Append(" ");
sbNotes.AppendLine(getValue("evtDescription", e));
sbNotes.AppendLine("____________________");
sbNotes.AppendLine(sNote);
}
//GET ATTACHMENTS
var attaches = (from l in e.Descendants("attachment") select l).ToList();
foreach (var a in attaches)
{
fbAttachment fbat = new fbAttachment();
fbat.fileName = getValue("sFileName", a);
string fileUrl = "https://fog.ayanova.com/" + System.Net.WebUtility.HtmlDecode(getValue("sURL", a)) + "&token=" + sToken;
/////
using (var aclient = new HttpClient())
{
var aresponse = aclient.GetAsync(fileUrl).Result;
if (aresponse.IsSuccessStatusCode)
{
// by calling .Result you are performing a synchronous call
var responseContent = aresponse.Content;
// by calling .Result you are synchronously reading the result
var astream = responseContent.ReadAsStreamAsync().Result;
MemoryStream ams = new MemoryStream();
astream.CopyTo(ams);
fbat.blob = ams.ToArray();
}
}
/////
//bugbug: Looks like this is always the same size, maybe it's getting an error page?
// result = httpClient.GetAsync(url).Result;
// stream = result.Content.ReadAsStreamAsync().Result;
// MemoryStream ms = new MemoryStream();
// stream.CopyTo(ms);
// fbat.blob = ms.ToArray();
f.attachments.Add(fbat);
}
}//bottom of events loop
if (sbNotes.Length > 0)
{
f.notes = sbNotes.ToString();
}
return f;
}
/////////////////////////////////
// Get header element value
//
private static string getValue(string sItem, XElement x)
{
return (string)(from el in x.Descendants(sItem) select el).First();
}
/////////////////////////////////////
//dto object
public class fbCase
{
public fbCase()
{
attachments = new List<fbAttachment>();
}
public bool noRecord;
public string id;
public string title;
public string project;
public int priority;
public string notes;
public long? created;
public long? closed;
public List<fbAttachment> attachments;
}
public class fbAttachment
{
public string fileName;
public byte[] blob;
}
//eoc
}
//eons
}
/*
<?xml version="1.0" encoding="UTF-8"?>
<response>
<cases count="1">
<case ixBug="3308" operations="edit,assign,resolve,remind">
<ixBug>3308</ixBug>
<ixBugParent>0</ixBugParent>
<sTitle><![CDATA[Port all Fogbugz functionality we use to Rockfish and decommission FogBugz]]></sTitle>
<sProject><![CDATA[RockFish]]></sProject>
<ixPriority>1</ixPriority>
<fOpen>true</fOpen>
<ixStatus>20</ixStatus>
<sStatus><![CDATA[Active]]></sStatus>
<dtOpened>2017-08-06T19:11:54Z</dtOpened>
<dtResolved></dtResolved>
<dtClosed></dtClosed>
<ixRelatedBugs>3240</ixRelatedBugs>
<events>
<event ixBugEvent="18150" ixBug="3308">
<ixBugEvent>18150</ixBugEvent>
<evt>1</evt>
<sVerb><![CDATA[Opened]]></sVerb>
<ixPerson>3</ixPerson>
<ixPersonAssignedTo>0</ixPersonAssignedTo>
<dt>2017-08-06T19:11:54Z</dt>
<s><![CDATA[http://help.fogcreek.com/the-fogbugz-api
And porting all existing data to rf including old closed cases etc.
Also must be searchable, generate case numbers, have category / project, and priority, accept file attachments like screenshots etc.
]]></s>
<fEmail>false</fEmail>
<fHTML>false</fHTML>
<fExternal>false</fExternal>
<sChanges></sChanges>
<sFormat></sFormat>
<rgAttachments></rgAttachments>
<evtDescription><![CDATA[Opened by John Cardinal]]></evtDescription>
<bEmail>false</bEmail>
<bExternal>false</bExternal>
<sPerson><![CDATA[John Cardinal]]></sPerson>
<sHtml><![CDATA[http://help.fogcreek.com/the-fogbugz-api<br />And porting all existing data to rf including old closed cases etc.<br />Also must be searchable, generate case numbers, have category / project, and priority, accept file attachments like screenshots etc.<br />]]></sHtml>
</event>
<event ixBugEvent="18151" ixBug="3308">
<ixBugEvent>18151</ixBugEvent>
<evt>3</evt>
<sVerb><![CDATA[Assigned]]></sVerb>
<ixPerson>3</ixPerson>
<ixPersonAssignedTo>3</ixPersonAssignedTo>
<dt>2017-08-06T19:11:55Z</dt>
<s></s>
<fEmail>false</fEmail>
<fHTML>false</fHTML>
<fExternal>false</fExternal>
<sChanges></sChanges>
<sFormat></sFormat>
<rgAttachments></rgAttachments>
<evtDescription><![CDATA[Assigned to John Cardinal by John Cardinal]]></evtDescription>
<bEmail>false</bEmail>
<bExternal>false</bExternal>
<sPerson><![CDATA[John Cardinal]]></sPerson>
<sHtml></sHtml>
</event>
<event ixBugEvent="18152" ixBug="3308">
<ixBugEvent>18152</ixBugEvent>
<evt>2</evt>
<sVerb><![CDATA[Edited]]></sVerb>
<ixPerson>3</ixPerson>
<ixPersonAssignedTo>0</ixPersonAssignedTo>
<dt>2017-08-07T21:18:55Z</dt>
<s><![CDATA[This query gets all the required data:
https://fog.ayanova.com/api.asp?cmd=search&cols=ixBug,sTitle,fOpen,ixStatus,dtOpened,dtResolved,dtClosed,ixRelatedBugs,events&token=jvmnpkrvc1i7hc6kn6dggqkibktgcc&q=3240
This is for case 3240 (q=3240) and the token I got from just viewing the network traffic using Fogbugz (which works fine).
The response is an xml document with a case/cases/case header and then an events collection under that which contains all the text added and other events like chaging the title etc which we don't care about. Also it contains urls for attachments to download and can use the url directly to get the attachment.
]]></s>
<fEmail>false</fEmail>
<fHTML>false</fHTML>
<fExternal>false</fExternal>
<sChanges></sChanges>
<sFormat></sFormat>
<rgAttachments></rgAttachments>
<evtDescription><![CDATA[Edited by John Cardinal]]></evtDescription>
<bEmail>false</bEmail>
<bExternal>false</bExternal>
<sPerson><![CDATA[John Cardinal]]></sPerson>
<sHtml><![CDATA[This query gets all the required data:<br />https://fog.ayanova.com/api.asp?cmd=search&amp;cols=ixBug,sTitle,fOpen,ixStatus,dtOpened,dtResolved,dtClosed,ixRelatedBugs,events&amp;token=jvmnpkrvc1i7hc6kn6dggqkibktgcc&amp;q=3240<br /><br />This is for case 3240 (q=3240) and the token I got from just viewing the network traffic using Fogbugz (which works fine).<br /><br /><br />The response is an xml document with a case/cases/case header and then an events collection under that which contains all the text added and other events like chaging the title etc which we don't care about.&nbsp; Also it contains urls for attachments to download and can use the url directly to get the attachment.<br /><br />]]></sHtml>
</event>
<event ixBugEvent="18153" ixBug="3308">
<ixBugEvent>18153</ixBugEvent>
<evt>2</evt>
<sVerb><![CDATA[Edited]]></sVerb>
<ixPerson>3</ixPerson>
<ixPersonAssignedTo>0</ixPersonAssignedTo>
<dt>2017-08-07T21:21:02Z</dt>
<s><![CDATA[As of right now this is the highest case number in use.
The procedure will be to start at 1 and fetch every case +1 until we get a response like this:
<?xml version="1.0" encoding="UTF-8"?><response><cases count="0"></cases></response>
Where cases count=0 and would normally be one and the cases branch would have the one case fetched.]]></s>
<fEmail>false</fEmail>
<fHTML>false</fHTML>
<fExternal>false</fExternal>
<sChanges></sChanges>
<sFormat></sFormat>
<rgAttachments></rgAttachments>
<evtDescription><![CDATA[Edited by John Cardinal]]></evtDescription>
<bEmail>false</bEmail>
<bExternal>false</bExternal>
<sPerson><![CDATA[John Cardinal]]></sPerson>
<sHtml><![CDATA[As of right now this is the highest case number in use.<br /><br />The procedure will be to start at 1 and fetch every case +1 until we get a response like this:<br />&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&lt;response&gt;&lt;cases count=&quot;0&quot;&gt;&lt;/cases&gt;&lt;/response&gt;<br /><br />Where cases count=0 and would normally be one and the cases branch would have the one case fetched.]]></sHtml>
</event>
<event ixBugEvent="18154" ixBug="3308">
<ixBugEvent>18154</ixBugEvent>
<evt>2</evt>
<sVerb><![CDATA[Edited]]></sVerb>
<ixPerson>3</ixPerson>
<ixPersonAssignedTo>0</ixPersonAssignedTo>
<dt>2017-08-07T21:24:48Z</dt>
<s><![CDATA[Fields to keep from old fogbugz:
PROJECTS - all of these in a table
CASES - every case:
CASE HEADER:
Case # (id), Title, project, priority
CASE EVENTS:
- s (string of text (CDATA))
- Any attachments
]]></s>
<fEmail>false</fEmail>
<fHTML>false</fHTML>
<fExternal>false</fExternal>
<sChanges></sChanges>
<sFormat></sFormat>
<rgAttachments></rgAttachments>
<evtDescription><![CDATA[Edited by John Cardinal]]></evtDescription>
<bEmail>false</bEmail>
<bExternal>false</bExternal>
<sPerson><![CDATA[John Cardinal]]></sPerson>
<sHtml><![CDATA[Fields to keep from old fogbugz:<br /><br />PROJECTS - all of these in a table<br /><br />CASES - every case:<br /><br />CASE HEADER:<br />Case # (id), Title, project, priority<br />&nbsp; CASE EVENTS:<br />&nbsp; &nbsp; - s (string of text (CDATA))<br />&nbsp; &nbsp; - Any attachments<br /><br />]]></sHtml>
</event>
<event ixBugEvent="18155" ixBug="3308">
<ixBugEvent>18155</ixBugEvent>
<evt>2</evt>
<sVerb><![CDATA[Edited]]></sVerb>
<ixPerson>3</ixPerson>
<ixPersonAssignedTo>0</ixPersonAssignedTo>
<dt>2017-08-07T21:34:27Z</dt>
<s></s>
<fEmail>false</fEmail>
<fHTML>false</fHTML>
<fExternal>false</fExternal>
<sChanges><![CDATA[Priority changed from '3 3' to '1 1'.
]]></sChanges>
<sFormat></sFormat>
<rgAttachments></rgAttachments>
<evtDescription><![CDATA[Edited by John Cardinal]]></evtDescription>
<bEmail>false</bEmail>
<bExternal>false</bExternal>
<sPerson><![CDATA[John Cardinal]]></sPerson>
<sHtml></sHtml>
</event>
<event ixBugEvent="18156" ixBug="3308">
<ixBugEvent>18156</ixBugEvent>
<evt>2</evt>
<sVerb><![CDATA[Edited]]></sVerb>
<ixPerson>3</ixPerson>
<ixPersonAssignedTo>0</ixPersonAssignedTo>
<dt>2017-08-07T21:37:37Z</dt>
<s><![CDATA[New will be primarily a single table, all events in fb now a single text field with date time headered sections for the events and hr lines or something.
New db tables:
CASE
CASEPROJECT
CASEBLOB
CASE Fields:
=-=-=-=-=-=-
ID (integer not null, case # autoincrement),
TITLE text not null,
PROJECT (fk CASEPROJECT not null),
PRIORITY (integer 1-5 not null),
NOTES (text nullable),
CREATED (date time as integer, nullable),
CLOSED (date time as integer, nullable, also serves as closed indicator or open indicator if null),
RELEASEVERSION (text nullable, public release version that fixed the item, i.e. if it was qboi 7.5 patch 1 then this would be 7.5.1),
RELEASENOTES (text nullable, single string of text that serves as customer description of fix).
CASEPROJECT fields:
=-=-=-=-=-=-=-=-=-=-
id (integer not null pk autoincrement),
name (text not null, project name i.e. QBOI)
CASEBLOB fields
id (integer not null pk autoincrement)
CASEID (fk not null link to case)
FILE (blob not null)
]]></s>
<fEmail>false</fEmail>
<fHTML>false</fHTML>
<fExternal>false</fExternal>
<sChanges></sChanges>
<sFormat></sFormat>
<rgAttachments></rgAttachments>
<evtDescription><![CDATA[Edited by John Cardinal]]></evtDescription>
<bEmail>false</bEmail>
<bExternal>false</bExternal>
<sPerson><![CDATA[John Cardinal]]></sPerson>
<sHtml><![CDATA[New will be primarily a single table, all events in fb now a single text field with date time headered sections for the events and hr lines or something. <br /><br />New db tables:<br />CASE<br />CASEPROJECT<br />CASEBLOB<br /><br />CASE Fields:<br />=-=-=-=-=-=-<br />ID (integer not null, case # autoincrement), <br />TITLE text not null, <br />PROJECT (fk CASEPROJECT not null), <br />PRIORITY (integer 1-5 not null), <br />NOTES (text nullable), <br />CREATED (date time as integer, nullable), <br />CLOSED (date time as integer, nullable, also serves as closed indicator or open indicator if null), <br />RELEASEVERSION (text nullable, public release version that fixed the item, i.e. if it was qboi 7.5 patch 1 then this would be 7.5.1),<br />RELEASENOTES (text nullable, single string of text that serves as customer description of fix).<br /><br />CASEPROJECT fields:<br />=-=-=-=-=-=-=-=-=-=-<br />id (integer not null pk autoincrement),<br />name (text not null, project name i.e. QBOI)<br /><br />CASEBLOB fields<br />id (integer not null pk autoincrement)<br />CASEID (fk not null link to case)<br />FILE (blob not null)<br /><br />]]></sHtml>
</event>
<event ixBugEvent="18157" ixBug="3308">
<ixBugEvent>18157</ixBugEvent>
<evt>2</evt>
<sVerb><![CDATA[Edited]]></sVerb>
<ixPerson>3</ixPerson>
<ixPersonAssignedTo>0</ixPersonAssignedTo>
<dt>2017-08-07T22:13:24Z</dt>
<s></s>
<fEmail>false</fEmail>
<fHTML>false</fHTML>
<fExternal>false</fExternal>
<sChanges><![CDATA[Revised John Cardinal's entry from 8/7/2017 at 9:37 PM UTC
]]></sChanges>
<sFormat></sFormat>
<rgAttachments></rgAttachments>
<evtDescription><![CDATA[Edited by John Cardinal]]></evtDescription>
<bEmail>false</bEmail>
<bExternal>false</bExternal>
<sPerson><![CDATA[John Cardinal]]></sPerson>
<sHtml></sHtml>
</event>
<event ixBugEvent="18158" ixBug="3308">
<ixBugEvent>18158</ixBugEvent>
<evt>2</evt>
<sVerb><![CDATA[Edited]]></sVerb>
<ixPerson>3</ixPerson>
<ixPersonAssignedTo>0</ixPersonAssignedTo>
<dt>2017-08-07T22:13:32Z</dt>
<s><![CDATA[UI will have option to "Append" and it will auto insert the date/time HR and then the text.]]></s>
<fEmail>false</fEmail>
<fHTML>false</fHTML>
<fExternal>false</fExternal>
<sChanges></sChanges>
<sFormat></sFormat>
<rgAttachments></rgAttachments>
<evtDescription><![CDATA[Edited by John Cardinal]]></evtDescription>
<bEmail>false</bEmail>
<bExternal>false</bExternal>
<sPerson><![CDATA[John Cardinal]]></sPerson>
<sHtml><![CDATA[UI will have option to &quot;Append&quot; and it will auto insert the date/time HR and then the text.]]></sHtml>
</event>
<event ixBugEvent="18159" ixBug="3308">
<ixBugEvent>18159</ixBugEvent>
<evt>2</evt>
<sVerb><![CDATA[Edited]]></sVerb>
<ixPerson>3</ixPerson>
<ixPersonAssignedTo>0</ixPersonAssignedTo>
<dt>2017-08-07T22:29:29Z</dt>
<s></s>
<fEmail>false</fEmail>
<fHTML>false</fHTML>
<fExternal>false</fExternal>
<sChanges><![CDATA[Revised John Cardinal's entry from 8/7/2017 at 9:37 PM UTC
]]></sChanges>
<sFormat></sFormat>
<rgAttachments></rgAttachments>
<evtDescription><![CDATA[Edited by John Cardinal]]></evtDescription>
<bEmail>false</bEmail>
<bExternal>false</bExternal>
<sPerson><![CDATA[John Cardinal]]></sPerson>
<sHtml></sHtml>
</event>
<event ixBugEvent="18160" ixBug="3308">
<ixBugEvent>18160</ixBugEvent>
<evt>2</evt>
<sVerb><![CDATA[Edited]]></sVerb>
<ixPerson>3</ixPerson>
<ixPersonAssignedTo>0</ixPersonAssignedTo>
<dt>2017-08-07T22:49:22Z</dt>
<s></s>
<fEmail>false</fEmail>
<fHTML>false</fHTML>
<fExternal>false</fExternal>
<sChanges><![CDATA[Title changed from 'Look into porting Fogbugz functionality we use to Rockfish' to 'Port all Fogbugz functionality we use to Rockfish and decommission FogBugz'.
]]></sChanges>
<sFormat></sFormat>
<rgAttachments></rgAttachments>
<evtDescription><![CDATA[Edited by John Cardinal]]></evtDescription>
<bEmail>false</bEmail>
<bExternal>false</bExternal>
<sPerson><![CDATA[John Cardinal]]></sPerson>
<sHtml></sHtml>
</event>
</events>
</case>
</cases>
</response>
*/

44
util/HexString.cs Normal file
View File

@@ -0,0 +1,44 @@
using System;
using System.Text;
namespace rockfishCore.Util
{
public static class HexString
{
public static string ToHex(string str)
{
var sb = new StringBuilder();
var bytes = Encoding.ASCII.GetBytes(str);
foreach (var t in bytes)
{
sb.Append(t.ToString("X2"));
}
return sb.ToString(); // returns: "48656C6C6F20776F726C64" for "Hello world"
}
public static string FromHex(string hexString)
{
var bytes = new byte[hexString.Length / 2];
for (var i = 0; i < bytes.Length; i++)
{
bytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
}
return Encoding.ASCII.GetString(bytes); // returns: "Hello world" for "48656C6C6F20776F726C64"
}
//eoc
}
//eons
}

521
util/KeyFactory.cs Normal file
View File

@@ -0,0 +1,521 @@
using System;
using System.Text;
using System.Collections.Generic;
using System.IO;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.OpenSsl;
using rockfishCore.Models;
using rockfishCore.Util;
namespace rockfishCore.Util
{
//Key generator controller
public static class KeyFactory
{
public const string PLUGIN_MBI_KEY = "MBI - Minimal browser interface";
public const string PLUGIN_WBI_KEY = "WBI - Web browser interface";
public const string PLUGIN_QBI_KEY = "QBI - QuickBooks interface";
public const string PLUGIN_QBOI_KEY = "QBOI - QuickBooks Online interface";
public const string PLUGIN_PTI_KEY = "PTI - US Sage 50/Peachtree interface";
public const string PLUGIN_QUICK_NOTIFICATION_KEY = "QuickNotification";
public const string PLUGIN_EXPORT_TO_XLS_KEY = "ExportToXls";
public const string PLUGIN_OUTLOOK_SCHEDULE_KEY = "OutlookSchedule";
public const string PLUGIN_OLI_KEY = "AyaNovaOLI";
public const string PLUGIN_IMPORT_EXPORT_CSV_DUPLICATE_KEY = "ImportExportCSVDuplicate";
public const string PLUGIN_RI_KEY = "RI - Responsive Interface";
private static Dictionary<string, DateTime> _plugins;
//Generate a key message reply from a key selection object
//CALLED BY LicenseController Generate route
public static string GetKeyReply(dtoKeyOptions ko, LicenseTemplates t, rockfishContext ct)
{
//case 3542
ko.installByDate = System.DateTime.Now.AddYears(1);//changed from one month to one year
string sKey = genKey(ko, ct);
string sMsg = genMessage(sKey, ko, t);
return sMsg;
}
//called by trialkeyrequesthandler.GenerateFromRequest
//get a trial key for named regTo
public static string GetTrialKey(string regTo, bool lite, LicenseTemplates t, string authorizedUserKeyGeneratorStamp,
string emailAddress, rockfishContext ct)//case 3233
{
DateTime dtoneMonth = System.DateTime.Now.AddMonths(1);
long oneMonth = DateUtil.DateToEpoch(System.DateTime.Now.AddMonths(1));
dtoKeyOptions ko = new dtoKeyOptions();
//case 3233
ko.emailAddress = emailAddress;
ko.customerId = 0; //not a customer so trial 0
ko.registeredTo = regTo;
ko.supportExpiresDate = oneMonth;
ko.isLite = lite;
ko.installByDate = dtoneMonth;
ko.authorizedUserKeyGeneratorStamp = authorizedUserKeyGeneratorStamp;
if (lite)
{
ko.users = 1;
}
else
{
ko.users = 5;
}
ko.licenseType = "webRequestedTrial";
ko.qbi = true;
ko.qbiSupportExpiresDate = oneMonth;
ko.qboi = true;
ko.qboiSupportExpiresDate = oneMonth;
ko.pti = true;
ko.ptiSupportExpiresDate = oneMonth;
ko.exportToXls = true;
ko.exportToXlsSupportExpiresDate = oneMonth;
ko.outlookSchedule = true;
ko.outlookScheduleSupportExpiresDate = oneMonth;
ko.oli = true;
ko.oliSupportExpiresDate = oneMonth;
ko.importExportCSVDuplicate = true;
ko.importExportCSVDuplicateSupportExpiresDate = oneMonth;
if (!lite)
{
ko.quickNotification = true;
ko.quickNotificationSupportExpiresDate = oneMonth;
ko.mbi = true;
ko.mbiSupportExpiresDate = oneMonth;
ko.wbi = true;
ko.wbiSupportExpiresDate = oneMonth;
ko.ri = true;
ko.riSupportExpiresDate = oneMonth;
}
string sKey = genKey(ko, ct);
string sMsg = genMessage(sKey, ko, t);
return sMsg;
}
//Take the key and the options and make a return message ready to send
private static string genMessage(string sKey, dtoKeyOptions ko, LicenseTemplates template)
{
string sMessage = "";
if (ko.licenseType == "new")
{
if (ko.isLite)
{
sMessage = template.LiteNew;
}
else
{
sMessage = template.FullNew;
}
}
else if (ko.licenseType == "addon")
{
if (ko.isLite)
{
sMessage = template.LiteAddOn;
}
else
{
sMessage = template.FullAddOn;
}
}
else//licensed trial
{
if (ko.isLite)
{
sMessage = template.LiteTrial;
}
else
{
sMessage = template.FullTrial;
}
}
//token substitutions
sMessage = sMessage.Replace("[LicenseExpiryDate]", ko.installByDate.ToString("D"));//https://github.com/dotnet/coreclr/issues/2317
sMessage = sMessage.Replace("[LicenseDescription]", LicenseInfo(ko));
sMessage = sMessage.Replace("[LicenseKey]", sKey);
return sMessage;
}
// Extra info to display about key at top of key message
private static string LicenseInfo(dtoKeyOptions ko)
{
StringBuilder sb = new StringBuilder();
sb.Append("LICENSE DETAILS\r\n");
sb.Append("This key must be installed before: ");
sb.Append(ko.installByDate.ToString("D"));
sb.Append("\r\n");
//if (kg.SelectedLicenseType == "Web requested trial")
//{
// sb.Append("*** This temporary license key has been provided for limited evaluation purposes only *** \r\n");
// sb.Append("This license will expire and AyaNova usage will be restricted after: " + kg.Expires.ToLongDateString() + "\r\n\r\n");
//}
if (ko.keyWillLockout)
{
sb.Append("*** This temporary license key is provided for evaluation use only pending payment ***\r\n");
sb.Append("This license will expire and AyaNova usage will be restricted after: " + DateUtil.EpochToString(ko.lockoutDate) + "\r\n");
sb.Append("\r\n");
sb.Append("A permanent license key will be sent to you when payment \r\n" +
"has been received and processed. There will be no extensions or \r\n" +
"exceptions. Please send in payment early enough to allow for \r\n" +
"mail and processing time to ensure uninterrupted use of AyaNova" + (ko.isLite ? " Lite" : "") + ". \r\n\r\n");
}
sb.Append("Registered to: ");
sb.Append(ko.registeredTo);
sb.Append("\r\n");
//case 3233
sb.Append("Fetch address: ");
sb.Append(ko.emailAddress);
sb.Append("\r\n");
sb.Append("Fetch code: ");
sb.Append(ko.fetchCode);
sb.Append("\r\n");
sb.Append("Scheduleable resources: ");
switch (ko.users)
{
case 1:
sb.AppendLine("1");
break;
case 5:
sb.AppendLine("Up to 5");
break;
case 10:
sb.AppendLine("Up to 10");
break;
case 15:
sb.AppendLine("Up to 15");//case 3550
break;
case 20:
sb.AppendLine("Up to 20");
break;
case 50:
sb.AppendLine("Up to 50");
break;
case 999:
sb.AppendLine("Up to 999");
break;
}
sb.AppendLine("Support and updates until: " + DateUtil.EpochToString(ko.supportExpiresDate) + "\r\n");
if (_plugins.Count > 0)
{
sb.Append("\r\n");
sb.Append("Plugins:\r\n");
foreach (KeyValuePair<string, DateTime> kv in _plugins)
{
sb.Append("\t");
sb.Append(kv.Key);
sb.Append(" support and updates until: ");
sb.Append(kv.Value.ToString("D"));
sb.Append("\r\n");
}
}
return sb.ToString();
}
/// <summary>
/// Generate keycode based on passed in data
/// This is called by both regular and trial license key routes
/// </summary>
/// <returns></returns>
private static string genKey(dtoKeyOptions ko, rockfishContext ct)
{
_plugins = new Dictionary<string, DateTime>();
if (ko.registeredTo == null || ko.registeredTo == "")
throw new ArgumentException("RegisteredTo is required", "RegisteredTo");
if (string.IsNullOrWhiteSpace(ko.emailAddress))
throw new ArgumentException("Email address is required", "emailAddress");
try
{
StringBuilder sbKey = new StringBuilder();
StringWriter sw = new StringWriter(sbKey);
//case 3233
ko.fetchCode = FetchKeyCode.generate();
using (Newtonsoft.Json.JsonWriter w = new Newtonsoft.Json.JsonTextWriter(sw))
{
w.Formatting = Newtonsoft.Json.Formatting.Indented;
//outer object start
w.WriteStartObject();
if (ko.isLite)
w.WritePropertyName("AyaNovaLiteLicenseKey");
else
w.WritePropertyName("AyaNovaLicenseKey");
w.WriteStartObject();//start of key object
w.WritePropertyName("SchemaVersion");
w.WriteValue("7");
//stamp a unique value in the key so it can be revoked later
//used to use the digest value of the key for this with xml key
//whole unix timestamp seconds but kept as a double to work beyond 2038
w.WritePropertyName("Id");
var vv = Math.Truncate((DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds);
string sId = vv.ToString();
if (sId.Contains(","))
sId = sId.Split('.')[0];
w.WriteValue(sId);
w.WritePropertyName("Created");
w.WriteValue(System.DateTime.Now);
w.WritePropertyName("Sub");
w.WriteValue("true");
w.WritePropertyName("RegisteredTo");
w.WriteValue(ko.registeredTo);//unicode test string
//case 3233
w.WritePropertyName("EmailAddress");
w.WriteValue(ko.emailAddress);
w.WritePropertyName("FetchCode");
w.WriteValue(ko.fetchCode);
//case 3187 - Source here
//rockfish
w.WritePropertyName("Source");
w.WriteValue(rockfishCore.Util.HexString.ToHex(ko.authorizedUserKeyGeneratorStamp));
w.WritePropertyName("InstallableUntil");
w.WriteValue(ko.installByDate);//case 3542, respect the KO option
w.WritePropertyName("TotalScheduleableUsers");
w.WriteValue(ko.users.ToString());//Needs to be a string to match rockfish format
w.WritePropertyName("Expires");
w.WriteValue(DateUtil.EpochToDate(ko.supportExpiresDate));
if (ko.keyWillLockout)
{
w.WritePropertyName("LockDate");
w.WriteValue(DateUtil.EpochToDate(ko.lockoutDate));
}
w.WritePropertyName("RequestedTrial");
bool bRequestedTrial = ko.licenseType == "webRequestedTrial";
w.WriteValue(bRequestedTrial.ToString());
//PLUGINS
w.WritePropertyName("Plugins");
w.WriteStartObject();//start of key object
w.WritePropertyName("Plugin");
w.WriteStartArray();
if (ko.mbi)
AddLicensePlugin(w, PLUGIN_MBI_KEY, DateUtil.EpochToDate(ko.mbiSupportExpiresDate));
if (ko.wbi)
AddLicensePlugin(w, PLUGIN_WBI_KEY, DateUtil.EpochToDate(ko.wbiSupportExpiresDate));
if (ko.qbi)
AddLicensePlugin(w, PLUGIN_QBI_KEY, DateUtil.EpochToDate(ko.qbiSupportExpiresDate));
if (ko.qboi)
AddLicensePlugin(w, PLUGIN_QBOI_KEY, DateUtil.EpochToDate(ko.qboiSupportExpiresDate));
if (ko.pti)
AddLicensePlugin(w, PLUGIN_PTI_KEY, DateUtil.EpochToDate(ko.ptiSupportExpiresDate));
if (ko.quickNotification)
AddLicensePlugin(w, PLUGIN_QUICK_NOTIFICATION_KEY, DateUtil.EpochToDate(ko.quickNotificationSupportExpiresDate));
if (ko.exportToXls)
AddLicensePlugin(w, PLUGIN_EXPORT_TO_XLS_KEY, DateUtil.EpochToDate(ko.exportToXlsSupportExpiresDate));
if (ko.outlookSchedule)
AddLicensePlugin(w, PLUGIN_OUTLOOK_SCHEDULE_KEY, DateUtil.EpochToDate(ko.outlookScheduleSupportExpiresDate));
if (ko.oli)
AddLicensePlugin(w, PLUGIN_OLI_KEY, DateUtil.EpochToDate(ko.oliSupportExpiresDate));
if (ko.importExportCSVDuplicate)
AddLicensePlugin(w, PLUGIN_IMPORT_EXPORT_CSV_DUPLICATE_KEY, DateUtil.EpochToDate(ko.importExportCSVDuplicateSupportExpiresDate));
if (ko.ri)
AddLicensePlugin(w, PLUGIN_RI_KEY, DateUtil.EpochToDate(ko.riSupportExpiresDate));
//end of plugins array
w.WriteEnd();
//end of plugins object
w.WriteEndObject();
//end of AyaNova/AyaNovaLite key object
w.WriteEndObject();
//close outer 'wrapper' object brace }
w.WriteEndObject();
}//end of using statement
// ## CALCULATE SIGNATURE
//GET JSON as a string with whitespace stripped outside of delimited strings
//http://stackoverflow.com/questions/8913138/minify-indented-json-string-in-net
string keyNoWS = System.Text.RegularExpressions.Regex.Replace(sbKey.ToString(), "(\"(?:[^\"\\\\]|\\\\.)*\")|\\s+", "$1");
//**** Note this is our real 2016 private key
var privatePEM = @"-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAz7wrvLDcKVMZ31HFGBnLWL08IodYIV5VJkKy1Z0n2snprhSi
u3izxTyz+SLpftvKHJpky027ii7l/pL9Bo3JcjU5rKrxXavnE7TuYPjXn16dNLd0
K/ERSU+pXLmUaVN0nUWuGuUMoGJMEXoulS6pJiG11yu3BM9fL2Nbj0C6a+UwzEHF
mns3J/daZOb4gAzMUdJfh9OJ0+wRGzR8ZxyC99Na2gDmqYglUkSMjwLTL/HbgwF4
OwmoQYJBcET0Wa6Gfb17SaF8XRBV5ZtpCsbStkthGeoXZkFriB9c1eFQLKpBYQo2
DW3H1MPG2nAlQZLbkJj5cSh7/t1bRF08m6P+EQIDAQABAoIBAQCGvTpxLRXgB/Kk
EtmQBEsMx9EVZEwZeKIqKuDsBP8wvf4/10ql5mhT6kehtK9WhSDW5J2z8DtQKZMs
SBKuCZE77qH2CPp9E17SPWzQoRbaW/gDlWpYhgf8URs89XH5zxO4XtXKw/4omRlV
zLYiNR2pifv0EHqpOAg5KGzewdEo4VgXgtRWpHZLMpH2Q0/5ZIKMhstI6vFHP1p7
jmU4YI6uxiu7rVrZDmIUsAGoTdMabNqK/N8hKaoBiIto0Jn1ck26g+emLg8m160y
Xciu5yFUU+PP1SJMUs+k1UnAWf4p46X9jRLQCBRue9o0Ntiq/75aljRoDvgdwDsR
mg4ZANqxAoGBAPBoM5KoMZ4sv8ZFv8V+V8hgL5xiLgGoiwQl91mRsHRM/NQU5A/w
tH8nmwUrJOrksV7kX9228smKmoliTptyGGyi1NPmSkA7cN9YYnENoOEBHCVNK9vh
P+bkbMYUDNMW4fgOj09oXtQtMl5E2B3OTGoNwZ2w13YQJ8RIniLPsX7nAoGBAN01
eQNcUzQk9YrFGTznOs8udDLBfigDxaNnawvPueulJdBy6ZXDDrKmkQQA7xxl8YPr
dNtBq2lOgnb6+smC15TaAfV/fb8BLmkSwdn4Fy0FApIXIEOnLq+wjkte98nuezl8
9KXDzaqNI9hPuk2i36tJuLLMH8hzldveWbWjSlRHAoGBAKRPE7CQtBjfjNL+qOta
RrT0yJWhpMANabYUHNJi+K8ET2jEPnuGkFa3wwPtUPYaCABLJhprB9Unnid3wTIM
8RSO1ddd9jGgbqy3w9Bw+BvQnmQAMpG9iedNB+r5mSpM4XSgvuIO+4EYwuwbMXpt
nVx+um4Eh75xnDxTRYGVYkLRAoGAaZVpUlpR+HSfooHbPv+bSWKB4ewLPCw4vHrT
VErtEfW8q9b9eRcmP81TMFcFykc6VN4g47pfh58KlKHM7DwAjDLWdohIy89TiKGE
V3acEUfv5y0UoFX+6ara8Ey+9upWdKUY3Lotw3ckoc3EPeQ84DQK7YSSswnAgLaL
mS/8fWcCgYBjRefVbEep161d2DGruk4X7eNI9TFJ278h6ydW5kK9aTJuxkrtKIp4
CYf6emoB4mLXFPvAmnsalkhN2iB29hUZCXXSUjpKZrpijL54Wdu2S6ynm7aT97NF
oArP0E2Vbow3JMxq/oeXmHbrLMLQfYyXwFmciLFigOtkd45bfHdrbA==
-----END RSA PRIVATE KEY-----";
PemReader pr = new PemReader(new StringReader(privatePEM));
AsymmetricCipherKeyPair keys = (AsymmetricCipherKeyPair)pr.ReadObject();
var encoder = new UTF8Encoding(false, true);
var inputData = encoder.GetBytes(keyNoWS);
var signer = SignerUtilities.GetSigner("SHA256WITHRSA");
signer.Init(true, keys.Private);
signer.BlockUpdate(inputData, 0, inputData.Length);
var sign = signer.GenerateSignature();
var signature = Convert.ToBase64String(sign);
System.Text.StringBuilder sbOut = new StringBuilder();
sbOut.AppendLine("[KEY");
sbOut.AppendLine(sbKey.ToString());
sbOut.AppendLine("KEY]");
sbOut.AppendLine("[SIGNATURE");
sbOut.AppendLine(signature);
sbOut.AppendLine("SIGNATURE]");
//case 3233 insert into db
License l = new License();
l.DtCreated = DateUtil.NowAsEpoch();
l.Code = ko.fetchCode;
l.CustomerId = ko.customerId;
l.Email = ko.emailAddress.ToLowerInvariant();
l.Key = sbOut.ToString();
l.RegTo = ko.registeredTo;
ct.License.Add(l);
ct.SaveChanges();
return sbOut.ToString();
}
catch (Exception ex)
{
return (ex.Message);
}
}
private static void AddLicensePlugin(Newtonsoft.Json.JsonWriter w, string pluginName, DateTime pluginExpires)
{
//this dictionary is used by the additional message code to
//make the human readable portion of the license
_plugins.Add(pluginName, pluginExpires);
//this is adding it to the actual key
w.WriteStartObject();
w.WritePropertyName("Item");
w.WriteValue(pluginName);
w.WritePropertyName("SubscriptionExpires");
w.WriteValue(pluginExpires);
w.WriteEndObject();
//----------------
}
//eoc
}
//eons
}

733
util/MimeTypeMap.cs Normal file
View File

@@ -0,0 +1,733 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace rockfishCore.Util
{
//https://github.com/HelmGlobal/MimeTypeMap/blob/master/src/MimeTypes/MimeTypeMap.cs
public static class MimeTypeMap
{
private static readonly Lazy<IDictionary<string, string>> _mappings = new Lazy<IDictionary<string, string>>(BuildMappings);
private static IDictionary<string, string> BuildMappings()
{
var mappings = new Dictionary<string, string>(StringComparer.CurrentCultureIgnoreCase) {
#region Big freaking list of mime types
// maps both ways,
// extension -> mime type
// and
// mime type -> extension
//
// any mime types on left side not pre-loaded on right side, are added automatically
// some mime types can map to multiple extensions, so to get a deterministic mapping,
// add those to the dictionary specifcially
//
// combination of values from Windows 7 Registry and
// from C:\Windows\System32\inetsrv\config\applicationHost.config
// some added, including .7z and .dat
//
// Some added based on http://www.iana.org/assignments/media-types/media-types.xhtml
// which lists mime types, but not extensions
//
{".323", "text/h323"},
{".3g2", "video/3gpp2"},
{".3gp", "video/3gpp"},
{".3gp2", "video/3gpp2"},
{".3gpp", "video/3gpp"},
{".7z", "application/x-7z-compressed"},
{".aa", "audio/audible"},
{".AAC", "audio/aac"},
{".aaf", "application/octet-stream"},
{".aax", "audio/vnd.audible.aax"},
{".ac3", "audio/ac3"},
{".aca", "application/octet-stream"},
{".accda", "application/msaccess.addin"},
{".accdb", "application/msaccess"},
{".accdc", "application/msaccess.cab"},
{".accde", "application/msaccess"},
{".accdr", "application/msaccess.runtime"},
{".accdt", "application/msaccess"},
{".accdw", "application/msaccess.webapplication"},
{".accft", "application/msaccess.ftemplate"},
{".acx", "application/internet-property-stream"},
{".AddIn", "text/xml"},
{".ade", "application/msaccess"},
{".adobebridge", "application/x-bridge-url"},
{".adp", "application/msaccess"},
{".ADT", "audio/vnd.dlna.adts"},
{".ADTS", "audio/aac"},
{".afm", "application/octet-stream"},
{".ai", "application/postscript"},
{".aif", "audio/aiff"},
{".aifc", "audio/aiff"},
{".aiff", "audio/aiff"},
{".air", "application/vnd.adobe.air-application-installer-package+zip"},
{".amc", "application/mpeg"},
{".anx", "application/annodex"},
{".apk", "application/vnd.android.package-archive" },
{".application", "application/x-ms-application"},
{".art", "image/x-jg"},
{".asa", "application/xml"},
{".asax", "application/xml"},
{".ascx", "application/xml"},
{".asd", "application/octet-stream"},
{".asf", "video/x-ms-asf"},
{".ashx", "application/xml"},
{".asi", "application/octet-stream"},
{".asm", "text/plain"},
{".asmx", "application/xml"},
{".aspx", "application/xml"},
{".asr", "video/x-ms-asf"},
{".asx", "video/x-ms-asf"},
{".atom", "application/atom+xml"},
{".au", "audio/basic"},
{".avi", "video/x-msvideo"},
{".axa", "audio/annodex"},
{".axs", "application/olescript"},
{".axv", "video/annodex"},
{".bas", "text/plain"},
{".bcpio", "application/x-bcpio"},
{".bin", "application/octet-stream"},
{".bmp", "image/bmp"},
{".c", "text/plain"},
{".cab", "application/octet-stream"},
{".caf", "audio/x-caf"},
{".calx", "application/vnd.ms-office.calx"},
{".cat", "application/vnd.ms-pki.seccat"},
{".cc", "text/plain"},
{".cd", "text/plain"},
{".cdda", "audio/aiff"},
{".cdf", "application/x-cdf"},
{".cer", "application/x-x509-ca-cert"},
{".cfg", "text/plain"},
{".chm", "application/octet-stream"},
{".class", "application/x-java-applet"},
{".clp", "application/x-msclip"},
{".cmd", "text/plain"},
{".cmx", "image/x-cmx"},
{".cnf", "text/plain"},
{".cod", "image/cis-cod"},
{".config", "application/xml"},
{".contact", "text/x-ms-contact"},
{".coverage", "application/xml"},
{".cpio", "application/x-cpio"},
{".cpp", "text/plain"},
{".crd", "application/x-mscardfile"},
{".crl", "application/pkix-crl"},
{".crt", "application/x-x509-ca-cert"},
{".cs", "text/plain"},
{".csdproj", "text/plain"},
{".csh", "application/x-csh"},
{".csproj", "text/plain"},
{".css", "text/css"},
{".csv", "text/csv"},
{".cur", "application/octet-stream"},
{".cxx", "text/plain"},
{".dat", "application/octet-stream"},
{".datasource", "application/xml"},
{".dbproj", "text/plain"},
{".dcr", "application/x-director"},
{".def", "text/plain"},
{".deploy", "application/octet-stream"},
{".der", "application/x-x509-ca-cert"},
{".dgml", "application/xml"},
{".dib", "image/bmp"},
{".dif", "video/x-dv"},
{".dir", "application/x-director"},
{".disco", "text/xml"},
{".divx", "video/divx"},
{".dll", "application/x-msdownload"},
{".dll.config", "text/xml"},
{".dlm", "text/dlm"},
{".doc", "application/msword"},
{".docm", "application/vnd.ms-word.document.macroEnabled.12"},
{".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
{".dot", "application/msword"},
{".dotm", "application/vnd.ms-word.template.macroEnabled.12"},
{".dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"},
{".dsp", "application/octet-stream"},
{".dsw", "text/plain"},
{".dtd", "text/xml"},
{".dtsConfig", "text/xml"},
{".dv", "video/x-dv"},
{".dvi", "application/x-dvi"},
{".dwf", "drawing/x-dwf"},
{".dwp", "application/octet-stream"},
{".dxr", "application/x-director"},
{".eml", "message/rfc822"},
{".emz", "application/octet-stream"},
{".eot", "application/vnd.ms-fontobject"},
{".eps", "application/postscript"},
{".etl", "application/etl"},
{".etx", "text/x-setext"},
{".evy", "application/envoy"},
{".exe", "application/octet-stream"},
{".exe.config", "text/xml"},
{".fdf", "application/vnd.fdf"},
{".fif", "application/fractals"},
{".filters", "application/xml"},
{".fla", "application/octet-stream"},
{".flac", "audio/flac"},
{".flr", "x-world/x-vrml"},
{".flv", "video/x-flv"},
{".fsscript", "application/fsharp-script"},
{".fsx", "application/fsharp-script"},
{".generictest", "application/xml"},
{".gif", "image/gif"},
{".group", "text/x-ms-group"},
{".gsm", "audio/x-gsm"},
{".gtar", "application/x-gtar"},
{".gz", "application/x-gzip"},
{".h", "text/plain"},
{".hdf", "application/x-hdf"},
{".hdml", "text/x-hdml"},
{".hhc", "application/x-oleobject"},
{".hhk", "application/octet-stream"},
{".hhp", "application/octet-stream"},
{".hlp", "application/winhlp"},
{".hpp", "text/plain"},
{".hqx", "application/mac-binhex40"},
{".hta", "application/hta"},
{".htc", "text/x-component"},
{".htm", "text/html"},
{".html", "text/html"},
{".htt", "text/webviewhtml"},
{".hxa", "application/xml"},
{".hxc", "application/xml"},
{".hxd", "application/octet-stream"},
{".hxe", "application/xml"},
{".hxf", "application/xml"},
{".hxh", "application/octet-stream"},
{".hxi", "application/octet-stream"},
{".hxk", "application/xml"},
{".hxq", "application/octet-stream"},
{".hxr", "application/octet-stream"},
{".hxs", "application/octet-stream"},
{".hxt", "text/html"},
{".hxv", "application/xml"},
{".hxw", "application/octet-stream"},
{".hxx", "text/plain"},
{".i", "text/plain"},
{".ico", "image/x-icon"},
{".ics", "application/octet-stream"},
{".idl", "text/plain"},
{".ief", "image/ief"},
{".iii", "application/x-iphone"},
{".inc", "text/plain"},
{".inf", "application/octet-stream"},
{".ini", "text/plain"},
{".inl", "text/plain"},
{".ins", "application/x-internet-signup"},
{".ipa", "application/x-itunes-ipa"},
{".ipg", "application/x-itunes-ipg"},
{".ipproj", "text/plain"},
{".ipsw", "application/x-itunes-ipsw"},
{".iqy", "text/x-ms-iqy"},
{".isp", "application/x-internet-signup"},
{".ite", "application/x-itunes-ite"},
{".itlp", "application/x-itunes-itlp"},
{".itms", "application/x-itunes-itms"},
{".itpc", "application/x-itunes-itpc"},
{".IVF", "video/x-ivf"},
{".jar", "application/java-archive"},
{".java", "application/octet-stream"},
{".jck", "application/liquidmotion"},
{".jcz", "application/liquidmotion"},
{".jfif", "image/pjpeg"},
{".jnlp", "application/x-java-jnlp-file"},
{".jpb", "application/octet-stream"},
{".jpe", "image/jpeg"},
{".jpeg", "image/jpeg"},
{".jpg", "image/jpeg"},
{".js", "application/javascript"},
{".json", "application/json"},
{".jsx", "text/jscript"},
{".jsxbin", "text/plain"},
{".latex", "application/x-latex"},
{".library-ms", "application/windows-library+xml"},
{".lit", "application/x-ms-reader"},
{".loadtest", "application/xml"},
{".lpk", "application/octet-stream"},
{".lsf", "video/x-la-asf"},
{".lst", "text/plain"},
{".lsx", "video/x-la-asf"},
{".lzh", "application/octet-stream"},
{".m13", "application/x-msmediaview"},
{".m14", "application/x-msmediaview"},
{".m1v", "video/mpeg"},
{".m2t", "video/vnd.dlna.mpeg-tts"},
{".m2ts", "video/vnd.dlna.mpeg-tts"},
{".m2v", "video/mpeg"},
{".m3u", "audio/x-mpegurl"},
{".m3u8", "audio/x-mpegurl"},
{".m4a", "audio/m4a"},
{".m4b", "audio/m4b"},
{".m4p", "audio/m4p"},
{".m4r", "audio/x-m4r"},
{".m4v", "video/x-m4v"},
{".mac", "image/x-macpaint"},
{".mak", "text/plain"},
{".man", "application/x-troff-man"},
{".manifest", "application/x-ms-manifest"},
{".map", "text/plain"},
{".master", "application/xml"},
{".mda", "application/msaccess"},
{".mdb", "application/x-msaccess"},
{".mde", "application/msaccess"},
{".mdp", "application/octet-stream"},
{".me", "application/x-troff-me"},
{".mfp", "application/x-shockwave-flash"},
{".mht", "message/rfc822"},
{".mhtml", "message/rfc822"},
{".mid", "audio/mid"},
{".midi", "audio/mid"},
{".mix", "application/octet-stream"},
{".mk", "text/plain"},
{".mmf", "application/x-smaf"},
{".mno", "text/xml"},
{".mny", "application/x-msmoney"},
{".mod", "video/mpeg"},
{".mov", "video/quicktime"},
{".movie", "video/x-sgi-movie"},
{".mp2", "video/mpeg"},
{".mp2v", "video/mpeg"},
{".mp3", "audio/mpeg"},
{".mp4", "video/mp4"},
{".mp4v", "video/mp4"},
{".mpa", "video/mpeg"},
{".mpe", "video/mpeg"},
{".mpeg", "video/mpeg"},
{".mpf", "application/vnd.ms-mediapackage"},
{".mpg", "video/mpeg"},
{".mpp", "application/vnd.ms-project"},
{".mpv2", "video/mpeg"},
{".mqv", "video/quicktime"},
{".ms", "application/x-troff-ms"},
{".msi", "application/octet-stream"},
{".mso", "application/octet-stream"},
{".mts", "video/vnd.dlna.mpeg-tts"},
{".mtx", "application/xml"},
{".mvb", "application/x-msmediaview"},
{".mvc", "application/x-miva-compiled"},
{".mxp", "application/x-mmxp"},
{".nc", "application/x-netcdf"},
{".nsc", "video/x-ms-asf"},
{".nws", "message/rfc822"},
{".ocx", "application/octet-stream"},
{".oda", "application/oda"},
{".odb", "application/vnd.oasis.opendocument.database"},
{".odc", "application/vnd.oasis.opendocument.chart"},
{".odf", "application/vnd.oasis.opendocument.formula"},
{".odg", "application/vnd.oasis.opendocument.graphics"},
{".odh", "text/plain"},
{".odi", "application/vnd.oasis.opendocument.image"},
{".odl", "text/plain"},
{".odm", "application/vnd.oasis.opendocument.text-master"},
{".odp", "application/vnd.oasis.opendocument.presentation"},
{".ods", "application/vnd.oasis.opendocument.spreadsheet"},
{".odt", "application/vnd.oasis.opendocument.text"},
{".oga", "audio/ogg"},
{".ogg", "audio/ogg"},
{".ogv", "video/ogg"},
{".ogx", "application/ogg"},
{".one", "application/onenote"},
{".onea", "application/onenote"},
{".onepkg", "application/onenote"},
{".onetmp", "application/onenote"},
{".onetoc", "application/onenote"},
{".onetoc2", "application/onenote"},
{".opus", "audio/ogg"},
{".orderedtest", "application/xml"},
{".osdx", "application/opensearchdescription+xml"},
{".otf", "application/font-sfnt"},
{".otg", "application/vnd.oasis.opendocument.graphics-template"},
{".oth", "application/vnd.oasis.opendocument.text-web"},
{".otp", "application/vnd.oasis.opendocument.presentation-template"},
{".ots", "application/vnd.oasis.opendocument.spreadsheet-template"},
{".ott", "application/vnd.oasis.opendocument.text-template"},
{".oxt", "application/vnd.openofficeorg.extension"},
{".p10", "application/pkcs10"},
{".p12", "application/x-pkcs12"},
{".p7b", "application/x-pkcs7-certificates"},
{".p7c", "application/pkcs7-mime"},
{".p7m", "application/pkcs7-mime"},
{".p7r", "application/x-pkcs7-certreqresp"},
{".p7s", "application/pkcs7-signature"},
{".pbm", "image/x-portable-bitmap"},
{".pcast", "application/x-podcast"},
{".pct", "image/pict"},
{".pcx", "application/octet-stream"},
{".pcz", "application/octet-stream"},
{".pdf", "application/pdf"},
{".pfb", "application/octet-stream"},
{".pfm", "application/octet-stream"},
{".pfx", "application/x-pkcs12"},
{".pgm", "image/x-portable-graymap"},
{".pic", "image/pict"},
{".pict", "image/pict"},
{".pkgdef", "text/plain"},
{".pkgundef", "text/plain"},
{".pko", "application/vnd.ms-pki.pko"},
{".pls", "audio/scpls"},
{".pma", "application/x-perfmon"},
{".pmc", "application/x-perfmon"},
{".pml", "application/x-perfmon"},
{".pmr", "application/x-perfmon"},
{".pmw", "application/x-perfmon"},
{".png", "image/png"},
{".pnm", "image/x-portable-anymap"},
{".pnt", "image/x-macpaint"},
{".pntg", "image/x-macpaint"},
{".pnz", "image/png"},
{".pot", "application/vnd.ms-powerpoint"},
{".potm", "application/vnd.ms-powerpoint.template.macroEnabled.12"},
{".potx", "application/vnd.openxmlformats-officedocument.presentationml.template"},
{".ppa", "application/vnd.ms-powerpoint"},
{".ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12"},
{".ppm", "image/x-portable-pixmap"},
{".pps", "application/vnd.ms-powerpoint"},
{".ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12"},
{".ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow"},
{".ppt", "application/vnd.ms-powerpoint"},
{".pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12"},
{".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
{".prf", "application/pics-rules"},
{".prm", "application/octet-stream"},
{".prx", "application/octet-stream"},
{".ps", "application/postscript"},
{".psc1", "application/PowerShell"},
{".psd", "application/octet-stream"},
{".psess", "application/xml"},
{".psm", "application/octet-stream"},
{".psp", "application/octet-stream"},
{".pub", "application/x-mspublisher"},
{".pwz", "application/vnd.ms-powerpoint"},
{".qht", "text/x-html-insertion"},
{".qhtm", "text/x-html-insertion"},
{".qt", "video/quicktime"},
{".qti", "image/x-quicktime"},
{".qtif", "image/x-quicktime"},
{".qtl", "application/x-quicktimeplayer"},
{".qxd", "application/octet-stream"},
{".ra", "audio/x-pn-realaudio"},
{".ram", "audio/x-pn-realaudio"},
{".rar", "application/x-rar-compressed"},
{".ras", "image/x-cmu-raster"},
{".rat", "application/rat-file"},
{".rc", "text/plain"},
{".rc2", "text/plain"},
{".rct", "text/plain"},
{".rdlc", "application/xml"},
{".reg", "text/plain"},
{".resx", "application/xml"},
{".rf", "image/vnd.rn-realflash"},
{".rgb", "image/x-rgb"},
{".rgs", "text/plain"},
{".rm", "application/vnd.rn-realmedia"},
{".rmi", "audio/mid"},
{".rmp", "application/vnd.rn-rn_music_package"},
{".roff", "application/x-troff"},
{".rpm", "audio/x-pn-realaudio-plugin"},
{".rqy", "text/x-ms-rqy"},
{".rtf", "application/rtf"},
{".rtx", "text/richtext"},
{".ruleset", "application/xml"},
{".s", "text/plain"},
{".safariextz", "application/x-safari-safariextz"},
{".scd", "application/x-msschedule"},
{".scr", "text/plain"},
{".sct", "text/scriptlet"},
{".sd2", "audio/x-sd2"},
{".sdp", "application/sdp"},
{".sea", "application/octet-stream"},
{".searchConnector-ms", "application/windows-search-connector+xml"},
{".setpay", "application/set-payment-initiation"},
{".setreg", "application/set-registration-initiation"},
{".settings", "application/xml"},
{".sgimb", "application/x-sgimb"},
{".sgml", "text/sgml"},
{".sh", "application/x-sh"},
{".shar", "application/x-shar"},
{".shtml", "text/html"},
{".sit", "application/x-stuffit"},
{".sitemap", "application/xml"},
{".skin", "application/xml"},
{".sldm", "application/vnd.ms-powerpoint.slide.macroEnabled.12"},
{".sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide"},
{".slk", "application/vnd.ms-excel"},
{".sln", "text/plain"},
{".slupkg-ms", "application/x-ms-license"},
{".smd", "audio/x-smd"},
{".smi", "application/octet-stream"},
{".smx", "audio/x-smd"},
{".smz", "audio/x-smd"},
{".snd", "audio/basic"},
{".snippet", "application/xml"},
{".snp", "application/octet-stream"},
{".sol", "text/plain"},
{".sor", "text/plain"},
{".spc", "application/x-pkcs7-certificates"},
{".spl", "application/futuresplash"},
{".spx", "audio/ogg"},
{".src", "application/x-wais-source"},
{".srf", "text/plain"},
{".SSISDeploymentManifest", "text/xml"},
{".ssm", "application/streamingmedia"},
{".sst", "application/vnd.ms-pki.certstore"},
{".stl", "application/vnd.ms-pki.stl"},
{".sv4cpio", "application/x-sv4cpio"},
{".sv4crc", "application/x-sv4crc"},
{".svc", "application/xml"},
{".svg", "image/svg+xml"},
{".swf", "application/x-shockwave-flash"},
{".t", "application/x-troff"},
{".tar", "application/x-tar"},
{".tcl", "application/x-tcl"},
{".testrunconfig", "application/xml"},
{".testsettings", "application/xml"},
{".tex", "application/x-tex"},
{".texi", "application/x-texinfo"},
{".texinfo", "application/x-texinfo"},
{".tgz", "application/x-compressed"},
{".thmx", "application/vnd.ms-officetheme"},
{".thn", "application/octet-stream"},
{".tif", "image/tiff"},
{".tiff", "image/tiff"},
{".tlh", "text/plain"},
{".tli", "text/plain"},
{".toc", "application/octet-stream"},
{".tr", "application/x-troff"},
{".trm", "application/x-msterminal"},
{".trx", "application/xml"},
{".ts", "video/vnd.dlna.mpeg-tts"},
{".tsv", "text/tab-separated-values"},
{".ttf", "application/font-sfnt"},
{".tts", "video/vnd.dlna.mpeg-tts"},
{".txt", "text/plain"},
{".u32", "application/octet-stream"},
{".uls", "text/iuls"},
{".user", "text/plain"},
{".ustar", "application/x-ustar"},
{".vb", "text/plain"},
{".vbdproj", "text/plain"},
{".vbk", "video/mpeg"},
{".vbproj", "text/plain"},
{".vbs", "text/vbscript"},
{".vcf", "text/x-vcard"},
{".vcproj", "application/xml"},
{".vcs", "text/plain"},
{".vcxproj", "application/xml"},
{".vddproj", "text/plain"},
{".vdp", "text/plain"},
{".vdproj", "text/plain"},
{".vdx", "application/vnd.ms-visio.viewer"},
{".vml", "text/xml"},
{".vscontent", "application/xml"},
{".vsct", "text/xml"},
{".vsd", "application/vnd.visio"},
{".vsi", "application/ms-vsi"},
{".vsix", "application/vsix"},
{".vsixlangpack", "text/xml"},
{".vsixmanifest", "text/xml"},
{".vsmdi", "application/xml"},
{".vspscc", "text/plain"},
{".vss", "application/vnd.visio"},
{".vsscc", "text/plain"},
{".vssettings", "text/xml"},
{".vssscc", "text/plain"},
{".vst", "application/vnd.visio"},
{".vstemplate", "text/xml"},
{".vsto", "application/x-ms-vsto"},
{".vsw", "application/vnd.visio"},
{".vsx", "application/vnd.visio"},
{".vtx", "application/vnd.visio"},
{".wav", "audio/wav"},
{".wave", "audio/wav"},
{".wax", "audio/x-ms-wax"},
{".wbk", "application/msword"},
{".wbmp", "image/vnd.wap.wbmp"},
{".wcm", "application/vnd.ms-works"},
{".wdb", "application/vnd.ms-works"},
{".wdp", "image/vnd.ms-photo"},
{".webarchive", "application/x-safari-webarchive"},
{".webm", "video/webm"},
{".webp", "image/webp"}, /* https://en.wikipedia.org/wiki/WebP */
{".webtest", "application/xml"},
{".wiq", "application/xml"},
{".wiz", "application/msword"},
{".wks", "application/vnd.ms-works"},
{".WLMP", "application/wlmoviemaker"},
{".wlpginstall", "application/x-wlpg-detect"},
{".wlpginstall3", "application/x-wlpg3-detect"},
{".wm", "video/x-ms-wm"},
{".wma", "audio/x-ms-wma"},
{".wmd", "application/x-ms-wmd"},
{".wmf", "application/x-msmetafile"},
{".wml", "text/vnd.wap.wml"},
{".wmlc", "application/vnd.wap.wmlc"},
{".wmls", "text/vnd.wap.wmlscript"},
{".wmlsc", "application/vnd.wap.wmlscriptc"},
{".wmp", "video/x-ms-wmp"},
{".wmv", "video/x-ms-wmv"},
{".wmx", "video/x-ms-wmx"},
{".wmz", "application/x-ms-wmz"},
{".woff", "application/font-woff"},
{".wpl", "application/vnd.ms-wpl"},
{".wps", "application/vnd.ms-works"},
{".wri", "application/x-mswrite"},
{".wrl", "x-world/x-vrml"},
{".wrz", "x-world/x-vrml"},
{".wsc", "text/scriptlet"},
{".wsdl", "text/xml"},
{".wvx", "video/x-ms-wvx"},
{".x", "application/directx"},
{".xaf", "x-world/x-vrml"},
{".xaml", "application/xaml+xml"},
{".xap", "application/x-silverlight-app"},
{".xbap", "application/x-ms-xbap"},
{".xbm", "image/x-xbitmap"},
{".xdr", "text/plain"},
{".xht", "application/xhtml+xml"},
{".xhtml", "application/xhtml+xml"},
{".xla", "application/vnd.ms-excel"},
{".xlam", "application/vnd.ms-excel.addin.macroEnabled.12"},
{".xlc", "application/vnd.ms-excel"},
{".xld", "application/vnd.ms-excel"},
{".xlk", "application/vnd.ms-excel"},
{".xll", "application/vnd.ms-excel"},
{".xlm", "application/vnd.ms-excel"},
{".xls", "application/vnd.ms-excel"},
{".xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12"},
{".xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12"},
{".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
{".xlt", "application/vnd.ms-excel"},
{".xltm", "application/vnd.ms-excel.template.macroEnabled.12"},
{".xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template"},
{".xlw", "application/vnd.ms-excel"},
{".xml", "text/xml"},
{".xmta", "application/xml"},
{".xof", "x-world/x-vrml"},
{".XOML", "text/plain"},
{".xpm", "image/x-xpixmap"},
{".xps", "application/vnd.ms-xpsdocument"},
{".xrm-ms", "text/xml"},
{".xsc", "application/xml"},
{".xsd", "text/xml"},
{".xsf", "text/xml"},
{".xsl", "text/xml"},
{".xslt", "text/xml"},
{".xsn", "application/octet-stream"},
{".xss", "application/xml"},
{".xspf", "application/xspf+xml"},
{".xtp", "application/octet-stream"},
{".xwd", "image/x-xwindowdump"},
{".z", "application/x-compress"},
{".zip", "application/zip"},
{"application/fsharp-script", ".fsx"},
{"application/msaccess", ".adp"},
{"application/msword", ".doc"},
{"application/octet-stream", ".bin"},
{"application/onenote", ".one"},
{"application/postscript", ".eps"},
{"application/vnd.ms-excel", ".xls"},
{"application/vnd.ms-powerpoint", ".ppt"},
{"application/vnd.ms-works", ".wks"},
{"application/vnd.visio", ".vsd"},
{"application/x-director", ".dir"},
{"application/x-shockwave-flash", ".swf"},
{"application/x-x509-ca-cert", ".cer"},
{"application/x-zip-compressed", ".zip"},
{"application/xhtml+xml", ".xhtml"},
{"application/xml", ".xml"}, // anomoly, .xml -> text/xml, but application/xml -> many thingss, but all are xml, so safest is .xml
{"audio/aac", ".AAC"},
{"audio/aiff", ".aiff"},
{"audio/basic", ".snd"},
{"audio/mid", ".midi"},
{"audio/wav", ".wav"},
{"audio/x-m4a", ".m4a"},
{"audio/x-mpegurl", ".m3u"},
{"audio/x-pn-realaudio", ".ra"},
{"audio/x-smd", ".smd"},
{"image/bmp", ".bmp"},
{"image/jpeg", ".jpg"},
{"image/pict", ".pic"},
{"image/png", ".png"},
{"image/tiff", ".tiff"},
{"image/x-macpaint", ".mac"},
{"image/x-quicktime", ".qti"},
{"message/rfc822", ".eml"},
{"text/html", ".html"},
{"text/plain", ".txt"},
{"text/scriptlet", ".wsc"},
{"text/xml", ".xml"},
{"video/3gpp", ".3gp"},
{"video/3gpp2", ".3gp2"},
{"video/mp4", ".mp4"},
{"video/mpeg", ".mpg"},
{"video/quicktime", ".mov"},
{"video/vnd.dlna.mpeg-tts", ".m2t"},
{"video/x-dv", ".dv"},
{"video/x-la-asf", ".lsf"},
{"video/x-ms-asf", ".asf"},
{"x-world/x-vrml", ".xof"},
#endregion
};
var cache = mappings.ToList(); // need ToList() to avoid modifying while still enumerating
foreach (var mapping in cache)
{
if (!mappings.ContainsKey(mapping.Value))
{
mappings.Add(mapping.Value, mapping.Key);
}
}
return mappings;
}
public static string GetMimeType(string extension)
{
if (extension == null)
{
throw new ArgumentNullException("extension");
}
if (!extension.StartsWith("."))
{
extension = "." + extension;
}
string mime;
return _mappings.Value.TryGetValue(extension, out mime) ? mime : "application/octet-stream";
}
public static string GetExtension(string mimeType)
{
if (mimeType == null)
{
throw new ArgumentNullException("mimeType");
}
if (mimeType.StartsWith("."))
{
throw new ArgumentException("Requested mime type is not valid: " + mimeType);
}
string extension;
if (_mappings.Value.TryGetValue(mimeType, out extension))
{
return extension;
}
throw new ArgumentException("Requested mime type is not registered: " + mimeType);
}
}
}

425
util/RavenKeyFactory.cs Normal file
View File

@@ -0,0 +1,425 @@
using System;
using System.Text;
using System.Collections.Generic;
using System.IO;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.OpenSsl;
using rockfishCore.Models;
using rockfishCore.Util;
namespace rockfishCore.Util
{
//Key generator controller
public static class RavenKeyFactory
{
//Scheduleable users
private const string SERVICE_TECHS_FEATURE_NAME = "ServiceTechs";
//Accounting add-on
private const string ACCOUNTING_FEATURE_NAME = "Accounting";
//This feature name means it's a trial key
private const string TRIAL_FEATURE_NAME = "TrialMode";
//This feature name means it's a SAAS or rental mode key for month to month hosted service
private const string RENTAL_FEATURE_NAME = "ServiceMode";
#region license classes
//DTO object returned on license query
internal class LicenseFeature
{
//name of feature / product
public string Feature { get; set; }
//Optional count for items that require it
public long Count { get; set; }
}
//DTO object for parsed key
internal class AyaNovaLicenseKey
{
public AyaNovaLicenseKey()
{
Features = new List<LicenseFeature>();
RegisteredTo = "UNLICENSED";
Id = RegisteredTo;
}
public bool IsEmpty
{
get
{
//Key is empty if it's not registered to anyone or there are no features in it
return string.IsNullOrWhiteSpace(RegisteredTo) || (Features == null || Features.Count == 0);
}
}
/// <summary>
/// Fetch the license status of the feature in question
/// </summary>
/// <param name="Feature"></param>
/// <returns>LicenseFeature object or null if there is no license</returns>
public LicenseFeature GetLicenseFeature(string Feature)
{
if (IsEmpty)
return null;
string lFeature = Feature.ToLowerInvariant();
foreach (LicenseFeature l in Features)
{
if (l.Feature.ToLowerInvariant() == lFeature)
{
return l;
}
}
return null;
}
/// <summary>
/// Check for the existance of license feature
/// </summary>
/// <param name="Feature"></param>
/// <returns>bool</returns>
public bool HasLicenseFeature(string Feature)
{
if (IsEmpty)
return false;
string lFeature = Feature.ToLowerInvariant();
foreach (LicenseFeature l in Features)
{
if (l.Feature.ToLowerInvariant() == lFeature)
{
return true;
}
}
return false;
}
public bool WillExpire
{
get
{
return LicenseExpiration < DateUtil.EmptyDateValue;
}
}
public bool LicenseExpired
{
get
{
return LicenseExpiration > DateTime.Now;
}
}
public bool MaintenanceExpired
{
get
{
return MaintenanceExpiration > DateTime.Now;
}
}
public bool TrialLicense
{
get
{
return HasLicenseFeature(TRIAL_FEATURE_NAME);
}
}
public bool RentalLicense
{
get
{
return HasLicenseFeature(RENTAL_FEATURE_NAME);
}
}
public string LicenseFormat { get; set; }
public string Id { get; set; }
public string RegisteredTo { get; set; }
public Guid DbId { get; set; }
public DateTime LicenseExpiration { get; set; }
public DateTime MaintenanceExpiration { get; set; }
public List<LicenseFeature> Features { get; set; }
}
#endregion
#region sample v8 key
// private static string SAMPLE_KEY = @"[KEY
// {
// ""Key"": {
// ""LicenseFormat"": ""2018"",
// ""Id"": ""34-1516288681"", <----Customer id followed by key serial id
// ""RegisteredTo"": ""Super TestCo"",
// ""DBID"": ""df558559-7f8a-4c7b-955c-959ebcdf71f3"",
// ""LicenseExpiration"": ""2019-01-18T07:18:01.2329138-08:00"", <--- UTC,special 1/1/5555 DateTime if perpetual license, applies to all features 1/1/5555 indicates not expiring
// ""MaintenanceExpiration"": ""2019-01-18T07:18:01.2329138-08:00"", <-- UTC, DateTime support and updates subscription runs out, applies to all features
// ""Features"": { <-- deprecate, collection doesn't need to be inside a property?
// ""Feature"": [
// {
// ""Name"": ""ServiceTechs"",
// ""Count"":""10"",
// },
// {
// ""Name"": ""Accounting""
// },
// {
// ""Name"": ""TrialMode""<---means is a trial key
// },
// {
// ""Name"": ""ServiceMode"" <----Means it's an SAAS/Rental key
// }
// ]
// }
// }
// }
// KEY]
// [SIGNATURE
// HEcY3JCVwK9HFXEFnldUEPXP4Q7xoZfMZfOfx1cYmfVF3MVWePyZ9dqVZcY7pk3RmR1BbhQdhpljsYLl+ZLTRhNa54M0EFa/bQnBnbwYZ70EQl8fz8WOczYTEBo7Sm5EyC6gSHtYZu7yRwBvhQzpeMGth5uWnlfPb0dMm0DQM7PaqhdWWW9GCSOdZmFcxkFQ8ERLDZhVMbd8PJKyLvZ+sGMrmYTAIoL0tqa7nrxYkM71uJRTAmQ0gEl4bJdxiV825U1J+buNQuTZdacZKEPSjQQkYou10jRbReUmP2vDpvu+nA1xdJe4b5LlyQL+jiIXH17lf93xlCUb0UkDpu8iNQ==
// SIGNATURE]\";
#endregion
#region RAVEN test code for development
//Trial key magic number for development and testing, all other guids will be fully licensed
private static Guid TEST_TRIAL_KEY_DBID = new Guid("{A6D18A8A-5613-4979-99DA-80D07641A2FE}");
public static string GetRavenTestKey(Guid dbid)
{
//Build a sample test key, sign it and return it
AyaNovaLicenseKey k = new AyaNovaLicenseKey();
k.LicenseFormat = "2018";
var vv = Math.Truncate((DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds);
string sId = vv.ToString();
if (sId.Contains(","))
sId = sId.Split('.')[0];
k.Id = $"00-{sId}";
k.RegisteredTo = "Test Testerson Inc.";
k.DbId = dbid;
//add accounting and user features either way
k.Features.Add(new LicenseFeature() { Feature = ACCOUNTING_FEATURE_NAME, Count = 0 });
k.Features.Add(new LicenseFeature() { Feature = SERVICE_TECHS_FEATURE_NAME, Count = 100 });
//fake trial key or fake licensed key
if (dbid == TEST_TRIAL_KEY_DBID)
{
k.MaintenanceExpiration = k.LicenseExpiration = DateTime.UtcNow.AddMonths(1);
k.Features.Add(new LicenseFeature() { Feature = TRIAL_FEATURE_NAME, Count = 0 });
}
else
{
k.MaintenanceExpiration = DateTime.UtcNow.AddYears(1);
k.LicenseExpiration = DateUtil.EmptyDateValue;//1/1/5555 as per spec
}
return genKey(k);
}
#endregion
/// <summary>
/// New RAVEN key generator, so far just for testing purposes
/// </summary>
/// <returns></returns>
private static string genKey(AyaNovaLicenseKey k)
{
if (string.IsNullOrWhiteSpace(k.RegisteredTo))
throw new ArgumentException("RegisteredTo is required", "RegisteredTo");
try
{
StringBuilder sbKey = new StringBuilder();
StringWriter sw = new StringWriter(sbKey);
using (Newtonsoft.Json.JsonWriter w = new Newtonsoft.Json.JsonTextWriter(sw))
{
w.Formatting = Newtonsoft.Json.Formatting.Indented;
//outer object start
w.WriteStartObject();
w.WritePropertyName("Key");
w.WriteStartObject();//start of key object
w.WritePropertyName("LicenseFormat");
w.WriteValue(k.LicenseFormat);
w.WritePropertyName("Id");
w.WriteValue(k.Id);
w.WritePropertyName("RegisteredTo");
w.WriteValue(k.RegisteredTo);
w.WritePropertyName("DBID");
w.WriteValue(k.DbId);
w.WritePropertyName("LicenseExpiration");
w.WriteValue(k.LicenseExpiration);
w.WritePropertyName("MaintenanceExpiration");
w.WriteValue(k.MaintenanceExpiration);
//FEATURES
// w.WritePropertyName("Features");
// w.WriteStartObject();
w.WritePropertyName("Features");
w.WriteStartArray();
foreach (LicenseFeature lf in k.Features)
{
w.WriteStartObject();
w.WritePropertyName("Name");
w.WriteValue(lf.Feature);
if (lf.Count > 0)
{
w.WritePropertyName("Count");
w.WriteValue(lf.Count);
}
w.WriteEndObject();
}
//end of features array
w.WriteEnd();
//end of features object
// w.WriteEndObject();
//end of AyaNova/AyaNovaLite key object
w.WriteEndObject();
//close outer 'wrapper' object brace }
w.WriteEndObject();
}//end of using statement
// ## CALCULATE SIGNATURE
//GET JSON as a string with whitespace stripped outside of delimited strings
//http://stackoverflow.com/questions/8913138/minify-indented-json-string-in-net
string keyNoWS = System.Text.RegularExpressions.Regex.Replace(sbKey.ToString(), "(\"(?:[^\"\\\\]|\\\\.)*\")|\\s+", "$1");
//**** Note this is our real 2016 private key
var privatePEM = @"-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAz7wrvLDcKVMZ31HFGBnLWL08IodYIV5VJkKy1Z0n2snprhSi
u3izxTyz+SLpftvKHJpky027ii7l/pL9Bo3JcjU5rKrxXavnE7TuYPjXn16dNLd0
K/ERSU+pXLmUaVN0nUWuGuUMoGJMEXoulS6pJiG11yu3BM9fL2Nbj0C6a+UwzEHF
mns3J/daZOb4gAzMUdJfh9OJ0+wRGzR8ZxyC99Na2gDmqYglUkSMjwLTL/HbgwF4
OwmoQYJBcET0Wa6Gfb17SaF8XRBV5ZtpCsbStkthGeoXZkFriB9c1eFQLKpBYQo2
DW3H1MPG2nAlQZLbkJj5cSh7/t1bRF08m6P+EQIDAQABAoIBAQCGvTpxLRXgB/Kk
EtmQBEsMx9EVZEwZeKIqKuDsBP8wvf4/10ql5mhT6kehtK9WhSDW5J2z8DtQKZMs
SBKuCZE77qH2CPp9E17SPWzQoRbaW/gDlWpYhgf8URs89XH5zxO4XtXKw/4omRlV
zLYiNR2pifv0EHqpOAg5KGzewdEo4VgXgtRWpHZLMpH2Q0/5ZIKMhstI6vFHP1p7
jmU4YI6uxiu7rVrZDmIUsAGoTdMabNqK/N8hKaoBiIto0Jn1ck26g+emLg8m160y
Xciu5yFUU+PP1SJMUs+k1UnAWf4p46X9jRLQCBRue9o0Ntiq/75aljRoDvgdwDsR
mg4ZANqxAoGBAPBoM5KoMZ4sv8ZFv8V+V8hgL5xiLgGoiwQl91mRsHRM/NQU5A/w
tH8nmwUrJOrksV7kX9228smKmoliTptyGGyi1NPmSkA7cN9YYnENoOEBHCVNK9vh
P+bkbMYUDNMW4fgOj09oXtQtMl5E2B3OTGoNwZ2w13YQJ8RIniLPsX7nAoGBAN01
eQNcUzQk9YrFGTznOs8udDLBfigDxaNnawvPueulJdBy6ZXDDrKmkQQA7xxl8YPr
dNtBq2lOgnb6+smC15TaAfV/fb8BLmkSwdn4Fy0FApIXIEOnLq+wjkte98nuezl8
9KXDzaqNI9hPuk2i36tJuLLMH8hzldveWbWjSlRHAoGBAKRPE7CQtBjfjNL+qOta
RrT0yJWhpMANabYUHNJi+K8ET2jEPnuGkFa3wwPtUPYaCABLJhprB9Unnid3wTIM
8RSO1ddd9jGgbqy3w9Bw+BvQnmQAMpG9iedNB+r5mSpM4XSgvuIO+4EYwuwbMXpt
nVx+um4Eh75xnDxTRYGVYkLRAoGAaZVpUlpR+HSfooHbPv+bSWKB4ewLPCw4vHrT
VErtEfW8q9b9eRcmP81TMFcFykc6VN4g47pfh58KlKHM7DwAjDLWdohIy89TiKGE
V3acEUfv5y0UoFX+6ara8Ey+9upWdKUY3Lotw3ckoc3EPeQ84DQK7YSSswnAgLaL
mS/8fWcCgYBjRefVbEep161d2DGruk4X7eNI9TFJ278h6ydW5kK9aTJuxkrtKIp4
CYf6emoB4mLXFPvAmnsalkhN2iB29hUZCXXSUjpKZrpijL54Wdu2S6ynm7aT97NF
oArP0E2Vbow3JMxq/oeXmHbrLMLQfYyXwFmciLFigOtkd45bfHdrbA==
-----END RSA PRIVATE KEY-----";
PemReader pr = new PemReader(new StringReader(privatePEM));
AsymmetricCipherKeyPair keys = (AsymmetricCipherKeyPair)pr.ReadObject();
var encoder = new UTF8Encoding(false, true);
var inputData = encoder.GetBytes(keyNoWS);
var signer = SignerUtilities.GetSigner("SHA256WITHRSA");
signer.Init(true, keys.Private);
signer.BlockUpdate(inputData, 0, inputData.Length);
var sign = signer.GenerateSignature();
var signature = Convert.ToBase64String(sign);
System.Text.StringBuilder sbOut = new StringBuilder();
sbOut.AppendLine("[KEY");
sbOut.AppendLine(sbKey.ToString());
sbOut.AppendLine("KEY]");
sbOut.AppendLine("[SIGNATURE");
sbOut.AppendLine(signature);
sbOut.AppendLine("SIGNATURE]");
return sbOut.ToString();
}
catch (Exception ex)
{
return (ex.Message);
}
}
// private static void AddLicensePlugin(Newtonsoft.Json.JsonWriter w, string pluginName, DateTime pluginExpires)
// {
// //this dictionary is used by the additional message code to
// //make the human readable portion of the license
// _plugins.Add(pluginName, pluginExpires);
// //this is adding it to the actual key
// w.WriteStartObject();
// w.WritePropertyName("Item");
// w.WriteValue(pluginName);
// w.WritePropertyName("SubscriptionExpires");
// w.WriteValue(pluginExpires);
// w.WriteEndObject();
// //----------------
// }
//eoc
}
//eons
}

563
util/RfMail.cs Normal file
View File

@@ -0,0 +1,563 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.IO;
using MailKit.Net.Smtp;
using MailKit.Net.Imap;
using MailKit.Search;
using MailKit;
using MimeKit;
namespace rockfishCore.Util
{
//http://www.mimekit.net/
public static class RfMail
{
public const string MAIL_SMPT_ADDRESS = "smtp.ayanova.com";
public const int MAIL_SMPT_PORT = 465;
public const string MAIL_IMAP_ADDRESS = "mail.ayanova.com";
public const int MAIL_IMAP_PORT = 993;
public const string MAIL_ACCOUNT_SUPPORT = "support@ayanova.com";
public const string MAIL_ACCOUNT_PASSWORD_SUPPORT = "e527b6c5a00c27bb61ca694b3de0ee178cbe3f1541a772774762ed48e9caf5ce";
public const string MAIL_ACCOUNT_SALES = "sales@ayanova.com";
public const string MAIL_ACCOUNT_PASSWORD_SALES = "6edbae5eb616a975abf86bcd9f45616f5c70c4f05189af60a1caaa62b406149d";
public enum rfMailAccount
{
support = 1,
sales = 2
}
public class rfMailMessage
{
public MimeMessage message;
public uint uid;
}
class DeliverStatusSmtpClient : SmtpClient
{
protected override DeliveryStatusNotification? GetDeliveryStatusNotifications(MimeMessage message, MailboxAddress mailbox)
{
return DeliveryStatusNotification.Delay |
DeliveryStatusNotification.Failure |
DeliveryStatusNotification.Success;
}
}
/////////////////////////////////////////////////////////
//
// Do the sending with optional deliver status receipt
//
public static void DoSend(MimeMessage message, string MailAccount, string MailAccountPassword, bool trackDeliveryStatus)
{
if (trackDeliveryStatus)
{
//set the return receipt and disposition to headers
message.Headers.Add("Return-Receipt-To", "<" + MailAccount + ">");
message.Headers.Add("Disposition-Notification-To", "<" + MailAccount + ">");
using (var client = new DeliverStatusSmtpClient())
{
//Accept all SSL certificates (in case the server supports STARTTLS)
//(we have a funky cert on the mail server)
client.ServerCertificateValidationCallback = (s, c, h, e) => true;
client.Connect(MAIL_SMPT_ADDRESS, MAIL_SMPT_PORT, true);
// Note: since we don't have an OAuth2 token, disable
// the XOAUTH2 authentication mechanism.
client.AuthenticationMechanisms.Remove("XOAUTH2");
// Note: only needed if the SMTP server requires authentication
client.Authenticate(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT);
client.Send(message);
client.Disconnect(true);
}
}
else
{
using (var client = new SmtpClient())
{
//Accept all SSL certificates (in case the server supports STARTTLS)
//(we have a funky cert on the mail server)
client.ServerCertificateValidationCallback = (s, c, h, e) => true;
client.Connect(MAIL_SMPT_ADDRESS, MAIL_SMPT_PORT, true);
// Note: since we don't have an OAuth2 token, disable
// the XOAUTH2 authentication mechanism.
client.AuthenticationMechanisms.Remove("XOAUTH2");
// Note: only needed if the SMTP server requires authentication
client.Authenticate(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT);
client.Send(message);
client.Disconnect(true);
}
}
}
public static void SendMessage(string MessageFrom, string MessageTo, string MessageSubject, string MessageBody,
bool trackDeliveryStatus = false, string CustomHeader = "", string CustomHeaderValue = "")
{
var message = new MimeMessage();
message.From.Add(new MailboxAddress(MessageFrom));
message.To.Add(new MailboxAddress(MessageTo));
message.Subject = MessageSubject;
message.Body = new TextPart("plain")
{
Text = MessageBody
};
message.Headers["X-Mailer"] = RfVersion.Full;
if (CustomHeader != "" && CustomHeaderValue != "")
{
message.Headers["X-Rockfish-" + CustomHeader] = CustomHeaderValue;
}
//send with optional tracking
DoSend(message, MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT, trackDeliveryStatus);
// using (var client = new SmtpClient())
// {
// //Accept all SSL certificates (in case the server supports STARTTLS)
// //(we have a funky cert on the mail server)
// client.ServerCertificateValidationCallback = (s, c, h, e) => true;
// client.Connect(MAIL_SMPT_ADDRESS, MAIL_SMPT_PORT, true);
// // Note: since we don't have an OAuth2 token, disable
// // the XOAUTH2 authentication mechanism.
// client.AuthenticationMechanisms.Remove("XOAUTH2");
// // Note: only needed if the SMTP server requires authentication
// client.Authenticate(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT);
// client.Send(message);
// client.Disconnect(true);
// }
}//send message
public static void ReplyMessage(uint replyToMessageId, rfMailAccount replyFromAccount, string replyBody,
bool replyToAll, bool trackDeliveryStatus = false, string CustomHeader = "", string CustomHeaderValue = "")
{
//get the original to reply to it:
MimeMessage message = GetMessage(replyToMessageId, replyFromAccount);
if (message == null)
{
throw new System.ArgumentException("RfMail:ReplyMessage->source message not found (id=" + replyToMessageId.ToString() + ")");
}
//construct the new message
var reply = new MimeMessage();
MailboxAddress from = null;
string from_account = "";
string from_account_password = "";
switch (replyFromAccount)
{
case rfMailAccount.sales:
from_account = MAIL_ACCOUNT_SALES;
from_account_password = MAIL_ACCOUNT_PASSWORD_SALES;
from = new MailboxAddress(from_account);
break;
default:
from_account = MAIL_ACCOUNT_SUPPORT;
from_account_password = MAIL_ACCOUNT_PASSWORD_SUPPORT;
from = new MailboxAddress(from_account);
break;
}
reply.From.Add(from);
// reply to the sender of the message
if (message.ReplyTo.Count > 0)
{
reply.To.AddRange(message.ReplyTo);
}
else if (message.From.Count > 0)
{
reply.To.AddRange(message.From);
}
else if (message.Sender != null)
{
reply.To.Add(message.Sender);
}
if (replyToAll)
{
// include all of the other original recipients (removing ourselves from the list)
reply.To.AddRange(message.To.Mailboxes.Where(x => x.Address != from.Address));
reply.Cc.AddRange(message.Cc.Mailboxes.Where(x => x.Address != from.Address));
}
// set the reply subject
if (!message.Subject.StartsWith("Re:", StringComparison.OrdinalIgnoreCase))
reply.Subject = "Re: " + message.Subject;
else
reply.Subject = message.Subject;
// construct the In-Reply-To and References headers
if (!string.IsNullOrEmpty(message.MessageId))
{
reply.InReplyTo = message.MessageId;
foreach (var id in message.References)
reply.References.Add(id);
reply.References.Add(message.MessageId);
}
// quote the original message text
using (var quoted = new StringWriter())
{
var sender = message.Sender ?? message.From.Mailboxes.FirstOrDefault();
var name = sender != null ? (!string.IsNullOrEmpty(sender.Name) ? sender.Name : sender.Address) : "someone";
quoted.WriteLine("On {0}, {1} wrote:", message.Date.ToString("f"), name);
using (var reader = new StringReader(message.TextBody))
{
string line;
while ((line = reader.ReadLine()) != null)
{
quoted.Write("> ");
quoted.WriteLine(line);
}
}
reply.Body = new TextPart("plain")
{
Text = replyBody + "\r\n\r\n\r\n" + quoted.ToString()
};
}
reply.Headers["X-Mailer"] = RfVersion.Full;
if (CustomHeader != "" && CustomHeaderValue != "")
{
reply.Headers["X-Rockfish-" + CustomHeader] = CustomHeaderValue;
}
//send with optional tracking
DoSend(reply, from_account, from_account_password, trackDeliveryStatus);
// using (var client = new SmtpClient())
// {
// //Accept all SSL certificates (in case the server supports STARTTLS)
// //(we have a funky cert on the mail server)
// client.ServerCertificateValidationCallback = (s, c, h, e) => true;
// client.Connect(MAIL_SMPT_ADDRESS, MAIL_SMPT_PORT, true);
// // Note: since we don't have an OAuth2 token, disable
// // the XOAUTH2 authentication mechanism.
// client.AuthenticationMechanisms.Remove("XOAUTH2");
// // Note: only needed if the SMTP server requires authentication
// client.Authenticate(from_account, from_account_password);
// client.Send(reply);
// client.Disconnect(true);
// }
//flag the message as having been replied to
FlagInboxMessageSeenReplied(replyToMessageId, replyFromAccount);
}//send message
//Fetch message by UID
public static MimeMessage GetMessage(uint uid, rfMailAccount fromAccount = rfMailAccount.support)
{
using (var client = new ImapClient())
{
// Accept all SSL certificates
client.ServerCertificateValidationCallback = (s, c, h, e) => true;
client.Connect(MAIL_IMAP_ADDRESS, MAIL_IMAP_PORT, true);
// Note: since we don't have an OAuth2 token, disable
// the XOAUTH2 authentication mechanism.
client.AuthenticationMechanisms.Remove("XOAUTH2");
if (fromAccount == rfMailAccount.support)
{
client.Authenticate(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT);
}
else
{
client.Authenticate(MAIL_ACCOUNT_SALES, MAIL_ACCOUNT_PASSWORD_SALES);
}
var inbox = client.Inbox;
inbox.Open(FolderAccess.ReadOnly);
var m = inbox.GetMessage(new UniqueId(uid));
client.Disconnect(true);
return m;
}
}//get message
//Fetch messages by Search query
public static List<rfMailMessage> GetMessages(SearchQuery query)
{
List<rfMailMessage> ret = new List<rfMailMessage>();
using (var client = new ImapClient())
{
// Accept all SSL certificates
client.ServerCertificateValidationCallback = (s, c, h, e) => true;
client.Connect(MAIL_IMAP_ADDRESS, MAIL_IMAP_PORT, true);
// Note: since we don't have an OAuth2 token, disable
// the XOAUTH2 authentication mechanism.
client.AuthenticationMechanisms.Remove("XOAUTH2");
client.Authenticate(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT);
var inbox = client.Inbox;
inbox.Open(FolderAccess.ReadOnly);
foreach (var uid in inbox.Search(query))
{
ret.Add(new rfMailMessage { message = inbox.GetMessage(uid), uid = uid.Id });
}
client.Disconnect(true);
}
return ret;
}//get message
//Flag message as seen and replied by UID
public static bool FlagInboxMessageSeenReplied(uint uid, rfMailAccount inAccount = rfMailAccount.support)
{
using (var client = new ImapClient())
{
// Accept all SSL certificates
client.ServerCertificateValidationCallback = (s, c, h, e) => true;
client.Connect(MAIL_IMAP_ADDRESS, MAIL_IMAP_PORT, true);
// Note: since we don't have an OAuth2 token, disable
// the XOAUTH2 authentication mechanism.
client.AuthenticationMechanisms.Remove("XOAUTH2");
if (inAccount == rfMailAccount.support)
{
client.Authenticate(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT);
}
else
{
client.Authenticate(MAIL_ACCOUNT_SALES, MAIL_ACCOUNT_PASSWORD_SALES);
}
var inbox = client.Inbox;
inbox.Open(FolderAccess.ReadWrite);
inbox.AddFlags(new UniqueId(uid), MessageFlags.Seen, true);
inbox.AddFlags(new UniqueId(uid), MessageFlags.Answered, true);
client.Disconnect(true);
return true;
}
}//get message
////////////////////////////////////////////////////
//Put a message in the drafts folder of support
//
public static void DraftMessage(string MessageFrom, string MessageTo, string MessageSubject, string MessageBody,
string CustomHeader = "", string CustomHeaderValue = "")
{
var message = new MimeMessage();
message.From.Add(new MailboxAddress(MessageFrom));
//case 3512 handle more than one email in the address
if (MessageTo.Contains(","))
{
List<MailboxAddress> mbAll = new List<MailboxAddress>();
var addrs = MessageTo.Split(',');
foreach (string addr in addrs)
{
mbAll.Add(new MailboxAddress(addr.Trim()));
}
message.To.AddRange(mbAll);
}
else
{
message.To.Add(new MailboxAddress(MessageTo));
}
message.Subject = MessageSubject;
message.Body = new TextPart("plain")
{
Text = MessageBody
};
if (CustomHeader != "" && CustomHeaderValue != "")
{
message.Headers["X-Rockfish-" + CustomHeader] = CustomHeaderValue;
}
//adapted from https://stackoverflow.com/questions/33365072/mailkit-sending-drafts
using (var client = new ImapClient())
{
try
{
// Accept all SSL certificates
client.ServerCertificateValidationCallback = (s, c, h, e) => true;
client.Connect(MAIL_IMAP_ADDRESS, MAIL_IMAP_PORT, true);
// Note: since we don't have an OAuth2 token, disable
// the XOAUTH2 authentication mechanism.
client.AuthenticationMechanisms.Remove("XOAUTH2");
client.Authenticate(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT);
var draftFolder = client.GetFolder("Drafts");//Our surgemail server works with this other servers in future might not
if (draftFolder != null)
{
draftFolder.Open(FolderAccess.ReadWrite);
draftFolder.Append(message, MessageFlags.Draft);
//draftFolder.Expunge();
}
}
catch (Exception ex)
{
throw new System.Exception("RfMail->DraftMessage() - Exception has occured: " + ex.Message);
}
client.Disconnect(true);
}
}//draft message
/////////////////////////////////////////////////////////////////
//Fetch summaries of unread messages in sales and support
//inboxes
//
public static List<rfMessageSummary> GetSalesAndSupportSummaries()
{
List<rfMessageSummary> ret = new List<rfMessageSummary>();
ret.AddRange(getInboxSummariesFor(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT));
ret.AddRange(getInboxSummariesFor(MAIL_ACCOUNT_SALES, MAIL_ACCOUNT_PASSWORD_SALES));
return ret;
}
private static List<rfMessageSummary> getInboxSummariesFor(string sourceAccount, string sourcePassword)
{
List<rfMessageSummary> ret = new List<rfMessageSummary>();
using (var client = new ImapClient())
{
// Accept all SSL certificates
client.ServerCertificateValidationCallback = (s, c, h, e) => true;
client.Connect(MAIL_IMAP_ADDRESS, MAIL_IMAP_PORT, true);
// Note: since we don't have an OAuth2 token, disable
// the XOAUTH2 authentication mechanism.
client.AuthenticationMechanisms.Remove("XOAUTH2");
client.Authenticate(sourceAccount, sourcePassword);
var inbox = client.Inbox;
inbox.Open(FolderAccess.ReadOnly);
var summaries = inbox.Fetch(0, -1, MessageSummaryItems.Full | MessageSummaryItems.UniqueId);
client.Disconnect(true);
foreach (var summary in summaries)
{
//Sometimes bad hombres don't set a from address so don't expect one
string sFrom = "UNKNOWN / NOT SET";
if (summary.Envelope.From.Count > 0)
{
sFrom = summary.Envelope.From[0].ToString().Replace("\"", "").Replace("<", "").Replace(">", "").Trim();
}
ret.Add(new rfMessageSummary
{
account = sourceAccount,
id = summary.UniqueId.Id,
subject = summary.Envelope.Subject,
sent = DateUtil.DateTimeOffSetNullableToEpoch(summary.Envelope.Date),
from = sFrom,
flags = summary.Flags.ToString().ToLowerInvariant()
});
}
}
//reverse the results array as emails come in oldest first order but we want oldest last
ret.Reverse();
return ret;
}
public class rfMessageSummary
{
public string account;
public uint id;
public string from;
public string subject;
public long? sent;
public string flags;
}
//Fetch a single string preview of message by Account / folder / UID
public static rfMessagePreview GetMessagePreview(string mailAccount, string mailFolder, uint uid)
{
using (var client = new ImapClient())
{
// Accept all SSL certificates
client.ServerCertificateValidationCallback = (s, c, h, e) => true;
client.Connect(MAIL_IMAP_ADDRESS, MAIL_IMAP_PORT, true);
// Note: since we don't have an OAuth2 token, disable
// the XOAUTH2 authentication mechanism.
client.AuthenticationMechanisms.Remove("XOAUTH2");
//TODO: make accounts reside in dictionary in future
if (mailAccount == "support@ayanova.com")
{
client.Authenticate(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT);
}
if (mailAccount == "sales@ayanova.com")
{
client.Authenticate(MAIL_ACCOUNT_SALES, MAIL_ACCOUNT_PASSWORD_SALES);
}
var fldr = client.GetFolder(mailFolder);
fldr.Open(FolderAccess.ReadOnly);
var m = fldr.GetMessage(new UniqueId(uid));
client.Disconnect(true);
StringBuilder sb = new StringBuilder();
sb.Append("From: ");
sb.AppendLine(m.From[0].ToString().Replace("\"", "").Replace("<", "").Replace(">", "").Trim());
sb.Append("To: ");
sb.AppendLine(mailAccount);
sb.Append("Subject: ");
sb.AppendLine(m.Subject);
sb.AppendLine();
sb.AppendLine();
sb.AppendLine(m.GetTextBody(MimeKit.Text.TextFormat.Plain));
rfMessagePreview preview = new rfMessagePreview();
preview.id = uid;
preview.preview = sb.ToString();
preview.isKeyRequest = m.Subject.StartsWith("Request for 30 day temporary");
return preview;
}
}//get message
public class rfMessagePreview
{
public bool isKeyRequest;
public string preview;
public uint id;
}
//----------------
}//eoc
}//eons

128
util/RfNotify.cs Normal file
View File

@@ -0,0 +1,128 @@
using System;
using System.Text;
using System.Text.RegularExpressions;//now there's at least 'two problems' :)
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using rockfishCore.Models;
namespace rockfishCore.Util
{
/*
This class handles formatting and sending various notifications
*/
public static class RfNotify
{
//template key magic strings
public const string KEY_SUB_RENEW_NOTICE_TEMPLATE = "SubRenewNotice";
///////////////////////////////////////////////////////////
//SUBSCRIPTION RENEWAL NOTICE
//
public static object SendSubscriptionRenewalNotice(rockfishContext ct, List<long> purchaseidlist)
{
try
{
var template = ct.TextTemplate.SingleOrDefault(m => m.Name == KEY_SUB_RENEW_NOTICE_TEMPLATE);
if (template == null)
{
return RfResponse.fail("couldn't find template SubRenewNotice");
}
//parse out tokens
//we expect TITLE and BODY tokens here
Dictionary<string, string> tempTokens = parseTemplateTokens(template.Template);
//get a list of all the products
var products = ct.Product.ToList();
//Get a list of all purchases that are in the list of purchase id's
var purchases = ct.Purchase.Where(c => purchaseidlist.Contains(c.Id)).ToList();
//rf6
// string emailTO = purchases[0].Email;
var cust = ct.Customer.AsNoTracking().First(r => r.Id == purchases[0].CustomerId);
string emailTO = cust.AdminEmail;
//get company name
//rf6
// string companyName = ct.Customer.Select(r => new { r.Id, r.Name })
// .Where(r => r.Id == purchases[0].CustomerId)
// .First().Name;
string companyName=cust.Name;
//TAGS EXPECTED: {{BODY=}}, {{TITLE=}}
//REPLACEMENT TOKENS EXPECTED: {{SUBLIST}}
StringBuilder sublist = new StringBuilder();
foreach (Purchase pc in purchases)
{
var pr = products.Where(p => p.ProductCode == pc.ProductCode).First();
decimal dRenew = Convert.ToDecimal(pr.RenewPrice) / 100m;
decimal dMonthly = Convert.ToDecimal(pr.RenewPrice) / 1200m;
string sRenew = String.Format("{0:C}", dRenew);
string sMonthly = String.Format("{0:C}", dMonthly);
string sRenewDate = DateUtil.EpochToDate(pc.ExpireDate).ToString("D");
sublist.Append("\t- ");
sublist.Append(pr.Name);
sublist.Append(" will renew on ");
sublist.Append(sRenewDate);
sublist.Append(" at US");
sublist.Append(sRenew);
sublist.Append(" for the year + taxes (which works out to only ");
sublist.Append(sMonthly);
sublist.Append(" per month)");
sublist.AppendLine();
}
string emailFrom = "support@ayanova.com";
string emailSubject = companyName + " " + tempTokens["TITLE"];
string emailBody = tempTokens["BODY"].Replace("<<SUBLIST>>", sublist.ToString());
//put email in drafts
RfMail.DraftMessage(emailFrom, emailTO, emailSubject, emailBody, "MsgType", "SubRenewNotice");
//tag purchase as notified
foreach (Purchase pc in purchases)
{
pc.RenewNoticeSent = true;
}
ct.SaveChanges();
}
catch (Exception ex)
{
return RfResponse.fail(ex.Message);
}
return RfResponse.ok();
}
///////////////////////////////////////////////////////////
//parse out all tokens and values in the template
private static Dictionary<string, string> parseTemplateTokens(string src)
{
Dictionary<string, string> ret = new Dictionary<string, string>();
var roughmatches = Regex.Matches(src, @"{{.*?}}", RegexOptions.Singleline);
foreach (Match roughmatch in roughmatches)
{
//capture the token name which is a regex group between {{ and =
var token = Regex.Match(roughmatch.Value, @"{{(.*?)=", RegexOptions.Singleline).Groups[1].Value;
//capture the token value which is a regex group between {{TOKENNAME= and }}
var tokenValue = Regex.Match(roughmatch.Value, @"{{" + token + "=(.*?)}}", RegexOptions.Singleline).Groups[1].Value;
ret.Add(token, tokenValue);
}
return ret;
}
//eoc
}
//eons
}

21
util/RfResponse.cs Normal file
View File

@@ -0,0 +1,21 @@
using System;
using System.Text;
using Microsoft.AspNetCore.Mvc;
namespace rockfishCore.Util
{
public static class RfResponse
{
public static object fail(string msg)
{
return new { error = 1, msg = msg };
}
public static object ok()
{
return new { ok = 1 };
}
}
}

389
util/RfSchema.cs Normal file
View File

@@ -0,0 +1,389 @@
using System;
using System.Text;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using rockfishCore.Models;
namespace rockfishCore.Util
{
//Key generator controller
public static class RfSchema
{
private static rockfishContext ctx;
/////////////////////////////////////////////////////////////////
/////////// CHANGE THIS ON NEW SCHEMA UPDATE ////////////////////
public const int DESIRED_SCHEMA_LEVEL = 15;
/////////////////////////////////////////////////////////////////
static int startingSchema = -1;
static int currentSchema = -1;
//check and update schema
public static void CheckAndUpdate(rockfishContext context)
{
ctx = context;
bool rfSetExists = false;
//update schema here?
using (var command = ctx.Database.GetDbConnection().CreateCommand())
{
//first of all, do we have a schema table yet (v0?)
command.CommandText = "SELECT name FROM sqlite_master WHERE type='table' AND name='rfset';";
ctx.Database.OpenConnection();
using (var result = command.ExecuteReader())
{
if (result.HasRows)
{
rfSetExists = true;
}
ctx.Database.CloseConnection();
}
}
//Create schema table (v1)
if (!rfSetExists)
{
//nope, no schema table, add it now and set to v1
using (var cmCreateRfSet = ctx.Database.GetDbConnection().CreateCommand())
{
context.Database.OpenConnection();
//first of all, do we have a schema table yet (v0?)
cmCreateRfSet.CommandText = "CREATE TABLE rfset (id INTEGER PRIMARY KEY, schema INTEGER NOT NULL);";
cmCreateRfSet.ExecuteNonQuery();
cmCreateRfSet.CommandText = "insert into rfset (schema) values (1);";
cmCreateRfSet.ExecuteNonQuery();
context.Database.CloseConnection();
startingSchema = 1;
currentSchema = 1;
}
}
else
{
//get current schema level
using (var cm = ctx.Database.GetDbConnection().CreateCommand())
{
cm.CommandText = "SELECT schema FROM rfset WHERE id=1;";
ctx.Database.OpenConnection();
using (var result = cm.ExecuteReader())
{
if (result.HasRows)
{
result.Read();
currentSchema = startingSchema = result.GetInt32(0);
ctx.Database.CloseConnection();
}
else
{
ctx.Database.CloseConnection();
throw new System.Exception("rockfish->RfSchema->CheckAndUpdate: Error reading schema version");
}
}
}
}
//Bail early no update?
if (currentSchema == DESIRED_SCHEMA_LEVEL)
return;
//************* SCHEMA UPDATES ******************
//////////////////////////////////////////////////
//schema 2 case 3283
if (currentSchema < 2)
{
//add renewal flag to purchase
exec("alter table purchase add renewNoticeSent boolean default 0 NOT NULL CHECK (renewNoticeSent IN (0,1))");
//Add product table with prices
exec("CREATE TABLE product (id INTEGER PRIMARY KEY, name text not null, productCode text, price integer, renewPrice integer )");
currentSchema = 2;
setSchemaLevel(currentSchema);
}
//////////////////////////////////////////////////
//schema 3 case 3283
if (currentSchema < 3)
{
//add products current as of 2017-July-18
exec("insert into product (name, productCode, price, renewPrice) values ('Key administration','300093112',3500, 3500);");
exec("insert into product (name, productCode, price, renewPrice) values ('Custom work','300151145',0, 0);");
exec("insert into product (name, productCode, price, renewPrice) values ('AyaNova RI sub (old / unused?)','300740314',19900, 6965);");
exec("insert into product (name, productCode, price, renewPrice) values ('Single AyaNova schedulable resource 1 year subscription license','300740315',15900, 5565);");
exec("insert into product (name, productCode, price, renewPrice) values ('Single AyaNova Lite 1 year subscription license','300740316',6900, 2415);");
exec("insert into product (name, productCode, price, renewPrice) values ('Up to 5 AyaNova schedulable resource 1 year subscription license','300740317', 69500, 24325);");
exec("insert into product (name, productCode, price, renewPrice) values ('Up to 10 AyaNova schedulable resource 1 year subscription license','300740318',119000, 41650);");
exec("insert into product (name, productCode, price, renewPrice) values ('Up to 20 AyaNova schedulable resource 1 year subscription license','300740319',198000, 69300);");
exec("insert into product (name, productCode, price, renewPrice) values ('AyaNova WBI (web browser interface) 1 year subscription license','300740321',9900, 3465);");
exec("insert into product (name, productCode, price, renewPrice) values ('AyaNova MBI (minimal browser interface) 1 year subscription license','300740322', 9900, 3465);");
exec("insert into product (name, productCode, price, renewPrice) values ('AyaNova QBI(QuickBooks interface) 1 year subscription license','300740323',9900, 3465);");
exec("insert into product (name, productCode, price, renewPrice) values ('AyaNova PTI(US Peachtree/Sage 50 interface) 1 year subscription license','300740324',9900, 3465);");
exec("insert into product (name, productCode, price, renewPrice) values ('AyaNova OLI(Outlook interface) 1 year subscription license','300740325',9900, 3465);");
exec("insert into product (name, productCode, price, renewPrice) values ('Plug-in Outlook Schedule Export 1 year subscription license','300740326',1900, 665);");
exec("insert into product (name, productCode, price, renewPrice) values ('Plug-in Export to XLS 1 year subscription license','300740327',1900, 665);");
exec("insert into product (name, productCode, price, renewPrice) values ('Plug-in Quick Notification 1 year subscription license','300740328',1900, 665);");
exec("insert into product (name, productCode, price, renewPrice) values ('Plug-in importexport.csv duplicate 1 year subscription license','300740329',1900, 665);");
exec("insert into product (name, productCode, price, renewPrice) values ('Up to 999 AyaNova schedulable resource 1 year subscription license','300741264',15000, 5250);");
currentSchema = 3;
setSchemaLevel(currentSchema);
}
//////////////////////////////////////////////////
//schema 4 case 3283
if (currentSchema < 4)
{
//Add template table to store email and other templates
exec("CREATE TABLE texttemplate (id INTEGER PRIMARY KEY, name text not null, template text)");
currentSchema = 4;
setSchemaLevel(currentSchema);
}
//////////////////////////////////////////////////
//schema 5 case 3253
if (currentSchema < 5)
{
exec("alter table customer add active boolean default 1 NOT NULL CHECK (active IN (0,1))");
currentSchema = 5;
setSchemaLevel(currentSchema);
}
//////////////////////////////////////////////////
//schema 6 case 3308
if (currentSchema < 6)
{
exec("CREATE TABLE rfcaseproject (id INTEGER PRIMARY KEY, name text not null)");
exec("CREATE TABLE rfcase (id INTEGER PRIMARY KEY, title text not null, rfcaseprojectid integer not null, " +
"priority integer default 3 NOT NULL CHECK (priority IN (1,2,3,4,5)), notes text, dtcreated integer, dtclosed integer, " +
"releaseversion text, releasenotes text, " +
"FOREIGN KEY (rfcaseprojectid) REFERENCES rfcaseproject(id))");
exec("CREATE TABLE rfcaseblob (id INTEGER PRIMARY KEY, rfcaseid integer not null, name text not null, file blob not null, " +
"FOREIGN KEY (rfcaseid) REFERENCES rfcase(id) ON DELETE CASCADE )");
currentSchema = 6;
setSchemaLevel(currentSchema);
}
//////////////////////////////////////////////////
//schema 7 case 3308
if (currentSchema < 7)
{
//empty any prior import data
exec("delete from rfcaseblob");
exec("delete from rfcase");
exec("delete from rfcaseproject");
//Trigger import of all fogbugz cases into rockfish
Util.FBImporter.Import(ctx);
//now get rid of the delete records
exec("delete from rfcase where title='deleted from FogBugz'");
currentSchema = 7;//<<-------------------- TESTING, CHANGE TO 7 BEFORE PRODUCTION
setSchemaLevel(currentSchema);
}
//////////////////////////////////////////////////
//schema 8
if (currentSchema < 8)
{
exec("alter table user add dlkey text");
currentSchema = 8;
setSchemaLevel(currentSchema);
}
//////////////////////////////////////////////////
//schema 9
if (currentSchema < 9)
{
exec("alter table user add dlkeyexp integer");
currentSchema = 9;
setSchemaLevel(currentSchema);
}
//////////////////////////////////////////////////
//schema 10 case 3233
if (currentSchema < 10)
{
exec("CREATE TABLE license (" +
"id INTEGER PRIMARY KEY, dtcreated integer not null, customerid integer not null, regto text not null, key text not null, code text not null, email text not null, " +
"fetchfrom text, dtfetched integer, fetched boolean default 0 NOT NULL CHECK (fetched IN (0,1))" +
")");
currentSchema = 10;
setSchemaLevel(currentSchema);
}
//////////////////////////////////////////////////
//schema 11 case 3550
if (currentSchema < 11)
{
//add 15 level product code
exec("insert into product (name, productCode, price, renewPrice) values ('Up to 15 AyaNova schedulable resource 1 year subscription license','300807973',165000, 57750);");
currentSchema = 11;
setSchemaLevel(currentSchema);
}
//////////////////////////////////////////////////
//schema 12
if (currentSchema < 12)
{
exec("alter table customer add supportEmail text");
exec("alter table customer add adminEmail text");
currentSchema = 12;
setSchemaLevel(currentSchema);
}
//////////////////////////////////////////////////
//schema 13
if (currentSchema < 13)
{
//Put all the purchase emails into the customer adminEmail field as CSV
//These will get all notification types
foreach (var purchase in ctx.Purchase.AsNoTracking())
{
if (!string.IsNullOrWhiteSpace(purchase.Email))
{
var cust = ctx.Customer.SingleOrDefault(m => m.Id == purchase.CustomerId);
if (cust == null)
{
throw new ArgumentNullException($"RFSCHEMA UPDATE 13 (purchases) CUSTOMER {purchase.CustomerId.ToString()} not found!!");
}
var purchaseEmails = purchase.Email.Split(",");
foreach (var email in purchaseEmails)
{
Util.CustomerUtils.AddAdminEmailIfNotPresent(cust, email);
}
ctx.SaveChanges();
}
}
// //Put all the contact emails into the customer adminEmail field and the support field as CSV
// //These will get all notification types
// foreach (var contact in ctx.Contact.AsNoTracking())
// {
// if (!string.IsNullOrWhiteSpace(contact.Email))
// {
// var cust = ctx.Customer.SingleOrDefault(m => m.Id == contact.CustomerId);
// if (cust == null)
// {
// throw new ArgumentNullException($"RFSCHEMA UPDATE 13 (contacts) CUSTOMER {contact.CustomerId.ToString()} not found!!");
// }
// var contactEmails = contact.Email.Split(",");
// foreach (var email in contactEmails)
// {
// Util.CustomerUtils.AddAdminEmailIfNotPresent(cust, email);
// }
// ctx.SaveChanges();
// }
// }
// //Put all the incident emails into the customer supportEmail field unless already in teh admin field or support field field and the support field as CSV
// //These will get only support related (updates/bug reports) notification types
// foreach (var incident in ctx.Incident.AsNoTracking())
// {
// if (!string.IsNullOrWhiteSpace(incident.Email))
// {
// var cust = ctx.Customer.SingleOrDefault(m => m.Id == incident.CustomerId);
// if (cust == null)
// {
// throw new ArgumentNullException($"RFSCHEMA UPDATE 13 (incidents) CUSTOMER {incident.CustomerId.ToString()} not found!!");
// }
// var incidentEmails = incident.Email.Split(",");
// foreach (var email in incidentEmails)
// {
// //See if incident email is already in adminEmail field:
// if (cust.AdminEmail != null && cust.AdminEmail.ToLowerInvariant().Contains(email.Trim().ToLowerInvariant()))
// {
// continue;//skip this one, it's already there
// }
// //It's not in the adminEmail field already so add it to the supportEmail
// //field (assumption: all incidents are support related and particularly ones that are not already in admin)
// Util.CustomerUtils.AddSupportEmailIfNotPresent(cust, email);
// }
// ctx.SaveChanges();
// }
// }
//NOTE: NOTIFICATION AND TRIAL tables have emails but they are dupes, empty or not required based on actual data so not going to import them
currentSchema = 13;
setSchemaLevel(currentSchema);
}
//////////////////////////////////////////////////
//schema 14
if (currentSchema < 14)
{
exec("update license set fetchfrom = 'redacted'");
currentSchema = 14;
setSchemaLevel(currentSchema);
}
//////////////////////////////////////////////////
//schema 15
if (currentSchema < 15)
{
exec("drop table contact");
exec("drop table incident");
exec("drop table notification");
exec("drop table trial");
currentSchema = 15;
setSchemaLevel(currentSchema);
}
//*************************************************************************************
}//eofunction
private static void setSchemaLevel(int nCurrentSchema)
{
exec("UPDATE RFSET SET schema=" + nCurrentSchema.ToString());
}
//execute command query
private static void exec(string q)
{
using (var cmCreateRfSet = ctx.Database.GetDbConnection().CreateCommand())
{
ctx.Database.OpenConnection();
cmCreateRfSet.CommandText = q;
cmCreateRfSet.ExecuteNonQuery();
ctx.Database.CloseConnection();
}
}
//eoclass
}
//eons
}

8
util/RfVersion.cs Normal file
View File

@@ -0,0 +1,8 @@
namespace rockfishCore.Util
{
public static class RfVersion
{
public const string NumberOnly="6.3";
public const string Full = "Rockfish server " + NumberOnly;
}
}

View File

@@ -0,0 +1,183 @@
using System;
using System.Text;
using System.Collections.Generic;
using rockfishCore.Models;
using rockfishCore.Util;
using MailKit.Net.Imap;
using MailKit.Search;
using MailKit;
using MimeKit;
namespace rockfishCore.Util
{
//Trial Key request handler
public static class TrialKeyRequestHandler
{
//non anonymous return object for array
//https://stackoverflow.com/a/3202396
private class retObject
{
public string from;
public string subject;
public string date;
public uint uid;
}
//MAILKIT DOCS
//https://github.com/jstedfast/MailKit#using-mailkit
public static dynamic Requests()
{
try
{
List<retObject> ret = new List<retObject>();
List<RfMail.rfMailMessage> msgs = RfMail.GetMessages(SearchQuery.SubjectContains("Request for 30 day temporary").And(SearchQuery.NotAnswered).And(SearchQuery.NotDeleted));
foreach (RfMail.rfMailMessage m in msgs)
{
ret.Add(new retObject()
{
uid = m.uid,
subject = m.message.Subject,
date = m.message.Date.LocalDateTime.ToString("g"),
from = m.message.From.ToString()
});
}
return new { ok = 1, requests = ret };
}
catch (Exception e)
{
return new
{
error = 1,
msg = "Error @ TrialKeyRequestHandler->Requests()",
error_detail = new { message = e.Message, stack = e.StackTrace }
};
}
}//requests
public static dynamic GenerateFromRequest(uint uid, LicenseTemplates licenseTemplates, string authUser, rockfishContext ct)
{
try
{
using (var client = new ImapClient())
{
var m = RfMail.GetMessage(uid);
string greetingReplySubject = "re: " + m.Subject;
//Extract request message fields
string replyTo = m.From.ToString();
//get request message body
string request = m.TextBody;
//Parse request message for deets
if (string.IsNullOrWhiteSpace(request))
{
throw new System.NotSupportedException("TrialKeyRequestHandler->GenerateFromRequest: error text body of request email is empty");
}
bool bLite = m.Subject.Contains("AyaNova Lite");
int nNameStart = request.IndexOf("Name:\r\n") + 7;
int nNameEnd = request.IndexOf("Company:\r\n");
int nCompanyStart = nNameEnd + 10;
int nCompanyEnd = request.IndexOf("Referrer:\r\n");
string sName = request.Substring(nNameStart, nNameEnd - nNameStart).Trim();
if (sName.Contains(" "))
sName = sName.Split(' ')[0];
string sRegTo = request.Substring(nCompanyStart, nCompanyEnd - nCompanyStart).Trim();
//make keycode
string keyCode = KeyFactory.GetTrialKey(sRegTo, bLite, licenseTemplates, authUser, replyTo, ct);//case 3233
//get greeting and subject
string greeting = string.Empty;
string keyEmailSubject = string.Empty;
if (bLite)
{
//No lite trial greeting but text looks ok to just use full trial greeting so going with that
//(the column is in the DB but there is no UI for it and it's null)
//greeting = licenseTemplates.LiteTrialGreeting.Replace("[FirstName]", sName);
greeting = licenseTemplates.FullTrialGreeting.Replace("[FirstName]", sName);
keyEmailSubject = "AyaNova Lite and all add-ons temporary 30 day Activation key";
}
else
{
greeting = licenseTemplates.FullTrialGreeting.Replace("[FirstName]", sName);
keyEmailSubject = "AyaNova and all add-on's temporary 30 day Activation key";
}
return new
{
ok = 1,
uid = uid,
requestReplyToAddress = replyTo,
requestFromReplySubject = keyEmailSubject,
requestKeyIsLite = bLite,
keycode = keyCode,
greeting = greeting,
greetingReplySubject = greetingReplySubject,
request = request
};
}
}
catch (Exception e)
{
return new
{
error = 1,
ok = 0,
msg = "Error @ TrialKeyRequestHandler->GenerateFromRequest()",
error_detail = new { message = e.Message, stack = e.StackTrace }
};
}
}//requests
public static dynamic SendTrialRequestResponse(dtoKeyRequestResponse k)
{
try
{
//-----------------
//Send both responses
//Send the greeting email
RfMail.SendMessage("support@ayanova.com", k.requestReplyToAddress, k.greetingReplySubject, k.greeting, true, "MsgType", "TrialRequestGreet");
//Send the license key email
RfMail.SendMessage("support@ayanova.com", k.requestReplyToAddress, k.requestFromReplySubject, k.keycode, true, "MsgType", "TrialRequestKey");
// Flag original request message as read and replied
RfMail.FlagInboxMessageSeenReplied(k.request_email_uid);
//------------------
return new { ok = 1 };
}
catch (Exception e)
{
return new
{
error = 1,
msg = "Error @ TrialKeyRequestHandler->SendTrialRequestResponse()",
error_detail = new { message = e.Message, stack = e.StackTrace }
};
}
}//requests
}//eoc
}//eons

35
util/fetchkeycode.cs Normal file
View File

@@ -0,0 +1,35 @@
using System;
namespace rockfishCore.Util
{
//Generate a random code for license key fetching
//doesn't have to be perfect, it's only temporary and
//requires knowledge of the customer / trial user
//email address to use it so it's kind of 2 factor
public static class FetchKeyCode
{
public static string generate()
{
//sufficient for this purpose
//https://stackoverflow.com/a/1344258/8939
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var stringChars = new char[10];
var random = new Random();
for (int i = 0; i < stringChars.Length; i++)
{
stringChars[i] = chars[random.Next(chars.Length)];
}
var finalString = new String(stringChars);
return finalString;
}
}//eoc
}//eons

38
util/hasher.cs Normal file
View File

@@ -0,0 +1,38 @@
using System;
using System.Security.Cryptography;
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
namespace rockfishCore.Util
{
//Authentication controller
public static class Hasher
{
public static string hash(string Salt, string Password)
{
//adapted from here:
//https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/consumer-apis/password-hashing
string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
password: Password,
salt: StringToByteArray(Salt),
prf: KeyDerivationPrf.HMACSHA512,
iterationCount: 10000,
numBytesRequested: 512 / 8));
return hashed;
}
//https://stackoverflow.com/questions/311165/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa/24343727#24343727
public static byte[] StringToByteArray(String hex)
{
int NumberChars = hex.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
}//eoc
}//eons

40
util/rfLogger.cs Normal file
View File

@@ -0,0 +1,40 @@
using Microsoft.Extensions.Logging;
using System;
using System.IO;
namespace rockfishCore.Util
{
//from a comment here: https://github.com/aspnet/EntityFramework/issues/6482
public class rfLoggerProvider : ILoggerProvider
{
public ILogger CreateLogger(string categoryName)
{
return new rfLogger();
}
public void Dispose()
{ }
private class rfLogger : ILogger
{
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
// File.AppendAllText(@"log.txt", formatter(state, exception));
Console.WriteLine("------------------------------------------------------------");
Console.WriteLine(formatter(state, exception));
Console.WriteLine("------------------------------------------------------------");
}
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
}
}
}