#!php # dict ::= < (metadict | alias)* > # metadict ::= < (cell)* > # alias ::= ( id value ) # value ::= ^oid | =literal # oid ::= id | id:scope #tables: { .. } # table ::= { toid (metatable | row | roid )* } # toid ::= oid /*default scope is c*/ # metatable ::= { (cell)* } #rows: [ .. ] # row ::= [ roid (metarow | cell)* ] # roid ::= oid /*default scope is rowScope from table*/ # metarow ::= [ (cell)* ] # cell ::= ( col slot ) # col ::= ^oid /*default scope is c*/ | name # slot ::= ^oid /*default scope is a*/ | =literal #cells: ( .. ) # cell ::= ( col slot ) # col ::= ^oid /*default scope is c*/ | name # name ::= [a-zA-Z:_] [a-zA-Z:_+-?!]* # slot ::= ^oid /*default scope is a*/ | =literal # oid ::= id | id:scope #groups (sections) @$${x{@ .. @$$}x}@ # group ::= gather items (commit | abort) # gather ::= space @$${ id {@ # commit ::= space @$$} id }@ # abort ::= space @$$}~abort~ id }@ #in scope=ns:msg:db:row:scope:msgs:all #key='flags' $msgflags=array( # see https://searchfox.org/comm-esr115/source/mailnews/base/public/nsMsgMessageFlags.idl 0x00000001 => 'Read', 0x00000002 => 'Replied', 0x00000004 => 'Marked', 0x00000008 => 'Expunged', 0x00000010 => 'HasRe', 0x00000020 => 'Elided', # The children of this sub-thread are folded in the display 0x00000040 => 'FeedMsg', 0x00000080 => 'Offline', # This news article or IMAP message is present in the disk cache 0x00000100 => 'Watched', 0x00000200 => 'SenderAuthed', 0x00000400 => 'Partial', 0x00000800 => 'Queued', 0x00001000 => 'Forwarded', 0x00002000 => 'Redirected', 0x0000E000 => 'Priority=13', 0x00010000 => 'New', 0x00040000 => 'Ignored', 0x00200000 => 'IMAPDeleted', 0x00400000 => 'MDNReportNeeded', 0x00800000 => 'MDNReportSent', 0x01000000 => 'Template', 0x10000000 => 'Attachment', # This message has files attached to it 0x0E000000 => 'Labels=25', ); #in scope=ns:msg:db:row:scope:dbfolderinfo:all #key='flags' $folderflags=array( # see https://searchfox.org/comm-esr115/source/mailnews/base/public/nsMsgFolderFlags.idl 0x00000001 => 'Newsgroup', 0x00000002 => 'Unused3', 0x00000004 => 'Mail', 0x00000008 => 'Directory', 0x00000010 => 'Elided', #Whether the children of this folder are currently hidden in the listing 0x00000020 => 'Virtual', 0x00000040 => 'Unused5', 0x00000080 => 'Unused2', 0x00000100 => 'Trash', 0x00000200 => 'SentMail', 0x00000400 => 'Drafts', 0x00000800 => 'Queue', 0x00001000 => 'Inbox', 0x00002000 => 'ImapBox', 0x00004000 => 'Archive', 0x00008000 => 'Unused1', 0x00010000 => 'Unused4', 0x00020000 => 'Unused7', 0x00040000 => 'Unused6', 0x00080000 => 'ImapPersonal', 0x00100000 => 'ImapPublic', 0x00200000 => 'ImapOtherUser', 0x00400000 => 'Templates', 0x00800000 => 'PersonalShared', 0x01000000 => 'ImapNoselect', 0x02000000 => 'CreatedOffline', 0x04000000 => 'ImapNoinferiors', 0x08000000 => 'Offline', 0x10000000 => 'OfflineEvents', 0x20000000 => 'CheckNew', 0x40000000 => 'Junk', 0x80000000 => 'Favorite', ); #key='sortType', 'sortColumns' $foldersorttype=array( # see https://searchfox.org/comm-esr115/source/mailnews/base/public/nsIMsgDBView.idl 0x11 => 'byNone', 0x12 => 'byDate', 0x13 => 'bySubject', 0x14 => 'byAuthor', 0x15 => 'byId', 0x16 => 'byThread', 0x17 => 'byPriority', 0x18 => 'byStatus', 0x19 => 'bySize', 0x1a => 'byFlagged', 0x1b => 'byUnread', 0x1c => 'byRecipient', 0x1d => 'byLocation', 0x1e => 'byTags', 0x1f => 'byJunkStatus', 0x20 => 'byAttachments', 0x21 => 'byAccount', 0x22 => 'byCustom', 0x23 => 'byReceived', 0x24 => 'byCorrespondent' ); #key='sortOrder', 'sortColumns' $foldersortorder=array( # see https://searchfox.org/comm-esr115/source/mailnews/base/public/nsIMsgDBView.idl 0 => 'none', 1 => 'ascending', 2 => 'descending' ); #key='viewFlags' $folderviewflags=array( # see https://searchfox.org/comm-esr115/source/mailnews/base/public/nsIMsgDBView.idl #kNone = 0x0; 0x1 => 'kThreadedDisplay', 0x8 => 'kShowIgnored', 0x10 => 'kUnreadOnly', 0x20 => 'kExpandAll', 0x40 => 'kGroupBySort' ); #key='viewType' $folderviewtype=array( # see https://searchfox.org/comm-esr115/source/mailnews/base/public/nsIMsgDBView.idl 0 => 'eShowAllThreads', 2 => 'eShowThreadsWithUnread', 3 => 'eShowWatchedThreadsWithUnread', 4 => 'eShowQuickSearchResults', 5 => 'eShowVirtualFolderResults', 6 => 'eShowSearch' ); #in scope=m #key='threadFlags (seems to be the same as msgflags, but is always zero) $threadFlags=array( ); #in scope=ns:msg:db:row:scope:ops:all #key='op' $ImapOpsFlags=array( # see https://searchfox.org/comm-esr115/source/mailnews/db/msgdb/public/nsIMsgOfflineImapOperation.idl 0x1 => 'kFlagsChanged', 0x2 => 'kMsgMoved', 0x4 => 'kMsgCopy', 0x8 => 'kMoveResult', 0x10 => 'kAppendDraft', 0x20 => 'kAddedHeader', 0x40 => 'kDeletedMsg', 0x80 => 'kMsgMarkedDeleted', 0x100 => 'kAppendTemplate', 0x200 => 'kDeleteAllMsgs', 0x400 => 'kAddKeywords', 0x800 => 'kRemoveKeywords' ); /* #key='newFlags'? my %ImapMsgFlags=( # see https://searchfox.org/comm-esr115/source/mailnews/imap/src/nsImapCore.h #define kNoImapMsgFlag 0x0000 #define kImapMsgSeenFlag 0x0001 #define kImapMsgAnsweredFlag 0x0002 #define kImapMsgFlaggedFlag 0x0004 #define kImapMsgDeletedFlag 0x0008 #define kImapMsgDraftFlag 0x0010 #define kImapMsgRecentFlag 0x0020 #define kImapMsgForwardedFlag 0x0040 /* Not always supported, check mailbox folder * / #define kImapMsgMDNSentFlag 0x0080 /* Not always supported. check mailbox folder * / #define kImapMsgCustomKeywordFlag 0x0100 /* this msg has a custom keyword * / #define kImapMsgSupportMDNSentFlag 0x2000 #define kImapMsgSupportForwardedFlag 0x4000 ); */ $table_sort = array( 'ns:msg:db:row:scope:msgs:all' => ' 0 ', 'ns:msg:db:row:scope:dbfolderinfo:all' => ' 1 ', 'ns:msg:db:row:scope:threads:all' => ' 2 ', 'm' => ' 3 ', 'ns:msg:db:row:scope:ops:all' => ' 4 ', 'ns:msg:db:row:scope:pending:all' => ' 5 ', 'ns:addrbk:db:row:scope:card:all' => ' 6 ', 'ns:addrbk:db:row:scope:data:all' => ' 7 ', 'ns:addrbk:db:row:scope:list:all' => ' 8 ', 'ns:msg:db:row:scope:folders:all' => ' 9 ', //panacea.dat 'ns:history:db:row:scope:history:all' => ' 10 ' ); $key_sort = array( 'ID' => ' 0 ', '_scope' => ' 1 ', '_group' => ' 2 ', '_info' => ' 3 ', 'date' => ' 4 ', 'dateReceived' => ' 5 ', 'sender' => ' 6 ', 'recipients' => ' 7 ', 'subject' => ' 8 ', 'size' => ' 9 ' ); $buffer=''; $verbose=0; $key_dict=array(); $val_dict=array(); $state=array(); $tables=array(); $refs=array(); function readmork($filename) { global $buffer, $pos; $buffer=@file_get_contents($filename); if ($buffer===false) { echo("Error: Failed to open '$filename'\n"); exit(1); } if (strlen($buffer)==0) exit(0); if (substr($buffer, 0, 33)!='// ') //SeaMonkey might have date at eol { echo("Error: Not a mork database\n"); exit(1); } $pos=strcspn($buffer, "\r\n", 0)+1; if (strcspn($buffer, "\r\n", $pos, 1)==0) $pos++; $pos--; } function nextChar($s='') { global $buffer, $pos; global $verbose; $pos++; while (true) { $c=substr($buffer, $pos, 1); if ($c==' ' || $c=="\n" || $c=="\r") $pos++; else break; } $c=substr($buffer, $pos, 1); #echo "next: $pos: ".substr($buffer, $pos, 1)."\n"; if (!$s) return $c; else if ($s==$c) return true; else { $pos--; return $c; } return ; } function getData() { global $buffer, $pos; global $verbose; $start=$pos+1; $data=''; while (true) { $end=strpos($buffer, ')', $start); $data.=substr($buffer, $start, $end-$start); //check if ')' is preceeded by '\' but not if \\) but if \\\) if (substr($buffer, $end-1, 1)=="\\" && (substr($buffer, $end-2, 1)!="\\" || #*\) substr($buffer, $end-2, 1)=="\\" && substr($buffer, $end-3, 1)=="\\") #*\\\) ) { $data=substr($data, 0, strlen($data)-1).')'; $start=$end+1; } else { break; } } $pos=$end; //remove \ CR LF in long lines $data=preg_replace("/\\\\[\r\n]+/", '', $data); // why \\\\ ? //remove CR LF Whitespace* $data=preg_replace("/[\r\n]+\s+/", '', $data); $p=strpos($data, '$'); while ($p!==false) { if ($p>0 && substr($data, $p-1, 1)=='\\') { //replace \$ with $ $data=substr($data, 0, $p-1).substr($data, $p); } else { //replace $HexHex with char $hex=substr($data, $p+1, 2); if ($hex!='00') { $data=substr($data, 0, $p).chr(hexdec($hex)).substr($data, $p+3); $p+=1; } else { $data=substr($data, 0, $p).substr($data, $p+3); # only in history.dat } } $p=strpos($data, '$', $p); } $data=preg_replace('/\\\\\\\\/', '\\', $data); #echo "end: $pos: ".substr($buffer, $pos, 1)."\n"; return $data; } function getOID() { global $buffer, $pos; global $state; global $verbose; $oid=''; $c=nextChar(); //skip whitespace $pos++; //don't use nextChar since we must stop on space while (true) { if (strpos('!-0123456789abcdefABCDEF', $c)!==false) { $oid.=$c; //id $c=substr($buffer, $pos++, 1); } else break; } #CS2C has '!C' as oid in '{119E:^80 {(k^98:c)(s=9)1CD:m } 11BC 11BC !C }' #both only inside group #if (strpos($oid, '!')!==false) echo "!! oid with '!' in '".implode('-', $state)."' at $pos\n"; #if (strpos($oid, '-')!==false) echo "!! oid with '-' in '".implode('-', $state)."' at $pos\n"; $ref=''; $name=''; //toplevel rows always with scope, rows inside table always without scope if ($oid && $c==':') { //scope #echo "oid with scope in ".implode('-', $state)."\n"; $c=substr($buffer, $pos++, 1); if ($c=='^') { //id, only in rows or tables but not metatables #echo "oid with scope start with ^ in ".implode('-', $state)."\n"; while (true) { $c=substr($buffer, $pos++, 1); if (strpos('0123456789abcdefABCDEF', $c)!==false) $ref.=$c; //id else break; } } else { //name, only in rows or metatables #echo "oid with scope and name in ".implode('-', $state)."\n"; while (preg_match('/[a-zA-Z_:]/', $c)) { $name.=$c; $c=substr($buffer, $pos++, 1); } } } #else echo "oid without scope in ".implode('-', $state)."\n"; $pos-=2; return array($oid, $ref, $name); } function getGID() { global $buffer, $pos; global $verbose; if (nextChar()!='$' || nextChar()!='$' || ($c=nextChar())!='{' && $c!='}') { echo "Error: Bad start of group at $pos\n"; # or bad end of group exit(1); } list ($gid)=getOID(); if (($c=nextChar())!='{' && $c!='}' || nextChar()!='@') { echo "Error: Bad start of group at $pos\n"; # or bad end of group exit(1); } return $gid; } function row($level) { global $buffer, $pos; global $verbose; if ($verbose) echo "row at $pos\n"; list ($rid, $ref, $name)=getOID(); if ($verbose) echo " rid=$rid (scope=$ref|$name) at $level at $pos\n"; #if ($level=='table' && ($ref || $name)) echo "!! unexpected scope in rid in table\n"; //happens in .mab if (nextChar('[')===true) { echo "Error: unexpected metarow at $pos\n"; exit(1); } $cells=array(); while (nextChar('(')===true) { $cell=getData(); if ($verbose) echo " cell=$cell\n"; array_push($cells, $cell); } if (($c=nextChar(']'))!==true) { echo "Error: end of row not found, have $c at $pos\n"; exit(1); } return array($rid, $ref, $name, $cells); } function splitCell($cell) { if (strpos($cell, '=')>0) { list($key, $val)=explode('=', $cell, 2); $s='='; } else { list($key, $val)=explode('^', substr($cell,1), 2); if (substr($cell, 0, 1)=='^') $key='^'.$key; $s='^'; } #echo "$cell -> $key $s $val\n"; return array($key, $s, $val); } function id2dec($id) { #$hid=hexdec("0x$id"); #if ($hid&0x80000000) { $hid=@reset(unpack('l', pack('l', hexdec($id)))); #} #echo "!! id2dec: $id $hid\n"; return $hid; } ################################ function parseMork() { global $buffer, $pos; global $key_dict, $val_dict; global $state; global $verbose; global $refs, $tables, $hash; #// top level comment #< < key dict #< value dict #{ table #[ row #@$${ section begin #@$$} section end $group='top level'; $total=0; while (true) { $c=nextChar(); if ($c=='') break; #if ($verbose) echo "main level have $c\n"; switch ($c) { ############### key or table dict ############################################### case '<': #if ($verbose) echo "dict at $pos\n"; $c=nextChar(); if ($c=='<') { array_push($state,'key dict'); if ($verbose) echo "key dict at $pos\n"; while (nextChar('(')===true) { $cell=getData(); if ($verbose) echo " cell=$cell\n"; if ($cell!='a=c') { echo "Error: unexpected key dictionary, expected 'a=c' but is '$cell' at $pos\n"; exit(1); } } if (($c=nextChar('>'))!==true) { echo "Error: end of metadict not found, have $c at $pos\n"; exit(1); } # eat up comment after metadict (the only place with a comment) $pos+=strcspn($buffer, "\r\n", $pos); while (nextChar('(')===true) { $cell=getData(); if ($verbose) echo " cell=$cell\n"; list($col, $s, $slot)=splitCell($cell); //only name=literal is used here if ($s=='^') echo "!! cell in key_dict unexpected starts with ^ at $pos\n"; if ($verbose) echo " col=$col slot=$slot\n"; $key_dict[$col]=$slot; } if (($c=nextChar('>'))!==true) { echo "Error: end of key dict not found, have $c at $pos\n"; exit(1); } array_pop($state); #if ($verbose) print_r($key_dict); } else { array_push($state,'value dict'); if ($verbose) echo "value dict at $pos\n"; $pos--; //put character back while (nextChar('(')===true) { $alias=getData(); if ($verbose) echo " alias=$alias\n"; list($id, $s, $value)=splitCell($alias); //only id=literal is used here if ($verbose) echo " id=$id value=$value\n"; if ($s=='^') echo "!! alias in val_dict unexpected starts with ^ at $pos\n"; if ($verbose && array_key_exists($id, $val_dict)) echo "!! key $id already exists in val_dict\n"; $val_dict[$id]=$value; } if (($c=nextChar('>'))!==true) { echo "Error: end of value dict not found, have $c at $pos\n"; exit(1); } array_pop($state); } break; case '{': ############### table ############################################### array_push($state,'table'); if ($verbose) echo "table at $pos\n"; list ($tid, $ref)=getOID(); //always with ref if ($verbose) echo " tid=$tid (scope=$ref) at $pos\n"; $mt=0; #e.g.: #{1:^80 {(k^96:c)(s=9)} ids* } contains all mails # 80=ns:msg:db:row:scope:msgs:all 96=ns:msg:db:table:kind:msgs # kommt sehr oft vor, aber immer immer 1:^80 und k^96 zusammen #{x:^80 {(k^97:c)(s=9)1:m } ids* } contains a thread # 80=ns:msg:db:row:scope:msgs:all 97=ns:msg:db:table:kind:thread # kommt oft vor, immer mit x:^80 und k^97 und y:m dahinter #{FFFFFFFD:^99 {(k^98:c)(s=9)} seems to contain thread starter # 99=ns:msg:db:row:scope:threads:all 98=ns:msg:db:table:kind:allthreads # kommt oft vor, immer mit FFFFFFFD:^99 und k^98 (nein nicht immer 99 und 98) #{1:^9E {(k^9F:c)(s=9)} folder # 9E=ns:msg:db:row:scope:dbfolderinfo:all 9F=ns:msg:db:table:kind:dbfolderinfo # genau einmal für den ordner #{-1:^C0 {||(k^C1:c)(s=9)} # C0=ns:msg:db:row:scope:pending:all C1=ns:msg:db:table:kind:pending # gelegentlich, immer mit -1:^C0 und k^C1 #{-1:^D4 {||(k^D5:c)(s=9)} # D4=ns:msg:db:row:scope:ops:all D5=ns:msg:db:table:kind:ops # gelegentlich, immer mit -1:^D4 und k^C5 #die k^nn sind die defaults für reine rowids innerhalb der table $mscope=$key_dict[$ref]; if ($verbose) echo "mscope=$mscope\n"; #msg folders *.msf: # ns:msg:db:row:scope:msgs:all # ns:msg:db:row:scope:pending:all # ns:msg:db:row:scope:threads:all # ns:msg:db:row:scope:dbfolderinfo:all # ns:msg:db:row:scope:ops:all #addressbooks *.mab: # ns:addrbk:db:row:scope:card:all #history.dat # ns:history:db:row:scope:history:all #panacea.dat # ns:msg:db:row:scope:folders:all if (!array_key_exists("$ref:$tid", $refs)) { $refs["$ref:$tid"]=array(); } $refsP=&$refs["$ref:$tid"]; $rpid="$ref:$tid"; //if $verbose if ($rpid=="FFFFFFFD:99") echo "x: rpid now $rpid\n"; $singleminus=false; while (true) { $c=nextChar(); if ($c=='{') { //metatable with cells array_push($state,'metatable'); // despite the Mork structure doc or grammar, metatable might contain // trailing row id or (in ancient history.dat files only) a row #(k^96:c)(s=9)} ns:msg:db:table:kind:msgs oft #(k^97:c)(s=9)100:m } ns:msg:db:table:kind:thread oft, aber jeder Wert (statt 100) nur einmal #(k^98:c)(s=9)} ns:msg:db:table:kind:allthreads oft #(k^9F:c)(s=9)} ns:msg:db:table:kind:dbfolderinfo einmal #(k^C1:c)(s=9)} ns:msg:db:table:kind:pending gelegentlich #(k^D5:c)(s=9)} ns:msg:db:table:kind:ops gelegentlich #(k^81:c)(s=9)[1(^8C=LE)]} ns:history:db:row:scope:history:all history.dat # there might be 's=9u' if ($verbose) echo " metatable at $pos\n"; if ($mt) echo "!! table with multiple metatables\n"; $mt++; $c=nextChar(); if ($c=='(') { $cell1=getData(); //always 'k^..:c' if ($verbose) echo " cell1=$cell1\n"; $kind=substr($cell1, 2, 2); //currently not used! if ($verbose) echo " kind=$kind\n"; $c=nextChar(); } if ($c=='(') { $cell2=getData(); //always 's=9' or 's=9u', meaning?, we ignore it if ($verbose) echo " cell2=$cell2\n"; if ($cell2!='s=9' && $cell2!='s=9u') echo "!! unexpected second cell in metatable, expected 's=9' or 's=9u', is $cell2\n"; $c=nextChar(); } while ($c=='(') { echo "!! unexpected 3., 4., .. cell in metatable at $pos\n"; $cell=getData(); if ($verbose) echo " cell=$cell\n"; $c=nextChar(); } $pos--; list ($rid, $ref, $name)=getOID(); if ($rid) { if ($verbose) echo " trailing rid $rid (scope=$ref|$name) in metatable at $pos\n"; #if ($name!='m') echo "!! unexpected scope in rid, expected 'm', is '$ref' or '$name'\n"; //might happen in history.dat if (!array_key_exists("m:$rid", $refs)) { $refs["m:$rid"]=array(); } $trefsP=&$refs["m:$rid"]; $tpid="m:$rid"; //if $verbose } else { unset($trefsP); $tpid='none'; //if $verbose } $c=nextChar(); if ($c=='[') { //only in ancient history.dat files if ($verbose) echo " row inside metatable (history.dat only) at $pos\n"; list($rid, $ref, $name, $cells)=row('metatable'); //ref and name are empty if ($verbose) echo " rid=$rid\n"; $c=nextChar(); foreach($cells as $cell) { list($key, $which, $val)=splitCell($cell); # ^8C = LE #8C probably 'ByteOrder', LE='LowEndian'? //TODO: what todo with this? } } if ($c!='}') { echo "Error: end of metatable not found, have $c at $pos\n"; exit(1); } array_pop($state); } else if ($c=='[') { //row inside table #if ($verbose) echo " row at $pos\n"; if (!$mt) echo "!! table without metatable\n"; array_push($state,'row'); list($rid, $ref, $name, $cells)=row('table'); array_pop($state); #if ($ref || $name) echo "!! tablerow with ref=$ref or name=$name at $pos\n"; //ref only in .mab if ($verbose) echo "tablerow: rid='$rid' ref='$ref' name='$name' cells=".implode('|',$cells)."\n"; $m=''; if (substr($rid, 0, 1)=='-') { $m='-'; $rid=substr($rid, 1); } if ($ref) { $scope=$key_dict{$ref}; } else { $scope=$mscope; } if ($verbose) echo "now: m=$m rid=$rid scope=$scope\n"; if ($singleminus) { unset($refsP[$rid]); if ($verbose) echo "remove $rid from refsP ($rpid)\n"; $singleminus=false; } else { $refsP[$rid]=1; if (isset($trefsP)) { if ($verbose) echo "set rid=$rid (1) in trefsP $tpid\n"; $trefsP[$rid]=1; } } if (!array_key_exists($scope, $tables)) $tables[$scope]=array(); $table=&$tables[$scope]; if (array_key_exists($rid, $table)) $hash=$table[$rid]; else $hash = array('ID' => $rid, '_sort' => id2dec($rid), '_group' => $group, '_scope' => $scope ); foreach($cells as $cell) { list($key, $which, $val)=splitCell($cell); if (substr($key,0,1)!='^') echo "!! key in tablerow does not start with ^ at $pos\n"; #if ($verbose) echo "A: cell is key='$key' which='$which' val='$val'\n"; $key=substr($key, 1); //cut off '^' # If the key isn't in the key table, ignore it if (!array_key_exists($key, $key_dict)) { #if ($verbose) echo "A: $key not found in key_dict\n"; continue; } else if ($which=='^') { $val=$val_dict[$val]; #if ($verbose) echo "A: val dereferenced to $val\n"; } else { #if ($verbose) echo "A: val is $val\n"; } $key=$key_dict[$key]; $hash[$key]=$val; } $total++; $table[$rid]=$hash; } else if ($c=='}') { //end of table break; } else { //must be a rid if (!$mt) echo "!! table without metatable\n"; $pos--; list ($rid, $ref, $name)=getOID(); if ($verbose) echo " rid=$rid (scope=$ref|$name) in table at $pos\n"; if ($rid=='-') { $singleminus=true; if ($verbose) echo "singleminus at $pos\n"; } else { $refsP[$rid]=1; if (isset($trefsP)) { if ($verbose) echo "set rid=$rid (2) in trefsP $tpid\n"; $trefsP[$rid]=1; } } } } array_pop($state); break; case '[': ############### row ############################################### array_push($state,'row'); list($rid, $ref, $scope, $cells)=row('main'); array_pop($state); if ($verbose) echo "mainrow: rid='$rid' ref='$ref' scope='$scope' cells=".implode('|',$cells)."\n"; if (!$cells) break; $m=''; if (substr($rid, 0, 1)=='-') { $m='-'; $rid=substr($rid, 1); } if ($ref) $scope=$key_dict[$ref]; #if (!$scope && !$ref) echo "!! no scope or ref in row\n"; if ($verbose) echo "now: m=$m rid=$rid scope=$scope\n"; if (!array_key_exists($scope, $tables)) $tables[$scope]=array(); $table=&$tables[$scope]; if (array_key_exists($rid, $table)) $hash=$table[$rid]; else $hash = array('ID' => $rid, '_sort' => id2dec($rid), '_group' => $group, '_scope' => $scope ); foreach($cells as $cell) { list($key, $which, $val)=splitCell($cell); if (substr($key,0,1)!='^') echo "!! key in tablerow does not start with ^ at $pos\n"; #if ($verbose) echo "A: cell is key='$key' which='$which' val='$val'\n"; $key=substr($key, 1); //cut off '^' # If the key isn't in the key table, ignore it if (!array_key_exists($key, $key_dict)) { #if ($verbose) echo "A: $key not found in key_dict\n"; continue; } else if ($which=='^') { $val=$val_dict[$val]; #if ($verbose) echo "A: val dereferenced to $val\n"; } else { #if ($verbose) echo "A: val is $val\n"; } $key=$key_dict[$key]; $hash[$key]=$val; } $total++; $table[$rid]=$hash; break; case '@': ############### group ############################################### $gid=getGID(); if ($group!='top level') { if ($group==$gid) { array_pop($state); if ($verbose) echo "end of group gid=$gid at $pos\n"; } else { echo "Error: unexpected end of group, expected $group but is $gid\n"; } $group='top level'; break; } array_push($state,'group'); if ($verbose) echo "start of group gid=$gid at $pos\n"; $end=strpos($buffer, '@$$}', $pos)+4; if (substr($buffer, $end, 1)=='~') { if ($verbose) echo "group aborted\n"; $pos=strpos($buffer, '}@', $end)+2; //skip group } $group=$gid; break; default: echo "Error: unexpected start character '$c' at $pos\n"; exit(1); } } } ################################ function outhash($hash) { global $key_sort; global $msgflags, $folderflags; global $foldersorttype, $foldersortorder, $folderviewflags, $folderviewtype; global $ImapOpsFlags; global $refs; global $verbose; $keys=array_keys($hash); uasort($keys, function($a, $b) { global $key_sort; $av=array_key_exists($a, $key_sort)?$key_sort[$a]:strtolower($a); $bv=array_key_exists($b, $key_sort)?$key_sort[$b]:strtolower($b); return $av <=> $bv; } ); foreach ($keys as $key) { if ($key=='_sort') continue; $v=$val = $hash[$key]; if ($key=='_scope' && $v=='' && !$verbose) continue; if ($key=='_group' && $v=='top level' && !$verbose) continue; if ($key=='date' || $key=='dateReceived' || $key == 'threadNewestMsgDate' || $key == 'folderDate' || strlen($val) == 8 && $key=='LastModifiedDate') { $val .= ' ('.date('r', hexdec($val)).')'; # or 'c' # $val .= ' ('.date('D M j H:i:s Y', hexdec($val)).')'; #like perl: Mon Jan 27 11:59:13 2025 } else if ($key=='MRMTime' || $key=='MRUTime' || strlen($val) > 8 && $key == 'LastModifiedDate') { $val .= ' ('.date('r', $val).')'; # $val .= ' ('.date('D M j H:i:s Y', $val).')'; #like perl: Mon Jan 27 11:59:13 2025 } else if ($key=='FirstVisitDate' || $key=='LastVisitDate') { // history.dat $val .= ' ('.date('r', $val/1000000).')'; # $val .= ' ('.date('D M j H:i:s Y', $val/1000000).')'; #like perl: Mon Jan 27 11:59:13 2025 } else if (preg_match('/Cellular|Phone|Zip/', $key)) { #nothing to do, but don't treat as hex } else if ($key=='flags' || $key=='newFlags') { //newFlags (in 'ops')?? $h=hexdec($v); $val.=' ('; if (preg_match('/dbfolderinfo/', $hash['_scope'])) { foreach ($folderflags as $k=>$v) { if ($k&$h) $val.=' '.$v; } } else { foreach ($msgflags as $k=>$v) { $s=@explode('=', $v, 2); if (isset($s[1])) $v=$s[0].'='.(((int)$k&(int)$h)>>(int)$s[1]); if ($k&$h) $val.=' '.$v; } } $val.=' )'; } else if ($key=='folderSize') { if ($val == 'ffffffffffffffff') { $val='-1'; } #else if ($val != 0) { $val=$val.' ('.hexdec($val).')'; } else if ($val != '0') { $val=hexdec($val); } } else if ($key=='hierDelim') { $val=chr(hexdec($val)); } else if ($key=='sortType') { // in dbfolderinfo $val.=' ( '.$foldersorttype[hexdec($v)].' )'; } else if ($key=='sortOrder') { // in dbfolderinfo $val.=' ( '.$foldersortorder[$v].' )'; } else if ($key=='sortColumns') { // in dbfolderinfo if ($v) { $i=0; $val=''; $p1=substr($v, $i, 1); $p2=substr($v, $i+1, 1); $val.=sprintf(" %s (%s)", $foldersorttype[ord($p1)], $foldersortorder[$p2]); $i+=2; if ($i$v) { if ($k&$h) $val.=' '.$v; } $val.=' )'; } else if ($key=='viewType') { //if _scope is dbfolderinfo $val.=' ( '.$folderviewtype[hexdec($v)].' )'; } else if ($key=='op') { $h=hexdec($v); $val.=' ('; #if (preg_match('/ops/', $hash['_scope'])) { foreach ($ImapOpsFlags as $k=>$v) { if ($k&$h) $val.=' '.$v; } $val.=' )'; } else if (preg_match('/^[\da-fA-F]{1,8}$/', $val)) { #else might be a telephone number # if ($val eq 'ffffffff') { $val="$val (-1)"; } # elsif (hex($val)>=10) { $val="$val (".hex($val).")"; } $d=id2dec($val); if ($d>=10 || $d<0) { $val="$val ($d)"; } } else { if (mb_detect_encoding($val)=='ASCII' && (stripos($val, '?Q?')||stripos($val, '?B?')) ) { # $nval=mb_decode_mimeheader($val); #does not convert '_' to space if php<8.3 # if ($nval!=$val) $val.=sprintf ("\n%16s = %s", '', $nval); # if ($nval!=$val) $val.=sprintf ("\n%16s = %s", '', mb_convert_encoding($nval, "UTF-8")); $nval=iconv_mime_decode($val, ICONV_MIME_DECODE_CONTINUE_ON_ERROR, 'utf-8'); if (!$nval) { //sometimes has a 'Detected an incomplete multibyte character' error $nval=mb_decode_mimeheader($val); //does not convert _ to space in older php $nval=str_replace('_', ' ', $nval); } if ($nval!=$val) $val.=sprintf ("\n%16s = %s", '', $nval); } } #if ($key=='_group') $key='_section'; //to compare to perl echo sprintf ("%16s = %s\n", $key, $val); } $id=$hash['ID']; $lrefs=null; if ($hash['_scope'] == 'ns:msg:db:row:scope:msgs:all') { if ($id != '1') $lrefs=&$refs{"80:$id"}; } else if ($hash['_scope'] == 'm') { # das kann nicht wirklich stimmen $lrefs=&$refs{"m:$id"}; } if ($lrefs) { ksort($lrefs, SORT_STRING); echo sprintf ("%16s = %s\n", '_refs', implode(' ', array_keys($lrefs))); #print_r($lrefs); } #else echo "No lrefs\n"; echo "\n"; } function showData() { global $key_dict, $val_dict; global $refs, $tables, $hash; #global $table_sort; global $table; global $verbose; #print_r($tables); #print_r($refs); #fwrite(STDERR,print_r(array_keys($refs), true)); $msgs=&$tables["ns:msg:db:row:scope:msgs:all"]; if ($msgs) { $refs=&$refs["80:1"]; if ($refs) { foreach (array_keys($msgs) as $k) { # print STDERR "msg $k not in refs\n" if !exists $refs->{$k}; #alle negativen (FFFFFFxx) ids if (!array_key_exists($k, $refs)) { #if ($verbose) echo "message $k deleted\n"; $msgs[$k]['_info']='Message deleted'; } foreach (array_keys($refs) as $k) { if ($verbose && !array_key_exists($k, $msgs)) echo "ref $k not in messages\n"; } } } } $tkeys=array_keys($tables); uasort($tkeys, function($a, $b) { global $table_sort; return $table_sort[$a] <=> $table_sort[$b]; } ); foreach ($tkeys as $tableid) { $table=$tables[$tableid]; //does not work with & if (!$table) continue; echo "\n==========================================================\n"; echo "$tableid\n\n"; $rids=array_keys($table); uasort($rids, function($a, $b) { global $table; return $table[$b]['_sort'] <=> $table[$a]['_sort']; } ); foreach($rids as $rowid) { outhash($table[$rowid]); } } } ################################ array_shift($argv); $switch=array_shift($argv); if (substr($switch, 0, 1)=='-') { if ($switch=='-v') $verbose=1; else { echo "illegal switch\n"; exit(1); } $filename=array_shift($argv); } else $filename=$switch; echo realpath($filename)."\n"; fwrite(STDERR, realpath($filename)."\n"); readmork($filename); parseMork(); #print_r($refs); showData(); ?>