1/* 2 * Copyright (c) 2025 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 Result from '../../common/Result' 17import { ResultMsg } from '../../common/ResultMsg' 18import Constants from '../../common/constant'; 19import fs, { ReadOptions } from '@ohos.file.fs'; 20import { getFileFd } from '../../common/FileUtils/utils'; 21import { HiLog } from '../../common/HiLog'; 22import { zlib } from '@kit.BasicServicesKit'; 23import { util } from '@kit.ArkTS'; 24import FileUtil from '../../common/external/FileUtil'; 25import { dlpPermission } from '@kit.DataProtectionKit'; 26import FileMetaInfo from '../../bean/data/FileMetaInfo'; 27import GlobalContext from '../../common/GlobalContext'; 28import { FileParseType } from '../../bean/data/FileParseType'; 29import { FileParseInfo } from '../../bean/data/FileParseInfo'; 30import OpenDlpFileData from '../data/OpenDlpFileData'; 31 32const TAG: string = 'FileParse'; 33 34const NUM_TO_TYPE_MAP = new Map<number, string>([ 35 [1, 'txt'], 36 [2, 'pdf'], 37 [3, 'doc'], 38 [4, 'docx'], 39 [5, 'ppt'], 40 [6, 'pptx'], 41 [7, 'xls'], 42 [8, 'xlsx'], 43 [9, 'bmp'], 44 [10, 'bm'], 45 [11, 'dng'], 46 [12, 'gif'], 47 [13, 'heic'], 48 [14, 'heics'], 49 [15, 'heif'], 50 [16, 'heifs'], 51 [17, 'hif'], 52 [18, 'jpg'], 53 [19, 'jpeg'], 54 [20, 'jpe'], 55 [21, 'png'], 56 [22, 'webp'], 57 [23, 'cur'], 58 [24, 'raf'], 59 [25, 'ico'], 60 [26, 'nrw'], 61 [27, 'rw2'], 62 [28, 'pef'], 63 [29, 'srw'], 64 [30, 'svg'], 65 [31, 'arw'], 66 [32, '3gpp2'], 67 [33, '3gp2'], 68 [34, '3g2'], 69 [35, '3gpp'], 70 [36, '3gp'], 71 [37, 'avi'], 72 [38, 'm4v'], 73 [39, 'f4v'], 74 [40, 'mp4v'], 75 [41, 'mpeg4'], 76 [42, 'mp4'], 77 [43, 'm2ts'], 78 [44, 'mts'], 79 [45, 'ts'], 80 [46, 'vt'], 81 [47, 'wrf'], 82 [48, 'mpeg'], 83 [49, 'mpeg2'], 84 [50, 'mpv2'], 85 [51, 'mp2v'], 86 [52, 'm2v'], 87 [53, 'm2t'], 88 [54, 'mpeg1'], 89 [55, 'mpv1'], 90 [56, 'mp1v'], 91 [57, 'm1v'], 92 [58, 'mpg'], 93 [59, 'mov'], 94 [60, 'mkv'], 95 [61, 'webm'], 96 [62, 'h264'], 97 [63, 'wbmp'], 98 [64, 'nef'], 99 [65, 'cr2'], 100]); 101 102abstract class FileParseBase { 103 protected metaInfo?: FileMetaInfo; 104 public fileSize: number = 0; 105 public parseType?: FileParseType; 106 107 constructor(fileSize: number) { 108 this.fileSize = fileSize; 109 } 110 111 public abstract parse(uri: string, ctxFilesDir: string): Promise<Result<FileMetaInfo>>; 112} 113 114class ZipParse extends FileParseBase { 115 constructor(fileSize: number) { 116 super(fileSize); 117 this.parseType = FileParseType.ZIP; 118 } 119 120 async parse(uri: string, ctxFilesDir: string): Promise<Result<FileMetaInfo>> { 121 const tempRandom = String(Math.random()).substring(Constants.RAND_START, Constants.RAND_END); 122 const filePath = ctxFilesDir + '/saveAs' + tempRandom; 123 const dirPath = ctxFilesDir + '/saveAsUnzip' + tempRandom; 124 const fileName = dirPath + '/dlp_cert'; 125 const generalInfoPath = dirPath + '/dlp_general_info'; 126 let file: fs.File | undefined; 127 let ff: fs.File | undefined; 128 try { 129 file = fs.openSync(uri, fs.OpenMode.READ_ONLY); 130 const fileInfo = fs.statSync(file.fd); 131 this.fileSize = fileInfo.size; 132 ff = await fs.open(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); 133 await fs.copyFile(file.fd, ff.fd); 134 fs.mkdirSync(dirPath, true); 135 await zlib.decompressFile(filePath, dirPath); 136 137 let dlpInfo = fs.readTextSync(fileName); 138 let infoArray = dlpInfo.split('accountType'); 139 let type = infoArray[Constants.NUMBER_ONE].slice(Constants.TYPE_START, Constants.TYPE_END); 140 141 let generalInfo = fs.readTextSync(generalInfoPath); 142 let generalInfoArray = generalInfo.split('realFileType'); 143 let realFileType: string = ''; 144 if (generalInfoArray.length === Constants.NUMBER_TWO) { 145 let realFileTypeStr = generalInfoArray[Constants.NUMBER_ONE].split('\"'); 146 if (realFileTypeStr.length > Constants.NUMBER_TWO) { 147 realFileType = realFileTypeStr[Constants.NUMBER_TWO]; 148 } 149 } 150 151 GlobalContext.store('accountType', Number(type)); 152 this.metaInfo = { 153 accountType: Number(type), 154 fileType: realFileType, 155 ownerAccount: '', 156 fileSize: this.fileSize 157 }; 158 return ResultMsg.buildSuccess(this.metaInfo); 159 } catch (error) { 160 HiLog.wrapError(TAG, error, 'Error parse zipFile'); 161 return ResultMsg.getErrMsg(Constants.ERR_JS_NOT_DLP_FILE); 162 } finally { 163 FileUtil.closeSync(file); 164 FileUtil.closeSync(ff); 165 FileUtil.unlinkSync(filePath); 166 FileUtil.rmdirSync(dirPath); 167 } 168 } 169} 170 171class RawParse extends FileParseBase { 172 constructor(fileSize: number) { 173 super(fileSize); 174 this.parseType = FileParseType.RAW; 175 } 176 177 async parse(uri: string, ctxFilesDir: string): Promise<Result<FileMetaInfo>> { 178 let file: fs.File | undefined; 179 try { 180 file = fs.openSync(uri, fs.OpenMode.READ_ONLY); 181 const fileInfo = fs.statSync(file.fd); 182 this.fileSize = fileInfo.size; 183 let data = new ArrayBuffer(Constants.HEAD_LENGTH_IN_BYTE); 184 let option: ReadOptions = { offset: 0, length: Constants.HEAD_LENGTH_IN_BYTE }; 185 fs.readSync(file.fd, data, option); 186 187 let buf = new Uint32Array(data, 0, Constants.HEAD_LENGTH_IN_U32); 188 let cert = new ArrayBuffer(buf[Constants.CERT_SIZE]); 189 let certOffset = Constants.CERT_OFFSET_4GB * buf[Constants.CERT_OFFSET + 1] + buf[Constants.CERT_OFFSET]; 190 option = { offset: certOffset, length: buf[Constants.CERT_SIZE] }; 191 fs.readSync(file.fd, cert, option); 192 193 const textDecoder: util.TextDecoder = util.TextDecoder.create('utf-8'); 194 const fdString: string = textDecoder.decodeToString(new Uint8Array(cert), { stream: false }); 195 const infoArray = fdString.split('accountType'); 196 const type = infoArray[1].slice(Constants.TYPE_START, Constants.TYPE_END); 197 const accountType = Number(type); 198 if (accountType !== dlpPermission.AccountType.CLOUD_ACCOUNT && 199 accountType !== dlpPermission.AccountType.DOMAIN_ACCOUNT) { 200 HiLog.error(TAG, `Error accountType: ${accountType}`); 201 return ResultMsg.getErrMsg(Constants.ERR_JS_NOT_DLP_FILE); 202 } 203 204 GlobalContext.store('accountType', accountType); 205 this.metaInfo = { 206 accountType: accountType, 207 fileType: NUM_TO_TYPE_MAP.has(buf[Constants.NUMBER_THREE]) ? 208 NUM_TO_TYPE_MAP.get(buf[Constants.NUMBER_THREE]) as string : '', 209 ownerAccount: '', 210 fileSize: this.fileSize 211 }; 212 return ResultMsg.buildSuccess(this.metaInfo); 213 } catch (error) { 214 HiLog.wrapError(TAG, error, 'Error parse rawfile'); 215 return ResultMsg.getErrMsg(Constants.ERR_JS_NOT_DLP_FILE); 216 } finally { 217 FileUtil.closeSync(file); 218 } 219 } 220} 221 222class FileFormatDetector { 223 static async detect(fd: number): Promise<Result<FileParseInfo>> { 224 HiLog.info(TAG, 'start detect'); 225 let data = new ArrayBuffer(Constants.HEAD_LENGTH_IN_BYTE); 226 let option: ReadOptions = { offset: 0, length: Constants.HEAD_LENGTH_IN_BYTE }; 227 let fileSize = 0; 228 try { 229 fs.readSync(fd, data, option); 230 const fileInfo = fs.statSync(fd); 231 fileSize = fileInfo.size; 232 } catch (error) { 233 HiLog.wrapError(TAG, error, 'FileFormatDetector error'); 234 return ResultMsg.getErrMsg(Constants.ERR_CODE_OPEN_FILE_ERROR); 235 } 236 let buf = new Uint32Array(data, 0, Constants.HEAD_LENGTH_IN_U32); 237 if (buf && buf[0] === Constants.DLP_ZIP_MAGIC) { 238 HiLog.debug(TAG, 'FileFormatDetector zip'); 239 const parseInfo: FileParseInfo = { parseType: FileParseType.ZIP, fileSize: fileSize }; 240 return ResultMsg.buildSuccess(parseInfo); 241 } 242 if (buf && (buf[0] === Constants.DLP_RAW_MAGIC || (buf.length >= 3 && buf[2] === Constants.DLP_RAW_MAGIC))) { 243 HiLog.debug(TAG, 'FileFormatDetector raw'); 244 const parseInfo: FileParseInfo = { parseType: FileParseType.RAW, fileSize: fileSize }; 245 return ResultMsg.buildSuccess(parseInfo); 246 } 247 HiLog.error(TAG, 'FileFormatDetector not dlp file'); 248 return ResultMsg.getErrMsg(Constants.ERR_JS_NOT_DLP_FILE); 249 } 250} 251 252export class FileParseFactory { 253 static async createFileParse(openDlpFileData: OpenDlpFileData): Promise<Result<FileParseBase>> { 254 HiLog.debug(TAG, 'CreateFileParse'); 255 const uri = openDlpFileData.uri; 256 let getFileFdRet = getFileFd(uri, fs.OpenMode.READ_WRITE); 257 if (getFileFdRet.errcode !== Constants.ERR_CODE_SUCCESS || !getFileFdRet.result) { 258 HiLog.info(TAG, 'getFileFd with READ_WRITE failed, try to READ_ONLY.'); 259 getFileFdRet = getFileFd(uri, fs.OpenMode.READ_ONLY); 260 if (getFileFdRet.errcode !== Constants.ERR_CODE_SUCCESS || !getFileFdRet.result) { 261 HiLog.error(TAG, 'getFileFd with READ_ONLY error.'); 262 return ResultMsg.buildMsg(getFileFdRet.errcode, getFileFdRet.errmsg); 263 } 264 } 265 let dlpFd = getFileFdRet.result; 266 const format = await FileFormatDetector.detect(dlpFd); 267 if (format.errcode !== Constants.ERR_CODE_SUCCESS || !format.result) { 268 HiLog.error(TAG, 'detect failed'); 269 return ResultMsg.buildMsg(format.errcode, format.errmsg); 270 } 271 FileUtil.closeSync(dlpFd); 272 switch (format.result.parseType) { 273 case FileParseType.ZIP: 274 openDlpFileData.fileParse = FileParseType.ZIP; 275 return ResultMsg.buildSuccess(new ZipParse(format.result.fileSize)); 276 case FileParseType.RAW: 277 openDlpFileData.fileParse = FileParseType.RAW; 278 return ResultMsg.buildSuccess(new RawParse(format.result.fileSize)); 279 default: 280 HiLog.error(TAG, 'createFileParse error'); 281 return ResultMsg.getErrMsg(Constants.ERR_JS_NOT_DLP_FILE); 282 } 283 } 284}