deakjahn / flutter_dropzone

A drag-and-drop Flutter plugin (Web). Only web and only from outside into Flutter.
https://pub.dev/packages/flutter_dropzone
90 stars 41 forks source link

Not working when "unsafe-inline" is not allowed by CSP #12

Closed CrisNegoianu closed 3 years ago

CrisNegoianu commented 3 years ago

Hi.

First, let me say, that I love this library. I think drag and drop is great in an application, so I really want to use it.

A content security policy is an essential tool in keeping a web app safe, so I would really like to keep it as tight as possible. For this reason my CSP does not allow "unsafe-inline" javascript to be executed. The library works great when I develop locally, but when I deploy and the CSP is used, I get the following error:

"Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' 'unsafe-eval'". Either the 'unsafe-inline' keyword, a hash ('sha256-/fuo3kxZwTXT/bT68lC/SmVk4//eXc7bLZbwKq3ZNqg='), or a nonce ('nonce-...') is required to enable inline execution."

If would be great if you could make it work with all javascript being in your js file, rather then using inline javascript.

Thank you for your work.

deakjahn commented 3 years ago

To tell you frankly, I have no idea whatsoever what part this references to. :-) Can you get a pointer from the error message or some console to the exact part? Apart from that, all I use are very standard approaches of Flutter Web, so unless it is something very specific that we can pinpoint, well... :-))

Come to think of it, it might be this line:

..append(ScriptElement()..text = 'flutter_dropzone_web.triggerBuild($viewId);')

If it is, I have currently no idea how we could circumvent this. I'm not aware of any other approach that can trigger an event when the corresponding element gets actually built in the DOM.

CrisNegoianu commented 3 years ago

Thanks for looking into this.

The offending line in the compiled main.js is this: containerElement.insertBefore(childElement, refNode);

And there's the context around it:

     for (t1 = this.__engine$_children, i = t1.length - 1, t2 = type$.HtmlElement, refNode = null; i >= 0; --i, refNode = childElement) {
        indexMapNew.toString;
        isStationary = C.JSArray_methods.indexOf$1(indexMapNew, i) !== -1 && C.JSArray_methods.contains$1(stationaryIndices, i);
        childElement = t2._as(t1[i].rootElement);
        if (!isStationary)
          if (refNode == null)
            containerElement.appendChild(childElement);
          else
            containerElement.insertBefore(childElement, refNode);
      }
    },
deakjahn commented 3 years ago

Is this debug or release? Where should I look for this main.js? I can only see a main.dart.js right now but that only has a dozen or so standard lines.

Is this your app using the plugin or the example of the plugin itself?

CrisNegoianu commented 3 years ago

It's released, but I build it like this: flutter build web --csp --profile temporarily adding the --profile parameter so the code is more readable.

deakjahn commented 3 years ago

Well, not yet. Is this the plugin example? I found a different main.dart.js now that might be similar to what I mentioned but no trace of a code like this in there. Also, I'm on flutter-beta there, as the minimum needed for web support.

CrisNegoianu commented 3 years ago

Yeah, the file is main.dart.js, I made a mistake earlier. I am using the beta channel, because as you say, web support is only available in there.

deakjahn commented 3 years ago

Then couldn't it be not the plugin example but a different app, yours? Because I can't seem to find any reference like that. No isStationary in the whole file for me.

deakjahn commented 3 years ago

Ooops, the CSP did it.

CrisNegoianu commented 3 years ago

The reason I know it's the plugin is because when I remove the DropzoneView the error no longer happens. I also tried changing where I place it. I even left it by itself on the page and the error still happens. I think you're right in thinking that it has to do with this line: ..append(ScriptElement()..text = 'flutter_dropzone_web.triggerBuild($viewId);')

I think what is happening is: doing it this way, it's trying to inject the plugin's javascript file with inline javascript, rather then reference it from the head html element, like I do with the rest of the javascript files used.

deakjahn commented 3 years ago

Well, you could comment out that line, it won't work, of course, but if it doesn't give an error then...

deakjahn commented 3 years ago

I wonder how on earth we could run --csp from under the IDE. It doesn't accept it if I just add it to the arguments (I'm using Android Studio but I don't think it would depend on this).

deakjahn commented 3 years ago

I'll try to see if this approach works.

CrisNegoianu commented 3 years ago

Cool. Thanks. On the running with --csp question, I don't believe that's possible. That flag is only used when building for release, not when running in debug mode.

deakjahn commented 3 years ago

Yes, I tried release, but that doesn't work from the IDE, either.

deakjahn commented 3 years ago

That didn't work. However, I found an idea that's pretty darn sick. :-) https://keithclark.co.uk/articles/working-with-elements-before-the-dom-is-ready/

Check out the modification.

CrisNegoianu commented 3 years ago

That looks really good, I believe it should work.

One minor suggestion: In file: flutter_dropzone_plugin.dart on line 39 where you add the script (beautiful piece of code, I must say :)) please also set the defer property to true. This will cause it to load just after the main application bundle, and not delay it's load.

Appreciate the time you spent on this. Once you've released it to pub.dev, I'll give it a go and let you know if it's working when released with CSP.