Skip to content

Commit

Permalink
Decode filenames from any URLs before using them
Browse files Browse the repository at this point in the history
  • Loading branch information
ClementPasteau committed Dec 5, 2023
1 parent 8be1961 commit 0be1a62
Show file tree
Hide file tree
Showing 13 changed files with 160 additions and 62 deletions.
6 changes: 4 additions & 2 deletions newIDE/app/src/AssetStore/InstallAsset.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
type Asset,
isPixelArt,
isPublicAssetResourceUrl,
extractFilenameWithExtensionFromPublicAssetResourceUrl,
extractDecodedFilenameWithExtensionFromPublicAssetResourceUrl,
} from '../Utils/GDevelopServices/Asset';
import newNameGenerator from '../Utils/NewNameGenerator';
import { unserializeFromJSObject } from '../Utils/Serializer';
Expand Down Expand Up @@ -81,7 +81,9 @@ export const installResource = (
const resourceOriginCleanedName: string = isPublicAssetResourceUrl(
resourceFileUrl
)
? extractFilenameWithExtensionFromPublicAssetResourceUrl(resourceFileUrl)
? extractDecodedFilenameWithExtensionFromPublicAssetResourceUrl(
resourceFileUrl
)
: resourceOriginRawName;
const resourceOriginIdentifier: string = serializedResource.origin
? serializedResource.origin.identifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import { type FileMetadata } from '../index';
import { type AuthenticatedUser } from '../../Profile/AuthenticatedUserContext';
import {
extractFilenameWithExtensionFromProductAuthorizedUrl,
extractDecodedFilenameWithExtensionFromProductAuthorizedUrl,
isProductAuthorizedResourceUrl,
} from '../../Utils/GDevelopServices/Shop';
import {
Expand Down Expand Up @@ -64,7 +64,7 @@ export const moveUrlResourcesToCloudFilesIfPrivate = async ({
if (isProductAuthorizedResourceUrl(resourceFile)) {
// This is a file that is temporarily accessible thanks to a token,
// so it should be downloaded and stored in the Cloud resources.
const filenameWithExtension = extractFilenameWithExtensionFromProductAuthorizedUrl(
const filenameWithExtension = extractDecodedFilenameWithExtensionFromProductAuthorizedUrl(
resourceFile
);
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
getCredentialsForCloudProject,
type UploadedProjectResourceFiles,
uploadProjectResourceFiles,
extractFilenameFromProjectResourceUrl,
extractDecodedFilenameFromProjectResourceUrl,
extractProjectUuidFromProjectResourceUrl,
} from '../../Utils/GDevelopServices/Project';
import { checkIfIsGDevelopCloudBucketUrl } from '../../Utils/CrossOrigin';
Expand All @@ -19,7 +19,7 @@ import {
} from '../../Utils/BlobDownloader';
import { isBlobURL, isURL } from '../../ResourcesList/ResourceUtils';
import {
extractFilenameWithExtensionFromProductAuthorizedUrl,
extractDecodedFilenameWithExtensionFromProductAuthorizedUrl,
fetchTokenForPrivateGameTemplateAuthorizationIfNeeded,
isPrivateGameTemplateResourceAuthorizedUrl,
} from '../../Utils/GDevelopServices/Shop';
Expand Down Expand Up @@ -91,7 +91,9 @@ export const moveUrlResourcesToCloudProject = async ({
resourceToFetchAndUpload.push({
resource,
url: resourceFile,
filename: extractFilenameFromProjectResourceUrl(resourceFile),
filename: extractDecodedFilenameFromProjectResourceUrl(
resourceFile
),
});
} else if (
isPrivateGameTemplateResourceAuthorizedUrl(resourceFile) &&
Expand All @@ -108,7 +110,7 @@ export const moveUrlResourcesToCloudProject = async ({
resourceToFetchAndUpload.push({
resource,
url: encodedResourceUrl,
filename: extractFilenameWithExtensionFromProductAuthorizedUrl(
filename: extractDecodedFilenameWithExtensionFromProductAuthorizedUrl(
resourceFile
),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
} from '../../Utils/BlobDownloader';
import { useGenericRetryableProcessWithProgress } from '../../Utils/UseGenericRetryableProcessWithProgress';
import { checkIfIsGDevelopCloudBucketUrl } from '../../Utils/CrossOrigin';
import { extractFilenameFromProjectResourceUrl } from '../../Utils/GDevelopServices/Project';
import { extractDecodedFilenameFromProjectResourceUrl } from '../../Utils/GDevelopServices/Project';
import {
archiveFiles,
type BlobFileDescriptor,
Expand Down Expand Up @@ -89,7 +89,9 @@ export const downloadResourcesAsBlobs = async ({
return {
resource,
url: resourceFile,
filename: extractFilenameFromProjectResourceUrl(resourceFile),
filename: extractDecodedFilenameFromProjectResourceUrl(
resourceFile
),
};
} else {
// Public URL resource: nothing to do.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import { retryIfFailed } from '../../Utils/RetryIfFailed';
import newNameGenerator from '../../Utils/NewNameGenerator';
import { type FileMetadata } from '../index';
import {
extractFilenameWithExtensionFromProductAuthorizedUrl,
extractDecodedFilenameWithExtensionFromProductAuthorizedUrl,
fetchTokenForPrivateGameTemplateAuthorizationIfNeeded,
isPrivateGameTemplateResourceAuthorizedUrl,
isProductAuthorizedResourceUrl,
} from '../../Utils/GDevelopServices/Shop';
import {
extractFilenameWithExtensionFromPublicAssetResourceUrl,
extractDecodedFilenameWithExtensionFromPublicAssetResourceUrl,
isPublicAssetResourceUrl,
} from '../../Utils/GDevelopServices/Asset';
import {
Expand All @@ -21,7 +21,7 @@ import {
} from '../../ResourcesList/ResourceUtils';
import { sanitizeFilename } from '../../Utils/Filename';
import { type AuthenticatedUser } from '../../Profile/AuthenticatedUserContext';
import { extractFilenameFromProjectResourceUrl } from '../../Utils/GDevelopServices/Project';
import { extractDecodedFilenameFromProjectResourceUrl } from '../../Utils/GDevelopServices/Project';
import axios from 'axios';
const electron = optionalRequire('electron');
const ipcRenderer = electron ? electron.ipcRenderer : null;
Expand Down Expand Up @@ -136,17 +136,19 @@ export const moveUrlResourcesToLocalFiles = async ({
let filename;
if (isProductAuthorizedResourceUrl(resourceFile)) {
// Resource is coming from a private asset or private game template.
filename = extractFilenameWithExtensionFromProductAuthorizedUrl(
filename = extractDecodedFilenameWithExtensionFromProductAuthorizedUrl(
resourceFile
);
} else if (isPublicAssetResourceUrl(resourceFile)) {
// Resource is coming from a public asset.
filename = extractFilenameWithExtensionFromPublicAssetResourceUrl(
filename = extractDecodedFilenameWithExtensionFromPublicAssetResourceUrl(
resourceFile
);
} else {
// Resource is a project resource or a generic url.
filename = extractFilenameFromProjectResourceUrl(resourceFile);
filename = extractDecodedFilenameFromProjectResourceUrl(
resourceFile
);
}

// Find a new file for the resource to download.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ const productAuthorizedUrl =
const encodedProductAuthorizedUrl =
'https://private-assets.gdevelop.io/a2adcae7-ceba-4c0d-ad0f-411bf83692ea/resources/Misc/stars_levels%20(3)%20%E6%B1%89%E5%AD%97.png?token=eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnZGV2ZWxvcC1zaG9wLWFwaSIsImF1ZCI6IjNwejJvWEZHSmVTaTVyVjROQ0pkclU4MjVUVDIiLCJleHAiOjE2NjY5NjM5NDY1OTUsInN1YiI6WyJhMmFkY2FlNy1jZWJhLTRjMGQtYWQwZi00MTFiZjgzNjkyZWEiLCJjM2ZmZjUyZS1lMTZjLTQxMTYtYTYzNS03ZjUzOGRmN2Y1YWEiXX0%3D.WY0V%2B2ypgT0PEWPUKVPSaiazKNfl4ib%2Bf89CpgcdxGo';
const publicResourceUrl =
'https://asset-resources.gdevelop.io/public-resources/16x16 Dungeon Tileset/Armor/0a130324cd2501a97027b518b41231896a81e25034fd3a7baaca9581d079f8b6_Imp_Run_2.png';
'https://asset-resources.gdevelop.io/public-resources/16x16 Dungeon Tileset/Armor/0a130324cd2501a97027b518b41231896a81e25034fd3a7baaca9581d079f8b6_Imp Run 2.png';
const encodedPublicResourceUrl =
'https://asset-resources.gdevelop.io/public-resources/16x16%20Dungeon%20Tileset/Armor/0a130324cd2501a97027b518b41231896a81e25034fd3a7baaca9581d079f8b6_Imp_Run_2.png';
'https://asset-resources.gdevelop.io/public-resources/16x16%20Dungeon%20Tileset/Armor/0a130324cd2501a97027b518b41231896a81e25034fd3a7baaca9581d079f8b6_Imp%20Run%202.png';
const localFileUrl = 'some-local-file.png';
const blobUrl = 'blob:http://something.com/1234567';

Expand Down Expand Up @@ -171,7 +171,7 @@ describe('LocalResourceMover', () => {
).toHaveBeenCalledWith(
'local-file-download',
encodedPublicResourceUrl,
path.join('assets', 'Imp_Run_2.png')
path.join('assets', 'Imp Run 2.png')
);
expect(fetchedResources.erroredResources).toEqual([]);
});
Expand Down Expand Up @@ -212,7 +212,7 @@ describe('LocalResourceMover', () => {
).toHaveBeenCalledWith(
'local-file-download',
encodedPublicResourceUrl,
path.join('assets', 'Imp_Run_2.png')
path.join('assets', 'Imp Run 2.png')
);
expect(fetchedResources.erroredResources).toEqual([
{ resourceName: 'MyResourceToDownload', error: expect.any(Error) },
Expand Down Expand Up @@ -260,7 +260,7 @@ describe('LocalResourceMover', () => {
).toHaveBeenCalledWith(
'local-file-download',
encodedPublicResourceUrl,
path.join('assets', 'Imp_Run_2.png')
path.join('assets', 'Imp Run 2.png')
);
expect(fetchedResources.erroredResources).toEqual([]);
});
Expand Down
4 changes: 2 additions & 2 deletions newIDE/app/src/ResourcesList/BrowserResourceSources.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import axios from 'axios';
import AlertMessage from '../UI/AlertMessage';
import { FileToCloudProjectResourceUploader } from './FileToCloudProjectResourceUploader';
import {
extractFilenameWithExtensionFromPublicAssetResourceUrl,
extractDecodedFilenameWithExtensionFromPublicAssetResourceUrl,
isPublicAssetResourceUrl,
} from '../Utils/GDevelopServices/Asset';

Expand All @@ -40,7 +40,7 @@ const ResourceStoreChooser = ({
const newResource = createNewResource();
newResource.setFile(chosenResourceUrl);
const resourceCleanedName = isPublicAssetResourceUrl(chosenResourceUrl)
? extractFilenameWithExtensionFromPublicAssetResourceUrl(
? extractDecodedFilenameWithExtensionFromPublicAssetResourceUrl(
chosenResourceUrl
)
: path.basename(chosenResourceUrl);
Expand Down
7 changes: 5 additions & 2 deletions newIDE/app/src/Utils/GDevelopServices/Asset.js
Original file line number Diff line number Diff line change
Expand Up @@ -450,13 +450,16 @@ const resourceFilenameRegex = new RegExp(
GDevelopPublicAssetResourcesStorageStagingBaseUrl
)})\\/public-resources\\/(.*)\\/([a-z0-9]{64})_(.*)`
);
export const extractFilenameWithExtensionFromPublicAssetResourceUrl = (
export const extractDecodedFilenameWithExtensionFromPublicAssetResourceUrl = (
url: string
): string => {
const matches = resourceFilenameRegex.exec(url);
if (!matches) {
throw new Error('The URL is not a valid public asset resource URL: ' + url);
}
const filenameWithExtension = matches[4];
return filenameWithExtension;
const decodedFilenameWithExtension = decodeURIComponent(
filenameWithExtension
);
return decodedFilenameWithExtension;
};
42 changes: 42 additions & 0 deletions newIDE/app/src/Utils/GDevelopServices/Asset.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// @flow
import { extractDecodedFilenameWithExtensionFromPublicAssetResourceUrl } from './Asset';

describe('Asset service', () => {
describe('extractDecodedFilenameWithExtensionFromPublicAssetResourceUrl', () => {
it('throws if not the right URL format', () => {
expect(() =>
extractDecodedFilenameWithExtensionFromPublicAssetResourceUrl(
'https://private-assets.gdevelop.io/a9fe5bce-de39-4147-a669-93fc5cd69632/file-to-download.png?token=1234567890'
)
).toThrow();
});
it('works if file has an extension', () => {
expect(
extractDecodedFilenameWithExtensionFromPublicAssetResourceUrl(
'https://asset-resources.gdevelop.io/public-resources/Pack name/b540a2c4b3a4d9a856819eb522473380fa98291e2f59fcf8905d99649a5b179b_file-to-download.png'
)
).toStrictEqual('file-to-download.png');
});
it('works if file has no extension', () => {
expect(
extractDecodedFilenameWithExtensionFromPublicAssetResourceUrl(
'https://asset-resources.gdevelop.io/public-resources/Pack name/b540a2c4b3a4d9a856819eb522473380fa98291e2f59fcf8905d99649a5b179b_file-to-download'
)
).toStrictEqual('file-to-download');
});
it('works if url is encoded', () => {
expect(
extractDecodedFilenameWithExtensionFromPublicAssetResourceUrl(
'https://asset-resources.gdevelop.io/public-resources/Pack%20name/b540a2c4b3a4d9a856819eb522473380fa98291e2f59fcf8905d99649a5b179b_file%20to%20download.png'
)
).toStrictEqual('file to download.png');
});
it('works if url is not encoded', () => {
expect(
extractDecodedFilenameWithExtensionFromPublicAssetResourceUrl(
'https://asset-resources.gdevelop.io/public-resources/Pack name/b540a2c4b3a4d9a856819eb522473380fa98291e2f59fcf8905d99649a5b179b_file to download.png'
)
).toStrictEqual('file to download.png');
});
});
});
8 changes: 5 additions & 3 deletions newIDE/app/src/Utils/GDevelopServices/Project.js
Original file line number Diff line number Diff line change
Expand Up @@ -603,16 +603,18 @@ const resourceFilenameRegex = new RegExp(
)}\\/(.*)\\/resources\\/(.*\\/)*([a-zA-Z0-9]+)-([^\\?\\n\\r]+)`
);

export const extractFilenameFromProjectResourceUrl = (url: string): string => {
export const extractDecodedFilenameFromProjectResourceUrl = (
url: string
): string => {
if (url.startsWith(GDevelopProjectResourcesStorage.baseUrl)) {
const matches = resourceFilenameRegex.exec(url);
if (matches) {
return matches[4];
return decodeURIComponent(matches[4]);
}
}

if (url.lastIndexOf('/') !== -1) {
return url.substring(url.lastIndexOf('/') + 1);
return decodeURIComponent(url.substring(url.lastIndexOf('/') + 1));
}
return url;
};
Expand Down
Loading

0 comments on commit 0be1a62

Please sign in to comment.