[php-src] Issue #7990: Build only php -S, passthru(), redirect to server from symlink
| From: | guest271314 | 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.