legacyAttachments.js 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import { Meteor } from 'meteor/meteor';
  2. import { WebApp } from 'meteor/webapp';
  3. import { ReactiveCache } from '/imports/reactiveCache';
  4. import { getAttachmentWithBackwardCompatibility, getOldAttachmentStream } from '/models/lib/attachmentBackwardCompatibility';
  5. // Ensure this file is loaded
  6. if (process.env.DEBUG === 'true') {
  7. console.log('Legacy attachments route loaded');
  8. }
  9. /**
  10. * Legacy attachment download route for CollectionFS compatibility
  11. * Handles downloads from old CollectionFS structure
  12. */
  13. if (Meteor.isServer) {
  14. // Handle legacy attachment downloads
  15. WebApp.connectHandlers.use('/cfs/files/attachments', (req, res, next) => {
  16. const attachmentId = req.url.split('/').pop();
  17. if (!attachmentId) {
  18. res.writeHead(404);
  19. res.end('Attachment not found');
  20. return;
  21. }
  22. try {
  23. // Try to get attachment with backward compatibility
  24. const attachment = getAttachmentWithBackwardCompatibility(attachmentId);
  25. if (!attachment) {
  26. res.writeHead(404);
  27. res.end('Attachment not found');
  28. return;
  29. }
  30. // Check permissions
  31. const board = ReactiveCache.getBoard(attachment.meta.boardId);
  32. if (!board) {
  33. res.writeHead(404);
  34. res.end('Board not found');
  35. return;
  36. }
  37. // Check if user has permission to download
  38. const userId = Meteor.userId();
  39. if (!board.isPublic() && (!userId || !board.hasMember(userId))) {
  40. res.writeHead(403);
  41. res.end('Access denied');
  42. return;
  43. }
  44. // Set appropriate headers
  45. res.setHeader('Content-Type', attachment.type || 'application/octet-stream');
  46. res.setHeader('Content-Length', attachment.size || 0);
  47. // Force attachment disposition for SVG files to prevent XSS attacks
  48. const isSvgFile = attachment.name && attachment.name.toLowerCase().endsWith('.svg');
  49. const disposition = isSvgFile ? 'attachment' : 'attachment'; // Always use attachment for legacy files
  50. res.setHeader('Content-Disposition', `${disposition}; filename="${attachment.name}"`);
  51. // Add security headers for SVG files
  52. if (isSvgFile) {
  53. res.setHeader('Content-Security-Policy', "default-src 'none'; script-src 'none'; object-src 'none';");
  54. res.setHeader('X-Content-Type-Options', 'nosniff');
  55. res.setHeader('X-Frame-Options', 'DENY');
  56. }
  57. // Get GridFS stream for legacy attachment
  58. const fileStream = getOldAttachmentStream(attachmentId);
  59. if (fileStream) {
  60. res.writeHead(200);
  61. fileStream.pipe(res);
  62. } else {
  63. res.writeHead(404);
  64. res.end('File not found in GridFS');
  65. }
  66. } catch (error) {
  67. console.error('Error serving legacy attachment:', error);
  68. res.writeHead(500);
  69. res.end('Internal server error');
  70. }
  71. });
  72. }