Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions src/utils/intuitAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
QBItemsResponseSchema,
SingleIdAndTokenResponseSchema,
} from '@/type/dto/intuitAPI.dto'
import { escapeForQBQuery } from '@/utils/string'
import { RetryableError } from '@/utils/error'
import CustomLogger from '@/utils/logger'
import httpStatus from 'http-status'
Expand Down Expand Up @@ -282,7 +283,8 @@ export default class IntuitAPI {
)
}

const sanitizedDisplayName = displayName && displayName.trim()
const sanitizedDisplayName =
displayName && escapeForQBQuery(displayName.trim())
let queryCondition = sanitizedDisplayName
? `DisplayName IN ('${sanitizedDisplayName}', '${this.getNameWithDeleted(sanitizedDisplayName)}')`
: `Id = '${id}'`
Expand Down Expand Up @@ -317,7 +319,7 @@ export default class IntuitAPI {
obj: { email },
message: `IntuitAPI#getCustomerByEmail | Customer query start for realmId: ${this.tokens.intuitRealmId}. Email: ${email}`,
})
const customerQuery = `SELECT Id, SyncToken, Active, CompanyName, PrimaryEmailAddr FROM Customer WHERE PrimaryEmailAddr = '${email}' AND Active in (true, false)`
const customerQuery = `SELECT Id, SyncToken, Active, CompanyName, PrimaryEmailAddr FROM Customer WHERE PrimaryEmailAddr = '${escapeForQBQuery(email)}' AND Active in (true, false)`
const qbCustomers = await this.customQuery(customerQuery)

if (!qbCustomers) return
Expand Down Expand Up @@ -361,7 +363,7 @@ export default class IntuitAPI {
)
}

const sanitizedName = name && name.trim()
const sanitizedName = name && escapeForQBQuery(name.trim())
let queryCondition = sanitizedName
? `Name IN ('${sanitizedName}', '${this.getNameWithDeleted(sanitizedName)}')`
: `Id = '${id}'`
Expand Down Expand Up @@ -578,7 +580,7 @@ export default class IntuitAPI {
obj: { invoiceNumber },
message: `IntuitAPI#getInvoice | invoice query start for realmId: ${this.tokens.intuitRealmId}. `,
})
const query = `select Id, SyncToken, DocNumber from Invoice where DocNumber = '${invoiceNumber}' maxresults 1`
const query = `select Id, SyncToken, DocNumber from Invoice where DocNumber = '${escapeForQBQuery(invoiceNumber)}' maxresults 1`
const invoice = await this.customQuery(query)

if (!invoice)
Expand Down Expand Up @@ -724,7 +726,8 @@ export default class IntuitAPI {
message: 'IntuitAPI#getAnAccount | Account query start for realmId: ',
})

const sanitizedAccountName = accountName && accountName.trim()
const sanitizedAccountName =
accountName && escapeForQBQuery(accountName.trim())
let queryCondition = sanitizedAccountName
? `Name IN ('${sanitizedAccountName}', '${this.getNameWithDeleted(sanitizedAccountName)}')`
: `Id = '${id}'`
Expand Down
10 changes: 9 additions & 1 deletion src/utils/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ export function replaceBeforeParens(
}
}

/**
* Escapes single quotes for use in QBO query strings.
* QBO query language uses backslash to escape single quotes: \\'
*/
export function escapeForQBQuery(input: string) {
return input.replace(/'/g, "\\'")
}

export function replaceSpecialCharsForQB(input: string) {
// list of allowed characters in QB.
// Doc: https://quickbooks.intuit.com/learn-support/en-us/help-article/account-management/acceptable-characters-quickbooks-online/L3CiHlD9J_US_en_US
Expand All @@ -37,7 +45,7 @@ export function replaceSpecialCharsForQB(input: string) {
'@',
'&',
'!',
// "'", even though included as allowed list in above docs, single quote is not allowed as this throws error.
"'",
'*',
'(',
')',
Expand Down
Loading