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
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Injectable } from '@nestjs/common';
import { BadRequestException, Injectable } from '@nestjs/common';
import { UserJobEntity, UserJobRepository } from '@impler/dal';
import { CreateUserJobCommand } from './create-userjob.command';
import { APIMessages } from '@shared/constants';
import { BadRequestException } from '@nestjs/common';
import { RSSXMLService } from '@impler/services';
import { isValidXMLMimeType } from '@shared/helpers/common.helper';
import { getMimeType, isValidXMLMimeType } from '@shared/helpers/common.helper';
import { WebSocketService } from '@shared/services';

@Injectable()
Expand All @@ -24,10 +23,11 @@ export class CreateUserJob {
}: CreateUserJobCommand): Promise<UserJobEntity> {
const rssService = new RSSXMLService(url);

const mimeType = await rssService.getMimeType(url);
try {
const mimeType = await getMimeType(url);
console.log('mime type is >>', mimeType);

if (isValidXMLMimeType(mimeType)) {
try {
if (isValidXMLMimeType(mimeType)) {
const abortController = new AbortController();

this.webSocketService.registerSessionAbort(webSocketSessionId, abortController);
Expand All @@ -54,9 +54,11 @@ export class CreateUserJob {
_templateId: _templateId,
externalUserId: externalUserId || (formattedExtra as unknown as Record<string, any>)?.externalUserId,
});
} catch (error) {}
} else {
throw new BadRequestException(APIMessages.INVALID_RSS_URL);
} else {
throw new BadRequestException(APIMessages.INVALID_RSS_URL);
}
} catch (error) {
throw new BadRequestException(error);
}
}
}
54 changes: 54 additions & 0 deletions apps/api/src/app/shared/helpers/common.helper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as Sentry from '@sentry/node';
import axios from 'axios';
import { BadRequestException } from '@nestjs/common';
import { APIMessages } from '../constants';
import { PaginationResult, Defaults, FileMimeTypesEnum } from '@impler/shared';
Expand Down Expand Up @@ -95,3 +96,56 @@ export function isValidXMLMimeType(mimeType: string): boolean {

return false;
}

export const getMimeType = async (url: string): Promise<string | null> => {
try {
if (!isUrlSafe(url)) {
throw new BadRequestException('Invalid URL');
}

const response = await axios.head(url, {
timeout: 10000,
maxRedirects: 3,
});

const mimeType = response.headers['content-type'] || null;

return mimeType?.split(';')[0] || null;
} catch (error) {
throw error;
}
};

export function isUrlSafe(url: string): boolean {
try {
const parsedUrl = new URL(url);

// Only allow HTTP and HTTPS
if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
return false;
}

// Block localhost and private IPs (except in development)
const hostname = parsedUrl.hostname.toLowerCase();
const isDevelopment = process.env.NODE_ENV === 'dev';

if (!isDevelopment && ['localhost', '127.0.0.1', '::1'].includes(hostname)) {
return false;
}

if (
hostname.startsWith('10.') ||
hostname.startsWith('192.168.') ||
hostname.startsWith('169.254.') ||
/^172\.(1[6-9]|2[0-9]|3[01])\./.test(hostname)
) {
return false;
}

return true;
} catch (error) {
return false;
}
}

export default { getMimeType };
11 changes: 0 additions & 11 deletions libs/services/src/rss-xml/rssxml.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -584,15 +584,4 @@ export class RSSXMLService {
throw error;
}
}

async getMimeType(url: string): Promise<string | null> {
try {
const response = await axios.head(url);
const mimeType = response.headers['content-type'] || null;

return mimeType?.split(';')[0] || null;
} catch (error) {
return null;
}
}
}
Loading