Skip to main content
Glama

PDF Reader MCP Server

by pablontiv
validation.ts4.56 kB
import { promises as fs } from 'fs'; import path from 'path'; import { getConfig } from '../config/server-config.js'; export class ValidationError extends Error { constructor(message: string, public code: string) { super(message); this.name = 'ValidationError'; } } export async function validateFilePath(filePath: string): Promise<void> { if (!filePath || typeof filePath !== 'string') { throw new ValidationError('File path must be a non-empty string', 'INVALID_PATH'); } // Check for security violations in path const hasDirectoryTraversal = filePath.includes('..') || filePath.includes('~'); // eslint-disable-next-line no-control-regex const hasControlChars = /[\x00-\x1f\x7f-\x9f]/.test(filePath); const hasNTFSStreams = filePath.includes(':') && process.platform === 'win32' && !path.isAbsolute(filePath); // Check for Windows reserved names const basename = path.basename(filePath, path.extname(filePath)); const windowsReserved = ['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'LPT1', 'LPT2']; const hasReservedName = windowsReserved.includes(basename.toUpperCase()); // Check for encoded directory traversal attempts let hasEncodedTraversal = false; try { const decodedPath = decodeURIComponent(filePath); hasEncodedTraversal = decodedPath.includes('..') || decodedPath.includes('~'); } catch (error) { hasEncodedTraversal = true; // Invalid URI encoding is suspicious } // Check for absolute paths to sensitive system directories const isSensitiveAbsolutePath = path.isAbsolute(filePath) && ( filePath.toLowerCase().includes('/etc/') || filePath.toLowerCase().includes('/root/') || filePath.toLowerCase().includes('/proc/') || filePath.toLowerCase().includes('/dev/') || filePath.toLowerCase().includes('c:\\windows\\') || filePath.toLowerCase().includes('/var/log/') || filePath.toLowerCase().includes('/sys/') ); // Throw generic security error for any violation if (hasDirectoryTraversal || hasControlChars || hasNTFSStreams || hasReservedName || hasEncodedTraversal || isSensitiveAbsolutePath) { throw new ValidationError('Invalid file path', 'SECURITY_VIOLATION'); } const resolvedPath = path.resolve(filePath); try { const stats = await fs.stat(resolvedPath); if (!stats.isFile()) { throw new ValidationError('Path does not point to a file', 'NOT_A_FILE'); } const config = getConfig(); if (stats.size > config.maxFileSize) { throw new ValidationError( `File size ${stats.size} exceeds maximum allowed size ${config.maxFileSize}`, 'FILE_TOO_LARGE' ); } } catch (error) { if (error instanceof ValidationError) { throw error; } throw new ValidationError('File not found or inaccessible', 'FILE_NOT_FOUND'); } } export async function validatePDFFile(filePath: string): Promise<void> { await validateFilePath(filePath); try { const buffer = await fs.readFile(filePath); if (buffer.length < 4) { throw new ValidationError('File is too small to be a valid PDF', 'INVALID_PDF'); } const header = buffer.subarray(0, 4).toString(); if (header !== '%PDF') { throw new ValidationError('File does not appear to be a PDF (invalid magic number)', 'INVALID_PDF'); } } catch (error) { if (error instanceof ValidationError) { throw error; } throw new ValidationError('Error reading PDF file', 'READ_ERROR'); } } export function parsePageRange(pageRange: string, totalPages: number): number[] { if (!pageRange || pageRange.toLowerCase() === 'all') { return Array.from({ length: totalPages }, (_, i) => i + 1); } const pages: number[] = []; const parts = pageRange.split(','); for (const part of parts) { const trimmed = part.trim(); if (trimmed.includes('-')) { const [start, end] = trimmed.split('-').map(s => parseInt(s.trim())); if (isNaN(start) || isNaN(end) || start < 1 || end < start || end > totalPages) { throw new ValidationError(`Invalid page range: ${trimmed}`, 'INVALID_PAGE_RANGE'); } for (let i = start; i <= end; i++) { pages.push(i); } } else { const pageNum = parseInt(trimmed); if (isNaN(pageNum) || pageNum < 1 || pageNum > totalPages) { throw new ValidationError(`Invalid page number: ${trimmed}`, 'INVALID_PAGE_NUMBER'); } pages.push(pageNum); } } return Array.from(new Set(pages)).sort((a, b) => a - b); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/pablontiv/pdf-reader-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server