Commit f9086ed4302e52d642bad61a3d95bff8fb558616
Committed by
GitHub
Merge pull request #314 from Feandil/issue_313
WIP: oleform: add support for other types of controls
Showing
4 changed files
with
291 additions
and
77 deletions
oletools/oleform.py
| @@ -24,6 +24,11 @@ class Mask(object): | @@ -24,6 +24,11 @@ class Mask(object): | ||
| 24 | def __getitem__(self, key): | 24 | def __getitem__(self, key): |
| 25 | return self._val[self._names.index(key)] | 25 | return self._val[self._names.index(key)] |
| 26 | 26 | ||
| 27 | + def consume(self, stream, props): | ||
| 28 | + for (name, size) in props: | ||
| 29 | + if self[name]: | ||
| 30 | + stream.read(size) | ||
| 31 | + | ||
| 27 | class FormPropMask(Mask): | 32 | class FormPropMask(Mask): |
| 28 | """FormPropMask: [MS-OFORMS] 2.2.10.2""" | 33 | """FormPropMask: [MS-OFORMS] 2.2.10.2""" |
| 29 | _size = 28 | 34 | _size = 28 |
| @@ -52,12 +57,60 @@ class MorphDataPropMask(Mask): | @@ -52,12 +57,60 @@ class MorphDataPropMask(Mask): | ||
| 52 | 'fBorderColor', 'fSpecialEffect', 'fMouseIcon', 'fPicture', 'fAccelerator', | 57 | 'fBorderColor', 'fSpecialEffect', 'fMouseIcon', 'fPicture', 'fAccelerator', |
| 53 | 'UnusedBits2', 'Reserved', 'fGroupName'] | 58 | 'UnusedBits2', 'Reserved', 'fGroupName'] |
| 54 | 59 | ||
| 60 | +class ImagePropMask(Mask): | ||
| 61 | + """ImagePropMask: [MS-OFORMS] 2.2.3.2""" | ||
| 62 | + _size = 15 | ||
| 63 | + _names = ['UnusedBits1_1', 'UnusedBits1_2', 'fAutoSize', 'fBorderColor', 'fBackColor', | ||
| 64 | + 'fBorderStyle', 'fMousePointer', 'fPictureSizeMode', 'fSpecialEffect', 'fSize', | ||
| 65 | + 'fPicture', 'fPictureAlignment', 'fPictureTiling', 'fVariousPropertyBits', | ||
| 66 | + 'fMouseIcon'] | ||
| 67 | + | ||
| 68 | +class CommandButtonPropMask(Mask): | ||
| 69 | + """CommandButtonPropMask: [MS-OFORMS] 2.2.1.2""" | ||
| 70 | + _size = 11 | ||
| 71 | + _names = ['fForeColor', 'fBackColor', 'fVariousPropertyBits', 'fCaption', 'fPicturePosition', | ||
| 72 | + 'fSize', 'fMousePointer', 'fPicture', 'fAccelerator', 'fTakeFocusOnClick', | ||
| 73 | + 'fMouseIcon'] | ||
| 74 | + | ||
| 75 | +class SpinButtonPropMask(Mask): | ||
| 76 | + """SpinButtonPropMask: [MS-OFORMS] 2.2.8.2""" | ||
| 77 | + _size = 15 | ||
| 78 | + _names = ['fForeColor', 'fBackColor', 'fVariousPropertyBits', 'fSize', 'UnusedBits1', | ||
| 79 | + 'fMin', 'fMax', 'fPosition', 'fPrevEnabled', 'fNextEnabled', 'fSmallChange', | ||
| 80 | + 'fOrientation', 'fDelay', 'fMouseIcon', 'fMousePointer'] | ||
| 81 | + | ||
| 82 | +class TabStripPropMask(Mask): | ||
| 83 | + """TabStripPropMask: [MS-OFORMS] 2.2.9.2""" | ||
| 84 | + _size = 25 | ||
| 85 | + _names = ['fListIndex', 'fBackColor', 'fForeColor', 'Unused1', 'fSize', 'fItems', | ||
| 86 | + 'fMousePointer', 'Unused2', 'fTabOrientation', 'fTabStyle', 'fMultiRow', | ||
| 87 | + 'fTabFixedWidth', 'fTabFixedHeight', 'fTooltips', 'Unused3', 'fTipStrings', | ||
| 88 | + 'Unused4', 'fNames', 'fVariousPropertyBits', 'fNewVersion', 'fTabsAllocated', | ||
| 89 | + 'fTags', 'fTabData', 'fAccelerator', 'fMouseIcon'] | ||
| 90 | + | ||
| 91 | +class LabelPropMask(Mask): | ||
| 92 | + """LabelPropMask: [MS-OFORMS] 2.2.4.2""" | ||
| 93 | + _size = 13 | ||
| 94 | + _names = ['fForeColor', 'fBackColor', 'fVariousPropertyBits', 'fCaption', | ||
| 95 | + 'fPicturePosition', 'fSize', 'fMousePointer', 'fBorderColor', 'fBorderStyle', | ||
| 96 | + 'fSpecialEffect', 'fPicture', 'fAccelerator', 'fMouseIcon'] | ||
| 97 | + | ||
| 98 | +class ScrollBarPropMask(Mask): | ||
| 99 | + """ScrollBarPropMask: [MS-OFORMS] 2.2.7.2""" | ||
| 100 | + _size = 17 | ||
| 101 | + _names = ['fForeColor', 'fBackColor', 'fVariousPropertyBits', 'fSize', 'fMousePointer', | ||
| 102 | + 'fMin', 'fMax', 'fPosition', 'UnusedBits1', 'fPrevEnabled', 'fNextEnabled', | ||
| 103 | + 'fSmallChange', 'fLargeChange', 'fOrientation', 'fProportionalThumb', | ||
| 104 | + 'fDelay', 'fMouseIcon'] | ||
| 105 | + | ||
| 55 | class ExtendedStream(object): | 106 | class ExtendedStream(object): |
| 56 | def __init__(self, stream, path): | 107 | def __init__(self, stream, path): |
| 57 | self._pos = 0 | 108 | self._pos = 0 |
| 58 | self._jumps = [] | 109 | self._jumps = [] |
| 59 | self._stream = stream | 110 | self._stream = stream |
| 60 | self._path = path | 111 | self._path = path |
| 112 | + self._padding = False | ||
| 113 | + self._pad_start = 0 | ||
| 61 | 114 | ||
| 62 | @classmethod | 115 | @classmethod |
| 63 | def open(cls, ole_file, path): | 116 | def open(cls, ole_file, path): |
| @@ -68,31 +121,55 @@ class ExtendedStream(object): | @@ -68,31 +121,55 @@ class ExtendedStream(object): | ||
| 68 | # print('declared size: %d' % ole_file.get_size(path)) | 121 | # print('declared size: %d' % ole_file.get_size(path)) |
| 69 | return cls(stream, path) | 122 | return cls(stream, path) |
| 70 | 123 | ||
| 71 | - def read(self, size): | 124 | + def _read(self, size): |
| 72 | self._pos += size | 125 | self._pos += size |
| 73 | return self._stream.read(size) | 126 | return self._stream.read(size) |
| 74 | 127 | ||
| 128 | + def _pad(self, start, size=4): | ||
| 129 | + offset = (self._pos - start) % size | ||
| 130 | + if offset: | ||
| 131 | + self._read(size - offset) | ||
| 132 | + | ||
| 133 | + def read(self, size): | ||
| 134 | + if self._padding: | ||
| 135 | + self._pad(self._pad_start, size) | ||
| 136 | + return self._read(size) | ||
| 137 | + | ||
| 75 | def will_jump_to(self, size): | 138 | def will_jump_to(self, size): |
| 76 | - self._next_jump = (True, size) | 139 | + self._next_jump = ('jump', (self._pos, size)) |
| 140 | + return self | ||
| 141 | + | ||
| 142 | + def will_pad(self): | ||
| 143 | + self._next_jump = ('pad', self._pos) | ||
| 77 | return self | 144 | return self |
| 78 | 145 | ||
| 79 | - def will_pad(self, pad=4): | ||
| 80 | - self._next_jump = (False, pad) | 146 | + def padded_struct(self): |
| 147 | + self._next_jump = ('padded', (self._padding, self._pad_start)) | ||
| 148 | + self._padding = True | ||
| 149 | + self._pad_start = self._pos | ||
| 81 | return self | 150 | return self |
| 82 | 151 | ||
| 83 | def __enter__(self): | 152 | def __enter__(self): |
| 84 | - (jump_type, size) = self._next_jump | ||
| 85 | - self._jumps.append((self._pos, jump_type, size)) | 153 | + assert(self._next_jump) |
| 154 | + self._jumps.append(self._next_jump) | ||
| 155 | + self._next_jump = None | ||
| 86 | 156 | ||
| 87 | def __exit__(self, exc_type, exc_value, traceback): | 157 | def __exit__(self, exc_type, exc_value, traceback): |
| 88 | if exc_type is None: | 158 | if exc_type is None: |
| 89 | - (start, jump_type, size) = self._jumps.pop() | ||
| 90 | - if jump_type: | ||
| 91 | - self.read(size - (self._pos - start)) | ||
| 92 | - else: | ||
| 93 | - align = (self._pos - start) % size | ||
| 94 | - if align: | ||
| 95 | - self.read(size - align) | 159 | + (jump_type, data) = self._jumps.pop() |
| 160 | + if jump_type == 'jump': | ||
| 161 | + (start, size) = data | ||
| 162 | + consummed = self._pos - start | ||
| 163 | + if consummed > size: | ||
| 164 | + self.raise_error('Bad jump: too much read ({0} > {1})'.format(consummed, size)) | ||
| 165 | + self.read(size - consummed) | ||
| 166 | + elif jump_type == 'pad': | ||
| 167 | + self._pad(data) | ||
| 168 | + elif jump_type == 'padded': | ||
| 169 | + (prev_padding, prev_pad_start) = data | ||
| 170 | + self._pad(self._pad_start) | ||
| 171 | + self._padding = prev_padding | ||
| 172 | + self._pad_start = prev_pad_start | ||
| 96 | 173 | ||
| 97 | def unpacks(self, format, size): | 174 | def unpacks(self, format, size): |
| 98 | return struct.unpack(format, self.read(size)) | 175 | return struct.unpack(format, self.read(size)) |
| @@ -148,8 +225,6 @@ def consume_GuidAndPicture(stream): | @@ -148,8 +225,6 @@ def consume_GuidAndPicture(stream): | ||
| 148 | def consume_CountOfBytesWithCompressionFlag(stream): | 225 | def consume_CountOfBytesWithCompressionFlag(stream): |
| 149 | # CountOfBytesWithCompressionFlag or CountOfCharsWithCompressionFlag: [MS-OFORMS] 2.4.14.2 or 2.4.14.3 | 226 | # CountOfBytesWithCompressionFlag or CountOfCharsWithCompressionFlag: [MS-OFORMS] 2.4.14.2 or 2.4.14.3 |
| 150 | count = stream.unpack('<L', 4) | 227 | count = stream.unpack('<L', 4) |
| 151 | - if not count & 0x80000000 and count != 0: | ||
| 152 | - stream.aise_error('Uncompress string length', 4) | ||
| 153 | return count & 0x7FFFFFFF | 228 | return count & 0x7FFFFFFF |
| 154 | 229 | ||
| 155 | def consume_SiteClassInfo(stream): | 230 | def consume_SiteClassInfo(stream): |
| @@ -175,33 +250,27 @@ def consume_OleSiteConcreteControl(stream): | @@ -175,33 +250,27 @@ def consume_OleSiteConcreteControl(stream): | ||
| 175 | with stream.will_jump_to(cbSite): | 250 | with stream.will_jump_to(cbSite): |
| 176 | propmask = SitePropMask(stream.unpack('<L', 4)) | 251 | propmask = SitePropMask(stream.unpack('<L', 4)) |
| 177 | # SiteDataBlock: [MS-OFORMS] 2.2.10.12.3 | 252 | # SiteDataBlock: [MS-OFORMS] 2.2.10.12.3 |
| 178 | - name_len = tag_len = id = 0 | ||
| 179 | - if propmask.fName: | ||
| 180 | - name_len = consume_CountOfBytesWithCompressionFlag(stream) | ||
| 181 | - if propmask.fTag: | ||
| 182 | - tag_len = consume_CountOfBytesWithCompressionFlag(stream) | ||
| 183 | - if propmask.fID: | ||
| 184 | - id = stream.unpack('<L', 4) | ||
| 185 | - for prop in ['fHelpContextID', 'fBitFlags', 'fObjectStreamSize']: | ||
| 186 | - if propmask[prop]: | ||
| 187 | - stream.read(4) | ||
| 188 | - tabindex = ClsidCacheIndex = 0 | ||
| 189 | - with stream.will_pad(): | 253 | + with stream.padded_struct(): |
| 254 | + name_len = tag_len = id = 0 | ||
| 255 | + if propmask.fName: | ||
| 256 | + name_len = consume_CountOfBytesWithCompressionFlag(stream) | ||
| 257 | + if propmask.fTag: | ||
| 258 | + tag_len = consume_CountOfBytesWithCompressionFlag(stream) | ||
| 259 | + if propmask.fID: | ||
| 260 | + id = stream.unpack('<L', 4) | ||
| 261 | + propmask.consume(stream, [('fHelpContextID', 4), ('fBitFlags', 4), ('fObjectStreamSize', 4)]) | ||
| 262 | + tabindex = ClsidCacheIndex = 0 | ||
| 190 | if propmask.fTabIndex: | 263 | if propmask.fTabIndex: |
| 191 | tabindex = stream.unpack('<H', 2) | 264 | tabindex = stream.unpack('<H', 2) |
| 192 | if propmask.fClsidCacheIndex: | 265 | if propmask.fClsidCacheIndex: |
| 193 | ClsidCacheIndex = stream.unpack('<H', 2) | 266 | ClsidCacheIndex = stream.unpack('<H', 2) |
| 194 | - if propmask.fGroupID: | ||
| 195 | - stream.read(2) | ||
| 196 | - # For the next 4 entries, the documentation adds padding, but it should already be aligned?? | ||
| 197 | - for prop in ['fControlTipText', 'fRuntimeLicKey', 'fControlSource', 'fRowSource']: | ||
| 198 | - if propmask[prop]: | ||
| 199 | - stream.read(4) | 267 | + propmask.consume(stream, [('fGroupID', 2), ('fControlTipText', 4), ('fRuntimeLicKey', 4), |
| 268 | + ('fControlSource', 4), ('fRowSource', 4)]) | ||
| 200 | # SiteExtraDataBlock: [MS-OFORMS] 2.2.10.12.4 | 269 | # SiteExtraDataBlock: [MS-OFORMS] 2.2.10.12.4 |
| 201 | name = stream.read(name_len) | 270 | name = stream.read(name_len) |
| 202 | tag = stream.read(tag_len) | 271 | tag = stream.read(tag_len) |
| 203 | return {'name': name, 'tag': tag, 'id': id, 'tabindex': tabindex, | 272 | return {'name': name, 'tag': tag, 'id': id, 'tabindex': tabindex, |
| 204 | - 'ClsidCacheIndex': ClsidCacheIndex} | 273 | + 'ClsidCacheIndex': ClsidCacheIndex, 'value': None} |
| 205 | 274 | ||
| 206 | def consume_FormControl(stream): | 275 | def consume_FormControl(stream): |
| 207 | # FormControl: [MS-OFORMS] 2.2.10.1 | 276 | # FormControl: [MS-OFORMS] 2.2.10.1 |
| @@ -210,9 +279,7 @@ def consume_FormControl(stream): | @@ -210,9 +279,7 @@ def consume_FormControl(stream): | ||
| 210 | with stream.will_jump_to(cbform): | 279 | with stream.will_jump_to(cbform): |
| 211 | propmask = FormPropMask(stream.unpack('<L', 4)) | 280 | propmask = FormPropMask(stream.unpack('<L', 4)) |
| 212 | # FormDataBlock: [MS-OFORMS] 2.2.10.3 | 281 | # FormDataBlock: [MS-OFORMS] 2.2.10.3 |
| 213 | - for prop in ['fBackColor', 'fForeColor', 'fNextAvailableID']: | ||
| 214 | - if propmask[prop]: | ||
| 215 | - stream.read(4) | 282 | + propmask.consume(stream, [('fBackColor', 4), ('fForeColor', 4), ('fNextAvailableID', 4)]) |
| 216 | if propmask.fBooleanProperties: | 283 | if propmask.fBooleanProperties: |
| 217 | BooleanProperties = stream.unpack('<L', 4) | 284 | BooleanProperties = stream.unpack('<L', 4) |
| 218 | FORM_FLAG_DONTSAVECLASSTABLE = (BooleanProperties & (1<<15)) >> 15 | 285 | FORM_FLAG_DONTSAVECLASSTABLE = (BooleanProperties & (1<<15)) >> 15 |
| @@ -233,11 +300,12 @@ def consume_FormControl(stream): | @@ -233,11 +300,12 @@ def consume_FormControl(stream): | ||
| 233 | consume_SiteClassInfo(stream) | 300 | consume_SiteClassInfo(stream) |
| 234 | (CountOfSites, CountOfBytes) = stream.unpacks('<LL', 8) | 301 | (CountOfSites, CountOfBytes) = stream.unpacks('<LL', 8) |
| 235 | remaining_SiteDepthsAndTypes = CountOfSites | 302 | remaining_SiteDepthsAndTypes = CountOfSites |
| 236 | - with stream.will_pad(): | ||
| 237 | - while remaining_SiteDepthsAndTypes > 0: | ||
| 238 | - remaining_SiteDepthsAndTypes -= consume_FormObjectDepthTypeCount(stream) | ||
| 239 | - for i in range(CountOfSites): | ||
| 240 | - yield consume_OleSiteConcreteControl(stream) | 303 | + with stream.will_jump_to(CountOfBytes): |
| 304 | + with stream.will_pad(): | ||
| 305 | + while remaining_SiteDepthsAndTypes > 0: | ||
| 306 | + remaining_SiteDepthsAndTypes -= consume_FormObjectDepthTypeCount(stream) | ||
| 307 | + for i in range(CountOfSites): | ||
| 308 | + yield consume_OleSiteConcreteControl(stream) | ||
| 241 | 309 | ||
| 242 | def consume_MorphDataControl(stream): | 310 | def consume_MorphDataControl(stream): |
| 243 | # MorphDataControl: [MS-OFORMS] 2.2.5.1 | 311 | # MorphDataControl: [MS-OFORMS] 2.2.5.1 |
| @@ -246,36 +314,25 @@ def consume_MorphDataControl(stream): | @@ -246,36 +314,25 @@ def consume_MorphDataControl(stream): | ||
| 246 | with stream.will_jump_to(cbMorphData): | 314 | with stream.will_jump_to(cbMorphData): |
| 247 | propmask = MorphDataPropMask(stream.unpack('<Q', 8)) | 315 | propmask = MorphDataPropMask(stream.unpack('<Q', 8)) |
| 248 | # MorphDataDataBlock: [MS-OFORMS] 2.2.5.3 | 316 | # MorphDataDataBlock: [MS-OFORMS] 2.2.5.3 |
| 249 | - for prop in ['fVariousPropertyBits', 'fBackColor', 'fForeColor', 'fMaxLength']: | ||
| 250 | - if propmask[prop]: | ||
| 251 | - stream.read(4) | ||
| 252 | - with stream.will_pad(): | ||
| 253 | - for prop in ['fBorderStyle', 'fScrollBars', 'fDisplayStyle', 'fMousePointer']: | ||
| 254 | - if propmask[prop]: | ||
| 255 | - stream.read(1) | ||
| 256 | - # PasswordChar, BoundColumn, TextColumn, ColumnCount, and ListRows are 2B + pad = 4B | ||
| 257 | - # ListWidth is 4B + pad = 4B | ||
| 258 | - for prop in ['fPasswordChar', 'fListWidth', 'fBoundColumn', 'fTextColumn', 'fColumnCount', | ||
| 259 | - 'fListRows']: | ||
| 260 | - if propmask[prop]: | ||
| 261 | - stream.read(4) | ||
| 262 | - with stream.will_pad(): | ||
| 263 | - if propmask.fcColumnInfo: | ||
| 264 | - stream.read(2) | ||
| 265 | - for prop in ['fMatchEntry', 'fListStyle', 'fShowDropButtonWhen', 'fDropButtonStyle', | ||
| 266 | - 'fMultiSelect']: | ||
| 267 | - if propmask[prop]: | ||
| 268 | - stream.read(1) | ||
| 269 | - if propmask.fValue: | ||
| 270 | - value_size = consume_CountOfBytesWithCompressionFlag(stream) | ||
| 271 | - else: | ||
| 272 | - value_size = 0 | ||
| 273 | - # Caption, PicturePosition, BorderColor, SpecialEffect, GroupName are 4B + pad = 4B | ||
| 274 | - # MouseIcon, Picture, Accelerator are 2B + pad = 4B | ||
| 275 | - for prop in ['fCaption', 'fPicturePosition', 'fBorderColor', 'fSpecialEffect', | ||
| 276 | - 'fMouseIcon', 'fPicture', 'fAccelerator', 'fGroupName']: | ||
| 277 | - if propmask[prop]: | ||
| 278 | - stream.read(4) | 317 | + with stream.padded_struct(): |
| 318 | + propmask.consume(stream, [('fVariousPropertyBits', 4), ('fBackColor', 4), | ||
| 319 | + ('fForeColor', 4), ('fMaxLength', 4), | ||
| 320 | + ('fBorderStyle', 1), ('fScrollBars', 1), | ||
| 321 | + ('fDisplayStyle', 1), ('fMousePointer', 1), | ||
| 322 | + ('fPasswordChar', 2), ('fListWidth', 4), | ||
| 323 | + ('fBoundColumn', 2), ('fTextColumn', 2), | ||
| 324 | + ('fColumnCount', 2), ('fListRows', 2), | ||
| 325 | + ('fcColumnInfo', 2), ('fMatchEntry', 1), | ||
| 326 | + ('fListStyle', 1), ('fShowDropButtonWhen', 1), | ||
| 327 | + ('fDropButtonStyle', 1), ('fMultiSelect', 1)]) | ||
| 328 | + if propmask.fValue: | ||
| 329 | + value_size = consume_CountOfBytesWithCompressionFlag(stream) | ||
| 330 | + else: | ||
| 331 | + value_size = 0 | ||
| 332 | + propmask.consume(stream, [('fCaption', 4), ('fPicturePosition', 4), | ||
| 333 | + ('fBorderColor', 4), ('fSpecialEffect', 4), | ||
| 334 | + ('fMouseIcon', 2), ('fPicture', 2), | ||
| 335 | + ('fAccelerator', 2), ('fGroupName', 4)]) | ||
| 279 | # MorphDataExtraDataBlock: [MS-OFORMS] 2.2.5.4 | 336 | # MorphDataExtraDataBlock: [MS-OFORMS] 2.2.5.4 |
| 280 | stream.read(8) | 337 | stream.read(8) |
| 281 | value = stream.read(value_size) | 338 | value = stream.read(value_size) |
| @@ -287,17 +344,141 @@ def consume_MorphDataControl(stream): | @@ -287,17 +344,141 @@ def consume_MorphDataControl(stream): | ||
| 287 | consume_TextProps(stream) | 344 | consume_TextProps(stream) |
| 288 | return value | 345 | return value |
| 289 | 346 | ||
| 347 | +def consume_ImageControl(stream): | ||
| 348 | + # ImageControl: [MS-OFORMS] 2.2.3.1 | ||
| 349 | + stream.check_values('ImageControl (versions)', '<BB', 2, (0, 2)) | ||
| 350 | + cbImage = stream.unpack('<H', 2) | ||
| 351 | + with stream.will_jump_to(cbImage): | ||
| 352 | + propmask = ImagePropMask(stream.unpack('<L', 4)) | ||
| 353 | + # Skip the DataBlock and the ExtraDataBlock | ||
| 354 | + # ImageStreamData: [MS-OFORMS] 2.2.3.5 | ||
| 355 | + if propmask.fPicture: | ||
| 356 | + consume_GuidAndPicture(stream) | ||
| 357 | + if propmask.fMouseIcon: | ||
| 358 | + consume_GuidAndPicture(stream) | ||
| 359 | + | ||
| 360 | +def consume_CommandButtonControl(stream): | ||
| 361 | + # CommandButtonControl: [MS-OFORMS] 2.2.1.1 | ||
| 362 | + stream.check_values('CommandButtonControl (versions)', '<BB', 2, (0, 2)) | ||
| 363 | + cbCommandButton = stream.unpack('<H', 2) | ||
| 364 | + with stream.will_jump_to(cbCommandButton): | ||
| 365 | + propmask = CommandButtonPropMask(stream.unpack('<L', 4)) | ||
| 366 | + # Skip the DataBlock and the ExtraDataBlock | ||
| 367 | + # ImageStreamData: [MS-OFORMS] 2.2.1.5 | ||
| 368 | + if propmask.fPicture: | ||
| 369 | + consume_GuidAndPicture(stream) | ||
| 370 | + if propmask.fMouseIcon: | ||
| 371 | + consume_GuidAndPicture(stream) | ||
| 372 | + consume_TextProps(stream) | ||
| 373 | + | ||
| 374 | +def consume_SpinButtonControl(stream): | ||
| 375 | + # SpinButtonControl: [MS-OFORMS] 2.2.8.1 | ||
| 376 | + stream.check_values('SpinButtonControl (versions)', '<BB', 2, (0, 2)) | ||
| 377 | + cbSpinButton = stream.unpack('<H', 2) | ||
| 378 | + with stream.will_jump_to(cbSpinButton): | ||
| 379 | + propmask = SpinButtonPropMask(stream.unpack('<L', 4)) | ||
| 380 | + # Skip the DataBlock and the ExtraDataBlock | ||
| 381 | + # SpinButtonStreamData: [MS-OFORMS] 2.2.8.5 | ||
| 382 | + if propmask.fMouseIcon: | ||
| 383 | + consume_GuidAndPicture(stream) | ||
| 384 | + | ||
| 385 | +def consume_TabStripControl(stream): | ||
| 386 | + # TabStripControl: [MS-OFORMS] 2.2.9.1 | ||
| 387 | + stream.check_values('TabStripControl (versions)', '<BB', 2, (0, 2)) | ||
| 388 | + cbTabStrip = stream.unpack('<H', 2) | ||
| 389 | + with stream.will_jump_to(cbTabStrip): | ||
| 390 | + propmask = TabStripPropMask(stream.unpack('<L', 4)) | ||
| 391 | + # TabStripDataBlock: [MS-OFORMS] 2.2.9.3 | ||
| 392 | + propmask.consume(stream, [('fListIndex', 4), ('fBackColor', 4), | ||
| 393 | + ('fForeColor', 4), ('fSize', 4), | ||
| 394 | + ('fMousePointer', 1), ('fTabOrientation', 4), | ||
| 395 | + ('fTabStyle', 4), ('fTabFixedWidth', 4), | ||
| 396 | + ('fTabFixedHeight', 4), ('fTipStrings', 4), | ||
| 397 | + ('fNames', 4), ('fVariousPropertyBits', 4), | ||
| 398 | + ('fTabsAllocated', 4), ('fTags', 4)]) | ||
| 399 | + tabData = 0 | ||
| 400 | + if propmask['fTabData']: | ||
| 401 | + tabData = stream.unpack('<L', 4) | ||
| 402 | + # Skip the ExtraDataBlock | ||
| 403 | + # TabStripStreamData: [MS-OFORMS] 2.2.9.5 | ||
| 404 | + if propmask.fMouseIcon: | ||
| 405 | + consume_GuidAndPicture(stream) | ||
| 406 | + # TextProps | ||
| 407 | + consume_TextProps(stream) | ||
| 408 | + # TabStripTabFlagData: [MS-OFORMS] 2.2.9.6 | ||
| 409 | + for i in range(tabData): | ||
| 410 | + stream.read(4) | ||
| 411 | + | ||
| 412 | +def consume_LabelControl(stream): | ||
| 413 | + # LabelControl: [MS-OFORMS] 2.2.4.1 | ||
| 414 | + stream.check_values('LabelControl (versions)', '<BB', 2, (0, 2)) | ||
| 415 | + cbLabel = stream.unpack('<H', 2) | ||
| 416 | + with stream.will_jump_to(cbLabel): | ||
| 417 | + propmask = LabelPropMask(stream.unpack('<L', 4)) | ||
| 418 | + # LabelDataBlock: [MS-OFORMS] 2.2.4.3 | ||
| 419 | + with stream.padded_struct(): | ||
| 420 | + propmask.consume(stream, [('fForeColor', 4), ('fBackColor', 4), | ||
| 421 | + ('fVariousPropertyBits', 4)]) | ||
| 422 | + if propmask.fCaption: | ||
| 423 | + caption_size = consume_CountOfBytesWithCompressionFlag(stream) | ||
| 424 | + else: | ||
| 425 | + caption_size = 0 | ||
| 426 | + propmask.consume(stream, [('fPicturePosition', 4), ('fMousePointer', 1), | ||
| 427 | + ('fBorderColor', 4), ('fBorderStyle', 2), | ||
| 428 | + ('fSpecialEffect', 2), ('fPicture', 2), | ||
| 429 | + ('fAccelerator', 2), ('fMouseIcon', 2)]) | ||
| 430 | + # LabelExtraDataBlock: [MS-OFORMS] 2.2.4.4 | ||
| 431 | + caption = stream.read(caption_size) | ||
| 432 | + stream.read(8) | ||
| 433 | + # LabelStreamData: [MS-OFORMS] 2.2.4.5 | ||
| 434 | + if propmask.fPicture: | ||
| 435 | + consume_GuidAndPicture(stream) | ||
| 436 | + if propmask.fMouseIcon: | ||
| 437 | + consume_GuidAndPicture(stream) | ||
| 438 | + # TextProps | ||
| 439 | + consume_TextProps(stream) | ||
| 440 | + return caption | ||
| 441 | + | ||
| 442 | +def consume_ScrollBarControl(stream): | ||
| 443 | + # ScrollBarControl: [MS-OFORMS] 2.2.7.1 | ||
| 444 | + stream.check_values('LabelControl (versions)', '<BB', 2, (0, 2)) | ||
| 445 | + cbScrollBar = stream.unpack('<H', 2) | ||
| 446 | + with stream.will_jump_to(cbScrollBar): | ||
| 447 | + propmask = ScrollBarPropMask(stream.unpack('<L', 4)) | ||
| 448 | + # Skip the DataBlock and the ExtraDataBlock | ||
| 449 | + # ScrollBarStreamData: [MS-OFORMS] 2.2.7.5 | ||
| 450 | + if propmask.fMouseIcon: | ||
| 451 | + consume_GuidAndPicture(stream) | ||
| 452 | + | ||
| 290 | def extract_OleFormVariables(ole_file, stream_dir): | 453 | def extract_OleFormVariables(ole_file, stream_dir): |
| 291 | control = ExtendedStream.open(ole_file, '/'.join(stream_dir + ['f'])) | 454 | control = ExtendedStream.open(ole_file, '/'.join(stream_dir + ['f'])) |
| 292 | variables = list(consume_FormControl(control)) | 455 | variables = list(consume_FormControl(control)) |
| 293 | # print('/'.join(stream_dir + ['o'])) | 456 | # print('/'.join(stream_dir + ['o'])) |
| 294 | data = ExtendedStream.open(ole_file, '/'.join(stream_dir + ['o'])) | 457 | data = ExtendedStream.open(ole_file, '/'.join(stream_dir + ['o'])) |
| 295 | for var in variables: | 458 | for var in variables: |
| 296 | - if var['ClsidCacheIndex'] != 23: | ||
| 297 | - #raise OleFormParsingError('Unsupported stored type: {0}'.format(str(var['ClsidCacheIndex']))) | 459 | + # See FormEmbeddedActiveXControlCached for type definition: [MS-OFORMS] 2.4.5 |
| 460 | + if var['ClsidCacheIndex'] == 7: | ||
| 461 | + consume_FormControl(data) | ||
| 462 | + elif var['ClsidCacheIndex'] == 12: | ||
| 463 | + consume_ImageControl(data) | ||
| 464 | + elif var['ClsidCacheIndex'] == 14: | ||
| 465 | + consume_FormControl(data) | ||
| 466 | + elif var['ClsidCacheIndex'] in [15, 23, 24, 25, 26, 27, 28]: | ||
| 467 | + var['value'] = consume_MorphDataControl(data) | ||
| 468 | + elif var['ClsidCacheIndex'] == 16: | ||
| 469 | + consume_SpinButtonControl(data) | ||
| 470 | + elif var['ClsidCacheIndex'] == 17: | ||
| 471 | + consume_CommandButtonControl(data) | ||
| 472 | + elif var['ClsidCacheIndex'] == 18: | ||
| 473 | + consume_TabStripControl(data) | ||
| 474 | + elif var['ClsidCacheIndex'] == 21: | ||
| 475 | + consume_LabelControl(data) | ||
| 476 | + elif var['ClsidCacheIndex'] == 47: | ||
| 477 | + consume_ScrollBarControl(data) | ||
| 478 | + elif var['ClsidCacheIndex'] == 57: | ||
| 479 | + consume_FormControl(data) | ||
| 480 | + else: | ||
| 298 | # TODO: use logging instead of print | 481 | # TODO: use logging instead of print |
| 299 | print('ERROR: Unsupported stored type in user form: {0}'.format(str(var['ClsidCacheIndex']))) | 482 | print('ERROR: Unsupported stored type in user form: {0}'.format(str(var['ClsidCacheIndex']))) |
| 300 | - var['value'] = None | ||
| 301 | - else: | ||
| 302 | - var['value'] = consume_MorphDataControl(data) | 483 | + break |
| 303 | return variables | 484 | return variables |
tests/oleform/__init__.py
0 โ 100644
tests/oleform/test_basic.py
0 โ 100644
| 1 | +""" Test oleform basic functionality """ | ||
| 2 | + | ||
| 3 | +import unittest | ||
| 4 | +from os.path import join | ||
| 5 | +import sys | ||
| 6 | + | ||
| 7 | +# Directory with test data, independent of current working directory | ||
| 8 | +from tests.test_utils import DATA_BASE_DIR | ||
| 9 | + | ||
| 10 | +if sys.version_info[0] <= 2: | ||
| 11 | + from oletools.olevba import VBA_Parser | ||
| 12 | +else: | ||
| 13 | + from oletools.olevba3 import VBA_Parser | ||
| 14 | + | ||
| 15 | +SAMPLES = [('oleform-PR314.docm', [('word/vbaProject.bin', u'UserFormTEST1', {'name': 'Label1', 'value': None, 'tag': 'l\x18sdf', 'ClsidCacheIndex': 21, 'id': 1, 'tabindex': 0}), ('word/vbaProject.bin', u'UserFormTEST1', {'name': 'TextBox1', 'value': 'heyhey', 'tag': '', 'ClsidCacheIndex': 23, 'id': 2, 'tabindex': 1}), ('word/vbaProject.bin', u'UserFormTEST1', {'name': 'ComboBox1', 'value': 'none dd', 'tag': '', 'ClsidCacheIndex': 25, 'id': 3, 'tabindex': 2}), ('word/vbaProject.bin', u'UserFormTEST1', {'name': 'CheckBox1', 'value': '1', 'tag': '', 'ClsidCacheIndex': 26, 'id': 5, 'tabindex': 4}), ('word/vbaProject.bin', u'UserFormTEST1', {'name': 'OptionButton1', 'value': '0', 'tag': '', 'ClsidCacheIndex': 27, 'id': 6, 'tabindex': 5}), ('word/vbaProject.bin', u'UserFormTEST1', {'name': 'ToggleButton1', 'value': '0', 'tag': '', 'ClsidCacheIndex': 28, 'id': 7, 'tabindex': 6}), ('word/vbaProject.bin', u'UserFormTEST1', {'name': 'Frame1', 'value': None, 'tag': '', 'ClsidCacheIndex': 14, 'id': 8, 'tabindex': 7}), ('word/vbaProject.bin', u'UserFormTEST1', {'name': 'TabStrip1', 'value': None, 'tag': '', 'ClsidCacheIndex': 18, 'id': 10, 'tabindex': 8}), ('word/vbaProject.bin', u'UserFormTEST1', {'name': 'CommandButton1', 'value': None, 'tag': '', 'ClsidCacheIndex': 17, 'id': 9, 'tabindex': 9}), ('word/vbaProject.bin', u'UserFormTEST1', {'name': 'MultiPage1', 'value': None, 'tag': '', 'ClsidCacheIndex': 57, 'id': 12, 'tabindex': 10}), ('word/vbaProject.bin', u'UserFormTEST1', {'name': 'ScrollBar1', 'value': None, 'tag': '', 'ClsidCacheIndex': 47, 'id': 16, 'tabindex': 11}), ('word/vbaProject.bin', u'UserFormTEST1', {'name': 'SpinButton1', 'value': None, 'tag': '', 'ClsidCacheIndex': 16, 'id': 17, 'tabindex': 12}), ('word/vbaProject.bin', u'UserFormTEST1', {'name': 'Image1', 'value': None, 'tag': '', 'ClsidCacheIndex': 12, 'id': 18, 'tabindex': 13}), ('word/vbaProject.bin', u'UserFormTEST1', {'name': 'ListBox1', 'value': '', 'tag': '', 'ClsidCacheIndex': 24, 'id': 4, 'tabindex': 3}), ('word/vbaProject.bin', u'UserFormTEST1/i08', {'name': 'TextBox2', 'value': 'abcd', 'tag': '', 'ClsidCacheIndex': 23, 'id': 20, 'tabindex': 0}), ('word/vbaProject.bin', u'UserFormTEST1/i12', {'name': '', 'value': None, 'tag': '', 'ClsidCacheIndex': 18, 'id': 13, 'tabindex': 2}), ('word/vbaProject.bin', u'UserFormTEST1/i12', {'name': 'Page1', 'value': None, 'tag': '', 'ClsidCacheIndex': 7, 'id': 14, 'tabindex': 0}), ('word/vbaProject.bin', u'UserFormTEST1/i12', {'name': 'Page2', 'value': None, 'tag': '', 'ClsidCacheIndex': 7, 'id': 15, 'tabindex': 1}), ('word/vbaProject.bin', u'UserFormTEST1/i12/i14', {'name': 'TextBox3', 'value': 'last one', 'tag': '', 'ClsidCacheIndex': 23, 'id': 24, 'tabindex': 0}), ('word/vbaProject.bin', u'UserFormTest2', {'name': 'Label1', 'value': None, 'tag': '', 'ClsidCacheIndex': 21, 'id': 1, 'tabindex': 0}), ('word/vbaProject.bin', u'UserFormTest2', {'name': 'Label2', 'value': None, 'tag': '', 'ClsidCacheIndex': 21, 'id': 2, 'tabindex': 1}), ('word/vbaProject.bin', u'UserFormTest2', {'name': 'TextBox1', 'value': '&\xe9"\'', 'tag': '', 'ClsidCacheIndex': 23, 'id': 3, 'tabindex': 2})])] | ||
| 16 | + | ||
| 17 | +class TestOleForm(unittest.TestCase): | ||
| 18 | + | ||
| 19 | + def test_samples(self): | ||
| 20 | + if sys.version_info[0] > 2: | ||
| 21 | + # Unfortunately, olevba3 doesn't have extract_form_strings_extended | ||
| 22 | + return | ||
| 23 | + for sample, expected_result in SAMPLES: | ||
| 24 | + full_name = join(DATA_BASE_DIR, 'oleform', sample) | ||
| 25 | + parser = VBA_Parser(full_name) | ||
| 26 | + variables = list(parser.extract_form_strings_extended()) | ||
| 27 | + self.assertEqual(variables, expected_result) | ||
| 28 | + | ||
| 29 | + | ||
| 30 | +# just in case somebody calls this file as a script | ||
| 31 | +if __name__ == '__main__': | ||
| 32 | + unittest.main() | ||
| 33 | + |
tests/test-data/oleform/oleform-PR314.docm
0 โ 100755
No preview for this file type