1/*
2 * Copyright (c) 2023 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 UIAbility from '@ohos.app.ability.UIAbility';
17import picker from '@ohos.file.picker';
18import Want from '@ohos.app.ability.Want';
19import AbilityConstant from '@ohos.app.ability.AbilityConstant';
20import window from '@ohos.window';
21import { BusinessError } from '@ohos.base';
22import ability from '@ohos.ability.ability';
23import dlpPermission from '@ohos.dlpPermission';
24import fs from '@ohos.file.fs';
25import fileUri from '@ohos.file.fileuri';
26import abilityManager from '@ohos.app.ability.abilityManager';
27import { getFileUriByPath, getFileFd, getAppId, isValidPath, defaultDlpFile } from '../common/utils';
28import Constants from '../common/constant';
29import GlobalContext from '../common/GlobalContext';
30import zlib from '@ohos.zlib';
31import { GetAlertMessage } from '../common/GetAlertMessage';
32import { HiLog } from '../common/HiLog';
33import dialogRequest from '@ohos.app.ability.dialogRequest';
34
35const TAG = 'SaveAs';
36
37class ChangeOption {
38  public offset: number = 0
39  public length: number = 0
40}
41
42const SUFFIX_INDEX = 2;
43const HEAD_LENGTH_IN_BYTE = 20;
44const HEAD_LENGTH_IN_U32 = 5;
45const TXT_OFFSET = 3;
46const SIZE_OFFSET = 4;
47const ARGS_ZERO = 0;
48const ARGS_ONE = 1;
49const ARGS_TWO = 2;
50const FILENAME_MAX_LEN = 255;
51const FOPEN_EXCEPTION_IS_DIR = 13900019;
52const SRC_URI_OFFSET = 4;
53const ACTION: Record<string, string> = {
54  'SELECT_ACTION': 'ohos.want.action.OPEN_FILE',
55  'SELECT_ACTION_MODAL': 'ohos.want.action.OPEN_FILE_SERVICE',
56  'SAVE_ACTION': 'ohos.want.action.CREATE_FILE',
57  'SAVE_ACTION_MODAL': 'ohos.want.action.CREATE_FILE_SERVICE',
58};
59
60const errCode: Record<string, number> = {
61  'INVALID_ARGS': 13900020,
62  'RESULT_ERROR': 13900042,
63  'NAME_TOO_LONG': 13900030,
64};
65
66const ERRCODE_MAP = new Map([
67  [errCode.INVALID_ARGS, 'Invalid argument'],
68  [errCode.RESULT_ERROR, 'Unknown error'],
69  [errCode.NAME_TOO_LONG, 'File name too long'],
70]);
71export default class SaveAsAbility extends UIAbility {
72  private result: ability.AbilityResult = {
73    resultCode: -1,
74    want: {
75      bundleName: '',
76      abilityName: '',
77      parameters: {
78        pick_path_return: [],
79        pick_fd_return: 0
80      }
81    }
82  };
83  private dlpFile: dlpPermission.DLPFile = defaultDlpFile;
84  private sandboxBundleName: string = '';
85  private resultUri: string = '';
86  private tokenId: number = -1;
87  private requestCode: number = -1;
88  private fileName: string = '';
89  private suffix: string = '';
90  private authPerm: dlpPermission.DLPFileAccess = dlpPermission.DLPFileAccess.READ_ONLY;
91  private isOK: boolean = true; // use with startAlertAbility
92  private zipFlag: boolean = false;
93  private random: string = String(Math.random()).substring(Constants.RAND_START, Constants.RAND_END);
94  private filePath: string = '';
95  private dirPath: string = '';
96  private encFile: string = '';
97  private zipPath: string = '';
98  private ff: fs.File | undefined = undefined;
99  private zf2: fs.File | undefined = undefined;
100  private zipName: string = '';
101  private file: fs.File | undefined = undefined;
102  private dstFdPicker: number = -1;
103  private newDlpFile: dlpPermission.DLPFile = defaultDlpFile;
104  private srcFdPicker: number = -1;
105  private linkFileName: string = '';
106
107  async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): Promise<void> {
108    HiLog.info(TAG, `onCreate`);
109    await this.prepareDlpFile(want);
110    try {
111      await abilityManager.notifySaveAsResult(this.result, this.requestCode);
112    } catch (err) {
113      HiLog.error(TAG, `notifySaveAsResult failed: ${JSON.stringify(err)}`);
114    }
115    if (this.isOK) {
116      this.context.terminateSelf();
117    }
118  }
119
120  onDestroy(): void {
121    HiLog.info(TAG, `onDestroy`);
122  }
123
124  async onWindowStageCreate(windowStage: window.WindowStage): Promise<void> {
125    // Main window is created, set main page for this ability
126    HiLog.info(TAG, `onWindowStageCreate`);
127  }
128
129  onWindowStageDestroy(): void {
130    // Main window is destroyed, release UI related resources
131    HiLog.info(TAG, `onWindowStageDestroy`);
132  }
133
134  onForeground(): void {
135    // Ability has brought to foreground
136    HiLog.info(TAG, `onForeground`);
137  }
138
139  onBackground(): void {
140    // Ability has back to background
141    HiLog.info(TAG, `onBackground`);
142  }
143
144  async parseParams(want: Want): Promise<boolean | void> {
145    if (want.parameters === undefined) {
146      HiLog.error(TAG, `invalid want`);
147      return false;
148    }
149
150    this.requestCode = want.parameters?.['requestCode'] as number;
151    if (this.requestCode === undefined) {
152      HiLog.error(TAG, `invalid requestCode`);
153      return false;
154    }
155
156    this.tokenId = want.parameters?.['ohos.aafwk.param.callerToken'] as number;
157    if (this.tokenId === undefined) {
158      HiLog.error(TAG, `invalid tokenId`);
159      return false;
160    }
161    try {
162      await this.checkParseParams(want);
163      return true;
164    } catch {
165      return false;
166    }
167  }
168
169  checkParseParams(want: Want): Promise<void> {
170    return new Promise(async (resolve, reject) => {
171      this.authPerm = (GlobalContext.load('token2File') as
172      Map<number, (number | string | dlpPermission.DLPFile | dlpPermission.DLPFileAccess)[]>)
173        .get(this.tokenId)?.[3] as dlpPermission.DLPFileAccess;
174      let dlpFileInfo = (GlobalContext.load('token2File') as
175      Map<number, (number | string | dlpPermission.DLPFile | dlpPermission.DLPFileAccess)[]>)
176        .get(this.tokenId)?.[0] as dlpPermission.DLPFile;
177      let contactAccount = dlpFileInfo.dlpProperty.contactAccount as string;
178      if (this.authPerm != dlpPermission.DLPFileAccess.CONTENT_EDIT &&
179        this.authPerm != dlpPermission.DLPFileAccess.FULL_CONTROL) {
180        HiLog.error(TAG, `invalid authPerm: ${this.authPerm}`);
181        this.isOK = false;
182        AppStorage.setOrCreate('contactAccount', contactAccount);
183        let accountFlag = await GetAlertMessage.checkAccountInfo(contactAccount);
184        if (!accountFlag) {
185          await GetAlertMessage.startAlertAbility(this.context, {
186            code: Constants.ERR_JS_APP_INSIDE_ERROR } as BusinessError);
187          reject();
188        }
189        await GetAlertMessage.startAlertAbility(this.context,
190          { code: Constants.ERR_JS_DLP_FILE_READ_ONLY } as BusinessError);
191        reject();
192      }
193      if (!(GlobalContext.load('token2File') as Map<number, Object[]>).has(this.tokenId)) {
194        HiLog.error(TAG, `invalid token2File`);
195        reject();
196      }
197      let newFileName = want.parameters?.['key_pick_file_name'] as [];
198      if (newFileName === undefined) {
199        HiLog.error(TAG, `invalid newFileName`);
200        reject();
201      }
202      this.fileName = newFileName.join('');
203      let splitNames = this.fileName.split('.');
204      HiLog.info(TAG, `splitNames: ${splitNames}`);
205      if (splitNames.length <= SUFFIX_INDEX) {
206        HiLog.error(TAG, `get suffix failed`);
207        reject();
208      }
209      this.suffix = splitNames[splitNames.length - SUFFIX_INDEX];
210      HiLog.info(TAG, `suffix is: ${this.suffix}`);
211      resolve();
212    })
213  }
214
215  deleteFile(file:string) {
216    try {
217      let res = fs.accessSync(file);
218      if (res) {
219        fs.unlinkSync(file);
220      }
221    } catch (err) {
222      HiLog.error(TAG, `deleteFile: ${JSON.stringify(err)}`);
223    }
224  }
225
226  rmDir(file:string) {
227    try {
228      let res = fs.accessSync(file);
229      if (res) {
230        fs.rmdirSync(file);
231      }
232    } catch (err) {
233      HiLog.error(TAG, `rmdirSync: ${JSON.stringify(err)}`);
234    }
235  }
236
237  async copyDlpHead(srcFd: number, dstFd: number) {
238    return new Promise<boolean>(async (resolve, reject) => {
239      let appDir = this.context.filesDir + '/';
240      this.filePath = appDir + 'saveAs' + this.random;
241      this.dirPath = appDir + 'saveAsUnzip' + this.random;
242      this.encFile = this.dirPath + '/encrypted_data';
243      this.zipPath = appDir + 'saveAsZip' + this.random;
244      this.zipName = this.zipPath + '.zip';
245      try {
246        let success = await this.copyDlpHeadData(srcFd, dstFd);
247        resolve(success);
248      } catch (err) {
249        HiLog.error(TAG, `copyDlpHead: ${JSON.stringify(err)}, message: ${JSON.stringify(err.message)}`);
250        try {
251          await fs.close(this.ff);
252          await fs.close(this.zf2);
253        } catch (error) {
254          HiLog.error(TAG, `close: ${JSON.stringify(error)}`);
255        }
256        resolve(false);
257      }
258      if (this.zipFlag) {
259        this.deleteFile(this.dirPath + '/encrypted_data');
260        this.deleteFile(this.dirPath + '/dlp_general_info');
261        this.deleteFile(this.dirPath + '/dlp_cert');
262        this.rmDir(this.dirPath);
263        this.deleteFile(this.filePath);
264        this.deleteFile(this.zipName);
265      }
266    })
267  }
268
269  async copyDlpHeadData(srcFd: number, dstFd: number) {
270    return new Promise<boolean>(async (resolve, reject) => {
271      let z = new ArrayBuffer(HEAD_LENGTH_IN_BYTE);
272      let option: ChangeOption = {
273        offset: 0,
274        length: HEAD_LENGTH_IN_BYTE
275      };
276      let num = fs.readSync(srcFd, z, option);
277      let buf = new Uint32Array(z, 0, HEAD_LENGTH_IN_U32);
278      let magic = buf[0];
279
280      if (magic !== Constants.DLP_ZIP_MAGIC) {
281        let txtOffset = buf[TXT_OFFSET];
282        let head = new ArrayBuffer(txtOffset);
283        option = {
284          offset: 0,
285          length: txtOffset
286        };
287        num = fs.readSync(srcFd, head, option);
288        let buf2 = new Uint32Array(head, 0, HEAD_LENGTH_IN_U32);
289        buf2[SIZE_OFFSET] = 0;
290        num = fs.writeSync(dstFd, head, option);
291        resolve(true);
292        return;
293      }
294
295      try {
296        this.zipFlag = true;
297        this.ff = await fs.open(this.filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
298        await fs.copyFile(srcFd, this.ff.fd);
299        await fs.mkdir(this.dirPath);
300        let options: zlib.Options = {
301          level: zlib.CompressLevel.COMPRESS_LEVEL_NO_COMPRESSION,
302          memLevel:zlib.MemLevel.MEM_LEVEL_DEFAULT,
303          strategy:zlib.CompressStrategy.COMPRESS_STRATEGY_DEFAULT_STRATEGY,
304        };
305        await zlib.decompressFile(this.filePath, this.dirPath, options);
306        await fs.truncate(this.encFile);
307        await zlib.compressFile(this.dirPath, this.zipPath, options);
308        this.zf2 = await fs.open(this.zipName, fs.OpenMode.READ_WRITE);
309        await fs.copyFile(this.zf2.fd, dstFd);
310        resolve(true);
311      } catch (err) {
312        HiLog.error(TAG, `open failed: ${JSON.stringify(err)}`);
313        resolve(false);
314      } finally {
315        if (this.ff || this.zf2) {
316          await fs.close(this.ff);
317          await fs.close(this.zf2);
318        }
319      }
320    })
321  }
322
323  isValidDirUri(uri: string | undefined) {
324    if (uri == undefined) {
325      return true;
326    }
327    let uriObj = new fileUri.FileUri(uri);
328    let dirUri = uriObj.getFullDirectoryUri(); // get directory path of a uri
329    let file: fs.File | undefined = undefined;
330    try {
331      file = fs.openSync(dirUri, fs.OpenMode.READ_ONLY);
332      let stat = fs.statSync(file.fd);
333      let isDir = stat.isDirectory();
334      HiLog.info(TAG, `test dir ${isDir ? 'is' : 'not'} a directory`);
335      return isDir;
336    } catch (e) {
337      let error = e as BusinessError;
338      if (error.code === FOPEN_EXCEPTION_IS_DIR) {
339        HiLog.info(TAG, `test dir is a directory`);
340        return true;
341      }
342      return false;
343    } finally {
344      if (file != undefined) {
345        fs.closeSync(file.fd);
346      }
347    }
348  }
349  
350  getSourceFileName() {
351    try {
352      let token2FileValue: (number | string | dlpPermission.DLPFile | dlpPermission.DLPFileAccess)[] =
353        (GlobalContext
354          .load('token2File') as Map<number, (number | string | dlpPermission.DLPFile | dlpPermission.DLPFileAccess)[]>)
355          .get(this.tokenId) as (number | string | dlpPermission.DLPFile | dlpPermission.DLPFileAccess)[];
356      let srcUri: string = token2FileValue[SRC_URI_OFFSET] as string;
357      let uriObj = new fileUri.FileUri(srcUri);
358      let dir = uriObj.getFullDirectoryUri(); // get directory path
359      let name = srcUri.replace(dir, '').replace('/', ''); // strip directory path and '/'
360      HiLog.info(TAG, 'getSourceFileName: ' + name);
361      return name;
362    } catch (e) {
363      let error = e as BusinessError;
364      HiLog.error(TAG, 'getSourceFileName error:' + error.message);
365      return '';
366    }
367  }
368
369  parseDocumentPickerSaveOption(args: picker.DocumentSaveOptions[], action: string) {
370    let config: Record<string, string | Record<string, Object>> = {
371      'action': action,
372      'parameters': {
373        'startMode': 'save',
374      } as Record<string, Object>
375    };
376
377    if (args.length > ARGS_ZERO && typeof args[ARGS_ZERO] === 'object') {
378      let option: picker.DocumentSaveOptions = args[ARGS_ZERO];
379      if ((option.newFileNames !== undefined) && option.newFileNames.length > 0) {
380        if (option.newFileNames[0] == undefined) { // if option name not provided, default to empty string
381          option.newFileNames[0] = '';
382        }
383        if (option.newFileNames[0].length > FILENAME_MAX_LEN) {
384          throw new Error('file name exceeds max length');
385        }
386        let srcName = this.getSourceFileName();
387        if (option.newFileNames[0].length > 0 && srcName != option.newFileNames[0]) {
388          HiLog.error(TAG, `src file name is not same with newFileName provided`);
389          throw new Error('picker filename error');
390        }
391        config.parameters['key_pick_file_name'] = option.newFileNames;
392        config.parameters['saveFile'] = option.newFileNames[0];
393      }
394
395      let isDir = this.isValidDirUri(option.defaultFilePathUri); // undefined returns true
396      if (isDir) {
397        config.parameters['key_pick_dir_path'] = option.defaultFilePathUri;
398      } else {
399        throw new Error(`defaultFilePathUri is not a valid directory uri`);
400      }
401      if ((option.fileSuffixChoices !== undefined) && option.fileSuffixChoices.length > 0) {
402        config.parameters['key_file_suffix_choices'] = option.fileSuffixChoices;
403      }
404    }
405
406    HiLog.info(TAG, `[picker] Save config: ${JSON.stringify(config)}`);
407    return config;
408  }
409
410  getDocumentPickerSaveResult(args: dialogRequest.RequestResult | ability.AbilityResult) {
411    let saveResult: Record<string, BusinessError | string[]> = {
412      'error': {} as BusinessError,
413      'data': []
414    };
415
416    if (((args as dialogRequest.RequestResult).result !== undefined &&
417        (args as dialogRequest.RequestResult).result === 0) ||
418        ((args as ability.AbilityResult).resultCode !== undefined &&
419        (args as ability.AbilityResult).resultCode === 0)) {
420      if (args.want && args.want.parameters) {
421        if (args.want.parameters.pick_path_return) {
422          saveResult.data = args.want.parameters.pick_path_return as string[];
423        } else {
424          saveResult.data = args.want.parameters['ability.params.stream'] as string[];
425        }
426      }
427    } else if (((args as dialogRequest.RequestResult).result !== undefined &&
428        (args as dialogRequest.RequestResult).result === 0) ||
429        ((args as ability.AbilityResult).resultCode !== undefined &&
430        (args as ability.AbilityResult).resultCode === -1)) {
431      saveResult.data = [];
432    } else {
433      saveResult.error = this.getErr(errCode.RESULT_ERROR) as BusinessError;
434    }
435
436    HiLog.info(TAG, `[picker] Save saveResult: ${JSON.stringify(saveResult)}`);
437    return saveResult;
438  }
439
440  getErr(errCode: number) {
441    return {code: errCode, message: ERRCODE_MAP.get(errCode)} as BusinessError;
442  }
443
444  async documentPickerSave(...args: Object[]): Promise<BusinessError | string[] | undefined> {
445    let config: Record<string, string | Record<string, Object>>;
446    let result: dialogRequest.RequestResult | ability.AbilityResult;
447    try {
448      config = this.parseDocumentPickerSaveOption(args, ACTION.SAVE_ACTION);
449      result = await this.context.startAbilityForResult(config, {windowMode: 0});
450    } catch (error) {
451      HiLog.info(TAG, `startAbilityForResult${JSON.stringify(error)}`);
452      return undefined;
453    }
454    HiLog.info(TAG, `[picker] Save result: ${JSON.stringify(result)}`);
455    try {
456      const saveResult: Record<string, BusinessError | string[]> = this.getDocumentPickerSaveResult(result);
457      if (args.length === ARGS_TWO && typeof args[ARGS_ONE] === 'function') {
458        return (args[ARGS_ONE] as Function)(saveResult.error, saveResult.data);
459      } else if (args.length === ARGS_ONE && typeof args[ARGS_ZERO] === 'function') {
460        return (args[ARGS_ZERO] as Function)(saveResult.error, saveResult.data);
461      }
462      return new Promise<BusinessError | string[]>((resolve, reject) => {
463        if (saveResult.data !== undefined) {
464          resolve(saveResult.data);
465        } else {
466          reject(saveResult.error);
467        }
468      })
469    } catch (resultError) {
470      HiLog.info(TAG, `[picker] Result error: ${resultError}`);
471    }
472    return undefined;
473  }
474
475  async prepareDlpFile(want: Want): Promise<void> {
476    HiLog.info(TAG, `getFile start prepareDlpFile`);
477    let uri = '';
478    let displayName = '';
479    let ret = await this.parseParams(want);
480    if (!ret) {
481      HiLog.info(TAG, `parseParams failed`);
482      return;
483    }
484    let documentSaveOptions = new picker.DocumentSaveOptions();
485    displayName = this.fileName;
486    documentSaveOptions.newFileNames = [displayName];
487    documentSaveOptions.fileSuffixChoices = [`.${this.suffix}.dlp`];
488    try {
489      let saveRes: BusinessError | string[] | undefined = await this.documentPickerSave(documentSaveOptions);
490      if (saveRes == undefined || (saveRes instanceof Array && saveRes.length == 0)) {
491        HiLog.error(TAG, `fail to get uri`);
492        return;
493      }
494      uri = saveRes[0]
495      if (!isValidPath(uri)) {
496        HiLog.error(TAG, `invalid uri`);
497        return;
498      }
499      try {
500        await this.fileOpen(uri);
501        await this.pickerDialog(uri, want);
502      } catch {
503        HiLog.info(TAG, `fileOpen or pickerDialog start failed`);
504        return;
505      }
506    } catch (err) {
507      HiLog.error(TAG, `DocumentViewPicker failed: ${JSON.stringify(err)}`);
508      try {
509        if (this.file != undefined) {
510          await fs.close(this.file);
511        }
512        await this.deleteUri(uri);
513      } catch (err) {
514        HiLog.info(TAG, `close fail: ${JSON.stringify(err)}`);
515      }
516      this.isOK = false;
517      await GetAlertMessage.startAlertAbility(this.context,
518        { code: Constants.ERR_JS_APP_INSIDE_ERROR } as BusinessError);
519      return;
520    }
521  }
522
523  pickerDialog(uri: string, want: Want): Promise<void> {
524    return new Promise(async (resolve, reject) => {
525      let token2FileValue: (number | string | dlpPermission.DLPFile | dlpPermission.DLPFileAccess)[] =
526        (GlobalContext
527        .load('token2File') as Map<number, (number | string | dlpPermission.DLPFile | dlpPermission.DLPFileAccess)[]>)
528        .get(this.tokenId) as (number | string | dlpPermission.DLPFile | dlpPermission.DLPFileAccess)[];
529      this.dlpFile = token2FileValue[0] as dlpPermission.DLPFile;
530      this.sandboxBundleName = token2FileValue[1] as string;
531      let appId: number = token2FileValue[2] as number;
532      let srcUri: string = token2FileValue[4] as string;
533      this.srcFdPicker = getFileFd(srcUri);
534      let success = await this.copyDlpHead(this.srcFdPicker, this.dstFdPicker);
535      if (!success) {
536        try {
537          await fs.close(this.file);
538          await this.deleteUri(uri);
539        } catch (err) {
540          HiLog.info(TAG, `close fail: ${JSON.stringify(err)}`);
541        }
542        fs.closeSync(this.srcFdPicker);
543        this.isOK = false;
544        await GetAlertMessage.startAlertAbility(this.context,
545          { code: Constants.ERR_JS_APP_INSIDE_ERROR } as BusinessError);
546        reject();
547      }
548      try {
549        await this.openDLPFile(uri, want);
550        await this.addDLPLinkFile(appId, uri);
551      } catch {
552        reject();
553      }
554      let linkFilePath = Constants.FUSE_PATH + this.linkFileName;
555      let linkUri = getFileUriByPath(linkFilePath);
556      (GlobalContext.load('token2File') as Map<number, Object[]>)
557        .set(this.tokenId, [this.dlpFile, this.sandboxBundleName, appId, this.authPerm, srcUri]);
558      let sandbox2linkFile: Map<string, (number | string | dlpPermission.DLPFile)[][]> =
559        GlobalContext.load('sandbox2linkFile') as Map<string, (number | string | dlpPermission.DLPFile)[][]>;
560      sandbox2linkFile.get(this.sandboxBundleName + appId)
561        ?.push([this.newDlpFile, this.linkFileName, this.dstFdPicker, this.tokenId]);
562      (GlobalContext.load('fileOpenHistory') as Map<string, Object[]>)
563        .set(uri, [this.sandboxBundleName, appId, this.linkFileName, linkUri]);
564      (GlobalContext.load('linkSet') as Set<string>).add(linkUri);
565      this.resultUri = getFileUriByPath(linkFilePath);
566      (this.result.want?.parameters?.pick_path_return as string[]).push(this.resultUri);
567      this.result.resultCode = 0;
568      fs.closeSync(this.srcFdPicker);
569      resolve();
570    })
571  }
572
573  fileOpen(uri: string): Promise<void> {
574    return new Promise(async (resolve, reject) => {
575      try {
576        this.file = await fs.open(uri, fs.OpenMode.READ_WRITE);
577        this.dstFdPicker = this.file.fd;
578        resolve();
579      } catch (err) {
580        HiLog.error(TAG, `open: ${uri}, failed: ${JSON.stringify(err)}`);
581        try {
582          if (this.file != undefined) {
583            await fs.close(this.file);
584          }
585          await this.deleteUri(uri);
586        } catch (err) {
587          HiLog.info(TAG, `close fail: ${JSON.stringify(err)}`);
588        }
589        this.isOK = false;
590        await GetAlertMessage.startAlertAbility(this.context,
591          { code: Constants.ERR_JS_APP_INSIDE_ERROR } as BusinessError);
592        reject();
593      }
594    })
595  }
596
597  openDLPFile(uri: string, want: Want): Promise<void> {
598    return new Promise(async (resolve, reject) => {
599      let callerAppId: string;
600      try {
601        let callerBundleName = want.parameters?.['ohos.aafwk.param.callerBundleName'] as string;
602        callerAppId = await getAppId(callerBundleName);
603        HiLog.info(TAG, `get AppId: ${callerAppId}`);
604      } catch {
605        reject();
606        return;
607      }
608      try {
609        this.newDlpFile = await dlpPermission.openDLPFile(this.dstFdPicker, callerAppId);
610      } catch (err) {
611        HiLog.error(TAG, `generateDlpFile: ${this.dstFdPicker}, failed: ${JSON.stringify(err)}`);
612        await GetAlertMessage.startAlertAbility(this.context, err);
613        try {
614          await fs.close(this.file);
615          await this.deleteUri(uri);
616        } catch (err) {
617          HiLog.info(TAG, `close fail: ${JSON.stringify(err)}`);
618        }
619        fs.closeSync(this.srcFdPicker);
620        reject();
621      }
622      resolve();
623    })
624  }
625
626  async addDLPLinkFile(appId: number, uri: string): Promise<void> {
627    return new Promise(async (resolve, reject) => {
628      let date = new Date();
629      let timestamp = new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(),
630        date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), date.getMilliseconds()).getTime();
631
632      this.linkFileName = String(this.sandboxBundleName).substring(0, Constants.BUNDLE_LEN) + '_' + appId +
633        '_' + timestamp + String(Math.random()).substring(Constants.RAND_START, Constants.RAND_END) + '.dlp.link.' +
634      this.suffix;
635
636      try {
637        await this.newDlpFile.addDLPLinkFile(this.linkFileName);
638      } catch (err) {
639        HiLog.error(TAG, `addDlpLinkFile failed: ${JSON.stringify(err)}`);
640        try {
641          await this.newDlpFile.closeDLPFile();
642        } catch (err) {
643          HiLog.error(TAG, `closeDlpFile failed: ${JSON.stringify(err)}`);
644        }
645        try {
646          await fs.close(this.file);
647          await this.deleteUri(uri);
648        } catch (err) {
649          HiLog.info(TAG, `close fail: ${JSON.stringify(err)}`);
650        }
651        fs.closeSync(this.srcFdPicker);
652        this.isOK = false;
653        await GetAlertMessage.startAlertAbility(this.context,
654          { code: Constants.ERR_JS_APP_INSIDE_ERROR } as BusinessError);
655        reject();
656      }
657      resolve();
658    })
659  }
660
661  deleteUri(uri: string): Promise<void> {
662    return new Promise(async (resolve, reject) => {
663      let uriInfo: fileUri.FileUri = new fileUri.FileUri('');
664      try {
665        uriInfo = new fileUri.FileUri(uri);
666      } catch (err) {
667        HiLog.error(TAG, `fileUri fail: ${JSON.stringify(err)}`);
668        reject();
669      }
670      try {
671        await fs.unlink(uriInfo.path);
672      } catch (err) {
673        HiLog.error(TAG, `unlink fail: ${JSON.stringify(err)}`);
674        reject();
675      }
676      resolve();
677    })
678  }
679};
680