@@ -2,9 +2,16 @@ import { expect } from 'chai';
22import { describe , it } from 'mocha' ;
33
44import { ProxyUtils } from '@/core/proxy-utils' ;
5+ import { RuleUtils } from '@/core/rule-utils' ;
6+ import * as ageUtils from '@/utils/age' ;
57
68describe ( 'Loon resource parser' , function ( ) {
7- const modulePath = require . resolve ( '../../products/resource-parser.loon.js' ) ;
9+ this . timeout ( 10000 ) ;
10+
11+ const modulePath = require . resolve (
12+ '../../products/resource-parser.loon.js' ,
13+ ) ;
14+ const downloadModule = require ( '@/utils/download' ) ;
815
916 function cleanupGlobals ( ) {
1017 delete global . $argument ;
@@ -19,6 +26,30 @@ describe('Loon resource parser', function () {
1926 delete require . cache [ modulePath ] ;
2027 }
2128
29+ async function runParser ( {
30+ argument = '' ,
31+ resource = '' ,
32+ resourceType = 1 ,
33+ resourceUrl = '' ,
34+ } = { } ) {
35+ global . $loon = 'Loon(842)' ;
36+ global . $argument = argument ;
37+ global . $resource = resource ;
38+ global . $resourceType = resourceType ;
39+ global . $resourceUrl = resourceUrl ;
40+
41+ return await new Promise ( ( resolve , reject ) => {
42+ global . $done = resolve ;
43+
44+ try {
45+ resetModule ( ) ;
46+ require ( modulePath ) ;
47+ } catch ( error ) {
48+ reject ( error ) ;
49+ }
50+ } ) ;
51+ }
52+
2253 it ( 'does not auto-enable includeUnsupportedProxy from build number' , async function ( ) {
2354 const originalParse = ProxyUtils . parse ;
2455 const originalProduce = ProxyUtils . produce ;
@@ -59,4 +90,250 @@ describe('Loon resource parser', function () {
5990 cleanupGlobals ( ) ;
6091 }
6192 } ) ;
93+
94+ it ( 'decrypts inline age-armored proxy resources from parser arguments' , async function ( ) {
95+ const originalParse = ProxyUtils . parse ;
96+ const originalProduce = ProxyUtils . produce ;
97+ let capturedInput ;
98+
99+ try {
100+ const pair = await ageUtils . generateKeyPair ( ) ;
101+ const armored = await ageUtils . encryptArmor (
102+ 'decrypted-proxy-resource' ,
103+ pair [ 'age-public-key' ] ,
104+ ) ;
105+
106+ ProxyUtils . parse = ( input ) => {
107+ capturedInput = input ;
108+ return [ { type : 'ss' } ] ;
109+ } ;
110+ ProxyUtils . produce = ( ) => 'proxy-output' ;
111+
112+ const result = await runParser ( {
113+ argument : `age-secret-key=${ encodeURIComponent (
114+ pair [ 'age-secret-key' ] ,
115+ ) } `,
116+ resource : armored ,
117+ resourceType : 1 ,
118+ } ) ;
119+
120+ expect ( result ) . to . equal ( 'proxy-output' ) ;
121+ expect ( capturedInput ) . to . equal ( 'decrypted-proxy-resource' ) ;
122+ } finally {
123+ ProxyUtils . parse = originalParse ;
124+ ProxyUtils . produce = originalProduce ;
125+ resetModule ( ) ;
126+ cleanupGlobals ( ) ;
127+ }
128+ } ) ;
129+
130+ it ( 'decrypts inline age-armored rule resources from parser arguments' , async function ( ) {
131+ const originalParse = RuleUtils . parse ;
132+ const originalProduce = RuleUtils . produce ;
133+ let capturedInput ;
134+
135+ try {
136+ const pair = await ageUtils . generateKeyPair ( ) ;
137+ const armored = await ageUtils . encryptArmor (
138+ 'DOMAIN,example.com,Proxy' ,
139+ pair [ 'age-public-key' ] ,
140+ ) ;
141+
142+ RuleUtils . parse = ( input ) => {
143+ capturedInput = input ;
144+ return [ { type : 'DOMAIN' , payload : 'example.com' } ] ;
145+ } ;
146+ RuleUtils . produce = ( ) => 'rule-output' ;
147+
148+ const result = await runParser ( {
149+ argument : `age-secret-key=${ encodeURIComponent (
150+ pair [ 'age-secret-key' ] ,
151+ ) } `,
152+ resource : armored ,
153+ resourceType : 2 ,
154+ } ) ;
155+
156+ expect ( result ) . to . equal ( 'rule-output' ) ;
157+ expect ( capturedInput ) . to . equal ( 'DOMAIN,example.com,Proxy' ) ;
158+ } finally {
159+ RuleUtils . parse = originalParse ;
160+ RuleUtils . produce = originalProduce ;
161+ resetModule ( ) ;
162+ cleanupGlobals ( ) ;
163+ }
164+ } ) ;
165+
166+ it ( 'keeps plain inline resources unchanged when age-secret-key is present' , async function ( ) {
167+ const originalParse = ProxyUtils . parse ;
168+ const originalProduce = ProxyUtils . produce ;
169+ let capturedInput ;
170+
171+ try {
172+ const pair = await ageUtils . generateKeyPair ( ) ;
173+
174+ ProxyUtils . parse = ( input ) => {
175+ capturedInput = input ;
176+ return [ { type : 'ss' } ] ;
177+ } ;
178+ ProxyUtils . produce = ( ) => 'proxy-output' ;
179+
180+ const result = await runParser ( {
181+ argument : `age-secret-key=${ encodeURIComponent (
182+ pair [ 'age-secret-key' ] ,
183+ ) } `,
184+ resource : 'plain-proxy-resource' ,
185+ resourceType : 1 ,
186+ } ) ;
187+
188+ expect ( result ) . to . equal ( 'proxy-output' ) ;
189+ expect ( capturedInput ) . to . equal ( 'plain-proxy-resource' ) ;
190+ } finally {
191+ ProxyUtils . parse = originalParse ;
192+ ProxyUtils . produce = originalProduce ;
193+ resetModule ( ) ;
194+ cleanupGlobals ( ) ;
195+ }
196+ } ) ;
197+
198+ it ( 'passes parser age-secret-key to remote proxy downloads' , async function ( ) {
199+ const originalDownload = downloadModule . default ;
200+ const originalParse = ProxyUtils . parse ;
201+ const originalProduce = ProxyUtils . produce ;
202+ let capturedInput ;
203+ let capturedOptions ;
204+
205+ try {
206+ const pair = await ageUtils . generateKeyPair ( ) ;
207+ const armored = await ageUtils . encryptArmor (
208+ 'remote-proxy-resource' ,
209+ pair [ 'age-public-key' ] ,
210+ ) ;
211+
212+ downloadModule . default = async ( ...args ) => {
213+ capturedOptions = args [ 8 ] ;
214+ return capturedOptions ?. [ 'age-secret-key' ] ===
215+ pair [ 'age-secret-key' ]
216+ ? 'remote-proxy-resource'
217+ : armored ;
218+ } ;
219+ ProxyUtils . parse = ( input ) => {
220+ capturedInput = input ;
221+ return [ { type : 'ss' } ] ;
222+ } ;
223+ ProxyUtils . produce = ( ) => 'proxy-output' ;
224+
225+ const result = await runParser ( {
226+ argument : `resourceUrlOnly=true&age-secret-key=${ encodeURIComponent (
227+ pair [ 'age-secret-key' ] ,
228+ ) } `,
229+ resource : '' ,
230+ resourceType : 1 ,
231+ resourceUrl : 'https://example.com/proxy.txt' ,
232+ } ) ;
233+
234+ expect ( result ) . to . equal ( 'proxy-output' ) ;
235+ expect ( capturedInput ) . to . equal ( 'remote-proxy-resource' ) ;
236+ expect ( capturedOptions ) . to . deep . include ( {
237+ 'age-secret-key' : pair [ 'age-secret-key' ] ,
238+ } ) ;
239+ } finally {
240+ downloadModule . default = originalDownload ;
241+ ProxyUtils . parse = originalParse ;
242+ ProxyUtils . produce = originalProduce ;
243+ resetModule ( ) ;
244+ cleanupGlobals ( ) ;
245+ }
246+ } ) ;
247+
248+ it ( 'passes parser age-secret-key to remote rule downloads' , async function ( ) {
249+ const originalDownload = downloadModule . default ;
250+ const originalParse = RuleUtils . parse ;
251+ const originalProduce = RuleUtils . produce ;
252+ let capturedInput ;
253+ let capturedOptions ;
254+
255+ try {
256+ const pair = await ageUtils . generateKeyPair ( ) ;
257+ const armored = await ageUtils . encryptArmor (
258+ 'DOMAIN,remote.example,Proxy' ,
259+ pair [ 'age-public-key' ] ,
260+ ) ;
261+
262+ downloadModule . default = async ( ...args ) => {
263+ capturedOptions = args [ 8 ] ;
264+ return capturedOptions ?. [ 'age-secret-key' ] ===
265+ pair [ 'age-secret-key' ]
266+ ? 'DOMAIN,remote.example,Proxy'
267+ : armored ;
268+ } ;
269+ RuleUtils . parse = ( input ) => {
270+ capturedInput = input ;
271+ return [ { type : 'DOMAIN' , payload : 'remote.example' } ] ;
272+ } ;
273+ RuleUtils . produce = ( ) => 'rule-output' ;
274+
275+ const result = await runParser ( {
276+ argument : `resourceUrlOnly=true&age-secret-key=${ encodeURIComponent (
277+ pair [ 'age-secret-key' ] ,
278+ ) } `,
279+ resource : 'ignored-rule-resource' ,
280+ resourceType : 2 ,
281+ resourceUrl : 'https://example.com/rule.txt' ,
282+ } ) ;
283+
284+ expect ( result ) . to . equal ( 'rule-output' ) ;
285+ expect ( capturedInput ) . to . equal ( 'DOMAIN,remote.example,Proxy' ) ;
286+ expect ( capturedOptions ) . to . deep . include ( {
287+ 'age-secret-key' : pair [ 'age-secret-key' ] ,
288+ } ) ;
289+ } finally {
290+ downloadModule . default = originalDownload ;
291+ RuleUtils . parse = originalParse ;
292+ RuleUtils . produce = originalProduce ;
293+ resetModule ( ) ;
294+ cleanupGlobals ( ) ;
295+ }
296+ } ) ;
297+
298+ it ( 'does not expose age-secret-key or return armor on decrypt failure' , async function ( ) {
299+ const originalParse = ProxyUtils . parse ;
300+ const originalProduce = ProxyUtils . produce ;
301+ const originalConsoleLog = console . log ;
302+ let logs = [ ] ;
303+
304+ try {
305+ const encryptPair = await ageUtils . generateKeyPair ( ) ;
306+ const wrongPair = await ageUtils . generateKeyPair ( ) ;
307+ const armored = await ageUtils . encryptArmor (
308+ 'decrypted-proxy-resource' ,
309+ encryptPair [ 'age-public-key' ] ,
310+ ) ;
311+
312+ ProxyUtils . parse = ( ) => {
313+ throw new Error ( 'parse should not succeed' ) ;
314+ } ;
315+ ProxyUtils . produce = ( ) => 'proxy-output' ;
316+ console . log = ( message ) => {
317+ logs . push ( String ( message ) ) ;
318+ } ;
319+
320+ const result = await runParser ( {
321+ argument : `age-secret-key=${ encodeURIComponent (
322+ wrongPair [ 'age-secret-key' ] ,
323+ ) } `,
324+ resource : armored ,
325+ resourceType : 1 ,
326+ } ) ;
327+
328+ expect ( result ) . to . not . contain ( ageUtils . AGE_ARMOR_HEADER ) ;
329+ expect ( logs . join ( '\n' ) ) . to . not . contain ( wrongPair [ 'age-secret-key' ] ) ;
330+ expect ( logs . join ( '\n' ) ) . to . contain ( 'age-secret-key' ) ;
331+ } finally {
332+ ProxyUtils . parse = originalParse ;
333+ ProxyUtils . produce = originalProduce ;
334+ console . log = originalConsoleLog ;
335+ resetModule ( ) ;
336+ cleanupGlobals ( ) ;
337+ }
338+ } ) ;
62339} ) ;
0 commit comments