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