Compare commits
40 Commits
72dee2de36
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8089bd4439 | ||
|
|
750435fc83 | ||
|
|
fd67132da9 | ||
|
|
43a87347b8 | ||
|
|
e1d2c59ed5 | ||
| 2d64acaab6 | |||
| 17e0e6e526 | |||
|
|
02a3eef5e7 | ||
|
|
11d2ab8c86 | ||
|
|
778c0a2827 | ||
|
|
00e385cdbe | ||
|
|
cf9cc22a1c | ||
|
|
0c5dc17ae6 | ||
|
|
4347543ef7 | ||
|
|
9d51abd9ab | ||
|
|
118443305b | ||
|
|
ab4453487e | ||
|
|
e003d9795b | ||
|
|
e88dbd98cc | ||
|
|
eba447cd06 | ||
|
|
9aec2d3150 | ||
|
|
f2e11f827f | ||
|
|
32b6129ae3 | ||
|
|
94e8ecb60a | ||
|
|
1cf8032be4 | ||
|
|
47033f737d | ||
|
|
0f85d8da56 | ||
|
|
31c2d94e0b | ||
|
|
0848f4a015 | ||
|
|
92958e4c64 | ||
|
|
4f20c08ed0 | ||
|
|
9400c1f917 | ||
|
|
ddbcb4be5e | ||
|
|
20357809a7 | ||
|
|
c1d730dffa | ||
|
|
1dfcf710ff | ||
|
|
d3ffd6c678 | ||
|
|
b294771061 | ||
|
|
a058aa5095 | ||
|
|
fa459026ff |
87
QuickSetup.md
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
# Quick Setup
|
||||||
|
|
||||||
|
## user_config.json
|
||||||
|
|
||||||
|
`user_config.json` (in the same folder as `app.js`) is where you put your local configuration overrides. It merges on top of `includes/config.json` — **do not edit `includes/config.json` directly**.
|
||||||
|
|
||||||
|
You only need to include keys you want to override. Copy `user_config.example.json` as a starting point, or start with a minimal file:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"service_ip": "192.168.1.x"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The file supports `// line comments` and `/* block comments */`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## configurator.js
|
||||||
|
|
||||||
|
`tools/configurator.js` is a command-line tool that sets or deletes individual keys in `user_config.json` without manually editing JSON.
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```
|
||||||
|
node tools/configurator.js <dot.path.key> <value> [--overwrite]
|
||||||
|
node tools/configurator.js <dot.path.key> --delete [--overwrite]
|
||||||
|
```
|
||||||
|
|
||||||
|
- Use `--overwrite` to replace a key that already exists.
|
||||||
|
- Keys are expressed as dot-separated paths (e.g. `config.keys.user_data_key`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Setting service_ip
|
||||||
|
|
||||||
|
`service_ip` tells the box where to connect, this CANNOT be `0.0.0.0`, and must be an address reachable by your box when it connects via your setup. Can be `127.0.0.1` if you are running TouchPPP or WebTV Viewer on the same machine as minisrv.
|
||||||
|
|
||||||
|
```
|
||||||
|
node tools/configurator.js config.service_ip 192.168.1.x --overwrite
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Setting user_data_key
|
||||||
|
|
||||||
|
`user_data_key` is used to encrypt user data. It should be a random secret string and **must be set before registering any users**.
|
||||||
|
Changing it after users have registered will require updating the userdata with `tools/update_user_data_key.js`. Making a backup
|
||||||
|
of `SessionStore/accounts` is recommended before running `tools/update_user_data_key.js`, it is pretty resilent against corruption, but just in case.
|
||||||
|
|
||||||
|
```
|
||||||
|
node tools/configurator.js config.keys.user_data_key YOUR_RANDOM_SECRET --overwrite
|
||||||
|
```
|
||||||
|
|
||||||
|
To generate a random key:
|
||||||
|
```
|
||||||
|
openssl rand -base64 32
|
||||||
|
```
|
||||||
|
|
||||||
|
## Disabling a standard service
|
||||||
|
|
||||||
|
You can disable a configured service by setting the `disabled: true` flag for that service. For example, to disable `wtv-admin`:
|
||||||
|
|
||||||
|
```
|
||||||
|
node tools/configurator.js services.wtv-admin.disabled true
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Enabling a disabled service
|
||||||
|
|
||||||
|
You can disable a configured service by setting the `disabled: false` flag for that service. For example, to enable `pc_services`:
|
||||||
|
|
||||||
|
```
|
||||||
|
node tools/configurator.js services.pc_services.disabled false
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom service pages
|
||||||
|
|
||||||
|
You can place your custom pages in `UserServiceVault/servicename/page.js`. For example, to override `wtv-home:/home`, you would create
|
||||||
|
`UserServiceVault/wtv-home/home.js`, and the server will automatically prioritize your page. You can mix and match service vaults, accessing
|
||||||
|
resources in the standard service vault within your custom pages.
|
||||||
|
|
||||||
|
## Updating minisrv
|
||||||
|
|
||||||
|
You can `git pull`, or extract a new archive over the existing folder. If you followed the directions and kept your changes in `user_config.json` and `UserServiceVault`,
|
||||||
|
then you can update minisrv without worrying about breakage or losing data. Do pay attention to the console, and if any deprecreations appear, fix them before updating to the version listed in the notice.
|
||||||
@@ -9,7 +9,7 @@ This open source server is in beta status. Use at your own risk.
|
|||||||
### Note:
|
### Note:
|
||||||
- This has nothing to do with streaming video.
|
- This has nothing to do with streaming video.
|
||||||
- This is a server for the MSNTV (formerly WebTV) set-top box, and its various emulators.
|
- This is a server for the MSNTV (formerly WebTV) set-top box, and its various emulators.
|
||||||
- This does not support MSNTV2.
|
- This does not yet fully support MSNTV2.
|
||||||
|
|
||||||
### Current status:
|
### Current status:
|
||||||
- DB-less flat file client session store and registration system
|
- DB-less flat file client session store and registration system
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const process = wtvshared.process;
|
|||||||
const util = wtvshared.util;
|
const util = wtvshared.util;
|
||||||
const nunjucks = require('nunjucks');
|
const nunjucks = require('nunjucks');
|
||||||
const {serialize, unserialize} = require('php-serialize');
|
const {serialize, unserialize} = require('php-serialize');
|
||||||
const {spawn} = require('child_process');
|
const {spawn, spawnSync} = require('child_process');
|
||||||
const http = require('follow-redirects').http;
|
const http = require('follow-redirects').http;
|
||||||
const httpx = require(classPath + "/HTTPX.js");
|
const httpx = require(classPath + "/HTTPX.js");
|
||||||
const { URL } = require('url');
|
const { URL } = require('url');
|
||||||
@@ -27,6 +27,7 @@ const WTVClientSessionData = require(classPath + "/WTVClientSessionData.js");
|
|||||||
const WTVMime = require(classPath + "/WTVMime.js");
|
const WTVMime = require(classPath + "/WTVMime.js");
|
||||||
const WTVFlashrom = require(classPath + "/WTVFlashrom.js");
|
const WTVFlashrom = require(classPath + "/WTVFlashrom.js");
|
||||||
const WTVImage = require(classPath + "/WTVImage.js");
|
const WTVImage = require(classPath + "/WTVImage.js");
|
||||||
|
const WTVAudioProxy = require(classPath + "/WTVAudioProxy.js");
|
||||||
const vm = require('vm');
|
const vm = require('vm');
|
||||||
const debug = require('debug')('app');
|
const debug = require('debug')('app');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
@@ -37,6 +38,30 @@ const protocolServers = [];
|
|||||||
|
|
||||||
const minisrv_config = wtvshared.getMiniSrvConfig(); // snatches minisrv_config
|
const minisrv_config = wtvshared.getMiniSrvConfig(); // snatches minisrv_config
|
||||||
const wtvmime = new WTVMime(minisrv_config);
|
const wtvmime = new WTVMime(minisrv_config);
|
||||||
|
const wtvAudioProxy = new WTVAudioProxy(minisrv_config);
|
||||||
|
|
||||||
|
function validateAudioProxy() {
|
||||||
|
if (!wtvAudioProxy || !wtvAudioProxy.isEnabled()) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const check = spawnSync(wtvAudioProxy.config.ffmpegPath, ['-hide_banner', '-version'], {
|
||||||
|
stdio: ['ignore', 'ignore', 'ignore'],
|
||||||
|
timeout: 5000
|
||||||
|
});
|
||||||
|
|
||||||
|
if (check.error || check.status !== 0) {
|
||||||
|
console.warn(` # AudioProxy disabled: ffmpeg not found or failed to execute at '${wtvAudioProxy.config.ffmpegPath}'.`);
|
||||||
|
if (check.error) console.warn(` # AudioProxy ffmpeg error: ${check.error.message}`);
|
||||||
|
wtvAudioProxy.config.enabled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(` * AudioProxy enabled: transcoding audio to ${wtvAudioProxy.config.bitrate} ${wtvAudioProxy.config.sampleRate}Hz mono MP3.`);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(` # AudioProxy disabled: ffmpeg validation failed: ${error.message}`);
|
||||||
|
wtvAudioProxy.config.enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
process
|
process
|
||||||
.on('SIGTERM', shutdown('SIGTERM'))
|
.on('SIGTERM', shutdown('SIGTERM'))
|
||||||
@@ -203,24 +228,43 @@ function getServiceString(service_name, overrides = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const DEPRECIATED_CONFIG_PATH = path.join(__dirname, 'includes', 'depreciated.json');
|
||||||
|
|
||||||
|
function loadDepreciatedPatterns() {
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(DEPRECIATED_CONFIG_PATH)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const raw = fs.readFileSync(DEPRECIATED_CONFIG_PATH, 'utf8');
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
|
||||||
|
if (!Array.isArray(parsed) || parsed.length === 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapped = parsed
|
||||||
|
.filter((entry) => entry && typeof entry.pattern === 'string')
|
||||||
|
.map((entry) => ({
|
||||||
|
id: entry.id || entry.pattern,
|
||||||
|
pattern: new RegExp(entry.pattern, entry.flags || 'g'),
|
||||||
|
message: entry.message || 'Deprecated API usage found',
|
||||||
|
removeVersion: entry.removeVersion || 'unknown',
|
||||||
|
replacement: entry.replacement || null
|
||||||
|
}));
|
||||||
|
|
||||||
|
return mapped.length > 0 ? mapped : {};
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to load includes/depreciated.json, using fallback deprecation patterns:', error.message);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Deprecation warnings configuration
|
// Deprecation warnings configuration
|
||||||
const deprecationWarnings = {
|
const deprecationWarnings = {
|
||||||
// Array of deprecated patterns with their details
|
// Array of deprecated patterns with their details
|
||||||
patterns: [
|
patterns: loadDepreciatedPatterns(),
|
||||||
{
|
|
||||||
// Example deprecations - you can modify these as needed
|
|
||||||
pattern: /session\_data\.hasCap\s*\(/g,
|
|
||||||
message: "session_data.hasCap() is deprecated and will be removed",
|
|
||||||
removeVersion: "0.9.80",
|
|
||||||
replacement: "Use session_data.capabilities.get() instead"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: /(?<!wtvshared\.)getServiceString\s*\(/g,
|
|
||||||
message: "getServiceString() is deprecated and will be removed",
|
|
||||||
removeVersion: "0.9.80",
|
|
||||||
replacement: "Use wtvshared.getServiceString() instead"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
// Enable/disable deprecation warnings globally
|
// Enable/disable deprecation warnings globally
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -338,7 +382,7 @@ const runScriptInVM = function (script_data, user_contextObj = {}, privileged =
|
|||||||
"service_vaults": service_vaults,
|
"service_vaults": service_vaults,
|
||||||
"service_deps": service_deps,
|
"service_deps": service_deps,
|
||||||
"ssid_sessions": ssid_sessions,
|
"ssid_sessions": ssid_sessions,
|
||||||
"moveArrayKey": wtvshared.moveArrayKey,
|
"moveArrayKey": wtvshared.moveArrayKey, // deprecated - use wtvshared.moveArrayKey() instead
|
||||||
"cwd": (filename) ? path.dirname(filename) : __dirname, // current working directory
|
"cwd": (filename) ? path.dirname(filename) : __dirname, // current working directory
|
||||||
|
|
||||||
// Our prototype overrides
|
// Our prototype overrides
|
||||||
@@ -468,7 +512,7 @@ async function handleCGI(executable, cgi_file, socket, request_headers, vault, s
|
|||||||
env.SERVER_PORT = request_data.port;
|
env.SERVER_PORT = request_data.port;
|
||||||
env.SERVER_ADDR = request_data.host;
|
env.SERVER_ADDR = request_data.host;
|
||||||
env.SERVER_NAME = request_data.host;
|
env.SERVER_NAME = request_data.host;
|
||||||
if ((minisrv_config.services[socket.service_name] && minisrv_config.services[socket.service_name].hide_minisrv_version) || minisrv_config.config.hide_server_version) {
|
if (minisrv_config.config.hide_minisrv_version) {
|
||||||
env.SERVER_SOFTWARE = "NodeJS; minisrv";
|
env.SERVER_SOFTWARE = "NodeJS; minisrv";
|
||||||
} else {
|
} else {
|
||||||
// Full version
|
// Full version
|
||||||
@@ -1256,7 +1300,7 @@ minisrv-no-mail-count: true`;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendToClient(socket, headers_obj, data = null) {
|
async function sendToClient(socket, headers_obj, data = null, throttle = 0) {
|
||||||
let headers = "";
|
let headers = "";
|
||||||
let content_length = 0;
|
let content_length = 0;
|
||||||
const eol = "\n";
|
const eol = "\n";
|
||||||
@@ -1272,6 +1316,7 @@ async function sendToClient(socket, headers_obj, data = null) {
|
|||||||
if (socket.destroy) socket.destroy();
|
if (socket.destroy) socket.destroy();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const isWebTVClient = Boolean(socket.ssid && ssid_sessions[socket.ssid]);
|
||||||
if (!socket.res) {
|
if (!socket.res) {
|
||||||
wtv_connection_close = (headers_obj["wtv-connection-close"]) ? true : false;
|
wtv_connection_close = (headers_obj["wtv-connection-close"]) ? true : false;
|
||||||
if (typeof (headers_obj["wtv-connection-close"]) !== 'undefined') delete headers_obj["wtv-connection-close"];
|
if (typeof (headers_obj["wtv-connection-close"]) !== 'undefined') delete headers_obj["wtv-connection-close"];
|
||||||
@@ -1367,24 +1412,22 @@ async function sendToClient(socket, headers_obj, data = null) {
|
|||||||
delete headers_obj['minisrv-no-last-modified'];
|
delete headers_obj['minisrv-no-last-modified'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (minisrv_config.config.image_decoder && minisrv_config.config.image_decoder.enabled) {
|
if (isWebTVClient && minisrv_config.config.image_decoder && minisrv_config.config.image_decoder.enabled) {
|
||||||
const contype_key = wtvshared.getCaseInsensitiveKey('content-type', headers_obj);
|
const contype_key = wtvshared.getCaseInsensitiveKey('content-type', headers_obj);
|
||||||
let pngOpts = {};
|
|
||||||
if (contype_key) {
|
if (contype_key) {
|
||||||
if (minisrv_config.config.image_decoder.image_formats && minisrv_config.config.image_decoder.image_formats.includes(headers_obj[contype_key].toLowerCase())) {
|
if (minisrv_config.config.image_decoder.image_formats && minisrv_config.config.image_decoder.image_formats.includes(headers_obj[contype_key].toLowerCase())) {
|
||||||
const convertOpts = {
|
const convertOpts = {
|
||||||
jpegQuality: minisrv_config.config.image_decoder.jpg_quality,
|
jpegQuality: minisrv_config.config.image_decoder.jpg_quality,
|
||||||
type: imageArtemisType
|
type: imageArtemisType,
|
||||||
|
imgopts: minisrv_config.config.image_decoder.image_options || null
|
||||||
};
|
};
|
||||||
|
|
||||||
if (minisrv_config.config.image_decoder.max_height > 0) convertOpts.maxHeight = minisrv_config.config.image_decoder.max_height;
|
if (minisrv_config.config.image_decoder.max_height > 0) convertOpts.maxHeight = minisrv_config.config.image_decoder.max_height;
|
||||||
if (minisrv_config.config.image_decoder.max_width > 0) convertOpts.maxWidth = minisrv_config.config.image_decoder.max_width;
|
if (minisrv_config.config.image_decoder.max_width > 0) convertOpts.maxWidth = minisrv_config.config.image_decoder.max_width;
|
||||||
if (minisrv_config.config.image_decoder.png_opts) {
|
|
||||||
pngOpts = minisrv_config.config.image_decoder.png_opts;
|
|
||||||
}
|
|
||||||
const sourceData = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
const sourceData = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
||||||
try {
|
try {
|
||||||
const converted = await WTVImage.ImageToWebTV(sourceData, convertOpts, pngOpts);
|
const converted = await WTVImage.ImageToWebTV(sourceData, convertOpts);
|
||||||
data = converted.data;
|
data = converted.data;
|
||||||
content_length = data.length;
|
content_length = data.length;
|
||||||
var i=0;
|
var i=0;
|
||||||
@@ -1392,7 +1435,7 @@ async function sendToClient(socket, headers_obj, data = null) {
|
|||||||
// Image is too big, try to reduce quality
|
// Image is too big, try to reduce quality
|
||||||
if (i < minisrv_config.config.image_decoder.max_quality_tries) {
|
if (i < minisrv_config.config.image_decoder.max_quality_tries) {
|
||||||
convertOpts.jpegQuality -= minisrv_config.config.image_decoder.jpeg_interval;
|
convertOpts.jpegQuality -= minisrv_config.config.image_decoder.jpeg_interval;
|
||||||
var converted2 = await WTVImage.ImageToWebTV(sourceData, convertOpts);
|
var converted2 = await WTVImage.ImageToWebTV(sourceData, convertOpts, pngOpts);
|
||||||
data = converted2.data;
|
data = converted2.data;
|
||||||
content_length = data.length;
|
content_length = data.length;
|
||||||
i++;
|
i++;
|
||||||
@@ -1421,6 +1464,27 @@ async function sendToClient(socket, headers_obj, data = null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isWebTVClient && wtvAudioProxy && wtvAudioProxy.isEnabled()) {
|
||||||
|
const contype_key = wtvshared.getCaseInsensitiveKey('content-type', headers_obj);
|
||||||
|
if (contype_key && wtvAudioProxy.shouldProxy(headers_obj[contype_key])) {
|
||||||
|
try {
|
||||||
|
const transformResult = await wtvAudioProxy.transformIfNeeded(headers_obj, data);
|
||||||
|
headers_obj = transformResult.headers;
|
||||||
|
data = transformResult.data;
|
||||||
|
content_length = Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data || '');
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Audio proxy error:', e.message);
|
||||||
|
headers_obj = {
|
||||||
|
"Status": "413 Audio Too Long",
|
||||||
|
"Content-type": "text/plain"
|
||||||
|
};
|
||||||
|
data = (e.code === 'AUDIO_TOO_LONG') ?
|
||||||
|
`Audio exceeds maximum allowed duration of ${wtvAudioProxy.config.maxDurationSeconds} seconds.` :
|
||||||
|
`Audio proxy failure: ${e.message}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// if client can do compression, see if its worth enabling
|
// if client can do compression, see if its worth enabling
|
||||||
// small files actually get larger, so don't compress them
|
// small files actually get larger, so don't compress them
|
||||||
@@ -1545,7 +1609,7 @@ async function sendToClient(socket, headers_obj, data = null) {
|
|||||||
if (!xpower && socket.service_name) {
|
if (!xpower && socket.service_name) {
|
||||||
// add X-Powered-By header if not WebTV and not already set
|
// add X-Powered-By header if not WebTV and not already set
|
||||||
xpower = 'X-Powered-By';
|
xpower = 'X-Powered-By';
|
||||||
if (minisrv_config.services[socket.service_name].hide_minisrv_version) {
|
if (minisrv_config.config.hide_minisrv_version) {
|
||||||
// Don't report version
|
// Don't report version
|
||||||
if (!socket.ssid) headers_obj[xpower] = "NodeJS; minisrv";
|
if (!socket.ssid) headers_obj[xpower] = "NodeJS; minisrv";
|
||||||
} else {
|
} else {
|
||||||
@@ -1556,7 +1620,7 @@ async function sendToClient(socket, headers_obj, data = null) {
|
|||||||
// delete if webtv
|
// delete if webtv
|
||||||
if (socket.ssid) delete headers_obj[xpower];
|
if (socket.ssid) delete headers_obj[xpower];
|
||||||
if (socket.service_name) {
|
if (socket.service_name) {
|
||||||
if (minisrv_config.services[socket.service_name].hide_minisrv_version) {
|
if (minisrv_config.config.hide_minisrv_version) {
|
||||||
// Don't report version
|
// Don't report version
|
||||||
if (!socket.ssid) headers_obj[xpower] = headers_obj[xpower] + "; NodeJS; minisrv";
|
if (!socket.ssid) headers_obj[xpower] = headers_obj[xpower] + "; NodeJS; minisrv";
|
||||||
} else {
|
} else {
|
||||||
@@ -1614,64 +1678,98 @@ async function sendToClient(socket, headers_obj, data = null) {
|
|||||||
let toClient = null;
|
let toClient = null;
|
||||||
if (typeof data === 'string') {
|
if (typeof data === 'string') {
|
||||||
toClient = headers + eol + data;
|
toClient = headers + eol + data;
|
||||||
sendToSocket(socket, Buffer.from(toClient));
|
sendToSocket(socket, Buffer.from(toClient), throttle);
|
||||||
} else if (typeof data === 'object') {
|
} else if (typeof data === 'object') {
|
||||||
let verbosity_mod = (headers_obj["wtv-encrypted"] === 'true') ? " encrypted response" : "";
|
let verbosity_mod = (headers_obj["wtv-encrypted"] === 'true') ? " encrypted response" : "";
|
||||||
if (socket_sessions[socket.id].secure_headers === true) {
|
if (socket_sessions[socket.id].secure_headers === true) {
|
||||||
// encrypt headers
|
// encrypt headers
|
||||||
if (minisrv_config.config.debug_flags.quiet) verbosity_mod += " with encrypted headers";
|
if (minisrv_config.config.debug_flags.quiet) verbosity_mod += " with encrypted headers";
|
||||||
const enc_headers = socket_sessions[socket.id].wtvsec.Encrypt(1, headers + eol);
|
const enc_headers = socket_sessions[socket.id].wtvsec.Encrypt(1, headers + eol);
|
||||||
sendToSocket(socket, new Buffer.from(concatArrayBuffer(enc_headers, data)));
|
sendToSocket(socket, new Buffer.from(concatArrayBuffer(enc_headers, data)), throttle);
|
||||||
} else {
|
} else {
|
||||||
sendToSocket(socket, new Buffer.from(concatArrayBuffer(Buffer.from(headers + eol), data)));
|
sendToSocket(socket, new Buffer.from(concatArrayBuffer(Buffer.from(headers + eol), data)), throttle);
|
||||||
}
|
}
|
||||||
if (minisrv_config.config.debug_flags.quiet) console.debug(" * Sent" + verbosity_mod + " " + headers_obj.Status + " to client (Content-Type:", headers_obj['Content-type'], "~", headers_obj['Content-length'], "bytes)");
|
if (minisrv_config.config.debug_flags.quiet) console.debug(" * Sent" + verbosity_mod + " " + headers_obj.Status + " to client (Content-Type:", headers_obj['Content-type'], "~", headers_obj['Content-length'], "bytes)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendToSocket(socket, data) {
|
async function sendToSocket(socket, data, throttle = 0) {
|
||||||
const chunk_size = 16384;
|
const chunk_size = 16384;
|
||||||
let can_write = true;
|
if (!throttle || throttle <= 0) {
|
||||||
let close_socket = false;
|
let can_write = true;
|
||||||
let expected_data_out = 0;
|
let close_socket = false;
|
||||||
while ((socket.bytesWritten === 0 || socket.bytesWritten !== expected_data_out) && can_write) {
|
let expected_data_out = 0;
|
||||||
if (expected_data_out === 0) expected_data_out = data.byteLength + (socket_sessions[socket.id].socket_total_written || 0);
|
while ((socket.bytesWritten === 0 || socket.bytesWritten !== expected_data_out) && can_write) {
|
||||||
if (socket.bytesWritten === expected_data_out) break;
|
if (expected_data_out === 0) expected_data_out = data.byteLength + (socket_sessions[socket.id].socket_total_written || 0);
|
||||||
|
if (socket.bytesWritten === expected_data_out) break;
|
||||||
|
|
||||||
const data_left = (expected_data_out - socket.bytesWritten);
|
const data_left = (expected_data_out - socket.bytesWritten);
|
||||||
// buffer size = lesser of chunk_size or size remaining
|
// buffer size = lesser of chunk_size or size remaining
|
||||||
const buffer_size = (data_left >= chunk_size) ? chunk_size : data_left;
|
const buffer_size = (data_left >= chunk_size) ? chunk_size : data_left;
|
||||||
if (buffer_size < 0) {
|
if (buffer_size < 0) {
|
||||||
socket.destroy();
|
socket.destroy();
|
||||||
close_socket = true;
|
close_socket = true;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
const offset = (data.byteLength - data_left);
|
||||||
|
const chunk = new Buffer.alloc(buffer_size);
|
||||||
|
data.copy(chunk, 0, offset, (offset + buffer_size));
|
||||||
|
can_write = socket.write(chunk);
|
||||||
|
if (!can_write) {
|
||||||
|
socket.once('drain', function () {
|
||||||
|
sendToSocket(socket, data, throttle);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const offset = (data.byteLength - data_left);
|
if (socket.bytesWritten === expected_data_out || close_socket) {
|
||||||
const chunk = new Buffer.alloc(buffer_size);
|
socket_sessions[socket.id].socket_total_written = socket.bytesWritten;
|
||||||
data.copy(chunk, 0, offset, (offset + buffer_size));
|
if (socket_sessions[socket.id].expecting_post_data) delete socket_sessions[socket.id].expecting_post_data;
|
||||||
can_write = socket.write(chunk);
|
if (socket_sessions[socket.id].header_buffer) delete socket_sessions[socket.id].header_buffer;
|
||||||
|
if (socket_sessions[socket.id].secure_buffer) delete socket_sessions[socket.id].secure_buffer;
|
||||||
|
if (socket_sessions[socket.id].buffer) delete socket_sessions[socket.id].buffer;
|
||||||
|
if (socket_sessions[socket.id].headers) delete socket_sessions[socket.id].headers;
|
||||||
|
if (socket_sessions[socket.id].post_data) delete socket_sessions[socket.id].post_data;
|
||||||
|
if (socket_sessions[socket.id].post_data_length) delete socket_sessions[socket.id].post_data_length;
|
||||||
|
if (socket_sessions[socket.id].post_data_percents_shown) delete socket_sessions[socket.id].post_data_percents_shown;
|
||||||
|
socket.setTimeout(minisrv_config.config.socket_timeout * 1000);
|
||||||
|
if (socket_sessions[socket.id] && socket_sessions[socket.id].close_me) socket.end();
|
||||||
|
if (socket_sessions[socket.id].destroy_me) socket.destroy();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset = 0;
|
||||||
|
while (offset < data.byteLength) {
|
||||||
|
const buffer_size = Math.min(chunk_size, data.byteLength - offset);
|
||||||
|
const chunk = Buffer.alloc(buffer_size);
|
||||||
|
data.copy(chunk, 0, offset, offset + buffer_size);
|
||||||
|
offset += buffer_size;
|
||||||
|
|
||||||
|
const can_write = socket.write(chunk);
|
||||||
if (!can_write) {
|
if (!can_write) {
|
||||||
socket.once('drain', function () {
|
await new Promise(resolve => socket.once('drain', resolve));
|
||||||
sendToSocket(socket, data);
|
}
|
||||||
});
|
|
||||||
break;
|
if (offset < data.byteLength) {
|
||||||
|
const delay_ms = Math.max(1, Math.round((buffer_size * 8) / throttle * 1000));
|
||||||
|
await new Promise(resolve => setTimeout(resolve, delay_ms));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (socket.bytesWritten === expected_data_out || close_socket) {
|
|
||||||
socket_sessions[socket.id].socket_total_written = socket.bytesWritten;
|
socket_sessions[socket.id].socket_total_written = socket.bytesWritten;
|
||||||
if (socket_sessions[socket.id].expecting_post_data) delete socket_sessions[socket.id].expecting_post_data;
|
if (socket_sessions[socket.id].expecting_post_data) delete socket_sessions[socket.id].expecting_post_data;
|
||||||
if (socket_sessions[socket.id].header_buffer) delete socket_sessions[socket.id].header_buffer;
|
if (socket_sessions[socket.id].header_buffer) delete socket_sessions[socket.id].header_buffer;
|
||||||
if (socket_sessions[socket.id].secure_buffer) delete socket_sessions[socket.id].secure_buffer;
|
if (socket_sessions[socket.id].secure_buffer) delete socket_sessions[socket.id].secure_buffer;
|
||||||
if (socket_sessions[socket.id].buffer) delete socket_sessions[socket.id].buffer;
|
if (socket_sessions[socket.id].buffer) delete socket_sessions[socket.id].buffer;
|
||||||
if (socket_sessions[socket.id].headers) delete socket_sessions[socket.id].headers;
|
if (socket_sessions[socket.id].headers) delete socket_sessions[socket.id].headers;
|
||||||
if (socket_sessions[socket.id].post_data) delete socket_sessions[socket.id].post_data;
|
if (socket_sessions[socket.id].post_data) delete socket_sessions[socket.id].post_data;
|
||||||
if (socket_sessions[socket.id].post_data_length) delete socket_sessions[socket.id].post_data_length;
|
if (socket_sessions[socket.id].post_data_length) delete socket_sessions[socket.id].post_data_length;
|
||||||
if (socket_sessions[socket.id].post_data_percents_shown) delete socket_sessions[socket.id].post_data_percents_shown;
|
if (socket_sessions[socket.id].post_data_percents_shown) delete socket_sessions[socket.id].post_data_percents_shown;
|
||||||
socket.setTimeout(minisrv_config.config.socket_timeout * 1000);
|
socket.setTimeout(minisrv_config.config.socket_timeout * 1000);
|
||||||
if (socket_sessions[socket.id] && socket_sessions[socket.id].close_me) socket.end();
|
if (socket_sessions[socket.id] && socket_sessions[socket.id].close_me) socket.end();
|
||||||
if (socket_sessions[socket.id].destroy_me) socket.destroy();
|
if (socket_sessions[socket.id].destroy_me) socket.destroy();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function concatArrayBuffer(buffer1, buffer2) {
|
function concatArrayBuffer(buffer1, buffer2) {
|
||||||
@@ -2406,6 +2504,8 @@ else console.log(" * Shenanigans disabled");
|
|||||||
if (minisrv_config.config.image_decoder.enabled) console.log(" * WebTV Unsupported images will be processed and converted for WebTV clients");
|
if (minisrv_config.config.image_decoder.enabled) console.log(" * WebTV Unsupported images will be processed and converted for WebTV clients");
|
||||||
else console.log(" * WebTV Unsupported images will not be processed, and sent to client as-is");
|
else console.log(" * WebTV Unsupported images will not be processed, and sent to client as-is");
|
||||||
|
|
||||||
|
validateAudioProxy();
|
||||||
|
|
||||||
ports.sort();
|
ports.sort();
|
||||||
pc_ports.sort();
|
pc_ports.sort();
|
||||||
|
|
||||||
@@ -2603,3 +2703,12 @@ if (pc_bind_ports.length > 0) console.log(` * Started HTTP Server on port${pc_bi
|
|||||||
if (protocolHandledPorts.size > 0) console.log(` * Started ${protocolHandledPorts.size} specialized protocol handler${protocolHandledPorts.size !== 1 ? "s" : ""} on port${protocolHandledPorts.size !== 1 ? "s" : ""} ` + [...protocolHandledPorts].map(([sn, sp, pt]) => `${pt} (${sp.toUpperCase()})`).join(", ") + "...");
|
if (protocolHandledPorts.size > 0) console.log(` * Started ${protocolHandledPorts.size} specialized protocol handler${protocolHandledPorts.size !== 1 ? "s" : ""} on port${protocolHandledPorts.size !== 1 ? "s" : ""} ` + [...protocolHandledPorts].map(([sn, sp, pt]) => `${pt} (${sp.toUpperCase()})`).join(", ") + "...");
|
||||||
const listening_ip_string = (minisrv_config.config.bind_ip !== "0.0.0.0") ? "IP: " + minisrv_config.config.bind_ip : "all interfaces";
|
const listening_ip_string = (minisrv_config.config.bind_ip !== "0.0.0.0") ? "IP: " + minisrv_config.config.bind_ip : "all interfaces";
|
||||||
console.log(" * Listening on", listening_ip_string, "~", "Service IP:", service_ip);
|
console.log(" * Listening on", listening_ip_string, "~", "Service IP:", service_ip);
|
||||||
|
|
||||||
|
// Security warning for default user data encryption key
|
||||||
|
if (minisrv_config.config.keys.user_data_key === "PNa$WN7gz}!T=t6X7^=|Ii##CEB~p\\EP") {
|
||||||
|
console.log(" * WARNING: You are using the default user data encryption key. This is not secure, and you should change it in the configuration file before registering any users.");
|
||||||
|
console.log(" * To generate a random key in bash or PowerShell, you can run: node ./tools/configurator.js config.keys.user_data_key $(openssl rand -base64 32)");
|
||||||
|
console.log(" * After changing the key in the user_config, please restart the server.");
|
||||||
|
console.log(" * If you had existing users prior to changing the key, you can run tools/update_user_data_key.js to update existing accounts with the new key.");
|
||||||
|
console.log(" * Making a backup of your user accounts before doing this is highly recommended, in case something goes wrong during the update process.");
|
||||||
|
}
|
||||||
23
zefie_wtvp_minisrv/includes/ServiceDeps/msntv2/emac.crt
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDwzCCAqugAwIBAgIULL+4ofEIwgHwoGgoEJ+mJOmeF4YwDQYJKoZIhvcNAQEF
|
||||||
|
BQAwcDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk9IMRMwEQYDVQQHDApCdXR0IENy
|
||||||
|
YWNrMSAwHgYDVQQKDBdVbmRlcndlYXIgSW5zcGVjdG9yICMxMjEdMBsGA1UECwwU
|
||||||
|
VGhpbmcgTG9va2VyIEV4cGVydHMwIBcNMDAwNDIwMDAwMDAxWhgPMjA2OTA2MDkw
|
||||||
|
MDAwMDFaMHAxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJPSDETMBEGA1UEBwwKQnV0
|
||||||
|
dCBDcmFjazEgMB4GA1UECgwXVW5kZXJ3ZWFyIEluc3BlY3RvciAjMTIxHTAbBgNV
|
||||||
|
BAsMFFRoaW5nIExvb2tlciBFeHBlcnRzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
|
||||||
|
MIIBCgKCAQEArN5xb8PqUKNNTitGuxyJJxEsWTIt7t7eobSq1+V3hTpknJEspRpb
|
||||||
|
JeD5Ep86pWn4aGoWYT0JU9w4FRJFPaBPAMmLFTIYv1bySlFwna+5J67dAtMm59Pr
|
||||||
|
t3ZSw2dQpads3LGZRSpkY1mv9GC2TVVk/TANWZmWYvEZZ4/2E2YuMJZ6v6gNIY7Y
|
||||||
|
pfJ6F6Q4+d/Dgrvow4E35lmNuM0Y95sdMhKy8HTJ0ecU/bMLKyoXXZyt7LxezT+T
|
||||||
|
w3J2EDVV03lDXZhb0h2ZECmpCAgnKsQ/WmLMjEGeojDQnm+QzTaFHqO0148RMbYN
|
||||||
|
lxURCZVl+keYe1LTHW/K+2f58DeeI0z11wIDAQABo1MwUTAdBgNVHQ4EFgQU0bsp
|
||||||
|
ZOhtJGP0dLS4sdaA4nmQ5ocwHwYDVR0jBBgwFoAU0bspZOhtJGP0dLS4sdaA4nmQ
|
||||||
|
5ocwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEANAV1WNeCggjK
|
||||||
|
eb8kIGkt2R3PMMBy2w+rC4X80xygh+NHU5neJKM1lpq6NLkt5rCBXNCTDb5uPjRv
|
||||||
|
mcrGeIYJYXMy39htjlYgPtfUiQx7e7eq3g1mMHYN26Gr9p7BwJW0QVvaA6zB3lqt
|
||||||
|
HGL7hwQyBCD9P/0sR91sjyLF9mPkaO4sBM5EbrV89LXbD+OPqn10X2Mu4trI3bd4
|
||||||
|
R02Pl0A/rDO/S7bOGc4HGGPSsmgeoapydvkqgnXL497zD1Sv103n96hiuq4Dhurj
|
||||||
|
gfnGxQvEN7MCc31HE2ufkTHjNc62RatseiCLxJE3sBhMl9aaNEhtM3FNDXZnhMbs
|
||||||
|
0/7C2b5kSg==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
28
zefie_wtvp_minisrv/includes/ServiceDeps/msntv2/emac.key
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCs3nFvw+pQo01O
|
||||||
|
K0a7HIknESxZMi3u3t6htKrX5XeFOmSckSylGlsl4PkSnzqlafhoahZhPQlT3DgV
|
||||||
|
EkU9oE8AyYsVMhi/VvJKUXCdr7knrt0C0ybn0+u3dlLDZ1Clp2zcsZlFKmRjWa/0
|
||||||
|
YLZNVWT9MA1ZmZZi8Rlnj/YTZi4wlnq/qA0hjtil8noXpDj538OCu+jDgTfmWY24
|
||||||
|
zRj3mx0yErLwdMnR5xT9swsrKhddnK3svF7NP5PDcnYQNVXTeUNdmFvSHZkQKakI
|
||||||
|
CCcqxD9aYsyMQZ6iMNCeb5DNNoUeo7TXjxExtg2XFREJlWX6R5h7UtMdb8r7Z/nw
|
||||||
|
N54jTPXXAgMBAAECggEAN5W7IfX8FPu9csIllyrAyygRe4jQDjvpmFNwj8VAHj29
|
||||||
|
dj6T/W6xGkfxrtQ2VlPxxRk/ovquUiHAgr4CN6OhY55qs2ENZhD+DXmMXZfQUHtA
|
||||||
|
o5TjsK2K4M4SJLTG0M06CuisYPoVl4CSPGEQnWM0+tiFombpXV0rDwrSVECJ36Mr
|
||||||
|
x/HPoMJQPewBSI4lG2ACQMBmQ+7WQVwl0mprJjIFJYW9BXFRkqmFKSXq6G302VcH
|
||||||
|
VOjiSOF/8BR64worbo9UrpPBPMLZMAW1rwZx50Chwhplben8B2uQNSqrc7Q9ZA3C
|
||||||
|
2XJ8Wi0KCeYmJy+xng3LT22Sdme1UC8YfC9iCsHfaQKBgQDe3sN97UK+WVIgvA8Q
|
||||||
|
+4+rpkUWOy/dCoTzPuxHy4qSTHyF723+URyVkNF/P5EtwKwpIuJ/WMwIyUF0EqLQ
|
||||||
|
0+6HrmqfO5CR+p/2xJhFPHi1qn3uCM8pw5O9i5MjhOr1jM1RQz1xe0rToLiTlq3x
|
||||||
|
sSrYzDmIs61kBH2NKaVp38Oh5QKBgQDGkObMw8Gyn5JT1ewkS0VGDT5bbVK5sptJ
|
||||||
|
KTkaikKTuNV5RLya+7SxnR7rOBWWLN+ZpdQygM8IlUh0TJiTZazUkKUSNv3isIbF
|
||||||
|
rfak10q8Zad6RkaQyyH7GKhBOnMs0z3kqgL0SvNhoIs2KKsE46UoMVE49/C9Ma8Y
|
||||||
|
zbdEBLTtCwKBgQCRJc934d/IDHAadZ/yVYOaLO1trxpbARDZQq+rinozEbE/oVGn
|
||||||
|
gbf2CJ0IHKQ2gfcdy2Rrv68SQdBpAgIbswr0Prmd/rMG/4zSr/LjlKCg3+qn7gDN
|
||||||
|
mFxN4+ruBRDo3syREhOgJsXy0gejx0x9zf6ztz35M9vG+c2y896Q93R3qQKBgA/e
|
||||||
|
AmN4fSD9+V5zqMQZs7ZuVn5N1R97s0b8YVDKnZlaWsyu/ndQB9dtm8vmFmuCuHd5
|
||||||
|
teQ3QNQJwdlxSXv10wLFcDttY4pa2tovFZeEkLdPVDvEI91sLhH3nXJg7lU1qCt+
|
||||||
|
nm/REXPKtXUleM0SN99nWXs47ObhcoGD5tIroh2TAoGBAISazlBBrmnNOavnUzWa
|
||||||
|
Ka2uXk/2txan8Fh9jul0g6GPPsI7Ej3YMf3ccC9iotVugteHrBUK0cNbesydw3J0
|
||||||
|
PIs05XLX0curxoTox2Jeynq+ajy2tnmrDHDfilXdxRt2rsQlobZi3qoJskwPFYhf
|
||||||
|
ssL5LnP78Pv/t2H01jDSpZXu
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
29
zefie_wtvp_minisrv/includes/ServiceDeps/msntv2/minisrv.crt
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIE8TCCA9mgAwIBAgIQhTOF71uduRa0SXk4z+A7ujANBgkqhkiG9w0BAQUFADB0
|
||||||
|
MRkwFwYDVQQDDBBtaW5pc3J2IHNlcnZpY2VzMREwDwYDVQQIDAhOZXcgWW9yazEL
|
||||||
|
MAkGA1UEBhMCVVMxHjAcBgkqhkiG9w0BCQEWD3plZmllQHplZmllLm5ldDEXMBUG
|
||||||
|
A1UECgwOWmVmaWUgTmV0d29ya3MwIBcNMDAwMTAxMTIwMDAwWhgPMjA5OTEyMzEy
|
||||||
|
MzU5NTlaMFExKTAnBgNVBAMTIGhlYWR3YWl0ZXIudHJ1c3RlZC5tc250di5tc24u
|
||||||
|
Y29tMRcwFQYDVQQKEw5aZWZpZSBOZXR3b3JrczELMAkGA1UEBhMCVVMwggEiMA0G
|
||||||
|
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6pNNH2lF7SFx8cEIF1ImA7AI4bv/W
|
||||||
|
qvbErvUYJOfrOLXOfvXnxWEbEfDk9+XEf+JD8PQo2rvze1cVcXjVO7i2m+c4jdWw
|
||||||
|
X/VPdRM0NpoppFXbWC7nWNuXhZD/S7f6pUEJez7BYUpEeBFdR9eFb8VPo8+kefMz
|
||||||
|
inYznvP1UAn9wwoSIFDglX9QbijkJ/ZKtOY3vxCMVBZedWVnMPEJt928NJBNDGcC
|
||||||
|
VeV1thEAAVbQBf5nyhF9VfblTzEHoxq+d6rvi4rVkd0ZYqQPCcafDFccXf6YNQcz
|
||||||
|
8cmwzha61bgLbJLPNPiSqbqL8GNfsHbt2vyX6OhYpKwF+Y2CCp0xbGflAgMBAAGj
|
||||||
|
ggGeMIIBmjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIFoDATBgNVHSUEDDAKBggrBgEF
|
||||||
|
BQcDATCCAWkGA1UdEQSCAWAwggFcgiBoZWFkd2FpdGVyLnRydXN0ZWQubXNudHYu
|
||||||
|
bXNuLmNvbYIZc2cxLnRydXN0ZWQubXNudHYubXNuLmNvbYIZc2cyLnRydXN0ZWQu
|
||||||
|
bXNudHYubXNuLmNvbYIZc2czLnRydXN0ZWQubXNudHYubXNuLmNvbYIZc2c0LnRy
|
||||||
|
dXN0ZWQubXNudHYubXNuLmNvbYINbXNudHYubXNuLmNvbYIWbWFpbC5zZXJ2aWNl
|
||||||
|
cy5saXZlLmNvbYIObG9naW4ubGl2ZS5jb22CEXBvcHRpbWl6ZS5tc24uY29tghFm
|
||||||
|
YXZvcml0ZXMubXNuLmNvbYIRbWVzc2VuZ2VyLm1zbi5jb22CEWxpdmVmaWxlc3Rv
|
||||||
|
cmUuY29tghZ1c2Vycy5zdG9yYWdlLmxpdmUuY29tgglnLm1zbi5jb22CF21zbmlh
|
||||||
|
bG9naW4ucGFzc3BvcnQuY29tgg1taW5pc3J2LmxvY2FsMA0GCSqGSIb3DQEBBQUA
|
||||||
|
A4IBAQAZTy82heE64hCFxEiIFIxglGyPVU14wA2gXrv82mci/U0h/xBHfIfQWY8d
|
||||||
|
ULM6dO6kEk2DriBbo2ET10rkBwCTqa1iSDRN1eg0umdT2vbEYigjOelZJQqJi3Ua
|
||||||
|
LGBrPh8PK7juGa37aEdMWFLxmDtfEXE//OmMiliXU6bIi44pqM571X3Q3WPh3C3K
|
||||||
|
xOCOwQMgTPovLJDwRIJNyTrnb0kI+1s7oOtZ+QUQa7frY0Bgxn4IMEnZoIkOcAkh
|
||||||
|
R1m/OKyjjqQ8EVM73dTeiNr0yByG64C8dsVhJVXPT3GZOl7p5Pof9VfQg9Qr39Vh
|
||||||
|
ds7T/BVzQ79G8IXQ+AgZnZHu7pnn
|
||||||
|
-----END CERTIFICATE-----
|
||||||
27
zefie_wtvp_minisrv/includes/ServiceDeps/msntv2/minisrv.key
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEowIBAAKCAQEAuqTTR9pRe0hcfHBCBdSJgOwCOG7/1qr2xK71GCTn6zi1zn71
|
||||||
|
58VhGxHw5PflxH/iQ/D0KNq783tXFXF41Tu4tpvnOI3VsF/1T3UTNDaaKaRV21gu
|
||||||
|
51jbl4WQ/0u3+qVBCXs+wWFKRHgRXUfXhW/FT6PPpHnzM4p2M57z9VAJ/cMKEiBQ
|
||||||
|
4JV/UG4o5Cf2SrTmN78QjFQWXnVlZzDxCbfdvDSQTQxnAlXldbYRAAFW0AX+Z8oR
|
||||||
|
fVX25U8xB6Mavneq74uK1ZHdGWKkDwnGnwxXHF3+mDUHM/HJsM4WutW4C2ySzzT4
|
||||||
|
kqm6i/BjX7B27dr8l+joWKSsBfmNggqdMWxn5QIDAQABAoIBAEGq0DNNmrF3aiLW
|
||||||
|
FESc3KwhXT6hvx22FRBqRg1ynq5hy4WVocsj5OBzVYAZwBt8qw0gb6cYHlyyHpeK
|
||||||
|
zuqnEnwdKiL5tB9UA6krFdCfDWptSU/dHNOEre4HrlZEO7zR+6nsVM4Q/uJMJD/f
|
||||||
|
kPJ/urokdl/2EB0oMCJFYKwEtx8yrxJriNAjr4he0ibLHKiSTobanpbJDaDrIvOk
|
||||||
|
3njH8TNxjj/wdIaLJIWP/xPNTgMmUERYT6fDRe9p7gXg8R2+kSuvhpZSCa/fHxp0
|
||||||
|
6E1UESZyfHQgieUfgD7SB9Teq2gxTBIHGDsRhjgHLAa5R+p0lc8DEIgO+32hOI6v
|
||||||
|
p3CdThkCgYEA90EtOQZeGt9yVfLOTP7G6WiuiBC/kS4aEJDbr+JqtFJtEOXZWWuh
|
||||||
|
pbT7M+r/IDS/+TFPRtXe4Xm8eFmUIyW59V+9/jAqhU43zQZye159oKs6lyTmk84j
|
||||||
|
byQVsarnYTQxa2psqVWDbzfDAR3M01vIPa9pSmeCBoZVCxoi0Cd8OtMCgYEAwT7V
|
||||||
|
s9K0oajY49Kgeo8RA77/a1tGqlaEuQRX8KR85wcGm3nG7rDMaxbEoGurhwy8n4HW
|
||||||
|
KigQxFezhjXaTvFonCgTg5Dm0jAaCtHsJw48tGpkXZWrJ+elCZZPSred3Z1hKmvJ
|
||||||
|
SJ64dGP+cS4icw5NzsoGEpJZHrr9BBVYbDNML2cCgYEAmXi8QEQij12Y056VzRbr
|
||||||
|
kp+mjdCPh+bsyNGRezf38ZukFTQGWEnFmVyf/BbmazAy5NNlmNtRr/TnNnCr0bEu
|
||||||
|
Hw9hl/B/xCTL4BgbYVZCdkMyZ/TApofyWJ82VAR4AE7sSfdSIT1yCsu63+uGYr76
|
||||||
|
qMdDfKqI+9HP4cdESp3nr38CgYAGKOOU9M1vLbukH22gGnlXXjo0CNfKzDE02I+Z
|
||||||
|
CxU0JAQw5oPRze7mJvajimsQRfapOvFBrL9EEuuVBphr1cQY3iopEnBZGNFrsN9P
|
||||||
|
K2QB+DY0yXWIMxkOoizq28l7a+3R9VeYKf8FLr7IisjsU/Nk+QmSg/m1Qg6Yl7mW
|
||||||
|
0VfHVwKBgHT1oKjsQQc+a1dxwgXO1AKTMpUbpy88vRMaGu2emnHcTg7Y0lpwhsMo
|
||||||
|
4SSh2kN4ijLs8BeDbpxQf4ygWrqxWyeV73c4om/ADo4RWzMBzMm3lEcHiHA8jJSJ
|
||||||
|
cFCTew4Xiqzkbae5zU+mL6Dsxw8KSrkzSa62P8dgAGWQ1RsjoI3F
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
@@ -1,15 +1,17 @@
|
|||||||
const minisrv_service_file = true;
|
const minisrv_service_file = true;
|
||||||
|
|
||||||
|
const title = minisrv_config.config.hide_minisrv_version ? "zefie's minisrv PC Services" : `zefie minisrv v${minisrv_config.version} PC Services`;
|
||||||
|
|
||||||
headers = `200 OK
|
headers = `200 OK
|
||||||
Content-Type: text/html`
|
Content-Type: text/html`
|
||||||
|
|
||||||
data = `<html>
|
data = `<html>
|
||||||
<head>
|
<head>
|
||||||
<title>zefie minisrv v${minisrv_config.version}</title>
|
<title>${title}</title>
|
||||||
</head>
|
</head>
|
||||||
<body bgcolor="#000000" text="#449944">
|
<body bgcolor="#000000" text="#449944">
|
||||||
<p>
|
<p>
|
||||||
Welcome to the zefie minisrv v${minisrv_config.version} PC Services
|
Welcome to ${title}
|
||||||
</p>
|
</p>
|
||||||
<hr>
|
<hr>
|
||||||
<a href="/viewergen/">WebTV Viewer Generator</a><br>
|
<a href="/viewergen/">WebTV Viewer Generator</a><br>
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
const minisrv_service_file = true;
|
||||||
|
|
||||||
|
headers = `200 OK
|
||||||
|
Content-type: text/html`;
|
||||||
|
|
||||||
|
data = `<HTML>
|
||||||
|
<HEAD>
|
||||||
|
<title id="title"></title>
|
||||||
|
</HEAD>
|
||||||
|
<body>
|
||||||
|
<iframe id=checkmail style="display:none"></iframe>
|
||||||
|
<script language="javascript">
|
||||||
|
var tvShell = new ActiveXObject("MSNTV.TVShell");
|
||||||
|
function IsNightlyEnabled() {
|
||||||
|
var taskScheduler = tvShell.TaskScheduler;
|
||||||
|
var updateTask = null;
|
||||||
|
for (var i = 0; ((i < taskScheduler.Count) && (updateTask == null)); i++) {
|
||||||
|
if (taskScheduler.Item(i).Caller == 'NightlyUpdate') {
|
||||||
|
updateTask = taskScheduler.Item(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (updateTask != null) {
|
||||||
|
return(true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function GotoBoxCheck() {
|
||||||
|
var url = 'https://sg1.trusted.msntv.msn.com/connection/GatePage.aspx?phase=BoxCheck&purpose=Authorize';
|
||||||
|
var parms='';
|
||||||
|
parms += 'BoxId=' + tvShell.SystemInfo.BoxIDService + '&';
|
||||||
|
parms += 'WANProvider=' + tvShell.ConnectionManager.WANProvider + '&';
|
||||||
|
parms += 'version=' + encodeURIComponent(tvShell.SystemInfo.LastVersion) + '&';
|
||||||
|
if ((tvShell.ConnectionManager.MSNIAManager != null) && (tvShell.ConnectionManager.MSNIAManager.CurrentConnector != null)) parms += 'ConnectorName=' + encodeURIComponent(tvShell.ConnectionManager.MSNIAManager.CurrentConnector.Name) + '&';
|
||||||
|
if (tvShell.UserManager.CurrentUser != null) parms += 'domain=' + encodeURIComponent(tvShell.UserManager.CurrentUser.Domain) + '&';
|
||||||
|
parms += 'NumRedirects=0&';
|
||||||
|
parms += 'NightlyEnabled=' + IsNightlyEnabled() + '&';
|
||||||
|
parms += 'x=y';
|
||||||
|
var myPanel = tvShell.PanelManager.Item('service');
|
||||||
|
if (myPanel) myPanel.PostToURL(url, parms);
|
||||||
|
}
|
||||||
|
var progressPanel = tvShell.PanelManager.Item('progress');
|
||||||
|
function SetProgress(text, percent) {
|
||||||
|
if (progressPanel) {
|
||||||
|
progressPanel.Document.SetProgressText(text);
|
||||||
|
progressPanel.Document.SetProgressPercent(percent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function IsServicePanel() {
|
||||||
|
if ((window.name == null) || ((window.name != null) && (window.name.toLowerCase() != 'service'))) {
|
||||||
|
return(false);
|
||||||
|
}
|
||||||
|
return(true);
|
||||||
|
}
|
||||||
|
function DontContinue() {
|
||||||
|
var currentUser = tvShell.UserManager.CurrentUser;
|
||||||
|
if (currentUser != null && currentUser.IsAuthorized) {
|
||||||
|
window.location.replace(tvShell.UserManager.CurrentUser.ServiceList.Item('home::home').URL);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tvShell.ConnectionManager.ServiceState = 'ReSignIn';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!IsServicePanel()) {
|
||||||
|
DontContinue();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tvShell.MeteringManager.Stop();
|
||||||
|
SetProgress('Please wait while we sign you into MSN TV.', 10);
|
||||||
|
GotoBoxCheck();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</HTML>
|
||||||
|
`;
|
||||||
@@ -0,0 +1,470 @@
|
|||||||
|
const minisrv_service_file = true;
|
||||||
|
|
||||||
|
if (!session_data) {
|
||||||
|
session_data = new WTVClientSessionData(minisrv_config, (socket.ssid || null))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sorry Zef :kek
|
||||||
|
// https://git.computernewb.com/yellows111/msnp-wiki/src/branch/master/docs/services/rst.md
|
||||||
|
// the RST_ cookie stuff was code that was temp until we had proper token authentication
|
||||||
|
const NS = {
|
||||||
|
SOAP: "http://schemas.xmlsoap.org/soap/envelope/",
|
||||||
|
WSSE: "http://schemas.xmlsoap.org/ws/2003/06/secext",
|
||||||
|
WSP: "http://schemas.xmlsoap.org/ws/2002/12/policy",
|
||||||
|
WSU: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd",
|
||||||
|
WSA: "http://schemas.xmlsoap.org/ws/2004/03/addressing",
|
||||||
|
WST: "http://schemas.xmlsoap.org/ws/2004/04/trust",
|
||||||
|
PSF: "http://schemas.microsoft.com/Passport/SoapServices/SOAPFault",
|
||||||
|
ENC: "http://www.w3.org/2001/04/xmlenc#",
|
||||||
|
DS: "http://www.w3.org/2000/09/xmldsig#"
|
||||||
|
};
|
||||||
|
|
||||||
|
function getCookie(cookieString, name) {
|
||||||
|
if (!cookieString) return null;
|
||||||
|
const match = cookieString.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
|
||||||
|
return match ? decodeURIComponent(match[1]) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCookie(name, value, options = {}) {
|
||||||
|
const cookie = `${name}=${encodeURIComponent(value)}`;
|
||||||
|
const path = options.path || '/';
|
||||||
|
const expires = options.expires || '';
|
||||||
|
return `${cookie}; path=${path}${expires ? `; expires=${expires}` : ''}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDateTime(dt) {
|
||||||
|
return dt.toISOString().replace(/\.\d{3}Z$/, 'Z');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getClientIP() {
|
||||||
|
const forwarded = request_headers['x-forwarded-for'];
|
||||||
|
if (forwarded) {
|
||||||
|
const ips = forwarded.split(',');
|
||||||
|
return ips[0].trim();
|
||||||
|
}
|
||||||
|
return request_headers['x-real-ip'] || '127.0.0.1';
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateRandomToken(userId, appliesTo, isLegacy = false) {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const randomPart = crypto.randomBytes(32).toString('hex');
|
||||||
|
|
||||||
|
if (isLegacy) {
|
||||||
|
const tokenData = `${userId}|${appliesTo}|${timestamp}|${randomPart}`;
|
||||||
|
return crypto.createHash('sha256').update(tokenData).digest('hex');
|
||||||
|
} else {
|
||||||
|
const tokenData = {
|
||||||
|
uid: userId,
|
||||||
|
app: appliesTo,
|
||||||
|
ts: timestamp,
|
||||||
|
rand: randomPart,
|
||||||
|
ver: '1.0'
|
||||||
|
};
|
||||||
|
return Buffer.from(JSON.stringify(tokenData)).toString('base64');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractXmlValue(xml, elementName) {
|
||||||
|
if (!xml) return null;
|
||||||
|
|
||||||
|
const patterns = [
|
||||||
|
new RegExp(`<${elementName}>([\\s\\S]*?)</${elementName}>`, 'i'),
|
||||||
|
new RegExp(`<wsse:${elementName}>([\\s\\S]*?)</wsse:${elementName}>`, 'i'),
|
||||||
|
new RegExp(`<wst:${elementName}>([\\s\\S]*?)</wst:${elementName}>`, 'i'),
|
||||||
|
new RegExp(`<ps:${elementName}>([\\s\\S]*?)</ps:${elementName}>`, 'i')
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const regex of patterns) {
|
||||||
|
const match = xml.match(regex);
|
||||||
|
if (match && match[1]) {
|
||||||
|
let value = match[1].trim();
|
||||||
|
value = value.replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&');
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractTokenFromCipherValue(xml) {
|
||||||
|
if (!xml) return null;
|
||||||
|
|
||||||
|
const cipherRegex = /<CipherValue>([\s\S]*?)<\/CipherValue>/gi;
|
||||||
|
let match;
|
||||||
|
let token = null;
|
||||||
|
|
||||||
|
while ((match = cipherRegex.exec(xml)) !== null) {
|
||||||
|
let cipherValue = match[1].trim();
|
||||||
|
if (cipherValue && cipherValue.length > 0) {
|
||||||
|
token = cipherValue;
|
||||||
|
debug("Found CipherValue token:", token.substring(0, 50) + "...");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateTokenAndGetUser(token) {
|
||||||
|
try {
|
||||||
|
let userId = null;
|
||||||
|
let email = null;
|
||||||
|
|
||||||
|
if (request_headers.cookie) {
|
||||||
|
userId = getCookie(request_headers.cookie, 'RST_Auth');
|
||||||
|
email = getCookie(request_headers.cookie, 'RST_Email');
|
||||||
|
if (!email) email = getCookie(request_headers.cookie, 'rst_email');
|
||||||
|
if (!email) email = getCookie(request_headers.cookie, 'rst_username');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userId) {
|
||||||
|
userId = crypto.createHash('md5').update(token).digest('hex');
|
||||||
|
email = `user_${userId.substring(0, 8)}@example.com`;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug(`Token validated - UserId: ${userId}, Email: ${email}`);
|
||||||
|
return { success: true, userId, email };
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Token validation error:", error);
|
||||||
|
return { success: false, userId: null, email: null };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateErrorResponse(errorCode, errorText) {
|
||||||
|
const now = formatDateTime(new Date());
|
||||||
|
headers = `Status: 200 OK
|
||||||
|
Content-type: text/xml; charset=utf-8`;
|
||||||
|
|
||||||
|
return `<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<S:Envelope xmlns:S="${NS.SOAP}" xmlns:psf="${NS.PSF}">
|
||||||
|
<S:Header>
|
||||||
|
<psf:pp>
|
||||||
|
<psf:serverVersion>1</psf:serverVersion>
|
||||||
|
<psf:authstate>0x80048800</psf:authstate>
|
||||||
|
<psf:reqstatus>${errorCode}</psf:reqstatus>
|
||||||
|
<psf:serverInfo Path="Live1" RollingUpgradeState="ExclusiveNew" LocVersion="0" ServerTime="${now}">
|
||||||
|
${minisrv_config.config.service_name} [minisrv ${minisrv_config.config.hide_minisrv_version ? "beta" : minisrv_version_string.replace("zefie's wtv minisrv ","")}]
|
||||||
|
</psf:serverInfo>
|
||||||
|
<psf:cookies></psf:cookies>
|
||||||
|
<psf:response></psf:response>
|
||||||
|
</psf:pp>
|
||||||
|
</S:Header>
|
||||||
|
<S:Body>
|
||||||
|
<S:Fault>
|
||||||
|
<S:Code>
|
||||||
|
<S:Value>S:Sender</S:Value>
|
||||||
|
<S:Subcode>
|
||||||
|
<S:Value>wst:FailedAuthentication</S:Value>
|
||||||
|
</S:Subcode>
|
||||||
|
</S:Code>
|
||||||
|
<S:Reason>
|
||||||
|
<S:Text xml:lang="en-US">Authentication Failure</S:Text>
|
||||||
|
</S:Reason>
|
||||||
|
<S:Detail>
|
||||||
|
<psf:error>
|
||||||
|
<psf:value>${errorCode}</psf:value>
|
||||||
|
<psf:internalerror>
|
||||||
|
<psf:code>0x80041012</psf:code>
|
||||||
|
<psf:text>${errorText}</psf:text>
|
||||||
|
</psf:internalerror>
|
||||||
|
</psf:error>
|
||||||
|
</S:Detail>
|
||||||
|
</S:Fault>
|
||||||
|
</S:Body>
|
||||||
|
</S:Envelope>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateSuccessResponse(requestBody, userId, email, firstName, lastName) {
|
||||||
|
const now = new Date();
|
||||||
|
const tomorrow = new Date(now.getTime() + 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
const createdTime = formatDateTime(now);
|
||||||
|
const expiresTime = formatDateTime(tomorrow);
|
||||||
|
|
||||||
|
const puid = crypto.randomBytes(16).toString('hex').toUpperCase();
|
||||||
|
const cid = crypto.randomBytes(8).toString('hex').toUpperCase();
|
||||||
|
|
||||||
|
const safeFirstName = firstName || email.split('@')[0] || "User";
|
||||||
|
const safeLastName = lastName || "User";
|
||||||
|
const clientIp = getClientIP();
|
||||||
|
|
||||||
|
const rstRegex = /<wst:RequestSecurityToken[\s\S]*?<\/wst:RequestSecurityToken>/gi;
|
||||||
|
const responses = [];
|
||||||
|
let match;
|
||||||
|
let foundRst = false;
|
||||||
|
let rstIndex = 0;
|
||||||
|
|
||||||
|
while ((match = rstRegex.exec(requestBody)) !== null) {
|
||||||
|
foundRst = true;
|
||||||
|
const rstBlock = match[0];
|
||||||
|
|
||||||
|
const addressMatch = rstBlock.match(/<wsa:Address>(.*?)<\/wsa:Address>/i);
|
||||||
|
let appliesTo = addressMatch ? addressMatch[1] : "urn:passport:compact";
|
||||||
|
|
||||||
|
const policyMatch = rstBlock.match(/<wsse:PolicyReference\s+URI="([^"]+)"/i);
|
||||||
|
const policy = policyMatch ? policyMatch[1] : null;
|
||||||
|
|
||||||
|
const isLegacy = appliesTo.includes("Passport.NET");
|
||||||
|
const tokenType = isLegacy ? "urn:passport:legacy" : "urn:passport:compact";
|
||||||
|
const needsProofToken = policy === "MBI_KEY_OLD";
|
||||||
|
|
||||||
|
const token = generateRandomToken(userId, appliesTo, isLegacy);
|
||||||
|
wtvshared.storeToken(token, socket.ssid, userId, expiresTime);
|
||||||
|
const tokenId = isLegacy ? `BinaryDAToken${rstIndex}` : `Compact${rstIndex}`;
|
||||||
|
const binarySecret = crypto.randomBytes(32).toString('base64');
|
||||||
|
|
||||||
|
let requestedSecurityToken;
|
||||||
|
if (isLegacy) {
|
||||||
|
requestedSecurityToken = `
|
||||||
|
<wst:RequestedSecurityToken>
|
||||||
|
<EncryptedData xmlns="${NS.ENC}" Id="${tokenId}" Type="http://www.w3.org/2001/04/xmlenc#Element">
|
||||||
|
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
|
||||||
|
<ds:KeyInfo xmlns:ds="${NS.DS}">
|
||||||
|
<ds:KeyName>http://Passport.NET/STS</ds:KeyName>
|
||||||
|
</ds:KeyInfo>
|
||||||
|
<CipherData>
|
||||||
|
<CipherValue>${token}</CipherValue>
|
||||||
|
</CipherData>
|
||||||
|
</EncryptedData>
|
||||||
|
</wst:RequestedSecurityToken>`;
|
||||||
|
} else {
|
||||||
|
let tokenValue = `t=${token}`;
|
||||||
|
if (needsProofToken) {
|
||||||
|
tokenValue += `&p=profile`;
|
||||||
|
}
|
||||||
|
requestedSecurityToken = `
|
||||||
|
<wst:RequestedSecurityToken>
|
||||||
|
<wsse:BinarySecurityToken Id="${tokenId}">${tokenValue}</wsse:BinarySecurityToken>
|
||||||
|
</wst:RequestedSecurityToken>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let responseXml = `
|
||||||
|
<wst:RequestSecurityTokenResponse>
|
||||||
|
<wst:TokenType>${tokenType}</wst:TokenType>
|
||||||
|
<wsp:AppliesTo xmlns:wsa="${NS.WSA}">
|
||||||
|
<wsa:EndpointReference>
|
||||||
|
<wsa:Address>${appliesTo}</wsa:Address>
|
||||||
|
</wsa:EndpointReference>
|
||||||
|
</wsp:AppliesTo>
|
||||||
|
<wst:LifeTime>
|
||||||
|
<wsu:Created>${createdTime}</wsu:Created>
|
||||||
|
<wsu:Expires>${expiresTime}</wsu:Expires>
|
||||||
|
</wst:LifeTime>
|
||||||
|
${requestedSecurityToken}
|
||||||
|
<wst:RequestedTokenReference>
|
||||||
|
<wsse:KeyIdentifier ValueType="${tokenType}"/>
|
||||||
|
<wsse:Reference URI="#${tokenId}"/>
|
||||||
|
</wst:RequestedTokenReference>`;
|
||||||
|
|
||||||
|
if (needsProofToken || isLegacy) {
|
||||||
|
responseXml += `
|
||||||
|
<wst:RequestedProofToken>
|
||||||
|
<wst:BinarySecret>${binarySecret}</wst:BinarySecret>
|
||||||
|
</wst:RequestedProofToken>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
responseXml += `
|
||||||
|
</wst:RequestSecurityTokenResponse>`;
|
||||||
|
|
||||||
|
responses.push(responseXml);
|
||||||
|
rstIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundRst) {
|
||||||
|
const defaultToken = generateRandomToken(userId, "urn:passport:compact", false);
|
||||||
|
wtvshared.storeToken(defaultToken, socket.ssid, userId, expiresTime);
|
||||||
|
responses.push(`
|
||||||
|
<wst:RequestSecurityTokenResponse>
|
||||||
|
<wst:TokenType>urn:passport:compact</wst:TokenType>
|
||||||
|
<wst:RequestedSecurityToken>
|
||||||
|
<wsse:BinarySecurityToken Id="Compact0">t=${defaultToken}</wsse:BinarySecurityToken>
|
||||||
|
</wst:RequestedSecurityToken>
|
||||||
|
<wst:LifeTime>
|
||||||
|
<wsu:Created>${createdTime}</wsu:Created>
|
||||||
|
<wsu:Expires>${expiresTime}</wsu:Expires>
|
||||||
|
</wst:LifeTime>
|
||||||
|
</wst:RequestSecurityTokenResponse>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = `Status: 200 OK
|
||||||
|
Content-type: text/xml; charset=utf-8
|
||||||
|
Set-Cookie: RST_Auth=${userId}; path=/; HttpOnly
|
||||||
|
Set-Cookie: RST_Email=${email}; path=/`;
|
||||||
|
|
||||||
|
return `<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<S:Envelope xmlns:S="${NS.SOAP}">
|
||||||
|
<S:Header>
|
||||||
|
<psf:pp xmlns:psf="${NS.PSF}">
|
||||||
|
<psf:serverVersion>1</psf:serverVersion>
|
||||||
|
<psf:PUID>${puid}</psf:PUID>
|
||||||
|
<psf:configVersion>16.000.26889.00</psf:configVersion>
|
||||||
|
<psf:uiVersion>3.100.2179.0</psf:uiVersion>
|
||||||
|
<psf:mobileConfigVersion>16.000.26208.0</psf:mobileConfigVersion>
|
||||||
|
<psf:authstate>0x48803</psf:authstate>
|
||||||
|
<psf:reqstatus>0x0</psf:reqstatus>
|
||||||
|
<psf:serverInfo Path="Live1" RollingUpgradeState="ExclusiveNew" LocVersion="0" ServerTime="${now.toISOString()}">
|
||||||
|
NOBELLIUM 16.0.30846.6
|
||||||
|
</psf:serverInfo>
|
||||||
|
<psf:cookies></psf:cookies>
|
||||||
|
<psf:browserCookies>
|
||||||
|
<psf:browserCookie Name="MH" URL="http://www.msn.com">MSFT; path=/; domain=.msn.com; expires=Wed, 30-Dec-2037 16:00:00 GMT</psf:browserCookie>
|
||||||
|
<psf:browserCookie Name="MH" URL="http://www.live.com">MSFT; path=/; domain=.live.com; expires=Wed, 30-Dec-2037 16:00:00 GMT</psf:browserCookie>
|
||||||
|
</psf:browserCookies>
|
||||||
|
<psf:credProperties>
|
||||||
|
<psf:credProperty Name="MainBrandID">MSFT</psf:credProperty>
|
||||||
|
<psf:credProperty Name="IsWinLiveUser">true</psf:credProperty>
|
||||||
|
<psf:credProperty Name="CID">${cid}</psf:credProperty>
|
||||||
|
<psf:credProperty Name="AuthMembername">${email}</psf:credProperty>
|
||||||
|
<psf:credProperty Name="Country">US</psf:credProperty>
|
||||||
|
<psf:credProperty Name="Language">1033</psf:credProperty>
|
||||||
|
<psf:credProperty Name="FirstName">${safeFirstName}</psf:credProperty>
|
||||||
|
<psf:credProperty Name="LastName">${safeLastName}</psf:credProperty>
|
||||||
|
<psf:credProperty Name="Flags">40100643</psf:credProperty>
|
||||||
|
<psf:credProperty Name="IP">${clientIp}</psf:credProperty>
|
||||||
|
</psf:credProperties>
|
||||||
|
<psf:extProperties>
|
||||||
|
<psf:extProperty Name="CID">${cid}</psf:extProperty>
|
||||||
|
</psf:extProperties>
|
||||||
|
<psf:response></psf:response>
|
||||||
|
</psf:pp>
|
||||||
|
</S:Header>
|
||||||
|
<S:Body>
|
||||||
|
<wst:RequestSecurityTokenResponseCollection
|
||||||
|
xmlns:wst="${NS.WST}"
|
||||||
|
xmlns:wsse="${NS.WSSE}"
|
||||||
|
xmlns:wsu="${NS.WSU}"
|
||||||
|
xmlns:wsp="${NS.WSP}"
|
||||||
|
xmlns:psf="${NS.PSF}">
|
||||||
|
${responses.join('\n ')}
|
||||||
|
</wst:RequestSecurityTokenResponseCollection>
|
||||||
|
</S:Body>
|
||||||
|
</S:Envelope>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function rstHandler() {
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Get POST data
|
||||||
|
let requestBody = '';
|
||||||
|
if (request_headers.post_data) {
|
||||||
|
if (Buffer.isBuffer(request_headers.post_data)) {
|
||||||
|
requestBody = request_headers.post_data.toString('utf8');
|
||||||
|
} else if (typeof request_headers.post_data === 'string') {
|
||||||
|
requestBody = request_headers.post_data;
|
||||||
|
} else if (typeof request_headers.post_data === 'object') {
|
||||||
|
requestBody = JSON.stringify(request_headers.post_data);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug("No post_data found. Available keys:", Object.keys(request_headers));
|
||||||
|
return generateErrorResponse("0x80048820", "No POST data received");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!requestBody || requestBody.trim() === '') {
|
||||||
|
debug("Empty request body");
|
||||||
|
return generateErrorResponse("0x80048820", "Empty request body");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authentication
|
||||||
|
let email = extractXmlValue(requestBody, 'Username');
|
||||||
|
let password = extractXmlValue(requestBody, 'Password');
|
||||||
|
|
||||||
|
let userId = null;
|
||||||
|
let userEmail = null;
|
||||||
|
let firstName = "User";
|
||||||
|
let lastName = "User";
|
||||||
|
|
||||||
|
if ((!email || !password) && requestBody.includes('CipherValue')) {
|
||||||
|
debug("No username/password found, trying token authentication...");
|
||||||
|
const token = extractTokenFromCipherValue(requestBody);
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
const tokenValidation = validateTokenAndGetUser(token);
|
||||||
|
if (tokenValidation.success) {
|
||||||
|
userId = tokenValidation.userId;
|
||||||
|
userEmail = tokenValidation.email;
|
||||||
|
debug(`Token authentication successful for: ${userEmail} (${userId})`);
|
||||||
|
|
||||||
|
if (request_headers.cookie) {
|
||||||
|
const cookieEmail = getCookie(request_headers.cookie, 'RST_Email');
|
||||||
|
const cookieUsername = getCookie(request_headers.cookie, 'rst_username');
|
||||||
|
if (cookieEmail) userEmail = cookieEmail;
|
||||||
|
if (cookieUsername) firstName = cookieUsername;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug("Token validation failed");
|
||||||
|
return generateErrorResponse("0x80048821", "Invalid token");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug("No token found in CipherValue");
|
||||||
|
return generateErrorResponse("0x80048820", "Missing credentials/token");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (email && password) {
|
||||||
|
debug(`Extracted - Email: ${email}, Password: ${password ? '***' : 'empty'}`);
|
||||||
|
|
||||||
|
if (email && email.indexOf('@') < 0) {
|
||||||
|
const domain = minisrv_config.config.domain_name || 'minisrv.local';
|
||||||
|
email = `${email}@${domain}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
userEmail = email;
|
||||||
|
firstName = email.split('@')[0];
|
||||||
|
userId = crypto.createHash('md5').update(email).digest('hex');
|
||||||
|
const validAuth = validateCredentials(email, password);
|
||||||
|
if (!validAuth) {
|
||||||
|
debug("Invalid credentials");
|
||||||
|
return generateErrorResponse("0x80048821", "Invalid credentials");
|
||||||
|
} else {
|
||||||
|
debug(`Authentication successful for: ${userEmail} (${userId})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
debug("Missing both credentials and token");
|
||||||
|
return generateErrorResponse("0x80048820", "Missing credentials/token");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userId || !userEmail) {
|
||||||
|
debug("Failed to get user identity");
|
||||||
|
return generateErrorResponse("0x80048821", "User identity not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const cookieHeaders = [
|
||||||
|
setCookie('rst_email', userEmail, { path: '/' }),
|
||||||
|
setCookie('rst_username', firstName, { path: '/' }),
|
||||||
|
setCookie('rst_authenticated', 'true', { path: '/', expires: 'Wed, 30-Dec-2037 16:00:00 GMT' })
|
||||||
|
];
|
||||||
|
|
||||||
|
const response = generateSuccessResponse(requestBody, userId, userEmail, firstName, lastName);
|
||||||
|
|
||||||
|
for (const cookie of cookieHeaders) {
|
||||||
|
headers += `\nSet-Cookie: ${cookie}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("RST Handler Error:", error);
|
||||||
|
console.error("Error stack:", error.stack);
|
||||||
|
return generateErrorResponse("0x80048820", `Internal error: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateCredentials(email, password) {
|
||||||
|
username = email.split('@')[0];
|
||||||
|
result_ary = session_data.findAccountByUsername(username);
|
||||||
|
if (result_ary[0]) {
|
||||||
|
if (!socket.ssid) {
|
||||||
|
socket.ssid = result_ary[1];
|
||||||
|
// second arg should handle secondary users
|
||||||
|
session_data.setSSID(socket.ssid, result_ary[2]);
|
||||||
|
}
|
||||||
|
return session_data.validateUserPassword(password);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let result = rstHandler();
|
||||||
|
if (result) {
|
||||||
|
data = result;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
const minisrv_service_file = true;
|
||||||
|
|
||||||
|
// Wrong email return: <LoginResponse Success="false"><Error Code="e5b"/></LoginResponse>
|
||||||
|
// Wrong Password return: <LoginResponse Success="false"><Error Code="e5a"/></LoginResponse>
|
||||||
|
|
||||||
|
// Example Client request: <LoginRequest><ClientInfo name="MSNTV" version="1.35"/><User><SignInName>example@example.com</SignInName><Password>example</Password><SavePassword>false</SavePassword></User><DAOption>1</DAOption><TargetOption>1</TargetOption></LoginRequest>
|
||||||
|
|
||||||
|
data = `<LoginResponse Success="true"><TnP>t=Disabled&p=Disabled</TnP></LoginResponse>`; // T and P cant be nulled they have to have some content in it
|
||||||
|
|
||||||
|
headers = `200 OK
|
||||||
|
Content-Type: text/xml`;
|
||||||
|
|
||||||
|
console.log(request_headers.query);
|
||||||
|
After Width: | Height: | Size: 43 B |
@@ -0,0 +1,528 @@
|
|||||||
|
const minisrv_service_file = true;
|
||||||
|
|
||||||
|
// Weather placeholder (static defaults — no live API)
|
||||||
|
const WeatherCity = 'Your City';
|
||||||
|
const WeatherTemp = '72';
|
||||||
|
const WeatherDescription = 'Sunny';
|
||||||
|
const WeatherIcon = '/Home/Weather/26.gif';
|
||||||
|
|
||||||
|
// News headlines
|
||||||
|
const NewsLink1 = '';
|
||||||
|
const NewsLink2 = '';
|
||||||
|
const NewsLink3 = '';
|
||||||
|
const NewsTitle1 = '...';
|
||||||
|
const NewsTitle2 = '...';
|
||||||
|
const NewsTitle3 = '...';
|
||||||
|
|
||||||
|
headers = `200 OK
|
||||||
|
Content-type: text/html`;
|
||||||
|
|
||||||
|
data = `<html xmlns:msntv>
|
||||||
|
<?import namespace="msntv" implementation="Shared/Anduril/HTC/en-us/TruncatedHtml.htc"?>
|
||||||
|
|
||||||
|
<head><title>Home</title>
|
||||||
|
<?import namespace="msntv" implementation="HTC/Shared/CustomButton.htc"?>
|
||||||
|
<?import namespace="msntv" implementation="/Home/Shared/BaseClient/HTCTransforms/en-us/LoopingDIV.htc"?>
|
||||||
|
|
||||||
|
<script src="/Include/2.0.261.900/localhost-1700/Shared/BaseClient/JsTransforms/en-us/PaneHelp.js" language="javascript" defer="true"></script>
|
||||||
|
<link href="/Include/2.0.261.778/localhost-1700/Home/Anduril/CssTransforms/en-us/Home.css" type="text/css" rel="StyleSheet">
|
||||||
|
<script src="/Include/2.0.261.778/localhost-1700/Home/Anduril/JsTransforms/en-us/Home.js" language="javascript"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var forceReload = false;
|
||||||
|
var l = 'd:' + new Date().valueOf() + '|';
|
||||||
|
function setCookie(name, value) {
|
||||||
|
{
|
||||||
|
var now = new Date();
|
||||||
|
var expires = new Date(now.getFullYear() + 1, now.getMonth(), now.getDate());
|
||||||
|
document.cookie = escape(name) + '=' + escape(value) + ';expires=' + expires.toGMTString() + ';path=/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getCookie(name) {
|
||||||
|
{
|
||||||
|
var str = document.cookie;
|
||||||
|
var arr = str.split('; ');
|
||||||
|
for (var i = arr.length - 1; i >= 0; i--) {
|
||||||
|
{
|
||||||
|
var c = arr[i].split('=');
|
||||||
|
if (c.length != 2)
|
||||||
|
continue;
|
||||||
|
if (unescape(c[0]) == name)
|
||||||
|
return unescape(c[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function syncCookie(cookieName, propValue) {
|
||||||
|
{
|
||||||
|
var c = getCookie(cookieName);
|
||||||
|
l += 'g:' + cookieName + ':' + c + '|';
|
||||||
|
if (c != propValue) {
|
||||||
|
{
|
||||||
|
setCookie(cookieName, propValue);
|
||||||
|
l += 's:' + cookieName + ':' + propValue + '|';
|
||||||
|
var check = getCookie(cookieName);
|
||||||
|
if (check == propValue)
|
||||||
|
forceReload = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var d = new Date();
|
||||||
|
var utcOffset = d.getTimezoneOffset();
|
||||||
|
syncCookie('UserUtcOffset', utcOffset);
|
||||||
|
var connSpeed;
|
||||||
|
try { { connSpeed = window.external.ClientCaps.connectionType; } }
|
||||||
|
catch (e) {
|
||||||
|
{
|
||||||
|
connSpeed = "undetected";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
syncCookie('UserConnectionSpeed', connSpeed);
|
||||||
|
try {
|
||||||
|
{
|
||||||
|
top.log(l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (forceReload)
|
||||||
|
location.replace(location.href);
|
||||||
|
</script></head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<body onload="initPage();" scroll="no" tabindex="-1">
|
||||||
|
|
||||||
|
<div id="focdiv" style="position:absolute;top:314px;left:27px;width:70px;height:70px;" onclick="goToMail();">
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.all["focdiv"].focus();
|
||||||
|
|
||||||
|
function handleLoopingDivReady()
|
||||||
|
{
|
||||||
|
document.all["focdiv"].style.display = "none";
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="topdiv">
|
||||||
|
|
||||||
|
<div class="textMenu">
|
||||||
|
<script xmlns:msntvuxp="msntvuxp.microsoft.com">
|
||||||
|
function goToService(serviceName) {window.location = window.external.SafeGetServiceURL(serviceName);}
|
||||||
|
function goToCenter(svcURL) {window.location = svcURL;}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div style="position:absolute; left:0px; top:0px" xmlns:msntvuxp="msntvuxp.microsoft.com">
|
||||||
|
<table border="0" class="TextMenuTbl">
|
||||||
|
<tr height="34" style="background-color: transparent;">
|
||||||
|
<td class="leftGradientTD"></td><td class="rightGradientTD">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="position:absolute; left:0px; top:0px" xmlns:msntvuxp="msntvuxp.microsoft.com">
|
||||||
|
<table border="0" class="TextMenuTbl">
|
||||||
|
<tr>
|
||||||
|
<td style="background-color: transparent;" width="8">
|
||||||
|
<img height="1" src="/Images/Shared/s.gif" width="8">
|
||||||
|
</td>
|
||||||
|
<td style="background-color: transparent;" align="left" width="162">
|
||||||
|
<a class="TextMenuLink" href="javascript:goToCenter('/Pages/UsingMSNTV/Main');" id="UsingMSNTVLinkID" onblur="umtvHasFocus=false">Using MSN TV</a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span style="position:absolute;left:0;top:35px;width:100%;height:2px;background-color:#8fc3d6;">
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<td style="background-color: transparent;" align="left" width="115">
|
||||||
|
<a class="TextMenuLink" href="javascript:void(window.open(' ', 'signout', 'msntv:panel'));" id="SignoutLinkID">Sign Out</a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span style="position:absolute;left:0;top:35px;width:100%;height:2px;background-color:#8fc3d6;"></span>
|
||||||
|
</div>
|
||||||
|
<td style="background-color: transparent;" align="left" width="106">
|
||||||
|
<a class="TextMenuLink" href="javascript:goToService('UAM::UAMbase');" id="AccountLinkID">Account</a>
|
||||||
|
</td>
|
||||||
|
<div>
|
||||||
|
<span style="position:absolute;left:0;top:35px;width:100%;height:2px;background-color:#8fc3d6;">
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<td style="background-color: transparent;" align="left" width="109">
|
||||||
|
<a class="TextMenuLink" href="javascript:goToService('settings::mainindex');" id="SettingsLinkID">Settings</a>
|
||||||
|
</td>
|
||||||
|
<div><span style="position:absolute;left:0;top:35px;width:100%;height:2px;background-color:#8fc3d6;">
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<td style="background-color: transparent;" align="left" width="78">
|
||||||
|
<a href='javascript:CallPaneHelp(PH_TOC);' id="HelpLinkID">
|
||||||
|
|
||||||
|
<table><tr>
|
||||||
|
<td valign="middle" class="TextMenuLinkSimulation">Help</td>
|
||||||
|
<td valign="middle" width="30" height="20">
|
||||||
|
<span id="helpIcon" style='src:url(msntv:/Shared/Images/Icon_Help_RelatedLink.png);'></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<div>
|
||||||
|
<span style="position:absolute;left:0;top:35px;width:100%;height:2px;background-color:#8fc3d6;">
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="infoPaneDiv">
|
||||||
|
|
||||||
|
<div class="promoImgDiv">
|
||||||
|
<div style="position:absolute; left:0px; top:0px">
|
||||||
|
<img id="PromoImageID" width="178" height="135" border="0" hspace="0" alt="Promotional Image" src="/Home/ads/webtv3.gif">
|
||||||
|
</div>
|
||||||
|
<div style="position:absolute; left:5px; top:0px"><a id="PromoImageLinkID" href="">
|
||||||
|
<table width="173" height="135"><tr><td></td></tr></table></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="timerRotatorDiv" class="personalPanelDiv" onclick="ClickRotator()">
|
||||||
|
<div>
|
||||||
|
<div style="top:0px; left:0px; width:176px; height:105px;" xmlns:msntvuxp="msntvuxp.microsoft.com">
|
||||||
|
<div class="PNGImage" style="width:176px;height:105px;src:/images/Home/HomeRotatorBGWeather.png;"></div>
|
||||||
|
</div>
|
||||||
|
<div style="position:absolute; top:0px; left:0px; width:178px; height:107px;" xmlns:msntvuxp="msntvuxp.microsoft.com">
|
||||||
|
<table class="wthrTbl" border="0" cellpadding="1" cellspacing="0">
|
||||||
|
<tr>
|
||||||
|
<td height="4" width="4" rowspan="4"><img src="images/Shared/s.gif" height="4" width="4"></td>
|
||||||
|
<td height="4" width="45"><img src="images/Shared/s.gif" height="4" width="45"></td>
|
||||||
|
<td height="4" width="10"><img src="images/Shared/s.gif" height="4" width="10"></td>
|
||||||
|
<td height="4" width="65"><img src="images/Shared/s.gif" height="4" width="65"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td id="CityCellID" class="wthrCityCell" colspan="3" valign="top">
|
||||||
|
<span class="wthrCityText">${WeatherCity}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td id="TRCID" class="wthrTempCond">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td id="TemperatureCellID" class="wthrTempCell">
|
||||||
|
<span class="wthrTempTxt">${WeatherTemp}°F</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td id="ConditionCellID" class="wthrCondCell">
|
||||||
|
<span class="wthrCondTxt">${WeatherDescription}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
<td id="PaddingID" width="10"><img src="images/Shared/s.gif" height="1" width="10"></td>
|
||||||
|
<td id="ConditionIconID" class="wthrCondIcon">
|
||||||
|
<img src="${WeatherIcon}" alt="Weather icon" style="width:65px;height:61px;">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td id="ProviderID" class="wthrProvider" colspan="3">
|
||||||
|
Open-Meteo
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<script xmlns:msntvuxp="msntvuxp.microsoft.com">
|
||||||
|
function clickPageRotatePanel() {
|
||||||
|
location.href = "/Pages/weather/yourcity";
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
InitRotator(timerRotatorDiv, 5000, new Array("/home/MoneyModule.aspx","/Pages/weather/WeatherModule"));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div ID="clockID" class="clockDiv">
|
||||||
|
</div>
|
||||||
|
<script>clockID.innerHTML = formClockLink();</script>
|
||||||
|
|
||||||
|
<div class="newsHdlnDiv">
|
||||||
|
<div class="newsHdlnTitleDiv"><span class="newsHdlnTitleText">Today on TV2</span></div>
|
||||||
|
<table style="top:0px;left:0px;width:365px;height:78px;">
|
||||||
|
<tr><td><a class="newsHdlnLink" id="newsHdlnLinkID1" href="${NewsLink1}">${NewsTitle1}</a></td></tr>
|
||||||
|
<tr><td><a class="newsHdlnLink" id="newsHdlnLinkID2" href="${NewsLink2}">${NewsTitle2}</a></td></tr>
|
||||||
|
<tr><td><a class="newsHdlnLink" id="newsHdlnLinkID3" href="${NewsLink3}">${NewsTitle3}</a></td></tr>
|
||||||
|
</table>
|
||||||
|
<div class="moreNewsDiv">
|
||||||
|
<table><tr>
|
||||||
|
<td><img src="msntv:/Shared/images/BulletCustom.gif" height="14" width="7"></td>
|
||||||
|
<td><img src="images/Shared/s.gif" height="1" width="4"></td>
|
||||||
|
<td style="height:37px"><a id="moreNewsLinkID" class="moreNewsLink" href="../pages/TopStories">More MSN news</a></td>
|
||||||
|
</tr></table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="promoPanelDiv">
|
||||||
|
<div style="position:absolute; width:355px; height:70px;">
|
||||||
|
<table style="width:365px;">
|
||||||
|
<td class="promoHdlnTitle" style="color:#1D704C">Using MSN TV</td>
|
||||||
|
<tr>
|
||||||
|
<td class="promoHdlnCell"><a class="promoHdlnLink" id="promoHdlnLinkID1" href="http://msntv.msn.com/pages/usingmsntv/tippage.aspx?id=set15">Tip: Turn on audible dialing </a></td></tr><tr>
|
||||||
|
<td class="promoHdlnCell"><a class="promoHdlnLink" id="promoHdlnLinkID2" href="http://msntv.msn.com/pages/UsingMSNTV/Article.aspx?id=feature3">Get better printing results</a></td></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div style="position:absolute; left:161px; top:74px; width:200px;">
|
||||||
|
<table class="MoreUsingLinkTable"><tr>
|
||||||
|
<td><img src="msntv:/Shared/Images/BulletCustom.gif" height="14" width="7"></td>
|
||||||
|
<td><img src="/Images/Shared/s.gif" height="1" width="4"></td>
|
||||||
|
<td>
|
||||||
|
<a id="MoreUsingLinkID" class="MoreNewsLink" href="http://headwaiter.trusted.msntv.msn.com/Pages/UsingMSNTV/Main">Go to Using MSN TV</a>
|
||||||
|
</td></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="searchID" class="searchDiv">
|
||||||
|
<div class="searchCenterDiv">
|
||||||
|
<script>
|
||||||
|
function doSearch(country){
|
||||||
|
if (searchFormID.searchFieldID.value == "")
|
||||||
|
{
|
||||||
|
window.location = window.external.SafeGetServiceURL('search::search') + "?FORM=WEBTV&cfg=MSTVXML&v=1&c="+country+"&x=26&y=14";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
window.location = window.external.SafeGetServiceURL('search::search') + "?FORM=WEBTV&cfg=MSTVXML&v=1&c="+country+"&x=26&y=14&q=" + escape(searchFormID.searchFieldID.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<div style="position:absolute; z-index:1; left:0; top:0px; width:100%; height:2px; background-color:#0c7faa;">
|
||||||
|
<table style="width:100%; height:2px;"><tr><td height="2"></td></tr></table>
|
||||||
|
</div>
|
||||||
|
<table class="searchTbl">
|
||||||
|
<form id="searchFormID" action="javascript:doSearch('US')">
|
||||||
|
<tr>
|
||||||
|
<td width="10"></td>
|
||||||
|
<td><a class="searchLink" href="javascript:doSearch('US');">Search</a><span class="searchLabelText"> or type www</span></td>
|
||||||
|
<td></td>
|
||||||
|
<td><span class="searchFieldText"><input id="searchFieldID" name="searchFieldName" class="searchField" type="text" size="28"></span></td>
|
||||||
|
<td></td>
|
||||||
|
<td valign="center"><msntv:custombutton id="GoButtonID" onClick="doSearch('US');" label="Go" /></td>
|
||||||
|
</tr></form></table>
|
||||||
|
<div style="position:absolute; left:0; top:33px; width:100%; height:2px; background-color:#1c4373;">
|
||||||
|
<table style="width:100%; height:2px;"><tr><td height="2"></td></tr></table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="iconNavBarID" class="iconNavBar">
|
||||||
|
<table class="iconNavBarMasterTbl">
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="middle">
|
||||||
|
<table class="iconNavBarTbl">
|
||||||
|
<tr>
|
||||||
|
<td class="iconNavBarTblFrameCell">
|
||||||
|
<table class="ApolloIcons" xmlns:msntv="http://tv.msn.com">
|
||||||
|
<tr height="70">
|
||||||
|
<td>
|
||||||
|
<msntv:loopingDIV id="navbar" hasInitialFocus="true" divWidthPX="554" onLoopingDivReady="handleLoopingDivReady()" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<script>
|
||||||
|
var nIcons;
|
||||||
|
var ImgURL = new Array();
|
||||||
|
var ImgWidth = new Array();
|
||||||
|
var ImgOverURL = new Array();
|
||||||
|
var IconURL = new Array();
|
||||||
|
|
||||||
|
function goToFavorites() {
|
||||||
|
window.open(" ", "favoritespanel", "msntv:panel");
|
||||||
|
}
|
||||||
|
function goToMessenger() {
|
||||||
|
if (window.external.SafeGetServiceURL('messenger::root') != null && window.external.SafeGetServiceURL('messenger::root') != "") window.open(" ", "impanel", "msntv:panel");
|
||||||
|
else window.location = "msntv:/OLTK/IMBlock.html";
|
||||||
|
}
|
||||||
|
function goToMail() {
|
||||||
|
if (window.external.SafeGetServiceURL('mail::listmail') != null && window.external.SafeGetServiceURL('mail::listmail') != "") window.location = window.external.SafeGetServiceURL('mail::listmail');
|
||||||
|
else window.location = "msntv:/OLTK/EmailBlock.html";
|
||||||
|
}
|
||||||
|
function goToChat() {
|
||||||
|
if (window.external.SafeGetServiceURL('chat::home') != null && window.external.SafeGetServiceURL('chat::home') != "") window.location = window.external.SafeGetServiceURL('chat::home');
|
||||||
|
else window.location = "msntv:/OLTK/chatBlock.html";
|
||||||
|
}
|
||||||
|
function goToSearch() { window.location = window.external.SafeGetServiceURL('search::main'); }
|
||||||
|
function goToMaps() { window.location = window.external.SafeGetServiceURL('maps::main'); }
|
||||||
|
function goToMusicHome() { window.location = window.external.SafeGetServiceURL('Music::Home'); }
|
||||||
|
function goToVideoHome() { window.location = window.external.SafeGetServiceURL('Video::Home'); }
|
||||||
|
function goToNetwork() { window.location = window.external.SafeGetServiceURL('Settings::HomeNetwork'); }
|
||||||
|
function goToAccount() { window.location = window.external.SafeGetServiceURL('UAM::UAMbase'); }
|
||||||
|
function goToSettings() { window.location = window.external.SafeGetServiceURL('settings::mainindex'); }
|
||||||
|
function goToCenter(svcURL) { window.location = svcURL; }
|
||||||
|
function goToPhotosApp() { window.location = window.external.SafeGetServiceURL('Photos'); }
|
||||||
|
function goToPhotosHome() { window.location = window.external.SafeGetServiceURL('Photo::Home'); }
|
||||||
|
|
||||||
|
function initIcons() {
|
||||||
|
var dq = '"';
|
||||||
|
for (index = 0; index < nIcons; index++) {
|
||||||
|
var realIndex = (index + nIcons - 1) % nIcons;
|
||||||
|
var cellHTML = '<span' +
|
||||||
|
' onFocus=' + dq + 'ImgObjs' + realIndex + ".src='" + ImgOverURL[realIndex] + "';" + dq +
|
||||||
|
' onBlur=' + dq + 'ImgObjs' + realIndex + ".src='" + ImgURL[realIndex] + "';" + dq +
|
||||||
|
' onClick=' + dq + IconURL[realIndex] + dq +
|
||||||
|
'><img id=' + dq + 'ImgObjs' + realIndex + dq +
|
||||||
|
" src='" + ImgURL[realIndex] + "' width=" + ImgWidth[realIndex] + ' height=61 border=0></span>';
|
||||||
|
navbar.AddCellHTML(cellHTML, ImgWidth[realIndex]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImgURL[0] = "/Images/Home/HomeIconFav.jpg";
|
||||||
|
ImgOverURL[0] = "/Images/Home/HomeIconFavOver.jpg";
|
||||||
|
ImgWidth[0] = 87;
|
||||||
|
IconURL[0] = "javascript:goToFavorites();";
|
||||||
|
|
||||||
|
ImgURL[1] = "/Images/Home/HomeIconMail.jpg";
|
||||||
|
ImgOverURL[1] = "/Images/Home/HomeIconMailOver.jpg";
|
||||||
|
ImgWidth[1] = 70;
|
||||||
|
IconURL[1] = "javascript:goToMail();";
|
||||||
|
|
||||||
|
ImgURL[2] = "/Images/Home/HomeIconMsgr.jpg";
|
||||||
|
ImgOverURL[2] = "/Images/Home/HomeIconMsgrOver.jpg";
|
||||||
|
ImgWidth[2] = 99;
|
||||||
|
IconURL[2] = "javascript:goToMessenger();";
|
||||||
|
|
||||||
|
ImgURL[3] = "/Images/Home/HomeIconMaps.jpg";
|
||||||
|
ImgOverURL[3] = "/Images/Home/HomeIconMapsOver.jpg";
|
||||||
|
ImgWidth[3] = 60;
|
||||||
|
IconURL[3] = "javascript:goToMaps();";
|
||||||
|
|
||||||
|
ImgURL[4] = "/Images/Home/HomeIconPhoto.jpg";
|
||||||
|
ImgOverURL[4] = "/Images/Home/HomeIconPhotoOver.jpg";
|
||||||
|
ImgWidth[4] = 70;
|
||||||
|
IconURL[4] = "javascript:goToPhotosApp();";
|
||||||
|
|
||||||
|
ImgURL[5] = "/Images/Home/HomeIconVideos.jpg";
|
||||||
|
ImgOverURL[5] = "/Images/Home/HomeIconVideosOver.jpg";
|
||||||
|
ImgWidth[5] = 70;
|
||||||
|
IconURL[5] = "javascript:goToVideoHome();";
|
||||||
|
|
||||||
|
ImgURL[6] = "/Images/Home/HomeIconMusic.jpg";
|
||||||
|
ImgOverURL[6] = "/Images/Home/HomeIconMusicOver.jpg";
|
||||||
|
ImgWidth[6] = 66;
|
||||||
|
IconURL[6] = "javascript:goToMusicHome();";
|
||||||
|
|
||||||
|
ImgURL[7] = "/Images/Home/HomeIconDiscuss.jpg";
|
||||||
|
ImgOverURL[7] = "/Images/Home/HomeIconDiscussOver.jpg";
|
||||||
|
ImgWidth[7] = 72;
|
||||||
|
IconURL[7] = "javascript:goToCenter('/Pages/Discuss/Home.aspx');";
|
||||||
|
|
||||||
|
ImgURL[8] = "/Images/Home/HomeIconMSNBC.jpg";
|
||||||
|
ImgOverURL[8] = "/Images/Home/HomeIconMSNBCOver.jpg";
|
||||||
|
ImgWidth[8] = 68;
|
||||||
|
IconURL[8] = "javascript:goToCenter('/Pages/News/TopStories.aspx');";
|
||||||
|
|
||||||
|
ImgURL[9] = "/Images/Home/HomeIconTWC.jpg";
|
||||||
|
ImgOverURL[9] = "/Images/Home/HomeIconTWCOver.jpg";
|
||||||
|
ImgWidth[9] = 81;
|
||||||
|
IconURL[9] = "javascript:goToCenter('/Pages/Weather/MyCity.aspx');";
|
||||||
|
|
||||||
|
ImgURL[10] = "/Images/Home/HomeIconEnt.jpg";
|
||||||
|
ImgOverURL[10] = "/Images/Home/HomeIconEntOver.jpg";
|
||||||
|
ImgWidth[10] = 125;
|
||||||
|
IconURL[10] = "javascript:goToCenter('/Pages/Entertainment/Home.aspx');";
|
||||||
|
|
||||||
|
ImgURL[11] = "/Images/Home/HomeIconTVListings.jpg";
|
||||||
|
ImgOverURL[11] = "/Images/Home/HomeIconTVListingsOver.jpg";
|
||||||
|
ImgWidth[11] = 96;
|
||||||
|
IconURL[11] = "javascript:goToCenter('http://tv.msn.com/tv/guide');";
|
||||||
|
|
||||||
|
ImgURL[12] = "/Images/Home/HomeIconSports.jpg";
|
||||||
|
ImgOverURL[12] = "/Images/Home/HomeIconSportsOver.jpg";
|
||||||
|
ImgWidth[12] = 82;
|
||||||
|
IconURL[12] = "javascript:goToCenter('/Pages/Sports/Main.aspx');";
|
||||||
|
|
||||||
|
ImgURL[13] = "/Images/Home/HomeIconMoney.jpg";
|
||||||
|
ImgOverURL[13] = "/Images/Home/HomeIconMoneyOver.jpg";
|
||||||
|
ImgWidth[13] = 68;
|
||||||
|
IconURL[13] = "javascript:goToCenter('/Pages/Money/Home.aspx');";
|
||||||
|
|
||||||
|
ImgURL[14] = "/Images/Home/HomeIconShopping.jpg";
|
||||||
|
ImgOverURL[14] = "/Images/Home/HomeIconShoppingOver.jpg";
|
||||||
|
ImgWidth[14] = 62;
|
||||||
|
IconURL[14] = "javascript:goToCenter('/Pages/Shopping/Main.aspx');";
|
||||||
|
|
||||||
|
ImgURL[15] = "/Images/Home/HomeIconGames.jpg";
|
||||||
|
ImgOverURL[15] = "/Images/Home/HomeIconGamesOver.jpg";
|
||||||
|
ImgWidth[15] = 70;
|
||||||
|
IconURL[15] = "javascript:goToCenter('/Pages/Games/Home.aspx');";
|
||||||
|
|
||||||
|
ImgURL[16] = "/Images/Home/HomeIconEncarta.jpg";
|
||||||
|
ImgOverURL[16] = "/Images/Home/HomeIconEncartaOver.jpg";
|
||||||
|
ImgWidth[16] = 74;
|
||||||
|
IconURL[16] = "javascript:goToCenter('http://g.msn.com/5TVANDURIL/1505')";
|
||||||
|
|
||||||
|
ImgURL[17] = "/Images/Home/HomeIconChat.jpg";
|
||||||
|
ImgOverURL[17] = "/Images/Home/HomeIconChatOver.jpg";
|
||||||
|
ImgWidth[17] = 55;
|
||||||
|
IconURL[17] = "javascript:goToChat();";
|
||||||
|
|
||||||
|
ImgURL[18] = "/Images/Home/HomeIconUsingMSN.jpg";
|
||||||
|
ImgOverURL[18] = "/Images/Home/HomeIconUsingMSNOver.jpg";
|
||||||
|
ImgWidth[18] = 127;
|
||||||
|
IconURL[18] = "javascript:goToCenter('/Pages/UsingMSNTV/Main');";
|
||||||
|
|
||||||
|
ImgURL[19] = "/Images/Home/HomeIconTTT.jpg";
|
||||||
|
ImgOverURL[19] = "/Images/Home/HomeIconTTTOver.jpg";
|
||||||
|
ImgWidth[19] = 116;
|
||||||
|
IconURL[19] = "javascript:goToCenter('/Pages/UsingMSNTV/ThingstoTry');";
|
||||||
|
|
||||||
|
ImgURL[20] = "/Images/Home/HomeIconSearch.jpg";
|
||||||
|
ImgOverURL[20] = "/Images/Home/HomeIconSearchOver.jpg";
|
||||||
|
ImgWidth[20] = 84;
|
||||||
|
IconURL[20] = "javascript:goToSearch();";
|
||||||
|
|
||||||
|
ImgObjs = new Array();
|
||||||
|
ImgOverObjs = new Array();
|
||||||
|
nIcons = ImgURL.length;
|
||||||
|
ImgOverObjs[0] = new Image();
|
||||||
|
ImgOverObjs[0].src = ImgOverURL[0];
|
||||||
|
for (var i = 0; i < nIcons; i++) {
|
||||||
|
ImgObjs[i] = new Image();
|
||||||
|
ImgObjs[i].src = ImgURL[i];
|
||||||
|
}
|
||||||
|
for (var i = 1; i < nIcons; i++) {
|
||||||
|
ImgOverObjs[i] = new Image();
|
||||||
|
ImgOverObjs[i].src = ImgOverURL[i];
|
||||||
|
}
|
||||||
|
initIcons();
|
||||||
|
</script>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function callCGif()
|
||||||
|
{
|
||||||
|
var i = new Image();
|
||||||
|
i.src = "http://c.msn.com/c.gif?DI=1455&PI=68206&PS=45577&TP=http://msntv.msn.com/HomePage.htm&RF=";
|
||||||
|
}
|
||||||
|
window.attachEvent("onload", callCGif);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body></html>`;
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
const minisrv_service_file = true;
|
||||||
|
|
||||||
|
headers = `200 OK
|
||||||
|
Content-type: text/html`;
|
||||||
|
|
||||||
|
data = `<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<script>
|
||||||
|
var forceReload = false;
|
||||||
|
var l = 'd:' + new Date().valueOf() + '|';
|
||||||
|
|
||||||
|
function setCookie(name, value) {
|
||||||
|
var now = new Date();
|
||||||
|
var expires = new Date(now.getFullYear() + 1, now.getMonth(), now.getDate());
|
||||||
|
document.cookie = escape(name) + '=' + escape(value) + ';expires=' + expires.toGMTString() + ';path=/';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCookie(name) {
|
||||||
|
var str = document.cookie;
|
||||||
|
var arr = str.split('; ');
|
||||||
|
for (var i = arr.length - 1; i >= 0; i--) {
|
||||||
|
var c = arr[i].split('=');
|
||||||
|
if (c.length != 2) continue;
|
||||||
|
if (unescape(c[0]) == name) return unescape(c[1]);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncCookie(cookieName, propValue) {
|
||||||
|
var c = getCookie(cookieName);
|
||||||
|
l += 'g:' + cookieName + ':' + c + '|';
|
||||||
|
if (c != propValue) {
|
||||||
|
setCookie(cookieName, propValue);
|
||||||
|
l += 's:' + cookieName + ':' + propValue + '|';
|
||||||
|
var check = getCookie(cookieName);
|
||||||
|
if (check == propValue) forceReload = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var d = new Date();
|
||||||
|
var utcOffset = d.getTimezoneOffset();
|
||||||
|
syncCookie('UserUtcOffset', utcOffset);
|
||||||
|
var connSpeed;
|
||||||
|
try {
|
||||||
|
connSpeed = window.external.ClientCaps.connectionType;
|
||||||
|
} catch (e) {
|
||||||
|
connSpeed = "undetected";
|
||||||
|
}
|
||||||
|
syncCookie('UserConnectionSpeed', connSpeed);
|
||||||
|
try {
|
||||||
|
top.log(l);
|
||||||
|
} catch (e) {}
|
||||||
|
if (forceReload) location.replace(location.href);
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div style="top:0px; left:0px; width:176px; height:105px;">
|
||||||
|
<div class="PNGImage" style="width:176px;height:105px;src:/Images/Home/HomeRotatorBGStock.png;"></div>
|
||||||
|
</div>
|
||||||
|
<table cellpadding="0" cellspacing="0" class="stocksTbl">
|
||||||
|
<tbody>
|
||||||
|
<tr height="8">
|
||||||
|
<td width="7"></td>
|
||||||
|
<td width="75"></td>
|
||||||
|
<td width="5"></td>
|
||||||
|
<td width="14"></td>
|
||||||
|
<td width="7"></td>
|
||||||
|
<td width="65"></td>
|
||||||
|
<td width="5"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td class="stocksCell" style="font-weight:bold; overflow:hidden; text-overflow:ellipsis">Dow</td>
|
||||||
|
<td></td>
|
||||||
|
<td>
|
||||||
|
<div class="PNGImage" style="src:/Images/Home/HomeStocksUpArrow.png; width:14px; height:24px"></div>
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
<td class="stocksCell" style="text-align: right;">+54.11</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="stocksRule" colspan="7"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td class="stocksCell" style="font-weight:bold; overflow:hidden; text-overflow:ellipsis">Nasdaq</td>
|
||||||
|
<td></td>
|
||||||
|
<td>
|
||||||
|
<div class="PNGImage" style="src:/Images/Home/HomeStocksUpArrow.png; width:14px; height:24px"></div>
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
<td class="stocksCell" style="text-align: right;">+6.31</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="stocksRule" colspan="7"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td class="stocksCell" style="font-weight:bold; overflow:hidden; text-overflow:ellipsis">S&P</td>
|
||||||
|
<td></td>
|
||||||
|
<td>
|
||||||
|
<div class="PNGImage" style="src:/Images/Home/HomeStocksUpArrow.png; width:14px; height:24px"></div>
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
<td class="stocksCell" style="text-align: right;">+3.19</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="stocksRule" colspan="7"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td id="ProviderID" class="wthrProvider" colspan="6">Source: MSN Money</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<!--<ROTATOR_FEEDBACK></ROTATOR_FEEDBACK>--><!--<ROTATOR_CLICKTHROUGH>/Pages/Money/MyStocks.aspx</ROTATOR_CLICKTHROUGH>-->
|
||||||
|
<script>
|
||||||
|
function clickPageRotatePanel() {
|
||||||
|
location.href = "/Pages/Money/MyStocks.aspx";
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>`;
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 771 B |
|
After Width: | Height: | Size: 685 B |
|
After Width: | Height: | Size: 806 B |
|
After Width: | Height: | Size: 175 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 383 B |
|
After Width: | Height: | Size: 403 B |
@@ -0,0 +1,104 @@
|
|||||||
|
const minisrv_service_file = true;
|
||||||
|
|
||||||
|
headers = `200 OK
|
||||||
|
Content-type: text/html`;
|
||||||
|
|
||||||
|
data = `<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<script>
|
||||||
|
var forceReload = false;
|
||||||
|
var l = 'd:' + new Date().valueOf() + '|';
|
||||||
|
|
||||||
|
function setCookie(name, value) {
|
||||||
|
var now = new Date();
|
||||||
|
var expires = new Date(now.getFullYear() + 1, now.getMonth(), now.getDate());
|
||||||
|
document.cookie = escape(name) + '=' + escape(value) + ';expires=' + expires.toGMTString() + ';path=/';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCookie(name) {
|
||||||
|
var str = document.cookie;
|
||||||
|
var arr = str.split('; ');
|
||||||
|
for (var i = arr.length - 1; i >= 0; i--) {
|
||||||
|
var c = arr[i].split('=');
|
||||||
|
if (c.length != 2) continue;
|
||||||
|
if (unescape(c[0]) == name) return unescape(c[1]);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncCookie(cookieName, propValue) {
|
||||||
|
var c = getCookie(cookieName);
|
||||||
|
l += 'g:' + cookieName + ':' + c + '|';
|
||||||
|
if (c != propValue) {
|
||||||
|
setCookie(cookieName, propValue);
|
||||||
|
l += 's:' + cookieName + ':' + propValue + '|';
|
||||||
|
var check = getCookie(cookieName);
|
||||||
|
if (check == propValue) forceReload = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var d = new Date();
|
||||||
|
var utcOffset = d.getTimezoneOffset();
|
||||||
|
syncCookie('UserUtcOffset', utcOffset);
|
||||||
|
var connSpeed;
|
||||||
|
try {
|
||||||
|
connSpeed = window.external.ClientCaps.connectionType;
|
||||||
|
} catch (e) {
|
||||||
|
connSpeed = "undetected";
|
||||||
|
}
|
||||||
|
syncCookie('UserConnectionSpeed', connSpeed);
|
||||||
|
try {
|
||||||
|
top.log(l);
|
||||||
|
} catch (e) {}
|
||||||
|
if (forceReload) location.replace(location.href);
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div style="top:0px; left:0px; width:176px; height:105px;" xmlns:msntvuxp="msntvuxp.microsoft.com">
|
||||||
|
<div class="PNGImage" style="width:176px;height:105px;src:/Images/Home/HomeRotatorBGWeather.png;"></div>
|
||||||
|
</div>
|
||||||
|
<div style="position:absolute; top:0px; left:0px; width:178px; height:107px;" xmlns:msntvuxp="msntvuxp.microsoft.com">
|
||||||
|
<table class="wthrTbl" border="0" cellpadding="1" cellspacing="0">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td height="4" width="4" rowspan="4"><img src="/Images/Shared/s.gif" height="4" width="4"></td>
|
||||||
|
<td height="4" width="45"><img src="/Images/Shared/s.gif" height="4" width="45"></td>
|
||||||
|
<td height="4" width="10"><img src="/Images/Shared/s.gif" height="4" width="10"></td>
|
||||||
|
<td height="4" width="65"><img src="/Images/Shared/s.gif" height="4" width="65"></td>
|
||||||
|
<td height="4" width="10" rowspan="4"><img src="/Images/Shared/s.gif" height="4" width="10"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td id="CityCellID" class="wthrCityCell" colspan="3" valign="top"><span class="wthrCityText">Your City</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td id="TRCID" class="wthrTempCond">
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td id="TemperatureCellID" class="wthrTempCell"><span class="wthrTempTxt">63°/50</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td id="ConditionCellID" class="wthrCondCell"><span class="wthrCondTxt">Mostly</span><br><span class="wthrCondTxt"> Cloudy</span></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
<td id="PaddingID" width="10"><img src="/Images/Shared/s.gif" height="1" width="10"></td>
|
||||||
|
<td id="ConditionIconID" class="wthrCondIcon"><span class="PNGImage" style="src:/Images/Shared/Weather/28.png;width:65px;height:61px;"></span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td id="ProviderID" class="wthrProvider" colspan="3">The Weather Channel ®</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--<ROTATOR_FEEDBACK></ROTATOR_FEEDBACK>--><!--<ROTATOR_CLICKTHROUGH>/Pages/Weather/YourCity.aspx</ROTATOR_CLICKTHROUGH>-->
|
||||||
|
<script xmlns:msntvuxp="msntvuxp.microsoft.com">
|
||||||
|
function clickPageRotatePanel() {
|
||||||
|
location.href = "/Pages/Weather/YourCity.aspx";
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>`;
|
||||||
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 671 B |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 811 B |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 811 B |
|
After Width: | Height: | Size: 1.0 KiB |