Paste clipboard does not work with multiline text in webview

Hi all, a tricky question:

I have function which displays a very simple form in a webview. It contains a text input field and two buttons. You can write in the text field or push a button to paste the clipboard into the text field (code below).
It works fine with simple text without line break. Of the clipboard contains multi line text it does not work at all.

Any ideas?

module.exports.SingleForm = async function(m) {
const clp=Pasteboard.pasteString()
log(clp)
let strHTMLOriginal = `<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1">
  </head>
<body>
<label>${m[0]}</label>
<p>
<style>
	  .wrapper {
            padding: 5px;
        }
      textarea {
            font-size: 16px;
            width: 90%;
        }
      button {
      font-size: 16px;
            width: 90%;
      }
</style>
<div class="wrapper">
<textarea rows=${m[2]} id="taTitle">${m[1]}</textarea>
</div>
<p>
<script>
function eraseText() {
    document.getElementById("taTitle").value = "";
}
function pasteText() {
    document.getElementById("taTitle").value = "${clp}";
}
</script>
 <button type="button" onclick="javascript:eraseText();">Clear</Button>
 <button type="button" onclick="javascript:pasteText();">Clear & Paste</Button>
</body>
</html>`

let viewOne = new WebView();
viewOne.loadHTML(strHTMLOriginal);
await viewOne.present();


let taTitle  = await viewOne.evaluateJavaScript(`document.getElementById("taTitle").value`);

return taTitle
}

The syntax for JavaScript string literals does not allow literal CR or literal LF. See the MDN’s description of string literals or the ECMAScript standard’s definition of string literals.

The easiest thing to do (but still not quite fully correct!, see below) is probably to use `...${JSON.stringify(clp)}...` instead of `..."${clp}"...` in your template literal.

Note: the JSON.stringify output will have the appropriate double quotes, do not surround your template literal substitution (${...}) with them.


Splicing string data into the middle of code is unexpectedly tricky. You basically have to take your raw string produce a new string that encodes that string in the (usually multi-layered) quoting context that it will be used.

In your case, you are putting your string in a JavaScript program inside an HTML document. The use of JSON.stringify takes care of representing the string in a way that the WebView JavaScript environment will accept, but there is still that outer HTML layer to consider. What if the string includes </script>? None of those characters are special in JS string literals, so JSON.stringify could* leave them as literal characters. This would mean that they would appear in your HTML string and would prematurely close your script tag; this would let the rest of the string “escape” into your HTML (and break your script block since it would end in an unterminated string in the middle of the function).

In the appropriate context this kind of problem can create code injection security issues.

* In my testing some (but not all) implementations of JSON.stringify seem to look for (variations of) </script> and add their own escaping (e.g. replacing the < with \x3C).


The safest way of handling this would probably be to have your WebView-side function fetch the string from (e.g.) a global variable and use an extra evaluateJavaScript to set that variable before presenting the WebView. The code string you build for evaluateJavaScript would be something like `window.clp = ${JSON.stringify(clp)}`. In this situation, we know that there are no other “layered” contexts in which the string might be interpreted (just JavaScript), so JSON.stringify is sufficient.


If that feels like overkill, you might be able to get away a slight variation of the first approach: `...${JSON.stringify(clp).replaceAll(/\/script/gi,'\\$&')}...`. This “escapes” the slash character of all occurrences of “/script”. If the original string has </script> it will be <\/script> in the HTML and not be recognized as a closing script tag. It will also be <\/script> in the JS string literal, but the backslash will be “filtered out” of the actual string value since / is not a special string literal escape character. This technique may still have “holes” in it, I have not researched all the details of what browsers actually accept as a closing tag…; I know it is case-insensitive and there can be whitespace after the tag name, but I think whitespace is not allowed between the slash and the tag name in a closing tag. Are there character sequences that my regexp does not match that browsers will still interpret as a closing script tag (when surrounded by angle brackets)? I don’t know for sure.

What an amazingly profound answer! Thank you very much.

I will try and give a feedback

Happy,
Christian

Short update: works like a charm.

Thank you again!

Christian