PythonCard is a GUI construction kit for building cross-platform
desktop applications on Windows, Mac OS X, and Linux.
Release 0.8 includes over 50 sample applications and tools to help
users build applications in Python, including codeEditor, findfiles,
and resourceEditor (layout editor). A list of changes since release
0.7.3.1 is at the end of this message. New samples include ataxx,
lsystem, moderator, montyhall, mp3player, reversi, twistedEchoClient.
There is also an experimental reStructuredText and HTML editor in the
codeEditor directory called restEditor.py.
PythonCard requires Python 2.3 or later and wxPython 2.5.2.7 or later.
You can download the latest release at:
http://sourceforge.net/project/showfiles.php?group_id=19015
Please be sure to look at the migration_guide.txt file in the docs
directory if you are upgrading from a previous release. Since the
package name has changed, you can continue to use the older
PythonCardPrototype package simultaneously with the new PythonCard
package, but you must upgrade to wxPython 2.5.2.7.
All the information you need about PythonCard can be found on the
project web page at:
http://pythoncard.sourceforge.net/
The installation instructions and walkthroughs are available on the
main documentation page:
http://pythoncard.sourceforge.net/documentation.html
For a list of most of the samples that have been built with PythonCard
and screenshots of them in action go to:
http://pythoncard.sourceforge.net/samples/samples.html
The kind people at SourceForge host the project:
http://sourceforge.net/projects/pythoncard/
If you want to get involved the main contact point is the Mailing list:
http://lists.sourceforge.net/lists/listinfo/pythoncard-users
Additional Notes:
Remember to backup or just delete your old PythonCard directory before
installing a new version, so that the old files aren't still in the
package directory. If you installed a previous version of PythonCard on
Windows using the binary installer, then you should be able to remove
the old package via the Add/Remove Programs Control Panel.
The distutils installer will put the framework, components, docs,
samples, and tools in Lib\site-packages or your Python directory
(typically C:\Python23). Of course, on Linux and Mac OS X that path
will be slightly different and have forward slashes.
Windows users should get a PythonCard menu in the Start->Programs menu
with links to the documentation, samples, codeEditor, findfiles, and
resourceEditor.
The tools and most of the samples will now keep their config and data
file info in the "pythoncard_config" directory created by the
framework. On Unix, the directory will be ~/pythoncard_config. On
Windows, the directory varies as described in the following post:
http://aspn.activestate.com/ASPN/Mail/Message/PythonCard/1496793
So, if you run a PythonCard app with any of the runtime tools and
select "Save Configuration" from the "Debug" menu, the window positions
and sizes of your runtime windows (Shell, Message Watcher, etc.) will
be saved in "pythoncard_config/pythoncard_config.txt" not the
PythonCard directory. Likewise, when you change the text style used by
the codeEditor via the "Styles..." menu item under the "Format" menu,
the modification will be saved in "pythoncard_config/stc-styles.cfg"
ka
---
Kevin Altis
altis(a)semi-retired.com
http://altis.pycs.net/
changelog.txt changes since release 0.7.3.1
Release 0.8 2004-08-18
getCommandLineArgs moved to util.py
runOptionsDialog moved to templates.dialogs.runOptionsDialog.py
dialog.py is now a thin wrapper around wx.lib.dialogs.py
all dialog results now use DialogResults class instead of dictionary
e.g. result.accepted instead of result['accepted']
see dialogs sample and other samples and tools for examples of change
menuDialog changed to insert in place, added default naming based on
label
changed Calendar component to CAL_SEQUENTIAL_MONTH_SELECTION style
added mp3player sample
switched to using wx.lib.statbmp.GenStaticBitmap on GTK for Image
component
Created unit-test facility.
defined ignore files for files that should not be imported to check
for tests.
runAllTests.py runs all tests from the current working directory
down, minus ignored.
added sample unit test class, in UnitTestSample.py
Added two unit tests for LSystem. Pulled out drawAbstractFractal() to
allow this.
Changed console_server.py and minimalTest.py so that importing the file
didn't run code.
updated childWindow resource loading to better support standalones
refactored resourceEditor to query component spec for default
set of attributes in on_componentAdd_command
refactored resourceEditor to dynamically build list of
available Components instead of using static list
converted turtle sample to use Python modules for the script
examples
fixed resourceEditor so it saves on Run and validates
component names
changed all occurances of stack in resources to application
removed Stack class
the application is now the "parent" of the main Background
changed __init__ for Background and CustomDialog so they
don't take a stack parameter
changed childWindow function
self.stack.app references are now self.application
added lsystem sample
added ToggleButton component
fixed enableCommand and disableCommand for components & menus
added ataxx sample
codeEditor now persists all View menu settings
added reversi (Othello) sample
made spirographInteractive its own sample
added restore (inverse of minimize) background window event
added twistedEchoClient sample
added twistedModel.py module to hold TwistedApplication
added redraw method to Widget to simplify immediate redraws
added event.target workaround for timer events
moved getStyleConfigPath to configuration module
renamed stc-styles.rc.cfg to stc-styles.cfg
removed unneeded WXMAC code blocks
removed get/set methods for position, size, foregroundColor,
backgroundColor in Background and CustomDialog and
replaced with properties
added removeListener method to EventQueue to enable the
clean removal of the Message Watcher window when closing app
changed codeEditor file dialog default from *.py to *.*
fixed Windows border offset in resourceEditor by
querying SystemSettings.GetMetric
added Bruce Eckel's moderator sample
updated Choice, ComboBox, List, RadioGroup to use
'selection' and 'stringSelection' attributes instead of
mixed-capability 'selected' and 'selection' attribute
see migration guide for more info
added templates sub-package to hold common backgrounds
and dialogs
updated all samples and tools to use lowercase skip()
removed CamelCase methods from BitmapCanvas (Draw -> draw)
changed Component __init__
init underlying control before Widget class
added makeNewId function
removed postInit
event binding is now part of component init
removed getId() from Widget, using GetId() calls in framework
*ROWLAND describe binding and spec changes here*
added SetValue workaround for TextArea component on Windows
removed dispatch.py and moved classes to event.py
SetFocus -> setFocus
Hide/Show -> visible attribute
added visible, position, and size properties to tool windows
added Tom Jacobs' montyhall sample
added about.py module
added About PythonCard dialog to codeEditor and resourceEditor
added singleton.py module
used by configuration.py, log.py, registry.py, and resource.py
removed Ptr classes from isinstance checks
removed old addresses052.py sample
renamed res.py to resource.py
replaced the use of __getattr__ and __setattr__ in Font,
StatusBar, and Widget classes with property(), so those
classes and components no longer have restrictive
attribute access
this change also eliminated the need for the
_createAttributes and _getAttributeNames methods
changed to wx.Frame for runtime tools on Windows to make
them the same across platforms
removed conditional code check PyCrust since PyCrust
is a standard part of wxPython now
added createStatusBar method to Background and
CustomDialog classes so applications can override that
method if they want to use a more complex StatusBar
made statusbar.StatusBar a direct subclass of wx.StatusBar
renamed PythonCardApp to Application
refactored config.py to configuration.py
removed PythonCardObject and all references to it
removed ObjectMap and all references to it
removed ObjectLookup and all references to it
changed on_openBackground to on_initialize
renamed pom.py to component.py
added test.py
added timer.py module and simple Timer wrapper
added deactivate event to Background
updated sound.py to use wx.Sound
changed model.py to require Python >= 2.3 and wxPython >= 2.5
converted DC methods to use tuples instead of separate
x, y and width, height args
changed wx.NULL to None
switched to wx package
from wxPython import changed to import wx
changed wx.wx style prefixes to wx. except
for wx.wxEVT constants
changed wx.wxHtmlEasyPrinting to wx.html.HtmlEasyPrinting
changed default on Message Watcher to show unused events
PythonCardPrototype package renamed to PythonCard
all references to Prototype updated in source and docs
After posting the call for grant proposals, at
http://www.python.org/psf/call-2004.html
we found that submitters still have a number of
questions. In order to encourage a public discussion
of such questions, we have created a mailing list
grants-discuss(a)python.org, whose info page is at
http://mail.python.org/mailman/listinfo/grants-discuss
People interested in the call are encourage to subscribe
to this list, and post questions and comments.
Martin v. Löwis
libgmail -- Python binding for Google's Gmail service
<http://libgmail.sf.net/>
The `libgmail` project is a pure Python binding to provide access to
Google's Gmail web-mail service.
The library currently ships with a demonstration utility to archive
messages from a Gmail account into mbox files, suitable for importing
into a local email client.
Also includes a demonstration utility that acts as a SMTP proxy to
allow mail to be sent from any standard mail client that uses SMTP
(e.g. Mail.app, Mozilla etc). (Now handles attachments.)
New demonstration utility acts as a POP3 proxy to allow mail to be
retrieved from any standard mail client that uses POP3 (e.g. Mail.app,
Mozilla etc).
Features demonstration utility to provide access to Gmail message
attachments via a download-only FTP proxy--this allows retrieval of
suitably marked attachments by a standard FTP client. Utilize more
of your Gmail space!
License: GPL 2.0 (gmailftpd.py/gmailpopd.py are dual licensed with PSF)
Major changes since 0.0.7:
* Fixed login to work again after it was broken by a Gmail change.
* Added trash/delete message thread & trash/delete single message
functionality. (By request.)
* POP3 proxy server demo. (By request.)
* Added `GmailLoginFailure` exception to enable tidier handling of
login failures (which could be bad username/password or a Gmail
change).
<p><a href="http://libgmail.sf.net/">libgmail 0.0.8</a> - The
`libgmail` project is a pure Python binding to provide access to
Google's Gmail web-mail service; includes SMTP, POP3 & FTP
proxies. (23-Aug-04)</p>
Hi,
a new version of PyCrash is released with new features and improvement.You
can
download it at:
http://sourceforge.net/project/showfiles.php?group_id=98026&package_id=1110…
What's new in this release:
* Many bug-fixes expecially related to the Win32 platform. Special thanks to
Gary Herron and Michele Petrazzo for their support.
Enjoy!
_______________________________________________________________________
About PyCrash Project:
PyCrash is a Run-Time Exception Dumper which handles uncaught exceptions
during the execution of Python programs and collects information about
the program context. PyCrash can be very useful in report bug information,
because the programmer can easily analyse the program execution context
of the crashed application.
Major information collected by PyCrash in the crash dump is:
- Information about operating system, Python and Python Standard
Library version and general information about the program that is
crashed (e.g., program name and version, time at witch program
started and ended, and so on)
- Information about the uncaught exceptions, like the exception type,
the context (namely method name) in which the exception occurred and
the exception value
- General information about variables state
- Information about the stack of each thread, like the list of stack
frames, the variables value in each stack frame, and so on
- General information about source code, like variable and function
position in source file that can be useful for the programmer to find
quickly bugs in source tree
The format of the crash dump file generated by PyCrash is XML, so the
programmer can easily read this file to understand why the program is
crashed.
Now, is also available a GUI browser, named PyCrash Viewer, which allows
developers to analyze quickly and easily PyCrash crash dump files in a
graphical manner.
* Starting from next version, we'll try to document all the PyCrash API
More information can be found at:
http://www.pycrash.org
Thanks!
<P><A HREF="http://www.pycrash.org";>PyCrash 0.4pre3</A> - a crash
handler for Python written applications. (21-08-04)</P>
Several new scripts and module updates over at pythonutils.
Visit http://www.voidspace.org.uk/atlantibots/pythonutils.html
*NEW* includer.py Version 1.0.0
Adds an INCLUDE command to python scripts for including other modules
into them. Lots of nifty features including recursive include, incdir
command, will remove relevant import statements and anything under the
'if __name__ ==...' line from included scripts etc....
Useful for distributing modules with several dependencies as a single
file. The features mean that it's possible to test a module using normal
import statements (of the type from module import....). When you run
includer.py, the INCLUDE command causes your included script (minus the
test code under a 'if __name__ ==...' line) to be added in *and* the
import statements are removed.
Using ##INCLUDE is the equivalent of 'from module import *' - avoiding
namespace pollution is up to you.
python includer.py infilename outfilename
Easy hey !
*NEW* proxycleaner.py Version 1.0.0
*NEW* approxClientproxy.py Version 0.1.0
*UPDATED* approx.py Version 0.4.0
approx.py is a cgiproxy for use by those in restricted internet
environments. (Work, internet cafes, colleges, libraries, restrictive
regimes etc). It is still at beta stage but functional. Cookie handling
with ClientCookie now works although support for multiple users and
cookie management will be added. Authentication and POST methods are the
next issues to work on. DEBUG mode added.
proxycleaner.py will 'clean' webpages that have been fetched through
approx or the James Marshall perl cgiproxy. Both these scripts modify
URLs a pages are fetched through them - this script undoes the modification.
approxClientproxy.py is a client script that works in conjunction with
approx.py - it runs on your machine. Instead of having approx modify
pages you can point your browser at approxClientproxy which handles the
communication between itself and approx. Things like javascript, which
approx can't modify, work through aPc which transparently mangles *all*
access to go through approx... At the moment it's a crude hack of
TCPwatch by the Zope corp.. but it works fine and will only improve.
Allows transparent unrestricted http access in a restricted environment.
*UPDATE* ConfigObj Version 3.2.0
Removed the charmap 'function' that eliminated unicode errors by
stamping on unicode. Unicode problems will now raise an exception. If
this causes you problems let me know (with examples if possible) and
I'll work on the unicode issues.
The test for list type entries is not an 'isinstance' test but a
'hasattr' test (append is the attribute tested for). This isn't
ubiquitous though.
Added the 'stringify' keyword - including changes to the listquote
module for this (which is also now a bit faster).
fullconfigobj.py is now built using includer.py
Uses a newer version of caseless than the previous distribution.
Changed license text.
*UPDATE* listquote Version 1.1.0
Added handling for non-string elements in elem_quote (optional).
Replaced some old += with lists and ''.join() for speed improvements...
Using basestring and hasattr('__getitem__') tests instead of
isinstance(list) and str in a couple of places.
Changed license text.
Made the tests useful.
*NEW* http_test.py Version 1.2.0
Now at http://www.voidspace.org.uk/atlantibots/recipebook.html (was
previously just online in the Python Cookbook)
A CGI script that will fetch a URL and display all server headers, saved
cookies, the first part of the content (as raw HTML) *and* all the CGI
environment variables. Some authentication code as well. Very useful for
debugging in various situation or as an illustration of several aspects
of basic CGI.
# Copyright Michael Foord
# Free to use, modify and relicense.
# No warranty express or implied for the accuracy, fitness to purpose or
otherwise for this code....
# Use at your own risk !!!
# E-mail michael AT foord DOT me DOT uk
# Maintained at www.voidspace.org.uk/atlantibots/pythonutils.html
--
http://www.Voidspace.org.uk
The Place where headspace meets cyberspace. Online resource site - covering science, technology, computing, cyberpunk, psychology, spirituality, fiction and more.
---
http://www.Voidspace.org.uk/atlantibots/pythonutils.html
Python utilities, modules and apps.
Including Nanagram, Dirwatcher and more.
---
http://www.fuchsiashockz.co.ukhttp://groups.yahoo.com/group/void-shockz
---
Everyone has talent. What is rare is the courage to follow talent
to the dark place where it leads. -Erica Jong
Ambition is a poor excuse for not having sense enough to be lazy.
-Milan Kundera
SCons is a software construction tool (build tool, or make tool) written
in Python. It is based on the design which won the Software Carpentry
build tool competition in August 2000.
Version 0.96 of SCons has been released and is available for download
from the SCons web site:
http://www.scons.org/
Or through the download link at the SCons project page at SourceForge:
http://sourceforge.net/projects/scons/
RPM and Debian packages and a Win32 installer are all available, in
addition to the traditional .tar.gz and .zip files.
WHAT'S NEW IN THIS RELEASE?
IMPORTANT: Release 0.96 contains the following interface changes:
- All Builder calls now return a *list* of Nodes, even when the Builder
only builds one file. This may require SConscript file changes if
you were manipulating the return values from Builders.
- The SConsignFile() function now uses a different internal database
format by default. This will cause a rebuild when you upgrade to
0.96 unless you modify your SConsignFile() call.
- The internal format of .sconsign files has been changed. The change
was coded to be backwards-compatible, but there might be corner
cases that cause warnings about "ignoring corrupt .sconsign files"
and rebuilds when you use SCons 0.96 for the first time in an
already-built tree.
- The scan_check function that can be supplied to a custom Scanner now
must take two arguments, the Node to be checked and a construction
environment. It previously only used the Node as an argument.
- The internal "node_factory" and "scanner" keyword arguments have
been removed from the Builder() function, in favor of separate
"target_factory," "source_factory," "target_scanner" and
"source"scanner" keywords, which are now documented.
- The Scanner add_skey() function has been dropped in favor of using
construction variables for the lists of file suffixes known to
a Scanner.
- File name extensions that contain all digits are now assumed to
be version numbers and treated as part of the file basename.
- The env.Append() and env.Prepend() methods have been changed to
behave like the rest of Python when either argument is a UserList.
See the release notes for more information about these changes.
This release adds the following new features:
- A new --debug=explain option tells SCons to report the reason(s)
why it thinks it must rebuild something.
- New Moc() and Uic() Builders provide more explicit control over
Qt builds, plus new construction variables to control them:
$QT_AUTOSCAN, $QT_DEBUG, $QT_MOCCXXPREFIX, $QT_MOCCXXSUFFIX,
$QT_MOCHPREFIX, $QT_MOCHSUFFIX, $QT_UICDECLPREFIX, $QT_UICDECLSUFFIX,
$QT_UICIMPLPREFIX, $QT_UICIMPLSUFFIX and $QT_UISUFFIX.
- Support for Fortran 90 and Fortran 95 has been added.
- The newer "ifort" versions of the Intel Fortran Compiler for Linux
are now supported.
- New functions have been added to return platform-independent Actions
that Chmod(), Copy(), Delete(), Mkdir(), Move() and Touch() files
and/or directories.
- A new Execute() function can now execute Actions directly at
SConscript-read time.
- A new $RPATH variable has been added that specifies a list of
directories for the GNU and IRIX linkers to search for shared
libraries.
- New $CPPSUFFIXES, $DSUFFIXES, $FORTRANSUFFIXES and $IDLSUFFIXES
variables have been added that make it easier to arrange for
additional file suffixes to be scanned by the default Scanners.
- A new Flatten() function can be used to turn nested lists of Nodes
(or other arguments) into a flat list.
- A new --debug=presub option prints the commands to be executed before
their construction variables are expanded.
- A new .win32 Node attribute will expand file names with Windows
backslash path separators on any system.
- A new ARGLIST variable makes it possible to fetch keyword=value
arguments in the order specified on the command line.
- Support has been added for the .dylib shared library suffix
and -dynamiclib linker option on Mac OS X (darwin).
This release enhances the following existing features:
- Environment override keywords can now be passed to the Command()
Builder.
- An emitter argument to a Builder() can now be a list of emitters
that will be called in sequence.
- The Java() Builder can now take more than one source directory.
- Scanners can now use suffix lists from construction variable
expansions.
- ParseConfig() now recognizes the -Wa, -Wl, -Wp and -pthread flags
and adds them to the appropriate variable(s).
- Dir (directory) Nodes can now be be created with user-specified
Builder objects.
- The SConf.CheckLib() method can now search a list of libraries.
- The env.WhereIs() method now takes a "reject" argument to weed out
specific path names.
- When calling Builders, SCons now issues a warning upon use of
the keywords "targets" and "sources", which are virtually always
typographic errors that otherwise silently (and confusingly) turn
into construction variable overrides.
- The $ASCOM, $ASPPCOM, $LINKCOM, $SHLINKCOM, $ARCOM, $LEXCOM and
$YACCCOM variables all have wrapper Actions by default. This makes
it easier to replace the default print behavior with a custom
strfunction().
- Individual tools that create libraries now override the default
$LIBPREFIX and $LIBSUFFIX values set by the platform. This makes
it easier to use Microsoft Visual Studio tools on a CygWin platform.
- If Visual Studio is installed, SCons now assumes its C/C++ compiler,
its linker and its MIDL compiler are available, too.
- SCons now searches for the ICL license file path name in the external
environment and the registry before resorting to the hard-coded
path name.
- When using Visual Studio, SCons now generates the PDB files at link
time, not compile time.
- Dependency tracking has been modified to eliminate spurious circular
dependencies in certain corner cases involving generated header
files, and to avoid rebuilding generated .h files when a #included
.h file changes.
- The internal Task.make_ready() method now creates a list of the
out-of-date Nodes for the task for use by the wrapping interface.
- A new single_source keyword argument when creating a Builder enforces
a single source file on calls to that Builder.
The following fixes have been added:
- The CacheDir() directory is now created if it doesn't exist.
- Construction variables can now be substituted in $LIBS expansions,
and $LIBS expansions now properly ignore null library values.
- Construction variables that can be searched for libraries now remove
.dll files from the list before feeding the list to Win32 compilers.
- All *PATH variables can now expand to include Nodes.
- The name of a Scanner.Classic instance is now initialized correctly.
- SCons now handles dangling symlinks without generating a stack trace.
- env.SourceCode() now works properly when called with an individual
file name or Node, not just with a directory name or Node.
- SCons now handles the lack of an external PATH environment variable.
- Use of $MSVS_IGNORE_IDE_PATHS was broken in 0.95 and is now fixed.
- The Command() global function now works properly with string actions;
this was broken in 0.95.
- A bug introduced in 0.95 in building shared libraries under MinGW
has been fixed.
- SCons now handles null entries (None or '') in tool lists or CPPPATH.
- SCons now handles exceptions in thread-safe ways, according to
Pythonic standards.
- Error messages have been improved when the evaluation of a
construction variable expansion yields a Python syntax error.
- File names on command lines are now escaped properly when the
expansion is concatenated with another string.
Performance has been improved as follows:
- Node creation has been sped up when calling a Builder by comparing
whether environments are the same object, not whether they have
equivalent construction variables.
- File system Nodes now cache their generated string values after
we've finished reading the SConscript() files.
- Node lookup has been made slightly more efficient.
- Deleting parents' implicit dependencies after a Node has been built
has been made more efficient.
The documentation has been improved:
- The User's Guide has had the following chapters and sections added:
A chapter describing how to install Python and SCons; a section
describing the declarative nature of SCons functions in SConscript
files; a chapter describing File and Directory Nodes and the return
values from Builders; a chapter describing the SConf (Autoconf-like)
functionality; a chapter describing Java builds.
- The early chapters of the User's Guide have been reorganized to
better explain concepts for new users (based on input from Robert
P. J. Day, many thanks).
- User's Guide fixes: the signatures of the various example *Options()
calls; triple-quote properly a multi-line Split() example.
- Man page additions: an example and explanation of how to use "tools =
['default', ..." when creating a construction environment; a section
describing File and Directory Nodes and some of their attributes
and methods; a section describing use of the target_factory and
source_factory keyword arguments when creating Builder objects.
- Man page fixes: formatting typos, misspellings, fix a bad example.
ABOUT SCONS
Distinctive features of SCons include:
- a global view of all dependencies; no multiple passes to get
everything built properly
- configuration files are Python scripts, allowing the full use of a
real scripting language to solve difficult build problems
- a modular architecture allows the SCons Build Engine to be
embedded in other Python software
- the ability to scan files for implicit dependencies (#include files);
- improved parallel build (-j) support that provides consistent
build speedup regardless of source tree layout
- use of MD5 signatures to decide if a file has really changed; no
need to "touch" files to fool make that something is up-to-date
- easily extensible through user-defined Builder and Scanner objects
- build actions can be Python code, as well as external commands
An scons-users mailing list is available for those interested in getting
started using SCons. You can subscribe at:
http://lists.sourceforge.net/lists/listinfo/scons-users
Alternatively, we invite you to subscribe to the low-volume
scons-announce mailing list to receive notification when new versions of
SCons become available:
http://lists.sourceforge.net/lists/listinfo/scons-announce
ACKNOWLEDGEMENTS
Special thanks to Chad Austin, Charles Crain, Tom Epperly, Ralf
W. Grosse-Kunstleve, Jonathan Gurley, Bob Halley, Chris Hoeppler, James
Juhasz, Chris Murray, Gary Oberbrunner, Simon Perkins, Kevin Quick,
Anthony Roach, sam th and Christoph Wiedemann for their contributions
to this release.
On behalf of the SCons team,
--SK
Hi,
I just wanted to let y'all know that the Python Success Stories
collection has nine new additions:
http://www.pythonology.com/success
These 28 stories include significant testimonials that can make
it easier to sell technical decision-makers (i.e., your boss)
on Python.
Whether you're building an online singles community or an air
traffic control system -- be sure to check it out!
Also In Print
-------------
Twelve stories, including the new ones, have been included in the
second O'Reilly Python Success Stories booklet, which recently went
into print and should be available at O'Reilly exhibit booths at
conferences.
Thanks,
Stephan Deibel
--
Wingware
Wing IDE for Python
Advancing Software Development
www.wingware.com
Hi,
I wanted to extract the meta-data from an encrypted/protected PDF file
and could not find any Python scripts to do this. So, I decided to
write something myself, the result follows.
This demonstration utility requires the `pdftools` files from
<http://www.boddie.org.uk/david/Projects/Python/pdftools/> but the
decryption functions themselves should be usable with other Python PDF
libraries.
Documentation is marginal and all I can say is that worked on the
three PDF files I tested it on... :-)
--Phil.
P.S. The usual Usenet-mangling warning applies--yeah, I know--I should
put it up on a web site somewhere... :-)
#!/usr/bin/python
#
# Decrypt PDF Info
#
# Decrypts PDF files and displays meta-data associated with them. (If
the
# file isn't encrypted the information is displayed as is.)
#
# The results are similar to xpdf's `pdfinfo` utility.
#
# It should be possible to decrypt all of the objects contained
# in the PDF, but this only reads the Document Information Dictionary.
#
# (Note: All the PDF handling is provided by `pdftools`, this just
adds
# the ability to deal with encrypted PDF files.)
#
# Requires:
# + pdftools
# <http://www.boddie.org.uk/david/Projects/Python/pdftools/>
#
# Based on:
# + `pdfdecrypt.pl`
# <http://www-2.cs.cmu.edu/~dst/Adobe/Gallery/pdfdecrypt.pl>
[PDFPL]
#
# Incorporates:
# + RC4 from CipherSaber implementation by Ka-Ping Yee
<ping(a)lfw.org>
# <http://www.xs4all.nl/~cg/ciphersaber/comp/python.txt>
#
# References:
# + <http://www-2.cs.cmu.edu/~dst/Adobe/Gallery/anon21jul01-pdf-encryption.txt>
[PDFE]
#
# Author:
# follower(a)myrealbox.com (Standing on *many* shoulders...)
#
import sys
import md5
import struct
from pdftools import PDFdocument
def arcfour(input, key):
"""
Perform the ARCFOUR (RC4) algorithm on a given input list of bytes
with
a key given as a list of bytes, and return the output as a list of
bytes.
(From CipherSaber implementation by Ka-Ping Yee <ping(a)lfw.org>
<http://www.xs4all.nl/~cg/ciphersaber/comp/python.txt>)
"""
i, j, state = 0, 0, range(256)
for i in range(256):
j = (j + state[i] + key[i % len(key)]) % 256
state[i], state[j] = state[j], state[i]
i, j, output = 0, 0, []
for byte in input:
i = (i + 1) % 256
j = (j + state[i]) % 256
state[i], state[j] = state[j], state[i]
n = (state[i] + state[j]) % 256
output.append(byte ^ state[n])
return output
_passwordPad = [
0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41,
0x64, 0x00, 0x4e, 0x56, 0xff, 0xfa, 0x01, 0x08,
0x2e, 0x2e, 0x00, 0xb6, 0xd0, 0x68, 0x3e, 0x80,
0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a]
passwordPad = "".join([chr(b) for b in _passwordPad])
def calculateFileKey(fileId, ownerHash, userHash, permissions,
userPassword = ""):
"""
Calculates the file key for the document as described in
references
(see [PDFE] and [PDFPL]).
"""
md = md5.new()
md.update((userPassword + passwordPad)[:32])
md.update(ownerHash)
md.update(struct.pack("<L", permissions))
md.update(fileId)
fileKey = md.digest()[:5]
return fileKey
def calculateObjectKey(fileKey, objectNumber, generationNumber):
"""
Calculates the key for the object as described in references
(see [PDFE] and [PDFPL]).
"""
md = md5.new()
md.update(fileKey)
md.update(struct.pack("<L", objectNumber)[:3])
md.update(struct.pack("<L", generationNumber)[:2])
objectKey = md.digest()[:10]
return objectKey
class NotEncryptedException(Exception):
"""
The supplied PDF document is not encrypted.
"""
KEY_OWNER_HASH = 'O'
KEY_USER_HASH = 'U'
KEY_PERMISSIONS = 'P'
def getFileKey(doc, userPassword = ""):
"""
Extracts the information required to calculate the file key
from the supplied PDF document.
In most cases `userPassword` can be left empty.
"""
fileId = doc.trailer_dict['ID'][0] # Is the ID always repeated?
try:
encryptDict = doc.dereference(doc.trailer_dict['Encrypt'])
except KeyError:
raise NotEncryptedException
# TODO: Check encryption version is ok. (filter/standard/v/1/r/2)
ownerHash = encryptDict[KEY_OWNER_HASH]
userHash = encryptDict[KEY_USER_HASH]
# `permissions` should be "four-byte integer, LSB first."
permissions = encryptDict[KEY_PERMISSIONS]
fileKey = calculateFileKey(fileId, ownerHash, userHash,
permissions,
userPassword)
# Sanity check user password
assert(decrypt(userHash, fileKey) == passwordPad)
return fileKey
def decrypt(text, key):
"""
Decrypts the supplied object (as a string) with the supplied key.
Returns "plain text" form of object as a string.
"""
return "".join([chr(b)
for b in arcfour(map(ord, text), map(ord, key))])
def showDocumentInfo(doc, fileKey):
"""
Displays the content of the (optionally encrypted) Document
Information
Dictionary for the supplied PDF document.
"""
infoDictRef = doc.trailer_dict['Info']
objectNumber = infoDictRef.obj
generationNumber = infoDictRef.gen
infoDict = doc.dereference(infoDictRef)
objectKey = calculateObjectKey(fileKey, objectNumber,
generationNumber)
for field, encryptedValue in infoDict.iteritems():
if fileKey:
value = decrypt(encryptedValue, objectKey)
else:
value = encryptedValue
print "%s: %s" % (field, value)
if __name__ == "__main__":
try:
filename = sys.argv[1]
except IndexError:
raise SystemExit("Usage %s <filename.pdf>" % sys.argv[0])
doc = PDFdocument(filename)
try:
fileKey = getFileKey(doc)
except NotEncryptedException:
fileKey = ""
showDocumentInfo(doc, fileKey)
Hi,
I wanted to extract the meta-data from an encrypted/protected PDF file
and could not find any Python scripts to do this. So, I decided to
write something myself, the result follows.
This demonstration utility requires the `pdftools` files from
<http://www.boddie.org.uk/david/Projects/Python/pdftools/> but the
decryption functions themselves should be usable with other Python PDF
libraries.
Documentation is marginal and all I can say is that worked on the
three PDF files I tested it on... :-)
--Phil.
P.S. The usual Usenet-mangling warning applies--yeah, I know--I should
put it up on a web site somewhere... :-)
#!/usr/bin/python
#
# Decrypt PDF Info
#
# Decrypts PDF files and displays meta-data associated with them. (If
the
# file isn't encrypted the information is displayed as is.)
#
# The results are similar to xpdf's `pdfinfo` utility.
#
# It should be possible to decrypt all of the objects contained
# in the PDF, but this only reads the Document Information Dictionary.
#
# (Note: All the PDF handling is provided by `pdftools`, this just
adds
# the ability to deal with encrypted PDF files.)
#
# Requires:
# + pdftools
# <http://www.boddie.org.uk/david/Projects/Python/pdftools/>
#
# Based on:
# + `pdfdecrypt.pl`
# <http://www-2.cs.cmu.edu/~dst/Adobe/Gallery/pdfdecrypt.pl>
[PDFPL]
#
# Incorporates:
# + RC4 from CipherSaber implementation by Ka-Ping Yee
<ping(a)lfw.org>
# <http://www.xs4all.nl/~cg/ciphersaber/comp/python.txt>
#
# References:
# + <http://www-2.cs.cmu.edu/~dst/Adobe/Gallery/anon21jul01-pdf-encryption.txt>
[PDFE]
#
# Author:
# follower(a)myrealbox.com (Standing on *many* shoulders...)
#
import sys
import md5
import struct
from pdftools import PDFdocument
def arcfour(input, key):
"""
Perform the ARCFOUR (RC4) algorithm on a given input list of bytes
with
a key given as a list of bytes, and return the output as a list of
bytes.
(From CipherSaber implementation by Ka-Ping Yee <ping(a)lfw.org>
<http://www.xs4all.nl/~cg/ciphersaber/comp/python.txt>)
"""
i, j, state = 0, 0, range(256)
for i in range(256):
j = (j + state[i] + key[i % len(key)]) % 256
state[i], state[j] = state[j], state[i]
i, j, output = 0, 0, []
for byte in input:
i = (i + 1) % 256
j = (j + state[i]) % 256
state[i], state[j] = state[j], state[i]
n = (state[i] + state[j]) % 256
output.append(byte ^ state[n])
return output
_passwordPad = [
0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41,
0x64, 0x00, 0x4e, 0x56, 0xff, 0xfa, 0x01, 0x08,
0x2e, 0x2e, 0x00, 0xb6, 0xd0, 0x68, 0x3e, 0x80,
0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a]
passwordPad = "".join([chr(b) for b in _passwordPad])
def calculateFileKey(fileId, ownerHash, userHash, permissions,
userPassword = ""):
"""
Calculates the file key for the document as described in
references
(see [PDFE] and [PDFPL]).
"""
md = md5.new()
md.update((userPassword + passwordPad)[:32])
md.update(ownerHash)
md.update(struct.pack("<L", permissions))
md.update(fileId)
fileKey = md.digest()[:5]
return fileKey
def calculateObjectKey(fileKey, objectNumber, generationNumber):
"""
Calculates the key for the object as described in references
(see [PDFE] and [PDFPL]).
"""
md = md5.new()
md.update(fileKey)
md.update(struct.pack("<L", objectNumber)[:3])
md.update(struct.pack("<L", generationNumber)[:2])
objectKey = md.digest()[:10]
return objectKey
class NotEncryptedException(Exception):
"""
The supplied PDF document is not encrypted.
"""
KEY_OWNER_HASH = 'O'
KEY_USER_HASH = 'U'
KEY_PERMISSIONS = 'P'
def getFileKey(doc, userPassword = ""):
"""
Extracts the information required to calculate the file key
from the supplied PDF document.
In most cases `userPassword` can be left empty.
"""
fileId = doc.trailer_dict['ID'][0] # Is the ID always repeated?
try:
encryptDict = doc.dereference(doc.trailer_dict['Encrypt'])
except KeyError:
raise NotEncryptedException
# TODO: Check encryption version is ok. (filter/standard/v/1/r/2)
ownerHash = encryptDict[KEY_OWNER_HASH]
userHash = encryptDict[KEY_USER_HASH]
# `permissions` should be "four-byte integer, LSB first."
permissions = encryptDict[KEY_PERMISSIONS]
fileKey = calculateFileKey(fileId, ownerHash, userHash,
permissions,
userPassword)
# Sanity check user password
assert(decrypt(userHash, fileKey) == passwordPad)
return fileKey
def decrypt(text, key):
"""
Decrypts the supplied object (as a string) with the supplied key.
Returns "plain text" form of object as a string.
"""
return "".join([chr(b)
for b in arcfour(map(ord, text), map(ord, key))])
def showDocumentInfo(doc, fileKey):
"""
Displays the content of the (optionally encrypted) Document
Information
Dictionary for the supplied PDF document.
"""
infoDictRef = doc.trailer_dict['Info']
objectNumber = infoDictRef.obj
generationNumber = infoDictRef.gen
infoDict = doc.dereference(infoDictRef)
objectKey = calculateObjectKey(fileKey, objectNumber,
generationNumber)
for field, encryptedValue in infoDict.iteritems():
if fileKey:
value = decrypt(encryptedValue, objectKey)
else:
value = encryptedValue
print "%s: %s" % (field, value)
if __name__ == "__main__":
try:
filename = sys.argv[1]
except IndexError:
raise SystemExit("Usage %s <filename.pdf>" % sys.argv[0])
doc = PDFdocument(filename)
try:
fileKey = getFileKey(doc)
except NotEncryptedException:
fileKey = ""
showDocumentInfo(doc, fileKey)