Skip to content

Commit d412db1

Browse files
committed
feat: 转换 scMaxEachPostBytes 为 mihomo 兼容
1 parent 6006832 commit d412db1

3 files changed

Lines changed: 169 additions & 5 deletions

File tree

backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sub-store",
3-
"version": "2.21.81",
3+
"version": "2.21.82",
44
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket.",
55
"main": "src/main.js",
66
"scripts": {

backend/src/core/proxy-utils/parsers/index.js

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -873,12 +873,32 @@ function URI_VLESS() {
873873
if (isNotBlank(extra?.['xPaddingBytes'])) {
874874
xhttpOpts['x-padding-bytes'] = extra['xPaddingBytes'];
875875
}
876+
const scMaxEachPostBytes =
877+
extra?.['scMaxEachPostBytes'];
876878
if (
877-
typeof extra?.['scMaxEachPostBytes'] === 'number' &&
878-
Number.isFinite(extra['scMaxEachPostBytes'])
879+
typeof scMaxEachPostBytes === 'string' ||
880+
typeof scMaxEachPostBytes === 'number'
879881
) {
880-
xhttpOpts['sc-max-each-post-bytes'] =
881-
extra['scMaxEachPostBytes'];
882+
const normalizedValue = `${scMaxEachPostBytes}`;
883+
if (/^\s*[1-9]\d*\s*$/.test(normalizedValue)) {
884+
xhttpOpts['sc-max-each-post-bytes'] = parseInt(
885+
normalizedValue,
886+
10,
887+
);
888+
} else {
889+
const rangeMatch = normalizedValue.match(
890+
/^\s*([1-9]\d*)\s*-\s*([1-9]\d*)\s*$/,
891+
);
892+
if (rangeMatch) {
893+
const lowerBound = parseInt(rangeMatch[1], 10);
894+
const upperBound = parseInt(rangeMatch[2], 10);
895+
896+
if (upperBound >= lowerBound) {
897+
xhttpOpts['sc-max-each-post-bytes'] =
898+
upperBound;
899+
}
900+
}
901+
}
882902
}
883903
if (isPlainObject(extra?.xmux)) {
884904
const reuseSettings = {};

backend/src/test/proxy-parsers/v2ray-and-platforms.spec.js

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Base64 } from 'js-base64';
2+
import { expect } from 'chai';
23
import { describe, it } from 'mocha';
34

45
import { UUID, expectSubset, parseOne } from './helpers';
@@ -418,6 +419,149 @@ describe('VMess and VLESS parser coverage', function () {
418419
});
419420
});
420421

422+
it('parses xhttp VLESS shares with range-form scMaxEachPostBytes', function () {
423+
const extra = JSON.stringify({
424+
noGRPCHeader: true,
425+
xPaddingBytes: '64-128',
426+
scMaxEachPostBytes: '500000 - 1000000',
427+
xmux: {
428+
maxConnections: 0,
429+
maxConcurrency: '16-32',
430+
cMaxReuseTimes: '64-128',
431+
hMaxRequestTimes: '600-900',
432+
hMaxReusableSecs: '1800-3000',
433+
},
434+
});
435+
const proxy = parseOne(
436+
`vless://${UUID}@vless-xhttp.example.com:443?type=xhttp&security=tls&host=cdn.example.com&path=%2Fxhttp&mode=stream-up&extra=${encodeURIComponent(extra)}#VLESS%20XHTTP%20Range`,
437+
);
438+
439+
expectSubset(proxy, {
440+
type: 'vless',
441+
name: 'VLESS XHTTP Range',
442+
server: 'vless-xhttp.example.com',
443+
port: 443,
444+
uuid: UUID,
445+
tls: true,
446+
network: 'xhttp',
447+
_extra: extra,
448+
'xhttp-opts': {
449+
mode: 'stream-up',
450+
path: '/xhttp',
451+
headers: {
452+
Host: 'cdn.example.com',
453+
},
454+
'no-grpc-header': true,
455+
'x-padding-bytes': '64-128',
456+
'sc-max-each-post-bytes': 1000000,
457+
'reuse-settings': {
458+
'max-connections': '0',
459+
'max-concurrency': '16-32',
460+
'c-max-reuse-times': '64-128',
461+
'h-max-request-times': '600-900',
462+
'h-max-reusable-secs': '1800-3000',
463+
},
464+
},
465+
});
466+
});
467+
468+
it('parses xhttp VLESS shares with string-form scMaxEachPostBytes', function () {
469+
const extra = JSON.stringify({
470+
noGRPCHeader: true,
471+
xPaddingBytes: '64-128',
472+
scMaxEachPostBytes: '1000000',
473+
xmux: {
474+
maxConnections: 0,
475+
maxConcurrency: '16-32',
476+
cMaxReuseTimes: '64-128',
477+
hMaxRequestTimes: '600-900',
478+
hMaxReusableSecs: '1800-3000',
479+
},
480+
});
481+
const proxy = parseOne(
482+
`vless://${UUID}@vless-xhttp.example.com:443?type=xhttp&security=tls&host=cdn.example.com&path=%2Fxhttp&mode=stream-up&extra=${encodeURIComponent(extra)}#VLESS%20XHTTP%20String`,
483+
);
484+
485+
expectSubset(proxy, {
486+
type: 'vless',
487+
name: 'VLESS XHTTP String',
488+
server: 'vless-xhttp.example.com',
489+
port: 443,
490+
uuid: UUID,
491+
tls: true,
492+
network: 'xhttp',
493+
_extra: extra,
494+
'xhttp-opts': {
495+
mode: 'stream-up',
496+
path: '/xhttp',
497+
headers: {
498+
Host: 'cdn.example.com',
499+
},
500+
'no-grpc-header': true,
501+
'x-padding-bytes': '64-128',
502+
'sc-max-each-post-bytes': 1000000,
503+
'reuse-settings': {
504+
'max-connections': '0',
505+
'max-concurrency': '16-32',
506+
'c-max-reuse-times': '64-128',
507+
'h-max-request-times': '600-900',
508+
'h-max-reusable-secs': '1800-3000',
509+
},
510+
},
511+
});
512+
});
513+
514+
it('ignores invalid xhttp VLESS scMaxEachPostBytes values', function () {
515+
const invalidValues = ['1.5', 1.5, '0', 0, 'fast', '10-1'];
516+
517+
for (const value of invalidValues) {
518+
const extra = JSON.stringify({
519+
noGRPCHeader: true,
520+
xPaddingBytes: '64-128',
521+
scMaxEachPostBytes: value,
522+
xmux: {
523+
maxConnections: 0,
524+
maxConcurrency: '16-32',
525+
cMaxReuseTimes: '64-128',
526+
hMaxRequestTimes: '600-900',
527+
hMaxReusableSecs: '1800-3000',
528+
},
529+
});
530+
const proxy = parseOne(
531+
`vless://${UUID}@vless-xhttp.example.com:443?type=xhttp&security=tls&host=cdn.example.com&path=%2Fxhttp&mode=stream-up&extra=${encodeURIComponent(extra)}#VLESS%20XHTTP%20Invalid`,
532+
);
533+
534+
expectSubset(proxy, {
535+
type: 'vless',
536+
server: 'vless-xhttp.example.com',
537+
port: 443,
538+
uuid: UUID,
539+
tls: true,
540+
network: 'xhttp',
541+
_extra: extra,
542+
'xhttp-opts': {
543+
mode: 'stream-up',
544+
path: '/xhttp',
545+
headers: {
546+
Host: 'cdn.example.com',
547+
},
548+
'no-grpc-header': true,
549+
'x-padding-bytes': '64-128',
550+
'reuse-settings': {
551+
'max-connections': '0',
552+
'max-concurrency': '16-32',
553+
'c-max-reuse-times': '64-128',
554+
'h-max-request-times': '600-900',
555+
'h-max-reusable-secs': '1800-3000',
556+
},
557+
},
558+
});
559+
expect(proxy['xhttp-opts']).to.not.have.property(
560+
'sc-max-each-post-bytes',
561+
);
562+
}
563+
});
564+
421565
it('parses Shadowrocket VLESS shares', function () {
422566
const base = Base64.encode(
423567
`none:${UUID}@shadowrocket-vless.example.com:443`,

0 commit comments

Comments
 (0)