I faced an interesting question about uploading images at the SailsJS application. I want to upload images from the frontend side to the backend side (NodeJs, SailsJS), validate the image (images), to be sure that images are doesn't include malicious data (like hidden scripts inside image content), and then send this images to AWS storage (S3 bucket)).
On the front end side, everything is done in accordance with SailsJS requirements (text fields are on the top of the form, file on the bottom).
Tools for upload:
For images upload, I use skipper (skipper-better-S3) which is a built-in module in SailsJS. Skipper extends the HTTP body parser used in Sails and Express.
To implement this, I use the code mentioned below:
Vault.read((err, result) => {
if (err) {
console.log('Vault S3 Error', err);
} else {
request.file('logo').upload({
adapter: require('skipper-better-s3'),
key: result.access_key,
secret: result.secret_key,
bucket: sails.config.s3.bucket,
maxBytes: sails.config.globals.multipleMaxLogoSize,
dirname: `some-directory/${client.id}/logo`,
s3config: {
signatureVersion: 'v4',
sslEnabled: true,
sessionToken: result.security_token
},
s3params: {
ACL: 'private',
SSEKMSKeyId: process.env.AWS_S3_KMS_KEY_ARN,
ServerSideEncryption: 'aws:kms',
}
}, (error, uploadedFiles) => {
if (uploadedFiles.length === 0 ? true : Utility.isValidImage(uploadedFiles[0].filename)) {
if (error) {
sails.log.error(error);
return response.serverError();
}
if (uploadedFiles.length > 0) {
uploadedFiles.forEach(uploadedFile => { client.logo = `/logo/${client.id}/${uploadedFile[0].fd.split('/').pop()}`;
client.displayName = request.body.displayName ? request.body.displayName : client.displayName;
client.save().then(() => {
return response.ok({});
}, (error) => {
sails.log.error(error);
return response.serverError(error);
}
)})
} else if (conditionForBadRequest) {
return response.badRequest();
}
} else {
sails.log.error('Invalid Image Type');
return response.badRequest('Invalid Image Type');
}
});
}
})
This code snippet can check if the image has the correct extension (ending), but it doesn't allow us to check if the image includes malicious scripts (Cross-Site Scripting, Persistent XSS).
Based on the mentioned above I have two questions:
How it is possible to check if the uploaded image (it content) includes malicious scripts as we know that image content can be intercepted with the help of some tools (Burp Suite)?
What is the best place for the validation of the image? Maybe it's better to do at saveAs property of request.file('logo').upload() function?
Any help appreciated.
Related
I have a PWA which works fine both online and offline (but only with the initial files). However, the offline cache (let’s say a javascript file) is not being refreshed so whenever I am offline the old javascript file is used, but when online the new version is used.
On an iPad I can use Safari to go to the website and add the PWA to the home page.
If I then go offline, it works fine – all pages work etc.
But if I make a change to say a javascript file (something like adding an alert) and also change the version in my service worker, when I am online the change is reflected but when offline it remains at the older version
To clarify let’s say from the start, on going into a page it alerts “A1”
I then change the javascript to alert “A2” and change the version in the service worker.
If I run the app when online, sure enough the app says New Update Available and All Good (some alerts from the main.js file)
Then when I go into the actual page o the alert says “A2” – so all good.
Then go offline.
The alert still says “A1”
It seems that when online it uses the server latest files but when it tries to use cache the files are old and at the moment seem to be the original files.
I have read many sites on this with no success – some suggest it will sort itself in 24 hours. Some suggest setting the maxage of the service worker to 0 (but how do you do this?). Some say the files need renaming each time they change which seems very clunky.
The service worker is definitely working
main.js
$(document).ready(function () {
'use strict';
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register("/sw.js")
.then(res => {
console.log("service worker registered");
res.onupdatefound = () => {
const installingWorker = res.installing;
installingWorker.onstatechange = () => {
switch (installingWorker.state) {
case 'installed':
if (navigator.serviceWorker.controller){
alert("new update available");
forceReload();
}
else {
alert("all good");
}
break;
}
}
}
})
.catch(err => console.log("service worker not registered", err))
}
});
const forceReload = () =>{
console.log("ForceReload");
navigator.serviceWorker
.getRegistrations()
.then((registrations) =>{
console.log(registrations);
//alert("reg");
Promise.all(registrations.map((r) => r.unregister()))
caches.keys().then(function(names) {
for (let name of names)
caches.delete(name);
});
},
)
.then(() => {setTimeout(() => {
location.reload();
}, 500);
})
}
sw.js
let version =5; // update this to send update.
var cacheName = 'cacheV5'
var filesToCache = [
'/',
'/manifest.json',
'/index.html',
'/sales10.html',
'/getdata.html',
....
....
'/js/siteJS/sales10.js',
'/js/siteJS/getdata.js',
'/js/jquery/3.4.1/jquery.min.js',
'/js/bootstrap/bootstrap.min.js',
'/js/bootstrap/popper.min.js'
];
/* Start the service worker and cache all of the app's content */
self.addEventListener('install', function(e) {
self.skipWaiting();
e.waitUntil(
caches.open(cacheName).then(function(cache) {
return cache.addAll(filesToCache);
})
);
});
/* Serve cached content when offline */
self.addEventListener('fetch', function(e) {
e.respondWith(
caches.match(stripQueryStringAndHashFromPath(e.request.url.replace(/^.*\/\/[^\/]+/, ''))).then(function(response) {
return response || fetch(e.request);
})
);
});
function stripQueryStringAndHashFromPath(url) { //added this so when url paramerters passed grabbing the cashed js works
return url.split("?")[0].split("#")[0];
}
self.addEventListener('activate', function(event) {
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.filter(function(cacheName) {
return true;
}).map(function(cacheName) {
return caches.delete(cacheName);
})
);
})
);
});
I am using React Dropzone to upload files from React to firebase as shown below:
const onDrop = useCallback((acceptedFiles, fileRejections) => {
//Check if file type is image
//Check if file size < 5MB
//Upload
if (fileRejections.length > 0) {
setError(true);
} else setError(false);
if (acceptedFiles.length > 0) {
const file = acceptedFiles[0];
console.log(file);
setFile({
...file,
preview: URL.createObjectURL(file),
});
setFileUploaded(true);
}
}, []);
and this is my upload handler:
const handleImageUpload = () => {
//Upload Image to Firebase
//Check if file exists
if (file !== null || file !== undefined) {
const storageRef = ref(
Client.storage,
`/db-dev/user-metadata/portfolio/images/first-image.jpg`
);
console.log('Process begins');
uploadBytes(storageRef, file).then((snapshot) => {
console.log('Uploaded a blob or file!');
});
}
};
these two things do the work but I believe for some reason they're not encoding or decoding the image as in firebase storage folder I see image as invalid image.
Can someone help me to understand where things are going wrong? (To make sure file is loaded properly, I am also viewing the file using: preview: URL.createObjectURL(file), and it loads correctly in browser.
For file upload I am following the latest firebase documentation
It sets file type to octet-stream not sure what that means:
Edit 1: I tried to set metadata to image/jpeg:
uploadBytes(storageRef, file, {
contentType: 'image/jpeg',
}).then((snapshot) => {
console.log('Uploaded a blob or file!');
});
But now it shows:
The problem was in this step:
setFile({
...file,
preview: URL.createObjectURL(file),
});
for some reason it wasn't spreading correctly. I changed it to:
setFile({
file:file,
preview: URL.createObjectURL(file),
});
and the upload function to:
const handleImageUpload = () => {
//Upload Image to Firebase
//Check if file exists
if (file !== null || file !== undefined) {
const storageRef = ref(
Client.storage,
`/db-dev/user-metadata/portfolio/images/first-image.jpg`
);
console.log('Process begins');
uploadBytes(storageRef, file.file, {
contentType: file.file.type,
}).then((snapshot) => {
console.log('Uploaded a blob or file!');
});
}
};
and then it worked fine. Although this was a really silly thing on my part but hope this helps someone in future
How can I store photos in mongodb and display it in my template Dynamically .
I have already created a form which stores the data of the user but I want to fetch the photo and render it through the template . Is there any way to do that ?
MongoDB only showing me C:\fakepath\33783991_259829344578817_7526307875543580672_n.jpg" ! What does that mean ? Is there any working package for meteor file except cloudinary ?
If you don't mind using a package use this one Meteor-Files
It's very easy this is an example below according to the documentation:
Upload form (template):
<template name="uploadForm">
{{#with currentUpload}}
Uploading <b>{{file.name}}</b>:
<span id="progress">{{progress.get}}%</span>
{{else}}
<input id="fileInput" type="file" />
{{/with}}
</template>
Shared code:
import { FilesCollection } from 'meteor/ostrio:files';
const Images = new FilesCollection({collectionName: 'Images'});
export default Images; // To be imported in other files
Client's code:
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
Template.uploadForm.onCreated(function () {
this.currentUpload = new ReactiveVar(false);
});
Template.uploadForm.helpers({
currentUpload() {
return Template.instance().currentUpload.get();
}
});
Template.uploadForm.events({
'change #fileInput'(e, template) {
if (e.currentTarget.files && e.currentTarget.files[0]) {
// We upload only one file, in case
// multiple files were selected
const upload = Images.insert({
file: e.currentTarget.files[0],
streams: 'dynamic',
chunkSize: 'dynamic'
}, false);
upload.on('start', function () {
template.currentUpload.set(this);
});
upload.on('end', function (error, fileObj) {
if (error) {
alert('Error during upload: ' + error);
} else {
alert('File "' + fileObj.name + '" successfully uploaded');
}
template.currentUpload.set(false);
});
upload.start();
}
}
});
By default if config.storagePath isn't passed into Constructor it equals to assets/app/uploads and relative to a running script
On development stage: yourDevAppDir/.meteor/local/build/programs/server. Note: All files will be removed as soon as your application rebuilds or you run meteor reset. To keep your storage persistent during development use an absolute path outside of your project folder, e.g. /data directory.
On production: yourProdAppDir/programs/server. Note: If using MeteorUp (MUP), Docker volumes must to be added to mup.json, see MUP usage
Hint:
You may then use the upload by base64 settings in the insert method
and listen on the onuploaded event to save in your database.
To show the image in your template you may code it like so
<img src="data:image/jpeg;base64,{{ImginBase64}}" class="img-responsive">
Read more about Data URI Scheme
Source : Documentation
You should encode your image in base64, in order to save it in a mongodb document.
Beware to not exceed the 16MB BSON format limit (or use Mongodb's GridFS). In the template you can use the base64 string of the image in the src attribute of the img.
It is better to use an Object storage service like GridFS, S3 or Google Cloud storage, and link it with your Mongo document. Alternatively, you can store your images in base64 format inside the Document itself.
https://forums.meteor.com/t/meteor-secure-file-upload-download-with-s3/38197
There are lot of packages that you can use for this.
I recommend CollectionFS .
You need to add this 3 packages and you're all set .
cfs:standard-packages
cfs:gridfs // storage adapter package . You can change this if you want.
cfs:filesystem
Let's start with Inserting Image.
1. Create ImageCollection.js in your lib folder
import { Mongo } from 'meteor/mongo';
export const BOOK = new Mongo.Collection('books');
var imageStore = new FS.Store.GridFS("images");
export const Images = new FS.Collection("images", {
stores: [imageStore]
});Images.deny({
insert: function(){
return false;
},
update: function(){
return false;
},
remove: function(){
return false;
},
download: function(){
return false;
}
});
Images.allow({
insert: function(){
return true;
},
update: function(){
return true;
},
remove: function(){
return true;
},
download: function(){
return true;
}
})
2. Import Images collection in Client and Server side.
For eg,
import {Images} from '../lib/imageCollection';
3. Add Input type "file" in form and according to your Use.
4. Create a change event in .JS file of that template.
'change #bookCover': function (event) {
event.preventDefault();
console.log("changed!")
var files = event.target.files;
for (var i = 0, ln = files.length; i < ln; i++) {
Images.insert(files[i], function (err, fileObj) {
// Inserted new doc with ID fileObj._id, and kicked off the data upload using HTTP
bookImgId=fileObj._id;
});
}
},
Check in Your Database Image will be inserted.
5. To Display Image Add this HTML where you want to see Image.
6. Add This code in Your js file where you are displaying image.
bookImage: function (id) {
// console.log(id);
var imageBook = Images.findOne({_id:id});
// console.log("img: "+imageBook);
var imageUrl = imageBook.url();
return imageUrl; // Where Images is an FS.Collection instance
}
Note : Make sure you're importing your Book collection where you want display Image.
I have seen form file upload example in ExtJS4 and i need customize progress of the file upload.
I see waitMsg config property, but i don't want use that and i don't want use extjs 3rd party plugins.
So, how i can get simply current upload progress from upload form in extjs?
The waitMsg uses a message box with an infinitely auto-updating progress bar. So you can't just create a progressbar that displays the current upload.
You could create your own Ext.ProgressBar and estimate the upload time and when its done you set it to the max value. But I guess you don't want that.
To answer your question: You cannot simply track the current upload progress.
If you really need this user experience you can try a 3rd party component.
To quote the docs:
File uploads are not performed using normal "Ajax" techniques, that is
they are not performed using XMLHttpRequests. Instead the form is
submitted in the standard manner with the DOM element
temporarily modified to have its target set to refer to a dynamically
generated, hidden which is inserted into the document but
removed after the return data has been gathered.
To show real progress you can use onprogress callback of XMLHttpRequest:
Ext.override(Ext.data.request.Ajax, {
openRequest: function (options) {
var xhr = this.callParent(arguments);
if (options.progress) {
xhr.upload.onprogress = options.progress;
}
return xhr;
}
});
Then use like here:
Ext.Ajax.request({
url: '/upload/files',
rawData: data,
headers: { 'Content-Type': null }, //to use content type of FormData
progress: function (e) {
console.log('progress', e.loaded / e.total);
}
});
See online demo here.
buttons: [{
text: 'Upload',
handler: function () {
var form = this.up('form').getForm();
if (form.isValid()) {
form.submit({
url: '/upload/file',
waitMsg: 'Uploading your file...',
success: function (f, a) {
var result = a.result,
data = result.data,
name = data.name,
size = data.size,
message = Ext.String.format('<b>Message:</b> {0}<br>' +
'<b>FileName:</b> {1}<br>' +
'<b>FileSize:</b> {2} bytes',
result.msg, name, size);
Ext.Msg.alert('Success', message);
},
failure: function (f, a) {
Ext.Msg.alert('Failure', a.result.msg);
}
});
}
}
}]
Live demo with progress window is here
I'm running into a problem with my app (ASP.NET MVC 2) where I can't upload files (images in my case). I've changed the web.config to accept up to 20MB, and I'm trying to upload a file that's only 3MB.
The app itself has two ways to upload. The initial upload which starts a Gallery and then an additional upload to append to a Gallery.
The initial works like a charm, but the appending one fails with no explanation. Even if I re-upload the initial image as an append it still fails.
I'm a little stuck on this so I would appreciate any help you guys can offer.
Thanks in advance!
EDIT
If I "hack" the form with Firebug and direct it to the initial upload Url it works, but when it's directing to the Url it should be posting to it fails...
EDIT 2
Per Rob's request, here's the code handling the initial gallery and appending image:
[HttpPost, ValidateAntiForgeryToken]
public RedirectToRouteResult PutGallery( // Move to Ajax
[Bind(Prefix = "Gallery", Include = "ClubId,EventId,RHAccountId,RHCategoryId,Year")] Gallery Gallery,
HttpPostedFileBase File) {
if (ModelState.IsValid && (File.ContentLength > 0)) {
if (Gallery.RHAccountId > 0) {
Gallery.RHUser = this.fdc.RHAccounts.Single(
a =>
(a.RHAccountId == Gallery.RHAccountId)).RHUser;
} else {
if (!this.fdc.RHUsers.Any(
u =>
(u.User.Name == Gallery.Username))) {
if (!this.fdc.Users.Any(
u =>
(u.Name == Gallery.Username))) {
Gallery.RHUser = new RHUser() {
User = new User() {
Name = Gallery.Username
}
};
} else {
Gallery.RHUser = new RHUser() {
User = this.fdc.Users.Single(
u =>
(u.Name == Gallery.Username))
};
};
} else {
Gallery.RHUser = this.fdc.RHUsers.Single(
u =>
(u.User.Name == Gallery.Username));
};
};
Image Image = new Image() {
Gallery = Gallery
};
this.fdc.Galleries.InsertOnSubmit(Gallery);
this.fdc.Images.InsertOnSubmit(Image);
this.fdc.SubmitChanges();
Files.Save(Image.ImageId, File);
return RedirectToAction("Default", "Site");
} else {
return RedirectToAction("Default", "Site");
};
}
[HttpPost, ValidateAntiForgeryToken]
public RedirectToRouteResult PutImage(
[Bind(Prefix = "Image", Include = "GalleryId")] Image Image,
HttpPostedFileBase File) {
Gallery Gallery = this.fdc.Galleries.Single(
g =>
(g.GalleryId == Image.GalleryId));
if (File.ContentLength > 0) {
this.fdc.Images.InsertOnSubmit(Image);
this.fdc.SubmitChanges();
Files.Save(Image.ImageId, File);
};
return RedirectToAction("Gallery", "Site", new {
Category = Gallery.RHCategory.Category.EncodedName,
GalleryId = Gallery.GalleryId
});
}
SIDENOTE:
Could Cassini, VS 2010's built in web server, be the cause?
Ok, so I figured it out, it only took a lengthy install of IIS locally on my machine + the configuration, to have it tell me that I miss-spelled controller as controlls in the routes.
Really annoying that it took all of that to get the real error, so Cassini was partially at fault...
So, the moral of the story is, make sure you spell everything correctly.