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