• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import fs from 'fs';
17import path from 'path';
18import JSON5 from 'json5';
19
20import type * as ts from 'typescript';
21
22import { FileUtils } from '../utils/FileUtils';
23import {
24  type ReservedNameInfo,
25  ApiExtractor,
26  containWildcards,
27  getMapFromJson,
28  performancePrinter,
29  PropCollections,
30  renameFileNameModule,
31  separateUniversalReservedItem,
32  wildcardTransformer,
33} from '../ArkObfuscator';
34
35import { isDebug, isFileExist, sortAndDeduplicateStringArr } from './utils';
36import { nameCacheMap, yellow } from './CommonObject';
37import { ListUtil } from '../utils/ListUtil';
38
39enum OptionType {
40  NONE,
41  KEEP,
42  KEEP_DTS,
43  KEEP_GLOBAL_NAME,
44  KEEP_PROPERTY_NAME,
45  KEEP_FILE_NAME,
46  KEEP_COMMENTS,
47  DISABLE_OBFUSCATION,
48  ENABLE_PROPERTY_OBFUSCATION,
49  ENABLE_STRING_PROPERTY_OBFUSCATION,
50  ENABLE_TOPLEVEL_OBFUSCATION,
51  ENABLE_FILENAME_OBFUSCATION,
52  ENABLE_EXPORT_OBFUSCATION,
53  COMPACT,
54  REMOVE_LOG,
55  REMOVE_COMMENTS,
56  PRINT_NAMECACHE,
57  APPLY_NAMECACHE,
58}
59
60type SystemApiContent = {
61  ReservedNames?: string[];
62  ReservedPropertyNames?: string[];
63  ReservedGlobalNames?: string[];
64};
65
66/* ObConfig's properties:
67 *   ruleOptions: {
68 *    enable: boolean
69 *    rules: string[]
70 *   }
71 *   consumerRules: string[]
72 *
73 * ObfuscationConfig's properties:
74 *   selfConfig: ObConfig
75 *   dependencies: { libraries: ObConfig[], hars: string[] }
76 *   sdkApis: string[]
77 *   obfuscationCacheDir: string
78 *   exportRulePath: string
79 */
80class ObOptions {
81  disableObfuscation: boolean = false;
82  enablePropertyObfuscation: boolean = false;
83  enableStringPropertyObfuscation: boolean = false;
84  enableToplevelObfuscation: boolean = false;
85  enableFileNameObfuscation: boolean = false;
86  enableExportObfuscation: boolean = false;
87  removeComments: boolean = false;
88  compact: boolean = false;
89  removeLog: boolean = false;
90  printNameCache: string = '';
91  applyNameCache: string = '';
92
93  merge(other: ObOptions): void {
94    this.disableObfuscation = this.disableObfuscation || other.disableObfuscation;
95    this.enablePropertyObfuscation = this.enablePropertyObfuscation || other.enablePropertyObfuscation;
96    this.enableToplevelObfuscation = this.enableToplevelObfuscation || other.enableToplevelObfuscation;
97    this.enableStringPropertyObfuscation =
98      this.enableStringPropertyObfuscation || other.enableStringPropertyObfuscation;
99    this.removeComments = this.removeComments || other.removeComments;
100    this.compact = this.compact || other.compact;
101    this.removeLog = this.removeLog || other.removeLog;
102    this.enableFileNameObfuscation = this.enableFileNameObfuscation || other.enableFileNameObfuscation;
103    this.enableExportObfuscation = this.enableExportObfuscation || other.enableExportObfuscation;
104    if (other.printNameCache.length > 0) {
105      this.printNameCache = other.printNameCache;
106    }
107    if (other.applyNameCache.length > 0) {
108      this.applyNameCache = other.applyNameCache;
109    }
110  }
111}
112
113export class MergedConfig {
114  options: ObOptions = new ObOptions();
115  reservedPropertyNames: string[] = [];
116  reservedGlobalNames: string[] = [];
117  reservedNames: string[] = [];
118  reservedFileNames: string[] = [];
119  keepComments: string[] = [];
120  keepSourceOfPaths: string[] = []; // The file path or folder path configured by the developer.
121  universalReservedPropertyNames: RegExp[] = []; // Support reserved property names contain wildcards.
122  universalReservedGlobalNames: RegExp[] = []; // Support reserved global names contain wildcards.
123  keepUniversalPaths: RegExp[] = []; // Support reserved paths contain wildcards.
124  excludeUniversalPaths: RegExp[] = []; // Support excluded paths contain wildcards.
125  excludePathSet: Set<string> = new Set();
126
127  merge(other: MergedConfig): void {
128    this.options.merge(other.options);
129    this.reservedPropertyNames.push(...other.reservedPropertyNames);
130    this.reservedGlobalNames.push(...other.reservedGlobalNames);
131    this.reservedFileNames.push(...other.reservedFileNames);
132    this.keepComments.push(...other.keepComments);
133    this.keepSourceOfPaths.push(...other.keepSourceOfPaths);
134    this.keepUniversalPaths.push(...other.keepUniversalPaths);
135    this.excludeUniversalPaths.push(...other.excludeUniversalPaths);
136    other.excludePathSet.forEach((excludePath) => {
137      this.excludePathSet.add(excludePath);
138    });
139  }
140
141  sortAndDeduplicate(): void {
142    this.reservedPropertyNames = sortAndDeduplicateStringArr(this.reservedPropertyNames);
143    this.reservedGlobalNames = sortAndDeduplicateStringArr(this.reservedGlobalNames);
144    this.reservedFileNames = sortAndDeduplicateStringArr(this.reservedFileNames);
145    this.keepComments = sortAndDeduplicateStringArr(this.keepComments);
146    this.keepSourceOfPaths = sortAndDeduplicateStringArr(this.keepSourceOfPaths);
147  }
148
149  serializeMergedConfig(): string {
150    let resultStr: string = '';
151    const keys = Object.keys(this.options);
152    for (const key of keys) {
153      // skip the export of some switches.
154      if (this.options[key] === true && ObConfigResolver.exportedSwitchMap.has(String(key))) {
155        resultStr += ObConfigResolver.exportedSwitchMap.get(String(key)) + '\n';
156      }
157    }
158
159    if (this.reservedGlobalNames.length > 0) {
160      resultStr += ObConfigResolver.KEEP_GLOBAL_NAME + '\n';
161      this.reservedGlobalNames.forEach((item) => {
162        resultStr += item + '\n';
163      });
164    }
165    if (this.reservedPropertyNames.length > 0) {
166      resultStr += ObConfigResolver.KEEP_PROPERTY_NAME + '\n';
167      this.reservedPropertyNames.forEach((item) => {
168        resultStr += item + '\n';
169      });
170    }
171    return resultStr;
172  }
173}
174
175export class ObConfigResolver {
176  sourceObConfig: any;
177  logger: any;
178  isHarCompiled: boolean | undefined;
179  isTerser: boolean;
180
181  constructor(projectConfig: any, logger: any, isTerser?: boolean) {
182    this.sourceObConfig = projectConfig.obfuscationOptions;
183    this.logger = logger;
184    this.isHarCompiled = projectConfig.compileHar;
185    this.isTerser = isTerser;
186  }
187
188  public resolveObfuscationConfigs(): MergedConfig {
189    let sourceObConfig = this.sourceObConfig;
190    if (!sourceObConfig) {
191      return new MergedConfig();
192    }
193    let enableObfuscation: boolean = sourceObConfig.selfConfig.ruleOptions.enable;
194
195    let selfConfig: MergedConfig = new MergedConfig();
196    if (enableObfuscation) {
197      this.getSelfConfigs(selfConfig);
198      enableObfuscation = !selfConfig.options.disableObfuscation;
199    } else {
200      selfConfig.options.disableObfuscation = true;
201    }
202
203    let needConsumerConfigs: boolean =
204      this.isHarCompiled &&
205      sourceObConfig.selfConfig.consumerRules &&
206      sourceObConfig.selfConfig.consumerRules.length > 0;
207    let needDependencyConfigs: boolean = enableObfuscation || needConsumerConfigs;
208
209    let dependencyConfigs: MergedConfig = new MergedConfig();
210    const dependencyMaxLength: number = Math.max(
211      sourceObConfig.dependencies.libraries.length,
212      sourceObConfig.dependencies.hars.length,
213    );
214    if (needDependencyConfigs && dependencyMaxLength > 0) {
215      dependencyConfigs = new MergedConfig();
216      this.getDependencyConfigs(sourceObConfig, dependencyConfigs);
217      enableObfuscation = enableObfuscation && !dependencyConfigs.options.disableObfuscation;
218    }
219    const mergedConfigs: MergedConfig = this.getMergedConfigs(selfConfig, dependencyConfigs);
220    this.handleReservedArray(mergedConfigs);
221
222    let needKeepSystemApi =
223      enableObfuscation &&
224      (mergedConfigs.options.enablePropertyObfuscation ||
225        (mergedConfigs.options.enableExportObfuscation && mergedConfigs.options.enableToplevelObfuscation));
226
227    if (needKeepSystemApi && sourceObConfig.obfuscationCacheDir) {
228      const systemApiCachePath: string = path.join(sourceObConfig.obfuscationCacheDir, 'systemApiCache.json');
229      if (isFileExist(systemApiCachePath)) {
230        this.getSystemApiConfigsByCache(mergedConfigs, systemApiCachePath);
231      } else {
232        performancePrinter?.iniPrinter?.startEvent('  Scan system api');
233        this.getSystemApiCache(mergedConfigs, systemApiCachePath);
234        performancePrinter?.iniPrinter?.endEvent('  Scan system api');
235      }
236    }
237
238    if (needConsumerConfigs) {
239      let selfConsumerConfig = new MergedConfig();
240      this.getSelfConsumerConfig(selfConsumerConfig);
241      this.genConsumerConfigFiles(sourceObConfig, selfConsumerConfig, dependencyConfigs);
242    }
243
244    return mergedConfigs;
245  }
246
247  private getSelfConfigs(selfConfigs: MergedConfig): void {
248    if (this.sourceObConfig.selfConfig.ruleOptions.rules) {
249      const configPaths: string[] = this.sourceObConfig.selfConfig.ruleOptions.rules;
250      for (const path of configPaths) {
251        this.getConfigByPath(path, selfConfigs);
252      }
253    }
254  }
255
256  private getConfigByPath(path: string, configs: MergedConfig): void {
257    let fileContent = undefined;
258    try {
259      fileContent = fs.readFileSync(path, 'utf-8');
260    } catch (err) {
261      this.logger.error(`Failed to open ${path}. Error message: ${err}`);
262      throw err;
263    }
264    this.handleConfigContent(fileContent, configs, path);
265  }
266
267  private handleReservedArray(mergedConfigs: MergedConfig): void {
268    if (mergedConfigs.options.enablePropertyObfuscation && mergedConfigs.reservedPropertyNames) {
269      const propertyReservedInfo: ReservedNameInfo = separateUniversalReservedItem(mergedConfigs.reservedPropertyNames);
270      mergedConfigs.universalReservedPropertyNames = propertyReservedInfo.universalReservedArray;
271      mergedConfigs.reservedPropertyNames = propertyReservedInfo.specificReservedArray;
272    }
273
274    if (mergedConfigs.options.enableToplevelObfuscation && mergedConfigs.reservedGlobalNames) {
275      const globalReservedInfo: ReservedNameInfo = separateUniversalReservedItem(mergedConfigs.reservedGlobalNames);
276      mergedConfigs.universalReservedGlobalNames = globalReservedInfo.universalReservedArray;
277      mergedConfigs.reservedGlobalNames = globalReservedInfo.specificReservedArray;
278    }
279  }
280
281  // obfuscation options
282  static readonly KEEP = '-keep';
283  static readonly KEEP_DTS = '-keep-dts';
284  static readonly KEEP_GLOBAL_NAME = '-keep-global-name';
285  static readonly KEEP_PROPERTY_NAME = '-keep-property-name';
286  static readonly KEEP_FILE_NAME = '-keep-file-name';
287  static readonly KEEP_COMMENTS = '-keep-comments';
288  static readonly DISABLE_OBFUSCATION = '-disable-obfuscation';
289  static readonly ENABLE_PROPERTY_OBFUSCATION = '-enable-property-obfuscation';
290  static readonly ENABLE_STRING_PROPERTY_OBFUSCATION = '-enable-string-property-obfuscation';
291  static readonly ENABLE_TOPLEVEL_OBFUSCATION = '-enable-toplevel-obfuscation';
292  static readonly ENABLE_FILENAME_OBFUSCATION = '-enable-filename-obfuscation';
293  static readonly ENABLE_EXPORT_OBFUSCATION = '-enable-export-obfuscation';
294  static readonly REMOVE_COMMENTS = '-remove-comments';
295  static readonly COMPACT = '-compact';
296  static readonly REMOVE_LOG = '-remove-log';
297  static readonly PRINT_NAMECACHE = '-print-namecache';
298  static readonly APPLY_NAMECACHE = '-apply-namecache';
299
300  // renameFileName, printNameCache, applyNameCache, removeComments and keepComments won't be reserved in obfuscation.txt file.
301  static exportedSwitchMap: Map<string, string> = new Map([
302    ['disableObfuscation', ObConfigResolver.KEEP_DTS],
303    ['enablePropertyObfuscation', ObConfigResolver.ENABLE_PROPERTY_OBFUSCATION],
304    ['enableStringPropertyObfuscation', ObConfigResolver.ENABLE_STRING_PROPERTY_OBFUSCATION],
305    ['enableToplevelObfuscation', ObConfigResolver.ENABLE_TOPLEVEL_OBFUSCATION],
306    ['compact', ObConfigResolver.COMPACT],
307    ['removeLog', ObConfigResolver.REMOVE_LOG],
308  ]);
309
310  private getTokenType(token: string): OptionType {
311    switch (token) {
312      case ObConfigResolver.KEEP_DTS:
313        return OptionType.KEEP_DTS;
314      case ObConfigResolver.KEEP_GLOBAL_NAME:
315        return OptionType.KEEP_GLOBAL_NAME;
316      case ObConfigResolver.KEEP_PROPERTY_NAME:
317        return OptionType.KEEP_PROPERTY_NAME;
318      case ObConfigResolver.KEEP_FILE_NAME:
319        return OptionType.KEEP_FILE_NAME;
320      case ObConfigResolver.KEEP_COMMENTS:
321        return OptionType.KEEP_COMMENTS;
322      case ObConfigResolver.DISABLE_OBFUSCATION:
323        return OptionType.DISABLE_OBFUSCATION;
324      case ObConfigResolver.ENABLE_PROPERTY_OBFUSCATION:
325        return OptionType.ENABLE_PROPERTY_OBFUSCATION;
326      case ObConfigResolver.ENABLE_STRING_PROPERTY_OBFUSCATION:
327        return OptionType.ENABLE_STRING_PROPERTY_OBFUSCATION;
328      case ObConfigResolver.ENABLE_TOPLEVEL_OBFUSCATION:
329        return OptionType.ENABLE_TOPLEVEL_OBFUSCATION;
330      case ObConfigResolver.ENABLE_FILENAME_OBFUSCATION:
331        return OptionType.ENABLE_FILENAME_OBFUSCATION;
332      case ObConfigResolver.ENABLE_EXPORT_OBFUSCATION:
333        return OptionType.ENABLE_EXPORT_OBFUSCATION;
334      case ObConfigResolver.REMOVE_COMMENTS:
335        return OptionType.REMOVE_COMMENTS;
336      case ObConfigResolver.COMPACT:
337        return OptionType.COMPACT;
338      case ObConfigResolver.REMOVE_LOG:
339        return OptionType.REMOVE_LOG;
340      case ObConfigResolver.PRINT_NAMECACHE:
341        return OptionType.PRINT_NAMECACHE;
342      case ObConfigResolver.APPLY_NAMECACHE:
343        return OptionType.APPLY_NAMECACHE;
344      case ObConfigResolver.KEEP:
345        return OptionType.KEEP;
346      default:
347        return OptionType.NONE;
348    }
349  }
350
351  private handleConfigContent(data: string, configs: MergedConfig, configPath: string): void {
352    data = this.removeComments(data);
353    const tokens = data.split(/[',', '\t', ' ', '\n', '\r\n']/).filter((item) => item !== '');
354    let type: OptionType = OptionType.NONE;
355    let tokenType: OptionType;
356    let dtsFilePaths: string[] = [];
357    let keepConfigs: string[] = [];
358    for (let i = 0; i < tokens.length; i++) {
359      const token = tokens[i];
360      tokenType = this.getTokenType(token);
361      // handle switches cases
362      switch (tokenType) {
363        case OptionType.DISABLE_OBFUSCATION: {
364          configs.options.disableObfuscation = true;
365          continue;
366        }
367        case OptionType.ENABLE_PROPERTY_OBFUSCATION: {
368          configs.options.enablePropertyObfuscation = true;
369          continue;
370        }
371        case OptionType.ENABLE_STRING_PROPERTY_OBFUSCATION: {
372          configs.options.enableStringPropertyObfuscation = true;
373          continue;
374        }
375        case OptionType.ENABLE_TOPLEVEL_OBFUSCATION: {
376          configs.options.enableToplevelObfuscation = true;
377          continue;
378        }
379        case OptionType.REMOVE_COMMENTS: {
380          configs.options.removeComments = true;
381          continue;
382        }
383        case OptionType.ENABLE_FILENAME_OBFUSCATION: {
384          configs.options.enableFileNameObfuscation = true;
385          continue;
386        }
387        case OptionType.ENABLE_EXPORT_OBFUSCATION: {
388          configs.options.enableExportObfuscation = true;
389          continue;
390        }
391        case OptionType.COMPACT: {
392          configs.options.compact = true;
393          continue;
394        }
395        case OptionType.REMOVE_LOG: {
396          configs.options.removeLog = true;
397          continue;
398        }
399        case OptionType.KEEP:
400        case OptionType.KEEP_DTS:
401        case OptionType.KEEP_GLOBAL_NAME:
402        case OptionType.KEEP_PROPERTY_NAME:
403        case OptionType.KEEP_FILE_NAME:
404        case OptionType.KEEP_COMMENTS:
405        case OptionType.PRINT_NAMECACHE:
406        case OptionType.APPLY_NAMECACHE:
407          type = tokenType;
408          continue;
409        default: {
410          // fall-through
411        }
412      }
413      // handle 'keep' options and 'namecache' options
414      switch (type) {
415        case OptionType.KEEP: {
416          keepConfigs.push(token);
417          continue;
418        }
419        case OptionType.KEEP_DTS: {
420          dtsFilePaths.push(token);
421          continue;
422        }
423        case OptionType.KEEP_GLOBAL_NAME: {
424          configs.reservedGlobalNames.push(token);
425          continue;
426        }
427        case OptionType.KEEP_PROPERTY_NAME: {
428          configs.reservedPropertyNames.push(token);
429          continue;
430        }
431        case OptionType.KEEP_FILE_NAME: {
432          configs.reservedFileNames.push(token);
433          continue;
434        }
435        case OptionType.KEEP_COMMENTS: {
436          configs.keepComments.push(token);
437          continue;
438        }
439        case OptionType.PRINT_NAMECACHE: {
440          configs.options.printNameCache = this.resolvePath(configPath, token);
441          type = OptionType.NONE;
442          continue;
443        }
444        case OptionType.APPLY_NAMECACHE: {
445          const absNameCachePath: string = this.resolvePath(configPath, token);
446          this.determineNameCachePath(absNameCachePath, configPath);
447          configs.options.applyNameCache = absNameCachePath;
448          type = OptionType.NONE;
449          continue;
450        }
451        default:
452          continue;
453      }
454    }
455
456    this.resolveDts(dtsFilePaths, configs);
457    this.resolveKeepConfig(keepConfigs, configs, configPath);
458  }
459
460  // get absolute path
461  private resolvePath(configPath: string, token: string): string {
462    if (path.isAbsolute(token)) {
463      return token;
464    }
465    const configDirectory = path.dirname(configPath);
466    return path.resolve(configDirectory, token);
467  }
468
469  // get names in .d.ts files and add them into reserved list
470  private resolveDts(dtsFilePaths: string[], configs: MergedConfig): void {
471    ApiExtractor.mPropertySet.clear();
472    dtsFilePaths.forEach((token) => {
473      ApiExtractor.traverseApiFiles(token, ApiExtractor.ApiType.KEEP_DTS);
474    });
475    configs.reservedNames = configs.reservedNames.concat([...ApiExtractor.mPropertySet]);
476    configs.reservedPropertyNames = configs.reservedPropertyNames.concat([...ApiExtractor.mPropertySet]);
477    configs.reservedGlobalNames = configs.reservedGlobalNames.concat([...ApiExtractor.mPropertySet]);
478    ApiExtractor.mPropertySet.clear();
479  }
480
481  public resolveKeepConfig(keepConfigs: string[], configs: MergedConfig, configPath: string): void {
482    for (let keepPath of keepConfigs) {
483      let tempAbsPath: string;
484      const isExclude: boolean = keepPath.startsWith('!');
485      // 1: remove '!'
486      tempAbsPath = FileUtils.getAbsPathBaseConfigPath(configPath, isExclude ? keepPath.substring(1) : keepPath);
487
488      // contains '*', '?'
489      if (containWildcards(tempAbsPath)) {
490        const regexPattern = wildcardTransformer(tempAbsPath, true);
491        const regexOperator = new RegExp(`^${regexPattern}$`);
492        if (isExclude) {
493          // start with '!'
494          configs.excludeUniversalPaths.push(regexOperator);
495        } else {
496          configs.keepUniversalPaths.push(regexOperator);
497        }
498        continue;
499      }
500
501      if (isExclude) {
502        // exclude specific path
503        configs.excludePathSet.add(tempAbsPath);
504        continue;
505      }
506
507      if (!fs.existsSync(tempAbsPath)) {
508        this.logger.warn(yellow + 'ArkTS: The path of obfuscation \'-keep\' configuration does not exist: ' + keepPath);
509        continue;
510      }
511      tempAbsPath = fs.realpathSync(tempAbsPath);
512      configs.keepSourceOfPaths.push(FileUtils.toUnixPath(tempAbsPath));
513    }
514  }
515
516  // the content from '#' to '\n' are comments
517  private removeComments(data: string): string {
518    const commentStart = '#';
519    const commentEnd = '\n';
520    let tmpStr = '';
521    let isInComments = false;
522    for (let i = 0; i < data.length; i++) {
523      if (isInComments) {
524        isInComments = data[i] !== commentEnd;
525      } else if (data[i] !== commentStart) {
526        tmpStr += data[i];
527      } else {
528        isInComments = true;
529      }
530    }
531    return tmpStr;
532  }
533
534  /**
535   * systemConfigs includes the API directorys.
536   * component directory and pre_define.js file path needs to be concatenated
537   * @param systemConfigs
538   */
539  private getSystemApiCache(systemConfigs: MergedConfig, systemApiCachePath: string): void {
540    ApiExtractor.mPropertySet.clear();
541    ApiExtractor.mSystemExportSet.clear();
542
543    interface ArkUIWhitelist {
544      ReservedPropertyNames: string[]
545    }
546    let arkUIWhitelist: ArkUIWhitelist = { ReservedPropertyNames: [] };
547    const sdkApis: string[] = sortAndDeduplicateStringArr(this.sourceObConfig.sdkApis);
548    for (let apiPath of sdkApis) {
549      this.getSdkApiCache(apiPath);
550      const UIPath: string = path.join(apiPath, '../build-tools/ets-loader/lib/pre_define.js');
551      if (fs.existsSync(UIPath)) {
552        this.getUIApiCache(UIPath);
553      }
554      const arkUIWhitelistPath: string = path.join(apiPath, '../build-tools/ets-loader/obfuscateWhiteList.json5');
555      if (fs.existsSync(arkUIWhitelistPath)) {
556        arkUIWhitelist = JSON5.parse(fs.readFileSync(arkUIWhitelistPath, 'utf-8'));
557      }
558    }
559    let systemApiContent: SystemApiContent = {};
560
561    if (systemConfigs.options.enablePropertyObfuscation) {
562      const savedNameAndPropertyList: string[] = sortAndDeduplicateStringArr([...ApiExtractor.mPropertySet]);
563      systemApiContent.ReservedNames = savedNameAndPropertyList;
564      systemApiContent.ReservedPropertyNames = [...savedNameAndPropertyList, ...arkUIWhitelist.ReservedPropertyNames];
565      systemConfigs.reservedPropertyNames.push(...savedNameAndPropertyList, ...arkUIWhitelist.ReservedPropertyNames);
566      systemConfigs.reservedNames.push(...savedNameAndPropertyList);
567    }
568    if (systemConfigs.options.enableToplevelObfuscation && systemConfigs.options.enableExportObfuscation) {
569      const savedExportNames: string[] = sortAndDeduplicateStringArr([...ApiExtractor.mSystemExportSet]);
570      systemApiContent.ReservedGlobalNames = savedExportNames;
571      systemConfigs.reservedGlobalNames.push(...savedExportNames);
572    }
573
574    if (!fs.existsSync(path.dirname(systemApiCachePath))) {
575      fs.mkdirSync(path.dirname(systemApiCachePath), { recursive: true });
576    }
577    fs.writeFileSync(systemApiCachePath, JSON.stringify(systemApiContent, null, 2));
578    ApiExtractor.mPropertySet.clear();
579    ApiExtractor.mSystemExportSet.clear();
580  }
581
582  private getSdkApiCache(sdkApiPath: string): void {
583    ApiExtractor.traverseApiFiles(sdkApiPath, ApiExtractor.ApiType.API);
584    const componentPath: string = path.join(sdkApiPath, '../component');
585    if (fs.existsSync(componentPath)) {
586      ApiExtractor.traverseApiFiles(componentPath, ApiExtractor.ApiType.COMPONENT);
587    }
588  }
589
590  private getUIApiCache(uiApiPath: string): void {
591    ApiExtractor.extractStringsFromFile(uiApiPath);
592  }
593
594  private getDependencyConfigs(sourceObConfig: any, dependencyConfigs: MergedConfig): void {
595    for (const lib of sourceObConfig.dependencies.libraries || []) {
596      if (lib.consumerRules && lib.consumerRules.length > 0) {
597        for (const path of lib.consumerRules) {
598          const thisLibConfigs = new MergedConfig();
599          this.getConfigByPath(path, dependencyConfigs);
600          dependencyConfigs.merge(thisLibConfigs);
601        }
602      }
603    }
604
605    if (
606      sourceObConfig.dependencies &&
607      sourceObConfig.dependencies.hars &&
608      sourceObConfig.dependencies.hars.length > 0
609    ) {
610      for (const path of sourceObConfig.dependencies.hars) {
611        const thisHarConfigs = new MergedConfig();
612        this.getConfigByPath(path, dependencyConfigs);
613        dependencyConfigs.merge(thisHarConfigs);
614      }
615    }
616  }
617
618  private getSystemApiConfigsByCache(systemConfigs: MergedConfig, systemApiCachePath: string): void {
619    let systemApiContent: {
620      ReservedPropertyNames?: string[];
621      ReservedNames?: string[];
622      ReservedGlobalNames?: string[];
623    } = JSON.parse(fs.readFileSync(systemApiCachePath, 'utf-8'));
624    if (systemApiContent.ReservedPropertyNames) {
625      systemConfigs.reservedPropertyNames = ListUtil.uniqueMergeList(systemConfigs.reservedPropertyNames, systemApiContent.ReservedPropertyNames);
626    }
627    if (systemApiContent.ReservedNames) {
628      systemConfigs.reservedNames = ListUtil.uniqueMergeList(systemConfigs.reservedNames, systemApiContent.ReservedNames);
629    }
630    if (systemApiContent.ReservedGlobalNames) {
631      systemConfigs.reservedGlobalNames = ListUtil.uniqueMergeList(systemConfigs.reservedGlobalNames, systemApiContent.ReservedGlobalNames);
632    }
633  }
634
635  private getSelfConsumerConfig(selfConsumerConfig: MergedConfig): void {
636    for (const path of this.sourceObConfig.selfConfig.consumerRules) {
637      this.getConfigByPath(path, selfConsumerConfig);
638    }
639  }
640
641  private getMergedConfigs(selfConfigs: MergedConfig, dependencyConfigs: MergedConfig): MergedConfig {
642    if (dependencyConfigs) {
643      selfConfigs.merge(dependencyConfigs);
644    }
645    selfConfigs.sortAndDeduplicate();
646    return selfConfigs;
647  }
648
649  private genConsumerConfigFiles(
650    sourceObConfig: any,
651    selfConsumerConfig: MergedConfig,
652    dependencyConfigs: MergedConfig,
653  ): void {
654    selfConsumerConfig.merge(dependencyConfigs);
655    selfConsumerConfig.sortAndDeduplicate();
656    this.writeConsumerConfigFile(selfConsumerConfig, sourceObConfig.exportRulePath);
657  }
658
659  public writeConsumerConfigFile(selfConsumerConfig: MergedConfig, outpath: string): void {
660    const configContent: string = selfConsumerConfig.serializeMergedConfig();
661    fs.writeFileSync(outpath, configContent);
662  }
663
664  private determineNameCachePath(nameCachePath: string, configPath: string): void {
665    if (!fs.existsSync(nameCachePath)) {
666      throw new Error(`The applied namecache file '${nameCachePath}' configured by '${configPath}' does not exist.`);
667    }
668  }
669}
670
671/**
672 * Collect reserved file name configured in oh-package.json5 and module.json5.
673 * @param ohPackagePath The 'main' and 'types' fileds in oh-package.json5 need to be reserved.
674 * @param projectConfig Several paths or file contents in projectconfig need to be reserved.
675 *   1: module.json's 'srcEntry' field
676 *   2: projectPath: /library/src/main/ets
677 *   3: cachePath: /library/build/default/cache/default/default@HarCompileArkTs/esmodules/release
678 *      target reserved path: /library/build/default/cache/default/default@HarCompileArkTs/esmodules/release/src/main/ets
679 *   4: aceModuleBuild/etsFortgz directory: /library/build/default/intermediates/loader_out/etsFortgz
680 *      If compile the hsp module, the declaration file will be written to the 'aceModuleBuild/etsFortgz' directory.
681 * @param modulePathMap packageName of local har package should be reserved as it is a fixed part of ohmUrl.
682 *   example: modulePathMap: { packageName: path }
683 * @returns reservedFileNames
684 */
685export function collectResevedFileNameInIDEConfig(
686  ohPackagePath: string,
687  projectConfig: any,
688  modulePathMap: Object | undefined,
689  entryArray: Array<string> | undefined,
690): string[] {
691  const reservedFileNames: string[] = [];
692  const moduleJsonPath: string = projectConfig.aceModuleJsonPath;
693  const projectPath: string = projectConfig.projectPath;
694  const cachePath: string = projectConfig.cachePath;
695
696  if (entryArray) {
697    entryArray.forEach((element) => {
698      FileUtils.collectPathReservedString(element, reservedFileNames);
699    });
700  }
701
702  if (modulePathMap) {
703    const modulePaths = Object.values(modulePathMap);
704    const moduleNames = Object.keys(modulePathMap);
705    modulePaths.forEach((val) => {
706      FileUtils.collectPathReservedString(val, reservedFileNames);
707    });
708    moduleNames.forEach((val) => {
709      FileUtils.collectPathReservedString(val, reservedFileNames);
710    });
711  }
712  if (fs.existsSync(ohPackagePath)) {
713    const ohPackageContent = JSON5.parse(fs.readFileSync(ohPackagePath, 'utf-8'));
714    ohPackageContent.main && FileUtils.collectPathReservedString(ohPackageContent.main, reservedFileNames);
715    ohPackageContent.types && FileUtils.collectPathReservedString(ohPackageContent.types, reservedFileNames);
716  }
717
718  if (fs.existsSync(moduleJsonPath)) {
719    const moduleJsonContent = JSON5.parse(fs.readFileSync(moduleJsonPath, 'utf-8'));
720    moduleJsonContent.module?.srcEntry &&
721      FileUtils.collectPathReservedString(moduleJsonContent.module?.srcEntry, reservedFileNames);
722  }
723
724  if (projectConfig.compileShared || projectConfig.byteCodeHar) {
725    FileUtils.collectPathReservedString(projectConfig.aceModuleBuild, reservedFileNames);
726    reservedFileNames.push('etsFortgz');
727  }
728
729  FileUtils.collectPathReservedString(projectPath, reservedFileNames);
730  FileUtils.collectPathReservedString(cachePath, reservedFileNames);
731  return reservedFileNames;
732}
733
734export function readNameCache(nameCachePath: string, logger: any): void {
735  try {
736    const fileContent = fs.readFileSync(nameCachePath, 'utf-8');
737    const nameCache: {
738      compileSdkVersion?: string;
739      [key: string]: Object;
740      PropertyCache?: Object;
741      FileNameCache?: Object;
742    } = JSON.parse(fileContent);
743    if (nameCache.PropertyCache) {
744      PropCollections.historyMangledTable = getMapFromJson(nameCache.PropertyCache);
745    }
746    if (nameCache.FileNameCache) {
747      renameFileNameModule.historyFileNameMangledTable = getMapFromJson(nameCache.FileNameCache);
748    }
749
750    const { compileSdkVersion, PropertyCache, FileNameCache, ...rest } = nameCache;
751    Object.keys(rest).forEach((key) => {
752      nameCacheMap.set(key, rest[key]);
753    });
754  } catch (err) {
755    logger.error(`Failed to open ${nameCachePath}. Error message: ${err}`);
756  }
757}
758
759/**
760 * collect the reserved or excluded paths containing wildcards
761 */
762export function handleUniversalPathInObf(mergedObConfig: MergedConfig, allSourceFilePaths: Set<string>): void {
763  if (
764    !mergedObConfig ||
765    (mergedObConfig.keepUniversalPaths.length === 0 && mergedObConfig.excludeUniversalPaths.length === 0)
766  ) {
767    return;
768  }
769  for (const realFilePath of allSourceFilePaths) {
770    let isReserved = false;
771    for (const universalPath of mergedObConfig.keepUniversalPaths) {
772      if (universalPath.test(realFilePath)) {
773        isReserved = true;
774        break;
775      }
776    }
777    for (const excludePath of mergedObConfig.excludeUniversalPaths) {
778      if (excludePath.test(realFilePath)) {
779        isReserved = false;
780        mergedObConfig.excludePathSet.add(realFilePath);
781        break;
782      }
783    }
784    if (isReserved) {
785      mergedObConfig.keepSourceOfPaths.push(realFilePath);
786    }
787  }
788}
789
790export function getArkguardNameCache(
791  enablePropertyObfuscation: boolean,
792  enableFileNameObfuscation: boolean,
793  sdkVersion: string,
794  entryPackageInfo: string,
795): string {
796  let writeContent: string = '';
797  let nameCacheCollection: {
798    compileSdkVersion?: string;
799    PropertyCache?: Object;
800    FileNameCache?: Object;
801    entryPackageInfo?: string;
802  } = Object.fromEntries(nameCacheMap.entries());
803  nameCacheCollection.compileSdkVersion = sdkVersion;
804  nameCacheCollection.entryPackageInfo = entryPackageInfo;
805
806  if (enablePropertyObfuscation) {
807    const mergedPropertyNameCache: Map<string, string> = new Map();
808    fillNameCache(PropCollections.historyMangledTable, mergedPropertyNameCache);
809    fillNameCache(PropCollections.globalMangledTable, mergedPropertyNameCache);
810    nameCacheCollection.PropertyCache = Object.fromEntries(mergedPropertyNameCache);
811  }
812
813  if (enableFileNameObfuscation) {
814    const mergedFileNameCache: Map<string, string> = new Map();
815    fillNameCache(renameFileNameModule.historyFileNameMangledTable, mergedFileNameCache);
816    fillNameCache(renameFileNameModule.globalFileNameMangledTable, mergedFileNameCache);
817    nameCacheCollection.FileNameCache = Object.fromEntries(mergedFileNameCache);
818  }
819
820  writeContent += JSON.stringify(nameCacheCollection, null, 2);
821  return writeContent;
822}
823
824function fillNameCache(table: Map<string, string>, nameCache: Map<string, string>): void {
825  if (table) {
826    for (const [key, value] of table.entries()) {
827      nameCache.set(key, value);
828    }
829  }
830  return;
831}
832
833export function writeObfuscationNameCache(
834  projectConfig: any,
835  entryPackageInfo: string,
836  obfuscationCacheDir?: string,
837  printNameCache?: string,
838): void {
839  if (!projectConfig.arkObfuscator) {
840    return;
841  }
842  let options = projectConfig.obfuscationMergedObConfig.options;
843  let writeContent = getArkguardNameCache(
844    options.enablePropertyObfuscation,
845    options.enableFileNameObfuscation,
846    projectConfig.etsLoaderVersion,
847    entryPackageInfo,
848  );
849  if (obfuscationCacheDir && obfuscationCacheDir.length > 0) {
850    const defaultNameCachePath: string = path.join(obfuscationCacheDir, 'nameCache.json');
851    if (!fs.existsSync(path.dirname(defaultNameCachePath))) {
852      fs.mkdirSync(path.dirname(defaultNameCachePath), { recursive: true });
853    }
854    fs.writeFileSync(defaultNameCachePath, writeContent);
855  }
856  if (printNameCache && printNameCache.length > 0) {
857    fs.writeFileSync(printNameCache, writeContent);
858  }
859}
860
861export function generateConsumerObConfigFile(obfuscationOptions: any, logger: any): void {
862  const projectConfig = { obfuscationOptions, compileHar: true };
863  const obConfig: ObConfigResolver = new ObConfigResolver(projectConfig, logger);
864  obConfig.resolveObfuscationConfigs();
865}
866
867export function mangleFilePath(originalPath: string): string {
868  const mangledFilePath = renameFileNameModule.getMangleCompletePath(originalPath);
869  return mangledFilePath;
870}
871
872export function enableObfuscatedFilePathConfig(isPackageModules: boolean, projectConfig: any): boolean {
873  const isDebugMode = isDebug(projectConfig);
874  const hasObfuscationConfig = projectConfig.obfuscationMergedObConfig;
875  if (isDebugMode || !hasObfuscationConfig) {
876    return false;
877  }
878  const disableObfuscation = hasObfuscationConfig.options.disableObfuscation;
879  const enableFileNameObfuscation = hasObfuscationConfig.options.enableFileNameObfuscation;
880  if (disableObfuscation || !enableFileNameObfuscation) {
881    return false;
882  }
883  return true;
884}
885
886export function handleObfuscatedFilePath(filePath: string, isPackageModules: boolean, projectConfig: Object): string {
887  if (!enableObfuscatedFilePathConfig(isPackageModules, projectConfig)) {
888    return filePath;
889  }
890  // Do not obfuscate the file path in dir oh_modules.
891  if (!isPackageModules) {
892    return mangleFilePath(filePath);
893  }
894  // When open the config 'enableFileNameObfuscation', keeping all paths in unix format.
895  return FileUtils.toUnixPath(filePath);
896}
897
898export function enableObfuscateFileName(isPackageModules: boolean, projectConfig: Object): boolean {
899  if (!enableObfuscatedFilePathConfig(isPackageModules, projectConfig)) {
900    return false;
901  }
902
903  // Do not obfuscate the file path in dir oh_modules.
904  if (!isPackageModules) {
905    return true;
906  }
907  // When open the config 'enableFileNameObfuscation', keeping all paths in unix format.
908  return false;
909}
910
911/**
912 * Get the relative path relative to the project based on the file's associated project
913 */
914export function getRelativeSourcePath(
915  filePath: string,
916  projectRootPath: string,
917  belongProjectPath: string | undefined,
918): string {
919  filePath = FileUtils.toUnixPath(filePath);
920
921  if (projectRootPath) {
922    projectRootPath = FileUtils.toUnixPath(projectRootPath);
923  }
924
925  if (belongProjectPath) {
926    belongProjectPath = FileUtils.toUnixPath(belongProjectPath);
927  }
928
929  let relativeFilePath: string = filePath;
930  if (projectRootPath && filePath.startsWith(projectRootPath)) {
931    relativeFilePath = filePath.replace(projectRootPath + '/', '');
932  } else if (belongProjectPath) {
933    relativeFilePath = filePath.replace(belongProjectPath + '/', '');
934  }
935
936  return relativeFilePath;
937}
938