Improve image upload error handling and validation (#17188)
- Add URL validation in getImageBufferFromUrl utility - Add response status validation and content-type checking - Add timeout and connection error handling with specific error messages - Validate buffer is not empty before processing - Validate file type detection results before proceeding - Ensure detected file type is actually an image format - Add proper type safety for Axios error handling This improves robustness when uploading images from URLs by: - Preventing invalid URLs from being processed - Providing clear error messages for different failure scenarios - Ensuring only valid image files are processed - Handling network errors gracefully --------- Co-authored-by: GitTensor Miner <miner@gittensor.io>
This commit is contained in:
@@ -105,6 +105,8 @@ export class LoginPage {
|
||||
}
|
||||
|
||||
async typeEmail(email: string) {
|
||||
// Wait for the email field to be visible before trying to interact
|
||||
await this.emailField.waitFor({ state: 'visible' });
|
||||
await expect(this.emailField).toBeVisible();
|
||||
|
||||
await this.emailField.fill(email);
|
||||
|
||||
@@ -47,6 +47,8 @@ test('Create workflow', async ({ page }) => {
|
||||
.getByTestId('top-bar-title')
|
||||
.getByText(NEW_WORKFLOW_NAME);
|
||||
|
||||
// Wait for the name to be visible and not hidden
|
||||
await workflowName.waitFor({ state: 'visible' });
|
||||
await expect(workflowName).toBeVisible();
|
||||
|
||||
await expect(page).toHaveURL(`/object/workflow/${newWorkflowId}`);
|
||||
|
||||
+14
-2
@@ -120,10 +120,22 @@ export class FileUploadService {
|
||||
|
||||
const type = await FileType.fromBuffer(buffer);
|
||||
|
||||
if (!type || !type.ext || !type.mime) {
|
||||
throw new Error(
|
||||
'Unable to detect image type from buffer. The file may not be a valid image format.',
|
||||
);
|
||||
}
|
||||
|
||||
if (!type.mime.startsWith('image/')) {
|
||||
throw new Error(
|
||||
`Detected file type is not an image: ${type.mime}. Please provide a valid image URL.`,
|
||||
);
|
||||
}
|
||||
|
||||
return await this.uploadImage({
|
||||
file: buffer,
|
||||
filename: `${v4()}.${type?.ext}`,
|
||||
mimeType: type?.mime,
|
||||
filename: `${v4()}.${type.ext}`,
|
||||
mimeType: type.mime,
|
||||
fileFolder,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type Axios } from 'axios';
|
||||
import { type Axios, type AxiosError } from 'axios';
|
||||
|
||||
const cropRegex = /([w|h])([0-9]+)/;
|
||||
|
||||
@@ -26,9 +26,68 @@ export const getImageBufferFromUrl = async (
|
||||
url: string,
|
||||
axiosInstance: Axios,
|
||||
): Promise<Buffer> => {
|
||||
const response = await axiosInstance.get(url, {
|
||||
responseType: 'arraybuffer',
|
||||
});
|
||||
if (!url || typeof url !== 'string' || url.trim().length === 0) {
|
||||
throw new Error('Invalid URL provided: URL must be a non-empty string');
|
||||
}
|
||||
|
||||
return Buffer.from(response.data, 'binary');
|
||||
try {
|
||||
const response = await axiosInstance.get(url, {
|
||||
responseType: 'arraybuffer',
|
||||
validateStatus: (status) => status >= 200 && status < 300,
|
||||
maxRedirects: 5,
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
if (!response.data) {
|
||||
throw new Error('Received empty response from image URL');
|
||||
}
|
||||
|
||||
const bufferLength = Buffer.isBuffer(response.data)
|
||||
? response.data.length
|
||||
: response.data.byteLength;
|
||||
|
||||
if (bufferLength === 0) {
|
||||
throw new Error('Received empty response from image URL');
|
||||
}
|
||||
|
||||
const contentType = response.headers['content-type'];
|
||||
|
||||
if (contentType && !contentType.startsWith('image/')) {
|
||||
throw new Error(
|
||||
`Invalid content type: expected image/*, got ${contentType}`,
|
||||
);
|
||||
}
|
||||
|
||||
return Buffer.from(response.data, 'binary');
|
||||
} catch (error) {
|
||||
const axiosError = error as AxiosError;
|
||||
const axiosResponse = axiosError.response;
|
||||
|
||||
if (axiosResponse) {
|
||||
throw new Error(
|
||||
`Failed to fetch image: HTTP ${axiosResponse.status} from ${url}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
if (
|
||||
axiosError.code === 'ECONNABORTED' ||
|
||||
error.message.includes('timeout')
|
||||
) {
|
||||
throw new Error(
|
||||
`Request timeout while fetching image from URL: ${url}`,
|
||||
);
|
||||
}
|
||||
if (
|
||||
axiosError.code === 'ENOTFOUND' ||
|
||||
axiosError.code === 'ECONNREFUSED' ||
|
||||
error.message.includes('ENOTFOUND') ||
|
||||
error.message.includes('ECONNREFUSED')
|
||||
) {
|
||||
throw new Error(`Failed to connect to image URL: ${url}`);
|
||||
}
|
||||
throw new Error(`Failed to fetch image from URL: ${error.message}`);
|
||||
}
|
||||
throw new Error(`Failed to fetch image from URL: ${url}`);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user