Skip to content

Commit

Permalink
feat: header 파싱 새 구조 변경
Browse files Browse the repository at this point in the history
  • Loading branch information
hahnlee committed Apr 10, 2024
1 parent 73b7083 commit d3a4070
Show file tree
Hide file tree
Showing 6 changed files with 235 additions and 157 deletions.
4 changes: 0 additions & 4 deletions packages/parser/src/__tests__/parse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,4 @@ describe('parse', () => {
assert.strictEqual(hwpDocument.sections[0].width, 59528)
assert.strictEqual(hwpDocument.sections[0].height, 84188)
})

it('should parse signature', () => {
assert.strictEqual(hwpDocument.header.signature, 'HWP Document File')
})
})
2 changes: 1 addition & 1 deletion packages/parser/src/doc-info-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export class DocInfoParser {

if (
properties.compress === BinDataCompress.COMPRESS
|| (properties.compress === BinDataCompress.DEFAULT && this.header.properties.compressed)
|| (properties.compress === BinDataCompress.DEFAULT && this.header.flags.compressed)
) {
const data = inflate(Uint8Array.from(payload), { windowBits: -15 })
this.result.binData.push(new BinData(properties, extension, data))
Expand Down
18 changes: 4 additions & 14 deletions packages/parser/src/models/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,9 @@ import { HWPHeader } from './header.js'
import { Section } from './section.js'

export class HWPDocument {
header: HWPHeader

info: DocInfo

sections: Section[]

constructor(
header: HWPHeader,
info: DocInfo,
sections: Section[],
) {
this.header = header
this.info = info
this.sections = sections
}
public header: HWPHeader,
public info: DocInfo,
public sections: Section[],
) {}
}
253 changes: 191 additions & 62 deletions packages/parser/src/models/header.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Copyright Han Lee <hanlee.dev@gmail.com> and other contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* 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
*
Expand All @@ -14,77 +14,206 @@
* limitations under the License.
*/

import { HWPVersion } from './version.js'

interface Properties {
/** 압축 여부 */
compressed: boolean

/** 암호 설정 여부 */
encrypted: boolean

/** 배포용 문서 여부 */
distribution: boolean

/** 스크립트 저장 여부 */
script: boolean

/** DRM 보안 문서 여부 */
drm: boolean

/** XMLTemplate 스토리지 존재 여부 */
hasXmlTemplateStorage: boolean
import { find, type CFB$Container } from 'cfb'

/** 문서 이력 관리 존재 여부 */
vcs: boolean

/** 전자 서명 정보 존재 여부 */
hasElectronicSignatureInfomation: boolean

/** 공인 인증서 암호화 여부 */
certificateEncryption: boolean

/** 전자 서명 예비 저장 여부 */
prepareSignature: boolean

/** 공인 인증서 DRM 보안 문서 여부 */
certificateDRM: boolean

/** CCL 문서 여부 */
ccl: boolean

/** 모바일 최적화 여부 */
mobile: boolean
import { HWPVersion } from './version.js'
import { ByteReader } from '../utils/byte-reader.js'
import { getBitValue } from '../utils/bit-utils.js'

/** 개인 정보 보안 문서 여부 */
isPrivacySecurityDocument: boolean
const SIGNATURE = 'HWP Document File'
const FILE_HEADER_BYTES = 256

/** 변경 추적 문서 여부 */
trackChanges: boolean
export class HWPHeader {
constructor(
public version: HWPVersion,
public flags: Flags,
public license: License,
public encryptVersion: EncryptVersion,
public kogl: KOGL,
public reserved: ArrayBuffer
) {}

static fromCfbContainer(container: CFB$Container): HWPHeader {
const fileHeader = find(container, 'FileHeader')

if (!fileHeader) {
throw new Error('Cannot find FileHeader')
}

const reader = new ByteReader(Uint8Array.from(fileHeader.content).buffer)
return HWPHeader.fromByteReader(reader)
}

/** 공공누리(KOGL) 저작권 문서 */
kogl: boolean
static fromByteReader(reader: ByteReader): HWPHeader {
if (reader.length() !== FILE_HEADER_BYTES) {
throw new Error(
`FileHeader must be ${FILE_HEADER_BYTES} bytes, Received: ${reader.length()}`
)
}

const signature = new TextDecoder().decode(reader.read(17))
if (signature !== SIGNATURE) {
throw new Error(
`hwp file's signature should be ${SIGNATURE}. Received version: ${signature}`
)
}

// Reserved
reader.read(15)

const revision = reader.readUInt8()
const build = reader.readUInt8()
const minor = reader.readUInt8()
const major = reader.readUInt8()
const version = new HWPVersion(major, minor, build, revision)

const flags = Flags.fromBits(reader.readUInt32())
const license = License.fromBits(reader.readUInt32())
const encryptVersion = mapEncryptVersion(reader.readInt32())
const kogl = mapKogl(reader.readUInt8())
const reserved = reader.read(207)

if (!reader.isEOF()) {
throw new Error('FileHeader is not EOF')
}

return new HWPHeader(
version,
flags,
license,
encryptVersion,
kogl,
reserved
)
}
}

/** 비디오 컨트롤 포함 여부 */
hasVideoControl: boolean
export class Flags {
constructor(
/** 압축 여부 */
public compressed: boolean,
/** 암호 설정 여부 */
public encrypted: boolean,
/** 배포용 문서 여부 */
public distributed: boolean,
/** 스크립트 저장 여부 */
public script: boolean,
/** DRM 보안 문서 여부 */
public drm: boolean,
/** XMLTemplate 스토리지 존재 여부 */
public xmlTemplateStorage: boolean,
/** 문서 이력 관리 존재 여부 */
public vcs: boolean,
/** 전자 서명 정보 존재 여부 */
public electronicSignatured: boolean,
/** 공인 인증서 암호화 여부 */
public certificateEncrypted: boolean,
/** 전자 서명 예비 저장 여부 */
public prepareSignatured: boolean,
/** 공인 인증서 DRM 보안 문서 여부 */
public certificateDrm: boolean,
/** CCL 문서 여부 */
public ccl: boolean,
/** 모바일 최적화 여부 */
public mobileOptimized: boolean,
/** 개인 정보 보안 문서 여부 */
public privacySecurityDocument: boolean,
/** 변경 추적 문서 여부 */
public trackChanges: boolean,
/** 공공누리(KOGL) 저작권 문서 */
public kogl: boolean,
/** 비디오 컨트롤 포함 여부 */
public videoControl: boolean,
/** 차례 필드 컨트롤 포함 여부 */
public orderFieldControl: boolean
) {}

static fromBits(bits: number): Flags {
return new Flags(
Boolean(getBitValue(bits, 0)),
Boolean(getBitValue(bits, 1)),
Boolean(getBitValue(bits, 2)),
Boolean(getBitValue(bits, 3)),
Boolean(getBitValue(bits, 4)),
Boolean(getBitValue(bits, 5)),
Boolean(getBitValue(bits, 6)),
Boolean(getBitValue(bits, 7)),
Boolean(getBitValue(bits, 8)),
Boolean(getBitValue(bits, 9)),
Boolean(getBitValue(bits, 10)),
Boolean(getBitValue(bits, 11)),
Boolean(getBitValue(bits, 12)),
Boolean(getBitValue(bits, 13)),
Boolean(getBitValue(bits, 14)),
Boolean(getBitValue(bits, 15)),
Boolean(getBitValue(bits, 16)),
Boolean(getBitValue(bits, 17))
)
}
}

/** 차례 필드 컨트롤 포함 여부 */
hasOrderFieldControl: boolean
export class License {
constructor(
public ccl: boolean,
public replicationRestrictions: boolean,
public replicationAlike: boolean,
public reserved: number
) {}

static fromBits(bits: number): License {
return new License(
Boolean(getBitValue(bits, 0)),
Boolean(getBitValue(bits, 1)),
Boolean(getBitValue(bits, 2)),
getBitValue(bits, 3, 32)
)
}
}

/**
* @see https://github.com/hahnlee/hwp.js/blob/master/docs/hwp/5.0/FileHeader.md
*/
export class HWPHeader {
version: HWPVersion
export enum EncryptVersion {
None,
/** 한/글 2.5 버전 이하 */
HWP2_5,
/** 한/글 3.0 버전 Enhanced */
HWP3Enhanced,
/** 한/글 3.0 버전 Old */
HWP3Old,
/** 한/글 7.0 버전 이후 */
HWP7,
}

signature: string
function mapEncryptVersion(byte: number): EncryptVersion {
switch (byte) {
case 0:
return EncryptVersion.None
case 1:
return EncryptVersion.HWP2_5
case 2:
return EncryptVersion.HWP3Enhanced
case 3:
return EncryptVersion.HWP3Old
case 4:
return EncryptVersion.HWP7
default:
throw new Error(`Unknown encrypt version: ${byte}`)
}
}

properties: Properties
export enum KOGL {
None,
KOR = 6,
US = 15,
}

constructor(version: HWPVersion, signature: string, properties: Properties) {
this.version = version
this.signature = signature
this.properties = properties
function mapKogl(byte: number): KOGL {
switch (byte) {
case 0:
return KOGL.None
case 6:
return KOGL.KOR
case 15:
return KOGL.US
default:
throw new Error(`Unknown KOGL: ${byte}`)
}
}
Loading

0 comments on commit d3a4070

Please sign in to comment.