Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not attempt string replacements on binary files #1405

Merged
merged 2 commits into from
Aug 26, 2024
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"got": "^11.8.6",
"graceful-fs": "^4.2.11",
"ignore": "^5.3.2",
"isbinaryfile": "^5.0.2",
"jszip": "^3.10.1",
"mime": "2.6.0",
"minimatch": "^9.0.5",
Expand Down
11 changes: 9 additions & 2 deletions src/convert/replacements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
*/
import { readFile } from 'node:fs/promises';
import { Transform, Readable } from 'node:stream';
import { sep, posix, join, isAbsolute } from 'node:path';
import { sep, posix, join, isAbsolute, extname } from 'node:path';
import { Lifecycle, Messages, SfError, SfProject } from '@salesforce/core';
import { minimatch } from 'minimatch';
import { Env } from '@salesforce/kit';
import { ensureString, isString } from '@salesforce/ts-types';
import { isBinaryFileSync } from 'isbinaryfile';
import { SourcePath } from '../common/types';
import { SourceComponent } from '../resolve/sourceComponent';
import { MarkedReplacement, ReplacementConfig, ReplacementEvent } from './types';
Expand All @@ -20,14 +21,20 @@ const messages = Messages.loadMessages('@salesforce/source-deploy-retrieve', 'sd

const fileContentsCache = new Map<string, string>();

// First do a quick check for common text extensions
// If that fails, confirm that it is not a binary file
const textExtensions = new Set(['.cls', '.xml', '.json', '.js', '.css', '.html', '.htm', '.txt', '.md']);
const isTextFile = (path: string): boolean => textExtensions.has(extname(path)) || !isBinaryFileSync(path);

/** If a component has replacements, you get it piped through the replacementStream
* Otherwise, you'll get the original readable stream
* Ignore binary files, they will get corrupted in the replacement process
*/
export const getReplacementStreamForReadable = (
component: SourceComponent,
path: SourcePath
): Readable | ReplacementStream =>
component.replacements?.[path]
component.replacements?.[path] && isTextFile(path)
? component.tree.stream(path).pipe(new ReplacementStream(component.replacements?.[path]))
: component.tree.stream(path);

Expand Down
2 changes: 1 addition & 1 deletion test/collections/componentSet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ describe('ComponentSet', () => {
{ members: ['replaceStuff'], name: 'ApexClass' },
{ members: ['TestObj__c.FieldA__c'], name: 'CustomField' },
{ members: ['TestObj__c'], name: 'CustomObject' },
{ members: ['Test'], name: 'StaticResource' },
{ members: ['ImageTest', 'Test'], name: 'StaticResource' },
],
version,
});
Expand Down
24 changes: 24 additions & 0 deletions test/nuts/local/replacements/replacements.nut.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,29 @@ describe('e2e replacements test', () => {
}
}
});
it('skips images in static resources to prevent file corruption', async () => {
const srZipPath = path.join(session.project.dir, 'unzipped', 'staticresources', 'ImageTest.resource');
expect(fs.existsSync(srZipPath)).to.be.true;
const srZip = await JSZip.loadAsync(fs.readFileSync(srZipPath));

// static resource zip should have 2 files:
// 1. test-image.png, 2. test-image.resource-meta.xml
expect(Object.entries(srZip.files).length).to.equal(2);

const imageMeta = srZip.file('test-image.resource-meta.xml');
if (imageMeta && !imageMeta.dir) {
const content = await imageMeta.async('nodebuffer');
const imageMetaAsString = content.toString();
expect(imageMetaAsString).to.not.include('placeholder');
expect(imageMetaAsString).to.include('foo');
}

const image = srZip.file('test-image.png');
if (image && !image.dir) {
const content = await image.async('nodebuffer');
// The file size would be much larger if it was corrupted via the string replacement method
expect(content.byteLength).to.equal(1562121);
}
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<StaticResource xmlns="http://soap.sforce.com/2006/04/metadata">
<cacheControl>Private</cacheControl>
<contentType>application/zip</contentType>
<description>added from sfdx plugin</description>
<fullName>ImageTest</fullName>
</StaticResource>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<StaticResource xmlns="http://soap.sforce.com/2006/04/metadata">
<cacheControl>Private</cacheControl>
<contentType>image/png</contentType>
<description>Test placeholder</description>
</StaticResource>
5 changes: 5 additions & 0 deletions test/nuts/local/replacements/testProj/sfdx-project.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@
"glob": "**/*.css",
"replaceWithEnv": "THE_REPLACEMENT",
"stringToReplace": "placeholder"
},
{
"glob": "force-app/main/default/staticresources/ImageTest/**/**",
"replaceWithEnv": "THE_REPLACEMENT",
"stringToReplace": "placeholder"
}
],
"sfdcLoginUrl": "https://login.salesforce.com",
Expand Down
36 changes: 8 additions & 28 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3233,6 +3233,11 @@ isarray@~1.0.0:
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==

isbinaryfile@^5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-5.0.2.tgz#fe6e4dfe2e34e947ffa240c113444876ba393ae0"
integrity sha512-GvcjojwonMjWbTkfMpnVHVqXW/wKMYDfEpY94/8zy8HFMOqb/VL6oeONq9v87q4ttVlaTLnGXnJD4B5B1OTGIg==

isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
Expand Down Expand Up @@ -5066,16 +5071,7 @@ srcset@^5.0.0:
resolved "https://registry.yarnpkg.com/srcset/-/srcset-5.0.0.tgz#9df6c3961b5b44a02532ce6ae4544832609e2e3f"
integrity sha512-SqEZaAEhe0A6ETEa9O1IhSPC7MdvehZtCnTR0AftXk3QhY2UNgb+NApFOUPZILXk/YTDfFxMTNJOBpzrJsEdIA==

"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"

string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
Expand Down Expand Up @@ -5134,14 +5130,7 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"

"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"

strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1:
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
Expand Down Expand Up @@ -5631,7 +5620,7 @@ workerpool@^6.5.1:
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544"
integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==

"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
Expand All @@ -5649,15 +5638,6 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"

wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"

wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
Expand Down
Loading