@xaypay/tui 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,10 @@ All notable changes to `@xaypay/tui` will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+
9
+ ## [0.3.2] - 2026-04-02
10
+ - **FILE** Refactor: pdf / heic / heif validation.
11
+
8
12
  ## [0.3.1] - 2026-03-19
9
13
 
10
14
  ### Added
package/dist/index.es.js CHANGED
@@ -2145,20 +2145,17 @@ const File = /*#__PURE__*/forwardRef(({
2145
2145
  const hasTable = names.has("1Table") || names.has("0Table");
2146
2146
  return hasWordDocument && hasTable ? Promise.resolve() : Promise.reject();
2147
2147
  } else if (type === 'pdf') {
2148
+ const text = new TextDecoder().decode(buffer);
2149
+ if (!text.includes("%%EOF")) return Promise.reject();
2150
+ if (!text.startsWith("%PDF-")) return Promise.reject();
2151
+ if (!text.includes("startxref")) return Promise.reject();
2148
2152
  if (dataView.byteLength < 8) {
2149
2153
  return Promise.reject();
2150
2154
  } else {
2151
2155
  if (dataView.getUint8(0) === 0x25 && dataView.getUint8(1) === 0x50 && dataView.getUint8(2) === 0x44 && dataView.getUint8(3) === 0x46) {
2152
2156
  const len = dataView.byteLength;
2153
2157
  let end = len - 1;
2154
- while (end >= 0 && (dataView.getUint8(end) === 0x0A ||
2155
- // \n
2156
- dataView.getUint8(end) === 0x0D ||
2157
- // \r
2158
- dataView.getUint8(end) === 0x20 ||
2159
- // space
2160
- dataView.getUint8(end) === 0x09 // tab
2161
- )) {
2158
+ while (end >= 0 && (dataView.getUint8(end) === 0x0A || dataView.getUint8(end) === 0x0D || dataView.getUint8(end) === 0x20 || dataView.getUint8(end) === 0x09)) {
2162
2159
  end--;
2163
2160
  }
2164
2161
  if (end >= 4 && dataView.getUint8(end - 4) === 0x25 && dataView.getUint8(end - 3) === 0x25 && dataView.getUint8(end - 2) === 0x45 && dataView.getUint8(end - 1) === 0x4F && dataView.getUint8(end) === 0x46) {
@@ -2171,23 +2168,57 @@ const File = /*#__PURE__*/forwardRef(({
2171
2168
  }
2172
2169
  }
2173
2170
  } else if (type === 'heic') {
2174
- if (dataView.byteLength < 12) {
2171
+ const length = dataView.byteLength;
2172
+ if (length < 32) {
2175
2173
  return Promise.reject();
2176
- } else {
2177
- if (dataView.getUint8(0) === 0x66 && dataView.getUint8(1) === 0x74 && dataView.getUint8(2) === 0x79 && dataView.getUint8(3) === 0x70) {
2178
- if (dataView.getUint8(dataView.length - 4) === 0x6D && dataView.getUint8(dataView.length - 3) === 0x64 && dataView.getUint8(dataView.length - 2) === 0x61 && dataView.getUint8(dataView.length - 1) === 0x74) {
2179
- const majorBrand = String.fromCharCode(dataView.getUint8(4), dataView.getUint8(5), dataView.getUint8(6), dataView.getUint8(7));
2180
- const heicIdentifiers = ['heic', 'heix', 'mif1', 'msf1', 'heif'];
2181
- if (heicIdentifiers.includes(majorBrand)) {
2182
- return Promise.resolve();
2183
- } else {
2184
- return Promise.reject();
2185
- }
2186
- } else {
2187
- return Promise.reject();
2188
- }
2174
+ }
2175
+
2176
+ // --- Check 'ftyp' at offset 4 ---
2177
+ if (dataView.getUint8(4) !== 0x66 ||
2178
+ // f
2179
+ dataView.getUint8(5) !== 0x74 ||
2180
+ // t
2181
+ dataView.getUint8(6) !== 0x79 ||
2182
+ // y
2183
+ dataView.getUint8(7) !== 0x70 // p
2184
+ ) {
2185
+ return Promise.reject();
2186
+ }
2187
+
2188
+ // --- Check major brand ---
2189
+ const majorBrand = String.fromCharCode(dataView.getUint8(8), dataView.getUint8(9), dataView.getUint8(10), dataView.getUint8(11));
2190
+ const validBrands = ['heic', 'heix', 'hevc', 'hevx', 'mif1', 'msf1', 'heif'];
2191
+ if (!validBrands.includes(majorBrand)) {
2192
+ return Promise.reject();
2193
+ }
2194
+
2195
+ // --- Scan boxes (basic ISO BMFF parsing) ---
2196
+ let offset = 0;
2197
+ let foundMeta = false;
2198
+ let foundMdat = false;
2199
+ while (offset + 8 <= length) {
2200
+ const size = dataView.getUint32(offset);
2201
+ const type = String.fromCharCode(dataView.getUint8(offset + 4), dataView.getUint8(offset + 5), dataView.getUint8(offset + 6), dataView.getUint8(offset + 7));
2202
+
2203
+ // Invalid size → corrupted file
2204
+ if (size < 8 || offset + size > length) {
2205
+ return Promise.reject();
2189
2206
  }
2207
+ if (type === "meta") foundMeta = true;
2208
+ if (type === "mdat") foundMdat = true;
2209
+ offset += size;
2210
+ }
2211
+
2212
+ // --- Require at least meta box ---
2213
+ if (!foundMeta) {
2214
+ return Promise.reject();
2215
+ }
2216
+
2217
+ // Optional but recommended
2218
+ if (!foundMdat) {
2219
+ return Promise.reject();
2190
2220
  }
2221
+ return Promise.resolve();
2191
2222
  } else {
2192
2223
  return Promise.reject();
2193
2224
  }
package/dist/index.js CHANGED
@@ -2176,20 +2176,17 @@ const File = /*#__PURE__*/React.forwardRef(({
2176
2176
  const hasTable = names.has("1Table") || names.has("0Table");
2177
2177
  return hasWordDocument && hasTable ? Promise.resolve() : Promise.reject();
2178
2178
  } else if (type === 'pdf') {
2179
+ const text = new TextDecoder().decode(buffer);
2180
+ if (!text.includes("%%EOF")) return Promise.reject();
2181
+ if (!text.startsWith("%PDF-")) return Promise.reject();
2182
+ if (!text.includes("startxref")) return Promise.reject();
2179
2183
  if (dataView.byteLength < 8) {
2180
2184
  return Promise.reject();
2181
2185
  } else {
2182
2186
  if (dataView.getUint8(0) === 0x25 && dataView.getUint8(1) === 0x50 && dataView.getUint8(2) === 0x44 && dataView.getUint8(3) === 0x46) {
2183
2187
  const len = dataView.byteLength;
2184
2188
  let end = len - 1;
2185
- while (end >= 0 && (dataView.getUint8(end) === 0x0A ||
2186
- // \n
2187
- dataView.getUint8(end) === 0x0D ||
2188
- // \r
2189
- dataView.getUint8(end) === 0x20 ||
2190
- // space
2191
- dataView.getUint8(end) === 0x09 // tab
2192
- )) {
2189
+ while (end >= 0 && (dataView.getUint8(end) === 0x0A || dataView.getUint8(end) === 0x0D || dataView.getUint8(end) === 0x20 || dataView.getUint8(end) === 0x09)) {
2193
2190
  end--;
2194
2191
  }
2195
2192
  if (end >= 4 && dataView.getUint8(end - 4) === 0x25 && dataView.getUint8(end - 3) === 0x25 && dataView.getUint8(end - 2) === 0x45 && dataView.getUint8(end - 1) === 0x4F && dataView.getUint8(end) === 0x46) {
@@ -2202,23 +2199,57 @@ const File = /*#__PURE__*/React.forwardRef(({
2202
2199
  }
2203
2200
  }
2204
2201
  } else if (type === 'heic') {
2205
- if (dataView.byteLength < 12) {
2202
+ const length = dataView.byteLength;
2203
+ if (length < 32) {
2206
2204
  return Promise.reject();
2207
- } else {
2208
- if (dataView.getUint8(0) === 0x66 && dataView.getUint8(1) === 0x74 && dataView.getUint8(2) === 0x79 && dataView.getUint8(3) === 0x70) {
2209
- if (dataView.getUint8(dataView.length - 4) === 0x6D && dataView.getUint8(dataView.length - 3) === 0x64 && dataView.getUint8(dataView.length - 2) === 0x61 && dataView.getUint8(dataView.length - 1) === 0x74) {
2210
- const majorBrand = String.fromCharCode(dataView.getUint8(4), dataView.getUint8(5), dataView.getUint8(6), dataView.getUint8(7));
2211
- const heicIdentifiers = ['heic', 'heix', 'mif1', 'msf1', 'heif'];
2212
- if (heicIdentifiers.includes(majorBrand)) {
2213
- return Promise.resolve();
2214
- } else {
2215
- return Promise.reject();
2216
- }
2217
- } else {
2218
- return Promise.reject();
2219
- }
2205
+ }
2206
+
2207
+ // --- Check 'ftyp' at offset 4 ---
2208
+ if (dataView.getUint8(4) !== 0x66 ||
2209
+ // f
2210
+ dataView.getUint8(5) !== 0x74 ||
2211
+ // t
2212
+ dataView.getUint8(6) !== 0x79 ||
2213
+ // y
2214
+ dataView.getUint8(7) !== 0x70 // p
2215
+ ) {
2216
+ return Promise.reject();
2217
+ }
2218
+
2219
+ // --- Check major brand ---
2220
+ const majorBrand = String.fromCharCode(dataView.getUint8(8), dataView.getUint8(9), dataView.getUint8(10), dataView.getUint8(11));
2221
+ const validBrands = ['heic', 'heix', 'hevc', 'hevx', 'mif1', 'msf1', 'heif'];
2222
+ if (!validBrands.includes(majorBrand)) {
2223
+ return Promise.reject();
2224
+ }
2225
+
2226
+ // --- Scan boxes (basic ISO BMFF parsing) ---
2227
+ let offset = 0;
2228
+ let foundMeta = false;
2229
+ let foundMdat = false;
2230
+ while (offset + 8 <= length) {
2231
+ const size = dataView.getUint32(offset);
2232
+ const type = String.fromCharCode(dataView.getUint8(offset + 4), dataView.getUint8(offset + 5), dataView.getUint8(offset + 6), dataView.getUint8(offset + 7));
2233
+
2234
+ // Invalid size → corrupted file
2235
+ if (size < 8 || offset + size > length) {
2236
+ return Promise.reject();
2220
2237
  }
2238
+ if (type === "meta") foundMeta = true;
2239
+ if (type === "mdat") foundMdat = true;
2240
+ offset += size;
2241
+ }
2242
+
2243
+ // --- Require at least meta box ---
2244
+ if (!foundMeta) {
2245
+ return Promise.reject();
2246
+ }
2247
+
2248
+ // Optional but recommended
2249
+ if (!foundMdat) {
2250
+ return Promise.reject();
2221
2251
  }
2252
+ return Promise.resolve();
2222
2253
  } else {
2223
2254
  return Promise.reject();
2224
2255
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xaypay/tui",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.es.js",