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 { BusinessError } from '@ohos.base';
17import Want from '@ohos.app.ability.Want';
18import fileUri from '@ohos.file.fileuri';
19import ServiceExtension from '@ohos.app.ability.ServiceExtensionAbility';
20import fs from '@ohos.file.fs';
21import dlpPermission from '@ohos.dlpPermission';
22import IdlDlpRpcServiceStub from '../MainAbility/data/IIdlDlpRpcServiceTs/id_dlpRpc_service_stub';
23import {
24  closeDlpFileCallback,
25  sandBoxLinkFileCallback,
26  fileOpenHistoryCallback,
27  linkSetCallback,
28  genDlpFileCallback,
29  openDlpFileCallback,
30  recoverDlpFileCallback,
31  replaceDlpLinkFileCallback,
32  resumeFuseLinkCallback,
33  stopFuseLinkCallback
34} from '../MainAbility/data/IIdlDlpRpcServiceTs/i_id_dlpRpc_service';
35import IDLDLPProperty from './sequenceable/dlpClass';
36import { IAuthUser } from './sequenceable/dlpClass';
37import GlobalContext from '../common/GlobalContext';
38import Constants from '../common/constant';
39import { HiLog } from '../common/HiLog';
40
41const TAG = 'service_extability';
42
43class DlpRpcServiceStub extends IdlDlpRpcServiceStub {
44  private dlpFileMap: Map<string, dlpPermission.DLPFile | null> = new Map<string, dlpPermission.DLPFile | null>();
45  private inFile: fs.File | undefined = undefined;
46  private outFile: fs.File | undefined = undefined;
47
48  constructor(des: string) {
49    super(des);
50  }
51
52  getOpeningFile(inputUri: string) : dlpPermission.DLPFile | null {
53    let sandbox2linkFile: Map<string, (number | string | dlpPermission.DLPFile)[][]> = GlobalContext.load('sandbox2linkFile') as Map<string, (number | string | dlpPermission.DLPFile)[][]>;
54    if (sandbox2linkFile !== undefined) {
55      for (let key of Array.from<(number | string | dlpPermission.DLPFile)[][]>(sandbox2linkFile.values())) {
56        for (let j of key) {
57          if (j[4] === inputUri) {
58            let dlpFile: dlpPermission.DLPFile = j[0] as dlpPermission.DLPFile;
59            return dlpFile;
60          }
61        }
62      }
63    }
64
65    if (this.dlpFileMap.has(inputUri)) {
66      let dlpFile: dlpPermission.DLPFile = this.dlpFileMap.get(inputUri) as dlpPermission.DLPFile;
67      return dlpFile;
68    }
69    return null;
70  }
71
72  async genDlpFile(inputUri: string, outputUri: string, dlp: IDLDLPProperty, callback: genDlpFileCallback
73  ): Promise<void> {
74    HiLog.info(TAG, `genDlpFile in service`);
75    let result: Record<string, number>;
76    try {
77      result = await this.genDlpFileFd(inputUri, outputUri);
78    } catch (error) {
79      callback(error);
80      return;
81    }
82    let dlpP: dlpPermission.DLPProperty = {
83      'ownerAccount' : dlp.ownerAccount,
84      'ownerAccountID' : dlp.ownerAccountID,
85      'ownerAccountType' : dlp.ownerAccountType,
86      'authUserList' : dlp.authUserList,
87      'contactAccount' : dlp.contactAccount,
88      'offlineAccess' : dlp.offlineAccess,
89      'everyoneAccessList' : dlp.everyoneAccessList,
90      'expireTime': dlp.expireTime
91    }
92    try {
93      let dlpFile = await dlpPermission.generateDLPFile(result.inFileFd, result.outFileFd, dlpP);
94      if (!this.dlpFileMap.has(outputUri)) {
95        this.dlpFileMap.set(outputUri, dlpFile);
96        AppStorage.setOrCreate('dlpFileMap', this.dlpFileMap);
97      } else {
98        let rawDlpFile = this.dlpFileMap.get(outputUri) ?? null;
99        if (rawDlpFile !== null) {
100          try {
101            await rawDlpFile.closeDLPFile();
102          } catch (err) {
103            HiLog.error(TAG, `closeDlpFile file: ${JSON.stringify(err)}`);
104          }
105        }
106        this.dlpFileMap.delete(outputUri);
107        this.dlpFileMap.set(outputUri, dlpFile);
108        AppStorage.setOrCreate('dlpFileMap', this.dlpFileMap);
109      }
110      callback(0);
111    } catch (err) {
112      HiLog.info(TAG, `genDlpFile file: ${JSON.stringify(err)}`);
113      await this.closeFile();
114      callback((err as BusinessError).code);
115    }
116  }
117
118  async closeFile(): Promise<void> {
119    try {
120      await fs.close(this.inFile);
121    } catch (err) {
122      HiLog.info(TAG, `close fail: ${JSON.stringify(err)}`);
123    }
124    try {
125      await fs.close(this.outFile);
126    } catch (err) {
127      HiLog.info(TAG, `close fail: ${JSON.stringify(err)}`);
128    }
129  }
130
131  async genDlpFileFd(inputUri: string, outputUri: string): Promise<Record<string, number>> {
132    return new Promise(async (resolve, reject) => {
133      let inFileFd: number = -1;
134      let outFileFd: number = -1;
135      try {
136        this.inFile = await fs.open(inputUri, fs.OpenMode.READ_WRITE);
137        inFileFd = this.inFile.fd;
138      } catch (error) {
139        HiLog.error(TAG, `open: ${inputUri}, failed: ${JSON.stringify(error)}`);
140        reject((error as BusinessError).code);
141        return;
142      }
143      let uriInfo: fileUri.FileUri = new fileUri.FileUri('');
144      try {
145        uriInfo = new fileUri.FileUri(outputUri);
146      } catch (err) {
147        HiLog.error(TAG, `fileUri fail: ${JSON.stringify(err)}`);
148      }
149      try {
150        this.outFile = await fs.open(outputUri, fs.OpenMode.READ_WRITE);
151        outFileFd = this.outFile.fd;
152      } catch (error) {
153        try {
154          await fs.close(this.inFile);
155          await fs.unlink(uriInfo.path);
156        } catch (err) {
157          HiLog.error(TAG, `unlink fail: ${JSON.stringify(err)}`);
158        }
159        reject((error as BusinessError).code);
160        return;
161      }
162      let result = {
163        'inFileFd': inFileFd,
164        'outFileFd': outFileFd
165      } as Record<string, number>;
166      resolve(result);
167    })
168  }
169
170  async openDlpFile(srcUri: string, callerAppId: string, callback: openDlpFileCallback): Promise<void> {
171    HiLog.info(TAG, `openDlpFile start: ${srcUri}`);
172    let inFile = await fs.open(srcUri, fs.OpenMode.READ_WRITE);
173    let dlpFile: dlpPermission.DLPFile;
174    let authUserListNew: IAuthUser[] = [];
175    try {
176      dlpFile = await dlpPermission.openDLPFile(inFile.fd, callerAppId);
177      dlpFile.dlpProperty.authUserList?.forEach(item => {
178        authUserListNew.push(
179          new IAuthUser(
180            item.authAccount,
181            item.authAccountType,
182            item.dlpFileAccess,
183            item.permExpiryTime
184          ))
185      })
186      let _dlp = new IDLDLPProperty(
187        dlpFile.dlpProperty.ownerAccount,
188        dlpFile.dlpProperty.ownerAccountID,
189        dlpFile.dlpProperty.ownerAccountType,
190        authUserListNew,
191        dlpFile.dlpProperty.contactAccount,
192        dlpFile.dlpProperty.offlineAccess,
193        dlpFile.dlpProperty.everyoneAccessList ?? [],
194        dlpFile.dlpProperty.expireTime ?? 0,
195      );
196      callback(0, _dlp, '');
197      if (!this.dlpFileMap.has(srcUri)) {
198        this.dlpFileMap.set(srcUri, dlpFile);
199        AppStorage.setOrCreate('dlpFileMap', this.dlpFileMap);
200      } else {
201        HiLog.info(TAG, `map is overwrite`);
202        this.dlpFileMap.delete(srcUri);
203        this.dlpFileMap.set(srcUri, dlpFile);
204        AppStorage.setOrCreate('dlpFileMap', this.dlpFileMap);
205      }
206    } catch (err) {
207      let _dlp = new IDLDLPProperty('', '', 0, [], '', true, [], 0);
208      callback((err as BusinessError).code, _dlp, (err as BusinessError).message);
209    } finally {
210      if (inFile) {
211        fs.closeSync(inFile);
212      }
213    }
214  }
215
216  async stopFuseLink(uri: string, callback: stopFuseLinkCallback): Promise<void> {
217    HiLog.info(TAG, `stopFuseLink start: ${uri}`);
218    let dlpFile: dlpPermission.DLPFile | null = this.getOpeningFile(uri);
219    if (dlpFile !== null) {
220      await dlpFile.stopFuseLink();
221    } else {
222      HiLog.info(TAG, `stopFuseLink not find: ${uri}`);
223      callback(-1);
224    }
225  }
226
227  async resumeFuseLink(uri: string, callback: resumeFuseLinkCallback): Promise<void> {
228    HiLog.info(TAG, `resumeFuseLink start`);
229    let dlpFile: dlpPermission.DLPFile | null = this.getOpeningFile(uri);
230    if (dlpFile !== null) {
231      await dlpFile.resumeFuseLink();
232    } else {
233      HiLog.info(TAG, `resumeFuseLink not find: ${uri}`);
234      callback(-1);
235    }
236  }
237
238  async replaceDlpLinkFile(srcUri: string, linkFileName: string, callback: replaceDlpLinkFileCallback): Promise<void> {
239    GlobalContext.load('sandbox2linkFile');
240    if (this.dlpFileMap.has(srcUri)) {
241      let dlpFile: dlpPermission.DLPFile = this.dlpFileMap.get(srcUri) as dlpPermission.DLPFile;
242      let sandbox2linkFile: Map<string, (number | string | dlpPermission.DLPFile)[][]> = GlobalContext.load('sandbox2linkFile') as Map<string, (number | string | dlpPermission.DLPFile)[][]>;
243      for (let key of Array.from<(number | string | dlpPermission.DLPFile)[][]>(sandbox2linkFile.values())) {
244        for (let j of key) {
245          if (j[1] === linkFileName) {
246            j[0] = dlpFile;
247          }
248        }
249      }
250      await dlpFile.replaceDLPLinkFile(linkFileName);
251    } else {
252      HiLog.info(TAG, `replaceDLPLinkFile not find: ${srcUri}`);
253      callback(-1);
254    }
255  }
256
257  async recoverDlpFile(srcUri: string, pathUri: string, callback: recoverDlpFileCallback): Promise<void> {
258    let dlpFile: dlpPermission.DLPFile | null = this.getOpeningFile(srcUri);
259    if (dlpFile !== null) {
260      let inFile: fs.File | undefined;
261      try {
262        inFile = await fs.open(pathUri, fs.OpenMode.READ_WRITE);
263        await dlpFile.recoverDLPFile(inFile.fd);
264      } catch (err) {
265        HiLog.error(TAG, `recoverDlpFileInner4: ${JSON.stringify(err)}`);
266        callback((err as BusinessError).code);
267      } finally {
268        if (inFile) {
269          fs.closeSync(inFile);
270        }
271      }
272    } else {
273      HiLog.info(TAG, `recoverDlpFile not find: ${srcUri}`);
274      callback(-1);
275    }
276  }
277
278  async closeDlpFile(srcUri: string, callback: closeDlpFileCallback): Promise<void> {
279    HiLog.info(TAG, `closeDlpFile start`);
280    let dlpFile: dlpPermission.DLPFile | null = this.getOpeningFile(srcUri);
281    if (dlpFile !== null) {
282      try {
283        await dlpFile.closeDLPFile();
284        if (this.dlpFileMap.has(srcUri)) {
285          this.dlpFileMap.delete(srcUri);
286          AppStorage.setOrCreate('dlpFileMap', this.dlpFileMap);
287        }
288        callback(0);
289      } catch (err) {
290        HiLog.error(TAG, `closeDlpFile file: ${JSON.stringify(err)}`);
291        callback((err as BusinessError).code);
292      }
293    }
294  }
295
296  async sandBoxLinkFile(linkFileName: string, callerToken: number, callback: sandBoxLinkFileCallback): Promise<void> {
297    let sandbox2linkFile: Map<string, (number | string | dlpPermission.DLPFile)[][]> = GlobalContext.load('sandbox2linkFile') as Map<string, (number | string | dlpPermission.DLPFile)[][]>;
298    for (let value of Array.from<(number | string | dlpPermission.DLPFile)[][]>(sandbox2linkFile.values())) {
299      for (let linkFile of value) {
300        let _dlp = new IDLDLPProperty('', '', 0, [], '', true, [], 0);
301        if (linkFile[Constants.FILE_OPEN_HISTORY_ONE] === linkFileName) {
302          let authUserListNew: IAuthUser[] = [];
303          if (callerToken !== linkFile[Constants.FILE_OPEN_HISTORY_THREE]) {
304            HiLog.error(TAG, `found file, but token invalid: ${linkFileName}`);
305            callback(-1, _dlp, '');
306          } else {
307            let linkFileInfo: Record<number, number | string | dlpPermission.DLPFile> =
308              linkFile[0] as Record<number, number | string | dlpPermission.DLPFile>;
309            linkFileInfo['dlpProperty'].authUserList.forEach((item: IAuthUser)=> {
310              authUserListNew.push(
311                new IAuthUser(
312                  item.authAccount,
313                  item.authAccountType,
314                  item.dlpFileAccess,
315                  item.permExpiryTime
316                ))
317            })
318            _dlp = new IDLDLPProperty(
319              linkFileInfo['dlpProperty'].ownerAccount,
320              linkFileInfo['dlpProperty'].ownerAccountID,
321              linkFileInfo['dlpProperty'].ownerAccountType,
322              authUserListNew,
323              linkFileInfo['dlpProperty'].contactAccount,
324              linkFileInfo['dlpProperty'].offlineAccess,
325              linkFileInfo['dlpProperty'].everyoneAccessList ?? [],
326              linkFileInfo['dlpProperty'].expireTime ?? 0
327            );
328            let fileUri = linkFile[4];
329            callback(0, _dlp, fileUri.toString());
330          }
331        } else {
332          HiLog.error(TAG, `request from sandbox, but can not find dlp file by linkFileName: ${linkFileName}`);
333          callback(-1, _dlp, '');
334        }
335      }
336    }
337  }
338
339  async fileOpenHistory(uri: string, callback: fileOpenHistoryCallback): Promise<void> {
340    HiLog.info(TAG, `fileOpenHistory start`);
341    if ((GlobalContext.load('fileOpenHistory') as Map<string, (number | string)[]>)?.has(uri)) {
342      callback(0);
343    } else {
344      callback(-1);
345    }
346  }
347
348  async linkSet(uri: string, callback: linkSetCallback): Promise<void> {
349    HiLog.info(TAG, `linkSet start`);
350    if ((GlobalContext.load('linkSet') as Map<string, (number | string)[]>)?.has(uri)) {
351      callback(0);
352    } else {
353      callback(-1);
354    }
355  }
356}
357
358export default class ServiceExtAbility extends ServiceExtension {
359  onCreate(want: Want) {
360    HiLog.info(TAG, `onCreate, want: ${want.abilityName}`);
361  }
362
363  onRequest(want: Want, startId: number) {
364    HiLog.info(TAG, `onRequest, want: ${want.abilityName}`);
365  }
366
367  onConnect(want: Want) {
368    HiLog.info(TAG, `service onConnect, want: ${want.abilityName}`);
369    return new DlpRpcServiceStub('dlpRpc service stub');
370  }
371
372  onDisconnect(want: Want): void {
373    HiLog.info(TAG, `onDisconnect, want: ${want.abilityName}`);
374  }
375
376  onDestroy(): void {
377    HiLog.info(TAG, `onDestroy`);
378  }
379}