[php-src] Issue #7990: Build only php -S, passthru(), redirect to server from symlink

From: Date: Sat, 22 Jan 2022 18:17:52 +0000
Subject: [php-src] Issue #7990: Build only php -S, passthru(), redirect to server from symlink
Groups: php.bugs 
Request: Send a blank email to php-bugs+get-239229@lists.php.net to get a copy of this message
Issue: https://github.com/php/php-src/issues/7990 Comment Author: guest271314 > I can't tell what you're doing Executing arbitrary shell scripts on any web page. In this case streaming live system audio output to speakers ("What-U-Hear") to the web page caller. Consider a jam-session that can be streamed to other peers and saved locally, an internet radio station where the source is the audio I am playing on mpv, or the browser itself. Chrome restricts capture of monitor devices on *nix deliberately, so it is not possible to capture audio output to speakers or headphones that are not being played in a Chrome tab, i.e., using navigator.mediaDevices.getDisplayMedia({audio: true, video: true}) or navigator.mediaDevices.getUserMedia({audio: true}), which are microphone only capture devices on *nix. Browser extensions have a "web_accessible_resources" key where sites matched can request the local resource bypassing CORS and CSP restrictions. ``` "web_accessible_resources": [{ "resources": [ "*.html", "*.js", "*.svg", "*.png", "*.php", "*.txt"], "matches": [ "<all_urls>" ], "extensions": [ ] }], ``` For example, GitHub serves CSP headers that prevent requesting http://localhost:8000 with fetch()`, errors will be thrown. When fetch('chrome-extension://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/test.txt') is called on GitHub, e.g., at console or in an <iframe> the extension inserts into the page the response is served, though only the static file. I can start and stop the local server using Native Messaging, for example https://github.com/guest271314/native-messaging-espeak-ng/blob/master/local_server.sh ``` #!/bin/bash # https://stackoverflow.com/a/24777120 send_message() { message="$1" # Calculate the byte size of the string. # NOTE: This assumes that byte length is identical to the string length! # Do not use multibyte (unicode) characters, escape them instead, e.g. # message='"Some unicode character:\u1234"' messagelen=${#message} # Convert to an integer in native byte order. # If you see an error message in Chrome's stdout with # "Native Messaging host tried sending a message that is ... bytes long.", # then just swap the order, i.e. messagelen1 <-> messagelen4 and # messagelen2 <-> messagelen3 messagelen1=$(( ($messagelen ) & 0xFF )) messagelen2=$(( ($messagelen >> 8) & 0xFF )) messagelen3=$(( ($messagelen >> 16) & 0xFF )) messagelen4=$(( ($messagelen >> 24) & 0xFF )) # Print the message byte length followed by the actual message. printf "$(printf '\\x%x\\x%x\\x%x\\x%x' \ $messagelen1 $messagelpen2 $messagelen3 $messagelen4)%s" "$message" } local_server() { if pgrep -f 'php -S localhost:8000' > /dev/null; then pkill -f 'php -S localhost:8000' & send_message '"Local server off."' else php -S localhost:8000 & send_message '"Local server on."' fi } local_server ``` https://github.com/guest271314/native-messaging-espeak-ng/blob/master/nativeTransferableStream.js ``` onload = async () => { chrome.runtime.sendNativeMessage( 'native_messaging_espeakng', {}, async (nativeMessage) => { parent.postMessage(nativeMessage, name); await new Promise((resolve) => setTimeout(resolve, 100)); const controller = new AbortController(); const { signal } = controller; parent.postMessage('Ready.', name); onmessage = async (e) => { if (e.data instanceof ReadableStream) { try { const { value: file, done } = await e.data.getReader().read(); const fd = new FormData(); const stdin = await file.text(); fd.append(file.name, stdin); const { body } = await fetch('http://localhost:8000', { method: 'post', cache: 'no-store', credentials: 'omit', body: fd, signal, }); parent.postMessage(body, name, [body]); } catch (err) { parent.postMessage(err, name); } } else { if (e.data === 'Done writing input stream.') { chrome.runtime.sendNativeMessage( 'native_messaging_espeakng', {}, (nativeMessage) => { parent.postMessage(nativeMessage, name); } ); } if (e.data === 'Abort.') { controller.abort(); } } }; } ); }; ``` where here the output of espeak-ng is captured and streamed to browser https://github.com/guest271314/native-messaging-espeak-ng/blob/master/index.php, again, to workaround the fact that Chromium does not support capture of window.speechSythesis.speak() audio output; the audio is not played on the Chromium tab, rather at the system level, why this code https://stackoverflow.com/a/70665493 does not work as expected (see above re getDisplayMedia({audio: true, video: true}) Tab capture; https://github.com/guest271314/captureSystemAudio#background). ``` <?php if (isset($_POST["espeakng"])) { header('Vary: Origin'); header("Access-Control-Allow-Origin: chrome-extension://<id>"); header("Access-Control-Allow-Methods: POST"); header("Content-Type: application/octet-stream"); header("X-Powered-By:"); echo passthru($_POST["espeakng"]); exit(); } ``` Currently I am streaming using Native Messaging, using the <iframe> approach, in pertinent part https://github.com/guest271314/captureSystemAudio/blob/master/native_messaging/capture_system_audio/capture_system_audio.py ``` while True: receivedMessage = getMessage() process = subprocess.Popen(split(receivedMessage), stdout=subprocess.PIPE) os.set_blocking(process.stdout.fileno(), False) for chunk in iter(lambda: process.stdout.read(1024 * 1024), b''): if chunk is not None: encoded = str([int('%02X' % i, 16) for i in chunk]) sendMessage(encodeMessage(encoded)) ``` https://github.com/guest271314/captureSystemAudio/blob/master/native_messaging/capture_system_audio/transferableStream.js ``` async function handleMessage(value, port) { try { await writer.ready; await writer.write(new Uint8Array(JSON.parse(value))); } catch (e) { console.error(e.message); } return true; } ``` https://github.com/guest271314/captureSystemAudio/blob/master/native_messaging/capture_system_audio/audioStream.js ``` async nativeMessageStream() { return new Promise((resolve) => { onmessage = (e) => { if (e.origin === this.src.origin) { console.log(e.data); if (!this.source) { this.source = e.source; } if (e.data === 1) { this.source.postMessage( { type: 'start', message: this.stdin }, '*' ); } if (e.data === 0) { document .querySelectorAll([src="${this.src.href}"]) .forEach((f) => { document.body.removeChild(f); }); onmessage = null; } if (e.data instanceof ReadableStream) { this.stdout = e.data; resolve(this.captureSystemAudio()); } } }; this.transferableWindow = document.createElement('iframe'); this.transferableWindow.style.display = 'none'; this.transferableWindow.name = location.href; this.transferableWindow.src = this.src.href; document.body.appendChild(this.transferableWindow); }).catch((err) => { throw err; }); } ``` Ideally I do not want to inject an <iframe> into the arbitrary web page, as when errors occur before the process completes that <iframe> can be still in the page, requiring both page and extension reload to regain expected functionality. PHP passthru() works for my uses cases, WebTransport is verbose and does not actually support indefinite streaming per my testing https://groups.google.com/a/chromium.org/g/web-transport-dev/c/-nRKS9ws8tc/m/pIAo2gVDAgAJ. The one caveat that starting and stopping the server I have experienced is the delay between pgrep being called and the server starting, thus the await new Promise((resolve) => setTimeout(resolve, 100)); in nativeTransferableStream.js before fetch() call. Practically, I don't need the remainder of PHP outside of local server functionality and passthru(), perhaps getmypid() to avoid costly pgrep, if I need to check if server is already running, if I don't just keep the server on. That is why I asked how to build with only php -S and passthru() (exec()) functionality, so I can essentially port that minimal PHP build for my use cases. > PHP is not good for such tasks, but since you know something about JS, you should use NodeJS. PHP works as expected for my uses cases. See above proofs. From what I gather NodeJS is expensive. I am trying to travel as light as possible. Comparatively what is the cost of that code versus index.php, above? What is the CPU usage comparing https://stackoverflow.com/a/48443161 with https://github.com/simov/native-messaging; and php -S host:port with require("http")? Creating the server is not the issue, accessing the local server on arbitrary web pages is. I started diving into PAC files https://bugs.chromium.org/p/chromium/issues/detail?id=839566#c40 after I filed this issue, which might be an additional alternative solution. I want to be able to request the ''chrome-extension://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/test.txt'' and get the response from the PHP development server that I start and stop using Native Messaging at arbitrary web pages. Naively I am talking about ln [OPTION]... [-T] TARGET LINK_NAME run in the Chromium extension folder and requests to 'test.txt' being forward to the local PHP server which issues response.

« previous php.bugs (#239229) next »