Skip to content

Commit

Permalink
Do not attempt string replacements on binary files (#1405)
Browse files Browse the repository at this point in the history
* fix: skip replacements on binary files

* chore: test tweak
  • Loading branch information
iowillhoit authored Aug 26, 2024
1 parent 7996ca8 commit 5f819ec
Show file tree
Hide file tree
Showing 9 changed files with 61 additions and 31 deletions.
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

0 comments on commit 5f819ec

Please sign in to comment.