msoffice_biff.h (12145B)
1 /* 2 This file is part of libextractor. 3 Copyright (C) 2026 Vidyut Samanta and Christian Grothoff 4 5 libextractor is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published 7 by the Free Software Foundation; either version 3, or (at your 8 option) any later version. 9 10 libextractor is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with libextractor; see the file COPYING. If not, write to the 17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 Boston, MA 02110-1301, USA. 19 */ 20 /** 21 * @file plugins/msoffice_biff.h 22 * @brief extract user names from the "File Protection Block" of an 23 * Excel BIFF stream 24 * @author Christian Grothoff 25 * 26 * Excel stores the name of the user that saved the file in the 27 * WRITEACCESS record, and the name of the user that write-protected 28 * the file in the FILESHARING record. Both are part of the "File 29 * Protection Block" at the very beginning of the workbook globals 30 * substream (section 4.19 of OpenOffice.org's documentation of the 31 * Microsoft Excel file format, [MS-XLS] sections 2.4.349 and 2.4.117). 32 * Neither is covered by the OLE2 property sets, so this information is 33 * missed by extractors that only look at (Document)SummaryInformation. 34 * 35 * The very same substream exists both inside the "Workbook" (BIFF8) or 36 * "Book" (BIFF5) stream of an OLE2 container *and* as the entire 37 * content of a pre-OLE2 Excel file (BIFF2-BIFF4 wrote the record 38 * stream straight to disk). This header is therefore shared between 39 * the `ole2' plugin (which has an OLE2 container to open first) and 40 * the `msoffice' plugin (which handles the bare streams). 41 */ 42 #ifndef MSOFFICE_BIFF_H 43 #define MSOFFICE_BIFF_H 44 45 #include "platform.h" 46 #include "extractor.h" 47 #include "convert.h" 48 49 50 /** 51 * BIFF record identifier for FILESHARING. 52 */ 53 #define BIFF_ID_FILESHARING 0x005B 54 55 /** 56 * BIFF record identifier for WRITEACCESS. 57 */ 58 #define BIFF_ID_WRITEACCESS 0x005C 59 60 /** 61 * BIFF record identifier for CODEPAGE. 62 */ 63 #define BIFF_ID_CODEPAGE 0x0042 64 65 /** 66 * BIFF record identifier for EOF. 67 */ 68 #define BIFF_ID_EOF 0x000A 69 70 /** 71 * Longest user name we are willing to report. The format allows for 72 * 109 characters; we accept a bit more to tolerate sloppy writers, but 73 * refuse to allocate unbounded amounts of memory. 74 */ 75 #define BIFF_MAX_NAME_LEN 1024 76 77 /** 78 * Maximum number of records we inspect before giving up. The File 79 * Protection Block is at the very start of the globals substream, so 80 * a small limit is plenty and bounds the work done on hostile input. 81 */ 82 #define BIFF_MAX_RECORDS 256 83 84 85 /** 86 * Read a 16 bit little endian value. 87 * 88 * @param p buffer to read from, must have at least 2 valid bytes 89 * @return the value read 90 */ 91 static uint16_t 92 biff_le16 (const unsigned char *p) 93 { 94 return (uint16_t) (p[0] | (p[1] << 8)); 95 } 96 97 98 /** 99 * Determine the BIFF version a stream is written in from its leading 100 * BOF record. 101 * 102 * @param rid record identifier of the first record 103 * @param data payload of the first record 104 * @param size number of bytes in @a data 105 * @return 2, 3, 4, 5 or 8, or 0 if @a rid is not a BOF record 106 */ 107 static unsigned int 108 biff_version_from_bof (uint16_t rid, 109 const unsigned char *data, 110 size_t size) 111 { 112 switch (rid) 113 { 114 case 0x0009: 115 return 2; 116 case 0x0209: 117 return 3; 118 case 0x0409: 119 return 4; 120 case 0x0809: 121 /* BIFF5 and BIFF8 share the record identifier and are told apart 122 by the version field of the record. */ 123 if (size < 2) 124 return 0; 125 return (biff_le16 (data) >= 0x0600) ? 8 : 5; 126 default: 127 return 0; 128 } 129 } 130 131 132 /** 133 * Map the value of a CODEPAGE record to an iconv character set name. 134 * 135 * @param cv value of the CODEPAGE record 136 * @return name of the character set, NULL if unknown 137 */ 138 static const char * 139 biff_codepage_to_charset (uint16_t cv) 140 { 141 switch (cv) 142 { 143 case 367: 144 return "ASCII"; 145 case 437: 146 return "CP437"; 147 case 737: 148 return "CP737"; 149 case 775: 150 return "CP775"; 151 case 850: 152 return "CP850"; 153 case 852: 154 return "CP852"; 155 case 855: 156 return "CP855"; 157 case 857: 158 return "CP857"; 159 case 860: 160 return "CP860"; 161 case 861: 162 return "CP861"; 163 case 862: 164 return "CP862"; 165 case 863: 166 return "CP863"; 167 case 864: 168 return "CP864"; 169 case 865: 170 return "CP865"; 171 case 866: 172 return "CP866"; 173 case 869: 174 return "CP869"; 175 case 874: 176 return "CP874"; 177 case 932: 178 return "CP932"; 179 case 936: 180 return "CP936"; 181 case 949: 182 return "CP949"; 183 case 950: 184 return "CP950"; 185 case 1200: 186 return "UTF-16LE"; 187 case 1250: 188 return "CP1250"; 189 case 1251: 190 return "CP1251"; 191 case 32769: /* BIFF2-BIFF3 wrote this for "ANSI" */ 192 case 1252: 193 return "CP1252"; 194 case 1253: 195 return "CP1253"; 196 case 1254: 197 return "CP1254"; 198 case 1255: 199 return "CP1255"; 200 case 1256: 201 return "CP1256"; 202 case 1257: 203 return "CP1257"; 204 case 1258: 205 return "CP1258"; 206 case 10000: 207 case 32768: 208 return "MACINTOSH"; 209 default: 210 return NULL; 211 } 212 } 213 214 215 /** 216 * Strip trailing spaces (used as padding by WRITEACCESS) and leading 217 * and trailing white space from @a s, in place. 218 * 219 * @param s 0-terminated string to trim 220 * @return pointer into @a s to the first non-blank character 221 */ 222 static char * 223 biff_trim (char *s) 224 { 225 size_t len = strlen (s); 226 227 while ( (0 < len) && 228 (isspace ((unsigned char) s[len - 1])) ) 229 s[--len] = '\0'; 230 while (isspace ((unsigned char) s[0])) 231 s++; 232 return s; 233 } 234 235 236 /** 237 * Decode a BIFF string into UTF-8. 238 * 239 * For BIFF2-BIFF5 this is a "byte string with 8-bit string length" 240 * (section 2.5.2): one length byte followed by that many characters in 241 * the workbook's code page. For BIFF8 it is a "Unicode string with 242 * 16-bit string length" (section 2.5.3): a 16 bit character count, a 243 * flags byte and then either 8 bit (compressed) or 16 bit characters, 244 * possibly preceded by rich text and phonetic size fields. 245 * 246 * @param data record payload 247 * @param size number of bytes in @a data 248 * @param off offset of the string within @a data 249 * @param biff BIFF version of the stream 250 * @param charset character set to assume for 8 bit characters 251 * @return UTF-8 string to be freed by the caller, NULL on error 252 */ 253 static char * 254 biff_decode_string (const unsigned char *data, 255 size_t size, 256 size_t off, 257 unsigned int biff, 258 const char *charset) 259 { 260 size_t cch; 261 size_t need; 262 263 if (off >= size) 264 return NULL; 265 if (8 > biff) 266 { 267 cch = data[off++]; 268 if ( (0 == cch) || 269 (BIFF_MAX_NAME_LEN < cch) || 270 (off + cch > size) ) 271 return NULL; 272 return EXTRACTOR_common_convert_to_utf8 ((const char *) &data[off], 273 cch, 274 charset); 275 } 276 if (off + 3 > size) 277 return NULL; 278 cch = biff_le16 (&data[off]); 279 { 280 unsigned char flags = data[off + 2]; 281 282 off += 3; 283 if (0 != (flags & 0x08)) 284 off += 2; /* cRun of a rich text string */ 285 if (0 != (flags & 0x04)) 286 off += 4; /* cbExtRst of an Asian phonetic string */ 287 if ( (0 == cch) || 288 (BIFF_MAX_NAME_LEN < cch) ) 289 return NULL; 290 need = (0 != (flags & 0x01)) ? 2 * cch : cch; 291 if ( (off > size) || 292 (off + need > size) ) 293 return NULL; 294 return EXTRACTOR_common_convert_to_utf8 ((const char *) &data[off], 295 need, 296 (0 != (flags & 0x01)) 297 ? "UTF-16LE" 298 : charset); 299 } 300 } 301 302 303 /** 304 * Determine the character set of a BIFF stream by locating its 305 * CODEPAGE record. The File Protection Block precedes CODEPAGE in the 306 * stream, so we have to look ahead before we can decode any of its 307 * strings. 308 * 309 * @param data the BIFF stream 310 * @param size number of bytes in @a data 311 * @return name of the character set, never NULL 312 */ 313 static const char * 314 biff_find_charset (const unsigned char *data, 315 size_t size) 316 { 317 size_t off = 0; 318 unsigned int i; 319 320 for (i = 0; i < BIFF_MAX_RECORDS; i++) 321 { 322 uint16_t rid; 323 uint16_t len; 324 325 if (off + 4 > size) 326 break; 327 rid = biff_le16 (&data[off]); 328 len = biff_le16 (&data[off + 2]); 329 if (off + 4 + (size_t) len > size) 330 break; 331 if (BIFF_ID_CODEPAGE == rid) 332 { 333 const char *cs; 334 335 if ( (2 <= len) && 336 (NULL != (cs = biff_codepage_to_charset (biff_le16 (&data[off 337 + 4])))) ) 338 return cs; 339 break; 340 } 341 if ( (BIFF_ID_EOF == rid) && 342 (0 < i) ) 343 break; 344 off += 4 + (size_t) len; 345 } 346 return "CP1252"; 347 } 348 349 350 /** 351 * Scan the globals substream of a BIFF stream and report the user 352 * names found in its File Protection Block. 353 * 354 * @param data the BIFF stream, starting at its BOF record 355 * @param size number of bytes in @a data 356 * @param plugin_name name to report to @a proc as the source plugin 357 * @param proc function to call on meta data found 358 * @param proc_cls closure for @a proc 359 * @return 0 to continue extracting, 1 if @a proc asked us to stop, 360 * -1 if @a data does not start with a BOF record 361 */ 362 static int 363 EXTRACTOR_msoffice_biff_extract (const unsigned char *data, 364 size_t size, 365 const char *plugin_name, 366 EXTRACTOR_MetaDataProcessor proc, 367 void *proc_cls) 368 { 369 const char *charset; 370 size_t off = 0; 371 unsigned int biff; 372 unsigned int i; 373 int ret = 0; 374 375 if (4 > size) 376 return -1; 377 biff = biff_version_from_bof (biff_le16 (data), 378 &data[4], 379 (size - 4 < (size_t) biff_le16 (&data[2])) 380 ? size - 4 381 : (size_t) biff_le16 (&data[2])); 382 if (0 == biff) 383 return -1; 384 charset = (8 == biff) ? "CP1252" : biff_find_charset (data, size); 385 for (i = 0; i < BIFF_MAX_RECORDS; i++) 386 { 387 uint16_t rid; 388 uint16_t len; 389 const unsigned char *rec; 390 char *name = NULL; 391 enum EXTRACTOR_MetaType type; 392 393 if (off + 4 > size) 394 break; 395 rid = biff_le16 (&data[off]); 396 len = biff_le16 (&data[off + 2]); 397 if (off + 4 + (size_t) len > size) 398 break; 399 rec = &data[off + 4]; 400 off += 4 + (size_t) len; 401 if ( (BIFF_ID_EOF == rid) && 402 (0 < i) ) 403 break; /* end of the globals substream */ 404 switch (rid) 405 { 406 case BIFF_ID_WRITEACCESS: 407 /* The user name that saved the file. Note that the record is 408 padded with spaces to a fixed size, and that the size differs 409 between BIFF versions (and between writers). */ 410 name = biff_decode_string (rec, len, 0, biff, charset); 411 type = EXTRACTOR_METATYPE_LAST_SAVED_BY; 412 break; 413 case BIFF_ID_FILESHARING: 414 /* The user name that write-protected the file, preceded by the 415 read-only recommendation flag and the password hash. */ 416 if (5 > len) 417 break; 418 name = biff_decode_string (rec, len, 4, biff, charset); 419 type = EXTRACTOR_METATYPE_CREATOR; 420 break; 421 default: 422 break; 423 } 424 if (NULL == name) 425 continue; 426 { 427 char *trimmed = biff_trim (name); 428 429 if ( ('\0' != trimmed[0]) && 430 (0 != proc (proc_cls, 431 plugin_name, 432 type, 433 EXTRACTOR_METAFORMAT_UTF8, 434 "text/plain", 435 trimmed, 436 strlen (trimmed) + 1)) ) 437 ret = 1; 438 } 439 free (name); 440 if (0 != ret) 441 break; 442 } 443 return ret; 444 } 445 446 447 #endif 448 /* end of msoffice_biff.h */