View difference between Paste ID: sQSNwN8B and
SHOW: | | - or go back to the newest paste.
1-
1+
#! /usr/bin/python
2
3
# ineptpdf8.4.51.pyw
4
# ineptpdf, version 8.4.51
5
6
# To run this program install Python 2.7 from http://www.python.org/download/ 
7
#
8
# PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
9
#
10
# and PyWin Extension (Win32API module) from
11
# http://sourceforge.net/projects/pywin32/files/
12
#
13
# Make sure to install the dedicated versions for Python 2.7.
14
# 
15
# It's recommended to use the 32-Bit Python Windows versions (even with a 64-bit
16
# Windows system).
17
#
18
# Save this script file as
19
# ineptpdf8.4.51.pyw and double-click on it to run it.
20
21
# Revision history:
22
#   1 - Initial release
23
#   2 - Improved determination of key-generation algorithm
24
#   3 - Correctly handle PDF >=1.5 cross-reference streams
25
#   4 - Removal of ciando's personal ID (anon)
26
#   5 - removing small bug with V3 ebooks (anon)
27
#   6 - changed to adeptkey4.der format for 1.7.2 support (anon)
28
#   6.1 - backward compatibility for 1.7.1 and old adeptkey.der (anon)
29
#   7 - Get cross reference streams and object streams working for input.
30
#       Not yet supported on output but this only effects file size,
31
#       not functionality. (anon2)
32
#   7.1 - Correct a problem when an old trailer is not followed by startxref (anon2)
33
#   7.2 - Correct malformed Mac OS resource forks for Stanza
34
#       - Support for cross ref streams on output (decreases file size) (anon2)
35
#   7.3 - Correct bug in trailer with cross ref stream that caused the error (anon2)
36
#         "The root object is missing or invalid" in Adobe Reader.
37
#   7.4 - Force all generation numbers in output file to be 0, like in v6.
38
#         Fallback code for wrong xref improved (search till last trailer
39
#         instead of first) (anon2)
40
#   8 - fileopen user machine identifier support (Tetrachroma)
41
#   8.1 - fileopen user cookies support (Tetrachroma)
42
#   8.2 - fileopen user name/password support (Tetrachroma)
43
#   8.3 - fileopen session cookie support (Tetrachroma)
44
#   8.3.1 - fix for the "specified key file does not exist" error (Tetrachroma)
45
#   8.3.2 - improved server result parsing (Tetrachroma)
46
#   8.4 - Ident4D and encrypted Uuid support (Tetrachroma)
47
#   8.4.1 - improved MAC address processing (Tetrachroma)
48
#   8.4.2 - FowP3Uuid fallback file processing (Tetrachroma)
49
#   8.4.3 - improved user/password pdf file detection (Tetrachroma)
50
#   8.4.4 - small bugfix (Tetrachroma)
51
#   8.4.5 - improved cookie host searching (Tetrachroma)
52
#   8.4.6 - STRICT parsing disabled (non-standard pdf processing) (Tetrachroma)
53
#   8.4.7 - UTF-8 input file conversion (Tetrachroma)
54
#   8.4.8 - fix for more rare utf8 problems (Tetrachroma)
55
#   8.4.9 - solution for utf8 in comination with
56
#           ident4id method (Tetrachroma)
57
#   8.4.10 - line feed processing, non c system drive patch, nrbook support (Tetrachroma)
58
#   8.4.11 - alternative ident4id calculation (Tetrachroma)
59
#   8.4.12 - fix for capital username characters and
60
#            other unusual user login names (Tetrachroma & ZeroPoint)
61
#   8.4.13 - small bug fixes (Tetrachroma)
62
#   8.4.14 - fix for non-standard-conform fileopen pdfs (Tetrachroma)
63
#   8.4.15 - 'bad file descriptor'-fix (Tetrachroma)
64
#   8.4.16 - improves user/pass detection (Tetrachroma)
65
#   8.4.17 - fix for several '=' chars in a DPRM entity (Tetrachroma)
66
#   8.4.18 - follow up bug fix for the DPRM problem,
67
#            more readable error messages (Tetrachroma)
68
#   8.4.19 - 2nd fix for 'bad file descriptor' problem (Tetrachroma)
69
#   8.4.20 - follow up patch (Tetrachroma)
70
#   8.4.21 - 3rd patch for 'bad file descriptor' (Tetrachroma)
71
#   8.4.22 - disable prints for exception prevention (Tetrachroma)
72
#   8.4.23 - check for additional security attributes (Tetrachroma)
73
#   8.4.24 - improved cookie session support (Tetrachroma)
74
#   8.4.25 - more compatibility with unicode files (Tetrachroma)
75
#   8.4.26 - automated session/user cookie request function (works
76
#            only with Firefox 3.x+) (Tetrachroma)
77
#   8.4.27 - user/password fallback
78
#   8.4.28 - AES decryption, improved misconfigured pdf handling,
79
#            limited experimental APS support (Tetrachroma & Neisklar)
80
#   8.4.29 - backport for bad formatted rc4 encrypted pdfs (Tetrachroma)
81
#   8.4.30 - extended authorization attributes support (Tetrachroma)
82
#   8.4.31 - improved session cookie and better server response error
83
#            handling (Tetrachroma)
84
#   8.4.33 - small cookie optimizations (Tetrachroma)
85
#   8.4.33 - debug output option (Tetrachroma)
86
#   8.4.34 - better user/password management
87
#            handles the 'AskUnp' response) (Tetrachroma)
88
#   8.4.35 - special handling for non-standard systems (Tetrachroma)
89
#   8.4.36 - previous machine/disk handling [PrevMach/PrevDisk] (Tetrachroma)
90
#   8.4.36 - FOPN_flock support (Tetrachroma)
91
#   8.4.37 - patch for unicode paths/filenames (Tetrachroma)
92
#   8.4.38 - small fix for user/password dialog (Tetrachroma)
93
#   8.4.39 - sophisticated request mode differentiation, forced
94
#            uuid calculation (Tetrachroma)
95
#   8.4.40 - fix for non standard server responses (Tetrachroma)
96
#   8.4.41 - improved user/password request windows,
97
#            better server response tolerance (Tetrachroma)
98
#   8.4.42 - improved nl/cr server response parsing (Tetrachroma)
99
#   8.4.43 - fix for user names longer than 13 characters and special
100
#            uuid encryption (Tetrachroma)
101
#   8.4.44 - another fix for ident4d problem (Tetrachroma)
102
#   8.4.45 - 2nd fix for ident4d problem (Tetrachroma)
103
#   8.4.46 - script cleanup and optimizations (Tetrachroma)
104
#   8.4.47 - script identification change to Adobe Reader (Tetrachroma)
105
#   8.4.48 - improved tolerance for false file/registry entries (Tetrachroma)
106
#   8.4.49 - improved username encryption (Tetrachroma)
107
#   8.4.50 - improved (experimental) APS support (Tetrachroma & Neisklar)
108
#   8.4.51 - automatic APS offline key retrieval (works only for
109
#            Onleihe right now) (80ka80 & Tetrachroma)
110
111
"""
112
Decrypts Adobe ADEPT-encrypted and Fileopen PDF files.
113
"""
114
115
from __future__ import with_statement
116
117
__license__ = 'GPL v3'
118
119
import sys
120
import os
121
import re
122
import zlib
123
import struct
124
import hashlib
125
from itertools import chain, islice
126
import xml.etree.ElementTree as etree
127
import Tkinter
128
import Tkconstants
129
import tkFileDialog
130
import tkMessageBox
131
# added for fileopen support
132
import urllib
133
import urlparse
134
import time
135
import socket
136
import string
137
import uuid
138
import subprocess
139
import time
140
import getpass
141
from ctypes import *
142
import traceback
143
import inspect
144
import tempfile
145
import sqlite3
146
import httplib
147
try:
148
    from Crypto.Cipher import ARC4
149
    # needed for newer pdfs
150
    from Crypto.Cipher import AES
151
    from Crypto.Hash import SHA256
152
    from Crypto.PublicKey import RSA
153
    
154
except ImportError:
155
    ARC4 = None
156
    RSA = None
157
try:
158
    from cStringIO import StringIO
159
except ImportError:
160
    from StringIO import StringIO
161
162
class ADEPTError(Exception):
163
    pass
164
165
# global variable (needed for fileopen and password decryption)
166
INPUTFILEPATH = ''
167
KEYFILEPATH = ''
168
PASSWORD = ''
169
DEBUG_MODE = False
170
IVERSION = '8.4.51'
171
172
# Do we generate cross reference streams on output?
173
# 0 = never
174
# 1 = only if present in input
175
# 2 = always
176
177
GEN_XREF_STM = 1
178
179
# This is the value for the current document
180
gen_xref_stm = False # will be set in PDFSerializer
181
182
### 
183
### ASN.1 parsing code from tlslite
184
185
def bytesToNumber(bytes):
186
    total = 0L
187
    for byte in bytes:
188
        total = (total << 8) + byte
189
    return total
190
191
class ASN1Error(Exception):
192
    pass
193
194
class ASN1Parser(object):
195
    class Parser(object):
196
        def __init__(self, bytes):
197
            self.bytes = bytes
198
            self.index = 0
199
    
200
        def get(self, length):
201
            if self.index + length > len(self.bytes):
202
                raise ASN1Error("Error decoding ASN.1")
203
            x = 0
204
            for count in range(length):
205
                x <<= 8
206
                x |= self.bytes[self.index]
207
                self.index += 1
208
            return x
209
    
210
        def getFixBytes(self, lengthBytes):
211
            bytes = self.bytes[self.index : self.index+lengthBytes]
212
            self.index += lengthBytes
213
            return bytes
214
    
215
        def getVarBytes(self, lengthLength):
216
            lengthBytes = self.get(lengthLength)
217
            return self.getFixBytes(lengthBytes)
218
    
219
        def getFixList(self, length, lengthList):
220
            l = [0] * lengthList
221
            for x in range(lengthList):
222
                l[x] = self.get(length)
223
            return l
224
    
225
        def getVarList(self, length, lengthLength):
226
            lengthList = self.get(lengthLength)
227
            if lengthList % length != 0:
228
                raise ASN1Error("Error decoding ASN.1")
229
            lengthList = int(lengthList/length)
230
            l = [0] * lengthList
231
            for x in range(lengthList):
232
                l[x] = self.get(length)
233
            return l
234
    
235
        def startLengthCheck(self, lengthLength):
236
            self.lengthCheck = self.get(lengthLength)
237
            self.indexCheck = self.index
238
    
239
        def setLengthCheck(self, length):
240
            self.lengthCheck = length
241
            self.indexCheck = self.index
242
    
243
        def stopLengthCheck(self):
244
            if (self.index - self.indexCheck) != self.lengthCheck:
245
                raise ASN1Error("Error decoding ASN.1")
246
    
247
        def atLengthCheck(self):
248
            if (self.index - self.indexCheck) < self.lengthCheck:
249
                return False
250
            elif (self.index - self.indexCheck) == self.lengthCheck:
251
                return True
252
            else:
253
                raise ASN1Error("Error decoding ASN.1")
254
255
    def __init__(self, bytes):
256
        p = self.Parser(bytes)
257
        p.get(1)
258
        self.length = self._getASN1Length(p)
259
        self.value = p.getFixBytes(self.length)
260
261
    def getChild(self, which):
262
        p = self.Parser(self.value)
263
        for x in range(which+1):
264
            markIndex = p.index
265
            p.get(1)
266
            length = self._getASN1Length(p)
267
            p.getFixBytes(length)
268
        return ASN1Parser(p.bytes[markIndex:p.index])
269
270
    def _getASN1Length(self, p):
271
        firstLength = p.get(1)
272
        if firstLength<=127:
273
            return firstLength
274
        else:
275
            lengthLength = firstLength & 0x7F
276
            return p.get(lengthLength)
277
278
###
279
### PDF parsing routines from pdfminer, with changes for EBX_HANDLER
280
281
##  Utilities
282
##
283
def choplist(n, seq):
284
    '''Groups every n elements of the list.'''
285
    r = []
286
    for x in seq:
287
        r.append(x)
288
        if len(r) == n:
289
            yield tuple(r)
290
            r = []
291
    return
292
293
def nunpack(s, default=0):
294
    '''Unpacks up to 4 bytes big endian.'''
295
    l = len(s)
296
    if not l:
297
        return default
298
    elif l == 1:
299
        return ord(s)
300
    elif l == 2:
301
        return struct.unpack('>H', s)[0]
302
    elif l == 3:
303
        return struct.unpack('>L', '\x00'+s)[0]
304
    elif l == 4:
305
        return struct.unpack('>L', s)[0]
306
    else:
307
        return TypeError('invalid length: %d' % l)
308
309
310
STRICT = 0
311
312
313
##  PS Exceptions
314
##
315
class PSException(Exception): pass
316
class PSEOF(PSException): pass
317
class PSSyntaxError(PSException): pass
318
class PSTypeError(PSException): pass
319
class PSValueError(PSException): pass
320
321
322
##  Basic PostScript Types
323
##
324
325
# PSLiteral
326
class PSObject(object): pass
327
328
class PSLiteral(PSObject):
329
    '''
330
    PS literals (e.g. "/Name").
331
    Caution: Never create these objects directly.
332
    Use PSLiteralTable.intern() instead.
333
    '''
334
    def __init__(self, name):
335
        self.name = name
336
        return
337
    
338
    def __repr__(self):
339
        name = []
340
        for char in self.name:
341
            if not char.isalnum():
342
                char = '#%02x' % ord(char)
343
            name.append(char)
344
        return '/%s' % ''.join(name)
345
346
# PSKeyword
347
class PSKeyword(PSObject):
348
    '''
349
    PS keywords (e.g. "showpage").
350
    Caution: Never create these objects directly.
351
    Use PSKeywordTable.intern() instead.
352
    '''
353
    def __init__(self, name):
354
        self.name = name
355
        return
356
    
357
    def __repr__(self):
358
        return self.name
359
360
# PSSymbolTable
361
class PSSymbolTable(object):
362
    
363
    '''
364
    Symbol table that stores PSLiteral or PSKeyword.
365
    '''
366
    
367
    def __init__(self, classe):
368
        self.dic = {}
369
        self.classe = classe
370
        return
371
    
372
    def intern(self, name):
373
        if name in self.dic:
374
            lit = self.dic[name]
375
        else:
376
            lit = self.classe(name)
377
            self.dic[name] = lit
378
        return lit
379
380
PSLiteralTable = PSSymbolTable(PSLiteral)
381
PSKeywordTable = PSSymbolTable(PSKeyword)
382
LIT = PSLiteralTable.intern
383
KWD = PSKeywordTable.intern
384
KEYWORD_BRACE_BEGIN = KWD('{')
385
KEYWORD_BRACE_END = KWD('}')
386
KEYWORD_ARRAY_BEGIN = KWD('[')
387
KEYWORD_ARRAY_END = KWD(']')
388
KEYWORD_DICT_BEGIN = KWD('<<')
389
KEYWORD_DICT_END = KWD('>>')
390
391
392
def literal_name(x):
393
    if not isinstance(x, PSLiteral):
394
        if STRICT:
395
            raise PSTypeError('Literal required: %r' % x)
396
        else:
397
            return str(x)
398
    return x.name
399
400
def keyword_name(x):
401
    if not isinstance(x, PSKeyword):
402
        if STRICT:
403
            raise PSTypeError('Keyword required: %r' % x)
404
        else:
405
            return str(x)
406
    return x.name
407
408
409
##  PSBaseParser
410
##
411
EOL = re.compile(r'[\r\n]')
412
SPC = re.compile(r'\s')
413
NONSPC = re.compile(r'\S')
414
HEX = re.compile(r'[0-9a-fA-F]')
415
END_LITERAL = re.compile(r'[#/%\[\]()<>{}\s]')
416
END_HEX_STRING = re.compile(r'[^\s0-9a-fA-F]')
417
HEX_PAIR = re.compile(r'[0-9a-fA-F]{2}|.')
418
END_NUMBER = re.compile(r'[^0-9]')
419
END_KEYWORD = re.compile(r'[#/%\[\]()<>{}\s]')
420
END_STRING = re.compile(r'[()\134]')
421
OCT_STRING = re.compile(r'[0-7]')
422
ESC_STRING = { 'b':8, 't':9, 'n':10, 'f':12, 'r':13, '(':40, ')':41, '\\':92 }
423
424
class PSBaseParser(object):
425
426
    '''
427
    Most basic PostScript parser that performs only basic tokenization.
428
    '''
429
    BUFSIZ = 4096
430
431
    def __init__(self, fp):
432
        self.fp = fp
433
        self.seek(0)
434
        return
435
436
    def __repr__(self):
437
        return '<PSBaseParser: %r, bufpos=%d>' % (self.fp, self.bufpos)
438
439
    def flush(self):
440
        return
441
    
442
    def close(self):
443
        self.flush()
444
        return
445
    
446
    def tell(self):
447
        return self.bufpos+self.charpos
448
449
    def poll(self, pos=None, n=80):
450
        pos0 = self.fp.tell()
451
        if not pos:
452
            pos = self.bufpos+self.charpos
453
        self.fp.seek(pos)
454
        ##print >>sys.stderr, 'poll(%d): %r' % (pos, self.fp.read(n))
455
        self.fp.seek(pos0)
456
        return
457
458
    def seek(self, pos):
459
        '''
460
        Seeks the parser to the given position.
461
        '''
462
        self.fp.seek(pos)
463
        # reset the status for nextline()
464
        self.bufpos = pos
465
        self.buf = ''
466
        self.charpos = 0
467
        # reset the status for nexttoken()
468
        self.parse1 = self.parse_main
469
        self.tokens = []
470
        return
471
472
    def fillbuf(self):
473
        if self.charpos < len(self.buf): return
474
        # fetch next chunk.
475
        self.bufpos = self.fp.tell()
476
        self.buf = self.fp.read(self.BUFSIZ)
477
        if not self.buf:
478
            raise PSEOF('Unexpected EOF')
479
        self.charpos = 0
480
        return
481
    
482
    def parse_main(self, s, i):
483
        m = NONSPC.search(s, i)
484
        if not m:
485
            return (self.parse_main, len(s))
486
        j = m.start(0)
487
        c = s[j]
488
        self.tokenstart = self.bufpos+j
489
        if c == '%':
490
            self.token = '%'
491
            return (self.parse_comment, j+1)
492
        if c == '/':
493
            self.token = ''
494
            return (self.parse_literal, j+1)
495
        if c in '-+' or c.isdigit():
496
            self.token = c
497
            return (self.parse_number, j+1)
498
        if c == '.':
499
            self.token = c
500
            return (self.parse_float, j+1)
501
        if c.isalpha():
502
            self.token = c
503
            return (self.parse_keyword, j+1)
504
        if c == '(':
505
            self.token = ''
506
            self.paren = 1
507
            return (self.parse_string, j+1)
508
        if c == '<':
509
            self.token = ''
510
            return (self.parse_wopen, j+1)
511
        if c == '>':
512
            self.token = ''
513
            return (self.parse_wclose, j+1)
514
        self.add_token(KWD(c))
515
        return (self.parse_main, j+1)
516
                            
517
    def add_token(self, obj):
518
        self.tokens.append((self.tokenstart, obj))
519
        return
520
    
521
    def parse_comment(self, s, i):
522
        m = EOL.search(s, i)
523
        if not m:
524
            self.token += s[i:]
525
            return (self.parse_comment, len(s))
526
        j = m.start(0)
527
        self.token += s[i:j]
528
        # We ignore comments.
529
        #self.tokens.append(self.token)
530
        return (self.parse_main, j)
531
    
532
    def parse_literal(self, s, i):
533
        m = END_LITERAL.search(s, i)
534
        if not m:
535
            self.token += s[i:]
536
            return (self.parse_literal, len(s))
537
        j = m.start(0)
538
        self.token += s[i:j]
539
        c = s[j]
540
        if c == '#':
541
            self.hex = ''
542
            return (self.parse_literal_hex, j+1)
543
        self.add_token(LIT(self.token))
544
        return (self.parse_main, j)
545
    
546
    def parse_literal_hex(self, s, i):
547
        c = s[i]
548
        if HEX.match(c) and len(self.hex) < 2:
549
            self.hex += c
550
            return (self.parse_literal_hex, i+1)
551
        if self.hex:
552
            self.token += chr(int(self.hex, 16))
553
        return (self.parse_literal, i)
554
555
    def parse_number(self, s, i):
556
        m = END_NUMBER.search(s, i)
557
        if not m:
558
            self.token += s[i:]
559
            return (self.parse_number, len(s))
560
        j = m.start(0)
561
        self.token += s[i:j]
562
        c = s[j]
563
        if c == '.':
564
            self.token += c
565
            return (self.parse_float, j+1)
566
        try:
567
            self.add_token(int(self.token))
568
        except ValueError:
569
            pass
570
        return (self.parse_main, j)
571
    def parse_float(self, s, i):
572
        m = END_NUMBER.search(s, i)
573
        if not m:
574
            self.token += s[i:]
575
            return (self.parse_float, len(s))
576
        j = m.start(0)
577
        self.token += s[i:j]
578
        self.add_token(float(self.token))
579
        return (self.parse_main, j)
580
    
581
    def parse_keyword(self, s, i):
582
        m = END_KEYWORD.search(s, i)
583
        if not m:
584
            self.token += s[i:]
585
            return (self.parse_keyword, len(s))
586
        j = m.start(0)
587
        self.token += s[i:j]
588
        if self.token == 'true':
589
            token = True
590
        elif self.token == 'false':
591
            token = False
592
        else:
593
            token = KWD(self.token)
594
        self.add_token(token)
595
        return (self.parse_main, j)
596
597
    def parse_string(self, s, i):
598
        m = END_STRING.search(s, i)
599
        if not m:
600
            self.token += s[i:]
601
            return (self.parse_string, len(s))
602
        j = m.start(0)
603
        self.token += s[i:j]
604
        c = s[j]
605
        if c == '\\':
606
            self.oct = ''
607
            return (self.parse_string_1, j+1)
608
        if c == '(':
609
            self.paren += 1
610
            self.token += c
611
            return (self.parse_string, j+1)
612
        if c == ')':
613
            self.paren -= 1
614
            if self.paren:
615
                self.token += c
616
                return (self.parse_string, j+1)
617
        self.add_token(self.token)
618
        return (self.parse_main, j+1)
619
    def parse_string_1(self, s, i):
620
        c = s[i]
621
        if OCT_STRING.match(c) and len(self.oct) < 3:
622
            self.oct += c
623
            return (self.parse_string_1, i+1)
624
        if self.oct:
625
            self.token += chr(int(self.oct, 8))
626
            return (self.parse_string, i)
627
        if c in ESC_STRING:
628
            self.token += chr(ESC_STRING[c])
629
        return (self.parse_string, i+1)
630
631
    def parse_wopen(self, s, i):
632
        c = s[i]
633
        if c.isspace() or HEX.match(c):
634
            return (self.parse_hexstring, i)
635
        if c == '<':
636
            self.add_token(KEYWORD_DICT_BEGIN)
637
            i += 1
638
        return (self.parse_main, i)
639
640
    def parse_wclose(self, s, i):
641
        c = s[i]
642
        if c == '>':
643
            self.add_token(KEYWORD_DICT_END)
644
            i += 1
645
        return (self.parse_main, i)
646
647
    def parse_hexstring(self, s, i):
648
        m = END_HEX_STRING.search(s, i)
649
        if not m:
650
            self.token += s[i:]
651
            return (self.parse_hexstring, len(s))
652
        j = m.start(0)
653
        self.token += s[i:j]
654
        token = HEX_PAIR.sub(lambda m: chr(int(m.group(0), 16)),
655
                                                 SPC.sub('', self.token))
656
        self.add_token(token)
657
        return (self.parse_main, j)
658
659
    def nexttoken(self):
660
        while not self.tokens:
661
            self.fillbuf()
662
            (self.parse1, self.charpos) = self.parse1(self.buf, self.charpos)
663
        token = self.tokens.pop(0)
664
        return token
665
666
    def nextline(self):
667
        '''
668
        Fetches a next line that ends either with \\r or \\n.
669
        '''
670
        linebuf = ''
671
        linepos = self.bufpos + self.charpos
672
        eol = False
673
        while 1:
674
            self.fillbuf()
675
            if eol:
676
                c = self.buf[self.charpos]
677
                # handle '\r\n'
678
                if c == '\n':
679
                    linebuf += c
680
                    self.charpos += 1
681
                break
682
            m = EOL.search(self.buf, self.charpos)
683
            if m:
684
                linebuf += self.buf[self.charpos:m.end(0)]
685
                self.charpos = m.end(0)
686
                if linebuf[-1] == '\r':
687
                    eol = True
688
                else:
689
                    break
690
            else:
691
                linebuf += self.buf[self.charpos:]
692
                self.charpos = len(self.buf)
693
        return (linepos, linebuf)
694
695
    def revreadlines(self):
696
        '''
697
        Fetches a next line backword. This is used to locate
698
        the trailers at the end of a file.
699
        '''
700
        self.fp.seek(0, 2)
701
        pos = self.fp.tell()
702
        buf = ''
703
        while 0 < pos:
704
            prevpos = pos
705
            pos = max(0, pos-self.BUFSIZ)
706
            self.fp.seek(pos)
707
            s = self.fp.read(prevpos-pos)
708
            if not s: break
709
            while 1:
710
                n = max(s.rfind('\r'), s.rfind('\n'))
711
                if n == -1:
712
                    buf = s + buf
713
                    break
714
                yield s[n:]+buf
715
                s = s[:n]
716
                buf = ''
717
        return
718
719
720
##  PSStackParser
721
##
722
class PSStackParser(PSBaseParser):
723
724
    def __init__(self, fp):
725
        PSBaseParser.__init__(self, fp)
726
        self.reset()
727
        return
728
    
729
    def reset(self):
730
        self.context = []
731
        self.curtype = None
732
        self.curstack = []
733
        self.results = []
734
        return
735
736
    def seek(self, pos):
737
        PSBaseParser.seek(self, pos)
738
        self.reset()
739
        return
740
741
    def push(self, *objs):
742
        self.curstack.extend(objs)
743
        return
744
    def pop(self, n):
745
        objs = self.curstack[-n:]
746
        self.curstack[-n:] = []
747
        return objs
748
    def popall(self):
749
        objs = self.curstack
750
        self.curstack = []
751
        return objs
752
    def add_results(self, *objs):
753
        self.results.extend(objs)
754
        return
755
756
    def start_type(self, pos, type):
757
        self.context.append((pos, self.curtype, self.curstack))
758
        (self.curtype, self.curstack) = (type, [])
759
        return
760
    def end_type(self, type):
761
        if self.curtype != type:
762
            raise PSTypeError('Type mismatch: %r != %r' % (self.curtype, type))
763
        objs = [ obj for (_,obj) in self.curstack ]
764
        (pos, self.curtype, self.curstack) = self.context.pop()
765
        return (pos, objs)
766
767
    def do_keyword(self, pos, token):
768
        return
769
    
770
    def nextobject(self, direct=False):
771
        '''
772
        Yields a list of objects: keywords, literals, strings, 
773
        numbers, arrays and dictionaries. Arrays and dictionaries
774
        are represented as Python sequence and dictionaries.
775
        '''
776
        while not self.results:
777
            (pos, token) = self.nexttoken()
778
            ##print (pos,token), (self.curtype, self.curstack)
779
            if (isinstance(token, int) or
780
                    isinstance(token, float) or
781
                    isinstance(token, bool) or
782
                    isinstance(token, str) or
783
                    isinstance(token, PSLiteral)):
784
                # normal token
785
                self.push((pos, token))
786
            elif token == KEYWORD_ARRAY_BEGIN:
787
                # begin array
788
                self.start_type(pos, 'a')
789
            elif token == KEYWORD_ARRAY_END:
790
                # end array
791
                try:
792
                    self.push(self.end_type('a'))
793
                except PSTypeError:
794
                    if STRICT: raise
795
            elif token == KEYWORD_DICT_BEGIN:
796
                # begin dictionary
797
                self.start_type(pos, 'd')
798
            elif token == KEYWORD_DICT_END:
799
                # end dictionary
800
                try:
801
                    (pos, objs) = self.end_type('d')
802
                    if len(objs) % 2 != 0:
803
                        raise PSSyntaxError(
804
                            'Invalid dictionary construct: %r' % objs)
805
                    d = dict((literal_name(k), v) \
806
                                 for (k,v) in choplist(2, objs))
807
                    self.push((pos, d))
808
                except PSTypeError:
809
                    if STRICT: raise
810
            else:
811
                self.do_keyword(pos, token)
812
            if self.context:
813
                continue
814
            else:
815
                if direct:
816
                    return self.pop(1)[0]
817
                self.flush()
818
        obj = self.results.pop(0)
819
        return obj
820
821
822
LITERAL_CRYPT = PSLiteralTable.intern('Crypt')
823
LITERALS_FLATE_DECODE = (PSLiteralTable.intern('FlateDecode'), PSLiteralTable.intern('Fl'))
824
LITERALS_LZW_DECODE = (PSLiteralTable.intern('LZWDecode'), PSLiteralTable.intern('LZW'))
825
LITERALS_ASCII85_DECODE = (PSLiteralTable.intern('ASCII85Decode'), PSLiteralTable.intern('A85'))
826
827
828
##  PDF Objects
829
##
830
class PDFObject(PSObject): pass
831
832
class PDFException(PSException): pass
833
class PDFTypeError(PDFException): pass
834
class PDFValueError(PDFException): pass
835
class PDFNotImplementedError(PSException): pass
836
837
838
##  PDFObjRef
839
##
840
class PDFObjRef(PDFObject):
841
    
842
    def __init__(self, doc, objid, genno):
843
        if objid == 0:
844
            if STRICT:
845
                raise PDFValueError('PDF object id cannot be 0.')
846
        self.doc = doc
847
        self.objid = objid
848
        self.genno = genno
849
        return
850
851
    def __repr__(self):
852
        return '<PDFObjRef:%d %d>' % (self.objid, self.genno)
853
854
    def resolve(self):
855
        return self.doc.getobj(self.objid)
856
857
858
# resolve
859
def resolve1(x):
860
    '''
861
    Resolve an object. If this is an array or dictionary,
862
    it may still contains some indirect objects inside.
863
    '''
864
    while isinstance(x, PDFObjRef):
865
        x = x.resolve()
866
    return x
867
868
def resolve_all(x):
869
    '''
870
    Recursively resolve X and all the internals.
871
    Make sure there is no indirect reference within the nested object.
872
    This procedure might be slow.
873
    '''
874
    while isinstance(x, PDFObjRef):
875
        x = x.resolve()
876
    if isinstance(x, list):
877
        x = [ resolve_all(v) for v in x ]
878
    elif isinstance(x, dict):
879
        for (k,v) in x.iteritems():
880
            x[k] = resolve_all(v)
881
    return x
882
883
def decipher_all(decipher, objid, genno, x):
884
    '''
885
    Recursively decipher X.
886
    '''
887
    if isinstance(x, str):
888
        return decipher(objid, genno, x)
889
    decf = lambda v: decipher_all(decipher, objid, genno, v)
890
    if isinstance(x, list):
891
        x = [decf(v) for v in x]
892
    elif isinstance(x, dict):
893
        x = dict((k, decf(v)) for (k, v) in x.iteritems())
894
    return x
895
896
897
# Type cheking
898
def int_value(x):
899
    x = resolve1(x)
900
    if not isinstance(x, int):
901
        if STRICT:
902
            raise PDFTypeError('Integer required: %r' % x)
903
        return 0
904
    return x
905
906
def float_value(x):
907
    x = resolve1(x)
908
    if not isinstance(x, float):
909
        if STRICT:
910
            raise PDFTypeError('Float required: %r' % x)
911
        return 0.0
912
    return x
913
914
def num_value(x):
915
    x = resolve1(x)
916
    if not (isinstance(x, int) or isinstance(x, float)):
917
        if STRICT:
918
            raise PDFTypeError('Int or Float required: %r' % x)
919
        return 0
920
    return x
921
922
def str_value(x):
923
    x = resolve1(x)
924
    if not isinstance(x, str):
925
        if STRICT:
926
            raise PDFTypeError('String required: %r' % x)
927
        return ''
928
    return x
929
930
def list_value(x):
931
    x = resolve1(x)
932
    if not (isinstance(x, list) or isinstance(x, tuple)):
933
        if STRICT:
934
            raise PDFTypeError('List required: %r' % x)
935
        return []
936
    return x
937
938
def dict_value(x):
939
    x = resolve1(x)
940
    if not isinstance(x, dict):
941
        if STRICT:
942
            raise PDFTypeError('Dict required: %r' % x)
943
        return {}
944
    return x
945
946
def stream_value(x):
947
    x = resolve1(x)
948
    if not isinstance(x, PDFStream):
949
        if STRICT:
950
            raise PDFTypeError('PDFStream required: %r' % x)
951
        return PDFStream({}, '')
952
    return x
953
954
# ascii85decode(data)
955
def ascii85decode(data):
956
  n = b = 0
957
  out = ''
958
  for c in data:
959
    if '!' <= c and c <= 'u':
960
      n += 1
961
      b = b*85+(ord(c)-33)
962
      if n == 5:
963
        out += struct.pack('>L',b)
964
        n = b = 0
965
    elif c == 'z':
966
      assert n == 0
967
      out += '\0\0\0\0'
968
    elif c == '~':
969
      if n:
970
        for _ in range(5-n):
971
          b = b*85+84
972
        out += struct.pack('>L',b)[:n-1]
973
      break
974
  return out
975
976
977
##  PDFStream type
978
class PDFStream(PDFObject):
979
    def __init__(self, dic, rawdata, decipher=None):
980
        length = int_value(dic.get('Length', 0))
981
        eol = rawdata[length:]
982
        # quick and dirty fix for false length attribute,
983
        # might not work if the pdf stream parser has a problem
984
        if decipher != None and decipher.__name__ == 'decrypt_aes':
985
            if (len(rawdata) % 16) != 0:
986
                cutdiv = len(rawdata) // 16
987
                rawdata = rawdata[:16*cutdiv]
988
        else:
989
            if eol in ('\r', '\n', '\r\n'):
990
                rawdata = rawdata[:length]
991
                
992
        self.dic = dic
993
        self.rawdata = rawdata
994
        self.decipher = decipher
995
        self.data = None
996
        self.decdata = None
997
        self.objid = None
998
        self.genno = None
999
        return
1000
1001
    def set_objid(self, objid, genno):
1002
        self.objid = objid
1003
        self.genno = genno
1004
        return
1005
    
1006
    def __repr__(self):
1007
        if self.rawdata:
1008
            return '<PDFStream(%r): raw=%d, %r>' % \
1009
                   (self.objid, len(self.rawdata), self.dic)
1010
        else:
1011
            return '<PDFStream(%r): data=%d, %r>' % \
1012
                   (self.objid, len(self.data), self.dic)
1013
1014
    def decode(self):
1015
        assert self.data is None and self.rawdata is not None
1016
        data = self.rawdata
1017
        if self.decipher:
1018
            # Handle encryption
1019
            data = self.decipher(self.objid, self.genno, data)
1020
            if gen_xref_stm:
1021
                self.decdata = data # keep decrypted data
1022
        if 'Filter' not in self.dic:
1023
            self.data = data
1024
            self.rawdata = None
1025
            ##print self.dict
1026
            return
1027
        filters = self.dic['Filter']
1028
        if not isinstance(filters, list):
1029
            filters = [ filters ]
1030
        for f in filters:
1031
            if f in LITERALS_FLATE_DECODE:
1032
                # will get errors if the document is encrypted.
1033
                data = zlib.decompress(data)
1034
            elif f in LITERALS_LZW_DECODE:
1035
                data = ''.join(LZWDecoder(StringIO(data)).run())
1036
            elif f in LITERALS_ASCII85_DECODE:
1037
                data = ascii85decode(data)
1038
            elif f == LITERAL_CRYPT:
1039
                raise PDFNotImplementedError('/Crypt filter is unsupported')
1040
            else:
1041
                raise PDFNotImplementedError('Unsupported filter: %r' % f)
1042
            # apply predictors
1043
            if 'DP' in self.dic:
1044
                params = self.dic['DP']
1045
            else:
1046
                params = self.dic.get('DecodeParms', {})
1047
            if 'Predictor' in params:
1048
                pred = int_value(params['Predictor'])
1049
                if pred:
1050
                    if pred != 12:
1051
                        raise PDFNotImplementedError(
1052
                            'Unsupported predictor: %r' % pred)
1053
                    if 'Columns' not in params:
1054
                        raise PDFValueError(
1055
                            'Columns undefined for predictor=12')
1056
                    columns = int_value(params['Columns'])
1057
                    buf = ''
1058
                    ent0 = '\x00' * columns
1059
                    for i in xrange(0, len(data), columns+1):
1060
                        pred = data[i]
1061
                        ent1 = data[i+1:i+1+columns]
1062
                        if pred == '\x02':
1063
                            ent1 = ''.join(chr((ord(a)+ord(b)) & 255) \
1064
                                               for (a,b) in zip(ent0,ent1))
1065
                        buf += ent1
1066
                        ent0 = ent1
1067
                    data = buf
1068
        self.data = data
1069
        self.rawdata = None
1070
        return
1071
1072
    def get_data(self):
1073
        if self.data is None:
1074
            self.decode()
1075
        return self.data
1076
1077
    def get_rawdata(self):
1078
        return self.rawdata
1079
1080
    def get_decdata(self):
1081
        if self.decdata is not None:
1082
            return self.decdata
1083
        data = self.rawdata
1084
        if self.decipher and data:
1085
            # Handle encryption
1086
            data = self.decipher(self.objid, self.genno, data)
1087
        return data
1088
1089
        
1090
##  PDF Exceptions
1091
##
1092
class PDFSyntaxError(PDFException): pass
1093
class PDFNoValidXRef(PDFSyntaxError): pass
1094
class PDFEncryptionError(PDFException): pass
1095
class PDFPasswordIncorrect(PDFEncryptionError): pass
1096
1097
# some predefined literals and keywords.
1098
LITERAL_OBJSTM = PSLiteralTable.intern('ObjStm')
1099
LITERAL_XREF = PSLiteralTable.intern('XRef')
1100
LITERAL_PAGE = PSLiteralTable.intern('Page')
1101
LITERAL_PAGES = PSLiteralTable.intern('Pages')
1102
LITERAL_CATALOG = PSLiteralTable.intern('Catalog')
1103
1104
1105
##  XRefs
1106
##
1107
1108
##  PDFXRef
1109
##
1110
class PDFXRef(object):
1111
1112
    def __init__(self):
1113
        self.offsets = None
1114
        return
1115
1116
    def __repr__(self):
1117
        return '<PDFXRef: objs=%d>' % len(self.offsets)
1118
1119
    def objids(self):
1120
        return self.offsets.iterkeys()
1121
1122
    def load(self, parser):
1123
        self.offsets = {}
1124
        while 1:
1125
            try:
1126
                (pos, line) = parser.nextline()
1127
            except PSEOF:
1128
                raise PDFNoValidXRef('Unexpected EOF - file corrupted?')
1129
            if not line:
1130
                raise PDFNoValidXRef('Premature eof: %r' % parser)
1131
            if line.startswith('trailer'):
1132
                parser.seek(pos)
1133
                break
1134
            f = line.strip().split(' ')
1135
            if len(f) != 2:
1136
                raise PDFNoValidXRef('Trailer not found: %r: line=%r' % (parser, line))
1137
            try:
1138
                (start, nobjs) = map(int, f)
1139
            except ValueError:
1140
                raise PDFNoValidXRef('Invalid line: %r: line=%r' % (parser, line))
1141
            for objid in xrange(start, start+nobjs):
1142
                try:
1143
                    (_, line) = parser.nextline()
1144
                except PSEOF:
1145
                    raise PDFNoValidXRef('Unexpected EOF - file corrupted?')
1146
                f = line.strip().split(' ')
1147
                if len(f) != 3:
1148
                    raise PDFNoValidXRef('Invalid XRef format: %r, line=%r' % (parser, line))
1149
                (pos, genno, use) = f
1150
                if use != 'n': continue
1151
                self.offsets[objid] = (int(genno), int(pos))
1152
        self.load_trailer(parser)
1153
        return
1154
    
1155
    KEYWORD_TRAILER = PSKeywordTable.intern('trailer')
1156
    def load_trailer(self, parser):
1157
        try:
1158
            (_,kwd) = parser.nexttoken()
1159
            assert kwd is self.KEYWORD_TRAILER
1160
            (_,dic) = parser.nextobject(direct=True)
1161
        except PSEOF:
1162
            x = parser.pop(1)
1163
            if not x:
1164
                raise PDFNoValidXRef('Unexpected EOF - file corrupted')
1165
            (_,dic) = x[0]
1166
        self.trailer = dict_value(dic)
1167
        return
1168
1169
    def getpos(self, objid):
1170
        try:
1171
            (genno, pos) = self.offsets[objid]
1172
        except KeyError:
1173
            raise
1174
        return (None, pos)
1175
1176
1177
##  PDFXRefStream
1178
##
1179
class PDFXRefStream(object):
1180
1181
    def __init__(self):
1182
        self.index = None
1183
        self.data = None
1184
        self.entlen = None
1185
        self.fl1 = self.fl2 = self.fl3 = None
1186
        return
1187
1188
    def __repr__(self):
1189
        return '<PDFXRef: objids=%s>' % self.index
1190
1191
    def objids(self):
1192
        for first, size in self.index:
1193
            for objid in xrange(first, first + size):
1194
                yield objid
1195
    
1196
    def load(self, parser, debug=0):
1197
        (_,objid) = parser.nexttoken() # ignored
1198
        (_,genno) = parser.nexttoken() # ignored
1199
        (_,kwd) = parser.nexttoken()
1200
        (_,stream) = parser.nextobject()
1201
        if not isinstance(stream, PDFStream) or \
1202
           stream.dic['Type'] is not LITERAL_XREF:
1203
            raise PDFNoValidXRef('Invalid PDF stream spec.')
1204
        size = stream.dic['Size']
1205
        index = stream.dic.get('Index', (0,size))
1206
        self.index = zip(islice(index, 0, None, 2),
1207
                         islice(index, 1, None, 2))
1208
        (self.fl1, self.fl2, self.fl3) = stream.dic['W']
1209
        self.data = stream.get_data()
1210
        self.entlen = self.fl1+self.fl2+self.fl3
1211
        self.trailer = stream.dic
1212
        return
1213
    
1214
    def getpos(self, objid):
1215
        offset = 0
1216
        for first, size in self.index:
1217
            if first <= objid  and objid < (first + size):
1218
                break
1219
            offset += size
1220
        else:
1221
            raise KeyError(objid)
1222
        i = self.entlen * ((objid - first) + offset)
1223
        ent = self.data[i:i+self.entlen]
1224
        f1 = nunpack(ent[:self.fl1], 1)
1225
        if f1 == 1:
1226
            pos = nunpack(ent[self.fl1:self.fl1+self.fl2])
1227
            genno = nunpack(ent[self.fl1+self.fl2:])
1228
            return (None, pos)
1229
        elif f1 == 2:
1230
            objid = nunpack(ent[self.fl1:self.fl1+self.fl2])
1231
            index = nunpack(ent[self.fl1+self.fl2:])
1232
            return (objid, index)
1233
        # this is a free object
1234
        raise KeyError(objid)
1235
1236
1237
##  PDFDocument
1238
##
1239
##  A PDFDocument object represents a PDF document.
1240
##  Since a PDF file is usually pretty big, normally it is not loaded
1241
##  at once. Rather it is parsed dynamically as processing goes.
1242
##  A PDF parser is associated with the document.
1243
##
1244
class PDFDocument(object):
1245
1246
    def __init__(self):
1247
        self.xrefs = []
1248
        self.objs = {}
1249
        self.parsed_objs = {}
1250
        self.root = None
1251
        self.catalog = None
1252
        self.parser = None
1253
        self.encryption = None
1254
        self.decipher = None
1255
        # dictionaries for fileopen
1256
        self.fileopen = {}
1257
        self.urlresult = {}        
1258
        self.ready = False
1259
        return
1260
1261
    # set_parser(parser)
1262
    #   Associates the document with an (already initialized) parser object.
1263
    def set_parser(self, parser):
1264
        if self.parser: return
1265
        self.parser = parser
1266
        # The document is set to be temporarily ready during collecting
1267
        # all the basic information about the document, e.g.
1268
        # the header, the encryption information, and the access rights 
1269
        # for the document.
1270
        self.ready = True
1271
        # Retrieve the information of each header that was appended
1272
        # (maybe multiple times) at the end of the document.
1273
        self.xrefs = parser.read_xref()
1274
        for xref in self.xrefs:
1275
            trailer = xref.trailer
1276
            if not trailer: continue
1277
1278
            # If there's an encryption info, remember it.
1279
            if 'Encrypt' in trailer:
1280
                #assert not self.encryption
1281
                try:
1282
                    self.encryption = (list_value(trailer['ID']),
1283
                                   dict_value(trailer['Encrypt']))
1284
                # fix for bad files
1285
                except:
1286
                    self.encryption = ('ffffffffffffffffffffffffffffffffffff',
1287
                                       dict_value(trailer['Encrypt']))
1288
            if 'Root' in trailer:
1289
                self.set_root(dict_value(trailer['Root']))
1290
                break
1291
        else:
1292
            raise PDFSyntaxError('No /Root object! - Is this really a PDF?')
1293
        # The document is set to be non-ready again, until all the
1294
        # proper initialization (asking the password key and
1295
        # verifying the access permission, so on) is finished.
1296
        self.ready = False
1297
        return
1298
1299
    # set_root(root)
1300
    #   Set the Root dictionary of the document.
1301
    #   Each PDF file must have exactly one /Root dictionary.
1302
    def set_root(self, root):
1303
        self.root = root
1304
        self.catalog = dict_value(self.root)
1305
        if self.catalog.get('Type') is not LITERAL_CATALOG:
1306
            if STRICT:
1307
                raise PDFSyntaxError('Catalog not found!')
1308
        return
1309
    # initialize(password='')
1310
    #   Perform the initialization with a given password.
1311
    #   This step is mandatory even if there's no password associated
1312
    #   with the document.
1313
    def initialize(self, password=''):
1314
        if not self.encryption:
1315
            self.is_printable = self.is_modifiable = self.is_extractable = True
1316
            self.ready = True
1317
            return
1318
        (docid, param) = self.encryption
1319
        type = literal_name(param['Filter'])
1320
        if type == 'Adobe.APS':
1321
            return self.initialize_adobe_ps(password, docid, param)
1322
        if type == 'Standard':
1323
            return self.initialize_standard(password, docid, param)
1324
        if type == 'EBX_HANDLER':
1325
            return self.initialize_ebx(password, docid, param)
1326
        if type == 'FOPN_fLock':
1327
            # remove of unnecessairy password attribute
1328
            return self.initialize_fopn_flock(docid, param)  
1329
        if type == 'FOPN_foweb':
1330
            # remove of unnecessairy password attribute
1331
            return self.initialize_fopn(docid, param)
1332
        raise PDFEncryptionError('Unknown filter: param=%r' % param)
1333
1334
    def initialize_adobe_ps(self, password, docid, param):
1335
        global KEYFILEPATH
1336
        self.decrypt_key = self.genkey_adobe_ps(param)
1337
        self.genkey = self.genkey_v4
1338
        self.decipher = self.decrypt_aes
1339
        self.ready = True
1340
        return
1341
    
1342
    def getPrincipalKey(self, k=None, url=None, referer=None):
1343
            if url == None:
1344
                    url="ssl://edc.bibliothek-digital.de/edcws/services/urn:EDCLicenseService"
1345
            data1='<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SO'+\
1346
            'AP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http'+\
1347
            '://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/'+\
1348
            'XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns1="'+\
1349
            'http://edc.adobe.com/edcwebservice" xmlns:impl="http://localhost:8080/axis/s'+\
1350
            'ervices/urn:EDCLicenseService" xmlns:ns2="http://common.edc.adobe.com" xmlns:ns1="'+\
1351
            'http://ns.adobe.com/PolicyServer/ws"><SOAP-ENV:Header><EDCSecurity>&lt;wsse:Security '+\
1352
            'xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-'+\
1353
            '1.0.xsd"&gt;&lt;wsse:UsernameToken&gt;&lt;wsse:Username&gt;edc_anonymous&lt;/wsse:Username&'+\
1354
            'gt;&lt;wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-'+\
1355
            'token-profile-1.0#PasswordText"&gt;edc_anonymous&lt;/wsse:Password&gt;&lt;/wsse:UsernameToken&'+\
1356
            'gt;&lt;/wsse:Security&gt;</EDCSecurity><Version>7</Version><Locale>de-de</Locale></SOAP-ENV:Header>'+\
1357
            '<SOAP-ENV:Body><impl:synchronize><SynchronizationRequest><firstTime>1</firstTime><licenseSeqNum>0</'+\
1358
            'licenseSeqNum><policySeqNum>1</policySeqNum><revocationSeqNum>0</revocationSeqNum><'+\
1359
            'watermarkTemplateSeqNum>0</watermarkTemplateSeqNum></SynchronizationRequest></'+\
1360
            'impl:synchronize></SOAP-ENV:Body></SOAP-ENV:Envelope>'
1361
            if k not in url[:40]:
1362
                return None
1363
            #~ extract host and path:
1364
            host=re.compile(r'[a-zA-Z]://([^/]+)/.+', re.I).search(url).group(1)
1365
            urlpath=re.compile(r'[a-zA-Z]://[^/]+(/.+)', re.I).search(url).group(1)
1366
            
1367
            # open a socket connection on port 80
1368
1369
            conn = httplib.HTTPSConnection(host, 443)
1370
            
1371
            #~ Headers for request
1372
            headers={"Accept": "*/*", "Host": host, "User-Agent": "Mozilla/3.0 (compatible; Acrobat EDC SOAP 1.0)",
1373
                     "Content-Type": "text/xml; charset=utf-8", "Cache-Control": "no-cache", "SOAPAction": ""}
1374
            
1375
            # send data1 and headers
1376
            try:
1377
                    conn.request("POST", urlpath, data1, headers)
1378
            except:
1379
                    raise ADEPTError("Could not post request to '"+host+"'.")
1380
            
1381
            # read respose
1382
            try:
1383
                    response = conn.getresponse()
1384
                    responsedata=response.read()
1385
            except:
1386
                    raise ADEPTError("Could not read response from '"+host+"'.")
1387
            
1388
            # close connection
1389
            conn.close()
1390
            
1391
            try:
1392
                    key=re.compile(r'PricipalKey"((?!<key>).)*<key[^>]*>(((?!</key>).)*)</key>', re.I).search(responsedata).group(2)
1393
            
1394
            except :
1395
                    key=None
1396
            return key
1397
1398
    def genkey_adobe_ps(self, param):
1399
        # nice little offline principal keys dictionary
1400
        principalkeys = { 'bibliothek-digital.de': 'Dzqx8McQUNd2CDzBVmtnweUxVWlqJTMqyYtiDIc4dZI='.decode('base64')}
1401
        for k, v in principalkeys.iteritems():
1402
            result = self.getPrincipalKey(k)
1403
            #print result
1404
            if result != None:
1405
                principalkeys[k] = result.decode('base64')
1406
            else:
1407
                raise ADEPTError("No (Online) PrincipalKey found.")
1408
                
1409
        self.is_printable = self.is_modifiable = self.is_extractable = True
1410
##        print 'keyvalue'
1411
##        print len(keyvalue)
1412
##        print keyvalue.encode('hex')
1413
        length = int_value(param.get('Length', 0)) / 8
1414
        edcdata = str_value(param.get('EDCData')).decode('base64')
1415
        pdrllic = str_value(param.get('PDRLLic')).decode('base64')
1416
        pdrlpol = str_value(param.get('PDRLPol')).decode('base64')          
1417
        #print 'ecd rights'
1418
        edclist = []
1419
        for pair in edcdata.split('\n'):
1420
            edclist.append(pair)
1421
##        print edclist
1422
##        print 'edcdata decrypted'
1423
##        print edclist[0].decode('base64').encode('hex')
1424
##        print edclist[1].decode('base64').encode('hex')
1425
##        print edclist[2].decode('base64').encode('hex')
1426
##        print edclist[3].decode('base64').encode('hex')
1427
##        print 'offlinekey'
1428
##        print len(edclist[9].decode('base64'))
1429
##        print pdrllic
1430
        # principal key request
1431
        for key in principalkeys:
1432
            if key in pdrllic:
1433
                principalkey = principalkeys[key]
1434
            else:
1435
                raise ADEPTError('Cannot find principal key for this pdf')
1436
##        print 'minorversion'
1437
##        print int(edclist[8])
1438
        # fix for minor version
1439
##        minorversion = int(edclist[8]) - 100
1440
##        if minorversion < 1:
1441
##            minorversion = 1
1442
##        print int(minorversion)
1443
        shakey = SHA256.new()
1444
        shakey.update(principalkey)
1445
##        for i in range(0,minorversion):
1446
##            shakey.update(principalkey)
1447
        shakey = shakey.digest()
1448
##        shakey = SHA256.new(principalkey).digest()
1449
        ivector = 16 * chr(0)
1450
        #print shakey
1451
        plaintext = AES.new(shakey,AES.MODE_CBC,ivector).decrypt(edclist[9].decode('base64'))
1452
        if plaintext[-16:] != 16 * chr(16):
1453
            raise ADEPTError('Offlinekey cannot be decrypted, aborting (hint: redownload pdf) ...')
1454
        pdrlpol = AES.new(plaintext[16:32],AES.MODE_CBC,edclist[2].decode('base64')).decrypt(pdrlpol)
1455
        if ord(pdrlpol[-1]) < 1 or ord(pdrlpol[-1]) > 16:
1456
            raise ADEPTError('Could not decrypt PDRLPol, aborting ...')
1457
        else:
1458
            cutter = -1 * ord(pdrlpol[-1])
1459
            #print cutter
1460
            pdrlpol = pdrlpol[:cutter]            
1461
        #print plaintext.encode('hex')
1462
        #print 'pdrlpol'
1463
        #print pdrlpol
1464
        return plaintext[:16]
1465
    
1466
    PASSWORD_PADDING = '(\xbfN^Nu\x8aAd\x00NV\xff\xfa\x01\x08..' \
1467
                       '\x00\xb6\xd0h>\x80/\x0c\xa9\xfedSiz'
1468
    # experimental aes pw support
1469
    def initialize_standard(self, password, docid, param):
1470
        # copy from a global variable
1471
        V = int_value(param.get('V', 0))
1472
        if (V <=0 or V > 4):
1473
            raise PDFEncryptionError('Unknown algorithm: param=%r' % param)
1474
        length = int_value(param.get('Length', 40)) # Key length (bits)
1475
        O = str_value(param['O'])
1476
        R = int_value(param['R']) # Revision
1477
        if 5 <= R:
1478
            raise PDFEncryptionError('Unknown revision: %r' % R)
1479
        U = str_value(param['U'])
1480
        P = int_value(param['P'])
1481
        try:
1482
            EncMetadata = str_value(param['EncryptMetadata'])
1483
        except:
1484
            EncMetadata = 'True'
1485
        self.is_printable = bool(P & 4)        
1486
        self.is_modifiable = bool(P & 8)
1487
        self.is_extractable = bool(P & 16)
1488
        self.is_annotationable = bool(P & 32)
1489
        self.is_formsenabled = bool(P & 256)
1490
        self.is_textextractable = bool(P & 512)
1491
        self.is_assemblable = bool(P & 1024)
1492
        self.is_formprintable = bool(P & 2048) 
1493
        # Algorithm 3.2
1494
        password = (password+self.PASSWORD_PADDING)[:32] # 1
1495
        hash = hashlib.md5(password) # 2
1496
        hash.update(O) # 3
1497
        hash.update(struct.pack('<l', P)) # 4
1498
        hash.update(docid[0]) # 5
1499
        # aes special handling if metadata isn't encrypted
1500
        if EncMetadata == ('False' or 'false'):
1501
            hash.update('ffffffff'.decode('hex'))
1502
            # 6
1503
##            raise PDFNotImplementedError(
1504
##                'Revision 4 encryption is currently unsupported')
1505
        if 5 <= R:
1506
            # 8
1507
            for _ in xrange(50):
1508
                hash = hashlib.md5(hash.digest()[:length/8])
1509
        key = hash.digest()[:length/8]
1510
        if R == 2:
1511
            # Algorithm 3.4
1512
            u1 = ARC4.new(key).decrypt(password)
1513
        elif R >= 3:
1514
            # Algorithm 3.5
1515
            hash = hashlib.md5(self.PASSWORD_PADDING) # 2
1516
            hash.update(docid[0]) # 3
1517
            x = ARC4.new(key).decrypt(hash.digest()[:16]) # 4
1518
            for i in xrange(1,19+1):
1519
                k = ''.join( chr(ord(c) ^ i) for c in key )
1520
                x = ARC4.new(k).decrypt(x)
1521
            u1 = x+x # 32bytes total
1522
        if R == 2:
1523
            is_authenticated = (u1 == U)
1524
        else:
1525
            is_authenticated = (u1[:16] == U[:16])
1526
        if not is_authenticated:
1527
            raise ADEPTError('Password is not correct.')
1528
##            raise PDFPasswordIncorrect
1529
        self.decrypt_key = key
1530
        # genkey method
1531
        if V == 1 or V == 2:
1532
            self.genkey = self.genkey_v2
1533
        elif V == 3:
1534
            self.genkey = self.genkey_v3
1535
        elif V == 4:
1536
            self.genkey = self.genkey_v2
1537
         #self.genkey = self.genkey_v3 if V == 3 else self.genkey_v2
1538
        # rc4
1539
        if V != 4:
1540
            self.decipher = self.decipher_rc4  # XXX may be AES
1541
        # aes
1542
        elif V == 4 and Length == 128:
1543
            elf.decipher = self.decipher_aes
1544
        elif V == 4 and Length == 256:
1545
            raise PDFNotImplementedError('AES256 encryption is currently unsupported')
1546
        self.ready = True
1547
        return
1548
1549
    def initialize_ebx(self, password, docid, param):
1550
        global KEYFILEPATH
1551
        self.is_printable = self.is_modifiable = self.is_extractable = True
1552
        # keyfile path is wrong
1553
        if KEYFILEPATH == False:
1554
            errortext = 'Cannot find adeptkey.der keyfile. Use ineptkey to generate it.'
1555
            raise ADEPTError(errortext)
1556
        with open(password, 'rb') as f:
1557
            keyder = f.read()
1558
        #    KEYFILEPATH = ''
1559
        key = ASN1Parser([ord(x) for x in keyder])
1560
        key = [bytesToNumber(key.getChild(x).value) for x in xrange(1, 4)]
1561
        rsa = RSA.construct(key)
1562
        length = int_value(param.get('Length', 0)) / 8
1563
        rights = str_value(param.get('ADEPT_LICENSE')).decode('base64')
1564
        rights = zlib.decompress(rights, -15)
1565
        rights = etree.fromstring(rights)
1566
        expr = './/{http://ns.adobe.com/adept}encryptedKey'
1567
        bookkey = ''.join(rights.findtext(expr)).decode('base64')
1568
        bookkey = rsa.decrypt(bookkey)
1569
        if bookkey[0] != '\x02':
1570
            raise ADEPTError('error decrypting book session key')
1571
        index = bookkey.index('\0') + 1
1572
        bookkey = bookkey[index:]
1573
        ebx_V = int_value(param.get('V', 4))
1574
        ebx_type = int_value(param.get('EBX_ENCRYPTIONTYPE', 6))
1575
        # added because of the booktype / decryption book session key error
1576
        if ebx_V == 3:
1577
            V = 3        
1578
        elif ebx_V < 4 or ebx_type < 6:
1579
            V = ord(bookkey[0])
1580
            bookkey = bookkey[1:]
1581
        else:
1582
            V = 2
1583
        if length and len(bookkey) != length:
1584
            raise ADEPTError('error decrypting book session key')
1585
        self.decrypt_key = bookkey
1586
        self.genkey = self.genkey_v3 if V == 3 else self.genkey_v2
1587
        self.decipher = self.decrypt_rc4
1588
        self.ready = True
1589
        return
1590
1591
    # fileopen support    
1592
    def initialize_fopn_flock(self, docid, param):
1593
        raise ADEPTError('FOPN_fLock not supported, yet ...')
1594
        # debug mode processing
1595
        global DEBUG_MODE
1596
        global IVERSION
1597
        if DEBUG_MODE == True:
1598
            if os.access('.',os.W_OK) == True:
1599
                debugfile = open('ineptpdf-'+IVERSION+'-debug.txt','w')
1600
            else:
1601
                raise ADEPTError('Cannot write debug file, current directory is not writable')
1602
        self.is_printable = self.is_modifiable = self.is_extractable = True
1603
        # get parameters and add it to the fo dictionary
1604
        self.fileopen['V'] = int_value(param.get('V',2))        
1605
        # crypt base
1606
        (docid, param) = self.encryption
1607
        #rights = dict_value(param['Info'])
1608
        rights = param['Info']        
1609
        #print rights
1610
        if DEBUG_MODE == True: debugfile.write(rights + '\n\n')
1611
##        for pair in rights.split(';'):
1612
##            try:
1613
##                key, value = pair.split('=',1)
1614
##                self.fileopen[key] = value
1615
##            # fix for some misconfigured INFO variables
1616
##            except:
1617
##                pass
1618
##        kattr = { 'SVID': 'ServiceID', 'DUID': 'DocumentID', 'I3ID': 'Ident3ID', \
1619
##                  'I4ID': 'Ident4ID', 'VERS': 'EncrVer', 'PRID': 'USR'}
1620
##        for keys in  kattr:
1621
##            try:
1622
##                self.fileopen[kattr[keys]] = self.fileopen[keys]
1623
##                del self.fileopen[keys]
1624
##            except:
1625
##                continue
1626
        # differentiate OS types
1627
##        sysplatform = sys.platform
1628
##        # if ostype is Windows
1629
##        if sysplatform=='win32':
1630
##            self.osuseragent = 'Windows NT 6.0'
1631
##            self.get_macaddress = self.get_win_macaddress
1632
##            self.fo_sethwids = self.fo_win_sethwids
1633
##            self.BrowserCookie = WinBrowserCookie
1634
##        elif sysplatform=='linux2':
1635
##            adeptout = 'Linux is not supported, yet.\n'
1636
##            raise ADEPTError(adeptout)
1637
##            self.osuseragent = 'Linux i686'
1638
##            self.get_macaddress = self.get_linux_macaddress            
1639
##            self.fo_sethwids = self.fo_linux_sethwids            
1640
##        else:
1641
##            adeptout = ''
1642
##            adeptout = adeptout + 'Due to various privacy violations from Apple\n'
1643
##            adeptout = adeptout + 'Mac OS X support is disabled by default.'
1644
##            raise ADEPTError(adeptout)            
1645
##        # add static arguments for http/https request
1646
##        self.fo_setattributes()
1647
##        # add hardware specific arguments for http/https request        
1648
##        self.fo_sethwids()
1649
##
1650
##        if 'Code' in self.urlresult:            
1651
##            if self.fileopen['Length'] == len(self.urlresult['Code']):
1652
##                self.decrypt_key = self.urlresult['Code']
1653
##            else:
1654
##                self.decrypt_key = self.urlresult['Code'].decode('hex')
1655
##        else:
1656
##            raise ADEPTError('Cannot find decryption key.')
1657
        self.decrypt_key = 'stuff'
1658
        self.genkey = self.genkey_v2
1659
        self.decipher = self.decrypt_rc4
1660
        self.ready = True
1661
        return
1662
1663
    def initialize_fopn(self, docid, param):
1664
        # debug mode processing
1665
        global DEBUG_MODE
1666
        global IVERSION
1667
        if DEBUG_MODE == True:
1668
            if os.access('.',os.W_OK) == True:
1669
                debugfile = open('ineptpdf-'+IVERSION+'-debug.txt','w')
1670
            else:
1671
                raise ADEPTError('Cannot write debug file, current directory is not writable')
1672
        self.is_printable = self.is_modifiable = self.is_extractable = True
1673
        # get parameters and add it to the fo dictionary
1674
        self.fileopen['Length'] = int_value(param.get('Length', 0)) / 8
1675
        self.fileopen['VEID'] = str_value(param.get('VEID'))
1676
        self.fileopen['BUILD'] = str_value(param.get('BUILD'))
1677
        self.fileopen['SVID'] = str_value(param.get('SVID'))
1678
        self.fileopen['DUID'] = str_value(param.get('DUID'))
1679
        self.fileopen['V'] = int_value(param.get('V',2))        
1680
        # crypt base
1681
        rights = str_value(param.get('INFO')).decode('base64')
1682
        rights = self.genkey_fileopeninfo(rights)
1683
        if DEBUG_MODE == True: debugfile.write(rights + '\n\n')    
1684
        for pair in rights.split(';'):
1685
            try:
1686
                key, value = pair.split('=',1)
1687
                self.fileopen[key] = value
1688
            # fix for some misconfigured INFO variables
1689
            except:
1690
                pass
1691
        kattr = { 'SVID': 'ServiceID', 'DUID': 'DocumentID', 'I3ID': 'Ident3ID', \
1692
                  'I4ID': 'Ident4ID', 'VERS': 'EncrVer', 'PRID': 'USR'}
1693
        for keys in  kattr:
1694
            # fishing some misconfigured slashs out of it
1695
            try:
1696
                self.fileopen[kattr[keys]] = urllib.quote(self.fileopen[keys],safe='')
1697
                del self.fileopen[keys]
1698
            except:
1699
                continue
1700
        # differentiate OS types
1701
        sysplatform = sys.platform
1702
        # if ostype is Windows
1703
        if sysplatform=='win32':
1704
            self.osuseragent = 'Windows NT 6.0'
1705
            self.get_macaddress = self.get_win_macaddress
1706
            self.fo_sethwids = self.fo_win_sethwids
1707
            self.BrowserCookie = WinBrowserCookie
1708
        elif sysplatform=='linux2':
1709
            adeptout = 'Linux is not supported, yet.\n'
1710
            raise ADEPTError(adeptout)
1711
            self.osuseragent = 'Linux i686'
1712
            self.get_macaddress = self.get_linux_macaddress            
1713
            self.fo_sethwids = self.fo_linux_sethwids            
1714
        else:
1715
            adeptout = ''
1716
            adeptout = adeptout + 'Mac OS X is not supported, yet.'
1717
            adeptout = adeptout + 'Read the blogs FAQs for more information'
1718
            raise ADEPTError(adeptout)            
1719
        # add static arguments for http/https request
1720
        self.fo_setattributes()
1721
        # add hardware specific arguments for http/https request        
1722
        self.fo_sethwids()
1723
        #if DEBUG_MODE == True: debugfile.write(self.fileopen)
1724
        if 'UURL' in self.fileopen:
1725
            buildurl = self.fileopen['UURL']
1726
        else:
1727
            buildurl = self.fileopen['PURL']
1728
        # fix for bad DPRM structure
1729
        if self.fileopen['DPRM'][0] != r'/':
1730
            self.fileopen['DPRM'] = r'/' + self.fileopen['DPRM']
1731
        # genius fix for bad server urls (IMHO)
1732
        if '?' in self.fileopen['DPRM']:
1733
            buildurl = buildurl + self.fileopen['DPRM'] + '&'
1734
        else:
1735
            buildurl = buildurl + self.fileopen['DPRM'] + '?'            
1736
1737
        # debug customization
1738
        #self.fileopen['Machine'] = ''
1739
        #self.fileopen['Disk'] = ''
1740
1741
1742
        surl = ( 'Stamp', 'Mode', 'USR', 'ServiceID', 'DocumentID',\
1743
                 'Ident3ID', 'Ident4ID','DocStrFmt', 'OSType', 'OSName', 'OSData', 'Language',\
1744
                 'LngLCID', 'LngRFC1766', 'LngISO4Char', 'Build', 'ProdVer', 'EncrVer',\
1745
                 'Machine', 'Disk', 'Uuid', 'PrevMach', 'PrevDisk',\
1746
                 'FormHFT',\
1747
                 'SelServer', 'AcroVersion', 'AcroProduct', 'AcroReader',\
1748
                 'AcroCanEdit', 'AcroPrefIDib', 'InBrowser', 'CliAppName',\
1749
                 'DocIsLocal', 'DocPathUrl', 'VolName', 'VolType', 'VolSN',\
1750
                 'FSName',  'FowpKbd', 'OSBuild',\
1751
                  'RequestSchema')
1752
        
1753
        #settings request and special modes
1754
        if 'EVER' in self.fileopen and float(self.fileopen['EVER']) < 3.8:
1755
            self.fileopen['Mode'] = 'ICx'
1756
       
1757
        origurl = buildurl
1758
        buildurl = buildurl + 'Request=Setting'        
1759
        for keys in surl:
1760
            try:
1761
                buildurl = buildurl + '&' + keys + '=' + self.fileopen[keys]
1762
            except:
1763
                continue
1764
        if DEBUG_MODE == True: debugfile.write( 'settings url:\n')
1765
        if DEBUG_MODE == True: debugfile.write( buildurl+'\n\n')
1766
        # custom user agent identification?
1767
        if 'AGEN' in self.fileopen:
1768
            useragent = self.fileopen['AGEN']
1769
            urllib.URLopener.version = useragent
1770
        # attribute doesn't exist - take the default user agent
1771
        else:
1772
            urllib.URLopener.version = self.osuseragent
1773
        # try to open the url
1774
        try:
1775
            u = urllib.urlopen(buildurl)
1776
            u.geturl()
1777
            result = u.read()
1778
        except:
1779
            raise ADEPTError('No internet connection or a blocking firewall!')
1780
##        finally:
1781
##            u.close()
1782
        # getting rid of the line feed
1783
        if DEBUG_MODE == True: debugfile.write('Settings'+'\n')
1784
        if DEBUG_MODE == True: debugfile.write(result+'\n\n')
1785
        #get rid of unnecessary characters
1786
        result = result.rstrip('\n')
1787
        result = result.rstrip(chr(13))
1788
        result = result.lstrip('\n')
1789
        result = result.lstrip(chr(13)) 
1790
        self.surlresult = {}
1791
        for pair in result.split('&'):
1792
            try:
1793
                key, value = pair.split('=',1)
1794
                # fix for bad server response
1795
                if key not in self.surlresult:
1796
                    self.surlresult[key] = value
1797
            except:
1798
                pass
1799
        if 'RequestSchema' in self.surlresult:
1800
            self.fileopen['RequestSchema'] = self.surlresult['RequestSchema']
1801
        if 'ServerSessionData' in self.surlresult:
1802
            self.fileopen['ServerSessionData'] = self.surlresult['ServerSessionData']
1803
        if 'SetScope' in self.surlresult:
1804
            self.fileopen['RequestSchema'] = self.surlresult['SetScope']            
1805
        #print self.surlresult
1806
        if 'RetVal' in self.surlresult and 'SEMO' not in self.fileopen and(('Reason' in self.surlresult and \
1807
           self.surlresult['Reason'] == 'AskUnp') or ('SetTarget' in self.surlresult and\
1808
                                               self.surlresult['SetTarget'] == 'UnpDlg')):
1809
            # get user and password dialog
1810
            try:
1811
                self.gen_pw_dialog(self.surlresult['UnpUiName'], self.surlresult['UnpUiPass'],\
1812
                                   self.surlresult['UnpUiTitle'], self.surlresult['UnpUiOk'],\
1813
                                   self.surlresult['UnpUiSunk'], self.surlresult['UnpUiComm'])
1814
            except:
1815
                self.gen_pw_dialog()
1816
            
1817
        # the fileopen check might not be always right because of strange server responses    
1818
        if 'SEMO' in self.fileopen and (self.fileopen['SEMO'] == '1'\
1819
            or self.fileopen['SEMO'] == '2') and ('CSES' in self.fileopen and\
1820
                                                  self.fileopen['CSES'] != 'fileopen'):
1821
            # get the url name for the cookie(s)
1822
            if 'CURL' in self.fileopen:
1823
                self.surl = self.fileopen['CURL']
1824
            if 'CSES' in self.fileopen:
1825
                self.cses = self.fileopen['CSES']
1826
            elif 'PHOS' in self.fileopen:
1827
                self.surl = self.fileopen['PHOS']
1828
            elif 'LHOS' in self.fileopen:
1829
                self.surl = self.fileopen['LHOS']
1830
            else:
1831
                raise ADEPTError('unknown Cookie name.\n Check ineptpdf forum for further assistance')
1832
            self.pwfieldreq = 1
1833
            # session cookie processing
1834
            if self.fileopen['SEMO'] == '1':
1835
                cookies = self.BrowserCookie()
1836
                #print self.cses
1837
                #print self.surl
1838
                csession = cookies.getcookie(self.cses,self.surl)
1839
                if csession != None:
1840
                    self.fileopen['Session'] = csession
1841
                    self.gui = False
1842
                # fallback
1843
                else:
1844
                    self.pwtk = Tkinter.Tk()
1845
                    self.pwtk.title('Ineptpdf8')
1846
                    self.pwtk.minsize(150, 0)
1847
                    infotxt1 = 'Get the session cookie key manually (Firefox step-by-step:\n'+\
1848
                               'Start Firefox -> Tools -> Options -> Privacy -> Show Cookies\n'+\
1849
                               '-> Search for a cookie from ' + self.surl +' with the\n'+\
1850
                               'name ' + self.cses +' and copy paste the content field in the\n'+\
1851
                               'Session Content field. Remove possible spaces or new lines at the '+\
1852
                               'end\n (cursor must be blinking right behind the last character)'
1853
                    self.label0 = Tkinter.Label(self.pwtk, text=infotxt1)
1854
                    self.label0.pack()
1855
                    self.label1 = Tkinter.Label(self.pwtk, text="Session Content")
1856
                    self.pwfieldreq = 0
1857
                    self.gui = True
1858
            # user cookie processing                                     
1859
            elif self.fileopen['SEMO'] == '2':
1860
                cookies = self.BrowserCookie()
1861
                #print self.cses
1862
                #print self.surl
1863
                name = cookies.getcookie('name',self.surl)
1864
                passw = cookies.getcookie('pass',self.surl)                    
1865
                if name != None or passw != None:
1866
                    self.fileopen['UserName'] = urllib.quote(name)
1867
                    self.fileopen['UserPass'] = urllib.quote(passw)
1868
                    self.gui = False
1869
                # fallback
1870
                else:
1871
                    self.pwtk = Tkinter.Tk()
1872
                    self.pwtk.title('Ineptpdf8')
1873
                    self.pwtk.minsize(150, 0)
1874
                    self.label1 = Tkinter.Label(self.pwtk, text="Username")
1875
                    infotxt1 = 'Get the user cookie keys manually (Firefox step-by-step:\n'+\
1876
                               'Start Firefox -> Tools -> Options -> Privacy -> Show Cookies\n'+\
1877
                               '-> Search for cookies from ' + self.surl +' with the\n'+\
1878
                               'name name in the user field and copy paste the content field in the\n'+\
1879
                               'username field. Do the same with the name pass in the password field).'
1880
                    self.label0 = Tkinter.Label(self.pwtk, text=infotxt1)
1881
                    self.label0.pack()                                       
1882
                    self.pwfieldreq = 1
1883
                    self.gui = True
1884
##            else:
1885
##                self.pwtk = Tkinter.Tk()
1886
##                self.pwtk.title('Ineptpdf8')
1887
##                self.pwtk.minsize(150, 0)
1888
##                self.pwfieldreq = 0
1889
##                self.label1 = Tkinter.Label(self.pwtk, text="Username")
1890
##                self.pwfieldreq = 1
1891
##                self.gui = True
1892
            if self.gui == True:
1893
                self.un_entry = Tkinter.Entry(self.pwtk)
1894
                # cursor here
1895
                self.un_entry.focus()
1896
                self.label2 = Tkinter.Label(self.pwtk, text="Password")
1897
                self.pw_entry = Tkinter.Entry(self.pwtk, show="*")
1898
                self.button = Tkinter.Button(self.pwtk, text='Go for it!', command=self.fo_save_values)
1899
                # widget layout, stack vertical
1900
                self.label1.pack()
1901
                self.un_entry.pack()
1902
                # create a password label and field
1903
                if self.pwfieldreq == 1:
1904
                    self.label2.pack()
1905
                    self.pw_entry.pack()
1906
                self.button.pack()
1907
                self.pwtk.update()            
1908
                # start the event loop
1909
                self.pwtk.mainloop()
1910
          
1911
        # original request
1912
        # drive through tupple for building the permission url
1913
        burl = ( 'Stamp', 'Mode', 'USR', 'ServiceID', 'DocumentID',\
1914
                 'Ident3ID', 'Ident4ID','DocStrFmt', 'OSType', 'Language',\
1915
                 'LngLCID', 'LngRFC1766', 'LngISO4Char', 'Build', 'ProdVer', 'EncrVer',\
1916
                 'Machine', 'Disk', 'Uuid', 'PrevMach', 'PrevDisk', 'User', 'SaUser', 'SaSID',\
1917
                 # special security measures
1918
                 'HostIsDomain', 'PhysHostname', 'LogiHostname', 'SaRefDomain',\
1919
                 'FormHFT', 'UserName', 'UserPass', 'Session', \
1920
                 'SelServer', 'AcroVersion', 'AcroProduct', 'AcroReader',\
1921
                 'AcroCanEdit', 'AcroPrefIDib', 'InBrowser', 'CliAppName',\
1922
                 'DocIsLocal', 'DocPathUrl', 'VolName', 'VolType', 'VolSN',\
1923
                 'FSName', 'ServerSessionData', 'FowpKbd', 'OSBuild', \
1924
                 'DocumentSessionData', 'RequestSchema')
1925
        
1926
        buildurl = origurl
1927
        buildurl = buildurl + 'Request=DocPerm'
1928
        for keys in burl:
1929
            try:
1930
                buildurl = buildurl + '&' + keys + '=' + self.fileopen[keys]
1931
            except:
1932
                continue
1933
        if DEBUG_MODE == True: debugfile.write('1st url:'+'\n')
1934
        if DEBUG_MODE == True: debugfile.write(buildurl+'\n\n')
1935
        # custom user agent identification?
1936
        if 'AGEN' in self.fileopen:
1937
            useragent = self.fileopen['AGEN']
1938
            urllib.URLopener.version = useragent
1939
        # attribute doesn't exist - take the default user agent
1940
        else:
1941
            urllib.URLopener.version = self.osuseragent
1942
        # try to open the url
1943
        try:
1944
            u = urllib.urlopen(buildurl)
1945
            u.geturl()
1946
            result = u.read()
1947
        except:
1948
            raise ADEPTError('No internet connection or a blocking firewall!')
1949
##        finally:
1950
##            u.close()
1951
        # getting rid of the line feed
1952
        if DEBUG_MODE == True: debugfile.write('1st preresult'+'\n')
1953
        if DEBUG_MODE == True: debugfile.write(result+'\n\n')
1954
        #get rid of unnecessary characters
1955
        result = result.rstrip('\n')
1956
        result = result.rstrip(chr(13))
1957
        result = result.lstrip('\n')
1958
        result = result.lstrip(chr(13)) 
1959
        self.urlresult = {}
1960
        for pair in result.split('&'):
1961
            try:
1962
                key, value = pair.split('=',1)
1963
                self.urlresult[key] = value
1964
            except:
1965
                pass
1966
##        if 'RequestSchema' in self.surlresult:
1967
##            self.fileopen['RequestSchema'] = self.urlresult['RequestSchema']
1968
         #self.urlresult
1969
        #result[0:8] == 'RetVal=1') or (result[0:8] == 'RetVal=2'):
1970
        if ('RetVal' in self.urlresult and (self.urlresult['RetVal'] != '1' and \
1971
                                            self.urlresult['RetVal'] != '2' and \
1972
                                            self.urlresult['RetVal'] != 'Update' and \
1973
                                            self.urlresult['RetVal'] != 'Answer')):
1974
            
1975
            if ('Reason' in self.urlresult and (self.urlresult['Reason'] == 'BadUserPwd'\
1976
                or self.urlresult['Reason'] == 'AskUnp')) or ('SwitchTo' in self.urlresult\
1977
                    and (self.urlresult['SwitchTo'] == 'Dialog')):
1978
                if 'ServerSessionData' in self.urlresult:
1979
                    self.fileopen['ServerSessionData'] = self.urlresult['ServerSessionData']
1980
                if 'DocumentSessionData' in self.urlresult:
1981
                    self.fileopen['DocumentSessionData'] = self.urlresult['DocumentSessionData']        
1982
                buildurl = origurl
1983
                buildurl = buildurl + 'Request=DocPerm'
1984
                self.gen_pw_dialog()
1985
                # password not found - fallback
1986
                for keys in burl:
1987
                    try:
1988
                        buildurl = buildurl + '&' + keys + '=' + self.fileopen[keys]
1989
                    except:
1990
                        continue
1991
                if DEBUG_MODE == True: debugfile.write( '2ndurl:')
1992
                if DEBUG_MODE == True: debugfile.write( buildurl+'\n\n')
1993
                # try to open the url
1994
                try:
1995
                    u = urllib.urlopen(buildurl)
1996
                    u.geturl()
1997
                    result = u.read()
1998
                except:
1999
                    raise ADEPTError('No internet connection or a blocking firewall!')
2000
                # getting rid of the line feed
2001
                if DEBUG_MODE == True: debugfile.write( '2nd preresult')
2002
                if DEBUG_MODE == True: debugfile.write( result+'\n\n')
2003
                #get rid of unnecessary characters
2004
                result = result.rstrip('\n')
2005
                result = result.rstrip(chr(13))
2006
                result = result.lstrip('\n')
2007
                result = result.lstrip(chr(13)) 
2008
                self.urlresult = {}
2009
                for pair in result.split('&'):
2010
                    try:
2011
                        key, value = pair.split('=',1)
2012
                        self.urlresult[key] = value
2013
                    except:
2014
                        pass
2015
        # did it work?
2016
        if ('RetVal' in self.urlresult and (self.urlresult['RetVal'] != '1' and \
2017
                                                    self.urlresult['RetVal'] != '2' and
2018
                                                    self.urlresult['RetVal'] != 'Update' and \
2019
                                                    self.urlresult['RetVal'] != 'Answer')):
2020
            raise ADEPTError('Decryption was not successfull.\nReason: ' + self.urlresult['Error'])
2021
        # fix for non-standard-conform fileopen pdfs
2022
##        if self.fileopen['Length'] != 5 and self.fileopen['Length'] != 16:
2023
##            if self.fileopen['V'] == 1:
2024
##                self.fileopen['Length'] = 5
2025
##            else:
2026
##                self.fileopen['Length'] = 16
2027
        # patch for malformed pdfs
2028
        #print len(self.urlresult['Code'])
2029
        #print self.urlresult['Code'].encode('hex')
2030
        if 'code' in self.urlresult:
2031
            self.urlresult['Code'] = self.urlresult['code']
2032
        if 'Code' in self.urlresult:            
2033
            if len(self.urlresult['Code']) == 5 or len(self.urlresult['Code']) == 16:
2034
                self.decrypt_key = self.urlresult['Code']
2035
            else:
2036
                self.decrypt_key = self.urlresult['Code'].decode('hex')
2037
        else:
2038
            raise ADEPTError('Cannot find decryption key.')
2039
        self.genkey = self.genkey_v2
2040
        self.decipher = self.decrypt_rc4
2041
        self.ready = True
2042
        return
2043
    
2044
    def gen_pw_dialog(self, Username='Username', Password='Password', Title='User/Password Authentication',\
2045
                      OK='Proceed', Text1='Authorization', Text2='Enter Required Data'):
2046
        self.pwtk = Tkinter.Tk()
2047
        self.pwtk.title(Title)
2048
        self.pwtk.minsize(150, 0)
2049
        self.label1 = Tkinter.Label(self.pwtk, text=Text1)
2050
        self.label2 = Tkinter.Label(self.pwtk, text=Text2)
2051
        self.label3 = Tkinter.Label(self.pwtk, text=Username)
2052
        self.pwfieldreq = 1        
2053
        self.gui = True
2054
        self.un_entry = Tkinter.Entry(self.pwtk)
2055
        # cursor here
2056
        self.un_entry.focus()
2057
        self.label4 = Tkinter.Label(self.pwtk, text=Password)
2058
        self.pw_entry = Tkinter.Entry(self.pwtk, show="*")
2059
        self.button = Tkinter.Button(self.pwtk, text=OK, command=self.fo_save_values)
2060
        # widget layout, stack vertical
2061
        self.label1.pack()
2062
        self.label2.pack()
2063
        self.label3.pack()        
2064
        self.un_entry.pack()
2065
        # create a password label and field
2066
        if self.pwfieldreq == 1:
2067
            self.label4.pack()
2068
            self.pw_entry.pack()
2069
        self.button.pack()
2070
        self.pwtk.update()            
2071
        # start the event loop
2072
        self.pwtk.mainloop()
2073
        
2074
    # genkey functions
2075
    def genkey_v2(self, objid, genno):
2076
        objid = struct.pack('<L', objid)[:3]
2077
        genno = struct.pack('<L', genno)[:2]
2078
        key = self.decrypt_key + objid + genno
2079
        hash = hashlib.md5(key)
2080
        key = hash.digest()[:min(len(self.decrypt_key) + 5, 16)]
2081
        return key
2082
    
2083
    def genkey_v3(self, objid, genno):
2084
        objid = struct.pack('<L', objid ^ 0x3569ac)
2085
        genno = struct.pack('<L', genno ^ 0xca96)
2086
        key = self.decrypt_key
2087
        key += objid[0] + genno[0] + objid[1] + genno[1] + objid[2] + 'sAlT'
2088
        hash = hashlib.md5(key)
2089
        key = hash.digest()[:min(len(self.decrypt_key) + 5, 16)]
2090
        return key
2091
2092
    # aes v2 and v4 algorithm
2093
    def genkey_v4(self, objid, genno):
2094
        objid = struct.pack('<L', objid)[:3]
2095
        genno = struct.pack('<L', genno)[:2]
2096
        key = self.decrypt_key + objid + genno + 'sAlT'
2097
        hash = hashlib.md5(key)
2098
        key = hash.digest()[:min(len(self.decrypt_key) + 5, 16)]
2099
        return key
2100
2101
    def decrypt_aes(self, objid, genno, data):
2102
        key = self.genkey(objid, genno)
2103
        ivector = data[:16]
2104
        data = data[16:]
2105
        plaintext = AES.new(key,AES.MODE_CBC,ivector).decrypt(data)
2106
        # remove pkcs#5 aes padding
2107
        cutter = -1 * ord(plaintext[-1])
2108
        #print cutter
2109
        plaintext = plaintext[:cutter]
2110
        return plaintext
2111
2112
    def decrypt_aes256(self, objid, genno, data):
2113
        key = self.genkey(objid, genno)
2114
        ivector = data[:16]
2115
        data = data[16:]
2116
        plaintext = AES.new(key,AES.MODE_CBC,ivector).decrypt(data)
2117
        # remove pkcs#5 aes padding
2118
        cutter = -1 * ord(plaintext[-1])
2119
        #print cutter
2120
        plaintext = plaintext[:cutter]
2121
        return plaintext
2122
    
2123
    def decrypt_rc4(self, objid, genno, data):
2124
        key = self.genkey(objid, genno)
2125
        return ARC4.new(key).decrypt(data)
2126
2127
    # fileopen user/password dialog    
2128
    def fo_save_values(self):
2129
        getout = 0
2130
        username = 0
2131
        password = 0
2132
        username = self.un_entry.get()
2133
        if self.pwfieldreq == 1:        
2134
            password = self.pw_entry.get()
2135
        un_length = len(username)
2136
        if self.pwfieldreq == 1:                
2137
            pw_length = len(password)
2138
        if (un_length != 0):
2139
            if self.pwfieldreq == 1:
2140
                if (pw_length != 0):
2141
                    getout = 1
2142
            else:
2143
                getout = 1
2144
        if getout == 1:
2145
            if 'SEMO' in self.fileopen and self.fileopen['SEMO'] == '1':
2146
                self.fileopen['Session'] = urllib.quote(username)
2147
            else:
2148
                self.fileopen['UserName'] = urllib.quote(username)
2149
            if self.pwfieldreq == 1:
2150
                self.fileopen['UserPass'] = urllib.quote(password)
2151
            else:
2152
                pass
2153
                #self.fileopen['UserPass'] = self.fileopen['UserName']
2154
            # doesn't always close the password window, who
2155
            # knows why (Tkinter secrets ;=))
2156
            self.pwtk.quit()
2157
   
2158
    
2159
    def fo_setattributes(self):
2160
        self.fileopen['Request']='DocPerm'
2161
        self.fileopen['Mode']='CNR'
2162
        self.fileopen['DocStrFmt']='ASCII'
2163
        self.fileopen['Language']='ENU'
2164
        self.fileopen['LngLCID']='ENU'
2165
        self.fileopen['LngRFC1766']='en'
2166
        self.fileopen['LngISO4Char']='en-us'
2167
        self.fileopen['ProdVer']='1.8.7.9'
2168
        self.fileopen['FormHFT']='Yes'
2169
        self.fileopen['SelServer']='Yes'
2170
        self.fileopen['AcroCanEdit']='Yes'
2171
        self.fileopen['AcroPrefIDib']='Yes'
2172
        self.fileopen['InBrowser']='Unk'
2173
        self.fileopen['CliAppName']=''
2174
        self.fileopen['DocIsLocal']='Yes'
2175
        self.fileopen['FowpKbd']='Yes'
2176
        self.fileopen['RequestSchema']='Default'
2177
        
2178
    # get nic mac address
2179
    def get_linux_macaddress(self):
2180
        try:
2181
            for line in os.popen("/sbin/ifconfig"):
2182
                if line.find('Ether') > -1:
2183
                    mac = line.split()[4]
2184
                    break
2185
            return mac.replace(':','') 
2186
        except:
2187
            raise ADEPTError('Cannot find MAC address. Get forum help.')
2188
2189
    def get_win_macaddress(self):
2190
        try:
2191
            gasize = c_ulong(5000)
2192
            p = create_string_buffer(5000)
2193
            GetAdaptersInfo = windll.iphlpapi.GetAdaptersInfo
2194
            GetAdaptersInfo(byref(p),byref(gasize))
2195
            return p[0x194:0x19a].encode('hex')
2196
        except:
2197
            raise ADEPTError('Cannot find MAC address. Get forum help.')
2198
        
2199
    # custom conversion 5 bytes to 8 chars method
2200
    def fo_convert5to8(self, edisk):
2201
        # byte to number/char mapping table
2202
        darray=[0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x41,0x42,0x43,0x44,0x45,\
2203
                0x46,0x47,0x48,0x4A,0x4B,0x4C,0x4D,0x4E,0x50,0x51,0x52,0x53,0x54,\
2204
                0x55,0x56,0x57,0x58,0x59,0x5A]
2205
        pdid = struct.pack('<I', int(edisk[0:4].encode("hex"),16))
2206
        pdid = int(pdid.encode("hex"),16)
2207
        outputhw = ''
2208
        # disk id processing
2209
        for i in range(0,6):
2210
            index = pdid & 0x1f
2211
            # shift the disk id 5 bits to the right
2212
            pdid = pdid >> 5
2213
            outputhw = outputhw + chr(darray[index])
2214
        pdid = (ord(edisk[4]) << 2)|pdid
2215
        # get the last 2 bits from the hwid + low part of the cpuid
2216
        for i in range(0,2):
2217
            index = pdid & 0x1f
2218
            # shift the disk id 5 bits to the right
2219
            pdid = pdid >> 5
2220
            outputhw = outputhw + chr(darray[index])
2221
        return outputhw
2222
2223
    # Linux processing
2224
    def fo_linux_sethwids(self):
2225
        # linux specific attributes
2226
        self.fileopen['OSType']='Linux'
2227
        self.fileopen['AcroProduct']='AcroReader'
2228
        self.fileopen['AcroReader']='Yes'
2229
        self.fileopen['AcroVersion']='9.101'
2230
        self.fileopen['FSName']='ext3'    
2231
        self.fileopen['Build']='878'
2232
        self.fileopen['ProdVer']='1.8.5.1'
2233
        self.fileopen['OSBuild']='2.6.33'        
2234
        # write hardware keys
2235
        hwkey = 0
2236
        pmac = self.get_macaddress().decode("hex");
2237
        self.fileopen['Disk'] = self.fo_convert5to8(pmac[1:])
2238
        # get primary used default mac address
2239
        self.fileopen['Machine'] = self.fo_convert5to8(pmac[1:])
2240
        # get uuid
2241
        # check for reversed offline handler 6AB83F4Ah + AFh 6AB83F4Ah
2242
        if 'LILA' in self.fileopen:
2243
            pass
2244
        if 'Ident4ID' in self.fileopen:
2245
            self.fileopen['User'] = getpass.getuser()
2246
            self.fileopen['SaUser'] = getpass.getuser()
2247
            try:
2248
                cuser = winreg.HKEY_CURRENT_USER
2249
                FOW3_UUID = 'Software\\Fileopen'
2250
                regkey = winreg.OpenKey(cuser, FOW3_UUID)
2251
                userkey = winreg.QueryValueEx(regkey, 'Fowp3Uuid')[0]
2252
#                if self.genkey_cryptmach(userkey)[0:4] != 'ec20':
2253
                self.fileopen['Uuid'] = self.genkey_cryptmach(userkey)[4:]
2254
##                elif self.genkey_cryptmach(userkey)[0:4] != 'ec20':
2255
##                    self.fileopen['Uuid'] = self.genkey_cryptmach(userkey,1)[4:]
2256
##                else:
2257
            except:
2258
                raise ADEPTError('Cannot find FowP3Uuid file - reason might be Adobe (Reader) X.'\
2259
                                 'Read the FAQs for more information how to solve the problem.')
2260
        else:
2261
            self.fileopen['Uuid'] = str(uuid.uuid1())
2262
        # get time stamp
2263
        self.fileopen['Stamp'] = str(time.time())[:-3]
2264
        # get fileopen input pdf name + path
2265
        self.fileopen['DocPathUrl'] = 'file%3a%2f%2f%2f'\
2266
                                      + urllib.quote(os.path.normpath(INPUTFILEPATH))
2267
        # clear the link
2268
        #INPUTFILEPATH = ''
2269
##        # get volume name (urllib quote necessairy?) urllib.quote(
2270
##        self.fileopen['VolName'] = win32api.GetVolumeInformation("C:\\")[0]
2271
##        # get volume serial number
2272
##        self.fileopen['VolSN'] = str(win32api.GetVolumeInformation("C:\\")[1])
2273
        return
2274
2275
    # Windows processing
2276
    def fo_win_sethwids(self):
2277
        # Windows specific attributes        
2278
        self.fileopen['OSType']='Windows'
2279
        self.fileopen['OSName']='Vista'
2280
        self.fileopen['OSData']='Service%20Pack%204'        
2281
        self.fileopen['AcroProduct']='Reader'
2282
        self.fileopen['AcroReader']='Yes'     
2283
        self.fileopen['OSBuild']='7600'
2284
        self.fileopen['AcroVersion']='9.1024'
2285
        self.fileopen['Build']='879'        
2286
        # write hardware keys
2287
        hwkey = 0
2288
        # get the os type and save it in ostype
2289
        try:
2290
            import win32api
2291
            import win32security
2292
            import win32file
2293
            import _winreg as winreg                
2294
        except:
2295
            raise ADEPTError('PyWin Extension (Win32API module) needed.\n'+\
2296
                             'Download from http://sourceforge.net/projects/pywin32/files/ ')
2297
        try:
2298
            v0 = win32api.GetVolumeInformation('C:\\')
2299
            v1 = win32api.GetSystemInfo()[6]
2300
            # fix for possible negative integer (Python problem)
2301
            volserial = v0[1] & 0xffffffff
2302
            lowcpu = v1 & 255
2303
            highcpu = (v1 >> 8) & 255
2304
            # changed to int
2305
            volserial = struct.pack('<I', int(volserial))
2306
            lowcpu   = struct.pack('B', lowcpu)
2307
            highcpu = struct.pack('B', highcpu)
2308
            encrypteddisk = volserial + lowcpu + highcpu
2309
            self.fileopen['Disk'] = self.fo_convert5to8(encrypteddisk)            
2310
        except:
2311
            # no c system drive available empty disk attribute
2312
            self.fileopen['Disk'] = ''          
2313
        # get primary used default mac address
2314
        pmac = self.get_macaddress().decode("hex");
2315
        self.fileopen['Machine'] = self.fo_convert5to8(pmac[1:])
2316
        if 'LIFF' in self.fileopen:
2317
            if 'Yes' in self.fileopen['LIFF']:
2318
                hostname = socket.gethostname()
2319
                self.fileopen['HostIsDomain']='Yes'
2320
                if '1' in self.fileopen['LIFF']:
2321
                    self.fileopen['PhysHostname']= hostname
2322
                    self.fileopen['LogiHostname']= hostname
2323
                    self.fileopen['SaRefDomain']= hostname
2324
        # default users
2325
        self.user = win32api.GetUserName().lower()
2326
        self.sauser = win32api.GetUserName()                      
2327
        # get uuid
2328
        # check for reversed offline handler
2329
        if 'LILA' in self.fileopen and self.fileopen['LILA'] == 'Yes':
2330
##            self.fileopen['User'] = win32api.GetUserName().lower()
2331
##            self.fileopen['SaUser'] = win32api.GetUserName()
2332
          
2333
            # get sid / sasid
2334
            try:
2335
                psid = win32security.LookupAccountName("",self.sauser)[0]
2336
                psid = win32security.ConvertSidToStringSid(psid)
2337
                self.fileopen['SaSID'] = psid
2338
                self.fileopen['User'] = urllib.quote(self.user)
2339
                self.fileopen['SaUser'] = urllib.quote(self.sauser)                
2340
            # didn't work use a generic one
2341
            except:
2342
                self.fileopen['SaSID'] = 'S-1-5-21-1380067357-584463869-1343024091-1000'
2343
        #if 'Ident4d' in self.fileopen or 'LILA' in self.fileopen:
2344
        # always calculate the right uuid
2345
        userkey = []        
2346
        try:
2347
            cuser = winreg.HKEY_CURRENT_USER
2348
            FOW3_UUID = 'Software\\Fileopen'
2349
            regkey = winreg.OpenKey(cuser, FOW3_UUID)
2350
            userkey.append(winreg.QueryValueEx(regkey, 'Fowp3Uuid')[0])
2351
        except:
2352
            pass
2353
        try:
2354
            fopath = os.environ['AppData']+'\\FileOpen\\'
2355
            fofilename = 'Fowpmadi.txt'
2356
            f = open(fopath+fofilename, 'rb')
2357
            userkey.append(f.read()[0:40])
2358
            f.close()
2359
        except:
2360
            pass
2361
        if not userkey:
2362
            raise ADEPTError('Cannot find FowP3Uuid in registry or file.\n'\
2363
                                 +'Did Adobe (Reader) open the pdf file?')
2364
        cresult = self.genkey_cryptmach(userkey)
2365
        if cresult != False:
2366
            self.fileopen['Uuid'] = cresult
2367
        # kind of a long shot we'll see about it
2368
        else:
2369
            self.fileopen['Uuid'] = str(uuid.uuid1())
2370
##        else:
2371
##            self.fileopen['Uuid'] = str(uuid.uuid1())
2372
        # get time stamp
2373
        self.fileopen['Stamp'] = str(time.time())[:-3]
2374
        # get fileopen input pdf name + path
2375
        # print INPUTFILEPATH
2376
        self.fileopen['DocPathUrl'] = 'file%3a%2f%2f%2f'\
2377
                                      + urllib.quote(INPUTFILEPATH)
2378
        # determine voltype
2379
        voltype = ('Unknown', 'Invalid', 'Removable', 'Fixed', 'Remote', 'CDRom', 'RamDisk')
2380
        dletter = os.path.splitdrive(INPUTFILEPATH)[0] + '\\'
2381
        self.fileopen['VolType'] = voltype[win32file.GetDriveType(dletter)]        
2382
        # get volume name (urllib quote necessairy?) urllib.quote(
2383
        self.fileopen['VolName'] = urllib.quote(win32api.GetVolumeInformation(dletter)[0])
2384
        # get volume serial number (fix for possible negative numbers)           
2385
        self.fileopen['VolSN'] = str(win32api.GetVolumeInformation(dletter)[1])
2386
        # no c volume so skip it
2387
        self.fileopen['FSName'] = win32api.GetVolumeInformation(dletter)[4]
2388
        # get previous mac address or disk handling
2389
        userkey = []
2390
        try:
2391
            cuser = winreg.HKEY_CURRENT_USER
2392
            FOW3_UUID = 'Software\\Fileopen'
2393
            regkey = winreg.OpenKey(cuser, FOW3_UUID)
2394
            userkey.append(winreg.QueryValueEx(regkey, 'Fowp3Madi')[0])
2395
        except:
2396
            pass
2397
        try:
2398
            fopath = os.environ['AppData']+'\\FileOpen\\'
2399
            fofilename = 'Fowpmadi.txt'
2400
            f = open(fopath+fofilename, 'rb')
2401
            userkey.append(f.read()[40:])
2402
            f.close()
2403
        except:
2404
            pass
2405
        if not userkey:
2406
            raise ADEPTError('Cannot find FowP3Madi in registry or file.\n'\
2407
                             +'Did Adobe Reader open the pdf file?')
2408
        cresult = self.genkey_cryptmach(userkey)
2409
        if cresult != False:
2410
            machdisk = self.genkey_cryptmach(userkey)
2411
            machine = machdisk[:8]
2412
            disk = machdisk[8:]
2413
        # did not find the required information, false it
2414
        else:
2415
            machdisk = False
2416
            machine = False
2417
            disk = False
2418
        if machine != self.fileopen['Machine'] and machdisk != False:
2419
            self.fileopen['PrevMach'] = machine
2420
        if disk != self.fileopen['Disk'] and machdisk != False:
2421
            self.fileopen['PrevDisk'] = disk        
2422
        return
2423
2424
    # decryption routine for the INFO area
2425
    def genkey_fileopeninfo(self, data):
2426
        input1 = struct.pack('L', 0xa4da49de)
2427
        seed   = struct.pack('B', 0x82)
2428
        key = input1[3] + input1[2] +input1[1] +input1[0] + seed
2429
        hash = hashlib.md5()
2430
        key = hash.update(key)
2431
        spointer4 = struct.pack('<L', 0xec8d6c58)
2432
        seed = struct.pack('B', 0x07)
2433
        key = spointer4[3] + spointer4[2] + spointer4[1] + spointer4[0] + seed 
2434
        key = hash.update(key)
2435
        md5 = hash.digest()
2436
        key = md5[0:10]
2437
        return ARC4.new(key).decrypt(data)
2438
2439
    def genkey_cryptmach(self, data):
2440
        # nested subfunction
2441
        def genkeysub(uname, mode=False):
2442
            key_string = '37A4DA49DE82064939A60B1D8D7B5F0F8873B6D93E'.decode('hex')
2443
            m = hashlib.md5()
2444
            m.update(key_string[:3])
2445
            m.update(uname[:13]) # max 13 characters 13 - sizeof(username)
2446
            if (13 - len(uname)) > 0 and mode == True:
2447
                m.update(key_string[:(13-len(uname))])
2448
            md5sum = m.digest()[0:16]
2449
            # print md5sum.encode('hex')
2450
            # normal ident4id calculation
2451
            retval = []
2452
            for sdata in data:
2453
                retval.append(ARC4.new(md5sum).decrypt(sdata))
2454
            for rval in retval:
2455
                if rval[:4] == 'ec20':
2456
                    return rval[4:]
2457
            return False
2458
        # start normal execution    
2459
        # list for username variants
2460
        unamevars = []
2461
        # fill username variants list
2462
        unamevars.append(self.user)
2463
        unamevars.append(self.user + chr(0))
2464
        unamevars.append(self.user.lower())
2465
        unamevars.append(self.user.lower() + chr(0))
2466
        unamevars.append(self.user.upper())
2467
        unamevars.append(self.user.upper() + chr(0))
2468
        # go through it
2469
        for uname in unamevars:
2470
            result = genkeysub(uname, True)
2471
            if result != False:
2472
              return result            
2473
            result = genkeysub(uname)
2474
            if result != False:
2475
              return result
2476
        # didn't find it, return false
2477
        return False
2478
##        raise ADEPTError('Unsupported Ident4D Decryption,\n'+\
2479
##                             'report the bug to the ineptpdf script forum')                
2480
                
2481
    KEYWORD_OBJ = PSKeywordTable.intern('obj')
2482
    
2483
    def getobj(self, objid):
2484
        if not self.ready:
2485
            raise PDFException('PDFDocument not initialized')
2486
        #assert self.xrefs
2487
        if objid in self.objs:
2488
            genno = 0
2489
            obj = self.objs[objid]
2490
        else:
2491
            for xref in self.xrefs:
2492
                try:
2493
                    (stmid, index) = xref.getpos(objid)
2494
                    break
2495
                except KeyError:
2496
                    pass
2497
            else:
2498
                #if STRICT:
2499
                #    raise PDFSyntaxError('Cannot locate objid=%r' % objid)
2500
                return None
2501
            if stmid:
2502
                if gen_xref_stm:
2503
                    return PDFObjStmRef(objid, stmid, index)
2504
# Stuff from pdfminer: extract objects from object stream
2505
                stream = stream_value(self.getobj(stmid))
2506
                if stream.dic.get('Type') is not LITERAL_OBJSTM:
2507
                    if STRICT:
2508
                        raise PDFSyntaxError('Not a stream object: %r' % stream)
2509
                try:
2510
                    n = stream.dic['N']
2511
                except KeyError:
2512
                    if STRICT:
2513
                        raise PDFSyntaxError('N is not defined: %r' % stream)
2514
                    n = 0
2515
2516
                if stmid in self.parsed_objs:
2517
                    objs = self.parsed_objs[stmid]
2518
                else:
2519
                    parser = PDFObjStrmParser(stream.get_data(), self)
2520
                    objs = []
2521
                    try:
2522
                        while 1:
2523
                            (_,obj) = parser.nextobject()
2524
                            objs.append(obj)
2525
                    except PSEOF:
2526
                        pass
2527
                    self.parsed_objs[stmid] = objs
2528
                genno = 0
2529
                i = n*2+index
2530
                try:
2531
                    obj = objs[i]
2532
                except IndexError:
2533
                    raise PDFSyntaxError('Invalid object number: objid=%r' % (objid))
2534
                if isinstance(obj, PDFStream):
2535
                    obj.set_objid(objid, 0)
2536
###
2537
            else:
2538
                self.parser.seek(index)
2539
                (_,objid1) = self.parser.nexttoken() # objid
2540
                (_,genno) = self.parser.nexttoken() # genno
2541
                #assert objid1 == objid, (objid, objid1)
2542
                (_,kwd) = self.parser.nexttoken()
2543
        # #### hack around malformed pdf files
2544
        #        assert objid1 == objid, (objid, objid1)
2545
##                if objid1 != objid:
2546
##                    x = []
2547
##                    while kwd is not self.KEYWORD_OBJ:
2548
##                        (_,kwd) = self.parser.nexttoken()
2549
##                        x.append(kwd)
2550
##                    if x:
2551
##                        objid1 = x[-2]
2552
##                        genno = x[-1]
2553
##                
2554
                if kwd is not self.KEYWORD_OBJ:
2555
                    raise PDFSyntaxError(
2556
                        'Invalid object spec: offset=%r' % index)
2557
                (_,obj) = self.parser.nextobject()
2558
                if isinstance(obj, PDFStream):
2559
                    obj.set_objid(objid, genno)
2560
                if self.decipher:
2561
                    obj = decipher_all(self.decipher, objid, genno, obj)
2562
            self.objs[objid] = obj
2563
        return obj
2564
2565
# helper class for cookie retrival 
2566
class WinBrowserCookie():
2567
    def __init__(self):
2568
        pass
2569
    def getcookie(self, cname, chost):
2570
        # check firefox db
2571
        fprofile =  os.environ['AppData']+r'\Mozilla\Firefox'
2572
        pinifile = 'profiles.ini'
2573
        fini = os.path.normpath(fprofile + '\\' + pinifile)
2574
        try:
2575
            with open(fini,'r') as ffini:
2576
                firefoxini =  ffini.read()
2577
        # Firefox not installed or on an USB stick
2578
        except:
2579
            return None
2580
        for pair in firefoxini.split('\n'):
2581
            try:
2582
                key, value = pair.split('=',1)
2583
                if key == 'Path':
2584
                    fprofile = os.path.normpath(fprofile+'//'+value+'//'+'cookies.sqlite')
2585
                    break
2586
            # asdf
2587
            except:
2588
                continue
2589
        if os.path.isfile(fprofile):
2590
            try:
2591
                con = sqlite3.connect(fprofile,1)
2592
            except:
2593
                raise ADEPTError('Firefox Cookie data base locked. Close Firefox and try again')
2594
            cur = con.cursor()
2595
            try:            
2596
                cur.execute("select value from moz_cookies where name=? and host=?", (cname, chost))
2597
            except Exception:
2598
                raise ADEPTError('Firefox Cookie database is locked. Close Firefox and try again')
2599
            try:
2600
                return cur.fetchone()[0]
2601
            except Exception:
2602
                # sometimes is a dot in front of the host
2603
                chost = '.'+chost
2604
                cur.execute("select value from moz_cookies where name=? and host=?", (cname, chost))
2605
                try:
2606
                    return cur.fetchone()[0]
2607
                except:
2608
                    return None
2609
                
2610
class PDFObjStmRef(object):
2611
    maxindex = 0
2612
    def __init__(self, objid, stmid, index):
2613
        self.objid = objid
2614
        self.stmid = stmid
2615
        self.index = index
2616
        if index > PDFObjStmRef.maxindex:
2617
            PDFObjStmRef.maxindex = index
2618
2619
    
2620
##  PDFParser
2621
##
2622
class PDFParser(PSStackParser):
2623
2624
    def __init__(self, doc, fp):
2625
        PSStackParser.__init__(self, fp)
2626
        self.doc = doc
2627
        self.doc.set_parser(self)
2628
        return
2629
2630
    def __repr__(self):
2631
        return '<PDFParser>'
2632
2633
    KEYWORD_R = PSKeywordTable.intern('R')
2634
    KEYWORD_ENDOBJ = PSKeywordTable.intern('endobj')
2635
    KEYWORD_STREAM = PSKeywordTable.intern('stream')
2636
    KEYWORD_XREF = PSKeywordTable.intern('xref')
2637
    KEYWORD_STARTXREF = PSKeywordTable.intern('startxref')
2638
    def do_keyword(self, pos, token):
2639
        if token in (self.KEYWORD_XREF, self.KEYWORD_STARTXREF):
2640
            self.add_results(*self.pop(1))
2641
            return
2642
        if token is self.KEYWORD_ENDOBJ:
2643
            self.add_results(*self.pop(4))
2644
            return
2645
        
2646
        if token is self.KEYWORD_R:
2647
            # reference to indirect object
2648
            try:
2649
                ((_,objid), (_,genno)) = self.pop(2)
2650
                (objid, genno) = (int(objid), int(genno))
2651
                obj = PDFObjRef(self.doc, objid, genno)
2652
                self.push((pos, obj))
2653
            except PSSyntaxError:
2654
                pass
2655
            return
2656
            
2657
        if token is self.KEYWORD_STREAM:
2658
            # stream object
2659
            ((_,dic),) = self.pop(1)
2660
            dic = dict_value(dic)
2661
            try:
2662
                objlen = int_value(dic['Length'])
2663
            except KeyError:
2664
                if STRICT:
2665
                    raise PDFSyntaxError('/Length is undefined: %r' % dic)
2666
                objlen = 0
2667
            self.seek(pos)
2668
            try:
2669
                (_, line) = self.nextline()  # 'stream'
2670
            except PSEOF:
2671
                if STRICT:
2672
                    raise PDFSyntaxError('Unexpected EOF')
2673
                return
2674
            pos += len(line)
2675
            self.fp.seek(pos)
2676
            data = self.fp.read(objlen)
2677
            self.seek(pos+objlen)
2678
            while 1:
2679
                try:
2680
                    (linepos, line) = self.nextline()
2681
                except PSEOF:
2682
                    if STRICT:
2683
                        raise PDFSyntaxError('Unexpected EOF')
2684
                    break
2685
                if 'endstream' in line:
2686
                    i = line.index('endstream')
2687
                    objlen += i
2688
                    data += line[:i]
2689
                    break
2690
                objlen += len(line)
2691
                data += line
2692
            self.seek(pos+objlen)
2693
            obj = PDFStream(dic, data, self.doc.decipher)
2694
            self.push((pos, obj))
2695
            return
2696
        
2697
        # others
2698
        self.push((pos, token))
2699
        return
2700
2701
    def find_xref(self):
2702
        # search the last xref table by scanning the file backwards.
2703
        prev = None
2704
        for line in self.revreadlines():
2705
            line = line.strip()
2706
            if line == 'startxref': break
2707
            if line:
2708
                prev = line
2709
        else:
2710
            raise PDFNoValidXRef('Unexpected EOF')
2711
        return int(prev)
2712
2713
    # read xref table
2714
    def read_xref_from(self, start, xrefs):
2715
        self.seek(start)
2716
        self.reset()
2717
        try:
2718
            (pos, token) = self.nexttoken()
2719
        except PSEOF:
2720
            raise PDFNoValidXRef('Unexpected EOF')
2721
        if isinstance(token, int):
2722
            # XRefStream: PDF-1.5
2723
            if GEN_XREF_STM == 1:
2724
                global gen_xref_stm
2725
                gen_xref_stm = True
2726
            self.seek(pos)
2727
            self.reset()
2728
            xref = PDFXRefStream()
2729
            xref.load(self)
2730
        else:
2731
            if token is not self.KEYWORD_XREF:
2732
                raise PDFNoValidXRef('xref not found: pos=%d, token=%r' % 
2733
                                     (pos, token))
2734
            self.nextline()
2735
            xref = PDFXRef()
2736
            xref.load(self)
2737
        xrefs.append(xref)
2738
        trailer = xref.trailer
2739
        if 'XRefStm' in trailer:
2740
            pos = int_value(trailer['XRefStm'])
2741
            self.read_xref_from(pos, xrefs)
2742
        if 'Prev' in trailer:
2743
            # find previous xref
2744
            pos = int_value(trailer['Prev'])
2745
            self.read_xref_from(pos, xrefs)
2746
        return
2747
        
2748
    # read xref tables and trailers
2749
    def read_xref(self):
2750
        xrefs = []
2751
        trailerpos = None
2752
        try:
2753
            pos = self.find_xref()
2754
            self.read_xref_from(pos, xrefs)
2755
        except PDFNoValidXRef:
2756
            # fallback
2757
            self.seek(0)
2758
            pat = re.compile(r'^(\d+)\s+(\d+)\s+obj\b')
2759
            offsets = {}
2760
            xref = PDFXRef()
2761
            while 1:
2762
                try:
2763
                    (pos, line) = self.nextline()
2764
                except PSEOF:
2765
                    break
2766
                if line.startswith('trailer'):
2767
                    trailerpos = pos # remember last trailer
2768
                m = pat.match(line)
2769
                if not m: continue
2770
                (objid, genno) = m.groups()
2771
                offsets[int(objid)] = (0, pos)
2772
            if not offsets: raise
2773
            xref.offsets = offsets
2774
            if trailerpos:
2775
                self.seek(trailerpos)
2776
                xref.load_trailer(self)
2777
                xrefs.append(xref)
2778
        return xrefs
2779
2780
##  PDFObjStrmParser
2781
##
2782
class PDFObjStrmParser(PDFParser):
2783
2784
    def __init__(self, data, doc):
2785
        PSStackParser.__init__(self, StringIO(data))
2786
        self.doc = doc
2787
        return
2788
2789
    def flush(self):
2790
        self.add_results(*self.popall())
2791
        return
2792
2793
    KEYWORD_R = KWD('R')
2794
    def do_keyword(self, pos, token):
2795
        if token is self.KEYWORD_R:
2796
            # reference to indirect object
2797
            try:
2798
                ((_,objid), (_,genno)) = self.pop(2)
2799
                (objid, genno) = (int(objid), int(genno))
2800
                obj = PDFObjRef(self.doc, objid, genno)
2801
                self.push((pos, obj))
2802
            except PSSyntaxError:
2803
                pass
2804
            return
2805
        # others
2806
        self.push((pos, token))
2807
        return
2808
2809
###
2810
### My own code, for which there is none else to blame
2811
2812
class PDFSerializer(object):
2813
    def __init__(self, inf, keypath):
2814
        global GEN_XREF_STM, gen_xref_stm
2815
        gen_xref_stm = GEN_XREF_STM > 1
2816
        self.version = inf.read(8)
2817
        inf.seek(0)
2818
        self.doc = doc = PDFDocument()
2819
        parser = PDFParser(doc, inf)
2820
        doc.initialize(keypath)
2821
        self.objids = objids = set()
2822
        for xref in reversed(doc.xrefs):
2823
            trailer = xref.trailer
2824
            for objid in xref.objids():
2825
                objids.add(objid)
2826
        trailer = dict(trailer)
2827
        trailer.pop('Prev', None)
2828
        trailer.pop('XRefStm', None)
2829
        if 'Encrypt' in trailer:
2830
            objids.remove(trailer.pop('Encrypt').objid)
2831
        self.trailer = trailer
2832
2833
    def dump(self, outf):
2834
        self.outf = outf
2835
        self.write(self.version)
2836
        self.write('\n%\xe2\xe3\xcf\xd3\n')
2837
        doc = self.doc
2838
        objids = self.objids
2839
        xrefs = {}
2840
        maxobj = max(objids)
2841
        trailer = dict(self.trailer)
2842
        trailer['Size'] = maxobj + 1
2843
        for objid in objids:
2844
            obj = doc.getobj(objid)
2845
            if isinstance(obj, PDFObjStmRef):
2846
                xrefs[objid] = obj
2847
                continue
2848
            if obj is not None:
2849
                try:
2850
                    genno = obj.genno
2851
                except AttributeError:
2852
                    genno = 0
2853
                xrefs[objid] = (self.tell(), genno)
2854
                self.serialize_indirect(objid, obj)
2855
        startxref = self.tell()
2856
2857
        if not gen_xref_stm:
2858
            self.write('xref\n')
2859
            self.write('0 %d\n' % (maxobj + 1,))
2860
            for objid in xrange(0, maxobj + 1):
2861
                if objid in xrefs:
2862
                    # force the genno to be 0
2863
                    self.write("%010d 00000 n \n" % xrefs[objid][0])
2864
                else:
2865
                    self.write("%010d %05d f \n" % (0, 65535))
2866
            
2867
            self.write('trailer\n')
2868
            self.serialize_object(trailer)
2869
            self.write('\nstartxref\n%d\n%%%%EOF' % startxref)
2870
2871
        else: # Generate crossref stream.
2872
2873
            # Calculate size of entries
2874
            maxoffset = max(startxref, maxobj)
2875
            maxindex = PDFObjStmRef.maxindex
2876
            fl2 = 2
2877
            power = 65536
2878
            while maxoffset >= power:
2879
                fl2 += 1
2880
                power *= 256
2881
            fl3 = 1
2882
            power = 256
2883
            while maxindex >= power:
2884
                fl3 += 1
2885
                power *= 256
2886
                    
2887
            index = []
2888
            first = None
2889
            prev = None
2890
            data = []
2891
            # Put the xrefstream's reference in itself
2892
            startxref = self.tell()
2893
            maxobj += 1
2894
            xrefs[maxobj] = (startxref, 0)
2895
            for objid in sorted(xrefs):
2896
                if first is None:
2897
                    first = objid
2898
                elif objid != prev + 1:
2899
                    index.extend((first, prev - first + 1))
2900
                    first = objid
2901
                prev = objid
2902
                objref = xrefs[objid]
2903
                if isinstance(objref, PDFObjStmRef):
2904
                    f1 = 2
2905
                    f2 = objref.stmid
2906
                    f3 = objref.index
2907
                else:
2908
                    f1 = 1
2909
                    f2 = objref[0]
2910
                    # we force all generation numbers to be 0
2911
                    # f3 = objref[1]
2912
                    f3 = 0
2913
                
2914
                data.append(struct.pack('>B', f1))
2915
                data.append(struct.pack('>L', f2)[-fl2:])
2916
                data.append(struct.pack('>L', f3)[-fl3:])
2917
            index.extend((first, prev - first + 1))
2918
            data = zlib.compress(''.join(data))
2919
            dic = {'Type': LITERAL_XREF, 'Size': prev + 1, 'Index': index,
2920
                   'W': [1, fl2, fl3], 'Length': len(data), 
2921
                   'Filter': LITERALS_FLATE_DECODE[0],
2922
                   'Root': trailer['Root'],}
2923
            if 'Info' in trailer:
2924
                dic['Info'] = trailer['Info']
2925
            xrefstm = PDFStream(dic, data)
2926
            self.serialize_indirect(maxobj, xrefstm)
2927
            self.write('startxref\n%d\n%%%%EOF' % startxref)
2928
    def write(self, data):
2929
        self.outf.write(data)
2930
        self.last = data[-1:]
2931
2932
    def tell(self):
2933
        return self.outf.tell()
2934
2935
    def escape_string(self, string):
2936
        string = string.replace('\\', '\\\\')
2937
        string = string.replace('\n', r'\n')
2938
        string = string.replace('(', r'\(')
2939
        string = string.replace(')', r'\)')
2940
         # get rid of ciando id
2941
        regularexp = re.compile(r'http://www.ciando.com/index.cfm/intRefererID/\d{5}')
2942
        if regularexp.match(string): return ('http://www.ciando.com') 
2943
        return string
2944
    
2945
    def serialize_object(self, obj):
2946
        if isinstance(obj, dict):
2947
            # Correct malformed Mac OS resource forks for Stanza
2948
            if 'ResFork' in obj and 'Type' in obj and 'Subtype' not in obj \
2949
                   and isinstance(obj['Type'], int):
2950
                obj['Subtype'] = obj['Type']
2951
                del obj['Type']
2952
            # end - hope this doesn't have bad effects
2953
            self.write('<<')
2954
            for key, val in obj.items():
2955
                self.write('/%s' % key)
2956
                self.serialize_object(val)
2957
            self.write('>>')
2958
        elif isinstance(obj, list):
2959
            self.write('[')
2960
            for val in obj:
2961
                self.serialize_object(val)
2962
            self.write(']')
2963
        elif isinstance(obj, str):
2964
            self.write('(%s)' % self.escape_string(obj))
2965
        elif isinstance(obj, bool):
2966
            if self.last.isalnum():
2967
                self.write(' ')
2968
            self.write(str(obj).lower())            
2969
        elif isinstance(obj, (int, long, float)):
2970
            if self.last.isalnum():
2971
                self.write(' ')
2972
            self.write(str(obj))
2973
        elif isinstance(obj, PDFObjRef):
2974
            if self.last.isalnum():
2975
                self.write(' ')            
2976
            self.write('%d %d R' % (obj.objid, 0))
2977
        elif isinstance(obj, PDFStream):
2978
            ### If we don't generate cross ref streams the object streams
2979
            ### are no longer useful, as we have extracted all objects from
2980
            ### them. Therefore leave them out from the output.
2981
            if obj.dic.get('Type') == LITERAL_OBJSTM and not gen_xref_stm:
2982
                    self.write('(deleted)')
2983
            else:
2984
                data = obj.get_decdata()
2985
                self.serialize_object(obj.dic)
2986
                self.write('stream\n')
2987
                self.write(data)
2988
                self.write('\nendstream')
2989
        else:
2990
            data = str(obj)
2991
            if data[0].isalnum() and self.last.isalnum():
2992
                self.write(' ')
2993
            self.write(data)
2994
    
2995
    def serialize_indirect(self, objid, obj):
2996
        self.write('%d 0 obj' % (objid,))
2997
        self.serialize_object(obj)
2998
        if self.last.isalnum():
2999
            self.write('\n')
3000
        self.write('endobj\n')
3001
3002
def cli_main(argv=sys.argv):
3003
    progname = os.path.basename(argv[0])
3004
    if RSA is None:
3005
        print "%s: This script requires PyCrypto, which must be installed " \
3006
              "separately.  Read the top-of-script comment for details." % \
3007
              (progname,)
3008
        return 1
3009
    if len(argv) != 4:
3010
        print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,)
3011
        return 1
3012
    keypath, inpath, outpath = argv[1:]
3013
    with open(inpath, 'rb') as inf:
3014
        serializer = PDFSerializer(inf, keypath)
3015
        # hope this will fix the 'bad file descriptor' problem
3016
        with open(outpath, 'wb') as outf:
3017
        # help construct to make sure the method runs to the end
3018
            serializer.dump(outf)
3019
    return 0
3020
3021
3022
class DecryptionDialog(Tkinter.Frame):
3023
    def __init__(self, root):
3024
        # debug mode debugging
3025
        global DEBUG_MODE
3026
        Tkinter.Frame.__init__(self, root, border=5)
3027
        ltext='Select file for decryption\n(Ignore Password / Key file option for Fileopen/APS PDFs)'        
3028
        self.status = Tkinter.Label(self, text=ltext)
3029
        self.status.pack(fill=Tkconstants.X, expand=1)
3030
        body = Tkinter.Frame(self)
3031
        body.pack(fill=Tkconstants.X, expand=1)
3032
        sticky = Tkconstants.E + Tkconstants.W
3033
        body.grid_columnconfigure(1, weight=2)
3034
        Tkinter.Label(body, text='Password\nor Key file').grid(row=0)
3035
        self.keypath = Tkinter.Entry(body, width=30)
3036
        self.keypath.grid(row=0, column=1, sticky=sticky)
3037
        if os.path.exists('adeptkey.der'):
3038
            self.keypath.insert(0, 'adeptkey.der')
3039
        button = Tkinter.Button(body, text="...", command=self.get_keypath)
3040
        button.grid(row=0, column=2)
3041
        Tkinter.Label(body, text='Input file').grid(row=1)
3042
        self.inpath = Tkinter.Entry(body, width=30)
3043
        self.inpath.grid(row=1, column=1, sticky=sticky)
3044
        button = Tkinter.Button(body, text="...", command=self.get_inpath)
3045
        button.grid(row=1, column=2)
3046
        Tkinter.Label(body, text='Output file').grid(row=2)
3047
        self.outpath = Tkinter.Entry(body, width=30)
3048
        self.outpath.grid(row=2, column=1, sticky=sticky)
3049
        debugmode = Tkinter.Checkbutton(self, text = "Debug Mode (writable directory required)", command=self.debug_toggle, height=2, \
3050
                 width = 40)             
3051
        debugmode.pack()         
3052
        button = Tkinter.Button(body, text="...", command=self.get_outpath)
3053
        button.grid(row=2, column=2)
3054
        buttons = Tkinter.Frame(self)
3055
        buttons.pack()
3056
  
3057
3058
        botton = Tkinter.Button(
3059
            buttons, text="Decrypt", width=10, command=self.decrypt)
3060
        botton.pack(side=Tkconstants.LEFT)
3061
        Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
3062
        button = Tkinter.Button(
3063
            buttons, text="Quit", width=10, command=self.quit)
3064
        button.pack(side=Tkconstants.RIGHT)
3065
         
3066
3067
    def get_keypath(self):
3068
        keypath = tkFileDialog.askopenfilename(
3069
            parent=None, title='Select ADEPT key file',
3070
            defaultextension='.der', filetypes=[('DER-encoded files', '.der'),
3071
                                                ('All Files', '.*')])
3072
        if keypath:
3073
            keypath = os.path.normpath(os.path.realpath(keypath))
3074
            self.keypath.delete(0, Tkconstants.END)
3075
            self.keypath.insert(0, keypath)
3076
        return
3077
3078
    def get_inpath(self):
3079
        inpath = tkFileDialog.askopenfilename(
3080
            parent=None, title='Select ADEPT or FileOpen-encrypted PDF file to decrypt',
3081
            defaultextension='.pdf', filetypes=[('PDF files', '.pdf'),
3082
                                                 ('All files', '.*')])
3083
        if inpath:
3084
            inpath = os.path.normpath(os.path.realpath(inpath))
3085
            self.inpath.delete(0, Tkconstants.END)
3086
            self.inpath.insert(0, inpath)
3087
        return
3088
3089
    def debug_toggle(self):
3090
        global DEBUG_MODE
3091
        if DEBUG_MODE == False:
3092
            DEBUG_MODE = True
3093
        else:
3094
            DEBUG_MODE = False
3095
            
3096
    def get_outpath(self):
3097
        outpath = tkFileDialog.asksaveasfilename(
3098
            parent=None, title='Select unencrypted PDF file to produce',
3099
            defaultextension='.pdf', filetypes=[('PDF files', '.pdf'),
3100
                                                 ('All files', '.*')])
3101
        if outpath:
3102
            outpath = os.path.normpath(os.path.realpath(outpath))
3103
            self.outpath.delete(0, Tkconstants.END)
3104
            self.outpath.insert(0, outpath)
3105
        return
3106
3107
    def decrypt(self):
3108
        global INPUTFILEPATH
3109
        global KEYFILEPATH
3110
        global PASSWORD
3111
        keypath = self.keypath.get()
3112
        inpath = self.inpath.get()
3113
        outpath = self.outpath.get()
3114
        if not keypath or not os.path.exists(keypath):
3115
            # keyfile doesn't exist
3116
            KEYFILEPATH = False
3117
            PASSWORD = keypath            
3118
        if not inpath or not os.path.exists(inpath):
3119
            self.status['text'] = 'Specified input file does not exist'
3120
            return
3121
        if not outpath:
3122
            self.status['text'] = 'Output file not specified'
3123
            return
3124
        if inpath == outpath:
3125
            self.status['text'] = 'Must have different input and output files'
3126
            return
3127
        # patch for non-ascii characters
3128
        INPUTFILEPATH = inpath.encode('utf-8')
3129
        argv = [sys.argv[0], keypath, inpath, outpath]
3130
        self.status['text'] = 'Processing ...'
3131
        try:
3132
            cli_main(argv)
3133
        except Exception, a:
3134
            self.status['text'] = 'Error: ' + str(a)
3135
            return
3136
        self.status['text'] = 'File successfully decrypted.\n'+\
3137
                              'Close this window or decrypt another pdf file.'
3138
        return
3139
3140
def gui_main():
3141
    root = Tkinter.Tk()
3142
    if RSA is None:
3143
        root.withdraw()
3144
        tkMessageBox.showerror(
3145
            "INEPT PDF and FileOpen Decrypter",
3146
            "This script requires PyCrypto, which must be installed "
3147
            "separately.  Read the top-of-script comment for details.")
3148
        return 1
3149
    root.title('INEPT PDF Decrypter 8.4.51 (FileOpen/APS-Support)')
3150
    root.resizable(True, False)
3151
    root.minsize(370, 0)
3152
    DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
3153
    root.mainloop()
3154
    return 0
3155
3156
3157
if __name__ == '__main__':
3158
    if len(sys.argv) > 1:
3159
        sys.exit(cli_main())
3160
    sys.exit(gui_main())