MpjmdZdZddlZddlmZddlmZddlmZm Z m Z ddl m Z ddl Z ddlZ ddlmZej*dZd Zd Zd Zd Zd ZdZdZdZdZdZ dZ!dZ"dZ#dZ$dZ%dZ&dZ'dZ(dZ)dZ*dZ+dZ,Gdd e-Z.Gd!d"e/Z0Gd#d$e-Z1d%Z2e3d&k(rejhe2yy#e$r ddlmcmZYwxYw)'a Common operations for OpenXML files (docx, xlsx, pptx, ...) This is mostly based on ECMA-376 (5th edition, Part 1) http://www.ecma-international.org/publications/standards/Ecma-376.htm See also: Notes on Microsoft's implementation of ECMA-376: [MS-0E376] .. codeauthor:: Intra2net AG License: BSD, see source code or documentation ooxml is part of the python-oletools package: http://www.decalage.info/python/oletools z0.54.2N) log_helper)uopen)ZipFile BadZipfile is_zipfile)splitextooxmlz[Content_Types].xmlz _rels/.rels)zz Word.Documentz Excel.Sheetwordexcel powerpointnonemixedzword-xmlz excel-xmlz word-xml2003z excel-xml2003c2|y|jddk(rZ|jjddk(rxlsz-->docz-->pptz-->_z!!!z, z...]])tagcountformatindextextappendreplacetailattribitems startswithCONTENT_TYPES_EXCELCONTENT_TYPES_WORDCONTENT_TYPES_PPTCONTENT_TYPES_NEUTRALjoinlen)elempartskeyvaluers \/mnt/ssd/data/Dropbox/adrian/scripts/msg_venv/lib/python3.12/site-packages/oletools/ooxml.py debug_strr0s | xx{cdhhnnS1Q6"))$((488>>#3Fq3H3I*JKN""488,/ yy ]))$))*;*;D%*HIJ yy ]))$))*;*;D%*HIJkk'') $ U \((e45 -  34b Y& !!"45b Y& !!"34b Y& //b W$ b V#  $ ::e D 4y3DSzG## $;c|tjjdk(rt|tSt|t S)zC version-independent test for isinstance(some_var, (str, unicode)) )sys version_infomajor isinstance basestringstr)some_vars r/isstrr;s0 "(J// h $$r1ct|}|jrd}t|d5}tjt |j d}ddd|stS|jd}|tk(rtS|tk(rtStSd}d}d} |jtD]\}} }tj!dt#| z | j$d} || j)t*z}|| j)t,z}|| j)t.z} |r |s|st4S|s |r|st6S|s |s|rt8S|s |s|stStj;d t<S#1swY3xYw#t&$rYwxYw#t0$r>} | j2j)drt| j2vr tcYd} ~ Sd} ~ wwxYw) z6 return one of the DOCTYPE_* constants or raise error NrrF rinvalid subfilez'Encountered contradictory content types) XmlParser is_single_xmlrresearchOFFICE_XML_PROGID_REGEXread DOCTYPE_NONEgroupsWORD_XML_PROG_IDDOCTYPE_WORD_XMLEXCEL_XML_PROG_IDDOCTYPE_EXCEL_XMLiter_xmlFILE_CONTENT_TYPESloggerdebugr0r"KeyErrorr$r%r&r'BadOOXML more_info DOCTYPE_WORD DOCTYPE_EXCELDOCTYPE_POWERPOINTwarning DOCTYPE_MIXED) filenameparsermatchhandleprog_idis_docis_xlsis_ppt_r+ content_typeoo_errs r/get_typerds x F  8S ! JVII5v{{47HIE J ,,.# & &# # ' '$ $ F F F //*<= AJAtQ LL40 1 #{{=9  l--.AB BF l--.@A AF l--.?@ @F A fV fV &V!! & NN<= Q J J&       & &'8 9"f&6&66  sT*F"=F! F/A F!F FF!FF!! G(*2G#G("G##G(cb t|}|tk(ryy#t$rYyt$rYywxYw)z? Determine whether given file is an ooxml file; tries get_type FT)rdrRIOErrorrG)rYdoctypes r/is_ooxmlrhs@8$ ,   s  ...ceZdZdZdZddZdZdZdZdZ d Z dd Z e jfd Zd Zd ZdZdZdZdZdZy) ZipSubFilea A file-like object like ZipFile.open returns them, with size and seek() ZipFile.open() gives file handles that can be read but not seek()ed since the file is being decompressed in the background. This class implements a reset() function (close and re-open stream) and a seek() that uses it. --> can be used as argument to olefile.OleFileIO and olefile.isOleFile() Can be used as a context manager:: with zipfile.ZipFile('file.zip') as zipper: # replaces with zipper.open(subfile) as handle: with ZipSubFile(zipper, 'subfile') as handle: print('subfile in file.zip has size {0}, starts with {1}' .format(handle.size, handle.read(20))) handle.reset() Attributes always present: container: the containing zip file name: name of file within zip file mode: open-mode, 'r' per default size: size of the stream (constructor arg or taken from ZipFile.getinfo) closed: True if there was an open() but no close() since then Attributes only not-None after open() and before close(): handle: direct handle to subfile stream, created by ZipFile.open() pos: current position within stream (can deviate from actual position in self.handle if we fake jump to end) See also (and maybe could some day merge with): ppt_record_parser.IterStream; also: oleobj.FakeFile iNc`||_||_|O|j|j|_t j dj|jn||_d|jvrtdj|||_ d|_ d|_ d|_ y)z1 remember all necessary vars but do not open yet Nzzip stream has size {0}wz%Can only read, mode "{0}" not allowedT) containernamegetinfo file_sizesizerOrPrlower ValueErrormoder\posclosed)selfrmrYrtrqs r/__init__zZipSubFile.__init__s" <!))(3==DI LL299$))D EDI $**, D$fTl, ,   r1cyNTrws r/readablezZipSubFile.readable!r1cyNFr{r|s r/writablezZipSubFile.writable$sr1cyrzr{r|s r/seekablezZipSubFile.seekable'r~r1c|j td|jj|j|j |_d|_d|_|S)zA open subfile for reading; open mode given to constructor before zre-opening file not supported!rF)r\rfrmopenrnrtrurvr|s r/rzZipSubFile.open*sK ;; ":; ;nn))$))TYY?   r1ctd)z write is not allowed zwriting not implemented)rfrwargskwargss r/writezZipSubFile.write4s/00r1c|j td|j|jk\ry|jj |}|xjt |z c_|S)z read given number of bytes (or all data) from stream returns bytes (i.e. str in python2, bytes in python3) zread on closed handler1)r\rfrurqrFr*)rwrqdatas r/rFzZipSubFile.read8sX ;; 12 2 88tyy {{% CI r1c(|tjk(r|}n`|tjk(r|j|z}n=|tjk(r|j |z}nt dj||dkr td||jk(ry|dk(r|jy||jkr"|j|j|y||j kr|j||jz y||_y)z5 re-position point so read() will continue elsewhere z(invalid offset {0}, need SEEK_* constantrz%Seek beyond start of file not allowedN) ioSEEK_SETSEEK_CURruSEEK_ENDrqrsrrfreset _seek_skip)rwruoffsetnew_poss r/seekzZipSubFile.seekHs R[[ G r{{ "hhnG r{{ "ii#oGG$fVn. . Q;AB B   \ JJL txx  JJL OOG $ tyy OOGdhh. /DHr1ct||j\}}t|D]}|j|j|j|y)z< helper for seek: skip forward by given amount using read() N)divmod CHUNK_SIZErangerF)rwto_skipn_chunksleftoverras r/rzZipSubFile._seek_skipjsH$GT__=(x 'A IIdoo & ' (r1c|jS)z$ inform about position of next read )rur|s r/tellzZipSubFile.tellus xxr1cD|j|jy)z close and re-open N)closerr|s r/rzZipSubFile.resetzs  r1cz|j|jjd|_d|_d|_y)z close file NT)r\rrurvr|s r/rzZipSubFile.closes3 ;; " KK     r1c&|j|S)z* start of context manager; opens the file )rr|s r/ __enter__zZipSubFile.__enter__s  r1c$|jy)z) end of context manager; closes the file N)rrs r/__exit__zZipSubFile.__exit__s  r1c|jd}nI|jdk(rd}n7|j|jk\rd}ndj|j}dj|j|j|j |S)z7 creates a nice textual representation for this object rvrzopen, at startz open, at endzopen, at pos {0}z*[ZipSubFile {0} (size {1}, mode {2}, {3})])r\rurqrrnrt)rwstatuss r/__str__zZipSubFile.__str__so ;; F XX]%F XX "#F'..txx8F;tyy$))TYY? @r1)r=N)r)__name__ __module__ __qualname____doc__rrxr}rrrrrFrrrrrrrrrrr{r1r/rjrjsd>J"1 "{{ D    @r1rjc$eZdZdZdfd ZxZS)rRz4 exception thrown if file is not an office XML file cvtt| dj||rd|znd||_||_y)z3 create exception, remember filename and more_info z {0} is not an Office XML file{1}z: N)superrRrxrrYrS)rwrYrS __class__s r/rxzBadOOXML.__init__s; h& . VH)dY. D F! "r1N)rrrrrx __classcell__)rs@r/rRrRs>##r1rRc8eZdZdZdZdZd dZd dZdZdZ y) rAa parser for OOXML files handles two different types of files: "regular" OOXML files are zip archives that contain xml data and possibly other files in binary format. In Office 2003, Microsoft introduced another xml-based format, which uses a single xml file as data source. The content of these types is also different. Method :py:meth:`is_single_xml` tells them apart. cL||_d|_t|_d|_yr)rY did_iter_allsetsubfiles_no_xml_is_single_xml)rwrYs r/rxzXmlParser.__init__s#  !"u"r1cX|j |jSt|jrd|_yd}t|jd5}t j t |jd}ddd|rd|_yt|jd#1swY)xYw)z determine whether this is "regular" ooxml or a single xml file Raises a BadOOXML if this is neither one or the other NFr=r>Tzis no zip and has no prog_id) rrrYrrCrDrErFrR)rwr[r\s r/rBzXmlParser.is_single_xmls    *&& & dmm $"'D  4==# & J&II5v{{47HIE J "&D t}}&DEE  J Js *B  B)Nc#K|jrE|rt|jdt|jd5}d|fdddd|_yd}d} t |j}|s|j }n!t|r|g}ntd|D}|D]#}|j|d5}||fddd%|sd|_|r|jyy#1swYd|_yxYw#1swY_xYw#t$r'}t|jdt|zd}~wt$rt|jdwxYw#|r|jwwxYww) a! Find files in zip or just give single xml file yields pairs (subfile-name, file-handle) where file-handle is an open file-like object. (Do not care too much about encoding here, the xml parser reads the encoding from the first lines in the file.) zxml has no subfilesrbNTc3 K|]}|ywrr{).0args r/ z'XmlParser.iter_files..s$9SS$9s r=zinvalid subfile: znot in zip format) rBrRrYrrrnamelistr;tuplerQr9rr)rwrr\zippersubfilessubfileorig_errs r/ iter_fileszXmlParser.iter_filessv    t}}.CDDdmmT* #fFl" # $D FH # /%0H4[ $xH %$9D$99H'.GWc2.f%vo-...(,D%LLN= # $D  .. Dt}}2S]BDD Ct}}.ABB CLLNsf?E3C+E3A D =C>D E3+C;0E3>D D E"D55#EEE00E3c #K|g}nYt|r(|g}tjdj|n&|}tjdj||j |D]\}}d}d}g} t j ||D]V\} } |  | dk(r`| j|vrLtjdj| j|| j| j|f|dz }o| dk(sJ|dz}|dk\sJ| j|v} | r| j|f} | d | k(r| jnztjd j| | d | rP| d d|k\rEtjd j| d | j| r | d d|k\rE| s|s|| |f|rC| rG| jY |dk(rJy#t$rtjd YRwxYw#t j$r}|jj||t!|j"d |j%drtj&}ntj}|dj||dzYd}~d}~wwxYww)ai Iterate xml contents of document If given subfile name[s] as optional arg[s], will only parse that subfile[s] yields 3-tuples (subfilename, element, depth) where depth indicates how deep in the hierarchy the element is located. Containers of element will come *after* the elements they contain (since they are only finished then). Subfiles that are not xml (e.g. OLE or image files) are remembered internally and can be retrieved using iter_non_xml(). The argument need_children is set to False per default. If you need to access an element's children, set it to True. Note, however, that leaving it at False should save a lot of memory. Otherwise, the parser has to keep every single element in memory since the last element returned is the root which has the rest of the document as children. c.f. http://www.ibm.com/developerworks/xml/library/x-hiperfparse/ Argument tags restricts output to tags with names from that list (or equal to that string). Children are preserved for these. Nzlooking for tags: {0})startendrrz remember start of tag {0} at {1}rrrzBfound end for wanted tag {0} but last start tag {1} does not matchzrecover: pop {0}z0found end of {0} at depth {1} but no start eventzcontent is not valid XMLz.xmlz$ xml-parsing for {0} failed ({1}). z Run iter_non_xml to investigate.)r;rOrPrrET iterparserrpoperror IndexErrorclear ParseErrorraddrRrYendswithrW)rwr need_childrentags want_tagsrr\eventsdepth inside_tagseventr+ is_wantedcurr_tagerrlogs r/rMzXmlParser.iter_xmls0 <I 4[I LL077= >I LL077= >#x8< OGV%FEK7 8#%<<#?(%KE4| '88y0"LL)K*0&5*AC'..%/@A   E>*>QJE A:&: $I 5I $(HHe#4;*2(: + 1 & .66 content-type - defaults is a dict that maps extension --> content-type No guarantees on accuracy of these content types! Default Extension.rNrz/found content type for extension {0[0]}: {0[1]}rOverridePartName/z-found content type for subfile {0[0]}: {0[1]}r@)rBrMrNrrr"r$rrOrPrrRrSdict)rwdefaultsfilesrar+ extensionrrcs r/get_content_typeszXmlParser.get_content_typeses    r6M "mm,>? = 488$$Y/ $ K 8I ++C0$-abM OOY M0J$KLLL"**0&"*>@XX&&z2"kk*5G))#.")!"+LL'4;;}+E!FGLL"**0&r*;= =.E{DN** **+<=&&*:*::  sCE!A/E!! F#*/FF#c#K|jstjd|jsy|j ry|j \}}t |j5}|jD]m}|jdr|dd}d}||vr||}n-t|d}|jdr|dd}||vr||}t||5}|||fdddo dddy#1swYxYw#1swYyxYww)a retrieve subfiles that were found by iter_xml to be non-xml also looks for content type info in the [Content_Types].xml subfile. yields 3-tuples (filename, content_type, file_handle) where content_type is based on filename or default for extension or is None, and file_handle is a ZipSubFile. Caller does not have to care about closing handle, will be closed even in error condition. To handle binary parts of an xlsb file, use xls_parser.parse_xlsb_part zQDid not iterate through complete file. Should run iter_xml() without args, first.Nrrr) rrOrWrrBrrrYr$rrj)rw content_typescontent_defaultsrrrbrr\s r/ iter_non_xmlzXmlParser.iter_non_xmls/  NNH I##      *.*@*@*B' ' T]] # 8v// 8%%c*%abkG# m+#0#9L ( 1! 4I ++C0$-abM  $44'7 'B 08F!<7788 8 8 888 8 8s7A(D *A+C=C1 C=( D 1C: 6C==DD r)NFN) rrrrrxrBrrMrrr{r1r/rArAs*# F*+#Z]~&+P&8r1rAc jtjddttjdk7r t dyt dt tjdzttjd}|jD]4\}}}|dks t dj|d |zt|6t|jD]:\}\}}}t d j||xsd |d kDs/t d ntjy)z Test xml parsing; called when running this file as a script. Prints every element found in input file (to be given as command line arg). FrPr3z/To test this code, give me a single file as argzDetected type: rz {0} {1}{2}r?z Non-XML subfile: {0} of type {1}unknowndz...r)renable_loggingr*r4argvprintrdrArMrr0 enumerater end_logging)rZrr+rrrbras r/testrs eW- 388} @A hsxx{3 34sxx{ #F & 1Pu 19 -&&wu ioN OP.7v7J7J7L-M))q 1vg|9z: < 3; &M   r1__main__)5r __version__r4oletools.common.log_helperroletools.common.io_encodingrzipfilerrros.pathrrrC lxml.etreeetreer ImportErrorxml.etree.cElementTree cElementTreeget_or_create_silent_loggerrOrNFILE_RELATIONSHIPSr%r&r'r(rErIrKrTrUrVrGrXrJrLDOCTYPE_WORD_XML2003DOCTYPE_EXCEL_XML2003r0r;rdrhobjectrjrsrRrArrexitr{r1r/r s= X  1-33 ( 0 / / 8+" A"!  !  %'@%-` $r@r@j #z #"88D > z CHHTV](''(sB88C C