/*
* Copyright (c) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Result from '../../common/base/Result'
import { ResultMsg } from '../../common/base/ResultMsg'
import Constants from '../../common/constants/constant';
import fs, { ReadOptions } from '@ohos.file.fs';
import { checkValidAccountType, getFileFd } from '../../utils/utils';
import { HiLog } from '../../common/base/HiLog';
import { zlib } from '@kit.BasicServicesKit';
import { util } from '@kit.ArkTS';
import FileUtil from '../../utils/external/FileUtil';
import { dlpPermission } from '@kit.DataProtectionKit';
import FileMetaInfo from '../../common/bean/data/FileMetaInfo';
import GlobalContext from '../../common/base/GlobalContext';
import { FileParseInfo } from '../../common/bean/data/FileParseInfo';
import OpenDlpFileData from '../data/OpenDlpFileData';
import FileUtils from '../../utils/FileUtils';
import { FileParseType } from '../../common/enum/FileEnum';
const TAG: string = 'FileParse';
const ENTERPRISE_ACCOUNT: number = 4;
const NUM_TO_TYPE_MAP = new Map<number, string>([
[1, 'txt'],
[2, 'pdf'],
[3, 'doc'],
[4, 'docx'],
[5, 'ppt'],
[6, 'pptx'],
[7, 'xls'],
[8, 'xlsx'],
[9, 'bmp'],
[10, 'bm'],
[11, 'dng'],
[12, 'gif'],
[13, 'heic'],
[14, 'heics'],
[15, 'heif'],
[16, 'heifs'],
[17, 'hif'],
[18, 'jpg'],
[19, 'jpeg'],
[20, 'jpe'],
[21, 'png'],
[22, 'webp'],
[23, 'cur'],
[24, 'raf'],
[25, 'ico'],
[26, 'nrw'],
[27, 'rw2'],
[28, 'pef'],
[29, 'srw'],
[30, 'svg'],
[31, 'arw'],
[32, '3gpp2'],
[33, '3gp2'],
[34, '3g2'],
[35, '3gpp'],
[36, '3gp'],
[37, 'avi'],
[38, 'm4v'],
[39, 'f4v'],
[40, 'mp4v'],
[41, 'mpeg4'],
[42, 'mp4'],
[43, 'm2ts'],
[44, 'mts'],
[45, 'ts'],
[46, 'vt'],
[47, 'wrf'],
[48, 'mpeg'],
[49, 'mpeg2'],
[50, 'mpv2'],
[51, 'mp2v'],
[52, 'm2v'],
[53, 'm2t'],
[54, 'mpeg1'],
[55, 'mpv1'],
[56, 'mp1v'],
[57, 'm1v'],
[58, 'mpg'],
[59, 'mov'],
[60, 'mkv'],
[61, 'webm'],
[62, 'h264'],
[63, 'wbmp'],
[64, 'nef'],
[65, 'cr2'],
[66, 'mp3'],
[67, 'flac'],
[68, 'm4a'],
[69, 'aac'],
[70, 'wav'],
[71, 'ogg'],
[72, 'amr'],
[73, 'm4b'],
[74, 'pot'],
[75, 'avif'],
[76, 'svgz'],
[77, 'raw'],
[78, 'ief'],
[79, 'jp2'],
[80, 'jpg2'],
[81, 'jpx'],
[82, 'jpf'],
[83, 'pcx'],
[84, 'djvu'],
[85, 'djv'],
[86, 'crw'],
[87, 'ras'],
[88, 'cdr'],
[89, 'pat'],
[90, 'cdt'],
[91, 'cpt'],
[92, 'erf'],
[93, 'art'],
[94, 'jng'],
[95, 'orf'],
[96, 'pnm'],
[97, 'pbm'],
[98, 'pgm'],
[99, 'ppm'],
[100, 'rgb'],
[101, 'xbm'],
[102, 'xwd'],
[103, 'yt'],
[104, 'mpe'],
[105, 'qt'],
[106, 'mpv'],
[107, 'flv'],
[108, 'rmvb'],
[109, 'axv'],
[110, 'dv'],
[111, 'fli'],
[112, 'ogv'],
[113, 'mxu'],
[114, 'lsf'],
[115, 'lsx'],
[116, 'mng'],
[117, 'asx'],
[118, 'wm'],
[119, 'wmv'],
[120, 'wmx'],
[121, 'wvx'],
[122, 'movie'],
[123, 'ac3'],
[124, 'imy'],
[125, 'rtttl'],
[126, 'xmf'],
[127, 'mxmf'],
[128, 'm4p'],
[129, 'm3u'],
[130, 'smf'],
[131, 'mka'],
[132, 'ra'],
[133, 'snd'],
[134, 'mp2'],
[135, 'csv'],
[136, 'rtf'],
[137, 'xml'],
[138, 'cpp'],
[139, 'cxx'],
[140, 'cc'],
[141, 'html'],
[142, 'htm'],
[143, 'java'],
]);
interface DlpGeneralInfo {
certSize?: number,
contactAccount: string,
realFileType: string,
fileId: string,
allowedOpenCount: number,
waterMarkConfig: boolean,
countdown: number,
}
interface CertInfo {
accountType: dlpPermission.AccountType
}
abstract class FileParseBase {
protected metaInfo?: FileMetaInfo;
public fileSize: number = 0;
public parseType?: FileParseType;
constructor(fileSize: number) {
this.fileSize = fileSize;
}
public abstract parse(uri: string, ctxFilesDir: string): Promise<Result<FileMetaInfo>>;
}
class ZipParse extends FileParseBase {
constructor(fileSize: number) {
super(fileSize);
this.parseType = FileParseType.ZIP;
}
async parseAccountType(fileName: string): Promise<Result<number>> {
let dlpInfo = fs.readTextSync(fileName);
const infoArray = dlpInfo.split('accountType');
let accountType: number = 0;
if (infoArray.length === Constants.NUMBER_TWO) {
let type = infoArray[Constants.NUMBER_ONE].slice(Constants.TYPE_START, Constants.TYPE_END).trim();
accountType = Number(type);
} else {
HiLog.error(TAG, 'dlp file not contain accountType');
return ResultMsg.getErrMsg(Constants.ERR_JS_NOT_DLP_FILE);
}
if (!checkValidAccountType(accountType)) {
HiLog.error(TAG, 'checkValidAccountType invalid');
return ResultMsg.getErrMsg(Constants.ERR_JS_NOT_DLP_FILE);
}
return ResultMsg.buildSuccess(accountType);
}
private parseRealFileType(generalInfo: string): string {
const realFileTypeInfoArray = generalInfo.split('realFileType');
let realFileType: string = '';
if (realFileTypeInfoArray.length === Constants.NUMBER_TWO) {
let realFileTypeStr = realFileTypeInfoArray[Constants.NUMBER_ONE].split('\"');
if (realFileTypeStr.length > Constants.NUMBER_TWO) {
realFileType = realFileTypeStr[Constants.NUMBER_TWO];
}
}
return FileUtils.getExtractRealType(realFileType);
}
private parseContactAccount(generalInfo: string): string {
const contactAccountInfoArray = generalInfo.split('contactAccount');
let contactAccount: string = '';
if (contactAccountInfoArray.length === Constants.NUMBER_TWO) {
let contactAccountStr = contactAccountInfoArray[Constants.NUMBER_ONE].split('\"');
if (contactAccountStr.length > Constants.NUMBER_TWO) {
contactAccount = contactAccountStr[Constants.NUMBER_TWO];
}
}
return contactAccount;
}
private parseFileId(generalInfo: string): string {
const fileIdInfoArray = generalInfo.split('fileId');
let fileId: string = '';
if (fileIdInfoArray.length === Constants.NUMBER_TWO) {
let fileIdStr = fileIdInfoArray[Constants.NUMBER_ONE].split('\"');
if (fileIdStr.length > Constants.NUMBER_TWO) {
fileId = fileIdStr[Constants.NUMBER_TWO];
}
}
return fileId;
}
private parseAllowedOpenCount(generalInfo: string, fileId: string): number {
let allowedOpenCount: number = -1;
const allowedOpenCountArray = generalInfo.split('allowedOpenCount');
if (allowedOpenCountArray.length === 1) {
allowedOpenCount = fileId === '' ? -1 : 1;
} else {
try {
let jsonObj: object = JSON.parse(generalInfo);
let allowedOpenCountCon: number = jsonObj['allowedOpenCount'];
allowedOpenCount = typeof allowedOpenCountCon === 'number' ? allowedOpenCountCon : -1;
} catch (error) {
HiLog.wrapError(TAG, error, 'allowedOpenCount is error');
}
}
return allowedOpenCount;
}
private parseWaterMarkConfig(generalInfo: string): boolean {
let waterMarkConfig: boolean = false;
try {
let jsonObj: object = JSON.parse(generalInfo);
let waterMarkConfigCon: boolean = jsonObj['waterMarkConfig'];
waterMarkConfig = typeof waterMarkConfigCon === 'boolean' ? waterMarkConfigCon : false;
} catch (error) {
HiLog.wrapError(TAG, error, 'waterMarkConfig is error');
}
return waterMarkConfig;
}
private parseCountdown(generalInfo: string): number {
let countdown: number = 0;
try {
let jsonObj: object = JSON.parse(generalInfo);
let countdownCon: number = jsonObj['countdown'];
countdown = typeof countdownCon === 'number' ? countdownCon : 0;
} catch (error) {
HiLog.wrapError(TAG, error, 'countdown is error');
}
return countdown;
}
async parseDlpGeneralInfo(generalInfoPath: string): Promise<DlpGeneralInfo> {
const generalInfo = fs.readTextSync(generalInfoPath);
const realFileType = this.parseRealFileType(generalInfo);
const contactAccount = this.parseContactAccount(generalInfo);
const fileId = this.parseFileId(generalInfo);
const allowedOpenCount = this.parseAllowedOpenCount(generalInfo, fileId);
const waterMarkConfig = this.parseWaterMarkConfig(generalInfo);
const countdown = this.parseCountdown(generalInfo);
const dlpGeneralInfo: DlpGeneralInfo = {
contactAccount: contactAccount,
realFileType: realFileType,
fileId: fileId,
allowedOpenCount: allowedOpenCount,
waterMarkConfig: waterMarkConfig,
countdown: countdown
}
return dlpGeneralInfo;
}
async parse(uri: string, ctxFilesDir: string): Promise<Result<FileMetaInfo>> {
const tempRandom = String(Math.random()).substring(Constants.RAND_START, Constants.RAND_END);
const filePath = ctxFilesDir + '/saveAs' + tempRandom;
const dirPath = ctxFilesDir + '/saveAsUnzip' + tempRandom;
const fileName = dirPath + '/dlp_cert';
const generalInfoPath = dirPath + '/dlp_general_info';
let file: fs.File | undefined;
let ff: fs.File | undefined;
try {
file = fs.openSync(uri, fs.OpenMode.READ_ONLY);
const fileInfo = fs.statSync(file.fd);
this.fileSize = fileInfo.size;
ff = await fs.open(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
await fs.copyFile(file.fd, ff.fd);
fs.mkdirSync(dirPath, true);
await zlib.decompressFile(filePath, dirPath);
const parseAccountTypeRet = await this.parseAccountType(fileName);
if (parseAccountTypeRet.errcode !== Constants.ERR_CODE_SUCCESS || !parseAccountTypeRet.result) {
HiLog.error(TAG, 'parseAccountType error');
return ResultMsg.buildMsg(parseAccountTypeRet.errcode, parseAccountTypeRet.errmsg);
}
const accountType = parseAccountTypeRet.result;
GlobalContext.store('accountType', accountType);
const dlpGeneralInfo: DlpGeneralInfo = await this.parseDlpGeneralInfo(generalInfoPath);
this.metaInfo = {
accountType: accountType,
fileType: dlpGeneralInfo.realFileType,
ownerAccount: dlpGeneralInfo.contactAccount,
fileSize: this.fileSize,
fileId: dlpGeneralInfo.fileId,
allowedOpenCount: dlpGeneralInfo.allowedOpenCount,
waterMarkConfig: dlpGeneralInfo.waterMarkConfig,
countdown: dlpGeneralInfo.countdown
};
return ResultMsg.buildSuccess(this.metaInfo);
} catch (error) {
HiLog.wrapError(TAG, error, 'Error parse zipFile');
return ResultMsg.getErrMsg(Constants.ERR_JS_NOT_DLP_FILE);
} finally {
FileUtil.closeSync(file);
FileUtil.closeSync(ff);
FileUtil.unlinkSync(filePath);
FileUtil.rmdirSync(dirPath);
}
}
}
class RawParse extends FileParseBase {
constructor(fileSize: number) {
super(fileSize);
this.parseType = FileParseType.RAW;
}
async parseDlpGeneralInfo(buf: Uint32Array, file: fs.File): Promise<DlpGeneralInfo> {
let contactAccount = new ArrayBuffer(buf[Constants.CONTACT_ACCOUNT_SIZE]);
let option: ReadOptions =
{ offset: buf[Constants.CONTACT_ACCOUNT_OFFSET], length: buf[Constants.CONTACT_ACCOUNT_SIZE] };
fs.readSync(file.fd, contactAccount, option);
const textDecoder: util.TextDecoder = util.TextDecoder.create('utf-8');
const contactString: string = textDecoder.decodeToString(new Uint8Array(contactAccount), { stream: false });
let fileId = new ArrayBuffer(Constants.FILE_ID_SIZE);
option = { offset: this.fileSize - Constants.FILE_ID_SIZE, length: Constants.FILE_ID_SIZE };
fs.readSync(file.fd, fileId, option);
const fileIdString: string = textDecoder.decodeToString(new Uint8Array(fileId), { stream: false });
let flag = new ArrayBuffer(Constants.INT32_SIZE);
option = { offset: this.fileSize - Constants.FLAG_SIZE, length: Constants.INT32_SIZE };
fs.readSync(file.fd, flag, option);
let flagValue: number = new Int32Array(flag)[0];
let allowedOpenCount: number = -1;
if (flagValue === 1) {
option = { offset: this.fileSize - Constants.ALLOWED_OPEN_SIZE, length: Constants.INT32_SIZE };
fs.readSync(file.fd, flag, option);
allowedOpenCount = new Int32Array(flag)[0];
} else {
allowedOpenCount = fileIdString.trim() != '' ? 1 : -1;
}
let waterMarkConfig: boolean = false;
option = { offset: this.fileSize - Constants.WATERMARK_CONFIG_SIZE, length: Constants.INT32_SIZE };
fs.readSync(file.fd, flag, option);
waterMarkConfig = new Int32Array(flag)[0] === 1 ? true : false;
let countdown: number = -1;
option = {offset: this.fileSize - Constants.COUNTDOWN_SIZE, length: Constants.INT32_SIZE};
fs.readSync(file.fd, flag, option);
let view = new DataView(flag);
countdown = view.getInt32(0, true);
let fileType = buf[Constants.NUMBER_THREE];
if (typeof fileType === 'number' && fileType > Constants.COUNTDOWN_FILE_TYPE) {
fileType = fileType - Constants.COUNTDOWN_FILE_TYPE;
}
const dlpGeneralInfo: DlpGeneralInfo = {
contactAccount: contactString ?? '',
realFileType: NUM_TO_TYPE_MAP.has(fileType) ? NUM_TO_TYPE_MAP.get(fileType) as string : '',
fileId: fileIdString ?? '',
allowedOpenCount: allowedOpenCount,
waterMarkConfig: waterMarkConfig,
countdown: countdown,
}
return dlpGeneralInfo;
}
async parse(uri: string, ctxFilesDir: string): Promise<Result<FileMetaInfo>> {
let file: fs.File | undefined;
try {
file = fs.openSync(uri, fs.OpenMode.READ_ONLY);
const fileInfo = fs.statSync(file.fd);
this.fileSize = fileInfo.size;
let data = new ArrayBuffer(Constants.HEAD_LENGTH_IN_BYTE);
let option: ReadOptions = { offset: 0, length: Constants.HEAD_LENGTH_IN_BYTE };
fs.readSync(file.fd, data, option);
let buf = new Uint32Array(data, 0, Constants.HEAD_LENGTH_IN_U32);
let cert = new ArrayBuffer(buf[Constants.CERT_SIZE]);
let certOffset = Constants.CERT_OFFSET_4GB * buf[Constants.CERT_OFFSET + 1] + buf[Constants.CERT_OFFSET];
option = { offset: certOffset, length: buf[Constants.CERT_SIZE] };
fs.readSync(file.fd, cert, option);
const textDecoder: util.TextDecoder = util.TextDecoder.create('utf-8');
const certString: string = textDecoder.decodeToString(new Uint8Array(cert), { stream: false });
const certInfo: CertInfo = JSON.parse(certString) as CertInfo;
const accountType = certInfo.accountType;
if (!checkValidAccountType(accountType)) {
HiLog.error(TAG, 'checkValidAccountType invalid');
return ResultMsg.getErrMsg(Constants.ERR_JS_NOT_DLP_FILE);
}
GlobalContext.store('accountType', accountType);
const dlpGeneralInfo: DlpGeneralInfo = await this.parseDlpGeneralInfo(buf, file);
this.metaInfo = {
accountType: accountType,
fileType: dlpGeneralInfo.realFileType,
ownerAccount: dlpGeneralInfo.contactAccount,
fileSize: this.fileSize,
fileId: dlpGeneralInfo.fileId,
allowedOpenCount: dlpGeneralInfo.allowedOpenCount,
waterMarkConfig: dlpGeneralInfo.waterMarkConfig,
countdown: dlpGeneralInfo.countdown,
};
return ResultMsg.buildSuccess(this.metaInfo);
} catch (error) {
HiLog.wrapError(TAG, error, 'Error parse rawfile');
return ResultMsg.getErrMsg(Constants.ERR_JS_NOT_DLP_FILE);
} finally {
FileUtil.closeSync(file);
}
}
}
class FileFormatDetector {
static async detect(fd: number): Promise<Result<FileParseInfo>> {
HiLog.info(TAG, 'start detect');
let data = new ArrayBuffer(Constants.HEAD_LENGTH_IN_BYTE);
let option: ReadOptions = { offset: 0, length: Constants.HEAD_LENGTH_IN_BYTE };
let fileSize = 0;
try {
fs.readSync(fd, data, option);
const fileInfo = fs.statSync(fd);
fileSize = fileInfo.size;
} catch (error) {
HiLog.wrapError(TAG, error, 'FileFormatDetector error');
return ResultMsg.getErrMsg(Constants.ERR_CODE_OPEN_FILE_ERROR);
}
let buf = new Uint32Array(data, 0, Constants.HEAD_LENGTH_IN_U32);
if (buf && buf[0] === Constants.DLP_ZIP_MAGIC) {
HiLog.debug(TAG, 'FileFormatDetector zip');
const parseInfo: FileParseInfo = { parseType: FileParseType.ZIP, fileSize: fileSize };
return ResultMsg.buildSuccess(parseInfo);
}
if (buf && (buf[0] === Constants.DLP_RAW_MAGIC || (buf.length >= 3 && buf[2] === Constants.DLP_RAW_MAGIC))) {
HiLog.debug(TAG, 'FileFormatDetector raw');
const parseInfo: FileParseInfo = { parseType: FileParseType.RAW, fileSize: fileSize };
return ResultMsg.buildSuccess(parseInfo);
}
HiLog.error(TAG, 'FileFormatDetector not dlp file');
return ResultMsg.getErrMsg(Constants.ERR_JS_NOT_DLP_FILE);
}
}
export class FileParseFactory {
static async createFileParse(openDlpFileData: OpenDlpFileData): Promise<Result<FileParseBase>> {
HiLog.debug(TAG, 'CreateFileParse');
const uri = openDlpFileData.uri;
let getFileFdRet = getFileFd(uri, fs.OpenMode.READ_WRITE);
if (getFileFdRet.errcode !== Constants.ERR_CODE_SUCCESS || !getFileFdRet.result) {
HiLog.info(TAG, 'getFileFd with READ_WRITE failed, try to READ_ONLY.');
getFileFdRet = getFileFd(uri, fs.OpenMode.READ_ONLY);
if (getFileFdRet.errcode !== Constants.ERR_CODE_SUCCESS || !getFileFdRet.result) {
HiLog.error(TAG, 'getFileFd with READ_ONLY error.');
return ResultMsg.buildMsg(getFileFdRet.errcode, getFileFdRet.errmsg);
}
}
let dlpFd = getFileFdRet.result;
const format = await FileFormatDetector.detect(dlpFd);
if (format.errcode !== Constants.ERR_CODE_SUCCESS || !format.result) {
FileUtil.closeSync(dlpFd);
HiLog.error(TAG, 'detect failed');
return ResultMsg.buildMsg(format.errcode, format.errmsg);
}
FileUtil.closeSync(dlpFd);
switch (format.result.parseType) {
case FileParseType.ZIP:
openDlpFileData.fileParse = FileParseType.ZIP;
return ResultMsg.buildSuccess(new ZipParse(format.result.fileSize));
case FileParseType.RAW:
openDlpFileData.fileParse = FileParseType.RAW;
return ResultMsg.buildSuccess(new RawParse(format.result.fileSize));
default:
HiLog.error(TAG, 'createFileParse error');
return ResultMsg.getErrMsg(Constants.ERR_JS_NOT_DLP_FILE);
}
}
}