Skip to content

Commit 35e9961

Browse files
authored
Merge pull request #793 from bcgov/yj
Yj
2 parents 30850f6 + 6ee7bea commit 35e9961

File tree

10 files changed

+115
-22
lines changed

10 files changed

+115
-22
lines changed

server/StrDss.Common/CommonUtils.cs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using System.IO.Compression;
1+
using Ganss.Xss;
2+
using System.Collections;
3+
using System.IO.Compression;
24
using System.Reflection;
35
using System.Text.RegularExpressions;
46

@@ -95,5 +97,64 @@ public static bool IsValidEmailAddress(string email)
9597

9698
return true;
9799
}
100+
101+
private static readonly HtmlSanitizer _sanitizer = new HtmlSanitizer();
102+
103+
public static void SanitizeObject<T>(T input)
104+
{
105+
if (input == null) return;
106+
107+
// Handle if the input is a list or collection
108+
if (input is IEnumerable collection && input.GetType() != typeof(string))
109+
{
110+
foreach (var item in collection)
111+
{
112+
if (item != null && item.GetType().IsClass)
113+
{
114+
SanitizeObject(item);
115+
}
116+
}
117+
return;
118+
}
119+
120+
Type type = input.GetType();
121+
foreach (PropertyInfo property in type.GetProperties())
122+
{
123+
if (property.PropertyType == typeof(string) && property.CanWrite)
124+
{
125+
string? value = property.GetValue(input) as string;
126+
if (!string.IsNullOrEmpty(value))
127+
{
128+
// Sanitize the string property
129+
string sanitizedValue = _sanitizer.Sanitize(value);
130+
property.SetValue(input, sanitizedValue);
131+
}
132+
}
133+
else if (property.PropertyType.IsClass && property.PropertyType != typeof(string))
134+
{
135+
// Recursively sanitize nested objects
136+
object? nestedObject = property.GetValue(input);
137+
if (nestedObject != null)
138+
{
139+
SanitizeObject(nestedObject);
140+
}
141+
}
142+
else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType) && property.CanWrite)
143+
{
144+
// Sanitize elements in collections (lists, arrays, etc.)
145+
IEnumerable? nestedCollection = property.GetValue(input) as IEnumerable;
146+
if (nestedCollection != null)
147+
{
148+
foreach (var item in nestedCollection)
149+
{
150+
if (item != null && item.GetType().IsClass)
151+
{
152+
SanitizeObject(item);
153+
}
154+
}
155+
}
156+
}
157+
}
158+
}
98159
}
99160
}

server/StrDss.Common/Constants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ public class Constants
66
public static DateTime MinDate = new DateTime(1900, 1, 1);
77
public const string VancouverTimeZone = "America/Vancouver";
88
public const string PacificTimeZone = "Pacific Standard Time";
9+
public const string StandardTakedownDetail = "Remove the listing from the platform, do not allow transactions for payments associated with the listing, and cancel all booking associated with the listing.";
910
}
1011
public static class Entities
1112
{

server/StrDss.Common/StrDss.Common.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10+
<PackageReference Include="HtmlSanitizer" Version="8.1.870" />
1011
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.3.7" />
1112
<PackageReference Include="System.IO.Compression" Version="4.3.0" />
1213
<PackageReference Include="System.IO.Compression.ZipFile" Version="4.3.0" />

server/StrDss.Service/DelistingService.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using AutoMapper;
2+
using Ganss.Xss;
23
using Microsoft.AspNetCore.Http;
34
using Microsoft.Extensions.Configuration;
45
using Microsoft.Extensions.Logging;
@@ -55,6 +56,8 @@ public DelistingService(ICurrentUser currentUser, IFieldValidatorService validat
5556

5657
public async Task<Dictionary<string, List<string>>> CreateTakedownNoticeAsync(TakedownNoticeCreateDto dto)
5758
{
59+
CommonUtils.SanitizeObject(dto);
60+
5861
var platform = await _orgService.GetOrganizationByIdAsync(dto.PlatformId);
5962
var lg = await _orgService.GetOrganizationByIdAsync(_currentUser.OrganizationId);
6063

@@ -203,6 +206,8 @@ private async Task SendTakedownNoticeAsync(TakedownNoticeCreateDto dto, Organiza
203206
}
204207
public async Task<(Dictionary<string, List<string>> errors, EmailPreview preview)> GetTakedownNoticePreviewAsync(TakedownNoticeCreateDto dto)
205208
{
209+
CommonUtils.SanitizeObject(dto);
210+
206211
var platform = await _orgService.GetOrganizationByIdAsync(dto.PlatformId);
207212
var lg = await _orgService.GetOrganizationByIdAsync(_currentUser.OrganizationId);
208213

@@ -250,6 +255,8 @@ private TakedownNotice GetTakedownNoticeTemplate(TakedownNoticeCreateDto dto, Or
250255

251256
public async Task<Dictionary<string, List<string>>> CreateTakedownNoticesFromListingAsync(TakedownNoticesFromListingDto[] listings)
252257
{
258+
CommonUtils.SanitizeObject(listings);
259+
253260
var errors = new Dictionary<string, List<string>>();
254261
var emailRegex = RegexDefs.GetRegexInfo(RegexDefs.Email);
255262
var templates = new List<TakedownNoticeFromListing>();
@@ -392,6 +399,8 @@ private async Task SendTakedownNoticeEmailFromListingAsync(TakedownNoticesFromLi
392399

393400
public async Task<(Dictionary<string, List<string>> errors, EmailPreview preview)> GetTakedownNoticesFromListingPreviewAsync(TakedownNoticesFromListingDto[] listings)
394401
{
402+
CommonUtils.SanitizeObject(listings);
403+
395404
var errors = new Dictionary<string, List<string>>();
396405
var emailRegex = RegexDefs.GetRegexInfo(RegexDefs.Email);
397406
var templates = new List<TakedownNoticeFromListing>();
@@ -443,6 +452,8 @@ private async Task SendTakedownNoticeEmailFromListingAsync(TakedownNoticesFromLi
443452

444453
public async Task<Dictionary<string, List<string>>> CreateTakedownRequestsFromListingAsync(TakedownRequestsFromListingDto[] listings)
445454
{
455+
CommonUtils.SanitizeObject(listings);
456+
446457
var errors = new Dictionary<string, List<string>>();
447458
var templates = new List<TakedownRequestFromListing>();
448459

@@ -529,6 +540,8 @@ private TakedownRequestFromListing CreateTakedownRequestTemplateFromListing(Take
529540
OrgCd = rentalListing.OrganizationCd,
530541
ListingId = rentalListing.PlatformListingNo,
531542
Info = $"{rentalListing.OrganizationCd}-{rentalListing.PlatformListingNo}",
543+
IsWithStandardDetail = listing.IsWithStandardDetail,
544+
TakedownRequestDetail = listing.CustomDetailTxt,
532545
To = new string[] { _currentUser.EmailAddress },
533546
Cc = listing.CcList ?? new List<string>()
534547
};
@@ -587,6 +600,8 @@ private async Task SendTakedownRequestEmailFromListingAsync(TakedownRequestsFrom
587600

588601
public async Task<(Dictionary<string, List<string>> errors, EmailPreview preview)> GetTakedownRequestsFromListingPreviewAsync(TakedownRequestsFromListingDto[] listings)
589602
{
603+
CommonUtils.SanitizeObject(listings);
604+
590605
var errors = new Dictionary<string, List<string>>();
591606
var templates = new List<TakedownRequestFromListing>();
592607

@@ -624,6 +639,8 @@ private async Task SendTakedownRequestEmailFromListingAsync(TakedownRequestsFrom
624639

625640
public async Task<Dictionary<string, List<string>>> CreateTakedownRequestAsync(TakedownRequestCreateDto dto)
626641
{
642+
CommonUtils.SanitizeObject(dto);
643+
627644
var platform = await _orgService.GetOrganizationByIdAsync(dto.PlatformId);
628645
var lg = await _orgService.GetOrganizationByIdAsync(_currentUser.OrganizationId);
629646

@@ -748,13 +765,16 @@ private TakedownRequest GetTakedownRequestTemplate(TakedownRequestCreateDto dto,
748765
To = dto.ToList,
749766
Cc = dto.CcList,
750767
Info = dto.ListingUrl,
768+
IsWithStandardDetail = dto.IsWithStandardDetail,
751769
Preview = preview
752770
};
753771
return template;
754772
}
755773

756774
public async Task<(Dictionary<string, List<string>> errors, EmailPreview preview)> GetTakedownRequestPreviewAsync(TakedownRequestCreateDto dto)
757775
{
776+
CommonUtils.SanitizeObject(dto);
777+
758778
var platform = await _orgService.GetOrganizationByIdAsync(dto.PlatformId);
759779
var lg = await _orgService.GetOrganizationByIdAsync(_currentUser.OrganizationId);
760780

@@ -813,7 +833,7 @@ private async Task ProcessTakedownRequestBatchEmailAsync(OrganizationDto platfor
813833
ListingId = x.UnreportedListingNo ?? "",
814834
Url = x.UnreportedListingUrl ?? "",
815835
RequestedBy = x.RequestingOrganization?.OrganizationNm ?? "",
816-
TakedownRequest = (x.IsWithStandardDetail ?? false) ? "Remove the listing from the platform, do not allow transactions for payments associated with the listing, and cancel all booking associated with the listing." : "",
836+
TakedownRequest = (x.IsWithStandardDetail ?? false) ? Constants.StandardTakedownDetail : "",
817837
TakedownRequestDetail = x.CustomDetailTxt ?? ""
818838
})
819839
.ToList();
@@ -1071,6 +1091,8 @@ private async Task<Dictionary<string, List<string>>> ValidateBatchTakedownNotice
10711091
}
10721092
public async Task<(Dictionary<string, List<string>> errors, EmailPreview preview)> GetComplianceOrdersFromListingPreviewAsync(ComplianceOrderDto[] listings)
10731093
{
1094+
CommonUtils.SanitizeObject(listings);
1095+
10741096
var errors = new Dictionary<string, List<string>>();
10751097
var templates = new List<ComplianceOrderFromListing>();
10761098

@@ -1143,6 +1165,8 @@ private async Task ProcessComplianceOrderListings(ComplianceOrderDto[] listings,
11431165
}
11441166
public async Task<Dictionary<string, List<string>>> CreateComplianceOrdersFromListingAsync(ComplianceOrderDto[] listings)
11451167
{
1168+
CommonUtils.SanitizeObject(listings);
1169+
11461170
var errors = new Dictionary<string, List<string>>();
11471171
var emailRegex = RegexDefs.GetRegexInfo(RegexDefs.Email);
11481172
var templates = new List<ComplianceOrderFromListing>();

server/StrDss.Service/EmailTemplates/ComplianceOrderFromListing.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public override string GetContent()
2222
return (Preview ? GetPreviewHeader() : "") + $@"
2323
Dear Host,<br/><br/>
2424
<b>This message has been sent to you by B.C.'s Short-term Rental Compliance Unit regarding your short-term rental listing:</b><br/><b>{Url}</b><br/><br/>
25-
<b>{Sanitize(Comment)}</b><br/>
25+
<b>{Comment}</b><br/>
2626
";
2727
}
2828

server/StrDss.Service/EmailTemplates/EmailTemplateBase.cs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
1-
using Ganss.Xss;
2-
using StrDss.Common;
1+
using StrDss.Common;
32
using StrDss.Model;
43

54
namespace StrDss.Service.EmailTemplates
65
{
76
public class EmailTemplateBase
87
{
98
private IEmailMessageService _emailService { get; set; }
10-
private HtmlSanitizer _sanitizer { get; set; }
11-
129
public EmailTemplateBase(IEmailMessageService emailService)
1310
{
1411
_emailService = emailService;
15-
_sanitizer = new HtmlSanitizer();
1612
}
1713

1814
public string Subject { get; set; } = "";
@@ -61,10 +57,5 @@ public async Task<string> SendEmail()
6157

6258
return await _emailService.SendEmailAsync(emailContent);
6359
}
64-
65-
public string Sanitize(string? text)
66-
{
67-
return _sanitizer.Sanitize(text ?? "");
68-
}
6960
}
7061
}

server/StrDss.Service/EmailTemplates/TakedownNotice.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ public override string GetContent()
2323
return (Preview ? GetPreviewHeader() : "") + $@"
2424
Dear Host,<br/><br/>
2525
Short-term rental accommodations in your community are regulated by your local government. The {LgName} has determined that the following short-term rental listing is not in compliance with an applicable local government business licence requirement:<br/><br/>
26-
<b>{Sanitize(Url)}</b><br/><br/>
27-
Listing ID Number: <b>{Sanitize(ListingId)}</b><br/><br/>
26+
<b>{Url}</b><br/><br/>
27+
Listing ID Number: <b>{ListingId}</b><br/><br/>
2828
Under the provincial <a href='https://www.bclaws.gov.bc.ca/civix/document/id/bills/billsprevious/4th42nd:gov35-1'><i>Short-Term Rental Accommodations Act</i></a> and its regulations, the local government may submit a request to the short-term rental platform to cease providing platform services (e.g., remove this listing from the platform and cancel bookings) within a period of 5-90 days after the date of delivery of this Notice. Short-term rental platforms are required to comply with the local government’s request within 5 days of receiving the request.<br/><br/>
2929
This Notice has been issued by {LgName}.<br/><br/>
30-
{Sanitize(Comment)}<br/><br/>
30+
{Comment}<br/><br/>
3131
For more information on this Notice, or local government short-term rental business licences, please contact your local government.<br/><br/>
3232
For more information on the <i>Short-term Rental Accommodations Act</i>, please visit: <a href='https://www2.gov.bc.ca/gov/content/housing-tenancy/short-term-rentals'>New rules for short-term rentals - Province of British Columbia (gov.bc.ca)</a>.<br/><br/>
3333

server/StrDss.Service/EmailTemplates/TakedownNoticeFromListing.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public string GetHtmlPreview()
2727
Listing ID Number: <strong>[Listing ID]</strong><br/><br/>
2828
Under the provincial <a href='https://www.bclaws.gov.bc.ca/civix/document/id/bills/billsprevious/4th42nd:gov35-1'><i>Short-Term Rental Accommodations Act</i></a> and its regulations, the local government may submit a request to the short-term rental platform to cease providing platform services (e.g., remove this listing from the platform and cancel bookings) within a period of 5-90 days after the date of delivery of this Notice. Short-term rental platforms are required to comply with the local government’s request within 5 days of receiving the request.<br/><br/>
2929
This Notice has been issued by <strong>[local government]</strong>.<br/><br/>
30-
<strong>[Optional remarks]</strong><br/><br/>
30+
<strong>{Comment}</strong><br/><br/>
3131
For more information on this Notice, or local government short-term rental business licences, please contact your local government.<br/><br/>
3232
For more information on the <i>Short-term Rental Accommodations Act</i>, please visit: <a href='https://www2.gov.bc.ca/gov/content/housing-tenancy/short-term-rentals'>New rules for short-term rentals - Province of British Columbia (gov.bc.ca)</a>.<br/><br/>
3333

server/StrDss.Service/EmailTemplates/TakedownRequest.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,26 @@ public TakedownRequest(IEmailMessageService emailService)
1313

1414
public string Url { get; set; } = "";
1515
public string? ListingId { get; set; }
16+
public bool IsWithStandardDetail { get; set; }
17+
public string TakedownRequestDetail { get; set; } = "";
1618

1719
public override string GetContent()
1820
{
21+
var standardText = IsWithStandardDetail ? Constants.StandardTakedownDetail : "";
22+
1923
Subject = "Confirmation of Takedown Request";
2024

2125
return (Preview ? GetPreviewHeader() : "") + $@"
2226
A takedown request for the following short-term rental listing was submitted to the Province of B.C.’s Short-term Rental Data Portal and will be delivered to the platform at 11:50pm PST tonight:<br/><br/>
23-
<b>{Sanitize(Url)}</b><br/><br/>
24-
Listing ID Number: <b>{Sanitize(ListingId)}</b><br/><br/>
27+
<b>{Url}</b><br/><br/>
28+
Listing ID Number: <b>{ListingId}</b><br/><br/>"
29+
+ (IsWithStandardDetail ? $@"{standardText}<br/><br/>" : "")
30+
+ (TakedownRequestDetail.IsNotEmpty() ? $@"{TakedownRequestDetail}<br/><br/>" : "")
31+
+ $@"
2532
Under the <a href='https://www.bclaws.gov.bc.ca/civix/document/id/bills/billsprevious/4th42nd:gov35-1'>Short-Term Rental Accommodations Act</a> and its regulations, the platform is required to comply with the request within 5 days from the date of receipt of the request. If the platform fails to comply with the request (e.g., remove the listing), local governments can escalate the matter to the Director of the Provincial STR Compliance and Enforcement Unit at: <a href='mailto: CEUescalations@gov.bc.ca'>CEUescalations@gov.bc.ca</a>.<br/><br/>
2633
This email has been automatically generated. Please do not reply to this email.
2734
";
35+
2836
}
2937
}
3038
}

server/StrDss.Service/EmailTemplates/TakedownRequestFromListing.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace StrDss.Service.EmailTemplates
1+
using StrDss.Common;
2+
3+
namespace StrDss.Service.EmailTemplates
24
{
35
public class TakedownRequestFromListing : TakedownRequest
46
{
@@ -14,10 +16,15 @@ public string GetHtmlPreview()
1416
{
1517
Subject = "Confirmation of Takedown Request";
1618

19+
var standardText = IsWithStandardDetail ? Constants.StandardTakedownDetail : "";
20+
1721
return (Preview ? GetPreviewHeader() : "") + $@"
1822
A takedown request for the following short-term rental listing was submitted to the Province of B.C.’s Short-term Rental Data Portal and will be delivered to the platform at 11:50pm PST tonight:<br/><br/>
19-
<strong>[URL of the listing]</strong><br/><br/>
20-
Listing ID Number: <strong>[Listing ID]</strong><br/><br/>
23+
<b>{Url}</b><br/><br/>
24+
Listing ID Number: <b>{ListingId}</b><br/><br/>"
25+
+ (IsWithStandardDetail ? $@"{standardText}<br/><br/>" : "")
26+
+ (TakedownRequestDetail.IsNotEmpty() ? $@"{TakedownRequestDetail}<br/><br/>" : "")
27+
+ $@"
2128
Under the <a href='https://www.bclaws.gov.bc.ca/civix/document/id/bills/billsprevious/4th42nd:gov35-1'>Short-Term Rental Accommodations Act</a> and its regulations, the platform is required to comply with the request within 5 days from the date of receipt of the request. If the platform fails to comply with the request (e.g., remove the listing), local governments can escalate the matter to the Director of the Provincial STR Compliance and Enforcement Unit at: <a href='mailto: CEUescalations@gov.bc.ca'>CEUescalations@gov.bc.ca</a>.<br/><br/>
2229
This email has been automatically generated. Please do not reply to this email.
2330
";

0 commit comments

Comments
 (0)