一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
69 行
2.3 KiB
TypeScript
69 行
2.3 KiB
TypeScript
import { ProviderClassification, ProviderType } from './types';
|
|
|
|
// The split is "does my data leave my private network", so loopback, LAN and
|
|
// internal-only hostnames all count as local. `.local` is excluded on purpose:
|
|
// mDNS can resolve it to any machine on an untrusted network.
|
|
const privateHosts = new Set(['localhost', '::1', '0.0.0.0', '::']);
|
|
|
|
const privateSuffixes = ['.localhost', '.internal', '.home.arpa'];
|
|
|
|
// Dotted quad is enough because new URL() has already canonicalised octal, hex,
|
|
// decimal and short forms - `http://2130706433/` arrives here as "127.0.0.1".
|
|
const isPrivateIpV4 = (host: string) => {
|
|
const parts = host.split('.');
|
|
if (parts.length !== 4) return false;
|
|
|
|
const bytes = parts.map(p => (/^\d{1,3}$/.test(p) ? Number(p) : -1));
|
|
if (bytes.some(b => b < 0 || b > 255)) return false;
|
|
|
|
const [a, b] = bytes;
|
|
if (a === 10) return true;
|
|
if (a === 127) return true;
|
|
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
if (a === 192 && b === 168) return true;
|
|
if (a === 169 && b === 254) return true;
|
|
return false;
|
|
};
|
|
|
|
const isPrivateIpV6 = (host: string) => {
|
|
// URL.hostname keeps the brackets around IPv6 literals.
|
|
const address = host.replace(/^\[/, '').replace(/\]$/, '');
|
|
if (privateHosts.has(address)) return true;
|
|
// Unique local (fc00::/7) then link-local (fe80::/10).
|
|
if (/^f[cd][0-9a-f]{2}:/.test(address)) return true;
|
|
if (/^fe[89ab][0-9a-f]:/.test(address)) return true;
|
|
return false;
|
|
};
|
|
|
|
const isPrivateHost = (host: string) => {
|
|
if (!host) return false;
|
|
if (privateHosts.has(host)) return true;
|
|
if (privateSuffixes.some(suffix => host === suffix.substring(1) || host.endsWith(suffix))) return true;
|
|
if (isPrivateIpV4(host)) return true;
|
|
if (isPrivateIpV6(host)) return true;
|
|
return false;
|
|
};
|
|
|
|
const hostFromBaseUrl = (baseUrl: string): string => {
|
|
if (!baseUrl) return '';
|
|
try {
|
|
return new URL(baseUrl).hostname.toLowerCase();
|
|
} catch {
|
|
return '';
|
|
}
|
|
};
|
|
|
|
const deriveClassification = (
|
|
providerType: ProviderType,
|
|
baseUrl: string,
|
|
): ProviderClassification => {
|
|
if (providerType === 'anthropic') return 'remote';
|
|
if (providerType === 'joplin-cloud') return 'remote';
|
|
if (providerType === 'openai-compatible') {
|
|
return isPrivateHost(hostFromBaseUrl(baseUrl)) ? 'local' : 'remote';
|
|
}
|
|
return 'remote';
|
|
};
|
|
|
|
export default deriveClassification;
|