From b71ec627d7577c1d853db34a47dec0ef7842de67 Mon Sep 17 00:00:00 2001 From: "SAITO-PC-3\\saito.k" Date: Wed, 11 Dec 2024 14:07:39 +0900 Subject: [PATCH] =?UTF-8?q?=E7=89=B9=E5=88=A5=E3=81=AA=E6=96=87=E5=AD=97?= =?UTF-8?q?=E5=88=97=E3=82=92=E3=82=A8=E3=82=B9=E3=82=B1=E3=83=BC=E3=83=97?= =?UTF-8?q?=E3=81=97=E3=81=A6=E3=81=8B=E3=82=89replaceAll=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/functions/licenseAutoAllocation.ts | 43 +- .../src/gateways/sendgrid/sendgrid.service.ts | 399 +++++++++--------- .../src/gateways/sendgrid/sendgrid.spec.ts | 110 ++++- 3 files changed, 336 insertions(+), 216 deletions(-) diff --git a/dictation_function/src/functions/licenseAutoAllocation.ts b/dictation_function/src/functions/licenseAutoAllocation.ts index c37df17..f2d0f82 100644 --- a/dictation_function/src/functions/licenseAutoAllocation.ts +++ b/dictation_function/src/functions/licenseAutoAllocation.ts @@ -636,15 +636,15 @@ export async function sendMailWithU108( "utf-8" ); html = templateU108NoParentHtml - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(USER_NAME, userName) - .replaceAll(USER_EMAIL, userMail) - .replaceAll(TOP_URL, url); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(USER_NAME, escapeDollar(userName)) + .replaceAll(USER_EMAIL, escapeDollar(userMail)) + .replaceAll(TOP_URL, escapeDollar(url)); text = templateU108NoParentText - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(USER_NAME, userName) - .replaceAll(USER_EMAIL, userMail) - .replaceAll(TOP_URL, url); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(USER_NAME, escapeDollar(userName)) + .replaceAll(USER_EMAIL, escapeDollar(userMail)) + .replaceAll(TOP_URL, escapeDollar(url)); } else { const templateU108Html = readFileSync( path.resolve(__dirname, `../templates/template_U_108.html`), @@ -655,17 +655,17 @@ export async function sendMailWithU108( "utf-8" ); html = templateU108Html - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(DEALER_NAME, dealerAccountName) - .replaceAll(USER_NAME, userName) - .replaceAll(USER_EMAIL, userMail) - .replaceAll(TOP_URL, url); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)) + .replaceAll(USER_NAME, escapeDollar(userName)) + .replaceAll(USER_EMAIL, escapeDollar(userMail)) + .replaceAll(TOP_URL, escapeDollar(url)); text = templateU108Text - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(DEALER_NAME, dealerAccountName) - .replaceAll(USER_NAME, userName) - .replaceAll(USER_EMAIL, userMail) - .replaceAll(TOP_URL, url); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)) + .replaceAll(USER_NAME, escapeDollar(userName)) + .replaceAll(USER_EMAIL, escapeDollar(userMail)) + .replaceAll(TOP_URL, escapeDollar(url)); } const uniqueCustomerAdminMails = [...new Set(customerAdminMails)]; const ccMails = uniqueCustomerAdminMails.includes(userMail) ? [] : [userMail]; @@ -694,3 +694,10 @@ class autoAllocationList { accountId: number; userIds: number[]; } + +/** + * 与えられた文字列内の $ を $$ にエスケープする + * @param str - エスケープする対象の文字列 + * @returns エスケープ後の文字列 + */ +export const escapeDollar = (str: string): string => str.replace(/\$/g, "$$$$"); \ No newline at end of file diff --git a/dictation_server/src/gateways/sendgrid/sendgrid.service.ts b/dictation_server/src/gateways/sendgrid/sendgrid.service.ts index 46226f8..ad56460 100644 --- a/dictation_server/src/gateways/sendgrid/sendgrid.service.ts +++ b/dictation_server/src/gateways/sendgrid/sendgrid.service.ts @@ -379,11 +379,11 @@ export class SendGridService { const subject = 'Account Registered Notification [U-101]'; const url = new URL(this.appDomain).href; const html = this.templateU101Html - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(TOP_URL, url); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(TOP_URL, escapeDollar(url)); const text = this.templateU101Text - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(TOP_URL, url); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(TOP_URL, escapeDollar(url)); await this.sendMail( context, @@ -434,8 +434,8 @@ export class SendGridService { const verifyUrl = `${url}?verify=${token}`; const subject = 'User Registration Notification [U-102]'; - const html = this.templateU102Html.replaceAll(VERIFY_LINK, verifyUrl); - const text = this.templateU102Text.replaceAll(VERIFY_LINK, verifyUrl); + const html = this.templateU102Html.replaceAll(VERIFY_LINK, escapeDollar(verifyUrl)); + const text = this.templateU102Text.replaceAll(VERIFY_LINK, escapeDollar(verifyUrl)); await this.sendMail( context, @@ -481,15 +481,15 @@ export class SendGridService { // メールの本文を作成する const html = this.templateU105Html - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(DEALER_NAME, dealerAccountName) - .replaceAll(PO_NUMBER, poNumber) - .replaceAll(LICENSE_QUANTITY, `${lisenceCount}`); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)) + .replaceAll(PO_NUMBER, escapeDollar(poNumber)) + .replaceAll(LICENSE_QUANTITY, escapeDollar(`${lisenceCount}`)); const text = this.templateU105Text - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(DEALER_NAME, dealerAccountName) - .replaceAll(PO_NUMBER, poNumber) - .replaceAll(LICENSE_QUANTITY, `${lisenceCount}`); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)) + .replaceAll(PO_NUMBER, escapeDollar(poNumber)) + .replaceAll(LICENSE_QUANTITY, escapeDollar(`${lisenceCount}`)); // メールを送信する await this.sendMail( @@ -536,15 +536,15 @@ export class SendGridService { // メールの本文を作成する const html = this.templateU106Html - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(DEALER_NAME, dealerAccountName) - .replaceAll(PO_NUMBER, poNumber) - .replaceAll(LICENSE_QUANTITY, `${lisenceCount}`); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)) + .replaceAll(PO_NUMBER, escapeDollar(poNumber)) + .replaceAll(LICENSE_QUANTITY, escapeDollar(`${lisenceCount}`)); const text = this.templateU106Text - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(DEALER_NAME, dealerAccountName) - .replaceAll(PO_NUMBER, poNumber) - .replaceAll(LICENSE_QUANTITY, `${lisenceCount}`); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)) + .replaceAll(PO_NUMBER, escapeDollar(poNumber)) + .replaceAll(LICENSE_QUANTITY, escapeDollar(`${lisenceCount}`)); // メールを送信する await this.sendMail( @@ -591,15 +591,15 @@ export class SendGridService { // メールの本文を作成する const html = this.templateU107Html - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(DEALER_NAME, dealerAccountName) - .replaceAll(PO_NUMBER, poNumber) - .replaceAll(LICENSE_QUANTITY, `${lisenceCount}`); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)) + .replaceAll(PO_NUMBER, escapeDollar(poNumber)) + .replaceAll(LICENSE_QUANTITY,escapeDollar(`${lisenceCount}`)); const text = this.templateU107Text - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(DEALER_NAME, dealerAccountName) - .replaceAll(PO_NUMBER, poNumber) - .replaceAll(LICENSE_QUANTITY, `${lisenceCount}`); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)) + .replaceAll(PO_NUMBER, escapeDollar(poNumber)) + .replaceAll(LICENSE_QUANTITY,escapeDollar(`${lisenceCount}`)); // メールを送信する await this.sendMail( @@ -648,28 +648,28 @@ export class SendGridService { if (dealerAccountName === null) { html = this.templateU108NoParentHtml - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(USER_NAME, userName) - .replaceAll(USER_EMAIL, userMail) - .replaceAll(TOP_URL, url); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(USER_NAME, escapeDollar(userName)) + .replaceAll(USER_EMAIL, escapeDollar(userMail)) + .replaceAll(TOP_URL, escapeDollar(url)); text = this.templateU108NoParentText - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(USER_NAME, userName) - .replaceAll(USER_EMAIL, userMail) - .replaceAll(TOP_URL, url); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(USER_NAME, escapeDollar(userName)) + .replaceAll(USER_EMAIL, escapeDollar(userMail)) + .replaceAll(TOP_URL, escapeDollar(url)); } else { html = this.templateU108Html - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(DEALER_NAME, dealerAccountName) - .replaceAll(USER_NAME, userName) - .replaceAll(USER_EMAIL, userMail) - .replaceAll(TOP_URL, url); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)) + .replaceAll(USER_NAME, escapeDollar(userName)) + .replaceAll(USER_EMAIL, escapeDollar(userMail)) + .replaceAll(TOP_URL, escapeDollar(url)); text = this.templateU108Text - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(DEALER_NAME, dealerAccountName) - .replaceAll(USER_NAME, userName) - .replaceAll(USER_EMAIL, userMail) - .replaceAll(TOP_URL, url); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)) + .replaceAll(USER_NAME, escapeDollar(userName)) + .replaceAll(USER_EMAIL, escapeDollar(userMail)) + .replaceAll(TOP_URL, escapeDollar(url)); } const ccAddress = customerAdminMails.includes(userMail) ? [] : [userMail]; @@ -719,15 +719,15 @@ export class SendGridService { // メールの本文を作成する const html = this.templateU109Html - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(DEALER_NAME, dealerAccountName) - .replaceAll(PO_NUMBER, poNumber) - .replaceAll(LICENSE_QUANTITY, `${lisenceCount}`); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)) + .replaceAll(PO_NUMBER, escapeDollar(poNumber)) + .replaceAll(LICENSE_QUANTITY, escapeDollar(`${lisenceCount}`)); const text = this.templateU109Text - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(DEALER_NAME, dealerAccountName) - .replaceAll(PO_NUMBER, poNumber) - .replaceAll(LICENSE_QUANTITY, `${lisenceCount}`); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)) + .replaceAll(PO_NUMBER, escapeDollar(poNumber)) + .replaceAll(LICENSE_QUANTITY, escapeDollar(`${lisenceCount}`)); // メールを送信する await this.sendMail( @@ -769,14 +769,14 @@ export class SendGridService { // メールの本文を作成する const html = this.templateU111Html - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName) - .replaceAll(TOP_URL, url); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(PRIMARY_ADMIN_NAME, escapeDollar(primaryAdminName)) + .replaceAll(TOP_URL, escapeDollar(url)); const text = this.templateU111Text - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName) - .replaceAll(TOP_URL, url); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(PRIMARY_ADMIN_NAME, escapeDollar(primaryAdminName)) + .replaceAll(TOP_URL, escapeDollar(url)); // メールを送信する await this.sendMail( @@ -825,24 +825,24 @@ export class SendGridService { if (dealerAccountName === null) { // メールの本文を作成する html = this.templateU112NoParentHtml - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName) - .replaceAll(TOP_URL, url); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(PRIMARY_ADMIN_NAME, escapeDollar(primaryAdminName)) + .replaceAll(TOP_URL, escapeDollar(url)); text = this.templateU112NoParentText - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName) - .replaceAll(TOP_URL, url); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(PRIMARY_ADMIN_NAME, escapeDollar(primaryAdminName)) + .replaceAll(TOP_URL, escapeDollar(url)); } else { html = this.templateU112Html - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(DEALER_NAME, dealerAccountName) - .replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName) - .replaceAll(TOP_URL, url); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)) + .replaceAll(PRIMARY_ADMIN_NAME, escapeDollar(primaryAdminName)) + .replaceAll(TOP_URL, escapeDollar(url)); text = this.templateU112Text - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(DEALER_NAME, dealerAccountName) - .replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName) - .replaceAll(TOP_URL, url); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)) + .replaceAll(PRIMARY_ADMIN_NAME, escapeDollar(primaryAdminName)) + .replaceAll(TOP_URL, escapeDollar(url)); } // メールを送信する @@ -884,11 +884,11 @@ export class SendGridService { // メールの本文を作成する const html = this.templateU113Html - .replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName) - .replaceAll(TEMPORARY_PASSWORD, temporaryPassword); + .replaceAll(PRIMARY_ADMIN_NAME, escapeDollar(primaryAdminName)) + .replaceAll(TEMPORARY_PASSWORD, escapeDollar(temporaryPassword)); const text = this.templateU113Text - .replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName) - .replaceAll(TEMPORARY_PASSWORD, temporaryPassword); + .replaceAll(PRIMARY_ADMIN_NAME, escapeDollar(primaryAdminName)) + .replaceAll(TEMPORARY_PASSWORD, escapeDollar(temporaryPassword)); // メールを送信する await this.sendMail( @@ -947,11 +947,11 @@ export class SendGridService { // メールの本文を作成する const html = this.templateU114Html - .replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName) - .replaceAll(VERIFY_LINK, verifyUrl); + .replaceAll(PRIMARY_ADMIN_NAME, escapeDollar(primaryAdminName)) + .replaceAll(VERIFY_LINK, escapeDollar(verifyUrl)); const text = this.templateU114Text - .replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName) - .replaceAll(VERIFY_LINK, verifyUrl); + .replaceAll(PRIMARY_ADMIN_NAME, escapeDollar(primaryAdminName)) + .replaceAll(VERIFY_LINK, escapeDollar(verifyUrl)); // メールを送信する await this.sendMail( @@ -994,11 +994,11 @@ export class SendGridService { // メールの本文を作成する const html = this.templateU115Html - .replaceAll(USER_NAME, userName) - .replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName); + .replaceAll(USER_NAME, escapeDollar(userName)) + .replaceAll(PRIMARY_ADMIN_NAME, escapeDollar(primaryAdminName)); const text = this.templateU115Text - .replaceAll(USER_NAME, userName) - .replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName); + .replaceAll(USER_NAME, escapeDollar(userName)) + .replaceAll(PRIMARY_ADMIN_NAME, escapeDollar(primaryAdminName)); // 管理者ユーザーの情報を変更した場合にはTOに管理者のメールアドレスを設定するので、CCには管理者のメールアドレスを設定しない const ccAdminMails = adminMails.filter((x) => x !== userMail); @@ -1044,11 +1044,11 @@ export class SendGridService { // メールの本文を作成する const html = this.templateU116Html - .replaceAll(USER_NAME, userName) - .replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName); + .replaceAll(USER_NAME, escapeDollar(userName)) + .replaceAll(PRIMARY_ADMIN_NAME, escapeDollar(primaryAdminName)); const text = this.templateU116Text - .replaceAll(USER_NAME, userName) - .replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName); + .replaceAll(USER_NAME, escapeDollar(userName)) + .replaceAll(PRIMARY_ADMIN_NAME, escapeDollar(primaryAdminName)); // メールを送信する await this.sendMail( @@ -1094,15 +1094,15 @@ export class SendGridService { // メールの本文を作成する const html = this.templateU117Html - .replaceAll(AUTHOR_NAME, authorName) - .replaceAll(FILE_NAME, fileName) - .replaceAll(TYPIST_NAME, typistName) - .replaceAll(PRIMARY_ADMIN_NAME, adminName); + .replaceAll(AUTHOR_NAME, escapeDollar(authorName)) + .replaceAll(FILE_NAME, escapeDollar(fileName)) + .replaceAll(TYPIST_NAME, escapeDollar(typistName)) + .replaceAll(PRIMARY_ADMIN_NAME, escapeDollar(adminName)); const text = this.templateU117Text - .replaceAll(AUTHOR_NAME, authorName) - .replaceAll(FILE_NAME, fileName) - .replaceAll(TYPIST_NAME, typistName) - .replaceAll(PRIMARY_ADMIN_NAME, adminName); + .replaceAll(AUTHOR_NAME, escapeDollar(authorName)) + .replaceAll(FILE_NAME, escapeDollar(fileName)) + .replaceAll(TYPIST_NAME, escapeDollar(typistName)) + .replaceAll(PRIMARY_ADMIN_NAME, escapeDollar(adminName)); // OMDS_IS-380 Dictation Workflow完了通知 [U-117]  をTypistには送信しないようにしたいの対応のため送信先からtypistEmailを削除 2024年8月7日 const to = [authorEmail].filter((x): x is string => x !== null); @@ -1144,20 +1144,19 @@ export class SendGridService { if (!dealerAccountName) { html = this.templateU118NoParentHtml.replaceAll( - CUSTOMER_NAME, - customerAccountName, + CUSTOMER_NAME,escapeDollar( customerAccountName), ); text = this.templateU118NoParentText.replaceAll( CUSTOMER_NAME, - customerAccountName, + escapeDollar(customerAccountName), ); } else { html = this.templateU118Html - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(DEALER_NAME, dealerAccountName); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)); text = this.templateU118Text - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(DEALER_NAME, dealerAccountName); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)); } // メールを送信する @@ -1205,19 +1204,19 @@ export class SendGridService { if (!dealerAccountName) { html = this.templateU119NoParentHtml.replaceAll( CUSTOMER_NAME, - customerAccountName, + escapeDollar(customerAccountName), ); text = this.templateU119NoParentText.replaceAll( CUSTOMER_NAME, - customerAccountName, + escapeDollar(customerAccountName), ); } else { html = this.templateU119Html - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(DEALER_NAME, dealerAccountName); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)); text = this.templateU119Text - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(DEALER_NAME, dealerAccountName); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)); } // メールを送信する @@ -1266,24 +1265,24 @@ export class SendGridService { if (!dealerAccountName) { html = this.templateU120NoParentHtml - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(REQUEST_TIME, requestTime) - .replaceAll(FILE_NAME, fileName); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(REQUEST_TIME, escapeDollar(requestTime)) + .replaceAll(FILE_NAME, escapeDollar(fileName)); text = this.templateU120NoParentText - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(REQUEST_TIME, requestTime) - .replaceAll(FILE_NAME, fileName); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(REQUEST_TIME, escapeDollar(requestTime)) + .replaceAll(FILE_NAME, escapeDollar(fileName)); } else { html = this.templateU120Html - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(DEALER_NAME, dealerAccountName) - .replaceAll(REQUEST_TIME, requestTime) - .replaceAll(FILE_NAME, fileName); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)) + .replaceAll(REQUEST_TIME, escapeDollar(requestTime)) + .replaceAll(FILE_NAME, escapeDollar(fileName)); text = this.templateU120Text - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(DEALER_NAME, dealerAccountName) - .replaceAll(REQUEST_TIME, requestTime) - .replaceAll(FILE_NAME, fileName); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)) + .replaceAll(REQUEST_TIME, escapeDollar(requestTime)) + .replaceAll(FILE_NAME, escapeDollar(fileName)); } // メールを送信する @@ -1332,24 +1331,24 @@ export class SendGridService { if (!dealerAccountName) { html = this.templateU121NoParentHtml - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(REQUEST_TIME, requestTime) - .replaceAll(FILE_NAME, fileName); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(REQUEST_TIME, escapeDollar(requestTime)) + .replaceAll(FILE_NAME, escapeDollar(fileName)); text = this.templateU121NoParentText - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(REQUEST_TIME, requestTime) - .replaceAll(FILE_NAME, fileName); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(REQUEST_TIME, escapeDollar(requestTime)) + .replaceAll(FILE_NAME, escapeDollar(fileName)); } else { html = this.templateU121Html - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(DEALER_NAME, dealerAccountName) - .replaceAll(REQUEST_TIME, requestTime) - .replaceAll(FILE_NAME, fileName); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)) + .replaceAll(REQUEST_TIME, escapeDollar(requestTime)) + .replaceAll(FILE_NAME, escapeDollar(fileName)); text = this.templateU121Text - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(DEALER_NAME, dealerAccountName) - .replaceAll(REQUEST_TIME, requestTime) - .replaceAll(FILE_NAME, fileName); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)) + .replaceAll(REQUEST_TIME, escapeDollar(requestTime)) + .replaceAll(FILE_NAME, escapeDollar(fileName)); } // メールを送信する @@ -1432,52 +1431,52 @@ export class SendGridService { if (!dealerAccountName) { html = this.templateU122NoParentHtml - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(EMAIL_DUPLICATION_EN, duplicateEmailsMsgEn) - .replaceAll(EMAIL_DUPLICATION_DE, duplicateEmailsMsgDe) - .replaceAll(EMAIL_DUPLICATION_FR, duplicateEmailsMsgFr) - .replaceAll(AUTHOR_ID_DUPLICATION_EN, duplicateAuthorIdsMsgEn) - .replaceAll(AUTHOR_ID_DUPLICATION_DE, duplicateAuthorIdsMsgDe) - .replaceAll(AUTHOR_ID_DUPLICATION_FR, duplicateAuthorIdsMsgFr) - .replaceAll(UNEXPECTED_ERROR_EN, otherErrorsMsgEn) - .replaceAll(UNEXPECTED_ERROR_DE, otherErrorsMsgDe) - .replaceAll(UNEXPECTED_ERROR_FR, otherErrorsMsgFr); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(EMAIL_DUPLICATION_EN, escapeDollar(duplicateEmailsMsgEn)) + .replaceAll(EMAIL_DUPLICATION_DE, escapeDollar(duplicateEmailsMsgDe)) + .replaceAll(EMAIL_DUPLICATION_FR, escapeDollar(duplicateEmailsMsgFr)) + .replaceAll(AUTHOR_ID_DUPLICATION_EN, escapeDollar(duplicateAuthorIdsMsgEn)) + .replaceAll(AUTHOR_ID_DUPLICATION_DE, escapeDollar(duplicateAuthorIdsMsgDe)) + .replaceAll(AUTHOR_ID_DUPLICATION_FR, escapeDollar(duplicateAuthorIdsMsgFr)) + .replaceAll(UNEXPECTED_ERROR_EN, escapeDollar(otherErrorsMsgEn)) + .replaceAll(UNEXPECTED_ERROR_DE, escapeDollar(otherErrorsMsgDe)) + .replaceAll(UNEXPECTED_ERROR_FR, escapeDollar(otherErrorsMsgFr)); text = this.templateU122NoParentText - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(EMAIL_DUPLICATION_EN, duplicateEmailsMsgEn) - .replaceAll(EMAIL_DUPLICATION_DE, duplicateEmailsMsgDe) - .replaceAll(EMAIL_DUPLICATION_FR, duplicateEmailsMsgFr) - .replaceAll(AUTHOR_ID_DUPLICATION_EN, duplicateAuthorIdsMsgEn) - .replaceAll(AUTHOR_ID_DUPLICATION_DE, duplicateAuthorIdsMsgDe) - .replaceAll(AUTHOR_ID_DUPLICATION_FR, duplicateAuthorIdsMsgFr) - .replaceAll(UNEXPECTED_ERROR_EN, otherErrorsMsgEn) - .replaceAll(UNEXPECTED_ERROR_DE, otherErrorsMsgDe) - .replaceAll(UNEXPECTED_ERROR_FR, otherErrorsMsgFr); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(EMAIL_DUPLICATION_EN, escapeDollar(duplicateEmailsMsgEn)) + .replaceAll(EMAIL_DUPLICATION_DE, escapeDollar(duplicateEmailsMsgDe)) + .replaceAll(EMAIL_DUPLICATION_FR, escapeDollar(duplicateEmailsMsgFr)) + .replaceAll(AUTHOR_ID_DUPLICATION_EN, escapeDollar(duplicateAuthorIdsMsgEn)) + .replaceAll(AUTHOR_ID_DUPLICATION_DE, escapeDollar(duplicateAuthorIdsMsgDe)) + .replaceAll(AUTHOR_ID_DUPLICATION_FR, escapeDollar(duplicateAuthorIdsMsgFr)) + .replaceAll(UNEXPECTED_ERROR_EN, escapeDollar(otherErrorsMsgEn)) + .replaceAll(UNEXPECTED_ERROR_DE, escapeDollar(otherErrorsMsgDe)) + .replaceAll(UNEXPECTED_ERROR_FR, escapeDollar(otherErrorsMsgFr)); } else { html = this.templateU122Html - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(DEALER_NAME, dealerAccountName) - .replaceAll(EMAIL_DUPLICATION_EN, duplicateEmailsMsgEn) - .replaceAll(EMAIL_DUPLICATION_DE, duplicateEmailsMsgDe) - .replaceAll(EMAIL_DUPLICATION_FR, duplicateEmailsMsgFr) - .replaceAll(AUTHOR_ID_DUPLICATION_EN, duplicateAuthorIdsMsgEn) - .replaceAll(AUTHOR_ID_DUPLICATION_DE, duplicateAuthorIdsMsgDe) - .replaceAll(AUTHOR_ID_DUPLICATION_FR, duplicateAuthorIdsMsgFr) - .replaceAll(UNEXPECTED_ERROR_EN, otherErrorsMsgEn) - .replaceAll(UNEXPECTED_ERROR_DE, otherErrorsMsgDe) - .replaceAll(UNEXPECTED_ERROR_FR, otherErrorsMsgFr); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)) + .replaceAll(EMAIL_DUPLICATION_EN, escapeDollar(duplicateEmailsMsgEn)) + .replaceAll(EMAIL_DUPLICATION_DE, escapeDollar(duplicateEmailsMsgDe)) + .replaceAll(EMAIL_DUPLICATION_FR, escapeDollar(duplicateEmailsMsgFr)) + .replaceAll(AUTHOR_ID_DUPLICATION_EN, escapeDollar(duplicateAuthorIdsMsgEn)) + .replaceAll(AUTHOR_ID_DUPLICATION_DE, escapeDollar(duplicateAuthorIdsMsgDe)) + .replaceAll(AUTHOR_ID_DUPLICATION_FR, escapeDollar(duplicateAuthorIdsMsgFr)) + .replaceAll(UNEXPECTED_ERROR_EN, escapeDollar(otherErrorsMsgEn)) + .replaceAll(UNEXPECTED_ERROR_DE, escapeDollar(otherErrorsMsgDe)) + .replaceAll(UNEXPECTED_ERROR_FR, escapeDollar(otherErrorsMsgFr)); text = this.templateU122Text - .replaceAll(CUSTOMER_NAME, customerAccountName) - .replaceAll(DEALER_NAME, dealerAccountName) - .replaceAll(EMAIL_DUPLICATION_EN, duplicateEmailsMsgEn) - .replaceAll(EMAIL_DUPLICATION_DE, duplicateEmailsMsgDe) - .replaceAll(EMAIL_DUPLICATION_FR, duplicateEmailsMsgFr) - .replaceAll(AUTHOR_ID_DUPLICATION_EN, duplicateAuthorIdsMsgEn) - .replaceAll(AUTHOR_ID_DUPLICATION_DE, duplicateAuthorIdsMsgDe) - .replaceAll(AUTHOR_ID_DUPLICATION_FR, duplicateAuthorIdsMsgFr) - .replaceAll(UNEXPECTED_ERROR_EN, otherErrorsMsgEn) - .replaceAll(UNEXPECTED_ERROR_DE, otherErrorsMsgDe) - .replaceAll(UNEXPECTED_ERROR_FR, otherErrorsMsgFr); + .replaceAll(CUSTOMER_NAME, escapeDollar(customerAccountName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)) + .replaceAll(EMAIL_DUPLICATION_EN, escapeDollar(duplicateEmailsMsgEn)) + .replaceAll(EMAIL_DUPLICATION_DE, escapeDollar(duplicateEmailsMsgDe)) + .replaceAll(EMAIL_DUPLICATION_FR, escapeDollar(duplicateEmailsMsgFr)) + .replaceAll(AUTHOR_ID_DUPLICATION_EN, escapeDollar(duplicateAuthorIdsMsgEn)) + .replaceAll(AUTHOR_ID_DUPLICATION_DE, escapeDollar(duplicateAuthorIdsMsgDe)) + .replaceAll(AUTHOR_ID_DUPLICATION_FR, escapeDollar(duplicateAuthorIdsMsgFr)) + .replaceAll(UNEXPECTED_ERROR_EN, escapeDollar(otherErrorsMsgEn)) + .replaceAll(UNEXPECTED_ERROR_DE, escapeDollar(otherErrorsMsgDe)) + .replaceAll(UNEXPECTED_ERROR_FR, escapeDollar(otherErrorsMsgFr)); } // メールを送信する @@ -1522,13 +1521,13 @@ export class SendGridService { const subject = 'Partner Account Deleted Notification [U-123]'; const html = this.templateU123Html - .replaceAll(CUSTOMER_NAME, partnerAccountName) - .replaceAll(PRIMARY_ADMIN_NAME, partnerPrimaryName) - .replaceAll(DEALER_NAME, dealerAccountName); + .replaceAll(CUSTOMER_NAME, escapeDollar(partnerAccountName)) + .replaceAll(PRIMARY_ADMIN_NAME, escapeDollar(partnerPrimaryName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)); const text = this.templateU123Text - .replaceAll(CUSTOMER_NAME, partnerAccountName) - .replaceAll(PRIMARY_ADMIN_NAME, partnerPrimaryName) - .replaceAll(DEALER_NAME, dealerAccountName); + .replaceAll(CUSTOMER_NAME, escapeDollar(partnerAccountName)) + .replaceAll(PRIMARY_ADMIN_NAME, escapeDollar(partnerPrimaryName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)); // メールを送信する await this.sendMail( @@ -1573,15 +1572,15 @@ export class SendGridService { const url = new URL(this.appDomain).href; const html = this.templateU124Html - .replaceAll(CUSTOMER_NAME, partnerAccountName) - .replaceAll(PRIMARY_ADMIN_NAME, partnerPrimaryName) - .replaceAll(DEALER_NAME, dealerAccountName) - .replaceAll(TOP_URL, url); + .replaceAll(CUSTOMER_NAME, escapeDollar(partnerAccountName)) + .replaceAll(PRIMARY_ADMIN_NAME, escapeDollar(partnerPrimaryName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)) + .replaceAll(TOP_URL, escapeDollar(url)); const text = this.templateU124Text - .replaceAll(CUSTOMER_NAME, partnerAccountName) - .replaceAll(PRIMARY_ADMIN_NAME, partnerPrimaryName) - .replaceAll(DEALER_NAME, dealerAccountName) - .replaceAll(TOP_URL, url); + .replaceAll(CUSTOMER_NAME, escapeDollar(partnerAccountName)) + .replaceAll(PRIMARY_ADMIN_NAME, escapeDollar(partnerPrimaryName)) + .replaceAll(DEALER_NAME, escapeDollar(dealerAccountName)) + .replaceAll(TOP_URL, escapeDollar(url)); // メールを送信する await this.sendMail( @@ -1657,3 +1656,9 @@ export class SendGridService { } } } +/** + * 与えられた文字列内の $ を $$ にエスケープする + * @param str - エスケープする対象の文字列 + * @returns エスケープ後の文字列 + */ +export const escapeDollar = (str: string): string => str.replace(/\$/g, "$$$$"); \ No newline at end of file diff --git a/dictation_server/src/gateways/sendgrid/sendgrid.spec.ts b/dictation_server/src/gateways/sendgrid/sendgrid.spec.ts index d93dd8b..1e90d1a 100644 --- a/dictation_server/src/gateways/sendgrid/sendgrid.spec.ts +++ b/dictation_server/src/gateways/sendgrid/sendgrid.spec.ts @@ -1,5 +1,5 @@ import sendgrid from '@sendgrid/mail'; -import { SendGridService } from './sendgrid.service'; +import { escapeDollar, SendGridService } from './sendgrid.service'; import { Context, makeContext } from '../../common/log'; // sendgridのsend関数をモック化 @@ -131,3 +131,111 @@ describe('SendGridService', () => { ).rejects.toThrow('Send failed'); }); }); + +describe('escapeDollar', () => { + test('文字列に$が含まれていない場合はそのまま返す', () => { + expect(escapeDollar('Hello World')).toBe('Hello World'); + expect(escapeDollar('No dollars here!')).toBe('No dollars here!'); + expect(escapeDollar('')).toBe(''); + }); + + test('$が文字列の先頭にある場合、エスケープされる', () => { + expect(escapeDollar('$Hello')).toBe('$$Hello'); + }); + + test('$が文字列の末尾にある場合、エスケープされる', () => { + expect(escapeDollar('World$')).toBe('World$$'); + }); + + test('$が文字列の途中にある場合、エスケープされる', () => { + expect(escapeDollar('Hello$World')).toBe('Hello$$World'); + }); + + test('複数の$が文字列に含まれる場合、すべてエスケープされる', () => { + expect(escapeDollar('$$Hello$$World$$')).toBe('$$$$Hello$$$$World$$$$'); + expect(escapeDollar('$1$2$3')).toBe('$$1$$2$$3'); + }); + + test('特殊文字と$が混在する場合でも$が正しくエスケープされる', () => { + expect(escapeDollar('Price: $5, Tax: $0.50')).toBe( + 'Price: $$5, Tax: $$0.50', + ); + expect(escapeDollar('Special.$World$^$')).toBe('Special.$$World$$^$$'); + }); + + test('文字列が$だけの場合、すべてエスケープされる', () => { + expect(escapeDollar('$')).toBe('$$'); + expect(escapeDollar('$$$')).toBe('$$$$$$'); + }); + + test('すでにエスケープされた$が含まれる場合、さらにエスケープされる', () => { + expect(escapeDollar('$$Already$$Escaped$$')).toBe( + '$$$$Already$$$$Escaped$$$$', + ); + }); + + test('空白や改行、タブ文字を含む文字列でも$がエスケープされる', () => { + expect(escapeDollar(' $Leading$Trailing$ ')).toBe( + ' $$Leading$$Trailing$$ ', + ); + expect(escapeDollar('Line1$\nLine2$\tEnd$')).toBe( + 'Line1$$\nLine2$$\tEnd$$', + ); + }); +}); + + +describe('特殊文字を含む置き換え処理でescapeDollarを使用するテスト', () => { + test('escapeDollarを使わない場合と使った場合の置き換え結果を比較', () => { + const input = 'This is $TEMPORARY_PASSWORD$ for you.'; + const replacement = 'T$&AS$123$'; + + // escapeDollarを使わない場合 + const resultWithoutEscape = input.replaceAll( + '$TEMPORARY_PASSWORD$', + replacement, + ); + + // escapeDollarを使った場合 + const safeReplacement = escapeDollar(replacement); + const resultWithEscape = input.replaceAll( + '$TEMPORARY_PASSWORD$', + safeReplacement, + ); + + // 意図しない結果になることを確認 + expect(resultWithoutEscape).not.toBe('This is T$&AS$123$ for you.'); + expect(resultWithoutEscape).toContain('$TEMPORARY_PASSWORD$'); // 意図通りに置換されていない場合が含まれる + + // escapeDollarを使用した結果が意図通りであることを確認 + expect(resultWithEscape).toBe('This is T$&AS$123$ for you.'); + }); + + test("特殊文字 ($1, $`, $') を含む場合の置換処理を確認", () => { + const input = 'This is $TEMPORARY_PASSWORD$ for you.'; + const replacementWithGroup = 'Group-$1-Value'; + const replacementWithBacktick = 'Backtick-$`-Value'; + const replacementWithSingleQuote = "Single-$'-Value"; + + // $1: キャプチャグループ + const resultWithGroup = input.replaceAll( + '$TEMPORARY_PASSWORD$', + escapeDollar(replacementWithGroup), + ); + expect(resultWithGroup).toBe('This is Group-$1-Value for you.'); + + // $`: 置換対象文字列の先頭部分 + const resultWithBacktick = input.replaceAll( + '$TEMPORARY_PASSWORD$', + escapeDollar(replacementWithBacktick), + ); + expect(resultWithBacktick).toBe('This is Backtick-$`-Value for you.'); + + // $': 置換対象文字列の後続部分 + const resultWithSingleQuote = input.replaceAll( + '$TEMPORARY_PASSWORD$', + escapeDollar(replacementWithSingleQuote), + ); + expect(resultWithSingleQuote).toBe("This is Single-$'-Value for you."); + }); +});